Galactic Blast Released on App World!

I’m very excited to say Galactic Blast is now officially available for purchase from App World!!

Pick it up now and save the galaxy, right from your Blackberry!

And for all the aspiring developers out there, don’t forget to check out the tutorial to make games just like this one.

Screenshots of Our Upcoming Blackberry Game!

Well – I’m very excited. If you’ve been following the Twitter feed at all, you know that I’ve been working at a breakneck pace trying to get Galactic Blast completed. You might recognize the title from the demo game featured in our tutorial on creating a Blackberry game. The commercial release of Galactic Blast is built off a very similar framework to what’s featured in the demo, only with a LOT of extra stuff added – pre-rendered 3d graphics, bonus rounds, weapon upgrades, etc.

So without further ado, screenshots!

The Gamma-3 base - the final boss.

The demolition controller destroys the derelict ships in decommissioned shipyard A-3. In this case, however, he's trying to destroy you! Third boss in the game.

The Sutoran Nebula is home to the Phoraxian Shadow Fleet. They hide within the clouds of the nebula to make detection more difficult.

Every 5 waves your SR-13 kicks it into hyperdrive, and you enter the bonus round. Pass through as many rings as possible for maximum points!

Main menu. Start/Resume game, Instructions, View High Scores, Change settings (Sound/Music/Vibration, etc), and Quit.

Title Screen

I just submitted the application to RIM for approval on App World, so hopefully it checks out and it will be available for download soon! More to come…

Unfortunate News for a Realtime Blackberry Mixer

Overview

Sometimes I’m not sure if it’s a good thing or a bad one, but I have the kind of personality that when I get involved trying to solve a problem, I can’t stop until it’s solved. This works out well as eventually the issue at hand is fixed, but it also has the side effect that I forsake pretty much everything else until I can resolve things. When I can’t find a solution, or there isn’t a good one, it drives me nuts.

And unfortunately, I’m in that irritated mindset now. If you read my post on Mixing Two or More Sounds Together on the Blackberry, you saw the video I posted of a software sound mixer I had written. While the mixer did indeed work, the problem was I had only tested one scenario – where the user has chosen to start mixing all the sounds together at the same time. This use case is no problem.

The Ongoing Problem

The use case that’s an issue (and honestly the whole point of a realtime mixer), is where you start playing one sound, and then mix another one in at an arbitrary time later. The reason why this is an issue is fairly easily explained (and unfortunately not apparent to me until I had written most of the code).

As you know if you’re a Blackberry developer, all user code is written in Java – the BB runs a JVM where all the software runs – that’s how the phone is designed. This creates a layer of abstraction, where the user doesn’t have direct access to the hardware, and where the code runs relatively slow (compared to native instructions). Because of this, RIM provided mappings from the javax package’s Player class to the Blackberry sound hardware.

The Problem of Buffers and Fetching

Player does a number of things – but the core of it retrieves data from a sound stream (a wav file in our case), massages it as necessary, and then loads the sound hardware buffers with the data. The big problem is at the stage when Player retrieves data. It does so in chunks of data – specifically (from my experiments and as other people on the web have reported) in 58000 byte chunks. That means that is takes in a few seconds of sound at a time so the hardware buffers don’t underrun during playback. You can do some tricks with blocking the stream and force it not to load in this full 58000 – and you will immediately hear the effects – choppy audio. The 58000 byte buffer is not to be annoying – it’s necessary for smooth and clean audio.

And this is why we can’t do realtime mixing. We don’t have direct access to the hardware buffers, we only have access to the buffer streams we can feed to Player. And if Player demands a few seconds of audio at each read, we cannot insert a sound into the few seconds that we feed to it after the fact.

An Example of the Problem

I have two sounds. Drums.wav which is a 20 second wav, and flute.wav which is a 5 second wave. I would like to play drums over and over again, and then immediately mix the flute in when the user presses the ‘F’ button.

Player immediately reads in, let’s say 5 seconds of drum.wav. Then at second 3, the user presses the ‘F’ button. Our program can immediately queue in flute.wav into the input stream, but it won’t be played until second 5, since the Player already read in 5 seconds. Even if we do some creative thread blocking, we can never guarantee a real time mix at all times.

The Code (Could Still Be Useful in Certain Applications)

