kelvinluck.com

a stroke of luck

Second steps with Flash 10 audio programming

A while back I did some experimenting with the new Flash 10 audio features. Since then I’ve received a couple of emails from people who have noticed that the flash player can freeze up when the mp3 file is initially extracted with the Sound.extract command – especially with longer mp3 files.

The solution is to simply extract only as much of the sound as you need to work with on each sampleData callback. However, this can get confusing when you combine it with the speed changing code from my first example. So I’ve put together another example which uses this method:

The code is available for download here or you can see it below:

package com.kelvinluck.audio
{
   import flash.events.Event;
   import flash.events.SampleDataEvent;
   import flash.media.Sound;
   import flash.media.SoundChannel;
   import flash.net.URLRequest;
   import flash.utils.ByteArray;    

   /**
    * @author Kelvin Luck
    */

   public class MP3Player
   {
     
      public static const BYTES_PER_CALLBACK:int = 4096; // Should be >= 2048 && < = 8192

      private var _playbackSpeed:Number = 1;

      public function set playbackSpeed(value:Number):void
      {
         if (value < 0) {
            throw new Error('Playback speed must be positive!');
         }
         _playbackSpeed = value;
      }

      private var _mp3:Sound;
      private var _dynamicSound:Sound;
      private var _channel:SoundChannel;

      private var _phase:Number;
      private var _numSamples:int;

      public function MP3Player()
      {
      }

      public function loadAndPlay(request:URLRequest):void
      {
         _mp3 = new Sound();
         _mp3.addEventListener(Event.COMPLETE, mp3Complete);
         _mp3.load(request);
      }

      public function playLoadedSound(s:Sound):void
      {
         _mp3 = s;
         play();
      }
     
      public function stop():void
      {
         if (_dynamicSound) {
            _dynamicSound.removeEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
            _channel.removeEventListener(Event.SOUND_COMPLETE, onSoundFinished);
            _dynamicSound = null;
            _channel = null;
         }
      }

      private function mp3Complete(event:Event):void
      {
         play();
      }

      private function play():void
      {
         stop();
         _dynamicSound = new Sound();
         _dynamicSound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
         
         _numSamples = int(_mp3.length * 44.1);
         
         _phase = 0;
         _channel = _dynamicSound.play();
         _channel.addEventListener(Event.SOUND_COMPLETE, onSoundFinished);
      }
     
      private function onSoundFinished(event:Event):void
      {
         _channel.removeEventListener(Event.SOUND_COMPLETE, onSoundFinished);
         _channel = _dynamicSound.play();
         _channel.addEventListener(Event.SOUND_COMPLETE, onSoundFinished);
      }

      private function onSampleData( event:SampleDataEvent ):void
      {
         var l:Number;
         var r:Number;
         var p:int;
         
         
         var loadedSamples:ByteArray = new ByteArray();
         var startPosition:int = int(_phase);
         _mp3.extract(loadedSamples, BYTES_PER_CALLBACK * _playbackSpeed, startPosition);
         loadedSamples.position = 0;
         
         while (loadedSamples.bytesAvailable > 0) {
           
            p = int(_phase - startPosition) * 8;
           
            if (p < loadedSamples.length - 8 && event.data.length <= BYTES_PER_CALLBACK * 8) {
               
               loadedSamples.position = p;
               
               l = loadedSamples.readFloat();
               r = loadedSamples.readFloat();
           
               event.data.writeFloat(l);
               event.data.writeFloat(r);
               
            } else {
               loadedSamples.position = loadedSamples.length;
            }
           
            _phase += _playbackSpeed;
           
            // loop
            if (_phase >= _numSamples) {
               _phase -= _numSamples;
               break;
            }
         }
      }
   }
}

You can compare it to the code in the original post to see the changes I made.

One thing to note is that there is still a delay when you load an MP3 in my example. This is because I am using the same FileReference.browse > Sound object hack as last time and this needs to loop over the entire loaded mp3 file while turning it into a Sound object. This wouldn’t be an issue in most use-cases where you have loaded the sound through Sound.load.

I also removed the option of playing the sound backwards in this example as that would have added further complexity to the code and hurt my head even more!



Tweetcoding – 140 characters of actionscript 3

Just over a week ago, Grant Skinner started a competition on Twitter called Tweetcoding. It’s very simple:
#tweetcoding: code something cool in <=140 characters of AS3
By happy coincidence this was just after I’d decided to give Twitter another chance and so I heard about the competition and decided to get involved. The first thing I did was to put together a quick tweetcoding minifier using jQuery – it lets you paste in your (slightly) readable AS3 code and it strips unnecessary whitespace and tells you how many characters you’ve used. Definitely much easier than the find and replace gymnastics I was doing in my text editor to start with!

Next I had big plans for creating a website to allow you to compile your own tweetcodes online. But all three of my approaches failed – I couldn’t trigger Java (and therefore mxmlc) from PHP on my shared host, I couldn’t piggyback on the wonderfl API (they use mxmlc behind the scenes too) and screaming donkey couldn’t handle the DisplayList. Luckily, Robert Cadena had the same idea and managed to execute it wonderfully and produce the tweetcoding compiling robot (I’m not sure what it’s really called!). You can visit that page and check out all of the great entries without compiling yourself.

The next step was to find a way to compile my tweetcoding from FDT – my preferred editor. I tried using Flash but it’s little code window annoyed me quickly and I don’t have CS4 yet so I couldn’t access any of the flash player 10 features. So I set up a project in FDT and created the following class:

package
{
   import flash.filters.*;
   import flash.text.TextField;  
   import flash.media.Microphone;  
   import flash.ui.Mouse;  
   import flash.events.*;        
   import flash.display.*;

   dynamic public class Tweetcode extends MovieClip
   {

      public var g:Graphics;
      public var mt:Function;
      public var lt:Function;
      public var ls:Function;
      public var m:Class;
      public var r:Function;
      public var s:Function;
      public var o:Object;
      public var i:Number = 0;

      public function Tweetcode():void
      {
         stage.scaleMode='noScale';
         stage.align='top';
         g = graphics;
         mt = g.moveTo;
         lt = g.lineTo;
         ls = g.lineStyle;
         m = Math;
         r = m.random;
         s = m.sin;
         o = {};

         addEventListener("enterFrame", f);
      }

      public function f(e:Event):void
      {
         // 140 characters here!
      }
   }
}

As you can see, it includes Grant’s gimmie code and space for me to add my code. I’ve added extra imports as I’ve needed them but I’m sure there are more that could be included. And I added some static typing to the variables even though this isn’t necessary – it does means I get a little help from FDT’s code hinting. Then I added ” –strict=false” to the mxmlc command line in my launch target and I was good to go :)

Below are some of my tweetcoding attempts in reverse chronological order (that doesn’t necessarily mean that they get better though!). Make sure you check out all of the other entries as well though – there is some incredible stuff. It is amazing what you can cram into so few characters!

Flickering Flame

A simple flame-like effect. View

g.clear(),o[++i]={x:mouseX,y:mouseY,b:9},filters=[new BlurFilter(4,4)];for each(p in o)a=p.b-=.2,ls(a,3e9,a),mt(p.x,p.y),lt(p.x+a,p.y--+a);

Windmill

Blow into your microphone to make it spin around!. View

if(i&lt;6.5) q=Microphone.getMicrophone(),q.setLoopBack(),ls(2),x=y=99,mt(0,0),lt(90*s(i),90*m.cos(i)),i+=m.PI/18;rotation+=q.activityLevel/9;

Colourful bondage

The coloured lines want to keep your mouse prisoner. View

g.clear(),Mouse.hide(),o[++i]={x:mouseX,y:mouseY,b:7,c:i< &lt;16+i<&lt;32};for each(p in o)a=p.b-=.3,ls(a,p.c,a),mt(p.x,p.y),lt(mouseX,mouseY);

Stripy wallpaper

Just some nice animating blue stripes. View

g.clear();t=stage,o[++i]={x:300+s(i)*300,b:9,c:9*i};for each(p in o)a=p.b-=.3,ls(a,p.c,a),l=p.a,mt(p.x,0),lt(p.x,t.stageHeight);

Mouse bubbles

Little bubble like particles escaping from the mouse. View

g.clear(),o[++i]={x:mouseX,y:mouseY,a:r()*9-5,b:r()*9};for each(p in o)a=p.b--,ls(2),p.a*=.9,p.b*=.9,mt(p.x+=p.a,p.y+=p.b),lt(p.x+1,p.y+1);

Ninja the mouse killing line

Weird title (I guess the tweetcoding had gone to my head) but probably my favourite of my entries. View

g.clear();o[++i]={x:mouseX,y:mouseY,a:o[i-1],b:9};for each(p in o)!p.a||(ls(p.b--),l=p.a,mt(p.x-=(p.x-l.x)/6,p.y-=(p.y-l.y)/6),lt(l.x,l.y));

Heartbeat

Short and simple. View

g.clear();o[++i]={x:i,y:99+s(i)*99,a:o[i-1]||{x:0,y:99}};for each(p in o)ls(1,p.x*0x020101),mt(p.x,p.y-=(p.y-p.a.y)/6),lt(p.x,p.y+2);

Marching ants

They follow the mouse and slowly straighten out over time. View

g.clear();ls(2);o[++i]={x:mouseX,y:mouseY,a:o[i-1]||{x:250,y:250}};for each(p in o)mt(p.x-=(p.x-p.a.x)/6,p.y-=(p.y-p.a.y)/6),lt(p.x+1,p.y);

Silly String

Stringy stuff falls from your mouse. View

g.clear();ls(2);o[++i]={x:mouseX,y:mouseY,a:o[i-1]||{x:9,y:9}};for each(p in o)mt(p.x-=(p.x-p.a.x)/6,p.y-=(p.y-p.a.y)/6),lt(p.a.x,p.a.y);

Random Silly String

Stringy stuff gets spread around the screen. View

g.clear();ls(1);o[++i]={x:500*r(),y:500*r(),a:o[i-1]||{x:9,y:9}};for each(p in o)mt(p.x-=(p.x-p.a.x)/6,p.y-=(p.y-p.a.y)/6),lt(p.a.x,p.a.y);

First try

This one is from before there was any gimmie code so it's 140 characters that run by themselves. Unsurprisingly, they do very little! View

var w=x=y=200,b,g=graphics,m=Math,l=g.lineTo,c=m.cos,s=m.sin,i=361;while(i--){g.lineStyle(1,m.random()*0xfff);l(w*c(i),w*s(i));l(0,0);}

Tweetcoding is great fun and seems to be part of a trend to impose constraints to trigger creativity. I've been a close follower of the 25 lines competition and been blown away by what people have achieved there. I'm also really interested in the 4k flash game competition and would love to find the time to put an entry together. It's incredible the amazing results you can get by abandoning anything like best practises and trying to squeeze something interesting out of a constrained situation. Good clean geek fun :D



Interviewed on actionscripthero.org

Last week I received an email Pablo Parrado of actionscripthero.org:
We’re trying to make ActionScriptHero.org a community portal that explores that social aspect of the Flash community and get to know the people behind the names.
He went on to ask me to take part in a series of interviews he is doing. I was honored to be asked and to add my thoughts to those of some leading lights of the flash world (42 in total at the moment). So I completed the interview and yesterday it went live. If you want to read my rambling thoughts about the past and future of flash then please head on over to my interview with actionscript hero. Thanks Pablo!

Progressive enhancement with jQuery example

Update: shAIR is now called Sharify so I’ve updated the links below to point to the new website.

As I previously wrote, we just launched a new product called shAIRSharify. The website was a great chance to implement some progressive enhancement using my favourite javascript library, jQuery. So I thought I’d take the chance here to explain some of the things I did and how they continue to work without javascript enabled. None of this is rocket science by any stretch of the imagination but I thought is was worthy of a quick description.

Since much of the functionality discussed below is behind a login in the admin panel I put together a little example sandbox page which demonstrates the points below.

Styled submit buttons

The beautiful site design from hoppermagic called for submit buttons which don’t really look like buttons at all. Styling a normal submit button is fairly limited and hard to do cross browser so instead I decided to use javascript to swap out the submit button for a normal link which has an event listener which submits the form. If js is disabled the user gets a normal, accessible submit button. If js is available then we know that we will be able to use javascript to submit the form.

The code in question looks like this:

$(':submit').each(
   function()
   {
      var $this = $(this);
      var f = this.form;
      var link = $('<a href="#">' + $this.val() + '</a>')
         .bind(
            'click',
            function()
            {
               $(f).trigger('submit');
               return false;
            }
         )
      $this.after(link).css({position:'absolute', top:'-2000px'});
   }
)

Note that rather than hiding the real submit button with visibility:hidden or display:none we instead just move it way off the screen. This means that you can still submit the form by pressing return in one of the form fields (IE doesn’t allow this if there is no visible submit field in the form).

Form validation

Using the validation plugin made it very quick and easy to add clientside validation to my forms. This doesn’t replace serverside validation but it means that you can inform your users of their errors and give them meaningful feedback quickly. One thing that I would love to see added to the validation plugin is some sort of hooks for when the error message is shown and hidden. I wanted my errors to animate nicely into and out of existence. I managed to hack in a slideDown on the appear by abusing the errorPlacement callback but I wasn’t able to animate the disappearance of the element.

The code related to the validation plugin looks like this:

$forms = $('.validated-form');
if ($forms.length) {
 
   $forms.validate(
      {
         errorPlacement: function(error, element)
            {
               element.after(error);
               error.hide().slideDown();
            }
      }
   );  
}

Note that I only try and call the validate method where I find forms with a class of validated-form. This is because I use the same javascript throughout my site but I only include the validate plugin (and add the validated-form class to forms) on pages where it is required. I would get errors complaining that the validate method couldn’t be found if I didn’t have the check.

Tooltips

Another easy one thanks to the jquery.simpletip plugin. None of the examples on that page seem to use the tooltip in a particularly gracefully degrading way though. In my case I decided to use the title element of the items I was creating a tooltip for to hold the text of the tooltip. This means that without javascript enabled people will still see (less pretty) tooltips. The code is very simple, it grabs the value of the title and passes it into the simpletip initialiser. We also need to remember to empty the title attribute so that the browser doesn’t display it’s tooltip as well as ours.

$('.tt').each(
   function()
   {
      var $tt = $(this);
      $tt.simpletip(
         $tt.attr('title'),
         {
            hook: {
               tooltip: 'topLeft',
               target: 'bottomLeft'
            },
            offset: [20, -5],
            stem: {
               corner: 'topLeft',
               color: '#000',
               size: 15
            }
         }
      ).attr('title', '');
   }
);

FAQs accordian

For the FAQs page I wanted to make it easy for people to scan the questions and then click on the one they wanted the answer for. And jquery made it really easy to add a bit of animation to this too. As an added extra I also add a CSS class to the relevant question heading which changes the direction of the arrow besides it. The answers are hidden by javascript on document ready so users without javascript will just see a normal list of questions followed by answers.

var $h2;
var $answer;
$('.answer').hide();
$('#faq h2').bind(
   'click',
   function()
   {
      if ($h2 && $h2[0] != this) {
         $answer.slideUp();
         $h2.removeClass('open');
      }
      $h2 = $(this);
      $answer = $h2.next();
      $answer.slideDown();
      $h2.addClass('open');
   }
)

Message for IE6 users

I spent a little bit of time making sure that the site was at least usable in IE6. But I didn’t want to waste too much time making sure everything looked perfect. So I decided to add a little message to the top of every page which encourages IE6 users to upgrade their browser. This way they are at least aware why things don’t look quite right and they can’t complain. Luckily, IE6 is a (slowly) dying beast and will be used by a very small proportion of the site’s target audience (developers who are using Adobe AIR).

Summary

So that’s a quick description of some of the simple steps we took to make the website as beautiful and easy to use as possible for people on modern browsers while making sure it is still accessible and available to people using older browsers or who have javascript disabled (for whatever reason).

With the tools and information web developers have at their disposal these days it is so easy to build a site in this manner that there really is no excuse for sites which are completely broken with javascript disabled. Hopefully these simple examples illustrate the some of the concepts behind progressive enhancement.



New This happened website

This happened is a series of events focusing on the stories behind interaction design. Having ideas is easier than making them happen. We delve into projects that exist today, how their concepts and production process can help inform future work.
This happened is an event originally organised by Chris O’Shea, Joel Gethin Lewis and Andreas Müller in London. I’ve been lucky enough to attend a few times and it is always really interesting and inspiring. The fact that the speakers talk about the process and the failures along the way is much more interesting than someone just showcasing their work. And I find the field of “interaction design” really interesting – a world somewhere between computers and the real world and somewhere between art and science.

So when Chris asked if I’d help out with a flickr viewer for the new website I was more than happy to help. I put together a little swf which is used throughout the site. It connects to the Flickr API (using as yet unfinished and unrealeased as3 version of flashr) and grabs photos matching certain machine tags. Depending on where in the site you are the swf displays relevant photos (e.g. from a particular city or a particular event) as a simple slidehow. The thishappened team can easily choose to display any photos that attendees have uploaded to flickr with relevant permissions. And they keep editorial control over the content via a special “auth” machine tag which they can generate through their CMS. It’s all pretty simple but it’s a nice way to bring user generated content to their site easily.

This happened is branching out and encouraging people to host events across the world so keep an eye out for one near you (or even set one up yourself) and if you get the chance make sure you attend.



Introducing shAIR – easily monetize your Adobe AIR applications

Update: shAIR is now called Sharify so I’ve updated the links below to point to the new website.

I am very pleased to announce the release of my latest project: shAIR. It is a product that is designed to be used by developers of Adobe AIR applications and makes it very easy for those developers to add “shAIRware” functionality to their applications.

shAIR

To quote Dr Woohoo – an early beta tester of shAIR:
shAIR solves the greatest challenge to the AIR platform for serious developers – how to integrate a trial period, registration & authentication – by creating a simple solution that helps protect and commoditize their applications and intellectual property. It’s simply brilliant!
shAIR is a combination of an as3 swc file and the website/ API which this connects to. A developer integrates the swc into their application and uses the administration panel on the shAIR website to create and administer licenses for the application.

The beautiful identity and website design was done by my good friends over at hoppermagic – thanks guys :) You should get in touch with them if you want to commision any similarily exquisite work!

The launch of shAIR is also interesting because it marks a change in the direction of my company (Luck Laboratories Ltd) from a purely consultancy based company to one which also produces it’s own products. It’s going to be an interesting ride but I’m really excited by the possibilities for shAIR and looking around the web I think it is something that lots of people have been looking for.



Data scraping with YQL and jQuery

For a project that I’m currently working on I need a list of all the US National Parks in XML format. Google didn’t come up with anything so I decided that I would need to somehow grab the data from this list on Wikipedia. The problem is that the list is in messy HTML but I want some nice clean XML ready for parsing with E4X in Flash.

There are a number of ways I could parse the data. If I knew Ruby and had an environment set up I’d probably use hpricot. Or I could get my hands dirty again with PHP and DOMDocument. Or if the Wikipedia page was XML or could be converted into XML easily then I could use an XSL transform. Or I’m sure there are hundreds of other approaches… But in this case I just wanted to very quickly and easily write a script which would grab and translate the data so I could get on with the rest of the project.

That’s when I thought of using jQuery to parse the data – it is the perfect tool for navigating a HTML DOM and extracting information from it. So I wrote a script which would use AJAX to load the page from Wikipedia. And that’s where I hit the first hurdle: “Access to restricted URI denied” – you can’t make crossdomain AJAX calls because of security restrictions in the browser :(

At this point I had at least a couple of ways to proceed with my jQuery approach:
  • Copy the HTML file from Wikipedia to my server thus avoiding the cross domain issues.
  • Write a quick serverside redirect script to live on my server and grab the page from Wikipedia and echo it back out.
I didn’t like the idea of either of those options but luckily at this point I remembered reading about YQL:
The YQL platform provides a single endpoint service that enables developers to query, filter and combine data across Yahoo! and beyond.
After a quick flick through the documentation and some testing in the YQL Console I put together a script which would grab the relevant page from Wikipedia and convert it into a JSONP call which allows us to get around the cross-domain AJAX issues. As an added extra it’s really easy to add some XPath to your YQL so I’m grabbing only the relevant table from the Wikipedia document which cuts down on the complexity of my javascript. Here’s what I ended up with:
SELECT * FROM html WHERE url="http://en.wikipedia.org/wiki/List_of_United_States_National_Parks_by_state" AND xpath="//table[@class='wikitable sortable']"

If you run this code in the console you’ll see that it grabs the relevant table from wikipedia and returns it as XML or JSON. From here it’s easy to make the AJAX call from jQuery and then loop over the JSON returned creating an XML document. If you are interested in the details of that you can check out the complete example.

I was really impressed with how easy it was to quickly figure out YQL and I think it’s a really useful service. Even if you just use it to convert a HTML page to a valid XML document then it is still invaluable for all sorts of screen scraping purposes (it’s always much easier to parse XML than HTML tag soup). One improvement I’d love to see the addition of a CSS style selection engine as well as the XPath one. And the documentation could maybe be clearer (I figured out the above script by checking examples on other blogs rather than by reading the docs). But overall I give Yahoo! a big thumbs up for YQL and look forward to using it again soon…



Using spell checker for TinyMCE with .NET on IIS

Recently I’ve been working on a website which is built in C sharp using the .NET framework (version 2) and running on IIS. This is a fairly different experience to my usual linux/ apache/ php/ mysql and actionscript setup but it’s been interesting and rewarding. I’m planning a series of posts documenting things that I’ve found out and like or dislike about this environment but for now just a quick one about how to get the spell checker for TinyMCE working…

TinyMCE is “a platform independent web based Javascript HTML WYSIWYG editor control” which is very popular on the web (in fact I am using it to create this post in wordpress!). It comes with heaps of functionality and plugins including a spellchecker. As you’ll see at that link though, the spell checker requires PHP on the serverside… Or does it?

If you look on the TinyMCE download page you will see that there is a .NET package available. I was curious about what this was, especially when I looked through the zip and found that it contained a SpellChecker folder. Looking at the code it turns out that this is the alternative of the PHP code necessary for implementing the spell checker – it accepts the AJAX request from a TinyMCE instance, sends it off to Google’s spell checking web service and sends the relevant results back in the AJAX response. Yay! Now I needed to find out how to use it – for some unknown reason this all seems to be undocumented and google wasn’t bringing back any answers either.

Looking through the c# code, I found that the .NET package is implemented as an IHttpHandler. Once I knew that it was pretty easy to figure out how to configure IIS to use the code. Basically you need to drop the Moxiecode.TinyMCE.dll from the .NET package into the bin folder of your website. Then open your web.config file and find the httpHandlers node inside the system.web node and add the following line in:

<add verb="*" path="TinyMCEHandler.aspx" type="Moxiecode.TinyMCE.Web.HttpHandler, Moxiecode.TinyMCE" validate="false"></add>

This tells IIS that all calls to /TinyMCEHandler.aspx should infact be handled by the Moxiecode.TinyMCE.Web.HttpHandler class in the Moxiecode.TinyMCE assembly. The validate=”false” means that IIS won’t check if this dll and class can be found unless you actually request that page (this is useful if you have seperate applications mapped to virtual directories below this web.config as it will prevent those applications throwing errors because they can’t find the dll)

Now you just have to set up your TinyMCE instances to include a spell checker button and to know the correct place to look for the serverside script. Here is how we instantiate TinyMCE:

tinyMCE.init({
   mode : "textareas",
   theme : "advanced",
   plugins : "paste, spellchecker",
   theme_advanced_buttons1 : "bold,italic,underline,separator,justifyleft,justifycenter,justifyright,justifyfull,separator,bullist,numlist,separator,undo,redo,separator,link,unlink,separator,cut,copy,paste,pastetext,pasteword,separator,forecolor,backcolor,separator,code,separator,spellchecker",
   spellchecker_rpc_url: "/TinyMCEHandler.aspx?module=SpellChecker"
});

The important things are:

  • Added “spellchecker” to the plugins list.
  • Added a “spellchecker” button to the buttons list.
  • Added the spellchecker_rpc_url which tells TinyMCE where to find the serverside script.
And that’s all there is to it. As you can see, it’s very easy as all the code is available but for some reason it seems to be undocumented and I couldn’t find any information out there about how to set it up. So hopefully this post will save someone else the time I spent figuring it out :)

Happy Christmas and Happy New Year!

This Christmas I am living in Canada whilst most of my friends and family are back in England or elsewhere. So I decided to make a virtual Christmas card that I could easily and quickly send anywhere. So here’s one for the reader(s?) of my blog:

You can click on the card to flip it over and read the message on the back but remember to flip back and see how the snow stacks up on the figures in the foreground… It’s not exactly realistic but I like it :)

I managed to put the whole thing together in about a day (which didn’t stop me being about a day late sending it out!) thanks to a bunch of open source projects. So massive thanks to the authors of and contributors to:
  • Papervision for the 3d (although thinking about it, I could have possibly tried to do something this simple using the new “postcards in space” 3d of the flash player 10).
  • Flint particle system for the snow (and I’m hoping to do more with this engine soon – it’s really nicely put together).
  • Pure MVC for holding it together.
  • Code Igniter for the backend (so I can send different cards to different people).
  • GTween for the tweening.
  • And also to Icon Drawer for the icons who are standing in the foreground of this card.
And now it’s time to go out and celebrate the approach of another year! I’m looking forward to lots of cool stuff for next year, both work and play. So Happy Christmas and Happy New Year everyone!! And all the best for 2009 :D

First steps with flash 10 audio programming

As I was reading my RSS feeds yesterday I came across a blog post by Andre Michelle where he released some sourcecode for using the new Sound APIs in Flash Player 10. I had a little spare time so I decided to finally set up FDT to allow me to author flash 10 swfs (which was easier than I expected) so I could do some playing.

The idea was to re-create my wave sequencer experiment using the APIs but to get started I did something simpler. I wrote a little class which allows you to load an MP3 file and play it back with the ability to change the playback speed dynamically. Here it is:

You can see the sourcecode for the relevant file below. The interesting stuff from an audio point of view is happening in the onSampleData callback. This is triggered by the Flash player whenever it needs a new buffer of audio samples to play. The code in that function is commented and hopefully pretty self explanatory. It is derived from code in my old wave sequencer experiment which was itself derived from some code in the popforge library.

package com.kelvinluck.audio
{
   import flash.events.Event;
   import flash.events.SampleDataEvent;
   import flash.media.Sound;
   import flash.net.URLRequest;
   import flash.utils.ByteArray;

   /**
    * @author Kelvin Luck
    */

   public class MP3Player
   {

      private var _playbackSpeed:Number = 1;

      public function set playbackSpeed(value:Number):void
      {
         _playbackSpeed = value;
      }

      private var _mp3:Sound;
      private var _loadedMP3Samples:ByteArray;
      private var _dynamicSound:Sound;

      private var _phase:Number;
      private var _numSamples:int;

      public function MP3Player()
      {
      }

      public function loadAndPlay(request:URLRequest):void
      {
         _mp3 = new Sound();
         _mp3.addEventListener(Event.COMPLETE, mp3Complete);
         _mp3.load(request);
      }

      public function playLoadedSound(s:Sound):void
      {
         var bytes:ByteArray = new ByteArray();
         s.extract(bytes, int(s.length * 44.1));
         play(bytes);
      }
     
      public function stop():void
      {
         if (_dynamicSound) {
            _dynamicSound.removeEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
            _dynamicSound = null;
         }
      }

      private function mp3Complete(event:Event):void
      {
         playLoadedSound(_mp3);
      }

      private function play(bytes:ByteArray):void
      {
         stop();
         _dynamicSound = new Sound();
         _dynamicSound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
         
         _loadedMP3Samples = bytes;
         _numSamples = bytes.length / 8;
         
         _phase = 0;
         _dynamicSound.play();
      }

      private function onSampleData( event:SampleDataEvent ):void
      {
         
         var l:Number;
         var r:Number;
         
         var outputLength:int = 0;
         while (outputLength < 2048) {
            // until we have filled up enough output buffer
           
            // move to the correct location in our loaded samples ByteArray
            _loadedMP3Samples.position = int(_phase) * 8; // 4 bytes per float and two channels so the actual position in the ByteArray is a factor of 8 bigger than the phase
           
            // read out the left and right channels at this position
            l = _loadedMP3Samples.readFloat();
            r = _loadedMP3Samples.readFloat();
           
            // write the samples to our output buffer
            event.data.writeFloat(l);
            event.data.writeFloat(r);
           
            outputLength++;
           
            // advance the phase by the speed...
            _phase += _playbackSpeed;
           
            // and deal with looping (including looping back past the beginning when playing in reverse)
            if (_phase < 0) {
               _phase += _numSamples;
            } else if (_phase >= _numSamples) {
               _phase -= _numSamples;
            }
         }
      }
   }
}

As you can see, there are three public methods in the above class. loadAndPlay will load an mp3 file into a sound object and start playing it at the desired playbackSpeed. stop will stop the currently playing mp3. And playLoadedSound will start playing an already loaded sound object at the desired playbackSpeed. This is useful if you have already preloaded your sound objects but it is also useful for another important reason as you can see in the demo.

Thanks to some great work from an old friend of mine, it is possible to dynamically create a Sound object based on an MP3 loaded through the new FileReference.load() functionality in Flash 10. This is why in the demo you can browse for an mp3 file on your local machine which can then be dynamically controlled by Flash immediately without sending it to a server first.

You can download the complete FDT project of my demo here if you want to look through all of the code. I’m excited by the possibilities that are opening up in flash now that Adobe made some noise – I’ve got a long way to go before I can do anything nearly as incredible as the Hobnox audio tool but I’ve got some ideas and I’m looking forward to playing around with them :)

Update: Check out my follow on post where I examine how to extract the audio on demand rather than up front.