From 84b115f15f8fc819c4a909cc1968851a731cbbd3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 10 Apr 2020 19:13:52 -0400 Subject: [PATCH] Attempts to move forward in defining what the parts of an OPL are meant to do. --- Components/OPL2/OPL2.cpp | 171 +++++++++++++++-------------------- Components/OPL2/OPL2.hpp | 188 ++++++++++++++++++++++++++++++--------- 2 files changed, 217 insertions(+), 142 deletions(-) diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index 4e64f9463..dc14a8335 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -157,38 +157,37 @@ void OPLL::write_register(uint8_t address, uint8_t value) { return; } - // Locations 0x30 to 0x38: select an instrument in the top nibble, set a channel volume in the lower. - if(address >= 0x30 && address <= 0x38) { - const auto index = address - 0x30; - const auto instrument = value >> 4; - - channels_[index].output_level = value & 0xf; - channels_[index].modulator = &operators_[instrument * 2]; - return; - } - // Register 0xe is a cut-down version of the OPLL's register 0xbd. if(address == 0xe) { depth_rhythm_control_ = value & 0x3f; return; } - // Registers 0x10 to 0x18 set the bottom part of the channel frequency. - if(address >= 0x10 && address <= 0x18) { - const auto index = address - 0x10; - channels_[index].frequency = (channels_[index].frequency & ~0xff) | value; - return; - } + const auto index = address & 0xf; + if(index > 8) return; - // 0x20 to 0x28 set sustain on/off, key on/off, octave and a single extra bit of frequency. - // So they're a lot like OPLL registers 0xb0 to 0xb8, but not identical. - if(address >= 0x20 && address <= 0x28) { - const auto index = address - 0x20; - channels_[index].frequency = (channels_[index].frequency & 0xff) | (value & 1); - channels_[index].octave = (value >> 1) & 0x7; - channels_[index].key_on = value & 0x10; - channels_[index].hold_sustain_level = value & 0x20; - return; + switch(address & 0xf0) { + case 0x30: + // Select an instrument in the top nibble, set a channel volume in the lower. + channels_[index].output_level = value & 0xf; + channels_[index].modulator = &operators_[(value >> 4) * 2]; + break; + + case 0x10: + // Set the bottom part of the channel frequency. + channels_[index].frequency = (channels_[index].frequency & ~0xff) | value; + break; + + case 0x20: + // Set sustain on/off, key on/off, octave and a single extra bit of frequency. + // So they're a lot like OPLL registers 0xb0 to 0xb8, but not identical. + channels_[index].frequency = (channels_[index].frequency & 0xff) | (value & 1); + channels_[index].octave = (value >> 1) & 0x7; + channels_[index].key_on = value & 0x10; + channels_[index].hold_sustain_level = value & 0x20; + break; + + default: break; } }); } @@ -197,21 +196,22 @@ void OPLL::setup_fixed_instrument(int number, const uint8_t *data) { auto modulator = &operators_[number * 2]; auto carrier = &operators_[number * 2 + 1]; + modulator->set_am_vibrato_hold_sustain_ksr_multiple(data[0]); + carrier->set_am_vibrato_hold_sustain_ksr_multiple(data[1]); + modulator->set_scaling_output(data[2]); + // Set waveforms — only sine and halfsine are available. - carrier->waveform = Operator::Waveform((data[3] & 0x10) ? 1 : 0); - modulator->waveform = Operator::Waveform((data[3] & 0x08) ? 1 : 0); + carrier->set_waveform((data[3] >> 4) & 1); + modulator->set_waveform((data[3] >> 3) & 1); - // Set modulator amplitude and key-scale level. - modulator->scaling_level = data[2] >> 6; - modulator->output_level = data[2] & 0x3f; + // TODO: data[3] b0-b2: modulator feedback level + // TODO: data[3] b6, b7: carrier key-scale level -// set_opl2_register(0x20 + carrier, source[0]); -// set_opl2_register(0x20 + modulator, source[1]); -// set_opl2_register(0x40 + carrier, source[2]); -// set_opl2_register(0x60 + carrier, source[4]); -// set_opl2_register(0x60 + modulator, source[5]); -// set_opl2_register(0x80 + carrier, source[6]); -// set_opl2_register(0x80 + modulator, source[7]); + // Set ADSR parameters. + modulator->set_attack_decay(data[4]); + carrier->set_attack_decay(data[5]); + modulator->set_sustain_release(data[6]); + carrier->set_sustain_release(data[7]); } /* @@ -266,59 +266,28 @@ void OPL2::write_register(uint8_t address, uint8_t value) { // Operator modifications. // - // The 18 operators are spreat out across 22 addresses; each group of - // six is framed within an eight-byte area thusly: - constexpr int operator_by_address[] = { - 0, 1, 2, 3, 4, 5, -1, -1, - 6, 7, 8, 9, 10, 11, -1, -1, - 12, 13, 14, 15, 16, 17, -1, -1 - }; + if((address >= 0x20 && address < 0xa0) || address >= 0xe0) { + // The 18 operators are spreat out across 22 addresses; each group of + // six is framed within an eight-byte area thusly: + constexpr int operator_by_address[] = { + 0, 1, 2, 3, 4, 5, -1, -1, + 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + }; - if(address >= 0x20 && address <= 0x35) { - const auto index = operator_by_address[address - 0x20]; + const auto index = operator_by_address[address & 0x1f]; if(index == -1) return; - operators_[index].apply_amplitude_modulation = value & 0x80; - operators_[index].apply_vibrato = value & 0x40; - operators_[index].hold_sustain_level = value & 0x20; - operators_[index].keyboard_scaling_rate = value & 0x10; - operators_[index].frequency_multiple = value & 0xf; - return; - } + switch(address & 0xe0) { + case 0x20: operators_[index].set_am_vibrato_hold_sustain_ksr_multiple(value); break; + case 0x40: operators_[index].set_scaling_output(value); break; + case 0x60: operators_[index].set_attack_decay(value); break; + case 0x80: operators_[index].set_sustain_release(value); break; + case 0xe0: operators_[index].set_waveform(value); break; - if(address >= 0x40 && address <= 0x55) { - const auto index = operator_by_address[address - 0x40]; - if(index == -1) return; - - operators_[index].scaling_level = value >> 6; - operators_[index].output_level = value & 0x3f; - return; - } - - if(address >= 0x60 && address <= 0x75) { - const auto index = operator_by_address[address - 0x60]; - if(index == -1) return; - - operators_[index].attack_rate = value >> 5; - operators_[index].decay_rate = value & 0xf; - return; - } - - if(address >= 0x80 && address <= 0x95) { - const auto index = operator_by_address[address - 0x80]; - if(index == -1) return; - - operators_[index].sustain_level = value >> 5; - operators_[index].release_rate = value & 0xf; - return; - } - - if(address >= 0xe0 && address <= 0xf5) { - const auto index = operator_by_address[address - 0xe0]; - if(index == -1) return; - - operators_[index].waveform = Operator::Waveform(value & 3); - return; + default: break; + } } @@ -326,21 +295,25 @@ void OPL2::write_register(uint8_t address, uint8_t value) { // Channel modifications. // - if(address >= 0xa0 && address <= 0xa8) { - channels_[address - 0xa0].frequency = (channels_[address - 0xa0].frequency & ~0xff) | value; - return; - } + if(address >= 0xa0 && address <= 0xd0) { + const auto index = address & 0xf; + if(index > 8) return; - if(address >= 0xb0 && address <= 0xb8) { - channels_[address - 0xb0].frequency = (channels_[address - 0xb0].frequency & 0xff) | ((value & 3) << 8); - channels_[address - 0xb0].octave = (value >> 2) & 0x7; - channels_[address - 0xb0].key_on = value & 0x20;; - return; - } + switch(address & 0xf0) { + case 0xa0: + channels_[index].frequency = (channels_[index].frequency & ~0xff) | value; + break; + case 0xb0: + channels_[index].frequency = (channels_[index].frequency & 0xff) | ((value & 3) << 8); + channels_[index].octave = (value >> 2) & 0x7; + channels_[index].key_on = value & 0x20;; + break; + case 0xc0: + channels_[index].feedback_strength = (value >> 1) & 0x7; + channels_[index].use_fm_synthesis = value & 1; + break; + } - if(address >= 0xc0 && address <= 0xc8) { - channels_[address - 0xc0].feedback_strength = (value >> 1) & 0x7; - channels_[address - 0xc0].use_fm_synthesis = value & 1; return; } diff --git a/Components/OPL2/OPL2.hpp b/Components/OPL2/OPL2.hpp index 80cb6295e..c3f5102c2 100644 --- a/Components/OPL2/OPL2.hpp +++ b/Components/OPL2/OPL2.hpp @@ -18,68 +18,170 @@ namespace Yamaha { namespace OPL { -struct Operator { - /// If true then an amplitude modulation of "3.7Hz" is applied, - /// with a depth "determined by the AM-DEPTH of the BD register"? - bool apply_amplitude_modulation = false; +/*! + Models an operator. - /// If true then a vibrato of '6.4 Hz' is applied, with a depth - /// "determined by VOB_DEPTH of the BD register"? - bool apply_vibrato = false; + In Yamaha FM terms, an operator is a combination of a few things: - /// Selects between an ADSR envelope that holds at the sustain level - /// for as long as this key is on, releasing afterwards, and one that - /// simply switches straight to the release rate once the sustain - /// level is hit, getting back to 0 regardless of an ongoing key-on. - bool hold_sustain_level = false; + * an oscillator, producing one of a handful of sine-derived waveforms; + * an ADSR output level envelope; and + * a bunch of potential adjustments to those two things: + * optional tremolo and/or vibrato (the rates of which are global); + * the option to skip 'sustain' in ADSR and go straight to release (since no sustain period is supplied, + it otherwise runs for as long as the programmer leaves a channel enabled); + * an attenuation for the output level; and + * a factor by which to speed up the ADSR envelope as a function of frequency. - /// Provides a potential faster step through the ADSR envelope. Cf. p12. - bool keyboard_scaling_rate = false; + Oscillator frequency isn't set directly, it's a multiple of the owning channel, in which + frequency is set as a combination of f-num and octave. +*/ +class Operator { + public: + /// Sets this operator's attack rate as the top nibble of @c value, its decay rate as the bottom nibble. + void set_attack_decay(uint8_t value) { + attack_rate = value >> 4; + decay_rate = value & 0xf; + } - /// Indexes a lookup table to determine what multiple of the channel's frequency - /// this operator is advancing at. - int frequency_multiple = 0; + /// Sets this operator's sustain level as the top nibble of @c value, its release rate as the bottom nibble. + void set_sustain_release(uint8_t value) { + sustain_level = value >> 4; + release_rate = value & 0xf; + } - /// Sets the current output level of this modulator, as an attenuation. - int output_level = 0; + /// Sets this operator's key scale level as the top two bits of @c value, its total output level as the low six bits. + void set_scaling_output(uint8_t value) { + scaling_level = value >> 6; + output_level = value & 0x3f; + } - /// Selects attenuation that is applied as a function of interval. Cf. p14. - int scaling_level = 0; + /// Sets this operator's waveform using the low two bits of @c value. + void set_waveform(uint8_t value) { + waveform = Operator::Waveform(value & 3); + } - /// Sets the ADSR rates. - int attack_rate = 0; - int decay_rate = 0; - int sustain_level = 0; - int release_rate = 0; + /// From the top nibble of @c value sets the AM, vibrato, hold/sustain level and keyboard sampling rate flags; + /// uses the bottom nibble to set the frequency multiplier. + void set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value) { + apply_amplitude_modulation = value & 0x80; + apply_vibrato = value & 0x40; + hold_sustain_level = value & 0x20; + keyboard_scaling_rate = value & 0x10; + frequency_multiple = value & 0xf; + } - /// Selects the generated waveform. - enum class Waveform { - Sine, HalfSine, AbsSine, PulseSine - } waveform = Waveform::Sine; + void update(int channel_frequency, int channel_octave) { + // Per the documentation: + // F-Num = Music Frequency * 2^(20-Block) / 49716 + // + // Given that a 256-entry table is used to store a quarter of a sine wave, + // making 1024 steps per complete wave, add what I've called frequency + // to an accumulator and move on whenever that exceeds 2^(10 - octave). + // + // ... subject to each operator having a frequency multiple. + // + // Or: 2^19? + + // This encodes the MUL -> multiple table given on page 12, + // multiplied by two. + constexpr int multipliers[] = { + 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 + }; + + // Update the raw phase. + const int octave_divider = (10 - channel_octave) << 9; + divider_ += multipliers[frequency_multiple] * channel_frequency; + raw_phase_ += divider_ / octave_divider; + divider_ %= octave_divider; + + // Hence calculate phase (TODO: by also taking account of vibrato). + constexpr int waveforms[4][4] = { + {1023, 1023, 1023, 1023}, // Sine: don't mask in any quadrant. + {511, 511, 0, 0}, // Half sine: keep the first half in tact, lock to 0 in the second half. + {511, 511, 511, 511}, // AbsSine: endlessly repeat the first half of the sine wave. + {255, 0, 255, 0}, // PulseSine: act as if the first quadrant is in the first and third; lock the other two to 0. + }; + phase = raw_phase_ & waveforms[int(waveform)][(raw_phase_ >> 8) & 3]; + + // TODO: calculate output volume properly; apply: ADSR and amplitude modulation (tremolo, I assume?) + volume = output_level; + } + + // Outputs. + int phase = 0; // Will be in the range [0, 1023], mapping into a 1024-unit sine curve. + int volume = 0; + + private: + /// If true then an amplitude modulation of "3.7Hz" is applied, + /// with a depth "determined by the AM-DEPTH of the BD register"? + bool apply_amplitude_modulation = false; + + /// If true then a vibrato of '6.4 Hz' is applied, with a depth + /// "determined by VOB_DEPTH of the BD register"? + bool apply_vibrato = false; + + /// Selects between an ADSR envelope that holds at the sustain level + /// for as long as this key is on, releasing afterwards, and one that + /// simply switches straight to the release rate once the sustain + /// level is hit, getting back to 0 regardless of an ongoing key-on. + bool hold_sustain_level = false; + + /// Provides a potential faster step through the ADSR envelope. Cf. p12. + bool keyboard_scaling_rate = false; + + /// Indexes a lookup table to determine what multiple of the channel's frequency + /// this operator is advancing at. + int frequency_multiple = 0; + + /// Sets the current output level of this modulator, as an attenuation. + int output_level = 0; + + /// Selects attenuation that is applied as a function of interval. Cf. p14. + int scaling_level = 0; + + /// Sets the ADSR rates. + int attack_rate = 0; + int decay_rate = 0; + int sustain_level = 0; + int release_rate = 0; + + /// Selects the generated waveform. + enum class Waveform { + Sine, HalfSine, AbsSine, PulseSine + } waveform = Waveform::Sine; + + // Ephemeral state. + int raw_phase_ = 0; + int divider_ = 0; }; +/*! + Models an L-type two-operator channel. + + Assuming FM synthesis is enabled, the channel modulates the output of the carrier with that of the modulator. +*/ struct Channel { + /// 'F-Num' in the spec; this plus the current octave determines channel frequency. int frequency = 0; + + /// Linked with the frequency, determines the channel frequency. int octave = 0; + + /// Sets sets this channel on or off, as an input to the ADSR envelope, bool key_on = false; + + /// Sets the degree of feedback applied to the modulator. int feedback_strength = 0; + + /// Selects between FM synthesis, using the modulator to modulate the carrier, or simple mixing of the two + /// underlying operators as completely disjoint entities. bool use_fm_synthesis = true; // This should be called at a rate of around 49,716 Hz. - void update() { - // Per the documentation: - // F-Num = Music Frequency * 2^(20-Block) / 49716 - // - // Given that a 256-entry table is used to store a quarter of a sine wave, - // making 1024 steps per complete wave, add what I've called frequency - // to an accumulator and move on whenever that exceeds 2^(10 - octave). - // - // TODO: but, how does that apply to the two operator multipliers? - // - // Or: 2^19? + void update(Operator *carrier, Operator *modulator) { + modulator->update(frequency, octave); + carrier->update(frequency, octave); } - - // Stateful information. }; template class OPLBase: public ::Outputs::Speaker::SampleSource {