VirtualBros

cpscotti edited this page Mar 6, 2011 · 6 revisions

Why and What:

One common idea that comes to mind when thinking about the possibilities of the Push Snowboarding project is to make the phone "react" to your snowboarding and the most popular way to do so is, apparently, making the phone scream, laugh at or ovate the rider. I've being saying for weeks that the app has been written with that kind of thing in mind and that it would be fairly easy for anyone to implement that. The time has come for me to stop talking and start coding. It's Saturday night and I'm staying at home (I've got a cold!).

Justifications given, here's the Master Plan:

  • Create a new (abstract) Device (see Devices) that subscribes to the airtime detector.
  • Make this device keep track of the riders jumps and play sounds accordingly.

I'll call it "Virtual Bros" - if you were ever into any extreme sport or the likes you know your bros are always there to either ovate at your awesome tricks or to (friendly) laugh at your crashes.

Tech Brief:

To accomplish our hilarious - maybe irritating - app, we'll use Phonon which is the multimedia API provided with Qt. Phonon provides some really high-level functions that just simply work, that's what we'll be using.

Maybe the best place to start reading about phonon is Here. There you'll find the "Playback" section and those seventeen lines are enough for all we're doing here :D.

Pre-requisites:

The world is not perfect and we'll need a little work-around to make this work on the N8 (I think this is not needed for the ones with the newer firmware). You'll need to install this SIS FILE that applies a small fix to the Qt shipped with your phone. For more info on why this is needed, see this and this

The .pro file:

Before we get to the device, we need to add Phonon to the pro file (PushBurton2.pro):

QT       += core gui xml phonon #Adding phonon only!

and add the sound we want to play to the deployed files:

