1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 21:29:53 +00:00

Merge pull request #18 from TomHarte/Vic-20

Adds basic NTSC VIC-20 emulation.
This commit is contained in:
Thomas Harte 2016-06-14 18:20:06 -04:00 committed by GitHub
commit 8c82f6ae37
29 changed files with 1523 additions and 90 deletions

4
.gitignore vendored
View File

@ -18,8 +18,8 @@ DerivedData
*.xcuserstate
.DS_Store
# Exclude Electron ROMs
OSBindings/Mac/Clock Signal/Resources/Electron/*
# Exclude system ROMs
ROMImages/*
# CocoaPods
#

12
Components/6522/6522.cpp Normal file
View File

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

233
Components/6522/6522.hpp Normal file
View File

@ -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 <cstdint>
#include <cstdio>
namespace MOS {
class MOS6522Delegate {
public:
virtual void mos6522_did_change_interrupt_status(void *mos6522) = 0;
};
template <class T> 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<T *>(this)->set_port_output(1, value); // TODO: handshake
break;
case 0x1:
_registers.output[0] = value;
static_cast<T *>(this)->set_port_output(0, value); // TODO: handshake
break;
case 0xf:
// No handshake, so write directly
_registers.output[0] = value;
static_cast<T *>(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<T *>(this)->get_port_input(1);
case 0x0: return _registers.output[1];//static_cast<T *>(this)->get_port_input(1);
case 0xf: // TODO: handshake, latching
case 0x1: return static_cast<T *>(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 */

440
Components/6560/6560.cpp Normal file
View File

@ -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 04
0, 4, 1, 3, 2, 2, 1, 3,
2, 1, 2, 1, 2, 3, 2, 3
};
// uint8_t pal_chrominances[16] = { // range is 015; 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

86
Components/6560/6560.hpp Normal file
View File

@ -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<MOS6560Speaker> {
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<Outputs::CRT::CRT> _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 */

View File

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

136
Machines/Vic-20/Vic20.cpp Normal file
View File

@ -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 <algorithm>
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<MOS::MOS6560>(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);
}
}
}

151
Machines/Vic-20/Vic20.hpp Normal file
View File

@ -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<UserPortVIA> {
};
class KeyboardVIA: public MOS::MOS6522<KeyboardVIA> {
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<Machine>, 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<Machine>::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<MOS::MOS6560> _mos6560;
UserPortVIA _userPortVIA;
KeyboardVIA _keyboardVIA;
};
}
#endif /* Vic20_hpp */

View File

