1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-17 13:29:02 +00:00
CLK/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift

215 lines
6.4 KiB
Swift
Raw Normal View History

//
// MachineDocument.swift
// Clock Signal
//
// Created by Thomas Harte on 04/01/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
import Cocoa
import AudioToolbox
class MachineDocument:
NSDocument,
NSWindowDelegate,
CSMachineDelegate,
CSOpenGLViewDelegate,
CSOpenGLViewResponderDelegate,
CSBestEffortUpdaterDelegate,
CSAudioQueueDelegate
{
lazy var actionLock = NSLock()
lazy var drawLock = NSLock()
var machine: CSMachine!
var name: String! {
get {
return nil
}
}
func aspectRatio() -> NSSize {
return NSSize(width: 4.0, height: 3.0)
}
@IBOutlet weak var openGLView: CSOpenGLView! {
didSet {
openGLView.delegate = self
openGLView.responderDelegate = self
}
}
@IBOutlet var optionsPanel: MachinePanel!
2016-09-16 02:12:12 +00:00
@IBAction func showOptions(_ sender: AnyObject!) {
2016-04-18 01:43:39 +00:00
optionsPanel?.setIsVisible(true)
}
2016-09-16 02:12:12 +00:00
fileprivate var audioQueue: CSAudioQueue! = nil
fileprivate lazy var bestEffortUpdater: CSBestEffortUpdater = {
let updater = CSBestEffortUpdater()
updater.delegate = self
return updater
}()
override var windowNibName: String? {
return "MachineDocument"
}
2016-09-16 02:12:12 +00:00
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
super.windowControllerDidLoadNib(aController)
// establish the output aspect ratio and audio
let displayAspectRatio = self.aspectRatio()
aController.window?.contentAspectRatio = displayAspectRatio
2016-09-16 02:12:12 +00:00
openGLView.perform(glContext: {
self.machine.setView(self.openGLView, aspectRatio: Float(displayAspectRatio.width / displayAspectRatio.height))
})
setupClockRate()
self.machine.delegate = self
self.optionsPanel?.establishStoredOptions()
}
2016-09-16 02:12:12 +00:00
func machineDidChangeClockRate(_ machine: CSMachine!) {
setupClockRate()
}
2016-09-16 02:12:12 +00:00
func machineDidChangeClockIsUnlimited(_ machine: CSMachine!) {
self.bestEffortUpdater.runAsUnlimited = machine.clockIsUnlimited
}
2016-09-16 02:12:12 +00:00
fileprivate func setupClockRate() {
// establish and provide the audio queue, taking advice as to an appropriate sampling rate
2016-06-15 12:07:25 +00:00
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
2016-09-16 02:12:12 +00:00
let selectedSamplingRate = self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate)))
if selectedSamplingRate > 0 {
2016-06-15 12:07:25 +00:00
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
audioQueue.delegate = self
self.machine.audioQueue = self.audioQueue
2016-10-10 11:45:09 +00:00
self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize)
}
self.bestEffortUpdater.clockRate = self.machine.clockRate
}
override func close() {
bestEffortUpdater.flush()
actionLock.lock()
drawLock.lock()
openGLView.invalidate()
openGLView.openGLContext!.makeCurrentContext()
actionLock.unlock()
drawLock.unlock()
super.close()
}
// MARK: configuring
2016-09-16 02:12:12 +00:00
func configureAs(_ analysis: CSStaticAnalyser) {
if let machine = analysis.newMachine() {
self.machine = machine
}
2016-09-16 02:12:12 +00:00
analysis.apply(to: self.machine)
if let optionsPanelNibName = analysis.optionsPanelNibName {
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
self.optionsPanel.machine = self.machine
showOptions(self)
}
}
override func read(from url: URL, ofType typeName: String) throws {
if let analyser = CSStaticAnalyser(fileAt: url) {
self.displayName = analyser.displayName
self.configureAs(analyser)
} else {
throw NSError(domain: "MachineDocument", code: -1, userInfo: nil)
}
}
// MARK: the pasteboard
2016-09-16 02:12:12 +00:00
func paste(_ sender: AnyObject!) {
let pasteboard = NSPasteboard.general()
if let string = pasteboard.string(forType: NSPasteboardTypeString) {
self.machine.paste(string)
}
}
// MARK: CSBestEffortUpdaterDelegate
2016-09-16 02:12:12 +00:00
final func bestEffortUpdater(_ bestEffortUpdater: CSBestEffortUpdater!, runForCycles cycles: UInt, didSkipPreviousUpdate: Bool) {
runForNumberOfCycles(Int32(cycles))
}
2016-09-16 02:12:12 +00:00
func runForNumberOfCycles(_ numberOfCycles: Int32) {
let cyclesToRunFor = min(numberOfCycles, Int32(bestEffortUpdater.clockRate / 10))
2016-09-16 02:12:12 +00:00
if actionLock.try() {
self.machine.runForNumber(ofCycles: cyclesToRunFor)
actionLock.unlock()
}
}
// MARK: CSAudioQueueDelegate
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
bestEffortUpdater.update()
}
// MARK: CSOpenGLViewDelegate
2016-09-16 02:12:12 +00:00
final func openGLView(_ view: CSOpenGLView, drawViewOnlyIfDirty onlyIfDirty: Bool) {
bestEffortUpdater.update()
2016-09-16 02:12:12 +00:00
if drawLock.try() {
self.machine.drawView(forPixelSize: view.backingSize, onlyIfDirty: onlyIfDirty)
drawLock.unlock()
}
}
// MARK: NSDocument overrides
2016-09-16 02:12:12 +00:00
override func data(ofType typeName: String) throws -> Data {
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
// MARK: Input management
2016-09-16 02:12:12 +00:00
fileprivate func withKeyboardMachine(_ action: (CSKeyboardMachine) -> ()) {
if let keyboardMachine = self.machine as? CSKeyboardMachine {
action(keyboardMachine)
}
}
fileprivate func withJoystickMachine(_ action: (CSJoystickMachine) -> ()) {
if let joystickMachine = self.machine as? CSJoystickMachine {
action(joystickMachine)
}
}
fileprivate func sendJoystickEvent(_ machine: CSJoystickMachine, keyCode: UInt16, isPressed: Bool) {
switch keyCode {
case 123: machine.setDirection(.left, onPad: 0, isPressed: isPressed)
case 126: machine.setDirection(.up, onPad: 0, isPressed: isPressed)
case 124: machine.setDirection(.right, onPad: 0, isPressed: isPressed)
case 125: machine.setDirection(.down, onPad: 0, isPressed: isPressed)
default: machine.setButtonAt(0, onPad: 0, isPressed: isPressed)
}
}
2016-09-16 02:12:12 +00:00
func windowDidResignKey(_ notification: Notification) {
self.withKeyboardMachine { $0.clearAllKeys() }
}
2016-09-16 02:12:12 +00:00
func keyDown(_ event: NSEvent) {
self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: true) }
self.withJoystickMachine { sendJoystickEvent($0, keyCode: event.keyCode, isPressed: false) }
}
2016-09-16 02:12:12 +00:00
func keyUp(_ event: NSEvent) {
self.withKeyboardMachine { $0.setKey(event.keyCode, isPressed: false) }
self.withJoystickMachine { sendJoystickEvent($0, keyCode: event.keyCode, isPressed: true) }
}
2016-09-16 02:12:12 +00:00
func flagsChanged(_ newModifiers: NSEvent) {
self.withKeyboardMachine {
2016-09-16 02:12:12 +00:00
$0.setKey(VK_Shift, isPressed: newModifiers.modifierFlags.contains(.shift))
$0.setKey(VK_Control, isPressed: newModifiers.modifierFlags.contains(.control))
$0.setKey(VK_Command, isPressed: newModifiers.modifierFlags.contains(.command))
$0.setKey(VK_Option, isPressed: newModifiers.modifierFlags.contains(.option))
}
}
}