Sep 1, 2008

Rock Band Drums in Linux

I announced some time ago that I had gotten a Wii, and the other day, I finally found Rock Band for it (they released it back in June, but unfortunately they didn't ship it with French manuals so they didn't get it in Quebec at the major retailers, had to go to a smaller game shop to get it).

One thing I noticed about it is that all the instruments are USB devices. So of course, the geek in me wonders, "Hmm, maybe I could hook that up to my computer...". So I tried it. I used the drums, since they're my favourite instrument in the game.

To get Linux to recognize it was simple for me. I just plugged it in. You can try it for yourself, type "lsusb" with the drums plugged in, and when they're not. You should see a line that disappears. I get this:
Bus 002 Device 006: ID 1bad:0005
It doesn't say what it is, but that's no biggie.

UPDATE (Nov. 13, 2008): In Intrepid 64-bit, the joystick no longer works. If you're having problems, click here. Also I've written a follow-up post if you actually want to download something to look at.

Now the hard part is to get it to work with your programs (it's not actually that hard). The funny thing is that the drum kit is actually a joystick, at least as far as the computer knows. In effect, the little D-pad is the joystick and all the drums/buttons/pedal are buttons. So you can use anything that interfaces with a joystick in order to use the drums in your program. I used SDL for this, which is a library for simple access to devices.

In order to use SDL to access your drums, you need a platform that supports SDL. Fortunately SDL is supported on a wide variety of platforms, so you should be OK. For this tutorial I'll be talking about Ubuntu Linux with g++, as that is what I used.
Next, you need a language with bindings to SDL. This is a lot of languages. I used C++, but you could write it in Haskell if you wanted to.

I make no guarantees about the drum kits for other systems like PS3 or XBox 360. I'm using the Wii version. I also don't know if it will give the same values for Windows or Mac OSX (don't see why it wouldn't).

So let's start. We'll do a bit of initialization:
#include <SDL.h>
#include <SDL_mixer.h>
#include <iostream>

using namespace std;

int main(){
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0){
cout << "Something messed up. :(" <<endl;
exit(1);
}

atexit(SDL_Quit);
What this does is initalizes SDL. We want to initialize the joystick so that we can access the drums, we want to initialize video so we can create a window (for some reason the events were messed up when I didn't have a window) and you'll want sound if you actually want to play sounds with your drums. Then we tell the system to call SDL_Quit when the program exits. This is good to clean up after ourselves.
Note that I include the SDL_mixer library, this makes it a lot easier to play sounds.

Now we want to initialize the joystick. It's really easy. I'm going to skip error checking and assume you only have one joystick plugged in and that it will work the first try. If you have multiple joysticks plugged in (or devices that pretend to be joysticks, like the drums), you can use SDL_NumJoysticks() to see how many you have. Then you can iterate over them and call SDL_JoystickName() to see the name of the joystick. Choose the one that says "Nintendo Wii Drum Kit" or something like that in it.
//here I assume you have the joystick at 0,
//if not replace it with a different number
SDL_Joystick * joystick = SDL_JoystickOpen(0);

//make it so that events are triggered for joysticks
SDL_JoystickEventState(SDL_ENABLE);
Ok, so our joystick is enabled. Let's load some sounds now. You can see the docs for the SDL_mixer functions here if you want to know what they're doing.
Mix_Chunk * sounds[5];
const char * waves[] = {"bass.wav", "crash.wav", "hihat.wav",
"snare.wav", "kick.wav"};

if (Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 4096) != 0){
cout <<"Couldn't open audio device: " <<Mix_GetError()
<<endl;
exit(1);
}

for (int i = 0; i < 5; i++){
sounds[i] = Mix_LoadWAV(waves[i]);

if (sounds[i] == NULL)
cout <<"Loading sound " <<waves[i] <<" failed:"
<<Mix_GetError() <<endl;
}
Here we set 5 sounds to load. This corresponds to the 5 buttons for the drum kit. I put it in an order that I used on the drums once upon a time, but you can do whatever you want. You could even make it beep or quack when you hit one.
We make a call to Mix_OpenAudio(), which initializes the mixer, tells it what frequency we want, how many channels, etc. This lets us make sound.
We then load in all the WAV data into 5 sounds with a call to Mix_LoadWAV().

One last thing to initialize. We need to create a window:
SDL_Surface * window = SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE);
Done. We have initialized everything, now let's get ready to rock! We need to set up an event handling structure and check for drum events. The way the drums work is it sends a number for each button. The numbers are like this:
0: blue
1: green
2: red
3: yellow
4: foot pedal

