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

使用AKKeyboardView连接AKRhodesPiano时如何生成MIDI文件?

Generating MIDI Files from AKKeyboardView + AKRhodesPiano in AudioKit

Great question! I’ve tackled this exact use case before, and there are two solid approaches—one super straightforward using AudioKit’s built-in tools, and a more manual method if you need full control over the MIDI data. Let’s break them down:

The Easy Way: Use AKMIDIRecorder

AudioKit has a AKMIDIRecorder class that handles all the heavy lifting of capturing MIDI events and writing them to a file. This is by far the simplest approach since you don’t have to manage timestamps or MIDI file formatting yourself.

Step 1: Set Up the Recorder

First, initialize an AKMIDIRecorder and point it to a file path where you want to save the MIDI file:

import AudioKit

class YourViewController: UIViewController {
    var rhodesPiano = AKRhodesPiano()
    var midiRecorder: AKMIDIRecorder!
    var midiFileURL: URL!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set up your AudioKit engine (assuming you already have this)
        AudioKit.output = rhodesPiano
        try? AudioKit.start()
        
        // Create a URL for the MIDI file (save to Documents directory)
        let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        midiFileURL = documentsDir.appendingPathComponent("rhodes_recording.mid")
        
        // Initialize the recorder
        do {
            midiRecorder = try AKMIDIRecorder()
        } catch {
            print("Failed to initialize MIDI recorder: \(error.localizedDescription)")
            return
        }
    }
}

Step 2: Connect Keyboard Events to the Recorder

Update your AKKeyboardView callbacks to send note events to both the Rhodes piano and the MIDI recorder. This way, you hear the sound while capturing the MIDI data:

// Assuming you have an AKKeyboardView instance hooked up
keyboardView.noteOn = { [weak self] noteNumber, velocity in
    guard let self = self else { return }
    // Play the note on the Rhodes
    self.rhodesPiano.play(noteNumber: noteNumber, velocity: velocity)
    // Send the Note On event to the recorder
    self.midiRecorder.sendEvent(AKMIDIEvent.noteOn(noteNumber: noteNumber, velocity: velocity, channel: 0))
}

keyboardView.noteOff = { [weak self] noteNumber in
    guard let self = self else { return }
    // Stop the note on the Rhodes
    self.rhodesPiano.stop(noteNumber: noteNumber)
    // Send the Note Off event to the recorder
    self.midiRecorder.sendEvent(AKMIDIEvent.noteOff(noteNumber: noteNumber, velocity: 0, channel: 0))
}

Step 3: Start/Stop Recording

Add controls to start and stop the recording (like buttons in your UI):

@IBAction func startRecordingTapped(_ sender: UIButton) {
    do {
        try midiRecorder.startRecording(to: midiFileURL)
        print("Recording started!")
    } catch {
        print("Failed to start recording: \(error.localizedDescription)")
    }
}

@IBAction func stopRecordingTapped(_ sender: UIButton) {
    do {
        try midiRecorder.stopRecording()
        print("MIDI file saved to: \(midiFileURL.path)")
    } catch {
        print("Failed to stop recording: \(error.localizedDescription)")
    }
}

Manual Method: Record Events & Build the MIDI File Yourself

If you need more control over the MIDI data (like editing events before saving), you can manually capture note events and construct an AKMIDIFile from scratch.

Step 1: Define a Struct to Store Events

First, create a simple struct to track each note’s details and timestamp:

struct MIDINoteEvent {
    let noteNumber: MIDINoteNumber
    let velocity: MIDIVelocity
    let isNoteOn: Bool
    let timestamp: Double // Uses AKClock's synchronized time
}

var recordedEvents: [MIDINoteEvent] = []

Step 2: Capture Keyboard Events

Update your keyboard callbacks to save events with timestamps from AKClock (this ensures timing matches AudioKit’s playback):

keyboardView.noteOn = { note, velocity in
    let event = MIDINoteEvent(
        noteNumber: note,
        velocity: velocity,
        isNoteOn: true,
        timestamp: AKClock.currentTime
    )
    recordedEvents.append(event)
    self.rhodesPiano.play(noteNumber: note, velocity: velocity)
}

keyboardView.noteOff = { note in
    let event = MIDINoteEvent(
        noteNumber: note,
        velocity: 0,
        isNoteOn: false,
        timestamp: AKClock.currentTime
    )
    recordedEvents.append(event)
    self.rhodesPiano.stop(noteNumber: note)
}

Step 3: Generate & Save the MIDI File

Use AKMIDIFile to convert your recorded events into a valid MIDI file. You’ll need to convert timestamps to MIDI ticks (based on your project’s tempo):

func generateAndSaveMIDIFile() throws {
    guard !recordedEvents.isEmpty else {
        throw NSError(domain: "MIDIError", code: 0, userInfo: [NSLocalizedDescriptionKey: "No recorded events to save"])
    }
    
    // Configure MIDI file settings (standard 480 ticks per quarter note, 120 BPM)
    let ticksPerQuarterNote: UInt16 = 480
    let tempo = 120.0
    let secondsPerTick = 60.0 / (tempo * Double(ticksPerQuarterNote))
    
    // Create an empty MIDI file
    var midiFile = AKMIDIFile()
    
    // Calculate relative timestamps from the first event
    let startTime = recordedEvents.first!.timestamp
    var currentTick: UInt32 = 0
    
    for event in recordedEvents {
        let relativeTime = event.timestamp - startTime
        let eventTick = UInt32(relativeTime / secondsPerTick)
        let deltaTick = eventTick - currentTick
        
        if event.isNoteOn {
            try midiFile.addNoteOn(
                noteNumber: event.noteNumber,
                velocity: event.velocity,
                channel: 0,
                delta: deltaTick
            )
        } else {
            try midiFile.addNoteOff(
                noteNumber: event.noteNumber,
                velocity: event.velocity,
                channel: 0,
                delta: deltaTick
            )
        }
        
        currentTick = eventTick
    }
    
    // Add an end-of-track marker
    try midiFile.addEndOfTrack(delta: 0)
    
    // Save the file
    let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let saveURL = documentsDir.appendingPathComponent("custom_rhodes_recording.mid")
    try midiFile.write(to: saveURL)
    
    print("Custom MIDI file saved to: \(saveURL.path)")
}

Key Notes for Both Methods

  • MIDI Channels: The examples use channel 0 (which maps to MIDI channel 1 in most DAWs). Adjust this if you need to record on a different channel.
  • Tempo: If your project uses a non-120 BPM tempo, update the tempo value in the manual method to match your AKClock’s tempo.
  • AudioKit Version: These examples work with AudioKit 5+. If you’re on an older version, some class names or methods might differ.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:18:31