The release of Ableton Live 11 introduced a number of new features for working with MIDI notes:
Support for MPE
Note probability
Support for velocity deviation
Support for release velocity
The Max for Live API has been updated to provide access to this new data in Live, and to allow for manipulating notes without discarding this new data.
The second part of our look at what’s new in Live for Max for Live users will provide a brief overview of the new features in the Live notes API.
We’re also including a pair of example Max for Live devices which contain snippets that you can copy and paste to help you hit the ground running as you learn to use these new features.
And don’t worry if you’re not really familiar with dictionaries (dicts) or note IDs in Max — two mechanisms in Max you’ll be using when you work with the notes API — we’ve tried to walk you through how dicts and note IDs are used in the example device snippets.
Before and after
For those of you who are already familiar with working with the notes API in Live 10 (or for those of you who’d rather read patches than text), let’s start with a device from the download (Notes API - Before and After.amxd) that will give you a side-by-side comparison of how common tasks used to be done, and the new Live 11 notes API ways of doing them:
Note: the Notes API - Before and After device is frozen. After unfreezing, you can find its frozen abstractions and javascript files in your Documents folder, in Max 8/Max for Live Devices/Notes API - Before and After Project.
You’ll find examples here of all the common API-based notes operations you already know (getting, adding, removing, modifying (all notes or selected notes), duplicating and observing) with examples of the new notes API versions you can copy, paste, and modify as needed to quickly add the new functionality to your own devices.
The API specification
You will find the full specification for the notes API in the Live Object Model document listed as part of the Clip specification.
The new features of the Live 11 notes API involves working with note IDs and dictionaries in Max (for more details on this, see below).
The new notes API methods are:
get_notes_extended: returns a dict with all notes in a specified time and pitch range
get_selected_notes_extended: returns a dict with all selected notes
add_new_notes: accepts a notes dict without note IDs to add new notes
apply_note_modifications: accepts a notes dict with note IDs of notes to modify
remove_notes_extended: removes all notes in a pitch and time range
get_notes_by_id: gets a note for a certain note ID
remove_notes_by_id: removes a note with a certain note ID
To see exactly what parameters to provide to each of these methods and what they return, please refer to the specification in the Live Object Model document.
The old notes API methods are still available under conditions
The old note access methods are still available for legacy devices in Live 11, but using them is discouraged by warnings in the Max Window.
get_notes
set_notes
get_selected_notes
To prevent unintentionally removing new note data like MPE, the following old methods that remove notes will show a warning popup:
remove_notes
replace_selected_notes
The warning pop-ups can be permanently silenced by the user by choosing Don't show again.
Accessing the API with dicts
The dict (short for “dictionary") object family in Max provides versatile access to all kinds of data. A dict represents series of key-value pairs in a leveled structure. The structure of these dicts follow that of the JSON format, which will be helpful for those of you who are familiar with web development: a key can point to a string, float or int, or to another dict.
Additionally, a key can point to an array of any of these things.
The Notes API Basics.amxd Max for Live device included in the download provides a basic example of how to use the notes API with Max dict objects:
The order of key/value pairs in a dict is not fixed, nor is the number of keys and values. This is crucial to the new notes API, because it needs to be extendible; we’ll want older devices to continue to work without modifications if new note information is added in the future.
Dicts are passed by reference
When outputting a dict to another object, instead of copying all the data over a patch cord, a reference to this data is transmitted. That is why you see something like dictionary u944000887 when you show a dict in a message box.
Note: Modifying a dict will affect all subsequent usages of this dict. To avoid problems, you should make a copy of a dict before modifying it. You can find a demonstration of this in the IntroToDict subpatcher you’ll find in the Notes API - Before and After device.
For more information about dicts, take a look at the more in-depth dict reference in the Max documentation.
Keeping track of notes with note IDs
The previous method for modifying a note in Live 11 involved getting the note’s properties, removing them, and then adding a new note with similar properties. Adding MPE support meant that this old method would result in discarding all per-note MPE data.
Live 11 guarantees that all note data is preserved by letting you provide new values for an existing note ID when you perform modification rather than removing and replacing note IDs.
Note IDs are simply integers that uniquely identify a note within a clip. You cannot generate these IDs yourself; you can only find out about them by getting existing notes from a clip.
The new procedure for modifying a note with the API looks like this:
Get current notes with get_notes_extended, which provides you with a dict of all the notes.
Get the note_id of the note you need to modify from the resulting dict.
Create a new dict containing the new note values that retains the existing note_id.
Send this new dict back to the Clip with apply_note_modifications.
Note IDs are different from other references in Max
Note IDs are different from existing reference types in Max, like Live Object Model IDs (e.g, id 56) or uuids (e.g., dictionary u48298492). These references are always globally unique and have a keyword prepended, whereas note IDs are unique only within a clip and are just ints (3). For more details, see the IntroToNoteIDs subpatcher in the Before and After device.
The notes API in Javascript
Javascript is uniquely well-equipped for dealing with the new notes API in Live 11 — the notes data is structured as JSON, and JSON is invented for Javascript. All new notes API operations are available to you; take a look at the Before and After example device, in the Javascript subpatcher for bits of example code that are ready to be copied into your own device patches.
We hope that these articles have given you a good first look at the improvements and new features in Live 11 for Max for Live users, and we’re excited to see what you’ll make using them!
Corrections
2021-04-22: In a previous version of Notes API - Before and After.amxd, a pitch span of 127 was used when the intention was to apply an operation to the full pitch range. This has now been updated to 128.
2021-05-28: Added examples for outputting notes data from Javascript to dict and for inputting notes data from dict to Javascript.
Ok. That works great. Maybe the "Notes API - Before and After.amxd" file was missing in the first download? ;) Thanks a lot for the new features and finally the very good explaination!
I wanted to add a quick note that with the current version of Max, there is a limitation in the size of the dict that can be passed back and forth to and from Live, which might result in a warning message in the Max window when trying to change or request a lot of notes. A fix for this is in the pipeline though, this limitation should be gone in the next version of Live.
Many thanks for the JS examples. A quick question. I am adding notes to a clip in several steps (i.e., new notes to existing notes). Is there a simple way to get the note ID of the notes added the last time (we can assume that the note ID are known of the old notes if that helps). I can think of a couple of cumbversome ways to do this but if there is an easy one that would be great. Again what you are doing is superinteresting.
The only approach I know of is indeed to store a list of note IDs in the clip and to scan the notes again for new IDs after the new notes were added. I agree it would be better for convenience and performance if the add_new_notes method would directly return the newly added note ids. I've noted that as a feature request.
Maybe here someone can help please. I'm trying to iterate through all the notes and put them out from js. I edited 'NotesAPIget.js' but don't find out how to output all elements from the array and not only one element.
autowatch = 1 inlets = 2 outlets = 1
var clipApi = null
function id(id) { clipApi = new LiveAPI("id " + id) if (clipApi.path === "") clipApi = null }
function printLatestNote(from_time, time_span) { if (clipApi == null) throw("No clip")
var notesJson = clipApi.call("get_notes_extended", 0, 127, from_time, time_span) var notesObject = JSON.parse(notesJson) var notesArray = notesObject.notes post("length " + notesArray.length + "\n") printNote(notesArray[0]) }
Thanks so much, Broc. Works perfect. Tried a few different lines, also with forEach function but i didn't understand the 'grammar'. Have to learn a lot of javascript... it's so powerfull.
Great article! [sneak preview from the beta testing arena: limitations with the dict size for MIDI notes and the API have been addressed in the latest beta; so soon limitations should be gone].
For sets of chords it would be great to send a dictionary as a sub dictionary to another dicitonary like dict.pack but using the dict name or like dict.sub @name or using replace this would be awesome.
I am not fully sure what your use case is, but some things that are hard to do with dict objects can be done much more easily with Javascript.
I just added two examples in the Javascript subpatcher of the Notes API Before and After device that demonstrate how to transfer notes data to and from dict objects.
Maybe a little late but I take the chance anyway. I am struggling with selecting notes programmatically (JS). Assume for example that I have 3 notes starting in beat 1 that I have selected. How can I move the selection to, say, beat 2, i.e., deselecting notes in beat 1 and selecting multiple notes in beat 2. Are there any LOM functions that can be used?
Hi Jan, it would be great of you could send an email to support@cycling74.com about your feature wish, the ability to control the Session Clip insert marker. Ideally, perhaps you could roughly describe your use case. Thanks!
Hi Ricardo, the option to select notes has been added in Live 11.1. If you have access to the Live public beta, you should be able to call `select_notes_by_id` on a Clip object to select notes. This has not been documented in Max yet, but can be found out by sending `getinfo` to a live.object that is set to a Clip object.
Thanks for a very informative article! Do you know if the new version supports parallel processing within .amxd objects? My design for a multiphonic synthesizer in Max8 and gen~ needs a poly~ object to provide 32 reliable voices with 30% total CPU usage on a 4GHz i7, so I am distributing it as a compiled .exe with reWire drivers and MIDI interface. I'd like to add more controllers, but in MIDI channels more than ~110 parameters per channel have to done with sysex rather than CC# controller values. While sysex could support more, I'd much rather convert it into a M4L device and use Ableton's preset manager, because with 16 channels of multiphony, I've already hit the limit of ~1,700+NRPN MIDI CC values for device parameters.
Just wanted to say I am excited about the growth of the Notes API. I have Scheme for Max running nicely and locked in tight in Live now, so interacting with the sequence data from Scheme/Lisp is fully doable and will open a lot of possibilities! Live + Max + s7 Scheme is a joy.
Hi Mattijs, in the documentation, for example for Clip.get_notes_by_id(..), we can also pass in a dictionary with a "note_ids" key, associated with a list of ids. I can't seem to get this to work, and see no example for this available. Can you confirm that this is actually implemented and working? Related to this, I also can't define which fields of the note I want returned (according to the docs, I could do that with a "return" list inside the same dictionary).
This is the same for every other function that is documented similarly. At least those that I tried.
Hello. When selected notes are moved up or down with cursor keys in live11 the existing notes aren't overwritten. The existing notes remain if the edited notes are moved further. Is there a corresponding command in maxforlive which maintains the existing notes? apply_note_modifications and replace_selected_notes doesn't work this way.
Hi DFWAUDIO, that's a good point, there is currently no way to have selected notes be kept separate from notes they (temporarily) replace. I added this in our feature wishes tracker.
For now, you could maybe work around this by storing all the notes of a clip in a dict before moving a selection of notes.
Am I missing something - or is it not possible to modify the MPE event data via the API yet? The MPE compatibility means only that the data is carried over modifications? I am currently editing the PerNoteEvent data a bit hackish directly inside the Clip XMLs...
Hi Eero, that is correct, it is not yet possible to modify the MPE automation graphs as stored in clips in Live. The notes API only supports copying the MPE data implicitly when moving notes around. This is certainly a wish for the future, but support for this hasn't made it into Live yet.
For clarity: manipulating incoming MPE data in real-time -is- possible with Max for Live, using the mpeparse object and its kin.
Thanks a lot for the tutorial; it is very informative. I'm using the example called Add to add a chord, for instance, a C maj7. But I'm struggling to find a way to adjust the pitch and detune each note of the chord in some specific cents. I've been trying to use MPE Control, but I can only adjust the whole chord after generating it, using a mpeparse and then a poly~PitchSlide. I can't find a way to adjust each MPE individually per note in the chord when generating the chord. The concept is quite simple, but to achieve it seems to be not possible from Max?