diff --git a/NoteDetection.xcodeproj/project.pbxproj b/NoteDetection.xcodeproj/project.pbxproj index 8974a0aa..52d58814 100644 --- a/NoteDetection.xcodeproj/project.pbxproj +++ b/NoteDetection.xcodeproj/project.pbxproj @@ -44,13 +44,13 @@ 032160EC1F04EE7100C9AE3E /* OSStatus+LocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032160CD1F04EE7100C9AE3E /* OSStatus+LocalizedError.swift */; }; 032160ED1F04EE7100C9AE3E /* PitchDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032160CE1F04EE7100C9AE3E /* PitchDetection.swift */; }; 032160EF1F04EE7100C9AE3E /* ProcessedAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032160D01F04EE7100C9AE3E /* ProcessedAudio.swift */; }; + 035A1DC320BAECF7009F4248 /* LightController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0368462120B88457006C3C9E /* LightController.swift */; }; + 0368462220B88457006C3C9E /* LightController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0368462120B88457006C3C9E /* LightController.swift */; }; 037122141E8532A0009CC189 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 037122131E8532A0009CC189 /* .swiftlint.yml */; }; 0377917E206C15A300350967 /* YamahaLightControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0377917D206C15A300350967 /* YamahaLightControl.swift */; }; 0377917F206C15A300350967 /* YamahaLightControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0377917D206C15A300350967 /* YamahaLightControl.swift */; }; 03779181206C161100350967 /* YamahaMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03779180206C161100350967 /* YamahaMessages.swift */; }; 03779182206C161100350967 /* YamahaMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03779180206C161100350967 /* YamahaMessages.swift */; }; - 03779184206C163700350967 /* MIDIOutConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03779183206C163700350967 /* MIDIOutConnection.swift */; }; - 03779185206C163700350967 /* MIDIOutConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03779183206C163700350967 /* MIDIOutConnection.swift */; }; 038A82871F211A7600875E5E /* AVAudioEngine+Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038A82861F211A7600875E5E /* AVAudioEngine+Category.swift */; }; 03935B092074A61700D3C325 /* MemoryLeakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03935B082074A61700D3C325 /* MemoryLeakTests.swift */; }; 03A7F73A1E850D5300849AE2 /* NoteDetection.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A7F7301E850D5300849AE2 /* NoteDetection.framework */; }; @@ -58,8 +58,8 @@ 03A8F41020A3040400171C35 /* RMS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A8F40E20A3010900171C35 /* RMS.swift */; }; 03B029261F94C7CA00367C53 /* MIDIEndpointRef+NetworkSessionMacOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B029241F94C7BD00367C53 /* MIDIEndpointRef+NetworkSessionMacOS.swift */; }; 03B103D32062B80A003CAE4D /* NoteDetection.h in Headers */ = {isa = PBXBuildFile; fileRef = 03A7F7331E850D5300849AE2 /* NoteDetection.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 03B103ED2063C484003CAE4D /* CoreMIDIOutConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B103EC2063C484003CAE4D /* CoreMIDIOutConnection.swift */; }; - 03B103EE2063C484003CAE4D /* CoreMIDIOutConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B103EC2063C484003CAE4D /* CoreMIDIOutConnection.swift */; }; + 03B103ED2063C484003CAE4D /* MIDIOutConnectionApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B103EC2063C484003CAE4D /* MIDIOutConnectionApple.swift */; }; + 03B103EE2063C484003CAE4D /* MIDIOutConnectionApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B103EC2063C484003CAE4D /* MIDIOutConnectionApple.swift */; }; 03CBB9391F7E5FA900574D4D /* MIDIEndpointRef+NetworkSessionIOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03CBB9381F7E5FA900574D4D /* MIDIEndpointRef+NetworkSessionIOS.swift */; }; 03DD8F261F7D571000E0D523 /* MIDIPacket+UInt8Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032160C51F04EE7100C9AE3E /* MIDIPacket+UInt8Array.swift */; }; 03DD8F271F7D571400E0D523 /* MIDIEngineApple.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032160C21F04EE7100C9AE3E /* MIDIEngineApple.swift */; }; @@ -154,10 +154,10 @@ 0321617F1F065B8E00C9AE3E /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = SOURCE_ROOT; }; 032161801F065BB600C9AE3E /* build_android.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_android.sh; sourceTree = SOURCE_ROOT; }; 032E8CF01FC8641300729088 /* AndroidActivityContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AndroidActivityContext.swift; path = Sources/NoteDetection/AndroidActivityContext.swift; sourceTree = SOURCE_ROOT; }; + 0368462120B88457006C3C9E /* LightController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LightController.swift; path = Sources/NoteDetection/LightController.swift; sourceTree = SOURCE_ROOT; }; 037122131E8532A0009CC189 /* .swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; 0377917D206C15A300350967 /* YamahaLightControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YamahaLightControl.swift; path = Sources/NoteDetection/YamahaLightControl.swift; sourceTree = SOURCE_ROOT; }; 03779180206C161100350967 /* YamahaMessages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = YamahaMessages.swift; path = Sources/NoteDetection/YamahaMessages.swift; sourceTree = SOURCE_ROOT; }; - 03779183206C163700350967 /* MIDIOutConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MIDIOutConnection.swift; path = Sources/NoteDetection/MIDIOutConnection.swift; sourceTree = SOURCE_ROOT; }; 038A82841F2118A600875E5E /* MIDIEngineAndroid.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MIDIEngineAndroid.swift; path = Sources/NoteDetection/MIDIEngineAndroid.swift; sourceTree = SOURCE_ROOT; }; 038A82861F211A7600875E5E /* AVAudioEngine+Category.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "AVAudioEngine+Category.swift"; path = "Sources/NoteDetection/AVAudioEngine+Category.swift"; sourceTree = SOURCE_ROOT; }; 03935B082074A61700D3C325 /* MemoryLeakTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryLeakTests.swift; sourceTree = ""; }; @@ -169,7 +169,7 @@ 03A7F7401E850D5300849AE2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 03A8F40E20A3010900171C35 /* RMS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RMS.swift; sourceTree = ""; }; 03B029241F94C7BD00367C53 /* MIDIEndpointRef+NetworkSessionMacOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MIDIEndpointRef+NetworkSessionMacOS.swift"; sourceTree = ""; }; - 03B103EC2063C484003CAE4D /* CoreMIDIOutConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = CoreMIDIOutConnection.swift; path = Sources/NoteDetection/CoreMIDIOutConnection.swift; sourceTree = SOURCE_ROOT; }; + 03B103EC2063C484003CAE4D /* MIDIOutConnectionApple.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MIDIOutConnectionApple.swift; path = Sources/NoteDetection/MIDIOutConnectionApple.swift; sourceTree = SOURCE_ROOT; }; 03CBB9381F7E5FA900574D4D /* MIDIEndpointRef+NetworkSessionIOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MIDIEndpointRef+NetworkSessionIOS.swift"; sourceTree = ""; }; 5C0E56731F0BE92700793A7F /* NoteDetection.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NoteDetection.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 5C0E56751F0BE92700793A7F /* NoteDetectionMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NoteDetectionMac.h; sourceTree = ""; }; @@ -219,7 +219,6 @@ 038A82811F2117BD00875E5E /* Android */, 038A82801F210F2F00875E5E /* Apple */, 032160C01F04EE7100C9AE3E /* MIDIDevice.swift */, - 03779183206C163700350967 /* MIDIOutConnection.swift */, 032160C41F04EE7100C9AE3E /* MIDIMessage.swift */, ); name = MIDIEngine; @@ -301,7 +300,7 @@ 032160C81F04EE7100C9AE3E /* MIDIObjectRef.swift */, 03CBB9381F7E5FA900574D4D /* MIDIEndpointRef+NetworkSessionIOS.swift */, 03B029241F94C7BD00367C53 /* MIDIEndpointRef+NetworkSessionMacOS.swift */, - 03B103EC2063C484003CAE4D /* CoreMIDIOutConnection.swift */, + 03B103EC2063C484003CAE4D /* MIDIOutConnectionApple.swift */, ); name = Apple; sourceTree = ""; @@ -366,6 +365,7 @@ 03A7F7331E850D5300849AE2 /* NoteDetection.h */, 03A7F7341E850D5300849AE2 /* Info.plist */, 03A8F40E20A3010900171C35 /* RMS.swift */, + 0368462120B88457006C3C9E /* LightController.swift */, ); path = NoteDetection; sourceTree = ""; @@ -631,7 +631,6 @@ 0377917E206C15A300350967 /* YamahaLightControl.swift in Sources */, 032160D81F04EE7100C9AE3E /* CrossPlatformHelper.swift in Sources */, 032160EA1F04EE7100C9AE3E /* OnsetDetection.swift in Sources */, - 03779184206C163700350967 /* MIDIOutConnection.swift in Sources */, 032160DB1F04EE7100C9AE3E /* FilterBank.swift in Sources */, 032160E11F04EE7100C9AE3E /* MIDIEngineApple.swift in Sources */, 032160EC1F04EE7100C9AE3E /* OSStatus+LocalizedError.swift in Sources */, @@ -641,7 +640,7 @@ 032160E51F04EE7100C9AE3E /* MIDINoteDetection.swift in Sources */, 03CBB9391F7E5FA900574D4D /* MIDIEndpointRef+NetworkSessionIOS.swift in Sources */, 03A8F40F20A3010900171C35 /* RMS.swift in Sources */, - 03B103ED2063C484003CAE4D /* CoreMIDIOutConnection.swift in Sources */, + 03B103ED2063C484003CAE4D /* MIDIOutConnectionApple.swift in Sources */, 032160D31F04EE7100C9AE3E /* AudioHelper.swift in Sources */, 032160EF1F04EE7100C9AE3E /* ProcessedAudio.swift in Sources */, 032160DF1F04EE7100C9AE3E /* MIDIDevice.swift in Sources */, @@ -655,6 +654,7 @@ 032160E91F04EE7100C9AE3E /* NoteDetection.swift in Sources */, 032160DA1F04EE7100C9AE3E /* Filter.swift in Sources */, 03779181206C161100350967 /* YamahaMessages.swift in Sources */, + 0368462220B88457006C3C9E /* LightController.swift in Sources */, 032160D21F04EE7100C9AE3E /* AudioEngineIOS.swift in Sources */, 032160DC1F04EE7100C9AE3E /* Filterbank+ChromaVector.swift in Sources */, 032160E71F04EE7100C9AE3E /* MIDIObjectRef.swift in Sources */, @@ -685,7 +685,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 03B103EE2063C484003CAE4D /* CoreMIDIOutConnection.swift in Sources */, + 03B103EE2063C484003CAE4D /* MIDIOutConnectionApple.swift in Sources */, 5CC5C8E11F0BEB940037F1FF /* AudioEngineMac.swift in Sources */, 5C0E568B1F0BE96900793A7F /* AudioNoteDetection.swift in Sources */, 5C0E56951F0BE96900793A7F /* MIDINoteDetection.swift in Sources */, @@ -697,6 +697,7 @@ 5C0E56961F0BE96900793A7F /* MIDINumber.swift in Sources */, 5C0E56971F0BE96900793A7F /* MusicalNote.swift in Sources */, 5C0E56991F0BE96900793A7F /* OnsetDetection.swift in Sources */, + 035A1DC320BAECF7009F4248 /* LightController.swift in Sources */, 5C0E56941F0BE96900793A7F /* InputType.swift in Sources */, 03DD8F271F7D571400E0D523 /* MIDIEngineApple.swift in Sources */, 5C0E56921F0BE96900793A7F /* FlowMathApple.swift in Sources */, @@ -706,7 +707,6 @@ 03779182206C161100350967 /* YamahaMessages.swift in Sources */, 5C0E56901F0BE96900793A7F /* FilterBank.swift in Sources */, 03DD8F2A1F7D58C600E0D523 /* OSStatus+LocalizedError.swift in Sources */, - 03779185206C163700350967 /* MIDIOutConnection.swift in Sources */, 03A8F41020A3040400171C35 /* RMS.swift in Sources */, 5C0E569E1F0BE96900793A7F /* ProcessedAudio.swift in Sources */, 5C0E568D1F0BE96900793A7F /* CrossPlatformHelper.swift in Sources */, diff --git a/Package.swift b/Package.swift index b51b08b3..4e3a360d 100644 --- a/Package.swift +++ b/Package.swift @@ -26,7 +26,7 @@ let package = Package( "MIDIEngine+Debug.swift", "MIDIPacket+UInt8Array.swift", "MIDIObjectRef.swift", - "CoreMIDIOutConnection.swift", + "MIDIOutConnectionApple.swift", "OSStatus+LocalizedError.swift", "AVAudioEngine+Category.swift" ] diff --git a/Sources/NoteDetection/LightController.swift b/Sources/NoteDetection/LightController.swift new file mode 100644 index 00000000..ccf0e403 --- /dev/null +++ b/Sources/NoteDetection/LightController.swift @@ -0,0 +1,58 @@ +// +// LightController.swift +// NoteDetectionIOS +// +// Created by flowing erik on 25.05.18. +// Copyright © 2018 flowkey. All rights reserved. +// + +import Foundation + +public typealias LightControlStatusChangedCallback = (LightControlStatus) -> Void + +public enum LightControlStatus { + case notAvailable // no compatible device is connected + case disabled // compatible device connected but disabled by user + case enabled // compatible device connected and enabled by user + + // toggles between enabled and disabled or returns notAvailable + public func toggled() -> LightControlStatus { + switch self { + case .enabled: return .disabled + case .disabled: return .enabled + case .notAvailable: return .notAvailable + } + } +} + +public protocol LightController { + func set(onLightControlStatusChanged: @escaping LightControlStatusChangedCallback) + func disableLightControl() throws + func enableLightControl() throws +} + +public enum LightControlError: Error { + case notAvailable +} + +extension NoteDetection: LightController { + public func set(onLightControlStatusChanged: @escaping (LightControlStatus) -> Void) { + self.onLightControlStatusChanged = onLightControlStatusChanged + } + + public func disableLightControl() throws { + guard let lightControl = lightControl else { + throw LightControlError.notAvailable + } + lightControl.isEnabled = false + onLightControlStatusChanged?(.disabled) + } + + public func enableLightControl() throws { + guard let lightControl = lightControl else { + throw LightControlError.notAvailable + } + lightControl.isEnabled = true + onLightControlStatusChanged?(.enabled) + } +} diff --git a/Sources/NoteDetection/MIDIEngineApple.swift b/Sources/NoteDetection/MIDIEngineApple.swift index 96aca2c0..994cc0fe 100644 --- a/Sources/NoteDetection/MIDIEngineApple.swift +++ b/Sources/NoteDetection/MIDIEngineApple.swift @@ -94,7 +94,7 @@ class MIDIEngine { for destIndex in 0 ..< MIDIGetNumberOfDestinations() { let destination = MIDIGetDestination(destIndex) let destRefCon = UnsafeMutablePointer.allocate(capacity: 0) - midiOutConnections.append(CoreMIDIOutConnection( + midiOutConnections.append(MIDIOutConnection( source: outputPort, destination: destination, refCon: destRefCon diff --git a/Sources/NoteDetection/MIDIOutConnection.swift b/Sources/NoteDetection/MIDIOutConnection.swift deleted file mode 100644 index 741c1476..00000000 --- a/Sources/NoteDetection/MIDIOutConnection.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// MIDIOutConnection.swift -// NoteDetectionIOS -// -// Created by flowing erik on 22.03.18. -// Copyright © 2018 flowkey. All rights reserved. -// - -public protocol MIDIOutConnection { - var displayName: String { get } - func send(messages: [[UInt8]]) -} diff --git a/Sources/NoteDetection/MIDIOutConnectionAndroid.swift b/Sources/NoteDetection/MIDIOutConnectionAndroid.swift new file mode 100644 index 00000000..298c336f --- /dev/null +++ b/Sources/NoteDetection/MIDIOutConnectionAndroid.swift @@ -0,0 +1,33 @@ +// +// MIDIOutConnectionAndroid.swift +// NoteDetection +// +// Created by flowing erik on 22.03.18. +// Copyright © 2018 flowkey. All rights reserved. +// + +// MIDIOutConnection stub for Android, to be implemented when we support Yamaha Light Control in Android +public class MIDIOutConnection { + private let refCon: UnsafeMutablePointer + + init(refCon: UnsafeMutablePointer) { + self.refCon = refCon + } +} + +extension MIDIOutConnection { + public var displayName: String { + // TODO: Implement Me + return "MIDIOutConnection Stub" + } + + public func send(messages: [[UInt8]]) { + // TODO: Implement Me + } +} + +extension MIDIOutConnection: Equatable { + public static func == (lhs: MIDIOutConnection, rhs: MIDIOutConnection) -> Bool { + return lhs.refCon == rhs.refCon + } +} diff --git a/Sources/NoteDetection/CoreMIDIOutConnection.swift b/Sources/NoteDetection/MIDIOutConnectionApple.swift similarity index 91% rename from Sources/NoteDetection/CoreMIDIOutConnection.swift rename to Sources/NoteDetection/MIDIOutConnectionApple.swift index 761a5e00..1001498a 100644 --- a/Sources/NoteDetection/CoreMIDIOutConnection.swift +++ b/Sources/NoteDetection/MIDIOutConnectionApple.swift @@ -9,7 +9,7 @@ import Foundation import CoreMIDI -class CoreMIDIOutConnection: MIDIOutConnection { +public class MIDIOutConnection { let source: MIDIPortRef let destination: MIDIEndpointRef let refCon: UnsafeMutablePointer @@ -37,9 +37,9 @@ class CoreMIDIOutConnection: MIDIOutConnection { } } -extension CoreMIDIOutConnection: Hashable { +extension MIDIOutConnection: Hashable { public var hashValue: Int { return refCon.hashValue } - public static func == (lhs: CoreMIDIOutConnection, rhs: CoreMIDIOutConnection) -> Bool { + public static func == (lhs: MIDIOutConnection, rhs: MIDIOutConnection) -> Bool { return lhs.refCon == rhs.refCon } } diff --git a/Sources/NoteDetection/NoteDetection.swift b/Sources/NoteDetection/NoteDetection.swift index 2bdbc90d..20e94947 100644 --- a/Sources/NoteDetection/NoteDetection.swift +++ b/Sources/NoteDetection/NoteDetection.swift @@ -24,7 +24,16 @@ public class NoteDetection { var noteDetector: NoteDetector! // implicitly unwrapped so we can use self.createNoteDetector() on init let audioEngine: AudioEngine let midiEngine: MIDIEngine - var lightControl: YamahaLightControl? + var lightControl: YamahaLightControl? { + didSet { + if let lightControl = lightControl { + onLightControlStatusChanged?(lightControl.isEnabled ? .enabled : .disabled) + } else { + onLightControlStatusChanged?(.notAvailable) + } + } + } + var onLightControlStatusChanged: LightControlStatusChangedCallback? fileprivate var ignoreUntilDeadline: Timestamp? @@ -46,9 +55,8 @@ public class NoteDetection { } midiEngine.set(onMIDIOutConnectionsChanged: { [weak self] outConnections in - if outConnections.count == 0 { - // kill lightControl if there are no connections - // ToDo: actually check if outConnections contains lightControl.connection + if let lightControl = self?.lightControl, !outConnections.contains(lightControl.connection) { + // kill lightControl if outconnnection is gone self?.lightControl = nil } YamahaLightControl.sendClavinovaModelRequest(on: outConnections) diff --git a/Sources/NoteDetection/YamahaLightControl.swift b/Sources/NoteDetection/YamahaLightControl.swift index aa44e730..6a819355 100644 --- a/Sources/NoteDetection/YamahaLightControl.swift +++ b/Sources/NoteDetection/YamahaLightControl.swift @@ -8,6 +8,12 @@ private let NOTE_OFF: UInt8 = 8 class YamahaLightControl { let connection: MIDIOutConnection + var isEnabled = false { + didSet { + if isEnabled { animateLights() } + else { turnOffLights(at: currentLightningKeys) } + } + } // MARK: Public API @@ -17,7 +23,11 @@ class YamahaLightControl { self.switchGuideOn() self.switchLightsOnNoSound() self.turnOffAllLights() - self.animateLights() + self.isEnabled = true + } + + deinit { + self.turnOffAllLights() } private var currentLightningKeys: [UInt8] = [] { @@ -80,6 +90,7 @@ class YamahaLightControl { // MARK: Private API private func turnOnLights(at keys: [UInt8]) { + guard isEnabled else { return } keys.forEach { key in let message = self.createNoteOnMessage(channel: LIGHT_CONTROL_CHANNEL, key: key) self.connection.send(messages: [message])