|
| 1 | +package opl2 |
| 2 | + |
| 3 | +import ( |
| 4 | + "testing" |
| 5 | +) |
| 6 | + |
| 7 | +// Use the native OPL rate for deterministic table values in tests. |
| 8 | +const ( |
| 9 | + testRate = RoundedOPLRATE |
| 10 | +) |
| 11 | + |
| 12 | +// Test that waveform writes propagate to the operator (reg E0) and refresh |
| 13 | +// the derived waveform start/mask data. This guards the regression where the |
| 14 | +// register change early-returned and never updated the operator state. |
| 15 | +func TestOperatorWaveformWriteUpdates(t *testing.T) { |
| 16 | + chip := NewChip(testRate, false) |
| 17 | + // Enable waveform selection (otherwise waveFormMask would clamp values). |
| 18 | + chip.WriteReg(0x01, 0x20) |
| 19 | + |
| 20 | + op := chip.GetOperatorByIndex(0) |
| 21 | + if op == nil { |
| 22 | + t.Fatalf("nil operator returned") |
| 23 | + } |
| 24 | + initialStart := op.waveStart |
| 25 | + |
| 26 | + chip.WriteReg(0xE0, 0x01) // select waveform 1 |
| 27 | + |
| 28 | + expectedStart := int(cWaveStartTable[1]) << cWaveSh |
| 29 | + if op.waveStart != expectedStart { |
| 30 | + t.Fatalf("waveStart not updated: got %d, want %d (initial %d)", op.waveStart, expectedStart, initialStart) |
| 31 | + } |
| 32 | + if op.regE0 != 0x01 { |
| 33 | + t.Fatalf("regE0 not stored: got 0x%02x, want 0x01", op.regE0) |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +// Validate percussion key-on/off handling driven by register BD toggles. |
| 38 | +// Ensures bass drum key state tracks the BD bit correctly when percussion mode is enabled. |
| 39 | +func TestPercussionKeyOnOff(t *testing.T) { |
| 40 | + chip := NewChip(testRate, false) |
| 41 | + |
| 42 | + // Enable percussion mode without bass drum key-on. |
| 43 | + chip.WriteReg(0xBD, 0x20) |
| 44 | + if chip.ch[6].op[0].keyOn != 0 || chip.ch[6].op[1].keyOn != 0 { |
| 45 | + t.Fatalf("bass drum keyOn should be cleared after enabling percussion: op0=%02x op1=%02x", chip.ch[6].op[0].keyOn, chip.ch[6].op[1].keyOn) |
| 46 | + } |
| 47 | + |
| 48 | + // Turn on bass drum bit. |
| 49 | + chip.WriteReg(0xBD, 0x30) |
| 50 | + if chip.ch[6].op[0].keyOn&0x2 == 0 || chip.ch[6].op[1].keyOn&0x2 == 0 { |
| 51 | + t.Fatalf("bass drum keyOn not set: op0=%02x op1=%02x", chip.ch[6].op[0].keyOn, chip.ch[6].op[1].keyOn) |
| 52 | + } |
| 53 | + |
| 54 | + // Turn off bass drum bit while keeping percussion enabled. |
| 55 | + chip.WriteReg(0xBD, 0x20) |
| 56 | + if chip.ch[6].op[0].keyOn != 0 || chip.ch[6].op[1].keyOn != 0 { |
| 57 | + t.Fatalf("bass drum keyOn not cleared: op0=%02x op1=%02x", chip.ch[6].op[0].keyOn, chip.ch[6].op[1].keyOn) |
| 58 | + } |
| 59 | + |
| 60 | + // Disable percussion entirely; state should remain cleared. |
| 61 | + chip.WriteReg(0xBD, 0x00) |
| 62 | + if chip.ch[6].op[0].keyOn != 0 || chip.ch[6].op[1].keyOn != 0 { |
| 63 | + t.Fatalf("bass drum keyOn not cleared after percussion disable: op0=%02x op1=%02x", chip.ch[6].op[0].keyOn, chip.ch[6].op[1].keyOn) |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +// Verify 4-operator synth mode selection matches the bit combinations in |
| 68 | +// regC0 for paired channels when OPL3 is active. |
| 69 | +func TestFourOpSynthSelection(t *testing.T) { |
| 70 | + chip := NewChip(testRate, true) |
| 71 | + // Enable OPL3 features explicitly; Setup leaves opl3Active disabled. |
| 72 | + chip.WriteReg(0x105, 0x01) |
| 73 | + if chip.opl3Active == 0 { |
| 74 | + t.Fatalf("opl3Active not enabled") |
| 75 | + } |
| 76 | + |
| 77 | + // Enable 4-op on logical channels 0/3 (bit 0). |
| 78 | + chip.WriteReg(0x104, 0x01) |
| 79 | + |
| 80 | + ch0 := chip.GetChannelByIndex(0) // logical channel 0 |
| 81 | + ch1 := chip.GetChannelByIndex(3) // logical channel 3 (pair with bit 0) |
| 82 | + if ch0 == nil || ch1 == nil { |
| 83 | + t.Fatalf("failed to get four-op channel pair") |
| 84 | + } |
| 85 | + |
| 86 | + // Set synth bits to pattern 01 -> sm3FMAM for the paired channels. |
| 87 | + ch0.WriteC0(chip, 0x02) // bit0 = 0 (change!=0) |
| 88 | + ch1.WriteC0(chip, 0x03) // bit0 = 1 (change!=0) |
| 89 | + |
| 90 | + if ch0.synthHandler < sm4Start { |
| 91 | + t.Fatalf("four-op handler not selected: got %v (ch0=0x%02x ch1=0x%02x)", ch0.synthHandler, ch0.regC0, ch1.regC0) |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +// Confirm WriteAddr maps ports correctly, matching the C++ helper logic. |
| 96 | +func TestWriteAddrMapping(t *testing.T) { |
| 97 | + opl2Chip := NewChip(testRate, false) |
| 98 | + opl3Chip := NewChip(testRate, true) |
| 99 | + opl3Chip.WriteReg(0x105, 0x01) // enable opl3Active |
| 100 | + |
| 101 | + if got := opl2Chip.WriteAddr(0, 0xAA); got != 0xAA { |
| 102 | + t.Fatalf("opl2 port0 mapping mismatch: got 0x%x", got) |
| 103 | + } |
| 104 | + if got := opl2Chip.WriteAddr(2, 0x05); got != 0x105 { |
| 105 | + t.Fatalf("opl2 port2 special mapping mismatch: got 0x%x", got) |
| 106 | + } |
| 107 | + if got := opl2Chip.WriteAddr(2, 0x06); got != 0x06 { |
| 108 | + t.Fatalf("opl2 port2 normal mapping mismatch: got 0x%x", got) |
| 109 | + } |
| 110 | + if got := opl3Chip.WriteAddr(2, 0x11); got != 0x111 { |
| 111 | + t.Fatalf("opl3 port2 mapping mismatch: got 0x%x", got) |
| 112 | + } |
| 113 | + if got := opl2Chip.WriteAddr(1, 0x22); got != 0 { |
| 114 | + t.Fatalf("unexpected mapping for unused port: got 0x%x", got) |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +// Ensure block generation does not panic and produces the expected frame counts |
| 119 | +// for mono (OPL2) and stereo (OPL3) output buffers. |
| 120 | +func TestGenerateBlockSizes(t *testing.T) { |
| 121 | + samples := uint(8) |
| 122 | + |
| 123 | + opl2Chip := NewChip(testRate, false) |
| 124 | + opl2Buf := make([]int32, samples) |
| 125 | + opl2Chip.GenerateBlock2(samples, opl2Buf) |
| 126 | + |
| 127 | + opl3Chip := NewChip(testRate, true) |
| 128 | + opl3Buf := make([]int32, samples*2) |
| 129 | + opl3Chip.GenerateBlock3(samples, opl3Buf) |
| 130 | + |
| 131 | + if len(opl2Buf) != int(samples) { |
| 132 | + t.Fatalf("opl2 buffer length changed: got %d", len(opl2Buf)) |
| 133 | + } |
| 134 | + if len(opl3Buf) != int(samples*2) { |
| 135 | + t.Fatalf("opl3 buffer length changed: got %d", len(opl3Buf)) |
| 136 | + } |
| 137 | +} |
0 commit comments