1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-29 00:29:34 +00:00

Attempts to bring audio to the Apple II.

By factoring the audio toggle out from the MSX.
This commit is contained in:
Thomas Harte 2018-04-17 22:28:13 -04:00
parent a07c99d778
commit f22c23cb4c
5 changed files with 126 additions and 48 deletions

View 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_;
}

View 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 */

View File

@ -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 {

View File

@ -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;

View File

@ -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 */,