kelvinluck.com

a stroke of luck

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 :)



Experiment with Papervision 3D particles and effects


A while back I was prototyping something for a client which involved lots of red dots moving around in 3D space, realised using Papervision 3D. I didn’t end up persuing this route with the client in the end but the effect was pretty cool so I thought I might as well share it here.

The idea is that there is a bunch of particles who are bouncing around randomly stuck within an invisible cube. The effect looked OK by itself but then I decided to try adding effects. I used a BlurFilter and a BitmapColorEffect to give the each of the particles trails. Then I changed the clipping point like in the original borg cube effects demo to give the impression of the particles falling. I like this version the best - if you move your mouse from side to side around the bottom of the demo swf then it starts to look like some kind of flocking is going on (like in my perlin noise experiment).

Click on the image below to see the demo. Click inside the demo swf to give it focus and then you can use the following keys:
  • 1 - Sets the render mode to normal clean particles (the default).
  • 2 - Sets the render mode to particles with trails.
  • 3 - Sets the render mode to falling particles with trails.
  • c - Toggles display of a cube showing the area the particles are contained within.

Particles and effects in Papervision 3D

The sourcecode for this example is pretty simple. You can see it below or you can download it from here.

package  
{
   import org.papervision3d.core.effects.BitmapColorEffect;
   import org.papervision3d.core.effects.BitmapLayerEffect;
   import org.papervision3d.core.geom.Particles;
   import org.papervision3d.materials.WireframeMaterial;
   import org.papervision3d.materials.utils.MaterialsList;
   import org.papervision3d.objects.DisplayObject3D;
   import org.papervision3d.objects.primitives.Cube;
   import org.papervision3d.view.AbstractView;
   import org.papervision3d.view.BasicView;
   import org.papervision3d.view.layer.BitmapEffectLayer;
   
   import flash.display.StageQuality;
   import flash.events.Event;
   import flash.events.KeyboardEvent;
   import flash.filters.BlurFilter;
   import flash.geom.Point;      

   /**
    * @author Kelvin Luck
    */

   [SWF(width='450', height='450', backgroundColor='#000000', frameRate='41')]

   public class ParticlesCube extends BasicView
   {
     
      public static const NUM_PARTICLES:int = 300;
      public static const CONTAINING_CUBE_SIZE:int = 500;
     
      public static const RENDER_MODE_CLEAN:int = 0;
      public static const RENDER_MODE_TRAILS:int = 1;
      public static const RENDER_MODE_FALLING:int = 2;
     
      private var particlesContainer:DisplayObject3D;
      private var particlesHolder:Particles;
      private var particles:Array;
      private var boundsCube:Cube;

      private var bfx:BitmapEffectLayer;
     
      private var _renderMode:int;
      public function set renderMode(value:int):void
      {
         if (value == _renderMode) return;
         
         clearBitmapEffects();
         
         var clippingPoint:Point = new Point();
         
         switch (value) {
            case RENDER_MODE_CLEAN:
               // nothing - effects already cleared above...
               break;
            case RENDER_MODE_FALLING:
               clippingPoint.y = -2;
               // fall through...
            case RENDER_MODE_TRAILS:
               bfx = new BitmapEffectLayer(viewport, stage.stageWidth, stage.stageHeight, true, 0xffffff);
               
               bfx.addEffect(new BitmapLayerEffect(new BlurFilter(2, 2, 2)));
               bfx.addEffect(new BitmapColorEffect(1, 1, 1, .9));
               
               bfx.clippingPoint = clippingPoint;
               
               bfx.addDisplayObject3D(particlesHolder);
               
               viewport.containerSprite.addLayer(bfx);
               break;
            default:
               throw new Error(value + ' is an invalid render mode');
         }
         _renderMode = value;
      }
     
      private var _displayCube:Boolean = true;
      public function set displayCube(value:Boolean):void
      {
         if (value != _displayCube) {
            _displayCube = value;
            boundsCube.visible = value;
         }
      }

      public function ParticlesCube()
      {
         super(550, 550);
         
         stage.quality = StageQuality.MEDIUM;
         
         particlesContainer = new DisplayObject3D();
         scene.addChild(particlesContainer);
         
         var cubeMaterial:WireframeMaterial = new WireframeMaterial(0x0000ff, 1, 2);
         var materialsList:MaterialsList = new MaterialsList();
         materialsList.addMaterial(cubeMaterial, 'all');
         
         boundsCube = new Cube(materialsList, CONTAINING_CUBE_SIZE, CONTAINING_CUBE_SIZE, CONTAINING_CUBE_SIZE);
         particlesContainer.addChild(boundsCube);
         displayCube = false;
         
         particlesHolder = new Particles();
         particlesContainer.addChild(particlesHolder);
         
         init(NUM_PARTICLES, CONTAINING_CUBE_SIZE);
         
         stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
         
         startRendering();
      }

      public function init(numParticles:int, containingCubeSize:int):void
      {
         var movingParticle:MovingParticle;
         
         if (particles) {
            particlesHolder.removeAllParticles();
         }
         
         particles = [];
         
         var i:int = numParticles;
         while (i--) {
            movingParticle = new MovingParticle(containingCubeSize);
            particlesHolder.addParticle(movingParticle);
            particles.push(movingParticle);
         }
         
      }

      override protected function onRenderTick(event:Event = null):void
      {
         // move each particle
         var movingParticle:MovingParticle;
         for each (movingParticle in particles) {
            movingParticle.position();
         }
         
         // twist the container based on mouse position
         particlesContainer.rotationY+=((stage.stageWidth/2)-mouseX)/200;
         particlesContainer.rotationX+=((stage.stageHeight/2)-mouseY)/200;
         
         // render
         super.onRenderTick(event);
      }
     
      private function clearBitmapEffects():void
      {
         if (bfx) {
            viewport.containerSprite.removeLayer(bfx);
            bfx = null;
         }
      }
     
      private function onKeyDown(event:KeyboardEvent):void
      {
         switch (String.fromCharCode(event.keyCode)) {
            case '1':
               renderMode = RENDER_MODE_CLEAN;
               break;
            case '2':
               renderMode = RENDER_MODE_TRAILS;
               break;
            case '3':
               renderMode = RENDER_MODE_FALLING;
               break;
            case 'c':
            case 'C':
               displayCube = !_displayCube;
               break;
         }
      }
   }
}

import org.papervision3d.core.geom.renderables.Particle;
import org.papervision3d.materials.special.ParticleMaterial;

internal class MovingParticle extends Particle
{
   
   public static const PARTICLE_SIZE:int = 10;
   public static const MAX_SPEED:int = 5;
   
   private var dX:Number;
   private var dY:Number;
   private var dZ:Number;
   private var halfSize:Number;

   public function MovingParticle(containingCubeSize:int)
   {
      var mat:ParticleMaterial = new ParticleMaterial(0xff0000, 1, ParticleMaterial.SHAPE_CIRCLE);
      super(mat, PARTICLE_SIZE);
     
      var size:int = containingCubeSize;
      halfSize = size / 2;
     
      x = (Math.random() * size) - halfSize;
      y = (Math.random() * size) - halfSize;
      z = (Math.random() * size) - halfSize;
     
      dX = Math.random() * MAX_SPEED;
      dY = Math.random() * MAX_SPEED;
      dZ = Math.random() * MAX_SPEED;
     
   }
   
   public function position():void
   {
      x += dX;
      if (x > halfSize || x < -halfSize) dX *= -1;
      y += dY;
      if (y > halfSize || y < -halfSize) dY *= -1;
      z += dZ;
      if (z > halfSize || z < -halfSize) dZ *= -1;
   }
}
// This line is just to stop the code formatter on my blog getting confused! >


New multiplayer papervision game


I’ve just released my entry into the Nonoba Multiplayer API Kick Off competition. It’s a multiplayer take on the old memory cards game where you have to turn over pairs of cards and try to remember what was under each one. The multiplayer aspect makes it much more frantic and fun as other players are looking at the same cards at the same time as you and you don’t want them to steal your pairs!

It will probably make more sense if you try it out yourself so go ahead and click the image below to play multiplayer memory mayhem!
My good friend Leigh Kayley did the designs (including illustrating all of the cool animals) and I built the game using Papervision, PureMVC, GTween and the Nonoba multiplayer API.

While I was building the game I did a little prototype for the score board transitions using papervision which you can see by clicking the image below. Click anywhere in the movie to give a random player some random points (and so maybe rearrange the scores) and press any keyboard key to toggle some mouse following behaviour.

Papervision 3D score panels test

Unfortunately in the game you can’t really see the 3d transitions on the scores so I thought I’d upload this for people to look at. And I’ve also uploaded the source code for anyone who is interested. It’s probably not the best because it was a prototype stuck together quickly but it may be useful to someone…

Update:

I’m pleased to say that Multiplayer Memory Mayhem won third prize in the multiplayer kickoff competion on Nonoba… Yay!


Wave Sequencer experiment


A couple of weeks ago I attended the FitC Amsterdam conference which was great fun. One of the most inspiring sessions I saw was Andre Michelle’s “Making REAL Music Within Flash” where he talked us through generating sound in flash using the infamous flash sound hack and then showed us how he is redefining what should be possible in flash by building the hobnox audio tool.

I travelled to Amsterdam by train and ferry which meant that I had some spare time on the way back to play and luckily Andre and Joa Ebert have released the complex code behind the flash sound hack as part of their popforge open source library.

So I built my Wave Sequencer. The idea is that it loads in a wav file and splits it into 16 equally sized chunks. You can then re-arrange these chunks to create new variations on the beat. Each 16 chunk section is a pattern and you can create up to 16 different patterns by choosing different patterns from the “pattern bank” at the bottom of the screen.

The loop I’m using is the famous Amen break and I’ve sped it up a bit (and given you a slider so you can control the speed yourself). If you play around with it you’ll see you can slice and dice your own old skool jungle riddims :)

Screenshot of Wave Sequencer Experiment

Note: If you are experiencing choppy audio then you are probably running the latest revision of the flash player (9.0.115.0) which breaks onSoundComplete. So join the petition and ask Adobe to make some noise. I’ve compiled a standalone version of the app against the older 9.0.28.0 player and you can download the PC standalone version which doesn’t have the audio issues.

Obviously this little experiment is very rough around the edges and there is a lot that can be done to improve it but I’ve been too busy with “real work” to look at it in the weeks since I got back from FitC and I thought it was better to publish it as is than to leave it to get lost on my hard drive…

My code here is really simple, all of the complex stuff is done by the popforge library. So big thanks to Andre for the inspiration and the sourcecode :) Hopefully I’ll find some time in the coming months to take this a lot further and to turn it into an AIR app which is actually useful!



Wellcome Trust competition


A few months back I heard about a competition being organised by Ico Design, BD4D and the Wellcome Trust. The idea behind this competition was to “produce an interactive Flash piece that is playful and engaging using at least 2 words from the Word Soup”. The “Word Soup” is a collection of words that form part of the branding for the Wellcome Collection.

I managed to scrape together a little bit of time so I put together an entry. It was a mashup between the provided words and Flickr. I built it using Flashr and skinned it with a nice clean design knocked up by my good friend Leigh Kayley (based on the look and feel of the scramble game we found on the Wellcome collection’s site).

The entry is described quite succinctly in an article in Design Week so I’ll borrow their words:

Kelvin Luck asks us to look at images plucked from Flickr and choose from the wordsoup which one was used to tag the picture. It is harder than you think: what word could describe a masked woman in a spangly bikini who is wielding a chainsaw? Clue: it is not what you might think.

If that doesn’t explain it then you can play it yourself and see if you can get on the high scores. It can be frustrating at times because some people tag their photos a bit randomly but that adds to the adictiveness :)

Last night I was invited to the launch event for the new online exhibits site (where all of the competition entries are showcased) and the announcement of the competition’s winners. I was very surprised and pleased to find out that my entry got the second prize! Yay :)

I’d recommend checking out the site to see the many cool entries and the very nice innovative interface for finding them (also courtesy of Ico Design)



Flash on the Beach and some Perlin Noise


Last week I went to the Flash on the Beach conference and as has been said by many people, it was amazing.

I decided to go for more of the inspirational rather than technical sessions and saw some absolutely amazing speakers including (in order of appearance): Hoss Gifford, Joshua Davis, Brendan Dawes, Craig Swann, Mario Klingemann, Robert Hodgin, Erik Natzke, Chris Allen, Dr Woohoo, Andre Michelle, Marcos Weskamp and Jared Tarbell. Wow!

And as well as all these amazing talks there was the opportunity to meet loads of friends - old and new - and to chat about geeky stuff. And then there was all of the booze and the parties… And a girl in a box!

Anyway, one particular example from Robert got me thinking and wanting to play… He mentioned the PerlinNoise function and talked about how it could be used to simulate flocking. And he showed a slide where a perlin noise image had been used to set the rotation of a set of arrows displayed on top of it. I wanted to play with this and had a little bit of time over the weekend so I managed to come up with this:

My perlin creatures

It’s really simple stuff - you can get the sourcecode and see the process that it evolved through here. The code isn’t beautiful or optimised as I was trying to concentrate on playing rather than doing it right (a concept which seemed to be a recurring theme of the conference).

And I wasn’t the only one who got interested by Robert’s mention of perlin noise. Seb Lee-Delisle posted on Sunday about 3D Perlin Noise in Flash - it looks like a really interesting way to take this further. I can’t wait to see what he comes up with and hopefully to find some more time to play with it myself :)



Flash CS3 trial breaks Juniper Network Connect


It’s been a long time since I’ve posted here because I have been crazily busy working on a massive flex site with a great team over at Tribal DDB London. Hopefully it will go live at the start of next year so I can talk more about it. In the meantime, here is a quick tip for anyone having trouble connecting to a VPN powered by Juniper software’s Network Connect after installing the trial version of Flash CS3.

After installing a trial of Flash CS3 I noticed that I became unable to connect to the VPN. Network Connect would connect briefly and then pop up an alert saying “The Network Connect session terminated. Do you want to reconnect? (nc.windows.app.23711).”

After a bit of googling I found out that there were complaints about the early betas of CS3 and the Bonjour service that they installed affecting networking. And this is what was causing my problem. If you press Ctrl-Alt-Del and look at your processes then you will find a “mDNSResponder.exe” service running after installing Flash CS3. Ending this process allows you to connect through Network Connect.

You will have to do this every time you have restarted your computer as the process is automatically started with the system. Alternatively you can stop the service which starts the mDNSResponder process.

Go to Start Menu > Run and type in “services.msc” to open the services configuration panel. Look for a service with a name something like “##Id_String1.6844F930_1628_4223_B5CC_5BB94B879762##” (I think the bit after the dot will change on each computer). To confirm it’s the correct service double click on it and look at the “Path to executable” - it should be “C:\Program Files\Bonjour\mDNSResponder.exe”. If it is then change the “Startup type” to “disabled” and press the “Stop” button. You should now be able to connect to your VPN.

As far as I can tell, neither of these solutions have had a detrimental effect on my trial install of Flash so I think that they are safe changes to make but no guarantees! Hopefully the information will save someone the time it took me to find the answer.



Flashconference rocked!


At the end of last week I visited Stuttgart, Germany for FlashConference and I have to say it was wicked! A week later I’ve finally found the time to write a quick overview of the conference…

First up a massive thanks to Mario for getting me on the guest list and motivating me to make the journey - it was definitely worth it. A top notch list of very inspiring speakers - if I had any complaints it would only be that they tried to fit so much into one day so there was no space between the sessions (not even for lunch!).

The day started with a bit of an infomercial from Adobe, nothing particularly new although I’d never seen Scout before and it was a pretty nifty apollo sample app (basically a web browser with some of the features from Firebug and the web developer toolbar built in).

Then Ilias Sounas gave a talk showing how he had made some of his animations in Flash and talking about the importance of telling a good story. He also screened some of his animations - Circle of life was my favourite.

Next up Peter Elst gave an insight onto developing Flash for alternative devices. Everything from PSPs and Nintendo Wiis to Chumbys and other strange devices. Very interesting stuff and an area I definitely want to find some excuses to do some work in. Luckily Peter also had a bunch of ideas for how to convince your clients that building flash stuff for these devices is a good idea so maybe I’ll be able to use those ideas to convince some clients…

Then Claus Wahlers gave a talk about the Flash CS3 components which he helped to build. They definitely look like they are a major step forwards from all the previous ones especially in terms of how easy they are to extend and skin. I’m looking forward to getting my copy of CS3 and playing with them…

I skipped the next session (”Mercedes-Benz E-Class Experience Paris-Beijing”) because I needed to get some lunch! So did quite a few other people and so I got to meet up with Ralph, Asger, Martin, Peter, Claus and Wouter.

After lunch rushed back in for Mario’s session: “The blind Sketchmaker” - Artificial Creativity and Evolutionary Art in Flash. He started by discussing whether computers could create Art. After deciding that this wasn’t the case (as Art is more than just a picture - it’s a complete belief system around it) he went on to prove that he certainly could! He wrote an AS3/ Apollo app which could analyse pictures by many different criteria and learn rules about what makes a “good” picture. The program could then evolve it’s own pictures applying these rules to decide which direction to evolve in. He then got some of these generated pictures painted as full size oil paintings courtesy of a company in China who sent him some spam. Amazing, inspiring stuff - check out the finished artworks on flickr.

Then came Marcus Weskamp who talked about his experience building Wieden and Kennedy’s site. This site needed a way to display the hundreds of jobs this advertising agency has done and Marcus put his data visualisation skills to good use creating a timeline and 3d nodemap showing how all the projects, offices, people and clients were related to each other. Especially interesting was how the idea for the site evolved as a result of meetings between Marcus and his client.

Next Holger Eggert gave an insight into creating animated films in Flash. He gave an insight into how to construct a good story and use Flash/ Photoshop/ After Effects to make it real and he showed some of his work (and work in progress).

Finally, Andre Michelle gave an overview of advanced audio programming in Flash together with a hack which makes it possible to basically write your own samples in Flash and play them back. He demo’d a bunch of little apps he’s built from 303 simulators to little drum machines to .mod file players and tested the conference sound system while he was at it! Again, amazingly inspiring stuff, I need to find some time to play with all of this!

After that we all managed to get into the speakers dinner/ drinks and thus started a big night in Stuttgart… But that’s a different story!

As I said, the conference was really inspiring and it was great to get to meet all of the people involved too. Now that the hangover has died away I’m looking forward to finding some time to play with some of the new ideas I’ve got and looking forward to next years conference!



Dynamic shared fonts in Flash


Shared fonts in Flash have always promised a great deal but failed to deliver due to a confusing and buggy implementation.

Recently I built a site which made extensive use of shared fonts. To make the site load smoothly it is made up of a number of swfs - one for each section. And rather than embedding Helvetica Nueue Bold Condensed (the font used for navigation and headings) in each swf I used a shared font. This means that the 17kb of font information is only loaded once and is then cached and shared between swfs. This seemed to work exactly as advertised with none of the bugs I remember from previous attempts - maybe something was fixed in the Flash Player?

So far so good. But then I ran into a problem… The site is available in a variety of locales including Russia and Poland. These require different versions of the font (the Cyrillic and Central European respectively). The problem is that Flash doesn’t provide a way to dynamically choose a shared font library to load - the location of the shared font is hard coded in the library item in your FLA.

So what do you do? Abandon shared font libraries and dynamically load a different font swf dependant on the locale and use actionscript to apply a text format referencing the correct font to all of the relevant text fields? Pay for the Shared Fonts Manager? Neither seemed like a nice solution so I tried something else…

Instead of pointing your shared font directly to an swf you instead point it to a serverside page (of the technology of your choice - the site above uses .NET while the example below uses PHP). This page reads the current locale from a cookie and passes the correct font swf back based on this value. Surprisingly enough this seems to work perfectly and allows Home of HD to work across different locales while providing only the necessary font to each user.

An example is probably worth a thousand words so you can see this technique in action and download the source files if you want to see how it’s done.

The only other issue is that you want your fonts to show up while developing. No problem. Just rename the default font swf file to the name of the serverside script (e.g. fonts.php in my example) and magically it works. Just remember not to overwrite your actual serverside script with this dummy one when you upload your changes!

Update

While writing this article and looking for links about shared fonts I stumbled across a post by Mario Klingemann where he suggests this exact technique. Back in 2003! Since I already wrote the examples and article I thought I might as well publish it but kudos to Mario for coming up with the idea years before me!



Automatically generating exclude.xml files


I’ve recently been working on a project where a number of swf’s use common classes. The initial swf loaded contains most of these classes but so do the child swfs it loads into itself. This is because they need to be able to call methods in and dispatch events to classes in the main swf. Importing the classes gives me compile time type checking and auto complete (in FDT - my editor of choice).

So I discovered excude.xml files. These allow you to specify classes which you don’t want to be compiled into a given swf. Since all AS2 classes live in the _global scope you can share them between different swf’s. So if your initial swf includes a class that you use in a child swf you load into it then compiling that class into the child swf is redundant.

exclude.xml files are easy enough to understand - simply create a file with the same name as your fla but with _exclude.xml added on (e.g. nav.fla would have an exclude file called nav_exclude.xml) and in it list all the classes you want to exclude from compilation into that swf.

The problem with exclude.xml files is when a class you are excluding has a dependency on other classes the dependant classes aren’t automatically excluded. And manually figuring out the dependency tree in any non-trivial application is tricky to say the least. So what we need is a way to find all of the classes which are included in a given swf. You can then use this to generate an exclude list for any other swfs that will be loaded after it.

I found a program which sounded like they might do just that: sexieR on OSFlash. Unfortunately I got put off by it’s requirements and and didn’t get around to finding out if it could do what I wanted.

Then I realised I could generate the exclude list very easily myself from Actionscript! Since all classes are instantiated as Objects in the _global namespace we can simply recursively loop over this namespace and note all the classes we come across. So I wrote my “IncludedClasses” class, which does the above and outputs a string for you to copy and paste into an exclude.xml file. You simply have to temporarily include this line in your “master” fla (the one which contains the classes you want to access from other flas):

trace(com.kelvinluck.util.IncludedClasses.getInstance().getExcludeXml());

Then in the output window you will find a string which you can copy and paste into an XML file with the correct name and then - bobs your uncle - you’re sorted :)

The IncludedClasses class is shown below or you can download it here.

/**
* Class: IncludedClasses
*
* @author KLuck
*/

class com.kelvinluck.util.IncludedClasses
{
   
   private static var instance:IncludedClasses;
   
   /**
   * Private constructor - Singleton
   **/

   private function IncludedClasses()
   {
     
   }
   
   function getExcludeXml():String
   {
      var classes:Array = _arrayUnique(_getClasses(_global, '', []));
      var r:String = '<excludeassets>' + newline;
      for (var i:Number=0; i<classes .length; i++) {
         r += '<asset name="' + classes[i] + '">' + newline;
      }
      r += '</classes></excludeassets>';
      return r;
   }
   
   function getArrayString():String
   {
      var r:String = '["' + newline;
      r += _arrayUnique(_getClasses(_global, '', [])).join('", "');
      r += '"]';
      return r;
   }
   
   private function _getClasses(obj:Object, path:String, classes:Array):Array
   {
      var ret:Array = [];
      for (var name:String in obj) {

         if (typeof(obj[name]) == 'function') {
            var firstLetter:String = name.substr(0, 1);
            if (firstLetter.toUpperCase() == firstLetter) {
               classes.push(path + '.' + name);
            }
         } else {
            var passPath = path == '' ? name : path + '.' + name;
            classes = classes.concat(_getClasses(obj[name], passPath, classes));
         }
      }
      return classes.concat(ret);
   }
   private function _arrayUnique(a:Array):Array
   {
      var o:Object = {};
      for (var i:Number=0; i<a .length; i++) {
         o[a[i]] = true;
      }
      var r:Array = [];
      for (var i:String in o) {
         r.unshift(i);
      }
      return r;
   }
   
   
   /**
   * @return singleton instance of IncludedClasses
   */

   public static function getInstance():IncludedClasses
   {
      if (instance == null)
         instance = new IncludedClasses();
      return instance;
   }
   function toString():String
   {
      return '[com.kelvinluck.util.IncludedClasses]';
   }
   
}

So simply run this script in your “master” fla (or library fla) and then create exlude files for all your “slave” fla’s. In the project I’m working on this knocked 30-40KB of each of the included swf’s which makes it definitely worthwhile.