Difference between revisions of "macOS NSSound"

From Lazarus wiki
Jump to navigationJump to search
m (→‎Stop: Code fix (for debugging run))
m (→‎Playing sounds from elsewhere: Init var; fix case)
Line 154: Line 154:
 
Var
 
Var
 
   Form1: TForm1;
 
   Form1: TForm1;
   mySound: NSSound;                                         // Create an object based on the NSSound class
+
   mySound: NSSound = Nil;                                                           // Create an object based on the NSSound class
  
 
...
 
...
Line 176: Line 176:
 
If (Assigned (mySound)) then
 
If (Assigned (mySound)) then
 
   begin
 
   begin
     MySound.Release;
+
     mySound.Release;
     MySound := Nil;
+
     mySound := Nil;
 
   end;
 
   end;
  

Revision as of 14:17, 8 May 2020

English (en)

macOSlogo.png

This article applies to macOS only.

See also: Multiplatform Programming Guide


NSSound Encoding and file format support

NSSound supports any audio encodings and file formats that QuickTime does (as it still uses QuickTime for certain things) and, as of macOS 10.3 (Panther), it also uses CoreAudio for playback. The NSSound initializer first tries to use CoreAudio with the file it is initialized with. If that fails, NSSound tries to use QuickTime. If that fails, NSSound initialization will fail.

NSSound will use CoreAudio for any files supported by the CoreAudio AudioFile APIs as long as the file type is supported in <AudioToolbox/AudioFile.h> and the encoding format has codec support (with the exception of Linear PCM which is always supported). The table below shows CoreAudio support.

Sound File Format Sound Data Formats
AAC (.aac, .adts) 'aac '
AC3 (.ac3) 'ac-3'
AIFC (.aif, .aiff,.aifc) BEI8, BEI16, BEI24, BEI32, BEF32, BEF64, 'ulaw', 'alaw', 'MAC3', 'MAC6', 'ima4' , 'QDMC', 'QDM2', 'Qclp', 'agsm'
AIFF (.aiff) BEI8, BEI16, BEI24, BEI32
Apple Core Audio Format (.caf) '.mp3', 'MAC3', 'MAC6', 'QDM2', 'QDMC', 'Qclp', 'Qclq', 'aac ', 'agsm', 'alac', 'alaw', 'drms', 'dvi ', 'ima4', 'lpc ', BEI8, BEI16, BEI24, BEI32, BEF32, BEF64, LEI16, LEI24, LEI32, LEF32, LEF64, 'ms\x00\x02', 'ms\x00\x11', 'ms\x001', 'ms\x00U', 'ms \x00', 'samr', 'ulaw'
MPEG Layer 3 (.mp3) '.mp3'
MPEG 4 Audio (.mp4) 'aac '
MPEG 4 Audio (.m4a) 'aac ', alac'
NeXT/Sun Audio (.snd, .au) BEI8, BEI16, BEI24, BEI32, BEF32, BEF64, 'ulaw'
Sound Designer II (.sd2) BEI8, BEI16, BEI24, BEI32
WAVE (.wav) LEUI8, LEI16, LEI24, LEI32, LEF32, LEF64, 'ulaw', 'alaw'
Light bulb  Note: Apple ceased support for QuickTime on macOS in 2018 at which time the underlying media framework for QuickTime and QTKit was deprecated in favour of a newer graphics framework, AVFoundation. QuickTime's underlying media framework was completely removed from macOS 10.15 (Catalina).

QuickTime supports the following audio file and data formats:

  • A-law
  • Advanced Audio Coding (AAC)
  • AMR Narrowband
  • Apple Lossless
  • Au file format
  • Audio Interchange File Format (AIFF)
  • Apple Core Audio Format (CAF)
  • FLAC (since macOS 10.13)
  • MACE
  • Microsoft Adaptive DPCM (MS ADPCM)
  • MIDI
  • MPEG-1 Audio Layer 3 (MP3)
  • Pulse-code modulation (PCM)
  • QCELP (Qualcomm PureVoice)
  • QDesign
  • Waveform Audio File Format (WAV)
  • μ-law

Playing system sounds

Here is the list of macOS system sounds (found in /System/Library/Sounds): Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse, Ping, Pop, Purr, Sosumi, Submarine, and Tink.

