From 1d288b08b6f58f9f11c33c14976002c7bf0b70e3 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Thu, 19 Nov 2020 21:19:27 -0500 Subject: [PATCH] Attempts the two most basic forms of DOC output. Sans interrupts. Or register reads of any variety. --- Machines/Apple/AppleIIgs/AppleIIgs.cpp | 2 +- Machines/Apple/AppleIIgs/Sound.cpp | 70 ++++++++++++++++++++++++-- Machines/Apple/AppleIIgs/Sound.hpp | 3 ++ 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index ed9f136d7..ef57b4de6 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -764,7 +764,7 @@ class ConcreteMachine: AudioSource mixer_; Outputs::Speaker::LowpassSpeaker speaker_; Cycles cycles_since_audio_update_; - static constexpr int audio_divider = 8; + static constexpr int audio_divider = 16; void update_audio() { const auto divided_cycles = cycles_since_audio_update_.divide(Cycles(audio_divider)); sound_glu_.run_for(divided_cycles); diff --git a/Machines/Apple/AppleIIgs/Sound.cpp b/Machines/Apple/AppleIIgs/Sound.cpp index 53d6391fe..d7c12fb9c 100644 --- a/Machines/Apple/AppleIIgs/Sound.cpp +++ b/Machines/Apple/AppleIIgs/Sound.cpp @@ -74,7 +74,7 @@ void GLU::EnsoniqState::set_register(uint16_t address, uint8_t value) { interrupt_state = value; break; case 0xe1: - oscillator_count = (value >> 1) ? (value >> 1) : 1; + oscillator_count = 1 + ((value >> 1) & 31); break; case 0xe2: /* Writing to the analogue to digital input definitely makes no sense. */ @@ -164,8 +164,49 @@ void GLU::generate_audio(size_t number_of_samples, std::int16_t *target, int16_t (void)range; auto next_store = pending_stores_[pending_store_read_].load(std::memory_order::memory_order_acquire); - for(size_t c = 0; c < number_of_samples; c++) { - target[c] = 0; + for(size_t sample = 0; sample < number_of_samples; sample++) { + + // TODO: there's a bit of a hack here where it is assumed that the input clock has been + // divided in advance. Real hardware divides by 8, I think? + + // Seed output as 0. + int output = 0; + + // Apply phase updates to all enabled oscillators. + for(int c = 0; c < remote_.oscillator_count; c++) { + // Don't do anything for halted oscillators. + if(remote_.oscillators[c].control&1) continue; + + remote_.oscillators[c].position += remote_.oscillators[c].velocity; + + // Test for a new halting event. + switch(remote_.oscillators[c].control & 6) { + case 0: // Free-run mode; just keep the counter to its genuine 24 bits and always update sample. + remote_.oscillators[c].position &= 0x00ffffff; + output += remote_.oscillators[c].output(remote_.ram_); + break; + + case 2: // One-shot mode; check for end of run. Otherwise update sample. + if(remote_.oscillators[c].position & 0xff000000) { + remote_.oscillators[c].position = 0; + remote_.oscillators[c].control |= 1; + } else { + output += remote_.oscillators[c].output(remote_.ram_); + } + break; + + case 4: // Sync/AM mode. + assert(false); + break; + + case 6: // Swap mode. + assert(false); + break; + } + } + + // Maximum total output was 32 channels times a 16-bit range. Map that down. + target[sample] = (output * output_range_) >> 20; // Apply any RAM writes that interleave here. ++pending_store_read_time_; @@ -178,6 +219,29 @@ void GLU::generate_audio(size_t number_of_samples, std::int16_t *target, int16_t } } +uint8_t GLU::EnsoniqState::Oscillator::sample(uint8_t *ram) { + // Table size mask should be 0x8000 for the largest table size, and 0xff00 for + // the smallest. + const uint16_t table_size_mask = 0xffff >> (8 - ((table_size >> 3) & 7)); + + // The pointer should use (at most) 15 bits; starting with bit 1 for resolution 0 + // and starting at bit 8 for resolution 7. + const uint16_t table_pointer = uint16_t(position >> (1 + table_size&7)); + + // The full pointer is composed of the bits of the programmed address not touched by + // the table pointer, plus the table pointer. + const uint16_t sample_address = (address & ~table_size_mask) | (table_pointer & table_size_mask); + + // Ignored here: bit 6 should select between RAM banks. But for now this is IIgs-centric, + // and that has only one bank of RAM. + return ram[sample_address]; +} + +int16_t GLU::EnsoniqState::Oscillator::output(uint8_t *ram) { + // Samples are unsigned 8-bit; do the proper work to make volume work correctly. + return int8_t(sample(ram) ^ 128) * volume; +} + void GLU::skip_audio(EnsoniqState &state, size_t number_of_samples) { (void)number_of_samples; (void)state; diff --git a/Machines/Apple/AppleIIgs/Sound.hpp b/Machines/Apple/AppleIIgs/Sound.hpp index c84bd6615..5fb7a3278 100644 --- a/Machines/Apple/AppleIIgs/Sound.hpp +++ b/Machines/Apple/AppleIIgs/Sound.hpp @@ -75,6 +75,9 @@ class GLU: public Outputs::Speaker::SampleSource { uint8_t address; uint8_t control; uint8_t table_size; + + uint8_t sample(uint8_t *ram); + int16_t output(uint8_t *ram); } oscillators[32]; // TODO: do all of these need to be on the audio thread?