Saving a wave pattern to a .WAV file

Henko
Posts: 814
Joined: Tue Apr 09, 2013 12:23 pm
My devices: iPhone,iPad
Windows
Location: Groningen, Netherlands
Flag: Netherlands

Saving a wave pattern to a .WAV file

Post by Henko »

Long ago (dec. 4th, 2016) i was fooling around with waveforms, FFT, and things like that. At last i have made a function to encapsulate such waveforms in a .WAV file, in order that they can be played.
In this little test program a very simple sine wave is generated. More elaborate wave patterns could be made, using a mix of other basic waveforms and ADSR techniques, but those i leave for the real sound freaks.

Code: Select all

N=16000 ! dim v(N) ! pi=4*atan(1)
signal(N,v)                      ' generate a wavepattern
wav_file(N,v,"out.wav")   ' encapsulate it in a .WAV file
play_it("out.wav")           ' play it
end


' function in which a waveform is made up by the user.
' nb = the number of values in the wave array s().
' s() = the array with samples, being numerical values.
'    the values may have any value. they are 
'    normalized to integers in the range 0-255 by the 
'    wav_file() function.
' in this test program, a very simple wave pattern is
' generated: a simple sine wave.
'
def signal(nb,s())    ' a very simple (sine) wavepattern
freq=440 ! sample_rate=11025
fac=2*.pi*freq/sample_rate
for i=0 to nb-1 ! s(i)=sin(fac*i) ! next i
end def

' function to encapsulate a series of samples in a .WAV file.
' the sample values are normalized to integers in the 0-255 range.
' the .WAV file is made up as mono, 11025 sample rate, and 8 bits
' per sample. A more elaborate encapsulation function would allow 
' choises for these characteristics.
' nbytes = the number of samplevalues in the s() array.
' file$ = the .WAV file to wich the data are to be saved.

def wav_file(nbytes,s(),file$)
dim snd(nbytes),out(44),x(4)
bmax=-1000000 ! bmin=1000000
for i=0 to nbytes-1 
  bmax=max(bmax,s(i)) ! bmin=min(bmin,s(i))
  next i
fac=255/(bmax-bmin)
for i=0 to nbytes-1
  snd(i)=s(i)-bmin ! snd(i)=floor(fac*snd(i))
  next i
for i=0 to 43 ! read out(i) ! next i
num2hex4(nbytes,x)
for i=0 to 3 ! out(i+40)=x(i) ! next i
num2hex4(nbytes+36,x)
for i=0 to 3 ! out(i+4)=x(i) ! next i
file file$ writedim out,44,0
file file$ writedim snd,nbytes,0
file file$ setpos 0
data 82,73,70,70,156,145,0,0,87,65,86
data 69,102,109,116,32,16,0,0,0,1,0
data 1,0,17,43,0,0,17,43,0,0,1
data 0,8,0,100,97,116,97,180,1,0,0
return
end def

def play_it(mus$)
music m$ load mus$
ml=music_length(m$)
music m$ play
pause ml
end def

def num2hex4(x,a())
a(0)=0 ! a(1)=0 ! a(2)=0 ! a(3)=0
f1=256 ! f2=f1*f1 ! f3=f1*f2
a(3)=floor(x/f3)
if a(3) then x-=a(3)*f3
a(2)=floor(x/f2)
if a(2) then x-=a(2)*f2
a(1)=floor(x/f1)
if a(1) then x-=a(1)*f1
a(0)=x
return
end def

{signal_util}    '  code to be found in a post dd dec.4,2016
' def sine_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def saw_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def tri_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def block_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def noise(N,v(),ampl,distr$,mode$)
' def hex2dec(h$)
' def dec2hex$(num,npos)
' def h2d(h$)
' def d2h$(num)
' def play_music(mus$)
' def wav(m$)
' def wav_info(m$,xs,ys,ww,hh,R,G,B,alpha)
' def r_fft(N,x(),reX(),imX())
' def c_fft(M,x())
' def graph(txt$,N,v(),ytop,sc)
' def graph_magn(M,rx(),ix(),ytop)
' def sigstat(N,v(),st())
' def box(ytop)
' def box2(ytop)
' def cntrl(xs,ys)
' def sbar(xs,ys,ww,ss,sw)
' def b_p(b$)

User avatar
rbytes
Posts: 1338
Joined: Sun May 31, 2015 12:11 am
My devices: iPhone 11 Pro Max
iPad Pro 11
MacBook
Dell Inspiron laptop
CHUWI Plus 10 convertible Windows/Android tablet
Location: Calgary, Canada
Flag: Canada
Contact:

