upgrade to uv, many fxes

This commit is contained in:
Yonokid
2025-05-02 00:05:25 -04:00
parent e306c62dce
commit 8b59cd4587
14 changed files with 881 additions and 179 deletions

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

View File

@@ -7,13 +7,13 @@ tja_path = 'Songs'
video_path = 'Videos' video_path = 'Videos'
[keybinds] [keybinds]
left_kat = ['D'] left_kat = ['E']
left_don = ['F'] left_don = ['F']
right_don = ['J'] right_don = ['J']
right_kat = ['K'] right_kat = ['I']
[audio] [audio]
device_type = 'WASAPI' device_type = 'ASIO'
asio_buffer = 6 asio_buffer = 6
[video] [video]

View File

@@ -29,7 +29,9 @@ class Animation:
self.duration, self.duration,
self.params['total_distance'], self.params['total_distance'],
self.params['start_position'], self.params['start_position'],
delay=self.params.get('delay', 0.0)) delay=self.params.get('delay', 0.0),
ease_in=self.params.get('ease_in', None),
ease_out=self.params.get('ease_out', None))
elif self.type == 'texture_change': elif self.type == 'texture_change':
self.texture_change(current_ms, self.texture_change(current_ms,
self.duration, self.duration,
@@ -50,8 +52,7 @@ class Animation:
initial_size=self.params.get('final_size', 1.0), initial_size=self.params.get('final_size', 1.0),
delay=self.params.get('delay', 0.0) + self.duration) delay=self.params.get('delay', 0.0) + self.duration)
def fade(self, current_ms: float, duration: float, initial_opacity: float, final_opacity: float, delay: float, ease_in: str | None, ease_out: str | None) -> None: def _ease_out_progress(self, progress: float, ease: str | None) -> float:
def _ease_out_progress(progress: float, ease: str | None) -> float:
if ease == 'quadratic': if ease == 'quadratic':
return progress * (2 - progress) return progress * (2 - progress)
elif ease == 'cubic': elif ease == 'cubic':
@@ -60,7 +61,7 @@ class Animation:
return 1 - pow(2, -10 * progress) return 1 - pow(2, -10 * progress)
else: else:
return progress return progress
def _ease_in_progress(progress: float, ease: str | None) -> float: def _ease_in_progress(self, progress: float, ease: str | None) -> float:
if ease == 'quadratic': if ease == 'quadratic':
return progress * progress return progress * progress
elif ease == 'cubic': elif ease == 'cubic':
@@ -69,6 +70,8 @@ class Animation:
return pow(2, 10 * (progress - 1)) return pow(2, 10 * (progress - 1))
else: else:
return progress return progress
def fade(self, current_ms: float, duration: float, initial_opacity: float, final_opacity: float, delay: float, ease_in: str | None, ease_out: str | None) -> None:
elapsed_time = current_ms - self.start_ms elapsed_time = current_ms - self.start_ms
if elapsed_time < delay: if elapsed_time < delay:
self.attribute = initial_opacity self.attribute = initial_opacity
@@ -79,27 +82,32 @@ class Animation:
self.is_finished = True self.is_finished = True
if ease_in is not None: if ease_in is not None:
progress = _ease_in_progress(elapsed_time / duration, ease_in) progress = self._ease_in_progress(elapsed_time / duration, ease_in)
elif ease_out is not None: elif ease_out is not None:
progress = _ease_out_progress(elapsed_time / duration, ease_out) progress = self._ease_out_progress(elapsed_time / duration, ease_out)
else: else:
progress = elapsed_time / duration progress = elapsed_time / duration
current_opacity = initial_opacity + (final_opacity - initial_opacity) * progress current_opacity = initial_opacity + (final_opacity - initial_opacity) * progress
self.attribute = current_opacity self.attribute = current_opacity
def move(self, current_ms: float, duration: float, total_distance: float, start_position: float, delay: float) -> None: def move(self, current_ms: float, duration: float, total_distance: float, start_position: float, delay: float, ease_in: str | None, ease_out: str | None) -> None:
elapsed_time = current_ms - self.start_ms elapsed_time = current_ms - self.start_ms
if elapsed_time < delay: if elapsed_time < delay:
self.attribute = start_position self.attribute = start_position
elapsed_time -= delay elapsed_time -= delay
if elapsed_time <= duration: if elapsed_time <= duration:
if ease_in is not None:
progress = self._ease_in_progress(elapsed_time / duration, ease_in)
elif ease_out is not None:
progress = self._ease_out_progress(elapsed_time / duration, ease_out)
else:
progress = elapsed_time / duration progress = elapsed_time / duration
self.attribute = start_position + (total_distance * progress) self.attribute = start_position + (total_distance * progress)
else: else:
self.attribute = start_position + total_distance self.attribute = start_position + total_distance
self.is_finished = True self.is_finished = True
def texture_change(self, current_ms: float, duration: float, textures: list[tuple[float, float, float]]) -> None: def texture_change(self, current_ms: float, duration: float, textures: list[tuple[float, float, int]]) -> None:
elapsed_time = current_ms - self.start_ms elapsed_time = current_ms - self.start_ms
if elapsed_time <= duration: if elapsed_time <= duration:
for start, end, index in textures: for start, end, index in textures:

View File

@@ -741,3 +741,5 @@ class AudioEngineWrapper:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}' and '{type(self._module).__name__}' has no attribute '{name}'") 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"]) audio = AudioEngineWrapper(get_config()["audio"]["device_type"])
if get_config()["audio"]["device_type"] == 'ASIO':
audio.set_master_volume(0.75)

View File

@@ -371,6 +371,7 @@ class TJAParser:
raise Exception("Balloon note found, but no count was specified") raise Exception("Balloon note found, but no count was specified")
note = Balloon(note) note = Balloon(note)
note.count = int(balloon[balloon_index]) note.count = int(balloon[balloon_index])
balloon_index += 1
self.current_ms += increment self.current_ms += increment
play_note_list.append(note) play_note_list.append(note)
self.get_moji(play_note_list, ms_per_measure) self.get_moji(play_note_list, ms_per_measure)

View File

@@ -1,13 +1,13 @@
import os import os
import tempfile import tempfile
import time import time
import tomllib
import zipfile import zipfile
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import pyray as ray import pyray as ray
import tomllib
#TJA Format creator is unknown. I did not create the format, but I did write the parser though. #TJA Format creator is unknown. I did not create the format, but I did write the parser though.
@@ -95,16 +95,33 @@ def get_config() -> dict[str, Any]:
config_file = tomllib.load(f) config_file = tomllib.load(f)
return config_file return config_file
def draw_scaled_texture(texture, x: int, y: int, scale: float, color: ray.Color) -> None:
width = texture.width
height = texture.height
src_rect = ray.Rectangle(0, 0, width, height)
dst_rect = ray.Rectangle(x, y, width*scale, height*scale)
ray.draw_texture_pro(texture, src_rect, dst_rect, ray.Vector2(0, 0), 0, color)
@dataclass
class SessionData:
selected_song: str = '' #Path
selected_difficulty: int = 0
song_title: str = ''
result_score: int = 0
result_good: int = 0
result_ok: int = 0
result_bad: int = 0
result_max_combo: int = 0
result_total_drumroll: int = 0
result_gauge_length: int = 0
session_data = SessionData()
def reset_session():
return SessionData()
@dataclass @dataclass
class GlobalData: class GlobalData:
videos_cleared = False
selected_song: str = '' #Path
selected_difficulty: int = -1
song_title: str = ''
result_good: int = -1
result_ok: int = -1
result_bad: int = -1
result_score: int = -1
songs_played: int = 0 songs_played: int = 0
global_data = GlobalData() global_data = GlobalData()
@@ -168,3 +185,49 @@ class OutlinedText:
def unload(self): def unload(self):
ray.unload_texture(self.texture) ray.unload_texture(self.texture)
'''
class RenderTextureStack:
def __init__(self):
"""Initialize an empty stack for render textures."""
self.texture_stack = []
def load_render_texture(self, width, height):
"""Create and return a render texture with the specified dimensions."""
return ray.load_render_texture(width, height)
def begin_texture_mode(self, target):
"""Begin drawing to the render texture and add it to the stack."""
ray.begin_texture_mode(target)
self.texture_stack.append(target)
return target
def end_texture_mode(self, pop_count=1):
"""End the texture mode for the specified number of textures in the stack."""
if not self.texture_stack:
raise IndexError("Cannot end texture mode: texture stack is empty")
# Ensure pop_count is within valid range
pop_count = min(pop_count, len(self.texture_stack))
# End the texture modes and pop from stack
for _ in range(pop_count):
ray.end_texture_mode()
self.texture_stack.pop()
def get_texture(self, target):
"""Get the texture from the render texture."""
return ray.get_texture_default(target)
def draw_texture(self, texture, pos_x, pos_y, tint=ray.WHITE):
"""Draw a texture at the specified position with the given tint."""
ray.draw_texture(texture, pos_x, pos_y, tint)
def get_current_target(self):
"""Get the current active render target from the stack."""
if not self.texture_stack:
return None
return self.texture_stack[-1]
render_stack = RenderTextureStack()
'''

View File

