JUCE Tutorial 16 - Creating a Basic Delay Effect

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody i'd like to welcome you to another juice tutorial and in this tutorial what we're going to do is create a very basic delay effect so this is almost like the hello world of delay effects and this is drawing on our last tutorial where we created a circular delay buffer and now what we're going to do is we're going to draw some audio data from that delay buffer and pull it back into our main buffer creating this delay effect before we get started just wanted to tell you about our audio programming community on discord we now have nearly 7 000 members we hope that you can come join us we have people from all over the world all different experience levels come join us the audio programmer dot com forward slash community also we connect with different companies to help provide opportunities for audio developers as well and we have some of those listed on our jobs board and that is over at the audioprogrammer.com forward slash jobs so if you're an audio programmer and you're looking for a contract or permanent job opportunity come check us out so let's go ahead and get started and i'm going to start off by giving a big shout out to luca gregio who actually pointed out an error from my last tutorial on circular delay buffers where he pointed out online 175 previously i had channel data for one of these arguments and it should actually be chanda data plus num samples to end and i have some details on that on the last tutorial so if you want to dig into that a little bit more check out the last tutorial i'll put a link to it up in the corner and you can find out more so let's go ahead and get started so i'm going to go over here to the actually i'm going to go up here to the top just to review a little bit so last tutorial we built this circular delay buffer the intention of this is that we have audio data from the past that we're going to store in another buffer and then at a later point what we want to do is we want to take that data and we want to pull it back into our main buffer that's going to create this delay effect now last tutorial we created the size of this delay buffer which in this case is sample rate times two seconds so this would be two seconds of uh of sample time so if our sample rate was forty four thousand one and we were multiplying that by two it would be 88 200 samples uh so that would be an 88 200 uh index array and that would actually be a 2d array if we were operating in a stereo situation but i'm not going to make that too complicated uh for people who are just starting off but just think of it as a as an 88 200 sample array if we were operating at a sample rate of forty four thousand one hundred okay so it holds two cent two seconds of audio going down here to our process block which is where we're actually doing our heavy lifting and our audio processing we have this function that we created in our last tutorial called fill buffer where we actually are taking the audio that's happening at present and we're copying it to this circular delay buffer of course we had to have some if and else statements just to check the bounds of the circular delay buffer make sure that we're not running off the end of it and that we're always staying within the boundaries of the data of of the amount of space that we've allotted for this circular delay buffer so now we've done that and what we want to do is we want to grab some of this data back from this from this delay buffer and we now want to add it back into our main buffer and that's what creates this delay or echo type of effect okay and what i'm going to do is i'm going to start off with some pseudo code just so we can get a high level idea of what we're trying to do here so the the whole idea of a circular buffer and copying this data is that we want to retrieve some data from in the past right so we have this variable that we created last tutorial called right position and we can think of right position as where is our audio at currently right so i would just say where is our audio currently because that's where we are um that's where we're currently writing we're taking the audio and we're writing to our circular delay buffer so i say this because right position you can think of as current position of the audio now we have this other position that some people like to call read position which is going to be a position that's going to be in the past okay so uh so the whole reason that we have the circular delay buffer is so we can look in the past now the delay buffer in this situation is one second of is two seconds of audio time uh so what we might what we might want to do let's say that we wanted to look one second into the past right so from a high level we could say that reposition is right position minus one second in the past right because we have two seconds to work with now one second in the past let's just substitute that because we know that our sample rate is how many how many samples of audio plays in one second right so we know that this will mean sample rate amount of audio or just sample rate right so if i say right position minus sample rate equals the read position then what that means is that our reposition is one second in the past from our right position from our current position so it's reading one one second in the past okay now uh if you are following along and you followed along to our last tutorial you may realize something which is that the right position may be at the very beginning at the very first callback the right position is actually going to be zero right and our sample rate is going to be let's just say it's forty four thousand one hundred that means that our read position would be negative forty four thousand one hundred right now how is that going to work if we're reading into a an array because we know that the minimum that the the first index of an array is zero so if our if our reposition is negative 44 100 how is that going to work uh and the answer is that it doesn't right so what we need to do is something similar to what we did in our last tutorial which is that if our reposition is less than zero we need to wrap it back around to the end of the circular delay buffer okay and you will see i have some notes here i've written most of them in cursive so uh maybe difficult for some people to read but you'll see what i what i've drawn here and this displays this uh this example perfectly so in this situation for one reason or another our read position is negative two right so it's a negative it's a negative index we can't have that and our delay buffer size in this situation is 512. right so so we have 512 size delay buffer and reposition to minus 2. so since we can't read into an index of minus 2 what we want to do is we want to wrap it back around so that we end up with index 510 and we notice that last index is 511 because we we know that in c plus plus an index of an array starts with zero so zero to five eleven gives you a size of 512. so that's a common stumbling block for people who are just starting so just keep that in mind so taking this and then trying to pull a little bit of logic out of it we can look above and we can see what this is kind of common sense right so now we just need to make it into a little bit of code and we can say if the reposition is less than zero so in this situation reposition is negative two then the read position is equal to the delay buffer size which is 512 plus the read position which is minus 2 right so i got this wrong before i actually put delay buffer size minus the read position you can see that i emphasize the plus sign there okay because if you have a minus reposition and your reposition is minus two that actually turns it into a plus which would mean that it would give us uh a position of 513 which is not what we want we want a position of 510 so that's why it's delayed buffer size plus reposition when read position is a negative number okay so that's that's what that means so we can go ahead and just put that in in here right so let's just start by writing this and then later on we'll pull this out into its own function so we can say auto read position is equal to the right position minus we have this handy function called get sample rate which gets our sample rate and once again this is one second of audio from in the past in the delay buffer right and then once again we're doing that check so we will say if read position is less than zero we know that that's that would be an invalid index then we could say reposition and we'll just use a shorthand here plus equals delay buffer size okay that will ensure that we stay within the bounds of our of our delay buffer when we're trying to read back into the past so now what we want to do is we want to now that we have a reposition we know where we're reading from we are going to need to do some checking again to ensure that our data uh that the data that we're trying to read doesn't go off the edge of our uh of our delay buffer that we wrap back around that we do some logic to once again make sure that that's circular behavior okay so let's start off with a a simple uh the the most simple situation which would be um if the reposition um so i need i want to go back a step actually because i want to uh go back to high level again right so how much data are we processing per uh per callback right so every time this process block calls how much data do you think that is supposed to process uh per callback now if you've seen my other tutorials you would know that it is normally buffer size amount of audio that's processing right so each callback that happens we are filling the we are filling the main buffer with buffer size amount of audios and that's normally it's normally a power of two but it doesn't it's not always but so most times it is so something like 256 512 1024 2048 so it's normally that amount of data so with that being said what if our reposition so our reposition is keeping track of where we want to read from in our circular delay buffer what if our read position is towards the end of this circular delay buffer and that it doesn't have buffer size amount of data to actually copy uh to to actually move over to our our main buffer then we're going to need to do a little bit of copy the rest of the contents from the the remaining contents from the end and some of it at the beginning like we did for the circular delay uh circular delay buffer that we did in the last tutorial right so let's talk about the easiest situation where that's not the where that's not the case so we can say every position plus buffer size is less than delay buffer size then we know that we're in a situation that is all fine right we know that we know that the reposition and that the reposition plus the buffer size is still within the bounds of the delay buffer and that we haven't we're not trying to go outside of the delay buffer size so that's all right then what we could do is once again we have this nice function supplied by juice called uh we will do buffer dot add from not copy from so you have a copy from which just copies and overwrites the data then we have this other one called add from which actually adds the data rather than copies it i'm going to do add from with ramp the reason i'm going to do that is because i'm going to have some some some gain so a lot of times with delay you have a little bit less of the the volume on the delay effect itself is a little bit less or sometimes it's filtered there are all different types of things you can do with it but in this situation i'm just going to make it just a little bit less so we can make the uh make the delay effect a little bit more apparent otherwise it'll just sound like two of the same signal so i will make that 0.7 and then let's just start off with uh these different arguments so we have add from with ramp first thing is which channel of the buffer like i said these buffers are actually 2d arrays okay so we think of them conceptually or at a high level in terms of one-dimensional uh but in a stereo setting then it's actually two channels of that which makes it a 2d array okay so that's all right since we're already going through our channel for loop we can just put channel in here to make sure that this iterates between zero and total num input channels which would be normally that would be two so this will be iterating channel will be iterating between 0 and 1. destination start sample so which start sample in the buffer uh so that would be zero right because we want to start uh we want to add in from the beginning of the buffer the source uh this one gets a little bit trickier okay because what we're going to do is we're going to take the delay buffer and we're going to grab a read pointer okay so read pointer just means that we're going to read it we're not changing what's in the delay buffer we're just reading what it is hence the names read position and right position okay so we're just reading what it is grabbing that once again channel number is just going to be channel which is iterating between zero and one sample index is going to be what do you think it's going to be okay needs to be read position because that's where we're reading the data from that's the starting point of where we're reading the data from okay and num samples okay so let's look back at our if statement if reposition plus buffer size is less than delay buffer size so that means that we're okay in this situation to copy buffer size amount of samples so here i can just put buffer size like that okay and that's it we're done there right so that is fine but then we have this little else situation which is where uh we don't have where reposition plus buffer size is greater than the delay buffer size that lets us know that if we try to copy buffer size amount of samples from the read position which could be near the end of our delay buffer then what will happen is that we're going to go into uh undefined territory where we're going to try to read into memory that actually isn't supposed to be used for our uh for our or uh for for this buffer okay so here we have uh another little a a little situation right where i'm just using a um just very small numbers just so we can conceptualize it easy and we have a reposition of seven our buffer size is five right so we see that seven plus 5 equals 12 but we see that our delay buffer size is 10 right so we see that if we tried if if our reposition if we tried to copy or add buffer size amount of samples that we would run off the end of our delay buffer that's not what we want to do so from a high level what what do we want to do we want to say well we know that if the delay buffer size is 10 and the reposition is seven that we have three indices that we can copy right so it's three three uh indexes to the end so that's why i have that that this here seven eight and nine uh the seventh eighth and ninth index of the delay buffer can be copied to zero one and two of a regular buffer and that is a variable that i've called num to end three right so it makes sense and then we say well how many how many samples do we have left that we still need to copy well that would be the read position minus the buffer size right so 7 minus 5 gives us 2. right so then what we want to do is take the 0th and 1th first index from the delay buffer and copy that to the 3rd and 4th index of our main buffer okay so that so that's what we want to do now we just need to now we just need to actually do that in code so this is going to be two steps so first thing that we need to do is determine auto num samples to end we will set that equal to the delay buffer size minus the read position right because that gives it that will tell us how many samples or how many indices are left at the very end of this uh of this array or of our buffer and now we just need to do another add from with ramp and then once again this is channel and then destination start sample so we're copying to the first part of the buffer so this will be zero the source once again we want to get a read pump a read pointer from here so it's delay buffer get read pointer once again this is our channel and then we have the sample index which is once again read position now we get to num samples right this is the tricky one how many samples do we want to copy or how many samples do we want to add to our buffer from our delay buffer well that's num samples to end that's where we got that's why we calculated num samples to end right so num samples to end and what i'm going to do is i'm just going to pull a little um a little small very local variable out for the gain so i'll just call this auto g equals 0.7 f but this will just be temporary i mean we could come back a little bit a little bit later oh not gg g there we go just give this a little bit of a gain factor there and so now we just need to calculate num samples at the beginning right so how much data we have left so we could say auto num samples at start equals buffer size because that's the total amount of data that we want that we want to move or want to add from our delay buffer to our main buffer buffer size minus the amount that we've already copied which is num samples to end right and then we could do once again buffer add from with ramp then we have channel destination start sample okay a little bit tricky to figure out here because what what's happened is that we we started at zero we copied num samples to end amount of data there uh so we want our index to start at num samples to end okay so so if we've copied num samples to n amount of data uh let's say let's say it was three so that would be zero one and two which means that the next index that we would need to copy to would be three right so that's why we have num samples to end and not num samples to end plus one okay then we have once again delay buffer get read pointer and then once again we'll just put channel here and our sample index is going to be let's think about this for a moment it will be zero because we're back at the beginning of the delay buffer because we read to the end of the delay buffer and now we're going back to the beginning to read from there and the amount of samples is num samples at top at start and then the the gains i'm just going to keep as that 0.7 for now and that's it we should we should be good so let's go ahead and test this out i've got ableton uh loaded to to launch when we uh when we when this actually builds so here we are we're in ableton and now we are loading loading and now we'll load our plugin and i've put it on the track and now grab a sample and grab this little drum beat and just if you have your headphones turned up really high just turn them down just a little bit just so i don't um blow your eardrums out and uh what we should hear is we should hear this beat and then we should hear a slightly delayed beat behind it [Music] okay so that so that works just fine right so let me just play it to you regular without the delay [Music] and then with the delay effect again so we see that this delay effect actually works works just fine right so so that's great so all right so now we're just going to move this out into a function all right so this feels like enough of uh enough of a body of work where we want to make this into its own function so what i'm going to do is i'm just going to cut this from here i'm going to create a new function i will call i will call this read from buffer just going to keep no arguments for the moment we're going to bring some in in just a moment and then we have now we're going to create this function here we're of course going to need some outside information uh in in here right so let's let's figure out what we need to what we need to know so right position and get sample rate those are all uh member variables so that's fine we're going to need to know the delay buffer size so we will say it delay buffer size what else do we need we need um so we need buffer size and buffer size we also need access to our buffer and to our delay buffer and to our channel so we got so let's see who do it channel it i'm going to just take these and put these into an order i think is sensible buffer size and delay buffer size then we need the buffer and the delay buffer so this will be let me go up here and i'm going to cheat a little bit so this is a reference to our buffer and then we're doing another audio buffer reference there to delay buffer and anything else i think i think that's good okay what i like to do what i've heard is a good rule of thumb is to put the larger objects at the beginning of the arguments i've heard that that's convention i don't remember where i heard that from but that seems like a sensible rule so i'm going to follow that and then what i need to do is i've just copied all of that and i'm going to then make sure i put it in my header file or else it will not work and then i'm going to do one more thing which is going to be let's let's test it again but let's change the delay the delay amount right so what if i wanted it to be a half second um so then it would be get sample rate times 0.5 right and we'll come back we will come back to this and uh and i'll actually make this into a real uh parameter that you'll be able to modify but just to show you that this actually this actually works so here we go so we're going to do half second delay and we are launching ableton and hopefully this will work so we got we got an error oh the error is that we need to put in these parameters so buffer we go read from buffer so this will be buffer delay buffer channel buffer size delay buffer size i could actually make this a little bit more concise and just calculate buffer size and delay buffer size in uh from the buffer and delay buffer that i pass in but i'm not going to worry about that for now and here we go so we're building we're loading ableton and now this should be a faster delay great so that so that works so i hope that you found that helpful and that's that's really it any sort of feedback that you might have please feel free to leave a comment uh in the um in the comments below please be sure to give it a like and subscribe if you're not already subscribed to the channel and i will see you next time
Info
Channel: The Audio Programmer
Views: 869
Rating: undefined out of 5
Keywords: Juce Framework, framework, juce audio, creative programming, audio coding, vst development, vst, software development, c++, audio programming, juce, plugin development, delay, ring buffer, delay effect, echo, plug-in, music tech, music technology
Id: eA5Mhbric6Y
Channel Id: undefined
Length: 32min 36sec (1956 seconds)
Published: Tue Oct 26 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.