Some basic questions
-
Ok I've spent a little time playing with it this evening - need to spend more time though! I love the modulators, really easy to set up and very intuitive. One question regarding these, could you implement (if it isn't already there) a quick way to copy a modulation tables values so that it can be pasted in another?
The legato with retrigger module is great, I've spent a long time scripting a similar thing in Kontakt so it's really nice that this is there and built in. I'd like to be able to tweak some of its parameters though such as the crossfade type, the crossfade time - also, is it fading pitch as well as volume? I like that it can handle chords too, I'm guessing this works with a time threshold to see if multiple notes are played at the same time?
You mention that the legato feature could be done using script rather than the built in module, could you give me an example of how I could fade out one note and fade in another using script?
I'm still getting used to the way this works and being so ingrained with Kontakt's workflow I'm slow at getting to grips with this system. From what I understand it doesn't include groups in the same way Kontakt does, but I can use multiple sampler modules to add multiple sets of samples and within the sampler modules I can add round robin groups, is it possible to link the round robin groups across sampler modules - I'm thinking here of using multiple samplers for multi-mic recordings and so I'll want the round robins to be in sync with each other.
Thanks for your help, I'm really excited by the possibilities of HISE, I look forward to exploring some of the synth modules soon too!
-
@24nbbq2h:
could you implement (if it isn't already there) a quick way to copy a modulation tables values so that it can be pasted in another?
Yes, this would be a useful feature. I'll check if I find a spare mouse-modifier key combo for the popup menu…
@24nbbq2h:
You mention that the legato feature could be done using script rather than the built in module, could you give me an example of how I could fade out one note and fade in another using script?
The built in module is actually a hardcoded script:
<processor type="ScriptProcessor" id="LegatoWithRetriggerScript" bypassed="0" script="// [onInit] // =============== // onInit Callback var lastNote = -1; var possibleRetriggerNote = -1; var lastVelo = 0;// [/onInit] // [onNoteOn] // ================= // onNoteOn Callback function onNoteOn() { if(lastNote != -1) { Synth.noteOff(lastNote); possibleRetriggerNote = lastNote; } lastNote = Message.getNoteNumber(); lastVelo = Message.getVelocity(); } // [/onNoteOn] // [onNoteOff] // ================== // onNoteOff Callback function onNoteOff() { if(Message.getNoteNumber() == possibleRetriggerNote) { possibleRetriggerNote = -1; } if(Message.getNoteNumber() == lastNote) { if(possibleRetriggerNote != -1) { Synth.playNote(possibleRetriggerNote, lastVelo); lastNote = possibleRetriggerNote; possibleRetriggerNote = -1; } else { lastNote = -1; } } } // [/onNoteOff] // [onController] // ===================== // onController Callback function onController() { } // [/onController] // [onTimer] // ================ // onTimer Callback function onTimer() { } // [/onTimer] // [onControl] // ================== // onControl Callback function onControl(number, value) { } // [/onControl] "><editorstates bodyshown="1" visible="1" solo="0" oninitopen="1" onnoteonopen="0" onnoteoffopen="0" oncontrolleropen="0" oncontrolopen="0" ontimeropen="0"> </editorstates></processor>
Although I have to check why it allows chords (there is actually no logic allowing this and it seems that notes within the same audio buffer slip through, so thanks for discovering this bug
There are no explicit fade ins / fade out API calls, but the simple noteOff / playNote methods are sufficient to do this job.
@24nbbq2h:
is it possible to link the round robin groups across sampler modules - I'm thinking here of using multiple samplers for multi-mic recordings and so I'll want the round robins to be in sync with each other.
Yes, syncing these RR groups would be theoretically possible. But if the round robin amount is the same on all samplers and they receive the same MIDI messages, they can't get actually out of sync.
However I advice spending not too much time on getting multimic samples work in different samplers. I am trying to get rid of design duplicates and having to keep all the modulators and scripts in sync with each multimic sampler seems not very clever. So the best workflow would be to merge multiple samples into one zone and treat them as one sample. Unfortunately this is a rather complex redesign of the internal engine, so I will take some time to do this right.
I would rather suggest using different sampler modules for different sample groups. For example, here is the layout of a Cello library I am working on:
C-String (Container) Spiccato Attacks (Sampler) Bow Change Attacks (Sampler) Release Trigger Samples (Sampler) Sustain Dynamic Crossfade (Sampler) Legato Intervals (Sampler) G-String (Container) ...
The correct string is selected with a script in the root container (it changes the MIDI channel to 1-4 to send the messages to the respective string container only)
Every sampler module has its own rules / playback conditions and I wrote little scripts for each sampler module to define its behaviour. So yes, coming from KONTAKT this seems a bit unusual at first, but once you get used to writing scripts this way it really starts making sense.
-
Ah that's very interesting, thanks for posting the code for the legato. So it seems to me that currently we don't have access to individual note events (event IDs in KSP) but only to note numbers that we can refer to when using the synth object to play and stop notes.
With the round robins staying in sync, will this work if they are random - or will each sampler be at a different position?
With regard to using MIDI channels to direct incoming MIDI to the correct container - what do we do if we require more than 16?
Is key switching achieved by turning samplers on and off via script?
-
@2bmdf5xy:
So it seems to me that currently we don't have access to individual note events (event IDs in KSP)
Well there is the API call Message.getNoteEventId() which provides the same functionality as the KSP EVENT_ID system, but I didn't use it for a long time, because pretty anything can be achieved using the note number as identifier.
As far as I know, event ids in KSP are simply enumerated note on messages with their respective note off messages having the same id. For which scenarios do you need to use the event ids instead of the notenumbers?
@2bmdf5xy:
With the round robins staying in sync, will this work if they are random - or will each sampler be at a different position?
There is no built in random functionality in the sampler module. The default mode is plain and simple Round Robin, and everything else you need must be implemented by deactivating the default mode and then setting the group index in each note on with a ScriptProcessor.
For a random RR group behaviour across multiple samplers, you would need to use the new global variable system to store the calculated random value in a cross-script acessible place:
<processor type="SynthChain" id="Random Example" bypassed="0" gain="1" balance="0" voicelimit="64" killfadetime="20" iconcolour="0" packagename="" views="32.rk1bzA.....D.........zRMvfSNybCNy.CMv.PmA.." currentview="-1"><editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="MidiProcessorChain" id="Midi Processor" bypassed="0"> <editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="ScriptProcessor" id="Random Calculator" bypassed="0" script="// [onInit] // =============== // onInit Callback var NUM_GROUPS = 7; Globals.randomGroupIndex = 0;// [/onInit] // [onNoteOn] // ================= // onNoteOn Callback function onNoteOn() { Globals.randomGroupIndex = Math.floor(Math.random() * NUM_GROUPS); } // [/onNoteOn] // [onNoteOff] // ================== // onNoteOff Callback function onNoteOff() { } // [/onNoteOff] // [onController] // ===================== // onController Callback function onController() { } // [/onController] // [onTimer] // ================ // onTimer Callback function onTimer() { } // [/onTimer] // [onControl] // ================== // onControl Callback function onControl(number, value) { } // [/onControl] "> <editorstates bodyshown="1" visible="1" solo="0" oninitopen="0" onnoteonopen="1" onnoteoffopen="0" oncontrolleropen="0" oncontrolopen="0" ontimeropen="0"> </editorstates></processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="GainModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="PitchModulation" bypassed="1" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="EffectChain" id="FX" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="GlobalModulatorContainer" id="Global Mods" bypassed="0" gain="0.25" balance="0" voicelimit="64" killfadetime="20" iconcolour="0"> <editorstates bodyshown="1" visible="0" solo="0"><childprocessors><processor type="MidiProcessorChain" id="Midi Processor" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="Global Modulators" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="PitchModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="EffectChain" id="FX" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors></editorstates> </processor> <processor type="StreamingSampler" id="Sampler" bypassed="0" gain="1" balance="0" voicelimit="64" killfadetime="20" iconcolour="0" preloadsize="8192" voiceamount="64" samplerrepeatmode="0" rrgroupamount="7" pitchtracking="1" oneshot="0" crossfadegroups="0" purged="0" samplemap=""> <editorstates bodyshown="1" visible="1" solo="0" gainmodulationshown="1" settingsshown="1"> <childprocessors><processor type="MidiProcessorChain" id="Midi Processor" bypassed="0"> <editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="ScriptProcessor" id="Random Group Starter" bypassed="0" script="// [onInit] // =============== // onInit Callback Synth.enableRoundRobin(false);// [/onInit] // [onNoteOn] // ================= // onNoteOn Callback function onNoteOn() { Synth.setActiveGroup(Globals.randomGroupIndex); } // [/onNoteOn] // [onNoteOff] // ================== // onNoteOff Callback function onNoteOff() { } // [/onNoteOff] // [onController] // ===================== // onController Callback function onController() { } // [/onController] // [onTimer] // ================ // onTimer Callback function onTimer() { } // [/onTimer] // [onControl] // ================== // onControl Callback function onControl(number, value) { } // [/onControl] "> <editorstates bodyshown="1" visible="1" solo="0" oninitopen="0" onnoteonopen="1" onnoteoffopen="0" oncontrolleropen="0" oncontrolopen="0" ontimeropen="0"> </editorstates></processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="GainModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0"><childprocessors><processor type="SimpleEnvelope" id="DefaultEnvelope" bypassed="0" intensity="1" attack="5" release="10"> <editorstates bodyshown="1" visible="1" solo="0"><childprocessors><processor type="ModulatorChain" id="Attack Time Modulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors></editorstates> </processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="PitchModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="EffectChain" id="FX" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="Sample Start" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="Group Fade" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors> <samplemap filename="" savemode="0" relativepath="0" useglobalfolder="0"></samplemap> </editorstates></processor> <processor type="StreamingSampler" id="Sampler2" bypassed="0" gain="1" balance="0" voicelimit="64" killfadetime="20" iconcolour="0" preloadsize="8192" voiceamount="64" samplerrepeatmode="0" rrgroupamount="7" pitchtracking="1" oneshot="0" crossfadegroups="0" purged="0" samplemap=""> <editorstates bodyshown="1" visible="1" solo="0" gainmodulationshown="1" settingsshown="1"> <childprocessors><processor type="MidiProcessorChain" id="Midi Processor" bypassed="0"> <editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="ScriptProcessor" id="Random Group Starter2" bypassed="0" script="// [onInit] // =============== // onInit Callback Synth.enableRoundRobin(false);// [/onInit] // [onNoteOn] // ================= // onNoteOn Callback function onNoteOn() { Synth.setActiveGroup(Globals.randomGroupIndex); } // [/onNoteOn] // [onNoteOff] // ================== // onNoteOff Callback function onNoteOff() { } // [/onNoteOff] // [onController] // ===================== // onController Callback function onController() { } // [/onController] // [onTimer] // ================ // onTimer Callback function onTimer() { } // [/onTimer] // [onControl] // ================== // onControl Callback function onControl(number, value) { } // [/onControl] "> <editorstates bodyshown="1" visible="1" solo="0" oninitopen="0" onnoteonopen="1" onnoteoffopen="0" oncontrolleropen="0" oncontrolopen="0" ontimeropen="0"> </editorstates></processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="GainModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0"><childprocessors><processor type="SimpleEnvelope" id="DefaultEnvelope2" bypassed="0" intensity="1" attack="5" release="10"> <editorstates bodyshown="1" visible="1" solo="0"><childprocessors><processor type="ModulatorChain" id="Attack Time Modulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors></editorstates> </processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="PitchModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="EffectChain" id="FX" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="Sample Start" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="Group Fade" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors> <samplemap filename="" savemode="0" relativepath="0" useglobalfolder="0"></samplemap> </editorstates></processor></childprocessors> <macro_controls> <macro name="Macro 1" value="2699.044921875" midi_cc="-940932133"> <macro name="Macro 2" value="2.1764259838796818e-010" midi_cc="1444674178"> <macro name="Macro 3" value="-154.62451171875" midi_cc="-1095762105"> <macro name="Macro 4" value="-50893783040" midi_cc="1161862878"> <macro name="Macro 5" value="-6.8249268446654871e+033" midi_cc="95410065"> <macro name="Macro 6" value="1.4962941347400682e+023" midi_cc="1343045913"> <macro name="Macro 7" value="4.5894038888110843e+032" midi_cc="517467102"> <macro name="Macro 8" value="1.4573495506627694e-019" midi_cc="-2059494292"></macro> </macro></macro></macro></macro></macro></macro></macro></macro_controls></editorstates></processor>
(You need at least v0.984 for this to work).
@2bmdf5xy:
With regard to using MIDI channels to direct incoming MIDI to the correct container - what do we do if we require more than 16?
You could divide them into multiple containers and change the MIDI channel in the containers script (which gives you 256 possible routings), but Elan already pointed out that there has to be a nicer way of doing this and I will try to find another straightforward solution.
However, the MIDI channel approach is not the only way, it is only the quickest and resource-saving way Those 4 bits of channel information in the MIDI message are transmitted anyway so why not simply use them?
There are many other ways of achieving key switching / choosing the correct module to play and it all depends on the actual design of the instrument.
@2bmdf5xy:
Is key switching achieved by turning samplers on and off via script?
Nope, this would result in audible clicks (same like if you mute a track in your DAW). I would rather activate / deactivate MIDI processing. So if you really need to turn something on / off, I would suggest enabling / disabling an "Ignore all events" ScriptProcessor in every sound generator you need
-
Thanks!
How does the synth object's note off command know which note to turn off if the same key has been pressed twice and both notes are still sounding? In KSP I'd use the event ID to differentiate between the two notes so I only turned off the correct one.
Event IDs are important in Kontakt if you want to change any parameter of a note after it's been played, like its volume, pitch, panning, or velocity. It's very important when scripting fades. It can also be accessed across different callbacks and by storing the ID of a note generated script it can be used to identify real notes and script generated notes. It is used for lots of other things too but I can't think of any other specific occasions at the moment.
-
Ah, OK now it all comes back to me
But I have this weird feeling that the EVENT_ID system is more suited for the KONTAKT architecture and I don't know if imposing this system on HISE would be smart.
For example there are no artificial notes. You simply generate notes or ignore notes and every processor gets the messages that the previous processors created / deleted.
The next conceptual difference is that due to the modularity of this system, the ScriptProcessor cannot know which voices its messages are going to start (it only manipulates the incoming MIDI buffer), so adding functionality like changing pitch values of a started note by scripting would need some weird loopback system which seems a little hacky to me.I'd rather add more specialised Modulators that can be controlled by a script than adding the functionality to scripting because it raises the reusability of those modules and feels somehow cleaner for me.
I would suggest we take a look at the bigger picture in order to find out what behaviour (on the highest level) you need. For example why would you want to know "which note to turn off if the same key has been pressed twice and both notes are still sounding"? I came pretty far by either killing the old note in the new note on or limit the number of voices with the "Voice Amount" property.
-
Yes, coming up with an alternative way to accomplish the same tasks in HISE sounds good. I've been trying to think of some more specific examples where I use event IDs:
My main use for them at the moment is for a artificial legato and glide/portamento feature which relies on cross fading two notes and changing their pitches over time (in a loop). So that as the two notes cross fade their tunings also overlap until the note that fades out is tuned to the new note's starting tuning, and the new note's destination tuning is whatever key has been played - kind of hard to explain but this was used in the famous SIPS and WIPS KSP scripts to achieve a musical sounding legato effect.
In my true legato scripts I always turn off the old note before playing a new one (to prevent hanging notes) and some instruments I've worked on have same note legato transitions and the intervals are mapped to the same keys as the sustain notes (but in different groups) so I could end up with three samples triggering for one note but I only want to hear a max of two at a time (during cross fades) so I need to be able to turn the old ones off using their ID - if there are release samples too then things get more complex.
-
<processor type="SynthChain" id="PortamentoTest" bypassed="0" gain="0.47315123677253723" balance="0" voicelimit="64" killfadetime="20" iconcolour="0" packagename="" views="32.rk1bzA.....A.........DC....a..............." currentview="-1"><editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="MidiProcessorChain" id="Midi Processor" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="GainModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="PitchModulation" bypassed="1" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="EffectChain" id="FX" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="SineSynth" id="Sine Wave Generator" bypassed="0" gain="1" balance="0" voicelimit="1" killfadetime="50" iconcolour="0" octavetranspose="0" semitones="0" usefreqratio="0" coarsefreqratio="1" finefreqratio="0" saturationamount="0"> <editorstates bodyshown="1" visible="1" solo="0" gainmodulationshown="1"><childprocessors><processor type="MidiProcessorChain" id="Midi Processor" bypassed="0"> <editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="ScriptProcessor" id="Script Processor" bypassed="0" script="// [onInit] // =============== // onInit Callback glideTime = Content.addKnob("glideTime", 19, 9); // [JSON glideTime] Content.setPropertiesFromJSON("glideTime", { "text": "Glide Time", "visible": true, "enabled": true, "x": 0, "y": 0, "width": 128, "height": 48, "min": 0, "max": 20000, "tooltip": "", "bgColour": 0, "itemColour": 4294967295, "itemColour2": 4294967295, "textColour": 4294967295, "macroControl": -1, "zOrder": "Normal order", "mode": "Time", "style": "Knob", "stepSize": 1, "middlePosition": 400, "suffix": "", "filmstripImage": "Use default skin", "numStrips": 0, "isVertical": true }); // [/JSON glideTime] GlideEnvelope = Synth.getModulator("GlideEnvelope"); var lastNote = 0; // [/onInit] // [onNoteOn] // ================= // onNoteOn Callback function onNoteOn() { if(Synth.isLegatoInterval()) { GlideEnvelope.setIntensity(lastNote - Message.getNoteNumber()); } else { GlideEnvelope.setIntensity(0); } lastNote = Message.getNoteNumber(); } // [/onNoteOn] // [onNoteOff] // ================== // onNoteOff Callback function onNoteOff() { } // [/onNoteOff] // [onController] // ===================== // onController Callback function onController() { } // [/onController] // [onTimer] // ================ // onTimer Callback function onTimer() { } // [/onTimer] // [onControl] // ================== // onControl Callback function onControl(number, value) { GlideEnvelope.setAttribute(GlideEnvelope.Attack, value); } // [/onControl] "> <editorstates bodyshown="1" visible="1" solo="0" oninitopen="0" onnoteonopen="0" onnoteoffopen="0" oncontrolleropen="0" oncontrolopen="0" ontimeropen="0" contentshown="1"> </editorstates></processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="GainModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0"><childprocessors><processor type="SimpleEnvelope" id="DefaultEnvelope" bypassed="0" intensity="1" attack="38" release="58"> <editorstates bodyshown="1" visible="1" solo="0"><childprocessors><processor type="ModulatorChain" id="Attack Time Modulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors></editorstates> </processor></childprocessors></editorstates> </processor> <processor type="ModulatorChain" id="PitchModulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="1" solo="0" folded="0"><childprocessors><processor type="TableEnvelope" id="GlideEnvelope" bypassed="0" intensity="1.1892070770263672" attack="320" release="20" attacktabledata="24..........9C...vO...f+.........vO" releasetabledata="24..........9C...vO...f+.........vO"> <editorstates bodyshown="1" visible="1" solo="0"><childprocessors><processor type="ModulatorChain" id="AttackTime Modulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor> <processor type="ModulatorChain" id="ReleaseTime Modulation" bypassed="0" intensity="1"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors></editorstates> </processor></childprocessors></editorstates> </processor> <processor type="EffectChain" id="FX" bypassed="0"> <editorstates bodyshown="1" visible="0" solo="0" folded="1"> </editorstates></processor></childprocessors></editorstates> </processor></childprocessors> <macro_controls> <macro name="Macro 1" value="-2.3040434428601907e+024" midi_cc="-92127378"> <macro name="Macro 2" value="-418416332451610620" midi_cc="-1090121960"> <macro name="Macro 3" value="-1.1368151686694912e+022" midi_cc="-2119832504"> <macro name="Macro 4" value="4.48624021795433e-039" midi_cc="0"> <macro name="Macro 5" value="0" midi_cc="0"> <macro name="Macro 6" value="0" midi_cc="0"> <macro name="Macro 7" value="0" midi_cc="0"> <macro name="Macro 8" value="0" midi_cc="0"></macro> </macro></macro></macro></macro></macro></macro></macro></macro_controls></editorstates></processor>
This patch shows an example of how a table envelope could simulate the glide / portamento effect by controlling the intensity of a table envelope. This is by no means a useable setup for your use case, but it shows how the two modules can be connected to achieve the same behaviour as scripting the fades.
For your behaviour, you would need to somehow pitch the releasing note and this is not possible with this modulator, but since glide is a pretty common function, I could simply write a custom modulator that does exactly this.
@2j0kfb8j:
and the intervals are mapped to the same keys as the sustain notes (but in different groups) so I could end up with three samples triggering for one note
Well in HISE you would seperate the sample types into different sampler modules and simply write scripts for each type.
I did some legato interval sampling and I came up with this solution:
Lets assume the samples have this filename structure, where the first note is the start note and the second note is the target note:
Sample_C1_C#1.wav Sample_C1_D1.wav ...
Then you would need to create a sampler with 128 groups. The automapper settings map the first note to the group index and the second note to the root note of the sample. So in this case, both samples would go into group 24 (=C1) and get the root note C#1 and D1.
A ScriptProcessor in this sampler would simply select the group index (= the starting note) in case of a interval :
if(Synth.isLegatoInterval()) { Synth.enableRoundRobin(lastNote); } else { Message.ignoreEvent(true); } lastNote = Message.getNoteNumber();
This is everything you need to implement true legato samples. You might want to kill the old note and change the attack time of an envelope in the sustain samples between intervals and non-interval notes, but that would be another script in another sampler, so there is no need for checking which event-id causes which voice to start.
@2j0kfb8j:
if there are release samples too then things get more complex.
If you need release samples, you would simply add another sampler with a release trigger module and don't care about the other samplers.
-
This looks promising, how does the fade out work with that true legato script? is it using the ADSR envelope to achieve it?
A glide modulator would is a very good idea, especially if we can activate it via a CC or key switch (I imaging this is already possible), some control of the parameters (fade time, pitch bend amount etc.) would be useful too. The way I have scripted my glide is that it glides between each sample, so if I were to glide from C1 to E1 it would actually be gliding between the samples C1-C#1, C#1-D1, D1-D#1, D#1-E1, this way it preserves the formants across even full octave glides which is really nice when simulating a trombone slide for instance. I can also put in a little pause at each step of the glide to create effects like trumpet rips or just a more staggered glide.
-
@3415c09g:
This looks promising, how does the fade out work with that true legato script? is it using the ADSR envelope to achieve it?
If you call Synth.noteOff() then the envelopes will jump into release fade and will fade out, so yes, this is how it works.
The kind of glide you described is a little bit more complicated because it involves selecting different samples (which again is in the ScriptProcessor ballpark). I'll think of something here and maybe this might be a reason to add some script fade API calls
-
this is great! I wish I had more time to play around with this... soon!
-
I agree that this feature has to be done. I've been looking a long time for this and it is very rare with mixed results. For synth leads and bass sounds it is invaluable. I do know it is possible as Korg, Roland, and especially Ensoniq keyboards can do this great with samples, but it is an invaluable feature.
Yes, coming up with an alternative way to accomplish the same tasks in HISE sounds good. I've been trying to think of some more specific examples where I use event IDs:
My main use for them at the moment is for a artificial legato and glide/portamento feature which relies on cross fading two notes and changing their pitches over time (in a loop). So that as the two notes cross fade their tunings also overlap until the note that fades out is tuned to the new note's starting tuning, and the new note's destination tuning is whatever key has been played - kind of hard to explain but this was used in the famous SIPS and WIPS KSP scripts to achieve a musical sounding legato effect.
In my true legato scripts I always turn off the old note before playing a new one (to prevent hanging notes) and some instruments I've worked on have same note legato transitions and the intervals are mapped to the same keys as the sustain notes (but in different groups) so I could end up with three samples triggering for one note but I only want to hear a max of two at a time (during cross fades) so I need to be able to turn the old ones off using their ID - if there are release samples too then things get more complex.
-
Hi, it seems you've made great progress with HISE since I last checked in! What's the latest with multi-mic sample sets?
-
Sorry for the late response, it got a bit quiet around here
Multimic samples are implemented, here is a blog post that describes the procedure:
-
Thanks, it looks pretty comprehensive. I'll have to test it out
-
Can the individual samples of a multi-mic sample have their start position offset?
What I'm thinking of here is if I record an instrument with close, stage, and far mics, they are going to be offset slightly with the mics farthest away the most delayed. When all mics are active this is fine but if, lets say, all the mics except the far mics are purged then there could be a noticeable delay between pressing the key and the start of the sample. So in this situation is might be nice for the user to be able to shift the start position of the sample.
-
I don't think this is practical. Moving the sample start means resetting all preload buffers (the beginning of the sample that resides in memory until the hard drive can fetch the rest).
If you really care about this delay, you could remove a fixed amount from the far sample (so that it is "in sync" with the close mic) and introduce a artificial delay using the delay FX, which can be altered on the fly.
-
Ah that sounds like a workable solution
-
What is about globals? I mean are they global for unique plugin or for any plugin in the project?
Generally, in the any future would be grate to have both separately, but now I need only to know :) -
Globals are only accessible in one plugin. Making shared stuff across different plugins in a DAW is a source of constant trouble so I wouldn't bother with this...