Commit in-progress work just to demo
This should be cleaned up into proper commits
This commit is contained in:
parent
89011c79db
commit
c0288bb0b2
@ -2,8 +2,8 @@
|
||||
HEADER_GLOBAL = ['TITLE', 'TITLEJA', 'SUBTITLE', 'SUBTITLEJA', 'BPM', 'WAVE', 'OFFSET', 'DEMOSTART', 'GENRE']
|
||||
HEADER_COURSE = ['COURSE', 'LEVEL', 'BALLOON', 'SCOREINIT', 'SCOREDIFF', 'TTRO' 'WBEAT']
|
||||
BRANCH_COMMANDS = ['START', 'END', 'BRANCHSTART', 'BRANCHEND', 'N', 'E', 'M']
|
||||
MEASURE_COMMANDS = ['MEASURE', 'GOGOSTART', 'GOGOEND', 'SCROLL', 'BPMCHANGE', 'TTBREAK' 'LEVELHOLD']
|
||||
UNUSED_COMMANDS = ['DELAY', 'SECTION', 'BMSCROLL', 'HBSCROLL', 'BARLINEON', 'BARLINEOFF']
|
||||
MEASURE_COMMANDS = ['MEASURE', 'GOGOSTART', 'GOGOEND', 'BARLINEON', 'BARLINEOFF', 'SCROLL', 'BPMCHANGE', 'TTBREAK' 'LEVELHOLD']
|
||||
UNUSED_COMMANDS = ['DELAY', 'SECTION', 'BMSCROLL', 'HBSCROLL']
|
||||
COMMAND = BRANCH_COMMANDS + MEASURE_COMMANDS + UNUSED_COMMANDS
|
||||
|
||||
# Note types for TJA files
|
||||
|
@ -20,64 +20,129 @@ default_measure = {
|
||||
}
|
||||
|
||||
|
||||
def preprocessTJAMeasures(tja):
|
||||
"""
|
||||
Merge TJA 'data' and 'event' fields into a single measure property, and split
|
||||
measures into sub-measures whenever a mid-measure BPM change occurs.
|
||||
|
||||
The TJA parser produces measure objects with two important properties:
|
||||
- 'data': Contains the note data (1: don, 2: ka, etc.) along with spacing (s)
|
||||
- 'events' Contains event commands such as MEASURE, BPMCHANGE, GOGOTIME, etc.
|
||||
|
||||
However, notes and events can be intertwined within a single measure. So, it's
|
||||
not possible to process them separately; they must be considered as single sequence.
|
||||
|
||||
A particular danger is BPM changes. TJA allows multiple BPMs within a single measure,
|
||||
but the fumen format permits one BPM per measure. So, a TJA measure must be split up
|
||||
if it has multiple BPM changes within a measure.
|
||||
|
||||
In the future, this logic should probably be moved into the TJA parser itself.
|
||||
"""
|
||||
measuresCorrected = []
|
||||
|
||||
currentBPM = 0
|
||||
for measure in tja['measures']:
|
||||
# Step 1: Combine notes and events
|
||||
notes = [{'pos': i, 'type': 'note', 'value': TJA_NOTE_TYPES[note]}
|
||||
for i, note in enumerate(measure['data']) if note != '0']
|
||||
events = [{'pos': e['position'], 'type': e['name'], 'value': e['value']}
|
||||
for e in measure['events']]
|
||||
combined = []
|
||||
while notes or events:
|
||||
if events and notes:
|
||||
if notes[0]['pos'] >= events[0]['pos']:
|
||||
combined.append(events.pop(0))
|
||||
else:
|
||||
combined.append(notes.pop(0))
|
||||
elif events:
|
||||
combined.append(events.pop(0))
|
||||
elif notes:
|
||||
combined.append(notes.pop(0))
|
||||
|
||||
# Step 2: Split measure into submeasure
|
||||
submeasures = []
|
||||
measure_cur = {'bpm': currentBPM, 'pos_start': 0, 'data': []}
|
||||
for data in combined:
|
||||
if data['type'] == 'bpm':
|
||||
currentBPM = float(data['value'])
|
||||
# Case 1: BPM change at the start of a measure; just change PM
|
||||
if data['pos'] == 0:
|
||||
measure_cur['bpm'] = currentBPM
|
||||
# Case 2: BPM change mid-measure, so start a new sub-measure
|
||||
else:
|
||||
measure_cur['pos_end'] = data['pos']
|
||||
submeasures.append(measure_cur)
|
||||
measure_cur = {'bpm': currentBPM, 'pos_start': data['pos'], 'data': []}
|
||||
else:
|
||||
measure_cur['data'].append(data)
|
||||
measure_cur['pos_end'] = len(measure['data'])
|
||||
submeasures.append(measure_cur)
|
||||
|
||||
# Append the newly-created measures
|
||||
for submeasure in submeasures:
|
||||
measuresCorrected.append({
|
||||
'bpm': submeasure['bpm'],
|
||||
'subdivisions': len(measure['data']),
|
||||
'pos_start': submeasure['pos_start'],
|
||||
'pos_end': submeasure['pos_end'],
|
||||
'time_sig': measure['length'],
|
||||
'data': submeasure['data'],
|
||||
'properties': measure['properties'],
|
||||
})
|
||||
|
||||
return measuresCorrected
|
||||
|
||||
|
||||
def convertTJAToFumen(fumen, tja):
|
||||
fumen['measures'] = fumen['measures'][9:]
|
||||
tja['measures'] = preprocessTJAMeasures(tja)
|
||||
|
||||
# Fumen offset for the first measure that has a barline
|
||||
fumenOffset1 = float(tja['metadata']['offset']) * -1000
|
||||
fumenOffset = float(tja['metadata']['offset']) * -1000
|
||||
|
||||
# Variables that will change over time due to events
|
||||
currentBPM = 0.0
|
||||
currentGogo = False
|
||||
currentHidden = False
|
||||
currentBranch = 'normal' # TODO: Program in branch support
|
||||
|
||||
# Parse TJA measures to create converted TJA -> Fumen file
|
||||
tjaConverted = {'measures': []}
|
||||
for i, measureTJA in enumerate(tja['measures']):
|
||||
measureFumenExample = fumen['measures'][i+9]
|
||||
for idx_m, measureTJA in enumerate(tja['measures']):
|
||||
measureFumen = deepcopy(default_measure)
|
||||
|
||||
# TODO Event: GOGOTIME
|
||||
|
||||
# TODO Event: HIDDEN
|
||||
|
||||
# TODO Event: BARLINE
|
||||
|
||||
# TODO Event: MEASURE
|
||||
|
||||
# Event: BPMCHANGE
|
||||
# TODO: Handle TJA measure being broken up into multiple Fumen measures due to mid-measure BPM changes
|
||||
midMeasureBPM = [(0, currentBPM)]
|
||||
for event in measureTJA['events']:
|
||||
if event['name'] == 'bpm':
|
||||
currentBPM = float(event['value'])
|
||||
if event['position'] == 0:
|
||||
midMeasureBPM[0] = (0, currentBPM,)
|
||||
else:
|
||||
midMeasureBPM.append((event['position'], currentBPM))
|
||||
if len(midMeasureBPM) > 1:
|
||||
test = None
|
||||
measureFumen['bpm'] = currentBPM
|
||||
|
||||
# TODO: `measureFumen['fumenOffset']
|
||||
# Will need to account for BARLINEON and BARLINEOFF.
|
||||
# Some examples that line up with actual fumen data:
|
||||
# fumenOffset0 = (fumenOffset1 - measureLength)
|
||||
# fumenOffset2 = (fumenOffset1 + measureLength)
|
||||
measureLength = 240_000 / currentBPM
|
||||
# measureFumen['fumenOffset'] = prev['fumenOffset'] + measureLength
|
||||
# Compute the fumenOffset
|
||||
measureLength = measureTJA['pos_end'] - measureTJA['pos_start']
|
||||
if measureLength == measureTJA['subdivisions']:
|
||||
measureRatio = 1.0 # This avoids the case of a measure with no notes --> 0 / 0 --> DivisionByZeroError
|
||||
else:
|
||||
measureRatio = measureLength / measureTJA['subdivisions']
|
||||
measureDuration = (240_000 / measureTJA['bpm']) * measureRatio
|
||||
# This is a bodge I'm using just for Rokuchounen to Ichiya Monogatari
|
||||
# Its first measure happens _before_ the first barline
|
||||
# So, we actually need to shift the offsets by 1 to get everything to line up
|
||||
if idx_m == 0:
|
||||
measureFumen['fumenOffset'] = fumenOffset - measureDuration
|
||||
else:
|
||||
measureFumen['fumenOffset'] = tjaConverted['measures'][-1]['fumenOffset'] + measureDurationNext
|
||||
measureDurationNext = measureDuration
|
||||
|
||||
# Create note dictionaries based on TJA measure data (containing 0's plus 1/2/3/4/etc. for notes)
|
||||
note_counter = 0
|
||||
for i, note_value in enumerate(measureTJA['data']):
|
||||
if note_value != '0':
|
||||
for idx_d, data in enumerate(measureTJA['data']):
|
||||
if data['type'] == 'gogo':
|
||||
pass
|
||||
elif data['type'] == 'hidden':
|
||||
pass
|
||||
elif data['type'] == 'note':
|
||||
note = deepcopy(default_note)
|
||||
note['pos'] = measureLength * (i / len(measureTJA['data']))
|
||||
note['type'] = TJA_NOTE_TYPES[note_value] # TODO: Handle BALLOON/DRUMROLL
|
||||
note['pos'] = measureDuration * (data['pos'] - measureTJA['pos_start']) / measureLength
|
||||
note['type'] = data['value'] # TODO: Handle BALLOON/DRUMROLL
|
||||
note['scoreInit'] = tja['scoreInit'] # Probably not fully accurate
|
||||
note['scoreDiff'] = tja['scoreDiff'] # Probably not fully accurate
|
||||
measureFumen[currentBranch][note_counter] = note
|
||||
note_counter += 1
|
||||
measureFumen[currentBranch]['length'] = note_counter
|
||||
measureFumen['bpm'] = measureTJA['bpm']
|
||||
|
||||
# Append the measure to the tja's list of measures
|
||||
tjaConverted['measures'].append(measureFumen)
|
||||
|
@ -143,9 +143,13 @@ def getCourse(tjaHeaders, lines):
|
||||
measureDividend = int(matchMeasure.group(1))
|
||||
measureDivisor = int(matchMeasure.group(2))
|
||||
elif line['name'] == 'GOGOSTART':
|
||||
measureEvents.append({"name": 'gogoStart', "position": len(measureData)})
|
||||
measureEvents.append({"name": 'gogo', "position": len(measureData), "value": '1'})
|
||||
elif line['name'] == 'GOGOEND':
|
||||
measureEvents.append({"name": 'gogoEnd', "position": len(measureData)})
|
||||
measureEvents.append({"name": 'gogo', "position": len(measureData), "value": '0'})
|
||||
elif line['name'] == 'BARLINEON':
|
||||
measureEvents.append({"name": 'barline', "position": len(measureData), "value": '1'})
|
||||
elif line['name'] == 'BARLINEOFF':
|
||||
measureEvents.append({"name": 'barline', "position": len(measureData), "value": '0'})
|
||||
elif line['name'] == 'SCROLL':
|
||||
measureEvents.append({"name": 'scroll', "position": len(measureData), "value": float(line['value'])})
|
||||
elif line['name'] == 'BPMCHANGE':
|
||||
@ -204,7 +208,7 @@ def getCourse(tjaHeaders, lines):
|
||||
# Search for BPM event in the first measure
|
||||
for i in range(len(measures[0]['events'])):
|
||||
evt = measures[0]['events'][i]
|
||||
if evt.name == 'bpm' and evt.position == 0:
|
||||
if evt['name'] == 'bpm' and evt['position'] == 0:
|
||||
firstBPMEventFound = True
|
||||
# If not present, insert a BPM event into the first measure using the global header metadata
|
||||
if not firstBPMEventFound:
|
||||
|
@ -9,8 +9,8 @@ tja2fumen_version = "v0.1"
|
||||
if __name__ == "__main__":
|
||||
# arguments = parser.parse_args()
|
||||
arguments = {
|
||||
"input_fumen": "./test-data/ia6cho_m.bin", # NB: Contains only oni chart
|
||||
"input_tja": "./test-data/Rokuchounen to Ichiya Monogatari.tja", # NB: Contains 5 charts
|
||||
"input_fumen": "test-data/ia6cho_m.bin", # NB: Contains only oni chart
|
||||
"input_tja": "test-data/Rokuchounen to Ichiya Monogatari.tja", # NB: Contains 5 charts
|
||||
}
|
||||
# Parse fumen
|
||||
inputFile = open(arguments["input_fumen"], "rb")
|
||||
@ -31,4 +31,6 @@ if __name__ == "__main__":
|
||||
|
||||
# Try converting the Oni TJA chart to match the Oni fumen
|
||||
convertedTJA = convertTJAToFumen(parsedSongFumen, parsedSongsTJA['Oni'])
|
||||
# writeFumen(outputFile, convertedTJA)
|
||||
outputName = inputFile.name.split('.')[0] + ".bin"
|
||||
outputFile = open(outputName, "wb")
|
||||
writeFumen(outputFile, convertedTJA)
|
||||
|
Loading…
Reference in New Issue
Block a user