changeset 178:88198b0afacc

add: windows audio, not super smooth, maybe replace with directsound?
author Sam <sam@basx.dev>
date Tue, 02 May 2023 10:37:11 -0700
parents 42e99cb20da2
children fc85ed0abfcf
files src/semicongine/audio.nim src/semicongine/platform/windows/audio.nim tests/test_audio.nim
diffstat 3 files changed, 45 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/src/semicongine/audio.nim	Tue May 02 02:28:47 2023 -0700
+++ b/src/semicongine/audio.nim	Tue May 02 10:37:11 2023 -0700
@@ -1,11 +1,19 @@
 import std/tables
 import std/locks
 
+when defined(windows): # used for setting audio thread priority on windows
+  import winim
+when defined(linux):
+  import std/posix
+
 import ./audiotypes
 import ./platform/audio
 
 export audiotypes
 
+const NBUFFERS = 4
+const BUFFERSAMPLECOUNT = 2048
+
 type
   Playback = object
     sound: Sound
@@ -23,7 +31,8 @@
     level: Level
     device: NativeSoundDevice
     lock: Lock
-    buffer: SoundData
+    buffers: seq[SoundData]
+    currentBuffer: int
 
 proc loadSoundResource(resourcePath: string): Sound =
   assert false, "Not implemented yet"
@@ -37,8 +46,12 @@
 
 proc setupDevice(mixer: var Mixer) =
   # call this inside audio thread
-  mixer.buffer = newSeq[Sample](512)
-  mixer.device = openSoundDevice(44100, addr mixer.buffer)
+  var bufferaddresses: seq[ptr SoundData]
+  for i in 0 ..< NBUFFERS:
+    mixer.buffers.add newSeq[Sample](BUFFERSAMPLECOUNT)
+  for i in 0 ..< mixer.buffers.len:
+    bufferaddresses.add (addr mixer.buffers[i])
+  mixer.device = openSoundDevice(44100, bufferaddresses)
 
 proc loadSound*(mixer: var Mixer, name: string, resource: string) =
   assert not (name in mixer.sounds)
@@ -133,7 +146,7 @@
 
 proc updateSoundBuffer(mixer: var Mixer) =
   # mix
-  for i in 0 ..< mixer.buffer.len:
+  for i in 0 ..< mixer.buffers[mixer.currentBuffer].len:
     var currentSample = (0'i16, 0'i16)
     mixer.lock.withLock():
       for track in mixer.tracks.mvalues:
@@ -153,9 +166,11 @@
               stoppedSounds.add id
         for id in stoppedSounds:
           track.playing.del(id)
-      mixer.buffer[i] = currentSample
+      mixer.buffers[mixer.currentBuffer][i] = currentSample
   # send data to sound device
-  mixer.device.writeSoundData()
+  # mixer.device.writeSoundData((mixer.currentBuffer - 1) %% mixer.buffers.len)
+  mixer.device.writeSoundData(mixer.currentBuffer)
+  mixer.currentBuffer = (mixer.currentBuffer + 1) mod mixer.buffers.len
 
 
 proc destroy*(mixer: var Mixer) =
@@ -167,6 +182,8 @@
 var mixer* = createShared(Mixer)
 
 proc audioWorker() {.thread.} =
+  when defined(linux):
+    nice(-20)
   onThreadDestruction(proc() = mixer[].lock.withLock(mixer[].destroy()); freeShared(mixer))
   mixer[].setupDevice()
   while true:
@@ -176,3 +193,5 @@
   mixer[] = initMixer()
   var audiothread: Thread[void]
   audiothread.createThread(audioWorker)
+  when defined(window):
+    SetThreadPriority(audiothread.handle(), THREAD_PRIORITY_TIME_CRITICAL)
--- a/src/semicongine/platform/windows/audio.nim	Tue May 02 02:28:47 2023 -0700
+++ b/src/semicongine/platform/windows/audio.nim	Tue May 02 10:37:11 2023 -0700
@@ -11,9 +11,9 @@
 type
   NativeSoundDevice* = object
     handle: HWAVEOUT
