ついにWAVLoaderが完成!ただしフォーマットは限定(Player10)

2008/08/22

何やら一部では北京五輪の開会式で口パクだ合成だとあるらしいですが、個人的には作り物なんだからいいんじゃないの?という感じがしている、きんくまです。

いやそれよりなにより、この間ホワイトノイズを出しちゃったWavLoaderなんですが、ついに音を出すことができました!
miurrorさん、ありがとうございました!!!

サンプルはこちら(要Player10)


それで前回も紹介したのですが、以下のページをよく読んでください。特に英語のできる方はよく見てください。
Adobe Is Making Some Noise Part 2
Adobe Is Making Some Noise Part 3
英検3級に落ちる程度の英語力の私が頑張って解読したところ、以下のものかと思うのですがいかがでしょう?

生成できるサウンドデータは
44100Hz = 44.1kHz(例のCD並みの音質)
2チャンネル (stereo)
1サンプルあたりのビット数: (32 ビット) 浮動小数点数

あと生成するサンプルプログラムで

var sound:Sound = new Sound();
function sineWavGenerator(event:SampleDataEvent):void
{
      for ( var c:int=0; c<1234; c++ )
      {
          var sample:Number = Math.sin((Number(c+event.position)/Math.PI/2))*0.25;
          event.data.writeFloat(sample);
          event.data.writeFloat(sample);
     }
}
sound.addEventListener("sampleData",sineWavGenerator);
sound.play();

これはSampleDataEventをうけてSoundBufferをまわしている部分だと思います。それでforループの部分のcという変数なんですが、これって最初(Alpha版)と仕様が変わって2048から8192ということでOKですかね?仕様変更はどこかのブログに書いてあったと思うのですが、どこか忘れてしまいました。値を2047と8193にしたところ、エラーになったんで、これでいいのかと思うのですが。
それからcは、サンプル数ということなのかなと思います。cが2048のときは1バッファあたり2048サンプルを書きこんで、それを吐きだし終わったらまたSampleDataEventが発行されてまた戻る。こんな感じなのかな?
それで中で宣言しているSampleのとる値は-1から1にしないとだめかな?
って、全部未確認なので疑問形。

さて、今回の肝心のソースなんです。この本をよみながら、WAVフォーマットをちびちびと確認 + popforgeをみながらいろいろこねくりまわしたあげく、あてずっぽうに値を設定したら動いちゃったんで、仕組みはよくわかりません。特に0x7fffの意味。16bitと関係しているのかな?
※追記 本を読んだところチャネルデータがとる最大値32767と等しいんだと思う。と思ったら、あれ?0x7fffは等しくないや。

今回読みこんだWAVファイルはサンプリングレート44.1kHz, 16Bit, Stereoです。他の形式になるとどうすればいいのかは、今のところわかりません。

とにかくソースです。それでは、みなさん良い週末を!