@ -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 = "<group>"; };
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
4B886FF11D03B517004291C3 /* Vic20.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Vic20.hpp; path = "Vic-20/Vic20.hpp"; sourceTree = "<group>"; };
4B886FF31D03B61E004291C3 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
4B886FF41D03B61E004291C3 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = "<group>"; };
4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; };
4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = "<group>"; };
@ -676,9 +686,12 @@
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
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 = "<group>"; };
4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = "<group>"; };
4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = "<group>"; };
4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; };
4BC9DF461D04565200F44158 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = "<group>"; };
4BC9DF4D1D04691600F44158 /* 6560.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6560.cpp; sourceTree = "<group>"; };
4BC9DF4E1D04691600F44158 /* 6560.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6560.hpp; sourceTree = "<group>"; };
4BCA98C11D065CA20062F44C /* 6522.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6522.cpp; sourceTree = "<group>"; };
4BCA98C21D065CA20062F44C /* 6522.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6522.hpp; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -848,6 +866,15 @@
path = Formats;
sourceTree = "<group>";
};
4B886FF61D03B632004291C3 /* Vic-20 */ = {
isa = PBXGroup;
children = (
4B886FF01D03B517004291C3 /* Vic20.cpp */,
4B886FF11D03B517004291C3 /* Vic20.hpp */,
);
name = "Vic-20";
sourceTree = "<group>";
};
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 = "<group>";
};
4BC9DF4A1D04691600F44158 /* Components */ = {
isa = PBXGroup;
children = (
4BC9DF4B1D04691600F44158 /* 6522 */,
4BC9DF4C1D04691600F44158 /* 6560 */,
);
name = Components;
path = ../../Components;
sourceTree = "<group>";
};
4BC9DF4B1D04691600F44158 /* 6522 */ = {
isa = PBXGroup;
children = (
4BCA98C11D065CA20062F44C /* 6522.cpp */,
4BCA98C21D065CA20062F44C /* 6522.hpp */,
);
path = 6522;
sourceTree = "<group>";
};
4BC9DF4C1D04691600F44158 /* 6560 */ = {
isa = PBXGroup;
children = (
4BC9DF4D1D04691600F44158 /* 6560.cpp */,
4BC9DF4E1D04691600F44158 /* 6560.hpp */,
);
path = 6560;
sourceTree = "<group>";
};
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
isa = PBXGroup;
children = (
4BE5F85B1C3E1C2500C43F01 /* Electron */,
4BC9DF441D044FCA00F44158 /* ROMImages */,
);
path = Resources;
sourceTree = "<group>";
};
4BE5F85B1C3E1C2500C43F01 /* Electron */ = {
isa = PBXGroup;
children = (
4BCB70B31C947DDC005B1712 /* plus1.rom */,
4BE5F85C1C3E1C2500C43F01 /* basic.rom */,
4BE5F85D1C3E1C2500C43F01 /* os.rom */,
);
path = Electron;
sourceTree = "<group>";
};
/* 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 = "<group>";
};
4B73C71B1D036C030074D992 /* Vic20Document.xib */ = {
isa = PBXVariantGroup;
children = (
4B73C71C1D036C030074D992 /* Base */,
);
name = Vic20Document.xib;
sourceTree = "<group>";
};
4BB73EA51B587A5100552FC2 /* Atari2600Document.xib */ = {
isa = PBXVariantGroup;
children = (

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
</dependencies>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="Atari2600Document" customModule="Clock_Signal" customModuleProvider="target">
<connections>
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="xOd-HO-29H" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="133" y="235" width="400" height="300"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<value key="minSize" type="size" width="228" height="171"/>
<view key="contentView" id="gIp-Ho-8D9">
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<openGLView useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
<rect key="frame" x="0.0" y="0.0" width="400" height="300"/>
</openGLView>
</subviews>
<constraints>
<constraint firstItem="DEG-fq-cjd" firstAttribute="centerX" secondItem="gIp-Ho-8D9" secondAttribute="centerX" id="ES5-nL-N3h"/>
<constraint firstItem="DEG-fq-cjd" firstAttribute="height" secondItem="gIp-Ho-8D9" secondAttribute="height" id="YoB-qI-LFX"/>
<constraint firstItem="DEG-fq-cjd" firstAttribute="centerY" secondItem="gIp-Ho-8D9" secondAttribute="centerY" id="d5Y-3a-CEI"/>
<constraint firstItem="DEG-fq-cjd" firstAttribute="width" secondItem="gIp-Ho-8D9" secondAttribute="width" id="mYS-bH-DST"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-x8E"/>
</connections>
</window>
</objects>
</document>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -74,6 +74,20 @@
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).ElectronDocument</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>prg</string>
</array>
<key>CFBundleTypeName</key>
<string>Vic-20 Cartridge</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<integer>0</integer>
<key>NSDocumentClass</key>
<string>$(PRODUCT_MODULE_NAME).Vic20Document</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>

View File

@ -10,7 +10,7 @@
@import AudioToolbox;
#define AudioQueueNumAudioBuffers 4
#define AudioQueueStreamLength 1024
#define AudioQueueStreamLength 4096
#define AudioQueueBufferLength 512
enum {

View File

@ -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 <CSKeyboardMachine>
- (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;

View File

@ -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 <NSObject>
- (void)setKey:(uint16_t)key isPressed:(BOOL)isPressed;
- (void)clearAllKeys;
@end

View File

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

View File

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

View File

@ -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 <CSKeyboardMachine>
- (void)setKernelROM:(nonnull NSData *)rom;
- (void)setBASICROM:(nonnull NSData *)rom;
- (void)setCharactersROM:(nonnull NSData *)rom;
- (void)setPRG:(nonnull NSData *)prg;
@end

View File

@ -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<NSNumber *, NSNumber *> *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

View File

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

View File

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

View File

@ -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 T> 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 T> class Filter: public Speaker {
_buffer_in_progress_pointer = 0;
_stepper = std::unique_ptr<SignalProcessing::Stepper>(new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second));
_filter = std::unique_ptr<SignalProcessing::FIRFilter>(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<SignalProcessing::FIRFilter>(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<int16_t>(new int16_t[_number_of_taps]);
_input_buffer_depth = 0;

View File

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

View File

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