Re: Saving a wave pattern to a .WAV file

Post by rbytes »

Amazing how fast this works! And the tones are quite pleasant. With enough of them, we could even make music! :D
The only thing that gets me down is gravity...

User avatar
rbytes
Posts: 1338
Joined: Sun May 31, 2015 12:11 am
My devices: iPhone 11 Pro Max
iPad Pro 11
MacBook
Dell Inspiron laptop
CHUWI Plus 10 convertible Windows/Android tablet
Location: Calgary, Canada
Flag: Canada
Contact:

Re: Saving a wave pattern to a .WAV file

Post by rbytes »

It would be fun to create multi-toned wave files. I just played with the code long enough to get it to generate a 75 Hz file and a 150 Hz wave file in one run, and play them one after the other. It was the start of a pretty good bass line, but not ideal since I didn't combine them into a single wave file, and there is an audible click between them. You would undoubtedly have a better way. ;)
The only thing that gets me down is gravity...

Henko
Posts: 814
Joined: Tue Apr 09, 2013 12:23 pm
My devices: iPhone,iPad
Windows
Location: Groningen, Netherlands
Flag: Netherlands

Re: Saving a wave pattern to a .WAV file

Post by Henko »

Hi Richard,

It will not be too difficult to append a data stream (a tone) to an existing .WAV file. The function definition:
def wav_file(nbytes,s(),file$)
would become :
def wav_file(nbytes,s(),file$,mode$)
With mode$="0" for a new file (or overwriting an existing one)
and mode$="+" for appending
Furthermore, two fields in the .WAV header should be modified.
So, it can be done rather easy.

The data streams to append could be easily generated by the functions in the "signal_util" library. There are four of them and a "noise" generator. By example the function:
def sine_signal(N,v(),ampl,freq,shift,vdc,mode$)
Can be used to create a data stream with mode$="0"
Then, to "sharpen" the sound a bit, you would mix the near identical saw_signal function into it with mode$="+", and of course the same N and v() parameters, but playing with the other parameters.

In this way, you can make a .wav file with any number of tones.
As for the length of the tones, the sample speed in the current .wav header is 11025 samples/sec. Hence, the number of samples for a tone should be:
N = 11025 * T, where T is the required length of the tone in seconds.

Furthermore, the current number of channels in the .wav header is one. This could be modified, but this affects the required organization of the bytes in the data stream, as you can see in the document:
"http://soundfile.sapp.org/doc/WaveFormat/"

Finally, the wave generating functions could be extended to include some ADSR like modulation, or it could be applied to the the final datastream that is appended to a .wav file, but i do not intend to go as far as that.

A lot of extensions are possible, but where does it end? I think it ends up in a set of very inferior tools when compared with the already avalable SB instructions (and the iOS functions behind them).

It was just fun to play a little with the basics. It could be usefull in situations that could not be solved bij the standard SB tools.

A last remark: i noticed that the sound quality of low frequency tones varies strongly with the volume, that is, one my iPad.

Let me know if you want the facility to append tones to a .wav file, or maybe you want to try it yourself?

User avatar
rbytes
Posts: 1338
Joined: Sun May 31, 2015 12:11 am
My devices: iPhone 11 Pro Max
iPad Pro 11
MacBook
Dell Inspiron laptop
CHUWI Plus 10 convertible Windows/Android tablet
Location: Calgary, Canada
Flag: Canada
Contact:

Re: Saving a wave pattern to a .WAV file

Post by rbytes »

I would be interested in getting a facility to do that. Is it possible to create chords, or are we limited to sequential tones?
The only thing that gets me down is gravity...

Henko
Posts: 814
Joined: Tue Apr 09, 2013 12:23 pm
My devices: iPhone,iPad
Windows
Location: Groningen, Netherlands
Flag: Netherlands

Re: Saving a wave pattern to a .WAV file

Post by Henko »

Hello Richard,
One little step ahead.
First of alI i added flexibility to the encapsulation function. The way in which a sample string is processed can now be specified in terms of:
- number of channels (enables chords?)
- sample rate
- bits per channel
In the code to follow, the simplest way of serially playing notes is shown. Three notes are generated and added to the sample stream, which is then fed to the encapsulation function. In this way all substreams (each of the three notes) are interpreted the same (specified) way. The internet documentation hints at a possibility to give each substream its own specification. I still have to figure that out.
I also will try the multichannel feature, which should enable playing chords.

