Repository navigation
MIDI objects, list / matrix / mask controls, MIDI batching and sysex in the ossia binding - #225
Merged
Merged
Conversation
Message kinds and range lists, a note tracker that pairs every note-on with one note-off, a frame-dated event queue, a per-note channel allocator, euclid and swing, and a chord-type / mode table (chord types from free-midi-chords, MIT). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Midi scaler plays its Scala tuning as MIDI: one channel per note with a pitch bend for the configured range, or MTS single-note tuning sysex. Midi dispatcher spreads notes over a channel set (round robin, random, ping-pong, chord orderings with a grace period, surplus channel). Midi MPE converts MIDI to MPE and back, and shapes or randomizes per-note expression. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Holds note-offs while a pedal (CC 64 or a toggle) is down, in All, Chord, Semi-sostenuto and Sostenuto modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Kinds list (including sysex and real-time), channel / note / CC range lists with an invert, a value range, linear channel / note / CC maps, and a Rejected output with everything that did not pass. Note-offs follow their note-on's output and mapping when the settings change while notes are held. The Channel control keeps the selected channel, as its label says; it used to drop it. Each CC is sent once in CC mode; it used to be sent twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Midi transform turns one message kind into another, with byte sources, insert / replace / drop modes and an input filter; produced notes always get their note-off. Midi CC snapshot sends up to eight stored CC values, immediately or with soft takeover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
A Clock out output carries the incoming clock and transport messages, divided by N, shifted by N clocks at each Start, delayed by up to a second, with or without SPP. The decoded outputs are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Note echoes, synced or in milliseconds, with repeats, velocity feedback, an accumulating pitch step, dry / wet, fixed length and per-note expression forwarded to the echoes. Every echo gets its note-off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Shapes velocity, release velocity, CC, pitch bend (in 14 bits) or pressure: fixed value, rescale, curve, compression, gain with key tracking, offset, seeded random and clip, in that order. Note-ons never become note-offs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Chance can be drawn per note, per chord or per period on the musical grid, velocity can act as a probability, a euclidean gate drops notes on rests, and one CC can get a seeded random offset. stop() releases what sounds. Seeded takes are unchanged at the new controls' defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Sections, tabs and grids for Midi transform, CC snapshot, value shaper, delay, MPE and Humanize, with the controls that do not apply to the current mode greyed out. Humanize's columns are titled sections so nothing is clipped. Labels: transform "Value 1/2", "Fixed channel", "Only channel", byte ranges and modes; value shaper "CC or note", "Random add"; Humanize "Random CC", "CC random range"; MPE "Release reserve (ms)". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
A note-off goes to the output its note-on went to, whatever the type selection says: with Types "noteon", or the Note on filter type, the note-offs of kept notes were sent to Rejected and the notes hung. Layout in Basic / Select / Remap sections; labels "Types", "Invert ranges", "Value range", "Value mode", "Outside map". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
With no surplus channel, a note that finds no free channel takes the one released longest ago, even within its release reserve, or else ends the oldest sounding note (in the chord modes, the one on the channel of the same rank) and takes its channel. A legato chord change longer than the grace time used to lose the whole new chord. Grouped layout; "Surplus channel", "Grace (ms)", "Release reserve (ms)". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
"Input bend range" (default 2) scales the incoming pitch bend; it was read with the output Bend range, so a keyboard wheel bent 48 semitones with an MPE synth setup. Grouped layout, MIDI output controls greyed in MTS mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
- Rate code 2 is 29.97 fps and 3 is 30 fps, as the MTC spec says; they were swapped, also when picking the frame rate by hand. - Full-frame MTC sysex is decoded (it was gated on meta events), for any device id. - Sync Mode and Framerate start at MTC and clock / Auto in the host too, with readable labels. - Grouped layout: Decode and Clock out sections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Progressions.hpp parses the chord-symbol notation of free-midi-chords (diatonic numerals, mingus suffixes, accidentals, slash basses, rests) without allocating, realizes symbols in a key and mode, and voices them with the closest inversion. ProgressionTable.hpp holds the 194 progressions of free-midi-chords (MIT) with their moods. Midi progression player steps through a progression of the table or a custom one every N beats (locked to bars), on a trigger or on each played note; it plays voiced chords, snaps played notes to the chord or scale, or maps white keys to chord tones, and outputs the current chord, degree and root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Two engines, one object. Turing: a shift register of 1 to 16 steps whose recirculating bit is redrawn with probability 1 - Lock, negative Lock giving the doubled inverted loop; gates from register bits like the Pulses expander. Marbles: per-step draws shaped by Spread and Bias, Deja vu replaying a loop of Length steps, Density / Regularity gates and Jitter. Steps on the musical grid (or free, or advanced by input notes or a Step impulse), quantized to a scale weighted by Steps, swing, gate length, velocity range, accent, seed; Value and Gate outputs to drive other processes. Inspired by Music Thing Modular's Turing Machine (Tom Whitwell) and Mutable Instruments Marbles (Emilie Gillet); no code taken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
A grid of up to 32 x 16 cells whose rows are scale notes (or a drum note map); a play-head steps through the columns on the musical grid and plays the live cells, or only the cells born this generation. The world evolves once per sweep or every N steps, under Life-like rules (Life, HighLife, Seeds, Day and Night, Maze, Move or custom B/S text), from a random fill, a classic pattern or a drawn pattern, and reseeds itself when it dies or repeats. The layer shows the grid live; a click toggles a cell. No allocation in the processing path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
halp::value_list holds up to N numbers or aggregates of numbers in a fixed-capacity vector, with optional column descriptions and count bounds. halp::note_matrix maps the 12 degrees of an octave to output degrees, or mutes them. halp::channel_mask holds the 16 MIDI channels as toggles. List conversion from ossia keeps the first elements of a fixed-capacity container instead of overflowing it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
…text Dispatcher, Scaler and Filter channels are channel masks. The Filter's channel, note and CC maps are rows of input and output ranges, up to four, the first match applying. Transform's byte ranges are integer range spinboxes, Life's note map a list of notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
…ontrol, Sync Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Quarter frames received before a full frame could complete later and overwrite the new position, and clocks counted before it were added on top. A full frame resets the quarter-frame assembly and the clock interpolation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
…ixes - Layouts: tabs and grids for Filter, Transform, MPE, Delay, Humanizer, CC snapshot, Value shaper, Life, Random sequencer, Progression player. Midi life's grid fills its view at every size. - Proportions are 0-1 knobs (Delay feedback and dry/wet, Life gate, swing and density, gates, Value shaper gains 0-4 and 0-2). - halp_flag(process_exec) on every object whose stop() releases notes: without it the host never calls stop(). - Transform: a produced note ends when What or Into changes. Dispatcher and MPE: note-offs passed through do not free a busy channel. Sustain: Chord mode ends the chord held at pedal down. Delay: expression echoes cannot crowd note echoes out of the queue (EventQueue reports drops, make_room). Value shaper: seed 0 keeps running across a stop. Life: no burst when switching sync off, swing on the grid's odd steps, no drift in free mode. Scaler: MTS sysex fits libremidi's inline storage on 32-bit targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
- MidiFilter: note-offs of notes rejected only for their kind go to the main output when note-offs are selected. - MidiCommon: truncated channel messages classify as other; NoteTracker can end a note at a given time, used by the dispatcher when it steals a voice. - Life, RandomSequencer, ProgressionPlayer: seeks come from transport() and from a rewind of the quarters; free steps follow the transport. - MidiSyncInput: clock out follows Song Position through divide and shift, tempo from the clocks' timestamps, quarter-frame time as of the last piece. - MidiMpe: RPN and NRPN sequences on member channels configure the input (RPN 0 sets the bend range) instead of reaching the single output channel. - MidiValueShaper: "any controller" leaves bank select, RPN/NRPN and channel mode messages alone. - MidiScaler: reads note bytes in place and reserves its frequency output. - from_value: vecN into a fixed-capacity list keeps the elements that fit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
A node runs once per token, and a buffer holds several when a process loops inside it or stops inside it. The outlet was cleared on each run, so the MIDI sent before a loop point, or in the last partial tick before a stop, was lost. Batching per buffer is now the default; the outlets and the batch reserve local_midi_tick_batch_reserve packets, or the buffer size, and local_midi_tick_batch false keeps the old behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
The sounding note's channel is kept with its pitch: a new note ends the previous one on its own channel, a release ends it only when its channel matches, all notes off clears it, and stopping the process ends it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Each MIDI input reassembles the SysEx7 packets it receives into one MIDI 1 message, across the runs of a buffer and across buffers, without allocating; UMP inputs get the packets unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
halp_meta(alignment, halp::alignments::left / center / right) on a container aligns its children; on a grid each column is as wide as its widest item, so their edges line up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
The free-running seed was reloaded unchanged on every reset, so each play repeated the same take. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
A grid value is {unit, value}: free seconds (0 is none), a note value
(a fraction of a whole note, 4 quarters), bars of the current signature,
or a multiple of the object's own period. The ossia binding reads and
writes it as vec2f, the time chooser's encoding, so a time chooser turned
into a grid chooser keeps its saved values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
…s, no CC snapshot - Midi filter: Types is a grid of toggles; Notes and CCs are rows of ranges. - Midi transform and Midi value shaper output the value of each message they produce. - Midi Glide: Priority, On release, Transition (retrigger or legato) and Latch, with a Hold button. - Midi sustain: Pedal is a momentary button. - Midi CC snapshot is removed: cues do this. - halp::toggle_grid: a grid of named toggles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
…gain The tuning goes with the RPN selecting tuning program 0 on all 16 channels, which receivers such as FluidSynth need to apply it; Send tuning and playback start send it again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MIDI objects and binding work used by ossia score's MIDI effects branch.
New objects (examples/Advanced/MidiScaler): Midi transform, CC snapshot, value shaper, dispatcher, MPE, sustain, delay, random sequencer (Turing / Marbles), progression player (194 progressions, Roman numeral parser), Game of Life sequencer.
Extended: Midi filter (Types, channel mask, note / CC ranges, value range, remap tabs, Rejected outlet, note-offs follow their note-on), Midi scaler (MIDI output: per-note pitch bend or MTS sysex), Humanize (chance per chord / period, Euclid gate, random CC), MIDI Sync In (full-frame MTC, rates 29.97 / 30 fixed, tempo from timestamps, Clock out with divide / shift / delay), MidiGlide (note-offs on their note's channel, ends on stop). All MIDI objects get process_exec and release their notes on stop.
halp: value_list, note_matrix, channel_mask controls; drag_payload; timeline_clip (files placed together in one box).
ossia binding:
Depends on celtera/libremidi#268 (sysex reassembly helpers). Tested through score: 105 targeted test programs pass, including test_nodes_ossia_midi.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Qr9WkcUjEGCueg7eG1KxSr