Context: For a legacy Flex/Actionscript drawing app, I need to add scaling of simple symbols. The app uses the Graffiti lib for drawing and the resulting shape data is stored as GraphicsPathCommands serialized to XML using the Degrafa lib for save and reload. I need to enable the user to scale these graphics and then get updated path data which can be serialized. The symbols are simple but more complicated than simple geometry. For example:
Question: I converted the SVG data for this symbol to Actionscript GraphicsPathCommands and am able to draw it, and of course translation is easy – but I don't know how I would scale it, given a bounding box defined by a user dragging out a marquee rectangle in the app.
Does anyone know of either an Actionscript way of transforming the command data, or a Javascript snippet for scaling SVG which I can port to Actionscript?
For reference, an example of the Actionscript GraphicsPathCommands for drawing a star is below.
public function DrawPathExample()
{
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
graphics.beginFill(0x003366);
graphics.drawPath(star_commands, star_coord);
}
Solution
A full solution for interactively scaling GraphicsPathCommand data is below. The path data was derived from an SVG put through this SVGParser. It generates path drawing commands in the form of graphics.lineTo(28.4,16.8);. A couple of utility functions separate the data from the commands and store them in Vectors so the data can be serialized. I don't need to use arbitrary SWGs so I just hardcoded the data.
package classes
{
import flash.display.GraphicsPathCommand;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
public class DrawSVG extends Sprite
{
private var startPt:Point = new Point();
private var selectRect:Rectangle = new Rectangle();
private var viewBox:Rectangle = new Rectangle();
protected var commands:Vector.<int> = new Vector.<int>();
protected var drawingData:Vector.<Number> = new Vector.<Number>();
protected var sourceDrawingData:Vector.<Number> = new Vector.<Number>();
public function DrawSVG()
{
super();
this.addEventListener(Event.ADDED_TO_STAGE, setup);
setupWomanData();
}
private function setup(event:Event):void
{
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
private function onMouseDown(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
// offset so graphic draws centered on click point
startPt = new Point(event.stageX - (viewBox.width /2), event.stageY - (viewBox.height /2));
selectRect = new Rectangle(startPt.x, startPt.y, viewBox.width, viewBox.height);
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, selectRect.width, selectRect.height);
}
private function onMouseMove(event:MouseEvent):void
{
selectRect.width = Math.max(viewBox.width, Math.abs(event.stageX - startPt.x));
selectRect.height = Math.max(viewBox.height, Math.abs(event.stageY - startPt.y));
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
this.graphics.clear();
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, viewBox.width * scaleFactor, viewBox.height * scaleFactor);
}
private function onMouseUp(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
createSprite(commands, drawingData);
}
private function drawSymbol(toScale:Number):void
{
drawingData.length = 0;
for (var i:int = 0; i < sourceDrawingData.length; i++) {
drawingData[i] = Math.max(sourceDrawingData[i], sourceDrawingData[i] * toScale);
drawingData[i] += i % 2 == 0 ? startPt.x : startPt.y ;
}
this.graphics.clear();
this.graphics.lineStyle();
this.graphics.beginFill(0xff0000);
this.graphics.drawPath(commands, drawingData);
this.graphics.endFill();
}
private function createSprite(command:Vector.<int>, coord:Vector.<Number>):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0xff);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
return s;
}
private function setupWomanData():void
{
commands = new Vector.<int>();
drawingData = new Vector.<Number>();
viewBox= new Rectangle(0, 0, 24.629, 52.336);
addMoveToCmd(12.31,10.3);
addCurveToCmd(13.37,10.3,14.3,9.89);
addCurveToCmd(15.24,9.48,15.94,8.78);
addCurveToCmd(16.64,8.08,17.05,7.14);
addCurveToCmd(17.46,6.2,17.46,5.15);
addCurveToCmd(17.46,4.1,17.05,3.16);
addCurveToCmd(16.64,2.23,15.94,1.52);
addCurveToCmd(15.24,0.82,14.3,0.41);
addCurveToCmd(13.37,0,12.31,0);
addCurveToCmd(11.26,0,10.33,0.41);
addCurveToCmd(9.39,0.82,8.69,1.52);
addCurveToCmd(7.98,2.23,7.57,3.16);
addCurveToCmd(7.16,4.1,7.16,5.15);
addCurveToCmd(7.16,6.2,7.57,7.14);
addCurveToCmd(7.98,8.08,8.69,8.78);
addCurveToCmd(9.39,9.48,10.33,9.89);
addCurveToCmd(11.26,10.3,12.31,10.3);
addLineToCmd(12.314,10.304);
addMoveToCmd(24.6,26.36);
addLineToCmd(20.7,12.77);
addCurveToCmd(20.62,12.3,20.39,11.91);
addCurveToCmd(20.15,11.51,19.81,11.23);
addCurveToCmd(19.47,10.94,19.04,10.78);
addCurveToCmd(18.61,10.62,18.14,10.62);
addLineToCmd(6.49,10.62);
addCurveToCmd(6.02,10.62,5.59,10.78);
addCurveToCmd(5.16,10.94,4.82,11.23);
addCurveToCmd(4.48,11.51,4.24,11.91);
addCurveToCmd(4.01,12.3,3.93,12.77);
addLineToCmd(0.03,26.36);
addCurveToCmd(0.01,26.4,0.01,26.45);
addCurveToCmd(-0.01,26.5,-0.01,26.55);
addCurveToCmd(0.01,26.6,0.01,26.65);
addCurveToCmd(0.02,26.69,0.03,26.74);
addCurveToCmd(-0.15,27.95,0.55,28.69);
addCurveToCmd(1.25,29.44,2.2,29.6);
addCurveToCmd(3.15,29.77,4.05,29.3);
addCurveToCmd(4.95,28.84,5.17,27.63);
addLineToCmd(6.85,21.37);
addLineToCmd(4.07,34.88);
addCurveToCmd(3.81,35.51,3.91,36.15);
addCurveToCmd(4,36.78,4.35,37.3);
addCurveToCmd(4.7,37.81,5.26,38.13);
addCurveToCmd(5.81,38.45,6.49,38.45);
addLineToCmd(6.78,38.45);
addLineToCmd(6.78,49.72);
addCurveToCmd(6.78,50.99,7.59,51.62);
addCurveToCmd(8.41,52.25,9.39,52.25);
addCurveToCmd(10.37,52.25,11.19,51.62);
addCurveToCmd(12,50.99,12,49.72);
addLineToCmd(12,38.45);
addLineToCmd(12.63,38.45);
addLineToCmd(12.63,49.72);
addCurveToCmd(12.63,50.99,13.44,51.62);
addCurveToCmd(14.26,52.25,15.24,52.25);
addCurveToCmd(16.22,52.25,17.04,51.62);
addCurveToCmd(17.85,50.99,17.85,49.72);
addLineToCmd(17.85,38.45);
addLineToCmd(18.14,38.45);
addCurveToCmd(18.82,38.45,19.38,38.13);
addCurveToCmd(19.93,37.81,20.28,37.3);
addCurveToCmd(20.63,36.78,20.72,36.14);
addCurveToCmd(20.81,35.51,20.56,34.87);
addLineToCmd(17.78,21.37);
addLineToCmd(19.45,27.58);
addCurveToCmd(19.67,28.79,20.57,29.27);
addCurveToCmd(21.47,29.75,22.43,29.6);
addCurveToCmd(23.38,29.45,24.08,28.7);
addCurveToCmd(24.78,27.96,24.6,26.74);
addCurveToCmd(24.61,26.69,24.62,26.65);
addCurveToCmd(24.63,26.6,24.63,26.55);
addCurveToCmd(24.63,26.5,24.62,26.45);
addCurveToCmd(24.62,26.4,24.6,26.36);
addLineToCmd(24.601,26.356);
}
protected function addCurveToCmd(p1:Number, p2:Number, p3:Number, p4:Number):void
{
commands.push(GraphicsPathCommand.CURVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
sourceDrawingData.push(p3);
sourceDrawingData.push(p4);
}
protected function addMoveToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.MOVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
protected function addLineToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.LINE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
}
}
Seems like there is a pretty straightforward way to do this. It looks like the only thing to scale are the coordinates themselves, so you may just apply a scale factor.
Based on your example:
public function ASEntryPoint() {
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
//reference shape to detect initial size
var s:Shape = shapeInRect(star_commands, star_coord);
var bounds:Rectangle = s.getBounds(s);
s.graphics.lineStyle(1);
s.graphics.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
addChild(s);
//fit to target
var targetSize:Rectangle = new Rectangle(150, 100, 75, 60);
//detect lesser factor - assuming you need to preserve proportions
var kx:Number = targetSize.width / (bounds.width);
var ky:Number = targetSize.height / (bounds.height);
var toUse:Number = kx < ky ? kx : ky;
//apply to coords
for (var i:int = 0; i < star_coord.length; i++) {
//size
star_coord[i] *= toUse;
//fix initial offset
star_coord[i] -= i % 2 == 0 ? bounds.x * toUse : bounds.y * toUse;
}
//draw
addChild(shapeInRect(star_commands, star_coord, targetSize));
}
private function shapeInRect(command:Vector.<int>, coord:Vector.<Number>, rect:Rectangle = null):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0x003366);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
if (rect){
s.graphics.lineStyle(1);
s.graphics.drawRect(0, 0, rect.width, rect.height);
s.x = rect.x;
s.y = rect.y;
}
return s;
}
I've been working on a way to stream mic data to a server, cycle back to clients, and play back in a packet by packet manner. So far, I have the client connectivity, intercommunication, voice sending, voice receiving, buffer storage, and a broken playback. The voice coming back plays at the proper speed without scratchy noise, but it's only ever playing a % of the voice buffer, recycling, and playing the new first %. I need the client to only play sound data it retreives once (asside from resampling for proper audio speeds) and then never again.
package Voip
{
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.system.System;
import flash.utils.ByteArray;
import flash.utils.Timer;
public class SoundObj
{
private var ID:int;
public var sound:Sound;
public var buf:ByteArray;
public var _vbuf:ByteArray;
public var _numSamples:int;
public var _phase:Number = 0;
public var killtimer:Timer = null;
public var _delaytimer:Timer = new Timer(1000, 1);
public function SoundObj(id:int)
{
ID = id;
buf = new ByteArray();
_vbuf = new ByteArray();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer, false, 0, true);
sound.play();
}
public function receive(bytes:ByteArray):void {
var i:int = _vbuf.position;
_vbuf.position = _vbuf.length;
_vbuf.writeBytes(bytes);
_vbuf.position = i;
_numSamples = _vbuf.length/4;
/*var i:int = buf.position;
buf.position = buf.length; // write to end
buf.writeBytes(bytes);
buf.position = i; // return to origin
if (_delaytimer == null) {
_delaytimer = new Timer(1000, 1);
_delaytimer.addEventListener(TimerEvent.TIMER, finaldata);
_delaytimer.start();
}
if (!_delaytimer.running) {
// timer not running, dump buffer and reset.
//var index:int = _vbuf.position;
//_vbuf.position = _vbuf.length;
//_vbuf.writeBytes(buf);
_vbuf = buf;
_vbuf.position = 0;
buf = new ByteArray();
//_vbuf.position = index;
//sound.extract(_vbuf, int(_vbuf.length * 44.1));
_phase = 0;
_numSamples = _vbuf.length/4;
// reset killtimer to silence timeout
killtimer = new Timer(1000, 1);
killtimer.addEventListener(TimerEvent.TIMER, killtimerEvent);
killtimer.start();
}*/
}
public function killtimerEvent(event:TimerEvent):void {
_delaytimer = null;
}
// send remaining data
public function finaldata(event:TimerEvent):void {
if (buf.length > 0) {
trace("adding final content");
//var _buf:ByteArray = new ByteArray();
//var index:int = int(_phase)*4;
//if (index >= _vbuf.length)
// index = _vbuf.position;
/*_buf.writeBytes(_vbuf, index, _vbuf.length-index);
_buf.writeBytes(buf);
buf = new ByteArray();*/
//_vbuf = _buf;
// add remaining buffer to playback
var index:int = _vbuf.position;
_vbuf.position = _vbuf.length;
_vbuf.writeBytes(buf);
_vbuf.position = index;
// wipe buffer
buf = new ByteArray();
//sound.extract(_vbuf, int(_vbuf.length * 44.1));
_phase = 0;
//_numSamples = _vbuf.length/4;
_numSamples = _vbuf.length/4;
// reset killtimer to silence timeout
killtimer = new Timer(1000, 1);
killtimer.addEventListener(TimerEvent.TIMER, killtimerEvent);
killtimer.start();
}
}
public function SoundBuffer(event:SampleDataEvent):void {
//try {
//trace("[SoundBuffer:"+ID+"]");
//sound.removeEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
// buffer 4KB of data
for (var i:int = 0; i < 4096; i++)
{
var l:Number = 0;
var r:Number = 0;
if (_vbuf.length > int(_phase)*4) {
_vbuf.position = int(_phase)*4;
l = _vbuf.readFloat();
if (_vbuf.position < _vbuf.length)
r = _vbuf.readFloat();
else
r = l;
}
//if (_vbuf.position == _vbuf.length)
//_vbuf = new ByteArray();
event.data.writeFloat(l);
event.data.writeFloat(r);
_phase += (16/44.1);
if (_phase >= _numSamples) {
_phase -= _numSamples;
}
}
System.gc();
}
}
}
The initial idea was to create a SoundObj in my scene, use obj.receive(bytes) to add data to the buffer to be played back the next time the Sound player needed new data. I've been fiddling around trying to get it to work in one way or another since. The timers were designed to determine when to buffer more data, but never really worked as desired.
Proper double buffer, proper playback.
package VoipOnline
{
import flash.events.SampleDataEvent;
import flash.events.TimerEvent;
import flash.media.Sound;
import flash.system.System;
import flash.utils.ByteArray;
import flash.utils.Timer;
import flashx.textLayout.formats.Float;
public class SoundObj
{
public var ID:int;
public var sound:Sound;
internal var _readBuf:ByteArray;
internal var _writeBuf:ByteArray;
internal var n:Number;
internal var _phase:Number;
internal var _numSamples:int;
internal var myTimer:Timer;
internal var bytes:int;
public function SoundObj(id:int)
{
ID = id;
_readBuf = new ByteArray();
_writeBuf = new ByteArray();
bytes = 0;
myTimer = new Timer(10000, 0);
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
myTimer.start();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
sound.play();
}
public function receive(bytes:ByteArray):void
{
var i:int = _writeBuf.position;
_writeBuf.position = _writeBuf.length;
_writeBuf.writeBytes(bytes);
_writeBuf.position = i;
this.bytes += bytes.length;
}
private function timerHandler(e:TimerEvent):void{
trace((bytes/10) + " bytes per second.");
bytes = 0;
}
public function SoundBuffer(event:SampleDataEvent):void
{
//trace((_readBuf.length/8)+" in buffer, and "+(_writeBuf.length/8)+" waiting.");
for (var i:int = 0; i < 4096; i++)
{
var l:Number = 0; // silence
var r:Number = 0; // silence
if (_readBuf.length > int(_phase)*8) {
_readBuf.position = int(_phase)*8;
l = _readBuf.readFloat();
if (_readBuf.position < _readBuf.length)
r = _readBuf.readFloat();
else {
r = l;
Buffer();
}
} else {
Buffer();
}
event.data.writeFloat(l);
event.data.writeFloat(r);
_phase += 0.181;
}
}
private function Buffer():void {
// needs new data
// snip 4096 bytes
var buf:ByteArray = new ByteArray();
var len:int = (_writeBuf.length >= 4096 ? 4096 : _writeBuf.length);
buf.writeBytes(_writeBuf, 0, len);
// remove snippet
var tmp:ByteArray = new ByteArray();
tmp.writeBytes(_writeBuf, len, _writeBuf.length-len);
_writeBuf = tmp;
// plug in snippet
_readBuf = buf;
_writeBuf = new ByteArray();
_readBuf.position = 0;
_phase = 0;
}
}
}
These code snippets are based on this mic setup:
mic = Microphone.getMicrophone();
mic.addEventListener(SampleDataEvent.SAMPLE_DATA, this.micParse); // raw mic data stream handler
mic.codec = SoundCodec.SPEEX;
mic.setUseEchoSuppression(true);
mic.gain = 100;
mic.rate = 44;
mic.setSilenceLevel(voicelimit.value, 1);
After considerable testing, this seems to provide the best results so far. Little grainy, but it IS compressed and filterred. Some of the issues I'm having seem to be the fault of the server. I'm only receiving ~30% of the bytes I'm sending out. That being said, the code above works. You simply adjust the _phase increment to modify speed. (0.181 == 16/44/2) Credit will go where credit is due, even if his sample didn't quite solve the issues at hand, it was still a considerable step forward.
I have prepared some sample data and fed it into your example and got only noise sound. I have simplified your class to only two buffers one for receiving samples and second one for providing them. Hope this will work:
package {
import flash.events.*;
import flash.media.*;
import flash.utils.*;
public class SoundObj
{
private var ID:int;
public var sound:Sound;
public var _readBuf:ByteArray;
public var _writeBuf:ByteArray;
public function SoundObj(id:int)
{
ID = id;
_readBuf = new ByteArray();
_writeBuf = new ByteArray();
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, SoundBuffer);
sound.play();
}
public function receive(bytes:ByteArray):void
{
var i:int = _writeBuf.position;
_writeBuf.position = _writeBuf.length;
_writeBuf.writeBytes(bytes);
_writeBuf.position = i;
sound.play();
}
public function SoundBuffer(event:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
if (_readBuf.position < _readBuf.length)
event.data.writeFloat(_readBuf.readFloat());
else
{
if (_writeBuf.length >= 81920)
{
_readBuf = _writeBuf;
_writeBuf = new ByteArray();
}
if (_readBuf.position < _readBuf.length)
event.data.writeFloat(_readBuf.readFloat());
else
{
//event.data.writeFloat( 0 );
}
}
}
}
}
}
// microphone sample parsing with rate change
function micParse(event:SampleDataEvent):void
{
var soundBytes:ByteArray = new ByteArray();
var i:uint = 0;
var n:Number = event.data.bytesAvailable * 44 / mic.rate * 2; // *2 for stereo
var f:Number = 0;
while(event.data.bytesAvailable)
{
i++;
var sample:Number = event.data.readFloat();
for (; f <= i; f+= mic.rate / 2 / 44)
{
soundBytes.writeFloat(sample);
}
}
snd.receive(soundBytes);
}
I'm making sinewave sound with AS3 using SampleDataEvent. I can make a pure sinewave easily enough, but if i try to sweep through frequencies i get horrible popping. Here's the code i'm using - any help would be great.
package
{
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.Timer;
public class DynamicSound extends Sprite
{
private var sound:Sound;
private var noise:Number = 0;
private var f:Number = 1000;
private var v:Number = 1;
private var sweepDown:Boolean = true;
// make the sound
public function DynamicSound():void
{
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onCallback);
sound.play();
sweep();
}
// create the sinewave
private function onCallback(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
noise += 1;
var sampleNumber = noise;
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
}
}
// sweep up and down frequency
private function sweep() {
var timer:Timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
if(sweepDown){
f--;
} else {
f++;
}
if (f <=600 ){
sweepDown = false;
}
if (f >= 1000) {
sweepDown = true;
}
trace(f);
}
}
}
}
I believe the fault is with the approach, the sweep needs to be gradual, when you step it like that you create an abrupt change in the sound wave, which is interpreted as a short high-frequency signal - a pop or click. The way I'd recommend you do this modulation would be inside the callback loop.
set a destination freq (dF) and a current frequency(cF), and instead of doing an abrupt change set cF = cF*0.8 + dF*0.2 inside the loop, this should remove the abrupt change and have it happen over several samples.
//When the frequency changes, the phase will also change.
//By adjusting the phase, there will no longer be horrible popping.
//This will solve the problem:
var f_old:Number = f;
noise=noise*f_old/f; f_old=f;
//Copy/paste in the first frame of the main timeline:
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.Timer;
var sound:Sound;
var noise:Number = 0;
var f:Number = 1000;
var v:Number = 1;
var sweepDown:Boolean = true;
var f_old:Number = f;
// make the sound
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onCallback);
sound.play();
sweep();
// create the sinewave
function onCallback(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
noise += 1;
var sampleNumber = noise;
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
}
}
// sweep up and down frequency
function sweep() {
var timer:Timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
if(sweepDown){
f--;
} else {
f++;
}
if (f <=600 ){
sweepDown = false;
}
if (f >= 1000) {
sweepDown = true;
}
trace(f);
//Keep the instantaneous value at the same level:
noise=noise*f_old/f;
f_old=f;
}
}
//Explanation:
//The instantaneous value of the wave must not change at the moment of frequency change.
//In this script the instantaneous value is determined by:
//v * Math.sin(sampleNumber * f / 44100)
//noise = sampleNumber → v * Math.sin(sampleNumber * f / 44100) = v * Math.sin(noise * f / 44100)
//Keep the instantaneous value at the same level:
//v * Math.sin(noise_new * f / 44100) = v * Math.sin(noise* f_old / 44100)
//Cancel out v*Math.sin and /44100 →
//noise_new * f = noise* f_old → noise_new = noise*f_old/f
//Because noise gets a new value, there is no need to give noise the name noise_new. →
//noise = noise*f_old/f