Archive

Posts Tagged ‘uSightRead’

Do you hear what I hear?

June 19th, 2009 beardo No comments

One of the next big features I’m working on for the next version of uSightRead is sound. Now, this post isn’t about getting sound to play on the iPhone (which can be a pain in the arse), but about how I created the audio content for the game.

I wanted to have the actual notes play when the player gets the note correct. For the range of notes that I support (bass and treble clef) I needed 50 different notes. To create them I made a project in Cubase 5, simply entered the MIDI key editor and step-wise entered the notes I needed. I set the tempo so that each whole note would be one second long, and exported a wav file (16-bit, 22,050 sample rate). I thought it would be a simple matter of finding an audio splitting app, and breaking the one second chunks out of the large wav file.

Boy was I wrong. The first problem is that one note will “bleed” into the next note and can cause a small pop when cut into the one second chunks. Similarly, if the audio is in the middle of an up or down trend when it is cut at the end, a pop will occur because the sample goes from a high value or low value to zero immediately. This was not acceptable. I could get around these problems by exporting the notes one at a time, but I steadfastly refused to do this for 50 notes. Enter Python.

As of 2.6 Python has a built in wav file module called wave. To get around the first problem, I simply made the notes play for eight seconds, which allowed the notes to naturally decay before the next one started. The second problem required more work. I setup a Python script to break an audio file into one second chunks, and then scanned the last 5% of the file for a low point after which I simply zeroed out the samples. This eliminated any pops at the end. It also skips the middle seven seconds because I don’t need them. All-in-all it probably took me longer to make the script than to manually export the notes, but I also can re-do the entire operation with say, another instrument without any hassle.

Here’s the script:

import sys
import wave
import struct

notes = ['C', 'Cs', 'D', 'Ds', 'E', 'F', 'Fs', 'G', 'Gs', 'A', 'As', 'B'];

names = [];
for i in range(1,8):
  for note in notes:
    names.append("%s%d.wav" % (note, i));

inputFile = sys.argv[1];

print "Splitting file: " + inputFile;
inputWav = wave.openfp(inputFile, "rb");

print "n Frames: %d" % inputWav.getnframes();
print "Frame Rate: %d" % inputWav.getframerate();
print "Channels: %d" % inputWav.getnchannels();

totalSeconds = inputWav.getnframes() / inputWav.getframerate();
frameRate = inputWav.getframerate();
sampleWidth = inputWav.getsampwidth();

skipSeconds = 8;
for second in range(0, totalSeconds, skipSeconds):
  print "Part: %05d" % second;

  # determine the output file name
  name = "%05d.wav" % second ;
  if (len(names) > 0): name = names.pop(0);

  outWav = wave.openfp(name , "wb");
  outWav.setnchannels(1);
  outWav.setsampwidth(sampleWidth);
  outWav.setframerate(frameRate);

  outputFrames = inputWav.readframes(frameRate);
  packFormat = "%uB" % frameRate * sampleWidth;
  outputBytes = list(struct.unpack(packFormat, outputFrames));

  last = 0;
  previous = 1;
  first = frameRate * sampleWidth - int((frameRate * sampleWidth) * 0.05);
  for x in reversed(range(first, len(outputBytes) - 1, 2)):
    # The data was little endian, compute the sample value
    value = outputBytes[x] * 255 + outputBytes[x+1];
    if value < 50:
      last = x;
      break;

  outputBytes[last:] = [0 for x in range(last, len(outputBytes))];

  # write output
  outWav.writeframes(struct.pack(packFormat, *outputBytes));
  outWav.close();

  # skip intervening seconds
  for i in range(0, skipSeconds -1): inputWav.readframes(frameRate);

In the end it was pretty simple, but I haven’t done Python in about 8 years, and so there was a lot of internet-search based programming.

Guest post for 47hats

June 16th, 2009 beardo No comments

I did a guest post for 47hats. It’s a website dedicated to MicroISVs and other small software business related stuff. If you’re a small software shop, you should check it out. The direct link to my post is here.

Meanwhile, I continue to add new features to uSightRead in preparation for the 1.2 release. I hoping to have it done and submitted to the App store in about two weeks. Unfortunately version 1.1 is still in review, so if you bought uSightRead and are waiting for the bug fixes, it’s coming.

