Thank you very much
@jeeswg, this is exactly what I was looking for.
I reworked your example a bit by adding memory copying to sinus version:
Code: Select all
; Create .wav files. Based on work of jeeswg: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=34584&hilit=WAV
; Ammended by mslonik on 2024-02-04.
#Requires AutoHotkey v1.1.34+ ; Displays an error and quits if a version requirement is not met.
#SingleInstance, force ; Only one instance of this script may run at a time!
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn ; Enable warnings to assist with detecting common errors.
ListLines, Off ; ListLines is disabled to make it harder to determine how script works.
SetWorkingDir, % A_ScriptDir ; Ensures a consistent starting directory.
;==================================================
;settings:
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), only mono = 1 is currently supported
vData := 0
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; e.g. 1 * 22 050 [samp/s] * 10 [s] * 2 [bytes] = 220 500 [bytes]
if !(vChannels = 1)
{
MsgBox, % "error: only mono currently supported"
return
}
;==================================================
F_WAV_Header()
q:: ;triangular waves
F_SubTri()
return
w:: ;sine waves
F_SubSin()
return
e::
MsgBox, 64, % A_ScriptDir, % A_ScriptName . "`n`n" . "Bye!", 5
ExitApp, 0
return
;==================================================
F_WAV_Header()
{
;header is 58 bytes long
;e.g. FOURCC identifiers: RIFF, 'fmt ', data, fact
global ;assume-global mode of operation
VarSetCapacity(vData, 58 + vSizeData, 0) ;Enlarges a variable's holding capacity; vData is enlarged by 58 bytes (to keep header) and fills it with 0
StrPut("RIFF", &vData + 0, "CP0") ;chunkID; copies the string "RIFF" to vData with encoding "CP0" (system default ANSI code page)
NumPut(50 + vSizeData, vData, 4) ;chunkSize; stores a number 50 + vSizeData in vData + offset = 4.
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)
}
;==================================================
F_SubTri()
{
;diagonal lines in pseudo-arrays, with different slope ratios:
;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
global ;assume-global mode of operation
local vListNum := "1,2,3,4,5" ;5x pseudo-arrays, 2x seconds eac
, vList := ""
, vType := "tri"
, vPath := A_ScriptDir . "\" . A_Year . A_MM . A_DD . "_" . A_Hour . A_Min . "_" . "WAVgenerator" . "_" . vType . ".wav"
, vLimit1 := 0
, vLimit2 := 0
, vIndex := ""
, vIndex2 := ""
, vDiff := ""
, vAddr := &vData + 58
, vEnd := &vData + 58 + vSizeData
, vBatchDuration := 2
, vBatchSamples := vSamplesPerSec * vBatchDuration ;e.g. 22 050 [samples/s] * 2 [s] = 44 100 [samples]
, vEndWave := 0
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
}
}
Loop, Parse, vListNum, % ","
{
vList := vList%A_LoopField%
StrReplace(vList, ",", "", vSamplesPerWave)
vSamplesPerWave += 1
if (vSamplesPerWave <= 0)
break
vEndWave := vAddr + vBatchSamples * 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
}
}
F_SavePlay(vPath)
}
;==================================================
F_SubSin()
{
global ;assume-global mode of operation
local vAddr := &vData + 58 ;beginning of wave data, initial value
, vEnd := &vData + 58 + vSizeData ;vSizeData [b], vEnd [b]
, vFreq := 261.63 ;[Hz]; middle C, see https://en.wikipedia.org/wiki/Piano_key_frequencies
, vBatchDuration := 2 ;seconds
, vBatchSamples := vSamplesPerSec * vBatchDuration ;e.g. 22 050 [samples/s] * 2 [s] = 44 100 [samples]
, vSPAddr := 0 ;Single Period Address
, vTemp := 0
, vPtrSize := 0 ;in bytes
, vType := "sin"
, vPath := A_ScriptDir . "\" . A_Year . A_MM . A_DD . "_" . A_Hour . A_Min . "_" . "WAVgenerator" . "_" . vType . ".wav"
if (vBytesPerSample = 1)
vPtrType := "UChar"
, vPtrSize := 1
else if (vBytesPerSample = 2)
vPtrType := "Short"
, vPtrSize := 2
else
return
Loop
{
vSamplesPerWave := Floor(vSamplesPerSec / vFreq) ;or samples per period; e.g. 22 050 [samples/s] / 261.63 [Hz] = 84 [samples]
if (vSamplesPerWave <= 0)
break
vEndWave := vAddr + vBatchSamples * vBytesPerSample ;[b]
vSPAddr := F_SinglePeriod(vSamplesPerWave, vBytesPerSample) ;Single Period Address
Loop
{
if (vAddr + vSamplesPerWave * vBytesPerSample > vEnd)
|| (vAddr + vSamplesPerWave * vBytesPerSample > vEndWave)
break
F_BinaryCopy(vSPAddr, vAddr, vSamplesPerWave, vPtrType, vPtrSize)
; JEE_WaveWriteNote(vAddr, vSamplesPerWave, vBytesPerSample)
vAddr += vSamplesPerWave * vBytesPerSample
}
vFreq += 20
}
F_SavePlay(vPath)
}
;==================================================
F_BinaryCopy(vWhat, vWhere, vHowMany, vPtrType, vPtrSize)
{
global ;assume-global mode of operation
local vTemp := 0
Loop, % vHowMany
{
vTemp := NumGet(vWhat + 0, (A_Index - 1) * vPtrSize, vPtrType)
NumPut(vTemp, vWhere + 0, (A_Index - 1) * vPtrSize, vPtrType)
}
}
;==================================================
F_SavePlay(vPath)
{
global ;assume-global mode of operation
if !FileExist(vPath)
{
oFile := FileOpen(vPath, "w")
oFile.RawWrite(vData, 58 + vSizeData)
oFile.Close()
SoundPlay, % vPath, wait
}
}
;==================================================
F_SinglePeriod(vSamplesPerWave, vBytesPerSample)
{
local pi := 3.141592653589793
, vPtrType := ""
, vNum1 := 0
, vNum2 := 0
, vAdjustNew := 0
, vTempNew := 0
, vNumNew := 0
, GrantCap := 0
vWholePeriod := 0 ;global variable to store samples of the whole period
GrantCap := VarSetCapacity(vWholePeriod, vSamplesPerWave * vBytesPerSample, 0) ;Enlarges a variable's holding capacityand filled with 0
if (vBytesPerSample = 1)
vPtrType := "UChar", vNum1 := 1, vNum2 := 127
else if (vBytesPerSample = 2)
vPtrType := "Short", vNum1 := 0, vNum2 := 32767
else
return
vAdjustNew := (2 * pi) / vSamplesPerWave ;size of single step in [rad]; actually I suppose the formula should be (2 * pi) / (vSamplesPerWave - 1), but then it doesn't sound anymore like original file, it is more harsh
, vTempNew := 0
Loop, % vSamplesPerWave
{
vNumNew := Round((Sin(vTempNew) + vNum1) * vNum2)
NumPut(vNumNew, vWholePeriod, (A_Index - 1) * vBytesPerSample, vPtrType) ;Do not pass a variable reference if the variable contains the target address; in that case, pass an expression such as MyVar + 0
vTempNew += vAdjustNew
GrantCap := NumGet(vWholePeriod, (A_Index - 1) * vBytesPerSample, vPtrType)
}
return &vWholePeriod
}
;==================================================
;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)
{
local pi := 3.141592653589793
, OnlySinOld := 0
, OnlySinNew := 0
, vPtrType := ""
, vNum1 := 0
, vNum2 := 0
, vAdjustNew := 0
, vTempNew := 0
if (vBytesPerSample = 1)
vPtrType := "UChar", vNum1 := 1, vNum2 := 127
else if (vBytesPerSample = 2)
vPtrType := "Short", vNum1 := 0, vNum2 := 32767
else
return
vAdjustNew := (2 * pi) / vSamplesPerWave ;size of single step in [rad]; actually I suppose the formula should be (2 * pi) / (vSamplesPerWave - 1), but then it doesn't sound anymore like original file, it is more harsh
, vTempNew := 0
Loop, % vSamplesPerWave
{
vNumNew := Round((Sin(vTempNew) + vNum1) * vNum2)
NumPut(vNumNew, vAddr + 0, (A_Index - 1) * vBytesPerSample, vPtrType) ;Do not pass a variable reference if the variable contains the target address; in that case, pass an expression such as MyVar + 0
vTempNew += vAdjustNew
}
}
;==================================================
JEE_WaveWriteList(vAddr, vList, vBytesPerSample)
{
global ;assume-global mode of operation
local vPtrType := ""
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)
}
Next I thought I can prepare
F_SoundBeep, written in AutoHotkey analogue of native
SoundBeep:
Code: Select all
; Create .wav file which resembles SoundBeep command. Based on work of jeeswg: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=34584&hilit=WAV
; Ammended by mslonik on 2024-02-04.
#Requires AutoHotkey v1.1.34+ ; Displays an error and quits if a version requirement is not met.
#SingleInstance, force ; Only one instance of this script may run at a time!
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn ; Enable warnings to assist with detecting common errors.
ListLines, Off ; ListLines is disabled to make it harder to determine how script works.
SetWorkingDir, % A_ScriptDir ; Ensures a consistent starting directory.
;==================================================
vDuration := 2000 ;[ms]
, vFrequency := 261.63 ;[Hz]
F_SoundBeep(vFrequency, vDuration)
ExitApp, 0
;==================================================
F_SoundBeep(vFrequency, vDuration)
{
F_WAV_Header(vDuration)
F_SubSin(vFrequency)
F_SavePlay(vFrequency, vDuration)
}
;==================================================
F_WAV_Header(vDuration)
{
;header is 58 bytes long
;e.g. FOURCC identifiers: RIFF, 'fmt ', data, fact
global ;assume-global mode of operation
;settings:
vQual := "22050,16,1" ;radio quality: PCM 22.050 kHz, 16 Bit, Mono (Sound Recorder default)
oTemp := StrSplit(vQual, ",")
vSamplesPerSec := oTemp.1
vBitsPerSample := oTemp.2
vChannels := oTemp.3 ;number of channels (mono or stereo), only mono = 1 is currently supported
vData := 0
vBytesPerSample := vBitsPerSample / 8
vSamples := vSamplesPerSec * vDuration / 1000 ;samples per channel [s]
vSizeData := vChannels * vSamples * vBytesPerSample ;bytes in sample area; e.g. 1 * 22 050 [samp/s] * 10 [s] * 2 [bytes] = 220 500 [bytes]
VarSetCapacity(vData, 58 + vSizeData, 0) ;Enlarges a variable's holding capacity; vData is enlarged by 58 bytes (to keep header) and fills it with 0
StrPut("RIFF", &vData + 0, "CP0") ;chunkID; copies the string "RIFF" to vData with encoding "CP0" (system default ANSI code page)
NumPut(50 + vSizeData, vData, 4) ;chunkSize; stores a number 50 + vSizeData in vData + offset = 4.
StrPut("WAVE", &vData + 8, "CP0") ;(format)
StrPut("fmt ", &vData + 12, "CP0") ;chunkID ;'fmt ' uses a WAVEFORMATEX structure
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)
}
;==================================================
F_SubSin(vFreq)
{
global ;assume-global mode of operation
local vAddr := &vData + 58 ;beginning of wave data, initial value
, vEnd := &vData + 58 + vSizeData ;vSizeData [b], vEnd [b]
, vBatchDuration := 2 ;seconds
, vBatchSamples := vSamplesPerSec * vBatchDuration ;e.g. 22 050 [samples/s] * 2 [s] = 44 100 [samples]
, vSPAddr := 0 ;Single Period Address
, vTemp := 0
, vPtrSize := 0 ;in bytes
if (vBytesPerSample = 1)
vPtrType := "UChar"
, vPtrSize := 1
else if (vBytesPerSample = 2)
vPtrType := "Short"
, vPtrSize := 2
else
return
vSamplesPerWave := Floor(vSamplesPerSec / vFreq) ;or samples per period; e.g. 22 050 [samples/s] / 261.63 [Hz] = 84 [samples]
if (vSamplesPerWave <= 0)
return
; vEndWave := vAddr + vBatchSamples * vBytesPerSample ;[b]
vSPAddr := F_SinglePeriod(vSamplesPerWave, vBytesPerSample) ;Single Period Address
Loop
{
if (vAddr + vSamplesPerWave * vBytesPerSample > vEnd)
; || (vAddr + vSamplesPerWave * vBytesPerSample > vEndWave)
break
F_BinaryCopy(vSPAddr, vAddr, vSamplesPerWave, vPtrType, vPtrSize)
vAddr += vSamplesPerWave * vBytesPerSample
}
}
;==================================================
F_BinaryCopy(vWhat, vWhere, vHowMany, vPtrType, vPtrSize)
{
global ;assume-global mode of operation
local vTemp := 0
Loop, % vHowMany
{
vTemp := NumGet(vWhat + 0, (A_Index - 1) * vPtrSize, vPtrType)
NumPut(vTemp, vWhere + 0, (A_Index - 1) * vPtrSize, vPtrType)
}
}
;==================================================
F_SavePlay(vFreq, vDur)
{
global ;assume-global mode of operation
local vPath := A_ScriptDir . "\" SubStr(A_ScriptName, 1, -4) . "_" . vFreq . "_" . "Hz" . "_" . vDur . "_" . "ms" . ".wav"
; , vPath := A_ScriptDir . "\" . A_Year . A_MM . A_DD . "_" . A_Hour . A_Min . "_" . vType . "_" . vFreq . "_" . "Hz" . "_" . vDur . "_" . "ms" . ".wav"
if !FileExist(vPath)
{
oFile := FileOpen(vPath, "w")
oFile.RawWrite(vData, 58 + vSizeData)
oFile.Close()
}
SoundPlay, % vPath, wait
}
;==================================================
F_SinglePeriod(vSamplesPerWave, vBytesPerSample)
{
local pi := 3.141592653589793
, vPtrType := ""
, vNum1 := 0
, vNum2 := 0
, vAdjustNew := 0
, vTempNew := 0
, vNumNew := 0
, GrantCap := 0
vWholePeriod := 0 ;global variable to store samples of the whole period
GrantCap := VarSetCapacity(vWholePeriod, vSamplesPerWave * vBytesPerSample, 0) ;Enlarges a variable's holding capacityand filled with 0
if (vBytesPerSample = 1)
vPtrType := "UChar", vNum1 := 1, vNum2 := 127
else if (vBytesPerSample = 2)
vPtrType := "Short", vNum1 := 0, vNum2 := 32767
else
return
vAdjustNew := (2 * pi) / vSamplesPerWave ;size of single step in [rad]; actually I suppose the formula should be (2 * pi) / (vSamplesPerWave - 1), but then it doesn't sound anymore like original file, it is more harsh
, vTempNew := 0
Loop, % vSamplesPerWave
{
vNumNew := Round((Sin(vTempNew) + vNum1) * vNum2)
NumPut(vNumNew, vWholePeriod, (A_Index - 1) * vBytesPerSample, vPtrType) ;Do not pass a variable reference if the variable contains the target address; in that case, pass an expression such as MyVar + 0
vTempNew += vAdjustNew
GrantCap := NumGet(vWholePeriod, (A_Index - 1) * vBytesPerSample, vPtrType)
}
return &vWholePeriod
}
And finally I've prepared a demo with small GUI, which compares sound of
F_SoundBeep and native
SoundBeep. For comparison I've used frequencies of piano
instrument, 1 s long in duration.
Code: Select all
; Compare sound of .wav file to SoundBeep command. Based on work of jeeswg: https://www.autohotkey.com/boards/viewtopic.php?f=6&t=34584&hilit=WAV
; Please remember to save this file as UTF-8 with BOM.
; Ammended by mslonik on 2024-02-04.
#Requires AutoHotkey v1.1.34+ ; Displays an error and quits if a version requirement is not met.
#SingleInstance, force ; Only one instance of this script may run at a time!
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
#Warn ; Enable warnings to assist with detecting common errors.
ListLines, Off ; ListLines is disabled to make it harder to determine how script works.
SetWorkingDir, % A_ScriptDir ; Ensures a consistent starting directory.
;==================================================
Gui, PianoNoteDemo: New, -Resize +HwndPNDHwnd +Owner -MaximizeBox -MinimizeBox
Gui, PianoNoteDemo: Add, Text, x0 y0 HwndPND_T1, % "Please select piano note:`n`n"
Gui, PianoNoteDemo: Add, Text, x0 y0 HwndPND_T1, % "The first value is piano key no. left-to-right, next is note code, next is frequency in [Hz]"
Gui, PianoNoteDemo: Add, ListBox, w200 r1 vvFreq, ; piano key frequencies: https://en.wikipedia.org/wiki/Piano_key_frequencies
( Join
1 A0 27.50000|
2 A♯0/B♭0 29.13524|
3 B0 30.86771|
4 C1 Pedal C 32.70320|
5 C♯1/D♭1 34.64783|
6 D1 36.70810|
7 D♯1/E♭1 38.89087|
8 E1 41.20344|
9 F1 43.65353|
10 F♯1/G♭1 46.24930|
11 G1 48.99943|
12 G♯1/A♭1 51.91309|
13 A1 55.00000|
14 A♯1/B♭1 58.27047|
15 B1 61.73541|
16 C2 Deep C 65.40639|
17 C♯2/D♭2 69.29566|
18 D2 73.41619|
19 D♯2/E♭2 77.78175|
20 E2 82.40689|
21 F2 87.30706|
22 F♯2/G♭2 92.49861|
23 G2 97.99886|
24 G♯2/A♭2 103.8262|
25 A2 110.0000|
26 A♯2/B♭2 116.5409|
27 B2 123.4708|
28 C3 130.8128|
29 C♯3/D♭3 138.5913|
30 D3 146.8324|
31 D♯3/E♭3 155.5635|
32 E3 164.8138|
33 F3 174.6141|
34 F♯3/G♭3 184.9972|
35 G3 195.9977|
36 G♯3/A♭3 207.6523|
37 A3 220.0000|
38 A♯3/B♭3 233.0819|
39 B3 246.9417|
40 C4 Middle C 261.6256||
41 C♯4/D♭4 277.1826|
42 D4 293.6648|
43 D♯4/E♭4 311.1270|
44 E4 329.6276|
45 F4 349.2282|
46 F♯4/G♭4 369.9944|
47 G4 391.9954|
48 G♯4/A♭4 415.3047|
49 A4 A440 440.0000|
50 A♯4/B♭4 466.1638|
51 B4 493.8833|
52 C5 Tenor C 523.2511|
53 C♯5/D♭5 554.3653|
54 D5 587.3295|
55 D♯5/E♭5 622.2540|
56 E5 659.2551|
57 F5 698.4565|
58 F♯5/G♭5 739.9888|
59 G5 783.9909|
60 G♯5/A♭5 830.6094|
61 A5 880.0000|
62 A♯5/B♭5 932.3275|
63 B5 987.7666|
64 C6 Soprano C (High C) 1046.502|
65 C♯6/D♭6 1108.731|
66 D6 1174.659|
67 D♯6/E♭6 1244.508|
68 E6 1318.510|
69 F6 1396.913|
70 F♯6/G♭6 1479.978|
71 G6 1567.982|
72 G♯6/A♭6 1661.219|
73 A6 1760.000|
74 A♯6/B♭6 1864.655|
75 B6 1975.533|
76 C7 Double high C 2093.005|
77 C♯7/D♭7 2217.461|
78 D7 2349.318|
79 D♯7/E♭7 2489.016|
80 E7 2637.020|
81 F7 2793.826|
82 F♯7/G♭7 2959.955|
83 G7 3135.963|
84 G♯7/A♭7 3322.438|
85 A7 3520.000|
86 A♯7/B♭7 3729.310|
87 B7 3951.066|
88 C8 Eighth octave 4186.009|
)
Gui, PianoNoteDemo: Add, Button, x+20 Default w150 gF_ButtonGenWave, generate 1 s wav
Gui, PianoNoteDemo: Add, Button, y+10 w150 gF_SoundBeep, SoundBeep 1 s
Gui, PianoNoteDemo: Show, Center AutoSize, % SubStr(A_ScriptName, 1, -4)
return
;==================================================
PianoNoteDemoGuiClose(GuiHwnd)
{
ExitApp, 0
}
;==================================================
F_GetFreq()
{
global ;assume-global mode of operation
local vLastSpace := 0
, vF := 0
Gui, PianoNoteDemo: Submit, NoHide
vLastSpace := InStr(vFreq, A_Space, false, 0, 1)
return SubStr(vFreq, vLastSpace)
}
;==================================================
F_SoundBeep()
{
global ;assume-global mode of operation
local Frequency := F_GetFreq()
, Duration := 1000
SoundBeep, % Frequency, % Duration
}
;==================================================
F_ButtonGenWave()
{
global ;assume-global mode of operation
local Frequency := F_GetFreq()
, WavPattern := A_ScriptDir . "\" SubStr(A_ScriptName, 1, -4) . "_" . Frequency . "_" . "Hz" . "_" . 1000 . "_" . "ms" . ".wav"
if (FileExist(WavPattern))
{
SoundPlay, % WavPattern
return
}
F_WAV_Header(1000) ;[ms]
F_SubSin(Frequency)
F_SavePlay(Frequency, 1000)
}
;==================================================
F_WAV_Header(vDuration)
{
;header is 58 bytes long
;e.g. FOURCC identifiers: RIFF, 'fmt ', data, fact
global ;assume-global mode of operation
;settings:
vQual := "22050,16,1" ;radio quality: PCM 22.050 kHz, 16 Bit, Mono (Sound Recorder default)
oTemp := StrSplit(vQual, ",")
vSamplesPerSec := oTemp.1
vBitsPerSample := oTemp.2
vChannels := oTemp.3 ;number of channels (mono or stereo), only mono = 1 is currently supported
vData := 0
vBytesPerSample := vBitsPerSample / 8
vSamples := vSamplesPerSec * vDuration / 1000 ;samples per channel [s]
vSizeData := vChannels * vSamples * vBytesPerSample ;bytes in sample area; e.g. 1 * 22 050 [samp/s] * 10 [s] * 2 [bytes] = 220 500 [bytes]
VarSetCapacity(vData, 58 + vSizeData, 0) ;Enlarges a variable's holding capacity; vData is enlarged by 58 bytes (to keep header) and fills it with 0
StrPut("RIFF", &vData + 0, "CP0") ;chunkID; copies the string "RIFF" to vData with encoding "CP0" (system default ANSI code page)
NumPut(50 + vSizeData, vData, 4) ;chunkSize; stores a number 50 + vSizeData in vData + offset = 4.
StrPut("WAVE", &vData + 8, "CP0") ;(format)
StrPut("fmt ", &vData + 12, "CP0") ;chunkID ;'fmt ' uses a WAVEFORMATEX structure
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)
}
;==================================================
F_SubSin(vFreq)
{
global ;assume-global mode of operation
local vAddr := &vData + 58 ;beginning of wave data, initial value
, vEnd := &vData + 58 + vSizeData ;vSizeData [b], vEnd [b]
, vBatchDuration := 2 ;seconds
, vBatchSamples := vSamplesPerSec * vBatchDuration ;e.g. 22 050 [samples/s] * 2 [s] = 44 100 [samples]
, vSPAddr := 0 ;Single Period Address
, vTemp := 0
, vPtrSize := 0 ;in bytes
if (vBytesPerSample = 1)
vPtrType := "UChar"
, vPtrSize := 1
else if (vBytesPerSample = 2)
vPtrType := "Short"
, vPtrSize := 2
else
return
vSamplesPerWave := Floor(vSamplesPerSec / vFreq) ;or samples per period; e.g. 22 050 [samples/s] / 261.63 [Hz] = 84 [samples]
if (vSamplesPerWave <= 0)
return
; vEndWave := vAddr + vBatchSamples * vBytesPerSample ;[b]
vSPAddr := F_SinglePeriod(vSamplesPerWave, vBytesPerSample) ;Single Period Address
Loop
{
if (vAddr + vSamplesPerWave * vBytesPerSample > vEnd)
; || (vAddr + vSamplesPerWave * vBytesPerSample > vEndWave)
break
F_BinaryCopy(vSPAddr, vAddr, vSamplesPerWave, vPtrType, vPtrSize)
vAddr += vSamplesPerWave * vBytesPerSample
}
}
;==================================================
F_BinaryCopy(vWhat, vWhere, vHowMany, vPtrType, vPtrSize)
{
global ;assume-global mode of operation
local vTemp := 0
Loop, % vHowMany
{
vTemp := NumGet(vWhat + 0, (A_Index - 1) * vPtrSize, vPtrType)
NumPut(vTemp, vWhere + 0, (A_Index - 1) * vPtrSize, vPtrType)
}
}
;==================================================
F_SavePlay(vFreq, vDur)
{
global ;assume-global mode of operation
local vPath := A_ScriptDir . "\" SubStr(A_ScriptName, 1, -4) . "_" . vFreq . "_" . "Hz" . "_" . vDur . "_" . "ms" . ".wav"
; , vPath := A_ScriptDir . "\" . A_Year . A_MM . A_DD . "_" . A_Hour . A_Min . "_" . vType . "_" . vFreq . "_" . "Hz" . "_" . vDur . "_" . "ms" . ".wav"
if !FileExist(vPath)
{
oFile := FileOpen(vPath, "w")
oFile.RawWrite(vData, 58 + vSizeData)
oFile.Close()
}
SoundPlay, % vPath, wait
}
;==================================================
F_SinglePeriod(vSamplesPerWave, vBytesPerSample)
{
local pi := 3.141592653589793
, vPtrType := ""
, vNum1 := 0
, vNum2 := 0
, vAdjustNew := 0
, vTempNew := 0
, vNumNew := 0
, GrantCap := 0
vWholePeriod := 0 ;global variable to store samples of the whole period
GrantCap := VarSetCapacity(vWholePeriod, vSamplesPerWave * vBytesPerSample, 0) ;Enlarges a variable's holding capacityand filled with 0
if (vBytesPerSample = 1)
vPtrType := "UChar", vNum1 := 1, vNum2 := 127
else if (vBytesPerSample = 2)
vPtrType := "Short", vNum1 := 0, vNum2 := 32767
else
return
vAdjustNew := (2 * pi) / vSamplesPerWave ;size of single step in [rad]; actually I suppose the formula should be (2 * pi) / (vSamplesPerWave - 1), but then it doesn't sound anymore like original file, it is more harsh
, vTempNew := 0
Loop, % vSamplesPerWave
{
vNumNew := Round((Sin(vTempNew) + vNum1) * vNum2)
NumPut(vNumNew, vWholePeriod, (A_Index - 1) * vBytesPerSample, vPtrType) ;Do not pass a variable reference if the variable contains the target address; in that case, pass an expression such as MyVar + 0
vTempNew += vAdjustNew
GrantCap := NumGet(vWholePeriod, (A_Index - 1) * vBytesPerSample, vPtrType)
}
return &vWholePeriod
}
Thank you again for your awesome work
.
Kind regards, mslonik (
)