All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
SamplePlayerExample.cpp
SamplePlayerExample-screenshot.png
Screenshot of the SamplePlayerExample example

The Sampleplayer Sounds example demonstrates how to use the Resonant audio library in Cornerstone to play samples to headphones or loudspeakers.

The logic of this application is implemented in a custom widget called SamplePlayerWidget. It is a composite widget that combines various text boxes, buttons and sliders so that the user can toggle the playback of audio sample files and control the playback parameters.

namespace Examples
{
class SamplePlayerWidget : public MultiWidgets::Widget
{
public:
SamplePlayerWidget(const QString & sampleName, MultiWidgets::WidgetPtr parent);
virtual ~SamplePlayerWidget();
virtual void update(const MultiWidgets::FrameInfo &frameInfo) OVERRIDE;
private:
// Here are call-back functions that control the audio engine based on the user input
void togglePlayback();
void setGain(Radiant::BinaryData & data);
void setRelativePitch(Radiant::BinaryData & data);
void setPlayhead(Radiant::BinaryData & data);
void toggleLooping();
MultiWidgets::TextWidgetPtr addTextWidget(const QString & text);
MultiWidgets::SliderWidgetPtr m_playHeadSlider;
MultiWidgets::TextWidgetPtr m_loopToggle;
bool m_disableSeek;
};
SamplePlayerWidget::SamplePlayerWidget(const QString & sampleName, MultiWidgets::WidgetPtr parent)
: Widget(parent)
, m_parameters(sampleName)
, m_disableSeek(false)
{
setBackgroundColor(0.2f, 0.2f, 0.5f, 0.95f);
setCSSType("SamplePlayerWidget");
setSize(500, 100);
float y = 0;
float sliderLeft = 100;
float sliderwidth = width() - sliderLeft;
addTextWidget("Tap to toggle sample playback")->setSize(width(), 20);
y += 30;
MultiWidgets::TextWidgetPtr label = addTextWidget(sampleName);
label->setSize(width(), 20);
label->setLocation(0, y);
label->setHeight(label->heightForWidth(width()));
y += label->height() + 10;
addTextWidget("Gain")->setLocation(0, y);
MultiWidgets::SliderWidgetPtr gainSlider = MultiWidgets::create<MultiWidgets::SliderWidget>(this);
gainSlider->setLocation(sliderLeft, y);
gainSlider->setMinVal(0);
gainSlider->setMaxVal(200.0f);
gainSlider->setCurrentValue(m_parameters.gain());
gainSlider->setStep(0.01f);
gainSlider->setWidth(sliderwidth);
gainSlider->eventAddListenerBd("value", [=](Radiant::BinaryData & data) { this->setGain(data); } );
y += 30;
addTextWidget("Pitch")->setLocation(0, y);
MultiWidgets::SliderWidgetPtr pitchSlider = MultiWidgets::create<MultiWidgets::SliderWidget>(this);
pitchSlider->setLocation(sliderLeft, y);
pitchSlider->setMinVal(0.1);
pitchSlider->setMaxVal(10.0f);
pitchSlider->setCurrentValue(m_parameters.relativePitch());
pitchSlider->setStep(0.01f);
pitchSlider->setWidth(sliderwidth);
pitchSlider->eventAddListenerBd("value", [=](Radiant::BinaryData & data) { this->setRelativePitch(data); } );
y += 30;
addTextWidget("Playhead")->setLocation(0, y);
m_playHeadSlider = MultiWidgets::create<MultiWidgets::SliderWidget>(this);
m_playHeadSlider->setLocation(sliderLeft, y);
m_playHeadSlider->setWidth(sliderwidth);
m_playHeadSlider->eventAddListenerBd("value", [=](Radiant::BinaryData & data) { this->setPlayhead(data); } );
m_playHeadSlider->setCurrentValue(0.0f);
// m_playHeadSlider->setStep(0.0f);
y += 30;
m_loopToggle = MultiWidgets::create<MultiWidgets::TextWidget>("Loop", this);
m_loopToggle->setLocation(0, y);
m_loopToggle->setInputFlags(MultiWidgets::Widget::INPUT_SINGLE_TAPS);
m_loopToggle->eventAddListener("single-tap", [=] { this->toggleLooping(); } );
m_loopToggle->setSize(200, 20);
y += 30;
setHeight(y);
eventAddListener("single-tap", [=] { this->togglePlayback(); } );
SF_INFO info;
SNDFILE * sndf = Resonant::AudioFileHandler::open(sampleName.toLocal8Bit().data(), SFM_READ, &info);
if(!sndf) {
Radiant::error("SamplePlayerWidget::SamplePlayerWidget # Failed to open file %s",
sampleName.toLocal8Bit().data());
return;
}
sf_close(sndf);
m_playHeadSlider->setMaxVal(info.frames / (float) info.samplerate);
}
SamplePlayerWidget::~SamplePlayerWidget()
{
}
void SamplePlayerWidget::update(const MultiWidgets::FrameInfo & /*frameInfo*/ )
{
/* Set the widget to pink when the sample is playing, and otherwise to gray. */
if(m_noteInfo.isPlaying()) {
setBackgroundColor(1.0f, 0.0, 0.4f, 1.0f);
//if(m_noteInfo.playHeadSeconds() != m_playHeadSlider->currentValue()) {
m_disableSeek = true;
m_playHeadSlider->setCurrentValue(m_noteInfo.playHeadSeconds());
m_disableSeek = false;
// }
}
else {
setBackgroundColor(0.3f, 0.3f, 0.3f, 1.0f);
}
}
void SamplePlayerWidget::togglePlayback()
{
auto player = Resonant::DSPNetwork::instance()->samplePlayer();
if(!m_noteInfo.isPlaying()) {
m_noteInfo = player->playSample(m_parameters);
}
else
player->stopSample(m_noteInfo);
}
void SamplePlayerWidget::setGain(Radiant::BinaryData &data)
{
m_parameters.setGain(data.readFloat32());
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleGain(m_noteInfo, m_parameters.gain());
}
}
void SamplePlayerWidget::setRelativePitch(Radiant::BinaryData &data)
{
m_parameters.setRelativePitch(data.readFloat32());
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleRelativePitch(m_noteInfo,
m_parameters.relativePitch());
}
}
void SamplePlayerWidget::setPlayhead(Radiant::BinaryData &data)
{
if(m_disableSeek)
return;
m_parameters.setSamplePlayhead(data.readFloat32());
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSamplePlayHead(m_noteInfo, m_parameters.samplePlayhead());
}
}
void SamplePlayerWidget::toggleLooping()
{
m_parameters.setLoop(!m_parameters.loop());
if(m_parameters.loop()) {
m_loopToggle->setBackgroundColor(0.3f, 0.3f, 1.0f, 1.0f);
}
else {
m_loopToggle->setBackgroundColor(0, 0, 0, 1);
}
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleLooping(m_noteInfo, m_parameters.loop());
}
}
// Utility function to create nice text boxes
MultiWidgets::TextWidgetPtr SamplePlayerWidget::addTextWidget(const QString &text)
{
MultiWidgets::TextWidgetPtr textWidget = MultiWidgets::create<MultiWidgets::TextWidget>(text, this);
textWidget->setBackgroundColor(0, 0, 0, 0);
textWidget->setInputFlags(MultiWidgets::Widget::INPUT_NONE);
textWidget->setColor(1, 1, 1, 1);
textWidget->setSize(100, 20);
return textWidget;
}
INTRUSIVE_PTR_TYPEDEF(SamplePlayerWidget);
INTRUSIVE_PTR_WIDGET_DEFINE(SamplePlayerWidget);
}

Unless the user specifies another directory using the –dir <path> syntax, we will try to load sounds from the Sounds folder in a predetermined list of search paths, starting from the working directory. This is done by using the Radiant::ResourceLocator class. If we don't find any audio files, the application is just aborted with an error message. We filter the searched directory with a mime type that finds all supported audio types:

int main(int argc, char ** argv)
{
Valuable::AttributeString soundDirectory(&app, "dir", "Sounds");
bool ok = app.init(argc, argv);
if(!ok)
return 1;

For each audio file we create a text box that shows the name of the audio file. The widgets are located diagonally starting from the top-left corner of the scene.

// Locate specified directory
auto dirs = Radiant::ResourceLocator::instance()->locate(soundDirectory);
if(dirs.empty()) {
Radiant::error("Could not locate directory \"%s\".", soundDirectory->toUtf8().data());
return 1;
}
// Check first found directory
auto audioFiles = Radiant::Directory::findByMimePattern(dirs.front(), "audio/*");
if(!audioFiles.count()) {
Radiant::error("There are no audio files in directory \"%s\". "
"Please use --dir option to specify directory",
soundDirectory->toUtf8().data());
return 1;
}

For each audio sample we create a SamplePlayerWidget. This widget contains sliders and buttons to control the sample playback.

for(int i = 0; i < audioFiles.count(); i++) {
Examples::SamplePlayerWidgetPtr playerWidget =
MultiWidgets::create<Examples::SamplePlayerWidget>(audioFiles.fileNameWithPath(i), app.mainLayer());
playerWidget->setLocation(i * 20, i * ((int) playerWidget->height() + 10) % (int) app.mainLayer()->height());
}

Finally we run the application. As soon as the user interacts with the text boxes corresponding audio sample is played through the loudspeaker that is closest to the widget center point.

return app.run();
}

