mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Starts tidying up the OPL2.
This is as a precursor to switching to using the proper table lookups, which I hope will automatically fix my range issues.
This commit is contained in:
parent
30ff399218
commit
0aceddd088
57
Components/OPL2/Implementation/Channel.cpp
Normal file
57
Components/OPL2/Implementation/Channel.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
//
|
||||||
|
// Channel.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 15/04/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Channel.hpp"
|
||||||
|
|
||||||
|
using namespace Yamaha::OPL;
|
||||||
|
|
||||||
|
void Channel::set_frequency_low(uint8_t value) {
|
||||||
|
period_ = (period_ &~0xff) | value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::set_10bit_frequency_octave_key_on(uint8_t value) {
|
||||||
|
period_ = (period_ & 0xff) | ((value & 3) << 8);
|
||||||
|
octave_ = (value >> 2) & 0x7;
|
||||||
|
key_on_ = value & 0x20;
|
||||||
|
frequency_shift_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::set_9bit_frequency_octave_key_on(uint8_t value) {
|
||||||
|
period_ = (period_ & 0xff) | ((value & 1) << 8);
|
||||||
|
octave_ = (value >> 1) & 0x7;
|
||||||
|
key_on_ = value & 0x10;;
|
||||||
|
frequency_shift_ = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Channel::set_feedback_mode(uint8_t value) {
|
||||||
|
feedback_strength_ = (value >> 1) & 0x7;
|
||||||
|
use_fm_synthesis_ = value & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Channel::update(Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides, OperatorOverrides *carrier_overrides) {
|
||||||
|
modulator->update(modulator_state_, key_on_, period_ << frequency_shift_, octave_, modulator_overrides);
|
||||||
|
carrier->update(carrier_state_, key_on_, period_ << frequency_shift_, octave_, carrier_overrides);
|
||||||
|
|
||||||
|
// TODO: almost everything. This is a quick test.
|
||||||
|
// Specifically: use lookup tables.
|
||||||
|
const auto modulator_level = level(modulator_state_, 0.0f); // TODO: what's the proper scaling on this?
|
||||||
|
return int(level(carrier_state_, modulator_level) * 20'000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Channel::is_audible(Operator *carrier, OperatorOverrides *carrier_overrides) {
|
||||||
|
return carrier->is_audible(carrier_state_, carrier_overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Channel::level(OperatorState &state, float modulator_level) {
|
||||||
|
const float phase = modulator_level + float(state.phase) / 1024.0f;
|
||||||
|
const float phase_attenuation = logf(1.0f + sinf(float(M_PI) * 2.0f * phase));
|
||||||
|
const float total_attenuation = phase_attenuation + float(state.attenuation) / 1023.0f;
|
||||||
|
const float result = expf(total_attenuation / 2.0f);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
81
Components/OPL2/Implementation/Channel.hpp
Normal file
81
Components/OPL2/Implementation/Channel.hpp
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// Channel.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 15/04/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Channel_hpp
|
||||||
|
#define Channel_hpp
|
||||||
|
|
||||||
|
#include "Operator.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace Yamaha {
|
||||||
|
namespace OPL {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
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.
|
||||||
|
|
||||||
|
TODO: make this a template on period counter size in bits?
|
||||||
|
*/
|
||||||
|
class Channel {
|
||||||
|
public:
|
||||||
|
/// Sets the low 8 bits of frequency control.
|
||||||
|
void set_frequency_low(uint8_t value);
|
||||||
|
|
||||||
|
/// Sets the high two bits of a 10-bit frequency control, along with this channel's
|
||||||
|
/// block/octave, and key on or off.
|
||||||
|
void set_10bit_frequency_octave_key_on(uint8_t value);
|
||||||
|
|
||||||
|
/// Sets the high two bits of a 9-bit frequency control, along with this channel's
|
||||||
|
/// block/octave, and key on or off.
|
||||||
|
void set_9bit_frequency_octave_key_on(uint8_t value);
|
||||||
|
|
||||||
|
/// Sets the amount of feedback provided to the first operator (i.e. the modulator)
|
||||||
|
/// associated with this channel, and whether FM synthesis is in use.
|
||||||
|
void set_feedback_mode(uint8_t value);
|
||||||
|
|
||||||
|
/// This should be called at a rate of around 49,716 Hz; it returns the current output level
|
||||||
|
/// level for this channel.
|
||||||
|
int update(Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides = nullptr, OperatorOverrides *carrier_overrides = nullptr);
|
||||||
|
|
||||||
|
/// @returns @c true if this channel is currently producing any audio; @c false otherwise;
|
||||||
|
bool is_audible(Operator *carrier, OperatorOverrides *carrier_overrides = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
float level(OperatorState &state, float modulator_level);
|
||||||
|
|
||||||
|
/// 'F-Num' in the spec; this plus the current octave determines channel frequency.
|
||||||
|
int period_ = 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;
|
||||||
|
|
||||||
|
/// Used internally to make both the 10-bit OPL2 frequency selection and 9-bit OPLL/VRC7 frequency
|
||||||
|
/// selections look the same when passed to the operators.
|
||||||
|
int frequency_shift_ = 0;
|
||||||
|
|
||||||
|
// Stored separately because carrier/modulator may not be unique per channel —
|
||||||
|
// on the OPLL there's an extra level of indirection.
|
||||||
|
OperatorState carrier_state_, modulator_state_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Channel_hpp */
|
184
Components/OPL2/Implementation/Operator.cpp
Normal file
184
Components/OPL2/Implementation/Operator.cpp
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
//
|
||||||
|
// Operator.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 15/04/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "Operator.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace Yamaha::OPL;
|
||||||
|
|
||||||
|
void Operator::set_attack_decay(uint8_t value) {
|
||||||
|
attack_rate_ = (value & 0xf0) >> 2;
|
||||||
|
decay_rate_ = (value & 0x0f) << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Operator::set_sustain_release(uint8_t value) {
|
||||||
|
sustain_level_ = (value & 0xf0) >> 4;
|
||||||
|
release_rate_ = (value & 0x0f) << 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Operator::set_scaling_output(uint8_t value) {
|
||||||
|
scaling_level_ = value >> 6;
|
||||||
|
attenuation_ = value & 0x3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Operator::set_waveform(uint8_t value) {
|
||||||
|
waveform_ = Operator::Waveform(value & 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Operator::set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value) {
|
||||||
|
apply_amplitude_modulation_ = value & 0x80;
|
||||||
|
apply_vibrato_ = value & 0x40;
|
||||||
|
use_sustain_level_ = value & 0x20;
|
||||||
|
keyboard_scaling_rate_ = value & 0x10;
|
||||||
|
frequency_multiple_ = value & 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Operator::is_audible(OperatorState &state, OperatorOverrides *overrides) {
|
||||||
|
if(state.adsr_phase_ == OperatorState::ADSRPhase::Release) {
|
||||||
|
if(overrides) {
|
||||||
|
if(overrides->attenuation == 0xf) return false;
|
||||||
|
} else {
|
||||||
|
if(attenuation_ == 0x3f) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state.adsr_attenuation_ != 511;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Operator::update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorOverrides *overrides) {
|
||||||
|
// Per the documentation:
|
||||||
|
//
|
||||||
|
// Delta phase = ( [desired freq] * 2^19 / [input clock / 72] ) / 2 ^ (b - 1)
|
||||||
|
//
|
||||||
|
// After experimentation, I think this gives rate calculation as formulated below.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// TODO: if this is the real formula (i.e. a downward shift for channel_octave), this is a highly
|
||||||
|
// suboptimal way to do this. Could just keep one accumulator and shift that downward for the result.
|
||||||
|
const int octave_divider = 2048 >> channel_octave;
|
||||||
|
state.divider_ %= octave_divider;
|
||||||
|
state.divider_ += channel_period;
|
||||||
|
state.raw_phase_ += multipliers[frequency_multiple_] * (state.divider_ / octave_divider);
|
||||||
|
// TODO: this last step introduces aliasing, but is a quick way to verify whether the multiplier should
|
||||||
|
// be applied also to the octave.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
};
|
||||||
|
state.phase = state.raw_phase_ & waveforms[int(waveform_)][(state.raw_phase_ >> 8) & 3];
|
||||||
|
|
||||||
|
// Key-on logic: any time it is false, be in the release state.
|
||||||
|
// On the leading edge of it becoming true, enter the attack state.
|
||||||
|
if(!key_on) {
|
||||||
|
state.adsr_phase_ = OperatorState::ADSRPhase::Release;
|
||||||
|
state.time_in_phase_ = 0;
|
||||||
|
} else if(!state.last_key_on_) {
|
||||||
|
state.adsr_phase_ = OperatorState::ADSRPhase::Attack;
|
||||||
|
state.time_in_phase_ = 0;
|
||||||
|
}
|
||||||
|
state.last_key_on_ = key_on;
|
||||||
|
|
||||||
|
// Adjust the ADSR attenuation appropriately;
|
||||||
|
// cf. http://forums.submarine.org.uk/phpBB/viewtopic.php?f=9&t=16 (primarily) for the source of the maths below.
|
||||||
|
|
||||||
|
// "An attack rate value of 52 (AR = 13) has 32 samples in the attack phase, an attack rate value of 48 (AR = 12)
|
||||||
|
// has 64 samples in the attack phase, but pairs of samples show the same envelope attenuation. I am however struggling to find a plausible algorithm to match the experimental results.
|
||||||
|
|
||||||
|
const auto current_phase = state.adsr_phase_;
|
||||||
|
switch(current_phase) {
|
||||||
|
case OperatorState::ADSRPhase::Attack: {
|
||||||
|
const int attack_rate = attack_rate_; // TODO: key scaling rate. Which I do not yet understand.
|
||||||
|
|
||||||
|
// Rules:
|
||||||
|
//
|
||||||
|
// An attack rate of '13' has 32 samples in the attack phase; a rate of '12' has the same 32 steps, but spread out over 64 samples, etc.
|
||||||
|
// An attack rate of '14' uses a divide by four instead of two.
|
||||||
|
// 15 is instantaneous.
|
||||||
|
|
||||||
|
if(attack_rate >= 56) {
|
||||||
|
state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 2) - 1;
|
||||||
|
} else {
|
||||||
|
const int sample_length = 1 << (14 - (attack_rate >> 2)); // TODO: don't throw away KSR bits.
|
||||||
|
if(!(state.time_in_phase_ & (sample_length - 1))) {
|
||||||
|
state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 3) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
|
||||||
|
if(attack_rate > 60 || state.adsr_attenuation_ <= 0) {
|
||||||
|
state.adsr_attenuation_ = 0;
|
||||||
|
state.adsr_phase_ = OperatorState::ADSRPhase::Decay;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OperatorState::ADSRPhase::Release:
|
||||||
|
case OperatorState::ADSRPhase::Decay:
|
||||||
|
{
|
||||||
|
// Rules:
|
||||||
|
//
|
||||||
|
// (relative to a 511 scale)
|
||||||
|
//
|
||||||
|
// A rate of 0 is no decay at all.
|
||||||
|
// A rate of 1 means increase 4 per cycle.
|
||||||
|
// A rate of 2 means increase 2 per cycle.
|
||||||
|
// A rate of 3 means increase 1 per cycle.
|
||||||
|
// A rate of 4 means increase 1 every other cycle.
|
||||||
|
// (etc)
|
||||||
|
const int decrease_rate = (state.adsr_phase_ == OperatorState::ADSRPhase::Decay) ? decay_rate_ : release_rate_; // TODO: again, key scaling rate.
|
||||||
|
|
||||||
|
if(decrease_rate) {
|
||||||
|
// TODO: don't throw away KSR bits.
|
||||||
|
switch(decrease_rate >> 2) {
|
||||||
|
case 1: state.adsr_attenuation_ += 4; break;
|
||||||
|
case 2: state.adsr_attenuation_ += 2; break;
|
||||||
|
default: {
|
||||||
|
const int sample_length = 1 << ((decrease_rate >> 2) - 4);
|
||||||
|
if(!(state.time_in_phase_ & (sample_length - 1))) {
|
||||||
|
++state.adsr_attenuation_;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to the proper range.
|
||||||
|
state.adsr_attenuation_ = std::min(state.adsr_attenuation_, 511);
|
||||||
|
|
||||||
|
// Check for the decay exit condition.
|
||||||
|
if(state.adsr_phase_ == OperatorState::ADSRPhase::Decay && state.adsr_attenuation_ >= (sustain_level_ << 5)) {
|
||||||
|
state.adsr_attenuation_ = sustain_level_ << 5;
|
||||||
|
state.adsr_phase_ = ((overrides && overrides->use_sustain_level) || use_sustain_level_) ? OperatorState::ADSRPhase::Sustain : OperatorState::ADSRPhase::Release;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OperatorState::ADSRPhase::Sustain:
|
||||||
|
// Nothing to do.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(state.adsr_phase_ == current_phase) {
|
||||||
|
++state.time_in_phase_;
|
||||||
|
} else {
|
||||||
|
state.time_in_phase_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the ADSR attenuation and overall channel attenuation, clamping to the permitted range.
|
||||||
|
if(overrides) {
|
||||||
|
state.attenuation = state.adsr_attenuation_ + (overrides->attenuation << 4);
|
||||||
|
} else {
|
||||||
|
state.attenuation = state.adsr_attenuation_ + (attenuation_ << 2);
|
||||||
|
}
|
||||||
|
}
|
134
Components/OPL2/Implementation/Operator.hpp
Normal file
134
Components/OPL2/Implementation/Operator.hpp
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//
|
||||||
|
// Operator.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 15/04/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Operator_hpp
|
||||||
|
#define Operator_hpp
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Yamaha {
|
||||||
|
namespace OPL {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Describes the ephemeral state of an operator.
|
||||||
|
*/
|
||||||
|
struct OperatorState {
|
||||||
|
public:
|
||||||
|
int phase = 0; // Will be in the range [0, 1023], mapping into a 1024-unit sine curve.
|
||||||
|
int attenuation = 1023; // Will be in the range [0, 1023].
|
||||||
|
|
||||||
|
private:
|
||||||
|
int divider_ = 0;
|
||||||
|
int raw_phase_ = 0;
|
||||||
|
|
||||||
|
enum class ADSRPhase {
|
||||||
|
Attack, Decay, Sustain, Release
|
||||||
|
} adsr_phase_ = ADSRPhase::Attack;
|
||||||
|
int time_in_phase_ = 0;
|
||||||
|
int adsr_attenuation_ = 511;
|
||||||
|
bool last_key_on_ = false;
|
||||||
|
|
||||||
|
friend class Operator;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Describes parts of an operator that are genuinely stored per-operator on the OPLL;
|
||||||
|
these can be provided to the Operator in order to have it ignore its local values
|
||||||
|
if the host is an OPLL or VRC7.
|
||||||
|
*/
|
||||||
|
struct OperatorOverrides {
|
||||||
|
int attenuation = 0;
|
||||||
|
bool use_sustain_level = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Models an operator.
|
||||||
|
|
||||||
|
In Yamaha FM terms, an operator is a combination of a few things:
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
Oscillator period isn't set directly, it's a multiple of the owning channel, in which
|
||||||
|
period 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);
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Sets this operator's waveform using the low two bits of @c value.
|
||||||
|
void set_waveform(uint8_t value);
|
||||||
|
|
||||||
|
/// 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 period multiplier.
|
||||||
|
void set_am_vibrato_hold_sustain_ksr_multiple(uint8_t value);
|
||||||
|
|
||||||
|
/// Provides one clock tick to the operator, along with the relevant parameters of its channel.
|
||||||
|
void update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorOverrides *overrides = nullptr);
|
||||||
|
|
||||||
|
/// @returns @c true if this channel currently has a non-zero output; @c false otherwise.
|
||||||
|
bool is_audible(OperatorState &state, OperatorOverrides *overrides = nullptr);
|
||||||
|
|
||||||
|
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 use_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 attenuation_ = 0;
|
||||||
|
|
||||||
|
/// Selects attenuation that is applied as a function of interval. Cf. p14.
|
||||||
|
int scaling_level_ = 0;
|
||||||
|
|
||||||
|
/// Sets the ADSR rates. These all provide the top four bits of a six-bit number;
|
||||||
|
/// the bottom two bits... are 'RL'?
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Operator_hpp */
|
151
Components/OPL2/Implementation/Tables.h
Normal file
151
Components/OPL2/Implementation/Tables.h
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
//
|
||||||
|
// Tables.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 15/04/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef Tables_h
|
||||||
|
#define Tables_h
|
||||||
|
|
||||||
|
namespace Yamaha {
|
||||||
|
namespace OPL {
|
||||||
|
|
||||||
|
/*
|
||||||
|
These are the OPL's built-in log-sin and exponentiation tables, as recovered by
|
||||||
|
Matthew Gambrell and Olli Niemitalo in 'OPLx decapsulated'. Despite the formulas
|
||||||
|
being well known, I've elected not to generate these at runtime because even if I
|
||||||
|
did, I'd just end up with the proper values laid out in full in a unit test, and
|
||||||
|
they're very compact.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/// Defines the first quadrant of a sine curve, as calculated by round(-log(sin((x+0.5)*pi/256/2))/log(2)*256).
|
||||||
|
constexpr int log_sin[] = {
|
||||||
|
2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137,
|
||||||
|
1091, 1050, 1013, 979, 949, 920, 894, 869,
|
||||||
|
846, 825, 804, 785, 767, 749, 732, 717,
|
||||||
|
701, 687, 672, 659, 646, 633, 621, 609,
|
||||||
|
598, 587, 576, 566, 556, 546, 536, 527,
|
||||||
|
518, 509, 501, 492, 484, 476, 468, 461,
|
||||||
|
453, 446, 439, 432, 425, 418, 411, 405,
|
||||||
|
399, 392, 386, 380, 375, 369, 363, 358,
|
||||||
|
352, 347, 341, 336, 331, 326, 321, 316,
|
||||||
|
311, 307, 302, 297, 293, 289, 284, 280,
|
||||||
|
276, 271, 267, 263, 259, 255, 251, 248,
|
||||||
|
244, 240, 236, 233, 229, 226, 222, 219,
|
||||||
|
215, 212, 209, 205, 202, 199, 196, 193,
|
||||||
|
190, 187, 184, 181, 178, 175, 172, 169,
|
||||||
|
167, 164, 161, 159, 156, 153, 151, 148,
|
||||||
|
146, 143, 141, 138, 136, 134, 131, 129,
|
||||||
|
127, 125, 122, 120, 118, 116, 114, 112,
|
||||||
|
110, 108, 106, 104, 102, 100, 98, 96,
|
||||||
|
94, 92, 91, 89, 87, 85, 83, 82,
|
||||||
|
80, 78, 77, 75, 74, 72, 70, 69,
|
||||||
|
67, 66, 64, 63, 62, 60, 59, 57,
|
||||||
|
56, 55, 53, 52, 51, 49, 48, 47,
|
||||||
|
46, 45, 43, 42, 41, 40, 39, 38,
|
||||||
|
37, 36, 35, 34, 33, 32, 31, 30,
|
||||||
|
29, 28, 27, 26, 25, 24, 23, 23,
|
||||||
|
22, 21, 20, 20, 19, 18, 17, 17,
|
||||||
|
16, 15, 15, 14, 13, 13, 12, 12,
|
||||||
|
11, 10, 10, 9, 9, 8, 8, 7,
|
||||||
|
7, 7, 6, 6, 5, 5, 5, 4,
|
||||||
|
4, 4, 3, 3, 3, 2, 2, 2,
|
||||||
|
2, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Defines exponentiation; to get exponential of x is (exp[x&255] + 1024) << (x >> 8)
|
||||||
|
constexpr int exp[] = {
|
||||||
|
0, 3, 6, 8, 11, 14, 17, 20,
|
||||||
|
22, 25, 28, 31, 34, 37, 40, 42,
|
||||||
|
45, 48, 51, 54, 57, 60, 63, 66,
|
||||||
|
69, 72, 75, 78, 81, 84, 87, 90,
|
||||||
|
93, 96, 99, 102, 105, 108, 111, 114,
|
||||||
|
117, 120, 123, 126, 130, 133, 136, 139,
|
||||||
|
142, 145, 148, 152, 155, 158, 161, 164,
|
||||||
|
168, 171, 174, 177, 181, 184, 187, 190,
|
||||||
|
194, 197, 200, 204, 207, 210, 214, 217,
|
||||||
|
220, 224, 227, 231, 234, 237, 241, 244,
|
||||||
|
248, 251, 255, 258, 262, 265, 268, 272,
|
||||||
|
276, 279, 283, 286, 290, 293, 297, 300,
|
||||||
|
304, 308, 311, 315, 318, 322, 326, 329,
|
||||||
|
333, 337, 340, 344, 348, 352, 355, 359,
|
||||||
|
363, 367, 370, 374, 378, 382, 385, 389,
|
||||||
|
393, 397, 401, 405, 409, 412, 416, 420,
|
||||||
|
424, 428, 432, 436, 440, 444, 448, 452,
|
||||||
|
456, 460, 464, 468, 472, 476, 480, 484,
|
||||||
|
488, 492, 496, 501, 505, 509, 513, 517,
|
||||||
|
521, 526, 530, 534, 538, 542, 547, 551,
|
||||||
|
555, 560, 564, 568, 572, 577, 581, 585,
|
||||||
|
590, 594, 599, 603, 607, 612, 616, 621,
|
||||||
|
625, 630, 634, 639, 643, 648, 652, 657,
|
||||||
|
661, 666, 670, 675, 680, 684, 689, 693,
|
||||||
|
698, 703, 708, 712, 717, 722, 726, 731,
|
||||||
|
736, 741, 745, 750, 755, 760, 765, 770,
|
||||||
|
774, 779, 784, 789, 794, 799, 804, 809,
|
||||||
|
814, 819, 824, 829, 834, 839, 844, 849,
|
||||||
|
854, 859, 864, 869, 874, 880, 885, 890,
|
||||||
|
895, 900, 906, 911, 916, 921, 927, 932,
|
||||||
|
937, 942, 948, 953, 959, 964, 969, 975,
|
||||||
|
980, 986, 991, 996, 1002, 1007, 1013, 1018
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Credit for the fixed register lists goes to Nuke.YKT; I found them at:
|
||||||
|
https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instrument_rom
|
||||||
|
|
||||||
|
The arrays below begin with channel 1, then each line is a single channel defined
|
||||||
|
in exactly the same terms as the OPL's user-defined channel.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
constexpr uint8_t opll_patch_set[] = {
|
||||||
|
0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17,
|
||||||
|
0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13,
|
||||||
|
0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x11, 0x23,
|
||||||
|
0x31, 0x61, 0x0e, 0x07, 0xa8, 0x64, 0x70, 0x27,
|
||||||
|
0x32, 0x21, 0x1e, 0x06, 0xe0, 0x76, 0x00, 0x28,
|
||||||
|
0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18,
|
||||||
|
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x10, 0x07,
|
||||||
|
0x23, 0x21, 0x2d, 0x14, 0xa2, 0x72, 0x00, 0x07,
|
||||||
|
0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17,
|
||||||
|
0x41, 0x61, 0x0b, 0x18, 0x85, 0xf7, 0x71, 0x07,
|
||||||
|
0x13, 0x01, 0x83, 0x11, 0xfa, 0xe4, 0x10, 0x04,
|
||||||
|
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
|
||||||
|
0x61, 0x50, 0x0c, 0x05, 0xc2, 0xf5, 0x20, 0x42,
|
||||||
|
0x01, 0x01, 0x55, 0x03, 0xc9, 0x95, 0x03, 0x02,
|
||||||
|
0x61, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0x40, 0x13,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr uint8_t vrc7_patch_set[] = {
|
||||||
|
0x03, 0x21, 0x05, 0x06, 0xe8, 0x81, 0x42, 0x27,
|
||||||
|
0x13, 0x41, 0x14, 0x0d, 0xd8, 0xf6, 0x23, 0x12,
|
||||||
|
0x11, 0x11, 0x08, 0x08, 0xfa, 0xb2, 0x20, 0x12,
|
||||||
|
0x31, 0x61, 0x0c, 0x07, 0xa8, 0x64, 0x61, 0x27,
|
||||||
|
0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28,
|
||||||
|
0x02, 0x01, 0x06, 0x00, 0xa3, 0xe2, 0xf4, 0xf4,
|
||||||
|
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07,
|
||||||
|
0x23, 0x21, 0x22, 0x17, 0xa2, 0x72, 0x01, 0x17,
|
||||||
|
0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01,
|
||||||
|
0xb5, 0x01, 0x0f, 0x0f, 0xa8, 0xa5, 0x51, 0x02,
|
||||||
|
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
|
||||||
|
0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16,
|
||||||
|
0x01, 0x02, 0xd3, 0x05, 0xc9, 0x95, 0x03, 0x02,
|
||||||
|
0x61, 0x63, 0x0c, 0x00, 0x94, 0xc0, 0x33, 0xf6,
|
||||||
|
0x21, 0x72, 0x0d, 0x00, 0xc1, 0xd5, 0x56, 0x06,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr uint8_t percussion_patch_set[] = {
|
||||||
|
0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d,
|
||||||
|
0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x48,
|
||||||
|
0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* Tables_h */
|
@ -11,98 +11,12 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace {
|
#include "Implementation/Tables.h"
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Credit for the fixed register lists goes to Nuke.YKT; I found them at:
|
|
||||||
https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#ym2413_instrument_rom
|
|
||||||
|
|
||||||
The arrays below begin with channel 1, each line is a single channel and the
|
|
||||||
format per channel is, from first byte to eighth:
|
|
||||||
|
|
||||||
Bytes 1 and 2:
|
|
||||||
Registers 1 and 2, i.e. modulator and carrier
|
|
||||||
amplitude modulation select, vibrato select, etc.
|
|
||||||
|
|
||||||
Byte 3:
|
|
||||||
b7, b6: modulator key scale level
|
|
||||||
b5...b0: modulator total level (inverted)
|
|
||||||
|
|
||||||
Byte 4:
|
|
||||||
b7: carrier key scale level
|
|
||||||
b3...b0: feedback level and waveform selects as per register 4
|
|
||||||
|
|
||||||
Bytes 5, 6:
|
|
||||||
Registers 4 and 5, i.e. decay and attack rate, modulator and carrier.
|
|
||||||
|
|
||||||
Bytes 7, 8:
|
|
||||||
Registers 6 and 7, i.e. decay-sustain level and release rate, modulator and carrier.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
constexpr uint8_t opll_patch_set[] = {
|
|
||||||
0x71, 0x61, 0x1e, 0x17, 0xd0, 0x78, 0x00, 0x17,
|
|
||||||
0x13, 0x41, 0x1a, 0x0d, 0xd8, 0xf7, 0x23, 0x13,
|
|
||||||
0x13, 0x01, 0x99, 0x00, 0xf2, 0xc4, 0x11, 0x23,
|
|
||||||
0x31, 0x61, 0x0e, 0x07, 0xa8, 0x64, 0x70, 0x27,
|
|
||||||
0x32, 0x21, 0x1e, 0x06, 0xe0, 0x76, 0x00, 0x28,
|
|
||||||
0x31, 0x22, 0x16, 0x05, 0xe0, 0x71, 0x00, 0x18,
|
|
||||||
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x10, 0x07,
|
|
||||||
0x23, 0x21, 0x2d, 0x14, 0xa2, 0x72, 0x00, 0x07,
|
|
||||||
0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17,
|
|
||||||
0x41, 0x61, 0x0b, 0x18, 0x85, 0xf7, 0x71, 0x07,
|
|
||||||
0x13, 0x01, 0x83, 0x11, 0xfa, 0xe4, 0x10, 0x04,
|
|
||||||
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
|
|
||||||
0x61, 0x50, 0x0c, 0x05, 0xc2, 0xf5, 0x20, 0x42,
|
|
||||||
0x01, 0x01, 0x55, 0x03, 0xc9, 0x95, 0x03, 0x02,
|
|
||||||
0x61, 0x41, 0x89, 0x03, 0xf1, 0xe4, 0x40, 0x13,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr uint8_t vrc7_patch_set[] = {
|
|
||||||
0x03, 0x21, 0x05, 0x06, 0xe8, 0x81, 0x42, 0x27,
|
|
||||||
0x13, 0x41, 0x14, 0x0d, 0xd8, 0xf6, 0x23, 0x12,
|
|
||||||
0x11, 0x11, 0x08, 0x08, 0xfa, 0xb2, 0x20, 0x12,
|
|
||||||
0x31, 0x61, 0x0c, 0x07, 0xa8, 0x64, 0x61, 0x27,
|
|
||||||
0x32, 0x21, 0x1e, 0x06, 0xe1, 0x76, 0x01, 0x28,
|
|
||||||
0x02, 0x01, 0x06, 0x00, 0xa3, 0xe2, 0xf4, 0xf4,
|
|
||||||
0x21, 0x61, 0x1d, 0x07, 0x82, 0x81, 0x11, 0x07,
|
|
||||||
0x23, 0x21, 0x22, 0x17, 0xa2, 0x72, 0x01, 0x17,
|
|
||||||
0x35, 0x11, 0x25, 0x00, 0x40, 0x73, 0x72, 0x01,
|
|
||||||
0xb5, 0x01, 0x0f, 0x0f, 0xa8, 0xa5, 0x51, 0x02,
|
|
||||||
0x17, 0xc1, 0x24, 0x07, 0xf8, 0xf8, 0x22, 0x12,
|
|
||||||
0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x18, 0x16,
|
|
||||||
0x01, 0x02, 0xd3, 0x05, 0xc9, 0x95, 0x03, 0x02,
|
|
||||||
0x61, 0x63, 0x0c, 0x00, 0x94, 0xc0, 0x33, 0xf6,
|
|
||||||
0x21, 0x72, 0x0d, 0x00, 0xc1, 0xd5, 0x56, 0x06,
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr uint8_t percussion_patch_set[] = {
|
|
||||||
0x01, 0x01, 0x18, 0x0f, 0xdf, 0xf8, 0x6a, 0x6d,
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0xc8, 0xd8, 0xa7, 0x48,
|
|
||||||
0x05, 0x01, 0x00, 0x00, 0xf8, 0xaa, 0x59, 0x55,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace Yamaha::OPL;
|
using namespace Yamaha::OPL;
|
||||||
|
|
||||||
|
|
||||||
template <typename Child>
|
template <typename Child>
|
||||||
OPLBase<Child>::OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
OPLBase<Child>::OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||||
// Populate the exponential and log-sine tables; formulas here taken from Matthew Gambrell
|
|
||||||
// and Olli Niemitalo's decapping and reverse-engineering of the OPL2.
|
|
||||||
for(int c = 0; c < 256; ++c) {
|
|
||||||
exponential_[c] = int(round((pow(2.0, double(c) / 256.0) - 1.0) * 1024.0));
|
|
||||||
|
|
||||||
const double sine = sin((double(c) + 0.5) * M_PI/512.0);
|
|
||||||
log_sin_[c] = int(
|
|
||||||
round(
|
|
||||||
-log(sine) / log(2.0) * 256.0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Child>
|
template <typename Child>
|
||||||
void OPLBase<Child>::write(uint16_t address, uint8_t value) {
|
void OPLBase<Child>::write(uint16_t address, uint8_t value) {
|
||||||
@ -237,7 +151,7 @@ void OPLL::write_register(uint8_t address, uint8_t value) {
|
|||||||
// Set sustain on/off, key on/off, octave and a single extra bit of frequency.
|
// 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.
|
// So they're a lot like OPLL registers 0xb0 to 0xb8, but not identical.
|
||||||
channels_[index].set_9bit_frequency_octave_key_on(value);
|
channels_[index].set_9bit_frequency_octave_key_on(value);
|
||||||
channels_[index].overrides.hold_sustain_level = value & 0x20;
|
channels_[index].overrides.use_sustain_level = value & 0x20;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
@ -386,139 +300,3 @@ uint8_t OPL2::read(uint16_t address) {
|
|||||||
// b5 = timer 2 flag
|
// b5 = timer 2 flag
|
||||||
return 0xff;
|
return 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Operators
|
|
||||||
|
|
||||||
void Operator::update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorOverrides *overrides) {
|
|
||||||
// Per the documentation:
|
|
||||||
//
|
|
||||||
// Delta phase = ( [desired freq] * 2^19 / [input clock / 72] ) / 2 ^ (b - 1)
|
|
||||||
//
|
|
||||||
// After experimentation, I think this gives rate calculation as formulated below.
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// TODO: if this is the real formula (i.e. a downward shift for channel_octave), this is a highly
|
|
||||||
// suboptimal way to do this. Could just keep one accumulator and shift that downward for the result.
|
|
||||||
const int octave_divider = 2048 >> channel_octave;
|
|
||||||
state.divider_ %= octave_divider;
|
|
||||||
state.divider_ += channel_period;
|
|
||||||
state.raw_phase_ += multipliers[frequency_multiple] * (state.divider_ / octave_divider);
|
|
||||||
// TODO: this last step introduces aliasing, but is a quick way to verify whether the multiplier should
|
|
||||||
// be applied also to the octave.
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
};
|
|
||||||
state.phase = state.raw_phase_ & waveforms[int(waveform)][(state.raw_phase_ >> 8) & 3];
|
|
||||||
|
|
||||||
// Key-on logic: any time it is false, be in the release state.
|
|
||||||
// On the leading edge of it becoming true, enter the attack state.
|
|
||||||
if(!key_on) {
|
|
||||||
state.adsr_phase_ = OperatorState::ADSRPhase::Release;
|
|
||||||
state.time_in_phase_ = 0;
|
|
||||||
} else if(!state.last_key_on_) {
|
|
||||||
state.adsr_phase_ = OperatorState::ADSRPhase::Attack;
|
|
||||||
state.time_in_phase_ = 0;
|
|
||||||
}
|
|
||||||
state.last_key_on_ = key_on;
|
|
||||||
|
|
||||||
// Adjust the ADSR attenuation appropriately;
|
|
||||||
// cf. http://forums.submarine.org.uk/phpBB/viewtopic.php?f=9&t=16 (primarily) for the source of the maths below.
|
|
||||||
|
|
||||||
// "An attack rate value of 52 (AR = 13) has 32 samples in the attack phase, an attack rate value of 48 (AR = 12)
|
|
||||||
// has 64 samples in the attack phase, but pairs of samples show the same envelope attenuation. I am however struggling to find a plausible algorithm to match the experimental results.
|
|
||||||
|
|
||||||
const auto current_phase = state.adsr_phase_;
|
|
||||||
switch(current_phase) {
|
|
||||||
case OperatorState::ADSRPhase::Attack: {
|
|
||||||
const int attack_rate = attack_rate_; // TODO: key scaling rate. Which I do not yet understand.
|
|
||||||
|
|
||||||
// Rules:
|
|
||||||
//
|
|
||||||
// An attack rate of '13' has 32 samples in the attack phase; a rate of '12' has the same 32 steps, but spread out over 64 samples, etc.
|
|
||||||
// An attack rate of '14' uses a divide by four instead of two.
|
|
||||||
// 15 is instantaneous.
|
|
||||||
|
|
||||||
if(attack_rate >= 56) {
|
|
||||||
state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 2) - 1;
|
|
||||||
} else {
|
|
||||||
const int sample_length = 1 << (14 - (attack_rate >> 2)); // TODO: don't throw away KSR bits.
|
|
||||||
if(!(state.time_in_phase_ & (sample_length - 1))) {
|
|
||||||
state.adsr_attenuation_ = state.adsr_attenuation_ - (state.adsr_attenuation_ >> 3) - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Two possible terminating conditions: (i) the attack rate is 15; (ii) full volume has been reached.
|
|
||||||
if(attack_rate > 60 || state.adsr_attenuation_ <= 0) {
|
|
||||||
state.adsr_attenuation_ = 0;
|
|
||||||
state.adsr_phase_ = OperatorState::ADSRPhase::Decay;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case OperatorState::ADSRPhase::Release:
|
|
||||||
case OperatorState::ADSRPhase::Decay:
|
|
||||||
{
|
|
||||||
// Rules:
|
|
||||||
//
|
|
||||||
// (relative to a 511 scale)
|
|
||||||
//
|
|
||||||
// A rate of 0 is no decay at all.
|
|
||||||
// A rate of 1 means increase 4 per cycle.
|
|
||||||
// A rate of 2 means increase 2 per cycle.
|
|
||||||
// A rate of 3 means increase 1 per cycle.
|
|
||||||
// A rate of 4 means increase 1 every other cycle.
|
|
||||||
// (etc)
|
|
||||||
const int decrease_rate = (state.adsr_phase_ == OperatorState::ADSRPhase::Decay) ? decay_rate_ : release_rate_; // TODO: again, key scaling rate.
|
|
||||||
|
|
||||||
if(decrease_rate) {
|
|
||||||
// TODO: don't throw away KSR bits.
|
|
||||||
switch(decrease_rate >> 2) {
|
|
||||||
case 1: state.adsr_attenuation_ += 4; break;
|
|
||||||
case 2: state.adsr_attenuation_ += 2; break;
|
|
||||||
default: {
|
|
||||||
const int sample_length = 1 << ((decrease_rate >> 2) - 4);
|
|
||||||
if(!(state.time_in_phase_ & (sample_length - 1))) {
|
|
||||||
++state.adsr_attenuation_;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp to the proper range.
|
|
||||||
state.adsr_attenuation_ = std::min(state.adsr_attenuation_, 511);
|
|
||||||
|
|
||||||
// Check for the decay exit condition.
|
|
||||||
if(state.adsr_phase_ == OperatorState::ADSRPhase::Decay && state.adsr_attenuation_ >= (sustain_level_ << 5)) {
|
|
||||||
state.adsr_attenuation_ = sustain_level_ << 5;
|
|
||||||
state.adsr_phase_ = ((overrides && overrides->hold_sustain_level) || hold_sustain_level) ? OperatorState::ADSRPhase::Sustain : OperatorState::ADSRPhase::Release;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case OperatorState::ADSRPhase::Sustain:
|
|
||||||
// Nothing to do.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(state.adsr_phase_ == current_phase) {
|
|
||||||
++state.time_in_phase_;
|
|
||||||
} else {
|
|
||||||
state.time_in_phase_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combine the ADSR attenuation and overall channel attenuation, clamping to the permitted range.
|
|
||||||
if(overrides) {
|
|
||||||
state.attenuation = state.adsr_attenuation_ + (overrides->attenuation << 4);
|
|
||||||
} else {
|
|
||||||
state.attenuation = state.adsr_attenuation_ + (attenuation_ << 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -13,241 +13,14 @@
|
|||||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||||
#include "../../Numeric/LFSR.hpp"
|
#include "../../Numeric/LFSR.hpp"
|
||||||
|
|
||||||
|
#include "Implementation/Channel.hpp"
|
||||||
|
#include "Implementation/Operator.hpp"
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace Yamaha {
|
namespace Yamaha {
|
||||||
|
|
||||||
|
|
||||||
namespace OPL {
|
namespace OPL {
|
||||||
|
|
||||||
/*!
|
|
||||||
Describes the ephemeral state of an operator.
|
|
||||||
*/
|
|
||||||
struct OperatorState {
|
|
||||||
public:
|
|
||||||
int phase = 0; // Will be in the range [0, 1023], mapping into a 1024-unit sine curve.
|
|
||||||
int attenuation = 1023; // Will be in the range [0, 1023].
|
|
||||||
|
|
||||||
private:
|
|
||||||
int divider_ = 0;
|
|
||||||
int raw_phase_ = 0;
|
|
||||||
|
|
||||||
enum class ADSRPhase {
|
|
||||||
Attack, Decay, Sustain, Release
|
|
||||||
} adsr_phase_ = ADSRPhase::Attack;
|
|
||||||
int time_in_phase_ = 0;
|
|
||||||
int adsr_attenuation_ = 511;
|
|
||||||
bool last_key_on_ = false;
|
|
||||||
|
|
||||||
friend class Operator;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Describes parts of an operator that are genuinely stored per-operator on the OPLL;
|
|
||||||
these can be provided to the Operator in order to have it ignore its local values
|
|
||||||
if the host is an OPLL or VRC7.
|
|
||||||
*/
|
|
||||||
struct OperatorOverrides {
|
|
||||||
int attenuation = 0;
|
|
||||||
bool hold_sustain_level = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Models an operator.
|
|
||||||
|
|
||||||
In Yamaha FM terms, an operator is a combination of a few things:
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
Oscillator period isn't set directly, it's a multiple of the owning channel, in which
|
|
||||||
period 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 & 0xf0) >> 2;
|
|
||||||
decay_rate_ = (value & 0x0f) << 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 & 0xf0) >> 4;
|
|
||||||
release_rate_ = (value & 0x0f) << 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
attenuation_ = value & 0x3f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets this operator's waveform using the low two bits of @c value.
|
|
||||||
void set_waveform(uint8_t value) {
|
|
||||||
waveform = Operator::Waveform(value & 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 period 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void update(OperatorState &state, bool key_on, int channel_period, int channel_octave, OperatorOverrides *overrides = nullptr);
|
|
||||||
|
|
||||||
bool is_audible(OperatorState &state, OperatorOverrides *overrides = nullptr) {
|
|
||||||
if(state.adsr_phase_ == OperatorState::ADSRPhase::Release) {
|
|
||||||
if(overrides) {
|
|
||||||
if(overrides->attenuation == 0xf) return false;
|
|
||||||
} else {
|
|
||||||
if(attenuation_ == 0x3f) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state.adsr_attenuation_ != 511;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 attenuation_ = 0;
|
|
||||||
|
|
||||||
/// Selects attenuation that is applied as a function of interval. Cf. p14.
|
|
||||||
int scaling_level = 0;
|
|
||||||
|
|
||||||
/// Sets the ADSR rates. These all provide the top four bits of a six-bit number;
|
|
||||||
/// the bottom two bits... are 'RL'?
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
class Channel {
|
|
||||||
public:
|
|
||||||
/// Sets the low 8 bits of frequency control.
|
|
||||||
void set_frequency_low(uint8_t value) {
|
|
||||||
period_ = (period_ &~0xff) | value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the high two bits of a 10-bit frequency control, along with this channel's
|
|
||||||
/// block/octave, and key on or off.
|
|
||||||
void set_10bit_frequency_octave_key_on(uint8_t value) {
|
|
||||||
period_ = (period_ & 0xff) | ((value & 3) << 8);
|
|
||||||
octave = (value >> 2) & 0x7;
|
|
||||||
key_on = value & 0x20;
|
|
||||||
frequency_shift = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the high two bits of a 9-bit frequency control, along with this channel's
|
|
||||||
/// block/octave, and key on or off.
|
|
||||||
void set_9bit_frequency_octave_key_on(uint8_t value) {
|
|
||||||
period_ = (period_ & 0xff) | ((value & 1) << 8);
|
|
||||||
octave = (value >> 1) & 0x7;
|
|
||||||
key_on = value & 0x10;;
|
|
||||||
frequency_shift = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the amount of feedback provided to the first operator (i.e. the modulator)
|
|
||||||
/// associated with this channel, and whether FM synthesis is in use.
|
|
||||||
void set_feedback_mode(uint8_t value) {
|
|
||||||
feedback_strength = (value >> 1) & 0x7;
|
|
||||||
use_fm_synthesis = value & 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This should be called at a rate of around 49,716 Hz; it returns the current output level
|
|
||||||
/// level for this channel.
|
|
||||||
int update(Operator *modulator, Operator *carrier, OperatorOverrides *modulator_overrides = nullptr, OperatorOverrides *carrier_overrides = nullptr) {
|
|
||||||
modulator->update(modulator_state_, key_on, period_ << frequency_shift, octave, modulator_overrides);
|
|
||||||
carrier->update(carrier_state_, key_on, period_ << frequency_shift, octave, carrier_overrides);
|
|
||||||
|
|
||||||
// TODO: almost everything. This is a quick test.
|
|
||||||
// Specifically: use lookup tables.
|
|
||||||
const auto modulator_level = level(modulator_state_, 0.0f); // TODO: what's the proper scaling on this?
|
|
||||||
return int(level(carrier_state_, modulator_level) * 20'000.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @returns @c true if this channel is currently producing any audio; @c false otherwise;
|
|
||||||
bool is_audible(Operator *carrier, OperatorOverrides *carrier_overrides = nullptr) {
|
|
||||||
return carrier->is_audible(carrier_state_, carrier_overrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
float level(OperatorState &state, float modulator_level) {
|
|
||||||
const float phase = modulator_level + float(state.phase) / 1024.0f;
|
|
||||||
const float phase_attenuation = logf(1.0f + sinf(float(M_PI) * 2.0f * phase));
|
|
||||||
const float total_attenuation = phase_attenuation + float(state.attenuation) / 1023.0f;
|
|
||||||
const float result = expf(total_attenuation / 2.0f);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 'F-Num' in the spec; this plus the current octave determines channel frequency.
|
|
||||||
int period_ = 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;
|
|
||||||
|
|
||||||
/// Used internally to make both the 10-bit OPL2 frequency selection and 9-bit OPLL/VRC7 frequency
|
|
||||||
/// selections look the same when passed to the operators.
|
|
||||||
int frequency_shift = 0;
|
|
||||||
|
|
||||||
// Stored separately because carrier/modulator may not be unique per channel —
|
|
||||||
// on the OPLL there's an extra level of indirection.
|
|
||||||
OperatorState carrier_state_, modulator_state_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
|
||||||
public:
|
public:
|
||||||
void write(uint16_t address, uint8_t value);
|
void write(uint16_t address, uint8_t value);
|
||||||
@ -257,9 +30,6 @@ template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource
|
|||||||
|
|
||||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||||
|
|
||||||
int exponential_[256];
|
|
||||||
int log_sin_[256];
|
|
||||||
|
|
||||||
uint8_t depth_rhythm_control_;
|
uint8_t depth_rhythm_control_;
|
||||||
uint8_t csm_keyboard_split_;
|
uint8_t csm_keyboard_split_;
|
||||||
bool waveform_enable_;
|
bool waveform_enable_;
|
||||||
|
@ -778,6 +778,12 @@
|
|||||||
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
|
4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; };
|
||||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; };
|
||||||
4BC0CB282446BC7B00A79DBB /* OPLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */; };
|
4BC0CB282446BC7B00A79DBB /* OPLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */; };
|
||||||
|
4BC0CB322447EC7D00A79DBB /* Operator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB302447EC7C00A79DBB /* Operator.cpp */; };
|
||||||
|
4BC0CB332447EC7D00A79DBB /* Operator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB302447EC7C00A79DBB /* Operator.cpp */; };
|
||||||
|
4BC0CB342447EC7D00A79DBB /* Operator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB302447EC7C00A79DBB /* Operator.cpp */; };
|
||||||
|
4BC0CB372447EC9A00A79DBB /* Channel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB352447EC9A00A79DBB /* Channel.cpp */; };
|
||||||
|
4BC0CB382447EC9A00A79DBB /* Channel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB352447EC9A00A79DBB /* Channel.cpp */; };
|
||||||
|
4BC0CB392447EC9A00A79DBB /* Channel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC0CB352447EC9A00A79DBB /* Channel.cpp */; };
|
||||||
4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
|
4BC131702346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
|
||||||
4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
|
4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */; };
|
||||||
4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; };
|
4BC131762346DE9100E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131752346DE9100E4FF3D /* StaticAnalyser.cpp */; };
|
||||||
@ -1650,6 +1656,11 @@
|
|||||||
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
|
4BBFE83E21015DAE00BF1C40 /* CSJoystickManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSJoystickManager.h; sourceTree = "<group>"; };
|
||||||
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
|
4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = "<group>"; };
|
||||||
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OPLTests.mm; sourceTree = "<group>"; };
|
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OPLTests.mm; sourceTree = "<group>"; };
|
||||||
|
4BC0CB302447EC7C00A79DBB /* Operator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Operator.cpp; sourceTree = "<group>"; };
|
||||||
|
4BC0CB312447EC7C00A79DBB /* Operator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Operator.hpp; sourceTree = "<group>"; };
|
||||||
|
4BC0CB352447EC9A00A79DBB /* Channel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Channel.cpp; sourceTree = "<group>"; };
|
||||||
|
4BC0CB362447EC9A00A79DBB /* Channel.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Channel.hpp; sourceTree = "<group>"; };
|
||||||
|
4BC0CB3A2447ECAE00A79DBB /* Tables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tables.h; sourceTree = "<group>"; };
|
||||||
4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = "<group>"; };
|
||||||
4BC1316E2346DE5000E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
4BC1316E2346DE5000E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||||
4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = "<group>"; };
|
||||||
@ -3496,6 +3507,18 @@
|
|||||||
path = "Joystick Manager";
|
path = "Joystick Manager";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4BC0CB2F2447EC7C00A79DBB /* Implementation */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4BC0CB302447EC7C00A79DBB /* Operator.cpp */,
|
||||||
|
4BC0CB312447EC7C00A79DBB /* Operator.hpp */,
|
||||||
|
4BC0CB352447EC9A00A79DBB /* Channel.cpp */,
|
||||||
|
4BC0CB362447EC9A00A79DBB /* Channel.hpp */,
|
||||||
|
4BC0CB3A2447ECAE00A79DBB /* Tables.h */,
|
||||||
|
);
|
||||||
|
path = Implementation;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4BC1316C2346DE5000E4FF3D /* Atari2600 */ = {
|
4BC1316C2346DE5000E4FF3D /* Atari2600 */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -3528,6 +3551,7 @@
|
|||||||
4BC57CDF2436BFE000FBC404 /* OPL2 */ = {
|
4BC57CDF2436BFE000FBC404 /* OPL2 */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4BC0CB2F2447EC7C00A79DBB /* Implementation */,
|
||||||
4BC57CE02436BFE000FBC404 /* OPL2.cpp */,
|
4BC57CE02436BFE000FBC404 /* OPL2.cpp */,
|
||||||
4BC57CE12436BFE000FBC404 /* OPL2.hpp */,
|
4BC57CE12436BFE000FBC404 /* OPL2.hpp */,
|
||||||
);
|
);
|
||||||
@ -4336,6 +4360,7 @@
|
|||||||
4B894529201967B4007DE474 /* Disk.cpp in Sources */,
|
4B894529201967B4007DE474 /* Disk.cpp in Sources */,
|
||||||
4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */,
|
4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */,
|
||||||
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
|
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
|
||||||
|
4BC0CB392447EC9A00A79DBB /* Channel.cpp in Sources */,
|
||||||
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
|
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
|
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
|
||||||
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
|
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
|
||||||
@ -4438,6 +4463,7 @@
|
|||||||
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */,
|
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */,
|
||||||
4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
|
4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
|
||||||
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
|
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
|
||||||
|
4BC0CB342447EC7D00A79DBB /* Operator.cpp in Sources */,
|
||||||
4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */,
|
4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */,
|
||||||
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */,
|
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */,
|
||||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
||||||
@ -4513,6 +4539,7 @@
|
|||||||
4B89451E201967B4007DE474 /* Tape.cpp in Sources */,
|
4B89451E201967B4007DE474 /* Tape.cpp in Sources */,
|
||||||
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */,
|
4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */,
|
||||||
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,
|
||||||
|
4BC0CB322447EC7D00A79DBB /* Operator.cpp in Sources */,
|
||||||
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */,
|
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */,
|
||||||
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
|
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
|
||||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
||||||
@ -4671,6 +4698,7 @@
|
|||||||
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */,
|
4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */,
|
||||||
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
|
4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */,
|
||||||
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
|
4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */,
|
||||||
|
4BC0CB372447EC9A00A79DBB /* Channel.cpp in Sources */,
|
||||||
4B894524201967B4007DE474 /* Tape.cpp in Sources */,
|
4B894524201967B4007DE474 /* Tape.cpp in Sources */,
|
||||||
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,
|
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */,
|
||||||
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */,
|
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */,
|
||||||
@ -4739,6 +4767,7 @@
|
|||||||
4B778F4A23A5F1FB0000D260 /* StaticAnalyser.cpp in Sources */,
|
4B778F4A23A5F1FB0000D260 /* StaticAnalyser.cpp in Sources */,
|
||||||
4BD91D772401C2B8007BDC91 /* PatrikRakTests.swift in Sources */,
|
4BD91D772401C2B8007BDC91 /* PatrikRakTests.swift in Sources */,
|
||||||
4B680CE223A5553100451D43 /* 68000ComparativeTests.mm in Sources */,
|
4B680CE223A5553100451D43 /* 68000ComparativeTests.mm in Sources */,
|
||||||
|
4BC0CB332447EC7D00A79DBB /* Operator.cpp in Sources */,
|
||||||
4B778F3723A5F11C0000D260 /* Parser.cpp in Sources */,
|
4B778F3723A5F11C0000D260 /* Parser.cpp in Sources */,
|
||||||
4B778F4523A5F1CD0000D260 /* SegmentParser.cpp in Sources */,
|
4B778F4523A5F1CD0000D260 /* SegmentParser.cpp in Sources */,
|
||||||
4B90467422C6FADD000E2074 /* 68000BitwiseTests.mm in Sources */,
|
4B90467422C6FADD000E2074 /* 68000BitwiseTests.mm in Sources */,
|
||||||
@ -4826,6 +4855,7 @@
|
|||||||
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */,
|
4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */,
|
||||||
4B778F1223A5EC720000D260 /* CRT.cpp in Sources */,
|
4B778F1223A5EC720000D260 /* CRT.cpp in Sources */,
|
||||||
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */,
|
4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */,
|
||||||
|
4BC0CB382447EC9A00A79DBB /* Channel.cpp in Sources */,
|
||||||
4B778F3C23A5F16F0000D260 /* FIRFilter.cpp in Sources */,
|
4B778F3C23A5F16F0000D260 /* FIRFilter.cpp in Sources */,
|
||||||
4B778F5423A5F2600000D260 /* UnformattedTrack.cpp in Sources */,
|
4B778F5423A5F2600000D260 /* UnformattedTrack.cpp in Sources */,
|
||||||
4B778EF823A5EB6E0000D260 /* NIB.cpp in Sources */,
|
4B778EF823A5EB6E0000D260 /* NIB.cpp in Sources */,
|
||||||
|
@ -51,6 +51,7 @@ SOURCES += glob.glob('../../Components/AY38910/*.cpp')
|
|||||||
SOURCES += glob.glob('../../Components/DiskII/*.cpp')
|
SOURCES += glob.glob('../../Components/DiskII/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
|
SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/OPL2/*.cpp')
|
SOURCES += glob.glob('../../Components/OPL2/*.cpp')
|
||||||
|
SOURCES += glob.glob('../../Components/OPL2/Implementation/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
|
SOURCES += glob.glob('../../Components/SN76489/*.cpp')
|
||||||
SOURCES += glob.glob('../../Components/Serial/*.cpp')
|
SOURCES += glob.glob('../../Components/Serial/*.cpp')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user