Note, that is is necessary to have the library "signal-util" into the same directory.

Code: Select all

N=4000 ! pi=4*atan(1)
dim v(3*N),sig(N),a(4)

sine_signal(N,sig,100,440,0,0,"0")     ' generate A4 tone
for i=0 to N-1 ! v(i)=sig(i) ! next i  ' xfer it to stream v
sine_signal(N,sig,100,880,0,0,"0")     ' generate A5 tone
for i=0 to N-1 ! v(i+N)=sig(i) ! next i  'append it to stream v
block_signal(N,sig,100,440,0,0,"0")    ' generate block A4 tone
for i=0 to N-1 ! v(i+2*N)=sig(i) ! next i  '  append

wav_file(3*N,v,1,4000,8,"out.wav")   ' encapsulate in a .WAV file
play_music("out.wav")                   ' play it
wav_info("out.wav",100,100,300,500,.7,.7,.7,1)  ' show header info
end


' function to encapsulate a series of samples in a .WAV file.
' the sample values are normalized to integers in the 0-255 range.
' n_bytes = the number of sample values in the sample array s().
' n_channels = the number of (parallel) channels
' s_rate = the sample rate (8000, 11025, 44100, etc)
' bps = bits per sample
' file$ = the .WAV file to wich the data are to be saved.
'
def wav_file(n_bytes,s(),n_channels,s_rate,bps,file$)
dim snd(n_bytes),out(44),x(4)
bmax=-1000000 ! bmin=1000000
for i=0 to n_bytes-1 
  bmax=max(bmax,s(i)) ! bmin=min(bmin,s(i))
  next i
fac=255/(bmax-bmin)
for i=0 to n_bytes-1
  snd(i)=s(i)-bmin ! snd(i)=floor(fac*snd(i))
  next i
for i=0 to 43 ! read out(i) ! next i
b_rate=n_channels*bps/8*s_rate
num2hex4(n_bytes+36,x)     ' file size
for i=0 to 3 ! out(i+4)=x(i) ! next i
num2hex4(n_channels,x)  ' number of channels
for i=0 to 1 ! out(i+22)=x(i) ! next i
num2hex4(s_rate,x)      ' sample rate
for i=0 to 3 ! out(i+24)=x(i) ! next i
num2hex4(b_rate,x)      ' byte rate
for i=0 to 3 ! out(i+28)=x(i) ! next i
num2hex4(bps,x)         ' bits per sample
for i=0 to 1 ! out(i+34)=x(i) ! next i
num2hex4(n_bytes,x)  ' chunk size
for i=0 to 3 ! out(i+40)=x(i) ! next i
file file$ writedim out,44,0
file file$ writedim snd,n_bytes,0
file file$ setpos 0
data 82,73,70,70,0,0,0,0,87,65,86
data 69,102,109,116,32,16,0,0,0,1,0
data 1,0,0,0,0,0,0,0,0,0,1
data 0,8,0,100,97,116,97,0,0,0,0
return
end def

def play_it(mus$)
music m$ load mus$
ml=music_length(m$)
music m$ play
pause ml
end def

def num2hex4(x,a())
a(0)=0 ! a(1)=0 ! a(2)=0 ! a(3)=0
f1=256 ! f2=f1*f1 ! f3=f1*f2
a(3)=floor(x/f3)
if a(3) then x-=a(3)*f3
a(2)=floor(x/f2)
if a(2) then x-=a(2)*f2
a(1)=floor(x/f1)
if a(1) then x-=a(1)*f1
a(0)=x
return
end def

{signal_util}    '  code to be found in a post dd dec.4,2016
' def sine_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def saw_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def tri_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def block_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def noise(N,v(),ampl,distr$,mode$)
' def hex2dec(h$)
' def dec2hex$(num,npos)
' def h2d(h$)
' def d2h$(num)
' def play_music(mus$)
' def wav(m$)
' def wav_info(m$,xs,ys,ww,hh,R,G,B,alpha)
' def r_fft(N,x(),reX(),imX())
' def c_fft(M,x())
' def graph(txt$,N,v(),ytop,sc)
' def graph_magn(M,rx(),ix(),ytop)
' def sigstat(N,v(),st())
' def box(ytop)
' def box2(ytop)
' def cntrl(xs,ys)
' def sbar(xs,ys,ww,ss,sw)
' def b_p(b$)
IMG_1441.PNG
IMG_1441.PNG (323.67 KiB) Viewed 5327 times

