1

我想根据振荡器波的当前部分设置动画/更改颜色,以创建与波匹配的脉冲颜色效果?波可以是任何类型,例如正弦波、三角波等,因此这将根据波的类型产生不同的脉冲,并且振荡器的频率也可以改变(然后我也想改变脉冲时间)。

大概我需要从振荡器对象中获取幅度和波长(这些甚至是我需要的正确术语吗?)并在使用一些 css 动画时将其与 Draw 对象相关联,但我有点不知道在哪里开始?特别是围绕tone.js部分,我将如何获得所需的值以及如何随着时间的推移从颜色A变为B并返回等。我是否需要包含一个像 p5 这样的外部库,或者我可以通过tone.js 单独使用draw 来完成这一切吗?

干杯大卫

编辑 - @paulwheeler - 非常感谢您的所有评论,这是一个很大的帮助。为了更清楚,并回答您的疑问,如果您有更多意见,这就是我所拥有和需要的 -

i) 我有两个从 Tone.js 连续播放的振荡器。它们以 A 和 B 频率“播放”,以产生双耳声音。我将两者之间的差异称为范围。例如,Osc1 可能是 100hz,osc2 可能是 104hz,范围是 4hz。这些振荡器可以是tone.js 允许的任何类型(锯齿波、正弦波、三角形......)

ii)当声音播放时,我想获取范围频率并将其附加到两种颜色。因此,在频率的峰值处,颜色 A 将显示为背景色,而在频率的波谷处,颜色 B 将显示。在这些时间之间,颜色将在两者之间变化,即在任何时间 t,颜色将代表波上的 Y 位置,即从 0 开始的距离。这将使它看起来颜色正在变化以匹配波浪的形状。有点像 css 中的这里(但在这个例子中只使用关键帧,只是为了举例说明我的意思) -

https://jsfiddle.net/CF3Np/4/

@keyframes animation {
0%     {background-color:red;}
50.0%  {background-color:green;}
100.0%  {background-color:red;}
}
4

1 回答 1

2

从对tone.js 文档的粗略扫描来看,它似乎拥有您想要合成和分析声音的所有内容。但是,它似乎没有任何图形功能。就绘制图形而言,p5.js 无疑是一种选择。然而,这里对“css 动画”的引用有点不合适,因为像 p5.js 这样的库在设计时并没有真正考虑到 css 样式。相反,您将自己完成所有动画,并为每一帧调用绘图函数。

在分析声音以将它们作为数据处理并可能将它们可视化时,您正在寻找的东西是快速傅里叶变换。这是一种将声波样本分解为单独的正弦波分量的算法,您可以使用这些正弦波的幅度来根据分量频率来测量声音。p5.j ​​s (通过p5.sound插件)和tone.js都有 FFT 类。如果您已经在使用tone.js,您可能希望坚持使用它来分析声音,但您当然可以使用p5.js 为它们创建可视化。只是作为一个概念示例,这是一个纯 p5.js 示例:

const notes = [{
    name: 'c',
    freq: 261.6
  },
  {
    name: 'c#',
    freq: 277.2,
    sharp: true
  },
  {
    name: 'd',
    freq: 293.7
  },
  {
    name: 'd#',
    freq: 311.1,
    sharp: true
  },
  {
    name: 'e',
    freq: 329.6
  },
  {
    name: 'f',
    freq: 349.2
  },
  {
    name: 'f#',
    freq: 370.0,
    sharp: true
  },
  {
    name: 'g',
    freq: 392.0
  },
  {
    name: 'g#',
    freq: 415.3,
    sharp: true
  },
  {
    name: 'a',
    freq: 440.0
  },
  {
    name: 'a#',
    freq: 466.2,
    sharp: true
  },
  {
    name: 'b',
    freq: 493.9
  },
];

let playing = {};
let fft;
let bg;

function setup() {
  createCanvas(windowWidth, windowHeight);
  colorMode(HSB);
  bg = color('lightgray');

  for (let note of notes) {
    note.osc = new p5.Oscillator();
    note.osc.freq(note.freq);
    note.osc.amp(0);
  }

  fft = new p5.FFT();
}

function toggleNote(name) {
  let note = notes.filter(n => n.name === name)[0];
  if (playing[name] === undefined) {
    // First play
    note.osc.start();
  }
  if (playing[name] = !playing[name]) {
    // fade in a little
    note.osc.amp(0.2, 0.2);
  } else {
    // fade out a little
    note.osc.amp(0, 0.4);
  }
}

function playNote(name) {
  let note = notes.filter(n => n.name === name)[0];
  if (playing[name] === undefined) {
    // First play
    note.osc.start();
  }
  playing[name] = true;
  note.osc.amp(0.2, 0.2);
}

function releaseNote(name) {
  let note = notes.filter(n => n.name === name)[0];
  playing[name] = false;
  note.osc.amp(0, 0.4);
}

function draw() {
  background(bg);
  let w = width / 3;
  let h = min(height, w * 0.8);
  drawSpectrumGraph(w, 0, w, h);
  drawWaveformGraph(w * 2, 0, w, h);
  drawKeyboard();
  bg = color((fft.getCentroid() * 1.379) % 360, 30, 50);
}

function drawSpectrumGraph(left, top, w, h) {
  let spectrum = fft.analyze();

  stroke('limegreen');
  fill('darkgreen');
  strokeWeight(1);

  beginShape();
  vertex(left, top + h);

  for (let i = 0; i < spectrum.length; i++) {
    vertex(
      left + map(log(i), 0, log(spectrum.length), 0, w),
      top + map(spectrum[i], 0, 255, h, 0)
    );
  }

  vertex(left + w, top + h);
  endShape(CLOSE);
}

function drawWaveformGraph(left, top, w, h) {
  let waveform = fft.waveform();

  stroke('limegreen');
  noFill();
  strokeWeight(1);

  beginShape();

  for (let i = 0; i < waveform.length; i++) {
    let x = map(i * 5, 0, waveform.length, 0, w);
    let y = map(waveform[i], -1, 2, h / 10 * 8, 0);
    vertex(left + x, top + y);
  }

  endShape();
}

function drawKeyboard() {
  let w = width / 3;
  let h = min(height, w * 0.8);
  let x = 1;
  let keyWidth = (w - 8) / 7;
  let sharpWidth = keyWidth * 0.8;
  noStroke();
  let sharpKeys = [];
  for (let note of notes) {
    fill(playing[note.name] ? 'beige' : 'ivory');
    if (note.sharp) {
      sharpKeys.push({
        fill: playing[note.name] ? 'black' : 'dimgray',
        rect: [x - sharpWidth / 2, 0, sharpWidth, h / 2, 0, 0, 4, 4]
      });
    } else {
      rect(x, 0, keyWidth, h - 1, 0, 0, 4, 4);
      x += keyWidth + 1;
    }
  }
  for (let key of sharpKeys) {
    fill(key.fill);
    rect(...key.rect);
  }
}

let keymap = {
  'z': 'c',
  's': 'c#',
  'x': 'd',
  'd': 'd#',
  'c': 'e',
  'v': 'f',
  'g': 'f#',
  'b': 'g',
  'h': 'g#',
  'n': 'a',
  'j': 'a#',
  'm': 'b',
}

function keyPressed(e) {
  let note = keymap[e.key];
  if (note) {
    playNote(note);
  }
}

function keyReleased(e) {
  let note = keymap[e.key];
  if (note) {
    releaseNote(note);
  }
}

function mouseClicked() {
  if (mouseX < width / 3) {
    let w = width / 3;
    let h = w * 0.8;
    let x = 1;
    let keyWidth = (w - 8) / 7;
    let sharpWidth = keyWidth * 0.8;
    let naturalKeys = [];
    let sharpKeys = [];
    for (let note of notes) {
      if (note.sharp) {
        sharpKeys.push({
          name: note.name,
          bounds: {
            left: x - sharpWidth / 2,
            top: 0,
            right: x - sharpWidth / 2 + sharpWidth,
            bottom: h / 2
          }
        });
      } else {
        naturalKeys.push({
          name: note.name,
          bounds: {
            left: x,
            top: 0,
            right: x + keyWidth,
            bottom: h - 1
          }
        });
        x += keyWidth + 1;
      }
    }

    for (let {
        bounds,
        name
      } of sharpKeys.concat(naturalKeys)) {
      if (mouseX > bounds.left && mouseX < bounds.right &&
        mouseY > bounds.top && mouseY < bounds.bottom) {
        toggleNote(name);
        break;
      }
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/addons/p5.sound.min.js"></script>

于 2021-05-16T02:23:04.267 回答