ndspy.soundSequence
: Sound Sequences¶
The ndspy.soundSequence
module contains classes and enumerations related
to SSEQ sound seqeuence files and sound sequence events. If you’re interested
in SSAR sound sequence archive files, you’ll need to use
ndspy.soundSequenceArchive
in addition to this one.
Sound sequences are conceptually similar to MIDI files. A sound sequence is essentially just a list of “events.” Notes are the most common type of event, but there are also a wide variety of events that can change things like volume, panning, and flow of control.
Documentation for sequence event classes can be found on the Sequence Events page.
Note
While the terms “channel” and “track” are often used interchangeably in
other documentation, ndspy consistently uses “channel” to refer to hardware
channels and “track” to refer to SSEQ tracks at the software level, as
defined by the DefineTracksSequenceEvent
.
See also
If you aren’t familiar with how SDAT files are structured, consider reading the appendix explaining this.
- Sequence Events
SequenceEvent
NoteSequenceEvent
RestSequenceEvent
InstrumentSwitchSequenceEvent
BeginTrackSequenceEvent
JumpSequenceEvent
CallSequenceEvent
RandomSequenceEvent
FromVariableSequenceEvent
IfSequenceEvent
VariableAssignmentSequenceEvent
VariableAdditionSequenceEvent
VariableSubtractionSequenceEvent
VariableMultiplicationSequenceEvent
VariableDivisionSequenceEvent
VariableShiftSequenceEvent
VariableRandSequenceEvent
VariableUnknownB7SequenceEvent
VariableEqualSequenceEvent
VariableGreaterThanOrEqualSequenceEvent
VariableGreaterThanSequenceEvent
VariableLessThanOrEqualSequenceEvent
VariableLessThanSequenceEvent
VariableNotEqualSequenceEvent
PanSequenceEvent
TrackVolumeSequenceEvent
GlobalVolumeSequenceEvent
TransposeSequenceEvent
PortamentoSequenceEvent
PortamentoRangeSequenceEvent
TrackPrioritySequenceEvent
MonoPolySequenceEvent
MonoPolySequenceEvent.Value
TieSequenceEvent
PortamentoFromSequenceEvent
VibratoDepthSequenceEvent
VibratoSpeedSequenceEvent
VibratoTypeSequenceEvent
VibratoTypeSequenceEvent.Value
VibratoRangeSequenceEvent
PortamentoOnOffSequenceEvent
PortamentoDurationSequenceEvent
AttackRateSequenceEvent
DecayRateSequenceEvent
SustainRateSequenceEvent
ReleaseRateSequenceEvent
BeginLoopSequenceEvent
ExpressionSequenceEvent
PrintVariableSequenceEvent
VibratoDelaySequenceEvent
TempoSequenceEvent
SweepPitchSequenceEvent
EndLoopSequenceEvent
ReturnSequenceEvent
DefineTracksSequenceEvent
EndTrackSequenceEvent
RawDataSequenceEvent
Parsed and Unparsed SSEQs and SSARs¶
In general, SSEQ and SSAR events data (hereafter referred to as just “SSEQ files”, since they’re both the same in this regard) cannot always be represented as a list of event objects. Not only does one encounter the halting problem because SSEQ supports variables and conditional branching, but sequence events could conceivably use overlapping data (although this has never actually been seen in practice). This complicates ndspy’s efforts to be both easy-to-use and compatible with a wide range of valid input files.
On one hand, most real-life SSEQ files don’t have a very complicated structure, and their events data usually can in fact be represented as a list of events. Being able to access a SSEQ's data in this way is very intuitive and powerful.
On the other hand, ndspy should be useful for editing any valid SSEQ file, including ones with events data too complicated for it to parse correctly. It should therefore let users see and manipulate the raw binary events data if they want to, or if it can’t be parsed automatically.
To handle this, in ndspy, a SSEQ can be in one of two states: “unparsed” or “parsed.” An unparsed SSEQ can become parsed, but once a SSEQ has been parsed, it cannot go back to being unparsed.
A SSEQ loaded from file data (including file data within a SDAT) will
initially be “unparsed.” At this stage, ndspy has not yet attempted to parse
the events data, and that data can be found in the .eventsData
attribute.
If you want to access events data as a list, you need to call the
.parse()
function on the SSEQ to switch it to the “parsed” state. This
function (largely powered by readsoundSequence-events()
) attempts to
parse the events data; if it’s succcessful, the events will be placed in the
.events
attribute. The function is pretty intelligent, but it nevertheless
has a relatively high failure rate due to the sheer complexity of events data,
so it’s good practice to wrap the call in a try:
/except Exception:
block. If it throws an exception, the SSEQ will remain in the unparsed state.
You can check the state of a SSEQ using the .parsed
attribute.
Once a SSEQ has been parsed, it can never be unparsed (at least not
directly), and .eventsData
becomes inaccessible. If you really need
to work with binary event data from a parsed SSEQ, a technique that might
work for you is to call .save()
and then create a new SSEQ
(or
SSAR
) object from the resulting file data. Be aware, though, that
there’s no guarantee that saving an unmodified SSEQ will reproduce the
original file data exactly, especially if it’s been parsed.
Multi-track Sequences¶
An SSEQ or SSAR sequence can have up to 16 tracks. The game will automatically begin executing the sequence event data at the very beginning (or, for SSARs, at whichever event the sequence indicates) on track 0.
If you intend to use more than one track, you must have a
DefineTracksSequenceEvent
as the first event in your sequence. This
declares all of the track IDs you intend to use. This should be followed by one
BeginTrackSequenceEvent
per track (except for track 0), all in a
row, which state where the events for each track begin in the events list.
Since track 0 is the default track which is executing all of these
track-definition events, don’t add a BeginTrackSequenceEvent
for it – just put its events starting immediately after the final
BeginTrackSequenceEvent
.
Sequence Variables¶
The sequence player for SSEQ and SSAR keeps track of an array of 16-bit [1] signed [2] integers [3] that you can use for whatever you like. These are known as sequence variables (or just “variables”), and are referenced by ID number (array index).
You can use the following sequence events to perform mathematical operations on variables:
(variable) = value
:VariableAssignmentSequenceEvent
(variable) += value
:VariableAdditionSequenceEvent
(variable) -= value
:VariableSubtractionSequenceEvent
(variable) *= value
:VariableMultiplicationSequenceEvent
(variable) /= value
:VariableDivisionSequenceEvent
(variable) <<= value
:VariableShiftSequenceEvent
(variable) = random_int_between(0, value)
:VariableRandSequenceEvent
Once you have values in variables, there are two [4] primary ways you can use them:
Using Variable Values as Sequence Event Arguments¶
FromVariableSequenceEvent
lets you use a variable’s value as the
last argument to some other sequence event.
Conditional Execution¶
In addition to variables, the sequence player also keeps track of a conditional flag that can be used to skip over certain sequence events. The following sequence events update the conditional flag:
condFlag = ((variable) == value)
VariableEqualSequenceEvent
condFlag = ((variable) >= value)
VariableGreaterThanOrEqualSequenceEvent
condFlag = ((variable) > value)
VariableGreaterThanSequenceEvent
condFlag = ((variable) <= value)
VariableLessThanOrEqualSequenceEvent
condFlag = ((variable) < value)
VariableLessThanSequenceEvent
condFlag = ((variable) != value)
VariableNotEqualSequenceEvent
After running one of these, you can use an IfSequenceEvent
to
perform conditional execution – the sequence event immediately following the
IfSequenceEvent
will be skiped if the conditional flag is false.
Todo
How many variables exist?
Can we double-check that variables are per-sequence rather than per-track?
How about the conditional flag?
- class ndspy.soundSequence.SSEQ([file[, unk02[, bankID[, volume[, channelPressure[, polyphonicPressure[, playerID]]]]]]])¶
A SSEQ sequence file. This is a piece of music, usually used for background music or jingles (such as the “you died” theme in New Super Mario Bros.).
- Parameters:
file (bytes) – The data to be read as an SSEQ file. If this is not provided, the SSEQ object will initially be empty.
unk02 – The initial value for the
unk02
attribute.bankID – The initial value for the
bankID
attribute.volume – The initial value for the
volume
attribute.channelPressure – The initial value for the
channelPressure
attribute.polyphonicPressure – The initial value for the
polyphonicPressure
attribute.playerID – The initial value for the
playerID
attribute.
- channelPressure¶
The channel pressure for the sequence. The exact meaning of this is unclear.
- Type:
- Default:
64
- dataMergeOptimizationID¶
When saving a SDAT file containing multiple SSEQ files, ndspy will check if any of them save to identical data. If it finds any, it will only encode the data for them once and then reference it multiple times, to save some space. This attribute is an extra field that is also compared between SSEQ files, which you can use to exclude particular ones from this optimization.
Since this defaults to 0 for all SSEQs created from scratch, this optimization will happen by default. It’s unlikely that you will need to use this attribute to disable the optimization, but you can.
Note
This value is not explicitly saved in the SSEQ file or in the SDAT file containing it.
- Type:
- Default:
0
- events¶
The list of sequence events contained in this SSEQ. This is only available in parsed SSEQs (ones with
parsed
set toTrue
).See also
Parsed and Unparsed SSEQs and SSARs – the introductory text explaining the difference between parsed and unparsed SSEQs.
eventsData
– the equivalent attribute that is available before parsing.- Type:
- Default:
[]
- eventsData¶
The raw event data contained in this SSEQ. This is only available in unparsed SSEQs (ones with
parsed
set toFalse
).See also
Parsed and Unparsed SSEQs and SSARs – the introductory text explaining the difference between parsed and unparsed SSEQs.
events
– the equivalent attribute that becomes available after parsing.- Type:
- parsed¶
Whether
parse()
has ever been called on this SSEQ object. This determines whethereventsData
orevents
is available.This attribute is read-only.
See also
Parsed and Unparsed SSEQs and SSARs – the introductory text explaining the difference between parsed and unparsed SSEQs.
- Type:
- Default:
True
- playerID¶
The ID of the sequence player that will be used to play this sequence.
- Type:
- Default:
0
- polyphonicPressure¶
The polyphonic pressure for the sequence. The exact meaning of this is unclear.
- Type:
- Default:
50
- unk02¶
The value following the SSEQ’s file ID in the “INFO” section of the SDAT file it is contained in. Its purpose is unknown.
Note
This value is not explicitly saved in the SSEQ file, but it is saved in the SDAT file if the SSEQ is within one.
- Type:
- Default:
0
- volume¶
The overall volume of the sequence. This is an integer between 0 and 127, inclusive. It’s a good idea to leave this set to 127 and adjust volume using other, more precise methods (such as setting the track volume or individual note velocities).
- Type:
- Default:
127
- classmethod fromEvents(events[, unk02[, bankID[, volume[, channelPressure[, polyphonicPressure[, playerID]]]]]])¶
Create a new SSEQ object from a list of sequence events.
- Parameters:
events (
list
ofSequenceEvent
) – The list of sequence events in the new SSEQ.unk02 – The initial value for the
unk02
attribute.bankID – The initial value for the
bankID
attribute.volume – The initial value for the
volume
attribute.channelPressure – The initial value for the
channelPressure
attribute.polyphonicPressure – The initial value for the
polyphonicPressure
attribute.playerID – The initial value for the
playerID
attribute.
- classmethod fromFile(filePath[, ...])¶
Load an SSEQ from a filesystem file. This is a convenience function.
- Parameters:
filePath (
str
or other path-like object) – The path to the SSEQ file to open.
Further parameters are the same as those of the default constructor.
- Returns:
The SSEQ object.
- Return type:
- parse()¶
Attempt to process
eventsData
to createevents
. If successful, this switches the SSEQ from the unparsed to the parsed state (see Parsed and Unparsed SSEQs and SSARs for a more detailed explanation).Parsing events data is complex and even completely impossible in some cases. If unsuccessful, this function will raise an exception and the SSEQ will remain in the unparsed state.
This function is idempotent, meaning that calling it on a SSEQ already in the parsed state will do nothing.
- save()¶
Generate file data representing this SSEQ, and then return that data,
unk02
,bankID
,volume
,channelPressure
,polyphonicPressure
, andplayerID
as a 7-tuple. This matches the parameters of the default class constructor.- Returns:
The SSEQ file data,
unk02
,bankID
,volume
,channelPressure
,polyphonicPressure
, andplayerID
.- Return type:
(data, unk02, bankID, volume, channelPressure, polyphonicPressure, playerID)
, wheredata
is of typebytes
and all of the other elements are of typeint
- ndspy.soundSequence.printSequenceEventList(events[, labels[, linePrefix]])¶
Produce a string representation of a list of sequence events. You can optionally provide a dictionary of labels to mark certain events, and a prefix string that will be prepended to every line.
Note
This is a relatively low-level function, mainly intended to power
SSEQ
andndspy.soundSequenceArchive.SSAR
. If you’re using those classes, you can simply call thestr()
function on them to get a nice printout of their contents instead of calling this function directly.- Parameters:
events (
list
ofSequenceEvent
) – The sequence events to be printed.labels (
dict
:{name: event}
(wherename
is of typestr
andevent
is of typeSequenceEvent
)) –A dictionary containing any labels you would like to apply to particular events in the output string. If you specify multiple labels for the same event, all of them will be included. You can also provide entries with values set to
None
; these labels will be included in the output without pointing to any event.- default:
{}
linePrefix (
str
) –A string that will be prepended to every line in the output string. (This is mainly useful for indenting the string.)
- default:
''
- Returns:
A string representing the list of sequence events.
- Return type:
- readsoundSequence-events(data[, notableOffsets])
Convert raw sequence event data (as seen in SSEQ and SSAR files) to a list of
SequenceEvent
objects. This is the inverse ofsavesoundSequence-events()
.A second list will also be returned that contains the elements from the first list that appeared in the input data at the offsets given in
notableOffsets
. This is useful if the data can be played from multiple different starting offsets, as is the case with SSAR files. It is safe to assume that every element of this second list is also an element of the first list, and that the length of the second list will match the length ofnotableOffsets
.Note
This is a relatively low-level function. Most of the time, you should use the
SSEQ
andndspy.soundSequenceArchive.SSAR
classes, which call this function for you in their respectiveparse()
methods.Warning
Parsing events data into a list of event objects is complex, and even completely impossible in some extreme cases. As such, you should wrap calls to this function in
try
/except Exception:
blocks, and implement fallback strategies in case the call fails.- Parameters:
- Returns:
A list of sequence events that represents the data, and a list containing references to the event objects that were indicated in the
notableOffsets
argument.- Return type:
(events, notableEvents)
, where both elements arelist
s ofSequenceEvent
- savesoundSequence-events(events[, notableEvents])
Convert a list of
SequenceEvent
objects to raw sequence event data. This is the inverse ofreadsoundSequence-events()
.A second list will also be returned that contains the offsets in the output data of the elements from
notableEvents
. This is useful if the data can be played from multiple different starting points, as is the case with SSAR files. Every element ofnotableEvents
must also appear inevents
. It is safe to assume that the length of the second list will match the length ofnotableEvents
.Note
This is a relatively low-level function. Most of the time, you should use the
SSEQ
andndspy.soundSequenceArchive.SSAR
classes, which call this function for you in their respectivesave()
methods.- Parameters:
events (
list
ofSequenceEvent
) – The sequence events to be saved.notableEvents (
list
ofSequenceEvent
) –A list of sequence events in
events
for which you would like to know the offsets in the output data.- default:
[]
- Returns:
The raw sequence event data, and a list containing offsets into it that point to the events given in the
notableEvents
argument.- Return type:
(data, notableOffsets)
, wheredata
is of typebytes
andnotableOffsets
is alist
ofint