mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-24 12:30:17 +00:00
Attempts to bring audio to the Apple II.
By factoring the audio toggle out from the MSX.
This commit is contained in:
parent
a07c99d778
commit
f22c23cb4c
38
Components/AudioToggle/AudioToggle.cpp
Normal file
38
Components/AudioToggle/AudioToggle.cpp
Normal file
@ -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_;
|
||||
}
|
39
Components/AudioToggle/AudioToggle.hpp
Normal file
39
Components/AudioToggle/AudioToggle.hpp
Normal file
@ -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 */
|
@ -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<uint8_t> 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<Audio::Toggle> 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<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::string &machine, const std::vector<std::string> &names)> &roms_with_names) override {
|
||||
|
@ -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<std::unique_ptr<Configurable::Option>> 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<GI::AY38910::AY38910, AudioToggle, Konami::SCC> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle, Konami::SCC>> speaker_;
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, Audio::Toggle, Konami::SCC>> speaker_;
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
bool tape_player_is_sleeping_ = false;
|
||||
|
@ -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 = "<group>"; };
|
||||
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
|
||||
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
|
||||
4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = AudioToggle.hpp; sourceTree = "<group>"; };
|
||||
4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AudioToggle.cpp; sourceTree = "<group>"; };
|
||||
4B5FADB81DE3151600AEC565 /* FileHolder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileHolder.cpp; sourceTree = "<group>"; };
|
||||
4B5FADB91DE3151600AEC565 /* FileHolder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileHolder.hpp; sourceTree = "<group>"; };
|
||||
4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Microdisc.cpp; path = Oric/Microdisc.cpp; sourceTree = "<group>"; };
|
||||
@ -1929,6 +1933,15 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B595FAA2086DFBA0083CAA8 /* AudioToggle */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B595FAB2086DFBA0083CAA8 /* AudioToggle.hpp */,
|
||||
4B595FAC2086DFBA0083CAA8 /* AudioToggle.cpp */,
|
||||
);
|
||||
path = AudioToggle;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 */,
|
||||
|
Loading…
Reference in New Issue
Block a user