Last night I did get sound working. Sound is not the easiest thing on the iPhone if you want to do anything remotely complicated. I may do a post on it, but right now I have a major head cold and can barely get my “day job” done much less anything else.

A chicken in every pot, and a bug in every app

June 7th, 2009 beardo No comments

Alas, I am not a perfect coding machine. Two bugs were found in version 1.0 of uSightRead so I quickly fixed them and released version 1.1. First, the game was not correctly restoring the saved level and score when the user resumed from a saved game. Secondly, the “Game Over” screen wouldn’t disappear if you started a new game. Both were easily found and fixed. I added two enhancements as well. First, you lose balloon power when you misidentify a note, and second the note speed was generally increased. After playing for a while, easy was way too slow.

The upgrade has already been submitted to the iTunes store and will be available as soon as Apple approves it. On a brighter note (get it, note, music game, heh), I did manage to sell two copies of uSightRead on the first day. On top of that, only one of them was my wife!

I’ve also handed out some promo codes and the received positive feedback. Definitely encouraging.

There’s an app for that

June 6th, 2009 beardo No comments

So let’s say you want a fun little game to learn to read music? There’s an app for that.

That’s right, uSightRead is live on the App store. It was submitted late Tuesday, and approved for sale on the following Saturday. That makes about four days from submission to approval. Not too shabby, although it is a simple game which probably aided in the quick review.

You can buy it here (opens iTunes). I’m so excited I may have just wet myself.

uSightRead submitted to the App Store

June 3rd, 2009 beardo No comments

Well, after about a month of hard after-hours work, version 1.0 of uSightRead has been submitted to the iTunes App Store. Hooray!

Now it’s just a waiting game for it to be approved. I’m really happy with the way it came out, and suprised at how easy it was to develop for the iPhone. There were some features that I cut out, but it just gives me a reason to keep going and to have additional releases. I already have plans for two more releases, but for now I’m going to take a little breather.

One other thing that I was having an issue with is setting up the bank information in App Store so that I could get paid. The issue is that they require a bank that has a SWIFT code. The SWIFT system is a proprietary international banking system that allows for money to be wire transfered across country borders. Most small banks (like the one I use) aren’t on the SWIFT system. I ended up opening an account specifically for the app store at Wells Fargo. Just a heads up for other budding iPhone developers out there.

Knocking on the door to success?

May 26th, 2009 beardo No comments

Got two opportunities over the long weekend to work on uSightRead. As of last night, the basic game works like a champ. Notes fly by, and the player must hit the correct note at the right time, and boom, they get points. I spent some time refactoring the code to support ledger lines for notes outside the staff. These are just sprites that follow along with the notes that are already there. The other thing I added was an overlay using Cocoa Touch. The overlay contains the score label, and a button for opening the settings.

I could have done the score and button with the Cocos2D-iPhone framework, but I didn’t really see anything that supported a button without having to build it up myself. I did find the Label class, so that would have worked for the score, but it seemed simpler to use Cocoa Touch. The one issue I had was if the UIView covered the entire surface, it would to throw off the touch handling withing Cocos2D-iPhone. To solve this, I simply made the UIView only take up the top 35 pixels of the app.

The only things left for the 1.0 release are to build the options/preferences menu, saving of those preferences (and high scores) and then some simple clean up of game mechanics (starting a new game, etc). I plan on releasing the 1.0 version and then providing free updates with new features. Lots of ideas, just not a ton of time to implement them. Hopefully by the end of the week I can have it submitted to the iTunes store and the button on the right will actually work :)

Let there be multi-touch and animation

May 18th, 2009 beardo No comments

Saturday was very productive for me. I re-did the input layer to support multi-touch. Now you can play more than one note at a time on the virtual piano portion of the game. I’m not sure it’s 100% right, and there seems to be occasions where the key gets “stuck” in the down position. I’ll have to debug that before release. I also added labels to the keys for those that don’t know the notes on a piano. I actually implemented the labels with an AtlasSpriteManager and AtlasSprites. The way it works is that you compound multiple sprites in a single texture (one png file for me), and set the sprites to be different regions of the shared texture. This allows it to load the texture once, draw all the sprites, and then move on to the next texture.

