11RIA 闪客社区 - 最赞 Animate Flash 论坛

搜索
查看: 1845|回复: 1
上一主题 下一主题

[音频 & 声道 & 录音] 转载 AS3声音合成---声音基础

[复制链接] TA的其它主题
发表于 2019-2-26 13:38:58 | 显示全部楼层 |阅读模式

【游客模式】——注册会员,加入11RIA 闪客社区吧!一起见证Flash的再次辉煌……

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本帖最后由 TKCB 于 2019-2-26 15:05 编辑

要想成为高手,就得像高手学习,多看高手的作品.下面是BIT-101大大写的Sound Synthesis一文.

原为地址:http://www.bit-101.com/blog/?p=2660

学习就应该不怕麻烦,既得之,则译之!下面是译文:


我一直都想写一些关于声音合成的东西。但是因为没有找到很好这方面的素材而迟迟没有动作.所以现在只能找些素材东拼西凑写下这篇文章.

首先,我们来看一下Sound对象的一些基本结构,如果通过代码来控制它并创建一些随机的声音。稍后,再学习如何创建真正的声音波形及声音的合成等等.

开门见山

Flash10具有声音合成的功能,实际这个功能在Flash9就已经有了,但是实现同样的效果对Flash9来说是一件头疼的事,而这些声音合成的API在Flash10中变得更加标准化了.

要进行声音合成,首先要创建一个Sound对象并对它的SAMPLE_DATA事件(SampleDataEvent.SAMPLE_DATA)进行侦听.这个事件在声音中没有声音数据播放时触发.然后播放声音.

var sound:Sound=new Sound();

sound.addEventListener(SampleDataEvent.SAMPLE_DATA,onSampleData);

sound.play();

此时,因为没有加载任何如MP3,WAV等声音,也没有导入任何的声音数据流,也就是说没有任何声音播放,所以SAMPLE_DATA事件会立即触发.因此我们要添加一个侦听器函数:

function onSampleData(se:SampleDataEvent):void

{

}

接下来要给这个Sound对象添加一些声音数据来播放.那么如何添加呢?侦听器方法的形参SampleDataEvent有个二进制的data属性,可以通过对这个属性赋值二进制数据来给声音对象添加播放的声音数据.对二进制变量赋值要使用ByteArray.float方法.通常情况下赋值范围在-1.0到1.0之间.此时对二进制变量设置的每个float数值就是常说的样本.样本数通常为2048和8192.

OK.这个范围已经够宽了.那哪个值最好的呢?如果你选择比较低的值比如2048,声音播放时,数据流会很快超出这些值,然后再次出发SAMPLE_DATA事件,要求再次添加声音数据.如果选择比较大的值比如8192的话,声音需要花费4倍于2048的时间来超出这些值,因此事件侦听器被触发的频率也随之降低了4倍.

所以样本数越大运行性能越好.但是如果是动态的生成声音数据,样本数越大意味着延迟越大.延迟指的是UI或者程序变更声音与真正听到声音变更这之间的声音差.举个例子,比如你需要在用户按下按钮时将一个400HZ的音调变到800HZ.但是当用户按下按钮时声音在400HZ的音调上取了8000个样本,此时声音会继续播放这8000个样本直至播放完毕,然后调用SAMPLE_DATA事件侦听器要求跟多的数据.因此用户可能会感觉到从他们按下按钮到听到声音变调,中间有一点延迟.如果选择比较小的样本数-2048-可以缩短延迟而使其不易察觉.

现在我们来制造一些噪音.我们在-1.0到1.0之间选区2048个随机的样本值并写入二进制数据中.有一点你必须知道,实际我们做了两次写入二进制数据的动作,一次是给左声道,一次给右声道,下面是完整的代码:

import flash.media.Sound;

import flash.events.SampleDataEvent;

var sound:Sound=new Sound();

sound.addEventListener(SampleDataEvent.SAMPLE_DATA,onSampleData);

sound.play();

function onSampleData(event:SampleDataEvent):void

{

    for(var i:int = 0; i < 2048; i++)

    {

        var sample:Number = Math.random() * 2.0 - 1.0; // -1 to 1

        event.data.writeFloat(sample); // left

        event.data.writeFloat(sample); // right

    }

}

测试上面的代码,你会听到一些兹兹的有点像收音机找不到频道的声音.注意,现在我们生成的噪音中,左右声道是同一个样本值,因为两个通道的二进制数据写入的是同一个值,所以我们生成的是一个单频道的声音.生成立体声音的代码如下:

function onSampleData(event:SampleDataEvent):void

{

    for(var i:int = 0; i < 2048; i++)

    {

        var sampleA:Number = Math.random() * 2.0 - 1.0; // -1 to 1

        var sampleB:Number = Math.random() * 2.0 - 1.0; // -1 to 1

        event.data.writeFloat(sampleA); // left

        event.data.writeFloat(sampleB); // right

    }

}

