code:
/*
 * RenderManager - exporting logic common between the CLI and GUI.
 *
 * Copyright (c) 2015 Ryan Roden-Corrent <ryan/at/rcorre.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDebug>
#include <QDir>

#include "RenderManager.h"
#include "Song.h"
#include "BBTrackContainer.h"
#include "BBTrack.h"

RenderManager::RenderManager(
		const Mixer::qualitySettings & qualitySettings,
		const OutputSettings & outputSettings,
		ProjectRenderer::ExportFileFormats fmt,
		QString outputPath) :
	m_qualitySettings(qualitySettings),
	m_oldQualitySettings( Engine::mixer()->currentQualitySettings() ),
	m_outputSettings(outputSettings),
	m_format(fmt),
	m_outputPath(outputPath)
{
	Engine::mixer()->storeAudioDevice();
}

RenderManager::~RenderManager()
{
	Engine::mixer()->restoreAudioDevice();  // Also deletes audio dev.
	Engine::mixer()->changeQuality( m_oldQualitySettings );
}

void RenderManager::abortProcessing()
{
	if ( m_activeRenderer ) {
		disconnect( m_activeRenderer.get(), SIGNAL( finished() ),
				this, SLOT( renderNextTrack() ) );
		m_activeRenderer->abortProcessing();
	}
	restoreMutedState();
}

// Called to render each new track when rendering tracks individually.
void RenderManager::renderNextTrack()
{
	m_activeRenderer.reset();

	if( m_tracksToRender.isEmpty() )
	{
		// nothing left to render
		restoreMutedState();
		emit finished();
	}
	else
	{
		// pop the next track from our rendering queue
		Track* renderTrack = m_tracksToRender.back();
		m_tracksToRender.pop_back();

		// mute everything but the track we are about to render
		for (auto track : m_unmuted)
		{
			track->setMuted(track != renderTrack);
		}

		// for multi-render, prefix each output file with a different number
		int trackNum = m_tracksToRender.size() + 1;

		render( pathForTrack(renderTrack, trackNum) );
	}
}

// Render the song into individual tracks
void RenderManager::renderTracks()
{
	const TrackContainer::TrackList & tl = Engine::getSong()->tracks();

	// find all currently unnmuted tracks -- we want to render these.
	for( auto it = tl.begin(); it != tl.end(); ++it )
	{
		Track* tk = (*it);
		Track::TrackTypes type = tk->type();

		// Don't render automation tracks
		if ( tk->isMuted() == false &&
				( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
		{
			m_unmuted.push_back(tk);
		}
	}

	const TrackContainer::TrackList t2 = Engine::getBBTrackContainer()->tracks();
	for( auto it = t2.begin(); it != t2.end(); ++it )
	{
		Track* tk = (*it);
		Track::TrackTypes type = tk->type();

		// Don't render automation tracks
		if ( tk->isMuted() == false &&
				( type == Track::InstrumentTrack || type == Track::SampleTrack ) )
		{
			m_unmuted.push_back(tk);
		}
	}

	// copy the list of unmuted tracks into our rendering queue.
	// we need to remember which tracks were unmuted to restore state at the end.
	m_tracksToRender = m_unmuted;

	renderNextTrack();
}

// Render the song into a single track
void RenderManager::renderProject()
{
	render( m_outputPath );
}

void RenderManager::render(QString outputPath)
{
	m_activeRenderer = std::make_unique<ProjectRenderer>(
			m_qualitySettings,
			m_outputSettings,
			m_format,
			outputPath);

	if( m_activeRenderer->isReady() )
	{
		// pass progress signals through
		connect( m_activeRenderer.get(), SIGNAL( progressChanged( int ) ),
				this, SIGNAL( progressChanged( int ) ) );

		// when it is finished, render the next track.
		// if we have not queued any tracks, renderNextTrack will just clean up
		connect( m_activeRenderer.get(), SIGNAL( finished() ),
				this, SLOT( renderNextTrack() ) );

		m_activeRenderer->startProcessing();
	}
	else
	{
		qDebug( "Renderer failed to acquire a file device!" );
		renderNextTrack();
	}
}

// Unmute all tracks that were muted while rendering tracks
void RenderManager::restoreMutedState()
{
	while( !m_unmuted.isEmpty() )
	{
		Track* restoreTrack = m_unmuted.back();
		m_unmuted.pop_back();
		restoreTrack->setMuted( false );
	}
}

// Determine the output path for a track when rendering tracks individually
QString RenderManager::pathForTrack(const Track *track, int num)
{
	QString extension = ProjectRenderer::getFileExtensionFromFormat( m_format );
	QString name = track->name();
	name = name.remove(QRegExp(FILENAME_FILTER));
	name = QString( "%1_%2%3" ).arg( num ).arg( name ).arg( extension );
	return QDir(m_outputPath).filePath(name);
}

void RenderManager::updateConsoleProgress()
{
	if ( m_activeRenderer )
	{
		m_activeRenderer->updateConsoleProgress();

		int totalNum = m_unmuted.size();
		if ( totalNum > 0 )
		{
			// we are rendering multiple tracks, append a track counter to the output
			int trackNum = totalNum - m_tracksToRender.size();
			fprintf( stderr, "(%d/%d)", trackNum, totalNum );
		}
	}
}


code:
/*
 * RingBuffer.cpp - an effective and flexible implementation of a ringbuffer for LMMS
 *
 * Copyright (c) 2014 Vesa Kivimäki
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "RingBuffer.h"
#include "Engine.h"
#include "Mixer.h"
#include "MixHelpers.h"

RingBuffer::RingBuffer( f_cnt_t size ) :
	m_fpp( Engine::mixer()->framesPerPeriod() ),
	m_samplerate( Engine::mixer()->processingSampleRate() ),
	m_size( size + m_fpp )
{
	m_buffer = new sampleFrame[ m_size ];
	memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
	m_position = 0;
}

RingBuffer::RingBuffer( float size ) :
	m_fpp( Engine::mixer()->framesPerPeriod() ),
	m_samplerate( Engine::mixer()->processingSampleRate() )
{
	m_size = msToFrames( size ) + m_fpp;
	m_buffer = new sampleFrame[ m_size ];
	memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
	m_position = 0;
	setSamplerateAware( true );
	//qDebug( "m_size %d, m_position %d", m_size, m_position );
}

RingBuffer::~RingBuffer()
{
	delete[] m_buffer;
}

void RingBuffer::reset()
{
	memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
	m_position = 0;
}

void RingBuffer::changeSize( f_cnt_t size )
{
	size += m_fpp;
	sampleFrame * tmp = m_buffer;
	m_size = size;
	m_buffer = new sampleFrame[ m_size ];
	memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
	m_position = 0;
	delete[] tmp;
}

void RingBuffer::changeSize( float size )
{
	changeSize( msToFrames( size ) );
}

void RingBuffer::setSamplerateAware( bool b )
{
	if( b )
	{
		connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ), Qt::UniqueConnection );
	}
	else
	{
		disconnect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSamplerate() ) );
	}
}

void RingBuffer::advance()
{
	m_position = ( m_position + m_fpp ) % m_size;
}

void RingBuffer::movePosition( f_cnt_t amount )
{
	m_position = ( m_position + amount ) % m_size;
}

void RingBuffer::movePosition( float amount )
{
	movePosition( msToFrames( amount ) );
}

void RingBuffer::pop( sampleFrame * dst )
{
	if( m_position + m_fpp <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		memcpy( dst, & m_buffer [ m_position ], m_fpp * sizeof( sampleFrame ) );
		memset( & m_buffer[m_position], 0, m_fpp * sizeof( sampleFrame ) );
	}
	else
	{
		f_cnt_t first = m_size - m_position;
		f_cnt_t second = m_fpp - first;

		memcpy( dst, & m_buffer [ m_position ], first * sizeof( sampleFrame ) );
		memset( & m_buffer [m_position], 0, first * sizeof( sampleFrame ) );

		memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) );
		memset( m_buffer, 0, second * sizeof( sampleFrame ) );
	}

	m_position = ( m_position + m_fpp ) % m_size;
}

void RingBuffer::read( sampleFrame * dst, f_cnt_t offset )
{
	f_cnt_t pos = ( m_position + offset ) % m_size;
	if( pos < 0 ) { pos += m_size; }

	if( pos + m_fpp <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		memcpy( dst, & m_buffer [pos], m_fpp * sizeof( sampleFrame ) );
	}
	else
	{
		f_cnt_t first = m_size - pos;
		f_cnt_t second = m_fpp - first;

		memcpy( dst, & m_buffer [pos], first * sizeof( sampleFrame ) );

		memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) );
	}
}

void RingBuffer::read( sampleFrame * dst, float offset )
{
	read( dst, msToFrames( offset ) );
}

void RingBuffer::read( sampleFrame * dst, f_cnt_t offset, f_cnt_t length )
{
	f_cnt_t pos = ( m_position + offset ) % m_size;
	if( pos < 0 ) { pos += m_size; }

	if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		memcpy( dst, & m_buffer [pos], length * sizeof( sampleFrame ) );
	}
	else
	{
		f_cnt_t first = m_size - pos;
		f_cnt_t second = length - first;

		memcpy( dst, & m_buffer [pos], first * sizeof( sampleFrame ) );

		memcpy( & dst [first], m_buffer, second * sizeof( sampleFrame ) );
	}
}

void RingBuffer::read( sampleFrame * dst, float offset, f_cnt_t length )
{
	read( dst, msToFrames( offset ), length );
}

void RingBuffer::write( sampleFrame * src, f_cnt_t offset, f_cnt_t length )
{
	const f_cnt_t pos = ( m_position + offset ) % m_size;
	if( length == 0 ) { length = m_fpp; }

	if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		memcpy( & m_buffer [pos], src, length * sizeof( sampleFrame ) );
	}
	else
	{
		f_cnt_t first = m_size - pos;
		f_cnt_t second = length - first;

		memcpy( & m_buffer [pos], src, first * sizeof( sampleFrame ) );

		memcpy( m_buffer, & src [first], second * sizeof( sampleFrame ) );
	}
}

void RingBuffer::write( sampleFrame * src, float offset, f_cnt_t length )
{
	write( src, msToFrames( offset ), length );
}

void RingBuffer::writeAdding( sampleFrame * src, f_cnt_t offset, f_cnt_t length )
{
	const f_cnt_t pos = ( m_position + offset ) % m_size;
	if( length == 0 ) { length = m_fpp; }

	if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		MixHelpers::add( & m_buffer [pos], src, length );
	}
	else
	{
		f_cnt_t first = m_size - pos;
		f_cnt_t second = length - first;

		MixHelpers::add( & m_buffer[pos], src, first );

		MixHelpers::add( m_buffer, & src[first], second );
	}
}

void RingBuffer::writeAdding( sampleFrame * src, float offset, f_cnt_t length )
{
	writeAdding( src, msToFrames( offset ), length );
}

void RingBuffer::writeAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level )
{
	const f_cnt_t pos = ( m_position + offset ) % m_size;
	//qDebug( "pos %d m_pos %d ofs %d siz %d", pos, m_position, offset, m_size );
	if( length == 0 ) { length = m_fpp; }

	if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		MixHelpers::addMultiplied( & m_buffer[pos], src, level, length );
	}
	else
	{
		f_cnt_t first = m_size - pos;
		f_cnt_t second = length - first;

		MixHelpers::addMultiplied( & m_buffer[pos], src, level, first );

		MixHelpers::addMultiplied( m_buffer, & src [first], level, second );
	}
}

void RingBuffer::writeAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level )
{
	f_cnt_t ofs = msToFrames( offset );
	writeAddingMultiplied( src, ofs, length, level );
}

void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, f_cnt_t offset, f_cnt_t length, float level )
{
	const f_cnt_t pos = ( m_position + offset ) % m_size;
	if( length == 0 ) { length = m_fpp; }

	if( pos + length <= m_size ) // we won't go over the edge so we can just memcpy here
	{
		MixHelpers::addSwappedMultiplied( & m_buffer [pos], src, level, length );
	}
	else
	{
		f_cnt_t first = m_size - pos;
		f_cnt_t second = length - first;

		MixHelpers::addSwappedMultiplied( & m_buffer [pos], src, level, first );

		MixHelpers::addSwappedMultiplied( m_buffer, & src [first], level, second );
	}
}

void RingBuffer::writeSwappedAddingMultiplied( sampleFrame * src, float offset, f_cnt_t length, float level )
{
	writeSwappedAddingMultiplied( src, msToFrames( offset ), length, level );
}

void RingBuffer::updateSamplerate()
{
	float newsize = static_cast<float>( ( m_size - m_fpp ) * Engine::mixer()->processingSampleRate() ) / m_samplerate;
	m_size = static_cast<f_cnt_t>( ceilf( newsize ) ) + m_fpp;
	m_samplerate = Engine::mixer()->processingSampleRate();
	delete[] m_buffer;
	m_buffer = new sampleFrame[ m_size ];
	memset( m_buffer, 0, m_size * sizeof( sampleFrame ) );
	m_position = 0;
}

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * SampleBuffer.cpp - container-class SampleBuffer
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "SampleBuffer.h"

#include <algorithm>

#include <QBuffer>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QPainter>

#include <sndfile.h>

#define OV_EXCLUDE_STATIC_CALLBACKS
#ifdef LMMS_HAVE_OGGVORBIS
#include <vorbis/vorbisfile.h>
#endif

#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H
#include <FLAC/stream_encoder.h>
#endif

#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H
#include <FLAC/stream_decoder.h>
#endif

#include "base64.h"
#include "ConfigManager.h"
#include "DrumSynth.h"
#include "endian_handling.h"
#include "Engine.h"
#include "GuiApplication.h"
#include "Mixer.h"
#include "PathUtil.h"

#include "FileDialog.h"

SampleBuffer::SampleBuffer() :
	m_audioFile(""),
	m_origData(nullptr),
	m_origFrames(0),
	m_data(nullptr),
	m_frames(0),
	m_startFrame(0),
	m_endFrame(0),
	m_loopStartFrame(0),
	m_loopEndFrame(0),
	m_amplification(1.0f),
	m_reversed(false),
	m_frequency(BaseFreq),
	m_sampleRate(mixerSampleRate())
{

	connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(sampleRateChanged()));
	update();
}

SampleBuffer::SampleBuffer(const QString & audioFile, bool isBase64Data)
	: SampleBuffer()
{
	if (isBase64Data)
	{
		loadFromBase64(audioFile);
	}
	else
	{
		m_audioFile = audioFile;
		update();
	}
}

SampleBuffer::SampleBuffer(const sampleFrame * data, const f_cnt_t frames)
	: SampleBuffer()
{
	if (frames > 0)
	{
		m_origData = MM_ALLOC(sampleFrame, frames);
		memcpy(m_origData, data, frames * BYTES_PER_FRAME);
		m_origFrames = frames;
		update();
	}
}

SampleBuffer::SampleBuffer(const f_cnt_t frames)
	: SampleBuffer()
{
	if (frames > 0)
	{
		m_origData = MM_ALLOC(sampleFrame, frames);
		memset(m_origData, 0, frames * BYTES_PER_FRAME);
		m_origFrames = frames;
		update();
	}
}

SampleBuffer::SampleBuffer(const SampleBuffer& orig)
{
	orig.m_varLock.lockForRead();

	m_audioFile = orig.m_audioFile;
	m_origFrames = orig.m_origFrames;
	m_origData = (m_origFrames > 0) ? MM_ALLOC(sampleFrame, m_origFrames) : nullptr;
	m_frames = orig.m_frames;
	m_data = (m_frames > 0) ? MM_ALLOC(sampleFrame, m_frames) : nullptr;
	m_startFrame = orig.m_startFrame;
	m_endFrame = orig.m_endFrame;
	m_loopStartFrame = orig.m_loopStartFrame;
	m_loopEndFrame = orig.m_loopEndFrame;
	m_amplification = orig.m_amplification;
	m_reversed = orig.m_reversed;
	m_frequency = orig.m_frequency;
	m_sampleRate = orig.m_sampleRate;

	//Deep copy m_origData and m_data from original
	const auto origFrameBytes = m_origFrames * BYTES_PER_FRAME;
	const auto frameBytes = m_frames * BYTES_PER_FRAME;
	if (orig.m_origData != nullptr && origFrameBytes > 0)
		{ memcpy(m_origData, orig.m_origData, origFrameBytes); }
	if (orig.m_data != nullptr && frameBytes > 0)
		{ memcpy(m_data, orig.m_data, frameBytes); }

	orig.m_varLock.unlock();
}

void swap(SampleBuffer& first, SampleBuffer& second) noexcept
{
	using std::swap;

	// Lock both buffers for writing, with address as lock ordering
	if (&first == &second) { return; }
	else if (&first > &second)
	{
		first.m_varLock.lockForWrite();
		second.m_varLock.lockForWrite();
	}
	else
	{
		second.m_varLock.lockForWrite();
		first.m_varLock.lockForWrite();
	}

	first.m_audioFile.swap(second.m_audioFile);
	swap(first.m_origData, second.m_origData);
	swap(first.m_data, second.m_data);
	swap(first.m_origFrames, second.m_origFrames);
	swap(first.m_frames, second.m_frames);
	swap(first.m_startFrame, second.m_startFrame);
	swap(first.m_endFrame, second.m_endFrame);
	swap(first.m_loopStartFrame, second.m_loopStartFrame);
	swap(first.m_loopEndFrame, second.m_loopEndFrame);
	swap(first.m_amplification, second.m_amplification);
	swap(first.m_frequency, second.m_frequency);
	swap(first.m_reversed, second.m_reversed);
	swap(first.m_sampleRate, second.m_sampleRate);

	// Unlock again
	first.m_varLock.unlock();
	second.m_varLock.unlock();
}

SampleBuffer& SampleBuffer::operator=(SampleBuffer that)
{
	swap(*this, that);
	return *this;
}

SampleBuffer::~SampleBuffer()
{
	MM_FREE(m_origData);
	MM_FREE(m_data);
}

void SampleBuffer::sampleRateChanged()
{
	update(true);
}

sample_rate_t SampleBuffer::mixerSampleRate()
{
	return Engine::mixer()->processingSampleRate();
}

void SampleBuffer::update(bool keepSettings)
{
	const bool lock = (m_data != nullptr);
	if (lock)
	{
		Engine::mixer()->requestChangeInModel();
		m_varLock.lockForWrite();
		MM_FREE(m_data);
	}

	// File size and sample length limits
	const int fileSizeMax = 300; // MB
	const int sampleLengthMax = 90; // Minutes

	bool fileLoadError = false;
	if (m_audioFile.isEmpty() && m_origData != nullptr && m_origFrames > 0)
	{
		// TODO: reverse- and amplification-property is not covered
		// by following code...
		m_data = MM_ALLOC(sampleFrame, m_origFrames);
		memcpy(m_data, m_origData, m_origFrames * BYTES_PER_FRAME);
		if (keepSettings == false)
		{
			m_frames = m_origFrames;
			m_loopStartFrame = m_startFrame = 0;
			m_loopEndFrame = m_endFrame = m_frames;
		}
	}
	else if (!m_audioFile.isEmpty())
	{
		QString file = PathUtil::toAbsolute(m_audioFile);
		int_sample_t * buf = nullptr;
		sample_t * fbuf = nullptr;
		ch_cnt_t channels = DEFAULT_CHANNELS;
		sample_rate_t samplerate = mixerSampleRate();
		m_frames = 0;

		const QFileInfo fileInfo(file);
		if (fileInfo.size() > fileSizeMax * 1024 * 1024)
		{
			fileLoadError = true;
		}
		else
		{
			// Use QFile to handle unicode file names on Windows
			QFile f(file);
			SNDFILE * sndFile;
			SF_INFO sfInfo;
			sfInfo.format = 0;
			if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false)))
			{
				f_cnt_t frames = sfInfo.frames;
				int rate = sfInfo.samplerate;
				if (frames / rate > sampleLengthMax * 60)
				{
					fileLoadError = true;
				}
				sf_close(sndFile);
			}
			f.close();
		}

		if (!fileLoadError)
		{
#ifdef LMMS_HAVE_OGGVORBIS
			// workaround for a bug in libsndfile or our libsndfile decoder
			// causing some OGG files to be distorted -> try with OGG Vorbis
			// decoder first if filename extension matches "ogg"
			if (m_frames == 0 && fileInfo.suffix() == "ogg")
			{
				m_frames = decodeSampleOGGVorbis(file, buf, channels, samplerate);
			}
#endif
			if (m_frames == 0)
			{
				m_frames = decodeSampleSF(file, fbuf, channels, samplerate);
			}
#ifdef LMMS_HAVE_OGGVORBIS
			if (m_frames == 0)
			{
				m_frames = decodeSampleOGGVorbis(file, buf, channels, samplerate);
			}
#endif
			if (m_frames == 0)
			{
				m_frames = decodeSampleDS(file, buf, channels, samplerate);
			}
		}

		if (m_frames == 0 || fileLoadError)  // if still no frames, bail
		{
			// sample couldn't be decoded, create buffer containing
			// one sample-frame
			m_data = MM_ALLOC(sampleFrame, 1);
			memset(m_data, 0, sizeof(*m_data));
			m_frames = 1;
			m_loopStartFrame = m_startFrame = 0;
			m_loopEndFrame = m_endFrame = 1;
		}
		else // otherwise normalize sample rate
		{
			normalizeSampleRate(samplerate, keepSettings);
		}
	}
	else
	{
		// neither an audio-file nor a buffer to copy from, so create
		// buffer containing one sample-frame
		m_data = MM_ALLOC(sampleFrame, 1);
		memset(m_data, 0, sizeof(*m_data));
		m_frames = 1;
		m_loopStartFrame = m_startFrame = 0;
		m_loopEndFrame = m_endFrame = 1;
	}

	if (lock)
	{
		m_varLock.unlock();
		Engine::mixer()->doneChangeInModel();
	}

	emit sampleUpdated();

	if (fileLoadError)
	{
		QString title = tr("Fail to open file");
		QString message = tr("Audio files are limited to %1 MB "
				"in size and %2 minutes of playing time"
				).arg(fileSizeMax).arg(sampleLengthMax);
		if (gui)
		{
			QMessageBox::information(nullptr,
				title, message,	QMessageBox::Ok);
		}
		else
		{
			fprintf(stderr, "%s\n", message.toUtf8().constData());
		}
	}
}

void SampleBuffer::convertIntToFloat(
	int_sample_t * & ibuf,
	f_cnt_t frames,
	int channels
)
{
	// following code transforms int-samples into float-samples and does amplifying & reversing
	const float fac = 1 / OUTPUT_SAMPLE_MULTIPLIER;
	m_data = MM_ALLOC(sampleFrame, frames);
	const int ch = (channels > 1) ? 1 : 0;

	// if reversing is on, we also reverse when scaling
	bool isReversed = m_reversed;
	int idx = isReversed ? (frames - 1) * channels : 0;
	for (f_cnt_t frame = 0; frame < frames; ++frame)
	{
		m_data[frame][0] = ibuf[idx+0] * fac;
		m_data[frame][1] = ibuf[idx+ch] * fac;
		idx += isReversed ? -channels : channels;
	}

	delete[] ibuf;
}

void SampleBuffer::directFloatWrite(
	sample_t * & fbuf,
	f_cnt_t frames,
	int channels
)
{

	m_data = MM_ALLOC(sampleFrame, frames);
	const int ch = (channels > 1) ? 1 : 0;

	// if reversing is on, we also reverse when scaling
	bool isReversed = m_reversed;
	int idx = isReversed ? (frames - 1) * channels : 0;
	for (f_cnt_t frame = 0; frame < frames; ++frame)
	{
		m_data[frame][0] = fbuf[idx+0];
		m_data[frame][1] = fbuf[idx+ch];
		idx += isReversed ? -channels : channels;
	}

	delete[] fbuf;
}

void SampleBuffer::normalizeSampleRate(const sample_rate_t srcSR, bool keepSettings)
{
	const sample_rate_t oldRate = m_sampleRate;
	// do samplerate-conversion to our default-samplerate
	if (srcSR != mixerSampleRate())
	{
		SampleBuffer * resampled = resample(srcSR, mixerSampleRate());

		m_sampleRate = mixerSampleRate();
		MM_FREE(m_data);
		m_frames = resampled->frames();
		m_data = MM_ALLOC(sampleFrame, m_frames);
		memcpy(m_data, resampled->data(), m_frames * sizeof(sampleFrame));
		delete resampled;
	}

	if (keepSettings == false)
	{
		// update frame-variables
		m_loopStartFrame = m_startFrame = 0;
		m_loopEndFrame = m_endFrame = m_frames;
	}
	else if (oldRate != mixerSampleRate())
	{
		auto oldRateToNewRateRatio = static_cast<float>(mixerSampleRate()) / oldRate;

		m_startFrame = qBound(0, f_cnt_t(m_startFrame * oldRateToNewRateRatio), m_frames);
		m_endFrame = qBound(m_startFrame, f_cnt_t(m_endFrame * oldRateToNewRateRatio), m_frames);
		m_loopStartFrame = qBound(0, f_cnt_t(m_loopStartFrame * oldRateToNewRateRatio), m_frames);
		m_loopEndFrame = qBound(m_loopStartFrame, f_cnt_t(m_loopEndFrame * oldRateToNewRateRatio), m_frames);
		m_sampleRate = mixerSampleRate();
	}
}

f_cnt_t SampleBuffer::decodeSampleSF(
	QString fileName,
	sample_t * & buf,
	ch_cnt_t & channels,
	sample_rate_t & samplerate
)
{
	SNDFILE * sndFile;
	SF_INFO sfInfo;
	sfInfo.format = 0;
	f_cnt_t frames = 0;
	sf_count_t sfFramesRead;

	// Use QFile to handle unicode file names on Windows
	QFile f(fileName);
	if (f.open(QIODevice::ReadOnly) && (sndFile = sf_open_fd(f.handle(), SFM_READ, &sfInfo, false)))
	{
		frames = sfInfo.frames;

		buf = new sample_t[sfInfo.channels * frames];
		sfFramesRead = sf_read_float(sndFile, buf, sfInfo.channels * frames);

		if (sfFramesRead < sfInfo.channels * frames)
		{
#ifdef DEBUG_LMMS
			qDebug("SampleBuffer::decodeSampleSF(): could not read"
				" sample %s: %s", fileName, sf_strerror(nullptr));
#endif
		}
		channels = sfInfo.channels;
		samplerate = sfInfo.samplerate;

		sf_close(sndFile);
	}
	else
	{
#ifdef DEBUG_LMMS
		qDebug("SampleBuffer::decodeSampleSF(): could not load "
				"sample %s: %s", fileName, sf_strerror(nullptr));
#endif
	}
	f.close();

	//write down either directly or convert i->f depending on file type

	if (frames > 0 && buf != nullptr)
	{
		directFloatWrite(buf, frames, channels);
	}

	return frames;
}

#ifdef LMMS_HAVE_OGGVORBIS

// callback-functions for reading ogg-file

size_t qfileReadCallback(void * ptr, size_t size, size_t n, void * udata )
{
	return static_cast<QFile *>(udata)->read((char*) ptr, size * n);
}

int qfileSeekCallback(void * udata, ogg_int64_t offset, int whence)
{
	QFile * f = static_cast<QFile *>(udata);

	if (whence == SEEK_CUR)
	{
		f->seek(f->pos() + offset);
	}
	else if (whence == SEEK_END)
	{
		f->seek(f->size() + offset);
	}
	else
	{
		f->seek(offset);
	}
	return 0;
}

int qfileCloseCallback(void * udata)
{
	delete static_cast<QFile *>(udata);
	return 0;
}

long qfileTellCallback(void * udata)
{
	return static_cast<QFile *>(udata)->pos();
}

f_cnt_t SampleBuffer::decodeSampleOGGVorbis(
	QString fileName,
	int_sample_t * & buf,
	ch_cnt_t & channels,
	sample_rate_t & samplerate
)
{
	static ov_callbacks callbacks =
	{
		qfileReadCallback,
		qfileSeekCallback,
		qfileCloseCallback,
		qfileTellCallback
	} ;

	OggVorbis_File vf;

	f_cnt_t frames = 0;

	QFile * f = new QFile(fileName);
	if (f->open(QFile::ReadOnly) == false)
	{
		delete f;
		return 0;
	}

	int err = ov_open_callbacks(f, &vf, nullptr, 0, callbacks);

	if (err < 0)
	{
		switch (err)
		{
			case OV_EREAD:
				printf("SampleBuffer::decodeSampleOGGVorbis():"
						" media read error\n");
				break;
			case OV_ENOTVORBIS:
				printf("SampleBuffer::decodeSampleOGGVorbis():"
					" not an Ogg Vorbis file\n");
				break;
			case OV_EVERSION:
				printf("SampleBuffer::decodeSampleOGGVorbis():"
						" vorbis version mismatch\n");
				break;
			case OV_EBADHEADER:
				printf("SampleBuffer::decodeSampleOGGVorbis():"
					" invalid Vorbis bitstream header\n");
				break;
			case OV_EFAULT:
				printf("SampleBuffer::decodeSampleOgg(): "
					"internal logic fault\n");
				break;
		}
		delete f;
		return 0;
	}

	ov_pcm_seek(&vf, 0);

	channels = ov_info(&vf, -1)->channels;
	samplerate = ov_info(&vf, -1)->rate;

	ogg_int64_t total = ov_pcm_total(&vf, -1);

	buf = new int_sample_t[total * channels];
	int bitstream = 0;
	long bytesRead = 0;

	do
	{
		bytesRead = ov_read(&vf,
				(char *) &buf[frames * channels],
				(total - frames) * channels * BYTES_PER_INT_SAMPLE,
				isLittleEndian() ? 0 : 1,
				BYTES_PER_INT_SAMPLE,
				1,
				&bitstream
		);

		if (bytesRead < 0)
		{
			break;
		}
		frames += bytesRead / (channels * BYTES_PER_INT_SAMPLE);
	}
	while (bytesRead != 0 && bitstream == 0);

	ov_clear(&vf);

	// if buffer isn't empty, convert it to float and write it down
	if (frames > 0 && buf != nullptr)
	{
		convertIntToFloat(buf, frames, channels);
	}

	return frames;
}
#endif

f_cnt_t SampleBuffer::decodeSampleDS(
	QString fileName,
	int_sample_t * & buf,
	ch_cnt_t & channels,
	sample_rate_t & samplerate
)
{
	DrumSynth ds;
	f_cnt_t frames = ds.GetDSFileSamples(fileName, buf, channels, samplerate);

	if (frames > 0 && buf != nullptr)
	{
		convertIntToFloat(buf, frames, channels);
	}

	return frames;

}

bool SampleBuffer::play(
	sampleFrame * ab,
	handleState * state,
	const fpp_t frames,
	const float freq,
	const LoopMode loopMode
)
{
	f_cnt_t startFrame = m_startFrame;
	f_cnt_t endFrame = m_endFrame;
	f_cnt_t loopStartFrame = m_loopStartFrame;
	f_cnt_t loopEndFrame = m_loopEndFrame;

	if (endFrame == 0 || frames == 0)
	{
		return false;
	}

	// variable for determining if we should currently be playing backwards in a ping-pong loop
	bool isBackwards = state->isBackwards();

	const double freqFactor = (double) freq / (double) m_frequency *
		m_sampleRate / Engine::mixer()->processingSampleRate();

	// calculate how many frames we have in requested pitch
	const f_cnt_t totalFramesForCurrentPitch = static_cast<f_cnt_t>(
		(endFrame - startFrame) / freqFactor
	);

	if (totalFramesForCurrentPitch == 0)
	{
		return false;
	}

	// this holds the index of the first frame to play
	f_cnt_t playFrame = qMax(state->m_frameIndex, startFrame);

	if (loopMode == LoopOff)
	{
		if (playFrame >= endFrame || (endFrame - playFrame) / freqFactor == 0)
		{
			// the sample is done being played
			return false;
		}
	}
	else if (loopMode == LoopOn)
	{
		playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame);
	}
	else
	{
		playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame);
	}

	f_cnt_t fragmentSize = (f_cnt_t)(frames * freqFactor) + MARGIN[state->interpolationMode()];

	sampleFrame * tmp = nullptr;

	// check whether we have to change pitch...
	if (freqFactor != 1.0 || state->m_varyingPitch)
	{
		SRC_DATA srcData;
		// Generate output
		srcData.data_in =
			getSampleFragment(playFrame, fragmentSize, loopMode, &tmp, &isBackwards,
			loopStartFrame, loopEndFrame, endFrame )->data();
		srcData.data_out = ab->data();
		srcData.input_frames = fragmentSize;
		srcData.output_frames = frames;
		srcData.src_ratio = 1.0 / freqFactor;
		srcData.end_of_input = 0;
		int error = src_process(state->m_resamplingData, &srcData);
		if (error)
		{
			printf("SampleBuffer: error while resampling: %s\n",
							src_strerror(error));
		}
		if (srcData.output_frames_gen > frames)
		{
			printf("SampleBuffer: not enough frames: %ld / %d\n",
					srcData.output_frames_gen, frames);
		}
		// Advance
		switch (loopMode)
		{
			case LoopOff:
				playFrame += srcData.input_frames_used;
				break;
			case LoopOn:
				playFrame += srcData.input_frames_used;
				playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame);
				break;
			case LoopPingPong:
			{
				f_cnt_t left = srcData.input_frames_used;
				if (state->isBackwards())
				{
					playFrame -= srcData.input_frames_used;
					if (playFrame < loopStartFrame)
					{
						left -= (loopStartFrame - playFrame);
						playFrame = loopStartFrame;
					}
					else left = 0;
				}
				playFrame += left;
				playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame);
				break;
			}
		}
	}
	else
	{
		// we don't have to pitch, so we just copy the sample-data
		// as is into pitched-copy-buffer

		// Generate output
		memcpy(ab,
			getSampleFragment(playFrame, frames, loopMode, &tmp, &isBackwards,
				loopStartFrame, loopEndFrame, endFrame),
			frames * BYTES_PER_FRAME);
		// Advance
		switch (loopMode)
		{
			case LoopOff:
				playFrame += frames;
				break;
			case LoopOn:
				playFrame += frames;
				playFrame = getLoopedIndex(playFrame, loopStartFrame, loopEndFrame);
				break;
			case LoopPingPong:
			{
				f_cnt_t left = frames;
				if (state->isBackwards())
				{
					playFrame -= frames;
					if (playFrame < loopStartFrame)
					{
						left -= (loopStartFrame - playFrame);
						playFrame = loopStartFrame;
					}
					else left = 0;
				}
				playFrame += left;
				playFrame = getPingPongIndex(playFrame, loopStartFrame, loopEndFrame);
				break;
			}
		}
	}

	if (tmp != nullptr)
	{
		MM_FREE(tmp);
	}

	state->setBackwards(isBackwards);
	state->setFrameIndex(playFrame);

	for (fpp_t i = 0; i < frames; ++i)
	{
		ab[i][0] *= m_amplification;
		ab[i][1] *= m_amplification;
	}

	return true;
}

sampleFrame * SampleBuffer::getSampleFragment(
	f_cnt_t index,
	f_cnt_t frames,
	LoopMode loopMode,
	sampleFrame * * tmp,
	bool * backwards,
	f_cnt_t loopStart,
	f_cnt_t loopEnd,
	f_cnt_t end
) const
{
	if (loopMode == LoopOff)
	{
		if (index + frames <= end)
		{
			return m_data + index;
		}
	}
	else if (loopMode == LoopOn)
	{
		if (index + frames <= loopEnd)
		{
			return m_data + index;
		}
	}
	else
	{
		if (!*backwards && index + frames < loopEnd)
		{
			return m_data + index;
		}
	}

	*tmp = MM_ALLOC(sampleFrame, frames);

	if (loopMode == LoopOff)
	{
		f_cnt_t available = end - index;
		memcpy(*tmp, m_data + index, available * BYTES_PER_FRAME);
		memset(*tmp + available, 0, (frames - available) * BYTES_PER_FRAME);
	}
	else if (loopMode == LoopOn)
	{
		f_cnt_t copied = qMin(frames, loopEnd - index);
		memcpy(*tmp, m_data + index, copied * BYTES_PER_FRAME);
		f_cnt_t loopFrames = loopEnd - loopStart;
		while (copied < frames)
		{
			f_cnt_t todo = qMin(frames - copied, loopFrames);
			memcpy(*tmp + copied, m_data + loopStart, todo * BYTES_PER_FRAME);
			copied += todo;
		}
	}
	else
	{
		f_cnt_t pos = index;
		bool currentBackwards = pos < loopStart
			? false
			: *backwards;
		f_cnt_t copied = 0;

		if (currentBackwards)
		{
			copied = qMin(frames, pos - loopStart);
			for (int i = 0; i < copied; i++)
			{
				(*tmp)[i][0] = m_data[pos - i][0];
				(*tmp)[i][1] = m_data[pos - i][1];
			}
			pos -= copied;
			if (pos == loopStart) { currentBackwards = false; }
		}
		else
		{
			copied = qMin(frames, loopEnd - pos);
			memcpy(*tmp, m_data + pos, copied * BYTES_PER_FRAME);
			pos += copied;
			if (pos == loopEnd) { currentBackwards = true; }
		}

		while (copied < frames)
		{
			if (currentBackwards)
			{
				f_cnt_t todo = qMin(frames - copied, pos - loopStart);
				for (int i = 0; i < todo; i++)
				{
					(*tmp)[copied + i][0] = m_data[pos - i][0];
					(*tmp)[copied + i][1] = m_data[pos - i][1];
				}
				pos -= todo;
				copied += todo;
				if (pos <= loopStart) { currentBackwards = false; }
			}
			else
			{
				f_cnt_t todo = qMin(frames - copied, loopEnd - pos);
				memcpy(*tmp + copied, m_data + pos, todo * BYTES_PER_FRAME);
				pos += todo;
				copied += todo;
				if (pos >= loopEnd) { currentBackwards = true; }
			}
		}
		*backwards = currentBackwards;
	}

	return *tmp;
}

f_cnt_t SampleBuffer::getLoopedIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const
{
	if (index < endf)
	{
		return index;
	}
	return startf + (index - startf) % (endf - startf);
}

f_cnt_t SampleBuffer::getPingPongIndex(f_cnt_t index, f_cnt_t startf, f_cnt_t endf) const
{
	if (index < endf)
	{
		return index;
	}
	const f_cnt_t loopLen = endf - startf;
	const f_cnt_t loopPos = (index - endf) % (loopLen * 2);

	return (loopPos < loopLen)
		? endf - loopPos
		: startf + (loopPos - loopLen);
}

void SampleBuffer::visualize(
	QPainter & p,
	const QRect & dr,
	const QRect & clip,
	f_cnt_t fromFrame,
	f_cnt_t toFrame
)
{
	if (m_frames == 0) { return; }

	const bool focusOnRange = toFrame <= m_frames && 0 <= fromFrame && fromFrame < toFrame;
	//p.setClipRect(clip);
	const int w = dr.width();
	const int h = dr.height();

	const int yb = h / 2 + dr.y();
	const float ySpace = h * 0.5f;
	const int nbFrames = focusOnRange ? toFrame - fromFrame : m_frames;

	const int fpp = qBound<int>(1, nbFrames / w, 20);
	QPointF * l = new QPointF[nbFrames / fpp + 1];
	QPointF * r = new QPointF[nbFrames / fpp + 1];
	int n = 0;
	const int xb = dr.x();
	const int first = focusOnRange ? fromFrame : 0;
	const int last = focusOnRange ? toFrame - 1 : m_frames - 1;

	for (int frame = first; frame <= last; frame += fpp)
	{
		auto x = xb + ((frame - first) * double(w) / nbFrames);
		// Partial Y calculation
		auto py = ySpace * m_amplification;
		l[n] = QPointF(x, (yb - (m_data[frame][0] * py)));
		r[n] = QPointF(x, (yb - (m_data[frame][1] * py)));
		++n;
	}

	p.setRenderHint(QPainter::Antialiasing);
	p.drawPolyline(l, nbFrames / fpp);
	p.drawPolyline(r, nbFrames / fpp);

	delete[] l;
	delete[] r;
}

QString SampleBuffer::openAudioFile() const
{
	FileDialog ofd(nullptr, tr("Open audio file"));

	QString dir;
	if (!m_audioFile.isEmpty())
	{
		QString f = m_audioFile;
		if (QFileInfo(f).isRelative())
		{
			f = ConfigManager::inst()->userSamplesDir() + f;
			if (QFileInfo(f).exists() == false)
			{
				f = ConfigManager::inst()->factorySamplesDir() +
								m_audioFile;
			}
		}
		dir = QFileInfo(f).absolutePath();
	}
	else
	{
		dir = ConfigManager::inst()->userSamplesDir();
	}
	// change dir to position of previously opened file
	ofd.setDirectory(dir);
	ofd.setFileMode(FileDialog::ExistingFiles);

	// set filters
	QStringList types;
	types << tr("All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc "
					"*.aif *.aiff *.au *.raw)")
		<< tr("Wave-Files (*.wav)")
		<< tr("OGG-Files (*.ogg)")
		<< tr("DrumSynth-Files (*.ds)")
		<< tr("FLAC-Files (*.flac)")
		<< tr("SPEEX-Files (*.spx)")
		//<< tr("MP3-Files (*.mp3)")
		//<< tr("MIDI-Files (*.mid)")
		<< tr("VOC-Files (*.voc)")
		<< tr("AIFF-Files (*.aif *.aiff)")
		<< tr("AU-Files (*.au)")
		<< tr("RAW-Files (*.raw)")
		//<< tr("MOD-Files (*.mod)")
		;
	ofd.setNameFilters(types);
	if (!m_audioFile.isEmpty())
	{
		// select previously opened file
		ofd.selectFile(QFileInfo(m_audioFile).fileName());
	}

	if (ofd.exec () == QDialog::Accepted)
	{
		if (ofd.selectedFiles().isEmpty())
		{
			return QString();
		}
		return PathUtil::toShortestRelative(ofd.selectedFiles()[0]);
	}

	return QString();
}

QString SampleBuffer::openAndSetAudioFile()
{
	QString fileName = this->openAudioFile();

	if(!fileName.isEmpty())
	{
		this->setAudioFile(fileName);
	}

	return fileName;
}

QString SampleBuffer::openAndSetWaveformFile()
{
	if (m_audioFile.isEmpty())
	{
		m_audioFile = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac";
	}

	QString fileName = this->openAudioFile();

	if (!fileName.isEmpty())
	{
		this->setAudioFile(fileName);
	}
	else
	{
		m_audioFile = "";
	}

	return fileName;
}

#undef LMMS_HAVE_FLAC_STREAM_ENCODER_H	/* not yet... */
#undef LMMS_HAVE_FLAC_STREAM_DECODER_H

#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H
FLAC__StreamEncoderWriteStatus flacStreamEncoderWriteCallback(
	const FLAC__StreamEncoder * /*encoder*/,
	const FLAC__byte buffer[],
	unsigned int /*samples*/,
	unsigned int bytes,
	unsigned int /*currentFrame*/,
	void * clientData
)
{
/*	if (bytes == 0)
	{
		return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
	}*/
	return (static_cast<QBuffer *>(clientData)->write(
				(const char *) buffer, bytes) == (int) bytes)
		? FLAC__STREAM_ENCODER_WRITE_STATUS_OK
		: FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
}

void flacStreamEncoderMetadataCallback(
	const FLAC__StreamEncoder *,
	const FLAC__StreamMetadata * metadata,
	void * clientData
)
{
	QBuffer * b = static_cast<QBuffer *>(clientData);
	b->seek(0);
	b->write((const char *) metadata, sizeof(*metadata));
}

#endif

QString & SampleBuffer::toBase64(QString & dst) const
{
#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H
	const f_cnt_t FRAMES_PER_BUF = 1152;

	FLAC__StreamEncoder * flacEnc = FLAC__stream_encoder_new();
	FLAC__stream_encoder_set_channels(flacEnc, DEFAULT_CHANNELS);
	FLAC__stream_encoder_set_blocksize(flacEnc, FRAMES_PER_BUF);
/*	FLAC__stream_encoder_set_do_exhaustive_model_search(flacEnc, true);
	FLAC__stream_encoder_set_do_mid_side_stereo(flacEnc, true);*/
	FLAC__stream_encoder_set_sample_rate(flacEnc,
		Engine::mixer()->sampleRate());

	QBuffer baWriter;
	baWriter.open(QBuffer::WriteOnly);

	FLAC__stream_encoder_set_write_callback(flacEnc,
		flacStreamEncoderWriteCallback);
	FLAC__stream_encoder_set_metadata_callback(flacEnc,
		flacStreamEncoderMetadataCallback);
	FLAC__stream_encoder_set_client_data(flacEnc, &baWriter);

	if (FLAC__stream_encoder_init(flacEnc) != FLAC__STREAM_ENCODER_OK)
	{
		printf("Error within FLAC__stream_encoder_init()!\n");
	}

	f_cnt_t frameCnt = 0;

	while (frameCnt < m_frames)
	{
		f_cnt_t remaining = qMin<f_cnt_t>(FRAMES_PER_BUF, m_frames - frameCnt);
		FLAC__int32 buf[FRAMES_PER_BUF * DEFAULT_CHANNELS];
		for (f_cnt_t f = 0; f < remaining; ++f)
		{
			for (ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch)
			{
				buf[f*DEFAULT_CHANNELS+ch] = (FLAC__int32)(
					Mixer::clip(m_data[f+frameCnt][ch]) *
						OUTPUT_SAMPLE_MULTIPLIER);
			}
		}
		FLAC__stream_encoder_process_interleaved(flacEnc, buf, remaining);
		frameCnt += remaining;
	}
	FLAC__stream_encoder_finish(flacEnc);
	FLAC__stream_encoder_delete(flacEnc);
	printf("%d %d\n", frameCnt, (int)baWriter.size());
	baWriter.close();

	base64::encode(baWriter.buffer().data(), baWriter.buffer().size(), dst);

#else	/* LMMS_HAVE_FLAC_STREAM_ENCODER_H */

	base64::encode((const char *) m_data,
		m_frames * sizeof(sampleFrame), dst);

#endif	/* LMMS_HAVE_FLAC_STREAM_ENCODER_H */

	return dst;
}

SampleBuffer * SampleBuffer::resample(const sample_rate_t srcSR, const sample_rate_t dstSR )
{
	sampleFrame * data = m_data;
	const f_cnt_t frames = m_frames;
	const f_cnt_t dstFrames = static_cast<f_cnt_t>((frames / (float) srcSR) * (float) dstSR);
	SampleBuffer * dstSB = new SampleBuffer(dstFrames);
	sampleFrame * dstBuf = dstSB->m_origData;

	// yeah, libsamplerate, let's rock with sinc-interpolation!
	int error;
	SRC_STATE * state;
	if ((state = src_new(SRC_SINC_MEDIUM_QUALITY, DEFAULT_CHANNELS, &error)) != nullptr)
	{
		SRC_DATA srcData;
		srcData.end_of_input = 1;
		srcData.data_in = data->data();
		srcData.data_out = dstBuf->data();
		srcData.input_frames = frames;
		srcData.output_frames = dstFrames;
		srcData.src_ratio = (double) dstSR / srcSR;
		if ((error = src_process(state, &srcData)))
		{
			printf("SampleBuffer: error while resampling: %s\n", src_strerror(error));
		}
		src_delete(state);
	}
	else
	{
		printf("Error: src_new() failed in sample_buffer.cpp!\n");
	}
	dstSB->update();
	return dstSB;
}

