diff --git a/Components/AY38910/AY38910.cpp b/Components/AY38910/AY38910.cpp new file mode 100644 index 000000000..8595db8d9 --- /dev/null +++ b/Components/AY38910/AY38910.cpp @@ -0,0 +1,264 @@ +// +// AY-3-8910.cpp +// Clock Signal +// +// Created by Thomas Harte on 14/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "AY38910.hpp" + +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), + _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[8] = _output_registers[9] = _output_registers[10] = 0; + + // set up envelope lookup tables + for(int c = 0; c < 16; c++) + { + for(int p = 0; p < 32; p++) + { + switch(c) + { + case 0: case 1: case 2: case 3: case 9: + _envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0; + _envelope_overflow_masks[c] = 0x1f; + break; + case 4: case 5: case 6: case 7: case 15: + _envelope_shapes[c][p] = (p < 16) ? p : 0; + _envelope_overflow_masks[c] = 0x1f; + break; + + case 8: + _envelope_shapes[c][p] = (p & 0xf) ^ 0xf; + _envelope_overflow_masks[c] = 0x00; + break; + case 12: + _envelope_shapes[c][p] = (p & 0xf); + _envelope_overflow_masks[c] = 0x00; + break; + + case 10: + _envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0xf : 0x0); + _envelope_overflow_masks[c] = 0x00; + break; + case 14: + _envelope_shapes[c][p] = (p & 0xf) ^ ((p < 16) ? 0x0 : 0xf); + _envelope_overflow_masks[c] = 0x00; + break; + + case 11: + _envelope_shapes[c][p] = (p < 16) ? (p^0xf) : 0xf; + _envelope_overflow_masks[c] = 0x1f; + break; + case 13: + _envelope_shapes[c][p] = (p < 16) ? p : 0xf; + _envelope_overflow_masks[c] = 0x1f; + break; + } + } + } + + // set up volume lookup table + float max_volume = 8192; + float root_two = sqrtf(2.0f); + for(int v = 0; v < 16; v++) + { + _volumes[v] = (int)(max_volume / powf(root_two, (float)(v ^ 0xf))); + } + _volumes[0] = 0; +} + +void AY38910::set_clock_rate(double clock_rate) +{ + set_input_rate((float)clock_rate); +} + +void AY38910::get_samples(unsigned int number_of_samples, int16_t *target) +{ + for(int c = 0; c < number_of_samples; c++) + { + // 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; + _master_divider++; + int resulting_steps = ((_master_divider ^ former_master_divider) >> 4) & 1; + + // 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; + + // update the tone channels + step_channel(0); + step_channel(1); + step_channel(2); + + // ... 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; + + // ... 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]; + +#undef step_channel +#undef shift + + // 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 + + 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) + }; +#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); +} + +void AY38910::select_register(uint8_t r) +{ + _selected_register = r & 0xf; +} + +void AY38910::set_register_value(uint8_t value) +{ + _registers[_selected_register] = value; + if(_selected_register < 14) + { + int selected_register = _selected_register; + enqueue([=] () { + uint8_t masked_value = value; + switch(selected_register) + { + case 0: case 2: case 4: + _tone_generator_controls[selected_register >> 1] = + (_tone_generator_controls[selected_register >> 1] & ~0xff) | value; + 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); + break; + + case 11: + _envelope_period = (_envelope_period & ~0xff) | value; +// printf("e: %d", _envelope_period); + break; + + case 12: + _envelope_period = (_envelope_period & 0xff) | (int)(value << 8); +// printf("e: %d", _envelope_period); + break; + + case 13: + masked_value &= 0xf; + _envelope_position = 0; + break; + } + _output_registers[selected_register] = masked_value; + }); + } +} + +uint8_t AY38910::get_register_value() +{ + return _registers[_selected_register]; +} + +uint8_t AY38910::get_port_output(bool port_b) +{ + return _registers[port_b ? 15 : 14]; +} + +void AY38910::set_data_input(uint8_t r) +{ + _data_input = r; +} + +uint8_t AY38910::get_data_output() +{ + return _data_output; +} + +void AY38910::set_control_lines(ControlLines control_lines) +{ + ControlState new_state; + switch((int)control_lines) + { + default: new_state = Inactive; break; + + case (int)(BCDIR | BC2 | BC1): + case BCDIR: + case BC1: new_state = LatchAddress; break; + + case (int)(BC2 | BC1): new_state = Read; break; + case (int)(BCDIR | BC2): new_state = Write; break; + } + + if(new_state != _control_state) + { + _control_state = new_state; + switch(new_state) + { + default: break; + case LatchAddress: select_register(_data_input); break; + case Write: set_register_value(_data_input); break; + case Read: _data_output = get_register_value(); break; + } + } +} diff --git a/Components/AY38910/AY38910.hpp b/Components/AY38910/AY38910.hpp new file mode 100644 index 000000000..c6b5f7f78 --- /dev/null +++ b/Components/AY38910/AY38910.hpp @@ -0,0 +1,93 @@ +// +// AY-3-8910.hpp +// Clock Signal +// +// Created by Thomas Harte on 14/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef AY_3_8910_hpp +#define AY_3_8910_hpp + +#include "../../Outputs/Speaker.hpp" + +namespace GI { + +/*! + Provides emulation of an AY-3-8910 / YM2149, which is a three-channel sound chip with a + noise generator and a volume envelope generator, which also provides two bidirectional + interface ports. +*/ +class AY38910: public ::Outputs::Filter { + public: + /// Creates a new AY38910. + AY38910(); + + /// Sets the clock rate at which this AY38910 will be run. + void set_clock_rate(double clock_rate); + + enum ControlLines { + BC1 = (1 << 0), + BC2 = (1 << 1), + BCDIR = (1 << 2) + }; + + /// Sets the value the AY would read from its data lines if it were not outputting. + void set_data_input(uint8_t r); + + /// Gets the value that would appear on the data lines if only the AY is outputting. + uint8_t get_data_output(); + + /// Sets the + void set_control_lines(ControlLines control_lines); + + /*! + Gets the value that would appear on the requested interface port if it were in output mode. + @parameter port_b @c true to get the value for Port B, @c false to get the value for Port A. + */ + uint8_t get_port_output(bool port_b); + + // 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 _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]; + + enum ControlState { + Inactive, + LatchAddress, + Read, + Write + } _control_state; + + void select_register(uint8_t r); + void set_register_value(uint8_t value); + uint8_t get_register_value(); + + uint8_t _data_input, _data_output; +}; + +}; + +#endif /* AY_3_8910_hpp */ diff --git a/Concurrency/AsyncTaskQueue.cpp b/Concurrency/AsyncTaskQueue.cpp index 6f9f40027..1aa544c5e 100644 --- a/Concurrency/AsyncTaskQueue.cpp +++ b/Concurrency/AsyncTaskQueue.cpp @@ -17,23 +17,25 @@ AsyncTaskQueue::AsyncTaskQueue() : should_destruct_(false) { std::function next_function; - queue_mutex_.lock(); + // Take lock, check for a new task + std::unique_lock lock(queue_mutex_); if(!pending_tasks_.empty()) { next_function = pending_tasks_.front(); pending_tasks_.pop_front(); } - queue_mutex_.unlock(); if(next_function) { + // If there is a task, release lock and perform it + lock.unlock(); next_function(); } else { - std::unique_lock lock(queue_mutex_); + // If there isn't a task, atomically block on the processing condition and release the lock + // until there's something pending (and then release it again via scope) processing_condition_.wait(lock); - lock.unlock(); } } })); @@ -44,20 +46,24 @@ AsyncTaskQueue::~AsyncTaskQueue() should_destruct_ = true; enqueue([](){}); thread_->join(); + thread_.reset(); } void AsyncTaskQueue::enqueue(std::function function) { - queue_mutex_.lock(); - pending_tasks_.push_back(function); - queue_mutex_.unlock(); - std::lock_guard lock(queue_mutex_); + pending_tasks_.push_back(function); processing_condition_.notify_all(); } -void AsyncTaskQueue::synchronise() +void AsyncTaskQueue::flush() { - // TODO -// std::mutex + std::shared_ptr flush_mutex(new std::mutex); + std::shared_ptr flush_condition(new std::condition_variable); + std::unique_lock lock(*flush_mutex); + enqueue([=] () { + std::unique_lock inner_lock(*flush_mutex); + flush_condition->notify_all(); + }); + flush_condition->wait(lock); } diff --git a/Concurrency/AsyncTaskQueue.hpp b/Concurrency/AsyncTaskQueue.hpp index 6d06f2264..77485265a 100644 --- a/Concurrency/AsyncTaskQueue.hpp +++ b/Concurrency/AsyncTaskQueue.hpp @@ -39,7 +39,7 @@ class AsyncTaskQueue { /*! Blocks the caller until all previously-enqueud functions have completed. */ - void synchronise(); + void flush(); private: std::unique_ptr thread_; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 844b85f9c..78a63c13d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -16,7 +16,8 @@ using namespace Commodore::Vic20; Machine::Machine() : _rom(nullptr), - _is_running_at_zero_cost(false) + _is_running_at_zero_cost(false), + _tape(1022727) { // create 6522s, serial port and bus _userPortVIA.reset(new UserPortVIA); @@ -350,36 +351,13 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) } } -//void Machine::set_tape(std::shared_ptr tape) -//{ -// _tape.set_tape(tape); -// if(_should_automatically_load_media) set_typer_for_string("LOAD\nRUN\n"); -//} - -void Machine::tape_did_change_input(Tape *tape) +void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) { _keyboardVIA->set_control_line_input(KeyboardVIA::Port::A, KeyboardVIA::Line::One, tape->get_input()); } #pragma mark - Disc -/*void Machine::set_disk(std::shared_ptr disk) -{ - // construct the 1540 - _c1540.reset(new ::Commodore::C1540::Machine); - - // attach it to the serial bus - _c1540->set_serial_bus(_serialBus); - - // hand it the disk - _c1540->set_disk(disk); - - // install the ROM if it was previously set - install_disk_rom(); - - if(_should_automatically_load_media) set_typer_for_string("LOAD\"*\",8,1\nRUN\n"); -}*/ - void Machine::install_disk_rom() { if(_driveROM && _c1540) @@ -496,20 +474,3 @@ bool Machine::typer_set_next_character(::Utility::Typer *typer, char character, return true; } -#pragma mark - Tape - -Tape::Tape() : TapePlayer(1022727) {} - -void Tape::set_motor_control(bool enabled) {} -void Tape::set_tape_output(bool set) {} - -void Tape::process_input_pulse(Storage::Tape::PRG::Pulse pulse) -{ - bool new_input_level = pulse.type == Storage::Tape::PRG::Pulse::Low; - if(_input_level != new_input_level) - { - _input_level = new_input_level; - if(_delegate) _delegate->tape_did_change_input(this); - } -} - diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index eb2bcabb5..f652bbdbe 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -214,29 +214,6 @@ class SerialPort : public ::Commodore::Serial::Port { std::weak_ptr _userPortVIA; }; -class Tape: public Storage::Tape::TapePlayer { - public: - Tape(); - - void set_motor_control(bool enabled); - void set_tape_output(bool set); - inline bool get_input() { return _input_level; } - - class Delegate { - public: - virtual void tape_did_change_input(Tape *tape) = 0; - }; - void set_delegate(Delegate *delegate) - { - _delegate = delegate; - } - - private: - Delegate *_delegate; - virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse); - bool _input_level; -}; - class Vic6560: public MOS::MOS6560 { public: inline void perform_read(uint16_t address, uint8_t *pixel_data, uint8_t *colour_data) @@ -254,7 +231,7 @@ class Machine: public CRTMachine::Machine, public MOS::MOS6522IRQDelegate::Delegate, public Utility::TypeRecipient, - public Tape::Delegate, + public Storage::Tape::BinaryTapePlayer::Delegate, public ConfigurationTarget::Machine { public: @@ -301,7 +278,7 @@ class Machine: virtual bool typer_set_next_character(Utility::Typer *typer, char character, int phase); // for Tape::Delegate - virtual void tape_did_change_input(Tape *tape); + virtual void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape); private: uint8_t _characterROM[0x1000]; @@ -332,7 +309,7 @@ class Machine: // std::shared_ptr<::Commodore::Serial::DebugPort> _debugPort; // Tape - Tape _tape; + Storage::Tape::BinaryTapePlayer _tape; bool _use_fast_tape_hack, _should_automatically_load_media; bool _is_running_at_zero_cost; diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index e2659146e..12c8fbd24 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -148,13 +148,15 @@ class Machine: Machine(); void set_rom(ROMSlot slot, std::vector data, bool is_writeable); - void configure_as_target(const StaticAnalyser::Target &target); void set_key_state(Key key, bool isPressed); void clear_all_keys(); inline void set_use_fast_tape_hack(bool activate) { _use_fast_tape_hack = activate; } + // to satisfy ConfigurationTarget::Machine + void configure_as_target(const StaticAnalyser::Target &target); + // to satisfy CPU6502::Processor unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); void synchronise(); diff --git a/Machines/MemoryFuzzer.cpp b/Machines/MemoryFuzzer.cpp new file mode 100644 index 000000000..4c7934f2d --- /dev/null +++ b/Machines/MemoryFuzzer.cpp @@ -0,0 +1,27 @@ +// +// MemoryFuzzer.cpp +// Clock Signal +// +// Created by Thomas Harte on 19/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "MemoryFuzzer.hpp" + +#include + +void Memory::Fuzz(uint8_t *buffer, size_t size) +{ + unsigned int divider = ((unsigned int)RAND_MAX + 1) / 256; + unsigned int shift = 1, value = 1; + while(value < divider) + { + value <<= 1; + shift++; + } + + for(size_t c = 0; c < size; c++) + { + buffer[c] = (uint8_t)(rand() >> shift); + } +} diff --git a/Machines/MemoryFuzzer.hpp b/Machines/MemoryFuzzer.hpp new file mode 100644 index 000000000..648915033 --- /dev/null +++ b/Machines/MemoryFuzzer.hpp @@ -0,0 +1,21 @@ +// +// MemoryFuzzer.hpp +// Clock Signal +// +// Created by Thomas Harte on 19/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef MemoryFuzzer_hpp +#define MemoryFuzzer_hpp + +#include +#include + +namespace Memory { + +void Fuzz(uint8_t *buffer, size_t size); + +} + +#endif /* MemoryFuzzer_hpp */ diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp new file mode 100644 index 000000000..5f0becc8a --- /dev/null +++ b/Machines/Oric/Oric.cpp @@ -0,0 +1,124 @@ +// +// Oric.cpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Oric.hpp" +#include "../MemoryFuzzer.hpp" + +using namespace Oric; + +Machine::Machine() : _cycles_since_video_update(0) +{ + set_clock_rate(1000000); + _via.tape.reset(new Storage::Tape::BinaryTapePlayer(1000000)); + _via.set_interrupt_delegate(this); + _keyboard.reset(new Keyboard); + _via.keyboard = _keyboard; + clear_all_keys(); + _via.tape->set_delegate(this); + Memory::Fuzz(_ram, sizeof(_ram)); +} + +void Machine::configure_as_target(const StaticAnalyser::Target &target) +{ + if(target.tapes.size()) + { + _via.tape->set_tape(target.tapes.front()); + } +} + +void Machine::set_rom(std::vector data) +{ + memcpy(_rom, data.data(), std::min(data.size(), sizeof(_rom))); +} + +unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) +{ + if(address >= 0xc000) + { + if(isReadOperation(operation)) *value = _rom[address&16383]; + } + else + { + if((address & 0xff00) == 0x0300) + { + if(isReadOperation(operation)) *value = _via.get_register(address); + else _via.set_register(address, *value); + } + else + { + if(isReadOperation(operation)) + *value = _ram[address]; + else + { + if(address >= 0x9800) update_video(); + _ram[address] = *value; + } + } + } + + _via.run_for_half_cycles(2); + _via.tape->run_for_cycles(1); + _cycles_since_video_update++; + return 1; +} + +void Machine::synchronise() +{ + update_video(); + _via.synchronise(); +} + +void Machine::update_video() +{ + _videoOutput->run_for_cycles(_cycles_since_video_update); + _cycles_since_video_update = 0; +} + +void Machine::setup_output(float aspect_ratio) +{ + _videoOutput.reset(new VideoOutput(_ram)); + _via.ay8910.reset(new GI::AY38910()); + _via.ay8910->set_clock_rate(1000000); +} + +void Machine::close_output() +{ + _videoOutput.reset(); + _via.ay8910.reset(); +} + +void Machine::mos6522_did_change_interrupt_status(void *mos6522) +{ + set_irq_line(_via.get_interrupt_line()); +} + +void Machine::set_key_state(Key key, bool isPressed) +{ + if(key == KeyNMI) + { + set_nmi_line(isPressed); + } + else + { + if(isPressed) + _keyboard->rows[key >> 8] |= (key & 0xff); + else + _keyboard->rows[key >> 8] &= ~(key & 0xff); + } +} + +void Machine::clear_all_keys() +{ + memset(_keyboard->rows, 0, sizeof(_keyboard->rows)); +} + +void Machine::tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player) +{ + // set CB1 + _via.set_control_line_input(VIA::Port::B, VIA::Line::One, tape_player->get_input()); +} diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp new file mode 100644 index 000000000..d37a9429d --- /dev/null +++ b/Machines/Oric/Oric.hpp @@ -0,0 +1,165 @@ +// +// Oric.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Oric_hpp +#define Oric_hpp + +#include "../../Processors/6502/CPU6502.hpp" +#include "../../Components/6522/6522.hpp" +#include "../../Components/AY38910/AY38910.hpp" +#include "../../Storage/Tape/Tape.hpp" + +#include "../ConfigurationTarget.hpp" +#include "../CRTMachine.hpp" +#include "../Typer.hpp" + +#include "Video.hpp" + +#include +#include +#include + +namespace Oric { + +enum Key: uint16_t { + Key3 = 0x0000 | 0x80, KeyX = 0x0000 | 0x40, Key1 = 0x0000 | 0x20, + KeyV = 0x0000 | 0x08, Key5 = 0x0000 | 0x04, KeyN = 0x0000 | 0x02, Key7 = 0x0000 | 0x01, + KeyD = 0x0100 | 0x80, KeyQ = 0x0100 | 0x40, KeyEscape = 0x0100 | 0x20, + KeyF = 0x0100 | 0x08, KeyR = 0x0100 | 0x04, KeyT = 0x0100 | 0x02, KeyJ = 0x0100 | 0x01, + KeyC = 0x0200 | 0x80, Key2 = 0x0200 | 0x40, KeyZ = 0x0200 | 0x20, KeyControl = 0x0200 | 0x10, + Key4 = 0x0200 | 0x08, KeyB = 0x0200 | 0x04, Key6 = 0x0200 | 0x02, KeyM = 0x0200 | 0x01, + KeyQuote = 0x0300 | 0x80, KeyBackSlash = 0x0300 | 0x40, + KeyMinus = 0x0300 | 0x08, KeySemiColon = 0x0300 | 0x04, Key9 = 0x0300 | 0x02, KeyK = 0x0300 | 0x01, + KeyRight = 0x0400 | 0x80, KeyDown = 0x0400 | 0x40, KeyLeft = 0x0400 | 0x20, KeyLeftShift = 0x0400 | 0x10, + KeyUp = 0x0400 | 0x08, KeyFullStop = 0x0400 | 0x04, KeyComma = 0x0400 | 0x02, KeySpace = 0x0400 | 0x01, + KeyOpenSquare = 0x0500 | 0x80, KeyCloseSquare = 0x0500 | 0x40, KeyDelete = 0x0500 | 0x20, KeyFunction = 0x0500 | 0x10, + KeyP = 0x0500 | 0x08, KeyO = 0x0500 | 0x04, KeyI = 0x0500 | 0x02, KeyU = 0x0500 | 0x01, + KeyW = 0x0600 | 0x80, KeyS = 0x0600 | 0x40, KeyA = 0x0600 | 0x20, + KeyE = 0x0600 | 0x08, KeyG = 0x0600 | 0x04, KeyH = 0x0600 | 0x02, KeyY = 0x0600 | 0x01, + KeyEquals = 0x0700 | 0x80, KeyReturn = 0x0700 | 0x20, KeyRightShift = 0x0700 | 0x10, + KeyForwardSlash = 0x0700 | 0x08, Key0 = 0x0700 | 0x04, KeyL = 0x0700 | 0x02, Key8 = 0x0700 | 0x01, + + KeyNMI = 0xffff, +}; + +class Machine: + public CPU6502::Processor, + public CRTMachine::Machine, + public ConfigurationTarget::Machine, + public MOS::MOS6522IRQDelegate::Delegate, + public Storage::Tape::BinaryTapePlayer::Delegate { + + public: + Machine(); + + void set_rom(std::vector data); + void set_key_state(Key key, bool isPressed); + void clear_all_keys(); + + // to satisfy ConfigurationTarget::Machine + void configure_as_target(const StaticAnalyser::Target &target); + + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void synchronise(); + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output(); + virtual std::shared_ptr get_crt() { return _videoOutput->get_crt(); } + virtual std::shared_ptr get_speaker() { return _via.ay8910; } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + + // to satisfy MOS::MOS6522IRQDelegate::Delegate + void mos6522_did_change_interrupt_status(void *mos6522); + + // to satisfy Storage::Tape::BinaryTapePlayer::Delegate + void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape_player); + + private: + // RAM and ROM + uint8_t _ram[65536], _rom[16384]; + int _cycles_since_video_update; + inline void update_video(); + + // Outputs + std::unique_ptr _videoOutput; + + // Keyboard + class Keyboard { + public: + uint8_t row; + uint8_t rows[8]; + }; + + // VIA (which owns the tape and the AY) + class VIA: public MOS::MOS6522, public MOS::MOS6522IRQDelegate { + public: + using MOS6522IRQDelegate::set_interrupt_status; + + void set_control_line_output(Port port, Line line, bool value) + { + if(line) + { + if(port) _ay_bdir = value; else _ay_bc1 = value; + update_ay(); + } + } + + void set_port_output(Port port, uint8_t value, uint8_t direction_mask) { + if(port) + { + keyboard->row = value; + tape->set_motor_control(value & 0x40); + } + else + { + ay8910->set_data_input(value); + } + } + + uint8_t get_port_input(Port port) { + if(port) + { + uint8_t column = ay8910->get_port_output(false) ^ 0xff; + return (keyboard->rows[keyboard->row & 7] & column) ? 0x08 : 0x00; + } + else + { + return ay8910->get_data_output(); + } + } + + inline void run_for_half_cycles(unsigned int number_of_cycles) + { + _half_cycles_since_ay_update += number_of_cycles; + MOS::MOS6522::run_for_half_cycles(number_of_cycles); + } + + std::shared_ptr ay8910; + std::shared_ptr tape; + std::shared_ptr keyboard; + + inline void synchronise() { ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); _half_cycles_since_ay_update = 0; } + + private: + void update_ay() + { + ay8910->run_for_cycles(_half_cycles_since_ay_update >> 1); + _half_cycles_since_ay_update = 0; + ay8910->set_control_lines( (GI::AY38910::ControlLines)((_ay_bdir ? GI::AY38910::BCDIR : 0) | (_ay_bc1 ? GI::AY38910::BC1 : 0) | GI::AY38910::BC2)); + } + bool _ay_bdir, _ay_bc1; + unsigned int _half_cycles_since_ay_update; + }; + VIA _via; + std::shared_ptr _keyboard; +}; + +} +#endif /* Oric_hpp */ diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp new file mode 100644 index 000000000..7cbe9b29f --- /dev/null +++ b/Machines/Oric/Video.cpp @@ -0,0 +1,178 @@ +// +// Video.cpp +// Clock Signal +// +// Created by Thomas Harte on 12/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Video.hpp" + +using namespace Oric; + +namespace { + const unsigned int PAL50VSyncStartPosition = 256*64; + const unsigned int PAL60VSyncStartPosition = 234*64; + const unsigned int PAL50VSyncEndPosition = 259*64; + const unsigned int PAL60VSyncEndPosition = 238*64; + const unsigned int PAL50Period = 312*64; + const unsigned int PAL60Period = 262*64; +} + +VideoOutput::VideoOutput(uint8_t *memory) : + _ram(memory), + _frame_counter(0), _counter(0), + _state(Sync), _cycles_in_state(0), + _is_graphics_mode(false), + _character_set_base_address(0xb400), + _phase(0), + _v_sync_start_position(PAL50VSyncStartPosition), _v_sync_end_position(PAL50VSyncEndPosition), + _counter_period(PAL50Period), _next_frame_is_sixty_hertz(false) +{ + _crt.reset(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 1)); + + // TODO: this is a copy and paste from the Electron; factor out. + _crt->set_rgb_sampling_function( + "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" + "{" + "uint texValue = texture(sampler, coordinate).r;" + "texValue >>= 4 - (int(icoordinate.x * 8) & 4);" + "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));" + "}"); + + _crt->set_output_device(Outputs::CRT::Television); + _crt->set_visible_area(_crt->get_rect_for_area(50, 224, 16 * 6, 40 * 6, 4.0f / 3.0f)); +} + +std::shared_ptr VideoOutput::get_crt() +{ + return _crt; +} + +void VideoOutput::run_for_cycles(int number_of_cycles) +{ + // Vertical: 0–39: pixels; otherwise blank; 48–53 sync + // Horizontal: 0–223: pixels; otherwise blank; 256–259 sync + + while(number_of_cycles--) + { + _counter = (_counter + 1)%_counter_period; + int h_counter =_counter & 63; + + if(!h_counter) + { + _ink = 0xff; + _paper = 0x00; + _use_alternative_character_set = _use_double_height_characters = _blink_text = false; + set_character_set_base_address(); + _phase += 64; + + if(!_counter) + { + _phase += 128; + _frame_counter++; + + _v_sync_start_position = _next_frame_is_sixty_hertz ? PAL60VSyncStartPosition : PAL50VSyncStartPosition; + _v_sync_end_position = _next_frame_is_sixty_hertz ? PAL60VSyncEndPosition : PAL50VSyncEndPosition; + _counter_period = _next_frame_is_sixty_hertz ? PAL60Period : PAL50Period; + } + } + + State new_state = Blank; + if( + (h_counter >= 48 && h_counter <= 53) || + (_counter >= _v_sync_start_position && _counter <= _v_sync_end_position)) new_state = Sync; + else if(h_counter >= 54 && h_counter <= 56) new_state = ColourBurst; + else if(_counter < 224*64 && h_counter < 40) new_state = Pixels; + + if(_state != new_state) + { + switch(_state) + { + case ColourBurst: _crt->output_colour_burst(_cycles_in_state * 6, _phase, 128); break; + case Sync: _crt->output_sync(_cycles_in_state * 6); break; + case Blank: _crt->output_blank(_cycles_in_state * 6); break; + case Pixels: _crt->output_data(_cycles_in_state * 6, 2); break; + } + _state = new_state; + _cycles_in_state = 0; + if(_state == Pixels) _pixel_target = _crt->allocate_write_area(120); + } + _cycles_in_state++; + + if(new_state == Pixels) { + uint8_t pixels, control_byte; + + if(_is_graphics_mode && _counter < 200*64) + { + control_byte = pixels = _ram[0xa000 + (_counter >> 6) * 40 + h_counter]; + } + else + { + int address = 0xbb80 + (_counter >> 9) * 40 + h_counter; + control_byte = _ram[address]; + int line = _use_double_height_characters ? ((_counter >> 7) & 7) : ((_counter >> 6) & 7); + pixels = _ram[_character_set_base_address + (control_byte&127) * 8 + line]; + } + + uint8_t inverse_mask = (control_byte & 0x80) ? 0x77 : 0x00; + if(_blink_text) inverse_mask ^= (_frame_counter&32) ? 0x77 : 0x00; + + if((control_byte & 0x7f) >= 32) + { + if(_pixel_target) + { + uint8_t colours[2] = { + (uint8_t)(_paper ^ inverse_mask), + (uint8_t)(_ink ^ inverse_mask), + }; + + _pixel_target[0] = (colours[(pixels >> 4)&1] & 0x0f) | (colours[(pixels >> 5)&1] & 0xf0); + _pixel_target[1] = (colours[(pixels >> 2)&1] & 0x0f) | (colours[(pixels >> 3)&1] & 0xf0); + _pixel_target[2] = (colours[(pixels >> 0)&1] & 0x0f) | (colours[(pixels >> 1)&1] & 0xf0); + } + } + else + { + switch(control_byte & 0x7f) + { + case 0x00: _ink = 0x00; break; + case 0x01: _ink = 0x44; break; + case 0x02: _ink = 0x22; break; + case 0x03: _ink = 0x66; break; + case 0x04: _ink = 0x11; break; + case 0x05: _ink = 0x55; break; + case 0x06: _ink = 0x33; break; + case 0x07: _ink = 0x77; break; + + case 0x08: case 0x09: case 0x0a: case 0x0b: + case 0x0c: case 0x0d: case 0x0e: case 0x0f: + _use_alternative_character_set = (control_byte&1); + _use_double_height_characters = (control_byte&2); + _blink_text = (control_byte&4); + set_character_set_base_address(); + break; + + case 0x10: _paper = 0x00; break; + case 0x11: _paper = 0x44; break; + case 0x12: _paper = 0x22; break; + case 0x13: _paper = 0x66; break; + case 0x14: _paper = 0x11; break; + case 0x15: _paper = 0x55; break; + case 0x16: _paper = 0x33; break; + case 0x17: _paper = 0x77; break; + + case 0x18: case 0x19: case 0x1a: case 0x1b: + case 0x1c: case 0x1d: case 0x1e: case 0x1f: + _is_graphics_mode = (control_byte & 4); + _next_frame_is_sixty_hertz = !(control_byte & 2); + break; + + default: break; + } + if(_pixel_target) _pixel_target[0] = _pixel_target[1] = _pixel_target[2] = (uint8_t)(_paper ^ inverse_mask); + } + if(_pixel_target) _pixel_target += 3; + } + } +} diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp new file mode 100644 index 000000000..1a562347b --- /dev/null +++ b/Machines/Oric/Video.hpp @@ -0,0 +1,58 @@ +// +// Video.hpp +// Clock Signal +// +// Created by Thomas Harte on 12/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Machines_Oric_Video_hpp +#define Machines_Oric_Video_hpp + +#include "../../Outputs/CRT/CRT.hpp" + +namespace Oric { + +class VideoOutput { + public: + VideoOutput(uint8_t *memory); + std::shared_ptr get_crt(); + void run_for_cycles(int number_of_cycles); + + private: + uint8_t *_ram; + std::shared_ptr _crt; + + // Counters and limits + int _counter, _frame_counter; + int _v_sync_start_position, _v_sync_end_position, _counter_period; + + // Output state + enum State { + Blank, Sync, Pixels, ColourBurst + } _state; + unsigned int _cycles_in_state; + uint8_t *_pixel_target; + + // Registers + uint8_t _ink, _paper; + + int _character_set_base_address; + inline void set_character_set_base_address() + { + if(_is_graphics_mode) _character_set_base_address = _use_alternative_character_set ? 0x9c00 : 0x9800; + else _character_set_base_address = _use_alternative_character_set ? 0xb800 : 0xb400; + } + + bool _is_graphics_mode; + bool _next_frame_is_sixty_hertz; + bool _use_alternative_character_set; + bool _use_double_height_characters; + bool _blink_text; + + uint8_t _phase; +}; + +} + +#endif /* Video_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 984fb1149..9dd259d2f 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -19,12 +19,16 @@ 4B1E85751D170228001EF87D /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85731D170228001EF87D /* Typer.cpp */; }; 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; }; 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; }; + 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */; }; + 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; }; + 4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */; }; 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; }; 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; }; 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; }; 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; }; 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; }; 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */; }; + 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; }; 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; }; 4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; }; 4B30512D1D989E2200B4FED8 /* Drive.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512B1D989E2200B4FED8 /* Drive.cpp */; }; @@ -36,12 +40,14 @@ 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0C91D318B44005DD7A7 /* MOS6522Bridge.mm */; }; 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; }; 4B3BA0D11D318B44005DD7A7 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */; }; + 4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; 4B4C83701D4F623200CD541F /* D64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4C836E1D4F623200CD541F /* D64.cpp */; }; 4B4DC8211D2C2425003C5BF8 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; }; 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8261D2C2470003C5BF8 /* C1540.cpp */; }; 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; }; 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; }; 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; }; + 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */; }; 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; }; 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; }; 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; @@ -356,6 +362,9 @@ 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; }; 4BCA6CC81D9DD9F000C2D7B2 /* CommodoreROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */; }; + 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; }; + 4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */; }; + 4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */; }; 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */; }; 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; }; 4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; }; @@ -413,6 +422,10 @@ 4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = ""; }; 4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = ""; }; 4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = ""; }; + 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = ""; }; + 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = ""; }; + 4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; }; + 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OricOptionsPanel.swift; sourceTree = ""; }; 4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = ""; }; 4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = ""; }; 4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = ""; }; @@ -428,6 +441,8 @@ 4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = ""; }; 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = ""; }; 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = ""; }; + 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = ""; }; + 4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = ""; }; 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = ""; }; 4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = ""; }; 4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = ""; }; @@ -451,6 +466,8 @@ 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = ""; }; 4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = ""; }; 4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = ""; }; + 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = ""; }; + 4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = ""; }; 4B4C836E1D4F623200CD541F /* D64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = D64.cpp; sourceTree = ""; }; 4B4C836F1D4F623200CD541F /* D64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = D64.hpp; sourceTree = ""; }; 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = ""; }; @@ -462,6 +479,8 @@ 4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = ""; }; 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = ""; }; 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = ""; }; + 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = ""; }; + 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = ""; }; 4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = ""; }; 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = ""; }; 4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = ""; }; @@ -820,6 +839,12 @@ 4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreROM.cpp; path = Encodings/CommodoreROM.cpp; sourceTree = ""; }; 4BCA6CC71D9DD9F000C2D7B2 /* CommodoreROM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreROM.hpp; path = Encodings/CommodoreROM.hpp; sourceTree = ""; }; 4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = ""; }; + 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = ""; }; + 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = ""; }; + 4BCF1FA61DADC5250039D2E7 /* CSOric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOric.h; sourceTree = ""; }; + 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSOric.mm; sourceTree = ""; }; + 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.cpp; sourceTree = ""; }; + 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = ""; }; 4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = ""; }; 4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = ""; }; 4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = ""; }; @@ -969,6 +994,8 @@ 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */, 4B2A539B1D117D36003C6002 /* CSElectron.h */, 4B2A539C1D117D36003C6002 /* CSElectron.mm */, + 4BCF1FA61DADC5250039D2E7 /* CSOric.h */, + 4BCF1FA71DADC5250039D2E7 /* CSOric.mm */, 4B2A539D1D117D36003C6002 /* CSVic20.h */, 4B2A539E1D117D36003C6002 /* CSVic20.mm */, ); @@ -1033,6 +1060,15 @@ path = Bridges; sourceTree = ""; }; + 4B4A762D1DB1A35C007AAE2E /* AY38910 */ = { + isa = PBXGroup; + children = ( + 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */, + 4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */, + ); + name = AY38910; + sourceTree = ""; + }; 4B4DC81D1D2C2425003C5BF8 /* Commodore */ = { isa = PBXGroup; children = ( @@ -1069,10 +1105,12 @@ 4B8FE2281DA1EDDF0090D3CE /* ElectronOptionsPanel.swift */, 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, 4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */, + 4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */, 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */, 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */, 4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */, 4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */, + 4B2A332B1DB86821002876E3 /* OricOptions.xib */, 4B8FE2191DA19D5F0090D3CE /* Vic20Options.xib */, ); path = Documents; @@ -1138,6 +1176,8 @@ 4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */, 4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */, 4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */, + 4B59199A1DAC6C46005BB85C /* OricTAP.cpp */, + 4B59199B1DAC6C46005BB85C /* OricTAP.hpp */, ); path = Formats; sourceTree = ""; @@ -1563,13 +1603,16 @@ 4BB73EDC1B587CA500552FC2 /* Machines */ = { isa = PBXGroup; children = ( + 4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */, 4B1E85731D170228001EF87D /* Typer.cpp */, 4BA9C3CF1D8164A9002DDB61 /* ConfigurationTarget.hpp */, 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, + 4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */, 4B1E85741D170228001EF87D /* Typer.hpp */, 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B4DC81D1D2C2425003C5BF8 /* Commodore */, 4B2E2D9E1C3A070900138695 /* Electron */, + 4BCF1FA51DADC3E10039D2E7 /* Oric */, ); name = Machines; path = ../../Machines; @@ -1634,10 +1677,11 @@ 4BC9DF4A1D04691600F44158 /* Components */ = { isa = PBXGroup; children = ( + 4BD468F81D8DF4290084958B /* 1770 */, 4BC9DF4B1D04691600F44158 /* 6522 */, 4B1E85791D174DEC001EF87D /* 6532 */, 4BC9DF4C1D04691600F44158 /* 6560 */, - 4BD468F81D8DF4290084958B /* 1770 */, + 4B4A762D1DB1A35C007AAE2E /* AY38910 */, ); name = Components; path = ../../Components; @@ -1669,6 +1713,26 @@ name = Encodings; sourceTree = ""; }; + 4BCF1FA51DADC3E10039D2E7 /* Oric */ = { + isa = PBXGroup; + children = ( + 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */, + 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */, + 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */, + 4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */, + ); + name = Oric; + sourceTree = ""; + }; + 4BCF1FAC1DADD41F0039D2E7 /* Oric */ = { + isa = PBXGroup; + children = ( + 4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */, + 4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */, + ); + name = Oric; + sourceTree = ""; + }; 4BD14B121D7462810088EAD6 /* Acorn */ = { isa = PBXGroup; children = ( @@ -1741,6 +1805,7 @@ 4BD14B121D7462810088EAD6 /* Acorn */, 4BA799961D8B65730045123D /* Atari */, 4BC830D21D6E7C6D0000A26F /* Commodore */, + 4BCF1FAC1DADD41F0039D2E7 /* Oric */, ); name = StaticAnalyser; sourceTree = ""; @@ -1858,6 +1923,7 @@ buildActionMask = 2147483647; files = ( 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, + 4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */, 4B8FE21B1DA19D5F0090D3CE /* Atari2600Options.xib in Resources */, 4B8FE21C1DA19D5F0090D3CE /* MachineDocument.xib in Resources */, 4B8FE21E1DA19D5F0090D3CE /* Vic20Options.xib in Resources */, @@ -2160,13 +2226,16 @@ 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */, + 4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, + 4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */, 4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */, 4BA799951D8B656E0045123D /* StaticAnalyser.cpp in Sources */, 4BF829601D8F3C87001BAE39 /* CRC.cpp in Sources */, + 4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */, 4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */, @@ -2177,7 +2246,9 @@ 4BD69F941D98760000243FE1 /* AcornADF.cpp in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, + 4B2A332A1DB8544D002876E3 /* MemoryFuzzer.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, + 4B2A332F1DB86869002876E3 /* OricOptionsPanel.swift in Sources */, 4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */, 4BF829661D8F732B001BAE39 /* Disk.cpp in Sources */, 4BC5E4921D7ED365008CF980 /* StaticAnalyser.cpp in Sources */, @@ -2192,6 +2263,7 @@ 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, + 4BCF1FA81DADC5250039D2E7 /* CSOric.mm in Sources */, 4B6C73BD1D387AE500AFCFCA /* DiskController.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, 4B4DC8281D2C2470003C5BF8 /* C1540.cpp in Sources */, @@ -2205,6 +2277,7 @@ 4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */, 4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */, 4BF1354C1D6D2C300054B2EA /* StaticAnalyser.cpp in Sources */, + 4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, 4B8FE2201DA19D7C0090D3CE /* Atari2600OptionsPanel.swift in Sources */, @@ -2222,6 +2295,7 @@ 4B8FE2271DA1DE2D0090D3CE /* NSBundle+DataResource.m in Sources */, 4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */, 4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */, + 4BCF1FAB1DADD41B0039D2E7 /* StaticAnalyser.cpp in Sources */, 4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */, 4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */, 4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */, @@ -2276,6 +2350,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 4B2A332B1DB86821002876E3 /* OricOptions.xib */ = { + isa = PBXVariantGroup; + children = ( + 4B2A332C1DB86821002876E3 /* Base */, + ); + name = OricOptions.xib; + sourceTree = ""; + }; 4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h index e17fb372f..5bdf71d4a 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h @@ -11,7 +11,7 @@ @class CSAudioQueue; @protocol CSAudioQueueDelegate -- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue; +- (void)audioQueueIsRunningDry:(nonnull CSAudioQueue *)audioQueue; @end /*! diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index d7fd0b598..a99fdf3b5 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -14,13 +14,22 @@ @implementation CSAudioQueue { AudioQueueRef _audioQueue; + size_t _queuedSamples; + BOOL _hasHad256; } #pragma mark - AudioQueue callbacks - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer { - [self.delegate audioQueueDidCompleteBuffer:self]; + 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; + AudioQueueFreeBuffer(_audioQueue, buffer); } @@ -97,6 +106,8 @@ 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); diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib b/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib new file mode 100644 index 000000000..e1cdad4d0 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Base.lproj/OricOptions.xib @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index 61839333c..cd7a1da3a 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -8,6 +8,7 @@ #import "CSAtari2600.h" #import "CSElectron.h" +#import "CSOric.h" #import "CSVic20.h" #import "CSStaticAnalyser.h" diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index d709fef5b..8f54d6884 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -92,6 +92,7 @@ class MachineDocument: } override func close() { + bestEffortUpdater.flush() actionLock.lock() drawLock.lock() openGLView.invalidate() @@ -120,6 +121,8 @@ class MachineDocument: if let analyser = CSStaticAnalyser(fileAt: url) { self.displayName = analyser.displayName self.configureAs(analyser) + } else { + throw NSError(domain: "MachineDocument", code: -1, userInfo: nil) } } @@ -145,7 +148,7 @@ class MachineDocument: } // MARK: CSAudioQueueDelegate - final func audioQueueDidCompleteBuffer(_ audioQueue: CSAudioQueue) { + final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) { bestEffortUpdater.update() } diff --git a/OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift b/OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift new file mode 100644 index 000000000..aefd75a26 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Documents/OricOptionsPanel.swift @@ -0,0 +1,35 @@ +// +// OricOptionsPanel.swift +// Clock Signal +// +// Created by Thomas Harte on 19/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +class OricOptionsPanel: MachinePanel { + var oric: CSOric! { + get { + return self.machine as! CSOric + } + } + + fileprivate let displayTypeUserDefaultsKey = "oric.displayType" + + @IBOutlet var displayTypeButton: NSPopUpButton? + @IBAction func setDisplayType(_ sender: NSPopUpButton!) { + oric.useCompositeOutput = (sender.indexOfSelectedItem == 1) + UserDefaults.standard.set(sender.indexOfSelectedItem, forKey: self.displayTypeUserDefaultsKey) + } + + override func establishStoredOptions() { + super.establishStoredOptions() + let standardUserDefaults = UserDefaults.standard + standardUserDefaults.register(defaults: [ + displayTypeUserDefaultsKey: 0, + ]) + + let displayType = standardUserDefaults.integer(forKey: self.displayTypeUserDefaultsKey) + oric.useCompositeOutput = (displayType == 1) + self.displayTypeButton?.selectItem(at: displayType) + } +} diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 81a0c5b10..bdc20fa2b 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -94,7 +94,7 @@ tap CFBundleTypeName - Commodore Tape Image + Tape Image CFBundleTypeRole Viewer LSTypeIsPackage diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 1cf33e3ee..f8c07c466 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -10,10 +10,17 @@ #import "CSMachine.h" #import "CSMachine+Target.h" -#import "Clock_Signal-Swift.h" -#include "StaticAnalyser.hpp" #import "CSMachine+Subclassing.h" +#include "StaticAnalyser.hpp" + +#import "CSAtari2600.h" +#import "CSElectron.h" +#import "CSOric.h" +#import "CSVic20.h" + +#import "Clock_Signal-Swift.h" + @implementation CSStaticAnalyser { StaticAnalyser::Target _target; @@ -38,21 +45,23 @@ { switch(_target.machine) { - case StaticAnalyser::Target::Electron: return @"ElectronOptions"; - case StaticAnalyser::Target::Vic20: return @"Vic20Options"; case StaticAnalyser::Target::Atari2600: return @"Atari2600Options"; + case StaticAnalyser::Target::Electron: return @"ElectronOptions"; + case StaticAnalyser::Target::Oric: return @"OricOptions"; + case StaticAnalyser::Target::Vic20: return @"Vic20Options"; + default: return nil; } - - return nil; } - (CSMachine *)newMachine { switch(_target.machine) { - case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; - case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; case StaticAnalyser::Target::Atari2600: return [[CSAtari2600 alloc] init]; + case StaticAnalyser::Target::Electron: return [[CSElectron alloc] init]; + case StaticAnalyser::Target::Oric: return [[CSOric alloc] init]; + case StaticAnalyser::Target::Vic20: return [[CSVic20 alloc] init]; + default: return nil; } } diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm index 93a398b33..493b4836c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSElectron.mm @@ -10,7 +10,6 @@ #include "Electron.hpp" #include "StaticAnalyser.hpp" -#include "TapeUEF.hpp" #import "CSMachine+Subclassing.h" #import "NSData+StdVector.h" diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h new file mode 100644 index 000000000..3cd3e67d4 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.h @@ -0,0 +1,16 @@ +// +// CSOric.h +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSMachine.h" +#import "CSKeyboardMachine.h" + +@interface CSOric : CSMachine + +@property(nonatomic, assign) BOOL useCompositeOutput; + +@end diff --git a/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm new file mode 100644 index 000000000..c6dde0e4a --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/Wrappers/CSOric.mm @@ -0,0 +1,145 @@ +// +// CSOric.m +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSOric.h" + +#include "Oric.hpp" +#include "StaticAnalyser.hpp" + +#import "CSMachine+Subclassing.h" +#import "NSData+StdVector.h" +#import "NSBundle+DataResource.h" + +@implementation CSOric +{ + Oric::Machine _oric; +} + +- (instancetype)init +{ + self = [super init]; + if(self) + { + NSData *rom = [self rom:@"basic11"]; // test108j + if(rom) _oric.set_rom(rom.stdVector8); + } + return self; +} + +- (NSData *)rom:(NSString *)name +{ + return [[NSBundle mainBundle] dataForResource:name withExtension:@"rom" subdirectory:@"ROMImages/Oric"]; +} + +- (CRTMachine::Machine * const)machine +{ + return &_oric; +} + +#pragma mark - CSKeyboardMachine + +- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed +{ + @synchronized(self) { + switch(key) + { + case VK_ANSI_0: _oric.set_key_state(Oric::Key::Key0, isPressed); break; + case VK_ANSI_1: _oric.set_key_state(Oric::Key::Key1, isPressed); break; + case VK_ANSI_2: _oric.set_key_state(Oric::Key::Key2, isPressed); break; + case VK_ANSI_3: _oric.set_key_state(Oric::Key::Key3, isPressed); break; + case VK_ANSI_4: _oric.set_key_state(Oric::Key::Key4, isPressed); break; + case VK_ANSI_5: _oric.set_key_state(Oric::Key::Key5, isPressed); break; + case VK_ANSI_6: _oric.set_key_state(Oric::Key::Key6, isPressed); break; + case VK_ANSI_7: _oric.set_key_state(Oric::Key::Key7, isPressed); break; + case VK_ANSI_8: _oric.set_key_state(Oric::Key::Key8, isPressed); break; + case VK_ANSI_9: _oric.set_key_state(Oric::Key::Key9, isPressed); break; + + case VK_ANSI_Q: _oric.set_key_state(Oric::Key::KeyQ, isPressed); break; + case VK_ANSI_W: _oric.set_key_state(Oric::Key::KeyW, isPressed); break; + case VK_ANSI_E: _oric.set_key_state(Oric::Key::KeyE, isPressed); break; + case VK_ANSI_R: _oric.set_key_state(Oric::Key::KeyR, isPressed); break; + case VK_ANSI_T: _oric.set_key_state(Oric::Key::KeyT, isPressed); break; + case VK_ANSI_Y: _oric.set_key_state(Oric::Key::KeyY, isPressed); break; + case VK_ANSI_U: _oric.set_key_state(Oric::Key::KeyU, isPressed); break; + case VK_ANSI_I: _oric.set_key_state(Oric::Key::KeyI, isPressed); break; + case VK_ANSI_O: _oric.set_key_state(Oric::Key::KeyO, isPressed); break; + case VK_ANSI_P: _oric.set_key_state(Oric::Key::KeyP, isPressed); break; + case VK_ANSI_A: _oric.set_key_state(Oric::Key::KeyA, isPressed); break; + case VK_ANSI_S: _oric.set_key_state(Oric::Key::KeyS, isPressed); break; + case VK_ANSI_D: _oric.set_key_state(Oric::Key::KeyD, isPressed); break; + case VK_ANSI_F: _oric.set_key_state(Oric::Key::KeyF, isPressed); break; + case VK_ANSI_G: _oric.set_key_state(Oric::Key::KeyG, isPressed); break; + case VK_ANSI_H: _oric.set_key_state(Oric::Key::KeyH, isPressed); break; + case VK_ANSI_J: _oric.set_key_state(Oric::Key::KeyJ, isPressed); break; + case VK_ANSI_K: _oric.set_key_state(Oric::Key::KeyK, isPressed); break; + case VK_ANSI_L: _oric.set_key_state(Oric::Key::KeyL, isPressed); break; + case VK_ANSI_Z: _oric.set_key_state(Oric::Key::KeyZ, isPressed); break; + case VK_ANSI_X: _oric.set_key_state(Oric::Key::KeyX, isPressed); break; + case VK_ANSI_C: _oric.set_key_state(Oric::Key::KeyC, isPressed); break; + case VK_ANSI_V: _oric.set_key_state(Oric::Key::KeyV, isPressed); break; + case VK_ANSI_B: _oric.set_key_state(Oric::Key::KeyB, isPressed); break; + case VK_ANSI_N: _oric.set_key_state(Oric::Key::KeyN, isPressed); break; + case VK_ANSI_M: _oric.set_key_state(Oric::Key::KeyM, isPressed); break; + + case VK_Space: _oric.set_key_state(Oric::Key::KeySpace, isPressed); break; + case VK_Return: _oric.set_key_state(Oric::Key::KeyReturn, isPressed); break; + case VK_ANSI_Minus: _oric.set_key_state(Oric::Key::KeyMinus, isPressed); break; + case VK_ANSI_Equal: _oric.set_key_state(Oric::Key::KeyEquals, isPressed); break; + case VK_ANSI_Backslash: + _oric.set_key_state(Oric::Key::KeyBackSlash, isPressed); break; + case VK_ANSI_Slash: _oric.set_key_state(Oric::Key::KeyForwardSlash, isPressed); break; + + case VK_ANSI_LeftBracket: + _oric.set_key_state(Oric::Key::KeyOpenSquare, isPressed); break; + case VK_ANSI_RightBracket: + _oric.set_key_state(Oric::Key::KeyCloseSquare, isPressed); break; + case VK_ANSI_Quote: _oric.set_key_state(Oric::Key::KeyQuote, isPressed); break; + + case VK_RightArrow: _oric.set_key_state(Oric::Key::KeyRight, isPressed); break; + case VK_LeftArrow: _oric.set_key_state(Oric::Key::KeyLeft, isPressed); break; + case VK_DownArrow: _oric.set_key_state(Oric::Key::KeyDown, isPressed); break; + case VK_UpArrow: _oric.set_key_state(Oric::Key::KeyUp, isPressed); break; + + case VK_Delete: _oric.set_key_state(Oric::Key::KeyDelete, isPressed); break; + case VK_Escape: _oric.set_key_state(Oric::Key::KeyEscape, isPressed); break; + + case VK_ANSI_Comma: _oric.set_key_state(Oric::Key::KeyComma, isPressed); break; + case VK_ANSI_Period: _oric.set_key_state(Oric::Key::KeyFullStop, isPressed); break; + + case VK_ANSI_Semicolon: + _oric.set_key_state(Oric::Key::KeySemiColon, isPressed); break; + + case VK_Shift: _oric.set_key_state(Oric::Key::KeyLeftShift, isPressed); break; + case VK_RightShift: _oric.set_key_state(Oric::Key::KeyRightShift, isPressed); break; + case VK_Control: _oric.set_key_state(Oric::Key::KeyControl, isPressed); break; + + case VK_ANSI_Grave: + case VK_F12: _oric.set_key_state(Oric::Key::KeyNMI, isPressed); break; + + default: + printf("%02x\n", key); + break; + } + } +} + +- (void)clearAllKeys +{ + _oric.clear_all_keys(); +} + +#pragma mark - Options + +- (void)setUseCompositeOutput:(BOOL)useCompositeOutput { + @synchronized(self) { + _useCompositeOutput = useCompositeOutput; + _oric.get_crt()->set_output_device(useCompositeOutput ? Outputs::CRT::Television : Outputs::CRT::Monitor); + } +} + +@end diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h index 2ed7a43bc..7130f3636 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h @@ -25,5 +25,6 @@ @property (nonatomic, weak) id delegate; - (void)update; +- (void)flush; @end diff --git a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m index 10c0a133d..31d758c72 100644 --- a/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m +++ b/OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m @@ -38,7 +38,7 @@ { dispatch_async(_serialDispatchQueue, ^{ NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate]; - if(_previousTimeInterval > DBL_EPSILON) + if(_previousTimeInterval > DBL_EPSILON && timeInterval > _previousTimeInterval) { NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval; double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError; @@ -63,4 +63,9 @@ } } +- (void)flush +{ + dispatch_sync(_serialDispatchQueue, ^{}); +} + @end diff --git a/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift b/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift index 7fd637992..f07de2fa4 100644 --- a/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift +++ b/OSBindings/Mac/Clock SignalTests/6502InterruptTests.swift @@ -42,7 +42,9 @@ class MOS6502InterruptTests: XCTestCase { XCTAssert(machine.value(for: .programCounter) == 0x4004, "No interrupt should have occurred from interrupt raised between instructions") // run for a further 7 cycles, confirm that the IRQ vector was jumped to - machine.runForNumber(ofCycles: 7) + machine.runForNumber(ofCycles: 6) + XCTAssert(machine.value(for: .programCounter) != 0x1234, "Interrupt routine should not yet have begun") + machine.runForNumber(ofCycles: 1) XCTAssert(machine.value(for: .programCounter) == 0x1234, "Interrupt routine should just have begun") } diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index b151f627c..9d1b9436f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -53,7 +53,7 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display switch(displayType) { case DisplayType::PAL50: - set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 1135, 4); + set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500); // i.e. 283.7516 break; case DisplayType::NTSC60: diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index a38e63795..59d4ca962 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -103,7 +103,7 @@ class CRT { machines output will run at a fixed multiple of the clock rate; knowing this divisor can improve internal precision. - @param height_of_dispaly The number of lines that nominally form one field of the display, rounded + @param height_of_display The number of lines that nominally form one field of the display, rounded up to the next whole integer. @param colour_cycle_numerator Specifies the numerator for the per-line frequency of the colour subcarrier. diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 04ff49c8f..918dbe4a6 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -26,7 +26,7 @@ namespace Outputs { Intended to be a parent class, allowing descendants to pick the strategy by which input samples are mapped to output samples. */ -class Speaker: public Concurrency::AsyncTaskQueue { +class Speaker { public: class Delegate { public: @@ -85,9 +85,18 @@ class Speaker: public Concurrency::AsyncTaskQueue { set_needs_updated_filter_coefficients(); } - Speaker() : _buffer_in_progress_pointer(0), _requested_number_of_taps(0), _high_frequency_cut_off(-1.0) {} + Speaker() : _buffer_in_progress_pointer(0), _requested_number_of_taps(0), _high_frequency_cut_off(-1.0), _queue(new Concurrency::AsyncTaskQueue) {} protected: + void enqueue(std::function function) + { + _queue->enqueue(function); + } + void flush() + { + _queue->flush(); + } + std::unique_ptr _buffer_in_progress; float _high_frequency_cut_off; int _buffer_size; @@ -109,6 +118,8 @@ class Speaker: public Concurrency::AsyncTaskQueue { int16_t throwaway_samples[quantity]; get_samples(quantity, throwaway_samples); } + + std::unique_ptr _queue; }; /*! @@ -123,6 +134,11 @@ class Speaker: public Concurrency::AsyncTaskQueue { */ template class Filter: public Speaker { public: + ~Filter() + { + flush(); + } + void run_for_cycles(unsigned int input_cycles) { enqueue([=]() { diff --git a/README.md b/README.md index afd5d685c..a728a72b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ So its aims are: In terms of platforms, it currently contains emulations of the: * Acorn Electron; -* Atari 2600; and +* Atari 2600; +* Oric 1/Atmos; and * Commodore Vic-20 (and Commodore 1540/1). ## Single-click Loading diff --git a/StaticAnalyser/Oric/StaticAnalyser.cpp b/StaticAnalyser/Oric/StaticAnalyser.cpp new file mode 100644 index 000000000..e99798690 --- /dev/null +++ b/StaticAnalyser/Oric/StaticAnalyser.cpp @@ -0,0 +1,28 @@ +// +// StaticAnalyser.cpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "StaticAnalyser.hpp" + +using namespace StaticAnalyser::Oric; + +void StaticAnalyser::Oric::AddTargets( + const std::list> &disks, + const std::list> &tapes, + const std::list> &cartridges, + std::list &destination) +{ + // TODO: any sort of sanity checking at all; at the minute just trust the file type + // approximation already performed. + Target target; + target.machine = Target::Oric; + target.probability = 1.0; + target.disks = disks; + target.tapes = tapes; + target.cartridges = cartridges; + destination.push_back(target); +} diff --git a/StaticAnalyser/Oric/StaticAnalyser.hpp b/StaticAnalyser/Oric/StaticAnalyser.hpp new file mode 100644 index 000000000..275278089 --- /dev/null +++ b/StaticAnalyser/Oric/StaticAnalyser.hpp @@ -0,0 +1,28 @@ +// +// StaticAnalyser.hpp +// Clock Signal +// +// Created by Thomas Harte on 11/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef StaticAnalyser_Oric_StaticAnalyser_hpp +#define StaticAnalyser_Oric_StaticAnalyser_hpp + +#include "../StaticAnalyser.hpp" + +namespace StaticAnalyser { +namespace Oric { + +void AddTargets( + const std::list> &disks, + const std::list> &tapes, + const std::list> &cartridges, + std::list &destination +); + +} +} + + +#endif /* StaticAnalyser_hpp */ diff --git a/StaticAnalyser/StaticAnalyser.cpp b/StaticAnalyser/StaticAnalyser.cpp index 78647447e..aaabb819e 100644 --- a/StaticAnalyser/StaticAnalyser.cpp +++ b/StaticAnalyser/StaticAnalyser.cpp @@ -14,6 +14,7 @@ #include "Acorn/StaticAnalyser.hpp" #include "Atari/StaticAnalyser.hpp" #include "Commodore/StaticAnalyser.hpp" +#include "Oric/StaticAnalyser.hpp" // Cartridges #include "../Storage/Cartridge/Formats/BinaryDump.hpp" @@ -27,6 +28,7 @@ // Tapes #include "../Storage/Tape/Formats/CommodoreTAP.hpp" +#include "../Storage/Tape/Formats/OricTAP.hpp" #include "../Storage/Tape/Formats/TapePRG.hpp" #include "../Storage/Tape/Formats/TapeUEF.hpp" @@ -34,7 +36,8 @@ typedef int TargetPlatformType; enum class TargetPlatform: TargetPlatformType { Acorn = 1 << 0, Atari2600 = 1 << 1, - Commodore = 1 << 2 + Commodore = 1 << 2, + Oric = 1 << 3 }; using namespace StaticAnalyser; @@ -104,7 +107,8 @@ std::list StaticAnalyser::GetTargets(const char *file_name) Format("rom", cartridges, Cartridge::BinaryDump, TargetPlatform::Acorn) // ROM Format("ssd", disks, Disk::SSD, TargetPlatform::Acorn) // SSD - Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP + Format("tap", tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore) + Format("tap", tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric) Format("uef", tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape) #undef Format @@ -113,8 +117,9 @@ std::list StaticAnalyser::GetTargets(const char *file_name) // Hand off to platform-specific determination of whether these things are actually compatible and, // if so, how to load them. (TODO) if(potential_platforms & (TargetPlatformType)TargetPlatform::Acorn) Acorn::AddTargets(disks, tapes, cartridges, targets); - if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets); if(potential_platforms & (TargetPlatformType)TargetPlatform::Atari2600) Atari::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Commodore) Commodore::AddTargets(disks, tapes, cartridges, targets); + if(potential_platforms & (TargetPlatformType)TargetPlatform::Oric) Oric::AddTargets(disks, tapes, cartridges, targets); free(lowercase_extension); return targets; diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 94df52560..ca20b271a 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -33,7 +33,8 @@ struct Target { enum { Atari2600, Electron, - Vic20 + Vic20, + Oric } machine; float probability; diff --git a/Storage/Tape/Formats/OricTAP.cpp b/Storage/Tape/Formats/OricTAP.cpp new file mode 100644 index 000000000..f36a8cca6 --- /dev/null +++ b/Storage/Tape/Formats/OricTAP.cpp @@ -0,0 +1,181 @@ +// +// OricTAP.cpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "OricTAP.hpp" + +#include + +using namespace Storage::Tape; + +OricTAP::OricTAP(const char *file_name) : _file(NULL) +{ + struct stat file_stats; + stat(file_name, &file_stats); + _file_length = (size_t)file_stats.st_size; + + _file = fopen(file_name, "rb"); + + if(!_file) + throw ErrorNotOricTAP; + + // read and check the file signature + uint8_t signature[4]; + if(fread(signature, 1, 4, _file) != 4) + throw ErrorNotOricTAP; + + if(signature[0] != 0x16 || signature[1] != 0x16 || signature[2] != 0x16 || signature[3] != 0x24) + throw ErrorNotOricTAP; + + // then rewind and start again + virtual_reset(); +} + +OricTAP::~OricTAP() +{ + if(_file) fclose(_file); +} + +void OricTAP::virtual_reset() +{ +// fseek(_file, 0, SEEK_SET); + _bit_count = 13; + _phase = _next_phase = LeadIn; + _phase_counter = 0; + _pulse_counter = 0; +} + +Tape::Pulse OricTAP::virtual_get_next_pulse() +{ + // Each byte byte is written as 13 bits: 0, eight bits of data, parity, three 1s. + if(_bit_count == 13) + { + if(_next_phase != _phase) + { + _phase = _next_phase; + _phase_counter = 0; + } + + _bit_count = 0; + uint8_t next_byte = 0; + switch(_phase) + { + case LeadIn: + next_byte = _phase_counter < 258 ? 0x16 : 0x24; + _phase_counter++; + if(_phase_counter == 259) // 256 artificial bytes plus the three in the file = 259 + { + _next_phase = Header; + } + break; + + case Header: + // Counts are relative to: + // [0, 1]: "two bytes unused" (on the Oric 1) + // 2: program type + // 3: auto indicator + // [4, 5]: end address of data + // [6, 7]: start address of data + // 8: "unused" (on the Oric 1) + // [9...]: filename, up to NULL byte + next_byte = (uint8_t)fgetc(_file); + + if(_phase_counter == 4) _data_end_address = (uint16_t)(next_byte << 8); + if(_phase_counter == 5) _data_end_address |= next_byte; + if(_phase_counter == 6) _data_start_address = (uint16_t)(next_byte << 8); + if(_phase_counter == 7) _data_start_address |= next_byte; + + _phase_counter++; + if(_phase_counter >= 9 && !next_byte) // advance after the filename-ending NULL byte + { + _next_phase = Gap; + } + break; + + case Gap: + _phase_counter++; + if(_phase_counter == 8) + { + _next_phase = Data; + } + break; + + case Data: + next_byte = (uint8_t)fgetc(_file); + if(feof(_file)) _phase = End; +// _phase_counter++; +// if(_phase_counter == (_data_end_address - _data_start_address)+1) +// { +// _phase_counter = 0; +// if((size_t)ftell(_file) == _file_length) +// { +// _next_phase = End; +// } +// else +// { +// _next_phase = LeadIn; +// } +// } + break; + + case End: + break; + } + + uint8_t parity = next_byte; + parity ^= (parity >> 4); + parity ^= (parity >> 2); + parity ^= (parity >> 1); + _current_value = (uint16_t)(((uint16_t)next_byte << 1) | ((parity&1) << 9) | (7 << 10)); + } + + // In slow mode, a 0 is 4 periods of 1200 Hz, a 1 is 8 periods at 2400 Hz. + // In fast mode, a 1 is a single period of 2400 Hz, a 0 is a 2400 Hz pulse followed by a 1200 Hz pulse. + // This code models fast mode. + Tape::Pulse pulse; + pulse.length.clock_rate = 4800; + int next_bit; + + switch(_phase) + { + case End: + pulse.type = Pulse::Zero; + pulse.length.length = 4800; + return pulse; + + case Gap: + next_bit = 1; + break; + + default: + next_bit = _current_value & 1; + break; + } + + if(next_bit) + { + pulse.length.length = 1; + } + else + { + pulse.length.length = _pulse_counter ? 2 : 1; + } + pulse.type = _pulse_counter ? Pulse::High : Pulse::Low; // TODO + + _pulse_counter ^= 1; + if(!_pulse_counter) + { + _current_value >>= 1; + _bit_count++; + } + return pulse; +} + +bool OricTAP::is_at_end() +{ + return _phase == End; +} diff --git a/Storage/Tape/Formats/OricTAP.hpp b/Storage/Tape/Formats/OricTAP.hpp new file mode 100644 index 000000000..af3eaf7f4 --- /dev/null +++ b/Storage/Tape/Formats/OricTAP.hpp @@ -0,0 +1,59 @@ +// +// OricTAP.hpp +// Clock Signal +// +// Created by Thomas Harte on 10/10/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef OricTAP_hpp +#define OricTAP_hpp + +#include "../Tape.hpp" +#include + +namespace Storage { +namespace Tape { + +/*! + Provides a @c Tape containing an Oric-format tape image, which is a byte stream capture. +*/ +class OricTAP: public Tape { + public: + /*! + Constructs an @c OricTAP containing content from the file with name @c file_name. + + @throws ErrorNotOricTAP if this file could not be opened and recognised as a valid Oric-format TAP. + */ + OricTAP(const char *file_name); + ~OricTAP(); + + enum { + ErrorNotOricTAP + }; + + // implemented to satisfy @c Tape + bool is_at_end(); + + private: + void virtual_reset(); + Pulse virtual_get_next_pulse(); + + FILE *_file; + size_t _file_length; + + uint16_t _current_value; + int _bit_count; + int _pulse_counter; + int _phase_counter; + + enum Phase { + LeadIn, Header, Data, Gap, End + } _phase, _next_phase; + uint16_t _data_end_address, _data_start_address; +}; + +} +} + +#endif /* OricTAP_hpp */ diff --git a/Storage/Tape/Tape.hpp b/Storage/Tape/Tape.hpp index cbf330e49..fcf2b3d2a 100644 --- a/Storage/Tape/Tape.hpp +++ b/Storage/Tape/Tape.hpp @@ -94,6 +94,43 @@ class TapePlayer: public TimedEventLoop { Tape::Pulse _current_pulse; }; +class BinaryTapePlayer: public TapePlayer { + public: + BinaryTapePlayer(unsigned int input_clock_rate) : TapePlayer(input_clock_rate), _motor_is_running(false) {} + void set_motor_control(bool enabled) { _motor_is_running = enabled; } + void set_tape_output(bool set) {} // TODO + inline bool get_input() { return _input_level; } + + void run_for_cycles(int number_of_cycles) { + if(_motor_is_running) { + TapePlayer::run_for_cycles(number_of_cycles); + } + } + + class Delegate { + public: + virtual void tape_did_change_input(BinaryTapePlayer *tape_player) = 0; + }; + void set_delegate(Delegate *delegate) + { + _delegate = delegate; + } + + private: + Delegate *_delegate; + virtual void process_input_pulse(Storage::Tape::Tape::Pulse pulse) + { + bool new_input_level = pulse.type == Tape::Pulse::Low; + if(_input_level != new_input_level) + { + _input_level = new_input_level; + if(_delegate) _delegate->tape_did_change_input(this); + } + } + bool _input_level; + bool _motor_is_running; +}; + } }