Below I have included all my work on this project. It consists of a “MixerStream”, which is meant to be a replacement to InputStream. You feed an instantiated MixerStream object to Player, and then call the MixerStream “addStream” method to mix other audio files, in real time, into the Player. It does work – but there will be large delays between when the sound is added, to when it is heard, due to the problem described above.

Note – this code is currently hardcoded to only work with one format of PCM stream right now (I put two hardcoded headers of a 44.1khz, 16bit, stereo, and a 44.1khz, 8bit, mono) – in addition to doing a very low tech way of mixing streams together. The method “getMixedByte” is all setup for future functionality (Allowing any format of PCM, doing better mixing, preventing clipping, etc) – but I’m not up to doing any more work with this right now. It only works as a semi-real time mixer, which I don’t think is too useful – the only place where it might be nice is if you’re developing a Blackberry audio-editing application, or something to that effect – where realtime isn’t necessary. If you are doing this – feel free to use the code below – I’ll provide any help I can.

So for now – this project is abandoned. Frustrating, but necessary for now. Hopefully with the new QNX based OS coming out, there will be more support for directly accessing the hardware buffers.

package com.synthdreams;

import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;

import net.rim.device.api.ui.component.Dialog;


// The MixerStream class allows us to add multiple PCM wave streams to mix together in real time
public class MixerStream extends InputStream{
   private Vector _streamVector; // All wave streams currently managed by the mixer
   int[] _headerArray;
   int _headerPos;
   int _totalRead;
   
   
   // WaveStream class is an InputWrapper stream that stores wav audio information
   private class WaveStream extends InputStream {
      private InputStream _fileStream; // The input stream containing the wav data
      private int _sampleRate; // Samplerate of the wave
      private int _bitRate; // Bitrate of the wave
      private int _channels; // Channels in the wave 
    
      public int getSampleRate() { return _sampleRate; }
      public int getBitRate() { return _bitRate; }
      public int getChannels() { return _channels; }
      
      public WaveStream(InputStream passStream) throws IOException {
         byte[] byteArray;
         String tempChunkName;
         int tempChunkSize;
         
         // Assign the file stream, then read in header info
         _fileStream = passStream;

         // 4 - ChunkID (RIFF)
         _fileStream.read(byteArray = new byte[4]);
         if (new String(byteArray).equals("RIFF") == false) {
            throw new IOException("Not a valid wave file (ChunkID).");
         }
         
         // 4 - ChunkSize
         _fileStream.read(byteArray = new byte[4]);

         // 4 - Format (WAVE)
         _fileStream.read(byteArray = new byte[4]);
         if (new String(byteArray).equals("WAVE") == false) {
            throw new IOException("Not a valid wave file (Format).");
         }
         
         // 4 - SubchunkID (fmt)
         _fileStream.read(byteArray = new byte[3]);
         if (new String(byteArray).equals("fmt") == false) {
            throw new IOException("Not a valid wave file (SubchunkID).");
         }
         _fileStream.read(byteArray = new byte[1]);
         
         // 4 - Subchunk1Size
         _fileStream.read(byteArray = new byte[4]);        
         tempChunkSize = ((byteArray[3] & 0xff) << 24) | ((byteArray[2] & 0xff) << 16) | ((byteArray[1] & 0xff) << 8) | (byteArray[0] & 0xff);
             
         // 2 - AudioFormat(1)
         _fileStream.read(byteArray = new byte[2]);
         if (byteArray[0] != 1) {
            throw new IOException("PCM Compression not supported.");
         }

         // 2 - NumChannels
         _fileStream.read(byteArray = new byte[2]);
         _channels = ((byteArray[1] & 0xff) << 8) | (byteArray[0] & 0xff); 
 
         // 4 - Sample Rate
         _fileStream.read(byteArray = new byte[4]);
         _sampleRate = ((byteArray[3] & 0xff) << 24) | ((byteArray[2] & 0xff) << 16) | ((byteArray[1] & 0xff) << 8) | (byteArray[0] & 0xff);
         
         // 6 - Byte Rate, Block Align
         _fileStream.read(byteArray = new byte[6]);

         // 2 - Bitrate
         _fileStream.read(byteArray = new byte[2]);
         _bitRate = ((byteArray[1] & 0xff) << 8) | (byteArray[0] & 0xff); 
         
         // variable - Read in rest of chunk 1
         _fileStream.read(byteArray = new byte[tempChunkSize-16]);
         
         // Burn through unneeded chunks until we get to data
         tempChunkName = "";
         tempChunkSize = 0;
         while (tempChunkName.equals("data") == false) {
            // Read in name and size of chunk
            _fileStream.read(byteArray = new byte[4]);
            tempChunkName = new String(byteArray);
            _fileStream.read(byteArray = new byte[4]);
            tempChunkSize = ((byteArray[3] & 0xff) << 24) | ((byteArray[2] & 0xff) << 16) | ((byteArray[1] & 0xff) << 8) | (byteArray[0] & 0xff);

            // Burn through non-data chunks
            if (tempChunkName.equals("data") == false) {
               _fileStream.read(byteArray = new byte[tempChunkSize]); 
            }
         }
         // End of header
      }
      
