mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 03:32:32 +00:00
commit
33b93b7098
@ -12,8 +12,8 @@ using namespace GI;
|
|||||||
|
|
||||||
AY38910::AY38910() :
|
AY38910::AY38910() :
|
||||||
_selected_register(0),
|
_selected_register(0),
|
||||||
_channel_output{0, 0, 0}, _channel_dividers{0, 0, 0}, _tone_generator_controls{0, 0, 0},
|
_tone_counters{0, 0, 0}, _tone_periods{0, 0, 0}, _tone_outputs{0, 0, 0},
|
||||||
_noise_shift_register(0xffff), _noise_divider(0), _noise_output(0),
|
_noise_shift_register(0xffff), _noise_period(0), _noise_counter(0), _noise_output(0),
|
||||||
_envelope_divider(0), _envelope_period(0), _envelope_position(0),
|
_envelope_divider(0), _envelope_period(0), _envelope_position(0),
|
||||||
_output_registers{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
_output_registers{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
{
|
{
|
||||||
@ -82,92 +82,98 @@ void AY38910::set_clock_rate(double clock_rate)
|
|||||||
|
|
||||||
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
|
||||||
{
|
{
|
||||||
for(int c = 0; c < number_of_samples; c++)
|
int c = 0;
|
||||||
|
while((_master_divider&15) && c < number_of_samples)
|
||||||
{
|
{
|
||||||
// a master divider divides the clock by 16;
|
target[c] = _output_volume;
|
||||||
// resulting_steps will be 1 if a tick occurred, 0 otherwise
|
|
||||||
int former_master_divider = _master_divider;
|
|
||||||
_master_divider++;
|
_master_divider++;
|
||||||
int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1;
|
c++;
|
||||||
|
}
|
||||||
|
|
||||||
// Bluffer's guide to the stuff below: I wanted to avoid branches. If I avoid branches then
|
while(c < number_of_samples)
|
||||||
// I avoid stalls.
|
{
|
||||||
//
|
#define step_channel(c) \
|
||||||
// Repeating patterns are:
|
if(_tone_counters[c]) _tone_counters[c]--;\
|
||||||
// (1) decrement, then shift a high-order bit right and mask to get 1 for did underflow, 0 otherwise;
|
else\
|
||||||
// (2) did_underflow * a + (did_underflow ^ 1) * b to pick between reloading and not reloading
|
{\
|
||||||
int did_underflow;
|
_tone_outputs[c] ^= 1;\
|
||||||
#define shift(x, r, steps) \
|
_tone_counters[c] = _tone_periods[c];\
|
||||||
x -= steps; \
|
}
|
||||||
did_underflow = (x >> 16)&1; \
|
|
||||||
x = did_underflow * r + (did_underflow^1) * x;
|
|
||||||
|
|
||||||
#define step_channel(c) \
|
|
||||||
shift(_channel_dividers[c], _tone_generator_controls[c], resulting_steps); \
|
|
||||||
_channel_output[c] ^= did_underflow;
|
|
||||||
|
|
||||||
// update the tone channels
|
// update the tone channels
|
||||||
step_channel(0);
|
step_channel(0);
|
||||||
step_channel(1);
|
step_channel(1);
|
||||||
step_channel(2);
|
step_channel(2);
|
||||||
|
|
||||||
|
#undef step_channel
|
||||||
|
|
||||||
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
// ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting
|
||||||
// it into the official 17 upon divider underflow.
|
// it into the official 17 upon divider underflow.
|
||||||
shift(_noise_divider, _output_registers[6]&0x1f, resulting_steps);
|
if(_noise_counter) _noise_counter--;
|
||||||
_noise_output ^= did_underflow&_noise_shift_register&1;
|
else
|
||||||
_noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17;
|
{
|
||||||
_noise_shift_register >>= did_underflow;
|
_noise_counter = _noise_period;
|
||||||
|
_noise_output ^= _noise_shift_register&1;
|
||||||
|
_noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17;
|
||||||
|
_noise_shift_register >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
|
// ... and the envelope generator. Table based for pattern lookup, with a 'refill' step — a way of
|
||||||
// implementing non-repeating patterns by locking them to table position 0x1f.
|
// implementing non-repeating patterns by locking them to table position 0x1f.
|
||||||
// int envelope_divider = ((_master_divider ^ former_master_divider) >> 8) & 1;
|
if(_envelope_divider) _envelope_divider--;
|
||||||
shift(_envelope_divider, _envelope_period, resulting_steps);
|
else
|
||||||
_envelope_position += did_underflow;
|
{
|
||||||
int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5);
|
_envelope_divider = _envelope_period;
|
||||||
_envelope_position = (_envelope_position & 0x1f) | refill;
|
_envelope_position ++;
|
||||||
int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position];
|
if(_envelope_position == 32) _envelope_position = _envelope_overflow_masks[_output_registers[13]];
|
||||||
|
}
|
||||||
|
|
||||||
#undef step_channel
|
evaluate_output_volume();
|
||||||
#undef shift
|
|
||||||
|
|
||||||
// The output level for a channel is:
|
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
|
||||||
// 1 if neither tone nor noise is enabled;
|
{
|
||||||
// 0 if either tone or noise is enabled and its value is low.
|
target[c] = _output_volume;
|
||||||
// (which is implemented here with reverse logic, assuming _channel_output and _noise_output are already inverted)
|
c++;
|
||||||
|
_master_divider++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AY38910::evaluate_output_volume()
|
||||||
|
{
|
||||||
|
int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position];
|
||||||
|
|
||||||
|
// The output level for a channel is:
|
||||||
|
// 1 if neither tone nor noise is enabled;
|
||||||
|
// 0 if either tone or noise is enabled and its value is low.
|
||||||
|
// (which is implemented here with reverse logic, assuming _channel_output and _noise_output are already inverted)
|
||||||
#define level(c, tb, nb) \
|
#define level(c, tb, nb) \
|
||||||
(((((_output_registers[7] >> tb)&1)^1) & _channel_output[c]) | ((((_output_registers[7] >> nb)&1)^1) & _noise_output)) ^ 1
|
(((((_output_registers[7] >> tb)&1)^1) & _tone_outputs[c]) | ((((_output_registers[7] >> nb)&1)^1) & _noise_output)) ^ 1
|
||||||
|
|
||||||
int channel_levels[3] = {
|
int channel_levels[3] = {
|
||||||
level(0, 0, 3),
|
level(0, 0, 3),
|
||||||
level(1, 1, 4),
|
level(1, 1, 4),
|
||||||
level(2, 2, 5),
|
level(2, 2, 5),
|
||||||
};
|
};
|
||||||
#undef level
|
#undef level
|
||||||
|
|
||||||
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
|
// Channel volume is a simple selection: if the bit at 0x10 is set, use the envelope volume; otherwise use the lower four bits
|
||||||
#define channel_volume(c) \
|
#define channel_volume(c) \
|
||||||
((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0xf)
|
((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0xf)
|
||||||
|
|
||||||
int volumes[3] = {
|
int volumes[3] = {
|
||||||
channel_volume(8),
|
channel_volume(8),
|
||||||
channel_volume(9),
|
channel_volume(9),
|
||||||
channel_volume(10)
|
channel_volume(10)
|
||||||
};
|
};
|
||||||
#undef channel_volume
|
#undef channel_volume
|
||||||
|
|
||||||
// Mix additively. TODO: non-linear volume.
|
// Mix additively.
|
||||||
target[c] = (int16_t)(
|
_output_volume = (int16_t)(
|
||||||
_volumes[volumes[0]] * channel_levels[0] +
|
_volumes[volumes[0]] * channel_levels[0] +
|
||||||
_volumes[volumes[1]] * channel_levels[1] +
|
_volumes[volumes[1]] * channel_levels[1] +
|
||||||
_volumes[volumes[2]] * channel_levels[2]
|
_volumes[volumes[2]] * channel_levels[2]
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AY38910::skip_samples(unsigned int number_of_samples)
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
// printf("Skip %d\n", number_of_samples);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AY38910::select_register(uint8_t r)
|
void AY38910::select_register(uint8_t r)
|
||||||
@ -186,23 +192,31 @@ void AY38910::set_register_value(uint8_t value)
|
|||||||
switch(selected_register)
|
switch(selected_register)
|
||||||
{
|
{
|
||||||
case 0: case 2: case 4:
|
case 0: case 2: case 4:
|
||||||
_tone_generator_controls[selected_register >> 1] =
|
case 1: case 3: case 5:
|
||||||
(_tone_generator_controls[selected_register >> 1] & ~0xff) | value;
|
{
|
||||||
|
int channel = selected_register >> 1;
|
||||||
|
|
||||||
|
if(selected_register & 1)
|
||||||
|
_tone_periods[channel] = (_tone_periods[channel] & 0xff) | (uint16_t)((value&0xf) << 8);
|
||||||
|
else
|
||||||
|
_tone_periods[channel] = (_tone_periods[channel] & ~0xff) | value;
|
||||||
|
_tone_counters[channel] = _tone_periods[channel];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: case 3: case 5:
|
case 6:
|
||||||
_tone_generator_controls[selected_register >> 1] =
|
_noise_period = value & 0x1f;
|
||||||
(_tone_generator_controls[selected_register >> 1] & 0xff) | (uint16_t)((value&0xf) << 8);
|
_noise_counter = _noise_period;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 11:
|
case 11:
|
||||||
_envelope_period = (_envelope_period & ~0xff) | value;
|
_envelope_period = (_envelope_period & ~0xff) | value;
|
||||||
// printf("e: %d", _envelope_period);
|
_envelope_divider = _envelope_period;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 12:
|
case 12:
|
||||||
_envelope_period = (_envelope_period & 0xff) | (int)(value << 8);
|
_envelope_period = (_envelope_period & 0xff) | (int)(value << 8);
|
||||||
// printf("e: %d", _envelope_period);
|
_envelope_divider = _envelope_period;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 13:
|
case 13:
|
||||||
@ -211,6 +225,7 @@ void AY38910::set_register_value(uint8_t value)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_output_registers[selected_register] = masked_value;
|
_output_registers[selected_register] = masked_value;
|
||||||
|
evaluate_output_volume();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,31 +49,30 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
|||||||
|
|
||||||
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
// to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption
|
||||||
void get_samples(unsigned int number_of_samples, int16_t *target);
|
void get_samples(unsigned int number_of_samples, int16_t *target);
|
||||||
void skip_samples(unsigned int number_of_samples);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _selected_register;
|
int _selected_register;
|
||||||
uint8_t _registers[16], _output_registers[16];
|
uint8_t _registers[16], _output_registers[16];
|
||||||
|
|
||||||
int _tone_generator_controls[3];
|
|
||||||
int _channel_dividers[3];
|
|
||||||
int _channel_output[3];
|
|
||||||
|
|
||||||
int _volumes[16];
|
|
||||||
|
|
||||||
int _master_divider;
|
int _master_divider;
|
||||||
|
|
||||||
int _noise_divider;
|
int _tone_periods[3];
|
||||||
|
int _tone_counters[3];
|
||||||
|
int _tone_outputs[3];
|
||||||
|
|
||||||
|
int _noise_period;
|
||||||
|
int _noise_counter;
|
||||||
int _noise_shift_register;
|
int _noise_shift_register;
|
||||||
int _noise_output;
|
int _noise_output;
|
||||||
|
|
||||||
int _envelope_period;
|
int _envelope_period;
|
||||||
int _envelope_divider;
|
int _envelope_divider;
|
||||||
|
|
||||||
int _envelope_position;
|
int _envelope_position;
|
||||||
int _envelope_shapes[16][32];
|
int _envelope_shapes[16][32];
|
||||||
int _envelope_overflow_masks[16];
|
int _envelope_overflow_masks[16];
|
||||||
|
|
||||||
|
int _volumes[16];
|
||||||
|
|
||||||
enum ControlState {
|
enum ControlState {
|
||||||
Inactive,
|
Inactive,
|
||||||
LatchAddress,
|
LatchAddress,
|
||||||
@ -86,6 +85,9 @@ class AY38910: public ::Outputs::Filter<AY38910> {
|
|||||||
uint8_t get_register_value();
|
uint8_t get_register_value();
|
||||||
|
|
||||||
uint8_t _data_input, _data_output;
|
uint8_t _data_input, _data_output;
|
||||||
|
|
||||||
|
int16_t _output_volume;
|
||||||
|
inline void evaluate_output_volume();
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -10,8 +10,14 @@
|
|||||||
|
|
||||||
using namespace Concurrency;
|
using namespace Concurrency;
|
||||||
|
|
||||||
AsyncTaskQueue::AsyncTaskQueue() : should_destruct_(false)
|
AsyncTaskQueue::AsyncTaskQueue()
|
||||||
|
#ifndef __APPLE__
|
||||||
|
: should_destruct_(false)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||||
|
#else
|
||||||
thread_.reset(new std::thread([this]() {
|
thread_.reset(new std::thread([this]() {
|
||||||
while(!should_destruct_)
|
while(!should_destruct_)
|
||||||
{
|
{
|
||||||
@ -39,25 +45,37 @@ AsyncTaskQueue::AsyncTaskQueue() : should_destruct_(false)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncTaskQueue::~AsyncTaskQueue()
|
AsyncTaskQueue::~AsyncTaskQueue()
|
||||||
{
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
dispatch_release(serial_dispatch_queue_);
|
||||||
|
#else
|
||||||
should_destruct_ = true;
|
should_destruct_ = true;
|
||||||
enqueue([](){});
|
enqueue([](){});
|
||||||
thread_->join();
|
thread_->join();
|
||||||
thread_.reset();
|
thread_.reset();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
void AsyncTaskQueue::enqueue(std::function<void(void)> function)
|
||||||
{
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||||
|
#else
|
||||||
std::lock_guard<std::mutex> lock(queue_mutex_);
|
std::lock_guard<std::mutex> lock(queue_mutex_);
|
||||||
pending_tasks_.push_back(function);
|
pending_tasks_.push_back(function);
|
||||||
processing_condition_.notify_all();
|
processing_condition_.notify_all();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsyncTaskQueue::flush()
|
void AsyncTaskQueue::flush()
|
||||||
{
|
{
|
||||||
|
#ifdef __APPLE__
|
||||||
|
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||||
|
#else
|
||||||
std::shared_ptr<std::mutex> flush_mutex(new std::mutex);
|
std::shared_ptr<std::mutex> flush_mutex(new std::mutex);
|
||||||
std::shared_ptr<std::condition_variable> flush_condition(new std::condition_variable);
|
std::shared_ptr<std::condition_variable> flush_condition(new std::condition_variable);
|
||||||
std::unique_lock<std::mutex> lock(*flush_mutex);
|
std::unique_lock<std::mutex> lock(*flush_mutex);
|
||||||
@ -66,4 +84,5 @@ void AsyncTaskQueue::flush()
|
|||||||
flush_condition->notify_all();
|
flush_condition->notify_all();
|
||||||
});
|
});
|
||||||
flush_condition->wait(lock);
|
flush_condition->wait(lock);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
#include <list>
|
#include <list>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <dispatch/dispatch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Concurrency {
|
namespace Concurrency {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -42,12 +46,16 @@ class AsyncTaskQueue {
|
|||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifdef __APPLE__
|
||||||
|
dispatch_queue_t serial_dispatch_queue_;
|
||||||
|
#else
|
||||||
std::unique_ptr<std::thread> thread_;
|
std::unique_ptr<std::thread> thread_;
|
||||||
|
|
||||||
std::mutex queue_mutex_;
|
std::mutex queue_mutex_;
|
||||||
std::list<std::function<void(void)>> pending_tasks_;
|
std::list<std::function<void(void)>> pending_tasks_;
|
||||||
std::condition_variable processing_condition_;
|
std::condition_variable processing_condition_;
|
||||||
std::atomic_bool should_destruct_;
|
std::atomic_bool should_destruct_;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,22 +14,13 @@
|
|||||||
@implementation CSAudioQueue
|
@implementation CSAudioQueue
|
||||||
{
|
{
|
||||||
AudioQueueRef _audioQueue;
|
AudioQueueRef _audioQueue;
|
||||||
size_t _queuedSamples;
|
|
||||||
BOOL _hasHad256;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - AudioQueue callbacks
|
#pragma mark - AudioQueue callbacks
|
||||||
|
|
||||||
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||||
{
|
{
|
||||||
size_t samplesInBuffer = (size_t)(buffer->mAudioDataByteSize / sizeof(int16_t));
|
[self.delegate audioQueueIsRunningDry:self];
|
||||||
if(_queuedSamples >= 128 && _queuedSamples - samplesInBuffer < 128 && _hasHad256)
|
|
||||||
{
|
|
||||||
_hasHad256 = NO;
|
|
||||||
[self.delegate audioQueueIsRunningDry:self];
|
|
||||||
}
|
|
||||||
_queuedSamples -= samplesInBuffer;
|
|
||||||
|
|
||||||
AudioQueueFreeBuffer(_audioQueue, buffer);
|
AudioQueueFreeBuffer(_audioQueue, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,8 +97,6 @@ static void audioOutputCallback(
|
|||||||
{
|
{
|
||||||
AudioQueueBufferRef newBuffer;
|
AudioQueueBufferRef newBuffer;
|
||||||
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
||||||
_queuedSamples += lengthInSamples;
|
|
||||||
_hasHad256 |= (_queuedSamples >= 256);
|
|
||||||
|
|
||||||
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer);
|
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer);
|
||||||
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
||||||
|
Loading…
Reference in New Issue
Block a user