There is one catch though. The numbers 0-3 also correspond to the 1, A, B and 2 buttons respectively so to differentiate, the drum kit also sends a 7 every time you hit one of the pads (but not the pedal). So when you hit the blue one, you actually receive two buttons: a 0, then a 7.

We need to do a little magic to keep track of what button was hit. I do this by recording the last button that was hit, and then when a 7 is received, I know that it was a pad and that I should play a sound (unless it was a 4, in which case I don't wait for a 7 after).

Let's set up the event loop:
SDL_Event evt;
bool loop = true;
int last_btn = 0;

while (loop){
if (SDL_PollEvent(&evt)){ //check for event
if (evt.type == SDL_QUIT){
//close button, quit
loop = false;
}else if (evt.type == SDL_JOYBUTTONDOWN){
//joystick button hit, let's play
if (evt.jbutton.button == 7) //pad
Mix_PlayChannel(-1, sounds[last_btn], 0);
else if (evt.jbutton.button == 4) //pedal
Mix_PlayChannel(-1, sounds[4], 0);
else
last_btn = evt.jbutton.button;
}
}
}
In our example, we handle events by polling. There are other ways to do events with SDL, but this is the best way for games in my opinion.
We check to see if there is an event by calling SDL_PollEvent(). If there is, the function returns true and puts the info into our evt structure.
There are only two events we care about, SDL_QUIT and SDL_JOYBUTTONDOWN (we could use the joystick axes events to check to see if people used the D-pad, but we just want to play drums). If we get a quit event, then we just tell the loop to stop going and then go on with it. If it is a joystick button down event, it means someone hit something. We then want to check to see what was hit. If it is a 7, it means we have just hit a pad and the last_btn variable holds whatever pad it was. We make a call to Mix_PlayChannel() to play our WAV file.
If the button was a 4, it was the pedal, so play the pedal sound (which should be at sounds[4]).
Finally, if it was anything else, just store the value and be done with it.

Now we need to clean up:
for (int i = 0; i < 5; i++)
Mix_FreeChunk(sounds[i]);
Mix_CloseAudio();

// remember to use the same number here
// as you did with SDL_JoystickOpen()
SDL_JoystickClose(0);
And we're done! To compile with g++ you use the following command, assuming your code was in drums.cpp:
g++ `pkg-config --libs --cflags sdl` -lSDL_mixer drums.cpp -o drums
Then:
./drums
Now you should get a little black window pop up, and when you have it selected your drums should play sounds (so long as you have the WAV files in the same folder as the executable, you can get some good tracks here).

So yeah, Frets on Fire with drums anyone?

Let me know if you have any questions.

Update Nov. 12, 2008: Intrepid uses a HAL that doesn't use the Wii Drums properly. To fix it, you have to tell X what to do. However, keep in mind that if you don't follow these directions to the letter, you may run into big problems.

First, install the driver:
sudo apt-get install xserver-xorg-input-joystick
Backup your xorg.conf file:
cd /etc/X11
sudo cp xorg.conf xorg.conf.bak
You'll need to see what the kernel is loading your joystick as. Do this:
lshal > before   (with drums not plugged in)
lshal > after (with drums plugged in)
diff before after | grep event
This will output some stuff about the drums. You should see somewhere that it says /dev/input/event6 or something with a different number (event4, event5, etc.). Remember this number.

Next you'll need to make some tweaks to xorg.conf. Type:
gedit xorg.conf
You'll see a bunch of commented out sections, uncomment those (except for the actual comments). Before those add this, replacing event6 with whatever you had above:
Section "ServerFlags"
Option "AutoAddDevices" "False"
EndSection

Section "InputDevice"
Identifier "Configured Joystick"
Driver "joystick"
Option "Device" "/dev/input/event6"
EndSection

If you don't know what you're doing, write the following down on a piece of paper:
sudo cp /etc/X11/xorg.conf.bak /etc/X11/xorg.conf
If you mess up, this will get your system back. If things die and you can't log in, press Ctrl+Alt+F1 to switch to a terminal, log in normally, and type the stuff above. Then restart your computer.

Log out and log in again (assuming all went well) and you should be able to use the drums as a joystick again.

10 comments:

Guillaume Theoret said...

That's so cool =)

Vincent said...

Génial!!

Je teste ça ce soir à la maison et je vois si je peux créer un truc pour le faire entrer comme greffon dans Ardour! As-tu testé avec ubuntu-studio?

Rob Britton said...

Non, j'ai jamais utilisé ubuntu-studio.

Si tu veux créer un truc, c'est bon, mais je croix que Ardour est ecrit avec GTK, est-ce que c'est compatible avec SDL? Il y a un chose qui s'appelle libjsw qui est compatible avec GTK, mais je sais pas si c'est facile ou non.
Je suis intéressé par cette idée, dis-moi si tu vas créer quelque-chose et je croix que je peux t'aider.

Après j'avais écrit cet article, j'ai testé avec la guitare et maintenant je veux créer un jeu, probablement pas comme Rock Band ou Guitar Hero mais quelque chose où on faire un autre chose avec ces instruments. Je dirais plus, mais je parle pas bien le français et je sais pas si je fais du sens...

Vincent said...

Mon but c'est de pouvoir connecter une application à jackd qui servira pour émuler du son pour fins d'enregistrement sonores.

Il faut que je passe le son du drum à Ardour au travers de Jack. Avec la librairie SDL et avec l'adapteur Java JSDL je vais être en mesure de développer un logiciel qui va me permettre d'enregistrer du drum professionnel avec un drum de Rock Band. Gros défi!!

Java ROCKS!!! :)