@@ -9,63 +9,68 @@ class VideoPlayer:
def __init__(self, path: str): def __init__(self, path: str):
self.video_path = path self.video_path = path
self.start_ms = None self.start_ms = None
self.current_frame = None self.current_frame = None
self.last_frame = self.current_frame self.last_frame = self.current_frame
self.frame_index = 0 self.frame_index = 0
self.frames = [] self.frames = []
self.cap = cv2.VideoCapture(self.video_path) self.cap = cv2.VideoCapture(self.video_path)
self.fps = self.cap.get(cv2.CAP_PROP_FPS) self.fps = self.cap.get(cv2.CAP_PROP_FPS)
self.is_finished_list = [False, False, False] # Added third flag for frame conversion
self.is_finished = [False, False] self.all_frames_converted = False
audio_path = path[:-4] + '.ogg' audio_path = path[:-4] + '.ogg'
self.audio = audio.load_music_stream(audio_path) self.audio = audio.load_music_stream(audio_path)
def _convert_frames_background(self): def is_finished(self) -> bool:
if not self.cap.isOpened(): return all(self.is_finished_list)
raise ValueError("Error: Could not open video file.")
total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
if len(self.frames) == total_frames:
return 0
self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frame_index)
success, frame = self.cap.read()
timestamp = (self.frame_index / self.fps * 1000)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
new_frame = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8)
self.frames.append((timestamp, new_frame))
self.frame_index += 1
def _convert_frames(self): def _convert_frames(self):
"""Legacy method that converts all frames at once"""
if not self.cap.isOpened(): if not self.cap.isOpened():
raise ValueError("Error: Could not open video file.") raise ValueError("Error: Could not open video file.")
frame_count = 0 frame_count = 0
success, frame = self.cap.read() success, frame = self.cap.read()
while success: while success:
timestamp = (frame_count / self.fps * 1000) timestamp = (frame_count / self.fps * 1000)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
new_frame = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8) new_frame = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8)
self.frames.append((timestamp, new_frame)) self.frames.append((timestamp, new_frame))
success, frame = self.cap.read() success, frame = self.cap.read()
frame_count += 1 frame_count += 1
self.cap.release() self.cap.release()
print(f"Extracted {len(self.frames)} frames.") print(f"Extracted {len(self.frames)} frames.")
self.start_ms = get_current_ms() self.start_ms = get_current_ms()
self.all_frames_converted = True
self.is_finished_list[2] = True
def convert_frames_background(self):
"""Converts a single frame each time it's called"""
if self.all_frames_converted:
return
if not self.cap.isOpened():
self.cap = cv2.VideoCapture(self.video_path)
if not self.cap.isOpened():
raise ValueError("Error: Could not open video file.")
# Process one frame
success, frame = self.cap.read()
if success:
timestamp = (len(self.frames) / self.fps * 1000)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
new_frame = ray.Image(frame_rgb.tobytes(), frame_rgb.shape[1], frame_rgb.shape[0], 1, ray.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8)
self.frames.append((timestamp, new_frame))
else:
# No more frames to convert
self.cap.release()
print(f"Extracted {len(self.frames)} frames.")
self.all_frames_converted = True
self.is_finished_list[2] = True
def _check_for_start(self): def _check_for_start(self):
if self.frames == []: # Start audio once we have at least one frame
self._convert_frames() if len(self.frames) > 0 and not audio.is_music_stream_playing(self.audio):
if not audio.is_music_stream_playing(self.audio):
audio.play_music_stream(self.audio) audio.play_music_stream(self.audio)
def _audio_manager(self): def _audio_manager(self):
@@ -73,21 +78,25 @@ class VideoPlayer:
time_played = audio.get_music_time_played(self.audio) / audio.get_music_time_length(self.audio) time_played = audio.get_music_time_played(self.audio) / audio.get_music_time_length(self.audio)
ending_lenience = 0.95 ending_lenience = 0.95
if time_played > ending_lenience: if time_played > ending_lenience:
self.is_finished[1] = True self.is_finished_list[1] = True
def update(self): def update(self):
self._check_for_start() self._check_for_start()
self._audio_manager() self._audio_manager()
if self.frame_index == len(self.frames)-1: # Check if we've reached the end of available frames
self.is_finished[0] = True if self.frame_index == len(self.frames) - 1 and self.all_frames_converted:
self.is_finished_list[0] = True
return return
if self.start_ms is None: if self.start_ms is None:
return return
# Only proceed if we have frames to display
if self.frame_index < len(self.frames):
timestamp, frame = self.frames[self.frame_index][0], self.frames[self.frame_index][1] timestamp, frame = self.frames[self.frame_index][0], self.frames[self.frame_index][1]
elapsed_time = get_current_ms() - self.start_ms elapsed_time = get_current_ms() - self.start_ms
if elapsed_time >= timestamp: if elapsed_time >= timestamp:
self.current_frame = ray.load_texture_from_image(frame) self.current_frame = ray.load_texture_from_image(frame)
if self.last_frame != self.current_frame and self.last_frame is not None: if self.last_frame != self.current_frame and self.last_frame is not None:
@@ -99,10 +108,12 @@ class VideoPlayer:
if self.current_frame is not None: if self.current_frame is not None:
ray.draw_texture(self.current_frame, 0, 0, ray.WHITE) ray.draw_texture(self.current_frame, 0, 0, ray.WHITE)
def __del__(self): def stop(self):
if hasattr(self, 'current_frame') and self.current_frame: if hasattr(self, 'current_frame') and self.current_frame:
ray.unload_texture(self.current_frame) ray.unload_texture(self.current_frame)
if hasattr(self, 'last_frame') and self.last_frame: if hasattr(self, 'last_frame') and self.last_frame:
ray.unload_texture(self.last_frame) ray.unload_texture(self.last_frame)
if audio.is_music_stream_playing(self.audio): if audio.is_music_stream_playing(self.audio):
audio.stop_music_stream(self.audio) audio.stop_music_stream(self.audio)
if hasattr(self, 'cap') and self.cap.isOpened():
self.cap.release()

View File

@@ -1,7 +1,7 @@
import pyray as ray import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.utils import get_config, global_data from libs.utils import get_config
from scenes.entry import EntryScreen from scenes.entry import EntryScreen
from scenes.game import GameScreen from scenes.game import GameScreen
from scenes.result import ResultScreen from scenes.result import ResultScreen
@@ -78,10 +78,6 @@ def main():
if screen == title_screen: if screen == title_screen:
ray.clear_background(ray.BLACK) ray.clear_background(ray.BLACK)
else: else:
if not global_data.videos_cleared:
del title_screen.op_video
del title_screen.attract_video
global_data.videos_cleared = True
ray.clear_background(ray.WHITE) ray.clear_background(ray.WHITE)
if next_screen is not None: if next_screen is not None:

17
pyproject.toml Normal file
View File

@@ -0,0 +1,17 @@
[project]
name = "pytaiko"
version = "0.1.0"
description = "Taiko no Tatsujin simulator written in python and raylib"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"numpy>=2.2.5",
"opencv-python>=4.11.0.86",
"pydub>=0.25.1",
"raylib>=5.5.0.2",
"raylib-dynamic>=5.5.0.2",
"ruff>=0.11.7",
"scipy>=1.15.2",
"sounddevice>=0.5.1",
"audioop-lts; python_version >= '3.13'",
]

View File

