ndspy.soundBank: Instrument Banks

The ndspy.soundBank module contains classes and functions related to the SBNK instrument bank file included in SDAT sound archives.

See also

If you aren’t familiar with how SDAT files are structured, consider reading the appendix explaining this.

ndspy.soundBank.NO_INSTRUMENT_TYPE

The type value of a nonexistent instrument: 0.

ndspy.soundBank.SINGLE_NOTE_PCM_INSTRUMENT_TYPE

The type value of a SingleNoteInstrument that plays a SWAV from a SWAR: 1.

ndspy.soundBank.SINGLE_NOTE_PSG_SQUARE_WAVE_INSTRUMENT_TYPE

The type value of a SingleNoteInstrument that plays a square wave using the Nintendo DS’s PSG hardware: 2.

ndspy.soundBank.SINGLE_NOTE_PSG_WHITE_NOISE_INSTRUMENT_TYPE

The type value of a SingleNoteInstrument that plays white noise using the Nintendo DS’s PSG hardware: 3.

ndspy.soundBank.RANGE_INSTRUMENT_TYPE

The type value of a RangeInstrument: 16.

ndspy.soundBank.REGIONAL_INSTRUMENT_TYPE

The type value of a RegionalInstrument: 17.

class ndspy.soundBank.NoteType
Base class:

enum.IntEnum

An enumeration that distinguishes between the three primary types of note definitions.

See also

NoteDefinition – for more information about these type values.

PCM

The type value of a NoteDefinition that plays an SWAV from a SWAR: 1.

PSG_SQUARE_WAVE

The type value of a NoteDefinition that plays a square wave using the Nintendo DS’s PSG hardware: 2.

PSG_WHITE_NOISE

The type value of a NoteDefinition that plays white noise using the Nintendo DS’s PSG hardware: 3.

class ndspy.soundBank.NoteDefinition([waveID_dutyCycle[, waveArchiveIDID[, pitch[, attack[, decay[, sustain[, release[, pan[, type]]]]]]]]])

A note definition within a SBNK instrument. This can be thought of as a template from which many notes of different pitches can be played.

There are three known meaningful type values (type) associated with this class, which affect which attributes are meaningful:

  • NoteType.PCM will produce a PCM note definition, which can play a SWAV wave file from a SWAR wave archive file.

    If the note definition is of this type, you can use the waveID and waveArchiveIDID attributes to set the SWAV and SWAR IDs, respectively.

  • NoteType.PSG_SQUARE_WAVE will produce a PSG square-wave note definition, which uses the Nintendo DS’s PSG hardware to play a square wave.

    If the instrument is of this type, you can use the dutyCycle attribute to set the square wave’s duty cycle.

  • NoteType.PSG_WHITE_NOISE will produce a PSG white noise note definition, which uses the Nintendo DS’s PSG hardware to play white noise.

    There are no attributes that are specific to this instrument type.

Attributes not mentioned above will work with all type values.

Parameters:
  • waveID_dutyCycle – The initial value for the waveID and dutyCycle attributes.

  • waveArchiveIDID – The initial value for the waveArchiveIDID attribute.

  • pitch – The initial value for the pitch attribute.

  • attack – The initial value for the attack attribute.

  • decay – The initial value for the decay attribute.

  • sustain – The initial value for the sustain attribute.

  • release – The initial value for the release attribute.

  • pan – The initial value for the pan attribute.

  • type – The initial value for the type attribute.

attack

The speed at which the note will fade from 0 to 100% volume when it begins to play. 0 is the slowest speed possible, and 127 is instant.

See also

The Wikipedia page on envelope explains attack, decay, sustain, and release values.

Section 4.2 (Articulation Data) in the kiwi.ds Nitro Composer File (*.sdat) Specification explains this in more detail.

Note

The link in the sentence “See this file for more details on how to interpret the articulation data” may be broken; here is the correct link.

Type:

int

Default:

127

decay

The speed at which the note will fade from 100% volume to the sustain level after the attack phase is finished. 0 is the slowest speed possible, and 127 is instant.

See also

The Wikipedia page on envelope explains attack, decay, sustain, and release values.

Section 4.2 (Articulation Data) in the kiwi.ds Nitro Composer File (*.sdat) Specification explains this in more detail.

Note

The link in the sentence “See this file for more details on how to interpret the articulation data” may be broken; here is the correct link.

Type:

int

Default:

127

dutyCycle

The duty cycle of the PSG square wave defined by this note definition. Values are as follows:

Attribute value

Actual duty cycle

0

12.5%

1

25%

2

37.5%

3

50%

4

62.5%

5

75%

6

87.5%

7

0%

Higher values are bitwise-AND-ed with 7.

Note

This only has an effect if type is NoteType.PSG_SQUARE_WAVE.

Note

This is an alias for waveID. This does not cause conflicts, since this attribute only affects note definitions that define PSG square waves, which do not use SWAVs at all.

Type:

int

Default:

0

pan

The note’s stereo panning value. A value of 64 is centered. Smaller values pan to the left, and larger values pan to the right.

Note

SSEQ sequence events can also specify panning values, using ndspy.soundSequence.PanSequenceEvents. The interplay between instrument and track panning may cause your track’s sounds to ultimately be panned differently from how your pan value dictates.

Type:

int

Default:

64

pitch

The pitch number that the instrument sample wave plays. This is used to calculate the adjusted sample rate that the wave needs to be played at to produce a desired actual pitch in the sequence.

This is measured in half-steps; 60 is middle C. Valid values are between 0 and 127, inclusive.

Type:

int

Default:

60

release

The speed at which the note will fade from the sustain level to 0% volume when it is released. 0 is the slowest speed possible, and 127 is instant.

See also

The Wikipedia page on envelope explains attack, decay, sustain, and release values.

Section 4.2 (Articulation Data) in the kiwi.ds Nitro Composer File (*.sdat) Specification explains this in more detail.

Note

The link in the sentence “See this file for more details on how to interpret the articulation data” may be broken; here is the correct link.

Type:

int

Default:

127

sustain

The volume that the note will remain at after the attack and decay phases are finished. 0 is no volume, and 127 is 100% volume.

See also

The Wikipedia page on envelope explains attack, decay, sustain, and release values.

Section 4.2 (Articulation Data) in the kiwi.ds Nitro Composer File (*.sdat) Specification explains this in more detail.

Note

The link in the sentence “See this file for more details on how to interpret the articulation data” may be broken; here is the correct link.

Type:

int

Default:

127

type

The type of sound that will be produced when this note definition is played. The value of this attribute affects whether other attributes are meaningful or not, such as dutyCycle, waveID, and waveArchiveIDID.

Warning

If this note definition is within a SingleNoteInstrument, this attribute is an alias for SingleNoteInstrument.type (automatically cast to and from NoteType for you). See the documentation for SingleNoteInstrument.type for more information.

See also

NoteDefinition – for more information about valid values for this attribute.

Type:

NoteType (or int)

Default:

NoteType.PCM

waveArchiveIDID

An index into the SWAR IDs list of the SBNK this note definition is a part of (SBNK.waveArchiveIDs). This, in turn, indicates the ID number (index) of the SWAR where the SWAV for this note’s instrument sample can be found.

Warning

This is not the index of the SWAR in ndspy.soundArchive.SDAT.waveArchives!

For example, if this attribute has a value 3, you would look up sbnk.waveArchiveIDs[3] in the SBNK this note definition resides in. The value you find there is the actual SWAR ID, which you can use to get the actual SWAR from the SDAT: sdat.waveArchives[swarID].

Note

This only has an effect if type is NoteType.PCM.

See also

waveID – the ID number of the SWAV to use from the SWAR.

Type:

int

Default:

0

waveID

The ID number (index) of the SWAV to use as the instrument sample for this note.

Note

This only has an effect if type is NoteType.PCM.

Note

This is an alias for dutyCycle. This does not cause conflicts, since that attribute only affects note definitions that define PSG square waves, which do not use SWAVs at all.

See also

waveArchiveIDID – the ID number of the ID number of the SWAR where this SWAV can be found.

Type:

int

Default:

0

classmethod fromData(data[, type])

Create a note definition from raw file data that does not include the type value at the beginning.

See also

fromDataWithType() – use this function instead if the file data does include type.

Parameters:
  • data (bytes) – The data to be read. Only the first 10 bytes will be used.

  • type – The initial value for the type attribute.

Returns:

The note definition object.

Return type:

NoteDefinition

classmethod fromDataWithType(data)

Create a note definition from raw file data that includes the type value at the beginning.

See also

fromData() – use this function instead if the file data does not include type.

Parameters:

data (bytes) – The data to be read. Only the first 12 bytes will be used.

Returns:

The note definition object.

Return type:

NoteDefinition

save()

Generate data representing this note definition, without including the type value at the beginning.

See also

saveWithType() – use this function instead if you want the data to include type.

Returns:

The note definition data.

Return type:

bytes

saveWithType()

Generate data representing this note definition, including the type value at the beginning.

See also

save() – use this function instead if you do not want the data to include type.

Returns:

The note definition data.

Return type:

bytes

class ndspy.soundBank.Instrument(type)

An instrument within a SBNK file.

This is an abstract base class, and should be subclassed in order to be used.

See also

SingleNoteInstrument – the subclass that should be used for type values 1 through 15 (SINGLE_NOTE_PCM_INSTRUMENT_TYPE, SINGLE_NOTE_PSG_SQUARE_WAVE_INSTRUMENT_TYPE, and SINGLE_NOTE_PSG_WHITE_NOISE_INSTRUMENT_TYPE).

RangeInstrument – the subclass that should be used for type value 16 (RANGE_INSTRUMENT_TYPE).

RegionalInstrument – the subclass that should be used for type value 17 (REGIONAL_INSTRUMENT_TYPE).

Parameters:

type – The initial value for the type attribute.

bankOrderKey

This attribute has to do with the way instrument data structs are sorted within the SBNK. The data structs are always first sorted by instrument type (first types < 16, then type 16, then type 17). Within each of those three groups, though, the order is arbitrary. Thus, this key allows you to set up whatever arrangement you want.

It’s extremely unlikely that you’ll ever need to look at or change this, since the order of the structs doesn’t really affect anything.

Note

This value is not explicitly saved in the SBNK file.

Type:

int

Default:

0

dataMergeOptimizationID

When saving a SBNK, ndspy will check if any instruments have 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 instruments, which you can use to exclude particular instruments from this optimization.

Since this defaults to 0 for all instruments 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 SBNK file.

Type:

int

Default:

0

type

The type value of this instrument.

Warning

In the SingleNoteInstrument subclass, this is an alias for instrument.noteDefinition.type. See SingleNoteInstrument.type for more information.

Type:

int

classmethod fromData(type, data, startOffset)

Create an instrument from raw file data.

This method must be implemented in subclasses; this abstract-base-class implementation simply raises NotImplementedError.

Parameters:
  • type – The initial value for the type attribute.

  • data (bytes) – The data to be read. The instrument data need not be at the beginning of it.

  • startOffset (int) – The offset in the data where the instrument data begins. This is not the place in the SBNK where the instrument type value is; rather, it is the place pointed to by the offset that comes just after that.

Returns:

The instrument object, and the number of bytes that were read to create it.

Return type:

(instrument, bytesRead), where instrument is of type Instrument and bytesRead is of type int.

save()

Return the instrument’s type value as a 1-tuple. Subclasses may return longer tuples with more data; currently, all subclasses add a bytes instance.

Returns:

The instrument’s type value as a 1-tuple.

Return type:

(type,), where type is of type int

class ndspy.soundBank.SingleNoteInstrument(noteDefinition)
Base class:

Instrument

An instrument that contains one note definition and nothing else. This is usually used for sound effects, which often contain one sound each anyway. This class encompasses instrument type (Instrument.type) values 1 through 15.

See the base class documentation (Instrument) for information about inherited functions and attributes.

Parameters:

noteDefinition – The initial value for the noteDefinition attribute.

noteDefinition

The note definition that this instrument will use.

Type:

NoteDefinition

type

The type value of this instrument. See Instrument.Type for more information.

