# HG changeset patch # User Sam # Date 1682968426 -25200 # Node ID 36e10cc04a33824611313a042b841f87c4a3149c # Parent ac75746f6b362e9bd313acf7d45848e76c5dc89d fix: bad audio buffer handling, reduce latency (unbearable on windows) diff -r ac75746f6b36 -r 36e10cc04a33 src/semicongine/audio.nim --- a/src/semicongine/audio.nim Tue May 02 01:03:03 2023 +0700 +++ b/src/semicongine/audio.nim Tue May 02 02:13:46 2023 +0700 @@ -23,6 +23,7 @@ level: Level device: NativeSoundDevice lock: Lock + buffer: SoundData proc loadSoundResource(resourcePath: string): Sound = assert false, "Not implemented yet" @@ -31,10 +32,14 @@ result = Mixer( tracks: {"": Track(level: 1'f)}.toTable, level: 1'f, - device: openSoundDevice(SAMPLERATE, BUFFERSIZE), ) result.lock.initLock() +proc setupDevice(mixer: var Mixer) = + # call this inside audio thread + mixer.buffer = newSeq[Sample](512) + mixer.device = openSoundDevice(44100, addr mixer.buffer) + proc loadSound*(mixer: var Mixer, name: string, resource: string) = assert not (name in mixer.sounds) mixer.sounds[name] = loadSoundResource(resource) @@ -128,8 +133,7 @@ proc updateSoundBuffer(mixer: var Mixer) = # mix - var buffer = newSeq[Sample](BUFFERSIZE) - for i in 0 ..< buffer.len: + for i in 0 ..< mixer.buffer.len: var currentSample = (0'i16, 0'i16) mixer.lock.withLock(): for track in mixer.tracks.mvalues: @@ -149,9 +153,9 @@ stoppedSounds.add id for id in stoppedSounds: track.playing.del(id) - buffer[i] = currentSample + mixer.buffer[i] = currentSample # send data to sound device - mixer.device.updateSoundBuffer(buffer) + mixer.device.writeSoundData() proc destroy*(mixer: var Mixer) = @@ -164,6 +168,7 @@ proc audioWorker() {.thread.} = onThreadDestruction(proc() = mixer[].lock.withLock(mixer[].destroy()); freeShared(mixer)) + mixer[].setupDevice() while true: mixer[].updateSoundBuffer() diff -r ac75746f6b36 -r 36e10cc04a33 src/semicongine/audiotypes.nim --- a/src/semicongine/audiotypes.nim Tue May 02 01:03:03 2023 +0700 +++ b/src/semicongine/audiotypes.nim Tue May 02 02:13:46 2023 +0700 @@ -5,9 +5,6 @@ # # ffmpeg -i -f s16le -ac 2 -ar 48000 -acodec pcm_s16le -const SAMPLERATE* = 44100 -const BUFFERSIZE* = 512 - type Level* = 0'f .. 1'f Sample* = (int16, int16) @@ -19,10 +16,10 @@ sin(x * 2 * Pi * f) result = ret -proc sineSoundData*(f: float, len: float): SoundData = - let dt = 1'f / float(SAMPLERATE) +proc sineSoundData*(f: float, len: float, rate: int): SoundData = + let dt = 1'f / float(rate) var sine = sinewave(f) - for i in 0 ..< int(SAMPLERATE * len): + for i in 0 ..< int(float(rate) * len): let t = dt * float(i) let value = int16(sine(t) * float(high(int16))) result.add (value, value) diff -r ac75746f6b36 -r 36e10cc04a33 src/semicongine/platform/linux/audio.nim --- a/src/semicongine/platform/linux/audio.nim Tue May 02 01:03:03 2023 +0700 +++ b/src/semicongine/platform/linux/audio.nim Tue May 02 02:13:46 2023 +0700 @@ -41,8 +41,9 @@ type NativeSoundDevice* = object handle: snd_pcm_p + buffer: ptr SoundData -proc openSoundDevice*(sampleRate: uint32, bufferSize: uint32): NativeSoundDevice = +proc openSoundDevice*(sampleRate: uint32, buffer: ptr SoundData): NativeSoundDevice = var hw_params: snd_pcm_hw_params_p = nil checkAlsaResult snd_pcm_open(addr result.handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_BLOCK) @@ -53,15 +54,15 @@ checkAlsaResult snd_pcm_hw_params_set_format(result.handle, hw_params, SND_PCM_FORMAT_S16_LE) checkAlsaResult snd_pcm_hw_params_set_rate(result.handle, hw_params, sampleRate, 0) checkAlsaResult snd_pcm_hw_params_set_channels(result.handle, hw_params, 2) - checkAlsaResult snd_pcm_hw_params_set_buffer_size(result.handle, hw_params, snd_pcm_uframes_t(bufferSize)) + checkAlsaResult snd_pcm_hw_params_set_buffer_size(result.handle, hw_params, snd_pcm_uframes_t(buffer[].len)) checkAlsaResult snd_pcm_hw_params(result.handle, hw_params) snd_pcm_hw_params_free(hw_params) + result.buffer = buffer -proc updateSoundBuffer*(soundDevice: NativeSoundDevice, buffer: var SoundData) = - var ret = snd_pcm_writei(soundDevice.handle, addr buffer[0], snd_pcm_uframes_t(buffer.len)) +proc writeSoundData*(soundDevice: NativeSoundDevice) = + var ret = snd_pcm_writei(soundDevice.handle, addr soundDevice.buffer[][0], snd_pcm_uframes_t(soundDevice.buffer[].len)) if ret < 0: checkAlsaResult snd_pcm_recover(soundDevice.handle, cint(ret), 0) - proc closeSoundDevice*(soundDevice: NativeSoundDevice) = discard snd_pcm_close(soundDevice.handle) diff -r ac75746f6b36 -r 36e10cc04a33 src/semicongine/platform/windows/audio.nim --- a/src/semicongine/platform/windows/audio.nim Tue May 02 01:03:03 2023 +0700 +++ b/src/semicongine/platform/windows/audio.nim Tue May 02 02:13:46 2023 +0700 @@ -11,8 +11,9 @@ type NativeSoundDevice* = object handle: HWAVEOUT + buffer: WAVEHDR -proc openSoundDevice*(sampleRate: uint32, bufferSize: uint32): NativeSoundDevice = +proc openSoundDevice*(sampleRate: uint32, buffer: ptr SoundData): NativeSoundDevice = var format = WAVEFORMATEX( wFormatTag: WAVE_FORMAT_PCM, nChannels: 2, @@ -24,11 +25,9 @@ ) checkWinMMResult waveOutOpen(addr result.handle, WAVE_MAPPER, addr format, DWORD_PTR(0), DWORD_PTR(0), CALLBACK_NULL) - -proc updateSoundBuffer*(soundDevice: NativeSoundDevice, buffer: var SoundData) = - var data = WAVEHDR( - lpData: cast[cstring](addr buffer[0]), - dwBufferLength: DWORD(buffer.len * sizeof(Sample)), + result.buffer = WAVEHDR( + lpData: cast[cstring](addr buffer[][0]), + dwBufferLength: DWORD(buffer[].len * sizeof(Sample)), dwBytesRecorded: 0, dwUser: DWORD_PTR(0), dwFlags: 0, @@ -36,11 +35,13 @@ lpNext: nil, reserved: DWORD_PTR(0) ) - checkWinMMResult waveOutPrepareHeader(soundDevice.handle, addr data, UINT(sizeof(WAVEHDR))) - checkWinMMResult waveOutWrite(soundDevice.handle, addr data, UINT(sizeof(WAVEHDR))) - while (data.dwFlags and WHDR_DONE) != 1: + checkWinMMResult waveOutPrepareHeader(result.handle, addr result.buffer, UINT(sizeof(WAVEHDR))) + +proc writeSoundData*(soundDevice: var NativeSoundDevice) = + checkWinMMResult waveOutWrite(soundDevice.handle, addr soundDevice.buffer, UINT(sizeof(WAVEHDR))) + while (soundDevice.buffer.dwFlags and WHDR_DONE) != 1: discard - checkWinMMResult waveOutUnprepareHeader(soundDevice.handle, addr data, UINT(sizeof(WAVEHDR))) -proc closeSoundDevice*(soundDevice: NativeSoundDevice) = +proc closeSoundDevice*(soundDevice: var NativeSoundDevice) = + checkWinMMResult waveOutUnprepareHeader(soundDevice.handle, addr soundDevice.buffer, UINT(sizeof(WAVEHDR))) waveOutClose(soundDevice.handle) diff -r ac75746f6b36 -r 36e10cc04a33 tests/test_audio.nim --- a/tests/test_audio.nim Tue May 02 01:03:03 2023 +0700 +++ b/tests/test_audio.nim Tue May 02 02:13:46 2023 +0700 @@ -6,8 +6,8 @@ proc test1() = - mixer[].addSound("test1", newSound(sineSoundData(1000, 2))) - mixer[].addSound("test2", newSound(sineSoundData(500, 2))) + mixer[].addSound("test1", newSound(sineSoundData(1000, 2, 44100))) + mixer[].addSound("test2", newSound(sineSoundData(500, 2, 44100))) let s1 = mixer[].play("test1", loop=true) @@ -28,19 +28,19 @@ proc test2() = let # notes - c = sineSoundData(261.6256, 0.5) - d = sineSoundData(293.6648, 0.5) - e = sineSoundData(329.6276, 0.5) - f = sineSoundData(349.2282, 0.5) - g = sineSoundData(391.9954, 0.5) - a = sineSoundData(440.0000, 0.5) - b = sineSoundData(493.8833, 0.5) - bb = sineSoundData(466.1638, 0.5) - c2 = sineSoundData(523.2511, 0.5) - d2 = sineSoundData(587.3295, 0.5) - bbShort = sineSoundData(466.1638, 0.25) - c2Short = sineSoundData(523.2511, 0.25) - d2Short = sineSoundData(587.3295, 0.25) + c = sineSoundData(261.6256, 0.5, 44100) + d = sineSoundData(293.6648, 0.5, 44100) + e = sineSoundData(329.6276, 0.5, 44100) + f = sineSoundData(349.2282, 0.5, 44100) + g = sineSoundData(391.9954, 0.5, 44100) + a = sineSoundData(440.0000, 0.5, 44100) + b = sineSoundData(493.8833, 0.5, 44100) + bb = sineSoundData(466.1638, 0.5, 44100) + c2 = sineSoundData(523.2511, 0.5, 44100) + d2 = sineSoundData(587.3295, 0.5, 44100) + bbShort = sineSoundData(466.1638, 0.25, 44100) + c2Short = sineSoundData(523.2511, 0.25, 44100) + d2Short = sineSoundData(587.3295, 0.25, 44100) # song frerejaquesData = concat( @@ -70,7 +70,7 @@ song.add sample mixer[].addSound("pianosong", newSound(song)) - mixer[].addSound("ping", newSound(sineSoundData(500, 0.05))) + mixer[].addSound("ping", newSound(sineSoundData(500, 0.05, 44100))) mixer[].addTrack("effects") discard mixer[].play("pianosong")