mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 16:31:31 +00:00
Merge pull request #21 from TomHarte/LowLatencyAudio
Attempts to apply some sort of quality-of-service guarantee to audio
This commit is contained in:
commit
d6cb4fe15c
@ -68,6 +68,8 @@ class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine {
|
||||
virtual Outputs::CRT::CRT *get_crt() { return _crt; }
|
||||
virtual Outputs::Speaker *get_speaker() { return &_speaker; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
virtual double get_clock_rate() { return 1194720; }
|
||||
// TODO: different rate for PAL
|
||||
|
||||
private:
|
||||
uint8_t *_rom, *_romPages[4], _ram[128];
|
||||
|
@ -23,6 +23,16 @@ class Machine {
|
||||
virtual Outputs::Speaker *get_speaker() = 0;
|
||||
|
||||
virtual void run_for_cycles(int number_of_cycles) = 0;
|
||||
|
||||
virtual double get_clock_rate() = 0;
|
||||
class Delegate {
|
||||
public:
|
||||
virtual void machine_did_change_clock_rate(Machine *machine) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *delegate) { this->delegate = delegate; }
|
||||
|
||||
protected:
|
||||
Delegate *delegate;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -165,6 +165,7 @@ class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine, T
|
||||
virtual Outputs::CRT::CRT *get_crt() { return _crt.get(); }
|
||||
virtual Outputs::Speaker *get_speaker() { return &_speaker; }
|
||||
virtual void run_for_cycles(int number_of_cycles) { CPU6502::Processor<Machine>::run_for_cycles(number_of_cycles); }
|
||||
virtual double get_clock_rate() { return 2000000; }
|
||||
|
||||
// to satisfy Tape::Delegate
|
||||
virtual void tape_did_change_interrupt_status(Tape *tape);
|
||||
|
@ -108,6 +108,8 @@ class Machine: public CPU6502::Processor<Machine>, public CRTMachine::Machine, p
|
||||
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); }
|
||||
virtual double get_clock_rate() { return 1022727; }
|
||||
// TODO: or 1108405 for PAL; see http://www.antimon.org/dl/c64/code/stable.txt
|
||||
|
||||
// to satisfy MOS::MOS6522::Delegate
|
||||
virtual void mos6522_did_change_interrupt_status(void *mos6522);
|
||||
|
@ -8,19 +8,20 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
|
||||
4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */; };
|
||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145D1B5887A600E04248 /* CPU6502.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414571B58879D00E04248 /* CPU6502.cpp */; };
|
||||
4B14145E1B5887AA00E04248 /* CPU6502AllRAM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414591B58879D00E04248 /* CPU6502AllRAM.cpp */; };
|
||||
4B1414601B58885000E04248 /* WolfgangLorenzTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */; };
|
||||
4B1414621B58888700E04248 /* KlausDormannTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1414611B58888700E04248 /* KlausDormannTests.swift */; };
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2409531C45AB05004DA684 /* Speaker.cpp */; };
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
|
||||
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; };
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
|
||||
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539C1D117D36003C6002 /* CSElectron.mm */; };
|
||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539E1D117D36003C6002 /* CSVic20.mm */; };
|
||||
4B2E2D951C399D1200138695 /* ElectronDocument.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2E2D931C399D1200138695 /* ElectronDocument.xib */; };
|
||||
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
|
||||
4B55CE4B1C3B3B0C0093A61B /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */; };
|
||||
4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */; };
|
||||
4B55CE541C3B7ABF0093A61B /* CSElectron.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE531C3B7ABF0093A61B /* CSElectron.mm */; };
|
||||
4B55CE581C3B7D360093A61B /* Atari2600Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE561C3B7D360093A61B /* Atari2600Document.swift */; };
|
||||
4B55CE591C3B7D360093A61B /* ElectronDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE571C3B7D360093A61B /* ElectronDocument.swift */; };
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
|
||||
@ -31,7 +32,6 @@
|
||||
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 */; };
|
||||
@ -318,6 +318,7 @@
|
||||
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 */; };
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -342,8 +343,6 @@
|
||||
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = "<group>"; };
|
||||
4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = "<group>"; };
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ClockSignal-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
4B1414571B58879D00E04248 /* CPU6502.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CPU6502.cpp; sourceTree = "<group>"; };
|
||||
4B1414581B58879D00E04248 /* CPU6502.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502.hpp; sourceTree = "<group>"; };
|
||||
@ -354,19 +353,25 @@
|
||||
4B2409531C45AB05004DA684 /* Speaker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Speaker.cpp; path = ../../Outputs/Speaker.cpp; sourceTree = "<group>"; };
|
||||
4B2409541C45AB05004DA684 /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Speaker.hpp; path = ../../Outputs/Speaker.hpp; sourceTree = "<group>"; };
|
||||
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
|
||||
4B2A53901D117D36003C6002 /* CSAudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAudioQueue.h; sourceTree = "<group>"; };
|
||||
4B2A53911D117D36003C6002 /* CSAudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSAudioQueue.m; sourceTree = "<group>"; };
|
||||
4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSKeyboardMachine.h; sourceTree = "<group>"; };
|
||||
4B2A53941D117D36003C6002 /* CSMachine+Subclassing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Subclassing.h"; sourceTree = "<group>"; };
|
||||
4B2A53951D117D36003C6002 /* CSMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMachine.h; sourceTree = "<group>"; };
|
||||
4B2A53961D117D36003C6002 /* CSMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMachine.mm; sourceTree = "<group>"; };
|
||||
4B2A53971D117D36003C6002 /* KeyCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; };
|
||||
4B2A53991D117D36003C6002 /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; };
|
||||
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = "<group>"; };
|
||||
4B2A539B1D117D36003C6002 /* CSElectron.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSElectron.h; sourceTree = "<group>"; };
|
||||
4B2A539C1D117D36003C6002 /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = "<group>"; };
|
||||
4B2A539D1D117D36003C6002 /* CSVic20.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSVic20.h; sourceTree = "<group>"; };
|
||||
4B2A539E1D117D36003C6002 /* CSVic20.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSVic20.mm; sourceTree = "<group>"; };
|
||||
4B2E2D941C399D1200138695 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ElectronDocument.xib; sourceTree = "<group>"; };
|
||||
4B2E2D971C3A06EC00138695 /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
|
||||
4B2E2D981C3A06EC00138695 /* Atari2600.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Atari2600.hpp; sourceTree = "<group>"; };
|
||||
4B2E2D991C3A06EC00138695 /* Atari2600Inputs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Atari2600Inputs.h; sourceTree = "<group>"; };
|
||||
4B2E2D9B1C3A070400138695 /* Electron.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Electron.cpp; path = Electron/Electron.cpp; sourceTree = "<group>"; };
|
||||
4B2E2D9C1C3A070400138695 /* Electron.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Electron.hpp; path = Electron/Electron.hpp; sourceTree = "<group>"; };
|
||||
4B55CE491C3B3B0C0093A61B /* CSAtari2600.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSAtari2600.h; sourceTree = "<group>"; };
|
||||
4B55CE4A1C3B3B0C0093A61B /* CSAtari2600.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAtari2600.mm; sourceTree = "<group>"; };
|
||||
4B55CE4C1C3B3BDA0093A61B /* CSMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMachine.h; sourceTree = "<group>"; };
|
||||
4B55CE4D1C3B3BDA0093A61B /* CSMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMachine.mm; sourceTree = "<group>"; };
|
||||
4B55CE4F1C3B78A80093A61B /* CSMachine+Subclassing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Subclassing.h"; sourceTree = "<group>"; };
|
||||
4B55CE521C3B7ABF0093A61B /* CSElectron.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSElectron.h; sourceTree = "<group>"; };
|
||||
4B55CE531C3B7ABF0093A61B /* CSElectron.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSElectron.mm; sourceTree = "<group>"; };
|
||||
4B55CE561C3B7D360093A61B /* Atari2600Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atari2600Document.swift; sourceTree = "<group>"; };
|
||||
4B55CE571C3B7D360093A61B /* ElectronDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElectronDocument.swift; sourceTree = "<group>"; };
|
||||
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
|
||||
@ -381,10 +386,7 @@
|
||||
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>"; };
|
||||
4BB297E01B587D8300A49093 /* 6502_functional_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = 6502_functional_test.bin; sourceTree = "<group>"; };
|
||||
4BB297E11B587D8300A49093 /* AllSuiteA.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = AllSuiteA.bin; sourceTree = "<group>"; };
|
||||
@ -687,11 +689,12 @@
|
||||
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; };
|
||||
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>"; };
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CSBestEffortUpdater.h; path = Updater/CSBestEffortUpdater.h; sourceTree = "<group>"; };
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CSBestEffortUpdater.m; path = Updater/CSBestEffortUpdater.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -765,6 +768,41 @@
|
||||
path = ../../SignalProcessing;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2A538F1D117D36003C6002 /* Audio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2A53901D117D36003C6002 /* CSAudioQueue.h */,
|
||||
4B2A53911D117D36003C6002 /* CSAudioQueue.m */,
|
||||
);
|
||||
path = Audio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2A53921D117D36003C6002 /* Machine */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2A53931D117D36003C6002 /* CSKeyboardMachine.h */,
|
||||
4B2A53941D117D36003C6002 /* CSMachine+Subclassing.h */,
|
||||
4B2A53951D117D36003C6002 /* CSMachine.h */,
|
||||
4B2A53961D117D36003C6002 /* CSMachine.mm */,
|
||||
4B2A53971D117D36003C6002 /* KeyCodes.h */,
|
||||
4B2A53981D117D36003C6002 /* Wrappers */,
|
||||
);
|
||||
path = Machine;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2A53981D117D36003C6002 /* Wrappers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
|
||||
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
|
||||
4B2A539B1D117D36003C6002 /* CSElectron.h */,
|
||||
4B2A539C1D117D36003C6002 /* CSElectron.mm */,
|
||||
4B2A539D1D117D36003C6002 /* CSVic20.h */,
|
||||
4B2A539E1D117D36003C6002 /* CSVic20.mm */,
|
||||
);
|
||||
path = Wrappers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B2E2D961C3A06EC00138695 /* Atari2600 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -794,26 +832,6 @@
|
||||
name = Outputs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B55CE481C3B3B0C0093A61B /* Wrappers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */,
|
||||
4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */,
|
||||
4B55CE491C3B3B0C0093A61B /* CSAtari2600.h */,
|
||||
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;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B55CE551C3B7D360093A61B /* Documents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1180,13 +1198,15 @@
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
|
||||
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
|
||||
4B2A538F1D117D36003C6002 /* Audio */,
|
||||
4B55CE551C3B7D360093A61B /* Documents */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4B2A53921D117D36003C6002 /* Machine */,
|
||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||
4B55CE481C3B3B0C0093A61B /* Wrappers */,
|
||||
);
|
||||
path = "Clock Signal";
|
||||
sourceTree = "<group>";
|
||||
@ -1295,6 +1315,15 @@
|
||||
path = 6560;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BD5F1961D1352A000631CD1 /* Updater */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BD5F1931D13528900631CD1 /* CSBestEffortUpdater.h */,
|
||||
4BD5F1941D13528900631CD1 /* CSBestEffortUpdater.m */,
|
||||
);
|
||||
name = Updater;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1711,31 +1740,32 @@
|
||||
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 */,
|
||||
4BD5F1951D13528900631CD1 /* CSBestEffortUpdater.m 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 */,
|
||||
4B2A53A11D117D36003C6002 /* CSAtari2600.mm in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4BBF99141C8FBA6F0075DAFB /* CRTInputBufferBuilder.cpp in Sources */,
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||
4B55CE4E1C3B3BDA0093A61B /* CSMachine.mm in Sources */,
|
||||
4B2E2D9D1C3A070400138695 /* Electron.cpp in Sources */,
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
||||
4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */,
|
||||
4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */,
|
||||
4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */,
|
||||
4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */,
|
||||
4B14145B1B58879D00E04248 /* CPU6502.cpp in Sources */,
|
||||
4B2A53A01D117D36003C6002 /* CSMachine.mm in Sources */,
|
||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */,
|
||||
4BB73EA21B587A5100552FC2 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
28
OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h
Normal file
28
OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// AudioQueue.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/01/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class CSAudioQueue;
|
||||
|
||||
@protocol CSAudioQueueDelegate
|
||||
- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue;
|
||||
@end
|
||||
|
||||
@interface CSAudioQueue : NSObject
|
||||
|
||||
- (nonnull instancetype)initWithSamplingRate:(Float64)samplingRate;
|
||||
- (void)enqueueAudioBuffer:(nonnull const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples;
|
||||
|
||||
@property (nonatomic, readonly) Float64 samplingRate;
|
||||
@property (nonatomic, weak) id<CSAudioQueueDelegate> delegate;
|
||||
|
||||
+ (Float64)preferredSamplingRate;
|
||||
@property (nonatomic, readonly) NSUInteger bufferSize;
|
||||
|
||||
@end
|
@ -6,12 +6,12 @@
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AudioQueue.h"
|
||||
#import "CSAudioQueue.h"
|
||||
@import AudioToolbox;
|
||||
|
||||
#define AudioQueueNumAudioBuffers 4
|
||||
#define AudioQueueStreamLength 4096
|
||||
#define AudioQueueBufferLength 512
|
||||
#define AudioQueueBufferMaxLength 8192
|
||||
#define AudioQueueNumAudioBuffers 3
|
||||
#define AudioQueueMaxStreamLength (AudioQueueBufferMaxLength*AudioQueueNumAudioBuffers)
|
||||
|
||||
enum {
|
||||
AudioQueueCanProceed,
|
||||
@ -19,23 +19,29 @@ enum {
|
||||
AudioQueueIsInvalidated
|
||||
};
|
||||
|
||||
@implementation AudioQueue
|
||||
@implementation CSAudioQueue
|
||||
{
|
||||
NSUInteger _bufferLength;
|
||||
NSUInteger _streamLength;
|
||||
|
||||
AudioQueueRef _audioQueue;
|
||||
AudioQueueBufferRef _audioBuffers[AudioQueueNumAudioBuffers];
|
||||
|
||||
unsigned int _audioStreamReadPosition, _audioStreamWritePosition;
|
||||
int16_t _audioStream[AudioQueueStreamLength];
|
||||
int16_t _audioStream[AudioQueueMaxStreamLength];
|
||||
|
||||
NSConditionLock *_writeLock;
|
||||
BOOL _isInvalidated;
|
||||
int _dequeuedCount;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark AudioQueue callbacks and setup; for pushing audio out
|
||||
|
||||
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||
{
|
||||
[self.delegate audioQueueDidCompleteBuffer:self];
|
||||
|
||||
[_writeLock lock];
|
||||
|
||||
const unsigned int writeLead = _audioStreamWritePosition - _audioStreamReadPosition;
|
||||
@ -44,15 +50,15 @@ enum {
|
||||
// TODO: if write lead is too great, skip some audio
|
||||
if(writeLead >= audioDataSampleSize)
|
||||
{
|
||||
size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamReadPosition % AudioQueueStreamLength);
|
||||
size_t samplesBeforeOverflow = _streamLength - (_audioStreamReadPosition % _streamLength);
|
||||
if(audioDataSampleSize <= samplesBeforeOverflow)
|
||||
{
|
||||
memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], buffer->mAudioDataByteSize);
|
||||
memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % _streamLength], buffer->mAudioDataByteSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t bytesRemaining = samplesBeforeOverflow * sizeof(int16_t);
|
||||
memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % AudioQueueStreamLength], bytesRemaining);
|
||||
memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % _streamLength], bytesRemaining);
|
||||
memcpy(buffer->mAudioData, &_audioStream[0], buffer->mAudioDataByteSize - bytesRemaining);
|
||||
}
|
||||
_audioStreamReadPosition += audioDataSampleSize;
|
||||
@ -82,7 +88,7 @@ static void audioOutputCallback(
|
||||
AudioQueueRef inAQ,
|
||||
AudioQueueBufferRef inBuffer)
|
||||
{
|
||||
[(__bridge AudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSamplingRate:(Float64)samplingRate
|
||||
@ -94,8 +100,13 @@ static void audioOutputCallback(
|
||||
_writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanProceed];
|
||||
_samplingRate = samplingRate;
|
||||
|
||||
// determine buffer sizes
|
||||
_bufferLength = AudioQueueBufferMaxLength;
|
||||
while((Float64)_bufferLength*50.0 > samplingRate) _bufferLength >>= 1;
|
||||
_streamLength = _bufferLength * AudioQueueNumAudioBuffers;
|
||||
|
||||
/*
|
||||
Describe a mono, 16bit, 44.1Khz audio format
|
||||
Describe a mono 16bit stream of the requested sampling rate
|
||||
*/
|
||||
AudioStreamBasicDescription outputDescription;
|
||||
|
||||
@ -122,7 +133,7 @@ static void audioOutputCallback(
|
||||
0,
|
||||
&_audioQueue))
|
||||
{
|
||||
UInt32 bufferBytes = AudioQueueBufferLength * sizeof(int16_t);
|
||||
UInt32 bufferBytes = (UInt32)(_bufferLength * sizeof(int16_t));
|
||||
|
||||
int c = AudioQueueNumAudioBuffers;
|
||||
while(c--)
|
||||
@ -166,18 +177,18 @@ static void audioOutputCallback(
|
||||
while(1)
|
||||
{
|
||||
[_writeLock lockWhenCondition:AudioQueueCanProceed];
|
||||
if((_audioStreamReadPosition + AudioQueueStreamLength) - _audioStreamWritePosition >= lengthInSamples)
|
||||
if((_audioStreamReadPosition + _streamLength) - _audioStreamWritePosition >= lengthInSamples)
|
||||
{
|
||||
size_t samplesBeforeOverflow = AudioQueueStreamLength - (_audioStreamWritePosition % AudioQueueStreamLength);
|
||||
size_t samplesBeforeOverflow = _streamLength - (_audioStreamWritePosition % _streamLength);
|
||||
|
||||
if(samplesBeforeOverflow < lengthInSamples)
|
||||
{
|
||||
memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, samplesBeforeOverflow * sizeof(int16_t));
|
||||
memcpy(&_audioStream[_audioStreamWritePosition % _streamLength], buffer, samplesBeforeOverflow * sizeof(int16_t));
|
||||
memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&_audioStream[_audioStreamWritePosition % AudioQueueStreamLength], buffer, lengthInSamples * sizeof(int16_t));
|
||||
memcpy(&_audioStream[_audioStreamWritePosition % _streamLength], buffer, lengthInSamples * sizeof(int16_t));
|
||||
}
|
||||
|
||||
_audioStreamWritePosition += lengthInSamples;
|
||||
@ -193,9 +204,11 @@ static void audioOutputCallback(
|
||||
|
||||
- (NSInteger)writeLockCondition
|
||||
{
|
||||
return ((_audioStreamWritePosition - _audioStreamReadPosition) < (AudioQueueStreamLength - AudioQueueBufferLength)) ? AudioQueueCanProceed : AudioQueueWait;
|
||||
return ((_audioStreamWritePosition - _audioStreamReadPosition) < (_streamLength - _bufferLength)) ? AudioQueueCanProceed : AudioQueueWait;
|
||||
}
|
||||
|
||||
#pragma mark - Sampling Rate getters
|
||||
|
||||
+ (AudioDeviceID)defaultOutputDevice
|
||||
{
|
||||
AudioObjectPropertyAddress address;
|
||||
@ -205,7 +218,7 @@ static void audioOutputCallback(
|
||||
|
||||
AudioDeviceID deviceID;
|
||||
UInt32 size = sizeof(AudioDeviceID);
|
||||
return AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &deviceID) ? 0 : deviceID;
|
||||
return AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &deviceID) ? 0 : deviceID;
|
||||
}
|
||||
|
||||
+ (Float64)preferredSamplingRate
|
||||
@ -217,7 +230,12 @@ static void audioOutputCallback(
|
||||
|
||||
Float64 samplingRate;
|
||||
UInt32 size = sizeof(Float64);
|
||||
return AudioHardwareServiceGetPropertyData([self defaultOutputDevice], &address, 0, NULL, &size, &samplingRate) ? 0.0 : samplingRate;
|
||||
return AudioObjectGetPropertyData([self defaultOutputDevice], &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &samplingRate) ? 0.0 : samplingRate;
|
||||
}
|
||||
|
||||
- (NSUInteger)bufferSize
|
||||
{
|
||||
return _bufferLength;
|
||||
}
|
||||
|
||||
@end
|
@ -10,4 +10,5 @@
|
||||
#import "CSVic20.h"
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "AudioQueue.h"
|
||||
#import "CSAudioQueue.h"
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
@ -16,11 +16,6 @@ class Atari2600Document: MachineDocument {
|
||||
}
|
||||
|
||||
// MARK: NSDocument overrides
|
||||
override init() {
|
||||
super.init()
|
||||
self.intendedCyclesPerSecond = 1194720
|
||||
}
|
||||
|
||||
override class func autosavesInPlace() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
@ -27,8 +27,6 @@ class ElectronDocument: MachineDocument {
|
||||
override func windowControllerDidLoadNib(aController: NSWindowController) {
|
||||
super.windowControllerDidLoadNib(aController)
|
||||
|
||||
self.intendedCyclesPerSecond = 2000000
|
||||
|
||||
if let os = rom("os"), basic = rom("basic") {
|
||||
self.electron.setOSROM(os)
|
||||
self.electron.setBASICROM(basic)
|
||||
|
@ -9,8 +9,14 @@
|
||||
import Cocoa
|
||||
import AudioToolbox
|
||||
|
||||
class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDelegate, NSWindowDelegate {
|
||||
|
||||
class MachineDocument:
|
||||
NSDocument,
|
||||
NSWindowDelegate,
|
||||
CSOpenGLViewDelegate,
|
||||
CSOpenGLViewResponderDelegate,
|
||||
CSBestEffortUpdaterDelegate,
|
||||
CSAudioQueueDelegate
|
||||
{
|
||||
lazy var actionLock = NSLock()
|
||||
lazy var drawLock = NSLock()
|
||||
func machine() -> CSMachine! {
|
||||
@ -33,7 +39,12 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
|
||||
optionsPanel?.setIsVisible(true)
|
||||
}
|
||||
|
||||
var audioQueue : AudioQueue! = nil
|
||||
private var audioQueue: CSAudioQueue! = nil
|
||||
private lazy var bestEffortUpdater: CSBestEffortUpdater = {
|
||||
let updater = CSBestEffortUpdater()
|
||||
updater.delegate = self
|
||||
return updater
|
||||
}()
|
||||
|
||||
override func windowControllerDidLoadNib(aController: NSWindowController) {
|
||||
super.windowControllerDidLoadNib(aController)
|
||||
@ -46,13 +57,16 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
|
||||
})
|
||||
|
||||
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
|
||||
let maximumSamplingRate = AudioQueue.preferredSamplingRate()
|
||||
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
|
||||
let selectedSamplingRate = self.machine().idealSamplingRateFromRange(NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
|
||||
if selectedSamplingRate > 0 {
|
||||
audioQueue = AudioQueue(samplingRate: Float64(selectedSamplingRate))
|
||||
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
|
||||
audioQueue.delegate = self
|
||||
self.machine().audioQueue = self.audioQueue
|
||||
self.machine().setAudioSamplingRate(selectedSamplingRate)
|
||||
self.machine().setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.bufferSize / 2)
|
||||
}
|
||||
|
||||
self.bestEffortUpdater.clockRate = self.machine().clockRate
|
||||
}
|
||||
|
||||
override func close() {
|
||||
@ -66,35 +80,17 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
|
||||
super.close()
|
||||
}
|
||||
|
||||
var intendedCyclesPerSecond: Int64 = 0
|
||||
private var cycleCountError: Int64 = 0
|
||||
private var lastTime: CVTimeStamp?
|
||||
private var skippedFrames = 0
|
||||
final func openGLView(view: CSOpenGLView, didUpdateToTime time: CVTimeStamp, didSkipPreviousUpdate : Bool, frequency : Double) {
|
||||
if let lastTime = lastTime {
|
||||
// perform (time passed in seconds) * (intended cycles per second), converting and
|
||||
// maintaining an error count to deal with underflow
|
||||
let videoTimeScale64 = Int64(time.videoTimeScale)
|
||||
let videoTimeCount = ((time.videoTime - lastTime.videoTime) * intendedCyclesPerSecond) + cycleCountError
|
||||
cycleCountError = videoTimeCount % videoTimeScale64
|
||||
var numberOfCycles = videoTimeCount / videoTimeScale64
|
||||
// MARK: CSBestEffortUpdaterDelegate
|
||||
final func bestEffortUpdater(bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
|
||||
runForNumberOfCycles(Int32(cycles))
|
||||
}
|
||||
|
||||
// if the emulation has fallen behind then silently limit the request;
|
||||
// some actions — e.g. the host computer waking after sleep — may give us a
|
||||
// prohibitive backlog
|
||||
if didSkipPreviousUpdate {
|
||||
skippedFrames++
|
||||
} else {
|
||||
skippedFrames = 0
|
||||
}
|
||||
|
||||
// run for at most three frames up to and until that causes overshoots in the
|
||||
// permitted processing window for at least four consecutive frames, in which
|
||||
// case limit to one
|
||||
numberOfCycles = min(numberOfCycles, Int64(Double(intendedCyclesPerSecond) * frequency * ((skippedFrames > 4) ? 3.0 : 1.0)))
|
||||
runForNumberOfCycles(Int32(numberOfCycles))
|
||||
func runForNumberOfCycles(numberOfCycles: Int32) {
|
||||
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
|
||||
if actionLock.tryLock() {
|
||||
self.machine().runForNumberOfCycles(cyclesToRunFor)
|
||||
actionLock.unlock()
|
||||
}
|
||||
lastTime = time
|
||||
}
|
||||
|
||||
// MARK: Utilities for children
|
||||
@ -106,15 +102,14 @@ class MachineDocument: NSDocument, CSOpenGLViewDelegate, CSOpenGLViewResponderDe
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: CSOpenGLViewDelegate
|
||||
func runForNumberOfCycles(numberOfCycles: Int32) {
|
||||
if actionLock.tryLock() {
|
||||
self.machine().runForNumberOfCycles(numberOfCycles)
|
||||
actionLock.unlock()
|
||||
}
|
||||
// MARK: CSAudioQueueDelegate
|
||||
final func audioQueueDidCompleteBuffer(audioQueue: CSAudioQueue) {
|
||||
bestEffortUpdater.update()
|
||||
}
|
||||
|
||||
func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
|
||||
// MARK: CSOpenGLViewDelegate
|
||||
final func openGLView(view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
|
||||
bestEffortUpdater.update()
|
||||
if drawLock.tryLock() {
|
||||
self.machine().drawViewForPixelSize(view.backingSize, onlyIfDirty: onlyIfDirty)
|
||||
drawLock.unlock()
|
||||
|
@ -18,8 +18,6 @@ class Vic20Document: MachineDocument {
|
||||
// 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)
|
||||
|
@ -8,19 +8,26 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CSOpenGLView.h"
|
||||
#import "AudioQueue.h"
|
||||
#import "CSAudioQueue.h"
|
||||
|
||||
@class CSMachine;
|
||||
@protocol CSMachineDelegate
|
||||
- (void)machineDidChangeClockRate:(CSMachine *)machine;
|
||||
@end
|
||||
|
||||
@interface CSMachine : NSObject
|
||||
|
||||
- (void)runForNumberOfCycles:(int)numberOfCycles;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
- (void)setAudioSamplingRate:(float)samplingRate;
|
||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize;
|
||||
|
||||
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio;
|
||||
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty;
|
||||
|
||||
@property (nonatomic, weak) AudioQueue *audioQueue;
|
||||
@property (nonatomic, strong) CSAudioQueue *audioQueue;
|
||||
@property (nonatomic, readonly) CSOpenGLView *view;
|
||||
@property (nonatomic, weak) id<CSMachineDelegate> delegate;
|
||||
@property (nonatomic, readonly) double clockRate;
|
||||
|
||||
@end
|
@ -47,19 +47,19 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAudioSamplingRate:(float)samplingRate {
|
||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize {
|
||||
@synchronized(self) {
|
||||
_speakerDelegate.machine = self;
|
||||
[self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate];
|
||||
[self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate bufferSize:bufferSize];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate {
|
||||
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
|
||||
@synchronized(self) {
|
||||
Outputs::Speaker *speaker = self.machine->get_speaker();
|
||||
if(speaker)
|
||||
{
|
||||
speaker->set_output_rate(sampleRate, 512);
|
||||
speaker->set_output_rate(sampleRate, (int)bufferSize);
|
||||
speaker->set_delegate(delegate);
|
||||
return YES;
|
||||
}
|
||||
@ -88,4 +88,8 @@ struct SpeakerDelegate: public Outputs::Speaker::Delegate {
|
||||
self.machine->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
|
||||
}
|
||||
|
||||
- (double)clockRate {
|
||||
return self.machine->get_clock_rate();
|
||||
}
|
||||
|
||||
@end
|
28
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h
Normal file
28
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// CSBestEffortUpdater.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@import CoreVideo;
|
||||
|
||||
@class CSBestEffortUpdater;
|
||||
|
||||
@protocol CSBestEffortUpdaterDelegate <NSObject>
|
||||
|
||||
- (void)bestEffortUpdater:(CSBestEffortUpdater *)bestEffortUpdater runForCycles:(NSUInteger)cycles didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface CSBestEffortUpdater : NSObject
|
||||
|
||||
@property (nonatomic, assign) double clockRate;
|
||||
@property (nonatomic, weak) id<CSBestEffortUpdaterDelegate> delegate;
|
||||
|
||||
- (void)update;
|
||||
|
||||
@end
|
64
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m
Normal file
64
OSBindings/Mac/Clock Signal/Updater/CSBestEffortUpdater.m
Normal file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// CSBestEffortUpdater.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/06/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSBestEffortUpdater.h"
|
||||
|
||||
@implementation CSBestEffortUpdater
|
||||
{
|
||||
// these are inherently handled only by thread-safe constructions
|
||||
uint32_t _updateIsOngoing;
|
||||
dispatch_queue_t _serialDispatchQueue;
|
||||
|
||||
// these are permitted for modification on _serialDispatchQueue only
|
||||
NSTimeInterval _previousTimeInterval;
|
||||
NSTimeInterval _cyclesError;
|
||||
BOOL _hasSkipped;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if(self = [super init])
|
||||
{
|
||||
_serialDispatchQueue = dispatch_queue_create("Best Effort Updater", DISPATCH_QUEUE_SERIAL);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
const uint32_t processingMask = 0x01;
|
||||
|
||||
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
|
||||
if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing))
|
||||
{
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
NSTimeInterval timeInterval = [NSDate timeIntervalSinceReferenceDate];
|
||||
if(_previousTimeInterval > DBL_EPSILON)
|
||||
{
|
||||
NSTimeInterval timeToRunFor = timeInterval - _previousTimeInterval;
|
||||
double cyclesToRunFor = timeToRunFor * self.clockRate + _cyclesError;
|
||||
|
||||
_cyclesError = fmod(cyclesToRunFor, 1.0);
|
||||
NSUInteger integerCyclesToRunFor = (NSUInteger)cyclesToRunFor;
|
||||
|
||||
[self.delegate bestEffortUpdater:self runForCycles:integerCyclesToRunFor didSkipPreviousUpdate:_hasSkipped];
|
||||
}
|
||||
_previousTimeInterval = timeInterval;
|
||||
_hasSkipped = NO;
|
||||
OSAtomicTestAndClear(processingMask, &_updateIsOngoing);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
_hasSkipped = YES;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -12,14 +12,6 @@
|
||||
@class CSOpenGLView;
|
||||
|
||||
@protocol CSOpenGLViewDelegate
|
||||
/*!
|
||||
Tells the delegate that time has advanced.
|
||||
@param view The view sending the message.
|
||||
@param time The time to which time has advanced.
|
||||
@param didSkipPreviousUpdate @c YES if the previous update that would have occurred was skipped because a didUpdateToTime: call prior to that was still ongoing; @c NO otherwise.
|
||||
*/
|
||||
- (void)openGLView:(nonnull CSOpenGLView *)view didUpdateToTime:(CVTimeStamp)time didSkipPreviousUpdate:(BOOL)didSkipPreviousUpdate frequency:(double)frequency;
|
||||
|
||||
/*!
|
||||
Requests that the delegate produce an image of its current output state. May be called on
|
||||
any queue or thread.
|
||||
|
@ -12,9 +12,6 @@
|
||||
|
||||
@implementation CSOpenGLView {
|
||||
CVDisplayLinkRef _displayLink;
|
||||
uint32_t _updateIsOngoing;
|
||||
BOOL _hasSkipped;
|
||||
dispatch_queue_t _serialDispatchQueue;
|
||||
}
|
||||
|
||||
- (void)prepareOpenGL
|
||||
@ -34,10 +31,6 @@
|
||||
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
|
||||
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat);
|
||||
|
||||
// create a serial dispatch queue
|
||||
_serialDispatchQueue = dispatch_queue_create("OpenGLView", DISPATCH_QUEUE_SERIAL);
|
||||
// dispatch_set_target_queue(_serialDispatchQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
|
||||
// set the clear colour
|
||||
[self.openGLContext makeCurrentContext];
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
||||
@ -55,24 +48,6 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
|
||||
- (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency
|
||||
{
|
||||
const uint32_t processingMask = 0x01;
|
||||
|
||||
// Always post an -openGLView:didUpdateToTime: if a previous one isn't still ongoing. This is the hook upon which the substantial processing occurs.
|
||||
if(!OSAtomicTestAndSet(processingMask, &_updateIsOngoing))
|
||||
{
|
||||
CVTimeStamp time = *now;
|
||||
BOOL didSkip = _hasSkipped;
|
||||
dispatch_async(_serialDispatchQueue, ^{
|
||||
[self.delegate openGLView:self didUpdateToTime:time didSkipPreviousUpdate:didSkip frequency:frequency];
|
||||
OSAtomicTestAndClear(processingMask, &_updateIsOngoing);
|
||||
});
|
||||
_hasSkipped = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasSkipped = YES;
|
||||
}
|
||||
|
||||
// Draw the display now regardless of other activity.
|
||||
[self drawViewOnlyIfDirty:YES];
|
||||
}
|
||||
|
@ -1,20 +0,0 @@
|
||||
//
|
||||
// AudioQueue.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/01/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface AudioQueue : NSObject
|
||||
|
||||
- (instancetype)initWithSamplingRate:(Float64)samplingRate;
|
||||
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples;
|
||||
|
||||
@property (nonatomic, readonly) Float64 samplingRate;
|
||||
|
||||
+ (Float64)preferredSamplingRate;
|
||||
|
||||
@end
|
Loading…
Reference in New Issue
Block a user