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を接続して、ノイズが減るかどうか試してみる。
0 件のコメント:
コメントを投稿