Mitch said...

Totally awesome. I tweaked this to work with the PS3 controllers; they don't add the 7. However the latency isn't very good for playing, I don't know how to optimize it. If anyone wants to help or wants to know what I did feel free to e-mail me at mjpolifka a t yahoo d ot com.

Rob Britton said...

Is it the sound latency or the input latency? I seem to recall there being some problem with fast beats and input, but it might have been something else.

For sound there is a problem when the buffer is too large. It's better to use a lower quality sound and shrink your buffer down to somewhere like 512 to 1024 (current value is 4096 on the Mix_OpenAudio call).

mitch said...

I played around with the buffer with no noticeable results. I did notice that it was only a problem with the longer sound clips. My hi-hat is quite responsive while my snare gets backed up and my crash cymbal only plays about 6 times then stops for a second or so. For casual use this program works great and the lag isn't noticeable. I'm looking to try and use rock band drums to play in a band however, so I'll have to do some more optimization. I've already enabled switching drum sets using start and select.

Rob Britton said...

Nice! That sounds like it'd be fun. I started up a project on Google Code for my little code bit that I did up, if you're interested on working together on something let me know, my email should be accessible through my Google Code profile.

The sound problems you're having are with buffer sizes and bitrate and what-not. If you shrink the buffer size, it reduces the lag, but also causes skips like the one you mentioned. You can decrease the bitrate of the sounds, but then it reduces the quality. I don't know much more than this, otherwise I'd start trying to optimize it.

I'm thinking there might be a better option than SDL for this, maybe coding directly to OpenAL or ESD or something. However from what I've read, Linux audio is a big mess, so this may be opening up more problems than it will solve. Plus when you do this you also lose the cross-platform aspect of SDL.

Miles Prower said...

Je viens de tenter avec le drumset de RB1 (PS3) sur Ubuntu 8.04, et ils ne sont même pas reconnu par lsusb… Hm :/

J'espérais pouvoir m'amuser dans Hydrogen. Je vais continuer à fouiner.

Anonymous said...

or we could boot up the old windows machine(i never use mine now that i discovered linux) and use traktor and a game pad mapper. it doesnt use ubuntu but it gets the job done. i use my rockband set as midi controllers