diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index fab7e94b3..30e5ddc46 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -12,45 +12,56 @@ 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) { + // 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(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(max_volume / powf(root_two, static_cast(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 222d11c74..2a1d777c4 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); @@ -109,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, 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(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 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 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(ZX8081ClockRate) / 2.0f); diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 7b1a6914b..175c1d515 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ target(new Target); + target->machine = Analyser::Machine::AtariST; + _targets.push_back(std::move(target)); + } + return self; +} + - (NSString *)optionsPanelNibName { switch(_targets.front()->machine) { case Analyser::Machine::AmstradCPC: return @"CompositeOptions"; diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index fe1865d35..3b58df322 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1,8 +1,8 @@ - + - + @@ -17,14 +17,14 @@ - + - +