Sign in Join
Wiki copytemporary

macOS NSSound: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: macOS NSSound

OldNewLine
1+## NSSound Encoding and file format support
2+
3+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.
4+
5+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.
6+
7+> **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).
8+
9+QuickTime supports the following audio file and data formats:
10+
11+- A-law
12+- Advanced Audio Coding (AAC)
13+- AMR Narrowband
14+- Apple Lossless
15+- Au file format
16+- Audio Interchange File Format (AIFF)
17+- Apple Core Audio Format (CAF)
18+- FLAC (since macOS 10.13)
19+- MACE
20+- Microsoft Adaptive DPCM (MS ADPCM)
21+- MIDI
22+- MPEG-1 Audio Layer 3 (MP3)
23+- Pulse-code modulation (PCM)
24+- QCELP (Qualcomm PureVoice)
25+- QDesign
26+- Waveform Audio File Format (WAV)
27+- μ-law
28+
29+## Playing system sounds
30+
31+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.
32+
33+A system sound can be played very easily using the NSSound class. The process is very simple:
34+
35+1. Create an object based on the NSSound class;
36+2. Load an audio file into the NSSound object using the *SoundNamed* class method;
37+3. Play the audio file using the *Play* class method.
38+
39+Here is the code to do that:
40+
41+```pascal>
42+
43+unit Unit1;
44+
45+{$mode objfpc}{$H+}
46+{$modeswitch objectivec1}
47+
48+interface
49+
50+uses
51+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll, CocoaUtils;
52+
53+...
54+
55+Var
56+ Form1: TForm1;
57+ mySound: NSSound := Nil; // Create an object based on the NSSound class
58+
59+...
60+
61+procedure TForm1.PlayButtonClick(Sender: TObject);
62+var
63+ status: boolean = false;
64+begin
65+ mySound := NSSound.soundNamed(NSStringUTF8('Frog')); // Load an audio file into the NSSound object
66+ mySound.play; // Play the audio file
67+
68+ if(not status) then // Play returns true/false for success/failure
69+ ShowMessage('Error!');
70+end;
71+
72+...
73+
74+end.
75+</syntaxhighlight>
76+
77+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.
78+
79+You can also combine the loading of a sound and playing it and omit the creation of a sound object:
80+
81+<syntaxhighlight lang=pascal>
82+NSSound.soundNamed(NSStringUTF8('Frog')).play; // Load and play audio file
83+</syntaxhighlight>
84+
85+== Playing sounds from your application bundle ==
86+
87+The ''soundNamed'' class method searches your application’s main [[Application_Bundle#Application_bundle_layout|bundle]] and if no sound file can be located in the application main bundle, the following directories are searched in order:
88+
89+* <tt>~/Library/Sounds</tt>
90+* <tt>/Library/Sounds</tt>
91+* <tt>/Network/Library/Sounds</tt>
92+* <tt>/System/Library/Sounds</tt>
93+
94+If no data file can be found for the sound name, no object is created and nil is returned.
95+
96+Copy the sound file from <tt>/Applications/iPhoto.app/Contents/Resources/Music/Minuet in G.mp3</tt> to the Resources sub-directory of your [[Application_Bundle#Application_bundle_layout|application bundle]]. You can now play that sound file using the code below:
97+
98+<syntaxhighlight lang=
99+```
100+
101+unit Unit1;
102+
103+{\$mode objfpc}{\$H+} {\$modeswitch objectivec1}
104+
105+interface
106+
107+uses
108+
109+` Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll, CocoaUtils;   `
110+
111+...
112+
113+Var
114+
115+` Form1: TForm1;`\
116+` mySound: NSSound := Nil;                                     // Create an object based on the NSSound class`
117+
118+...
119+
120+procedure TForm1.PlayButtonClick(Sender: TObject); var
121+
122+` status: boolean = false;`
123+
124+begin
125+
126+` mySound := NSSound.soundNamed(NSStringUTF8('Minuet in G'));  // Load an audio file into the NSSound object `\
127+` mySound.play;                                                // Play the audio file `
128+
129+` if(not status) then                                          // Play returns true/false for success/failure`\
130+`   ShowMessage('Error!'); `
131+
132+end;
133+
134+...
135+
136+end.
137+
138+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.
139+
140+## Playing sounds from elsewhere
141+
142+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.
143+
144+``` pascal
145+
146+unit Unit1;
147+
148+{$mode objfpc}{$H+}
149+{$modeswitch objectivec1}
150+
151+interface
152+uses
153+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll, CocoaUtils;
154+
155+...
156+
157+Var
158+ Form1: TForm1;
159+ mySound: NSSound = Nil; // Create an object based on the NSSound class
160+
161+...
162+
163+procedure TForm1.PlayButtonClick(Sender: TObject);
164+var
165+ audioFilePath: NSString;
166+ status: boolean = false;
167+begin
168+ audioFilePath := NSStringUTF8('/Applications/iPhoto.app/Contents/Resources/Music/Barbeque Blues.m4a');
169+ // mySound := NSSound.alloc;
170+ // mySound := mySound.initWithContentsOfFile_byReference(audioFilePath, True);
171+ // The two lines above can be combined into the single line below
172+ mySound := NSSound.alloc.initWithContentsOfFile_byReference(audioFilePath, True); // Load an audio file into the NSSound object
173+ mySound.Play; // Play the audio file
174+
175+ if(not status) then // Play returns true/false for success/failure
176+ ShowMessage('Error!');
177+end;
178+
179+...
180+
181+end.
182+```
183+
184+As we used *NSSound.alloc...* in the code above, do not forget to use *mySound.Release; mySound :- Nil; mySound.dealloc* somewhere appropriate or you'll create a memory leak every time you play the file. This is not necessary when using *NSSound.soundNamed(soundName)* class method in the previous two examples because that method creates an NSSound object for you using a helper function or, as the Apple documentation states more opaquely the "NSSound instance is initialized with the sound data identified by soundName".
185+
186+## Managing sound playback
187+
188+This section describes how to manage the playback of a sound using the NSSound class.
189+
190+### Pause
191+
192+``` pascal
193+
194+procedure TForm1.PauseButtonClick(Sender: TObject);
195+begin
196+ mySound.pause;
197+end;
198+```
199+
200+### Resume
201+
202+``` pascal
203+
204+procedure TForm1.ResumeButtonClick(Sender: TObject);
205+begin
206+ mySound.resume;
207+end;
208+```
209+
210+### Stop
211+
212+``` pascal
213+
214+procedure TForm1.StopButtonClick(Sender: TObject);
215+begin
216+ mySound.Stop;
217+end;
218+```
219+
220+### Volume control
221+
222+``` pascal
223+
224+mySound.SetVolume(0.5);
225+```
226+
227+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. macOS 10.5+ required.
228+
229+### Loop control
230+
231+When the value of the *loops* instance property is True, the sound restarts playback when it finishes and does not send didFinishPlaying message to its delegate when it reaches the end of its content and restarts playback. The default value of this property is False. macOS 10.5+ required.
232+
233+``` pascal
234+
235+mySound.setLoops(True); // Set true and the sound will continue looping
236+```
237+
238+### Sound duration
239+
240+The duration instance property provides the duration of the sound in seconds. macOS 10.5+ required.
241+
242+``` pascal
243+
244+status := mySound.play;
245+
246+if(status) then
247+ ShowMessage('Duration: ' + FloatToStrF(mySound.duration, ffFixed, 0, 2) + ' seconds')
248+else
249+ ShowMessage('Not OK');
250+```
251+
252+### Sound progress
253+
254+The currentTime instance property returns the sound’s playback progress in seconds. A sounds starts with currentTime equal to zero and ends with currentTime equal to sound duration minus one. macOS 10.5+ required.
255+
256+``` pascal
257+
258+procedure TForm1.PauseButtonClick(Sender: TObject);
259+begin
260+ mySound.pause;
261+
262+ ShowMessage('Progress: ' + FloatToStrF(mySound.currentTime, ffFixed, 0, 2)
263+ + ' of ' + FloatToStrF(mySound.duration, ffFixed, 0, 2) + ' seconds');
264+end;
265+```
266+
267+### Determine if sound is playing
268+
269+``` pascal
270+
271+if(mySound.IsPlaying) then
272+ ShowMessage('Sound is playing')
273+else
274+ ShowMessage('Sound is not playing');
275+```
276+
277+### Determine when sound finishes playing
278+
279+This is somewhat more complicated. First, create a delegate object:
280+
281+``` pascal
282+Uses
283+ ...
284+
285+type
286+
287+ { NSSoundFinishedDelegate }
288+ SoundFinishedDelegate = objcclass(NSObject, NSSoundDelegateProtocol)
289+ public
290+ procedure sound_didFinishPlaying(Sound: NSSound; FinishedPlaying: Boolean);
291+ message 'sound:didFinishPlaying:';
292+ end;
293+```
294+
295+Add two new variables: *SoundDelegate* for the SoundFinishedDelegate and a *Playing* boolean variable to indicate whether a sound is playing.
296+
297+``` pascal
298+Var
299+ Form1: TForm1;
300+ mySound: NSSound = Nil; // Create an object based on the NSSound class
301+ SoundDelegate : SoundFinishedDelegate;
302+ SoundPlaying : Boolean = False;
303+```
304+
305+Now create the new delegate procedure:
306+
307+``` pascal
308+{ NSSoundFinishedDelegate }
309+procedure SoundFinishedDelegate.sound_didFinishPlaying(Sound: NSSound; FinishedPlaying: Boolean);
310+begin
311+ if(FinishedPlaying) then
312+ begin
313+ SoundPlaying := False;
314+
315+ Form1.PlayButton.Enabled := True;
316+ Form1.PauseButton.Enabled := False;
317+ Form1.ResumeButton.Enabled := False;
318+ Form1.StopButton.Enabled := False;
319+
320+ Sound.Release; // avoid memory
321+ Sound := Nil; // leak when using
322+ Sound.dealloc; // NSSound.alloc...
323+ end;
324+end;
325+```
326+
327+The updated play procedure:
328+
329+``` pascal
330+
331+procedure TForm1.PlayButtonClick(Sender: TObject);
332+var
333+ status: Boolean;
334+begin
335+ Sound := NSSound.alloc.initWithContentsOfFile_byReference(NSStringUTF8
336+ ('/Applications/iPhoto.app/Contents/Resources/Music/Barbeque Blues.m4a'), True); // File system sound file
337+ //Sound := NSSound.SoundNamed(NSStringUTF8('Minuet in G')); // Bundle sound file
338+ //Sound := NSSound.soundNamed(NSStringUTF8('Frog')); // System sound file
339+
340+ Sound.setDelegate(SoundDelegate);
341+
342+ status := Sound.play;
343+ if(status) then
344+ SoundPlaying := True
345+ else
346+ ShowMessage('Playing failed!');
347+
348+ PlayButton.Enabled := False;
349+ PauseButton.Enabled := True;
350+ StopButton.Enabled := True;
351+end;
352+```
353+
354+Finally, the new Initialisation clause for the delegate object:
355+
356+``` pascal
357+
358+Initialization
359+ SoundDelegate := SoundFinishedDelegate.alloc.init;
360+End.
361+```
362+
363+Here is the whole unit for the application which now takes care of the enabled-state of the Play/Pause/Resume/Stop buttons:
364+
365+``` pascal
366+
367+unit Unit1;
368+
369+{$mode objfpc}{$H+}
370+{$modeswitch objectivec1}
371+
372+interface
373+
374+uses
375+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, CocoaAll,
376+ CocoaUtils;
377+
378+type
379+
380+ { NSSoundFinishedDelegate }
381+ SoundFinishedDelegate = objcclass(NSObject, NSSoundDelegateProtocol)
382+ public
383+ procedure sound_didFinishPlaying(Sound: NSSound; FinishedPlaying: Boolean);
384+ message 'sound:didFinishPlaying:';
385+ end;
386+
387+ { TForm1 }
388+ TForm1 = class(TForm)
389+ StopButton: TButton;
390+ PlayButton: TButton;
391+ PauseButton: TButton;
392+ ResumeButton: TButton;
393+ procedure StopButtonClick(Sender: TObject);
394+ procedure FormShow(Sender: TObject);
395+ procedure PlayButtonClick(Sender: TObject);
396+ procedure PauseButtonClick(Sender: TObject);
397+ procedure ResumeButtonClick(Sender: TObject);
398+ private
399+ public
400+end;
401+
402+var
403+ Form1: TForm1;
404+ Sound: NSSound = Nil;
405+ SoundDelegate : SoundFinishedDelegate;
406+ SoundPlaying : Boolean = False;
407+
408+implementation
409+
410+{$R *.lfm}
411+
412+{ NSSoundFinishedDelegate }
413+
414+procedure SoundFinishedDelegate.sound_didFinishPlaying(Sound: NSSound; FinishedPlaying: Boolean);
415+begin
416+ if(FinishedPlaying) then
417+ begin
418+ SoundPlaying := False;
419+
420+ Form1.PlayButton.Enabled := True;
421+ Form1.PauseButton.Enabled := False;
422+ Form1.ResumeButton.Enabled := False;
423+ Form1.StopButton.Enabled := False;
424+
425+ Sound.Release; // avoid memory
426+ Sound := Nil; // leak when using
427+ Sound.dealloc; // NSSound.alloc...
428+ end;
429+end;
430+
431+{ TForm1 }
432+
433+// Setup button state
434+procedure TForm1.FormShow(Sender: TObject);
435+begin
436+ PauseButton.Enabled := False;
437+ ResumeButton.Enabled := False;
438+ StopButton.Enabled := False;
439+end;
440+
441+// Play
442+procedure TForm1.PlayButtonClick(Sender: TObject);
443+var
444+ status: Boolean;
445+begin
446+ Sound := NSSound.alloc.initWithContentsOfFile_byReference(NSStringUTF8
447+ ('/Applications/iPhoto.app/Contents/Resources/Music/Barbeque Blues.m4a'), True); // File system sound example
448+ //Sound := NSSound.SoundNamed(NSStringUTF8('Minuet in G')); // Bundle sound file
449+ //Sound := NSSound.soundNamed(NSStringUTF8('Frog')); // System file
450+
451+ Sound.setDelegate(SoundDelegate);
452+
453+ status := Sound.play;
454+ if(status) then
455+ SoundPlaying := True
456+ else
457+ ShowMessage('Playing failed!');
458+
459+ PlayButton.Enabled := False;
460+ PauseButton.Enabled := True;
461+ StopButton.Enabled := True;
462+end;
463+
464+// Pause
465+procedure TForm1.PauseButtonClick(Sender: TObject);
466+begin
467+ if(SoundPlaying) then
468+ begin
469+ Sound.pause;
470+ PauseButton.Enabled := False;
471+ StopButton.Enabled := False;
472+ ResumeButton.Enabled := True;
473+ end;
474+end;
475+
476+// Resume
477+procedure TForm1.ResumeButtonClick(Sender: TObject);
478+begin
479+ if(SoundPlaying) then
480+ begin
481+ Sound.resume;
482+ ResumeButton.Enabled := False;
483+ PauseButton.Enabled := True;
484+ StopButton.Enabled := True;
485+ end;
486+end;
487+
488+// Stop
489+procedure TForm1.StopButtonClick(Sender: TObject);
490+begin
491+ if(SoundPlaying) then
492+ begin
493+ SoundPlaying := False;
494+ Sound.stop;
495+
496+ Sound.Release; // avoid memory
497+ Sound := Nil; // leak when using
498+ Sound.dealloc; // NSSound.alloc...
499+
500+ PlayButton.Enabled := True;
501+ PauseButton.Enabled := False;
502+ ResumeButton.Enabled := False;
503+ StopButton.Enabled := False;
504+ end;
505+end;
506+
507+Initialization
508+ SoundDelegate := SoundFinishedDelegate.alloc.init;
509+end.
510+```
511+
512+## See also
513+
514+- [[macOS Play Alert Sound|System Sound Services]]
515+- [[macOS Audio Player]]
516+- [[macOS Audio Recorder]]
517+- [[macOS Play Alert Sound]]
518+- [[macOS Programming Tips|NSBeep]]
519+- [[macOS Sound Utilities]]
520+- [[Multimedia Programming]]
521+
522+## External links
523+
524+- [Apple: NSSound Class](https://developer.apple.com/documentation/appkit/nssound)
525+- [Apple: Introduction to Sound Programming Topics for Cocoa](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Sound/Sound.html#//apple_ref/doc/uid/10000104-SW1)
526+- [Apple: System Sound Services](https://developer.apple.com/documentation/audiotoolbox/system_sound_services)
527+
528+---
529+*From the Free Pascal wiki, page "macOS NSSound", as it stood on 2021-02-19 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history