void SampleBuffer::setAudioFile(const QString & audioFile)
{
	m_audioFile = PathUtil::toShortestRelative(audioFile);
	update();
}

#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H

struct flacStreamDecoderClientData
{
	QBuffer * readBuffer;
	QBuffer * writeBuffer;
} ;

FLAC__StreamDecoderReadStatus flacStreamDecoderReadCallback(
	const FLAC__StreamDecoder * /*decoder*/,
	FLAC__byte * buffer,
	unsigned int * bytes,
	void * clientData
)
{
	int res = static_cast<flacStreamDecoderClientData *>(
		clientData)->readBuffer->read((char *) buffer, *bytes);

	if (res > 0)
	{
		*bytes = res;
		return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
	}

	*bytes = 0;
	return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
}

FLAC__StreamDecoderWriteStatus flacStreamDecoderWriteCallback(
	const FLAC__StreamDecoder * /*decoder*/,
	const FLAC__Frame * frame,
	const FLAC__int32 * const buffer[],
	void * clientData
)
{
	if (frame->header.channels != 2)
	{
		printf("channels != 2 in flacStreamDecoderWriteCallback()\n");
		return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
	}

	if (frame->header.bits_per_sample != 16)
	{
		printf("bits_per_sample != 16 in flacStreamDecoderWriteCallback()\n");
		return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
	}

	const f_cnt_t numberOfFrames = frame->header.blocksize;
	for (f_cnt_t f = 0; f < numberOfFrames; ++f)
	{
		sampleFrame sframe = { buffer[0][f] / OUTPUT_SAMPLE_MULTIPLIER,
					buffer[1][f] / OUTPUT_SAMPLE_MULTIPLIER
		} ;
		static_cast<flacStreamDecoderClientData *>(
					clientData )->writeBuffer->write(
				(const char *) sframe, sizeof(sframe));
	}
	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void flacStreamDecoderMetadataCallback(
	const FLAC__StreamDecoder *,
	const FLAC__StreamMetadata *,
	void * /*clientData*/
)
{
	printf("stream decoder metadata callback\n");
/*	QBuffer * b = static_cast<QBuffer *>(clientData);
	b->seek(0);
	b->write((const char *) metadata, sizeof(*metadata));*/
}

void flacStreamDecoderErrorCallback(
	const FLAC__StreamDecoder *,
	FLAC__StreamDecoderErrorStatus status,
	void * /*clientData*/
)
{
	printf("error callback! %d\n", status);
	// what to do now??
}

#endif

void SampleBuffer::loadFromBase64(const QString & data)
{
	char * dst = nullptr;
	int dsize = 0;
	base64::decode(data, &dst, &dsize);

#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H

	QByteArray origData = QByteArray::fromRawData(dst, dsize);
	QBuffer baReader(&origData);
	baReader.open(QBuffer::ReadOnly);

	QBuffer baWriter;
	baWriter.open(QBuffer::WriteOnly);

	flacStreamDecoderClientData cdata = { &baReader, &baWriter } ;

	FLAC__StreamDecoder * flacDec = FLAC__stream_decoder_new();

	FLAC__stream_decoder_set_read_callback(flacDec,
		flacStreamDecoderReadCallback);
	FLAC__stream_decoder_set_write_callback(flacDec,
		flacStreamDecoderWriteCallback);
	FLAC__stream_decoder_set_error_callback(flacDec,
		flacStreamDecoderErrorCallback);
	FLAC__stream_decoder_set_metadata_callback(flacDec,
		flacStreamDecoderMetadataCallback);
	FLAC__stream_decoder_set_client_data(flacDec, &cdata);

	FLAC__stream_decoder_init(flacDec);

	FLAC__stream_decoder_process_until_end_of_stream(flacDec);

	FLAC__stream_decoder_finish(flacDec);
	FLAC__stream_decoder_delete(flacDec);

	baReader.close();

	origData = baWriter.buffer();
	printf("%d\n", (int) origData.size());

	m_origFrames = origData.size() / sizeof(sampleFrame);
	MM_FREE(m_origData);
	m_origData = MM_ALLOC(sampleFrame, m_origFrames);
	memcpy(m_origData, origData.data(), origData.size());

#else /* LMMS_HAVE_FLAC_STREAM_DECODER_H */

	m_origFrames = dsize / sizeof(sampleFrame);
	MM_FREE(m_origData);
	m_origData = MM_ALLOC(sampleFrame, m_origFrames);
	memcpy(m_origData, dst, dsize);

#endif

	delete[] dst;

	m_audioFile = QString();
	update();
}

void SampleBuffer::setStartFrame(const f_cnt_t s)
{
	m_startFrame = s;
}

void SampleBuffer::setEndFrame(const f_cnt_t e)
{
	m_endFrame = e;
}

void SampleBuffer::setAmplification(float a)
{
	m_amplification = a;
	emit sampleUpdated();
}

void SampleBuffer::setReversed(bool on)
{
	Engine::mixer()->requestChangeInModel();
	m_varLock.lockForWrite();
	if (m_reversed != on) { std::reverse(m_data, m_data + m_frames); }
	m_reversed = on;
	m_varLock.unlock();
	Engine::mixer()->doneChangeInModel();
	emit sampleUpdated();
}

SampleBuffer::handleState::handleState(bool varyingPitch, int interpolationMode) :
	m_frameIndex(0),
	m_varyingPitch(varyingPitch),
	m_isBackwards(false)
{
	int error;
	m_interpolationMode = interpolationMode;

	if ((m_resamplingData = src_new(interpolationMode, DEFAULT_CHANNELS, &error)) == nullptr)
	{
		qDebug("Error: src_new() failed in sample_buffer.cpp!\n");
	}
}

SampleBuffer::handleState::~handleState()
{
	src_delete(m_resamplingData);
}


code:
/*
 * SamplePlayHandle.cpp - implementation of class SamplePlayHandle
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "SamplePlayHandle.h"
#include "AudioPort.h"
#include "BBTrack.h"
#include "Engine.h"
#include "InstrumentTrack.h"
#include "Mixer.h"
#include "SampleTrack.h"

SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) :
	PlayHandle( TypeSamplePlayHandle ),
	m_sampleBuffer( sharedObject::ref( sampleBuffer ) ),
	m_doneMayReturnTrue( true ),
	m_frame( 0 ),
	m_ownAudioPort( ownAudioPort ),
	m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ),
	m_volumeModel( &m_defaultVolumeModel ),
	m_track( NULL ),
	m_bbTrack( NULL )
{
	if (ownAudioPort)
	{
		setAudioPort( new AudioPort( "SamplePlayHandle", false ) );
	}
}

SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) :
	SamplePlayHandle( new SampleBuffer( sampleFile ) , true)
{
	sharedObject::unref( m_sampleBuffer );
}

SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) :
	SamplePlayHandle( tco->sampleBuffer() , false)
{
	m_track = tco->getTrack();
	setAudioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() );
}

SamplePlayHandle::~SamplePlayHandle()
{
	sharedObject::unref( m_sampleBuffer );
	if( m_ownAudioPort )
	{
		delete audioPort();
	}
}

void SamplePlayHandle::play( sampleFrame * buffer )
{
	const fpp_t fpp = Engine::mixer()->framesPerPeriod();
	//play( 0, _try_parallelizing );
	if( framesDone() >= totalFrames() )
	{
		memset( buffer, 0, sizeof( sampleFrame ) * fpp );
		return;
	}

	sampleFrame * workingBuffer = buffer;
	f_cnt_t frames = fpp;

	// apply offset for the first period
	if( framesDone() == 0 )
	{
		memset( buffer, 0, sizeof( sampleFrame ) * offset() );
		workingBuffer += offset();
		frames -= offset();
	}

	if( !( m_track && m_track->isMuted() )
				&& !( m_bbTrack && m_bbTrack->isMuted() ) )
	{
/*		stereoVolumeVector v =
			{ { m_volumeModel->value() / DefaultVolume,
				m_volumeModel->value() / DefaultVolume } };*/
		if( ! m_sampleBuffer->play( workingBuffer, &m_state, frames,
								BaseFreq ) )
		{
			memset( workingBuffer, 0, frames * sizeof( sampleFrame ) );
		}
	}

	m_frame += frames;
}

