diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 296af36..e4607db 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -44,7 +44,6 @@ jobs: mingw-w64-x86_64-speex mingw-w64-x86_64-cmake mingw-w64-x86_64-pkg-config - # Note: Removed mingw-w64-x86_64-portaudio since we're using our own - name: Verify local PortAudio library (Windows) if: runner.os == 'Windows' @@ -199,6 +198,12 @@ jobs: sudo make install shell: bash + - name: Upload Artifacts + uses: actions/upload-artifact@v4 + with: + name: PyTaiko-${{ runner.os }}-${{ runner.arch }} + path: release/ + - name: Install uv uses: astral-sh/setup-uv@v4 @@ -264,12 +269,6 @@ jobs: fi shell: bash - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: PyTaiko-${{ runner.os }}-${{ runner.arch }} - path: release/ - - name: Upload Release uses: softprops/action-gh-release@v2 if: github.ref == 'refs/heads/main' && github.event_name == 'push' diff --git a/config.toml b/config.toml index dd030ad..c4eb34a 100644 --- a/config.toml +++ b/config.toml @@ -32,9 +32,18 @@ right_don = [17] right_kat = [12] [audio] +# device_type: 0 = default, check console output from list_host_apis() for other options device_type = 0 +# sample_rate: -1 = use device default (usually 44100 or 48000) sample_rate = -1 -buffer_size = 0 +# buffer_size: Size in samples per audio buffer +# - 0 = let driver choose (may result in very small buffers with ASIO, typically 64) +# - ASIO users: Use 128, 256, 512, 1024, or 2048 (must be power of 2) +# - ASIO drivers have strict minimum buffer sizes (often 64-256 samples) +# - If you request a buffer size below the driver's minimum, it will use the minimum instead +# - Lower = less latency but higher CPU usage; Higher = more latency but more stable +# - Recommended: 512 for most users, 256 for low latency, 1024+ for stability +buffer_size = 512 exclusive = false [volume] diff --git a/libs/audio/audio.c b/libs/audio/audio.c index d5a4982..712bfd4 100644 --- a/libs/audio/audio.c +++ b/libs/audio/audio.c @@ -6,6 +6,9 @@ // stream to portaudio #include "portaudio.h" +#ifdef _WIN32 +#include "pa_asio.h" +#endif #include #include #include @@ -324,6 +327,44 @@ void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned lon AUDIO.System.outputParameters.hostApiSpecificStreamInfo = NULL; AUDIO.System.sampleRate = sample_rate; + const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device); + const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi); + +#ifdef _WIN32 + if (hostApiInfo->type == paASIO) { + long minSize, maxSize, preferredSize, granularity; + PaError asioErr = PaAsio_GetAvailableBufferSizes(AUDIO.System.outputParameters.device, + &minSize, &maxSize, &preferredSize, &granularity); + + if (asioErr == paNoError) { + TRACELOG(LOG_INFO, "AUDIO: ASIO buffer size constraints:"); + TRACELOG(LOG_INFO, " > Minimum: %ld samples", minSize); + TRACELOG(LOG_INFO, " > Maximum: %ld samples", maxSize); + TRACELOG(LOG_INFO, " > Preferred: %ld samples", preferredSize); + if (granularity == -1) { + TRACELOG(LOG_INFO, " > Granularity: Powers of 2 only"); + } else if (granularity == 0) { + TRACELOG(LOG_INFO, " > Granularity: Fixed size (min=max=preferred)"); + } else { + TRACELOG(LOG_INFO, " > Granularity: %ld samples", granularity); + } + + // Warn if requested buffer size is out of range + if (buffer_size > 0 && buffer_size < minSize) { + TRACELOG(LOG_WARNING, "AUDIO: Requested buffer size (%lu) is below ASIO minimum (%ld)", buffer_size, minSize); + TRACELOG(LOG_WARNING, "AUDIO: Driver will use %ld samples instead", minSize); + } else if (buffer_size > maxSize) { + TRACELOG(LOG_WARNING, "AUDIO: Requested buffer size (%lu) exceeds ASIO maximum (%ld)", buffer_size, maxSize); + TRACELOG(LOG_WARNING, "AUDIO: Driver will use %ld samples instead", maxSize); + } else if (buffer_size == 0) { + TRACELOG(LOG_INFO, "AUDIO: Buffer size not specified, driver will choose (likely %ld samples)", preferredSize); + } + } else { + TRACELOG(LOG_WARNING, "AUDIO: Failed to query ASIO buffer sizes: %s", Pa_GetErrorText(asioErr)); + } + } +#endif + err = Pa_OpenStream(&AUDIO.System.stream, NULL, // No input &AUDIO.System.outputParameters, // Output parameters @@ -351,17 +392,24 @@ void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned lon AUDIO.System.isReady = true; - const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device); - const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi); - TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); TRACELOG(LOG_INFO, " > Backend: PortAudio | %s", hostApiInfo->name); TRACELOG(LOG_INFO, " > Device: %s", deviceInfo->name); TRACELOG(LOG_INFO, " > Format: %s", "Float32"); + const PaStreamInfo *streamInfo = Pa_GetStreamInfo(AUDIO.System.stream); TRACELOG(LOG_INFO, " > Channels: %d", AUDIO_DEVICE_CHANNELS); TRACELOG(LOG_INFO, " > Sample rate: %f", AUDIO.System.sampleRate); - TRACELOG(LOG_INFO, " > Buffer size: %lu", buffer_size); - TRACELOG(LOG_INFO, " > Latency: %f ms", Pa_GetStreamInfo(AUDIO.System.stream)->outputLatency * 1000.0); + TRACELOG(LOG_INFO, " > Buffer size: %lu (requested)", buffer_size); + TRACELOG(LOG_INFO, " > Latency: %f ms", streamInfo->outputLatency * 1000.0); +#ifdef _WIN32 + if (hostApiInfo->type == paASIO) { + unsigned long estimatedBufferSize = (unsigned long)(streamInfo->outputLatency * AUDIO.System.sampleRate); + TRACELOG(LOG_INFO, " > Estimated actual buffer: ~%lu samples (based on latency)", estimatedBufferSize); + if (buffer_size > 0 && estimatedBufferSize != buffer_size) { + TRACELOG(LOG_INFO, " > Note: ASIO driver adjusted buffer size to meet its constraints"); + } + } +#endif } void close_audio_device(void) diff --git a/scenes/devtest.py b/scenes/devtest.py index f969364..39f312e 100644 --- a/scenes/devtest.py +++ b/scenes/devtest.py @@ -16,7 +16,7 @@ class DevScreen: if not self.screen_init: self.screen_init = True tex.load_screen_textures('game') - self.obj = JudgeCounter(10) + self.obj = JudgeCounter() def on_screen_end(self, next_screen: str): self.screen_init = False @@ -24,11 +24,11 @@ class DevScreen: def update(self): self.on_screen_start() - self.obj.update() + self.obj.update(0, 0, 0, 0) if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER): return self.on_screen_end('GAME') if ray.is_key_pressed(ray.KeyboardKey.KEY_SPACE): - self.obj = JudgeCounter(500) + self.obj = JudgeCounter() def draw(self): ray.draw_rectangle(0, 0, 1280, 720, ray.GREEN)