Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411423 Posts in 69363 Topics- by 58416 Members - Latest Member: JamesAGreen

April 19, 2024, 04:08:44 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityTownhallForum IssuesArchived subforums (read only)Tutorials(AS3) Playing music (mod/xm) with Flod 3
Pages: [1]
Print
Author Topic: (AS3) Playing music (mod/xm) with Flod 3  (Read 7930 times)
pgil
Guest
« on: March 31, 2012, 04:00:58 PM »

PART I:  The Basics

Want to see hear how FLOD sounds? Check out my FLOD 3 Demo.

NOTE: Christian Corti just released FLOD 4. Like, just yesterday. This tutorial still works with the new version, though! Just copy the code from the "Flod 4.0" folder of the archive instead of "Flod 3.0".

I thought I'd write a tutorial showing how to play .xm and .mod music files using Christian Corti's Flod 3 library. Flod is really great, but the latest version doesn't really have any documentation, and there's no good "Getting Started" guide that I could find.  mod and xm files are a great option for browser and mobile games, because they can pack a lot of music into a small file size.

If you don't care about reading the whole tutorial, you can downloaded the completed FlashDevelop project here.



STEP 1: Downloading Flod

First, we need to download the Flod source. We can get that HERE: https://github.com/photonstorm/Flod

Near the top of that page, on the right side, you'll see a tab that says Download.  Click that, and download the source as a .zip archive.

Once the file's downloaded, open it up. Within the archive, you'll see a folder called Photonstorm-Flod-blablablabbbab.... And inside THAT, you'll find more folders:



Flod 2.0 Archive - contains old versions of Flod. We don't care about these.
Flym - Plays YM files, an old music format from Atari ST computers. We don't care about it for this tutorial.
Media - Contains some PNG files with the FLOD logo. Again, don't care.
Sample Tunes - Contains some songs that you can use for testing.  You can use the .mod files in this tutorial if you like...

Flod 3.0 Flod 4.0 - This has all the code we'll be working with.

Open the Flod 3.0 folder. Inside it you'll find a folder called Neoart.  Copy Neoart into your project's src folder:




STEP 2: Adding Flod to Your Project

Now that you've got Flod in your source folder, you'll need to decide what class you'd like to play your songs from. For my games, I usually have an Assets class in the top level, where I embed all my graphics, sounds, and things like that as static constants. I like doing this because it lets me access all my assets globally:


A typical Assets class


I'm going to embed all my Flod songs, and also create a function to play them, in this class.

First, we need to import some of Flod's classes, plus Flash's Bytearray class:

Quote
package  
{

   public class Assets
   {
      import neoart.flod.core.CorePlayer;
      import neoart.flod.FileLoader;
      import flash.utils.ByteArray;
      
      public function Assets()
      {
         
      }
      
   }

}


Now we'll create some variables to help Flod:

Quote
package  
{

   public class Assets
   {
      import neoart.flod.core.CorePlayer;
      import neoart.flod.FileLoader;
      import flash.utils.ByteArray;

      //Variables for Flod 3:
      public static var
      player      : CorePlayer,
      stream      : ByteArray,
      flodisplaying   : Boolean;
      ;   
   

      public function Assets()
      {
         
      }
      
   }

}

"stream" is where we'll store the data of the currently playing module.  "player" is the... thing that plays it.  "flodisplaying" tells us if Flod is playing something.


STEP 3: Embed some songs!

Before we can use Flod, we need some songs to play, right? If you want the songs I'm uisng, download the completed project and look in the Assets folder.

I'm using one .mod, and one .xm, and I've placed them a subfolder within my project folder called assets:



Now we just need to embed them in our Assets class:

Quote
package  
{

   public class Assets
   {
      import neoart.flod.core.CorePlayer;
      import neoart.flod.FileLoader;
      import flash.utils.ByteArray;

      //Variables for Flod 3:
      public static var
      player      : CorePlayer,
      stream      : ByteArray,
      flodisplaying   : Boolean;
      ;   

      [Embed(source="../assets/zabutom - ergo-kalyptic chase.xm", mimeType="application/octet-stream")]
      public static const Song_Chase:Class;   
      
      [Embed(source="../assets/vim-pot_boiled_lunch.mod", mimeType="application/octet-stream")]
      public static const Song_Lunch:Class;

      
      public function Assets()
      {
         
      }
      
   }

}

You'll notice there are 2 dots ".." at the beginning of the file's path. That's because by default, the class looks in its own folder for files to embed.  The ".." tells it to move up one level, so you can fine the assets folder.