bool SamplePlayHandle::isFinished() const
{
	return framesDone() >= totalFrames() && m_doneMayReturnTrue == true;
}

bool SamplePlayHandle::isFromTrack( const Track * _track ) const
{
	return m_track == _track || m_bbTrack == _track;
}

f_cnt_t SamplePlayHandle::totalFrames() const
{
	return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / m_sampleBuffer->sampleRate() );
}


code:
/*
 * SampleRecordHandle.cpp - implementation of class SampleRecordHandle
 *
 * Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "SampleRecordHandle.h"
#include "BBTrack.h"
#include "Engine.h"
#include "InstrumentTrack.h"
#include "Mixer.h"
#include "SampleBuffer.h"
#include "SampleTrack.h"
#include "debug.h"

SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) :
	PlayHandle( TypeSamplePlayHandle ),
	m_framesRecorded( 0 ),
	m_minLength( tco->length() ),
	m_track( tco->getTrack() ),
	m_bbTrack( NULL ),
	m_tco( tco )
{
}

SampleRecordHandle::~SampleRecordHandle()
{
	if( !m_buffers.empty() )
	{
		SampleBuffer* sb;
		createSampleBuffer( &sb );
		m_tco->setSampleBuffer( sb );
	}

	while( !m_buffers.empty() )
	{
		delete[] m_buffers.front().first;
		m_buffers.erase( m_buffers.begin() );
	}
	m_tco->setRecord( false );
}

void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ )
{
	const sampleFrame * recbuf = Engine::mixer()->inputBuffer();
	const f_cnt_t frames = Engine::mixer()->inputBufferFrames();
	writeBuffer( recbuf, frames );
	m_framesRecorded += frames;

	TimePos len = (tick_t)( m_framesRecorded / Engine::framesPerTick() );
	if( len > m_minLength )
	{
//		m_tco->changeLength( len );
		m_minLength = len;
	}
}

bool SampleRecordHandle::isFinished() const
{
	return false;
}

bool SampleRecordHandle::isFromTrack( const Track * _track ) const
{
	return( m_track == _track || m_bbTrack == _track );
}

f_cnt_t SampleRecordHandle::framesRecorded() const
{
	return( m_framesRecorded );
}

void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf )
{
	const f_cnt_t frames = framesRecorded();
	// create buffer to store all recorded buffers in
	sampleFrame * data = new sampleFrame[frames];
	// make sure buffer is cleaned up properly at the end...
	sampleFrame * data_ptr = data;

	assert( data != NULL );

	// now copy all buffers into big buffer
	for( bufferList::const_iterator it = m_buffers.begin();
						it != m_buffers.end(); ++it )
	{
		memcpy( data_ptr, ( *it ).first, ( *it ).second *
							sizeof( sampleFrame ) );
		data_ptr += ( *it ).second;
	}
	// create according sample-buffer out of big buffer
	*sampleBuf = new SampleBuffer( data, frames );
	( *sampleBuf)->setSampleRate( Engine::mixer()->inputSampleRate() );
	delete[] data;
}

void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,
					const f_cnt_t _frames )
{
	sampleFrame * buf = new sampleFrame[_frames];
	for( f_cnt_t frame = 0; frame < _frames; ++frame )
	{
		for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl )
		{
			buf[frame][chnl] = _ab[frame][chnl];
		}
	}
	m_buffers.push_back( qMakePair( buf, _frames ) );
}


code:
/*
 * SerializingObject.cpp - implementation of SerializingObject
 *
 * Copyright (c) 2008-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDomElement>

#include "SerializingObject.h"

SerializingObject::SerializingObject() :
	m_hook( NULL )
{
}

SerializingObject::~SerializingObject()
{
	if( m_hook )
	{
		m_hook->m_hookedIn = NULL;
	}
}

QDomElement SerializingObject::saveState( QDomDocument& doc, QDomElement& parent )
{
	QDomElement element = doc.createElement( nodeName() );
	parent.appendChild( element );

	saveSettings( doc, element );

	if( hook() )
	{
		hook()->saveSettings( doc, element );
	}

	return element;
}

void SerializingObject::restoreState( const QDomElement& element )
{
	loadSettings( element );

	if( hook() )
	{
		hook()->loadSettings( element );
	}
}

void SerializingObject::setHook( SerializingObjectHook* hook )
{
	if( m_hook )
	{
		m_hook->m_hookedIn = NULL;
	}

	m_hook = hook;

	if( m_hook )
	{
		m_hook->m_hookedIn = this;
	}
}

void SerializingObject::saveSettings( QDomDocument& doc, QDomElement& element )
{
	Q_UNUSED(doc)
	Q_UNUSED(element)
}

void SerializingObject::loadSettings( const QDomElement& element )
{
	Q_UNUSED(element)
}


code:
/*
 * Song.cpp - root of the model tree
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Song.h"
#include <QTextStream>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>

#include <algorithm>
#include <cmath>
#include <functional>

#include "AutomationTrack.h"
#include "AutomationEditor.h"
#include "BBEditor.h"
#include "BBTrack.h"
#include "BBTrackContainer.h"
#include "ConfigManager.h"
#include "ControllerRackView.h"
#include "ControllerConnection.h"
#include "embed.h"
#include "EnvelopeAndLfoParameters.h"
#include "FxMixer.h"
#include "FxMixerView.h"
#include "GuiApplication.h"
#include "ExportFilter.h"
#include "InstrumentTrack.h"
#include "NotePlayHandle.h"
#include "Pattern.h"
#include "PianoRoll.h"
#include "ProjectJournal.h"
#include "ProjectNotes.h"
#include "SongEditor.h"
#include "TimeLineWidget.h"
#include "PeakController.h"

tick_t TimePos::s_ticksPerBar = DefaultTicksPerBar;

Song::Song() :
	TrackContainer(),
	m_globalAutomationTrack( dynamic_cast<AutomationTrack *>(
				Track::create( Track::HiddenAutomationTrack,
								this ) ) ),
	m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ),
	m_timeSigModel( this ),
	m_oldTicksPerBar( DefaultTicksPerBar ),
	m_masterVolumeModel( 100, 0, 200, this, tr( "Master volume" ) ),
	m_masterPitchModel( 0, -12, 12, this, tr( "Master pitch" ) ),
	m_nLoadingTrack( 0 ),
	m_fileName(),
	m_oldFileName(),
	m_modified( false ),
	m_loadOnLaunch( true ),
	m_recording( false ),
	m_exporting( false ),
	m_exportLoop( false ),
	m_renderBetweenMarkers( false ),
	m_playing( false ),
	m_paused( false ),
	m_savingProject( false ),
	m_loadingProject( false ),
	m_isCancelled( false ),
	m_playMode( Mode_None ),
	m_length( 0 ),
	m_patternToPlay( NULL ),
	m_loopPattern( false ),
	m_elapsedTicks( 0 ),
	m_elapsedBars( 0 ),
	m_loopRenderCount(1),
	m_loopRenderRemaining(1)
{
	for(int i = 0; i < Mode_Count; ++i) m_elapsedMilliSeconds[i] = 0;
	connect( &m_tempoModel, SIGNAL( dataChanged() ),
			this, SLOT( setTempo() ), Qt::DirectConnection );
	connect( &m_tempoModel, SIGNAL( dataUnchanged() ),
			this, SLOT( setTempo() ), Qt::DirectConnection );
	connect( &m_timeSigModel, SIGNAL( dataChanged() ),
			this, SLOT( setTimeSignature() ), Qt::DirectConnection );

	connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this,
						SLOT( updateFramesPerTick() ) );

	connect( &m_masterVolumeModel, SIGNAL( dataChanged() ),
			this, SLOT( masterVolumeChanged() ), Qt::DirectConnection );
/*	connect( &m_masterPitchModel, SIGNAL( dataChanged() ),
			this, SLOT( masterPitchChanged() ) );*/

	qRegisterMetaType<Note>( "Note" );
	setType( SongContainer );
}

Song::~Song()
{
	m_playing = false;
	delete m_globalAutomationTrack;
}

void Song::masterVolumeChanged()
{
	Engine::mixer()->setMasterGain( m_masterVolumeModel.value() /
								100.0f );
}

void Song::setTempo()
{
	Engine::mixer()->requestChangeInModel();
	const bpm_t tempo = ( bpm_t ) m_tempoModel.value();
	PlayHandleList & playHandles = Engine::mixer()->playHandles();
	for( PlayHandleList::Iterator it = playHandles.begin();
						it != playHandles.end(); ++it )
	{
		NotePlayHandle * nph = dynamic_cast<NotePlayHandle *>( *it );
		if( nph && !nph->isReleased() )
		{
			nph->lock();
			nph->resize( tempo );
			nph->unlock();
		}
	}
	Engine::mixer()->doneChangeInModel();

	Engine::updateFramesPerTick();

	m_vstSyncController.setTempo( tempo );

	emit tempoChanged( tempo );
}

void Song::setTimeSignature()
{
	TimePos::setTicksPerBar( ticksPerBar() );
	emit timeSignatureChanged( m_oldTicksPerBar, ticksPerBar() );
	emit dataChanged();
	m_oldTicksPerBar = ticksPerBar();

	m_vstSyncController.setTimeSignature(
		getTimeSigModel().getNumerator(), getTimeSigModel().getDenominator() );
}

void Song::savePos()
{
	TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine;

	if( tl != NULL )
	{
		tl->savePos( m_playPos[m_playMode] );
	}
}

