1
0
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:
Thomas Harte 2020-04-15 22:10:50 -04:00
parent 30ff399218
commit 0aceddd088
9 changed files with 644 additions and 458 deletions

View 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;
}

View 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 */

View 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);
}
}

View 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 */

View 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 */

View File

@ -11,98 +11,12 @@
#include <cassert>
#include <cmath>
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 <typename Child>
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
)
);
}
}
OPLBase<Child>::OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
template <typename Child>
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.
// 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);
}
}

View File

@ -13,241 +13,14 @@
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Numeric/LFSR.hpp"
#include "Implementation/Channel.hpp"
#include "Implementation/Operator.hpp"
#include <atomic>
#include <cmath>
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 <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
public:
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_;
int exponential_[256];
int log_sin_[256];
uint8_t depth_rhythm_control_;
uint8_t csm_keyboard_split_;
bool waveform_enable_;

View File

@ -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 = "<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>"; };
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>"; };
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>"; };
@ -3496,6 +3507,18 @@
path = "Joystick Manager";
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 */ = {
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 */,

View File

@ -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')