      public int read() throws IOException {
         return _fileStream.read();
      }           
   }

   public MixerStream()  {
      _headerPos = 0;
      _totalRead = 0;
      
      // A constructed wav header for a 44100hz, 16bit, stereo wav of maximum length
      /*_headerArray = new byte[] {'R','I','F','F', (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, 
                                 'W','A','V','E', 'f','m','t', 0x20, 0x10, 0x00, 0x00, 0x00, 
                                 0x01, 0x00, 0x02, 0x00, 0x44, (byte)0xAC, 0x00, 0x00,
                                 0x10, (byte)0xB1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00,
                                 'd','a','t','a', (byte)0xDB, (byte)0xFF, (byte)0xFF, (byte)0xFF};*/
      
      // A constructed wav header for a 4410hz, 8bit, mono wav of maximum length
      _headerArray = new int[] {'R','I','F','F', 0xFF, 0xFF, 0xFF, 0xFF, 
            'W','A','V','E', 'f','m','t', 0x20, 0x10, 0x00, 0x00, 0x00, 
            0x01, 0x00, 0x01, 0x00, 0x44, 0xAC, 0x00, 0x00,
            0x44, 0xAC, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 
            'd','a','t','a', 0xDB, 0xFF, 0xFF, 0xFF};
      
      _streamVector = new Vector();
   }
      
      
   // MixerStream will first present a wav header (as documented above), then will mix data
   // from all added streams.  If there aren't any streams, it will block.
   public int read() throws IOException {
      // Increase the total count of bytes read
      _totalRead++;
            
      // First present header       
      if (_headerPos < _headerArray.length) {
         return _headerArray[_headerPos++];    
      }
      else {
         // Mix any streams together
         if (_streamVector.size() > 0) {
            return getMixedByte();
         }
         else {
            // If no streams are available, normally block.  
            // Return 0 during the prefetch process (58000 bytes) so prefetch 
            // doesn't block forever
            try {
               if (_totalRead < 58001) return 0;
               
               synchronized(_streamVector) {
                  _streamVector.wait();
               }
               
               return getMixedByte();
            }
            catch (Exception e) {
               return 0;
            }
         }
      }
   }
   
   // This method will mix all the available streams together, keep track of current
   // playback byte position to accommodate different PCM formats, and normalize the
   // sound mixing.
   private int getMixedByte() throws IOException {
      int tempRead;
      int tempValue;
      
      tempValue = 0;
      
      // Loop through each stream
      for (int lcv = 0 ; lcv < _streamVector.size() ; lcv++) {
         tempRead = ((WaveStream)_streamVector.elementAt(lcv)).read();
         
         // If we're at the end of the stream, remove it.  Otherwise, add it
         if (tempRead == -1) {
            _streamVector.removeElementAt(lcv--);
         }
         else {
            tempValue += tempRead;
         }
      }
      
      // Normalize
      if (_streamVector.size() > 0) {
         tempValue /= _streamVector.size();
         return tempValue;
      }
      else {
         return 0;   
      }
   }
   
