add better WASAPI

This commit is contained in:
Yonokid
2025-05-13 16:23:39 -04:00
parent 965b9bea37
commit cca980716f
4 changed files with 59 additions and 50 deletions

View File

@@ -101,8 +101,8 @@ def main():
ray.toggle_fullscreen() ray.toggle_fullscreen()
next_screen = screen.update() next_screen = screen.update()
screen.draw()
ray.clear_background(ray.BLACK) ray.clear_background(ray.BLACK)
screen.draw()
if next_screen is not None: if next_screen is not None:
current_screen = next_screen current_screen = next_screen

View File

@@ -5,7 +5,6 @@ import time
import wave import wave
from threading import Lock, Thread from threading import Lock, Thread
import pyray as ray
from numpy import ( from numpy import (
abs as np_abs, abs as np_abs,
) )
@@ -413,10 +412,10 @@ class Music:
except Exception: except Exception:
raise Exception("unable to close music stream") raise Exception("unable to close music stream")
class ASIOEngine: class AudioEngine:
def __init__(self): def __init__(self, type: str):
self.target_sample_rate = 48000 self.target_sample_rate = 48000
self.buffer_size = get_config()["audio"]["asio_buffer"] self.buffer_size = get_config()["audio"]["buffer_size"]
self.sounds = {} self.sounds = {}
self.music_streams = {} self.music_streams = {}
self.stream = None self.stream = None
@@ -431,6 +430,7 @@ class ASIOEngine:
# Threading for music stream updates # Threading for music stream updates
self.update_thread = None self.update_thread = None
self.update_thread_running = False self.update_thread_running = False
self.type = type
def _initialize_asio(self): def _initialize_asio(self):
"""Set up ASIO device""" """Set up ASIO device"""
@@ -438,11 +438,12 @@ class ASIOEngine:
hostapis = sd.query_hostapis() hostapis = sd.query_hostapis()
asio_api_index = -1 asio_api_index = -1
for i, api in enumerate(hostapis): for i, api in enumerate(hostapis):
if isinstance(api, dict) and 'name' in api and api['name'] == 'ASIO': if isinstance(api, dict) and 'name' in api and api['name'] == self.type:
asio_api_index = i asio_api_index = i
break break
if asio_api_index is not None: print(hostapis)
if isinstance(hostapis, tuple):
asio_api = hostapis[asio_api_index] asio_api = hostapis[asio_api_index]
if isinstance(asio_api, dict) and 'default_output_device' in asio_api: if isinstance(asio_api, dict) and 'default_output_device' in asio_api:
default_asio_device = asio_api['default_output_device'] default_asio_device = asio_api['default_output_device']
@@ -461,9 +462,7 @@ class ASIOEngine:
self.output_channels = 2 self.output_channels = 2
return True return True
else: else:
print("No default ASIO device found, using system default.") print("ASIO API not found, using system default device.")
else:
print("ASIO API not found, using system default device.")
# If we get here, use default system device # If we get here, use default system device
self.device_id = None self.device_id = None
@@ -740,21 +739,5 @@ class ASIOEngine:
return self.music_streams[music].get_time_played() return self.music_streams[music].get_time_played()
raise ValueError(f"Music stream {music} not initialized") raise ValueError(f"Music stream {music} not initialized")
class AudioEngineWrapper: audio = AudioEngine(get_config()["audio"]["device_type"])
def __init__(self, host_api): audio.set_master_volume(0.75)
self.host_api = host_api
if host_api == 'WASAPI':
self._module = ray
elif host_api == 'ASIO':
self._module = ASIOEngine()
else:
raise Exception("Invalid host API passed to wrapper")
def __getattr__(self, name):
try:
return getattr(self._module, name)
except AttributeError:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}' and '{type(self._module).__name__}' has no attribute '{name}'")
audio = AudioEngineWrapper(get_config()["audio"]["device_type"])
if get_config()["audio"]["device_type"] == 'ASIO':
audio.set_master_volume(0.75)

View File

