1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-06 01:28:57 +00:00

Merge pull request #57 from TomHarte/Oric

Adds very provisional Oric emulation
This commit is contained in:
Thomas Harte 2016-10-20 07:39:38 -04:00 committed by GitHub
commit 79ed215254
39 changed files with 1699 additions and 106 deletions

View File

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

View File

@ -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<AY38910> {
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 */

View File

@ -17,23 +17,25 @@ AsyncTaskQueue::AsyncTaskQueue() : should_destruct_(false)
{
std::function<void(void)> next_function;
queue_mutex_.lock();
// Take lock, check for a new task
std::unique_lock<std::mutex> 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<std::mutex> 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<void(void)> function)
{
queue_mutex_.lock();
pending_tasks_.push_back(function);
queue_mutex_.unlock();
std::lock_guard<std::mutex> lock(queue_mutex_);
pending_tasks_.push_back(function);
processing_condition_.notify_all();
}
void AsyncTaskQueue::synchronise()
void AsyncTaskQueue::flush()
{
// TODO
// std::mutex
std::shared_ptr<std::mutex> flush_mutex(new std::mutex);
std::shared_ptr<std::condition_variable> flush_condition(new std::condition_variable);
std::unique_lock<std::mutex> lock(*flush_mutex);
enqueue([=] () {
std::unique_lock<std::mutex> inner_lock(*flush_mutex);
flush_condition->notify_all();
});
flush_condition->wait(lock);
}

View File

@ -39,7 +39,7 @@ class AsyncTaskQueue {
/*!
Blocks the caller until all previously-enqueud functions have completed.
*/
void synchronise();
void flush();
private:
std::unique_ptr<std::thread> thread_;

View File

@ -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<Storage::Tape::Tape> 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<Storage::Disk::Disk> 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);
}
}

View File

@ -214,29 +214,6 @@ class SerialPort : public ::Commodore::Serial::Port {
std::weak_ptr<UserPortVIA> _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<Vic6560> {
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;

View File

@ -148,13 +148,15 @@ class Machine:
Machine();
void set_rom(ROMSlot slot, std::vector<uint8_t> 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();

27
Machines/MemoryFuzzer.cpp Normal file
View File

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

21
Machines/MemoryFuzzer.hpp Normal file
View File

@ -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 <cstdint>
#include <cstddef>
namespace Memory {
void Fuzz(uint8_t *buffer, size_t size);
}
#endif /* MemoryFuzzer_hpp */

124
Machines/Oric/Oric.cpp Normal file
View File

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

165
Machines/Oric/Oric.hpp Normal file
View File

@ -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 <cstdint>
#include <vector>
#include <memory>
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<Machine>,
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public MOS::MOS6522IRQDelegate::Delegate,
public Storage::Tape::BinaryTapePlayer::Delegate {
public:
Machine();
void set_rom(std::vector<uint8_t> 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<Outputs::CRT::CRT> get_crt() { return _videoOutput->get_crt(); }
virtual std::shared_ptr<Outputs::Speaker> get_speaker() { return _via.ay8910; }
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::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> _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<VIA>, 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<VIA>::run_for_half_cycles(number_of_cycles);
}
std::shared_ptr<GI::AY38910> ay8910;
std::shared_ptr<Storage::Tape::BinaryTapePlayer> tape;
std::shared_ptr<Keyboard> 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> _keyboard;
};
}
#endif /* Oric_hpp */

178
Machines/Oric/Video.cpp Normal file
View File

@ -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<Outputs::CRT::CRT> VideoOutput::get_crt()
{
return _crt;
}
void VideoOutput::run_for_cycles(int number_of_cycles)
{
// Vertical: 039: pixels; otherwise blank; 4853 sync
// Horizontal: 0223: pixels; otherwise blank; 256259 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;
}
}
}

58
Machines/Oric/Video.hpp Normal file
View File

@ -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<Outputs::CRT::CRT> get_crt();
void run_for_cycles(int number_of_cycles);
private:
uint8_t *_ram;
std::shared_ptr<Outputs::CRT::CRT> _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 */