# The position of this lines in the file is not important. 
# (I added it just before the symbian{} block just bc it made more sense to me)
soundFiles.sources = sounds/*.mp3 #given that you have a folder "sounds" in your project's folder
# note that we're not telling qmake where this files would be deployed to so everything will go to the
# default position which is "C:\Private\ece639d4" (ece639d4 is the app's UID3)
# if one wanted a different folder, you need to specify soundFiles.path
DEPLOYMENT += soundFiles

For more information on the deployment statement see this or this

Coding our new Device

Now things get interesting. Using the "New.." wizard, we create a new "C++ Class", named VirtualBrosDevice with PushBurtonGenericDevice, select "Inherits QObject" on "Type Information" and hit next.. finish.

Modify VirtualBrosDevice.h so that it looks like (follow the comments for explanations here!):

//....
#include <Phonon/Phonon>
#include <QString>
#include <typeinfo> //needed by the subscribe mechanism

#include <QApplication>//needed to get the app's private folder

#include "pushburtongenericdevice.h"

#include "npushairtimetick.h" //data structure that will "bring" the air time data
#include "pushn8airtimedetector.h" //device who we'll subscribe to

class VirtualBrosDevice : public PushBurtonGenericDevice
{
    Q_OBJECT
public:
    explicit VirtualBrosDevice(QObject *parent = 0); //Was created automatically

    //We'll need the good'ol destructor to keep things clean
    ~VirtualBrosDevice();

    //The following two functions are Pure Virtual functions on PushBurtonGenericDevice
    //you have to implement them - there's no implementation on the baseclass
    //(Watch the =0 at the end of their declarations there!!)
    QString get_description();
    bool is_online();

    //Our device subscribes to other devices so we need to reimplement these 2 functions
    //accordingly. One can argue that subscribesToAny wouldn't be necessary but it SURE
    //prevents doing a huge amount of unnecessary checks.
    bool subscribesToAny();//returns true..

    //This function will return true when the given device is a AirTimeDetector
    bool subscribesTo(PushBurtonGenericDevice* deviceType);


signals:

public slots:
    //last but not least, we need to reimplement the slot that will receive all the incoming data,
    //"judge" it and stream sounds accordingly
    void incoming_reading(NPushLogTick *);

private:
    //pointer that will hold the sound ready to be "played"
    Phonon::MediaObject *applause;
};

The cpp is where the magic resides so we'll go a little bit slower on it. First, constructor and destructor:

#include "virtualbrosdevice.h"

VirtualBrosDevice::VirtualBrosDevice(QObject *parent) :
    PushBurtonGenericDevice(parent)
{
    applause = 0;

    QString applausePath;
//This whole thing here is necessary just to get access to the app's private folder (which will be "C:\Private\ece639d4")
#ifdef Q_OS_SYMBIAN
    //This is a tricky part but all we need here is to retrieve the path to where we deployed the sound file.
    //read more at:
    //http://wiki.forum.nokia.com/index.php/Get_private_path_in_Qt#Converting_path_from_Qt_format_to_Symbian_format
    QString privatePathQt(QApplication::applicationDirPath() );
    QString privatePathSymbian(QDir::toNativeSeparators(privatePathQt));

    //Oh, and the sound file? I've got it from freesound.org , everything is on CC there.. so no prob. 
    //You should probably get your own.. (the one I'm using is not thaat cool)
    applausePath = privatePathSymbian + "\\applause.mp3";
#else
    //For testing purposes, I added the file to C:/ on the windows pc. In this way it'll work on the simulator too.
    applausePath = "/applause.mp3";
#endif

    //This loads the file and leaves it in a "ready to play" state.
    //Note also that "createPlayer" is the simplest way to playing one sound using Phonon 
    //and it basically "takes ownership" of the sound sink and that sort of thing so if we 
    //wanted more sounds playing concurrently we would need to use the less minimalistic calls to phonon.
    applause = Phonon::createPlayer(Phonon::MusicCategory, Phonon::MediaSource(applausePath));
}

VirtualBrosDevice::~VirtualBrosDevice()
{
    //Just doing "responsible" memory management and stopping the sound "just in case"
    if(applause) {
        applause->stop();
        applause->deleteLater();
    }
}

Following is the implementation of the basic virtual functions from PushBurtonGenericDevice All quite straightforward:

QString VirtualBrosDevice::get_description()
{
    //Just giving it a name for sake of id
    return "VirtualBros";
}

bool VirtualBrosDevice::is_online()
{
    //if pointer is valid, it's good to go
    return (applause);
}

//This function is called by PushDevicesHolder before calling "subscribesTo" to all other devices
//obviously, if this returns false, PushDevicesHolder will skip that check.
bool VirtualBrosDevice::subscribesToAny()
{
    return true;
}


//This is an important bit. The code here checks if the device passed as a parameter is actually a 
//PushN8AirTimeDetector. Obviously, for all other devices it'll return false.
bool VirtualBrosDevice::subscribesTo(PushBurtonGenericDevice* deviceType)
{
    if(typeid(*deviceType) == typeid(PushN8AirTimeDetector)){
        return true;
    } else {
        return false;
    }
}

All that's missing on our device now is to tell it "what to do" when we receive an air-time. Since the VirtualBrosDevice subscribes to the PushN8AirTimeDetector, all output from the air time detector will be connected to the slot void VirtualBrosDevice::incomming_reading(NPushLogTick * tick). Into that slot we check which type of incomming data we're receiving (remember that a device can emit different kinds of LogTicks and that different devices may emit the same LogTick) then we check if the rider just landed an air and if so.. we play some applause.

void VirtualBrosDevice::incoming_reading(NPushLogTick * tick)
{
    //Checking if input tick is a AirTimeTick
    if(typeid(*tick) == typeid(NPushAirTimeTick)) {
        NPushAirTimeTick * airTimeTick = (NPushAirTimeTick *)tick;

        //The AirTimeTick provides all known data from the "current"
        //jump/airtime. For example, it'll tell you if the rider just
        //landed a jump through the bool "landed"
        if(airTimeTick->landed) {
            applause->seek(0);
            applause->play();
        }
        
        //The airTimeTick also brings airTimeTick->msecsOnAir which 
        //tells you for how long the rider is in the air
        //Basically, when a rider does a jump, this slot will receive 
        //lots of ticks with msecsOnAir increasing until the rider hits 
        //the ground. Then it'll receive a tick with 
        //airTimeTick->landed == true
        //and with msecsOnAir holding the final measure of how long 
        //the air time was.
        
    }
}

Ok, as far as the Devices goes, we're done!

Installing your device

All we need now is to start/connect the device at startup. For that one solution is to hard code it into DevicesManager. That's how it is for now..: Add to the top of DevicesManager.h:

#include "virtualbrosdevice.h"

and on DevicesManager.cpp, look for the SetupAbstractDevices function, there you'll add your device:

void DevicesManager::SetupAbstractDevices()
{
    //HERE it is! Just allocate your new device and push it back to the devicesHolder! That's it!
    //two more lines of code! :D
    VirtualBrosDevice * virtualBros = new VirtualBrosDevice();
    configuredDevices->push_back(virtualBros);

    PushN8AbsNormFeetDevice * absFeetDevice = new PushN8AbsNormFeetDevice();
    configuredDevices->push_back(absFeetDevice);

    PushN8AirTimeDetector * airTimeDetector = new PushN8AirTimeDetector();
    configuredDevices->push_back(airTimeDetector);

    PushN8SimpleReportsGenerator * reportGenerator = new PushN8SimpleReportsGenerator();
    configuredDevices->push_back(reportGenerator);
}

Well.. that's all! Easiest way to test it is to start a run on the phone and then throw your phone up; if you don't have either the foot pressure sensors or the motion box connected this will trick an air time. Yep, the phone works with whatever is available You'll notice that there is a tiny delay in between the landing and the start of the sound; that's not LAG. The air time detector only emits the "landed" tick when it's SURE the rider landed and that occurs a few moments after the actual touch on the ground. But note that the msecsOnAir value does NOT suffer from that problem, it should measure exactly the time between take off and landing.

Well, that's all! I've added a branch (called cool-devices) to the project to host this crazy tiny things. If you just want to test it before actually reading this whole thing, just checkout from there!

Hope it helps! Clovis