Category Archives: Blackberry

BerryCraft Source – Jave Minecraft I/O Engine

Below is the source to BerryCraft.  Most of the project is pretty trivial – however, MCIO.java (MineCraft IO) contains a class capable of full communication with a Minecraft server, correctly sending/receiving all Minecraft packet types as documented here.  BerryCraft itself only implements chat/time functionality (and logins of course), but the MCIO could be used to build out any Minecraft functionality (player positioning, mob spawning and attributes, inventory, etc).

If you do end up using MCIO, let me know, I’d love to see the results!

BerryCraft Source

 

 

BerryCraft Available for Download

I fixed up the last of the bugs, and added in a small amount of new functionality, and the first iteration of the BerryCraft, the Blackberry Minecraft chat client, is now complete.

 

The first iteration includes the following functionality:

  1. Connects to Minecraft server from your Blackberry (handles both authenticated or offline modes)
  2. Allows chatting with players online (and sending commands such as /time, /list, etc if available to your account)
  3. Has 3 user-assignable macros for repeatedly used messages/commands (good for admin commands)
  4. Allows user to set launcher and protocol version, which assuming no major protocol changes, should allow the client to continue to work with future server versions.
  5. Works with BB devices OS 5.0 and up over any connection type (Wifi, BIS, MDS, etc)

Additionally, the source code is available here – it includes an IO engine that fully sends/receives all packet types and a shell Game class to implement future functionality past chatting.

Download OTA here!

BerryCraft – Update 5/13/12

I was hoping to release the first iteration of the BerryCraft client tonight, and though I got a lot done this weekend, there are still a couple errors that are preventing it from going primetime.

Most importantly, I fleshed out 99% of the Minecraft protocol IO functions, so it can send and retrieve all 68 or so packet types as documented.  Obviously it only actually implements the basic connection/chat/time functionality (as opposed to drawing anything), but as far as communicating with the server, it understands everything (just  a couple errros still popping up from things not working right).  This is almost a necessity, as the server could potentially send any packet type over, and if the client doesn’t receive it properly, it will crash – so it has to be ready for anything in the protocol specification.  Internally, it can tell when mobs are spawned/move/look, when painting/items spawn, player abilities/movements, sound effects happen, etc etc.

Also – the client stays up for as long as desired (I ran it for 30-40 minutes at one point) – so it should be stable once the fixes are done.

Hopefully it won’t take more more than a couple nights to fix the bugs – expect another post when it’s good to go!

Authenticated Minecraft Logins Working for BerryCraft

A quick update – a lot of people had shown interest in BerryCraft – the Blackberry Minecraft admin/chat client I’d been working on.  It was a bit of a hack before and didn’t work with servers that required Minecraft.net authentication, so I wanted to fix it up before starting to release it out.   Tonight I successfully got the authenticated logins working.  I want to clean it up a bit more, and then stat releasing it in iterations.  The first will be mainly just a chat client, since that’s pretty much implemented.  Then we’ll see about inventory management and some other goodies if things work out well.

Blackberry Minecraft Chat Client

First off, if you haven’t tried Minecraft yet, it is a ridiculously addictive game in which you, the player, dig tunnels, collect materials, craft items, and build up the world around you. You explore underground caverns, build houses, castles, farms, etc. It’s a ton of fun (and definitely a good way to waste hours of your life). If you haven’t seen it before, check it out.

I run a Minecraft server for a few friends of mine, and even if I wasn’t in the game, I wanted to be able to chat with them and run server comands on the go. So I wrote a VERY quick and extremely dirty (and buggy) Minecraft chat client for the Blackberry that will connect to a server, and let you chat and run commands.

BerryCraft Chat:

It has a few limitations – specifically that it will only connect to a server that has minecraft.net authentication turned off. It wouldn’t be too difficult to insert this functionality into it, but I banged this out in a couple days and don’t really have time to put any polish on it.

UPDATE: BerryCraft can be downloaded here.

Good First Week for Galactic Blast!

I tweeted this as well, but I just wanted to thank everyone who has been supportive of both the Blackberry Game Development Tutorial, as well as our commercial release of Galactic Blast! – over the first week, we sold over 100 copies! It’s a great feeling to know people are out there enjoying your game.

Thanks again everyone!

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;
   }
}

Blackberry – Mixing 2 or More Sounds Together (Concept Video)

An Audio Issue

One thing that I (and many others judging by forum posts) have run into is the fact that the Blackberry isn’t very good at mixing more than one sound together. You’ll be listening to music, or playing a game with music, and suddenly you’ll get a text message, and it will simply terminate the current sound and play the new one. If you’ve got a friendly application, it will restart/resume the original audio, but it’s a jarring audio experience.

What I’m about to post won’t fix that. However – developers also run into the issue when they want to mix sounds together in their applications and games. Play 2 or more sound effects simultaneously and/or while music is playing. I’ve seen official RIM developers comment that certain devices can achieve two simultaneous sounds by instantiating the Player class twice – but from what I gather, this is on GSM only phones – and still limited to two. On CDMA devices, you’re completely out of luck.

The Cheap and Quick Workaround

In my 6 part tutorial on writing a Blackberry game, in the audio article, I mentioned how the Alert.startAudio method can be used for simple sound effects (tone/duration pairs) that will play simultaneously while your midi or mp3 music plays in the background. For many applications, this is enough if you don’t need sophisticated sound effects.

The Start of a Mixer

I’m guessing that the limitation is imposed by the audio chipset/DAC inside of CDMA devices, and perhaps RIM realizes the CPU time involved in a software based audio mixer would slow the phone down too much. However, I think the software option should be there, and developers can use it if they’d like – they might not need a large amount of CPU, or might be dealing with low quality sound files – there are a few scenarios where a software mixer would just be a nice options.

Tonight I sat down and wrote a quick and dirty proof of concept application showing a PCM audio mixer in action. I loaded in 3 audio 44.1khz 8bit mono files and mixed them together in real time. I didn’t normalize the audio at all, so its a little soft, but the code works, and could be expanded on quite a bit to make a full featured mixer. Maybe I’ll run into problems down the road that RIM already has, but it’ll be interesting nonetheless.

Here is the video of the test: