create wav files from scratch (draw a wav file)

Post your working scripts, libraries and tools
User avatar
jeeswg
Posts: 6644
Joined: 19 Dec 2016, 01:58
Location: UK

create wav files from scratch (draw a wav file)

16 Jul 2017, 17:39

If you create sine waves in 16 bit v. 8 bit, you can hear the difference in quality, the 8-bit version sounds machine-like, the 16-bit version sound natural.

Note: I believe that when I created this script, it gave the exact same binary data as Sound Recorder did in Windows XP, so if you created a silent 60-second wave sound via this script and via Sound Recorder, the files would be identical.

The wav files in this example are 58 bytes of header data, and the rest is a block of (1-byte/2-byte) 'samples', a number that specifies the y-coordinate of a wave at a certain point in time. Multiple y-coordinates giving you the shape of a wave.

Code: Select all

;create wav by jeeswg
ListLines, Off

q:: ;triangular waves
w:: ;sine waves
if InStr(A_ThisHotkey, "q")
	vType := "tri"
else if InStr(A_ThisHotkey, "w")
	vType := "sin"

;settings:
vDir := A_Desktop
vPath = %vDir%\z %vType% %A_Now%.wav
vQual := "22050,16,1" ;radio quality: PCM 22.050 kHz, 16 Bit, Mono (Sound Recorder default)
;vQual := "44100,16,2" ;cd quality: PCM 44.100 kHz, 16 Bit, Stereo
;vQual := "22050,8,1" ;radio quality: PCM 22.050 kHz, 8 Bit, Mono (New Wave Sound)
vDuration := 10 ;seconds

;==================================================

oTemp := StrSplit(vQual, ",")
vSamplesPerSec := oTemp.1
vBitsPerSample := oTemp.2
vChannels := oTemp.3 ;number of channels (mono or stereo)
oTemp := ""
vBytesPerSample := vBitsPerSample/8
;if vSamples = 1000, mono file will have 1000 samples, stereo file will have 2000 samples (1000 for left speaker, 1000 for right speaker)
vSamples := vSamplesPerSec*vDuration ;samples per channel
vSizeData := vChannels * vSamples * vBytesPerSample ;bytes in sample area:

if !(vChannels = 1)
{
	MsgBox, % "error: only mono currently supported"
	return
}

;==================================================

;header is 58 bytes
;e.g. FOURCC identifiers: RIFF, 'fmt ', data, fact
VarSetCapacity(vData, 58+vSizeData, 0)

StrPut("RIFF", &vData+0, "CP0") ;chunkID
NumPut(50 + vSizeData, vData, 4) ;chunkSize
StrPut("WAVE", &vData+8, "CP0") ;(format)

;'fmt ' uses a WAVEFORMATEX structure
StrPut("fmt ", &vData+12, "CP0") ;chunkID
NumPut(18, vData, 16) ;chunkSize ;e.g. 18 for PCM
NumPut(1, vData, 20, "UShort") ;wFormatTag ;WAVE_FORMAT_PCM := 0x1
NumPut(vChannels, vData, 22, "UShort") ;nChannels ;1=mono, 2=stereo (number of channels)
NumPut(vSamplesPerSec, vData, 24) ;nSamplesPerSec ;e.g. 22050
NumPut(vChannels*vSamplesPerSec*vBytesPerSample, vData, 28) ;nAvgBytesPerSec ;nChannels * nSamplesPerSec * (wBitsPerSample/8)
NumPut(vChannels*vBytesPerSample, vData, 32, "UShort") ;nBlockAlign ;nChannels * (wBitsPerSample/8)
NumPut(vBitsPerSample, vData, 34, "UShort") ;wBitsPerSample
NumPut(0, vData, 36, "UShort") ;cbSize

StrPut("fact", &vData+38, "CP0") ;chunkID
NumPut(4, vData, 42) ;chunkSize
NumPut(vSamples, vData, 46) ;(number of samples (per channel))

StrPut("data", &vData+50, "CP0") ;chunkID
NumPut(vSamples*vChannels*vBytesPerSample, vData, 54) ;chunkSize ;'NumSamples' * nChannels * (wBitsPerSample/8)

;==================================================

if (vType = "tri")
	Gosub SubTri
else if (vType = "sin")
	Gosub SubSin
return

;==================================================

SubTri:
;diagonal lines:
;e.g. 1: 0, 1, 2, ..., 254, 255, 254, ..., 2, 1
;e.g. 2: 0, 2, 4, ..., 252, 254, 252, ..., 4, 2
;e.g. 4: 0, 4, 8, ..., 248, 252, 248, ..., 8, 4
;e.g. 8: 0, 8, 16, ..., 240, 248, 240, ..., 16, 8

vListNum := "1,2,3,4,5"
if (vBytesPerSample = 1)
	vLimit1 := 0, vLimit2 := 255
else if (vBytesPerSample = 2)
	vLimit1 := -32768, vLimit2 := 32767
Loop, Parse, vListNum, % ","
{
	vIndex2 := A_LoopField
	vList%vIndex2% := vIndex := vLimit1
	vDiff := A_LoopField
	if (vBytesPerSample = 2)
		vDiff *= 256
	Loop
	{
		vIndex += vDiff
		vList%vIndex2% .= "," vIndex
		if (vIndex + vDiff > vLimit2)
			break
	}
	Loop
	{
		vIndex -= vDiff
		vList%vIndex2% .= "," vIndex
		if (vIndex - vDiff <= vLimit1)
			break
	}
}

