Generated Docs

Major MIDI

This site is generated from README.md.

Edit the README, then rerun python3 docs/generate_docs.py.

Single Source

  • README is the source of truth
  • docs/index.html is generated
  • docs/styles.css controls presentation

Overview

Major MIDI is a Daisy Patch SM firmware for playing Standard MIDI Files from SD card through a SoundFont 2 synth engine, with a compact front-panel UI, live MIDI input, loop playback, internal or external sync, and assignable CV/gate I/O.

This README is written as an operator guide first and a developer guide second, so it can be reused as source material for a website or user manual.

What Major MIDI Does

Major MIDI turns a Daisy Patch SM-based module into a multitimbral MIDI file player and live MIDI SoundFont instrument.

At a high level it gives you:

CapabilityNotes
.mid playback from SD cardStandard MIDI Files loaded from 0:/midi
.sf2 SoundFont loadingSoundFonts loaded from 0:/soundfonts
16-channel mixerPer-channel mix control from the front panel
Per-channel program selectionFile-driven or manually overridden
Loop playback with saved metadataSong settings can be written into MIDI metadata
Live UART MIDI inputHardware MIDI input on the Patch SM build
Live external USB MIDI inputAdditional live performance input path
Internal tempo or external syncSwitchable transport clock source
Assignable CV/gate I/OCV inputs plus configurable gate/CV outputs

The system is designed around a small OLED and a minimal set of controls, so the workflow is fast once the front-panel logic is learned.

Quick Start

If you just want to get sound quickly:

  1. Put at least one .mid file into 0:/midi.
  2. Put at least one .sf2 file into 0:/soundfonts.
  3. Insert the SD card and boot the module.
  4. Open the main menu with a long encoder press.
  5. Load a MIDI file.
  6. Load an SF2.
  7. Return to the performance screen.
  8. Press Play.

If sync is set to external and no valid external clock is present, playback will not run. In that case either:

OptionAction
Use internal syncSet the sync source switch to internal
Keep external syncProvide MIDI clock or gate clock

SD Card Layout

Major MIDI expects these folders:

0:/midi
0:/soundfonts

Example:

0:/midi/825.mid
0:/midi/beat_loop.mid
0:/soundfonts/microgm.sf2
0:/soundfonts/another.sf2

The browser ignores hidden AppleDouble files such as:

Ignored file examples
._581.mid
._microgm.sf2

Boot Behavior

On boot:

StepBehavior
DisplayOLED shows a splash screen
Media scanSD card is scanned
ContentThe first available MIDI and SF2 can be loaded
Config restoreSaved CV/gate config is read from 0:/major_midi_cv_gate.bin if present

The firmware does not background-save CV/gate changes while running, because SD writes during active streaming caused freezes. The safe save path is Save All.

Front Panel Overview

Major MIDI uses:

ControlRole
B1..B4Channel / bank buttons
K1..K4Parameter knobs for the visible channels
PlayTransport control
Encoder with push switchNavigation, editing, and shift actions
OLED displayMain feedback display

These controls do different things depending on the current mode:

ModePurpose
performancePlayback and quick mixing
muteFast per-channel muting
loop editQuick loop boundary editing
main menuTop-level configuration pages
submenu pageParameter editing inside a selected page

Performance Screen

The performance screen is the default operating view.

Top line example:

STP 120 M001 B1 V

This means:

FieldMeaning
STP or PLYStop or play state
120Current BPM
M001Current measure
B1Visible bank
VCurrent quick page
LineShows
Second lineCurrently loaded MIDI file
Third lineCurrently loaded SF2

The bottom of the screen shows the visible 4 channels as columns.

Muted channels are shown with a leading *, for example:

*01

Zero values are shown as:

-

Performance Workflow

Banks

The 16 MIDI channels are grouped in banks of 4:

BankChannels
B11-4
B25-8
B39-12
B413-16

Press a bank button once to select that bank.

Quick Pages

The performance screen has 5 quick pages:

PageFunction
VVolume
PPan
RReverb send
CChorus send
GProgram

There are two ways to move between pages:

MethodAction
Encoder + B1Cycle quick page
Repeated quick bank pressAdvance the current page for that bank

Example:

  • press B1 once: bank 1, page stays as-is
  • press B1 again quickly: page advances
  • press B1 again quickly: page advances again

So if you are on bank 1:

  • B1 once may show B1 V
  • B1 twice more quickly may land on B1 R

