Created
August 13, 2020 20:40
-
-
Save davidfloegel/76c4e10b5dd40fe3887ef31f3f3fb50f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// MidiPlayer.swift | |
// harmonic | |
// | |
// Created by David Floegel on 03/07/2020. | |
// | |
import Foundation | |
import AudioKit | |
import UIKit | |
@objc(MidiPlayer) | |
class MidiPlayer: RCTEventEmitter { | |
var isSetUp = false | |
var mixer = AKMixer() | |
// playback = | |
var sequencer = AKSequencer() | |
var sampler = AKAppleSampler() | |
var noteNumbers: [NSNumber] = [] | |
// listening | |
var tracker: AKFrequencyTracker! | |
var startTime: TimeInterval = 0 | |
var recordedFrequencies: [[Double]] = [] | |
var timer: Timer? | |
override init() { | |
super.init() | |
} | |
@objc | |
func setupSynth() { | |
if (isSetUp) { | |
print("Synth already setup") | |
return; | |
} | |
print("Setup synth") | |
try? AudioKit.stop() | |
try? AKSettings.setSession(category: .playAndRecord) | |
AKSettings.playbackWhileMuted = true | |
AKSettings.defaultToSpeaker = true | |
try? sampler.loadSoundFont("UprightPiano", preset: 0, bank: 0) | |
sequencer = AKSequencer(targetNode: sampler) | |
sampler.setOutput(to: self.mixer) | |
// ------------------------------------ | |
let microphone = AKMicrophone() | |
let filter = AKLowPassFilter(microphone, cutoffFrequency: 20000, resonance: 0) | |
self.tracker = AKFrequencyTracker(filter) | |
let silence = AKBooster(tracker, gain: 0) | |
self.tracker.stop() | |
silence.setOutput(to: self.mixer) // set output to mixer | |
AKSettings.audioInputEnabled = true | |
// | |
AudioKit.output = self.mixer | |
do { | |
try AudioKit.start() | |
} catch { | |
print("start audio kit") | |
print(error) | |
} | |
self.isSetUp = true | |
} | |
@objc | |
func destroySynth() { | |
print("Destroy synth") | |
if (self.isSetUp) { | |
do { | |
try AudioKit.stop() | |
AKSettings.audioInputEnabled = false | |
self.isSetUp = false | |
} catch { | |
print(error) | |
} | |
} | |
} | |
@objc | |
func playNotation(_ bpm: NSNumber, midiNotes: [NSDictionary]) { | |
sequencer.tracks[0].clear() | |
for (i, note) in midiNotes.enumerated() { | |
sequencer.tracks[0].add( | |
noteNumber: UInt8(truncating: note["midiValue"] as! NSNumber), | |
velocity: 100, | |
position: Double(i), | |
duration: Double(truncating: note["duration"] as! NSNumber) / 1000 | |
) | |
noteNumbers.append(note["midiValue"] as! NSNumber) | |
} | |
// TODO for some reason this doesn't correspond to the total of note durations | |
// it's also very unclear whether this is milliseconds, or seconds, or whatever. | |
sequencer.length = 600 // sequencer.tracks[0].length | |
sequencer.tempo = Double(truncating: bpm) | |
sequencer.loopEnabled = false | |
sequencer.playFromStart() | |
} | |
@objc | |
func stopNotation() { | |
sequencer.tracks[0].stop() | |
sequencer.stop() | |
for (note) in self.noteNumbers { | |
try? self.sampler.stop(noteNumber: UInt8(truncating: note), channel: 0) | |
} | |
} | |
@objc | |
func startListening() { | |
print("Start listening") | |
self.startTime = NSDate().timeIntervalSince1970 | |
self.tracker.start() | |
DispatchQueue.main.async(execute: { | |
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.collectFrequencies), userInfo: nil, repeats: true) | |
}) | |
} | |
@objc | |
func stopListening() { | |
print("Stop listening") | |
self.timer?.invalidate() | |
self.tracker.stop() | |
self.recordedFrequencies = []; | |
AKSettings.audioInputEnabled = false | |
} | |
@objc | |
func collectFrequencies() { | |
if (self.tracker != nil && self.tracker.isStarted) { | |
let f = self.tracker.frequency | |
let a = self.tracker.amplitude | |
let t = NSDate().timeIntervalSince1970 - self.startTime | |
if f > 100 && f <= 2000 { | |
self.recordedFrequencies.append([t * 1000, f, a]) | |
self.sendEvent(withName: "readFrequency", body: f) | |
} | |
} | |
} | |
@objc | |
func getFrequencies(_ callback: RCTResponseSenderBlock) { | |
callback([self.recordedFrequencies]) | |
} | |
@objc | |
override static func requiresMainQueueSetup() -> Bool { | |
return true | |
} | |
// Thread 1: Exception: "Error when sending event: readFrequency with body: 117.23126983642578. Bridge is not set. This is probably because you've explicitly synthesized the bridge in MidiPlayer, even though it's inherited from RCTEventEmitter." | |
// what's that? | |
@objc override func supportedEvents() -> [String]! { | |
return ["readFrequency"] | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment