diff --git a/Analyser/Static/AppleII/Target.hpp b/Analyser/Static/AppleII/Target.hpp index 0ad262201..f08bf1f68 100644 --- a/Analyser/Static/AppleII/Target.hpp +++ b/Analyser/Static/AppleII/Target.hpp @@ -34,12 +34,14 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl::set_control_lines(ControlLines control_line update_bus(); } +template +void AY38910SampleSource::set_reset(bool active) { + if(active == reset_) return; + reset_ = active; + + // Reset upon the leading edge; TODO: is this right? + if(reset_) { + reset(); + } +} + +template +void AY38910SampleSource::reset() { + // TODO: the below is a guess. Look up real answers. + + selected_register_ = 0; + std::fill(registers_, registers_ + 16, 0); + + task_queue_.enqueue([&] { + std::fill(output_registers_, output_registers_ + 16, 0); + evaluate_output_volume(); + }); +} + template void AY38910SampleSource::update_bus() { // Assume no output, unless this turns out to be a read. diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index 0c21c1063..afb412e01 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -70,6 +70,7 @@ template class AY38910SampleSource { public: /// Creates a new AY38910. AY38910SampleSource(Personality, Concurrency::AsyncTaskQueue &); + AY38910SampleSource(const AY38910SampleSource &) = delete; /// Sets the value the AY would read from its data lines if it were not outputting. void set_data_input(uint8_t r); @@ -80,6 +81,12 @@ template class AY38910SampleSource { /// Sets the current control line state, as a bit field. void set_control_lines(ControlLines control_lines); + /// Strobes the reset line. + void reset(); + + /// Sets the current value of the reset line. + void set_reset(bool reset); + /*! Gets the value that would appear on the requested interface port if it were in output mode. @parameter port_b @c true to get the value for Port B, @c false to get the value for Port A. @@ -114,13 +121,15 @@ template class AY38910SampleSource { private: Concurrency::AsyncTaskQueue &task_queue_; - int selected_register_ = 0; - uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - uint8_t output_registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + bool reset_ = false; - int tone_periods_[3] = {0, 0, 0}; - int tone_counters_[3] = {0, 0, 0}; - int tone_outputs_[3] = {0, 0, 0}; + int selected_register_ = 0; + uint8_t registers_[16]{}; + uint8_t output_registers_[16]{}; + + int tone_periods_[3]{}; + int tone_counters_[3]{}; + int tone_outputs_[3]{}; int noise_period_ = 0; int noise_counter_ = 0; diff --git a/Machines/Apple/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp index dace81906..ff9e19f67 100644 --- a/Machines/Apple/AppleII/AppleII.cpp +++ b/Machines/Apple/AppleII/AppleII.cpp @@ -15,7 +15,9 @@ #include "../../../Processors/6502/6502.hpp" #include "../../../Components/AudioToggle/AudioToggle.hpp" +#include "../../../Components/AY38910/AY38910.hpp" +#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp" #include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "../../../Outputs/Log.hpp" @@ -24,6 +26,7 @@ #include "DiskIICard.hpp" #include "Joystick.hpp" #include "LanguageCardSwitches.hpp" +#include "Mockingboard.hpp" #include "SCSICard.hpp" #include "Video.hpp" @@ -41,17 +44,71 @@ namespace { -constexpr int DiskIISlot = 6; // Apple recommended slot 6 for the (first) Disk II. -constexpr int SCSISlot = 7; // Install the SCSI card in slot 7, to one-up any connected Disk II. +constexpr int DiskIISlot = 6; // Apple recommended slot 6 for the (first) Disk II. +constexpr int SCSISlot = 7; // Install the SCSI card in slot 7, to one-up any connected Disk II. +constexpr int MockingboardSlot = 4; // Conventional Mockingboard slot. + + +// The system's master clock rate. +// +// Quick note on this: +// +// * 64 out of 65 CPU cycles last for 14 cycles of the master clock; +// * every 65th cycle lasts for 16 cycles of the master clock; +// * that keeps CPU cycles in-phase with the colour subcarrier: each line of output is 64*14 + 16 = 912 master cycles long; +// * ... and since hsync is placed to make each line 228 colour clocks long that means 4 master clocks per colour clock; +// * ... which is why all Apple II video modes are expressible as four binary outputs per colour clock; +// * ... and hence seven pixels per memory access window clock in high-res mode, 14 in double high-res, etc. +constexpr float master_clock = 14318180.0; + +/// Provides an AY that runs at the CPU rate divided by 4 given an input of the master clock divided by 2, +/// allowing for stretched CPU clock cycles. +struct StretchedAYPair: + Apple::II::AYPair, + public Outputs::Speaker::BufferSource { + + using AYPair::AYPair; + + template + void apply_samples(std::size_t number_of_samples, Outputs::Speaker::StereoSample *target) { + + // (1) take 64 windows of 7 input cycles followed by one window of 8 input cycles; + // (2) after each four windows, advance the underlying AY. + // + // i.e. advance after: + // + // * 28 cycles, {16 times, then 15 times, then 15 times, then 15 times}; + // * 29 cycles, once. + // + // so: + // 16, 1; 15, 1; 15, 1; 15, 1 + // + // i.e. add an extra one on the 17th, 33rd, 49th and 65th ticks in a 65-tick loop. + for(std::size_t c = 0; c < number_of_samples; c++) { + ++subdivider_; + if(subdivider_ == 28) { + ++phase_; + subdivider_ = (phase_ & 15) ? 0 : -1; + if(phase_ == 65) phase_ = 0; + + advance(); + } + + target[c] = level(); + } + } + + private: + int phase_ = 0; + int subdivider_ = 0; +}; } namespace Apple { namespace II { -#define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) - -template class ConcreteMachine: +template class ConcreteMachine: public Apple::II::Machine, public MachineTypes::TimedMachine, public MachineTypes::ScanProducer, @@ -83,14 +140,14 @@ template class ConcreteMachine: false>; Processor m6502_; VideoBusHandler video_bus_handler_; - Apple::II::Video::Video video_; + Apple::II::Video::Video video_; int cycles_into_current_line_ = 0; Cycles cycles_since_video_update_; void update_video() { video_.run_for(cycles_since_video_update_.flush()); } - static constexpr int audio_divider = 8; + static constexpr int audio_divider = has_mockingboard ? 1 : 8; void update_audio() { speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider))); } @@ -109,9 +166,23 @@ template class ConcreteMachine: Concurrency::AsyncTaskQueue audio_queue_; Audio::Toggle audio_toggle_; - Outputs::Speaker::PullLowpass speaker_; + StretchedAYPair ays_; + using SourceT = + std::conditional_t, Audio::Toggle>; + using LowpassT = Outputs::Speaker::PullLowpass; + + Outputs::Speaker::CompoundSource mixer_; + Outputs::Speaker::PullLowpass speaker_; Cycles cycles_since_audio_update_; + constexpr SourceT &lowpass_source() { + if constexpr (has_mockingboard) { + return mixer_; + } else { + return audio_toggle_; + } + } + // MARK: - Cards static constexpr size_t NoActiveCard = 7; // There is no 'card 0' in internal numbering. size_t active_card_ = NoActiveCard; @@ -155,6 +226,24 @@ template class ConcreteMachine: pick_card_messaging_group(card); } + void card_did_change_interrupt_flags(Apple::II::Card *) final { + bool nmi = false; + bool irq = false; + + for(const auto &card: cards_) { + if(card) { + nmi |= card->nmi(); + irq |= card->irq(); + } + } + m6502_.set_nmi_line(nmi); + m6502_.set_irq_line(irq); + } + + Apple::II::Mockingboard *mockingboard() { + return dynamic_cast(cards_[MockingboardSlot - 1].get()); + } + Apple::II::DiskIICard *diskii_card() { return dynamic_cast(cards_[DiskIISlot - 1].get()); } @@ -225,7 +314,7 @@ template class ConcreteMachine: const auto zero_state = auxiliary_switches_.zero_state(); uint8_t *const ram = zero_state ? aux_ram_ : ram_; - uint8_t *const rom = is_iie() ? &rom_[3840] : rom_.data(); + uint8_t *const rom = is_iie(model) ? &rom_[3840] : rom_.data(); // Which way the region here is mapped to be banks 1 and 2 is // arbitrary. @@ -286,7 +375,7 @@ template class ConcreteMachine: } bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final { - if constexpr (!is_iie()) { + if constexpr (!is_iie(model)) { if(is_repeat && !repeat_is_pressed_) return true; } @@ -297,7 +386,7 @@ template class ConcreteMachine: case Key::Down: value = 0x0a; break; case Key::Up: value = 0x0b; break; case Key::Backspace: - if(is_iie()) { + if(is_iie(model)) { value = 0x7f; break; } else { @@ -305,7 +394,7 @@ template class ConcreteMachine: } case Key::Enter: value = 0x0d; break; case Key::Tab: - if (is_iie()) { + if (is_iie(model)) { value = '\t'; break; } else { @@ -316,7 +405,7 @@ template class ConcreteMachine: case Key::LeftOption: case Key::RightMeta: - if (is_iie()) { + if (is_iie(model)) { open_apple_is_pressed = is_pressed; return true; } else { @@ -325,7 +414,7 @@ template class ConcreteMachine: case Key::RightOption: case Key::LeftMeta: - if (is_iie()) { + if (is_iie(model)) { closed_apple_is_pressed = is_pressed; return true; } else { @@ -346,7 +435,7 @@ template class ConcreteMachine: case Key::F9: case Key::F10: case Key::F11: repeat_is_pressed_ = is_pressed; - if constexpr (!is_iie()) { + if constexpr (!is_iie(model)) { if(is_pressed && (!is_repeat || character_is_pressed_)) { keyboard_input_ = uint8_t(last_pressed_character_ | 0x80); } @@ -374,12 +463,12 @@ template class ConcreteMachine: } // Prior to the IIe, the keyboard could produce uppercase only. - if(!is_iie()) value = char(toupper(value)); + if(!is_iie(model)) value = char(toupper(value)); if(control_is_pressed_ && isalpha(value)) value &= 0xbf; // TODO: properly map IIe keys - if(!is_iie() && shift_is_pressed_) { + if(!is_iie(model) && shift_is_pressed_) { switch(value) { case 0x27: value = 0x22; break; // ' -> " case 0x2c: value = 0x3c; break; // , -> < @@ -480,12 +569,12 @@ template class ConcreteMachine: video_bus_handler_(ram_, aux_ram_), video_(video_bus_handler_), audio_toggle_(audio_queue_), - speaker_(audio_toggle_), + ays_(audio_queue_), + mixer_(ays_, audio_toggle_), + speaker_(lowpass_source()), language_card_(*this), auxiliary_switches_(*this), keyboard_(&m6502_) { - // The system's master clock rate. - constexpr float master_clock = 14318180.0; // This is where things get slightly convoluted: establish the machine as having a clock rate // equal to the number of cycles of work the 6502 will actually achieve. Which is less than @@ -559,6 +648,12 @@ template class ConcreteMachine: install_card(SCSISlot, new Apple::II::SCSICard(roms, int(master_clock / 14.0f))); } + if(target.has_mockingboard) { + // The Mockingboard has a parasitic relationship with this class due to the way + // that audio outputs are implemented in this emulator. + install_card(MockingboardSlot, new Apple::II::Mockingboard(ays_)); + } + rom_ = std::move(roms.find(system)->second); // The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary. if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) { @@ -629,7 +724,7 @@ template class ConcreteMachine: if(write_pages_[address >> 8]) write_pages_[address >> 8][address & 0xff] = *value; } - if(is_iie()) { + if(is_iie(model)) { auxiliary_switches_.access(address, isReadOperation(operation)); } } else { @@ -669,7 +764,7 @@ template class ConcreteMachine: *value &= 0x7f; if( joysticks_.button(0) || - (is_iie() && keyboard_.open_apple_is_pressed) + (is_iie(model) && keyboard_.open_apple_is_pressed) ) *value |= 0x80; break; @@ -677,7 +772,7 @@ template class ConcreteMachine: *value &= 0x7f; if( joysticks_.button(1) || - (is_iie() && keyboard_.closed_apple_is_pressed) + (is_iie(model) && keyboard_.closed_apple_is_pressed) ) *value |= 0x80; break; @@ -699,7 +794,7 @@ template class ConcreteMachine: } break; // The IIe-only state reads follow... -#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); +#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie(model)) *value = (*value & 0x7f) | (s ? 0x80 : 0x00); case 0xc011: IIeSwitchRead(language_card_.state().bank2); break; case 0xc012: IIeSwitchRead(language_card_.state().read); break; case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break; @@ -718,12 +813,12 @@ template class ConcreteMachine: #undef IIeSwitchRead case 0xc07f: - if(is_iie()) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00); + if(is_iie(model)) *value = (*value & 0x7f) | (video_.get_annunciator_3() ? 0x80 : 0x00); break; } } else { // Write-only switches. All IIe as currently implemented. - if(is_iie()) { + if(is_iie(model)) { auxiliary_switches_.access(address, false); switch(address) { default: break; @@ -775,7 +870,7 @@ template class ConcreteMachine: case 0xc05e: case 0xc05f: - if(is_iie()) { + if(is_iie(model)) { update_video(); video_.set_annunciator_3(!(address&1)); } @@ -785,7 +880,7 @@ template class ConcreteMachine: keyboard_.clear_keyboard_input(); // On the IIe, reading C010 returns additional key info. - if(is_iie() && isReadOperation(operation)) { + if(is_iie(model) && isReadOperation(operation)) { *value = (keyboard_.get_key_is_down() ? 0x80 : 0x00) | (keyboard_.get_keyboard_input() & 0x7f); } break; @@ -922,7 +1017,7 @@ template class ConcreteMachine: } bool prefers_logical_input() final { - return is_iie(); + return is_iie(model); } Inputs::Keyboard &get_keyboard() final { @@ -988,12 +1083,23 @@ using namespace Apple::II; std::unique_ptr Machine::AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) { using Target = Analyser::Static::AppleII::Target; const Target *const appleii_target = dynamic_cast(target); - switch(appleii_target->model) { - default: return nullptr; - case Target::Model::II: return std::make_unique>(*appleii_target, rom_fetcher); - case Target::Model::IIplus: return std::make_unique>(*appleii_target, rom_fetcher); - case Target::Model::IIe: return std::make_unique>(*appleii_target, rom_fetcher); - case Target::Model::EnhancedIIe: return std::make_unique>(*appleii_target, rom_fetcher); + + if(appleii_target->has_mockingboard) { + switch(appleii_target->model) { + default: return nullptr; + case Target::Model::II: return std::make_unique>(*appleii_target, rom_fetcher); + case Target::Model::IIplus: return std::make_unique>(*appleii_target, rom_fetcher); + case Target::Model::IIe: return std::make_unique>(*appleii_target, rom_fetcher); + case Target::Model::EnhancedIIe: return std::make_unique>(*appleii_target, rom_fetcher); + } + } else { + switch(appleii_target->model) { + default: return nullptr; + case Target::Model::II: return std::make_unique>(*appleii_target, rom_fetcher); + case Target::Model::IIplus: return std::make_unique>(*appleii_target, rom_fetcher); + case Target::Model::IIe: return std::make_unique>(*appleii_target, rom_fetcher); + case Target::Model::EnhancedIIe: return std::make_unique>(*appleii_target, rom_fetcher); + } } } diff --git a/Machines/Apple/AppleII/Card.hpp b/Machines/Apple/AppleII/Card.hpp index a5b6f0afa..22c4411c5 100644 --- a/Machines/Apple/AppleII/Card.hpp +++ b/Machines/Apple/AppleII/Card.hpp @@ -49,12 +49,16 @@ class Card { // by an access to $cfff. }; + // TODO: I think IO and Device are the wrong way around above. + // See https://retrocomputing.stackexchange.com/questions/5730/how-did-the-address-decode-for-apple-ii-expansion-cards-work + // and/or https://gglabs.us/sites/gglabs.us/files/a2scsi-A00_sch.pdf + /*! Advances time by @c cycles, of which @c stretches were stretched. This is posted only to cards that announced a select constraint. Cards with no constraints, that want to be informed of every machine cycle, will receive - a call to perform_bus_operation every cycle and should use that for time keeping. + a call to @c perform_bus_operation every cycle and should use that for time keeping. */ virtual void run_for([[maybe_unused]] Cycles cycles, [[maybe_unused]] int stretches) {} @@ -93,8 +97,15 @@ class Card { /*! Cards may supply a target for activity observation if desired. */ virtual void set_activity_observer([[maybe_unused]] Activity::Observer *observer) {} + /// @returns The current semantic NMI line output of this card. + virtual bool nmi() { return false; } + + /// @returns The current semantic IRQ line output of this card. + virtual bool irq() { return false; } + struct Delegate { virtual void card_did_change_select_constraints(Card *card) = 0; + virtual void card_did_change_interrupt_flags(Card *card) = 0; }; void set_delegate(Delegate *delegate) { delegate_ = delegate; diff --git a/Machines/Apple/AppleII/Mockingboard.hpp b/Machines/Apple/AppleII/Mockingboard.hpp new file mode 100644 index 000000000..b7d98544c --- /dev/null +++ b/Machines/Apple/AppleII/Mockingboard.hpp @@ -0,0 +1,135 @@ +// +// Mockingboard.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/02/2024. +// Copyright © 2024 Thomas Harte. All rights reserved. +// + +#pragma once + +#include "Card.hpp" + +#include "../../../Components/6522/6522.hpp" + +namespace Apple::II { + +class AYPair { + public: + AYPair(Concurrency::AsyncTaskQueue &queue) : + ays_{ + {GI::AY38910::Personality::AY38910, queue}, + {GI::AY38910::Personality::AY38910, queue}, + } {} + + void advance() { + ays_[0].advance(); + ays_[1].advance(); + } + + void set_sample_volume_range(std::int16_t range) { + ays_[0].set_sample_volume_range(range); + ays_[1].set_sample_volume_range(range); + } + + bool is_zero_level() const { + return ays_[0].is_zero_level() && ays_[1].is_zero_level(); + } + + Outputs::Speaker::StereoSample level() const { + Outputs::Speaker::StereoSample level; + level.left = ays_[0].level(); + level.right = ays_[1].level(); + return level; + } + + GI::AY38910::AY38910SampleSource &get(int index) { + return ays_[index]; + } + + private: + GI::AY38910::AY38910SampleSource ays_[2]; +}; + +class Mockingboard: public Card { + public: + Mockingboard(AYPair &ays) : + vias_{ {handlers_[0]}, {handlers_[1]} }, + handlers_{ {*this, ays.get(0)}, {*this, ays.get(1)}} { + set_select_constraints(0); + } + + void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final { + if(!(select & Device)) { + return; + } + + int index = (address >> 7) & 1; + if(is_read) { + *value = vias_[index].read(address); + } else { + vias_[index].write(address, *value); + } + } + + void run_for(Cycles cycles, int) final { + vias_[0].run_for(cycles); + vias_[1].run_for(cycles); + } + + bool nmi() final { + return handlers_[1].interrupt; + } + + bool irq() final { + return handlers_[0].interrupt; + } + + void did_change_interrupt_flags() { + delegate_->card_did_change_interrupt_flags(this); + } + + private: + struct AYVIA: public MOS::MOS6522::PortHandler { + AYVIA(Mockingboard &card, GI::AY38910::AY38910SampleSource &ay) : + card(card), ay(ay) {} + + void set_interrupt_status(bool status) { + interrupt = status; + card.did_change_interrupt_flags(); + } + + void set_port_output(MOS::MOS6522::Port port, uint8_t value, uint8_t) { + if(port) { + using ControlLines = GI::AY38910::ControlLines; + ay.set_control_lines( + ControlLines( + ((value & 1) ? ControlLines::BC1 : 0) | + ((value & 2) ? ControlLines::BDIR : 0) | + ControlLines::BC2 + ) + ); + + ay.set_reset(!(value & 4)); + } else { + ay.set_data_input(value); + } + } + + uint8_t get_port_input(MOS::MOS6522::Port port) { + if(!port) { + return ay.get_data_output(); + } + return 0xff; + } + + bool interrupt; + Mockingboard &card; + GI::AY38910::AY38910SampleSource &ay; + }; + + MOS::MOS6522::MOS6522 vias_[2]; + AYVIA handlers_[2]; +}; + +} diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 3babbde3e..069031998 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1568,6 +1568,7 @@ 4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = ""; }; 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = ""; }; 4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = ""; }; + 4B72FAEF2B7D51BB000C7E90 /* Mockingboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mockingboard.hpp; sourceTree = ""; }; 4B74CF7F2312FA9C00500CE8 /* HFV.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = HFV.hpp; sourceTree = ""; }; 4B74CF802312FA9C00500CE8 /* HFV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HFV.cpp; sourceTree = ""; }; 4B75F978280D7C5100121055 /* 68000DecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000DecoderTests.mm; sourceTree = ""; }; @@ -4840,6 +4841,7 @@ 4B2E86E125DC95150024F1E9 /* Joystick.hpp */, 4BF40A5525424C770033EA39 /* LanguageCardSwitches.hpp */, 4BE0151C286A8C8E00EA42E9 /* MemorySwitches.hpp */, + 4B72FAEF2B7D51BB000C7E90 /* Mockingboard.hpp */, 4B4C81C428B3C5CD00F84AE9 /* SCSICard.hpp */, 4BCE004F227CE8CA000CA200 /* Video.hpp */, 4B8DF4F2254E141700F3433C /* VideoSwitches.hpp */, diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index 1df0c77b7..e7f863ba9 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -141,7 +141,7 @@ typedef int Kilobytes; - (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model chipMemorySize:(Kilobytes)chipMemorySize fastMemorySize:(Kilobytes)fastMemorySize; - (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model; -- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController; +- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController hasMockingboard:(BOOL)hasMockingboard; - (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize; - (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model memorySize:(Kilobytes)memorySize; - (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 9bdcc1f9e..f825e844f 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -93,7 +93,7 @@ return self; } -- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController { +- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController hasMockingboard:(BOOL)hasMockingboard { self = [super init]; if(self) { using Target = Analyser::Static::AppleII::Target; @@ -110,6 +110,7 @@ case CSMachineAppleIIDiskControllerSixteenSector: target->disk_controller = Target::DiskController::SixteenSector; break; case CSMachineAppleIIDiskControllerThirteenSector: target->disk_controller = Target::DiskController::ThirteenSector; break; } + target->has_mockingboard = hasMockingboard; _targets.push_back(std::move(target)); } return self; diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 7b53dd6f6..b445ea9eb 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 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -49,7 +49,7 @@ Gw - + @@ -104,7 +104,7 @@ Gw - + @@ -135,7 +135,7 @@ Gw - + @@ -143,7 +143,7 @@ Gw - + @@ -202,7 +202,7 @@ Gw - + @@ -223,19 +223,19 @@ Gw - + - - + + - - + + @@ -243,7 +243,7 @@ Gw - + @@ -258,7 +258,7 @@ Gw - + @@ -271,16 +271,25 @@ Gw + - + + + @@ -292,7 +301,7 @@ Gw - + @@ -300,7 +309,7 @@ Gw - + @@ -357,7 +366,7 @@ Gw - + @@ -515,7 +524,7 @@ Gw - + @@ -523,7 +532,7 @@ Gw - + @@ -531,7 +540,7 @@ Gw - + @@ -539,7 +548,7 @@ Gw - + @@ -547,7 +556,7 @@ Gw - + @@ -593,7 +602,7 @@ Gw - + @@ -653,7 +662,7 @@ Gw - + @@ -661,7 +670,7 @@ Gw - + @@ -716,7 +725,7 @@ Gw - + @@ -754,7 +763,7 @@ Gw - + @@ -796,7 +805,7 @@ Gw - + @@ -804,7 +813,7 @@ Gw - + @@ -876,7 +885,7 @@ Gw - + @@ -884,7 +893,7 @@ Gw - + @@ -937,7 +946,7 @@ Gw - + @@ -985,7 +994,7 @@ Gw - + @@ -1026,7 +1035,7 @@ Gw - + @@ -1075,6 +1084,7 @@ Gw + diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index d9afc3431..a9217dba3 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -26,6 +26,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { // MARK: - Apple II properties @IBOutlet var appleIIModelButton: NSPopUpButton! @IBOutlet var appleIIDiskControllerButton: NSPopUpButton! + @IBOutlet var appleIIMockingboardButton: NSButton! // MARK: - Apple IIgs properties @IBOutlet var appleIIgsModelButton: NSPopUpButton! @@ -117,6 +118,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { // Apple II settings appleIIModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIModel")) appleIIDiskControllerButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIDiskController")) + appleIIMockingboardButton.state = standardUserDefaults.bool(forKey: "new.appleIIMockingboard") ? .on : .off // Apple IIgs settings appleIIgsModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsModel")) @@ -187,6 +189,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { // Apple II settings standardUserDefaults.set(appleIIModelButton.selectedTag(), forKey: "new.appleIIModel") standardUserDefaults.set(appleIIDiskControllerButton.selectedTag(), forKey: "new.appleIIDiskController") + standardUserDefaults.set(appleIIMockingboardButton.state == .on, forKey: "new.appleIIMockingboard") // Apple IIgs settings standardUserDefaults.set(appleIIgsModelButton.selectedTag(), forKey: "new.appleIIgsModel") @@ -292,7 +295,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate { default: diskController = .none } - return CSStaticAnalyser(appleIIModel: model, diskController: diskController) + return CSStaticAnalyser(appleIIModel: model, diskController: diskController, hasMockingboard: appleIIMockingboardButton.state == .on) case "appleiigs": var model: CSMachineAppleIIgsModel = .ROM00 diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 37080d954..e21fc48ac 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -1042,6 +1042,8 @@ void MainWindow::start_appleII() { case 2: target->disk_controller = Target::DiskController::None; break; } + target->has_mockingboard = ui->appleIIMockingboardCheckBox->isChecked(); + launchTarget(std::move(target)); } diff --git a/OSBindings/Qt/mainwindow.ui b/OSBindings/Qt/mainwindow.ui index eac216907..82ad39f93 100644 --- a/OSBindings/Qt/mainwindow.ui +++ b/OSBindings/Qt/mainwindow.ui @@ -202,6 +202,13 @@ + + + + Has Mockingboard + + + diff --git a/Outputs/Speaker/Implementation/CompoundSource.hpp b/Outputs/Speaker/Implementation/CompoundSource.hpp index bdfe93545..2cafa10c7 100644 --- a/Outputs/Speaker/Implementation/CompoundSource.hpp +++ b/Outputs/Speaker/Implementation/CompoundSource.hpp @@ -77,29 +77,28 @@ template class CompoundSource: // Map up and return. for(std::size_t c = 0; c < number_of_samples; c++) { - Outputs::Speaker::apply(target[c].left, StereoSample(conversion_source_[c])); + Outputs::Speaker::apply(target[c], StereoSample(conversion_source_[c])); } - return; - } + } else { + constexpr bool is_final_source = sizeof...(R) == 0; - constexpr bool is_final_source = sizeof...(R) == 0; - - // Get the rest of the output, if any. - if constexpr (!is_final_source) { - next_source_.template apply_samples(number_of_samples, target); - } - - if(source_.is_zero_level()) { - // This component is currently outputting silence; therefore don't add anything to the output - // audio. Zero fill only if this is the final source (as everything above will be additive). - if constexpr (is_final_source) { - Outputs::Speaker::fill(target, target + number_of_samples, typename SampleT::type()); + // Get the rest of the output, if any. + if constexpr (!is_final_source) { + next_source_.template apply_samples(number_of_samples, target); } - return; - } - // Add in this component's output. - source_.template apply_samples(number_of_samples, target); + if(source_.is_zero_level()) { + // This component is currently outputting silence; therefore don't add anything to the output + // audio. Zero fill only if this is the final source (as everything above will be additive). + if constexpr (is_final_source) { + Outputs::Speaker::fill(target, target + number_of_samples, typename SampleT::type()); + } + return; + } + + // Add in this component's output. + source_.template apply_samples(number_of_samples, target); + } } void set_scaled_volume_range(int16_t range, double *volumes, double scale) {