Well, I think I found a solution for a Linux command-line multitrack audio looper - and that is to use a ChucK => Strongly-timed, On-the-fly Audio Programming Language script.
First of all, ChucK
can be installed via sudo apt-get install chuck
in Debian/Ubuntu. However, after you install, try to type chuck
and then TAB in terminal; you should get something like:
$ chuck
chuck chuck.alsa chuck.oss
... that is to say - note that there are three ChucK
executables; each for a separate Linux audio backend - and note that the default executable, chuck
, actually refers to JACK (the other executables, obviously, refer to ALSA and OSS).
Since most typical users on Linux (also myself) do not actually have jack
installed - running the chuck
executable may cause dissapointment, as it won't result with sound (given that jack
is not present on the system); see, for instance:
You may want to run chuck --probe
and see what is reported for different backends - expect a complaint from chuck
if JACK is not present and running, but chuck.alsa
should pass:
$ chuck --probe
[chuck]: (via rtaudio): no devices found for compiled audio APIs!
[chuck]:
# .....
$ chuck.alsa --probe
[chuck]: found 4 device(s) ...
[chuck]: ------( chuck -- dac1 )---------------
[chuck]: device name = "hw:SB,0"
[chuck]: probe [success] ...
[chuck]: # output channels = 6
[chuck]: # input channels = 2
[chuck]: # duplex Channels = 2
# .....
For most users, then, running chuck.alsa
instead should do the trick - however, note that this will likely take up the audio device directly (doesn't look like chuck
is aware of pulseaudio
) - and then you will not be able to run other audio-generating programs (like vlc
) in parallel, and have the sound mixed from both applications (the other application will basically stay silent).
But this being noted - we can now proceed with a ChucK
script for looping.
One of the great things about ChucK
is that basially we can define one script that handles the playback and looping of just one sound - and then call multiple instances of that script in parallel - which effectively creates a multitrack audio looper!
After some messing around with ChucK
examples - see:
... I managed to cook my own script - primarily based on examples: sndbuf.ck and valueat.ck - which I've called loopsndbuf.ck:
// sound file
// initialize empty string variable for filename
"" => string filename;
// set a default filename to be loaded
"/path/to/freesound.org/100391__dobroide__20100627-creek.wav" => filename;
// if arguments are passed to the script,
// use the first argument as filename instead
if( me.args() ) me.arg(0) => filename;
0.5 => float myvolume;
if( me.args() ) if (me.arg(1) != "") Std.atof(me.arg(1)) => myvolume;
<<< "myvolume: " + myvolume >>>;
SndBuf buf;
filename => buf.read;
myvolume => buf.gain;
1.0 => buf.rate;
// time loop
Impulse i => dac;
while( true )
{
int pos;
repeat( buf.samples() )
{
buf.valueAt( pos ) => i.next;
pos++;
1::samp => now;
}
}
(see the online version for more comments)
Having saved this script, we can now call it with chuck
as interpreter:
chuck.alsa loopsndbuf.ck
... which will start loading the sound as in default settings, and looping it. Or, we can call the script with arguments - note that the character to separate arguments from a chuck
script is colon (:
):
chuck.alsa loopsndbuf.ck:/path/to/freesound.org/23222__erdie__thunderstorm2.wav:0.4
... or, - finally - we can call multiple instances of the loopsndbuf.ck
script, which chuck
will run in parallel. For this, I'd rather put everything in a bash script, let's call it loopchuck.sh
:
set -x
PTH="/path/to/freesound.org"
chuck.alsa \
loopsndbuf.ck:$PTH/15528__ch0cchi__domestic-cat-purr.wav:1.0 \
loopsndbuf.ck:$PTH/100391__dobroide__20100627-creek.wav:0.05 \
loopsndbuf.ck:$PTH/23222__erdie__thunderstorm2.wav:0.4 \
loopsndbuf.ck:$PTH/2519__rhumphries__rbh-rain-01.wav:0.4 \
loopsndbuf.ck:$PTH/18766__reinsamba__chimney-fire.wav:0.4 \
loopsndbuf.ck:$PTH/53380__eric5335__meadow-ambience.wav:0.4
Running this script should basically load separate instances of the script, each with their own file and volume - chuck
will then first load all audio into memory, spitting something like this to stdout:
$ ./loopchuck.sh
++ PTH=/path/to/freesound.org
++ chuck.alsa loopsndbuf.ck:/path/to ....
"myvolume: 1.0000" : (string)
"myvolume: 0.0500" : (string)
"myvolume: 0.4000" : (string)
"myvolume: 0.4000" : (string)
"myvolume: 0.4000" : (string)
"myvolume: 0.4000" : (string)
... and then, it will have all the scripts start at the same time - and then it will loop all the sounds individually at sample accuracy, according to their file length - wrapping the loop for each file, when it concludes, separately. In other words, the phases of the loops are independent (similar to how the GUI program terminatorX treats loops by default) - and that is exactly what I was looking for :)
!! Note that you may have to press CTRL+C twice to fully stop the loopchuck.sh
script. (and yes, the audio samples used are from Freesound.org)
Note, however, that while chuck
loads all these files in memory, it uses quite a bit of CPU reproducing and mixing these loops - and so expect to have drops in the audio, if the OS has to handle some heavy operations (like GUI, say in the case when scrolling a webpage in your browser). In order to avoid this, I guess a jack
setup has to be used - but then one has to get involved with Linux real-time priorities and whatnot (which isn't necessarily a straightforward process).
Well, I guess I'm happy now - since this is all I wanted (I just wish I could mix this audio output with one from say vlc
or youtube in firefox
, though - done, check other answer) :)
; hope it helps others, too,
Cheers!
Best Answer
Let's create some sample files:
First, let's run a find command:
As you can see
{}
is replaced with the list of all the files thatfind
found. In this example, we have six matched files yetecho
is run only once.Note that shells have a limit on the number of characters that they will accept on a single command line.
find
knows this and, if there are too many files to put on one command line,find
will runecho
several times with different files until all the file names have been processed. This is why "the number of invocations of the command will be much less than the number of matched files."Let's test this by creating many files in our directory:
Now, let's execute a find command:
As you can see, even though this directory had over 10,000 files, the exec command was only run three times.