1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-22 12:33:29 +00:00

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.
This commit is contained in:
Thomas Harte 2019-06-01 14:39:40 -04:00
parent 938928865d
commit 723137c0d4
8 changed files with 214 additions and 24 deletions

View File

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

View File

@ -37,6 +37,8 @@ template <typename T> void MOS6522<T>::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 <typename T> void MOS6522<T>::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 <typename T> uint8_t MOS6522<T>::get_register(int address) {
}
template <typename T> uint8_t MOS6522<T>::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 <typename T> void MOS6522<T>::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 <typename T> void MOS6522<T>::set_control_line_input(Port port, Line li
}
template <typename T> void MOS6522<T>::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 <typename T> void MOS6522<T>::do_phase2() {
}
template <typename T> void MOS6522<T>::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 <typename T> void MOS6522<T>::run_for(const HalfCycles half_cycles) {
}
}
template <typename T> void MOS6522<T>::flush() {
bus_handler_.run_for(time_since_bus_handler_call_.flush());
bus_handler_.flush();
}
/*! Runs for a specified number of cycles. */
template <typename T> void MOS6522<T>::run_for(const Cycles cycles) {
int number_of_cycles = cycles.as_int();
@ -363,6 +379,7 @@ template <typename T> void MOS6522<T>::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);
}
}

View File

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

View File

@ -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));
// }
}

View File

@ -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 <array>
#include <atomic>
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<uint8_t, 2048> buffer;
std::atomic<int> 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 */

View File

@ -10,6 +10,7 @@
#include <array>
#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<Apple::Macintosh::Audio> 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
b2b0: 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<uint16_t, 64*1024> ram_;
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
Video video_;
Audio audio_;
RealTimeClock clock_;
Keyboard keyboard_;

View File

@ -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 <Analyser::Static::Oric::Target::DiskInterface disk_interface> 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 <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
forceinline void flush() {
update_video();
via_port_handler_.flush();
via_.flush();
flush_diskii();
}

View File

@ -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 = "<group>"; };
4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; };
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
4B9378E222A199C600973513 /* Audio.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Audio.cpp; sourceTree = "<group>"; };
4B9378E322A199C600973513 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = "<group>"; };
4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = "<group>"; };
4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CSROMFetcher.hpp; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,