/* === S Y N F I G ========================================================= */
/*! \file synfig/soundprocessor.cpp
** \brief Template Header
**
** $Id$
**
** \legal
** ......... ... 2014 Ivan Mahonin
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License, or (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
** \endlegal
*/
/* ========================================================================= */
/* === H E A D E R S ======================================================= */
#ifdef USING_PCH
# include "pch.h"
#else
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <cmath>
#include <vector>
#include <Mlt.h>
#include "general.h"
#include "soundprocessor.h"
#endif
using namespace std;
using namespace synfig;
using namespace etl;
/* === M A C R O S ========================================================= */
/* === G L O B A L S ======================================================= */
/* === P R O C E D U R E S ================================================= */
/* === M E T H O D S ======================================================= */
class SoundProcessor::Internal
{
public:
static bool initialized;
std::vector<PlayOptions> stack;
Mlt::Profile profile;
Mlt::Producer *last_track;
Mlt::Consumer *consumer;
bool playing;
Time position;
void clear() {
if (last_track != NULL) { delete last_track; last_track = NULL; }
if (consumer != NULL) { consumer->stop(); delete consumer; consumer = NULL; }
stack.clear();
stack.push_back(PlayOptions());
}
Internal(): last_track(), consumer(), playing(), position(0.0) { clear(); }
~Internal() { clear(); }
};
bool SoundProcessor::Internal::initialized = false;
SoundProcessor::SoundProcessor()
{
assert(Internal::initialized);
internal = new Internal();
}
SoundProcessor::~SoundProcessor() { delete internal; }
void SoundProcessor::clear()
{ internal->clear(); }
void SoundProcessor::beginGroup(const PlayOptions &playOptions)
{
internal->stack.push_back(PlayOptions(
internal->stack.back().delay + playOptions.delay,
internal->stack.back().volume * playOptions.volume ));
}
void SoundProcessor::endGroup()
{
assert(internal->stack.size() > 1);
if (internal->stack.size() > 1)
internal->stack.pop_back();
}
void SoundProcessor::addSound(const PlayOptions &playOptions, const Sound &sound)
{
PlayOptions options(
internal->stack.back().delay + playOptions.delay,
internal->stack.back().volume * playOptions.volume );
if (options.volume <= 0.0) return;
// Create track
Mlt::Producer *track = new Mlt::Producer(internal->profile, (String("avformat:") + sound.filename).c_str());
if (track->get_producer() == NULL || track->get_length() <= 0) {
delete track;
track = new Mlt::Producer(internal->profile, (String("vorbis:") + sound.filename).c_str());
if (track->get_producer() == NULL || track->get_length() <= 0) { delete track; return; }
}
int delay = (int)round(options.delay*internal->profile.fps());
if (-delay >= track->get_length()) { delete track; return; }
// set volume
if (!approximate_equal(options.volume, Real(1))) {
Mlt::Filter *filter = new Mlt::Filter(internal->profile, "volume");
filter->set("gain", options.volume);
track->attach(*filter);
}
// set delay
if (delay < 0) {
// cut
track->set_in_and_out(-delay, -1);
} else
if (delay > 0) {
Mlt::Playlist *playlist = new Mlt::Playlist();
playlist->blank(delay);
playlist->append(*track);
delete track;
track = playlist;
}
if (internal->last_track == NULL) {
// create background infinite track,
// show (and track position) must go on even after all sounds finished
bool success = true;
Mlt::Producer *infinite_track = new Mlt::Producer(internal->profile, "tone");
if (success && !infinite_track->get_producer()) {
success = false;
error("SoundProcessor: cannot create the 'tone' MLT producer for fake infinite track");
}
if (success && infinite_track->set("frequency", 1.0)) {
success = false;
error("SoundProcessor: cannot set frequency of the 'tone' MLT producer for fake infinite track");
}
if (success && infinite_track->set("level", -1000000.0)) {
success = false;
error("SoundProcessor: cannot mute the 'tone' MLT producer for fake infinite track");
}
if (infinite_track->get_producer() == NULL) {
delete infinite_track;
internal->last_track = track;
return;
}
internal->last_track = infinite_track;
}
set_position(0.0);
// Combine tracks
Mlt::Tractor *tractor = new Mlt::Tractor();
Mlt::Multitrack *multitrack = tractor->multitrack();
multitrack->connect(*internal->last_track, 0);
multitrack->connect(*track, 1);
delete multitrack;
Mlt::Transition transition(internal->profile, "mix");
transition.set("combine", 1);
Mlt::Field *field = tractor->field();
field->plant_transition(transition, 0, 1);
delete field;
delete internal->last_track;
internal->last_track = tractor;
}
Time SoundProcessor::get_position() const
{
return Time(internal->last_track == NULL ? 0.0 :
(double)internal->last_track->position()/internal->profile.fps() );
}
void SoundProcessor::set_position(Time value)
{
Time dt = value - get_position();
if (dt >= Time(-0.01) && dt <= Time(0.01))
return;
if (internal->last_track != NULL) {
bool restart = internal->playing && internal->consumer;
if (restart) set_playing(false);
internal->last_track->seek( (int)round(value*internal->profile.fps()) );
if (restart) set_playing(true);
}
}
bool SoundProcessor::get_playing() const
{ return internal->playing; }
void SoundProcessor::set_playing(bool value)
{
if (value == internal->playing) return;
internal->playing = value;
if (internal->playing) {
if (internal->last_track != NULL) {
internal->last_track->set_speed(1.0);
internal->consumer = new Mlt::Consumer(internal->profile, "sdl_audio");
internal->consumer->connect(*internal->last_track);
internal->consumer->start();
}
} else {
if (internal->consumer) {
internal->consumer->stop();
delete internal->consumer;
internal->consumer = NULL;
}
}
}
bool SoundProcessor::subsys_init() {
if (!Internal::initialized)
Internal::initialized = Mlt::Factory::init();
return Internal::initialized;
}
bool SoundProcessor::subsys_stop()
{
Mlt::Factory::close();
return true;
}
/* === E N T R Y P O I N T ================================================= */