Skip to content

Instantly share code, notes, and snippets.

@davidfloegel
Created August 13, 2020 20:40
Show Gist options
  • Save davidfloegel/76c4e10b5dd40fe3887ef31f3f3fb50f to your computer and use it in GitHub Desktop.
Save davidfloegel/76c4e10b5dd40fe3887ef31f3f3fb50f to your computer and use it in GitHub Desktop.
//
// 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