// // 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)), _speaker(new Speaker), _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 = cos(phase + phaseOffset);" "return mix(y, step(yC, 14) * chroma, amplitude);" "}"); // default to NTSC set_output_mode(OutputMode::NTSC); // 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_output_mode(OutputMode output_mode) { 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, }; uint8_t *chrominances; Outputs::CRT::DisplayType display_type; switch(output_mode) { case OutputMode::PAL: chrominances = pal_chrominances; display_type = Outputs::CRT::PAL50; _timing.cycles_per_line = 71; _timing.line_counter_increment_offset = 0; _timing.lines_per_progressive_field = 312; _timing.supports_interlacing = false; break; case OutputMode::NTSC: chrominances = ntsc_chrominances; display_type = Outputs::CRT::NTSC60; _timing.cycles_per_line = 65; _timing.line_counter_increment_offset = 65 - 33; // TODO: this is a bit of a hack; separate vertical and horizontal counting _timing.lines_per_progressive_field = 261; _timing.supports_interlacing = true; break; } _crt->set_new_display_type((unsigned int)(_timing.cycles_per_line*4), display_type); for(int c = 0; c < 16; c++) { _colours[c] = (uint8_t)((luminances[c] << 4) | chrominances[c]); } } // TODO: set clock rate void MOS6560::set_register(int address, uint8_t value) { address &= 0xf; _registers.direct_values[address] = value; switch(address) { case 0x0: _registers.interlaced = !!(value&0x80) && _timing.supports_interlacing; _registers.first_column_location = value & 0x7f; break; case 0x1: _registers.first_row_location = value; break; case 0x2: _registers.number_of_columns = value & 0x7f; _registers.video_matrix_start_address = (uint16_t)((_registers.video_matrix_start_address & 0x3c00) | ((value & 0x80) << 2)); break; case 0x3: _registers.number_of_rows = (value >> 1)&0x3f; _registers.tall_characters = !!(value&0x01); break; case 0x5: _registers.character_cell_start_address = (uint16_t)((value & 0x0f) << 10); _registers.video_matrix_start_address = (uint16_t)((_registers.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(); _registers.auxiliary_colour = _colours[value >> 4]; _speaker->set_volume(value & 0xf); break; case 0xf: { uint8_t new_border_colour = _colours[value & 0x07]; if(_this_state == State::Border && new_border_colour != _registers.borderColour) { output_border(_cycles_in_state * 4); _cycles_in_state = 0; } _registers.invertedCells = !((value >> 3)&1); _registers.borderColour = new_border_colour; _registers.backgroundColour = _colours[value >> 4]; } break; // TODO: the lightpen, etc default: break; } } uint8_t MOS6560::get_register(int address) { address &= 0xf; int current_line = (_full_frame_counter + _timing.line_counter_increment_offset) / _timing.cycles_per_line; switch(address) { default: return _registers.direct_values[address]; case 0x03: return (uint8_t)(current_line << 7) | (_registers.direct_values[3] & 0x7f); case 0x04: return (current_line >> 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 = _registers.borderColour; _crt->output_level(number_of_cycles); // _crt->output_blank(number_of_cycles); } uint16_t MOS6560::get_address() { // keep track of the amount of time since the speaker was updated; lazy updates are applied _cycles_since_speaker_update++; // keep an old copy of the vertical count because that test is a cycle later than the actual changes int previous_vertical_counter = _vertical_counter; // keep track of internal time relative to this scanline _horizontal_counter++; _full_frame_counter++; if(_horizontal_counter == _timing.cycles_per_line) { if(_horizontal_drawing_latch) { _current_character_row++; if( (_current_character_row == 16) || (_current_character_row == 8 && !_registers.tall_characters) ) { _current_character_row = 0; _current_row++; } _pixel_line_cycle = -1; _columns_this_line = -1; _column_counter = -1; } _horizontal_counter = 0; _horizontal_drawing_latch = false; _vertical_counter ++; if(_vertical_counter == (_registers.interlaced ? (_is_odd_frame ? 262 : 263) : _timing.lines_per_progressive_field)) { _vertical_counter = 0; _full_frame_counter = 0; _is_odd_frame ^= true; _current_row = 0; _rows_this_field = -1; _vertical_drawing_latch = false; _base_video_matrix_address_counter = 0; _current_character_row = 0; } } // check for vertical starting events _vertical_drawing_latch |= _registers.first_row_location == (previous_vertical_counter >> 1); _horizontal_drawing_latch |= _vertical_drawing_latch && (_horizontal_counter == _registers.first_column_location); if(_pixel_line_cycle >= 0) _pixel_line_cycle++; switch(_pixel_line_cycle) { case -1: if(_horizontal_drawing_latch) { _pixel_line_cycle = 0; _video_matrix_address_counter = _base_video_matrix_address_counter; } break; case 1: _columns_this_line = _registers.number_of_columns; break; case 2: if(_rows_this_field < 0) _rows_this_field = _registers.number_of_rows; break; case 3: if(_current_row < _rows_this_field) _column_counter = 0; break; } uint16_t fetch_address = 0x1c; if(_column_counter >= 0 && _column_counter < _columns_this_line*2) { if(_column_counter&1) { fetch_address = _registers.character_cell_start_address + (_character_code*(_registers.tall_characters ? 16 : 8)) + _current_character_row; } else { fetch_address = (uint16_t)(_registers.video_matrix_start_address + _video_matrix_address_counter); _video_matrix_address_counter++; if( (_current_character_row == 15) || (_current_character_row == 7 && !_registers.tall_characters) ) { _base_video_matrix_address_counter = _video_matrix_address_counter; } } } return fetch_address; } void MOS6560::set_graphics_value(uint8_t value, uint8_t colour_value) { // TODO: there should be a further two-cycle delay on pixels being output; the reverse bit should // divide the byte it is set for 3:1 and then continue as usual. // determine output state; colour burst and sync timing are currently a guess if(_horizontal_counter > _timing.cycles_per_line-4) _this_state = State::ColourBurst; else if(_horizontal_counter > _timing.cycles_per_line-7) _this_state = State::Sync; else { _this_state = (_column_counter >= 0 && _column_counter < _columns_this_line*2) ? State::Pixels : State::Border; } // apply vertical sync if( (_vertical_counter < 3 && (_is_odd_frame || !_registers.interlaced)) || (_registers.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++; 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)) { uint8_t colours[2]; if(_registers.invertedCells) { colours[0] = cell_colour; colours[1] = _registers.backgroundColour; } else { colours[0] = _registers.backgroundColour; colours[1] = cell_colour; } pixel_pointer[0] = colours[(_character_value >> 7)&1]; pixel_pointer[1] = colours[(_character_value >> 6)&1]; pixel_pointer[2] = colours[(_character_value >> 5)&1]; pixel_pointer[3] = colours[(_character_value >> 4)&1]; pixel_pointer[4] = colours[(_character_value >> 3)&1]; pixel_pointer[5] = colours[(_character_value >> 2)&1]; pixel_pointer[6] = colours[(_character_value >> 1)&1]; pixel_pointer[7] = colours[(_character_value >> 0)&1]; } else { uint8_t colours[4] = {_registers.backgroundColour, _registers.borderColour, cell_colour, _registers.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; } _column_counter++; } } void MOS6560::update_audio() { _speaker->run_for_cycles(_cycles_since_speaker_update >> 2); _cycles_since_speaker_update &= 3; } #pragma mark - Audio MOS6560::Speaker::Speaker() : _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 MOS6560::Speaker::set_volume(uint8_t volume) { _volume = volume; } void MOS6560::Speaker::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] = (_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 MOS6560::Speaker::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); // this sums the output of all three sounds channels plus a DC offset for volume; // TODO: what's the real ratio of this stuff? 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 + _volume * 44; } } void MOS6560::Speaker::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