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:
parent
c34a548fa0
commit
1a97cc8a91
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
Loading…
x
Reference in New Issue
Block a user