   // Queue an audio stream to be mixed by MixerStream
   public int addStream(InputStream passStream) throws IOException {
      WaveStream tempStream;
      
      tempStream = new WaveStream(passStream);
      
      // Make sure this WaveStream is compatible with MixerStream
      // Check for sample rates
      if (tempStream.getSampleRate() != 11025 && tempStream.getSampleRate() != 22050 && tempStream.getSampleRate() != 44100) {
         throw new IOException("Sample rate not supported.  (11025, 22050, 44100)");
      }
      
      // Check for bitrates
      if (tempStream.getBitRate() != 8 && tempStream.getBitRate() != 16) {
         throw new IOException("Bit rate not supported.  (8, 16)");
      }
      
      // Check for channels
      if (tempStream.getChannels() != 1 && tempStream.getChannels() != 1) {
         throw new IOException("Number of channels not supported.  (1, 2)");
      }
      
      // Wave Stream checks out, let's add it to the list of streams to mix
      _streamVector.addElement(tempStream);
      
      try {
         // Notify the read method that there's data now if it's currently blocked
         synchronized(_streamVector) {
            _streamVector.notify();
         }
      }
      catch (Exception e) {
         Dialog.inform("Error notifying _streamVector");
      }
      return 0;
   }
}

Congratulations and Website Updates!

First off, big congratulations to @merman1974, the winner of the Synthetic Dreams Spooktacular Giveaway Contest! We got lots of turnout and made a number of cool, new retro-friends on Twitter.

Next, speaking of Twitter, some website news. I’ve cleaned some clutter off the front page a bit, and added a Twitter feed as well. I’m pretty active with my tweets these days, so I figured it would be nice to carry that over onto the website.

Thanks again everyone!

Shredz64 at NJ Science and Engineering Festival

A quick notice for you in the NJ area tomorrow (10/24/10) – Jeff Brace and the gang at MARCH (Mid Atlantic Retro Computing Hobbyists) will be demonstrating Shredz64 at the NJ Science and Engineering Festival in Clifton, NJ. This is the second time Jeff and MARCH have been kind enough to show off our game and adapter, and it’s really appreciated.

It looks like they’re going to have a lot of very cool technology at the festival, so be sure to check it out.

Player Videos of Shredz64 in Action

It’s always a blast to see people enjoying the game – here are a couple of videos posted by some dedicated Shredz64 players.

The first is a nice and detailed video by Arkanoid_376970 who does much better on Zak McKracken than I could hope to:

The next is by Anders C who was kind enough to bring Shredz64 along to Retrogathering 2010 in Stockhold, Sweden:

Thanks guys! ๐Ÿ™‚

Win a Free PSX64 Interface and Help a Great Cause!

If you’ve been following along on the blog, you’ll know that I recently got a Twitter account. Yeah – I was definitely one of the hold outs. But – I wanted a place to post little tidbits that weren’t really blog worthy, but were interesting none-the-less, and Twitter is the perfect place for that. Plus, I’ve already met some pretty groovy people through it – and it’s a great place to get news out fast. However, Twitter is definitely a more-the-merrier kind of thing, so along that vein, presenting:

The Synthetic Dreams Spooktacular Giveaway!

First, the prize: we’ll be giving away a free PSX64 Interface, along with a copy of Shredz64 to the lucky winner. Additionally, we’ll be donating $50.00 to one of the charities below – to which is by choice of the winner.

Heart to Heart International – Disaster Response and Medical Aid
Global Links – Medical Aid and Health Education
Vitamin Angels – Nutrients for Infants and Children
Books for Africa – Literature and Education for Africa

Not only do you get to rock out to your favorite SID tunes Guitar Hero style and reinvigorate your old C64, Amiga, and Atari games with a Playstation controller – but you also get to help out those less fortunate who could really use a hand.

The Rules

The rules are simple! Follow me, @ToniWestbrook, on Twitter between now and Halloween (October 31st). Once you’re following me, send me a tweet saying you’d like to participate in the contest – and your name will be entered into the drawing! (And I’ll follow you back!)

You can even double your chances to win – after tweeting the above to me, if you then tweet to all your followers:

“RT: @ToniWestbrook Win a free PSX64 to play Guitar Hero on your Commodore 64 while helping those in need! Details: http://bit.ly/cK1YKF”

You’ll be entered twice!

Keep following along, and at midnight (EST) at October 31st, the winner will be announced.

More on the Charities

