The Nendo Track¤
This page provides a thorough introduction into one of the basic concepts of Nendo Core: The class NendoTrack
. It is the most relevant user-facing object in Nendo Core. It represents an audio artifact (imported or generated) that can be interacted with in various ways.
Developer info
If you are a developer, you might as well just directly dive into the API Referece. This page looks at the NendoTrack
from a user's perspective.
Fields
Field | Type | Description |
---|---|---|
id |
uuid |
The unique ID of the track. |
uder_id |
uuid |
The ID of the user who "owns" the track. |
signal |
np.ndarray |
The track's waveform in the form of a numpy.ndarray . |
sr |
int |
The sample rate of the track's signal . |
track_type |
str |
A free-form type descriptor that can be used by developers to further differentiate between different kinds of track objects. Unless explicitly set by the developer, "track" is used as a placeholder value. |
visibility |
Visibility |
A special field that can be used by developers to assign different visibilities to tracks that can be used to isolate tracks between users. Possible values are public , private and deleted . |
resource |
NendoResource |
The NendoResource representing the file on disk to which the NendoTrack.signal corresponds. |
plugin_data |
list[NendoPluginData] |
List of NendoPluginData entries as produced by Analysis Plugins. |
related_tracks |
list[NendoTrackTrackRelationship] |
List containing all relationships to other nendo tracks. |
related_collections |
list[NendoTrackCollectionRelationship] |
List containing all relationships to nendo collections. |
meta |
dict |
A dictionary containing a track's metadata. |
Manually changing fields
It is not recommended to overwrite the fields mentioned in the above table manually! Instead, you should be using the functions described in the following paragraphs. If you end up manipulating the fields directly, be aware that things might break and that you need to manually persist any changes by saving the track.
Creating a track¤
Tracks are created by nendo everytime a file is added to the library or when a plugin is called that produces a new track as its output.
Example
Creating a track from a file on disk:
track = nendo.library.add_track("/path/to/file.mp3")
Example
Creating a track from a file on disk and creating a relationship to an existing track:
# track_1 = ...
track_2 = nendo.library.add_related_track("/path/to/file.mp3", related_track_id=track_1.id)
Example
Creating a track from a signal, in the form of a numpy.ndarray
:
# signal, sr = ...
track = nendo.library.add_track_from_signal(signal=signal, sr=sr)
Example
Creating a track by running a plugin:
track = nendo.plugins.gen_musicgen("Epic guitar solo")
Saving, exporting and deleting a track¤
When you change a track by writing to any of its fields directly or delete the in-memory object by using python's del track
, the changes are not persisted to the nendo library. You have to call the corresponding track functions to persist your changes:
Example
# track = ...
track.save()
A track can be exported to file in a given format using the NendoTrack.export()
function. Please note that the function either accepts a full path to the target file as its file_path
parameter, in which case the desired format is automatically deduced from the specified file ending. If instead a path to a directory is given as file_path
, the filename will be automatically generated by nendo and the output format will be determined by the file_format
parameter.
Example
track.export(file_path="/path/to/output/target_file.mp3")
# or
track.export(file_path="/path/to/output/", file_format="mp3")
# the latter will automatically generate a filename
Supported formats
When exporting a track this way, the supported formats are wav
, mp3
and ogg
.
Example
# track = ...
track.delete()
# Now, the track has been removed from the nendo library.
# To also delete the object from memory, call:
del track
Manipulating tracks¤
Nendo provides various functions that make it easy to manipulate tracks as typically needed in audio processing workflows.
To obtain a resampled signal from an existing track:
Example
resampled_signal = track.resample(48000) # resample to 48kHz
Warning
Resampling a track using track.resample()
will never change the file in the nendo library. It only overwrites the track.signal
and track.sr
values in-memory and additionally returns the resampled signal in the form of a numpy ndarray
.
To combine two tracks into one by overlaying them, you can use the following conveniece function:
Example
track_1 = nendo.library.add_track("/path/to/file_1.wav")
track_2 = nendo.library.add_track("/path/to/file_2.wav")
combined_track = track_1.overlay(track_2, gain_db=0.12)
# The `gain_db` is applied to the second track
# combined_track is a new track with a relationship to track_1
To slice a track to the first 10 seconds, use:
Example
track_slice = track.slice(10)
# track_slice now contains the signal of the track's first 10 seconds
To slice a track from second 5 to 10:
Example
track_slice = track.slice(start = 5, end = 10)
Warning
track.slice()
returns the slice as a numpy.ndarray
. The original track is not changed. To save the slice as a new signal, use Nendo.library.add_track_from_signal(signal=track_slice, sr=track.sr)
or, to add the sliced track with a relationship to the original track, use track.add_related_track_from_signal(signal=track_slice, sr=track.sr, related_track_id=track.id)
.
Working with relationships¤
As stated above, it is possible for tracks in Nendo Core to have relationships to other tracks, as well as to collections. This feature is used to track the "history" of how a specific audio sample was obtained and to group tracks together into playlists, etc. The following examples show how to work with a track's relationships to other tracks.
To add a new track to nendo that has a relationship to an existing given track:
Example
# track_1 = ...
track_2 = track_1.add_related_track("/path/to/file.mp3")
Hey, I have seen this above!
You might have noticed that this function closely resembles the Nendo.library.add_related_track()
function (minus the related_track_id
parameter). As most of the functions defined on the NendoTrack
, the add_related_track()
is but a shortcut that calls the corresponding function that is defined as part of the nendo library.
To check whether a NendoTrack
has any relationships, you can use:
Example
>>> # track = ...
>>> track.has_relationship()
True
And to check only for a specific relationship_type
:
Example
>>> # track = ...
>>> track.has_relationship(relationship_type="stem")
False
To check whether a given track has a relationship to another given track:
Example
# track_1 = ...
# track_2 = track_1.add_related_track(...)
>>> track_1.has_related_track(track_2.id)
True
The NendoTrack.related_tracks
field contains a list of NendoRelationship
objects. To obtain a list of all NendoTrack
objects that are connected to the current track by means of a NendoRelationship
, you can use the NendoTrack.get_related_tracks()
method.
Example
# track = ...
related_tracks = track.get_related_tracks()
Please refer to the API reference to see the full list of parameters for this function.
Working with a track's metadata¤
Tracks in nendo have metadata attached to them, in the form of the NendoTrack.meta
dictionary. For example, upon importing track into the nendo library from a file, its parsed ID3 information is stored to NendoTrack.meta
.
Note
The NendoTrack.meta
field is a normal dictionary, whose fields can be accessed simply via track.meta['album']
and added via track.meta.update({"album": "Autobahn"})
. However, manipulating the meta
dictionary directly like that will not persist the changes to the nendo library, which is why the functions below exist and should be used: They will take care of persisting any changes to the meta
dictionary into the nendo library.
Arbitrary metadata can be added to NendoTrack.meta
by passing a dictionary with the desired key: value
pairs:
Example
# track = ...
track.set_meta({"composer": "Sarah"})
The NendoTrack.meta
field is a normal dictionary, whose fields can be accessed simply via track.meta['album']
. However, there is also a convenience function that will return None
if the key can not be found (instead of raising a KeyError
):
Example
>>> # track = ...
>>> existing_meta_value = track.get_meta("album")
>>> print(existing_meta_value)
'Wish you were here'
>>> missing_meta_value = track.get_meta("something")
>>> print(missing_meta_value)
>>>
To check whether a given track has metadata belonging to a specific key:
Example
>>> # track = ...
>>> track.has_meta("album")
True
>>> track.has_meta("something")
False
To remove a specific field from a track's metadata:
Example
# track = ...
track.remove_meta("album")
Working with plugin data¤
Plugin data version
Plugin data in Nendo Core is versioned, in accordance with the version of the plugin that was used to produce it. That means, that if a specific plugin is used to process a given track twice, it will only overwrite the previous data if the version of the plugin has not changed since the last run. This is to ensure proper traceability of results and to make the nendo library compatible with plugin upgrades.
The NendoTrack
object provides various functions for processing it with a NendoPlugin
and for accessing the resulting NendoPluginData
:
To run a plugin on the given NendoTrack
, you can use the NendoTrack.process()
function:
Example
track.process("classify_core") # this runs the "classify_core" plugin on the track
Upon processing a track, a nendo AnalysisPlugin
produces NendoPluginData
and "attaches" it to the track's plugin_data
field. You can access that field directly or use the NendoTrack.get_plugin_data()
convenience function to filter for the data of a specific plugin. The function accepts two arguments that can be either specified in isolation or together to refine the filtering of plugin_data
. If you call the functions without any aruguments, all plugin data will be returned, just as if you'd directly accessed the field NendoTrack.plugin_data
. The function will return a list of NendoPluginData
in all cases, except when the list has only a single entry. In this case, the value
of the plugin data will be returned immediately.
Example
If you specify the plugin_name
, the plugin_data
will be filtered and only entries whose plugin_name
field matches the provided name will be returned.
>>> plugin_data = track.get_plugin_data(
... plugin_name="nendo_plugin_classify_core",
... )
>>> for entry in plugin_data:
... print(entry)
----------------
plugin name: nendo_plugin_classify_core
plugin version: 0.1.0
key: loudness
value: 105.32542419433594
----------------
plugin name: nendo_plugin_classify_core
plugin version: 0.1.0
key: duration
value: 121.1
# ...
If you are sure that for a specific key, only a single plugin_data
entry exists, you can also use the get_plugin_value()
method that will directly return the value for a given key. If the track has multiple plugin_data
entries for the given key, the first one is returned (and actually, in most cases, it would make sense to use the get_plugin_data()
method instead to make sure you really retrieve the value what you want).
Example
>>> plugin_data = track.get_plugin_value(
... key = "duration",
... )
>>> print(plugin_data)
121.1
Working with collections¤
Aside from having relationships to other tracks, a NendoTrack
can also have relationships to one or more collections. You can use the following functions to add or remove such assignments directly from the NendoTrack
object:
To add a given track to an existing collection, you can use the track.add_to_collection()
function. Use the position argument specifies the position of the track inside the collection. Set it to 0 to prepend it to the beginning, omit the parameter entirely to append it to the end.
Example
# track = ...
# collection = ...
track.add_to_collection(collection_id=collection.id, position=5)
To remove a given track from an existing collection:
Example
# track = ...
# collection = ...
track.remove_from_collection(collection.id)
Playing tracks¤
There are a few simple functions to allow for preview playpack of tracks from inside the python console.
Warning
The following functions are intended for very simple previewing. No skipping, rewinding, etc. are support. Playback can only be stopped by using the keyboard shortcut Ctrl + C
.
Example
track.play() # plays the track once
Example
track.loop() # plays the track on repeat ("looped")
Success
That's all you need to know about the NendoTrack
. Next up, learn more about collections in nendo.