1
0
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:
Thomas Harte 2016-06-16 21:44:16 -04:00 committed by GitHub
commit d6cb4fe15c
28 changed files with 303 additions and 175 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

@ -10,4 +10,5 @@
#import "CSVic20.h"
#import "CSOpenGLView.h"
#import "AudioQueue.h"
#import "CSAudioQueue.h"
#import "CSBestEffortUpdater.h"

View File

@ -16,11 +16,6 @@ class Atari2600Document: MachineDocument {
}
// MARK: NSDocument overrides
override init() {
super.init()
self.intendedCyclesPerSecond = 1194720
}
override class func autosavesInPlace() -> Bool {
return true
}

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View 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

View File

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

View File

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

View File

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