1

I'm trying to use Apple's AVMIDIPlayer object for playing a MIDI file. It seems easy enough in Swift, using the following code:

let midiFile:NSURL = NSURL(fileURLWithPath:"/path/to/midifile.mid")
var midiPlayer: AVMIDIPlayer?

do {
    try midiPlayer = AVMIDIPlayer(contentsOf: midiFile as URL, soundBankURL: nil)
    midiPlayer?.prepareToPlay()
} catch {
    print("could not create MIDI player")
}

midiPlayer?.play {
    print("finished playing")
}

And it plays for about 0.05 seconds. I presume I need to frame it in some kind of loop. I've tried a simple solution:

while stillGoing {
midiPlayer?.play {
    let stillGoing = false
}
}

which works, but ramps up the CPU massively. Is there a better way? Further to the first comment, I've tried making a class, and while it doesn't flag any errors, it doesn't work either.

class midiPlayer {

var player: AVMIDIPlayer?

    func play(file: String) {
        let myURL = URL(string: file)
do {
    try self.player = AVMIDIPlayer.init(contentsOf: myURL!, soundBankURL: nil)
    self.player?.prepareToPlay()
} catch {
    print("could not create MIDI player")
}
    self.player?.play()
    }
func stop() {
self.player?.stop()
}
}

// main

let myPlayer = midiPlayer()
let midiFile = "/path/to/midifile.mid"
myPlayer.play(file: midiFile)
Binarian
  • 12,296
  • 8
  • 53
  • 84
benwiggy
  • 1,440
  • 17
  • 35

3 Answers3

2

You need to ensure that the midiPlayer object exists until it's done playing. If the above code is just in a single function, midiPlayer will be destroyed when the function returns because there are no remaining references to it. Typically you would declare midiPlayer as a property of an object, like a subclassed controller.

Brendan Shanks
  • 3,141
  • 14
  • 13
  • So I have to create a class with this code as function/method, and then create an instance of the class, and call the method? So far, this is just a CLI script, so no UI controllers. – benwiggy Apr 26 '17 at 06:24
  • 1
    Ah ok, in that case using the loop is the only way to go. Put a 'sleep(1)' call inside the while loop so it doesn't eat all your CPU time. Also, I'm pretty sure you'll need to move the 'midiPlayer?.play' call out of the while loop. – Brendan Shanks Apr 26 '17 at 15:31
2

You were close with your loop. You just need to give the CPU time to go off and do other things instead of constantly checking to see if midiPlayer is finished yet. Add a call to usleep() in your loop. This one checks every tenth of a second:

let midiFile:NSURL = NSURL(fileURLWithPath:"/Users/steve/Desktop/Untitled.mid")
var midiPlayer: AVMIDIPlayer?

do {
    try midiPlayer = AVMIDIPlayer(contentsOfURL: midiFile, soundBankURL: nil)
    midiPlayer?.prepareToPlay()
} catch {
    print("could not create MIDI player")
}

var stillGoing = true
while stillGoing {
    midiPlayer?.play {
        print("finished playing")
        stillGoing = false
    }
    usleep(100000)
}
SSteve
  • 10,550
  • 5
  • 46
  • 72
0

Combining Brendan and Steve's answers, the key is sleep or usleep and sticking the play method outside the loop to avoid revving the CPU.

player?.play({return})
while player!.isPlaying { 
sleep(1) // or usleep(10000)
}

The original stillGoing value works, but there is also an isPlaying method.

.play needs something between its brackets to avoid hanging forever after completion.

Many thanks.

benwiggy
  • 1,440
  • 17
  • 35