From 386a7ca442933f957ed206df33a87dd31233b766 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Mon, 4 May 2020 21:14:51 -0400
Subject: [PATCH] Continues doing away with the attempt heavily to interleave
 the OPLL and OPL2, creating a new OPLL class.

---
 .../OPL2/Implementation/EnvelopeGenerator.hpp |   1 +
 Components/OPL2/Implementation/OPLBase.hpp    |  40 ++++
 .../OPL2/Implementation/WaveformGenerator.hpp |   8 +-
 Components/OPL2/OPL2.cpp                      |  87 +++----
 Components/OPL2/OPL2.hpp                      |  21 +-
 Components/OPL2/OPLL.cpp                      | 215 ++++++++++++++++++
 Components/OPL2/OPLL.hpp                      | 102 +++++++++
 Machines/MasterSystem/MasterSystem.cpp        |   2 +-
 .../Clock Signal.xcodeproj/project.pbxproj    |  14 +-
 9 files changed, 420 insertions(+), 70 deletions(-)
 create mode 100644 Components/OPL2/Implementation/OPLBase.hpp
 create mode 100644 Components/OPL2/OPLL.cpp
 create mode 100644 Components/OPL2/OPLL.hpp

diff --git a/Components/OPL2/Implementation/EnvelopeGenerator.hpp b/Components/OPL2/Implementation/EnvelopeGenerator.hpp
index d4bb37426..af721781b 100644
--- a/Components/OPL2/Implementation/EnvelopeGenerator.hpp
+++ b/Components/OPL2/Implementation/EnvelopeGenerator.hpp
@@ -11,6 +11,7 @@
 
 #include <optional>
 #include <functional>
