Compare commits

...

2 Commits

Author SHA1 Message Date
Skyler Lehmkuhl 728b88365d Add drums and guitar 2026-02-21 08:31:35 -05:00
Skyler Lehmkuhl 84f1f8e7d7 Add orchestral sampled instruments 2026-02-21 07:28:19 -05:00
450 changed files with 7793 additions and 1 deletions

View File

@ -231,7 +231,7 @@ impl Voice {
envelope_phase: EnvelopePhase::Attack,
envelope_value: 0.0,
crossfade_buffer: Vec::new(),
crossfade_length: 1000, // ~20ms at 48kHz (longer for smoother loops)
crossfade_length: 4800, // ~100ms at 48kHz — hides loop seams in sustained instruments
}
}
}

View File

@ -79,6 +79,8 @@ assets = [
["assets/icons/32x32.png", "usr/share/icons/hicolor/32x32/apps/lightningbeam-editor.png", "644"],
["assets/icons/128x128.png", "usr/share/icons/hicolor/128x128/apps/lightningbeam-editor.png", "644"],
["assets/icons/256x256.png", "usr/share/icons/hicolor/256x256/apps/lightningbeam-editor.png", "644"],
# Factory instrument presets and samples — copied to share dir, preserving directory tree
["target/release/presets/**/*", "usr/share/lightningbeam-editor/presets/", "644"],
]
[package.metadata.generate-rpm]
@ -121,3 +123,10 @@ mode = "644"
source = "assets/icons/256x256.png"
dest = "/usr/share/icons/hicolor/256x256/apps/lightningbeam-editor.png"
mode = "644"
# Factory instrument presets and samples (built by build.rs into target dir)
[[package.metadata.generate-rpm.assets]]
source = "target/release/presets/"
dest = "/usr/share/lightningbeam-editor/presets/"
mode = "644"
doc = false

View File