View File

@ -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 = "<group>"; };
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
4B2A33281DB8544D002876E3 /* MemoryFuzzer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryFuzzer.cpp; sourceTree = "<group>"; };
4B2A33291DB8544D002876E3 /* MemoryFuzzer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MemoryFuzzer.hpp; sourceTree = "<group>"; };
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 = "<group>"; };
4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = "<group>"; };
4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = "<group>"; };
4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = "<group>"; };
@ -428,6 +441,8 @@
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
4B2BFC5D1D613E0200BA3AA9 /* TapePRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapePRG.cpp; sourceTree = "<group>"; };
4B2BFC5E1D613E0200BA3AA9 /* TapePRG.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapePRG.hpp; sourceTree = "<group>"; };
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Oric/Video.cpp; sourceTree = "<group>"; };
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Video.hpp; path = Oric/Video.hpp; sourceTree = "<group>"; };
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
@ -451,6 +466,8 @@
4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MOS6532Bridge.mm; sourceTree = "<group>"; };
4B3BA0CC1D318B44005DD7A7 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
4B3BA0CD1D318B44005DD7A7 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = "<group>"; };
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = "<group>"; };
4B4C836E1D4F623200CD541F /* D64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = D64.cpp; sourceTree = "<group>"; };
4B4C836F1D4F623200CD541F /* D64.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = D64.hpp; sourceTree = "<group>"; };
4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Vic20.cpp; sourceTree = "<group>"; };
@ -462,6 +479,8 @@
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
4B59199A1DAC6C46005BB85C /* OricTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OricTAP.cpp; sourceTree = "<group>"; };
4B59199B1DAC6C46005BB85C /* OricTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OricTAP.hpp; sourceTree = "<group>"; };
4B643F381D77AD1900D431D6 /* CSStaticAnalyser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSStaticAnalyser.h; path = StaticAnalyser/CSStaticAnalyser.h; sourceTree = "<group>"; };
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
@ -820,6 +839,12 @@
4BCA6CC61D9DD9F000C2D7B2 /* CommodoreROM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreROM.cpp; path = Encodings/CommodoreROM.cpp; sourceTree = "<group>"; };
4BCA6CC71D9DD9F000C2D7B2 /* CommodoreROM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreROM.hpp; path = Encodings/CommodoreROM.hpp; sourceTree = "<group>"; };
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = "<group>"; };
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
4BCF1FA61DADC5250039D2E7 /* CSOric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOric.h; sourceTree = "<group>"; };
4BCF1FA71DADC5250039D2E7 /* CSOric.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSOric.mm; sourceTree = "<group>"; };
4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Oric/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD14B0F1D74627C0088EAD6 /* StaticAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StaticAnalyser.cpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.cpp; sourceTree = "<group>"; };
4BD14B101D74627C0088EAD6 /* StaticAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = StaticAnalyser.hpp; path = ../../StaticAnalyser/Acorn/StaticAnalyser.hpp; sourceTree = "<group>"; };
4BD328FD1D7E3EB5003B8C44 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = ../../StaticAnalyser/TapeParser.hpp; sourceTree = "<group>"; };
@ -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 = "<group>";
};
4B4A762D1DB1A35C007AAE2E /* AY38910 */ = {
isa = PBXGroup;
children = (
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */,
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */,
);
name = AY38910;
sourceTree = "<group>";
};
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 = "<group>";
@ -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 = "<group>";
};
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
isa = PBXGroup;
children = (
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */,
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */,
4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */,
4B2BFDB11DAEF5FF001A68B8 /* Video.hpp */,
);
name = Oric;
sourceTree = "<group>";
};
4BCF1FAC1DADD41F0039D2E7 /* Oric */ = {
isa = PBXGroup;
children = (
4BCF1FA91DADD41B0039D2E7 /* StaticAnalyser.cpp */,
4BCF1FAA1DADD41B0039D2E7 /* StaticAnalyser.hpp */,
);
name = Oric;
sourceTree = "<group>";
};
4BD14B121D7462810088EAD6 /* Acorn */ = {
isa = PBXGroup;
children = (
@ -1741,6 +1805,7 @@
4BD14B121D7462810088EAD6 /* Acorn */,
4BA799961D8B65730045123D /* Atari */,
4BC830D21D6E7C6D0000A26F /* Commodore */,
4BCF1FAC1DADD41F0039D2E7 /* Oric */,
);
name = StaticAnalyser;
sourceTree = "<group>";
@ -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 = "<group>";
};
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -11,7 +11,7 @@
@class CSAudioQueue;
@protocol CSAudioQueueDelegate
- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue;
- (void)audioQueueIsRunningDry:(nonnull CSAudioQueue *)audioQueue;
@end
/*!

View File

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

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11201" systemVersion="16A323" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11201"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="OricOptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="61"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rh8-km-57n">
<rect key="frame" x="18" y="17" width="165" height="26"/>
<popUpButtonCell key="cell" type="push" title="SCART" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tJM-kX-gaK" id="8SX-c5-ud1">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="L06-TO-EF0">
<items>
<menuItem title="SCART" state="on" id="tJM-kX-gaK"/>
<menuItem title="Composite" id="fFm-fS-rWG"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="setDisplayType:" target="ZW7-Bw-4RP" id="PAH-CZ-zlk"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="rh8-km-57n" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="VRo-6R-IKd"/>
<constraint firstAttribute="trailing" secondItem="rh8-km-57n" secondAttribute="trailing" constant="20" id="urO-Ac-aqK"/>
<constraint firstItem="rh8-km-57n" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="xJx-0U-vUw"/>
</constraints>
</view>
<connections>
<outlet property="displayTypeButton" destination="rh8-km-57n" id="FB2-Zg-VKq"/>
</connections>
<point key="canvasLocation" x="129" y="35.5"/>
</window>
</objects>
</document>

View File

@ -8,6 +8,7 @@
#import "CSAtari2600.h"
#import "CSElectron.h"
#import "CSOric.h"
#import "CSVic20.h"
#import "CSStaticAnalyser.h"

View File

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

View File

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

View File

@ -94,7 +94,7 @@
<string>tap</string>
</array>
<key>CFBundleTypeName</key>
<string>Commodore Tape Image</string>
<string>Tape Image</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>

View File

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

View File

@ -10,7 +10,6 @@
#include "Electron.hpp"
#include "StaticAnalyser.hpp"
#include "TapeUEF.hpp"
#import "CSMachine+Subclassing.h"
#import "NSData+StdVector.h"

View File

@ -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 <CSKeyboardMachine>
@property(nonatomic, assign) BOOL useCompositeOutput;
@end

View File

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

View File

@ -25,5 +25,6 @@
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
- (void)update;
- (void)flush;
@end

View File

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

View File

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

View File

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

View File

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

View File

@ -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<void(void)> function)
{
_queue->enqueue(function);
}
void flush()
{
_queue->flush();
}
std::unique_ptr<int16_t> _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<Concurrency::AsyncTaskQueue> _queue;
};
/*!
@ -123,6 +134,11 @@ class Speaker: public Concurrency::AsyncTaskQueue {
*/
template <class T> class Filter: public Speaker {
public:
~Filter()
{
flush();
}
void run_for_cycles(unsigned int input_cycles)
{
enqueue([=]() {

View File

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

View File

@ -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<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<StaticAnalyser::Target> &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);
}

View File

@ -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<std::shared_ptr<Storage::Disk::Disk>> &disks,
const std::list<std::shared_ptr<Storage::Tape::Tape>> &tapes,
const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges,
std::list<Target> &destination
);
}
}
#endif /* StaticAnalyser_hpp */

View File

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

View File

@ -33,7 +33,8 @@ struct Target {
enum {
Atari2600,
Electron,
Vic20
Vic20,
Oric
} machine;
float probability;

View File

@ -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 <sys/stat.h>
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;
}

View File

@ -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 <stdint.h>
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 */

View File

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