Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Home

tompaana edited this page · 3 revisions
Clone this wiki locally

Combined screenshots

The XNA Drumkit is a virtual drumkit that lets you play percussion sounds by tapping sound pads. You can record your beats and play them back later. It is also possible to play the drums on top of your last recording. The application has been developed purely on top of XNA.

The application contains two views for playing:

  • simple view with 2D pads
  • 3D drumkit view

Screenshot 1  Screenshot 2

Drumkit has been also implemented for Series 40, Symbian and Harmattan. If you are interested about the differences and similarities of these implementations, take a look at the comparison wiki page.

Prerequisites

You need the following to develop and test this example:

  • Windows Phone SDK (includes Visual Studio 2010 Express for Windows Phone, Windows Phone Emulator etc.)
  • Device with Windows Phone OS 7.0 or higher
  • Zune client software installed on you computer

If Visual Studio 2010 Professional or higher is already installed on your computer, an add-in for Visual Studio 2010 Professional is automatically installed along with Windows Phone SDK.

Design

XNA Drumkit is a port from JME. Because of the bigger screen area, there is room for additional drum pads. A wheel menu can be used for selecting different percussion sounds to the pads. In the alternative Drumset view, there is no wheel menu, because every drum sound is already available. Unlike the JME Drumkit, XNA Drumkit also has support for multipoint touch.

Implementation

The core part of the example application is the fast response to touch events. As in any music software which aims to play sounds in real time, the delay basically defines whether the application is usable or not. In this example the latency is good enough for this kind of application, although there is still room for improvement.

The application has been built solely on top of XNA, but initially the development was planned to use Silverlight. The problem with Silverlight is inconsistent touch handling. In addition, the audio library is a built-in part of XNA, so the decision was made to rely fully on XNA framework. Compared to Java, the sound handling is easier, because XNA supports 16 simultaneous playing sound effects, whereas JME supports only a few. Therefore, there was no need for special manual management for audio.

Basic structure

Drumkit consists of the 'Main' class, which is responsible for game loop, that includes updating and drawing, switching between the two views and delivering touch events for them. Both views have a background, pads, buttons, and an info overlay. A pad holds a sound and can have multiple rectangular touch areas that trigger the sound to be played.

There are buttons for info, switching view, recording, playback, and exiting the application. The buttons have different images for the pressed and unpressed states. Additionally, there is a button for stopping playback, which is shown only during playback. The record button is an animated toggle button. The animation utilizes a sprite image and indicates that recording is on.

Reacting to the touch events

As mentioned earlier, Silverlight is inconsistent with the type of raw touch events. When playing the pads, the events do not always come out as they should. The FrameReported event is used for capturing touch events. This works fairly well, but when trying to play a beat, it's crucial, that no events get dropped.

Edit: Actually this the problem has been fixed and after short testing on Lumia 800 it seems, that handling FrameReported event is much faster way to get touch events than polling them in a game loop.

public MainPage()
{
    InitializeComponent();
    .
    .
    .
    Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
}

void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
    TouchPointCollection points = e.GetTouchPoints(this);
    foreach (TouchPoint point in points)
    {
        if (point.Action == TouchAction.Down)
        {
            // Play sound
        }
    }
}

In XNA, the approach is different. Since XNA is a game framework, there is a game loop, which calls update and draw methods at a certain frequency. The frequency can be fixed or not. In Windows Phone 7, the maximum frame rate is 30 fps. Since the touch panel has to be polled for touch events on every update, the 30fps limitation introduces a maximum of 33ms delay. Before a better solution is available, this is how the Drumkit works. Fortunately the Mango release sets the limitation up to 60 fps, which is better. However, it doesn't automatically mean the latency is diminished.

At the moment the code used for touch handling is as follows:

public class Main : Microsoft.Xna.Framework.Game
{
    public Main()
    {
    .
    .
    .
        TargetElapsedTime = TimeSpan.FromTicks(333333);    // 30fps
    }

    protected override void Update(GameTime gameTime)
    {
            inputState.Update();
            HandleInput(inputState);
            .
            .
            .
    }

    public void HandleInput(InputState input)
    {
        foreach (TouchLocation touch in input.touchState)
        {
            // Handle touch events
        }
        foreach (GestureSample gesture in input.Gestures)
        {
            // Handle gestures
        }
    }
}

// Custom class that stores the current touch state and gestures
public class InputState
{        
    public TouchCollection touchState;
    public readonly List<GestureSample> Gestures = new List<GestureSample>();

    public void Update()
    {
        touchState = TouchPanel.GetState();
        Gestures.Clear();
        while (TouchPanel.IsGestureAvailable)
        {
            Gestures.Add(TouchPanel.ReadGesture());
        }
    }
}

Playing samples

