Some basic questions
-
I haven't had much time to delve into HISE yet but really looking forward to building some libraries in it.
What scripting capabilities do we have - I see in the scripting documentation that JavaScript is available but not much detail on it, should I be looking for JS documentation online?
What is Protoplug and what can it do?
Do you we have control over individual note events ala Kontakt?
Is it possible to do equal power fades via scripts? -
@yc6yhxz6:
should I be looking for JS documentation online?
There is not much to know about Javascript. Most advanced features of Javascript are used mainly for web development - the usual script stuff is quite simple from a language point of view. I suggest you scroll through this tutorial to get the basics (variable declaration, loops, conditions…):
http://hartinstruments.net/hise_mkdocs/Javascript/index.html
The major difference between the scripting approach in HISE and KONTAKT is that due to the modularity of HISE scripts can be placed anywhere which allow much better encapsulation (You can place different scripts on different sound generators and just care about the logic of this sound generator).
@yc6yhxz6:
What is Protoplug and what can it do?
Protoplug is another open source project which uses Lua (another scripting language) for DSP processing. The engine itself is more powerful than the Javascript Engine of HISE, but in its current state it doesn't support OS X in 64bit mode and in order to get a API with so many features like the current one of the Javascripting Engine it would require a major rewrite.
I embedded it for testing purposes, but I decided to exclude the code from the current builds - until there is no 64bit OS X support I don't want anyone to use it and start depending on it…
However if this issue gets sorted out, this system (or an own engine based on the LuaJIT compiler) could allow scripting of actual effects and modulators.
@yc6yhxz6:
Do you we have control over individual note events ala Kontakt?
Yes, that would be the onNoteOn / onNoteOff callbacks.
As far as I know Elan is working on a tutorial about writing scripts. And there is one pretty simple example in the blog and I am planning to add new script tutorials when I find the time.
@yc6yhxz6:
Is it possible to do equal power fades via scripts?
Currently the scripting engine has no API calls to fade voices or change their pitch. But there are many modulators which have user definable curves which can model a equal power fade behavior if that is needed. And these modulators can be controlled via script. What use case do you have in mind?
-
Thanks for the detailed response. I need fade and tuning control over individual note events to be able to accomplish things like true legato transitions as well as other similar crossfading tasks. Some of which can be accomplished with modulators too but that isn't always an option.
-
I came pretty far with this approach (including some advanced legato modelling).
This is an example of how the script can control an envelope which is set to either 1.0 ms or 1000.0 ms depending on the note number. It works polyphonically (so changing the attack rate while another note is still in its attack rate doesn't change the old attack time).
HiseSnippet 1027.3oc4W02iZaCF2AvRkz0s0oNs8mQUZRfVEW3taqapp65cTnBsxA5fccRSaUlfygUC1zDGtRq52w8QoeC17KIjjqF5wzzcqZQBT7yq+7uG6G6LHj4gihXg.qaNZ4bLv5SfCWR4SaMEQnftOFXcGX6WFiB5flf6Ra+Jzr4AXvQKmihhvS.VVkehzRqpU.pm+7fiPAHpGNSD.bJi3geJYFgmIcvi9YRfJriHyxY89OpqGi1hEvhEnpLzELG48BzY3iQRyJAAKH3yi.Vtv81sQ3KZN90G1nQiWOdYuc2zumB7hCCwT9oB2AVPq+R7XUo8DBmENji3XQLqbDaxxgSYmSkotI3TRDYrX9pFLTfIMh5vBlHm7x2AslRBlLHkHi.hXOHiVKqo06.6QlPVIOid+bkBmLOxSsVktzvy0H7ZZ.dfbnqhFcBPvlDGf3EQlrxlnfvnEPVWJGSiH7kYEt2cv0JbuMb.g6M0LdKc8f2jh+sfs88wd7LvVA14WuZqzepFJ2DNjPwps3Jf7kpwNOCs.67DLEGJI0q1s2883hrOJDQilyhJD3g3YjQLpjPxD9KQ3Ng3WdhrHmWdKFJznp2cPGwbznOCQ73P0pkCmwho775rJWrj.KTRbWWughaZxUB+.MMpbMzzXK5oY89aCuMbnWHYNOSiDaeQhz0fNsRPo+nLbmcb9MFsKkv+caw6Or3iTjVqSKTPvXwg.11pihFvNGG1JjEE4KVd47PG0x4Fmg4qZKT6tFr7t0efLk6jlSaM.Nlww8olfPJHzVjCF9wTOY0cktZ0seicUhesdhYr3rJIXjZNNd1XbXs5N+jy82u9srq9FwupFvViHL+PNOjLNliqYx.gVQtumSyFt0efHJuU7CGDg+2Intttow09s1IzTJyjmn78MyT4oJe+MvU99Zx5BoQF2j7zhQ4grf.b3ZR0prkYo4Dlo+h4LeNRRqrOk4LpSlRu47nTcwTjDuhSpMRdI1rw4RMpZI08bVfBhwlmUhj9dcupd45Bn2azeNllNVuHHURSvp5UdixnSCRyKRQJYBLblkzIwsz9Ox8T9G1g7FZ79YvQHQfZSWfCXBA5SbMrQ7CiY8N07RNAGfQQENZUajJmOFwQ.quAt69MJ9rnu3O+uU95O1R72d8AIQZs9orKmepmQ8AWjGKe43QCbWoKYs9qfISQwxHmOZtZ5WCSY3Opf8FuQc4+edi5SXwbB8rdHwopuRbmPwA7CE2j0CKPBkJNOV9kkkjswzicki0WkkNwM8yNST1TN1JQYyTkWI4XFxKj8bOcCZ4NvanjHl2T0mYWE1SN1oIPcNilalItz4y87R1Ea1gc2VG1aacX+s0guaac362VGt+15vOrdG9aoKoyEC
(Copy this code into the clipboard and right click on the root container header and select "Replace with Clipboard Content")
-
This looks interesting, I'll need to study it a bit more. Will this allow me to implement true legato transitions? or retrigger notes when a key has been released? I'm looking to see if I can convert some of my Kontakt scripts to work with HISE
-
@14bte0ul:
Will this allow me to implement true legato transitions?
Yes. All legato samples need to be put into an own sampler module with 128 groups and "Group Index = start note number". The script processor that selects the correct group and plays the legato transition is trivially simple (I will upload a snippet that does this).
@14bte0ul:
or retrigger notes when a key has been released?
Yes. There is already a hardcoded MIDI module ("Legato with Retrigger") that offers this functionality, but it can of course be scripted too.
@14bte0ul:
I'm looking to see if I can convert some of my Kontakt scripts to work with HISE
Of course some of the core logic can be implemented in Javascript, but I would refrain from trying to clone the KONTAKT scripts - you give away all the advantages of the modular approach.
If you want, you can post some simple script in KSP and I will try to convert it to a HISE script for starters.
-
Thank you, I think I need to go away and experiment with HISE and get used to its paradigm before I ask any more questions
-
Sure. Let me know if you get stuck somewhere - which is pretty easy with the current state of documentation
-
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.