XIAO ESP32S3でゴッドファーザーのテーマにトライ

2026年1月23日金曜日

マイコン工作

 https://www.notyet-maker.com/2026/01/xiao-esp32s3esp-audio-effects.html

の続き。


前回、Smoke on the Waterっぽい音を作ることができたので、

Slashのギターソロで有名な、ゴッドファーザーのテーマの再現に

トライしてみた。


前回と同様、コード生成は、Geminiでおこなうが、なかなかうまく

いかなかったので、ChatGPTにレビューしてもらって進めた。


それでも、なかなかうまくいかない。


ChatGPTが「設計士」だとすれば、Geminiは「現場の音響監督」として

使い分けるのが良いとのこと。


どうも音階が合わないので、Geminiに聞いた。

これがゴッドファーザーのテーマの音階らしい。


ミ・ラ・ドーーー・シ・ラー・ドー

(タ・タ・ターー・タ・ター・ター)

ラー・シー・ラー・ファ・ソ・ミーーーー

(ター・ター・ター・タ・タ・ーーー)


///

import gc

import math

import time

from array import array

import audiobusio

import board

import synthio


# --- Hardware / runtime ---

SAMPLE_RATE = 22050

I2S_BCLK = board.D6

I2S_LRCLK = board.D3

I2S_DATA = board.D7


# --- Sequencer (Godfather Main Theme: Slash Tone) ---

BEAT_SEC = 60 / 84

OCTAVE_MULT = 1.0

E_BASE = 329.63

A_BASE = 440.00

B_BASE = 493.88

C_BASE = 523.25

F_BASE = 349.23

G_BASE = 392.00

E4 = E_BASE * OCTAVE_MULT

A4 = A_BASE * OCTAVE_MULT

B4 = B_BASE * OCTAVE_MULT

C5 = C_BASE * OCTAVE_MULT

F4 = F_BASE * OCTAVE_MULT

G4 = G_BASE * OCTAVE_MULT


NOTE_SEQUENCE_ABSOLUTE = [

    (0, E4, 1),

    (1, A4, 1),

    (2, C5, 2),

    (4, B4, 1),

    (5, A4, 2),

    (7, C5, 1),

    (8, A4, 1),

    (9, B4, 1),

    (10, A4, 1),

    (11, F4, 1),

    (12, G4, 1),

    (13, E4, 3),

]

LOOP_STEPS = 16

RELEASE_TAIL_SEC = 0.05

NOTE_VELOCITY = 0.6


# --- Sound layer (synthio) ---

SIMPLE_TUNING_WAVE = True

AMP_MIN = 0.02

AMP_MAX = 0.04

ATTACK = 0.003

DECAY = 0.40

SUSTAIN = 0.85

RELEASE = 0.12


UPDATE_STEP = 0.0


LP_FC_NORMAL = 4800.0

LP_FC_DAMP = 1500.0

SAT_DRIVE = 0.6

POST_GAIN = 0.60

CAB_FC = 1800.0

CAB_CUT = 0.55

CAB_MID_DRIVE = 0.30


WAVE_LEN = 128

MAX_I16 = 32767

SUSTAIN_AMP_BOOST = 1.0

AMP_SOFT_CLIP = 0.10

SUSTAIN_BRIGHT_MIX = 0.36

SUSTAIN_DARK_MIX = 0.12

SUSTAIN_WOBBLE_MAX_CENTS = 0.0

VIBRATO_START_MS = 999999

VIBRATO_RATE_HZ = 3.3

VEL_BINS = (0.35, 0.65, 1.0)

HARMONIC_PROFILE_GAIN = 0.28

HARMONIC_PROFILE = ((2, -0.22), (3, 0.30), (4, -0.18), (5, 0.20), (6, -0.12))


DRIVE_BASE = 0.5

ODD3_BASE = 0.65

ODD5_BASE = 0.18

PHASE_WARP_BASE = 0.00


# --- Noise burst layer (synthio note) ---

rng = 1


def rand_unit():

    global rng

    rng = (rng * 1103515245 + 12345) & 0x7FFFFFFF

    return rng / 0x7FFFFFFF


def cents_to_ratio(cents):

    return 2 ** (cents / 1200.0)


def alpha_from_fc(fc):

    dt = 1.0 / SAMPLE_RATE

    rc = 1.0 / (2.0 * math.pi * fc)

    return dt / (rc + dt)


def soft_sat(x, drive):

    y = x + drive * (x * x * x)

    return max(-1.0, min(1.0, y))


def clamp_i16(value):

    return max(-MAX_I16, min(MAX_I16, int(value)))


def soft_clip_amp(value):

    if value <= AMP_SOFT_CLIP:

        return value

    excess = value - AMP_SOFT_CLIP

    return AMP_SOFT_CLIP + (excess / (1.0 + (excess * 8.0)))


def mix_waves(wave_a, wave_b, mix):

    w = array("h", [0] * WAVE_LEN)

    for i in range(WAVE_LEN):

        w[i] = clamp_i16((wave_a[i] * (1.0 - mix)) + (wave_b[i] * mix))

    return w


def vel_key(value):

    return min(VEL_BINS, key=lambda x: abs(x - value))


WAVE_CACHE = {}

SIMPLE_WAVES = None


def make_sine_wave():

    w = array("h", [0] * WAVE_LEN)

    for i in range(WAVE_LEN):

        w[i] = clamp_i16(math.sin(2.0 * math.pi * i / WAVE_LEN) * MAX_I16)

    return w


SINE = make_sine_wave()


def get_simple_waves():

    global SIMPLE_WAVES

    if SIMPLE_WAVES is None:

        wave = make_sine_wave()

        SIMPLE_WAVES = (wave, wave, wave, wave, wave)

    return SIMPLE_WAVES


