mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 23:52:26 +00:00
107 lines
3.2 KiB
C++
107 lines
3.2 KiB
C++
//
|
|
// Sound.hpp
|
|
// Clock Signal
|
|
//
|
|
// Created by Thomas Harte on 04/11/2020.
|
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
|
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
|
#include "../../../Concurrency/AsyncTaskQueue.hpp"
|
|
#include "../../../Outputs/Speaker/Implementation/BufferSource.hpp"
|
|
|
|
namespace Apple::IIgs::Sound {
|
|
|
|
class GLU: public Outputs::Speaker::BufferSource<GLU, false> { // TODO: isn't this stereo?
|
|
public:
|
|
GLU(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
|
|
|
void set_control(uint8_t);
|
|
uint8_t get_control();
|
|
void set_data(uint8_t);
|
|
uint8_t get_data();
|
|
void set_address_low(uint8_t);
|
|
uint8_t get_address_low();
|
|
void set_address_high(uint8_t);
|
|
uint8_t get_address_high();
|
|
|
|
void run_for(Cycles);
|
|
Cycles next_sequence_point() const;
|
|
bool get_interrupt_line();
|
|
|
|
// SampleSource.
|
|
template <Outputs::Speaker::Action action>
|
|
void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
|
void set_sample_volume_range(std::int16_t range);
|
|
bool is_zero_level() const { return false; } // TODO.
|
|
|
|
private:
|
|
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
|
|
|
uint16_t address_ = 0;
|
|
|
|
// Use a circular buffer for piping memory alterations onto the audio
|
|
// thread; it would be prohibitive to defer every write individually.
|
|
//
|
|
// Assumed: on most modern architectures, an atomic 64-bit read or
|
|
// write can be achieved locklessly.
|
|
struct MemoryWrite {
|
|
uint32_t time;
|
|
uint16_t address;
|
|
uint8_t value;
|
|
bool enabled;
|
|
};
|
|
static_assert(sizeof(MemoryWrite) == 8);
|
|
constexpr static int StoreBufferSize = 16384;
|
|
|
|
std::atomic<MemoryWrite> pending_stores_[StoreBufferSize];
|
|
uint32_t pending_store_read_ = 0, pending_store_read_time_ = 0;
|
|
uint32_t pending_store_write_ = 0, pending_store_write_time_ = 0;
|
|
|
|
// Maintain state both 'locally' (i.e. on the emulation thread) and
|
|
// 'remotely' (i.e. on the audio thread).
|
|
struct EnsoniqState {
|
|
uint8_t ram_[65536];
|
|
struct Oscillator {
|
|
uint32_t position;
|
|
|
|
// Programmer-set values.
|
|
uint16_t velocity;
|
|
uint8_t volume;
|
|
uint8_t address;
|
|
uint8_t control;
|
|
uint8_t table_size;
|
|
|
|
// Derived state.
|
|
uint32_t overflow_mask; // If a non-zero bit gets anywhere into the overflow mask, this channel
|
|
// has wrapped around. It's a function of table_size.
|
|
bool interrupt_request = false; // Will be non-zero if this channel would request an interrupt, were
|
|
// it currently enabled to do so.
|
|
|
|
uint8_t sample(uint8_t *ram);
|
|
int16_t output(uint8_t *ram);
|
|
} oscillators[32];
|
|
|
|
// Some of these aren't actually needed on both threads.
|
|
uint8_t control = 0;
|
|
int oscillator_count = 1;
|
|
|
|
void set_register(uint16_t address, uint8_t value);
|
|
} local_, remote_;
|
|
|
|
// Functions to update an EnsoniqState; these don't belong to the state itself
|
|
// because they also access the pending stores (inter alia).
|
|
template <Outputs::Speaker::Action action>
|
|
void generate_audio(size_t number_of_samples, Outputs::Speaker::MonoSample *target);
|
|
void skip_audio(EnsoniqState &state, size_t number_of_samples);
|
|
|
|
// Audio-thread state.
|
|
int16_t output_range_ = 0;
|
|
};
|
|
|
|
}
|