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_GLOBAL = ['TITLE', 'TITLEJA', 'SUBTITLE', 'SUBTITLEJA', 'BPM', 'WAVE', 'OFFSET', 'DEMOSTART', 'GENRE']
|
||||||
HEADER_COURSE = ['COURSE', 'LEVEL', 'BALLOON', 'SCOREINIT', 'SCOREDIFF', 'TTRO' 'WBEAT']
|
HEADER_COURSE = ['COURSE', 'LEVEL', 'BALLOON', 'SCOREINIT', 'SCOREDIFF', 'TTRO' 'WBEAT']
|
||||||
BRANCH_COMMANDS = ['START', 'END', 'BRANCHSTART', 'BRANCHEND', 'N', 'E', 'M']
|
BRANCH_COMMANDS = ['START', 'END', 'BRANCHSTART', 'BRANCHEND', 'N', 'E', 'M']
|
||||||
MEASURE_COMMANDS = ['MEASURE', 'GOGOSTART', 'GOGOEND', 'SCROLL', 'BPMCHANGE', 'TTBREAK' 'LEVELHOLD']
|
MEASURE_COMMANDS = ['MEASURE', 'GOGOSTART', 'GOGOEND', 'BARLINEON', 'BARLINEOFF', 'SCROLL', 'BPMCHANGE', 'TTBREAK' 'LEVELHOLD']
|
||||||
UNUSED_COMMANDS = ['DELAY', 'SECTION', 'BMSCROLL', 'HBSCROLL', 'BARLINEON', 'BARLINEOFF']
|
UNUSED_COMMANDS = ['DELAY', 'SECTION', 'BMSCROLL', 'HBSCROLL']
|
||||||
COMMAND = BRANCH_COMMANDS + MEASURE_COMMANDS + UNUSED_COMMANDS
|
COMMAND = BRANCH_COMMANDS + MEASURE_COMMANDS + UNUSED_COMMANDS
|
||||||
|
|
||||||
# Note types for TJA files
|
# 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):
|
def convertTJAToFumen(fumen, tja):
|
||||||
|
fumen['measures'] = fumen['measures'][9:]
|
||||||
|
tja['measures'] = preprocessTJAMeasures(tja)
|
||||||
|
|
||||||
# Fumen offset for the first measure that has a barline
|
# 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
|
# Variables that will change over time due to events
|
||||||
currentBPM = 0.0
|
|
||||||
currentGogo = False
|
currentGogo = False
|
||||||
currentHidden = False
|
currentHidden = False
|
||||||
currentBranch = 'normal' # TODO: Program in branch support
|
currentBranch = 'normal' # TODO: Program in branch support
|
||||||
|
|
||||||
# Parse TJA measures to create converted TJA -> Fumen file
|
# Parse TJA measures to create converted TJA -> Fumen file
|
||||||
tjaConverted = {'measures': []}
|
tjaConverted = {'measures': []}
|
||||||
for i, measureTJA in enumerate(tja['measures']):
|
for idx_m, measureTJA in enumerate(tja['measures']):
|
||||||
measureFumenExample = fumen['measures'][i+9]
|
|
||||||
measureFumen = deepcopy(default_measure)
|
measureFumen = deepcopy(default_measure)
|
||||||
|
|
||||||
# TODO Event: GOGOTIME
|
# Compute the fumenOffset
|
||||||
|
measureLength = measureTJA['pos_end'] - measureTJA['pos_start']
|
||||||
# TODO Event: HIDDEN
|
if measureLength == measureTJA['subdivisions']:
|
||||||
|
measureRatio = 1.0 # This avoids the case of a measure with no notes --> 0 / 0 --> DivisionByZeroError
|
||||||
# TODO Event: BARLINE
|
else:
|
||||||
|
measureRatio = measureLength / measureTJA['subdivisions']
|
||||||
# TODO Event: MEASURE
|
measureDuration = (240_000 / measureTJA['bpm']) * measureRatio
|
||||||
|
# This is a bodge I'm using just for Rokuchounen to Ichiya Monogatari
|
||||||
# Event: BPMCHANGE
|
# Its first measure happens _before_ the first barline
|
||||||
# TODO: Handle TJA measure being broken up into multiple Fumen measures due to mid-measure BPM changes
|
# So, we actually need to shift the offsets by 1 to get everything to line up
|
||||||
midMeasureBPM = [(0, currentBPM)]
|
if idx_m == 0:
|
||||||
for event in measureTJA['events']:
|
measureFumen['fumenOffset'] = fumenOffset - measureDuration
|
||||||
if event['name'] == 'bpm':
|
else:
|
||||||
currentBPM = float(event['value'])
|
measureFumen['fumenOffset'] = tjaConverted['measures'][-1]['fumenOffset'] + measureDurationNext
|
||||||
if event['position'] == 0:
|
measureDurationNext = measureDuration
|
||||||
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
|
|
||||||
|
|
||||||
# Create note dictionaries based on TJA measure data (containing 0's plus 1/2/3/4/etc. for notes)
|
# Create note dictionaries based on TJA measure data (containing 0's plus 1/2/3/4/etc. for notes)
|
||||||
note_counter = 0
|
note_counter = 0
|
||||||
for i, note_value in enumerate(measureTJA['data']):
|
for idx_d, data in enumerate(measureTJA['data']):
|
||||||
if note_value != '0':
|
if data['type'] == 'gogo':
|
||||||
|
pass
|
||||||
|
elif data['type'] == 'hidden':
|
||||||
|
pass
|
||||||
|
elif data['type'] == 'note':
|
||||||
note = deepcopy(default_note)
|
note = deepcopy(default_note)
|
||||||
note['pos'] = measureLength * (i / len(measureTJA['data']))
|
note['pos'] = measureDuration * (data['pos'] - measureTJA['pos_start']) / measureLength
|
||||||
note['type'] = TJA_NOTE_TYPES[note_value] # TODO: Handle BALLOON/DRUMROLL
|
note['type'] = data['value'] # TODO: Handle BALLOON/DRUMROLL
|
||||||
note['scoreInit'] = tja['scoreInit'] # Probably not fully accurate
|
note['scoreInit'] = tja['scoreInit'] # Probably not fully accurate
|
||||||
note['scoreDiff'] = tja['scoreDiff'] # Probably not fully accurate
|
note['scoreDiff'] = tja['scoreDiff'] # Probably not fully accurate
|
||||||
measureFumen[currentBranch][note_counter] = note
|
measureFumen[currentBranch][note_counter] = note
|
||||||
note_counter += 1
|
note_counter += 1
|
||||||
measureFumen[currentBranch]['length'] = note_counter
|
measureFumen[currentBranch]['length'] = note_counter
|
||||||
|
measureFumen['bpm'] = measureTJA['bpm']
|
||||||
|
|
||||||
# Append the measure to the tja's list of measures
|
# Append the measure to the tja's list of measures
|
||||||
tjaConverted['measures'].append(measureFumen)
|
tjaConverted['measures'].append(measureFumen)
|
||||||
|
@ -143,9 +143,13 @@ def getCourse(tjaHeaders, lines):
|
|||||||
measureDividend = int(matchMeasure.group(1))
|
measureDividend = int(matchMeasure.group(1))
|
||||||
measureDivisor = int(matchMeasure.group(2))
|
measureDivisor = int(matchMeasure.group(2))
|
||||||
elif line['name'] == 'GOGOSTART':
|
elif line['name'] == 'GOGOSTART':
|
||||||
measureEvents.append({"name": 'gogoStart', "position": len(measureData)})
|
measureEvents.append({"name": 'gogo', "position": len(measureData), "value": '1'})
|
||||||
elif line['name'] == 'GOGOEND':
|
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':
|
elif line['name'] == 'SCROLL':
|
||||||
measureEvents.append({"name": 'scroll', "position": len(measureData), "value": float(line['value'])})
|
measureEvents.append({"name": 'scroll', "position": len(measureData), "value": float(line['value'])})
|
||||||
elif line['name'] == 'BPMCHANGE':
|
elif line['name'] == 'BPMCHANGE':
|
||||||
@ -204,7 +208,7 @@ def getCourse(tjaHeaders, lines):
|
|||||||
# Search for BPM event in the first measure
|
# Search for BPM event in the first measure
|
||||||
for i in range(len(measures[0]['events'])):
|
for i in range(len(measures[0]['events'])):
|
||||||
evt = measures[0]['events'][i]
|
evt = measures[0]['events'][i]
|
||||||
if evt.name == 'bpm' and evt.position == 0:
|
if evt['name'] == 'bpm' and evt['position'] == 0:
|
||||||
firstBPMEventFound = True
|
firstBPMEventFound = True
|
||||||
# If not present, insert a BPM event into the first measure using the global header metadata
|
# If not present, insert a BPM event into the first measure using the global header metadata
|
||||||
if not firstBPMEventFound:
|
if not firstBPMEventFound:
|
||||||
|
@ -9,8 +9,8 @@ tja2fumen_version = "v0.1"
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# arguments = parser.parse_args()
|
# arguments = parser.parse_args()
|
||||||
arguments = {
|
arguments = {
|
||||||
"input_fumen": "./test-data/ia6cho_m.bin", # NB: Contains only oni chart
|
"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_tja": "test-data/Rokuchounen to Ichiya Monogatari.tja", # NB: Contains 5 charts
|
||||||
}
|
}
|
||||||
# Parse fumen
|
# Parse fumen
|
||||||
inputFile = open(arguments["input_fumen"], "rb")
|
inputFile = open(arguments["input_fumen"], "rb")
|
||||||
@ -31,4 +31,6 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Try converting the Oni TJA chart to match the Oni fumen
|
# Try converting the Oni TJA chart to match the Oni fumen
|
||||||
convertedTJA = convertTJAToFumen(parsedSongFumen, parsedSongsTJA['Oni'])
|
convertedTJA = convertTJAToFumen(parsedSongFumen, parsedSongsTJA['Oni'])
|
||||||
# writeFumen(outputFile, convertedTJA)
|
outputName = inputFile.name.split('.')[0] + ".bin"
|
||||||
|
outputFile = open(outputName, "wb")
|
||||||
|
writeFumen(outputFile, convertedTJA)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user