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
{
{
public:
virtual ~SamplePlayerWidget();
private:
void togglePlayback();
void toggleLooping();
MultiWidgets::TextWidgetPtr addTextWidget(const QString & text);
MultiWidgets::SliderWidgetPtr m_playHeadSlider;
MultiWidgets::TextWidgetPtr m_loopToggle;
bool m_disableSeek;
};
, 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);
y += 30;
m_loopToggle = MultiWidgets::create<MultiWidgets::TextWidget>("Loop", this);
m_loopToggle->setLocation(0, y);
m_loopToggle->eventAddListener("single-tap", [=] { this->toggleLooping(); } );
m_loopToggle->setSize(200, 20);
y += 30;
setHeight(y);
eventAddListener("single-tap", [=] { this->togglePlayback(); } );
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()
{
}
{
if(m_noteInfo.isPlaying()) {
setBackgroundColor(1.0f, 0.0, 0.4f, 1.0f);
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);
}
{
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleGain(m_noteInfo, m_parameters.gain());
}
}
{
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleRelativePitch(m_noteInfo,
m_parameters.relativePitch());
}
}
{
if(m_disableSeek)
return;
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());
}
}
MultiWidgets::TextWidgetPtr SamplePlayerWidget::addTextWidget(const QString &text)
{
MultiWidgets::TextWidgetPtr textWidget = MultiWidgets::create<MultiWidgets::TextWidget>(text, this);
textWidget->setBackgroundColor(0, 0, 0, 0);
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)
{
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.
auto dirs = Radiant::ResourceLocator::instance()->locate(soundDirectory);
if(dirs.empty()) {
Radiant::error(
"Could not locate directory \"%s\".", soundDirectory->toUtf8().data());
return 1;
}
if(!audioFiles.count()) {
"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.
The full source code for the entire example is listed below:
#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>
namespace Examples
{
{
public:
virtual ~SamplePlayerWidget();
private:
void togglePlayback();
void toggleLooping();
MultiWidgets::TextWidgetPtr addTextWidget(const QString & text);
MultiWidgets::SliderWidgetPtr m_playHeadSlider;
MultiWidgets::TextWidgetPtr m_loopToggle;
bool m_disableSeek;
};
, 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);
y += 30;
m_loopToggle = MultiWidgets::create<MultiWidgets::TextWidget>("Loop", this);
m_loopToggle->setLocation(0, y);
m_loopToggle->eventAddListener("single-tap", [=] { this->toggleLooping(); } );
m_loopToggle->setSize(200, 20);
y += 30;
setHeight(y);
eventAddListener("single-tap", [=] { this->togglePlayback(); } );
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()
{
}
{
if(m_noteInfo.isPlaying()) {
setBackgroundColor(1.0f, 0.0, 0.4f, 1.0f);
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);
}
{
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleGain(m_noteInfo, m_parameters.gain());
}
}
{
if(m_noteInfo.isPlaying()) {
Resonant::DSPNetwork::instance()->samplePlayer()->setSampleRelativePitch(m_noteInfo,
m_parameters.relativePitch());
}
}
{
if(m_disableSeek)
return;
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());
}
}
MultiWidgets::TextWidgetPtr SamplePlayerWidget::addTextWidget(const QString &text)
{
MultiWidgets::TextWidgetPtr textWidget = MultiWidgets::create<MultiWidgets::TextWidget>(text, this);
textWidget->setBackgroundColor(0, 0, 0, 0);
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)
{
bool ok = app.
init(argc, argv);
if(!ok)
return 1;
auto dirs = Radiant::ResourceLocator::instance()->locate(soundDirectory);
if(dirs.empty()) {
Radiant::error(
"Could not locate directory \"%s\".", soundDirectory->toUtf8().data());
return 1;
}
if(!audioFiles.count()) {
"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());
}
}