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

Merge pull request #59 from TomHarte/AYFixes

Reduces AY processor load
This commit is contained in:
Thomas Harte 2016-10-23 20:44:48 -04:00 committed by GitHub
commit 33b93b7098
5 changed files with 124 additions and 91 deletions

View File

@ -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,60 +82,73 @@ 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
// I avoid stalls.
//
// Repeating patterns are:
// (1) decrement, then shift a high-order bit right and mask to get 1 for did underflow, 0 otherwise;
// (2) did_underflow * a + (did_underflow ^ 1) * b to pick between reloading and not reloading
int did_underflow;
#define shift(x, r, steps) \
x -= steps; \
did_underflow = (x >> 16)&1; \
x = did_underflow * r + (did_underflow^1) * x;
while(c < number_of_samples)
{
#define step_channel(c) \ #define step_channel(c) \
shift(_channel_dividers[c], _tone_generator_controls[c], resulting_steps); \ if(_tone_counters[c]) _tone_counters[c]--;\
_channel_output[c] ^= did_underflow; else\
{\
_tone_outputs[c] ^= 1;\
_tone_counters[c] = _tone_periods[c];\
}
// 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_counter = _noise_period;
_noise_output ^= _noise_shift_register&1;
_noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17; _noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17;
_noise_shift_register >>= did_underflow; _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
for(int ic = 0; ic < 16 && c < number_of_samples; ic++)
{
target[c] = _output_volume;
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: // The output level for a channel is:
// 1 if neither tone nor noise is enabled; // 1 if neither tone nor noise is enabled;
// 0 if either tone or noise is enabled and its value is low. // 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) // (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),
@ -155,19 +168,12 @@ void AY38910::get_samples(unsigned int number_of_samples, int16_t *target)
}; };
#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();
}); });
} }
} }

View File

@ -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();
}; };
}; };

View File

@ -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
} }

View File

@ -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
}; };
} }

View File

@ -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));
if(_queuedSamples >= 128 && _queuedSamples - samplesInBuffer < 128 && _hasHad256)
{
_hasHad256 = NO;
[self.delegate audioQueueIsRunningDry:self]; [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);