From 723137c0d42992126b64676e46dde1fd93b91343 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 1 Jun 2019 14:39:40 -0400 Subject: [PATCH] With some time additions to the 6522, starts wiring in Macintosh audio. The audio buffer is also the disk motor buffer, so this is preparatory to further disk work. --- Components/6522/6522.hpp | 11 +++ .../Implementation/6522Implementation.hpp | 19 ++++- Components/DiskII/IWM.cpp | 14 ++-- Machines/Apple/Macintosh/Audio.cpp | 50 ++++++++++++ Machines/Apple/Macintosh/Audio.hpp | 78 +++++++++++++++++++ Machines/Apple/Macintosh/Macintosh.cpp | 45 ++++++++--- Machines/Oric/Oric.cpp | 9 +-- .../Clock Signal.xcodeproj/project.pbxproj | 12 ++- 8 files changed, 214 insertions(+), 24 deletions(-) create mode 100644 Machines/Apple/Macintosh/Audio.cpp create mode 100644 Machines/Apple/Macintosh/Audio.hpp diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp index bc2bdbc58..e23bfa8a4 100644 --- a/Components/6522/6522.hpp +++ b/Components/6522/6522.hpp @@ -47,6 +47,12 @@ class PortHandler { /// Sets the current logical value of the interrupt line. void set_interrupt_status(bool status) {} + + /// Provides a measure of time elapsed between other calls. + void run_for(HalfCycles duration) {} + + /// Receives passed-on flush() calls from the 6522. + void flush() {} }; /*! @@ -108,12 +114,17 @@ template class MOS6522: public MOS6522Storage { /// @returns @c true if the IRQ line is currently active; @c false otherwise. bool get_interrupt_line(); + /// Updates the port handler to the current time and then requests that it flush. + void flush(); + private: void do_phase1(); void do_phase2(); void shift_in(); void shift_out(); + T &bus_handler_; + HalfCycles time_since_bus_handler_call_; void access(int address); diff --git a/Components/6522/Implementation/6522Implementation.hpp b/Components/6522/Implementation/6522Implementation.hpp index e9f104f60..aaded6596 100644 --- a/Components/6522/Implementation/6522Implementation.hpp +++ b/Components/6522/Implementation/6522Implementation.hpp @@ -37,6 +37,8 @@ template void MOS6522::set_register(int address, uint8_t value) case 0x0: // Write Port B. // Store locally and communicate outwards. registers_.output[1] = value; + + bus_handler_.run_for(time_since_bus_handler_call_.flush()); bus_handler_.set_port_output(Port::B, value, registers_.data_direction[1]); registers_.interrupt_flags &= ~(InterruptFlag::CB1ActiveEdge | ((registers_.peripheral_control&0x20) ? 0 : InterruptFlag::CB2ActiveEdge)); @@ -45,6 +47,8 @@ template void MOS6522::set_register(int address, uint8_t value) case 0xf: case 0x1: // Write Port A. registers_.output[0] = value; + + bus_handler_.run_for(time_since_bus_handler_call_.flush()); bus_handler_.set_port_output(Port::A, value, registers_.data_direction[0]); if(handshake_modes_[1] != HandshakeMode::None) { @@ -193,7 +197,8 @@ template uint8_t MOS6522::get_register(int address) { } template uint8_t MOS6522::get_port_input(Port port, uint8_t output_mask, uint8_t output) { - uint8_t input = bus_handler_.get_port_input(port); + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + const uint8_t input = bus_handler_.get_port_input(port); return (input & ~output_mask) | (output & output_mask); } @@ -206,6 +211,8 @@ template void MOS6522::reevaluate_interrupts() { bool new_interrupt_status = get_interrupt_line(); if(new_interrupt_status != last_posted_interrupt_status_) { last_posted_interrupt_status_ = new_interrupt_status; + + bus_handler_.run_for(time_since_bus_handler_call_.flush()); bus_handler_.set_interrupt_status(new_interrupt_status); } } @@ -251,6 +258,8 @@ template void MOS6522::set_control_line_input(Port port, Line li } template void MOS6522::do_phase2() { + ++ time_since_bus_handler_call_; + registers_.last_timer[0] = registers_.timer[0]; registers_.last_timer[1] = registers_.timer[1]; @@ -281,6 +290,8 @@ template void MOS6522::do_phase2() { } template void MOS6522::do_phase1() { + ++ time_since_bus_handler_call_; + // IRQ is raised on the half cycle after overflow if((registers_.timer[1] == 0xffff) && !registers_.last_timer[1] && timer_is_running_[1]) { timer_is_running_[1] = false; @@ -339,6 +350,11 @@ template void MOS6522::run_for(const HalfCycles half_cycles) { } } +template void MOS6522::flush() { + bus_handler_.run_for(time_since_bus_handler_call_.flush()); + bus_handler_.flush(); +} + /*! Runs for a specified number of cycles. */ template void MOS6522::run_for(const Cycles cycles) { int number_of_cycles = cycles.as_int(); @@ -363,6 +379,7 @@ template void MOS6522::set_control_line_output(Port port, Line l // control line is actually in output mode. control_outputs_[port].lines[line] = value; if(registers_.peripheral_control & (0x08 << (port * 4))) { + bus_handler_.run_for(time_since_bus_handler_call_.flush()); bus_handler_.set_control_line_output(port, line, value); } } diff --git a/Components/DiskII/IWM.cpp b/Components/DiskII/IWM.cpp index 8b1cfbe0e..1071b85fd 100644 --- a/Components/DiskII/IWM.cpp +++ b/Components/DiskII/IWM.cpp @@ -38,11 +38,14 @@ uint8_t IWM::read(int address) { // you must first turn off Q7 by reading or writing dBase+q7L. Then turn on Q6 by accessing dBase+q6H. // After that, the IWM will be able to pass data from the disk's RD/SENSE line through to you." // - // My thoughts: I'm unclear on how passing data from RD/SENSE is conflated with reading "any of the disk - // registers", but the text reads as though the access happens internally but isn't passed on in the case - // of Q6 and Q7 being in the incorrect state. + // My understanding: + // + // Q6 = 1, Q7 = 0 reads the status register. The meaning of the top 'SENSE' bit is then determined by + // the CA0,1,2 and SEL switches as described in Inside Macintosh, summarised above as RD/SENSE. - printf("Read %d: ", address&1); + if(address&1) { + return 0xff; + } switch(state_ & (Q6 | Q7 | ENABLE)) { default: @@ -73,7 +76,7 @@ uint8_t IWM::read(int address) { printf("Reading status ([%d] including ", (state_&DRIVESEL) ? 2 : 1); // Determine the SENSE input. - uint8_t sense = 0; + uint8_t sense = 0x80; switch(state_ & (CA2 | CA1 | CA0 | SEL)) { default: printf("unknown)\n"); @@ -85,7 +88,6 @@ uint8_t IWM::read(int address) { case SEL: // Disk in place. printf("disk in place)\n"); - sense = 0x80; break; case CA0: // Disk head stepping. diff --git a/Machines/Apple/Macintosh/Audio.cpp b/Machines/Apple/Macintosh/Audio.cpp new file mode 100644 index 000000000..7f476db0b --- /dev/null +++ b/Machines/Apple/Macintosh/Audio.cpp @@ -0,0 +1,50 @@ +// +// Audio.cpp +// Clock Signal +// +// Created by Thomas Harte on 31/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#include "Audio.hpp" + +using namespace Apple::Macintosh; + +Audio::Audio(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {} + +// MARK: - Inputs + +void Audio::post_sample(uint8_t sample) { + const auto write_pointer = sample_queue_.write_pointer.load(); +// const auto pointers + + const auto read_pointer = sample_queue_.read_pointer.load(); +} + +void Audio::set_volume(int volume) { + task_queue_.defer([=] () { + volume_ = volume; + }); +} + +void Audio::set_enabled(bool on) { + task_queue_.defer([=] () { + is_enabled_ = on; + }); +} + +// MARK: - Output generation + +bool Audio::is_zero_level() { + return !volume_ || !is_enabled_; +} + +void Audio::set_sample_volume_range(std::int16_t range) { + volume_multiplier_ = range / 7; +} + +void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { +// if(is_zero_level()) { +// memset(target, 0, number_of_samples * sizeof(int16_t)); +// } +} diff --git a/Machines/Apple/Macintosh/Audio.hpp b/Machines/Apple/Macintosh/Audio.hpp new file mode 100644 index 000000000..ea3c035cb --- /dev/null +++ b/Machines/Apple/Macintosh/Audio.hpp @@ -0,0 +1,78 @@ +// +// Audio.hpp +// Clock Signal +// +// Created by Thomas Harte on 31/05/2019. +// Copyright © 2019 Thomas Harte. All rights reserved. +// + +#ifndef Audio_hpp +#define Audio_hpp + +#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../../Concurrency/AsyncTaskQueue.hpp" +#include "../../../ClockReceiver/ClockReceiver.hpp" + +#include +#include + +namespace Apple { +namespace Macintosh { + +/*! + Implements the Macintosh's audio output hardware, using a + combination +*/ +class Audio: public ::Outputs::Speaker::SampleSource { + public: + Audio(Concurrency::DeferringAsyncTaskQueue &task_queue); + + /*! + Macintosh audio is (partly) sourced by the same scanning + hardware as the video; each line it collects an additional + word of memory, half of which is used for audio output. + + Use this method to add a newly-collected sample to the queue. + */ + void post_sample(uint8_t sample); + + /*! + Macintosh audio also separately receives an output volume + level, in the range 0 to 7. + + Use this method to set the current output volume. + */ + void set_volume(int volume); + + /*! + A further factor in audio output is the on-off toggle. + */ + void set_enabled(bool on); + + // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter. + void get_samples(std::size_t number_of_samples, int16_t *target); + bool is_zero_level(); + void set_sample_volume_range(std::int16_t range); + + private: + Concurrency::DeferringAsyncTaskQueue &task_queue_; + + // A queue of fetched samples; read from by one thread, + // written to by another. + struct { + std::array buffer; + std::atomic read_pointer, write_pointer; + } sample_queue_; + + // Stateful variables, modified from the audio generation + // thread only. + int volume_ = 0; + bool is_enabled_ = false; + + std::int16_t volume_multiplier_ = 0; +}; + +} +} + +#endif /* Audio_hpp */ diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index be88f79b4..a03c10489 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -10,6 +10,7 @@ #include +#include "Audio.hpp" #include "Keyboard.hpp" #include "RealTimeClock.hpp" #include "Video.hpp" @@ -18,9 +19,10 @@ //#define LOG_TRACE -#include "../../../Processors/68000/68000.hpp" #include "../../../Components/6522/6522.hpp" #include "../../../Components/DiskII/IWM.hpp" +#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" +#include "../../../Processors/68000/68000.hpp" #include "../../Utility/MemoryPacker.hpp" @@ -42,7 +44,7 @@ class ConcreteMachine: mc68000_(*this), video_(ram_.data()), via_(via_port_handler_), - via_port_handler_(*this, clock_, keyboard_, video_, iwm_), + via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_), iwm_(CLOCK_RATE) { // Grab a copy of the ROM and convert it into big-endian data. @@ -62,7 +64,7 @@ class ConcreteMachine: } Outputs::Speaker::Speaker *get_speaker() override { - return nullptr; + return &audio_.speaker; } void run_for(const Cycles cycles) override { @@ -227,7 +229,14 @@ class ConcreteMachine: } void flush() { + // Flush the video before the audio queue; in a Mac the + // video is responsible for providing part of the + // audio signal, so the two aren't as distinct as in + // most machines. // video_.run_for(time_since_video_update_.flush()); + + // As above: flush audio after video. + audio_.queue.flush(); } void set_rom_is_overlay(bool rom_is_overlay) { @@ -250,10 +259,23 @@ class ConcreteMachine: } }; + struct Audio { + Concurrency::DeferringAsyncTaskQueue queue; + Apple::Macintosh::Audio audio; + Outputs::Speaker::LowpassSpeaker speaker; + HalfCycles time_since_update; + + Audio() : audio(queue), speaker(audio) {} + + void flush() { + speaker.run_for(queue, time_since_update.flush_cycles()); + } + }; + class VIAPortHandler: public MOS::MOS6522::PortHandler { public: - VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, IWM &iwm) : - machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), iwm_(iwm) {} + VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, Audio &audio, IWM &iwm) : + machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm) {} using Port = MOS::MOS6522::Port; using Line = MOS::MOS6522::Line; @@ -274,15 +296,16 @@ class ConcreteMachine: b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer b2–b0: audio output volume */ -// printf("6522 A: %02x\n", value); - iwm_.flush(); iwm_.iwm.set_select(!(value & 0x20)); machine_.set_use_alternate_screen_buffer(!(value & 0x40)); machine_.set_rom_is_overlay(!!(value & 0x10)); - // TODO: alternate sound buffer, and audio output volume. + audio_.flush(); + audio_.audio.set_volume(value & 7); + + // TODO: alternate sound buffer. break; case Port::B: @@ -300,7 +323,8 @@ class ConcreteMachine: if(value & 0x4) clock_.abort(); else clock_.set_input(!!(value & 0x2), !!(value & 0x1)); - // TODO: sound enabled/disabled. + audio_.flush(); + audio_.audio.set_enabled(!!(value & 0x80)); break; } } @@ -336,6 +360,7 @@ class ConcreteMachine: RealTimeClock &clock_; Keyboard &keyboard_; Video &video_; + Audio &audio_; IWM &iwm_; }; @@ -343,7 +368,9 @@ class ConcreteMachine: std::array ram_; CPU::MC68000::Processor mc68000_; + Video video_; + Audio audio_; RealTimeClock clock_; Keyboard keyboard_; diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 34b847688..58ebdd907 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -170,7 +170,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { /*! Advances time. This class manages the AY's concept of time to permit updating-on-demand. */ - inline void run_for(const Cycles cycles) { + inline void run_for(const HalfCycles cycles) { cycles_since_ay_update_ += cycles; } @@ -182,11 +182,11 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler { private: void update_ay() { - speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush()); + speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush_cycles()); } bool ay_bdir_ = false; bool ay_bc1_ = false; - Cycles cycles_since_ay_update_; + HalfCycles cycles_since_ay_update_; Concurrency::DeferringAsyncTaskQueue &audio_queue_; GI::AY38910::AY38910 &ay8910_; @@ -434,7 +434,6 @@ template class Co } via_.run_for(Cycles(1)); - via_port_handler_.run_for(Cycles(1)); tape_player_.run_for(Cycles(1)); switch(disk_interface) { default: break; @@ -456,7 +455,7 @@ template class Co forceinline void flush() { update_video(); - via_port_handler_.flush(); + via_.flush(); flush_diskii(); } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 78852d3f5..10022b850 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -296,6 +296,7 @@ 4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; }; 4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; }; 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; + 4B9378E422A199C600973513 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9378E222A199C600973513 /* Audio.cpp */; }; 4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; }; 4B98A05F1FFAD62400ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; }; 4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */; }; @@ -1031,6 +1032,8 @@ 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = ""; }; 4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = ""; }; + 4B9378E222A199C600973513 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = ""; }; + 4B9378E322A199C600973513 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = ""; }; 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = ""; }; 4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = ""; }; 4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CSROMFetcher.hpp; sourceTree = ""; }; @@ -3065,12 +3068,14 @@ 4BCE0057227CFFCA000CA200 /* Macintosh */ = { isa = PBXGroup; children = ( + 4B9378E222A199C600973513 /* Audio.cpp */, 4BCE0058227CFFCA000CA200 /* Macintosh.cpp */, 4BCE005E227D39AB000CA200 /* Video.cpp */, + 4B9378E322A199C600973513 /* Audio.hpp */, + 4BDB3D8522833321002D3CEE /* Keyboard.hpp */, 4BCE0059227CFFCA000CA200 /* Macintosh.hpp */, 4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */, 4BCE005F227D39AB000CA200 /* Video.hpp */, - 4BDB3D8522833321002D3CEE /* Keyboard.hpp */, ); path = Macintosh; sourceTree = ""; @@ -3254,11 +3259,11 @@ 4BF660691F281573002CB053 /* ClockReceiver */ = { isa = PBXGroup; children = ( + 4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */, + 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, 4BF6606A1F281573002CB053 /* ClockReceiver.hpp */, 4BB06B211F316A3F00600C7A /* ForceInline.hpp */, - 4BB146C61F49D7D700253439 /* ClockingHintSource.hpp */, 4B449C942063389900A095C8 /* TimeTypes.hpp */, - 4B8A7E85212F988200F2BBC6 /* ClockDeferrer.hpp */, ); name = ClockReceiver; path = ../../ClockReceiver; @@ -3905,6 +3910,7 @@ 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, + 4B9378E422A199C600973513 /* Audio.cpp in Sources */, 4B89451E201967B4007DE474 /* Tape.cpp in Sources */, 4BAF2B4E2004580C00480230 /* DMK.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */,