├── README.md ├── as3sfxr.as3proj ├── bin └── as3sfxr.swf └── src ├── SfxrApp.as ├── SfxrParams.as ├── SfxrSynth.as ├── assets ├── Thumbs.db ├── amiga4ever.ttf ├── as3sfxr.png └── sfbtom.png └── ui ├── TinyButton.as ├── TinyCheckbox.as └── TinySlider.as /README.md: -------------------------------------------------------------------------------- 1 | A port of sfxr from C++ to AS3, using the new sound and file capabilities of Flash Player 10. 2 | 3 | [Use the tool to generate sounds here!](http://www.superflashbros.net/as3sfxr/) 4 | 5 | sfxr was originally created by Tomas Pettersson: http://www.drpetter.se/project_sfxr.html 6 | 7 | ### New Features 8 | * Asynchronous caching 9 | * Cache-during-first-play 10 | * Automatic cache clearing on parameter change 11 | * Faster synthesis 12 | 13 | Choose from 4 different osciltors - square, saw, sine and noise. Adjust the 22 parameters to find a sound effect you like. Then save it out as a .wav file, or save the parameter settings to load back in later and tweak the sound further. 14 | 15 | Features 7 'generator' functions, which produce random but familiar game sounds such as pickup/coin, laser/shoot and explosion. 16 | 17 | ### as3sfxr API 18 | You can use the SfxrSynth class in your own code to generate sfx on the fly, without the need to import .wav files. The first time a sound is played, it's synthesized live, but also cached, so that the next time it's played, it's just played out of the cache. Alternatively, pre-cache the sound at a time of your choosing to have it ready to play. 19 | 20 | Find a sound you like using the app, copy out the settings and paste them right into your code: 21 | 22 | ``` 23 | var synth:SfxrSynth = new SfxrSynth(); 24 | synth.params.setSettingsString("0,,0.271,,0.18,0.395,,0.201,,,,,,0.284,,,,,0.511,,,,,0.5"); 25 | ... 26 | synth.play(); 27 | ``` 28 | 29 | ### Mutations 30 | The playMutated() method allows you to play a slightly mutated version of your sound, without losing the original settings. All the mutated sounds will therefore be based around the original sound, whereas just using the mutate() method changes the settings each time causing the sound to drift away from the original. The mutation parameter controls the size of the mutation 31 | 32 | ``` 33 | var synth:SfxrSynth = new SfxrSynth(); 34 | synth.params.setSettingsString("0,,0.271,,0.18,0.395,,0.201,,,,,,0.284,,,,,0.511,,,,,0.5"); 35 | setInterval(synth.playMutated, 1000, 0.05); 36 | ``` 37 | 38 | ###Caching 39 | The caching methods cacheSound() and cacheMutations() allow you to generate the full wave whenever you like, before playing the cached wave. Reading from the wave ByteArray is a lot faster than creating the wave as it plays. If a callback function is passed in, the sound is cached asynchronously, taking a few milliseconds per frame to cache and calling the callback when it's complete. Especially useful for caching a number of mutations of long sounds. 40 | 41 | ``` 42 | var synth:SfxrSynth = new SfxrSynth(); 43 | synth.params.setSettingsString("0,,0.271,,0.18,0.395,,0.201,,,,,,0.284,,,,,0.511,,,,,0.5"); 44 | synth.cacheSound(); 45 | ... 46 | synth.play(); 47 | ``` 48 | 49 | Enjoy! 50 | -------------------------------------------------------------------------------- /as3sfxr.as3proj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /bin/as3sfxr.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFBTom/as3sfxr/5a5032e21b37f9b308ffee203e23b988714bcd57/bin/as3sfxr.swf -------------------------------------------------------------------------------- /src/SfxrApp.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.CapsStyle; 4 | import flash.display.DisplayObject; 5 | import flash.display.GraphicsPath; 6 | import flash.display.GraphicsSolidFill; 7 | import flash.display.GraphicsStroke; 8 | import flash.display.IGraphicsData; 9 | import flash.display.JointStyle; 10 | import flash.display.LineScaleMode; 11 | import flash.display.Sprite; 12 | import flash.events.ContextMenuEvent; 13 | import flash.events.Event; 14 | import flash.events.KeyboardEvent; 15 | import flash.events.MouseEvent; 16 | import flash.events.TextEvent; 17 | import flash.geom.Rectangle; 18 | import flash.net.FileFilter; 19 | import flash.net.FileReference; 20 | import flash.net.navigateToURL; 21 | import flash.net.URLRequest; 22 | import flash.text.Font; 23 | import flash.text.TextField; 24 | import flash.text.TextFieldType; 25 | import flash.text.TextFormat; 26 | import flash.text.TextFormatAlign; 27 | import flash.ui.ContextMenu; 28 | import flash.ui.Keyboard; 29 | import flash.ui.Mouse; 30 | import flash.ui.MouseCursor; 31 | import flash.utils.ByteArray; 32 | import flash.utils.Dictionary; 33 | import flash.utils.Endian; 34 | import ui.TinyButton; 35 | import ui.TinyCheckbox; 36 | import ui.TinySlider; 37 | 38 | /** 39 | * SfxrApp 40 | * 41 | * Copyright 2010 Thomas Vian 42 | * 43 | * Licensed under the Apache License, Version 2.0 (the "License"); 44 | * you may not use this file except in compliance with the License. 45 | * You may obtain a copy of the License at 46 | * 47 | * http://www.apache.org/licenses/LICENSE-2.0 48 | * 49 | * Unless required by applicable law or agreed to in writing, software 50 | * distributed under the License is distributed on an "AS IS" BASIS, 51 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 52 | * See the License for the specific language governing permissions and 53 | * limitations under the License. 54 | * 55 | * @author Thomas Vian 56 | */ 57 | [SWF(width='640', height='500', backgroundColor='#C0B090', frameRate='25')] 58 | public class SfxrApp extends Sprite 59 | { 60 | //-------------------------------------------------------------------------- 61 | // 62 | // Properties 63 | // 64 | //-------------------------------------------------------------------------- 65 | 66 | [Embed(source = "assets/amiga4ever.ttf", fontName = "Amiga4Ever", mimeType = "application/x-font", embedAsCFF = "false")] 67 | private var Amiga4Ever:Class; // Pixel font, original was in a tga file 68 | 69 | [Embed(source = "assets/as3sfxr.png")] 70 | private var As3sfxrLogo:Class; // as3sfxr logo, for the top right 71 | 72 | [Embed(source = "assets/sfbtom.png")] 73 | private var SfbtomLogo:Class; // SFBTOM logo, for the bottom left corner 74 | 75 | private var _synth:SfxrSynth; // Synthesizer instance 76 | 77 | private var _sampleRate:uint = 44100; // Sample rate to export .wav at 78 | private var _bitDepth:uint = 16; // Bit depth to export .wav at 79 | 80 | private var _playOnChange:Boolean = true; // If the sound should be played after releasing a slider or changing type 81 | private var _mutePlayOnChange:Boolean; // If the change playing should be muted because of non-user changes 82 | 83 | private var _propLookup:Dictionary; // Look up for property names using a slider key 84 | private var _sliderLookup:Object; // Look up for sliders using a property name key 85 | private var _waveformLookup:Array; // Look up for waveform buttons 86 | private var _squareLookup:Array; // Look up for sliders controlling a square wave property 87 | 88 | private var _back:TinyButton; // Button to skip back a sound 89 | private var _forward:TinyButton; // Button to skip forward a sound 90 | private var _history:Vector.; // List of generated settings 91 | private var _historyPos:int; // Current history position 92 | 93 | private var _copyPaste:TextField; // Input TextField for the settings 94 | 95 | private var _fileRef:FileReference; // File reference for loading in sfs file 96 | 97 | private var _logoRect:Rectangle; // Click rectangle for SFB website link 98 | private var _sfxrRect:Rectangle; // Click rectangle for LD website link 99 | private var _volumeRect:Rectangle; // Click rectangle for resetting volume 100 | 101 | //-------------------------------------------------------------------------- 102 | // 103 | // Constructor 104 | // 105 | //-------------------------------------------------------------------------- 106 | 107 | /** 108 | * Waits until on the stage before init 109 | */ 110 | public function SfxrApp() 111 | { 112 | if (stage) init(); 113 | else addEventListener(Event.ADDED_TO_STAGE, init); 114 | } 115 | 116 | //-------------------------------------------------------------------------- 117 | // 118 | // Init Method 119 | // 120 | //-------------------------------------------------------------------------- 121 | 122 | /** 123 | * Initialises the synthesizer and draws the interface 124 | * @param e Added to stage event 125 | */ 126 | private function init(e:Event = null):void 127 | { 128 | removeEventListener(Event.ADDED_TO_STAGE, init); 129 | 130 | _synth = new SfxrSynth(); 131 | _synth.params.randomize(); 132 | 133 | _propLookup = new Dictionary(); 134 | _sliderLookup = {}; 135 | _waveformLookup = []; 136 | _squareLookup = []; 137 | 138 | _history = new Vector.(); 139 | _history.push(_synth.params); 140 | 141 | drawGraphics(); 142 | drawButtons(); 143 | drawSliders(); 144 | drawCopyPaste(); 145 | 146 | updateSliders(); 147 | updateButtons(); 148 | updateCopyPaste(); 149 | } 150 | 151 | //-------------------------------------------------------------------------- 152 | // 153 | // Button Methods 154 | // 155 | //-------------------------------------------------------------------------- 156 | 157 | /** 158 | * Adds the buttons to the stage 159 | */ 160 | private function drawButtons():void 161 | { 162 | // Generator 163 | addButton("PICKUP/COIN", clickPickupCoin, 4, 32); 164 | addButton("LASER/SHOOT", clickLaserShoot, 4, 62); 165 | addButton("EXPLOSION", clickExplosion, 4, 92); 166 | addButton("POWERUP", clickPowerup, 4, 122); 167 | addButton("HIT/HURT", clickHitHurt, 4, 152); 168 | addButton("JUMP", clickJump, 4, 182); 169 | addButton("BLIP/SELECT", clickBlipSelect, 4, 212); 170 | addButton("MUTATE", clickMutate, 4, 339); 171 | addButton("RANDOMIZE", clickRandomize, 4, 369, 2); 172 | 173 | // History 174 | _back = addButton("BACK", clickBack, 4, 399); 175 | _forward = addButton("FORWARD", clickForward, 4, 429); 176 | _back.enabled = false; 177 | _forward.enabled = false; 178 | 179 | // Waveform 180 | addButton("SQUAREWAVE", clickSquarewave, 130, 28, 1, true); 181 | addButton("SAWTOOTH", clickSawtooth, 250, 28, 1, true); 182 | addButton("SINEWAVE", clickSinewave, 370, 28, 1, true); 183 | addButton("NOISE", clickNoise, 490, 28, 1, true); 184 | 185 | // Play / save / export 186 | addButton("PLAY SOUND", clickPlaySound, 490, 271); 187 | addButton("LOAD SOUND", clickLoadSound, 490, 321); 188 | addButton("SAVE SOUND", clickSaveSound, 490, 351); 189 | addButton("EXPORT .WAV", clickExportWav, 490, 401, 3); 190 | addButton("44100 HZ", clickSampleRate, 490, 431); 191 | addButton("16-BIT", clickBitDepth, 490, 461); 192 | } 193 | 194 | /** 195 | * Adds a single button 196 | * @param label Text to display on the button 197 | * @param onClick Callback function called when the button is clicked 198 | * @param x X position of the button 199 | * @param y Y position of the button 200 | * @param border Thickness of the border in pixels 201 | * @param selectable If the button is selectable 202 | * @param selected If the button starts as selected 203 | */ 204 | private function addButton(label:String, onClick:Function, x:Number, y:Number, border:Number = 1, selectable:Boolean = false):TinyButton 205 | { 206 | var button:TinyButton = new TinyButton(onClick, label, border, selectable); 207 | button.x = x; 208 | button.y = y; 209 | addChild(button); 210 | 211 | if(selectable) _waveformLookup.push(button); 212 | 213 | return button; 214 | } 215 | 216 | /** 217 | * Updates the buttons to reflect the synthesizer 218 | */ 219 | private function updateButtons():void 220 | { 221 | _mutePlayOnChange = true; 222 | 223 | selectedSwitch(_waveformLookup[_synth.params.waveType]); 224 | 225 | _mutePlayOnChange = false; 226 | } 227 | 228 | //-------------------------------------------------------------------------- 229 | // 230 | // Generator Methods 231 | // 232 | //-------------------------------------------------------------------------- 233 | 234 | /** 235 | * Sets the synthesizer to generate a pickup/coin sound and previews it 236 | * @param button Button pressed 237 | */ 238 | private function clickPickupCoin(button:TinyButton):void 239 | { 240 | addToHistory(); 241 | _synth.params.generatePickupCoin(); 242 | updateSliders(); 243 | updateButtons(); 244 | updateCopyPaste(); 245 | _synth.play(); 246 | } 247 | 248 | /** 249 | * Sets the synthesizer to generate a laser/shoot sound and previews it 250 | * @param button Button pressed 251 | */ 252 | private function clickLaserShoot(button:TinyButton):void 253 | { 254 | addToHistory(); 255 | _synth.params.generateLaserShoot(); 256 | updateSliders(); 257 | updateButtons(); 258 | updateCopyPaste(); 259 | _synth.play(); 260 | } 261 | 262 | /** 263 | * Sets the synthesizer to generate an explosion sound and previews it 264 | * @param button Button pressed 265 | */ 266 | private function clickExplosion(button:TinyButton):void 267 | { 268 | addToHistory(); 269 | _synth.params.generateExplosion(); 270 | updateSliders(); 271 | updateButtons(); 272 | updateCopyPaste(); 273 | _synth.play(); 274 | } 275 | 276 | /** 277 | * Sets the synthesizer to generate a powerup sound and previews it 278 | * @param button Button pressed 279 | */ 280 | private function clickPowerup(button:TinyButton):void 281 | { 282 | addToHistory(); 283 | _synth.params.generatePowerup(); 284 | updateSliders(); 285 | updateButtons(); 286 | updateCopyPaste(); 287 | _synth.play(); 288 | } 289 | 290 | /** 291 | * Sets the synthesizer to generate a hit/hurt sound and previews it 292 | * @param button Button pressed 293 | */ 294 | private function clickHitHurt(button:TinyButton):void 295 | { 296 | addToHistory(); 297 | _synth.params.generateHitHurt(); 298 | updateSliders(); 299 | updateButtons(); 300 | updateCopyPaste(); 301 | _synth.play(); 302 | } 303 | 304 | /** 305 | * Sets the synthesizer to generate a jump sound and previews it 306 | * @param button Button pressed 307 | */ 308 | private function clickJump(button:TinyButton):void 309 | { 310 | addToHistory(); 311 | _synth.params.generateJump(); 312 | updateSliders(); 313 | updateButtons(); 314 | updateCopyPaste(); 315 | _synth.play(); 316 | } 317 | 318 | /** 319 | * Sets the synthesizer to generate a blip/select sound and previews it 320 | * @param button Button pressed 321 | */ 322 | private function clickBlipSelect(button:TinyButton):void 323 | { 324 | addToHistory(); 325 | _synth.params.generateBlipSelect(); 326 | updateSliders(); 327 | updateButtons(); 328 | updateCopyPaste(); 329 | _synth.play(); 330 | } 331 | 332 | /** 333 | * Sets the synthesizer to mutate the sound and preview it 334 | * @param button Button pressed 335 | */ 336 | private function clickMutate(button:TinyButton):void 337 | { 338 | addToHistory(); 339 | _synth.params.mutate(); 340 | updateSliders(); 341 | updateButtons(); 342 | updateCopyPaste(); 343 | _synth.play(); 344 | } 345 | 346 | /** 347 | * Sets the synthesizer to randomize the sound and preview it 348 | * @param button Button pressed 349 | */ 350 | private function clickRandomize(button:TinyButton):void 351 | { 352 | addToHistory(); 353 | _synth.params.randomize(); 354 | updateSliders(); 355 | updateButtons(); 356 | updateCopyPaste(); 357 | _synth.play(); 358 | } 359 | 360 | //-------------------------------------------------------------------------- 361 | // 362 | // History Methods 363 | // 364 | //-------------------------------------------------------------------------- 365 | 366 | /** 367 | * When the back button is clicked, moves back through the history 368 | * @param button TinyButton clicked 369 | */ 370 | private function clickBack(button:TinyButton):void 371 | { 372 | _historyPos--; 373 | if(_historyPos == 0) _back.enabled = false; 374 | if(_historyPos < _history.length - 1) _forward.enabled = true; 375 | 376 | _synth.stop(); 377 | _synth.params = _history[_historyPos]; 378 | 379 | updateSliders(); 380 | updateButtons(); 381 | updateCopyPaste(); 382 | 383 | _synth.play(); 384 | } 385 | 386 | /** 387 | * When the forward button is clicked, moves forward through the history 388 | * @param button TinyButton clicked 389 | */ 390 | private function clickForward(button:TinyButton):void 391 | { 392 | _historyPos++; 393 | if(_historyPos > 0) _back.enabled = true; 394 | if(_historyPos == _history.length - 1) _forward.enabled = false; 395 | 396 | _synth.stop(); 397 | _synth.params = _history[_historyPos]; 398 | 399 | updateSliders(); 400 | updateButtons(); 401 | updateCopyPaste(); 402 | 403 | _synth.play(); 404 | } 405 | 406 | /** 407 | * Adds a new sound effect to the history. 408 | * Called just before a new sound effect is generated. 409 | */ 410 | private function addToHistory():void 411 | { 412 | _historyPos++; 413 | _synth.params = _synth.params.clone(); 414 | _history = _history.slice(0, _historyPos); 415 | _history.push(_synth.params); 416 | 417 | _back.enabled = true; 418 | _forward.enabled = false; 419 | } 420 | 421 | //-------------------------------------------------------------------------- 422 | // 423 | // Waveform Methods 424 | // 425 | //-------------------------------------------------------------------------- 426 | 427 | /** 428 | * Selects the squarewave waveform type 429 | * @param button Button pressed 430 | */ 431 | private function clickSquarewave(button:TinyButton):void 432 | { 433 | _synth.params.waveType = 0; 434 | selectedSwitch(button); 435 | updateCopyPaste(); 436 | if (_playOnChange && !_mutePlayOnChange) _synth.play(); 437 | } 438 | 439 | /** 440 | * Selects the sawtooth waveform type 441 | * @param button Button pressed 442 | */ 443 | private function clickSawtooth(button:TinyButton):void 444 | { 445 | _synth.params.waveType = 1; 446 | selectedSwitch(button); 447 | updateCopyPaste(); 448 | if (_playOnChange && !_mutePlayOnChange) _synth.play(); 449 | } 450 | 451 | /** 452 | * Selects the sinewave waveform type 453 | * @param button Button pressed 454 | */ 455 | private function clickSinewave(button:TinyButton):void 456 | { 457 | _synth.params.waveType = 2; 458 | selectedSwitch(button); 459 | updateCopyPaste(); 460 | if (_playOnChange && !_mutePlayOnChange) _synth.play(); 461 | } 462 | 463 | /** 464 | * Selects the noise waveform type 465 | * @param button Button pressed 466 | */ 467 | private function clickNoise(button:TinyButton):void 468 | { 469 | _synth.params.waveType = 3; 470 | selectedSwitch(button); 471 | updateCopyPaste(); 472 | if (_playOnChange && !_mutePlayOnChange) _synth.play(); 473 | } 474 | 475 | /** 476 | * Unselects all the waveform buttons and selects the one passed in 477 | * @param select Selects this button 478 | */ 479 | private function selectedSwitch(select:TinyButton):void 480 | { 481 | for(var i:uint = 0, l:uint = _waveformLookup.length; i < l; i++) 482 | { 483 | if(_waveformLookup[i] != select) _waveformLookup[i].selected = false; 484 | } 485 | 486 | if(!select.selected) select.selected = true; 487 | 488 | for(i = 0; i < 2; i++) 489 | { 490 | _squareLookup[i].dimLabel = _synth.params.waveType != 0; 491 | } 492 | } 493 | 494 | //-------------------------------------------------------------------------- 495 | // 496 | // Play/Save/Export Methods 497 | // 498 | //-------------------------------------------------------------------------- 499 | 500 | /** 501 | * Previews the sound 502 | * @param button Button pressed 503 | */ 504 | private function clickPlaySound(button:TinyButton):void 505 | { 506 | _synth.play(); 507 | } 508 | 509 | /** 510 | * Opens a browse window to load a sound setting file 511 | * @param button Button pressed 512 | */ 513 | private function clickLoadSound(button:TinyButton):void 514 | { 515 | _fileRef = new FileReference(); 516 | _fileRef.addEventListener(Event.SELECT, onSelectSettings); 517 | _fileRef.browse([new FileFilter("SFX Sample Files (*.sfs)", "*.sfs")]); 518 | } 519 | 520 | /** 521 | * When the user selects a file, begins loading it 522 | * @param e Select event 523 | */ 524 | private function onSelectSettings(e:Event):void 525 | { 526 | _fileRef.cancel(); 527 | 528 | _fileRef.removeEventListener(Event.SELECT, onSelectSettings); 529 | _fileRef.addEventListener(Event.COMPLETE, onLoadSettings); 530 | _fileRef.load(); 531 | } 532 | 533 | /** 534 | * Once loaded, passes the file to the synthesizer to parse 535 | * @param e Complete event 536 | */ 537 | private function onLoadSettings(e:Event):void 538 | { 539 | _fileRef.removeEventListener(Event.COMPLETE, onLoadSettings); 540 | 541 | addToHistory(); 542 | setSettingsFile(_fileRef.data); 543 | updateSliders(); 544 | updateButtons(); 545 | updateCopyPaste(); 546 | 547 | _fileRef = null; 548 | } 549 | 550 | /** 551 | * Saves out a sound settings file 552 | * @param button Button pressed 553 | */ 554 | private function clickSaveSound(button:TinyButton):void 555 | { 556 | var file:ByteArray = getSettingsFile(); 557 | 558 | new FileReference().save(file, "sfx.sfs"); 559 | } 560 | 561 | /** 562 | * Exports the sound as a .wav file 563 | * @param button Button pressed 564 | */ 565 | private function clickExportWav(button:TinyButton):void 566 | { 567 | var file:ByteArray = _synth.getWavFile(_sampleRate, _bitDepth); 568 | 569 | new FileReference().save(file, "sfx.wav"); 570 | } 571 | 572 | /** 573 | * Switches the sample rate between 44100Hz and 22050Hz 574 | * @param button Button pressed 575 | */ 576 | private function clickSampleRate(button:TinyButton):void 577 | { 578 | if(_sampleRate == 44100) _sampleRate = 22050; 579 | else _sampleRate = 44100; 580 | 581 | button.label = _sampleRate + " HZ"; 582 | } 583 | 584 | /** 585 | * Switches the bit depth between 16-bit and 8-bit 586 | * @param button Button pressed 587 | */ 588 | private function clickBitDepth(button:TinyButton):void 589 | { 590 | if(_bitDepth == 16) _bitDepth = 8; 591 | else _bitDepth = 16; 592 | 593 | button.label = _bitDepth + "-BIT"; 594 | } 595 | 596 | //-------------------------------------------------------------------------- 597 | // 598 | // Settings File Methods 599 | // 600 | //-------------------------------------------------------------------------- 601 | 602 | /** 603 | * Writes the current parameters to a ByteArray and returns it 604 | * Compatible with the original Sfxr files 605 | * @return ByteArray of settings data 606 | */ 607 | public function getSettingsFile():ByteArray 608 | { 609 | var file:ByteArray = new ByteArray(); 610 | file.endian = Endian.LITTLE_ENDIAN; 611 | 612 | file.writeInt(102); 613 | file.writeInt(_synth.params.waveType); 614 | file.writeFloat(_synth.params.masterVolume); 615 | 616 | file.writeFloat(_synth.params.startFrequency); 617 | file.writeFloat(_synth.params.minFrequency); 618 | file.writeFloat(_synth.params.slide); 619 | file.writeFloat(_synth.params.deltaSlide); 620 | file.writeFloat(_synth.params.squareDuty); 621 | file.writeFloat(_synth.params.dutySweep); 622 | 623 | file.writeFloat(_synth.params.vibratoDepth); 624 | file.writeFloat(_synth.params.vibratoSpeed); 625 | file.writeFloat(0); 626 | 627 | file.writeFloat(_synth.params.attackTime); 628 | file.writeFloat(_synth.params.sustainTime); 629 | file.writeFloat(_synth.params.decayTime); 630 | file.writeFloat(_synth.params.sustainPunch); 631 | 632 | file.writeBoolean(false); 633 | file.writeFloat(_synth.params.lpFilterResonance); 634 | file.writeFloat(_synth.params.lpFilterCutoff); 635 | file.writeFloat(_synth.params.lpFilterCutoffSweep); 636 | file.writeFloat(_synth.params.hpFilterCutoff); 637 | file.writeFloat(_synth.params.hpFilterCutoffSweep); 638 | 639 | file.writeFloat(_synth.params.phaserOffset); 640 | file.writeFloat(_synth.params.phaserSweep); 641 | 642 | file.writeFloat(_synth.params.repeatSpeed); 643 | 644 | file.writeFloat(_synth.params.changeSpeed); 645 | file.writeFloat(_synth.params.changeAmount); 646 | 647 | return file; 648 | } 649 | 650 | /** 651 | * Reads parameters from a ByteArray file 652 | * Compatible with the original Sfxr files 653 | * @param file ByteArray of settings data 654 | */ 655 | public function setSettingsFile(file:ByteArray):void 656 | { 657 | file.position = 0; 658 | file.endian = Endian.LITTLE_ENDIAN; 659 | 660 | var version:int = file.readInt(); 661 | 662 | if(version != 100 && version != 101 && version != 102) return; 663 | 664 | _synth.params.waveType = file.readInt(); 665 | _synth.params.masterVolume = (version == 102) ? file.readFloat() : 0.5; 666 | 667 | _synth.params.startFrequency = file.readFloat(); 668 | _synth.params.minFrequency = file.readFloat(); 669 | _synth.params.slide = file.readFloat(); 670 | _synth.params.deltaSlide = (version >= 101) ? file.readFloat() : 0.0; 671 | 672 | _synth.params.squareDuty = file.readFloat(); 673 | _synth.params.dutySweep = file.readFloat(); 674 | 675 | _synth.params.vibratoDepth = file.readFloat(); 676 | _synth.params.vibratoSpeed = file.readFloat(); 677 | var unusedVibratoDelay:Number = file.readFloat(); 678 | 679 | _synth.params.attackTime = file.readFloat(); 680 | _synth.params.sustainTime = file.readFloat(); 681 | _synth.params.decayTime = file.readFloat(); 682 | _synth.params.sustainPunch = file.readFloat(); 683 | 684 | var unusedFilterOn:Boolean = file.readBoolean(); 685 | _synth.params.lpFilterResonance = file.readFloat(); 686 | _synth.params.lpFilterCutoff = file.readFloat(); 687 | _synth.params.lpFilterCutoffSweep = file.readFloat(); 688 | _synth.params.hpFilterCutoff = file.readFloat(); 689 | _synth.params.hpFilterCutoffSweep = file.readFloat(); 690 | 691 | _synth.params.phaserOffset = file.readFloat(); 692 | _synth.params.phaserSweep = file.readFloat(); 693 | 694 | _synth.params.repeatSpeed = file.readFloat(); 695 | 696 | _synth.params.changeSpeed = (version >= 101) ? file.readFloat() : 0.0; 697 | _synth.params.changeAmount = (version >= 101) ? file.readFloat() : 0.0; 698 | } 699 | 700 | //-------------------------------------------------------------------------- 701 | // 702 | // Slider Methods 703 | // 704 | //-------------------------------------------------------------------------- 705 | 706 | /** 707 | * Adds the sliders to the stage, plus single checkbox 708 | */ 709 | private function drawSliders():void 710 | { 711 | addSlider("ATTACK TIME", "attackTime", 350, 70); 712 | addSlider("SUSTAIN TIME", "sustainTime", 350, 88); 713 | addSlider("SUSTAIN PUNCH", "sustainPunch", 350, 106); 714 | addSlider("DECAY TIME", "decayTime", 350, 124); 715 | addSlider("START FREQUENCY", "startFrequency", 350, 142).defaultValue = 0.5; 716 | addSlider("MIN FREQUENCY", "minFrequency", 350, 160); 717 | addSlider("SLIDE", "slide", 350, 178, true); 718 | addSlider("DELTA SLIDE", "deltaSlide", 350, 196, true); 719 | addSlider("VIBRATO DEPTH", "vibratoDepth", 350, 214); 720 | addSlider("VIBRATO SPEED", "vibratoSpeed", 350, 232); 721 | addSlider("CHANGE AMOUNT", "changeAmount", 350, 250, true); 722 | addSlider("CHANGE SPEED", "changeSpeed", 350, 268); 723 | addSlider("SQUARE DUTY", "squareDuty", 350, 286, false, true); 724 | addSlider("DUTY SWEEP", "dutySweep", 350, 304, true, true); 725 | addSlider("REPEAT SPEED", "repeatSpeed", 350, 322); 726 | addSlider("PHASER OFFSET", "phaserOffset", 350, 340, true); 727 | addSlider("PHASER SWEEP", "phaserSweep", 350, 358, true); 728 | addSlider("LP FILTER CUTOFF", "lpFilterCutoff", 350, 376).defaultValue = 1.0; 729 | addSlider("LP FILTER CUTOFF SWEEP", "lpFilterCutoffSweep", 350, 394, true); 730 | addSlider("LP FILTER RESONANCE", "lpFilterResonance", 350, 412); 731 | addSlider("HP FILTER CUTOFF", "hpFilterCutoff", 350, 430); 732 | addSlider("HP FILTER CUTOFF SWEEP", "hpFilterCutoffSweep", 350, 448, true); 733 | addSlider("", "masterVolume", 492, 251); 734 | 735 | var checkbox:TinyCheckbox = new TinyCheckbox(onCheckboxChange, "PLAY ON CHANGE"); 736 | checkbox.x = 350; 737 | checkbox.y = 466; 738 | addChild(checkbox); 739 | } 740 | 741 | /** 742 | * Adds a single slider 743 | * @param label Text label to display next to the slider 744 | * @param property Property name to link with the slider 745 | * @param x X position of slider 746 | * @param y Y Position of slider 747 | * @param plusMinus If the slider ranges from -1 to 1 (true) or 0 to 1 (false) 748 | * @param square If the slider is linked to the square duty properties 749 | */ 750 | private function addSlider(label:String, property:String, x:Number, y:Number, plusMinus:Boolean = false, square:Boolean = false):TinySlider 751 | { 752 | var slider:TinySlider = new TinySlider(onSliderChange, label, plusMinus); 753 | slider.x = x; 754 | slider.y = y; 755 | addChild(slider); 756 | 757 | _propLookup[slider] = property; 758 | _sliderLookup[property] = slider; 759 | 760 | if (square) _squareLookup.push(slider); 761 | 762 | return slider; 763 | } 764 | 765 | /** 766 | * Updates the property on the synthesizer to the slider's value 767 | * @param slider 768 | */ 769 | private function onSliderChange(slider:TinySlider):void 770 | { 771 | _synth.params[_propLookup[slider]] = slider.value; 772 | 773 | updateCopyPaste(); 774 | 775 | if (_playOnChange && !_mutePlayOnChange) _synth.play(); 776 | } 777 | 778 | /** 779 | * Updates the sliders to reflect the synthesizer 780 | */ 781 | private function updateSliders():void 782 | { 783 | _mutePlayOnChange = true; 784 | 785 | for(var prop:String in _sliderLookup) 786 | { 787 | _sliderLookup[prop].value = _synth.params[prop]; 788 | } 789 | 790 | _mutePlayOnChange = false; 791 | } 792 | 793 | /** 794 | * Changes if the sound should play on params change 795 | * @param checkbox Checbox clicked 796 | */ 797 | private function onCheckboxChange(checkbox:TinyCheckbox):void 798 | { 799 | _playOnChange = checkbox.value; 800 | } 801 | 802 | //-------------------------------------------------------------------------- 803 | // 804 | // Copy Paste Methods 805 | // 806 | //-------------------------------------------------------------------------- 807 | 808 | /** 809 | * Adds a TextField over the whole app. 810 | * Allows for right-click copy/paste, as well as ctrl-c/ctrl-v 811 | */ 812 | private function drawCopyPaste():void 813 | { 814 | _copyPaste = new TextField(); 815 | _copyPaste.addEventListener(TextEvent.TEXT_INPUT, updateFromCopyPaste); 816 | _copyPaste.addEventListener(KeyboardEvent.KEY_DOWN, updateCopyPaste); 817 | _copyPaste.addEventListener(KeyboardEvent.KEY_UP, updateCopyPaste); 818 | _copyPaste.defaultTextFormat = new TextFormat("Amiga4Ever", 8, 0); 819 | _copyPaste.wordWrap = false; 820 | _copyPaste.multiline = false; 821 | _copyPaste.type = TextFieldType.INPUT; 822 | _copyPaste.embedFonts = true; 823 | _copyPaste.width = 640; 824 | _copyPaste.height = 580; 825 | _copyPaste.x = 0; 826 | _copyPaste.y = -20; 827 | addChild(_copyPaste); 828 | 829 | _copyPaste.contextMenu = new ContextMenu(); 830 | _copyPaste.contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, updateCopyPaste); 831 | 832 | Mouse.cursor = MouseCursor.ARROW; 833 | } 834 | 835 | /** 836 | * Updates the contents of the textfield to a representation of the settings 837 | * @param e Optional event 838 | */ 839 | private function updateCopyPaste(e:Event = null):void 840 | { 841 | _copyPaste.text = _synth.params.getSettingsString(); 842 | 843 | _copyPaste.setSelection(0, _copyPaste.text.length); 844 | stage.focus = _copyPaste; 845 | } 846 | 847 | /** 848 | * When the textfield is pasted into, and the new info parses, updates the settings 849 | * @param e Text input event 850 | */ 851 | private function updateFromCopyPaste(e:TextEvent):void 852 | { 853 | if (e.text.split(",").length == 24) addToHistory(); 854 | 855 | if (!_synth.params.setSettingsString(e.text)) 856 | { 857 | _copyPaste.setSelection(0, _copyPaste.text.length); 858 | stage.focus = _copyPaste; 859 | 860 | _copyPaste.text = _synth.params.getSettingsString(); 861 | } 862 | 863 | _copyPaste.setSelection(0, _copyPaste.text.length); 864 | stage.focus = _copyPaste; 865 | 866 | updateSliders(); 867 | updateButtons(); 868 | } 869 | 870 | //-------------------------------------------------------------------------- 871 | // 872 | // Graphics Methods 873 | // 874 | //-------------------------------------------------------------------------- 875 | 876 | /** 877 | * Draws the extra labels, frames and lines to the stage 878 | */ 879 | private function drawGraphics():void 880 | { 881 | var lines:Vector. = new Vector.(); 882 | lines.push(new GraphicsStroke(2, false, LineScaleMode.NORMAL, CapsStyle.NONE, JointStyle.MITER, 3, new GraphicsSolidFill(0))); 883 | lines.push(new GraphicsPath(Vector.([1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,2]), 884 | Vector.([ 114,0, 114,500, 885 | 160,66, 460,66, 886 | 160,138, 460,138, 887 | 160,246, 460,246, 888 | 160,282, 460,282, 889 | 160,318, 460,318, 890 | 160,336, 460,336, 891 | 160,372, 460,372, 892 | 160, 462, 460, 462, 893 | 160, 480, 460, 480, 894 | 590,255, 618,255, 618,411, 590,411]))); 895 | lines.push(new GraphicsStroke(1, false, LineScaleMode.NORMAL, CapsStyle.NONE, JointStyle.MITER, 3, new GraphicsSolidFill(0))); 896 | lines.push(new GraphicsPath(Vector.([1,2,1,2,1,2,1,2]), 897 | Vector.([ 160, 65, 160, 481, 898 | 460, 65, 460, 481]))); 899 | 900 | graphics.drawGraphicsData(lines); 901 | 902 | graphics.lineStyle(2, 0xFF0000, 1, true, LineScaleMode.NORMAL, CapsStyle.SQUARE, JointStyle.MITER); 903 | graphics.drawRect(549.5, 250.5, 43, 10); 904 | 905 | addLabel("CLICK ON LABELS", 484, 118, 0x877569, 500); 906 | addLabel("TO RESET SLIDERS", 480, 132, 0x877569, 500); 907 | 908 | addLabel("COPY/PASTE SETTINGS", 470, 158, 0x877569, 500); 909 | addLabel("TO SHARE SOUNDS", 484, 172, 0x877569, 500); 910 | 911 | addLabel("BASED ON SFXR BY", 480, 198, 0x877569, 500); 912 | addLabel("TOMAS PETTERSSON", 480, 212, 0x877569, 500); 913 | 914 | addLabel("VOLUME", 516, 235, 0); 915 | 916 | addLabel("GENERATOR", 6, 8, 0x504030); 917 | addLabel("MANUAL SETTINGS", 122, 8, 0x504030); 918 | 919 | var as3sfxrLogo:DisplayObject = new As3sfxrLogo(); 920 | as3sfxrLogo.x = 476; 921 | as3sfxrLogo.y = 62; 922 | addChild(as3sfxrLogo); 923 | 924 | var sfbtomLogo:DisplayObject = new SfbtomLogo(); 925 | sfbtomLogo.x = 4; 926 | sfbtomLogo.y = 459; 927 | addChild(sfbtomLogo); 928 | 929 | _logoRect = sfbtomLogo.getBounds(stage); 930 | _sfxrRect = new Rectangle(480, 195, 100, 30); 931 | _volumeRect = new Rectangle(516, 235, 200, 15); 932 | 933 | stage.addEventListener(MouseEvent.MOUSE_DOWN, onClick); 934 | } 935 | 936 | /** 937 | * Handles clicking either 938 | * @param e Click event 939 | */ 940 | private function onClick(e:MouseEvent):void 941 | { 942 | if (_logoRect.contains(stage.mouseX, stage.mouseY)) navigateToURL(new URLRequest("http://twitter.com/SFBTom")); 943 | if (_sfxrRect.contains(stage.mouseX, stage.mouseY)) navigateToURL(new URLRequest("http://www.drpetter.se/project_sfxr.html")); 944 | if (_volumeRect.contains(stage.mouseX, stage.mouseY)) _sliderLookup["masterVolume"].value = 0.5; 945 | } 946 | 947 | /** 948 | * Adds a label 949 | * @param label Text to display 950 | * @param x X position of the label 951 | * @param y Y position of the label 952 | * @param colour Colour of the text 953 | */ 954 | private function addLabel(label:String, x:Number, y:Number, colour:uint, width:Number = 200):void 955 | { 956 | var txt:TextField = new TextField(); 957 | txt.defaultTextFormat = new TextFormat("Amiga4Ever", 8, colour); 958 | txt.selectable = false; 959 | txt.embedFonts = true; 960 | txt.text = label; 961 | txt.width = width; 962 | txt.height = 15; 963 | txt.x = x; 964 | txt.y = y; 965 | addChild(txt); 966 | } 967 | } 968 | } -------------------------------------------------------------------------------- /src/SfxrParams.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | /** 4 | * SfxrParams 5 | * 6 | * Copyright 2010 Thomas Vian 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | * @author Thomas Vian 21 | */ 22 | public class SfxrParams 23 | { 24 | //-------------------------------------------------------------------------- 25 | // 26 | // Properties 27 | // 28 | //-------------------------------------------------------------------------- 29 | 30 | /** If the parameters have been changed since last time (shouldn't used cached sound) */ 31 | public var paramsDirty:Boolean; 32 | 33 | private var _waveType :uint = 0; // Shape of the wave (0:square, 1:saw, 2:sin or 3:noise) 34 | 35 | private var _masterVolume :Number = 0.5; // Overall volume of the sound (0 to 1) 36 | 37 | private var _attackTime :Number = 0.0; // Length of the volume envelope attack (0 to 1) 38 | private var _sustainTime :Number = 0.0; // Length of the volume envelope sustain (0 to 1) 39 | private var _sustainPunch :Number = 0.0; // Tilts the sustain envelope for more 'pop' (0 to 1) 40 | private var _decayTime :Number = 0.0; // Length of the volume envelope decay (yes, I know it's called release) (0 to 1) 41 | 42 | private var _startFrequency :Number = 0.0; // Base note of the sound (0 to 1) 43 | private var _minFrequency :Number = 0.0; // If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1) 44 | 45 | private var _slide :Number = 0.0; // Slides the note up or down (-1 to 1) 46 | private var _deltaSlide :Number = 0.0; // Accelerates the slide (-1 to 1) 47 | 48 | private var _vibratoDepth :Number = 0.0; // Strength of the vibrato effect (0 to 1) 49 | private var _vibratoSpeed :Number = 0.0; // Speed of the vibrato effect (i.e. frequency) (0 to 1) 50 | 51 | private var _changeAmount :Number = 0.0; // Shift in note, either up or down (-1 to 1) 52 | private var _changeSpeed :Number = 0.0; // How fast the note shift happens (only happens once) (0 to 1) 53 | 54 | private var _squareDuty :Number = 0.0; // Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1) 55 | private var _dutySweep :Number = 0.0; // Sweeps the duty up or down (-1 to 1) 56 | 57 | private var _repeatSpeed :Number = 0.0; // Speed of the note repeating - certain variables are reset each time (0 to 1) 58 | 59 | private var _phaserOffset :Number = 0.0; // Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1) 60 | private var _phaserSweep :Number = 0.0; // Sweeps the phase up or down (-1 to 1) 61 | 62 | private var _lpFilterCutoff :Number = 0.0; // Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1) 63 | private var _lpFilterCutoffSweep:Number = 0.0; // Sweeps the low-pass cutoff up or down (-1 to 1) 64 | private var _lpFilterResonance :Number = 0.0; // Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1) 65 | 66 | private var _hpFilterCutoff :Number = 0.0; // Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1) 67 | private var _hpFilterCutoffSweep:Number = 0.0; // Sweeps the high-pass cutoff up or down (-1 to 1) 68 | 69 | //-------------------------------------------------------------------------- 70 | // 71 | // Getters / Setters 72 | // 73 | //-------------------------------------------------------------------------- 74 | 75 | /** Shape of the wave (0:square, 1:saw, 2:sin or 3:noise) */ 76 | public function get waveType():uint { return _waveType; } 77 | public function set waveType(value:uint):void { _waveType = value > 3 ? 0 : value; paramsDirty = true; } 78 | 79 | /** Overall volume of the sound (0 to 1) */ 80 | public function get masterVolume():Number { return _masterVolume; } 81 | public function set masterVolume(value:Number):void { _masterVolume = clamp1(value); paramsDirty = true; } 82 | 83 | /** Length of the volume envelope attack (0 to 1) */ 84 | public function get attackTime():Number { return _attackTime; } 85 | public function set attackTime(value:Number):void { _attackTime = clamp1(value); paramsDirty = true; } 86 | 87 | /** Length of the volume envelope sustain (0 to 1) */ 88 | public function get sustainTime():Number { return _sustainTime; } 89 | public function set sustainTime(value:Number):void { _sustainTime = clamp1(value); paramsDirty = true; } 90 | 91 | /** Tilts the sustain envelope for more 'pop' (0 to 1) */ 92 | public function get sustainPunch():Number { return _sustainPunch; } 93 | public function set sustainPunch(value:Number):void { _sustainPunch = clamp1(value); paramsDirty = true; } 94 | 95 | /** Length of the volume envelope decay (yes, I know it's called release) (0 to 1) */ 96 | public function get decayTime():Number { return _decayTime; } 97 | public function set decayTime(value:Number):void { _decayTime = clamp1(value); paramsDirty = true; } 98 | 99 | /** Base note of the sound (0 to 1) */ 100 | public function get startFrequency():Number { return _startFrequency; } 101 | public function set startFrequency(value:Number):void { _startFrequency = clamp1(value); paramsDirty = true; } 102 | 103 | /** If sliding, the sound will stop at this frequency, to prevent really low notes (0 to 1) */ 104 | public function get minFrequency():Number { return _minFrequency; } 105 | public function set minFrequency(value:Number):void { _minFrequency = clamp1(value); paramsDirty = true; } 106 | 107 | /** Slides the note up or down (-1 to 1) */ 108 | public function get slide():Number { return _slide; } 109 | public function set slide(value:Number):void { _slide = clamp2(value); paramsDirty = true; } 110 | 111 | /** Accelerates the slide (-1 to 1) */ 112 | public function get deltaSlide():Number { return _deltaSlide; } 113 | public function set deltaSlide(value:Number):void { _deltaSlide = clamp2(value); paramsDirty = true; } 114 | 115 | /** Strength of the vibrato effect (0 to 1) */ 116 | public function get vibratoDepth():Number { return _vibratoDepth; } 117 | public function set vibratoDepth(value:Number):void { _vibratoDepth = clamp1(value); paramsDirty = true; } 118 | 119 | /** Speed of the vibrato effect (i.e. frequency) (0 to 1) */ 120 | public function get vibratoSpeed():Number { return _vibratoSpeed; } 121 | public function set vibratoSpeed(value:Number):void { _vibratoSpeed = clamp1(value); paramsDirty = true; } 122 | 123 | /** Shift in note, either up or down (-1 to 1) */ 124 | public function get changeAmount():Number { return _changeAmount; } 125 | public function set changeAmount(value:Number):void { _changeAmount = clamp2(value); paramsDirty = true; } 126 | 127 | /** How fast the note shift happens (only happens once) (0 to 1) */ 128 | public function get changeSpeed():Number { return _changeSpeed; } 129 | public function set changeSpeed(value:Number):void { _changeSpeed = clamp1(value); paramsDirty = true; } 130 | 131 | /** Controls the ratio between the up and down states of the square wave, changing the tibre (0 to 1) */ 132 | public function get squareDuty():Number { return _squareDuty; } 133 | public function set squareDuty(value:Number):void { _squareDuty = clamp1(value); paramsDirty = true; } 134 | 135 | /** Sweeps the duty up or down (-1 to 1) */ 136 | public function get dutySweep():Number { return _dutySweep; } 137 | public function set dutySweep(value:Number):void { _dutySweep = clamp2(value); paramsDirty = true; } 138 | 139 | /** Speed of the note repeating - certain variables are reset each time (0 to 1) */ 140 | public function get repeatSpeed():Number { return _repeatSpeed; } 141 | public function set repeatSpeed(value:Number):void { _repeatSpeed = clamp1(value); paramsDirty = true; } 142 | 143 | /** Offsets a second copy of the wave by a small phase, changing the tibre (-1 to 1) */ 144 | public function get phaserOffset():Number { return _phaserOffset; } 145 | public function set phaserOffset(value:Number):void { _phaserOffset = clamp2(value); paramsDirty = true; } 146 | 147 | /** Sweeps the phase up or down (-1 to 1) */ 148 | public function get phaserSweep():Number { return _phaserSweep; } 149 | public function set phaserSweep(value:Number):void { _phaserSweep = clamp2(value); paramsDirty = true; } 150 | 151 | /** Frequency at which the low-pass filter starts attenuating higher frequencies (0 to 1) */ 152 | public function get lpFilterCutoff():Number { return _lpFilterCutoff; } 153 | public function set lpFilterCutoff(value:Number):void { _lpFilterCutoff = clamp1(value); paramsDirty = true; } 154 | 155 | /** Sweeps the low-pass cutoff up or down (-1 to 1) */ 156 | public function get lpFilterCutoffSweep():Number { return _lpFilterCutoffSweep; } 157 | public function set lpFilterCutoffSweep(value:Number):void { _lpFilterCutoffSweep = clamp2(value); paramsDirty = true; } 158 | 159 | /** Changes the attenuation rate for the low-pass filter, changing the timbre (0 to 1) */ 160 | public function get lpFilterResonance():Number { return _lpFilterResonance; } 161 | public function set lpFilterResonance(value:Number):void { _lpFilterResonance = clamp1(value); paramsDirty = true; } 162 | 163 | /** Frequency at which the high-pass filter starts attenuating lower frequencies (0 to 1) */ 164 | public function get hpFilterCutoff():Number { return _hpFilterCutoff; } 165 | public function set hpFilterCutoff(value:Number):void { _hpFilterCutoff = clamp1(value); paramsDirty = true; } 166 | 167 | /** Sweeps the high-pass cutoff up or down (-1 to 1) */ 168 | public function get hpFilterCutoffSweep():Number { return _hpFilterCutoffSweep; } 169 | public function set hpFilterCutoffSweep(value:Number):void { _hpFilterCutoffSweep = clamp2(value); paramsDirty = true; } 170 | 171 | //-------------------------------------------------------------------------- 172 | // 173 | // Generator Methods 174 | // 175 | //-------------------------------------------------------------------------- 176 | 177 | /** 178 | * Sets the parameters to generate a pickup/coin sound 179 | */ 180 | public function generatePickupCoin():void 181 | { 182 | resetParams(); 183 | 184 | _startFrequency = 0.4 + Math.random() * 0.5; 185 | 186 | _sustainTime = Math.random() * 0.1; 187 | _decayTime = 0.1 + Math.random() * 0.4; 188 | _sustainPunch = 0.3 + Math.random() * 0.3; 189 | 190 | if(Math.random() < 0.5) 191 | { 192 | _changeSpeed = 0.5 + Math.random() * 0.2; 193 | _changeAmount = 0.2 + Math.random() * 0.4; 194 | } 195 | } 196 | 197 | /** 198 | * Sets the parameters to generate a laser/shoot sound 199 | */ 200 | public function generateLaserShoot():void 201 | { 202 | resetParams(); 203 | 204 | _waveType = uint(Math.random() * 3); 205 | if(_waveType == 2 && Math.random() < 0.5) _waveType = uint(Math.random() * 2); 206 | 207 | _startFrequency = 0.5 + Math.random() * 0.5; 208 | _minFrequency = _startFrequency - 0.2 - Math.random() * 0.6; 209 | if(_minFrequency < 0.2) _minFrequency = 0.2; 210 | 211 | _slide = -0.15 - Math.random() * 0.2; 212 | 213 | if(Math.random() < 0.33) 214 | { 215 | _startFrequency = 0.3 + Math.random() * 0.6; 216 | _minFrequency = Math.random() * 0.1; 217 | _slide = -0.35 - Math.random() * 0.3; 218 | } 219 | 220 | if(Math.random() < 0.5) 221 | { 222 | _squareDuty = Math.random() * 0.5; 223 | _dutySweep = Math.random() * 0.2; 224 | } 225 | else 226 | { 227 | _squareDuty = 0.4 + Math.random() * 0.5; 228 | _dutySweep =- Math.random() * 0.7; 229 | } 230 | 231 | _sustainTime = 0.1 + Math.random() * 0.2; 232 | _decayTime = Math.random() * 0.4; 233 | if(Math.random() < 0.5) _sustainPunch = Math.random() * 0.3; 234 | 235 | if(Math.random() < 0.33) 236 | { 237 | _phaserOffset = Math.random() * 0.2; 238 | _phaserSweep = -Math.random() * 0.2; 239 | } 240 | 241 | if(Math.random() < 0.5) _hpFilterCutoff = Math.random() * 0.3; 242 | } 243 | 244 | /** 245 | * Sets the parameters to generate an explosion sound 246 | */ 247 | public function generateExplosion():void 248 | { 249 | resetParams(); 250 | _waveType = 3; 251 | 252 | if(Math.random() < 0.5) 253 | { 254 | _startFrequency = 0.1 + Math.random() * 0.4; 255 | _slide = -0.1 + Math.random() * 0.4; 256 | } 257 | else 258 | { 259 | _startFrequency = 0.2 + Math.random() * 0.7; 260 | _slide = -0.2 - Math.random() * 0.2; 261 | } 262 | 263 | _startFrequency *= _startFrequency; 264 | 265 | if(Math.random() < 0.2) _slide = 0.0; 266 | if(Math.random() < 0.33) _repeatSpeed = 0.3 + Math.random() * 0.5; 267 | 268 | _sustainTime = 0.1 + Math.random() * 0.3; 269 | _decayTime = Math.random() * 0.5; 270 | _sustainPunch = 0.2 + Math.random() * 0.6; 271 | 272 | if(Math.random() < 0.5) 273 | { 274 | _phaserOffset = -0.3 + Math.random() * 0.9; 275 | _phaserSweep = -Math.random() * 0.3; 276 | } 277 | 278 | if(Math.random() < 0.33) 279 | { 280 | _changeSpeed = 0.6 + Math.random() * 0.3; 281 | _changeAmount = 0.8 - Math.random() * 1.6; 282 | } 283 | } 284 | 285 | /** 286 | * Sets the parameters to generate a powerup sound 287 | */ 288 | public function generatePowerup():void 289 | { 290 | resetParams(); 291 | 292 | if(Math.random() < 0.5) _waveType = 1; 293 | else _squareDuty = Math.random() * 0.6; 294 | 295 | if(Math.random() < 0.5) 296 | { 297 | _startFrequency = 0.2 + Math.random() * 0.3; 298 | _slide = 0.1 + Math.random() * 0.4; 299 | _repeatSpeed = 0.4 + Math.random() * 0.4; 300 | } 301 | else 302 | { 303 | _startFrequency = 0.2 + Math.random() * 0.3; 304 | _slide = 0.05 + Math.random() * 0.2; 305 | 306 | if(Math.random() < 0.5) 307 | { 308 | _vibratoDepth = Math.random() * 0.7; 309 | _vibratoSpeed = Math.random() * 0.6; 310 | } 311 | } 312 | 313 | _sustainTime = Math.random() * 0.4; 314 | _decayTime = 0.1 + Math.random() * 0.4; 315 | } 316 | 317 | /** 318 | * Sets the parameters to generate a hit/hurt sound 319 | */ 320 | public function generateHitHurt():void 321 | { 322 | resetParams(); 323 | 324 | _waveType = uint(Math.random() * 3); 325 | if(_waveType == 2) _waveType = 3; 326 | else if(_waveType == 0) _squareDuty = Math.random() * 0.6; 327 | 328 | _startFrequency = 0.2 + Math.random() * 0.6; 329 | _slide = -0.3 - Math.random() * 0.4; 330 | 331 | _sustainTime = Math.random() * 0.1; 332 | _decayTime = 0.1 + Math.random() * 0.2; 333 | 334 | if(Math.random() < 0.5) _hpFilterCutoff = Math.random() * 0.3; 335 | } 336 | 337 | /** 338 | * Sets the parameters to generate a jump sound 339 | */ 340 | public function generateJump():void 341 | { 342 | resetParams(); 343 | 344 | _waveType = 0; 345 | _squareDuty = Math.random() * 0.6; 346 | _startFrequency = 0.3 + Math.random() * 0.3; 347 | _slide = 0.1 + Math.random() * 0.2; 348 | 349 | _sustainTime = 0.1 + Math.random() * 0.3; 350 | _decayTime = 0.1 + Math.random() * 0.2; 351 | 352 | if(Math.random() < 0.5) _hpFilterCutoff = Math.random() * 0.3; 353 | if(Math.random() < 0.5) _lpFilterCutoff = 1.0 - Math.random() * 0.6; 354 | } 355 | 356 | /** 357 | * Sets the parameters to generate a blip/select sound 358 | */ 359 | public function generateBlipSelect():void 360 | { 361 | resetParams(); 362 | 363 | _waveType = uint(Math.random() * 2); 364 | if(_waveType == 0) _squareDuty = Math.random() * 0.6; 365 | 366 | _startFrequency = 0.2 + Math.random() * 0.4; 367 | 368 | _sustainTime = 0.1 + Math.random() * 0.1; 369 | _decayTime = Math.random() * 0.2; 370 | _hpFilterCutoff = 0.1; 371 | } 372 | 373 | /** 374 | * Resets the parameters, used at the start of each generate function 375 | */ 376 | protected function resetParams():void 377 | { 378 | paramsDirty = true; 379 | 380 | _waveType = 0; 381 | _startFrequency = 0.3; 382 | _minFrequency = 0.0; 383 | _slide = 0.0; 384 | _deltaSlide = 0.0; 385 | _squareDuty = 0.0; 386 | _dutySweep = 0.0; 387 | 388 | _vibratoDepth = 0.0; 389 | _vibratoSpeed = 0.0; 390 | 391 | _attackTime = 0.0; 392 | _sustainTime = 0.3; 393 | _decayTime = 0.4; 394 | _sustainPunch = 0.0; 395 | 396 | _lpFilterResonance = 0.0; 397 | _lpFilterCutoff = 1.0; 398 | _lpFilterCutoffSweep = 0.0; 399 | _hpFilterCutoff = 0.0; 400 | _hpFilterCutoffSweep = 0.0; 401 | 402 | _phaserOffset = 0.0; 403 | _phaserSweep = 0.0; 404 | 405 | _repeatSpeed = 0.0; 406 | 407 | _changeSpeed = 0.0; 408 | _changeAmount = 0.0; 409 | } 410 | 411 | //-------------------------------------------------------------------------- 412 | // 413 | // Randomize Methods 414 | // 415 | //-------------------------------------------------------------------------- 416 | 417 | /** 418 | * Randomly adjusts the parameters ever so slightly 419 | */ 420 | public function mutate(mutation:Number = 0.05):void 421 | { 422 | if (Math.random() < 0.5) startFrequency += Math.random() * mutation*2 - mutation; 423 | if (Math.random() < 0.5) minFrequency += Math.random() * mutation*2 - mutation; 424 | if (Math.random() < 0.5) slide += Math.random() * mutation*2 - mutation; 425 | if (Math.random() < 0.5) deltaSlide += Math.random() * mutation*2 - mutation; 426 | if (Math.random() < 0.5) squareDuty += Math.random() * mutation*2 - mutation; 427 | if (Math.random() < 0.5) dutySweep += Math.random() * mutation*2 - mutation; 428 | if (Math.random() < 0.5) vibratoDepth += Math.random() * mutation*2 - mutation; 429 | if (Math.random() < 0.5) vibratoSpeed += Math.random() * mutation*2 - mutation; 430 | if (Math.random() < 0.5) attackTime += Math.random() * mutation*2 - mutation; 431 | if (Math.random() < 0.5) sustainTime += Math.random() * mutation*2 - mutation; 432 | if (Math.random() < 0.5) decayTime += Math.random() * mutation*2 - mutation; 433 | if (Math.random() < 0.5) sustainPunch += Math.random() * mutation*2 - mutation; 434 | if (Math.random() < 0.5) lpFilterCutoff += Math.random() * mutation*2 - mutation; 435 | if (Math.random() < 0.5) lpFilterCutoffSweep += Math.random() * mutation*2 - mutation; 436 | if (Math.random() < 0.5) lpFilterResonance += Math.random() * mutation*2 - mutation; 437 | if (Math.random() < 0.5) hpFilterCutoff += Math.random() * mutation*2 - mutation; 438 | if (Math.random() < 0.5) hpFilterCutoffSweep += Math.random() * mutation*2 - mutation; 439 | if (Math.random() < 0.5) phaserOffset += Math.random() * mutation*2 - mutation; 440 | if (Math.random() < 0.5) phaserSweep += Math.random() * mutation*2 - mutation; 441 | if (Math.random() < 0.5) repeatSpeed += Math.random() * mutation*2 - mutation; 442 | if (Math.random() < 0.5) changeSpeed += Math.random() * mutation*2 - mutation; 443 | if (Math.random() < 0.5) changeAmount += Math.random() * mutation*2 - mutation; 444 | } 445 | 446 | /** 447 | * Sets all parameters to random values 448 | */ 449 | public function randomize():void 450 | { 451 | paramsDirty = true; 452 | 453 | _waveType = uint(Math.random() * 4); 454 | 455 | _attackTime = pow(Math.random()*2-1, 4); 456 | _sustainTime = pow(Math.random()*2-1, 2); 457 | _sustainPunch = pow(Math.random()*0.8, 2); 458 | _decayTime = Math.random(); 459 | 460 | _startFrequency = (Math.random() < 0.5) ? pow(Math.random()*2-1, 2) : (pow(Math.random() * 0.5, 3) + 0.5); 461 | _minFrequency = 0.0; 462 | 463 | _slide = pow(Math.random()*2-1, 5); 464 | _deltaSlide = pow(Math.random()*2-1, 3); 465 | 466 | _vibratoDepth = pow(Math.random()*2-1, 3); 467 | _vibratoSpeed = Math.random()*2-1; 468 | 469 | _changeAmount = Math.random()*2-1; 470 | _changeSpeed = Math.random()*2-1; 471 | 472 | _squareDuty = Math.random()*2-1; 473 | _dutySweep = pow(Math.random()*2-1, 3); 474 | 475 | _repeatSpeed = Math.random()*2-1; 476 | 477 | _phaserOffset = pow(Math.random()*2-1, 3); 478 | _phaserSweep = pow(Math.random()*2-1, 3); 479 | 480 | _lpFilterCutoff = 1 - pow(Math.random(), 3); 481 | _lpFilterCutoffSweep = pow(Math.random()*2-1, 3); 482 | _lpFilterResonance = Math.random()*2-1; 483 | 484 | _hpFilterCutoff = pow(Math.random(), 5); 485 | _hpFilterCutoffSweep = pow(Math.random()*2-1, 5); 486 | 487 | if(_attackTime + _sustainTime + _decayTime < 0.2) 488 | { 489 | _sustainTime = 0.2 + Math.random() * 0.3; 490 | _decayTime = 0.2 + Math.random() * 0.3; 491 | } 492 | 493 | if((_startFrequency > 0.7 && _slide > 0.2) || (_startFrequency < 0.2 && _slide < -0.05)) 494 | { 495 | _slide = -_slide; 496 | } 497 | 498 | if(_lpFilterCutoff < 0.1 && _lpFilterCutoffSweep < -0.05) 499 | { 500 | _lpFilterCutoffSweep = -_lpFilterCutoffSweep; 501 | } 502 | } 503 | 504 | //-------------------------------------------------------------------------- 505 | // 506 | // Settings String Methods 507 | // 508 | //-------------------------------------------------------------------------- 509 | 510 | /** 511 | * Returns a string representation of the parameters for copy/paste sharing 512 | * @return A comma-delimited list of parameter values 513 | */ 514 | public function getSettingsString():String 515 | { 516 | var string:String = String(waveType); 517 | string += "," + to4DP(_attackTime) + "," + to4DP(_sustainTime) 518 | + "," + to4DP(_sustainPunch) + "," + to4DP(_decayTime) 519 | + "," + to4DP(_startFrequency) + "," + to4DP(_minFrequency) 520 | + "," + to4DP(_slide) + "," + to4DP(_deltaSlide) 521 | + "," + to4DP(_vibratoDepth) + "," + to4DP(_vibratoSpeed) 522 | + "," + to4DP(_changeAmount) + "," + to4DP(_changeSpeed) 523 | + "," + to4DP(_squareDuty) + "," + to4DP(_dutySweep) 524 | + "," + to4DP(_repeatSpeed) + "," + to4DP(_phaserOffset) 525 | + "," + to4DP(_phaserSweep) + "," + to4DP(_lpFilterCutoff) 526 | + "," + to4DP(_lpFilterCutoffSweep) + "," + to4DP(_lpFilterResonance) 527 | + "," + to4DP(_hpFilterCutoff)+ "," + to4DP(_hpFilterCutoffSweep) 528 | + "," + to4DP(_masterVolume); 529 | 530 | return string; 531 | } 532 | 533 | /** 534 | * Parses a settings string into the parameters 535 | * @param string Settings string to parse 536 | * @return If the string successfully parsed 537 | */ 538 | public function setSettingsString(string:String):Boolean 539 | { 540 | var values:Array = string.split(","); 541 | 542 | if (values.length != 24) return false; 543 | 544 | waveType = uint(values[0]) || 0; 545 | attackTime = Number(values[1]) || 0; 546 | sustainTime = Number(values[2]) || 0; 547 | sustainPunch = Number(values[3]) || 0; 548 | decayTime = Number(values[4]) || 0; 549 | startFrequency = Number(values[5]) || 0; 550 | minFrequency = Number(values[6]) || 0; 551 | slide = Number(values[7]) || 0; 552 | deltaSlide = Number(values[8]) || 0; 553 | vibratoDepth = Number(values[9]) || 0; 554 | vibratoSpeed = Number(values[10]) || 0; 555 | changeAmount = Number(values[11]) || 0; 556 | changeSpeed = Number(values[12]) || 0; 557 | squareDuty = Number(values[13]) || 0; 558 | dutySweep = Number(values[14]) || 0; 559 | repeatSpeed = Number(values[15]) || 0; 560 | phaserOffset = Number(values[16]) || 0; 561 | phaserSweep = Number(values[17]) || 0; 562 | lpFilterCutoff = Number(values[18]) || 0; 563 | lpFilterCutoffSweep = Number(values[19]) || 0; 564 | lpFilterResonance = Number(values[20]) || 0; 565 | hpFilterCutoff = Number(values[21]) || 0; 566 | hpFilterCutoffSweep = Number(values[22]) || 0; 567 | masterVolume = Number(values[23]) || 0; 568 | 569 | return true; 570 | } 571 | 572 | 573 | //-------------------------------------------------------------------------- 574 | // 575 | // Copying Methods 576 | // 577 | //-------------------------------------------------------------------------- 578 | 579 | /** 580 | * Returns a copy of this SfxrParams with all settings duplicated 581 | * @return A copy of this SfxrParams 582 | */ 583 | public function clone():SfxrParams 584 | { 585 | var out:SfxrParams = new SfxrParams(); 586 | out.copyFrom(this); 587 | 588 | return out; 589 | } 590 | 591 | /** 592 | * Copies parameters from another instance 593 | * @param params Instance to copy parameters from 594 | */ 595 | public function copyFrom(params:SfxrParams, makeDirty:Boolean = false):void 596 | { 597 | _waveType = params.waveType; 598 | _attackTime = params.attackTime; 599 | _sustainTime = params.sustainTime; 600 | _sustainPunch = params.sustainPunch; 601 | _decayTime = params.decayTime; 602 | _startFrequency = params.startFrequency; 603 | _minFrequency = params.minFrequency; 604 | _slide = params.slide; 605 | _deltaSlide = params.deltaSlide; 606 | _vibratoDepth = params.vibratoDepth; 607 | _vibratoSpeed = params.vibratoSpeed; 608 | _changeAmount = params.changeAmount; 609 | _changeSpeed = params.changeSpeed; 610 | _squareDuty = params.squareDuty; 611 | _dutySweep = params.dutySweep; 612 | _repeatSpeed = params.repeatSpeed; 613 | _phaserOffset = params.phaserOffset; 614 | _phaserSweep = params.phaserSweep; 615 | _lpFilterCutoff = params.lpFilterCutoff; 616 | _lpFilterCutoffSweep = params.lpFilterCutoffSweep; 617 | _lpFilterResonance = params.lpFilterResonance; 618 | _hpFilterCutoff = params.hpFilterCutoff; 619 | _hpFilterCutoffSweep = params.hpFilterCutoffSweep; 620 | _masterVolume = params.masterVolume; 621 | 622 | if (makeDirty) paramsDirty = true; 623 | } 624 | 625 | 626 | //-------------------------------------------------------------------------- 627 | // 628 | // Util Methods 629 | // 630 | //-------------------------------------------------------------------------- 631 | 632 | /** 633 | * Clams a value to betwen 0 and 1 634 | * @param value Input value 635 | * @return The value clamped between 0 and 1 636 | */ 637 | private function clamp1(value:Number):Number { return (value > 1.0) ? 1.0 : ((value < 0.0) ? 0.0 : value); } 638 | 639 | /** 640 | * Clams a value to betwen -1 and 1 641 | * @param value Input value 642 | * @return The value clamped between -1 and 1 643 | */ 644 | private function clamp2(value:Number):Number { return (value > 1.0) ? 1.0 : ((value < -1.0) ? -1.0 : value); } 645 | 646 | /** 647 | * Quick power function 648 | * @param base Base to raise to power 649 | * @param power Power to raise base by 650 | * @return The calculated power 651 | */ 652 | private function pow(base:Number, power:int):Number 653 | { 654 | switch(power) 655 | { 656 | case 2: return base*base; 657 | case 3: return base*base*base; 658 | case 4: return base*base*base*base; 659 | case 5: return base*base*base*base*base; 660 | } 661 | 662 | return 1.0; 663 | } 664 | 665 | 666 | /** 667 | * Returns the number as a string to 4 decimal places 668 | * @param value Number to convert 669 | * @return Number to 4dp as a string 670 | */ 671 | private function to4DP(value:Number):String 672 | { 673 | if (value < 0.0001 && value > -0.0001) return ""; 674 | 675 | var string:String = String(value); 676 | var split:Array = string.split("."); 677 | if (split.length == 1) 678 | { 679 | return string; 680 | } 681 | else 682 | { 683 | var out:String = split[0] + "." + split[1].substr(0, 4); 684 | while (out.substr(out.length - 1, 1) == "0") out = out.substr(0, out.length - 1); 685 | 686 | return out; 687 | } 688 | } 689 | } 690 | } -------------------------------------------------------------------------------- /src/SfxrSynth.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Shape; 4 | import flash.events.Event; 5 | import flash.events.SampleDataEvent; 6 | import flash.media.Sound; 7 | import flash.media.SoundChannel; 8 | import flash.utils.ByteArray; 9 | import flash.utils.Endian; 10 | import flash.utils.getTimer; 11 | /** 12 | * SfxrSynth 13 | * 14 | * Copyright 2010 Thomas Vian 15 | * 16 | * Licensed under the Apache License, Version 2.0 (the "License"); 17 | * you may not use this file except in compliance with the License. 18 | * You may obtain a copy of the License at 19 | * 20 | * http://www.apache.org/licenses/LICENSE-2.0 21 | * 22 | * Unless required by applicable law or agreed to in writing, software 23 | * distributed under the License is distributed on an "AS IS" BASIS, 24 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | * See the License for the specific language governing permissions and 26 | * limitations under the License. 27 | * 28 | * @author Thomas Vian 29 | */ 30 | public class SfxrSynth 31 | { 32 | //-------------------------------------------------------------------------- 33 | // 34 | // Sound Parameters 35 | // 36 | //-------------------------------------------------------------------------- 37 | 38 | private var _params:SfxrParams = new SfxrParams; // Params instance 39 | 40 | private var _sound:Sound; // Sound instance used to play the sound 41 | private var _channel:SoundChannel; // SoundChannel instance of playing Sound 42 | 43 | private var _mutation:Boolean; // If the current sound playing or caching is a mutation 44 | 45 | private var _cachedWave:ByteArray; // Cached wave data from a cacheSound() call 46 | private var _cachingNormal:Boolean; // If the synth is caching a normal sound 47 | 48 | private var _cachingMutation:int; // Current caching ID 49 | private var _cachedMutation:ByteArray; // Current caching wave data for mutation 50 | private var _cachedMutations:Vector.; // Cached mutated wave data 51 | private var _cachedMutationsNum:uint; // Number of cached mutations 52 | private var _cachedMutationAmount:Number; // Amount to mutate during cache 53 | 54 | private var _cachingAsync:Boolean; // If the synth is currently caching asynchronously 55 | private var _cacheTimePerFrame:uint; // Maximum time allowed per frame to cache sound asynchronously 56 | private var _cachedCallback:Function; // Function to call when finished caching asynchronously 57 | private var _cacheTicker:Shape; // Shape used for enterFrame event 58 | 59 | private var _waveData:ByteArray; // Full wave, read out in chuncks by the onSampleData method 60 | private var _waveDataPos:uint; // Current position in the waveData 61 | private var _waveDataLength:uint; // Number of bytes in the waveData 62 | private var _waveDataBytes:uint; // Number of bytes to write to the soundcard 63 | 64 | private var _original:SfxrParams; // Copied properties for mutation base 65 | 66 | //-------------------------------------------------------------------------- 67 | // 68 | // Synth Variables 69 | // 70 | //-------------------------------------------------------------------------- 71 | 72 | private var _finished:Boolean; // If the sound has finished 73 | 74 | private var _masterVolume:Number; // masterVolume * masterVolume (for quick calculations) 75 | 76 | private var _waveType:uint; // The type of wave to generate 77 | 78 | private var _envelopeVolume:Number; // Current volume of the envelope 79 | private var _envelopeStage:int; // Current stage of the envelope (attack, sustain, decay, end) 80 | private var _envelopeTime:Number; // Current time through current enelope stage 81 | private var _envelopeLength:Number; // Length of the current envelope stage 82 | private var _envelopeLength0:Number; // Length of the attack stage 83 | private var _envelopeLength1:Number; // Length of the sustain stage 84 | private var _envelopeLength2:Number; // Length of the decay stage 85 | private var _envelopeOverLength0:Number; // 1 / _envelopeLength0 (for quick calculations) 86 | private var _envelopeOverLength1:Number; // 1 / _envelopeLength1 (for quick calculations) 87 | private var _envelopeOverLength2:Number; // 1 / _envelopeLength2 (for quick calculations) 88 | private var _envelopeFullLength:Number; // Full length of the volume envelop (and therefore sound) 89 | 90 | private var _sustainPunch:Number; // The punch factor (louder at begining of sustain) 91 | 92 | private var _phase:int; // Phase through the wave 93 | private var _pos:Number; // Phase expresed as a Number from 0-1, used for fast sin approx 94 | private var _period:Number; // Period of the wave 95 | private var _periodTemp:Number; // Period modified by vibrato 96 | private var _maxPeriod:Number; // Maximum period before sound stops (from minFrequency) 97 | 98 | private var _slide:Number; // Note slide 99 | private var _deltaSlide:Number; // Change in slide 100 | private var _minFreqency:Number; // Minimum frequency before stopping 101 | 102 | private var _vibratoPhase:Number; // Phase through the vibrato sine wave 103 | private var _vibratoSpeed:Number; // Speed at which the vibrato phase moves 104 | private var _vibratoAmplitude:Number; // Amount to change the period of the wave by at the peak of the vibrato wave 105 | 106 | private var _changeAmount:Number; // Amount to change the note by 107 | private var _changeTime:int; // Counter for the note change 108 | private var _changeLimit:int; // Once the time reaches this limit, the note changes 109 | 110 | private var _squareDuty:Number; // Offset of center switching point in the square wave 111 | private var _dutySweep:Number; // Amount to change the duty by 112 | 113 | private var _repeatTime:int; // Counter for the repeats 114 | private var _repeatLimit:int; // Once the time reaches this limit, some of the variables are reset 115 | 116 | private var _phaser:Boolean; // If the phaser is active 117 | private var _phaserOffset:Number; // Phase offset for phaser effect 118 | private var _phaserDeltaOffset:Number; // Change in phase offset 119 | private var _phaserInt:int; // Integer phaser offset, for bit maths 120 | private var _phaserPos:int; // Position through the phaser buffer 121 | private var _phaserBuffer:Vector.; // Buffer of wave values used to create the out of phase second wave 122 | 123 | private var _filters:Boolean; // If the filters are active 124 | private var _lpFilterPos:Number; // Adjusted wave position after low-pass filter 125 | private var _lpFilterOldPos:Number; // Previous low-pass wave position 126 | private var _lpFilterDeltaPos:Number; // Change in low-pass wave position, as allowed by the cutoff and damping 127 | private var _lpFilterCutoff:Number; // Cutoff multiplier which adjusts the amount the wave position can move 128 | private var _lpFilterDeltaCutoff:Number; // Speed of the low-pass cutoff multiplier 129 | private var _lpFilterDamping:Number; // Damping muliplier which restricts how fast the wave position can move 130 | private var _lpFilterOn:Boolean; // If the low pass filter is active 131 | 132 | private var _hpFilterPos:Number; // Adjusted wave position after high-pass filter 133 | private var _hpFilterCutoff:Number; // Cutoff multiplier which adjusts the amount the wave position can move 134 | private var _hpFilterDeltaCutoff:Number; // Speed of the high-pass cutoff multiplier 135 | 136 | private var _noiseBuffer:Vector.; // Buffer of random values used to generate noise 137 | 138 | private var _superSample:Number; // Actual sample writen to the wave 139 | private var _sample:Number; // Sub-sample calculated 8 times per actual sample, averaged out to get the super sample 140 | private var _sampleCount:uint; // Number of samples added to the buffer sample 141 | private var _bufferSample:Number; // Another supersample used to create a 22050Hz wave 142 | 143 | //-------------------------------------------------------------------------- 144 | // 145 | // Getters / Setters 146 | // 147 | //-------------------------------------------------------------------------- 148 | 149 | /** The sound parameters */ 150 | public function get params():SfxrParams { return _params; } 151 | public function set params(value:SfxrParams):void 152 | { 153 | _params = value; 154 | _params.paramsDirty = true; 155 | } 156 | 157 | //-------------------------------------------------------------------------- 158 | // 159 | // Sound Methods 160 | // 161 | //-------------------------------------------------------------------------- 162 | 163 | /** 164 | * Plays the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later. 165 | * If they're not, plays from the cached sound. 166 | * Won't play if caching asynchronously. 167 | */ 168 | public function play():void 169 | { 170 | if (_cachingAsync) return; 171 | 172 | stop(); 173 | 174 | _mutation = false; 175 | 176 | if (_params.paramsDirty || _cachingNormal || !_cachedWave) 177 | { 178 | // Needs to cache new data 179 | _cachedWave = new ByteArray; 180 | _cachingNormal = true; 181 | _waveData = null; 182 | 183 | reset(true); 184 | } 185 | else 186 | { 187 | // Play from cached data 188 | _waveData = _cachedWave; 189 | _waveData.position = 0; 190 | _waveDataLength = _waveData.length; 191 | _waveDataBytes = 24576; 192 | _waveDataPos = 0; 193 | } 194 | 195 | if (!_sound) (_sound = new Sound()).addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData); 196 | 197 | _channel = _sound.play(); 198 | } 199 | 200 | /** 201 | * Plays a mutation of the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later. 202 | * If they're not, plays from the cached sound. 203 | * Won't play if caching asynchronously. 204 | * @param mutationAmount Amount of mutation 205 | * @param mutationsNum The number of mutations to cache before picking from them 206 | */ 207 | public function playMutated(mutationAmount:Number = 0.05, mutationsNum:uint = 15):void 208 | { 209 | stop(); 210 | 211 | if (_cachingAsync) return; 212 | 213 | _mutation = true; 214 | 215 | _cachedMutationsNum = mutationsNum; 216 | 217 | if (_params.paramsDirty || !_cachedMutations) 218 | { 219 | // New set of mutations 220 | _cachedMutations = new Vector.(); 221 | _cachingMutation = 0; 222 | } 223 | 224 | if (_cachingMutation != -1) 225 | { 226 | // Continuing caching new mutations 227 | _cachedMutation = new ByteArray; 228 | _cachedMutations[_cachingMutation] = _cachedMutation; 229 | _waveData = null; 230 | 231 | _original = _params.clone(); 232 | _params.mutate(mutationAmount); 233 | reset(true); 234 | } 235 | else 236 | { 237 | // Play from random cached mutation 238 | _waveData = _cachedMutations[uint(_cachedMutations.length * Math.random())]; 239 | _waveData.position = 0; 240 | _waveDataLength = _waveData.length; 241 | _waveDataBytes = 24576; 242 | _waveDataPos = 0; 243 | } 244 | 245 | if (!_sound) (_sound = new Sound()).addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData); 246 | 247 | _channel = _sound.play(); 248 | } 249 | 250 | /** 251 | * Stops the currently playing sound 252 | */ 253 | public function stop():void 254 | { 255 | if(_channel) 256 | { 257 | _channel.stop(); 258 | _channel = null; 259 | } 260 | 261 | if(_original) 262 | { 263 | _params.copyFrom(_original); 264 | _original = null; 265 | } 266 | } 267 | 268 | /** 269 | * If there is a cached sound to play, reads out of the data. 270 | * If there isn't, synthesises new chunch of data, caching it as it goes. 271 | * @param e SampleDataEvent to write data to 272 | */ 273 | private function onSampleData(e:SampleDataEvent):void 274 | { 275 | if(_waveData) 276 | { 277 | if(_waveDataPos + _waveDataBytes > _waveDataLength) _waveDataBytes = _waveDataLength - _waveDataPos; 278 | 279 | if(_waveDataBytes > 0) e.data.writeBytes(_waveData, _waveDataPos, _waveDataBytes); 280 | 281 | _waveDataPos += _waveDataBytes; 282 | } 283 | else 284 | { 285 | var length:uint; 286 | var i:uint, l:uint; 287 | 288 | if (_mutation) 289 | { 290 | if (_original) 291 | { 292 | _waveDataPos = _cachedMutation.position; 293 | 294 | if (synthWave(_cachedMutation, 3072, true)) 295 | { 296 | _params.copyFrom(_original); 297 | _original = null; 298 | 299 | _cachingMutation++; 300 | 301 | if ((length = _cachedMutation.length) < 24576) 302 | { 303 | // If the sound is smaller than the buffer length, add silence to allow it to play 304 | _cachedMutation.position = length; 305 | for(i = 0, l = 24576 - length; i < l; i++) _cachedMutation.writeFloat(0.0); 306 | } 307 | 308 | if (_cachingMutation >= _cachedMutationsNum) 309 | { 310 | _cachingMutation = -1; 311 | } 312 | } 313 | 314 | _waveDataBytes = _cachedMutation.length - _waveDataPos; 315 | 316 | e.data.writeBytes(_cachedMutation, _waveDataPos, _waveDataBytes); 317 | } 318 | } 319 | else 320 | { 321 | if (_cachingNormal) 322 | { 323 | _waveDataPos = _cachedWave.position; 324 | 325 | if (synthWave(_cachedWave, 3072, true)) 326 | { 327 | if ((length = _cachedWave.length) < 24576) 328 | { 329 | // If the sound is smaller than the buffer length, add silence to allow it to play 330 | _cachedWave.position = length; 331 | for(i = 0, l = 24576 - length; i < l; i++) _cachedWave.writeFloat(0.0); 332 | } 333 | 334 | _cachingNormal = false; 335 | } 336 | 337 | _waveDataBytes = _cachedWave.length - _waveDataPos; 338 | 339 | e.data.writeBytes(_cachedWave, _waveDataPos, _waveDataBytes); 340 | } 341 | } 342 | } 343 | } 344 | 345 | //-------------------------------------------------------------------------- 346 | // 347 | // Cached Sound Methods 348 | // 349 | //-------------------------------------------------------------------------- 350 | 351 | /** 352 | * Cache the sound for speedy playback. 353 | * If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds 354 | * per frame to cache, them calling the callback when it's done. 355 | * If not, the whole sound is cached imidiately - can freeze the player for a few seconds, especially in debug mode. 356 | * @param callback Function to call when the caching is complete 357 | * @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame 358 | */ 359 | public function cacheSound(callback:Function = null, maxTimePerFrame:uint = 5):void 360 | { 361 | stop(); 362 | 363 | if (_cachingAsync) return; 364 | 365 | reset(true); 366 | 367 | _cachedWave = new ByteArray(); 368 | 369 | if (Boolean(callback)) 370 | { 371 | _mutation = false; 372 | _cachingNormal = true; 373 | _cachingAsync = true; 374 | _cacheTimePerFrame = maxTimePerFrame; 375 | 376 | _cachedCallback = callback; 377 | 378 | if (!_cacheTicker) _cacheTicker = new Shape; 379 | 380 | _cacheTicker.addEventListener(Event.ENTER_FRAME, cacheSection); 381 | } 382 | else 383 | { 384 | _cachingNormal = false; 385 | _cachingAsync = false; 386 | 387 | synthWave(_cachedWave, _envelopeFullLength, true); 388 | 389 | var length:uint = _cachedWave.length; 390 | 391 | if(length < 24576) 392 | { 393 | // If the sound is smaller than the buffer length, add silence to allow it to play 394 | _cachedWave.position = length; 395 | for(var i:uint = 0, l:uint = 24576 - length; i < l; i++) _cachedWave.writeFloat(0.0); 396 | } 397 | } 398 | } 399 | 400 | /** 401 | * Caches a series of mutations on the source sound. 402 | * If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds 403 | * per frame to cache, them calling the callback when it's done. 404 | * If not, the whole sound is cached imidiately - can freeze the player for a few seconds, especially in debug mode. 405 | * @param mutationsNum Number of mutations to cache 406 | * @param mutationAmount Amount of mutation 407 | * @param callback Function to call when the caching is complete 408 | * @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame 409 | */ 410 | public function cacheMutations(mutationsNum:uint, mutationAmount:Number = 0.05, callback:Function = null, maxTimePerFrame:uint = 5):void 411 | { 412 | stop(); 413 | 414 | if (_cachingAsync) return; 415 | 416 | _cachedMutationsNum = mutationsNum; 417 | _cachedMutations = new Vector.(); 418 | 419 | if (Boolean(callback)) 420 | { 421 | _mutation = true; 422 | 423 | _cachingMutation = 0; 424 | _cachedMutation = new ByteArray; 425 | _cachedMutations[0] = _cachedMutation; 426 | _cachedMutationAmount = mutationAmount; 427 | 428 | _original = _params.clone(); 429 | _params.mutate(mutationAmount); 430 | 431 | reset(true); 432 | 433 | _cachingAsync = true; 434 | _cacheTimePerFrame = maxTimePerFrame; 435 | 436 | _cachedCallback = callback; 437 | 438 | if (!_cacheTicker) _cacheTicker = new Shape; 439 | 440 | _cacheTicker.addEventListener(Event.ENTER_FRAME, cacheSection); 441 | } 442 | else 443 | { 444 | var original:SfxrParams = _params.clone(); 445 | 446 | for(var i:uint = 0; i < _cachedMutationsNum; i++) 447 | { 448 | _params.mutate(mutationAmount); 449 | cacheSound(); 450 | _cachedMutations[i] = _cachedWave; 451 | _params.copyFrom(original); 452 | } 453 | 454 | _cachingMutation = -1; 455 | } 456 | } 457 | 458 | /** 459 | * Performs the asynchronous cache, working for up to _cacheTimePerFrame milliseconds per frame 460 | * @param e enterFrame event 461 | */ 462 | private function cacheSection(e:Event):void 463 | { 464 | var cacheStartTime:uint = getTimer(); 465 | 466 | while (getTimer() - cacheStartTime < _cacheTimePerFrame) 467 | { 468 | if (_mutation) 469 | { 470 | _waveDataPos = _cachedMutation.position; 471 | 472 | if (synthWave(_cachedMutation, 500, true)) 473 | { 474 | _params.copyFrom(_original); 475 | _params.mutate(_cachedMutationAmount); 476 | reset(true); 477 | 478 | _cachingMutation++; 479 | _cachedMutation = new ByteArray; 480 | _cachedMutations[_cachingMutation] = _cachedMutation; 481 | 482 | if (_cachingMutation >= _cachedMutationsNum) 483 | { 484 | _cachingMutation = -1; 485 | _cachingAsync = false; 486 | 487 | _params.paramsDirty = false; 488 | 489 | _cachedCallback(); 490 | _cachedCallback = null; 491 | _cacheTicker.removeEventListener(Event.ENTER_FRAME, cacheSection); 492 | 493 | return; 494 | } 495 | } 496 | } 497 | else 498 | { 499 | _waveDataPos = _cachedWave.position; 500 | 501 | if (synthWave(_cachedWave, 500, true)) 502 | { 503 | _cachingNormal = false; 504 | _cachingAsync = false; 505 | 506 | _cachedCallback(); 507 | _cachedCallback = null; 508 | _cacheTicker.removeEventListener(Event.ENTER_FRAME, cacheSection); 509 | 510 | return; 511 | } 512 | } 513 | } 514 | } 515 | 516 | //-------------------------------------------------------------------------- 517 | // 518 | // Synth Methods 519 | // 520 | //-------------------------------------------------------------------------- 521 | 522 | /** 523 | * Resets the runing variables from the params 524 | * Used once at the start (total reset) and for the repeat effect (partial reset) 525 | * @param totalReset If the reset is total 526 | */ 527 | private function reset(totalReset:Boolean):void 528 | { 529 | // Shorter reference 530 | var p:SfxrParams = _params; 531 | 532 | _period = 100.0 / (p.startFrequency * p.startFrequency + 0.001); 533 | _maxPeriod = 100.0 / (p.minFrequency * p.minFrequency + 0.001); 534 | 535 | _slide = 1.0 - p.slide * p.slide * p.slide * 0.01; 536 | _deltaSlide = -p.deltaSlide * p.deltaSlide * p.deltaSlide * 0.000001; 537 | 538 | if (p.waveType == 0) 539 | { 540 | _squareDuty = 0.5 - p.squareDuty * 0.5; 541 | _dutySweep = -p.dutySweep * 0.00005; 542 | } 543 | 544 | if (p.changeAmount > 0.0) _changeAmount = 1.0 - p.changeAmount * p.changeAmount * 0.9; 545 | else _changeAmount = 1.0 + p.changeAmount * p.changeAmount * 10.0; 546 | 547 | _changeTime = 0; 548 | 549 | if(p.changeSpeed == 1.0) _changeLimit = 0; 550 | else _changeLimit = (1.0 - p.changeSpeed) * (1.0 - p.changeSpeed) * 20000 + 32; 551 | 552 | if(totalReset) 553 | { 554 | p.paramsDirty = false; 555 | 556 | _masterVolume = p.masterVolume * p.masterVolume; 557 | 558 | _waveType = p.waveType; 559 | 560 | if (p.sustainTime < 0.01) p.sustainTime = 0.01; 561 | 562 | var totalTime:Number = p.attackTime + p.sustainTime + p.decayTime; 563 | if (totalTime < 0.18) 564 | { 565 | var multiplier:Number = 0.18 / totalTime; 566 | p.attackTime *= multiplier; 567 | p.sustainTime *= multiplier; 568 | p.decayTime *= multiplier; 569 | } 570 | 571 | _sustainPunch = p.sustainPunch; 572 | 573 | _phase = 0; 574 | 575 | _minFreqency = p.minFrequency; 576 | 577 | _filters = p.lpFilterCutoff != 1.0 || p.hpFilterCutoff != 0.0; 578 | 579 | _lpFilterPos = 0.0; 580 | _lpFilterDeltaPos = 0.0; 581 | _lpFilterCutoff = p.lpFilterCutoff * p.lpFilterCutoff * p.lpFilterCutoff * 0.1; 582 | _lpFilterDeltaCutoff = 1.0 + p.lpFilterCutoffSweep * 0.0001; 583 | _lpFilterDamping = 5.0 / (1.0 + p.lpFilterResonance * p.lpFilterResonance * 20.0) * (0.01 + _lpFilterCutoff); 584 | if (_lpFilterDamping > 0.8) _lpFilterDamping = 0.8; 585 | _lpFilterDamping = 1.0 - _lpFilterDamping; 586 | _lpFilterOn = p.lpFilterCutoff != 1.0; 587 | 588 | _hpFilterPos = 0.0; 589 | _hpFilterCutoff = p.hpFilterCutoff * p.hpFilterCutoff * 0.1; 590 | _hpFilterDeltaCutoff = 1.0 + p.hpFilterCutoffSweep * 0.0003; 591 | 592 | _vibratoPhase = 0.0; 593 | _vibratoSpeed = p.vibratoSpeed * p.vibratoSpeed * 0.01; 594 | _vibratoAmplitude = p.vibratoDepth * 0.5; 595 | 596 | _envelopeVolume = 0.0; 597 | _envelopeStage = 0; 598 | _envelopeTime = 0; 599 | _envelopeLength0 = p.attackTime * p.attackTime * 100000.0; 600 | _envelopeLength1 = p.sustainTime * p.sustainTime * 100000.0; 601 | _envelopeLength2 = p.decayTime * p.decayTime * 100000.0 + 10; 602 | _envelopeLength = _envelopeLength0; 603 | _envelopeFullLength = _envelopeLength0 + _envelopeLength1 + _envelopeLength2; 604 | 605 | _envelopeOverLength0 = 1.0 / _envelopeLength0; 606 | _envelopeOverLength1 = 1.0 / _envelopeLength1; 607 | _envelopeOverLength2 = 1.0 / _envelopeLength2; 608 | 609 | _phaser = p.phaserOffset != 0.0 || p.phaserSweep != 0.0; 610 | 611 | _phaserOffset = p.phaserOffset * p.phaserOffset * 1020.0; 612 | if(p.phaserOffset < 0.0) _phaserOffset = -_phaserOffset; 613 | _phaserDeltaOffset = p.phaserSweep * p.phaserSweep * p.phaserSweep * 0.2; 614 | _phaserPos = 0; 615 | 616 | if(!_phaserBuffer) _phaserBuffer = new Vector.(1024, true); 617 | if(!_noiseBuffer) _noiseBuffer = new Vector.(32, true); 618 | 619 | for(var i:uint = 0; i < 1024; i++) _phaserBuffer[i] = 0.0; 620 | for(i = 0; i < 32; i++) _noiseBuffer[i] = Math.random() * 2.0 - 1.0; 621 | 622 | _repeatTime = 0; 623 | 624 | if (p.repeatSpeed == 0.0) _repeatLimit = 0; 625 | else _repeatLimit = int((1.0-p.repeatSpeed) * (1.0-p.repeatSpeed) * 20000) + 32; 626 | } 627 | } 628 | 629 | /** 630 | * Writes the wave to the supplied buffer ByteArray 631 | * @param buffer A ByteArray to write the wave to 632 | * @param waveData If the wave should be written for the waveData 633 | * @return If the wave is finished 634 | */ 635 | private function synthWave(buffer:ByteArray, length:uint, waveData:Boolean = false, sampleRate:uint = 44100, bitDepth:uint = 16):Boolean 636 | { 637 | _finished = false; 638 | 639 | _sampleCount = 0; 640 | _bufferSample = 0.0; 641 | 642 | for(var i:uint = 0; i < length; i++) 643 | { 644 | if (_finished) return true; 645 | 646 | // Repeats every _repeatLimit times, partially resetting the sound parameters 647 | if(_repeatLimit != 0) 648 | { 649 | if(++_repeatTime >= _repeatLimit) 650 | { 651 | _repeatTime = 0; 652 | reset(false); 653 | } 654 | } 655 | 656 | // If _changeLimit is reached, shifts the pitch 657 | if(_changeLimit != 0) 658 | { 659 | if(++_changeTime >= _changeLimit) 660 | { 661 | _changeLimit = 0; 662 | _period *= _changeAmount; 663 | } 664 | } 665 | 666 | // Acccelerate and apply slide 667 | _slide += _deltaSlide; 668 | _period *= _slide; 669 | 670 | // Checks for frequency getting too low, and stops the sound if a minFrequency was set 671 | if(_period > _maxPeriod) 672 | { 673 | _period = _maxPeriod; 674 | if(_minFreqency > 0.0) _finished = true; 675 | } 676 | 677 | _periodTemp = _period; 678 | 679 | // Applies the vibrato effect 680 | if(_vibratoAmplitude > 0.0) 681 | { 682 | _vibratoPhase += _vibratoSpeed; 683 | _periodTemp = _period * (1.0 + Math.sin(_vibratoPhase) * _vibratoAmplitude); 684 | } 685 | 686 | _periodTemp = int(_periodTemp); 687 | if(_periodTemp < 8) _periodTemp = 8; 688 | 689 | // Sweeps the square duty 690 | if (_waveType == 0) 691 | { 692 | _squareDuty += _dutySweep; 693 | if(_squareDuty < 0.0) _squareDuty = 0.0; 694 | else if (_squareDuty > 0.5) _squareDuty = 0.5; 695 | } 696 | 697 | // Moves through the different stages of the volume envelope 698 | if(++_envelopeTime > _envelopeLength) 699 | { 700 | _envelopeTime = 0; 701 | 702 | switch(++_envelopeStage) 703 | { 704 | case 1: _envelopeLength = _envelopeLength1; break; 705 | case 2: _envelopeLength = _envelopeLength2; break; 706 | } 707 | } 708 | 709 | // Sets the volume based on the position in the envelope 710 | switch(_envelopeStage) 711 | { 712 | case 0: _envelopeVolume = _envelopeTime * _envelopeOverLength0; break; 713 | case 1: _envelopeVolume = 1.0 + (1.0 - _envelopeTime * _envelopeOverLength1) * 2.0 * _sustainPunch; break; 714 | case 2: _envelopeVolume = 1.0 - _envelopeTime * _envelopeOverLength2; break; 715 | case 3: _envelopeVolume = 0.0; _finished = true; break; 716 | } 717 | 718 | // Moves the phaser offset 719 | if (_phaser) 720 | { 721 | _phaserOffset += _phaserDeltaOffset; 722 | _phaserInt = int(_phaserOffset); 723 | if(_phaserInt < 0) _phaserInt = -_phaserInt; 724 | else if (_phaserInt > 1023) _phaserInt = 1023; 725 | } 726 | 727 | // Moves the high-pass filter cutoff 728 | if(_filters && _hpFilterDeltaCutoff != 0.0) 729 | { 730 | _hpFilterCutoff *= _hpFilterDeltaCutoff; 731 | if(_hpFilterCutoff < 0.00001) _hpFilterCutoff = 0.00001; 732 | else if(_hpFilterCutoff > 0.1) _hpFilterCutoff = 0.1; 733 | } 734 | 735 | _superSample = 0.0; 736 | for(var j:int = 0; j < 8; j++) 737 | { 738 | // Cycles through the period 739 | _phase++; 740 | if(_phase >= _periodTemp) 741 | { 742 | _phase = _phase - _periodTemp; 743 | 744 | // Generates new random noise for this period 745 | if(_waveType == 3) 746 | { 747 | for(var n:uint = 0; n < 32; n++) _noiseBuffer[n] = Math.random() * 2.0 - 1.0; 748 | } 749 | } 750 | 751 | // Gets the sample from the oscillator 752 | switch(_waveType) 753 | { 754 | case 0: // Square wave 755 | { 756 | _sample = ((_phase / _periodTemp) < _squareDuty) ? 0.5 : -0.5; 757 | break; 758 | } 759 | case 1: // Saw wave 760 | { 761 | _sample = 1.0 - (_phase / _periodTemp) * 2.0; 762 | break; 763 | } 764 | case 2: // Sine wave (fast and accurate approx) 765 | { 766 | _pos = _phase / _periodTemp; 767 | _pos = _pos > 0.5 ? (_pos - 1.0) * 6.28318531 : _pos * 6.28318531; 768 | _sample = _pos < 0 ? 1.27323954 * _pos + .405284735 * _pos * _pos : 1.27323954 * _pos - 0.405284735 * _pos * _pos; 769 | _sample = _sample < 0 ? .225 * (_sample *-_sample - _sample) + _sample : .225 * (_sample * _sample - _sample) + _sample; 770 | 771 | break; 772 | } 773 | case 3: // Noise 774 | { 775 | _sample = _noiseBuffer[uint(_phase * 32 / int(_periodTemp))]; 776 | break; 777 | } 778 | } 779 | 780 | // Applies the low and high pass filters 781 | if (_filters) 782 | { 783 | _lpFilterOldPos = _lpFilterPos; 784 | _lpFilterCutoff *= _lpFilterDeltaCutoff; 785 | if(_lpFilterCutoff < 0.0) _lpFilterCutoff = 0.0; 786 | else if(_lpFilterCutoff > 0.1) _lpFilterCutoff = 0.1; 787 | 788 | if(_lpFilterOn) 789 | { 790 | _lpFilterDeltaPos += (_sample - _lpFilterPos) * _lpFilterCutoff; 791 | _lpFilterDeltaPos *= _lpFilterDamping; 792 | } 793 | else 794 | { 795 | _lpFilterPos = _sample; 796 | _lpFilterDeltaPos = 0.0; 797 | } 798 | 799 | _lpFilterPos += _lpFilterDeltaPos; 800 | 801 | _hpFilterPos += _lpFilterPos - _lpFilterOldPos; 802 | _hpFilterPos *= 1.0 - _hpFilterCutoff; 803 | _sample = _hpFilterPos; 804 | } 805 | 806 | // Applies the phaser effect 807 | if (_phaser) 808 | { 809 | _phaserBuffer[_phaserPos&1023] = _sample; 810 | _sample += _phaserBuffer[(_phaserPos - _phaserInt + 1024) & 1023]; 811 | _phaserPos = (_phaserPos + 1) & 1023; 812 | } 813 | 814 | _superSample += _sample; 815 | } 816 | 817 | // Averages out the super samples and applies volumes 818 | _superSample = _masterVolume * _envelopeVolume * _superSample * 0.125; 819 | 820 | // Clipping if too loud 821 | if(_superSample > 1.0) _superSample = 1.0; 822 | else if(_superSample < -1.0) _superSample = -1.0; 823 | 824 | if(waveData) 825 | { 826 | // Writes same value to left and right channels 827 | buffer.writeFloat(_superSample); 828 | buffer.writeFloat(_superSample); 829 | } 830 | else 831 | { 832 | _bufferSample += _superSample; 833 | 834 | _sampleCount++; 835 | 836 | // Writes mono wave data to the .wav format 837 | if(sampleRate == 44100 || _sampleCount == 2) 838 | { 839 | _bufferSample /= _sampleCount; 840 | _sampleCount = 0; 841 | 842 | if(bitDepth == 16) buffer.writeShort(int(32000.0 * _bufferSample)); 843 | else buffer.writeByte(_bufferSample * 127 + 128); 844 | 845 | _bufferSample = 0.0; 846 | } 847 | } 848 | } 849 | 850 | return false; 851 | } 852 | 853 | 854 | //-------------------------------------------------------------------------- 855 | // 856 | // .wav File Methods 857 | // 858 | //-------------------------------------------------------------------------- 859 | 860 | /** 861 | * Returns a ByteArray of the wave in the form of a .wav file, ready to be saved out 862 | * @param sampleRate Sample rate to generate the .wav at 863 | * @param bitDepth Bit depth to generate the .wav at 864 | * @return Wave in a .wav file 865 | */ 866 | public function getWavFile(sampleRate:uint = 44100, bitDepth:uint = 16):ByteArray 867 | { 868 | stop(); 869 | 870 | reset(true); 871 | 872 | if (sampleRate != 44100) sampleRate = 22050; 873 | if (bitDepth != 16) bitDepth = 8; 874 | 875 | var soundLength:uint = _envelopeFullLength; 876 | if (bitDepth == 16) soundLength *= 2; 877 | if (sampleRate == 22050) soundLength /= 2; 878 | 879 | var filesize:int = 36 + soundLength; 880 | var blockAlign:int = bitDepth / 8; 881 | var bytesPerSec:int = sampleRate * blockAlign; 882 | 883 | var wav:ByteArray = new ByteArray(); 884 | 885 | // Header 886 | wav.endian = Endian.BIG_ENDIAN; 887 | wav.writeUnsignedInt(0x52494646); // Chunk ID "RIFF" 888 | wav.endian = Endian.LITTLE_ENDIAN; 889 | wav.writeUnsignedInt(filesize); // Chunck Data Size 890 | wav.endian = Endian.BIG_ENDIAN; 891 | wav.writeUnsignedInt(0x57415645); // RIFF Type "WAVE" 892 | 893 | // Format Chunk 894 | wav.endian = Endian.BIG_ENDIAN; 895 | wav.writeUnsignedInt(0x666D7420); // Chunk ID "fmt " 896 | wav.endian = Endian.LITTLE_ENDIAN; 897 | wav.writeUnsignedInt(16); // Chunk Data Size 898 | wav.writeShort(1); // Compression Code PCM 899 | wav.writeShort(1); // Number of channels 900 | wav.writeUnsignedInt(sampleRate); // Sample rate 901 | wav.writeUnsignedInt(bytesPerSec); // Average bytes per second 902 | wav.writeShort(blockAlign); // Block align 903 | wav.writeShort(bitDepth); // Significant bits per sample 904 | 905 | // Data Chunk 906 | wav.endian = Endian.BIG_ENDIAN; 907 | wav.writeUnsignedInt(0x64617461); // Chunk ID "data" 908 | wav.endian = Endian.LITTLE_ENDIAN; 909 | wav.writeUnsignedInt(soundLength); // Chunk Data Size 910 | 911 | synthWave(wav, _envelopeFullLength, false, sampleRate, bitDepth); 912 | 913 | wav.position = 0; 914 | 915 | return wav; 916 | } 917 | } 918 | } -------------------------------------------------------------------------------- /src/assets/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFBTom/as3sfxr/5a5032e21b37f9b308ffee203e23b988714bcd57/src/assets/Thumbs.db -------------------------------------------------------------------------------- /src/assets/amiga4ever.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFBTom/as3sfxr/5a5032e21b37f9b308ffee203e23b988714bcd57/src/assets/amiga4ever.ttf -------------------------------------------------------------------------------- /src/assets/as3sfxr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFBTom/as3sfxr/5a5032e21b37f9b308ffee203e23b988714bcd57/src/assets/as3sfxr.png -------------------------------------------------------------------------------- /src/assets/sfbtom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFBTom/as3sfxr/5a5032e21b37f9b308ffee203e23b988714bcd57/src/assets/sfbtom.png -------------------------------------------------------------------------------- /src/ui/TinyButton.as: -------------------------------------------------------------------------------- 1 | package ui 2 | { 3 | import flash.display.BlendMode; 4 | import flash.display.CapsStyle; 5 | import flash.display.JointStyle; 6 | import flash.display.LineScaleMode; 7 | import flash.display.Shape; 8 | import flash.display.Sprite; 9 | import flash.events.Event; 10 | import flash.events.MouseEvent; 11 | import flash.geom.ColorTransform; 12 | import flash.geom.Rectangle; 13 | import flash.text.TextField; 14 | import flash.text.TextFormat; 15 | import flash.text.TextFormatAlign; 16 | 17 | /** 18 | * TinyButton 19 | * 20 | * Copyright 2010 Thomas Vian 21 | * 22 | * Licensed under the Apache License, Version 2.0 (the "License"); 23 | * you may not use this file except in compliance with the License. 24 | * You may obtain a copy of the License at 25 | * 26 | * http://www.apache.org/licenses/LICENSE-2.0 27 | * 28 | * Unless required by applicable law or agreed to in writing, software 29 | * distributed under the License is distributed on an "AS IS" BASIS, 30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | * See the License for the specific language governing permissions and 32 | * limitations under the License. 33 | * 34 | * @author Thomas Vian 35 | */ 36 | public class TinyButton extends Sprite 37 | { 38 | //-------------------------------------------------------------------------- 39 | // 40 | // Properties 41 | // 42 | //-------------------------------------------------------------------------- 43 | 44 | protected var _backOff:Shape; // Button graphic when unselected 45 | protected var _backDown:Shape; // Button graphic when being clicked 46 | protected var _backSelected:Shape; // Button graphic when selected 47 | 48 | protected var _formatOff:TextFormat; // TextFormat when unselected (used for colouring) 49 | protected var _formatDown:TextFormat; // TextFormat when being clicked (used for colouring) 50 | protected var _formatSelected:TextFormat; // TextFormat when selected (used for colouring) 51 | 52 | protected var _text:TextField; // Label TextField (left aligned) 53 | 54 | protected var _rect:Rectangle; // Bounds of the button in the context of the stage 55 | 56 | protected var _selected:Boolean; // If the button is selected (only used for wave selection) 57 | protected var _selectable:Boolean; // If the button is selectable (only used for wave selection) 58 | 59 | protected var _enabled:Boolean; // If the button is currently clickable 60 | 61 | protected var _onClick:Function; // Callback function for when the button is clicked 62 | 63 | //-------------------------------------------------------------------------- 64 | // 65 | // Getters / Setters 66 | // 67 | //-------------------------------------------------------------------------- 68 | 69 | /** Sets the text on the button */ 70 | public function set label(v:String):void {_text.text = v;} 71 | 72 | /** Selects/unselects the button */ 73 | public function get selected():Boolean {return _selected;} 74 | public function set selected(v:Boolean):void 75 | { 76 | _selected = v; 77 | 78 | removeChildAt(0); 79 | 80 | if(_selected) 81 | { 82 | addChildAt(_backSelected, 0); 83 | setFormat(_formatSelected); 84 | } 85 | else 86 | { 87 | addChildAt(_backOff, 0); 88 | setFormat(_formatOff); 89 | } 90 | } 91 | 92 | /** Enables/disables the button */ 93 | public function set enabled(value:Boolean):void 94 | { 95 | if(value) alpha = 1.0; 96 | else alpha = 0.3; 97 | 98 | _enabled = value; 99 | } 100 | 101 | //-------------------------------------------------------------------------- 102 | // 103 | // Constructor 104 | // 105 | //-------------------------------------------------------------------------- 106 | 107 | /** 108 | * Creates the TinyButton, adding text and a background shape. 109 | * Defaults to the off state. 110 | * @param onClick Callback function called when the button is clicked 111 | * @param label Text to display on the button (left aligned) 112 | * @param border Thickness of the border in pixels 113 | * @param selectable If the button should be selectable 114 | */ 115 | public function TinyButton(onClick:Function, label:String, border:Number = 2, selectable:Boolean = false):void 116 | { 117 | _onClick = onClick; 118 | 119 | _selectable = selectable; 120 | _selected = false; 121 | _enabled = true; 122 | 123 | _backOff = drawRect(border, 0, 0xA09088); 124 | _backDown = drawRect(border, 0xA09088, 0xFFF0E0); 125 | _backSelected = drawRect(border, 0, 0x988070); 126 | 127 | _formatOff = new TextFormat("Amiga4Ever", 8, 0); 128 | _formatDown = new TextFormat("Amiga4Ever", 8, 0xA09088); 129 | _formatSelected = new TextFormat("Amiga4Ever", 8, 0xFFF0E0); 130 | 131 | _text = new TextField(); 132 | _text.defaultTextFormat = _formatOff; 133 | _text.mouseEnabled = false; 134 | _text.selectable = false; 135 | _text.embedFonts = true; 136 | _text.text = label; 137 | _text.width = 104; 138 | _text.height = 16; 139 | _text.x = _text.y = 2; 140 | 141 | addChild(_backOff); 142 | addChild(_text); 143 | 144 | mouseChildren = false; 145 | blendMode = BlendMode.LAYER; 146 | 147 | addEventListener(Event.ADDED_TO_STAGE, onAdded); 148 | } 149 | 150 | /** 151 | * Once the button is on the stage, the event listener can be set up and rectangles recorded 152 | * @param e Added to stage event 153 | */ 154 | private function onAdded(e:Event):void 155 | { 156 | removeEventListener(Event.ADDED_TO_STAGE, onAdded) 157 | stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); 158 | 159 | _rect = getBounds(stage); 160 | } 161 | 162 | //-------------------------------------------------------------------------- 163 | // 164 | // Mouse Methods 165 | // 166 | //-------------------------------------------------------------------------- 167 | 168 | /** 169 | * Sets the button to the down state 170 | * @param e MouseEvent 171 | */ 172 | private function onMouseDown(e:MouseEvent):void 173 | { 174 | if (_enabled && _rect.contains(stage.mouseX, stage.mouseY)) 175 | { 176 | stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); 177 | 178 | removeChildAt(0); 179 | 180 | addChildAt(_backDown, 0); 181 | setFormat(_formatDown); 182 | } 183 | } 184 | 185 | /** 186 | * Sets the button to the off state if not selectable, switches state between off and selected if it is. 187 | * Calls the onClick callback 188 | * @param e MouseEvent 189 | */ 190 | private function onMouseUp(e:MouseEvent):void 191 | { 192 | stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); 193 | 194 | removeChildAt(0); 195 | 196 | if(_selectable && (_selected = !_selected)) 197 | { 198 | addChildAt(_backSelected, 0); 199 | setFormat(_formatSelected); 200 | } 201 | else 202 | { 203 | addChildAt(_backOff, 0); 204 | setFormat(_formatOff); 205 | } 206 | 207 | _onClick(this); 208 | } 209 | 210 | //-------------------------------------------------------------------------- 211 | // 212 | // Util Methods 213 | // 214 | //-------------------------------------------------------------------------- 215 | 216 | /** 217 | * Returns a background shape with the specified colours and border 218 | * @param border Thickness of the border in pixels 219 | * @param borderColour Colour of the border 220 | * @param fillColour Colour of the fill 221 | * @return The drawn rectangle Shape 222 | */ 223 | private function drawRect(border:uint, borderColour:uint, fillColour:uint):Shape 224 | { 225 | var rect:Shape = new Shape(); 226 | rect.graphics.lineStyle(border, borderColour, 1, true, LineScaleMode.NORMAL, CapsStyle.SQUARE, JointStyle.MITER); 227 | rect.graphics.beginFill(fillColour, 1); 228 | rect.graphics.drawRect(0, 0, 104, 18); 229 | rect.graphics.endFill(); 230 | return rect; 231 | } 232 | 233 | /** 234 | * Sets both the current and default text format 235 | * @param format TextFormat to apply to the text 236 | */ 237 | private function setFormat(format:TextFormat):void 238 | { 239 | _text.defaultTextFormat = format; 240 | _text.setTextFormat(format); 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /src/ui/TinyCheckbox.as: -------------------------------------------------------------------------------- 1 | package ui 2 | { 3 | import flash.display.CapsStyle; 4 | import flash.display.DisplayObject; 5 | import flash.display.JointStyle; 6 | import flash.display.LineScaleMode; 7 | import flash.display.Shape; 8 | import flash.display.Sprite; 9 | import flash.events.Event; 10 | import flash.events.MouseEvent; 11 | import flash.geom.Rectangle; 12 | import flash.text.AntiAliasType; 13 | import flash.text.TextField; 14 | import flash.text.TextFormat; 15 | import flash.text.TextFormatAlign; 16 | 17 | /** 18 | * TinyCheckbox 19 | * 20 | * Copyright 2010 Thomas Vian 21 | * 22 | * Licensed under the Apache License, Version 2.0 (the "License"); 23 | * you may not use this file except in compliance with the License. 24 | * You may obtain a copy of the License at 25 | * 26 | * http://www.apache.org/licenses/LICENSE-2.0 27 | * 28 | * Unless required by applicable law or agreed to in writing, software 29 | * distributed under the License is distributed on an "AS IS" BASIS, 30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | * See the License for the specific language governing permissions and 32 | * limitations under the License. 33 | * 34 | * @author Thomas Vian 35 | */ 36 | public class TinyCheckbox extends Sprite 37 | { 38 | //-------------------------------------------------------------------------- 39 | // 40 | // Properties 41 | // 42 | //-------------------------------------------------------------------------- 43 | 44 | protected var _back:Shape; // Background colour shape 45 | protected var _tick:Shape; // Tick indicating selection 46 | protected var _border:Shape; // Border shape to cover the borders of the back and tick 47 | 48 | protected var _text:TextField; // Label TextField positioned to the left of the checkbox (right aligned) 49 | 50 | protected var _rect:Rectangle; // Bounds of the checkbox and text in the context of the stage 51 | 52 | protected var _value:Boolean; // The current value of the checkbox 53 | 54 | protected var _onChange:Function; // Callback function called when the value of the checkbox changes 55 | 56 | //-------------------------------------------------------------------------- 57 | // 58 | // Getters / Setters 59 | // 60 | //-------------------------------------------------------------------------- 61 | 62 | /** The value of the checkbox */ 63 | public function get value():Boolean {return _value;} 64 | public function set value(v:Boolean):void 65 | { 66 | if (v != _value) 67 | { 68 | _value = v; 69 | 70 | _tick.visible = _value; 71 | 72 | _onChange(this); 73 | } 74 | } 75 | 76 | //-------------------------------------------------------------------------- 77 | // 78 | // Constructor 79 | // 80 | //-------------------------------------------------------------------------- 81 | 82 | /** 83 | * Creates the TinyCheckbox, adding text and three shapes 84 | * @param onChange Callback function called when the value of the checkbox changes 85 | * @param label Label to display to the left of the checkbox 86 | */ 87 | public function TinyCheckbox(onChange:Function, label:String = "") 88 | { 89 | _onChange = onChange; 90 | _value = true; 91 | 92 | mouseChildren = false; 93 | addEventListener(Event.ADDED_TO_STAGE, onAdded); 94 | 95 | _back = drawRect(0, 0x807060); 96 | _border = drawRect(0, 0xFFFFFF, 0); 97 | _tick = drawRect(0xFFFFFF, 0xF0C090, 1, true); 98 | 99 | if(label != "") 100 | { 101 | _text = new TextField(); 102 | _text.defaultTextFormat = new TextFormat("Amiga4Ever", 8, 0, null, null, null, null, null, TextFormatAlign.RIGHT); 103 | _text.antiAliasType = AntiAliasType.ADVANCED; 104 | _text.selectable = false; 105 | _text.embedFonts = true; 106 | _text.text = label; 107 | _text.width = 200; 108 | _text.height = 20; 109 | _text.x = -205; 110 | _text.y = -2.5; 111 | addChild(_text); 112 | } 113 | 114 | addChild(_back); 115 | addChild(_tick); 116 | addChild(_border); 117 | } 118 | 119 | /** 120 | * Once the checkbox is on the stage, the event listener can be set up and rectangles recorded 121 | * @param e Added to stage event 122 | */ 123 | private function onAdded(e:Event):void 124 | { 125 | removeEventListener(Event.ADDED_TO_STAGE, onAdded) 126 | stage.addEventListener(MouseEvent.CLICK, onMouseClick); 127 | 128 | _rect = _back.getBounds(stage); 129 | 130 | if (_text) _rect = _rect.union(_text.getBounds(stage)); 131 | } 132 | 133 | //-------------------------------------------------------------------------- 134 | // 135 | // Mouse Methods 136 | // 137 | //-------------------------------------------------------------------------- 138 | 139 | /** 140 | * Switches the value of the checkbox 141 | * @param e MouseEvent 142 | */ 143 | protected function onMouseClick(e:MouseEvent):void 144 | { 145 | if (_rect.contains(stage.mouseX, stage.mouseY)) 146 | { 147 | value = !_value; 148 | } 149 | } 150 | 151 | //-------------------------------------------------------------------------- 152 | // 153 | // Util Methods 154 | // 155 | //-------------------------------------------------------------------------- 156 | 157 | /** 158 | * Returns a background shape with the specified colours and alpha 159 | * @param borderColour Colour of the border 160 | * @param fillColour Colour of the fill 161 | * @param fillAlpha Alpha of the fill 162 | * @param tick If the box should be drawn smaller (for the tick) 163 | * @return The drawn rectangle Shape 164 | */ 165 | private function drawRect(borderColour:uint, fillColour:uint, fillAlpha:Number = 1, tick:Boolean = false):Shape 166 | { 167 | var rect:Shape = new Shape(); 168 | rect.graphics.lineStyle(1, borderColour, tick ? 0 : 1, true, LineScaleMode.NORMAL, CapsStyle.SQUARE, JointStyle.MITER); 169 | rect.graphics.beginFill(fillColour, fillAlpha); 170 | if (tick) rect.graphics.drawRect(2, 2, 6, 6); 171 | else rect.graphics.drawRect(0, 0, 9, 9); 172 | rect.graphics.endFill(); 173 | return rect; 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /src/ui/TinySlider.as: -------------------------------------------------------------------------------- 1 | package ui 2 | { 3 | import flash.display.CapsStyle; 4 | import flash.display.DisplayObject; 5 | import flash.display.JointStyle; 6 | import flash.display.LineScaleMode; 7 | import flash.display.Shape; 8 | import flash.display.Sprite; 9 | import flash.events.Event; 10 | import flash.events.MouseEvent; 11 | import flash.geom.Rectangle; 12 | import flash.text.AntiAliasType; 13 | import flash.text.TextField; 14 | import flash.text.TextFormat; 15 | import flash.text.TextFormatAlign; 16 | 17 | /** 18 | * TinySlider 19 | * 20 | * Copyright 2010 Thomas Vian 21 | * 22 | * Licensed under the Apache License, Version 2.0 (the "License"); 23 | * you may not use this file except in compliance with the License. 24 | * You may obtain a copy of the License at 25 | * 26 | * http://www.apache.org/licenses/LICENSE-2.0 27 | * 28 | * Unless required by applicable law or agreed to in writing, software 29 | * distributed under the License is distributed on an "AS IS" BASIS, 30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | * See the License for the specific language governing permissions and 32 | * limitations under the License. 33 | * 34 | * @author Thomas Vian 35 | */ 36 | public class TinySlider extends Sprite 37 | { 38 | //-------------------------------------------------------------------------- 39 | // 40 | // Properties 41 | // 42 | //-------------------------------------------------------------------------- 43 | 44 | protected var _back:Shape; // Background colour shape 45 | protected var _bar:Shape; // Coloured bar to indicate value 46 | protected var _border:Shape; // Border shape to cover the borders of the back and bar 47 | 48 | protected var _text:TextField; // Label TextField positioned to the left of the slider (right aligned) 49 | 50 | protected var _rect:Rectangle; // Bounds of the slider in the context of the stage 51 | protected var _textRect:Rectangle; // Bounds of the label in the context of the stage 52 | 53 | protected var _formatLight:TextFormat; // Format for colouring the undimmed text (black) 54 | protected var _formatDim:TextFormat; // Format for colouring the dimmed text (gray) 55 | 56 | protected var _plusMinus:Boolean; // If the slider ranges from -1 to 1, instead of 0 to 1 57 | protected var _value:Number; // The current value of the slider 58 | protected var _defaultValue:Number; // The default value of the slider 59 | 60 | protected var _newValue:Number; // New value being dragged to, used as value when released 61 | 62 | protected var _onChange:Function; // Callback function called when the value of the slider changes 63 | 64 | //-------------------------------------------------------------------------- 65 | // 66 | // Getters / Setters 67 | // 68 | //-------------------------------------------------------------------------- 69 | 70 | /** Changes the text to gray if true, black if false */ 71 | public function set dimLabel(v:Boolean):void {_text.setTextFormat(v? _formatDim : _formatLight);} 72 | 73 | /** The default value of the slider */ 74 | public function set defaultValue(v:Number):void { _defaultValue = v; } 75 | 76 | /** The value of the slider */ 77 | public function get value():Number {return _value;} 78 | public function set value(v:Number):void 79 | { 80 | if (_plusMinus && v < -1.0) v = -1.0; 81 | else if (!_plusMinus && v < 0.0) v = 0.0; 82 | else if (v > 1.0) v = 1.0; 83 | 84 | if (v != _value) 85 | { 86 | _value = v; 87 | 88 | _bar.scaleX = _plusMinus ? (_value + 1.0) * 0.5 : _value; 89 | 90 | _onChange(this); 91 | } 92 | } 93 | 94 | //-------------------------------------------------------------------------- 95 | // 96 | // Constructor 97 | // 98 | //-------------------------------------------------------------------------- 99 | 100 | /** 101 | * Creates the TinySlider, adding text and three shapes 102 | * @param onChange Callback function called when the value of the slider changes 103 | * @param label Label to display to the left of the slider 104 | * @param plusMinus If the slider ranges from -1 to 1, instead of 0 to 1 105 | */ 106 | public function TinySlider(onChange:Function, label:String = "", plusMinus:Boolean = false) 107 | { 108 | _onChange = onChange; 109 | _plusMinus = plusMinus; 110 | _value = 0.0; 111 | 112 | _defaultValue = 0.0; 113 | 114 | mouseChildren = false; 115 | addEventListener(Event.ADDED_TO_STAGE, onAdded); 116 | 117 | _back = drawRect(0, 0x807060); 118 | _bar = drawRect(0xFFFFFF, 0xF0C090); 119 | _border = drawRect(0, 0xFFFFFF, 0); 120 | 121 | _bar.scaleX = _plusMinus ? 0.5 : 0.0; 122 | 123 | if (plusMinus) 124 | { 125 | _border.graphics.moveTo(50, 0); 126 | _border.graphics.lineTo(50, -1); 127 | _border.graphics.moveTo(50, 9); 128 | _border.graphics.lineTo(50, 10); 129 | } 130 | 131 | if(label != "") 132 | { 133 | _text = new TextField(); 134 | _text.defaultTextFormat = new TextFormat("Amiga4Ever", 8, null, null, null, null, null, null, TextFormatAlign.RIGHT); 135 | _text.antiAliasType = AntiAliasType.ADVANCED; 136 | _text.selectable = false; 137 | _text.embedFonts = true; 138 | _text.text = label; 139 | _text.width = 200; 140 | _text.height = 20; 141 | _text.x = -205; 142 | _text.y = -2.5; 143 | addChild(_text); 144 | 145 | _formatLight = new TextFormat(null, null, 0); 146 | _formatDim = new TextFormat(null, null, 0x808080); 147 | } 148 | 149 | addChild(_back); 150 | addChild(_bar); 151 | addChild(_border); 152 | } 153 | 154 | /** 155 | * Once the slider is on the stage, the event listener can be set up and rectangles recorded 156 | * @param e Added to stage event 157 | */ 158 | private function onAdded(e:Event):void 159 | { 160 | removeEventListener(Event.ADDED_TO_STAGE, onAdded) 161 | stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); 162 | 163 | _rect = _back.getBounds(stage); 164 | 165 | if (_text) _textRect = _text.getBounds(stage); 166 | } 167 | 168 | //-------------------------------------------------------------------------- 169 | // 170 | // Mouse Methods 171 | // 172 | //-------------------------------------------------------------------------- 173 | 174 | /** 175 | * Starts listening for mouse move and updates the value, or resets the value if you click on the text 176 | * @param e MouseEvent 177 | */ 178 | protected function onMouseDown(e:MouseEvent):void 179 | { 180 | if (_rect.contains(stage.mouseX, stage.mouseY)) 181 | { 182 | updateValue(); 183 | 184 | stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); 185 | stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); 186 | } 187 | else if (_textRect && _textRect.contains(stage.mouseX, stage.mouseY)) 188 | { 189 | value = _defaultValue; 190 | } 191 | } 192 | 193 | /** 194 | * Updates the value when the mouse moves 195 | * @param e MouseEvent 196 | */ 197 | protected function onMouseMove(e:MouseEvent):void 198 | { 199 | updateValue(); 200 | 201 | e.updateAfterEvent(); 202 | } 203 | 204 | /** 205 | * Stops listening for mouse move, sets value to _newValue 206 | * @param e MouseEvent 207 | */ 208 | protected function onMouseUp(e:MouseEvent):void 209 | { 210 | stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); 211 | stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); 212 | 213 | value = _newValue; 214 | } 215 | 216 | //-------------------------------------------------------------------------- 217 | // 218 | // Util Methods 219 | // 220 | //-------------------------------------------------------------------------- 221 | 222 | /** 223 | * Returns a background shape with the specified colours and alpha 224 | * @param borderColour Colour of the border 225 | * @param fillColour Colour of the fill 226 | * @param fillAlpha Alpha of the fill 227 | * @return The drawn rectangle Shape 228 | */ 229 | private function drawRect(borderColour:uint, fillColour:uint, fillAlpha:Number = 1):Shape 230 | { 231 | var rect:Shape = new Shape(); 232 | rect.graphics.lineStyle(1, borderColour, 1, true, LineScaleMode.NORMAL, CapsStyle.SQUARE, JointStyle.MITER); 233 | rect.graphics.beginFill(fillColour, fillAlpha); 234 | rect.graphics.drawRect(0, 0, 100, 9); 235 | rect.graphics.endFill(); 236 | return rect; 237 | } 238 | 239 | /** 240 | * Updates the _newValue based on mouse position 241 | */ 242 | protected function updateValue():void 243 | { 244 | _newValue = _plusMinus ? (mouseX / 100) * 2 - 1 : mouseX / 100; 245 | 246 | if (_plusMinus && _newValue < -1.0) _newValue = -1.0; 247 | else if (!_plusMinus && _newValue < 0.0) _newValue = 0.0; 248 | else if (_newValue > 1.0) _newValue = 1.0; 249 | 250 | _bar.scaleX = _plusMinus ? (_newValue + 1.0) * 0.5 : _newValue; 251 | } 252 | } 253 | } --------------------------------------------------------------------------------