问题
I'm trying to use AudioKit to playback a sound on each beat of a measure(s). Although I've implemented the code from this similar question regarding callbacks via AudioKit, I can't seem to get the sequencer to update changes and playback properly. It will play once accurately, however after rewinding and changing the values it will only use the initial values (or not playback at all).
My intent is to create a struct of measures with beat values for each measure, then use MIDI and the callback to play different sounds dependent on how many measures/beats there are. Thanks!
import UIKit
import AudioKit
class ViewController: UIViewController {
let sequencer = AKSequencer()
let click = AKSynthSnare()
let callbackInst = AKCallbackInstrument()
// Create the struct that defines each line
struct Line {
var name: String
var measures: Int
var beatsPerMeasure: Int
func totalBeats() -> Int {
return (measures * beatsPerMeasure)
}
}
// Initialize intro line
var intro = Line(name: "Intro", measures: 0, beatsPerMeasure: 0)
// A function to create/update/playback the sequence on button press
func playBack() {
let metronomeTrack = sequencer.newTrack()
metronomeTrack?.setMIDIOutput(click.midiIn)
let callbackTrack = sequencer.newTrack()
callbackTrack?.setMIDIOutput(callbackInst.midiIn)
for steps in 0 ... Int(measuresRowOneValue) {
// this will trigger the sampler on the four down beats
metronomeTrack?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
// set the midiNote number to the current beat number
callbackTrack?.add(noteNumber: MIDINoteNumber(steps), velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
// set the callback
callbackInst.callback = {status, noteNumber, velocity in
guard status == .noteOn else { return }
print("beat number: \(noteNumber + 1)")
}
}
}
@IBOutlet weak var rowOneLocationOne: UIImageView!
// Listener for UI display values
var measuresRowOneValue: Int = 0 {
didSet {
intro.measures = measuresRowOneValue
}
}
@IBAction func rowOnePlusButton(_ sender: UIButton) {
measuresRowOneValue += 1
}
@IBAction func rowOneMinusButton(_ sender: UIButton) {
measuresRowOneValue -= 1
}
@IBAction func playbackStart(_ sender: UIButton) {
playBack()
sequencer.play()
}
@IBAction func playbackStop(_ sender: UIButton) {
sequencer.stop()
}
@IBAction func playbackRestart(_ sender: UIButton) {
sequencer.rewind()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
AudioKit.output = click
try!AudioKit.start()
}
}
回答1:
There are a few confusing things in your code, so I'm not sure if this is your only issue, but minimally, every time you change the length of your sequence, you will need to call setLength()
followed by enableLooping
. Basically, by default (i.e., unless you explicitly set the length) the length of the sequence will be the length of the longest track in the sequence. In your 'playback' method you are adding track on top of track without removing the old ones so it has no way of knowing how long you intend the sequence to be.
Your 'playback' method is doing two distinct things (neither of which involves playback). You might want to break it up. You could have a setup()
to do the things that only ever need to be done once (create the tracks, set their outputs, set up the callback) and a rewriteSequence()
methods that gets called when you want to re-write the tracks. This way you can reuse your existing tracks rather than continuously creating new ones.
var metronomeTrack: AKMusicTrack!
var callbackTrack: AKMusicTrack!
// call this just once at the start
func setup() {
metronomeTrack = sequencer.newTrack()
metronomeTrack?.setMIDIOutput(click.midiIn)
callbackTrack = sequencer.newTrack()
callbackTrack?.setMIDIOutput(callbackInst.midiIn)
callbackInst.callback = {status, noteNumber, velocity in
guard status == .noteOn else { return }
print("beat number: \(noteNumber + 1)")
}
}
// call this each time you want to change the sequence
func rewriteSequence() {
metronomeTrack?.clear()
callbackTrack?.clear()
for steps in 0 ... Int(measuresRowOneValue) {
metronomeTrack?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
callbackTrack?.add(noteNumber: MIDINoteNumber(steps), velocity: 100, position: AKDuration(beats: Double(steps)), duration: AKDuration(beats: 0.5))
}
// This will make sure it loops correctly:
sequencer.setLength(AKDuration(beats: Double(measuresRowOneValue)))
sequencer.enableLooping()
}
I hope this helps.
来源:https://stackoverflow.com/questions/50622527/audiokit-ios-aksequencer-not-restarting-playback-accurately