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.
Subpages
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)VariableEqualSequenceEventcondFlag = ((variable) >= value)VariableGreaterThanOrEqualSequenceEventcondFlag = ((variable) > value)VariableGreaterThanSequenceEventcondFlag = ((variable) <= value)VariableLessThanOrEqualSequenceEventcondFlag = ((variable) < value)VariableLessThanSequenceEventcondFlag = ((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?
| [1] | This has been proven by checking that 0xFFFF + 1 == 0. |
| [2] | This has been proven by checking that 1 - 2 < 0. |
| [3] | This has been proven by checking that (3 / 2) * 2 == 2. |
| [4] | There also exists a PrintVariableSequenceEvent, which is not
well-understood. |
-
class
ndspy.soundSequence.SSEQ([file[, unk02[, bankID[, volume[, channelPressure[, polyphonicPressure[, playerID]]]]]]])[source]¶ 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
unk02attribute. - bankID – The initial value for the
bankIDattribute. - volume – The initial value for the
volumeattribute. - channelPressure – The initial value for the
channelPressureattribute. - polyphonicPressure – The initial value for the
polyphonicPressureattribute. - playerID – The initial value for the
playerIDattribute.
-
channelPressure¶ The channel pressure for the sequence. The exact meaning of this is unclear.
Type: intDefault: 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: intDefault: 0
-
events¶ The list of sequence events contained in this SSEQ. This is only available in parsed SSEQs (ones with
parsedset 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: listofSequenceEventDefault: []
-
eventsData¶ The raw event data contained in this SSEQ. This is only available in unparsed SSEQs (ones with
parsedset 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: bytes
-
parsed¶ Whether
parse()has ever been called on this SSEQ object. This determines whethereventsDataoreventsis 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: boolDefault: True
-
polyphonicPressure¶ The polyphonic pressure for the sequence. The exact meaning of this is unclear.
Type: intDefault: 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: intDefault: 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: intDefault: 127
-
classmethod
fromEvents(events[, unk02[, bankID[, volume[, channelPressure[, polyphonicPressure[, playerID]]]]]])[source]¶ Create a new SSEQ object from a list of sequence events.
Parameters: - events (
listofSequenceEvent) – The list of sequence events in the new SSEQ. - unk02 – The initial value for the
unk02attribute. - bankID – The initial value for the
bankIDattribute. - volume – The initial value for the
volumeattribute. - channelPressure – The initial value for the
channelPressureattribute. - polyphonicPressure – The initial value for the
polyphonicPressureattribute. - playerID – The initial value for the
playerIDattribute.
- events (
-
classmethod
fromFile(filePath[, ...])[source]¶ Load an SSEQ from a filesystem file. This is a convenience function.
Parameters: filePath ( stror 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: SSEQ
-
parse()[source]¶ Attempt to process
eventsDatato 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()[source]¶ Generate file data representing this SSEQ, and then return that data,
unk02,bankID,volume,channelPressure,polyphonicPressure, andplayerIDas 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), wheredatais of typebytesand all of the other elements are of typeint
-
ndspy.soundSequence.printSequenceEventList(events[, labels[, linePrefix]])[source]¶ 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
SSEQandndspy.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 (
listofSequenceEvent) – The sequence events to be printed. - labels (
dict:{name: event}(wherenameis of typestrandeventis 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: - events (
-
readsoundSequence-events(data[, notableOffsets]) Convert raw sequence event data (as seen in SSEQ and SSAR files) to a list of
SequenceEventobjects. 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
SSEQandndspy.soundSequenceArchive.SSARclasses, 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
notableOffsetsargument.Return type: (events, notableEvents), where both elements arelists ofSequenceEvent
-
savesoundSequence-events(events[, notableEvents]) Convert a list of
SequenceEventobjects 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 ofnotableEventsmust 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
SSEQandndspy.soundSequenceArchive.SSARclasses, which call this function for you in their respectivesave()methods.Parameters: - events (
listofSequenceEvent) – The sequence events to be saved. - notableEvents (
listofSequenceEvent) –A list of sequence events in
eventsfor 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
notableEventsargument.Return type: (data, notableOffsets), wheredatais of typebytesandnotableOffsetsis alistofint- events (