There are a lot of future scientists, doctors, and engineers waiting to soar, but they may never get the chance without food, medicine, or education. This blog, and Synthetic Dreams as a whole, is about letting people achieve their dreams – but before you can do that, you need your basic needs met – and sometimes you need a helping hand to meet them.

Each of the charities above has been verified with Charity Navigator.

Good Luck Everyone!

Shredz64 – Check It Out at Maker Faire This Weekend!

First off, before I make the plug – if you haven’t checked out MAKE or the Maker Faire before, you TOTALLY should. There are a lot of imaginative people with a ton of amazing, brilliant, or just plain zany creations. It’s very cool stuff.

And speaking of Maker Faire – if you’re around NYC this weekend (Sept 25th and 26th), or are within driving distance, you’re in luck! The New York Hall of Science in Queens will be hosting this awesome event. Still on the fence? Well – time to hop off, because Jeff Brace and the gang at MARCH. (Mid Atlantic Retro Computing Hobbyists) will be demonstrating, amongst other retro goodness, Shredz64. If you haven’t tried it in person yet, now’s your chance.

So enjoy the explosion of engineering this weekend, and get your retro on while you’re at it!

My Newest Robotic Buddy, Vincent (CompuRobot)

It’s not uncommon if you love programming, computers, tinkering, and all things electronic, that you also have a love for robots. I am not unique in this regard! I’m a fan of programmable bots in general (I have a few of the Lego mindstorm robots, including the newer NXT). Recently, my friend Ryan gave me this little guy:

He bears the name “COMPUROBOT”, and as can be seen, shows a striking resemblance to Vincent from Disney’s The Black Hole. For this reason, that’s the name I’ve chosen for him.At first glance, Vincent looks like a simple toy – he has a fun shape, features LEDs for eyes, flashing bulb in his belly, colorful stickers, and a sturdy design. He is driven by two independent wheels below and supported in the front by two smaller wheels. However, there is quite a bit more at play than a simple toy that runs around the room. Examining the top of Vincent reveals a 5×5 Matrix of buttons, each with an icon indicating its function. It turns out Vincent, the Compurobot, is a programmable robot!

Though some of the icons seem to suggest movement, I wasn’t quite sure what all of them meant, or in what the protocol for using them was, so I luckily was able to find the manual online.

Capabilities

Vincent features a 4-bit processor and a small amount of RAM that can hold up to 48 commands. Though its volatile and erases after turning him off, the procedure is very simple for programming. Simply hit the button of the action (forward, backward, turn, play noise, etc), and then the number of seconds you wish him to perform it. E.g. hitting FORWARD, 4, LEFT, 3, BACK, 6 and then START (green) would cause him to go forward for 3 seconds, turn left for 3 seconds, then back for 6 seconds. Sound can be played simultaneously (you turn it on and off with 1 and 3, respectively), so Vincent can make cool noises while charging along. He even features a 3-gear module with a 9400 RPM motor, which can be programmed as well (icon with the circle and 3 connected lines).

Though the CPU appears to be very simple without the ability to perform conditional processing (which also leaves out the possibility of looping), a neat little feature it does have is the ability to multiply time amounts. The X button on the control panel is multiplication – so if you’d like Vincent to go forward 48 seconds, you can hit 6 X 8. Just a nice little added feature!

The Atari Connection

What could be most neat of all about this little guy? He was produced by Axlon – which you may not have heard of. But Axlon was a company created by Nolan Bushnell, founder of Atari. Axlon had produced a few such robots, but they never took off like his earlier company did.

All in all, a very cool present! Thanks Ryan!

Shredz64 at the Portland Retro Gaming Expo

Just a heads up, if you’re around the Portland, OR area this weekend (September 18-19), stop by the Portland Retro Gaming Expo. Not only does it promise to have TONS of retro games, hardware, and general awesomeness to play and buy, but the Commodore Computer Club and Users Group of Vancouver, WA will be at the Expo demonstrating Shredz64 and the PSX64 in action! They’ll also have lots of other Commodore goodness to check out.

The expo is located at:

Portland Crowne Plaza
1441 NE 2nd Avenue
Portland, OR
503-233-2401

And full details can be obtained on the website. Personally I’m very jealous of everyone who gets to attend, I wish I could be there myself – it looks like its going to be a blast! Check it out if you can!