Skip to content

Commit

Permalink
SampleBuffer -> Recording: Use less blocking locks with addData and
Browse files Browse the repository at this point in the history
resetData.

I've added two new methods:
	* tryAddData
	* tryResetData

Which do the same, but without locking. If we've failed to lock, no
problem, just wait until the next time.
  • Loading branch information
Reflexe authored and lukas-w committed Jul 30, 2018
1 parent c8337dc commit f765773
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 38 deletions.
26 changes: 24 additions & 2 deletions include/SampleBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,21 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject
static QString tryToMakeRelative( const QString & _file );
static QString tryToMakeAbsolute(const QString & file);

/**
* @brief Try to add data to the buffer,
* @param begin Beginning of an InputIterator.
* @param end End of an InputIterator.
* @param shouldLockMixer Should we call requestChangeInModel?
* @return
*/
bool tryAddData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true);

/**
* @brief Add data to the buffer,
* @param begin Beginning of an InputIterator.
* @param end End of an InputIterator.
* @param shouldLockMixer Should we call requestChangeInModel?
*
* @warning That locks m_varLock for write.
* @return
*/
void addData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true);

Expand All @@ -271,6 +279,15 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject
*/
void resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true);

/**
* @brief A non-blocking version of resetData.
* @param newData mm, that's the new data.
* @param dataSampleRate Sample rate for @a newData.
* @param shouldLockMixer Should we call requestChangeInModel?
* @return
*/
bool tryResetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true);

/**
* @brief Just reverse the current buffer.
* @param shouldLockMixer Should we call requestChangeInModel?
Expand All @@ -290,6 +307,9 @@ public slots:
void sampleRateChanged();

protected:
void internalAddData(const DataVector &vector, sample_rate_t sampleRate);
void internalResetData(DataVector &&newData, sample_rate_t dataSampleRate);

QString & toBase64( QString & _dst ) const;
inline void setSampleRate( sample_rate_t _rate )
{
Expand Down Expand Up @@ -360,6 +380,8 @@ public slots:
*/
void beginBufferChange (bool shouldLock, bool shouldLockMixer=true);

bool tryBeginBufferChange(bool shouldLock, bool shouldLockMixer=true);

/**
* @brief Do some actions necessary after changing m_data.
* @param shouldUnlock The same value you've used on @a beginBufferChange.
Expand Down
91 changes: 80 additions & 11 deletions src/core/SampleBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ void SampleBuffer::sampleRateChanged() {
}
}

void SampleBuffer::internalAddData(const DataVector &vector, sample_rate_t sampleRate)
{
Q_ASSERT(sampleRate == m_sampleRate);
m_data.insert (m_data.end (), vector.cbegin (), vector.cend ());
}

void SampleBuffer::internalResetData(DataVector &&newData, sample_rate_t dataSampleRate) {
Q_UNUSED(dataSampleRate);
m_audioFile = QString();
m_data = std::move (newData);
}

sample_rate_t SampleBuffer::mixerSampleRate()
{
return Engine::mixer ()->processingSampleRate ();
Expand Down Expand Up @@ -546,7 +558,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
bool is_backwards = _state->isBackwards();

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

// calculate how many frames we have in requested pitch
const f_cnt_t total_frames_for_current_pitch = static_cast<f_cnt_t>( (
Expand Down Expand Up @@ -594,7 +606,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
src_data.data_out = _ab->data ();
src_data.input_frames = fragment_size;
src_data.output_frames = _frames;
src_data.src_ratio = 1.0 / freq_factor;
src_data.src_ratio = double(Engine::mixer()->processingSampleRate())/ double(m_sampleRate);
src_data.end_of_input = 0;
int error = src_process( _state->m_resamplingData,
&src_data );
Expand Down Expand Up @@ -1141,6 +1153,23 @@ void SampleBuffer::beginBufferChange(bool shouldLock, bool shouldLockMixer)
}
}

bool SampleBuffer::tryBeginBufferChange(bool shouldLock, bool shouldLockMixer) {
bool result = true;

if (shouldLockMixer) {
Engine::mixer ()->requestChangeInModel ();
}

if (shouldLock) {
result = m_varLock.tryLockForWrite();

if (! result)
Engine::mixer ()->doneChangeInModel();
}

return result;
}

void SampleBuffer::doneBufferChange(bool shouldUnlock,
sample_rate_t bufferSampleRate,
bool shouldUnlockMixer) {
Expand All @@ -1160,25 +1189,53 @@ void SampleBuffer::doneBufferChange(bool shouldUnlock,
emit sampleUpdated();
}

bool SampleBuffer::tryAddData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer) {
DataVector newVector;

if (sampleRate != m_sampleRate) {
// We should resample this data;

newVector = resampleData (vector, sampleRate, m_sampleRate);
sampleRate = m_sampleRate;
}

// First of all, don't let anyone read.
if (! tryBeginBufferChange (true, shouldLockMixer))
return false;
{
if (newVector.empty())
internalAddData(vector,
sampleRate);
else
internalAddData(newVector,
sampleRate);
}
doneBufferChange (true, /* lock */
this->sampleRate(),
shouldLockMixer);

