diff --git a/Components/AudioToggle/AudioToggle.cpp b/Components/AudioToggle/AudioToggle.cpp new file mode 100644 index 000000000..e0545ea4a --- /dev/null +++ b/Components/AudioToggle/AudioToggle.cpp @@ -0,0 +1,38 @@ +// +// AudioToggle.cpp +// Clock Signal +// +// Created by Thomas Harte on 17/04/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "AudioToggle.hpp" + +using namespace Audio; + +Audio::Toggle::Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) : + audio_queue_(audio_queue) {} + +void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) { + for(std::size_t sample = 0; sample < number_of_samples; ++sample) { + target[sample] = level_; + } +} + +void Toggle::set_sample_volume_range(std::int16_t range) { + volume_ = range; +} + +void Toggle::skip_samples(const std::size_t number_of_samples) {} + +void Toggle::set_output(bool enabled) { + if(is_enabled_ == enabled) return; + is_enabled_ = enabled; + audio_queue_.defer([=] { + level_ = enabled ? volume_ : 0; + }); +} + +bool Toggle::get_output() { + return is_enabled_; +} diff --git a/Components/AudioToggle/AudioToggle.hpp b/Components/AudioToggle/AudioToggle.hpp new file mode 100644 index 000000000..e3d29ac30 --- /dev/null +++ b/Components/AudioToggle/AudioToggle.hpp @@ -0,0 +1,39 @@ +// +// AudioToggle.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/04/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef AudioToggle_hpp +#define AudioToggle_hpp + +#include "../../Outputs/Speaker/Implementation/SampleSource.hpp" +#include "../../Concurrency/AsyncTaskQueue.hpp" + +namespace Audio { + +/*! + Provides a sample source that can programmatically be set to one of two values. +*/ +class Toggle: public Outputs::Speaker::SampleSource { + public: + Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue); + + void get_samples(std::size_t number_of_samples, std::int16_t *target); + void set_sample_volume_range(std::int16_t range); + void skip_samples(const std::size_t number_of_samples); + + void set_output(bool enabled); + bool get_output(); + + private: + bool is_enabled_ = false; + int16_t level_ = 0, volume_ = 0; + Concurrency::DeferringAsyncTaskQueue &audio_queue_; +}; + +} + +#endif /* AudioToggle_hpp */ diff --git a/Machines/AppleII/AppleII.cpp b/Machines/AppleII/AppleII.cpp index 9e51d7fd1..27376376b 100644 --- a/Machines/AppleII/AppleII.cpp +++ b/Machines/AppleII/AppleII.cpp @@ -13,6 +13,9 @@ #include "../Utility/MemoryFuzzer.hpp" #include "../../Processors/6502/6502.hpp" +#include "../../Components/AudioToggle/AudioToggle.hpp" + +#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp" #include "Video.hpp" @@ -48,6 +51,9 @@ class ConcreteMachine: void update_video() { video_->run_for(cycles_since_video_update_.flush()); } + void update_audio() { + speaker_.run_for(cycles_since_audio_update_.divide(Cycles(16))); + } uint8_t ram_[48*1024]; std::vector rom_; @@ -55,11 +61,19 @@ class ConcreteMachine: uint16_t rom_start_address_; uint8_t keyboard_input_ = 0x00; + Concurrency::DeferringAsyncTaskQueue audio_queue_; + Audio::Toggle audio_toggle_; + Outputs::Speaker::LowpassSpeaker speaker_; + Cycles cycles_since_audio_update_; + public: ConcreteMachine(): m6502_(*this), - video_bus_handler_(ram_) { + video_bus_handler_(ram_), + audio_toggle_(audio_queue_), + speaker_(audio_toggle_) { set_clock_rate(1022727); + speaker_.set_input_rate(7159089.0f / 16.0f); Memory::Fuzz(ram_, sizeof(ram_)); } @@ -77,11 +91,12 @@ class ConcreteMachine: } Outputs::Speaker::Speaker *get_speaker() override { - return nullptr; + return &speaker_; } Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) { ++ cycles_since_video_update_; + cycles_since_audio_update_ += Cycles(7); switch(address) { default: @@ -109,7 +124,6 @@ class ConcreteMachine: update_video(); } ram_[address] = *value; -// printf("%04x <- %02x\n", address, *value); } } break; @@ -126,16 +140,22 @@ class ConcreteMachine: case 0xc010: keyboard_input_ &= 0x7f; break; + + case 0xc030: + update_audio(); + audio_toggle_.set_output(!audio_toggle_.get_output()); + break; } // The Apple II has a slightly weird timing pattern: every 65th CPU cycle is stretched // by an extra 1/7th. That's because one cycle lasts 3.5 NTSC colour clocks, so after // 65 cycles a full line of 227.5 colour clocks have passed. But the high-rate binary // signal approximation that produces colour needs to be in phase, so a stretch of exactly - // 0.5 further colour cycles is added. + // 0.5 further colour cycles is added. The video class handles that implicitly, but it + // needs to be accumulated here for the audio. cycles_into_current_line_ = (cycles_into_current_line_ + 1) % 65; if(!cycles_into_current_line_) { - // Do something. Do something else. + ++ cycles_since_audio_update_; } return Cycles(1); @@ -143,6 +163,8 @@ class ConcreteMachine: void flush() { update_video(); + update_audio(); + audio_queue_.perform(); } bool set_rom_fetcher(const std::function>>(const std::string &machine, const std::vector &names)> &roms_with_names) override { diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 5794be009..e94b9ed50 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -23,6 +23,7 @@ #include "../../Components/1770/1770.hpp" #include "../../Components/9918/9918.hpp" #include "../../Components/8255/i8255.hpp" +#include "../../Components/AudioToggle/AudioToggle.hpp" #include "../../Components/AY38910/AY38910.hpp" #include "../../Components/KonamiSCC/KonamiSCC.hpp" @@ -50,44 +51,6 @@ std::vector> get_options() { ); } -/*! - Provides a sample source that can programmatically be set to one of two values. -*/ -class AudioToggle: public Outputs::Speaker::SampleSource { - public: - AudioToggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) : - audio_queue_(audio_queue) {} - - void get_samples(std::size_t number_of_samples, std::int16_t *target) { - for(std::size_t sample = 0; sample < number_of_samples; ++sample) { - target[sample] = level_; - } - } - - void set_sample_volume_range(std::int16_t range) { - volume_ = range; - } - - void skip_samples(const std::size_t number_of_samples) {} - - void set_output(bool enabled) { - if(is_enabled_ == enabled) return; - is_enabled_ = enabled; - audio_queue_.defer([=] { - level_ = enabled ? volume_ : 0; - }); - } - - bool get_output() { - return is_enabled_; - } - - private: - bool is_enabled_ = false; - int16_t level_ = 0, volume_ = 0; - Concurrency::DeferringAsyncTaskQueue &audio_queue_; -}; - class AYPortHandler: public GI::AY38910::PortHandler { public: AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {} @@ -600,7 +563,7 @@ class ConcreteMachine: class i8255PortHandler: public Intel::i8255::PortHandler { public: - i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) : + i8255PortHandler(ConcreteMachine &machine, Audio::Toggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) : machine_(machine), audio_toggle_(audio_toggle), tape_player_(tape_player) {} void set_value(int port, uint8_t value) { @@ -637,7 +600,7 @@ class ConcreteMachine: private: ConcreteMachine &machine_; - AudioToggle &audio_toggle_; + Audio::Toggle &audio_toggle_; Storage::Tape::BinaryTapePlayer &tape_player_; }; @@ -647,10 +610,10 @@ class ConcreteMachine: Concurrency::DeferringAsyncTaskQueue audio_queue_; GI::AY38910::AY38910 ay_; - AudioToggle audio_toggle_; + Audio::Toggle audio_toggle_; Konami::SCC scc_; - Outputs::Speaker::CompoundSource mixer_; - Outputs::Speaker::LowpassSpeaker> speaker_; + Outputs::Speaker::CompoundSource mixer_; + Outputs::Speaker::LowpassSpeaker> speaker_; Storage::Tape::BinaryTapePlayer tape_player_; bool tape_player_is_sleeping_ = false; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index a9b81b855..432e5621c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -208,6 +208,8 @@ 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; 4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */; }; 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; + 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */; }; + 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 */; }; 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; @@ -856,6 +858,8 @@ 4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MFMSectorDump.hpp; sourceTree = ""; }; 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = ""; }; 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = ""; }; + 4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioToggle.hpp; sourceTree = ""; }; + 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioToggle.cpp; sourceTree = ""; }; 4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = ""; }; 4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = ""; }; 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = ""; }; @@ -1929,6 +1933,15 @@ path = Views; sourceTree = ""; }; + 4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = { + isa = PBXGroup; + children = ( + 4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */, + 4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */, + ); + path = AudioToggle; + sourceTree = ""; + }; 4B643F3B1D77AD6D00D431D6 /* StaticAnalyser */ = { isa = PBXGroup; children = ( @@ -2837,6 +2850,7 @@ 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( + 4B595FAA2086DFBA0083CAA8 /* AudioToggle */, 4BD468F81D8DF4290084958B /* 1770 */, 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, @@ -3607,6 +3621,7 @@ 4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */, 4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */, 4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */, + 4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, 4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */, 4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */, 4B89452D201967B4007DE474 /* Tape.cpp in Sources */, @@ -3652,6 +3667,7 @@ 4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, + 4B595FAD2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */, 4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,