Appending Audio Clip/s with Nyquist

Hi all,

A question, I would like to add a short, mono sound clip at various points in a track.
A few years ago, @steve posted some code to do something similar.
First one creates a new mono track, at the beginning the required sound clip that needs to be
copied multiple times is placed and selected.
Then the Nyquist code is run, with offsets provided like such:

(setf times (list
5.830000000
6.546190476
7.260136054
7.982108844
8.698843537
9.431904762))


(defun snd (t0)
  (sim (s-rest 0)
    (at-abs t0 (cue *track*))))

(simrep (i (length times)) (snd (nth i times)))

The above works perfectly but there is a problem, any more than 50 time entries in the list
leads to an overflow.
I have around 500 - 600 time entries.
So, my question is, how to overcome the limitation?
T.I.A.
Paul

See my post here for an alternative approach that can be used for more repeats without the stack overflow problem: Nyquist Prompt script and lastlog.txt contents provided - #14 by steve

Hi Steve,

The code you posted does work but not doing what I intend.
Your code repeats the sound 1000 times offset each time by 1ms.

What I’m hoping to achieve is the sound being placed at specific offsets dictated by
the list in the variable “times”, i.e.
at 5.8300 secs then at 6.5461 secs etc.
Thanks.

UPDATE:

I dumped the idea of using Nyquist due to the various limitations.
First I export a label track with the required timings and run that through an external
C program.
It will read the timings from the label track, add a sound sample at those times and create a wav file.
The resulting wav file I just import/drag back into Audacity.
Problem solved.

Interesting. What is the “C program” that you used?

Yes I know. I posted the link because it shows how you can modify the code that you have so that it can run with more than 50 time entries. The simrep function loops by recursion, and so is limited by the stack size. The code that I linked to loops by iteration, so it doesn’t have that limitation.

It’s something I cobbled together myself.

Basically it works like such:

First thing it does is take the sound sample I want to add and converts it to a temp raw file.
The reason for this is that then appending to the file is easy as no headers to worry about.

Then, it reads the first column of the exported LabelTrack.txt file to get the timings, line by line.
Takes the first “time”, subtracts 0 and places the audio sample in a raw file.
It then takes the second “time”, subtracts the first “time” and also subtracts the duration
of the sound sample and appends that to the raw file.
And so on until the last line.
Then it takes the raw PCM samples, adds a header, modifies the fields in the header such as
duration, etc and writes out the real wav file.
Last thing it does is delete the temp files.

Nice. You should publish that code somewhere.

An alternative approach, that may have been a bit simpler to write, would be to use the command-line audio too SoX.

Like this:

(defun do-repeats (sig offsets)
  (let ((out (s-rest 0)))
    (dolist (t0 offsets)
      (setf out (sim out
                     (at-abs t0 (cue sig)))))
    out))


