mirror of
https://github.com/TomHarte/CLK.git
synced 2025-02-09 02:31:22 +00:00
Edging towards audio output; the speaker is given appropriate input and output rates, and then updated with current divider and enabled/disabled status.
This commit is contained in:
parent
d28abdc037
commit
d9a7ef9e46
@ -20,7 +20,9 @@ static const int crt_cycles_per_line = crt_cycles_multiplier * cycles_per_line;
|
|||||||
Machine::Machine() :
|
Machine::Machine() :
|
||||||
_interruptControl(0),
|
_interruptControl(0),
|
||||||
_frameCycles(0),
|
_frameCycles(0),
|
||||||
_outputPosition(0),
|
_displayOutputPosition(0),
|
||||||
|
_audioOutputPosition(0),
|
||||||
|
_audioOutputPositionError(0),
|
||||||
_currentOutputLine(0)
|
_currentOutputLine(0)
|
||||||
{
|
{
|
||||||
memset(_keyStates, 0, sizeof(_keyStates));
|
memset(_keyStates, 0, sizeof(_keyStates));
|
||||||
@ -29,6 +31,8 @@ Machine::Machine() :
|
|||||||
_interruptStatus = 0x02;
|
_interruptStatus = 0x02;
|
||||||
for(int c = 0; c < 16; c++)
|
for(int c = 0; c < 16; c++)
|
||||||
memset(_roms[c], 0xff, 16384);
|
memset(_roms[c], 0xff, 16384);
|
||||||
|
|
||||||
|
_speaker.set_input_rate(62500);
|
||||||
setup6502();
|
setup6502();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,13 +136,23 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 0x6:
|
case 0x6:
|
||||||
printf("Counter\n");
|
if(!isReadOperation(operation))
|
||||||
|
{
|
||||||
|
if(_speaker.is_enabled)
|
||||||
|
update_audio();
|
||||||
|
_speaker.divider = *value;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 0x7:
|
case 0x7:
|
||||||
if(!isReadOperation(operation))
|
if(!isReadOperation(operation))
|
||||||
{
|
{
|
||||||
_screenMode = ((*value) >> 3)&7;
|
// update screen mode
|
||||||
if(_screenMode == 7) _screenMode = 4;
|
uint8_t new_screen_mode = ((*value) >> 3)&7;
|
||||||
|
if(new_screen_mode == 7) new_screen_mode = 4;
|
||||||
|
if(new_screen_mode != _screenMode)
|
||||||
|
{
|
||||||
|
update_display();
|
||||||
|
_screenMode = new_screen_mode;
|
||||||
switch(_screenMode)
|
switch(_screenMode)
|
||||||
{
|
{
|
||||||
case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break;
|
case 0: case 1: case 2: _screenModeBaseAddress = 0x3000; break;
|
||||||
@ -146,7 +160,17 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
case 4: case 5: _screenModeBaseAddress = 0x5800; break;
|
case 4: case 5: _screenModeBaseAddress = 0x5800; break;
|
||||||
case 6: _screenModeBaseAddress = 0x6000; break;
|
case 6: _screenModeBaseAddress = 0x6000; break;
|
||||||
}
|
}
|
||||||
printf("Misc. control\n");
|
}
|
||||||
|
|
||||||
|
// update speaker mode
|
||||||
|
bool new_speaker_is_enabled = (*value & 6) == 2;
|
||||||
|
if(new_speaker_is_enabled != _speaker.is_enabled)
|
||||||
|
{
|
||||||
|
update_audio();
|
||||||
|
_speaker.is_enabled = new_speaker_is_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: tape mode, tape motor, caps lock LED
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -222,15 +246,36 @@ unsigned int Machine::perform_bus_operation(CPU6502::BusOperation operation, uin
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
_frameCycles += cycles;
|
_frameCycles += cycles;
|
||||||
if(_frameCycles == cycles_per_frame)
|
switch(_frameCycles)
|
||||||
{
|
{
|
||||||
|
case 64*128:
|
||||||
|
update_audio();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 128*128:
|
||||||
|
update_audio();
|
||||||
|
signal_interrupt(InterruptRealTimeClock);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 196*128:
|
||||||
|
update_audio();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 284*128:
|
||||||
|
update_audio();
|
||||||
|
signal_interrupt(InterruptDisplayEnd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case cycles_per_frame:
|
||||||
update_display();
|
update_display();
|
||||||
|
update_audio();
|
||||||
_frameCycles = 0;
|
_frameCycles = 0;
|
||||||
_outputPosition = 0;
|
_displayOutputPosition = 0;
|
||||||
|
_audioOutputPosition = 0;
|
||||||
_currentOutputLine = 0;
|
_currentOutputLine = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
if(_frameCycles == 128*128) signal_interrupt(InterruptRealTimeClock);
|
|
||||||
if(_frameCycles == 284*128) signal_interrupt(InterruptDisplayEnd);
|
|
||||||
|
|
||||||
return cycles;
|
return cycles;
|
||||||
}
|
}
|
||||||
@ -266,6 +311,14 @@ inline void Machine::evaluate_interrupts()
|
|||||||
set_irq_line(_interruptStatus & 1);
|
set_irq_line(_interruptStatus & 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void Machine::update_audio()
|
||||||
|
{
|
||||||
|
int difference = _frameCycles - _audioOutputPosition;
|
||||||
|
_audioOutputPosition = _frameCycles;
|
||||||
|
_speaker.run_for_cycles((_audioOutputPositionError + difference) >> 5);
|
||||||
|
_audioOutputPositionError = (_audioOutputPositionError + difference)&31;
|
||||||
|
}
|
||||||
|
|
||||||
inline void Machine::update_display()
|
inline void Machine::update_display()
|
||||||
{
|
{
|
||||||
const int lines_of_hsync = 3;
|
const int lines_of_hsync = 3;
|
||||||
@ -275,26 +328,26 @@ inline void Machine::update_display()
|
|||||||
if(_frameCycles >= end_of_hsync)
|
if(_frameCycles >= end_of_hsync)
|
||||||
{
|
{
|
||||||
// assert sync for the first three lines of the display, with a break at the end for horizontal alignment
|
// assert sync for the first three lines of the display, with a break at the end for horizontal alignment
|
||||||
if(_outputPosition < end_of_hsync)
|
if(_displayOutputPosition < end_of_hsync)
|
||||||
{
|
{
|
||||||
for(int c = 0; c < lines_of_hsync; c++)
|
for(int c = 0; c < lines_of_hsync; c++)
|
||||||
{
|
{
|
||||||
_crt->output_sync(119 * crt_cycles_multiplier);
|
_crt->output_sync(119 * crt_cycles_multiplier);
|
||||||
_crt->output_blank(9 * crt_cycles_multiplier);
|
_crt->output_blank(9 * crt_cycles_multiplier);
|
||||||
}
|
}
|
||||||
_outputPosition = end_of_hsync;
|
_displayOutputPosition = end_of_hsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(_outputPosition >= end_of_hsync && _outputPosition < _frameCycles)
|
while(_displayOutputPosition >= end_of_hsync && _displayOutputPosition < _frameCycles)
|
||||||
{
|
{
|
||||||
const int current_line = _outputPosition >> 7;
|
const int current_line = _displayOutputPosition >> 7;
|
||||||
const int line_position = _outputPosition & 127;
|
const int line_position = _displayOutputPosition & 127;
|
||||||
|
|
||||||
// all lines then start with 9 cycles of sync
|
// all lines then start with 9 cycles of sync
|
||||||
if(!line_position)
|
if(!line_position)
|
||||||
{
|
{
|
||||||
_crt->output_sync(9 * crt_cycles_multiplier);
|
_crt->output_sync(9 * crt_cycles_multiplier);
|
||||||
_outputPosition += 9;
|
_displayOutputPosition += 9;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -308,7 +361,7 @@ inline void Machine::update_display()
|
|||||||
if(line_position == 9)
|
if(line_position == 9)
|
||||||
{
|
{
|
||||||
_crt->output_blank(119 * crt_cycles_multiplier);
|
_crt->output_blank(119 * crt_cycles_multiplier);
|
||||||
_outputPosition += 119;
|
_displayOutputPosition += 119;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -317,7 +370,7 @@ inline void Machine::update_display()
|
|||||||
if(line_position == 9)
|
if(line_position == 9)
|
||||||
{
|
{
|
||||||
_crt->output_blank(15 * crt_cycles_multiplier);
|
_crt->output_blank(15 * crt_cycles_multiplier);
|
||||||
_outputPosition += 15;
|
_displayOutputPosition += 15;
|
||||||
|
|
||||||
_crt->allocate_write_area(80 * crt_cycles_multiplier);
|
_crt->allocate_write_area(80 * crt_cycles_multiplier);
|
||||||
_currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0);
|
_currentLine = (uint8_t *)_crt->get_write_target_for_buffer(0);
|
||||||
@ -392,7 +445,7 @@ inline void Machine::update_display()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_outputPosition++;
|
_displayOutputPosition++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(line_position == 104)
|
if(line_position == 104)
|
||||||
@ -408,7 +461,7 @@ inline void Machine::update_display()
|
|||||||
_currentLine = nullptr;
|
_currentLine = nullptr;
|
||||||
_crt->output_data(80 * crt_cycles_multiplier);
|
_crt->output_data(80 * crt_cycles_multiplier);
|
||||||
_crt->output_blank(24 * crt_cycles_multiplier);
|
_crt->output_blank(24 * crt_cycles_multiplier);
|
||||||
_outputPosition += 24;
|
_displayOutputPosition += 24;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,7 @@ class Machine: public CPU6502::Processor<Machine> {
|
|||||||
void set_key_state(Key key, bool isPressed);
|
void set_key_state(Key key, bool isPressed);
|
||||||
|
|
||||||
Outputs::CRT *get_crt() { return _crt; }
|
Outputs::CRT *get_crt() { return _crt; }
|
||||||
|
Outputs::Speaker *get_speaker() { return &_speaker; }
|
||||||
const char *get_signal_decoder();
|
const char *get_signal_decoder();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -84,17 +85,22 @@ class Machine: public CPU6502::Processor<Machine> {
|
|||||||
|
|
||||||
Outputs::CRT *_crt;
|
Outputs::CRT *_crt;
|
||||||
|
|
||||||
int _frameCycles, _outputPosition;
|
int _frameCycles, _displayOutputPosition, _audioOutputPosition, _audioOutputPositionError;
|
||||||
|
|
||||||
uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress;
|
uint16_t _startScreenAddress, _startLineAddress, _currentScreenAddress;
|
||||||
int _currentOutputLine;
|
int _currentOutputLine;
|
||||||
uint8_t *_currentLine;
|
uint8_t *_currentLine;
|
||||||
|
|
||||||
inline void update_display();
|
inline void update_display();
|
||||||
|
inline void update_audio();
|
||||||
inline void signal_interrupt(Interrupt interrupt);
|
inline void signal_interrupt(Interrupt interrupt);
|
||||||
inline void evaluate_interrupts();
|
inline void evaluate_interrupts();
|
||||||
|
|
||||||
class Speaker: public ::Speaker::Filter<Speaker> {
|
class Speaker: public ::Outputs::Filter<Speaker> {
|
||||||
};
|
public:
|
||||||
|
uint8_t divider;
|
||||||
|
bool is_enabled;
|
||||||
|
} _speaker;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,11 @@
|
|||||||
_electron.get_crt()->set_delegate(delegate);
|
_electron.get_crt()->set_delegate(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate {
|
||||||
|
_electron.get_speaker()->set_output_rate(44100, 512);
|
||||||
|
_electron.get_speaker()->set_output_quality(15);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setView:(CSCathodeRayView *)view {
|
- (void)setView:(CSCathodeRayView *)view {
|
||||||
[super setView:view];
|
[super setView:view];
|
||||||
[view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB];
|
[view setSignalDecoder:[NSString stringWithUTF8String:_electron.get_signal_decoder()] type:CSCathodeRayViewSignalTypeRGB];
|
||||||
|
@ -7,14 +7,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "CSMachine.h"
|
#import "CSMachine.h"
|
||||||
#include "../../../../Outputs/CRT.hpp"
|
#include "CRT.hpp"
|
||||||
|
#include "Speaker.hpp"
|
||||||
|
|
||||||
@interface CSMachine (Subclassing)
|
@interface CSMachine (Subclassing)
|
||||||
|
|
||||||
|
- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate;
|
||||||
- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate;
|
- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate;
|
||||||
- (void)doRunForNumberOfCycles:(int)numberOfCycles;
|
|
||||||
- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync;
|
|
||||||
|
|
||||||
|
- (void)doRunForNumberOfCycles:(int)numberOfCycles;
|
||||||
- (void)perform:(dispatch_block_t)action;
|
- (void)perform:(dispatch_block_t)action;
|
||||||
|
|
||||||
|
- (void)crt:(Outputs::CRT *)crt didEndFrame:(CRTFrame *)frame didDetectVSync:(BOOL)didDetectVSync;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -53,6 +53,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) {
|
|||||||
if (self) {
|
if (self) {
|
||||||
_crtDelegate.machine = self;
|
_crtDelegate.machine = self;
|
||||||
[self setCRTDelegate:&_crtDelegate];
|
[self setCRTDelegate:&_crtDelegate];
|
||||||
|
[self setSpeakerDelegate:nil];
|
||||||
_serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL);
|
_serialDispatchQueue = dispatch_queue_create("Machine queue", DISPATCH_QUEUE_SERIAL);
|
||||||
_runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped];
|
_runningLock = [[NSConditionLock alloc] initWithCondition:CSMachineRunningStateStopped];
|
||||||
}
|
}
|
||||||
@ -61,6 +62,7 @@ typedef NS_ENUM(NSInteger, CSAtari2600RunningState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {}
|
- (void)setCRTDelegate:(Outputs::CRT::Delegate *)delegate {}
|
||||||
|
- (void)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate {}
|
||||||
- (void)doRunForNumberOfCycles:(int)numberOfCycles {}
|
- (void)doRunForNumberOfCycles:(int)numberOfCycles {}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
|
|
||||||
#include "Speaker.hpp"
|
#include "Speaker.hpp"
|
||||||
|
|
||||||
using namespace Speaker;
|
using namespace Outputs;
|
||||||
|
|
||||||
|
@ -10,17 +10,18 @@
|
|||||||
#define Speaker_hpp
|
#define Speaker_hpp
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "../SignalProcessing/Stepper.hpp"
|
#include "../SignalProcessing/Stepper.hpp"
|
||||||
|
|
||||||
namespace Speaker {
|
namespace Outputs {
|
||||||
|
|
||||||
class Delegate {
|
class Speaker {
|
||||||
|
public:
|
||||||
|
class Delegate {
|
||||||
public:
|
public:
|
||||||
virtual void speaker_did_complete_samples(uint8_t *buffer);
|
virtual void speaker_did_complete_samples(uint8_t *buffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T> class Filter {
|
|
||||||
public:
|
|
||||||
void set_output_rate(int cycles_per_second, int buffer_size)
|
void set_output_rate(int cycles_per_second, int buffer_size)
|
||||||
{
|
{
|
||||||
_output_cycles_per_second = cycles_per_second;
|
_output_cycles_per_second = cycles_per_second;
|
||||||
@ -50,18 +51,7 @@ template <class T> class Filter {
|
|||||||
set_needs_updated_filter_coefficients();
|
set_needs_updated_filter_coefficients();
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_for_cycles(int input_cycles)
|
protected:
|
||||||
{
|
|
||||||
if(_coefficients_are_dirty) update_filter_coefficients();
|
|
||||||
|
|
||||||
// point sample for now, as a temporary measure
|
|
||||||
while(input_cycles--)
|
|
||||||
{
|
|
||||||
static_cast<T *>(this)->perform_bus_operation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint16_t *_buffer_in_progress;
|
uint16_t *_buffer_in_progress;
|
||||||
int _buffer_size;
|
int _buffer_size;
|
||||||
int _buffer_in_progress_pointer;
|
int _buffer_in_progress_pointer;
|
||||||
@ -76,14 +66,31 @@ template <class T> class Filter {
|
|||||||
{
|
{
|
||||||
_coefficients_are_dirty = true;
|
_coefficients_are_dirty = true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T> class Filter: public Speaker {
|
||||||
|
public:
|
||||||
|
void run_for_cycles(int input_cycles)
|
||||||
|
{
|
||||||
|
if(_coefficients_are_dirty) update_filter_coefficients();
|
||||||
|
|
||||||
|
// point sample for now, as a temporary measure
|
||||||
|
while(input_cycles--)
|
||||||
|
{
|
||||||
|
// static_cast<T *>(this)->perform_bus_operation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SignalProcessing::Stepper *_stepper;
|
||||||
|
|
||||||
void update_filter_coefficients()
|
void update_filter_coefficients()
|
||||||
{
|
{
|
||||||
_coefficients_are_dirty = false;
|
_coefficients_are_dirty = false;
|
||||||
_buffer_in_progress_pointer = 0;
|
_buffer_in_progress_pointer = 0;
|
||||||
|
|
||||||
delete[] _stepper;
|
delete _stepper;
|
||||||
_stepper = Stepper(_input_cycles_per_second, _output_cycles_per_second);
|
_stepper = new SignalProcessing::Stepper((uint64_t)_input_cycles_per_second, (uint64_t)_output_cycles_per_second);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user