From d85ae21b2f06a85cc9f4b867ebd849367719a153 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 18 Dec 2019 19:28:41 -0500
Subject: [PATCH 1/2] Adds an explicit declaration of chip type to all AY
 users.

---
 Components/AY38910/AY38910.cpp         | 4 ++--
 Components/AY38910/AY38910.hpp         | 9 ++++++++-
 Machines/AmstradCPC/AmstradCPC.cpp     | 2 +-
 Machines/Atari/ST/AtariST.cpp          | 2 +-
 Machines/ColecoVision/ColecoVision.cpp | 2 +-
 Machines/MSX/MSX.cpp                   | 2 +-
 Machines/Oric/Oric.cpp                 | 2 +-
 Machines/ZX8081/ZX8081.cpp             | 2 +-
 8 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp
index fab7e94b3..9266baadc 100644
--- a/Components/AY38910/AY38910.cpp
+++ b/Components/AY38910/AY38910.cpp
@@ -12,8 +12,8 @@
 
 using namespace GI::AY38910;
 
-AY38910::AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
-	// set up envelope lookup tables
+AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
+	// Set up envelope lookup tables.
 	for(int c = 0; c < 16; c++) {
 		for(int p = 0; p < 32; p++) {
 			switch(c) {
diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp
index 222d11c74..1eae0cefe 100644
--- a/Components/AY38910/AY38910.hpp
+++ b/Components/AY38910/AY38910.hpp
@@ -52,6 +52,13 @@ enum ControlLines {
 	BDIR	= (1 << 2)
 };
 
+enum class Personality {
+	/// Provides 16 volume levels to envelopes.
+	AY38910,
+	/// Provides 32 volume levels to envelopes.
+	YM2149F
+};
+
 /*!
 	Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a
 	noise generator and a volume envelope generator, which also provides two bidirectional
@@ -60,7 +67,7 @@ enum ControlLines {
 class AY38910: public ::Outputs::Speaker::SampleSource {
 	public:
 		/// Creates a new AY38910.
-		AY38910(Concurrency::DeferringAsyncTaskQueue &task_queue);
+		AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
 
 		/// Sets the value the AY would read from its data lines if it were not outputting.
 		void set_data_input(uint8_t r);
diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp
index bf869cf12..3b707348e 100644
--- a/Machines/AmstradCPC/AmstradCPC.cpp
+++ b/Machines/AmstradCPC/AmstradCPC.cpp
@@ -124,7 +124,7 @@ class InterruptTimer {
 class AYDeferrer {
 	public:
 		/// Constructs a new AY instance and sets its clock rate.
-		AYDeferrer() : ay_(audio_queue_), speaker_(ay_) {
+		AYDeferrer() : ay_(GI::AY38910::Personality::AY38910, audio_queue_), speaker_(ay_) {
 			speaker_.set_input_rate(1000000);
 		}
 
diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp
index a9e8697c9..447c20ce5 100644
--- a/Machines/Atari/ST/AtariST.cpp
+++ b/Machines/Atari/ST/AtariST.cpp
@@ -63,7 +63,7 @@ class ConcreteMachine:
 			mc68000_(*this),
 			keyboard_acia_(Cycles(500000)),
 			midi_acia_(Cycles(500000)),
-			ay_(audio_queue_),
+			ay_(GI::AY38910::Personality::YM2149F, audio_queue_),
 			speaker_(ay_),
 			ikbd_(keyboard_acia_->transmit, keyboard_acia_->receive) {
 			set_clock_rate(CLOCK_RATE);
diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp
index c3386d633..965cc7604 100644
--- a/Machines/ColecoVision/ColecoVision.cpp
+++ b/Machines/ColecoVision/ColecoVision.cpp
@@ -123,7 +123,7 @@ class ConcreteMachine:
 			z80_(*this),
 			vdp_(TI::TMS::TMS9918A),
 			sn76489_(TI::SN76489::Personality::SN76489, audio_queue_, sn76489_divider),
-			ay_(audio_queue_),
+			ay_(GI::AY38910::Personality::AY38910, audio_queue_),
 			mixer_(sn76489_, ay_),
 			speaker_(mixer_) {
 			speaker_.set_input_rate(3579545.0f / static_cast<float>(sn76489_divider));
diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp
index bed05c068..5997cb0b1 100644
--- a/Machines/MSX/MSX.cpp
+++ b/Machines/MSX/MSX.cpp
@@ -154,7 +154,7 @@ class ConcreteMachine:
 			z80_(*this),
 			vdp_(TI::TMS::TMS9918A),
 			i8255_(i8255_port_handler_),
-			ay_(audio_queue_),
+			ay_(GI::AY38910::Personality::AY38910, audio_queue_),
 			audio_toggle_(audio_queue_),
 			scc_(audio_queue_),
 			mixer_(ay_, audio_toggle_, scc_),
diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp
index 158acc11e..3b7aca12e 100644
--- a/Machines/Oric/Oric.cpp
+++ b/Machines/Oric/Oric.cpp
@@ -213,7 +213,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
 		ConcreteMachine(const Analyser::Static::Oric::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
 				m6502_(*this),
 				video_output_(ram_),
-				ay8910_(audio_queue_),
+				ay8910_(GI::AY38910::Personality::AY38910, audio_queue_),
 				speaker_(ay8910_),
 				via_port_handler_(audio_queue_, ay8910_, speaker_, tape_player_, keyboard_),
 				via_(via_port_handler_),
diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp
index 024601e41..c06bd8d10 100644
--- a/Machines/ZX8081/ZX8081.cpp
+++ b/Machines/ZX8081/ZX8081.cpp
@@ -69,7 +69,7 @@ template<bool is_zx81> class ConcreteMachine:
 		ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
 			z80_(*this),
 			tape_player_(ZX8081ClockRate),
-			ay_(audio_queue_),
+			ay_(GI::AY38910::Personality::AY38910, audio_queue_),
 			speaker_(ay_) {
 			set_clock_rate(ZX8081ClockRate);
 			speaker_.set_input_rate(static_cast<float>(ZX8081ClockRate) / 2.0f);

From 206ab380c70460c57d3b1fd7e984baab5ffc70db Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Wed, 18 Dec 2019 22:03:02 -0500
Subject: [PATCH 2/2] Introduces double-resolution envelopes for the Atari ST.

---
 Components/AY38910/AY38910.cpp | 78 +++++++++++++++++++++-------------
 Components/AY38910/AY38910.hpp |  6 +--
 2 files changed, 52 insertions(+), 32 deletions(-)

diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp
index 9266baadc..30e5ddc46 100644
--- a/Components/AY38910/AY38910.cpp
+++ b/Components/AY38910/AY38910.cpp
@@ -13,44 +13,55 @@
 using namespace GI::AY38910;
 
 AY38910::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
+	// Don't use the low bit of the envelope position if this is an AY.
+	envelope_position_mask_ |= personality == Personality::AY38910;
+
 	// Set up envelope lookup tables.
 	for(int c = 0; c < 16; c++) {
-		for(int p = 0; p < 32; p++) {
+		for(int p = 0; p < 64; p++) {
 			switch(c) {
 				case 0: case 1: case 2: case 3: case 9:
-					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0;
-					envelope_overflow_masks_[c] = 0x1f;
+					/* Envelope: \____ */
+					envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0;
+					envelope_overflow_masks_[c] = 0x3f;
 				break;
 				case 4: case 5: case 6: case 7: case 15:
-					envelope_shapes_[c][p] = (p < 16) ? p : 0;
-					envelope_overflow_masks_[c] = 0x1f;
+					/* Envelope: /____ */
+					envelope_shapes_[c][p] = (p < 32) ? p : 0;
+					envelope_overflow_masks_[c] = 0x3f;
 				break;
 
 				case 8:
-					envelope_shapes_[c][p] = (p & 0xf) ^ 0xf;
+					/* Envelope: \\\\\\\\ */
+					envelope_shapes_[c][p] = (p & 0x1f) ^ 0x1f;
 					envelope_overflow_masks_[c] = 0x00;
 				break;
 				case 12:
-					envelope_shapes_[c][p] = (p & 0xf);
+					/* Envelope: //////// */
+					envelope_shapes_[c][p] = (p & 0x1f);
 					envelope_overflow_masks_[c] = 0x00;
 				break;
 
 				case 10:
-					envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0);
+					/* Envelope: \/\/\/\/ */
+					envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x1f : 0x0);
 					envelope_overflow_masks_[c] = 0x00;
 				break;
 				case 14:
-					envelope_shapes_[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf);
+					/* Envelope: /\/\/\/\ */
+					envelope_shapes_[c][p] = (p & 0x1f) ^ ((p < 32) ? 0x0 : 0x1f);
 					envelope_overflow_masks_[c] = 0x00;
 				break;
 
 				case 11:
-					envelope_shapes_[c][p] = (p < 16) ? (p^0xf) : 0xf;
-					envelope_overflow_masks_[c] = 0x1f;
+					/* Envelope: \------	(if - is high) */
+					envelope_shapes_[c][p] = (p < 32) ? (p^0x1f) : 0x1f;
+					envelope_overflow_masks_[c] = 0x3f;
 				break;
 				case 13:
-					envelope_shapes_[c][p] = (p < 16) ? p : 0xf;
-					envelope_overflow_masks_[c] = 0x1f;
+					/* Envelope: /------- */
+					envelope_shapes_[c][p] = (p < 32) ? p : 0x1f;
+					envelope_overflow_masks_[c] = 0x3f;
 				break;
 			}
 		}
