You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS AudioKit中AKSequencer无法准确重启播放问题

Fixing AKSequencer Update & Playback Issues in Your AudioKit Project

Let's break down why your sequencer isn't updating correctly when you modify parameters after the first play, and fix it step by step.

Key Issues in Your Current Code

  1. You're creating new tracks every time playBack() runs — old tracks remain in the sequencer and conflict with new ones, causing unexpected behavior on subsequent plays.
  2. The callback is set inside a loop — this overwrites the callback multiple times unnecessarily, and it only needs to be configured once.
  3. You're not utilizing your Line struct's beat logic — your current code ignores beatsPerMeasure, so you're not generating the correct number of beats per measure.
  4. No cleanup before reconfiguring the sequencer — when you rewind and adjust parameters, the sequencer still holds old track data.

Corrected Code

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 with default beats per measure (e.g., 4/4 time)
    var intro = Line(name: "Intro", measures: 1, beatsPerMeasure: 4)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set up AudioKit once
        AudioKit.output = click
        try! AudioKit.start()
        
        // Configure callback ONCE, not in a loop
        callbackInst.callback = { status, noteNumber, velocity in
            guard status == .noteOn else { return }
            print("beat number: \(noteNumber + 1)")
        }
    }
    
    // A function to create/update/playback the sequence on button press
    func playBack() {
        // Critical: Clear all existing tracks before creating new ones
        sequencer.stop()
        sequencer.deleteAllTracks()
        
        let metronomeTrack = sequencer.newTrack()
        metronomeTrack?.setMIDIOutput(click.midiIn)
        
        let callbackTrack = sequencer.newTrack()
        callbackTrack?.setMIDIOutput(callbackInst.midiIn)
        
        // Calculate total beats using your Line struct
        let totalBeats = intro.totalBeats()
        
        for beat in 0..<totalBeats {
            let beatPosition = AKDuration(beats: Double(beat))
            
            // Trigger metronome on each beat
            metronomeTrack?.add(noteNumber: 60, velocity: 100, position: beatPosition, duration: AKDuration(beats: 0.5))
            
            // Send beat number as MIDI note to callback
            callbackTrack?.add(noteNumber: MIDINoteNumber(beat), velocity: 100, position: beatPosition, duration: AKDuration(beats: 0.5))
        }
        
        // Rewind to start after setting up tracks
        sequencer.rewind()
    }
    
    @IBOutlet weak var rowOneLocationOne: UIImageView!
    
    // Listener for UI display values
    var measuresRowOneValue: Int = 1 {
        didSet {
            intro.measures = measuresRowOneValue
        }
    }
    
    @IBAction func rowOnePlusButton(_ sender: UIButton) {
        measuresRowOneValue += 1
    }
    
    @IBAction func rowOneMinusButton(_ sender: UIButton) {
        // Prevent negative measures
        guard measuresRowOneValue > 1 else { return }
        measuresRowOneValue -= 1
    }
    
    @IBAction func playbackStart(_ sender: UIButton) {
        playBack()
        sequencer.play()
    }
    
    @IBAction func playbackStop(_ sender: UIButton) {
        sequencer.stop()
    }
    
    @IBAction func playbackRestart(_ sender: UIButton) {
        sequencer.stop()
        sequencer.rewind()
    }
}

What Changed & Why

  • Track Cleanup: Added sequencer.deleteAllTracks() at the start of playBack() — this removes all old tracks so new configurations don't conflict with previous ones.
  • Callback Configuration: Moved the callback setup to viewDidLoad() — callbacks only need to be defined once, not every time you rebuild the sequence.
  • Utilized Line Struct: Now uses intro.totalBeats() to calculate the correct number of beats based on measures and beats per measure, which aligns with your original struct design.
  • Prevented Negative Measures: Added a guard clause in the minus button action to avoid invalid 0 or negative measure counts.
  • Rewind After Setup: Added sequencer.rewind() after building tracks to ensure playback starts from the first beat every time.

Additional Tips

  • If you want to change beatsPerMeasure dynamically (e.g., switch between 4/4 and 3/4), add a UI control to update intro.beatsPerMeasure just like you do for measures.
  • For more complex sequencing, consider storing track references as class properties instead of recreating them every time, but deleting tracks is the simplest fix for your use case.

内容的提问来源于stack exchange,提问作者moosgrn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:48:22