User avatar
rbytes
Posts: 1338
Joined: Sun May 31, 2015 12:11 am
My devices: iPhone 11 Pro Max
iPad Pro 11
MacBook
Dell Inspiron laptop
CHUWI Plus 10 convertible Windows/Android tablet
Location: Calgary, Canada
Flag: Canada
Contact:

Re: Saving a wave pattern to a .WAV file

Post by rbytes »

This utility is progressing nicely. I looked up the precise tunings for A Major
In equal temperament (interesting back story on that system of tuning),
which I found at https://pages.mtu.edu/~suits/notefreqs.html).
I completed an octave scale starting from 440 Hz, also called A4.

Changing note timing also changes pitches proportionately, so to create notes
of various lengths there would need to be a timing variable that is independent of pitch.

Code: Select all

/*
Wave File Generator
by Henko January 2018
with correct frequencies for
an equal-tempered scale
in A major
*/
N=4000 ! pi=4*atan(1)
dim v(9*N),sig(N),a(4)

sine_signal(N,sig,100,440,0,0,"0")     ' generate A4 tone
for i=0 to N-1 ! v(i)=sig(i) ! next i  ' xfer it to stream v
sine_signal(N,sig,100,493.88,0,0,"0")     ' generate B4 tone
for i=0 to N-1 ! v(i+N)=sig(i) ! next i  ' xfer it to stream v
sine_signal(N,sig,100,554.37,0,0,"0")     ' generate C#5 tone
for i=0 to N-1 ! v(i+2*N)=sig(i) ! next i  ' xfer it to stream v
sine_signal(N,sig,100,587.33,0,0,"0")     ' generate D5 tone
for i=0 to N-1 ! v(i+3*N)=sig(i) ! next i  ' xfer it to stream v
sine_signal(N,sig,100,659.25,0,0,"0")     ' generate E5 tone
for i=0 to N-1 ! v(i+4*N)=sig(i) ! next i  ' xfer it to stream v
sine_signal(N,sig,100,739.99,0,0,"0")     ' generate F#5 tone
for i=0 to N-1 ! v(i+5*N)=sig(i) ! next i  'append it to stream v
sine_signal(N,sig,100,830.61,0,0,"0")     ' generate G#5 tone
for i=0 to N-1 ! v(i+6*N)=sig(i) ! next i  'append it to stream v
sine_signal(N,sig,100,880,0,0,"0")     ' generate A5 tone
for i=0 to N-1 ! v(i+7*N)=sig(i) ! next i  'append it to stream v
block_signal(N,sig,100,440,0,0,"0")    ' generate block A4 tone
for i=0 to N-1 ! v(i+8*N)=sig(i) ! next i  '  append

wav_file(9*N,v,1,4000,8,"out.wav")   ' encapsulate in a .WAV file
play_music("out.wav")                   ' play it
wav_info("out.wav",100,100,300,500,.7,.7,.7,1)  ' show header info
end


' function to encapsulate a series of samples in a .WAV file.
' the sample values are normalized to integers in the 0-255 range.
' n_bytes = the number of sample values in the sample array s().
' n_channels = the number of (parallel) channels
' s_rate = the sample rate (8000, 11025, 44100, etc)
' bps = bits per sample
' file$ = the .WAV file to wich the data are to be saved.
'
def wav_file(n_bytes,s(),n_channels,s_rate,bps,file$)
dim snd(n_bytes),out(44),x(4)
bmax=-1000000 ! bmin=1000000
for i=0 to n_bytes-1 
  bmax=max(bmax,s(i)) ! bmin=min(bmin,s(i))
  next i
fac=255/(bmax-bmin)
for i=0 to n_bytes-1
  snd(i)=s(i)-bmin ! snd(i)=floor(fac*snd(i))
  next i
for i=0 to 43 ! read out(i) ! next i
b_rate=n_channels*bps/8*s_rate
num2hex4(n_bytes+36,x)     ' file size
for i=0 to 3 ! out(i+4)=x(i) ! next i
num2hex4(n_channels,x)  ' number of channels
for i=0 to 1 ! out(i+22)=x(i) ! next i
num2hex4(s_rate,x)      ' sample rate
for i=0 to 3 ! out(i+24)=x(i) ! next i
num2hex4(b_rate,x)      ' byte rate
for i=0 to 3 ! out(i+28)=x(i) ! next i
num2hex4(bps,x)         ' bits per sample
for i=0 to 1 ! out(i+34)=x(i) ! next i
num2hex4(n_bytes,x)  ' chunk size
for i=0 to 3 ! out(i+40)=x(i) ! next i
file file$ writedim out,44,0
file file$ writedim snd,n_bytes,0
file file$ setpos 0
data 82,73,70,70,0,0,0,0,87,65,86
data 69,102,109,116,32,16,0,0,0,1,0
data 1,0,0,0,0,0,0,0,0,0,1
data 0,8,0,100,97,116,97,0,0,0,0
return
end def

def play_it(mus$)
music m$ load mus$
ml=music_length(m$)
music m$ play
pause ml
end def

def num2hex4(x,a())
a(0)=0 ! a(1)=0 ! a(2)=0 ! a(3)=0
f1=256 ! f2=f1*f1 ! f3=f1*f2
a(3)=floor(x/f3)
if a(3) then x-=a(3)*f3
a(2)=floor(x/f2)
if a(2) then x-=a(2)*f2
a(1)=floor(x/f1)
if a(1) then x-=a(1)*f1
a(0)=x
return
end def

{signal_util}    '  code to be found in a post dd dec.4,2016
' def sine_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def saw_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def tri_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def block_signal(N,v(),ampl,freq,shift,vdc,mode$)
' def noise(N,v(),ampl,distr$,mode$)
' def hex2dec(h$)
' def dec2hex$(num,npos)
' def h2d(h$)
' def d2h$(num)
' def play_music(mus$)
' def wav(m$)
' def wav_info(m$,xs,ys,ww,hh,R,G,B,alpha)
' def r_fft(N,x(),reX(),imX())
' def c_fft(M,x())
' def graph(txt$,N,v(),ytop,sc)
' def graph_magn(M,rx(),ix(),ytop)
' def sigstat(N,v(),st())
' def box(ytop)
' def box2(ytop)
' def cntrl(xs,ys)
' def sbar(xs,ys,ww,ss,sw)
' def b_p(b$)
The only thing that gets me down is gravity...

Henko
Posts: 814
Joined: Tue Apr 09, 2013 12:23 pm
My devices: iPhone,iPad
Windows
Location: Groningen, Netherlands
Flag: Netherlands

Re: Saving a wave pattern to a .WAV file

Post by Henko »

Here's a little tool.

Code: Select all

dim tone$(13),freq(13)
for i=1 to 12 ! read tone$(i) ! next i
fac=2^(1/12)
input "Which octave? ":n
print "Octave "&n ! print
freq(10)=440*2^(n-4)
for i=10 to 2 step -1 ! freq(i-1)=freq(i)/fac ! next i
freq(11)=freq(10)*fac ! freq(12)=freq(11)*fac
for i=1 to 12
  print tone$(i);" = "; int(10*freq(i))/10
  next i
data "c ","c+","d ","d+","e ","f ","f+","g ","g+","a ","a+","b "
end
IMG_1442.PNG
IMG_1442.PNG (304.54 KiB) Viewed 5319 times

User avatar
rbytes
Posts: 1338
Joined: Sun May 31, 2015 12:11 am
My devices: iPhone 11 Pro Max
iPad Pro 11
MacBook
Dell Inspiron laptop
CHUWI Plus 10 convertible Windows/Android tablet
Location: Calgary, Canada
Flag: Canada
Contact:

Re: Saving a wave pattern to a .WAV file

Post by rbytes »

Quite elegant!
The only thing that gets me down is gravity...

Henko
Posts: 814
Joined: Tue Apr 09, 2013 12:23 pm
My devices: iPhone,iPad
Windows
Location: Groningen, Netherlands
Flag: Netherlands

Re: Saving a wave pattern to a .WAV file

Post by Henko »

"Changing note timing also changes pitches proportionately, so to create notes
of various lengths there would need to be a timing variable that is independent of pitch."

Hi Richard,
I don't quite understand what you mean here.

The duration T of a note is simply : T = number_of_samples x sample_rate,
hence, if T is the required duration of the tone, and the value for sample_rate is passed to the "wav_file() function, then the generated sample stream for that note must consist of N = number_of_samples = T / sample_rate.

The pitch of the tone is entirely defined by the frequency.

Within the generating functions like sine_signal(), the time variable is calculated in such a way, that the N samples contain a number of cycles which is equal to the specified frequency.

Post Reply