package {
  import flash.display.Sprite;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.events.SampleDataEvent;
  import flash.media.Sound;
  import flash.net.URLLoader;
  import flash.net.URLLoaderDataFormat;
  import flash.net.URLRequest;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;
  import flash.text.TextFormatAlign;
  import flash.utils.ByteArray;
  import flash.utils.Endian;

  [SWF(width="400", height="300", frameRate="30", backgroundColor="#ffffff")]
  public class WavSample2 extends Sprite
  {
  private var path:String = "theme.wav";
  private var wavData:ByteArray;

  public function WavSample2()
  {
    stage.scaleMode = StageScaleMode.NO_SCALE;
    setStartBtn();
  }

  private function soundWAV(e:SampleDataEvent):void
  {
    var left:Number;
    var right:Number;

    for ( var c:int=0; c < 2048; c++ )
    {
       left = wavData.readShort() / 0x7fff;
       right = wavData.readShort() / 0x7fff;
        if(left > 1 || left < -1)
        {
        left = 0;
        }
        if(right > 1 || right < -1)
        {
        right = 0;
        }

       e.data.writeFloat(left);
       e.data.writeFloat(right);

       if(wavData.bytesAvailable <= 0)
       {
         wavData.position = 0;
       }
     }
  }

  private function init():void
  {
    var loader:URLLoader = new URLLoader();
    loader.dataFormat = URLLoaderDataFormat.BINARY;
    loader.addEventListener(Event.COMPLETE, completeHandler);
    var request:URLRequest = new URLRequest(path);

      try {
        loader.load(request);
      } catch (error:Error) {
        trace("Unable to load requested document.");
      }
  }

    private function completeHandler(event:Event):void
    {
      //読み込んだバイトデータをWav形式で解析
      var loader:URLLoader = URLLoader(event.target);
      wavData = event.target.data as ByteArray;
      wavData = parse(wavData);
      wavData.position = 0;

      var mySound:Sound = new Sound();
    //mySound.addEventListener(Event.SAMPLE_DATA, soundWAV);
    mySound.addEventListener("sampleData", soundWAV);
    mySound.play();
    }

    private function setStartBtn():void
    {
    var sp:Sprite = new Sprite();
    sp.graphics.beginFill(0x00ff00, 1);
    sp.graphics.drawRect(0,0,250,50);
    sp.graphics.endFill();

    var tff:TextFormat = new TextFormat();
    tff.align = TextFormatAlign.CENTER;
    tff.size = 14;
    var tf:TextField = new TextField();
    tf.autoSize = TextFieldAutoSize.LEFT;
    tf.selectable = false;
    tf.text = "音量をしぼってから慎重にスタート。";
    tf.setTextFormat(tff);
    sp.addChild(tf);
    tf.x = 20;
    tf.y = 15;
    addChild(sp);
    sp.x = (stage.stageWidth - sp.width) / 2;
    sp.y = (stage.stageHeight - sp.height) / 2 - 20;
    sp.addEventListener(MouseEvent.MOUSE_DOWN, onStartDown);
    }

    private function onStartDown(e:MouseEvent):void
    {
    var sp:Sprite = e.currentTarget as Sprite;
    removeChild(sp);
    init();
    }

    private function parse( bytes: ByteArray ): ByteArray
  {
    bytes.position = 0;
    bytes.endian = Endian.LITTLE_ENDIAN;

    bytes.readUTFBytes( 4 ); // RIFF
    bytes.readUnsignedInt(); // entire fileLength - 8
    bytes.readUTFBytes( 4 ); // WAVE

    var id: String;
    var length: uint;
    var position: uint;
    var parseBytes:ByteArray = new ByteArray();
    var compression:uint;
    var channels:uint;
    var rate:uint;
    var bytesPerSecond:uint;
    var blockAlign:uint;
    var bits:uint;

    while( bytes.position < bytes.length )
    {
    id = bytes.readUTFBytes( 4 );
    length = bytes.readUnsignedInt();
    position = bytes.position;

    switch( id )
    {
      case 'fmt ':
      compression = bytes.readUnsignedShort();
      channels = bytes.readUnsignedShort();
      rate = bytes.readUnsignedInt();
      bytesPerSecond = bytes.readUnsignedInt();
      blockAlign = bytes.readUnsignedShort();
      bits = bytes.readUnsignedShort();
      bytes.position = position + length;

      var log:String = "";
      log += "フォーマットタイプ(1だとPCM): " + compression;
      log += "nチャネル数(2だとステレオ) : " + channels;
      log += "nサンプリング周波数 : " + rate + "[Hz]";
      log += "n平均データ転送速度 : " + bytesPerSecond;
      log += "nブロックサイズ : " + blockAlign;
      log += "nサンプルあたりのビット数 : " + bits + "[bit]";
      trace(log);
      break;

      case 'data':
      var data: ByteArray = new ByteArray();
      data.endian = Endian.LITTLE_ENDIAN;
      data.writeBytes( bytes, position, length );
      data.position = 0;
      parseBytes = data;
      bytes.position = position + length;
      break;

      default:
      bytes.position = position + length;
      break;
    }
    }
    return parseBytes;
  }
  }
}

import flash.display.Sprite;
import flash.text.TextField;
import flash.geom.Rectangle;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;

class Button extends Sprite
{
  private var textbox:TextField;
  private var textf:TextFormat;
  private var background:Sprite;

  public function Button(text:String, color:Number)
  {
  textbox = new TextField();
  addChild(textbox);
  textbox.autoSize = TextFieldAutoSize.LEFT;
  textbox.selectable = false;
  textbox.x = 5;
  textbox.y = 5;
  textbox.text = text;
  textf = new TextFormat();
  textf.color = 0xffffff;
  textbox.setTextFormat(textf);
  background = new Sprite();
  drawRect(background, color);
  addChildAt(background,0);
  var bk:Sprite = new Sprite();
  drawRect(bk, 0, 0);
  addChild(bk);
  }

  private function drawRect(sp:Sprite, color:Number, alpha:Number = 1):void
  {
  sp.graphics.clear();
  var rect:Rectangle = this.getBounds(textbox);
  sp.graphics.beginFill(color, alpha);
  sp.graphics.drawRect(0, 0, rect.width + 10, 30);
  sp.graphics.endFill();
  }

  public function setColor(color:Number, alpha:Number = 1):void
  {
  drawRect(background, color, alpha);
  }

  public function setText(text:String):void
  {
  textbox.text = text;
  textbox.setTextFormat(textf);
  }
}


自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る