+#include "LowFrequencyOscillator.hpp"
 
 namespace Yamaha {
 namespace OPL {
diff --git a/Components/OPL2/Implementation/OPLBase.hpp b/Components/OPL2/Implementation/OPLBase.hpp
new file mode 100644
index 000000000..6b6a71038
--- /dev/null
+++ b/Components/OPL2/Implementation/OPLBase.hpp
@@ -0,0 +1,40 @@
+//
+//  OPLBase.hpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 03/05/2020.
+//  Copyright © 2020 Thomas Harte. All rights reserved.
+//
+
+#ifndef OPLBase_h
+#define OPLBase_h
+
+#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
+#include "../../../Concurrency/AsyncTaskQueue.hpp"
+
+namespace Yamaha {
+namespace OPL {
+
+template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
+	public:
+		void write(uint16_t address, uint8_t value) {
+			if(address & 1) {
+				static_cast<Child *>(this)->write_register(selected_register_, value);
+			} else {
+				selected_register_ = value;
+			}
+		}
+
+	protected:
+		OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
+
+		Concurrency::DeferringAsyncTaskQueue &task_queue_;
+
+	private:
+		uint8_t selected_register_ = 0;
+};
+
+}
+}
+
+#endif /* OPLBase_h */
diff --git a/Components/OPL2/Implementation/WaveformGenerator.hpp b/Components/OPL2/Implementation/WaveformGenerator.hpp
index 0008dd3cf..381fe7eea 100644
--- a/Components/OPL2/Implementation/WaveformGenerator.hpp
+++ b/Components/OPL2/Implementation/WaveformGenerator.hpp
@@ -15,12 +15,12 @@
 namespace Yamaha {
 namespace OPL {
 
+enum class Waveform {
+	Sine, HalfSine, AbsSine, PulseSine
+};
+
 template <int phase_precision> class WaveformGenerator {
 	public:
-		enum class Waveform {
-			Sine, HalfSine, AbsSine, PulseSine
-		};
-
 		/*!
 			@returns The output of waveform @c form at [integral] phase @c phase.
 		*/
diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp
index b12af5b3b..6fc19ca59 100644
--- a/Components/OPL2/OPL2.cpp
+++ b/Components/OPL2/OPL2.cpp
@@ -18,6 +18,8 @@
 
 using namespace Yamaha::OPL;
 
+/*
+
 template <typename Child>
 OPLBase<Child>::OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
 
@@ -79,43 +81,42 @@ void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) {
 		audio_offset_ = (audio_offset_ + 1) % update_period;
 	}
 
-
-/*	// Fill in any leftover from the previous session.
-	if(audio_offset_) {
-		while(audio_offset_ < update_period && number_of_samples) {
-			*target = int16_t(channels_[audio_offset_ / channel_output_period].level);
-			++target;
-			++audio_offset_;
-			--number_of_samples;
-		}
-		audio_offset_ = 0;
-	}
-
-	// End now if that provided everything that was asked for.
-	if(!number_of_samples) return;
-
-	int total_updates = int(number_of_samples) / update_period;
-	number_of_samples %= size_t(update_period);
-	audio_offset_ = int(number_of_samples);
-
-	while(total_updates--) {
-		update_all_chanels();
-
-		for(int c = 0; c < update_period; ++c) {
-			*target = int16_t(channels_[c / channel_output_period].level);
-			++target;
-		}
-	}
-
-	// If there are any other spots remaining, fill them.
-	if(number_of_samples) {
-		update_all_chanels();
-
-		for(int c = 0; c < int(number_of_samples); ++c) {
-			*target = int16_t(channels_[c / channel_output_period].level);
-			++target;
-		}
-	}*/
+//	// Fill in any leftover from the previous session.
+//	if(audio_offset_) {
+//		while(audio_offset_ < update_period && number_of_samples) {
+//			*target = int16_t(channels_[audio_offset_ / channel_output_period].level);
+//			++target;
+//			++audio_offset_;
+//			--number_of_samples;
+//		}
+//		audio_offset_ = 0;
+//	}
+//
+//	// End now if that provided everything that was asked for.
+//	if(!number_of_samples) return;
+//
+//	int total_updates = int(number_of_samples) / update_period;
+//	number_of_samples %= size_t(update_period);
+//	audio_offset_ = int(number_of_samples);
+//
+//	while(total_updates--) {
+//		update_all_chanels();
+//
+//		for(int c = 0; c < update_period; ++c) {
+//			*target = int16_t(channels_[c / channel_output_period].level);
+//			++target;
+//		}
+//	}
+//
+//	// If there are any other spots remaining, fill them.
+//	if(number_of_samples) {
+//		update_all_chanels();
+//
+//		for(int c = 0; c < int(number_of_samples); ++c) {
+//			*target = int16_t(channels_[c / channel_output_period].level);
+//			++target;
+//		}
+//	}
 }
 
 void OPLL::set_sample_volume_range(std::int16_t range) {
@@ -274,9 +275,9 @@ void OPLL::update_all_chanels() {
 #undef VOLUME
 }
 
-/*
-template <Personality personality>
-void OPL2<personality>::get_samples(std::size_t number_of_samples, std::int16_t *target) {
+
+//template <Personality personality>
+//void OPL2<personality>::get_samples(std::size_t number_of_samples, std::int16_t *target) {
 	// TODO.
 	//  out = exp(logsin(phase2 + exp(logsin(phase1) + gain1)) + gain2)
 
@@ -301,9 +302,9 @@ void OPL2<personality>::get_samples(std::size_t number_of_samples, std::int16_t
 //		Tom tom, using operator 14,
 //		Cymbal, using operator 17; and
 //		Symbol, using operator 13.
-}
+//}
+
 
-*/
 
 void OPL2::write_register(uint8_t address, uint8_t value) {
 
@@ -393,3 +394,5 @@ uint8_t OPL2::read(uint16_t address) {
 	//	b5 = timer 2 flag
 	return 0xff;
 }
+
+*/
diff --git a/Components/OPL2/OPL2.hpp b/Components/OPL2/OPL2.hpp
index e9949c73a..a583c0848 100644
--- a/Components/OPL2/OPL2.hpp
+++ b/Components/OPL2/OPL2.hpp
@@ -20,24 +20,7 @@
 namespace Yamaha {
 namespace OPL {
 
-template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource {
-	public:
-		void write(uint16_t address, uint8_t value);
-
-	protected:
-		OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue);
-
-		Concurrency::DeferringAsyncTaskQueue &task_queue_;
-		LowFrequencyOscillator oscillator_;
-
-		uint8_t depth_rhythm_control_;
-		uint8_t csm_keyboard_split_;
-		bool waveform_enable_;
-
-	private:
-		uint8_t selected_register_ = 0;
-};
-
+/*
 struct OPL2: public OPLBase<OPL2> {
 	public:
 		// Creates a new OPL2.
@@ -132,7 +115,7 @@ struct OPLL: public OPLBase<OPLL> {
 		int audio_offset_ = 0;
 
 		std::atomic<int> total_volume_;
-};
+};*/
 
 }
 }
diff --git a/Components/OPL2/OPLL.cpp b/Components/OPL2/OPLL.cpp
new file mode 100644
index 000000000..121bee38d
--- /dev/null
+++ b/Components/OPL2/OPLL.cpp
@@ -0,0 +1,215 @@
+//
+//  OPLL.cpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 03/05/2020.
+//  Copyright © 2020 Thomas Harte. All rights reserved.
+//
+
+#include "OPLL.hpp"
+
+#include <cassert>
+
+using namespace Yamaha::OPL;
+
+OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7):
+	OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
+	// Due to the way that sound mixing works on the OPLL, the audio divider may not
+	// be larger than 4.
+	assert(audio_divider <= 4);
+
+	// Set up proper damping management.
+	for(int c = 0; c < 9; ++c) {
+		envelope_generators_[c].set_should_damp([this, c] {
+			// Propagate attack mode to the modulator, and reset both phases.
+			envelope_generators_[c + 9].set_key_on(true);
+			phase_generators_[c + 0].reset();
+			phase_generators_[c + 9].reset();
+		});
+	}
+}
+
+// MARK: - Machine-facing programmatic input.
+
+void OPLL::write_register(uint8_t address, uint8_t value) {
+	// The OPLL doesn't have timers or other non-audio functions, so all writes
+	// go to the audio queue.
+	task_queue_.defer([this, address, value] {
+		// The first 8 locations are used to define the custom instrument, and have
+		// exactly the same format as the patch set arrays at the head of this file.
+		if(address < 8) {
+			custom_instrument_[address] = value;
+
+			// Update all channels that refer to instrument 0.
+			for(int c = 0; c < 9; ++c) {
+				if(!channels_[c].instrument) {
+					install_instrument(c);
+				}
+			}
+
+			return;
+		}
+
+		// Register 0xe enables or disables rhythm mode and contains the
+		// percussion key-on bits.
+		if(address == 0xe) {
+			rhythm_mode_enabled_ = value & 0x20;
+			rhythm_generators_[0].set_key_on(value & 0x01);
+			rhythm_generators_[1].set_key_on(value & 0x02);
+			rhythm_generators_[2].set_key_on(value & 0x04);
+			rhythm_generators_[3].set_key_on(value & 0x08);
+			rhythm_generators_[4].set_key_on(value & 0x10);
+			return;
+		}
+
+		// That leaves only per-channel selections, for which the addressing
+		// is completely orthogonal; check that a valid channel is being requested.
+		const auto index = address & 0xf;
+		if(index > 8) return;
+
+		switch(address & 0xf0) {
+			default: break;
+
+			// Address 1x sets the low 8 bits of the period for channel x.
+			case 0x10:
+				channels_[index].period = (channels_[index].period & ~0xff) | value;
+				set_channel_period(index);
+			return;
+
+			// Address 2x Sets the octave and a single bit of the frequency, as well
+			// as setting key on and sustain mode.
+			case 0x20:
+				channels_[index].period = (channels_[index].period & 0xff) | (value & 1);
+				channels_[index].octave = (value >> 1) & 7;
+				set_channel_period(index);
+
+				// In this implementation the first 9 envelope generators are for
+				// channel carriers, and their will_attack callback is used to trigger
+				// key-on for modulators. But key-off needs to be set to both envelope
+				// generators now.
+				if(value & 0x10) {
+					envelope_generators_[index].set_key_on(true);
+				} else {
+					envelope_generators_[index + 0].set_key_on(false);
+					envelope_generators_[index + 9].set_key_on(false);
+				}
+
+				// Set sustain bit to both the relevant operators.
+				channels_[index].use_sustain = value & 0x20;
+				set_use_sustain(index);
+			return;
+
+			// Address 3x selects the instrument and attenuation for a channel;
+			// in rhythm mode some of the nibbles that ordinarily identify instruments
+			// instead nominate additional attenuations. This code reads those back
+			// from the stored instrument values.
+			case 0x30:
+				channels_[index].instrument = value >> 4;
+				channels_[index].attenuation = value >> 4;
+				install_instrument(index);
+			return;
+		}
+	});
+}
+
+void OPLL::set_channel_period(int channel) {
+	phase_generators_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
+	phase_generators_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
+
+	key_level_scalers_[channel + 0].set_period(channels_[channel].period, channels_[channel].octave);
+	key_level_scalers_[channel + 9].set_period(channels_[channel].period, channels_[channel].octave);
+}
+
+const uint8_t *OPLL::instrument_definition(int instrument) {
+	// Instrument 0 is the custom instrument.
+	if(!instrument) return custom_instrument_;
+
+	// Instruments other than 0 are taken from the fixed set.
+	const int index = (instrument - 1) * 8;
+	return is_vrc7_ ? &vrc7_patch_set[index] : &opll_patch_set[index];
+}
+
+void OPLL::install_instrument(int channel) {
+	auto &carrier_envelope = envelope_generators_[channel + 0];
+	auto &carrier_phase = phase_generators_[channel + 0];
+	auto &carrier_scaler = key_level_scalers_[channel + 0];
+
+	auto &modulator_envelope = envelope_generators_[channel + 9];
+	auto &modulator_phase = phase_generators_[channel + 9];
+	auto &modulator_scaler = key_level_scalers_[channel + 9];
+
+	const uint8_t *const instrument = instrument_definition(channels_[channel].instrument);
+
+	// Bytes 0 (modulator) and 1 (carrier):
+	//
+	//	b0-b3:	multiplier;
+	//	b4:		key-scale rate enable;
+	//	b5:		sustain-level enable;
+	//	b6:		vibrato enable;
+	//	b7:		tremolo enable.
+	modulator_phase.set_multiple(instrument[0] & 0xf);
+	channels_[channel].modulator_key_rate_scale_multiplier = (instrument[0] >> 4) & 1;
+	modulator_phase.set_vibrato_enabled(instrument[0] & 0x40);
+	modulator_envelope.set_tremolo_enabled(instrument[0] & 0x80);
+
+	carrier_phase.set_multiple(instrument[1] & 0xf);
+	channels_[channel].carrier_key_rate_scale_multiplier = (instrument[1] >> 4) & 1;
+	carrier_phase.set_vibrato_enabled(instrument[1] & 0x40);
+	carrier_envelope.set_tremolo_enabled(instrument[1] & 0x80);
+
+	// Pass off bit 5.
+	set_use_sustain(channel);
+
+	// Byte 2:
+	//
+	//	b0–b5:	modulator attenuation;
+	//	b6–b7:	modulator key-scale level.
+	modulator_scaler.set_key_scaling_level(instrument[3] >> 6);
+	channels_[channel].modulator_attenuation = instrument[2] & 0x3f;
+
+	// Byte 3:
+	//
+	//	b0–b2:	modulator feedback level;
+	//	b3:		modulator waveform selection;
+	//	b4:		carrier waveform selection;
+	//	b5:		[unused]
+	//	b6–b7:	carrier key-scale level.
+	channels_[channel].modulator_feedback = instrument[3] & 7;
+	channels_[channel].modulator_waveform = Waveform((instrument[3] >> 3) & 1);
+	channels_[channel].carrier_waveform = Waveform((instrument[3] >> 4) & 1);
+	carrier_scaler.set_key_scaling_level(instrument[3] >> 6);
+
+	// Bytes 4 (modulator) and 5 (carrier):
+	//
+	//	b0–b3:	decay rate;
+	//	b4–b7:	attack rate.
+	modulator_envelope.set_decay_rate(instrument[4] & 0xf);
+	modulator_envelope.set_attack_rate(instrument[4] >> 4);
+	carrier_envelope.set_decay_rate(instrument[5] & 0xf);
+	carrier_envelope.set_attack_rate(instrument[5] >> 4);
+
+	// Bytes 6 (modulator) and 7 (carrier):
+	//
+	//	b0–b3:	release rate;
+	//	b4–b7:	sustain level.
+	modulator_envelope.set_release_rate(instrument[6] & 0xf);
+	modulator_envelope.set_sustain_level(instrument[6] >> 4);
+	carrier_envelope.set_release_rate(instrument[7] & 0xf);
+	carrier_envelope.set_release_rate(instrument[7] >> 4);
+}
+
+void OPLL::set_use_sustain(int channel) {
+	const uint8_t *const instrument = instrument_definition(channels_[channel].instrument);
+	envelope_generators_[channel + 0].set_sustain_level((instrument[1] & 0x20) || channels_[channel].use_sustain);
+	envelope_generators_[channel + 9].set_sustain_level((instrument[0] & 0x20) || channels_[channel].use_sustain);
+}
+
+// MARK: - Output generation.
+
+void OPLL::set_sample_volume_range(std::int16_t range) {
+	total_volume_ = range;
+}
+
+void OPLL::get_samples(std::size_t number_of_samples, std::int16_t *target) {
+	// TODO.
+}
diff --git a/Components/OPL2/OPLL.hpp b/Components/OPL2/OPLL.hpp
new file mode 100644
index 000000000..192977204
--- /dev/null
+++ b/Components/OPL2/OPLL.hpp
@@ -0,0 +1,102 @@
+//
+//  OPLL.hpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 03/05/2020.
+//  Copyright © 2020 Thomas Harte. All rights reserved.
+//
+
+#ifndef OPLL_hpp
+#define OPLL_hpp
+
+#include "Implementation/OPLBase.hpp"
+#include "Implementation/EnvelopeGenerator.hpp"
+#include "Implementation/KeyLevelScaler.hpp"
+#include "Implementation/PhaseGenerator.hpp"
+#include "Implementation/LowFrequencyOscillator.hpp"
+#include "Implementation/WaveformGenerator.hpp"
+
+#include <atomic>
+
+namespace Yamaha {
+namespace OPL {
+
+class OPLL: public OPLBase<OPLL> {
+	public:
+		/// Creates a new OPLL or VRC7.
+		OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false);
+
+		/// As per ::SampleSource; provides audio output.
+		void get_samples(std::size_t number_of_samples, std::int16_t *target);
+		void set_sample_volume_range(std::int16_t range);
+
+		/// Reads from the OPL.
+		uint8_t read(uint16_t address);
+
+	private:
+		friend OPLBase<OPLL>;
+		void write_register(uint8_t address, uint8_t value);
+
+		int audio_divider_ = 0;
+		std::atomic<int> total_volume_;
+
+		static constexpr int period_precision = 9;
+		static constexpr int envelope_precision = 9;
+
+		// Standard melodic phase and envelope generators.
+		PhaseGenerator<period_precision> phase_generators_[18];
+		EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
+		KeyLevelScaler<period_precision> key_level_scalers_[18];
+
+		// Dedicated rhythm envelope generators and attenuations.
+		EnvelopeGenerator<envelope_precision, period_precision> rhythm_generators_[5];
+		int rhythm_attenuations_[5];
+
+		// Channel specifications.
+		struct Channel {
+			int octave = 0;
+			int period = 0;
+			int instrument = 0;
+
+			int attenuation = 0;
+			int modulator_attenuation = 0;
+
+			Waveform carrier_waveform = Waveform::Sine;
+			Waveform modulator_waveform = Waveform::Sine;
+
+			int carrier_key_rate_scale_multiplier = 0;
+			int modulator_key_rate_scale_multiplier = 0;
+
+			int modulator_feedback = 0;
+
+			bool use_sustain = false;
+		} channels_[9];
+
+		// The low-frequency oscillator.
+		LowFrequencyOscillator oscillator_;
+		bool rhythm_mode_enabled_ = false;
+		bool is_vrc7_ = false;
+
+		// Contains the current configuration of the custom instrument.
+		uint8_t custom_instrument_[8];
+
+		// Helpers to push per-channel information.
+
+		/// Pushes the current octave and period to channel @c channel.
+		void set_channel_period(int channel);
+
+		/// Installs the appropriate instrument on channel @c channel.
+		void install_instrument(int channel);
+
+		/// Sets whether the sustain level is used for channel @c channel based on its current instrument
+		/// and the user's selection.
+		void set_use_sustain(int channel);
+
+		/// @returns The 8-byte definition of instrument @c instrument.
+		const uint8_t *instrument_definition(int instrument);
+};
+
+}
+}
+
+#endif /* OPLL_hpp */
diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp
index c6cfcc518..c153be338 100644
--- a/Machines/MasterSystem/MasterSystem.cpp
+++ b/Machines/MasterSystem/MasterSystem.cpp
@@ -12,7 +12,7 @@
 
 #include "../../Components/9918/9918.hpp"
 #include "../../Components/SN76489/SN76489.hpp"
-#include "../../Components/OPL2/OPL2.hpp"
+#include "../../Components/OPL2/OPLL.hpp"
 
 #include "../MachineTypes.hpp"
 #include "../../Configurable/Configurable.hpp"
diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index a63d45e50..21e8789d3 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -222,6 +222,8 @@
 		4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; };
 		4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */; };
 		4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; };