@@ -63,16 +74,24 @@ void AY38910::set_sample_volume_range(std::int16_t range) {
 	// set up volume lookup table
 	const float max_volume = static_cast<float>(range) / 3.0f;	// As there are three channels.
 	const float root_two = sqrtf(2.0f);
-	for(int v = 0; v < 16; v++) {
-		volumes_[v] = static_cast<int>(max_volume / powf(root_two, static_cast<float>(v ^ 0xf)));
+	for(int v = 0; v < 32; v++) {
+		volumes_[v] = int(max_volume / powf(root_two, float(v ^ 0x1f) / 2.0f));
 	}
-	volumes_[0] = 0;
 	evaluate_output_volume();
 }
 
 void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
+	// Note on structure below: the real AY has a built-in divider of 8
+	// prior to applying its tone and noise dividers. But the YM fills the
+	// same total periods for noise and tone with double-precision envelopes.
+	// Therefore this class implements a divider of 4 and doubles the tone
+	// and noise periods. The envelope ticks along at the divide-by-four rate,
+	// but if this is an AY rather than a YM then its lowest bit is forced to 1,
+	// matching the YM datasheet's depiction of envelope level 31 as equal to
+	// programmatic volume 15, envelope level 29 as equal to programmatic 14, etc.
+
 	std::size_t c = 0;
-	while((master_divider_&7) && c < number_of_samples) {
+	while((master_divider_&3) && c < number_of_samples) {
 		target[c] = output_volume_;
 		master_divider_++;
 		c++;
@@ -83,49 +102,49 @@ void AY38910::get_samples(std::size_t number_of_samples, int16_t *target) {
 	if(tone_counters_[c]) tone_counters_[c]--;\
 	else {\
 		tone_outputs_[c] ^= 1;\
-		tone_counters_[c] = tone_periods_[c];\
+		tone_counters_[c] = tone_periods_[c] << 1;\
 	}
 
-		// update the tone channels
+		// Update the tone channels.
 		step_channel(0);
 		step_channel(1);
 		step_channel(2);
 
 #undef step_channel
 
-		// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
+		// Update the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
 		// it into the official 17 upon divider underflow.
 		if(noise_counter_) noise_counter_--;
 		else {
-			noise_counter_ = noise_period_;
+			noise_counter_ = noise_period_ << 1;	// To cover the double resolution of envelopes.
 			noise_output_ ^= noise_shift_register_&1;
 			noise_shift_register_ |= ((noise_shift_register_ ^ (noise_shift_register_ >> 3))&1) << 17;
 			noise_shift_register_ >>= 1;
 		}
 
-		// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
-		// implementing non-repeating patterns by locking them to table position 0x1f.
+		// Update the envelope generator. Table based for pattern lookup, with a 'refill' step: a way of
+		// implementing non-repeating patterns by locking them to the final table position.
 		if(envelope_divider_) envelope_divider_--;
 		else {
 			envelope_divider_ = envelope_period_;
 			envelope_position_ ++;
-			if(envelope_position_ == 32) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
+			if(envelope_position_ == 64) envelope_position_ = envelope_overflow_masks_[output_registers_[13]];
 		}
 
 		evaluate_output_volume();
 
-		for(int ic = 0; ic < 8 && c < number_of_samples; ic++) {
+		for(int ic = 0; ic < 4 && c < number_of_samples; ic++) {
 			target[c] = output_volume_;
 			c++;
 			master_divider_++;
 		}
 	}
 
-	master_divider_ &= 7;
+	master_divider_ &= 3;
 }
 
 void AY38910::evaluate_output_volume() {
-	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_];
+	int envelope_volume = envelope_shapes_[output_registers_[13]][envelope_position_ | envelope_position_mask_];
 
 	// The output level for a channel is:
 	//	1 if neither tone nor noise is enabled;
@@ -142,9 +161,10 @@ void AY38910::evaluate_output_volume() {
 	};
 #undef level
 
-		// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
+		// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits,
+		// mapped to the range 1–31 in case this is a YM.
 #define channel_volume(c)	\
-	((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (output_registers_[c]&0xf)
+	((output_registers_[c] >> 4)&1) * envelope_volume + (((output_registers_[c] >> 4)&1)^1) * (((output_registers_[c]&0xf) << 1) + 1)
 
 	const int volumes[3] = {
 		channel_volume(8),
diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp
index 1eae0cefe..2a1d777c4 100644
--- a/Components/AY38910/AY38910.hpp
+++ b/Components/AY38910/AY38910.hpp
@@ -116,11 +116,11 @@ class AY38910: public ::Outputs::Speaker::SampleSource {
 
 		int envelope_period_ = 0;
 		int envelope_divider_ = 0;
-		int envelope_position_ = 0;
-		int envelope_shapes_[16][32];
+		int envelope_position_ = 0, envelope_position_mask_ = 0;
+		int envelope_shapes_[16][64];
 		int envelope_overflow_masks_[16];
 
-		int volumes_[16];
+		int volumes_[32];
 
 		enum ControlState {
 			Inactive,