@@ -160,8 +160,9 @@ class GameScreen:
self.on_screen_start() self.on_screen_start()
self.current_ms = get_current_ms() - self.start_ms self.current_ms = get_current_ms() - self.start_ms
if (self.current_ms >= self.tja.offset*1000 + self.start_delay) and not self.song_started: if (self.current_ms >= self.tja.offset*1000 + self.start_delay) and not self.song_started:
if not audio.is_sound_playing(self.song_music): if self.song_music is not None:
audio.play_sound(self.song_music) if not audio.is_sound_playing(self.song_music):
audio.play_sound(self.song_music)
if self.movie is not None: if self.movie is not None:
self.movie.start(get_current_ms()) self.movie.start(get_current_ms())
self.song_started = True self.song_started = True
@@ -252,6 +253,7 @@ class Player:
self.gauge_hit_effect: list[GaugeHitEffect] = [] self.gauge_hit_effect: list[GaugeHitEffect] = []
self.autoplay_hit_side = 'L' self.autoplay_hit_side = 'L'
self.last_subdivision = -1
def get_result_score(self): def get_result_score(self):
return self.score, self.good_count, self.ok_count, self.bad_count, self.total_drumroll, self.max_combo return self.score, self.good_count, self.ok_count, self.bad_count, self.total_drumroll, self.max_combo
@@ -475,30 +477,54 @@ class Player:
self.check_note(game_screen, config["note_type"]) self.check_note(game_screen, config["note_type"])
self.input_log[game_screen.current_ms] = (hit_type, key) self.input_log[game_screen.current_ms] = (hit_type, key)
def autoplay_manager(self, game_screen): def autoplay_manager(self, game_screen: GameScreen):
if not get_config()["general"]["autoplay"]: if not get_config()["general"]["autoplay"]:
return return
if len(self.play_notes) == 0: if len(self.play_notes) == 0:
return return
note = self.play_notes[0] note = self.play_notes[0]
if game_screen.current_ms >= note.hit_ms and note.type != 8: if self.is_drumroll or self.is_balloon:
hit_type = 'DON' subdivision_in_ms = game_screen.current_ms // ((60000 * 4 / game_screen.tja.bpm) / 24)
if note.type == 2 or note.type == 4: if subdivision_in_ms > self.last_subdivision:
hit_type = 'KAT' self.last_subdivision = subdivision_in_ms
self.lane_hit_effect = LaneHitEffect(hit_type) hit_type = 'DON'
if self.autoplay_hit_side == 'L': self.lane_hit_effect = LaneHitEffect(hit_type)
self.autoplay_hit_side = 'R' if self.autoplay_hit_side == 'L':
else: self.autoplay_hit_side = 'R'
self.autoplay_hit_side = 'L' else:
self.draw_drum_hit_list.append(DrumHitEffect(hit_type, self.autoplay_hit_side)) self.autoplay_hit_side = 'L'
sound = game_screen.sound_don if hit_type == "DON" else game_screen.sound_kat self.draw_drum_hit_list.append(DrumHitEffect(hit_type, self.autoplay_hit_side))
audio.play_sound(sound) audio.play_sound(game_screen.sound_don)
type = note.type type = note.type
if type == 6 or type == 9: if type == 6 or type == 9:
type = 3 type = 3
elif type == 5 or type == 7: elif type == 5 or type == 7:
type = 1 type = 1
self.check_note(game_screen, type) self.check_note(game_screen, type)
else:
while game_screen.current_ms >= note.hit_ms and note.type <= 4:
hit_type = 'DON'
if note.type == 2 or note.type == 4:
hit_type = 'KAT'
self.lane_hit_effect = LaneHitEffect(hit_type)
if self.autoplay_hit_side == 'L':
self.autoplay_hit_side = 'R'
else:
self.autoplay_hit_side = 'L'
self.draw_drum_hit_list.append(DrumHitEffect(hit_type, self.autoplay_hit_side))
sound = game_screen.sound_don if hit_type == "DON" else game_screen.sound_kat
audio.play_sound(sound)
type = note.type
if type == 6 or type == 9:
type = 3
elif type == 5 or type == 7:
type = 1
self.check_note(game_screen, type)
if len(self.play_notes) > 0:
note = self.play_notes[0]
print(note)
else:
break
def update(self, game_screen: GameScreen): def update(self, game_screen: GameScreen):

View File

@@ -1,6 +1,6 @@
import os import os
from pathlib import Path
import sqlite3 import sqlite3
from pathlib import Path
import pyray as ray import pyray as ray