Coding Challenge #130.1: Drawing with Fourier Transform and Epicycles

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
(bell dings) - Hello, welcome to a coding challenge. I actually just finished this coding challenge, and I'm coming back to re-record a little intro to it. And what I made in this coding challenge is a drawing machine. Maybe let's call this a Fourier transform drawing machine. There's a few more things we want to do with it. There's going to be some followup videos. But this very, very long video, if you can stand to watch it, has, as part of it, at the end, this. This is the end result. I am using an algorithm called Fourier Transform to take an arbitrary signal, in this case, a sequence of X's and a sequence of Y's, the path of the Coding Train logo, thank you to Tom Fevrier. I will link to Tom Fevrier's Twitter, who provided the path for this particular logo, to trace the path of the logo through this sort of sequence of rotating circles, sometimes referred to as epicycles. Okay, so what's going on here? So, first thing I should mention is this is a continuation of my coding challenge Fourier Series. And so, what I did in that particular coding challenge, which was inspired by a Smarter Everyday video on the same topic, was create the Fourier series for a square wave. I don't know why I just had to write that. But in this video, I'm going to do something different, which is I'm going to use the Fourier transform algorithm. And these are different concepts. I somewhat conflated these in my previous video. The idea of the Fourier transform. Now, where I know this algorithm from, where I learned about this algorithm first in like, learning about coding and creative coding and new media and sound and video is with the terminology FFT, and actually, if you go into the p5 sound library, you'll see there is a class or function called p5.FFT. I don't remember exactly what it's called, but something like that. The F here stands for fast, Fast Fourier Transform. The algorithm I'm going to implement, by the way, is discrete Fourier transform. Why is this FFT thing, where is it typically used? Well, it's typically used in, it's in signal processing, but a familiar place where you might find that is in audio signal processing. So let's say you have some arbitrary sound wave that maybe looks like this, and it has a really high-pitched, awful, screeching sound in it. How would you filter that out? Well, if you could take this sound wave pattern and break it into a bunch of smaller sound waves, a bunch of sound waves that have varying amplitudes and frequencies, then you could take, you could sort of remove the one that has the high-pitched sound in it and then add them all back together and get a new sound wave. So the idea of a Fourier transform, I think I said this in the Fourier Series, but it's unsmoothie-ing a smoothie. If we could take a smoothie that's made with blueberry, mango, and strawberries, and like, separate it out and then put it back together without the strawberries, this is essentially what happens in signal processing. But in this video, what I'm going to do is instead of the signal being an audio, it's going to be a series of X values or a series of Y values, and eventually, there actually a way to do that with the X, Y values together that I will get to. So, I am not going to go too deep into the math in this particular video. I'm not going to derive the Fourier Transform algorithm. I'm not going to talk about Fast Fourier Transform. I'm just going to use a very crude, discrete Fourier Transform implementation just to get the thing working. If you want to know more about the math, though, let me reference a few really key references. The 3blue1brown video, But what is the Fourier Transform? will give you an infinitely better understanding of what the Fourier Transform algorithm is and what it does, and even how it works, way better than my trying to ramble through that over on the whiteboard. I would also highly recommend this GoldPlatedGoof video, Fourier Analysis for the Rest of Us, which goes through the details of the math in a much deeper way, and then there's this wonderful video from Mathologer, Epicycles, complex Fourier series and Homer Simpson's orbit, which will give you the full story of everything that I'm trying to do. But hopefully, what's useful to you that's different in my video than from these is I'm just going to sit here and attempt to code the thing. And I know that it's going to work, because I already did it, and here is the result. So enjoy. This is a very long video. I hope that if you watch it, you get the code, you make your own variation of it, please share it with me. You can go to thecodingtrain.com, to the particular coding challenge page, and then there's instructions on how to submit a user community variation thingy there. Okay, goodbye, enjoy, or not, or whatever. I am ready to start coding, finally. Now, this is where I left off before in my Fourier Series coding challenge, and the difference now is what I want to do is be able to have an arbitrary signal and then compute what all of these amplitudes and frequencies and phases should be. So, the way that I'm going to do that is, so let's think about this. This is really a bunch of Y values that I'm calculating. So let's make an array called something like Y, and this is going to be the signal, this is my signal. This could be audio, it could be Y positions, any arbitrary digital signal/array of numbers. Then what I want to do is I want to have like, the Fourier transform of that particular signal. So I want to say, fourierY = fourierTransform, or maybe like dft, discrete Fourier Transform, of the Y value. So this is the idea. The first thing that I need to do is compute the discrete Fourier transform of an arbitrary signal. Now I need some kind of signal. So I think what I'm going to absurdly do is hardcode the signal. Let's actually make it the square wave, and then we'll know if it kind of worked. So what is the square wave? Square wave would be something like 100, 100, 100, - 100, -100, -100, and then like, do that a few times. So this is going to be my arbitrary signal, which I'm just hardcoding the square wave. And we'll do some interesting things that we might, maybe I'll try a Perlin noise signal or just a sine wave signal. We'll try different things, random numbers, and see what that does. So then, this actual code here can largely stay the same, 'cause in theory, the difference is now, instead of following the specific Fourier Series for the square wave, I just need to take the results of my discrete Fourier transform. So this would be a loop that's going to go through the length of that transform, how many different wave patterns are there that I'm adding together, and then this ultimately, I'm going to have to figure out. So let's comment this out right now, and there's a little bit of an issue where I have this x and y as like, local variables here. But let's, I think this will be okay. So let's refresh this, and DFT is not fine. Okay, step one, let's write the discrete Fourier Transform algorithm. So, I'm going to start by making a function called dft. It's going to have some array of values, and now, I need to, at the end, I need to return something. (laughs) The idea is that I will return the discrete Fourier transform of those values. So, a couple things. One is I highly recommend, if you want to pause this video right now and read this particular article on the Algorithm Archive by James Schloss or the LeIOS YouTube channel. This is a really nice article about Fourier transforms and the discrete Fourier Transform algorithm, and this particular algorithm for FFT. But what I'm going to do is I'm just going to follow exactly what's here on the Wikipedia page. So, my signal is x sub n, lowercase xn. So what I need to do is basically, and the transform is capital X sub k. So I need to write a function that computes this exact equation and returns it as an array, and this is exactly what I'm going to do. This is exciting. Now, one thing I should mention is that in order to work with Fourier transforms, I need to understand something called a complex number. Now, if I recall correctly, the last time a complex number came up on this YouTube channel was probably in my Mandelbrot set coding challenge, where I visualized the famous Mandelbrot fractal, and I referenced something called an imaginary number, and I was way too informal and loosey goosey and jokey about how I talked about imaginary numbers being this pretend thing that doesn't exist, which is absolutely incorrect. The reason why the term imaginary is used is because there is no real number solution to the square root of negative one, but the square root of negative one is referred to in mathematics as i. I is a complex number. A complex number is a number with both a real, a, plus an imaginary component. So it's two real values, a real value and another real value kind of multiplied by i, the square root of negative one. So, this is the idea of a complex number, and by the way, another way for me to refer to a complex number is by its position on the complex plane. So if this were the real axis, and this were the imaginary axis, this would be a, and this would be b, and this is a vector representation, whoa, of this particular complex number. So, why do I bring this up? The reason why I bring this up is that the Fourier Transform algorithm, even if I start with an array of real numbers, single numbers, I'm going to apply the Fourier Transform and get out a complex number. What I'm going to return from that function is both a's and b's, otherwise known as the real component, which is often written in code as re, and the imaginary component, which is often written as im. So this is one bit that I really need to understand before working with this formula. So now is this moment, this moment that happens to you in life, where you see one of these formulas on a Wikipedia page or in a math textbook, and you're a creative coder making some kind of visualization thing, and you just want to stop. But together, you and me, we're not going to stop. We're going to figure out how to translate all this notation and symbols and stuff into JavaScript code. Now, again, it'll be super interesting to go down the rabbit hole of deriving all these formulas and the background for how Fourier transform works, but I'm not going to do that. If you look in the video description, there are several excellent videos and resources linked to that will give you that background. But I do want to mention one thing, which is quite important, which is that this particular formula in the top here for the discrete Fourier transform uses this symbol e, Euler's number, or the base of natural law. This is a very famous number in mathematics, much like pi, but there's also a very well known formula called Euler's formula, which looks like this: e to the i, which, complex number i, x, equals cosine of x plus i times sine of x. Really interesting, kind of looks like polar to Cartesian coordinate transformation. All this stuff is interrelated, right? But so, that is where, if I come back to here, this is where we get the second line here, using Euler's formula from the particular formula that's up top. But this is the one that I want to implement, and I have written the formula out right over here so we can unpack it a little bit. What are the components we need to understand? Now, really, if this were a math lesson about Fourier Transform, we wouldn't be using summation; we would be using integration. But because we're working on a computer, and I need to write a loop, and I don't have infinity as a tool that I can just use, I need to, instead of doing integration, do summation, and that's why also this is called discrete Fourier transform, 'cause I'm doing it over this sort of like, discrete space. Okay, so this means summation. So this should give you clue that I can just do like a for loop, going from zero all the way up to n. And by the way, n is going to be the length, the number of values I have in my signal, so the length of that original array. Oh, and then the other thing that's really important is that basically what I get to do is separate out. This is the real component, and this is the imaginary component. So even though this is all written as one formula, I'm going to sum up all the real components and all the imaginary components together. And by the way, as Simon, who has been watching this live, pointed out to me, there are only complex numbers. The term imaginary, it's really too bad that it's called imaginary, 'cause it's very misleading. But a real number is just a complex number with the imaginary component as zero. Okay, so, I should be able to start writing the code for this now, right? This is my signal. It's a little confusing that this is called X, 'cause I called it Y, but this is just the values, the vals. This n is vals.length in my code, and then, k, we're going to have to work out what k is. I know what cosine is, two pi, and all these things. So we're going to work out what k is. Oh, boy, I'm so silly. What is k? This should actually, this is, this is, I completely forgot to write what is quite possibly the most important part of this formula (instructor laughs) over here, which is capital X, big X, sub k equals. So, this is what I'm trying to calculate. I'm trying to create an array of k elements. At each element k, I'm going to sum up n from zero to the end. So there's a little bit of a nested loop going on here. I want to loop through every k, which is going from zero all the way up to n, and then also sum up. So k is going to stay constant within each one of these, and k is actually really the frequency, we'll see that, the frequency of the particular wave pattern in that slot. Okay, all right, so let me write the code for this. The first thing that I want to do is create an empty array. This is where I'm going to store all of the results. Then I need to write a loop, which is let k = 0; k < N; k++, and then I'm going to be saying fourier[K] = something. So, this, by the way, I mean, I could call this capital X if I wanted to be, and maybe I will, just to follow this notation exactly. So this is capital X; this is lowercase x. So let's actually, as silly as this might be, let's change this to x so I can use all the same notation as that formula. So, I'm trying to calculate this. Now, in order to do this, I need to, for each one, go through N. So this is where the nested loop comes in. To calculate each element of capital X[k], I need to sum up n going from zero to the n, okay? And then, I'm going to start doing this formula, okay. So, I need to sum up what? I need to sum up both a real component and an imaginary component. So let's make a real component and an imaginary component. The real component is going to go up by some amount, and the imaginary component, ooh, I do not want to ask Siri. Actually, Siri, please, could you help me with this Fourier transform problem? Just write the code for me. So I'm going to sum up the real and imaginary components, and then, I'm going to make some object, which is basically just, what's that thing called where, but I'm not going to worry about it right now, the fancy ES6 way of making an object, if I'm doing this. So then I'm going to make an object that has the real and imaginary components, and I'm going to return it. There we go. So this is the process, whoo. I could use, HollowBrian in the chat is saying use a p vector. I could certainly use a vector object, but I'm going to write it this way. Okay, so here we are, good, good, good, good, good. That's my, I've got this and this. Now I just need to add this stuff in there. So let's do the real component first. Now, one thing you might notice, this appears twice. So if anything ever appears twice, that might give you a hint to just put that in a variable. So let's do that. So let's say, I don't know what to call that. I'm going to just call that something. (instructor laughs) Somebody give me an idea what to call it. I'm going to say TWO-PI times k times n divided by n. All right, so the chat's telling me to call it angle or some angle name, but actually, phi is a good one, 'cause it's the value that's going into cosine and sine, which is like an angle, or a Greek letter phi is often used. So I'm going to call it phi, and then I'm going to say, the real component equals, and I'm looking over there 'cause that's where my formula is written, is going to say x[n] times cosine of phi. Boy, this should look strangely like a lot of code that I write a lot. And then, x[n] times, and this should say cosine, times sine of phi, but one thing you'll notice here is that this, there is a minus here. This minus here is not, is quite an important detail. So, what I'm going to do is I don't know the best way to handle this, but I could just say minus equals, but maybe I'll, yeah, let's just say minus equals. Let's do that, okay. And then, this is called enhanced object literals, thank you. I can just say this. So this will give me that, and there, okay. So, (instructor laughs) all of that explanation, wow, and here we are, discrete Fourier transform. All right, so thank you to the chat for asking a couple key questions and for pointing out some errors. For example, I forgot to actually define what n is, which is x.length. You know, I should probably get in the habit of using the variable declaration const when I know it's something that's going to stay a constant like this, so I will attempt to use that here. These cannot be const, because I'm adding them up together. Now, another thing that is typically done with discrete Fourier transform is to take the sum, and then average its contribution over n. So I would also say the real component equals the real component divided by n. The imaginary component is the imaginary component divided by n. So I'm going to add that in. And then someone asked me, well, oh, but this is another question that came in, which was the question, oh, there's i here. Why don't I see i in your formula, in your code? Where is i in your code? And so, i isn't explicitly in here, but what I'm doing, I'm referencing i by separating out the real and imaginary components. So, the imaginary component is always paired with i, and the real component is in the form a + bi. So this is a, that's b, but I don't actually have to put i in the equation itself. So, let's save this. Let's feel happy that we completed something. I'm going to refresh, I'm going to go back to my code page. So let's just see if that function dft does something, it doesn't actually produce an error. So I have the y array, which is my hardcoded square wave, and if I call dft on y, here in the console, we can see, I get, right, I had 12 values in my signal. I get 12 complex numbers back, each one with a real and an imaginary component. So this looks good, in the sense that there are numbers here. I don't see an error, no red, no not a number. So hopefully, we're in the right place. Now, the question is, what, what, what do I do with these real and imaginary components? How do those things actually become circular epicycles? For a circular epicycle, what do I need? I need an amplitude, right. That's basically the radius of that circle. I need a frequency, which is how many cycles through the circle does it rotate per unit of time, and then I also need, this is what's called a phase. And the phase, another way is think of it as an offset. So where does the cycle begin? Where does that circular wave pattern begin? That's the phase. So I need these three things. So, somehow, what I need to be able to do is I need to be able to say right here, well, the frequency is something, the amplitude is something, and the phase is something. And the secret to this lies in the fact that a complex number is like a vector, and in fact, here we go. The amplitude is the length of that vector, and the phase, is the angle of that vector. Well, so that's amplitude and phase, but what's frequency? Well, guess what? I don't know if there as a clue to this on that Wikipedia page, but the frequency is actually just k. Frequency, yeah, discrete frequency components. So the whole point of doing this is to take the signal and divide it into a bunch of discrete frequency components, zero, one, two, three, four, et cetera. So, here we go, frequency is k, and that's a little bit redundant, but I might do something with sorting later, so I'm going to need to keep track of that. Amplitude is the square root of the real times real plus imaginary times imaginary. This is basically the magnitude of a vector, the square root of each component squared. This is Pythagorean's theorem at play. And then the phase is that angle, which I can use the atan2, and the y would be the imaginary? I think it's this. I'm going to just reference the code that I wrote before. (instructor laughs) Yes, I got it right. So this would be the phase. So now, I can add frequency, amplitude, and phase here. And I can refresh this page, I can say dft(y), and let's take a look at any one of these, and we can see all right, I've got an amplitude, I've got a frequency, I've got a phase. Whoo! We are ready. We are ready to start actually now putting this into our code. And the good news is, we have the code for drawing these epicycles already. I commented it out. It was right here. So, if, in fact, I have this fourierY array, I have this code from before that was drawing the results of those epicycles. So, I can comment this all back in, but now, it's not a specific Fourier series for the square wave; it's whatever's come out of my Fourier transform. And in this case, n is actually, it's confusing that I'm using n here, but I'm actually going to just call this frequency, is fourierY.freq, and the radius is fourierY times amplitude, and now, not times, is the amplitude. So the frequency is the frequency, the radius is the amplitude, and now I can say multiply time times frequency plus that phase. So, and I know I could have, I could get these things out into a variable in like, one line, but I'm just going to write this in here, phase. So, all the code remains the same. The difference is what I'm going to do. Time is the that's moving forward, right? It's the angle, and if the frequency is one, it takes one unit of time for it to rotate a full rotation around. Phase is the offset and radius is the amplitude. So, one thing that I have to be very careful about here is that I can't just arbitrarily have this 0.05 thing here. What I need to do is I need to have like, the value dt. What is the amount of time I move each frame of animation? This would be TWO_PI being a full cycle divided by how many frequency values I have. So now, time goes up by this, and we should see now, dare I say, (instructor laughs) nothing? (bell dings) Oops, I have a terrible error here, ah! I forgot to put index i here. I've got to pull out, right, I'm getting the frequency, amplitude, and phase of each element of the array fourierY. Whoops, sorry about that. There we go, all right. So this looks kind of like maybe it's right, but it doesn't look right at all. Like, something's happening that's pretty decent, but it's wrong. So here's the one thing that's a little bit unfortunate. (instructor laughs) I'm off my 90 degrees here, I'm pretty sure. So let's just add HALF_PI in here, because, and there we go. Now, (instructor laughs) this is actually correct! This is sort of crazy, though, 'cause it's kind of like, is this really, what we're doing? The thing is I have so, I have so few values. So it would make more sense for me to actually count, to precompute some kind of more interesting signal. So, let's forget about hardcoding the signal for a second, and let's just say I'm going to have a signal with a hundred values, and let's just make them all random numbers. This is going to be a little bit insane, probably, but let's pick a number between negative 100 and 100. So you can see, look at this. This, there it is! This is the crazy set of epicycles to draw these random numbers! (instructor shouts) I could also now do Perlin noise instead, and like, just have it increased by some arbitrary amount. All right, so there we go. I'm kind of doing nonsense here. But the point is, any arbitrary signal that I have, I can now compute the Fourier. And you can see, by the way, this is it cycling back to the beginning. That's why it almost looks like this crazy heartbeat, and there's this extra bit here of it, like the first one not rotating at all, just with this offset up, because the values itself don't perfectly average a round zero. But this is not the point. This is not the point of what I want to do. The point of what I want to do, and let me just show you to be clear. If I just made this like i, like a linear function, you can see this, look at this. These are all the epicycles for basically now, I've got the triangle wave, 'cause it's going down and then back up at the top and down repeating over and over again. Okay, (instructor claps and laughs) We are getting somewhere. Okay, deep breath. First thing is, in my excitement and exuberance over what I've accomplished, I was calling this a triangle wave. You know, there's kind of a triangle there. This is a sawtooth wave, which is what I've recreated right here. But, I need to take a breath here and talk about what the next step is going to be. The idea now is what I want to do is I want to be able to draw an arbitrary path, which, in addition to having Y's, I want to have X's as well. So I need to do the Fourier transform twice. Now, ultimately, there's another way of doing this where I do one Fourier transform on a set of complex numbers where the real and imaginary components are the X's and the Y's, but I'm going to stick into this sort of like, simple place that I am right now, and I am going to add two signals now, an X and a Y. So I need the transform for both the X and the Y. I'm just going to like, in a sort of silly way, have the X's and the Y's be the same, and then I'm going to apply the same exact Fourier transform to both the X's and the Y's. And now, here's the complicated part. This loop here is visualizing the results of the Fourier transform as a sequence of rotating circles, epicycles. So what I want to do though is, I think what's going to be good is to refactor this, not later, but now. (upbeat disco music) ♪ I will refactor this later ♪ - And put this in a function. ♪ You know I will refactor this later ♪ - Let's call it epicycle. ♪ I will refactor this later ♪ ♪ You know I will refactor this later ♪ - So the idea is that maybe I would get an X and a Y. Like, what's where it's going to start, and then, I get the set of epicycles, and I draw them all. So this is a generic fourier[1]. So in other words, the idea being that I could say, and I'm going to get rid of this translate, and I'm going to say, basically, what I want to be able to say is like, draw, what did I call it? Draw Fourier? No, I called it epicycles. Epicycles, like 100, comma, what's the size of this? Windmill at 200, a fourierX, and the epicycles, you know, 300, 200, fourierY. So we should see both of these now. Let's take a look. Epicycles is not defined. Oh, I don't know why I did this capital C. That's probably unnecessary. Okay, great. So we can see, look. So those are, and now, the epicycles are the same, 'cause they have the same values. Let's give them different values. So let's actually, let's do something kind of goofy. Let's make it draw a circle. So this is going to be 100 times cosine of, this is so silly, of an angle, and that angle is map(i), which goes from zero to 100 to zero to TWO_PI. This is just so I can have some kind of path to work with that's like, very recognizable, so I know whether it's working or not, and then Y will be the sine of that. So we can see, okay, this looks promising, right? Now we can see here are the epicycle calculations for the X's and the Y's. Now, one thing that's off, though, is that, remember how I had to like, I kind of like, glossed over this, but I was like, had the thing on its side? This HALF_PI is really like the rotation of the whole thing, of how I want to display it. So let's make that an argument here, and we'll call that rotation. And so the Y's, oh, that's when I, do I want it? Yeah, I think I can just do that when I draw it, that's fine. Where is this now? (instructor laughs) The Y's should be off by HALF_PI, and the X's should not be. And so now look at this. Okay, so in theory now, (instructor laughs) we're getting somewhere, don't worry. What I want to do, let's position this over here, and then let's position this a little bit further over in here. And now what I want to do is over there, where that mouse pointer is, I want to take the Y from here and take the X from there and draw the path. So, instead of wave, I was drawing a wave pattern, I now want an array that is the full path. I want to basically get the end result. So let's have this epicycles function return, it should return a value which is a vector. createVector with x and y. So whatever the end result, like the last x and y point, the end of that epicycle sequence, and then, so I have vx is this, that's the vector for the X's, and vy is that, and now I want to say, path.unshift. Okay, so I want to create a new vector. This is like, where I want to draw the thing, I need a vector, which is the x component of vx and the y component of vy, and then I want to put that in the path, and then this, let's get rid of this line for a second. I'm going to, instead of drawing this wave, I'm going to iterate over the path. I don't know if I've, this is a little bit of a strange refactoring of what I had before, but I think it's going to make sense to you in a second. Let's see. I might have to come back and explain this. (instructor laughs) Let's put this in here. And wave is not defined, because it is path, path. I'm not going to worry about this right now. Let's kick that out. All right, it's over there. Oh, it's in the wrong spot. (instructor laughs) So, this is actually correct. I just put the, now I realize, like, (bell dings) this is correct; I just put these in weird places. Like, I want the one that's doing the Y over here and the one that's doing the X over there. I don't know why it just like intuitively didn't put them in the right place. So this should be 400, 50, and this should be like, 50, 200. This has nothing to do with Fourier transforms. It's just the weird way. There we go. (instructor laughs) So, I wanted to see them like this. So now you can see, those are the Fourier transforms for this particular circle, and let's add a line back in now, which is basically this thing. So I also want to draw a line from the vx.vy, oh, so, vx.x, vx.y, to v.x, v.y. Like, I just want to draw these two lines, and the the same thing from the y.x and the y.y. Wow, my naming is wildly confusing here. So this could definitely use some refactoring, but. There we go. Now we can see those lines. So this is good. Now, I don't like the way this is spaced out, so let's, one way to fix that would just be to make this thing smaller, and that sort of helps me a little bit. But this can move over. I don't know why I put it all the way over there. Let's move this over to 300. Okay, this is a little bit better. Now, let's make something more interesting here, which is, (instructor laughs) let's start using Perlin noise again. So I'm going to say noise and noise, plus some arbitrary amount, and we can see, look at this. So you can see that this works, and let's give it, let's make the amplitude bigger, and let's give it like, 500 values, whoops, and there's also no reason why. This is very silly. These should just be in one loop. But let's give it more values, and let's just say, you know, i divided by 50. I'm just doing arbitrary stuff, 'cause the whole point of this is to do a drawing. All right, but we can see how this now will take any arbitrary signal and compute the Fourier transform for the X's and Y's and draw that path. (instructor claps) Now, the nice thing about this is I'm about to almost instantaneously do something to make this much more interesting. I am going to go and grab the Coding Train logo path. The whole point of this is forget about computing a path. I want to have a known path, a drawing. (bell dings) I am back, and I have brought in a JavaScript file that just has a big array of X's and Y's, all in a variable called drawing, which is the continuous path of the Coding Train logo. Thank you to, I'll link to Tom Fevrier on Twitter for sending me this particular path. So what I'm going to do now is if I go to the code, we've done all of this work for this moment. (instructor laughs) Oh boy, this better work. I'm so excited. I'm going to go here, right, and now, I'm going to say, I mean, this is a little bit silly, the way I'm doing this, by drawing.length, i++. I'm going to go through, right? Remember, this variable, drawing, is just an array of X's and Y's, and I'm going to make the X's drawing[i].x, and the Y's, drawing[i].y. Now, here's the thing. I happen to know that the complexity of that drawing is way more detailed than I need, and it's going to run very, very slow. So I'm actually going to add a variable called skip, and I'm going to like, skip every 10, and I'm going to say += skip, and then I'm going to change this to push. So I'm going to skip and only do every five vertices of the drawing. I'm doing this in advance, 'cause I already know, looking at that, I don't need that many points. So this should now give me all of the points, all of the X's and Y's, from that particular drawing. (instructor laughs) (drum roll) I'm about to go hit refresh, and hope that this works. (instructor laughs) There it is! (dramatic fanfare) So there it is. Now, this doesn't look as beautiful as it possibly could, and there's a couple reasons for that. Right now, one thing is, these look like these weird alien creatures, by the way, but it would be really nice to have the epicycles render in order of amplitude. So right now, they're rendering in order of frequency, and it's like this strange machine, almost like, drawing machine. It would be amazing if someone could build this physically. But what I'm going to do is sort them. So, I'm going to say fourierX.sort, and fourierY.sort. Now, the JavaScript sort function allows you to pass in a callback, which is essentially a function that tells you how to compare each element, and I want to compare them by amplitude. So I can actually say, any two arbitrary elements, and I'm going to use ES6 syntax for this. If you haven't watched my arrow syntax or higher order array videos, this is something that will give you background for this. And then I can just say, I think a.amp - b.amp, right? Because if I get a positive number, it'll put one in front of the other. If I get a negative number, it'll put the other one. If they're equal, it'll leave them. So this is sorting each one of those by amplitude, and if I refresh this, oops, sorry, the smallest one is, I sorted it in reverse order. So let's put b there, b here, a here, and let's also give myself, let's clean up some stuff here. Let's make this 800 by 600. Let's set the offsets to width divided by two, 100, height divided by two, whoops, sorry, 100, height divided by two. Let's set these offset a little bit more. Let's refresh, whoops, let's shrink this up, and let's move this down a little bit. Will this work? (instructor laughs) All right! (bell dings) (train whistle toots) This is the thing finished! 72 hours later, there it is. Oh, it's off the bottom. Oh no, it's kind of sitting right there perfectly. How did my math, that was totally accidental, by the way. Now it's just going to draw it again over and over again. But I want to do a couple other things. One thing I want to do is if time does a full cycle, then I want to set time back equal to zero, and path, clear the path. All right, this concludes this particular coding challenge, where I took a discrete Fourier transform, this particular math function, I applied it to an arbitrary signal, two signals of X's and Y's. Then I rendered those as rotating epicycles, and I had to draw the path. Whoo, I'm very excited that I accomplished this. So, there are two things that I want to do, probably more than two, and those are going to come in separate videos, if you made it through this one. I am going to, first, I'm going to take a break, and I'm going to come back, and I'm going to make it so that the user draws something, and then it computes the Fourier transform. That really, by the way, you should go do that yourself right now. So take this code that I've released, find the link in the video description, and go make a version where the user draws something and then do the Fourier transform. That's a fun exercise. I'm going to do that in the next video, and then, I am going to rewrite this so that I have the Fourier transform done with the X and Y's together as a complex number, and I just have one set of epicycles rendering the full path. But I kind of like these two X and Y machine things. It's kind of cool. Oh, yes, and Melvin in the chat is saying, oh, you could use the Quick, Draw dataset. So I'm going to leave that to you, the viewer. Please make this, share it widely. Make a version of this that renders random drawings from the Quick, Draw data set. That would be super fun. I would love to see that, okay? If you make any of these exciting, fun, beautiful, strange, ugly, whatever they are, variations on this particular coding challenge, please go to the link to codingtrain.com, look for the instructions on how to submit your variation, and submit yours, and if you have trouble doing that, file a GitHub issue or something saying, I want to submit mine, but I don't know how, and we will help, okay? Goodbye, everybody. (bell dings) (train whistle toots) And I will leave you with something that really needs to happen to this code. (upbeat disco music) ♪ I will refactor this later ♪ ♪ You know I will refactor this later ♪ ♪ I will refactor ♪ (upbeat pop music) (bell dings)
Info
Channel: The Coding Train
Views: 232,391
Rating: 4.9712563 out of 5
Keywords: computer science, programming, daniel shiffman, coding, the coding train, coding challenge, creative coding, code challenge, coding train coding challenge, javascript (programming language), programming challenge, fourier series, fourier transform, fourier visualized, fourier series visualization, fourier series square wave, fourier transform square wave, discrete fourier transform, discrete fourier transform example, epicycles fourier series, epicycles drawing
Id: MY4luNgGfms
Channel Id: undefined
Length: 46min 9sec (2769 seconds)
Published: Thu Jan 24 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.