现在我们为每个通道写入一个不同的随机值作为样本.测试代码,可以感觉到声音有点”空间”的感觉了(带上耳机效果更明显些).这个”空间”的感觉很细微,你可能感觉不到他跟单频道区别,所以为了可以在这两种效果之间快速切换,我们稍微修改一下代码如下:

import flash.media.Sound;

import flash.events.SampleDataEvent;

import flash.events.MouseEvent;

var sound:Sound = new Sound();

sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);

sound.play();

var mono:Boolean = true;

stage.addEventListener(MouseEvent.CLICK, onClick);

function onClick(event:MouseEvent):void

{

    mono = !mono;

}

function onSampleData(event:SampleDataEvent):void

{

    for(var i:int = 0; i < 2048; i++)

    {

        var sampleA:Number = Math.random() * 2.0 - 1.0; // -1 to 1

        var sampleB:Number = Math.random() * 2.0 - 1.0; // -1 to 1

        event.data.writeFloat(sampleA); // left

        if(mono)

        {

            event.data.writeFloat(sampleA); // left again

        }

        else

        {

            event.data.writeFloat(sampleB); // right

        }

    }

}

我添加一个布尔值变量,mono,当鼠标点击时它在true和false之间进行切换.如果mono为true,在左右声道中分别写入sampleA.如果mono为false,则在左声道写入sampleA,在右声道写入sampleB.测试代码并在舞台中点击鼠标.差别还是很细微的,不过现在你应该能够感觉到了.

要想看到(更确切的说应该是听到)延迟的结果,在for循环中将2048改8192.现在点击鼠标时,你可以很明显的感觉到从鼠标点击到声音从单声道切换为立体声之间的延迟了.

另外还要注意样本数.我说”通常”是使用2048到8192之间的值.实际上,如果你试着用大于8192的值,你会得到一个错误提示”某个参数无效”.所以样本数被强制限制小于8192.你可以选择一个小于2048的值,但那样声音会跳过样本数然后认为声音已经播放结束了.所以不会在触发SAMPLE_DATA事件,相反会触发COMPLETE事件.所以如果你想要声音持续播放的话,需要一直提供至少2048个样本数.

本文将为你介绍如何使用AS3声音对象创建一个特定频率的波形。我会默认你已经看过本系列的第一节内容。

声音基础

声音从本质上讲就是空气压力的变化。这对初学者来说是一个非常难理解的术语。空气由各种各样的分子组成,但是这些分子在空气中的分布是不均匀的。有些区域的分子排列比较紧密,因此空气压力也比较大;而有些区域的分子排列的间距就比较大。例如吉他琴弦的震动时,它会以特定的速度前后运动。当琴弦像某一方向移动时,它会将该方向上存在的分子挤到一起,进而使这一部分的空气密度变大。当琴弦像反方向回弹时,会生成一小部分的真空,当然这不是真正意义上的真空,只是这部分区域中包含的分子很少。然后琴弦再次回弹,创建另一个高密度空气区域,依次类推。

这些高密度和低密度空气开始通过空气传播,最终碰到你的耳朵。然后高密度空气会将你的耳膜“挤压”进去,然后稀薄的空气让耳膜再次恢复原形,结果就是你的耳膜开始以与吉他琴弦大致相同的频率进行震动。然后这个震动会带动你的骨头震动,然后以相同的频率刺激你的神经,像你的大脑传递一个信号,这就是“C大调”。

当你使用麦克风或者耳机录音时,需要通过一种鼓膜或者其他可以震动的东西震动创建电信号,以同样的方法或者其他方法记录声音。在声音回放时,计算机会重新产生这些电信号,并使扬声器以相同的频率震动。这个震动跟吉他琴弦产生的震动是一样的,以同样的方法通过挤压空气并传播到你的耳朵,然后你会听到的是相同的声音。

声音合成

但是,在讨论到声音合成时,我们就要从头开始了。Flash(或者说你的声卡)通过耳机或者扬声器处理并产生正确的电信号,然后震动空气。但是你要用数学的方法计算出这个震动的幅度和频率。

在本教程的第一部分,我们创建了一些随机值来使扬声器或者耳机毫无规律的震动,这样产生的结果是一个类似无线电的噪声。要生成真实的音调还有很多工作要做,还有很多东西要学习。

数字声音

在模拟声音的世界里,比如老唱片或八轨磁带(那是和我的年龄差不多久远的年代了),声音是通过唱针撞击唱片盘道或者磁带上磁场变化来编码的。数字声音则是通过一定的时间间隔对声压采样实现的。

举一个最简单的声音形式,正弦波,下面是一个平滑的模拟声音曲线:

波形" title="AS3声音合成波形" style="border: 0px; list-style: none; max-width: 690px;">

下面是对这个曲线进行50次采样数字声音形式:

波形" title="AS3声音合成波形" style="border: 0px; list-style: none; max-width: 690px;">