vAddr := &vData + 58
vEnd := &vData + 58 + vSizeData
vDuration := 2000 ;milliseconds
vSamplesPerDuration := Floor((vSamplesPerSec*vDuration)/1000)
Loop, Parse, vListNum, % ","
{
	vList := vList%A_LoopField%
	StrReplace(vList, ",", "", vSamplesPerWave), vSamplesPerWave += 1
	if (vSamplesPerWave <= 0)
		break
	vEndWave := vAddr + vSamplesPerDuration*vBytesPerSample
	;MsgBox, % vSamplesPerWave
	;MsgBox, % A_Index " " vAddr
	Loop
	{
		if (vAddr + vSamplesPerWave*vBytesPerSample > vEnd)
		|| (vAddr + vSamplesPerWave*vBytesPerSample > vEndWave)
			break
		JEE_WaveWriteList(vAddr, vList, vBytesPerSample)
		vAddr += vSamplesPerWave*vBytesPerSample
	}
}
Gosub SubEnd
return

;==================================================

SubSin:
vAddr := &vData + 58
vEnd := &vData + 58 + vSizeData
vFreq := 261.63 ;middle C
vDuration := 2000 ;milliseconds
vSamplesPerDuration := Floor((vSamplesPerSec*vDuration)/1000)
Loop
{
	vSamplesPerWave := Floor(vSamplesPerSec/vFreq)
	if (vSamplesPerWave <= 0)
		break
	vEndWave := vAddr + vSamplesPerDuration*vBytesPerSample
	;MsgBox, % vSamplesPerWave
	;MsgBox, % A_Index " " vAddr
	Loop
	{
		if (vAddr + vSamplesPerWave*vBytesPerSample > vEnd)
		|| (vAddr + vSamplesPerWave*vBytesPerSample > vEndWave)
			break
		JEE_WaveWriteNote(vAddr, vSamplesPerWave, vBytesPerSample)
		vAddr += vSamplesPerWave*vBytesPerSample
	}
	vFreq += 20
}
Gosub SubEnd
return

;==================================================

SubEnd:
if !FileExist(vPath)
{
	oFile := FileOpen(vPath, "w")
	oFile.RawWrite(vData, 58+vSizeData)
	oFile.Close()
	Run, % vPath
	;MsgBox, % "done"
}
return

;==================================================

;e.g. middle C: 261.63 Hz
;we need 261.63 waves per second
;if there are 22050 samples per second
;we need 22050/261.63 = 84.28 samples per wave
;(write sine wave)
JEE_WaveWriteNote(vAddr, vSamplesPerWave, vBytesPerSample)
{
	if (vBytesPerSample = 1)
		vPtrType := "UChar", vNum1 := 1, vNum2 := 127
	else if (vBytesPerSample = 2)
		vPtrType := "Short", vNum1 := 0, vNum2 := 32767
	else
		return
	vAdjust := 360 / vSamplesPerWave
	vTemp := 0
	vList := ""
	Loop, % Floor(vSamplesPerWave)
	{
		vNum := Round((Sin(vTemp*0.01745329252)+vNum1)*vNum2) ;pi/180 (approximately 0.01745329252)
		vList .= vNum ","
		vTemp += vAdjust
		NumPut(vNum, vAddr+0, (A_Index-1)*vBytesPerSample, vPtrType)
	}
	;MsgBox, % vList
}

;==================================================

JEE_WaveWriteList(vAddr, vList, vBytesPerSample)
{
	if (vBytesPerSample = 1)
		vPtrType := "UChar"
	else if (vBytesPerSample = 2)
		vPtrType := "Short"
	else
		return
	Loop, Parse, vList, % ","
		NumPut(A_LoopField, vAddr+0, (A_Index-1)*vBytesPerSample, vPtrType)
}
Good links:
WAVE File Format
http://web.archive.org/web/200801131952 ... h/wave.htm
Microsoft WAVE soundfile format
http://web.archive.org/web/200812101456 ... aveFormat/
Wave format
https://sharkysoft.com/archive/lava/doc ... ontent.htm
[a 'fmt ' chunk uses a WAVEFORMATEX structure]
WAVEFORMATEX structure (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx
[use of 'chunkID' and 'chunkSize']
Resource Interchange File Format (RIFF) (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

More links:
Wave File Specifications
http://www-mmsp.ece.mcgill.ca/Documents ... /WAVE.html
riff-format.txt
http://www.neurophys.wisc.edu/auditory/riff-format.txt
WAV - Wikipedia
https://en.wikipedia.org/wiki/WAV
Last edited by jeeswg on 24 Jul 2017, 18:23, edited 3 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
jeeswg
Posts: 6644
Joined: 19 Dec 2016, 01:58
Location: UK

Re: create wav files from scratch (draw a wav file)

16 Jul 2017, 22:00

Here is a musical SoundBeep example, useful for testing that waves are of the correct pitch:

musical SoundBeeps, MIDI note numbers, scientific pitch notation - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=34619
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 3890
Joined: 17 Jul 2016, 01:02
Contact:

Re: create wav files from scratch (draw a wav file)

18 Jul 2017, 05:17

Really cool jeeswg, well done! :clap:
Thanks for sharing, Cheers. ☕
kilimra
Posts: 7
Joined: 30 Dec 2015, 16:47

Re: create wav files from scratch (draw a wav file)

18 Aug 2017, 22:55

HI, jeeswg, are you interested in writing a script to create GIF? I need it very much.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: Ben, swub and 36 guests