问题描述
我想根据振荡器波形的当前部分设置动画/更改颜色,以创建与波形匹配的脉冲颜色效果?波浪可以是任何类型,例如正弦、三角等,因此这会根据波形类型产生不同的脉冲,并且振荡器的频率也可以改变(然后我想也改变脉冲时序)。
大概我需要从振荡器对象中获取振幅和波长(这些甚至是我需要的正确术语吗?),并在使用一些 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(但在这个例子中只使用关键帧,只是为了举例说明我的视觉意思)-
@keyframes animation {
0% {background-color:red;}
50.0% {background-color:green;}
100.0% {background-color:red;}
}
解决方法
从 tone.js documentation 的粗略扫描来看,它似乎拥有您想要合成和分析声音的一切。但是,它似乎没有任何图形功能。就绘制图形而言,p5.js 当然是一种选择。然而,这里对“css 动画”的引用有点不合适,因为像 p5.js 这样的库在设计时并没有真正考虑 css 样式。相反,您将自己完成所有动画,并为每一帧调用绘图函数。
在分析声音以便将它们作为数据进行处理并可能将它们可视化时,您正在寻找的东西是 Fast Fourier Transform。这是一种将声波样本分解为单独的正弦波分量的算法,您可以使用这些正弦波的幅度根据分量频率来测量声音。 p5.js(通过 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,{
name: 'e',freq: 329.6
},{
name: 'f',freq: 349.2
},{
name: 'f#',freq: 370.0,{
name: 'g',freq: 392.0
},{
name: 'g#',freq: 415.3,{
name: 'a',freq: 440.0
},{
name: 'a#',freq: 466.2,{
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,w,h);
drawWaveformGraph(w * 2,h);
drawKeyboard();
bg = color((fft.getCentroid() * 1.379) % 360,30,50);
}
function drawSpectrumGraph(left,top,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),log(spectrum.length),w),top + map(spectrum[i],255,h,0)
);
}
vertex(left + w,top + h);
endShape(CLOSE);
}
function drawWaveformGraph(left,h) {
let waveform = fft.waveform();
stroke('limegreen');
noFill();
strokeWeight(1);
beginShape();
for (let i = 0; i < waveform.length; i++) {
let x = map(i * 5,waveform.length,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,sharpWidth,h / 2,4,4]
});
} else {
rect(x,keyWidth,h - 1,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,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>