Help us understand the problem. What is going on with this article?

threeでsound

More than 1 year has passed since last update.

概要

shadertoyのmainsoundが魔法だったので、調べてみた。
threeで実装してみた。

参考にしたページ

https://qiita.com/tatmos/items/9ed8b47a44823f96dddf
https://codepen.io/fand/pen/baVdpK

サンプルコード

const DURATION = 6;
const WIDTH = 512;
const HEIGHT = 512;
const fragmentShader = `
precision mediump float;
uniform float iSampleRate;
uniform float iBlockOffset;
#define PI              3.1415926535
#define A               0.0
#define B               2.0
#define C               3.0
#define D               5.0
#define E               7.0
#define F               8.0
#define G               10.0
float rand(vec2 co)
{
    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}
float calcHertz(float scale)
{
    return 441.0 * pow(2.0, scale / 12.0) * PI;
}
float calcHertz(float octave, float note)
{
    return calcHertz(octave * 12.0 + note);
}
float rect(float time)
{
    return sign(fract(time / PI / 2.0) - 0.5);
}
float rect2(float time)
{
    return sign(fract(time / PI / 2.0) - 0.25);
}
float rect3(float time)
{
    return sign(fract(time / PI / 2.0) - 0.125);
}
float eg(float time, float gate)
{
    return  exp(-gate * (time));
}
#define Sin1(u, v)          sound += clamp(sin(time * calcHertz(u, v)) * (1.0 - localTime2 + sin(time * 80.0) * 0.1), -0.3, 0.3);
#define Rect1(u, v, l)      sound += rect(time * calcHertz(u, v)) * l;
#define Rect2(u, v, l)      sound += rect2(time * calcHertz(u, v)) * l;
#define Rect3(u, v, l)      sound += rect3(time * calcHertz(u, v)) * l;
#define BD_Rect1(u, v, l)   sound += rect(time * calcHertz(u, v +  eg(localTime2, 2.0))) * l;
vec2 mainSound(float time)
{
    float localTime = mod(time * 0.5 , 8.0);
    float localTime2 = mod(localTime , 1.0);
    vec2 sound = vec2(0.0);
    if (rand(vec2 (localTime2)) * 2.0 > 0.2)
    {
        localTime2 = mod(localTime * 16.0, 1.0);
        BD_Rect1(0.0, C, eg(localTime2, 8.0));
    }
    if (localTime < 2.0)
    {
        localTime2 = mod(localTime * 2.0, 1.0);
        float op0 = sin(1.0 * calcHertz(1.0, C) * time) * eg(localTime2, 1.0);
        float op1 = sin(1.14 * op0) * eg(localTime2, 2.0);
        float op2 = sin(1.5 * calcHertz(1.0, C) * time) * eg(localTime2, 5.0);
        float op3 = sin(2.14 * op2) * eg(localTime2, 2.0);
        sound += vec2(op1 * 0.5 + op3 * 0.5);
    } 
    else if (localTime < 4.0)
    {
        localTime2 = mod(localTime * 2.0, 1.0);
        float op0 = sin(8.0 * calcHertz(1.0, C) * time) * eg(localTime2, 3.0);
        float op1 = sin(6.5 * op0) *  eg(localTime2, 1.0);
        float op2 = sin(3.14 * calcHertz(1.0, C) * time) * eg(localTime2, 1.0);
        float op3 = sin(6.5 * op2) *  eg(localTime2, 1.0);
        sound += vec2(op1 * 0.5 + op3 * 0.5);
    } 
    else if (localTime < 6.0)
    {
        localTime2 = mod(localTime * 1.0, 1.0);
        Rect1(0.0, C, eg(localTime2, 1.0));
        Rect1(1.0, E, eg(localTime2, 1.0));
        Rect1(1.0, G, eg(localTime2, 1.0));
        Rect1(1.0, B, eg(localTime2, 1.0));
        sound *= 0.25;
    } 
    else if (localTime < 7.0)
    {
        localTime2 = mod(localTime * 3.0, 1.0);
        Rect2(0.0, C, eg(localTime2, 1.0));
        Rect2(1.0, E, eg(localTime2, 1.0));
        Rect2(1.0, F, eg(localTime2, 1.0));
        Rect2(1.0, A, eg(localTime2, 1.0));
        sound *= 0.25;
    }
    else if (localTime < 8.0)
    {
        localTime2 = mod(localTime * 6.0, 1.0);
        Rect3(0.0, G, eg(localTime2, 2.0));
        Rect3(2.0, E, eg(localTime2, 2.0));
        Rect3(1.0, F, eg(localTime2, 3.0));
        Rect3(1.0, B, eg(localTime2, 3.0));
        sound *= 0.25;
    }
    return vec2(sound);
}
void main()
{
    float t = iBlockOffset + ((gl_FragCoord.x - 0.5) + (gl_FragCoord.y - 0.5) * 512.0) / iSampleRate;
    vec2 y = mainSound(t);
    vec2 v  = floor((0.5 + 0.5 * y) * 65536.0);
    vec2 vl = mod(v, 256.0) / 255.0;
    vec2 vh = floor(v / 256.0) / 255.0;
    gl_FragColor = vec4(vl.x, vh.x, vl.y, vh.y);
}`;
const ctx = new window.AudioContext();
const node = ctx.createBufferSource();
node.connect(ctx.destination);
node.loop = true;
const audioBuffer = ctx.createBuffer(2, ctx.sampleRate * DURATION, ctx.sampleRate);
const canvas = document.createElement('canvas');
canvas.width = WIDTH;
canvas.height = HEIGHT;
const renderer = new THREE.WebGLRenderer({ 
    canvas,
    alpha: true
});
const wctx = renderer.getContext();
const uniforms = {
    iBlockOffset: {
        type: 'f', 
        value: 0.0
    },
    iSampleRate: {
        type: 'f', 
        value: ctx.sampleRate
    },
};
const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.ShaderMaterial({ 
    uniforms, 
    fragmentShader
});
const plane = new THREE.Mesh(geometry, material);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
camera.position.set(0, 0, 1);
camera.lookAt(scene.position);
scene.add(plane);
const target = new THREE.WebGLRenderTarget(WIDTH, HEIGHT);
const samples = WIDTH * HEIGHT;
const numBlocks = (ctx.sampleRate * DURATION) / samples;
for (let i = 0; i < numBlocks; i++) 
{
    uniforms.iBlockOffset.value = i * samples / ctx.sampleRate;
    renderer.render(scene, camera, target, true);
    const pixels = new Uint8Array(WIDTH * HEIGHT * 4);
    wctx.readPixels(0, 0, WIDTH, HEIGHT, wctx.RGBA, wctx.UNSIGNED_BYTE, pixels);
    const outputDataL = audioBuffer.getChannelData(0);
    const outputDataR = audioBuffer.getChannelData(1);
    for (let j = 0; j < samples; j++)
    {
        outputDataL[i * samples + j] = (pixels[j * 4 + 0] + 256 * pixels[j * 4 + 1]) / 65535 * 2 - 1;
        outputDataR[i * samples + j] = (pixels[j * 4 + 2] + 256 * pixels[j * 4 + 3]) / 65535 * 2 - 1;
    }
}
node.buffer = audioBuffer;
node.start(0);


成果物

http://jsdo.it/ohisama1/wk5b

以上。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away