Drumkit utilizes the SoundEffect class under Microsoft.Xna.Framework.Audio together with Wave format drum samples. They are presented in 32-bit mono with a sample rate of 44100Hz. MP3 cannot be used, since the format requires having a short silence in the beginning of an audio file and therefore is not optimal for low latency solutions.

Playing a sample is easy. First, the SoundEffect needs to be loaded using ContentManager. It can then be played simply by calling the Play() function:

SoundEffect sample;
protected override void LoadContent()
{
    // Load sample.wav
    sample = Content.Load<SoundEffect>("Audio/sample")
}

public void playSample() {
    sample.Play();
}

JME Drumkit has the limitation of maximum 8 existing players, and only few can be played simultaneously. Windows Phone also has such a limitation, but it is much higher and therefore doesn't affect the XNA Drumkit. However, in some solutions, it may be necessary to take this into account.

Recording

Recording is implemented simply by saving timestamps. The recording functionality is implemented to the Recorder class. There is also Sample struct, which is used to save a timestamp and associate a specific pad with it. When the pad is played while recording is on, a new Sampleobject gets created and added into a List called tape.

struct Sample
{            
    public long timestamp;
    public Pad pad;
    public SoundEffect sound;

    public Sample(long timestamp, Pad pad)
    {
        this.timestamp = timestamp;
        this.pad = pad;
        this.sound = pad.Sound;
    }
}

List<Sample> tape = new List<Sample>();

public void RecordSample(Pad pad)
{
    tape.Add(new Sample(GetTimestamp(), pad));
}

public long GetTimestamp()
{
    return DateTime.Now.Ticks;
}

Playback

On every update, the Recorderchecks whether it is time to play a sample from the tape List. When the tape reaches its end, the TapeEnded event gets raised.

long started = 0;
int position = 0;
public event EventHandler TapeEnded = delegate {};

public void Update()
{   
    while (position < tape.Count && GetTimestamp() >= started + tape[position].timestamp - tape[0].timestamp)
    {   
        tape[position].sound.Play();
        position++;
    }                

    if (position >= tape.Count) {
        Stop();                
        this.TapeEnded(this, new EventArgs());
    }
}

Wheel menu

The wheel menu has a selection of samples for pads. It opens with a long tap and has a simple hide and show animation. The menu consists of 12 buttons, which are positioned in a circle. Animations have been achieved by varying the circle radius and the angle passed to sine and cosine functions.

Animations use Timer and TimerCallbacks. The following update function gets called after lengthening or shortening the radius. The loop calculates new positions for wheel buttons.

Wheel

private Point location;
private int radius = 0;

public void Update()
{
    float ratio = (float) radius / maxRadius;

    int newX = location.X + (int)((destination.X - location.X) * ratio);
    int newY = location.Y + (int)((double)(destination.Y - location.Y) * ratio);
    int btnX = 0;
    int btnY = 0;

    // AngleExtra is used to create the spinning effect
    double angleExtra = 8 * ratio;

    for (int i = 0; i < buttons.Count; i++)
    {
        btnX = (int)(Math.Cos(i * angle + angleExtra) * radius);
        btnY = (int)(Math.Sin(i * angle + angleExtra) * radius);
        buttons[i].Center = new Point(btnX + newX, btnY + newY);
    }
}

Sprite animation

Sprite

Drumkit uses sprite animation on the record button to indicate recording state. Sprite animation is a single bitmap which contains all the frames for the animation. Frames are shown one at the time. The frames have equal width and height and the images need to be positioned correctly inside the individual frames to ensure smooth animation. Timer cannot be used to animate frames as with the wheel menu, but the game loop updates the frames instead. The draw method only draws a single frame to the spriteBatch.

private int frameCount;
private float timePerFrame;
private int frame;
private float totalElapsed;
private int frameWidth;
private int frameHeight;
private Texture2D sprite;
private Rectangle targetRect;

public void UpdateFrame(float elapsed)
{
    totalElapsed += elapsed;
    if (totalElapsed > timePerFrame)
    {
        frame++;                
        frame = frame % frameCount;
        totalElapsed -= timePerFrame;
    }
}

override public void Draw(SpriteBatch spriteBatch)
{
    Rectangle sourceRect = new Rectangle(0, frameHeight * frame, frameWidth, frameHeight);
    spriteBatch.Draw(sprite, targetRect, sourceRect, Color.White);
}

Conclusion

Porting Drumkit from Java to C# was a fairly simple process, since the two languages have lots of similarities. However, Java ME offers a limited set of functionality, whereas C# together with XNA offer an excellent environment for game development. Especially the sophisticated event handling makes developing pleasant. XNA sound libraries offer low enough latency for audio playback. Multipoint touch is also a great addition for this type of application.

Something went wrong with that request. Please try again.