※2008.12.20追記
→このとき書いたものから修正した、のこぎり波とかのソースです。
こんにちは、ソフトバンクだというのに、この間までVodafone携帯を使っていました。いいチャンスだと思い、思い切ってiPhoneに機種変更をしました。ところが!きちんと注意書きを読まなかったため最も楽しみにしていたAppができません。
というのも、わたくしはクレジットカードを持っていないため、iPhoneのアクティベーションができないんです。アクティベーションができないと無料のソフトもダウンロードできなくて、がっくしです。
なので、クレジットカードを申し込んだんですが、フリーという不安定な立場で審査が下りるか夜も眠れない日々です。(ウソ)
さてAstroです。音の生成って楽しいですね。
今回は、のこぎり波、矩形波などを生成してみました。式は例によって適当なんで、疑ってきいてみてください。

●サイン波、矩形波、のこぎり波、三角波、(ホワイト?)ノイズ。の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);
}
}
}
}