Player10でのこぎり波とか(波形表示機能つき)

2008/08/26

※2008.12.20追記
→このとき書いたものから修正した、のこぎり波とかのソースです。

こんにちは、ソフトバンクだというのに、この間までVodafone携帯を使っていました。いいチャンスだと思い、思い切ってiPhoneに機種変更をしました。ところが!きちんと注意書きを読まなかったため最も楽しみにしていたAppができません。
というのも、わたくしはクレジットカードを持っていないため、iPhoneのアクティベーションができないんです。アクティベーションができないと無料のソフトもダウンロードできなくて、がっくしです。
なので、クレジットカードを申し込んだんですが、フリーという不安定な立場で審査が下りるか夜も眠れない日々です。(ウソ)

さてAstroです。音の生成って楽しいですね。
今回は、のこぎり波、矩形波などを生成してみました。式は例によって適当なんで、疑ってきいてみてください。

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

●サイン波、矩形波、のこぎり波、三角波、(ホワイト?)ノイズ。の5種類の波形が生成可能。
●キーボードに応じて、周波数をきりかえて音を出す。
●音量を調整可能。
●波形をSoundMixer.computeSpectrumからでなく、生成する音のデータから作成。(例の本のmiurrorさんのページを参考に)
この波形表示はサンプルバッファごとに描画しているので、すぐ流れていったりしちゃいますね。これを同期をとって静止画のように止められればよいのですが、、。これってオシロスコープの役割と同じなのかな?

以下ソースです。鍵盤とかslideとかのコンポーネントとかは、別途swcとしてflaから書き出したものを使用しています。
三角波がたまにノイズがプチプチなることがあります。どなたか改善方法を教えてくださるとうれしいのですが、、。

