diff --git a/FruitMachine.xcodeproj/project.pbxproj b/FruitMachine.xcodeproj/project.pbxproj index dd0f13d..30cb7e3 100644 --- a/FruitMachine.xcodeproj/project.pbxproj +++ b/FruitMachine.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2A16ADBB1F33C341004A0333 /* DiskImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A16ADBA1F33C341004A0333 /* DiskImage.swift */; }; 2A2126841F2A9FA300E43DC1 /* DebuggerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */; }; 2A22EBFB1F21A7A700A36A61 /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */; }; 2A5BC5191F29A28D008C03BE /* AppleIScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BC5181F29A28D008C03BE /* AppleIScreenView.swift */; }; @@ -55,6 +56,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2A16ADBA1F33C341004A0333 /* DiskImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskImage.swift; sourceTree = ""; }; 2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggerWindowController.swift; sourceTree = ""; }; 2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerExtensions.swift; sourceTree = ""; }; 2A5BC5181F29A28D008C03BE /* AppleIScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIScreenView.swift; sourceTree = ""; }; @@ -117,6 +119,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2A16ADB91F33C2ED004A0333 /* DiskII */ = { + isa = PBXGroup; + children = ( + 2A63C2391F32CCE900D4F4F8 /* DiskII.swift */, + 2A16ADBA1F33C341004A0333 /* DiskImage.swift */, + ); + path = DiskII; + sourceTree = ""; + }; 2A5BC51A1F29A2EB008C03BE /* Frameworks */ = { isa = PBXGroup; children = ( @@ -138,7 +149,7 @@ 2A63C2381F32CCDB00D4F4F8 /* Peripherals */ = { isa = PBXGroup; children = ( - 2A63C2391F32CCE900D4F4F8 /* DiskII.swift */, + 2A16ADB91F33C2ED004A0333 /* DiskII */, 2A63C23B1F32CD4800D4F4F8 /* Peripheral.swift */, ); path = Peripherals; @@ -398,6 +409,7 @@ 2AA8B5FC1F2A8EAD002B350F /* Terminal.swift in Sources */, 2A5BC51E1F29A4C3008C03BE /* AppleIBitmapDisplay.swift in Sources */, 2AD458CE1F205EB700F05121 /* AppDelegate.swift in Sources */, + 2A16ADBB1F33C341004A0333 /* DiskImage.swift in Sources */, 2A91852A1F2EA84D00A9E5BE /* BitmapPixels.swift in Sources */, 2AE42E431F28665300C4900E /* A1CharacterGenerator.swift in Sources */, 2A6DC7F01F30495D0066FE0D /* ScreenView.swift in Sources */, diff --git a/FruitMachine/AppleII/AppleII.swift b/FruitMachine/AppleII/AppleII.swift index f38d3cf..e9d3c22 100644 --- a/FruitMachine/AppleII/AppleII.swift +++ b/FruitMachine/AppleII/AppleII.swift @@ -26,7 +26,7 @@ final class AppleII: NSObject, EmulatedSystem { var CYCLES_PER_BATCH: Int let emulatorViewDelegate = AppleII.ScreenDelegate() - let emulatorView = AppleII.ScreenView(frame: NSMakeRect(0, 0, 560, 384)) + let emulatorView = AppleII.ScreenView(frame: NSMakeRect(0, 16, 560, 384)) let emuScreenLayer = CALayer() required init(cpuFrequency: Double, fps: Double) { @@ -39,12 +39,12 @@ final class AppleII: NSObject, EmulatedSystem { for i in 1...7 { backplane[i] = nil } + backplane[6] = DiskII(slot: 6, romPath: "/Users/luigi/apple2/341-0027-a.p5") super.init() loadROMs() setupMemory(ramConfig: .sixteenK) - backplane[6] = DiskII(slot: 6, romPath: "/Users/luigi/apple2/341-0027-a.p5") emuScreenLayer.shouldRasterize = true emuScreenLayer.delegate = emulatorViewDelegate @@ -199,7 +199,7 @@ final class AppleII: NSObject, EmulatedSystem { CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.null //not connected } for page in 224 ..< 256 { - CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.ro //not connected + CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.ro //ROM } } diff --git a/FruitMachine/AppleII/Peripherals/DiskII.swift b/FruitMachine/AppleII/Peripherals/DiskII.swift deleted file mode 100644 index e41b6ca..0000000 --- a/FruitMachine/AppleII/Peripherals/DiskII.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// DiskII.swift -// FruitMachine -// -// Created by Christopher Rohl on 8/2/17. -// Copyright © 2017 Christopher Rohl. All rights reserved. -// - -import Cocoa - -class DiskII: NSObject, Peripheral { - let slotNumber: Int - let romManager: ROMManager - var readMemoryOverride: ReadOverride? = nil - var writeMemoryOverride: WriteOverride? = nil - - init(slot: Int, romPath: String) { - slotNumber = slot - romManager = ROMManager(path: romPath, atAddress: 0x0, size: 256) - - super.init() - - readMemoryOverride = ReadOverride(start: UInt16(0xC000 + (0x100 * slotNumber)), - end: UInt16(0xC0FF + (0x100 * slotNumber)), - readAnyway: false, - action: actionReadMemory) - } - - func actionReadMemory(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? { - let offset: UInt16 = 0xC000 + UInt16(slotNumber*0x100) - let local = address - offset - - return getMemoryMappedByte(address: local) - } - - private func getMemoryMappedByte(address: UInt16) -> UInt8 { - //Disk II just maps its ROM to the memory addressed by the slot. - - return romManager.ROM[Int(address)] - } - - func installOverrides() { - CPU.sharedInstance.memoryInterface.read_overrides.append(readMemoryOverride!) - } -} diff --git a/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift b/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift new file mode 100644 index 0000000..4a627cc --- /dev/null +++ b/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift @@ -0,0 +1,203 @@ +// +// DiskII.swift +// FruitMachine +// +// Created by Christopher Rohl on 8/2/17. +// Copyright © 2017 Christopher Rohl. All rights reserved. +// + +import Cocoa + +/* + Commands: + 0 = PHASE 0 OFF + 1 = PHASE 0 ON + 2 = PHASE 1 OFF + 3 = PHASE 1 ON + 4 = PHASE 2 OFF + 5 = PHASE 2 ON + 6 = PHASE 3 OFF + 7 = PHASE 3 ON + 8 = TURN MOTOR OFF + 9 = TURN MOTOR ON + A = SELECT DRIVE 1 + B = SELECT DRIVE 2 + C = Q6 -> L + D = Q6 -> H + E = Q7 -> L + F = Q7 -> H + + Q6 Q7 + L L READ + H L SENSE WRITE PROTECT OR PREWRITE STATE + L H WRITE + H H WRITE LOAD + */ + +class DiskII: NSObject, Peripheral { + enum MotorPhase { + case Phase0 + case Phase1 + case Phase2 + case Phase3 + } + + /* Notifications */ + static let N_Drive1MotorOn = NSNotification.Name(rawValue: "Drive1MotorOn") + static let N_Drive2MotorOn = NSNotification.Name(rawValue: "Drive2MotorOn") + static let N_Drive1MotorOff = NSNotification.Name(rawValue: "Drive1MotorOff") + static let N_Drive2MotorOff = NSNotification.Name(rawValue: "Drive2MotorOff") + + /* Softswitches */ + struct Softswitches { + var Phase0 = false + var Phase1 = false + var Phase2 = false + var Phase3 = false + var MotorPowered = false + var DriveSelect = false //false = 1, true = 2 + var Q6 = false + var Q7 = false + } + + let slotNumber: Int + let romManager: ROMManager + var readMemoryOverride: ReadOverride? = nil + var readIOOverride: ReadOverride? = nil + var writeIOOverride: WriteOverride? = nil + var softswitches = Softswitches() + + var currentTrack: Int = 0 + var motorPhase: MotorPhase = .Phase0 + + var diskImage = DiskImage(diskPath: "/Users/luigi/apple2/master.dsk") + + init(slot: Int, romPath: String) { + slotNumber = slot + romManager = ROMManager(path: romPath, atAddress: 0x0, size: 256) + + super.init() + + readMemoryOverride = ReadOverride(start: UInt16(0xC000 + (0x100 * slotNumber)), + end: UInt16(0xC0FF + (0x100 * slotNumber)), + readAnyway: false, + action: actionReadMemory) + + readIOOverride = ReadOverride(start: UInt16(0xC080 + (0x10 * slotNumber)), + end: UInt16(0xC08F + (0x10 * slotNumber)), + readAnyway: false, + action: actionDispatchOperation) + + writeIOOverride = WriteOverride(start: UInt16(0xC080 + (0x10 * slotNumber)), + end: UInt16(0xC08F + (0x10 * slotNumber)), + writeAnyway: false, + action: actionDispatchOperation) + } + + //http://ftp.twaren.net/NetBSD/misc/wrstuden/Apple_PDFs/Software%20control%20of%20IWM.pdf + private func actionDispatchOperation(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? { + let operationNumber = UInt8(address & 0xFF) - UInt8(0x80 & 0xFF) - UInt8(0x10 * slotNumber) + + print("Disk II command: \(operationNumber)") + + //Update the softswitches. + switch(operationNumber) { + case 0: + softswitches.Phase0 = false + case 1: + softswitches.Phase0 = true + if(motorPhase == .Phase1) { + motorPhase = .Phase0 + if(currentTrack % 2 == 0) { + if(currentTrack >= 0) { currentTrack -= 1 } + } + } else if(motorPhase == .Phase3) { + motorPhase = .Phase0 + if(currentTrack % 2 == 1) { + if(currentTrack <= 34) { currentTrack += 1 } + } + } + case 2: + softswitches.Phase1 = false + case 3: + softswitches.Phase1 = true + if(motorPhase == .Phase0 || motorPhase == .Phase2) { + motorPhase = .Phase1 + } + case 4: + softswitches.Phase2 = false + case 5: + softswitches.Phase2 = true + if(motorPhase == .Phase3) { + motorPhase = .Phase2 + if(currentTrack % 2 == 0) { + if(currentTrack >= 0) { currentTrack -= 1 } + } + } else if(motorPhase == .Phase1) { + motorPhase = .Phase2 + if(currentTrack % 2 == 0) { + if(currentTrack <= 34) { currentTrack += 1 } + } + } + case 6: + softswitches.Phase3 = false + case 7: + softswitches.Phase3 = true + if(motorPhase == .Phase0 || motorPhase == .Phase2) { + motorPhase = .Phase3 + } + case 8: + softswitches.MotorPowered = false + if(softswitches.DriveSelect == false) { + NotificationCenter.default.post(name: DiskII.N_Drive1MotorOff, object: nil) + } else { + NotificationCenter.default.post(name: DiskII.N_Drive2MotorOff, object: nil) + } + case 9: + softswitches.MotorPowered = true + if(softswitches.DriveSelect == false) { + NotificationCenter.default.post(name: DiskII.N_Drive1MotorOn, object: nil) + } else { + NotificationCenter.default.post(name: DiskII.N_Drive2MotorOn, object: nil) + } + case 10: + softswitches.DriveSelect = false + case 11: + softswitches.DriveSelect = true + case 12: + softswitches.Q6 = false + if(byte == nil) { //read + //write to Q6L -> read byte + } + case 13: + softswitches.Q6 = true + case 14: + softswitches.Q7 = false + case 15: + softswitches.Q7 = true + default: + print("Unknown command? This can't happen.") + } + + return 0x00 + } + + func installOverrides() { + CPU.sharedInstance.memoryInterface.read_overrides.append(readMemoryOverride!) + CPU.sharedInstance.memoryInterface.read_overrides.append(readIOOverride!) + CPU.sharedInstance.memoryInterface.write_overrides.append(writeIOOverride!) + } + + private func actionReadMemory(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? { + let offset: UInt16 = 0xC000 + UInt16(slotNumber*0x100) + let local = address - offset + + return getMemoryMappedByte(address: local) + } + + private func getMemoryMappedByte(address: UInt16) -> UInt8 { + //Disk II just maps its ROM to the memory addressed by the slot. + + return romManager.ROM[Int(address)] + } +} diff --git a/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift b/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift new file mode 100644 index 0000000..d6fc632 --- /dev/null +++ b/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift @@ -0,0 +1,248 @@ +// +// DosFormat.swift +// FruitMachine +// +// Created by Christopher Rohl on 8/3/17. +// Copyright © 2017 Christopher Rohl. All rights reserved. +// + +import Cocoa + +protocol DiskImageFormat { + static var BYTES_PER_SECTOR: Int { get } + static var SECTORS_PER_TRACK: Int { get } + static var TRACKS_PER_DISK: Int { get } + static var BYTES_PER_TRACK: Int { get } +} + +class Dos33Image: DiskImageFormat { + static let BYTES_PER_SECTOR: Int = 256 + static let SECTORS_PER_TRACK: Int = 16 + static let TRACKS_PER_DISK: Int = 35 + static let BYTES_PER_TRACK: Int = BYTES_PER_SECTOR * SECTORS_PER_TRACK + + //Sectors in a track are in this order. + static let sectorOrder = [0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15] + + struct VTOC { + //http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system + + //$00 unused + let catalogTrackNumber = 0 //$01 + let catalogSectorNumber = 0 //$02 + let dosInitVersion = 0 //$03 + //$04-05 unused + let volumeNumber = 0 //$06 + //$07-$26 unused + let maxTrackSectorPairs = 0 //$27, should be 122 + //$28-$2F unused + let lastFormattedTrack = 0 //$30 + let trackDirection = 0 //$31 + //$32-$33 unused + let tracksPerDisk = 0 //$34 + let sectorsPerTrack = 0 //$35 + let bytesPerSector = 0 //$36-$37 + } + + let tableOfContents = VTOC() + + static func readTrackAndSector(imageData: [UInt8], trackNum: Int, sectorNum: Int) -> [UInt8] { + //Find the track in our disk. + let trackOffset = trackNum * Dos33Image.BYTES_PER_TRACK + //Find the sector in this track. + let sectorOffset = sectorOrder.index(of: sectorNum)! * Dos33Image.BYTES_PER_SECTOR + let offset = trackOffset + sectorOffset + + return Array(imageData[offset ..< offset + Dos33Image.BYTES_PER_SECTOR]) + } +} + +class DiskImage: NSObject { + enum DiskFormat { + case Dos33 + case Prodos + case Raw + } + + var encodedTracks = [UInt8]() + var fileSize: UInt64 = 0 + var image: DiskImageFormat? + + init(diskPath: String) { + do { + let attr = try FileManager.default.attributesOfItem(atPath: diskPath) + fileSize = attr[FileAttributeKey.size] as! UInt64 + } catch { + print("Error in DiskImage: \(error)") + } + + super.init() + var rawData: [UInt8]? + + rawData = loadImageBytes(path: diskPath, size: Int(fileSize)) + if(rawData == nil) { + print("Couldn't load disk image") + return + } + //Is this a DOS 3.3 format image? Read one sector from track $11. + let catalogSector: [UInt8] = Dos33Image.readTrackAndSector(imageData: rawData!, trackNum: 0x11, sectorNum: 0) + + encodeDos33Track(imageData: rawData!, index: 0, volumeNumber: Int(catalogSector[0x06])) + } + + func loadImageBytes(path: String, size: Int) -> [UInt8]? { + do { + var data = [UInt8](repeating: 0xCC, count: Int(fileSize)) + + let fileContent: NSData = try NSData(contentsOfFile: path) + fileContent.getBytes(&data, range: NSRange(location: 0, length: Int(size))) + + return data + } catch { + print(error) + } + + return nil + } + + func encodeDos33Track(imageData: [UInt8], index: Int, volumeNumber: Int) { + var encodedData = [UInt8]() + let dataOffset = index * Dos33Image.BYTES_PER_TRACK + + //Prologue: add 48 self-syncing bytes + for _ in 0..<0x30 { encodedData.append(selfSync) } + + for sectorNum in 0 ..< Dos33Image.SECTORS_PER_TRACK { + //Address Field + encodedData.append(contentsOf: addressPrologue) + encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(volumeNumber)))) //Volume byte + encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(index)))) //Track number + encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(sectorNum)))) //Sector number + let checksum: UInt8 = UInt8(volumeNumber) ^ UInt8(index) ^ UInt8(sectorNum) + encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(checksum)))) //Checksum value + encodedData.append(contentsOf: addressEpilogue) + + //Gap2 - 5 bytes + for _ in 0..<5 { encodedData.append(selfSync) } + + //Data Field + encodedData.append(contentsOf: dataPrologue) + encodedData.append(contentsOf: EncodeSectorSixAndTwo(sector: Dos33Image.readTrackAndSector(imageData: imageData, trackNum: index, sectorNum: sectorNum))) + encodedData.append(contentsOf: dataEpilogue) + + //Gap2 + for _ in 0..<5 { encodedData.append(selfSync) } + } + + } + + func UInt16toUInt8Array(word: UInt16) -> [UInt8] { + var r = [UInt8]() + r.append(UInt8((word & 0xFF00) >> 8)) + r.append(UInt8(word & 0x00FF)) + + return r + } + + func EncodeSectorSixAndTwo(sector: [UInt8]) -> [UInt8] { + let encodedBuffer = SixAndTwoPrenibblize(sector: sector) + var writtenData = [UInt8](repeating: 0x00, count: 343) + + //We have a prepared buffer. + writtenData[0] = SixAndTwoTranslationTable[Int(0 ^ encodedBuffer[0x155])] + writtenData[86] = SixAndTwoTranslationTable[Int(encodedBuffer[0x100] ^ encodedBuffer[0x00])] + + for index in 0x00 ... 0xFE { + writtenData[87 + index] = SixAndTwoTranslationTable[Int(encodedBuffer[index] ^ encodedBuffer[index + 1])] + } + + for (i, index) in (0x100 ... 0x154).enumerated() { + writtenData[85-i] = SixAndTwoTranslationTable[Int(encodedBuffer[index] ^ encodedBuffer[index + 1])] + } + + writtenData[342] = SixAndTwoTranslationTable[Int(encodedBuffer[0xFF])] + + return writtenData + } + + func SixAndTwoPrenibblize(sector: [UInt8]) -> [UInt8] { + //Create a 342-byte buffer from a 256-byte sector. + var nibblized: [UInt8] = [UInt8](repeating: 0x00, count: 342) + + for byte in 0x00...0x55 { + //nibblized[byte] = SixAndTwoTranslationTable[Int(sector[byte] >> 2)] + nibblized[byte] = sector[byte] >> 2 + let b0 = (sector[byte] & 0b00000001) + let b1 = (sector[byte] & 0b00000010) + let low = 0x00 | (b0 << 1 | b1 >> 1) + + nibblized[0x155 - byte] |= low + } + + for byte in 0x56...0xAA { + //nibblized[byte] = SixAndTwoTranslationTable[Int(sector[byte] >> 2)] + nibblized[byte] = sector[byte] >> 2 + let b0 = (sector[byte] & 0b00000001) + let b1 = (sector[byte] & 0b00000010) + let low = (b0 << 1 | b1 >> 1) + + nibblized[0x155 - (byte % 0x56)] |= (low << 2) + } + + for byte in 0xAB...0xFF { + //nibblized[byte] = SixAndTwoTranslationTable[Int(sector[byte] >> 2)] + nibblized[byte] = sector[byte] >> 2 + let b0 = (sector[byte] & 0b00000001) + let b1 = (sector[byte] & 0b00000010) + let low = (b0 << 1 | b1 >> 1) + + //Now we have a full six bits. + let completeLow: UInt8 = nibblized[0x155 - (byte % 0x56)] | (low << 4) + //nibblized[0x155 - (byte % 0x56)] = SixAndTwoTranslationTable[Int(completeLow)] + nibblized[0x155 - (byte % 0x56)] = completeLow + } + + return nibblized + } + + //Convert bytes to the different encoding schemes. + func FourAndFourEncode(byte: UInt8) -> UInt16 { + /* + 4 and 4 encoded bytes require two bytes (by splitting actual bits + evenly between two bytes) and have the following format: + + 1 b7 1 b5 1 b3 1 b1 + 1 b6 1 b4 1 b2 1 b0 + */ + var encoded: UInt16 = 0 + + let hi: UInt16 = UInt16((byte >> 1) | 0b10101010) + let lo: UInt16 = UInt16(byte | 0b10101010) + + encoded = (hi << 8) | lo + return encoded + } + + func SixAndTwoEncode(byte: UInt8) -> UInt8 { + return SixAndTwoTranslationTable[Int(byte)] + } + + //A group of self-syncing bytes. This pattern can be repeated as long as required. + //let selfSyncFive: [UInt8] = [0b11111111, 0b00111111, 0b11001111, 0b11110011, 0b11111100] + let selfSync: UInt8 = 0xFF + + let addressPrologue: [UInt8] = [0xD5, 0xAA, 0x96] + let addressEpilogue: [UInt8] = [0xDE, 0xAA, 0xEB] + + let dataPrologue: [UInt8] = [0xD5, 0xAA, 0xAD] + let dataEpilogue: [UInt8] = [0xDE, 0xAA, 0xEB] + + let SixAndTwoTranslationTable: [UInt8] = [0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, + 0xA7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, + 0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, + 0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, + 0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff] +} diff --git a/FruitMachine/AppleIIViewController.swift b/FruitMachine/AppleIIViewController.swift index 3f4cb5c..98cadb7 100644 --- a/FruitMachine/AppleIIViewController.swift +++ b/FruitMachine/AppleIIViewController.swift @@ -10,6 +10,9 @@ import Cocoa class AppleIIViewController: NSViewController { + @IBOutlet weak var lbl_Drive1: NSTextField! + @IBOutlet weak var lbl_Drive2: NSTextField! + let computer = AppleII.sharedInstance var debuggerWindowController: DebuggerWindowController! var preferencesWindowController: PreferencesWindowController! @@ -25,6 +28,8 @@ class AppleIIViewController: NSViewController { self.view.addSubview(computer.emulatorView) + setupDriveNotifications() + self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0, target: self, selector: #selector(runEmulation), @@ -81,4 +86,28 @@ class AppleIIViewController: NSViewController { return nil } + func setupDriveNotifications() { + NotificationCenter.default.addObserver(self, selector: #selector(self.drive1MotorOn), name: DiskII.N_Drive1MotorOn, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.drive2MotorOn), name: DiskII.N_Drive2MotorOn, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.drive1MotorOff), name: DiskII.N_Drive1MotorOff, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(self.drive2MotorOff), name: DiskII.N_Drive2MotorOff, object: nil) + } + + /* drive lights */ + @objc func drive1MotorOff(notification: NSNotification) { + lbl_Drive1.textColor = NSColor.textColor + } + + @objc func drive2MotorOff(notification: NSNotification) { + lbl_Drive2.textColor = NSColor.textColor + } + + @objc func drive1MotorOn(notification: NSNotification) { + lbl_Drive1.textColor = NSColor.red + } + + @objc func drive2MotorOn(notification: NSNotification) { + lbl_Drive2.textColor = NSColor.red + } + } diff --git a/FruitMachine/FruitMachine.storyboard b/FruitMachine/FruitMachine.storyboard index 9939e8d..2fa1cf0 100644 --- a/FruitMachine/FruitMachine.storyboard +++ b/FruitMachine/FruitMachine.storyboard @@ -157,10 +157,10 @@ - + - - + + @@ -178,9 +178,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + + +