Subsections of API
Abstraction Reference
Hmmmm…
Subsections of Abstraction Reference
darr.get_size
Returns the number of keys in a dict
When using a dict to mimic an array, use this to retrieve the length of the array.
OUTLETS
0 int
number of keys in dict
MESSAGES
symbol
dict name
SEE ALSO
djazz
flowchart TB; AudioIn1((Audio\nIn L)) AudioIn2((Audio\nIn R)) MidiIn((MIDI In)) TapIn((Tap\nIn)) PattrIn((Pattr\nIn)) DataIn((File\nData\nIn)) PresetIn((Presets In)) Master[Master Control] Audio[Djazz Audio] Midi[Djazz MIDI] PattrStorage[PattrStorage] click Master "./../components/master_control.html" "Master Control" click Audio "audio.html" "Master Control" click Midi "midi.html" "Master Control" AudioOut1(((Audio\nOut 1L))) AudioOut2(((Audio\nOut 1R))) AudioOut3(((Audio\nOut 2L))) AudioOut4(((Audio\nOut 2R))) AudioOut5(((Audio\nOut 3L))) AudioOut6(((Audio\nOut 3R))) MidiOut(((MIDI Out))) PattrOut(((Pattr Out))) AudioIn1--->Audio AudioIn2--->Audio TapIn-->Master PattrIn-->Master PattrIn-->Audio PattrIn-->Midi DataIn-->Audio DataIn-->Midi DataIn-->Master Master-->Audio Master-->Midi MidiIn-->Midi Audio-->AudioOut1 Audio-->AudioOut2 Audio-->AudioOut3 Audio-->AudioOut4 Audio-->AudioOut5 Audio-->AudioOut6 Midi-->MidiOut PresetIn-->PattrStorage PattrStorage-->PattrOut
INLETS
1 signal
Audio In 1
signal Audio signal is sent to djazz_audio_in
2 signal
Audio In 2
signal Audio signal is sent to djazz_audio_in
3 list
Midi In
list (int pitch, int velocity, int channel) sent to djazz_midi_in
4 bang
Triggers next beat
bang All active tracks MIDI and audio tracks will play their data located at the current beat in tempo when a bang is received. Any armed recording tracks will record any input during this beat in tempo.
5 list
Asynchronous input (such as that sent from pattr objects)
list (symbol argument-name anything argument-value) See the section on pattrs
6 symbol
Loads records needed for playback
symbol The name of a Max dict that has been loaded with a JSON file. These are either song files or score files.
7 anything
Messages to the pattrstorage objects
anything This is only used to send the “clientwindow” message to the pattrstorage object, which is useful for debugging. Any preset-related messages could be sent, but they should actually be sent to djazz_control.
OUTLETS
0 signal
Audio Track 1 Out 1
Audio signal
1 signal
Audio Track 1 Out 2
Audio signal
2 signal
Audio Track 2 Out 1
Audio signal
3 signal
Audio Track 2 Out 2
Audio signal
4 signal
Audio Track 3 Out 1
Audio signal
5 signal
Audio Track 3 Out 2
Audio signal
6 list
MIDI output
A MIDI note is sent out as a list (int pitch, int velocity, int channel)
7 list
View control data
ATTRIBUTES
None
MESSAGES
### MESSAGES
-See asynchronous messages.
-See
Page not found: /3_api/1_abstraction_references/components/master_control
for details about the following:/master_control
Page not found: /3_api/1_abstraction_references/components/master_control#loop-section-active
0/1Page not found: /3_api/1_abstraction_references/components/master_control
list (int int) start-beat end-beat master::start_beat intmaster::end_beat int
master::initial_tempo float
-See Audio In for details about the following:
/audio_in
audio::audio_in::record_active 0/1
audio::audio_in::max_repetitions int
audio::audio_in::save::folder_path symbol
-See Audio Out for details about the following:
/audio_out
(for n from 1-3):
audio::audio_out::beat_players::beat_player_n::crossfade_time_in_ms int
audio::audio_out::beat_players::beat_player_n::audio_buffer_offset_in_ms int
(for n from 1-3):
audio::audio_out::listeners::listener_n 0/1
audio::audio_out::listeners::include_master 0/1
(for n from 1-3):
audio::audio_out::audio_out_bank::track_n::volume int 0-127
audio::audio_out::audio_out_bank::track_n::mute 0/1
audio::audio_out::audio_out_bank::track_n::solo 0/1
audio::audio_out::audio_out_bank::track_n::active 0/1
(for n from 1-3):
audio::audio_out::generators::audio_beat_generator_n::loop_length int
audio::audio_out::generators::audio_beat_generator_n::speed_numerator int
audio::audio_out::generators::audio_beat_generator_n::end_beat int
audio::audio_out::generators::audio_beat_generator_n::next_beat int
audio::audio_out::generators::audio_beat_generator_n::loop_section_active 0/1
audio::audio_out::generators::audio_beat_generator_n::speed_denominator int
audio::audio_out::generators::audio_beat_generator_n::speed_active 0/1
audio::audio_out::generators::audio_beat_generator_n::loop_section_beats int
audio::audio_out::generators::audio_beat_generator_n::pitch_range int
audio::audio_out::generators::audio_beat_generator_n::max_continuity int
audio::audio_out::generators::audio_beat_generator_n::improvise 0/1
audio::audio_out::generators::audio_beat_generator_n::transpose_octave int
audio::audio_out::generators::audio_beat_generator_n::transpose_pitch int
(for n from 1-3):
audio::audio_out::data_loaders::track_loader_n::repetition int
(for n from 1-15):
midi::midi_out::midi_out_bank::track_n::mute 0/1
midi::midi_out::midi_out_bank::track_n::active 0/1
midi::midi_out::midi_out_bank::track_n::solo 0/1
midi::midi_out::midi_out_bank::track_n::volume int 0-127
midi::midi_out::midi_out_bank::track_n::port symbol
midi::midi_out::midi_out_bank::track_n::channel_out int 1-16
midi::midi_out::midi_out_bank::track_n::ctrl_msg int
midi::midi_out::midi_out_bank::track_n::effect_list::transpose_octave::octaves int -255-+255
midi::midi_out::midi_out_bank::track_n::effect_list::transpose_pitch::semitones int -255-+255
midi::midi_out::data_loaders::scores::score_file_1::file_path symbol
midi::midi_out::data_loaders::scores::score_file_2::file_path symbol
midi::midi_out::data_loaders::track_loader_1::repetition int 0-255
midi::midi_out::data_loaders::track_loader_2::repetition int 0-255
(for n from 1-15):
midi::midi_out::listeners::listener_n
midi::midi_out::listeners::include_master
(for n from 1-15):
midi::midi_out::generators::midi_beat_generator_n::loop_length int
midi::midi_out::generators::midi_beat_generator_n::speed_numerator int
midi::midi_out::generators::midi_beat_generator_n::end_beat int
midi::midi_out::generators::midi_beat_generator_n::start_beat int
midi::midi_out::generators::midi_beat_generator_n::loop_section_active 0/1
midi::midi_out::generators::midi_beat_generator_n::speed_denominator int
midi::midi_out::generators::midi_beat_generator_n::speed_active 0/1
midi::midi_out::generators::midi_beat_generator_n::loop_section_beats list (int int)
midi::midi_out::generators::midi_beat_generator_n::pitch_range 0-255
midi::midi_out::generators::midi_beat_generator_n::max_continuity 0-255
midi::midi_out::generators::midi_beat_generator_n::improvise 0/1
midi::midi_in::folder_path symbol
midi::midi_in::record_active 0/1
midi::midi_in::save::folder_path symbol
SEE ALSO
SEE ALSO
darr.set_at
Sets the entry at the given index
When using a dict to mimic an array, use this as the assignment operator [] or set.
MESSAGES
list
left inlet: (int anything) index, entry
symbol
right inlet: dict name
SEE ALSO
djazz view
djazz.analyzer
Takes a label representing the current musical state, and returns another label to be used by the improvisation algorithm.
The input label can be passed to it by anything–the master control, other generators, or the same generator in which this analyzer resides.
Currently only passes the chord label for the next beat, passed by the master control, directly through without modification.
So for now this object exists more as a placeholder. This is where real-time music analysis code should go.
OUTLETS
0 list: symbol symbol
label + new symbol
MESSAGES
symbol
a label representing the current musical state to be used by the improvisation algorithm.
SEE ALSO
djazz.listener_control
djazz.listeners_router
djazz.beat_generator
djazz.midi.midi_beat_generator
djazz.audio.beat_generator
djazz view control
djazz.antescofo_file_to_tracks_dict
Converts an antescofo file into a Max dict (which can then be saved as a JSON file).
Dict format:
tracks
int
beat
int
notes
int: note data
int: note data
int
…
OUTLETS
0 symbol
tracks dict name
MESSAGES
symbol
full path to antescofo file
clear
clears the Max dict
SEE ALSO
djazz.antescofo_get_tempo
Uses antescofo to infer the current tempo based on time between input messages.
Loads the file antescofo_djazz.txt, which must be in the Max search path (currently in data/antescofo_scores).
OUTLETS
0 float
tempo inferred from time between inputs
MESSAGES
bang
left inlet: bangs will cause inferred tempo to be output. Initial tempo must be set and bangs must not deviate far from current tempo. Two bangs must occur before tempo is inferred.
initial_tempo
left inlet: ( + float) tempo to set antescofo’s tempo inference mechanism. Initial input tempo must be close to this.
SEE ALSO
djazz.audio.beat_generator
Upon receipt of a beat number, label, and tempo, sends data about the next beat to read to the beat reader.
Pattr messages (must be passed via an external pattrhub/pattrstorage):
transpose_pitch (int) -255 - +255
transpose_octave (int) -255 - +255
next_beat (int) 0-255
end_beat (int) 0-255
speed_active (int) 0/1
speed_numerator (int) 1-255
speed_denominator (int) 1-255
loop_length (int) 1-8
loop_section_active (int) 0/1
loop_section_beats (list: int int)
improvise (int) 0/1
pitch_range (int) 0-11
max_continuity (int) 0-255
OUTLETS
0 list
(int int int int) 1. beat generator number, 2. track number, 3. start time in ms, 4. end time in ms
MESSAGES
symbol
right inlet: label for analyzer
beat
left inlet: (+ int) incoming beat number
label
left inlet: (+ symbol) incoming beat label
tempo
left inlet: (+ float) current tempo
factor_oracle
left inlet: (+ symbol) dict name
beat_dict_name
left inlet: (+ symbol) dict name
SEE ALSO
djazz.beat_generator
djazz.audio.audio_out
djazz.audio.beat_player
Plays back a beat of audio at a time from a buffer, where beats are passed in in a dict.
Uses supervp to playback in tempo and with pitch transposition.
OUTLETS
0 signal
audio out 1
1 signal
audio out 2
MESSAGES
list
left inlet: (int float int int) 1. pitch transposition, 2. tempo, 3. start time, 4. end time
audio_buffer_offset_in_ms
middle inlet: (+ int) adjusts the latency of the audio output. Default is 170 ms.
crossfade_time_in_ms
middle inlet: (+ int) adjusts the crossfade time between beats.
audio_buffer_name
right inlet: (+ symbol) audio buffer to play from
SEE ALSO
djazz.audio.beat_generator
djazz.audio.supervp_player
djazz.audio.beat_reader
Reads the note data at the given beat data and sends it out to the beat player.
OUTLETS
0 list
(int int int) 1. track number, 2. start time in ms 3. end time in ms
1 int
beat count
MESSAGES
list
left inlet: (symbol int float) 1. beat dict name, 2. pitch transposition, 3. tempo
symbol
right inlet: beat list dict name
SEE ALSO
djazz.audio.listeners_router
Controls which other audio generator(s) a generator will listen to
Pattr messages (must be passed via an external pattrhub/pattrstorage):
include_master (int) 0/1
listener_1 (int 1-3) which player listener 1 is listening to
listener_2 (int 1-3) which player listener 2 is listening to
listener_3 (int 1-3) which player listener 3 is listening to
OUTLETS
0 symbol
label received from player being listened to
MESSAGES
list
right inlet: (int symbol) listener number and label passed from listener
label
left inlet: (symbol) the label passed from the master control. Will be passed through if include_master is set to 1
SEE ALSO
djazz.analyzer
djazz.audio.record
Records an audio buffer and a beat list, given the appropriate input.
OUTLETS
0 repetitions + int
sends every time a new repetition is added to beat list
1 setsize + int, crop 0 + int
sets the size of the buffer when recording is started/stopped, with/without clearing it, respectively
MESSAGES
signal
left inlet: audio in 1
signal
in1: audio in 2
audio_buffer_name
right inlet: (+ symbol) name of audio buffer to record to
audio_score_dict_name
right inlet: (+ symbol) name of audio score (+ beat list) to write to
beat
right inlet: (+ int) starts writing a new beat when received
initial_tempo
right inlet: (+ float) bpm by which to calculate next beat and note durations
loop_section_beats
right inlet: (+ list: int int) start beat, end beat
will begin a new repetition whenever end beat is reached, restarting at start beat
SEE ALSO
djazz.audio
djazz.audio.supervp_player
Wraps the supervp player for audio playback
OUTLETS
0 signal
audio out 1
1 signal
audio out 2
2 signal
position in audio file
MESSAGES
list
left inlet: float float float int:1. start time in ms, 2. end time in ms, 3. duration in ms, 4. transposition (in semitones)
int
in1: crossfade time between beats in ms
symbol
right inlet: buffer name to play from
SEE ALSO
djazz control
djazz.audio.transpose_octaves
Adjusts message being sent to djaz.audio.beat_player so that the audio will be transposed up the desired number of octaves.
Pattr messages (must be passed via an external pattrhub/pattrstorage):
octaves (int) -255 to 255
OUTLETS
0 list
(symbol int float) 1. beat dict name, 2. pitch transposition, 3. tempo
MESSAGES
list
left inlet: (+ list: symbol int float) 1. beat dict name, 2. pitch transposition 3. tempo
SEE ALSO
djazz.audio.transpose_pitch
Adjusts message being sent to djaz.audio.beat_player so that the audio will be transposed up the desired number of semitones.
Pattr messages (must be passed via an external pattrhub/pattrstorage):
semitones (int) -255 to 255
OUTLETS
0 list
symbol int float; 1. beat dict name, 2. pitchtransposition, 3. tempo
MESSAGES
list
left inlet: (+ list: symbol int float) 1. beat dict name, 2. pitch transposition, 3. tempo
SEE ALSO
djazz.audio
Handles all the audio portion of djazz.
OUTLETS
0 signal
audio out 1 left
1 signal
audio out 1 right
2 signal
audio out 2 left
3 signal
audio out 2 right
4 signal
audio out 3 left
5 signal
audio out 3 right
MESSAGES
signal
left inlet: audio in 1
signal
in1: audio in 2
-
audio_buffer_name
in2: (+ symbol) name of audio buffer to record to and play from
record_active
in2: (+ 0/1) 1 arms the audio buffer to record; will not start recording until a beat number is received. 0 turns off record state.
initial_tempo
in2: (+ float) bpm by which to calculate next beat and note durations
loop_section_beats
in2: (+ list: int int) start beat, end beat
will begin a new repetition whenever end beat is reached, restarting at start beat
max_repetitions
in2: (+ int) will stop recording when this number is reached
in
right inlet: (+ symbol) folder name audio data to load for audio in subpatcher
out
right inlet: (+ symbol) folder name audio data to load for audio out subpatcher
SEE ALSO
djazz.audio.record
djazz.audio.supervp_player
djazz.audio_out_track
Passes audio signal through. Control with djazz.view.audio_track.
Pattr messages (must be passed via an external pattrhub/pattrstorage):
volume (int) 0-127
active (int) 0/1
solo (int) 0/1
mute (int) 0/1
OUTLETS
0 signal
audio signal 1/L
1 signal
audio signal 2/R
MESSAGES
signal
left inlet: audio signal 1/L
right inlet: audio signal 2/R
SEE ALSO
djazz.view.audio_out_track
djazz.solo_bank
djazz.bang_speed
When active, will output beats in the polyrhythm given by numerator/denominator. Incoming beats will not be output, except for those that fall in phase with the output beats.
OUTLETS
0 bang
-bangs over time in the polyrhythm determined by numerator and denominator
1 float
-tempo using output bangs as beat
MESSAGES
bang
depends on polyrhythm state
left inlet: When effect off (active = 0), passed directly through. When effect on (active = 1), triggers a new polyrhythm, or is ignored if a polyrhythm is in course.
active
left inlet: (+ 0/1) turns effect off or on
numerator
left inlet: (+ int) number of evenly spaced bangs to output in the space of denominator beats at the given tempo
denominator
left inlet: (+ int) number of beats to output numerator evenly spaced bangs over
SEE ALSO
djazz.beat_clock
Inputs bangs and outputs a beat number, based on loop, step, start, and end parameters.
Will not output when end beat is reached or after.
OUTLETS
0 int
next beat
1 bang
bang when loop occurs
2 bang
bang when end is reached
MESSAGES
bang
left inlet: bang for next beat
next_beat
right inlet: (+ int) sets the next beat to play when next bang is received
end
right inlet: (+ int) sets the end beat; when reached, no more output will occur
step
right inlet: (+ int) how many beats to advance upon receiving a bang; default is 1
SEE ALSO
Externals
Subsections of Externals
Antescofo
Antescofo is used in two places:
The master control , in its subpatcher djazz.antescofo_get_tempo to calculate the tempo. Uses the score “djazz_antescofo.txt” in the djazz_data/scores folder.
The MIDI beat reader , to sequence and send out midi notes in the proper tempo and rhythm. Uses the score “antescofo_play_beat.txt” in the djazz_data/scores folder.
Midifile
This is a small C application that is called from the shell in the make-score-file tool. It converts MIDI data to a .txt file, which is then converted by the tool into JSON.
supervp
SuperVP is used in the djazz.audio_beat_player abstraction. It is called via the djazz.audio.supervp_player wrapper abstraction.
File formats used
Audio data is saved in WAV form. Besides this, all Djazz data is saved to and loaded from JSON in order to be imported into Max dictionaries. This includes MIDI data.
The antescofo .txt files included with song folders is converted to JSON when loaded.
Improvisation
Subsections of Improvisation
Generator Components
flowchart TB; in(( )) out(( )) subgraph SP[Score Player]; direction TB in1(( )) speed1[Speed Control] bc[Beat Clock] out1(( )) in1 -->|bang| speed1 -->|bang| bc -->|beat number| out1 end subgraph I[Improviser]; direction TB in2(( )) speed2[Speed Control] label[hold label] FOP[Factor Oracle Player] out2(( )) in2 --> |label |label in2 -->|label| speed2 -->|bang| label -->|label| FOP -->|beat number| out2 end in-->in1 in-->in2 out1-->out out2-->out
The generator uses the beat number when improvise mode is not on; it uses this to play the next beat or a different beat if one has been selected by the user. In improvise mode, the beat number is not considered. The label produced by the analyzer is used, as described above.
The beat generator can play in two modes: score player and improviser. Each of these also contain two modes: play at the tempo given by the tap, or play at a different speed: double speed, quadruple speed, half speed, and one and a half speed. This speed change is controlled by an object–the “Speed Control” that, when messages are passed to the generator, modifies the timing of their distribution to the generator’s internal objects. Even though both the score player and the improviser use the Speed Control, the musical result is different, because the Score Player receives beat number messages and the Improviser receives beat label messages. The musical result is described in the manual section on improvisation . Following is a description of the abstractions that control these processes.
The Score Player contains two subpatchers, a “Master Clock Follower” and an “Internal Clock Follower.” In Score Player mode, if the Speed Control is active, beat numbers are sent to the Internal Clock Follower. The Internal Clock Follower contains its own Beat Clock object. It is passed the same asynchronous messages as the Master Beat Clock, concerning the song beat data like the end beat, the current section beats, and whether the current chapter is being looped. If the Speed Control is inactive, the beat number messages given by the Master Beat Clock are simply passed through. If Speed Control is active, beat numbers are output at a different tempo than the input tap.
In the Improviser, it is the beat label messages are passed. Beat labels are passed in at the master tempo. When a label is received, it is saved in a message object and a bang is sent to the Speed Control. If active, the speed control at its modified tempo, which trigger the saved label to be passed to the factor oracle player. The result is that the factor oracle player outputs beats that conform to the harmony of the given beat, even though they are at a different tempo.
Factor Oracle and Player
The factor oracle
The factor oracle data structure is described in detail in the following references: reference 1 reference 2 reference 3
The factor oracle player
The factor oracle player creates the improvisation. As its name suggests, it contains and makes use of the “factor_oracle” Max abstraction.
The algorithm for choosing beats from the factor oracle is described in this paper.
When the maximum continuity is reached, it takes a suffix link and searches for a matching label. If no matching label is found, it chooses a random state.
The analyzer uses the same set of symbols as the factor oracle. It is used both offline, to create a file of labeled beats for a song, and online, to convert the data at the beginning of each beat into a symbol to be passed to the factor oracle player. This symbol is then used as the query to the factor oracle to produce the next beat.
Labels
Definition:
A beat is a collection of notes that occurs between two timepoints. These timepoints are considered to occur at regular intervals. Thus a piece of music that has a pulse can be considered a sequence of beats.
At each new beat, calculate a new beat’s worth of music to play. The calculation consists of finding a beat in the database that is the “best match” (or at least a “good match”) based on the data that exists at the beginning of the new beat.
This data can be based on any of the following:
The location in the song/musical piece. For example, “the A minor 7 chord that occurs at the beginning of measure 13.” A good match is a beat from the database that occurs over the same chord. This could be the same beat as in the original piece, or it could be a different beat that has the same chord, which is more interesting. This creates correct adherence to musical form, if such form exists in the piece.
What we just played in the last beat. For example, an ascending scale in the previous beat could look for a melody that starts on the next note in the scale in the next beat. This creates continuity.
What other listeners have played. This creates responsiveness.
In the current usage of Djazz, this match is based only on option 1: the chord symbol that occurs on the given beat.
Label matching
Matching is done by comparing labels. Each beat has a label which reflects the data described above. It is a string of symbols. The nature of this string is determined by the user. In the current usage of Djazz, this string represents the chord symbol, which consists of the chord root and its quality, separated by an underline. Chord roots are numbered from zero to eleven, with zero representing C; thus, C# (or Db) is 1, D is 2, etc. For example, “0_maj7” represents a C major seventh chord.
A match can be made by exact comparison, or by “fuzzy” methods. In the current usage of Djazz, a chord represents a match if the quality is the same, but the root can be a small distance away from the desired one. The melody is then transposed to match the correct root.
Offline analysis currently consists of adding the chord labels to each beat. The user inputs this using a graphical tool that takes chord information and stores it in a list of beats; that is, no analysis is done save for copying the “chord chart” into a data file. This data file, which also contains other data like the song tempo, time signature, section starting and ending beats, and song title, is loaded into Djazz when a song is played.
Online analysis, as a result, is just a case of reading the chord label from the dictionary when a beat number is generated. This is in fact done by the master clock: it sends out the tempo, chord label, beat number, in immediate succession each time a tap is input.
The methods here are general, and the current usage of Djazz can be changed to admit other types of music. This would involve using an extant system, devising a new one, or modifying an extant one to label beats. Optionally defining the definition of a match, if it is not defined as exact.
The following criteria must be met: The online analyzer uses the same set of labels as the offline analyzer, if an offline analyzer is used.
Also, the architecture is modular, which means that other methods of improvisation are possible. That means that another object can replace the factor oracle player within “improviser” as long as it adheres to the following criteria:
- It receives a label at the beginning of each beat
- It produces a beat number, in response to the label, that represents the next beat to play.
Example: Song with the same chord for all beats.
In the case where all the beat labels are the same, the factor oracle player algorithm will ground out to simply choosing a measure aleatorically.
In some cases this could be desired. It is perhaps more interesting, though, to take advantage of the label-matching behavior to create one’s own labels, and thus direct the improvisation.
Following the description of the matching criteria, the first half of the label must be a chord root symbol, but the second half can be anything: it will be matched by exact string matching.
A song with beats labeled thus:
E m7 | E m7 | E m7 | E m7 | E m7 | E m7 | E m7 | E m7 | |
---|---|---|---|---|---|---|---|---|
E m7 | E m7 | E m7 | E m7 | E m7 | E m7 | E m7 | E m7 |
courld be written as
E 1 | E 2 | E 3 | E 4 | E 1 | E 2 | E 3 | E 4 | |
---|---|---|---|---|---|---|---|---|
E 1 | E 2 | E 3 | E 4 | E 1 | E 2 | E 3 | E 4 |
which would always select a measure labelled with a 2 to follow a measure labelled with a 1, a 3 to follow a 2, etc. Like this, formal positions in sections could be preserved.
E a | E f | E d | E c | E a | E b | E a | E b | |
---|---|---|---|---|---|---|---|---|
E d | E f | E c | E 4 | E 1 | E 2 | E 3 | E 4 |
Djazz supplies a software framework for empirical inquiry into the suitability of a particular data encoding for music: ascertaining the “correctness” of an analysis via listening to the resulting synthesis.
External controls and views
To use a new kind of Launchpad, a Launchpad device file must be created and added to the djazz_data/presets folder, and a corresponding Max objects added in the external_controllers subpatcher at the top level of the.
Subsections of External controls and views
Parameters
Parameters
Djazz uses Max parameters to attach a device, ewither a controller or a view, to the system. The Max parameter system is similar to its pattr system: when a message is received in the form <parameter name > <parameter value >, the parameter with the called name is updated with the given value. Unlike pattrs, though, whose scope is contained within an object or abstraction, parameters have global scope.
All pattrs in djazz.view are parameter enabled.
The object _djazz.parameter_handler.js_ß maintains an array of parameter listeners, one for each parameter, and outputs parameter names and values upon when values are changed. Device interfaces can be connected to the inlet or outlet of this object to modify or display parameter values.
To create a Max patcher that serves as an interface for an external device:
- Control devices should output messages in the form <parameter name> <parameter value>.
- View devices should output messages in the form <parameter name> <parameter value>. See the djazz.launchpad_interface patcher as an example of an abstraction that does both.
A list of all parameters and their current values can be accessed by opening “Parameters” in the Max menu.
NOTE to developers interested in JS Max listeners: The JS Max objects Parameterlistener and maxobjlisteners are actually hard to deal with because you can’t delete them. So they continue to live, inaccessible but possibly still affecting output, until garbage collection comes their way.
Dicts
Dictionaries are used in Djazz in different ways, for representing songs, architecture, and as another example for the launchpad, to keep track of how buttons are mapped to parameters and how parameters are mapped to lights. For this, several different types of dictionaries were required:
- Two device-specific dicts
- One mapping dict
- Two runtime parameter dicts
The format for each of these dicts can be done in different ways. Some criteria exist:
- User-created files should be easy to create, either by editing the text or with a max object; prefereably both.
- Data access in the dicts at runtime must be efficient.
These two criteria ask for different formats. Also, we might need to change them in the future, to accomodate new devices.
To address these issues, database accessor files exist to translate between the formats of the various dicts. Each one exports a set of methods that access data or modify data. the implementation of the methods are hidden to the user. Each one is specific to the context it acts in. The exported methods have the same names, but the implementation is different depending on the structure of the dict it reads or writes to. thus, we can use a single javascript object to read the desired dictionaries and then translate them into the format the system needs. Each accessor is imported into the module using a require statement. The module names passed to these require statements are given as jsarguments to the object. If we change or add a new dict format, we write a new reader, which only involves rewriting the implementation of the exported methods—and replace it in the appropriate require field.
Device-specific dicts
The two device-specific dicts are imported from JSON files; thus are two device-specific files:
- the device file
- the grid file
These must be written in order to connect a new device with view and control capabilities, like a Launchpad, to Djazz.
Device dict
the device file contains device-specific data:
- the device name (used for routing messages in Djazz)
- any metadata about the device itself
- the number of midi controls
- the number of cc controls
- the MIDI/CC codes for each color. Colors are represented by two variables, their hue (the name of the color itself) and their value (bright or dim).
- the MIDI/CC codes for each button’s illumination behavior. For the Launchpad Pro MK3, buttons can glow statically, they can flash, or they can pulse.
For the Launchpad Pro MK3, the data file is this:
{
"device" : "Launchpad Mini"
,
"manual" : "https://leemans.ch/latex/doc_launchpad-programmers-reference.pdf"
,
"midi_count" : 120
,
"cc_count" : 0
,
"colors" : {
"none" : {
"bright" : 0
,
"dim" : 0
}
,
"red" : {
"dim" : 1
,
"bright" : 3
}
,
"orange" : {
"dim" : 17
,
"bright" : 51
}
,
"yellow" : {
"dim" : 41
,
"bright" : 51
}
,
"green" : {
"dim" : 16
,
"bright" : 48
}
,
"brown" : {
"dim" : 38
,
"bright" : 35
}
}
,
"behaviors" : {
"static" : 12
,
"flashing" : 8
}
}
Grid dict
the grid file describes the way a song grid can be represented on the Launchpad. It contains the following information:
the device name
the cell numbers that represent song chapters. these are listed in order of the chapters they represent. In the following code example, then, “cc 89” represents chapter 1, “cc 79” represents chapter 2, etc.
the colors that represent the state of a chapter in the grid. A grid cell (chapter or bar) is in one of the following states:
- playing: the cell is currently being played
- waiting: the cell has been selected to be played, and will start playing as soon as the next beat occurs
- off: neither waiting or playing
- unused: the song does not contain the chapter or bar associated with this cell
the cell numbers that represent song bars (listed in order like chapter numbers)
the colors that represent the state of a bar in the grid (same as chapter states).
optional metadata about the device, such as the links to manufacturer’s information
For the Launchpad Pro MK3, the grid file is this:
{
"device" : "Launchpad Pro MK3"
,
"manual" : "https://fael-downloads-prod.focusrite.com/customer/prod/s3fs-public/downloads/LPP3_prog_ref_guide_200415.pdf"
,
"grid" : {
"chapter" : {
"cells" : [
"cc 89", "cc 79", "cc 69", "cc 59",
"cc 49", "cc 39", "cc 29", "cc 19"
]
,
"colors" : {
"unused" : "none",
"off" : "green dim static",
"waiting" : "green dim static",
"playing" : "green bright static"
}
}
,
"bar" : {
"cells" : [
"midi 81", "midi 82", "midi 83", "midi 84", "midi 85", "midi 86", "midi 87", "midi 88",
"midi 71", "midi 72", "midi 73", "midi 74", "midi 75", "midi 76", "midi 77", "midi 78",
"midi 61", "midi 62", "midi 63", "midi 64", "midi 65", "midi 66", "midi 67", "midi 68",
"midi 51", "midi 52", "midi 53", "midi 54", "midi 55", "midi 56", "midi 57", "midi 58"
]
,
"colors" : {
"unused" : "none",
"off" : "brown dim static",
"waiting" : "red dim static",
"playing" : "red bright static"
}
}
}
}
Mapping dict
A mapping file is a file that the user creates, using the editing tool in Djazz, that contains the mappings between the Launchpad cells and the parameters she wants to control. It also imports into it the grid.
Runtime parameter dicts
Launchpads have both view and control capabilities; that is, they can send input to Djazz (control) as well as show output (view).
System Architecture
Synchronous and Asynchoronous Input
Input is passed to patchers two ways: synchronously and asynchronously, depending on the type of input data. Synchoronous data;
- beat number (int),
- current tempo (float)
- beat label (symbol)
- loop section beats (list: int int) the start and end beat of the section being looped. This is used to calculate the length of repetitions for live MIDI and audio input. It can change when the current beat advances to the next chapter, so this data is triggered and sent out by the master control instead of asynchronously.
- dict names (symbol), such as those for beat lists and factor oracles . This is because data must be loaded before other control variables can be set.
This data is passed serially through patch cords. Send and receive Max objects are not used, to ensure synchrony, as well as to aid debugging and avoid complicating the control flow.
Asynchronous data is anything changed by the user using the GUI. A lot of this data is well known to audio interface users: track volume, MIDI and audio effects like pitch transposition, for instance.
Subsections of System Architecture
General Approach
Synchronous data is passed using patch lines. Sends and receives are avoided in order to allow event probes, protect encapsulation, and generally keep patchers readable.
Asynchronous data is stored in pattrs, and updated with pattrhubs and pattrstorages.
Asynchronous Data and the MVCVC Design
ARCHITECTURE
Djazz uses the familiar Model-View-Control-View Control (MVCVC) design pattern. Each of these components will be discussed below. This particular interpretation of MVCVC was influenced by the design specifications for audio plugin development for the WWISE audio engine distributed by Audiokinetic.
flowchart TB; V[View]; C[Control]; M[Model]; VC[View Control]; V --> C C --> M M --> VC VC --> V
This design is primarily to maintain an organised control flow of asynchronous data–data that is input by the user. This is done using Max’s pattr system; any user-controlled variable is stored as a separate pattr in the view, control, the model, and possibly the view control. Their values are passed through each of these components in a directed path, possibly undergoing name translation or value conversion along the way:
flowchart TB; subgraph V[View]; end subgraph C[Control]; direction TB tvc[translate view to control] ctrlpattrs[control pattrs] tcm[translate control to model] tvc --> ctrlpattrs--> tcm end subgraph M[Model]; end subgraph VC[View Control]; direction TB tmv[translate model to view] end V --> tvc tcm --> M tmv --> V M --> tmv
While the path resembles a loop, output restriction in the view in fact keep messages from being passed on, thus avoiding circularity.
MODEL
The model (djazz.maxpat) is the actual Djazz patcher, in the sense that all the functionality occurs in this patcher: MIDI and audio input and output processing, timing synchronization, improvisation calculation, etc.
Abstractions in the model contain pattrs which are set by sending messages in the form <pattr name> <pattr value>. The pattrhub Max object distributes these messages to the appropriate pattr objects.
CONTR0L
The control acts as the interface to pass messages to the model. It is similar to a shell in an operating system, or a parameter bank in an audio plugin. Its input represents the set of all possible asynchronous messages that can be sent to Djazz. Its job is to translate these messages to their appropriate messages that the model understands, and to handle any value initialization and message dependencies.
There are many pattrs in the model.
- Not all pattrs may be desired to be used, or exposed to the user.
- The organization of the pattr hierarchy in the model may not reflect the user’s conception
- it may be wished to call pattrs in the model via other variables that need some processing to determine their value.
The control, then, acts a shell which exposes the messages that can be sent to Djazz. By replacing the control, you can change the commands it responds to without changing the model itself.
To make modularity easier, pattrs in the control are grouped in encapsulations. This means that calling nested pattrs demands the “::” syntax (see example below).
VIEW CONTROL
The model outputs pattrs to any view that wants to receive them. It does this the same way as the control: a pattrstorage object is at the top level of the model patcher, and when a pattr value is updated, it outputs the pattr name and value.
There are two types of pattrs that the model outputs: those that are specifically for a view, and the parameters that were sent from the control. Values in abstractions in the model that are to be sent to the view control are stored as pattrs inside subpatchers named “view,” so that they can be extracted from the other pattrs passed out of hte model.
VIEW
The view is a collection of bpatchers containing graphical controls and data fields. It triggers messages to be sent to the control. Different views could be used, and even several views at the same time. The Launchpad interfaces are also views.
Note: with this method, the interior pattrhub objects end up not being used at all, but it’s still good to leave them in so that these objects can be reused and are not dependent on being called this way.
Finally, unused variables simply aren’t called. But to be absolutely sure that uncalled variables don’t give us trouble, by storing state that have forgotten about, or by accidentally being called, due to sharing names accidentally with a control variable, we can turn off their visibility to the pattrstorage system, which means they can never be called.
To avoid an infinite loop, we make use the pattrstorage object’s outputmode attribute. In the control and model, the pattrstorage outputmode is set to 2: any changed values are sent out. But in the view, the output mode is 6: values that are changed by objects in the pattr system are not sent out from the pattrstorage object. That is, only values that are changed by user interaction are sent out. Thus the values received by pattrhub are set, but not sent to the control. This way, pattr values can be sent back from the model into the view (via the view control object which filters them and translates their names if necessary), without these pattr values being again passed out to the control (which would create an infinite loop).
EXAMPLE
As an example, we follow the message sent when the pitch on MIDI track 13 is changed, using either the up, down, or reset-pitch button:
The corresponding pattr in the view is named transpose_pitch, and takes an integer value. The up, down, and reset buttons are proper to the usage conceived for this particular graphical interface, so their logic is contained within the view; they modify the transpose_pitch pattr and do not send any messages directly to the control.
In the view, the triggered pattr is located in the bpatcher named track_13, which itself is inside the bpatcher named midi. When triggered, the pattrstorage object inside the view updates its value and sends out the message midi::track_13::transpose_pitch 0. This message is sent out the second view outlet and into djazz.control.
In djazz_control, the pattrhub object distributes the message to the corresponding subpatcher named midi, which contains a djazz.control.track abstraction named__track_13_, which contains a pattr object named transpose_pitch.
This pattr now sends its updated value 0 directly (not via the pattrstorage object in djazz.control) through the djazz.control.midi.translate_track abstraction in order to rename it for the correct pattr object in the model:
The message sent to the model is midi::midi_out::midi_out_bank::track_13::effect_list::transpose_pitch::semitones 0. It is received by the pattrhub object in the model and sent to the corresponding patcher:
When the pattr in the model is updated, the model’s pattrstorage sends its name and value to the view control, which translates it and sends it back to the view.
The state of the variable can be seen in each of the four debug windows (view, control, model, and view control).
Pattr Visibility
For more complex abstractions that use several pattrs, as well as abstractions that lend themselves to reuse, such as the beat-generators which are nested in midi- and audio-beat-generators, Djazz uses the second method of pattr passing:
flowchart LR; in(( )) subgraph A[A]; subgraph B[B]; subgraph C[C]; direction TB p[pattr my_pattr] end end end in -->|A::B::C::my_pattr|p
flowchart LR; in(( )) subgraph A[A]; direction TB pA[pattr my_pattr_A] subgraph B[B]; direction TB pB[pattr my_pattr_B] subgraph C[C]; direction TB pC[pattr my_pattr_C] end end end in -->|my_pattr_A|pA pA -->|my_pattr_B|pB pB -->|my_pattr_C|pC
Pattrs within nested abstractions are “hidden” from calls to the exterior abstraction. They are updated by calling a pattr in the exterior abstraction, which translates them to the appropriate pattr call to the interior abstraction; sometimes this sequence of calls to nested pattrs has several levels.
In Max, a pattr is never actually hidden, as it can always be called using the double-colon syntax corresponding to the object in which it is located. But a pattr can be hidden to the pattrstorage object that references it by setting the pattr’s visibility attribute to zero. The small object pattrvis.js was written to turn on and off the visibility of all the pattrs in an abstraction; it can act hierarchically as well, turning on or off all the visibility of pattrs in subpatchers.
The pattrvis.js object is used in the appropriate abstractions, and does not have an effect on the functionnality of the program. It can be used to keep the debugging windows more readable, when nested pattrs have their visibility off. To inspect pattr messages passed to nested abstractions, turn their visibilities on by passing 1 to the pattrvis.js object in the corresponding abstraction.
Synchronous Data in the Model
Because Djazz is beat-based, most objects are built to receive and process information at recurring instances. Djazz is played by inputting a tap, either manually, or from a built-in metronome, or from another application. The tap triggers a beat, which triggers notes to be output from the factor oracle or the score, which triggers note data to be output from antescofo, which triggers sound to be output by the audio or midi outputs.
Upon receiving a tap, Djazz plays music by selecting sections from scores in one of two ways: playing it back “straight,” or by calculating an improvisation using the factor oracle algorithm. This results in a reshuffling of the beats, which lines up with the harmonic content of the song. The factor oracle algorithm can be modified with pattern-matching methods. The input to the factor oracle come from scores and real-time input, either audio or MIDI, and is output as audio or MIDI. It uses Antescofo to generate notes both from scores and generated as improvisations in tempo.
TOP LEVEL
graph TB; A[Master Clock] B[Audio] C[MIDI] A-->|tempo, beat number, label| B A-->|tempo, beat number, label| C
In a nutshell, Djazz is made up of the following:
- several players which function independently.
- master control for synchronising timing and for broadcasting global commands.
The master control sends out the following data synchronously to all the players:
- tempo,
- beat,
- beat label.
It sends this data immediately in succession and in this order to midi and audio generators. This order is important, so that the generators can calculate the correct information to play at the beginning of each beat. It determinmes this data in the following ways:
- It contains a beat clock abstraction which acts as a master clock; it outputs a beat number when it receives a bang. It increments its beat number with each output, but also keeps track of position in a song form and adjusts the beat in accordance if looped.
- It keeps track of tempo if tempo is manually input and fluctuates. This uses antescofo.
- It reads from the song dict to get label.
GENERATORS
The abstractions that do most of the work in Djazz are the generators. They accept the data passed by the master control and convert them into appropriate musical output. There are two types of generators: MIDI and audio. Their construction is very similar; the difference is a result of the way audio data is played differently from MIDI data.
MIDI GENERATORS
flowchart TB; gIn(( )) g1[Generator 1] g2[Generator 2] g3[Generator 3] g4[Generator 4] g5[Generator 5] mbPlayer[MIDI Beat Player] t1[MIDI\nTrack 1] t2[MIDI\nTrack 2] t3[MIDI\nTrack 3] t4[MIDI\nTrack 4] t5[MIDI\nTrack 5] gOut((( ))) gIn --> g1 --> mbPlayer gIn --> g2 --> mbPlayer gIn --> g3 --> mbPlayer gIn --> g4 --> mbPlayer gIn --> g5 --> mbPlayer mbPlayer --> t1 --> gOut mbPlayer --> t2 --> gOut mbPlayer --> t3 --> gOut mbPlayer --> t4 --> gOut mbPlayer --> t5 --> gOut
AUDIO GENERATORS
To write
Adding to Djazz
Subsections of Adding to Djazz
Adding to Djazz
To add functionality to Djazz, use the following method:
IN MODEL:
- Create your patcher. Use pattr objects for asynchronous input. You can include a pattrhub object to broadcast pattr messages for reusability’s sake, but this is not needed if the patcher is inside the model patcher (djazz.maxpat).
- Connect your patcher in the appropriate place inside djazz.maxpat. See the specific descriptions for examples.
IN CONTROL:
- Inside djazz.control, place a subpatcher, possibly containing other subpatchers, with pattr objects, so that the patcher/pattr hierarchy reflects the way in which you want your new functionality to be called.
- Create a translator subpatcher and connect the output of the control subpatcher to it. This translator subpatcher should convert the message passed into djazz.control (<control variable name> <control variable value>) to the message needed by the model (<model variable name> <model variable value>). The Max regexp and __ objects are good for this. You can use the Debug View button to open the lists of pattrs in the model and control to make sure things are being named properly.
- Parameter-enable the pattrs if you want to trigger them with external devices via Djazz’s parameter handler patcher. Alternatively, you could pattern-enable the pattrs in the view (following section). Only parameter-enable one set of pattrs: those in the control or those in the view.
- Connect the translator to the control output that goes to the model.
IN A VIEW:
- Create a bpatcher containing the graphic controls and views for your patcher. Attach pattr objects to the controls.
- Create a translator subpatcher and connect the output of the control subpatcher to it. This translator subpatcher should convert the message passed out of your bpatcher (<view variable name> <view variable value>) to the message needed by the control (<control variable name> <control variable value>). The Max regexp and __ objects are good for this.
- When placed in djazz.view, the pattrstorage system will handle the messaging; you don’t have to connect your bpatcher to an output.
IN A VIEW CONTROL:
- If necessary, add a subpatcher with pattrs, and a corresponding trnaslator, to djazz.view_control if you want to pass data from the model back into your view subpatcher.
Be sure to give all your pattrs initial values, and that these initial values are the same in the view, control, model, and possibly view control.
MIDI Effects
Effects that modify MIDI output should be placed inside the djazz.midi_out_effect_list abstraction inside the djazz.midi_out_track abstraction:
Audio Effects
Effects that modify audio output should be placed in one of two places, depending how they function. There are two places where audio can be modified:
If the object does not process audio data itself, but determines parameters to pass to the supervp player, so that it plays audio data back differently, it should be placed in the djazz.audio_beat_generator abstraction. This is how Djazz’s audio_transpose_pitch and audio_transpose_octave effects work:
If the object processes audio itself, it should be placed between an djazz.beat_player object and an audio.out object:
Audio Effects
The factor oracle player, which Djazz uses to improvise, can be modified or replaced by another algorithm. There are several levels of depth to which you can modify the improvisation functionality; read the API section on improvisation . Here is just an explanation for where to put a new improviser Max object or abstraction.
Replace the improviser.maxpat abstraction in djazz.beat_generator. Your new improviser object should conform to the same input and output messages as the improviser.maxpat (see the reference page ).
Going further
The sections in this chapter refer to functionality that is not in Djazz, but where the architecture is organized to offer a development environment that encourages musical exploration as well as design issues in music programming.
Subsections of Going further
Changing the analysis in the factor oracle
Enabling listeners and Real-Time Analysis
Dynamic Object Creation and Destruction
This section describes an untested method for simplifying dynamic object creation and destruction in Max, using the MVC design described in another section. It was originally intended to be used with Djazz. It was coded using Javascript arrays and dictionaries of Max object instances, which ran into threading issues when these instances were created and destroyed before the Javascript thread was updated. Since then, Max has introduced Array objects (non-Javascript), which hopefully will obviate this problem; all code could be done in pure Max.
Dynamic creation and destruction of objects: arrays and dicts
- Hierarchies
- Saving and reloading architecture
Objects in Djazz should be created and destroyed, so you can set up variable numbers of different types of players, and different midi and audio outputs. The resulting configurations can then be saved and reloaded.
There are two data structures for saving objects: dictionaries and arrays, because some objects occur as ordered sets [midi tracks, effects], while others don’t (players, although they could).
Ordered sets are placed in arrays using javascript. This way they can be addressed using their indices in the array, and array operations can be used to keep track and change them.
In javascript you can make arrays of objects. To delete them, you have to both remove them from the patcher and remove them from the array or you will get undefined stuff.
The midi out bank is an example of a hierarchy of objects containing arrays. The bank contains tracks, and the tracks contain (among other things) midi effects. Tracks can be created and deleted, and so can effects. [SLIDE: tree]
In addition, there are effects that apply to groups of tracks:
- BANK
- TRACK GROUP
- TRACK
- EFFECT_LIST
- EFFECT
To make this structure:
Each object in the hierarchy contains a javascript object with the variable name “components,” which is responsible for creating, deleting, and dispatching messages to, and gathering data from the hierarchy objects it contains.
Each « component » object contains an array of objects.
When objects are created, they are also placed in the array.
The code in each of these component objects is similar, and they could be abstracted into classes (prototypes) that components are derived from, if this method seems important enough to do that.
The bank can be saved as a dictionary.
Dictionary entries can be dictionaries and arrays, and array entries can be dictionaries and arrays.
The midi bank dictionary contains arrays which contain dictionaries which contain arrays. Arbitrary nesting of dictionaries and arrays is possible. Access and modification becomes complicated, which I’ll talk about in section 4.
To save a midi bank layout:
Javascript objects can declare attributes: values that can be accessed like normal max objects. Attributes can be dictionaries. Attribute values can also have custom getters and setters, which means that the attribute value does not have be something actually stored. A getter can dynamically construct it when it is invoked, and a setter can do something other than save the given value.
The midi bank thus has an attribute « bank_dict » that, when queried, builds a dictionary from its components’ data. It writes an array of its tracks, calling each of its tracks to give it the required data for its given array index. It calls each track by requesting a dict attribute from the track representing its components. The track builds a dict in the same way, calling its effect components. This process continues until a recursively-built dictionary is completed, then the midi bank passes it to the caller which writes it as a json file.
To reload the midi bank, the opposite occurs: the attribute value setter builds the track list by creating each track and then sending it the corresponding track dict so that it can build itself.
The actual dict values are at no time saved.
[EXAMPLE]
This can be applied to the entire architecture of a session, including all the players. It will be once we’ve worked out what all the players will be and how they will be arranged.
Because of the hierarchical arrangement of dictionaries, the midi bank can have its own set of presets that can be loaded and saved inside the preset of an entire session.
To load an entire session including the pattr presets, the architecture dictionary has to be loaded first, and then the preset file.
Both the model and the view contain these component objects.
Communication between the view and model is in the rightmost wire.
Building from these dicts in the model and the view treat the dict like a model, and the model and view become its views.
Communication between the control and model during construction must be handled carefully and I’m interested in how to do it better. Originally I thought it would be great to include the dict attributes in the pattr system. Then they could be controlled in the same way that the rest of the parameters are controlled.
The problem is this. [SLIDE] Javascript operates on a different (low-priority) thread than Max objects. When it calls Max to creat an object (new default), it passes control to this thread and continues to the next javascript command—we don’t know when the max command will finish in relation to the javascript.
As components in the view hierarchy are built, we can’t assume that their analogs in the model are built at the same time. Thus messages cannot be passed from one to the other.
Thus we cannot count on the pattr system working when the hierarchies are constructed. It has to use a different system.
Creating effects: to create a midi effect, several easy standards must be met:
- MIDI notes follow a given value (list of numbers)
- MIDI comes in the left inlet and out the left inlet
- A control/view and a model patch
- Communication btw control and model occur via pattr, so pattrs with the same name (possibly hierarchic) must exist in both patches
- Control and model patchers must be put in appropriately named subfolders of a folder titled by the effect name.
If you do this, the effect will show up in the effect list.
This gives an easy, max-less way for developers to add their own effects.
Notes on Rewriting Djazz
Subsections of Notes on Rewriting Djazz
Dicts in Max and javascript
Dicts, like colls, are persistent and global, so use them only as read-only. Don’t use them as variables to pass around or for keeping track of state. Pattrs basically do this anyway so just use them to keep track of state snapshots.
You can’t put max objects in a dictionary. But you can put them in a javascript array. Max patchers basically are dictionaries, though, for objects in them with scripting names (varnames). They must both use hash tables to access their contents.