-    buffer: WAVEHDR
+    buffers: seq[WAVEHDR]
  
-proc openSoundDevice*(sampleRate: uint32, buffer: ptr SoundData): NativeSoundDevice =
+proc openSoundDevice*(sampleRate: uint32, buffers: seq[ptr SoundData]): NativeSoundDevice =
   var format = WAVEFORMATEX(
     wFormatTag: WAVE_FORMAT_PCM,
     nChannels: 2,
@@ -23,26 +23,24 @@
     wBitsPerSample: 16,
     cbSize: 0,
   )
+  checkWinMMResult waveOutOpen(addr result.handle, WAVE_MAPPER, addr format, DWORD_PTR(0), DWORD_PTR(0), CALLBACK_NULL)
 
-  checkWinMMResult waveOutOpen(addr result.handle, WAVE_MAPPER, addr format, DWORD_PTR(0), DWORD_PTR(0), CALLBACK_NULL)
-  result.buffer = WAVEHDR(
-    lpData: cast[cstring](addr buffer[][0]),
-    dwBufferLength: DWORD(buffer[].len * sizeof(Sample)),
-    dwBytesRecorded: 0,
-    dwUser: DWORD_PTR(0),
-    dwFlags: 0,
-    dwLoops: 1,
-    lpNext: nil,
-    reserved: DWORD_PTR(0)
-  )
-  checkWinMMResult waveOutPrepareHeader(result.handle, addr result.buffer, UINT(sizeof(WAVEHDR)))
+  for i in 0 ..< buffers.len:
+    result.buffers.add WAVEHDR(
+      lpData: cast[cstring](addr buffers[i][][0]),
+      dwBufferLength: DWORD(buffers[i][].len * sizeof(Sample)),
+      dwLoops: 1,
+    )
+  for i in 0 ..< result.buffers.len:
+    checkWinMMResult waveOutPrepareHeader(result.handle, addr result.buffers[i], UINT(sizeof(WAVEHDR)))
+    checkWinMMResult waveOutWrite(result.handle, addr result.buffers[i], UINT(sizeof(WAVEHDR)))
   
-# add double buffering: https://stackoverflow.com/questions/49605552/double-buffered-waveoutwrite-stuttering-like-hell
-proc writeSoundData*(soundDevice: var NativeSoundDevice) =
-  checkWinMMResult waveOutWrite(soundDevice.handle, addr soundDevice.buffer, UINT(sizeof(WAVEHDR)))
-  while (soundDevice.buffer.dwFlags and WHDR_DONE) != 1:
+proc writeSoundData*(soundDevice: var NativeSoundDevice, buffer: int) =
+  while (soundDevice.buffers[buffer].dwFlags and WHDR_DONE) == 0:
     discard
+  checkWinMMResult waveOutWrite(soundDevice.handle, addr soundDevice.buffers[buffer], UINT(sizeof(WAVEHDR)))
 
 proc closeSoundDevice*(soundDevice: var NativeSoundDevice) =
-  discard waveOutUnprepareHeader(soundDevice.handle, addr soundDevice.buffer, UINT(sizeof(WAVEHDR)))
+  for i in 0 ..< soundDevice.buffers.len:
+    discard waveOutUnprepareHeader(soundDevice.handle, addr soundDevice.buffers[i], UINT(sizeof(WAVEHDR)))
   waveOutClose(soundDevice.handle)
--- a/tests/test_audio.nim	Tue May 02 02:28:47 2023 -0700
+++ b/tests/test_audio.nim	Tue May 02 10:37:11 2023 -0700
@@ -85,5 +85,6 @@
   mixer[].stop()
   test2()
   mixer[].stop()
-  test3()
-  mixer[].stop()
+  while true:
+    test3()
+    mixer[].stop()