可以看到,采样后的版本并不像模拟声音曲线那么精确。但是在高质量的数字声音中,采样的样本数非常多,足以让大部分人无法感觉到它与模拟声音的区别。在Flash中进行声音合成时,每秒钟采集44100个样本。记住这个数字,稍后的计算中会用到它。

现在我们需要对声音采样一些样本,然后创建上面你所看到的正弦波。正弦波的峰值是1.0,谷值是-1.0,以及中值是0.0。为了方便理解,首先我在一秒的时间内只创建一个正弦波。然后使用一个名为position的变量来记录位置。其初始值为0,每创建一个样本对其加1。因此在第一秒的声音中,这个变量的取值范围是0到44100。

如果用这个position变量除以44100,我们可以得到一个0.0到1.0值,然再乘以2PI,这样就得到了一个0到2PI的值,这正是用Math.sin函数创建正弦波所需要的。代码如下:

import flash.media.Sound;

import flash.events.SampleDataEvent;

import flash.events.MouseEvent;

var position:int = 0;

var sound:Sound = new Sound();

sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);

sound.play();

function onSampleData(event:SampleDataEvent):void

{

              for(var i:int = 0; i < 2048; i++)

              {

                            var phase:Number = position / 44100 * Math.PI * 2;

                            position ++;

                            var sample:Number = Math.sin(phase);

                            event.data.writeFloat(sample); // left

                            event.data.writeFloat(sample); // right

              }

}

运行上面的代码,程序会在每一秒种创建一个完整的正弦波。当然,这是一个1HZ的声波(注:HZ是频率单位,频率即采样次数,而不是样本数,正文中提到的44100是样本数),这个频率太低是人耳听不到的。要生成指定频率的声音,只需用频率乘以你要听到的频道就可以了。人耳可以听到的频率大致在25到25000HZ之间。标准音节里的中央A是440HZ。我们来试一下,稍微改动一下下面的代码:

var sample:Number = Math.sin(phase * 440);

此时你会听到中央A调。你可以在网上轻松的找到各个音调对应的频率:

A 440

B flat 466

B 494

C 523

C sharp 554

D 587

D sharp 622

E 659

F 698

F sharp 740

G 784

A flat 831

A 880

或者,你也可以了解每个音调之间的计算关系,上面的音调都可以通过下面的的公式在440HZ的基础上计算出来:

440*2^(n/12)

我们可以再添加一个变量n和一个timer对象,并在timer事件处理函数中对n逐次加1,然后用上的公式计算出不同的音调:

import flash.media.Sound;

import flash.events.SampleDataEvent;

import flash.events.MouseEvent;

import flash.utils.Timer;

import flash.events.TimerEvent;

var position:int = 0;

var n:Number = 0;

var sound:Sound = new Sound();

sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);

sound.play();

function onSampleData(event:SampleDataEvent):void

{

              for(var i:int = 0; i < 2048; i++)

              {

                            var phase:Number = position / 44100 * Math.PI * 2;

                            position ++;

                            var sample:Number = Math.sin(phase * 440 * Math.pow(2, n / 12));

                            event.data.writeFloat(sample); // left

                            event.data.writeFloat(sample); // right

              }

}

var timer:Timer = new Timer(500);

timer.addEventListener(TimerEvent.TIMER, onTimer);

timer.start();

function onTimer(event:TimerEvent):void

{

              n++;

}

我们也可以借助于Math.random方法做一个简单的人工作曲器:

function onTimer(event:TimerEvent):void

{

              n = Math.floor(Math.random() * 20 - 5);

              timer.delay = 125 * (1 + Math.floor(Math.random() * 8));

}

这几句代码会为我们生成不同时长不同音调的声音。


评分

参与人数 1银子 +10 贡献 +1 收起 理由
TKCB + 10 + 1 11RIA six six six.(666)

查看全部评分

发表于 2019-2-26 15:06:02 | 显示全部楼层
【11RIA 闪客社区,评分公示】:
是否有价值:一般(银子 +10  贡献 +1)
是否原创:否
是否翻译:否
如对自己的评分有疑问,则咨询版主、管理员等。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐 上一条 /1 下一条

感谢所有支持论坛的朋友:下面展示最新的5位赞助和充值的朋友……更多赞助和充值朋友的信息,请查看:永远的感谢名单

SGlW(66139)、 anghuo(841)、 whdsyes(255)、 longxia(60904)、 囫囵吞澡(58054)

下面展示总排行榜的前3名(T1-T3)和今年排行榜的前3名的朋友(C1-C3)……更多信息,请查看:总排行榜今年排行榜

T1. fhqu1462(969)、 T2. lwlpluto(14232)、 T3. 1367926921(962)  |  C1. anghuo(147)、 C2. fdisker(27945)、 C3. 囫囵吞澡(58054)



快速回复 返回顶部 返回列表