mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-27 06:35:04 +00:00
Merge pull request #18 from TomHarte/Vic-20
Adds basic NTSC VIC-20 emulation.
This commit is contained in:
commit
8c82f6ae37
4
.gitignore
vendored
4
.gitignore
vendored
@ -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
12
Components/6522/6522.cpp
Normal 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
233
Components/6522/6522.hpp
Normal 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
440
Components/6560/6560.cpp
Normal 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 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
|
86
Components/6560/6560.hpp
Normal file
86
Components/6560/6560.hpp
Normal 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 */
|
@ -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
136
Machines/Vic-20/Vic20.cpp
Normal 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
151
Machines/Vic-20/Vic20.hpp
Normal 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 */
|
@ -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 = (
|
||||
|
@ -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>
|
||||
|
42
OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib
Normal file
42
OSBindings/Mac/Clock Signal/Base.lproj/Vic20Document.xib
Normal 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>
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
47
OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift
Normal file
47
OSBindings/Mac/Clock Signal/Documents/Vic20Document.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -10,7 +10,7 @@
|
||||
@import AudioToolbox;
|
||||
|
||||
#define AudioQueueNumAudioBuffers 4
|
||||
#define AudioQueueStreamLength 1024
|
||||
#define AudioQueueStreamLength 4096
|
||||
#define AudioQueueBufferLength 512
|
||||
|
||||
enum {
|
||||
|
@ -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;
|
||||
|
||||
|
16
OSBindings/Mac/Clock Signal/Wrappers/CSKeyboardMachine.h
Normal file
16
OSBindings/Mac/Clock Signal/Wrappers/CSKeyboardMachine.h
Normal 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
|
@ -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;
|
||||
|
@ -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
|
||||
|
19
OSBindings/Mac/Clock Signal/Wrappers/CSVic20.h
Normal file
19
OSBindings/Mac/Clock Signal/Wrappers/CSVic20.h
Normal 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
|
122
OSBindings/Mac/Clock Signal/Wrappers/CSVic20.mm
Normal file
122
OSBindings/Mac/Clock Signal/Wrappers/CSVic20.mm
Normal 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
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
16
ROMImages/Electron/readme.txt
Normal file
16
ROMImages/Electron/readme.txt
Normal 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
|
17
ROMImages/Vic20/readme.txt
Normal file
17
ROMImages/Vic20/readme.txt
Normal 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
|
Loading…
x
Reference in New Issue
Block a user