A system sound can be played very easily using the NSSound class. The process is very simple:

  1. Create an object based on the NSSound class;
  2. Load an audio file into the NSSound object using the SoundNamed class method;
  3. Play the audio file using the Play class method.

Here is the code to do that:

unit Unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$modeswitch objectivec2}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll, CocoaUtils;   

...

Var
  Form1: TForm1;
  mySound: NSSound;                                   // Create an object based on the NSSound class

...

procedure TForm1.PlayButtonClick(Sender: TObject);
begin
  mySound := NSSound.soundNamed(NSStringUTF8('Frog'));  // Load an audio file into the NSSound object 
  mySound.play;                                         // Play the audio file 
end;

...

Finalization

If (Assigned (mySound)) then
  begin
    MySound.Release;
    MySound := Nil;
  end;

end.

While the Frog system sound file is named Frog.aiff, you can drop the file extension (.aiff) from the filename as we did in the above example code.

Playing sounds from your application bundle

If there is no system sound in /System/Library/Sounds with the name you’ve specified, the soundNamed class method searches your application’s main bundle, and then the /Library/Sounds and ~/Library/Sounds directories for sound files with the specified name. If no data can be found for the sound name, nil is returned.

Copy the sound file from /Applications/iPhoto.app/Contents/Resources/Music/Minuet in G.mp3 to the Resources sub-directory of your application bundle. You can now play that sound using the code below:

unit Unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$modeswitch objectivec2}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll, CocoaUtils;   

...

Var
  Form1: TForm1;
  mySound: NSSound;                                            // Create an object based on the NSSound class

...

procedure TForm1.PlayButtonClick(Sender: TObject);
begin
  mySound := NSSound.soundNamed(NSStringUTF8('Minuet in G'));  // Load an audio file into the NSSound object 
  mySound.play;                                                // Play the audio file 
end;

...

Finalization

If (Assigned (mySound)) then
  begin
    MySound.Release;
    MySound := Nil;
  end;

end.

Note that once again the file extension is not used when loading sound files using the soundNamed class method. Also note that AIFF files must use the .aiff file extension (not .aif) for it to be located by the soundNamed class method.

Playing sounds from elsewhere

If you want to load audio files from somewhere else in the file system other than from the system sounds directory or your application bundle, you can use the NSSound class method initWithContentsOfFile_byReference. As you would expect, this method attempts to initialize a newly allocated NSSound instance with the audio data in the specified file. The following code example shows you how to create an NSSound instance and initialize it with a sound file using a pathname to the file.

unit Unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$modeswitch objectivec2}

interface
uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll, CocoaUtils;   

...

Var
  Form1: TForm1;
  mySound: NSSound = Nil;                                                           // Create an object based on the NSSound class

...

procedure TForm1.PlayButtonClick(Sender: TObject);
var
  audioFilePath: NSString;
begin
  audioFilePath := NSStringUTF8('/Applications/iPhoto.app/Contents/Resources/Music/Barbeque Blues.m4a');
  // mySound := NSSound.alloc;
  // mySound := mySound.initWithContentsOfFile_byReference(audioFilePath, True);
  // The two lines above can be combined into the single line below
  mySound := NSSound.alloc.initWithContentsOfFile_byReference(audioFilePath, True);  // Load an audio file into the NSSound object
  mySound.Play;                                                                      // Play the audio file
end;                        

...

Finalization

If (Assigned (mySound)) then
  begin
    mySound.Release;
    mySound := Nil;
  end;

end.

Managing sound playback

This section describes how to manage the playback of a sound using the NSSound class.

Pause

procedure TForm1.PauseButtonClick(Sender: TObject);
begin
  mySound.pause;
end;

Resume

procedure TForm1.ResumeButtonClick(Sender: TObject);
begin
  mySound.resume;
end;

Stop

procedure TForm1.StopButtonClick(Sender: TObject);
begin
  mySound.Stop;
  MySound := Nil; 
end;

Volume control

mySound.SetVolume(0.5);

The valid range is between 0.0 and 1.0. The value of this property does not affect the systemwide volume. Make sure you set it after loading and before playing the sound file.

Loop control

t/c

Determine if a sound is playing

if(mySound.IsPlaying) then
  ShowMessage('Sound is playing')
else   
  ShowMessage('Sound is not playing');

Determine when a sound finishes playing

t/c when I discover how to use delegates in Pascal :-)

See also

External links