def get_waves(velocity):

    if SIMPLE_TUNING_WAVE:

        return get_simple_waves()

    key = vel_key(velocity)

    if key in WAVE_CACHE:

        return WAVE_CACHE[key]

    drive = DRIVE_BASE + (0.5 * key)

    wave_normal = make_wave(drive, ODD3_BASE, ODD5_BASE, PHASE_WARP_BASE, LP_FC_NORMAL)

    wave_attack = make_wave(drive * 1.3, ODD3_BASE * 1.25, ODD5_BASE * 1.15, PHASE_WARP_BASE, LP_FC_NORMAL)

    wave_damp = make_wave(drive, ODD3_BASE, ODD5_BASE, PHASE_WARP_BASE, LP_FC_DAMP)

    waves = (

        wave_normal,

        wave_attack,

        mix_waves(wave_normal, wave_attack, SUSTAIN_BRIGHT_MIX),

        mix_waves(wave_normal, wave_attack, SUSTAIN_DARK_MIX),

        wave_damp,

    )

    WAVE_CACHE[key] = waves

    return waves


def make_wave(drive, odd3, odd5, phase_warp, lp_fc):

    w = array("h", [0] * WAVE_LEN)

    lp_alpha, lp_y, cab_alpha, cab_y = alpha_from_fc(lp_fc), 0.0, alpha_from_fc(CAB_FC), 0.0

    for i in range(WAVE_LEN):

        phase = 2.0 * math.pi * i / WAVE_LEN

        x = math.sin(phase + phase_warp * math.sin(phase))

        for h, weight in HARMONIC_PROFILE:

            x += HARMONIC_PROFILE_GAIN * weight * math.sin(h * phase)

        x = x + (odd3 * drive) * (x ** 3) + (odd5 * drive) * (x ** 5)

        x = soft_sat(x, SAT_DRIVE)

        lp_y += lp_alpha * (x - lp_y)

        cab_y += cab_alpha * (lp_y - cab_y)

        z = soft_sat((lp_y * (1.0 - CAB_CUT)) + (cab_y * CAB_CUT), CAB_MID_DRIVE) * POST_GAIN

        w[i] = clamp_i16(z * MAX_I16)

    return w


i2s = audiobusio.I2SOut(I2S_BCLK, I2S_LRCLK, I2S_DATA)

synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE)

i2s.play(synth)

gc.collect()

gc.disable()


env = synthio.Envelope(attack_time=ATTACK, decay_time=DECAY, sustain_level=SUSTAIN, release_time=RELEASE)


NOTE_AMPLITUDE = 0.07


note = synthio.Note(frequency=E4, envelope=env, amplitude=NOTE_AMPLITUDE, waveform=SINE)

note_pressed = False


event_index = 0

loop_offset = 0

start_time = time.monotonic() + 1.0

note_off_time = 0.0

current_freq = None

MIN_SLEEP_SEC = 0.001

NOTE_GAP_SEC = 0.003

next_press_time = start_time


while True:

    now = time.monotonic()

    # Catch-up scheduling: advance through missed events without drift.

    if current_freq is None:

        while True:

            next_time = start_time + (loop_offset + NOTE_SEQUENCE_ABSOLUTE[event_index][0]) * BEAT_SEC

            if now < next_time:

                break

            f, h = NOTE_SEQUENCE_ABSOLUTE[event_index][1], NOTE_SEQUENCE_ABSOLUTE[event_index][2]

            target_off = next_time + (BEAT_SEC * h)

            # Skip events that are entirely in the past.

            if now > target_off:

                event_index += 1

                if event_index >= len(NOTE_SEQUENCE_ABSOLUTE):

                    event_index = 0

                    loop_offset += LOOP_STEPS

                continue

            current_freq = f

            note_off_time = target_off

            note.frequency = current_freq

            next_press_time = max(next_time, now) + NOTE_GAP_SEC

            event_index += 1

            if event_index >= len(NOTE_SEQUENCE_ABSOLUTE):

                event_index = 0

                loop_offset += LOOP_STEPS

            break


    if current_freq is not None and now >= note_off_time:

        if note_pressed:

            synth.release(note)

            note_pressed = False

        current_freq = None


    if current_freq is not None and not note_pressed and now >= next_press_time:

        synth.press(note)

        note_pressed = True


    next_note_time = start_time + (loop_offset + NOTE_SEQUENCE_ABSOLUTE[event_index][0]) * BEAT_SEC

    next_wake = now + MIN_SLEEP_SEC

    if now < next_note_time:

        next_wake = min(next_wake, next_note_time)

    if current_freq is not None and (not note_pressed) and now < next_press_time:

        next_wake = min(next_wake, next_press_time)

    if note_pressed and now < note_off_time:

        next_wake = min(next_wake, note_off_time)

    sleep_time = max(MIN_SLEEP_SEC, next_wake - now)

    time.sleep(sleep_time)

///

で、メロディは鳴ったが、ノイジー。


ChatGPTの力を借り、

https://www.atomic14.com/videos/posts/At8PDQ3g7FQ

に、GAINピンをフローティングにしておくと出力にランダムノイズが発生する、

という記述を発見。


今度は、GAINピンに3.3Vを接続して、ノイズが減るかどうか試してみる。


このブログを検索

ブログ アーカイブ

XIAO ESP32S3+MAX98357Aでエレキギターの音にトライ

https://www.notyet-maker.com/2026/01/max98357a.html の続き。 前回、ノイズはなくせたので、今度は、エレキギターっぽい音にトライ。 YouTubeにアップロードしたところ、ジージーした音しか聴こえないが、 実物は、ゴッドファーザー...

QooQ