Skip to content

Support independent VST instruments for melody and chords - #27

Merged
denmase merged 1 commit into
mainfrom
claude/noteeditor-translate-english-fworkt
Sep 18, 2026
Merged

denmase merged 1 commit into
mainfrom
claude/noteeditor-translate-english-fworkt

Conversation

@denmase

@denmase denmase commented Sep 18, 2026

Copy link
Copy Markdown
Owner

Summary

The bundled SoundFont engine (BassMidiSynthesizer) is multi-timbral: it already plays melody
and chords with independently selectable GM instruments, since one BASSMIDI stream can host all
16 MIDI channels with different programs at once. The VST engine (BassVstSynthesizer) never had
this — it hosted a single BASSVST plugin instance shared by both the melody and chord MIDI
channels, so a VST instrument choice applied to the whole score instead of per-part, unlike the
InstrumentDialog (SoundFont) experience. This PR brings the VST path to parity.

What changed

  • AudioEngineDialog: now exposes independent Melody/Chords VST plugin path fields (mirroring
    InstrumentDialog's existing two-instrument pattern), instead of one shared path. Leaving
    Chords blank reuses the melody plugin for both parts.
  • BassVstSynthesizer: takes (melodyPluginPath, chordPluginPath). Loads two independent
    BASSVST plugin handles and routes NoteOn/NoteOff/ProgramChange by MIDI channel
    (ScoreMidiSchedule.ChordChannel → chord handle, everything else → melody handle — confirmed via
    grep that these are the only two channels used anywhere in the codebase). When the chord path is
    blank or identical to the melody path, it falls back to sharing a single handle (matching the
    previous single-plugin behavior exactly, and avoiding a redundant load / double-free on
    dispose).
  • AppTheme: VstPluginPath setting replaced with VstMelodyPluginPath +
    VstChordPluginPath. Existing settings migrate automatically on load (old VstPluginPath value
    becomes the new melody path if the new field isn't present yet); nothing is silently lost on
    upgrade.
  • AppBootstrapper / MainForm: updated to construct/persist both paths through the
    dialog and into BassVstSynthesizer's new constructor.
  • README.md: updated the VST feature bullet to describe independent melody/chord plugins.

Test plan

This sandbox has no .NET SDK WindowsDesktop workload, real Windows, or audio hardware, so BASSVST
itself can't be exercised directly here. Verified via the same pipeline used for every change this
session:

  • dotnet build against the real project/package graph succeeds.
  • dotnet format --verify-no-changes passes.
  • Compiled and ran the full existing regression suite (23 tests across Harness.cs and
    MainFormHarness.cs, covering MainForm, DI wiring, and non-UI services) under Mono + Xvfb
    against the built assembly — all pass unmodified, confirming no regressions to tab/session/
    playback-coordinator behavior from the earlier phases.
  • Manually reasoned through the shared-handle fallback and dispose paths to confirm no
    double-free when melody and chord paths are the same (the common case, and the exact behavior
    every existing user's settings will have after migration).

Not verified in this sandbox (same caveat as all prior BASS-touching work this session): actually
loading two distinct real VST2 plugin DLLs and hearing independent melody/chord instrument audio
requires a real Windows machine.


🤖 Generated with Claude Code

https://claude.ai/code/session_01Pguj4XSScE141p1ScWoqEr


Generated by Claude Code

The VST2 playback engine sent both melody and chord notes to one loaded
plugin instance, unlike the bundled SoundFont path which already lets
melody and chords use independent General MIDI instruments (they're
multi-timbral across MIDI channels; most VST2 instrument plugins are
not, so a single shared plugin couldn't actually give melody and chords
distinct sounds).

- AudioEngineDialog now has separate Melody/Chords VST plugin path
  fields; leaving Chords blank reuses the melody plugin for both parts
  (preserves today's single-plugin behavior as the default).
- BassVstSynthesizer optionally loads a second BASSVST channel for the
  chord plugin, routing NoteOn/NoteOff/ProgramChange by
  ScoreMidiSchedule.MelodyChannel/ChordChannel; falls back to one shared
  handle when the chord path is empty or the same file, so the common
  case doesn't pay for a second plugin instance.
- AppTheme persists VstMelodyPluginPath/VstChordPluginPath instead of
  one VstPluginPath, migrating an existing settings.json's old field to
  VstMelodyPluginPath on load so a previously configured plugin isn't
  silently dropped.

Verified by compiling the real, unmodified source via `dotnet build`
against the true NuGet/WinForms graph, then executing that same built
assembly's IL under Mono/Xvfb for the full existing multi-tab/playback
test suite -- no regressions. The BASSVST native audio path itself isn't
exercised (no VST plugin files or audio hardware in this sandbox); please
confirm on a real Windows machine with an actual VST2 instrument plugin.
@denmase
denmase merged commit 755444a into main Sep 18, 2026
1 check passed
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