[#mono-column] #circlefree mode accepts non-16ths notes and falls back to normal symbols when needed
This commit is contained in:
parent
9e0c4f5c2b
commit
857dd899a7
@ -1,4 +1,6 @@
|
|||||||
# Unreleased
|
# Unreleased
|
||||||
|
## Added
|
||||||
|
- [#mono-column] #circlefree mode accepts non-16ths notes and falls back to normal symbols when needed
|
||||||
## Fixed
|
## Fixed
|
||||||
- [jubeat-analyser]
|
- [jubeat-analyser]
|
||||||
- Raise exception earlier when a mono-column file is detected by the other #memo parsers (based on "--" separator lines)
|
- Raise exception earlier when a mono-column file is detected by the other #memo parsers (based on "--" separator lines)
|
||||||
|
@ -31,6 +31,7 @@ from ..dump_tools import (
|
|||||||
DEFAULT_EXTRA_SYMBOLS,
|
DEFAULT_EXTRA_SYMBOLS,
|
||||||
DIRECTION_TO_ARROW,
|
DIRECTION_TO_ARROW,
|
||||||
DIRECTION_TO_LINE,
|
DIRECTION_TO_LINE,
|
||||||
|
NOTE_TO_CIRCLE_FREE_SYMBOL,
|
||||||
JubeatAnalyserDumpedSection,
|
JubeatAnalyserDumpedSection,
|
||||||
LongNoteEnd,
|
LongNoteEnd,
|
||||||
SortedDefaultDict,
|
SortedDefaultDict,
|
||||||
@ -38,7 +39,6 @@ from ..dump_tools import (
|
|||||||
fraction_to_decimal,
|
fraction_to_decimal,
|
||||||
jubeat_analyser_file_dumper,
|
jubeat_analyser_file_dumper,
|
||||||
)
|
)
|
||||||
from ..symbols import CIRCLE_FREE_SYMBOLS, NOTE_SYMBOLS
|
|
||||||
|
|
||||||
|
|
||||||
class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
|
class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
|
||||||
@ -62,19 +62,18 @@ class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
|
|||||||
frames: List[Dict[NotePosition, str]] = []
|
frames: List[Dict[NotePosition, str]] = []
|
||||||
frame: Dict[NotePosition, str] = {}
|
frame: Dict[NotePosition, str] = {}
|
||||||
for note in self.notes:
|
for note in self.notes:
|
||||||
|
time_in_section = note.time - self.current_beat
|
||||||
|
symbol = self.symbols[time_in_section]
|
||||||
if isinstance(note, LongNote):
|
if isinstance(note, LongNote):
|
||||||
needed_positions = set(note.positions_covered())
|
needed_positions = set(note.positions_covered())
|
||||||
if needed_positions & frame.keys():
|
if needed_positions & frame.keys():
|
||||||
frames.append(frame)
|
frames.append(frame)
|
||||||
frame = {}
|
frame = {}
|
||||||
|
|
||||||
direction = note.tail_direction()
|
direction = note.tail_direction()
|
||||||
arrow = DIRECTION_TO_ARROW[direction]
|
arrow = DIRECTION_TO_ARROW[direction]
|
||||||
line = DIRECTION_TO_LINE[direction]
|
line = DIRECTION_TO_LINE[direction]
|
||||||
for is_first, is_last, pos in mark_ends(note.positions_covered()):
|
for is_first, is_last, pos in mark_ends(note.positions_covered()):
|
||||||
if is_first:
|
if is_first:
|
||||||
time_in_section = note.time - self.current_beat
|
|
||||||
symbol = self.symbols[time_in_section]
|
|
||||||
frame[pos] = symbol
|
frame[pos] = symbol
|
||||||
elif is_last:
|
elif is_last:
|
||||||
frame[pos] = arrow
|
frame[pos] = arrow
|
||||||
@ -84,18 +83,13 @@ class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
|
|||||||
if note.position in frame:
|
if note.position in frame:
|
||||||
frames.append(frame)
|
frames.append(frame)
|
||||||
frame = {}
|
frame = {}
|
||||||
time_in_section = note.time - self.current_beat
|
|
||||||
symbol = self.symbols[time_in_section]
|
|
||||||
frame[note.position] = symbol
|
frame[note.position] = symbol
|
||||||
elif isinstance(note, LongNoteEnd):
|
elif isinstance(note, LongNoteEnd):
|
||||||
if note.position in frame:
|
if note.position in frame:
|
||||||
frames.append(frame)
|
frames.append(frame)
|
||||||
frame = {}
|
frame = {}
|
||||||
time_in_section = note.time - self.current_beat
|
if circle_free and symbol in NOTE_TO_CIRCLE_FREE_SYMBOL:
|
||||||
if circle_free:
|
symbol = NOTE_TO_CIRCLE_FREE_SYMBOL[symbol]
|
||||||
symbol = CIRCLE_FREE_SYMBOLS[int(time_in_section)]
|
|
||||||
else:
|
|
||||||
symbol = self.symbols[time_in_section]
|
|
||||||
frame[note.position] = symbol
|
frame[note.position] = symbol
|
||||||
|
|
||||||
frames.append(frame)
|
frames.append(frame)
|
||||||
@ -108,9 +102,7 @@ class MonoColumnDumpedSection(JubeatAnalyserDumpedSection):
|
|||||||
yield "".join(frame.get(NotePosition(x, y), "□") for x in range(4))
|
yield "".join(frame.get(NotePosition(x, y), "□") for x in range(4))
|
||||||
|
|
||||||
|
|
||||||
def _raise_if_unfit_for_mono_column(
|
def _raise_if_unfit_for_mono_column(chart: Chart, timing: Timing) -> None:
|
||||||
chart: Chart, timing: Timing, circle_free: bool = False
|
|
||||||
) -> None:
|
|
||||||
if len(timing.events) < 1:
|
if len(timing.events) < 1:
|
||||||
raise ValueError("No BPM found in file") from None
|
raise ValueError("No BPM found in file") from None
|
||||||
|
|
||||||
@ -128,16 +120,6 @@ def _raise_if_unfit_for_mono_column(
|
|||||||
" mono_column format is not supported by jubeatools"
|
" mono_column format is not supported by jubeatools"
|
||||||
)
|
)
|
||||||
|
|
||||||
if circle_free and any(
|
|
||||||
(note.time + note.duration) % BeatsTime(1, 4) != 0
|
|
||||||
for note in chart.notes
|
|
||||||
if isinstance(note, LongNote)
|
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"Chart contains long notes whose ending timing aren't"
|
|
||||||
" representable in #circlefree mode"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _section_factory(b: BeatsTime) -> MonoColumnDumpedSection:
|
def _section_factory(b: BeatsTime) -> MonoColumnDumpedSection:
|
||||||
return MonoColumnDumpedSection(current_beat=b)
|
return MonoColumnDumpedSection(current_beat=b)
|
||||||
@ -151,7 +133,7 @@ def _dump_mono_column_chart(
|
|||||||
circle_free: bool = False,
|
circle_free: bool = False,
|
||||||
) -> StringIO:
|
) -> StringIO:
|
||||||
|
|
||||||
_raise_if_unfit_for_mono_column(chart, timing, circle_free)
|
_raise_if_unfit_for_mono_column(chart, timing)
|
||||||
|
|
||||||
sections = create_sections_from_chart(
|
sections = create_sections_from_chart(
|
||||||
_section_factory, chart, difficulty, timing, metadata, circle_free
|
_section_factory, chart, difficulty, timing, metadata, circle_free
|
||||||
|
@ -188,28 +188,20 @@ class MonoColumnParser(JubeatAnalyserParser):
|
|||||||
unfinished_longs: Dict[NotePosition, UnfinishedLongNote] = {}
|
unfinished_longs: Dict[NotePosition, UnfinishedLongNote] = {}
|
||||||
for section_starting_beat, section, bloc in self._iter_blocs():
|
for section_starting_beat, section, bloc in self._iter_blocs():
|
||||||
should_skip: Set[NotePosition] = set()
|
should_skip: Set[NotePosition] = set()
|
||||||
|
|
||||||
# 1/3 : look for ends to unfinished long notes
|
# 1/3 : look for ends to unfinished long notes
|
||||||
for pos, unfinished_long in unfinished_longs.items():
|
for pos, unfinished_long in unfinished_longs.items():
|
||||||
x, y = astuple(pos)
|
x, y = astuple(pos)
|
||||||
symbol = bloc[y][x]
|
symbol = bloc[y][x]
|
||||||
if self.circle_free:
|
if self.circle_free and symbol in CIRCLE_FREE_SYMBOLS:
|
||||||
if symbol in CIRCLE_FREE_SYMBOLS:
|
should_skip.add(pos)
|
||||||
should_skip.add(pos)
|
symbol_time = CIRCLE_FREE_TO_BEATS_TIME[symbol]
|
||||||
symbol_time = CIRCLE_FREE_TO_BEATS_TIME[symbol]
|
note_time = section_starting_beat + symbol_time
|
||||||
note_time = section_starting_beat + symbol_time
|
yield unfinished_long.ends_at(note_time)
|
||||||
yield unfinished_long.ends_at(note_time)
|
elif symbol in section.symbols:
|
||||||
elif symbol in section.symbols:
|
should_skip.add(pos)
|
||||||
raise SyntaxError(
|
symbol_time = section.symbols[symbol]
|
||||||
"Can't have a note symbol on the holding square of"
|
note_time = section_starting_beat + symbol_time
|
||||||
" an unfinished long note when #circlefree is on"
|
yield unfinished_long.ends_at(note_time)
|
||||||
)
|
|
||||||
else:
|
|
||||||
if symbol in section.symbols:
|
|
||||||
should_skip.add(pos)
|
|
||||||
symbol_time = section.symbols[symbol]
|
|
||||||
note_time = section_starting_beat + symbol_time
|
|
||||||
yield unfinished_long.ends_at(note_time)
|
|
||||||
|
|
||||||
unfinished_longs = {
|
unfinished_longs = {
|
||||||
k: unfinished_longs[k] for k in unfinished_longs.keys() - should_skip
|
k: unfinished_longs[k] for k in unfinished_longs.keys() - should_skip
|
||||||
|
@ -2,9 +2,8 @@ from typing import Iterable, Union
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from jubeatools.song import BeatsTime, LongNote, NotePosition, TapNote
|
|
||||||
|
|
||||||
from jubeatools.formats.jubeat_analyser.mono_column.load import MonoColumnParser
|
from jubeatools.formats.jubeat_analyser.mono_column.load import MonoColumnParser
|
||||||
|
from jubeatools.song import BeatsTime, LongNote, NotePosition, TapNote
|
||||||
|
|
||||||
|
|
||||||
def compare_chart_notes(
|
def compare_chart_notes(
|
||||||
|
@ -5,6 +5,9 @@ from typing import List, Set, Union
|
|||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
|
|
||||||
|
from jubeatools.formats import Format
|
||||||
|
from jubeatools.formats.jubeat_analyser.mono_column.dump import _dump_mono_column_chart
|
||||||
|
from jubeatools.formats.jubeat_analyser.mono_column.load import MonoColumnParser
|
||||||
from jubeatools.song import (
|
from jubeatools.song import (
|
||||||
BeatsTime,
|
BeatsTime,
|
||||||
BPMEvent,
|
BPMEvent,
|
||||||
@ -12,19 +15,14 @@ from jubeatools.song import (
|
|||||||
LongNote,
|
LongNote,
|
||||||
Metadata,
|
Metadata,
|
||||||
SecondsTime,
|
SecondsTime,
|
||||||
|
Song,
|
||||||
TapNote,
|
TapNote,
|
||||||
Timing,
|
Timing,
|
||||||
Song
|
|
||||||
)
|
)
|
||||||
from jubeatools.testutils.strategies import NoteOption, long_note
|
from jubeatools.testutils.strategies import NoteOption, long_note
|
||||||
from jubeatools.testutils.strategies import notes as notes_strat
|
from jubeatools.testutils.strategies import notes as notes_strat
|
||||||
from jubeatools.testutils.strategies import tap_note
|
from jubeatools.testutils.strategies import tap_note
|
||||||
|
|
||||||
from jubeatools.formats import Format
|
|
||||||
from jubeatools.formats.jubeat_analyser.mono_column.dump import _dump_mono_column_chart
|
|
||||||
from jubeatools.formats.jubeat_analyser.mono_column.load import MonoColumnParser
|
|
||||||
|
|
||||||
|
|
||||||
from ..test_utils import load_and_dump_then_check, memo_compatible_song
|
from ..test_utils import load_and_dump_then_check, memo_compatible_song
|
||||||
|
|
||||||
|
|
||||||
@ -86,4 +84,4 @@ def test_that_many_notes_roundtrip(notes: List[Union[TapNote, LongNote]]) -> Non
|
|||||||
|
|
||||||
@given(memo_compatible_song(), st.booleans())
|
@given(memo_compatible_song(), st.booleans())
|
||||||
def test_that_full_chart_roundtrips(song: Song, circle_free: bool) -> None:
|
def test_that_full_chart_roundtrips(song: Song, circle_free: bool) -> None:
|
||||||
load_and_dump_then_check(Format.MONO_COLUMN, song, circle_free)
|
load_and_dump_then_check(Format.MONO_COLUMN, song, circle_free)
|
||||||
|
Loading…
Reference in New Issue
Block a user