mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-17 11:18:30 -04:00
1592 lines
36 KiB
C++
1592 lines
36 KiB
C++
/*
|
|
* 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 "Oscillator.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#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 "AudioEngine.h"
|
|
#include "base64.h"
|
|
#include "ConfigManager.h"
|
|
#include "DrumSynth.h"
|
|
#include "endian_handling.h"
|
|
#include "Engine.h"
|
|
#include "GuiApplication.h"
|
|
#include "Note.h"
|
|
#include "PathUtil.h"
|
|
|
|
#include "FileDialog.h"
|
|
|
|
namespace lmms
|
|
{
|
|
|
|
SampleBuffer::SampleBuffer() :
|
|
m_userAntiAliasWaveTable(nullptr),
|
|
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(DefaultBaseFreq),
|
|
m_sampleRate(audioEngineSampleRate())
|
|
{
|
|
|
|
connect(Engine::audioEngine(), 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::audioEngineSampleRate()
|
|
{
|
|
return Engine::audioEngine()->processingSampleRate();
|
|
}
|
|
|
|
|
|
void SampleBuffer::update(bool keepSettings)
|
|
{
|
|
const bool lock = (m_data != nullptr);
|
|
if (lock)
|
|
{
|
|
Engine::audioEngine()->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 = audioEngineSampleRate();
|
|
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::audioEngine()->doneChangeInModel();
|
|
}
|
|
|
|
emit sampleUpdated();
|
|
|
|
// allocate space for anti-aliased wave table
|
|
if (m_userAntiAliasWaveTable == nullptr)
|
|
{
|
|
m_userAntiAliasWaveTable = std::make_unique<OscillatorConstants::waveform_t>();
|
|
}
|
|
Oscillator::generateAntiAliasUserWaveTable(this);
|
|
|
|
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::getGUI() != nullptr)
|
|
{
|
|
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 != audioEngineSampleRate())
|
|
{
|
|
SampleBuffer * resampled = resample(srcSR, audioEngineSampleRate());
|
|
|
|
m_sampleRate = audioEngineSampleRate();
|
|
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 != audioEngineSampleRate())
|
|
{
|
|
auto oldRateToNewRateRatio = static_cast<float>(audioEngineSampleRate()) / 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 = audioEngineSampleRate();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
{
|
|
auto 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;
|
|
|
|
auto 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 // LMMS_HAVE_OGGVORBIS
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
// The SampleBuffer can play a given sample with increased or decreased pitch. However, only
|
|
// samples that contain a tone that matches the default base note frequency of 440 Hz will
|
|
// produce the exact requested pitch in [Hz].
|
|
const double freqFactor = (double) freq / (double) m_frequency *
|
|
m_sampleRate / Engine::audioEngine()->processingSampleRate();
|
|
|
|
// calculate how many frames we have in requested pitch
|
|
const auto 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);
|
|
}
|
|
|
|
|
|
/* @brief Draws a sample buffer on the QRect given in the range [fromFrame, toFrame)
|
|
* @param QPainter p: Painter object for the painting operations
|
|
* @param QRect dr: QRect where the buffer will be drawn in
|
|
* @param QRect clip: QRect used for clipping
|
|
* @param f_cnt_t fromFrame: First frame of the range
|
|
* @param f_cnt_t toFrame: Last frame of the range non-inclusive
|
|
*/
|
|
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;
|
|
//TODO: If the clip QRect is not being used we should remove it
|
|
//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 double fpp = std::max(1., static_cast<double>(nbFrames) / w);
|
|
// There are 2 possibilities: Either nbFrames is bigger than
|
|
// the width, so we will have width points, or nbFrames is
|
|
// smaller than the width (fpp = 1) and we will have nbFrames
|
|
// points
|
|
const int totalPoints = nbFrames > w
|
|
? w
|
|
: nbFrames;
|
|
std::vector<QPointF> fEdgeMax(totalPoints);
|
|
std::vector<QPointF> fEdgeMin(totalPoints);
|
|
std::vector<QPointF> fRmsMax(totalPoints);
|
|
std::vector<QPointF> fRmsMin(totalPoints);
|
|
int curPixel = 0;
|
|
const int xb = dr.x();
|
|
const int first = focusOnRange ? fromFrame : 0;
|
|
const int last = focusOnRange ? toFrame - 1 : m_frames - 1;
|
|
// When the number of frames isn't perfectly divisible by the
|
|
// width, the remaining frames don't fit the last pixel and are
|
|
// past the visible area. lastVisibleFrame is the index number of
|
|
// the last visible frame.
|
|
const int visibleFrames = (fpp * w);
|
|
const int lastVisibleFrame = focusOnRange
|
|
? fromFrame + visibleFrames - 1
|
|
: visibleFrames - 1;
|
|
|
|
for (double frame = first; frame <= last && frame <= lastVisibleFrame; frame += fpp)
|
|
{
|
|
float maxData = -1;
|
|
float minData = 1;
|
|
|
|
float rmsData[2] = {0, 0};
|
|
|
|
// Find maximum and minimum samples within range
|
|
for (int i = 0; i < fpp && frame + i <= last; ++i)
|
|
{
|
|
for (int j = 0; j < 2; ++j)
|
|
{
|
|
auto curData = m_data[static_cast<int>(frame) + i][j];
|
|
|
|
if (curData > maxData) { maxData = curData; }
|
|
if (curData < minData) { minData = curData; }
|
|
|
|
rmsData[j] += curData * curData;
|
|
}
|
|
}
|
|
|
|
const float trueRmsData = (rmsData[0] + rmsData[1]) / 2 / fpp;
|
|
const float sqrtRmsData = sqrt(trueRmsData);
|
|
const float maxRmsData = qBound(minData, sqrtRmsData, maxData);
|
|
const float minRmsData = qBound(minData, -sqrtRmsData, maxData);
|
|
|
|
// If nbFrames >= w, we can use curPixel to calculate X
|
|
// but if nbFrames < w, we need to calculate it proportionally
|
|
// to the total number of points
|
|
auto x = nbFrames >= w
|
|
? xb + curPixel
|
|
: xb + ((static_cast<double>(curPixel) / nbFrames) * w);
|
|
// Partial Y calculation
|
|
auto py = ySpace * m_amplification;
|
|
fEdgeMax[curPixel] = QPointF(x, (yb - (maxData * py)));
|
|
fEdgeMin[curPixel] = QPointF(x, (yb - (minData * py)));
|
|
fRmsMax[curPixel] = QPointF(x, (yb - (maxRmsData * py)));
|
|
fRmsMin[curPixel] = QPointF(x, (yb - (minRmsData * py)));
|
|
++curPixel;
|
|
}
|
|
|
|
for (int i = 0; i < totalPoints; ++i)
|
|
{
|
|
p.drawLine(fEdgeMax[i], fEdgeMin[i]);
|
|
}
|
|
|
|
p.setPen(p.pen().color().lighter(123));
|
|
|
|
for (int i = 0; i < totalPoints; ++i)
|
|
{
|
|
p.drawLine(fRmsMax[i], fRmsMin[i]);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SampleBuffer::openAudioFile() const
|
|
{
|
|
gui::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(gui::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 // LMMS_HAVE_FLAC_STREAM_ENCODER_H
|
|
|
|
|
|
|
|
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::audioEngine()->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)(
|
|
AudioEngine::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 auto dstFrames = static_cast<f_cnt_t>((frames / (float)srcSR) * (float)dstSR);
|
|
auto 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 // LMMS_HAVE_FLAC_STREAM_DECODER_H
|
|
|
|
|
|
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 // LMMS_HAVE_FLAC_STREAM_DECODER_H
|
|
|
|
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::audioEngine()->requestChangeInModel();
|
|
m_varLock.lockForWrite();
|
|
if (m_reversed != on) { std::reverse(m_data, m_data + m_frames); }
|
|
m_reversed = on;
|
|
m_varLock.unlock();
|
|
Engine::audioEngine()->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);
|
|
}
|
|
|
|
} // namespace lmms
|