A VST3 plugin for drawing music as free pitch curves. You draw with a pencil on a time × pitch canvas, and pitch is continuous: a stroke can glide, wobble or sit between semitones. Each stroke plays as one note, strokes can overlap, and the plugin drives an external synthesizer over MIDI using MPE or pitch bend.
- Pencil, Line, Select and Eraser tools; Shift snaps the pencil to semitones; the eraser cuts strokes apart.
- Drawing aids: pencil smoothing against hand jitter, a Line tool for clean held notes and glides, a Smooth command, and a pitch magnet that pulls toward the notes of a chosen key and scale while keeping glides smooth.
- Zoom and scroll in time and pitch.
- Vibrato per stroke (depth, rate in Hz, fade-in), added on top of the drawing so it can be changed or removed later.
- Copy, cut, paste, duplicate via the system clipboard, so strokes also paste between PitchPaint instances.
- Dynamics per stroke: draw loudness over time in the Dynamics lane, or use Swell / Fade / Hairpin / Accent; sent as MPE pressure or a CC of your choice.
- Velocity per stroke: set it for new strokes, or select strokes and change theirs; line thickness shows it.
- Snap to a time grid (1 bar … 1/32, triplets): note starts, lengths, moves and eraser cuts land on the grid, while pitch stays free.
- Any number of overlapping strokes: up to 15 sounding at once in MPE mode.
- Select, box-select, move (Shift: by whole semitones), delete; undo/redo.
- Loops of 1–16 bars following the host's transport; everything is saved with the project.
- Two MIDI output modes: MPE (one channel per stroke) and Mono pitch-bend (one stroke at a time, any synth).
- Built-in Preview sound that plays exactly what is drawn, as a reference for checking your synth.
Requires CMake ≥ 3.25 and a C++20 compiler (tested: Visual Studio 2022 Build Tools, MSVC 14.44, Windows 11). JUCE 9.0.3 is fetched automatically at configure time.
cmake -S . -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config ReleaseOn Windows, build from a short folder path (for example C:\dev\PitchPaint): MSBuild fails when the build
folder is nested very deeply (paths over ~260 characters inside JUCE's helper build).
The plugin ends up in build/PitchPaint_artefacts/Release/VST3/PitchPaint.vst3. Copy it to
C:\Program Files\Common Files\VST3, or add that folder to your host's VST3 search paths.
- Put PitchPaint on a track as an instrument, and your synth after it on the same track (or route PitchPaint's MIDI output to the synth's track). In REAPER: PitchPaint in FX slot 1, synth in slot 2.
- Set up the synth for the output mode (see below). The yellow line at the bottom of PitchPaint's window reminds you what's needed.
- Draw, press play in the host. PitchPaint loops its pattern while the host transport runs.
| Action | How |
|---|---|
| Draw | Pencil (P): drag. Shift: snap to semitones. Drawing back over a stroke redraws that part. |
| Draw straight | Line (L): click to place points; straight segments between them (flat = steady note, slanted = glide). Double-click, Enter or right-click finishes; Esc cancels. Points follow the time grid. |
| Steady the pencil | Smoothing (default 40%): filters hand jitter in pitch while you draw; timing is never smoothed. Big, deliberate moves still follow quickly. |
| Smooth existing strokes | Select them, then Smooth (M). Press again for more; start and end pitches stay. |
| Key, scale, magnet | Key + Scale (chromatic, major, minor, modes, pentatonics, blues, whole tone) and Magnet (0–100%). The magnet pulls pitches toward the scale's notes as they play, so held notes settle in tune and glides stay continuous. It doesn't change what you drew, so you can adjust it any time. Shift snaps to scale notes; out-of-scale rows are shaded. |
| Snap timing | Snap box (default 1/16; G turns it on/off). Stroke starts and ends land on grid lines, so note lengths are whole grid steps; with snapping on, a click places a one-step note. Hold Alt to ignore the grid for one action. |
| Erase | Eraser (E), or right-drag with any tool: cuts out the part of a stroke it passes over (a cut in the middle leaves two strokes). Hold Shift to delete whole strokes. |
| Select / move | Select (S): click, Shift-click, or drag a box. Drag a selected stroke to move; Shift moves by whole semitones. |
| Delete selection | Delete or Backspace |
| Copy / cut / paste / duplicate | Ctrl+C / Ctrl+X / Ctrl+V / Ctrl+D, or the Edit menu. Paste goes to the mouse position (on the grid) when the mouse is over the canvas, otherwise to the original time; duplicate goes right after the selection. Pasted strokes are selected, ready to move. |
| Dynamics | Dynamics lane under the canvas (toggle with the Dynamics button): select strokes, then drag in the lane to draw their loudness over time, or click Swell, Fade, Hairpin, Accent or Clear. Send chooses the MIDI control: Pressure (MPE standard), CC11 Expression, CC1 Mod wheel, CC74 Brightness or CC7 Volume. The first time you add dynamics, Send switches on by itself. |
| Vibrato | Vibrato...: depth (cents), rate (Hz) and fade-in (beats) for the selected strokes, or for new strokes when nothing is selected. Applied after the pitch magnet, so the magnet never flattens it. |
| Undo / redo | Ctrl+Z / Ctrl+Y (or Ctrl+Shift+Z), or the toolbar buttons |
| Select all / deselect | Ctrl+A / Esc |
| Velocity | Velocity field: with nothing selected it sets the velocity of new strokes; with strokes selected (label turns to Selected velocity) it changes theirs. Thicker line = higher velocity; selected strokes show their value. |
| Move selection by keyboard | Arrow keys: ±1 semitone / ±1 grid step (¼ beat with snapping off); with Alt: ±10 cents / ±1/32 beat |
| Scroll pitch / time | Mouse wheel / Shift+wheel (or sideways scroll) |
| Zoom time / pitch | Ctrl+wheel / Alt+wheel |
The horizontal lines sit exactly on semitones (labels: C4 = MIDI 60 = middle C = 261.63 Hz).
Every control has a tooltip, and the bottom bar always shows what your synth needs and confirms edits ("Deleted 2 strokes. Ctrl+Z to undo."). The toolbar is fully usable from the keyboard (Tab, Space/Enter), controls have screen-reader names, and colours meet WCAG AA contrast; see docs/design-system.md.
REAPER and keyboard shortcuts: REAPER may keep keys for itself. Enable Send all keyboard input to plugin in the FX window's menu, or use the toolbar buttons.
A drawn pitch is sent as a note number (the semitone nearest the stroke's start) plus a continuous pitch bend. The synth only follows the drawing if it applies the bend with the same range as PitchPaint's Bend range setting. A mismatched range is the most common reason for "the sound doesn't match the drawing": the note starts right but moves too little or too much.
- MPE lower zone: each sounding stroke gets its own member channel (2–16), so every stroke bends independently.
- On start and whenever settings change, PitchPaint sends the MPE Configuration Message and the bend range (RPN 0, default ±48 semitones) on every member channel.
- The synth must be in MPE mode with the same member bend range. Many synths need MPE switched on by hand.
- Surge XT: Menu → MPE → Enable MPE (its MPE bend range defaults to 48). Surge ignores the MPE Configuration Message, so this must be done in Surge. Verified.
- New notes take the channel that has been free longest, so release tails are rarely bent by a new note. If more than 15 strokes sound at once, the oldest note is cut.
- Everything on MIDI channel 1, one stroke at a time: the most recently started stroke wins, and an earlier one that is still running takes over again when it ends.
- Works with synths without MPE. Set the synth's pitch-bend range to PitchPaint's Bend range.
- If a stroke moves further than the bend range from its note, the note is retriggered on a new note number. That makes an audible re-attack, so use a large range (12–48) if your synth allows it.
- Pitch bend is 14-bit: one step is about 0.6 cents at ±48 and about 0.02 cents at ±2.
- Note starts and ends are sample-accurate. Bends are updated every 64 samples (≈1.3 ms at 48 kHz), only when the value changes.
VST3 has no raw MIDI output. Notes leave PitchPaint as VST3 note events, and pitch bend and controllers as
LegacyMIDICCOutEvent. The host must pass a plugin's MIDI output on to another plugin.
| Host | Status |
|---|---|
| REAPER 7.81 (Windows) | Verified. MIDI flows to the next FX on the track. Automated checks: MIDI as received by the next plugin matches the drawing within 0.03 semitones in MPE and mono modes; drawings survive save/reload. |
| Bitwig Studio, Cubase / Nuendo | Not tested. These hosts can route VST3 MIDI output, so they are expected to work. Reports welcome. |
| FL Studio, Studio One | Not tested; plugin MIDI output routing differs per host. |
| Ableton Live | Live does not route MIDI output from plugins. Only the built-in Preview sound works. |
| Logic Pro | Does not load VST3 (AU only). |
| Synth | Status |
|---|---|
| Surge XT, dynamics | Measured: Surge XT's default patch ignores pressure, CC11, CC1, CC74 and CC7, so dynamics are silent until the patch routes one of them (for example pressure or expression to volume or the filter). Check Surge's manual for routing modulation sources; the Preview sound plays pressure, CC11 and CC7 as volume, so you can hear the intended shape. |
| Surge XT | Verified in MPE mode with MPE enabled in Surge: follows the drawing within a few hundredths of a semitone. With MPE off it applies ±2 and doesn't follow; use Mono mode and set ranges to match. |
| Dexed | Not verified: it receives the MIDI, but its default patch couldn't be pitch-measured reliably. Should work in Mono mode with the bend ranges matched. |
| Other synths | MPE synths: enable MPE, match the bend range. Non-MPE synths: Mono mode, match the bend range. Synths in omni mode apply every channel's bends to every note, which breaks MPE mode. |
Other limitations:
- PitchPaint plays only while the host transport runs, and loops its own pattern (1–16 bars of 4 beats). A seek or host loop jump releases all notes; they restart from the new position.
- No MIDI file import or export yet.
GitHub Actions builds the plugin on Windows and runs CoreTests and EditorTests on every push; the built
PitchPaint.vst3 is attached to each run as an artifact.
ctest --test-dir build -C ReleaseCoreTests: the JUCE-free core (pattern model, MIDI renderer, preview synth measured in Hz, undo, save format).EditorTests: the real editor and processor driven offscreen with synthetic mouse and key events.
End-to-end in REAPER (uses an isolated REAPER config and a dummy audio device; your REAPER settings are untouched):
powershell -ExecutionPolicy Bypass -File Tests/reaper/run.ps1pluginval (passes at strictness 10; GUI tests skipped because they open windows):
pluginval --strictness-level 10 --skip-gui-tests --validate-in-process --validate build/PitchPaint_artefacts/Release/VST3/PitchPaint.vst3AGPL-3.0-or-later (see LICENSE). Built with JUCE, used under the AGPLv3. VST is a registered trademark of Steinberg Media Technologies GmbH.
