Migrating from simfile 1.0 to 2.0
Version 1.0 of the simfile library was released in 2013. It only supported SM files and was primarily developed for Python 2, with support for Python 3 on a separate branch.
Version 2.0 is a near-complete rewrite of the library exclusively for Python 3, with SSC support as the flagship feature. Aside from new features, the design of the library has changed significantly to bring it in line with similar modern Python libraries.
Simfile & chart classes
In 1.0, the simfile & chart classes were simfile.Simfile
and
simfile.Chart
.
In 2.0, the simfile & chart classes are split by simfile type:
For SM files, the
simfile.sm
module providesSMSimfile
andSMChart
.For SSC files, the
simfile.ssc
module providesSSCSimfile
andSSCChart
.
Additionally, simfile.types
provides the union types Simfile
and Chart
, which are used to annotate parameters & return types where
either implementation is acceptable.
Reading simfiles
In 1.0, the Simfile
constructor accepted a filename or file object, and
a .from_string
class method handled loading from string data:
>>> from simfile import Simfile # 1.0
>>> from_filename = Simfile('testdata/nekonabe/nekonabe.sm')
>>> # or...
>>> with open('testdata/nekonabe/nekonabe.sm', 'r') as infile:
... from_file_obj = Simfile(infile)
...
>>> # or...
>>> from_string = Simfile.from_string(str(from_file_obj))
In 2.0, each of these options has a corresponding function in the top-level
simfile
module:
>>> import simfile # 2.0
>>> from_filename = simfile.open('testdata/nekonabe/nekonabe.sm')
>>> # or...
>>> with open('testdata/nekonabe/nekonabe.sm', 'r') as infile:
... from_file_obj = simfile.load(infile)
...
>>> # or...
>>> from_string = simfile.loads(str(from_file_obj))
These methods determine which simfile format to use automatically, but you can
alternatively instantiate the simfile classes directly. They take a named
file
or string
argument:
>>> from simfile.sm import SMSimfile # 2.0
>>> with open('testdata/nekonabe/nekonabe.sm', 'r') as infile:
... from_file_obj = SMSimfile(file=infile)
...
>>> # or...
>>> from_string = SMSimfile(string=str(from_file_obj))
Writing simfiles
In 1.0, simfile objects had a .save
method that took a maybe-optional
filename parameter:
>>> from simfile import Simfile # 1.0
>>> from_filename = Simfile('testdata/nekonabe/nekonabe.sm') # filename supplied
>>> from_filename.save() # no problem!
>>> from_string = Simfile.from_string(str(from_filename)) # no filename supplied
>>> try:
... from_string.save() # to where?
... except ValueError:
... from_string.save('testdata/nekonabe/nekonabe.sm') # much better 🙄
In 2.0, simfile objects no longer know their own filenames. Either pass a file
object to the simfile’s serialize()
method or
use simfile.mutate()
for a more guided workflow.
Finding charts
In 1.0, the list of charts at Simfile.charts
offered convenience
methods for getting a single chart or finding multiple charts:
>>> from simfile import Simfile # 1.0
>>> sm = Simfile('testdata/nekonabe/nekonabe.sm')
>>> single_novice = sm.charts.get(difficulty='Beginner')
>>> single_novice.stepstype
dance-single
>>> expert_charts = sm.charts.filter(difficulty='Challenge')
>>> [ex.stepstype for ex in expert_charts]
['dance-double', 'dance-single']
In 2.0, these convenience methods have been removed in favor of for-loops and
the built-in filter
function. Writing your own predicates as Python
code is much more flexibile than the 1.0 convenience methods, which could only
find charts by exact property matches.
Special property types
In 1.0, certain properties of simfiles and charts were automatically converted from strings to richer representations.
The “BPMS” and “STOPS” simfile parameters were converted to
Timing
objects that offered convenient access to the beat & value pairs:>>> from simfile import Simfile # 1.0 >>> sm = Simfile('testdata/nekonabe/nekonabe.sm') >>> print(type(sm['BPMS'])) <class 'simfile.simfile.Timing'> >>> print(type(sm['STOPS'])) <class 'simfile.simfile.Timing'>
The “meter” and “notes” chart attributes were converted to an integer and a
Notes
object, respectively:>>> from simfile import Simfile # 1.0 >>> sm = Simfile('testdata/nekonabe/nekonabe.sm') >>> chart = sm.charts[0] >>> print(type(chart.meter)) <class 'int'> >>> print(type(chart.notes)) <class 'simfile.simfile.Notes'>
In 2.0, all properties of simfiles and charts are kept as strings. This prevents wasting CPU cycles for use cases that don’t benefit from the richer representations, keeps the underlying data structures homogeneously typed, and significantly reduces the number of reasons why parsing a simfile might fail.
If you need rich timing data, use the simfile.timing
package:
>>> import simfile # 2.0
>>> from simfile.timing import TimingData
>>> nekonabe = simfile.open('testdata/nekonabe/nekonabe.sm')
>>> timing_data = TimingData(nekonabe)
>>> print(timing_data.bpms[0])
BeatValue(beat=Beat(0), value=Decimal('150.000'))
If you need rich note data, use the simfile.notes
package:
>>> import simfile # 2.0
>>> from simfile.notes import NoteData
>>> from simfile.timing import Beat
>>> nekonabe = simfile.open('testdata/nekonabe/nekonabe.sm')
>>> for note in NoteData(nekonabe.charts[0]):
... if note.beat > Beat(18): break
... print(note)
...
Note(beat=Beat(16.25), column=3, note_type=NoteType.TAP)
Note(beat=Beat(16.5), column=2, note_type=NoteType.TAP)
Note(beat=Beat(17.25), column=2, note_type=NoteType.TAP)
Note(beat=Beat(17.5), column=3, note_type=NoteType.TAP)
Keeping these modules separate from the core simfile & chart classes enables them to be much more fully-featured than their 1.0 counterparts.