Variable frequency audio generator...

Hi all...

I intend to do an Audio Function Generator using Awk, (already started thanks to Don), but the biggest thing I have struggled with was variable frequency.

I was going to generate differing sized waveforms on the fly but that would that would mean the frequencies are dependent on any even number of bytes as odd numbered bytes could never create a symmetrical waveform like a sinewave or alternatively have a pre calculated look up table of lots of waveforms and sizes...

Then VOILA, a little lateral thinking created this idea; how about using a back door and forcing a .WAV file to use a different RATE inside its header.

Well it WORKS! WOOHOO...

This is a bash DEMO and has no error checking.
It suffers from aliasing at LF and minor frequency error on HF with and 8 byte sinewave set of values as because of the sampling frequency used.

#!/bin/bash
# VariFreq.sh
# NOTE:- This DEMO is only for 8 bit depth, unsigned integer, mono wave samnples.
# Other depths, stereo and signed 16 bit integers are equally posiible but this is all I need.
# Creative Commons Licence CC0. (Public Domain). First Upload to http://www.unix.com.
#
HEADER_START="\\x52\\x49\\x46\\x46\\x24\\x00\\x08\\x00\\x57\\x41\\x56\\x45\\x66\\x6d\\x74\\x20\\x10\\x00\\x00\\x00\\x01\\x00\\x01\\x00"
# bit 'RATE' and byte 'rate' default settings at 8000 SPS, 8 bit depth, mono, unsigned integer.
RATE="\\x40\\x1f\\x00\\x00\\x40\\x1f\\x00\\x00"
HEADER_END="\\x01\\x00\\x08\\x00\\x64\\x61\\x74\\x61\\x00\\x00\\x08\\x00"
DATA="\\x80\\x26\\x00\\x26\\x7F\\xD9\\xFF\\xD9"
FREQ=1000
SAMPLE=8000
BURST=10
#
clear
#
audio_signal()
{
	# Generate the 512KB file.
	printf "%b" "$HEADER_START" > /tmp/sinewave.wav
	printf "%b" "$RATE" >> /tmp/sinewave.wav
	printf "%b" "$HEADER_END" >> /tmp/sinewave.wav
	for waveform in {0..15}
	do
	        DATA=$DATA$DATA
	done
	printf "%b" "$DATA" >> /tmp/sinewave.wav
}
#
while true
do
	printf "\nEnter frequency between 250 and 6000, Ctrl-C to stop:- "
	read -r FREQ
	echo ""
	# Limited error checking here as it is a DEMO.
	if [ "$FREQ" = "" ]
	then
		FREQ=1000
	fi
	SAMPLE=$(( 8 * FREQ ))
	if [ "$FREQ" -gt 6000 ] || [ "$FREQ" -lt 250 ]
	then
		# Reset to 1KHz.
		SAMPLE=8000
	fi
	SAMPLE=$( printf "%04x" "$SAMPLE" )
	RATE="\\x${SAMPLE:2:2}\\x${SAMPLE:0:2}\\x00\\x00\\x${SAMPLE:2:2}\\x${SAMPLE:0:2}\\x00\\x00"
	DATA="\\x80\\x26\\x00\\x26\\x7F\\xD9\\xFF\\xD9"
	audio_signal
	# OSX command line player.
	afplay -t $BURST -d /tmp/sinewave.wav
	# ALSA command line player.
	# aplay -d $BURST -v /tmp/sinewave.wav
done

Results, MBP OSX 10.7.5, default bash terminal.

Enter frequency between 250 and 6000, Ctrl-C to stop:- 1357

Playing file: /tmp/sinewave.wav
Playing format: AudioStreamBasicDescription:  1 ch,  10856 Hz, 'lpcm' (0x00000008) 8-bit unsigned integer
Buffer Byte Size: 16384, Num Packets to Read: 16384

Enter frequency between 250 and 6000, Ctrl-C to stop:- 577

Playing file: /tmp/sinewave.wav
Playing format: AudioStreamBasicDescription:  1 ch,   4616 Hz, 'lpcm' (0x00000008) 8-bit unsigned integer
Buffer Byte Size: 16384, Num Packets to Read: 16384

Enter frequency between 250 and 6000, Ctrl-C to stop:- 5781

Playing file: /tmp/sinewave.wav
Playing format: AudioStreamBasicDescription:  1 ch,  46248 Hz, 'lpcm' (0x00000008) 8-bit unsigned integer
Buffer Byte Size: 23124, Num Packets to Read: 23124

Enter frequency between 250 and 6000, Ctrl-C to stop:- 234

Playing file: /tmp/sinewave.wav
Playing format: AudioStreamBasicDescription:  1 ch,   8000 Hz, 'lpcm' (0x00000008) 8-bit unsigned integer
Buffer Byte Size: 16384, Num Packets to Read: 16384

Enter frequency between 250 and 6000, Ctrl-C to stop:- 6001

Playing file: /tmp/sinewave.wav
Playing format: AudioStreamBasicDescription:  1 ch,   8000 Hz, 'lpcm' (0x00000008) 8-bit unsigned integer
Buffer Byte Size: 16384, Num Packets to Read: 16384

Enter frequency between 250 and 6000, Ctrl-C to stop:- ^C
AMIGA:barrywalker~/Desktop/Code/Shell> _

Enjoy finding simple solutuons to often bery difficult problems...

Bazza.

Interesting solution! I'm surprised it can tell the diff between 6000 and 6001 hz, most audio devices run at fixed frequencies and most players don't bother correcting. Using your much-beloved sox utility on a fixed-rate raw file might give you more reliable and portable control -- resample it to one rate and force it to output at another rate.

Something like:

printf "\x01\xff" > file0 # Square wave
printf "" > file1

# Double the number of wavelengths 2^8 i.e. 256 times
for N in 1 2 3 4 5 6 7 8
do
        cat file0 >> file1
        cat file1 >> file0
done

while true
do
        cat file0 file1
done | sox ...

rm -f file0 file1

Hi Corona688...

Files are not needed if you are using SOX and piping.
An ultra simple string piped into sox without the need for files at all...
But I am NOT using SOX here but the very, very limited afplay audio player for OSX.
afplay cannot replay raw files only a few basic formats of which .WAV is one.

#!/usr/local/bin/dash
square="    ~~~~"
for n in 1 2 3 4 5 6 7 8 9 10
do
	square=$square$square
done
echo "$square" | sox -b 8 -c 1 -e unsigned-integer -t raw -r 8000 - -d

Results:-

Last login: Fri Aug 19 10:08:28 on ttys000
AMIGA:barrywalker~> cd Desktop/Code/Shell
AMIGA:barrywalker~/Desktop/Code/Shell> chmod 755 sox_pipe.sh
AMIGA:barrywalker~/Desktop/Code/Shell> ./sox_pipe.sh

-: (raw)

  Encoding: Unsigned PCM  
  Channels: 1 @ 8-bit    
Samplerate: 8000Hz       
Replaygain: off         
  Duration: unknown      

In:0.00% 00:00:01.02 [00:00:00.00] Out:45.2k [======|======] Hd:1.3 Clip:0    
Done.
AMIGA:barrywalker~/Desktop/Code/Shell> _

Just change the sample speed inside SOX from 8000 to anything you like within limits.