The static labels aren’t where AtlasSprites really shine, the best use of them is for animations. I added a pulsing gate that “kills” the notes that make it past the player. I started with a png file containing all the animation frames:
killer

Here’s the code to make the it pulsate:

Texture2D *killerTexture =
  [[TextureMgr sharedTextureMgr] addImage:@"killer.png"];
killerManager =
  [[AtlasSpriteManager alloc] initWithTexture:killerTexture capacity:9];
AtlasSprite *ks =
  [killerManager createSpriteWithRect:CGRectMake(0, 0, 32, 134)];
[ks setPosition:ccp(wallLocation, STAFFSPACING * 2)];
AtlasAnimation *animation =
  [AtlasAnimation animationWithName:@"pulse" delay:0.1f];
for(int i = 0; i < 9;i++)
{
  [animation addFrameWithRect: CGRectMake(i*32.0, 0.0, 32, 134)];
}
for (int i = 8; i > 0; i--)
{
  [animation addFrameWithRect: CGRectMake(i*32.0, 0.0, 32, 134)];
}

[killerManager addChild:ks];
[self addChild:killerManager z:5];
id action =
  [RepeatForever actionWithAction:
    [Animate actionWithAnimation: animation]];

[ks runAction:action];

Basically, you load the texture, create a atlas sprite manager with the texture, create a sprite in that manager, and then build an animation based on the different portions of the texture. One thing to note is that creating a sprite with the AtlasSpriteManager’s createSpriteWithRect message does not add it to the manager. You still need to do that yourself. Also note that you don’t add the AtlasSprite to the layer/scene, you only add the AtlasSpriteManager which handles drawing all of the child sprites. The animation is pretty straight forward, just add the individual frames, set the delay, and you’re off. In this example I add the frames going forward, and then going backwards.

I did start out with only five frames, but it was choppy on the actual device, so I expanded it to nine frames and it’s smooth as butter.

Getting touched

May 13th, 2009 beardo 2 comments

I had an opportunity last night to work on the input component of uSightRead. The main part of the game is a single scene, with layers for the individual parts. There is a main game display layer and an input layer. The input layer consists of keys from a piano that respond to touches. Initially I had thought that each sprite would be able to handle its own touches and then simply pass a message up. This is not how it works. With cocos2d-iphone only a layer can receive touches. I discovered this in a answer to this question on Stack Overflow, which the answer goes into how to do bounds checking.

If you note the highest rated answer shows how to compute where the point touched within your layer, and then determine if the touch was inside a label. This is doing a basic rectangle point intersection. The example code uses a compound if statement, but I simply built a rectangle based on my sprite, and used the CGRectContainsPoint function found. You can read about the built-in geometry functions here. Here’s what my code looks like:

CGRect spriteRect =
  CGRectMake(self.position.x - spriteWidth / 2,
                   self.position.y - spriteHeight / 2,
                   spriteWidth,
                   spriteHeight);

if(CGRectContainsPoint(spriteRect, cLoc)
{
  NSLog(@"Sprite was touched");
  return kEventHandled;
}
else
{
  return kEventIgnored;
}

The one thing to note is the subtraction of half the width and height to compute the postion. This is because the position is actually the center of the Sprite.

Actual progress

May 11th, 2009 beardo No comments

I got a decent amount of time to work on the iPhone version of uSightRead this weekend. I’ve made a deal with the family that I can work on it uninterrupted (unless the baby wakes up) after 9:00. So this Friday and Saturday I stayed up till about midnight working. I was rewarded with a fully functional game display with notes smoothly scrolling by.

Everything worked great on the simulator, so I decided to see how it ran on my iPhone. It was then I realized that yes, I am developing for a mobile device and some things need to be optimized. I was initially storing each all of the notes in an array, and having the sprites already in the scene. This worked okay with about 100 notes, but after that it started having intermitent pauses and my target was about 2000 notes queued up.  I changed the logic to only add the notes to the scene when it came close to being displayed. This works much better. I’m still looping through all notes and updating their location each frame, and so there some more optimizations that can be done.

Aside from this optimization, I’m ready to move on to the input area.