@ -5,6 +5,9 @@ use std::path::PathBuf;
fn main() {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
// Copy factory instrument presets next to the binary (works on all platforms)
bundle_factory_presets();
if target_os == "windows" {
bundle_windows_dlls();
}
@ -128,6 +131,56 @@ fn bundle_windows_dlls() {
println!("cargo:warning=Bundled FFmpeg DLLs to {}", target_dir.display());
}
/// Copy factory instrument presets into target dir so they're next to the binary.
/// This makes them available for:
/// - Portable/AppImage builds (preset browser checks <exe>/presets/)
/// - Windows builds (same)
/// - cargo-deb/rpm packaging (assets reference target/release/presets/)
fn bundle_factory_presets() {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let instruments_src = manifest_dir.join("../../src/assets/instruments");
let out_dir = env::var("OUT_DIR").unwrap();
let target_dir = PathBuf::from(&out_dir)
.parent().unwrap()
.parent().unwrap()
.parent().unwrap()
.to_path_buf();
let presets_dst = target_dir.join("presets");
if !instruments_src.exists() {
println!("cargo:warning=Factory instruments not found at {:?}, skipping", instruments_src);
return;
}
// Re-run if instruments change
println!("cargo:rerun-if-changed={}", instruments_src.display());
if let Err(e) = copy_dir_recursive(&instruments_src, &presets_dst) {
println!("cargo:warning=Failed to copy factory presets: {}", e);
}
}
/// Recursively copy a directory tree, preserving structure.
/// Only copies .json and .mp3 files (skips README.md, etc.)
fn copy_dir_recursive(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if src_path.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else if let Some(ext) = src_path.extension() {
let ext = ext.to_string_lossy().to_lowercase();
if ext == "json" || ext == "mp3" {
fs::copy(&src_path, &dst_path)?;
}
}
}
Ok(())
}
fn copy_library(lib_name: &str, search_paths: &[&str], lib_dir: &PathBuf) {
let mut copied = false;

View File

@ -0,0 +1,569 @@
#!/usr/bin/env python3
"""Build factory instrument presets from Virtual Playing Orchestra 3 samples.
Usage:
python3 scripts/build_instruments.py
Converts WAV samples to MP3 and generates MultiSampler JSON presets.
"""
import json
import os
import re
import subprocess
import sys
from pathlib import Path
VPO = Path.home() / "Downloads" / "Virtual-Playing-Orchestra3" / "libs"
INSTRUMENTS_DIR = Path(__file__).parent.parent / "src" / "assets" / "instruments"
# Note name to semitone offset (within octave)
NOTE_MAP = {
'c': 0, 'c#': 1, 'db': 1, 'd': 2, 'd#': 3, 'eb': 3,
'e': 4, 'f': 5, 'f#': 6, 'gb': 6, 'g': 7, 'g#': 8, 'ab': 8,
'a': 9, 'a#': 10, 'bb': 10, 'b': 11,
}
def note_to_midi(note_name: str, octave: int) -> int:
"""Convert note name + octave to MIDI number. C4 = 60."""
semitone = NOTE_MAP[note_name.lower()]
return (octave + 1) * 12 + semitone
def parse_sso_filename(filename: str) -> dict | None:
"""Parse SSO-style: instrument-sus-note-PB-loop.wav (e.g. 1st-violins-sus-a#3.wav)
Also handles flats: oboe-a#3, basses-sus-d#2, etc.
"""
m = re.search(r'([a-g][#b]?)(\d+)', filename.lower())
if not m:
return None
note, octave = m.group(1), int(m.group(2))
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note.upper()}{octave}"}
def parse_nbo_filename(filename: str) -> dict | None:
"""Parse NBO-style: octave_note.wav (e.g. 3_Bb-PB-loop.wav)"""
m = re.match(r'(\d+)_([A-Ga-g][b#]?)', filename)
if not m:
return None
octave, note = int(m.group(1)), m.group(2)
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note}{octave}"}
def parse_nbo_with_dynamics(filename: str) -> dict | None:
"""Parse NBO2-style with dynamics: octave_note_p.wav or octave_note.wav"""
m = re.match(r'(\d+)_([A-Ga-g][b#]?)(?:_(p|f|mf|ff))?', filename)
if not m:
return None
octave, note = int(m.group(1)), m.group(2)
dynamic = m.group(3)
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note}{octave}", 'dynamic': dynamic}
def parse_mw_viola_filename(filename: str) -> dict | None:
"""Parse MW-style: Violas_note.wav (e.g. Violas_c4.wav, Violas_d#3.wav)"""
m = re.search(r'_([a-g][#b]?)(\d+)\.wav', filename.lower())
if not m:
return None
note, octave = m.group(1), int(m.group(2))
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note.upper()}{octave}"}
def parse_mw_horn_filename(filename: str) -> dict | None:
"""Parse MW horn: horns-sus-ff-note-PB-loop.wav or horns-sus-mp-note-PB-loop.wav"""
# Extract dynamics marker (ff, mp) from filename
dyn_match = re.search(r'-(ff|mp|mf|p|pp)-', filename.lower())
dynamic = dyn_match.group(1) if dyn_match else None
# Extract note
m = re.search(r'([a-g][#b]?)(\d+)', filename.lower())
if not m:
return None
note, octave = m.group(1), int(m.group(2))
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note.upper()}{octave}", 'dynamic': dynamic}
def parse_vsco_harp_filename(filename: str) -> dict | None:
"""Parse VSCO harp: KSHarp_Note_dyn.wav (e.g. KSHarp_A4_mf.wav)"""
m = re.search(r'KSHarp_([A-G][b#]?)(\d+)', filename)
if not m:
return None
note, octave = m.group(1), int(m.group(2))
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note}{octave}"}
def convert_wav_to_mp3(wav_path: Path, mp3_path: Path, bitrate: str = '192k'):
"""Convert WAV to MP3 using ffmpeg with peak normalization."""
mp3_path.parent.mkdir(parents=True, exist_ok=True)
if mp3_path.exists():
return # Skip if already converted
# Peak-normalize to -1dBFS so all samples have consistent max level.
# Using dynaudnorm with very gentle settings to avoid changing the
# character of the sound — just brings everything to the same peak level.
subprocess.run([
'ffmpeg', '-i', str(wav_path),
'-af', 'loudnorm=I=-16:TP=-1:LRA=11',
'-ar', '44100', '-ab', bitrate,
'-y', '-loglevel', 'error',
str(mp3_path)
], check=True)
def compute_key_ranges(layers: list[dict]) -> list[dict]:
"""Compute key_min/key_max for each layer by splitting at midpoints between adjacent root notes."""
if not layers:
return layers
layers.sort(key=lambda l: l['root_key'])
for i, layer in enumerate(layers):
if i == 0:
layer['key_min'] = 0
else:
midpoint = (layers[i-1]['root_key'] + layer['root_key']) // 2 + 1
layer['key_min'] = midpoint
layers[i-1]['key_max'] = midpoint - 1
if i == len(layers) - 1:
layer['key_max'] = 127
return layers
def make_preset(name: str, description: str, tags: list[str], layers: list[dict],
attack: float = 0.01, release: float = 0.3) -> dict:
"""Generate a complete instrument preset JSON."""
return {
"metadata": {
"name": name,
"description": description,
"author": "Virtual Playing Orchestra 3",
"version": 1,
"tags": tags
},
"midi_targets": [0],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [100.0, 100.0]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": f"{name} Sampler",
"parameters": {
"0": 1.0, # gain
"1": attack, # attack
"2": release, # release
"3": 0.0 # transpose
},
"sample_data": {
"type": "multi_sampler",
"layers": layers
},
"position": [350.0, 0.0]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [700.0, 100.0]
}
],
"connections": [
{"from_node": 0, "from_port": 0, "to_node": 1, "to_port": 0},
{"from_node": 1, "from_port": 0, "to_node": 2, "to_port": 0}
]
}
def build_simple_instrument(name: str, description: str, tags: list[str],
source_dir: Path, output_subdir: str,
filename_filter=None, parser=parse_sso_filename,
attack: float = 0.01, release: float = 0.3,
loop: bool = False):
"""Build a single-velocity instrument from a directory of WAV files."""
out_dir = INSTRUMENTS_DIR / output_subdir
samples_dir = out_dir / "samples"
samples_dir.mkdir(parents=True, exist_ok=True)
layers = []
wav_files = sorted(source_dir.glob("*.wav"))
for wav in wav_files:
if filename_filter and not filename_filter(wav.name):
continue
parsed = parser(wav.name)
if not parsed:
print(f" WARNING: Could not parse {wav.name}, skipping")
continue
mp3_name = f"{parsed['note']}.mp3"
mp3_path = samples_dir / mp3_name
print(f" Converting {wav.name} -> {mp3_name} (MIDI {parsed['midi']})")
convert_wav_to_mp3(wav, mp3_path)
layer = {
"file_path": f"samples/{mp3_name}",
"root_key": parsed['midi'],
"velocity_min": 0,
"velocity_max": 127,
}
if loop:
layer["loop_mode"] = "continuous"
layers.append(layer)
layers = compute_key_ranges(layers)
preset = make_preset(name, description, tags, layers, attack, release)
preset_path = out_dir / f"{output_subdir.split('/')[-1]}.json"
with open(preset_path, 'w') as f:
json.dump(preset, f, indent=2)
print(f" -> Wrote {preset_path} ({len(layers)} layers)")
return layers
def build_dynamics_instrument(name: str, description: str, tags: list[str],
source_dir: Path, output_subdir: str,
filename_filter=None, parser=parse_nbo_with_dynamics,
attack: float = 0.01, release: float = 0.3,
loop: bool = False):
"""Build an instrument with velocity layers from dynamics markings."""
out_dir = INSTRUMENTS_DIR / output_subdir
samples_dir = out_dir / "samples"
samples_dir.mkdir(parents=True, exist_ok=True)
# Group samples by dynamics level
# Map dynamics markings to velocity ranges (soft to loud)
DYNAMICS_ORDER = ['pp', 'p', 'mp', 'mf', 'f', 'ff']
dynamics_groups: dict[str | None, list[dict]] = {}
wav_files = sorted(source_dir.glob("*.wav"))
for wav in wav_files:
if filename_filter and not filename_filter(wav.name):
continue
parsed = parser(wav.name)
if not parsed:
print(f" WARNING: Could not parse {wav.name}, skipping")
continue
dyn = parsed.get('dynamic')
suffix = f"_{dyn}" if dyn else ""
mp3_name = f"{parsed['note']}{suffix}.mp3"
mp3_path = samples_dir / mp3_name
print(f" Converting {wav.name} -> {mp3_name} (MIDI {parsed['midi']}, dyn={dyn})")
convert_wav_to_mp3(wav, mp3_path)
layer = {
"file_path": f"samples/{mp3_name}",
"root_key": parsed['midi'],
}
if loop:
layer["loop_mode"] = "continuous"
dynamics_groups.setdefault(dyn, []).append(layer)
# Determine velocity ranges based on how many dynamics levels exist
# Treat None (unmarked) as forte — it's the "normal" dynamic
dyn_keys = sorted(dynamics_groups.keys(),
key=lambda d: DYNAMICS_ORDER.index(d) if d and d in DYNAMICS_ORDER else
(DYNAMICS_ORDER.index('f') if d is None else 3))
if len(dyn_keys) == 1:
# Only one dynamics level — full velocity
for layer in dynamics_groups[dyn_keys[0]]:
layer["velocity_min"] = 0
layer["velocity_max"] = 127
else:
num_levels = len(dyn_keys)
vel_step = 128 // num_levels
for i, dyn in enumerate(dyn_keys):
vel_min = i * vel_step
vel_max = (i + 1) * vel_step - 1 if i < num_levels - 1 else 127
for layer in dynamics_groups[dyn]:
layer["velocity_min"] = vel_min
layer["velocity_max"] = vel_max
# Compute key ranges separately for each velocity group
all_layers = []
for dyn, group in dynamics_groups.items():
group = compute_key_ranges(group)
all_layers.extend(group)
preset = make_preset(name, description, tags, all_layers, attack, release)
preset_path = out_dir / f"{output_subdir.split('/')[-1]}.json"
with open(preset_path, 'w') as f:
json.dump(preset, f, indent=2)
dyn_summary = ", ".join(f"{k or 'default'}: {len(v)}" for k, v in dynamics_groups.items())
print(f" -> Wrote {preset_path} ({len(all_layers)} layers: {dyn_summary})")
return all_layers
def build_combined_instrument(name: str, description: str, tags: list[str],
component_dirs: list[str], output_subdir: str,
attack: float = 0.01, release: float = 0.3):
"""Build a combined instrument that references samples from component instruments.
component_dirs: list of output_subdir paths for component instruments, ordered low to high pitch.
Splits the keyboard range across them.
"""
out_dir = INSTRUMENTS_DIR / output_subdir
out_dir.mkdir(parents=True, exist_ok=True)
# Load each component's preset to get its layers
all_component_layers = []
for comp_dir in component_dirs:
comp_path = INSTRUMENTS_DIR / comp_dir
json_files = list(comp_path.glob("*.json"))
if not json_files:
print(f" WARNING: No preset found in {comp_dir}")
continue
with open(json_files[0]) as f:
comp_preset = json.load(f)
comp_layers = comp_preset["nodes"][1]["sample_data"]["layers"]
# Adjust file paths to be relative from the combined instrument dir
rel_prefix = os.path.relpath(comp_path, out_dir)
for layer in comp_layers:
layer["file_path"] = f"{rel_prefix}/{layer['file_path']}"
all_component_layers.extend(comp_layers)
# Re-sort by root key and recompute ranges across all layers
# Group by velocity range to handle dynamics separately
vel_groups = {}
for layer in all_component_layers:
vel_key = (layer["velocity_min"], layer["velocity_max"])
vel_groups.setdefault(vel_key, []).append(layer)
final_layers = []
for vel_key, group in vel_groups.items():
group = compute_key_ranges(group)
# Preserve the original velocity range
for layer in group:
layer["velocity_min"] = vel_key[0]
layer["velocity_max"] = vel_key[1]
final_layers.extend(group)
preset = make_preset(name, description, tags, final_layers, attack, release)
preset_path = out_dir / f"{output_subdir.split('/')[-1]}.json"
with open(preset_path, 'w') as f:
json.dump(preset, f, indent=2)
print(f" -> Wrote {preset_path} ({len(final_layers)} layers from {len(component_dirs)} components)")
def main():
print("=== Building Lightningbeam Factory Instruments from VPO3 ===\n")
if not VPO.exists():
print(f"ERROR: VPO3 not found at {VPO}")
sys.exit(1)
# --- STRINGS ---
print("\n[1/14] Violin Section (SSO 1st Violins sustain)")
build_simple_instrument(
"Violin Section", "Orchestral violin section with sustained bowing",
["strings", "violin", "section", "orchestral"],
VPO / "SSO" / "Samples" / "1st Violins",
"strings/violin-section",
filename_filter=lambda f: 'sus' in f.lower(),
parser=parse_sso_filename,
attack=0.05, release=0.4,
loop=True,
)
print("\n[2/14] Viola Section (Mattias-Westlund)")
build_simple_instrument(
"Viola Section", "Orchestral viola section with sustained bowing",
["strings", "viola", "section", "orchestral"],
VPO / "Mattias-Westlund" / "ViolaSect" / "Samples",
"strings/viola-section",
parser=parse_mw_viola_filename,
attack=0.05, release=0.4,
loop=True,
)
print("\n[3/14] Cello Section (NBO sustain)")
build_simple_instrument(
"Cello Section", "Orchestral cello section with sustained bowing",
["strings", "cello", "section", "orchestral"],
VPO / "NoBudgetOrch" / "CelloSect" / "Sustain",
"strings/cello-section",
parser=parse_nbo_filename,
attack=0.05, release=0.4,
loop=True,
)
print("\n[4/14] Bass Section (SSO sustain)")
build_simple_instrument(
"Bass Section", "Orchestral double bass section with sustained bowing",
["strings", "bass", "contrabass", "section", "orchestral"],
VPO / "SSO" / "Samples" / "Basses",
"strings/bass-section",
filename_filter=lambda f: 'sus' in f.lower(),
parser=parse_sso_filename,
attack=0.08, release=0.5,
loop=True,
)
print("\n[5/14] Harp (VSCO2-CE)")
build_simple_instrument(
"Harp", "Concert harp",
["strings", "harp", "orchestral"],
VPO / "VSCO2-CE" / "Strings" / "Harp",
"strings/harp",
parser=parse_vsco_harp_filename,
attack=0.001, release=0.8,
)
# --- WOODWINDS ---
print("\n[6/14] Flute Section (NBO)")
build_simple_instrument(
"Flute", "Orchestral flute section",
["woodwinds", "flute", "section", "orchestral"],
VPO / "NoBudgetOrch" / "FluteSect",
"woodwinds/flute",
parser=parse_nbo_filename,
attack=0.03, release=0.3,
loop=True,
)
print("\n[7/14] Oboe (SSO solo)")
build_simple_instrument(
"Oboe", "Solo oboe",
["woodwinds", "oboe", "solo", "orchestral"],
VPO / "SSO" / "Samples" / "Oboe",
"woodwinds/oboe",
filename_filter=lambda f: f.endswith('.wav') and 'readme' not in f.lower(),
parser=parse_sso_filename,
attack=0.02, release=0.25,
loop=True,
)
print("\n[8/14] Clarinet Section (NBO)")
build_simple_instrument(
"Clarinet", "Orchestral clarinet section",
["woodwinds", "clarinet", "section", "orchestral"],
VPO / "NoBudgetOrch" / "ClarinetSect" / "Sustain",
"woodwinds/clarinet",
parser=parse_nbo_filename,
attack=0.02, release=0.25,
loop=True,
)
print("\n[9/14] Bassoon (SSO)")
build_simple_instrument(
"Bassoon", "Solo bassoon",
["woodwinds", "bassoon", "solo", "orchestral"],
VPO / "SSO" / "Samples" / "Bassoon",
"woodwinds/bassoon",
parser=parse_sso_filename,
attack=0.03, release=0.3,
loop=True,
)
# --- BRASS ---
print("\n[10/14] Horn Section (Mattias-Westlund, ff + mp dynamics)")
build_dynamics_instrument(
"Horn Section", "French horn section with forte and mezzo-piano dynamics",
["brass", "horn", "french horn", "section", "orchestral"],
VPO / "Mattias-Westlund" / "Horns" / "Samples",
"brass/horn-section",
parser=parse_mw_horn_filename,
attack=0.04, release=0.4,
loop=True,
)
print("\n[11/14] Trumpet Section (NBO2 with dynamics)")
build_dynamics_instrument(
"Trumpet Section", "Orchestral trumpet section with piano and forte dynamics",
["brass", "trumpet", "section", "orchestral"],
VPO / "NoBudgetOrch2" / "Trumpet" / "TrumpetSect" / "Sustain",
"brass/trumpet-section",
attack=0.02, release=0.3,
loop=True,
)
print("\n[12/14] Trombone Section (NBO2 with dynamics)")
build_dynamics_instrument(
"Trombone Section", "Orchestral trombone section with piano and forte dynamics",
["brass", "trombone", "section", "orchestral"],
VPO / "NoBudgetOrch2" / "Trombone" / "TromboneSect" / "Sustain",
"brass/trombone-section",
attack=0.03, release=0.35,
loop=True,
)
print("\n[13/14] Tuba (SSO sustain)")
build_simple_instrument(
"Tuba", "Orchestral tuba",
["brass", "tuba", "orchestral"],
VPO / "SSO" / "Samples" / "Tuba",
"brass/tuba",
filename_filter=lambda f: 'sus' in f.lower(),
parser=parse_sso_filename,
attack=0.04, release=0.4,
loop=True,
)
# --- PERCUSSION ---
print("\n[14/14] Timpani (NBO)")
build_simple_instrument(
"Timpani", "Orchestral timpani",
["percussion", "timpani", "orchestral"],
VPO / "NoBudgetOrch" / "Timpani",
"orchestral/timpani",
parser=lambda f: parse_sso_filename(f), # Note-octave format like A2-PB.wav
attack=0.001, release=1.5,
)
# --- COMBINED INSTRUMENTS ---
print("\n[Combined] Strings")
build_combined_instrument(
"Strings", "Full string section — auto-selects violin, viola, cello, or bass by pitch range",
["strings", "section", "orchestral", "combined"],
[
"strings/bass-section",
"strings/cello-section",
"strings/viola-section",
"strings/violin-section",
],
"strings/strings-combined",
attack=0.05, release=0.4,
)
print("\n[Combined] Woodwinds")
build_combined_instrument(
"Woodwinds", "Full woodwind section — auto-selects bassoon, clarinet, oboe, or flute by pitch range",
["woodwinds", "section", "orchestral", "combined"],
[
"woodwinds/bassoon",
"woodwinds/clarinet",
"woodwinds/oboe",
"woodwinds/flute",
],
"woodwinds/woodwinds-combined",
attack=0.03, release=0.3,
)
print("\n[Combined] Brass")
build_combined_instrument(
"Brass", "Full brass section — auto-selects tuba, trombone, horn, or trumpet by pitch range",
["brass", "section", "orchestral", "combined"],
[
"brass/tuba",
"brass/trombone-section",
"brass/horn-section",
"brass/trumpet-section",
],
"brass/brass-combined",
attack=0.03, release=0.35,
)
print("\n=== Done! ===")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,563 @@
#!/usr/bin/env python3
"""Build non-orchestral factory instrument presets.
Sources:
- Acoustic Guitar: University of Iowa MIS (unrestricted license)
- Bass Guitar: Karoryfer Growlybass CC0 (public domain)
- Drum Kit: Salamander Drumkit (public domain)
Usage:
python3 scripts/build_non_orchestral.py
# or with anaconda (needed for aubio):
~/anaconda3/bin/python3 scripts/build_non_orchestral.py
"""
import json
import os
import re
import subprocess
import sys
from pathlib import Path
# Try to import aubio (needed for guitar splitting)
try:
import aubio
HAS_AUBIO = True
except ImportError:
HAS_AUBIO = False
print("WARNING: aubio not installed — guitar splitting will be skipped")
print(" Install with: pip install aubio")
SAMPLES_DIR = Path.home() / "Downloads" / "non-orchestral-samples"
INSTRUMENTS_DIR = Path(__file__).parent.parent / "src" / "assets" / "instruments"
NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
FLAT_TO_SHARP = {'Db': 'C#', 'Eb': 'D#', 'Gb': 'F#', 'Ab': 'G#', 'Bb': 'A#'}
NOTE_MAP = {
'c': 0, 'c#': 1, 'db': 1, 'd': 2, 'd#': 3, 'eb': 3,
'e': 4, 'f': 5, 'f#': 6, 'gb': 6, 'g': 7, 'g#': 8, 'ab': 8,
'a': 9, 'a#': 10, 'bb': 10, 'b': 11,
}
def note_to_midi(note_name: str, octave: int) -> int:
return (octave + 1) * 12 + NOTE_MAP[note_name.lower()]
def midi_to_name(midi: int) -> str:
return f"{NOTE_NAMES[midi % 12]}{midi // 12 - 1}"
def parse_note_str(n: str) -> int:
"""Parse 'E2', 'Bb5', 'C#3' etc to MIDI number."""
if len(n) >= 3 and n[1] in 'b#':
name, oct = n[:2], int(n[2:])
name = FLAT_TO_SHARP.get(name, name)
else:
name, oct = n[0], int(n[1:])
return (oct + 1) * 12 + NOTE_NAMES.index(name)
def convert_to_mp3(input_path: Path, mp3_path: Path, bitrate: str = '192k'):
"""Convert any audio to normalized MP3."""
mp3_path.parent.mkdir(parents=True, exist_ok=True)
if mp3_path.exists():
return
subprocess.run([
'ffmpeg', '-i', str(input_path),
'-af', 'loudnorm=I=-16:TP=-1:LRA=11',
'-ar', '44100', '-ab', bitrate,
'-y', '-loglevel', 'error',
str(mp3_path)
], check=True)
def extract_segment(input_path: Path, output_path: Path, start: float, end: float,
bitrate: str = '192k'):
"""Extract a time segment from audio and convert to normalized MP3."""
output_path.parent.mkdir(parents=True, exist_ok=True)
if output_path.exists():
return
duration = end - start
subprocess.run([
'ffmpeg', '-ss', str(start), '-i', str(input_path),
'-t', str(duration),
'-af', 'loudnorm=I=-16:TP=-1:LRA=11',
'-ar', '44100', '-ab', bitrate,
'-y', '-loglevel', 'error',
str(output_path)
], check=True)
def compute_key_ranges(layers: list[dict]) -> list[dict]:
if not layers:
return layers
layers.sort(key=lambda l: l['root_key'])
for i, layer in enumerate(layers):
if i == 0:
layer['key_min'] = 0
else:
midpoint = (layers[i-1]['root_key'] + layer['root_key']) // 2 + 1
layer['key_min'] = midpoint
layers[i-1]['key_max'] = midpoint - 1
if i == len(layers) - 1:
layer['key_max'] = 127
return layers
def make_preset(name: str, description: str, author: str, tags: list[str],
layers: list[dict], attack: float = 0.01, release: float = 0.3) -> dict:
return {
"metadata": {
"name": name,
"description": description,
"author": author,
"version": 1,
"tags": tags
},
"midi_targets": [0],
"output_node": 2,
"nodes": [
{
"id": 0, "node_type": "MidiInput", "name": "MIDI In",
"parameters": {}, "position": [100.0, 100.0]
},
{
"id": 1, "node_type": "MultiSampler", "name": f"{name} Sampler",
"parameters": {"0": 1.0, "1": attack, "2": release, "3": 0.0},
"sample_data": {"type": "multi_sampler", "layers": layers},
"position": [350.0, 0.0]
},
{
"id": 2, "node_type": "AudioOutput", "name": "Out",
"parameters": {}, "position": [700.0, 100.0]
}
],
"connections": [
{"from_node": 0, "from_port": 0, "to_node": 1, "to_port": 0},
{"from_node": 1, "from_port": 0, "to_node": 2, "to_port": 0}
]
}
# ============================================================
# ACOUSTIC GUITAR (University of Iowa MIS)
# ============================================================
def detect_onsets(fpath: str, threshold: float = 0.3, minioi: float = 2.0,
method: str = "default") -> list[float]:
"""Detect note onsets in an audio file using aubio."""
src = aubio.source(fpath, 44100, 512)
onset_det = aubio.onset(method, 1024, 512, 44100)
onset_det.set_threshold(threshold)
onset_det.set_minioi_s(minioi)
onsets = []
while True:
samples, read = src()
if onset_det(samples):
onsets.append(onset_det.get_last_s())
if read < 512:
break
if not onsets or onsets[0] > 1.0:
onsets.insert(0, 0.0)
return onsets
def get_file_duration(fpath: str) -> float:
"""Get audio file duration in seconds."""
result = subprocess.run(
['ffprobe', '-v', 'error', '-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1', fpath],
capture_output=True, text=True)
return float(result.stdout.strip())
# Preferred string for each MIDI note range (avoids duplicates across strings)
GUITAR_STRING_RANGES = {
'sulE': (40, 49), # E2-C#3
'sulA': (50, 54), # D3-F#3
'sulD': (55, 58), # G3-A#3
'sulG': (59, 63), # B3-D#4
'sulB': (64, 68), # E4-G#4
'sul_E': (69, 83), # A4-B5
}
def build_guitar():
"""Split Iowa MIS guitar chromatic scales into individual notes and build preset."""
if not HAS_AUBIO:
print(" SKIPPED (aubio required)")
return
guitar_dir = SAMPLES_DIR / "iowa-guitar" / "extracted" / "1644stereo"
if not guitar_dir.exists():
print(f" ERROR: Guitar samples not found at {guitar_dir}")
return
out_dir = INSTRUMENTS_DIR / "guitar" / "acoustic-guitar"
samples_dir = out_dir / "samples"
samples_dir.mkdir(parents=True, exist_ok=True)
# Process each dynamic level
DYNAMICS = {'pp': (0, 42), 'mf': (43, 95), 'ff': (96, 127)}
all_layers = []
for dyn, (vel_min, vel_max) in DYNAMICS.items():
print(f" Processing {dyn} dynamics...")
# Use lower threshold for pp to catch quiet onsets
threshold = 0.2 if dyn == 'pp' else 0.3
for fname in sorted(os.listdir(guitar_dir)):
if not fname.endswith('.aif'):
continue
parts = fname.replace('.aif', '').split('.')
if parts[1] != dyn:
continue
string = parts[2]
note_range_str = parts[3]
# Parse note range
m = re.match(r'([A-G][b#]?\d)([A-G][b#]?\d)', note_range_str)
if m:
file_lo = parse_note_str(m.group(1))
file_hi = parse_note_str(m.group(2))
else:
file_lo = file_hi = parse_note_str(note_range_str)
# Check overlap with preferred range for this string
pref_lo, pref_hi = GUITAR_STRING_RANGES.get(string, (0, 0))
overlap_lo = max(file_lo, pref_lo)
overlap_hi = min(file_hi, pref_hi)
if overlap_lo > overlap_hi:
continue # No notes needed from this file
fpath = str(guitar_dir / fname)
total_notes = file_hi - file_lo + 1
if total_notes == 1:
# Single note file
mp3_name = f"{midi_to_name(file_lo)}_{dyn}.mp3"
print(f" {fname} -> {mp3_name}")
convert_to_mp3(Path(fpath), samples_dir / mp3_name)
all_layers.append({
"file_path": f"samples/{mp3_name}",
"root_key": file_lo,
"velocity_min": vel_min,
"velocity_max": vel_max,
})
continue
# Multi-note file: detect onsets and split
onsets = detect_onsets(fpath, threshold=threshold)
duration = get_file_duration(fpath)
if len(onsets) != total_notes:
# Try progressively different thresholds and methods
found = False
for method in ["default", "specflux"]:
for t in [0.1, 0.15, 0.2, 0.5, 0.8, 1.0]:
onsets = detect_onsets(fpath, threshold=t, method=method)
if len(onsets) == total_notes:
found = True
break
if found:
break
if not found:
print(f" SKIPPING {fname} (no threshold/method gives {total_notes} onsets)")
continue
# Extract each needed note
for note_idx in range(total_notes):
midi = file_lo + note_idx
if midi < overlap_lo or midi > overlap_hi:
continue # Not in our preferred range
start = onsets[note_idx]
end = onsets[note_idx + 1] if note_idx + 1 < len(onsets) else duration
# Trim to max 8 seconds per note (plenty for guitar decay)
end = min(end, start + 8.0)
mp3_name = f"{midi_to_name(midi)}_{dyn}.mp3"
print(f" {fname} [{note_idx}] -> {mp3_name} ({start:.2f}s-{end:.2f}s)")
extract_segment(Path(fpath), samples_dir / mp3_name, start, end)
all_layers.append({
"file_path": f"samples/{mp3_name}",
"root_key": midi,
"velocity_min": vel_min,
"velocity_max": vel_max,
})
# Compute key ranges per velocity group
vel_groups = {}
for layer in all_layers:
vel_key = (layer["velocity_min"], layer["velocity_max"])
vel_groups.setdefault(vel_key, []).append(layer)
final_layers = []
for vel_key, group in vel_groups.items():
group = compute_key_ranges(group)
for layer in group:
layer["velocity_min"] = vel_key[0]
layer["velocity_max"] = vel_key[1]
final_layers.extend(group)
preset = make_preset(
"Acoustic Guitar",
"Nylon-string classical guitar (Raimundo 118) with three velocity layers",
"University of Iowa MIS",
["guitar", "acoustic", "nylon", "classical"],
final_layers,
attack=0.001, release=0.8,
)
preset_path = out_dir / "acoustic-guitar.json"
with open(preset_path, 'w') as f:
json.dump(preset, f, indent=2)
print(f" -> Wrote {preset_path} ({len(final_layers)} layers)")
# ============================================================
# BASS GUITAR (Karoryfer Growlybass)
# ============================================================
def parse_growlybass_filename(filename: str) -> dict | None:
"""Parse Growlybass naming: note_dyn_rr.wav (e.g. a2_ff_rr1.wav, db2_pp_rr3.wav)"""
m = re.match(r'([a-g][b#]?)(\d+)_(pp|p|f|ff)_rr(\d+)\.wav', filename.lower())
if not m:
return None
note, octave = m.group(1), int(m.group(2))
dynamic = m.group(3)
rr = int(m.group(4))
midi = note_to_midi(note, octave)
return {'midi': midi, 'note': f"{note.upper()}{octave}", 'dynamic': dynamic, 'rr': rr}
def build_bass_guitar():
"""Build bass guitar instrument from Karoryfer Growlybass samples."""
source_dir = SAMPLES_DIR / "growlybass" / "extracted" / "Growlybass" / "sustain"
if not source_dir.exists():
print(f" ERROR: Growlybass samples not found at {source_dir}")
return
out_dir = INSTRUMENTS_DIR / "guitar" / "bass-guitar"
samples_dir = out_dir / "samples"
samples_dir.mkdir(parents=True, exist_ok=True)
# Growlybass has 4 dynamics (pp, p, f, ff) and 4 round robins each.
# We'll use round robin 1 only (our MultiSampler doesn't support round robin yet)
# and map all 4 dynamics to velocity layers.
DYNAMICS_ORDER = ['pp', 'p', 'f', 'ff']
dynamics_groups: dict[str, list[dict]] = {}
for wav in sorted(source_dir.glob("*.wav")):
parsed = parse_growlybass_filename(wav.name)
if not parsed:
print(f" WARNING: Could not parse {wav.name}")
continue
if parsed['rr'] != 1:
continue # Only use round robin 1
dyn = parsed['dynamic']
mp3_name = f"{parsed['note']}_{dyn}.mp3"
mp3_path = samples_dir / mp3_name
print(f" Converting {wav.name} -> {mp3_name} (MIDI {parsed['midi']})")
convert_to_mp3(wav, mp3_path)
layer = {
"file_path": f"samples/{mp3_name}",
"root_key": parsed['midi'],
}
dynamics_groups.setdefault(dyn, []).append(layer)
# Assign velocity ranges
num_levels = len(dynamics_groups)
vel_step = 128 // num_levels
dyn_keys = sorted(dynamics_groups.keys(),
key=lambda d: DYNAMICS_ORDER.index(d))
for i, dyn in enumerate(dyn_keys):
vel_min = i * vel_step
vel_max = (i + 1) * vel_step - 1 if i < num_levels - 1 else 127
for layer in dynamics_groups[dyn]:
layer["velocity_min"] = vel_min
layer["velocity_max"] = vel_max
# Compute key ranges per velocity group
all_layers = []
for dyn, group in dynamics_groups.items():
group = compute_key_ranges(group)
all_layers.extend(group)
preset = make_preset(
"Bass Guitar",
"Electric bass guitar (Squier Jazz) with four velocity layers",
"Karoryfer Samples (CC0)",
["guitar", "bass", "electric"],
all_layers,
attack=0.001, release=0.5,
)
preset_path = out_dir / "bass-guitar.json"
with open(preset_path, 'w') as f:
json.dump(preset, f, indent=2)
dyn_summary = ", ".join(f"{k}: {len(v)}" for k, v in dynamics_groups.items())
print(f" -> Wrote {preset_path} ({len(all_layers)} layers: {dyn_summary})")
# ============================================================
# DRUM KIT (Salamander Drumkit)
# ============================================================
# Salamander uses GM-like drum mapping.
# Files: kick_OH_F_1.wav, snare_OH_FF_1.wav, hihatClosed_OH_P_1.wav, etc.
# OH = overhead mic, F/FF/P/PP/MP/Ghost = dynamics, number = round robin
# GM drum map — maps Salamander drum names to MIDI notes
GM_DRUMS = {
'kick': 36, # C2 - Bass Drum 1
'snare': 38, # D2 - Acoustic Snare
'snareOFF': 40, # E2 - Electric Snare (snares off)
'snareStick': 37, # C#2 - Side Stick
'hihatClosed': 42, # F#2 - Closed Hi-Hat
'hihatOpen': 46, # A#2 - Open Hi-Hat
'hihatFoot': 44, # G#2 - Pedal Hi-Hat
'hiTom': 50, # D3 - High Tom
'loTom': 45, # A2 - Low Tom
'crash1': 49, # C#3 - Crash Cymbal 1
'crash2': 57, # A3 - Crash Cymbal 2
'ride1': 51, # D#3 - Ride Cymbal 1
'ride1Bell': 53, # F3 - Ride Bell
'cowbell': 56, # G#3 - Cowbell
'splash1': 55, # G3 - Splash Cymbal
}
def parse_salamander_filename(filename: str) -> dict | None:
"""Parse Salamander naming: drum_OH_dyn_rr.wav or drum_dyn_rr.wav"""
# Try with OH mic prefix first
m = re.match(r'(\w+?)_OH_([A-Za-z]+)_(\d+)\.wav', filename)
if not m:
# Some drums (cowbell, bellchime) don't have _OH_
m = re.match(r'(\w+?)_([A-Z][A-Za-z]*)_(\d+)\.wav', filename)
if not m:
return None
drum, dynamic, rr = m.group(1), m.group(2).lower(), int(m.group(3))
midi = GM_DRUMS.get(drum)
if midi is None:
return None
return {'midi': midi, 'drum': drum, 'dynamic': dynamic, 'rr': rr}
def build_drum_kit():
"""Build drum kit instrument from Salamander Drumkit samples."""
# Find the OH (overhead mic) sample directory
sal_base = SAMPLES_DIR / "salamander-drums"
source_dir = None
for candidate in [sal_base / "OH",
sal_base / "salamanderDrumkit" / "OH"]:
if candidate.exists():
source_dir = candidate
break
if source_dir is None:
for p in sal_base.rglob("OH"):
if p.is_dir():
source_dir = p
break
if source_dir is None:
print(f" ERROR: Salamander OH samples not found under {sal_base}")
return
print(f" Using samples from: {source_dir}")
out_dir = INSTRUMENTS_DIR / "drums" / "drum-kit"
samples_dir = out_dir / "samples"
samples_dir.mkdir(parents=True, exist_ok=True)
# Group by drum type and dynamics
# We'll use OH (overhead) mic for a natural stereo image
# Only use round robin 1 to keep size down
drum_groups: dict[str, dict[str, list]] = {} # drum -> {dyn: [layers]}
for wav in sorted(source_dir.glob("*.wav")):
parsed = parse_salamander_filename(wav.name)
if not parsed:
continue
if parsed['rr'] != 1:
continue
drum = parsed['drum']
dyn = parsed['dynamic']
mp3_name = f"{drum}_{dyn}.mp3"
mp3_path = samples_dir / mp3_name
print(f" Converting {wav.name} -> {mp3_name} (MIDI {parsed['midi']})")
convert_to_mp3(wav, mp3_path)
drum_groups.setdefault(drum, {}).setdefault(dyn, []).append({
"file_path": f"samples/{mp3_name}",
"root_key": parsed['midi'],
})
# Build layers: each drum piece gets its own MIDI note
# Dynamics map to velocity layers
DYNAMICS_ORDER = ['ghost', 'pp', 'p', 'mp', 'mf', 'f', 'ff']
all_layers = []
for drum, dyn_map in drum_groups.items():
dyn_keys = sorted(dyn_map.keys(),
key=lambda d: DYNAMICS_ORDER.index(d) if d in DYNAMICS_ORDER else 3)
num_levels = len(dyn_keys)
if num_levels == 1:
for layer in list(dyn_map.values())[0]:
layer["velocity_min"] = 0
layer["velocity_max"] = 127
layer["key_min"] = layer["root_key"]
layer["key_max"] = layer["root_key"]
all_layers.append(layer)
else:
vel_step = 128 // num_levels
for i, dyn in enumerate(dyn_keys):
vel_min = i * vel_step
vel_max = (i + 1) * vel_step - 1 if i < num_levels - 1 else 127
for layer in dyn_map[dyn]:
layer["velocity_min"] = vel_min
layer["velocity_max"] = vel_max
layer["key_min"] = layer["root_key"]
layer["key_max"] = layer["root_key"]
all_layers.append(layer)
preset = make_preset(
"Drum Kit",
"Acoustic drum kit (Salamander) — GM-compatible MIDI mapping",
"Salamander Drumkit (Public Domain)",
["drums", "percussion", "kit", "acoustic"],
all_layers,
attack=0.001, release=0.5,
)
preset_path = out_dir / "drum-kit.json"
with open(preset_path, 'w') as f:
json.dump(preset, f, indent=2)
print(f" -> Wrote {preset_path} ({len(all_layers)} layers, {len(drum_groups)} drums)")
# ============================================================
# MAIN
# ============================================================
def main():
print("=== Building Non-Orchestral Factory Instruments ===\n")
print("\n[1/3] Acoustic Guitar (University of Iowa MIS)")
build_guitar()
print("\n[2/3] Bass Guitar (Karoryfer Growlybass)")
build_bass_guitar()
print("\n[3/3] Drum Kit (Salamander Drumkit)")
build_drum_kit()
print("\n=== Done! ===")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,758 @@
{
"metadata": {
"name": "Brass",
"description": "Full brass section \u2014 auto-selects tuba, trombone, horn, or trumpet by pitch range",
"author": "Virtual Playing Orchestra 3",
"version": 1,
"tags": [
"brass",
"section",
"orchestral",
"combined"
]
},
"midi_targets": [
0
],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [
100.0,
100.0
]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": "Brass Sampler",
"parameters": {
"0": 1.0,
"1": 0.03,
"2": 0.35,
"3": 0.0
},
"sample_data": {
"type": "multi_sampler",
"layers": [
{
"file_path": "../tuba/samples/E1.mp3",
"root_key": 28,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 0,
"key_max": 29
},
{
"file_path": "../tuba/samples/G1.mp3",
"root_key": 31,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 30,
"key_max": 32
},
{
"file_path": "../tuba/samples/A#1.mp3",
"root_key": 34,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 33,
"key_max": 35
},
{
"file_path": "../tuba/samples/C#2.mp3",
"root_key": 37,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 36,
"key_max": 38
},
{
"file_path": "../tuba/samples/E2.mp3",
"root_key": 40,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 39,
"key_max": 41
},
{
"file_path": "../tuba/samples/G2.mp3",
"root_key": 43,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 42,
"key_max": 44
},
{
"file_path": "../tuba/samples/A#2.mp3",
"root_key": 46,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 45,
"key_max": 47
},
{
"file_path": "../tuba/samples/C#3.mp3",
"root_key": 49,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 48,
"key_max": 50
},
{
"file_path": "../tuba/samples/E3.mp3",
"root_key": 52,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 51,
"key_max": 127
},
{
"file_path": "../horn-section/samples/E2_ff.mp3",
"root_key": 40,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 0,
"key_max": 40
},
{
"file_path": "../trombone-section/samples/F2.mp3",
"root_key": 41,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 41,
"key_max": 42
},
{
"file_path": "../horn-section/samples/G2_ff.mp3",
"root_key": 43,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 43,
"key_max": 44
},
{
"file_path": "../trombone-section/samples/A2.mp3",
"root_key": 45,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 45,
"key_max": 45
},
{
"file_path": "../horn-section/samples/A#2_ff.mp3",
"root_key": 46,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 46,
"key_max": 47
},
{
"file_path": "../trombone-section/samples/C3.mp3",
"root_key": 48,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 48,
"key_max": 48
},
{
"file_path": "../horn-section/samples/C#3_ff.mp3",
"root_key": 49,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 49,
"key_max": 50
},
{
"file_path": "../trombone-section/samples/Eb3.mp3",
"root_key": 51,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 51,
"key_max": 51
},
{
"file_path": "../horn-section/samples/E3_ff.mp3",
"root_key": 52,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 52,
"key_max": 53
},
{
"file_path": "../trombone-section/samples/Gb3.mp3",
"root_key": 54,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 54,
"key_max": 54
},
{
"file_path": "../horn-section/samples/G3_ff.mp3",
"root_key": 55,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 55,
"key_max": 55
},
{
"file_path": "../trumpet-section/samples/G3.mp3",
"root_key": 55,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 56,
"key_max": 55
},
{
"file_path": "../trumpet-section/samples/Ab3.mp3",
"root_key": 56,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 56,
"key_max": 56
},
{
"file_path": "../trombone-section/samples/A3.mp3",
"root_key": 57,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 57,
"key_max": 57
},
{
"file_path": "../horn-section/samples/A#3_ff.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 58,
"key_max": 58
},
{
"file_path": "../trumpet-section/samples/Bb3.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 59,
"key_max": 59
},
{
"file_path": "../trombone-section/samples/C4.mp3",
"root_key": 60,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 60,
"key_max": 60
},
{
"file_path": "../trumpet-section/samples/C4.mp3",
"root_key": 60,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 61,
"key_max": 60
},
{
"file_path": "../horn-section/samples/C#4_ff.mp3",
"root_key": 61,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 61,
"key_max": 61
},
{
"file_path": "../trumpet-section/samples/D4.mp3",
"root_key": 62,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 62,
"key_max": 62
},
{
"file_path": "../trombone-section/samples/Eb4.mp3",
"root_key": 63,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 63,
"key_max": 63
},
{
"file_path": "../horn-section/samples/E4_ff.mp3",
"root_key": 64,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 64,
"key_max": 64
},
{
"file_path": "../trumpet-section/samples/E4.mp3",
"root_key": 64,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 65,
"key_max": 64
},
{
"file_path": "../trumpet-section/samples/F4.mp3",
"root_key": 65,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 65,
"key_max": 65
},
{
"file_path": "../trombone-section/samples/Gb4.mp3",
"root_key": 66,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 66,
"key_max": 66
},
{
"file_path": "../horn-section/samples/G4_ff.mp3",
"root_key": 67,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 67,
"key_max": 67
},
{
"file_path": "../trumpet-section/samples/G4.mp3",
"root_key": 67,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 68,
"key_max": 68
},
{
"file_path": "../trombone-section/samples/A4.mp3",
"root_key": 69,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 69,
"key_max": 69
},
{
"file_path": "../trumpet-section/samples/A4.mp3",
"root_key": 69,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 70,
"key_max": 69
},
{
"file_path": "../horn-section/samples/A#4_ff.mp3",
"root_key": 70,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 70,
"key_max": 70
},
{
"file_path": "../trumpet-section/samples/Bb4.mp3",
"root_key": 70,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 71,
"key_max": 71
},
{
"file_path": "../trombone-section/samples/C5.mp3",
"root_key": 72,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 72,
"key_max": 72
},
{
"file_path": "../trumpet-section/samples/C5.mp3",
"root_key": 72,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 73,
"key_max": 72
},
{
"file_path": "../horn-section/samples/C#5_ff.mp3",
"root_key": 73,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 73,
"key_max": 73
},
{
"file_path": "../trumpet-section/samples/D5.mp3",
"root_key": 74,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 74,
"key_max": 74
},
{
"file_path": "../trumpet-section/samples/Eb5.mp3",
"root_key": 75,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 75,
"key_max": 75
},
{
"file_path": "../horn-section/samples/E5_ff.mp3",
"root_key": 76,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 76,
"key_max": 76
},
{
"file_path": "../trumpet-section/samples/F5.mp3",
"root_key": 77,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 77,
"key_max": 78
},
{
"file_path": "../trumpet-section/samples/G5.mp3",
"root_key": 79,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 79,
"key_max": 80
},
{
"file_path": "../trumpet-section/samples/A5.mp3",
"root_key": 81,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 81,
"key_max": 82
},
{
"file_path": "../trumpet-section/samples/B5.mp3",
"root_key": 83,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 83,
"key_max": 84
},
{
"file_path": "../trumpet-section/samples/Db6.mp3",
"root_key": 85,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 85,
"key_max": 127
},
{
"file_path": "../horn-section/samples/E2_mp.mp3",
"root_key": 40,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 0,
"key_max": 40
},
{
"file_path": "../trombone-section/samples/F2_p.mp3",
"root_key": 41,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 41,
"key_max": 42
},
{
"file_path": "../horn-section/samples/G2_mp.mp3",
"root_key": 43,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 43,
"key_max": 44
},
{
"file_path": "../trombone-section/samples/Bb2_p.mp3",
"root_key": 46,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 45,
"key_max": 46
},
{
"file_path": "../horn-section/samples/A#2_mp.mp3",
"root_key": 46,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 47,
"key_max": 47
},
{
"file_path": "../horn-section/samples/C#3_mp.mp3",
"root_key": 49,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 48,
"key_max": 50
},
{
"file_path": "../trombone-section/samples/Eb3_p.mp3",
"root_key": 51,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 51,
"key_max": 51
},
{
"file_path": "../horn-section/samples/E3_mp.mp3",
"root_key": 52,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 52,
"key_max": 53
},
{
"file_path": "../horn-section/samples/G3_mp.mp3",
"root_key": 55,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 54,
"key_max": 55
},
{
"file_path": "../trombone-section/samples/Ab3_p.mp3",
"root_key": 56,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 56,
"key_max": 57
},
{
"file_path": "../horn-section/samples/A#3_mp.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 58,
"key_max": 58
},
{
"file_path": "../trumpet-section/samples/Bb3_p.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 59,
"key_max": 59
},
{
"file_path": "../trombone-section/samples/Db4_p.mp3",
"root_key": 61,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 60,
"key_max": 61
},
{
"file_path": "../horn-section/samples/C#4_mp.mp3",
"root_key": 61,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 62,
"key_max": 62
},
{
"file_path": "../trumpet-section/samples/Eb4_p.mp3",
"root_key": 63,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 63,
"key_max": 63
},
{
"file_path": "../horn-section/samples/E4_mp.mp3",
"root_key": 64,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 64,
"key_max": 65
},
{
"file_path": "../trombone-section/samples/Gb4_p.mp3",
"root_key": 66,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 66,
"key_max": 66
},
{
"file_path": "../horn-section/samples/G4_mp.mp3",
"root_key": 67,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 67,
"key_max": 67
},
{
"file_path": "../trumpet-section/samples/Ab4_p.mp3",
"root_key": 68,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 68,
"key_max": 69
},
{
"file_path": "../horn-section/samples/A#4_mp.mp3",
"root_key": 70,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 70,
"key_max": 70
},
{
"file_path": "../trombone-section/samples/B4_p.mp3",
"root_key": 71,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 71,
"key_max": 72
},
{
"file_path": "../horn-section/samples/C#5_mp.mp3",
"root_key": 73,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 73,
"key_max": 73
},
{
"file_path": "../trumpet-section/samples/Db5_p.mp3",
"root_key": 73,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 74,
"key_max": 74
},
{
"file_path": "../horn-section/samples/E5_mp.mp3",
"root_key": 76,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 75,
"key_max": 78
},
{
"file_path": "../trumpet-section/samples/Ab5_p.mp3",
"root_key": 80,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 79,
"key_max": 127
}
]
},
"position": [
350.0,
0.0
]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [
700.0,
100.0
]
}
],
"connections": [
{
"from_node": 0,
"from_port": 0,
"to_node": 1,
"to_port": 0
},
{
"from_node": 1,
"from_port": 0,
"to_node": 2,
"to_port": 0
}
]
}

View File

@ -0,0 +1,309 @@
{
"metadata": {
"name": "Horn Section",
"description": "French horn section with forte and mezzo-piano dynamics",
"author": "Virtual Playing Orchestra 3",
"version": 1,
"tags": [
"brass",
"horn",
"french horn",
"section",
"orchestral"
]
},
"midi_targets": [
0
],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [
100.0,
100.0
]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": "Horn Section Sampler",
"parameters": {
"0": 1.0,
"1": 0.04,
"2": 0.4,
"3": 0.0
},
"sample_data": {
"type": "multi_sampler",
"layers": [
{
"file_path": "samples/E2_ff.mp3",
"root_key": 40,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 0,
"key_max": 41
},
{
"file_path": "samples/G2_ff.mp3",
"root_key": 43,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 42,
"key_max": 44
},
{
"file_path": "samples/A#2_ff.mp3",
"root_key": 46,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 45,
"key_max": 47
},
{
"file_path": "samples/C#3_ff.mp3",
"root_key": 49,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 48,
"key_max": 50
},
{
"file_path": "samples/E3_ff.mp3",
"root_key": 52,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 51,
"key_max": 53
},
{
"file_path": "samples/G3_ff.mp3",
"root_key": 55,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 54,
"key_max": 56
},
{
"file_path": "samples/A#3_ff.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 57,
"key_max": 59
},
{
"file_path": "samples/C#4_ff.mp3",
"root_key": 61,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 60,
"key_max": 62
},
{
"file_path": "samples/E4_ff.mp3",
"root_key": 64,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 63,
"key_max": 65
},
{
"file_path": "samples/G4_ff.mp3",
"root_key": 67,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 66,
"key_max": 68
},
{
"file_path": "samples/A#4_ff.mp3",
"root_key": 70,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 69,
"key_max": 71
},
{
"file_path": "samples/C#5_ff.mp3",
"root_key": 73,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 72,
"key_max": 74
},
{
"file_path": "samples/E5_ff.mp3",
"root_key": 76,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 75,
"key_max": 127
},
{
"file_path": "samples/E2_mp.mp3",
"root_key": 40,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 0,
"key_max": 41
},
{
"file_path": "samples/G2_mp.mp3",
"root_key": 43,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 42,
"key_max": 44
},
{
"file_path": "samples/A#2_mp.mp3",
"root_key": 46,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 45,
"key_max": 47
},
{
"file_path": "samples/C#3_mp.mp3",
"root_key": 49,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 48,
"key_max": 50
},
{
"file_path": "samples/E3_mp.mp3",
"root_key": 52,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 51,
"key_max": 53
},
{
"file_path": "samples/G3_mp.mp3",
"root_key": 55,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 54,
"key_max": 56
},
{
"file_path": "samples/A#3_mp.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 57,
"key_max": 59
},
{
"file_path": "samples/C#4_mp.mp3",
"root_key": 61,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 60,
"key_max": 62
},
{
"file_path": "samples/E4_mp.mp3",
"root_key": 64,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 63,
"key_max": 65
},
{
"file_path": "samples/G4_mp.mp3",
"root_key": 67,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 66,
"key_max": 68
},
{
"file_path": "samples/A#4_mp.mp3",
"root_key": 70,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 69,
"key_max": 71
},
{
"file_path": "samples/C#5_mp.mp3",
"root_key": 73,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 72,
"key_max": 74
},
{
"file_path": "samples/E5_mp.mp3",
"root_key": 76,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 75,
"key_max": 127
}
]
},
"position": [
350.0,
0.0
]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [
700.0,
100.0
]
}
],
"connections": [
{
"from_node": 0,
"from_port": 0,
"to_node": 1,
"to_port": 0
},
{
"from_node": 1,
"from_port": 0,
"to_node": 2,
"to_port": 0
}
]
}

View File

@ -0,0 +1,236 @@
{
"metadata": {
"name": "Trombone Section",
"description": "Orchestral trombone section with piano and forte dynamics",
"author": "Virtual Playing Orchestra 3",
"version": 1,
"tags": [
"brass",
"trombone",
"section",
"orchestral"
]
},
"midi_targets": [
0
],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [
100.0,
100.0
]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": "Trombone Section Sampler",
"parameters": {
"0": 1.0,
"1": 0.03,
"2": 0.35,
"3": 0.0
},
"sample_data": {
"type": "multi_sampler",
"layers": [
{
"file_path": "samples/F2.mp3",
"root_key": 41,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 0,
"key_max": 43
},
{
"file_path": "samples/A2.mp3",
"root_key": 45,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 44,
"key_max": 46
},
{
"file_path": "samples/C3.mp3",
"root_key": 48,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 47,
"key_max": 49
},
{
"file_path": "samples/Eb3.mp3",
"root_key": 51,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 50,
"key_max": 52
},
{
"file_path": "samples/Gb3.mp3",
"root_key": 54,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 53,
"key_max": 55
},
{
"file_path": "samples/A3.mp3",
"root_key": 57,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 56,
"key_max": 58
},
{
"file_path": "samples/C4.mp3",
"root_key": 60,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 59,
"key_max": 61
},
{
"file_path": "samples/Eb4.mp3",
"root_key": 63,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 62,
"key_max": 64
},
{
"file_path": "samples/Gb4.mp3",
"root_key": 66,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 65,
"key_max": 67
},
{
"file_path": "samples/A4.mp3",
"root_key": 69,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 68,
"key_max": 70
},
{
"file_path": "samples/C5.mp3",
"root_key": 72,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 71,
"key_max": 127
},
{
"file_path": "samples/F2_p.mp3",
"root_key": 41,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 0,
"key_max": 43
},
{
"file_path": "samples/Bb2_p.mp3",
"root_key": 46,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 44,
"key_max": 48
},
{
"file_path": "samples/Eb3_p.mp3",
"root_key": 51,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 49,
"key_max": 53
},
{
"file_path": "samples/Ab3_p.mp3",
"root_key": 56,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 54,
"key_max": 58
},
{
"file_path": "samples/Db4_p.mp3",
"root_key": 61,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 59,
"key_max": 63
},
{
"file_path": "samples/Gb4_p.mp3",
"root_key": 66,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 64,
"key_max": 68
},
{
"file_path": "samples/B4_p.mp3",
"root_key": 71,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 69,
"key_max": 127
}
]
},
"position": [
350.0,
0.0
]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [
700.0,
100.0
]
}
],
"connections": [
{
"from_node": 0,
"from_port": 0,
"to_node": 1,
"to_port": 0
},
{
"from_node": 1,
"from_port": 0,
"to_node": 2,
"to_port": 0
}
]
}

View File

@ -0,0 +1,281 @@
{
"metadata": {
"name": "Trumpet Section",
"description": "Orchestral trumpet section with piano and forte dynamics",
"author": "Virtual Playing Orchestra 3",
"version": 1,
"tags": [
"brass",
"trumpet",
"section",
"orchestral"
]
},
"midi_targets": [
0
],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [
100.0,
100.0
]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": "Trumpet Section Sampler",
"parameters": {
"0": 1.0,
"1": 0.02,
"2": 0.3,
"3": 0.0
},
"sample_data": {
"type": "multi_sampler",
"layers": [
{
"file_path": "samples/G3.mp3",
"root_key": 55,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 0,
"key_max": 55
},
{
"file_path": "samples/Ab3.mp3",
"root_key": 56,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 56,
"key_max": 57
},
{
"file_path": "samples/Bb3.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 58,
"key_max": 59
},
{
"file_path": "samples/C4.mp3",
"root_key": 60,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 60,
"key_max": 61
},
{
"file_path": "samples/D4.mp3",
"root_key": 62,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 62,
"key_max": 63
},
{
"file_path": "samples/E4.mp3",
"root_key": 64,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 64,
"key_max": 64
},
{
"file_path": "samples/F4.mp3",
"root_key": 65,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 65,
"key_max": 66
},
{
"file_path": "samples/G4.mp3",
"root_key": 67,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 67,
"key_max": 68
},
{
"file_path": "samples/A4.mp3",
"root_key": 69,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 69,
"key_max": 69
},
{
"file_path": "samples/Bb4.mp3",
"root_key": 70,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 70,
"key_max": 71
},
{
"file_path": "samples/C5.mp3",
"root_key": 72,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 72,
"key_max": 73
},
{
"file_path": "samples/D5.mp3",
"root_key": 74,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 74,
"key_max": 74
},
{
"file_path": "samples/Eb5.mp3",
"root_key": 75,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 75,
"key_max": 76
},
{
"file_path": "samples/F5.mp3",
"root_key": 77,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 77,
"key_max": 78
},
{
"file_path": "samples/G5.mp3",
"root_key": 79,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 79,
"key_max": 80
},
{
"file_path": "samples/A5.mp3",
"root_key": 81,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 81,
"key_max": 82
},
{
"file_path": "samples/B5.mp3",
"root_key": 83,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 83,
"key_max": 84
},
{
"file_path": "samples/Db6.mp3",
"root_key": 85,
"loop_mode": "continuous",
"velocity_min": 64,
"velocity_max": 127,
"key_min": 85,
"key_max": 127
},
{
"file_path": "samples/Bb3_p.mp3",
"root_key": 58,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 0,
"key_max": 60
},
{
"file_path": "samples/Eb4_p.mp3",
"root_key": 63,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 61,
"key_max": 65
},
{
"file_path": "samples/Ab4_p.mp3",
"root_key": 68,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 66,
"key_max": 70
},
{
"file_path": "samples/Db5_p.mp3",
"root_key": 73,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 71,
"key_max": 76
},
{
"file_path": "samples/Ab5_p.mp3",
"root_key": 80,
"loop_mode": "continuous",
"velocity_min": 0,
"velocity_max": 63,
"key_min": 77,
"key_max": 127
}
]
},
"position": [
350.0,
0.0
]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [
700.0,
100.0
]
}
],
"connections": [
{
"from_node": 0,
"from_port": 0,
"to_node": 1,
"to_port": 0
},
{
"from_node": 1,
"from_port": 0,
"to_node": 2,
"to_port": 0
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,154 @@
{
"metadata": {
"name": "Tuba",
"description": "Orchestral tuba",
"author": "Virtual Playing Orchestra 3",
"version": 1,
"tags": [
"brass",
"tuba",
"orchestral"
]
},
"midi_targets": [
0
],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [
100.0,
100.0
]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": "Tuba Sampler",
"parameters": {
"0": 1.0,
"1": 0.04,
"2": 0.4,
"3": 0.0
},
"sample_data": {
"type": "multi_sampler",
"layers": [
{
"file_path": "samples/E1.mp3",
"root_key": 28,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 0,
"key_max": 29
},
{
"file_path": "samples/G1.mp3",
"root_key": 31,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 30,
"key_max": 32
},
{
"file_path": "samples/A#1.mp3",
"root_key": 34,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 33,
"key_max": 35
},
{
"file_path": "samples/C#2.mp3",
"root_key": 37,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 36,
"key_max": 38
},
{
"file_path": "samples/E2.mp3",
"root_key": 40,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 39,
"key_max": 41
},
{
"file_path": "samples/G2.mp3",
"root_key": 43,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 42,
"key_max": 44
},
{
"file_path": "samples/A#2.mp3",
"root_key": 46,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 45,
"key_max": 47
},
{
"file_path": "samples/C#3.mp3",
"root_key": 49,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 48,
"key_max": 50
},
{
"file_path": "samples/E3.mp3",
"root_key": 52,
"velocity_min": 0,
"velocity_max": 127,
"loop_mode": "continuous",
"key_min": 51,
"key_max": 127
}
]
},
"position": [
350.0,
0.0
]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [
700.0,
100.0
]
}
],
"connections": [
{
"from_node": 0,
"from_port": 0,
"to_node": 1,
"to_port": 0
},
{
"from_node": 1,
"from_port": 0,
"to_node": 2,
"to_port": 0
}
]
}

View File

@ -0,0 +1,346 @@
{
"metadata": {
"name": "Drum Kit",
"description": "Acoustic drum kit (Salamander) \u2014 GM-compatible MIDI mapping",
"author": "Salamander Drumkit (Public Domain)",
"version": 1,
"tags": [
"drums",
"percussion",
"kit",
"acoustic"
]
},
"midi_targets": [
0
],
"output_node": 2,
"nodes": [
{
"id": 0,
"node_type": "MidiInput",
"name": "MIDI In",
"parameters": {},
"position": [
100.0,
100.0
]
},
{
"id": 1,
"node_type": "MultiSampler",
"name": "Drum Kit Sampler",
"parameters": {
"0": 1.0,
"1": 0.001,
"2": 0.5,
"3": 0.0
},
"sample_data": {
"type": "multi_sampler",
"layers": [
{
"file_path": "samples/cowbell_p.mp3",
"root_key": 56,
"velocity_min": 0,
"velocity_max": 41,
"key_min": 56,
"key_max": 56
},
{
"file_path": "samples/cowbell_mp.mp3",
"root_key": 56,
"velocity_min": 42,
"velocity_max": 83,
"key_min": 56,
"key_max": 56
},
{
"file_path": "samples/cowbell_ff.mp3",
"root_key": 56,
"velocity_min": 84,
"velocity_max": 127,
"key_min": 56,
"key_max": 56
},
{
"file_path": "samples/crash1_p.mp3",
"root_key": 49,
"velocity_min": 0,
"velocity_max": 63,
"key_min": 49,
"key_max": 49
},
{
"file_path": "samples/crash1_ff.mp3",
"root_key": 49,
"velocity_min": 64,
"velocity_max": 127,
"key_min": 49,
"key_max": 49
},
{
"file_path": "samples/crash2_p.mp3",
"root_key": 57,
"velocity_min": 0,
"velocity_max": 63,
"key_min": 57,
"key_max": 57
},
{
"file_path": "samples/crash2_ff.mp3",
"root_key": 57,
"velocity_min": 64,
"velocity_max": 127,
"key_min": 57,
"key_max": 57
},
{
"file_path": "samples/hiTom_p.mp3",
"root_key": 50,
"velocity_min": 0,
"velocity_max": 41,
"key_min": 50,
"key_max": 50
},
{
"file_path": "samples/hiTom_f.mp3",
"root_key": 50,
"velocity_min": 42,
"velocity_max": 83,
"key_min": 50,
"key_max": 50
},
{
"file_path": "samples/hiTom_ff.mp3",
"root_key": 50,
"velocity_min": 84,
"velocity_max": 127,
"key_min": 50,
"key_max": 50
},
{
"file_path": "samples/hihatClosed_p.mp3",
"root_key": 42,
"velocity_min": 0,
"velocity_max": 63,
"key_min": 42,
"key_max": 42
},
{
"file_path": "samples/hihatClosed_f.mp3",
"root_key": 42,
"velocity_min": 64,
"velocity_max": 127,
"key_min": 42,
"key_max": 42
},
{
"file_path": "samples/hihatFoot_mp.mp3",
"root_key": 44,
"velocity_min": 0,
"velocity_max": 127,
"key_min": 44,
"key_max": 44
},
{
"file_path": "samples/hihatOpen_p.mp3",
"root_key": 46,
"velocity_min": 0,
"velocity_max": 41,
"key_min": 46,
"key_max": 46
},
{
"file_path": "samples/hihatOpen_f.mp3",
"root_key": 46,
"velocity_min": 42,
"velocity_max": 83,
"key_min": 46,
"key_max": 46
},
{
"file_path": "samples/hihatOpen_ff.mp3",
"root_key": 46,
"velocity_min": 84,
"velocity_max": 127,
"key_min": 46,
"key_max": 46
},
{
"file_path": "samples/kick_p.mp3",
"root_key": 36,
"velocity_min": 0,
"velocity_max": 41,
"key_min": 36,
"key_max": 36
},
{
"file_path": "samples/kick_f.mp3",
"root_key": 36,
"velocity_min": 42,
"velocity_max": 83,
"key_min": 36,
"key_max": 36
},
{
"file_path": "samples/kick_ff.mp3",
"root_key": 36,
"velocity_min": 84,
"velocity_max": 127,
"key_min": 36,
"key_max": 36
},
{
"file_path": "samples/loTom_pp.mp3",
"root_key": 45,
"velocity_min": 0,
"velocity_max": 41,
"key_min": 45,
"key_max": 45
},
{
"file_path": "samples/loTom_mp.mp3",
"root_key": 45,
"velocity_min": 42,
"velocity_max": 83,
"key_min": 45,
"key_max": 45
},
{
"file_path": "samples/loTom_ff.mp3",
"root_key": 45,
"velocity_min": 84,
"velocity_max": 127,
"key_min": 45,
"key_max": 45
},
{
"file_path": "samples/ride1Bell_f.mp3",
"root_key": 53,
"velocity_min": 0,
"velocity_max": 127,
"key_min": 53,
"key_max": 53
},
{
"file_path": "samples/ride1_mp.mp3",
"root_key": 51,
"velocity_min": 0,
"velocity_max": 63,
"key_min": 51,
"key_max": 51
},
{
"file_path": "samples/ride1_ff.mp3",
"root_key": 51,
"velocity_min": 64,
"velocity_max": 127,
"key_min": 51,
"key_max": 51
},
{
"file_path": "samples/snareOFF_p.mp3",
"root_key": 40,
"velocity_min": 0,
"velocity_max": 63,
"key_min": 40,
"key_max": 40
},
{
"file_path": "samples/snareOFF_f.mp3",
"root_key": 40,
"velocity_min": 64,
"velocity_max": 127,
"key_min": 40,
"key_max": 40
},
{
"file_path": "samples/snareStick_f.mp3",
"root_key": 37,
"velocity_min": 0,
"velocity_max": 127,
"key_min": 37,
"key_max": 37
},
{
"file_path": "samples/snare_ghost.mp3",
"root_key": 38,
"velocity_min": 0,
"velocity_max": 31,
"key_min": 38,
"key_max": 38
},
{
"file_path": "samples/snare_mp.mp3",
"root_key": 38,
"velocity_min": 32,
"velocity_max": 63,
"key_min": 38,
"key_max": 38
},
{
"file_path": "samples/snare_f.mp3",
"root_key": 38,
"velocity_min": 64,
"velocity_max": 95,
"key_min": 38,
"key_max": 38
},
{
"file_path": "samples/snare_ff.mp3",
"root_key": 38,
"velocity_min": 96,
"velocity_max": 127,
"key_min": 38,
"key_max": 38
},
{
"file_path": "samples/splash1_p.mp3",
"root_key": 55,
"velocity_min": 0,
"velocity_max": 63,
"key_min": 55,
"key_max": 55
},
{
"file_path": "samples/splash1_f.mp3",
"root_key": 55,
"velocity_min": 64,
"velocity_max": 127,
"key_min": 55,
"key_max": 55
}
]
},
"position": [
350.0,
0.0
]
},
{
"id": 2,
"node_type": "AudioOutput",
"name": "Out",
"parameters": {},
"position": [
700.0,
100.0
]
}
],
"connections": [
{
"from_node": 0,
"from_port": 0,
"to_node": 1,
"to_port": 0
},
{
"from_node": 1,
"from_port": 0,
"to_node": 2,
"to_port": 0
}
]
}

Some files were not shown because too many files have changed in this diff Show More