void Song::processNextBuffer()
{
	m_vstSyncController.setPlaybackJumped(false);

	// If nothing is playing, there is nothing to do
	if (!m_playing) { return; }

	// At the beginning of the song, we have to reset the LFOs
	if (m_playMode == Mode_PlaySong && getPlayPos() == 0)
	{
		EnvelopeAndLfoParameters::instances()->reset();
	}

	TrackList trackList;
	int clipNum = -1; // The number of the clip that will be played

	// Determine the list of tracks to play and the clip number
	switch (m_playMode)
	{
		case Mode_PlaySong:
			trackList = tracks();
			break;

		case Mode_PlayBB:
			if (Engine::getBBTrackContainer()->numOfBBs() > 0)
			{
				clipNum = Engine::getBBTrackContainer()->currentBB();
				trackList.push_back(BBTrack::findBBTrack(clipNum));
			}
			break;

		case Mode_PlayPattern:
			if (m_patternToPlay)
			{
				clipNum = m_patternToPlay->getTrack()->getTCONum(m_patternToPlay);
				trackList.push_back(m_patternToPlay->getTrack());
			}
			break;

		default:
			return;
	}

	// If we have no tracks to play, there is nothing to do
	if (trackList.empty()) { return; }

	// If the playback position is outside of the range [begin, end), move it to
	// begin and inform interested parties.
	// Returns true if the playback position was moved, else false.
	const auto enforceLoop = [this](const TimePos& begin, const TimePos& end)
	{
		if (getPlayPos() < begin || getPlayPos() >= end)
		{
			setToTime(begin);
			m_vstSyncController.setPlaybackJumped(true);
			emit updateSampleTracks();
			return true;
		}
		return false;
	};

	const auto timeline = getPlayPos().m_timeLine;
	const auto loopEnabled = !m_exporting && timeline && timeline->loopPointsEnabled();

	// Ensure playback begins within the loop if it is enabled
	if (loopEnabled) { enforceLoop(timeline->loopBegin(), timeline->loopEnd()); }

	// Inform VST plugins if the user moved the play head
	if (getPlayPos().jumped())
	{
		m_vstSyncController.setPlaybackJumped(true);
		getPlayPos().setJumped(false);
	}

	const auto framesPerTick = Engine::framesPerTick();
	const auto framesPerPeriod = Engine::mixer()->framesPerPeriod();

	f_cnt_t frameOffsetInPeriod = 0;

	while (frameOffsetInPeriod < framesPerPeriod)
	{
		auto frameOffsetInTick = getPlayPos().currentFrame();

		// If a whole tick has elapsed, update the frame and tick count, and check any loops
		if (frameOffsetInTick >= framesPerTick)
		{
			// Transfer any whole ticks from the frame count to the tick count
			const auto elapsedTicks = static_cast<int>(frameOffsetInTick / framesPerTick);
			getPlayPos().setTicks(getPlayPos().getTicks() + elapsedTicks);
			frameOffsetInTick -= elapsedTicks * framesPerTick;
			getPlayPos().setCurrentFrame(frameOffsetInTick);

			// If we are playing a BB track, or a pattern with no loop enabled,
			// loop back to the beginning when we reach the end
			if (m_playMode == Mode_PlayBB)
			{
				enforceLoop(TimePos{0}, TimePos{Engine::getBBTrackContainer()->lengthOfCurrentBB(), 0});
			}
			else if (m_playMode == Mode_PlayPattern && m_loopPattern && !loopEnabled)
			{
				enforceLoop(TimePos{0}, m_patternToPlay->length());
			}

			// Handle loop points, and inform VST plugins of the loop status
			if (loopEnabled || (m_loopRenderRemaining > 1 && getPlayPos() >= timeline->loopBegin()))
			{
				m_vstSyncController.startCycle(
					timeline->loopBegin().getTicks(), timeline->loopEnd().getTicks());

				// Loop if necessary, and decrement the remaining loops if we did
				if (enforceLoop(timeline->loopBegin(), timeline->loopEnd())
					&& m_loopRenderRemaining > 1)
				{
					m_loopRenderRemaining--;
				}
			}
			else
			{
				m_vstSyncController.stopCycle();
			}
		}

		const f_cnt_t framesUntilNextPeriod = framesPerPeriod - frameOffsetInPeriod;
		const f_cnt_t framesUntilNextTick = static_cast<f_cnt_t>(std::ceil(framesPerTick - frameOffsetInTick));

		// We want to proceed to the next buffer or tick, whichever is closer
		const auto framesToPlay = std::min(framesUntilNextPeriod, framesUntilNextTick);

		if (frameOffsetInPeriod == 0)
		{
			// First frame of buffer: update VST sync position.
			// This must be done after we've corrected the frame/tick count,
			// but before actually playing any frames.
			m_vstSyncController.setAbsolutePosition(getPlayPos().getTicks()
				+ getPlayPos().currentFrame() / static_cast<double>(framesPerTick));
			m_vstSyncController.update();
		}

		if (static_cast<f_cnt_t>(frameOffsetInTick) == 0)
		{
			// First frame of tick: process automation and play tracks
			processAutomations(trackList, getPlayPos(), framesToPlay);
			for (const auto track : trackList)
			{
				track->play(getPlayPos(), framesToPlay, frameOffsetInPeriod, clipNum);
			}
		}

		// Update frame counters
		frameOffsetInPeriod += framesToPlay;
		frameOffsetInTick += framesToPlay;
		getPlayPos().setCurrentFrame(frameOffsetInTick);
		m_elapsedMilliSeconds[m_playMode] += TimePos::ticksToMilliseconds(framesToPlay / framesPerTick, getTempo());
		m_elapsedBars = m_playPos[Mode_PlaySong].getBar();
		m_elapsedTicks = (m_playPos[Mode_PlaySong].getTicks() % ticksPerBar()) / 48;
	}
}

void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp_t)
{
	AutomatedValueMap values;

	QSet<const AutomatableModel*> recordedModels;

	TrackContainer* container = this;
	int tcoNum = -1;

	switch (m_playMode)
	{
	case Mode_PlaySong:
		break;
	case Mode_PlayBB:
	{
		Q_ASSERT(tracklist.size() == 1);
		Q_ASSERT(tracklist.at(0)->type() == Track::BBTrack);
		auto bbTrack = dynamic_cast<BBTrack*>(tracklist.at(0));
		auto bbContainer = Engine::getBBTrackContainer();
		container = bbContainer;
		tcoNum = bbTrack->index();
	}
		break;
	default:
		return;
	}

	values = container->automatedValuesAt(timeStart, tcoNum);
	TrackList tracks = container->tracks();

	Track::tcoVector tcos;
	for (Track* track : tracks)
	{
		if (track->type() == Track::AutomationTrack) {
			track->getTCOsInRange(tcos, 0, timeStart);
		}
	}

	// Process recording
	for (TrackContentObject* tco : tcos)
	{
		auto p = dynamic_cast<AutomationPattern *>(tco);
		TimePos relTime = timeStart - p->startPosition();
		if (p->isRecording() && relTime >= 0 && relTime < p->length())
		{
			const AutomatableModel* recordedModel = p->firstObject();
			p->recordValue(relTime, recordedModel->value<float>());

			recordedModels << recordedModel;
		}
	}

	// Apply values
	for (auto it = values.begin(); it != values.end(); it++)
	{
		if (! recordedModels.contains(it.key()))
		{
			it.key()->setAutomatedValue(it.value());
		}
	}
}

void Song::setModified(bool value)
{
	if( !m_loadingProject && m_modified != value)
	{
		m_modified = value;
		emit modified();
	}
}

bool Song::isExportDone() const
{
	return !isExporting() || m_playPos[m_playMode] >= m_exportSongEnd;
}

int Song::getExportProgress() const
{
	TimePos pos = m_playPos[m_playMode];

	if (pos >= m_exportSongEnd)
	{
		return 100;
	}
	else if (pos <= m_exportSongBegin)
	{
		return 0;
	}
	else if (pos >= m_exportLoopEnd)
	{
		pos = (m_exportLoopBegin-m_exportSongBegin) + (m_exportLoopEnd - m_exportLoopBegin) *
			m_loopRenderCount + (pos - m_exportLoopEnd);
	}
	else if ( pos >= m_exportLoopBegin )
	{
		pos = (m_exportLoopBegin-m_exportSongBegin) + ((m_exportLoopEnd - m_exportLoopBegin) *
			(m_loopRenderCount - m_loopRenderRemaining)) + (pos - m_exportLoopBegin);
	}
	else
	{
		pos = (pos - m_exportSongBegin);
	}

	return (float)pos/(float)m_exportEffectiveLength*100.0f;
}

void Song::playSong()
{
	m_recording = false;

	if( isStopped() == false )
	{
		stop();
	}

	m_playMode = Mode_PlaySong;
	m_playing = true;
	m_paused = false;

	m_vstSyncController.setPlaybackState( true );

	savePos();

	emit playbackStateChanged();
}

void Song::record()
{
	m_recording = true;
	// TODO: Implement
}

void Song::playAndRecord()
{
	playSong();
	m_recording = true;
}

void Song::playBB()
{
	if( isStopped() == false )
	{
		stop();
	}

	m_playMode = Mode_PlayBB;
	m_playing = true;
	m_paused = false;

	m_vstSyncController.setPlaybackState( true );

	savePos();

	emit playbackStateChanged();
}

void Song::playPattern( const Pattern* patternToPlay, bool loop )
{
	if( isStopped() == false )
	{
		stop();
	}

	m_patternToPlay = patternToPlay;
	m_loopPattern = loop;

	if( m_patternToPlay != NULL )
	{
		m_playMode = Mode_PlayPattern;
		m_playing = true;
		m_paused = false;
	}

	savePos();

	emit playbackStateChanged();
}

void Song::updateLength()
{
	m_length = 0;
	m_tracksMutex.lockForRead();
	for (auto track : tracks())
	{
		if (m_exporting && track->isMuted())
		{
			continue;
		}

		const bar_t cur = track->length();
		if( cur > m_length )
		{
			m_length = cur;
		}
	}
	m_tracksMutex.unlock();

	emit lengthChanged( m_length );
}

void Song::setPlayPos( tick_t ticks, PlayModes playMode )
{
	tick_t ticksFromPlayMode = m_playPos[playMode].getTicks();
	m_elapsedTicks += ticksFromPlayMode - ticks;
	m_elapsedMilliSeconds[playMode] += TimePos::ticksToMilliseconds( ticks - ticksFromPlayMode, getTempo() );
	m_playPos[playMode].setTicks( ticks );
	m_playPos[playMode].setCurrentFrame( 0.0f );
	m_playPos[playMode].setJumped( true );

// send a signal if playposition changes during playback
	if( isPlaying() )
	{
		emit playbackPositionChanged();
		emit updateSampleTracks();
	}
}

void Song::togglePause()
{
	if( m_paused == true )
	{
		m_playing = true;
		m_paused = false;
	}
	else
	{
		m_playing = false;
		m_paused = true;
	}

	m_vstSyncController.setPlaybackState( m_playing );

	emit playbackStateChanged();
}

void Song::stop()
{
	// do not stop/reset things again if we're stopped already
	if( m_playMode == Mode_None )
	{
		return;
	}

	TimeLineWidget * tl = m_playPos[m_playMode].m_timeLine;
	m_paused = false;
	m_recording = true;

	if( tl )
	{
		switch( tl->behaviourAtStop() )
		{
			case TimeLineWidget::BackToZero:
				m_playPos[m_playMode].setTicks(0);
				m_elapsedMilliSeconds[m_playMode] = 0;
				break;

			case TimeLineWidget::BackToStart:
				if( tl->savedPos() >= 0 )
				{
					m_playPos[m_playMode].setTicks(tl->savedPos().getTicks());
					setToTime(tl->savedPos());

					tl->savePos( -1 );
				}
				break;

			case TimeLineWidget::KeepStopPosition:
				break;
		}
	}
	else
	{
		m_playPos[m_playMode].setTicks( 0 );
		m_elapsedMilliSeconds[m_playMode] = 0;
	}
	m_playing = false;

	m_elapsedMilliSeconds[Mode_None] = m_elapsedMilliSeconds[m_playMode];
	m_playPos[Mode_None].setTicks(m_playPos[m_playMode].getTicks());

	m_playPos[m_playMode].setCurrentFrame( 0 );

	m_vstSyncController.setPlaybackState( m_exporting );
	m_vstSyncController.setAbsolutePosition(
		m_playPos[m_playMode].getTicks()
		+ m_playPos[m_playMode].currentFrame()
		/ (double) Engine::framesPerTick() );

	// remove all note-play-handles that are active
	Engine::mixer()->clear();

	m_playMode = Mode_None;

	emit stopped();
	emit playbackStateChanged();
}

void Song::startExport()
{
	stop();

	m_exporting = true;
	updateLength();

	if (m_renderBetweenMarkers)
	{
		m_exportSongBegin = m_exportLoopBegin = m_playPos[Mode_PlaySong].m_timeLine->loopBegin();
		m_exportSongEnd = m_exportLoopEnd = m_playPos[Mode_PlaySong].m_timeLine->loopEnd();

		m_playPos[Mode_PlaySong].setTicks( m_playPos[Mode_PlaySong].m_timeLine->loopBegin().getTicks() );
	}
	else
	{
		m_exportSongEnd = TimePos(m_length, 0);

		// Handle potentially ridiculous loop points gracefully.
		if (m_loopRenderCount > 1 && m_playPos[Mode_PlaySong].m_timeLine->loopEnd() > m_exportSongEnd)
		{
			m_exportSongEnd = m_playPos[Mode_PlaySong].m_timeLine->loopEnd();
		}

		if (!m_exportLoop)
			m_exportSongEnd += TimePos(1,0);

		m_exportSongBegin = TimePos(0,0);
		// FIXME: remove this check once we load timeline in headless mode
		if (m_playPos[Mode_PlaySong].m_timeLine)
		{
			m_exportLoopBegin = m_playPos[Mode_PlaySong].m_timeLine->loopBegin() < m_exportSongEnd &&
				m_playPos[Mode_PlaySong].m_timeLine->loopEnd() <= m_exportSongEnd ?
				m_playPos[Mode_PlaySong].m_timeLine->loopBegin() : TimePos(0,0);
			m_exportLoopEnd = m_playPos[Mode_PlaySong].m_timeLine->loopBegin() < m_exportSongEnd &&
				m_playPos[Mode_PlaySong].m_timeLine->loopEnd() <= m_exportSongEnd ?
				m_playPos[Mode_PlaySong].m_timeLine->loopEnd() : TimePos(0,0);
		}

		m_playPos[Mode_PlaySong].setTicks( 0 );
	}

	m_exportEffectiveLength = (m_exportLoopBegin - m_exportSongBegin) + (m_exportLoopEnd - m_exportLoopBegin)
		* m_loopRenderCount + (m_exportSongEnd - m_exportLoopEnd);
	m_loopRenderRemaining = m_loopRenderCount;

	playSong();

	m_vstSyncController.setPlaybackState( true );
}

void Song::stopExport()
{
	stop();
	m_exporting = false;

	m_vstSyncController.setPlaybackState( m_playing );
}

void Song::insertBar()
{
	m_tracksMutex.lockForRead();
	for( TrackList::const_iterator it = tracks().begin();
					it != tracks().end(); ++it )
	{
		( *it )->insertBar( m_playPos[Mode_PlaySong] );
	}
	m_tracksMutex.unlock();
}