The full source code for the entire example is listed below:

/* Copyright (C) 2007-2013 Multi Touch Oy, Finland, http://www.multitaction.com
*
* This file is part of MultiTouch Cornerstone.
*
* All rights reserved. You may use this file only for purposes for which you
* have a specific, written permission from Multi Touch Oy.
*
*/
#include <MultiWidgets/Application.hpp>
#include <MultiWidgets/SliderWidget.hpp>
#include <MultiWidgets/StayInsideParentOperator.hpp>
#include <MultiWidgets/TextWidget.hpp>
#include <Radiant/Directory.hpp>
#include <Radiant/ResourceLocator.hpp>
#include <Resonant/AudioFileHandler.hpp>
#include <Resonant/DSPNetwork.hpp>
#include <Resonant/ModuleSamplePlayer.hpp>
#include <Valuable/CmdParser.hpp>
#include <Valuable/AttributeString.hpp>
#include <QDir>
#include <sndfile.h> // For SF_INFO
namespace Examples
{
class SamplePlayerWidget : public MultiWidgets::Widget
{
public:
SamplePlayerWidget(const QString & sampleName, MultiWidgets::WidgetPtr parent);
virtual ~SamplePlayerWidget();
virtual void update(const MultiWidgets::FrameInfo &frameInfo) OVERRIDE;
private:
// Here are call-back functions that control the audio engine based on the user input
void togglePlayback();
void setGain(Radiant::BinaryData & data);
void setRelativePitch(Radiant::BinaryData & data);
void setPlayhead(Radiant::BinaryData & data);
void toggleLooping();
MultiWidgets::TextWidgetPtr addTextWidget(const QString & text);
MultiWidgets::SliderWidgetPtr m_playHeadSlider;
MultiWidgets::TextWidgetPtr m_loopToggle;
bool m_disableSeek;
};
SamplePlayerWidget::SamplePlayerWidget(const QString & sampleName, MultiWidgets::WidgetPtr parent)
: Widget(parent)
, m_parameters(sampleName)
, m_disableSeek(false)
{
setBackgroundColor(0.2f, 0.2f, 0.5f, 0.95f);
setCSSType("SamplePlayerWidget");
setSize(500, 100);
float y = 0;
float sliderLeft = 100;
float sliderwidth = width() - sliderLeft;
addTextWidget("Tap to toggle sample playback")->setSize(width(), 20);
y += 30;
MultiWidgets::TextWidgetPtr label = addTextWidget(sampleName);
label->setSize(width(), 20);
label->setLocation(0, y);
label->setHeight(label->heightForWidth(width()));
y += label->height() + 10;
addTextWidget("Gain")->setLocation(0, y);
MultiWidgets::SliderWidgetPtr gainSlider = MultiWidgets::create<MultiWidgets::SliderWidget>(this);
gainSlider->setLocation(sliderLeft, y);
gainSlider->setMinVal(0);
gainSlider->setMaxVal(200.0f);
gainSlider->setCurrentValue(m_parameters.gain());
gainSlider->setStep(0.01f);
gainSlider->setWidth(sliderwidth);
gainSlider->eventAddListenerBd("value", [=](Radiant::BinaryData & data) { this->setGain(data); } );
y += 30;
addTextWidget("Pitch")->setLocation(0, y);
MultiWidgets::SliderWidgetPtr pitchSlider = MultiWidgets::create<MultiWidgets::SliderWidget>(this);
pitchSlider->setLocation(sliderLeft, y);
pitchSlider->setMinVal(0.1);
pitchSlider->setMaxVal(10.0f);
pitchSlider->setCurrentValue(m_parameters.relativePitch());
pitchSlider->setStep(0.01f);
pitchSlider->setWidth(sliderwidth);
pitchSlider->eventAddListenerBd("value", [=](Radiant::BinaryData & data) { this->setRelativePitch(data); } );
y += 30;
addTextWidget("Playhead")->setLocation(0, y);
m_playHeadSlider = MultiWidgets::create<MultiWidgets::SliderWidget>(this);
m_playHeadSlider->setLocation(sliderLeft, y);
m_playHeadSlider->setWidth(sliderwidth);
m_playHeadSlider->eventAddListenerBd("value", [=](Radiant::BinaryData & data) { this->setPlayhead(data); } );
m_playHeadSlider->setCurrentValue(0.0f);
// m_playHeadSlider->setStep(0.0f);
y += 30;
m_loopToggle = MultiWidgets::create<MultiWidgets::TextWidget>("Loop", this);
m_loopToggle->setLocation(0, y);
m_loopToggle->setInputFlags(MultiWidgets::Widget::INPUT_SINGLE_TAPS);
m_loopToggle->eventAddListener("single-tap", [=] { this->toggleLooping(); } );
m_loopToggle->setSize(200, 20);
y += 30;
setHeight(y);
eventAddListener("single-tap", [=] { this->togglePlayback(); } );
SF_INFO info;
SNDFILE * sndf = Resonant::AudioFileHandler::open(sampleName.toLocal8Bit().data(), SFM_READ, &info);
if(!sndf) {
Radiant::error("SamplePlayerWidget::SamplePlayerWidget # Failed to open file %s",
sampleName.toLocal8Bit().data());
return;
}
sf_close(sndf);
m_playHeadSlider->setMaxVal(info.frames / (float) info.samplerate);
}
SamplePlayerWidget::~SamplePlayerWidget()
{
}
void SamplePlayerWidget::update(const MultiWidgets::FrameInfo & /*frameInfo*/ )
{
/* Set the widget to pink when the sample is playing, and otherwise to gray. */
if(m_noteInfo.isPlaying()) {
setBackgroundColor(1.0f, 0.0, 0.4f, 1.0f);
//if(m_noteInfo.playHeadSeconds() != m_playHeadSlider->currentValue()) {
m_disableSeek = true;
m_playHeadSlider->setCurrentValue(m_noteInfo.playHeadSeconds());
m_disableSeek = false;
// }
}
else {
setBackgroundColor(0.3f, 0.3f, 0.3f, 1.0f);
}
}
void SamplePlayerWidget::togglePlayback()
{
auto player = Resonant::DSPNetwork::instance()->samplePlayer();
if(!m_noteInfo.isPlaying()) {
m_noteInfo = player->playSample(m_parameters);
}
else
player->stopSample(m_noteInfo);
}
void SamplePlayerWidget::setGain(Radiant::BinaryData &data)
{
m_parameters.setGain(data.readFloat32());
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleGain(m_noteInfo, m_parameters.gain());
}
}
void SamplePlayerWidget::setRelativePitch(Radiant::BinaryData &data)
{
m_parameters.setRelativePitch(data.readFloat32());
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleRelativePitch(m_noteInfo,
m_parameters.relativePitch());
}
}
void SamplePlayerWidget::setPlayhead(Radiant::BinaryData &data)
{
if(m_disableSeek)
return;
m_parameters.setSamplePlayhead(data.readFloat32());
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSamplePlayHead(m_noteInfo, m_parameters.samplePlayhead());
}
}
void SamplePlayerWidget::toggleLooping()
{
m_parameters.setLoop(!m_parameters.loop());
if(m_parameters.loop()) {
m_loopToggle->setBackgroundColor(0.3f, 0.3f, 1.0f, 1.0f);
}
else {
m_loopToggle->setBackgroundColor(0, 0, 0, 1);
}
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleLooping(m_noteInfo, m_parameters.loop());
}
}
// Utility function to create nice text boxes
MultiWidgets::TextWidgetPtr SamplePlayerWidget::addTextWidget(const QString &text)
{
MultiWidgets::TextWidgetPtr textWidget = MultiWidgets::create<MultiWidgets::TextWidget>(text, this);
textWidget->setBackgroundColor(0, 0, 0, 0);
textWidget->setInputFlags(MultiWidgets::Widget::INPUT_NONE);
textWidget->setColor(1, 1, 1, 1);
textWidget->setSize(100, 20);
return textWidget;
}
INTRUSIVE_PTR_TYPEDEF(SamplePlayerWidget);
INTRUSIVE_PTR_WIDGET_DEFINE(SamplePlayerWidget);
}
int main(int argc, char ** argv)
{
Valuable::AttributeString soundDirectory(&app, "dir", "Sounds");
bool ok = app.init(argc, argv);
if(!ok)
return 1;
// Locate specified directory
auto dirs = Radiant::ResourceLocator::instance()->locate(soundDirectory);
if(dirs.empty()) {
Radiant::error("Could not locate directory \"%s\".", soundDirectory->toUtf8().data());
return 1;
}
// Check first found directory
auto audioFiles = Radiant::Directory::findByMimePattern(dirs.front(), "audio/*");
if(!audioFiles.count()) {
Radiant::error("There are no audio files in directory \"%s\". "
"Please use --dir option to specify directory",
soundDirectory->toUtf8().data());
return 1;
}
for(int i = 0; i < audioFiles.count(); i++) {
Examples::SamplePlayerWidgetPtr playerWidget =
MultiWidgets::create<Examples::SamplePlayerWidget>(audioFiles.fileNameWithPath(i), app.mainLayer());
playerWidget->setLocation(i * 20, i * ((int) playerWidget->height() + 10) % (int) app.mainLayer()->height());
}
return app.run();
}