Warning

The type values for a single-note instrument and its note definition are encoded as a single shared value in the SBNK file; thus, they are required to be the same. As such, this property is an alias for instrument.noteDefinition.type (automatically cast to and from int for you).

See also

NoteDefinition.type – the attribute that this is an alias of.

Type:

int

classmethod fromData(type, data, startOffset)

Create a single-note instrument from raw file data.

Parameters:
  • type – The initial value for the type attribute. This should be between 1 and 15, inclusive.

  • data (bytes) – The data to be read. The instrument data need not be at the beginning of it.

  • startOffset (int) – The offset in the data where the instrument data begins. This is not the place in the SBNK where the instrument type value is; rather, it is the place pointed to by the offset that comes just after that.

Returns:

The instrument object, and the number of bytes that were read to create it.

Return type:

(instrument, bytesRead), where instrument is of type SingleNoteInstrument and bytesRead is of type int.

save()

Generate file data representing this instrument, and then return the instrument’s type value and that data as a pair.

Returns:

The instrument’s type value and data representing the instrument, as a pair.

Return type:

(type, data), where type is of type int and data is of type bytes

class ndspy.soundBank.RangeInstrument(firstPitch, noteDefinitions)
Base class:

Instrument

An instrument that contains one note definition for each pitch in a given range. This is usually used for drumsets, since it is ideal for instruments with many distinct sounds that each only need to be played at one pitch. This class is for instrument type (Instrument.type) value 16 (RANGE_INSTRUMENT_TYPE).

See the base class documentation (Instrument) for information about inherited functions and attributes.

Parameters:
  • firstPitch – The initial value for the firstPitch attribute.

  • noteDefinitions – The initial value for the noteDefinitions attribute.

firstPitch

The pitch number that can be played to access the first note in noteDefinitions. The second note (if there is one) can then be played as this value plus 1, and so on.

This is measured in half-steps; 60 is middle C. Valid values are between 0 and 127, inclusive.

Type:

int

noteDefinitions

The list of note definitions that this instrument will use.

Type:

list of NoteDefinition

classmethod fromData(_, data, startOffset)

Create a range instrument from raw file data.

Parameters:
  • _ (any type) – Ignored. This exists as a placeholder for the “type” parameter that exists in the signature of this function in the superclass (Instrument), so that this function can be called without any special-casing.

  • data (bytes) – The data to be read. The instrument data need not be at the beginning of it.

  • startOffset (int) – The offset in the data where the instrument data begins. This is not the place in the SBNK where the instrument type value is; rather, it is the place pointed to by the offset that comes just after that.

Returns:

The instrument object, and the number of bytes that were read to create it.

Return type:

(instrument, bytesRead), where instrument is of type RangeInstrument and bytesRead is of type int.

save()

Generate file data representing this instrument, and then return the instrument’s type value and that data as a pair.

Returns:

The instrument’s type value and data representing the instrument, as a pair.

Return type:

(type, data), where type is of type int and data is of type bytes

class ndspy.soundBank.RegionalInstrument(regions)
Base class:

Instrument

An instrument that partitions the range [0, 127] into sections, and contains one note definition for each. This is used for most musical instruments, because it lets you use a few samples to cover a large range of pitches. Using a different sample for each note would be more accurate, but would use much more memory. Using only one sample for an instrument would cause it to sound increasingly distorted when playing notes that are far away from the sample’s pitch.

This class is for instrument type (Instrument.type) value 17 (REGIONAL_INSTRUMENT_TYPE).

See the base class documentation (Instrument) for information about inherited functions and attributes.

Parameters:

regions – The initial value for the regions attribute.

regions

The list of regions included in this instrument. These should be sorted in order of increasing Region.lastPitch, and the last region should have Region.lastPitch = 127. This ensures that the entire range of pitches from 0 to 127 inclusive is covered.

You can define up to 8 regions. The realistic minimum number of regions is 1 (although such an instrument would probably be better represented as a SingleNoteInstrument); you can save a regional instrument with no regions, but it is unknown how such an instrument would behave in an actual game.

Type:

list of Region

