Merge pull request #557 from diizy/loops
SampleBuffer, AFP: implement ping-pong loop mode
@@ -46,6 +46,11 @@ class EXPORT SampleBuffer : public QObject, public sharedObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum LoopMode {
|
||||
LoopOff = 0,
|
||||
LoopOn,
|
||||
LoopPingPong
|
||||
};
|
||||
class EXPORT handleState
|
||||
{
|
||||
public:
|
||||
@@ -62,11 +67,21 @@ public:
|
||||
m_frameIndex = _index;
|
||||
}
|
||||
|
||||
inline bool isBackwards() const
|
||||
{
|
||||
return m_isBackwards;
|
||||
}
|
||||
|
||||
inline void setBackwards( bool _backwards )
|
||||
{
|
||||
m_isBackwards = _backwards;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
f_cnt_t m_frameIndex;
|
||||
const bool m_varyingPitch;
|
||||
bool m_isBackwards;
|
||||
SRC_STATE * m_resamplingData;
|
||||
|
||||
friend class SampleBuffer;
|
||||
@@ -86,7 +101,7 @@ public:
|
||||
bool play( sampleFrame * _ab, handleState * _state,
|
||||
const fpp_t _frames,
|
||||
const float _freq,
|
||||
const bool _looped = false );
|
||||
const LoopMode _loopmode = LoopOff );
|
||||
|
||||
void visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0 );
|
||||
inline void visualize( QPainter & _p, const QRect & _dr, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0 )
|
||||
@@ -108,7 +123,7 @@ public:
|
||||
{
|
||||
return m_endFrame;
|
||||
}
|
||||
|
||||
|
||||
inline f_cnt_t loopStartFrame() const
|
||||
{
|
||||
return m_loopStartFrame;
|
||||
@@ -272,10 +287,13 @@ private:
|
||||
float m_frequency;
|
||||
sample_rate_t m_sampleRate;
|
||||
|
||||
sampleFrame * getSampleFragment( f_cnt_t _start, f_cnt_t _frames,
|
||||
bool _looped,
|
||||
sampleFrame * * _tmp ) const;
|
||||
f_cnt_t getLoopedIndex( f_cnt_t _index ) const;
|
||||
sampleFrame * 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;
|
||||
f_cnt_t getLoopedIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const;
|
||||
f_cnt_t getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const;
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* audio_file_processor.cpp - instrument for using audio-files
|
||||
*
|
||||
* Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
|
||||
*
|
||||
*
|
||||
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -76,9 +76,10 @@ audioFileProcessor::audioFileProcessor( InstrumentTrack * _instrument_track ) :
|
||||
m_endPointModel( 1, 0, 1, 0.0000001f, this, tr( "End of sample" ) ),
|
||||
m_loopPointModel( 0, 0, 1, 0.0000001f, this, tr( "Loopback point" ) ),
|
||||
m_reverseModel( false, this, tr( "Reverse sample" ) ),
|
||||
m_loopModel( false, this, tr( "Loop enabled" ) ),
|
||||
m_loopModel( 0, 0, 2, this, tr( "Loop mode" ) ),
|
||||
m_stutterModel( false, this, tr( "Stutter" ) ),
|
||||
m_nextPlayStartPoint( 0 )
|
||||
m_nextPlayStartPoint( 0 ),
|
||||
m_nextPlayBackwards( false )
|
||||
{
|
||||
connect( &m_reverseModel, SIGNAL( dataChanged() ),
|
||||
this, SLOT( reverseModelChanged() ) );
|
||||
@@ -116,6 +117,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
if( m_stutterModel.value() == true && _n->frequency() < 20.0 )
|
||||
{
|
||||
m_nextPlayStartPoint = m_sampleBuffer.startFrame();
|
||||
m_nextPlayBackwards = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,9 +128,11 @@ void audioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
// Restart playing the note if in stutter mode, not in loop mode,
|
||||
// and we're at the end of the sample.
|
||||
m_nextPlayStartPoint = m_sampleBuffer.startFrame();
|
||||
m_nextPlayBackwards = false;
|
||||
}
|
||||
_n->m_pluginData = new handleState( _n->hasDetuningInfo() );
|
||||
((handleState *)_n->m_pluginData)->setFrameIndex( m_nextPlayStartPoint );
|
||||
((handleState *)_n->m_pluginData)->setBackwards( m_nextPlayBackwards );
|
||||
|
||||
// debug code
|
||||
/* qDebug( "frames %d", m_sampleBuffer.frames() );
|
||||
@@ -141,7 +145,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
if( m_sampleBuffer.play( _working_buffer,
|
||||
(handleState *)_n->m_pluginData,
|
||||
frames, _n->frequency(),
|
||||
m_loopModel.value() ) )
|
||||
static_cast<SampleBuffer::LoopMode>( m_loopModel.value() ) ) )
|
||||
{
|
||||
applyRelease( _working_buffer, _n );
|
||||
instrumentTrack()->processAudioBuffer( _working_buffer,
|
||||
@@ -161,6 +165,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n,
|
||||
if( m_stutterModel.value() == true )
|
||||
{
|
||||
m_nextPlayStartPoint = ((handleState *)_n->m_pluginData)->frameIndex();
|
||||
m_nextPlayBackwards = ((handleState *)_n->m_pluginData)->isBackwards();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +219,7 @@ void audioFileProcessor::loadSettings( const QDomElement & _this )
|
||||
m_ampModel.loadSettings( _this, "amp" );
|
||||
m_startPointModel.loadSettings( _this, "sframe" );
|
||||
m_endPointModel.loadSettings( _this, "eframe" );
|
||||
|
||||
|
||||
// compat code for not having a separate loopback point
|
||||
if( _this.hasAttribute( "lframe" ) )
|
||||
{
|
||||
@@ -307,6 +312,7 @@ void audioFileProcessor::ampModelChanged( void )
|
||||
void audioFileProcessor::stutterModelChanged()
|
||||
{
|
||||
m_nextPlayStartPoint = m_sampleBuffer.startFrame();
|
||||
m_nextPlayBackwards = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -321,28 +327,38 @@ void audioFileProcessor::loopPointChanged( void )
|
||||
m_endPointModel.setValue( m_startPointModel.value() );
|
||||
m_startPointModel.setValue( tmp );
|
||||
}
|
||||
|
||||
|
||||
// check if start & end overlap and nudge end up if so
|
||||
if( m_startPointModel.value() == m_endPointModel.value() )
|
||||
{
|
||||
m_endPointModel.setValue( qMin( m_endPointModel.value() + 0.001f, 1.0f ) );
|
||||
}
|
||||
|
||||
const f_cnt_t f_start = static_cast<f_cnt_t>( m_startPointModel.value() *
|
||||
( m_sampleBuffer.frames()-1 ) );
|
||||
const f_cnt_t f_end = static_cast<f_cnt_t>( m_endPointModel.value() *
|
||||
( m_sampleBuffer.frames()-1 ) );
|
||||
m_nextPlayStartPoint = f_start;
|
||||
|
||||
// check that loop point is between start-end points
|
||||
// check that loop point is between start-end points and not overlapping with endpoint
|
||||
// ...and move start/end points ahead if loop point is moved over them
|
||||
if( m_loopPointModel.value() >= m_endPointModel.value() )
|
||||
{
|
||||
m_endPointModel.setValue( m_loopPointModel.value() + 0.001f );
|
||||
if( m_endPointModel.value() == 1.0f )
|
||||
{
|
||||
m_loopPointModel.setValue( 1.0f - 0.001f );
|
||||
}
|
||||
}
|
||||
if( m_loopPointModel.value() < m_startPointModel.value() )
|
||||
m_loopPointModel.setValue( m_startPointModel.value() );
|
||||
if( m_loopPointModel.value() > m_endPointModel.value() )
|
||||
m_loopPointModel.setValue( m_endPointModel.value() );
|
||||
{
|
||||
m_startPointModel.setValue( m_loopPointModel.value() );
|
||||
}
|
||||
|
||||
const f_cnt_t f_start = static_cast<f_cnt_t>( m_startPointModel.value() * ( m_sampleBuffer.frames()-1 ) );
|
||||
const f_cnt_t f_end = static_cast<f_cnt_t>( m_endPointModel.value() * ( m_sampleBuffer.frames()-1 ) );
|
||||
const f_cnt_t f_loop = static_cast<f_cnt_t>( m_loopPointModel.value() * ( m_sampleBuffer.frames()-1 ) );
|
||||
|
||||
m_nextPlayStartPoint = f_start;
|
||||
m_nextPlayBackwards = false;
|
||||
|
||||
m_sampleBuffer.setStartFrame( f_start );
|
||||
m_sampleBuffer.setEndFrame( f_end );
|
||||
m_sampleBuffer.setLoopStartFrame( static_cast<f_cnt_t>( m_loopPointModel.value() * ( m_sampleBuffer.frames()-1 ) ) );
|
||||
m_sampleBuffer.setLoopStartFrame( f_loop );
|
||||
m_sampleBuffer.setLoopEndFrame( f_end );
|
||||
emit dataChanged();
|
||||
}
|
||||
@@ -377,13 +393,13 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument,
|
||||
m_openAudioFileButton->setWhatsThis(
|
||||
tr( "Click here, if you want to open another audio-file. "
|
||||
"A dialog will appear where you can select your file. "
|
||||
"Settings like looping-mode, start and end-points, "
|
||||
"Settings like looping-mode, start and end-points, "
|
||||
"amplify-value, and so on are not reset. So, it may not "
|
||||
"sound like the original sample.") );
|
||||
|
||||
m_reverseButton = new pixmapButton( this );
|
||||
m_reverseButton->setCheckable( TRUE );
|
||||
m_reverseButton->move( 174, 124 );
|
||||
m_reverseButton->move( 200, 124 );
|
||||
m_reverseButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"reverse_on" ) );
|
||||
m_reverseButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
@@ -394,18 +410,51 @@ AudioFileProcessorView::AudioFileProcessorView( Instrument * _instrument,
|
||||
"This is useful for cool effects, e.g. a reversed "
|
||||
"crash." ) );
|
||||
|
||||
m_loopButton = new pixmapButton( this );
|
||||
m_loopButton->setCheckable( TRUE );
|
||||
m_loopButton->move( 200, 124 );
|
||||
m_loopButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_on" ) );
|
||||
m_loopButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_off" ) );
|
||||
toolTip::add( m_loopButton, tr( "Enable loop" ) );
|
||||
m_loopButton->setWhatsThis(
|
||||
tr( "This button enables looping. "
|
||||
// loop button group
|
||||
|
||||
pixmapButton * m_loopOffButton = new pixmapButton( this );
|
||||
m_loopOffButton->setCheckable( TRUE );
|
||||
m_loopOffButton->move( 174, 144 );
|
||||
m_loopOffButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_off_on" ) );
|
||||
m_loopOffButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_off_off" ) );
|
||||
toolTip::add( m_loopOffButton, tr( "Disable loop" ) );
|
||||
m_loopOffButton->setWhatsThis(
|
||||
tr( "This button disables looping. "
|
||||
"The sample plays only once from start to end. " ) );
|
||||
|
||||
|
||||
pixmapButton * m_loopOnButton = new pixmapButton( this );
|
||||
m_loopOnButton->setCheckable( TRUE );
|
||||
m_loopOnButton->move( 200, 144 );
|
||||
m_loopOnButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_on_on" ) );
|
||||
m_loopOnButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_on_off" ) );
|
||||
toolTip::add( m_loopOnButton, tr( "Enable loop" ) );
|
||||
m_loopOnButton->setWhatsThis(
|
||||
tr( "This button enables forwards-looping. "
|
||||
"The sample loops between the end point and the loop point." ) );
|
||||
|
||||
|
||||
pixmapButton * m_loopPingPongButton = new pixmapButton( this );
|
||||
m_loopPingPongButton->setCheckable( TRUE );
|
||||
m_loopPingPongButton->move( 226, 144 );
|
||||
m_loopPingPongButton->setActiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_pingpong_on" ) );
|
||||
m_loopPingPongButton->setInactiveGraphic( PLUGIN_NAME::getIconPixmap(
|
||||
"loop_pingpong_off" ) );
|
||||
toolTip::add( m_loopPingPongButton, tr( "Enable loop" ) );
|
||||
m_loopPingPongButton->setWhatsThis(
|
||||
tr( "This button enables ping-pong-looping. "
|
||||
"The sample loops backwards and forwards between the end point "
|
||||
"and the loop point." ) );
|
||||
|
||||
m_loopGroup = new automatableButtonGroup( this );
|
||||
m_loopGroup->addButton( m_loopOffButton );
|
||||
m_loopGroup->addButton( m_loopOnButton );
|
||||
m_loopGroup->addButton( m_loopPingPongButton );
|
||||
|
||||
m_stutterButton = new pixmapButton( this );
|
||||
m_stutterButton->setCheckable( true );
|
||||
m_stutterButton->move( 226, 124 );
|
||||
@@ -604,7 +653,7 @@ void AudioFileProcessorView::modelChanged( void )
|
||||
m_endKnob->setModel( &a->m_endPointModel );
|
||||
m_loopKnob->setModel( &a->m_loopPointModel );
|
||||
m_reverseButton->setModel( &a->m_reverseModel );
|
||||
m_loopButton->setModel( &a->m_loopModel );
|
||||
m_loopGroup->setModel( &a->m_loopModel );
|
||||
m_stutterButton->setModel( &a->m_stutterModel );
|
||||
sampleUpdated();
|
||||
}
|
||||
@@ -675,16 +724,23 @@ void AudioFileProcessorWaveView::mousePressEvent( QMouseEvent * _me )
|
||||
{
|
||||
m_isDragging = true;
|
||||
m_draggingLastPoint = _me->pos();
|
||||
|
||||
const int start_dist = qAbs( m_startFrameX - _me->x() );
|
||||
const int end_dist = qAbs( m_endFrameX - _me->x() );
|
||||
const int loop_dist = qAbs( m_loopFrameX - _me->x() );
|
||||
|
||||
|
||||
const int x = _me->x();
|
||||
|
||||
const int start_dist = qAbs( m_startFrameX - x );
|
||||
const int end_dist = qAbs( m_endFrameX - x );
|
||||
const int loop_dist = qAbs( m_loopFrameX - x );
|
||||
|
||||
draggingType dt = sample_loop; int md = loop_dist;
|
||||
if( start_dist < loop_dist ) { dt = sample_start; md = start_dist; }
|
||||
if( end_dist < start_dist ) { dt = sample_end; md = end_dist; }
|
||||
|
||||
if( md < 3 )
|
||||
else if( end_dist < loop_dist ) { dt = sample_end; md = end_dist; }
|
||||
|
||||
/* qDebug( "x %d", x );
|
||||
qDebug( "loopframex %d", m_loopFrameX );
|
||||
qDebug( "dt %d", dt );
|
||||
qDebug( "md %d", md );*/
|
||||
|
||||
if( md < 4 )
|
||||
{
|
||||
m_draggingType = dt;
|
||||
}
|
||||
@@ -780,7 +836,6 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe )
|
||||
|
||||
p.drawPixmap( s_padding, s_padding, m_graph );
|
||||
|
||||
p.setPen( QColor( 0xFF, 0xFF, 0xFF ) ); //TODO: put into a qproperty
|
||||
const QRect graph_rect( s_padding, s_padding, width() - 2 * s_padding, height() - 2 * s_padding );
|
||||
const f_cnt_t frames = m_to - m_from;
|
||||
m_startFrameX = graph_rect.x() + ( m_sampleBuffer.startFrame() - m_from ) *
|
||||
@@ -792,17 +847,21 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe )
|
||||
const int played_width_px = ( m_framesPlayed - m_from ) *
|
||||
double( graph_rect.width() ) / frames;
|
||||
|
||||
// loop point line
|
||||
p.setPen( QColor( 0x7F, 0xFF, 0xFF ) ); //TODO: put into a qproperty
|
||||
p.drawLine( m_loopFrameX, graph_rect.y(),
|
||||
m_loopFrameX,
|
||||
graph_rect.height() + graph_rect.y() );
|
||||
|
||||
// start/end lines
|
||||
p.setPen( QColor( 0xFF, 0xFF, 0xFF ) ); //TODO: put into a qproperty
|
||||
p.drawLine( m_startFrameX, graph_rect.y(),
|
||||
m_startFrameX,
|
||||
graph_rect.height() + graph_rect.y() );
|
||||
p.drawLine( m_endFrameX, graph_rect.y(),
|
||||
m_endFrameX,
|
||||
graph_rect.height() + graph_rect.y() );
|
||||
|
||||
p.setPen( QColor( 0x7F, 0xFF, 0xFF ) ); //TODO: put into a qproperty
|
||||
p.drawLine( m_loopFrameX, graph_rect.y(),
|
||||
m_loopFrameX,
|
||||
graph_rect.height() + graph_rect.y() );
|
||||
|
||||
|
||||
if( m_endFrameX - m_startFrameX > 2 )
|
||||
{
|
||||
@@ -1006,7 +1065,7 @@ void AudioFileProcessorWaveView::setKnobs( knob * _start, knob * _end, knob * _l
|
||||
|
||||
m_endKnob->setWaveView( this );
|
||||
m_endKnob->setRelatedKnob( m_startKnob );
|
||||
|
||||
|
||||
m_loopKnob->setWaveView( this );
|
||||
}
|
||||
|
||||
@@ -1130,7 +1189,7 @@ bool AudioFileProcessorWaveView::knob::checkBound( double _v ) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if( ( m_relatedKnob->model()->value() - _v > 0 ) !=
|
||||
( m_relatedKnob->model()->value() - model()->value() >= 0 ) )
|
||||
return false;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "SampleBuffer.h"
|
||||
#include "knob.h"
|
||||
#include "pixmap_button.h"
|
||||
#include "automatable_button.h"
|
||||
|
||||
|
||||
|
||||
@@ -91,10 +92,11 @@ private:
|
||||
FloatModel m_endPointModel;
|
||||
FloatModel m_loopPointModel;
|
||||
BoolModel m_reverseModel;
|
||||
BoolModel m_loopModel;
|
||||
IntModel m_loopModel;
|
||||
BoolModel m_stutterModel;
|
||||
|
||||
f_cnt_t m_nextPlayStartPoint;
|
||||
bool m_nextPlayBackwards;
|
||||
|
||||
friend class AudioFileProcessorView;
|
||||
|
||||
@@ -137,7 +139,7 @@ private:
|
||||
|
||||
pixmapButton * m_openAudioFileButton;
|
||||
pixmapButton * m_reverseButton;
|
||||
pixmapButton * m_loopButton;
|
||||
automatableButtonGroup * m_loopGroup;
|
||||
pixmapButton * m_stutterButton;
|
||||
|
||||
} ;
|
||||
@@ -270,7 +272,7 @@ private:
|
||||
|
||||
static bool isCloseTo( int _a, int _b )
|
||||
{
|
||||
return qAbs( _a - _b ) < 3;
|
||||
return qAbs( _a - _b ) < 4;
|
||||
}
|
||||
|
||||
} ;
|
||||
|
||||
BIN
plugins/audio_file_processor/loop_off_off.png
Normal file
|
After Width: | Height: | Size: 534 B |
BIN
plugins/audio_file_processor/loop_off_on.png
Normal file
|
After Width: | Height: | Size: 463 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
plugins/audio_file_processor/loop_pingpong_off.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
plugins/audio_file_processor/loop_pingpong_on.png
Normal file
|
After Width: | Height: | Size: 611 B |
@@ -149,7 +149,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n,
|
||||
hdata->sample->frequency();
|
||||
|
||||
if( hdata->sample->play( _working_buffer, hdata->state, frames,
|
||||
play_freq, m_loopedModel.value() ) )
|
||||
play_freq, m_loopedModel.value() ? SampleBuffer::LoopOn : SampleBuffer::LoopOff ) )
|
||||
{
|
||||
applyRelease( _working_buffer, _n );
|
||||
instrumentTrack()->processAudioBuffer( _working_buffer,
|
||||
|
||||
@@ -610,23 +610,31 @@ f_cnt_t SampleBuffer::decodeSampleDS( const char * _f,
|
||||
bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
|
||||
const fpp_t _frames,
|
||||
const float _freq,
|
||||
const bool _looped )
|
||||
{
|
||||
const LoopMode _loopmode )
|
||||
{
|
||||
QMutexLocker ml( &m_varLock );
|
||||
|
||||
f_cnt_t startFrame = m_startFrame;
|
||||
f_cnt_t endFrame = m_endFrame;
|
||||
f_cnt_t loopStartFrame = m_loopStartFrame;
|
||||
f_cnt_t loopEndFrame = m_loopEndFrame;
|
||||
|
||||
engine::mixer()->clearAudioBuffer( _ab, _frames );
|
||||
|
||||
if( m_endFrame == 0 || _frames == 0 )
|
||||
if( endFrame == 0 || _frames == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// variable for determining if we should currently be playing backwards in a ping-pong loop
|
||||
bool is_backwards = _state->isBackwards();
|
||||
|
||||
const double freq_factor = (double) _freq / (double) m_frequency *
|
||||
m_sampleRate / 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>( (
|
||||
m_endFrame - m_startFrame ) /
|
||||
endFrame - startFrame ) /
|
||||
freq_factor );
|
||||
|
||||
if( total_frames_for_current_pitch == 0 )
|
||||
@@ -634,36 +642,47 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// this holds the number of the first frame to play
|
||||
f_cnt_t play_frame = _state->m_frameIndex;
|
||||
if( play_frame < m_startFrame )
|
||||
|
||||
if( play_frame < startFrame )
|
||||
{
|
||||
play_frame = m_startFrame;
|
||||
play_frame = startFrame;
|
||||
}
|
||||
|
||||
// this holds the number of remaining frames in current loop
|
||||
f_cnt_t frames_for_loop;
|
||||
if( _looped )
|
||||
if( _loopmode == LoopOn )
|
||||
{
|
||||
play_frame = getLoopedIndex( play_frame );
|
||||
frames_for_loop = static_cast<f_cnt_t>(
|
||||
( m_loopEndFrame - play_frame ) /
|
||||
freq_factor );
|
||||
play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame );
|
||||
frames_for_loop = static_cast<f_cnt_t>( ( loopEndFrame - play_frame ) / freq_factor );
|
||||
}
|
||||
|
||||
else if( _loopmode == LoopPingPong )
|
||||
{
|
||||
play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame );
|
||||
if( is_backwards )
|
||||
frames_for_loop = static_cast<f_cnt_t>( ( play_frame - loopStartFrame ) / freq_factor );
|
||||
else
|
||||
frames_for_loop = static_cast<f_cnt_t>( ( loopEndFrame - play_frame ) / freq_factor );
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if( play_frame >= m_endFrame )
|
||||
if( play_frame >= endFrame )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
frames_for_loop = static_cast<f_cnt_t>(
|
||||
( m_endFrame - play_frame ) /
|
||||
freq_factor );
|
||||
|
||||
frames_for_loop = static_cast<f_cnt_t>( ( endFrame - play_frame ) / freq_factor );
|
||||
|
||||
if( frames_for_loop == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sampleFrame * tmp = NULL;
|
||||
|
||||
@@ -673,10 +692,10 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
|
||||
SRC_DATA src_data;
|
||||
// Generate output
|
||||
const f_cnt_t margin = 64;
|
||||
f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor )
|
||||
+ margin;
|
||||
src_data.data_in = getSampleFragment( play_frame,
|
||||
fragment_size, _looped, &tmp )[0];
|
||||
f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) + margin;
|
||||
src_data.data_in =
|
||||
getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards,
|
||||
loopStartFrame, loopEndFrame, endFrame )[0];
|
||||
src_data.data_out = _ab[0];
|
||||
src_data.input_frames = fragment_size;
|
||||
src_data.output_frames = _frames;
|
||||
@@ -695,10 +714,32 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
|
||||
src_data.output_frames_gen, _frames );
|
||||
}
|
||||
// Advance
|
||||
play_frame += src_data.input_frames_used;
|
||||
if( _looped )
|
||||
switch( _loopmode )
|
||||
{
|
||||
play_frame = getLoopedIndex( play_frame );
|
||||
case LoopOff:
|
||||
play_frame += src_data.input_frames_used;
|
||||
break;
|
||||
case LoopOn:
|
||||
play_frame += src_data.input_frames_used;
|
||||
play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame );
|
||||
break;
|
||||
case LoopPingPong:
|
||||
{
|
||||
f_cnt_t left = src_data.input_frames_used;
|
||||
if( _state->isBackwards() )
|
||||
{
|
||||
play_frame -= src_data.input_frames_used;
|
||||
if( play_frame < loopStartFrame )
|
||||
{
|
||||
left -= ( loopStartFrame - play_frame );
|
||||
play_frame = loopStartFrame;
|
||||
}
|
||||
else left = 0;
|
||||
}
|
||||
play_frame += left;
|
||||
play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -708,19 +749,43 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
|
||||
|
||||
// Generate output
|
||||
memcpy( _ab,
|
||||
getSampleFragment( play_frame, _frames, _looped, &tmp ),
|
||||
getSampleFragment( play_frame, _frames, _loopmode, &tmp, &is_backwards,
|
||||
loopStartFrame, loopEndFrame, endFrame ),
|
||||
_frames * BYTES_PER_FRAME );
|
||||
// Advance
|
||||
play_frame += _frames;
|
||||
if( _looped )
|
||||
switch( _loopmode )
|
||||
{
|
||||
play_frame = getLoopedIndex( play_frame );
|
||||
case LoopOff:
|
||||
play_frame += _frames;
|
||||
break;
|
||||
case LoopOn:
|
||||
play_frame += _frames;
|
||||
play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame );
|
||||
break;
|
||||
case LoopPingPong:
|
||||
{
|
||||
f_cnt_t left = _frames;
|
||||
if( _state->isBackwards() )
|
||||
{
|
||||
play_frame -= _frames;
|
||||
if( play_frame < loopStartFrame )
|
||||
{
|
||||
left -= ( loopStartFrame - play_frame );
|
||||
play_frame = loopStartFrame;
|
||||
}
|
||||
else left = 0;
|
||||
}
|
||||
play_frame += left;
|
||||
play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] tmp;
|
||||
|
||||
_state->m_frameIndex = play_frame;
|
||||
_state->setBackwards( is_backwards );
|
||||
_state->setFrameIndex( play_frame );
|
||||
|
||||
return true;
|
||||
|
||||
@@ -729,45 +794,108 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state,
|
||||
|
||||
|
||||
|
||||
sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _start,
|
||||
f_cnt_t _frames, bool _looped, sampleFrame * * _tmp ) const
|
||||
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( _looped )
|
||||
|
||||
if( _loopmode == LoopOff )
|
||||
{
|
||||
if( _start + _frames <= m_loopEndFrame )
|
||||
if( _index + _frames <= _end )
|
||||
{
|
||||
return m_data + _start;
|
||||
return m_data + _index;
|
||||
}
|
||||
}
|
||||
else if( _loopmode == LoopOn )
|
||||
{
|
||||
if( _index + _frames <= _loopend )
|
||||
{
|
||||
return m_data + _index;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( _start + _frames <= m_endFrame )
|
||||
{
|
||||
return m_data + _start;
|
||||
}
|
||||
if( ! *_backwards && _index + _frames < _loopend )
|
||||
return m_data + _index;
|
||||
}
|
||||
|
||||
*_tmp = new sampleFrame[_frames];
|
||||
|
||||
if( _looped )
|
||||
if( _loopmode == LoopOff )
|
||||
{
|
||||
f_cnt_t copied = m_loopEndFrame - _start;
|
||||
memcpy( *_tmp, m_data + _start, copied * BYTES_PER_FRAME );
|
||||
f_cnt_t loop_frames = m_loopEndFrame - m_loopStartFrame;
|
||||
while( _frames - copied > 0 )
|
||||
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 loop_frames = _loopend - _loopstart;
|
||||
while( copied < _frames )
|
||||
{
|
||||
f_cnt_t todo = qMin( _frames - copied, loop_frames );
|
||||
memcpy( *_tmp + copied, m_data + m_loopStartFrame,
|
||||
todo * BYTES_PER_FRAME );
|
||||
memcpy( *_tmp + copied, m_data + _loopstart, todo * BYTES_PER_FRAME );
|
||||
copied += todo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
f_cnt_t available = m_endFrame - _start;
|
||||
memcpy( *_tmp, m_data + _start, available * BYTES_PER_FRAME );
|
||||
memset( *_tmp + available, 0, ( _frames - available ) *
|
||||
BYTES_PER_FRAME );
|
||||
f_cnt_t pos = _index;
|
||||
bool backwards = *_backwards;
|
||||
f_cnt_t copied = 0;
|
||||
|
||||
if( backwards )
|
||||
{
|
||||
copied = qMin( _frames, pos - _loopstart );
|
||||
for( int i=0; i < copied; i++ )
|
||||
{
|
||||
//memcpy( *_tmp + i, m_data + pos - i, BYTES_PER_FRAME );
|
||||
(*_tmp)[i][0] = m_data[ pos - i ][0];
|
||||
(*_tmp)[i][1] = m_data[ pos - i ][1];
|
||||
}
|
||||
pos -= copied;
|
||||
if( pos == _loopstart ) backwards = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
copied = qMin( _frames, _loopend - pos );
|
||||
memcpy( *_tmp, m_data + pos, copied * BYTES_PER_FRAME );
|
||||
pos += copied;
|
||||
if( pos == _loopend ) backwards = true;
|
||||
}
|
||||
|
||||
while( copied < _frames )
|
||||
{
|
||||
if( pos >= _loopend ) backwards = true;
|
||||
if( pos <= _loopstart ) backwards = false;
|
||||
pos = qBound( _loopstart, pos, _loopend );
|
||||
/*qDebug( backwards ? "backwards" : "forwards" );
|
||||
qDebug( "pos %d", pos );*/
|
||||
if( backwards )
|
||||
{
|
||||
f_cnt_t todo = qMin( _frames - copied, pos - _loopstart );
|
||||
for ( int i=0; i < todo; i++ )
|
||||
{
|
||||
//memcpy( *_tmp + ( copied + i ), m_data + ( pos - i ), BYTES_PER_FRAME );
|
||||
(*_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 ) backwards = 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 ) backwards = true;
|
||||
}
|
||||
}
|
||||
*_backwards = backwards;
|
||||
}
|
||||
|
||||
return *_tmp;
|
||||
@@ -776,17 +904,30 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _start,
|
||||
|
||||
|
||||
|
||||
f_cnt_t SampleBuffer::getLoopedIndex( f_cnt_t _index ) const
|
||||
f_cnt_t SampleBuffer::getLoopedIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const
|
||||
{
|
||||
if( _index < m_loopEndFrame )
|
||||
if( _index < _endf )
|
||||
{
|
||||
return _index;
|
||||
}
|
||||
return m_loopStartFrame + ( _index - m_loopStartFrame )
|
||||
% ( m_loopEndFrame - m_loopStartFrame );
|
||||
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,
|
||||
@@ -915,19 +1056,19 @@ QString SampleBuffer::openAndSetWaveformFile()
|
||||
{
|
||||
m_audioFile = configManager::inst()->factorySamplesDir() + "waveforms/10saw.flac";
|
||||
}
|
||||
|
||||
|
||||
QString fileName = this->openAudioFile();
|
||||
|
||||
if(!fileName.isEmpty())
|
||||
{
|
||||
this->setAudioFile( fileName );
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
m_audioFile = "";
|
||||
}
|
||||
|
||||
return fileName;
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
@@ -1328,7 +1469,8 @@ QString SampleBuffer::tryToMakeAbsolute( const QString & _file )
|
||||
|
||||
SampleBuffer::handleState::handleState( bool _varying_pitch ) :
|
||||
m_frameIndex( 0 ),
|
||||
m_varyingPitch( _varying_pitch )
|
||||
m_varyingPitch( _varying_pitch ),
|
||||
m_isBackwards( false )
|
||||
{
|
||||
int error;
|
||||
if( ( m_resamplingData = src_new(/*
|
||||
|
||||