PageSourceSearch

https://pupwife.com/scripts/daw/soundfonts.js

js pupwife.com collected 2026-09-30 15:06:39 UTC 16,234 bytes, 404 lines download raw bytes

1// ============================================
2// SOUNDFONTS - Instrument Sound Generation
3// ============================================
4// Handles different instrument soundfonts and their synthesis
5
6// Note: SF2Loader import is now dynamic (in initSF2Loader) to support FluidSynth
7// The old SF2Loader (sf2synth.js) is being replaced with FluidSynthLoader (js-synthesizer)
8
9import { registerSynthPresets, normalizeSynthPreset } from './synthPresets.js';
10
11export class Soundfonts {
12    constructor(audioContext, masterGain) {
13        this.audioContext = audioContext;
14        this.masterGain = masterGain;
15        this.currentSoundfont = 'synthesizer';
16        this.soundfontSettings = {};
17        this.percussionMap = new Map();
18        this.sf2Loader = null; // Will be initialized if SF2 support is available
19        this.initPercussionMap();
20        this.initSoundfontPresets();
21        this.initSF2Loader();
22    }
23    
24    // Initialize SF2 loader if available
25    initSF2Loader() {
26        try {
27            // Wait a bit for the library to load (it loads asynchronously)
28            setTimeout(() => {
29                // Check if FluidSynth (js-synthesizer) is available
30                // It might be exposed as Synthesizer or JSSynth.Synthesizer
31                const hasSynthesizer = (typeof Synthesizer !== 'undefined' && Synthesizer !== null) ||
32                                     (typeof window.JSSynth !== 'undefined' && window.JSSynth && window.JSSynth.Synthesizer);
33                
34                if (hasSynthesizer) {
35                    // Import FluidSynthLoader dynamically
36                    import('./fluidsynth-loader.js').then(module => {
37                        this.sf2Loader = new module.FluidSynthLoader(this.audioContext, this.masterGain);
38                        console.log('✓ SF2 loader initialized successfully (using FluidSynth/js-synthesizer)');
39                        
40                        // Dispatch event to notify that SF2 loader is ready
41                        window.dispatchEvent(new CustomEvent('sf2LoaderReady'));
42                    }).catch(error => {
43                        console.error('Failed to load FluidSynth loader module:', error);
44                        this.sf2Loader = null;
45                    });
46                } else {
47                    console.warn('⚠ FluidSynth (js-synthesizer) not available - SF2 soundfonts will be disabled');
48                    console.warn('Synthesizer type:', typeof Synthesizer, 'JSSynth type:', typeof window.JSSynth);
49                    console.warn('Please ensure js-synthesizer library is loaded in daw.html');
50                    console.warn('Download from: https://github.com/jet2jet/js-synthesizer');
51                    console.warn('Place libfluidsynth-*.js and js-synthesizer.js in scripts/daw/libs/');
52                    console.warn('SF2 features will be disabled - only synthesized sounds will work');
53                    this.sf2Loader = null; // Explicitly set to null
54                }
55            }, 1500); // Wait 1.5 seconds for async library loading
56        } catch (error) {
57            console.error('Error initializing SF2 loader:', error);
58            this.sf2Loader = null;
59        }
60    }
61    
62    // Initialize percussion mapping (MIDI note -> drum sound name)
63    initPercussionMap() {
64        // Standard General MIDI drum mapping
65        this.percussionMap.set(36, 'kick');      // C1 - Kick Drum
66        this.percussionMap.set(38, 'snare');     // D1 - Snare Drum
67        this.percussionMap.set(40, 'snare');     // E1 - Electric Snare
68        this.percussionMap.set(42, 'hihat');     // F#1 - Hi-Hat Closed
69        this.percussionMap.set(44, 'hihat');     // G#1 - Hi-Hat Pedal
70        this.percussionMap.set(46, 'hihat');     // A#1 - Hi-Hat Open
71        this.percussionMap.set(47, 'tom');       // B1 - Low-Mid Tom
72        this.percussionMap.set(48, 'tom');       // C2 - Hi-Mid Tom
73        this.percussionMap.set(50, 'tom');       // D2 - High Tom
74        this.percussionMap.set(49, 'crash');     // C#2 - Crash Cymbal 1
75        this.percussionMap.set(57, 'crash');     // A2 - Crash Cymbal 2
76        this.percussionMap.set(51, 'ride');      // D#2 - Ride Cymbal 1
77        this.percussionMap.set(59, 'ride');       // B2 - Ride Cymbal 2
78        this.percussionMap.set(52, 'china');      // E2 - Chinese Cymbal
79        this.percussionMap.set(55, 'splash');     // G2 - Splash Cymbal
80        this.percussionMap.set(56, 'cowbell');    // G#2 - Cowbell
81        this.percussionMap.set(54, 'tambourine'); // F#2 - Tambourine
82        this.percussionMap.set(39, 'clap');       // D#1 - Hand Clap
83    }
84    
85    // Initialize soundfont presets with default settings
86    initSoundfontPresets() {
87        // Synthesizer (default / manual)
88        this.soundfontSettings.synthesizer = normalizeSynthPreset({
89            type: 'synthesizer',
90            waveform: 'sine',
91            pulseWidth: 0.5,
92            attack: 0.01,
93            decay: 0.1,
94            sustain: 0.7,
95            release: 0.3
96        });
97
98        registerSynthPresets(this.soundfontSettings);
99        
100        // Brass instruments - made more distinct
101        this.soundfontSettings['brass-trumpet'] = {
102            type: 'brass',
103            instrument: 'trumpet',
104            waveform: 'sawtooth', // Bright, piercing
105            brightness: 0.9,  // Very bright, piercing
106            attack: 0.02,      // Quick attack
107            decay: 0.08,
108            sustain: 0.85,
109            release: 0.15,     // Quick release
110            vibratoRate: 6,    // Faster vibrato
111            vibratoDepth: 0.15 // More vibrato
112        };
113        
114        this.soundfontSettings['brass-french-horn'] = {
115            type: 'brass',
116            instrument: 'french-horn',
117            waveform: 'triangle', // Mellow, warm
118            brightness: 0.4,  // Mellow, warm
119            attack: 0.12,      // Slower attack
120            decay: 0.2,
121            sustain: 0.7,
122            release: 0.4,      // Longer release
123            vibratoRate: 3.5,  // Slower vibrato
124            vibratoDepth: 0.06 // Subtle vibrato
125        };
126        
127        this.soundfontSettings['brass-trombone'] = {
128            type: 'brass',
129            instrument: 'trombone',
130            waveform: 'square', // Medium brightness, distinct
131            brightness: 0.65, // Medium brightness
132            attack: 0.04,      // Medium attack
133            decay: 0.1,
134            sustain: 0.82,
135            release: 0.22,     // Medium release
136            vibratoRate: 4,    // Medium vibrato rate
137            vibratoDepth: 0.12 // Medium vibrato depth
138        };
139        
140        this.soundfontSettings['brass-tuba'] = {
141            type: 'brass',
142            instrument: 'tuba',
143            waveform: 'sine', // Deep, dark
144            brightness: 0.2,  // Very dark, deep
145            attack: 0.15,      // Very slow attack
146            decay: 0.25,
147            sustain: 0.65,
148            release: 0.5,      // Very long release
149            vibratoRate: 2.5,  // Very slow vibrato
150            vibratoDepth: 0.03 // Very subtle vibrato
151        };
152        
153        // Woodwinds - made more distinct
154        this.soundfontSettings['woodwind-flute'] = {
155            type: 'woodwind',
156            instrument: 'flute',
157            waveform: 'sine', // Very clean, pure
158            breathiness: 0.2,  // Very clean, pure
159            attack: 0.01,       // Very quick attack
160            decay: 0.03,
161            sustain: 0.95,      // Very high sustain
162            release: 0.1,       // Quick release
163            vibratoRate: 7,     // Fast, expressive vibrato
164            vibratoDepth: 0.18  // More vibrato
165        };
166        
167        this.soundfontSettings['woodwind-saxophone'] = {
168            type: 'woodwind',
169            instrument: 'saxophone',
170            waveform: 'sawtooth', // Rich, jazzy
171            breathiness: 0.7,  // Very breathy, jazzy
172            attack: 0.05,       // Slower attack
173            decay: 0.12,
174            sustain: 0.8,
175            release: 0.25,      // Longer release
176            vibratoRate: 4.5,   // Medium-slow vibrato
177            vibratoDepth: 0.2   // Strong vibrato
178        };
179        
180        this.soundfontSettings['woodwind-clarinet'] = {
181            type: 'woodwind',
182            instrument: 'clarinet',
183            waveform: 'triangle', // Smooth, mellow
184            breathiness: 0.5,  // Medium breathiness
185            attack: 0.03,
186            decay: 0.08,
187            sustain: 0.88,
188            release: 0.15,
189            vibratoRate: 5.5,   // Medium vibrato rate
190            vibratoDepth: 0.12  // Medium vibrato
191        };
192        
193        this.soundfontSettings['woodwind-oboe'] = {
194            type: 'woodwind',
195            instrument: 'oboe',
196            waveform: 'square', // Reedy, distinctive
197            breathiness: 0.4,  // Less breathy, more reedy
198            attack: 0.06,       // Slower attack
199            decay: 0.15,
200            sustain: 0.72,
201            release: 0.28,      // Longer release
202            vibratoRate: 7.5,   // Fast vibrato
203            vibratoDepth: 0.16  // Strong vibrato
204        };
205        
206        this.soundfontSettings['woodwind-bassoon'] = {
207            type: 'woodwind',
208            instrument: 'bassoon',
209            waveform: 'triangle', // Deep, warm
210            breathiness: 0.6,  // More breathy, reedy
211            attack: 0.1,        // Slow attack
212            decay: 0.18,
213            sustain: 0.65,
214            release: 0.35,      // Long release
215            vibratoRate: 3.5,   // Slow vibrato
216            vibratoDepth: 0.1   // Subtle vibrato
217        };
218        
219        // Strings - made more distinct
220        this.soundfontSettings['string-violin'] = {
221            type: 'string',
222            instrument: 'violin',
223            waveform: 'sawtooth', // Bright, rich harmonics
224            bowPressure: 0.85, // High pressure, bright
225            attack: 0.005,     // Very quick attack
226            decay: 0.03,
227            sustain: 0.95,      // Very high sustain
228            release: 0.2,       // Quick release
229            vibratoRate: 7,     // Fast vibrato
230            vibratoDepth: 0.25  // Strong vibrato
231        };
232        
233        this.soundfontSettings['string-viola'] = {
234            type: 'string',
235            instrument: 'viola',
236            waveform: 'sawtooth', // Rich, warm
237            bowPressure: 0.7,  // Medium-high pressure
238            attack: 0.015,
239            decay: 0.05,
240            sustain: 0.88,
241            release: 0.28,      // Medium release
242            vibratoRate: 6,     // Medium-fast vibrato
243            vibratoDepth: 0.22  // Strong vibrato
244        };
245        
246        this.soundfontSettings['string-cello'] = {
247            type: 'string',
248            instrument: 'cello',
249            waveform: 'triangle', // Warm, mellow
250            bowPressure: 0.55, // Lower pressure, warmer
251            attack: 0.025,
252            decay: 0.1,
253            sustain: 0.82,
254            release: 0.4,       // Longer release
255            vibratoRate: 4.5,   // Slower vibrato
256            vibratoDepth: 0.18  // Medium vibrato
257        };
258        
259        this.soundfontSettings['string-double-bass'] = {
260            type: 'string',
261            instrument: 'double-bass',
262            waveform: 'sine', // Deep, warm
263            bowPressure: 0.4,  // Low pressure, very warm
264            attack: 0.08,       // Slow attack
265            decay: 0.15,
266            sustain: 0.7,
267            release: 0.5,       // Very long release
268            vibratoRate: 3,     // Slow vibrato
269            vibratoDepth: 0.12  // Subtle vibrato
270        };
271        
272        // Vocal choral - made more distinct
273        const vowels = ['ah', 'oh', 'oo', 'ee'];
274        const genders = ['male', 'female', 'mixed'];
275        const vowelFormants = {
276            'ah': 0.0,
277            'oh': -0.15,
278            'oo': -0.3,
279            'ee': 0.2
280        };
281        
282        genders.forEach(gender => {
283            vowels.forEach(vowel => {
284                const key = `vocal-${gender}-${vowel}`;
285                const baseFormant = gender === 'male' ? -0.25 : (gender === 'female' ? 0.25 : 0);
286                const vowelFormant = vowelFormants[vowel] || 0;
287                
288                this.soundfontSettings[key] = {
289                    type: 'vocal',
290                    gender: gender,
291                    vowel: vowel,
292                    formantShift: baseFormant + vowelFormant,
293                    attack: 0.06,
294                    decay: 0.12,
295                    sustain: 0.88,
296                    release: 0.35,
297                    vibratoRate: gender === 'male' ? 4.5 : (gender === 'female' ? 5.5 : 5),
298                    vibratoDepth: gender === 'male' ? 0.08 : (gender === 'female' ? 0.12 : 0.1)
299                };
300            });
301        });
302        
303        // Percussion
304        this.soundfontSettings['percussion-drums'] = {
305            type: 'percussion',
306            kit: 'standard'
307        };
308    }
309    
310    // Set the current soundfont
311    setSoundfont(soundfontName) {
312        this.currentSoundfont = soundfontName;
313        return this.soundfontSettings[soundfontName] || this.soundfontSettings.synthesizer;
314    }
315    
316    // Get current soundfont settings
317    getCurrentSettings() {
318        return this.soundfontSettings[this.currentSoundfont] || this.soundfontSettings.synthesizer;
319    }
320    
321    // Update a setting for the current soundfont
322    updateSetting(key, value) {
323        const settings = this.getCurrentSettings();
324        if (settings) {
325            settings[key] = value;
326        }
327    }
328    
329    // Get percussion sound name for a MIDI note
330    getPercussionSound(note) {
331        return this.percussionMap.get(note) || 'kick';
332    }
333    
334    // Check if current soundfont is percussion
335    isPercussion() {
336        const settings = this.getCurrentSettings();
337        return settings && settings.type === 'percussion';
338    }
339    
340    // Get all available SF2 soundfonts
341    getAllSF2Soundfonts() {
342        if (!this.sf2Loader) {
343            return [];
344        }
345        return this.sf2Loader.getAllSF2Soundfonts();
346    }
347    
348    // Check if a soundfont is an SF2 file
349    isSF2Soundfont(soundfontName) {
350        if (!this.sf2Loader) {
351            return false;
352        }
353        // Check if it's in the predefined list
354        const sf2List = this.sf2Loader.getAllSF2Soundfonts();
355        if (sf2List.some(sf => sf.displayName === soundfontName)) {
356            return true;
357        }
358        // Also check if it's a custom loaded soundfont
359        if (this.sf2Loader.loadedSoundfonts && this.sf2Loader.loadedSoundfonts.has(soundfontName)) {
360            return true;
361        }
362        return false;
363    }
364    
365    // Load an SF2 soundfont
366    async loadSF2Soundfont(soundfontName) {
367        if (!this.sf2Loader) {
368            console.warn('SF2 loader not available');
369            return false;
370        }
371        try {
372            await this.sf2Loader.loadSoundfont(soundfontName);
373            return true;
374        } catch (error) {
375            console.error(`Error loading SF2 soundfont ${soundfontName}:`, error);
376            return false;
377        }
378    }
379    
380    // Play a note using SF2 soundfont
381    playSF2Note(soundfontName, midiNote, velocity = 127) {
382        if (!this.sf2Loader) {
383            return null;
384        }
385        return this.sf2Loader.playNote(soundfontName, midiNote, velocity);
386    }
387    
388    // Stop a note using SF2 soundfont
389    stopSF2Note(soundfontName, midiNote) {
390        if (!this.sf2Loader) {
391            return;
392        }
393        this.sf2Loader.stopNote(soundfontName, midiNote);
394    }
395    
396    // Check if SF2 soundfont is loaded
397    isSF2Loaded(soundfontName) {
398        if (!this.sf2Loader) {
399            return false;
400        }
401        return this.sf2Loader.isLoaded(soundfontName);
402    }
403}
404

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.