Files
lmms/src/core/SampleBuffer.cpp
saker 0b27497be2 clang-tidy: Apply modernize-use-auto everywhere (#6480)
Note: clang-tidy was run with `--format-style=file`.
2022-09-14 19:27:53 +02:00

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