Let's Build a Synth with Juce Part 4 - Cleaning Up the Oscillator Voice

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up everybody i'd like to welcome you to another juice tutorial in this juice tutorial what we're going to talk about is smoothing out a problem that we're having with our oscillator at the moment so uh for anybody who's been following along you may notice that there's a small clicking sound that happens whenever you press or release a note and a couple people have brought this to my attention and something that really puzzled me for a while on why this was actually happening and luckily zanakio's from the audio programmer discord actually helped shine some light on what we need to do here and uh and i thought about it a little bit further and did some more research and now i'm going to share that with you and it was a real learning experience for me so before we get started let me tell you about the audio programmer community on discord so now we have over 5000 members in our audio promo discord and these are developers of all levels uh who are all trying to make their own audio apps audio plugins and just learn more about digital signal processing and audio programming in general so you can come join us for free we're on the audioprogrammer.comforwardslash community and i'll put the link in the description below so let's go ahead and get started and currently at the moment we're in our synth voice class so this refers to the actual voice of the synthesizer and we only have one voice that's playing at at a particular time so i had this maybe naive understanding that originally that when you have the audio the audio callback that's happening in our main process block that in our processor i was under the impression that when we call synth uh render next block that what would happen is that this would just take your whole buffer and it would uh process the audio the process the synth audio or the voice audio from xero to whatever buffer dot get numb samples is and that that would be the end of the story and so that's why we have the synth voice render next block code as it is because there's this assumption that we're going to go from zero to buffer dot get num samples whatever that might be that's not exactly accurate at all because we have one small caveat which is that we have these midi messages that we're doing so when we when we press a key it sends a mini message or when we release a key it also sends a mini message as well and the thing is is that that doesn't just happen at uh sample zero in an audio buffer right you can you can press a key at any time in your audio buffer and it needs to know where an audio buffer it that key has been pressed and then it needs to be able to respond and uh start the note or stop the note or do whatever it needs to do where at the appropriate position in the buffer not just from zero to uh get numb samples right so we need to account for that and so we have this uh process of offsetting that actually happens in our uh in in our audio buffer when we get these many many messages in and i'll show you how that happens another thing that we need to consider and the reason for the clicking itself is that we are taking an audio we're taking our output buffer and the problem is is that when we get an output buffer into runner next block what may be happening is it may already have uh it may already have audio in the uh in the process block there may be something that's already happening in our process block and the problem is is that if what we're doing is we're taking our whole process block and we're uh putting it into we're actually applying this process of processing the oscillator processing the gain applying the adsr to the buffer and things like that but the problem is is that if we have a note that's in the release stage and uh and that we need to play another note what's going to happen is if we're applying this to the same buffer then what's going to happen is what's going to happen if the phase of our sine wave currently isn't at zero let's say it's at negative uh 0.18 and that when we start the uh when we start the phase of the note that we play at that particular time which is at zero you're going to get a disjunction between that negative 0.18 and the zero of when you're pressing the next note and that's why you get that clicking because uh what's happening is that your the buffer is playing a certain note and then when you're starting off another another note that says zero is saying hold on a second our note isn't isn't at phase zero so you're not going to get that smooth kind of transition between when one side note is done playing and when you actually hit the next note and you're going to get that clicking so to solve that what we need to do is we actually need to create another audio blo another uh audio buffer and then what we're going to do is we're going to take the oscillator code and the gain code and the ads are and we're going to apply that to the um to this other buffer and then we're going to add that uh that information to our main output buffer so if so if our output buffer already has uh audio information that is inside of it then what it's going to do is it's actually going to change the positions appropriately so we don't get that clicking okay so uh i hope that i've explained that properly so let's so let's go ahead and just create another audio buffer and we'll do this uh it'll be juice audio buffer and this needs to be we need to declare what type this is this is of type float and now call this synth buffer so let's look at the documentation again and see what it says because it gives us some great clues on what we need to do so once again it's it tells us exactly what we need to do here it says the output audio data must be added to the current contents of the buffer provided right so that's that's where that's why we're saying that we need two different containers of audio information in order for this to uh in order for this to work properly and so now what we need to do is we need to uh the one of the biggest mistakes that that we have when it comes to audio buffers and taking one buffer and taking the information moving it to another place is uh that a lot of people they forget to set asides to the to the uh to the other buffer and normally you would not call a method like this right so we have our synth buffer i think i call it synth buffer synth buffer and then we have set size so normally this something like this would not be allowed in in uh in an audio in something that's being called by the audio thread okay but in this instance we actually have uh some some some help from the juice team where they've actually been able to make this where the audio buffer doesn't allocate every single time that that we're going through the buffer unless it needs to allocate a little bit of a little bit more more memory to account uh for the size that we need you'll see what you'll see what i mean in a minute uh so set size so we see that we have this audiobuffer.set size and this is where we're going to put our synth information right and what we see is that we have these optional flags here uh so we have one flag that says keep existing content which is something that we don't want to do because we want to replace the content every single callback and then we have this boolean called clear extra space and we can see what that means here if clear extra space is true then any extra channels of space that is allocated will also be cleared if false then this space is left uninitialized and uh and then we have if avoid reallocating is true then changing the buffer size won't reduce the amount of memory already currently allocated but it'll increase it if we need more if it's false then a new allocation will be done okay so we don't want any new allocations in the audio buffer we do not want that to happen okay so what we're going to do is we're going to say uh synthbuffer.setsize and then for our new number of channels we just want to get how many channels that we have in our main audio buffer and then for a new number of samples we will just put num samples and then we will actually use these flags so keep existing content we want set to false clear extra space we want we want to be set to false as well and then we definitely want avoid reallocating set to true because we don't want to re-allocate any more memory every time we go through this audio callback only the minimum so if our number of samples has to be larger than whatever the size is that we have then it'll just allocate just enough to account for those additional samples so so now we're setting the size of the buffer and one thing that we want to do is we want to make sure that we clear this out every single time we call render next block so we will say synth buffer clear and then we will put the new information into our synth buffer so what we need to do is we need we need to make our audio block a reference to synth buffer rather than output buffer and then we're going to put in the oscillator information we're going to put in the gain information and then we're going to put in the adsr information okay and this needs to be synth buffer as well and then now what we have is we have our information that's in our synth buffer and what we need to do is we need to add it to our main output buffer which is coming in from our processor so just to show you again so we have our process block and this is our main process block our main audio callback where we have our audio buffer and then we're taking the audio buffer and we're putting it into render next block okay so that's that's why we're that's why we have this output buffer that's coming from our processor and now we need to add our synth buffer to our output buffer just in case output buffer already has some sort of audio information in it okay so what we need to do is we need to go through each channel so we will say 4 int channel equals zero channel is less than output buffer get num channels plus plus channel and then what we could do is we we can use the audio buffer method called add from so we need to use there are two ways we can do this we could use either one of these i'll show you the longer one so i think we need the longer one in this case so this is add from and this is where we add samples from one buffer to another and so once we do that then what's happening is that we're actually resetting if there's audio that's already inside of the buffer then what we're doing is we're readjusting so we aren't getting those uh really annoying clicks in our buffer so so what we will say is output buffer add from and we'll use this really long one here so destination channel so we're iterating through the channels and what we'll put in here is channel so destination start sample will just be start sample and then our source so we need our source here which is synth buffer and then source channel once again is channel and then we have source start sample which is going to be zero and the number of samples which is going to be num samples okay now what's interesting is that like i said it's it can be quite confusing and it confused me for a while when you look at your processor and you see synth dot render next block and you say okay i'm inputting a zero and i'm inputting buffer dot get num samples and then when i go to synth voice and um and i'm getting this i'm getting start sample which would be you would think it would be zero and num samples would be just whatever buffer dot get num samples is but that's not quite the case okay and i'll show you that uh right after we do a couple other things okay so there are a couple other things that we need to do here one of which is we should go back and we should read the uh the rest of the documentation here because there are a couple other things that we need to do okay so there's one that says if the voice is currently silent it should just return without doing anything okay so let's let's do that right so there's a boolean called i believe it's called is voice active so we can say if that is voice active so if we don't have any voices active we can just return which means that it will just return out of this method without calling any of these other uh these other parts of this method okay and then we also have actually i'm going to do this over here so let's see what else if the sound of the voice finishes playing during the course of this render block it it must call clear current note to tell the synthesizer that it has finished okay and we could do that by saying if adsr is active not so if if our adsr is not active then clear current note which will reset the state of our of our voice if our adsr is not active i also decided to uh add something up here in our start in our stop note where we could say uh if if we don't want to allow a tail off or if adsr is not active or is active not then we can clear current note and i will just do that just so we keep the conventions the same throughout okay there we go so i think we're okay now uh one other clue one other very uh um obvious thing that we need to do that we haven't done yet is we haven't given our adsr any sort of parameters okay so a little bit later we'll actually attach this to our value tree but for now what we could do is we could just set some default parameters for us to work with so we have adsr params which are parameters for our adsr so we could say adsr programs dot attack we'll just set some arbitrary values here so let's just say 0.8 and then decay equals um 0.8 again we'll set our sustain to 1 and then we will set our decay maybe to 1.5 seconds and now let's oh we need to do one more thing before we get moving which is that we need to set uh give our adsr object our new parameters with set parameters okay so we would just take the adsr params and put it in there like so and we should be okay so here we go hopefully hopefully the sound of the synth is loud enough for everybody so here we go and once we've once we've done this we will investigate and i'll show you uh some some other things that i learned here about this so now we have our synth so it's just it doesn't have a ui yet so here it is and if i hit this key oh we got a problem okay so let's let's investigate this because this is uh related to what we uh uh what we were talking about before so let's have a look at where this happens so this happened in our apply envelope to buffer okay so actually this needs to be adsr or synth buffer get num samples okay so and this needs to be zero okay because this is applying to uh this is of course applying to our synth buffer okay so we don't want to mix up start sample and uh so anything that we're applying to our synth buffer should go from zero to synth buffer dot get numb samples but anything that we're applying to our output buffer should apply from start sample to num samples and you'll see what i mean in just a minute why that needs to happen so very easy to get this confused and it was quite confusing for me as well and i will uh i'll show you a little bit more about why that needs to happen or about the reason that this is so easy to get confused so here we go back to our synth again hopefully this will work here we are so i hope you can hear that but you can hear no more clicking great so that's so that pro that problem is fixed now so we don't have that clicking anymore that's uh that's really annoying so now let's let's go a little bit deeper and let's have a look at what happens here so what i will do is i'm going to first say so as i said before one of the big keys about this is that when we put in when we call render next block and we give it arguments of xero and buffer dot get num samples we assume that when we when we actually go to git next when we actually go to render next block which is calling in our synth voice that start sample is still going to be 0 and num samples is still going to be output buffer not get num samples but that's not correct and this goes back to what i was telling you before so let's just set a uh let's set let's set the application to pause if start sample is not zero okay so we'll say something like if start sample it's not equal to zero then we'll just call jsrt false which will actually pause the execution of our plugin when this is called and then what we can do is we can look through the call stack and we can actually inspect what is what is happening okay so so there we go we're going to call this again here we have our synth okay it's running fine hasn't broken yet but now i'm going to hit a key now i'm going to hit a uh now i'm going to hit a midi key and now we get this breakpoint jsor false okay so so now we're in debug mode where we can actually just look through this and see kind of what's happening here uh so first thing that we want to do is we want to look at start sample right and we can see that start sample is actually 237 okay so that's where our sample that's that's where our start sample is and we can look at num samples and num samples is 19. so it looks like our turtle buffer size is 256 samples okay so we're actually going from 237 to uh and uh and we're only going uh 19 samples uh in this particular in this particular instance now we can look through the call stack okay so let's go all the way back to where we're actually calling this from the processor okay so we can go back back back back to where our process block was actually called and now we can look through and we can actually just process this step by step and see what's happening okay so is in render not when we call synth dot render next block we are starting from zero and then we have buffer dot get num samples uh so let's see here so if i go down here buffer i hope you can see this actually uh so we see that our the size of our buffer is 256 at this particular moment in time okay so synth dot render next block calls synth dot render next block all right and then we look in here and we see start sample is zero and we see num samples is 256. so so far so good okay so the question is how do we get how do we get from zero to 250 from where our start sample is 0 and 256 to where it's changing in render next block so let's see where this actually happens so now uh render next block is calling process next block and now we have this process next block okay and now we have this midi iterator find next sample position and start sample well this is a bit misleading because start sample at this particular at this particular moment like when this first called before at the current time it's 237 but when this before i hit the key the start sample was actually the start sample was actually zero so um so what happens is that this midi iterator uh actually looks at where where in the buffer we were when the key was hit and actually applies an offset in order to calculate where in the buffer where in our audio buffer we hit the midi key and how many how many samples we need to process uh to get to the end of our buffer so that's the reason why when we go to render when we get to this line 177 we have uh our start sample at 237 which is saying okay when you hit that key you were 237 samples into your callback and the number of samples which is 19 and which means okay your 237 in and you have to process 19 samples in order to get to the 256 256 samples and then from there it calls it called voice dot render next block or voice run the next block and that's how we got to where we're uh where we actually are in our synth voice and we call run the next block and we're ending up with our start sample at 237 rather than at zero because our midi iterator is actually saying hold on a second you aren't when you hit that key you weren't at uh at position zero in your audio buffer you were at position 237 so that's why that's happened okay and i will show you one more thing just to just to take that home okay so if we go into our so let me go back here and actually i will just keep this there for now uh what we'll do and and we're going to have a tutorial coming up soon where we actually talk more about the midi buffer class and um and um how to how to use this uh but there's a little handy piece of code in here that we could just that we can just throw in here really quick which is a midi buffer iterator so you have this uh you have this midi buffer iterator class which has been deprecated and this has been deprecated in favor of midi buffer iterator okay which is not in a separate um which is not inside the midi buffer class it's a whole separate class and in here we have this kind of handy little piece of code here to actually get the description of a mini message so this will actually get the information from any mini message that we send because it needs to know when we press the note how hard we press the note what note that we actually pressed and so on like i said we'll do a separate we'll do a separate tutorial uh in the future uh with my friend ayala mir and uh we will actually go a little bit further into how to actually use this but for now this will just uh not comb what i was saying about time stamps so we're we have a couple errors here so these are juice objects so we just need to pre precede them with juice like so and then what we're going to do so all this is doing is this is going through our our midi buffer which is uh looking out for any message any mini messages that we might be sending from a piece of equipment or in this case my keyboard or um you know from a midi controller and uh and it's basically getting us the description so i'm just going to change this to get get timestamp okay now the problem is this returns a double and write to log needs a string so all i'm going to do is i'm just going to wrap this in a juice string like so and then i'm just going to put a little precursor to that and i will say timestamp like this and now a little plus and we should be good to go so what this will do is if it's receiving a midi message it will it will show us the timestamp of where in our buffer that has actually happened okay and i will just show you what i mean here so we're just going to compile this it's interesting because my my uh my computer is blazing fast today which is quite unusual okay so here we go so now we're going to pop open this vst3 and back into our synth and here we are nothing here yet uh and then you're gonna see that i'm gonna hit a midi note and we should get a time stamp here okay and so we see that we got the time stamp and it says 224 right and that means that when we hit when we hit that key we were 224 samples into our audio buffer so that's why our start sample is 224 and why the number of samples is going to be whatever 256 minus 224 is which is going to be 32 so that's how many samples that we would have to process in that particular callback in order to actually get to the end rather than trying to go from zero to 256 every single callback okay so that's the reason why because if we hit it in the middle if if we hit a key in the middle of our callback then we don't want then we don't want a situation where uh we're either a we need to wait to the next callback where it registers that we've hit a key or b where we're trying to process too many samples at once so if we're in the middle of the callback then what we need to do is we need to offset and subtract however many uh samples in we are from however many samples there are in the buffer which will tell us how many samples we need to process i hope that's not too confusing for you uh so yeah i hope that that was helpful for you some people ask me to go a little bit deeper on some of these tutorials and uh and so i hope uh i hope that this was um you know helpful in understanding what's actually happening because i know it was very helpful for me thanks once again to uh the people that help us in the audio program or discord uh people like zanakios yalamir and uh who help us understand these tough things so if you like that uh and you found it helpful please be sure to like and subscribe and i will see you next time
Info
Channel: The Audio Programmer
Views: 2,306
Rating: undefined out of 5
Keywords: juce, synth, synthesizer, cplusplus, c++, juce framework, computer science, oscillator, synth voice, audio development, audio programming
Id: lSzGYt3oSNU
Channel Id: undefined
Length: 33min 27sec (2007 seconds)
Published: Fri Jan 29 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.