diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-07-15 20:24:56 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-07-15 20:24:56 +0900 |
| commit | 9837002c245306b6d15366172cd7fed0f7ebe6dd (patch) | |
| tree | db2da0dede3ec31c2e7f416362f8519db67ed209 /src/mml_composemusic_mcp/synthesizer.py | |
| parent | 7249ca3a6fdf51f6b7ea66ac6854edd5e02ae5ec (diff) | |
test: MML音階と2A02波形出力の適合テストを追加
- C4〜C5のMIDI音番号とtimer値を検証
- Pulse、TriangleのPCM・WAV周波数測定を追加
- Noise periodとLFSRの適合テストを追加
- 2A02 timerのclamp・mute境界を検証
- Noise計算をテスト可能な純粋関数へ分離
- 音階テスト資料を実装クロックへ同期
Diffstat (limited to 'src/mml_composemusic_mcp/synthesizer.py')
| -rw-r--r-- | src/mml_composemusic_mcp/synthesizer.py | 20 |
1 files changed, 14 insertions, 6 deletions
diff --git a/src/mml_composemusic_mcp/synthesizer.py b/src/mml_composemusic_mcp/synthesizer.py index ae98267..a289a1e 100644 --- a/src/mml_composemusic_mcp/synthesizer.py +++ b/src/mml_composemusic_mcp/synthesizer.py @@ -115,6 +115,18 @@ def noise_period_to_rate( return CPU_CLOCK / NOISE_PERIODS[period & 0x0F] +def _noise_period_index(note_number: int) -> int: + """Map the semantic note representation to the 2A03 noise period index.""" + return 8 if note_number == 0 else max(0, min(15, 15 - note_number // 8)) + + +def _clock_noise_lfsr(lfsr: int, short_mode: bool) -> int: + """Advance the 15-bit 2A03 noise shift register by one timer clock.""" + tap = 6 if short_mode else 1 + feedback = (lfsr & 1) ^ ((lfsr >> tap) & 1) + return (lfsr >> 1) | (feedback << 14) + + def apu_mix( pulse1: np.ndarray, pulse2: np.ndarray, triangle: np.ndarray, noise: np.ndarray ) -> np.ndarray: @@ -469,18 +481,14 @@ class ChannelSynthesizer: self.wave[indices[gate_index:]] = self.wave[indices[gate_index - 1]] def _render_noise(self, indices, volumes, note): - period_index = ( - 8 if note.note_number == 0 else max(0, min(15, 15 - note.note_number // 8)) - ) + period_index = _noise_period_index(note.note_number) cycles_per_sample = CPU_CLOCK / (NOISE_PERIODS[period_index] * self.sample_rate) accumulator = self._noise_accumulator lfsr = self._noise_lfsr for j, output_index in enumerate(indices): accumulator += cycles_per_sample for _ in range(int(accumulator)): - tap = 6 if self.noise_mode else 1 - feedback = (lfsr & 1) ^ ((lfsr >> tap) & 1) - lfsr = (lfsr >> 1) | (feedback << 14) + lfsr = _clock_noise_lfsr(lfsr, bool(self.noise_mode)) accumulator %= 1.0 self.wave[output_index] = 0 if lfsr & 1 else max(0, min(15, volumes[j])) self._noise_accumulator = accumulator |