classmethod fromData(_, data, startOffset)

Create a regional instrument from raw file data.

Parameters:
  • _ (any type) – Ignored. This exists as a placeholder for the “type” parameter that exists in the signature of this function in the superclass (Instrument), so that this function can be called without any special-casing.

  • data (bytes) – The data to be read. The instrument data need not be at the beginning of it.

  • startOffset (int) – The offset in the data where the instrument data begins. This is not the place in the SBNK where the instrument type value is; rather, it is the place pointed to by the offset that comes just after that.

Returns:

The instrument object, and the number of bytes that were read to create it.

Return type:

(instrument, bytesRead), where instrument is of type RegionalInstrument and bytesRead is of type int.

save()

Generate file data representing this instrument, and then return the instrument’s type value and that data as a pair.

Returns:

The instrument’s type value and data representing the instrument, as a pair.

Return type:

(type, data), where type is of type int and data is of type bytes

Raises:

ValueError – if there are more than 8 regions in regions

class RegionalInstrument.Region(lastPitch, noteDefinition)

A region within a regional instrument. The highest pitch included in the region is lastPitch. The lowest pitch included in the region is 0 if this is the first region in the instrument, or 1 + the lastPitch of the previous region if it is not.

Parameters:
  • lastPitch – The initial value for the lastPitch attribute.

  • noteDefinition – The initial value for the noteDefinition attribute.

lastPitch

The highest pitch value included in this region.

This is measured in half-steps; 60 is middle C. Valid values are between 0 and 127, inclusive.

Type:

int

noteDefinition

The note definition that will be used to play notes within this region.

Type:

NoteDefinition

ndspy.soundBank.instrumentClass(type)

A convenience function that returns the Instrument subclass that should be used to load an instrument with the given type value.

Parameters:

type (int) – The type value to find the class for.

Returns:

The class object or None:

Return type:

int

Raises:

ValueError – if type is larger than 17

ndspy.soundBank.guessInstrumentType(data, startOffset, possibleTypes, bytesAvailable)

Try to guess the type of instrument stored in some binary data based on both the data and a set of possible types (ones that haven’t been ruled out by the instrument’s position in the surrounding data). This function is entirely based on heuristics, so it may return different answers for similar data, and it cannot always be accurate.

Types 1, 2 and 3 (SINGLE_NOTE_PCM_INSTRUMENT_TYPE, SINGLE_NOTE_PSG_SQUARE_WAVE_INSTRUMENT_TYPE, and SINGLE_NOTE_PSG_WHITE_NOISE_INSTRUMENT_TYPE) are considered equivalent by this function, since they are very similar and all use the same Python class (SingleNoteInstrument).

None will be returned if it’s very unlikely that there is an instrument at that position.

Parameters:
  • data (bytes) – The data to be read. The possible instrument data need not be at the beginning of it.

  • startOffset (int) – The offset in the data where the possible instrument data begins. This is not the place in the SBNK where the instrument type value is (as then this function would be trivial); rather, it is the place pointed to by the offset that comes just after that.

  • possibleTypes (set of int, or list of int) –

    The set of possible instrument types that should be considered.

    SINGLE_NOTE_PSG_SQUARE_WAVE_INSTRUMENT_TYPE and SINGLE_NOTE_PSG_SQUARE_WAVE_INSTRUMENT_TYPE are both treated as aliases of SINGLE_NOTE_PCM_INSTRUMENT_TYPE.

  • bytesAvailable (int) – The number of bytes that are available for a possible instrument to occupy. This lets the function rule out instrument types that would be too long and overlap the following instrument.

Returns:

The best guess for the instrument type value, or None if it seems unlikely that there is any instrument in the data there.

Return type:

int or None

class ndspy.soundBank.SBNK([file[, unk02[, waveArchiveIDs]]])

A SBNK instrument bank file. This defines a set of instruments that sequences and sequence archives can use.

Parameters:
  • file (bytes) – The data to be read as an SBNK file. If this is not provided, the SBNK object will initially be empty.

  • unk02 – The initial value for the unk02 attribute.

  • waveArchiveIDs

    The initial value for the waveArchiveIDs attribute.

    There can be up to four IDs here. You may include Nones to pad the list length to four, but they will be removed.