If you then press B2 once:

  • bank changes to 2
  • page stays R

If you then press B3 quickly three times:

  • bank changes to 3
  • page cycles three times
  • for example R -> C -> G -> V

Knobs

K1..K4 always control the 4 visible channels for the active quick page.

Examples:

PageK1..K4 control
VVolume for the visible channels
PPan
RReverb send
CChorus send
GProgram override

The knob system uses pickup/hysteresis behavior, so a knob usually needs to cross the current stored value before it starts changing that parameter. This prevents abrupt jumps after bank/page changes.

Play Button

Play toggles:

ButtonResult
PlayToggle play / stop

If external sync is selected and there is no valid external clock, the transport will not free-run.

Encoder Turn

In performance mode, turning the encoder changes BPM.

BPM range:

ParameterRange
BPM20 to 300

If a song BPM override or CV BPM control is active, that affects the effective playback tempo.

Shift Actions

The encoder press acts as shift in performance mode.

Available combos:

ComboAction
Encoder + B1Cycle quick page
Encoder + B2Enter or exit mute mode
Encoder + B3Mute all channels or unmute all channels
Encoder + B4Enter loop edit
Encoder + PlayEnters menu behavior in the current UI model via long-press / menu system

Mute All Behavior

Mute All is not a separate global mute engine anymore.

Instead, it directly toggles the muted flag on all 16 channels:

StateResult
Not all channels mutedMute them all
All channels already mutedUnmute them all

This means mute state remains per-channel and visible everywhere.

Mute Mode

Mute mode is meant for fast channel muting without touching the mix values.

While in mute mode:

ControlAction
B1..B4Toggle mute on the visible 4 channels
Encoder + B1..B4Switch to another bank while staying in mute mode
Encoder pressReturn to performance

Important:

BehaviorResult
MutingDoes not overwrite volume
UnmutingReturns to the previous channel volume value

Loop Edit

Loop edit is a quick front-panel way to set loop boundaries.

In loop edit:

ControlAction
K1Loop start
K2Loop end
Encoder pressExit loop edit

Under the hood, Major MIDI keeps more precise loop metadata than the coarse performance controls expose. Loop playback was reworked to avoid seam restarts and instead pre-schedule the next loop cycle, which is why loop timing is now much more stable than earlier versions.

Load MIDI

This page shows the available .mid files.

Behavior:

StepBehavior
SelectionChoose a file with the encoder
ConfirmPress encoder to load it
LoadingA loading screen appears
SuccessReturns to performance mode
FailureStays in the menu and shows an error message

Load SF2

This page works the same way as Load MIDI, but for SoundFonts.

OutcomeResult
SuccessThe SF2 is loaded and the UI returns to performance mode
FailureThe UI stays in the menu and an error overlay is shown

FX Settings

This page adjusts the global FX parameters used by the synth engine.

Parameters:

Parameter
Rev Time
Rev LPF
Rev HPF
Ch Depth
Ch Speed

These are global synth settings, not per-channel send levels.

Per-channel reverb and chorus amounts are on:

Location
Performance quick pages
SF2 settings channel page

Song Settings

This page stores song-level metadata in the current MIDI file.

Parameters:

ParameterPurpose
BPM OvrPer-song BPM override
LoopLoop enabled or disabled
Loop StLoop start measure
Loop EndLoop end measure / derived loop length
Save To MIDIWrite the current song metadata into the MIDI file

This data is stored in the custom MMID meta-event block.

What Song Settings Persist

Current song save data includes:

Saved song data
BPM override
Loop enabled
Loop start
Loop beat/sub data
Loop length
Other Major MIDI song settings already supported by the metadata block

SF2 Settings

This page is the deeper per-channel and synth configuration page.

Parameters:

Parameter
Voices
Channel
Mute
Volume
Pan
RevSend
ChoSend
Program
Trans

Voices

Voices sets the maximum synth voice count.

Range:

ParameterRange
Voices4 to 32

This directly affects CPU load. If you hear crunching at high polyphony:

RecommendationWhy
Lower the voice countReduces CPU load
Be extra conservative with dense MIDI files and heavy FXThose combinations increase load fastest

Channel

Selects which MIDI channel the rest of the SF2 page edits.

Program

Program behavior is important:

ModeBehavior
Program FileFollow the MIDI file's program changes
Program 000..127Force a program override on that channel

Program overrides are also available from the live performance G page.

Transpose

Global transpose for melodic channels.

