ym2149 0.6.0

Cycle-accurate YM2149 PSG emulator with real-time streaming audio output
Documentation
# YM2149 Core Architecture

Hardware-accurate emulation of the Yamaha YM2149 Programmable Sound Generator chip.

## Scope

This document describes the **ym2149-core** crate only. For the overall workspace architecture including YM file parsing, playback, and Bevy integration, see the [root ARCHITECTURE.md](../../ARCHITECTURE.md).

## System Overview

```mermaid
graph TD
    subgraph ym2149-core["YM2149 Core Emulator"]
        API["Public API<br/>Ym2149Backend trait"] --> CHIP["Chip Implementation<br/>ym2149/chip.rs"]
        CHIP --> ENV["Envelope Generator"]
        CHIP --> NOISE["Noise Generator"]
        CHIP --> TONE["3x Tone Generators"]
        CHIP --> MIX["Mixer & Filter"]
        ENV --> OUT["Audio Output<br/>f32 samples"]
        NOISE --> OUT
        TONE --> OUT
        MIX --> OUT
    end
```

## Hardware Emulation (ym2149/)

The chip is a pure hardware emulator with sample-by-sample synthesis.

### Chip Structure

```mermaid
graph TD
    subgraph Synthesis["Signal Generation"]
        N["Noise Gen<br/>(LFSR)"] --> M["Mixer"]
        E["Envelope Gen<br/>(lookup table)"] --> M
        A["Channel A<br/>(phase acc)"] --> M
        B["Channel B<br/>(phase acc)"] --> M
        C["Channel C<br/>(phase acc)"] --> M
    end

    subgraph Control["Register Bank R0-R15"]
        R["Freq, Amp, Mixer,<br/>Envelope, Noise"]
    end

    R --> Synthesis
    Synthesis --> O["Output<br/>f32: -1.0..1.0"]
```

### Clock Flow (44.1 kHz)

Each output sample is generated by calling `clock()` once:

```
clock() [called at 44.1 kHz sample rate]
  ├─ envelope_gen.clock()        → Amplitude: 0-15
  ├─ noise_gen.clock()           → Noise bit: 0 or 1
  ├─ channel_a/b/c.clock()       → Waveform: ±1.0 (square)
  ├─ Apply mixer masks (R7)      → Enable/disable tone & noise per channel
  ├─ Hardware AND gate logic     → Combine tone & noise waveforms
  ├─ Apply amplitude (register or envelope)
  └─ Sum channels & color filter → Output sample (-1.0 to 1.0)
```

### Register Map

```
R0-R1:   Channel A frequency (12-bit)
R2-R3:   Channel B frequency (12-bit)
R4-R5:   Channel C frequency (12-bit)
R6:      Noise frequency divider (5-bit)
R7:      Mixer control (bits 0-2: tone enable, bits 3-5: noise enable)
R8:      Channel A amplitude (bits 0-3) + envelope flag (bit 4)
R9:      Channel B amplitude
R10:     Channel C amplitude
R11-R12: Envelope frequency divider (16-bit)
R13:     Envelope shape (4-bit: 16 waveforms)
R14-R15: I/O ports (not emulated)
```

### Key Components

#### Envelope Generator
- **16 waveform shapes**: attack, decay, release, sustain, buzzer, hold, sawtooth modes
- **Mechanism**: 16-bit phase accumulator clocked by frequency divider (R11-12)
- **Output**: 0-15 amplitude value via pre-computed lookup table
- **Effect**: Smooth amplitude modulation for expressive tones

#### Channels (Tone Generators)
- **Waveform**: Square via phase accumulator (hardware)
- **Frequency**: Extracted from 12-bit register value + master clock divider
- **Phase accumulation**: 32-bit fixed-point (16.16) for sub-sample precision
- **Output**: ±1.0 float amplitude, modulated by register or envelope

#### Noise Generator
- **Type**: 17-bit Linear Feedback Shift Register (LFSR)
- **Frequency**: Divider-based clock, controlled by R6
- **Output**: Single white noise bit (0 or 1)
- **Hardware-accurate**: Matches YM2149 tap positions

#### Mixer
- **Gate logic**: Hardware AND combines tone and noise per channel
- **Enable mask**: R7 bits control which channels produce output
- **Effect overrides**: SID/DigiDrum can force channels on/off (via public methods)
- **Output combining**: Simple addition of 3 channels (auto-scales)
- **Color filter**: Optional ST-style filter for authentic tone

---

## Backend Trait

The `Ym2149Backend` trait provides a common interface for all chip implementations:

