Another WAV/WAVE composing example

Share your advanced PureBasic knowledge/code with the community.
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Another WAV/WAVE composing example

Post by Lunasole »

There is just one main function, converting your data to ready-to-use unpacked WAV file: CreateWaveArray().
I think it's pretty simple to use and understand. It's not perfect and optimized but works nice ^^
It comes from 2002 and originally created by Axel Brink, I've remembered it from my earlier experiments with sounds and ported to PB.
8-bit and 16-bit are supported, multichannel sound too (didn't tested yet) and wide range of sample rate.

The test_append_freq() function here is for example only, you decide what and how you will append as sound samples.
The same with PlayWave().

Code: Select all


;Reads sample data and returns an array with bytes which can be
;written to a .wav file
;Uses PCM (i.e. no compression)
;Source, VB5/VB6:		By Axel Brink (axel@fmf.nl), 21-May-2002
;Ported to Purebasic by Lunasole, 2016

;WaveData:  * Contents:  Sample values of type Long
;           * Structure: An array(channel, samplenumber)
;           * Channels:  The number of elements in the first dimension
;                        determines the number of channels
;           * Samples:   The number of elements in the second dimension
;                        determines the number of samples
;                        8-bit samples must be between 0 and 255
;                        16-bit samples must be between -32768 and 32767
;           Note that indexing is supposed to start at 0.
;SampleRate:             Number of samples per second. Typically 44100.
;BitsPerSample:          Number of bits per sample. 8 or 16.
;RETURN:		0 on error, output array size else
Procedure CreateWaveArray(Array OutputFile.a (1), Array WaveData.l (2), SampleRate, BitsPerSample)
	Protected.l Byte1Mask = 255      ;Long integers are stored little endian.
	Protected.l Byte2Mask = 65280	 ;These bit masks select bytes from a number to store.
	Protected.l Byte3Mask = 16711680
	Protected.l Byte2Divisor = 256
	Protected.l Byte3Divisor = 65536
	Protected.l Byte4Divisor = 16777216
	
	Protected.l NumSamples, NumChannels, FileSize, ByteRate 
	Protected.l BlockAlign ;## "The number of bytes for one sample including all channels.
						   ;    I wonder what happens when this number isn't an integer?"
	
	Protected.l ChunkSize  ;## "36 + SubChunk2Size, or more precisely:
						   ;                               4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
						   ;                               This is the size of the rest of the chunk
						   ;                               following this number.  This is the size of the
						   ;                               entire file in bytes minus 8 bytes for the
						   ;                               two fields not included in this count:
						   ;                               ChunkID and ChunkSize."
	Protected.l Subchunk2Size  ;## "== NumSamples * NumChannels * BitsPerSample/8
							   ;                               This is the number of bytes in the data.
							   ;                               You can also think of this as the size
							   ;                               of the read of the subchunk following this
							   ;                               number."
	Protected.l SampleNr, ChannelNr
	
	If BitsPerSample <> 8 And BitsPerSample <> 16
		ProcedureReturn 0
	EndIf
	
	NumChannels = ArraySize(WaveData(), 1) + 1
	NumSamples = ArraySize(WaveData(), 2) + 1
	
	ByteRate = SampleRate * NumChannels * BitsPerSample / 8
	BlockAlign = NumChannels * BitsPerSample / 8
	
	FileSize = 44 + NumSamples * BlockAlign
	ChunkSize = FileSize - 8
	Subchunk2Size = NumSamples * NumChannels * BitsPerSample / 8
	
	ReDim OutputFile.a (FileSize - 1)
	
	;** ChunkID **
	OutputFile(0) = Asc("R")
	OutputFile(1) = Asc("I")
	OutputFile(2) = Asc("F")
	OutputFile(3) = Asc("F")
	
	;** ChunkSize **
	OutputFile(4) = ChunkSize & Byte1Mask ;Little endian
	OutputFile(5) = (ChunkSize & Byte2Mask) / Byte2Divisor
	OutputFile(6) = (ChunkSize & Byte3Mask) / Byte3Divisor
	OutputFile(7) = ChunkSize / Byte4Divisor
	
	;** Format **
	OutputFile(8) = Asc("W")
	OutputFile(9) = Asc("A")
	OutputFile(10) = Asc("V")
	OutputFile(11) = Asc("E")
	
	;** Subchunk1ID **
	OutputFile(12) = Asc("f")
	OutputFile(13) = Asc("m")
	OutputFile(14) = Asc("t")
	OutputFile(15) = Asc(" ")
	
	;** Subchunk1Size ** (16 for PCM)
	OutputFile(16) = 16 & Byte1Mask
	OutputFile(17) = (16 & Byte2Mask) / Byte2Divisor
	OutputFile(18) = (16 & Byte3Mask) / Byte3Divisor
	OutputFile(19) = 16 / Byte4Divisor
	
	;** AudioFormat ** (1 for PCM)
	OutputFile(20) = 1 & Byte1Mask
	OutputFile(21) = (1 & Byte2Mask) / Byte2Divisor
	
	;** NumChannels **
	OutputFile(22) = NumChannels & Byte1Mask
	OutputFile(23) = (NumChannels & Byte2Mask) / Byte2Divisor
	
	;** SampleRate **
	OutputFile(24) = SampleRate & Byte1Mask
	OutputFile(25) = (SampleRate & Byte2Mask) / Byte2Divisor
	OutputFile(26) = (SampleRate & Byte3Mask) / Byte3Divisor
	OutputFile(27) = SampleRate / Byte4Divisor
	
	;** ByteRate **
	OutputFile(28) = ByteRate & Byte1Mask
	OutputFile(29) = (ByteRate & Byte2Mask) / Byte2Divisor
	OutputFile(30) = (ByteRate & Byte3Mask) / Byte3Divisor
	OutputFile(31) = ByteRate / Byte4Divisor
	
	;** BlockAlign **
	OutputFile(32) = BlockAlign & Byte1Mask
	OutputFile(33) = (BlockAlign & Byte2Mask) / Byte2Divisor
	
	;** BitsPerSample **
	OutputFile(34) = BitsPerSample & Byte1Mask
	OutputFile(35) = (BitsPerSample & Byte2Mask) / Byte2Divisor
	
	;** Subchunk2ID **
	OutputFile(36) = Asc("d")
	OutputFile(37) = Asc("a")
	OutputFile(38) = Asc("t")
	OutputFile(39) = Asc("a")
	
	;** Subchunk2Size **
	OutputFile(40) = Subchunk2Size & Byte1Mask
	OutputFile(41) = (Subchunk2Size & Byte2Mask) / Byte2Divisor
	OutputFile(42) = (Subchunk2Size & Byte3Mask) / Byte3Divisor
	OutputFile(43) = Subchunk2Size / Byte4Divisor
	
	If BitsPerSample = 8  ;Samples are unsigned bytes; from 0 to 255
		For SampleNr = 0 To NumSamples - 1
			For ChannelNr = 0 To NumChannels - 1
				OutputFile(44 + BlockAlign * SampleNr + ChannelNr) = WaveData(ChannelNr, SampleNr) & Byte1Mask
			Next ChannelNr
		Next SampleNr
	ElseIf BitsPerSample = 16  ;Samples are 2;s complement signed bytes; from -32768 to 32767
		For SampleNr = 0 To NumSamples - 1
			For ChannelNr = 0 To NumChannels - 1
				OutputFile(44 + BlockAlign * SampleNr + ChannelNr * BitsPerSample / 8) = WaveData(ChannelNr, SampleNr) & Byte1Mask
				OutputFile(45 + BlockAlign * SampleNr + ChannelNr * BitsPerSample / 8) = (WaveData(ChannelNr, SampleNr) & Byte2Mask) / Byte2Divisor
			Next ChannelNr
		Next SampleNr
	EndIf
	
	ProcedureReturn ArraySize(OutputFile())
EndProcedure



; additional functions
Procedure test_append_freq(Array Frames.l(2), VOL, FREQ, TIME, QUALITY, MODE)
	Protected WMax, i, a = ArraySize(Frames(), 2), i_max = TIME * (QUALITY * 0.001)
	If MODE = 8
		WMax = 255 * VOL
	ElseIf MODE = 16
		WMax = 32765 * VOL
	EndIf
	
	ReDim Frames(0, a + i_max)
	For i = 0 To i_max
		Frames(0, i + a) = (WMax * Sin(#PI * 2 * FREQ * i / QUALITY))
	Next i
EndProcedure

Procedure PlayWave(Array Frames.l(2), QUALITY, MODE)
	;following will dump wav to a file
	Protected Dim OutF.a (0)
	CreateWaveArray(OutF(), Frames(), QUALITY, MODE)
	
	Define x = CatchSound(#PB_Any, @OutF(0))
	If IsSound(x)
		; dump to file
		; 		CreateFile(2, "out.wav")
		; 			WriteData(2, OutF(), ArraySize(OutF()))
		; 		CloseFile(2)
		
		PlaySound(x)
		Delay(SoundLength(x, #PB_Sound_Millisecond) + 256)
		FreeSound(x)
	EndIf
EndProcedure


;;;;;;;;;;;;;;;;;
; the first dimension of array is number of channels (0 means 1 channel or 'mono')
; the second contains sound samples for every channel
Dim WaveFrames.l (0, 0)

Define Q = 44100     				; 44100 ; 22050 ; 11025 ; 5512
Define M = 8						; 8 or 16 bits
Define T = 2 * 1000					; time in ms
Define F							; frequency

InitSound()

; add some sounds
For F = 0 To 20000 Step 500
	test_append_freq (WaveFrames(), 1.0, F, 90, Q, M)
Next F

; pack samples to wave file and play it (or save to disk)
PlayWave(WaveFrames(), Q, M)
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
User avatar
idle
Always Here
Always Here
Posts: 5913
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: Another WAV/WAVE composing example

Post by idle »

that would be great for retro 8 bit games, works on ubuntu 14.04 x64
Windows 11, Manjaro, Raspberry Pi OS
Image
User avatar
Derren
Enthusiast
Enthusiast
Posts: 316
Joined: Sat Jul 23, 2011 1:13 am
Location: Germany

Re: Another WAV/WAVE composing example

Post by Derren »

THIS is quite awesome! Thanks for posting :) :) :)

Simple, randomized "groove" that kinda reminds of the aforementioned 8-bit games: :D

Code: Select all

For F = 0 To 20000 Step 500
   test_append_freq (WaveFrames(), 1.0, 220*(Random(3)+1), 90, Q, M)
   test_append_freq (WaveFrames(), 0, 440, 40, Q, M)
Next F
I wonder, can you play multiple notes at once? Adding a bassline would really make this a strong tool
Post Reply