Drum channel handling remains separate so channel 10 behaves as expected for kits.

CV/Gate

Phase 1 CV/gate routing is implemented.

The page dynamically hides irrelevant options. For example:

FieldWhen it appears
ChannelOnly when the selected mode needs a channel
CCOnly when the selected mode needs a CC number
Sync resolutionOnly for sync gate output

CV Inputs

Available inputs:

Input
CV In 1
CV In 2

Modes:

ModeFunction
OffDisabled
MasterVolMap CV to overall output gain
BPMMap CV to tempo
Ch CCSend a continuous CC to the selected channel

CV Input: MasterVol

Maps CV to overall output gain.

CV Input: BPM

Maps CV to tempo.

Current range:

ParameterRange
CV BPM control20 to 300 BPM

CV Input: Ch CC

Sends a continuous CC to the selected channel.

Gate Outputs

Available outputs:

Output
Gate Out 1
Gate Out 2

Modes:

ModeFunction
OffDisabled
SyncOutput a transport clock pulse
ResetOutput a short pulse every measure
Ch GateOutput a gate while the selected channel has active notes

Sync

Outputs regular pulses at:

Sync resolution
1/4
1/8
1/16
1/32
1/64

Reset

Outputs a short pulse every measure.

Ch Gate

Outputs a gate while the selected channel has active notes.

CV Outputs

Available outputs:

Output
CV Out 1
CV Out 2

Modes:

ModeFunction
OffDisabled
PitchMonophonic pitch CV for the selected channel
CCOutput the current CC value for the selected channel and CC number

Pitch

Pitch mode is monophonic per selected channel.

Priority options:

Pitch Priority
Highest
Lowest

CC

Outputs the current CC value for the selected channel and CC number.

Save All

Save All is available from the main menu and includes a confirm/cancel screen.

Saved data:

Saved itemDestination
Current MIDI song settingsCurrent MIDI file
Current CV/gate routing0:/major_midi_cv_gate.bin

Safety behavior:

ConditionResult
Playback stoppedSave can proceed
Playback activeUI shows Stop Playback First

This is currently the safe persistence path for CV/gate settings.

Sync

Major MIDI supports:

Sync ModeSource
InternalInternal BPM engine
ExternalMIDI clock or gate pulse sync

The sync source switch is on MCP23017 GPB1.

Internal Sync

When the switch is up:

Switch StateBehavior
UpThe transport uses the internal BPM engine and encoder BPM changes apply directly

External Sync

When the switch is down:

Switch StateBehavior
DownTransport follows external timing

Current external sync sources:

External SourceInput
MIDI clockMIDI input
Gate pulse syncgate_in_1

Priority:

Priority Order
MIDI clock if locked
Otherwise gate clock if locked

If external sync is selected and no valid external clock is present:

ConditionResult
External sync selected with no valid clockPlayback does not free-run

External Sync Notes

External sync behavior has improved substantially, but this is still one of the more complex areas of the firmware. In particular:

  • clock drop/reacquire behavior is still something to keep testing
  • gate pulse expectations matter
  • MIDI start/continue/stop and gate clock behavior do not map perfectly to every external setup

MIDI Input

Supported live MIDI inputs:

InputMapping
UART MIDIA2/A3
External USB MIDIA8/A9

Live MIDI is serviced on the fixed control/audio path for better timing than simple main-loop polling.

Handled message types:

Message Type
Note on
Note off
Control change
Program change
Pitch bend
All notes off
All sound off
MIDI clock
MIDI start
MIDI continue
MIDI stop

Program Handling

Program handling now works in three layers:

  1. file program changes from the MIDI file
  2. restored file program state on seek / loop start
  3. user program override on top

This matters because Major MIDI can:

Scenario
Start in the middle of a song
Start from a loop point
Wrap loops continuously

The firmware now restores the latest file program state before a seek point, then applies the user override if one exists.

Performance Program Page

The G quick page is the fast live program page.

Behavior:

BehaviorResult
Bank selectionChooses which 4 channels you are editing
KnobsSet the program override for those 4 channels
Bottom row on the G pageShows the current program values

Looping

Looping in Major MIDI is not a naive stop-and-restart seam anymore.

Current loop design:

Loop BehaviorEffect
Loop playback is scheduled aheadAvoids seam restarts
The next loop cycle is appended before the seamKeeps timing tight
Loop timing uses the active BPMTempo remains consistent
Loop starts can begin immediately from the chosen loop pointFaster operator workflow
Stuck notes at the loop boundary are explicitly terminated on wrapPrevents hanging notes