@@ -16,6 +16,7 @@ from libs.utils import (
load_all_textures_from_zip, load_all_textures_from_zip,
load_image_from_zip, load_image_from_zip,
load_texture_from_zip, load_texture_from_zip,
session_data,
) )
@@ -25,11 +26,8 @@ class GameScreen:
self.height = height self.height = height
self.judge_x = 414 self.judge_x = 414
self.current_ms = 0 self.current_ms = 0
self.result_transition = None self.result_transition = None
self.song_info = None self.song_info = None
self.screen_init = False self.screen_init = False
def load_textures(self): def load_textures(self):
@@ -99,12 +97,12 @@ class GameScreen:
self.textures['onp_fusen'][0]] self.textures['onp_fusen'][0]]
self.tja = TJAParser(song) self.tja = TJAParser(song)
self.tja.get_metadata() metadata = self.tja.get_metadata()
self.tja.distance = self.width - self.judge_x self.tja.distance = self.width - self.judge_x
self.start_delay = 0 self.start_delay = 0
global_data.song_title = self.tja.title session_data.song_title = self.tja.title
self.player_1 = Player(self, 1, difficulty, get_config()["general"]["judge_offset"]) self.player_1 = Player(self, 1, difficulty, metadata, get_config()["general"]["judge_offset"])
self.song_music = audio.load_sound(str(Path(self.tja.wave))) self.song_music = audio.load_sound(str(Path(self.tja.wave)))
self.start_ms = (get_current_ms() - self.tja.offset*1000) + self.start_delay self.start_ms = (get_current_ms() - self.tja.offset*1000) + self.start_delay
@@ -113,7 +111,7 @@ class GameScreen:
def on_screen_start(self): def on_screen_start(self):
if not self.screen_init: if not self.screen_init:
self.screen_init = True self.screen_init = True
self.init_tja(global_data.selected_song, global_data.selected_difficulty) self.init_tja(session_data.selected_song, session_data.selected_difficulty)
self.current_ms = get_current_ms() - self.start_ms self.current_ms = get_current_ms() - self.start_ms
self.song_info = SongInfo(self.current_ms, self.tja.title, 'TEST') self.song_info = SongInfo(self.current_ms, self.tja.title, 'TEST')
self.result_transition = None self.result_transition = None
@@ -139,7 +137,8 @@ class GameScreen:
if self.result_transition.is_finished: if self.result_transition.is_finished:
return self.on_screen_end() return self.on_screen_end()
elif len(self.player_1.play_note_list) == 0 and (len(self.player_1.current_notes) == 0) and not audio.is_sound_playing(self.song_music): elif len(self.player_1.play_note_list) == 0 and (len(self.player_1.current_notes) == 0) and not audio.is_sound_playing(self.song_music):
global_data.result_good, global_data.result_ok, global_data.result_bad, global_data.result_score = self.player_1.get_result_score() session_data.result_score, session_data.result_good, session_data.result_ok, session_data.result_bad, session_data.result_max_combo, session_data.result_total_drumroll = self.player_1.get_result_score()
session_data.result_gauge_length = self.player_1.gauge.gauge_length
self.result_transition = ResultTransition(self.current_ms, self.height) self.result_transition = ResultTransition(self.current_ms, self.height)
audio.play_sound(self.sound_result_transition) audio.play_sound(self.sound_result_transition)
@@ -152,7 +151,7 @@ class GameScreen:
class Player: class Player:
def __init__(self, game_screen: GameScreen, player_number: int, difficulty: int, judge_offset: int): def __init__(self, game_screen: GameScreen, player_number: int, difficulty: int, metadata, judge_offset: int):
self.timing_good = 25.0250015258789 self.timing_good = 25.0250015258789
self.timing_ok = 75.0750045776367 self.timing_ok = 75.0750045776367
self.timing_bad = 108.441665649414 self.timing_bad = 108.441665649414
@@ -161,6 +160,8 @@ class Player:
self.difficulty = difficulty self.difficulty = difficulty
self.play_note_list, self.draw_note_list, self.draw_bar_list = game_screen.tja.notes_to_position(self.difficulty) self.play_note_list, self.draw_note_list, self.draw_bar_list = game_screen.tja.notes_to_position(self.difficulty)
self.total_notes = len([note for note in self.play_note_list if 0 < note.type < 5])
print(self.total_notes)
self.base_score = calculate_base_score(self.play_note_list) self.base_score = calculate_base_score(self.play_note_list)
self.judge_offset = judge_offset self.judge_offset = judge_offset
@@ -201,8 +202,10 @@ class Player:
self.input_log: dict[float, str] = dict() self.input_log: dict[float, str] = dict()
self.gauge = Gauge(game_screen.current_ms, self.difficulty, metadata[-1][self.difficulty][0])
def get_result_score(self): def get_result_score(self):
return self.good_count, self.ok_count, self.bad_count, self.score return self.score, self.good_count, self.ok_count, self.bad_count, self.total_drumroll, self.max_combo
def get_position(self, game_screen: GameScreen, ms: float, pixels_per_frame: float) -> int: def get_position(self, game_screen: GameScreen, ms: float, pixels_per_frame: float) -> int:
return int(game_screen.width + pixels_per_frame * 60 / 1000 * (ms - game_screen.current_ms + self.judge_offset) - 64) return int(game_screen.width + pixels_per_frame * 60 / 1000 * (ms - game_screen.current_ms + self.judge_offset) - 64)
@@ -398,7 +401,6 @@ class Player:
def balloon_manager(self, game_screen: GameScreen): def balloon_manager(self, game_screen: GameScreen):
if self.balloon_anim is not None: if self.balloon_anim is not None:
print(self.is_balloon)
self.balloon_anim.update(game_screen, game_screen.current_ms, self.curr_balloon_count, not self.is_balloon) self.balloon_anim.update(game_screen, game_screen.current_ms, self.curr_balloon_count, not self.is_balloon)
if self.balloon_anim.is_finished: if self.balloon_anim.is_finished:
self.balloon_anim = None self.balloon_anim = None
@@ -451,6 +453,8 @@ class Player:
self.score_counter.update(game_screen.current_ms, self.score) self.score_counter.update(game_screen.current_ms, self.score)
self.key_manager(game_screen) self.key_manager(game_screen)
self.gauge.update(game_screen.current_ms, self.good_count, self.ok_count, self.bad_count, self.total_notes)
def draw_drumroll(self, game_screen: GameScreen, head: Drumroll, current_eighth: int): def draw_drumroll(self, game_screen: GameScreen, head: Drumroll, current_eighth: int):
start_position = self.get_position(game_screen, head.load_ms, head.pixels_per_frame) start_position = self.get_position(game_screen, head.load_ms, head.pixels_per_frame)
tail = next((note for note in self.current_notes_draw[1:] if note.type == 8 and note.index > head.index), None) tail = next((note for note in self.current_notes_draw[1:] if note.type == 8 and note.index > head.index), None)
@@ -459,6 +463,8 @@ class Player:
is_big = int(head.type == 6) * 2 is_big = int(head.type == 6) * 2
end_position = self.get_position(game_screen, tail.load_ms, tail.pixels_per_frame) end_position = self.get_position(game_screen, tail.load_ms, tail.pixels_per_frame)
length = (end_position - start_position - 50) length = (end_position - start_position - 50)
if length <= 0:
end_position += 50
source_rect = ray.Rectangle(0,0,game_screen.note_type_list[8].width, game_screen.note_type_list[8].height) source_rect = ray.Rectangle(0,0,game_screen.note_type_list[8].width, game_screen.note_type_list[8].height)
dest_rect = ray.Rectangle(start_position+64, 192, length, game_screen.note_type_list[1][0].height) dest_rect = ray.Rectangle(start_position+64, 192, length, game_screen.note_type_list[1][0].height)
color = ray.Color(255, head.color, head.color, 255) color = ray.Color(255, head.color, head.color, 255)
@@ -472,7 +478,7 @@ class Player:
moji_texture = game_screen.texture_se_moji[head.moji] moji_texture = game_screen.texture_se_moji[head.moji]
ray.draw_texture(moji_texture, start_position - (moji_texture.width//2) + 64, 323, ray.WHITE) ray.draw_texture(moji_texture, start_position - (moji_texture.width//2) + 64, 323, ray.WHITE)
moji_texture = game_screen.texture_se_moji[tail.moji] moji_texture = game_screen.texture_se_moji[tail.moji]
ray.draw_texture(moji_texture, end_position - (moji_texture.width//2) + 64, 323, ray.WHITE) ray.draw_texture(moji_texture, (end_position - (moji_texture.width//2)) + 32, 323, ray.WHITE)
def draw_balloon(self, game_screen: GameScreen, head: Balloon, current_eighth: int): def draw_balloon(self, game_screen: GameScreen, head: Balloon, current_eighth: int):
offset = 12 offset = 12
@@ -526,21 +532,19 @@ class Player:
ray.draw_texture(moji_texture, position - (moji_texture.width//2) + 64, 323, ray.WHITE) ray.draw_texture(moji_texture, position - (moji_texture.width//2) + 64, 323, ray.WHITE)
#ray.draw_text(str(i), position+64, 192, 25, ray.GREEN) #ray.draw_text(str(i), position+64, 192, 25, ray.GREEN)
def draw_gauge(self, textures: list[ray.Texture]):
ray.draw_texture(textures[0], 327, 128, ray.WHITE)
ray.draw_texture(textures[1], 483, 124, ray.WHITE)
ray.draw_texture(textures[10], 483, 124, ray.fade(ray.WHITE, 0.15))
ray.draw_texture(textures[11], 1038, 141, ray.WHITE)
ray.draw_texture(textures[12], 1187, 130, ray.WHITE)
def draw(self, game_screen: GameScreen): def draw(self, game_screen: GameScreen):
ray.draw_texture(game_screen.textures['lane'][0], 332, 184, ray.WHITE) ray.draw_texture(game_screen.textures['lane'][0], 332, 184, ray.WHITE)
self.draw_gauge(game_screen.textures['gage_don_1p_hard']) self.gauge.draw(game_screen.textures['gage_don_1p_hard'])
if self.lane_hit_effect is not None: if self.lane_hit_effect is not None:
self.lane_hit_effect.draw(game_screen.textures['lane_hit']) self.lane_hit_effect.draw(game_screen.textures['lane_hit'])
ray.draw_texture(game_screen.textures['lane_hit'][17], 342, 184, ray.WHITE) ray.draw_texture(game_screen.textures['lane_hit'][17], 342, 184, ray.WHITE)
for anim in self.draw_judge_list: for anim in self.draw_judge_list:
anim.draw(game_screen.textures['lane_hit'], game_screen.textures['lane_hit_effect']) anim.draw(game_screen.textures['lane_hit'], game_screen.textures['lane_hit_effect'])
#ray.draw_texture(game_screen.textures['onp_kiseki_don_1p'][0], 350, 192, ray.WHITE)
#ray.draw_texture(game_screen.textures['onp_kiseki_don_1p'][22], 332, -84, ray.WHITE)
#ray.draw_texture(game_screen.textures['onp_kiseki_don_1p'][6], 1187 - 29, 130 - 29, ray.WHITE)
self.draw_bars(game_screen) self.draw_bars(game_screen)
self.draw_notes(game_screen) self.draw_notes(game_screen)
ray.draw_texture(game_screen.textures['lane_obi'][0], 0, 184, ray.WHITE) ray.draw_texture(game_screen.textures['lane_obi'][0], 0, 184, ray.WHITE)
@@ -670,36 +674,87 @@ class DrumHitEffect:
class NoteArc: class NoteArc:
def __init__(self, note_texture: ray.Texture, current_ms: float, player_number: int): def __init__(self, note_texture: ray.Texture, current_ms: float, player_number: int):
self.texture = note_texture self.texture = note_texture
self.arc_points = 25 self.arc_points = 22
self.create_ms = current_ms self.create_ms = current_ms
self.player_number = player_number self.player_number = player_number
self.x_i = 414 - 64 curve_height = 425
self.y_i = 168
self.start_x, self.start_y = 350, 192
self.end_x, self.end_y = 1158, 101
if self.player_number == 1:
# Control point influences the curve shape
self.control_x = (self.start_x + self.end_x) // 2
self.control_y = min(self.start_y, self.end_y) - curve_height # Arc upward
else:
# For player 2 (assumed to be a downward arc)
self.control_x = (self.start_x + self.end_x) // 2
self.control_y = max(self.start_y, self.end_y) + curve_height # Arc downward
self.x_i = self.start_x
self.y_i = self.start_y
self.is_finished = False
num_precalc_points = 100 # More points for better approximation
self.path_points = []
self.path_distances = [0.0] # Cumulative distance at each point
prev_x, prev_y = self.start_x, self.start_y
total_distance = 0.0
for i in range(1, num_precalc_points + 1):
t = i / num_precalc_points
x = int((1-t)**2 * self.start_x + 2*(1-t)*t * self.control_x + t**2 * self.end_x)
y = int((1-t)**2 * self.start_y + 2*(1-t)*t * self.control_y + t**2 * self.end_y)
# Calculate distance from previous point
dx = x - prev_x
dy = y - prev_y
distance = math.sqrt(dx*dx + dy*dy)
total_distance += distance
self.path_points.append((x, y))
self.path_distances.append(total_distance)
prev_x, prev_y = x, y
self.total_path_length = total_distance
self.x_i = self.start_x
self.y_i = self.start_y
self.is_finished = False self.is_finished = False
def update(self, current_ms: float): def update(self, current_ms: float):
if self.x_i >= 1150: if self.x_i >= self.end_x:
self.is_finished = True self.is_finished = True
radius = 414 self.x_i = self.end_x
#Start at 180 degrees, end at 0 self.y_i = self.end_y
theta_start = 3.14 return
if self.player_number == 1:
theta_end = 2 * 3.14
#center of circle that does not exist
center_x, center_y = 785, 168
else:
theta_end = 0
center_x, center_y = 785, 468
ms_since_call = (current_ms - self.create_ms) / 16.67 ms_since_call = (current_ms - self.create_ms) / 16.67
if ms_since_call < 0: ms_since_call = max(0, min(ms_since_call, self.arc_points))
ms_since_call = 0
if ms_since_call > self.arc_points: # Calculate desired distance along the path (constant speed)
ms_since_call = self.arc_points target_distance = (ms_since_call / self.arc_points) * self.total_path_length
angle_change = (theta_end - theta_start) / self.arc_points
theta_i = theta_start + ms_since_call * angle_change # Find the closest pre-calculated points
self.x_i = int(center_x + radius * math.cos(theta_i)) index = 0
self.y_i = int(center_y + radius * 0.5 * math.sin(theta_i)) while index < len(self.path_distances) - 1 and self.path_distances[index + 1] < target_distance:
index += 1
# Interpolate between the points
if index < len(self.path_distances) - 1:
d1 = self.path_distances[index]
d2 = self.path_distances[index + 1]
if d2 > d1: # Avoid division by zero
fraction = (target_distance - d1) / (d2 - d1)
x1, y1 = self.path_points[index - 1] if index > 0 else (self.start_x, self.start_y)
x2, y2 = self.path_points[index]
self.x_i = int(x1 + fraction * (x2 - x1))
self.y_i = int(y1 + fraction * (y2 - y1))
else:
# At the end of the path
self.x_i = self.end_x
self.y_i = self.end_y
def draw(self, game_screen): def draw(self, game_screen):
ray.draw_texture(self.texture, self.x_i, self.y_i, ray.WHITE) ray.draw_texture(self.texture, self.x_i, self.y_i, ray.WHITE)
@@ -1028,6 +1083,7 @@ class ResultTransition:
self.move = Animation(current_ms, 983.33, 'move') self.move = Animation(current_ms, 983.33, 'move')
self.move.params['start_position'] = 0.0 self.move.params['start_position'] = 0.0
self.move.params['total_distance'] = screen_height//2 self.move.params['total_distance'] = screen_height//2
self.move.params['ease_out'] = 'quadratic'
self.is_finished = False self.is_finished = False
@@ -1048,3 +1104,127 @@ class ResultTransition:
ray.draw_texture(texture_2, x, (0 - texture_2.height//2) - (texture_1.height//2) + int(self.move.attribute), ray.WHITE) ray.draw_texture(texture_2, x, (0 - texture_2.height//2) - (texture_1.height//2) + int(self.move.attribute), ray.WHITE)
ray.draw_texture(texture_2, x, (screen_height) + (texture_1.height//2) - (texture_2.height//2) - int(self.move.attribute), ray.WHITE) ray.draw_texture(texture_2, x, (screen_height) + (texture_1.height//2) - (texture_2.height//2) - int(self.move.attribute), ray.WHITE)
x += texture_2.width x += texture_2.width
class Gauge:
def __init__(self, current_ms: float, difficulty: int, level: int):
self.gauge_length = 0
self.difficulty = min(3, difficulty)
self.clear_start = [0, 0, 68, 68]
self.level = min(10, level)
self.table = [
[
None,
{"clear_rate": 36.0, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 38.0, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 38.0, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 44.0, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 44.0, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
],
[
None,
{"clear_rate": 45.939, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 45.939, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 48.676, "ok_multiplier": 0.75, "bad_multiplier": -0.5},
{"clear_rate": 49.232, "ok_multiplier": 0.75, "bad_multiplier": -0.75},
{"clear_rate": 52.5, "ok_multiplier": 0.75, "bad_multiplier": -1.0},
{"clear_rate": 52.5, "ok_multiplier": 0.75, "bad_multiplier": -1.0},
{"clear_rate": 52.5, "ok_multiplier": 0.75, "bad_multiplier": -1.0},
],
[
None,
{"clear_rate": 54.325, "ok_multiplier": 0.75, "bad_multiplier": -0.75},
{"clear_rate": 54.325, "ok_multiplier": 0.75, "bad_multiplier": -0.75},
{"clear_rate": 50.774, "ok_multiplier": 0.75, "bad_multiplier": -1.0},
{"clear_rate": 48.410, "ok_multiplier": 0.75, "bad_multiplier": -1.17},
{"clear_rate": 47.246, "ok_multiplier": 0.75, "bad_multiplier": -1.25},
{"clear_rate": 48.120, "ok_multiplier": 0.75, "bad_multiplier": -1.25},
{"clear_rate": 48.120, "ok_multiplier": 0.75, "bad_multiplier": -1.25},
{"clear_rate": 48.120, "ok_multiplier": 0.75, "bad_multiplier": -1.25},
],
[
None,
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.603, "ok_multiplier": 0.5, "bad_multiplier": -1.6},
{"clear_rate": 56.0, "ok_multiplier": 0.5, "bad_multiplier": -2.0},
{"clear_rate": 61.428, "ok_multiplier": 0.5, "bad_multiplier": -2.0},
{"clear_rate": 61.428, "ok_multiplier": 0.5, "bad_multiplier": -2.0},
]
]
self.gauge_update_anim = None
self.rainbow_fade_in = None
self.rainbow_animation = None
def _create_rainbow_anim(self, current_ms):
anim = Animation(current_ms, (16.67*8) * 3, 'texture_change')
anim.params['textures'] = []
for i in range(8):
anim.params['textures'].append(((16.67* 3)*i, (16.67 * 3)*(i+1), i))
return anim
def _create_anim(self, current_ms: float, init: float, final: float):
anim = Animation(current_ms, 450, 'fade')
anim.params['initial_opacity'] = init
anim.params['final_opacity'] = final
return anim
def update(self, current_ms: float, good_count: int, ok_count: int, bad_count: int, total_notes: int):
gauge_length = int(((good_count +
(ok_count * self.table[self.difficulty][self.level]["ok_multiplier"] +
bad_count * self.table[self.difficulty][self.level]["bad_multiplier"])) / total_notes) * (100 * (self.clear_start[self.difficulty] / self.table[self.difficulty][self.level]["clear_rate"])))
previous_length = self.gauge_length
self.gauge_length = min(87, gauge_length)
if self.gauge_length == 87 and self.rainbow_fade_in is None:
self.rainbow_fade_in = self._create_anim(current_ms, 0.0, 1.0)
if self.gauge_length > previous_length:
self.gauge_update_anim = self._create_anim(current_ms, 1.0, 0.0)
if self.gauge_update_anim is not None:
self.gauge_update_anim.update(current_ms)
if self.gauge_update_anim.is_finished:
self.gauge_update_anim = None
if self.rainbow_fade_in is not None:
self.rainbow_fade_in.update(current_ms)
if self.rainbow_animation is None:
self.rainbow_animation = self._create_rainbow_anim(current_ms)
else:
self.rainbow_animation.update(current_ms)
if self.rainbow_animation.is_finished or self.gauge_length < 87:
self.rainbow_animation = None
def draw(self, textures: list[ray.Texture]):
ray.draw_texture(textures[0], 327, 132, ray.WHITE)
ray.draw_texture(textures[1], 483, 124, ray.WHITE)
if self.gauge_length == 87 and self.rainbow_fade_in is not None and self.rainbow_animation is not None:
if 0 < self.rainbow_animation.attribute < 8:
ray.draw_texture(textures[1 + int(self.rainbow_animation.attribute)], 483, 124, ray.fade(ray.WHITE, self.rainbow_fade_in.attribute))
ray.draw_texture(textures[2 + int(self.rainbow_animation.attribute)], 483, 124, ray.fade(ray.WHITE, self.rainbow_fade_in.attribute))
if self.rainbow_fade_in is None or not self.rainbow_fade_in.is_finished:
for i in range(self.gauge_length):
if i == 68:
ray.draw_texture(textures[16], 491 + (i*textures[13].width), 160 - 24, ray.WHITE)
elif i > 68:
ray.draw_texture(textures[15], 491 + (i*textures[13].width) + 2, 160 - 22, ray.WHITE)
ray.draw_texture(textures[20], 491 + (i*textures[13].width) + 2, 160, ray.WHITE)
else:
ray.draw_texture(textures[13], 491 + (i*textures[13].width), 160, ray.WHITE)
if self.gauge_update_anim is not None and self.gauge_length < 88:
if self.gauge_length == 69:
ray.draw_texture(textures[17], 491 + (self.gauge_length*textures[13].width) - 13, 160 - 8 - 24, ray.fade(ray.WHITE, self.gauge_update_anim.attribute))
elif self.gauge_length > 69:
ray.draw_texture(textures[21], 491 + (self.gauge_length*textures[13].width) - 13, 160 - 8 - 22, ray.fade(ray.WHITE, self.gauge_update_anim.attribute))
else:
ray.draw_texture(textures[14], 491 + (self.gauge_length*textures[13].width) - 13, 160 - 8, ray.fade(ray.WHITE, self.gauge_update_anim.attribute))
ray.draw_texture(textures[10], 483, 124, ray.fade(ray.WHITE, 0.15))
if self.gauge_length >= 69:
ray.draw_texture(textures[18], 1038, 141, ray.WHITE)
ray.draw_texture(textures[19], 1187, 130, ray.WHITE)
else:
ray.draw_texture(textures[11], 1038, 141, ray.WHITE)
ray.draw_texture(textures[12], 1187, 130, ray.WHITE)

View File

@@ -2,50 +2,62 @@ from pathlib import Path
import pyray as ray import pyray as ray
from libs import utils
from libs.animation import Animation from libs.animation import Animation
from libs.audio import audio from libs.audio import audio
from libs.utils import ( from libs.utils import (
OutlinedText, OutlinedText,
draw_scaled_texture,
get_config,
get_current_ms, get_current_ms,
global_data, global_data,
load_all_textures_from_zip, load_all_textures_from_zip,
session_data,
) )
def draw_scaled_texture(texture, x: int, y: int, scale: float, color: ray.Color) -> None:
width = texture.width
height = texture.height
src_rect = ray.Rectangle(0, 0, width, height)
dst_rect = ray.Rectangle(x, y, width*scale, height*scale)
ray.draw_texture_pro(texture, src_rect, dst_rect, ray.Vector2(0, 0), 0, color)
class ResultScreen: class ResultScreen:
def __init__(self, width: int, height: int): def __init__(self, width: int, height: int):
self.width = width self.width = width
self.height = height self.height = height
sounds_dir = Path("Sounds") self.screen_init = False
self.sound_don = audio.load_sound(str(sounds_dir / "inst_00_don.wav")) self.load_sounds()
self.sound_kat = audio.load_sound(str(sounds_dir / "inst_00_katsu.wav"))
self.bgm = audio.load_sound(str(sounds_dir / "result" / "JINGLE_SEISEKI [1].ogg"))
def load_textures(self):
zip_file = Path('Graphics/lumendata/enso_result.zip') zip_file = Path('Graphics/lumendata/enso_result.zip')
self.textures = load_all_textures_from_zip(zip_file) self.textures = load_all_textures_from_zip(zip_file)
self.song_info = FontText(global_data.song_title, 40).texture def load_sounds(self):
self.screen_init = False sounds_dir = Path("Sounds")
self.sound_don = audio.load_sound(str(sounds_dir / "inst_00_don.wav"))
self.fade_in = None self.sound_kat = audio.load_sound(str(sounds_dir / "inst_00_katsu.wav"))
self.sound_num_up = audio.load_sound(str(sounds_dir / "result" / "SE_RESULT [4].ogg"))
self.bgm_volume = 1.0 self.bgm = audio.load_sound(str(sounds_dir / "result" / "JINGLE_SEISEKI [1].ogg"))
def on_screen_start(self): def on_screen_start(self):
if not self.screen_init: if not self.screen_init:
self.textures = load_all_textures_from_zip(Path('Graphics/lumendata/enso_result.zip')) self.load_textures()
self.screen_init = True self.screen_init = True
self.song_info = FontText(global_data.song_title, 40).texture self.song_info = FontText(session_data.song_title, 40).texture
self.bgm_volume = 1.0
audio.play_sound(self.bgm) audio.play_sound(self.bgm)
self.fade_in = FadeIn(get_current_ms()) self.fade_in = FadeIn(get_current_ms())
self.gauge = None
self.score_delay = None
self.score_animator = ScoreAnimator(session_data.result_score)
self.score = -1
self.good = -1
self.ok = -1
self.bad = -1
self.max_combo = -1
self.total_drumroll = -1
self.update_list = [['score', session_data.result_score],
['good', session_data.result_good],
['ok', session_data.result_ok],
['bad', session_data.result_bad],
['total_drumroll', session_data.result_total_drumroll],
['max_combo', session_data.result_max_combo]]
self.update_index = 0
self.is_skipped = False
def on_screen_end(self): def on_screen_end(self):
self.screen_init = False self.screen_init = False
@@ -55,28 +67,80 @@ class ResultScreen:
for texture in self.textures[zip]: for texture in self.textures[zip]:
ray.unload_texture(texture) ray.unload_texture(texture)
audio.stop_sound(self.bgm) audio.stop_sound(self.bgm)
utils.session_data = utils.reset_session()
return "SONG_SELECT" return "SONG_SELECT"
def update_score_animation(self, is_skipped):
if self.is_skipped:
setattr(self, self.update_list[self.update_index][0], self.update_list[self.update_index][1])
if self.update_index == len(self.update_list) - 1:
return
self.update_index += 1
elif self.score_delay is not None:
if get_current_ms() > self.score_delay:
if self.score_animator is not None and not self.score_animator.is_finished:
curr_num = self.update_list[self.update_index][0]
setattr(self, self.update_list[self.update_index][0], self.score_animator.next_score())
if self.update_list[self.update_index] != curr_num:
audio.play_sound(self.sound_num_up)
if self.score_animator.is_finished:
audio.play_sound(self.sound_don)
self.score_delay += 750
if self.update_index == len(self.update_list) - 1:
self.is_skipped = True
return
self.update_index += 1
self.score_animator = ScoreAnimator(self.update_list[self.update_index][1])
self.score_delay += 16.67 * 3
def update(self): def update(self):
self.on_screen_start() self.on_screen_start()
if ray.is_key_pressed(ray.KeyboardKey.KEY_ENTER):
return self.on_screen_end()
if self.fade_in is not None: if self.fade_in is not None:
self.fade_in.update(get_current_ms()) self.fade_in.update(get_current_ms())
if self.fade_in.is_finished and self.gauge is None:
self.gauge = Gauge(get_current_ms(), session_data.result_gauge_length)
if self.gauge is not None:
self.gauge.update(get_current_ms())
if self.gauge.is_finished and self.score_delay is None:
self.score_delay = get_current_ms() + 1883
left_dons = get_config()["keybinds"]["left_don"]
right_dons = get_config()["keybinds"]["right_don"]
for don in left_dons + right_dons:
if ray.is_key_pressed(ord(don)):
if not self.is_skipped:
self.is_skipped = True
audio.play_sound(self.sound_don)
else:
return self.on_screen_end()
self.update_score_animation(self.is_skipped)
def draw_score_info(self): def draw_score_info(self):
for i in range(len(str(global_data.result_good))): if self.good > -1:
ray.draw_texture(self.textures['result'][int(str(global_data.result_good)[::-1][i]) + 136], 943-(i*24), 186, ray.WHITE) for i in range(len(str(self.good))):
for i in range(len(str(global_data.result_ok))): ray.draw_texture(self.textures['result'][int(str(self.good)[::-1][i]) + 136], 943-(i*24), 186, ray.WHITE)
ray.draw_texture(self.textures['result'][int(str(global_data.result_ok)[::-1][i]) + 136], 943-(i*24), 227, ray.WHITE) if self.ok > -1:
for i in range(len(str(global_data.result_bad))): for i in range(len(str(self.ok))):
ray.draw_texture(self.textures['result'][int(str(global_data.result_bad)[::-1][i]) + 136], 943-(i*24), 267, ray.WHITE) ray.draw_texture(self.textures['result'][int(str(self.ok)[::-1][i]) + 136], 943-(i*24), 227, ray.WHITE)
if self.bad > -1:
for i in range(len(str(self.bad))):
ray.draw_texture(self.textures['result'][int(str(self.bad)[::-1][i]) + 136], 943-(i*24), 267, ray.WHITE)
if self.max_combo > -1:
for i in range(len(str(self.max_combo))):
ray.draw_texture(self.textures['result'][int(str(self.max_combo)[::-1][i]) + 136], 1217-(i*24), 227, ray.WHITE)
if self.total_drumroll > -1:
for i in range(len(str(self.total_drumroll))):
ray.draw_texture(self.textures['result'][int(str(self.total_drumroll)[::-1][i]) + 136], 1217-(i*24), 186, ray.WHITE)
def draw_total_score(self): def draw_total_score(self):
ray.draw_texture(self.textures['result'][167], 554, 236, ray.WHITE) ray.draw_texture(self.textures['result'][167], 554, 236, ray.WHITE)
for i in range(len(str(global_data.result_score))): if self.score > -1:
ray.draw_texture(self.textures['result'][int(str(global_data.result_score)[::-1][i]) + 156], 723-(i*21), 252, ray.WHITE) for i in range(len(str(self.score))):
ray.draw_texture(self.textures['result'][int(str(self.score)[::-1][i]) + 156], 723-(i*21), 252, ray.WHITE)
def draw(self): def draw(self):
x = 0 x = 0
@@ -96,11 +160,8 @@ class ResultScreen:
ray.draw_texture(self.textures['result'][175], 532, 98, ray.fade(ray.WHITE, 0.75)) ray.draw_texture(self.textures['result'][175], 532, 98, ray.fade(ray.WHITE, 0.75))
draw_scaled_texture(self.textures['result'][217], 554, 109, (10/11), ray.WHITE) if self.gauge is not None:
draw_scaled_texture(self.textures['result'][226], 554, 109, (10/11), ray.fade(ray.WHITE, 0.15)) self.gauge.draw(self.textures['result'])
draw_scaled_texture(self.textures['result'][176], 1185, 116, (10/11), ray.WHITE)
draw_scaled_texture(self.textures['result'][187], 1058, 124, (10/11), ray.WHITE)
draw_scaled_texture(self.textures['result'][188], 1182, 115, (10/11), ray.WHITE)
ray.draw_texture(self.textures['result'][170], 817, 186, ray.WHITE) ray.draw_texture(self.textures['result'][170], 817, 186, ray.WHITE)
ray.draw_texture(self.textures['result'][171], 817, 227, ray.WHITE) ray.draw_texture(self.textures['result'][171], 817, 227, ray.WHITE)
@@ -123,9 +184,12 @@ class FadeIn:
self.fadein.params['delay'] = 100 self.fadein.params['delay'] = 100
self.fade = ray.fade(ray.WHITE, self.fadein.attribute) self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
self.is_finished = False
def update(self, current_ms: float): def update(self, current_ms: float):
self.fadein.update(current_ms) self.fadein.update(current_ms)
self.fade = ray.fade(ray.WHITE, self.fadein.attribute) self.fade = ray.fade(ray.WHITE, self.fadein.attribute)
self.is_finished = self.fadein.is_finished
def draw(self, screen_width: int, screen_height: int, texture_1: ray.Texture, texture_2: ray.Texture): def draw(self, screen_width: int, screen_height: int, texture_1: ray.Texture, texture_2: ray.Texture):
x = 0 x = 0
@@ -143,9 +207,93 @@ class FontText:
def __init__(self, text, font_size): def __init__(self, text, font_size):
codepoint_count = ray.ffi.new('int *', 0) codepoint_count = ray.ffi.new('int *', 0)
codepoints_no_dup = set() codepoints_no_dup = set()
codepoints_no_dup.update(global_data.song_title) codepoints_no_dup.update(session_data.song_title)
codepoints = ray.load_codepoints(''.join(codepoints_no_dup), codepoint_count) codepoints = ray.load_codepoints(''.join(codepoints_no_dup), codepoint_count)
self.font = ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), 32, codepoints, 0) self.font = ray.load_font_ex(str(Path('Graphics/Modified-DFPKanteiryu-XB.ttf')), 32, codepoints, 0)
self.text = OutlinedText(self.font, str(text), font_size, ray.WHITE, ray.BLACK, outline_thickness=5) self.text = OutlinedText(self.font, str(text), font_size, ray.WHITE, ray.BLACK, outline_thickness=5)
self.texture = self.text.texture self.texture = self.text.texture
class ScoreAnimator:
def __init__(self, target_score):
self.target_score = str(target_score)
self.current_score_list = [[0,0] for i in range(len(self.target_score))]
self.digit_index = len(self.target_score) - 1
self.is_finished = False
def next_score(self):
if self.digit_index == -1:
self.is_finished = True
return int(''.join([str(item[0]) for item in self.current_score_list]))
curr_digit, counter = self.current_score_list[self.digit_index]
if counter < 9:
self.current_score_list[self.digit_index][1] += 1
self.current_score_list[self.digit_index][0] = (curr_digit + 1) % 10
else:
self.current_score_list[self.digit_index][0] = int(self.target_score[self.digit_index])
self.digit_index -= 1
return int(''.join([str(item[0]) for item in self.current_score_list]))
class Gauge:
def __init__(self, current_ms: float, gauge_length):
self.gauge_length = gauge_length
self.rainbow_animation = None
self.gauge_fade_in = Animation(get_current_ms(), 366, 'fade')
self.gauge_fade_in.params['initial_opacity'] = 0.0
self.gauge_fade_in.params['final_opacity'] = 1.0
self.is_finished = self.gauge_fade_in.is_finished
def _create_rainbow_anim(self, current_ms):
anim = Animation(current_ms, (16.67*8) * 3, 'texture_change')
anim.params['textures'] = []
for i in range(8):
anim.params['textures'].append(((16.67* 3)*i, (16.67 * 3)*(i+1), i))
return anim
def _create_anim(self, current_ms: float, init: float, final: float):
anim = Animation(current_ms, 450, 'fade')
anim.params['initial_opacity'] = init
anim.params['final_opacity'] = final
return anim
def update(self, current_ms: float):
if self.rainbow_animation is None:
self.rainbow_animation = self._create_rainbow_anim(current_ms)
else:
self.rainbow_animation.update(current_ms)
if self.rainbow_animation.is_finished:
self.rainbow_animation = None
self.gauge_fade_in.update(current_ms)
self.is_finished = self.gauge_fade_in.is_finished
def draw(self, textures: list[ray.Texture]):
color = ray.fade(ray.WHITE, self.gauge_fade_in.attribute)
draw_scaled_texture(textures[217], 554, 109, (10/11), color)
if self.gauge_length == 87 and self.rainbow_animation is not None:
if 0 < self.rainbow_animation.attribute < 8:
draw_scaled_texture(textures[217 + int(self.rainbow_animation.attribute)], 554, 109, (10/11), color)
draw_scaled_texture(textures[218 + int(self.rainbow_animation.attribute)], 554, 109, (10/11), color)
else:
for i in range(self.gauge_length+1):
width = int(i * 7.2)
if i == 69:
draw_scaled_texture(textures[192], 562 + width, 142 - 22, (10/11), color)
elif i > 69:
if i % 5 == 0:
draw_scaled_texture(textures[191], 561 + width, 142 - 20, (10/11), color)
draw_scaled_texture(textures[196], 561 + width, 142, (10/11), color)
draw_scaled_texture(textures[191], 562 + width, 142 - 20, (10/11), color)
draw_scaled_texture(textures[196], 562 + width, 142, (10/11), color)
else:
if i % 5 == 0:
draw_scaled_texture(textures[189], 561 + width, 142, (10/11), color)
draw_scaled_texture(textures[189], 562 + width, 142, (10/11), color)
draw_scaled_texture(textures[226], 554, 109, (10/11), ray.fade(ray.WHITE, min(0.15, self.gauge_fade_in.attribute)))
draw_scaled_texture(textures[176], 1185, 116, (10/11), color)
if self.gauge_length >= 69:
draw_scaled_texture(textures[194], 1058, 124, (10/11), color)
draw_scaled_texture(textures[195], 1182, 115, (10/11), color)
else:
draw_scaled_texture(textures[187], 1058, 124, (10/11), color)
draw_scaled_texture(textures[188], 1182, 115, (10/11), color)

View File

@@ -5,15 +5,13 @@ import pyray as ray
from libs.audio import audio from libs.audio import audio
from libs.tja import TJAParser from libs.tja import TJAParser
from libs.utils import get_config, global_data from libs.utils import get_config, session_data
class SongSelectScreen: class SongSelectScreen:
def __init__(self, width: int, height: int): def __init__(self, width: int, height: int):
self.width = width self.width = width
self.height = height self.height = height
self.is_song_select = True
self.is_difficulty_select = False
self.song_list: dict[str, list] = dict() self.song_list: dict[str, list] = dict()
self.selected_song = 0 self.selected_song = 0
self.selected_difficulty = 0 self.selected_difficulty = 0
@@ -31,15 +29,14 @@ class SongSelectScreen:
def on_screen_start(self): def on_screen_start(self):
if not self.screen_init: if not self.screen_init:
self.screen_init = True self.screen_init = True
self.is_song_select = True self.is_song_select = True
self.is_difficulty_select = False self.is_difficulty_select = False
def on_screen_end(self): def on_screen_end(self):
self.screen_init = False self.screen_init = False
audio.play_sound(self.sound_don) audio.play_sound(self.sound_don)
global_data.selected_song = list(self.song_list.keys())[self.selected_song] session_data.selected_song = list(self.song_list.keys())[self.selected_song]
global_data.selected_difficulty = self.selected_difficulty session_data.selected_difficulty = self.selected_difficulty
return "GAME" return "GAME"
def update_song_select(self): def update_song_select(self):

View File

@@ -22,20 +22,13 @@ class TitleScreen:
self.op_video_list = [str(file) for file in video_dir.glob("**/*.mp4")] self.op_video_list = [str(file) for file in video_dir.glob("**/*.mp4")]
video_dir = Path(get_config()["paths"]["video_path"]) / "attract_videos" video_dir = Path(get_config()["paths"]["video_path"]) / "attract_videos"
self.attract_video_list = [str(file) for file in video_dir.glob("**/*.mp4")] self.attract_video_list = [str(file) for file in video_dir.glob("**/*.mp4")]
self.attract_video = VideoPlayer(random.choice(self.attract_video_list)) self.load_sounds()
self.op_video = VideoPlayer(random.choice(self.op_video_list))
self.scene = 'Opening Video'
self.load_textures()
self.warning_board = WarningScreen(get_current_ms(), self)
self.screen_init = False self.screen_init = False
def get_videos(self): def get_videos(self):
return self.op_video, self.attract_video return self.op_video, self.attract_video
def load_textures(self): def load_sounds(self):
self.textures = load_all_textures_from_zip(Path('Graphics/lumendata/attract/keikoku.zip'))
sounds_dir = Path("Sounds") sounds_dir = Path("Sounds")
title_dir = sounds_dir / "title" title_dir = sounds_dir / "title"
@@ -43,14 +36,28 @@ class TitleScreen:
self.sound_bachi_hit = audio.load_sound(str(title_dir / "SE_ATTRACT_3.ogg")) self.sound_bachi_hit = audio.load_sound(str(title_dir / "SE_ATTRACT_3.ogg"))
self.sound_warning_message = audio.load_sound(str(title_dir / "VO_ATTRACT_3.ogg")) self.sound_warning_message = audio.load_sound(str(title_dir / "VO_ATTRACT_3.ogg"))
self.sound_warning_error = audio.load_sound(str(title_dir / "SE_ATTRACT_1.ogg")) self.sound_warning_error = audio.load_sound(str(title_dir / "SE_ATTRACT_1.ogg"))
self.sounds = [self.sound_bachi_swipe, self.sound_bachi_hit, self.sound_warning_message, self.sound_warning_error]
def load_textures(self):
self.textures = load_all_textures_from_zip(Path('Graphics/lumendata/attract/keikoku.zip'))
self.texture_black = load_texture_from_zip(Path('Graphics/lumendata/attract/movie.zip'), 'movie_img00000.png') self.texture_black = load_texture_from_zip(Path('Graphics/lumendata/attract/movie.zip'), 'movie_img00000.png')
def on_screen_start(self): def on_screen_start(self):
if not self.screen_init: if not self.screen_init:
self.screen_init = True self.screen_init = True
self.load_textures()
self.scene = 'Opening Video'
self.op_video = VideoPlayer(random.choice(self.op_video_list))
self.op_video._convert_frames()
self.warning_board = WarningScreen(get_current_ms(), self)
self.attract_video = VideoPlayer(random.choice(self.attract_video_list))
def on_screen_end(self) -> str: def on_screen_end(self) -> str:
self.op_video.stop()
self.attract_video.stop()
for sound in self.sounds:
if audio.is_sound_playing(sound):
audio.stop_sound(sound)
for zip in self.textures: for zip in self.textures:
for texture in self.textures[zip]: for texture in self.textures[zip]:
ray.unload_texture(texture) ray.unload_texture(texture)
@@ -60,19 +67,32 @@ class TitleScreen:
def scene_manager(self): def scene_manager(self):
if self.scene == 'Opening Video': if self.scene == 'Opening Video':
self.op_video.update() self.op_video.update()
if all(self.op_video.is_finished): self.attract_video.convert_frames_background()
if self.op_video.is_finished():
self.op_video = VideoPlayer(random.choice(self.op_video_list))
self.scene = 'Warning Board' self.scene = 'Warning Board'
self.warning_board = WarningScreen(get_current_ms(), self) self.warning_board = WarningScreen(get_current_ms(), self)
elif self.scene == 'Warning Board': elif self.scene == 'Warning Board':
self.warning_board.update(get_current_ms(), self) self.warning_board.update(get_current_ms(), self)
self.op_video.convert_frames_background()
self.attract_video.convert_frames_background()
if self.warning_board.is_finished: if self.warning_board.is_finished:
self.scene = 'Attract Video' self.scene = 'Attract Video'
self.attract_video = VideoPlayer(random.choice(self.attract_video_list)) self.attract_video.start_ms = get_current_ms()
elif self.scene == 'Attract Video': elif self.scene == 'Attract Video':
while not self.attract_video.all_frames_converted:
self.attract_video.convert_frames_background()
if self.attract_video.all_frames_converted:
self.attract_video.start_ms = get_current_ms()
self.attract_video.update() self.attract_video.update()
if all(self.attract_video.is_finished): self.op_video.convert_frames_background()
if self.attract_video.is_finished():
self.attract_video = VideoPlayer(random.choice(self.attract_video_list))
self.scene = 'Opening Video' self.scene = 'Opening Video'
self.op_video = VideoPlayer(random.choice(self.op_video_list)) self.op_video.start_ms = get_current_ms()
def update(self): def update(self):
self.on_screen_start() self.on_screen_start()

258
uv.lock generated Normal file
View File

@@ -0,0 +1,258 @@
version = 1
revision = 2
requires-python = ">=3.12"
resolution-markers = [
"sys_platform == 'darwin'",
"platform_machine == 'aarch64' and sys_platform == 'linux'",
"(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')",
]
[[package]]
name = "cffi"
version = "1.17.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" },
{ url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" },
{ url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" },
{ url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" },
{ url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" },
{ url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" },
{ url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" },
{ url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" },
{ url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" },
{ url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" },
{ url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" },
{ url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" },
{ url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" },
{ url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" },
{ url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" },
{ url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" },
{ url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" },
{ url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" },
{ url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" },
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
]
[[package]]
name = "inflection"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/7e/691d061b7329bc8d54edbf0ec22fbfb2afe61facb681f9aaa9bff7a27d04/inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", size = 15091, upload-time = "2020-08-22T08:16:29.139Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454, upload-time = "2020-08-22T08:16:27.816Z" },
]
[[package]]
name = "numpy"
version = "2.2.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633, upload-time = "2025-04-19T22:37:52.4Z" },
{ url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123, upload-time = "2025-04-19T22:38:15.058Z" },
{ url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817, upload-time = "2025-04-19T22:38:24.885Z" },
{ url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066, upload-time = "2025-04-19T22:38:35.782Z" },
{ url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277, upload-time = "2025-04-19T22:38:57.697Z" },
{ url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742, upload-time = "2025-04-19T22:39:22.689Z" },
{ url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825, upload-time = "2025-04-19T22:39:45.794Z" },
{ url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600, upload-time = "2025-04-19T22:40:13.427Z" },
{ url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626, upload-time = "2025-04-19T22:40:25.223Z" },
{ url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715, upload-time = "2025-04-19T22:40:44.528Z" },
{ url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload-time = "2025-04-19T22:41:16.234Z" },
{ url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload-time = "2025-04-19T22:41:38.472Z" },
{ url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload-time = "2025-04-19T22:41:47.823Z" },
{ url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload-time = "2025-04-19T22:41:58.689Z" },
{ url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload-time = "2025-04-19T22:42:19.897Z" },
{ url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload-time = "2025-04-19T22:42:44.433Z" },
{ url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload-time = "2025-04-19T22:43:09.928Z" },
{ url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload-time = "2025-04-19T22:43:36.983Z" },
{ url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload-time = "2025-04-19T22:47:10.523Z" },
{ url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload-time = "2025-04-19T22:47:30.253Z" },
{ url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload-time = "2025-04-19T22:44:09.251Z" },
{ url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload-time = "2025-04-19T22:44:31.383Z" },
{ url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload-time = "2025-04-19T22:44:40.361Z" },
{ url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload-time = "2025-04-19T22:44:51.188Z" },
{ url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload-time = "2025-04-19T22:45:12.451Z" },
{ url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload-time = "2025-04-19T22:45:37.734Z" },
{ url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload-time = "2025-04-19T22:46:01.908Z" },
{ url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload-time = "2025-04-19T22:46:28.585Z" },
{ url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload-time = "2025-04-19T22:46:39.949Z" },
{ url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload-time = "2025-04-19T22:47:00.147Z" },
]
[[package]]
name = "opencv-python"
version = "4.11.0.86"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322, upload-time = "2025-01-16T13:52:25.887Z" },
{ url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197, upload-time = "2025-01-16T13:55:21.222Z" },
{ url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439, upload-time = "2025-01-16T13:51:35.822Z" },
{ url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597, upload-time = "2025-01-16T13:52:08.836Z" },
{ url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337, upload-time = "2025-01-16T13:52:13.549Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" },
]
[[package]]
name = "pycparser"
version = "2.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" },
]
[[package]]
name = "pydub"
version = "0.25.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" },
]
[[package]]
name = "pytaiko"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "numpy" },
{ name = "opencv-python" },
{ name = "pydub" },
{ name = "raylib" },
{ name = "raylib-dynamic" },
{ name = "ruff" },
{ name = "scipy" },
{ name = "sounddevice" },
]
[package.metadata]
requires-dist = [
{ name = "numpy", specifier = ">=2.2.5" },
{ name = "opencv-python", specifier = ">=4.11.0.86" },
{ name = "pydub", specifier = ">=0.25.1" },
{ name = "raylib", specifier = ">=5.5.0.2" },
{ name = "raylib-dynamic", specifier = ">=5.5.0.2" },
{ name = "ruff", specifier = ">=0.11.7" },
{ name = "scipy", specifier = ">=1.15.2" },
{ name = "sounddevice", specifier = ">=0.5.1" },
]
[[package]]
name = "raylib"
version = "5.5.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/35/9bf3a2af73c55fd4310dcaec4f997c739888e0db9b4dfac71b7680810852/raylib-5.5.0.2.tar.gz", hash = "sha256:83c108ae3b4af40b53c93d1de2afbe309e986dd5efeb280ebe2e61c79956edb0", size = 181172, upload-time = "2024-11-26T11:12:02.791Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/2b/49bfa6833ad74ddf318d54ecafe73d535f583531469ecbd5b009d79667d1/raylib-5.5.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5233c529d9a0cfd469d88239c2182e55c5215a7755d83cc3d611148d3b9c9e67", size = 1706157, upload-time = "2024-11-26T11:09:43.6Z" },
{ url = "https://files.pythonhosted.org/packages/58/9c/8a3f4de0c81ad1228bf26410cfe3ecdc73011c59f18e542685ffc92c0120/raylib-5.5.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1f76204ffbc492722b571b12dbdc0dca89b10da76ddf48c12a3968d2db061dff", size = 1248027, upload-time = "2025-01-04T20:21:46.269Z" },
{ url = "https://files.pythonhosted.org/packages/7f/16/63baf1aae94832b9f5d15cafcee67bb6dd07a20cf64d40bac09903b79274/raylib-5.5.0.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f8cc2e39f1d6b29211a97ec0ac818a5b04c43a40e747e4b4622101d48c711f9e", size = 2195374, upload-time = "2024-11-26T11:09:46.114Z" },
{ url = "https://files.pythonhosted.org/packages/70/bd/61a006b4e3ce4a6ca974cb0ceeb19f3816815ebabac650e9bf82767e65f6/raylib-5.5.0.2-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:f12da578a28da7f48481f46323e5aab8dd25461982b0e80d045782d6e69649f5", size = 2299593, upload-time = "2024-11-26T11:09:48.963Z" },
{ url = "https://files.pythonhosted.org/packages/f4/4f/59d554cc495bea8235b17cebfc76ed57aaa602c613b870159e31282fd4c1/raylib-5.5.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:b40234bbad9523fd6a2049640c76a98b4d6f0b8f4bd19bd33eaee55faf5e050d", size = 1696780, upload-time = "2024-11-26T11:09:50.787Z" },
{ url = "https://files.pythonhosted.org/packages/ba/0a/78edc3ed1e2ca7e2ccea31ac5a8f4440a924662ee1042ecb76e08f465d8a/raylib-5.5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2bfa9419a12eeb259f99c75c351db167968306295a5017cd4735241eaf2fa399", size = 1706104, upload-time = "2024-11-26T11:09:53.571Z" },
{ url = "https://files.pythonhosted.org/packages/48/fb/8b79a03c0d63bd6613d3e25df26d6c42fe36689f052abefa00a311be53fc/raylib-5.5.0.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:216456d0fa8da85c253a8596ca4b35c995bbf8af0fb8fa5244a4c6f5debfe294", size = 1248018, upload-time = "2024-11-26T11:09:56.257Z" },
{ url = "https://files.pythonhosted.org/packages/57/42/8ef2a8e21a2b4ba7ac4ea13b1dd9bc5d1084020ddca06ad0c6582c784a4e/raylib-5.5.0.2-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:4f1c33b7d2404e70dbf3dd8d71701efb24902adfcb553122bf3d9441ea4fd6f0", size = 2299336, upload-time = "2024-11-26T11:09:59.307Z" },
{ url = "https://files.pythonhosted.org/packages/b4/67/4c25526fde4dabf5cc60093202f8d07a7dc4b1bd29d7b84db55b443a527c/raylib-5.5.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:6b60c97a9f6cfaeef4f59ff162ea29f02c18282d129edebb62f132b2ad6bb935", size = 1696790, upload-time = "2024-11-26T11:10:01.812Z" },
]
[[package]]
name = "raylib-dynamic"
version = "5.5.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
{ name = "inflection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/74/ee15f73ad900bc0ea013852626998f78ba1eec020f470bf9a6873f3725b4/raylib_dynamic-5.5.0.2.tar.gz", hash = "sha256:125c95e51754c56fc045052b22642e2d53839cb1aac9db9df99355744daae868", size = 3172948, upload-time = "2024-11-26T11:12:04.91Z" }
[[package]]
name = "ruff"
version = "0.11.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5b/89/6f9c9674818ac2e9cc2f2b35b704b7768656e6b7c139064fc7ba8fbc99f1/ruff-0.11.7.tar.gz", hash = "sha256:655089ad3224070736dc32844fde783454f8558e71f501cb207485fe4eee23d4", size = 4054861, upload-time = "2025-04-24T18:49:37.007Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b4/ec/21927cb906c5614b786d1621dba405e3d44f6e473872e6df5d1a6bca0455/ruff-0.11.7-py3-none-linux_armv6l.whl", hash = "sha256:d29e909d9a8d02f928d72ab7837b5cbc450a5bdf578ab9ebee3263d0a525091c", size = 10245403, upload-time = "2025-04-24T18:48:40.459Z" },
{ url = "https://files.pythonhosted.org/packages/e2/af/fec85b6c2c725bcb062a354dd7cbc1eed53c33ff3aa665165871c9c16ddf/ruff-0.11.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dd1fb86b168ae349fb01dd497d83537b2c5541fe0626e70c786427dd8363aaee", size = 11007166, upload-time = "2025-04-24T18:48:44.742Z" },
{ url = "https://files.pythonhosted.org/packages/31/9a/2d0d260a58e81f388800343a45898fd8df73c608b8261c370058b675319a/ruff-0.11.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d3d7d2e140a6fbbc09033bce65bd7ea29d6a0adeb90b8430262fbacd58c38ada", size = 10378076, upload-time = "2025-04-24T18:48:47.918Z" },
{ url = "https://files.pythonhosted.org/packages/c2/c4/9b09b45051404d2e7dd6d9dbcbabaa5ab0093f9febcae664876a77b9ad53/ruff-0.11.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4809df77de390a1c2077d9b7945d82f44b95d19ceccf0c287c56e4dc9b91ca64", size = 10557138, upload-time = "2025-04-24T18:48:51.707Z" },
{ url = "https://files.pythonhosted.org/packages/5e/5e/f62a1b6669870a591ed7db771c332fabb30f83c967f376b05e7c91bccd14/ruff-0.11.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3a0c2e169e6b545f8e2dba185eabbd9db4f08880032e75aa0e285a6d3f48201", size = 10095726, upload-time = "2025-04-24T18:48:54.243Z" },
{ url = "https://files.pythonhosted.org/packages/45/59/a7aa8e716f4cbe07c3500a391e58c52caf665bb242bf8be42c62adef649c/ruff-0.11.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49b888200a320dd96a68e86736cf531d6afba03e4f6cf098401406a257fcf3d6", size = 11672265, upload-time = "2025-04-24T18:48:57.639Z" },
{ url = "https://files.pythonhosted.org/packages/dd/e3/101a8b707481f37aca5f0fcc3e42932fa38b51add87bfbd8e41ab14adb24/ruff-0.11.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2b19cdb9cf7dae00d5ee2e7c013540cdc3b31c4f281f1dacb5a799d610e90db4", size = 12331418, upload-time = "2025-04-24T18:49:00.697Z" },
{ url = "https://files.pythonhosted.org/packages/dd/71/037f76cbe712f5cbc7b852e4916cd3cf32301a30351818d32ab71580d1c0/ruff-0.11.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64e0ee994c9e326b43539d133a36a455dbaab477bc84fe7bfbd528abe2f05c1e", size = 11794506, upload-time = "2025-04-24T18:49:03.545Z" },
{ url = "https://files.pythonhosted.org/packages/ca/de/e450b6bab1fc60ef263ef8fcda077fb4977601184877dce1c59109356084/ruff-0.11.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bad82052311479a5865f52c76ecee5d468a58ba44fb23ee15079f17dd4c8fd63", size = 13939084, upload-time = "2025-04-24T18:49:07.159Z" },
{ url = "https://files.pythonhosted.org/packages/0e/2c/1e364cc92970075d7d04c69c928430b23e43a433f044474f57e425cbed37/ruff-0.11.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7940665e74e7b65d427b82bffc1e46710ec7f30d58b4b2d5016e3f0321436502", size = 11450441, upload-time = "2025-04-24T18:49:11.41Z" },
{ url = "https://files.pythonhosted.org/packages/9d/7d/1b048eb460517ff9accd78bca0fa6ae61df2b276010538e586f834f5e402/ruff-0.11.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:169027e31c52c0e36c44ae9a9c7db35e505fee0b39f8d9fca7274a6305295a92", size = 10441060, upload-time = "2025-04-24T18:49:14.184Z" },
{ url = "https://files.pythonhosted.org/packages/3a/57/8dc6ccfd8380e5ca3d13ff7591e8ba46a3b330323515a4996b991b10bd5d/ruff-0.11.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:305b93f9798aee582e91e34437810439acb28b5fc1fee6b8205c78c806845a94", size = 10058689, upload-time = "2025-04-24T18:49:17.559Z" },
{ url = "https://files.pythonhosted.org/packages/23/bf/20487561ed72654147817885559ba2aa705272d8b5dee7654d3ef2dbf912/ruff-0.11.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a681db041ef55550c371f9cd52a3cf17a0da4c75d6bd691092dfc38170ebc4b6", size = 11073703, upload-time = "2025-04-24T18:49:20.247Z" },
{ url = "https://files.pythonhosted.org/packages/9d/27/04f2db95f4ef73dccedd0c21daf9991cc3b7f29901a4362057b132075aa4/ruff-0.11.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:07f1496ad00a4a139f4de220b0c97da6d4c85e0e4aa9b2624167b7d4d44fd6b6", size = 11532822, upload-time = "2025-04-24T18:49:23.765Z" },
{ url = "https://files.pythonhosted.org/packages/e1/72/43b123e4db52144c8add336581de52185097545981ff6e9e58a21861c250/ruff-0.11.7-py3-none-win32.whl", hash = "sha256:f25dfb853ad217e6e5f1924ae8a5b3f6709051a13e9dad18690de6c8ff299e26", size = 10362436, upload-time = "2025-04-24T18:49:27.377Z" },
{ url = "https://files.pythonhosted.org/packages/c5/a0/3e58cd76fdee53d5c8ce7a56d84540833f924ccdf2c7d657cb009e604d82/ruff-0.11.7-py3-none-win_amd64.whl", hash = "sha256:0a931d85959ceb77e92aea4bbedfded0a31534ce191252721128f77e5ae1f98a", size = 11566676, upload-time = "2025-04-24T18:49:30.938Z" },
{ url = "https://files.pythonhosted.org/packages/68/ca/69d7c7752bce162d1516e5592b1cc6b6668e9328c0d270609ddbeeadd7cf/ruff-0.11.7-py3-none-win_arm64.whl", hash = "sha256:778c1e5d6f9e91034142dfd06110534ca13220bfaad5c3735f6cb844654f6177", size = 10677936, upload-time = "2025-04-24T18:49:34.392Z" },
]
[[package]]
name = "scipy"
version = "1.15.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b7/b9/31ba9cd990e626574baf93fbc1ac61cf9ed54faafd04c479117517661637/scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec", size = 59417316, upload-time = "2025-02-17T00:42:24.791Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4b/5d/3c78815cbab499610f26b5bae6aed33e227225a9fa5290008a733a64f6fc/scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd", size = 38756184, upload-time = "2025-02-17T00:31:50.623Z" },
{ url = "https://files.pythonhosted.org/packages/37/20/3d04eb066b471b6e171827548b9ddb3c21c6bbea72a4d84fc5989933910b/scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301", size = 30163558, upload-time = "2025-02-17T00:31:56.721Z" },
{ url = "https://files.pythonhosted.org/packages/a4/98/e5c964526c929ef1f795d4c343b2ff98634ad2051bd2bbadfef9e772e413/scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93", size = 22437211, upload-time = "2025-02-17T00:32:03.042Z" },
{ url = "https://files.pythonhosted.org/packages/1d/cd/1dc7371e29195ecbf5222f9afeedb210e0a75057d8afbd942aa6cf8c8eca/scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20", size = 25232260, upload-time = "2025-02-17T00:32:07.847Z" },
{ url = "https://files.pythonhosted.org/packages/f0/24/1a181a9e5050090e0b5138c5f496fee33293c342b788d02586bc410c6477/scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e", size = 35198095, upload-time = "2025-02-17T00:32:14.565Z" },
{ url = "https://files.pythonhosted.org/packages/c0/53/eaada1a414c026673eb983f8b4a55fe5eb172725d33d62c1b21f63ff6ca4/scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8", size = 37297371, upload-time = "2025-02-17T00:32:21.411Z" },
{ url = "https://files.pythonhosted.org/packages/e9/06/0449b744892ed22b7e7b9a1994a866e64895363572677a316a9042af1fe5/scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11", size = 36872390, upload-time = "2025-02-17T00:32:29.421Z" },
{ url = "https://files.pythonhosted.org/packages/6a/6f/a8ac3cfd9505ec695c1bc35edc034d13afbd2fc1882a7c6b473e280397bb/scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53", size = 39700276, upload-time = "2025-02-17T00:32:37.431Z" },
{ url = "https://files.pythonhosted.org/packages/f5/6f/e6e5aff77ea2a48dd96808bb51d7450875af154ee7cbe72188afb0b37929/scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded", size = 40942317, upload-time = "2025-02-17T00:32:45.47Z" },
{ url = "https://files.pythonhosted.org/packages/53/40/09319f6e0f276ea2754196185f95cd191cb852288440ce035d5c3a931ea2/scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf", size = 38717587, upload-time = "2025-02-17T00:32:53.196Z" },
{ url = "https://files.pythonhosted.org/packages/fe/c3/2854f40ecd19585d65afaef601e5e1f8dbf6758b2f95b5ea93d38655a2c6/scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37", size = 30100266, upload-time = "2025-02-17T00:32:59.318Z" },
{ url = "https://files.pythonhosted.org/packages/dd/b1/f9fe6e3c828cb5930b5fe74cb479de5f3d66d682fa8adb77249acaf545b8/scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d", size = 22373768, upload-time = "2025-02-17T00:33:04.091Z" },
{ url = "https://files.pythonhosted.org/packages/15/9d/a60db8c795700414c3f681908a2b911e031e024d93214f2d23c6dae174ab/scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb", size = 25154719, upload-time = "2025-02-17T00:33:08.909Z" },
{ url = "https://files.pythonhosted.org/packages/37/3b/9bda92a85cd93f19f9ed90ade84aa1e51657e29988317fabdd44544f1dd4/scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27", size = 35163195, upload-time = "2025-02-17T00:33:15.352Z" },
{ url = "https://files.pythonhosted.org/packages/03/5a/fc34bf1aa14dc7c0e701691fa8685f3faec80e57d816615e3625f28feb43/scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0", size = 37255404, upload-time = "2025-02-17T00:33:22.21Z" },
{ url = "https://files.pythonhosted.org/packages/4a/71/472eac45440cee134c8a180dbe4c01b3ec247e0338b7c759e6cd71f199a7/scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32", size = 36860011, upload-time = "2025-02-17T00:33:29.446Z" },
{ url = "https://files.pythonhosted.org/packages/01/b3/21f890f4f42daf20e4d3aaa18182dddb9192771cd47445aaae2e318f6738/scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d", size = 39657406, upload-time = "2025-02-17T00:33:39.019Z" },
{ url = "https://files.pythonhosted.org/packages/0d/76/77cf2ac1f2a9cc00c073d49e1e16244e389dd88e2490c91d84e1e3e4d126/scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f", size = 40961243, upload-time = "2025-02-17T00:34:51.024Z" },
{ url = "https://files.pythonhosted.org/packages/4c/4b/a57f8ddcf48e129e6054fa9899a2a86d1fc6b07a0e15c7eebff7ca94533f/scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9", size = 38870286, upload-time = "2025-02-17T00:33:47.62Z" },
{ url = "https://files.pythonhosted.org/packages/0c/43/c304d69a56c91ad5f188c0714f6a97b9c1fed93128c691148621274a3a68/scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f", size = 30141634, upload-time = "2025-02-17T00:33:54.131Z" },
{ url = "https://files.pythonhosted.org/packages/44/1a/6c21b45d2548eb73be9b9bff421aaaa7e85e22c1f9b3bc44b23485dfce0a/scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6", size = 22415179, upload-time = "2025-02-17T00:33:59.948Z" },
{ url = "https://files.pythonhosted.org/packages/74/4b/aefac4bba80ef815b64f55da06f62f92be5d03b467f2ce3668071799429a/scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af", size = 25126412, upload-time = "2025-02-17T00:34:06.328Z" },
{ url = "https://files.pythonhosted.org/packages/b1/53/1cbb148e6e8f1660aacd9f0a9dfa2b05e9ff1cb54b4386fe868477972ac2/scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274", size = 34952867, upload-time = "2025-02-17T00:34:12.928Z" },
{ url = "https://files.pythonhosted.org/packages/2c/23/e0eb7f31a9c13cf2dca083828b97992dd22f8184c6ce4fec5deec0c81fcf/scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776", size = 36890009, upload-time = "2025-02-17T00:34:19.55Z" },
{ url = "https://files.pythonhosted.org/packages/03/f3/e699e19cabe96bbac5189c04aaa970718f0105cff03d458dc5e2b6bd1e8c/scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828", size = 36545159, upload-time = "2025-02-17T00:34:26.724Z" },
{ url = "https://files.pythonhosted.org/packages/af/f5/ab3838e56fe5cc22383d6fcf2336e48c8fe33e944b9037fbf6cbdf5a11f8/scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28", size = 39136566, upload-time = "2025-02-17T00:34:34.512Z" },
{ url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705, upload-time = "2025-02-17T00:34:43.619Z" },
]
[[package]]
name = "sounddevice"
version = "0.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/2d/b04ae180312b81dbb694504bee170eada5372242e186f6298139fd3a0513/sounddevice-0.5.1.tar.gz", hash = "sha256:09ca991daeda8ce4be9ac91e15a9a81c8f81efa6b695a348c9171ea0c16cb041", size = 52896, upload-time = "2024-10-12T09:40:12.24Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/06/d1/464b5fca3decdd0cfec8c47f7b4161a0b12972453201c1bf03811f367c5e/sounddevice-0.5.1-py3-none-any.whl", hash = "sha256:e2017f182888c3f3c280d9fbac92e5dbddac024a7e3442f6e6116bd79dab8a9c", size = 32276, upload-time = "2024-10-12T09:40:05.605Z" },
{ url = "https://files.pythonhosted.org/packages/6f/f6/6703fe7cf3d7b7279040c792aeec6334e7305956aba4a80f23e62c8fdc44/sounddevice-0.5.1-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:d16cb23d92322526a86a9490c427bf8d49e273d9ccc0bd096feecd229cde6031", size = 107916, upload-time = "2024-10-12T09:40:07.436Z" },
{ url = "https://files.pythonhosted.org/packages/57/a5/78a5e71f5ec0faedc54f4053775d61407bfbd7d0c18228c7f3d4252fd276/sounddevice-0.5.1-py3-none-win32.whl", hash = "sha256:d84cc6231526e7a08e89beff229c37f762baefe5e0cc2747cbe8e3a565470055", size = 312494, upload-time = "2024-10-12T09:40:09.355Z" },
{ url = "https://files.pythonhosted.org/packages/af/9b/15217b04f3b36d30de55fef542389d722de63f1ad81f9c72d8afc98cb6ab/sounddevice-0.5.1-py3-none-win_amd64.whl", hash = "sha256:4313b63f2076552b23ac3e0abd3bcfc0c1c6a696fc356759a13bd113c9df90f1", size = 363634, upload-time = "2024-10-12T09:40:11.065Z" },
]