diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp index 8595db8d9..c0db5eebb 100644 --- a/Components/AY38910/AY38910.cpp +++ b/Components/AY38910/AY38910.cpp @@ -12,8 +12,8 @@ using namespace GI; AY38910::AY38910() : _selected_register(0), - _channel_output{0, 0, 0}, _channel_dividers{0, 0, 0}, _tone_generator_controls{0, 0, 0}, - _noise_shift_register(0xffff), _noise_divider(0), _noise_output(0), + _tone_counters{0, 0, 0}, _tone_periods{0, 0, 0}, _tone_outputs{0, 0, 0}, + _noise_shift_register(0xffff), _noise_period(0), _noise_counter(0), _noise_output(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} { @@ -82,92 +82,98 @@ void AY38910::set_clock_rate(double clock_rate) 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; - // resulting_steps will be 1 if a tick occurred, 0 otherwise - int former_master_divider = _master_divider; + target[c] = _output_volume; _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; - -#define step_channel(c) \ - shift(_channel_dividers[c], _tone_generator_controls[c], resulting_steps); \ - _channel_output[c] ^= did_underflow; + while(c < number_of_samples) + { +#define step_channel(c) \ + if(_tone_counters[c]) _tone_counters[c]--;\ + else\ + {\ + _tone_outputs[c] ^= 1;\ + _tone_counters[c] = _tone_periods[c];\ + } // update the tone channels step_channel(0); step_channel(1); step_channel(2); +#undef step_channel + // ... the noise generator. This recomputes the new bit repeatedly but harmlessly, only shifting // it into the official 17 upon divider underflow. - shift(_noise_divider, _output_registers[6]&0x1f, resulting_steps); - _noise_output ^= did_underflow&_noise_shift_register&1; - _noise_shift_register |= ((_noise_shift_register ^ (_noise_shift_register >> 3))&1) << 17; - _noise_shift_register >>= did_underflow; + if(_noise_counter) _noise_counter--; + 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 >>= 1; + } // ... 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. -// int envelope_divider = ((_master_divider ^ former_master_divider) >> 8) & 1; - shift(_envelope_divider, _envelope_period, resulting_steps); - _envelope_position += did_underflow; - int refill = _envelope_overflow_masks[_output_registers[13]] * (_envelope_position >> 5); - _envelope_position = (_envelope_position & 0x1f) | refill; - int envelope_volume = _envelope_shapes[_output_registers[13]][_envelope_position]; + if(_envelope_divider) _envelope_divider--; + else + { + _envelope_divider = _envelope_period; + _envelope_position ++; + if(_envelope_position == 32) _envelope_position = _envelope_overflow_masks[_output_registers[13]]; + } -#undef step_channel -#undef shift + evaluate_output_volume(); - // 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) + 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: + // 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) \ - (((((_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] = { - level(0, 0, 3), - level(1, 1, 4), - level(2, 2, 5), - }; + int channel_levels[3] = { + level(0, 0, 3), + level(1, 1, 4), + level(2, 2, 5), + }; #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 #define channel_volume(c) \ ((_output_registers[c] >> 4)&1) * envelope_volume + (((_output_registers[c] >> 4)&1)^1) * (_output_registers[c]&0xf) - int volumes[3] = { - channel_volume(8), - channel_volume(9), - channel_volume(10) - }; + int volumes[3] = { + channel_volume(8), + channel_volume(9), + channel_volume(10) + }; #undef channel_volume - // Mix additively. TODO: non-linear volume. - target[c] = (int16_t)( - _volumes[volumes[0]] * channel_levels[0] + - _volumes[volumes[1]] * channel_levels[1] + - _volumes[volumes[2]] * channel_levels[2] - ); - } -} - -void AY38910::skip_samples(unsigned int number_of_samples) -{ - // TODO -// printf("Skip %d\n", number_of_samples); + // Mix additively. + _output_volume = (int16_t)( + _volumes[volumes[0]] * channel_levels[0] + + _volumes[volumes[1]] * channel_levels[1] + + _volumes[volumes[2]] * channel_levels[2] + ); } void AY38910::select_register(uint8_t r) @@ -186,23 +192,31 @@ void AY38910::set_register_value(uint8_t value) switch(selected_register) { case 0: case 2: case 4: - _tone_generator_controls[selected_register >> 1] = - (_tone_generator_controls[selected_register >> 1] & ~0xff) | value; + case 1: case 3: case 5: + { + 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; - case 1: case 3: case 5: - _tone_generator_controls[selected_register >> 1] = - (_tone_generator_controls[selected_register >> 1] & 0xff) | (uint16_t)((value&0xf) << 8); + case 6: + _noise_period = value & 0x1f; + _noise_counter = _noise_period; break; case 11: _envelope_period = (_envelope_period & ~0xff) | value; -// printf("e: %d", _envelope_period); + _envelope_divider = _envelope_period; break; case 12: _envelope_period = (_envelope_period & 0xff) | (int)(value << 8); -// printf("e: %d", _envelope_period); + _envelope_divider = _envelope_period; break; case 13: @@ -211,6 +225,7 @@ void AY38910::set_register_value(uint8_t value) break; } _output_registers[selected_register] = masked_value; + evaluate_output_volume(); }); } } diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp index c6b5f7f78..11f0045c6 100644 --- a/Components/AY38910/AY38910.hpp +++ b/Components/AY38910/AY38910.hpp @@ -49,31 +49,30 @@ class AY38910: public ::Outputs::Filter { // to satisfy ::Outputs::Speaker (included via ::Outputs::Filter; not for public consumption void get_samples(unsigned int number_of_samples, int16_t *target); - void skip_samples(unsigned int number_of_samples); private: int _selected_register; 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 _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_output; int _envelope_period; int _envelope_divider; - int _envelope_position; int _envelope_shapes[16][32]; int _envelope_overflow_masks[16]; + int _volumes[16]; + enum ControlState { Inactive, LatchAddress, @@ -86,6 +85,9 @@ class AY38910: public ::Outputs::Filter { uint8_t get_register_value(); uint8_t _data_input, _data_output; + + int16_t _output_volume; + inline void evaluate_output_volume(); }; }; diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 1aa544c5e..939cd016a 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -10,8 +10,14 @@ 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]() { while(!should_destruct_) { @@ -39,25 +45,37 @@ AsyncTaskQueue::AsyncTaskQueue() : should_destruct_(false) } } })); +#endif } AsyncTaskQueue::~AsyncTaskQueue() { +#ifdef __APPLE__ + dispatch_release(serial_dispatch_queue_); +#else should_destruct_ = true; enqueue([](){}); thread_->join(); thread_.reset(); +#endif } void AsyncTaskQueue::enqueue(std::function function) { +#ifdef __APPLE__ + dispatch_async(serial_dispatch_queue_, ^{function();}); +#else std::lock_guard lock(queue_mutex_); pending_tasks_.push_back(function); processing_condition_.notify_all(); +#endif } void AsyncTaskQueue::flush() { +#ifdef __APPLE__ + dispatch_sync(serial_dispatch_queue_, ^{}); +#else std::shared_ptr flush_mutex(new std::mutex); std::shared_ptr flush_condition(new std::condition_variable); std::unique_lock lock(*flush_mutex); @@ -66,4 +84,5 @@ void AsyncTaskQueue::flush() flush_condition->notify_all(); }); flush_condition->wait(lock); +#endif } diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index 77485265a..aa53dd954 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -14,6 +14,10 @@ #include #include +#ifdef __APPLE__ +#include +#endif + namespace Concurrency { /*! @@ -42,12 +46,16 @@ class AsyncTaskQueue { void flush(); private: +#ifdef __APPLE__ + dispatch_queue_t serial_dispatch_queue_; +#else std::unique_ptr thread_; std::mutex queue_mutex_; std::list> pending_tasks_; std::condition_variable processing_condition_; std::atomic_bool should_destruct_; +#endif }; } diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index a99fdf3b5..1153e0686 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -14,22 +14,13 @@ @implementation CSAudioQueue { AudioQueueRef _audioQueue; - size_t _queuedSamples; - BOOL _hasHad256; } #pragma mark - AudioQueue callbacks - (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]; - } - _queuedSamples -= samplesInBuffer; - + [self.delegate audioQueueIsRunningDry:self]; AudioQueueFreeBuffer(_audioQueue, buffer); } @@ -106,8 +97,6 @@ static void audioOutputCallback( { AudioQueueBufferRef newBuffer; size_t bufferBytes = lengthInSamples * sizeof(int16_t); - _queuedSamples += lengthInSamples; - _hasHad256 |= (_queuedSamples >= 256); AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer); memcpy(newBuffer->mAudioData, buffer, bufferBytes);