diff --git a/Machines/Enterprise/Dave.cpp b/Machines/Enterprise/Dave.cpp index ddb4b07c6..87a83afed 100644 --- a/Machines/Enterprise/Dave.cpp +++ b/Machines/Enterprise/Dave.cpp @@ -8,12 +8,12 @@ #include "Dave.hpp" -using namespace Enterprise; +using namespace Enterprise::Dave; -Dave::Dave(Concurrency::DeferringAsyncTaskQueue &audio_queue) : +Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) : audio_queue_(audio_queue) {} -void Dave::write(uint16_t address, uint8_t value) { +void Audio::write(uint16_t address, uint8_t value) { address &= 0xf; audio_queue_.defer([address, value, this] { switch(address) { @@ -35,11 +35,14 @@ void Dave::write(uint16_t address, uint8_t value) { noise_.ring_modulate = value & 0x80; break; - // TODO: - // - // 7: sync bits, optional D/As. - // - // (will handle interrupt bits elsewhere) + case 7: + channels_[0].sync = value & 0x01; + channels_[1].sync = value & 0x02; + channels_[2].sync = value & 0x04; + use_direct_output_[0] = value & 0x08; + use_direct_output_[1] = value & 0x10; + // Interrupt bits are handled separately. + break; case 8: case 9: case 10: channels_[address - 8].amplitude[0] = value & 0x3f; @@ -53,13 +56,43 @@ void Dave::write(uint16_t address, uint8_t value) { }); } -void Dave::set_sample_volume_range(int16_t range) { +void Audio::set_sample_volume_range(int16_t range) { audio_queue_.defer([range, this] { volume_ = range / (63*4); }); } -void Dave::get_samples(std::size_t number_of_samples, int16_t *target) { +void Audio::update_channel(int c) { + if(channels_[c].sync) { + channels_[c].count = channels_[c].reload; + channels_[c].output <<= 1; + return; + } + + auto output = channels_[c].output & 1; + channels_[c].output <<= 1; + if(!channels_[c].count) { + channels_[c].count = channels_[c].reload; + + if(channels_[c].distortion == Channel::Distortion::None) + output ^= 1; + else + output = poly_state_[int(channels_[c].distortion)]; + + if(channels_[c].high_pass && (channels_[(c+1)%3].output&3) == 2) { + output = 0; + } + if(channels_[c].ring_modulate) { + output = ~(output ^ channels_[(c+2)%3].output) & 1; + } + } else { + --channels_[c].count; + } + + channels_[c].output |= output; +} + +void Audio::get_samples(std::size_t number_of_samples, int16_t *target) { for(size_t c = 0; c < number_of_samples; c++) { poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next(); poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next(); @@ -69,52 +102,89 @@ void Dave::get_samples(std::size_t number_of_samples, int16_t *target) { } // Update tone channels. -#define update_channel(x) { \ - auto output = channels_[x].output & 1; \ - channels_[x].output <<= 1; \ - if(!channels_[x].count) { \ - channels_[x].count = channels_[x].reload; \ - \ - if(channels_[x].distortion == Channel::Distortion::None) \ - output ^= 1; \ - else \ - output = poly_state_[int(channels_[x].distortion)]; \ - \ - if(channels_[x].high_pass && (channels_[(x+1)%3].output&3) == 2) { \ - output = 0; \ - } \ - if(channels_[x].ring_modulate) { \ - output = ~(output ^ channels_[(x+2)%3].output) & 1; \ - } \ - } else { \ - --channels_[x].count; \ - } \ - channels_[x].output |= output; \ - } - update_channel(0); update_channel(1); update_channel(2); -#undef update_channel + // Update noise channel. - // TODO: update noise channel. + // Step 1: decide whether there is a tick to apply. + bool noise_tick = false; + if(noise_.frequency == Noise::Frequency::DivideByFour) { + if(!noise_.count) { + noise_tick = true; + noise_.count = 3; + } else { + --noise_.count; + } + } else { + noise_tick = (channels_[int(noise_.frequency) - 1].output&3) == 2; + } - // Dumbest ever first attempt: sum channels. + // Step 2: tick if necessary. + if(noise_tick) { + switch(noise_.polynomial) { + case Noise::Polynomial::SeventeenBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly17_.next()); + break; + case Noise::Polynomial::FifteenBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly15_.next()); + break; + case Noise::Polynomial::ElevenBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly11_.next()); + break; + case Noise::Polynomial::NineBit: + poly_state_[int(Channel::Distortion::None)] = uint8_t(poly9_.next()); + break; + } + + noise_.output <<= 1; + noise_.output |= poly_state_[int(Channel::Distortion::None)]; + + // Low pass: sample channel 2 on downward transitions of the prima facie output. + if(noise_.low_pass) { + if((noise_.output & 3) == 2) { + noise_.output = (noise_.output & ~1) | (channels_[2].output & 1); + } else { + noise_.output = (noise_.output & ~1) | (noise_.output & 1); + } + } + } + + // Apply noise high-pass at the rate of the tone channels. + if(noise_.high_pass && (channels_[0].output & 3) == 2) { + noise_.output &= ~1; + } + + // Update noise ring modulation, if any. + if(noise_.ring_modulate) { + noise_.final_output = !((noise_.output ^ channels_[1].output) & 1); + } else { + noise_.final_output = noise_.output & 1; + } + + // I'm unclear on the details of the time division multiplexing so, + // for now, just sum the outputs. target[(c << 1) + 0] = - volume_ * ( - channels_[0].amplitude[0] * (channels_[0].output & 1) + - channels_[1].amplitude[0] * (channels_[1].output & 1) + - channels_[2].amplitude[0] * (channels_[2].output & 1) + - noise_.amplitude[0] * noise_.output - ); + volume_ * + (use_direct_output_[0] ? + channels_[0].amplitude[0] + : ( + channels_[0].amplitude[0] * (channels_[0].output & 1) + + channels_[1].amplitude[0] * (channels_[1].output & 1) + + channels_[2].amplitude[0] * (channels_[2].output & 1) + + noise_.amplitude[0] * noise_.final_output + )); target[(c << 1) + 1] = - volume_ * ( - channels_[0].amplitude[1] * (channels_[0].output & 1) + - channels_[1].amplitude[1] * (channels_[1].output & 1) + - channels_[2].amplitude[1] * (channels_[2].output & 1) + - noise_.amplitude[1] * noise_.output - ); + volume_ * + (use_direct_output_[1] ? + channels_[0].amplitude[1] + : ( + channels_[0].amplitude[1] * (channels_[0].output & 1) + + channels_[1].amplitude[1] * (channels_[1].output & 1) + + channels_[2].amplitude[1] * (channels_[2].output & 1) + + noise_.amplitude[1] * noise_.final_output + )); } } diff --git a/Machines/Enterprise/Dave.hpp b/Machines/Enterprise/Dave.hpp index 1f52bb2f6..6f9b5fdfa 100644 --- a/Machines/Enterprise/Dave.hpp +++ b/Machines/Enterprise/Dave.hpp @@ -16,14 +16,15 @@ #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" namespace Enterprise { +namespace Dave { /*! Models a subset of Dave's behaviour; memory mapping and interrupt status is integrated into the main Enterprise machine. */ -class Dave: public Outputs::Speaker::SampleSource { +class Audio: public Outputs::Speaker::SampleSource { public: - Dave(Concurrency::DeferringAsyncTaskQueue &audio_queue); + Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue); void write(uint16_t address, uint8_t value); @@ -48,11 +49,13 @@ class Dave: public Outputs::Speaker::SampleSource { SevenBit = 3, } distortion = Distortion::None; uint8_t amplitude[2]{}; + bool sync = false; // Current state. uint16_t count = 0; int output = 0; } channels_[3]; + void update_channel(int); // Noise channel. struct Noise { @@ -76,9 +79,13 @@ class Dave: public Outputs::Speaker::SampleSource { bool ring_modulate = false; // Current state. - bool output = false; - uint16_t count = 0; + int count = 0; + int output = false; + bool final_output = false; } noise_; + void update_noise(); + + bool use_direct_output_[2]{}; // Global volume, per SampleSource obligations. int16_t volume_ = 0; @@ -98,6 +105,7 @@ class Dave: public Outputs::Speaker::SampleSource { uint8_t poly_state_[4]; }; +} } #endif /* Dave_hpp */ diff --git a/Machines/Enterprise/Enterprise.cpp b/Machines/Enterprise/Enterprise.cpp index 34ff7fcb5..4673387d9 100644 --- a/Machines/Enterprise/Enterprise.cpp +++ b/Machines/Enterprise/Enterprise.cpp @@ -493,8 +493,8 @@ template class ConcreteMachine: // Cf. timing guesses above. Concurrency::DeferringAsyncTaskQueue audio_queue_; - Dave dave_; - Outputs::Speaker::LowpassSpeaker speaker_; + Dave::Audio dave_; + Outputs::Speaker::LowpassSpeaker speaker_; HalfCycles time_since_audio_update_; // MARK: - EXDos card.