macOS MIDI Player
This article applies to macOS only.
See also: Multiplatform Programming Guide
Overview
The Apple AVFoundation framework combines four major technology areas: Playback and Editing, Media Capture, Audio and Speech Synthesis. Together these technologies encompass a wide range of tasks for capturing, processing, synthesising, controlling, importing and exporting audiovisual media on Apple platforms. The framework, available from macOS 10.7 (Lion), provides essential services for working with time-based audiovisual media.
- To play sound files, you can use AVAudioPlayer. Available from macOS 10.7 (Lion).
- To play video (or sound) files, you can use AVPlayer and AVPlayerLayer. Available from macOS 10.7 (Lion).
- To record audio, you can use AVAudioRecorder. Available from macOS 10.7 (Lion).
- To play MIDI or iMelody files, you can use AVMIDIPlayer. Available from macOS 10.10 (Yosemite).
AVMidiPlayer
The AVMidiPlayer class lets you play music file formats such as MIDI and iMelody (non-polyphonic sound format created for mobile phones). It is available from macOS 10.10 (Yosemite). The properties of this class are used for managing information about a music file such as the playback point within the sound’s timeline, duration and playback rate.
Example code
The example code below creates a basic MIDI file player application. This is useful in itself since Apple removed the ability to play MIDI files in macOS 10.9 (Mavericks) in 2013. While you could still download QuickTime 7 (a 32 bit application) from Apple, macOS 10.15 (Catalina) removed all support for 32 bit software, which includes QuickTime 7 and all media formats and codecs relying on it.
unit Unit1;
{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$linkframework AVFoundation}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs,
CocoaAll, LCLType, StdCtrls, ExtCtrls;
type
{ AVMIDIPlayer}
AVMIDIPlayerCompletionHandler = OpaqueCBlock;
AVMIDIPlayer = objcclass external(NSObject)
public
{Initializes a newly allocated MIDI player with the contents of the file
specified by the URL, using the specified sound bank.
inURL - The file to play.
bankURL - The URL of the sound bank. The sound bank must be a SoundFont2 or DLS bank.
For macOS the bankURL can be set to nil to use the default sound bank.
outError - Returns, by-reference, a description of the error, if an error occurs.}
function initWithContentsOfURL_error (inURL: NSURL; soundBankURL: NSURL; outError: NSErrorPtr): id;
message 'initWithContentsOfURL:soundBankURL:error:';
{Prepares to play the sequence by prerolling all events.
Happens automatically on play if it has not already been called, but may produce a
delay in startup.}
procedure prepareToPlay; message 'prepareToPlay';
{Plays the sequence.
completionHandler - A block that is executed when playback is completed.
If prepareToPlay has not been invoked, play may be delayed while the events are prerolled.}
procedure play (completionHandler: AVMIDIPlayerCompletionHandler); message 'play:';
{Stops playing the sequence.}
procedure stop; message 'stop';
{This property is the length of the currently loaded file in seconds.}
function duration: NSTimeInterval; message 'duration';
{A Boolean value that indicates whether the sequence is playing.
Note: The player may have reached the end of all the events in any of its tracks,
but it will return YES until it is stopped.}
function isPlaying: ObjCBOOL; message 'isPlaying';
{This property’s default value of 1.0 provides normal playback rate. Rate must be > 0.0.}
procedure setRate(newValue: single); message 'setRate:';
function rate: single; message 'rate';
{The current playback position in seconds. No range-checking is done to ensure
currenPosityion is <= Duration.
You can set the currentPosition of the player while the player is playing,
in which case playback will resume at the new position.}
procedure setCurrentPosition(newValue: NSTimeInterval); message 'setCurrentPosition:';
function currentPosition: NSTimeInterval; message 'currentPosition';
end;
{ TForm1 }
TForm1 = class(TForm)
DurationLabel: TLabel;
SecondsLabel: TLabel;
PauseButton: TButton;
StopPlayButton: TButton;
PlayMidiButton: TButton;
DurationTimer: TTimer;
procedure PauseButtonClick(Sender: TObject);
procedure PlayMidiButtonClick(Sender: TObject);
procedure StopPlayButtonClick(Sender: TObject);
procedure DurationTimerTimer(Sender: TObject);
private
public
end;
var
Form1: TForm1;
MyMidiPlayer : AVMidiPlayer = Nil;
filePos : NSTimeInterval = 0;
fileDuration : NSTimeInterval = 0;
implementation
{$R *.lfm}
// Play midi procedure
procedure PlayMidi(midiFileName : NSString);
var
path: NSString;
url : NSURL;
err : NSError;
begin
// Do nothing if already playing a midi file
if(MyMidiPlayer.IsPlaying) then
exit;
// If player has not been paused
if(filePOS = 0) then
begin
// Path to your application bundle's resource directory
// with the midi filename appended
path := NSBundle.mainBundle.resourcePath.stringByAppendingPathComponent(midiFileName);
url := NSURL.fileURLWithPath(path);
// Create MidiPlayer and load midi file
MyMidiPlayer := AVMidiPlayer.alloc.initWithContentsOfURL_error(url, Nil, @err);
// Save file duration
fileDuration := MyMidiPlayer.duration;
if Assigned(MyMidiPlayer) then
begin
MyMidiPlayer.play(Nil);
end
else
// Use the Applications > Utilities > Console application to find error messages
NSLog(NSStr('Error in procedure PlayMidi(): %@'), err);
end
// Otherwise resume playing existing file
else
begin
MyMidiPlayer.play(Nil);
end;
end;
{ TForm1 }
procedure TForm1.PlayMidiButtonClick(Sender: TObject);
begin
PlayMidi(NSStr('whitewed.mid'));
SecondsLabel.Caption := FormatFloat('#', MyMidiPlayer.currentPosition)
+ ' of ' + FormatFloat('#', fileDuration)
+ ' seconds';
// Setup Timer for duration
DurationTimer.Enabled := True;
end;
procedure TForm1.PauseButtonClick(Sender: TObject);
begin
if(MyMidiPlayer.IsPlaying) then
begin
MyMidiPlayer.stop;
DurationTimer.Enabled := False;
// Save file position for resumption
filePos := MyMidiPlayer.currentPosition;
end;
end;
procedure TForm1.StopPlayButtonClick(Sender: TObject);
begin
if(MyMidiPlayer.IsPlaying) then
begin
MyMidiPlayer.stop;
MyMidiPlayer := Nil;
filePos := 0;
end;
end;
procedure TForm1.DurationTimerTimer(Sender: TObject);
begin
//Stop timer if file not playing
if(MyMidiPlayer.currentPosition >= fileDuration) then
begin
DurationTimer.Enabled := False;
SecondsLabel.Caption := FormatFloat('#', fileDuration)
+ ' of ' + FormatFloat('#', fileDuration)
+ ' seconds';
MyMidiPlayer.Stop;
Exit;
end
else
begin
// Update current file position
SecondsLabel.Caption := FormatFloat('#', MyMidiPlayer.currentPosition)
+ ' of ' + FormatFloat('#', fileDuration)
+ ' seconds';
end;
end;
end.
See also
- System Sound Services
- macOS Audio Player
- macOS Audio Recorder
- macOS NSSound
- macOS Sound Utilities
- Multimedia Programming