13 KiB
TECHNICAL PLAN: DECENT SAMPLER + PIANOBOOK INSTALLATION AND SOUNDFONT MAPPING EXTRACTION FOR AI AGENT
1. Executive Overview
The system needs to fulfill two core requirements:
-
Integrate DecentSampler & Pianobook on Linux Server:
-
Install
DecentSampler.vst3(Linux 64-bit) into the Docker Server environment. -
Structure the Pianobook sample library directories (
.dspreset+.wavfiles). -
Integrate Preset Loading into Python
pedalboardfor offline rendering steps. -
Resolve the AI Agent's "Instrument Information Blindness" regarding SoundFont (
.sf2): -
Current State: The AI Agent and the system only load raw
.sf2files without knowing what instruments are contained within (which Bank, which Program/Patch number, or what the instrument names are). -
Solution:
-
Build a SoundFont Inspection Engine using Python (
sf2utils) to scan.sf2files upon upload/scan and extract the instrument catalog table (Bank, Program/Preset ID, Instrument Name). -
Export a Catalog table (
soundfont_catalog.json) and pass this context to the AI Agent. -
Update the AI Tool Schema so the AI accurately passes
soundfont_id,bank, andprogram(MIDI Program Change) when creating a Track.
2. DecentSampler + Pianobook Installation Plan (Server Backend)
2.1 Installing DecentSampler Linux Native VST3 in Docker
- Download DecentSampler Linux VST3:
- Download the official Linux 64-bit build from the DecentSampler website (
DecentSampler_Linux_x64.tar.gzor.vst3file).
- Server Directory Structure:
/opt/daw_engine/
├── vst3/
│ └── DecentSampler.vst3/ <-- Native Linux VST3 Binary
├── soundfonts/
│ ├── GeneralUser_GS.sf2
│ └── SGM-V2.01.sf2
└── samples/
└── pianobook/ <-- Pianobook Sample Libraries
├── salamander_grand_piano/
│ ├── salamander_piano.dspreset
│ └── samples/ (*.wav)
└── acoustic_guitar/
├── guitar.dspreset
└── samples/ (*.wav)
- Additions to
Dockerfile:
# Install audio system dependencies
RUN apt-get update && apt-get install -y \
libgl1-mesa-glx \
libfreetype6 \
libcurl4 \
&& rm -rf /var/lib/apt/lists/*
# Copy DecentSampler VST3 to Server
COPY ./vst_plugins/DecentSampler.vst3 /opt/daw_engine/vst3/DecentSampler.vst3
2.2 Integrating DecentSampler into the Python Engine (app/core/vst_engine.py)
The pedalboard library supports loading VST3 plugins and preset files for DecentSampler:
import os
from pedalboard import VST3Plugin
class DecentSamplerManager:
def __init__(self, vst_path="/opt/daw_engine/vst3/DecentSampler.vst3"):
self.vst_path = vst_path
def create_decent_sampler_instance(self, dspreset_path: str) -> VST3Plugin:
"""
Instantiates VST3 DecentSampler and loads the Pianobook sample preset (.dspreset) file.
"""
if not os.path.exists(self.vst_path):
raise FileNotFoundError(f"DecentSampler VST3 not found at {self.vst_path}")
plugin = VST3Plugin(self.vst_path)
# Load the Pianobook preset file into DecentSampler VST3
if os.path.exists(dspreset_path):
plugin.load_preset(dspreset_path)
return plugin
3. Designing the SoundFont Inspection Engine (Bank/Program Extraction)
Every SoundFont (.sf2) file is a collection of Presets (or Programs). For the AI Agent to know what sound presets exist inside the .sf2 file, the Backend must scan and parse the .sf2 file.
3.1 Adding Python SoundFont Inspection Libraries
Add to requirements.txt:
sf2utils>=0.9.0
mido>=1.3.0
3.2 Building the SoundFont Metadata Inspection Service (app/core/soundfont_inspector.py)
import os
import json
from sf2utils.sf2parse import Sf2File
class SoundFontInspector:
def __init__(self, sf_dir="/opt/daw_engine/soundfonts"):
self.sf_dir = sf_dir
def inspect_sf2_file(self, filepath: str) -> dict:
"""
Parses an .sf2 file and returns a complete instrument catalog (Bank, Program, Instrument Name).
"""
if not os.path.exists(filepath):
return {}
sf_name = os.path.basename(filepath)
sf_id = os.path.splitext(sf_name)[0].lower()
instruments = []
with open(filepath, 'rb') as f:
sf2 = Sf2File(f)
for preset in sf2.presets:
# Ignore EOP (End of Header) preset
if preset.name.strip() == "EOP" or (preset.bank == 128 and preset.preset == 127):
continue
instruments.append({
"bank": preset.bank, # Bank number (0 = General MIDI Standard, 128 = Percussion/Drums)
"program": preset.preset, # Program/Patch number (0-127)
"name": preset.name.strip(), # Instrument name (e.g. "Stereo Grand", "Violin", "Brass Section")
"is_percussion": (preset.bank == 128)
})
return {
"soundfont_id": sf_id,
"filename": sf_name,
"total_instruments": len(instruments),
"instruments": instruments
}
def generate_full_catalog(self, output_json_path="/opt/daw_engine/soundfont_catalog.json"):
"""
Scans all .sf2 files in the directory and builds a Catalog JSON for the AI Agent.
"""
catalog = {}
for root, dirs, files in os.walk(self.sf_dir):
for file in files:
if file.endswith(('.sf2', '.SF2')):
full_path = os.path.join(root, file)
sf_info = self.inspect_sf2_file(full_path)
catalog[sf_info["soundfont_id"]] = sf_info
with open(output_json_path, 'w', encoding='utf-8') as f:
json.dump(catalog, f, ensure_ascii=False, indent=2)
return catalog
3.3 Catalog File Output Structure (soundfont_catalog.json)
This JSON file serves as an Instrument Dictionary for the AI Agent:
{
"generaluser_gs": {
"soundfont_id": "generaluser_gs",
"filename": "GeneralUser_GS.sf2",
"total_instruments": 128,
"instruments": [
{ "bank": 0, "program": 0, "name": "Stereo Grand Piano", "is_percussion": false },
{ "bank": 0, "program": 19, "name": "Church Organ", "is_percussion": false },
{ "bank": 0, "program": 40, "name": "Violin Ensemble", "is_percussion": false },
{ "bank": 0, "program": 56, "name": "Trumpet", "is_percussion": false },
{ "bank": 128, "program": 0, "name": "Standard Drum Kit", "is_percussion": true }
]
}
}
4. Guiding the AI Agent in Instrument Selection & Accurate Note Loading
When the user types: "Create a Piano track and a Strings section track for 8 bars", the AI Agent needs to know precisely which soundfont, bank, and program numbers to assign to the Tracks.
4.1 Updating the AI Tool Function Spec (tools_spec.json)
Add soundfont_bank and soundfont_program fields to the Tool Schema sent to the AI:
{
"type": "function",
"function": {
"name": "generate_multitrack_midi",
"description": "Generates multi-track MIDI data along with appropriate SoundFont Program configurations.",
"parameters": {
"type": "object",
"properties": {
"tracks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"track_name": { "type": "string" },
"instrument_type": { "type": "string", "enum": ["PIANO", "STRINGS", "BRASS", "SYNTH", "DRUMS"] },
"soundfont_id": {
"type": "string",
"description": "ID of the SoundFont to use (e.g. 'generaluser_gs')"
},
"soundfont_bank": {
"type": "integer",
"default": 0,
"description": "MIDI Bank code (0 for melodic instruments, 128 for Drums)"
},
"soundfont_program": {
"type": "integer",
"description": "MIDI Program Number (0-127) corresponding to the instrument name in the Catalog"
},
"notes": { "type": "array", "items": { "type": "object" } }
},
"required": ["track_name", "soundfont_id", "soundfont_bank", "soundfont_program", "notes"]
}
}
}
}
}
}
4.2 Injecting the Catalog into the AI Agent's Context Prompt (Prompt Template)
Before sending the user query to the LLM, the system reads soundfont_catalog.json and injects a condensed catalog table into the System Instruction:
# System Context Prompt Injector
def build_ai_system_instruction(catalog_data: dict) -> str:
sf_summary = []
for sf_id, sf_info in catalog_data.items():
sf_summary.append(f"SoundFont ID: '{sf_id}' (File: {sf_info['filename']}):")
for inst in sf_info['instruments'][:20]: # Inject primary instrument lists
sf_summary.append(
f" - [{inst['name']}]: bank={inst['bank']}, program={inst['program']}"
)
catalog_context = "\n".join(sf_summary)
system_instruction = f"""
You are an AI Copilot for a DAW. Below is the Catalog of available SoundFonts on the system:
{catalog_context}
MANDATORY RULES WHEN CREATING TRACKS:
1. When creating any track, you MUST look up the catalog above and fill in the correct `soundfont_id`, `soundfont_bank`, and `soundfont_program`.
2. Example: If the user requests "Piano", select soundfont_id="generaluser_gs", soundfont_bank=0, soundfont_program=0 ("Stereo Grand Piano").
3. If the user requests "Violin/Strings", select soundfont_bank=0, soundfont_program=40 ("Violin Ensemble").
4. If the user requests "Drums", select soundfont_bank=128, soundfont_program=0 ("Standard Drum Kit").
"""
return system_instruction
4.3 Applying Program Change on Client & Server Render Layers
A. Client Browser Side (FluidSynth Wasm / SoundFont Player)
Upon receiving JSON from the AI, the Frontend invokes the Bank and Program selection function to trigger the correct sound:
// Client-side Javascript (spessasynth / fluidsynth.wasm)
function applyAITrackInstrument(trackId, soundfontBank, soundfontProgram) {
const channel = getTrackMIDIChannel(trackId);
// Send MIDI Bank Select (CC 0)
synthInstance.controllerChange(channel, 0, soundfontBank);
// Send MIDI Program Change
synthInstance.programChange(channel, soundfontProgram);
}
B. Server Offline Render Side (app/core/render_engine.py)
When rendering to a WAV file, Python inserts a MIDI Program Change event ahead of the Track's note sequence:
import mido
def create_midi_track_with_program(notes_data, bank=0, program=0):
midi_track = mido.MidiTrack()
# 1. Insert Bank Select (Control Change 0)
midi_track.append(mido.Message('control_change', channel=0, control=0, value=bank, time=0))
# 2. Insert Program Change (Instrument Sound Selection)
midi_track.append(mido.Message('program_change', channel=0, program=program, time=0))
# 3. Insert MIDI notes generated by AI
for note in notes_data:
start_tick = int(note['start_beat'] * 480) # 480 ticks per beat
dur_tick = int(note['duration_beats'] * 480)
pitch = int(note['pitch'])
vel = int(note['velocity'] * 127)
midi_track.append(mido.Message('note_on', note=pitch, velocity=vel, time=start_tick))
midi_track.append(mido.Message('note_off', note=pitch, velocity=0, time=dur_tick))
return midi_track
5. Action Checklist
- Step 1: Download Linux 64-bit
DecentSampler.vst3and copy it into/opt/daw_engine/vst3/. - Step 2: Download Pianobook sound libraries (e.g. Salamander Grand Piano) and extract them to
/opt/daw_engine/samples/pianobook/. - Step 3: Add
sf2utilstorequirements.txtand install it in Docker. - Step 4: Create
app/core/soundfont_inspector.pyto automatically scan all.sf2files in the project and generatesoundfont_catalog.json. - Step 5: Build API Endpoint
GET /api/v1/plugins/soundfonts/catalogreturning the extracted instrument catalog. - Step 6: Update the Prompt Template and AI Tool Schema to support
soundfont_bank&soundfont_program. - Step 7: End-to-End Verification: Type Prompt "Generate 8 bars of Brass horn music"
\rightarrowAI reads Catalog and selects Program56\rightarrowWeb Audio & Server Render produce the correct Brass horn instrument sound.