Added drums, snares, etc. OPL2 emulator is now feature complete.
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -147,9 +147,31 @@ class Opl2Channel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Opl2Noise {
|
||||||
|
int _seed = 0xFFFF;
|
||||||
|
|
||||||
|
// Simple 16-bit LFSR to match OPL2 noise characteristics
|
||||||
|
double next() {
|
||||||
|
int bit = ((_seed >> 0) ^ (_seed >> 2) ^ (_seed >> 3) ^ (_seed >> 5)) & 1;
|
||||||
|
_seed = (_seed >> 1) | (bit << 15);
|
||||||
|
return ((_seed & 1) == 1) ? 1.0 : -1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Opl2Emulator {
|
class Opl2Emulator {
|
||||||
static const int sampleRate = 44100;
|
static const int sampleRate = 44100;
|
||||||
|
|
||||||
|
bool rhythmMode = false;
|
||||||
|
|
||||||
|
// Key states for the 5 drums
|
||||||
|
bool bassDrumKey = false;
|
||||||
|
bool snareDrumKey = false;
|
||||||
|
bool tomTomKey = false;
|
||||||
|
bool topCymbalKey = false;
|
||||||
|
bool hiHatKey = false;
|
||||||
|
|
||||||
|
final Opl2Noise _noise = Opl2Noise();
|
||||||
|
|
||||||
final List<Opl2Channel> channels = List.generate(9, (_) => Opl2Channel());
|
final List<Opl2Channel> channels = List.generate(9, (_) => Opl2Channel());
|
||||||
|
|
||||||
// The master lock for waveforms
|
// The master lock for waveforms
|
||||||
@@ -276,14 +298,88 @@ class Opl2Emulator {
|
|||||||
op.waveform = data & 0x03;
|
op.waveform = data & 0x03;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (reg == 0xBD) {
|
||||||
|
rhythmMode = (data & 0x20) != 0; // Bit 5: Rhythm Enable
|
||||||
|
|
||||||
|
// Bits 0-4: Key-On for individual drums
|
||||||
|
bassDrumKey = (data & 0x10) != 0;
|
||||||
|
snareDrumKey = (data & 0x08) != 0;
|
||||||
|
tomTomKey = (data & 0x04) != 0;
|
||||||
|
topCymbalKey = (data & 0x02) != 0;
|
||||||
|
hiHatKey = (data & 0x01) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double generateSample() {
|
double generateSample() {
|
||||||
double mixedOutput = 0.0;
|
double mixedOutput = 0.0;
|
||||||
for (var channel in channels) {
|
|
||||||
mixedOutput += channel.getSample();
|
// Channels 0-5 always act as normal melodic FM channels
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
mixedOutput += channels[i].getSample();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!rhythmMode) {
|
||||||
|
// Standard mode: play channels 6, 7, and 8 normally
|
||||||
|
for (int i = 6; i < 9; i++) {
|
||||||
|
mixedOutput += channels[i].getSample();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// RHYTHM MODE: The last 3 channels are re-routed
|
||||||
|
mixedOutput += _generateBassDrum();
|
||||||
|
mixedOutput += _generateSnareAndHiHat();
|
||||||
|
mixedOutput += _generateTomAndCymbal();
|
||||||
|
}
|
||||||
|
|
||||||
return mixedOutput.clamp(-1.0, 1.0);
|
return mixedOutput.clamp(-1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Example of Bass Drum logic (Channel 6)
|
||||||
|
double _generateBassDrum() {
|
||||||
|
if (!bassDrumKey) return 0.0;
|
||||||
|
// Bass drum uses standard FM (Mod -> Car) but usually with very low frequency
|
||||||
|
return channels[6].getSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
double _generateSnareAndHiHat() {
|
||||||
|
double snareOut = 0.0;
|
||||||
|
double hiHatOut = 0.0;
|
||||||
|
|
||||||
|
// Snare uses Channel 7 Modulator
|
||||||
|
if (snareDrumKey) {
|
||||||
|
// The Snare is a mix of a periodic tone and white noise
|
||||||
|
double noise = _noise.next();
|
||||||
|
snareOut = channels[7].modulator.getSample(0.0) * noise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hi-Hat uses Channel 7 Carrier
|
||||||
|
if (hiHatKey) {
|
||||||
|
// Hi-Hats are almost pure high-frequency noise
|
||||||
|
hiHatOut =
|
||||||
|
_noise.next() *
|
||||||
|
channels[7].carrier.envVolume *
|
||||||
|
channels[7].carrier.volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (snareOut + hiHatOut) * 0.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double _generateTomAndCymbal() {
|
||||||
|
double tomOut = 0.0;
|
||||||
|
double cymbalOut = 0.0;
|
||||||
|
|
||||||
|
// Tom-tom uses Channel 8 Modulator
|
||||||
|
if (tomTomKey) {
|
||||||
|
// Toms are basically just a melodic sine wave with a fast decay
|
||||||
|
tomOut = channels[8].modulator.getSample(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top Cymbal uses Channel 8 Carrier
|
||||||
|
if (topCymbalKey) {
|
||||||
|
// Cymbals use the carrier but are usually phase-modulated by noise
|
||||||
|
double noise = _noise.next();
|
||||||
|
cymbalOut = channels[8].carrier.getSample(noise * 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (tomOut + cymbalOut) * 0.1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user