+		4B619099245FBF7B0013F202 /* OPLL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B619097245FBF7B0013F202 /* OPLL.cpp */; };
+		4B61909A245FBF7B0013F202 /* OPLL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B619097245FBF7B0013F202 /* OPLL.cpp */; };
 		4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; };
 		4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
 		4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
@@ -793,8 +795,6 @@
 		4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
 		4BC57CD92436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
 		4BC57CDA2436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
-		4BC57CE22436BFE000FBC404 /* OPL2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CE02436BFE000FBC404 /* OPL2.cpp */; };
-		4BC57CE32436BFE000FBC404 /* OPL2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CE02436BFE000FBC404 /* OPL2.cpp */; };
 		4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
 		4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
 		4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
@@ -1152,6 +1152,9 @@
 		4B619093245CD63E0013F202 /* EnvelopeGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EnvelopeGenerator.hpp; sourceTree = "<group>"; };
 		4B619094245E73B90013F202 /* KeyLevelScaler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyLevelScaler.hpp; sourceTree = "<group>"; };
 		4B619095245FA04B0013F202 /* WaveformGenerator.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WaveformGenerator.hpp; sourceTree = "<group>"; };
+		4B619096245FBEF80013F202 /* OPLBase.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = OPLBase.hpp; path = Implementation/OPLBase.hpp; sourceTree = "<group>"; };
+		4B619097245FBF7B0013F202 /* OPLL.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = OPLL.cpp; sourceTree = "<group>"; };
+		4B619098245FBF7B0013F202 /* OPLL.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = OPLL.hpp; sourceTree = "<group>"; };
 		4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DisplayMetrics.cpp; path = ../../Outputs/DisplayMetrics.cpp; sourceTree = "<group>"; };
 		4B622AE4222E0AD5008B59F2 /* DisplayMetrics.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DisplayMetrics.hpp; path = ../../Outputs/DisplayMetrics.hpp; sourceTree = "<group>"; };
 		4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
@@ -3575,6 +3578,9 @@
 				4BC0CB2F2447EC7C00A79DBB /* Implementation */,
 				4BC57CE02436BFE000FBC404 /* OPL2.cpp */,
 				4BC57CE12436BFE000FBC404 /* OPL2.hpp */,
