Placeholder Image

字幕列表 影片播放

  • (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)

(bell dings)

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

編碼挑戰#130.1:用傅立葉變換和外延畫法繪圖 (Coding Challenge #130.1: Drawing with Fourier Transform and Epicycles)

  • 4 0
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字