(multichan-expand #'do-repeats *track* times)

I normally don’t like to publish code for several reasons…

  1. By making it public, the author takes on a certain amount of responsibility for maintaining the code, responding to questions, problems, etc.
    I just don’t have the time.
    I created the code for my own use and it works for me, bugs and all.

  2. I only use Linux for my private work, so any code I publish will only be useful to a very
    small number of people.

I did think about using sox but very quickly ditched the idea due to the many bugs in sox.
Even small things like passing a float to sox, if it’s say .33678 and not 0.33678, it has a freakout.
There are others and version 14.x is just weird compared to the more stable 12.x

Sox-14-bugs

I could have included some of the source code from sox but since the only thing I really needed to do with audio is WAV → PCM and vice versa, it’s a very simple routine as the
wav header is pretty basic and only 44 bytes long.
So just landed up coding it from scratch.

You can post the source code on Github Gist which seems to be used for one-off or example code. It’s like pastebin. Since there’s no issue tracker or pull requests or other features like there are with a repository, there’s less expectation that the author will maintain it. It would be a good idea to add a license notice in a comment at the top (e.g., CC0 if you don’t care what anyone does with it, or CC-BY if you just want attribution, etc.), so that everyone knows what they are allowed to do with it. Then if someone else cares to do something serious with it, they can fork your code and maintain it themselves.

Also there may be more interest in this sort of task than you imagine—it sounds like it would be useful for podcasters to automate inserting bumpers or stingers.

I use Linux too. I imagine there’s a large overlap between the people who use Linux and people who know how to compile C code (if somebody wants to use your code in a non-standard OS like Windows they can port it themselves).

@steve and @christop

OK I have re-read what you both have written and reconsidered my approach but with a caveat,
please don’t expect help with the code if it does not work for any specific purpose.
I coded it with a specific purpose in mind and as far as that goes, it’s working
for me so as far as I’m concerned, it’s job done.

@christop

Github Gist is a great idea but I don’t want to have to sign up and go thru all that, so will
rather just post the code here as it is Audacity related.
As to your valid point about the overlap between Linux users and knowing C, I suspect there
will be an even bigger overlap if it was done in Bash, albeit with some dependencies.
So re-wrote it in Bash and will post here.
Please read the notes before using it.

Although the script looks long (217 lines, it’s mostly comments, the actual code is around 10% of that).

Regarding your example of someone using it for inserting sweepers and bumpers, I don’t think
it will be the right tool for the job, as it was written for short, periodic sound clips such as drums.
But feel free to experiment and modify as you see fit.

@steve

To make it easier for potential users to peruse the code before downloading it, will post here
as CODE and also as an attachment (just change the extension from .txt to .sh).
Hope that is OK.
If not, please feel free to amend this post as you see fit.

#!/bin/bash

# ====================================================================================
#
# License for this script is CC0
# and you as the user assume all responsibility by using it.
#
# Please read carefully before using.
#
# ============
# DESCRIPTION:
# ============
#
# Linux Bash script to take Audacity exported timing file and convert
# to a single wav file with drum beats seperated by the
# timing in LabelTrack.txt
#
# The resulting wav file can then be dragged back into Audacity.
#
# The original version of this script was actually a compiled C pgm.
# However after re-reading comments by @Steve and @christop on the
# Audacity forum, I realize that compiling and indeed knowing C and having
# the tool chain, may be a problem for many.
# So here is a Bash only version, the downside is it has some dependencies
# and some are version specific due to bugs in them.
# This script is also orders of magnitude slower than the compiled C pgm.
# I guess that is the price to be paid for convinience ;-)
# 
# Dependencies for this script are Sox, bc and ffmpeg
#
# Very N.B. The latest version of sox (14.x) has several known bugs,
# I used v12.18.2
# bc version is 1.07.1 but most will do.
# ffmpeg is version 4.1.10 but most newer ones will be OK.
#
# Note that silence.wav and beat.wav must be mono and originally created by you.
#
# To change the drum sample, just overwrite the current one
# but keep the name the same, i.e. beat.wav or change the script.
# Remember, must be mono 16 bit wav.
# Must also compensate for it's length when we send to bc.
# (Watch out for this or you will get wrong timing placements)
#
# ================
# NOTES ON USING:
# ================
#
# Create a folder, I called mine TimingsToBeats (same as this script)
# and in there place this script (with exec permissions), and silence.wav
# which is just a mono 10 sec clip of silence which this script will
# create temp files from with needed durations and then delete the temp files.
# It will not delete silence.wav itself.
#
# Make sure you create a 10 sec silence mono file before running this script.
# Easy enough in Audacity, just export as Mono, wav (16 bit) at 44.1KHz
# The exported LabelTrack.txt file from Audacity should also be saved in this folder.
#
# Also ensure you have a short, mono "beat.wav" file in the same folder.
# This is the audio clip that this script will insert at the timings in the LabelTrack.txt
#
# To execute the script, open a command prompt in the folder and type:
# ./TimingsToBeats.sh
#
# If all goes well, you should see a printout for each "time" entry and a 
# "Conversion Complete" message at the end.
# 
# ==========================
# MORE NOTES AND DISCLAIMER:
# ==========================
#
# I wrote this for a specific need I have, i.e. to add extra percussion to existing
# audio tracks.
# As such, percussion sounds tend to be short (50 - 100 mS in most cases) and also,
# and this is important, percussion is mono in a mix (i.e. center).
# With that in mind, this script is only meant for mono files, starting with the
# LabelTrack.txt file, to the silence and the extra percussion sound to add.
# It will not work with ultra long sounds or stereo tracks as is.
#
# The script is simple enough and well commented so if it does not work for you,
# modify it until it does.
#
# Also, please don't ask for help or send bug reports and expect a reply from me.
# I don't mean to come across as rude, but I simply don't have the time to tend to them.
# From time to time, I do visit the forum and may comment but don't hold me to it.
# You are of course free to discuss, modify and comment amongst yourselves for the
# benefit of yourself and others.
#
# As I mentioned before, this in the Bash version and as such, will not be as quick
# or as efficiently coded as it's C version.
#
# Are there bugs? Probably but it does the job for me so mission accomplished.
# Please also be aware that there is no error checking whatsoever, originally
# this script (pgm actually) was only for my own use until I was convinced to 
# publicly release it. ;-)
#
# Lastly, keep in mind that when used to add percussion sounds to an existing 
# audio track, there will be drift over time.
# This is due to the slight timing differences of the system where the audio track 
# was created versus the new track.
#
# For example, let's say you download a song from YT and it should be 120 BPM,
# I can guarantee that it's not exactly at that BPM and as the track progresses,
# the error will accumulate and drift out of time more and more.
# In practice, it's not a big deal to cut and then "nudge" bits of the newly created
# track to match up.
#
# For an average song of say 4 minutes, you will only need to make 15 or so cuts
# and "nudges".
# A lot less time consuming than manually having to add each new "drum" sound by hand.
# Do the math, a four minute song at 120 BPM with 4/4 timing, there will be close to
# several hundred drum beats.
# Something you don't want to do by hand, and hence the reason for this script.
#
# There are of course other ways to do something similar but they all have certain disadvantages.
# One example is Audacity's "Generate Rhythm Track" but you will be limited by the sounds it adds
# and drift will still be a problem.
#
# Hope it's of some use to others as is or you may find other uses for it.
# Paulo-V
# ====================================================================================


Input="LabelTrack.txt"
Silence="silence.wav"
DrumBeat="beat.wav"

OutputFileRaw="TheOutput.raw"
OutputFile="TheOutput.wav"

TempFileRaw="Temp.raw"
TempSilence="TempSilence.wav"

ThisLine=0
PreviousLine="000.000000"
CurrentLine="000.000000"

	# ======================================================================
	# Begin file read loop.
	# Note that Bash will work out that LabelTrack.txt has 3 columns
	#
	# This is the format that Audacity spits out the labels track and I haven't
	# found a way to change it.
	# Also make sure that there is no space between Label and Track in the text
	# file name when you export it.
	#
	# Since we only want the middle column, we still assign 3 vars to the read loop
	# i.e. Ignore, ThisLine, IgnoreThisToo.
	# but only use ThisLine.
	#
	# We should actually only use the first column (which is identical to the second)
	# but for some reason the Bash read command truncates the first number.
	# Didn't want to waste time finding out, so just used the second column.
	# =======================================================================

while read -r Ignore ThisLine IgnoreThisToo
do
	# =======================================================================
	# Send to bc to do the maths, subtract length of actual drum beat (50 mS)
	# and the sed at the end is to add a leading zero else sox complains.
	# =======================================================================
   
  CurrentLine=$(echo "scale=9;($ThisLine - $PreviousLine - 0.05)" | bc -l | sed 's/^\./0./')
  
	# ========================================================================
	# The line below is just a visual sanity timings check.
	# ========================================================================
  
  echo "current ---> "$CurrentLine "   this ----> "$ThisLine "   previous ---> " $PreviousLine
  
	# =========================================================================
	# Create temp silence file of duration picked up by timings,
	# Add that to drum beat and save in output file.
	# =========================================================================
	
  sox $Silence $TempSilence trim 0 $CurrentLine
  sox $TempSilence $DrumBeat $OutputFile
 
	# ==========================================================================
	# Now, it will convert that wav to raw pcm so we can just keep appending
	# without worring about headers.
	# ffmpeg works better than sox for this.
	# The -y at the end tells ffmpeg to overwrite any existing files
	# and the > /dev/null thing surpresses all the cli output garbage.
	# ==========================================================================
	
  ffmpeg -i $OutputFile -f s16le -acodec pcm_s16le $OutputFileRaw -y > /dev/null 2>&1
	
	# ===========================================================================
	# Do the actual concatinating (appending) with cat.
	# could have also used dd
	# dd if="$OutputFileRaw" of="$TempFileRaw" status=none conv=notrunc oflag=append
	# ===========================================================================
	
  cat $OutputFileRaw >> $TempFileRaw
  
  PreviousLine=$ThisLine

  
done < "$Input"

	# ============================================================================
	# End of timings file read loop so convert the raw pcm back to wav with ffmpeg
	# This is now the wav we import back into Audacity or other DAW.
	# ============================================================================
	
ffmpeg -f s16le -ar 44.1k -ac 1 -i $TempFileRaw $OutputFile -y > /dev/null 2>&1

	# ============================================================================
	# Remove temp files.
	# The wav file to be dragged back into Audacity is called TheOutput.wav
	# ============================================================================
	
rm $TempFileRaw
rm $OutputFileRaw
rm $TempSilence

echo "========== Conversion Complete ============="

The actual script is also attached to this post, see below.
(Remember to change extension from .txt to .sh and set required permissions).

TimingsToBeats.txt (9.1 KB)

Sure, it could be helpful for other Audacity users.

I do think it’s unfair to describe SoX as having “many bugs”. There is better known audio software with many more bugs than SoX. Also, the first bug I looked at probably isn’t really a bug (It looks tome that they have just run into a limitation of the wav format).

OK fair enough will change it to “several known bugs”.

Two more points that may be worth mentioning:

Keep in mind the old saying: garbage In, garbage out.
It applies here to how accurate the timings in LabelTrack are.
Spend time making sure that there are no false or out-of-time labels before you export.
Use filters, gates, etc on the audio.
Then when you finally use Analyze → Beat Finder and export the file, it will be accurate.

Once you start using this script a few times, it will get cumbersome to open the CLi everytime, no need.
Just open the folder/directory that the script (along with silence.wav and beat.wav) are placed, with your favourite GUI file manager.
Once you export (from Audacity) the LabelTrack.txt file (also to that dir),
simply double click on the script.
You will see a bunch of temp files being created and sizes changing rapidly.
Once finished, all temp files are auto deleted and you will be left with TheOutput.wav.
Just drag that wav file back into Audacity.

Very nice, and nicely commented.

I see now that you wrote it to make a drum track, and it makes sense to automate adding hundreds of beats. I’m sure I can come up with other uses for it. :wink:

Thanks for posting it!

Thank you @christop hope it’s of some use.

BTW, started a new thread about a possible, I repeat possible bug in Audacity, where it
exports ID3 metadata (even if you clear the fields) in WAV, MP3 and RAW (under certain conditions).

Here it is

This could have ill effects on creating and exporting any “drum beats” to use with my script above.
The result is a short glitch at the end of the sound.
I’m also suspecting that sox is not fully aware of ID3 data so when this line comes along:

sox $TempSilence $DrumBeat $OutputFile

It adds the extra ID3 data as part of the sound, resulting in very short, repetitive loud glitches.
They will look something like this:

DrumBeatGlitch

It may turn out to only be a problem on my side but just highlighting it if you do stumble
across this and it drives you nuts whilst you trying to work out what it is.

If you do run across this problem, there is a quick fix without the need to download anything.
I’m sure you have “dd” on your machine.

The “empty” metadata fields are normally around 52 bytes.
So first delete the last 52 bytes, let’s say from your wanted wav file:

dd if=TheWavFile.wav of=TheWavFile-no-id3.wav bs=1 count=$(($(stat -c %s TheWavFile.wav) - 52))

Then to make sure that file length stays the same so as not to trip up any file size pointers in the main header, add 52 bytes of zero:

dd if=/dev/zero bs=1 count=52 >> TheWavFile-no-id3.wav

This also works for raw files but in this case, no need to add the zero bytes at the end as there is no header.