From 0aceddd0883626e077256ed3518e6d08e2fdee0e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Wed, 15 Apr 2020 22:10:50 -0400 Subject: [PATCH] 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. --- Components/OPL2/Implementation/Channel.cpp | 57 +++++ Components/OPL2/Implementation/Channel.hpp | 81 ++++++ Components/OPL2/Implementation/Operator.cpp | 184 ++++++++++++++ Components/OPL2/Implementation/Operator.hpp | 134 ++++++++++ Components/OPL2/Implementation/Tables.h | 151 +++++++++++ Components/OPL2/OPL2.cpp | 228 +---------------- Components/OPL2/OPL2.hpp | 236 +----------------- .../Clock Signal.xcodeproj/project.pbxproj | 30 +++ OSBindings/SDL/SConstruct | 1 + 9 files changed, 644 insertions(+), 458 deletions(-) create mode 100644 Components/OPL2/Implementation/Channel.cpp create mode 100644 Components/OPL2/Implementation/Channel.hpp create mode 100644 Components/OPL2/Implementation/Operator.cpp create mode 100644 Components/OPL2/Implementation/Operator.hpp create mode 100644 Components/OPL2/Implementation/Tables.h diff --git a/Components/OPL2/Implementation/Channel.cpp b/Components/OPL2/Implementation/Channel.cpp new file mode 100644 index 000000000..46f611b21 --- /dev/null +++ b/Components/OPL2/Implementation/Channel.cpp @@ -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; +} diff --git a/Components/OPL2/Implementation/Channel.hpp b/Components/OPL2/Implementation/Channel.hpp new file mode 100644 index 000000000..58cd8d7ff --- /dev/null +++ b/Components/OPL2/Implementation/Channel.hpp @@ -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 + +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 */ diff --git a/Components/OPL2/Implementation/Operator.cpp b/Components/OPL2/Implementation/Operator.cpp new file mode 100644 index 000000000..f7cd80365 --- /dev/null +++ b/Components/OPL2/Implementation/Operator.cpp @@ -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 + +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); + } +} diff --git a/Components/OPL2/Implementation/Operator.hpp b/Components/OPL2/Implementation/Operator.hpp new file mode 100644 index 000000000..25578ebe2 --- /dev/null +++ b/Components/OPL2/Implementation/Operator.hpp @@ -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 + +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 */ diff --git a/Components/OPL2/Implementation/Tables.h b/Components/OPL2/Implementation/Tables.h new file mode 100644 index 000000000..5d4845e10 --- /dev/null +++ b/Components/OPL2/Implementation/Tables.h @@ -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 */ diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index cb5f1462b..e4c75fb51 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -11,98 +11,12 @@ #include #include -namespace { - -/* - - 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, -}; - -} +#include "Implementation/Tables.h" using namespace Yamaha::OPL; - template -OPLBase::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 - ) - ); - } -} +OPLBase::OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} template void OPLBase::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. // 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].overrides.hold_sustain_level = value & 0x20; + channels_[index].overrides.use_sustain_level = value & 0x20; break; default: break; @@ -386,139 +300,3 @@ uint8_t OPL2::read(uint16_t address) { // b5 = timer 2 flag 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); - } -} - diff --git a/Components/OPL2/OPL2.hpp b/Components/OPL2/OPL2.hpp index 537b6c486..05c8507f9 100644 --- a/Components/OPL2/OPL2.hpp +++ b/Components/OPL2/OPL2.hpp @@ -13,241 +13,14 @@ #include "../../Concurrency/AsyncTaskQueue.hpp" #include "../../Numeric/LFSR.hpp" +#include "Implementation/Channel.hpp" +#include "Implementation/Operator.hpp" + #include -#include 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 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 class OPLBase: public ::Outputs::Speaker::SampleSource { public: void write(uint16_t address, uint8_t value); @@ -257,9 +30,6 @@ template class OPLBase: public ::Outputs::Speaker::SampleSource Concurrency::DeferringAsyncTaskQueue &task_queue_; - int exponential_[256]; - int log_sin_[256]; - uint8_t depth_rhythm_control_; uint8_t csm_keyboard_split_; bool waveform_enable_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ca3b78ee1..7dd146177 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -778,6 +778,12 @@ 4BBFE83D21015D9C00BF1C40 /* CSJoystickManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFE83C21015D9C00BF1C40 /* CSJoystickManager.m */; }; 4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */; }; 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 */; }; 4BC131712346DE5000E4FF3D /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC1316F2346DE5000E4FF3D /* 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 = ""; }; 4BBFFEE51F7B27F1005F3FEB /* TrackSerialiser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = TrackSerialiser.cpp; sourceTree = ""; }; 4BC0CB272446BC7B00A79DBB /* OPLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = OPLTests.mm; sourceTree = ""; }; + 4BC0CB302447EC7C00A79DBB /* Operator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Operator.cpp; sourceTree = ""; }; + 4BC0CB312447EC7C00A79DBB /* Operator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Operator.hpp; sourceTree = ""; }; + 4BC0CB352447EC9A00A79DBB /* Channel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Channel.cpp; sourceTree = ""; }; + 4BC0CB362447EC9A00A79DBB /* Channel.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Channel.hpp; sourceTree = ""; }; + 4BC0CB3A2447ECAE00A79DBB /* Tables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tables.h; sourceTree = ""; }; 4BC1316D2346DE5000E4FF3D /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = StaticAnalyser.hpp; sourceTree = ""; }; 4BC1316E2346DE5000E4FF3D /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; 4BC1316F2346DE5000E4FF3D /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StaticAnalyser.cpp; sourceTree = ""; }; @@ -3496,6 +3507,18 @@ path = "Joystick Manager"; sourceTree = ""; }; + 4BC0CB2F2447EC7C00A79DBB /* Implementation */ = { + isa = PBXGroup; + children = ( + 4BC0CB302447EC7C00A79DBB /* Operator.cpp */, + 4BC0CB312447EC7C00A79DBB /* Operator.hpp */, + 4BC0CB352447EC9A00A79DBB /* Channel.cpp */, + 4BC0CB362447EC9A00A79DBB /* Channel.hpp */, + 4BC0CB3A2447ECAE00A79DBB /* Tables.h */, + ); + path = Implementation; + sourceTree = ""; + }; 4BC1316C2346DE5000E4FF3D /* Atari2600 */ = { isa = PBXGroup; children = ( @@ -3528,6 +3551,7 @@ 4BC57CDF2436BFE000FBC404 /* OPL2 */ = { isa = PBXGroup; children = ( + 4BC0CB2F2447EC7C00A79DBB /* Implementation */, 4BC57CE02436BFE000FBC404 /* OPL2.cpp */, 4BC57CE12436BFE000FBC404 /* OPL2.hpp */, ); @@ -4336,6 +4360,7 @@ 4B894529201967B4007DE474 /* Disk.cpp in Sources */, 4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */, 4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */, + 4BC0CB392447EC9A00A79DBB /* Channel.cpp in Sources */, 4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */, 4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */, 4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */, @@ -4438,6 +4463,7 @@ 4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */, 4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */, 4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */, + 4BC0CB342447EC7D00A79DBB /* Operator.cpp in Sources */, 4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */, 4BAD13441FF709C700FD114A /* MSX.cpp in Sources */, 4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */, @@ -4513,6 +4539,7 @@ 4B89451E201967B4007DE474 /* Tape.cpp in Sources */, 4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, + 4BC0CB322447EC7D00A79DBB /* Operator.cpp in Sources */, 4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */, 4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */, 4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */, @@ -4671,6 +4698,7 @@ 4BEBFB4D2002C4BF000708CC /* MSXDSK.cpp in Sources */, 4BBFBB6C1EE8401E00C01E7A /* ZX8081.cpp in Sources */, 4B83348A1F5DB94B0097E338 /* IRQDelegatePortHandler.cpp in Sources */, + 4BC0CB372447EC9A00A79DBB /* Channel.cpp in Sources */, 4B894524201967B4007DE474 /* Tape.cpp in Sources */, 4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */, 4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */, @@ -4739,6 +4767,7 @@ 4B778F4A23A5F1FB0000D260 /* StaticAnalyser.cpp in Sources */, 4BD91D772401C2B8007BDC91 /* PatrikRakTests.swift in Sources */, 4B680CE223A5553100451D43 /* 68000ComparativeTests.mm in Sources */, + 4BC0CB332447EC7D00A79DBB /* Operator.cpp in Sources */, 4B778F3723A5F11C0000D260 /* Parser.cpp in Sources */, 4B778F4523A5F1CD0000D260 /* SegmentParser.cpp in Sources */, 4B90467422C6FADD000E2074 /* 68000BitwiseTests.mm in Sources */, @@ -4826,6 +4855,7 @@ 4B778EEF23A5D6680000D260 /* AsyncTaskQueue.cpp in Sources */, 4B778F1223A5EC720000D260 /* CRT.cpp in Sources */, 4B778EF423A5DB3A0000D260 /* C1540.cpp in Sources */, + 4BC0CB382447EC9A00A79DBB /* Channel.cpp in Sources */, 4B778F3C23A5F16F0000D260 /* FIRFilter.cpp in Sources */, 4B778F5423A5F2600000D260 /* UnformattedTrack.cpp in Sources */, 4B778EF823A5EB6E0000D260 /* NIB.cpp in Sources */, diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 6c6f4e49f..07d9183f2 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -51,6 +51,7 @@ SOURCES += glob.glob('../../Components/AY38910/*.cpp') SOURCES += glob.glob('../../Components/DiskII/*.cpp') SOURCES += glob.glob('../../Components/KonamiSCC/*.cpp') SOURCES += glob.glob('../../Components/OPL2/*.cpp') +SOURCES += glob.glob('../../Components/OPL2/Implementation/*.cpp') SOURCES += glob.glob('../../Components/SN76489/*.cpp') SOURCES += glob.glob('../../Components/Serial/*.cpp')