How to linear crossfade from one (phase aligned) sample to another
-
Something for which I don't seem to be able to find a simple solution...
I have an attack sample (let's call it A1), and a release sample (let's call it R1).
If I trigger A1 and release it, I want the R1 sample start point to be offset by the length of time A1 was held for. It needs to be sample accurate as the original samples are phase aligned.
What I'm doing at the moment is logging Engine.getUptime() when A1 is triggered, and then logging the same when it is released to calculate the note length (this gives me the note length in seconds). I'm then multiplying that number by sample rate (in my case 88200), to give me the sample offset value. I'm then using Synth.playNoteWithStartOffset to offset the R1 sample by that amount.
On A1 noteOff, my idea was to 10ms linear fade out the A1 sample and 10ms linear fade in the R1 sample. But it's not working. A1 and R1 are out of phase.
Any help with this much appreciated!
Douglas.
-
Okay, silly mistake on my end (I had a detune on the noteOn callback).
So I can actually get these to play back as I want. However, Still some advice requested on how to set up the cross fade between samples.
As I said: on A1 noteOff, my idea is to have a 10ms linear fade out on the A1 sample and 10ms linear fade in the R1 sample. I'm currently doing this with the TableEnvelope, as it seems to be the best way I can get a linear fade out and fade in.
However, what if I wanted to adjust the curve (length and type) depending on how long the A1 sample was held for? Is TableEnvelope still the way to go? AHDSR envelope seems more flexible, but I can't see a way to adjust the release curve.
Any help with this much appreciated!
Douglas.
-
@tonewolf you'll have to dive into scriptnode and create your own custom modulator or envelope for this.
script envelope modulator is what you want (that's the polyphonic one).
-
The simple envelope is also purely linear in its default setting so for that use case I would just stick to this.
-
@Christoph-Hart Is the Simple Envelope polyphonic? Also, am I able to modulate the release time? I was using the Table Envelope because it appeared there was AttackTime and ReleaseTime modulators.
Basically, for any given performance of a single note (this is a piano library) the logic I'm looking for is this...
-
User triggers a noteOn event, which triggers the noteOn sample
-
noteOff event triggers a bit of script which does the following calculations...
a) calculate length of note (we have been logging noteOn and noteOff timestamps)
b) we offset the start point of the release sample by length of the the held note
c) we adjust the Attack Time of the noteOff sample, which is calculated according to the note length
d) we adjust the Release Time of the noteOn sample, which is calculated according to the note length -
we enter into the noteOn release phase, and we play the noteOff sample.
So, if I triggered 5 notes and then released them individually after holding them each for different lengths of time, they might all need to have different Attack Times and Release Times.
Perhaps I was doing something wrong, but when I was testing out the logic, it seemed like the release was being adjusted globally.
What would be the most efficient way of doing this? Last night I came to the idea of just using Synth.addVolumeFade(eventId, 50, -100); in the noteOff callback, and that seemed to work pretty well (or at least, it did actually do what I wanted and sounded correct, but I kept on getting an error message in the console telling me the noteOn with IDNNN didn't exist!).
-
-
This is my script for the attack samples...
function onNoteOff() { local eventId = Message.getEventId(); local noteNum = Message.getNoteNumber(); local noteLength = Globals.noteOnArray[noteNum]['noteLength']; local fadeTime = .128; if(noteLength <= .045) { // it must have been shorter than or equal to 45ms fadeTime = .01; } else if (noteLength <= .16) { // it must have been longer that 45ms, but shorter or equal to 160ms fadeTime = (((noteLength - .045) / .115) * .1) + .01; } else if (noteLength <= .45){ // it must have been longer than 160ms, but shorter or equal to 450ms fadeTime = (((noteLength - .16) / .290) * .018) + .110; } Synth.addVolumeFade(eventId, fadeTime*1000, -100); }
It functions well, but that last line generates an error in the console log...
"onNoteOff() - Line 25, column 21: NoteOn with ID1 wasn't found"
which seems to break the script and I can't add anthing after that line.
Any clues? The noteOff cancelling out the noteOn eventID?
-
@tonewolf The eventId there is the local variable you've assigned to the note off event ID at the top of your script. I don't thin you can fade out a note off message. You'd need to play a note instead and capture its event ID.
-
@d-healey my noteOn callback is empty, so the Sampler is simply triggering the mapped sample.
From what I can tell, the Event ID is the same for the noteOn as it is for the noteOff. That is to say, if I place the following in the onNoteOn() callback, also in the onNoteOff() callback...
Console.print(Message.getEventId())
...it returns the same ID for both.
-
btw, more experimenting and sticking this...
Message.ignoreEvent(true);
...at the top of the noteOff function call, and then this...
Synth.addVolumeFade(eventId, fadeTime*1000, -99); Synth.noteOffByEventId(eventId);
...does not throw an error. Seems a bit cludgy, though.
-
I'm fairly certain you can't kill real events, you have to make them artificial. For your use-case I think it's just a matter of making the note artificial in the onNoteOn callback and storing the ID. Then you can control it how you want in the note off callback.
Here's an example
HiseSnippet 932.3oc4W8zaaaCEmJ1pn1cqXsqnXG0gcvAHqvZqqs.EEMIN1EFqNwHJMa2JXod1lHTjZTTdyXXe21Gg8QoG18sGkThjVTybL5Vw1zAC+9G46u+dRS0JFjjnzDmNmrJFHNejavJoYwfETtjL9.hyscmPSLf1Km09qhoIIPHwwo0KrLb5zlj871muOUPkLnjEgbphyfWxi3lRtS28a3BwHZHbBOph1Ob2wLkbfRnRQ+okaeRLkcFcNbH0p1VtDmaLLjaT5.C0.InN6qBWErP8Cxb8Okmvei.rD9j.7fxYSFrfKBmddrlPHNtSKi7V4Q98bmvC4WvuLC7IYB7JsnZNvYqqxk7uFtjSEWpctKcG2.llGaJkX8ma4NVhEjYTLUW0Ux0k37KtCTnBRyChnmAizHwEFz6Q86uiG9y1OcVpjY3JomRdnx.GI6sc2epamt+bWu+rnYyZTl8ZzJg.zMJ1Vc0Wkg8joQuAz63sjJRgKTDC+54zard4TVdTWQQkbrjaNJFJnGoDg1bk8+WtBPJRa3+d03CnFpsnTvC0KFzFt0cbN.Vhc04knNtG.ImYTwXe8kpeXmiJLUPM0amryMEBv7QsZnsPIS3lUUmqtF8X8uxdr00Eui6TtgsnYebqF7QLS82gOVLY9wtCmMCXlRGrs6nuaSGCW+q+14W+sbC3RHCVL6xueFs22RWBdu.jf1l77uBjweccQFiWajwiXF75OQSkIwpjZGb.DwOQIsYgRluJAABfu+Xa0rJ+AJptQQu84ivfrQaBnlTcVawdQpTooVaPq50g1q2na8IhJ52.NY6O.P28eOAc+oEbK8H+Fgv+MWML2CVhHOiCeWH0Eh8dl2D7nv8jY386gnTy3LNUza6m9tAy6zsSVG8CnggmpDoQfssqWwYtimee6hhuv2to3CDru66KX++OB0Wqmry49X.OJV.CkKAAFjY93cwnZFMUXNma8NxIJoJdgRxYUGyOFLZ974ftpu2X.smwfuDWIm6s6wf.nUgr97ceIB0P0XdB1vbw0+UqZrd8Yt4tqmsG16euqna8e6UzGqRMb47ITrI7GQbhCSiBvMiL.ucoDDVrCmsrS44z8sz4qFkgYD+N9THz2R6THz+bg+ibGQTlV8ZVNpnsO8lYbv3Vl8EOcvO8Bo87IYHkUqkQ3ZsWyX0OpKY3WtoF9UapgObSM7q2TCezlZ3i2TCexesg125XuTiJJezjPlLcX1ZFGmgRJ1kmMQP9C3+t.QK