├── 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 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
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 |
83 |
84 |
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 | }
--------------------------------------------------------------------------------