async/chained SoundBeep function

Post your working scripts, libraries and tools for AHK v1.1 and older
r_glass
Posts: 4
Joined: 13 Oct 2021, 16:47

async/chained SoundBeep function

Post by r_glass » 25 Jan 2022, 09:41

This is really just a wrapper for PlaySound combined with viewtopic.php?t=34584
Pass frequency and duration pairs to SoundBeep() or SoundBeepAsync()
It creates a wav file matching the beep pairs' pattern, saves this in %TEMP% (so as not to unnecessarily duplicate the process of creating it with other scripts or across reloads), then loads that into memory and caches it for use with PlaySound
If given a sparse array it fills in the gaps with SoundBeep's defaults (523/150)

script

Code: Select all

SoundBeep(beeps*) {
  return SoundBeepNew(beeps)
}

SoundBeepAsync(beeps*) {
  return SoundBeepNew(beeps, 1)
}

SoundBeepNew(beeps, async=0, frequency=523, duration=150) {
  static cached := []
  beep_arr := []
  Loop, % if2(!beeps.Count(), beeps.maxIndex())
    if (Mod(A_Index, 2))
      beep_arr.Push([if2(beeps[A_Index],frequency), if2(beeps[A_Index+1],duration)])
  if (cached.HasKey(file := BeepFile(beep_arr)))
    sound := cached[file]
  else
    sound := cached[file] := Sound(BeepToFile(beep_arr, file))
  return PlaySound(sound, async)
}

BeepFile(beeps) {
  return arrJoin([A_Temp, A_ThisFunc, arrJoin(beeps, ",") ".wav"], "\")
}

BeepToFile(beeps, file) { ;; https://www.autohotkey.com/boards/viewtopic.php?t=34584
  static vSamplesPerSec  := 44100
  static vBitsPerSample  := 8
  static vChannels       := 1
  static vBytesPerSample := vBitsPerSample/8
  if (!FileExist(file))
  {
    vDuration := 0
    for each, beep in beeps
      vDuration += beep.2
    vDuration := vDuration/1000
    vSamples := vSamplesPerSec*vDuration
    vSizeData := vChannels * vSamples * vBytesPerSample
    VarSetCapacity(vData, 58+vSizeData, 0)
    StrPut("RIFF", &vData+0, "CP0")
    NumPut(50 + vSizeData, vData, 4)
    StrPut("WAVE", &vData+8, "CP0")
    StrPut("fmt ", &vData+12, "CP0")
    NumPut(18, vData, 16)
    NumPut(1, vData, 20, "UShort")
    NumPut(vChannels, vData, 22, "UShort")
    NumPut(vSamplesPerSec, vData, 24)
    NumPut(vChannels*vSamplesPerSec*vBytesPerSample, vData, 28)
    NumPut(vChannels*vBytesPerSample, vData, 32, "UShort")
    NumPut(vBitsPerSample, vData, 34, "UShort")
    NumPut(0, vData, 36, "UShort")
    StrPut("fact", &vData+38, "CP0")
    NumPut(4, vData, 42)
    NumPut(vSamples, vData, 46)
    StrPut("data", &vData+50, "CP0")
    NumPut(vSamples*vChannels*vBytesPerSample, vData, 54)
    vAddr := &vData + 58
    vEnd := &vData + 58 + vSizeData
    for each, beep in beeps
    {
      vFreq := beep.1
      vDuration := beep.2
      vSamplesPerDuration := Floor((vSamplesPerSec*vDuration)/1000)
      vSamplesPerWave := Floor(vSamplesPerSec/vFreq)
      if (vSamplesPerWave <= 0)
        break
      vEndWave := vAddr + vSamplesPerDuration*vBytesPerSample
      Loop
      {
        if (vAddr + vSamplesPerWave*vBytesPerSample > vEnd)
        || (vAddr + vSamplesPerWave*vBytesPerSample > vEndWave)
          break
        JEE_WaveWriteNote(vAddr, vSamplesPerWave, vBytesPerSample)
        vAddr += vSamplesPerWave*vBytesPerSample
      }
    }
    FileCreateDir, % Dir(file)
    oFile := FileOpen(file, "w", "CP1252")
    oFile.RawWrite(vData, 58+vSizeData)
    oFile.Close()
  }
  return file
}

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)
    vList .= vNum ","
    vTemp += vAdjust
    NumPut(vNum, vAddr+0, (A_Index-1)*vBytesPerSample, vPtrType)
  }
}

Sound(file) {
  static sounds := []
  static sounds_len := 0
  global
  if (sounds.HasKey(file))
    return sounds[file]
  else
  {
    local sound_var := arrJoin([A_ThisFunc, sounds_len += 1, A_TickCount], "_")
    local oFile := FileOpen(file, "r", "CP1252")
    oFile.RawRead(%sound_var%, FileGetSize(file))
    oFile.Close()
    return sounds[file] := &%sound_var%
  }
}

PlaySound(sound, async=0) { ;; https://www.autohotkey.com/board/topic/57631-crazy-scripting-resource-only-dll-for-dummies-36l-v07/page-4#entry609282
  static SND_SYNC        := 0x00000000 ;; play synchronously (default)
  static SND_ASYNC       := 0x00000001 ;; play asynchronously
  static SND_NODEFAULT   := 0x00000002 ;; silence (!default) if sound not found
  static SND_MEMORY      := 0x00000004 ;; pszSound points to a memory file
  static SND_LOOP        := 0x00000008 ;; loop the sound until next sndPlaySound
  static SND_NOSTOP      := 0x00000010 ;; don't stop any currently playing sound
  static SND_NOWAIT      := 0x00002000 ;; don't wait if the driver is busy
  static SND_ALIAS       := 0x00010000 ;; name is a registry alias
  static SND_ALIAS_ID    := 0x00110000 ;; alias is a predefined ID
  static SND_FILENAME    := 0x00020000 ;; name is file name
  static SND_RESOURCE    := 0x00040004 ;; name is resource name or atom
  static SND_PURGE       := 0x00000040 ;; purge non-static events for task
  static SND_APPLICATION := 0x00000080 ;; look for application specific association
  static SND_SENTRY      := 0x00080000 ;; Generate a SoundSentry event with this sound
  static SND_RING        := 0x00100000 ;; Treat this as a "ring" from a communications app - don't duck me
  static SND_SYSTEM      := 0x00200000 ;; Treat this as a system sound
  return DllCall("winmm.dll\PlaySound" (A_IsUnicode ? "W" : "A"), "UInt",sound, "UInt",0 , "UInt",SND_NODEFAULT | SND_MEMORY | (async ? SND_ASYNC : SND_SYNC))
}

if2(a, b="") {
  return a ? a : b
}

arrJoin(arr, del="") {
  out := ""
  for k, v in arr
    out .= del (isObject(v) ? arrJoin(v, del) : v)
  return SubStr(out, 1+StrLen(del))
}

Dir(Path, r=1) {
  Loop, %r%
    SplitPath, Path,, Path
  return Path
}

FileGetSize(Filename="", Units="") {
  FileGetSize, v, %Filename%, %Units%
  Return, v
}
examples

Code: Select all

Esc::ExitApp

1::SoundBeep(1000,150, 500,150, 250,150)
;; ==
2::SoundBeep(1000,   , 500,   , 250)

3::SoundBeep(523)
;; ==
4::SoundBeep()

;; doesn't stop execution, but can be interrupted by another beep generated by its own script
q::SoundBeepAsync(250,250, 500,250, 1000,250)
w::SoundBeepAsync(2500,300, 5000)
e::SoundBeepAsync(,, 5000,, 250,, 400)

Return to “Scripts and Functions (v1)”