Architecture of a Guitar
-
@aaronventure yes one sampler per string should be fine - you can even use one sampler per string per articulation (or tuck all articulations into one samplemap into different round robin group ranges or even note ranges).
The midi processing that selects the string is definitely a job for a script processor in the parent container - and the easiest way to route them to different channels is to set the midi channel of incoming events and then add a channel filter midi processor on each sampler
-
What's the overhead for 8x sampler vs 1x sampler, both in terms of memory and CPU?
I figured out I can actually lay out all the strings in the single sampler (map them to different velocities), and use a different sample map for different articulations, but it leaves me with a question - is sample map change instantaneous and consistent in offline performance, i.e. was it designed to, among other things, accomodate articulation changing via keyswitch?
Does the sample map contain the number of RR as well, or do I have to use the largest number I have and then script out the ones with fewer RRs?
-
@aaronventure You can't swap sample maps in real time.
I made some posts here and on Patreon back in 2019 when I was working on a guitar library, they might contain some useful info for you.
I use a single sampler with the strings and dynamics split by velocity. I use different groups or samplers for each articulation. I recommend you buy a few Kontakt guitar libraries to see how others have solved the problem.
-
8 samplers is totally fine, 8 * 6 strings = 48 samplers starts getting weird. Definitely make sure to limit the voice amount of each sampler to the max number you assume will be used as the internal streaming buffers will eat up hundreds of megabytes if you keep 48 samplers at the default voice limit of 256.
was it designed to, among other things, accomodate articulation changing via keyswitch?
No. Samplemap changes are asynchronous operations which suspend the audio processing and are definitely not intended to be used in a realtime operation.
Does the sample map contain the number of RR as well, or do I have to use the largest number I have and then script out the ones with fewer RRs?
Yes and the bulk of the work has already been done for you:
https://docs.hise.audio/scripting/scripting-api/sampler/index.html#refreshrrmap
https://docs.hise.audio/scripting/scripting-api/sampler/index.html#getrrgroupsformessageIn the script just make sure to call
refreshMap()
after a samplemap load, then you can query the exact amount of round robin numbers directly from the sampler to implement your custom RR script.I don't know how many articulations you have and whether you want them to be purgable individually, but from my limited knowledge the most efficient implementation would be:
- one sampler per string
- try to pack all articulations into one samplemap
- distribute the articulations across the entire note number range from 0 - 128,
- add a MIDI transposer that transposes the incoming note into the octave range of the selected articulation. This can also be done in the global script that distributes the notes to the strings via MIDI channels using
Message.setTransposeAmount()
which also handles the transposing of the matching note-off for you automatically.
You have 18 semitones per string which gives you 127 / 18 = 7 articulation slots that you can use with this approach. If you need more articulation slots, you'll need to use different RR groups to distinguish between articulations, which makes the scripting a bit more complex, but still manageable.
-
@d-healey Thanks. I've no issue doing it efficiently in Kontakt, I was just wondering what would the most painless and scalable approach in HISE be.
I always found the limitation of 127 velocities to be very limiting. I understand why it's in the 25 year old Kontakt, but it really is an aged paradigm today, with the limit per map being only 127x128 samples.
I think there's potential for the Y axis to be open (or at least the limit raised), with a setting that says "Map velocity to Y", which would enable the current behavior of velocity triggering the samples for the first 127 rows. This then makes any similar architectural issues trivial as you can just dump it all into a single map, define the ranges in the script and properly script out any kind of behavior you want with all the samples being laid out nicely in a single sampler.
-
@Christoph-Hart I have Sus/Mute/Pwr/PwrMute with 8/16/4/4 RRs. Sus has 2 layers. In Kontakt all of these fit nicely in one group spread out over velocities, I then have 8 groups, with the script allowing correct group for playing the event note.
From what I gather, here I can either have one articulation per sampler, which ends up being 4 samplers, and I make use of the built-in RR/group system to put all round robins, with all strings being laid out in a single map.
Or I go with one sampler per string, which is the exact same layout I have in Kontakt, but then it's just the question of making sure each event goes to the correct sampler. What other way of doing this is there, other than the MIDI channel method you mentioned? What if there are more than 16 samplers, hypothetically? Is there no "allowSynth()" or something like that which could be called in the Container MIDI Processor? In Kontakt it's ignore event> disallow all groups > allow desired groups > execute artificial event.
I'm sure I can set up communication with the MIDI processors of individual samplers which would then either ignore or process the event, no? What would be the best solution for this? Global variable? Or is that too slow?
-
@aaronventure said in Architecture of a Guitar:
In Kontakt all of these fit nicely in one group spread out over velocities, I then have 8 groups, with the script allowing correct group for playing the event note.
Why not do that in HISE?
-
@aaronventure said in Architecture of a Guitar:
What other way of doing this is there, other than the MIDI channel method you mentioned? What if there are more than 16 samplers, hypothetically? Is there no "allowSynth()" or something like that which could be called in the Container MIDI Processor? In Kontakt it's ignore event> disallow all groups > allow desired groups > execute artificial event.
So you have an incoming event(note on) now you need to send it to the correct sampler... so you can;
- change its midi note (as Christoph said) and make each sampler react to only one midi channel, so we are limited to only 16 samplers here...
or - have a "note generating midi processor" in each sampler, set the values you want in the processor and tell it to generate.. now you have unlimited samplers.. you can even let this processor manage articulations for you.
In both cases you need to manage note ons and offs...
- change its midi note (as Christoph said) and make each sampler react to only one midi channel, so we are limited to only 16 samplers here...
-
-
@aaronventure said in Architecture of a Guitar:
@Lindon Thanks. Setting values would be done by creating a control in each processor, and setting its value from the main container processor, right?
yep
- never used Global Cables (yet)...
-
What if there are more than 16 samplers
You have 256 MIDI channels inside HISE - you're welcome :)
-
@Christoph-Hart Hah, great. I don't think I'll ever need 256 samplers, so that settles it, I guess.
Re: global cables, I'm trying to wrap my head around it but there's only one thread on the forum and nothing in the hise_tutorial repo. Correct me if I got this wrong:
I create the cable wherever by first calling GlobalRoutingManager.getCable(id). I can then get a reference to that same cable from anywhere else by calling that same method. GlobalCable.registerCallback then registers a function for the cable change, but for that processor only, right? So each processor can have its callback for whenever the cable's value gets changed.
-
@aaronventure yes, but in this case I would not use global cables but stick to the message type that is already being passed around: the MIDI message (or its internal representation).
think of it this way: you have a central post office (your main script that calculates the string) and multiple smaller post offices (your samplers). Now if the packages they send between each other are completely self contained and all information they need to process the package is slapped onto the package with a sticker, they don't have to talk at all and everything is smooth and nothing can go wrong. A global cable in this case would be the equivalent of the central post office having to call the smaller offices and tell them: "the next package you're about to receive is for post office #3", then hope for the best that this information will receive the target on time and not get stuck somewhere along the way.
The prime use case for global cables is to send a modulation signal (or whatever) back to the UI or distribute an UI event to different targets, but if you can get by with only using midi messages for communication you should be good.
-
@Christoph-Hart Agreed, and thanks for the clarification!
-
@Christoph-Hart said in Architecture of a Guitar:
You have 256 MIDI channels inside HISE - you're welcome :)
Reply
How does this work, exactly? Message.setChannel says it can take only 1-16).