mirror of
https://github.com/TomHarte/CLK.git
synced 2024-07-07 23:29:06 +00:00
Attempts to move forward in defining what the parts of an OPL are meant to do.
This commit is contained in:
parent
a0d14f4030
commit
84b115f15f
@ -157,38 +157,37 @@ void OPLL::write_register(uint8_t address, uint8_t value) {
|
|||||||
return;
|
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.
|
// Register 0xe is a cut-down version of the OPLL's register 0xbd.
|
||||||
if(address == 0xe) {
|
if(address == 0xe) {
|
||||||
depth_rhythm_control_ = value & 0x3f;
|
depth_rhythm_control_ = value & 0x3f;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registers 0x10 to 0x18 set the bottom part of the channel frequency.
|
const auto index = address & 0xf;
|
||||||
if(address >= 0x10 && address <= 0x18) {
|
if(index > 8) return;
|
||||||
const auto index = address - 0x10;
|
|
||||||
channels_[index].frequency = (channels_[index].frequency & ~0xff) | value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0x20 to 0x28 set sustain on/off, key on/off, octave and a single extra bit of frequency.
|
switch(address & 0xf0) {
|
||||||
// So they're a lot like OPLL registers 0xb0 to 0xb8, but not identical.
|
case 0x30:
|
||||||
if(address >= 0x20 && address <= 0x28) {
|
// Select an instrument in the top nibble, set a channel volume in the lower.
|
||||||
const auto index = address - 0x20;
|
channels_[index].output_level = value & 0xf;
|
||||||
channels_[index].frequency = (channels_[index].frequency & 0xff) | (value & 1);
|
channels_[index].modulator = &operators_[(value >> 4) * 2];
|
||||||
channels_[index].octave = (value >> 1) & 0x7;
|
break;
|
||||||
channels_[index].key_on = value & 0x10;
|
|
||||||
channels_[index].hold_sustain_level = value & 0x20;
|
case 0x10:
|
||||||
return;
|
// 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 modulator = &operators_[number * 2];
|
||||||
auto carrier = &operators_[number * 2 + 1];
|
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.
|
// Set waveforms — only sine and halfsine are available.
|
||||||
carrier->waveform = Operator::Waveform((data[3] & 0x10) ? 1 : 0);
|
carrier->set_waveform((data[3] >> 4) & 1);
|
||||||
modulator->waveform = Operator::Waveform((data[3] & 0x08) ? 1 : 0);
|
modulator->set_waveform((data[3] >> 3) & 1);
|
||||||
|
|
||||||
// Set modulator amplitude and key-scale level.
|
// TODO: data[3] b0-b2: modulator feedback level
|
||||||
modulator->scaling_level = data[2] >> 6;
|
// TODO: data[3] b6, b7: carrier key-scale level
|
||||||
modulator->output_level = data[2] & 0x3f;
|
|
||||||
|
|
||||||
// set_opl2_register(0x20 + carrier, source[0]);
|
// Set ADSR parameters.
|
||||||
// set_opl2_register(0x20 + modulator, source[1]);
|
modulator->set_attack_decay(data[4]);
|
||||||
// set_opl2_register(0x40 + carrier, source[2]);
|
carrier->set_attack_decay(data[5]);
|
||||||
// set_opl2_register(0x60 + carrier, source[4]);
|
modulator->set_sustain_release(data[6]);
|
||||||
// set_opl2_register(0x60 + modulator, source[5]);
|
carrier->set_sustain_release(data[7]);
|
||||||
// set_opl2_register(0x80 + carrier, source[6]);
|
|
||||||
// set_opl2_register(0x80 + modulator, source[7]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -266,59 +266,28 @@ void OPL2::write_register(uint8_t address, uint8_t value) {
|
|||||||
// Operator modifications.
|
// Operator modifications.
|
||||||
//
|
//
|
||||||
|
|
||||||
// The 18 operators are spreat out across 22 addresses; each group of
|
if((address >= 0x20 && address < 0xa0) || address >= 0xe0) {
|
||||||
// six is framed within an eight-byte area thusly:
|
// The 18 operators are spreat out across 22 addresses; each group of
|
||||||
constexpr int operator_by_address[] = {
|
// six is framed within an eight-byte area thusly:
|
||||||
0, 1, 2, 3, 4, 5, -1, -1,
|
constexpr int operator_by_address[] = {
|
||||||
6, 7, 8, 9, 10, 11, -1, -1,
|
0, 1, 2, 3, 4, 5, -1, -1,
|
||||||
12, 13, 14, 15, 16, 17, -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 & 0x1f];
|
||||||
const auto index = operator_by_address[address - 0x20];
|
|
||||||
if(index == -1) return;
|
if(index == -1) return;
|
||||||
|
|
||||||
operators_[index].apply_amplitude_modulation = value & 0x80;
|
switch(address & 0xe0) {
|
||||||
operators_[index].apply_vibrato = value & 0x40;
|
case 0x20: operators_[index].set_am_vibrato_hold_sustain_ksr_multiple(value); break;
|
||||||
operators_[index].hold_sustain_level = value & 0x20;
|
case 0x40: operators_[index].set_scaling_output(value); break;
|
||||||
operators_[index].keyboard_scaling_rate = value & 0x10;
|
case 0x60: operators_[index].set_attack_decay(value); break;
|
||||||
operators_[index].frequency_multiple = value & 0xf;
|
case 0x80: operators_[index].set_sustain_release(value); break;
|
||||||
return;
|
case 0xe0: operators_[index].set_waveform(value); break;
|
||||||
}
|
|
||||||
|
|
||||||
if(address >= 0x40 && address <= 0x55) {
|
default: break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -326,21 +295,25 @@ void OPL2::write_register(uint8_t address, uint8_t value) {
|
|||||||
// Channel modifications.
|
// Channel modifications.
|
||||||
//
|
//
|
||||||
|
|
||||||
if(address >= 0xa0 && address <= 0xa8) {
|
if(address >= 0xa0 && address <= 0xd0) {
|
||||||
channels_[address - 0xa0].frequency = (channels_[address - 0xa0].frequency & ~0xff) | value;
|
const auto index = address & 0xf;
|
||||||
return;
|
if(index > 8) return;
|
||||||
}
|
|
||||||
|
|
||||||
if(address >= 0xb0 && address <= 0xb8) {
|
switch(address & 0xf0) {
|
||||||
channels_[address - 0xb0].frequency = (channels_[address - 0xb0].frequency & 0xff) | ((value & 3) << 8);
|
case 0xa0:
|
||||||
channels_[address - 0xb0].octave = (value >> 2) & 0x7;
|
channels_[index].frequency = (channels_[index].frequency & ~0xff) | value;
|
||||||
channels_[address - 0xb0].key_on = value & 0x20;;
|
break;
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,68 +18,170 @@ namespace Yamaha {
|
|||||||
|
|
||||||
namespace OPL {
|
namespace OPL {
|
||||||
|
|
||||||
struct Operator {
|
/*!
|
||||||
/// If true then an amplitude modulation of "3.7Hz" is applied,
|
Models an operator.
|
||||||
/// 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
|
In Yamaha FM terms, an operator is a combination of a few things:
|
||||||
/// "determined by VOB_DEPTH of the BD register"?
|
|
||||||
bool apply_vibrato = false;
|
|
||||||
|
|
||||||
/// Selects between an ADSR envelope that holds at the sustain level
|
* an oscillator, producing one of a handful of sine-derived waveforms;
|
||||||
/// for as long as this key is on, releasing afterwards, and one that
|
* an ADSR output level envelope; and
|
||||||
/// simply switches straight to the release rate once the sustain
|
* a bunch of potential adjustments to those two things:
|
||||||
/// level is hit, getting back to 0 regardless of an ongoing key-on.
|
* optional tremolo and/or vibrato (the rates of which are global);
|
||||||
bool hold_sustain_level = false;
|
* 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.
|
Oscillator frequency isn't set directly, it's a multiple of the owning channel, in which
|
||||||
bool keyboard_scaling_rate = false;
|
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
|
/// Sets this operator's sustain level as the top nibble of @c value, its release rate as the bottom nibble.
|
||||||
/// this operator is advancing at.
|
void set_sustain_release(uint8_t value) {
|
||||||
int frequency_multiple = 0;
|
sustain_level = value >> 4;
|
||||||
|
release_rate = value & 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the current output level of this modulator, as an attenuation.
|
/// Sets this operator's key scale level as the top two bits of @c value, its total output level as the low six bits.
|
||||||
int output_level = 0;
|
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.
|
/// Sets this operator's waveform using the low two bits of @c value.
|
||||||
int scaling_level = 0;
|
void set_waveform(uint8_t value) {
|
||||||
|
waveform = Operator::Waveform(value & 3);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the ADSR rates.
|
/// From the top nibble of @c value sets the AM, vibrato, hold/sustain level and keyboard sampling rate flags;
|
||||||
int attack_rate = 0;
|
/// uses the bottom nibble to set the frequency multiplier.
|
||||||
int decay_rate = 0;
|
void set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value) {
|
||||||
int sustain_level = 0;
|
apply_amplitude_modulation = value & 0x80;
|
||||||
int release_rate = 0;
|
apply_vibrato = value & 0x40;
|
||||||
|
hold_sustain_level = value & 0x20;
|
||||||
|
keyboard_scaling_rate = value & 0x10;
|
||||||
|
frequency_multiple = value & 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
/// Selects the generated waveform.
|
void update(int channel_frequency, int channel_octave) {
|
||||||
enum class Waveform {
|
// Per the documentation:
|
||||||
Sine, HalfSine, AbsSine, PulseSine
|
// F-Num = Music Frequency * 2^(20-Block) / 49716
|
||||||
} waveform = Waveform::Sine;
|
//
|
||||||
|
// 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 {
|
struct Channel {
|
||||||
|
/// 'F-Num' in the spec; this plus the current octave determines channel frequency.
|
||||||
int frequency = 0;
|
int frequency = 0;
|
||||||
|
|
||||||
|
/// Linked with the frequency, determines the channel frequency.
|
||||||
int octave = 0;
|
int octave = 0;
|
||||||
|
|
||||||
|
/// Sets sets this channel on or off, as an input to the ADSR envelope,
|
||||||
bool key_on = false;
|
bool key_on = false;
|
||||||
|
|
||||||
|
/// Sets the degree of feedback applied to the modulator.
|
||||||
int feedback_strength = 0;
|
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;
|
bool use_fm_synthesis = true;
|
||||||
|
|
||||||
// This should be called at a rate of around 49,716 Hz.
|
// This should be called at a rate of around 49,716 Hz.
|
||||||
void update() {
|
void update(Operator *carrier, Operator *modulator) {
|
||||||
// Per the documentation:
|
modulator->update(frequency, octave);
|
||||||
// F-Num = Music Frequency * 2^(20-Block) / 49716
|
carrier->update(frequency, octave);
|
||||||
//
|
|
||||||
// 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?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stateful information.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
||||||
|
Loading…
Reference in New Issue
Block a user