Simple AVAudioRecorder

I’m having trouble executing the rec command for SOX via AS, so I’m trying to compile a basic code using AVAudioRecorder, but I’m struggling, not sure what I’m doing wrong, any pointers would be really appreciated.

use framework "Foundation"
use framework "AVFoundation"
use scripting additions
set theString to "file:///Users/dh/Music/Samples/.:tempSample/Untitled.m4a"

set theURL to current application's |NSURL|'s URLWithString:theString
set theData to current application's NSData's dataWithContentsOfURL:theURL
set soundRecorder to current application's AVAudioRecorder's alloc()'s initWithFormat:standard
if soundRecorder is missing value then error (theError's localizedDescription() as text)

tell soundRecorder
	
	prepareToRecord()
	
	record {forDuration, 7}   -- or record()

end tell

Try this:


use framework "Foundation"
use framework "AVFAudio"
use scripting additions

property ca : current application

set theFilename to "~/Desktop/Test.m4a"

set theURL to ca's |NSURL|'s fileURLWithPath:((ca's NSString's stringWithString:theFilename)'s stringByExpandingTildeInPath())
set theFormat to ca's AVAudioFormat's alloc()'s initStandardFormatWithSampleRate:44100 channels:2
set {theRecorder, theError} to ca's AVAudioRecorder's alloc()'s initWithURL:(theURL) format:theFormat |error|:(reference)
if theRecorder = missing value then error (theError's localizedDescription() as text)

theRecorder's |record|()
delay 7
theRecorder's |stop|()

To play use:


use framework "Foundation"
use framework "AVFAudio"
use scripting additions

property ca : current application

set theFilename to "~/Desktop/Test.m4a" -- or: POSIX path of (choose file with prompt "Choose an Audio file:")

set theURL to ca's |NSURL|'s fileURLWithPath:((ca's NSString's stringWithString:theFilename)'s stringByExpandingTildeInPath())
set {thePlayer, theError} to ca's AVAudioPlayer's alloc()'s initWithContentsOfURL:theURL |error|:(reference)
if thePlayer = missing value then error (theError's localizedDescription() as text)

thePlayer's play()
delay 7
thePlayer's |stop|()

So a few things about your code before referencing Apple’s Documentation
I’ve seen you’ve been posting before and have tested this.

set theString to "file:///Users/dh/Music/Samples/.:tempSample/Untitled.m4a"

Are you not having any issues with the “/.:tempSample” part of your path?
Or is creating a hidden folder named “:tempSample”?

set theData to current application's NSData's dataWithContentsOfURL:theURL

Why are you reading data from this URL?
This should be the path that you want to record to correct?
And you don’t use it below?

set soundRecorder to current application's AVAudioRecorder's alloc()'s initWithFormat:standard

You have not defined the variable “standard”.
See my next reply about apple doc’s how to initialize a AVAudioRecorder.

tell soundRecorder
	prepareToRecord()
	record {forDuration, 7}   -- or record()
end tell

tell blocks are aimed for targerting script objects (which may be a scriptable application)
the soundRecorder is a subclass of NSObject and you should be using AsOBJc style (Objective-C)
method/function operations on this instance of AVAudioRecorder you’ve created.
See next post.

Starting with AVFoundation here:
https://developer.apple.com/documentation/avfoundation?language=objc

You see the AUDIO part: Audio Playback, Recording, and Processing
Play, record, and process audio; configure your app’s system audio behavior.

Which links you to here: https://developer.apple.com/documentation/avfoundation/audio_playback_recording_and_processing?language=objc

Then theres the Audio Recording section which links you here:
https://developer.apple.com/documentation/avfaudio/avaudiorecorder?language=objc

It lists two was to initialize a AVAudioRecorder.
And linking to the format one:
https://developer.apple.com/documentation/avfaudio/avaudiorecorder/1778755-initwithurl?language=objc


So now about passing the variable for the format.
If you notice in the declaration you can click on the AVAudioFormat.
(open that in a new tab):
https://developer.apple.com/documentation/avfaudio/avaudioformat?language=objc

A whole new class on setting up an instance of AVAudioFormat.
The quickest and easiest for now will be:
https://developer.apple.com/documentation/avfaudio/avaudioformat/1390416-initstandardformatwithsamplerate?language=objc

- (instancetype)initStandardFormatWithSampleRate:(double)sampleRate 
                                        channels:(AVAudioChannelCount)channels;

now that you have a format variable you can then use that in the initialization of your recorder:

- (instancetype)initWithURL:(NSURL *)url 
                     format:(AVAudioFormat *)format 
                      error:(NSError * _Nullable *)outError;

then go back to :
https://developer.apple.com/documentation/avfaudio/avaudiorecorder?language=objc

And you can refer to the Controlling recording etc.

you would do this as;


soundRecorder's prepareToRecord()
soundRecorder's record()
soundRecorder's pause()
soundRecorder's recorderAtTime:5.00 forDurations:10.0
soundRecorder's stop()

Note you may have to playground with the times and durations supplied not
sure how AsOBJc bridge’s those as they are expecting NSTimeIntervals

Declaration
typedef double NSTimeInterval;
Discussion
A NSTimeInterval value is always specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.

Thanks for the replies guys sand to technomorph for taking the time to go into detail. i really appreciate it

Im having issues in script debugger using AVFoundation and AVFAudio in Catalina (as ideally i was looking for something i could write into an ASObjC script, it says the modules are not available. Im not sure where its looking for the framework but i cant find any executables in any of the folders

DB - Do you know what path your script editor is looking for the AVF framework in?

I haven’t checked to see if Xcode is having issues yet, just got back from the studio so gonna have a look now

Doug

@dooglesnak:

You are right. AVFAudio cannot be loaded in Catalina - AVFoundation can. But the problem is the file extension. Apparently you can’t record to a compressed file with Applescript and AVAudioRecorder, because you can’t set the format. The AVFormatIDKey is an unsigned integer value and therefore the standard (Linear PCM, 16 bit) is always used.
Under Monterey you can specify “.m4a” as file extension. Under Catalina it must be “.wav”. This script works with Catalina:


use framework "Foundation"
use framework "AVFoundation"
use scripting additions

property ca : current application

set theFilename to "~/Desktop/Test.wav"

set theURL to ca's |NSURL|'s fileURLWithPath:((ca's NSString's stringWithString:theFilename)'s stringByExpandingTildeInPath())
set theFormat to ca's AVAudioFormat's alloc()'s initStandardFormatWithSampleRate:44100 channels:2
set {theRecorder, theError} to ca's AVAudioRecorder's alloc()'s initWithURL:(theURL) format:theFormat |error|:(reference)
if theRecorder = missing value then error (theError's localizedDescription() as text)

theRecorder's |record|()
delay 7
theRecorder's |stop|()

Here is another script that can be used to set the settings. This also works with Catalina, but I did not succeed to set the AVFormatIDKey to the kAudioFormatMPEG4AAC value. This is necessary to use the „m4a“ file extension under Catalina:


use framework "Foundation"
use framework "AVFoundation"
use scripting additions

property ca : current application
property kAudioFormatMPEG4AAC : a reference to ca's kAudioFormatMPEG4AAC

set theFilename to "~/Desktop/Test.wav"

set theURL to ca's |NSURL|'s fileURLWithPath:((ca's NSString's stringWithString:theFilename)'s stringByExpandingTildeInPath())
set theSettings to ca's NSMutableDictionary's dictionary()

theSettings's setObject:44100 forKey:"AVSampleRateKey"
theSettings's setObject:2 forKey:"AVNumberOfChannelsKey"
theSettings's setObject:16 forKey:"AVLinearPCMBitDepthKey"
theSettings's setObject:0 forKey:"AVLinearPCMIsBigEndianKey"

--theSettings's setObject:(kAudioFormatMPEG4AAC) forKey:"AVFormatIDKey" -- not working
--theSettings's setObject:(ca's kAudioFormatMPEG4AAC) forKey:"AVFormatIDKey" -- not working
--theSettings's setObject:192000 forKey:"AVEncoderBitRateKey"

set {theRecorder, theError} to ca's AVAudioRecorder's alloc()'s initWithURL:(theURL) settings:theSettings |error|:(reference)
if theRecorder = missing value then error (theError's localizedDescription() as text)

theRecorder's |record|()
delay 7
theRecorder's |stop|()

Maybe someone else has any idea how to set the format or you can do it with Xcode.

I sorted it (AVFAudio) and got your first script up and running, i looked in the Script Debugger manifest to check where the framework dependencies were set and placed copies of the framework including the executables into the respective folders and it works fine. Im not sure why it was looking in the home/library/framework folder for AVFoundation and AVFAudio, then System folder for Foundation.

I havent tried setting a format other than wav, though (annoying as it is that I cant add meta info to wavs) the frequency estimation engine will only accept wav files for processing.

It generates readings for frequency estimation over time with a certainty reading from .0 - 1.0, i think to strengthen the tonal content of the signal i’ll try to add a noise gate and some compression, which I guess would be called via AVAuioEngine, so hopefully I’ll be able to apply some similar code to initiate and arm those plugins whilst recording. I’m hoping this can all be achieved in AS and ASObjC

Success! i just tried wav, aif, ogg, even flac worked, additionally discovered at the end of the script the analysis engine processed all formats too!!! Neither AVFoundation or the NLP engine liked m4a or mp3 though. Perhaps you’d have to use AVAudioEngine for that

The ability to change the sample and bit rate in the latter scripts will certainly come in handy. Successfully recorded 8 bit linear pcm at 22050hz to 192000 in 32 bit wavs
Thank you

I think for m4a or mp3 format the AVFormatIDKey must be set. See: https://stackoverflow.com/questions/4259078/osstatus-error-1718449215

Edit: I have found the int value for kAudioFormatMPEG4AAC (1633772320) under: https://docs.microsoft.com/en-us/dotnet/api/audiotoolbox.audioformattype?view=xamarin-ios-sdk-12

Now setting AVFormatIDKey and recording as m4a works:


use framework "Foundation"
use framework "AVFoundation"
use scripting additions

property ca : current application

(*
AVFormatIDKey (UInt32 value) for MPEG-4 AAC codec: 1633772320
see: https://docs.microsoft.com/en-us/dotnet/api/audiotoolbox.audioformattype?view=xamarin-ios-sdk-12
for other Audio format identifiers
*)
property kAudioFormatMPEG4AAC : a reference to 1.63377232E+9

set theFilename to "~/Desktop/Test.m4a"

set theURL to ca's |NSURL|'s fileURLWithPath:((ca's NSString's stringWithString:theFilename)'s stringByExpandingTildeInPath())
set theSettings to ca's NSMutableDictionary's dictionary()

theSettings's setObject:kAudioFormatMPEG4AAC forKey:"AVFormatIDKey"
theSettings's setObject:44100 forKey:"AVSampleRateKey"
theSettings's setObject:2 forKey:"AVNumberOfChannelsKey"
theSettings's setObject:16 forKey:"AVLinearPCMBitDepthKey"
theSettings's setObject:0 forKey:"AVLinearPCMIsBigEndianKey"
theSettings's setObject:192000 forKey:"AVEncoderBitRateKey"

set {theRecorder, theError} to ca's AVAudioRecorder's alloc()'s initWithURL:(theURL) settings:theSettings |error|:(reference)
if theRecorder = missing value then error (theError's localizedDescription() as text)

theRecorder's |record|()
delay 5
theRecorder's |stop|()