return true;
}

void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer) {
DataVector newVector;

if (sampleRate != m_sampleRate) {
// We should resample this data;

newVector = resampleData (vector, sampleRate, m_sampleRate);
sampleRate = m_sampleRate;
}

// First of all, don't let anyone read.
beginBufferChange (true, shouldLockMixer);
{
if (! newVector.empty()) {
// Insert to the end of the resampled vector.
m_data.insert (m_data.end (), newVector.cbegin (), newVector.cend ());
} else {
// Insert to the end of the vector.
m_data.insert (m_data.end (), vector.cbegin (), vector.cend ());
}
if (newVector.empty())
internalAddData(vector,
sampleRate);
else
internalAddData(newVector,
sampleRate);
}
doneBufferChange (true, /* lock */
this->sampleRate(),
Expand All @@ -1188,12 +1245,24 @@ void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t
void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer) {
beginBufferChange (true, shouldLockMixer);
{
m_audioFile = QString();
m_data = std::move (newData);
internalResetData(std::move(newData), dataSampleRate);
}
doneBufferChange (true, /* lock */
dataSampleRate,
shouldLockMixer);
}

bool SampleBuffer::tryResetData(SampleBuffer::DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer) {
if (! tryBeginBufferChange (true, shouldLockMixer))
return false;
{
internalResetData(std::move(newData), dataSampleRate);
}
doneBufferChange (true, /* lock */
dataSampleRate,
shouldLockMixer);

return true;
}

void SampleBuffer::reverse(bool shouldLockMixer) {
Expand Down
23 changes: 12 additions & 11 deletions src/core/SampleBufferVisualizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,7 @@ void SampleBufferVisualizer::appendMultipleTacts(const SampleBuffer &sampleBuffe

Q_ASSERT((offsetFromTact + totalTime) <= MidiTime::ticksPerTact());

auto result = appendTact(sampleBuffer,
totalTime,
parentRect,
pen,
isCompleteTact);
if (! result) {
if (pixelsPerTime(totalTime) < 1) {
// We can't paint it.
// totalTime is too short. skip it.
// or just wait until we have enough frames.
Expand All @@ -127,9 +122,16 @@ void SampleBufferVisualizer::appendMultipleTacts(const SampleBuffer &sampleBuffe
// Wait until we have enough frames.
break;
}

}

auto result = appendTact(sampleBuffer,
totalTime,
parentRect,
pen,
isCompleteTact);
if (! result)
break;

if (isCompleteTact) {
m_cachedTime += ( m_currentPixmap.totalTime );
m_cachedPixmaps.push_back(std::move(m_currentPixmap));
Expand All @@ -151,14 +153,13 @@ bool SampleBufferVisualizer::appendTact(const SampleBuffer &sampleBuffer,
offsetFromTact,
totalTime,
isLastInTact);
if (currentPaintInTact.width() < 1) {
return false;
}
Q_ASSERT(currentPaintInTact.width() > 0);

// Generate the actual visualization.
auto fromFrame = MidiTime(m_cachedTime + offsetFromTact).frames (m_framesPerTact);

sampleBuffer.dataReadLock();
if (! sampleBuffer.tryDataReadLock())
return false;
auto poly = sampleBuffer.visualizeToPoly (currentPaintInTact,
QRect(),
fromFrame,
Expand Down
52 changes: 38 additions & 14 deletions src/core/SampleRecordHandle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTime

SampleRecordHandle::~SampleRecordHandle()
{
if (! m_currentBuffer.empty()) {
// We have data that has not been written into the buffer.
// force-write it into the buffer.

if (m_framesRecorded == 0) {
m_tco->sampleBuffer ()->resetData (std::move (m_currentBuffer),
Engine::mixer ()->inputSampleRate (),
false);
m_tco->setStartTimeOffset (m_startRecordTimeOffset);
} else {
m_tco->sampleBuffer ()->addData(m_currentBuffer,
Engine::mixer ()->inputSampleRate (),
false);
}
}

m_tco->updateLength ();

// If this is an automatically created tco,
Expand All @@ -68,26 +84,32 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ )
{
const sampleFrame * recbuf = Engine::mixer()->inputBuffer();
const f_cnt_t frames = Engine::mixer()->inputBufferFrames();
m_currentBuffer.clear ();

writeBuffer( recbuf, frames );

// It is the first buffer.
bool dataWrittenIntoSampleBuffer = true;

// Try to add data to the buffer.
// If we could not do that. We'll do that next time.
if (m_framesRecorded == 0) {
// Make sure we don't have the previous data.
m_tco->sampleBuffer ()->resetData (std::move (m_currentBuffer),
Engine::mixer ()->inputSampleRate (),
false);
dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryResetData (std::move (m_currentBuffer),
Engine::mixer ()->inputSampleRate (),
false);
m_tco->setStartTimeOffset (m_startRecordTimeOffset);
} else {
if (! m_currentBuffer.empty ()) {
m_tco->sampleBuffer ()->addData (m_currentBuffer,
Engine::mixer ()->inputSampleRate (),
false);
dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryAddData(m_currentBuffer,
Engine::mixer ()->inputSampleRate (),
false);
}
}

m_framesRecorded += frames;
m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ());
if (dataWrittenIntoSampleBuffer) {
m_framesRecorded += frames;
m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ());
m_currentBuffer.clear();
}
}


Expand Down Expand Up @@ -155,7 +177,9 @@ void SampleRecordHandle::copyBufferFromStereo(const sampleFrame *inputBuffer,
void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,
const f_cnt_t _frames )
{
m_currentBuffer.resize (_frames);
auto framesInBuffer = m_currentBuffer.size();
// Add _frames elements to the buffer.
m_currentBuffer.resize (framesInBuffer + _frames);

// Depending on the recording channel, copy the buffer as a
// mono-right, mono-left or stereo.
Expand All @@ -167,13 +191,13 @@ void SampleRecordHandle::writeBuffer( const sampleFrame * _ab,

switch(m_recordingChannel) {
case SampleTrack::RecordingChannel::MonoLeft:
copyBufferFromMonoLeft(_ab, m_currentBuffer.data (), _frames);
copyBufferFromMonoLeft(_ab, m_currentBuffer.data () + framesInBuffer, _frames);
break;
case SampleTrack::RecordingChannel::MonoRight:
copyBufferFromMonoRight(_ab, m_currentBuffer.data (), _frames);
copyBufferFromMonoRight(_ab, m_currentBuffer.data () + framesInBuffer, _frames);
break;
case SampleTrack::RecordingChannel::Stereo:
copyBufferFromStereo(_ab, m_currentBuffer.data (), _frames);
copyBufferFromStereo(_ab, m_currentBuffer.data () + framesInBuffer, _frames);
break;
default:
Q_ASSERT(false);
Expand Down

0 comments on commit f765773

Please sign in to comment.