STEP 4: Make a Global Function to Play Songs!

Now let's make Flod work. We're going to create a function in our Assets class called "playSong:"

Quote
package  
{

   public class Assets
   {
      import neoart.flod.core.CorePlayer;
      import neoart.flod.FileLoader;
      import flash.utils.ByteArray;

      //Variables for Flod 3:
      public static var
      player      : CorePlayer,
      stream      : ByteArray,
      flodisplaying   : Boolean;
      ;   

      [Embed(source="../assets/zabutom - ergo-kalyptic chase.xm", mimeType="application/octet-stream")]
      public static const Song_Chase:Class;   
      
      [Embed(source="../assets/vim-pot_boiled_lunch.mod", mimeType="application/octet-stream")]
      public static const Song_Lunch:Class;
      
      public function Assets()
      {
         
      }

      public static function playSong(Source:Class):void {

      }

      
   }
}

We want to be able to access this function from anywhere we want, so we made it a public static function.  The function ony takes one argument, "Source".  "Source" will be whatever .mod or .xm file we want to play.  Now we'll write some stuff in the function:

Quote
      public static function playSong(Source:Class):void {
               var loader:FileLoader = new FileLoader();
               stream = new Source as ByteArray;
               player = loader.load(stream);
               player.play();

      }

Now we're ready to test this thing!  Since we can call our function from anywhere, let's try playing a song right when the game starts, and do it from our Main function:

Quote
package
{
   import flash.display.Sprite;

   public class Main extends Sprite
   {
      
      public function Main():void
      {
         Assets.playSong(Assets.Song_Lunch);
      }
      
   }
   
}

Now click the Test Project button...

...If you did everything right, you'll hear music. Pretty cool, right?

That's not enough, though!  You want to be able to play more than one song. Let's add a keyboard event, so that you can pick a song to play by pressing the number keys:

Quote
package
{
   import flash.display.Sprite;
   import flash.events.Event;
   import flash.events.KeyboardEvent;
   import flash.ui.Keyboard;

   
   
   public class Main extends Sprite
   {
      
      public function Main():void
      {
         Assets.playSong(Assets.Song_Lunch);
         stage.addEventListener(KeyboardEvent.KEY_DOWN,keyboardDown)
      }
      
      private function keyboardDown(e:KeyboardEvent):void {
         if (e.keyCode == Keyboard.NUMBER_1) {
            Assets.playSong(Assets.Song_Lunch);
         }
         if (e.keyCode == Keyboard.NUMBER_2) {
            Assets.playSong(Assets.Song_Chase);
         }
      }

   }
   
}

With this, you can press 1 to hear "Pot-Boiled Lunch," and 2 to hear "ergo-kalyptic chase".

Don't test it yet, though! Flod doesn't automatically stop the currently playing song before it plays a new one, so we need to do this from our playSong function. Open up your Assets class and add this:

Quote
      public static function playSong(Source:Class):void {
               if (flodisplaying == true) player.stop();
               var loader:FileLoader = new FileLoader();
               stream = new Source as ByteArray;
               player = loader.load(stream);
               player.play();
               flodisplaying = true;
      }

Remember that flodisplaying variable we created before? We'll set that to true whenever we start a new song.  We'll also check it before we start playing, so that IF a song is already playing, we stop it.

Now test your program. Press 1 or 2 to play either song.

There's one more thing we need to add: A global function to stop songs.  That's super easy! Just add this to your assets class:

Quote
      public static function stopSong():void {
         player.stop();
         flodisplaying = false;
      }

Now we can add a Stop key to our main class:

Quote

      private function keyboardDown(e:KeyboardEvent):void {
         if (e.keyCode == Keyboard.NUMBER_1) {
            Assets.playSong(Assets.Song_Lunch);
         }
         if (e.keyCode == Keyboard.NUMBER_2) {
            Assets.playSong(Assets.Song_Chase);
         }
         if (e.keyCode == Keyboard.S) {
            Assets.stopSong();
         }

      }

Now when you test it again, you can stop the music with the S key.

You can download the complete project here: Flod Tutorial Part 1 (for FlashDevelop)

Some other things you should know:
-Songs with a lot of channels are pretty heavy on the cpu.  I wouldn't use more than 10 channels for a typical game (although you can probably get away with 16-20 channels if you're not doing a lot of other cpu-intensive stuff).
-Playback of some songs can be buggy.  Some things like vibratto and pitch slides can sound different in Flod than they do in the program you  compose with.
-Flod doesn't correctly play .mod songs saved in MilkyTracker.
-Flod throws a lot of warnings when you compile, because a lot of its internal variables have no type declaration.  I don't think this will cause any issues, but it's something to be aware of.

