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:

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:

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 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.
bankID

The ID of the instrument bank (SBNK) that this sequence will use.

Type:int
Default:0
channelPressure

The channel pressure for the sequence. The exact meaning of this is unclear.

Type:int
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:int
Default:0
events

The list of sequence events contained in this SSEQ. This is only available in parsed SSEQs (ones with parsed set to True).

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:list of SequenceEvent
Default:[]
eventsData

The raw event data contained in this SSEQ. This is only available in unparsed SSEQs (ones with parsed set to False).

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 whether eventsData or events 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:bool
Default:True
playerID

The ID of the sequence player that will be used to play this sequence.

Type:int
Default:0
polyphonicPressure

The polyphonic pressure for the sequence. The exact meaning of this is unclear.

Type:int
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:int
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:int
Default:127
classmethod fromEvents(events[, unk02[, bankID[, volume[, channelPressure[, polyphonicPressure[, playerID]]]]]])[source]

Create a new SSEQ object from a list of sequence events.

Parameters:
  • events (list of SequenceEvent) – 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[, ...])[source]

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:SSEQ
parse()[source]

Attempt to process eventsData to create events. 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, and playerID as a 7-tuple. This matches the parameters of the default class constructor.

Returns:The SSEQ file data, unk02, bankID, volume, channelPressure, polyphonicPressure, and playerID.
Return type:(data, unk02, bankID, volume, channelPressure, polyphonicPressure, playerID), where data is of type bytes and all of the other elements are of type int
saveToFile(filePath)[source]

Generate file data representing this SSEQ, and save it to a filesystem file. This is a convenience function.

Parameters:filePath (str or other path-like object) – The path to the SSEQ file to save to.
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 SSEQ and ndspy.soundSequenceArchive.SSAR. If you’re using those classes, you can simply call the str() function on them to get a nice printout of their contents instead of calling this function directly.

Parameters:
  • events (list of SequenceEvent) – The sequence events to be printed.
  • labels (dict: {name: event} (where name is of type str and event is of type SequenceEvent)) –

    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:

str

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 of savesoundSequence-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 of notableOffsets.

Note

This is a relatively low-level function. Most of the time, you should use the SSEQ and ndspy.soundSequenceArchive.SSAR classes, which call this function for you in their respective parse() 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:
  • data (bytes) – The raw sequence events data.
  • notableOffsets (list of int) –

    A list of offsets into the data that point to sequence events you’d like to receive references to.

    default:[]
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 are lists of SequenceEvent

savesoundSequence-events(events[, notableEvents])

Convert a list of SequenceEvent objects to raw sequence event data. This is the inverse of readsoundSequence-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 of notableEvents must also appear in events. It is safe to assume that the length of the second list will match the length of notableEvents.

Note

This is a relatively low-level function. Most of the time, you should use the SSEQ and ndspy.soundSequenceArchive.SSAR classes, which call this function for you in their respective save() methods.

Parameters:
  • events (list of SequenceEvent) – The sequence events to be saved.
  • notableEvents (list of SequenceEvent) –

    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), where data is of type bytes and notableOffsets is a list of int