I am trying to create a midi app in SwiftUI. I would like to play two instruments in a song.
I have created a test app based on code I found on the internet.
First you should hear some notes of violin, then piano. However, after playing the first note, all the following notes are played with piano sound.
How can I solve this?
The code is as follows:
import SwiftUI
struct ContentView: View {
@StateObject var player = MidiPlayer()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Button(action: playSong) {
Text("Hello, world!")
}
}
.padding()
.onAppear {
playSong()
}
}
private func playSong() {
let composer = SongComposer()
let song = composer.compose()
player.prepareSong(song: song)
player.playSong()
}
}
import Foundation
import AVFoundation
class MidiPlayer: ObservableObject {
var midiPlayer: AVMIDIPlayer?
var bankURL: URL
init() {
guard let bankURL = Bundle.main.url(forResource: "Nokia_Tongbao_Bank__Series_30__8-bit", withExtension: "sf2") else {
fatalError("\"Nokia_Tongbao_Bank__Series_30__8-bit.sf2\" file not found.")
}
self.bankURL = bankURL
}
func prepareSong(song: Song) {
var data: Unmanaged<CFData>?
guard MusicSequenceFileCreateData(song.musicSequence!, MusicSequenceFileTypeID.midiType, MusicSequenceFileFlags.eraseFile, 480, &data) == OSStatus(noErr) else {
fatalError("Cannot create music midi data")
}
if let md = data {
let midiData = md.takeUnretainedValue() as Data
do {
try self.midiPlayer = AVMIDIPlayer(data: midiData, soundBankURL: self.bankURL)
} catch let error {
fatalError(error.localizedDescription)
}
}
self.midiPlayer!.prepareToPlay()
}
func playSong() {
DispatchQueue.main.async {
if let md = self.midiPlayer {
md.currentPosition = 0
md.play()
}
}
}
}
import Foundation
import AVFoundation
class Song {
var musicSequence: MusicSequence?
var tracks: [Int: MusicTrack] = [:]
init() {
guard NewMusicSequence(&musicSequence) == OSStatus(noErr) else {
fatalError("Cannot create MusicSequence")
}
}
func addTrack(instrumentId: UInt8) -> Int {
var track: MusicTrack?
guard MusicSequenceNewTrack(musicSequence!, &track) == OSStatus(noErr) else {
fatalError("Cannot add track")
}
let trackId = tracks.count
tracks[trackId] = track
var inMessage = MIDIChannelMessage(status: 0xC0, data1: instrumentId, data2: 0, reserved: 0)
MusicTrackNewMIDIChannelEvent(track!, 0, &inMessage)
return trackId
}
func setTempo(tempo: Float64) {
let timeStamp = MusicTimeStamp(0)
var tempoTrack: MusicTrack?
MusicSequenceGetTempoTrack(musicSequence! ,&tempoTrack)
removeTempoEvents(tempoTrack: tempoTrack!)
MusicTrackNewExtendedTempoEvent(tempoTrack!, timeStamp, tempo)
}
private func removeTempoEvents(tempoTrack: MusicTrack) {
var tempIter: MusicEventIterator?
NewMusicEventIterator(tempoTrack, &tempIter)
var hasEvent: DarwinBoolean = false
MusicEventIteratorHasCurrentEvent(tempIter!, &hasEvent)
while (hasEvent == true) {
var stamp = MusicTimeStamp(0)
var type: MusicEventType = 0
var data: UnsafeRawPointer? = nil
var sizeData: UInt32 = 0
MusicEventIteratorGetEventInfo(tempIter!, &stamp, &type, &data, &sizeData)
if (type == kMusicEventType_ExtendedTempo) {
MusicEventIteratorDeleteEvent(tempIter!);
MusicEventIteratorHasCurrentEvent(tempIter!, &hasEvent)
} else {
MusicEventIteratorNextEvent(tempIter!)
MusicEventIteratorHasCurrentEvent(tempIter!, &hasEvent)
}
}
DisposeMusicEventIterator(tempIter!)
}
func addNote(trackId: Int, note: UInt8, duration: Float, position: Float) {
let time = MusicTimeStamp(position)
var musicNote = MIDINoteMessage(channel: 0,
note: note,
velocity: 64,
releaseVelocity: 0,
duration: duration)
guard MusicTrackNewMIDINoteEvent(tracks[trackId]!, time, &musicNote) == OSStatus(noErr) else {
fatalError("Cannot add Note")
}
}
}
import Foundation
class SongComposer {
func compose() -> Song {
let song = Song()
song.setTempo(tempo: 240)
var currentPosition: Float = 0
let trackId = song.addTrack(instrumentId: 48)
for _ in 0...1 {
song.addNote(trackId: trackId, note: 64,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
song.addNote(trackId: trackId, note: 63,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
}
song.addNote(trackId: trackId, note: 64,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
song.addNote(trackId: trackId, note: 59,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
song.addNote(trackId: trackId, note: 62,
duration: 1.0, position: currentPosition)
currentPosition += 2.0
song.addNote(trackId: trackId, note: 57, duration: 3.0, position:currentPosition)
song.addNote(trackId: trackId, note: 45, duration: 3.0, position:currentPosition)
currentPosition -= 1.0
song.addNote(trackId: trackId, note: 60,
duration: 1.0, position: currentPosition)
currentPosition += 4.0
// track 2
let track2Id = song.addTrack(instrumentId: 00)
for _ in 0...1 {
song.addNote(trackId: track2Id, note: 64,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
song.addNote(trackId: track2Id, note: 63,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
}
song.addNote(trackId: track2Id, note: 64,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
song.addNote(trackId: track2Id, note: 59,
duration: 1.0, position: currentPosition)
currentPosition += 1.0
song.addNote(trackId: track2Id, note: 62,
duration: 1.0, position: currentPosition)
currentPosition += 2.0
song.addNote(trackId: track2Id, note: 57, duration: 3.0, position:currentPosition)
song.addNote(trackId: trackId, note: 45, duration: 3.0, position:currentPosition)
currentPosition -= 1.0
song.addNote(trackId: track2Id, note: 60,
duration: 1.0, position: currentPosition)
return song
}
}
Thanks in advance.
I finaly found the solution for my problem. I made the following changes to the code:
Below the modified code for the Song class:
import Foundation
import AVFoundation
class Song {
var musicSequence: MusicSequence?
var tracks: [Int: MusicTrack] = [:]
init() {
guard NewMusicSequence(&musicSequence) == OSStatus(noErr) else {
fatalError("Cannot create MusicSequence")
}
}
func addTrack(instrumentId: UInt8) -> Int {
var track: MusicTrack?
guard MusicSequenceNewTrack(musicSequence!, &track) == OSStatus(noErr) else {
fatalError("Cannot add track")
}
let trackId = tracks.count
tracks[trackId] = track
let time = MusicTimeStamp(0.0)
var inMessage = MIDIChannelMessage(status: self.getStatus(trackId: trackId), data1: instrumentId, data2: 0, reserved: 0)
MusicTrackNewMIDIChannelEvent(track!, time, &inMessage)
return trackId
}
func setInstrument(trackId: Int, instrumentId: UInt8, position: Float) {
if let track = self.tracks[trackId] {
let time = MusicTimeStamp(position)
var inMessage = MIDIChannelMessage(status: self.getStatus(trackId: trackId), data1: instrumentId, data2: 0, reserved: 0)
MusicTrackNewMIDIChannelEvent(track, time, &inMessage)
}
}
private func getStatus(trackId: Int) -> UInt8 {
switch trackId {
case 0:
return 0xC0
case 1:
return 0xC1
case 2:
return 0xC2
case 3:
return 0xC3
case 4:
return 0xC4
case 5:
return 0xC5
case 6:
return 0xC6
case 7:
return 0xC7
case 8:
return 0xC8
case 9:
return 0xC9
case 10:
return 0xCA
case 11:
return 0xCB
case 12:
return 0xCC
case 13:
return 0xCD
case 14:
return 0xCE
case 15:
return 0xCF
default:
return 0xC0
}
}
func setTempo(tempo: Float64) {
let timeStamp = MusicTimeStamp(0)
var tempoTrack: MusicTrack?
MusicSequenceGetTempoTrack(musicSequence! ,&tempoTrack)
removeTempoEvents(tempoTrack: tempoTrack!)
MusicTrackNewExtendedTempoEvent(tempoTrack!, timeStamp, tempo)
}
private func removeTempoEvents(tempoTrack: MusicTrack) {
var tempIter: MusicEventIterator?
NewMusicEventIterator(tempoTrack, &tempIter)
var hasEvent: DarwinBoolean = false
MusicEventIteratorHasCurrentEvent(tempIter!, &hasEvent)
while (hasEvent == true) {
var stamp = MusicTimeStamp(0)
var type: MusicEventType = 0
var data: UnsafeRawPointer? = nil
var sizeData: UInt32 = 0
MusicEventIteratorGetEventInfo(tempIter!, &stamp, &type, &data, &sizeData)
if (type == kMusicEventType_ExtendedTempo) {
MusicEventIteratorDeleteEvent(tempIter!);
MusicEventIteratorHasCurrentEvent(tempIter!, &hasEvent)
} else {
MusicEventIteratorNextEvent(tempIter!)
MusicEventIteratorHasCurrentEvent(tempIter!, &hasEvent)
}
}
DisposeMusicEventIterator(tempIter!)
}
func addNote(trackId: Int, note: UInt8, duration: Float, position: Float) {
let time = MusicTimeStamp(position)
var musicNote = MIDINoteMessage(channel: UInt8(trackId <= 15 ? trackId : 0),
note: note,
velocity: 64,
releaseVelocity: 0,
duration: duration)
guard MusicTrackNewMIDINoteEvent(tracks[trackId]!, time, &musicNote) == OSStatus(noErr) else {
fatalError("Cannot add Note")
}
}
}
Happy programming! Kristof Nijs