mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 19:50:12 +01:00
update to add every missing feature ever
This commit is contained in:
2
.github/workflows/python-app.yml
vendored
2
.github/workflows/python-app.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
script-name: PyTaiko.py
|
||||
mode: app
|
||||
output-dir: .
|
||||
include-module: raylib,moviepy,numpy,sounddevice
|
||||
include-module: raylib,moviepy,numpy,sounddevice,tomlkit
|
||||
include-package: imageio_ffmpeg
|
||||
noinclude-setuptools-mode: nofollow
|
||||
noinclude-IPython-mode: nofollow
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ pytaiko.build
|
||||
pytaiko.dist
|
||||
pytaiko.onefile-build
|
||||
pytaiko.exe
|
||||
full.csv
|
||||
|
||||
29
PyTaiko.py
29
PyTaiko.py
@@ -2,12 +2,20 @@ import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
import pyray as ray
|
||||
from raylib.defines import (
|
||||
RL_FUNC_ADD,
|
||||
RL_ONE,
|
||||
RL_ONE_MINUS_SRC_ALPHA,
|
||||
RL_SRC_ALPHA,
|
||||
)
|
||||
|
||||
from libs import song_hash
|
||||
from libs.audio import audio
|
||||
from libs.utils import get_config, global_data, load_all_textures_from_zip
|
||||
from scenes.entry import EntryScreen
|
||||
from scenes.game import GameScreen
|
||||
from scenes.result import ResultScreen
|
||||
from scenes.settings import SettingsScreen
|
||||
from scenes.song_select import SongSelectScreen
|
||||
from scenes.title import TitleScreen
|
||||
|
||||
@@ -18,6 +26,7 @@ class Screens:
|
||||
SONG_SELECT = "SONG_SELECT"
|
||||
GAME = "GAME"
|
||||
RESULT = "RESULT"
|
||||
SETTINGS = "SETTINGS"
|
||||
|
||||
def create_song_db():
|
||||
with sqlite3.connect('scores.db') as con:
|
||||
@@ -33,7 +42,8 @@ def create_song_db():
|
||||
ok INTEGER,
|
||||
bad INTEGER,
|
||||
drumroll INTEGER,
|
||||
combo INTEGER
|
||||
combo INTEGER,
|
||||
clear INTEGER
|
||||
);
|
||||
'''
|
||||
cursor.execute(create_table_query)
|
||||
@@ -42,6 +52,7 @@ def create_song_db():
|
||||
|
||||
def main():
|
||||
create_song_db()
|
||||
song_hash.song_hashes = song_hash.build_song_hashes()
|
||||
screen_width: int = get_config()["video"]["screen_width"]
|
||||
screen_height: int = get_config()["video"]["screen_height"]
|
||||
render_width, render_height = ray.get_render_width(), ray.get_render_height()
|
||||
@@ -57,12 +68,12 @@ def main():
|
||||
ray.set_config_flags(ray.ConfigFlags.FLAG_MSAA_4X_HINT)
|
||||
ray.set_trace_log_level(ray.TraceLogLevel.LOG_ERROR)
|
||||
|
||||
ray.set_window_max_size(screen_width, screen_height)
|
||||
ray.set_window_min_size(screen_width, screen_height)
|
||||
#ray.set_window_max_size(screen_width, screen_height)
|
||||
#ray.set_window_min_size(screen_width, screen_height)
|
||||
ray.init_window(screen_width, screen_height, "PyTaiko")
|
||||
if get_config()["video"]["borderless"]:
|
||||
ray.toggle_borderless_windowed()
|
||||
ray.clear_window_state(ray.ConfigFlags.FLAG_WINDOW_TOPMOST)
|
||||
#ray.clear_window_state(ray.ConfigFlags.FLAG_WINDOW_TOPMOST)
|
||||
if get_config()["video"]["fullscreen"]:
|
||||
ray.maximize_window()
|
||||
|
||||
@@ -76,20 +87,20 @@ def main():
|
||||
song_select_screen = SongSelectScreen(screen_width, screen_height)
|
||||
game_screen = GameScreen(screen_width, screen_height)
|
||||
result_screen = ResultScreen(screen_width, screen_height)
|
||||
settings_screen = SettingsScreen(screen_width, screen_height)
|
||||
|
||||
screen_mapping = {
|
||||
Screens.ENTRY: entry_screen,
|
||||
Screens.TITLE: title_screen,
|
||||
Screens.SONG_SELECT: song_select_screen,
|
||||
Screens.GAME: game_screen,
|
||||
Screens.RESULT: result_screen
|
||||
Screens.RESULT: result_screen,
|
||||
Screens.SETTINGS: settings_screen
|
||||
}
|
||||
target = ray.load_render_texture(screen_width, screen_height)
|
||||
ray.set_texture_filter(target.texture, ray.TextureFilter.TEXTURE_FILTER_TRILINEAR)
|
||||
ray.gen_texture_mipmaps(target.texture)
|
||||
#lmaooooooooooooo
|
||||
#rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD)
|
||||
ray.rl_set_blend_factors_separate(0x302, 0x303, 1, 0x303, 0x8006, 0x8006)
|
||||
ray.rl_set_blend_factors_separate(RL_SRC_ALPHA, RL_ONE_MINUS_SRC_ALPHA, RL_ONE, RL_ONE_MINUS_SRC_ALPHA, RL_FUNC_ADD, RL_FUNC_ADD)
|
||||
ray.set_exit_key(ray.KeyboardKey.KEY_A)
|
||||
global_data.textures = load_all_textures_from_zip(Path('Graphics/lumendata/intermission.zip'))
|
||||
while not ray.window_should_close():
|
||||
@@ -118,7 +129,7 @@ def main():
|
||||
ray.draw_texture_pro(
|
||||
target.texture,
|
||||
ray.Rectangle(0, 0, target.texture.width, -target.texture.height),
|
||||
ray.Rectangle(0, 0, dpi_scale[0], dpi_scale[1]),
|
||||
ray.Rectangle(0, 0, screen_width, screen_height),
|
||||
ray.Vector2(0,0),
|
||||
0,
|
||||
ray.WHITE
|
||||
|
||||
15
config.toml
15
config.toml
@@ -1,12 +1,13 @@
|
||||
[general]
|
||||
fps_counter = false
|
||||
judge_offset = 0
|
||||
autoplay = true
|
||||
visual_offset = 0
|
||||
autoplay = false
|
||||
sfx = true
|
||||
language = 'ja'
|
||||
language = "ja"
|
||||
|
||||
[paths]
|
||||
tja_path = ['E:/Taiko/ESE', 'Songs', 'E:/Taiko/VersionSort']
|
||||
tja_path = ['Songs']
|
||||
video_path = 'Videos'
|
||||
|
||||
[keybinds]
|
||||
@@ -16,9 +17,9 @@ right_don = ['J']
|
||||
right_kat = ['K']
|
||||
|
||||
[audio]
|
||||
device_type = 'ASIO'
|
||||
buffer_size = 6
|
||||
sample_rate = 48000
|
||||
device_type = "Windows WASAPI"
|
||||
buffer_size = 22
|
||||
sample_rate = 44100
|
||||
exclusive = false
|
||||
|
||||
[video]
|
||||
@@ -26,4 +27,4 @@ screen_width = 1280
|
||||
screen_height = 720
|
||||
fullscreen = false
|
||||
borderless = false
|
||||
vsync = false
|
||||
vsync = true
|
||||
|
||||
@@ -1,40 +1,48 @@
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from libs.tja import TJAParser
|
||||
from libs.utils import get_config
|
||||
|
||||
song_hashes: Optional[dict] = None
|
||||
|
||||
|
||||
def process_tja_file(tja_file):
|
||||
"""Process a single TJA file and return hash or None if error"""
|
||||
tja = TJAParser(tja_file)
|
||||
all_notes = []
|
||||
for diff in tja.metadata.course_data:
|
||||
all_notes.extend(TJAParser.notes_to_position(TJAParser(tja.file_path), diff))
|
||||
all_notes.extend(
|
||||
TJAParser.notes_to_position(TJAParser(tja.file_path), diff)
|
||||
)
|
||||
hash = tja.hash_note_data(all_notes[0], all_notes[2])
|
||||
return hash
|
||||
|
||||
def build_song_hashes(output_file='cache/song_hashes.json'):
|
||||
|
||||
def build_song_hashes(output_file="cache/song_hashes.json"):
|
||||
existing_hashes = {}
|
||||
output_path = Path(output_file)
|
||||
if output_path.exists():
|
||||
try:
|
||||
with open(output_file, 'r', encoding='utf-8') as f:
|
||||
with open(output_file, "r", encoding="utf-8") as f:
|
||||
existing_hashes = json.load(f)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
print(f"Warning: Could not load existing hashes from {output_file}: {e}")
|
||||
print(
|
||||
f"Warning: Could not load existing hashes from {output_file}: {e}"
|
||||
)
|
||||
existing_hashes = {}
|
||||
|
||||
song_hashes = existing_hashes.copy()
|
||||
tja_paths = get_config()['paths']['tja_path']
|
||||
tja_paths = get_config()["paths"]["tja_path"]
|
||||
all_tja_files = []
|
||||
|
||||
for root_dir in tja_paths:
|
||||
root_path = Path(root_dir)
|
||||
all_tja_files.extend(root_path.rglob('*.tja'))
|
||||
all_tja_files.extend(root_path.rglob("*.tja"))
|
||||
|
||||
updated_count = 0
|
||||
for tja_file in all_tja_files:
|
||||
@@ -45,14 +53,14 @@ def build_song_hashes(output_file='cache/song_hashes.json'):
|
||||
|
||||
existing_hash = None
|
||||
for h, data in song_hashes.items():
|
||||
if data['file_path'] == str(tja_file):
|
||||
if data["file_path"] == str(tja_file):
|
||||
existing_hash = h
|
||||
break
|
||||
|
||||
if existing_hash is None:
|
||||
should_update = True
|
||||
else:
|
||||
stored_modified = song_hashes[existing_hash].get('last_modified', 0)
|
||||
stored_modified = song_hashes[existing_hash].get("last_modified", 0)
|
||||
if current_modified > stored_modified:
|
||||
should_update = True
|
||||
del song_hashes[existing_hash]
|
||||
@@ -61,17 +69,19 @@ def build_song_hashes(output_file='cache/song_hashes.json'):
|
||||
tja = TJAParser(tja_file)
|
||||
all_notes = []
|
||||
for diff in tja.metadata.course_data:
|
||||
all_notes.extend(TJAParser.notes_to_position(TJAParser(tja.file_path), diff))
|
||||
all_notes.extend(
|
||||
TJAParser.notes_to_position(TJAParser(tja.file_path), diff)
|
||||
)
|
||||
hash_val = tja.hash_note_data(all_notes[0], all_notes[2])
|
||||
song_hashes[hash_val] = {
|
||||
'file_path': str(tja_file),
|
||||
'last_modified': current_modified,
|
||||
'title': tja.metadata.title,
|
||||
'subtitle': tja.metadata.subtitle
|
||||
"file_path": str(tja_file),
|
||||
"last_modified": current_modified,
|
||||
"title": tja.metadata.title,
|
||||
"subtitle": tja.metadata.subtitle,
|
||||
}
|
||||
updated_count += 1
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(song_hashes, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"Song hashes saved to {output_file}. Updated {updated_count} files.")
|
||||
@@ -80,29 +90,84 @@ def build_song_hashes(output_file='cache/song_hashes.json'):
|
||||
|
||||
def get_japanese_songs_for_version(df, version_column):
|
||||
# Filter rows where the specified version column has 'YES'
|
||||
version_songs = df[df[version_column] == 'YES']
|
||||
version_songs = df[df[version_column] != "NO"]
|
||||
|
||||
# Extract Japanese titles (JPTITLE column)
|
||||
japanese_titles = version_songs['TITLE 【TITLE2】\nJPTITLE/「TITLE2」 より'].tolist()
|
||||
japanese_titles = version_songs[
|
||||
"TITLE 【TITLE2】\nJPTITLE/「TITLE2」 より"
|
||||
].tolist()
|
||||
|
||||
japanese_titles = [name.split('\n') for name in japanese_titles]
|
||||
second_lines = [name[1] for name in japanese_titles if len(name) > 1]
|
||||
japanese_titles = [name.split("\n") for name in japanese_titles]
|
||||
second_lines = [
|
||||
name[1] if len(name) > 1 else name[0] for name in japanese_titles
|
||||
]
|
||||
|
||||
all_tja_files = []
|
||||
direct_tja_paths = dict()
|
||||
text_files = dict()
|
||||
tja_paths = get_config()['paths']['tja_path']
|
||||
tja_paths = get_config()["paths"]["tja_path"]
|
||||
for root_dir in tja_paths:
|
||||
root_path = Path(root_dir)
|
||||
all_tja_files.extend(root_path.rglob('*.tja'))
|
||||
all_tja_files.extend(root_path.rglob("*.tja"))
|
||||
for tja in all_tja_files:
|
||||
tja_parse = TJAParser(tja)
|
||||
direct_tja_paths[tja_parse.metadata.title.get('ja', tja_parse.metadata.title['en'])] = tja
|
||||
tja_name = tja_parse.metadata.title.get(
|
||||
"ja", tja_parse.metadata.title["en"]
|
||||
)
|
||||
if "【双打】" in tja_name:
|
||||
tja_name = tja_name.strip("【双打】")
|
||||
tja_name = tja_name.strip()
|
||||
if tja_name in direct_tja_paths:
|
||||
direct_tja_paths[tja_name].append(tja)
|
||||
else:
|
||||
direct_tja_paths[tja_name] = [tja]
|
||||
for title in second_lines:
|
||||
if "・・・" in title:
|
||||
title = title.replace("・・・", "…")
|
||||
if "..." in title:
|
||||
title = title.replace("・・・", "…")
|
||||
|
||||
# Find all matching keys
|
||||
matches = []
|
||||
|
||||
# Check for exact title match
|
||||
if title in direct_tja_paths:
|
||||
path = direct_tja_paths[title]
|
||||
elif title.split('/')[0] in direct_tja_paths:
|
||||
path = direct_tja_paths[title.split('/')[0]]
|
||||
for path in direct_tja_paths[title]:
|
||||
matches.append((title, path))
|
||||
|
||||
# Also check for partial matches with the first part before '/'
|
||||
title_prefix = title.split("/")[0]
|
||||
for key in direct_tja_paths:
|
||||
if key.startswith(title_prefix) and key != title:
|
||||
for path in direct_tja_paths[key]:
|
||||
matches.append((key, path))
|
||||
|
||||
if not matches:
|
||||
for key in direct_tja_paths:
|
||||
if title.lower() in key.lower() or key.lower() in title.lower():
|
||||
for path in direct_tja_paths[key]:
|
||||
matches.append((key, path))
|
||||
|
||||
if not matches:
|
||||
from difflib import get_close_matches
|
||||
|
||||
close_matches = get_close_matches(
|
||||
title, direct_tja_paths.keys(), n=3, cutoff=0.6
|
||||
)
|
||||
for close_match in close_matches:
|
||||
for path in direct_tja_paths[close_match]:
|
||||
matches.append((close_match, path))
|
||||
|
||||
if len(matches) == 1:
|
||||
path = matches[0][1]
|
||||
elif len(matches) > 1:
|
||||
print(
|
||||
f"Multiple matches found for '{title.split('/')[0]} ({title.split('/')[1] if len(title.split('/')) > 1 else ''})':"
|
||||
)
|
||||
for i, (key, path_val) in enumerate(matches, 1):
|
||||
print(f"{i}. {key}: {path_val}")
|
||||
choice = int(input("Choose number: ")) - 1
|
||||
path = matches[choice][1]
|
||||
else:
|
||||
path = Path(input(f"NOT FOUND {title}: "))
|
||||
hash = process_tja_file(path)
|
||||
@@ -110,15 +175,24 @@ def get_japanese_songs_for_version(df, version_column):
|
||||
genre = Path(path).parent.parent.name
|
||||
if genre not in text_files:
|
||||
text_files[genre] = []
|
||||
text_files[genre].append(f"{hash}|{tja_parse.metadata.title['en'].strip()}|{tja_parse.metadata.subtitle['en'].strip()}")
|
||||
text_files[genre].append(
|
||||
f"{hash}|{tja_parse.metadata.title['en'].strip()}|{tja_parse.metadata.subtitle['en'].strip()}"
|
||||
)
|
||||
print(f"Added {title}: {path}")
|
||||
for genre in text_files:
|
||||
if not Path(version_column).exists():
|
||||
Path(version_column).mkdir()
|
||||
if not Path(f"{version_column}/{genre}").exists():
|
||||
Path(f"{version_column}/{genre}").mkdir()
|
||||
with open(Path(f"{version_column}/{genre}/song_list.txt"), 'w', encoding='utf-8-sig') as text_file:
|
||||
with open(
|
||||
Path(f"{version_column}/{genre}/song_list.txt"),
|
||||
"w",
|
||||
encoding="utf-8-sig",
|
||||
) as text_file:
|
||||
for item in text_files[genre]:
|
||||
text_file.write(item + '\n')
|
||||
text_file.write(item + "\n")
|
||||
return text_files
|
||||
|
||||
get_japanese_songs_for_version(pd.read_csv('full.csv'), 'AC12')
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
get_japanese_songs_for_version(pd.read_csv("full.csv"), sys.argv[1])
|
||||
|
||||
112
libs/tja.py
112
libs/tja.py
@@ -1,12 +1,25 @@
|
||||
import bisect
|
||||
import hashlib
|
||||
import math
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field, fields
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from libs.utils import get_pixels_per_frame, strip_comments
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def get_ms_per_measure(bpm_val, time_sig):
|
||||
#https://gist.github.com/KatieFrogs/e000f406bbc70a12f3c34a07303eec8b#measure
|
||||
if bpm_val == 0:
|
||||
return 0
|
||||
return 60000 * (time_sig * 4) / bpm_val
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def get_pixels_per_ms(pixels_per_frame):
|
||||
return pixels_per_frame / (1000 / 60)
|
||||
|
||||
@dataclass()
|
||||
class Note:
|
||||
type: int = field(init=False)
|
||||
@@ -150,6 +163,7 @@ def calculate_base_score(play_note_list: deque[Note | Drumroll | Balloon]) -> in
|
||||
return math.ceil(total_score / 10) * 10
|
||||
|
||||
class TJAParser:
|
||||
DIFFS = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"}
|
||||
def __init__(self, path: Path, start_delay: int = 0, distance: int = 866):
|
||||
self.file_path: Path = path
|
||||
|
||||
@@ -260,59 +274,54 @@ class TJAParser:
|
||||
self.ex_data.limited_time = True
|
||||
|
||||
def data_to_notes(self, diff):
|
||||
note_start = -1
|
||||
note_end = -1
|
||||
diff_name = self.DIFFS.get(diff, "").lower()
|
||||
|
||||
# Use enumerate for single iteration
|
||||
note_start = note_end = -1
|
||||
target_found = False
|
||||
diffs = {0: "easy", 1: "normal", 2: "hard", 3: "oni", 4: "edit", 5: "tower", 6: "dan"}
|
||||
# Get the name corresponding to this difficulty number
|
||||
diff_name = diffs.get(diff, "").lower()
|
||||
|
||||
i = 0
|
||||
while i < len(self.data):
|
||||
line = self.data[i]
|
||||
|
||||
# Check if this is the start of a difficulty section
|
||||
# Find the section boundaries
|
||||
for i, line in enumerate(self.data):
|
||||
if line.startswith("COURSE:"):
|
||||
course_value = line[7:].strip().lower()
|
||||
|
||||
# Match either the exact number or the name
|
||||
if (course_value.isdigit() and int(course_value) == diff) or course_value == diff_name:
|
||||
target_found = True
|
||||
else:
|
||||
target_found = False
|
||||
|
||||
# If we found our target section, look for START and END markers
|
||||
if target_found:
|
||||
if line == "#START":
|
||||
target_found = (course_value.isdigit() and int(course_value) == diff) or course_value == diff_name
|
||||
elif target_found:
|
||||
if note_start == -1 and line in ("#START", "#START P1"):
|
||||
note_start = i + 1
|
||||
elif line == "#END" and note_start != -1:
|
||||
note_end = i
|
||||
break # We found our complete section
|
||||
break
|
||||
|
||||
i += 1
|
||||
if note_start == -1 or note_end == -1:
|
||||
return []
|
||||
|
||||
# Process the section with minimal string operations
|
||||
notes = []
|
||||
bar = []
|
||||
#Check for measures and separate when comma exists
|
||||
for i in range(note_start, note_end):
|
||||
line = self.data[i]
|
||||
section_data = self.data[note_start:note_end]
|
||||
|
||||
for line in section_data:
|
||||
if line.startswith("#"):
|
||||
bar.append(line)
|
||||
elif line == ',':
|
||||
if not bar or all(item.startswith('#') for item in bar):
|
||||
bar.append('')
|
||||
notes.append(bar)
|
||||
bar = []
|
||||
else:
|
||||
if line == ',':
|
||||
if len(bar) == 0 or all(item.startswith('#') for item in bar):
|
||||
bar.append('')
|
||||
if line.endswith(','):
|
||||
bar.append(line[:-1])
|
||||
notes.append(bar)
|
||||
bar = []
|
||||
else:
|
||||
item = line.strip(',')
|
||||
bar.append(item)
|
||||
if item != line:
|
||||
notes.append(bar)
|
||||
bar = []
|
||||
bar.append(line)
|
||||
|
||||
if bar: # Add remaining items
|
||||
notes.append(bar)
|
||||
|
||||
return notes
|
||||
|
||||
def get_moji(self, play_note_list: deque[Note], ms_per_measure: float) -> None:
|
||||
def get_moji(self, play_note_list: list[Note], ms_per_measure: float) -> None:
|
||||
se_notes = {
|
||||
1: [0, 1, 2], # Note '1' has three possible sound effects
|
||||
2: [3, 4], # Note '2' has two possible sound effects
|
||||
@@ -373,9 +382,9 @@ class TJAParser:
|
||||
play_note_list[-3].moji = se_notes[play_note_list[-3].moji][2]
|
||||
|
||||
def notes_to_position(self, diff: int):
|
||||
play_note_list: deque[Note | Drumroll | Balloon] = deque()
|
||||
bar_list: deque[Note] = deque()
|
||||
draw_note_list: deque[Note | Drumroll | Balloon] = deque()
|
||||
play_note_list: list[Note | Drumroll | Balloon] = []
|
||||
draw_note_list: list[Note | Drumroll | Balloon] = []
|
||||
bar_list: list[Note] = []
|
||||
notes = self.data_to_notes(diff)
|
||||
balloon = self.metadata.course_data[diff].balloon.copy()
|
||||
count = 0
|
||||
@@ -431,18 +440,14 @@ class TJAParser:
|
||||
if skip_branch:
|
||||
continue
|
||||
|
||||
if bpm == 0:
|
||||
ms_per_measure = 0
|
||||
else:
|
||||
#https://gist.github.com/KatieFrogs/e000f406bbc70a12f3c34a07303eec8b#measure
|
||||
ms_per_measure = 60000 * (time_signature*4) / bpm
|
||||
ms_per_measure = get_ms_per_measure(bpm, time_signature)
|
||||
|
||||
#Create note object
|
||||
bar_line = Note()
|
||||
|
||||
#Determines how quickly the notes need to move across the screen to reach the judgment circle in time
|
||||
bar_line.pixels_per_frame = get_pixels_per_frame(bpm * time_signature * scroll_modifier, time_signature*4, self.distance)
|
||||
pixels_per_ms = bar_line.pixels_per_frame / (1000 / 60)
|
||||
pixels_per_ms = get_pixels_per_ms(bar_line.pixels_per_frame)
|
||||
|
||||
bar_line.hit_ms = self.current_ms
|
||||
if pixels_per_ms == 0:
|
||||
@@ -455,7 +460,7 @@ class TJAParser:
|
||||
if barline_added:
|
||||
bar_line.display = False
|
||||
|
||||
bar_list.append(bar_line)
|
||||
bisect.insort(bar_list, bar_line, key=lambda x: x.load_ms)
|
||||
barline_added = True
|
||||
|
||||
#Empty bar is still a bar, otherwise start increment
|
||||
@@ -471,12 +476,11 @@ class TJAParser:
|
||||
continue
|
||||
note = Note()
|
||||
note.hit_ms = self.current_ms
|
||||
if pixels_per_ms == 0:
|
||||
note.load_ms = note.hit_ms
|
||||
else:
|
||||
note.load_ms = note.hit_ms - (self.distance / pixels_per_ms)
|
||||
note.type = int(item)
|
||||
note.pixels_per_frame = bar_line.pixels_per_frame
|
||||
pixels_per_ms = get_pixels_per_ms(note.pixels_per_frame)
|
||||
note.load_ms = (note.hit_ms if pixels_per_ms == 0
|
||||
else note.hit_ms - (self.distance / pixels_per_ms))
|
||||
note.type = int(item)
|
||||
note.index = index
|
||||
note.bpm = bpm
|
||||
note.gogo_time = gogo_time
|
||||
@@ -489,10 +493,7 @@ class TJAParser:
|
||||
if balloon is None:
|
||||
raise Exception("Balloon note found, but no count was specified")
|
||||
note = Balloon(note)
|
||||
if not balloon:
|
||||
note.count = 1
|
||||
else:
|
||||
note.count = balloon.pop(0)
|
||||
note.count = 1 if not balloon else balloon.pop(0)
|
||||
elif item == '8':
|
||||
new_pixels_per_ms = play_note_list[-1].pixels_per_frame / (1000 / 60)
|
||||
if new_pixels_per_ms == 0:
|
||||
@@ -502,6 +503,7 @@ class TJAParser:
|
||||
note.pixels_per_frame = play_note_list[-1].pixels_per_frame
|
||||
self.current_ms += increment
|
||||
play_note_list.append(note)
|
||||
bisect.insort(draw_note_list, note, key=lambda x: x.load_ms)
|
||||
self.get_moji(play_note_list, ms_per_measure)
|
||||
index += 1
|
||||
if len(play_note_list) > 3:
|
||||
@@ -513,9 +515,7 @@ class TJAParser:
|
||||
# Sorting by load_ms is necessary for drawing, as some notes appear on the
|
||||
# screen slower regardless of when they reach the judge circle
|
||||
# Bars can be sorted like this because they don't need hit detection
|
||||
draw_note_list = deque(sorted(play_note_list, key=lambda n: n.load_ms))
|
||||
bar_list = deque(sorted(bar_list, key=lambda b: b.load_ms))
|
||||
return play_note_list, draw_note_list, bar_list
|
||||
return deque(play_note_list), deque(draw_note_list), deque(bar_list)
|
||||
|
||||
def hash_note_data(self, play_notes: deque[Note | Drumroll | Balloon], bars: deque[Note]):
|
||||
n = hashlib.sha256()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import hashlib
|
||||
import math
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import tomllib
|
||||
import tomlkit
|
||||
import zipfile
|
||||
from dataclasses import dataclass, field
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -85,6 +87,7 @@ def strip_comments(code: str) -> str:
|
||||
index += 1
|
||||
return result
|
||||
|
||||
@lru_cache
|
||||
def get_pixels_per_frame(bpm: float, time_signature: float, distance: float) -> float:
|
||||
if bpm == 0:
|
||||
return 0
|
||||
@@ -94,10 +97,94 @@ def get_pixels_per_frame(bpm: float, time_signature: float, distance: float) ->
|
||||
return (distance / total_frames)
|
||||
|
||||
def get_config() -> dict[str, Any]:
|
||||
with open('config.toml', "rb") as f:
|
||||
config_file = tomllib.load(f)
|
||||
with open('config.toml', "r", encoding="utf-8") as f:
|
||||
config_file = tomlkit.load(f)
|
||||
return config_file
|
||||
|
||||
def save_config(config: dict[str, Any]) -> None:
|
||||
with open('config.toml', "w", encoding="utf-8") as f:
|
||||
tomlkit.dump(config, f)
|
||||
|
||||
def is_l_don_pressed() -> bool:
|
||||
keys = get_config()["keybinds"]["left_don"]
|
||||
for key in keys:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
return True
|
||||
|
||||
if ray.is_gamepad_available(0):
|
||||
if ray.is_gamepad_button_pressed(0, 16):
|
||||
return True
|
||||
|
||||
mid_x, mid_y = (1280//2, 720)
|
||||
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
|
||||
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
|
||||
for i in range(min(ray.get_touch_point_count(), 10)):
|
||||
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
|
||||
if math.dist(tap_pos, (mid_x, mid_y)) < 300 and tap_pos[0] <= mid_x:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_r_don_pressed() -> bool:
|
||||
keys = get_config()["keybinds"]["right_don"]
|
||||
for key in keys:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
return True
|
||||
|
||||
if ray.is_gamepad_available(0):
|
||||
if ray.is_gamepad_button_pressed(0, 17):
|
||||
return True
|
||||
|
||||
mid_x, mid_y = (1280//2, 720)
|
||||
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
|
||||
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
|
||||
for i in range(min(ray.get_touch_point_count(), 10)):
|
||||
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
|
||||
if math.dist(tap_pos, (mid_x, mid_y)) < 300 and tap_pos[0] > mid_x:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_l_kat_pressed() -> bool:
|
||||
keys = get_config()["keybinds"]["left_kat"]
|
||||
for key in keys:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
return True
|
||||
|
||||
if ray.is_gamepad_available(0):
|
||||
if ray.is_gamepad_button_pressed(0, 10):
|
||||
return True
|
||||
|
||||
mid_x, mid_y = (1280//2, 720)
|
||||
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
|
||||
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
|
||||
for i in range(min(ray.get_touch_point_count(), 10)):
|
||||
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
|
||||
if math.dist(tap_pos, (mid_x, mid_y)) >= 300 and tap_pos[0] <= mid_x:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_r_kat_pressed() -> bool:
|
||||
keys = get_config()["keybinds"]["right_kat"]
|
||||
for key in keys:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
return True
|
||||
|
||||
if ray.is_gamepad_available(0):
|
||||
if ray.is_gamepad_button_pressed(0, 12):
|
||||
return True
|
||||
|
||||
mid_x, mid_y = (1280//2, 720)
|
||||
allowed_gestures = {ray.Gesture.GESTURE_TAP, ray.Gesture.GESTURE_DOUBLETAP}
|
||||
if ray.get_gesture_detected() in allowed_gestures and ray.is_gesture_detected(ray.get_gesture_detected()):
|
||||
for i in range(min(ray.get_touch_point_count(), 10)):
|
||||
tap_pos = (ray.get_touch_position(i).x, ray.get_touch_position(i).y)
|
||||
if math.dist(tap_pos, (mid_x, mid_y)) >= 300 and tap_pos[0] > mid_x:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def draw_scaled_texture(texture: ray.Texture, x: int, y: int, scale: float, color: ray.Color) -> None:
|
||||
src_rect = ray.Rectangle(0, 0, texture.width, texture.height)
|
||||
dst_rect = ray.Rectangle(x, y, texture.width*scale, texture.height*scale)
|
||||
|
||||
@@ -8,11 +8,13 @@ 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'",
|
||||
"moviepy>=2.1.2",
|
||||
"raylib-sdl>=5.5.0.2",
|
||||
"soundfile>=0.13.1",
|
||||
"pandas>=2.3.0",
|
||||
"tomlkit>=0.13.3",
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
||||
|
||||
import pyray as ray
|
||||
|
||||
from libs.utils import get_config, load_texture_from_zip
|
||||
from libs.utils import is_l_don_pressed, is_r_don_pressed, load_texture_from_zip
|
||||
|
||||
|
||||
class EntryScreen:
|
||||
@@ -18,16 +18,16 @@ class EntryScreen:
|
||||
if not self.screen_init:
|
||||
self.screen_init = True
|
||||
|
||||
def on_screen_end(self):
|
||||
def on_screen_end(self, next_screen: str):
|
||||
self.screen_init = False
|
||||
return "SONG_SELECT"
|
||||
return next_screen
|
||||
|
||||
def update(self):
|
||||
self.on_screen_start()
|
||||
keys = get_config()["keybinds"]["left_don"] + get_config()["keybinds"]["right_don"]
|
||||
for key in keys:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
return self.on_screen_end()
|
||||
if is_l_don_pressed() or is_r_don_pressed():
|
||||
return self.on_screen_end("SONG_SELECT")
|
||||
if ray.is_key_pressed(ray.KeyboardKey.KEY_F1):
|
||||
return self.on_screen_end("SETTINGS")
|
||||
|
||||
def draw(self):
|
||||
ray.draw_texture(self.texture_footer, 0, self.height - 151, ray.WHITE)
|
||||
|
||||
108
scenes/game.py
108
scenes/game.py
@@ -15,6 +15,10 @@ from libs.utils import (
|
||||
get_config,
|
||||
get_current_ms,
|
||||
global_data,
|
||||
is_l_don_pressed,
|
||||
is_l_kat_pressed,
|
||||
is_r_don_pressed,
|
||||
is_r_kat_pressed,
|
||||
load_all_textures_from_zip,
|
||||
load_image_from_zip,
|
||||
load_texture_from_zip,
|
||||
@@ -38,6 +42,7 @@ class GameScreen:
|
||||
self.end_ms = 0
|
||||
self.start_delay = 1000
|
||||
self.song_started = False
|
||||
self.prev_touch_count = 0
|
||||
|
||||
self.background = Background(width, height)
|
||||
|
||||
@@ -150,14 +155,14 @@ class GameScreen:
|
||||
result = cursor.fetchone()
|
||||
if result is None or session_data.result_score > result[0]:
|
||||
insert_query = '''
|
||||
INSERT OR REPLACE INTO Scores (hash, en_name, jp_name, diff, score, good, ok, bad, drumroll, combo)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
INSERT OR REPLACE INTO Scores (hash, en_name, jp_name, diff, score, good, ok, bad, drumroll, combo, clear)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
'''
|
||||
data = (hash, self.tja.metadata.title['en'],
|
||||
self.tja.metadata.title['ja'], self.player_1.difficulty,
|
||||
self.tja.metadata.title.get('ja', ''), self.player_1.difficulty,
|
||||
session_data.result_score, session_data.result_good,
|
||||
session_data.result_ok, session_data.result_bad,
|
||||
session_data.result_total_drumroll, session_data.result_max_combo)
|
||||
session_data.result_total_drumroll, session_data.result_max_combo, int(self.player_1.gauge.gauge_length > self.player_1.gauge.clear_start[min(self.player_1.difficulty, 3)]))
|
||||
cursor.execute(insert_query, data)
|
||||
con.commit()
|
||||
|
||||
@@ -222,6 +227,7 @@ class Player:
|
||||
|
||||
self.player_number = player_number
|
||||
self.difficulty = difficulty
|
||||
self.visual_offset = get_config()["general"]["visual_offset"]
|
||||
|
||||
self.play_notes, 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_notes if 0 < note.type < 5])
|
||||
@@ -259,7 +265,7 @@ class Player:
|
||||
|
||||
self.input_log: dict[float, tuple] = dict()
|
||||
|
||||
self.gauge = Gauge(self.difficulty, game_screen.tja.metadata.course_data[self.difficulty].level)
|
||||
self.gauge = Gauge(self.difficulty, game_screen.tja.metadata.course_data[self.difficulty].level, self.total_notes)
|
||||
self.gauge_hit_effect: list[GaugeHitEffect] = []
|
||||
|
||||
self.autoplay_hit_side = 'L'
|
||||
@@ -269,7 +275,7 @@ class Player:
|
||||
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:
|
||||
return int(game_screen.width + pixels_per_frame * 60 / 1000 * (ms - game_screen.current_ms) - 64)
|
||||
return int(game_screen.width + pixels_per_frame * 60 / 1000 * (ms - game_screen.current_ms) - 64) - self.visual_offset
|
||||
|
||||
def animation_manager(self, animation_list: list):
|
||||
if len(animation_list) <= 0:
|
||||
@@ -305,6 +311,7 @@ class Player:
|
||||
if 0 < note.type <= 4:
|
||||
self.combo = 0
|
||||
self.bad_count += 1
|
||||
self.gauge.add_bad()
|
||||
self.play_notes.popleft()
|
||||
elif note.type != 8:
|
||||
tail = self.play_notes[1]
|
||||
@@ -437,6 +444,7 @@ class Player:
|
||||
self.score += self.base_score
|
||||
self.base_score_list.append(ScoreCounterAnimation(self.base_score))
|
||||
self.note_correct(game_screen, curr_note)
|
||||
self.gauge.add_good()
|
||||
|
||||
elif (curr_note.hit_ms - Player.TIMING_OK) <= game_screen.current_ms <= (curr_note.hit_ms + Player.TIMING_OK):
|
||||
self.draw_judge_list.append(Judgement('OK', big, ms_display=game_screen.current_ms - curr_note.hit_ms))
|
||||
@@ -444,12 +452,14 @@ class Player:
|
||||
self.score += 10 * math.floor(self.base_score / 2 / 10)
|
||||
self.base_score_list.append(ScoreCounterAnimation(10 * math.floor(self.base_score / 2 / 10)))
|
||||
self.note_correct(game_screen, curr_note)
|
||||
self.gauge.add_ok()
|
||||
|
||||
elif (curr_note.hit_ms - Player.TIMING_BAD) <= game_screen.current_ms <= (curr_note.hit_ms + Player.TIMING_BAD):
|
||||
self.draw_judge_list.append(Judgement('BAD', big, ms_display=game_screen.current_ms - curr_note.hit_ms))
|
||||
self.bad_count += 1
|
||||
self.combo = 0
|
||||
self.play_notes.popleft()
|
||||
self.gauge.add_bad()
|
||||
|
||||
def drumroll_counter_manager(self, game_screen: GameScreen):
|
||||
if self.is_drumroll and self.curr_drumroll_count > 0 and self.drumroll_counter is None:
|
||||
@@ -467,26 +477,23 @@ class Player:
|
||||
if self.balloon_anim.is_finished:
|
||||
self.balloon_anim = None
|
||||
|
||||
def key_manager(self, game_screen: GameScreen):
|
||||
key_configs = [
|
||||
{"keys": get_config()["keybinds"]["left_don"], "type": "DON", "side": "L", "note_type": 1},
|
||||
{"keys": get_config()["keybinds"]["right_don"], "type": "DON", "side": "R", "note_type": 1},
|
||||
{"keys": get_config()["keybinds"]["left_kat"], "type": "KAT", "side": "L", "note_type": 2},
|
||||
{"keys": get_config()["keybinds"]["right_kat"], "type": "KAT", "side": "R", "note_type": 2}
|
||||
]
|
||||
for config in key_configs:
|
||||
for key in config["keys"]:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
hit_type = config["type"]
|
||||
self.lane_hit_effect = LaneHitEffect(hit_type)
|
||||
self.draw_drum_hit_list.append(DrumHitEffect(hit_type, config["side"]))
|
||||
def handle_input(self, game_screen: GameScreen):
|
||||
input_checks = [
|
||||
(is_l_don_pressed, 'DON', 'L', game_screen.sound_don),
|
||||
(is_r_don_pressed, 'DON', 'R', game_screen.sound_don),
|
||||
(is_l_kat_pressed, 'KAT', 'L', game_screen.sound_kat),
|
||||
(is_r_kat_pressed, 'KAT', 'R', game_screen.sound_kat)
|
||||
]
|
||||
for check_func, note_type, side, sound in input_checks:
|
||||
if check_func():
|
||||
self.lane_hit_effect = LaneHitEffect(note_type)
|
||||
self.draw_drum_hit_list.append(DrumHitEffect(note_type, side))
|
||||
|
||||
sound = game_screen.sound_don if hit_type == "DON" else game_screen.sound_kat
|
||||
if get_config()["general"]["sfx"]:
|
||||
audio.play_sound(sound)
|
||||
if get_config()["general"]["sfx"]:
|
||||
audio.play_sound(sound)
|
||||
|
||||
self.check_note(game_screen, config["note_type"])
|
||||
self.input_log[game_screen.current_ms] = (hit_type, key)
|
||||
self.check_note(game_screen, 1 if note_type == 'DON' else 2)
|
||||
self.input_log[game_screen.current_ms] = (note_type, side)
|
||||
|
||||
def autoplay_manager(self, game_screen: GameScreen):
|
||||
if not get_config()["general"]["autoplay"]:
|
||||
@@ -558,7 +565,7 @@ class Player:
|
||||
self.animation_manager(self.base_score_list)
|
||||
self.score_counter.update(get_current_ms(), self.score)
|
||||
self.autoplay_manager(game_screen)
|
||||
self.key_manager(game_screen)
|
||||
self.handle_input(game_screen)
|
||||
|
||||
self.gauge.update(get_current_ms(), self.good_count, self.ok_count, self.bad_count, self.total_notes)
|
||||
|
||||
@@ -686,6 +693,7 @@ class Player:
|
||||
self.score_counter.draw(game_screen)
|
||||
for anim in self.base_score_list:
|
||||
anim.draw(game_screen)
|
||||
#ray.draw_circle(game_screen.width//2, game_screen.height, 300, ray.ORANGE)
|
||||
|
||||
class Judgement:
|
||||
def __init__(self, type: str, big: bool, ms_display: Optional[float]=None):
|
||||
@@ -1232,8 +1240,10 @@ class ResultTransition:
|
||||
x += texture_2.width
|
||||
|
||||
class Gauge:
|
||||
def __init__(self, difficulty: int, level: int):
|
||||
def __init__(self, difficulty: int, level: int, total_notes: int):
|
||||
self.gauge_length = 0
|
||||
self.previous_length = 0
|
||||
self.total_notes = total_notes
|
||||
self.difficulty = min(3, difficulty)
|
||||
self.clear_start = [0, 0, 68, 68]
|
||||
self.level = min(10, level)
|
||||
@@ -1285,16 +1295,29 @@ class Gauge:
|
||||
self.rainbow_fade_in = None
|
||||
self.rainbow_animation = None
|
||||
|
||||
def add_good(self):
|
||||
self.gauge_update_anim = Animation.create_fade(450)
|
||||
self.previous_length = int(self.gauge_length)
|
||||
self.gauge_length += (1 / self.total_notes) * (100 * (self.clear_start[self.difficulty] / self.table[self.difficulty][self.level]["clear_rate"]))
|
||||
if self.gauge_length > 87:
|
||||
self.gauge_length = 87
|
||||
|
||||
def add_ok(self):
|
||||
self.gauge_update_anim = Animation.create_fade(450)
|
||||
self.previous_length = int(self.gauge_length)
|
||||
self.gauge_length += ((1 * self.table[self.difficulty][self.level]["ok_multiplier"]) / self.total_notes) * (100 * (self.clear_start[self.difficulty] / self.table[self.difficulty][self.level]["clear_rate"]))
|
||||
if self.gauge_length > 87:
|
||||
self.gauge_length = 87
|
||||
|
||||
def add_bad(self):
|
||||
self.previous_length = int(self.gauge_length)
|
||||
self.gauge_length += ((1 * self.table[self.difficulty][self.level]["bad_multiplier"]) / self.total_notes) * (100 * (self.clear_start[self.difficulty] / self.table[self.difficulty][self.level]["clear_rate"]))
|
||||
if self.gauge_length < 0:
|
||||
self.gauge_length = 0
|
||||
|
||||
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 = Animation.create_fade(450, initial_opacity=0.0, final_opacity=1.0)
|
||||
if self.gauge_length > previous_length:
|
||||
self.gauge_update_anim = Animation.create_fade(450)
|
||||
|
||||
if self.gauge_update_anim is not None:
|
||||
self.gauge_update_anim.update(current_ms)
|
||||
@@ -1314,12 +1337,13 @@ class Gauge:
|
||||
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:
|
||||
gauge_length = int(self.gauge_length)
|
||||
if 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):
|
||||
for i in range(gauge_length):
|
||||
if i == 68:
|
||||
ray.draw_texture(textures[16], 491 + (i*textures[13].width), 160 - 24, ray.WHITE)
|
||||
elif i > 68:
|
||||
@@ -1327,15 +1351,15 @@ class Gauge:
|
||||
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))
|
||||
if self.gauge_update_anim is not None and gauge_length < 88 and gauge_length != self.previous_length:
|
||||
if gauge_length == 69:
|
||||
ray.draw_texture(textures[17], 491 + (gauge_length*textures[13].width) - 13, 160 - 8 - 24, ray.fade(ray.WHITE, self.gauge_update_anim.attribute))
|
||||
elif gauge_length > 69:
|
||||
ray.draw_texture(textures[21], 491 + (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[14], 491 + (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:
|
||||
if gauge_length >= 69:
|
||||
ray.draw_texture(textures[18], 1038, 141, ray.WHITE)
|
||||
ray.draw_texture(textures[19], 1187, 130, ray.WHITE)
|
||||
else:
|
||||
|
||||
@@ -8,9 +8,10 @@ from libs.audio import audio
|
||||
from libs.utils import (
|
||||
OutlinedText,
|
||||
draw_scaled_texture,
|
||||
get_config,
|
||||
get_current_ms,
|
||||
global_data,
|
||||
is_l_don_pressed,
|
||||
is_r_don_pressed,
|
||||
load_all_textures_from_zip,
|
||||
session_data,
|
||||
)
|
||||
@@ -60,6 +61,7 @@ class ResultScreen:
|
||||
['max_combo', session_data.result_max_combo]]
|
||||
self.update_index = 0
|
||||
self.is_skipped = False
|
||||
self.start_ms = get_current_ms()
|
||||
|
||||
def on_screen_end(self):
|
||||
self.screen_init = False
|
||||
@@ -94,6 +96,14 @@ class ResultScreen:
|
||||
self.score_animator = ScoreAnimator(self.update_list[self.update_index][1])
|
||||
self.score_delay += 16.67 * 3
|
||||
|
||||
def handle_input(self):
|
||||
if is_r_don_pressed() or is_l_don_pressed():
|
||||
if not self.is_skipped:
|
||||
self.is_skipped = True
|
||||
else:
|
||||
if self.fade_out is None:
|
||||
self.fade_out = FadeOut()
|
||||
audio.play_sound(self.sound_don)
|
||||
|
||||
def update(self):
|
||||
self.on_screen_start()
|
||||
@@ -108,16 +118,9 @@ class ResultScreen:
|
||||
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
|
||||
else:
|
||||
if self.fade_out is None:
|
||||
self.fade_out = FadeOut()
|
||||
audio.play_sound(self.sound_don)
|
||||
if get_current_ms() >= self.start_ms + 5000:
|
||||
self.handle_input()
|
||||
|
||||
self.update_score_animation(self.is_skipped)
|
||||
|
||||
if self.fade_out is not None:
|
||||
@@ -263,12 +266,13 @@ class Gauge:
|
||||
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:
|
||||
gauge_length = int(self.gauge_length)
|
||||
if 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):
|
||||
for i in range(gauge_length+1):
|
||||
width = int(i * 7.2)
|
||||
if i == 69:
|
||||
draw_scaled_texture(textures[192], 562 + width, 142 - 22, (10/11), color)
|
||||
@@ -285,7 +289,7 @@ class Gauge:
|
||||
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:
|
||||
if gauge_length >= 69:
|
||||
draw_scaled_texture(textures[194], 1058, 124, (10/11), color)
|
||||
draw_scaled_texture(textures[195], 1182, 115, (10/11), color)
|
||||
else:
|
||||
|
||||
235
scenes/settings.py
Normal file
235
scenes/settings.py
Normal file
@@ -0,0 +1,235 @@
|
||||
import pyray as ray
|
||||
import sounddevice as sd
|
||||
|
||||
from libs.utils import (
|
||||
get_config,
|
||||
is_l_don_pressed,
|
||||
is_l_kat_pressed,
|
||||
is_r_don_pressed,
|
||||
is_r_kat_pressed,
|
||||
save_config,
|
||||
)
|
||||
|
||||
|
||||
class SettingsScreen:
|
||||
def __init__(self, width: int, height: int):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.screen_init = False
|
||||
self.config = get_config()
|
||||
self.headers = list(self.config.keys())
|
||||
self.headers.append('Exit')
|
||||
self.header_index = 0
|
||||
self.setting_index = 0
|
||||
self.in_setting_edit = False
|
||||
self.editing_key = False
|
||||
self.temp_key_input = ""
|
||||
|
||||
def on_screen_start(self):
|
||||
if not self.screen_init:
|
||||
self.screen_init = True
|
||||
|
||||
def on_screen_end(self):
|
||||
self.screen_init = False
|
||||
save_config(self.config)
|
||||
return "ENTRY"
|
||||
|
||||
def get_current_settings(self):
|
||||
"""Get the current section's settings as a list"""
|
||||
current_header = self.headers[self.header_index]
|
||||
if current_header == 'Exit' or current_header not in self.config:
|
||||
return []
|
||||
return list(self.config[current_header].items())
|
||||
|
||||
def handle_boolean_toggle(self, section, key):
|
||||
"""Toggle boolean values"""
|
||||
self.config[section][key] = not self.config[section][key]
|
||||
|
||||
def handle_numeric_change(self, section, key, increment):
|
||||
"""Handle numeric value changes"""
|
||||
current_value = self.config[section][key]
|
||||
|
||||
# Define step sizes for different settings
|
||||
step_sizes = {
|
||||
'judge_offset': 1,
|
||||
'visual_offset': 1,
|
||||
'buffer_size': 1,
|
||||
'sample_rate': 1000,
|
||||
}
|
||||
|
||||
step = step_sizes.get(key, 1)
|
||||
new_value = current_value + (step * increment)
|
||||
|
||||
# Apply constraints for specific settings
|
||||
if key == 'judge_offset':
|
||||
new_value = max(-50, min(50, new_value))
|
||||
elif key == 'visual_offset':
|
||||
new_value = max(-20, min(20, new_value))
|
||||
elif key == 'buffer_size':
|
||||
new_value = max(1, min(32, new_value))
|
||||
elif key == 'sample_rate':
|
||||
valid_rates = [22050, 44100, 48000, 88200, 96000]
|
||||
current_idx = valid_rates.index(current_value) if current_value in valid_rates else 2
|
||||
new_idx = max(0, min(len(valid_rates) - 1, current_idx + increment))
|
||||
new_value = valid_rates[new_idx]
|
||||
|
||||
self.config[section][key] = new_value
|
||||
|
||||
def handle_string_cycle(self, section, key):
|
||||
"""Cycle through predefined string values"""
|
||||
current_value = self.config[section][key]
|
||||
|
||||
hostapis = sd.query_hostapis()
|
||||
audio_devices = []
|
||||
if isinstance(hostapis, tuple):
|
||||
for api in hostapis:
|
||||
if isinstance(api, dict):
|
||||
audio_devices.append(api['name'])
|
||||
|
||||
options = {
|
||||
'language': ['ja', 'en'],
|
||||
'device_type': audio_devices
|
||||
}
|
||||
|
||||
if key in options:
|
||||
values = options[key]
|
||||
try:
|
||||
current_idx = values.index(current_value)
|
||||
new_idx = (current_idx + 1) % len(values)
|
||||
self.config[section][key] = values[new_idx]
|
||||
except ValueError:
|
||||
self.config[section][key] = values[0]
|
||||
|
||||
def handle_key_binding(self, section, key):
|
||||
"""Handle key binding changes"""
|
||||
self.editing_key = True
|
||||
self.temp_key_input = ""
|
||||
|
||||
def update_key_binding(self):
|
||||
"""Update key binding based on input"""
|
||||
key_pressed = ray.get_key_pressed()
|
||||
if key_pressed != 0:
|
||||
# Convert keycode to character
|
||||
if 65 <= key_pressed <= 90: # A-Z
|
||||
new_key = chr(key_pressed)
|
||||
current_header = self.headers[self.header_index]
|
||||
settings = self.get_current_settings()
|
||||
if settings:
|
||||
setting_key, _ = settings[self.setting_index]
|
||||
self.config[current_header][setting_key] = [new_key]
|
||||
self.editing_key = False
|
||||
elif key_pressed == ray.KeyboardKey.KEY_ESCAPE:
|
||||
self.editing_key = False
|
||||
|
||||
def update(self):
|
||||
self.on_screen_start()
|
||||
|
||||
# Handle key binding editing
|
||||
if self.editing_key:
|
||||
self.update_key_binding()
|
||||
return
|
||||
|
||||
current_header = self.headers[self.header_index]
|
||||
|
||||
# Exit handling
|
||||
if current_header == 'Exit' and (is_l_don_pressed() or is_r_don_pressed()):
|
||||
return self.on_screen_end()
|
||||
|
||||
# Navigation between sections
|
||||
if not self.in_setting_edit:
|
||||
if is_r_kat_pressed():
|
||||
self.header_index = (self.header_index + 1) % len(self.headers)
|
||||
self.setting_index = 0
|
||||
elif is_l_kat_pressed():
|
||||
self.header_index = (self.header_index - 1) % len(self.headers)
|
||||
self.setting_index = 0
|
||||
elif (is_l_don_pressed() or is_r_don_pressed()) and current_header != 'Exit':
|
||||
self.in_setting_edit = True
|
||||
else:
|
||||
# Navigation within settings
|
||||
settings = self.get_current_settings()
|
||||
if not settings:
|
||||
self.in_setting_edit = False
|
||||
return
|
||||
|
||||
if is_r_kat_pressed():
|
||||
self.setting_index = (self.setting_index + 1) % len(settings)
|
||||
elif is_l_kat_pressed():
|
||||
self.setting_index = (self.setting_index - 1) % len(settings)
|
||||
elif is_r_don_pressed():
|
||||
# Modify setting value
|
||||
setting_key, setting_value = settings[self.setting_index]
|
||||
|
||||
if isinstance(setting_value, bool):
|
||||
self.handle_boolean_toggle(current_header, setting_key)
|
||||
elif isinstance(setting_value, (int, float)):
|
||||
self.handle_numeric_change(current_header, setting_key, 1)
|
||||
elif isinstance(setting_value, str):
|
||||
if 'keybinds' in current_header:
|
||||
self.handle_key_binding(current_header, setting_key)
|
||||
else:
|
||||
self.handle_string_cycle(current_header, setting_key)
|
||||
elif isinstance(setting_value, list) and len(setting_value) > 0:
|
||||
if isinstance(setting_value[0], str) and len(setting_value[0]) == 1:
|
||||
# Key binding
|
||||
self.handle_key_binding(current_header, setting_key)
|
||||
elif is_l_don_pressed():
|
||||
# Modify setting value (reverse direction for numeric)
|
||||
setting_key, setting_value = settings[self.setting_index]
|
||||
|
||||
if isinstance(setting_value, bool):
|
||||
self.handle_boolean_toggle(current_header, setting_key)
|
||||
elif isinstance(setting_value, (int, float)):
|
||||
self.handle_numeric_change(current_header, setting_key, -1)
|
||||
elif isinstance(setting_value, str):
|
||||
if 'keybinds' not in current_header:
|
||||
self.handle_string_cycle(current_header, setting_key)
|
||||
|
||||
elif ray.is_key_pressed(ray.KeyboardKey.KEY_ESCAPE):
|
||||
self.in_setting_edit = False
|
||||
|
||||
def draw(self):
|
||||
# Draw title
|
||||
ray.draw_text("SETTINGS", 20, 20, 30, ray.WHITE)
|
||||
|
||||
# Draw section headers
|
||||
current_header = self.headers[self.header_index]
|
||||
for i, key in enumerate(self.headers):
|
||||
color = ray.GREEN
|
||||
if key == current_header:
|
||||
color = ray.YELLOW if not self.in_setting_edit else ray.ORANGE
|
||||
ray.draw_text(f'{key}', 20, i*25 + 70, 20, color)
|
||||
|
||||
# Draw current section settings
|
||||
if current_header != 'Exit' and current_header in self.config:
|
||||
settings = self.get_current_settings()
|
||||
|
||||
# Draw settings list
|
||||
for i, (key, value) in enumerate(settings):
|
||||
color = ray.GREEN
|
||||
if self.in_setting_edit and i == self.setting_index:
|
||||
color = ray.YELLOW
|
||||
|
||||
# Format value display
|
||||
if isinstance(value, list):
|
||||
display_value = ', '.join(map(str, value))
|
||||
else:
|
||||
display_value = str(value)
|
||||
|
||||
ray.draw_text(f'{key}: {display_value}', 250, i*25 + 70, 20, color)
|
||||
|
||||
# Draw instructions
|
||||
y_offset = len(settings) * 25 + 150
|
||||
if not self.in_setting_edit:
|
||||
ray.draw_text("Don/Kat: Navigate sections", 20, y_offset, 16, ray.GRAY)
|
||||
ray.draw_text("L/R Don: Enter section", 20, y_offset + 20, 16, ray.GRAY)
|
||||
else:
|
||||
ray.draw_text("Don/Kat: Navigate settings", 20, y_offset, 16, ray.GRAY)
|
||||
ray.draw_text("L/R Don: Modify value", 20, y_offset + 20, 16, ray.GRAY)
|
||||
ray.draw_text("ESC: Back to sections", 20, y_offset + 40, 16, ray.GRAY)
|
||||
|
||||
if self.editing_key:
|
||||
ray.draw_text("Press a key to bind (ESC to cancel)", 20, y_offset + 60, 16, ray.RED)
|
||||
else:
|
||||
# Draw exit instruction
|
||||
ray.draw_text("Press Don to exit settings", 250, 100, 20, ray.GREEN)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,13 @@ from pathlib import Path
|
||||
|
||||
import pyray as ray
|
||||
|
||||
from libs import song_hash
|
||||
from libs.animation import Animation
|
||||
from libs.audio import audio
|
||||
from libs.utils import (
|
||||
get_config,
|
||||
get_current_ms,
|
||||
is_l_don_pressed,
|
||||
is_r_don_pressed,
|
||||
load_all_textures_from_zip,
|
||||
load_texture_from_zip,
|
||||
)
|
||||
@@ -47,9 +48,6 @@ class TitleScreen:
|
||||
if not self.screen_init:
|
||||
self.screen_init = True
|
||||
self.load_textures()
|
||||
|
||||
song_hash.song_hashes = song_hash.build_song_hashes()
|
||||
|
||||
self.scene = 'Opening Video'
|
||||
self.op_video = VideoPlayer(random.choice(self.op_video_list))
|
||||
self.attract_video = VideoPlayer(random.choice(self.attract_video_list))
|
||||
@@ -97,10 +95,8 @@ class TitleScreen:
|
||||
self.on_screen_start()
|
||||
|
||||
self.scene_manager()
|
||||
keys = get_config()["keybinds"]["left_don"] + get_config()["keybinds"]["right_don"]
|
||||
for key in keys:
|
||||
if ray.is_key_pressed(ord(key)):
|
||||
return self.on_screen_end()
|
||||
if is_l_don_pressed() or is_r_don_pressed():
|
||||
return self.on_screen_end()
|
||||
|
||||
def draw(self):
|
||||
if self.scene == 'Opening Video':
|
||||
|
||||
170
uv.lock
generated
170
uv.lock
generated
@@ -143,15 +143,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a", size = 31246824, upload-time = "2025-01-16T21:34:28.6Z" },
|
||||
]
|
||||
|
||||
[[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 = "moviepy"
|
||||
version = "2.1.2"
|
||||
@@ -235,6 +226,47 @@ wheels = [
|
||||
{ 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 = "pandas"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490, upload-time = "2025-06-05T03:27:54.133Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/1e/ba313812a699fe37bf62e6194265a4621be11833f5fce46d9eae22acb5d7/pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca", size = 11551836, upload-time = "2025-06-05T03:26:22.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/cc/0af9c07f8d714ea563b12383a7e5bde9479cf32413ee2f346a9c5a801f22/pandas-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5f08eb9a445d07720776df6e641975665c9ea12c9d8a331e0f6890f2dcd76ef", size = 10807977, upload-time = "2025-06-05T16:50:11.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/3e/8c0fb7e2cf4a55198466ced1ca6a9054ae3b7e7630df7757031df10001fd/pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d", size = 11788230, upload-time = "2025-06-05T03:26:27.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/22/b493ec614582307faf3f94989be0f7f0a71932ed6f56c9a80c0bb4a3b51e/pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46", size = 12370423, upload-time = "2025-06-05T03:26:34.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/74/b012addb34cda5ce855218a37b258c4e056a0b9b334d116e518d72638737/pandas-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c06f6f144ad0a1bf84699aeea7eff6068ca5c63ceb404798198af7eb86082e33", size = 12990594, upload-time = "2025-06-06T00:00:13.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/81/b310e60d033ab64b08e66c635b94076488f0b6ce6a674379dd5b224fc51c/pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c", size = 13745952, upload-time = "2025-06-05T03:26:39.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/ac/f6ee5250a8881b55bd3aecde9b8cfddea2f2b43e3588bca68a4e9aaf46c8/pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a", size = 11094534, upload-time = "2025-06-05T03:26:43.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/46/24192607058dd607dbfacdd060a2370f6afb19c2ccb617406469b9aeb8e7/pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf", size = 11573865, upload-time = "2025-06-05T03:26:46.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/cc/ae8ea3b800757a70c9fdccc68b67dc0280a6e814efcf74e4211fd5dea1ca/pandas-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9d8c3187be7479ea5c3d30c32a5d73d62a621166675063b2edd21bc47614027", size = 10702154, upload-time = "2025-06-05T16:50:14.439Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/ba/a7883d7aab3d24c6540a2768f679e7414582cc389876d469b40ec749d78b/pandas-2.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ff730713d4c4f2f1c860e36c005c7cefc1c7c80c21c0688fd605aa43c9fcf09", size = 11262180, upload-time = "2025-06-05T16:50:17.453Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/a5/931fc3ad333d9d87b10107d948d757d67ebcfc33b1988d5faccc39c6845c/pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d", size = 11991493, upload-time = "2025-06-05T03:26:51.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/bf/0213986830a92d44d55153c1d69b509431a972eb73f204242988c4e66e86/pandas-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:404d681c698e3c8a40a61d0cd9412cc7364ab9a9cc6e144ae2992e11a2e77a20", size = 12470733, upload-time = "2025-06-06T00:00:18.651Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/0e/21eb48a3a34a7d4bac982afc2c4eb5ab09f2d988bdf29d92ba9ae8e90a79/pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b", size = 13212406, upload-time = "2025-06-05T03:26:55.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/d9/74017c4eec7a28892d8d6e31ae9de3baef71f5a5286e74e6b7aad7f8c837/pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be", size = 10976199, upload-time = "2025-06-05T03:26:59.594Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/57/5cb75a56a4842bbd0511c3d1c79186d8315b82dac802118322b2de1194fe/pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983", size = 11518913, upload-time = "2025-06-05T03:27:02.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/01/0c8785610e465e4948a01a059562176e4c8088aa257e2e074db868f86d4e/pandas-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6da97aeb6a6d233fb6b17986234cc723b396b50a3c6804776351994f2a658fd", size = 10655249, upload-time = "2025-06-05T16:50:20.17Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/6a/47fd7517cd8abe72a58706aab2b99e9438360d36dcdb052cf917b7bf3bdc/pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f", size = 11328359, upload-time = "2025-06-05T03:27:06.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/b3/463bfe819ed60fb7e7ddffb4ae2ee04b887b3444feee6c19437b8f834837/pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3", size = 12024789, upload-time = "2025-06-05T03:27:09.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/0c/e0704ccdb0ac40aeb3434d1c641c43d05f75c92e67525df39575ace35468/pandas-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1d2b33e68d0ce64e26a4acc2e72d747292084f4e8db4c847c6f5f6cbe56ed6d8", size = 12480734, upload-time = "2025-06-06T00:00:22.246Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/df/815d6583967001153bb27f5cf075653d69d51ad887ebbf4cfe1173a1ac58/pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9", size = 13223381, upload-time = "2025-06-05T03:27:15.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/88/ca5973ed07b7f484c493e941dbff990861ca55291ff7ac67c815ce347395/pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390", size = 10970135, upload-time = "2025-06-05T03:27:24.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/fb/0994c14d1f7909ce83f0b1fb27958135513c4f3f2528bde216180aa73bfc/pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575", size = 12141356, upload-time = "2025-06-05T03:27:34.547Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/a2/9b903e5962134497ac4f8a96f862ee3081cb2506f69f8e4778ce3d9c9d82/pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042", size = 11474674, upload-time = "2025-06-05T03:27:39.448Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/3a/3806d041bce032f8de44380f866059437fb79e36d6b22c82c187e65f765b/pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c", size = 11439876, upload-time = "2025-06-05T03:27:43.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/aa/3fc3181d12b95da71f5c2537c3e3b3af6ab3a8c392ab41ebb766e0929bc6/pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67", size = 11966182, upload-time = "2025-06-05T03:27:47.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/e7/e12f2d9b0a2c4a2cc86e2aabff7ccfd24f03e597d770abfa2acd313ee46b/pandas-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1991bbb96f4050b09b5f811253c4f3cf05ee89a589379aa36cd623f21a31d6f", size = 12547686, upload-time = "2025-06-06T00:00:26.142Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/c2/646d2e93e0af70f4e5359d870a63584dacbc324b54d73e6b3267920ff117/pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249", size = 13231847, upload-time = "2025-06-05T03:27:51.465Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.4.0"
|
||||
@@ -315,12 +347,14 @@ dependencies = [
|
||||
{ name = "moviepy" },
|
||||
{ name = "numpy" },
|
||||
{ name = "opencv-python" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pydub" },
|
||||
{ name = "raylib" },
|
||||
{ name = "raylib-dynamic" },
|
||||
{ name = "raylib-sdl" },
|
||||
{ name = "ruff" },
|
||||
{ name = "scipy" },
|
||||
{ name = "sounddevice" },
|
||||
{ name = "soundfile" },
|
||||
{ name = "tomlkit" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -329,12 +363,26 @@ requires-dist = [
|
||||
{ name = "moviepy", specifier = ">=2.1.2" },
|
||||
{ name = "numpy", specifier = ">=2.2.5" },
|
||||
{ name = "opencv-python", specifier = ">=4.11.0.86" },
|
||||
{ name = "pandas", specifier = ">=2.3.0" },
|
||||
{ name = "pydub", specifier = ">=0.25.1" },
|
||||
{ name = "raylib", specifier = ">=5.5.0.2" },
|
||||
{ name = "raylib-dynamic", specifier = ">=5.5.0.2" },
|
||||
{ name = "raylib-sdl", specifier = ">=5.5.0.2" },
|
||||
{ name = "ruff", specifier = ">=0.11.7" },
|
||||
{ name = "scipy", specifier = ">=1.15.2" },
|
||||
{ name = "sounddevice", specifier = ">=0.5.1" },
|
||||
{ name = "soundfile", specifier = ">=0.13.1" },
|
||||
{ name = "tomlkit", specifier = ">=0.13.3" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -347,42 +395,40 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raylib"
|
||||
version = "5.5.0.2"
|
||||
name = "pytz"
|
||||
version = "2025.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" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/c4/ce21721b474eb8f65379f7315b382ccfe1d5df728eea4dcf287b874e7461/raylib-5.5.0.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:37eb0ec97fc6b08f989489a50e09b5dde519e1bb8eb17e4033ac82227b0e5eda", size = 1703742, upload-time = "2024-11-26T11:09:31.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/61/138e305c82549869bb8cd41abe75571559eafbeab6aed1ce7d8fbe3ffd58/raylib-5.5.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bb9e506ecd3dbec6dba868eb036269837a8bde68220690842c3238239ee887ef", size = 1247449, upload-time = "2024-11-26T11:09:34.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/e0/dc638c42d1a505f0992263d48e1434d82c21afdf376b06f549d2e281dfd4/raylib-5.5.0.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:70aa8bed67875a8cf25191f35263ef92d646bdfcb1f507915c81562a321f4931", size = 2184315, upload-time = "2024-11-26T11:09:36.715Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1a/49db57283a28fdc1ff0e4604911b7fff085128c2ac8bdd9efa8c5c47439d/raylib-5.5.0.2-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:0365e8c578f72f598795d9377fc70342f0d62aa193c2f304ca048b3e28866752", size = 2278139, upload-time = "2024-11-26T11:09:39.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/8a/e1a690ab6889d4cb67346a2d32bad8b8e8b0f85ec826b00f76b0ad7e6ad6/raylib-5.5.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:5219be70e7fca03e9c4fddebf7e60e885d77137125c7a13f3800a947f8562a13", size = 1693944, upload-time = "2024-11-26T11:09:41.596Z" },
|
||||
{ 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" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/22/2e02e3738ad041f5ec2830aecdfab411fc2960bfc3400e03b477284bfaf7/raylib-5.5.0.2-pp311-pypy311_pp73-macosx_10_13_x86_64.whl", hash = "sha256:bc45fe1c0aac50aa319a9a66d44bb2bd0dcd038a44d95978191ae7bfeb4a06d8", size = 1216231, upload-time = "2025-02-12T04:21:59.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/7d/b29afedc4a706b12143f74f322cb32ad5a6f43e56aaca2a9fb89b0d94eee/raylib-5.5.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.whl", hash = "sha256:2242fd6079da5137e9863a447224f800adef6386ca8f59013a5d62cc5cadab2b", size = 1394928, upload-time = "2025-02-12T04:22:03.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/fa/2daf36d78078c6871b241168a36156169cfc8ea089faba5abe8edad304be/raylib-5.5.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e475a40764c9f83f9e66406bd86d85587eb923329a61ade463c3c59e1e880b16", size = 1564224, upload-time = "2025-02-12T04:22:05.911Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raylib-dynamic"
|
||||
name = "raylib-sdl"
|
||||
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" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/b6/24c241f86ed6101f277d600c8f69cd12368848f1e94ed82d21b4d9dfba11/raylib_sdl-5.5.0.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:265a312ea73d8987a4c58075db7f27e356e217d52b22383ccfacf07f6eeb3556", size = 2287593, upload-time = "2024-11-26T11:11:13.524Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/22/cada7093a7fc22ba8a68c032850175e7480b0e4149e782ee6093dca04006/raylib_sdl-5.5.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:8e86aa9ee0190cf02ae44bace9dc87379d171141a62daddd963dea40d40dbca7", size = 1745212, upload-time = "2025-01-04T20:21:04.281Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/a2/387b20de1be19ece354a6c2cda30dc369948438dd824095b7da36ed974e7/raylib_sdl-5.5.0.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:3e2a91fef2525cc9a5a13fb3dbbd77e6c614b4d33f446c6047720516628b80d4", size = 2827982, upload-time = "2024-11-26T11:11:15.736Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/b4/f96cfb79981ac8e692975ae06ca224043d92b386b7f8a80cb515a85775c3/raylib_sdl-5.5.0.2-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:5263010953358beffabca8f7f1dfedcccc0f931afb91cfb242fd7783c875d12c", size = 2967405, upload-time = "2024-11-26T11:11:18.039Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/6c/c26425f317392a98c414c1f14974f511b1abf7f36d54069c80026ce505a3/raylib_sdl-5.5.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:4dcb099fa0bfa3048585d76445fe15cae3b2418c919f5777c54f429e1d3eb888", size = 1617306, upload-time = "2024-11-26T11:11:20.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9d/8cd7466084f93b125c052f1825bc505d2822c19539dc5b3a2ac6a588fcaa/raylib_sdl-5.5.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:48e374f5c20e618f4c7c5664ac9e4f39f53245ffb9b62d6d133b8676716363b0", size = 2290643, upload-time = "2024-11-26T11:11:23.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/b0/1891f9712bed1083f1360d7f57f042a48bed110b5d5224256a33eeb9f302/raylib_sdl-5.5.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6cd980a3fabdbe88a6449acf7ab24686854dce258dea9ff5b0eb46b7eae981c0", size = 1746303, upload-time = "2024-11-26T11:11:26.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/de/6848b61e1df0eda838a61cb9e7060bd10d4af9616974c4360257c65ff8ff/raylib_sdl-5.5.0.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a8e0fd9399188943c81dadf60d3d13198da1f9e0d84cc2aefa9e0c0d5c6266d6", size = 2840025, upload-time = "2024-11-26T11:11:29.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/b8/d41eacab8666fb955877cb5e3d4afea7ba4d01f668d9e91aa1716c1230cb/raylib_sdl-5.5.0.2-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:92407aad5382958ec684c35d3a28c8fc091faca3b58fc5dd8b3495a1065520ff", size = 2985567, upload-time = "2024-11-26T11:11:32.887Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/07/d9f26daeda03ccd2d5b637fc3da25d039b39c27f1c2dde0ae9eeb2c40861/raylib_sdl-5.5.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ab31941d2247a043beb545f34ebef1844f1d9c5fac749032e991ee8c18439c5a", size = 1619555, upload-time = "2024-11-26T11:11:35.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/9c/e3d596db50bf4fb35f39478f1f529446dded5a7142c0583dbd8b19d52fe4/raylib_sdl-5.5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bc6fe38110da1c98c341ede7064277ff79fb1b2a607787bd772974b4e4105d9a", size = 2290691, upload-time = "2024-11-26T11:11:38.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/d3/70d418057bb162d04df5cc5249ff83c38d02b55dbfe9363334518f2276b6/raylib_sdl-5.5.0.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:696ff487d3688a6c36c05c0cfcad1cd891351268739e5dfc9037692e958caa56", size = 1746153, upload-time = "2024-11-26T11:11:41.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/8b/ccea24888331953eb43f2aa869d0a757093ebe88036d5234d1e83ef1785e/raylib_sdl-5.5.0.2-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:10672ac9c3b3bac2da95d99b1e128b7ebded8aff4f3325063544ea55c0712a62", size = 2985152, upload-time = "2024-11-26T11:11:44.078Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/d2/3a5e650d8c8dc3670a50ca000b6bed8f10f86872090eaa2a3450595f1528/raylib_sdl-5.5.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d7888a7ff42273a91b44520b209d24f20a9fd5eae0d9ac18851e52797c5d8b", size = 1619604, upload-time = "2024-11-26T11:11:46.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/2d/d531187e2c21d724fb07707dd71ac28d9e103d616ae595017f9b4679e8e2/raylib_sdl-5.5.0.2-pp311-pypy311_pp73-macosx_10_13_x86_64.whl", hash = "sha256:51017cb725fce7da0b8f220839f6db91e20bbb3ba58e7debe3b1fd67f4468188", size = 1809261, upload-time = "2025-02-12T04:22:11.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/c5/81ec5b8e0c9b89e95b85d20fd4685368dabc0d7fedc8050efdfe3bbcc2f2/raylib_sdl-5.5.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.whl", hash = "sha256:9453c29fc28b08dcae8a6432aeb07f13fbf8504fb1351941df64b9fb8ba0bac3", size = 2180109, upload-time = "2025-02-12T04:22:14.472Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/4f/8ba71611c74d6e3ff5e95d2935c1f5f98fc61183ebf70d4dfb09547a5767/raylib_sdl-5.5.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fd3841ab8d8d6ca24e4fbffc9514c58c0cf5bd29e3f2406006eba872416325b3", size = 1499920, upload-time = "2025-02-12T04:22:16.996Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
@@ -456,6 +502,15 @@ wheels = [
|
||||
{ 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 = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sounddevice"
|
||||
version = "0.5.1"
|
||||
@@ -471,6 +526,34 @@ wheels = [
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soundfile"
|
||||
version = "0.13.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.13.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tqdm"
|
||||
version = "4.67.1"
|
||||
@@ -482,3 +565,12 @@ sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user