https://www.notyet-maker.com/2026/01/xiao-esp32s3-3.html
の続き。
code .py を使う前に、コード停止方法を整理しておく。
REPL接続中ではないので、Ctrl+Cが使えない。
REPL接続は、USBマスストレージ(CIRCUITPY)と排他になってしまう。
なので、code .pyに、
# empty
を入力&保存して止めるのが、楽だと思う。
さて、
3. ESP32S3側でクリーン音を整え、Slash寄りにする。
の続き。
Slash寄りとは
「電子的に正確すぎる要素を、すべて弱めること」
だという。
具体的には、
---
① 高域を出しすぎない
→ ギターでは本来出ない「電子的に鋭い高音」を消すこと。
→ そのまま出すと“シンセ音”になる。
② 入力をわずかに飽和させる
→ 音量の大小が、そのまま直線的に音圧にならない状態にすること。
→ 大きく弾いても、急に音が尖らない。
③ 完全に正確な周期を避ける
→ 同じ周波数・同じ波形を、毎回まったく同じ形で出さないこと。
→ 数学的に正確だと「電子音」になる。
④ ビブラートは後段で、浅く入れる
→ 音を出した直後は揺らさない。
→ しばらく鳴ってから、周波数を少しだけ動かす。
---
とのこと。
synthio で出せる「エレキギターっぽさ」の実質的な限界の
ドレミファソラシドは↓。
///
import time, math, array
import board, audiobusio, synthio
SAMPLE_RATE = 18000
AMP = 0.10
ATTACK = 0.01
DECAY = 0.20
SUSTAIN = 0.25
RELEASE = 0.25
NOTE_ON_SEC = 0.35
NOTE_GAP_SEC = 0.25
PRE_SIL_MS = 4
BROKEN_MS = 12
JITTER_CENTS = 2.0
JITTER_TAU = 0.01
DRIVE_BASE = 0.6
DRIVE_BROKEN = 1.0
ODD3_BASE = 1.0
ODD5_BASE = 0.25
ODD3_JIT = 0.08
ODD5_JIT = 0.03
PHASE_WARP_BASE = 0.00
PHASE_WARP_BROKEN = 0.18
VIB_ON = True
VIB_DELAY = 0.10
VIB_RATE = 5.0
VIB_CENTS = 5.0
UPDATE_STEP = 0.006
LP_FC_NORMAL = 4500.0
LP_FC_BROKEN = 6000.0
SAT_DRIVE = 0.6
POST_GAIN = 1.8
WAVE_LEN = 128
MAX_I16 = 32767
NOTES = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25]
i2s = audiobusio.I2SOut(board.D6, board.D3, board.D7)
synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE)
i2s.play(synth)
env = synthio.Envelope(
attack_time=ATTACK,
decay_time=DECAY,
sustain_level=SUSTAIN,
release_time=RELEASE
)
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)
if y > 1.0:
y = 1.0
elif y < -1.0:
y = -1.0
return y
def make_wave(drive, odd3, odd5, phase_warp, lp_fc):
w = array.array("h", [0] * WAVE_LEN)
lp_alpha = alpha_from_fc(lp_fc)
lp_y = 0.0
for i in range(WAVE_LEN):
phase = 2 * math.pi * i / WAVE_LEN
phase = phase + phase_warp * math.sin(phase)
x = math.sin(phase)
x = x + (odd3 * drive) * (x * x * x) + (odd5 * drive) * (x * x * x * x * x)
x = soft_sat(x, SAT_DRIVE)
lp_y = lp_y + lp_alpha * (x - lp_y)
z = lp_y * POST_GAIN
if z > 1.0:
z = 1.0
elif z < -1.0:
z = -1.0
w[i] = int(z * MAX_I16)
return w
def play_note(freq):
odd3 = ODD3_BASE + (rand_unit() * 2.0 - 1.0) * ODD3_JIT
odd5 = ODD5_BASE + (rand_unit() * 2.0 - 1.0) * ODD5_JIT
drive_base = DRIVE_BASE + (rand_unit() * 0.05)
drive_broken = DRIVE_BROKEN + (rand_unit() * 0.08)
wave_normal = make_wave(drive_base, odd3, odd5, PHASE_WARP_BASE, LP_FC_NORMAL)
wave_broken = make_wave(drive_broken, odd3, odd5, PHASE_WARP_BROKEN, LP_FC_BROKEN)
note = synthio.Note(frequency=freq, envelope=env, amplitude=AMP, waveform=wave_normal)
synth.press(note)
start = time.monotonic()
pre_sil = PRE_SIL_MS / 1000.0
broken = BROKEN_MS / 1000.0
broken_end = pre_sil + broken
while time.monotonic() - start < NOTE_ON_SEC:
now = time.monotonic()
t = now - start
if t < pre_sil:
note.amplitude = AMP * 0.2
note.waveform = wave_normal
elif t < broken_end:
note.amplitude = AMP
note.waveform = wave_broken
jitter = (rand_unit() * 2.0 - 1.0) * JITTER_CENTS * math.exp(-(t - pre_sil) / JITTER_TAU)
note.frequency = freq * cents_to_ratio(jitter)
else:
note.amplitude = AMP
note.waveform = wave_normal
note.frequency = freq
if VIB_ON and t > VIB_DELAY and t > broken_end:
vib = cents_to_ratio(math.sin(2 * math.pi * VIB_RATE * now) * VIB_CENTS)
note.frequency = note.frequency * vib
time.sleep(UPDATE_STEP)
synth.release(note)
time.sleep(NOTE_GAP_SEC)
while True:
for f in NOTES:
play_note(f)
///
が、エレキギターっぽくない....
ChatGPTに問いただすと、
---
synthioの上に「後段処理(アンプ+キャビ相当)」を足せると思っていた
具体的には、音が出たあとにローパスや歪み段を挿せる前提で話を進めていましたが、synthio+audiobusioにはそれを差し込む入口がありませんでした。
synthioの範囲で「Slash風の歪み」まで到達できると思っていた
実際は、synthioでできるのは「ギターを意識したシンセ音(クリーン〜軽いクランチ寄り)」までで、アンプで潰して押し出すような歪みの質感は射程外でした。
最初から synthio を使わず、RawSample(ストリーム処理)で行くべきでした。
---
という誤認をしていたらしい。
なかなか進まない。
続きは次回。
0 件のコメント:
コメントを投稿