1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-02-16 18:30:32 +00:00

Start making some effort towards audio generation.

This commit is contained in:
Thomas Harte 2021-06-24 22:21:01 -04:00
parent c34a548fa0
commit 1a97cc8a91
3 changed files with 123 additions and 9 deletions

View File

@ -10,7 +10,64 @@
using namespace Enterprise;
Dave::Dave(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
audio_queue_(audio_queue) {}
void Dave::write(uint16_t address, uint8_t value) {
(void)address;
(void)value;
address &= 0xf;
audio_queue_.defer([address, value, this] {
switch(address) {
case 0: case 2: case 4:
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
break;
case 1: case 3: case 5:
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 4));
channels_[address >> 1].distortion = Channel::Distortion((value >> 4)&3);
channels_[address >> 1].high_pass = value & 0x40;
channels_[address >> 1].ring_modulate = value & 0x80;
break;
// TODO:
//
// 6: noise selection.
// 7: sync bits, optional D/As, interrupt rate (albeit some of that shouldn't be on this thread, or possibly even _here_).
// 11, 14: noise amplitude.
case 8: case 9: case 10:
channels_[address - 8].amplitude[0] = value & 0x3f;
break;
case 11: case 12: case 13:
channels_[address - 11].amplitude[1] = value & 0x3f;
break;
}
});
}
void Dave::get_samples(std::size_t number_of_samples, int16_t *target) {
// Step 1: divide input clock to 125,000 Hz (?)
for(size_t c = 0; c < number_of_samples; c++) {
#define update_channel(x) \
--channels_[x].count; \
if(!channels_[x].count) { \
channels_[x].output ^= true; \
channels_[x].count = channels_[x].reload; \
}
update_channel(0);
update_channel(1);
update_channel(2);
#undef update_channel
// Dumbest ever first attempt: sum channels.
target[(c << 1) + 0] =
channels_[0].amplitude[0] * channels_[0].output +
channels_[1].amplitude[0] * channels_[1].output +
channels_[2].amplitude[0] * channels_[2].output;
target[(c << 1) + 1] =
channels_[0].amplitude[1] * channels_[0].output +
channels_[1].amplitude[1] * channels_[1].output +
channels_[2].amplitude[1] * channels_[2].output;
}
}

View File

@ -11,7 +11,9 @@
#include <cstdint>
#include "../../Concurrency/AsyncTaskQueue.hpp"
#include "../../Numeric/LFSR.hpp"
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
namespace Enterprise {
@ -19,11 +21,37 @@ namespace Enterprise {
Models a subset of Dave's behaviour; memory mapping and interrupt status
is integrated into the main Enterprise machine.
*/
class Dave {
class Dave: public Outputs::Speaker::SampleSource {
public:
Dave(Concurrency::DeferringAsyncTaskQueue &audio_queue);
void write(uint16_t address, uint8_t value);
// MARK: - SampleSource.
void set_sample_volume_range([[maybe_unused]] int16_t range) {}
static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound.
void get_samples(std::size_t number_of_samples, int16_t *target);
private:
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
struct Channel {
// User-set values.
uint16_t reload = 0;
bool high_pass = false;
bool ring_modulate = false;
enum class Distortion {
None,
FourBit,
FiveBit,
SevenBit,
} distortion = Distortion::None;
uint8_t amplitude[2]{};
// Current state.
uint16_t count = 0;
bool output = true;
} channels_[3];
// Various polynomials that contribute to audio generation.
Numeric::LFSRv<0xc> poly4_;

View File

@ -15,11 +15,10 @@
#include "../MachineTypes.hpp"
#include "../../Processors/Z80/Z80.hpp"
#include "../../Analyser/Static/Enterprise/Target.hpp"
#include "../../ClockReceiver/JustInTime.hpp"
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
#include "../../Processors/Z80/Z80.hpp"
namespace Enterprise {
@ -65,6 +64,7 @@ namespace Enterprise {
template <bool has_disk_controller> class ConcreteMachine:
public CPU::Z80::BusHandler,
public Machine,
public MachineTypes::AudioProducer,
public MachineTypes::MappedKeyboardMachine,
public MachineTypes::MediaTarget,
public MachineTypes::ScanProducer,
@ -85,7 +85,9 @@ template <bool has_disk_controller> class ConcreteMachine:
ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
min_ram_slot_(min_ram_slot(target)),
z80_(*this),
nick_(ram_.end() - 65536) {
nick_(ram_.end() - 65536),
dave_(audio_queue_),
speaker_(dave_) {
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
set_clock_rate(4'000'000);
@ -207,10 +209,17 @@ template <bool has_disk_controller> class ConcreteMachine:
page<2>(0x00);
page<3>(0x00);
// Set up audio.
speaker_.set_input_rate(125000.0f); // TODO: a bigger number, and respect the programmable divider.
// Pass on any media.
insert_media(target.media);
}
~ConcreteMachine() {
audio_queue_.flush();
}
// MARK: - Z80::BusHandler.
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
@ -218,7 +227,7 @@ template <bool has_disk_controller> class ConcreteMachine:
// TODO: possibly apply an access penalty.
time_since_audio_update_ += cycle.length;
if(nick_ += cycle.length) {
const auto nick = nick_.last_valid();
const bool nick_interrupt_line = nick->get_interrupt_line();
@ -307,6 +316,7 @@ template <bool has_disk_controller> class ConcreteMachine:
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
case 0xa8: case 0xa9: case 0xaa: case 0xab:
case 0xac: case 0xad: case 0xae: case 0xaf:
update_audio();
dave_.write(address, *cycle.value);
break;
@ -314,6 +324,10 @@ template <bool has_disk_controller> class ConcreteMachine:
interrupt_mask_ = *cycle.value & 0x55;
interrupt_state_ &= ~*cycle.value;
update_interrupts();
if(interrupt_mask_ & 0x45) {
printf("Unimplemented interrupts requested: %02x\n", interrupt_mask_ & 0x45);
}
break;
case 0xb5:
active_key_line_ = *cycle.value & 0xf;
@ -349,11 +363,17 @@ template <bool has_disk_controller> class ConcreteMachine:
void flush() {
nick_.flush();
update_audio();
audio_queue_.perform();
}
inline void update_audio() {
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(32)));
}
private:
// MARK: - Memory layout
std::array<uint8_t, 256 * 1024> ram_;
std::array<uint8_t, 256 * 1024> ram_{};
std::array<uint8_t, 64 * 1024> exos_;
std::array<uint8_t, 16 * 1024> basic_;
std::array<uint8_t, 16 * 1024> exdos_rom_;
@ -408,6 +428,12 @@ template <bool has_disk_controller> class ConcreteMachine:
return nick_.last_valid()->get_scaled_scan_status();
}
// MARK: - AudioProducer
Outputs::Speaker::Speaker *get_speaker() final {
return &speaker_;
}
// MARK: - TimedMachine
void run_for(const Cycles cycles) override {
z80_.run_for(cycles);
@ -464,7 +490,10 @@ template <bool has_disk_controller> class ConcreteMachine:
bool previous_nick_interrupt_line_ = false;
// Cf. timing guesses above.
Concurrency::DeferringAsyncTaskQueue audio_queue_;
Dave dave_;
Outputs::Speaker::LowpassSpeaker<Dave> speaker_;
HalfCycles time_since_audio_update_;
// MARK: - EXDos card.
EXDos exdos_;