dataMergeOptimizationID

When saving a SDAT file containing multiple SBNK 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 SBNK files, which you can use to exclude particular ones from this optimization.

Since this defaults to 0 for all SBNKs 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 SBNK file or in the SDAT file containing it.

Type:

int

inaccessibleInstruments

Some SBNK files contain data for instruments that aren’t defined anywhere in the instrument table. For maximum accuracy, ndspy attempts to find and load these instruments using heuristics, so they can be included with the file when it is re-saved. These instruments can be found here.

Each dictionary key is the ID of the previous instrument that does have an ID, and each dictionary value is the list of inaccessible instruments that follow that one.

This may be more clear with an example:

Suppose there exists data for two inaccessible instruments between the data for instruments 12 and 7 (which is a very possible scenario, since instrument data usually does not follow instrument ID order). Call them inst1 and inst2. In this example, inaccessibleInstruments would contain the following:

{12: [inst1, inst2]}

This is read as “the two inaccessible instruments following the data for instrument 12 are inst1 and inst2”.

Since this attribute is mostly based on heuristics, it may miss instruments, or contain instruments of the wrong type.

Warning

While it is possible to put new instruments here, this is strongly recommended against, since it cannot be guaranteed that such instruments will be parsed correctly when the SBNK is saved and re-opened. Additionally, other tools that support SBNK may corrupt or remove this data. Also, why would you even do that?

You should either ignore this attribute, or treat it as read-only (although it’s fine to manually clear it if you want to ensure that your files will be as small as possible). In all cases, take whatever you find within it with a grain of salt.

In addition, this attribute may disappear in future versions of ndspy if it is discovered that these instruments do have an actual purpose.

Type:

dict: {previousID: instruments}, where previousID is of type int or None, and instruments is a list of instances of subclasses of Instrument

Default:

{}

instruments

The list of instruments contained in the SBNK. “Instrument IDs” are indices into this list.

Type:

list both of instances of subclasses of Instrument, and of None

Default:

[]

unk02

The value following the SBNK’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 SBNK file, but it is saved in the SDAT file if the SBNK is within one.

Type:

int

Default:

0

waveArchiveIDs

The list of SWAR IDs that instruments in this bank may use. This can contain up to four IDs.

If this SBNK is loaded through a sound group entry with its ndspy.soundGroup.GroupEntry.loadSBNKSWARsFrom attribute set to ndspy.soundGroup.SWARLoadMethod.fileIDs, the IDs in this list will be interpreted as raw SDAT file IDs instead of SWAR IDs.

Note

ndspy doesn’t expose raw SDAT file IDs, and the functionality described above seems to never really be used in practice (and there’s honestly no good reason to do so), so you don’t really need to worry about that case very much.

Type:

list of int (4 elements maximum)

Default:

[]

classmethod fromInstruments(instruments[, unk02[, waveArchiveIDs]])

Create a SBNK from a list of instruments.

Parameters:
  • instruments – The initial value for the instruments attribute.

  • unk02 – The initial value for the unk02 attribute.

  • waveArchiveIDs

    The initial value for the waveArchiveIDs attribute.

    There can be up to four IDs here. You may include Nones to pad the list length to four, but they will be removed.

Returns:

The SBNK object.

Return type:

SBNK

classmethod fromFile(filePath[, ...])

Load an SBNK from a filesystem file. This is a convenience function.

Parameters:

filePath (str or other path-like object) – The path to the SBNK file to open.

Further parameters are the same as those of the default constructor.

Returns:

The SBNK object.

Return type:

SBNK

save()

Generate file data representing this SBNK, and then return that data, unk02, and waveArchiveIDs as a triple. This matches the parameters of the default class constructor.

Returns:

The SBNK file data, unk02, and waveArchiveIDs.

Return type:

(data, unk02, waveArchiveIDs), where data is of type bytes, unk02 is of type int, and waveArchiveIDs is a list of int

saveToFile(filePath)

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

Parameters:

filePath (str or other path-like object) – The path to the SBNK file to save to.