Two days ago, when I was listening to songs on NetEase Cloud, I saw a function called whale sound, which looks like the following when I click on it. This is a very common music rhythm animation. In the process of music playing, the curves around the CD shake, bringing some auditory and visual double impact to the listener.

Today try to use code to achieve a [similar] effect, anxious friends can skip the article, directly view the final effect. Like friends do not be stingy points like, comments, attention, so THAT I will be more motivated to produce relevant articles.

(NetEase’s whale sound is actually exclusive to VIP vinyl.)

Antecedents feed

By default, this article requires readers to have mastered the basic usage of Canvas. If you are not familiar with it, you can review it first. I won’t repeat the basics of how to draw lines on Canvas.

I’ve written basic curve animations before.

The only difficulty

The only difficulty in this effect is how to achieve line animation, which has a certain randomness. Math.random() is the JS method that generates random numbers between 0 and 1 on average. However, because its random results are too discrete, there is no correlation between the results of the previous two executions, and the results directly applied to the vision are often not ideal. For example, on a plane, if you move the x-coordinate one pixel at a time and connect the y-coordinate to a random height, math.random () will produce something like this.

It is obvious that such random method cannot be translated into random curve animation. In order to achieve the natural random line animation in the renderings, an algorithm called Berlin Noise will be used in this example.

What is Berlin Noise?

Berlin Noise is a very powerful algorithm that is often used to generate random content, such as waveforms, undulating textures or textures in the game world. Berlin Noise has the concept of dimensions, for example one dimension can be used to simulate hand-drawn lines, and two dimensions can be used to generate flat effects such as flame, fog, etc.

As a general user of the algorithm, the concrete implementation of the algorithm will be put aside for now. Find the related implementation library simples-Noise on Github. Let me write two demos just to give you a sense of the power of this algorithm.

The basic usage of simplex-noise

const simplex = new SimplexNoise();
const value2d = simplex.noise2D(x, y);
const value3d = simplex.noise3D(x, y, z);
const value4d = simplex.noise4D(x, y, z, w);
Copy the code

Create an instance of noise2D, noise3D, noise4D with a number of dimensionally dependent arguments, two for 2D and three for 3D. Each of these methods returns the result of an algorithm calculated between [-0.5, 0.5].

As in the original example, change to move one pixel per x-coordinate and calculate the noise result based on the time parameter TY (increment per time).

// noise2D returns an interval of [-0.5, 0.5], which is computed to make the line fall on the screen
this.y = this.noise.noise2D(this.ty, 0) * window.innerHeight * 0.5 + window.innerHeight * 0.5;
Copy the code

Note that in the above example and the math.random () example, the X-axis moves at the same rate, there is no amplification of the X-axis, and there is no code for gabessel curves, and the natural curve on the image is generated by noise. You can modify the code on Codepen to see the effect.

Graphically, there is some correlation between the two adjacent random Y values. The difference between the two y values is related to the parameters passed to noise2D. In the above example, the TY parameter is modified after each rendering to affect the noise result next time.

// Modify ty after each redraw
this.ty += 0.01;
Copy the code

The closer the two parameters are, the smaller the difference between the results is. The larger the difference is, the larger the difference is. You can experiment by modifying the code on Codepen.

this.ty += 0.001;
Copy the code

this.ty += 0.1;
Copy the code

The X-axis is moving at the same rate

You can also animate random images generated by noise3D. (Random image 2D is enough, one more dimension for animation)

The core code is as follows:

ctx.save();
const size = 800;

var imgData = ctx.createImageData(size, size);
this.tx = 0;
for (var x = 0; x < size; x++) {
  this.ty = 0;
  for (var y = 0; y < size; y++) {
    // noise randomly generates an alpha value
    const alpha = (this.noise.noise3D(this.tx, this.ty, this.tz) + 0.5) * 255;
    const index = (x + y * size) * 4;
    imgData.data[index + 3] = alpha;
    this.ty += 0.01;
  }
  this.tx += 0.01;
}
ctx.putImageData(imgData, 0.0);
ctx.restore();
this.tz += 0.01;
Copy the code

The code above iterates through each pixel, calling noise3D to generate a random alpha value. The logic of passing in three parameters, TX and TY, is to bring the two adjacent pixels closer together, and tz exists to make new changes with each redraw to generate animation. If tz is a fixed value, there will be no change after the image is generated.

Conclusion: When using the noise method, dimension values are passed in every time, and inputs with the same dimension values give the same result. If you pass in a similar value of dimension, the next time you do it, the result will be similar to the last random result.

application

Get the algorithm right, and the rest of the work is negligible. The idea was to use Berlin Noise [randomly] to generate the coordinates of 360 points and connect all the points with lines.

First try

for (let degree = 0; degree < 360; degree++) {
  const radius = (degree / 180) * Math.PI;
  const length =
    base +
    this.noise.noise2D(
      radius,
      t
    ) *
      scale;

  this.vectors.push({
    x: length * Math.cos(radius),
    y: length * Math.sin(radius),
  });
}

this.drawLine();
Copy the code

Here, instead of randomly generating x and y values of a point, we first create [random length], and then convert it into coordinate values through length and Angle. However, the immediate result is this.

Since there is no direct correlation between the radius at the beginning and the end, there will be a very obvious split in the animation.

Use the periodicity of trig functions

In order to solve the difference between the starting point and the ending point, the periodicity of trigonometric function is used here to calculate the sine/cosine value of each Angle as the dimension parameter of noise. Then the coordinates of each two adjacent points are similar, and the final result is similar.

Modify code:

const length =
    base +
    this.noise.noise2D(
      Math.cos(radius),
      Math.sin(radius),
      t
    ) *
      scale;
Copy the code

In the same example, using noise3D, the third parameter, T, increases with each rendering, changing each time, and the noise calculation is different (but similar), thus creating an animation.

Finally, string the dots together and set them to pulsating music.

Set to a cool rap, if you see here, make sure you don’t miss the exclusive version. All of the code is available on my CodePen home page.

One step closer to

So far, music playback has nothing to do with animation. If you need linkage, you can also use the Audio API to get the music frame information and reflect it on the animation in real time.

const audioElement = document.getElementById("audio");
// Create audio Context
const audioContext = new AudioContext();
const audioSource = audioContext.createMediaElementSource(audioElement);

// Create a parser
const analyser = audioContext.createAnalyser();

// Correlation parser, and finally output to the device sound
audioSource.connect(analyser);
analyser.connect(audioContext.destination);

// The window size of the sample
analyser.fftSize = 256;

const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);

function renderFrame() {
    requestAnimationFrame(renderFrame);
    // Each requestAnimationFrame callback updates dataArray
    analyser.getByteFrequencyData(dataArray);
    // Audio data
    console.log(dataArray);
}
Copy the code

reference

  • An article to understand Berlin noise algorithm, with code to explain
  • Perlin Noise – The Nature of Code

I also opened a wechat public account, please scan the code to follow, the follow-up content will continue to update to the public account.