This is why loop playback is now much tighter than earlier revisions.

Performance Tuning Notes

If you are hearing crunching or overload:

ActionWhy
Reduce Voices in SF2 SettingsLowers synth CPU load
Use lighter SF2 filesReduces sample and voice overhead
Reduce extreme polyphony in the MIDI filePrevents dense note bursts from overloading playback

The synth engine also uses:

Built-in mitigation
Output limiting
Some FX load shedding at high voice counts

But max voices is still the main operator control for CPU load.

File Saving and Persistence

What Saves Per MIDI File

Song metadata in the MIDI file can include:

Per-file saved data
BPM override
Loop metadata
Program overrides already supported by the MMID metadata block

What Saves Globally

Currently:

Global saved dataLocation
CV/gate config0:/major_midi_cv_gate.bin

What Does Not Background-Save

CV/gate config is not auto-saved during runtime because SD writes collided with active SF2 and MIDI streaming and caused freezes. Save All is the safe user-facing save flow.

Hardware Mapping

Current firmware assumptions match the tested hardware used during development.

ItemMapping
OLEDI2C1 on B7/B8, address 0x3C
MCP23017I2C1 on B7/B8, address 0x20
Encoder A/BD9/D10
Encoder switchMCP GPB2
Sync source switchMCP GPB1
Bank buttons / playMCP inputs
KnobsCV_1..CV_4
CV inputsCV_5, CV_6
Gate input syncgate_in_1
Gate outputsgate_out_1, gate_out_2
UART MIDIUART_4 on A2/A3
External USB MIDIA8/A9

Build

From the repo root:

make -C DaisyExamples/patch_sm/SF2MidiPlayer

Artifacts:

Artifact
build/SF2MidiPlayer.elf
build/SF2MidiPlayer.hex
build/SF2MidiPlayer.bin

Typical Workflows

Play a Song

  1. Long-press encoder to open the menu.
  2. Load a MIDI file.
  3. Load an SF2.
  4. Return to performance.
  5. Press Play.

Mix the Visible 4 Channels

  1. Choose the bank with B1..B4.
  2. Cycle to the page you want:
  • V
  • P
  • R
  • C
  1. Turn K1..K4.

Change Instruments for a Bank

  1. Go to the desired bank.
  2. Cycle to page G.
  3. Turn K1..K4 to set programs for the 4 visible channels.

Set a Loop and Save It

  1. Enter Loop Edit or Song Settings.
  2. Set the loop start and loop length.
  3. Save the song settings to the MIDI file.

Configure a Clock Output

  1. Open CV/Gate.
  2. Set Gate Out 1 or Gate Out 2 to Sync.
  3. Choose a resolution.

Save Your Setup

  1. Stop playback.
  2. Open main menu.
  3. Choose Save All.
  4. Confirm.

Troubleshooting

The song will not start in external sync mode

Possible causes:

Possible Cause
No MIDI clock is present
No gate clock is present
Sync source switch is in external mode with no valid clock

I changed a program and other channels changed unexpectedly

This was previously caused by a synth-wide reset path tied to voice-count reapplication. The current firmware only reapplies max voices when the value actually changes.

CV/Gate menu changes used to freeze the module

That was caused by background SD writes during active streaming. Runtime autosave was removed and replaced with explicit save flow.

The loop used to hang notes

Loop wrap now flushes still-active notes at the seam before continuing the next cycle.

Playback crunches at high polyphony

Reduce:

Reduce
Voice count
File density
SF2 complexity

External sync is still not perfect for my setup

That area is still under active iteration. Test with a known-good clock source first and verify whether your external gate clock is behaving as expected.

Developer Notes

Major MIDI evolved from a simpler SF2 MIDI player into a more structured UI-driven firmware. The current architecture separates:

Layer
Hardware input
UI event translation
App state
Controller logic
Renderer
Transport / mixer behavior
CV/gate engine

Important implementation notes:

Note
SF2 and MIDI playback both stream from SD
Avoid runtime SD writes during active playback unless explicitly managed
Live MIDI timing was moved off naive main-loop polling
Loop playback now uses continuous ahead-of-time scheduling

License / Project Context

This project expects to live at:

DaisyExamples/patch_sm/SF2MidiPlayer

so it can build against the local libDaisy and DaisySP trees in this repository layout.