void Song::removeBar()
{
	m_tracksMutex.lockForRead();
	for( TrackList::const_iterator it = tracks().begin();
					it != tracks().end(); ++it )
	{
		( *it )->removeBar( m_playPos[Mode_PlaySong] );
	}
	m_tracksMutex.unlock();
}

void Song::addBBTrack()
{
	Track * t = Track::create( Track::BBTrack, this );
	Engine::getBBTrackContainer()->setCurrentBB( dynamic_cast<BBTrack *>( t )->index() );
}

void Song::addSampleTrack()
{
	( void )Track::create( Track::SampleTrack, this );
}

void Song::addAutomationTrack()
{
	( void )Track::create( Track::AutomationTrack, this );
}

bpm_t Song::getTempo()
{
	return ( bpm_t )m_tempoModel.value();
}

AutomationPattern * Song::tempoAutomationPattern()
{
	return AutomationPattern::globalAutomationPattern( &m_tempoModel );
}

AutomatedValueMap Song::automatedValuesAt(TimePos time, int tcoNum) const
{
	return TrackContainer::automatedValuesFromTracks(TrackList{m_globalAutomationTrack} << tracks(), time, tcoNum);
}

void Song::clearProject()
{
	Engine::projectJournal()->setJournalling( false );

	if( m_playing )
	{
		stop();
	}

	for( int i = 0; i < Mode_Count; i++ )
	{
		setPlayPos( 0, ( PlayModes )i );
	}

	Engine::mixer()->requestChangeInModel();

	if( gui && gui->getBBEditor() )
	{
		gui->getBBEditor()->trackContainerView()->clearAllTracks();
	}
	if( gui && gui->songEditor() )
	{
		gui->songEditor()->m_editor->clearAllTracks();
	}
	if( gui && gui->fxMixerView() )
	{
		gui->fxMixerView()->clear();
	}
	QCoreApplication::sendPostedEvents();
	Engine::getBBTrackContainer()->clearAllTracks();
	clearAllTracks();

	Engine::fxMixer()->clear();

	if( gui && gui->automationEditor() )
	{
		gui->automationEditor()->setCurrentPattern( NULL );
	}

	if( gui && gui->pianoRoll() )
	{
		gui->pianoRoll()->reset();
	}

	m_tempoModel.reset();
	m_masterVolumeModel.reset();
	m_masterPitchModel.reset();
	m_timeSigModel.reset();

	AutomationPattern::globalAutomationPattern( &m_tempoModel )->clear();
	AutomationPattern::globalAutomationPattern( &m_masterVolumeModel )->
									clear();
	AutomationPattern::globalAutomationPattern( &m_masterPitchModel )->
									clear();

	Engine::mixer()->doneChangeInModel();

	if( gui && gui->getProjectNotes() )
	{
		gui->getProjectNotes()->clear();
	}

	removeAllControllers();

	emit dataChanged();

	Engine::projectJournal()->clearJournal();

	Engine::projectJournal()->setJournalling( true );
}

// create new file
void Song::createNewProject()
{

	QString defaultTemplate = ConfigManager::inst()->userTemplateDir()
						+ "default.mpt";

	if( QFile::exists( defaultTemplate ) )
	{
		createNewProjectFromTemplate( defaultTemplate );
		return;
	}

	defaultTemplate = ConfigManager::inst()->factoryProjectsDir()
						+ "templates/default.mpt";
	if( QFile::exists( defaultTemplate ) )
	{
		createNewProjectFromTemplate( defaultTemplate );
		return;
	}

	m_loadingProject = true;

	clearProject();

	Engine::projectJournal()->setJournalling( false );

	m_oldFileName = "";
	setProjectFileName("");

	Track * t;
	t = Track::create( Track::InstrumentTrack, this );
	dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
					"tripleoscillator" );
	t = Track::create( Track::InstrumentTrack,
						Engine::getBBTrackContainer() );
	dynamic_cast<InstrumentTrack * >( t )->loadInstrument(
						"kicker" );
	Track::create( Track::SampleTrack, this );
	Track::create( Track::BBTrack, this );
	Track::create( Track::AutomationTrack, this );

	m_tempoModel.setInitValue( DefaultTempo );
	m_timeSigModel.reset();
	m_masterVolumeModel.setInitValue( 100 );
	m_masterPitchModel.setInitValue( 0 );

	QCoreApplication::instance()->processEvents();

	m_loadingProject = false;

	Engine::getBBTrackContainer()->updateAfterTrackAdd();

	Engine::projectJournal()->setJournalling( true );

	QCoreApplication::sendPostedEvents();

	setModified(false);
	m_loadOnLaunch = false;
}

void Song::createNewProjectFromTemplate( const QString & templ )
{
	loadProject( templ );
	// clear file-name so that user doesn't overwrite template when
	// saving...
	m_oldFileName = "";
	setProjectFileName("");
	// update window title
	m_loadOnLaunch = false;
}

// load given song
void Song::loadProject( const QString & fileName )
{
	QDomNode node;

	m_loadingProject = true;

	Engine::projectJournal()->setJournalling( false );

	m_oldFileName = m_fileName;
	setProjectFileName(fileName);

	DataFile dataFile( m_fileName );
	// if file could not be opened, head-node is null and we create
	// new project
	if( dataFile.head().isNull() )
	{
		if( m_loadOnLaunch )
		{
			createNewProject();
		}
		setProjectFileName(m_oldFileName);
		return;
	}

	m_oldFileName = m_fileName;

	clearProject();

	clearErrors();

	Engine::mixer()->requestChangeInModel();

	// get the header information from the DOM
	m_tempoModel.loadSettings( dataFile.head(), "bpm" );
	m_timeSigModel.loadSettings( dataFile.head(), "timesig" );
	m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" );
	m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" );

	if( m_playPos[Mode_PlaySong].m_timeLine )
	{
		// reset loop-point-state
		m_playPos[Mode_PlaySong].m_timeLine->toggleLoopPoints( 0 );
	}

	if( !dataFile.content().firstChildElement( "track" ).isNull() )
	{
		m_globalAutomationTrack->restoreState( dataFile.content().
						firstChildElement( "track" ) );
	}

	//Backward compatibility for LMMS <= 0.4.15
	PeakController::initGetControllerBySetting();

	// Load mixer first to be able to set the correct range for FX channels
	node = dataFile.content().firstChildElement( Engine::fxMixer()->nodeName() );
	if( !node.isNull() )
	{
		Engine::fxMixer()->restoreState( node.toElement() );
		if( gui )
		{
			// refresh FxMixerView
			gui->fxMixerView()->refreshDisplay();
		}
	}

	node = dataFile.content().firstChild();

	QDomNodeList tclist=dataFile.content().elementsByTagName("trackcontainer");
	m_nLoadingTrack=0;
	for( int i=0,n=tclist.count(); i<n; ++i )
	{
		QDomNode nd=tclist.at(i).firstChild();
		while(!nd.isNull())
		{
			if( nd.isElement() && nd.nodeName() == "track" )
			{
				++m_nLoadingTrack;
				if( nd.toElement().attribute("type").toInt() == Track::BBTrack )
				{
					n += nd.toElement().elementsByTagName("bbtrack").at(0)
						.toElement().firstChildElement().childNodes().count();
				}
				nd=nd.nextSibling();
			}
		}
	}

	while( !node.isNull() && !isCancelled() )
	{
		if( node.isElement() )
		{
			if( node.nodeName() == "trackcontainer" )
			{
				( (JournallingObject *)( this ) )->restoreState( node.toElement() );
			}
			else if( node.nodeName() == "controllers" )
			{
				restoreControllerStates( node.toElement() );
			}
			else if( gui )
			{
				if( node.nodeName() == gui->getControllerRackView()->nodeName() )
				{
					gui->getControllerRackView()->restoreState( node.toElement() );
				}
				else if( node.nodeName() == gui->pianoRoll()->nodeName() )
				{
					gui->pianoRoll()->restoreState( node.toElement() );
				}
				else if( node.nodeName() == gui->automationEditor()->m_editor->nodeName() )
				{
					gui->automationEditor()->m_editor->restoreState( node.toElement() );
				}
				else if( node.nodeName() == gui->getProjectNotes()->nodeName() )
				{
					 gui->getProjectNotes()->SerializingObject::restoreState( node.toElement() );
				}
				else if( node.nodeName() == m_playPos[Mode_PlaySong].m_timeLine->nodeName() )
				{
					m_playPos[Mode_PlaySong].m_timeLine->restoreState( node.toElement() );
				}
			}
		}
		node = node.nextSibling();
	}

	// quirk for fixing projects with broken positions of TCOs inside
	// BB-tracks
	Engine::getBBTrackContainer()->fixIncorrectPositions();

	// Connect controller links to their controllers
	// now that everything is loaded
	ControllerConnection::finalizeConnections();

	// Remove dummy controllers that was added for correct connections
	m_controllers.erase(std::remove_if(m_controllers.begin(), m_controllers.end(),
		[](Controller* c){return c->type() == Controller::DummyController;}),
		m_controllers.end());

	// resolve all IDs so that autoModels are automated
	AutomationPattern::resolveAllIDs();

	Engine::mixer()->doneChangeInModel();

	ConfigManager::inst()->addRecentlyOpenedProject( fileName );

	Engine::projectJournal()->setJournalling( true );

	emit projectLoaded();

	if( isCancelled() )
	{
		m_isCancelled = false;
		createNewProject();
		return;
	}

	if ( hasErrors())
	{
		if ( gui )
		{
			QMessageBox::warning( NULL, tr("LMMS Error report"), errorSummary(),
							QMessageBox::Ok );
		}
		else
		{
#if (QT_VERSION >= QT_VERSION_CHECK(5,15,0))
			QTextStream(stderr) << Engine::getSong()->errorSummary() << Qt::endl;
#else
			QTextStream(stderr) << Engine::getSong()->errorSummary() << endl;
#endif
		}
	}

	m_loadingProject = false;
	setModified(false);
	m_loadOnLaunch = false;
}

// only save current song as _filename and do nothing else
bool Song::saveProjectFile( const QString & filename )
{
	DataFile dataFile( DataFile::SongProject );
	m_savingProject = true;

	m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" );
	m_timeSigModel.saveSettings( dataFile, dataFile.head(), "timesig" );
	m_masterVolumeModel.saveSettings( dataFile, dataFile.head(), "mastervol" );
	m_masterPitchModel.saveSettings( dataFile, dataFile.head(), "masterpitch" );

	saveState( dataFile, dataFile.content() );

	m_globalAutomationTrack->saveState( dataFile, dataFile.content() );
	Engine::fxMixer()->saveState( dataFile, dataFile.content() );
	if( gui )
	{
		gui->getControllerRackView()->saveState( dataFile, dataFile.content() );
		gui->pianoRoll()->saveState( dataFile, dataFile.content() );
		gui->automationEditor()->m_editor->saveState( dataFile, dataFile.content() );
		gui->getProjectNotes()->SerializingObject::saveState( dataFile, dataFile.content() );
		m_playPos[Mode_PlaySong].m_timeLine->saveState( dataFile, dataFile.content() );
	}

	saveControllerStates( dataFile, dataFile.content() );

	m_savingProject = false;

	return dataFile.writeFile( filename );
}

// Save the current song
bool Song::guiSaveProject()
{
	DataFile dataFile( DataFile::SongProject );
	QString fileNameWithExtension = dataFile.nameWithExtension( m_fileName );
	setProjectFileName(fileNameWithExtension);

	bool const saveResult = saveProjectFile( m_fileName );

	if( saveResult )
	{
		setModified(false);
	}

	return saveResult;
}

// Save the current song with the given filename
bool Song::guiSaveProjectAs( const QString & _file_name )
{
	QString o = m_oldFileName;
	m_oldFileName = m_fileName;
	setProjectFileName(_file_name);

	bool saveResult = guiSaveProject();
	// After saving as, restore default save options.
	m_saveOptions.setDefaultOptions();

	if(!saveResult)
	{
		// Saving failed. Restore old filenames.
		setProjectFileName(m_oldFileName);
		m_oldFileName = o;

		return false;
	}

	m_oldFileName = m_fileName;

	return true;
}

void Song::saveControllerStates( QDomDocument & doc, QDomElement & element )
{
	// save settings of controllers
	QDomElement controllersNode = doc.createElement( "controllers" );
	element.appendChild( controllersNode );
	for( int i = 0; i < m_controllers.size(); ++i )
	{
		m_controllers[i]->saveState( doc, controllersNode );
	}
}

void Song::restoreControllerStates( const QDomElement & element )
{
	QDomNode node = element.firstChild();
	while( !node.isNull() && !isCancelled() )
	{
		Controller * c = Controller::create( node.toElement(), this );
		if (c) {addController(c);}
		else
		{
			// Fix indices to ensure correct connections
			m_controllers.append(Controller::create(
				Controller::DummyController, this));
		}

		node = node.nextSibling();
	}
}

void Song::removeAllControllers()
{
	while (m_controllers.size() != 0)
	{
		removeController(m_controllers.at(0));
	}

	m_controllers.clear();
}

void Song::exportProjectMidi(QString const & exportFileName) const
{
	// instantiate midi export plugin
	TrackContainer::TrackList const & tracks = this->tracks();
	TrackContainer::TrackList const & tracks_BB = Engine::getBBTrackContainer()->tracks();

	ExportFilter *exf = dynamic_cast<ExportFilter *> (Plugin::instantiate("midiexport", nullptr, nullptr));
	if (exf)
	{
		exf->tryExport(tracks, tracks_BB, getTempo(), m_masterPitchModel.value(), exportFileName);
	}
	else
	{
		qDebug() << "failed to load midi export filter!";
	}

}

