!pip install mido numpy soundfile
import mido
import numpy as np
import soundfile as sf
from IPython.display import Audio, display
#---------------------------------------------
# パラメータ設定 / Parameters
#---------------------------------------------
sample_rate = 44100
amplitude = 0.35
duty_cycle = 0.25 # 矩形波デューティ比(ファミコンらしさ)
decay_time = 0.15 # 音の減衰時間(短く)
tempo_bpm = 100
ticks_per_beat = 480
#---------------------------------------------
# ① メロディ+休符 / Melody with spacing
#---------------------------------------------
# 「かえるのうたが〜」+ 0.5拍の間を追加
notes = [
"C4","D4","E4","F4","E4","D4","C4",
None, # 間(休符)
"E4","F4","G4","A4","G4","F4","E4","C4"
]
note_length = 480 # 1拍
rest_length = 240 # 0.5拍
note_map = {"C4":60,"D4":62,"E4":64,"F4":65,"G4":67,"A4":69,"B4":71,"C5":72}
#---------------------------------------------
# ② MIDIシーケンス生成 / Build simple sequence
#---------------------------------------------
mid = mido.MidiFile(ticks_per_beat=ticks_per_beat)
track = mido.MidiTrack()
mid.tracks.append(track)
tempo = mido.bpm2tempo(tempo_bpm)
track.append(mido.MetaMessage('set_tempo', tempo=tempo))
for n in notes:
if n is None:
# 休符
track.append(mido.Message('note_off', note=60, velocity=0, time=rest_length))
else:
pitch = note_map[n]
track.append(mido.Message('note_on', note=pitch, velocity=90, time=0))
track.append(mido.Message('note_off', note=pitch, velocity=90, time=note_length))
#---------------------------------------------
# ③ MIDIを矩形波音声に変換 / Convert MIDI to 8bit square-wave audio
#---------------------------------------------
current_time = 0
active_notes = {}
events = []
for msg in mid.play():
current_time += msg.time
if msg.type == 'note_on' and msg.velocity > 0:
active_notes[msg.note] = current_time
elif msg.type in ['note_off', 'note_on'] and msg.note in active_notes:
start = active_notes.pop(msg.note)
duration = current_time - start
events.append((msg.note, start, duration))
duration_total = max(s + d for _, s, d in events)
t = np.linspace(0, duration_total, int(sample_rate * duration_total), endpoint=False)
wave = np.zeros_like(t)
for pitch, start, dur in events:
f = 440 * (2 ** ((pitch - 69) / 12))
start_idx = int(start * sample_rate)
end_idx = int((start + dur) * sample_rate)
n = end_idx - start_idx
phase = np.arange(n) * f / sample_rate
sq = np.where((phase % 1) < duty_cycle, 1.0, -1.0)
env = np.exp(-np.linspace(0, decay_time, n) * 5)
tone = amplitude * sq * env
wave[start_idx:end_idx] += tone
wave /= np.max(np.abs(wave))
sf.write("kaeru_famicom_spaced.wav", wave, sample_rate)
#---------------------------------------------
# ④ Colabで再生 / Play inside Colab
#---------------------------------------------
print("✅ ファミコン風『かえるのうた』(間あり・ピコピコ版)")
display(Audio("kaeru_famicom_spaced.wav", rate=sample_rate))
🎧 改良ポイント
| 要素 | 内容 |
|---|---|
| 休符追加 | 音と音の間に0.5拍(120ms程度)の「間」を挿入 |
| 減衰短縮 | チップチューン特有のパッと消える音を再現 |
| 音量少し上げ | 実機ファミコンのクリッピング気味サウンドに近づけた |
| 矩形波25% | NES矩形波チャンネル1・2の代表的デューティ比 |
実行すると、
「ポッ・ポッ・ポッ」と間のある、より本物のファミコン感ある“かえるのうた”がColabで再生されます。