Samplemap and Number of Samples, strange errors
-
I have a strange problem with the Sample Map management but I can't figure out what's going on.
My project is based on this:
https://github.com/christophhart/hise_tutorial/tree/master/CustomSampleImportSome Samples from some Samplemaps I can manage them well, no problem: I see the playhead, I see and edit EditLoop and EditRange correctly. Like This:
With other Samples from other Samplemaps this doesn't happen and the only difference is the velocity structure and the number of samples.
So should I assume that if I have multiple samples per samplemap I can't allow the user to edit them, while if I only have one the user can edit ranges and loops?
-
What I Think Might Be Missing:
- I suspect I need a way to explicitly select which sample in the map I'm working with.
- The
sound
variable needs to be dynamically updated based on the sample selected for editing, but Iām not sure how to implement that effectively.
reference: https://github.com/christophhart/hise_tutorial/tree/master/CustomSampleImport
-
@Mighty23 said in Samplemap and Number of Samples, strange errors:
I suspect I need a way to explicitly select which sample in the map I'm working with.
Yes, Christoph's project just works with a single sample so you will need to change it.
Looks like it's this line - https://github.com/christophhart/hise_tutorial/blob/master/CustomSampleImport/Scripts/SampleLoadSave.js#L81C2-L81C44
So you need to create the desired selection and from that selection pick out the sample you want to work with.
-
@d-healey said in Samplemap and Number of Samples, strange errors:
So you need to create the desired selection and from that selection pick out the sample you want to work with.
This is bad news but at least I know where it comes from, thank you very much for the clarification.
-
Surprisingly I managed to solve it as suggested by David.
(I didn't doubt his abilities, but mine )
I still have some graphical glitches, for example, when I press the note the "needle" jumps forward, but I'm satisfied with the "sound" side.If anyone is interested in teaming up to "clean up" my code, I'd be happy to share the project and the idea for the community.
SampleLoadSave.js
namespace SampleLoadSave { // Store all sounds in the current samplemap reg soundArray = []; // Temporary storage for sample loading reg pendingSample = []; // We'll store the samplemap as base64 into the drop panel SampleDropper.set("saveInPreset", true); /** Function to find the correct sound based on MIDI input */ inline function findSoundForNote(note, velocity) { for(s in soundArray) { if(note >= s.get(Sampler.LoKey) && note <= s.get(Sampler.HiKey) && velocity >= s.get(Sampler.LoVel) && velocity <= s.get(Sampler.HiVel)) { return s; } } return undefined; } /** Function to update the current sound and refresh UI */ inline function updateCurrentSound(newSound) { sound = newSound; totalSamples = sound.getRange(Sampler.SampleEnd)[1]; local xf = sound.get(Sampler.LoopXFade); local xfr = sound.getRange(Sampler.LoopXFade)[1]; local fadeValue = xfr > 0 ? xf / xfr : 0; Content.getComponent("XFade").setValue(fadeValue); Content.getComponent("Loop").setValue(sound.get(Sampler.LoopEnabled)); LoopPointDragger.updateLoopPoints(); } inline function setAndStore(property, value) { if(!isDefined(sound)) return; sound.set(property, value); storeSampleMapData(); } inline function storeSampleMapData() { local v = { "isCustom": isCustomMap, "value": "" }; if(isCustomMap) { if(isDefined(sound)) v.value = Sampler1.getSampleMapAsBase64(); } else v.value = Sampler1.getCurrentSampleMapId(); SampleDropper.setValue(v); } inline function handleNewSampleMapChoice(result) { if(result) { // Create new map Sampler1.loadSampleMapFromJSON(pendingSample); } else { // Add to existing map local currentMap = JSON.parse(Sampler1.getSampleMapAsJSON()); currentMap.push(pendingSample[0]); Sampler1.loadSampleMapFromJSON(currentMap); } pendingSample = []; // Clear pending sample } inline function loadSample(file) { pendingSample = [Sampler1.parseSampleFile(file)]; if (soundArray.length == 0) { Sampler1.loadSampleMapFromJSON(pendingSample); pendingSample = []; // Clear pending sample return; } Engine.showYesNoWindow("New Samplemap?", "Do you want to create a new samplemap? Click No to add to existing samples.", handleNewSampleMapChoice); } inline function initAfterSampleLoad() { local id = Sampler1.getCurrentSampleMapId(); isCustomMap = id == "CustomJSON"; if(isCustomMap || id.length == 0) SampleMapLoader.setValue(0); else SampleMapLoader.setValue(Sampler.getSampleMapList().indexOf(id) + 1); soundArray = Sampler1.createSelection(".*"); if(soundArray.length == 0) { sound = undefined; totalSamples = 0; LoopPointDragger.updateLoopPoints(); storeSampleMapData(); return; } sound = soundArray[0]; totalSamples = sound.getRange(Sampler.SampleEnd)[1]; local xf = sound.get(Sampler.LoopXFade); local xfr = sound.getRange(Sampler.LoopXFade)[1]; local fadeValue = xfr > 0 ? xf / xfr : 0; Content.getComponent("XFade").setValue(fadeValue); Content.getComponent("Loop").setValue(sound.get(Sampler.LoopEnabled)); LoopPointDragger.updateLoopPoints(); storeSampleMapData(); } inline function onSampleDropperControl(component, value) { if(isDefined(value.value)) { if(!value.isCustom) { if(value.value.length == 0) Sampler1.clearSampleMap(); else Sampler1.loadSampleMap(value.value); } else Sampler1.loadSampleMapFromBase64(value.value); } } SampleDropper.setMouseCallback(function(event) { if(event.doubleClick) { Sampler1.clearSampleMap(); return; } this.data.hover = event.hover; if(event.rightClick && event.clicked) FileSystem.browse(FileSystem.Samples, false, "*.wav", loadSample); this.repaint(); }); SampleDropper.setLoadingCallback(function(isPreloading) { if(!isPreloading) initAfterSampleLoad(); }); SampleDropper.setFileDropCallback("Drop & Hover", "*.wav", function(obj) { this.data.hover = obj.hover; if(obj.drop) loadSample(FileSystem.fromAbsolutePath(obj.fileName)); this.repaint(); }); SampleDropper.setPaintRoutine(function(g) { g.fillAll(0xDD444444); g.setColour(Colours.withAlpha(Colours.white, this.data.hover ? 0.8 : 0.4)); g.drawAlignedText("Drop File or right click to load sample", this.getLocalBounds(0), "centred"); }); }
NoteOn:
function onNoteOn() { if (SampleLoadSave.soundArray.length > 0) { local noteNumber = Message.getNoteNumber(); local velocity = Message.getVelocity(); local currentSound = SampleLoadSave.findSoundForNote(noteNumber, velocity); if(isDefined(currentSound)) { SampleLoadSave.updateCurrentSound(currentSound); } } }
LoopPointDragger.js
/** This defines a panel that will mimic the loop dragging facilities from the sampler */ namespace LoopPointDragger { // Create the path for the fade in const var fadePath = Content.createPath(); fadePath.startNewSubPath(0.0, 1.0); fadePath.lineTo(1.0, 0.0); fadePath.lineTo(1.0, 1.0); fadePath.closeSubPath(); const var fadeOutPath = Content.createPath(); fadeOutPath.startNewSubPath(0.0, 1.0); fadeOutPath.lineTo(0.0, 0.0); fadeOutPath.lineTo(1.0, 1.0); fadeOutPath.closeSubPath(); /** this will update the internal data for the panel and must be called whenever one of the sample property changes. */ inline function updateLoopPoints() { if(!isDefined(sound)) { LoopPanel.data.sx = 0; LoopPanel.data.sw = 0; LoopPanel.data.lx = 0; LoopPanel.data.lw = 0; LoopPanel.data.dx = 0; LoopPanel.data.dw = 0; LoopPanel.data.xfw = 0; LoopPanel.repaint(); return; } local w = LoopPanel.getWidth(); local ss = sound.get(Sampler.SampleStart) / totalSamples; local se = sound.get(Sampler.SampleEnd) / totalSamples; local ls = sound.get(Sampler.LoopStart) / totalSamples; local le = sound.get(Sampler.LoopEnd) / totalSamples; LoopPanel.data.sx = ss * w; LoopPanel.data.sw = (se - ss) * w; LoopPanel.data.lx = ls * w; LoopPanel.data.lw = (le - ls) * w; LoopPanel.data.dx = LoopPanel.data.lx; LoopPanel.data.dw = LoopPanel.data.lw; LoopPanel.data.xfw = parseInt(sound.get(Sampler.LoopXFade) / totalSamples * w); LoopPanel.repaint(); } SampleLoadSave.initAfterSampleLoad(); const var MINIMUM_LOOP_LENGTH = 5; LoopPanel.setMouseCallback(function(event) { this.data.hover = event.hover || event.drag; var midX = this.data.dx + 0.5 * this.data.dw; if(!event.drag) this.data.setLeft = event.x < midX; if(event.drag || event.mouseUp) { if(this.data.setLeft) { var right = this.data.dx + this.data.dw; var newLeft = Math.range(event.x, this.data.sx, right - MINIMUM_LOOP_LENGTH); this.data.dx = newLeft; this.data.dw = right - newLeft; } else { var maxRight = this.data.sx + this.data.sw; var newRight = Math.range(event.x, this.data.dx + MINIMUM_LOOP_LENGTH, maxRight); this.data.dw = newRight - this.data.dx; } } if(event.mouseUp) { var totalWidth = this.getWidth(); var loopStart = Math.round((this.data.dx / totalWidth) * totalSamples); var loopEnd = Math.round(((this.data.dx + this.data.dw) / totalWidth) * totalSamples); SampleLoadSave.setAndStore(Sampler.LoopStart, loopStart); SampleLoadSave.setAndStore(Sampler.LoopEnd, loopEnd); updateLoopPoints(); } this.repaint(); }); LoopPanel.setPaintRoutine(function(g) { var alpha = 0.2; var h = this.getHeight(); if(this.data.hover) alpha += 0.1; if(this.data.down) alpha += 0.1; var loopColour = Colours.withAlpha(Colours.cornflowerblue, alpha); var sampleColour = Colours.withAlpha(Colours.white, alpha); g.setColour(sampleColour); g.fillRect([this.data.sx, 0, this.data.sw, h]); if(!isDefined(sound)) return; if(sound.get(Sampler.LoopEnabled)) { g.setColour(loopColour); g.fillRect([this.data.dx, 0, this.data.dw, h]); if(this.data.xfw > 0) { var isReversed = Reverse.getValue(); var fadeInRect = [this.data.dx - this.data.xfw, 0, this.data.xfw, h]; var fadeOutRect = [this.data.dx - this.data.xfw + this.data.dw, 0, this.data.xfw, h]; if(isReversed) { fadeInRect[0] += this.data.dw + this.data.xfw; fadeOutRect[0] -= this.data.dw - this.data.xfw; } g.setColour(0x55447788); g.fillPath(isReversed ? fadeOutPath : fadePath, fadeInRect); g.setColour(0x33447788); g.fillPath(fadePath, fadeOutRect); g.fillPath(fadeOutPath, fadeOutRect); } if(this.data.hover) { g.setColour(Colours.withAlpha(loopColour, 1.0)); var handleX = this.data.dx; if(!this.data.setLeft) handleX += this.data.dw - 5; g.fillRect([handleX, 0, 5, h]); } } }); }
Interface/LogicPage:
const var Sampler1 = Synth.getSampler("Sampler1"); reg sound; reg totalSamples = 0; reg isCustomMap = false; // Edit mode constants const var EDIT_LOOP = 0; const var EDIT_RANGE = 1; const var SHOW_DROPPER = 2; // Get all UI components const var editButtons = [Content.getComponent("EditLoop"), Content.getComponent("EditRange"), Content.getComponent("ShowDropper")]; const var AudioWaveform1 = Content.getComponent("AudioWaveform1"); const var SampleDropper = Content.getComponent("SampleDropper"); const var LoopPanel = Content.getComponent("LoopPanel"); const var Reverse = Content.getComponent("Reverse"); const var EditRange = Content.getComponent("EditRange"); const var btnOscillator = Content.getComponent("btnOscillator"); const var btnWaveform = Content.getComponent("btnWaveform"); // Function to set edit mode inline function setEditMode(editMode) { AudioWaveform1.set("enableRange", editMode == EDIT_RANGE); LoopPanel.set("visible", editMode == EDIT_LOOP); SampleDropper.set("visible", editMode == SHOW_DROPPER); EditRange.setValue(1); if (editMode == EDIT_LOOP || editMode == EDIT_RANGE || editMode == SHOW_DROPPER) { btnOscillator.setValue(0); btnWaveform.setValue(1); btnOscillator.changed(); } } // Callback for edit buttons inline function setEditModeCallback(component, value) { if (value) { local mode = editButtons.indexOf(component); setEditMode(mode); } } // Set callbacks for all edit buttons for (b in editButtons) b.setControlCallback(setEditModeCallback); // Sample Map Loader handling inline function onSampleMapLoaderControl(component, value) { if (value > 0) { local id = Sampler.getSampleMapList()[value - 1]; Sampler1.loadSampleMap(id); } } const var SampleMapLoader = Content.getComponent("SampleMapLoader"); SampleMapLoader.set("items", Sampler.getSampleMapList().join("\n")); SampleMapLoader.setControlCallback(onSampleMapLoaderControl); // Loop controls inline function onLoopControl(component, value) { if (isDefined(sound)) { sound.set(Sampler.LoopEnabled, value); // Initialize loop end if needed if (sound.get(Sampler.LoopEnd) == 0) sound.set(Sampler.LoopEnd, sound.getRange(Sampler.LoopEnd)[1]); LoopPointDragger.updateLoopPoints(); SampleLoadSave.storeSampleMapData(); } } Content.getComponent("Loop").setControlCallback(onLoopControl); // Crossfade control inline function onXFadeControl(component, value) { if (isDefined(sound)) { local newValue = parseInt(sound.getRange(Sampler.LoopXFade)[1] * value); sound.set(Sampler.LoopXFade, newValue); LoopPointDragger.updateLoopPoints(); SampleLoadSave.storeSampleMapData(); } } Content.getComponent("XFade").setControlCallback(onXFadeControl); // Waveform range control inline function onAudioWaveformControl(component, value) { // proceed if we have a valid sound if (!isDefined(sound)) return; // The waveform sends an array with the range values local r = value; // Set the sample start/end points sound.set(Sampler.SampleStart, r[0]); sound.set(Sampler.SampleEnd, r[1]); // If looping is enabled, ensure loop points stay within the new range if (sound.get(Sampler.LoopEnabled)) { // Get current loop points local loopStart = sound.get(Sampler.LoopStart); local loopEnd = sound.get(Sampler.LoopEnd); // Adjust loop points if they're outside the new range if (loopStart < r[0]) sound.set(Sampler.LoopStart, r[0]); if (loopEnd > r[1]) sound.set(Sampler.LoopEnd, r[1]); } // Update the loop point display LoopPointDragger.updateLoopPoints(); // Store the changes SampleLoadSave.storeSampleMapData(); } AudioWaveform1.setControlCallback(onAudioWaveformControl); // Initial setup setEditMode(EDIT_RANGE); // Function to update interface for current sample inline function updateCurrentSampleInterface() { if (isDefined(sound)) { Content.getComponent("Loop").setValue(sound.get(Sampler.LoopEnabled)); local xf = sound.get(Sampler.LoopXFade); local xfr = sound.getRange(Sampler.LoopXFade)[1]; local fadeValue = xfr > 0 ? xf / xfr : 0; Content.getComponent("XFade").setValue(fadeValue); LoopPointDragger.updateLoopPoints(); } }