1khz test tone

  • Thread starter Thread starter Chris.
  • Start date Start date
I just found that while doing a search and was just about to post it.

Thanks!
 
most multitrack or wave editing programs should have a signal generator in them.
 
How about free AND embarrassingly complex? :D This will generate a raw file of the specified number of seconds in the specified bit depth, with the specified sine wave frequency, in big endian, unsigned raw format. Convertsion from that into something usable is left as an exercise for the reader, but probably involves sox.

WARNING: This may take DAYS even with a fast CPU. :D Yes, I could have done the obvious performance optimization and run the loop only up to $PERIOD, writing to a temp file, then copied the temp file $SECONDS * $FREQUENCY times into the output file, but that wouldn't be nearly as amusing. And even with that optimization, it would still probably take hours to compute this, and it might not even work. It's a quick and dirty hack that I wrote in the last thirty minutes or so just to prove it could be done....

Yes, I need a girlfriend. Badly.


#!/bin/sh

SECONDS=1
SAMPLE_RATE=48000
BITS=32
FREQUENCY=1000
PERIOD=`echo "$SAMPLE_RATE / $FREQUENCY" | bc`
MAXVAL=`echo "(2 ^ ($BITS)) - 1" | bc`
OUTFILE=$1

SAMPLES=$(($SECONDS * $SAMPLE_RATE))

if [ "x$OUTFILE" = "x" ] ; then
echo "Usage: sinewave.sh <filename>";
exit 0;
fi

# PI is 4*a(1)

writebyte()
{
if [ $1 -gt 256 ] ; then
echo "BAD VALUE: $1";
elif [ $1 -lt 0 ] ; then
echo "BAD VALUE: $1";
fi
perl -e "printf('%c', $1)" >> $OUTFILE
}

# Wipe the output file.
printf "" > $OUTFILE

I=0
while [ $I -lt $SAMPLES ] ; do
# echo "$I < $SAMPLES" 1>&2
FLOATVAL=`echo "s(($I / $PERIOD) * 8 * a(1))" | bc -l`
# echo "FLOATVAL IS $FLOATVAL"

# Convert to range of 0 to 1.0, then multiply times the maximum value.
SCALEDVAL=`echo "(($FLOATVAL + 1.0000000000000000000000000000000000) / 2.0000000000000000000000000000000000000) * $MAXVAL" | bc`

INTVAL=`echo "scale=0; $SCALEDVAL / 1" | bc`
# echo $INTVAL
if [ $BITS -eq 32 ] ; then
BYTE_0=`echo "$INTVAL / 16777216" | bc`
BYTE_1=`echo "($INTVAL / 65536) % 256" | bc`
BYTE_2=`echo "($INTVAL / 256) % 256" | bc`
BYTE_3=`echo "$INTVAL % 256" | bc`
writebyte $BYTE_0
writebyte $BYTE_1
writebyte $BYTE_2
writebyte $BYTE_3
elif [ $BITS -eq 24 ] ; then
BYTE_0=`echo "$INTVAL / 65536" | bc`
BYTE_1=`echo "($INTVAL / 256) % 256" | bc`
BYTE_2=`echo "$INTVAL % 256" | bc`
writebyte $BYTE_0
writebyte $BYTE_1
writebyte $BYTE_2
elif [ $BITS -eq 16 ] ; then
BYTE_0=`echo "$INTVAL / 256" | bc`
BYTE_1=`echo "$INTVAL % 256" | bc`
writebyte $BYTE_0
writebyte $BYTE_1
else
writebyte $INTVAL
fi
I=$(($I + 1))
done
 
Last edited:
Whoa... so you're basically doing a binary write? That's... uh... creative?
 
timthetortoise said:
Whoa... so you're basically doing a binary write? That's... uh... creative?

Heheh. You should see my shell script version of Tetris. I'm working on a shell script version of Breakout, too. That is some scary code. :D

I could have written this without using the one line of Perl, but I'd either have to use awk (pretty ugly) or a combination of bc (to convert to octal) and tr (to convert the octal to a byte, and at least in the bc/tr version, would have been about a tenth as fast.

BTW, not days as I originally thought. It looks like it should complete in about three hours even on my 400 MHz G3 PowerBook. Of course, that's for only a single second of audio....

Edit: had to remove the one line of Perl. Turns out that neither Perl nor the shell's echo builtin can echo a NULL byte. This frustrated my attempt to do that performance optimization, and also meant that the previous code skipped all the zero bytes. Oops!

The only way to make this work sanely was to write the bytes to a temp file directly with tr, then concatenate that onto the file.

The previous code also had some math bugs, both with the bc bits and the simple shell math. *sigh*

Oh, and Mac OS X (at least 10.3) apparently defines the SECONDS variable for me as a very large value, resulting in highly unexpected behavior....

