mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-16 18:30:32 +00:00
Attempts to complete Dave's audio duties.
This commit is contained in:
parent
f8b7c59616
commit
903e343895
@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user