diff --git a/.gitignore b/.gitignore index c2d42ca10..3eacdcc4a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,8 @@ DerivedData *.xcuserstate .DS_Store -# Exclude Electron ROMs -OSBindings/Mac/Clock Signal/Resources/Electron/* +# Exclude system ROMs +ROMImages/* # CocoaPods # diff --git a/Components/6522/6522.cpp b/Components/6522/6522.cpp new file mode 100644 index 000000000..412cf4791 --- /dev/null +++ b/Components/6522/6522.cpp @@ -0,0 +1,12 @@ +// +// 6522.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "6522.hpp" + +using namespace MOS; + diff --git a/Components/6522/6522.hpp b/Components/6522/6522.hpp new file mode 100644 index 000000000..15458d465 --- /dev/null +++ b/Components/6522/6522.hpp @@ -0,0 +1,233 @@ +// +// 6522.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef _522_hpp +#define _522_hpp + +#include +#include + +namespace MOS { + +class MOS6522Delegate { + public: + virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0; +}; + +template class MOS6522 { + private: + enum InterruptFlag: uint8_t { + CA2ActiveEdge = 1 << 0, + CA1ActiveEdge = 1 << 1, + ShiftRegister = 1 << 2, + CB2ActiveEdge = 1 << 3, + CB1ActiveEdge = 1 << 4, + Timer2 = 1 << 5, + Timer1 = 1 << 6, + }; + + public: + void set_register(int address, uint8_t value) + { + address &= 0xf; +// printf("6522 %p: %d <- %02x\n", this, address, value); + switch(address) + { + case 0x0: + _registers.output[1] = value; + static_cast(this)->set_port_output(1, value); // TODO: handshake + break; + case 0x1: + _registers.output[0] = value; + static_cast(this)->set_port_output(0, value); // TODO: handshake + break; + case 0xf: + // No handshake, so write directly + _registers.output[0] = value; + static_cast(this)->set_port_output(0, value); + break; + + case 0x2: + _registers.data_direction[1] = value; + break; + case 0x3: + _registers.data_direction[0] = value; + break; + + // Timer 1 + case 0x6: case 0x4: _registers.timer_latch[0] = (_registers.timer_latch[0]&0xff00) | value; break; + case 0x5: case 0x7: + _registers.timer_latch[0] = (_registers.timer_latch[0]&0x00ff) | (uint16_t)(value << 8); + _registers.interrupt_flags &= ~InterruptFlag::Timer1; + if(address == 0x05) + { + _registers.timer[0] = _registers.timer_latch[0]; + _timer_is_running[0] = true; + } + reevaluate_interrupts(); + break; + + // Timer 2 + case 0x8: _registers.timer_latch[1] = value; break; + case 0x9: + _registers.interrupt_flags &= ~InterruptFlag::Timer2; + _registers.timer[1] = _registers.timer_latch[1] | (uint16_t)(value << 8); + _timer_is_running[1] = true; + reevaluate_interrupts(); + break; + + // Shift + case 0xa: _registers.shift = value; break; + + // Control + case 0xb: _registers.auxiliary_control = value; break; + case 0xc: _registers.peripheral_control = value; break; + + // Interrupt control + case 0xd: + _registers.interrupt_flags &= ~value; + reevaluate_interrupts(); + break; + case 0xe: + if(value&0x80) + _registers.interrupt_enable |= value; + else + _registers.interrupt_enable &= ~value; + reevaluate_interrupts(); + break; + } + } + + uint8_t get_register(int address) + { + address &= 0xf; +// printf("6522 %p: %d\n", this, address); + switch(address) + { +// case 0x0: return (_registers.auxiliary_control & 0x40) ? _registers.input[1] : static_cast(this)->get_port_input(1); + case 0x0: return _registers.output[1];//static_cast(this)->get_port_input(1); + case 0xf: // TODO: handshake, latching + case 0x1: return static_cast(this)->get_port_input(0); + + case 0x2: return _registers.data_direction[1]; + case 0x3: return _registers.data_direction[0]; + + // Timer 1 + case 0x4: + _registers.interrupt_flags &= ~InterruptFlag::Timer1; + reevaluate_interrupts(); + return _registers.timer[0] & 0x00ff; + case 0x5: return _registers.timer[0] >> 8; + case 0x6: return _registers.timer_latch[0] & 0x00ff; + case 0x7: return _registers.timer_latch[0] >> 8; + + // Timer 2 + case 0x8: + _registers.interrupt_flags &= ~InterruptFlag::Timer2; + reevaluate_interrupts(); + return _registers.timer[1] & 0x00ff; + case 0x9: return _registers.timer[1] >> 8; + + case 0xa: return _registers.shift; + + case 0xb: return _registers.auxiliary_control; + case 0xc: return _registers.peripheral_control; + + case 0xd: return _registers.interrupt_flags | (get_interrupt_line() ? 0x80 : 0x00); + case 0xe: return _registers.interrupt_enable | 0x80; + } + + return 0xff; + } + + void set_control_line_input(int port, int line, bool value) + { + } + + void run_for_cycles(unsigned int number_of_cycles) + { + _registers.timer[0] -= number_of_cycles; + _registers.timer[1] -= number_of_cycles; + + if(!_registers.timer[1] && _timer_is_running[1]) + { + _timer_is_running[1] = false; + _registers.interrupt_flags |= InterruptFlag::Timer2; + reevaluate_interrupts(); + } + + if(!_registers.timer[0] && _timer_is_running[0]) + { + _registers.interrupt_flags |= InterruptFlag::Timer1; + reevaluate_interrupts(); + + // TODO: reload shouldn't occur for a further 1.5 cycles + if(_registers.auxiliary_control&0x40) + _registers.timer[0] = _registers.timer_latch[0]; + else + _timer_is_running[0] = false; + } + // TODO: lots of other status effects + } + + bool get_interrupt_line() + { + uint8_t interrupt_status = _registers.interrupt_flags & _registers.interrupt_enable & 0x7f; + return !!interrupt_status; + } + + void set_delegate(MOS6522Delegate *delegate) + { + _delegate = delegate; + } + + MOS6522() : + _timer_is_running{false, false}, + _last_posted_interrupt_status(false) + {} + + private: + // Intended to be overwritten + uint8_t get_port_input(int port) { return 0xff; } + void set_port_output(int port, uint8_t value) {} + + // Delegate and communications + MOS6522Delegate *_delegate; + bool _last_posted_interrupt_status; + inline void reevaluate_interrupts() + { + bool new_interrupt_status = get_interrupt_line(); + if(new_interrupt_status != _last_posted_interrupt_status) + { + _last_posted_interrupt_status = new_interrupt_status; + if(_delegate) _delegate->mos6522_did_change_interrupt_status(this); + } + } + + // The registers + struct Registers { + uint8_t output[2], input[2], data_direction[2]; + uint16_t timer[2], timer_latch[2]; + uint8_t shift; + uint8_t auxiliary_control, peripheral_control; + uint8_t interrupt_flags, interrupt_enable; + + // "A low reset (RES) input clears all R6522 internal registers to logic 0" + Registers() : + output{0, 0}, input{0, 0}, data_direction{0, 0}, + auxiliary_control(0), peripheral_control(0), + interrupt_flags(0), interrupt_enable(0) {} + } _registers; + + // Internal state other than the registers + bool _timer_is_running[2]; +}; + +} + +#endif /* _522_hpp */ diff --git a/Components/6560/6560.cpp b/Components/6560/6560.cpp new file mode 100644 index 000000000..f22b69204 --- /dev/null +++ b/Components/6560/6560.cpp @@ -0,0 +1,440 @@ +// +// 6560.cpp +// Clock Signal +// +// Created by Thomas Harte on 05/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "6560.hpp" + +using namespace MOS; + +MOS6560::MOS6560() : + _crt(new Outputs::CRT::CRT(65*4, 4, Outputs::CRT::NTSC60, 1)), + _horizontal_counter(0), + _vertical_counter(0), + _cycles_since_speaker_update(0), + _is_odd_frame(false) +{ + _crt->set_composite_sampling_function( + "float composite_sample(usampler2D texID, vec2 coordinate, vec2 iCoordinate, float phase, float amplitude)" + "{" + "uint c = texture(texID, coordinate).r;" + "float y = float(c >> 4) / 4.0;" + "uint yC = c & 15u;" + "float phaseOffset = 6.283185308 * float(yC) / 16.0;" + +// "float chroma = step(mod(phase + phaseOffset + 0.785398163397448, 6.283185308), 3.141592654);" + "float chroma = cos(phase + phaseOffset);" + "return mix(y, step(yC, 14) * chroma, amplitude);" + "}"); + + // set up colours table + // 0 + // 2, 6, 9, B, + // 4, 5, 8, A, C, E + // 3, 7, D, F + // 1 + uint8_t luminances[16] = { // range is 0–4 + 0, 4, 1, 3, 2, 2, 1, 3, + 2, 1, 2, 1, 2, 3, 2, 3 + }; +// uint8_t pal_chrominances[16] = { // range is 0–15; 15 is a special case meaning "no chrominance" +// 15, 15, 5, 13, 2, 10, 0, 8, +// 6, 7, 5, 13, 2, 10, 0, 8, +// }; + uint8_t ntsc_chrominances[16] = { + 15, 15, 2, 10, 4, 12, 6, 14, + 0, 8, 2, 10, 4, 12, 6, 14, + }; + for(int c = 0; c < 16; c++) + { + _colours[c] = (uint8_t)((luminances[c] << 4) | ntsc_chrominances[c]); + } + + // show only the centre + _crt->set_visible_area(_crt->get_rect_for_area(16, 237, 11*4, 55*4, 4.0f / 3.0f)); + _speaker.set_input_rate(255681.75); // assuming NTSC; clock rate / 4 +} + +void MOS6560::set_register(int address, uint8_t value) +{ + address &= 0xf; + _registers[address] = value; + switch(address) + { + case 0x0: + _interlaced = !!(value&0x80); + _first_column_location = value & 0x7f; + break; + + case 0x1: + _first_row_location = value; + break; + + case 0x2: + _number_of_columns = value & 0x7f; + _video_matrix_start_address = (uint16_t)((_video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); + break; + + case 0x3: + _number_of_rows = (value >> 1)&0x3f; + _tall_characters = !!(value&0x01); + break; + + case 0x5: + _character_cell_start_address = (uint16_t)((value & 0x0f) << 10); + _video_matrix_start_address = (uint16_t)((_video_matrix_start_address & 0x0200) | ((value & 0xf0) << 6)); + break; + + case 0xa: + case 0xb: + case 0xc: + case 0xd: + update_audio(); + _speaker.set_control(address - 0xa, value); + break; + + case 0xe: + update_audio(); + _auxiliary_colour = _colours[value >> 4]; + _speaker.set_volume(value & 0xf); + break; + + case 0xf: + if(_this_state == State::Border) + { + output_border(_cycles_in_state * 4); + _cycles_in_state = 0; + } + _invertedCells = !!((value >> 3)&1); + _borderColour = _colours[value & 0x07]; + _backgroundColour = _colours[value >> 4]; + break; + + // TODO: audio, primarily + + default: + break; + } +} + +uint8_t MOS6560::get_register(int address) +{ + address &= 0xf; + switch(address) + { + default: return _registers[address]; + case 0x03: return (uint8_t)(_vertical_counter << 7) | (_registers[3] & 0x7f); + case 0x04: return (_vertical_counter >> 1) & 0xff; + } +} + +void MOS6560::output_border(unsigned int number_of_cycles) +{ + uint8_t *colour_pointer = _crt->allocate_write_area(1); + if(colour_pointer) *colour_pointer = _borderColour; + _crt->output_level(number_of_cycles); +} + +uint16_t MOS6560::get_address() +{ + _cycles_since_speaker_update++; + + _horizontal_counter++; + if(_horizontal_counter == 65) + { + _horizontal_counter = 0; + _vertical_counter++; + _column_counter = -1; + + if(_vertical_counter == (_interlaced ? (_is_odd_frame ? 262 : 263) : 261)) + { + _is_odd_frame ^= true; + _vertical_counter = 0; + _row_counter = -1; + } + + if(_row_counter >= 0) + { + _row_counter++; + if(_row_counter == _number_of_rows*(_tall_characters ? 16 : 8)) _row_counter = -1; + } + else if(_vertical_counter == _first_row_location * 2) + { + _video_matrix_line_address_counter = _video_matrix_start_address; + _row_counter = 0; + } + } + + if(_column_counter >= 0) + { + _column_counter++; + if(_column_counter == _number_of_columns*2) + { + _column_counter = -1; + if((_row_counter&(_tall_characters ? 15 : 7)) == (_tall_characters ? 15 : 7)) + { + _video_matrix_line_address_counter = _video_matrix_address_counter; + } + } + } + else if(_horizontal_counter == _first_column_location) + { + _column_counter = 0; + _video_matrix_address_counter = _video_matrix_line_address_counter; + } + + // determine output state; colour burst and sync timing are currently a guess + if(_horizontal_counter > 61) _this_state = State::ColourBurst; + else if(_horizontal_counter > 57) _this_state = State::Sync; + else + { + _this_state = (_column_counter >= 0 && _row_counter >= 0) ? State::Pixels : State::Border; + } + + // apply vertical sync + if( + (_vertical_counter < 3 && (_is_odd_frame || !_interlaced)) || + (_interlaced && + ( + (_vertical_counter == 0 && _horizontal_counter > 32) || + (_vertical_counter == 1) || (_vertical_counter == 2) || + (_vertical_counter == 3 && _horizontal_counter <= 32) + ) + )) + _this_state = State::Sync; + + // update the CRT + if(_this_state != _output_state) + { + switch(_output_state) + { + case State::Sync: _crt->output_sync(_cycles_in_state * 4); break; + case State::ColourBurst: _crt->output_colour_burst(_cycles_in_state * 4, _is_odd_frame ? 128 : 0, 0); break; + case State::Border: output_border(_cycles_in_state * 4); break; + case State::Pixels: _crt->output_data(_cycles_in_state * 4, 1); break; + } + _output_state = _this_state; + _cycles_in_state = 0; + + pixel_pointer = nullptr; + if(_output_state == State::Pixels) + { + pixel_pointer = _crt->allocate_write_area(260); + } + } + _cycles_in_state++; + + // compute the address + if(_this_state == State::Pixels) + { + /* + Per http://tinyvga.com/6561 : + + The basic video timing is very simple. For + every character the VIC-I is about to display, it first fetches the + character code and colour, then the character appearance (from the + character generator memory). The character codes are read on every + raster line, thus making every line a "bad line". When the raster + beam is outside of the text window, the videochip reads from $001c for + most time. (Some videochips read from $181c instead.) The address + occasionally varies, but it might also be due to a flaky bus. (By + reading from unconnected address space, such as $9100-$910f, you can + read the data fetched by the videochip on the previous clock cycle.) + */ + if(_column_counter&1) + { + return _character_cell_start_address + (_character_code*(_tall_characters ? 16 : 8)) + (_row_counter&(_tall_characters ? 15 : 7)); + } + else + { + uint16_t result = _video_matrix_address_counter; + _video_matrix_address_counter++; + return result; + } + } + + return 0x1c; +} + +void MOS6560::set_graphics_value(uint8_t value, uint8_t colour_value) +{ + // TODO: this isn't correct, as _character_value will be + // accessed second, then output will roll over. Probably it's + // correct (given the delays upstream) to output all 8 on an &1 + // and to adjust the signalling to the CRT above? + if(_this_state == State::Pixels) + { + if(_column_counter&1) + { + _character_value = value; + + if(pixel_pointer) + { + uint8_t cell_colour = _colours[_character_colour & 0x7]; + if(!(_character_colour&0x8)) + { + pixel_pointer[0] = ((_character_value >> 7)&1) ? cell_colour : _backgroundColour; + pixel_pointer[1] = ((_character_value >> 6)&1) ? cell_colour : _backgroundColour; + pixel_pointer[2] = ((_character_value >> 5)&1) ? cell_colour : _backgroundColour; + pixel_pointer[3] = ((_character_value >> 4)&1) ? cell_colour : _backgroundColour; + pixel_pointer[4] = ((_character_value >> 3)&1) ? cell_colour : _backgroundColour; + pixel_pointer[5] = ((_character_value >> 2)&1) ? cell_colour : _backgroundColour; + pixel_pointer[6] = ((_character_value >> 1)&1) ? cell_colour : _backgroundColour; + pixel_pointer[7] = ((_character_value >> 0)&1) ? cell_colour : _backgroundColour; + } + else + { + uint8_t colours[4] = {_backgroundColour, _borderColour, cell_colour, _auxiliary_colour}; + pixel_pointer[0] = + pixel_pointer[1] = colours[(_character_value >> 6)&3]; + pixel_pointer[2] = + pixel_pointer[3] = colours[(_character_value >> 4)&3]; + pixel_pointer[4] = + pixel_pointer[5] = colours[(_character_value >> 2)&3]; + pixel_pointer[6] = + pixel_pointer[7] = colours[(_character_value >> 0)&3]; + } + pixel_pointer += 8; + } + } + else + { + _character_code = value; + _character_colour = colour_value; + } + } +} + +void MOS6560::update_audio() +{ + _speaker.run_for_cycles(_cycles_since_speaker_update >> 2); + _cycles_since_speaker_update &= 3; +} + +#pragma mark - Audio + +MOS6560Speaker::MOS6560Speaker() : + _volume(0), + _control_registers{0, 0, 0, 0}, + _shift_registers{0, 0, 0, 0}, + _counters{2, 1, 0, 0} // create a slight phase offset for the three channels +{ +} + +void MOS6560Speaker::set_volume(uint8_t volume) +{ + _volume = volume; +} + +void MOS6560Speaker::set_control(int channel, uint8_t value) +{ + _control_registers[channel] = value; +} + +// Source: VICE. Not original. +static uint8_t noise_pattern[] = { + 0x07, 0x1e, 0x1e, 0x1c, 0x1c, 0x3e, 0x3c, 0x38, 0x78, 0xf8, 0x7c, 0x1e, 0x1f, 0x8f, 0x07, 0x07, + 0xc1, 0xc0, 0xe0, 0xf1, 0xe0, 0xf0, 0xe3, 0xe1, 0xc0, 0xe0, 0x78, 0x7e, 0x3c, 0x38, 0xe0, 0xe1, + 0xc3, 0xc3, 0x87, 0xc7, 0x07, 0x1e, 0x1c, 0x1f, 0x0e, 0x0e, 0x1e, 0x0e, 0x0f, 0x0f, 0xc3, 0xc3, + 0xf1, 0xe1, 0xe3, 0xc1, 0xe3, 0xc3, 0xc3, 0xfc, 0x3c, 0x1e, 0x0f, 0x83, 0xc3, 0xc1, 0xc1, 0xc3, + 0xc3, 0xc7, 0x87, 0x87, 0xc7, 0x0f, 0x0e, 0x3c, 0x7c, 0x78, 0x3c, 0x3c, 0x3c, 0x38, 0x3e, 0x1c, + 0x7c, 0x1e, 0x3c, 0x0f, 0x0e, 0x3e, 0x78, 0xf0, 0xf0, 0xe0, 0xe1, 0xf1, 0xc1, 0xc3, 0xc7, 0xc3, + 0xe1, 0xf1, 0xe0, 0xe1, 0xf0, 0xf1, 0xe3, 0xc0, 0xf0, 0xe0, 0xf8, 0x70, 0xe3, 0x87, 0x87, 0xc0, + 0xf0, 0xe0, 0xf1, 0xe1, 0xe1, 0xc7, 0x83, 0x87, 0x83, 0x8f, 0x87, 0x87, 0xc7, 0x83, 0xc3, 0x83, + 0xc3, 0xf1, 0xe1, 0xc3, 0xc7, 0x81, 0xcf, 0x87, 0x03, 0x87, 0xc7, 0xc7, 0x87, 0x83, 0xe1, 0xc3, + 0x07, 0xc3, 0x87, 0x87, 0x07, 0x87, 0xc3, 0x87, 0x83, 0xe1, 0xc3, 0xc7, 0xc3, 0x87, 0x87, 0x8f, + 0x0f, 0x87, 0x87, 0x0f, 0xcf, 0x1f, 0x87, 0x8e, 0x0e, 0x07, 0x81, 0xc3, 0xe3, 0xc1, 0xe0, 0xf0, + 0xe0, 0xe3, 0x83, 0x87, 0x07, 0x87, 0x8e, 0x1e, 0x0f, 0x07, 0x87, 0x8f, 0x1f, 0x07, 0x87, 0xc1, + 0xf0, 0xe1, 0xe1, 0xe3, 0xc7, 0x0f, 0x03, 0x8f, 0x87, 0x0e, 0x1e, 0x1e, 0x0f, 0x87, 0x87, 0x0f, + 0x87, 0x1f, 0x0f, 0xc3, 0xc3, 0xf0, 0xf8, 0xf0, 0x70, 0xf1, 0xf0, 0xf0, 0xe1, 0xf0, 0xe0, 0x78, + 0x7c, 0x78, 0x7c, 0x70, 0x71, 0xe1, 0xe1, 0xc3, 0xc3, 0xc7, 0x87, 0x1c, 0x3c, 0x3c, 0x1c, 0x3c, + 0x7c, 0x1e, 0x1e, 0x1e, 0x1c, 0x3c, 0x78, 0xf8, 0xf8, 0xe1, 0xc3, 0x87, 0x1e, 0x1e, 0x3c, 0x3e, + 0x0f, 0x0f, 0x87, 0x1f, 0x8e, 0x0f, 0x0f, 0x8e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, 0x8f, 0x87, + 0x87, 0xc3, 0x83, 0xc1, 0xe1, 0xc3, 0xc1, 0xc3, 0xc7, 0x8f, 0x0f, 0x0f, 0x0f, 0x0f, 0x83, 0xc7, + 0xc3, 0xc1, 0xe1, 0xe0, 0xf8, 0x3e, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x78, 0x3e, 0x1e, 0x1e, 0x1e, + 0x0f, 0x0f, 0x0f, 0x1e, 0x0e, 0x1e, 0x1e, 0x0f, 0x0f, 0x87, 0x1f, 0x87, 0x87, 0x1c, 0x3e, 0x1f, + 0x0f, 0x0f, 0x8e, 0x3e, 0x0e, 0x3e, 0x1e, 0x1c, 0x3c, 0x7c, 0xfc, 0x38, 0x78, 0x78, 0x38, 0x78, + 0x70, 0xf8, 0x7c, 0x1e, 0x3c, 0x3c, 0x30, 0xf1, 0xf0, 0x70, 0x70, 0xe0, 0xf8, 0xf0, 0xf8, 0x78, + 0x78, 0x71, 0xe1, 0xf0, 0xe3, 0xc1, 0xf0, 0x71, 0xe3, 0xc7, 0x87, 0x8e, 0x3e, 0x0e, 0x1e, 0x3e, + 0x0f, 0x07, 0x87, 0x0c, 0x3e, 0x0f, 0x87, 0x0f, 0x1e, 0x3c, 0x3c, 0x38, 0x78, 0xf1, 0xe7, 0xc3, + 0xc3, 0xc7, 0x8e, 0x3c, 0x38, 0xf0, 0xe0, 0x7e, 0x1e, 0x3e, 0x0e, 0x0f, 0x0f, 0x0f, 0x03, 0xc3, + 0xc3, 0xc7, 0x87, 0x1f, 0x0e, 0x1e, 0x1c, 0x3c, 0x3c, 0x0f, 0x07, 0x07, 0xc7, 0xc7, 0x87, 0x87, + 0x8f, 0x0f, 0xc0, 0xf0, 0xf8, 0x60, 0xf0, 0xf0, 0xe1, 0xe3, 0xe3, 0xc3, 0xc3, 0xc3, 0x87, 0x0f, + 0x87, 0x8e, 0x1e, 0x1e, 0x3f, 0x1e, 0x0e, 0x1c, 0x3c, 0x7e, 0x1e, 0x3c, 0x38, 0x78, 0x78, 0x78, + 0x38, 0x78, 0x3c, 0xe1, 0xe3, 0x8f, 0x1f, 0x1c, 0x78, 0x70, 0x7e, 0x0f, 0x87, 0x07, 0xc3, 0xc7, + 0x0f, 0x1e, 0x3c, 0x0e, 0x0f, 0x0e, 0x1e, 0x03, 0xf0, 0xf0, 0xf1, 0xe3, 0xc1, 0xc7, 0xc0, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe0, 0x70, 0xe1, 0xf0, 0x78, 0x70, 0xe3, 0xc7, 0x0f, 0xc1, 0xe1, 0xe3, 0xc3, + 0xc0, 0xf0, 0xfc, 0x1c, 0x3c, 0x70, 0xf8, 0x70, 0xf8, 0x78, 0x3c, 0x70, 0xf0, 0x78, 0x70, 0x7c, + 0x7c, 0x3c, 0x38, 0x1e, 0x3e, 0x3c, 0x7e, 0x07, 0x83, 0xc7, 0xc1, 0xc1, 0xe1, 0xc3, 0xc3, 0xc3, + 0xe1, 0xe1, 0xf0, 0x78, 0x7c, 0x3e, 0x0f, 0x1f, 0x07, 0x8f, 0x0f, 0x83, 0x87, 0xc1, 0xe3, 0xe3, + 0xc3, 0xc3, 0xe1, 0xf0, 0xf8, 0xf0, 0x3c, 0x7c, 0x3c, 0x0f, 0x8e, 0x0e, 0x1f, 0x1f, 0x0e, 0x3c, + 0x38, 0x78, 0x70, 0x70, 0xf0, 0xf0, 0xf8, 0x70, 0x70, 0x78, 0x38, 0x3c, 0x70, 0xe0, 0xf0, 0x78, + 0xf1, 0xf0, 0x78, 0x3e, 0x3c, 0x0f, 0x07, 0x0e, 0x3e, 0x1e, 0x3f, 0x1e, 0x0e, 0x0f, 0x87, 0x87, + 0x07, 0x0f, 0x07, 0xc7, 0x8f, 0x0f, 0x87, 0x1e, 0x1e, 0x1f, 0x1e, 0x1e, 0x3c, 0x1e, 0x1c, 0x3e, + 0x0f, 0x03, 0xc3, 0x81, 0xe0, 0xf0, 0xfc, 0x38, 0x3c, 0x3e, 0x0e, 0x1e, 0x1c, 0x7c, 0x1e, 0x1f, + 0x0e, 0x3e, 0x1c, 0x78, 0x78, 0x7c, 0x1e, 0x3e, 0x1e, 0x3c, 0x1f, 0x0f, 0x1f, 0x0f, 0x0f, 0x8f, + 0x1c, 0x3c, 0x78, 0xf8, 0xf0, 0xf8, 0x70, 0xf0, 0x78, 0x78, 0x3c, 0x3c, 0x78, 0x3c, 0x1f, 0x0f, + 0x07, 0x86, 0x1c, 0x1e, 0x1c, 0x1e, 0x1e, 0x1f, 0x03, 0xc3, 0xc7, 0x8e, 0x3c, 0x3c, 0x1c, 0x18, + 0xf0, 0xe1, 0xc3, 0xe1, 0xc1, 0xe1, 0xe3, 0xc3, 0xc3, 0xe3, 0xc3, 0x83, 0x87, 0x83, 0x87, 0x0f, + 0x07, 0x07, 0xe1, 0xe1, 0xe0, 0x7c, 0x78, 0x38, 0x78, 0x78, 0x3c, 0x1f, 0x0f, 0x8f, 0x0e, 0x07, + 0x0f, 0x07, 0x83, 0xc3, 0xc3, 0x81, 0xf0, 0xf8, 0xf1, 0xe0, 0xe3, 0xc7, 0x1c, 0x3e, 0x1e, 0x0f, + 0x0f, 0xc3, 0xf0, 0xf0, 0xe3, 0x83, 0xc3, 0xc7, 0x07, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x07, 0x87, + 0x0f, 0x0f, 0x0e, 0x0f, 0x0f, 0x1e, 0x0f, 0x0f, 0x87, 0x87, 0x87, 0x8f, 0xc7, 0xc7, 0x83, 0x83, + 0xc3, 0xc7, 0x8f, 0x87, 0x07, 0xc3, 0x8e, 0x1e, 0x38, 0x3e, 0x3c, 0x38, 0x7c, 0x1f, 0x1c, 0x38, + 0x3c, 0x78, 0x7c, 0x1e, 0x1c, 0x3c, 0x3f, 0x1e, 0x0e, 0x3e, 0x1c, 0x3c, 0x1f, 0x0f, 0x07, 0xc3, + 0xe3, 0x83, 0x87, 0x81, 0xc1, 0xe3, 0xcf, 0x0e, 0x0f, 0x1e, 0x3e, 0x1e, 0x1f, 0x0f, 0x8f, 0xc3, + 0x87, 0x0e, 0x03, 0xf0, 0xf0, 0x70, 0xe0, 0xe1, 0xe1, 0xc7, 0x8e, 0x0f, 0x0f, 0x1e, 0x0e, 0x1e, + 0x1f, 0x1c, 0x78, 0xf0, 0xf1, 0xf1, 0xe0, 0xf1, 0xe1, 0xe1, 0xe0, 0xe0, 0xf1, 0xc1, 0xf0, 0x71, + 0xe1, 0xc3, 0x83, 0xc7, 0x83, 0xe1, 0xe1, 0xf8, 0x70, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0x70, 0xf8, + 0x70, 0x70, 0x61, 0xe0, 0xf0, 0xe1, 0xe0, 0x78, 0x71, 0xe0, 0xf0, 0xf8, 0x38, 0x1e, 0x1c, 0x38, + 0x70, 0xf8, 0x60, 0x78, 0x38, 0x3c, 0x3f, 0x1f, 0x0f, 0x1f, 0x0f, 0x1f, 0x87, 0x87, 0x83, 0x87, + 0x83, 0xe1, 0xe1, 0xf0, 0x78, 0xf1, 0xf0, 0x70, 0x38, 0x38, 0x70, 0xe0, 0xe3, 0xc0, 0xe0, 0xf8, + 0x78, 0x78, 0xf8, 0x38, 0xf1, 0xe1, 0xe1, 0xc3, 0x87, 0x87, 0x0e, 0x1e, 0x1f, 0x0e, 0x0e, 0x0f, + 0x0f, 0x87, 0xc3, 0x87, 0x07, 0x83, 0xc0, 0xf0, 0x38, 0x3c, 0x3c, 0x38, 0xf0, 0xfc, 0x3e, 0x1e, + 0x1c, 0x1c, 0x38, 0x70, 0xf0, 0xf1, 0xe0, 0xf0, 0xe0, 0xe0, 0xf1, 0xe3, 0xe0, 0xe1, 0xf0, 0xf0, + 0x78, 0x7c, 0x78, 0x3c, 0x78, 0x78, 0x38, 0x78, 0x78, 0x78, 0x78, 0x70, 0xe3, 0x83, 0x83, 0xe0, + 0xc3, 0xc1, 0xe1, 0xc1, 0xc1, 0xc1, 0xe3, 0xc3, 0xc7, 0x1e, 0x0e, 0x1f, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0e, 0x0e, 0x0e, 0x07, 0x83, 0x87, 0x87, 0x0e, 0x07, 0x8f, 0x0f, 0x0f, 0x0f, 0x0e, 0x1c, 0x70, + 0xe1, 0xe0, 0x71, 0xc1, 0x83, 0x83, 0x87, 0x0f, 0x1e, 0x18, 0x78, 0x78, 0x7c, 0x3e, 0x1c, 0x38, + 0xf0, 0xe1, 0xe0, 0x78, 0x70, 0x38, 0x3c, 0x3e, 0x1e, 0x3c, 0x1e, 0x1c, 0x70, 0x3c, 0x38, 0x3f, +}; + +#define shift(r) _shift_registers[r] = (uint8_t)((_shift_registers[r] << 1) | (((_shift_registers[r]^0x80)&_control_registers[r]) >> 7)); +#define increment(r) _shift_registers[r] = (_shift_registers[r]+1)%8191; +#define update(r, m, up) _counters[r]++; if((_counters[r] >> m) == 0x7f) { up(r); _counters[r] = _control_registers[r]&0x7f; } + +void MOS6560Speaker::get_samples(unsigned int number_of_samples, int16_t *target) +{ + for(unsigned int c = 0; c < number_of_samples; c++) + { + update(0, 2, shift); + update(1, 1, shift); + update(2, 0, shift); + update(3, 1, increment); + + target[c] = ( + (_shift_registers[0]&1) + + (_shift_registers[1]&1) + + (_shift_registers[2]&1) + + ((noise_pattern[_shift_registers[3] >> 3] >> (_shift_registers[3]&7))&(_control_registers[3] >> 7)&1) + ) * _volume * 700; + } +} + +void MOS6560Speaker::skip_samples(unsigned int number_of_samples) +{ + for(unsigned int c = 0; c < number_of_samples; c++) + { + update(0, 2, shift); + update(1, 1, shift); + update(2, 0, shift); + update(3, 1, increment); + } +} + +#undef shift +#undef increment +#undef update diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp new file mode 100644 index 000000000..59a73d8f4 --- /dev/null +++ b/Components/6560/6560.hpp @@ -0,0 +1,86 @@ +// +// 6560.hpp +// Clock Signal +// +// Created by Thomas Harte on 05/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef _560_hpp +#define _560_hpp + +#include "../../Outputs/CRT/CRT.hpp" +#include "../../Outputs/Speaker.hpp" + +namespace MOS { + +class MOS6560Speaker: public ::Outputs::Filter { + public: + MOS6560Speaker(); + + void set_volume(uint8_t volume); + void set_control(int channel, uint8_t value); + + void get_samples(unsigned int number_of_samples, int16_t *target); + void skip_samples(unsigned int number_of_samples); + + private: + unsigned int _counters[4]; + uint8_t _shift_registers[4]; + uint8_t _control_registers[4]; + uint8_t _volume; +}; + +class MOS6560 { + public: + MOS6560(); + Outputs::CRT::CRT *get_crt() { return _crt.get(); } + Outputs::Speaker *get_speaker() { return &_speaker; } + + uint16_t get_address(); + void set_graphics_value(uint8_t value, uint8_t colour_value); + + void synchronise() { update_audio(); } + + void set_register(int address, uint8_t value); + uint8_t get_register(int address); + + private: + std::unique_ptr _crt; + MOS6560Speaker _speaker; + + bool _interlaced, _tall_characters; + uint8_t _first_column_location, _first_row_location; + uint8_t _number_of_columns, _number_of_rows; + uint16_t _character_cell_start_address, _video_matrix_start_address; + uint8_t _backgroundColour, _borderColour, _auxiliary_colour; + bool _invertedCells; + + int _horizontal_counter, _vertical_counter; + + int _column_counter, _row_counter; + uint16_t _video_matrix_address_counter, _video_matrix_line_address_counter; + + enum State { + Sync, ColourBurst, Border, Pixels + } _this_state, _output_state; + unsigned int _cycles_in_state; + + uint8_t _character_code, _character_colour, _character_value; + + uint8_t *pixel_pointer; + + uint8_t _registers[16]; + uint8_t _colours[16]; + + bool _is_odd_frame; + + void output_border(unsigned int number_of_cycles); + + unsigned int _cycles_since_speaker_update; + void update_audio(); +}; + +} + +#endif /* _560_hpp */ diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 2f85d1f65..8912b56c0 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -69,7 +69,7 @@ void Machine::setup_output(float aspect_ratio) "uint iPhase = (c >> 4);" "float phaseOffset = 6.283185308 * float(iPhase - 1u) / 13.0;" - "return (float(y) / 14.0) * (1.0 - amplitude) + step(1, iPhase) * amplitude * cos(phase + phaseOffset);" + "return mix(float(y) / 14.0, step(1, iPhase) * cos(phase + phaseOffset), amplitude);" "}"); _crt->set_output_device(Outputs::CRT::Television); @@ -89,7 +89,7 @@ void Machine::switch_region() "uint direction = iPhase & 1u;" "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);" "phaseOffset *= 6.283185308 / 12.0;" - "return (float(y) / 14.0) * (1.0 - amplitude) + step(4, (iPhase + 2u) & 15u) * amplitude * cos(phase + phaseOffset);" + "return mix(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset), amplitude);" "}"); _crt->set_new_timing(228, 312, Outputs::CRT::ColourSpace::YUV, 228, 1); diff --git a/Machines/Vic-20/Vic20.cpp b/Machines/Vic-20/Vic20.cpp new file mode 100644 index 000000000..5a60dcc1d --- /dev/null +++ b/Machines/Vic-20/Vic20.cpp @@ -0,0 +1,136 @@ +// +// Vic20.cpp +// Clock Signal +// +// Created by Thomas Harte on 04/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Vic20.hpp" + +#include + +using namespace Vic20; + +Machine::Machine() : + _rom(nullptr) +{ + _userPortVIA.set_delegate(this); + _keyboardVIA.set_delegate(this); + set_reset_line(true); +} + +Machine::~Machine() +{ + delete[] _rom; +} + +unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value) +{ + set_reset_line(false); + + // run the phase-1 part of this cycle, in which the VIC accesses memory + uint16_t video_address = _mos6560->get_address(); + uint8_t video_value = 0xff; // TODO + if(!(video_address&0x2000)) + { + video_value = _characterROM[video_address & 0x0fff]; + } + else + { + video_address &= 0x1fff; + if(video_address < sizeof(_userBASICMemory)) video_value = _userBASICMemory[video_address]; + else if(video_address >= 0x1000 && video_address < 0x2000) video_value = _screenMemory[video_address&0x0fff]; + } + _mos6560->set_graphics_value(video_value, _colorMemory[video_address & 0x03ff]); + + // run the phase-2 part of the cycle, which is whatever the 6502 said it should be + if(isReadOperation(operation)) + { + *value = read_memory(address); + if((address&0xfff0) == 0x9000) + { + *value = _mos6560->get_register(address - 0x9000); + } + else if((address&0xfff0) == 0x9110) + { + *value = _userPortVIA.get_register(address - 0x9110); + } + else if((address&0xfff0) == 0x9120) + { + *value = _keyboardVIA.get_register(address - 0x9120); + } + } + else + { + uint8_t *ram = ram_pointer(address); + if(ram) *ram = *value; + else if((address&0xfff0) == 0x9000) + { + _mos6560->set_register(address - 0x9000, *value); + } + else if((address&0xfff0) == 0x9110) + { + _userPortVIA.set_register(address - 0x9110, *value); + } + else if((address&0xfff0) == 0x9120) + { + _keyboardVIA.set_register(address - 0x9120, *value); + } + } + + _userPortVIA.run_for_cycles(1); + _keyboardVIA.run_for_cycles(1); + return 1; +} + +#pragma mark - 6522 delegate + +void Machine::mos6522_did_change_interrupt_status(void *mos6522) +{ + bool irq = _userPortVIA.get_interrupt_line() || _keyboardVIA.get_interrupt_line(); + set_irq_line(irq); +} + +#pragma mark - Setup + +void Machine::setup_output(float aspect_ratio) +{ + _mos6560 = std::unique_ptr(new MOS::MOS6560()); +} + +void Machine::set_rom(ROMSlot slot, size_t length, const uint8_t *data) +{ + uint8_t *target = nullptr; + size_t max_length = 0x2000; + switch(slot) + { + case ROMSlotKernel: target = _kernelROM; break; + case ROMSlotCharacters: target = _characterROM; max_length = 0x1000; break; + case ROMSlotBASIC: target = _basicROM; break; + } + + if(target) + { + size_t length_to_copy = std::min(max_length, length); + memcpy(target, data, length_to_copy); + } +} + +void Machine::add_prg(size_t length, const uint8_t *data) +{ + if(length > 2) + { + _rom_address = (uint16_t)(data[0] | (data[1] << 8)); + _rom_length = (uint16_t)(length - 2); + if(_rom_address >= 0x1000 && _rom_address+_rom_length < 0x2000) + { + memcpy(&_screenMemory[_rom_address - 0x1000], &data[2], length - 2); + } + else + { + _rom = new uint8_t[length - 2]; + memcpy(_rom, &data[2], length - 2); + } + } +} diff --git a/Machines/Vic-20/Vic20.hpp b/Machines/Vic-20/Vic20.hpp new file mode 100644 index 000000000..6e9e09a85 --- /dev/null +++ b/Machines/Vic-20/Vic20.hpp @@ -0,0 +1,151 @@ +// +// Vic20.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Vic20_hpp +#define Vic20_hpp + +#include "../../Processors/6502/CPU6502.hpp" +#include "../../Components/6560/6560.hpp" +#include "../../Components/6522/6522.hpp" +#include "../CRTMachine.hpp" + +namespace Vic20 { + +enum ROMSlot { + ROMSlotKernel, + ROMSlotBASIC, + ROMSlotCharacters, +}; + + +#define key(line, mask) (((mask) << 3) | (line)) + +enum Key: uint16_t { + Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08), + Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80), + KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08), + KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80), + KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08), + KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80), + KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08), + KeyM = key(4, 0x10), KeyFullStop = key(4, 0x20), KeyRShift = key(4, 0x40), KeyF1 = key(4, 0x80), + KeyRunStop = key(3, 0x01), KeyLShift = key(3, 0x02), KeyX = key(3, 0x04), KeyV = key(3, 0x08), + KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80), + KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08), + KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80), + KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08), + KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80), + Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08), + Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80), +}; + +class UserPortVIA: public MOS::MOS6522 { +}; + +class KeyboardVIA: public MOS::MOS6522 { + public: + void set_key_state(Key key, bool isPressed) { + if(isPressed) + _columns[key & 7] &= ~(key >> 3); + else + _columns[key & 7] |= (key >> 3); + } + + void clear_all_keys() { + memset(_columns, 0xff, sizeof(_columns)); + } + + // to satisfy MOS::MOS6522 + uint8_t get_port_input(int port) { + if(!port) { + uint8_t result = 0xff; + for(int c = 0; c < 8; c++) + { + if(!(_activation_mask&(1 << c))) + result &= _columns[c]; + } + return result; + } + + return 0xff; + } + + void set_port_output(int port, uint8_t value) { + if(port) + _activation_mask = value; + } + + KeyboardVIA() { + clear_all_keys(); + } + private: + uint8_t _columns[8]; + uint8_t _activation_mask; +}; + +class Machine: public CPU6502::Processor, public CRTMachine::Machine, public MOS::MOS6522Delegate { + public: + Machine(); + ~Machine(); + + void set_rom(ROMSlot slot, size_t length, const uint8_t *data); + void add_prg(size_t length, const uint8_t *data); + void set_key_state(Key key, bool isPressed) { _keyboardVIA.set_key_state(key, isPressed); } + void clear_all_keys() { _keyboardVIA.clear_all_keys(); } + + // to satisfy CPU6502::Processor + unsigned int perform_bus_operation(CPU6502::BusOperation operation, uint16_t address, uint8_t *value); + void synchronise() { _mos6560->synchronise(); } + + // to satisfy CRTMachine::Machine + virtual void setup_output(float aspect_ratio); + virtual void close_output() {} + virtual Outputs::CRT::CRT *get_crt() { return _mos6560->get_crt(); } + virtual Outputs::Speaker *get_speaker() { return _mos6560->get_speaker(); } + virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor::run_for_cycles(number_of_cycles); } + + // to satisfy MOS::MOS6522::Delegate + virtual void mos6522_did_change_interrupt_status(void *mos6522); + + private: + uint8_t _characterROM[0x1000]; + uint8_t _basicROM[0x2000]; + uint8_t _kernelROM[0x2000]; + + uint8_t *_rom; + uint16_t _rom_address, _rom_length; + + uint8_t _userBASICMemory[0x0400]; + uint8_t _screenMemory[0x1000]; + uint8_t _colorMemory[0x0400]; + + inline uint8_t *ram_pointer(uint16_t address) { + if(address < sizeof(_userBASICMemory)) return &_userBASICMemory[address]; + if(address >= 0x1000 && address < 0x2000) return &_screenMemory[address&0x0fff]; + if(address >= 0x9400 && address < 0x9800) return &_colorMemory[address&0x03ff]; // TODO: make this 4-bit + return nullptr; + } + + inline uint8_t read_memory(uint16_t address) { + uint8_t *ram = ram_pointer(address); + if(ram) return *ram; + else if(address >= 0x8000 && address < 0x9000) return _characterROM[address&0x0fff]; + else if(address >= 0xc000 && address < 0xe000) return _basicROM[address&0x1fff]; + else if(address >= 0xe000) return _kernelROM[address&0x1fff]; + else if(address >= _rom_address && address < _rom_address+_rom_length) return _rom[address - _rom_address]; + return 0xff; + } + + std::unique_ptr _mos6560; + UserPortVIA _userPortVIA; + KeyboardVIA _keyboardVIA; +}; + +} + +#endif /* Vic20_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 0edc27528..963e8afcc 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -28,6 +28,10 @@ 4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; }; 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; }; 4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; }; + 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B73C7191D036BD90074D992 /* Vic20Document.swift */; }; + 4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B73C71B1D036C030074D992 /* Vic20Document.xib */; }; + 4B886FF21D03B517004291C3 /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B886FF01D03B517004291C3 /* Vic20.cpp */; }; + 4B886FF51D03B61E004291C3 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B886FF41D03B61E004291C3 /* CSVic20.mm */; }; 4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; }; 4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; }; 4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; }; @@ -311,9 +315,9 @@ 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; - 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BCB70B31C947DDC005B1712 /* plus1.rom */; }; - 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85C1C3E1C2500C43F01 /* basic.rom */; }; - 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */ = {isa = PBXBuildFile; fileRef = 4BE5F85D1C3E1C2500C43F01 /* os.rom */; }; + 4BC9DF451D044FCA00F44158 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; }; + 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; }; + 4BCA98C31D065CA20062F44C /* 6522.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCA98C11D065CA20062F44C /* 6522.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -373,6 +377,12 @@ 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = ""; }; 4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = ""; }; 4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; + 4B73C7191D036BD90074D992 /* Vic20Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20Document.swift; sourceTree = ""; }; + 4B73C71C1D036C030074D992 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/Vic20Document.xib"; sourceTree = SOURCE_ROOT; }; + 4B886FF01D03B517004291C3 /* Vic20.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Vic20.cpp; path = "Vic-20/Vic20.cpp"; sourceTree = ""; }; + 4B886FF11D03B517004291C3 /* Vic20.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Vic20.hpp; path = "Vic-20/Vic20.hpp"; sourceTree = ""; }; + 4B886FF31D03B61E004291C3 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = ""; }; + 4B886FF41D03B61E004291C3 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = ""; }; 4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = ""; }; 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = ""; }; 4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = ""; }; @@ -676,9 +686,12 @@ 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; - 4BCB70B31C947DDC005B1712 /* plus1.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = plus1.rom; sourceTree = ""; }; - 4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = ""; }; - 4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = ""; }; + 4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = ""; }; + 4BC9DF461D04565200F44158 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = ""; }; + 4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = ""; }; + 4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = ""; }; + 4BCA98C11D065CA20062F44C /* 6522.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6522.cpp; sourceTree = ""; }; + 4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -790,9 +803,12 @@ 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */, 4B55CE521C3B7ABF0093A61B /* CSElectron.h */, 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */, + 4BC9DF461D04565200F44158 /* CSKeyboardMachine.h */, 4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */, 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */, 4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */, + 4B886FF31D03B61E004291C3 /* CSVic20.h */, + 4B886FF41D03B61E004291C3 /* CSVic20.mm */, 4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */, ); path = Wrappers; @@ -806,6 +822,8 @@ 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */, 4B2E2D931C399D1200138695 /* ElectronDocument.xib */, 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */, + 4B73C7191D036BD90074D992 /* Vic20Document.swift */, + 4B73C71B1D036C030074D992 /* Vic20Document.xib */, ); path = Documents; sourceTree = ""; @@ -848,6 +866,15 @@ path = Formats; sourceTree = ""; }; + 4B886FF61D03B632004291C3 /* Vic-20 */ = { + isa = PBXGroup; + children = ( + 4B886FF01D03B517004291C3 /* Vic20.cpp */, + 4B886FF11D03B517004291C3 /* Vic20.hpp */, + ); + name = "Vic-20"; + sourceTree = ""; + }; 4BB297E41B587D8300A49093 /* Wolfgang Lorenz 6502 test suite */ = { isa = PBXGroup; children = ( @@ -1127,6 +1154,7 @@ 4BB73EA01B587A5100552FC2 /* Clock Signal */, 4BB73EB51B587A5100552FC2 /* Clock SignalTests */, 4BB73EC01B587A5100552FC2 /* Clock SignalUITests */, + 4BC9DF4A1D04691600F44158 /* Components */, 4BB73EDC1B587CA500552FC2 /* Machines */, 4B366DFD1B5C165F0026627B /* Outputs */, 4BB73EDD1B587CA500552FC2 /* Processors */, @@ -1191,9 +1219,10 @@ 4BB73EDC1B587CA500552FC2 /* Machines */ = { isa = PBXGroup; children = ( + 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, 4B2E2D961C3A06EC00138695 /* Atari2600 */, 4B2E2D9E1C3A070900138695 /* Electron */, - 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */, + 4B886FF61D03B632004291C3 /* Vic-20 */, ); name = Machines; path = ../../Machines; @@ -1238,24 +1267,42 @@ path = Shaders; sourceTree = ""; }; + 4BC9DF4A1D04691600F44158 /* Components */ = { + isa = PBXGroup; + children = ( + 4BC9DF4B1D04691600F44158 /* 6522 */, + 4BC9DF4C1D04691600F44158 /* 6560 */, + ); + name = Components; + path = ../../Components; + sourceTree = ""; + }; + 4BC9DF4B1D04691600F44158 /* 6522 */ = { + isa = PBXGroup; + children = ( + 4BCA98C11D065CA20062F44C /* 6522.cpp */, + 4BCA98C21D065CA20062F44C /* 6522.hpp */, + ); + path = 6522; + sourceTree = ""; + }; + 4BC9DF4C1D04691600F44158 /* 6560 */ = { + isa = PBXGroup; + children = ( + 4BC9DF4D1D04691600F44158 /* 6560.cpp */, + 4BC9DF4E1D04691600F44158 /* 6560.hpp */, + ); + path = 6560; + sourceTree = ""; + }; 4BE5F85A1C3E1C2500C43F01 /* Resources */ = { isa = PBXGroup; children = ( - 4BE5F85B1C3E1C2500C43F01 /* Electron */, + 4BC9DF441D044FCA00F44158 /* ROMImages */, ); path = Resources; sourceTree = ""; }; - 4BE5F85B1C3E1C2500C43F01 /* Electron */ = { - isa = PBXGroup; - children = ( - 4BCB70B31C947DDC005B1712 /* plus1.rom */, - 4BE5F85C1C3E1C2500C43F01 /* basic.rom */, - 4BE5F85D1C3E1C2500C43F01 /* os.rom */, - ); - path = Electron; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1367,11 +1414,10 @@ files = ( 4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */, 4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */, - 4BE5F85F1C3E1C2500C43F01 /* os.rom in Resources */, - 4BCB70B41C947DDC005B1712 /* plus1.rom in Resources */, 4BB73EA71B587A5100552FC2 /* Atari2600Document.xib in Resources */, + 4B73C71D1D036C030074D992 /* Vic20Document.xib in Resources */, 4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */, - 4BE5F85E1C3E1C2500C43F01 /* basic.rom in Resources */, + 4BC9DF451D044FCA00F44158 /* ROMImages in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1663,18 +1709,23 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, 4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */, 4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */, 4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */, + 4B886FF21D03B517004291C3 /* Vic20.cpp in Sources */, 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */, 4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */, 4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */, 4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */, 4BBB14311CD2CECE00BDB55C /* IntermediateShader.cpp in Sources */, + 4B886FF51D03B61E004291C3 /* CSVic20.mm in Sources */, + 4B73C71A1D036BD90074D992 /* Vic20Document.swift in Sources */, 4BBF99181C8FBA6F0075DAFB /* TextureTarget.cpp in Sources */, 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */, 4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */, + 4BCA98C31D065CA20062F44C /* 6522.cpp in Sources */, 4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */, 4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */, 4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */, @@ -1736,6 +1787,14 @@ path = ..; sourceTree = ""; }; + 4B73C71B1D036C030074D992 /* Vic20Document.xib */ = { + isa = PBXVariantGroup; + children = ( + 4B73C71C1D036C030074D992 /* Base */, + ); + name = Vic20Document.xib; + sourceTree = ""; + }; 4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib index 9e73f7195..e1f140fda 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Document.xib @@ -1,5 +1,5 @@ - + diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib new file mode 100644 index 000000000..e1f140fda --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h index 552a4cbd4..62a210db7 100644 --- a/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h +++ b/OSBindings/Mac/Clock Signal/ClockSignal-Bridging-Header.h @@ -3,8 +3,11 @@ // #import "CSMachine.h" +#import "CSKeyboardMachine.h" + #import "CSAtari2600.h" #import "CSElectron.h" +#import "CSVic20.h" #import "CSOpenGLView.h" #import "AudioQueue.h" diff --git a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift index e13fff8fa..fd546a79b 100644 --- a/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift +++ b/OSBindings/Mac/Clock Signal/Documents/Atari2600Document.swift @@ -26,17 +26,9 @@ class Atari2600Document: MachineDocument { } override var windowNibName: String? { - // Returns the nib file name of the document - // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this property and override -makeWindowControllers instead. return "Atari2600Document" } - override func dataOfType(typeName: String) throws -> NSData { - // Insert code here to write your document to data of the specified type. If outError != nil, ensure that you create and set an appropriate error when returning nil. - // You can also choose to override fileWrapperOfType:error:, writeToURL:ofType:error:, or writeToURL:ofType:forSaveOperation:originalContentsURL:error: instead. - throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) - } - override func readFromData(data: NSData, ofType typeName: String) throws { atari2600.setROM(data) } diff --git a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift index 9f73af395..c9cfef538 100644 --- a/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/ElectronDocument.swift @@ -20,16 +20,18 @@ class ElectronDocument: MachineDocument { return NSSize(width: 11.0, height: 10.0) } + private func rom(name: String) -> NSData? { + return dataForResource(name, ofType: "rom", inDirectory: "ROMImages/Electron") + } + override func windowControllerDidLoadNib(aController: NSWindowController) { super.windowControllerDidLoadNib(aController) self.intendedCyclesPerSecond = 2000000 - if let osPath = NSBundle.mainBundle().pathForResource("os", ofType: "rom") { - self.electron.setOSROM(NSData(contentsOfFile: osPath)!) - } - if let basicPath = NSBundle.mainBundle().pathForResource("basic", ofType: "rom") { - self.electron.setBASICROM(NSData(contentsOfFile: basicPath)!) + if let os = rom("os"), basic = rom("basic") { + self.electron.setOSROM(os) + self.electron.setBASICROM(basic) } establishStoredOptions() @@ -57,8 +59,8 @@ class ElectronDocument: MachineDocument { } override func readFromData(data: NSData, ofType typeName: String) throws { - if let plus1Path = NSBundle.mainBundle().pathForResource("plus1", ofType: "rom") { - electron.setROM(NSData(contentsOfFile: plus1Path)!, slot: 12) + if let plus1ROM = rom("plus1") { + electron.setROM(plus1ROM, slot: 12) } electron.setROM(data, slot: 15) } @@ -93,25 +95,4 @@ class ElectronDocument: MachineDocument { electron.useTelevisionOutput = (displayType == 1) self.displayTypeButton.selectItemAtIndex(displayType) } - - // MARK: NSWindowDelegate - func windowDidResignKey(notification: NSNotification) { - electron.clearAllKeys() - } - - // MARK: CSOpenGLViewResponderDelegate - override func keyDown(event: NSEvent) { - electron.setKey(event.keyCode, isPressed: true) - } - - override func keyUp(event: NSEvent) { - electron.setKey(event.keyCode, isPressed: false) - } - - override func flagsChanged(newModifiers: NSEvent) { - electron.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.ShiftKeyMask)) - electron.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) - electron.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) - electron.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.AlternateKeyMask)) - } } diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index e752f032f..f75a85f77 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -48,9 +48,11 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe // establish and provide the audio queue, taking advice as to an appropriate sampling rate let maximumSamplingRate = AudioQueue.preferredSamplingRate() let selectedSamplingRate = self.machine().idealSamplingRateFromRange(NSRange(location: 0, length: NSInteger(maximumSamplingRate))) - audioQueue = AudioQueue(samplingRate: Float64(selectedSamplingRate)) - self.machine().audioQueue = self.audioQueue - self.machine().setAudioSamplingRate(selectedSamplingRate) + if selectedSamplingRate > 0 { + audioQueue = AudioQueue(samplingRate: Float64(selectedSamplingRate)) + self.machine().audioQueue = self.audioQueue + self.machine().setAudioSamplingRate(selectedSamplingRate) + } } override func close() { @@ -95,6 +97,15 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe lastTime = time } + // MARK: Utilities for children + func dataForResource(name : String, ofType type: String, inDirectory directory: String) -> NSData? { + if let path = NSBundle.mainBundle().pathForResource(name, ofType: type, inDirectory: directory) { + return NSData(contentsOfFile: path) + } + + return nil + } + // MARK: CSOpenGLViewDelegate func runForNumberOfCycles(numberOfCycles: Int32) { if actionLock.tryLock() { @@ -110,8 +121,36 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe } } - // MARK: CSOpenGLViewResponderDelegate - func keyDown(event: NSEvent) {} - func keyUp(event: NSEvent) {} - func flagsChanged(newModifiers: NSEvent) {} + // MARK: NSDocument overrides + override func dataOfType(typeName: String) throws -> NSData { + throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) + } + + // MARK: Key forwarding + private func withKeyboardMachine(action: (CSKeyboardMachine) -> ()) { + if let keyboardMachine = self.machine() as? CSKeyboardMachine { + action(keyboardMachine) + } + } + + func windowDidResignKey(notification: NSNotification) { + self.withKeyboardMachine { $0.clearAllKeys() } + } + + func keyDown(event: NSEvent) { + self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: true) } + } + + func keyUp(event: NSEvent) { + self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: false) } + } + + func flagsChanged(newModifiers: NSEvent) { + self.withKeyboardMachine { + $0.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.ShiftKeyMask)) + $0.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.ControlKeyMask)) + $0.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.CommandKeyMask)) + $0.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.AlternateKeyMask)) + } + } } diff --git a/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift new file mode 100644 index 000000000..875e90a08 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift @@ -0,0 +1,47 @@ +// +// Vic20Document.swift +// Clock Signal +// +// Created by Thomas Harte on 04/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +import Foundation + +class Vic20Document: MachineDocument { + + private lazy var vic20 = CSVic20() + override func machine() -> CSMachine! { + return vic20 + } + + // MARK: NSDocument overrides + override init() { + super.init() + self.intendedCyclesPerSecond = 1022727 + // TODO: or 1108405 for PAL; see http://www.antimon.org/dl/c64/code/stable.txt + + if let kernel = rom("kernel-ntsc"), basic = rom("basic"), characters = rom("characters-english") { + vic20.setKernelROM(kernel) + vic20.setBASICROM(basic) + vic20.setCharactersROM(characters) + } + } + + override class func autosavesInPlace() -> Bool { + return true + } + + override var windowNibName: String? { + return "Vic20Document" + } + + // MARK: machine setup + private func rom(name: String) -> NSData? { + return dataForResource(name, ofType: "bin", inDirectory: "ROMImages/Vic20") + } + + override func readFromData(data: NSData, ofType typeName: String) throws { + vic20.setPRG(data) + } +} diff --git a/OSBindings/Mac/Clock Signal/Info.plist b/OSBindings/Mac/Clock Signal/Info.plist index 07d3a41d5..212e82a8e 100644 --- a/OSBindings/Mac/Clock Signal/Info.plist +++ b/OSBindings/Mac/Clock Signal/Info.plist @@ -74,6 +74,20 @@ NSDocumentClass $(PRODUCT_MODULE_NAME).ElectronDocument + + CFBundleTypeExtensions + + prg + + CFBundleTypeName + Vic-20 Cartridge + CFBundleTypeRole + Viewer + LSTypeIsPackage + 0 + NSDocumentClass + $(PRODUCT_MODULE_NAME).Vic20Document + CFBundleExecutable $(EXECUTABLE_NAME) diff --git a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m index 9a409ea60..5f1e65ea4 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Wrappers/AudioQueue.m @@ -10,7 +10,7 @@ @import AudioToolbox; #define AudioQueueNumAudioBuffers 4 -#define AudioQueueStreamLength 1024 +#define AudioQueueStreamLength 4096 #define AudioQueueBufferLength 512 enum { diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h index 04d8e51ac..1de8ac9e0 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSElectron.h @@ -6,19 +6,16 @@ // Copyright © 2016 Thomas Harte. All rights reserved. // -#include "CSMachine.h" -#import "KeyCodes.h" +#import "CSMachine.h" +#import "CSKeyboardMachine.h" -@interface CSElectron : CSMachine +@interface CSElectron : CSMachine - (void)setOSROM:(nonnull NSData *)rom; - (void)setBASICROM:(nonnull NSData *)rom; - (void)setROM:(nonnull NSData *)rom slot:(int)slot; - (BOOL)openUEFAtURL:(nonnull NSURL *)URL; -- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; -- (void)clearAllKeys; - @property (nonatomic, assign) BOOL useFastLoadingHack; @property (nonatomic, assign) BOOL useTelevisionOutput; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSKeyboardMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSKeyboardMachine.h new file mode 100644 index 000000000..34f3dea0f --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSKeyboardMachine.h @@ -0,0 +1,16 @@ +// +// CSKeyboardMachine.h +// Clock Signal +// +// Created by Thomas Harte on 05/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "KeyCodes.h" + +@protocol CSKeyboardMachine + +- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed; +- (void)clearAllKeys; + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h index c91487b7f..332328f1c 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.h @@ -14,8 +14,8 @@ - (void)runForNumberOfCycles:(int)numberOfCycles; -- (int)idealSamplingRateFromRange:(NSRange)range; -- (void)setAudioSamplingRate:(int)samplingRate; +- (float)idealSamplingRateFromRange:(NSRange)range; +- (void)setAudioSamplingRate:(float)samplingRate; - (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio; - (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty; diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm index eefdec3fd..188abe561 100644 --- a/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSMachine.mm @@ -36,25 +36,25 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { }]; } -- (int)idealSamplingRateFromRange:(NSRange)range { +- (float)idealSamplingRateFromRange:(NSRange)range { @synchronized(self) { Outputs::Speaker *speaker = self.machine->get_speaker(); if(speaker) { - return speaker->get_ideal_clock_rate_in_range((int)range.location, (int)(range.location + range.length)); + return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length)); } - return (int)range.location; + return 0; } } -- (void)setAudioSamplingRate:(int)samplingRate { +- (void)setAudioSamplingRate:(float)samplingRate { @synchronized(self) { _speakerDelegate.machine = self; [self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate]; } } -- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(int)sampleRate { +- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate { @synchronized(self) { Outputs::Speaker *speaker = self.machine->get_speaker(); if(speaker) @@ -88,5 +88,4 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate { self.machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false); } - @end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSVic20.h b/OSBindings/Mac/Clock Signal/Wrappers/CSVic20.h new file mode 100644 index 000000000..3a8339dd6 --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSVic20.h @@ -0,0 +1,19 @@ +// +// CSVic20.h +// Clock Signal +// +// Created by Thomas Harte on 04/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSMachine.h" +#import "CSKeyboardMachine.h" + +@interface CSVic20 : CSMachine + +- (void)setKernelROM:(nonnull NSData *)rom; +- (void)setBASICROM:(nonnull NSData *)rom; +- (void)setCharactersROM:(nonnull NSData *)rom; +- (void)setPRG:(nonnull NSData *)prg; + +@end diff --git a/OSBindings/Mac/Clock Signal/Wrappers/CSVic20.mm b/OSBindings/Mac/Clock Signal/Wrappers/CSVic20.mm new file mode 100644 index 000000000..cd5a0c18f --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Wrappers/CSVic20.mm @@ -0,0 +1,122 @@ +// +// CSVic20.m +// Clock Signal +// +// Created by Thomas Harte on 04/06/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#import "CSVic20.h" + +#import "Vic20.hpp" + +@implementation CSVic20 { + Vic20::Machine _vic20; +} + +- (CRTMachine::Machine * const)machine { + return &_vic20; +} + +- (void)setROM:(nonnull NSData *)rom slot:(Vic20::ROMSlot)slot { + _vic20.set_rom(slot, rom.length, (const uint8_t *)rom.bytes); +} + +- (void)setKernelROM:(nonnull NSData *)rom { + [self setROM:rom slot:Vic20::ROMSlotKernel]; +} + +- (void)setBASICROM:(nonnull NSData *)rom { + [self setROM:rom slot:Vic20::ROMSlotBASIC]; +} + +- (void)setCharactersROM:(nonnull NSData *)rom { + [self setROM:rom slot:Vic20::ROMSlotCharacters]; +} + +- (void)setPRG:(nonnull NSData *)prg { + _vic20.add_prg(prg.length, (const uint8_t *)prg.bytes); +} + +- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed { + static NSDictionary *vicKeysByKeys = @{ + @(VK_ANSI_1): @(Vic20::Key::Key1), @(VK_ANSI_2): @(Vic20::Key::Key2), + @(VK_ANSI_3): @(Vic20::Key::Key3), @(VK_ANSI_4): @(Vic20::Key::Key4), + @(VK_ANSI_5): @(Vic20::Key::Key5), @(VK_ANSI_6): @(Vic20::Key::Key6), + @(VK_ANSI_7): @(Vic20::Key::Key7), @(VK_ANSI_8): @(Vic20::Key::Key8), + @(VK_ANSI_9): @(Vic20::Key::Key9), @(VK_ANSI_0): @(Vic20::Key::Key0), + + @(VK_ANSI_Q): @(Vic20::Key::KeyQ), @(VK_ANSI_W): @(Vic20::Key::KeyW), + @(VK_ANSI_E): @(Vic20::Key::KeyE), @(VK_ANSI_R): @(Vic20::Key::KeyR), + @(VK_ANSI_T): @(Vic20::Key::KeyT), @(VK_ANSI_Y): @(Vic20::Key::KeyY), + @(VK_ANSI_U): @(Vic20::Key::KeyU), @(VK_ANSI_I): @(Vic20::Key::KeyI), + @(VK_ANSI_O): @(Vic20::Key::KeyO), @(VK_ANSI_P): @(Vic20::Key::KeyP), + @(VK_ANSI_A): @(Vic20::Key::KeyA), @(VK_ANSI_S): @(Vic20::Key::KeyS), + @(VK_ANSI_D): @(Vic20::Key::KeyD), @(VK_ANSI_F): @(Vic20::Key::KeyF), + @(VK_ANSI_G): @(Vic20::Key::KeyG), @(VK_ANSI_H): @(Vic20::Key::KeyH), + @(VK_ANSI_J): @(Vic20::Key::KeyJ), @(VK_ANSI_K): @(Vic20::Key::KeyK), + @(VK_ANSI_L): @(Vic20::Key::KeyL), @(VK_ANSI_Z): @(Vic20::Key::KeyZ), + @(VK_ANSI_X): @(Vic20::Key::KeyX), @(VK_ANSI_C): @(Vic20::Key::KeyC), + @(VK_ANSI_V): @(Vic20::Key::KeyV), @(VK_ANSI_B): @(Vic20::Key::KeyB), + @(VK_ANSI_N): @(Vic20::Key::KeyN), @(VK_ANSI_M): @(Vic20::Key::KeyM), + + @(VK_Space): @(Vic20::Key::KeySpace), + @(VK_Return): @(Vic20::Key::KeyReturn), + @(VK_Delete): @(Vic20::Key::KeyDelete), + @(VK_ANSI_Comma): @(Vic20::Key::KeyComma), + @(VK_ANSI_Period): @(Vic20::Key::KeyFullStop), + @(VK_ANSI_Minus): @(Vic20::Key::KeyDash), + @(VK_ANSI_Equal): @(Vic20::Key::KeyEquals), + @(VK_ANSI_Semicolon): @(Vic20::Key::KeyColon), + @(VK_ANSI_Quote): @(Vic20::Key::KeySemicolon), + @(VK_ANSI_Slash): @(Vic20::Key::KeySlash), + @(VK_Option): @(Vic20::Key::KeyCBM), + @(VK_Control): @(Vic20::Key::KeyControl), + + @(VK_F1): @(Vic20::Key::KeyF1), @(VK_F3): @(Vic20::Key::KeyF3), + @(VK_F5): @(Vic20::Key::KeyF5), @(VK_F7): @(Vic20::Key::KeyF7), + + @(VK_ANSI_Grave): @(Vic20::Key::KeyLeft), + @(VK_Tab): @(Vic20::Key::KeyRunStop), + @(VK_ANSI_LeftBracket): @(Vic20::Key::KeyAt), + @(VK_ANSI_RightBracket): @(Vic20::Key::KeyAsterisk), + @(VK_ANSI_Backslash): @(Vic20::Key::KeyUp), + + @(VK_RightArrow): @(Vic20::Key::KeyRight), + @(VK_DownArrow): @(Vic20::Key::KeyDown), + }; + + // Not yet mapped: + // KeyHome + // KeyPlus + // KeyGBP + + @synchronized(self) { + switch(key) + { + default: { + NSNumber *targetKey = vicKeysByKeys[@(key)]; + if(targetKey) + { + _vic20.set_key_state((Vic20::Key)targetKey.integerValue, isPressed); + } + else + NSLog(@"Unmapped: %02x", key); + } break; + + case VK_Shift: + // Yuck + _vic20.set_key_state(Vic20::Key::KeyLShift, isPressed); + _vic20.set_key_state(Vic20::Key::KeyRShift, isPressed); + break; + } + } +} + +- (void)clearAllKeys { + @synchronized(self) { + _vic20.clear_all_keys(); + } +} + +@end diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index 3f68c7684..f91f9c301 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -377,6 +377,10 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_ unsigned int horizontal_scan_period = _horizontal_flywheel->get_scan_period(); unsigned int horizontal_retrace_period = horizontal_period - horizontal_scan_period; + // make sure that the requested range is visible + if(first_cycle_after_sync < horizontal_retrace_period) first_cycle_after_sync = (int)horizontal_retrace_period; + if(first_cycle_after_sync + number_of_cycles > horizontal_scan_period) number_of_cycles = (int)(horizontal_scan_period - (unsigned)first_cycle_after_sync); + float start_x = (float)((unsigned)first_cycle_after_sync - horizontal_retrace_period) / (float)horizontal_scan_period; float width = (float)number_of_cycles / (float)horizontal_scan_period; @@ -384,6 +388,13 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_ unsigned int vertical_period = _vertical_flywheel->get_standard_period(); unsigned int vertical_scan_period = _vertical_flywheel->get_scan_period(); unsigned int vertical_retrace_period = vertical_period - vertical_scan_period; + + // make sure that the requested range is visible +// if((unsigned)first_line_after_sync * horizontal_period < vertical_retrace_period) +// first_line_after_sync = (vertical_retrace_period + horizontal_period - 1) / horizontal_period; +// if((first_line_after_sync + number_of_lines) * horizontal_period > vertical_scan_period) +// number_of_lines = (int)(horizontal_scan_period - (unsigned)first_cycle_after_sync); + float start_y = (float)(((unsigned)first_line_after_sync * horizontal_period) - vertical_retrace_period) / (float)vertical_scan_period; float height = (float)((unsigned)number_of_lines * horizontal_period) / vertical_scan_period; diff --git a/Outputs/CRT/Internals/CRTOpenGL.cpp b/Outputs/CRT/Internals/CRTOpenGL.cpp index e01ea78a5..1d888974f 100644 --- a/Outputs/CRT/Internals/CRTOpenGL.cpp +++ b/Outputs/CRT/Internals/CRTOpenGL.cpp @@ -334,6 +334,7 @@ void OpenGLOutputBuilder::draw_frame(unsigned int output_width, unsigned int out glActiveTexture(pixel_accumulation_texture_unit); framebuffer->bind_texture(); +// compositeTexture->bind_texture(); framebuffer->draw((float)output_width / (float)output_height); _fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); diff --git a/Outputs/Speaker.hpp b/Outputs/Speaker.hpp index 9742e86df..6cfd434a2 100644 --- a/Outputs/Speaker.hpp +++ b/Outputs/Speaker.hpp @@ -24,7 +24,7 @@ class Speaker { virtual void speaker_did_complete_samples(Speaker *speaker, const int16_t *buffer, int buffer_size) = 0; }; - int get_ideal_clock_rate_in_range(int minimum, int maximum) + float get_ideal_clock_rate_in_range(float minimum, float maximum) { // return exactly the input rate if possible if(_input_cycles_per_second >= minimum && _input_cycles_per_second <= maximum) return _input_cycles_per_second; @@ -36,7 +36,7 @@ class Speaker { return maximum; } - void set_output_rate(int cycles_per_second, int buffer_size) + void set_output_rate(float cycles_per_second, int buffer_size) { _output_cycles_per_second = cycles_per_second; if(_buffer_size != buffer_size) @@ -58,7 +58,7 @@ class Speaker { _delegate = delegate; } - void set_input_rate(int cycles_per_second) + void set_input_rate(float cycles_per_second) { _input_cycles_per_second = cycles_per_second; set_needs_updated_filter_coefficients(); @@ -74,7 +74,7 @@ class Speaker { bool _coefficients_are_dirty; Delegate *_delegate; - int _input_cycles_per_second, _output_cycles_per_second; + float _input_cycles_per_second, _output_cycles_per_second; void set_needs_updated_filter_coefficients() { @@ -181,7 +181,7 @@ template class Filter: public Speaker { } else { - _number_of_taps = (_input_cycles_per_second + _output_cycles_per_second) / _output_cycles_per_second; + _number_of_taps = (int)ceilf((_input_cycles_per_second + _output_cycles_per_second) / _output_cycles_per_second); _number_of_taps *= 2; _number_of_taps |= 1; } @@ -190,7 +190,7 @@ template class Filter: public Speaker { _buffer_in_progress_pointer = 0; _stepper = std::unique_ptr(new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second)); - _filter = std::unique_ptr(new SignalProcessing::FIRFilter((unsigned int)_number_of_taps, (unsigned int)_input_cycles_per_second, 0.0, (float)_output_cycles_per_second / 2.0f, SignalProcessing::FIRFilter::DefaultAttenuation)); + _filter = std::unique_ptr(new SignalProcessing::FIRFilter((unsigned int)_number_of_taps, (float)_input_cycles_per_second, 0.0, (float)_output_cycles_per_second / 2.0f, SignalProcessing::FIRFilter::DefaultAttenuation)); _input_buffer = std::unique_ptr(new int16_t[_number_of_taps]); _input_buffer_depth = 0; diff --git a/ROMImages/Electron/readme.txt b/ROMImages/Electron/readme.txt new file mode 100644 index 000000000..8e81ed5e6 --- /dev/null +++ b/ROMImages/Electron/readme.txt @@ -0,0 +1,16 @@ +ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository. + +Expected files: + +basic.rom +os.rom +plus1.rom + +Likely to be desired in the future: + +adfs.rom +ADFS-E00_1.rom +ADFS-E00_2.rom +DFSE00r3.rom +ElectronExpansionRomPresAP2-v1.23.rom +os300.rom diff --git a/ROMImages/Vic20/readme.txt b/ROMImages/Vic20/readme.txt new file mode 100644 index 000000000..360af257b --- /dev/null +++ b/ROMImages/Vic20/readme.txt @@ -0,0 +1,17 @@ +ROM files would ordinarily go here; the copyright status of these is uncertain so they have not been included in this repository. + +Expected files: + +basic.bin +characters-english.bin +kernel-ntsc.rom + +Likely to be desired in the future: + +characters-danish.bin +characters-japanese.bin +characters-swedish.bin +kernel-danish.bin +kernel-japanese.bin +kernel-pal.bin +kernel-swedish.bin \ No newline at end of file