A Swift Package providing production-grade DSP primitives for a MIDI 2–native sampler, building on
Fountain-Coach/midi2.
- Loop seam discovery & stitching (composite metric: amplitude + slope + spectrum)
- Adjacent-zone crossfades (key/velocity), equal-power windows, smoothstep mapping
- Micro phase alignment for seam minimization
- DC removal, loudness matching, slew-limited parameter changes
- Windowed-sinc offline resampler for pitch-matching before blending
- Unit tests for click-free loops and continuity
MIDI 2.0 (via UMP) increases resolution and enables per-note expression. This library ensures that sample seams, loops, and crossfades are artifact-free so your instrument takes full advantage of MIDI 2 expressiveness without audible clicks or comb filtering.
dependencies: [
.package(url: "https://github.com/Fountain-Coach/midi2", branch: "main"),
.package(url: "https://example.com/your/Midi2SamplerDSP", branch: "main")
]If the
midi2package product name is notMIDI2, adjust the dependency inPackage.swiftaccordingly.
This package integrates with Fountain-Store via a thin adapter.
The repository reads/writes Sampler Pack JSON documents and defers storage backends to Fountain-Store
(local FS, cloud, or git—depending on its configuration).
import Midi2SamplerDSP
let repo = SamplerPackRepository() // uses Fountain-Store adapter by default
var pack = SamplerPack(
instrument: Instrument(
name: "GrandPiano",
sampleRate: 48000,
zones: [
Zone(id: "zp_C4_vel64",
pitchCenter: 60,
pitchRange: [57,63],
velocityRange: [0.5,0.7],
file: "Audio/piano_C4_vel64.wav",
loop: Loop(start: 123456, end: 234567, overlap: 2048))
]
)
)
try repo.save(pack, to: "Instruments/GrandPiano/Instrument.json")
let loaded = try repo.load(at: "Instruments/GrandPiano/Instrument.json")If the Fountain-Store SwiftPM product name is not
FountainStore, adjust the.product(name: "FountainStore", package: "Fountain-Store")inPackage.swift, and theimport FountainStorestatements accordingly.
This package exposes a SamplerAgent that can be wired into FountainKit as a Tool-capable agent.
The agent supports operations like stitchLoop, savePack, and listPacks. Extend it with
renderNote, renderChord, evaluateSeam, etc., as you formalize your tool schema.
import Midi2SamplerDSP
// import FountainKit // depending on your app target
let agent = SamplerAgent() // uses Fountain-Store by default
let req = StitchLoopRequest(audioPath: "Audio/mono.rawf32", searchStart: 48000, searchEnd: 240000, overlap: 2048)
let payload = try JSONEncoder().encode(req)
let invocation = SamplerToolInvocation(kind: .stitchLoop, payload: payload)
let resultData = try await agent.handle(invocation)
let result = try JSONDecoder().decode(StitchLoopResult.self, from: resultData)
print("Loop points:", result)Note: The included WAV loader is a stub for proof-of-concept. Replace with proper WAV I/O for production (e.g.,
AVAudioFile/AudioToolbox), or integrate your existing asset pipeline.
This repository now ships an OpenAPI 3.1 spec at openapi/sampler.yaml.
Planning/Reasoning systems (e.g., FountainKit) should discover and invoke
sampler capabilities by reading this spec — no direct code dependency needed.
Primary endpoints
POST /loops:stitch— discover/write seamless loop points using the composite DSP metricPOST /packs:save— persistSamplerPackdocuments (via Fountain-Store backends)GET /packs?directory=...— list packsPOST /render:note— offline render for a single note (artifact URL)
Models are aligned with the Swift SamplerPack types to keep JSON and code in lockstep.
The package now includes an AudioIntrospector and corpus models to make audio files
first-class corpus citizens. Use it to compute summaries (RMS/peak/DC, spectral stats,
placeholder MFCC/chroma, optional loopability via LoopStitcher), generate embeddings
and simple fingerprints, then persist results via Fountain-Store.
OpenAPI endpoints for this flow are documented in openapi/sampler.yaml:
POST /corpus/audio:ingestPOST /corpus/audio:analyzeGET /corpus/audio:searchGET /corpus/audio:dedupe
The package includes a Validator to enforce quality and consistency:
PackValidator— checks SamplerPack structure and zone metadata (hard/soft/info).
Auto-corrects soft issues: fillformat/version, set recommended crossfade time, suggest/raise loop overlap, set default RMS.AudioValidator— gates AudioSummary quality (DC/clipping/loopability thresholds).
let packReport = PackValidator().validate(pack)
let (corrected, fixes) = PackValidator().autoCorrect(pack)
let audioReport = AudioValidator().validate(summary)The spec now exposes validator endpoints so planners and tools can gate changes before persisting:
POST /packs:validate→ returns aValidationReportfor aSamplerPackPOST /corpus/audio:validate→ returns aValidationReportfor anAudioSummary
Use ValidationService to wire these endpoints in your server quickly.
New DSP blocks:
VelocityTimbreModel— maps MIDI 2 velocity to tilt/presence/transient/noise/saturation.Biquad— low-shelf & peak with smoothed coeffs.TransientShaper— attack emphasis.SoftSaturator— tanh-like soft clipper.PositionEQ— simple mic perspective EQ chain.PedalResonance— short IR convolution scaffold.KeyOff— micro-sample layer for releases.TuningTables— per-note detune/inharmonicity.RealTimeNoteProcessor— ties it all together per note.
OpenAPI additions for morphing:
POST /morph/preset:applyPOST /morph/params:renderNotePOST /morph/fitCurves
Use a few velocity anchors and let DSP + MIDI 2 per-note expressivity smoothly cover the rest.
- Upgrade PedalResonance: current IR convolution is naive FIR. Replace with partitioned FFT or GPU (Metal) for real-time efficiency.
- Curve Fitting: implement
/morph/fitCurveswith a small regression or ML fitter that learns velocity→timbre mapping from corpus embeddings & sparse anchors. - CLI Demo: add
sampler morph-render --note 60 --vel2 12000style command to render a WAV so developers can immediately hear the morphing results.