Skip to content

feat: add MIDI harmonizer for chord-aware melody processing#4313

Draft
ogabrielluiz wants to merge 8 commits intoSynthstromAudible:mainfrom
ogabrielluiz:feature/midi-harmonizer
Draft

feat: add MIDI harmonizer for chord-aware melody processing#4313
ogabrielluiz wants to merge 8 commits intoSynthstromAudible:mainfrom
ogabrielluiz:feature/midi-harmonizer

Conversation

@ogabrielluiz
Copy link
Copy Markdown
Contributor

Adds a MIDI harmonizer that snaps melody notes to chord tones in real-time,
enabling chord-aware harmonization across MIDI/CV clips. A dedicated chord
channel feeds chord data while melody channels are harmonized according to
configurable rules — all per-clip with XML persistence.

Key Features

  • 5 snap modes: Nearest, Round Down, Round Up, Root, Root + 5th
  • 4 target tightness levels: Chord Tones (strict), Scale, Extensions, Loose
  • Diatonic intervals: add a harmonized voice at a scale-aware interval (3rd, 5th, 6th, octave — above or below)
  • Voice leading: smoother melodic motion by balancing input proximity with previous output
  • Per-note probability: 0–100% chance each note gets harmonized
  • Chord latch: hold the last chord even after keys are released
  • Retrigger: re-harmonize held notes when the chord changes
  • Transpose: shift harmonized output by ±24 semitones
  • Interval velocity offset: adjust velocity of the interval voice independently
  • Per-clip settings: each MIDI/CV clip stores its own harmonizer configuration

Usage

  1. Enable MIDI Harmonizer in Settings > Community Features
  2. Open a MIDI or CV clip
  3. In the clip's sound editor, find the Harmonizer submenu
  4. Set the Chord Channel to the MIDI channel carrying chord data
  5. Configure snap mode, tightness, and other parameters to taste
  6. Play melody notes — they snap to the active chord in real-time

Architecture

The harmonizer engine (midi_harmonizer.cpp/h) is a standalone library with
no firmware dependencies beyond standard C++, making it independently testable.

  • HarmonizerState: manages chord tracking (16 channels), per-note output
    mappings, and voice leading state
  • HarmonizerSettings (harmonizer_settings.h): lightweight POD config
    stored per-clip with XML serialization
  • Integration: MidiInstrument intercepts note-on/off and routes through
    the harmonizer; InstrumentClip handles persistence; PlaybackHandler
    resets state on stop

Functional Areas Affected

Area Files Change
Harmonizer engine io/midi/midi_harmonizer.cpp/h, harmonizer_settings.h New
Menu UI gui/menu_item/harmonizer/*.h (10 files) New
Sound editor gui/ui/sound_editor.cpp/h, menus.cpp/h Modified
Localization l10n/english.json, seven_segment.json, strings.h, generated files Modified
MIDI processing model/instrument/midi_instrument.cpp Modified
Clip persistence model/clip/instrument_clip.cpp/h Modified
Playback playback/playback_handler.cpp Modified
Runtime settings model/settings/runtime_feature_settings.cpp/h Modified

Testing

  • 86 unit tests (254 checks) covering all snap modes, tightness levels, voice
    leading, diatonic intervals, multi-clip isolation, chord channel switching,
    mapping lifecycle, and MIDI boundary conditions
  • Hardware testing needed for both OLED and 7-segment displays

Test Plan

  • Enable feature in Community Features and verify menu appears in MIDI/CV clips
  • Set chord channel, play chords on that channel, verify melody snapping
  • Test all 5 snap modes produce expected output
  • Test all 4 tightness levels (Chord Tones, Scale, Extensions, Loose)
  • Verify diatonic intervals (3rd, 5th, 6th, octave above/below)
  • Test voice leading produces smoother motion
  • Test probability at 0%, 50%, 100%
  • Test chord latch holds chord after release
  • Test retrigger re-harmonizes on chord change
  • Verify settings persist across save/load
  • Test on both OLED and 7-segment hardware
  • Verify feature disabled by default, no impact when off

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 14, 2026

Test Results

195 tests  +85   195 ✅ +85   1s ⏱️ ±0s
 26 suites +10     0 💤 ± 0 
 26 files   +10     0 ❌ ± 0 

Results for commit a73bef3. ± Comparison against base commit df93389.

♻️ This comment has been updated with latest results.

@seangoodvibes
Copy link
Copy Markdown
Collaborator

Hey just fyi this PR will need to be rebased to the main branch as the community branch is now obsolete.

Thanks!

ogabrielluiz and others added 8 commits March 5, 2026 10:26
Add standalone harmonizer library that maps incoming MIDI melody notes
to chord tones using configurable snap modes (Nearest, RoundDown,
RoundUp, Root, Root5th), target tightness levels (ChordTones, Scale,
Extensions, Loose), optional voice leading, diatonic intervals, and
per-note probability. The engine is self-contained with no firmware
dependencies beyond standard C++.
Wire the harmonizer engine into the Deluge firmware:
- MidiInstrument processes melody notes through the harmonizer,
  applying chord-based harmonization with configurable probability,
  interval offsets, retrigger, and latch behavior
- InstrumentClip stores per-clip HarmonizerSettings with XML
  serialization (read/write) and transpose clamping to [-24, 24]
- PlaybackHandler resets harmonizer state on playback stop
- Register MidiHarmonizer as a runtime feature setting
Add 10 menu item classes for the harmonizer submenu accessible from
MIDI/CV clips: mode, tightness, interval, transpose, chord channel,
probability, voice leading, retrigger, interval velocity, and latch.
Each item includes null-guard safety checks and runtime feature gating.

Register all harmonizer menu items in the sound editor, add l10n
strings for both OLED and 7-segment displays, and wire up the
submenu navigation.
Comprehensive CppUTest suite covering:
- ChordState and ChannelState data structures
- All 5 snap modes (Nearest, RoundDown, RoundUp, Root, Root5th)
- All 4 target tightness levels (ChordTones, Scale, Extensions, Loose)
- Voice leading behavior and diatonic interval computation
- Multi-clip isolation, chord channel changes, mapping lifecycle
- Boundary conditions (MIDI notes 0/127, empty chords, overflow)

86 tests, 254 checks, all passing.
Add comprehensive documentation for the MIDI harmonizer feature
covering setup, snap modes, target tightness, voice leading,
diatonic intervals, chord channel configuration, and all
harmonizer parameters.
The ARM cross-compiler deduces conflicting types (long vs int) for
std::clamp when mixing int32_t with integer literals. Use explicit
std::clamp<int32_t> to resolve the ambiguity.
The generated g_english.cpp and g_seven_segment.cpp had 14 harmonizer
strings that were missing from the source JSON files, causing the CI
build to fail when the generator reproduced shorter files. Add the
missing entries to both english.json and seven_segment.json and
regenerate the cpp files.
@ogabrielluiz ogabrielluiz force-pushed the feature/midi-harmonizer branch from 08c33f4 to 97f3b45 Compare March 5, 2026 13:31
@ogabrielluiz ogabrielluiz changed the base branch from community to main March 7, 2026 23:00
@ogabrielluiz ogabrielluiz force-pushed the feature/midi-harmonizer branch from 97f3b45 to a73bef3 Compare March 10, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants