memlnaut-nisps/playground/js/midi/midi-cc-map.js

105 lines
3.2 KiB
JavaScript
Raw Normal View History

2026-03-30 16:05:45 +02:00
// MIDI CC Map — user-defined CC parameter definitions
// Each param maps one MLP output to a MIDI CC message.
const STORAGE_KEY = 'nisps-midi-cc-map';
// Default starter set — common CC numbers
const DEFAULT_CC_MAP = [
{ name: 'Cutoff', cc: 74, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Resonance', cc: 71, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Attack', cc: 73, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Release', cc: 72, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Mod Wheel', cc: 1, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Volume', cc: 7, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Pan', cc: 10, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
{ name: 'Expression',cc: 11, channel: 1, min: 0, max: 1, curve: 0.5, muted: false, fixedValue: 0.5 },
];
// Well-known CC names for auto-labeling
const CC_NAMES = {
1: 'Mod Wheel', 2: 'Breath', 5: 'Portamento Time', 7: 'Volume', 10: 'Pan',
11: 'Expression', 64: 'Sustain', 65: 'Portamento', 66: 'Sostenuto',
67: 'Soft Pedal', 70: 'Sound Variation', 71: 'Resonance', 72: 'Release',
73: 'Attack', 74: 'Cutoff', 75: 'Decay', 76: 'Vib Rate', 77: 'Vib Depth',
78: 'Vib Delay', 91: 'Reverb', 92: 'Tremolo', 93: 'Chorus', 94: 'Detune',
95: 'Phaser',
};
/**
* Create a fresh CC param with defaults.
* @param {number} [cc=74] CC number
* @param {number} [channel=1] MIDI channel 1-16
* @returns {object} CC param definition
*/
export function createCCParam(cc = 74, channel = 1) {
return {
name: CC_NAMES[cc] || `CC ${cc}`,
cc,
channel,
min: 0,
max: 1,
curve: 0.5,
muted: false,
fixedValue: 0.5,
};
}
/**
* Load CC map from localStorage, or return default.
* @returns {Array} CC param definitions
*/
export function loadCCMap() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const saved = JSON.parse(raw);
if (Array.isArray(saved) && saved.length > 0) return saved;
}
} catch (e) {
console.warn('[MIDI CC] Failed to load map:', e);
}
return DEFAULT_CC_MAP.map(p => ({ ...p }));
}
/**
* Save CC map to localStorage.
* @param {Array} ccMap
*/
export function saveCCMap(ccMap) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(ccMap));
} catch (e) {
console.warn('[MIDI CC] Failed to save map:', e);
}
}
/**
* Export CC map as JSON string (for sharing).
* @param {Array} ccMap
* @returns {string}
*/
export function exportCCMap(ccMap) {
return JSON.stringify(ccMap, null, 2);
}
/**
* Import CC map from JSON string.
* @param {string} json
* @returns {Array|null} parsed map or null on failure
*/
export function importCCMap(json) {
try {
const parsed = JSON.parse(json);
if (!Array.isArray(parsed) || parsed.length === 0) return null;
// Validate shape
for (const p of parsed) {
if (typeof p.cc !== 'number' || typeof p.channel !== 'number') return null;
}
return parsed;
} catch (e) {
return null;
}
}
export { CC_NAMES, DEFAULT_CC_MAP };