Anyway, the version below works on both Linux and Mac OS X... in seconds instead of hours.

On a little endian box, the conversion command is:

sox -l -x -r 48000 -c 1 -u -t raw testsin.raw testsin.aiff

On a big endian box, the conversion command is

sox -l -r 48000 -c 1 -u -t raw testsin.raw testsin.aiff


#!/bin/sh

NSECONDS=1
SAMPLE_RATE=48000
BITS=32
FREQUENCY=1000
PERIOD=`echo "$SAMPLE_RATE / $FREQUENCY" | bc`
MAXVAL=`echo "(2 ^ ($BITS)) - 1" | bc`
OUTFILE=$1

SAMPLES=$(($NSECONDS * $SAMPLE_RATE))

if [ "x$OUTFILE" = "x" ] ; then
echo "Usage: sinewave.sh <filename>";
exit 0;
fi

# PI is 4*a(1)

writebyte()
{
if [ $1 -gt 256 ] ; then
echo "BAD VALUE: $1";
elif [ $1 -lt 0 ] ; then
echo "BAD VALUE: $1";
fi
VAL=$1
# if [ $1 -eq 0 ] ; then
# VAL=1 # gnu tr is buggy.
# fi
OCTVAL="$(echo "ibase=10; obase=8; $VAL" | bc)"
printf '.' | tr '.' "\\$OCTVAL" >> /tmp/dag_wavetable
}

echo "Generating wave table."

# echo "PERIOD: $PERIOD"

rm -f /tmp/dag_wavetable

I=0
while [ $I -lt $PERIOD ] ; do
# echo "$I < $SAMPLES" 1>&2
FLOATVAL=`echo "scale=20 ; s(($I / $PERIOD) * 8 * a(1))" | bc -l`
# echo "FLOATVAL IS $FLOATVAL"

# Convert to range of 0 to 1.0, then multiply times the maximum value.
SCALEDVAL=`echo "scale=20 ; (($FLOATVAL + 1.0000000000000000000000000000000000) / 2.0000000000000000000000000000000000000) * $MAXVAL.00000000000000000000000000" | bc`

# echo "SCALEDVAL IS $SCALEDVAL"

INTVAL=`echo "scale=0; $SCALEDVAL / 1" | bc`
# echo $INTVAL
if [ $BITS -eq 32 ] ; then
BYTE_0=`echo "$INTVAL / 16777216" | bc`
BYTE_1=`echo "($INTVAL / 65536) % 256" | bc`
BYTE_2=`echo "($INTVAL / 256) % 256" | bc`
BYTE_3=`echo "$INTVAL % 256" | bc`
writebyte $BYTE_0
writebyte $BYTE_1
writebyte $BYTE_2
writebyte $BYTE_3
# echo "$BYTE_0 $BYTE_1 $BYTE_2 $BYTE_3"
elif [ $BITS -eq 24 ] ; then
BYTE_0=`echo "$INTVAL / 65536" | bc`
BYTE_1=`echo "($INTVAL / 256) % 256" | bc`
BYTE_2=`echo "$INTVAL % 256" | bc`
writebyte $BYTE_0
writebyte $BYTE_1
writebyte $BYTE_2
elif [ $BITS -eq 16 ] ; then
BYTE_0=`echo "$INTVAL / 256" | bc`
BYTE_1=`echo "$INTVAL % 256" | bc`
writebyte $BYTE_0
writebyte $BYTE_1
else
writebyte $INTVAL
fi
I=$(($I + 1))
# echo "BUF: $(echo "$BUF" | sed 's/./\./g')"
done

echo "Writing file."

TEST_ECHO_N="$(echo -n "FOO")"


# Wipe the output file.
printf "" > $OUTFILE

# cat /tmp/dag_wavetable >> $OUTFILE
# exit 0

CYCLES=$(($FREQUENCY * $NSECONDS))
# echo "CYCLES: $CYCLES FREQUENCY: $FREQUENCY SECONDS: $NSECONDS"
COUNT=0
while [ $COUNT -lt $CYCLES ] ; do
cat /tmp/dag_wavetable >> $OUTFILE
# echo "$COUNT < $CYCLES"
COUNT=$(($COUNT + 1))
done

echo "Done."
 
Last edited:
iceyflame said:
i.... dont get it

It's a shell script. You have to be either a programmer or a total UNIX nerd to get it.

Shell scripts are designed primarily for really simple stuff like executing a few commands in a particular order. They are basically the UNIX/Linux equivalent of a .BAT file in DOS (if you remember those).

Thus, to use a shell script to do something as complex as generating an audio file requires a certain degree of massochism. It's sort of a badge of honor to be able to make shell scripts do things they were never intended to do.... :D
 
Back
Top