```rust
pub trait Ym2149Backend: Send {
    // Core methods (required)
    fn new() -> Self where Self: Sized;
    fn with_clocks(master_clock: u32, sample_rate: u32) -> Self where Self: Sized;
    fn reset(&mut self);
    fn write_register(&mut self, addr: u8, value: u8);
    fn read_register(&self, addr: u8) -> u8;
    fn load_registers(&mut self, regs: &[u8; 16]);
    fn dump_registers(&self) -> [u8; 16];
    fn clock(&mut self);
    fn get_sample(&self) -> f32;
    fn generate_samples(&mut self, count: usize) -> Vec<f32>;
    fn get_channel_outputs(&self) -> (f32, f32, f32);
    fn set_channel_mute(&mut self, channel: usize, mute: bool);
    fn is_channel_muted(&self, channel: usize) -> bool;
    fn set_color_filter(&mut self, enabled: bool);

    // Hardware-specific methods (default no-ops for compatibility)
    fn trigger_envelope(&mut self) { /* no-op */ }
    fn set_drum_sample_override(&mut self, _channel: usize, _sample: Option<f32>) { /* no-op */ }
    fn set_mixer_overrides(&mut self, _force_tone: [bool; 3], _force_noise_mute: [bool; 3]) { /* no-op */ }
}
```

**Implementations:**
- `Ym2149` (this crate): Hardware-accurate emulation with full YM6 effect support
- `SoftSynth` (ym2149-softsynth crate): Experimental synthesizer (effects ignored via default trait methods)

**Generic Player:**
The `Ym6PlayerGeneric<B: Ym2149Backend>` in ym2149-ym-replayer uses this trait, allowing any backend. The type alias `Ym6Player = Ym6PlayerGeneric<Ym2149>` provides the common hardware-accurate default.

---

## Effect Support Methods

The `Ym2149` implementation provides additional public methods for hardware effects (used by ym2149-ym-replayer):

### Mixer Overrides
```rust
pub fn set_mixer_overrides(&mut self, force_tone: [bool; 3], force_noise_mute: [bool; 3])
```
Used by SID voice and Sync Buzzer effects to override mixer settings.

### DigiDrum Support
```rust
pub fn set_drum_sample_override(&mut self, voice: usize, sample: Option<i32>)
```
Allows injecting drum sample values directly into channel outputs.

### Envelope Triggering
```rust
pub fn trigger_envelope(&mut self, shape: u8)
```
Immediate envelope restart, used by Sync Buzzer effect.

**Note**: These methods are hardware-specific and provided as trait default methods (no-ops) in the `Ym2149Backend` trait. The `Ym6PlayerGeneric<B: Ym2149Backend>` player is generic over backends, but YM6 hardware effects are only available when using the concrete `Ym2149` implementation (via the `Ym6Player` type alias or explicitly as `Ym6PlayerGeneric<Ym2149>`). Alternative backends like `SoftSynth` will compile but ignore these hardware-specific features.

---

## Performance

| Operation | Time | CPU |
|-----------|------|-----|
| Ym2149.clock() | ~1-2 µs per sample | ~5% per core |
| generate_samples(882) | ~1-2 ms per frame | typical VBL period |
| Total @ 44.1 kHz | ~45-90 ms per second | ~5% sustained |

Low CPU overhead enables playback on modest systems.

---

## Key Design Decisions

1. **Fixed-point phase accumulators** (16.16 format) for sub-sample frequency precision
2. **Pre-computed envelope lookup tables** (16 shapes × 65K values) for smooth, fast amplitude modulation
3. **Zero allocations** in sample generation hot path
4. **Hardware-accurate mixer logic** with proper AND gate behavior
5. **Trait-based backend** for alternative implementations (e.g., SoftSynth)
6. **Public effect methods** to support YM6 playback without breaking encapsulation

---

## Module Organization

```
ym2149-core/src/
├── ym2149/
│   ├── chip.rs           # Main Ym2149 implementation
│   ├── channel.rs        # Tone generator (square wave)
│   ├── envelope.rs       # Envelope generator with 16 shapes
│   ├── noise.rs          # 17-bit LFSR noise generator
│   ├── mixer.rs          # Channel mixing and filtering
│   ├── constants.rs      # Clock rates, frequencies
│   └── registers.rs      # Register definitions
├── backend.rs            # Ym2149Backend trait
├── mfp.rs                # MFP timer helpers
├── streaming/            # Optional audio output (feature: streaming)
├── visualization/        # Terminal UI helpers (feature: visualization)
└── lib.rs                # Public API exports
```

---

## Deprecated Modules (v0.6.0)

The following modules are deprecated and maintained only for backward compatibility. Use the new crates instead:

- `compression/` → Use `ym2149-ym-replayer` crate
- `ym_parser/` → Use `ym2149-ym-replayer` crate
- `ym_loader/` → Use `ym2149-ym-replayer` crate
- `replayer/` → Use `ym2149-ym-replayer` crate

---

## Related Documentation

- [Workspace Architecture]../../ARCHITECTURE.md - Overall system design
- [Streaming Guide]../../STREAMING_GUIDE.md - Real-time audio output details
- [API Documentation]https://docs.rs/ym2149 - Full API reference