void Song::updateFramesPerTick()
{
	Engine::updateFramesPerTick();
}

void Song::setModified()
{
	setModified(true);
}

void Song::setProjectFileName(QString const & projectFileName)
{
	if (m_fileName != projectFileName)
	{
		m_fileName = projectFileName;
		emit projectFileNameChanged();
	}
}

void Song::addController( Controller * controller )
{
	if( controller && !m_controllers.contains( controller ) )
	{
		m_controllers.append( controller );
		emit controllerAdded( controller );

		this->setModified();
	}
}

void Song::removeController( Controller * controller )
{
	int index = m_controllers.indexOf( controller );
	if( index != -1 )
	{
		m_controllers.remove( index );

		emit controllerRemoved( controller );
		delete controller;

		this->setModified();
	}
}

void Song::clearErrors()
{
	m_errors.clear();
}

void Song::collectError( const QString error )
{
	if (!m_errors.contains(error)) { m_errors[error] = 1; }
	else { m_errors[ error ]++; }
}

bool Song::hasErrors()
{
	return !(m_errors.isEmpty());
}

QString Song::errorSummary()
{
	QString errors;

	auto i = m_errors.constBegin();
	while (i != m_errors.constEnd())
	{
		errors.append( i.key() );
		if( i.value() > 1 )
		{
			errors.append( tr(" (repeated %1 times)").arg( i.value() ) );
		}
		errors.append("\n");
		++i;
	}

	errors.prepend( "\n\n" );
	errors.prepend( tr( "The following errors occurred while loading: " ) );

	return errors;
}

bool Song::isSavingProject() const {
	return m_savingProject;
}


code:
/*
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "StepRecorder.h"
#include "StepRecorderWidget.h"
#include "PianoRoll.h"

#include <QPainter>

#include <climits>
using std::min;
using std::max;

const int REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS = 70;

StepRecorder::StepRecorder(PianoRoll& pianoRoll, StepRecorderWidget& stepRecorderWidget):
	m_pianoRoll(pianoRoll),
	m_stepRecorderWidget(stepRecorderWidget),
	m_pattern(nullptr)
{
	m_stepRecorderWidget.hide();
}

void StepRecorder::initialize()
{
	connect(&m_updateReleasedTimer, SIGNAL(timeout()), this, SLOT(removeNotesReleasedForTooLong()));
}

void StepRecorder::start(const TimePos& currentPosition, const TimePos& stepLength)
{
	m_isRecording = true;

	setStepsLength(stepLength);

	// quantize current position to get start recording position
	const int q = m_pianoRoll.quantization();
	const int curPosTicks = currentPosition.getTicks();
	const int QuantizedPosTicks = (curPosTicks / q) * q;
	const TimePos& QuantizedPos = TimePos(QuantizedPosTicks);

	m_curStepStartPos = QuantizedPos;
	m_curStepLength = 0;

	m_stepRecorderWidget.show();

	m_stepRecorderWidget.showHint();

	prepareNewStep();
}

void StepRecorder::stop()
{
	m_stepRecorderWidget.hide();
	m_isRecording = false;
}

void StepRecorder::notePressed(const Note & n)
{
	//if this is the first pressed note in step, advance position
	if(!m_isStepInProgress)
	{
		m_isStepInProgress = true;

		//move curser one step forwards
		stepForwards();
	}

	StepNote* stepNote = findCurStepNote(n.key());
	if(stepNote == nullptr)
	{
		m_curStepNotes.append(new StepNote(Note(m_curStepLength, m_curStepStartPos, n.key(), n.getVolume(), n.getPanning())));
		m_pianoRoll.update();
	}
	else if (stepNote->isReleased())
	{
		stepNote->setPressed();
	}
}

void StepRecorder::noteReleased(const Note & n)
{
	StepNote* stepNote = findCurStepNote(n.key());

	if(stepNote != nullptr && stepNote->isPressed())
	{
		stepNote->setReleased();

		//if m_updateReleasedTimer is not already active, activate it
		//(when activated, the timer will re-set itself as long as there are released notes)
		if(!m_updateReleasedTimer.isActive())
		{
			m_updateReleasedTimer.start(REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS);
		}

		//check if all note are released, apply notes to pattern(or dimiss if length is zero) and prepare to record next step
		if(allCurStepNotesReleased())
		{
			if(m_curStepLength > 0)
			{
				applyStep();
			}
			else
			{
				dismissStep();
			}
		}
	}
}

bool StepRecorder::keyPressEvent(QKeyEvent* ke)
{
	bool event_handled = false;

	switch(ke->key())
	{
		case Qt::Key_Right:
		{
			if(!ke->isAutoRepeat())
			{
				stepForwards();
			}
			event_handled = true;
			break;
		}

		case Qt::Key_Left:
		{
			if(!ke->isAutoRepeat())
			{
				stepBackwards();
			}
			event_handled = true;
			break;
		}
	}

	return event_handled;
}

void StepRecorder::setStepsLength(const TimePos& newLength)
{
	if(m_isStepInProgress)
	{
		//update current step length by the new amount : (number_of_steps * newLength)
		m_curStepLength = (m_curStepLength / m_stepsLength) * newLength;

		updateCurStepNotes();
	}

	m_stepsLength = newLength;

	updateWidget();
}

QVector<Note*> StepRecorder::getCurStepNotes()
{
	QVector<Note*> notes;

	if(m_isStepInProgress)
	{
		for(StepNote* stepNote: m_curStepNotes)
		{
			notes.append(&stepNote->m_note);
		}
	}

	return notes;
}

void StepRecorder::stepForwards()
{
	if(m_isStepInProgress)
	{
		m_curStepLength += m_stepsLength;

		updateCurStepNotes();
	}
	else
	{
		m_curStepStartPos += m_stepsLength;
	}

	updateWidget();
}

void StepRecorder::stepBackwards()
{
	if(m_isStepInProgress)
	{
		if(m_curStepLength > 0)
		{
			m_curStepLength = max(m_curStepLength - m_stepsLength, 0);
		}
		else
		{
			//if length is already zero - move starting position backwards
			m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0);
		}

		updateCurStepNotes();
	}
	else
	{
		m_curStepStartPos = max(m_curStepStartPos - m_stepsLength, 0);
	}

	updateWidget();
}

void StepRecorder::applyStep()
{
	m_pattern->addJournalCheckPoint();

	for (const StepNote* stepNote : m_curStepNotes)
	{
		m_pattern->addNote(stepNote->m_note, false);
	}

	m_pattern->rearrangeAllNotes();
	m_pattern->updateLength();
	m_pattern->dataChanged();
	Engine::getSong()->setModified();

	prepareNewStep();
}

void StepRecorder::dismissStep()
{
	if(!m_isStepInProgress)
	{
		return;
	}

	prepareNewStep();
}

void StepRecorder::prepareNewStep()
{
	for(StepNote* stepNote : m_curStepNotes)
	{
		delete stepNote;
	}
	m_curStepNotes.clear();

	m_isStepInProgress = false;

	m_curStepStartPos = getCurStepEndPos();
	m_curStepLength = 0;

	updateWidget();
}

void StepRecorder::setCurrentPattern( Pattern* newPattern )
{
	if(m_pattern != NULL && m_pattern != newPattern)
	{
		dismissStep();
	}

	m_pattern = newPattern;
}

void StepRecorder::removeNotesReleasedForTooLong()
{
	int nextTimout = std::numeric_limits<int>::max();
	bool notesRemoved = false;

	QMutableVectorIterator<StepNote*> itr(m_curStepNotes);
	while (itr.hasNext())
	{
		StepNote* stepNote = itr.next();

		if(stepNote->isReleased())
		{
			const int timeSinceReleased = stepNote->timeSinceReleased(); // capture value to avoid wraparound when calculting nextTimout
			if (timeSinceReleased >= REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS)
			{
				delete stepNote;
				itr.remove();
				notesRemoved = true;
			}
			else
			{
				nextTimout = min(nextTimout, REMOVE_RELEASED_NOTE_TIME_THRESHOLD_MS - timeSinceReleased);
			}
		}
	}

	if(notesRemoved)
	{
		m_pianoRoll.update();
	}

	if(nextTimout != std::numeric_limits<int>::max())
	{
		m_updateReleasedTimer.start(nextTimout);
	}
	else
	{
		// no released note found for next timout, stop timer
		m_updateReleasedTimer.stop();
	}
}

TimePos StepRecorder::getCurStepEndPos()
{
	return m_curStepStartPos + m_curStepLength;
}

void StepRecorder::updateCurStepNotes()
{
	for (StepNote* stepNote : m_curStepNotes)
	{
		stepNote->m_note.setLength(m_curStepLength);
		stepNote->m_note.setPos(m_curStepStartPos);
	}
}

void StepRecorder::updateWidget()
{
	m_stepRecorderWidget.setStartPosition(m_curStepStartPos);
	m_stepRecorderWidget.setEndPosition(getCurStepEndPos());
	m_stepRecorderWidget.setStepsLength(m_stepsLength);
}

bool StepRecorder::allCurStepNotesReleased()
{
	for (const StepNote* stepNote : m_curStepNotes)
	{
		if(stepNote->isPressed())
		{
			return false;
		}
	}

	return true;
}

StepRecorder::StepNote* StepRecorder::findCurStepNote(const int key)
{
	for (StepNote* stepNote : m_curStepNotes)
	{
		if(stepNote->m_note.key() == key)
		{
			return stepNote;
		}
	}

	return nullptr;
}


code:
/*
 * TempoSyncKnobModel.cpp - adds bpm to ms conversion for knob class
 *
 * Copyright (c) 2005-2007 Danny McRae <khjklujn/at/yahoo.com>
 * Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program 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 program 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.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QAction>
#include <QDomElement>

#include "TempoSyncKnobModel.h"
#include "Engine.h"
#include "Song.h"

TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min,
				const float _max, const float _step,
				const float _scale, Model * _parent,
				const QString & _display_name ) :
	FloatModel( _val, _min, _max, _step, _parent, _display_name ),
	m_tempoSyncMode( SyncNone ),
	m_tempoLastSyncMode( SyncNone ),
	m_scale( _scale ),
	m_custom( _parent )
{
	connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ),
			this, SLOT( calculateTempoSyncTime( bpm_t ) ),
			Qt::DirectConnection );
}

TempoSyncKnobModel::~TempoSyncKnobModel()
{
}

void TempoSyncKnobModel::setTempoSync( QAction * _item )
{
	setTempoSync( _item->data().toInt() );
}

void TempoSyncKnobModel::setTempoSync( int _note_type )
{
	setSyncMode( ( TempoSyncMode ) _note_type );
	Engine::getSong()->setModified();
}

void TempoSyncKnobModel::calculateTempoSyncTime( bpm_t _bpm )
{
	float conversionFactor = 1.0;

	if( m_tempoSyncMode )
	{
		switch( m_tempoSyncMode )
		{
			case SyncCustom:
				conversionFactor =
			static_cast<float>( m_custom.getDenominator() ) /
			static_cast<float>( m_custom.getNumerator() );
				break;
			case SyncDoubleWholeNote:
				conversionFactor = 0.125;
				break;
			case SyncWholeNote:
				conversionFactor = 0.25;
				break;
			case SyncHalfNote:
				conversionFactor = 0.5;
				break;
			case SyncQuarterNote:
				conversionFactor = 1.0;
				break;
			case SyncEighthNote:
				conversionFactor = 2.0;
				break;
			case SyncSixteenthNote:
				conversionFactor = 4.0;
				break;
			case SyncThirtysecondNote:
				conversionFactor = 8.0;
				break;
			default: ;
		}
		bool journalling = testAndSetJournalling( false );
		float oneUnit = 60000.0 / ( _bpm * conversionFactor * m_scale );
		setValue( oneUnit * maxValue() );
		setJournalling( journalling );
	}

	if( m_tempoSyncMode != m_tempoLastSyncMode )
	{
		emit syncModeChanged( m_tempoSyncMode );
		m_tempoLastSyncMode = m_tempoSyncMode;
	}
}

void TempoSyncKnobModel::saveSettings( QDomDocument & _doc, QDomElement & _this,
							const QString & _name )
{
	_this.setAttribute( _name + "_syncmode", (int) syncMode() );
	m_custom.saveSettings( _doc, _this, _name );
	FloatModel::saveSettings( _doc, _this, _name );
}

void TempoSyncKnobModel::loadSettings( const QDomElement & _this,
							const QString & _name )
{
	FloatModel::loadSettings( _this, _name );
	m_custom.loadSettings( _this, _name );
	setSyncMode( ( TempoSyncMode ) _this.attribute( _name + "_syncmode" ).toInt() );
}

void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode )
{
	if( m_tempoSyncMode != _new_mode )
	{
		m_tempoSyncMode = _new_mode;
		if( _new_mode == SyncCustom )
		{
			connect( &m_custom, SIGNAL( dataChanged() ),
					this, SLOT( updateCustom() ),
					Qt::DirectConnection );
		}
	}
	calculateTempoSyncTime( Engine::getSong()->getTempo() );
}

void TempoSyncKnobModel::setScale( float _new_scale )
{
	m_scale = _new_scale;
	calculateTempoSyncTime( Engine::getSong()->getTempo() );
	emit scaleChanged( _new_scale );
}

void TempoSyncKnobModel::updateCustom()
{
	setSyncMode( SyncCustom );
}