Using Audacity Scripting From Python for Tempo Change

I am on Audacity 3.1.3 on Windows 10.

I am trying to use Audacity via Mod-Pipe-Scripting.
The connection is working and I can send command and receive replies.
See the PS.

I wanted to use Audacity Scripting for Tempo Changing (as it preserves the Pitch while changing speed).
I was looking for code example for this - and my search in the forum is not showing anything up.
I am sure that I am probably not navigating through this forum effectively.

What I would like to do is to the send the following:

an mp3 or wav file
how much to change the tempo (just one parameter for now)
and get back a new file with Changed Tempo.

I would appreciate any pointers to doing this basic thing, and ideally the sequence of scripting commands.

Thanks in advance,

Sri.

P.S.I have tried Rubberband, Pedals etc. they work but with poor results.
I would like to avoid them and want to Audacity Scripting from Python (via mod-script pipe or any other ways).

P.S. Python-Audacity Interaction from my Python Code

pipe-test.py, running on windows
Write to “\.\pipe\ToSrvPipe”
Read from “\.\pipe\FromSrvPipe”
– Both pipes exist. Good.
– File to write to has been opened
– File to read from has now been opened too

Send: >>>
Help: Command=Help
Rcvd: <<<

Send: >>>
Help: Command=“GetInfo”
Rcvd: <<<
{ “id”:“Help”, “name”:“Help”, “params”:
[
{ “key”:“Command”, “type”:“string”, “default”:“Help” },
{ “key”:“Format”, “type”:“enum”, “default”:“JSON”, “enum”:
[ “JSON”, “LISP”, “Brief” ] } ],
“url”:“Extra_Menu:_Scriptables_II#help”,
“tip”:“Gives help on a command.” }
BatchCommand finished: OK

I’d use the pipeclient library. For example:

import pipeclient

# File to be imported
input = 'full-path-and-name-of-file-to-import'

# Modified file to be exported
output = 'full-path-and-name-of-file-to-export'

# Number of channels (1 = mono, 2 = stereo)
chans = 1

# Change tempo amount
percent = 50

# Use high  quality? 1 = true, 0 = false
hq = 1


client = pipeclient.PipeClient()

# Import file to process
client.write(f'Import2:Filename="{input}"')

# Ensure that entire file is selected.
client.write('SelectAll:')

# Change Tempo
client.write(f'ChangeTempo:Percentage={percent} SBSMS={hq}')

# Export
client.write(f'Export2:Filename="{output}" NumChannels={chans}')

Thanks a lot for the speedy reply with full code. Will put this into action soon.
Hope to use this also as a model for reverbs and delays soon after.

Regards Sri.

Steve

I notice that you are using Import2 and Export2…
I do not see those script ids in the script reference manual. (3.1.0)
Can you clarify?

Thanks Sri.

Steve

Apologize for sending more questions your way… you being a good Samaritan.

I was able to change the tempo of a song and export it.

How do I wait for the command execution to finish?
As I see it is being run asynchronously without “blocking”
Is there any return handle for each command sent?

and also how to receive a response from Audacity?
does Audacity send a response for commands sent?

If these have to be done via other OS idioms, is there a recommended method?

Thanks Sri.

P.S. I also was not able to do pip install pipeclient. Is that the current state? (I am running Anaconda/Jupyter)
So I downloaded that file from Git/Hub, cut out the code related to interactive execution and importing that file.

They’re listed in the “Scripting Reference”. See: Scripting Reference - Audacity Manual
(Use your web browser’s “Search” function - usually “Ctrl + F”)


The “Import2:” and “Export2” commands allow you to specify the file.

Thanks Steve for the tip.

I found Import2 etc. listed under the Extra Scriptables and I was looking for it in the Import area in the beginning.

regards Sri.

Scripting is rather weak for getting information from Audacity back to Python, but you can receive a response from Audacity to each command sent. Waiting for the response effectively makes the command ‘blocking’.
Example:

import sys
import time
import pipeclient


input = 'full-path-and-name-of-file-to-import'
output = 'full-path-and-name-of-file-to-export'
chans = 1

# Change tempo settings
percent = 50
hq = 1

# Set timeout (seconds) for response.
# Ensure that this is long enough for the slowest command.
timeout = 60.0

def get_reply ():
    start = time.time()
    info = ""
    while info == "" and time.time() - start < timeout:
        info = client.read()
        time.sleep(0.05)
    if info == "":
        sys.exit(f'Timeout after {timeout} seconds')
    print(info)


client = pipeclient.PipeClient()


client.write(f'Import2:Filename="{input}"')
get_reply()

