9

我正在制作一个动画徽标,它将由频谱分析仪从零到十一显示。我正在寻找可以在各种浏览器上运行的东西,因此将其连接到HTML5 音频元素可能不是一种选择,因为我发现唯一可以在最新的 WebKit 和 Firefox 上工作的库发布。到目前为止,我一直在玩只是每隔一段时间生成一个随机值。这是我目前卡住的示例(使用 jQuery 的动画函数()):

<div id='Logo'>
    <div id='channelA' class='channel'></div>
    <div id='channelB' class='channel'></div>
    <div id='channelC' class='channel'></div>
    <div id='channelD' class='channel'></div>
    <div id='channelE' class='channel'></div>
    <div id='channelF' class='channel'></div>
    <div id='channelG' class='channel'></div>
</div>
<script>
  setInterval(function () {
    $('.channel').each(function () {
        $(this).animate({
            height: (Math.round(Math.random() * 185)) + 'px'
        });
    });
  }, 100);
</script>
<style>
#Logo {
    width: 245px;
    height: 245px;
    background: red;
}
div.channel {
    float: left;
    z-index: 9;
    background: white;
}
#channelA {
    width: 35px;
    height: 45px;
}
#channelB {
    width: 35px;
    height: 85px;
}
#channelC {
    width: 35px;
    height: 85px;
}
#channelD {
    width: 35px;
    height: 50px;
}
#channelE {
    width: 35px;
    height: 150px;
}
#channelF {
    width: 35px;
    height: 30px;
}
#channelG {
    width: 35px;
    height: 85px;
}
</style>

这看起来不“正确”。是否有可以生成“感觉”更像音频信号的数据的功能?我也对解决这个问题的其他方法感兴趣(也许我只需要在支持 HTML5 音频的浏览器中捕获频谱分析仪数据,然后在旧浏览器中“播放”。)

这是我想要的那种外观的一个例子:

在此处输入图像描述

在 JavaScript 中搜索Bézier 曲线的实现后,我开始混合生成的单曲来产生一些东西。虽然我的工作尚未完成,但如果这给其他人任何想法,这里是一个演示

4

4 回答 4

8

使频谱看起来真实(虚拟数据与否)的关键是为波段栏提供后备机制。

仅当新值高于当前值时才设置波段。如果不是,则当前值减少一个值(线性或对数)。回退的速度也会影响感知。

由于频谱分析仪中的数据不代表实际波形,而是 FFT(快速傅立叶变换)或每个频段的值,因此它可以很好地处理随机数据。你当然不会得到音乐的“节奏”指纹,但由于有后备,它在一定程度上仍然看起来很真实(好像有人想听噪音一样:-))。

一个例子如下 -

快照

在这里演示:http :
//jsfiddle.net/AbdiasSoftware/VXxwt/

初始 HTML,一个简单的 div:

<div id="logo"></div>

初始 CSS:

.band {
    background-color: #3897e0;
    border-radius:3px 3px 0 0;
}

以及创建虚拟频谱的主要呼吁:

makeSpectrum('logo', 300, 120, 7);

完整代码:

/**
 *    Turn an element into a virtual spectrum,
 *    Ken Fyrstenberg Nilsen, Public domain.
 *
 *    USAGE:
 *        makeSpectrum(id, width, height)
 *        makeSpectrum(id, width, height, bands)
 *        makeSpectrum(id, width, height, bands, volume)
 *
 *    id      id of the element to be converted into spectrum
 *    width   width in pixels of spectrum
 *    height  height in pixels of spectrum
 *    bands   (optional) number of "bands"
 *    volume  initial volume (0-1)
 *
 *    METHODS:
 *
 *    setVolume()    returns current volume
 *    setVolume(vol) sets new volume (0-1 float)
*/
function makeSpectrum(id, width, height, bands, volume) {

    bands = bands ? bands : 12;
    volume = volume ? volume : 1;

    if (bands < 1) bands = 1;
    if (bands > 128) bands = 128;

    // init parent element
    var parent = document.getElementById(id),
        bandElements = [];

    if (typeof parent === 'undefined')
        alert('Element ' +id + ' not found!');

    parent.style.display = 'block';
    parent.style.width = width + 'px';
    parent.style.height = height + 'px';
    parent.style.position = 'relative';

    var bandValues = [],
        oldBandValues = [],
        bw = (((width)/ bands) |0),
        me = this;

    function calcBand(bandNum) {
        var bv = bandValues[bandNum],
            obv = oldBandValues[bandNum];

        if (bv >= obv) obv = bv;
        obv -= 0.1;
        if (obv < 0 ) obv = 0;
        obv *= volume;        

        oldBandValues[bandNum] = obv;
        return obv;
    }

    function getFFT(band) {
        band = band ? band : bandValues;
        for(var i = 0; i < bands; i++) {
            band[i] = Math.random();
       }
    }    

    function createBands() {

        var i, html = '';
        for(i = 0; i < bands; i++) {
            h = 0
            html += '<div id="' + id + '_band' + i + '" ';
            html += 'style="display:block;position:absolute;';
            html += 'left:' + ((i * bw + 1)|0);
            html += 'px;top:' + ((height - height * h)|0);
            html += 'px;width:' + (bw - 2);
            html += 'px;height:' + ((height * h)|0);
            html += 'px;" class="band"></div>';
        }
        parent.innerHTML = html;

        for(i = 0; i < bands; i++) {
            var el = document.getElementById(id + '_band' + i);
            bandElements.push(el);
        }
    }    
    this.setVolume = function(vol) {

        if (arguments.length === 0)
            return volume;

        if (vol < 0) vol = 0;
        if (vol > 1) vol = 1;
        volume = vol;
    }
    this.setVolume(volume);

    this.createSnapshot = function() {

        var h, y, el;

        getFFT(bandValues);    

        for(var i = 0; i < bands; i++) {
            h = calcBand(i);
            el = bandElements[i].style;
            el.top = ((height - height * h)|0) + 'px';
            el.height =  ((height * h)|0) + 'px';
        }
        parent.innerHTML = html;
    }

    //init bands
    getFFT(oldBandValues);
    createBands();

    //GO
    setInterval(me.createSnapshot, 100);

    return this;
}
var sp = makeSpectrum('logo', 250, 100, null, 0);
var vol = 0;

function fadeIn() {
    vol += 0.02;
    sp.setVolume(vol);
    if (vol < 1) setTimeout(fadeIn, 60);
}
fadeIn();

代码没有优化,所以它在 CPU 上运行有点饿。它主要是为乐队生成 html 的方式。我喜欢在画布元素上执行此操作,这样效率会更高,但由于需要大量浏览器支持,我将其保留为 :-)

更新:

优化了缓存元素的循环设置高度和顶部。它还有一种方法setVolume()可以用来从循环等中设置整体“音量”。

使用淡入和新代码更新了示例(顶部链接)。

更新 2:

通过基于内部时钟模拟 BPM,在较低频率中增加了更多的真实感。我现在让时间影响前三个频段(如果频段数量允许):

var d = (new Date()).getMilliseconds() % 10;  //get milliseonds of second
band[0] = band[0] * 0.2 + (d / 10) * 0.8; //affect 1. band 80% + 20% random
band[1] = band[1] * 0.3 + (d / 10) * 0.7; //affect 2. band 70% + 30% random
band[2] = band[2] * 0.5 + (d / 10) * 0.5; //affect 3. band 50% + 50% random

它可能很微妙,但只是为了增加一点真实感。

此处带有静音功能的样式版本:http:
//jsfiddle.net/AbdiasSoftware/hVkPN/

风格版

于 2013-06-05T06:37:56.770 回答
3

我认为我更接近了(但我已经做了很多,所以我认为提供它作为我自己问题的答案是最好的方法,我仍然愿意接受更好的答案,或者就这种方法的问题提供反馈(哈) )。这是我迄今为止实现的(复杂的)代码(演示)。

在这里使用 HTML 和 CSS 的清理版本是 JavaScript:

var amp = 0,
    flutter_range = 10,
    channels = $('.channel'),
    turnItUp = setInterval(function () {
        amp += 0.01;
    }, 50),
    flutter = setInterval(function () {
        var levels = bezier([[0.3], [0.95], [1], [0]]),
            channel;
        for(channel = 0; channel < channels.length; channel++) {
            $(channels[channel]).animate({
                height: 245-(Math.round(((Math.random() * (flutter_range*2))-flutter_range)+(levels(channel/channels.length)*amp)*245))+'px'
            }, 50);
        }
    }, 100),
    //from: https://gist.github.com/atomizer/1049745
    bezier = function (pts) {
        return function (t) {
            for (var a = pts; a.length > 1; a = b) // do..while loop in disguise
                for (var i = 0, b = [], j; i < a.length - 1; i++) // cycle over control points
                    for (b[i] = [], j = 0; j < a[i].length; j++) // cycle over dimensions
                        b[i][j] = a[i][j] * (1 - t) + a[i + 1][j] * t; // interpolation
            return a[0];
        }
    };

setTimeout(function () {
    window.clearInterval(turnItUp);
}, 5000);

它的工作方式是在 5 秒内将“放大器”调高,同时填写贝塞尔曲线,然后对数据应用随机“颤动”,使其具有“音频感觉”。贝塞尔函数来自这个要点

于 2013-05-28T17:31:59.790 回答
2

我认为使动画看起来真实的最好方法是使用真实数据并计算条形的真实值。对实际数据进行动画处理会使条形图以逼真的方式上下移动,如果不进行处理,可能很难模仿这种行为。

均衡器条告诉您的是不同频率范围的幅度。您可以使用称为 FFT(快速傅里叶变换)的函数从实际音频文件中计算这些条形图。

几乎所有语言都有 FFT 的实现,即使是 Javascript。该算法将按如下方式工作:

  • 决定刷新的帧率。
  • 从音频文件中将 1/fps 的第二个样本加载到内存缓冲区中(未压缩,如果是 .mp3 则先将其转换为 .wav)
  • 将缓冲区发送到 FFT 函数。您将获得另一个缓冲区,其中包含一组值作为回报。
  • 决定您要显示多少条柱,并找到 FFT 缓冲区的一部分,该缓冲区有这么多带有有趣数据的样本。您还可以通过平均多个连续值来压缩输出 FFT 数组。
  • 数组(或平均组)中的每个值都会为您提供条形的高度。
  • 这将为您提供一帧动画。获取下一秒 1/fps 的音频并重复整个过程以获得下一帧。

FFT 占用大量 CPU 资源,因此您可能需要计算足够多帧的条形图,然后在脚本中使用计算值。

如果您想要一个示例来帮助您入门,请查看此演示:Fast Fourier Transforms with audiolib.js

此演示生成合成波形,在浏览器上播放(对您来说不是必需的)并在 Canvas 对象中绘制实时均衡器。由于某种原因,此页面上的示例对我不起作用,因此我下载了源代码 gist并将其安装在我的计算机上以使其工作。

于 2013-06-03T02:27:50.063 回答
1

我在音量、延迟和弯曲方面做了一些尝试。这是我的 jsfiddle 对它的看法。我认为非常接近它:-)但绝对可以改进(最右边的频道有一个小问题)。

[编辑:]好的,我改进了一点。这是另一个jsfiddle

如您所见,您不一定需要贝塞尔曲线实现来使弯曲工作。

$('.channel').each(function () {
    animate($(this));
});

var beat = 200;
var volume = 1;
setInterval(function() {
    beat = 60 + Math.random()*100;
    volume = Math.min(1,Math.max(0.1,volume + Math.random()-0.5));
}, 200);

function animate($channel) {
    var curving = Math.max(1, 30*Math.abs($channel.prevAll().length - 3));

    curving = curving + 100*volume;

    var height = Math.max(1,Math.min(curving, 240) + (Math.random()*50-25));

    $channel.animate({
        height: Math.round(height) + 'px',
    }, {
        duration: beat,
        complete: function() { animate($channel); }
    });
}
于 2013-06-03T10:13:04.417 回答