mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 19:50:12 +01:00
fix audio engine, add difficulty sorting kind of
This commit is contained in:
@@ -6,9 +6,9 @@ from threading import Lock, Thread
|
||||
from typing import Optional
|
||||
|
||||
import soundfile as sf
|
||||
from numpy import abs as np_abs
|
||||
from numpy import (
|
||||
arange,
|
||||
clip,
|
||||
column_stack,
|
||||
float32,
|
||||
frombuffer,
|
||||
@@ -16,13 +16,12 @@ from numpy import (
|
||||
int32,
|
||||
interp,
|
||||
mean,
|
||||
multiply,
|
||||
ndarray,
|
||||
ones,
|
||||
sqrt,
|
||||
uint8,
|
||||
zeros,
|
||||
)
|
||||
from numpy import max as np_max
|
||||
|
||||
os.environ["SD_ENABLE_ASIO"] = "1"
|
||||
import sounddevice as sd
|
||||
@@ -514,7 +513,7 @@ class AudioEngine:
|
||||
self.running = False
|
||||
self.sound_queue: queue.Queue[str] = queue.Queue()
|
||||
self.music_queue = queue.Queue()
|
||||
self.master_volume = 1.0
|
||||
self.master_volume = 0.70
|
||||
self.output_channels = 2 # Default to stereo
|
||||
self.audio_device_ready = False
|
||||
|
||||
@@ -523,6 +522,10 @@ class AudioEngine:
|
||||
self.update_thread_running = False
|
||||
self.type = type
|
||||
|
||||
self._output_buffer = None
|
||||
self._channel_conversion_buffer = None
|
||||
self._expected_frames = None
|
||||
|
||||
def _initialize_api(self) -> bool:
|
||||
"""Set up API device"""
|
||||
# Find API and use its default device
|
||||
@@ -572,30 +575,25 @@ class AudioEngine:
|
||||
|
||||
def _audio_callback(self, outdata: ndarray, frames: int, time: int, status: str) -> None:
|
||||
"""callback function for the sounddevice stream"""
|
||||
|
||||
if self._output_buffer is None:
|
||||
raise Exception("output buffer was not allocated")
|
||||
if status:
|
||||
print(f"Status: {status}")
|
||||
|
||||
self._process_sound_queue()
|
||||
self._process_music_queue()
|
||||
|
||||
# Pre-allocate output buffer (reuse if possible)
|
||||
if not hasattr(self, '_output_buffer') or self._output_buffer.shape != (frames, self.output_channels):
|
||||
self._output_buffer = zeros((frames, self.output_channels), dtype=float32)
|
||||
else:
|
||||
self._output_buffer.fill(0.0) # Clear previous data
|
||||
self._output_buffer.fill(0.0)
|
||||
|
||||
self._mix_sounds(self._output_buffer, frames)
|
||||
|
||||
self._mix_music(self._output_buffer, frames)
|
||||
|
||||
# Apply master volume in-place
|
||||
if self.master_volume != 1.0:
|
||||
self._output_buffer *= self.master_volume
|
||||
multiply(self._output_buffer, self.master_volume, out=self._output_buffer)
|
||||
|
||||
# Apply limiter only if needed
|
||||
max_val = np_max(np_abs(self._output_buffer))
|
||||
if max_val > 1.0:
|
||||
self._output_buffer /= max_val
|
||||
clip(self._output_buffer, -0.95, 0.95, out=self._output_buffer)
|
||||
|
||||
outdata[:] = self._output_buffer
|
||||
|
||||
@@ -668,19 +666,36 @@ class AudioEngine:
|
||||
output += music_data
|
||||
|
||||
def _convert_channels(self, data: ndarray, input_channels: int) -> ndarray:
|
||||
"""channel conversion with caching"""
|
||||
"""Channel conversion using single pre-allocated buffer"""
|
||||
if data.ndim == 1:
|
||||
data = data.reshape(-1, 1)
|
||||
input_channels = 1
|
||||
|
||||
frames = data.shape[0]
|
||||
|
||||
if input_channels == self.output_channels:
|
||||
return data
|
||||
|
||||
if self._channel_conversion_buffer is None:
|
||||
raise Exception("channel conversion buffer was not allocated")
|
||||
|
||||
self._channel_conversion_buffer[:frames, :self.output_channels].fill(0.0)
|
||||
|
||||
if input_channels == 1 and self.output_channels > 1:
|
||||
return data[:, None] * ones((1, self.output_channels), dtype=float32)
|
||||
# Mono to stereo/multi: broadcast to all channels
|
||||
for ch in range(self.output_channels):
|
||||
self._channel_conversion_buffer[:frames, ch] = data[:frames, 0]
|
||||
|
||||
elif input_channels > self.output_channels:
|
||||
if self.output_channels == 1:
|
||||
return mean(data, axis=1, keepdims=True)
|
||||
# Multi to mono: average channels
|
||||
self._channel_conversion_buffer[:frames, 0] = mean(data[:frames, :input_channels], axis=1)
|
||||
else:
|
||||
return data[:, :self.output_channels]
|
||||
# Multi to fewer channels: take first N channels
|
||||
self._channel_conversion_buffer[:frames, :self.output_channels] = data[:frames, :self.output_channels]
|
||||
|
||||
return data
|
||||
# Return a view of the converted data
|
||||
return self._channel_conversion_buffer[:frames, :self.output_channels]
|
||||
|
||||
def _start_update_thread(self) -> None:
|
||||
"""Start a thread to update music streams"""
|
||||
@@ -710,6 +725,9 @@ class AudioEngine:
|
||||
try:
|
||||
self._initialize_api()
|
||||
|
||||
self._expected_frames = self.buffer_size
|
||||
self._output_buffer = zeros((self._expected_frames, self.output_channels), dtype=float32)
|
||||
self._channel_conversion_buffer = zeros((self._expected_frames, max(8, self.output_channels)), dtype=float32)
|
||||
# Set up and start the stream
|
||||
extra_settings = None
|
||||
buffer_size = self.buffer_size
|
||||
@@ -752,6 +770,8 @@ class AudioEngine:
|
||||
self.music_streams = {}
|
||||
self.sound_queue = queue.Queue()
|
||||
self.music_queue = queue.Queue()
|
||||
self._output_buffer = None
|
||||
self._channel_conversion_buffer = None
|
||||
print("Audio device closed")
|
||||
return
|
||||
|
||||
|
||||
@@ -62,29 +62,22 @@ def build_song_hashes(output_dir=Path("cache")):
|
||||
|
||||
files_to_process = []
|
||||
|
||||
# O(n) pass to identify which files need processing
|
||||
for tja_path in all_tja_files:
|
||||
tja_path_str = str(tja_path)
|
||||
current_modified = tja_path.stat().st_mtime
|
||||
|
||||
# Skip files that haven't been modified since last run
|
||||
if current_modified <= saved_timestamp:
|
||||
# File hasn't changed, just restore to global_data if we have it
|
||||
current_hash = path_to_hash.get(tja_path_str)
|
||||
if current_hash is not None:
|
||||
global_data.song_paths[tja_path] = current_hash
|
||||
continue
|
||||
|
||||
# O(1) lookup instead of nested loops
|
||||
current_hash = path_to_hash.get(tja_path_str)
|
||||
|
||||
if current_hash is None:
|
||||
# New file (modified after saved_timestamp)
|
||||
files_to_process.append(tja_path)
|
||||
else:
|
||||
# File was modified after saved_timestamp, need to reprocess
|
||||
files_to_process.append(tja_path)
|
||||
# Clean up old hash
|
||||
if current_hash in song_hashes:
|
||||
del song_hashes[current_hash]
|
||||
del path_to_hash[tja_path_str]
|
||||
|
||||
@@ -208,6 +208,7 @@ class SessionData:
|
||||
result_max_combo: int = 0
|
||||
result_total_drumroll: int = 0
|
||||
result_gauge_length: int = 0
|
||||
prev_score: int = 0
|
||||
|
||||
session_data = SessionData()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user