client.write('SelectAll:')
get_reply()

client.write(f'ChangeTempo:Percentage={percent} SBSMS={hq}')
get_reply()

client.write(f'Export2:Filename="{output}" NumChannels={chans}')
get_reply()

and the result:

Sending command: Import2:Filename="full-path-and-name-of-file-to-import"
BatchCommand finished: OK

Sending command: SelectAll:
BatchCommand finished: OK

Sending command: ChangeTempo:Percentage=50 SBSMS=1
BatchCommand finished: OK

Sending command: Export2:Filename="full-path-and-name-of-file-to-export" NumChannels=1
Exported to WAV format: full-path-and-name-of-file-to-export
BatchCommand finished: OK

Scripting is rather weak for getting information from Audacity back to Python, but you can receive a response from Audacity to each command sent. Waiting for the response effectively makes the command ‘blocking’.

Steve thanks for the prompt tip and the code.
This is plenty for me… blocking on command completion … before issuing the next one.

With my (simple) Python Audio Composer and, Audacity Interface to Tempo, Pitch and Reverb – it is becoming a wonderful world for automation and experimentation, with precision, rigor and repeatability.

So much saving of manual labor (which interactive UI forces one to) and that alone opens significant new capabilities for creativity.

I will share some details and results soon here. Hopefully I will get more IDEAS from you and others.

regards Sri.

Steve

Unfortunately your tip and the code does not serve the need.

When I send a command, I do get a “none” reply back immediately.
Meaning “Audacity saying, received your command”
Useful, but also not very useful.

The send command is essentially “Non Blocking”.
Most scripts are a Sequence of commands to be executed one after another.

Ideally Audacity should send the reply after the command has been processed OR
send a Handle (like a Promise Construct in most programming languages) which can be used by the caller for checking Status or for registering some form of callback.
Or even a simple convention, like scripts writing a line to a log file which can be checked from Python??

Anything like this would be a great help and essential to properly use Audacity via mod-pipe interface.

How can I express this need as a priority request? for fully harnessing Audacity mod-pipe scripting interface?

Thanks Sri.

Please be specific so that I can see what you are doing - please post your script, plus a description of what is happening, and a description of what you want to happen.

Steve

Sorry if I was being vague.
I thought I had stated my need clearly and specifically.
I will describe it again without it being cruelly bounded to a use case.
No worries.

br Sri.

THE NEED
I issue a command and
Want to wait until the command is completed by Audacity.

A Simple Usecase:

The commands are issued from Python using pipeclient.py code.

load a wav file0
change the tempo
wait until this is complete # See Note 1
export it to file1
wait until this is done
load file1
apply some reverb
wait until this done
export to file2

thereon…

read file2 in Python and go ahead and do other things.


new
Note 1:
I observe that audacity does not execute the next Export command… even though Python goes ahead and issues the next one.
Looks like commands are buffered in the pipe and taken up for execution one by one by Audacity.
Python keeps sending commands - without knowing whether previous command completed successfully.

I am planning to use the presence of the “Exported File” to infer that Export command has succeeded.
Of course I will add a timeout to put a limit on how long I will look for it.
Thinking out now, I can use this for testing completion.

I am also just thinking of creating a custom macro (wrapping the scriptable) - so I can use file system to signal to Python.

Unfortunately Audacity cannot tell you when the command has completed, only when the command has been received. However, there are various ways to work around this limitation, for example you can send a dummy “Message:” command:

client.write('a-slow-command')
get_reply()
client.write('Message:Text="Slow command completed"')
get_reply()

(It gets a bit more complicated if you need to know if the command completed successfully, but then you can parse the reply and look for “BatchCommand finished: OK”)

Example:

def get_reply (prnt=False):
    start = time.time()
    info = ""
    while info == "" and time.time() - start < timeout:
        info = client.read()
        time.sleep(0.05)
    if info == "":
        sys.exit(f'Timeout after {timeout} seconds')
    if info[-26:-1] != 'BatchCommand finished: OK':
        sys.exit(f'Command failed')
    if prnt:
        print(info)


def send_blocking_command (cmd):
    client.write(cmd)
    get_reply()
    client.write(f'Message:Text="{cmd.split(":")[0]} completed"')
    get_reply(1)


client = pipeclient.PipeClient()

send_blocking_command(f'Import2:Filename="{input}"')
send_blocking_command('SelectAll:')
send_blocking_command(f'ChangeTempo:Percentage={percent} SBSMS={hq}')
send_blocking_command(f'Export2:Filename="{output}" NumChannels={chans}')