package
{
  import fl.controls.ComboBox;
  import fl.controls.Slider;
  import fl.events.SliderEvent;

  import flash.display.Graphics;
  import flash.display.MovieClip;
  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.media.SoundChannel;

  [SWF(width="400", height="300", frameRate="30", backgroundColor="#5696A0")]
  public class AstroWaveTypeTest extends Sprite
  {
        public static const SINE:String = "sine";
        public static const SAW:String = "saw";
        public static const TRIANGLE:String = "triangle";
        public static const PULSE:String = "pulse";
        public static const NOISE:String = "noise";

        private const WAVE_TYPE_CANVAS_BASE_WIDTH:uint = 150;
        private const WAVE_TYPE_CANVAS_BASE_HEIGHT:uint = 80;

        private var synth1:Sound;
        private var ch1:SoundChannel;
        private var frequency:Number;
        private var keyboardTable:Array;
        private var keyboardAsset:KeyboardAsset;
        private var waveType:String;
        private var waveTypeCombo:ComboBox;
        private var amplifier:Slider;
        private var volume:Number;
        private var waveTypeCanvas:Sprite;
        private var waveTypeCanvasBase:Sprite;


    public function AstroWaveTypeTest()
    {
      super();
      stage.scaleMode = StageScaleMode.NO_SCALE;
      initialize();
    }

    private function initialize():void
    {
      initializeKeyboardTable();
      waveType = SINE;
      volume = 0.5;

      keyboardAsset = new KeyboardAsset();
      addChild(keyboardAsset);
      addKeyboradListener();
      initializeCombobox();
      initializeAmplifier();

      initializeWaveTypeCanvas();

      synth1 = new Sound();
      synth1.addEventListener("sampleData",waveGenerator);
    }

    private function addKeyboradListener():void
    {
      var key:MovieClip;
      var i:uint;
      for(i = 1; i <= 24; i++)
      {
        key = keyboardAsset["key" + i];
        key.addEventListener(MouseEvent.MOUSE_DOWN, keyDownHandler);
        key.useHandCursor = key.buttonMode = true;
      }
    }

    private function initializeCombobox():void
    {
      waveTypeCombo = keyboardAsset.waveTypeCombo;
            waveTypeCombo.addItem( { label: "サイン波", data:SINE } );
            waveTypeCombo.addItem( { label: "矩形波", data:PULSE } );
            waveTypeCombo.addItem( { label: "のこぎり波", data:SAW } );
            waveTypeCombo.addItem( { label: "三角波", data:TRIANGLE } );
            waveTypeCombo.addItem( { label: "ノイズ", data:NOISE } );
            waveTypeCombo.addEventListener(Event.CHANGE, waveTypeComboChangeHandler);
    }

    private function waveTypeComboChangeHandler(e:Event):void
    {
      waveType = waveTypeCombo.value;
    }

    private function initializeAmplifier():void
    {
      amplifier = keyboardAsset.amplifier;
      amplifier.value = volume * 100;
      amplifier.width = 100;
      amplifier.tickInterval = 5;
      amplifier.addEventListener(SliderEvent.THUMB_DRAG, amplifierDrag);
    }

    private function amplifierDrag(e:SliderEvent):void
    {
      volume = amplifier.value / 100;
    }

    private function keyDownHandler(e:MouseEvent):void
    {
      stage.addEventListener(MouseEvent.MOUSE_UP, stageUpHandler);
      setFreqency(e.currentTarget as MovieClip);
      ch1 = synth1.play();
    }

    private function stageUpHandler(e:MouseEvent):void
    {
      stage.removeEventListener(MouseEvent.MOUSE_UP, stageUpHandler);
      ch1.stop();
    }

    private function initializeKeyboardTable():void
    {
      keyboardTable =
      [
        [0, "F"],
        [0, "F#"],
        [0, "G"],
        [0, "G#"],
        [1, "A"],
        [1, "A#"],
        [1, "B"],
        [1, "C"],
        [1, "C#"],
        [1, "D"],
        [1, "D#"],
        [1, "E"],
        [1, "F"],
        [1, "F#"],
        [1, "G"],
        [1, "G#"],
        [2, "A"],
        [2, "A#"],
        [2, "B"],
        [2, "C"],
        [2, "C#"],
        [2, "D"],
        [2, "D#"],
        [2, "E"]
      ]
    }

    private function setFreqency(key:MovieClip):void
    {
      var keyNum:uint = uint(key.name.substr("key".length));
      var keyStep:uint = keyNum % 12;
      keyStep = keyStep < 5 ? keyStep + 7 : keyStep - 5;
      var octave:Number = 110.5* Math.pow(2, keyboardTable[keyNum - 1][0]);
      frequency = octave * Math.pow(2, keyStep / 12);
    }

    private function waveGenerator(e:SampleDataEvent):void
    {
      var sample:Number;
      var f0:Number = frequency / 44100;
      var f1:Number = 44100 / frequency;
      var PAI2:Number = 2 * Math.PI;
      var waveArray:Array = new Array();

        for ( var c:int=0; c < 2048; c++ )
        {
          switch(waveType)
          {
            //サイン波
            case SINE:
            sample = Math.sin(PAI2 * f0 * Number(c + e.position));
            break;

            //矩形波
            case PULSE:
            sample = (Math.sin(PAI2 * f0 * Number(c + e.position)) < 0) ? 1 : -1;
            break;

            //のこぎり波
            case SAW:
            sample = 2 * f0 * ((c + e.position) % f1) - 1;
            break;

            //三角波
            case TRIANGLE:
            sample = (Math.sin(PAI2 * f0 * Number(c + e.position)) < 0) ?
                (4 * f0 * ((c + e.position) % (f1 / 2)) - 1) :
                (-4 * f0 * ((c + e.position) % (f1 / 2)) + 1);
            break;

            //ホワイトノイズ
            case NOISE:
            sample = Math.random() * 2 - 1;
            break;

            default: break;
          }

          //音量調節
          sample *= volume;

          //範囲外チェック
          sample = (sample < -1) ? -1 : sample;
          sample = (sample > 1) ? 1 : sample;

            e.data.writeFloat(sample);
            e.data.writeFloat(sample);

            //波形描画用に保存
            if(waveArray.length < 512)
            {
              waveArray.push(sample);
            }
        }

        //波形描画
        drawWaveType(waveArray);
    }

    private function initializeWaveTypeCanvas():void
    {
      waveTypeCanvasBase = new Sprite();
      addChild(waveTypeCanvasBase);
      waveTypeCanvasBase.graphics.beginFill(0x333333, 1);
      waveTypeCanvasBase.graphics.drawRect(0,0,WAVE_TYPE_CANVAS_BASE_WIDTH,WAVE_TYPE_CANVAS_BASE_HEIGHT);
      waveTypeCanvasBase.graphics.endFill();
      waveTypeCanvasBase.x = 230;
      waveTypeCanvasBase.y = 20;
      waveTypeCanvas = new Sprite();
      addChild(waveTypeCanvas);
    }

    private function drawWaveType(waveArray:Array):void
    {
      var g:Graphics = waveTypeCanvas.graphics;
      var step:uint = waveArray.length / WAVE_TYPE_CANVAS_BASE_WIDTH;
      var i:uint;
      var ptx:uint = waveTypeCanvasBase.x;
      var pty:uint = waveTypeCanvasBase.y + WAVE_TYPE_CANVAS_BASE_HEIGHT / 2;
      g.clear();
      g.lineStyle(1, 0xFF231E, 1);
      g.moveTo(ptx, pty);

      for(i = 0; i < WAVE_TYPE_CANVAS_BASE_WIDTH; i = i + step)
      {
        g.lineTo(i + ptx, waveArray[i] * WAVE_TYPE_CANVAS_BASE_HEIGHT / 2 + pty);
      }

    }
  }
}
LINEで送る
Pocket

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

ページトップへ戻る