That should be enough to get you started.  For our next tutorial, I'll show you how to change some of Flod's settings, and add volume control.

I might even do a little bit about writing songs in MilkyTracker, but until then, check out 01010111's XM Template with NES Samples. It's a great way to get started.
« Last Edit: April 02, 2012, 05:56:44 AM by pgil » Logged
pgil
Guest
« Reply #1 on: March 31, 2012, 04:07:43 PM »

PART II:  Changing Settings
Download the finished project (FlashDevelop)

STEP 1: Looping

By default, Flod doesn't loop songs... Well, it does if the song contains its own loop commands.  Otherwise, when the song reaches the end, it stops.

To make songs loop automatically, we just change an option before we play them. Go to your Assets class, to the playSong function, and add the following:

Quote

      public static function playSong(Source:Class):void {
               if (flodisplaying == true) player.stop();
               var loader:FileLoader = new FileLoader();
               stream = new Source as ByteArray;
               player = loader.load(stream);
               player.loopSong = 1;   
               player.play();
               flodisplaying = true;
      }

Now all your songs will loop by default!  When playing .mod and .xm tunes, you'll notice one big advantage over MP3s:  looping is completely seamless.  Flod treats every loop point as if it's just playing another measure of the song. Notes with a long decay can keep playing when the song loops, and you also won't hear that little gap of silence that you normally get with MP3 files.


STEP 2: Volume, and Flod's Guts

Now that we can play and loops songs, it might be nice to be able to control the volume.  The FLod player does have a "volume" variable that we can change, but as you'll find out, it won't affect all our music.  Let's change the volume in out playSOng function:

Quote

      public static function playSong(Source:Class):void {
               if (flodisplaying == true) player.stop();
               var loader:FileLoader = new FileLoader();
               stream = new Source as ByteArray;
               player = loader.load(stream);
               player.loopSong = 1;
               player.volume = .1;   
               player.play();
               flodisplaying = true;
      }

Test your project.  Now that you've changed the volume, the .mod song will play much more quietly than before.  That's good!

Now play an .xm song... Gah! It's still playing at full volume! 

Yeah, for whatever reason, Flod's .xm player doesn't listen to the core player's volume control.  But don't worry, we can fix this!

What we're going to do is ignore Flod's built-in volume control.  We'll make our own SoundTransform object in Flod's core player, and change the volume through that.



In FlashDevelop's project tree, open up neoart>flod>core>CorePlayer.as.  At the top of that class, add this line:

Quote

   package neoart.flod.core {
      import flash.events.*;
      import flash.media.*;
      import flash.utils.*;
      import neoart.flip.*;
      public class CorePlayer extends EventDispatcher {
         
         private var transform:SoundTransform = new SoundTransform(1, 0);

         public static const
            ENCODING : String = "us-ascii";
         public var
            quality   : int,
            record    : int,
            .
            .
            .

Now scroll down a little, to public function play. Look for a line thatt says "soundChan = sound.play(soundPos)". It should be around line 101.  Change it to this:

Quote
    public function play(processor:Sound = null):int {
      if (!version) return 0;
      if (soundPos == 0.0) initialize();
      sound = processor || new Sound();
      if (quality && (hardware is Soundblaster)) {
        sound.addEventListener(SampleDataEvent.SAMPLE_DATA, hardware.accurate);
      } else {
        sound.addEventListener(SampleDataEvent.SAMPLE_DATA, hardware.fast);
      }
      soundChan = sound.play(soundPos,0,transform);
      soundChan.addEventListener(Event.SOUND_COMPLETE, completeHandler);
      soundPos = 0.0;      return 1;
    }

Now when our songs play, they will know to listen to that SoundTransform object.

We're not done yet, though.  Make a new function in CorePlayer called setvolume:

Quote

    public function stop():void {
      if (!version) return;
      if (soundChan) removeEvents();
      soundPos = 0.0;
      reset();
    }
      public function setvolume(_vol:Number):void {
      if (soundChan != null) {
         var _transform:SoundTransform = soundChan.soundTransform;
         _transform.volume = _vol;
         soundChan.soundTransform = _transform;
         soundChan.soundTransform.volume = _vol;
      }
      else transform.volume = _vol;
   }

   
    public function process():void { }
    .
    .
    .

Now we can set the volume in out Assets.playSong function:

Quote
      public static function playSong(Source:Class):void {
               if (flodisplaying == true) player.stop();
               var loader:FileLoader = new FileLoader();
               stream = new Source as ByteArray;
               player = loader.load(stream);
               player.loopSong = 1;
               player.setvolume(.1);
               player.play();
               flodisplaying = true;
      }

Now test it.  All your songs will be 1/10th their original volume.


It would be nice to be able to control the volume in-game though, right?  A lot of games let you control the master volume by pressing the + and - keys.  Let's go to Main.as, and add a variable called MasterVolume:

Quote
   public class Main extends Sprite
   {
      public static var MasterVol:int = 10
      
      public function Main():void
      {
      .
      .
      .

Flash represents volume levels as a Number between 0 and 1.  In order to prevent rounding errors, I'm representing the Master Volume as an integer (0-10). I'll just divide by 10 when I pass that value to any SoundTransform object. Let's add out volume control keys, and make them set the volume of Flod:
Quote

      private function keyboardDown(e:KeyboardEvent):void {
         if (e.keyCode == Keyboard.NUMBER_1) {
            Assets.playSong(Assets.Song_Lunch);
         }
         if (e.keyCode == Keyboard.NUMBER_2) {
            Assets.playSong(Assets.Song_Chase);
         }
         if (e.keyCode == Keyboard.S) {
            Assets.stopSong();
         }
         if (e.keyCode == Keyboard.NUMPAD_ADD || e.keyCode == Keyboard.EQUAL) {
            if (MasterVol < 10) MasterVol += 1;
            Assets.player.setvolume(MasterVol / 10);
         }
         if (e.keyCode == Keyboard.NUMPAD_SUBTRACT || e.keyCode == Keyboard.MINUS) {
            if (MasterVol > 0) MasterVol -= 1;
            Assets.player.setvolume(MasterVol / 10);   
         }

      }

...And let's change out playSong function in Assets.as so that it uses our master volume too:

Quote
      public static function playSong(Source:Class):void {
               if (flodisplaying == true) player.stop();
               var loader:FileLoader = new FileLoader();
               stream = new Source as ByteArray;
               player = loader.load(stream);
               player.loopSong = 1;
               player.setvolume(Main.MasterVol/10);
               player.play();
               flodisplaying = true;
      }

Now test this thing!  If you did everything right, you should be able to change the volume with the + and - keys.  Pretty cool, right?

STEP 3: A Few Other Settings



When adding loopSong, you may have noticed the CorePlayer has some other options you can change. One important option is quality.  This changes how Flod mixes and renders sounds.  By default, quality is set to 1.  This means the sound is sent through a filter that smooths out the waveform.  This can make some songs sound better, but it comes at a cost-- the filter puts an extra load on the cpu.  It also doesn't make all music sound better.  Chiptunes can sometimes benefit from the aliasing and other artefacts that come from unfiltered digital mixing.  To hear your music without filtering, change the quality setting to zero.

(It's actually hard to hear the difference in the songs I gave you in Part 1, because both the Vim and Zabutom tune use rather sharp waveforms that don't benefit much from the filter. I added a few more songs in the project for Part 2. The Nagz song "My Dog Lives on the Moon" is full of smooth, silky sine waves, and will really show you the difference the quality setting makes). 

Another setting you might want to check out is stereo. Changing this variable will only have an effect on .mod files, and it changes the stereo separation. See, the .mod format originated on an old 16-bit computer called the Amiga.  The amiga had no control over the panning of sounds.  It could only pan entire channels hard left or hard right.  This doesn't always sound so good, especially if you're listening with headphones.  By default, the stereo option is set at 1, full panning.  Set it to .5 to mix the left and right channels a bit. Set it to 0 to play songs in mono.  Again, this doesn't affect .xm tunes, because they have more sophistocated panning controls, and tend to not have the problem thad .mod tunes have.

I also noticed a variable called tempo.  It only seems to work if I call it before playing a song. In other words, you can't change the tempo during playback. It's also overridden by tempo commands in the songs. So, not that useful... Unless we alter Flod some more!

...No... That's for another tutorial.


You can download a FlashDevelop project containing all the stuff we did in part 2 HERE. I also added comments to the code, and a few extra songs.


In Part 3, I'll talk about making your own songs.
Logged
01010111
Level 0
*



View Profile WWW
« Reply #2 on: June 29, 2012, 05:12:50 PM »

I just had to finally register for an account here so I could express my thanks!

I was looking for a way to set XM volume and stumbled on this article. Then I see a link to that NES template I had made a while ago Tears of Joy - so flattered right now.

Anyway, I had to hunt and peck at a couple of points due to an older Flod source, but it worked beautifully.

Thanks so much!
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic