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

Attempts to complete Dave's audio duties.

This commit is contained in:
Thomas Harte 2021-06-27 14:06:49 -04:00
parent f8b7c59616
commit 903e343895
3 changed files with 132 additions and 54 deletions

View File

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

View File

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

View File

@ -493,8 +493,8 @@ template <bool has_disk_controller> class ConcreteMachine:
// Cf. timing guesses above.
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave dave_;
Outputs::Speaker::LowpassSpeaker<Dave> speaker_;
Dave::Audio dave_;
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
HalfCycles time_since_audio_update_;
// MARK: - EXDos card.