mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
branches work maybe idk
This commit is contained in:
18
libs/tja.py
18
libs/tja.py
@@ -751,6 +751,7 @@ class TJAParser:
|
|||||||
state.time_signature = float(numerator) / float(denominator)
|
state.time_signature = float(numerator) / float(denominator)
|
||||||
|
|
||||||
def handle_scroll(self, part: str, state: ParserState):
|
def handle_scroll(self, part: str, state: ParserState):
|
||||||
|
if state.scroll_type != ScrollType.BMSCROLL:
|
||||||
if 'i' in part:
|
if 'i' in part:
|
||||||
normalized = part.replace('.i', 'j').replace('i', 'j')
|
normalized = part.replace('.i', 'j').replace('i', 'j')
|
||||||
normalized = normalized.replace(',', '')
|
normalized = normalized.replace(',', '')
|
||||||
@@ -790,7 +791,7 @@ class TJAParser:
|
|||||||
state.start_branch_y_scroll = state.scroll_y_modifier
|
state.start_branch_y_scroll = state.scroll_y_modifier
|
||||||
state.start_branch_barline = state.barline_display
|
state.start_branch_barline = state.barline_display
|
||||||
state.branch_balloon_index = state.balloon_index
|
state.branch_balloon_index = state.balloon_index
|
||||||
branch_params = part[13:]
|
branch_params = part
|
||||||
|
|
||||||
def set_branch_params(bar_list: list[Note], branch_params: str, section_bar: Optional[Note]):
|
def set_branch_params(bar_list: list[Note], branch_params: str, section_bar: Optional[Note]):
|
||||||
if bar_list and len(bar_list) > 1:
|
if bar_list and len(bar_list) > 1:
|
||||||
@@ -827,6 +828,7 @@ class TJAParser:
|
|||||||
|
|
||||||
def handle_lyric(self, part: str, state: ParserState):
|
def handle_lyric(self, part: str, state: ParserState):
|
||||||
timeline_obj = TimelineObject()
|
timeline_obj = TimelineObject()
|
||||||
|
timeline_obj.hit_ms = self.current_ms
|
||||||
timeline_obj.lyric = part
|
timeline_obj.lyric = part
|
||||||
state.curr_timeline.append(timeline_obj)
|
state.curr_timeline.append(timeline_obj)
|
||||||
|
|
||||||
@@ -891,21 +893,20 @@ class TJAParser:
|
|||||||
|
|
||||||
def handle_gogostart(self, part: str, state: ParserState):
|
def handle_gogostart(self, part: str, state: ParserState):
|
||||||
timeline_obj = TimelineObject()
|
timeline_obj = TimelineObject()
|
||||||
|
timeline_obj.hit_ms = self.current_ms
|
||||||
timeline_obj.gogo_time = True
|
timeline_obj.gogo_time = True
|
||||||
state.curr_timeline.append(timeline_obj)
|
state.curr_timeline.append(timeline_obj)
|
||||||
|
|
||||||
def handle_gogoend(self, part: str, state: ParserState):
|
def handle_gogoend(self, part: str, state: ParserState):
|
||||||
timeline_obj = TimelineObject()
|
timeline_obj = TimelineObject()
|
||||||
|
timeline_obj.hit_ms = self.current_ms
|
||||||
timeline_obj.gogo_time = False
|
timeline_obj.gogo_time = False
|
||||||
state.curr_timeline.append(timeline_obj)
|
state.curr_timeline.append(timeline_obj)
|
||||||
|
|
||||||
def handle_delay(self, part: str, state: ParserState):
|
def handle_delay(self, part: str, state: ParserState):
|
||||||
delay_ms = float(part) * 1000
|
delay_ms = float(part) * 1000
|
||||||
if state.scroll_type == ScrollType.BMSCROLL or state.scroll_type == ScrollType.HBSCROLL:
|
if state.scroll_type == ScrollType.BMSCROLL or state.scroll_type == ScrollType.HBSCROLL:
|
||||||
if delay_ms <= 0:
|
if delay_ms > 0:
|
||||||
# No changes if not positive
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Do not modify current_ms, it will be modified live
|
# Do not modify current_ms, it will be modified live
|
||||||
state.delay_current += delay_ms
|
state.delay_current += delay_ms
|
||||||
|
|
||||||
@@ -927,7 +928,6 @@ class TJAParser:
|
|||||||
if state.sudden_moving == 0:
|
if state.sudden_moving == 0:
|
||||||
state.sudden_moving = float('inf')
|
state.sudden_moving = float('inf')
|
||||||
|
|
||||||
'''
|
|
||||||
def handle_m(self, part: str, state: ParserState):
|
def handle_m(self, part: str, state: ParserState):
|
||||||
self.branch_m.append(NoteList())
|
self.branch_m.append(NoteList())
|
||||||
state.curr_note_list = self.branch_m[-1].play_notes
|
state.curr_note_list = self.branch_m[-1].play_notes
|
||||||
@@ -972,7 +972,6 @@ class TJAParser:
|
|||||||
state.barline_display = state.start_branch_barline
|
state.barline_display = state.start_branch_barline
|
||||||
state.balloon_index = state.branch_balloon_index
|
state.balloon_index = state.branch_balloon_index
|
||||||
state.is_branching = True
|
state.is_branching = True
|
||||||
'''
|
|
||||||
|
|
||||||
def add_bar(self, state: ParserState):
|
def add_bar(self, state: ParserState):
|
||||||
bar_line = Note()
|
bar_line = Note()
|
||||||
@@ -1044,13 +1043,16 @@ class TJAParser:
|
|||||||
init_bpm.bpm = state.bpm
|
init_bpm.bpm = state.bpm
|
||||||
state.curr_timeline.append(init_bpm)
|
state.curr_timeline.append(init_bpm)
|
||||||
|
|
||||||
|
state.bpmchange_last_bpm = state.bpm
|
||||||
|
state.delay_last_note_ms = self.current_ms
|
||||||
|
|
||||||
for bar in notes:
|
for bar in notes:
|
||||||
bar_length = sum(len(part) for part in bar if '#' not in part)
|
bar_length = sum(len(part) for part in bar if '#' not in part)
|
||||||
state.barline_added = False
|
state.barline_added = False
|
||||||
|
|
||||||
for part in bar:
|
for part in bar:
|
||||||
if part.startswith('#'):
|
if part.startswith('#'):
|
||||||
for cmd_prefix, handler in commands.items():
|
for cmd_prefix, handler in sorted(commands.items(), key=lambda x: len(x[0]), reverse=True):
|
||||||
if part.startswith(cmd_prefix):
|
if part.startswith(cmd_prefix):
|
||||||
value = part[len(cmd_prefix):].strip()
|
value = part[len(cmd_prefix):].strip()
|
||||||
handler(value, state)
|
handler(value, state)
|
||||||
|
|||||||
109
scenes/game.py
109
scenes/game.py
@@ -138,7 +138,6 @@ class GameScreen(Screen):
|
|||||||
self.tja = TJAParser(song, start_delay=self.start_delay)
|
self.tja = TJAParser(song, start_delay=self.start_delay)
|
||||||
if self.tja.metadata.bgmovie != Path() and self.tja.metadata.bgmovie.exists():
|
if self.tja.metadata.bgmovie != Path() and self.tja.metadata.bgmovie.exists():
|
||||||
self.movie = VideoPlayer(self.tja.metadata.bgmovie)
|
self.movie = VideoPlayer(self.tja.metadata.bgmovie)
|
||||||
self.movie.set_volume(0.0)
|
|
||||||
else:
|
else:
|
||||||
self.movie = None
|
self.movie = None
|
||||||
global_data.session_data[global_data.player_num].song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
|
global_data.session_data[global_data.player_num].song_title = self.tja.metadata.title.get(global_data.config['general']['language'].lower(), self.tja.metadata.title['en'])
|
||||||
@@ -214,6 +213,7 @@ class GameScreen(Screen):
|
|||||||
logger.info(f"Song started at {ms_from_start}")
|
logger.info(f"Song started at {ms_from_start}")
|
||||||
if self.movie is not None:
|
if self.movie is not None:
|
||||||
self.movie.start(get_current_ms())
|
self.movie.start(get_current_ms())
|
||||||
|
self.movie.set_volume(0.0)
|
||||||
self.song_started = True
|
self.song_started = True
|
||||||
|
|
||||||
def pause_song(self):
|
def pause_song(self):
|
||||||
@@ -384,6 +384,33 @@ class Player:
|
|||||||
self.autoplay_hit_side = Side.LEFT
|
self.autoplay_hit_side = Side.LEFT
|
||||||
self.last_subdivision = -1
|
self.last_subdivision = -1
|
||||||
|
|
||||||
|
def get_load_time(self, note):
|
||||||
|
note_half_w = tex.textures["notes"]["1"].width // 2
|
||||||
|
travel_distance = tex.screen_width - GameScreen.JUDGE_X
|
||||||
|
|
||||||
|
base_pixels_per_ms = (note.bpm / 240000 * abs(note.scroll_x) * travel_distance * tex.screen_scale)
|
||||||
|
|
||||||
|
if base_pixels_per_ms == 0:
|
||||||
|
base_pixels_per_ms = (note.bpm / 240000 * abs(note.scroll_y) * travel_distance * tex.screen_scale)
|
||||||
|
|
||||||
|
normal_travel_ms = (travel_distance + note_half_w) / base_pixels_per_ms
|
||||||
|
|
||||||
|
if not hasattr(note, "sudden_appear_ms") or not hasattr(note, "sudden_moving_ms") or note.sudden_appear_ms == float("inf"):
|
||||||
|
note.load_ms = note.hit_ms - normal_travel_ms
|
||||||
|
note.unload_ms = note.hit_ms + normal_travel_ms
|
||||||
|
return
|
||||||
|
|
||||||
|
note.load_ms = note.hit_ms - note.sudden_appear_ms
|
||||||
|
movement_duration = note.sudden_moving_ms
|
||||||
|
|
||||||
|
if movement_duration <= 0:
|
||||||
|
movement_duration = normal_travel_ms
|
||||||
|
|
||||||
|
sudden_pixels_per_ms = travel_distance / movement_duration
|
||||||
|
unload_offset = travel_distance / sudden_pixels_per_ms
|
||||||
|
note.unload_ms = note.hit_ms + unload_offset
|
||||||
|
|
||||||
|
|
||||||
def reset_chart(self):
|
def reset_chart(self):
|
||||||
notes, self.branch_m, self.branch_e, self.branch_n = self.tja.notes_to_position(self.difficulty)
|
notes, self.branch_m, self.branch_e, self.branch_n = self.tja.notes_to_position(self.difficulty)
|
||||||
self.play_notes, self.draw_note_list, self.draw_bar_list = apply_modifiers(notes, self.modifiers)
|
self.play_notes, self.draw_note_list, self.draw_bar_list = apply_modifiers(notes, self.modifiers)
|
||||||
@@ -417,69 +444,45 @@ class Player:
|
|||||||
if self.timeline and hasattr(self.timeline[self.timeline_index], 'bpm'):
|
if self.timeline and hasattr(self.timeline[self.timeline_index], 'bpm'):
|
||||||
self.bpm = self.timeline[self.timeline_index].bpm
|
self.bpm = self.timeline[self.timeline_index].bpm
|
||||||
for note in chain(self.draw_note_list, self.draw_bar_list):
|
for note in chain(self.draw_note_list, self.draw_bar_list):
|
||||||
distance_x = tex.screen_width - GameScreen.JUDGE_X
|
self.get_load_time(note)
|
||||||
distance_y = abs(note.scroll_y * ((tex.screen_width - GameScreen.JUDGE_X)/tex.screen_width) * tex.screen_width)
|
|
||||||
total_distance = math.sqrt(distance_x**2 + distance_y**2)
|
|
||||||
|
|
||||||
pixels_per_ms = note.bpm / 240000 * abs(note.scroll_x) * ((tex.screen_width - tex.textures["notes"]["1"].width//2) - GameScreen.JUDGE_X) * tex.screen_scale
|
|
||||||
|
|
||||||
if pixels_per_ms == 0:
|
|
||||||
pixels_per_ms = note.bpm / 240000 * abs(note.scroll_y) * ((tex.screen_width - tex.textures["notes"]["1"].width//2) - GameScreen.JUDGE_X) * tex.screen_scale
|
|
||||||
|
|
||||||
if note.scroll_x >= 0:
|
|
||||||
base_load_offset = total_distance / pixels_per_ms
|
|
||||||
note.unload_ms = note.hit_ms + (GameScreen.JUDGE_X / pixels_per_ms)
|
|
||||||
else:
|
|
||||||
base_load_offset = GameScreen.JUDGE_X / pixels_per_ms
|
|
||||||
note.unload_ms = note.hit_ms + (total_distance / pixels_per_ms)
|
|
||||||
|
|
||||||
if hasattr(note, 'sudden_appear_ms') and hasattr(note, 'sudden_moving_ms') and note.sudden_appear_ms != float('inf'):
|
|
||||||
note.load_ms = note.hit_ms - note.sudden_appear_ms
|
|
||||||
if note.scroll_x >= 0:
|
|
||||||
note.unload_ms = note.hit_ms + note.sudden_appear_ms - note.sudden_moving_ms + (GameScreen.JUDGE_X / pixels_per_ms)
|
|
||||||
else:
|
|
||||||
note.unload_ms = note.hit_ms + note.sudden_appear_ms - note.sudden_moving_ms + (total_distance / pixels_per_ms)
|
|
||||||
else:
|
|
||||||
note.load_ms = note.hit_ms - base_load_offset
|
|
||||||
|
|
||||||
self.draw_note_list = deque(sorted(self.draw_note_list, key=lambda n: n.load_ms))
|
self.draw_note_list = deque(sorted(self.draw_note_list, key=lambda n: n.load_ms))
|
||||||
self.draw_bar_list = deque(sorted(self.draw_bar_list, key=lambda n: n.load_ms))
|
|
||||||
|
|
||||||
# Handle HBSCROLL, BMSCROLL (pre-modify hit_ms, so that notes can't be literally hit, but are still visually different) - basically it applies the transformations of #BPMCHANGE and #DELAY to hit_ms, so that notes can't be hit even if its visaulyl
|
# Handle HBSCROLL, BMSCROLL (pre-modify hit_ms, so that notes can't be literally hit, but are still visually different) - basically it applies the transformations of #BPMCHANGE and #DELAY to hit_ms, so that notes can't be hit even if its visaulyl
|
||||||
for i, o in enumerate(self.timeline):
|
for i, o in enumerate(self.timeline):
|
||||||
if hasattr(o, 'bpmchange'):
|
if hasattr(o, 'bpmchange'):
|
||||||
hit_ms = o.hit_ms
|
hit_ms = o.hit_ms
|
||||||
bpmchange = o.bpmchange
|
bpmchange = o.bpmchange
|
||||||
for note in chain(self.play_notes, self.current_bars, self.draw_bar_list):
|
for note in chain(self.draw_note_list, self.draw_bar_list):
|
||||||
if note.hit_ms > hit_ms:
|
if note.hit_ms > hit_ms:
|
||||||
note.hit_ms = (note.hit_ms - hit_ms) / bpmchange + hit_ms
|
note.hit_ms = (note.hit_ms - hit_ms) / bpmchange + hit_ms
|
||||||
for i2 in range(i + 1, len(self.timeline)):
|
for i2 in range(i + 1, len(self.timeline)):
|
||||||
o2 = self.timeline[i2]
|
o2 = self.timeline[i2]
|
||||||
|
if not hasattr(o2, 'bpmchange'):
|
||||||
|
continue
|
||||||
o2.hit_ms = (o2.hit_ms - hit_ms) / bpmchange + hit_ms
|
o2.hit_ms = (o2.hit_ms - hit_ms) / bpmchange + hit_ms
|
||||||
elif hasattr(o, 'delay'):
|
elif hasattr(o, 'delay'):
|
||||||
hit_ms = o.hit_ms
|
hit_ms = o.hit_ms
|
||||||
delay = o.delay
|
delay = o.delay
|
||||||
for note in chain(self.play_notes, self.current_bars, self.draw_bar_list):
|
for note in chain(self.draw_note_list, self.draw_bar_list):
|
||||||
if note.hit_ms > hit_ms:
|
if note.hit_ms > hit_ms:
|
||||||
note.hit_ms += delay
|
note.hit_ms += delay
|
||||||
for i2 in range(i + 1, len(self.timeline)):
|
for i2 in range(i + 1, len(self.timeline)):
|
||||||
o2 = self.timeline[i2]
|
o2 = self.timeline[i2]
|
||||||
|
if not hasattr(o2, 'delay'):
|
||||||
|
continue
|
||||||
o2.hit_ms += delay
|
o2.hit_ms += delay
|
||||||
|
|
||||||
# Decide end_time after all transforms have been applied
|
# Decide end_time after all transforms have been applied
|
||||||
self.end_time = 0
|
self.end_time = self.play_notes[-1].hit_ms if self.play_notes else 0
|
||||||
if self.play_notes:
|
|
||||||
self.end_time = self.play_notes[-1].hit_ms
|
for branch in (self.branch_m, self.branch_e, self.branch_n):
|
||||||
if self.branch_m:
|
if branch:
|
||||||
for section in self.branch_m:
|
for section in branch:
|
||||||
if section.play_notes:
|
for note in section.draw_notes or []:
|
||||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
self.get_load_time(note)
|
||||||
if self.branch_e:
|
for note in section.bars or []:
|
||||||
for section in self.branch_e:
|
self.get_load_time(note)
|
||||||
if section.play_notes:
|
|
||||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
|
||||||
if self.branch_n:
|
|
||||||
for section in self.branch_n:
|
|
||||||
if section.play_notes:
|
if section.play_notes:
|
||||||
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
self.end_time = max(self.end_time, section.play_notes[-1].hit_ms)
|
||||||
|
|
||||||
@@ -504,11 +507,15 @@ class Player:
|
|||||||
return self.score, self.good_count, self.ok_count, self.bad_count, self.max_combo, self.total_drumroll
|
return self.score, self.good_count, self.ok_count, self.bad_count, self.max_combo, self.total_drumroll
|
||||||
|
|
||||||
def get_position_x(self, note, current_ms):
|
def get_position_x(self, note, current_ms):
|
||||||
|
if self.delay_start:
|
||||||
|
current_ms = self.delay_start
|
||||||
speedx = note.bpm / 240000 * note.scroll_x * (tex.screen_width - GameScreen.JUDGE_X) * tex.screen_scale
|
speedx = note.bpm / 240000 * note.scroll_x * (tex.screen_width - GameScreen.JUDGE_X) * tex.screen_scale
|
||||||
return GameScreen.JUDGE_X + (note.hit_ms - current_ms) * speedx
|
return GameScreen.JUDGE_X + (note.hit_ms - current_ms) * speedx
|
||||||
|
|
||||||
|
|
||||||
def get_position_y(self, note, current_ms):
|
def get_position_y(self, note, current_ms):
|
||||||
|
if self.delay_start:
|
||||||
|
current_ms = self.delay_start
|
||||||
speedy = note.bpm / 240000 * note.scroll_y * ((tex.screen_width - GameScreen.JUDGE_X)/tex.screen_width) * tex.screen_width
|
speedy = note.bpm / 240000 * note.scroll_y * ((tex.screen_width - GameScreen.JUDGE_X)/tex.screen_width) * tex.screen_width
|
||||||
return (note.hit_ms - current_ms) * speedy
|
return (note.hit_ms - current_ms) * speedy
|
||||||
|
|
||||||
@@ -589,25 +596,15 @@ class Player:
|
|||||||
timeline_object = self.timeline[self.timeline_index]
|
timeline_object = self.timeline[self.timeline_index]
|
||||||
should_advance = False
|
should_advance = False
|
||||||
|
|
||||||
'''
|
|
||||||
if hasattr(timeline_object, 'bpmchange') and timeline_object.hit_ms <= current_ms:
|
if hasattr(timeline_object, 'bpmchange') and timeline_object.hit_ms <= current_ms:
|
||||||
hit_ms = timeline_object.hit_ms
|
hit_ms = timeline_object.hit_ms
|
||||||
bpmchange = timeline_object.bpmchange
|
bpmchange = timeline_object.bpmchange
|
||||||
# Adjust notes (visually)
|
for note in chain(self.current_notes_draw, self.current_bars, self.draw_note_list, self.draw_bar_list):
|
||||||
for note in chain(self.play_notes, self.current_bars, self.draw_bar_list):
|
note.bpm *= bpmchange
|
||||||
# Already modified
|
self.get_load_time(note)
|
||||||
# note.hit_ms = (note.hit_ms - hit_ms) / bpmchange + hit_ms
|
|
||||||
# time_diff * note.pixels_per_frame need to be the same before and after the adjustment
|
|
||||||
# that means time_diff should be divided by self.bpmchange.bpmchange
|
|
||||||
# current_ms = self.bpmchange.hit_ms
|
|
||||||
time_diff = note.load_ms - hit_ms
|
|
||||||
note.load_ms = time_diff / bpmchange + hit_ms
|
|
||||||
|
|
||||||
note.pixels_per_frame_x *= bpmchange
|
|
||||||
note.pixels_per_frame_y *= bpmchange
|
|
||||||
self.bpm *= bpmchange
|
self.bpm *= bpmchange
|
||||||
should_advance = True
|
should_advance = True
|
||||||
'''
|
|
||||||
|
|
||||||
if hasattr(timeline_object, 'delay') and timeline_object.hit_ms <= current_ms:
|
if hasattr(timeline_object, 'delay') and timeline_object.hit_ms <= current_ms:
|
||||||
hit_ms = timeline_object.hit_ms
|
hit_ms = timeline_object.hit_ms
|
||||||
@@ -666,7 +663,6 @@ class Player:
|
|||||||
logger.info(f'branch condition measures started with conditions {self.branch_condition}, {e_req}, {m_req}, {self.current_bars[-1].hit_ms}')
|
logger.info(f'branch condition measures started with conditions {self.branch_condition}, {e_req}, {m_req}, {self.current_bars[-1].hit_ms}')
|
||||||
if not self.is_branch:
|
if not self.is_branch:
|
||||||
self.is_branch = True
|
self.is_branch = True
|
||||||
'''
|
|
||||||
if self.branch_condition == 'r':
|
if self.branch_condition == 'r':
|
||||||
end_time = self.branch_m[0].bars[0].load_ms
|
end_time = self.branch_m[0].bars[0].load_ms
|
||||||
end_roll = -1
|
end_roll = -1
|
||||||
@@ -706,7 +702,6 @@ class Player:
|
|||||||
seen_notes.add(note)
|
seen_notes.add(note)
|
||||||
|
|
||||||
self.curr_branch_reqs = [e_req, m_req, branch_start_time, max(len(seen_notes), 1)]
|
self.curr_branch_reqs = [e_req, m_req, branch_start_time, max(len(seen_notes), 1)]
|
||||||
'''
|
|
||||||
def play_note_manager(self, current_ms: float, background: Optional[Background]):
|
def play_note_manager(self, current_ms: float, background: Optional[Background]):
|
||||||
"""Manages the play_notes and removes if necessary"""
|
"""Manages the play_notes and removes if necessary"""
|
||||||
if self.don_notes and self.don_notes[0].hit_ms + Player.TIMING_BAD < current_ms:
|
if self.don_notes and self.don_notes[0].hit_ms + Player.TIMING_BAD < current_ms:
|
||||||
@@ -1099,7 +1094,6 @@ class Player:
|
|||||||
self.animation_manager(self.draw_drum_hit_list, current_time)
|
self.animation_manager(self.draw_drum_hit_list, current_time)
|
||||||
self.get_judge_position(ms_from_start)
|
self.get_judge_position(ms_from_start)
|
||||||
self.handle_scroll_type_commands(ms_from_start)
|
self.handle_scroll_type_commands(ms_from_start)
|
||||||
'''
|
|
||||||
if self.delay_start is not None and self.delay_end is not None:
|
if self.delay_start is not None and self.delay_end is not None:
|
||||||
# Currently, a delay is active: notes should be frozen at ms = delay_start
|
# Currently, a delay is active: notes should be frozen at ms = delay_start
|
||||||
# Check if it ended
|
# Check if it ended
|
||||||
@@ -1109,7 +1103,6 @@ class Player:
|
|||||||
note.load_ms += delay
|
note.load_ms += delay
|
||||||
self.delay_start = None
|
self.delay_start = None
|
||||||
self.delay_end = None
|
self.delay_end = None
|
||||||
'''
|
|
||||||
self.update_bpm(ms_from_start)
|
self.update_bpm(ms_from_start)
|
||||||
|
|
||||||
# More efficient arc management
|
# More efficient arc management
|
||||||
|
|||||||
Reference in New Issue
Block a user