+				4B619096245FBEF80013F202 /* OPLBase.hpp */,
+				4B619097245FBF7B0013F202 /* OPLL.cpp */,
+				4B619098245FBF7B0013F202 /* OPLL.hpp */,
 			);
 			path = OPL2;
 			sourceTree = "<group>";
@@ -4375,7 +4381,6 @@
 				4B894521201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
 				4B8318B522D3E548006DB630 /* Macintosh.cpp in Sources */,
 				4B7BA03123C2B19C00B98D9E /* Jasmin.cpp in Sources */,
-				4BC57CE32436BFE000FBC404 /* OPL2.cpp in Sources */,
 				4B7F188F2154825E00388727 /* MasterSystem.cpp in Sources */,
 				4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */,
 				4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
@@ -4530,6 +4535,7 @@
 				4B894537201967B4007DE474 /* Z80.cpp in Sources */,
 				4B055A9F1FAE85DA0060FFFF /* HFE.cpp in Sources */,
 				4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */,
+				4B61909A245FBF7B0013F202 /* OPLL.cpp in Sources */,
 				4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
 				4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */,
 				4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
@@ -4651,7 +4657,6 @@
 				4BEA52631DF339D7007E74F2 /* SoundGenerator.cpp in Sources */,
 				4BD67DD0209BF27B00AB2146 /* Encoder.cpp in Sources */,
 				4BAE495920328897004BE78E /* ZX8081OptionsPanel.swift in Sources */,
-				4BC57CE22436BFE000FBC404 /* OPL2.cpp in Sources */,
 				4B89451A201967B4007DE474 /* ConfidenceSummary.cpp in Sources */,
 				4BE0A3EE237BB170002AB46F /* ST.cpp in Sources */,
 				4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */,
@@ -4702,6 +4707,7 @@
 				4BD0FBC3233706A200148981 /* CSApplication.m in Sources */,
 				4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
 				4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
+				4B619099245FBF7B0013F202 /* OPLL.cpp in Sources */,
 				4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
 				4B7BA03423C58B1F00B98D9E /* STX.cpp in Sources */,
 				4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */,