From 8fd89ee673b761b40519331d11143fbb5c5bd9a3 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 6 Nov 2017 11:50:39 +0200 Subject: [PATCH 001/112] SampleTrack: Uncomment disabled recording-related code. --- src/tracks/SampleTrack.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 959d6389e8c..3b7fe60a72a 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -355,9 +355,9 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) contextMenu.addAction( embed::getIconPixmap( "muted" ), tr( "Mute/unmute (<%1> + middle click)" ).arg(UI_CTRL_KEY), m_tco, SLOT( toggleMute() ) ); - /*contextMenu.addAction( embed::getIconPixmap( "record" ), + contextMenu.addAction( embed::getIconPixmap( "record" ), tr( "Set/clear record" ), - m_tco, SLOT( toggleRecord() ) );*/ + m_tco, SLOT( toggleRecord() ) ); constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); @@ -552,7 +552,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) // recording sample tracks is not possible at the moment - /* if( m_tco->isRecord() ) + if( m_tco->isRecord() ) { p.setFont( pointSize<7>( p.font() ) ); @@ -563,7 +563,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.setBrush( QBrush( textColor() ) ); p.drawEllipse( 4, 5, 4, 4 ); - }*/ + } p.end(); From e86fd697f191ad3910571df4a813770f55680a85 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 6 Nov 2017 22:29:10 +0200 Subject: [PATCH 002/112] SampleTrack: Fix TCO not being played on the first tick (if it starts on tick 0) --- src/tracks/SampleTrack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 3b7fe60a72a..e14d3721d7d 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -631,7 +631,7 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, float framesPerTick = Engine::framesPerTick(); if( _start >= sTco->startPosition() && _start < sTco->endPosition() ) { - if( sTco->isPlaying() == false && _start > sTco->startPosition() + sTco->startTimeOffset() ) + if( sTco->isPlaying() == false && _start >= (sTco->startPosition() + sTco->startTimeOffset()) ) { f_cnt_t sampleStart = framesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); f_cnt_t tcoFrameLength = framesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); From 34eb63c9184febea9d767b5daca1fb0a0ce985e8 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 6 Nov 2017 22:41:13 +0200 Subject: [PATCH 003/112] AudioJack: Basic implementation of capture (without AUDIO_PORT_SUPPORT) --- include/AudioJack.h | 5 +++++ src/core/audio/AudioJack.cpp | 39 +++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index c3207c82984..4cae4d06962 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -38,6 +38,8 @@ #include #include +#include + #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" @@ -110,7 +112,10 @@ private slots: MidiJack *m_midiClient; QVector m_outputPorts; + QVector m_inputPorts; jack_default_audio_sample_t * * m_tempOutBufs; + jack_default_audio_sample_t * * m_tempInBufs; + std::unique_ptr m_inBuffer; surroundSampleFrame * m_outBuf; f_cnt_t m_framesDoneInCurBuf; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index bca41356b93..b31d0c16eae 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -56,6 +56,8 @@ AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) : m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) { + m_supportsCapture = true; + _success_ful = initJackClient(); if( _success_ful ) { @@ -188,7 +190,24 @@ bool AudioJack::initJackClient() JackPortIsOutput, 0 ) ); if( m_outputPorts.back() == NULL ) { - printf( "no more JACK-ports available!\n" ); + printf( "no more out JACK-ports available!\n" ); + return false; + } + } + + // Register In ports + for( ch_cnt_t ch = 0; ch < channels(); ++ch ) + { + QString name = QString( "master in " ) + + ( ( ch % 2 ) ? "R" : "L" ) + + QString::number( ch / 2 + 1 ); + m_inputPorts.push_back( jack_port_register( m_client, + name.toLatin1().constData(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0 ) ); + if( m_inputPorts.back() == NULL ) + { + printf( "no more in JACK-ports available!\n" ); return false; } } @@ -354,6 +373,10 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) m_tempOutBufs[c] = (jack_default_audio_sample_t *) jack_port_get_buffer( m_outputPorts[c], _nframes ); + m_tempInBufs[c] = + (jack_default_audio_sample_t *) jack_port_get_buffer( + m_inputPorts[c], _nframes ); + } #ifdef AUDIO_PORT_SUPPORT @@ -409,6 +432,20 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } } + m_inBuffer.reset (new sampleFrame[_nframes]); + + const float gain = mixer()->masterGain(); + for( int c = 0; c < channels(); ++c ) { + jack_default_audio_sample_t *channel_buffer = m_tempInBufs[c]; + + for( jack_nframes_t frame = 0; frame < _nframes; ++frame ) + { + m_inBuffer[frame][c] = channel_buffer[frame] / gain; + } + } + + mixer()->pushInputFrames (m_inBuffer.get (), _nframes); + if( _nframes != done ) { for( int c = 0; c < channels(); ++c ) From 1720fcda495b81ee5d174c1f846359f497cb4660 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 6 Nov 2017 22:42:40 +0200 Subject: [PATCH 004/112] Mixer & PlayHandle: Support PlayHandle without audioPort and set audioPort as nullptr by default. --- src/core/Mixer.cpp | 12 ++++++++---- src/core/PlayHandle.cpp | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 49cda8c34e2..408cee678e5 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -451,7 +451,8 @@ const surroundSampleFrame * Mixer::renderNextBuffer() } if( ( *it )->isFinished() ) { - ( *it )->audioPort()->removePlayHandle( ( *it ) ); + if (( *it )->audioPort()) + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); @@ -679,7 +680,8 @@ bool Mixer::addPlayHandle( PlayHandle* handle ) if( criticalXRuns() == false ) { m_newPlayHandles.push( handle ); - handle->audioPort()->addPlayHandle( handle ); + if (handle->audioPort()) + handle->audioPort()->addPlayHandle( handle ); return true; } @@ -701,7 +703,8 @@ void Mixer::removePlayHandle( PlayHandle * _ph ) if( _ph->affinityMatters() && _ph->affinity() == QThread::currentThread() ) { - _ph->audioPort()->removePlayHandle( _ph ); + if (_ph->audioPort()) + _ph->audioPort()->removePlayHandle( _ph ); bool removedFromList = false; // Check m_newPlayHandles first because doing it the other way around // creates a race condition @@ -761,7 +764,8 @@ void Mixer::removePlayHandlesOfTypes( Track * _track, const quint8 types ) { if( ( *it )->isFromTrack( _track ) && ( ( *it )->type() & types ) ) { - ( *it )->audioPort()->removePlayHandle( ( *it ) ); + if (( *it )->audioPort()) + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); diff --git a/src/core/PlayHandle.cpp b/src/core/PlayHandle.cpp index 9e92019a6b6..f62e25e6bb4 100644 --- a/src/core/PlayHandle.cpp +++ b/src/core/PlayHandle.cpp @@ -38,7 +38,8 @@ PlayHandle::PlayHandle(const Type type, f_cnt_t offset) : m_affinity(QThread::currentThread()), m_playHandleBuffer(BufferManager::acquire()), m_bufferReleased(true), - m_usesBuffer(true) + m_usesBuffer(true), + m_audioPort{nullptr} { } From 7074ac8124445a87e47d3cfca8c34f5d4027cceb Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Tue, 7 Nov 2017 21:16:28 +0200 Subject: [PATCH 005/112] Mixer -> pushInputFrames: Add an option to automaticly normalize input frames by the masterGain. --- include/Mixer.h | 5 ++++- src/core/Mixer.cpp | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/include/Mixer.h b/include/Mixer.h index e91cd15c25b..90d0e3ec64b 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -290,7 +290,7 @@ class LMMS_EXPORT Mixer : public QObject return m_fifoWriter != NULL; } - void pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ); + void pushInputFrames(const sampleFrame *_ab, const f_cnt_t _frames , bool shouldApplyMasterGain=false); inline const sampleFrame * inputBuffer() { @@ -312,6 +312,9 @@ class LMMS_EXPORT Mixer : public QObject inline bool isMetronomeActive() const { return m_metronomeActive; } inline void setMetronomeActive(bool value = true) { m_metronomeActive = value; } + void applyMasterGainToInputBuffer (sampleFrame *frames_data, + const size_t frames_count, uint channels_count, float gain); + void requestChangeInModel(); void doneChangeInModel(); diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 408cee678e5..0ffb91c4e5f 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -309,8 +309,7 @@ bool Mixer::criticalXRuns() const -void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) -{ +void Mixer::pushInputFrames(const sampleFrame * _ab, const f_cnt_t _frames, bool shouldApplyMasterGain) { requestChangeInModel(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; @@ -331,6 +330,12 @@ void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) } memcpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); + + if (!shouldApplyMasterGain) { + applyMasterGainToInputBuffer (&buf[ frames ], _frames, DEFAULT_CHANNELS, + masterGain ()); + } + m_inputBufferFrames[ m_inputBufferWrite ] += _frames; doneChangeInModel(); @@ -575,6 +580,16 @@ void Mixer::changeQuality( const struct qualitySettings & _qs ) startProcessing(); } +void Mixer::applyMasterGainToInputBuffer(sampleFrame *frames_data, const size_t frames_count, uint channels_count, + float gain) { + for (fpp_t f = 0; f < frames_count; ++f) { + for (uint channel = 0; channel < channels_count; ++channel) { + frames_data[f][channel] /= gain; + } + + } +} + From 86eb13f06d2a22f4753555a97e61e2cdfc3ad62b Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 8 Nov 2017 09:54:18 +0200 Subject: [PATCH 006/112] AudioPulseAudio: Switch playback sample type to float and add capture support. --- include/AudioPulseAudio.h | 6 +- src/core/audio/AudioPulseAudio.cpp | 99 +++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 19 deletions(-) diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index 49674669134..4b15d1bb282 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -72,12 +72,16 @@ class AudioPulseAudio : public QThread, public AudioDevice void streamWriteCallback( pa_stream * s, size_t length ); + void streamReadCallback(pa_stream *s, size_t length); void signalConnected( bool connected ); pa_stream * m_s; pa_sample_spec m_sampleSpec; + pa_stream * m_recordStream; + pa_sample_spec m_recordSampleSpec; + private: virtual void startProcessing(); @@ -87,8 +91,6 @@ class AudioPulseAudio : public QThread, public AudioDevice volatile bool m_quit; - bool m_convertEndian; - bool m_connected; QSemaphore m_connectedSemaphore; diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 20a106ed116..ada82d12c67 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -42,7 +42,10 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) } - +static void stream_read_callback(pa_stream *s, size_t length, void *userdata) +{ + static_cast( userdata )->streamReadCallback( s, length ); +} AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( @@ -50,15 +53,19 @@ AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer* _mixer ) : DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_s( NULL ), - m_quit( false ), - m_convertEndian( false ) + m_recordStream{nullptr}, + m_quit( false ) { _success_ful = false; - m_sampleSpec.format = PA_SAMPLE_S16LE; + m_sampleSpec.format = PA_SAMPLE_FLOAT32; m_sampleSpec.rate = sampleRate(); m_sampleSpec.channels = channels(); + m_recordSampleSpec = m_sampleSpec; + + m_supportsCapture = true; + _success_ful = true; } @@ -145,6 +152,27 @@ static void stream_state_callback( pa_stream *s, void * userdata ) } } +/* This routine is called whenever the stream state changes */ +static void record_stream_state_callback( pa_stream *s, void * userdata ) +{ + switch( pa_stream_get_state( s ) ) + { + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: + qDebug( "Record stream successfully created\n" ); + break; + + case PA_STREAM_FAILED: + default: + qCritical( "record stream errror: %s\n", + pa_strerror(pa_context_errno( + pa_stream_get_context( s ) ) ) ); + } +} + /* This is called whenever the context status changes */ @@ -165,6 +193,10 @@ static void context_state_callback(pa_context *c, void *userdata) pa_stream_set_state_callback( _this->m_s, stream_state_callback, _this ); pa_stream_set_write_callback( _this->m_s, stream_write_callback, _this ); + _this->m_recordStream = pa_stream_new( c, "lmms sample track record", &_this->m_sampleSpec, NULL); + pa_stream_set_state_callback( _this->m_recordStream, record_stream_state_callback, _this ); + pa_stream_set_read_callback ( _this->m_recordStream, stream_read_callback , _this ); + pa_buffer_attr buffer_attr; buffer_attr.maxlength = (uint32_t)(-1); @@ -182,10 +214,17 @@ static void context_state_callback(pa_context *c, void *userdata) buffer_attr.tlength = pa_usec_to_bytes( latency * PA_USEC_PER_MSEC, &_this->m_sampleSpec ); - pa_stream_connect_playback( _this->m_s, NULL, &buffer_attr, + pa_stream_connect_playback( _this->m_s, + NULL, + &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, // volume NULL ); + + pa_stream_connect_record (_this->m_recordStream, + NULL, + &buffer_attr, + PA_STREAM_ADJUST_LATENCY); _this->signalConnected( true ); break; } @@ -242,6 +281,8 @@ void AudioPulseAudio::run() pa_stream_disconnect( m_s ); pa_stream_unref( m_s ); + pa_stream_disconnect( m_recordStream ); + pa_stream_unref( m_recordStream ); } else { @@ -266,10 +307,9 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) { const fpp_t fpp = mixer()->framesPerPeriod(); surroundSampleFrame * temp = new surroundSampleFrame[fpp]; - int_sample_t* pcmbuf = (int_sample_t *)pa_xmalloc( fpp * channels() * sizeof(int_sample_t) ); size_t fd = 0; - while( fd < length/4 && m_quit == false ) + while( fd < length && m_quit == false ) { const fpp_t frames = getNextBuffer( temp ); if( !frames ) @@ -277,24 +317,49 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) m_quit = true; break; } - int bytes = convertToS16( temp, frames, - mixer()->masterGain(), - pcmbuf, - m_convertEndian ); - if( bytes > 0 ) - { - pa_stream_write( m_s, pcmbuf, bytes, NULL, 0, - PA_SEEK_RELATIVE ); + + auto gain = mixer()->masterGain(); + for (fpp_t f = 0; f < frames; ++f) { + temp[f][0] *= gain; + temp[f][1] *= gain; } - fd += frames; + + + + pa_stream_write( m_s, static_cast(temp), + frames * sizeof(surroundSampleFrame), NULL, 0, + PA_SEEK_RELATIVE ); + + fd += (frames * sizeof(surroundSampleFrame)); } - pa_xfree( pcmbuf ); delete[] temp; } +void AudioPulseAudio::streamReadCallback(pa_stream *s, size_t length) { + const sampleFrame *buffer = nullptr; + size_t buffer_size = 0; + + while (length > 0) { + + pa_stream_peek (m_recordStream, + reinterpret_cast (const_cast(&buffer)), + &buffer_size); + + fpp_t frames = buffer_size / sizeof (sampleFrame); + + pa_stream_drop (m_recordStream); + if (buffer_size && buffer) { + mixer()->pushInputFrames (buffer, + frames, + true); + } + + length -= buffer_size; + } +} void AudioPulseAudio::signalConnected( bool connected ) { From 45a484ae3706cb3fba0d57cd3cd84bc14ce23884 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 8 Nov 2017 11:15:52 +0200 Subject: [PATCH 007/112] Track -> SampleTrack: Do not save start time offset for empty SampleTCOs. --- include/SampleTrack.h | 5 +++++ src/core/Track.cpp | 4 +++- src/tracks/SampleTrack.cpp | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index ccb5a020e85..b11c1eb35b9 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -73,6 +73,11 @@ class SampleTCO : public TrackContentObject bool isPlaying() const; void setIsPlaying(bool isPlaying); + /** + * @brief isEmpty Check if this TCO has not content. + */ + bool isEmpty() const; + public slots: void setSampleBuffer( SampleBuffer* sb ); void setSampleFile( const QString & _sf ); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 64c17c9e8ec..a57e26646bf 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -982,7 +982,9 @@ void TrackContentObjectView::mouseMoveEvent( QMouseEvent * me ) m_tco->movePosition( t ); m_trackView->getTrackContentWidget()->changePosition(); m_tco->changeLength( m_tco->length() + ( oldPos - t ) ); - sTco->setStartTimeOffset( sTco->startTimeOffset() + ( oldPos - t ) ); + + if (! sTco->isEmpty ()) + sTco->setStartTimeOffset( sTco->startTimeOffset() + ( oldPos - t ) ); } } } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index e14d3721d7d..b7bb6fd25c0 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -215,6 +215,11 @@ void SampleTCO::setIsPlaying(bool isPlaying) m_isPlaying = isPlaying; } +bool SampleTCO::isEmpty() const +{ + return (sampleLength () == 0); +} + From c4b69a37c7c83c11fcc0e1670bb3af6af018c20f Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 8 Nov 2017 11:58:34 +0200 Subject: [PATCH 008/112] AudioJack: Allocate and deallocate m_tempInBufs. --- src/core/audio/AudioJack.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index b31d0c16eae..d8a4f7e78c0 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -52,6 +52,7 @@ AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) : m_active( false ), m_midiClient( NULL ), m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ), + m_tempInBufs( new jack_default_audio_sample_t *[channels()] ), m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) @@ -90,6 +91,7 @@ AudioJack::~AudioJack() jack_client_close( m_client ); } + delete[] m_tempInBufs; delete[] m_tempOutBufs; delete[] m_outBuf; From 0a9debcaee0d9fa0ce0a13f69450c5008826c691 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 8 Nov 2017 21:56:59 +0200 Subject: [PATCH 009/112] SampleTrack: Make sure an empty TCO with isRecord would still be "played" (recorded). --- src/tracks/SampleTrack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index b7bb6fd25c0..ec2bb4bdcb0 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -645,7 +645,8 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, //else we play the sample to the end but nothing more f_cnt_t samplePlayLength = tcoFrameLength > sampleBufferLength ? sampleBufferLength : tcoFrameLength; //we only play within the sampleBuffer limits - if( sampleStart < sampleBufferLength ) + // anyway, "play" (record) this TCO if is recording. + if( sampleStart < sampleBufferLength || sTco->isRecord ()) { sTco->setSampleStartFrame( sampleStart ); sTco->setSamplePlayLength( samplePlayLength ); From f72c5d94d36a5f2c93ee065f5e77b2e80c61dc11 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 8 Nov 2017 21:59:48 +0200 Subject: [PATCH 010/112] SampleTrack: Reset the start time offset when importing a new SampleBuffer. That solves problem with recording that caused samples to be moved and messed up. Thanks @-BaraMGB for helping. --- src/tracks/SampleTrack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index ec2bb4bdcb0..91d4e7a7bf6 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -138,6 +138,7 @@ const QString & SampleTCO::sampleFile() const void SampleTCO::setSampleBuffer( SampleBuffer* sb ) { sharedObject::unref( m_sampleBuffer ); + setStartTimeOffset( 0 ); m_sampleBuffer = sb; updateLength(); From 5241b4bd8800207f6f3be7649c72c8402ec193ed Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 9 Nov 2017 09:08:56 +0200 Subject: [PATCH 011/112] SampleRecordHandle: Apply start offset to reocrded TCO. Previously, begining record in any position would result the data being in the end of the TCO. @see https://github.com/LMMS/lmms/pull/3947#issuecomment-342985355 Thanks to @-BaraMGB :) --- src/core/SampleRecordHandle.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index cf9342b9e08..c77d24711a2 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -50,9 +50,15 @@ SampleRecordHandle::~SampleRecordHandle() { if( !m_buffers.empty() ) { + auto sampleStart = m_tco->sampleBuffer ()->startFrame (); + SampleBuffer* sb; createSampleBuffer( &sb ); m_tco->setSampleBuffer( sb ); + + // Apply the sample buffer offset from the start of the TCO. + MidiTime startTimeOffset = (tick_t)( sampleStart / Engine::framesPerTick()); + m_tco->setStartTimeOffset (startTimeOffset); } while( !m_buffers.empty() ) From 55029bdaca2a46c1b09b09e068064910c7926f8a Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 11 Nov 2017 20:33:54 +0200 Subject: [PATCH 012/112] Track, TrackView: Instead of trying every possible `dynamic_cast` in `updateMenu`, use a virtual function overriden by every Track implemetation. --- include/AutomationTrack.h | 6 +++ include/InstrumentTrack.h | 2 + include/Track.h | 7 +-- src/core/Track.cpp | 86 ++++++++++++---------------------- src/tracks/AutomationTrack.cpp | 31 ++++++++++++ src/tracks/InstrumentTrack.cpp | 12 +++++ 6 files changed, 84 insertions(+), 60 deletions(-) diff --git a/include/AutomationTrack.h b/include/AutomationTrack.h index 195c21e9d4b..89f0fa5deef 100644 --- a/include/AutomationTrack.h +++ b/include/AutomationTrack.h @@ -61,6 +61,7 @@ class AutomationTrack : public Track class AutomationTrackView : public TrackView { + Q_OBJECT public: AutomationTrackView( AutomationTrack* at, TrackContainerView* tcv ); virtual ~AutomationTrackView() = default; @@ -68,6 +69,11 @@ class AutomationTrackView : public TrackView virtual void dragEnterEvent( QDragEnterEvent * _dee ); virtual void dropEvent( QDropEvent * _de ); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations) override; + +public slots: + void recordingOn(); + void recordingOff(); } ; diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index c487438d0f8..27109e07607 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -319,6 +319,8 @@ class InstrumentTrackView : public TrackView QMenu * createFxMenu( QString title, QString newFxLabel ); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations) override; + protected: virtual void dragEnterEvent( QDragEnterEvent * _dee ); virtual void dropEvent( QDropEvent * _de ); diff --git a/include/Track.h b/include/Track.h index 1267d2ef742..8d6d235baeb 100644 --- a/include/Track.h +++ b/include/Track.h @@ -427,6 +427,8 @@ class TrackOperationsWidget : public QWidget ~TrackOperationsWidget(); + QPushButton *trackOps() const; + protected: virtual void mousePressEvent( QMouseEvent * me ); virtual void paintEvent( QPaintEvent * pe ); @@ -436,9 +438,6 @@ private slots: void cloneTrack(); void removeTrack(); void updateMenu(); - void toggleRecording(bool on); - void recordingOn(); - void recordingOff(); void clearTrack(); private: @@ -679,6 +678,8 @@ class TrackView : public QWidget, public ModelView, public JournallingObject // Currently instrument track and sample track supports it virtual QMenu * createFxMenu(QString title, QString newFxLabel); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations); + public slots: virtual bool close(); diff --git a/src/core/Track.cpp b/src/core/Track.cpp index a57e26646bf..567ade20038 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -1886,6 +1886,11 @@ void TrackOperationsWidget::clearTrack() t->unlock(); } +QPushButton *TrackOperationsWidget::trackOps() const +{ + return m_trackOps; +} + /*! \brief Remove this track from the track list @@ -1909,65 +1914,9 @@ void TrackOperationsWidget::removeTrack() */ void TrackOperationsWidget::updateMenu() { - QMenu * toMenu = m_trackOps->menu(); - toMenu->clear(); - toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), - tr( "Clone this track" ), - this, SLOT( cloneTrack() ) ); - toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), - tr( "Remove this track" ), - this, SLOT( removeTrack() ) ); - - if( ! m_trackView->trackContainerView()->fixedTCOs() ) - { - toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) ); - } - if (QMenu *fxMenu = m_trackView->createFxMenu(tr("FX %1: %2"), tr("Assign to new FX Channel"))) - { - toMenu->addMenu(fxMenu); - } - - if (InstrumentTrackView * trackView = dynamic_cast(m_trackView)) - { - toMenu->addSeparator(); - toMenu->addMenu(trackView->midiMenu()); - } - if( dynamic_cast( m_trackView ) ) - { - toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) ); - toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) ); - } -} - - -void TrackOperationsWidget::toggleRecording( bool on ) -{ - AutomationTrackView * atv = dynamic_cast( m_trackView ); - if( atv ) - { - for( TrackContentObject * tco : atv->getTrack()->getTCOs() ) - { - AutomationPattern * ap = dynamic_cast( tco ); - if( ap ) { ap->setRecording( on ); } - } - atv->update(); - } + return m_trackView->updateTrackOperationsWidgetMenu (this); } - - -void TrackOperationsWidget::recordingOn() -{ - toggleRecording( true ); -} - - -void TrackOperationsWidget::recordingOff() -{ - toggleRecording( false ); -} - - // =========================================================================== // track // =========================================================================== @@ -2674,6 +2623,29 @@ void TrackView::update() QWidget::update(); } +void TrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) +{ + QMenu * toMenu = trackOperations->m_trackOps->menu(); + + toMenu->clear(); + toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), + tr( "Clone this track" ), + trackOperations, SLOT( cloneTrack() ) ); + toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), + tr( "Remove this track" ), + trackOperations, SLOT( removeTrack() ) ); + + if( ! trackContainerView()->fixedTCOs() ) + { + toMenu->addAction( tr( "Clear this track" ), trackOperations, SLOT( clearTrack() ) ); + } + + if (QMenu *fxMenu = createFxMenu(tr("FX %1: %2"), tr("Assign to new FX Channel"))) + { + toMenu->addMenu(fxMenu); + } +} + diff --git a/src/tracks/AutomationTrack.cpp b/src/tracks/AutomationTrack.cpp index 11c919f0e42..2ea170bb89f 100644 --- a/src/tracks/AutomationTrack.cpp +++ b/src/tracks/AutomationTrack.cpp @@ -33,6 +33,7 @@ #include "TrackContainerView.h" #include "TrackLabelButton.h" +#include AutomationTrack::AutomationTrack( TrackContainer* tc, bool _hidden ) : Track( _hidden ? HiddenAutomationTrack : Track::AutomationTrack, tc ) @@ -140,4 +141,34 @@ void AutomationTrackView::dropEvent( QDropEvent * _de ) update(); } +void AutomationTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) { + TrackView::updateTrackOperationsWidgetMenu (trackOperations); + + auto toMenu = trackOperations->trackOps ()->menu (); + + toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) ); + toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) ); +} + +void AutomationTrackView::recordingOn() { + const Track::tcoVector & tcov = getTrack()->getTCOs(); + for( Track::tcoVector::const_iterator it = tcov.begin(); it != tcov.end(); ++it ) + { + AutomationPattern * ap = dynamic_cast( *it ); + if( ap ) { ap->setRecording( true ); } + } + update(); +} + + +void AutomationTrackView::recordingOff() { + const Track::tcoVector & tcov = getTrack()->getTCOs(); + for( Track::tcoVector::const_iterator it = tcov.begin(); it != tcov.end(); ++it ) + { + AutomationPattern * ap = dynamic_cast( *it ); + if( ap ) { ap->setRecording( false ); } + } + update(); +} + diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 6c95f3c9a23..977700bd27b 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1260,6 +1260,18 @@ QMenu * InstrumentTrackView::createFxMenu(QString title, QString newFxLabel) return fxMenu; } +void InstrumentTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) +{ + TrackView::updateTrackOperationsWidgetMenu (trackOperations); + + auto toMenu = trackOperations->trackOps ()->menu (); + QMenu *fxMenu = createFxMenu( tr( "FX %1: %2" ), tr( "Assign to new FX Channel" )); + toMenu->addMenu(fxMenu); + + toMenu->addSeparator(); + toMenu->addMenu( midiMenu() ); +} + From 522986894d6311dbea9f519efeea8509474efad1 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 30 Jul 2018 13:05:47 +0200 Subject: [PATCH 013/112] SampleTrack Recording: Add per SampleTrack and a global fallback for the input channel configurations. And support recording of one channel and converting it to two. The user would be able to set their input config per track, or globally. For each recorded sampletrack, the recorded will get the channel config and will duplicate the samples from one channel to the other in a case of mono input. --- include/SampleRecordHandle.h | 16 ++++++++ include/SampleTrack.h | 15 ++++++++ include/SongEditor.h | 7 ++++ include/lmms_basics.h | 3 ++ src/core/SampleRecordHandle.cpp | 66 +++++++++++++++++++++++++++++---- src/gui/editors/SongEditor.cpp | 39 +++++++++++++++++++ src/tracks/SampleTrack.cpp | 55 ++++++++++++++++++++++++++- 7 files changed, 192 insertions(+), 9 deletions(-) diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 22d9bf3156c..316e1911e89 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -31,6 +31,8 @@ #include "MidiTime.h" #include "PlayHandle.h" +#include "SampleTrack.h" + class BBTrack; class SampleBuffer; @@ -54,6 +56,17 @@ class SampleRecordHandle : public PlayHandle private: + void copyBufferFromMonoLeft( const sampleFrame * inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames); + void copyBufferFromMonoRight( const sampleFrame * inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames); + void copyBufferFromStereo( const sampleFrame * inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames); + + virtual void writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ); @@ -66,6 +79,9 @@ class SampleRecordHandle : public PlayHandle BBTrack * m_bbTrack; SampleTCO * m_tco; + // The recording type as it was when we started + // recording. + SampleTrack::RecordingChannel m_recordingChannel; } ; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index b11c1eb35b9..3b9f25cf625 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -138,6 +138,13 @@ class SampleTrack : public Track { Q_OBJECT public: + enum RecordingChannel : int { + None, + MonoRight, + MonoLeft, + Stereo, + }; + SampleTrack( TrackContainer* tc ); virtual ~SampleTrack(); @@ -166,12 +173,17 @@ class SampleTrack : public Track return "sampletrack"; } + RecordingChannel recordingChannel() const; + void setRecordingChannel(const RecordingChannel &recordingChannel); + public slots: void updateTcos(); void setPlayingTcos( bool isPlaying ); void updateEffectChannel(); private: + RecordingChannel m_recordingChannel = RecordingChannel::None; + FloatModel m_volumeModel; FloatModel m_panningModel; IntModel m_effectChannelModel; @@ -212,6 +224,8 @@ class SampleTrackView : public TrackView virtual QMenu * createFxMenu( QString title, QString newFxLabel ); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations) override; + public slots: void showEffects(); @@ -225,6 +239,7 @@ public slots: private slots: + void onRecordActionSelected (QAction *action); void assignFxLine( int channelIndex ); void createFxLine(); diff --git a/include/SongEditor.h b/include/SongEditor.h index f9b6aad1b56..cd460cde98d 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -32,6 +32,7 @@ #include "ActionGroup.h" #include "Editor.h" #include "TrackContainerView.h" +#include "SampleTrack.h" class QLabel; class QScrollBar; @@ -157,6 +158,8 @@ class SongEditorWindow : public Editor SongEditor* m_editor; + SampleTrack::RecordingChannel globalRecordChannel() const; + protected: virtual void resizeEvent( QResizeEvent * event ); virtual void changeEvent( QEvent * ); @@ -170,6 +173,8 @@ protected slots: void lostFocus(); void adjustUiAfterProjectLoad(); + void onRecordChannelSelected(QAction *action); + signals: void playTriggered(); void resized(); @@ -188,6 +193,8 @@ protected slots: QAction* m_crtlAction; ComboBox * m_zoomingComboBox; + + SampleTrack::RecordingChannel m_globalRecordChannel = SampleTrack::RecordingChannel::Stereo; }; #endif diff --git a/include/lmms_basics.h b/include/lmms_basics.h index cca04e97d8f..16d9fd22658 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -121,6 +121,9 @@ const ch_cnt_t SURROUND_CHANNELS = 2; #endif +// "In audio 1 is always the left" - umcaruje, 2017 +const ch_cnt_t RIGHT_CHANNEL_INDEX = 1; +const ch_cnt_t LEFT_CHANNEL_INDEX = 0; #ifdef LMMS_BUILD_WIN32 #define LADSPA_PATH_SEPERATOR ';' diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index c77d24711a2..9714a0524af 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -29,7 +29,6 @@ #include "InstrumentTrack.h" #include "Mixer.h" #include "SampleBuffer.h" -#include "SampleTrack.h" #include "debug.h" @@ -39,7 +38,8 @@ SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) : m_minLength( tco->length() ), m_track( tco->getTrack() ), m_bbTrack( NULL ), - m_tco( tco ) + m_tco( tco ), + m_recordingChannel{dynamic_cast(m_track)->recordingChannel ()} { } @@ -139,20 +139,72 @@ void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf ) delete[] data; } +void SampleRecordHandle::copyBufferFromMonoLeft(const sampleFrame *inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames) +{ + for( f_cnt_t frame = 0; frame < _frames; ++frame ) { + // Copy every first sample to the first and the second in the output buffer. + outputBuffer[frame][LEFT_CHANNEL_INDEX] = inputBuffer[frame][LEFT_CHANNEL_INDEX]; + outputBuffer[frame][RIGHT_CHANNEL_INDEX] = inputBuffer[frame][LEFT_CHANNEL_INDEX]; + } +} +void SampleRecordHandle::copyBufferFromMonoRight(const sampleFrame *inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames) +{ + for( f_cnt_t frame = 0; frame < _frames; ++frame ) { + // Copy every second sample to the first and the second in the output buffer. + outputBuffer[frame][LEFT_CHANNEL_INDEX] = inputBuffer[frame][RIGHT_CHANNEL_INDEX]; + outputBuffer[frame][RIGHT_CHANNEL_INDEX] = inputBuffer[frame][RIGHT_CHANNEL_INDEX]; + } +} - -void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, - const f_cnt_t _frames ) +void SampleRecordHandle::copyBufferFromStereo(const sampleFrame *inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames) { - sampleFrame * buf = new sampleFrame[_frames]; for( f_cnt_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) { - buf[frame][chnl] = _ab[frame][chnl]; + outputBuffer[frame][chnl] = inputBuffer[frame][chnl]; } } +} + + + + +void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, + const f_cnt_t _frames ) +{ + sampleFrame * buf = new sampleFrame[_frames]; + + // Depending on the recording channel, copy the buffer as a + // mono-right, mono-left or stereo. + + // Note that mono doesn't mean single channel, it means + // empty other channel. Therefore, we would just duplicate + // every frame from the mono channel + // to the empty channel. + + switch(m_recordingChannel) { + case SampleTrack::RecordingChannel::MonoLeft: + copyBufferFromMonoLeft(_ab, buf, _frames); + break; + case SampleTrack::RecordingChannel::MonoRight: + copyBufferFromMonoRight(_ab, buf, _frames); + break; + case SampleTrack::RecordingChannel::Stereo: + copyBufferFromStereo(_ab, buf, _frames); + break; + default: + Q_ASSERT(false); + break; + } + m_buffers.push_back( qMakePair( buf, _frames ) ); } diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index b397434b102..4c0c28d189c 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "AutomatableSlider.h" #include "ComboBox.h" @@ -673,6 +674,32 @@ SongEditorWindow::SongEditorWindow(Song* song) : m_recordAccompanyAction->setToolTip(tr( "Record samples from Audio-device while playing song or BB track")); m_stopAction->setToolTip(tr( "Stop song (Space)" )); + auto *recordMenu = new QMenu(this); + auto *recordGroup = new QActionGroup(this); + recordGroup->addAction (new QAction(tr("Stereo"), recordGroup))->setData (SampleTrack::RecordingChannel::Stereo); + recordGroup->addAction (new QAction(tr("Mono left"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoLeft); + recordGroup->addAction (new QAction(tr("Mono right"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoRight); + + recordGroup->setExclusive (true); + + for (auto *action : recordGroup->actions ()) { + action->setCheckable (true); + } + + // Check stereo by default. + recordGroup->actions ().first ()->setChecked (true); + + connect (recordGroup, SIGNAL(triggered(QAction*)), SLOT(onRecordChannelSelected(QAction*))); + + recordMenu->addActions (recordGroup->actions ()); + + QToolButton * recordTool = new QToolButton(this); + recordTool->setMenu(recordMenu); + recordTool->setToolTip (tr ("Select default channels to record.")); + recordTool->setPopupMode( QToolButton::InstantPopup ); + recordTool->setFixedWidth (17); + + m_toolBar->addWidget (recordTool); // Track actions DropToolBar *trackActionsToolBar = addDropToolBarToTop(tr("Track actions")); @@ -818,6 +845,13 @@ void SongEditorWindow::adjustUiAfterProjectLoad() m_editor->scrolled(0); } +void SongEditorWindow::onRecordChannelSelected(QAction *action) { + action->setChecked (true); + + // Set the recording channel according to the action's data. + m_globalRecordChannel = static_cast(action->data ().value()); +} + @@ -845,3 +879,8 @@ void SongEditorWindow::keyReleaseEvent( QKeyEvent *ke ) } } } + +SampleTrack::RecordingChannel SongEditorWindow::globalRecordChannel() const +{ + return m_globalRecordChannel; +} diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 91d4e7a7bf6..ce44f93ddce 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -362,7 +362,7 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) tr( "Mute/unmute (<%1> + middle click)" ).arg(UI_CTRL_KEY), m_tco, SLOT( toggleMute() ) ); contextMenu.addAction( embed::getIconPixmap( "record" ), - tr( "Set/clear record" ), + tr( "Set/clear record (Shift + Ctrl + left click)" ), m_tco, SLOT( toggleRecord() ) ); constructContextMenu( &contextMenu ); @@ -772,8 +772,20 @@ void SampleTrack::setPlayingTcos( bool isPlaying ) } } +SampleTrack::RecordingChannel SampleTrack::recordingChannel() const{ + // If we had defined a recording channel for this track, use + // it. Otherwise, use the global setting. + if (m_recordingChannel != RecordingChannel::None) { + return m_recordingChannel; + } else { + return gui->songEditor ()->globalRecordChannel (); + } +} - +void SampleTrack::setRecordingChannel(const RecordingChannel &recordingChannel) +{ + m_recordingChannel = recordingChannel; +} void SampleTrack::updateEffectChannel() { @@ -877,6 +889,38 @@ QMenu * SampleTrackView::createFxMenu(QString title, QString newFxLabel) return fxMenu; } +void SampleTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) { + TrackView::updateTrackOperationsWidgetMenu (trackOperations); + + SampleTrack * st = castModel(); + auto toMenu = trackOperations->trackOps ()->menu (); + + QMenu *recordMenu = toMenu->addMenu (tr ("Set record type")); + auto *recordChannels = new QActionGroup(recordMenu); + + recordChannels->setExclusive (true); + + recordChannels->addAction(tr( "default" ))->setData (SampleTrack::RecordingChannel::None); + recordChannels->addAction(tr( "Stereo" ))->setData (SampleTrack::RecordingChannel::Stereo); + recordChannels->addAction(tr( "Mono left" ))->setData (SampleTrack::RecordingChannel::MonoLeft); + recordChannels->addAction(tr( "Mono right" ))->setData (SampleTrack::RecordingChannel::MonoRight); + + for (auto *action : recordChannels->actions ()) { + action->setCheckable (true); + + if (static_cast(action->data ().value()) + == st->m_recordingChannel) + { + action->setChecked (true); + } + + } + + recordMenu->addActions (recordChannels->actions ()); + + connect (recordChannels, SIGNAL(triggered(QAction*)), SLOT(onRecordActionSelected(QAction*))); +} + @@ -1148,3 +1192,10 @@ void SampleTrackWindow::loadSettings(const QDomElement& element) } } + +void SampleTrackView::onRecordActionSelected(QAction *action) { + SampleTrack * st = castModel(); + st->setRecordingChannel (static_cast(action->data ().value())); + + action->setChecked (true); +} From e290203b07fba15a1befe4d52f88e0809853a99d Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 11 Nov 2017 20:40:26 +0200 Subject: [PATCH 014/112] Editor -> Style: Reorder editor buttons. --- src/gui/editors/Editor.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index c27eda4c06c..4da639e3c7a 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -25,6 +25,8 @@ #include "Editor.h" #include "Song.h" +#include "ComboBox.h" +#include "ToolButton.h" #include "MainWindow.h" #include "embed.h" @@ -107,10 +109,15 @@ Editor::Editor(bool record, bool stepRecord) : // Add actions to toolbar addButton(m_playAction, "playButton"); + addButton(m_stopAction, "stopButton"); + + // Seperate playback buttons and recording buttons. + m_toolBar->addSeparator (); + if (record) { - addButton(m_recordAction, "recordButton"); addButton(m_recordAccompanyAction, "recordAccompanyButton"); + addButton(m_recordAction, "m_recordAction"); } if(stepRecord) { From 48fea61a77764c502d820a4e8bc1f4addd79e188 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 12 Nov 2017 10:06:58 +0200 Subject: [PATCH 015/112] SampleTrack -> RecordingChannel: Remove the default option, instead, all options unchecked will be considered as global default. Suggested by @-tresf, Thanks! --- src/tracks/SampleTrack.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index ce44f93ddce..85c0c445ec3 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -900,7 +900,6 @@ void SampleTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *tra recordChannels->setExclusive (true); - recordChannels->addAction(tr( "default" ))->setData (SampleTrack::RecordingChannel::None); recordChannels->addAction(tr( "Stereo" ))->setData (SampleTrack::RecordingChannel::Stereo); recordChannels->addAction(tr( "Mono left" ))->setData (SampleTrack::RecordingChannel::MonoLeft); recordChannels->addAction(tr( "Mono right" ))->setData (SampleTrack::RecordingChannel::MonoRight); @@ -1195,7 +1194,15 @@ void SampleTrackWindow::loadSettings(const QDomElement& element) void SampleTrackView::onRecordActionSelected(QAction *action) { SampleTrack * st = castModel(); - st->setRecordingChannel (static_cast(action->data ().value())); + auto selectedRecordingChannel = static_cast(action->data ().value()); + + // If we've selected the current recording channel again, we should undo it. + if (selectedRecordingChannel == st->m_recordingChannel) { + st->setRecordingChannel (SampleTrack::RecordingChannel::None); + action->setChecked (false); + } else { + st->setRecordingChannel (selectedRecordingChannel); + action->setChecked (true); + } - action->setChecked (true); } From 6feeb4a7fce4a2ee9b5d111ba73e31959717d29b Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 12 Nov 2017 10:07:44 +0200 Subject: [PATCH 016/112] SampleTrack: Change recordingChannel menu label. --- src/tracks/SampleTrack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 85c0c445ec3..19b5490c684 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -895,7 +895,7 @@ void SampleTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *tra SampleTrack * st = castModel(); auto toMenu = trackOperations->trackOps ()->menu (); - QMenu *recordMenu = toMenu->addMenu (tr ("Set record type")); + QMenu *recordMenu = toMenu->addMenu (tr ("Set record channel")); auto *recordChannels = new QActionGroup(recordMenu); recordChannels->setExclusive (true); From 1bb14b50955912294bfde9ed9bb7a13b68872a29 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 17 Nov 2017 18:16:05 +0200 Subject: [PATCH 017/112] SampleTrack: In a case of recording, play a sample TCO even if its actual data starts after that. --- src/tracks/SampleTrack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 19b5490c684..6ef0007992a 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -637,7 +637,8 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, float framesPerTick = Engine::framesPerTick(); if( _start >= sTco->startPosition() && _start < sTco->endPosition() ) { - if( sTco->isPlaying() == false && _start >= (sTco->startPosition() + sTco->startTimeOffset()) ) + if( sTco->isPlaying() == false && (_start >= (sTco->startPosition() + sTco->startTimeOffset()) + || sTco->isRecord ()) ) { f_cnt_t sampleStart = framesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); f_cnt_t tcoFrameLength = framesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); From cbd669cef0772fec55c5c8689bc6565ef9248d19 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 17 Nov 2017 20:32:08 +0200 Subject: [PATCH 018/112] SampleRecordHandle: Obtain the offset to the record with a parameter instead of hacking sampleBuffer ()->startFrame (). That solves a bug with `startFrame ()` being negetive in recording some cases. --- include/SampleRecordHandle.h | 6 +++++- src/core/SampleRecordHandle.cpp | 11 ++++------- src/tracks/SampleTrack.cpp | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 316e1911e89..cc7ab867511 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -43,7 +43,7 @@ class Track; class SampleRecordHandle : public PlayHandle { public: - SampleRecordHandle( SampleTCO* tco ); + SampleRecordHandle( SampleTCO* tco , MidiTime startRecordTimeOffset); virtual ~SampleRecordHandle(); virtual void play( sampleFrame * _working_buffer ); @@ -82,6 +82,10 @@ class SampleRecordHandle : public PlayHandle // The recording type as it was when we started // recording. SampleTrack::RecordingChannel m_recordingChannel; + + // The offset from the start of m_track that the record has + // started from. + MidiTime m_startRecordTimeOffset; } ; diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 9714a0524af..794f14befc1 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -32,14 +32,15 @@ #include "debug.h" -SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) : +SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTimeOffset) : PlayHandle( TypeSamplePlayHandle ), m_framesRecorded( 0 ), m_minLength( tco->length() ), m_track( tco->getTrack() ), m_bbTrack( NULL ), m_tco( tco ), - m_recordingChannel{dynamic_cast(m_track)->recordingChannel ()} + m_recordingChannel{dynamic_cast(m_track)->recordingChannel ()}, + m_startRecordTimeOffset{startRecordTimeOffset} { } @@ -50,15 +51,11 @@ SampleRecordHandle::~SampleRecordHandle() { if( !m_buffers.empty() ) { - auto sampleStart = m_tco->sampleBuffer ()->startFrame (); - SampleBuffer* sb; createSampleBuffer( &sb ); m_tco->setSampleBuffer( sb ); - // Apply the sample buffer offset from the start of the TCO. - MidiTime startTimeOffset = (tick_t)( sampleStart / Engine::framesPerTick()); - m_tco->setStartTimeOffset (startTimeOffset); + m_tco->setStartTimeOffset (m_startRecordTimeOffset); } while( !m_buffers.empty() ) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 6ef0007992a..70e211801d1 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -676,7 +676,7 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, { return played_a_note; } - SampleRecordHandle* smpHandle = new SampleRecordHandle( st ); + SampleRecordHandle* smpHandle = new SampleRecordHandle( st , _start - st->startPosition ()); handle = smpHandle; } else From ef9d9e8b30d54f89cb8a57d5f44769b646ae1cd7 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Dec 2017 18:41:38 +0200 Subject: [PATCH 019/112] AudioJack: Fix a deadblock on exporting a sample with different sample rate. The deadblock was caused by the mixer stopping which make the recording thread to wait until it get started again and in the other side, the exporting thread which wants to acquire a mutex that had been locked by the recording thread. Many thanks to PhysSong :) --- src/core/audio/AudioJack.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index d8a4f7e78c0..37f09583d03 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -434,19 +434,21 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } } - m_inBuffer.reset (new sampleFrame[_nframes]); + if (! m_stopped) { + m_inBuffer.reset (new sampleFrame[_nframes]); - const float gain = mixer()->masterGain(); - for( int c = 0; c < channels(); ++c ) { - jack_default_audio_sample_t *channel_buffer = m_tempInBufs[c]; + const float gain = mixer()->masterGain(); + for( int c = 0; c < channels(); ++c ) { + jack_default_audio_sample_t *channel_buffer = m_tempInBufs[c]; - for( jack_nframes_t frame = 0; frame < _nframes; ++frame ) - { - m_inBuffer[frame][c] = channel_buffer[frame] / gain; + for( jack_nframes_t frame = 0; frame < _nframes; ++frame ) + { + m_inBuffer[frame][c] = channel_buffer[frame] / gain; + } } - } - mixer()->pushInputFrames (m_inBuffer.get (), _nframes); + mixer()->pushInputFrames (m_inBuffer.get (), _nframes); + } if( _nframes != done ) { From 31a69ddada6470e026977f09efbb496881da2863 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Dec 2017 18:50:56 +0200 Subject: [PATCH 020/112] SongEditor: Hide the global channel selection toolbox when capture support is not available. Thanks to @-Sawuare. --- src/gui/editors/SongEditor.cpp | 42 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 4c0c28d189c..bca169ad26e 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -659,6 +659,8 @@ SongEditorWindow::SongEditorWindow(Song* song) : m_editor(new SongEditor(song)), m_crtlAction( NULL ) { + bool isRecordSupported = Engine::mixer()->audioDev()->supportsCapture(); + setWindowTitle( tr( "Song-Editor" ) ); setWindowIcon( embed::getIconPixmap( "songeditor" ) ); @@ -674,32 +676,34 @@ SongEditorWindow::SongEditorWindow(Song* song) : m_recordAccompanyAction->setToolTip(tr( "Record samples from Audio-device while playing song or BB track")); m_stopAction->setToolTip(tr( "Stop song (Space)" )); - auto *recordMenu = new QMenu(this); - auto *recordGroup = new QActionGroup(this); - recordGroup->addAction (new QAction(tr("Stereo"), recordGroup))->setData (SampleTrack::RecordingChannel::Stereo); - recordGroup->addAction (new QAction(tr("Mono left"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoLeft); - recordGroup->addAction (new QAction(tr("Mono right"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoRight); + if (isRecordSupported) { + auto *recordMenu = new QMenu(this); + auto *recordGroup = new QActionGroup(this); + recordGroup->addAction (new QAction(tr("Stereo"), recordGroup))->setData (SampleTrack::RecordingChannel::Stereo); + recordGroup->addAction (new QAction(tr("Mono left"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoLeft); + recordGroup->addAction (new QAction(tr("Mono right"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoRight); - recordGroup->setExclusive (true); + recordGroup->setExclusive (true); - for (auto *action : recordGroup->actions ()) { - action->setCheckable (true); - } + for (auto *action : recordGroup->actions ()) { + action->setCheckable (true); + } - // Check stereo by default. - recordGroup->actions ().first ()->setChecked (true); + // Check stereo by default. + recordGroup->actions ().first ()->setChecked (true); - connect (recordGroup, SIGNAL(triggered(QAction*)), SLOT(onRecordChannelSelected(QAction*))); + connect (recordGroup, SIGNAL(triggered(QAction*)), SLOT(onRecordChannelSelected(QAction*))); - recordMenu->addActions (recordGroup->actions ()); + recordMenu->addActions (recordGroup->actions ()); - QToolButton * recordTool = new QToolButton(this); - recordTool->setMenu(recordMenu); - recordTool->setToolTip (tr ("Select default channels to record.")); - recordTool->setPopupMode( QToolButton::InstantPopup ); - recordTool->setFixedWidth (17); + QToolButton * recordTool = new QToolButton(this); + recordTool->setMenu(recordMenu); + recordTool->setToolTip (tr ("Select default channels to record.")); + recordTool->setPopupMode( QToolButton::InstantPopup ); + recordTool->setFixedWidth (17); - m_toolBar->addWidget (recordTool); + m_toolBar->addWidget (recordTool); + } // Track actions DropToolBar *trackActionsToolBar = addDropToolBarToTop(tr("Track actions")); From 52eef802c26eff7228a3c1d39c03e5ca203ffd03 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 9 Dec 2017 09:35:36 +0200 Subject: [PATCH 021/112] SongEditor: Hide the record button. --- src/gui/editors/SongEditor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index bca169ad26e..b0361245b11 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -672,7 +672,11 @@ SongEditorWindow::SongEditorWindow(Song* song) : // Set up buttons m_playAction->setToolTip(tr("Play song (Space)")); + + // Remove the record action. m_recordAction->setToolTip(tr("Record samples from Audio-device")); + m_toolBar->removeAction (m_recordAction); + m_recordAccompanyAction->setToolTip(tr( "Record samples from Audio-device while playing song or BB track")); m_stopAction->setToolTip(tr( "Stop song (Space)" )); From 1e9845f2f4c27eb03e81747ef2031747e3d2f1f8 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 16 Dec 2017 11:41:18 +0200 Subject: [PATCH 022/112] Bugfix - SampleTrack -> Load & Save: Fix recorded sample track not being played correctly after saving and loading a project due to sample rate not getting saved in the project file. --- src/tracks/SampleTrack.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 70e211801d1..c5eda9c650b 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -275,6 +275,8 @@ void SampleTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) QString s; _this.setAttribute( "data", m_sampleBuffer->toBase64( s ) ); } + + _this.setAttribute ("sample_rate", m_sampleBuffer->sampleRate ()); // TODO: start- and end-frame } @@ -295,6 +297,10 @@ void SampleTCO::loadSettings( const QDomElement & _this ) changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); setStartTimeOffset( _this.attribute( "off" ).toInt() ); + + if (_this.hasAttribute ("sample_rate")) { + m_sampleBuffer->setSampleRate (_this.attribute ("sample_rate").toInt ()); + } } From 7b49167538470c5b985285f020ff6cddca5dff2c Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 16 Dec 2017 11:47:08 +0200 Subject: [PATCH 023/112] Bugfix - SampleTrack -> Play: Fix sample track not being played in the right place when it not played from the begining. That has created a difference between the ticks and the metronome and the sample track. The cause of the problem was that the calculation of the frame to play was wrong: we had calculated `framesPerTick` according to the current engine's sample rate instead of the SampleBuffer's sample rate. --- include/Engine.h | 4 ++++ src/core/Engine.cpp | 5 +++++ src/tracks/SampleTrack.cpp | 9 ++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/Engine.h b/include/Engine.h index 18960ec8f4c..a93ced0d506 100644 --- a/include/Engine.h +++ b/include/Engine.h @@ -31,6 +31,7 @@ #include "lmms_export.h" +#include "lmms_basics.h" class BBTrackContainer; class DummyTrackContainer; @@ -100,6 +101,9 @@ class LMMS_EXPORT LmmsCore : public QObject { return s_framesPerTick; } + + static float framesPerTick (sample_rate_t sample_rate); + static void updateFramesPerTick(); static inline LmmsCore * inst() diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index 50e25b0b416..7f81c3f9395 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -105,6 +105,11 @@ void LmmsCore::destroy() delete ConfigManager::inst(); } +float LmmsCore::framesPerTick(sample_rate_t sample_rate) { + return sample_rate * 60.0f * 4 / + DefaultTicksPerTact / s_song->getTempo(); +} + diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index c5eda9c650b..dd6ffdf10c6 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -640,14 +640,17 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, { TrackContentObject * tco = getTCO( i ); SampleTCO * sTco = dynamic_cast( tco ); - float framesPerTick = Engine::framesPerTick(); + if( _start >= sTco->startPosition() && _start < sTco->endPosition() ) { if( sTco->isPlaying() == false && (_start >= (sTco->startPosition() + sTco->startTimeOffset()) || sTco->isRecord ()) ) { - f_cnt_t sampleStart = framesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); - f_cnt_t tcoFrameLength = framesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); + auto bufferFramesPerTick = Engine::framesPerTick (sTco->sampleBuffer ()->sampleRate ()); + f_cnt_t sampleStart = bufferFramesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); + + f_cnt_t tcoFrameLength = bufferFramesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); + f_cnt_t sampleBufferLength = sTco->sampleBuffer()->frames(); //if the Tco smaller than the sample length we play only until Tco end //else we play the sample to the end but nothing more From 333ba57ae0fb589958f7b3774b708ddf500edc84 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 16 Dec 2017 20:41:23 +0200 Subject: [PATCH 024/112] SampleTrack -> Recording: Enable per-track record setting and automaticlly create "record sample tco" if there is no recording tco. This commit adds per-track record setting. If a sample track is set as record, lmms will check if there is a tco that set to record: * If there is, it will record into it. * If there is not, it will create a special tco that get created on recording, and as long as the recording. --- include/SampleTrack.h | 6 +++++ include/Song.h | 2 ++ src/core/SampleRecordHandle.cpp | 4 ++++ src/core/Song.cpp | 2 ++ src/tracks/SampleTrack.cpp | 41 +++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 3b9f25cf625..0d3dc5c3d65 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -137,6 +137,7 @@ public slots: class SampleTrack : public Track { Q_OBJECT + mapPropertyFromModel(bool,isRecord,setRecord,m_recordModel); public: enum RecordingChannel : int { None, @@ -180,10 +181,13 @@ public slots: void updateTcos(); void setPlayingTcos( bool isPlaying ); void updateEffectChannel(); + void beforeRecord (); + void toggleRecord(); private: RecordingChannel m_recordingChannel = RecordingChannel::None; + BoolModel m_recordModel; FloatModel m_volumeModel; FloatModel m_panningModel; IntModel m_effectChannelModel; @@ -245,6 +249,8 @@ private slots: private: + QAction *m_toggleRecordAction; + EffectRackView * m_effectRack; SampleTrackWindow * m_window; Knob * m_volumeKnob; Knob * m_panningKnob; diff --git a/include/Song.h b/include/Song.h index d88a59e2b40..6b89d7d4b1e 100644 --- a/include/Song.h +++ b/include/Song.h @@ -342,6 +342,8 @@ public slots: void addBBTrack(); +signals: + void beforeRecord (); private slots: void insertBar(); diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 794f14befc1..5186b2cd3dd 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -49,6 +49,10 @@ SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTime SampleRecordHandle::~SampleRecordHandle() { + // If this is an automatically created tco, + // enable resizing. + m_tco->setAutoResize (false); + if( !m_buffers.empty() ) { SampleBuffer* sb; diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 2809c61adb1..7743e84508b 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -553,6 +553,8 @@ void Song::record() void Song::playAndRecord() { + emit beforeRecord (); + playSong(); m_recording = true; } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index dd6ffdf10c6..d8867339200 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -601,6 +601,7 @@ SampleTrack::SampleTrack( TrackContainer* tc ) : m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels()-1, 1); connect( &m_effectChannelModel, SIGNAL( dataChanged() ), this, SLOT( updateEffectChannel() ) ); + connect (Engine::getSong (), SIGNAL(beforeRecord()), this, SLOT(beforeRecord())); } @@ -641,6 +642,12 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, TrackContentObject * tco = getTCO( i ); SampleTCO * sTco = dynamic_cast( tco ); + // If this is an automaticlly created record track, resize it to the current + // position. + if (sTco->isRecord () && !sTco->isMuted () && sTco->getAutoResize ()) { + sTco->changeLength (_start - sTco->startPosition()); + } + if( _start >= sTco->startPosition() && _start < sTco->endPosition() ) { if( sTco->isPlaying() == false && (_start >= (sTco->startPosition() + sTco->startTimeOffset()) @@ -782,6 +789,36 @@ void SampleTrack::setPlayingTcos( bool isPlaying ) } } +void SampleTrack::beforeRecord() { + if (isRecord ()) { + bool isRecordTCOExist = false; + + for(auto &track : getTCOs ()) { + auto sampleTCO = static_cast(track); + + if (sampleTCO->isRecord() && !sampleTCO->isMuted()) + isRecordTCOExist = true; + } + + if (! isRecordTCOExist) { + auto fallbackRecordTCO = static_cast(createTCO (0)); + + fallbackRecordTCO->setRecord (true); + fallbackRecordTCO->movePosition (Engine::getSong ()->getPlayPos (Song::Mode_PlaySong)); + fallbackRecordTCO->changeLength (1); + fallbackRecordTCO->setSampleStartFrame (0); + fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); + fallbackRecordTCO->setIsPlaying (true); + + fallbackRecordTCO->setAutoResize (true); + } + } +} + +void SampleTrack::toggleRecord() { + setRecord (! isRecord ()); +} + SampleTrack::RecordingChannel SampleTrack::recordingChannel() const{ // If we had defined a recording channel for this track, use // it. Otherwise, use the global setting. @@ -928,6 +965,10 @@ void SampleTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *tra recordMenu->addActions (recordChannels->actions ()); connect (recordChannels, SIGNAL(triggered(QAction*)), SLOT(onRecordActionSelected(QAction*))); + + auto recordAction = toMenu->addAction( tr( "Toggle record" ), st, SLOT( toggleRecord() ) ); + recordAction->setCheckable (true); + recordAction->setChecked (st->isRecord ()); } From 16dc30b2dc7d4f219f9eaa71f156940b4fbd1539 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 17 Dec 2017 08:28:49 +0200 Subject: [PATCH 025/112] SampleTrack: On project save, save the values of SampleTrack::m_record and SampleTrack::m_recordingChannel. --- include/SampleTrack.h | 2 +- src/tracks/SampleTrack.cpp | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 0d3dc5c3d65..2a567e43804 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -185,7 +185,7 @@ public slots: void toggleRecord(); private: - RecordingChannel m_recordingChannel = RecordingChannel::None; + IntModel m_recordingChannelModel; BoolModel m_recordModel; FloatModel m_volumeModel; diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index d8867339200..d1632367dbb 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -594,7 +594,12 @@ SampleTrack::SampleTrack( TrackContainer* tc ) : m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), - m_audioPort( tr( "Sample track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ) + m_audioPort( tr( "Sample track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ), + m_recordingChannelModel(RecordingChannel::None, + RecordingChannel::None, + RecordingChannel::Stereo, + this, + tr ("Record channel")) { setName( tr( "Sample track" ) ); m_panningModel.setCenterValue( DefaultPanning ); @@ -741,6 +746,8 @@ void SampleTrack::saveTrackSpecificSettings( QDomDocument & _doc, m_volumeModel.saveSettings( _doc, _this, "vol" ); m_panningModel.saveSettings( _doc, _this, "pan" ); m_effectChannelModel.saveSettings( _doc, _this, "fxch" ); + m_recordModel.saveSettings(_doc, _this, "record"); + m_recordingChannelModel.saveSettings (_doc, _this, "record_channel"); } @@ -761,10 +768,13 @@ void SampleTrack::loadTrackSpecificSettings( const QDomElement & _this ) } node = node.nextSibling(); } + m_volumeModel.loadSettings( _this, "vol" ); m_panningModel.loadSettings( _this, "pan" ); m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels() - 1 ); m_effectChannelModel.loadSettings( _this, "fxch" ); + m_recordModel.loadSettings (_this, "record"); + m_recordingChannelModel.loadSettings (_this, "record_channel"); } @@ -822,8 +832,8 @@ void SampleTrack::toggleRecord() { SampleTrack::RecordingChannel SampleTrack::recordingChannel() const{ // If we had defined a recording channel for this track, use // it. Otherwise, use the global setting. - if (m_recordingChannel != RecordingChannel::None) { - return m_recordingChannel; + if (m_recordingChannelModel.value () != static_cast(RecordingChannel::None)) { + return static_cast(m_recordingChannelModel.value ()); } else { return gui->songEditor ()->globalRecordChannel (); } @@ -831,7 +841,7 @@ SampleTrack::RecordingChannel SampleTrack::recordingChannel() const{ void SampleTrack::setRecordingChannel(const RecordingChannel &recordingChannel) { - m_recordingChannel = recordingChannel; + m_recordingChannelModel.setValue (recordingChannel); } void SampleTrack::updateEffectChannel() @@ -954,12 +964,10 @@ void SampleTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *tra for (auto *action : recordChannels->actions ()) { action->setCheckable (true); - if (static_cast(action->data ().value()) - == st->m_recordingChannel) + if (action->data ().value() == st->m_recordingChannelModel.value ()) { action->setChecked (true); } - } recordMenu->addActions (recordChannels->actions ()); @@ -1248,7 +1256,7 @@ void SampleTrackView::onRecordActionSelected(QAction *action) { auto selectedRecordingChannel = static_cast(action->data ().value()); // If we've selected the current recording channel again, we should undo it. - if (selectedRecordingChannel == st->m_recordingChannel) { + if (selectedRecordingChannel == static_cast(st->m_recordingChannelModel.value ())) { st->setRecordingChannel (SampleTrack::RecordingChannel::None); action->setChecked (false); } else { @@ -1256,4 +1264,5 @@ void SampleTrackView::onRecordActionSelected(QAction *action) { action->setChecked (true); } + } From 0b2d8a7577cfa8b1fd7e6458d12c37f6029df4f9 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 17 Dec 2017 22:37:25 +0200 Subject: [PATCH 026/112] SampleTrack -> Minor: Reorder the constructor initialization order to avoid a warning. Thanks to @-PhysSong. --- src/tracks/SampleTrack.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index d1632367dbb..5a0e98e2af1 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -589,17 +589,18 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) SampleTrack::SampleTrack( TrackContainer* tc ) : Track( Track::SampleTrack, tc ), + m_recordingChannelModel(RecordingChannel::None, + RecordingChannel::None, + RecordingChannel::Stereo, + this, + tr ("Record channel")), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), - m_audioPort( tr( "Sample track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ), - m_recordingChannelModel(RecordingChannel::None, - RecordingChannel::None, - RecordingChannel::Stereo, - this, - tr ("Record channel")) + m_audioPort( tr( "Sample track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ) + { setName( tr( "Sample track" ) ); m_panningModel.setCenterValue( DefaultPanning ); From 75f84d6f30d72fc42f57768664a33289791bc2b0 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 23 Dec 2017 21:28:47 +0200 Subject: [PATCH 027/112] Travis: Use SDL2 on Windows. --- .travis/linux.win.install_raw.sh | 26 ++++++++++++++++++++++++++ .travis/linux.win32.install.sh | 4 ++++ .travis/linux.win64.install.sh | 4 ++++ 3 files changed, 34 insertions(+) create mode 100755 .travis/linux.win.install_raw.sh diff --git a/.travis/linux.win.install_raw.sh b/.travis/linux.win.install_raw.sh new file mode 100755 index 00000000000..796b90ba905 --- /dev/null +++ b/.travis/linux.win.install_raw.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +while read -r PACKAGE_URL_AND_OPTS; do + pushd "$PWD" + IFS=',' read -ra PACKAGE_URL_AND_OPTS <<< "$PACKAGE_URL_AND_OPTS" + + PACKAGE_URL="${PACKAGE_URL_AND_OPTS[0]}" + OPTS="${PACKAGE_URL_AND_OPTS[1]}" + + echo "Downloading $PACKAGE_URL ..." + + mkdir PACKAGE_URL_TEMP + cd PACKAGE_URL_TEMP + + curl "$PACKAGE_URL" | tar xfz - + dir_name=$(ls) + cd "$dir_name" + + ls * + echo "Installing package $dir_name (make ${OPTS}) ..." + sudo make $OPTS + + popd + rm -rf PACKAGE_URL_TEMP +done <<< "$MANUAL_PACKAGES_URLS" + diff --git a/.travis/linux.win32.install.sh b/.travis/linux.win32.install.sh index 6e73e7abea5..ea5fe14b12d 100755 --- a/.travis/linux.win32.install.sh +++ b/.travis/linux.win32.install.sh @@ -11,6 +11,8 @@ MINGW_PACKAGES="mingw32-x-sdl mingw32-x-libvorbis mingw32-x-fluidsynth mingw32-x # swh build dependencies SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl" +export MANUAL_PACKAGES_URLS="https://www.libsdl.org/release/SDL2-devel-2.0.7-mingw.tar.gz,install-package arch=i686-w64-mingw32 prefix=/opt/mingw32" + export MINGW_PACKAGES "$TRAVIS_BUILD_DIR/.travis/linux.win.download.sh" win32 @@ -20,6 +22,8 @@ PACKAGES="nsis cloog-isl libmpc3 qt4-linguist-tools mingw32 $MINGW_PACKAGES $SWH # shellcheck disable=SC2086 sudo apt-get install -y $PACKAGES +"$TRAVIS_BUILD_DIR/.travis/linux.win.install_raw.sh" + # ccache 3.2 is needed because mingw32-x-gcc is version 4.9, which causes cmake # to use @file command line passing, which in turn ccache 3.1.9 doesn't support pushd /tmp diff --git a/.travis/linux.win64.install.sh b/.travis/linux.win64.install.sh index 99ef7187f02..dc84601c488 100755 --- a/.travis/linux.win64.install.sh +++ b/.travis/linux.win64.install.sh @@ -10,6 +10,8 @@ MINGW_PACKAGES="mingw64-x-sdl mingw64-x-libvorbis mingw64-x-fluidsynth mingw64-x mingw64-x-fftw mingw64-x-flac mingw64-x-fltk mingw64-x-libsamplerate mingw64-x-pkgconfig mingw64-x-binutils mingw64-x-gcc mingw64-x-runtime mingw64-x-libgig mingw64-x-libsoundio mingw64-x-lame mingw64-x-qt5base" + +export MANUAL_PACKAGES_URLS="https://www.libsdl.org/release/SDL2-devel-2.0.7-mingw.tar.gz,install-package arch=x86_64-w64-mingw32 prefix=/opt/mingw64" export MINGW_PACKAGES @@ -17,3 +19,5 @@ export MINGW_PACKAGES # shellcheck disable=SC2086 sudo apt-get install -y $MINGW_PACKAGES + +"$TRAVIS_BUILD_DIR/.travis/linux.win.install_raw.sh" From 70f20464a5f88bd9899011e533180a58b53d71e0 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 25 Dec 2017 09:40:52 +0200 Subject: [PATCH 028/112] Travis -> InstallRaw: Fix Shellcheck warnings. --- .travis/linux.win.install_raw.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis/linux.win.install_raw.sh b/.travis/linux.win.install_raw.sh index 796b90ba905..67568ac5057 100755 --- a/.travis/linux.win.install_raw.sh +++ b/.travis/linux.win.install_raw.sh @@ -10,14 +10,15 @@ while read -r PACKAGE_URL_AND_OPTS; do echo "Downloading $PACKAGE_URL ..." mkdir PACKAGE_URL_TEMP - cd PACKAGE_URL_TEMP + cd PACKAGE_URL_TEMP || exit 1 curl "$PACKAGE_URL" | tar xfz - dir_name=$(ls) - cd "$dir_name" + cd "$dir_name" || exit 1 - ls * echo "Installing package $dir_name (make ${OPTS}) ..." + + # shellcheck disable=SC2086 sudo make $OPTS popd From 96ccbfebbc6d308e8291e26faa90ca73a39a3092 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 25 Dec 2017 09:41:22 +0200 Subject: [PATCH 029/112] Travis -> Linux: Use SDL2 by default. --- .travis/linux..install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index 2f1262d071b..e440d794662 100755 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -3,7 +3,7 @@ set -e PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev - libasound2-dev libjack-jackd2-dev libsdl-dev libsamplerate0-dev libstk0-dev stk + libasound2-dev libjack-jackd2-dev libsdl2-dev libsamplerate0-dev libstk0-dev stk libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev libgig-dev libsoundio-dev qt59base qt59translations qt59tools" From 5f68c8b906adc3d44dbcd5d5a39146900451ee5d Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 25 Dec 2017 10:06:58 +0200 Subject: [PATCH 030/112] CMake -> Install: Install SDL2.dll instead of SDL.dll. Many thanks to @-PhysSong. --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4ac6bf1332f..05d6e24e9ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -229,7 +229,7 @@ IF(NOT MSVC) "${MINGW_PREFIX}/bin/libfftw3f-3.dll" "${MINGW_PREFIX}/bin/libFLAC-8.dll" "${MINGW_PREFIX}/bin/libpng16-16.dll" - "${MINGW_PREFIX}/bin/SDL.dll" + "${MINGW_PREFIX}/bin/SDL2.dll" "${MINGW_PREFIX}/bin/libglib-2.0-0.dll" "${MINGW_PREFIX}/bin/libgthread-2.0-0.dll" "${MINGW_PREFIX}/bin/zlib1.dll" From acc1b8e014a54d8b44926a0293e71e5ebb8b3fec Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 24 Feb 2018 15:01:40 +0200 Subject: [PATCH 031/112] AudioJack: Connect capture stream to LMMS by default. --- include/AudioJack.h | 3 ++ src/core/audio/AudioJack.cpp | 57 ++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 4cae4d06962..5a0f6e328a6 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -104,6 +104,9 @@ private slots: void * _udata ); static void shutdownCallback( void * _udata ); + void connectJackPort (const char *outputPort, + const char *inputPort, + const char *portName); jack_client_t * m_client; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 37f09583d03..d87f74e7210 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -243,30 +243,30 @@ void AudioJack::startProcessing() - const char * * ports = jack_get_ports( m_client, NULL, NULL, + const char * * inputPorts = jack_get_ports( m_client, NULL, NULL, JackPortIsPhysical | JackPortIsInput ); - if( ports == NULL ) - { - printf( "no physical playback ports. you'll have to do " - "connections at your own!\n" ); + // Connect lmms to the default playback device. + if (inputPorts) { + for (uint i = 0; i < channels (); ++i) { + connectJackPort (jack_port_name (m_outputPorts[i]), inputPorts[i], "playback"); + } + + free( inputPorts ); } - else - { - for( ch_cnt_t ch = 0; ch < channels(); ++ch ) - { - if( jack_connect( m_client, jack_port_name( - m_outputPorts[ch] ), - ports[ch] ) ) - { - printf( "cannot connect output ports. you'll " - "have to do connections at your own!\n" - ); - } + + const char * * outputPorts = jack_get_ports( m_client, NULL, NULL, + JackPortIsPhysical | + JackPortIsOutput ); + // Connect lmms to the default capture device. + if (outputPorts) { + for (uint i = 0; i < channels (); ++i) { + connectJackPort (outputPorts[i], jack_port_name (m_inputPorts[i]), "capture"); } + + free( outputPorts ); } - free( ports ); } @@ -481,6 +481,27 @@ void AudioJack::shutdownCallback( void * _udata ) _this->zombified(); } +void AudioJack::connectJackPort(const char *outputPort, + const char *inputPort, + const char *portName) { + if(outputPort == NULL || inputPort == NULL) + { + printf( "no physical %s port. you'll have to do " + "connections at your own!\n", portName); + } + else + { + if( jack_connect( m_client, outputPort, + inputPort) ) + { + printf( "cannot connect %s port. you'll " + "have to do connections at your own!\n", + portName); + } + } + +} + From 715778f5a8224c52acfc12669345a3b1a4ac327b Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 24 Feb 2018 15:35:51 +0200 Subject: [PATCH 032/112] AudioSdl -> Recording - Bugfix: Due to spec.sampleRate being wrong, recording would stop early --- src/core/audio/AudioSdl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 42adb9b33c9..40b69a5d785 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -100,6 +100,7 @@ AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : #ifdef LMMS_HAVE_SDL2 m_inputAudioHandle = m_audioHandle; + m_inputAudioHandle.freq = mixer ()->inputSampleRate (); m_inputAudioHandle.callback = sdlInputAudioCallback; m_inputDevice = SDL_OpenAudioDevice (NULL, @@ -107,7 +108,7 @@ AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : &m_inputAudioHandle, &actual, 0); - if (m_inputDevice != 0) { + if (m_inputDevice != 0 && actual.freq == mixer ()->inputSampleRate ()) { m_supportsCapture = true; } else { m_supportsCapture = false; From 79cddac391bf597c23494d401e65a227f5819a8c Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 31 Mar 2018 14:24:05 +0300 Subject: [PATCH 033/112] Style -> SampleTrack Recording: Paint recorded sampletrack red (intead of writing "Rec"). --- data/themes/default/style.css | 1 + include/Track.h | 5 +++++ src/core/Track.cpp | 6 ++++++ src/tracks/SampleTrack.cpp | 28 +++++++++------------------- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/data/themes/default/style.css b/data/themes/default/style.css index 5d889295cae..08f778c29ec 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -646,6 +646,7 @@ TrackContentObjectView { qproperty-textColor: #fff; qproperty-textBackgroundColor: rgba(0, 0, 0, 75); qproperty-textShadowColor: rgba(0,0,0,200); + qproperty-recordingBackgroundColor: rgba(139,25,25,0.38); qproperty-gradient: false; /* boolean property, set true to have a gradient */ font-size: 11px; diff --git a/include/Track.h b/include/Track.h index 8d6d235baeb..ecd0f3e9c36 100644 --- a/include/Track.h +++ b/include/Track.h @@ -203,6 +203,7 @@ class TrackContentObjectView : public selectableObject, public ModelView Q_PROPERTY( QColor textBackgroundColor READ textBackgroundColor WRITE setTextBackgroundColor ) Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor ) Q_PROPERTY( QColor BBPatternBackground READ BBPatternBackground WRITE setBBPatternBackground ) + Q_PROPERTY( QColor recordingBackgroundColor READ recordingBackgroundColor WRITE setRecordingBackgroundColor ) Q_PROPERTY( bool gradient READ gradient WRITE setGradient ) public: @@ -223,6 +224,8 @@ class TrackContentObjectView : public selectableObject, public ModelView QColor textBackgroundColor() const; QColor textShadowColor() const; QColor BBPatternBackground() const; + QColor recordingBackgroundColor() const; + bool gradient() const; void setMutedColor( const QColor & c ); void setMutedBackgroundColor( const QColor & c ); @@ -232,6 +235,7 @@ class TrackContentObjectView : public selectableObject, public ModelView void setTextShadowColor( const QColor & c ); void setBBPatternBackground( const QColor & c ); void setGradient( const bool & b ); + void setRecordingBackgroundColor (const QColor & c ); // access needsUpdate member variable bool needsUpdate(); @@ -308,6 +312,7 @@ protected slots: QColor m_textBackgroundColor; QColor m_textShadowColor; QColor m_BBPatternBackground; + QColor m_recordingBackgroundColor; bool m_gradient; bool m_needsUpdate; diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 567ade20038..93bf3dc8615 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -368,6 +368,9 @@ QColor TrackContentObjectView::mutedColor() const QColor TrackContentObjectView::mutedBackgroundColor() const { return m_mutedBackgroundColor; } +QColor TrackContentObjectView::recordingBackgroundColor() const +{ return m_recordingBackgroundColor; } + QColor TrackContentObjectView::selectedColor() const { return m_selectedColor; } @@ -412,6 +415,9 @@ void TrackContentObjectView::setTextShadowColor( const QColor & c ) void TrackContentObjectView::setBBPatternBackground( const QColor & c ) { m_BBPatternBackground = QColor( c ); } +void TrackContentObjectView::setRecordingBackgroundColor (const QColor & c ) +{ m_recordingBackgroundColor = QColor( c ); } + void TrackContentObjectView::setGradient( const bool & b ) { m_gradient = b; } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 5a0e98e2af1..41d89586330 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -501,9 +501,14 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) QColor c; bool muted = m_tco->getTrack()->isMuted() || m_tco->isMuted(); - // state: selected, muted, normal - c = isSelected() ? selectedColor() : ( muted ? mutedBackgroundColor() - : painter.background().color() ); + if (isSelected ()) + c = selectedColor (); + else if (muted) + c = mutedBackgroundColor (); + else if (m_tco->isRecord ()) + c = recordingBackgroundColor (); + else + c = painter.background ().color (); lingrad.setColorAt( 1, c.darker( 300 ) ); lingrad.setColorAt( 0, c ); @@ -531,7 +536,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) float nom = Engine::getSong()->getTimeSigModel().getNumerator(); float den = Engine::getSong()->getTimeSigModel().getDenominator(); float ticksPerTact = DefaultTicksPerTact * nom / den; - + float offset = m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact(); QRect r = QRect( TCO_BORDER_WIDTH + offset, spacing, qMax( static_cast( m_tco->sampleLength() * ppt / ticksPerTact ), 1 ), rect().bottom() - 2 * spacing ); @@ -562,21 +567,6 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) embed::getIconPixmap( "muted", size, size ) ); } - // recording sample tracks is not possible at the moment - - if( m_tco->isRecord() ) - { - p.setFont( pointSize<7>( p.font() ) ); - - p.setPen( textShadowColor() ); - p.drawText( 10, p.fontMetrics().height()+1, "Rec" ); - p.setPen( textColor() ); - p.drawText( 9, p.fontMetrics().height(), "Rec" ); - - p.setBrush( QBrush( textColor() ) ); - p.drawEllipse( 4, 5, 4, 4 ); - } - p.end(); painter.drawPixmap( 0, 0, m_paintPixmap ); From c881c14d691c9bc644f7bef0554f177a7963f34b Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 9 Apr 2018 10:53:10 +0300 Subject: [PATCH 034/112] basics: Change sampleFrame to `std::array` in order to make stantard containers be able to store it. --- include/lmms_basics.h | 9 +++++---- src/core/Effect.cpp | 4 ++-- src/core/SampleBuffer.cpp | 10 +++++----- src/core/audio/AudioDevice.cpp | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/include/lmms_basics.h b/include/lmms_basics.h index 16d9fd22658..7d4a3caa507 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -32,9 +32,9 @@ #ifdef LMMS_HAVE_STDINT_H #include +#include #endif - typedef int32_t tact_t; typedef int32_t tick_t; typedef uint8_t volume_t; @@ -133,11 +133,12 @@ const ch_cnt_t LEFT_CHANNEL_INDEX = 0; -typedef sample_t sampleFrame[DEFAULT_CHANNELS]; -typedef sample_t surroundSampleFrame[SURROUND_CHANNELS]; +typedef std::array sampleFrame; + +typedef std::array surroundSampleFrame; #define ALIGN_SIZE 16 #if __GNUC__ -typedef sample_t sampleFrameA[DEFAULT_CHANNELS] __attribute__((__aligned__(ALIGN_SIZE))); +typedef std::array sampleFrameA __attribute__((__aligned__(ALIGN_SIZE))); #endif diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index c842977532f..a85afd9b5c8 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -203,8 +203,8 @@ void Effect::resample( int _i, const sampleFrame * _src_buf, } m_srcData[_i].input_frames = _frames; m_srcData[_i].output_frames = Engine::mixer()->framesPerPeriod(); - m_srcData[_i].data_in = (float *) _src_buf[0]; - m_srcData[_i].data_out = _dst_buf[0]; + m_srcData[_i].data_in = (float *) _src_buf[0].data (); + m_srcData[_i].data_out = _dst_buf[0].data (); m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr; m_srcData[_i].end_of_input = 0; int error; diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 0f58e756007..7c8cae47242 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -676,8 +676,8 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, // Generate output src_data.data_in = getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame )[0]; - src_data.data_out = _ab[0]; + loopStartFrame, loopEndFrame, endFrame )->data (); + 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; @@ -1179,9 +1179,9 @@ SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, { SRC_DATA src_data; src_data.end_of_input = 1; - src_data.data_in = data[0]; - src_data.data_out = dst_buf[0]; - src_data.input_frames = frames; + src_data.data_in = data ()->data (); + src_data.data_out = dst_sb->m_origData.data ()->data (); + src_data.input_frames = frames (); src_data.output_frames = dst_frames; src_data.src_ratio = (double) _dst_sr / _src_sr; if( ( error = src_process( state, &src_data ) ) ) diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index ab69e144b12..efb933a3249 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -196,8 +196,8 @@ void AudioDevice::resample( const surroundSampleFrame * _src, } m_srcData.input_frames = _frames; m_srcData.output_frames = _frames; - m_srcData.data_in = (float *) _src[0]; - m_srcData.data_out = _dst[0]; + m_srcData.data_in = (float *) _src[0].data (); + m_srcData.data_out = _dst[0].data (); m_srcData.src_ratio = (double) _dst_sr / _src_sr; m_srcData.end_of_input = 0; int error; From 84cea2b752e86c23df93e4fc0cafa7729e256f95 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 9 Apr 2018 10:55:41 +0300 Subject: [PATCH 035/112] SampleBuffer: Internally change buffer to std::vector and support addData. --- include/SampleBuffer.h | 49 ++++++++---- src/core/SampleBuffer.cpp | 154 +++++++++++++++++++------------------- 2 files changed, 115 insertions(+), 88 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 26dda70a9fe..793d1b29655 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -32,6 +32,8 @@ #include #include "lmms_export.h" +#include + #include "interpolation.h" #include "lmms_basics.h" #include "lmms_math.h" @@ -53,6 +55,8 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject Q_OBJECT MM_OPERATORS public: + typedef std::vector> DataVector; + enum LoopMode { LoopOff = 0, LoopOn, @@ -109,6 +113,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject SampleBuffer( const QString & _audio_file, bool _is_base64_data = false ); SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ); explicit SampleBuffer( const f_cnt_t _frames ); + SampleBuffer(SampleBuffer::DataVector &&movedData); virtual ~SampleBuffer(); @@ -168,7 +173,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject inline f_cnt_t frames() const { - return m_frames; + return m_data.size (); } inline float amplification() const @@ -208,7 +213,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject inline const sampleFrame * data() const { - return m_data; + return m_data.data (); } QString openAudioFile() const; @@ -230,17 +235,23 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject // dataUnlock(), out of loops for efficiency inline sample_t userWaveSample( const float _sample ) const { - f_cnt_t frames = m_frames; - sampleFrame * data = m_data; - const float frame = _sample * frames; - f_cnt_t f1 = static_cast( frame ) % frames; + f_cnt_t dataFrames = frames (); + const sampleFrame * data = this->data(); + const float frame = _sample * dataFrames; + f_cnt_t f1 = static_cast( frame ) % dataFrames; if( f1 < 0 ) { - f1 += frames; + f1 += dataFrames; } - return linearInterpolate( data[f1][0], data[ (f1 + 1) % frames ][0], fraction( frame ) ); + return linearInterpolate( data[f1][0], data[ (f1 + 1) % dataFrames ][0], fraction( frame ) ); + } + + bool tryDataReadLock () + { + return m_varLock.tryLockForRead (); } + void dataReadLock() { m_varLock.lockForRead(); @@ -254,7 +265,15 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject static QString tryToMakeRelative( const QString & _file ); static QString tryToMakeAbsolute(const QString & file); - + /** + * @brief Add data to the buffer, + * @param begin Beginning of an InputIterator. + * @param end End of an InputIterator. + * + * @warning That locks m_varLock for write. + */ + void addData(const DataVector::iterator begin, const DataVector::iterator end); + public slots: void setAudioFile( const QString & _audio_file ); void loadFromBase64( const QString & _data ); @@ -282,12 +301,15 @@ public slots: ch_cnt_t & _channels, sample_rate_t & _sample_rate ); + inline sampleFrame * data() + { + return m_data.data (); + } + QString m_audioFile; - sampleFrame * m_origData; - f_cnt_t m_origFrames; - sampleFrame * m_data; + DataVector m_origData; + DataVector m_data; QReadWriteLock m_varLock; - f_cnt_t m_frames; f_cnt_t m_startFrame; f_cnt_t m_endFrame; f_cnt_t m_loopStartFrame; @@ -297,6 +319,7 @@ public slots: float m_frequency; sample_rate_t m_sampleRate; + const sampleFrame * getSampleFragment( f_cnt_t _index, f_cnt_t _frames, LoopMode _loopmode, sampleFrame * * _tmp, diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 7c8cae47242..c2360c8eac2 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -62,10 +62,6 @@ SampleBuffer::SampleBuffer() : m_audioFile( "" ), - m_origData( NULL ), - m_origFrames( 0 ), - m_data( NULL ), - m_frames( 0 ), m_startFrame( 0 ), m_endFrame( 0 ), m_loopStartFrame( 0 ), @@ -105,25 +101,38 @@ SampleBuffer::SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ) { if( _frames > 0 ) { - m_origData = MM_ALLOC( sampleFrame, _frames ); - memcpy( m_origData, _data, _frames * BYTES_PER_FRAME ); - m_origFrames = _frames; - update(); + m_origData.assign (_data, _data + _frames); } } +SampleBuffer::SampleBuffer( SampleBuffer::DataVector &&movedData ) : + m_audioFile( "" ), + m_startFrame( 0 ), + m_endFrame( 0 ), + m_loopStartFrame( 0 ), + m_loopEndFrame( 0 ), + m_amplification( 1.0f ), + m_reversed( false ), + m_frequency( BaseFreq ), + m_sampleRate( Engine::mixer()->baseSampleRate() ) +{ + m_origData = std::move(movedData); + connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); + update(); +} + SampleBuffer::SampleBuffer( const f_cnt_t _frames ) - : SampleBuffer() + : SampleBuffer( + DataVector(static_cast(_frames), + sampleFrame{0,0}) + ) { if( _frames > 0 ) { - m_origData = MM_ALLOC( sampleFrame, _frames ); - memset( m_origData, 0, _frames * BYTES_PER_FRAME ); - m_origFrames = _frames; - update(); + m_data.resize (_frames, {0,0}); } } @@ -132,8 +141,6 @@ SampleBuffer::SampleBuffer( const f_cnt_t _frames ) SampleBuffer::~SampleBuffer() { - MM_FREE( m_origData ); - MM_FREE( m_data ); } @@ -144,14 +151,13 @@ void SampleBuffer::sampleRateChanged() } -void SampleBuffer::update( bool _keep_settings ) +void SampleBuffer::update(bool _keep_settings) { - const bool lock = ( m_data != NULL ); + const bool lock = ( m_data.size () != 0); if( lock ) { Engine::mixer()->requestChangeInModel(); m_varLock.lockForWrite(); - MM_FREE( m_data ); } // File size and sample length limits @@ -159,17 +165,18 @@ void SampleBuffer::update( bool _keep_settings ) const int sampleLengthMax = 90; // Minutes bool fileLoadError = false; - if( m_audioFile.isEmpty() && m_origData != NULL && m_origFrames > 0 ) + if( m_audioFile.isEmpty() && m_origData.size () > 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 ); + auto previousFrames = frames (); + m_data = std::move(m_origData); if( _keep_settings == false ) { - m_frames = m_origFrames; m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; + m_loopEndFrame = m_endFrame = frames (); + } else { + m_data.resize (previousFrames); } } else if( !m_audioFile.isEmpty() ) @@ -179,7 +186,7 @@ void SampleBuffer::update( bool _keep_settings ) sample_t * fbuf = NULL; ch_cnt_t channels = DEFAULT_CHANNELS; sample_rate_t samplerate = Engine::mixer()->baseSampleRate(); - m_frames = 0; + f_cnt_t dataFrames = 0; const QFileInfo fileInfo( file ); if( fileInfo.size() > fileSizeMax * 1024 * 1024 ) @@ -213,42 +220,41 @@ void SampleBuffer::update( bool _keep_settings ) // 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" ) + if( dataFrames == 0 && fileInfo.suffix() == "ogg" ) { - m_frames = decodeSampleOGGVorbis( file, buf, channels, samplerate ); + dataFrames = decodeSampleOGGVorbis( file, buf, channels, samplerate ); } #endif - if( m_frames == 0 ) + if( dataFrames == 0 ) { - m_frames = decodeSampleSF( file, fbuf, channels, + dataFrames = decodeSampleSF( file, fbuf, channels, samplerate ); } #ifdef LMMS_HAVE_OGGVORBIS - if( m_frames == 0 ) + if( dataFrames == 0 ) { - m_frames = decodeSampleOGGVorbis( file, buf, channels, + dataFrames = decodeSampleOGGVorbis( file, buf, channels, samplerate ); } #endif - if( m_frames == 0 ) + if( dataFrames == 0 ) { - m_frames = decodeSampleDS( file, buf, channels, + dataFrames = decodeSampleDS( file, buf, channels, samplerate ); } } - if ( m_frames == 0 || fileLoadError ) // if still no frames, bail + if ( dataFrames == 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_data.resize (1, {0,0}); m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = 1; } else // otherwise normalize sample rate { + m_data.resize (dataFrames); normalizeSampleRate( samplerate, _keep_settings ); } } @@ -256,9 +262,7 @@ void SampleBuffer::update( bool _keep_settings ) { // 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_data.resize (1, {0,0}); m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = 1; } @@ -295,7 +299,7 @@ void SampleBuffer::convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, // 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 ); + m_data.resize (_frames); const int ch = ( _channels > 1 ) ? 1 : 0; // if reversing is on, we also reverse when @@ -330,7 +334,7 @@ void SampleBuffer::directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _ { - m_data = MM_ALLOC( sampleFrame, _frames ); + m_data.resize (_frames); const int ch = ( _channels > 1 ) ? 1 : 0; // if reversing is on, we also reverse when @@ -370,11 +374,7 @@ void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, { SampleBuffer * resampled = resample( _src_sr, Engine::mixer()->baseSampleRate() ); - MM_FREE( m_data ); - m_frames = resampled->frames(); - m_data = MM_ALLOC( sampleFrame, m_frames ); - memcpy( m_data, resampled->data(), m_frames * - sizeof( sampleFrame ) ); + m_data = std::move(resampled->m_data); delete resampled; } @@ -382,7 +382,7 @@ void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, { // update frame-variables m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; + m_loopEndFrame = m_endFrame = frames (); } } @@ -782,7 +782,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, - +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 @@ -791,21 +791,21 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, { if( _index + _frames <= _end ) { - return m_data + _index; + return data () + _index; } } else if( _loopmode == LoopOn ) { if( _index + _frames <= _loopend ) { - return m_data + _index; + return data () + _index; } } else { if( ! *_backwards && _index + _frames < _loopend ) { - return m_data + _index; + return data () + _index; } } @@ -814,19 +814,19 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, if( _loopmode == LoopOff ) { f_cnt_t available = _end - _index; - memcpy( *_tmp, m_data + _index, available * BYTES_PER_FRAME ); + memcpy( *_tmp, 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 ); + memcpy( *_tmp, 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 + _loopstart, todo * BYTES_PER_FRAME ); + memcpy( *_tmp + copied, data () + _loopstart, todo * BYTES_PER_FRAME ); copied += todo; } } @@ -853,7 +853,7 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, else { copied = qMin( _frames, _loopend - pos ); - memcpy( *_tmp, m_data + pos, copied * BYTES_PER_FRAME ); + memcpy( *_tmp, data () + pos, copied * BYTES_PER_FRAME ); pos += copied; if( pos == _loopend ) backwards = true; } @@ -875,7 +875,7 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, else { f_cnt_t todo = qMin( _frames - copied, _loopend - pos ); - memcpy( *_tmp + copied, m_data + pos, todo * BYTES_PER_FRAME ); + memcpy( *_tmp + copied, data () + pos, todo * BYTES_PER_FRAME ); pos += todo; copied += todo; if( pos >= _loopend ) backwards = true; @@ -919,9 +919,9 @@ f_cnt_t SampleBuffer::getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame, f_cnt_t _to_frame ) { - if( m_frames == 0 ) return; + if( frames () == 0 ) return; - const bool focus_on_range = _to_frame <= m_frames + const bool focus_on_range = _to_frame <= frames () && 0 <= _from_frame && _from_frame < _to_frame; //_p.setClipRect( _clip ); const int w = _dr.width(); @@ -929,7 +929,7 @@ void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, const int yb = h / 2 + _dr.y(); const float y_space = h*0.5f; - const int nb_frames = focus_on_range ? _to_frame - _from_frame : m_frames; + const int nb_frames = focus_on_range ? _to_frame - _from_frame : frames (); const int fpp = tLimit( nb_frames / w, 1, 20 ); QPointF * l = new QPointF[nb_frames / fpp + 1]; @@ -937,7 +937,7 @@ void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, int n = 0; const int xb = _dr.x(); const int first = focus_on_range ? _from_frame : 0; - const int last = focus_on_range ? _to_frame : m_frames; + const int last = focus_on_range ? _to_frame : frames (); for( int frame = first; frame < last; frame += fpp ) { l[n] = QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), @@ -1150,8 +1150,8 @@ QString & SampleBuffer::toBase64( QString & _dst ) const #else /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ - base64::encode( (const char *) m_data, - m_frames * sizeof( sampleFrame ), _dst ); + base64::encode( (const char *) data (), + frames () * sizeof( sampleFrame ), _dst ); #endif /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ @@ -1164,12 +1164,9 @@ QString & SampleBuffer::toBase64( QString & _dst ) const SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, const sample_rate_t _dst_sr ) { - sampleFrame * data = m_data; - const f_cnt_t frames = m_frames; - const f_cnt_t dst_frames = static_cast( frames / + const f_cnt_t dst_frames = static_cast( frames ()/ (float) _src_sr * (float) _dst_sr ); SampleBuffer * dst_sb = new SampleBuffer( dst_frames ); - sampleFrame * dst_buf = dst_sb->m_origData; // yeah, libsamplerate, let's rock with sinc-interpolation! int error; @@ -1351,11 +1348,10 @@ void SampleBuffer::loadFromBase64( const QString & _data ) #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 ); - + m_origData.resize (dsize / sizeof( sampleFrame )); + memcpy (m_origData.data (), + dst, + m_origData.size ()); #endif delete[] dst; @@ -1456,12 +1452,20 @@ QString SampleBuffer::tryToMakeAbsolute(const QString& file) return file; } +void SampleBuffer::addData(const DataVector::iterator begin, const DataVector::iterator end) { + // First of all, don't let anyone read. + m_varLock.lockForWrite (); + { + // Insert to the end of the vector. + m_data.insert (m_data.end (), begin, end); + m_data.swap (m_origData); + m_data.clear (); + } + m_varLock.unlock (); - - - - + update (); +} SampleBuffer::handleState::handleState( bool _varying_pitch, int interpolation_mode ) : m_frameIndex( 0 ), From f394a593ec7cd029ebe9729e6fa98cbff2231314 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 9 Apr 2018 11:03:07 +0300 Subject: [PATCH 036/112] SampleRecordHandle and SampleTrack: Support in-recording updating and visualizing of a SampleTCO. SampleRecordHandle: Instead of adding buffers to a list and then joining it to new SampleBuffer. Add the in-recording to the new SampleBuffer. SampleTCOView: Make sure our SampleBuffer is not locked, if it is, skip this frame and flag it for update. Also make sure we're getting updated for a changes from SampleBuffer::sampleUpdated. --- include/SampleRecordHandle.h | 8 ++-- include/SampleTrack.h | 3 ++ src/core/SampleRecordHandle.cpp | 65 ++++++++------------------------- src/tracks/SampleTrack.cpp | 19 +++++++++- 4 files changed, 41 insertions(+), 54 deletions(-) diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index cc7ab867511..97efa1ebeec 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -28,14 +28,15 @@ #include #include +#include #include "MidiTime.h" #include "PlayHandle.h" #include "SampleTrack.h" +#include "SampleBuffer.h" class BBTrack; -class SampleBuffer; class SampleTCO; class Track; @@ -52,7 +53,6 @@ class SampleRecordHandle : public PlayHandle virtual bool isFromTrack( const Track * _track ) const; f_cnt_t framesRecorded() const; - void createSampleBuffer( SampleBuffer * * _sample_buf ); private: @@ -70,11 +70,11 @@ class SampleRecordHandle : public PlayHandle virtual void writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ); - typedef QList > bufferList; - bufferList m_buffers; f_cnt_t m_framesRecorded; MidiTime m_minLength; + SampleBuffer::DataVector m_currentBuffer; + Track * m_track; BBTrack * m_bbTrack; SampleTCO * m_tco; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 2a567e43804..26672b2cc6f 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -87,6 +87,9 @@ public slots: void updateTrackTcos(); +private slots: + void onSampleBufferChanged (); + private: SampleBuffer* m_sampleBuffer; BoolModel m_recordModel; diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 5186b2cd3dd..f467a730574 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -52,21 +52,6 @@ SampleRecordHandle::~SampleRecordHandle() // If this is an automatically created tco, // enable resizing. m_tco->setAutoResize (false); - - if( !m_buffers.empty() ) - { - SampleBuffer* sb; - createSampleBuffer( &sb ); - m_tco->setSampleBuffer( sb ); - - m_tco->setStartTimeOffset (m_startRecordTimeOffset); - } - - while( !m_buffers.empty() ) - { - delete[] m_buffers.front().first; - m_buffers.erase( m_buffers.begin() ); - } m_tco->setRecord( false ); } @@ -77,7 +62,19 @@ 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. + if (m_framesRecorded == 0) { + m_tco->setStartTimeOffset (m_startRecordTimeOffset); + m_tco->setSampleBuffer (new SampleBuffer(std::move (m_currentBuffer))); + m_tco->sampleBuffer ()->setSampleRate( Engine::mixer()->inputSampleRate() ); + } else { + if (! m_currentBuffer.empty ()) + m_tco->sampleBuffer ()->addData (m_currentBuffer.begin (), m_currentBuffer.end ()); + } + m_framesRecorded += frames; MidiTime len = (tick_t)( m_framesRecorded / Engine::framesPerTick() ); @@ -112,34 +109,6 @@ f_cnt_t SampleRecordHandle::framesRecorded() const return( m_framesRecorded ); } - - - -void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf ) -{ - const f_cnt_t frames = framesRecorded(); - // create buffer to store all recorded buffers in - sampleFrame * data = new sampleFrame[frames]; - // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; - - - assert( data != NULL ); - - // now copy all buffers into big buffer - for( bufferList::const_iterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) - { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); - data_ptr += ( *it ).second; - } - // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer( data, frames ); - ( *sampleBuf)->setSampleRate( Engine::mixer()->inputSampleRate() ); - delete[] data; -} - void SampleRecordHandle::copyBufferFromMonoLeft(const sampleFrame *inputBuffer, sampleFrame *outputBuffer, const f_cnt_t _frames) @@ -181,7 +150,7 @@ void SampleRecordHandle::copyBufferFromStereo(const sampleFrame *inputBuffer, void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ) { - sampleFrame * buf = new sampleFrame[_frames]; + m_currentBuffer.resize (_frames); // Depending on the recording channel, copy the buffer as a // mono-right, mono-left or stereo. @@ -193,20 +162,18 @@ void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, switch(m_recordingChannel) { case SampleTrack::RecordingChannel::MonoLeft: - copyBufferFromMonoLeft(_ab, buf, _frames); + copyBufferFromMonoLeft(_ab, m_currentBuffer.data (), _frames); break; case SampleTrack::RecordingChannel::MonoRight: - copyBufferFromMonoRight(_ab, buf, _frames); + copyBufferFromMonoRight(_ab, m_currentBuffer.data (), _frames); break; case SampleTrack::RecordingChannel::Stereo: - copyBufferFromStereo(_ab, buf, _frames); + copyBufferFromStereo(_ab, m_currentBuffer.data (), _frames); break; default: Q_ASSERT(false); break; } - - m_buffers.push_back( qMakePair( buf, _frames ) ); } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 41d89586330..756de29f9c8 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -89,6 +89,8 @@ SampleTCO::SampleTCO( Track * _track ) : //care about TCO position connect( this, SIGNAL( positionChanged() ), this, SLOT( updateTrackTcos() ) ); + connect (m_sampleBuffer, SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); + switch( getTrack()->trackContainer()->type() ) { case TrackContainer::BBContainer: @@ -140,6 +142,7 @@ void SampleTCO::setSampleBuffer( SampleBuffer* sb ) sharedObject::unref( m_sampleBuffer ); setStartTimeOffset( 0 ); m_sampleBuffer = sb; + connect (m_sampleBuffer, SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); updateLength(); emit sampleChanged(); @@ -200,6 +203,11 @@ void SampleTCO::updateTrackTcos() } } +void SampleTCO::onSampleBufferChanged() +{ + emit sampleChanged (); +} + @@ -540,7 +548,16 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) float offset = m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact(); QRect r = QRect( TCO_BORDER_WIDTH + offset, spacing, qMax( static_cast( m_tco->sampleLength() * ppt / ticksPerTact ), 1 ), rect().bottom() - 2 * spacing ); - m_tco->m_sampleBuffer->visualize( p, r, pe->rect() ); + + // Make sure our SampleBuffer is not locked, if it is, + // skip this frame and flag it for + if (m_tco->sampleBuffer ()->tryDataReadLock ()) { + m_tco->m_sampleBuffer->visualize( p, r, pe->rect() ); + m_tco->m_sampleBuffer->dataUnlock (); + } else { + // We have not really did much. + setNeedsUpdate (true); + } QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile()); QString filename = fileInfo.fileName(); From e47cdd2a1deb6cd1698ff803cf9ac8035ae6549f Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 29 Apr 2018 22:51:15 +0300 Subject: [PATCH 037/112] SampleBuffer: Work around compilation error with libsamplerate 0.1.8 See the HACK comment --- include/SampleBuffer.h | 10 ++++++++++ src/core/SampleBuffer.cpp | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 793d1b29655..85ddb56b975 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -284,6 +284,16 @@ public slots: void sampleRateChanged(); private: + // HACK: libsamplerate < 0.1.8 doesn't get read-only variables + // as const. It has been fixed in 0.1.9 but has not been + // shipped for some distributions. + // This function just returns a variable that should have + // been `const` as non-const to bypass using 0.1.9. + inline static sampleFrame * libSampleRateSrc (const sampleFrame *ptr) + { + return const_cast(ptr); + } + void update( bool _keep_settings = false ); void convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index c2360c8eac2..09de894bd97 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -675,8 +675,8 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, SRC_DATA src_data; // Generate output src_data.data_in = - getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame )->data (); + libSampleRateSrc(getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame ))->data (); src_data.data_out = _ab->data (); src_data.input_frames = fragment_size; src_data.output_frames = _frames; @@ -1176,7 +1176,7 @@ SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, { SRC_DATA src_data; src_data.end_of_input = 1; - src_data.data_in = data ()->data (); + src_data.data_in = libSampleRateSrc(data ())->data (); src_data.data_out = dst_sb->m_origData.data ()->data (); src_data.input_frames = frames (); src_data.output_frames = dst_frames; From ad3a03e295ec27c48467c091ff3d7fd5b6988d0e Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 7 May 2018 10:51:57 +0300 Subject: [PATCH 038/112] Recording and SampleTrack: Remove setSampleBuffer and introduce resetData. That solves a race condition when one thread calls sampleBuffer() and it gets destructed by another thread that calls setSampleBuffer. --- include/SampleBuffer.h | 4 +++- include/SampleTrack.h | 1 - src/core/SampleBuffer.cpp | 11 +++++++++++ src/core/SampleRecordHandle.cpp | 10 +++++++--- src/tracks/SampleTrack.cpp | 16 ++-------------- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 85ddb56b975..2c96827c5fd 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -273,7 +273,9 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject * @warning That locks m_varLock for write. */ void addData(const DataVector::iterator begin, const DataVector::iterator end); - + + void resetData(DataVector &&newData); + public slots: void setAudioFile( const QString & _audio_file ); void loadFromBase64( const QString & _data ); diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 26672b2cc6f..1d578197205 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -79,7 +79,6 @@ class SampleTCO : public TrackContentObject bool isEmpty() const; public slots: - void setSampleBuffer( SampleBuffer* sb ); void setSampleFile( const QString & _sf ); void updateLength(); void toggleRecord(); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 09de894bd97..6b216a3e155 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -1467,6 +1467,17 @@ void SampleBuffer::addData(const DataVector::iterator begin, const DataVector::i update (); } +void SampleBuffer::resetData(DataVector &&newData) { + m_varLock.lockForWrite (); + { + m_data.clear (); + m_origData = std::move (newData); + } + m_varLock.unlock (); + + update (); +} + SampleBuffer::handleState::handleState( bool _varying_pitch, int interpolation_mode ) : m_frameIndex( 0 ), m_varyingPitch( _varying_pitch ), diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index f467a730574..73aed4af097 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -49,6 +49,8 @@ SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTime SampleRecordHandle::~SampleRecordHandle() { + m_tco->updateLength (); + // If this is an automatically created tco, // enable resizing. m_tco->setAutoResize (false); @@ -67,12 +69,15 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) // It is the first buffer. if (m_framesRecorded == 0) { + // Make sure we don't have the previous data. + m_tco->sampleBuffer ()->resetData(std::move (m_currentBuffer)); m_tco->setStartTimeOffset (m_startRecordTimeOffset); - m_tco->setSampleBuffer (new SampleBuffer(std::move (m_currentBuffer))); + m_tco->sampleBuffer ()->setSampleRate( Engine::mixer()->inputSampleRate() ); } else { - if (! m_currentBuffer.empty ()) + if (! m_currentBuffer.empty ()) { m_tco->sampleBuffer ()->addData (m_currentBuffer.begin (), m_currentBuffer.end ()); + } } m_framesRecorded += frames; @@ -80,7 +85,6 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) MidiTime len = (tick_t)( m_framesRecorded / Engine::framesPerTick() ); if( len > m_minLength ) { -// m_tco->changeLength( len ); m_minLength = len; } } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 756de29f9c8..c27eb292f42 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -137,19 +137,6 @@ const QString & SampleTCO::sampleFile() const -void SampleTCO::setSampleBuffer( SampleBuffer* sb ) -{ - sharedObject::unref( m_sampleBuffer ); - setStartTimeOffset( 0 ); - m_sampleBuffer = sb; - connect (m_sampleBuffer, SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); - updateLength(); - - emit sampleChanged(); -} - - - void SampleTCO::setSampleFile( const QString & _sf ) { int length; @@ -631,7 +618,8 @@ SampleTrack::~SampleTrack() bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _offset, int _tco_num ) { - m_audioPort.effects()->startRunning(); + if (m_audioPort.effects()) + m_audioPort.effects()->startRunning(); bool played_a_note = false; // will be return variable tcoVector tcos; From 7e9a578bf9a136568abd87f9d332cb55a9b5155b Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 7 May 2018 22:34:21 +0300 Subject: [PATCH 039/112] MemoryManager: Add == and != operators for MmAllocator. --- include/MemoryManager.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index 4c225e026a9..e4c5fa3efe4 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -65,6 +65,18 @@ struct MmAllocator }; +template< class T1, class T2 > +constexpr bool operator==( const MmAllocator&, const MmAllocator&) +{ + return true; +} + +template< class T1, class T2 > +constexpr bool operator!=( const MmAllocator&, const MmAllocator&) +{ + return false; +} + #define MM_OPERATORS \ public: \ static void * operator new ( size_t size ) \ From ee5981d293bc7705df8ab5a4a8140ceb445012c1 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 9 May 2018 00:22:04 +0300 Subject: [PATCH 040/112] Recording: Don't apply masterGain for input frames. --- include/Mixer.h | 5 +---- src/core/Mixer.cpp | 18 +----------------- src/core/audio/AudioJack.cpp | 3 +-- src/core/audio/AudioPulseAudio.cpp | 3 +-- 4 files changed, 4 insertions(+), 25 deletions(-) diff --git a/include/Mixer.h b/include/Mixer.h index 90d0e3ec64b..fab7f442c16 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -290,7 +290,7 @@ class LMMS_EXPORT Mixer : public QObject return m_fifoWriter != NULL; } - void pushInputFrames(const sampleFrame *_ab, const f_cnt_t _frames , bool shouldApplyMasterGain=false); + void pushInputFrames(const sampleFrame *_ab, const f_cnt_t _frames); inline const sampleFrame * inputBuffer() { @@ -312,9 +312,6 @@ class LMMS_EXPORT Mixer : public QObject inline bool isMetronomeActive() const { return m_metronomeActive; } inline void setMetronomeActive(bool value = true) { m_metronomeActive = value; } - void applyMasterGainToInputBuffer (sampleFrame *frames_data, - const size_t frames_count, uint channels_count, float gain); - void requestChangeInModel(); void doneChangeInModel(); diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 0ffb91c4e5f..43bfcb79315 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -309,7 +309,7 @@ bool Mixer::criticalXRuns() const -void Mixer::pushInputFrames(const sampleFrame * _ab, const f_cnt_t _frames, bool shouldApplyMasterGain) { +void Mixer::pushInputFrames(const sampleFrame * _ab, const f_cnt_t _frames) { requestChangeInModel(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; @@ -331,11 +331,6 @@ void Mixer::pushInputFrames(const sampleFrame * _ab, const f_cnt_t _frames, bool memcpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); - if (!shouldApplyMasterGain) { - applyMasterGainToInputBuffer (&buf[ frames ], _frames, DEFAULT_CHANNELS, - masterGain ()); - } - m_inputBufferFrames[ m_inputBufferWrite ] += _frames; doneChangeInModel(); @@ -580,17 +575,6 @@ void Mixer::changeQuality( const struct qualitySettings & _qs ) startProcessing(); } -void Mixer::applyMasterGainToInputBuffer(sampleFrame *frames_data, const size_t frames_count, uint channels_count, - float gain) { - for (fpp_t f = 0; f < frames_count; ++f) { - for (uint channel = 0; channel < channels_count; ++channel) { - frames_data[f][channel] /= gain; - } - - } -} - - void Mixer::setAudioDevice( AudioDevice * _dev, diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index d87f74e7210..a8c4920bf06 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -437,13 +437,12 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) if (! m_stopped) { m_inBuffer.reset (new sampleFrame[_nframes]); - const float gain = mixer()->masterGain(); for( int c = 0; c < channels(); ++c ) { jack_default_audio_sample_t *channel_buffer = m_tempInBufs[c]; for( jack_nframes_t frame = 0; frame < _nframes; ++frame ) { - m_inBuffer[frame][c] = channel_buffer[frame] / gain; + m_inBuffer[frame][c] = channel_buffer[frame]; } } diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index ada82d12c67..c1ebbaa3272 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -353,8 +353,7 @@ void AudioPulseAudio::streamReadCallback(pa_stream *s, size_t length) { if (buffer_size && buffer) { mixer()->pushInputFrames (buffer, - frames, - true); + frames); } length -= buffer_size; From 7118380023f84ab66093b3ac992a7138a5c9cd5a Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 9 May 2018 00:23:45 +0300 Subject: [PATCH 041/112] SampleTCO -> recording: Export is_record attribute for future loading. --- src/tracks/SampleTrack.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index c27eb292f42..4c138699770 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -272,6 +272,7 @@ void SampleTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) } _this.setAttribute ("sample_rate", m_sampleBuffer->sampleRate ()); + _this.setAttribute ("is_record", isRecord ()); // TODO: start- and end-frame } @@ -296,6 +297,10 @@ void SampleTCO::loadSettings( const QDomElement & _this ) if (_this.hasAttribute ("sample_rate")) { m_sampleBuffer->setSampleRate (_this.attribute ("sample_rate").toInt ()); } + + if (_this.hasAttribute ("is_record")) { + setRecord (_this.attribute ("is_record").toInt ()); + } } From d588c56a991c77254ec539a9f267254b8aedaaae Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 9 May 2018 10:15:42 +0300 Subject: [PATCH 042/112] SampleBuffer: fix resampleing error: null output data. --- include/SampleBuffer.h | 4 +--- src/core/SampleBuffer.cpp | 26 ++++++++++---------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 2c96827c5fd..45e6c48a045 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -115,8 +115,6 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject explicit SampleBuffer( const f_cnt_t _frames ); SampleBuffer(SampleBuffer::DataVector &&movedData); - virtual ~SampleBuffer(); - bool play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, const float _freq, @@ -225,7 +223,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject // protect calls from the GUI to this function with dataReadLock() and // dataUnlock() - SampleBuffer * resample( const sample_rate_t _src_sr, + SampleBuffer resample( const sample_rate_t _src_sr, const sample_rate_t _dst_sr ); void normalizeSampleRate( const sample_rate_t _src_sr, diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 6b216a3e155..49adee68747 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -139,9 +139,6 @@ SampleBuffer::SampleBuffer( const f_cnt_t _frames ) -SampleBuffer::~SampleBuffer() -{ -} @@ -248,7 +245,7 @@ void SampleBuffer::update(bool _keep_settings) { // sample couldn't be decoded, create buffer containing // one sample-frame - m_data.resize (1, {0,0}); + m_data.clear (); m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = 1; } @@ -260,9 +257,7 @@ void SampleBuffer::update(bool _keep_settings) } else { - // neither an audio-file nor a buffer to copy from, so create - // buffer containing one sample-frame - m_data.resize (1, {0,0}); + m_data.clear (); m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = 1; } @@ -372,10 +367,9 @@ void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, // do samplerate-conversion to our default-samplerate if( _src_sr != Engine::mixer()->baseSampleRate() ) { - SampleBuffer * resampled = resample( _src_sr, + SampleBuffer resampled = resample( _src_sr, Engine::mixer()->baseSampleRate() ); - m_data = std::move(resampled->m_data); - delete resampled; + m_data = std::move(resampled.m_data); } if( _keep_settings == false ) @@ -1161,12 +1155,12 @@ QString & SampleBuffer::toBase64( QString & _dst ) const -SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ) +std::unique_ptr SampleBuffer::resample(const sample_rate_t _src_sr, + const sample_rate_t _dst_sr ) { const f_cnt_t dst_frames = static_cast( frames ()/ (float) _src_sr * (float) _dst_sr ); - SampleBuffer * dst_sb = new SampleBuffer( dst_frames ); + DataVector outputData(dst_frames); // yeah, libsamplerate, let's rock with sinc-interpolation! int error; @@ -1177,7 +1171,7 @@ SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, SRC_DATA src_data; src_data.end_of_input = 1; src_data.data_in = libSampleRateSrc(data ())->data (); - src_data.data_out = dst_sb->m_origData.data ()->data (); + src_data.data_out = outputData.data ()->data(); src_data.input_frames = frames (); src_data.output_frames = dst_frames; src_data.src_ratio = (double) _dst_sr / _src_sr; @@ -1192,8 +1186,8 @@ SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, { printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); } - dst_sb->update(); - return dst_sb; + + return std::unique_ptr(new SampleBuffer(std::move(outputData))); } From 1b7660d4683945b4cee8ad12d8bdd7b8dd60182e Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 17 May 2018 09:37:33 +0300 Subject: [PATCH 043/112] Cleanup for next commit: Remove the unused AudioSampleRecorder. --- include/AudioSampleRecorder.h | 59 ------------- src/core/CMakeLists.txt | 1 - src/core/audio/AudioSampleRecorder.cpp | 114 ------------------------- src/tracks/Pattern.cpp | 1 - 4 files changed, 175 deletions(-) delete mode 100644 include/AudioSampleRecorder.h delete mode 100644 src/core/audio/AudioSampleRecorder.cpp diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h deleted file mode 100644 index 69ac1949047..00000000000 --- a/include/AudioSampleRecorder.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * AudioSampleRecorder.h - device-class that implements recording - * audio-buffers into RAM - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * 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. - * - */ - -#ifndef AUDIO_SAMPLE_RECORDER_H -#define AUDIO_SAMPLE_RECORDER_H - -#include -#include - -#include "AudioDevice.h" - -class SampleBuffer; - - -class AudioSampleRecorder : public AudioDevice -{ -public: - AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, - Mixer* mixer ); - virtual ~AudioSampleRecorder(); - - f_cnt_t framesRecorded() const; - void createSampleBuffer( SampleBuffer** sampleBuffer ); - - -private: - virtual void writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - const float _master_gain ); - - typedef QList > BufferList; - BufferList m_buffers; - -} ; - - -#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ba41e089c7a..d894190b4c5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -85,7 +85,6 @@ set(LMMS_SRCS core/audio/AudioPortAudio.cpp core/audio/AudioSoundIo.cpp core/audio/AudioPulseAudio.cpp - core/audio/AudioSampleRecorder.cpp core/audio/AudioSdl.cpp core/midi/MidiAlsaRaw.cpp diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp deleted file mode 100644 index a36e0e9da06..00000000000 --- a/src/core/audio/AudioSampleRecorder.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * AudioSampleRecorder.cpp - device-class that implements recording - * audio-buffers into RAM - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * 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 "AudioSampleRecorder.h" -#include "SampleBuffer.h" -#include "debug.h" - - - -AudioSampleRecorder::AudioSampleRecorder( const ch_cnt_t _channels, - bool & _success_ful, - Mixer * _mixer ) : - AudioDevice( _channels, _mixer ), - m_buffers() -{ - _success_ful = true; -} - - - - -AudioSampleRecorder::~AudioSampleRecorder() -{ - while( !m_buffers.empty() ) - { - delete[] m_buffers.front().first; - m_buffers.erase( m_buffers.begin() ); - } -} - - - - -f_cnt_t AudioSampleRecorder::framesRecorded() const -{ - f_cnt_t frames = 0; - for( BufferList::ConstIterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) - { - frames += ( *it ).second; - } - return frames; -} - - - - -void AudioSampleRecorder::createSampleBuffer( SampleBuffer** sampleBuf ) -{ - const f_cnt_t frames = framesRecorded(); - // create buffer to store all recorded buffers in - sampleFrame * data = new sampleFrame[frames]; - // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; - - - assert( data != NULL ); - - // now copy all buffers into big buffer - for( BufferList::ConstIterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) - { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); - data_ptr += ( *it ).second; - } - // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer( data, frames ); - ( *sampleBuf )->setSampleRate( sampleRate() ); - delete[] data; -} - - - - -void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, const float ) -{ - sampleFrame * buf = new sampleFrame[_frames]; - for( fpp_t frame = 0; frame < _frames; ++frame ) - { - for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) - { - buf[frame][chnl] = _ab[frame][chnl]; - } - } - m_buffers.push_back( qMakePair( buf, _frames ) ); -} - - - diff --git a/src/tracks/Pattern.cpp b/src/tracks/Pattern.cpp index 32baf0b1484..c24a5dbf4b6 100644 --- a/src/tracks/Pattern.cpp +++ b/src/tracks/Pattern.cpp @@ -37,7 +37,6 @@ #include "PianoRoll.h" #include "RenameDialog.h" #include "SampleBuffer.h" -#include "AudioSampleRecorder.h" #include "BBTrackContainer.h" #include "StringPairDrag.h" #include "MainWindow.h" From a5b684cd3b16e4b51a2619b528e16b2040214420 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 18 May 2018 12:39:57 +0300 Subject: [PATCH 044/112] SampleBuffer: Remove the god function "update" and split it into a number of functions: - beginBufferChange, doneBufferChange: Guards for direct access to m_data. They will also resample the data if it is needed. Functions like `addData` will check additional parameter to know if we should lock the mixer in this context. - Add reverse and remove the attribute m_reversed: The only user was AudioFileProcessor and it added complexity for the file loading process. Right now we'll just `reverse` the buffer manually. - changeAudioFile: Will reset the current buffer and load a file into it. - Remove the raw constructor `SampleBuffer(sampleFrame*, f_cnt_t)`: It was used only once and could easily be converted to another constructor. --- include/SampleBuffer.h | 76 +- .../audio_file_processor.cpp | 20 +- .../audio_file_processor.h | 16 +- plugins/patman/patman.cpp | 6 +- src/core/SampleBuffer.cpp | 833 ++++++------------ src/core/SampleRecordHandle.cpp | 10 +- src/tracks/SampleTrack.cpp | 4 +- 7 files changed, 350 insertions(+), 615 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 45e6c48a045..fa350e58618 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -111,9 +111,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject // constructor which either loads sample _audio_file or decodes // base64-data out of string SampleBuffer( const QString & _audio_file, bool _is_base64_data = false ); - SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ); - explicit SampleBuffer( const f_cnt_t _frames ); - SampleBuffer(SampleBuffer::DataVector &&movedData); + SampleBuffer(DataVector &&movedData, sample_rate_t sampleRate); bool play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, @@ -179,11 +177,6 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject return m_amplification; } - inline bool reversed() const - { - return m_reversed; - } - inline float frequency() const { return m_frequency; @@ -220,12 +213,6 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject QString & toBase64( QString & _dst ) const; - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock() - SampleBuffer resample( const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ); - void normalizeSampleRate( const sample_rate_t _src_sr, bool _keep_settings = false ); @@ -267,23 +254,37 @@ class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject * @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. */ - void addData(const DataVector::iterator begin, const DataVector::iterator end); + void addData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true); - void resetData(DataVector &&newData); + /** + * @brief Reset the class and initialize it with @a newData. + * @param newData mm, that's the new data. + * @param dataSampleRate Sample rate for @a newData. + * @param shouldLockMixer Should we call requestChangeInModel? + */ + void resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true); + + /** + * @brief Just reverse the current buffer. + * @param shouldLockMixer Should we call requestChangeInModel? + * + * This function simply calls `std::reverse` on m_data. + */ + void reverse(bool shouldLockMixer=true); public slots: void setAudioFile( const QString & _audio_file ); - void loadFromBase64( const QString & _data ); + void loadFromBase64(const QString & _data , bool shouldLock); void setStartFrame( const f_cnt_t _s ); void setEndFrame( const f_cnt_t _e ); void setAmplification( float _a ); - void setReversed( bool _on ); void sampleRateChanged(); -private: +protected: // HACK: libsamplerate < 0.1.8 doesn't get read-only variables // as const. It has been fixed in 0.1.9 but has not been // shipped for some distributions. @@ -294,22 +295,17 @@ public slots: return const_cast(ptr); } - void update( bool _keep_settings = false ); + void changeAudioFile (QString audioFile, bool shouldLock, bool shouldKeepSettings); - void convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); - void directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _channels); + static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); - f_cnt_t decodeSampleSF( QString _f, sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _sample_rate ); + static DataVector decodeSampleSF( QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); #ifdef LMMS_HAVE_OGGVORBIS - f_cnt_t decodeSampleOGGVorbis( QString _f, int_sample_t * & _buf, + static DataVector decodeSampleOGGVorbis( QString _f, ch_cnt_t & _channels, - sample_rate_t & _sample_rate ); + sample_rate_t & _sample_rate); #endif - f_cnt_t decodeSampleDS( QString _f, int_sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _sample_rate ); + static DataVector decodeSampleDS( QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); inline sampleFrame * data() { @@ -317,7 +313,6 @@ public slots: } QString m_audioFile; - DataVector m_origData; DataVector m_data; QReadWriteLock m_varLock; f_cnt_t m_startFrame; @@ -325,7 +320,6 @@ public slots: f_cnt_t m_loopStartFrame; f_cnt_t m_loopEndFrame; float m_amplification; - bool m_reversed; float m_frequency; sample_rate_t m_sampleRate; @@ -339,6 +333,24 @@ public slots: f_cnt_t getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const; + static DataVector resampleData(const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t requiredSampleRate); + + /** + * @brief Do the actions necessary before changing m_data. + * @param shouldLock Is anyone else might be using m_data? + */ + void beginBufferChange (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. + * @param shouldKeepSettings Should we keep playback settings? + * @param bufferSampleRate The new m_data's sample rate. + */ + void doneBufferChange (bool shouldUnlock, + bool shouldKeepSettings, + sample_rate_t bufferSampleRate, + bool shouldUnlockMixer=true); signals: void sampleUpdated(); diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index c1cd725c10e..c939c245e0d 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -247,7 +247,7 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } else if( _this.attribute( "sampledata" ) != "" ) { - m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) ); + m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) , true); } m_loopModel.loadSettings( _this, "looped" ); @@ -267,6 +267,8 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } m_reverseModel.loadSettings( _this, "reversed" ); + // The current state of m_sampleBuffer. + m_isCurrentlyReversed = m_reverseModel.value (); m_stutterModel.loadSettings( _this, "stutter" ); if( _this.hasAttribute( "interp" ) ) @@ -343,7 +345,12 @@ void audioFileProcessor::setAudioFile( const QString & _audio_file, void audioFileProcessor::reverseModelChanged( void ) { - m_sampleBuffer.setReversed( m_reverseModel.value() ); + if (m_reverseModel.value () != m_isCurrentlyReversed) { + m_isCurrentlyReversed = m_reverseModel.value (); + + m_sampleBuffer.reverse (true); + } + m_nextPlayStartPoint = m_sampleBuffer.startFrame(); m_nextPlayBackwards = false; } @@ -602,7 +609,7 @@ void AudioFileProcessorView::newWaveView() delete m_waveView; m_waveView = 0; } - m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer ); + m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer, castModel()); m_waveView->move( 2, 172 ); m_waveView->setKnobs( dynamic_cast( m_startKnob ), @@ -729,8 +736,10 @@ void AudioFileProcessorWaveView::updateSampleRange() } } -AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf ) : +AudioFileProcessorWaveView::AudioFileProcessorWaveView(QWidget * _parent, int _w, int _h, SampleBuffer& buf , + audioFileProcessor *fileProcessor) : QWidget( _parent ), + m_audioFileProcessor{fileProcessor}, m_sampleBuffer( buf ), m_graph( QPixmap( _w - 2 * s_padding, _h - 2 * s_padding ) ), m_from( 0 ), @@ -1004,8 +1013,7 @@ void AudioFileProcessorWaveView::updateGraph() m_to = m_sampleBuffer.endFrame(); } - if( m_sampleBuffer.reversed() != m_reversed ) - { + if(m_audioFileProcessor->isReversed () != m_reversed) { reverse(); } else if( m_last_from == m_from && m_last_to == m_to && m_sampleBuffer.amplification() == m_last_amp ) diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index d17be147c0c..ef213ed2498 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -66,6 +66,10 @@ class audioFileProcessor : public Instrument virtual PluginView * instantiateView( QWidget * _parent ); + bool isReversed () const + { + return m_isCurrentlyReversed; + } public slots: void setAudioFile( const QString & _audio_file, bool _rename = true ); @@ -102,6 +106,15 @@ private slots: f_cnt_t m_nextPlayStartPoint; bool m_nextPlayBackwards; + /** + * @brief Is currently m_sampleBuffer reversed? + * + * Since m_sampleBuffer has no longer the `reversed` + * attribute, we track it here; m_reverseModel is + * only what the user want it to be. + */ + bool m_isCurrentlyReversed = false; + friend class AudioFileProcessorView; } ; @@ -235,6 +248,7 @@ public slots: sample_loop } ; + audioFileProcessor *m_audioFileProcessor; SampleBuffer& m_sampleBuffer; QPixmap m_graph; f_cnt_t m_from; @@ -256,7 +270,7 @@ public slots: bool m_animation; public: - AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf ); + AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf, audioFileProcessor *fileProcessor); void setKnobs(knob *_start, knob *_end, knob *_loop ); diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index 0ea9968dae7..df5a2995993 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -345,7 +345,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( } } - sampleFrame * data = new sampleFrame[frames]; + SampleBuffer::DataVector data(frames); for( f_cnt_t frame = 0; frame < frames; ++frame ) { @@ -356,9 +356,8 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( } } - SampleBuffer* psample = new SampleBuffer( data, frames ); + SampleBuffer* psample = new SampleBuffer( std::move(data), sample_rate ); psample->setFrequency( root_freq / 1000.0f ); - psample->setSampleRate( sample_rate ); if( modes & MODES_LOOPING ) { @@ -369,7 +368,6 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( m_patchSamples.push_back( psample ); delete[] wave_samples; - delete[] data; } fclose( fd ); return( LoadOK ); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 49adee68747..5e3d8d2bad5 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -39,15 +39,6 @@ #include #endif -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -#include -#endif - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H -#include -#endif - - #include "base64.h" #include "ConfigManager.h" #include "DrumSynth.h" @@ -67,13 +58,15 @@ SampleBuffer::SampleBuffer() : m_loopStartFrame( 0 ), m_loopEndFrame( 0 ), m_amplification( 1.0f ), - m_reversed( false ), m_frequency( BaseFreq ), m_sampleRate( Engine::mixer()->baseSampleRate() ) { connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); - update(); + beginBufferChange (false); + doneBufferChange (false, /* shouldLock */ + false, /* shouldKeepSettings */ + m_sampleRate); } @@ -84,202 +77,136 @@ SampleBuffer::SampleBuffer( const QString & _audio_file, { if( _is_base64_data ) { - loadFromBase64( _audio_file ); + loadFromBase64( _audio_file, false ); } else { - m_audioFile = _audio_file; - update(); + changeAudioFile (_audio_file, false, false); } } - - -SampleBuffer::SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ) - : SampleBuffer() +SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t sampleRate) : + SampleBuffer() { - if( _frames > 0 ) - { - m_origData.assign (_data, _data + _frames); - } + beginBufferChange (false); + setSampleRate (sampleRate); + m_data = std::move (movedData); + doneBufferChange ( + /* shouldLock */ false, + /* shouldKeepSettings */ false, + sampleRate + ); } -SampleBuffer::SampleBuffer( SampleBuffer::DataVector &&movedData ) : - m_audioFile( "" ), - m_startFrame( 0 ), - m_endFrame( 0 ), - m_loopStartFrame( 0 ), - m_loopEndFrame( 0 ), - m_amplification( 1.0f ), - m_reversed( false ), - m_frequency( BaseFreq ), - m_sampleRate( Engine::mixer()->baseSampleRate() ) -{ - m_origData = std::move(movedData); - connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); - update(); -} +void SampleBuffer::sampleRateChanged() { + auto previousSampleRate = sampleRate (); + if (Engine::mixer ()->baseSampleRate () == sampleRate ()) + return; + beginBufferChange (true); + setSampleRate (Engine::mixer ()->baseSampleRate ()); -SampleBuffer::SampleBuffer( const f_cnt_t _frames ) - : SampleBuffer( - DataVector(static_cast(_frames), - sampleFrame{0,0}) - ) -{ - if( _frames > 0 ) - { - m_data.resize (_frames, {0,0}); - } + // Resample the buffer. + doneBufferChange ( + /* shouldLock */ true, + /* shouldKeepSettings */ true, + previousSampleRate + ); } - - - - - - -void SampleBuffer::sampleRateChanged() +void SampleBuffer::changeAudioFile(QString audioFile, bool shouldLock, + bool shouldKeepSettings) { - update( true ); -} - - -void SampleBuffer::update(bool _keep_settings) -{ - const bool lock = ( m_data.size () != 0); - if( lock ) - { - Engine::mixer()->requestChangeInModel(); - m_varLock.lockForWrite(); - } + if (audioFile == "") + return; // 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.size () > 0 ) + QString file = tryToMakeAbsolute( audioFile ); + + ch_cnt_t channels = DEFAULT_CHANNELS; + sample_rate_t samplerate = Engine::mixer()->baseSampleRate(); + DataVector fileData; + + const QFileInfo fileInfo( file ); + if( fileInfo.size() > fileSizeMax * 1024 * 1024 ) { - // TODO: reverse- and amplification-property is not covered - // by following code... - auto previousFrames = frames (); - m_data = std::move(m_origData); - if( _keep_settings == false ) - { - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = frames (); - } else { - m_data.resize (previousFrames); - } + fileLoadError = true; } - else if( !m_audioFile.isEmpty() ) - { - QString file = tryToMakeAbsolute( m_audioFile ); - int_sample_t * buf = NULL; - sample_t * fbuf = NULL; - ch_cnt_t channels = DEFAULT_CHANNELS; - sample_rate_t samplerate = Engine::mixer()->baseSampleRate(); - f_cnt_t dataFrames = 0; - - const QFileInfo fileInfo( file ); - if( fileInfo.size() > fileSizeMax * 1024 * 1024 ) - { - fileLoadError = true; - } - else + + if (!fileLoadError) { + // Use QFile to handle unicode file names on Windows + QFile f(file); + f.open(QIODevice::ReadOnly); + SNDFILE * snd_file; + SF_INFO sf_info; + sf_info.format = 0; + if( ( snd_file = sf_open_fd( f.handle(), SFM_READ, &sf_info, false ) ) != NULL ) { - // Use QFile to handle unicode file names on Windows - QFile f(file); - f.open(QIODevice::ReadOnly); - SNDFILE * snd_file; - SF_INFO sf_info; - sf_info.format = 0; - if( ( snd_file = sf_open_fd( f.handle(), SFM_READ, &sf_info, false ) ) != NULL ) + f_cnt_t frames = sf_info.frames; + int rate = sf_info.samplerate; + if( frames / rate > sampleLengthMax * 60 ) { - f_cnt_t frames = sf_info.frames; - int rate = sf_info.samplerate; - if( frames / rate > sampleLengthMax * 60 ) - { - fileLoadError = true; - } - sf_close( snd_file ); + fileLoadError = true; } - f.close(); + sf_close( snd_file ); } + f.close(); + } + + if( !fileLoadError ) { - 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( dataFrames == 0 && fileInfo.suffix() == "ogg" ) - { - dataFrames = decodeSampleOGGVorbis( file, buf, channels, samplerate ); - } -#endif - if( dataFrames == 0 ) - { - dataFrames = decodeSampleSF( file, fbuf, channels, - samplerate ); - } #ifdef LMMS_HAVE_OGGVORBIS - if( dataFrames == 0 ) - { - dataFrames = decodeSampleOGGVorbis( file, buf, channels, - samplerate ); - } + // 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( fileInfo.suffix() == "ogg" ) + { + fileData = decodeSampleOGGVorbis( file, channels, samplerate ); + } #endif - if( dataFrames == 0 ) - { - dataFrames = decodeSampleDS( file, buf, channels, - samplerate ); - } + if(fileData.empty ()) + { + fileData = decodeSampleSF( file, channels, samplerate ); } - - if ( dataFrames == 0 || fileLoadError ) // if still no frames, bail +#ifdef LMMS_HAVE_OGGVORBIS + if( fileData.empty () ) { - // sample couldn't be decoded, create buffer containing - // one sample-frame - m_data.clear (); - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; + fileData = decodeSampleOGGVorbis( file, channels, samplerate ); } - else // otherwise normalize sample rate +#endif + if( fileData.empty () ) { - m_data.resize (dataFrames); - normalizeSampleRate( samplerate, _keep_settings ); + fileData = decodeSampleDS( file, channels, samplerate ); } } - else - { - m_data.clear (); - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; - } - if( lock ) - { - m_varLock.unlock(); - Engine::mixer()->doneChangeInModel(); + if (fileData.empty ()) { + fileLoadError = true; } - emit sampleUpdated(); - - if( fileLoadError ) - { + if (! fileLoadError) { + // Ok, we have no errors; let's update the actual data. + beginBufferChange (shouldLock); + m_data = std::move(fileData); + doneBufferChange (shouldLock, + shouldKeepSettings, + samplerate); + } else { 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 ); + "in size and %2 minutes of playing time" + ).arg( fileSizeMax ).arg( sampleLengthMax ); if( gui ) { QMessageBox::information( NULL, - title, message, QMessageBox::Ok ); + title, message, QMessageBox::Ok ); } else { @@ -288,76 +215,26 @@ void SampleBuffer::update(bool _keep_settings) } } - -void SampleBuffer::convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, int _channels) +SampleBuffer::DataVector 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.resize (_frames); + DataVector vector(_frames); const int ch = ( _channels > 1 ) ? 1 : 0; - // if reversing is on, we also reverse when - // scaling - if( m_reversed ) + int idx = 0; + for( f_cnt_t frame = 0; frame < _frames; + ++frame ) { - int idx = ( _frames - 1 ) * _channels; - 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 -= _channels; - } - } - else - { - int idx = 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 += _channels; - } + vector[frame][0] = _ibuf[idx+0] * fac; + vector[frame][1] = _ibuf[idx+ch] * fac; + idx += _channels; } delete[] _ibuf; -} - -void SampleBuffer::directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _channels) - -{ - - m_data.resize (_frames); - const int ch = ( _channels > 1 ) ? 1 : 0; - - // if reversing is on, we also reverse when - // scaling - if( m_reversed ) - { - int idx = ( _frames - 1 ) * _channels; - for( f_cnt_t frame = 0; frame < _frames; - ++frame ) - { - m_data[frame][0] = _fbuf[idx+0]; - m_data[frame][1] = _fbuf[idx+ch]; - idx -= _channels; - } - } - else - { - int idx = 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 += _channels; - } - } - delete[] _fbuf; + return vector; } @@ -365,11 +242,9 @@ void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, bool _keep_settings ) { // do samplerate-conversion to our default-samplerate - if( _src_sr != Engine::mixer()->baseSampleRate() ) + if( _src_sr != sampleRate ()) { - SampleBuffer resampled = resample( _src_sr, - Engine::mixer()->baseSampleRate() ); - m_data = std::move(resampled.m_data); + m_data = resampleData (m_data, _src_sr, sampleRate ()); } if( _keep_settings == false ) @@ -381,17 +256,52 @@ void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, } +SampleBuffer::DataVector +SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSampleRate, + sample_rate_t requiredSampleRate) +{ + const f_cnt_t dst_frames = static_cast( inputData.size ()/ + (float) inputSampleRate * (float) requiredSampleRate ); + DataVector outputData(dst_frames); + + // yeah, libsamplerate, let's rock with sinc-interpolation! + int error; + SRC_STATE * state; + if( ( state = src_new( SRC_SINC_MEDIUM_QUALITY, + DEFAULT_CHANNELS, &error ) ) != NULL ) + { + SRC_DATA src_data; + src_data.end_of_input = 1; + src_data.data_in = libSampleRateSrc(inputData.data ())->data (); + src_data.data_out = outputData.data ()->data(); + src_data.input_frames = inputData.size (); + src_data.output_frames = dst_frames; + src_data.src_ratio = (double) inputSampleRate / requiredSampleRate; + if( ( error = src_process( state, &src_data ) ) ) + { + printf( "SampleBuffer: error while resampling: %s\n", + src_strerror( error ) ); + } + src_delete( state ); + } + else + { + printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); + } + + return outputData; +} -f_cnt_t SampleBuffer::decodeSampleSF(QString _f, - sample_t * & _buf, +SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, ch_cnt_t & _channels, - sample_rate_t & _samplerate ) + sample_rate_t &_samplerate) { SNDFILE * snd_file; SF_INFO sf_info; sf_info.format = 0; f_cnt_t frames = 0; + DataVector vector; bool sf_rr = false; @@ -401,9 +311,20 @@ f_cnt_t SampleBuffer::decodeSampleSF(QString _f, if( ( snd_file = sf_open_fd( f.handle(), SFM_READ, &sf_info, false ) ) != NULL ) { frames = sf_info.frames; + vector.resize (frames); + sf_rr = sf_read_float( snd_file, vector.data ()->data (), DEFAULT_CHANNELS * frames ); - _buf = new sample_t[sf_info.channels * frames]; - sf_rr = sf_read_float( snd_file, _buf, sf_info.channels * frames ); + if (sf_info.channels != DEFAULT_CHANNELS) { +#ifdef DEBUG_LMMS + qDebug( "SampleBuffer::decodeSampleSF(): Not a stereo file: %s: %s", _f, sf_strerror( NULL ) ); +#endif + + _channels = sf_info.channels; + _samplerate = sf_info.samplerate; + + sf_close( snd_file ); + return vector; + } if( sf_rr < sf_info.channels * frames ) { @@ -426,14 +347,7 @@ f_cnt_t SampleBuffer::decodeSampleSF(QString _f, } f.close(); - //write down either directly or convert i->f depending on file type - - if ( frames > 0 && _buf != NULL ) - { - directFloatWrite ( _buf, frames, _channels); - } - - return frames; + return vector; } @@ -491,10 +405,9 @@ long qfileTellCallback( void * _udata ) -f_cnt_t SampleBuffer::decodeSampleOGGVorbis( QString _f, - int_sample_t * & _buf, +SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(QString _f, ch_cnt_t & _channels, - sample_rate_t & _samplerate ) + sample_rate_t & _samplerate) { static ov_callbacks callbacks = { @@ -512,7 +425,7 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( QString _f, if( f->open( QFile::ReadOnly ) == false ) { delete f; - return 0; + return {}; } int err = ov_open_callbacks( f, &vf, NULL, 0, callbacks ); @@ -543,7 +456,7 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( QString _f, break; } delete f; - return 0; + return {}; } ov_pcm_seek( &vf, 0 ); @@ -553,7 +466,7 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( QString _f, ogg_int64_t total = ov_pcm_total( &vf, -1 ); - _buf = new int_sample_t[total * _channels]; + auto _buf = new int_sample_t[total * _channels]; int bitstream = 0; long bytes_read = 0; @@ -577,30 +490,29 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( QString _f, if ( frames > 0 && _buf != NULL ) { - convertIntToFloat ( _buf, frames, _channels); + return convertIntToFloat ( _buf, frames, _channels); } - return frames; + return {}; } #endif -f_cnt_t SampleBuffer::decodeSampleDS( QString _f, - int_sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _samplerate ) +SampleBuffer::DataVector SampleBuffer::decodeSampleDS(QString _f, ch_cnt_t & _channels, + sample_rate_t & _samplerate) { DrumSynth ds; + int_sample_t *_buf = NULL; f_cnt_t frames = ds.GetDSFileSamples( _f, _buf, _channels, _samplerate ); if ( frames > 0 && _buf != NULL ) { - convertIntToFloat ( _buf, frames, _channels); + return convertIntToFloat ( _buf, frames, _channels); } - return frames; + return {}; } @@ -940,9 +852,10 @@ void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, ( yb - ( m_data[frame][1] * y_space * m_amplification ) ) ); ++n; } + _p.setRenderHint( QPainter::Antialiasing ); - _p.drawPolyline( l, nb_frames / fpp ); - _p.drawPolyline( r, nb_frames / fpp ); + _p.drawPolyline( l, (n-first-1) ); + _p.drawPolyline( r, (n-first-1) ); delete[] l; delete[] r; } @@ -1052,306 +965,41 @@ QString SampleBuffer::openAndSetWaveformFile() -#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/* _current_frame*/, - void * _client_data ) -{ -/* if( _bytes == 0 ) - { - return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; - }*/ - return ( static_cast( _client_data )->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 * _client_data ) -{ - QBuffer * b = static_cast( _client_data ); - b->seek( 0 ); - b->write( (const char *) _metadata, sizeof( *_metadata ) ); -} - -#endif - - - QString & SampleBuffer::toBase64( QString & _dst ) const { -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H - const f_cnt_t FRAMES_PER_BUF = 1152; - - FLAC__StreamEncoder * flac_enc = FLAC__stream_encoder_new(); - FLAC__stream_encoder_set_channels( flac_enc, DEFAULT_CHANNELS ); - FLAC__stream_encoder_set_blocksize( flac_enc, FRAMES_PER_BUF ); -/* FLAC__stream_encoder_set_do_exhaustive_model_search( flac_enc, true ); - FLAC__stream_encoder_set_do_mid_side_stereo( flac_enc, true );*/ - FLAC__stream_encoder_set_sample_rate( flac_enc, - Engine::mixer()->sampleRate() ); - QBuffer ba_writer; - ba_writer.open( QBuffer::WriteOnly ); - - FLAC__stream_encoder_set_write_callback( flac_enc, - flacStreamEncoderWriteCallback ); - FLAC__stream_encoder_set_metadata_callback( flac_enc, - flacStreamEncoderMetadataCallback ); - FLAC__stream_encoder_set_client_data( flac_enc, &ba_writer ); - if( FLAC__stream_encoder_init( flac_enc ) != FLAC__STREAM_ENCODER_OK ) - { - printf( "error within FLAC__stream_encoder_init()!\n" ); - } - f_cnt_t frame_cnt = 0; - while( frame_cnt < m_frames ) - { - f_cnt_t remaining = qMin( FRAMES_PER_BUF, - m_frames - frame_cnt ); - FLAC__int32 buf[FRAMES_PER_BUF * DEFAULT_CHANNELS]; - for( f_cnt_t f = 0; f < remaining; ++f ) - { - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) - { - buf[f*DEFAULT_CHANNELS+ch] = (FLAC__int32)( - Mixer::clip( m_data[f+frame_cnt][ch] ) * - OUTPUT_SAMPLE_MULTIPLIER ); - } - } - FLAC__stream_encoder_process_interleaved( flac_enc, buf, - remaining ); - frame_cnt += remaining; - } - FLAC__stream_encoder_finish( flac_enc ); - FLAC__stream_encoder_delete( flac_enc ); - printf("%d %d\n", frame_cnt, (int)ba_writer.size() ); - ba_writer.close(); - - base64::encode( ba_writer.buffer().data(), ba_writer.buffer().size(), - _dst ); - - -#else /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ - base64::encode( (const char *) data (), frames () * sizeof( sampleFrame ), _dst ); -#endif /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ - return _dst; } - - -std::unique_ptr SampleBuffer::resample(const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ) -{ - const f_cnt_t dst_frames = static_cast( frames ()/ - (float) _src_sr * (float) _dst_sr ); - DataVector outputData(dst_frames); - - // yeah, libsamplerate, let's rock with sinc-interpolation! - int error; - SRC_STATE * state; - if( ( state = src_new( SRC_SINC_MEDIUM_QUALITY, - DEFAULT_CHANNELS, &error ) ) != NULL ) - { - SRC_DATA src_data; - src_data.end_of_input = 1; - src_data.data_in = libSampleRateSrc(data ())->data (); - src_data.data_out = outputData.data ()->data(); - src_data.input_frames = frames (); - src_data.output_frames = dst_frames; - src_data.src_ratio = (double) _dst_sr / _src_sr; - if( ( error = src_process( state, &src_data ) ) ) - { - printf( "SampleBuffer: error while resampling: %s\n", - src_strerror( error ) ); - } - src_delete( state ); - } - else - { - printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); - } - - return std::unique_ptr(new SampleBuffer(std::move(outputData))); -} - - - - void SampleBuffer::setAudioFile( const QString & _audio_file ) { - m_audioFile = tryToMakeRelative( _audio_file ); - update(); -} - - - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - -struct flacStreamDecoderClientData -{ - QBuffer * read_buffer; - QBuffer * write_buffer; -} ; - - - -FLAC__StreamDecoderReadStatus flacStreamDecoderReadCallback( - const FLAC__StreamDecoder * - /*_decoder*/, - FLAC__byte * _buffer, - unsigned int * _bytes, - void * _client_data ) -{ - int res = static_cast( - _client_data )->read_buffer->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 * _client_data ) -{ - 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 frames = _frame->header.blocksize; - for( f_cnt_t frame = 0; frame < frames; ++frame ) - { - sampleFrame sframe = { _buffer[0][frame] / - OUTPUT_SAMPLE_MULTIPLIER, - _buffer[1][frame] / - OUTPUT_SAMPLE_MULTIPLIER - } ; - static_cast( - _client_data )->write_buffer->write( - (const char *) sframe, sizeof( sframe ) ); - } - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + changeAudioFile (_audio_file, true, false); } -void flacStreamDecoderMetadataCallback( const FLAC__StreamDecoder *, - const FLAC__StreamMetadata *, - void * /*_client_data*/ ) -{ - printf("stream decoder metadata callback\n"); -/* QBuffer * b = static_cast( _client_data ); - b->seek( 0 ); - b->write( (const char *) _metadata, sizeof( *_metadata ) );*/ -} - -void flacStreamDecoderErrorCallback( const FLAC__StreamDecoder *, - FLAC__StreamDecoderErrorStatus _status, - void * /*_client_data*/ ) -{ - printf("error callback! %d\n", _status); - // what to do now?? -} - -#endif - - -void SampleBuffer::loadFromBase64( const QString & _data ) +void SampleBuffer::loadFromBase64( const QString & _data , bool shouldLock) { char * dst = NULL; int dsize = 0; base64::decode( _data, &dst, &dsize ); -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - - QByteArray orig_data = QByteArray::fromRawData( dst, dsize ); - QBuffer ba_reader( &orig_data ); - ba_reader.open( QBuffer::ReadOnly ); - - QBuffer ba_writer; - ba_writer.open( QBuffer::WriteOnly ); - - flacStreamDecoderClientData cdata = { &ba_reader, &ba_writer } ; - - FLAC__StreamDecoder * flac_dec = FLAC__stream_decoder_new(); - - FLAC__stream_decoder_set_read_callback( flac_dec, - flacStreamDecoderReadCallback ); - FLAC__stream_decoder_set_write_callback( flac_dec, - flacStreamDecoderWriteCallback ); - FLAC__stream_decoder_set_error_callback( flac_dec, - flacStreamDecoderErrorCallback ); - FLAC__stream_decoder_set_metadata_callback( flac_dec, - flacStreamDecoderMetadataCallback ); - FLAC__stream_decoder_set_client_data( flac_dec, &cdata ); - - FLAC__stream_decoder_init( flac_dec ); - - FLAC__stream_decoder_process_until_end_of_stream( flac_dec ); - - FLAC__stream_decoder_finish( flac_dec ); - FLAC__stream_decoder_delete( flac_dec ); - - ba_reader.close(); - - orig_data = ba_writer.buffer(); - printf("%d\n", (int) orig_data.size() ); - - m_origFrames = orig_data.size() / sizeof( sampleFrame ); - MM_FREE( m_origData ); - m_origData = MM_ALLOC( sampleFrame, m_origFrames ); - memcpy( m_origData, orig_data.data(), orig_data.size() ); - -#else /* LMMS_HAVE_FLAC_STREAM_DECODER_H */ - - m_origData.resize (dsize / sizeof( sampleFrame )); - memcpy (m_origData.data (), + DataVector input(dsize / sizeof(sampleFrame)); + memcpy (input.data (), dst, - m_origData.size ()); -#endif + input.size () * sizeof (sampleFrame)); delete[] dst; + beginBufferChange (shouldLock); + m_data = std::move(input); m_audioFile = QString(); - update(); + doneBufferChange (shouldLock, + true, + sampleRate ()); } @@ -1376,21 +1024,10 @@ void SampleBuffer::setEndFrame( const f_cnt_t _e ) void SampleBuffer::setAmplification( float _a ) { m_amplification = _a; - emit sampleUpdated(); -} - - - -void SampleBuffer::setReversed( bool _on ) -{ - m_reversed = _on; - update( true ); + emit sampleUpdated(); } - - - QString SampleBuffer::tryToMakeRelative( const QString & file ) { if( QFileInfo( file ).isRelative() == false ) @@ -1446,32 +1083,6 @@ QString SampleBuffer::tryToMakeAbsolute(const QString& file) return file; } -void SampleBuffer::addData(const DataVector::iterator begin, const DataVector::iterator end) { - // First of all, don't let anyone read. - m_varLock.lockForWrite (); - { - // Insert to the end of the vector. - m_data.insert (m_data.end (), begin, end); - - m_data.swap (m_origData); - m_data.clear (); - } - m_varLock.unlock (); - - update (); -} - -void SampleBuffer::resetData(DataVector &&newData) { - m_varLock.lockForWrite (); - { - m_data.clear (); - m_origData = std::move (newData); - } - m_varLock.unlock (); - - update (); -} - SampleBuffer::handleState::handleState( bool _varying_pitch, int interpolation_mode ) : m_frameIndex( 0 ), m_varyingPitch( _varying_pitch ), @@ -1493,3 +1104,93 @@ SampleBuffer::handleState::~handleState() { src_delete( m_resamplingData ); } + +void SampleBuffer::beginBufferChange(bool shouldLock, bool shouldLockMixer) +{ + if (shouldLockMixer) { + Engine::mixer ()->requestChangeInModel (); + } + + if (shouldLock) { + m_varLock.lockForWrite (); + } +} + +void SampleBuffer::doneBufferChange(bool shouldUnlock, + bool shouldKeepSettings, + sample_rate_t bufferSampleRate, + bool shouldUnlockMixer) { + + normalizeSampleRate (bufferSampleRate, shouldKeepSettings); + + // TODO: reverse- and amplification-property is not covered + // by following code... + auto previousFrames = frames (); + if( shouldKeepSettings == false ) { + m_loopStartFrame = m_startFrame = 0; + m_loopEndFrame = m_endFrame = frames (); + } else { + m_data.resize (previousFrames); + } + + if (shouldUnlock) { + m_varLock.unlock (); + } + + if (shouldUnlockMixer) { + Engine::mixer ()->doneChangeInModel (); + } + + emit sampleUpdated(); +} + +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); + } + + // First of all, don't let anyone read. + beginBufferChange (true, shouldLockMixer); + { + if (! newVector.empty()) { + // Insert to the end of the resampled vector. + m_data.reserve (m_data.size () + newVector.size ()); + m_data.insert (m_data.end (), newVector.cbegin (), newVector.cend ()); + } else { + // Insert to the end of the vector. + m_data.reserve (m_data.size () + (vector.size ())); + m_data.insert (m_data.end (), vector.cbegin (), vector.cend ()); + } + } + doneBufferChange (true, /* lock */ + true, /* save settings */ + this->sampleRate(), + shouldLockMixer); +} + +void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer) { + beginBufferChange (true, shouldLockMixer); + { + m_audioFile = QString(); + m_data = std::move (newData); + } + doneBufferChange (true, /* lock */ + false, /* save settings */ + dataSampleRate, + shouldLockMixer); +} + +void SampleBuffer::reverse(bool shouldLockMixer) { + beginBufferChange (true, shouldLockMixer); + { + std::reverse(m_data.begin (), m_data.end ()); + } + doneBufferChange (true, /* should(Un)Lock? yes! */ + true, /* should we restore settings? */ + sampleRate (), /* we have not made any change in the sample rate. */ + shouldLockMixer); +} diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 73aed4af097..bfb62f22c86 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -70,13 +70,15 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) // It is the first buffer. if (m_framesRecorded == 0) { // Make sure we don't have the previous data. - m_tco->sampleBuffer ()->resetData(std::move (m_currentBuffer)); + m_tco->sampleBuffer ()->resetData (std::move (m_currentBuffer), + Engine::mixer ()->inputSampleRate (), + false); m_tco->setStartTimeOffset (m_startRecordTimeOffset); - - m_tco->sampleBuffer ()->setSampleRate( Engine::mixer()->inputSampleRate() ); } else { if (! m_currentBuffer.empty ()) { - m_tco->sampleBuffer ()->addData (m_currentBuffer.begin (), m_currentBuffer.end ()); + m_tco->sampleBuffer ()->addData (m_currentBuffer, + Engine::mixer ()->inputSampleRate (), + false); } } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 4c138699770..13cd56a79fe 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -288,7 +288,7 @@ void SampleTCO::loadSettings( const QDomElement & _this ) setSampleFile( _this.attribute( "src" ) ); if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) ) { - m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) ); + m_sampleBuffer->loadFromBase64( _this.attribute( "data" ), true); } changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); @@ -402,7 +402,7 @@ void SampleTCOView::dropEvent( QDropEvent * _de ) else if( StringPairDrag::decodeKey( _de ) == "sampledata" ) { m_tco->m_sampleBuffer->loadFromBase64( - StringPairDrag::decodeValue( _de ) ); + StringPairDrag::decodeValue( _de ) , true); m_tco->updateLength(); update(); _de->accept(); From 53d3b4c554833655d3b02e8bb8a930987e32d4e0 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 18 May 2018 12:42:39 +0300 Subject: [PATCH 045/112] Mixer: Fix another crash with play handle without an audioPort. --- src/core/Mixer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 43bfcb79315..65ff17ec2ab 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -397,7 +397,8 @@ const surroundSampleFrame * Mixer::renderNextBuffer() if( it != m_playHandles.end() ) { - ( *it )->audioPort()->removePlayHandle( ( *it ) ); + if ((*it )->audioPort()) + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); From c61523540cd58e2d45f04dcda939097450c9b4d4 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 18 May 2018 15:47:11 +0300 Subject: [PATCH 046/112] SampleRecordHandle: Tell mixer to remove us when we done. With isFinished (when possible). That will solve our "never ending recording after tco finishing" problem :) --- include/SampleRecordHandle.h | 6 +++++- src/core/SampleRecordHandle.cpp | 10 ++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 97efa1ebeec..4e5b74b30ef 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -71,7 +71,11 @@ class SampleRecordHandle : public PlayHandle const f_cnt_t _frames ); f_cnt_t m_framesRecorded; - MidiTime m_minLength; + + /** + * @brief Total of ticks we've recorded. + */ + MidiTime m_timeRecorded; SampleBuffer::DataVector m_currentBuffer; diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index bfb62f22c86..57de68120cf 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -35,7 +35,6 @@ SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTimeOffset) : PlayHandle( TypeSamplePlayHandle ), m_framesRecorded( 0 ), - m_minLength( tco->length() ), m_track( tco->getTrack() ), m_bbTrack( NULL ), m_tco( tco ), @@ -83,12 +82,7 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) } m_framesRecorded += frames; - - MidiTime len = (tick_t)( m_framesRecorded / Engine::framesPerTick() ); - if( len > m_minLength ) - { - m_minLength = len; - } + m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ()); } @@ -96,7 +90,7 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) bool SampleRecordHandle::isFinished() const { - return false; + return !m_tco->getAutoResize () && (m_startRecordTimeOffset + m_timeRecorded) >= m_tco->length (); } From 58314a495234ef88bebf472496a72aa0285fcc2a Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 19 May 2018 15:48:44 +0300 Subject: [PATCH 047/112] AudioPortAudio: Support recording. --- src/core/audio/AudioPortAudio.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index 5566d7a3634..26103ed6236 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -64,6 +64,7 @@ AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) : m_outBufPos( 0 ) { _success_ful = false; + m_supportsCapture = true; m_outBufSize = mixer()->framesPerPeriod(); @@ -170,9 +171,6 @@ AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) : printf( "Input device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( inDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( inDevIdx )->hostApi )->name ); printf( "Output device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( outDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( outDevIdx )->hostApi )->name ); - // TODO: debug Mixer::pushInputFrames() - //m_supportsCapture = true; - _success_ful = true; } @@ -260,7 +258,7 @@ int AudioPortAudio::process_callback( float * _outputBuffer, unsigned long _framesPerBuffer ) { - if( supportsCapture() ) + if( supportsCapture() && _inputBuffer) { mixer()->pushInputFrames( (sampleFrame*)_inputBuffer, _framesPerBuffer ); From 0aab43a7e75d0eb970c4dd4b1ca283e93947a27f Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 19 May 2018 23:12:21 +0300 Subject: [PATCH 048/112] SampleTrack: Move per-track slots from SampleTCO to SampleTrack. --- include/SampleTrack.h | 4 +- src/tracks/SampleTrack.cpp | 88 ++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 1d578197205..f5b33feb6bd 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -82,8 +82,6 @@ public slots: void setSampleFile( const QString & _sf ); void updateLength(); void toggleRecord(); - void playbackPositionChanged(); - void updateTrackTcos(); private slots: @@ -94,7 +92,6 @@ private slots: BoolModel m_recordModel; bool m_isPlaying; - friend class SampleTCOView; @@ -185,6 +182,7 @@ public slots: void updateEffectChannel(); void beforeRecord (); void toggleRecord(); + void playbackPositionChanged(); private: IntModel m_recordingChannelModel; diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 13cd56a79fe..8ca662a419c 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -52,6 +52,7 @@ #include "FxMixerView.h" #include "TabWidget.h" #include "TrackLabelButton.h" +#include "SampleBuffer.h" SampleTCO::SampleTCO( Track * _track ) : TrackContentObject( _track ), @@ -59,7 +60,6 @@ SampleTCO::SampleTCO( Track * _track ) : m_isPlaying( false ) { saveJournallingState( false ); - setSampleFile( "" ); restoreJournallingState(); // we need to receive bpm-change-events, because then we have to @@ -69,26 +69,6 @@ SampleTCO::SampleTCO( Track * _track ) : connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), this, SLOT( updateLength() ) ); - //care about positionmarker - TimeLineWidget * timeLine = Engine::getSong()->getPlayPos( Engine::getSong()->Mode_PlaySong ).m_timeLine; - if( timeLine ) - { - connect( timeLine, SIGNAL( positionMarkerMoved() ), this, SLOT( playbackPositionChanged() ) ); - } - //playbutton clicked or space key / on Export Song set isPlaying to false - connect( Engine::getSong(), SIGNAL( playbackStateChanged() ), - this, SLOT( playbackPositionChanged() ), Qt::DirectConnection ); - //care about loops - connect( Engine::getSong(), SIGNAL( updateSampleTracks() ), - this, SLOT( playbackPositionChanged() ), Qt::DirectConnection ); - //care about mute TCOs - connect( this, SIGNAL( dataChanged() ), this, SLOT( playbackPositionChanged() ) ); - //care about mute track - connect( getTrack()->getMutedModel(), SIGNAL( dataChanged() ), - this, SLOT( playbackPositionChanged() ), Qt::DirectConnection ); - //care about TCO position - connect( this, SIGNAL( positionChanged() ), this, SLOT( updateTrackTcos() ) ); - connect (m_sampleBuffer, SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); switch( getTrack()->trackContainer()->type() ) @@ -103,7 +83,9 @@ SampleTCO::SampleTCO( Track * _track ) : setAutoResize( false ); break; } - updateTrackTcos(); + + //care about TCO position + connect( this, SIGNAL( positionChanged() ), getTrack (), SLOT( updateTcos() ) ); } @@ -155,8 +137,7 @@ void SampleTCO::setSampleFile( const QString & _sf ) setStartTimeOffset( 0 ); - emit sampleChanged(); - emit playbackPositionChanged(); + // Already has been has been called sampleChanged from m_sampleBuffer. } @@ -170,26 +151,6 @@ void SampleTCO::toggleRecord() - -void SampleTCO::playbackPositionChanged() -{ - Engine::mixer()->removePlayHandlesOfTypes( getTrack(), PlayHandle::TypeSamplePlayHandle ); - SampleTrack * st = dynamic_cast( getTrack() ); - st->setPlayingTcos( false ); -} - - - - -void SampleTCO::updateTrackTcos() -{ - SampleTrack * sampletrack = dynamic_cast( getTrack() ); - if( sampletrack) - { - sampletrack->updateTcos(); - } -} - void SampleTCO::onSampleBufferChanged() { emit sampleChanged (); @@ -337,11 +298,9 @@ void SampleTCOView::updateSample() ToolTip::add( this, ( m_tco->m_sampleBuffer->audioFile() != "" ) ? m_tco->m_sampleBuffer->audioFile() : tr( "Double-click to open sample" ) ); + setNeedsUpdate (true); } - - - void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) { if( _cme->modifiers() ) @@ -432,7 +391,7 @@ void SampleTCOView::mousePressEvent( QMouseEvent * _me ) SampleTCO * sTco = dynamic_cast( getTrackContentObject() ); if( sTco ) { - sTco->updateTrackTcos(); + static_cast(sTco->getTrack ())->updateTcos (); } } TrackContentObjectView::mousePressEvent( _me ); @@ -449,7 +408,7 @@ void SampleTCOView::mouseReleaseEvent(QMouseEvent *_me) SampleTCO * sTco = dynamic_cast( getTrackContentObject() ); if( sTco ) { - sTco->playbackPositionChanged(); + static_cast(sTco->getTrack ())->playbackPositionChanged(); } } TrackContentObjectView::mouseReleaseEvent( _me ); @@ -607,6 +566,25 @@ SampleTrack::SampleTrack( TrackContainer* tc ) : connect( &m_effectChannelModel, SIGNAL( dataChanged() ), this, SLOT( updateEffectChannel() ) ); connect (Engine::getSong (), SIGNAL(beforeRecord()), this, SLOT(beforeRecord())); + + + //care about positionmarker + TimeLineWidget * timeLine = Engine::getSong()->getPlayPos( Engine::getSong()->Mode_PlaySong ).m_timeLine; + if( timeLine ) + { + connect( timeLine, SIGNAL( positionMarkerMoved() ), this, SLOT( playbackPositionChanged() ) ); + } + //playbutton clicked or space key / on Export Song set isPlaying to false + connect( Engine::getSong(), SIGNAL( playbackStateChanged() ), + this, SLOT( playbackPositionChanged() ), Qt::DirectConnection ); + //care about loops + connect( Engine::getSong(), SIGNAL( updateSampleTracks() ), + this, SLOT( playbackPositionChanged() ), Qt::DirectConnection ); + //care about mute TCOs + connect( this, SIGNAL( dataChanged() ), this, SLOT( playbackPositionChanged() ) ); + //care about mute track + connect( getMutedModel(), SIGNAL( dataChanged() ), + this, SLOT( playbackPositionChanged() ), Qt::DirectConnection ); } @@ -648,7 +626,7 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, TrackContentObject * tco = getTCO( i ); SampleTCO * sTco = dynamic_cast( tco ); - // If this is an automaticlly created record track, resize it to the current + // If this is an automatically created record track, resize it to the current // position. if (sTco->isRecord () && !sTco->isMuted () && sTco->getAutoResize ()) { sTco->changeLength (_start - sTco->startPosition()); @@ -816,10 +794,11 @@ void SampleTrack::beforeRecord() { fallbackRecordTCO->setRecord (true); fallbackRecordTCO->movePosition (Engine::getSong ()->getPlayPos (Song::Mode_PlaySong)); +// fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); fallbackRecordTCO->changeLength (1); fallbackRecordTCO->setSampleStartFrame (0); fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); - fallbackRecordTCO->setIsPlaying (true); + fallbackRecordTCO->setIsPlaying (false); fallbackRecordTCO->setAutoResize (true); } @@ -830,6 +809,13 @@ void SampleTrack::toggleRecord() { setRecord (! isRecord ()); } +void SampleTrack::playbackPositionChanged() +{ + Engine::mixer()->removePlayHandlesOfTypes( this, PlayHandle::TypeSamplePlayHandle ); + + setPlayingTcos( false ); +} + SampleTrack::RecordingChannel SampleTrack::recordingChannel() const{ // If we had defined a recording channel for this track, use // it. Otherwise, use the global setting. From c39ccbbd33b8c8aa61a132065e72d88e4fac9ca3 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 19 May 2018 23:13:22 +0300 Subject: [PATCH 049/112] Song -> Recording with loop: support creating of recording tcos in loops. --- src/core/Song.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 7743e84508b..52d7c09c951 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -263,6 +263,9 @@ void Song::processNextBuffer() m_vstSyncController.setPlaybackJumped( true ); emit updateSampleTracks(); + + if (isRecording ()) + emit beforeRecord (); } } @@ -345,6 +348,10 @@ void Song::processNextBuffer() m_vstSyncController.setPlaybackJumped( true ); + if (isRecording()) + { + emit beforeRecord (); + } emit updateSampleTracks(); } } From 3d9efbb6a949bac71c6291bee3c11a3b495e12e7 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 19 May 2018 23:14:20 +0300 Subject: [PATCH 050/112] SampleRecordHandle: Fix a bug when even when not recording, a track set as not record. --- src/core/SampleRecordHandle.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 57de68120cf..c444006e2b3 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -52,8 +52,10 @@ SampleRecordHandle::~SampleRecordHandle() // If this is an automatically created tco, // enable resizing. - m_tco->setAutoResize (false); - m_tco->setRecord( false ); + if (m_framesRecorded != 0) { + m_tco->setAutoResize (false); + m_tco->setRecord( false ); + } } From f4a70a72556cc63c57f4f6f41d7dac13ebbb9717 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Tue, 22 May 2018 15:45:35 +0300 Subject: [PATCH 051/112] SampleBuffer -> Samplerate - Fix two sample rate issues: 1. Resampling was the wrong (src_rate was invalid) 2. SampleBuffer was using baseSampleRate as the default samplerate instead of the actual processingSampleRate. --- include/SampleBuffer.h | 2 ++ src/core/SampleBuffer.cpp | 19 +++++++++++-------- src/core/SamplePlayHandle.cpp | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index fa350e58618..ac8b09022a8 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -285,6 +285,8 @@ public slots: void sampleRateChanged(); protected: + static sample_rate_t mixerSampleRate(); + // HACK: libsamplerate < 0.1.8 doesn't get read-only variables // as const. It has been fixed in 0.1.9 but has not been // shipped for some distributions. diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 5e3d8d2bad5..82da666d20a 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -59,7 +59,7 @@ SampleBuffer::SampleBuffer() : m_loopEndFrame( 0 ), m_amplification( 1.0f ), m_frequency( BaseFreq ), - m_sampleRate( Engine::mixer()->baseSampleRate() ) + m_sampleRate( mixerSampleRate () ) { connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); @@ -103,18 +103,23 @@ SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t void SampleBuffer::sampleRateChanged() { auto previousSampleRate = sampleRate (); - if (Engine::mixer ()->baseSampleRate () == sampleRate ()) + if (mixerSampleRate () == sampleRate ()) return; beginBufferChange (true); - setSampleRate (Engine::mixer ()->baseSampleRate ()); + setSampleRate (mixerSampleRate ()); // Resample the buffer. doneBufferChange ( /* shouldLock */ true, /* shouldKeepSettings */ true, previousSampleRate - ); + ); +} + +sample_rate_t SampleBuffer::mixerSampleRate() +{ + return Engine::mixer ()->processingSampleRate (); } void SampleBuffer::changeAudioFile(QString audioFile, bool shouldLock, @@ -131,7 +136,7 @@ void SampleBuffer::changeAudioFile(QString audioFile, bool shouldLock, QString file = tryToMakeAbsolute( audioFile ); ch_cnt_t channels = DEFAULT_CHANNELS; - sample_rate_t samplerate = Engine::mixer()->baseSampleRate(); + sample_rate_t samplerate = mixerSampleRate (); DataVector fileData; const QFileInfo fileInfo( file ); @@ -276,7 +281,7 @@ SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSamp src_data.data_out = outputData.data ()->data(); src_data.input_frames = inputData.size (); src_data.output_frames = dst_frames; - src_data.src_ratio = (double) inputSampleRate / requiredSampleRate; + src_data.src_ratio = (double) requiredSampleRate / inputSampleRate; if( ( error = src_process( state, &src_data ) ) ) { printf( "SampleBuffer: error while resampling: %s\n", @@ -1158,11 +1163,9 @@ void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t { if (! newVector.empty()) { // Insert to the end of the resampled vector. - m_data.reserve (m_data.size () + newVector.size ()); m_data.insert (m_data.end (), newVector.cbegin (), newVector.cend ()); } else { // Insert to the end of the vector. - m_data.reserve (m_data.size () + (vector.size ())); m_data.insert (m_data.end (), vector.cbegin (), vector.cend ()); } } diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index cae2f3cde01..55c2e626544 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -141,7 +141,7 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const f_cnt_t SamplePlayHandle::totalFrames() const { - return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / Engine::mixer()->baseSampleRate() ); + return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / m_sampleBuffer->sampleRate ()); } From b1ffa1954cf92d92a347ffec99350687dbf19433 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 23 May 2018 10:28:42 +0300 Subject: [PATCH 052/112] AudioJack: Use std::vector for buffer Improve performance by using std::vector::resize instead of deleting and reallocating every time. LmmsBasics: Add static asserts to make sure no padding is added to sampleFrame by std::array. Thanks to @-lukas-w! --- include/AudioJack.h | 2 +- include/lmms_basics.h | 12 ++++++++---- src/core/audio/AudioJack.cpp | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 5a0f6e328a6..81477190575 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -118,7 +118,7 @@ private slots: QVector m_inputPorts; jack_default_audio_sample_t * * m_tempOutBufs; jack_default_audio_sample_t * * m_tempInBufs; - std::unique_ptr m_inBuffer; + std::vector m_inBuffer; surroundSampleFrame * m_outBuf; f_cnt_t m_framesDoneInCurBuf; diff --git a/include/lmms_basics.h b/include/lmms_basics.h index 7d4a3caa507..cfaddfb2738 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -32,9 +32,10 @@ #ifdef LMMS_HAVE_STDINT_H #include -#include #endif +#include + typedef int32_t tact_t; typedef int32_t tick_t; typedef uint8_t volume_t; @@ -59,7 +60,7 @@ typedef uint32_t jo_id_t; // (unique) ID of a journalling object #define likely(x) Q_LIKELY(x) #define unlikely(x) Q_UNLIKELY(x) -// windows headers define "min" and "max" macros, breaking the methods bwloe +// windows headers define "min" and "max" macros, breaking the methods below #undef min #undef max @@ -131,8 +132,6 @@ const ch_cnt_t LEFT_CHANNEL_INDEX = 0; #define LADSPA_PATH_SEPERATOR ':' #endif - - typedef std::array sampleFrame; typedef std::array surroundSampleFrame; @@ -142,6 +141,11 @@ typedef std::array sampleFrameA __attribute__((__ali #endif +static_assert (sizeof(sampleFrame) == sizeof(sample_t) * DEFAULT_CHANNELS, + "sampleFrame's size is not equal to the sum of its parts"); +static_assert (sizeof(surroundSampleFrame) == sizeof(sample_t) * SURROUND_CHANNELS, + "surroundSampleFrame's size is not equal to the sum of its parts"); + #define STRINGIFY(s) STR(s) #define STR(PN) #PN diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index a8c4920bf06..b6ab9698e59 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -435,7 +435,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } if (! m_stopped) { - m_inBuffer.reset (new sampleFrame[_nframes]); + m_inBuffer.resize (_nframes); for( int c = 0; c < channels(); ++c ) { jack_default_audio_sample_t *channel_buffer = m_tempInBufs[c]; @@ -446,7 +446,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } } - mixer()->pushInputFrames (m_inBuffer.get (), _nframes); + mixer()->pushInputFrames (m_inBuffer.data (), _nframes); } if( _nframes != done ) From 210423f095542568ef7b32d335ce2131f6066514 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 23 May 2018 22:57:34 +0300 Subject: [PATCH 053/112] SampleBuffer: Don't use sharedObject Don't use sharedObject. It does not make any sense and could lead to bugs when allocated locally (see audio_file_processor). When needed, this commit used std::shared_ptr which was made for that. --- include/SampleBuffer.h | 2 +- include/SamplePlayHandle.h | 5 +++- include/SampleTrack.h | 4 ++-- plugins/patman/patman.cpp | 24 ++++++++----------- plugins/patman/patman.h | 5 ++-- .../triple_oscillator/TripleOscillator.cpp | 2 +- src/core/LfoController.cpp | 2 +- src/core/SamplePlayHandle.cpp | 8 +++---- src/gui/widgets/Graph.cpp | 7 +++--- src/tracks/SampleTrack.cpp | 3 +-- 10 files changed, 30 insertions(+), 32 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index ac8b09022a8..d00f298f1b0 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -50,7 +50,7 @@ class QRect; // may need to be higher - conversely, to optimize, some may work with lower values const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; -class LMMS_EXPORT SampleBuffer : public QObject, public sharedObject +class LMMS_EXPORT SampleBuffer : public QObject { Q_OBJECT MM_OPERATORS diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index d10c448378c..cb31f4b8c6a 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -30,6 +30,8 @@ #include "AutomatableModel.h" #include "PlayHandle.h" +#include + class BBTrack; class SampleTCO; class Track; @@ -40,6 +42,7 @@ class SamplePlayHandle : public PlayHandle { public: SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort = true ); + SamplePlayHandle( const std::shared_ptr &sampleBuffer, bool ownAudioPort = true ); SamplePlayHandle( const QString& sampleFile ); SamplePlayHandle( SampleTCO* tco ); virtual ~SamplePlayHandle(); @@ -77,7 +80,7 @@ class SamplePlayHandle : public PlayHandle private: - SampleBuffer * m_sampleBuffer; + std::shared_ptr m_sampleBuffer; bool m_doneMayReturnTrue; f_cnt_t m_frame; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index f5b33feb6bd..085ef92e1dd 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -59,7 +59,7 @@ class SampleTCO : public TrackContentObject return "sampletco"; } - SampleBuffer* sampleBuffer() + const std::shared_ptr sampleBuffer() { return m_sampleBuffer; } @@ -88,7 +88,7 @@ private slots: void onSampleBufferChanged (); private: - SampleBuffer* m_sampleBuffer; + std::shared_ptr m_sampleBuffer; BoolModel m_recordModel; bool m_isPlaying; diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index df5a2995993..db4c3a6231a 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -169,7 +169,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n, void patmanInstrument::deleteNotePluginData( NotePlayHandle * _n ) { handle_data * hdata = (handle_data *)_n->m_pluginData; - sharedObject::unref( hdata->sample ); + delete hdata->state; delete hdata; } @@ -356,7 +356,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( } } - SampleBuffer* psample = new SampleBuffer( std::move(data), sample_rate ); + auto psample = std::make_shared( std::move(data), sample_rate ); psample->setFrequency( root_freq / 1000.0f ); if( modes & MODES_LOOPING ) @@ -365,7 +365,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( psample->setLoopEndFrame( loop_end ); } - m_patchSamples.push_back( psample ); + m_patchSamples.push_back( std::move(psample) ); delete[] wave_samples; } @@ -378,11 +378,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( void patmanInstrument::unloadCurrentPatch( void ) { - while( !m_patchSamples.empty() ) - { - sharedObject::unref( m_patchSamples.back() ); - m_patchSamples.pop_back(); - } + m_patchFile.clear (); } @@ -393,18 +389,18 @@ void patmanInstrument::selectSample( NotePlayHandle * _n ) const float freq = _n->frequency(); float min_dist = HUGE_VALF; - SampleBuffer* sample = NULL; + std::shared_ptr sample; - for( QVector::iterator it = m_patchSamples.begin(); it != m_patchSamples.end(); ++it ) + for( auto &element : m_patchSamples ) { - float patch_freq = ( *it )->frequency(); + float patch_freq = element->frequency(); float dist = freq >= patch_freq ? freq / patch_freq : patch_freq / freq; if( dist < min_dist ) { min_dist = dist; - sample = *it; + sample = element; } } @@ -412,11 +408,11 @@ void patmanInstrument::selectSample( NotePlayHandle * _n ) hdata->tuned = m_tunedModel.value(); if( sample ) { - hdata->sample = sharedObject::ref( sample ); + hdata->sample = sample; } else { - hdata->sample = new SampleBuffer( NULL, 0 ); + hdata->sample = std::make_shared(); } hdata->state = new SampleBuffer::handleState( _n->hasDetuningInfo() ); diff --git a/plugins/patman/patman.h b/plugins/patman/patman.h index a3b5a39b898..ed653b2058f 100644 --- a/plugins/patman/patman.h +++ b/plugins/patman/patman.h @@ -31,6 +31,7 @@ #include "SampleBuffer.h" #include "AutomatableModel.h" #include "MemoryManager.h" +#include class PixmapButton; @@ -82,11 +83,11 @@ public slots: MM_OPERATORS SampleBuffer::handleState* state; bool tuned; - SampleBuffer* sample; + std::shared_ptr sample; } handle_data; QString m_patchFile; - QVector m_patchSamples; + QVector> m_patchSamples; BoolModel m_loopedModel; BoolModel m_tunedModel; diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index a883f75fa32..ec929959594 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -130,7 +130,7 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : OscillatorObject::~OscillatorObject() { - sharedObject::unref( m_sampleBuffer ); + delete m_sampleBuffer; } diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 2b2db2f1471..87ab2fc4559 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -71,7 +71,7 @@ LfoController::LfoController( Model * _parent ) : LfoController::~LfoController() { - sharedObject::unref( m_userDefSampleBuffer ); + delete m_userDefSampleBuffer; m_baseModel.disconnect( this ); m_speedModel.disconnect( this ); m_amountModel.disconnect( this ); diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 55c2e626544..82a12e25b04 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -32,9 +32,9 @@ -SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort ) : +SamplePlayHandle::SamplePlayHandle(const std::shared_ptr &sampleBuffer, bool ownAudioPort) : PlayHandle( TypeSamplePlayHandle ), - m_sampleBuffer( sharedObject::ref( sampleBuffer ) ), + m_sampleBuffer( sampleBuffer ), m_doneMayReturnTrue( true ), m_frame( 0 ), m_ownAudioPort( ownAudioPort ), @@ -53,9 +53,8 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPo SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : - SamplePlayHandle( new SampleBuffer( sampleFile ) , true) + SamplePlayHandle( std::make_shared( sampleFile, false ) , true) { - sharedObject::unref( m_sampleBuffer ); } @@ -73,7 +72,6 @@ SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) : SamplePlayHandle::~SamplePlayHandle() { - sharedObject::unref( m_sampleBuffer ); if( m_ownAudioPort ) { delete audioPort(); diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index 4710089dd1a..482e739a0d0 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -23,6 +23,8 @@ * */ +#include + #include #include #include @@ -582,7 +584,8 @@ void graphModel::setWaveToNoise() QString graphModel::setWaveToUser() { - SampleBuffer * sampleBuffer = new SampleBuffer; + auto sampleBuffer = std::unique_ptr{new SampleBuffer}; + QString fileName = sampleBuffer->openAndSetWaveformFile(); if( fileName.isEmpty() == false ) { @@ -595,8 +598,6 @@ QString graphModel::setWaveToUser() sampleBuffer->dataUnlock(); } - sharedObject::unref( sampleBuffer ); - emit samplesChanged( 0, length() - 1 ); return fileName; }; diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 8ca662a419c..fd10e45e773 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -69,7 +69,7 @@ SampleTCO::SampleTCO( Track * _track ) : connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), this, SLOT( updateLength() ) ); - connect (m_sampleBuffer, SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); + connect (m_sampleBuffer.get (), SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); switch( getTrack()->trackContainer()->type() ) { @@ -98,7 +98,6 @@ SampleTCO::~SampleTCO() { sampletrack->updateTcos(); } - sharedObject::unref( m_sampleBuffer ); } From 90c9b744e36179a51e135e588de0c897d5b04fdd Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 30 Jul 2018 14:23:22 +0200 Subject: [PATCH 054/112] SampleBuffer: Use JournallingObject Exporting and restoring is universally done now by saveSettings and loadSettings. That's allows us to make sure we don't left any important attributes like "sampleRate" behind --- include/SampleBuffer.h | 29 ++++++----- .../audio_file_processor.cpp | 17 ++++--- src/core/SampleBuffer.cpp | 39 +++++++++++++-- src/tracks/SampleTrack.cpp | 48 +++++++++---------- 4 files changed, 85 insertions(+), 48 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index d00f298f1b0..85e2527c176 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -39,6 +39,7 @@ #include "lmms_math.h" #include "shared_object.h" #include "MemoryManager.h" +#include "JournallingObject.h" class QPainter; @@ -50,7 +51,7 @@ class QRect; // may need to be higher - conversely, to optimize, some may work with lower values const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; -class LMMS_EXPORT SampleBuffer : public QObject +class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject { Q_OBJECT MM_OPERATORS @@ -106,13 +107,19 @@ class LMMS_EXPORT SampleBuffer : public QObject } ; - SampleBuffer(); // constructor which either loads sample _audio_file or decodes // base64-data out of string - SampleBuffer( const QString & _audio_file, bool _is_base64_data = false ); + SampleBuffer(const QString & _audio_file, bool _is_base64_data, sample_rate_t sampleRate=0); SampleBuffer(DataVector &&movedData, sample_rate_t sampleRate); + inline virtual QString nodeName() const override + { + return "samplebuffer"; + } + virtual void saveSettings(QDomDocument& doc, QDomElement& _this ) override; + virtual void loadSettings(const QDomElement& _this ) override; + bool play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, const float _freq, @@ -197,11 +204,6 @@ class LMMS_EXPORT SampleBuffer : public QObject m_frequency = _freq; } - inline void setSampleRate( sample_rate_t _rate ) - { - m_sampleRate = _rate; - } - inline const sampleFrame * data() const { return m_data.data (); @@ -211,8 +213,6 @@ class LMMS_EXPORT SampleBuffer : public QObject QString openAndSetAudioFile(); QString openAndSetWaveformFile(); - QString & toBase64( QString & _dst ) const; - void normalizeSampleRate( const sample_rate_t _src_sr, bool _keep_settings = false ); @@ -276,15 +276,22 @@ class LMMS_EXPORT SampleBuffer : public QObject */ void reverse(bool shouldLockMixer=true); + void loadFromBase64(const QString & _data , sample_rate_t sampleRate, bool shouldLock); + public slots: void setAudioFile( const QString & _audio_file ); - void loadFromBase64(const QString & _data , bool shouldLock); void setStartFrame( const f_cnt_t _s ); void setEndFrame( const f_cnt_t _e ); void setAmplification( float _a ); void sampleRateChanged(); protected: + QString & toBase64( QString & _dst ) const; + inline void setSampleRate( sample_rate_t _rate ) + { + m_sampleRate = _rate; + } + static sample_rate_t mixerSampleRate(); // HACK: libsamplerate < 0.1.8 doesn't get read-only variables diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index c939c245e0d..3f2b2656e7c 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -1,4 +1,4 @@ -/* +/* * audio_file_processor.cpp - instrument for using audio-files * * Copyright (c) 2004-2014 Tobias Doerffel @@ -210,13 +210,7 @@ void audioFileProcessor::deleteNotePluginData( NotePlayHandle * _n ) void audioFileProcessor::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - _this.setAttribute( "src", m_sampleBuffer.audioFile() ); - if( m_sampleBuffer.audioFile() == "" ) - { - QString s; - _this.setAttribute( "sampledata", - m_sampleBuffer.toBase64( s ) ); - } + m_sampleBuffer.saveState (_doc, _this); m_reverseModel.saveSettings( _doc, _this, "reversed" ); m_loopModel.saveSettings( _doc, _this, "looped" ); m_ampModel.saveSettings( _doc, _this, "amp" ); @@ -247,7 +241,12 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } else if( _this.attribute( "sampledata" ) != "" ) { - m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) , true); + m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) , + true, + Engine::mixer ()->baseSampleRate ()); + qWarning("Using default sampleRate. That could lead to invalid values"); + } else { + m_sampleBuffer.restoreState (_this.firstChildElement (m_sampleBuffer.nodeName ())); } m_loopModel.loadSettings( _this, "looped" ); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 82da666d20a..ed22d8f76ee 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -72,12 +73,12 @@ SampleBuffer::SampleBuffer() : SampleBuffer::SampleBuffer( const QString & _audio_file, - bool _is_base64_data ) + bool _is_base64_data, sample_rate_t sampleRate ) : SampleBuffer() { if( _is_base64_data ) { - loadFromBase64( _audio_file, false ); + loadFromBase64( _audio_file, false, sampleRate ); } else { @@ -99,7 +100,36 @@ SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t ); } +void SampleBuffer::saveSettings(QDomDocument &doc, QDomElement &_this) { + { + QString string; + _this.setAttribute( "data", toBase64(string) ); + } + + _this.setAttribute ("sampleRate", sampleRate ()); + _this.setAttribute ("amplification", amplification ()); + _this.setAttribute ("frequency", frequency ()); +} + +void SampleBuffer::loadSettings(const QDomElement &_this) { + if (_this.hasAttribute ("sampleRate")) { + m_sampleRate = _this.attribute ("sampleRate").toUInt (); + } else { + qWarning("SampleBuffer::loadSettings: Using default sampleRate. That could lead to invalid values"); + } + if (_this.hasAttribute ("amplification")) { + m_amplification = _this.attribute ("amplification").toFloat (); + } + + if (_this.hasAttribute ("frequency")) { + m_frequency = _this.attribute ("frequency").toFloat (); + } + + if (_this.hasAttribute ("data")) { + loadFromBase64 (_this.attribute("data"), m_sampleRate, true); + } +} void SampleBuffer::sampleRateChanged() { auto previousSampleRate = sampleRate (); @@ -986,7 +1016,8 @@ void SampleBuffer::setAudioFile( const QString & _audio_file ) -void SampleBuffer::loadFromBase64( const QString & _data , bool shouldLock) +void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleRate, + bool shouldLock) { char * dst = NULL; int dsize = 0; @@ -1004,7 +1035,7 @@ void SampleBuffer::loadFromBase64( const QString & _data , bool shouldLock) m_audioFile = QString(); doneBufferChange (shouldLock, true, - sampleRate ()); + sampleRate); } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index fd10e45e773..90e6a402ba6 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -223,15 +223,10 @@ void SampleTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) } _this.setAttribute( "len", length() ); _this.setAttribute( "muted", isMuted() ); - _this.setAttribute( "src", sampleFile() ); _this.setAttribute( "off", startTimeOffset() ); - if( sampleFile() == "" ) - { - QString s; - _this.setAttribute( "data", m_sampleBuffer->toBase64( s ) ); - } - _this.setAttribute ("sample_rate", m_sampleBuffer->sampleRate ()); + m_sampleBuffer->saveState (_doc, _this); + _this.setAttribute ("is_record", isRecord ()); // TODO: start- and end-frame } @@ -245,19 +240,33 @@ void SampleTCO::loadSettings( const QDomElement & _this ) { movePosition( _this.attribute( "pos" ).toInt() ); } - setSampleFile( _this.attribute( "src" ) ); - if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) ) - { - m_sampleBuffer->loadFromBase64( _this.attribute( "data" ), true); + + // Should not be happening after 1.3. + if (_this.hasAttribute ("src")) { + auto audioFile = _this.attribute ("src"); + + if (! audioFile.isEmpty ()) { + setSampleFile( _this.attribute( "src" ) ); + } } + + if (sampleFile () == QString()) { + // Data without any other info. + // Should not be happening after 1.3. + if (_this.hasAttribute ("data") && _this.attribute ("data") != QString()) { + qWarning("Using default sampleRate. That could lead to invalid values1"); + m_sampleBuffer->loadFromBase64 (_this.attribute ("data"), + Engine::mixer ()->baseSampleRate (), + true); + } else { + m_sampleBuffer->restoreState (_this.firstChildElement (m_sampleBuffer->nodeName ())); + } + } + changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); setStartTimeOffset( _this.attribute( "off" ).toInt() ); - if (_this.hasAttribute ("sample_rate")) { - m_sampleBuffer->setSampleRate (_this.attribute ("sample_rate").toInt ()); - } - if (_this.hasAttribute ("is_record")) { setRecord (_this.attribute ("is_record").toInt ()); } @@ -357,15 +366,6 @@ void SampleTCOView::dropEvent( QDropEvent * _de ) m_tco->setSampleFile( StringPairDrag::decodeValue( _de ) ); _de->accept(); } - else if( StringPairDrag::decodeKey( _de ) == "sampledata" ) - { - m_tco->m_sampleBuffer->loadFromBase64( - StringPairDrag::decodeValue( _de ) , true); - m_tco->updateLength(); - update(); - _de->accept(); - Engine::getSong()->setModified(); - } else { TrackContentObjectView::dropEvent( _de ); From 79c20bc31080bb6178bfcf6e0b64ccc2ad9486c8 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 23 May 2018 23:19:44 +0300 Subject: [PATCH 055/112] SampleBuffer: Don't normalize any SampleBuffer's sample rate to the Mixer's processing rate. That could lead to ireversable sample loss. Now we'll only resample when it needed and the processing sample rate is higher than the buffer's sample rate. Thanks to @-PhySong for pointing it out! --- include/SampleBuffer.h | 3 --- src/core/SampleBuffer.cpp | 48 ++++++++++++++++----------------------- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 85e2527c176..8b851b2715e 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -213,9 +213,6 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject QString openAndSetAudioFile(); QString openAndSetWaveformFile(); - void normalizeSampleRate( const sample_rate_t _src_sr, - bool _keep_settings = false ); - // protect calls from the GUI to this function with dataReadLock() and // dataUnlock(), out of loops for efficiency inline sample_t userWaveSample( const float _sample ) const diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index ed22d8f76ee..f1a36dba6dd 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -91,7 +91,6 @@ SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t SampleBuffer() { beginBufferChange (false); - setSampleRate (sampleRate); m_data = std::move (movedData); doneBufferChange ( /* shouldLock */ false, @@ -133,18 +132,27 @@ void SampleBuffer::loadSettings(const QDomElement &_this) { void SampleBuffer::sampleRateChanged() { auto previousSampleRate = sampleRate (); - if (mixerSampleRate () == sampleRate ()) + auto requiredSampleRate = mixerSampleRate (); + + if (requiredSampleRate == sampleRate ()) return; - beginBufferChange (true); - setSampleRate (mixerSampleRate ()); + // Only resample the buffer if the processing + // sample rate is higher than the SampleBuffer's + // sample rate. + if (requiredSampleRate > sampleRate ()) { + beginBufferChange (true); + + m_data = resampleData (m_data, previousSampleRate, requiredSampleRate); + + doneBufferChange ( + /* shouldLock */ true, + /* shouldKeepSettings */ true, + requiredSampleRate + ); + } + - // Resample the buffer. - doneBufferChange ( - /* shouldLock */ true, - /* shouldKeepSettings */ true, - previousSampleRate - ); } sample_rate_t SampleBuffer::mixerSampleRate() @@ -273,24 +281,6 @@ SampleBuffer::DataVector SampleBuffer::convertIntToFloat (int_sample_t * & _ibuf } -void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, - bool _keep_settings ) -{ - // do samplerate-conversion to our default-samplerate - if( _src_sr != sampleRate ()) - { - m_data = resampleData (m_data, _src_sr, sampleRate ()); - } - - if( _keep_settings == false ) - { - // update frame-variables - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = frames (); - } -} - - SampleBuffer::DataVector SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t requiredSampleRate) @@ -1157,7 +1147,7 @@ void SampleBuffer::doneBufferChange(bool shouldUnlock, sample_rate_t bufferSampleRate, bool shouldUnlockMixer) { - normalizeSampleRate (bufferSampleRate, shouldKeepSettings); + setSampleRate (bufferSampleRate); // TODO: reverse- and amplification-property is not covered // by following code... From a26a8df688c5ef902180663b6b33413a304b8371 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 24 May 2018 10:11:23 +0300 Subject: [PATCH 056/112] Windows -> Recording: Enable jack. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f9d66defcb..5aebbcc69c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ ENDIF(LMMS_BUILD_APPLE) IF(LMMS_BUILD_WIN32) SET(WANT_ALSA OFF) - SET(WANT_JACK OFF) + SET(WANT_JACK ON) SET(WANT_PULSEAUDIO OFF) SET(WANT_SNDIO OFF) SET(WANT_SOUNDIO OFF) From 196f0002f2e849eb32506595e9cd0f44d1b9f258 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 26 May 2018 08:48:35 +0300 Subject: [PATCH 057/112] Travis: Install jack on win32 builds for its headers. --- .travis/linux.win32.install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis/linux.win32.install.sh b/.travis/linux.win32.install.sh index ea5fe14b12d..fd83e6968c3 100755 --- a/.travis/linux.win32.install.sh +++ b/.travis/linux.win32.install.sh @@ -9,7 +9,7 @@ MINGW_PACKAGES="mingw32-x-sdl mingw32-x-libvorbis mingw32-x-fluidsynth mingw32-x mingw32-x-libgig mingw32-x-libsoundio mingw32-x-lame mingw32-x-qt5base" # swh build dependencies -SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl" +SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl libjack-dev" export MANUAL_PACKAGES_URLS="https://www.libsdl.org/release/SDL2-devel-2.0.7-mingw.tar.gz,install-package arch=i686-w64-mingw32 prefix=/opt/mingw32" From d0385d623ea089bde9c8da33fc29a58e9f3bcc11 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 26 May 2018 10:45:44 +0300 Subject: [PATCH 058/112] Song: Fix loops and recording. --- include/SampleTrack.h | 2 +- include/Song.h | 2 +- src/core/Song.cpp | 13 +++++++------ src/tracks/SampleTrack.cpp | 7 ++++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 085ef92e1dd..6e179f5162f 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -180,7 +180,7 @@ public slots: void updateTcos(); void setPlayingTcos( bool isPlaying ); void updateEffectChannel(); - void beforeRecord (); + void beforeRecordOn (MidiTime time); void toggleRecord(); void playbackPositionChanged(); diff --git a/include/Song.h b/include/Song.h index 6b89d7d4b1e..893c4258720 100644 --- a/include/Song.h +++ b/include/Song.h @@ -343,7 +343,7 @@ public slots: void addBBTrack(); signals: - void beforeRecord (); + void beforeRecordOn (MidiTime time); private slots: void insertBar(); diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 52d7c09c951..3a23a5c50ae 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -108,6 +108,7 @@ Song::Song() : this, SLOT( masterPitchChanged() ) );*/ qRegisterMetaType( "Note" ); + qRegisterMetaType("MidiTime"); setType( SongContainer ); } @@ -263,9 +264,6 @@ void Song::processNextBuffer() m_vstSyncController.setPlaybackJumped( true ); emit updateSampleTracks(); - - if (isRecording ()) - emit beforeRecord (); } } @@ -274,6 +272,11 @@ void Song::processNextBuffer() m_vstSyncController.setPlaybackJumped( true ); m_playPos[m_playMode].setJumped( false ); } + + if (isRecording()) + { + emit beforeRecordOn(getPlayPos()); + } f_cnt_t framesPlayed = 0; const float framesPerTick = Engine::framesPerTick(); @@ -350,7 +353,7 @@ void Song::processNextBuffer() if (isRecording()) { - emit beforeRecord (); + emit beforeRecordOn(getPlayPos()); } emit updateSampleTracks(); } @@ -560,8 +563,6 @@ void Song::record() void Song::playAndRecord() { - emit beforeRecord (); - playSong(); m_recording = true; } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 90e6a402ba6..c57506038cb 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -564,7 +564,7 @@ SampleTrack::SampleTrack( TrackContainer* tc ) : m_effectChannelModel.setRange( 0, Engine::fxMixer()->numChannels()-1, 1); connect( &m_effectChannelModel, SIGNAL( dataChanged() ), this, SLOT( updateEffectChannel() ) ); - connect (Engine::getSong (), SIGNAL(beforeRecord()), this, SLOT(beforeRecord())); + connect (Engine::getSong (), SIGNAL(beforeRecordOn(MidiTime)), this, SLOT(beforeRecordOn(MidiTime))); //care about positionmarker @@ -777,7 +777,8 @@ void SampleTrack::setPlayingTcos( bool isPlaying ) } } -void SampleTrack::beforeRecord() { +void SampleTrack::beforeRecordOn(MidiTime time) +{ if (isRecord ()) { bool isRecordTCOExist = false; @@ -792,7 +793,7 @@ void SampleTrack::beforeRecord() { auto fallbackRecordTCO = static_cast(createTCO (0)); fallbackRecordTCO->setRecord (true); - fallbackRecordTCO->movePosition (Engine::getSong ()->getPlayPos (Song::Mode_PlaySong)); + fallbackRecordTCO->movePosition (time); // fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); fallbackRecordTCO->changeLength (1); fallbackRecordTCO->setSampleStartFrame (0); From 0132e336421397bdb049863018beb4c02a3d96a7 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 26 May 2018 12:24:45 +0300 Subject: [PATCH 059/112] Win32 Installer -> Jack: Try to bundle jack installer. --- src/CMakeLists.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 05d6e24e9ee..6c7e15df9df 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -393,3 +393,15 @@ ELSE(NOT MSVC) # DESTINATION .) #ENDIF() ENDIF(NOT MSVC) + +IF(LMMS_BUILD_WIN32) + SET(JACK_INSTALLER_VERSION "1.9.11") + # Bundle jack + IF (WANT_JACK) + ADD_CUSTOM_TARGET(jack_binary_installer ALL + COMMAND "curl" -OL "https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + INSTALL(FILES "${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" DESTINATION .) + INSTALL(CODE "EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe)") + ENDIF() +ENDIF() From 2ccd530ad6aa1037401d389b599672e7ca7b5189 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 26 May 2018 07:28:01 -0700 Subject: [PATCH 060/112] Support Jack on Windows + Bundled jack Installer (#6) --- CMakeLists.txt | 32 +- cmake/nsis/CMakeLists.txt | 2 +- include/jack/control.h | 631 ++++++++++++++++ include/jack/intclient.h | 130 ++++ include/jack/jack.h | 1470 +++++++++++++++++++++++++++++++++++++ include/jack/jslist.h | 293 ++++++++ include/jack/midiport.h | 184 +++++ include/jack/ringbuffer.h | 243 ++++++ include/jack/session.h | 270 +++++++ include/jack/statistics.h | 57 ++ include/jack/systemdeps.h | 128 ++++ include/jack/thread.h | 160 ++++ include/jack/transport.h | 247 +++++++ include/jack/types.h | 740 +++++++++++++++++++ include/jack/weakjack.h | 125 ++++ include/jack/weakmacros.h | 97 +++ src/CMakeLists.txt | 4 +- 17 files changed, 4795 insertions(+), 18 deletions(-) create mode 100644 include/jack/control.h create mode 100644 include/jack/intclient.h create mode 100644 include/jack/jack.h create mode 100644 include/jack/jslist.h create mode 100644 include/jack/midiport.h create mode 100644 include/jack/ringbuffer.h create mode 100644 include/jack/session.h create mode 100644 include/jack/statistics.h create mode 100644 include/jack/systemdeps.h create mode 100644 include/jack/thread.h create mode 100644 include/jack/transport.h create mode 100644 include/jack/types.h create mode 100644 include/jack/weakjack.h create mode 100644 include/jack/weakmacros.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aebbcc69c4..67efe635665 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -380,23 +380,23 @@ ENDIF(NOT LMMS_HAVE_ALSA) # check for JACK IF(WANT_JACK) - PKG_CHECK_MODULES(JACK jack>=0.77) - IF(JACK_FOUND) - IF(WANT_WEAKJACK) - SET(LMMS_HAVE_WEAKJACK TRUE) - SET(WEAKJACK_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/3rdparty/weakjack/weakjack) - SET(STATUS_JACK "OK (weak linking enabled)") - # use dlsym instead - SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) - ELSE() + IF(WANT_WEAKJACK) + SET(LMMS_HAVE_WEAKJACK TRUE) + SET(WEAKJACK_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/3rdparty/weakjack/weakjack) + SET(STATUS_JACK "OK (weak linking enabled)") + # use dlsym instead + SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) + ELSE() + PKG_CHECK_MODULES(JACK jack>=0.77) + IF(JACK_FOUND) SET(STATUS_JACK "OK") - ENDIF() - SET(LMMS_HAVE_JACK TRUE) - ELSE(JACK_FOUND) - SET(JACK_INCLUDE_DIRS "") - SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similar) " - "if you require JACK support") - ENDIF(JACK_FOUND) + SET(LMMS_HAVE_JACK TRUE) + ELSE(JACK_FOUND) + SET(JACK_INCLUDE_DIRS "") + SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similar) " + "if you require JACK support or enable weak jack") + ENDIF(JACK_FOUND) + ENDIF() ENDIF(WANT_JACK) # check for FFTW3F-library diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index ac628d549a4..5a964d0c708 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -22,7 +22,7 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " \\\${IfNot} \\\${AtMostWin7} WriteRegDWORD HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\SideBySide\\\" \\\"PreferExternalManifest\\\" \\\"1\\\" \\\${EndIf} - " PARENT_SCOPE) + ExecWait '\\\"$INSTDIR\\\\Jack_v1.9.11_32_setup.exe\\\" /install'" PARENT_SCOPE) SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " \\\${unregisterExtension} \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" \\\${unregisterExtension} \\\".mmpz\\\" \\\"${PROJECT_NAME_UCASE} Project (compressed)\\\" diff --git a/include/jack/control.h b/include/jack/control.h new file mode 100644 index 00000000000..bacc9ac6805 --- /dev/null +++ b/include/jack/control.h @@ -0,0 +1,631 @@ +/* -*- Mode: C ; c-basic-offset: 4 -*- */ +/* + JACK control API + + Copyright (C) 2008 Nedko Arnaudov + Copyright (C) 2008 GRAME + + 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; version 2 of the License. + + 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; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/** + * @file jack/control.h + * @ingroup publicheader + * @brief JACK control API + * + */ + +#ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED +#define JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED + +#include +#include +#include +#if !defined(sun) && !defined(__sun__) +#include +#endif + +/** Parameter types, intentionally similar to jack_driver_param_type_t */ +typedef enum +{ + JackParamInt = 1, /**< @brief value type is a signed integer */ + JackParamUInt, /**< @brief value type is an unsigned integer */ + JackParamChar, /**< @brief value type is a char */ + JackParamString, /**< @brief value type is a string with max size of ::JACK_PARAM_STRING_MAX+1 chars */ + JackParamBool, /**< @brief value type is a boolean */ +} jackctl_param_type_t; + +/** Driver types */ +typedef enum +{ + JackMaster = 1, /**< @brief master driver */ + JackSlave /**< @brief slave driver */ +} jackctl_driver_type_t; + +/** @brief Max value that jackctl_param_type_t type can have */ +#define JACK_PARAM_MAX (JackParamBool + 1) + +/** @brief Max length of string parameter value, excluding terminating null char */ +#define JACK_PARAM_STRING_MAX 127 + +/** @brief Type for parameter value */ +/* intentionally similar to jack_driver_param_value_t */ +union jackctl_parameter_value +{ + uint32_t ui; /**< @brief member used for ::JackParamUInt */ + int32_t i; /**< @brief member used for ::JackParamInt */ + char c; /**< @brief member used for ::JackParamChar */ + char str[JACK_PARAM_STRING_MAX + 1]; /**< @brief member used for ::JackParamString */ + bool b; /**< @brief member used for ::JackParamBool */ +}; + +/** opaque type for server object */ +typedef struct jackctl_server jackctl_server_t; + +/** opaque type for driver object */ +typedef struct jackctl_driver jackctl_driver_t; + +/** opaque type for internal client object */ +typedef struct jackctl_internal jackctl_internal_t; + +/** opaque type for parameter object */ +typedef struct jackctl_parameter jackctl_parameter_t; + +/** opaque type for sigmask object */ +typedef struct jackctl_sigmask jackctl_sigmask_t; + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} /* Adjust editor indent */ +#endif + +/** + * @defgroup ControlAPI The API for starting and controlling a JACK server + * @{ + */ + +/** + * Call this function to setup process signal handling. As a general + * rule, it is required for proper operation for the server object. + * + * @param flags signals setup flags, use 0 for none. Currently no + * flags are defined + * + * @return the configurated signal set. + */ +jackctl_sigmask_t * +jackctl_setup_signals( + unsigned int flags); + +/** + * Call this function to wait on a signal set. + * + * @param signals signals set to wait on + */ +void +jackctl_wait_signals( + jackctl_sigmask_t * signals); + +/** + * Call this function to create server object. + * + * @param on_device_acquire - Optional callback to be called before device is acquired. If false is returned, device usage will fail + * @param on_device_release - Optional callback to be called after device is released. + * + * @return server object handle, NULL if creation of server object + * failed. Successfully created server object must be destroyed with + * paired call to ::jackctl_server_destroy + */ +jackctl_server_t * +jackctl_server_create( + bool (* on_device_acquire)(const char * device_name), + void (* on_device_release)(const char * device_name)); + +/** + * Call this function to destroy server object. + * + * @param server server object handle to destroy + */ +void +jackctl_server_destroy( + jackctl_server_t * server); + +/** + * Call this function to open JACK server + * + * @param server server object handle + * @param driver driver to use + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_open( + jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to start JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_start( + jackctl_server_t * server); + +/** + * Call this function to stop JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_stop( + jackctl_server_t * server); + +/** + * Call this function to close JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_close( + jackctl_server_t * server); + +/** + * Call this function to get list of available drivers. List node data + * pointers is a driver object handle (::jackctl_driver_t). + * + * @param server server object handle to get drivers for + * + * @return Single linked list of driver object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_drivers_list( + jackctl_server_t * server); + +/** + * Call this function to get list of server parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param server server object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_parameters( + jackctl_server_t * server); + +/** + * Call this function to get list of available internal clients. List node data + * pointers is a internal client object handle (::jackctl_internal_t). + * + * @param server server object handle to get internal clients for + * + * @return Single linked list of internal client object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_internals_list( + jackctl_server_t * server); + +/** + * Call this function to load one internal client. + * (can be used when the server is running) + * + * @param server server object handle + * @param internal internal to use + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_load_internal( + jackctl_server_t * server, + jackctl_internal_t * internal); + +/** + * Call this function to unload one internal client. + * (can be used when the server is running) + * + * @param server server object handle + * @param internal internal to unload + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_unload_internal( + jackctl_server_t * server, + jackctl_internal_t * internal); + +/** + * Call this function to add a slave in the driver slave list. + * (cannot be used when the server is running that is between + * jackctl_server_start and jackctl_server_stop) + * + * @param server server object handle + * @param driver driver to add in the driver slave list. + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_add_slave(jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to remove a slave from the driver slave list. + * (cannot be used when the server is running that is between + * jackctl_server_start and jackctl_server_stop) + * + * @param server server object handle + * @param driver driver to remove from the driver slave list. + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_remove_slave(jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to switch master driver. + * + * @param server server object handle + * @param driver driver to switch to + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_switch_master(jackctl_server_t * server, + jackctl_driver_t * driver); + + +/** + * Call this function to get name of driver. + * + * @param driver driver object handle to get name of + * + * @return driver name. Must not be modified. Always same for same + * driver object. + */ +const char * +jackctl_driver_get_name( + jackctl_driver_t * driver); + +/** + * Call this function to get type of driver. + * + * @param driver driver object handle to get name of + * + * @return driver type. Must not be modified. Always same for same + * driver object. + */ +jackctl_driver_type_t +jackctl_driver_get_type( + jackctl_driver_t * driver); + +/** + * Call this function to get list of driver parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param driver driver object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same driver object. + */ +const JSList * +jackctl_driver_get_parameters( + jackctl_driver_t * driver); + +/** + * Call this function to parse parameters for a driver. + * + * @param driver driver object handle + * @param argc parameter list len + * @param argv parameter list, as an array of char* + * + * @return success status: true - success, false - fail + */ +int +jackctl_driver_params_parse( + jackctl_driver_t * driver, + int argc, + char* argv[]); + +/** + * Call this function to get name of internal client. + * + * @param internal internal object handle to get name of + * + * @return internal name. Must not be modified. Always same for same + * internal object. + */ +const char * +jackctl_internal_get_name( + jackctl_internal_t * internal); + +/** + * Call this function to get list of internal parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param internal internal object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same internal object. + */ +const JSList * +jackctl_internal_get_parameters( + jackctl_internal_t * internal); + +/** + * Call this function to get parameter name. + * + * @param parameter parameter object handle to get name of + * + * @return parameter name. Must not be modified. Always same for same + * parameter object. + */ +const char * +jackctl_parameter_get_name( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter short description. + * + * @param parameter parameter object handle to get short description of + * + * @return parameter short description. Must not be modified. Always + * same for same parameter object. + */ +const char * +jackctl_parameter_get_short_description( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter long description. + * + * @param parameter parameter object handle to get long description of + * + * @return parameter long description. Must not be modified. Always + * same for same parameter object. + */ +const char * +jackctl_parameter_get_long_description( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter type. + * + * @param parameter parameter object handle to get type of + * + * @return parameter type. Always same for same parameter object. + */ +jackctl_param_type_t +jackctl_parameter_get_type( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter character. + * + * @param parameter parameter object handle to get character of + * + * @return character. + */ +char +jackctl_parameter_get_id( + jackctl_parameter_t * parameter); + +/** + * Call this function to check whether parameter has been set, or its + * default value is being used. + * + * @param parameter parameter object handle to check + * + * @return true - parameter is set, false - parameter is using default + * value. + */ +bool +jackctl_parameter_is_set( + jackctl_parameter_t * parameter); + +/** + * Call this function to reset parameter to its default value. + * + * @param parameter parameter object handle to reset value of + * + * @return success status: true - success, false - fail + */ +bool +jackctl_parameter_reset( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter value. + * + * @param parameter parameter object handle to get value of + * + * @return parameter value. + */ +union jackctl_parameter_value +jackctl_parameter_get_value( + jackctl_parameter_t * parameter); + +/** + * Call this function to set parameter value. + * + * @param parameter parameter object handle to get value of + * @param value_ptr pointer to variable containing parameter value + * + * @return success status: true - success, false - fail + */ +bool +jackctl_parameter_set_value( + jackctl_parameter_t * parameter, + const union jackctl_parameter_value * value_ptr); + +/** + * Call this function to get parameter default value. + * + * @param parameter parameter object handle to get default value of + * + * @return parameter default value. + */ +union jackctl_parameter_value +jackctl_parameter_get_default_value( + jackctl_parameter_t * parameter); + +/** + * Call this function check whether parameter has range constraint. + * + * @param parameter object handle of parameter to check + * + * @return whether parameter has range constraint. + */ +bool +jackctl_parameter_has_range_constraint( + jackctl_parameter_t * parameter); + +/** + * Call this function check whether parameter has enumeration constraint. + * + * @param parameter object handle of parameter to check + * + * @return whether parameter has enumeration constraint. + */ +bool +jackctl_parameter_has_enum_constraint( + jackctl_parameter_t * parameter); + +/** + * Call this function get how many enumeration values parameter has. + * + * @param parameter object handle of parameter + * + * @return number of enumeration values + */ +uint32_t +jackctl_parameter_get_enum_constraints_count( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter enumeration value. + * + * @param parameter object handle of parameter + * @param index index of parameter enumeration value + * + * @return enumeration value. + */ +union jackctl_parameter_value +jackctl_parameter_get_enum_constraint_value( + jackctl_parameter_t * parameter, + uint32_t index); + +/** + * Call this function to get parameter enumeration value description. + * + * @param parameter object handle of parameter + * @param index index of parameter enumeration value + * + * @return enumeration value description. + */ +const char * +jackctl_parameter_get_enum_constraint_description( + jackctl_parameter_t * parameter, + uint32_t index); + +/** + * Call this function to get parameter range. + * + * @param parameter object handle of parameter + * @param min_ptr pointer to variable receiving parameter minimum value + * @param max_ptr pointer to variable receiving parameter maximum value + */ +void +jackctl_parameter_get_range_constraint( + jackctl_parameter_t * parameter, + union jackctl_parameter_value * min_ptr, + union jackctl_parameter_value * max_ptr); + +/** + * Call this function to check whether parameter constraint is strict, + * i.e. whether supplying non-matching value will not work for sure. + * + * @param parameter parameter object handle to check + * + * @return whether parameter constraint is strict. + */ +bool +jackctl_parameter_constraint_is_strict( + jackctl_parameter_t * parameter); + +/** + * Call this function to check whether parameter has fake values, + * i.e. values have no user meaningful meaning and only value + * description is meaningful to user. + * + * @param parameter parameter object handle to check + * + * @return whether parameter constraint is strict. + */ +bool +jackctl_parameter_constraint_is_fake_value( + jackctl_parameter_t * parameter); + +/** + * Call this function to log an error message. + * + * @param format string + */ +void +jack_error( + const char *format, + ...); + +/** + * Call this function to log an information message. + * + * @param format string + */ +void +jack_info( + const char *format, + ...); + +/** + * Call this function to log an information message but only when + * verbose mode is enabled. + * + * @param format string + */ +void +jack_log( + const char *format, + ...); + +/* @} */ + +#if 0 +{ /* Adjust editor indent */ +#endif +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* #ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED */ diff --git a/include/jack/intclient.h b/include/jack/intclient.h new file mode 100644 index 00000000000..740c0f318ac --- /dev/null +++ b/include/jack/intclient.h @@ -0,0 +1,130 @@ +/* +* Copyright (C) 2004 Jack O'Quin +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* +*/ + +#ifndef __jack_intclient_h__ +#define __jack_intclient_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/** + * Get an internal client's name. This is useful when @ref + * JackUseExactName was not specified on jack_internal_client_load() + * and @ref JackNameNotUnique status was returned. In that case, the + * actual name will differ from the @a client_name requested. + * + * @param client requesting JACK client's handle. + * + * @param intclient handle returned from jack_internal_client_load() + * or jack_internal_client_handle(). + * + * @return NULL if unsuccessful, otherwise pointer to the internal + * client name obtained from the heap via malloc(). The caller should + * jack_free() this storage when no longer needed. + */ +char *jack_get_internal_client_name (jack_client_t *client, + jack_intclient_t intclient); + +/** + * Return the @ref jack_intclient_t handle for an internal client + * running in the JACK server. + * + * @param client requesting JACK client's handle. + * + * @param client_name for the internal client of no more than + * jack_client_name_size() characters. The name scope is local to the + * current server. + * + * @param status (if non-NULL) an address for JACK to return + * information from this operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * @return Opaque internal client handle if successful. If 0, the + * internal client was not found, and @a *status includes the @ref + * JackNoSuchClient and @ref JackFailure bits. + */ +jack_intclient_t jack_internal_client_handle (jack_client_t *client, + const char *client_name, + jack_status_t *status); + +/** + * Load an internal client into the JACK server. + * + * Internal clients run inside the JACK server process. They can use + * most of the same functions as external clients. Each internal + * client is built as a shared object module, which must declare + * jack_initialize() and jack_finish() entry points called at load and + * unload times. See @ref inprocess.c for an example. + * + * @param client loading JACK client's handle. + * + * @param client_name of at most jack_client_name_size() characters + * for the internal client to load. The name scope is local to the + * current server. + * + * @param options formed by OR-ing together @ref JackOptions bits. + * Only the @ref JackLoadOptions bits are valid. + * + * @param status (if non-NULL) an address for JACK to return + * information from the load operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * Optional parameters: depending on corresponding [@a options + * bits] additional parameters may follow @a status (in this order). + * + * @arg [@ref JackLoadName] (char *) load_name is the shared + * object file from which to load the new internal client (otherwise + * use the @a client_name). + * + * @arg [@ref JackLoadInit] (char *) load_init an arbitary + * string passed to the internal client's jack_initialize() routine + * (otherwise NULL), of no more than @ref JACK_LOAD_INIT_LIMIT bytes. + * + * @return Opaque internal client handle if successful. If this is 0, + * the load operation failed, the internal client was not loaded, and + * @a *status includes the @ref JackFailure bit. + */ +jack_intclient_t jack_internal_client_load (jack_client_t *client, + const char *client_name, + jack_options_t options, + jack_status_t *status, ...); +/** + * Unload an internal client from a JACK server. This calls the + * intclient's jack_finish() entry point then removes it. See @ref + * inprocess.c for an example. + * + * @param client unloading JACK client's handle. + * + * @param intclient handle returned from jack_internal_client_load() or + * jack_internal_client_handle(). + * + * @return 0 if successful, otherwise @ref JackStatus bits. + */ +jack_status_t jack_internal_client_unload (jack_client_t *client, + jack_intclient_t intclient); + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_intclient_h__ */ diff --git a/include/jack/jack.h b/include/jack/jack.h new file mode 100644 index 00000000000..b147bab3b6f --- /dev/null +++ b/include/jack/jack.h @@ -0,0 +1,1470 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_h__ +#define __jack_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + +/** + * Note: More documentation can be found in jack/types.h. + */ + + /************************************************************* + * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function + * added to the JACK API after the 0.116.2 release. + * + * Functions that predate this release are marked with + * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile + * time in a variety of ways. The default definition is empty, + * so that these symbols get normal linkage. If you wish to + * use all JACK symbols with weak linkage, include + * before jack.h. + *************************************************************/ + +#include + +/** + * Call this function to get version of the JACK, in form of several numbers + * + * @param major_ptr pointer to variable receiving major version of JACK. + * + * @param minor_ptr pointer to variable receiving minor version of JACK. + * + * @param major_ptr pointer to variable receiving micro version of JACK. + * + * @param major_ptr pointer to variable receiving protocol version of JACK. + * + */ +void +jack_get_version( + int *major_ptr, + int *minor_ptr, + int *micro_ptr, + int *proto_ptr) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Call this function to get version of the JACK, in form of a string + * + * @return Human readable string describing JACK version being used. + * + */ +const char * +jack_get_version_string(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @defgroup ClientFunctions Creating & manipulating clients + * @{ + */ + +/** + * Open an external client session with a JACK server. This interface + * is more complex but more powerful than jack_client_new(). With it, + * clients may choose which of several servers to connect, and control + * whether and how to start the server automatically, if it was not + * already running. There is also an option for JACK to generate a + * unique client name, when necessary. + * + * @param client_name of at most jack_client_name_size() characters. + * The name scope is local to each server. Unless forbidden by the + * @ref JackUseExactName option, the server will modify this name to + * create a unique variant, if needed. + * + * @param options formed by OR-ing together @ref JackOptions bits. + * Only the @ref JackOpenOptions bits are allowed. + * + * @param status (if non-NULL) an address for JACK to return + * information from the open operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * + * Optional parameters: depending on corresponding [@a options + * bits] additional parameters may follow @a status (in this order). + * + * @arg [@ref JackServerName] (char *) server_name selects + * from among several possible concurrent server instances. Server + * names are unique to each user. If unspecified, use "default" + * unless \$JACK_DEFAULT_SERVER is defined in the process environment. + * + * @return Opaque client handle if successful. If this is NULL, the + * open operation failed, @a *status includes @ref JackFailure and the + * caller is not a JACK client. + */ +jack_client_t * jack_client_open (const char *client_name, + jack_options_t options, + jack_status_t *status, ...) JACK_OPTIONAL_WEAK_EXPORT; + +/** +* \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN +* NEW JACK CLIENTS +* +* @deprecated Please use jack_client_open(). +*/ +jack_client_t * jack_client_new (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Disconnects an external client from a JACK server. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_client_close (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a JACK client name + * including the final NULL character. This value is a constant. + */ +int jack_client_name_size (void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return pointer to actual client name. This is useful when @ref + * JackUseExactName is not specified on open and @ref + * JackNameNotUnique status was returned. In that case, the actual + * name will differ from the @a client_name requested. + */ +char * jack_get_client_name (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Get the session ID for a client name. + * + * The session manager needs this to reassociate a client name to the session_id. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_get_uuid_for_client_name (jack_client_t *client, + const char *client_name) JACK_WEAK_EXPORT; + +/** + * Get the client name for a session_id. + * + * In order to snapshot the graph connections, the session manager needs to map + * session_ids to client names. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_get_client_name_by_uuid (jack_client_t *client, + const char *client_uuid ) JACK_WEAK_EXPORT; + +/** + * Load an internal client into the Jack server. + * + * Internal clients run inside the JACK server process. They can use + * most of the same functions as external clients. Each internal + * client must declare jack_initialize() and jack_finish() entry + * points, called at load and unload times. See inprocess.c for an + * example of how to write an internal client. + * + * @deprecated Please use jack_internal_client_load(). + * + * @param client_name of at most jack_client_name_size() characters. + * + * @param load_name of a shared object file containing the code for + * the new client. + * + * @param load_init an arbitary string passed to the jack_initialize() + * routine of the new client (may be NULL). + * + * @return 0 if successful. + */ +int jack_internal_client_new (const char *client_name, + const char *load_name, + const char *load_init) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Remove an internal client from a JACK server. + * + * @deprecated Please use jack_internal_client_unload(). + */ +void jack_internal_client_close (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Tell the Jack server that the program is ready to start processing + * audio. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_activate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to remove this @a client from the process + * graph. Also, disconnect all ports belonging to it, since inactive + * clients have no port connections. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_deactivate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return pid of client. If not available, 0 will be returned. + */ +int jack_get_client_pid (const char *name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the pthread ID of the thread running the JACK client side + * real-time code. + */ +jack_native_thread_t jack_client_thread_id (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @param client pointer to JACK client structure. + * + * Check if the JACK subsystem is running with -R (--realtime). + * + * @return 1 if JACK is running realtime, 0 otherwise + */ +int jack_is_realtime (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @defgroup NonCallbackAPI The non-callback API + * @{ + */ + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK CLIENTS. + * + * @deprecated Please use jack_cycle_wait() and jack_cycle_signal() functions. + */ +jack_nframes_t jack_thread_wait (jack_client_t *client, int status) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Wait until this JACK client should process data. + * + * @param client - pointer to a JACK client structure + * + * @return the number of frames of data to process + */ +jack_nframes_t jack_cycle_wait (jack_client_t* client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Signal next clients in the graph. + * + * @param client - pointer to a JACK client structure + * @param status - if non-zero, calling thread should exit + */ +void jack_cycle_signal (jack_client_t* client, int status) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a thread_callback in the RT thread. + * Typical use are in conjunction with @a jack_cycle_wait and @a jack_cycle_signal functions. + * The code in the supplied function must be suitable for real-time + * execution. That means that it cannot call functions that might + * block for a long time. This includes malloc, free, printf, + * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, + * pthread_cond_wait, etc, etc. See + * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 + * for more information. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. +*/ +int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup ClientCallbacks Setting Client Callbacks + * @{ + */ + +/** + * Tell JACK to call @a thread_init_callback once just after + * the creation of the thread in which all other callbacks + * will be handled. + * + * The code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code, causing JACK + * to remove that client from the process() graph. + */ +int jack_set_thread_init_callback (jack_client_t *client, + JackThreadInitCallback thread_init_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @param client pointer to JACK client structure. + * @param function The jack_shutdown function pointer. + * @param arg The arguments for the jack_shutdown function. + * + * Register a function (and argument) to be called if and when the + * JACK server shuts down the client thread. The function must + * be written as if it were an asynchonrous POSIX signal + * handler --- use only async-safe functions, and remember that it + * is executed from another thread. A typical function might + * set a flag or write to a pipe so that the rest of the + * application knows that the JACK client thread has shut + * down. + * + * NOTE: clients do not need to call this. It exists only + * to help more complex clients understand what is going + * on. It should be called before jack_client_activate(). + * + * NOTE: if a client calls this AND jack_on_info_shutdown(), then + * in case of a client thread shutdown, the callback + * passed to this function will not be called, and the one passed to + * jack_on_info_shutdown() will. + * + * NOTE: application should typically signal another thread to correctly + * finish cleanup, that is by calling "jack_client_close" + * (since "jack_client_close" cannot be called directly in the context + * of the thread that calls the shutdown callback). + */ +void jack_on_shutdown (jack_client_t *client, + JackShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @param client pointer to JACK client structure. + * @param function The jack_info_shutdown function pointer. + * @param arg The arguments for the jack_info_shutdown function. + * + * Register a function (and argument) to be called if and when the + * JACK server shuts down the client thread. The function must + * be written as if it were an asynchonrous POSIX signal + * handler --- use only async-safe functions, and remember that it + * is executed from another thread. A typical function might + * set a flag or write to a pipe so that the rest of the + * application knows that the JACK client thread has shut + * down. + * + * NOTE: clients do not need to call this. It exists only + * to help more complex clients understand what is going + * on. It should be called before jack_client_activate(). + * + * NOTE: if a client calls this AND jack_on_shutdown(), then + * in case of a client thread shutdown, the callback passed to + * jack_on_info_shutdown() will be called. + * + * NOTE: application should typically signal another thread to correctly + * finish cleanup, that is by calling "jack_client_close" + * (since "jack_client_close" cannot be called directly in the context + * of the thread that calls the shutdown callback). + */ +void jack_on_info_shutdown (jack_client_t *client, + JackInfoShutdownCallback shutdown_callback, void *arg) JACK_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a process_callback whenever there is + * work be done, passing @a arg as the second argument. + * + * The code in the supplied function must be suitable for real-time + * execution. That means that it cannot call functions that might + * block for a long time. This includes malloc, free, printf, + * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, + * pthread_cond_wait, etc, etc. See + * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 + * for more information. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_process_callback (jack_client_t *client, + JackProcessCallback process_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a freewheel_callback + * whenever we enter or leave "freewheel" mode, passing @a + * arg as the second argument. The first argument to the + * callback will be non-zero if JACK is entering freewheel + * mode, and zero otherwise. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_freewheel_callback (jack_client_t *client, + JackFreewheelCallback freewheel_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell JACK to call @a bufsize_callback whenever the size of the the + * buffer that will be passed to the @a process_callback is about to + * change. Clients that depend on knowing the buffer size must supply + * a @a bufsize_callback before activating themselves. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @param client pointer to JACK client structure. + * @param bufsize_callback function to call when the buffer size changes. + * @param arg argument for @a bufsize_callback. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_buffer_size_callback (jack_client_t *client, + JackBufferSizeCallback bufsize_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a srate_callback whenever the system + * sample rate changes. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_sample_rate_callback (jack_client_t *client, + JackSampleRateCallback srate_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a client_registration_callback whenever a + * client is registered or unregistered, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_client_registration_callback (jack_client_t *client, + JackClientRegistrationCallback + registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a registration_callback whenever a + * port is registered or unregistered, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ + int jack_set_port_registration_callback (jack_client_t *client, + JackPortRegistrationCallback + registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * Tell the JACK server to call @a connect_callback whenever a + * port is connected or disconnected, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_port_connect_callback (jack_client_t *client, + JackPortConnectCallback + connect_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * Tell the JACK server to call @a rename_callback whenever a + * port is renamed, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_port_rename_callback (jack_client_t *client, + JackPortRenameCallback + rename_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a graph_callback whenever the + * processing graph is reordered, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_graph_order_callback (jack_client_t *client, + JackGraphOrderCallback graph_callback, + void *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a xrun_callback whenever there is a + * xrun, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_xrun_callback (jack_client_t *client, + JackXRunCallback xrun_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * Tell the Jack server to call @a latency_callback whenever it + * is necessary to recompute the latencies for some or all + * Jack ports. + * + * @a latency_callback will be called twice each time it is + * needed, once being passed JackCaptureLatency and once + * JackPlaybackLatency. See @ref LatencyFunctions for + * the definition of each type of latency and related functions. + * + * IMPORTANT: Most JACK clients do NOT need to register a latency + * callback. + * + * Clients that meet any of the following conditions do NOT + * need to register a latency callback: + * + * - have only input ports + * - have only output ports + * - their output is totally unrelated to their input + * - their output is not delayed relative to their input + * (i.e. data that arrives in a given process() + * callback is processed and output again in the + * same callback) + * + * Clients NOT registering a latency callback MUST also + * satisfy this condition: + * + * - have no multiple distinct internal signal pathways + * + * This means that if your client has more than 1 input and + * output port, and considers them always "correlated" + * (e.g. as a stereo pair), then there is only 1 (e.g. stereo) + * signal pathway through the client. This would be true, + * for example, of a stereo FX rack client that has a + * left/right input pair and a left/right output pair. + * + * However, this is somewhat a matter of perspective. The + * same FX rack client could be connected so that its + * two input ports were connected to entirely separate + * sources. Under these conditions, the fact that the client + * does not register a latency callback MAY result + * in port latency values being incorrect. + * + * Clients that do not meet any of those conditions SHOULD + * register a latency callback. + * + * See the documentation for @ref jack_port_set_latency_range() + * on how the callback should operate. Remember that the @a mode + * argument given to the latency callback will need to be + * passed into @ref jack_port_set_latency_range() + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_latency_callback (jack_client_t *client, + JackLatencyCallback latency_callback, + void *) JACK_WEAK_EXPORT; +/*@}*/ + +/** + * @defgroup ServerClientControl Controlling & querying JACK server operation + * @{ + */ + +/** + * Start/Stop JACK's "freewheel" mode. + * + * When in "freewheel" mode, JACK no longer waits for + * any external event to begin the start of the next process + * cycle. + * + * As a result, freewheel mode causes "faster than realtime" + * execution of a JACK graph. If possessed, real-time + * scheduling is dropped when entering freewheel mode, and + * if appropriate it is reacquired when stopping. + * + * IMPORTANT: on systems using capabilities to provide real-time + * scheduling (i.e. Linux kernel 2.4), if onoff is zero, this function + * must be called from the thread that originally called jack_activate(). + * This restriction does not apply to other systems (e.g. Linux kernel 2.6 + * or OS X). + * + * @param client pointer to JACK client structure + * @param onoff if non-zero, freewheel mode starts. Otherwise + * freewheel mode ends. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_freewheel(jack_client_t* client, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Change the buffer size passed to the @a process_callback. + * + * This operation stops the JACK engine process cycle, then calls all + * registered @a bufsize_callback functions before restarting the + * process cycle. This will cause a gap in the audio flow, so it + * should only be done at appropriate stopping points. + * + * @see jack_set_buffer_size_callback() + * + * @param client pointer to JACK client structure. + * @param nframes new buffer size. Must be a power of two. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the sample rate of the jack system, as set by the user when + * jackd was started. + */ +jack_nframes_t jack_get_sample_rate (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the current maximum size that will ever be passed to the @a + * process_callback. It should only be used *before* the client has + * been activated. This size may change, clients that depend on it + * must register a @a bufsize_callback so they will be notified if it + * does. + * + * @see jack_set_buffer_size_callback() + */ +jack_nframes_t jack_get_buffer_size (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Old-style interface to become the timebase for the entire JACK + * subsystem. + * + * @deprecated This function still exists for compatibility with the + * earlier transport interface, but it does nothing. Instead, see + * transport.h and use jack_set_timebase_callback(). + * + * @return ENOSYS, function not implemented. + */ +int jack_engine_takeover_timebase (jack_client_t *) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * @return the current CPU load estimated by JACK. This is a running + * average of the time it takes to execute a full process cycle for + * all clients as a percentage of the real time available per cycle + * determined by the buffer size and sample rate. + */ +float jack_cpu_load (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup PortFunctions Creating & manipulating ports + * @{ + */ + +/** + * Create a new port for the client. This is an object used for moving + * data of any type in or out of the client. Ports may be connected + * in various ways. + * + * Each port has a short name. The port's full name contains the name + * of the client concatenated with a colon (:) followed by its short + * name. The jack_port_name_size() is the maximum length of this full + * name. Exceeding that will cause the port registration to fail and + * return NULL. + * + * The @a port_name must be unique among all ports owned by this client. + * If the name is not unique, the registration will fail. + * + * All ports have a type, which may be any non-NULL and non-zero + * length string, passed as an argument. Some port types are built + * into the JACK API, currently only JACK_DEFAULT_AUDIO_TYPE. + * + * @param client pointer to JACK client structure. + * @param port_name non-empty short name for the new port (not + * including the leading @a "client_name:"). Must be unique. + * @param port_type port type name. If longer than + * jack_port_type_size(), only that many characters are significant. + * @param flags @ref JackPortFlags bit mask. + * @param buffer_size must be non-zero if this is not a built-in @a + * port_type. Otherwise, it is ignored. + * + * @return jack_port_t pointer on success, otherwise NULL. + */ +jack_port_t * jack_port_register (jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove the port from the client, disconnecting any existing + * connections. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_port_unregister (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * This returns a pointer to the memory area associated with the + * specified port. For an output port, it will be a memory area + * that can be written to; for an input port, it will be an area + * containing the data from the port's connection(s), or + * zero-filled. if there are multiple inbound connections, the data + * will be mixed appropriately. + * + * FOR OUTPUT PORTS ONLY : DEPRECATED in Jack 2.0 !! + * --------------------------------------------------- + * You may cache the value returned, but only between calls to + * your "blocksize" callback. For this reason alone, you should + * either never cache the return value or ensure you have + * a "blocksize" callback and be sure to invalidate the cached + * address from there. + * + * Caching output ports is DEPRECATED in Jack 2.0, due to some new optimization (like "pipelining"). + * Port buffers have to be retrieved in each callback for proper functionning. + */ +void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the UUID of the jack_port_t + * + * @see jack_uuid_to_string() to convert into a string representation + */ +jack_uuid_t jack_port_uuid (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the full name of the jack_port_t (including the @a + * "client_name:" prefix). + * + * @see jack_port_name_size(). + */ +const char * jack_port_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the short name of the jack_port_t (not including the @a + * "client_name:" prefix). + * + * @see jack_port_name_size(). + */ +const char * jack_port_short_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the @ref JackPortFlags of the jack_port_t. + */ +int jack_port_flags (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the @a port type, at most jack_port_type_size() characters + * including a final NULL. + */ +const char * jack_port_type (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * @return the @a port type id. + */ +jack_port_type_id_t jack_port_type_id (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if the jack_port_t belongs to the jack_client_t. + */ +int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return number of connections to or from @a port. + * + * @pre The calling client must own @a port. + */ +int jack_port_connected (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if the locally-owned @a port is @b directly connected + * to the @a port_name. + * + * @see jack_port_name_size() + */ +int jack_port_connected_to (const jack_port_t *port, + const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return a null-terminated array of full port names to which the @a + * port is connected. If none, returns NULL. + * + * The caller is responsible for calling jack_free() on any non-NULL + * returned value. + * + * @param port locally owned jack_port_t pointer. + * + * @see jack_port_name_size(), jack_port_get_all_connections() + */ +const char ** jack_port_get_connections (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return a null-terminated array of full port names to which the @a + * port is connected. If none, returns NULL. + * + * The caller is responsible for calling jack_free() on any non-NULL + * returned value. + * + * This differs from jack_port_get_connections() in two important + * respects: + * + * 1) You may not call this function from code that is + * executed in response to a JACK event. For example, + * you cannot use it in a GraphReordered handler. + * + * 2) You need not be the owner of the port to get information + * about its connections. + * + * @see jack_port_name_size() + */ +const char ** jack_port_get_all_connections (const jack_client_t *client, + const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * + * @deprecated This function will be removed from a future version + * of JACK. Do not use it. There is no replacement. It has + * turned out to serve essentially no purpose in real-life + * JACK clients. + */ +int jack_port_tie (jack_port_t *src, jack_port_t *dst) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * + * @deprecated This function will be removed from a future version + * of JACK. Do not use it. There is no replacement. It has + * turned out to serve essentially no purpose in real-life + * JACK clients. + */ +int jack_port_untie (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK CLIENTS + * + * Modify a port's short name. May be called at any time. If the + * resulting full name (including the @a "client_name:" prefix) is + * longer than jack_port_name_size(), it will be truncated. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_set_name (jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Modify a port's short name. May NOT be called from a callback handling a server event. + * If the resulting full name (including the @a "client_name:" prefix) is + * longer than jack_port_name_size(), it will be truncated. + * + * @return 0 on success, otherwise a non-zero error code. + * + * This differs from jack_port_set_name() by triggering PortRename notifications to + * clients that have registered a port rename handler. + */ +int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set @a alias as an alias for @a port. May be called at any time. + * If the alias is longer than jack_port_name_size(), it will be truncated. + * + * After a successful call, and until JACK exits or + * @function jack_port_unset_alias() is called, @alias may be + * used as a alternate name for the port. + * + * Ports can have up to two aliases - if both are already + * set, this function will return an error. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_set_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove @a alias as an alias for @a port. May be called at any time. + * + * After a successful call, @a alias can no longer be + * used as a alternate name for the port. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_unset_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Get any aliases known for @port. + * + * @return the number of aliases discovered for the port + */ +int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for this @a port, turn input + * monitoring on or off. Otherwise, do nothing. + */ +int jack_port_request_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for this @a port_name, turn input + * monitoring on or off. Otherwise, do nothing. + * + * @return 0 on success, otherwise a non-zero error code. + * + * @see jack_port_name_size() + */ +int jack_port_request_monitor_by_name (jack_client_t *client, + const char *port_name, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for a port, this function turns + * on input monitoring if it was off, and turns it off if only one + * request has been made to turn it on. Otherwise it does nothing. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_port_ensure_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if input monitoring has been requested for @a port. + */ +int jack_port_monitoring_input (jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Establish a connection between two ports. + * + * When a connection exists, data written to the source port will + * be available to be read at the destination port. + * + * @pre The port types must be identical. + * + * @pre The @ref JackPortFlags of the @a source_port must include @ref + * JackPortIsOutput. + * + * @pre The @ref JackPortFlags of the @a destination_port must include + * @ref JackPortIsInput. + * + * @return 0 on success, EEXIST if the connection is already made, + * otherwise a non-zero error code + */ +int jack_connect (jack_client_t *client, + const char *source_port, + const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove a connection between two ports. + * + * @pre The port types must be identical. + * + * @pre The @ref JackPortFlags of the @a source_port must include @ref + * JackPortIsOutput. + * + * @pre The @ref JackPortFlags of the @a destination_port must include + * @ref JackPortIsInput. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_disconnect (jack_client_t *client, + const char *source_port, + const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Perform the same function as jack_disconnect() using port handles + * rather than names. This avoids the name lookup inherent in the + * name-based version. + * + * Clients connecting their own ports are likely to use this function, + * while generic connection clients (e.g. patchbays) would use + * jack_disconnect(). + */ +int jack_port_disconnect (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a full JACK port name + * including the final NULL character. This value is a constant. + * + * A port's full name contains the owning client name concatenated + * with a colon (:) followed by its short name and a NULL + * character. + */ +int jack_port_name_size(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a JACK port type name + * including the final NULL character. This value is a constant. + */ +int jack_port_type_size(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the buffersize of a port of type @arg port_type. + * + * this function may only be called in a buffer_size callback. + */ +size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) JACK_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup LatencyFunctions Managing and determining latency + * + * The purpose of JACK's latency API is to allow clients to + * easily answer two questions: + * + * - How long has it been since the data read from a port arrived + * at the edge of the JACK graph (either via a physical port + * or being synthesized from scratch)? + * + * - How long will it be before the data written to a port arrives + * at the edge of a JACK graph? + + * To help answering these two questions, all JACK ports have two + * latency values associated with them, both measured in frames: + * + * capture latency: how long since the data read from + * the buffer of a port arrived at + * a port marked with JackPortIsTerminal. + * The data will have come from the "outside + * world" if the terminal port is also + * marked with JackPortIsPhysical, or + * will have been synthesized by the client + * that owns the terminal port. + * + * playback latency: how long until the data + * written to the buffer of port will reach a port + * marked with JackPortIsTerminal. + * + * Both latencies might potentially have more than one value + * because there may be multiple pathways to/from a given port + * and a terminal port. Latency is therefore generally + * expressed a min/max pair. + * + * In most common setups, the minimum and maximum latency + * are the same, but this design accomodates more complex + * routing, and allows applications (and thus users) to + * detect cases where routing is creating an anomalous + * situation that may either need fixing or more + * sophisticated handling by clients that care about + * latency. + * + * See also @ref jack_set_latency_callback for details on how + * clients that add latency to the signal path should interact + * with JACK to ensure that the correct latency figures are + * used. + * @{ + */ + +/** + * The port latency is zero by default. Clients that control + * physical hardware with non-zero latency should call this + * to set the latency to its correct value. Note that the value + * should include any systemic latency present "outside" the + * physical hardware controlled by the client. For example, + * for a client controlling a digital audio interface connected + * to an external digital converter, the latency setting should + * include both buffering by the audio interface *and* the converter. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by a latency callback that calls @ref + * jack_port_set_latency_range(). + */ +void jack_port_set_latency (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * return the latency range defined by @a mode for + * @a port, in frames. + * + * See @ref LatencyFunctions for the definition of each latency value. + * + * This is normally used in the LatencyCallback. + * and therefor safe to execute from callbacks. + */ +void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; + +/** + * set the minimum and maximum latencies defined by + * @a mode for @a port, in frames. + * + * See @ref LatencyFunctions for the definition of each latency value. + * + * This function should ONLY be used inside a latency + * callback. The client should determine the current + * value of the latency using @ref jack_port_get_latency_range() + * (called using the same mode as @a mode) + * and then add some number of frames to that reflects + * latency added by the client. + * + * How much latency a client adds will vary + * dramatically. For most clients, the answer is zero + * and there is no reason for them to register a latency + * callback and thus they should never call this + * function. + * + * More complex clients that take an input signal, + * transform it in some way and output the result but + * not during the same process() callback will + * generally know a single constant value to add + * to the value returned by @ref jack_port_get_latency_range(). + * + * Such clients would register a latency callback (see + * @ref jack_set_latency_callback) and must know what input + * ports feed which output ports as part of their + * internal state. Their latency callback will update + * the ports' latency values appropriately. + * + * A pseudo-code example will help. The @a mode argument to the latency + * callback will determine whether playback or capture + * latency is being set. The callback will use + * @ref jack_port_set_latency_range() as follows: + * + * \code + * jack_latency_range_t range; + * if (mode == JackPlaybackLatency) { + * foreach input_port in (all self-registered port) { + * jack_port_get_latency_range (port_feeding_input_port, JackPlaybackLatency, &range); + * range.min += min_delay_added_as_signal_flows_from port_feeding to input_port; + * range.max += max_delay_added_as_signal_flows_from port_feeding to input_port; + * jack_port_set_latency_range (input_port, JackPlaybackLatency, &range); + * } + * } else if (mode == JackCaptureLatency) { + * foreach output_port in (all self-registered port) { + * jack_port_get_latency_range (port_fed_by_output_port, JackCaptureLatency, &range); + * range.min += min_delay_added_as_signal_flows_from_output_port_to_fed_by_port; + * range.max += max_delay_added_as_signal_flows_from_output_port_to_fed_by_port; + * jack_port_set_latency_range (output_port, JackCaptureLatency, &range); + * } + * } + * \endcode + * + * In this relatively simple pseudo-code example, it is assumed that + * each input port or output is connected to only 1 output or input + * port respectively. + * + * If a port is connected to more than 1 other port, then the + * range.min and range.max values passed to @ref + * jack_port_set_latency_range() should reflect the minimum and + * maximum values across all connected ports. + * + * See the description of @ref jack_set_latency_callback for more + * information. + */ +void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; + +/** + * Request a complete recomputation of all port latencies. This + * can be called by a client that has just changed the internal + * latency of its port using jack_port_set_latency + * and wants to ensure that all signal pathways in the graph + * are updated with respect to the values that will be returned + * by jack_port_get_total_latency. It allows a client + * to change multiple port latencies without triggering a + * recompute for each change. + * + * @return zero for successful execution of the request. non-zero + * otherwise. + */ +int jack_recompute_total_latencies (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the time (in frames) between data being available or + * delivered at/to a port, and the time at which it arrived at or is + * delivered to the "other side" of the port. E.g. for a physical + * audio output port, this is the time between writing to the port and + * when the signal will leave the connector. For a physical audio + * input port, this is the time between the sound arriving at the + * connector and the corresponding frames being readable from the + * port. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_port_get_latency_range() in any existing + * use cases. + */ +jack_nframes_t jack_port_get_latency (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * The maximum of the sum of the latencies in every + * connection path that can be drawn between the port and other + * ports with the @ref JackPortIsTerminal flag set. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_port_get_latency_range() in any existing + * use cases. + */ +jack_nframes_t jack_port_get_total_latency (jack_client_t *client, + jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Request a complete recomputation of a port's total latency. This + * can be called by a client that has just changed the internal + * latency of its port using jack_port_set_latency + * and wants to ensure that all signal pathways in the graph + * are updated with respect to the values that will be returned + * by jack_port_get_total_latency. + * + * @return zero for successful execution of the request. non-zero + * otherwise. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_recompute_total_latencies() in any existing + * use cases. + */ +int jack_recompute_total_latency (jack_client_t*, jack_port_t* port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/*@}*/ + +/** + * @defgroup PortSearching Looking up ports + * @{ + */ + +/** + * @param port_name_pattern A regular expression used to select + * ports by name. If NULL or of zero length, no selection based + * on name will be carried out. + * @param type_name_pattern A regular expression used to select + * ports by type. If NULL or of zero length, no selection based + * on type will be carried out. + * @param flags A value used to select ports by their flags. + * If zero, no selection based on flags will be carried out. + * + * @return a NULL-terminated array of ports that match the specified + * arguments. The caller is responsible for calling jack_free() any + * non-NULL returned value. + * + * @see jack_port_name_size(), jack_port_type_size() + */ +const char ** jack_get_ports (jack_client_t *client, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return address of the jack_port_t named @a port_name. + * + * @see jack_port_name_size() + */ +jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return address of the jack_port_t of a @a port_id. + */ +jack_port_t * jack_port_by_id (jack_client_t *client, + jack_port_id_t port_id) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup TimeFunctions Handling time + * @{ + * + * JACK time is in units of 'frames', according to the current sample rate. + * The absolute value of frame times is meaningless, frame times have meaning + * only relative to each other. + */ + +/** + * @return the estimated time in frames that has passed since the JACK + * server began the current process cycle. + */ +jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated current time in frames. + * This function is intended for use in other threads (not the process + * callback). The return value can be compared with the value of + * jack_last_frame_time to relate time in other threads to JACK time. + */ +jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the precise time at the start of the current process cycle. + * This function may only be used from the process callback, and can + * be used to interpret timestamps generated by jack_frame_time() in + * other threads with respect to the current process cycle. + * + * This is the only jack time function that returns exact time: + * when used during the process callback it always returns the same + * value (until the next process callback, where it will return + * that value + nframes, etc). The return value is guaranteed to be + * monotonic and linear in this fashion unless an xrun occurs. + * If an xrun occurs, clients must check this value again, as time + * may have advanced in a non-linear way (e.g. cycles may have been skipped). + */ +jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * This function may only be used from the process callback. + * It provides the internal cycle timing information as used by + * most of the other time related functions. This allows the + * caller to map between frame counts and microseconds with full + * precision (i.e. without rounding frame times to integers), + * and also provides e.g. the microseconds time of the start of + * the current cycle directly (it has to be computed otherwise). + * + * If the return value is zero, the following information is + * provided in the variables pointed to by the arguments: + * + * current_frames: the frame time counter at the start of the + * current cycle, same as jack_last_frame_time(). + * current_usecs: the microseconds time at the start of the + * current cycle. + * next_usecs: the microseconds time of the start of the next + * next cycle as computed by the DLL. + * period_usecs: the current best estimate of the period time in + * microseconds. + * + * NOTES: + * + * Because of the types used, all the returned values except period_usecs + * are unsigned. In computations mapping between frames and microseconds + * *signed* differences are required. The easiest way is to compute those + * separately and assign them to the appropriate signed variables, + * int32_t for frames and int64_t for usecs. See the implementation of + * jack_frames_to_time() and Jack_time_to_frames() for an example. + * + * Unless there was an xrun, skipped cycles, or the current cycle is the + * first after freewheeling or starting Jack, the value of current_usecs + * will always be the value of next_usecs of the previous cycle. + * + * The value of period_usecs will in general NOT be exactly equal to + * the difference of next_usecs and current_usecs. This is because to + * ensure stability of the DLL and continuity of the mapping, a fraction + * of the loop error must be included in next_usecs. For an accurate + * mapping between frames and microseconds, the difference of next_usecs + * and current_usecs should be used, and not period_usecs. + * + * @return zero if OK, non-zero otherwise. + */ +int jack_get_cycle_times(const jack_client_t *client, + jack_nframes_t *current_frames, + jack_time_t *current_usecs, + jack_time_t *next_usecs, + float *period_usecs) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated time in microseconds of the specified frame time + */ +jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated time in frames for the specified system time. + */ +jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return return JACK's current system time in microseconds, + * using the JACK clock source. + * + * The value returned is guaranteed to be monotonic, but not linear. + */ +jack_time_t jack_get_time(void) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup ErrorOutput Controlling error/information output + */ +/*@{*/ + +/** + * Display JACK error message. + * + * Set via jack_set_error_function(), otherwise a JACK-provided + * default will print @a msg (plus a newline) to stderr. + * + * @param msg error message text (no newline at end). + */ +extern void (*jack_error_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the @ref jack_error_callback for error message display. + * Set it to NULL to restore default_jack_error_callback function. + * + * The JACK library provides two built-in callbacks for this purpose: + * default_jack_error_callback() and silent_jack_error_callback(). + */ +void jack_set_error_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Display JACK info message. + * + * Set via jack_set_info_function(), otherwise a JACK-provided + * default will print @a msg (plus a newline) to stdout. + * + * @param msg info message text (no newline at end). + */ +extern void (*jack_info_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the @ref jack_info_callback for info message display. + * Set it to NULL to restore default_jack_info_callback function. + * + * The JACK library provides two built-in callbacks for this purpose: + * default_jack_info_callback() and silent_jack_info_callback(). + */ +void jack_set_info_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * The free function to be used on memory returned by jack_port_get_connections, + * jack_port_get_all_connections, jack_get_ports and jack_get_internal_client_name functions. + * This is MANDATORY on Windows when otherwise all nasty runtime version related crashes can occur. + * Developers are strongly encouraged to use this function instead of the standard "free" function in new code. + * + * @param ptr the memory pointer to be deallocated. + */ +void jack_free(void* ptr) JACK_OPTIONAL_WEAK_EXPORT; + + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_h__ */ diff --git a/include/jack/jslist.h b/include/jack/jslist.h new file mode 100644 index 00000000000..4a2bf7067ff --- /dev/null +++ b/include/jack/jslist.h @@ -0,0 +1,293 @@ +/* + Based on gslist.c from glib-1.2.9 (LGPL). + + Adaption to JACK, Copyright (C) 2002 Kai Vehmanen. + - replaced use of gtypes with normal ANSI C types + - glib's memory allocation routines replaced with + malloc/free calls + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_jslist_h__ +#define __jack_jslist_h__ + +#include +#include + +#ifdef sun +#define __inline__ +#endif + +typedef struct _JSList JSList; + +typedef int (*JCompareFunc) (void* a, void* b); +struct _JSList +{ + void *data; + JSList *next; +}; + +static __inline__ +JSList* +jack_slist_alloc (void) +{ + JSList *new_list; + + new_list = (JSList*)malloc(sizeof(JSList)); + if (new_list) { + new_list->data = NULL; + new_list->next = NULL; + } + + return new_list; +} + +static __inline__ +JSList* +jack_slist_prepend (JSList* list, void* data) +{ + JSList *new_list; + + new_list = (JSList*)malloc(sizeof(JSList)); + if (new_list) { + new_list->data = data; + new_list->next = list; + } + + return new_list; +} + +#define jack_slist_next(slist) ((slist) ? (((JSList *)(slist))->next) : NULL) +static __inline__ +JSList* +jack_slist_last (JSList *list) +{ + if (list) { + while (list->next) + list = list->next; + } + + return list; +} + +static __inline__ +JSList* +jack_slist_remove_link (JSList *list, + JSList *link) +{ + JSList *tmp; + JSList *prev; + + prev = NULL; + tmp = list; + + while (tmp) { + if (tmp == link) { + if (prev) + prev->next = tmp->next; + if (list == tmp) + list = list->next; + + tmp->next = NULL; + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static __inline__ +void +jack_slist_free (JSList *list) +{ + while (list) { + JSList *next = list->next; + free(list); + list = next; + } +} + +static __inline__ +void +jack_slist_free_1 (JSList *list) +{ + if (list) { + free(list); + } +} + +static __inline__ +JSList* +jack_slist_remove (JSList *list, + void *data) +{ + JSList *tmp; + JSList *prev; + + prev = NULL; + tmp = list; + + while (tmp) { + if (tmp->data == data) { + if (prev) + prev->next = tmp->next; + if (list == tmp) + list = list->next; + + tmp->next = NULL; + jack_slist_free (tmp); + + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static __inline__ +unsigned int +jack_slist_length (JSList *list) +{ + unsigned int length; + + length = 0; + while (list) { + length++; + list = list->next; + } + + return length; +} + +static __inline__ +JSList* +jack_slist_find (JSList *list, + void *data) +{ + while (list) { + if (list->data == data) + break; + list = list->next; + } + + return list; +} + +static __inline__ +JSList* +jack_slist_copy (JSList *list) +{ + JSList *new_list = NULL; + + if (list) { + JSList *last; + + new_list = jack_slist_alloc (); + new_list->data = list->data; + last = new_list; + list = list->next; + while (list) { + last->next = jack_slist_alloc (); + last = last->next; + last->data = list->data; + list = list->next; + } + } + + return new_list; +} + +static __inline__ +JSList* +jack_slist_append (JSList *list, + void *data) +{ + JSList *new_list; + JSList *last; + + new_list = jack_slist_alloc (); + new_list->data = data; + + if (list) { + last = jack_slist_last (list); + last->next = new_list; + + return list; + } else + return new_list; +} + +static __inline__ +JSList* +jack_slist_sort_merge (JSList *l1, + JSList *l2, + JCompareFunc compare_func) +{ + JSList list, *l; + + l = &list; + + while (l1 && l2) { + if (compare_func(l1->data, l2->data) < 0) { + l = l->next = l1; + l1 = l1->next; + } else { + l = l->next = l2; + l2 = l2->next; + } + } + l->next = l1 ? l1 : l2; + + return list.next; +} + +static __inline__ +JSList* +jack_slist_sort (JSList *list, + JCompareFunc compare_func) +{ + JSList *l1, *l2; + + if (!list) + return NULL; + if (!list->next) + return list; + + l1 = list; + l2 = list->next; + + while ((l2 = l2->next) != NULL) { + if ((l2 = l2->next) == NULL) + break; + l1 = l1->next; + } + l2 = l1->next; + l1->next = NULL; + + return jack_slist_sort_merge (jack_slist_sort (list, compare_func), + jack_slist_sort (l2, compare_func), + compare_func); +} + +#endif /* __jack_jslist_h__ */ + diff --git a/include/jack/midiport.h b/include/jack/midiport.h new file mode 100644 index 00000000000..af16e0d76a6 --- /dev/null +++ b/include/jack/midiport.h @@ -0,0 +1,184 @@ +/* + Copyright (C) 2004 Ian Esten + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#ifndef __JACK_MIDIPORT_H +#define __JACK_MIDIPORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + + +/** Type for raw event data contained in @ref jack_midi_event_t. */ +typedef unsigned char jack_midi_data_t; + + +/** A Jack MIDI event. */ +typedef struct _jack_midi_event +{ + jack_nframes_t time; /**< Sample index at which event is valid */ + size_t size; /**< Number of bytes of data in \a buffer */ + jack_midi_data_t *buffer; /**< Raw MIDI data */ +} jack_midi_event_t; + + +/** + * @defgroup MIDIAPI Reading and writing MIDI data + * @{ + */ + +/** Get number of events in a port buffer. + * + * @param port_buffer Port buffer from which to retrieve event. + * @return number of events inside @a port_buffer + */ +uint32_t +jack_midi_get_event_count(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get a MIDI event from an event port buffer. + * + * Jack MIDI is normalised, the MIDI event returned by this function is + * guaranteed to be a complete MIDI event (the status byte will always be + * present, and no realtime events will interspered with the event). + * + * @param event Event structure to store retrieved event in. + * @param port_buffer Port buffer from which to retrieve event. + * @param event_index Index of event to retrieve. + * @return 0 on success, ENODATA if buffer is empty. + */ +int +jack_midi_event_get(jack_midi_event_t *event, + void *port_buffer, + uint32_t event_index) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Clear an event buffer. + * + * This should be called at the beginning of each process cycle before calling + * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This + * function may not be called on an input port's buffer. + * + * @param port_buffer Port buffer to clear (must be an output port buffer). + */ +void +jack_midi_clear_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + +/** Reset an event buffer (from data allocated outside of JACK). + * + * This should be called at the beginning of each process cycle before calling + * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This + * function may not be called on an input port's buffer. + * + * @param port_buffer Port buffer to resetted. + */ +void +jack_midi_reset_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get the size of the largest event that can be stored by the port. + * + * This function returns the current space available, taking into account + * events already stored in the port. + * + * @param port_buffer Port buffer to check size of. + */ +size_t +jack_midi_max_event_size(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Allocate space for an event to be written to an event port buffer. + * + * Clients are to write the actual event data to be written starting at the + * pointer returned by this function. Clients must not write more than + * @a data_size bytes into this buffer. Clients must write normalised + * MIDI data to the port - no running status and no (1-byte) realtime + * messages interspersed with other messages (realtime messages are fine + * when they occur on their own, like other messages). + * + * Events must be written in order, sorted by their sample offsets. + * JACK will not sort the events for you, and will refuse to store + * out-of-order events. + * + * @param port_buffer Buffer to write event to. + * @param time Sample offset of event. + * @param data_size Length of event's raw data in bytes. + * @return Pointer to the beginning of the reserved event's data buffer, or + * NULL on error (ie not enough space). + */ +jack_midi_data_t* +jack_midi_event_reserve(void *port_buffer, + jack_nframes_t time, + size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Write an event into an event port buffer. + * + * This function is simply a wrapper for @ref jack_midi_event_reserve + * which writes the event data into the space reserved in the buffer. + * + * Clients must not write more than + * @a data_size bytes into this buffer. Clients must write normalised + * MIDI data to the port - no running status and no (1-byte) realtime + * messages interspersed with other messages (realtime messages are fine + * when they occur on their own, like other messages). + * + * Events must be written in order, sorted by their sample offsets. + * JACK will not sort the events for you, and will refuse to store + * out-of-order events. + * + * @param port_buffer Buffer to write event to. + * @param time Sample offset of event. + * @param data Message data to be written. + * @param data_size Length of @a data in bytes. + * @return 0 on success, ENOBUFS if there's not enough space in buffer for event. + */ +int +jack_midi_event_write(void *port_buffer, + jack_nframes_t time, + const jack_midi_data_t *data, + size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get the number of events that could not be written to @a port_buffer. + * + * This function returning a non-zero value implies @a port_buffer is full. + * Currently the only way this can happen is if events are lost on port mixdown. + * + * @param port_buffer Port to receive count for. + * @returns Number of events that could not be written to @a port_buffer. + */ +uint32_t +jack_midi_get_lost_event_count(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +#ifdef __cplusplus +} +#endif + + +#endif /* __JACK_MIDIPORT_H */ + + diff --git a/include/jack/ringbuffer.h b/include/jack/ringbuffer.h new file mode 100644 index 00000000000..74be11769ac --- /dev/null +++ b/include/jack/ringbuffer.h @@ -0,0 +1,243 @@ +/* + Copyright (C) 2000 Paul Davis + Copyright (C) 2003 Rohan Drape + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/** @file ringbuffer.h + * + * A set of library functions to make lock-free ringbuffers available + * to JACK clients. The `capture_client.c' (in the example_clients + * directory) is a fully functioning user of this API. + * + * The key attribute of a ringbuffer is that it can be safely accessed + * by two threads simultaneously -- one reading from the buffer and + * the other writing to it -- without using any synchronization or + * mutual exclusion primitives. For this to work correctly, there can + * only be a single reader and a single writer thread. Their + * identities cannot be interchanged. + */ + +typedef struct { + char *buf; + size_t len; +} +jack_ringbuffer_data_t ; + +typedef struct { + char *buf; + volatile size_t write_ptr; + volatile size_t read_ptr; + size_t size; + size_t size_mask; + int mlocked; +} +jack_ringbuffer_t ; + +/** + * Allocates a ringbuffer data structure of a specified size. The + * caller must arrange for a call to jack_ringbuffer_free() to release + * the memory associated with the ringbuffer. + * + * @param sz the ringbuffer size in bytes. + * + * @return a pointer to a new jack_ringbuffer_t, if successful; NULL + * otherwise. + */ +jack_ringbuffer_t *jack_ringbuffer_create(size_t sz); + +/** + * Frees the ringbuffer data structure allocated by an earlier call to + * jack_ringbuffer_create(). + * + * @param rb a pointer to the ringbuffer structure. + */ +void jack_ringbuffer_free(jack_ringbuffer_t *rb); + +/** + * Fill a data structure with a description of the current readable + * data held in the ringbuffer. This description is returned in a two + * element array of jack_ringbuffer_data_t. Two elements are needed + * because the data to be read may be split across the end of the + * ringbuffer. + * + * The first element will always contain a valid @a len field, which + * may be zero or greater. If the @a len field is non-zero, then data + * can be read in a contiguous fashion using the address given in the + * corresponding @a buf field. + * + * If the second element has a non-zero @a len field, then a second + * contiguous stretch of data can be read from the address given in + * its corresponding @a buf field. + * + * @param rb a pointer to the ringbuffer structure. + * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. + * + */ +void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec); + +/** + * Fill a data structure with a description of the current writable + * space in the ringbuffer. The description is returned in a two + * element array of jack_ringbuffer_data_t. Two elements are needed + * because the space available for writing may be split across the end + * of the ringbuffer. + * + * The first element will always contain a valid @a len field, which + * may be zero or greater. If the @a len field is non-zero, then data + * can be written in a contiguous fashion using the address given in + * the corresponding @a buf field. + * + * If the second element has a non-zero @a len field, then a second + * contiguous stretch of data can be written to the address given in + * the corresponding @a buf field. + * + * @param rb a pointer to the ringbuffer structure. + * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. + */ +void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec); + +/** + * Read data from the ringbuffer. + * + * @param rb a pointer to the ringbuffer structure. + * @param dest a pointer to a buffer where data read from the + * ringbuffer will go. + * @param cnt the number of bytes to read. + * + * @return the number of bytes read, which may range from 0 to cnt. + */ +size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt); + +/** + * Read data from the ringbuffer. Opposed to jack_ringbuffer_read() + * this function does not move the read pointer. Thus it's + * a convenient way to inspect data in the ringbuffer in a + * continous fashion. The price is that the data is copied + * into a user provided buffer. For "raw" non-copy inspection + * of the data in the ringbuffer use jack_ringbuffer_get_read_vector(). + * + * @param rb a pointer to the ringbuffer structure. + * @param dest a pointer to a buffer where data read from the + * ringbuffer will go. + * @param cnt the number of bytes to read. + * + * @return the number of bytes read, which may range from 0 to cnt. + */ +size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt); + +/** + * Advance the read pointer. + * + * After data have been read from the ringbuffer using the pointers + * returned by jack_ringbuffer_get_read_vector(), use this function to + * advance the buffer pointers, making that space available for future + * write operations. + * + * @param rb a pointer to the ringbuffer structure. + * @param cnt the number of bytes read. + */ +void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt); + +/** + * Return the number of bytes available for reading. + * + * @param rb a pointer to the ringbuffer structure. + * + * @return the number of bytes available to read. + */ +size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb); + +/** + * Lock a ringbuffer data block into memory. + * + * Uses the mlock() system call. This is not a realtime operation. + * + * @param rb a pointer to the ringbuffer structure. + */ +int jack_ringbuffer_mlock(jack_ringbuffer_t *rb); + +/** + * Reset the read and write pointers, making an empty buffer. + * + * This is not thread safe. + * + * @param rb a pointer to the ringbuffer structure. + */ +void jack_ringbuffer_reset(jack_ringbuffer_t *rb); + +/** + * Reset the internal "available" size, and read and write pointers, making an empty buffer. + * + * This is not thread safe. + * + * @param rb a pointer to the ringbuffer structure. + * @param sz the new size, that must be less than allocated size. + */ +void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz); + +/** + * Write data into the ringbuffer. + * + * @param rb a pointer to the ringbuffer structure. + * @param src a pointer to the data to be written to the ringbuffer. + * @param cnt the number of bytes to write. + * + * @return the number of bytes write, which may range from 0 to cnt + */ +size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, + size_t cnt); + +/** + * Advance the write pointer. + * + * After data have been written the ringbuffer using the pointers + * returned by jack_ringbuffer_get_write_vector(), use this function + * to advance the buffer pointer, making the data available for future + * read operations. + * + * @param rb a pointer to the ringbuffer structure. + * @param cnt the number of bytes written. + */ +void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt); + +/** + * Return the number of bytes available for writing. + * + * @param rb a pointer to the ringbuffer structure. + * + * @return the amount of free space (in bytes) available for writing. + */ +size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/jack/session.h b/include/jack/session.h new file mode 100644 index 00000000000..cb75cea74ed --- /dev/null +++ b/include/jack/session.h @@ -0,0 +1,270 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + Copyright (C) 2010 Torben Hohn + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __jack_session_h__ +#define __jack_session_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @defgroup SessionClientFunctions Session API for clients. + * @{ + */ + + +/** + * Session event type. + * + * If a client cant save templates, i might just do a normal save. + * + * There is no "quit without saving" event because a client might refuse to + * quit when it has unsaved data, but other clients may have already quit. + * This results in too much confusion, so it is unsupported. + */ +enum JackSessionEventType { + /** + * Save the session completely. + * + * The client may save references to data outside the provided directory, + * but it must do so by creating a link inside the provided directory and + * referring to that in any save files. The client must not refer to data + * files outside the provided directory directly in save files, because + * this makes it impossible for the session manager to create a session + * archive for distribution or archival. + */ + JackSessionSave = 1, + + /** + * Save the session completly, then quit. + * + * The rules for saving are exactly the same as for JackSessionSave. + */ + JackSessionSaveAndQuit = 2, + + /** + * Save a session template. + * + * A session template is a "skeleton" of the session, but without any data. + * Clients must save a session that, when restored, will create the same + * ports as a full save would have. However, the actual data contained in + * the session may not be saved (e.g. a DAW would create the necessary + * tracks, but not save the actual recorded data). + */ + JackSessionSaveTemplate = 3 +}; + +typedef enum JackSessionEventType jack_session_event_type_t; + +/** + * @ref jack_session_flags_t bits + */ +enum JackSessionFlags { + /** + * An error occured while saving. + */ + JackSessionSaveError = 0x01, + + /** + * Client needs to be run in a terminal. + */ + JackSessionNeedTerminal = 0x02 +}; + +/** + * Session flags. + */ +typedef enum JackSessionFlags jack_session_flags_t; + +struct _jack_session_event { + /** + * The type of this session event. + */ + jack_session_event_type_t type; + + /** + * Session directory path, with trailing separator. + * + * This directory is exclusive to the client; when saving the client may + * create any files it likes in this directory. + */ + const char *session_dir; + + /** + * Client UUID which must be passed to jack_client_open on session load. + * + * The client can specify this in the returned command line, or save it + * in a state file within the session directory. + */ + const char *client_uuid; + + /** + * Reply (set by client): the command line needed to restore the client. + * + * This is a platform dependent command line. It must contain + * ${SESSION_DIR} instead of the actual session directory path. More + * generally, just as in session files, clients should not include any + * paths outside the session directory here as this makes + * archival/distribution impossible. + * + * This field is set to NULL by Jack when the event is delivered to the + * client. The client must set to allocated memory that is safe to + * free(). This memory will be freed by jack_session_event_free. + */ + char *command_line; + + /** + * Reply (set by client): Session flags. + */ + jack_session_flags_t flags; + + /** + * Future flags. Set to zero for now. + */ + uint32_t future; +}; + +typedef struct _jack_session_event jack_session_event_t; + +/** + * Prototype for the client supplied function that is called + * whenever a session notification is sent via jack_session_notify(). + * + * Ownership of the memory of @a event is passed to the application. + * It must be freed using jack_session_event_free when its not used anymore. + * + * The client must promptly call jack_session_reply for this event. + * + * @param event The event structure. + * @param arg Pointer to a client supplied structure. + */ +typedef void (*JackSessionCallback)(jack_session_event_t *event, + void *arg); + +/** + * Tell the JACK server to call @a session_callback when a session event + * is to be delivered. + * + * setting more than one session_callback per process is probably a design + * error. if you have a multiclient application its more sensible to create + * a jack_client with only a session callback set. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_session_callback (jack_client_t *client, + JackSessionCallback session_callback, + void *arg) JACK_WEAK_EXPORT; + +/** + * Reply to a session event. + * + * This can either be called directly from the callback, or later from a + * different thread. For example, it is possible to push the event through a + * queue and execute the save code from the GUI thread. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_session_reply (jack_client_t *client, + jack_session_event_t *event) JACK_WEAK_EXPORT; + + +/** + * Free memory used by a jack_session_event_t. + * + * This also frees the memory used by the command_line pointer, if its non NULL. + */ +void jack_session_event_free (jack_session_event_t *event) JACK_WEAK_EXPORT; + + +/** + * Get the assigned uuid for client. + * Safe to call from callback and all other threads. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_client_get_uuid (jack_client_t *client) JACK_WEAK_EXPORT; + +/** + * @} + */ + +/** + * @defgroup JackSessionManagerAPI API for a session manager. + * + * @{ + */ + +typedef struct { + const char *uuid; + const char *client_name; + const char *command; + jack_session_flags_t flags; +} jack_session_command_t; + +/** + * Send an event to all clients listening for session callbacks. + * + * The returned strings of the clients are accumulated and returned as an array + * of jack_session_command_t. its terminated by ret[i].uuid == NULL target == + * NULL means send to all interested clients. otherwise a clientname + */ +jack_session_command_t *jack_session_notify ( + jack_client_t* client, + const char *target, + jack_session_event_type_t type, + const char *path) JACK_WEAK_EXPORT; + +/** + * Free the memory allocated by a session command. + */ +void jack_session_commands_free (jack_session_command_t *cmds) JACK_WEAK_EXPORT; + +/** + * Reserve a client name and associate it with a UUID. + * + * When a client later calls jack_client_open() and specifies the UUID, jackd + * will assign the reserved name. This allows a session manager to know in + * advance under which client name its managed clients will appear. + * + * @return 0 on success, otherwise a non-zero error code + */ +int +jack_reserve_client_name (jack_client_t *client, + const char *name, + const char *uuid) JACK_WEAK_EXPORT; + +/** + * Find out whether a client has set up a session callback. + * + * @return 0 when the client has no session callback, 1 when it has one. + * -1 on error. + */ +int +jack_client_has_session_callback (jack_client_t *client, const char *client_name) JACK_WEAK_EXPORT; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/jack/statistics.h b/include/jack/statistics.h new file mode 100644 index 00000000000..36b6f88fcba --- /dev/null +++ b/include/jack/statistics.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2004 Rui Nuno Capela, Lee Revell +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public License +* as published by the Free Software Foundation; either version 2.1 +* 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this program; if not, write to the Free +* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +* 02111-1307, USA. +* +*/ + +#ifndef __statistics_h__ +#define __statistics_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/** + * @return the maximum delay reported by the backend since + * startup or reset. When compared to the period size in usecs, this + * can be used to estimate the ideal period size for a given setup. + */ +float jack_get_max_delayed_usecs (jack_client_t *client); + +/** + * @return the delay in microseconds due to the most recent XRUN + * occurrence. This probably only makes sense when called from a @ref + * JackXRunCallback defined using jack_set_xrun_callback(). + */ +float jack_get_xrun_delayed_usecs (jack_client_t *client); + +/** + * Reset the maximum delay counter. This would be useful + * to estimate the effect that a change to the configuration of a running + * system (e.g. toggling kernel preemption) has on the delay + * experienced by JACK, without having to restart the JACK engine. + */ +void jack_reset_max_delayed_usecs (jack_client_t *client); + +#ifdef __cplusplus +} +#endif + +#endif /* __statistics_h__ */ diff --git a/include/jack/systemdeps.h b/include/jack/systemdeps.h new file mode 100644 index 00000000000..7cf144aab67 --- /dev/null +++ b/include/jack/systemdeps.h @@ -0,0 +1,128 @@ +/* +Copyright (C) 2004-2012 Grame + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __jack_systemdeps_h__ +#define __jack_systemdeps_h__ + +#ifndef POST_PACKED_STRUCTURE + + #ifdef __GNUC__ + /* POST_PACKED_STRUCTURE needs to be a macro which + expands into a compiler directive. The directive must + tell the compiler to arrange the preceding structure + declaration so that it is packed on byte-boundaries rather + than use the natural alignment of the processor and/or + compiler. + */ + + #define PRE_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE __attribute__((__packed__)) + + #else + + #ifdef _MSC_VER + #define PRE_PACKED_STRUCTURE1 __pragma(pack(push,1)) + #define PRE_PACKED_STRUCTURE PRE_PACKED_STRUCTURE1 + /* PRE_PACKED_STRUCTURE needs to be a macro which + expands into a compiler directive. The directive must + tell the compiler to arrange the following structure + declaration so that it is packed on byte-boundaries rather + than use the natural alignment of the processor and/or + compiler. + */ + #define POST_PACKED_STRUCTURE ;__pragma(pack(pop)) + /* and POST_PACKED_STRUCTURE needs to be a macro which + restores the packing to its previous setting */ + #else + #define PRE_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE + #endif /* _MSC_VER */ + + #endif /* __GNUC__ */ + +#endif + +#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(GNU_WIN32) + + #include + + #ifdef _MSC_VER /* Microsoft compiler */ + #define __inline__ inline + #if (!defined(int8_t) && !defined(_STDINT_H)) + #define __int8_t_defined + typedef char int8_t; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef long int32_t; + typedef unsigned long uint32_t; + typedef LONGLONG int64_t; + typedef ULONGLONG uint64_t; + #endif + #elif __MINGW32__ /* MINGW */ + #include + #include + #else /* other compilers ...*/ + #include + #include + #include + #endif + + #if !defined(_PTHREAD_H) && !defined(PTHREAD_WIN32) + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to HANDLE here. + */ + typedef HANDLE jack_native_thread_t; + #else + #ifdef PTHREAD_WIN32 // Added by JE - 10-10-2011 + #include // Makes sure we #include the ptw32 version ! + #endif + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to pthread_t here. + */ + typedef pthread_t jack_native_thread_t; + #endif + +#endif /* _WIN32 && !__CYGWIN__ && !GNU_WIN32 */ + +#if defined(__APPLE__) || defined(__linux__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) + + #if defined(__CYGWIN__) || defined(GNU_WIN32) + #include + #endif + #include + #include + #include + + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to pthread_t here. + */ + typedef pthread_t jack_native_thread_t; + +#endif /* __APPLE__ || __linux__ || __sun__ || sun */ + +#if defined(__arm__) || defined(__ppc__) || defined(__powerpc__) + #undef POST_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE +#endif /* __arm__ || __ppc__ || __powerpc__ */ + +#endif /* __jack_systemdeps_h__ */ diff --git a/include/jack/thread.h b/include/jack/thread.h new file mode 100644 index 00000000000..8b2c3944bda --- /dev/null +++ b/include/jack/thread.h @@ -0,0 +1,160 @@ +/* + Copyright (C) 2004 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_thread_h__ +#define __jack_thread_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +/* use 512KB stack per thread - the default is way too high to be feasible + * with mlockall() on many systems */ +#define THREAD_STACK 524288 + +/** @file thread.h + * + * Library functions to standardize thread creation for JACK and its + * clients. These interfaces hide some system variations in the + * handling of realtime scheduling and associated privileges. + */ + +/** + * @defgroup ClientThreads Creating and managing client threads + * @{ + */ + + /** + * @returns if JACK is running with realtime scheduling, this returns + * the priority that any JACK-created client threads will run at. + * Otherwise returns -1. + */ + +int jack_client_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @returns if JACK is running with realtime scheduling, this returns + * the maximum priority that a JACK client thread should use if the thread + * is subject to realtime scheduling. Otherwise returns -1. + */ + +int jack_client_max_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Attempt to enable realtime scheduling for a thread. On some + * systems that may require special privileges. + * + * @param thread POSIX thread ID. + * @param priority requested thread priority. + * + * @returns 0, if successful; EPERM, if the calling process lacks + * required realtime privileges; otherwise some other error number. + */ +int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Create a thread for JACK or one of its clients. The thread is + * created executing @a start_routine with @a arg as its sole + * argument. + * + * @param client the JACK client for whom the thread is being created. May be + * NULL if the client is being created within the JACK server. + * @param thread place to return POSIX thread ID. + * @param priority thread priority, if realtime. + * @param realtime true for the thread to use realtime scheduling. On + * some systems that may require special privileges. + * @param start_routine function the thread calls when it starts. + * @param arg parameter passed to the @a start_routine. + * + * @returns 0, if successful; otherwise some error number. + */ +int jack_client_create_thread (jack_client_t* client, + jack_native_thread_t *thread, + int priority, + int realtime, /* boolean */ + void *(*start_routine)(void*), + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Drop realtime scheduling for a thread. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ +int jack_drop_real_time_scheduling (jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Stop the thread, waiting for the thread handler to terminate. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ +int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Kill the thread. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ + int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +#ifndef _WIN32 + + typedef int (*jack_thread_creator_t)(pthread_t*, + const pthread_attr_t*, + void* (*function)(void*), + void* arg); +/** + * This function can be used in very very specialized cases + * where it is necessary that client threads created by JACK + * are created by something other than pthread_create(). After + * it is used, any threads that JACK needs for the client will + * will be created by calling the function passed to this + * function. + * + * No normal application/client should consider calling this. + * The specific case for which it was created involves running + * win32/x86 plugins under Wine on Linux, where it is necessary + * that all threads that might call win32 functions are known + * to Wine. + * + * Set it to NULL to restore thread creation function. + * + * @param creator a function that creates a new thread + * + */ +void jack_set_thread_creator (jack_thread_creator_t creator) JACK_OPTIONAL_WEAK_EXPORT; + +#endif + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_thread_h__ */ diff --git a/include/jack/transport.h b/include/jack/transport.h new file mode 100644 index 00000000000..9f18ef68876 --- /dev/null +++ b/include/jack/transport.h @@ -0,0 +1,247 @@ +/* + Copyright (C) 2002 Paul Davis + Copyright (C) 2003 Jack O'Quin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_transport_h__ +#define __jack_transport_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @defgroup TransportControl Transport and Timebase control + * @{ + */ + +/** + * Called by the timebase master to release itself from that + * responsibility. + * + * If the timebase master releases the timebase or leaves the JACK + * graph for any reason, the JACK engine takes over at the start of + * the next process cycle. The transport state does not change. If + * rolling, it continues to play, with frame numbers as the only + * available position information. + * + * @see jack_set_timebase_callback + * + * @param client the JACK client structure. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_release_timebase (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Register (or unregister) as a slow-sync client, one that cannot + * respond immediately to transport position changes. + * + * The @a sync_callback will be invoked at the first available + * opportunity after its registration is complete. If the client is + * currently active this will be the following process cycle, + * otherwise it will be the first cycle after calling jack_activate(). + * After that, it runs according to the ::JackSyncCallback rules. + * Clients that don't set a @a sync_callback are assumed to be ready + * immediately any time the transport wants to start. + * + * @param client the JACK client structure. + * @param sync_callback is a realtime function that returns TRUE when + * the client is ready. Setting @a sync_callback to NULL declares that + * this client no longer requires slow-sync processing. + * @param arg an argument for the @a sync_callback function. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_sync_callback (jack_client_t *client, + JackSyncCallback sync_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the timeout value for slow-sync clients. + * + * This timeout prevents unresponsive slow-sync clients from + * completely halting the transport mechanism. The default is two + * seconds. When the timeout expires, the transport starts rolling, + * even if some slow-sync clients are still unready. The @a + * sync_callbacks of these clients continue being invoked, giving them + * a chance to catch up. + * + * @see jack_set_sync_callback + * + * @param client the JACK client structure. + * @param timeout is delay (in microseconds) before the timeout expires. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_sync_timeout (jack_client_t *client, + jack_time_t timeout) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Register as timebase master for the JACK subsystem. + * + * The timebase master registers a callback that updates extended + * position information such as beats or timecode whenever necessary. + * Without this extended information, there is no need for this + * function. + * + * There is never more than one master at a time. When a new client + * takes over, the former @a timebase_callback is no longer called. + * Taking over the timebase may be done conditionally, so it fails if + * there was a master already. + * + * @param client the JACK client structure. + * @param conditional non-zero for a conditional request. + * @param timebase_callback is a realtime function that returns + * position information. + * @param arg an argument for the @a timebase_callback function. + * + * @return + * - 0 on success; + * - EBUSY if a conditional request fails because there was already a + * timebase master; + * - other non-zero error code. + */ +int jack_set_timebase_callback (jack_client_t *client, + int conditional, + JackTimebaseCallback timebase_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Reposition the transport to a new frame number. + * + * May be called at any time by any client. The new position takes + * effect in two process cycles. If there are slow-sync clients and + * the transport is already rolling, it will enter the + * ::JackTransportStarting state and begin invoking their @a + * sync_callbacks until ready. This function is realtime-safe. + * + * @see jack_transport_reposition, jack_set_sync_callback + * + * @param client the JACK client structure. + * @param frame frame number of new transport position. + * + * @return 0 if valid request, non-zero otherwise. + */ +int jack_transport_locate (jack_client_t *client, + jack_nframes_t frame) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Query the current transport state and position. + * + * This function is realtime-safe, and can be called from any thread. + * If called from the process thread, @a pos corresponds to the first + * frame of the current cycle and the state returned is valid for the + * entire cycle. + * + * @param client the JACK client structure. + * @param pos pointer to structure for returning current transport + * position; @a pos->valid will show which fields contain valid data. + * If @a pos is NULL, do not return position information. + * + * @return Current transport state. + */ +jack_transport_state_t jack_transport_query (const jack_client_t *client, + jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Return an estimate of the current transport frame, + * including any time elapsed since the last transport + * positional update. + * + * @param client the JACK client structure + */ +jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Request a new transport position. + * + * May be called at any time by any client. The new position takes + * effect in two process cycles. If there are slow-sync clients and + * the transport is already rolling, it will enter the + * ::JackTransportStarting state and begin invoking their @a + * sync_callbacks until ready. This function is realtime-safe. + * + * @see jack_transport_locate, jack_set_sync_callback + * + * @param client the JACK client structure. + * @param pos requested new transport position. + * + * @return 0 if valid request, EINVAL if position structure rejected. + */ +int jack_transport_reposition (jack_client_t *client, + const jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Start the JACK transport rolling. + * + * Any client can make this request at any time. It takes effect no + * sooner than the next process cycle, perhaps later if there are + * slow-sync clients. This function is realtime-safe. + * + * @see jack_set_sync_callback + * + * @param client the JACK client structure. + */ +void jack_transport_start (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Stop the JACK transport. + * + * Any client can make this request at any time. It takes effect on + * the next process cycle. This function is realtime-safe. + * + * @param client the JACK client structure. + */ +void jack_transport_stop (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Gets the current transport info structure (deprecated). + * + * @param client the JACK client structure. + * @param tinfo current transport info structure. The "valid" field + * describes which fields contain valid data. + * + * @deprecated This is for compatibility with the earlier transport + * interface. Use jack_transport_query(), instead. + * + * @pre Must be called from the process thread. + */ +void jack_get_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the transport info structure (deprecated). + * + * @deprecated This function still exists for compatibility with the + * earlier transport interface, but it does nothing. Instead, define + * a ::JackTimebaseCallback. + */ +void jack_set_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_transport_h__ */ diff --git a/include/jack/types.h b/include/jack/types.h new file mode 100644 index 00000000000..4641a99f281 --- /dev/null +++ b/include/jack/types.h @@ -0,0 +1,740 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_types_h__ +#define __jack_types_h__ + +#include + +typedef uint64_t jack_uuid_t; + +typedef int32_t jack_shmsize_t; + +/** + * Type used to represent sample frame counts. + */ +typedef uint32_t jack_nframes_t; + +/** + * Maximum value that can be stored in jack_nframes_t + */ +#define JACK_MAX_FRAMES (4294967295U) /* This should be UINT32_MAX, but C++ has a problem with that. */ + +/** + * Type used to represent the value of free running + * monotonic clock with units of microseconds. + */ +typedef uint64_t jack_time_t; + +/** + * Maximum size of @a load_init string passed to an internal client + * jack_initialize() function via jack_internal_client_load(). + */ +#define JACK_LOAD_INIT_LIMIT 1024 + +/** + * jack_intclient_t is an opaque type representing a loaded internal + * client. You may only access it using the API provided in @ref + * intclient.h "". + */ +typedef uint64_t jack_intclient_t; + +/** + * jack_port_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_port jack_port_t; + +/** + * jack_client_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_client jack_client_t; + +/** + * Ports have unique ids. A port registration callback is the only + * place you ever need to know their value. + */ +typedef uint32_t jack_port_id_t; + +typedef uint32_t jack_port_type_id_t; + +/** + * @ref jack_options_t bits + */ +enum JackOptions { + + /** + * Null value to use when no option bits are needed. + */ + JackNullOption = 0x00, + + /** + * Do not automatically start the JACK server when it is not + * already running. This option is always selected if + * \$JACK_NO_START_SERVER is defined in the calling process + * environment. + */ + JackNoStartServer = 0x01, + + /** + * Use the exact client name requested. Otherwise, JACK + * automatically generates a unique one, if needed. + */ + JackUseExactName = 0x02, + + /** + * Open with optional (char *) server_name parameter. + */ + JackServerName = 0x04, + + /** + * Load internal client from optional (char *) + * load_name. Otherwise use the @a client_name. + */ + JackLoadName = 0x08, + + /** + * Pass optional (char *) load_init string to the + * jack_initialize() entry point of an internal client. + */ + JackLoadInit = 0x10, + + /** + * pass a SessionID Token this allows the sessionmanager to identify the client again. + */ + JackSessionID = 0x20 +}; + +/** Valid options for opening an external client. */ +#define JackOpenOptions (JackSessionID|JackServerName|JackNoStartServer|JackUseExactName) + +/** Valid options for loading an internal client. */ +#define JackLoadOptions (JackLoadInit|JackLoadName|JackUseExactName) + +/** + * Options for several JACK operations, formed by OR-ing together the + * relevant @ref JackOptions bits. + */ +typedef enum JackOptions jack_options_t; + +/** + * @ref jack_status_t bits + */ +enum JackStatus { + + /** + * Overall operation failed. + */ + JackFailure = 0x01, + + /** + * The operation contained an invalid or unsupported option. + */ + JackInvalidOption = 0x02, + + /** + * The desired client name was not unique. With the @ref + * JackUseExactName option this situation is fatal. Otherwise, + * the name was modified by appending a dash and a two-digit + * number in the range "-01" to "-99". The + * jack_get_client_name() function will return the exact string + * that was used. If the specified @a client_name plus these + * extra characters would be too long, the open fails instead. + */ + JackNameNotUnique = 0x04, + + /** + * The JACK server was started as a result of this operation. + * Otherwise, it was running already. In either case the caller + * is now connected to jackd, so there is no race condition. + * When the server shuts down, the client will find out. + */ + JackServerStarted = 0x08, + + /** + * Unable to connect to the JACK server. + */ + JackServerFailed = 0x10, + + /** + * Communication error with the JACK server. + */ + JackServerError = 0x20, + + /** + * Requested client does not exist. + */ + JackNoSuchClient = 0x40, + + /** + * Unable to load internal client + */ + JackLoadFailure = 0x80, + + /** + * Unable to initialize client + */ + JackInitFailure = 0x100, + + /** + * Unable to access shared memory + */ + JackShmFailure = 0x200, + + /** + * Client's protocol version does not match + */ + JackVersionError = 0x400, + + /** + * Backend error + */ + JackBackendError = 0x800, + + /** + * Client zombified failure + */ + JackClientZombie = 0x1000 +}; + +/** + * Status word returned from several JACK operations, formed by + * OR-ing together the relevant @ref JackStatus bits. + */ +typedef enum JackStatus jack_status_t; + +/** + * @ref jack_latency_callback_mode_t + */ +enum JackLatencyCallbackMode { + + /** + * Latency Callback for Capture Latency. + * Input Ports have their latency value setup. + * In the Callback the client needs to set the latency of the output ports + */ + JackCaptureLatency, + + /** + * Latency Callback for Playback Latency. + * Output Ports have their latency value setup. + * In the Callback the client needs to set the latency of the input ports + */ + JackPlaybackLatency + +}; + +/** + * Type of Latency Callback (Capture or Playback) + */ +typedef enum JackLatencyCallbackMode jack_latency_callback_mode_t; + +/** + * Prototype for the client supplied function that is called + * by the engine when port latencies need to be recalculated + * + * @param mode playback or capture latency + * @param arg pointer to a client supplied data + * + * @return zero on success, non-zero on error + */ +typedef void (*JackLatencyCallback)(jack_latency_callback_mode_t mode, void *arg); + +/** + * the new latency API operates on Ranges. + */ +PRE_PACKED_STRUCTURE +struct _jack_latency_range +{ + /** + * minimum latency + */ + jack_nframes_t min; + /** + * maximum latency + */ + jack_nframes_t max; +} POST_PACKED_STRUCTURE; + +typedef struct _jack_latency_range jack_latency_range_t; + +/** + * Prototype for the client supplied function that is called + * by the engine anytime there is work to be done. + * + * @pre nframes == jack_get_buffer_size() + * @pre nframes == pow(2,x) + * + * @param nframes number of frames to process + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackProcessCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client thread routine called + * by the engine when the client is inserted in the graph. + * + * @param arg pointer to a client supplied structure + * + */ +typedef void *(*JackThreadCallback)(void* arg); + +/** + * Prototype for the client supplied function that is called + * once after the creation of the thread in which other + * callbacks will be made. Special thread characteristics + * can be set from this callback, for example. This is a + * highly specialized callback and most clients will not + * and should not use it. + * + * @param arg pointer to a client supplied structure + * + * @return void + */ +typedef void (*JackThreadInitCallback)(void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever the processing graph is reordered. + * + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackGraphOrderCallback)(void *arg); + +/** + * Prototype for the client-supplied function that is called whenever + * an xrun has occured. + * + * @see jack_get_xrun_delayed_usecs() + * + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackXRunCallback)(void *arg); + +/** + * Prototype for the @a bufsize_callback that is invoked whenever the + * JACK engine buffer size changes. Although this function is called + * in the JACK process thread, the normal process cycle is suspended + * during its operation, causing a gap in the audio flow. So, the @a + * bufsize_callback can allocate storage, touch memory not previously + * referenced, and perform other operations that are not realtime + * safe. + * + * @param nframes buffer size + * @param arg pointer supplied by jack_set_buffer_size_callback(). + * + * @return zero on success, non-zero on error + */ +typedef int (*JackBufferSizeCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client supplied function that is called + * when the engine sample rate changes. + * + * @param nframes new engine sample rate + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackSampleRateCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a port is registered or unregistered. + * + * @param port the ID of the port + * @param arg pointer to a client supplied data + * @param register non-zero if the port is being registered, + * zero if the port is being unregistered + */ +typedef void (*JackPortRegistrationCallback)(jack_port_id_t port, int /* register */, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a client is registered or unregistered. + * + * @param name a null-terminated string containing the client name + * @param register non-zero if the client is being registered, + * zero if the client is being unregistered + * @param arg pointer to a client supplied structure + */ +typedef void (*JackClientRegistrationCallback)(const char* name, int /* register */, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a port is connected or disconnected. + * + * @param a one of two ports connected or disconnected + * @param b one of two ports connected or disconnected + * @param connect non-zero if ports were connected + * zero if ports were disconnected + * @param arg pointer to a client supplied data + */ +typedef void (*JackPortConnectCallback)(jack_port_id_t a, jack_port_id_t b, int connect, void* arg); + +/** + * Prototype for the client supplied function that is called + * whenever the port name has been changed. + * + * @param port the port that has been renamed + * @param new_name the new name + * @param arg pointer to a client supplied structure + */ +typedef void (*JackPortRenameCallback)(jack_port_id_t port, const char* old_name, const char* new_name, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd starts or stops freewheeling. + * + * @param starting non-zero if we start starting to freewheel, zero otherwise + * @param arg pointer to a client supplied structure + */ +typedef void (*JackFreewheelCallback)(int starting, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client ressources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + * + * @param arg pointer to a client supplied structure + */ +typedef void (*JackShutdownCallback)(void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client ressources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + + * @param code a status word, formed by OR-ing together the relevant @ref JackStatus bits. + * @param reason a string describing the shutdown reason (backend failure, server crash... etc...). + * Note that this string will not be available anymore after the callback returns, so possibly copy it. + * @param arg pointer to a client supplied structure + */ +typedef void (*JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg); + +/** + * Used for the type argument of jack_port_register() for default + * audio ports and midi ports. + */ +#define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define JACK_DEFAULT_MIDI_TYPE "8 bit raw midi" + +/** + * For convenience, use this typedef if you want to be able to change + * between float and double. You may want to typedef sample_t to + * jack_default_audio_sample_t in your application. + */ +typedef float jack_default_audio_sample_t; + +/** + * A port has a set of flags that are formed by AND-ing together the + * desired values from the list below. The flags "JackPortIsInput" and + * "JackPortIsOutput" are mutually exclusive and it is an error to use + * them both. + */ +enum JackPortFlags { + + /** + * if JackPortIsInput is set, then the port can receive + * data. + */ + JackPortIsInput = 0x1, + + /** + * if JackPortIsOutput is set, then data can be read from + * the port. + */ + JackPortIsOutput = 0x2, + + /** + * if JackPortIsPhysical is set, then the port corresponds + * to some kind of physical I/O connector. + */ + JackPortIsPhysical = 0x4, + + /** + * if JackPortCanMonitor is set, then a call to + * jack_port_request_monitor() makes sense. + * + * Precisely what this means is dependent on the client. A typical + * result of it being called with TRUE as the second argument is + * that data that would be available from an output port (with + * JackPortIsPhysical set) is sent to a physical output connector + * as well, so that it can be heard/seen/whatever. + * + * Clients that do not control physical interfaces + * should never create ports with this bit set. + */ + JackPortCanMonitor = 0x8, + + /** + * JackPortIsTerminal means: + * + * for an input port: the data received by the port + * will not be passed on or made + * available at any other port + * + * for an output port: the data available at the port + * does not originate from any other port + * + * Audio synthesizers, I/O hardware interface clients, HDR + * systems are examples of clients that would set this flag for + * their ports. + */ + JackPortIsTerminal = 0x10, + +}; + +/** + * Transport states. + */ +typedef enum { + + /* the order matters for binary compatibility */ + JackTransportStopped = 0, /**< Transport halted */ + JackTransportRolling = 1, /**< Transport playing */ + JackTransportLooping = 2, /**< For OLD_TRANSPORT, now ignored */ + JackTransportStarting = 3, /**< Waiting for sync ready */ + JackTransportNetStarting = 4, /**< Waiting for sync ready on the network*/ + +} jack_transport_state_t; + +typedef uint64_t jack_unique_t; /**< Unique ID (opaque) */ + +/** + * Optional struct jack_position_t fields. + */ +typedef enum { + + JackPositionBBT = 0x10, /**< Bar, Beat, Tick */ + JackPositionTimecode = 0x20, /**< External timecode */ + JackBBTFrameOffset = 0x40, /**< Frame offset of BBT information */ + JackAudioVideoRatio = 0x80, /**< audio frames per video frame */ + JackVideoFrameOffset = 0x100 /**< frame offset of first video frame */ + +} jack_position_bits_t; + +/** all valid position bits */ +#define JACK_POSITION_MASK (JackPositionBBT|JackPositionTimecode) +#define EXTENDED_TIME_INFO + +PRE_PACKED_STRUCTURE +struct _jack_position { + + /* these four cannot be set from clients: the server sets them */ + jack_unique_t unique_1; /**< unique ID */ + jack_time_t usecs; /**< monotonic, free-rolling */ + jack_nframes_t frame_rate; /**< current frame rate (per second) */ + jack_nframes_t frame; /**< frame number, always present */ + + jack_position_bits_t valid; /**< which other fields are valid */ + + /* JackPositionBBT fields: */ + int32_t bar; /**< current bar */ + int32_t beat; /**< current beat-within-bar */ + int32_t tick; /**< current tick-within-beat */ + double bar_start_tick; + + float beats_per_bar; /**< time signature "numerator" */ + float beat_type; /**< time signature "denominator" */ + double ticks_per_beat; + double beats_per_minute; + + /* JackPositionTimecode fields: (EXPERIMENTAL: could change) */ + double frame_time; /**< current time in seconds */ + double next_time; /**< next sequential frame_time + (unless repositioned) */ + + /* JackBBTFrameOffset fields: */ + jack_nframes_t bbt_offset; /**< frame offset for the BBT fields + (the given bar, beat, and tick + values actually refer to a time + frame_offset frames before the + start of the cycle), should + be assumed to be 0 if + JackBBTFrameOffset is not + set. If JackBBTFrameOffset is + set and this value is zero, the BBT + time refers to the first frame of this + cycle. If the value is positive, + the BBT time refers to a frame that + many frames before the start of the + cycle. */ + + /* JACK video positional data (experimental) */ + + float audio_frames_per_video_frame; /**< number of audio frames + per video frame. Should be assumed + zero if JackAudioVideoRatio is not + set. If JackAudioVideoRatio is set + and the value is zero, no video + data exists within the JACK graph */ + + jack_nframes_t video_offset; /**< audio frame at which the first video + frame in this cycle occurs. Should + be assumed to be 0 if JackVideoFrameOffset + is not set. If JackVideoFrameOffset is + set, but the value is zero, there is + no video frame within this cycle. */ + + /* For binary compatibility, new fields should be allocated from + * this padding area with new valid bits controlling access, so + * the existing structure size and offsets are preserved. */ + int32_t padding[7]; + + /* When (unique_1 == unique_2) the contents are consistent. */ + jack_unique_t unique_2; /**< unique ID */ + +} POST_PACKED_STRUCTURE; + +typedef struct _jack_position jack_position_t; + +/** + * Prototype for the @a sync_callback defined by slow-sync clients. + * When the client is active, this callback is invoked just before + * process() in the same thread. This occurs once after registration, + * then subsequently whenever some client requests a new position, or + * the transport enters the ::JackTransportStarting state. This + * realtime function must not wait. + * + * The transport @a state will be: + * + * - ::JackTransportStopped when a new position is requested; + * - ::JackTransportStarting when the transport is waiting to start; + * - ::JackTransportRolling when the timeout has expired, and the + * position is now a moving target. + * + * @param state current transport state. + * @param pos new transport position. + * @param arg the argument supplied by jack_set_sync_callback(). + * + * @return TRUE (non-zero) when ready to roll. + */ +typedef int (*JackSyncCallback)(jack_transport_state_t state, + jack_position_t *pos, + void *arg); + + +/** + * Prototype for the @a timebase_callback used to provide extended + * position information. Its output affects all of the following + * process cycle. This realtime function must not wait. + * + * This function is called immediately after process() in the same + * thread whenever the transport is rolling, or when any client has + * requested a new position in the previous cycle. The first cycle + * after jack_set_timebase_callback() is also treated as a new + * position, or the first cycle after jack_activate() if the client + * had been inactive. + * + * The timebase master may not use its @a pos argument to set @a + * pos->frame. To change position, use jack_transport_reposition() or + * jack_transport_locate(). These functions are realtime-safe, the @a + * timebase_callback can call them directly. + * + * @param state current transport state. + * @param nframes number of frames in current period. + * @param pos address of the position structure for the next cycle; @a + * pos->frame will be its frame number. If @a new_pos is FALSE, this + * structure contains extended position information from the current + * cycle. If TRUE, it contains whatever was set by the requester. + * The @a timebase_callback's task is to update the extended + * information here. + * @param new_pos TRUE (non-zero) for a newly requested @a pos, or for + * the first cycle after the @a timebase_callback is defined. + * @param arg the argument supplied by jack_set_timebase_callback(). + */ +typedef void (*JackTimebaseCallback)(jack_transport_state_t state, + jack_nframes_t nframes, + jack_position_t *pos, + int new_pos, + void *arg); + +/********************************************************************* + * The following interfaces are DEPRECATED. They are only provided + * for compatibility with the earlier JACK transport implementation. + *********************************************************************/ + +/** + * Optional struct jack_transport_info_t fields. + * + * @see jack_position_bits_t. + */ +typedef enum { + + JackTransportState = 0x1, /**< Transport state */ + JackTransportPosition = 0x2, /**< Frame number */ + JackTransportLoop = 0x4, /**< Loop boundaries (ignored) */ + JackTransportSMPTE = 0x8, /**< SMPTE (ignored) */ + JackTransportBBT = 0x10 /**< Bar, Beat, Tick */ + +} jack_transport_bits_t; + +/** + * Deprecated struct for transport position information. + * + * @deprecated This is for compatibility with the earlier transport + * interface. Use the jack_position_t struct, instead. + */ +typedef struct { + + /* these two cannot be set from clients: the server sets them */ + + jack_nframes_t frame_rate; /**< current frame rate (per second) */ + jack_time_t usecs; /**< monotonic, free-rolling */ + + jack_transport_bits_t valid; /**< which fields are legal to read */ + jack_transport_state_t transport_state; + jack_nframes_t frame; + jack_nframes_t loop_start; + jack_nframes_t loop_end; + + long smpte_offset; /**< SMPTE offset (from frame 0) */ + float smpte_frame_rate; /**< 29.97, 30, 24 etc. */ + + int bar; + int beat; + int tick; + double bar_start_tick; + + float beats_per_bar; + float beat_type; + double ticks_per_beat; + double beats_per_minute; + +} jack_transport_info_t; + + +#endif /* __jack_types_h__ */ diff --git a/include/jack/weakjack.h b/include/jack/weakjack.h new file mode 100644 index 00000000000..4a5e7d30939 --- /dev/null +++ b/include/jack/weakjack.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2010 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __weakjack_h__ +#define __weakjack_h__ + +/** + * @defgroup WeakLinkage Managing support for newer/older versions of JACK + * @{ One challenge faced by developers is that of taking + * advantage of new features introduced in new versions + * of [ JACK ] while still supporting older versions of + * the system. Normally, if an application uses a new + * feature in a library/API, it is unable to run on + * earlier versions of the library/API that do not + * support that feature. Such applications would either + * fail to launch or crash when an attempt to use the + * feature was made. This problem cane be solved using + * weakly-linked symbols. + * + * When a symbol in a framework is defined as weakly + * linked, the symbol does not have to be present at + * runtime for a process to continue running. The static + * linker identifies a weakly linked symbol as such in + * any code module that references the symbol. The + * dynamic linker uses this same information at runtime + * to determine whether a process can continue + * running. If a weakly linked symbol is not present in + * the framework, the code module can continue to run as + * long as it does not reference the symbol. However, if + * the symbol is present, the code can use it normally. + * + * (adapted from: http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html) + * + * A concrete example will help. Suppose that someone uses a version + * of a JACK client we'll call "Jill". Jill was linked against a version + * of JACK that contains a newer part of the API (say, jack_set_latency_callback()) + * and would like to use it if it is available. + * + * When Jill is run on a system that has a suitably "new" version of + * JACK, this function will be available entirely normally. But if Jill + * is run on a system with an old version of JACK, the function isn't + * available. + * + * With normal symbol linkage, this would create a startup error whenever + * someone tries to run Jill with the "old" version of JACK. However, functions + * added to JACK after version 0.116.2 are all declared to have "weak" linkage + * which means that their abscence doesn't cause an error during program + * startup. Instead, Jill can test whether or not the symbol jack_set_latency_callback + * is null or not. If its null, it means that the JACK installed on this machine + * is too old to support this function. If its not null, then Jill can use it + * just like any other function in the API. For example: + * + * \code + * if (jack_set_latency_callback) { + * jack_set_latency_callback (jill_client, jill_latency_callback, arg); + * } + * \endcode + * + * However, there are clients that may want to use this approach to parts of the + * the JACK API that predate 0.116.2. For example, they might want to see if even + * really old basic parts of the API like jack_client_open() exist at runtime. + * + * Such clients should include before any other JACK header. + * This will make the \b entire JACK API be subject to weak linkage, so that any + * and all functions can be checked for existence at runtime. It is important + * to understand that very few clients need to do this - if you use this + * feature you should have a clear reason to do so. + * + * + */ + +#ifdef __APPLE__ +#define WEAK_ATTRIBUTE weak_import +#else +#define WEAK_ATTRIBUTE __weak__ +#endif + +#ifndef JACK_OPTIONAL_WEAK_EXPORT +/* JACK_OPTIONAL_WEAK_EXPORT needs to be a macro which + expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of + the symbol it used with. For this to work fully may + require linker arguments for the client as well. +*/ +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) +#else +/* Add other things here for non-gcc platforms */ +#endif +#endif + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +/* JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT needs to be a macro + which expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of the + symbol it is used with AND optionally to mark the symbol + as deprecated. For this to work fully may require + linker arguments for the client as well. +*/ +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((WEAK_ATTRIBUTE,__deprecated__)) +#else +/* Add other things here for non-gcc platforms */ +#endif +#endif + +/*@}*/ + +#endif /* weakjack */ diff --git a/include/jack/weakmacros.h b/include/jack/weakmacros.h new file mode 100644 index 00000000000..e736a1ee28f --- /dev/null +++ b/include/jack/weakmacros.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 2010 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __weakmacros_h__ +#define __weakmacros_h__ + +/************************************************************* + * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function + * added to the JACK API after the 0.116.2 release. + * + * Functions that predate this release are marked with + * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile + * time in a variety of ways. The default definition is empty, + * so that these symbols get normal linkage. If you wish to + * use all JACK symbols with weak linkage, include + * before jack.h. + *************************************************************/ + +#ifdef __APPLE__ +#define WEAK_ATTRIBUTE weak_import +#else +#define WEAK_ATTRIBUTE __weak__ +#endif + +#ifndef JACK_WEAK_EXPORT +#ifdef __GNUC__ +/* JACK_WEAK_EXPORT needs to be a macro which + expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of + the symbol it used with. For this to work full may + require linker arguments in the client as well. +*/ + +#ifdef _WIN32 + /* + Not working with __declspec(dllexport) so normal linking + Linking with JackWeakAPI.cpp will be the preferred way. + */ + #define JACK_WEAK_EXPORT +#else + #define JACK_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) +#endif + +#else +/* Add other things here for non-gcc platforms */ + +#ifdef _WIN32 +#define JACK_WEAK_EXPORT +#endif + +#endif +#endif + +#ifndef JACK_WEAK_EXPORT +#define JACK_WEAK_EXPORT +#endif + +#ifndef JACK_OPTIONAL_WEAK_EXPORT +#define JACK_OPTIONAL_WEAK_EXPORT +#endif + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((__deprecated__)) +#else +/* Add other things here for non-gcc platforms */ + +#ifdef _WIN32 +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#endif + +#endif /* __GNUC__ */ + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#endif + +#endif + +#endif /* __weakmacros_h__ */ + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6c7e15df9df..bca7e696bf2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -402,6 +402,8 @@ IF(LMMS_BUILD_WIN32) COMMAND "curl" -OL "https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) INSTALL(FILES "${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" DESTINATION .) - INSTALL(CODE "EXECUTE_PROCESS(COMMAND ${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe)") + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " + ExecWait '\\\"$INSTDIR\\\\Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\\\" /install' +") ENDIF() ENDIF() From ab9a2a35bb54b4a76a330885deb97ff3b91770a7 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 1 Jun 2018 22:29:13 +0300 Subject: [PATCH 061/112] SampleBuffer: Use `resetData` instead of `beginBufferChange` when possible. --- include/SampleBuffer.h | 6 +- .../audio_file_processor.cpp | 1 - src/core/SampleBuffer.cpp | 55 +++++-------------- 3 files changed, 16 insertions(+), 46 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 8b851b2715e..bac35cc6c5d 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -273,7 +273,8 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject */ void reverse(bool shouldLockMixer=true); - void loadFromBase64(const QString & _data , sample_rate_t sampleRate, bool shouldLock); + void loadFromBase64(const QString & _data , sample_rate_t sampleRate); + public slots: void setAudioFile( const QString & _audio_file ); @@ -301,7 +302,7 @@ public slots: return const_cast(ptr); } - void changeAudioFile (QString audioFile, bool shouldLock, bool shouldKeepSettings); + void changeAudioFile (QString audioFile); static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); @@ -354,7 +355,6 @@ public slots: * @param bufferSampleRate The new m_data's sample rate. */ void doneBufferChange (bool shouldUnlock, - bool shouldKeepSettings, sample_rate_t bufferSampleRate, bool shouldUnlockMixer=true); signals: diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 3f2b2656e7c..2249ac72359 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -242,7 +242,6 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) else if( _this.attribute( "sampledata" ) != "" ) { m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) , - true, Engine::mixer ()->baseSampleRate ()); qWarning("Using default sampleRate. That could lead to invalid values"); } else { diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index f1a36dba6dd..64e5509b03b 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -66,7 +66,6 @@ SampleBuffer::SampleBuffer() : connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); beginBufferChange (false); doneBufferChange (false, /* shouldLock */ - false, /* shouldKeepSettings */ m_sampleRate); } @@ -78,11 +77,11 @@ SampleBuffer::SampleBuffer( const QString & _audio_file, { if( _is_base64_data ) { - loadFromBase64( _audio_file, false, sampleRate ); + loadFromBase64( _audio_file, sampleRate ); } else { - changeAudioFile (_audio_file, false, false); + changeAudioFile (_audio_file); } } @@ -90,13 +89,7 @@ SampleBuffer::SampleBuffer( const QString & _audio_file, SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t sampleRate) : SampleBuffer() { - beginBufferChange (false); - m_data = std::move (movedData); - doneBufferChange ( - /* shouldLock */ false, - /* shouldKeepSettings */ false, - sampleRate - ); + resetData (std::move(movedData), sampleRate, false); } void SampleBuffer::saveSettings(QDomDocument &doc, QDomElement &_this) { @@ -126,7 +119,7 @@ void SampleBuffer::loadSettings(const QDomElement &_this) { } if (_this.hasAttribute ("data")) { - loadFromBase64 (_this.attribute("data"), m_sampleRate, true); + loadFromBase64 (_this.attribute("data"), m_sampleRate); } } @@ -141,18 +134,10 @@ void SampleBuffer::sampleRateChanged() { // sample rate is higher than the SampleBuffer's // sample rate. if (requiredSampleRate > sampleRate ()) { - beginBufferChange (true); - - m_data = resampleData (m_data, previousSampleRate, requiredSampleRate); - - doneBufferChange ( - /* shouldLock */ true, - /* shouldKeepSettings */ true, - requiredSampleRate - ); + resetData (resampleData (m_data, previousSampleRate, requiredSampleRate), + requiredSampleRate, + true); } - - } sample_rate_t SampleBuffer::mixerSampleRate() @@ -160,8 +145,7 @@ sample_rate_t SampleBuffer::mixerSampleRate() return Engine::mixer ()->processingSampleRate (); } -void SampleBuffer::changeAudioFile(QString audioFile, bool shouldLock, - bool shouldKeepSettings) +void SampleBuffer::changeAudioFile(QString audioFile) { if (audioFile == "") return; @@ -235,12 +219,7 @@ void SampleBuffer::changeAudioFile(QString audioFile, bool shouldLock, } if (! fileLoadError) { - // Ok, we have no errors; let's update the actual data. - beginBufferChange (shouldLock); - m_data = std::move(fileData); - doneBufferChange (shouldLock, - shouldKeepSettings, - samplerate); + resetData (std::move(fileData), samplerate); } else { QString title = tr( "Fail to open file" ); QString message = tr( "Audio files are limited to %1 MB " @@ -1001,13 +980,12 @@ QString & SampleBuffer::toBase64( QString & _dst ) const void SampleBuffer::setAudioFile( const QString & _audio_file ) { - changeAudioFile (_audio_file, true, false); + changeAudioFile (_audio_file); } -void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleRate, - bool shouldLock) +void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleRate) { char * dst = NULL; int dsize = 0; @@ -1020,12 +998,8 @@ void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleR delete[] dst; - beginBufferChange (shouldLock); - m_data = std::move(input); - m_audioFile = QString(); - doneBufferChange (shouldLock, - true, - sampleRate); + resetData (std::move(input), + sampleRate); } @@ -1191,7 +1165,6 @@ void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t } } doneBufferChange (true, /* lock */ - true, /* save settings */ this->sampleRate(), shouldLockMixer); } @@ -1203,7 +1176,6 @@ void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, m_data = std::move (newData); } doneBufferChange (true, /* lock */ - false, /* save settings */ dataSampleRate, shouldLockMixer); } @@ -1214,7 +1186,6 @@ void SampleBuffer::reverse(bool shouldLockMixer) { std::reverse(m_data.begin (), m_data.end ()); } doneBufferChange (true, /* should(Un)Lock? yes! */ - true, /* should we restore settings? */ sampleRate (), /* we have not made any change in the sample rate. */ shouldLockMixer); } From 8166e959c54d784f6821a1a03eff7409806bfa8e Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Fri, 1 Jun 2018 22:36:25 +0300 Subject: [PATCH 062/112] SampleBuffer: Make `frames()` thread-safe. --- include/SampleBuffer.h | 21 +++++++++++++++------ src/core/SampleBuffer.cpp | 15 +++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index bac35cc6c5d..64bc0e0440e 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -176,7 +176,10 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject inline f_cnt_t frames() const { - return m_data.size (); + dataReadLock(); + auto size = internalFrames(); + dataUnlock(); + return size; } inline float amplification() const @@ -217,7 +220,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject // dataUnlock(), out of loops for efficiency inline sample_t userWaveSample( const float _sample ) const { - f_cnt_t dataFrames = frames (); + f_cnt_t dataFrames = internalFrames(); const sampleFrame * data = this->data(); const float frame = _sample * dataFrames; f_cnt_t f1 = static_cast( frame ) % dataFrames; @@ -228,18 +231,18 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject return linearInterpolate( data[f1][0], data[ (f1 + 1) % dataFrames ][0], fraction( frame ) ); } - bool tryDataReadLock () + bool tryDataReadLock () const { return m_varLock.tryLockForRead (); } - void dataReadLock() + void dataReadLock() const { m_varLock.lockForRead(); } - void dataUnlock() + void dataUnlock() const { m_varLock.unlock(); } @@ -319,9 +322,15 @@ public slots: return m_data.data (); } + size_t internalFrames() const + { + return m_data.size (); + } + + QString m_audioFile; DataVector m_data; - QReadWriteLock m_varLock; + mutable QReadWriteLock m_varLock; f_cnt_t m_startFrame; f_cnt_t m_endFrame; f_cnt_t m_loopStartFrame; diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 64e5509b03b..9612690e1ee 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -972,7 +972,7 @@ QString SampleBuffer::openAndSetWaveformFile() QString & SampleBuffer::toBase64( QString & _dst ) const { base64::encode( (const char *) data (), - frames () * sizeof( sampleFrame ), _dst ); + internalFrames() * sizeof( sampleFrame ), _dst ); return _dst; } @@ -1117,22 +1117,13 @@ void SampleBuffer::beginBufferChange(bool shouldLock, bool shouldLockMixer) } void SampleBuffer::doneBufferChange(bool shouldUnlock, - bool shouldKeepSettings, sample_rate_t bufferSampleRate, bool shouldUnlockMixer) { setSampleRate (bufferSampleRate); - // TODO: reverse- and amplification-property is not covered - // by following code... - auto previousFrames = frames (); - if( shouldKeepSettings == false ) { - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = frames (); - } else { - m_data.resize (previousFrames); - } - + m_loopStartFrame = m_startFrame = 0; + m_loopEndFrame = m_endFrame = internalFrames(); if (shouldUnlock) { m_varLock.unlock (); } From 0ba71ca22e40b8e36ac0c1e9b897d9fafcaf22ea Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Jun 2018 08:07:20 +0300 Subject: [PATCH 063/112] SampleBuffer & SampleTCOView: Cache visualization while recording. --- include/SampleBuffer.h | 3 + include/SampleTrack.h | 24 +++++++ src/core/SampleBuffer.cpp | 41 +++++++----- src/tracks/SampleTrack.cpp | 134 ++++++++++++++++++++++++++++++------- 4 files changed, 162 insertions(+), 40 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 64bc0e0440e..7827bc35dc8 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -28,6 +28,7 @@ #include #include +#include #include @@ -131,6 +132,8 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject visualize( _p, _dr, _dr, _from_frame, _to_frame ); } + QPair visualizeToPoly( const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0); + inline const QString & audioFile() const { return m_audioFile; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 6e179f5162f..f8a5b9c2c35 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -126,8 +126,32 @@ public slots: private: + /** + * @brief Calculate the position and size for a visualization + * of a @arg totalTime frames fragment starting from + * @arg beginOffset. + * @param globalRect The parent rectangle. + * @param beginOffset Offset from the beginning of the tco. + * @param totalTime Total time we want to visualize. + * @param pixelsPerTact Current `pixelsPerTact()` result. + * @param shouldAddBorder Should we calculate border pixels? + */ + QRect getRectForSampleFragment (QRect globalRect, MidiTime beginOffset, + MidiTime totalTime, + float pixelsPerTact, bool shouldAddBorder=false); + SampleTCO * m_tco; QPixmap m_paintPixmap; + + + struct PaintCacheLine { + QPair paintPoly; + QRect paintRect; + float pixelsPerTact; + }; + + QVector m_paintMaps; + MidiTime m_cachedTime{0}; } ; diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 9612690e1ee..aa04fc1ef32 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -829,39 +829,48 @@ f_cnt_t SampleBuffer::getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame, f_cnt_t _to_frame ) { - if( frames () == 0 ) return; + auto polyPair = visualizeToPoly (_dr, _clip, _from_frame, _to_frame); - const bool focus_on_range = _to_frame <= frames () - && 0 <= _from_frame && _from_frame < _to_frame; + _p.setRenderHint( QPainter::Antialiasing ); + _p.drawPolyline (polyPair.first); + _p.drawPolyline (polyPair.second); +} + +QPair SampleBuffer::visualizeToPoly(const QRect &_dr, const QRect &_clip, + f_cnt_t _from_frame, f_cnt_t _to_frame) +{ + if( internalFrames () == 0 ) return {}; + + const bool focus_on_range = _to_frame <= internalFrames() + && _from_frame < _to_frame; //_p.setClipRect( _clip ); const int w = _dr.width(); const int h = _dr.height(); const int yb = h / 2 + _dr.y(); const float y_space = h*0.5f; - const int nb_frames = focus_on_range ? _to_frame - _from_frame : frames (); + const int nb_frames = focus_on_range ? _to_frame - _from_frame : internalFrames(); + if (nb_frames == 0) return {}; const int fpp = tLimit( nb_frames / w, 1, 20 ); - QPointF * l = new QPointF[nb_frames / fpp + 1]; - QPointF * r = new QPointF[nb_frames / fpp + 1]; + auto l = QPolygonF(); + auto r = QPolygonF(); + int n = 0; const int xb = _dr.x(); const int first = focus_on_range ? _from_frame : 0; - const int last = focus_on_range ? _to_frame : frames (); + const int last = focus_on_range ? _to_frame : internalFrames(); for( int frame = first; frame < last; frame += fpp ) { - l[n] = QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), - ( yb - ( m_data[frame][0] * y_space * m_amplification ) ) ); - r[n] = QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), - ( yb - ( m_data[frame][1] * y_space * m_amplification ) ) ); + l.push_back( QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), + ( yb - ( m_data[frame][0] * y_space * m_amplification ) ) )); + r.push_back(QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), + ( yb - ( m_data[frame][1] * y_space * m_amplification ) ) )); + ++n; } - _p.setRenderHint( QPainter::Antialiasing ); - _p.drawPolyline( l, (n-first-1) ); - _p.drawPolyline( r, (n-first-1) ); - delete[] l; - delete[] r; + return {std::move(l), std::move(r)}; } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index c57506038cb..df17358b909 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -189,7 +189,7 @@ void SampleTCO::updateLength() MidiTime SampleTCO::sampleLength() const { - return (int)( m_sampleBuffer->frames() / Engine::framesPerTick() ); + return (int)( m_sampleBuffer->frames() / Engine::framesPerTick(m_sampleBuffer->sampleRate ()) ); } @@ -256,8 +256,7 @@ void SampleTCO::loadSettings( const QDomElement & _this ) if (_this.hasAttribute ("data") && _this.attribute ("data") != QString()) { qWarning("Using default sampleRate. That could lead to invalid values1"); m_sampleBuffer->loadFromBase64 (_this.attribute ("data"), - Engine::mixer ()->baseSampleRate (), - true); + Engine::mixer ()->baseSampleRate ()); } else { m_sampleBuffer->restoreState (_this.firstChildElement (m_sampleBuffer->nodeName ())); } @@ -485,28 +484,72 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.setPen( !muted ? painter.pen().brush().color() : mutedColor() ); - const int spacing = TCO_BORDER_WIDTH + 1; - const float ppt = fixedTCOs() ? - ( parentWidget()->width() - 2 * TCO_BORDER_WIDTH ) - / (float) m_tco->length().getTact() : - pixelsPerTact(); + MidiTime currentSampleTime = m_tco->sampleLength(); + auto framesPerTick = Engine::framesPerTick(m_tco->sampleBuffer ()->sampleRate ()); + auto currentPixelsPerTact = pixelsPerTact(); + auto globalRect = getRectForSampleFragment (rect(), + 0, + currentSampleTime, + currentPixelsPerTact, + true); + + // Only TCOs that are being recorded into. + // Clear the cache and let it generate a new line + // and then visualize it. + if (!m_tco->isRecord () || !Engine::getSong ()->isRecording ()) { + m_cachedTime = 0; + m_paintMaps.clear (); + } - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - float ticksPerTact = DefaultTicksPerTact * nom / den; + // We've need to visualize more frames. + if (currentSampleTime > m_cachedTime) { + PaintCacheLine cacheLine; + + // We're visualizing the end: from the past-one we previously visualized + // to the last one in the buffer. + cacheLine.paintRect = getRectForSampleFragment (globalRect, + m_cachedTime, + currentSampleTime-m_cachedTime, + currentPixelsPerTact); + cacheLine.pixelsPerTact = currentPixelsPerTact; + + if (m_tco->sampleBuffer ()->tryDataReadLock ()) { + // Generate the actual visualization. + cacheLine.paintPoly = m_tco->m_sampleBuffer->visualizeToPoly (cacheLine.paintRect, + pe->rect(), + m_cachedTime.frames (framesPerTick), + currentSampleTime.frames (framesPerTick)); + m_tco->m_sampleBuffer->dataUnlock (); + + m_paintMaps.push_back (std::move (cacheLine)); + m_cachedTime = currentSampleTime; + } else { - float offset = m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact(); - QRect r = QRect( TCO_BORDER_WIDTH + offset, spacing, - qMax( static_cast( m_tco->sampleLength() * ppt / ticksPerTact ), 1 ), rect().bottom() - 2 * spacing ); + // We have not really did much. + setNeedsUpdate (true); + } - // Make sure our SampleBuffer is not locked, if it is, - // skip this frame and flag it for - if (m_tco->sampleBuffer ()->tryDataReadLock ()) { - m_tco->m_sampleBuffer->visualize( p, r, pe->rect() ); - m_tco->m_sampleBuffer->dataUnlock (); - } else { - // We have not really did much. - setNeedsUpdate (true); + } + + p.setRenderHint (QPainter::Antialiasing); + + // Draw all the cached maps. + for (auto &map : m_paintMaps) { + // Normalize cache that has been generated + // with different PixelsPerTact value (zoom has + // been changed). + if (map.pixelsPerTact > currentPixelsPerTact || map.pixelsPerTact < currentPixelsPerTact) { + QTransform transform; + transform.scale(static_cast(currentPixelsPerTact) / static_cast(map.pixelsPerTact), 1); + + map.paintPoly.first = transform.map(map.paintPoly.first); + map.paintPoly.second = transform.map(map.paintPoly.second); + map.pixelsPerTact = currentPixelsPerTact; + } + + // Paint the visualization. + p.drawPolyline (map.paintPoly.first); + p.drawPolyline (map.paintPoly.second); } QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile()); @@ -539,6 +582,48 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) painter.drawPixmap( 0, 0, m_paintPixmap ); } +QRect SampleTCOView::getRectForSampleFragment(QRect globalRect, MidiTime beginOffset, + MidiTime totalTime, float pixelsPerTact, bool shouldAddBorder) { + const int spacing = TCO_BORDER_WIDTH + 1; + float ppt; + if (fixedTCOs ()) { + auto width = globalRect.width (); + if (shouldAddBorder) + width =- 2 * TCO_BORDER_WIDTH; + + ppt = ( width ) + / (float) m_tco->length().getTact(); + } else { + ppt = pixelsPerTact; + } + + float nom = Engine::getSong()->getTimeSigModel().getNumerator(); + float den = Engine::getSong()->getTimeSigModel().getDenominator(); + float ticksPerTact = DefaultTicksPerTact * nom / den; + + float offset = (beginOffset + m_tco->startTimeOffset()) / ticksPerTact * pixelsPerTact; + if (shouldAddBorder) { + offset += TCO_BORDER_WIDTH; + } + + float top = globalRect.top (); + if (shouldAddBorder) { + top += spacing; + } + + float height = globalRect.height (); + if (shouldAddBorder) { + height -= spacing * 2; + } + + QRect r = QRect( globalRect.left () + offset, + top, + (qMax( static_cast( totalTime * ppt / ticksPerTact ), 1 )), + height); + + return r; +} + @@ -552,7 +637,7 @@ SampleTrack::SampleTrack( TrackContainer* tc ) : this, tr ("Record channel")), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, - tr( "Volume" ) ), + tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), @@ -785,8 +870,9 @@ void SampleTrack::beforeRecordOn(MidiTime time) for(auto &track : getTCOs ()) { auto sampleTCO = static_cast(track); - if (sampleTCO->isRecord() && !sampleTCO->isMuted()) + if (sampleTCO->isRecord() && !sampleTCO->isMuted()) { isRecordTCOExist = true; + } } if (! isRecordTCOExist) { From a356e7879b5e22f8d0e77af9d681b24fc6147922 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Jun 2018 08:08:47 +0300 Subject: [PATCH 064/112] PulseAudio: Disable recording since it produces voices from hell. --- src/core/audio/AudioPulseAudio.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index c1ebbaa3272..1a8f70f2960 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -58,13 +58,16 @@ AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer* _mixer ) : { _success_ful = false; + setSampleRate (Engine::mixer ()->baseSampleRate ()); m_sampleSpec.format = PA_SAMPLE_FLOAT32; m_sampleSpec.rate = sampleRate(); m_sampleSpec.channels = channels(); m_recordSampleSpec = m_sampleSpec; - m_supportsCapture = true; + // It does work. But it has clicking noises every few seconds. + // have no idea way. + m_supportsCapture = false; _success_ful = true; } From 6fe3267508128aa25df885c2cf751ab534d79f5c Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Jun 2018 08:09:34 +0300 Subject: [PATCH 065/112] AudioSdl: Disable recording in linux since it have a very high latency issues. --- src/core/audio/AudioSdl.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 40b69a5d785..45e6e043a22 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -99,6 +99,7 @@ AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : _success_ful = true; #ifdef LMMS_HAVE_SDL2 +#ifdef LMMS_BUILD_WIN32 m_inputAudioHandle = m_audioHandle; m_inputAudioHandle.freq = mixer ()->inputSampleRate (); m_inputAudioHandle.callback = sdlInputAudioCallback; @@ -114,7 +115,10 @@ AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : m_supportsCapture = false; qWarning ( "Couldn't open SDL capture device: %s\n", SDL_GetError ()); } - +#else + qWarning("SDL: Recording has been disabled on Linux since it " + "appears to have a growing latency"); +#endif #endif } From 3a43158df6edebe79b1a71805688a42ac81540c8 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Jun 2018 14:42:52 +0300 Subject: [PATCH 066/112] SampleTrack: Avoid crashes while recording by locking the mixer before making any changes in the track. --- src/tracks/SampleTrack.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index df17358b909..1c0ce8d849b 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -876,6 +876,8 @@ void SampleTrack::beforeRecordOn(MidiTime time) } if (! isRecordTCOExist) { + Engine::mixer()->requestChangeInModel(); + auto fallbackRecordTCO = static_cast(createTCO (0)); fallbackRecordTCO->setRecord (true); @@ -887,6 +889,8 @@ void SampleTrack::beforeRecordOn(MidiTime time) fallbackRecordTCO->setIsPlaying (false); fallbackRecordTCO->setAutoResize (true); + Engine::mixer()->doneChangeInModel(); + } } } From a056dfb5e56f2ca7c2e2fb1297e41f598ed5554e Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Jun 2018 15:34:29 +0300 Subject: [PATCH 067/112] SampleTrackView: Fix visualization not being cleared between recording sessions and handle sample start offset properly. --- include/SampleTrack.h | 6 ++++-- src/tracks/SampleTrack.cpp | 29 ++++++++++++++++++----------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/include/SampleTrack.h b/include/SampleTrack.h index f8a5b9c2c35..bf96ebbeea5 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -134,11 +134,13 @@ public slots: * @param beginOffset Offset from the beginning of the tco. * @param totalTime Total time we want to visualize. * @param pixelsPerTact Current `pixelsPerTact()` result. - * @param shouldAddBorder Should we calculate border pixels? + * @param isRootRect Is this a the global rectangle? + * and we should calculate spacing and + * sample offset? */ QRect getRectForSampleFragment (QRect globalRect, MidiTime beginOffset, MidiTime totalTime, - float pixelsPerTact, bool shouldAddBorder=false); + float pixelsPerTact, bool isRootRect=false); SampleTCO * m_tco; QPixmap m_paintPixmap; diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 1c0ce8d849b..9aa1615983a 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -493,10 +493,9 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) currentPixelsPerTact, true); - // Only TCOs that are being recorded into. - // Clear the cache and let it generate a new line - // and then visualize it. - if (!m_tco->isRecord () || !Engine::getSong ()->isRecording ()) { + + // We have a cache mismatch, lets clear the current one. + if (currentSampleTime < m_cachedTime) { m_cachedTime = 0; m_paintMaps.clear (); } @@ -528,7 +527,6 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) // We have not really did much. setNeedsUpdate (true); } - } p.setRenderHint (QPainter::Antialiasing); @@ -552,6 +550,14 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.drawPolyline (map.paintPoly.second); } + // Cache only TCOs that are being recorded into. + // Clear the cache and let it generate a new line + // and then visualize it. + if (!m_tco->isRecord () || !Engine::getSong ()->isRecording ()) { + m_cachedTime = 0; + m_paintMaps.clear (); + } + QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile()); QString filename = fileInfo.fileName(); paintTextLabel(filename, p); @@ -583,12 +589,12 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) } QRect SampleTCOView::getRectForSampleFragment(QRect globalRect, MidiTime beginOffset, - MidiTime totalTime, float pixelsPerTact, bool shouldAddBorder) { + MidiTime totalTime, float pixelsPerTact, bool isRootRect) { const int spacing = TCO_BORDER_WIDTH + 1; float ppt; if (fixedTCOs ()) { auto width = globalRect.width (); - if (shouldAddBorder) + if (isRootRect) width =- 2 * TCO_BORDER_WIDTH; ppt = ( width ) @@ -601,18 +607,19 @@ QRect SampleTCOView::getRectForSampleFragment(QRect globalRect, MidiTime beginOf float den = Engine::getSong()->getTimeSigModel().getDenominator(); float ticksPerTact = DefaultTicksPerTact * nom / den; - float offset = (beginOffset + m_tco->startTimeOffset()) / ticksPerTact * pixelsPerTact; - if (shouldAddBorder) { + float offset = (beginOffset) / ticksPerTact * pixelsPerTact; + if (isRootRect) { offset += TCO_BORDER_WIDTH; + offset += m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact; } float top = globalRect.top (); - if (shouldAddBorder) { + if (isRootRect) { top += spacing; } float height = globalRect.height (); - if (shouldAddBorder) { + if (isRootRect) { height -= spacing * 2; } From d11c826b98773d14edf9904ba1ce3aa330c06bab Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 2 Jun 2018 17:43:06 +0300 Subject: [PATCH 068/112] SamplePlayHandle: Fix export issues by correctly calculating totalFrames. Many thanks to @-PhySong. What a great find. --- src/core/SamplePlayHandle.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 82a12e25b04..c3bc07b260c 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -139,7 +139,10 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const f_cnt_t SamplePlayHandle::totalFrames() const { - return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / m_sampleBuffer->sampleRate ()); + f_cnt_t total_frames = ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ); + qreal processingToSampleRateRatio = static_cast(Engine::mixer()->processingSampleRate()) / static_cast(m_sampleBuffer->sampleRate ()); + + return static_cast(total_frames * processingToSampleRateRatio); } From 09f0b0dcc890cec42a6895e779d32ddf5cedd0ba Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 9 Jun 2018 21:41:06 +0300 Subject: [PATCH 069/112] SampleTCOView: Implement caching for visualization. --- include/SampleBuffer.h | 2 +- include/SampleBufferVisualizer.h | 171 +++++++++++++++++++++++ include/SampleTrack.h | 28 +--- src/core/CMakeLists.txt | 2 + src/core/SampleBuffer.cpp | 34 +++-- src/core/SampleBufferVisualizer.cpp | 207 ++++++++++++++++++++++++++++ src/tracks/SampleTrack.cpp | 159 ++++----------------- 7 files changed, 430 insertions(+), 173 deletions(-) create mode 100644 include/SampleBufferVisualizer.h create mode 100644 src/core/SampleBufferVisualizer.cpp diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 7827bc35dc8..28d8d49ce7f 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -132,7 +132,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject visualize( _p, _dr, _dr, _from_frame, _to_frame ); } - QPair visualizeToPoly( const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0); + QPair visualizeToPoly( const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0) const; inline const QString & audioFile() const { diff --git a/include/SampleBufferVisualizer.h b/include/SampleBufferVisualizer.h new file mode 100644 index 00000000000..9a8d2113759 --- /dev/null +++ b/include/SampleBufferVisualizer.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2018 Shmuel H. (shmuelhazan0/at/gmail.com) + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef SAMPLEBUFFERVISUALIZER_H +#define SAMPLEBUFFERVISUALIZER_H + +#include +#include + +#include "MidiTime.h" +#include "SampleBuffer.h" + + +/** + * @brief A caching implementation of a visualizer for SampleBuffer. + * + * This implementation splits the tco into fragments of + * 192 ticks. Then, it draws them into @a PixmapCacheLine. + */ +class SampleBufferVisualizer +{ + struct PixmapCacheLine + { + /** + * @brief The visualization of this cache line. + */ + QPixmap pixmap; + + /** + * @brief The rectangle of @a pixmap. + */ + QRect rect; + + /** + * @brief How much time @a pixmap is representing. + */ + MidiTime totalTime; + }; + +public: + SampleBufferVisualizer(); + + enum class Operation { + Clear, + Append + }; + + /** + * @brief Update the cache before drawing it. + * @param sampleBuffer The sample buffer to visualize. + * @param sampleStartOffset Offset from the sample buffer's beginning. + * @param sampleLength The time we should + * visualize (can be different that the sample buffer's length). + * @param parentRect The rectangle we should paint in. + * @param pixelsPerTact Current value of pixels per tact. + * @param framesPerTact Current value of frames per tact. + * @param pen The pen setting we should paint with. + * @param operation Should we clear the cache or just append in the end? + */ + void update(const SampleBuffer &sampleBuffer, + MidiTime sampleStartOffset, + MidiTime sampleLength, + const QRect &parentRect, + float pixelsPerTact, f_cnt_t framesPerTact, + const QPen &pen, + Operation operation); + + /** + * @brief draw Draw the current cache into @a painter. + */ + void draw(QPainter &painter); + +private: + /** + * @brief appendMultipleTacts Add one or more cache lines or + * just add data to an existing + * cache line. + * @param sampleBuffer The sample buffer to visualize. + * @param sampleLength Time to visualize. + * @param parentRect The rectangle we should paint in. + * @param pen The pen setting we should paint with. + */ + void appendMultipleTacts(const SampleBuffer &sampleBuffer, + MidiTime sampleLength, + const QRect &parentRect, const QPen &pen); + + /** + * @brief appendTact Add data to m_currentPixmap. + * @param sampleBuffer The sample buffer to visualize. + * @param totalTime How much time we should visualize from + * m_currentPixmap.totalTime. + * @param parentRect The rectangle we should paint in. + * @param pen + * @param pen The pen setting we should paint with. + * @return true if we have painted at least one + * pixel; false otherwise. + */ + bool appendTact(const SampleBuffer &sampleBuffer, + const MidiTime &totalTime, + const QRect &parentRect, const QPen &pen, bool isLastInTact); + + /** + * @brief getRectForSampleFragment Construct a rectangle for + * visualization of a given + * MidiTime range. + * @param parentRect The rectangle we should be in. + * @param beginOffset Offset of the rectangle from @a + * parentRect's left. + * @param totalTime The actual time to be painted. + * @param forceNotZeroWidth Should we make sure rect.width != 0? + */ + QRect getRectForSampleFragment(QRect parentRect, + MidiTime beginOffset, + MidiTime totalTime, + bool forceNotZeroWidth=false); + + /** + * @brief pixelsPerTime Calculate how much pixels a visualization of + * @a time would take. + */ + float pixelsPerTime (const MidiTime &time) + { + return ((time * m_pixelsPerTact) / MidiTime::ticksPerTact()); + } + + /** + * How much time have we cached already (not including + * the cache in m_currentPixmap). + */ + MidiTime m_cachedTime{0}; + + /** + * X time drawing offset from the beginning. + */ + MidiTime m_generalPaintOffset{0}; + /** + * X pixel drawing offset from the beginning. + */ + int m_drawPixelOffset = 0; + + QList m_cachedPixmaps; + + /** + * The current cache line we've not finished yet. + */ + PixmapCacheLine m_currentPixmap; + + float m_pixelsPerTact = 0; + f_cnt_t m_framesPerTact = 0; +}; + +#endif // SAMPLEBUFFERVISUALIZER_H diff --git a/include/SampleTrack.h b/include/SampleTrack.h index bf96ebbeea5..40b6c93472a 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -32,6 +32,7 @@ #include "FxMixer.h" #include "FxLineLcdSpinBox.h" #include "Track.h" +#include "SampleBufferVisualizer.h" class EffectRackView; class Knob; @@ -126,34 +127,9 @@ public slots: private: - /** - * @brief Calculate the position and size for a visualization - * of a @arg totalTime frames fragment starting from - * @arg beginOffset. - * @param globalRect The parent rectangle. - * @param beginOffset Offset from the beginning of the tco. - * @param totalTime Total time we want to visualize. - * @param pixelsPerTact Current `pixelsPerTact()` result. - * @param isRootRect Is this a the global rectangle? - * and we should calculate spacing and - * sample offset? - */ - QRect getRectForSampleFragment (QRect globalRect, MidiTime beginOffset, - MidiTime totalTime, - float pixelsPerTact, bool isRootRect=false); - SampleTCO * m_tco; - QPixmap m_paintPixmap; - - - struct PaintCacheLine { - QPair paintPoly; - QRect paintRect; - float pixelsPerTact; - }; - QVector m_paintMaps; - MidiTime m_cachedTime{0}; + SampleBufferVisualizer m_sampleBufferVisualizer; } ; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d894190b4c5..debd56edbe6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -99,5 +99,7 @@ set(LMMS_SRCS core/midi/MidiTime.cpp core/midi/MidiWinMM.cpp + core/SampleBufferVisualizer.cpp + PARENT_SCOPE ) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index aa04fc1ef32..16095a79a25 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -837,35 +837,47 @@ void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, } QPair SampleBuffer::visualizeToPoly(const QRect &_dr, const QRect &_clip, - f_cnt_t _from_frame, f_cnt_t _to_frame) + f_cnt_t _from_frame, f_cnt_t _to_frame) const { if( internalFrames () == 0 ) return {}; - const bool focus_on_range = _to_frame <= internalFrames() - && _from_frame < _to_frame; + const bool focus_on_range = _from_frame < _to_frame; + if (_to_frame > frames()) + _to_frame = frames(); + //_p.setClipRect( _clip ); const int w = _dr.width(); const int h = _dr.height(); - const int yb = h / 2 + _dr.y(); - const float y_space = h*0.5f; +// const int yb = h / 2 + _dr.y(); + int y_space = (h/2); + const int nb_frames = focus_on_range ? _to_frame - _from_frame : internalFrames(); if (nb_frames == 0) return {}; const int fpp = tLimit( nb_frames / w, 1, 20 ); - auto l = QPolygonF(); - auto r = QPolygonF(); + + bool shouldAddAdditionalPoint = (nb_frames % fpp) != 0; + int pointsCount = (nb_frames / fpp) + (shouldAddAdditionalPoint ? 1 : 0); + auto l = QPolygonF(pointsCount); + auto r = QPolygonF(pointsCount); int n = 0; const int xb = _dr.x(); const int first = focus_on_range ? _from_frame : 0; const int last = focus_on_range ? _to_frame : internalFrames(); + + int zeroPoint = _dr.y() + y_space; + if (h % 2 != 0) + zeroPoint += 1; for( int frame = first; frame < last; frame += fpp ) { - l.push_back( QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), - ( yb - ( m_data[frame][0] * y_space * m_amplification ) ) )); - r.push_back(QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), - ( yb - ( m_data[frame][1] * y_space * m_amplification ) ) )); + double x = (xb + (frame - first) * double( w ) / nb_frames); + + l[n] = QPointF(x, + ( zeroPoint + ( m_data[frame][0] * y_space * m_amplification ) ) ); + r[n] = QPointF(x, + ( zeroPoint + ( m_data[frame][1] * y_space * m_amplification ) ) ); ++n; } diff --git a/src/core/SampleBufferVisualizer.cpp b/src/core/SampleBufferVisualizer.cpp new file mode 100644 index 00000000000..4d1fa716a2e --- /dev/null +++ b/src/core/SampleBufferVisualizer.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2018 Shmuel H. (shmuelhazan0/at/gmail.com) + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ +#include "include/SampleBufferVisualizer.h" + +#include "Track.h" +#include "Engine.h" + +#include +#include + +SampleBufferVisualizer::SampleBufferVisualizer() +{ + +} + +void SampleBufferVisualizer::update(const SampleBuffer &sampleBuffer, + MidiTime sampleStartOffset, + MidiTime sampleLength, + const QRect &parentRect, + float pixelsPerTact, + f_cnt_t framesPerTact, + const QPen &pen, + SampleBufferVisualizer::Operation operation) +{ + // != + if (pixelsPerTact < m_pixelsPerTact || pixelsPerTact > m_pixelsPerTact + || framesPerTact != m_framesPerTact + || sampleLength < m_cachedTime) + operation = Operation::Clear; + + m_pixelsPerTact = pixelsPerTact; + m_generalPaintOffset = sampleStartOffset; + m_framesPerTact = framesPerTact; + m_drawPixelOffset = parentRect.left(); + + switch (operation) { + case Operation::Clear: + m_cachedPixmaps.clear(); + m_currentPixmap.pixmap = QPixmap(); + m_currentPixmap.totalTime = 0; + m_cachedTime = 0; + +#ifdef Q_FALLTHROUGH + Q_FALLTHROUGH(); +#endif + case Operation::Append: + appendMultipleTacts(sampleBuffer, + sampleLength, + parentRect.translated(-parentRect.x(), + 0), + pen); + break; + } +} + +void SampleBufferVisualizer::draw(QPainter &painter) +{ + float pixelOffset = pixelsPerTime(m_generalPaintOffset) + m_drawPixelOffset; + for (const auto &pixmap : m_cachedPixmaps) { + auto targetRect = pixmap.rect.translated(int(pixelOffset), + 0); + + painter.drawPixmap(targetRect, pixmap.pixmap); + pixelOffset += pixelsPerTime(pixmap.totalTime); + } + + auto targetRect = m_currentPixmap.rect.translated(int(pixelOffset), + 0); + painter.drawPixmap(targetRect, m_currentPixmap.pixmap); +} + +void SampleBufferVisualizer::appendMultipleTacts(const SampleBuffer &sampleBuffer, + MidiTime sampleLength, + const QRect &parentRect, + const QPen &pen) +{ + for (; (m_cachedTime+m_currentPixmap.totalTime) < sampleLength;) + { + MidiTime totalTime; + MidiTime offsetFromTact = m_currentPixmap.totalTime; + bool isCompleteTact = false; + + // we have more tacts to draw. finish the current one. + if (MidiTime(m_cachedTime+offsetFromTact).getTact() < sampleLength.getTact()) { + // Paint what left from the current tact. + totalTime = MidiTime::ticksPerTact() - offsetFromTact; + isCompleteTact = true; + } else { + // Draw only the ticks left in the current tact. + totalTime = sampleLength - m_cachedTime - m_currentPixmap.totalTime; + } + + Q_ASSERT((offsetFromTact + totalTime) <= MidiTime::ticksPerTact()); + + auto result = appendTact(sampleBuffer, + totalTime, + parentRect, + pen, + isCompleteTact); + if (! result) { + // We can't paint it. + // totalTime is too short. skip it. + // or just wait until we have enough frames. + if (isCompleteTact) { + // Skip it and continue to the next tact. + m_currentPixmap.totalTime += totalTime; + } else { + // Wait until we have enough frames. + break; + } + + } + + if (isCompleteTact) { + m_cachedTime += ( m_currentPixmap.totalTime ); + m_cachedPixmaps.push_back(std::move(m_currentPixmap)); + m_currentPixmap.pixmap = QPixmap(); + m_currentPixmap.totalTime = 0; + } + } +} + +bool SampleBufferVisualizer::appendTact(const SampleBuffer &sampleBuffer, + const MidiTime &totalTime, + const QRect &parentRect, + const QPen &pen, + bool isLastInTact) +{ + auto offsetFromTact = m_currentPixmap.totalTime; + + auto currentPaintInTact = getRectForSampleFragment (parentRect, + offsetFromTact, + totalTime, + isLastInTact); + if (currentPaintInTact.width() < 1) { + return false; + } + + // Generate the actual visualization. + auto fromFrame = MidiTime(m_cachedTime + offsetFromTact).frames (m_framesPerTact); + + sampleBuffer.dataReadLock(); + auto poly = sampleBuffer.visualizeToPoly (currentPaintInTact, + QRect(), + fromFrame, + fromFrame + totalTime.frames(m_framesPerTact)); + sampleBuffer.dataUnlock (); + + m_currentPixmap.totalTime += totalTime; + + m_currentPixmap.rect = getRectForSampleFragment (parentRect, + 0, + MidiTime::ticksPerTact()); + if (m_currentPixmap.pixmap.isNull()) { + m_currentPixmap.pixmap = QPixmap(m_currentPixmap.rect.size()); + m_currentPixmap.pixmap.fill(Qt::transparent); + } + + // Draw the points into the pixmap. + QPainter pixmapPainter (&m_currentPixmap.pixmap); + pixmapPainter.setPen(pen); + + pixmapPainter.setRenderHint( QPainter::Antialiasing ); + + pixmapPainter.drawPolyline (poly.first); + pixmapPainter.drawPolyline (poly.second); + pixmapPainter.end(); + + // Continue to the next tact or stop. + return true; +} + +QRect SampleBufferVisualizer::getRectForSampleFragment(QRect parentRect, MidiTime beginOffset, + MidiTime totalTime, + bool forceNotZeroWidth) { + int offset = pixelsPerTime(beginOffset); + + float top = parentRect.top (); + float height = parentRect.height (); + + QRect r = QRect( int(parentRect.x ()) + int(offset), + top, + int(qMax( int(pixelsPerTime(totalTime)) , (forceNotZeroWidth ? 1 : 0) )), + int(height)); + + + return r; +} diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 9aa1615983a..e2b65ce4757 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -1,4 +1,4 @@ -/* +/* * SampleTrack.cpp - implementation of class SampleTrack, a track which * provides arrangement of samples * @@ -284,8 +284,7 @@ TrackContentObjectView * SampleTCO::createView( TrackView * _tv ) SampleTCOView::SampleTCOView( SampleTCO * _tco, TrackView * _tv ) : TrackContentObjectView( _tco, _tv ), - m_tco( _tco ), - m_paintPixmap() + m_tco( _tco ) { // update UI and tooltip updateSample(); @@ -437,23 +436,10 @@ void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) void SampleTCOView::paintEvent( QPaintEvent * pe ) { - QPainter painter( this ); - - if( !needsUpdate() ) - { - painter.drawPixmap( 0, 0, m_paintPixmap ); - return; - } + QPainter p( this ); setNeedsUpdate( false ); - if (m_paintPixmap.isNull() || m_paintPixmap.size() != size()) - { - m_paintPixmap = QPixmap(size()); - } - - QPainter p( &m_paintPixmap ); - QLinearGradient lingrad( 0, 0, 0, height() ); QColor c; bool muted = m_tco->getTrack()->isMuted() || m_tco->isMuted(); @@ -465,7 +451,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) else if (m_tco->isRecord ()) c = recordingBackgroundColor (); else - c = painter.background ().color (); + c = p.background ().color (); lingrad.setColorAt( 1, c.darker( 300 ) ); lingrad.setColorAt( 0, c ); @@ -482,81 +468,28 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.fillRect( rect(), c ); } - p.setPen( !muted ? painter.pen().brush().color() : mutedColor() ); - - MidiTime currentSampleTime = m_tco->sampleLength(); - auto framesPerTick = Engine::framesPerTick(m_tco->sampleBuffer ()->sampleRate ()); - auto currentPixelsPerTact = pixelsPerTact(); - auto globalRect = getRectForSampleFragment (rect(), - 0, - currentSampleTime, - currentPixelsPerTact, - true); - - - // We have a cache mismatch, lets clear the current one. - if (currentSampleTime < m_cachedTime) { - m_cachedTime = 0; - m_paintMaps.clear (); - } - - // We've need to visualize more frames. - if (currentSampleTime > m_cachedTime) { - PaintCacheLine cacheLine; - - // We're visualizing the end: from the past-one we previously visualized - // to the last one in the buffer. - cacheLine.paintRect = getRectForSampleFragment (globalRect, - m_cachedTime, - currentSampleTime-m_cachedTime, - currentPixelsPerTact); - cacheLine.pixelsPerTact = currentPixelsPerTact; - - if (m_tco->sampleBuffer ()->tryDataReadLock ()) { - // Generate the actual visualization. - cacheLine.paintPoly = m_tco->m_sampleBuffer->visualizeToPoly (cacheLine.paintRect, - pe->rect(), - m_cachedTime.frames (framesPerTick), - currentSampleTime.frames (framesPerTick)); - m_tco->m_sampleBuffer->dataUnlock (); - - m_paintMaps.push_back (std::move (cacheLine)); - m_cachedTime = currentSampleTime; - } else { - - // We have not really did much. - setNeedsUpdate (true); - } - } - - p.setRenderHint (QPainter::Antialiasing); + p.setPen( !muted ? p.pen().brush().color() : mutedColor() ); - // Draw all the cached maps. - for (auto &map : m_paintMaps) { - // Normalize cache that has been generated - // with different PixelsPerTact value (zoom has - // been changed). - if (map.pixelsPerTact > currentPixelsPerTact || map.pixelsPerTact < currentPixelsPerTact) { - QTransform transform; - transform.scale(static_cast(currentPixelsPerTact) / static_cast(map.pixelsPerTact), 1); - - map.paintPoly.first = transform.map(map.paintPoly.first); - map.paintPoly.second = transform.map(map.paintPoly.second); - map.pixelsPerTact = currentPixelsPerTact; - } + auto timeSig = TimeSig(Engine::getSong()->getTimeSigModel()); + auto realTicksPerDefaultTicks = float(float(MidiTime::ticksPerTact(timeSig) / MidiTime::ticksPerTact())); + auto normalizedPixelsPerTact = pixelsPerTact() * realTicksPerDefaultTicks; + auto normalizedFramesPerTick = Engine::framesPerTick(m_tco->sampleBuffer()->sampleRate()) * realTicksPerDefaultTicks; - // Paint the visualization. - p.drawPolyline (map.paintPoly.first); - p.drawPolyline (map.paintPoly.second); - } + const int spacing = TCO_BORDER_WIDTH + 1; - // Cache only TCOs that are being recorded into. - // Clear the cache and let it generate a new line - // and then visualize it. - if (!m_tco->isRecord () || !Engine::getSong ()->isRecording ()) { - m_cachedTime = 0; - m_paintMaps.clear (); - } + QMargins margins(spacing, + TCO_BORDER_WIDTH-1, + spacing, + TCO_BORDER_WIDTH); + m_sampleBufferVisualizer.update(*m_tco->sampleBuffer(), + m_tco->startTimeOffset(), + m_tco->sampleLength(), + rect()-margins, + normalizedPixelsPerTact, + f_cnt_t (normalizedFramesPerTick), + p.pen(), + SampleBufferVisualizer::Operation::Append); + m_sampleBufferVisualizer.draw(p); QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile()); QString filename = fileInfo.fileName(); @@ -584,51 +517,6 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) } p.end(); - - painter.drawPixmap( 0, 0, m_paintPixmap ); -} - -QRect SampleTCOView::getRectForSampleFragment(QRect globalRect, MidiTime beginOffset, - MidiTime totalTime, float pixelsPerTact, bool isRootRect) { - const int spacing = TCO_BORDER_WIDTH + 1; - float ppt; - if (fixedTCOs ()) { - auto width = globalRect.width (); - if (isRootRect) - width =- 2 * TCO_BORDER_WIDTH; - - ppt = ( width ) - / (float) m_tco->length().getTact(); - } else { - ppt = pixelsPerTact; - } - - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - float ticksPerTact = DefaultTicksPerTact * nom / den; - - float offset = (beginOffset) / ticksPerTact * pixelsPerTact; - if (isRootRect) { - offset += TCO_BORDER_WIDTH; - offset += m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact; - } - - float top = globalRect.top (); - if (isRootRect) { - top += spacing; - } - - float height = globalRect.height (); - if (isRootRect) { - height -= spacing * 2; - } - - QRect r = QRect( globalRect.left () + offset, - top, - (qMax( static_cast( totalTime * ppt / ticksPerTact ), 1 )), - height); - - return r; } @@ -896,6 +784,7 @@ void SampleTrack::beforeRecordOn(MidiTime time) fallbackRecordTCO->setIsPlaying (false); fallbackRecordTCO->setAutoResize (true); + Engine::mixer()->doneChangeInModel(); } From d2d8bd7929673cbd0f0c5f92de79602107e4fa53 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 10 Jun 2018 00:50:33 +0300 Subject: [PATCH 070/112] SampleTCO -> Recording: Block resize and move actions while recording. --- include/Track.h | 7 ++++++- src/core/SampleRecordHandle.cpp | 3 +++ src/core/Song.cpp | 19 +++++++++++++------ src/core/Track.cpp | 16 ++++++++++++++-- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/include/Track.h b/include/Track.h index ecd0f3e9c36..51807b9e039 100644 --- a/include/Track.h +++ b/include/Track.h @@ -152,6 +152,9 @@ class TrackContentObject : public Model, public JournallingObject MidiTime startTimeOffset() const; void setStartTimeOffset( const MidiTime &startTimeOffset ); + bool isRecording() const; + void setIsRecording(bool value); + public slots: void copy(); void paste(); @@ -183,6 +186,8 @@ public slots: BoolModel m_soloModel; bool m_autoResize; + bool m_isRecording{false}; + bool m_selectViewOnCreate; friend class TrackContentObjectView; @@ -240,7 +245,7 @@ class TrackContentObjectView : public selectableObject, public ModelView // access needsUpdate member variable bool needsUpdate(); void setNeedsUpdate( bool b ); - + public slots: virtual bool close(); void cut(); diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index c444006e2b3..fdb689580a8 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -41,6 +41,7 @@ SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTime m_recordingChannel{dynamic_cast(m_track)->recordingChannel ()}, m_startRecordTimeOffset{startRecordTimeOffset} { + m_tco->setIsRecording(true); } @@ -56,6 +57,8 @@ SampleRecordHandle::~SampleRecordHandle() m_tco->setAutoResize (false); m_tco->setRecord( false ); } + + m_tco->setIsRecording(false); } diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 3a23a5c50ae..b3593e40f3a 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -272,11 +272,6 @@ void Song::processNextBuffer() m_vstSyncController.setPlaybackJumped( true ); m_playPos[m_playMode].setJumped( false ); } - - if (isRecording()) - { - emit beforeRecordOn(getPlayPos()); - } f_cnt_t framesPlayed = 0; const float framesPerTick = Engine::framesPerTick(); @@ -353,8 +348,9 @@ void Song::processNextBuffer() if (isRecording()) { - emit beforeRecordOn(getPlayPos()); + emit beforeRecordOn(tl->loopBegin()); } + emit updateSampleTracks(); } } @@ -565,6 +561,17 @@ void Song::playAndRecord() { playSong(); m_recording = true; + + MidiTime time = getPlayPos(); + auto tl = getPlayPos().m_timeLine; + bool checkLoop = + tl != NULL && tl->loopPointsEnabled(); + + if (checkLoop && (time >= tl->loopEnd() || time < tl->loopBegin())) { + time = tl->loopBegin(); + } + + emit beforeRecordOn(time); } diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 93bf3dc8615..a10a929027c 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -241,6 +241,15 @@ void TrackContentObject::setStartTimeOffset( const MidiTime &startTimeOffset ) m_startTimeOffset = startTimeOffset; } +bool TrackContentObject::isRecording() const +{ + return m_isRecording; +} + +void TrackContentObject::setIsRecording(bool value) +{ + m_isRecording = value; +} @@ -518,7 +527,6 @@ void TrackContentObjectView::updatePosition() } - /*! \brief Change the trackContentObjectView's display when something * being dragged enters it. * @@ -717,6 +725,10 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { + // Disallow changes to a track that is being recorded into. + if (m_tco->isRecording()) + return; + setInitialMousePos( me->pos() ); if( !fixedTCOs() && me->button() == Qt::LeftButton ) { @@ -731,7 +743,7 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) m_action = ToggleSelected; } } - else if( !me->modifiers() ) + else if( !me->modifiers()) { if( isSelected() ) { From b1fed89c55a37af8f3cb62c3c2d215a2c187ca97 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 10 Jun 2018 11:02:58 +0300 Subject: [PATCH 071/112] SampleBuffer -> SoundFile: Support mono files, show a warning for channel > 2. --- include/SampleBuffer.h | 2 +- src/core/SampleBuffer.cpp | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 28d8d49ce7f..f9f2776fb3a 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -312,7 +312,7 @@ public slots: static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); - static DataVector decodeSampleSF( QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); + static DataVector decodeSampleSF(QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate, QString &loadingWarning); #ifdef LMMS_HAVE_OGGVORBIS static DataVector decodeSampleOGGVorbis( QString _f, ch_cnt_t & _channels, diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 16095a79a25..c6d89ffd116 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -187,6 +187,7 @@ void SampleBuffer::changeAudioFile(QString audioFile) f.close(); } + QString loadingWarning; if( !fileLoadError ) { #ifdef LMMS_HAVE_OGGVORBIS @@ -200,7 +201,7 @@ void SampleBuffer::changeAudioFile(QString audioFile) #endif if(fileData.empty ()) { - fileData = decodeSampleSF( file, channels, samplerate ); + fileData = decodeSampleSF( file, channels, samplerate, loadingWarning ); } #ifdef LMMS_HAVE_OGGVORBIS if( fileData.empty () ) @@ -225,10 +226,12 @@ void SampleBuffer::changeAudioFile(QString audioFile) QString message = tr( "Audio files are limited to %1 MB " "in size and %2 minutes of playing time" ).arg( fileSizeMax ).arg( sampleLengthMax ); + if (! loadingWarning.isEmpty()) + message = loadingWarning; if( gui ) { QMessageBox::information( NULL, - title, message, QMessageBox::Ok ); + title, message, QMessageBox::Warning ); } else { @@ -299,7 +302,8 @@ SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSamp SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, ch_cnt_t & _channels, - sample_rate_t &_samplerate) + sample_rate_t &_samplerate, + QString &loadingWarning) { SNDFILE * snd_file; SF_INFO sf_info; @@ -316,18 +320,16 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, { frames = sf_info.frames; vector.resize (frames); - sf_rr = sf_read_float( snd_file, vector.data ()->data (), DEFAULT_CHANNELS * frames ); + sf_rr = sf_read_float( snd_file, vector.data ()->data (), min(DEFAULT_CHANNELS * frames, sf_info.channels * frames)); - if (sf_info.channels != DEFAULT_CHANNELS) { + if (sf_info.channels == 1) { #ifdef DEBUG_LMMS qDebug( "SampleBuffer::decodeSampleSF(): Not a stereo file: %s: %s", _f, sf_strerror( NULL ) ); #endif - - _channels = sf_info.channels; - _samplerate = sf_info.samplerate; - - sf_close( snd_file ); - return vector; + vector.resize(frames / 2); + } else if (sf_info.channels > DEFAULT_CHANNELS) { + loadingWarning = tr("The file you've selected has %1 channels. LMMS support " + "Stereo and Mono.").arg(sf_info.channels); } if( sf_rr < sf_info.channels * frames ) @@ -348,6 +350,7 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, qDebug( "SampleBuffer::decodeSampleSF(): could not load " "sample %s: %s", _f, sf_strerror( NULL ) ); #endif + loadingWarning = tr("SoundFile: Could not load: %1").arg(sf_strerror( NULL )); } f.close(); From b5123e67f2c305cd71afb9064e32d87dd623c77d Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 11 Jun 2018 12:49:13 +0300 Subject: [PATCH 072/112] SampleBuffer -> Recording: Use less blocking locks with addData and 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. --- include/SampleBuffer.h | 26 ++++++++- src/core/SampleBuffer.cpp | 91 +++++++++++++++++++++++++---- src/core/SampleBufferVisualizer.cpp | 23 ++++---- src/core/SampleRecordHandle.cpp | 52 ++++++++++++----- 4 files changed, 154 insertions(+), 38 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index f9f2776fb3a..40449ae03e0 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -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); @@ -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? @@ -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 ) { @@ -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. diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index c6d89ffd116..9cbccd19761 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -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 (); @@ -545,7 +557,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( ( @@ -593,7 +605,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 ); @@ -1140,6 +1152,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) { @@ -1159,6 +1188,34 @@ 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; @@ -1166,18 +1223,18 @@ void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t // 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(), @@ -1187,12 +1244,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) { diff --git a/src/core/SampleBufferVisualizer.cpp b/src/core/SampleBufferVisualizer.cpp index 4d1fa716a2e..791284df44d 100644 --- a/src/core/SampleBufferVisualizer.cpp +++ b/src/core/SampleBufferVisualizer.cpp @@ -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. @@ -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)); @@ -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, diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index fdb689580a8..232ef87f43d 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -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, @@ -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(); + } } @@ -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. @@ -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); From 8121812559072b2c84608d046dd120b499fb236e Mon Sep 17 00:00:00 2001 From: Lukas W Date: Mon, 30 Jul 2018 16:05:47 +0200 Subject: [PATCH 073/112] SampleBuffer: Fix mono sample decoding Fixes a regression from 7bc00564b2802c795f56dbeccb4ffa8e53ee3805 that wasn't fixed properly in c8337dce13bc230c42bd0b092c2bf97d592a4150. --- src/core/SampleBuffer.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 9cbccd19761..e74e6d650bf 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -331,19 +331,24 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, if( ( snd_file = sf_open_fd( f.handle(), SFM_READ, &sf_info, false ) ) != NULL ) { frames = sf_info.frames; - vector.resize (frames); - sf_rr = sf_read_float( snd_file, vector.data ()->data (), min(DEFAULT_CHANNELS * frames, sf_info.channels * frames)); + const auto channels = sf_info.channels; - if (sf_info.channels == 1) { -#ifdef DEBUG_LMMS - qDebug( "SampleBuffer::decodeSampleSF(): Not a stereo file: %s: %s", _f, sf_strerror( NULL ) ); -#endif - vector.resize(frames / 2); - } else if (sf_info.channels > DEFAULT_CHANNELS) { + MmAllocator::vector buffer(channels * frames); + sf_rr = sf_read_float( snd_file, buffer.data(), channels * frames); + + if (sf_info.channels > DEFAULT_CHANNELS) { loadingWarning = tr("The file you've selected has %1 channels. LMMS support " "Stereo and Mono.").arg(sf_info.channels); } + // Copy buffer using stereo + vector.resize (frames); + auto rightOffset = sf_info.channels > 1 ? 1 : 0; + for (size_t i = 0; i < frames; i++) { + vector[i][0] = buffer[i * channels]; + vector[i][1] = buffer[i * channels + rightOffset]; + } + if( sf_rr < sf_info.channels * frames ) { #ifdef DEBUG_LMMS From a4efebc3116647b03f665d286c8eae914b0cab1f Mon Sep 17 00:00:00 2001 From: Lukas W Date: Mon, 30 Jul 2018 16:08:23 +0200 Subject: [PATCH 074/112] SampleBuffer::play: Fix freq factor calculation Fixes regression from f7657731f09f76852a34c4bfcec4223bfce07f55 (presumably a copy/paste mistake) --- src/core/SampleBuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index e74e6d650bf..57ab214a458 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -610,7 +610,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 = double(Engine::mixer()->processingSampleRate())/ double(m_sampleRate); + src_data.src_ratio = 1.0 / freq_factor; src_data.end_of_input = 0; int error = src_process( _state->m_resamplingData, &src_data ); From 013d9f47b2e4feef113fe46d72fa96167ccdba57 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Mon, 30 Jul 2018 21:58:35 +0200 Subject: [PATCH 075/112] Remove bundled Jack headers, use system headers instead --- CMakeLists.txt | 33 +- include/jack/control.h | 631 ----------- include/jack/intclient.h | 130 --- include/jack/jack.h | 1470 -------------------------- include/jack/jslist.h | 293 ----- include/jack/midiport.h | 184 ---- include/jack/ringbuffer.h | 243 ----- include/jack/session.h | 270 ----- include/jack/statistics.h | 57 - include/jack/systemdeps.h | 128 --- include/jack/thread.h | 160 --- include/jack/transport.h | 247 ----- include/jack/types.h | 740 ------------- include/jack/weakjack.h | 125 --- include/jack/weakmacros.h | 97 -- src/3rdparty/weakjack/CMakeLists.txt | 2 +- 16 files changed, 31 insertions(+), 4779 deletions(-) delete mode 100644 include/jack/control.h delete mode 100644 include/jack/intclient.h delete mode 100644 include/jack/jack.h delete mode 100644 include/jack/jslist.h delete mode 100644 include/jack/midiport.h delete mode 100644 include/jack/ringbuffer.h delete mode 100644 include/jack/session.h delete mode 100644 include/jack/statistics.h delete mode 100644 include/jack/systemdeps.h delete mode 100644 include/jack/thread.h delete mode 100644 include/jack/transport.h delete mode 100644 include/jack/types.h delete mode 100644 include/jack/weakjack.h delete mode 100644 include/jack/weakmacros.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 67efe635665..99bacc86584 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,9 +383,36 @@ IF(WANT_JACK) IF(WANT_WEAKJACK) SET(LMMS_HAVE_WEAKJACK TRUE) SET(WEAKJACK_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/3rdparty/weakjack/weakjack) - SET(STATUS_JACK "OK (weak linking enabled)") - # use dlsym instead - SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) + + # We only need Jack's header files, so finding the host's headers in the case + # of cross-compiling is fine. + find_path(JACK_INCLUDE_DIR NAMES jack/jack.h CMAKE_FIND_ROOT_PATH_BOTH) + IF(JACK_INCLUDE_DIR) + SET(STATUS_JACK "OK (weak linking enabled)") + # use dlsym instead + SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) + SET(LMMS_HAVE_JACK TRUE) + + IF(NOT CMAKE_CROSSCOMPILING) + SET(JACK_INCLUDE_DIRS "${JACK_INCLUDE_DIR}") + ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + # We're cross-compiling but possibly using the host's jack + # library, so we need to use "-idirafter" to avoid conflicts + # between the host's and the target's header files. -idirafter + # will make sure that ${JACK_INCLUDE_DIR} is only considered for + # #include instructions after all other search directories have + # been exhausted. With normal "-I", we end up including the + # host's standard library header files, leading to confusing + # compilation errors due to conflicting declarations. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -idirafter ${JACK_INCLUDE_DIR}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -idirafter ${JACK_INCLUDE_DIR}") + ELSE() + SET(STATUS_JACK "Host's jack headers were found, but you're using an unknown cross-compiler. Try to use MinGW or supply a jack library for the target architecture at ${CMAKE_FIND_ROOT_PATHS}") + SET(LMMS_HAVE_JACK) + ENDIF() + ELSE() + SET(STATUS_JACK "no jack headers found, please install libjack0.100.0-dev (or similar) ") + ENDIF() ELSE() PKG_CHECK_MODULES(JACK jack>=0.77) IF(JACK_FOUND) diff --git a/include/jack/control.h b/include/jack/control.h deleted file mode 100644 index bacc9ac6805..00000000000 --- a/include/jack/control.h +++ /dev/null @@ -1,631 +0,0 @@ -/* -*- Mode: C ; c-basic-offset: 4 -*- */ -/* - JACK control API - - Copyright (C) 2008 Nedko Arnaudov - Copyright (C) 2008 GRAME - - 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; version 2 of the License. - - 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; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ -/** - * @file jack/control.h - * @ingroup publicheader - * @brief JACK control API - * - */ - -#ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED -#define JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED - -#include -#include -#include -#if !defined(sun) && !defined(__sun__) -#include -#endif - -/** Parameter types, intentionally similar to jack_driver_param_type_t */ -typedef enum -{ - JackParamInt = 1, /**< @brief value type is a signed integer */ - JackParamUInt, /**< @brief value type is an unsigned integer */ - JackParamChar, /**< @brief value type is a char */ - JackParamString, /**< @brief value type is a string with max size of ::JACK_PARAM_STRING_MAX+1 chars */ - JackParamBool, /**< @brief value type is a boolean */ -} jackctl_param_type_t; - -/** Driver types */ -typedef enum -{ - JackMaster = 1, /**< @brief master driver */ - JackSlave /**< @brief slave driver */ -} jackctl_driver_type_t; - -/** @brief Max value that jackctl_param_type_t type can have */ -#define JACK_PARAM_MAX (JackParamBool + 1) - -/** @brief Max length of string parameter value, excluding terminating null char */ -#define JACK_PARAM_STRING_MAX 127 - -/** @brief Type for parameter value */ -/* intentionally similar to jack_driver_param_value_t */ -union jackctl_parameter_value -{ - uint32_t ui; /**< @brief member used for ::JackParamUInt */ - int32_t i; /**< @brief member used for ::JackParamInt */ - char c; /**< @brief member used for ::JackParamChar */ - char str[JACK_PARAM_STRING_MAX + 1]; /**< @brief member used for ::JackParamString */ - bool b; /**< @brief member used for ::JackParamBool */ -}; - -/** opaque type for server object */ -typedef struct jackctl_server jackctl_server_t; - -/** opaque type for driver object */ -typedef struct jackctl_driver jackctl_driver_t; - -/** opaque type for internal client object */ -typedef struct jackctl_internal jackctl_internal_t; - -/** opaque type for parameter object */ -typedef struct jackctl_parameter jackctl_parameter_t; - -/** opaque type for sigmask object */ -typedef struct jackctl_sigmask jackctl_sigmask_t; - -#ifdef __cplusplus -extern "C" { -#endif -#if 0 -} /* Adjust editor indent */ -#endif - -/** - * @defgroup ControlAPI The API for starting and controlling a JACK server - * @{ - */ - -/** - * Call this function to setup process signal handling. As a general - * rule, it is required for proper operation for the server object. - * - * @param flags signals setup flags, use 0 for none. Currently no - * flags are defined - * - * @return the configurated signal set. - */ -jackctl_sigmask_t * -jackctl_setup_signals( - unsigned int flags); - -/** - * Call this function to wait on a signal set. - * - * @param signals signals set to wait on - */ -void -jackctl_wait_signals( - jackctl_sigmask_t * signals); - -/** - * Call this function to create server object. - * - * @param on_device_acquire - Optional callback to be called before device is acquired. If false is returned, device usage will fail - * @param on_device_release - Optional callback to be called after device is released. - * - * @return server object handle, NULL if creation of server object - * failed. Successfully created server object must be destroyed with - * paired call to ::jackctl_server_destroy - */ -jackctl_server_t * -jackctl_server_create( - bool (* on_device_acquire)(const char * device_name), - void (* on_device_release)(const char * device_name)); - -/** - * Call this function to destroy server object. - * - * @param server server object handle to destroy - */ -void -jackctl_server_destroy( - jackctl_server_t * server); - -/** - * Call this function to open JACK server - * - * @param server server object handle - * @param driver driver to use - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_open( - jackctl_server_t * server, - jackctl_driver_t * driver); - -/** - * Call this function to start JACK server - * - * @param server server object handle - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_start( - jackctl_server_t * server); - -/** - * Call this function to stop JACK server - * - * @param server server object handle - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_stop( - jackctl_server_t * server); - -/** - * Call this function to close JACK server - * - * @param server server object handle - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_close( - jackctl_server_t * server); - -/** - * Call this function to get list of available drivers. List node data - * pointers is a driver object handle (::jackctl_driver_t). - * - * @param server server object handle to get drivers for - * - * @return Single linked list of driver object handles. Must not be - * modified. Always same for same server object. - */ -const JSList * -jackctl_server_get_drivers_list( - jackctl_server_t * server); - -/** - * Call this function to get list of server parameters. List node data - * pointers is a parameter object handle (::jackctl_parameter_t). - * - * @param server server object handle to get parameters for - * - * @return Single linked list of parameter object handles. Must not be - * modified. Always same for same server object. - */ -const JSList * -jackctl_server_get_parameters( - jackctl_server_t * server); - -/** - * Call this function to get list of available internal clients. List node data - * pointers is a internal client object handle (::jackctl_internal_t). - * - * @param server server object handle to get internal clients for - * - * @return Single linked list of internal client object handles. Must not be - * modified. Always same for same server object. - */ -const JSList * -jackctl_server_get_internals_list( - jackctl_server_t * server); - -/** - * Call this function to load one internal client. - * (can be used when the server is running) - * - * @param server server object handle - * @param internal internal to use - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_load_internal( - jackctl_server_t * server, - jackctl_internal_t * internal); - -/** - * Call this function to unload one internal client. - * (can be used when the server is running) - * - * @param server server object handle - * @param internal internal to unload - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_unload_internal( - jackctl_server_t * server, - jackctl_internal_t * internal); - -/** - * Call this function to add a slave in the driver slave list. - * (cannot be used when the server is running that is between - * jackctl_server_start and jackctl_server_stop) - * - * @param server server object handle - * @param driver driver to add in the driver slave list. - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_add_slave(jackctl_server_t * server, - jackctl_driver_t * driver); - -/** - * Call this function to remove a slave from the driver slave list. - * (cannot be used when the server is running that is between - * jackctl_server_start and jackctl_server_stop) - * - * @param server server object handle - * @param driver driver to remove from the driver slave list. - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_remove_slave(jackctl_server_t * server, - jackctl_driver_t * driver); - -/** - * Call this function to switch master driver. - * - * @param server server object handle - * @param driver driver to switch to - * - * @return success status: true - success, false - fail - */ -bool -jackctl_server_switch_master(jackctl_server_t * server, - jackctl_driver_t * driver); - - -/** - * Call this function to get name of driver. - * - * @param driver driver object handle to get name of - * - * @return driver name. Must not be modified. Always same for same - * driver object. - */ -const char * -jackctl_driver_get_name( - jackctl_driver_t * driver); - -/** - * Call this function to get type of driver. - * - * @param driver driver object handle to get name of - * - * @return driver type. Must not be modified. Always same for same - * driver object. - */ -jackctl_driver_type_t -jackctl_driver_get_type( - jackctl_driver_t * driver); - -/** - * Call this function to get list of driver parameters. List node data - * pointers is a parameter object handle (::jackctl_parameter_t). - * - * @param driver driver object handle to get parameters for - * - * @return Single linked list of parameter object handles. Must not be - * modified. Always same for same driver object. - */ -const JSList * -jackctl_driver_get_parameters( - jackctl_driver_t * driver); - -/** - * Call this function to parse parameters for a driver. - * - * @param driver driver object handle - * @param argc parameter list len - * @param argv parameter list, as an array of char* - * - * @return success status: true - success, false - fail - */ -int -jackctl_driver_params_parse( - jackctl_driver_t * driver, - int argc, - char* argv[]); - -/** - * Call this function to get name of internal client. - * - * @param internal internal object handle to get name of - * - * @return internal name. Must not be modified. Always same for same - * internal object. - */ -const char * -jackctl_internal_get_name( - jackctl_internal_t * internal); - -/** - * Call this function to get list of internal parameters. List node data - * pointers is a parameter object handle (::jackctl_parameter_t). - * - * @param internal internal object handle to get parameters for - * - * @return Single linked list of parameter object handles. Must not be - * modified. Always same for same internal object. - */ -const JSList * -jackctl_internal_get_parameters( - jackctl_internal_t * internal); - -/** - * Call this function to get parameter name. - * - * @param parameter parameter object handle to get name of - * - * @return parameter name. Must not be modified. Always same for same - * parameter object. - */ -const char * -jackctl_parameter_get_name( - jackctl_parameter_t * parameter); - -/** - * Call this function to get parameter short description. - * - * @param parameter parameter object handle to get short description of - * - * @return parameter short description. Must not be modified. Always - * same for same parameter object. - */ -const char * -jackctl_parameter_get_short_description( - jackctl_parameter_t * parameter); - -/** - * Call this function to get parameter long description. - * - * @param parameter parameter object handle to get long description of - * - * @return parameter long description. Must not be modified. Always - * same for same parameter object. - */ -const char * -jackctl_parameter_get_long_description( - jackctl_parameter_t * parameter); - -/** - * Call this function to get parameter type. - * - * @param parameter parameter object handle to get type of - * - * @return parameter type. Always same for same parameter object. - */ -jackctl_param_type_t -jackctl_parameter_get_type( - jackctl_parameter_t * parameter); - -/** - * Call this function to get parameter character. - * - * @param parameter parameter object handle to get character of - * - * @return character. - */ -char -jackctl_parameter_get_id( - jackctl_parameter_t * parameter); - -/** - * Call this function to check whether parameter has been set, or its - * default value is being used. - * - * @param parameter parameter object handle to check - * - * @return true - parameter is set, false - parameter is using default - * value. - */ -bool -jackctl_parameter_is_set( - jackctl_parameter_t * parameter); - -/** - * Call this function to reset parameter to its default value. - * - * @param parameter parameter object handle to reset value of - * - * @return success status: true - success, false - fail - */ -bool -jackctl_parameter_reset( - jackctl_parameter_t * parameter); - -/** - * Call this function to get parameter value. - * - * @param parameter parameter object handle to get value of - * - * @return parameter value. - */ -union jackctl_parameter_value -jackctl_parameter_get_value( - jackctl_parameter_t * parameter); - -/** - * Call this function to set parameter value. - * - * @param parameter parameter object handle to get value of - * @param value_ptr pointer to variable containing parameter value - * - * @return success status: true - success, false - fail - */ -bool -jackctl_parameter_set_value( - jackctl_parameter_t * parameter, - const union jackctl_parameter_value * value_ptr); - -/** - * Call this function to get parameter default value. - * - * @param parameter parameter object handle to get default value of - * - * @return parameter default value. - */ -union jackctl_parameter_value -jackctl_parameter_get_default_value( - jackctl_parameter_t * parameter); - -/** - * Call this function check whether parameter has range constraint. - * - * @param parameter object handle of parameter to check - * - * @return whether parameter has range constraint. - */ -bool -jackctl_parameter_has_range_constraint( - jackctl_parameter_t * parameter); - -/** - * Call this function check whether parameter has enumeration constraint. - * - * @param parameter object handle of parameter to check - * - * @return whether parameter has enumeration constraint. - */ -bool -jackctl_parameter_has_enum_constraint( - jackctl_parameter_t * parameter); - -/** - * Call this function get how many enumeration values parameter has. - * - * @param parameter object handle of parameter - * - * @return number of enumeration values - */ -uint32_t -jackctl_parameter_get_enum_constraints_count( - jackctl_parameter_t * parameter); - -/** - * Call this function to get parameter enumeration value. - * - * @param parameter object handle of parameter - * @param index index of parameter enumeration value - * - * @return enumeration value. - */ -union jackctl_parameter_value -jackctl_parameter_get_enum_constraint_value( - jackctl_parameter_t * parameter, - uint32_t index); - -/** - * Call this function to get parameter enumeration value description. - * - * @param parameter object handle of parameter - * @param index index of parameter enumeration value - * - * @return enumeration value description. - */ -const char * -jackctl_parameter_get_enum_constraint_description( - jackctl_parameter_t * parameter, - uint32_t index); - -/** - * Call this function to get parameter range. - * - * @param parameter object handle of parameter - * @param min_ptr pointer to variable receiving parameter minimum value - * @param max_ptr pointer to variable receiving parameter maximum value - */ -void -jackctl_parameter_get_range_constraint( - jackctl_parameter_t * parameter, - union jackctl_parameter_value * min_ptr, - union jackctl_parameter_value * max_ptr); - -/** - * Call this function to check whether parameter constraint is strict, - * i.e. whether supplying non-matching value will not work for sure. - * - * @param parameter parameter object handle to check - * - * @return whether parameter constraint is strict. - */ -bool -jackctl_parameter_constraint_is_strict( - jackctl_parameter_t * parameter); - -/** - * Call this function to check whether parameter has fake values, - * i.e. values have no user meaningful meaning and only value - * description is meaningful to user. - * - * @param parameter parameter object handle to check - * - * @return whether parameter constraint is strict. - */ -bool -jackctl_parameter_constraint_is_fake_value( - jackctl_parameter_t * parameter); - -/** - * Call this function to log an error message. - * - * @param format string - */ -void -jack_error( - const char *format, - ...); - -/** - * Call this function to log an information message. - * - * @param format string - */ -void -jack_info( - const char *format, - ...); - -/** - * Call this function to log an information message but only when - * verbose mode is enabled. - * - * @param format string - */ -void -jack_log( - const char *format, - ...); - -/* @} */ - -#if 0 -{ /* Adjust editor indent */ -#endif -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* #ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED */ diff --git a/include/jack/intclient.h b/include/jack/intclient.h deleted file mode 100644 index 740c0f318ac..00000000000 --- a/include/jack/intclient.h +++ /dev/null @@ -1,130 +0,0 @@ -/* -* Copyright (C) 2004 Jack O'Quin -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU Lesser General Public License as published by -* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -* -*/ - -#ifndef __jack_intclient_h__ -#define __jack_intclient_h__ - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include - -/** - * Get an internal client's name. This is useful when @ref - * JackUseExactName was not specified on jack_internal_client_load() - * and @ref JackNameNotUnique status was returned. In that case, the - * actual name will differ from the @a client_name requested. - * - * @param client requesting JACK client's handle. - * - * @param intclient handle returned from jack_internal_client_load() - * or jack_internal_client_handle(). - * - * @return NULL if unsuccessful, otherwise pointer to the internal - * client name obtained from the heap via malloc(). The caller should - * jack_free() this storage when no longer needed. - */ -char *jack_get_internal_client_name (jack_client_t *client, - jack_intclient_t intclient); - -/** - * Return the @ref jack_intclient_t handle for an internal client - * running in the JACK server. - * - * @param client requesting JACK client's handle. - * - * @param client_name for the internal client of no more than - * jack_client_name_size() characters. The name scope is local to the - * current server. - * - * @param status (if non-NULL) an address for JACK to return - * information from this operation. This status word is formed by - * OR-ing together the relevant @ref JackStatus bits. - * - * @return Opaque internal client handle if successful. If 0, the - * internal client was not found, and @a *status includes the @ref - * JackNoSuchClient and @ref JackFailure bits. - */ -jack_intclient_t jack_internal_client_handle (jack_client_t *client, - const char *client_name, - jack_status_t *status); - -/** - * Load an internal client into the JACK server. - * - * Internal clients run inside the JACK server process. They can use - * most of the same functions as external clients. Each internal - * client is built as a shared object module, which must declare - * jack_initialize() and jack_finish() entry points called at load and - * unload times. See @ref inprocess.c for an example. - * - * @param client loading JACK client's handle. - * - * @param client_name of at most jack_client_name_size() characters - * for the internal client to load. The name scope is local to the - * current server. - * - * @param options formed by OR-ing together @ref JackOptions bits. - * Only the @ref JackLoadOptions bits are valid. - * - * @param status (if non-NULL) an address for JACK to return - * information from the load operation. This status word is formed by - * OR-ing together the relevant @ref JackStatus bits. - * - * Optional parameters: depending on corresponding [@a options - * bits] additional parameters may follow @a status (in this order). - * - * @arg [@ref JackLoadName] (char *) load_name is the shared - * object file from which to load the new internal client (otherwise - * use the @a client_name). - * - * @arg [@ref JackLoadInit] (char *) load_init an arbitary - * string passed to the internal client's jack_initialize() routine - * (otherwise NULL), of no more than @ref JACK_LOAD_INIT_LIMIT bytes. - * - * @return Opaque internal client handle if successful. If this is 0, - * the load operation failed, the internal client was not loaded, and - * @a *status includes the @ref JackFailure bit. - */ -jack_intclient_t jack_internal_client_load (jack_client_t *client, - const char *client_name, - jack_options_t options, - jack_status_t *status, ...); -/** - * Unload an internal client from a JACK server. This calls the - * intclient's jack_finish() entry point then removes it. See @ref - * inprocess.c for an example. - * - * @param client unloading JACK client's handle. - * - * @param intclient handle returned from jack_internal_client_load() or - * jack_internal_client_handle(). - * - * @return 0 if successful, otherwise @ref JackStatus bits. - */ -jack_status_t jack_internal_client_unload (jack_client_t *client, - jack_intclient_t intclient); - -#ifdef __cplusplus -} -#endif - -#endif /* __jack_intclient_h__ */ diff --git a/include/jack/jack.h b/include/jack/jack.h deleted file mode 100644 index b147bab3b6f..00000000000 --- a/include/jack/jack.h +++ /dev/null @@ -1,1470 +0,0 @@ -/* - Copyright (C) 2001 Paul Davis - Copyright (C) 2004 Jack O'Quin - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __jack_h__ -#define __jack_h__ - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include -#include -#include - -/** - * Note: More documentation can be found in jack/types.h. - */ - - /************************************************************* - * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function - * added to the JACK API after the 0.116.2 release. - * - * Functions that predate this release are marked with - * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile - * time in a variety of ways. The default definition is empty, - * so that these symbols get normal linkage. If you wish to - * use all JACK symbols with weak linkage, include - * before jack.h. - *************************************************************/ - -#include - -/** - * Call this function to get version of the JACK, in form of several numbers - * - * @param major_ptr pointer to variable receiving major version of JACK. - * - * @param minor_ptr pointer to variable receiving minor version of JACK. - * - * @param major_ptr pointer to variable receiving micro version of JACK. - * - * @param major_ptr pointer to variable receiving protocol version of JACK. - * - */ -void -jack_get_version( - int *major_ptr, - int *minor_ptr, - int *micro_ptr, - int *proto_ptr) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Call this function to get version of the JACK, in form of a string - * - * @return Human readable string describing JACK version being used. - * - */ -const char * -jack_get_version_string(void) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @defgroup ClientFunctions Creating & manipulating clients - * @{ - */ - -/** - * Open an external client session with a JACK server. This interface - * is more complex but more powerful than jack_client_new(). With it, - * clients may choose which of several servers to connect, and control - * whether and how to start the server automatically, if it was not - * already running. There is also an option for JACK to generate a - * unique client name, when necessary. - * - * @param client_name of at most jack_client_name_size() characters. - * The name scope is local to each server. Unless forbidden by the - * @ref JackUseExactName option, the server will modify this name to - * create a unique variant, if needed. - * - * @param options formed by OR-ing together @ref JackOptions bits. - * Only the @ref JackOpenOptions bits are allowed. - * - * @param status (if non-NULL) an address for JACK to return - * information from the open operation. This status word is formed by - * OR-ing together the relevant @ref JackStatus bits. - * - * - * Optional parameters: depending on corresponding [@a options - * bits] additional parameters may follow @a status (in this order). - * - * @arg [@ref JackServerName] (char *) server_name selects - * from among several possible concurrent server instances. Server - * names are unique to each user. If unspecified, use "default" - * unless \$JACK_DEFAULT_SERVER is defined in the process environment. - * - * @return Opaque client handle if successful. If this is NULL, the - * open operation failed, @a *status includes @ref JackFailure and the - * caller is not a JACK client. - */ -jack_client_t * jack_client_open (const char *client_name, - jack_options_t options, - jack_status_t *status, ...) JACK_OPTIONAL_WEAK_EXPORT; - -/** -* \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN -* NEW JACK CLIENTS -* -* @deprecated Please use jack_client_open(). -*/ -jack_client_t * jack_client_new (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * Disconnects an external client from a JACK server. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_client_close (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the maximum number of characters in a JACK client name - * including the final NULL character. This value is a constant. - */ -int jack_client_name_size (void) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return pointer to actual client name. This is useful when @ref - * JackUseExactName is not specified on open and @ref - * JackNameNotUnique status was returned. In that case, the actual - * name will differ from the @a client_name requested. - */ -char * jack_get_client_name (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Get the session ID for a client name. - * - * The session manager needs this to reassociate a client name to the session_id. - * - * The caller is responsible for calling jack_free(3) on any non-NULL - * returned value. - */ -char *jack_get_uuid_for_client_name (jack_client_t *client, - const char *client_name) JACK_WEAK_EXPORT; - -/** - * Get the client name for a session_id. - * - * In order to snapshot the graph connections, the session manager needs to map - * session_ids to client names. - * - * The caller is responsible for calling jack_free(3) on any non-NULL - * returned value. - */ -char *jack_get_client_name_by_uuid (jack_client_t *client, - const char *client_uuid ) JACK_WEAK_EXPORT; - -/** - * Load an internal client into the Jack server. - * - * Internal clients run inside the JACK server process. They can use - * most of the same functions as external clients. Each internal - * client must declare jack_initialize() and jack_finish() entry - * points, called at load and unload times. See inprocess.c for an - * example of how to write an internal client. - * - * @deprecated Please use jack_internal_client_load(). - * - * @param client_name of at most jack_client_name_size() characters. - * - * @param load_name of a shared object file containing the code for - * the new client. - * - * @param load_init an arbitary string passed to the jack_initialize() - * routine of the new client (may be NULL). - * - * @return 0 if successful. - */ -int jack_internal_client_new (const char *client_name, - const char *load_name, - const char *load_init) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * Remove an internal client from a JACK server. - * - * @deprecated Please use jack_internal_client_unload(). - */ -void jack_internal_client_close (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * Tell the Jack server that the program is ready to start processing - * audio. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_activate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the Jack server to remove this @a client from the process - * graph. Also, disconnect all ports belonging to it, since inactive - * clients have no port connections. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_deactivate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return pid of client. If not available, 0 will be returned. - */ -int jack_get_client_pid (const char *name) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the pthread ID of the thread running the JACK client side - * real-time code. - */ -jack_native_thread_t jack_client_thread_id (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * @param client pointer to JACK client structure. - * - * Check if the JACK subsystem is running with -R (--realtime). - * - * @return 1 if JACK is running realtime, 0 otherwise - */ -int jack_is_realtime (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @defgroup NonCallbackAPI The non-callback API - * @{ - */ - -/** - * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN - * NEW JACK CLIENTS. - * - * @deprecated Please use jack_cycle_wait() and jack_cycle_signal() functions. - */ -jack_nframes_t jack_thread_wait (jack_client_t *client, int status) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Wait until this JACK client should process data. - * - * @param client - pointer to a JACK client structure - * - * @return the number of frames of data to process - */ -jack_nframes_t jack_cycle_wait (jack_client_t* client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Signal next clients in the graph. - * - * @param client - pointer to a JACK client structure - * @param status - if non-zero, calling thread should exit - */ -void jack_cycle_signal (jack_client_t* client, int status) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the Jack server to call @a thread_callback in the RT thread. - * Typical use are in conjunction with @a jack_cycle_wait and @a jack_cycle_signal functions. - * The code in the supplied function must be suitable for real-time - * execution. That means that it cannot call functions that might - * block for a long time. This includes malloc, free, printf, - * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, - * pthread_cond_wait, etc, etc. See - * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 - * for more information. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code. -*/ -int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * @defgroup ClientCallbacks Setting Client Callbacks - * @{ - */ - -/** - * Tell JACK to call @a thread_init_callback once just after - * the creation of the thread in which all other callbacks - * will be handled. - * - * The code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code, causing JACK - * to remove that client from the process() graph. - */ -int jack_set_thread_init_callback (jack_client_t *client, - JackThreadInitCallback thread_init_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @param client pointer to JACK client structure. - * @param function The jack_shutdown function pointer. - * @param arg The arguments for the jack_shutdown function. - * - * Register a function (and argument) to be called if and when the - * JACK server shuts down the client thread. The function must - * be written as if it were an asynchonrous POSIX signal - * handler --- use only async-safe functions, and remember that it - * is executed from another thread. A typical function might - * set a flag or write to a pipe so that the rest of the - * application knows that the JACK client thread has shut - * down. - * - * NOTE: clients do not need to call this. It exists only - * to help more complex clients understand what is going - * on. It should be called before jack_client_activate(). - * - * NOTE: if a client calls this AND jack_on_info_shutdown(), then - * in case of a client thread shutdown, the callback - * passed to this function will not be called, and the one passed to - * jack_on_info_shutdown() will. - * - * NOTE: application should typically signal another thread to correctly - * finish cleanup, that is by calling "jack_client_close" - * (since "jack_client_close" cannot be called directly in the context - * of the thread that calls the shutdown callback). - */ -void jack_on_shutdown (jack_client_t *client, - JackShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @param client pointer to JACK client structure. - * @param function The jack_info_shutdown function pointer. - * @param arg The arguments for the jack_info_shutdown function. - * - * Register a function (and argument) to be called if and when the - * JACK server shuts down the client thread. The function must - * be written as if it were an asynchonrous POSIX signal - * handler --- use only async-safe functions, and remember that it - * is executed from another thread. A typical function might - * set a flag or write to a pipe so that the rest of the - * application knows that the JACK client thread has shut - * down. - * - * NOTE: clients do not need to call this. It exists only - * to help more complex clients understand what is going - * on. It should be called before jack_client_activate(). - * - * NOTE: if a client calls this AND jack_on_shutdown(), then - * in case of a client thread shutdown, the callback passed to - * jack_on_info_shutdown() will be called. - * - * NOTE: application should typically signal another thread to correctly - * finish cleanup, that is by calling "jack_client_close" - * (since "jack_client_close" cannot be called directly in the context - * of the thread that calls the shutdown callback). - */ -void jack_on_info_shutdown (jack_client_t *client, - JackInfoShutdownCallback shutdown_callback, void *arg) JACK_WEAK_EXPORT; - -/** - * Tell the Jack server to call @a process_callback whenever there is - * work be done, passing @a arg as the second argument. - * - * The code in the supplied function must be suitable for real-time - * execution. That means that it cannot call functions that might - * block for a long time. This includes malloc, free, printf, - * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, - * pthread_cond_wait, etc, etc. See - * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 - * for more information. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_set_process_callback (jack_client_t *client, - JackProcessCallback process_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the Jack server to call @a freewheel_callback - * whenever we enter or leave "freewheel" mode, passing @a - * arg as the second argument. The first argument to the - * callback will be non-zero if JACK is entering freewheel - * mode, and zero otherwise. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_set_freewheel_callback (jack_client_t *client, - JackFreewheelCallback freewheel_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell JACK to call @a bufsize_callback whenever the size of the the - * buffer that will be passed to the @a process_callback is about to - * change. Clients that depend on knowing the buffer size must supply - * a @a bufsize_callback before activating themselves. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @param client pointer to JACK client structure. - * @param bufsize_callback function to call when the buffer size changes. - * @param arg argument for @a bufsize_callback. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_buffer_size_callback (jack_client_t *client, - JackBufferSizeCallback bufsize_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the Jack server to call @a srate_callback whenever the system - * sample rate changes. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_sample_rate_callback (jack_client_t *client, - JackSampleRateCallback srate_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the JACK server to call @a client_registration_callback whenever a - * client is registered or unregistered, passing @a arg as a parameter. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_client_registration_callback (jack_client_t *client, - JackClientRegistrationCallback - registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the JACK server to call @a registration_callback whenever a - * port is registered or unregistered, passing @a arg as a parameter. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ - int jack_set_port_registration_callback (jack_client_t *client, - JackPortRegistrationCallback - registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - - /** - * Tell the JACK server to call @a connect_callback whenever a - * port is connected or disconnected, passing @a arg as a parameter. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_port_connect_callback (jack_client_t *client, - JackPortConnectCallback - connect_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - - /** - * Tell the JACK server to call @a rename_callback whenever a - * port is renamed, passing @a arg as a parameter. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_port_rename_callback (jack_client_t *client, - JackPortRenameCallback - rename_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the JACK server to call @a graph_callback whenever the - * processing graph is reordered, passing @a arg as a parameter. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_graph_order_callback (jack_client_t *client, - JackGraphOrderCallback graph_callback, - void *) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Tell the JACK server to call @a xrun_callback whenever there is a - * xrun, passing @a arg as a parameter. - * - * All "notification events" are received in a seperated non RT thread, - * the code in the supplied function does not need to be - * suitable for real-time execution. - * - * NOTE: this function cannot be called while the client is activated - * (after jack_activate has been called.) - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_xrun_callback (jack_client_t *client, - JackXRunCallback xrun_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * Tell the Jack server to call @a latency_callback whenever it - * is necessary to recompute the latencies for some or all - * Jack ports. - * - * @a latency_callback will be called twice each time it is - * needed, once being passed JackCaptureLatency and once - * JackPlaybackLatency. See @ref LatencyFunctions for - * the definition of each type of latency and related functions. - * - * IMPORTANT: Most JACK clients do NOT need to register a latency - * callback. - * - * Clients that meet any of the following conditions do NOT - * need to register a latency callback: - * - * - have only input ports - * - have only output ports - * - their output is totally unrelated to their input - * - their output is not delayed relative to their input - * (i.e. data that arrives in a given process() - * callback is processed and output again in the - * same callback) - * - * Clients NOT registering a latency callback MUST also - * satisfy this condition: - * - * - have no multiple distinct internal signal pathways - * - * This means that if your client has more than 1 input and - * output port, and considers them always "correlated" - * (e.g. as a stereo pair), then there is only 1 (e.g. stereo) - * signal pathway through the client. This would be true, - * for example, of a stereo FX rack client that has a - * left/right input pair and a left/right output pair. - * - * However, this is somewhat a matter of perspective. The - * same FX rack client could be connected so that its - * two input ports were connected to entirely separate - * sources. Under these conditions, the fact that the client - * does not register a latency callback MAY result - * in port latency values being incorrect. - * - * Clients that do not meet any of those conditions SHOULD - * register a latency callback. - * - * See the documentation for @ref jack_port_set_latency_range() - * on how the callback should operate. Remember that the @a mode - * argument given to the latency callback will need to be - * passed into @ref jack_port_set_latency_range() - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_latency_callback (jack_client_t *client, - JackLatencyCallback latency_callback, - void *) JACK_WEAK_EXPORT; -/*@}*/ - -/** - * @defgroup ServerClientControl Controlling & querying JACK server operation - * @{ - */ - -/** - * Start/Stop JACK's "freewheel" mode. - * - * When in "freewheel" mode, JACK no longer waits for - * any external event to begin the start of the next process - * cycle. - * - * As a result, freewheel mode causes "faster than realtime" - * execution of a JACK graph. If possessed, real-time - * scheduling is dropped when entering freewheel mode, and - * if appropriate it is reacquired when stopping. - * - * IMPORTANT: on systems using capabilities to provide real-time - * scheduling (i.e. Linux kernel 2.4), if onoff is zero, this function - * must be called from the thread that originally called jack_activate(). - * This restriction does not apply to other systems (e.g. Linux kernel 2.6 - * or OS X). - * - * @param client pointer to JACK client structure - * @param onoff if non-zero, freewheel mode starts. Otherwise - * freewheel mode ends. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_set_freewheel(jack_client_t* client, int onoff) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Change the buffer size passed to the @a process_callback. - * - * This operation stops the JACK engine process cycle, then calls all - * registered @a bufsize_callback functions before restarting the - * process cycle. This will cause a gap in the audio flow, so it - * should only be done at appropriate stopping points. - * - * @see jack_set_buffer_size_callback() - * - * @param client pointer to JACK client structure. - * @param nframes new buffer size. Must be a power of two. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the sample rate of the jack system, as set by the user when - * jackd was started. - */ -jack_nframes_t jack_get_sample_rate (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the current maximum size that will ever be passed to the @a - * process_callback. It should only be used *before* the client has - * been activated. This size may change, clients that depend on it - * must register a @a bufsize_callback so they will be notified if it - * does. - * - * @see jack_set_buffer_size_callback() - */ -jack_nframes_t jack_get_buffer_size (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Old-style interface to become the timebase for the entire JACK - * subsystem. - * - * @deprecated This function still exists for compatibility with the - * earlier transport interface, but it does nothing. Instead, see - * transport.h and use jack_set_timebase_callback(). - * - * @return ENOSYS, function not implemented. - */ -int jack_engine_takeover_timebase (jack_client_t *) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * @return the current CPU load estimated by JACK. This is a running - * average of the time it takes to execute a full process cycle for - * all clients as a percentage of the real time available per cycle - * determined by the buffer size and sample rate. - */ -float jack_cpu_load (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * @defgroup PortFunctions Creating & manipulating ports - * @{ - */ - -/** - * Create a new port for the client. This is an object used for moving - * data of any type in or out of the client. Ports may be connected - * in various ways. - * - * Each port has a short name. The port's full name contains the name - * of the client concatenated with a colon (:) followed by its short - * name. The jack_port_name_size() is the maximum length of this full - * name. Exceeding that will cause the port registration to fail and - * return NULL. - * - * The @a port_name must be unique among all ports owned by this client. - * If the name is not unique, the registration will fail. - * - * All ports have a type, which may be any non-NULL and non-zero - * length string, passed as an argument. Some port types are built - * into the JACK API, currently only JACK_DEFAULT_AUDIO_TYPE. - * - * @param client pointer to JACK client structure. - * @param port_name non-empty short name for the new port (not - * including the leading @a "client_name:"). Must be unique. - * @param port_type port type name. If longer than - * jack_port_type_size(), only that many characters are significant. - * @param flags @ref JackPortFlags bit mask. - * @param buffer_size must be non-zero if this is not a built-in @a - * port_type. Otherwise, it is ignored. - * - * @return jack_port_t pointer on success, otherwise NULL. - */ -jack_port_t * jack_port_register (jack_client_t *client, - const char *port_name, - const char *port_type, - unsigned long flags, - unsigned long buffer_size) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Remove the port from the client, disconnecting any existing - * connections. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_port_unregister (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * This returns a pointer to the memory area associated with the - * specified port. For an output port, it will be a memory area - * that can be written to; for an input port, it will be an area - * containing the data from the port's connection(s), or - * zero-filled. if there are multiple inbound connections, the data - * will be mixed appropriately. - * - * FOR OUTPUT PORTS ONLY : DEPRECATED in Jack 2.0 !! - * --------------------------------------------------- - * You may cache the value returned, but only between calls to - * your "blocksize" callback. For this reason alone, you should - * either never cache the return value or ensure you have - * a "blocksize" callback and be sure to invalidate the cached - * address from there. - * - * Caching output ports is DEPRECATED in Jack 2.0, due to some new optimization (like "pipelining"). - * Port buffers have to be retrieved in each callback for proper functionning. - */ -void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the UUID of the jack_port_t - * - * @see jack_uuid_to_string() to convert into a string representation - */ -jack_uuid_t jack_port_uuid (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the full name of the jack_port_t (including the @a - * "client_name:" prefix). - * - * @see jack_port_name_size(). - */ -const char * jack_port_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the short name of the jack_port_t (not including the @a - * "client_name:" prefix). - * - * @see jack_port_name_size(). - */ -const char * jack_port_short_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the @ref JackPortFlags of the jack_port_t. - */ -int jack_port_flags (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the @a port type, at most jack_port_type_size() characters - * including a final NULL. - */ -const char * jack_port_type (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - - /** - * @return the @a port type id. - */ -jack_port_type_id_t jack_port_type_id (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return TRUE if the jack_port_t belongs to the jack_client_t. - */ -int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return number of connections to or from @a port. - * - * @pre The calling client must own @a port. - */ -int jack_port_connected (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return TRUE if the locally-owned @a port is @b directly connected - * to the @a port_name. - * - * @see jack_port_name_size() - */ -int jack_port_connected_to (const jack_port_t *port, - const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return a null-terminated array of full port names to which the @a - * port is connected. If none, returns NULL. - * - * The caller is responsible for calling jack_free() on any non-NULL - * returned value. - * - * @param port locally owned jack_port_t pointer. - * - * @see jack_port_name_size(), jack_port_get_all_connections() - */ -const char ** jack_port_get_connections (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return a null-terminated array of full port names to which the @a - * port is connected. If none, returns NULL. - * - * The caller is responsible for calling jack_free() on any non-NULL - * returned value. - * - * This differs from jack_port_get_connections() in two important - * respects: - * - * 1) You may not call this function from code that is - * executed in response to a JACK event. For example, - * you cannot use it in a GraphReordered handler. - * - * 2) You need not be the owner of the port to get information - * about its connections. - * - * @see jack_port_name_size() - */ -const char ** jack_port_get_all_connections (const jack_client_t *client, - const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * - * @deprecated This function will be removed from a future version - * of JACK. Do not use it. There is no replacement. It has - * turned out to serve essentially no purpose in real-life - * JACK clients. - */ -int jack_port_tie (jack_port_t *src, jack_port_t *dst) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * - * @deprecated This function will be removed from a future version - * of JACK. Do not use it. There is no replacement. It has - * turned out to serve essentially no purpose in real-life - * JACK clients. - */ -int jack_port_untie (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN - * NEW JACK CLIENTS - * - * Modify a port's short name. May be called at any time. If the - * resulting full name (including the @a "client_name:" prefix) is - * longer than jack_port_name_size(), it will be truncated. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_port_set_name (jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * Modify a port's short name. May NOT be called from a callback handling a server event. - * If the resulting full name (including the @a "client_name:" prefix) is - * longer than jack_port_name_size(), it will be truncated. - * - * @return 0 on success, otherwise a non-zero error code. - * - * This differs from jack_port_set_name() by triggering PortRename notifications to - * clients that have registered a port rename handler. - */ -int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Set @a alias as an alias for @a port. May be called at any time. - * If the alias is longer than jack_port_name_size(), it will be truncated. - * - * After a successful call, and until JACK exits or - * @function jack_port_unset_alias() is called, @alias may be - * used as a alternate name for the port. - * - * Ports can have up to two aliases - if both are already - * set, this function will return an error. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_port_set_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Remove @a alias as an alias for @a port. May be called at any time. - * - * After a successful call, @a alias can no longer be - * used as a alternate name for the port. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_port_unset_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Get any aliases known for @port. - * - * @return the number of aliases discovered for the port - */ -int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * If @ref JackPortCanMonitor is set for this @a port, turn input - * monitoring on or off. Otherwise, do nothing. - */ -int jack_port_request_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * If @ref JackPortCanMonitor is set for this @a port_name, turn input - * monitoring on or off. Otherwise, do nothing. - * - * @return 0 on success, otherwise a non-zero error code. - * - * @see jack_port_name_size() - */ -int jack_port_request_monitor_by_name (jack_client_t *client, - const char *port_name, int onoff) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * If @ref JackPortCanMonitor is set for a port, this function turns - * on input monitoring if it was off, and turns it off if only one - * request has been made to turn it on. Otherwise it does nothing. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_port_ensure_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return TRUE if input monitoring has been requested for @a port. - */ -int jack_port_monitoring_input (jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Establish a connection between two ports. - * - * When a connection exists, data written to the source port will - * be available to be read at the destination port. - * - * @pre The port types must be identical. - * - * @pre The @ref JackPortFlags of the @a source_port must include @ref - * JackPortIsOutput. - * - * @pre The @ref JackPortFlags of the @a destination_port must include - * @ref JackPortIsInput. - * - * @return 0 on success, EEXIST if the connection is already made, - * otherwise a non-zero error code - */ -int jack_connect (jack_client_t *client, - const char *source_port, - const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Remove a connection between two ports. - * - * @pre The port types must be identical. - * - * @pre The @ref JackPortFlags of the @a source_port must include @ref - * JackPortIsOutput. - * - * @pre The @ref JackPortFlags of the @a destination_port must include - * @ref JackPortIsInput. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_disconnect (jack_client_t *client, - const char *source_port, - const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Perform the same function as jack_disconnect() using port handles - * rather than names. This avoids the name lookup inherent in the - * name-based version. - * - * Clients connecting their own ports are likely to use this function, - * while generic connection clients (e.g. patchbays) would use - * jack_disconnect(). - */ -int jack_port_disconnect (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the maximum number of characters in a full JACK port name - * including the final NULL character. This value is a constant. - * - * A port's full name contains the owning client name concatenated - * with a colon (:) followed by its short name and a NULL - * character. - */ -int jack_port_name_size(void) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the maximum number of characters in a JACK port type name - * including the final NULL character. This value is a constant. - */ -int jack_port_type_size(void) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the buffersize of a port of type @arg port_type. - * - * this function may only be called in a buffer_size callback. - */ -size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) JACK_WEAK_EXPORT; - -/*@}*/ - -/** - * @defgroup LatencyFunctions Managing and determining latency - * - * The purpose of JACK's latency API is to allow clients to - * easily answer two questions: - * - * - How long has it been since the data read from a port arrived - * at the edge of the JACK graph (either via a physical port - * or being synthesized from scratch)? - * - * - How long will it be before the data written to a port arrives - * at the edge of a JACK graph? - - * To help answering these two questions, all JACK ports have two - * latency values associated with them, both measured in frames: - * - * capture latency: how long since the data read from - * the buffer of a port arrived at - * a port marked with JackPortIsTerminal. - * The data will have come from the "outside - * world" if the terminal port is also - * marked with JackPortIsPhysical, or - * will have been synthesized by the client - * that owns the terminal port. - * - * playback latency: how long until the data - * written to the buffer of port will reach a port - * marked with JackPortIsTerminal. - * - * Both latencies might potentially have more than one value - * because there may be multiple pathways to/from a given port - * and a terminal port. Latency is therefore generally - * expressed a min/max pair. - * - * In most common setups, the minimum and maximum latency - * are the same, but this design accomodates more complex - * routing, and allows applications (and thus users) to - * detect cases where routing is creating an anomalous - * situation that may either need fixing or more - * sophisticated handling by clients that care about - * latency. - * - * See also @ref jack_set_latency_callback for details on how - * clients that add latency to the signal path should interact - * with JACK to ensure that the correct latency figures are - * used. - * @{ - */ - -/** - * The port latency is zero by default. Clients that control - * physical hardware with non-zero latency should call this - * to set the latency to its correct value. Note that the value - * should include any systemic latency present "outside" the - * physical hardware controlled by the client. For example, - * for a client controlling a digital audio interface connected - * to an external digital converter, the latency setting should - * include both buffering by the audio interface *and* the converter. - * - * @deprecated This method will be removed in the next major - * release of JACK. It should not be used in new code, and should - * be replaced by a latency callback that calls @ref - * jack_port_set_latency_range(). - */ -void jack_port_set_latency (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * return the latency range defined by @a mode for - * @a port, in frames. - * - * See @ref LatencyFunctions for the definition of each latency value. - * - * This is normally used in the LatencyCallback. - * and therefor safe to execute from callbacks. - */ -void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; - -/** - * set the minimum and maximum latencies defined by - * @a mode for @a port, in frames. - * - * See @ref LatencyFunctions for the definition of each latency value. - * - * This function should ONLY be used inside a latency - * callback. The client should determine the current - * value of the latency using @ref jack_port_get_latency_range() - * (called using the same mode as @a mode) - * and then add some number of frames to that reflects - * latency added by the client. - * - * How much latency a client adds will vary - * dramatically. For most clients, the answer is zero - * and there is no reason for them to register a latency - * callback and thus they should never call this - * function. - * - * More complex clients that take an input signal, - * transform it in some way and output the result but - * not during the same process() callback will - * generally know a single constant value to add - * to the value returned by @ref jack_port_get_latency_range(). - * - * Such clients would register a latency callback (see - * @ref jack_set_latency_callback) and must know what input - * ports feed which output ports as part of their - * internal state. Their latency callback will update - * the ports' latency values appropriately. - * - * A pseudo-code example will help. The @a mode argument to the latency - * callback will determine whether playback or capture - * latency is being set. The callback will use - * @ref jack_port_set_latency_range() as follows: - * - * \code - * jack_latency_range_t range; - * if (mode == JackPlaybackLatency) { - * foreach input_port in (all self-registered port) { - * jack_port_get_latency_range (port_feeding_input_port, JackPlaybackLatency, &range); - * range.min += min_delay_added_as_signal_flows_from port_feeding to input_port; - * range.max += max_delay_added_as_signal_flows_from port_feeding to input_port; - * jack_port_set_latency_range (input_port, JackPlaybackLatency, &range); - * } - * } else if (mode == JackCaptureLatency) { - * foreach output_port in (all self-registered port) { - * jack_port_get_latency_range (port_fed_by_output_port, JackCaptureLatency, &range); - * range.min += min_delay_added_as_signal_flows_from_output_port_to_fed_by_port; - * range.max += max_delay_added_as_signal_flows_from_output_port_to_fed_by_port; - * jack_port_set_latency_range (output_port, JackCaptureLatency, &range); - * } - * } - * \endcode - * - * In this relatively simple pseudo-code example, it is assumed that - * each input port or output is connected to only 1 output or input - * port respectively. - * - * If a port is connected to more than 1 other port, then the - * range.min and range.max values passed to @ref - * jack_port_set_latency_range() should reflect the minimum and - * maximum values across all connected ports. - * - * See the description of @ref jack_set_latency_callback for more - * information. - */ -void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; - -/** - * Request a complete recomputation of all port latencies. This - * can be called by a client that has just changed the internal - * latency of its port using jack_port_set_latency - * and wants to ensure that all signal pathways in the graph - * are updated with respect to the values that will be returned - * by jack_port_get_total_latency. It allows a client - * to change multiple port latencies without triggering a - * recompute for each change. - * - * @return zero for successful execution of the request. non-zero - * otherwise. - */ -int jack_recompute_total_latencies (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the time (in frames) between data being available or - * delivered at/to a port, and the time at which it arrived at or is - * delivered to the "other side" of the port. E.g. for a physical - * audio output port, this is the time between writing to the port and - * when the signal will leave the connector. For a physical audio - * input port, this is the time between the sound arriving at the - * connector and the corresponding frames being readable from the - * port. - * - * @deprecated This method will be removed in the next major - * release of JACK. It should not be used in new code, and should - * be replaced by jack_port_get_latency_range() in any existing - * use cases. - */ -jack_nframes_t jack_port_get_latency (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * The maximum of the sum of the latencies in every - * connection path that can be drawn between the port and other - * ports with the @ref JackPortIsTerminal flag set. - * - * @deprecated This method will be removed in the next major - * release of JACK. It should not be used in new code, and should - * be replaced by jack_port_get_latency_range() in any existing - * use cases. - */ -jack_nframes_t jack_port_get_total_latency (jack_client_t *client, - jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/** - * Request a complete recomputation of a port's total latency. This - * can be called by a client that has just changed the internal - * latency of its port using jack_port_set_latency - * and wants to ensure that all signal pathways in the graph - * are updated with respect to the values that will be returned - * by jack_port_get_total_latency. - * - * @return zero for successful execution of the request. non-zero - * otherwise. - * - * @deprecated This method will be removed in the next major - * release of JACK. It should not be used in new code, and should - * be replaced by jack_recompute_total_latencies() in any existing - * use cases. - */ -int jack_recompute_total_latency (jack_client_t*, jack_port_t* port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; - -/*@}*/ - -/** - * @defgroup PortSearching Looking up ports - * @{ - */ - -/** - * @param port_name_pattern A regular expression used to select - * ports by name. If NULL or of zero length, no selection based - * on name will be carried out. - * @param type_name_pattern A regular expression used to select - * ports by type. If NULL or of zero length, no selection based - * on type will be carried out. - * @param flags A value used to select ports by their flags. - * If zero, no selection based on flags will be carried out. - * - * @return a NULL-terminated array of ports that match the specified - * arguments. The caller is responsible for calling jack_free() any - * non-NULL returned value. - * - * @see jack_port_name_size(), jack_port_type_size() - */ -const char ** jack_get_ports (jack_client_t *client, - const char *port_name_pattern, - const char *type_name_pattern, - unsigned long flags) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return address of the jack_port_t named @a port_name. - * - * @see jack_port_name_size() - */ -jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return address of the jack_port_t of a @a port_id. - */ -jack_port_t * jack_port_by_id (jack_client_t *client, - jack_port_id_t port_id) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * @defgroup TimeFunctions Handling time - * @{ - * - * JACK time is in units of 'frames', according to the current sample rate. - * The absolute value of frame times is meaningless, frame times have meaning - * only relative to each other. - */ - -/** - * @return the estimated time in frames that has passed since the JACK - * server began the current process cycle. - */ -jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the estimated current time in frames. - * This function is intended for use in other threads (not the process - * callback). The return value can be compared with the value of - * jack_last_frame_time to relate time in other threads to JACK time. - */ -jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the precise time at the start of the current process cycle. - * This function may only be used from the process callback, and can - * be used to interpret timestamps generated by jack_frame_time() in - * other threads with respect to the current process cycle. - * - * This is the only jack time function that returns exact time: - * when used during the process callback it always returns the same - * value (until the next process callback, where it will return - * that value + nframes, etc). The return value is guaranteed to be - * monotonic and linear in this fashion unless an xrun occurs. - * If an xrun occurs, clients must check this value again, as time - * may have advanced in a non-linear way (e.g. cycles may have been skipped). - */ -jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * This function may only be used from the process callback. - * It provides the internal cycle timing information as used by - * most of the other time related functions. This allows the - * caller to map between frame counts and microseconds with full - * precision (i.e. without rounding frame times to integers), - * and also provides e.g. the microseconds time of the start of - * the current cycle directly (it has to be computed otherwise). - * - * If the return value is zero, the following information is - * provided in the variables pointed to by the arguments: - * - * current_frames: the frame time counter at the start of the - * current cycle, same as jack_last_frame_time(). - * current_usecs: the microseconds time at the start of the - * current cycle. - * next_usecs: the microseconds time of the start of the next - * next cycle as computed by the DLL. - * period_usecs: the current best estimate of the period time in - * microseconds. - * - * NOTES: - * - * Because of the types used, all the returned values except period_usecs - * are unsigned. In computations mapping between frames and microseconds - * *signed* differences are required. The easiest way is to compute those - * separately and assign them to the appropriate signed variables, - * int32_t for frames and int64_t for usecs. See the implementation of - * jack_frames_to_time() and Jack_time_to_frames() for an example. - * - * Unless there was an xrun, skipped cycles, or the current cycle is the - * first after freewheeling or starting Jack, the value of current_usecs - * will always be the value of next_usecs of the previous cycle. - * - * The value of period_usecs will in general NOT be exactly equal to - * the difference of next_usecs and current_usecs. This is because to - * ensure stability of the DLL and continuity of the mapping, a fraction - * of the loop error must be included in next_usecs. For an accurate - * mapping between frames and microseconds, the difference of next_usecs - * and current_usecs should be used, and not period_usecs. - * - * @return zero if OK, non-zero otherwise. - */ -int jack_get_cycle_times(const jack_client_t *client, - jack_nframes_t *current_frames, - jack_time_t *current_usecs, - jack_time_t *next_usecs, - float *period_usecs) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the estimated time in microseconds of the specified frame time - */ -jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return the estimated time in frames for the specified system time. - */ -jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @return return JACK's current system time in microseconds, - * using the JACK clock source. - * - * The value returned is guaranteed to be monotonic, but not linear. - */ -jack_time_t jack_get_time(void) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * @defgroup ErrorOutput Controlling error/information output - */ -/*@{*/ - -/** - * Display JACK error message. - * - * Set via jack_set_error_function(), otherwise a JACK-provided - * default will print @a msg (plus a newline) to stderr. - * - * @param msg error message text (no newline at end). - */ -extern void (*jack_error_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Set the @ref jack_error_callback for error message display. - * Set it to NULL to restore default_jack_error_callback function. - * - * The JACK library provides two built-in callbacks for this purpose: - * default_jack_error_callback() and silent_jack_error_callback(). - */ -void jack_set_error_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Display JACK info message. - * - * Set via jack_set_info_function(), otherwise a JACK-provided - * default will print @a msg (plus a newline) to stdout. - * - * @param msg info message text (no newline at end). - */ -extern void (*jack_info_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Set the @ref jack_info_callback for info message display. - * Set it to NULL to restore default_jack_info_callback function. - * - * The JACK library provides two built-in callbacks for this purpose: - * default_jack_info_callback() and silent_jack_info_callback(). - */ -void jack_set_info_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -/** - * The free function to be used on memory returned by jack_port_get_connections, - * jack_port_get_all_connections, jack_get_ports and jack_get_internal_client_name functions. - * This is MANDATORY on Windows when otherwise all nasty runtime version related crashes can occur. - * Developers are strongly encouraged to use this function instead of the standard "free" function in new code. - * - * @param ptr the memory pointer to be deallocated. - */ -void jack_free(void* ptr) JACK_OPTIONAL_WEAK_EXPORT; - - -#ifdef __cplusplus -} -#endif - -#endif /* __jack_h__ */ diff --git a/include/jack/jslist.h b/include/jack/jslist.h deleted file mode 100644 index 4a2bf7067ff..00000000000 --- a/include/jack/jslist.h +++ /dev/null @@ -1,293 +0,0 @@ -/* - Based on gslist.c from glib-1.2.9 (LGPL). - - Adaption to JACK, Copyright (C) 2002 Kai Vehmanen. - - replaced use of gtypes with normal ANSI C types - - glib's memory allocation routines replaced with - malloc/free calls - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __jack_jslist_h__ -#define __jack_jslist_h__ - -#include -#include - -#ifdef sun -#define __inline__ -#endif - -typedef struct _JSList JSList; - -typedef int (*JCompareFunc) (void* a, void* b); -struct _JSList -{ - void *data; - JSList *next; -}; - -static __inline__ -JSList* -jack_slist_alloc (void) -{ - JSList *new_list; - - new_list = (JSList*)malloc(sizeof(JSList)); - if (new_list) { - new_list->data = NULL; - new_list->next = NULL; - } - - return new_list; -} - -static __inline__ -JSList* -jack_slist_prepend (JSList* list, void* data) -{ - JSList *new_list; - - new_list = (JSList*)malloc(sizeof(JSList)); - if (new_list) { - new_list->data = data; - new_list->next = list; - } - - return new_list; -} - -#define jack_slist_next(slist) ((slist) ? (((JSList *)(slist))->next) : NULL) -static __inline__ -JSList* -jack_slist_last (JSList *list) -{ - if (list) { - while (list->next) - list = list->next; - } - - return list; -} - -static __inline__ -JSList* -jack_slist_remove_link (JSList *list, - JSList *link) -{ - JSList *tmp; - JSList *prev; - - prev = NULL; - tmp = list; - - while (tmp) { - if (tmp == link) { - if (prev) - prev->next = tmp->next; - if (list == tmp) - list = list->next; - - tmp->next = NULL; - break; - } - - prev = tmp; - tmp = tmp->next; - } - - return list; -} - -static __inline__ -void -jack_slist_free (JSList *list) -{ - while (list) { - JSList *next = list->next; - free(list); - list = next; - } -} - -static __inline__ -void -jack_slist_free_1 (JSList *list) -{ - if (list) { - free(list); - } -} - -static __inline__ -JSList* -jack_slist_remove (JSList *list, - void *data) -{ - JSList *tmp; - JSList *prev; - - prev = NULL; - tmp = list; - - while (tmp) { - if (tmp->data == data) { - if (prev) - prev->next = tmp->next; - if (list == tmp) - list = list->next; - - tmp->next = NULL; - jack_slist_free (tmp); - - break; - } - - prev = tmp; - tmp = tmp->next; - } - - return list; -} - -static __inline__ -unsigned int -jack_slist_length (JSList *list) -{ - unsigned int length; - - length = 0; - while (list) { - length++; - list = list->next; - } - - return length; -} - -static __inline__ -JSList* -jack_slist_find (JSList *list, - void *data) -{ - while (list) { - if (list->data == data) - break; - list = list->next; - } - - return list; -} - -static __inline__ -JSList* -jack_slist_copy (JSList *list) -{ - JSList *new_list = NULL; - - if (list) { - JSList *last; - - new_list = jack_slist_alloc (); - new_list->data = list->data; - last = new_list; - list = list->next; - while (list) { - last->next = jack_slist_alloc (); - last = last->next; - last->data = list->data; - list = list->next; - } - } - - return new_list; -} - -static __inline__ -JSList* -jack_slist_append (JSList *list, - void *data) -{ - JSList *new_list; - JSList *last; - - new_list = jack_slist_alloc (); - new_list->data = data; - - if (list) { - last = jack_slist_last (list); - last->next = new_list; - - return list; - } else - return new_list; -} - -static __inline__ -JSList* -jack_slist_sort_merge (JSList *l1, - JSList *l2, - JCompareFunc compare_func) -{ - JSList list, *l; - - l = &list; - - while (l1 && l2) { - if (compare_func(l1->data, l2->data) < 0) { - l = l->next = l1; - l1 = l1->next; - } else { - l = l->next = l2; - l2 = l2->next; - } - } - l->next = l1 ? l1 : l2; - - return list.next; -} - -static __inline__ -JSList* -jack_slist_sort (JSList *list, - JCompareFunc compare_func) -{ - JSList *l1, *l2; - - if (!list) - return NULL; - if (!list->next) - return list; - - l1 = list; - l2 = list->next; - - while ((l2 = l2->next) != NULL) { - if ((l2 = l2->next) == NULL) - break; - l1 = l1->next; - } - l2 = l1->next; - l1->next = NULL; - - return jack_slist_sort_merge (jack_slist_sort (list, compare_func), - jack_slist_sort (l2, compare_func), - compare_func); -} - -#endif /* __jack_jslist_h__ */ - diff --git a/include/jack/midiport.h b/include/jack/midiport.h deleted file mode 100644 index af16e0d76a6..00000000000 --- a/include/jack/midiport.h +++ /dev/null @@ -1,184 +0,0 @@ -/* - Copyright (C) 2004 Ian Esten - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - - -#ifndef __JACK_MIDIPORT_H -#define __JACK_MIDIPORT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - - -/** Type for raw event data contained in @ref jack_midi_event_t. */ -typedef unsigned char jack_midi_data_t; - - -/** A Jack MIDI event. */ -typedef struct _jack_midi_event -{ - jack_nframes_t time; /**< Sample index at which event is valid */ - size_t size; /**< Number of bytes of data in \a buffer */ - jack_midi_data_t *buffer; /**< Raw MIDI data */ -} jack_midi_event_t; - - -/** - * @defgroup MIDIAPI Reading and writing MIDI data - * @{ - */ - -/** Get number of events in a port buffer. - * - * @param port_buffer Port buffer from which to retrieve event. - * @return number of events inside @a port_buffer - */ -uint32_t -jack_midi_get_event_count(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; - - -/** Get a MIDI event from an event port buffer. - * - * Jack MIDI is normalised, the MIDI event returned by this function is - * guaranteed to be a complete MIDI event (the status byte will always be - * present, and no realtime events will interspered with the event). - * - * @param event Event structure to store retrieved event in. - * @param port_buffer Port buffer from which to retrieve event. - * @param event_index Index of event to retrieve. - * @return 0 on success, ENODATA if buffer is empty. - */ -int -jack_midi_event_get(jack_midi_event_t *event, - void *port_buffer, - uint32_t event_index) JACK_OPTIONAL_WEAK_EXPORT; - - -/** Clear an event buffer. - * - * This should be called at the beginning of each process cycle before calling - * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This - * function may not be called on an input port's buffer. - * - * @param port_buffer Port buffer to clear (must be an output port buffer). - */ -void -jack_midi_clear_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; - -/** Reset an event buffer (from data allocated outside of JACK). - * - * This should be called at the beginning of each process cycle before calling - * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This - * function may not be called on an input port's buffer. - * - * @param port_buffer Port buffer to resetted. - */ -void -jack_midi_reset_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; - - -/** Get the size of the largest event that can be stored by the port. - * - * This function returns the current space available, taking into account - * events already stored in the port. - * - * @param port_buffer Port buffer to check size of. - */ -size_t -jack_midi_max_event_size(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; - - -/** Allocate space for an event to be written to an event port buffer. - * - * Clients are to write the actual event data to be written starting at the - * pointer returned by this function. Clients must not write more than - * @a data_size bytes into this buffer. Clients must write normalised - * MIDI data to the port - no running status and no (1-byte) realtime - * messages interspersed with other messages (realtime messages are fine - * when they occur on their own, like other messages). - * - * Events must be written in order, sorted by their sample offsets. - * JACK will not sort the events for you, and will refuse to store - * out-of-order events. - * - * @param port_buffer Buffer to write event to. - * @param time Sample offset of event. - * @param data_size Length of event's raw data in bytes. - * @return Pointer to the beginning of the reserved event's data buffer, or - * NULL on error (ie not enough space). - */ -jack_midi_data_t* -jack_midi_event_reserve(void *port_buffer, - jack_nframes_t time, - size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; - - -/** Write an event into an event port buffer. - * - * This function is simply a wrapper for @ref jack_midi_event_reserve - * which writes the event data into the space reserved in the buffer. - * - * Clients must not write more than - * @a data_size bytes into this buffer. Clients must write normalised - * MIDI data to the port - no running status and no (1-byte) realtime - * messages interspersed with other messages (realtime messages are fine - * when they occur on their own, like other messages). - * - * Events must be written in order, sorted by their sample offsets. - * JACK will not sort the events for you, and will refuse to store - * out-of-order events. - * - * @param port_buffer Buffer to write event to. - * @param time Sample offset of event. - * @param data Message data to be written. - * @param data_size Length of @a data in bytes. - * @return 0 on success, ENOBUFS if there's not enough space in buffer for event. - */ -int -jack_midi_event_write(void *port_buffer, - jack_nframes_t time, - const jack_midi_data_t *data, - size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; - - -/** Get the number of events that could not be written to @a port_buffer. - * - * This function returning a non-zero value implies @a port_buffer is full. - * Currently the only way this can happen is if events are lost on port mixdown. - * - * @param port_buffer Port to receive count for. - * @returns Number of events that could not be written to @a port_buffer. - */ -uint32_t -jack_midi_get_lost_event_count(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -#ifdef __cplusplus -} -#endif - - -#endif /* __JACK_MIDIPORT_H */ - - diff --git a/include/jack/ringbuffer.h b/include/jack/ringbuffer.h deleted file mode 100644 index 74be11769ac..00000000000 --- a/include/jack/ringbuffer.h +++ /dev/null @@ -1,243 +0,0 @@ -/* - Copyright (C) 2000 Paul Davis - Copyright (C) 2003 Rohan Drape - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef _RINGBUFFER_H -#define _RINGBUFFER_H - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include - -/** @file ringbuffer.h - * - * A set of library functions to make lock-free ringbuffers available - * to JACK clients. The `capture_client.c' (in the example_clients - * directory) is a fully functioning user of this API. - * - * The key attribute of a ringbuffer is that it can be safely accessed - * by two threads simultaneously -- one reading from the buffer and - * the other writing to it -- without using any synchronization or - * mutual exclusion primitives. For this to work correctly, there can - * only be a single reader and a single writer thread. Their - * identities cannot be interchanged. - */ - -typedef struct { - char *buf; - size_t len; -} -jack_ringbuffer_data_t ; - -typedef struct { - char *buf; - volatile size_t write_ptr; - volatile size_t read_ptr; - size_t size; - size_t size_mask; - int mlocked; -} -jack_ringbuffer_t ; - -/** - * Allocates a ringbuffer data structure of a specified size. The - * caller must arrange for a call to jack_ringbuffer_free() to release - * the memory associated with the ringbuffer. - * - * @param sz the ringbuffer size in bytes. - * - * @return a pointer to a new jack_ringbuffer_t, if successful; NULL - * otherwise. - */ -jack_ringbuffer_t *jack_ringbuffer_create(size_t sz); - -/** - * Frees the ringbuffer data structure allocated by an earlier call to - * jack_ringbuffer_create(). - * - * @param rb a pointer to the ringbuffer structure. - */ -void jack_ringbuffer_free(jack_ringbuffer_t *rb); - -/** - * Fill a data structure with a description of the current readable - * data held in the ringbuffer. This description is returned in a two - * element array of jack_ringbuffer_data_t. Two elements are needed - * because the data to be read may be split across the end of the - * ringbuffer. - * - * The first element will always contain a valid @a len field, which - * may be zero or greater. If the @a len field is non-zero, then data - * can be read in a contiguous fashion using the address given in the - * corresponding @a buf field. - * - * If the second element has a non-zero @a len field, then a second - * contiguous stretch of data can be read from the address given in - * its corresponding @a buf field. - * - * @param rb a pointer to the ringbuffer structure. - * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. - * - */ -void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, - jack_ringbuffer_data_t *vec); - -/** - * Fill a data structure with a description of the current writable - * space in the ringbuffer. The description is returned in a two - * element array of jack_ringbuffer_data_t. Two elements are needed - * because the space available for writing may be split across the end - * of the ringbuffer. - * - * The first element will always contain a valid @a len field, which - * may be zero or greater. If the @a len field is non-zero, then data - * can be written in a contiguous fashion using the address given in - * the corresponding @a buf field. - * - * If the second element has a non-zero @a len field, then a second - * contiguous stretch of data can be written to the address given in - * the corresponding @a buf field. - * - * @param rb a pointer to the ringbuffer structure. - * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. - */ -void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, - jack_ringbuffer_data_t *vec); - -/** - * Read data from the ringbuffer. - * - * @param rb a pointer to the ringbuffer structure. - * @param dest a pointer to a buffer where data read from the - * ringbuffer will go. - * @param cnt the number of bytes to read. - * - * @return the number of bytes read, which may range from 0 to cnt. - */ -size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt); - -/** - * Read data from the ringbuffer. Opposed to jack_ringbuffer_read() - * this function does not move the read pointer. Thus it's - * a convenient way to inspect data in the ringbuffer in a - * continous fashion. The price is that the data is copied - * into a user provided buffer. For "raw" non-copy inspection - * of the data in the ringbuffer use jack_ringbuffer_get_read_vector(). - * - * @param rb a pointer to the ringbuffer structure. - * @param dest a pointer to a buffer where data read from the - * ringbuffer will go. - * @param cnt the number of bytes to read. - * - * @return the number of bytes read, which may range from 0 to cnt. - */ -size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt); - -/** - * Advance the read pointer. - * - * After data have been read from the ringbuffer using the pointers - * returned by jack_ringbuffer_get_read_vector(), use this function to - * advance the buffer pointers, making that space available for future - * write operations. - * - * @param rb a pointer to the ringbuffer structure. - * @param cnt the number of bytes read. - */ -void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt); - -/** - * Return the number of bytes available for reading. - * - * @param rb a pointer to the ringbuffer structure. - * - * @return the number of bytes available to read. - */ -size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb); - -/** - * Lock a ringbuffer data block into memory. - * - * Uses the mlock() system call. This is not a realtime operation. - * - * @param rb a pointer to the ringbuffer structure. - */ -int jack_ringbuffer_mlock(jack_ringbuffer_t *rb); - -/** - * Reset the read and write pointers, making an empty buffer. - * - * This is not thread safe. - * - * @param rb a pointer to the ringbuffer structure. - */ -void jack_ringbuffer_reset(jack_ringbuffer_t *rb); - -/** - * Reset the internal "available" size, and read and write pointers, making an empty buffer. - * - * This is not thread safe. - * - * @param rb a pointer to the ringbuffer structure. - * @param sz the new size, that must be less than allocated size. - */ -void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz); - -/** - * Write data into the ringbuffer. - * - * @param rb a pointer to the ringbuffer structure. - * @param src a pointer to the data to be written to the ringbuffer. - * @param cnt the number of bytes to write. - * - * @return the number of bytes write, which may range from 0 to cnt - */ -size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, - size_t cnt); - -/** - * Advance the write pointer. - * - * After data have been written the ringbuffer using the pointers - * returned by jack_ringbuffer_get_write_vector(), use this function - * to advance the buffer pointer, making the data available for future - * read operations. - * - * @param rb a pointer to the ringbuffer structure. - * @param cnt the number of bytes written. - */ -void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt); - -/** - * Return the number of bytes available for writing. - * - * @param rb a pointer to the ringbuffer structure. - * - * @return the amount of free space (in bytes) available for writing. - */ -size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/include/jack/session.h b/include/jack/session.h deleted file mode 100644 index cb75cea74ed..00000000000 --- a/include/jack/session.h +++ /dev/null @@ -1,270 +0,0 @@ -/* - Copyright (C) 2001 Paul Davis - Copyright (C) 2004 Jack O'Quin - Copyright (C) 2010 Torben Hohn - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#ifndef __jack_session_h__ -#define __jack_session_h__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/** - * @defgroup SessionClientFunctions Session API for clients. - * @{ - */ - - -/** - * Session event type. - * - * If a client cant save templates, i might just do a normal save. - * - * There is no "quit without saving" event because a client might refuse to - * quit when it has unsaved data, but other clients may have already quit. - * This results in too much confusion, so it is unsupported. - */ -enum JackSessionEventType { - /** - * Save the session completely. - * - * The client may save references to data outside the provided directory, - * but it must do so by creating a link inside the provided directory and - * referring to that in any save files. The client must not refer to data - * files outside the provided directory directly in save files, because - * this makes it impossible for the session manager to create a session - * archive for distribution or archival. - */ - JackSessionSave = 1, - - /** - * Save the session completly, then quit. - * - * The rules for saving are exactly the same as for JackSessionSave. - */ - JackSessionSaveAndQuit = 2, - - /** - * Save a session template. - * - * A session template is a "skeleton" of the session, but without any data. - * Clients must save a session that, when restored, will create the same - * ports as a full save would have. However, the actual data contained in - * the session may not be saved (e.g. a DAW would create the necessary - * tracks, but not save the actual recorded data). - */ - JackSessionSaveTemplate = 3 -}; - -typedef enum JackSessionEventType jack_session_event_type_t; - -/** - * @ref jack_session_flags_t bits - */ -enum JackSessionFlags { - /** - * An error occured while saving. - */ - JackSessionSaveError = 0x01, - - /** - * Client needs to be run in a terminal. - */ - JackSessionNeedTerminal = 0x02 -}; - -/** - * Session flags. - */ -typedef enum JackSessionFlags jack_session_flags_t; - -struct _jack_session_event { - /** - * The type of this session event. - */ - jack_session_event_type_t type; - - /** - * Session directory path, with trailing separator. - * - * This directory is exclusive to the client; when saving the client may - * create any files it likes in this directory. - */ - const char *session_dir; - - /** - * Client UUID which must be passed to jack_client_open on session load. - * - * The client can specify this in the returned command line, or save it - * in a state file within the session directory. - */ - const char *client_uuid; - - /** - * Reply (set by client): the command line needed to restore the client. - * - * This is a platform dependent command line. It must contain - * ${SESSION_DIR} instead of the actual session directory path. More - * generally, just as in session files, clients should not include any - * paths outside the session directory here as this makes - * archival/distribution impossible. - * - * This field is set to NULL by Jack when the event is delivered to the - * client. The client must set to allocated memory that is safe to - * free(). This memory will be freed by jack_session_event_free. - */ - char *command_line; - - /** - * Reply (set by client): Session flags. - */ - jack_session_flags_t flags; - - /** - * Future flags. Set to zero for now. - */ - uint32_t future; -}; - -typedef struct _jack_session_event jack_session_event_t; - -/** - * Prototype for the client supplied function that is called - * whenever a session notification is sent via jack_session_notify(). - * - * Ownership of the memory of @a event is passed to the application. - * It must be freed using jack_session_event_free when its not used anymore. - * - * The client must promptly call jack_session_reply for this event. - * - * @param event The event structure. - * @param arg Pointer to a client supplied structure. - */ -typedef void (*JackSessionCallback)(jack_session_event_t *event, - void *arg); - -/** - * Tell the JACK server to call @a session_callback when a session event - * is to be delivered. - * - * setting more than one session_callback per process is probably a design - * error. if you have a multiclient application its more sensible to create - * a jack_client with only a session callback set. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_set_session_callback (jack_client_t *client, - JackSessionCallback session_callback, - void *arg) JACK_WEAK_EXPORT; - -/** - * Reply to a session event. - * - * This can either be called directly from the callback, or later from a - * different thread. For example, it is possible to push the event through a - * queue and execute the save code from the GUI thread. - * - * @return 0 on success, otherwise a non-zero error code - */ -int jack_session_reply (jack_client_t *client, - jack_session_event_t *event) JACK_WEAK_EXPORT; - - -/** - * Free memory used by a jack_session_event_t. - * - * This also frees the memory used by the command_line pointer, if its non NULL. - */ -void jack_session_event_free (jack_session_event_t *event) JACK_WEAK_EXPORT; - - -/** - * Get the assigned uuid for client. - * Safe to call from callback and all other threads. - * - * The caller is responsible for calling jack_free(3) on any non-NULL - * returned value. - */ -char *jack_client_get_uuid (jack_client_t *client) JACK_WEAK_EXPORT; - -/** - * @} - */ - -/** - * @defgroup JackSessionManagerAPI API for a session manager. - * - * @{ - */ - -typedef struct { - const char *uuid; - const char *client_name; - const char *command; - jack_session_flags_t flags; -} jack_session_command_t; - -/** - * Send an event to all clients listening for session callbacks. - * - * The returned strings of the clients are accumulated and returned as an array - * of jack_session_command_t. its terminated by ret[i].uuid == NULL target == - * NULL means send to all interested clients. otherwise a clientname - */ -jack_session_command_t *jack_session_notify ( - jack_client_t* client, - const char *target, - jack_session_event_type_t type, - const char *path) JACK_WEAK_EXPORT; - -/** - * Free the memory allocated by a session command. - */ -void jack_session_commands_free (jack_session_command_t *cmds) JACK_WEAK_EXPORT; - -/** - * Reserve a client name and associate it with a UUID. - * - * When a client later calls jack_client_open() and specifies the UUID, jackd - * will assign the reserved name. This allows a session manager to know in - * advance under which client name its managed clients will appear. - * - * @return 0 on success, otherwise a non-zero error code - */ -int -jack_reserve_client_name (jack_client_t *client, - const char *name, - const char *uuid) JACK_WEAK_EXPORT; - -/** - * Find out whether a client has set up a session callback. - * - * @return 0 when the client has no session callback, 1 when it has one. - * -1 on error. - */ -int -jack_client_has_session_callback (jack_client_t *client, const char *client_name) JACK_WEAK_EXPORT; - -#ifdef __cplusplus -} -#endif -#endif diff --git a/include/jack/statistics.h b/include/jack/statistics.h deleted file mode 100644 index 36b6f88fcba..00000000000 --- a/include/jack/statistics.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -* Copyright (C) 2004 Rui Nuno Capela, Lee Revell -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public License -* as published by the Free Software Foundation; either version 2.1 -* 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 -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this program; if not, write to the Free -* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -* 02111-1307, USA. -* -*/ - -#ifndef __statistics_h__ -#define __statistics_h__ - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include - -/** - * @return the maximum delay reported by the backend since - * startup or reset. When compared to the period size in usecs, this - * can be used to estimate the ideal period size for a given setup. - */ -float jack_get_max_delayed_usecs (jack_client_t *client); - -/** - * @return the delay in microseconds due to the most recent XRUN - * occurrence. This probably only makes sense when called from a @ref - * JackXRunCallback defined using jack_set_xrun_callback(). - */ -float jack_get_xrun_delayed_usecs (jack_client_t *client); - -/** - * Reset the maximum delay counter. This would be useful - * to estimate the effect that a change to the configuration of a running - * system (e.g. toggling kernel preemption) has on the delay - * experienced by JACK, without having to restart the JACK engine. - */ -void jack_reset_max_delayed_usecs (jack_client_t *client); - -#ifdef __cplusplus -} -#endif - -#endif /* __statistics_h__ */ diff --git a/include/jack/systemdeps.h b/include/jack/systemdeps.h deleted file mode 100644 index 7cf144aab67..00000000000 --- a/include/jack/systemdeps.h +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright (C) 2004-2012 Grame - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation; either version 2.1 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; if not, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - -#ifndef __jack_systemdeps_h__ -#define __jack_systemdeps_h__ - -#ifndef POST_PACKED_STRUCTURE - - #ifdef __GNUC__ - /* POST_PACKED_STRUCTURE needs to be a macro which - expands into a compiler directive. The directive must - tell the compiler to arrange the preceding structure - declaration so that it is packed on byte-boundaries rather - than use the natural alignment of the processor and/or - compiler. - */ - - #define PRE_PACKED_STRUCTURE - #define POST_PACKED_STRUCTURE __attribute__((__packed__)) - - #else - - #ifdef _MSC_VER - #define PRE_PACKED_STRUCTURE1 __pragma(pack(push,1)) - #define PRE_PACKED_STRUCTURE PRE_PACKED_STRUCTURE1 - /* PRE_PACKED_STRUCTURE needs to be a macro which - expands into a compiler directive. The directive must - tell the compiler to arrange the following structure - declaration so that it is packed on byte-boundaries rather - than use the natural alignment of the processor and/or - compiler. - */ - #define POST_PACKED_STRUCTURE ;__pragma(pack(pop)) - /* and POST_PACKED_STRUCTURE needs to be a macro which - restores the packing to its previous setting */ - #else - #define PRE_PACKED_STRUCTURE - #define POST_PACKED_STRUCTURE - #endif /* _MSC_VER */ - - #endif /* __GNUC__ */ - -#endif - -#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(GNU_WIN32) - - #include - - #ifdef _MSC_VER /* Microsoft compiler */ - #define __inline__ inline - #if (!defined(int8_t) && !defined(_STDINT_H)) - #define __int8_t_defined - typedef char int8_t; - typedef unsigned char uint8_t; - typedef short int16_t; - typedef unsigned short uint16_t; - typedef long int32_t; - typedef unsigned long uint32_t; - typedef LONGLONG int64_t; - typedef ULONGLONG uint64_t; - #endif - #elif __MINGW32__ /* MINGW */ - #include - #include - #else /* other compilers ...*/ - #include - #include - #include - #endif - - #if !defined(_PTHREAD_H) && !defined(PTHREAD_WIN32) - /** - * to make jack API independent of different thread implementations, - * we define jack_native_thread_t to HANDLE here. - */ - typedef HANDLE jack_native_thread_t; - #else - #ifdef PTHREAD_WIN32 // Added by JE - 10-10-2011 - #include // Makes sure we #include the ptw32 version ! - #endif - /** - * to make jack API independent of different thread implementations, - * we define jack_native_thread_t to pthread_t here. - */ - typedef pthread_t jack_native_thread_t; - #endif - -#endif /* _WIN32 && !__CYGWIN__ && !GNU_WIN32 */ - -#if defined(__APPLE__) || defined(__linux__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) - - #if defined(__CYGWIN__) || defined(GNU_WIN32) - #include - #endif - #include - #include - #include - - /** - * to make jack API independent of different thread implementations, - * we define jack_native_thread_t to pthread_t here. - */ - typedef pthread_t jack_native_thread_t; - -#endif /* __APPLE__ || __linux__ || __sun__ || sun */ - -#if defined(__arm__) || defined(__ppc__) || defined(__powerpc__) - #undef POST_PACKED_STRUCTURE - #define POST_PACKED_STRUCTURE -#endif /* __arm__ || __ppc__ || __powerpc__ */ - -#endif /* __jack_systemdeps_h__ */ diff --git a/include/jack/thread.h b/include/jack/thread.h deleted file mode 100644 index 8b2c3944bda..00000000000 --- a/include/jack/thread.h +++ /dev/null @@ -1,160 +0,0 @@ -/* - Copyright (C) 2004 Paul Davis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __jack_thread_h__ -#define __jack_thread_h__ - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include -#include - -/* use 512KB stack per thread - the default is way too high to be feasible - * with mlockall() on many systems */ -#define THREAD_STACK 524288 - -/** @file thread.h - * - * Library functions to standardize thread creation for JACK and its - * clients. These interfaces hide some system variations in the - * handling of realtime scheduling and associated privileges. - */ - -/** - * @defgroup ClientThreads Creating and managing client threads - * @{ - */ - - /** - * @returns if JACK is running with realtime scheduling, this returns - * the priority that any JACK-created client threads will run at. - * Otherwise returns -1. - */ - -int jack_client_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * @returns if JACK is running with realtime scheduling, this returns - * the maximum priority that a JACK client thread should use if the thread - * is subject to realtime scheduling. Otherwise returns -1. - */ - -int jack_client_max_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Attempt to enable realtime scheduling for a thread. On some - * systems that may require special privileges. - * - * @param thread POSIX thread ID. - * @param priority requested thread priority. - * - * @returns 0, if successful; EPERM, if the calling process lacks - * required realtime privileges; otherwise some other error number. - */ -int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Create a thread for JACK or one of its clients. The thread is - * created executing @a start_routine with @a arg as its sole - * argument. - * - * @param client the JACK client for whom the thread is being created. May be - * NULL if the client is being created within the JACK server. - * @param thread place to return POSIX thread ID. - * @param priority thread priority, if realtime. - * @param realtime true for the thread to use realtime scheduling. On - * some systems that may require special privileges. - * @param start_routine function the thread calls when it starts. - * @param arg parameter passed to the @a start_routine. - * - * @returns 0, if successful; otherwise some error number. - */ -int jack_client_create_thread (jack_client_t* client, - jack_native_thread_t *thread, - int priority, - int realtime, /* boolean */ - void *(*start_routine)(void*), - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Drop realtime scheduling for a thread. - * - * @param thread POSIX thread ID. - * - * @returns 0, if successful; otherwise an error number. - */ -int jack_drop_real_time_scheduling (jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Stop the thread, waiting for the thread handler to terminate. - * - * @param thread POSIX thread ID. - * - * @returns 0, if successful; otherwise an error number. - */ -int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Kill the thread. - * - * @param thread POSIX thread ID. - * - * @returns 0, if successful; otherwise an error number. - */ - int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; - -#ifndef _WIN32 - - typedef int (*jack_thread_creator_t)(pthread_t*, - const pthread_attr_t*, - void* (*function)(void*), - void* arg); -/** - * This function can be used in very very specialized cases - * where it is necessary that client threads created by JACK - * are created by something other than pthread_create(). After - * it is used, any threads that JACK needs for the client will - * will be created by calling the function passed to this - * function. - * - * No normal application/client should consider calling this. - * The specific case for which it was created involves running - * win32/x86 plugins under Wine on Linux, where it is necessary - * that all threads that might call win32 functions are known - * to Wine. - * - * Set it to NULL to restore thread creation function. - * - * @param creator a function that creates a new thread - * - */ -void jack_set_thread_creator (jack_thread_creator_t creator) JACK_OPTIONAL_WEAK_EXPORT; - -#endif - -/* @} */ - -#ifdef __cplusplus -} -#endif - -#endif /* __jack_thread_h__ */ diff --git a/include/jack/transport.h b/include/jack/transport.h deleted file mode 100644 index 9f18ef68876..00000000000 --- a/include/jack/transport.h +++ /dev/null @@ -1,247 +0,0 @@ -/* - Copyright (C) 2002 Paul Davis - Copyright (C) 2003 Jack O'Quin - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __jack_transport_h__ -#define __jack_transport_h__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include - -/** - * @defgroup TransportControl Transport and Timebase control - * @{ - */ - -/** - * Called by the timebase master to release itself from that - * responsibility. - * - * If the timebase master releases the timebase or leaves the JACK - * graph for any reason, the JACK engine takes over at the start of - * the next process cycle. The transport state does not change. If - * rolling, it continues to play, with frame numbers as the only - * available position information. - * - * @see jack_set_timebase_callback - * - * @param client the JACK client structure. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_release_timebase (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Register (or unregister) as a slow-sync client, one that cannot - * respond immediately to transport position changes. - * - * The @a sync_callback will be invoked at the first available - * opportunity after its registration is complete. If the client is - * currently active this will be the following process cycle, - * otherwise it will be the first cycle after calling jack_activate(). - * After that, it runs according to the ::JackSyncCallback rules. - * Clients that don't set a @a sync_callback are assumed to be ready - * immediately any time the transport wants to start. - * - * @param client the JACK client structure. - * @param sync_callback is a realtime function that returns TRUE when - * the client is ready. Setting @a sync_callback to NULL declares that - * this client no longer requires slow-sync processing. - * @param arg an argument for the @a sync_callback function. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_set_sync_callback (jack_client_t *client, - JackSyncCallback sync_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Set the timeout value for slow-sync clients. - * - * This timeout prevents unresponsive slow-sync clients from - * completely halting the transport mechanism. The default is two - * seconds. When the timeout expires, the transport starts rolling, - * even if some slow-sync clients are still unready. The @a - * sync_callbacks of these clients continue being invoked, giving them - * a chance to catch up. - * - * @see jack_set_sync_callback - * - * @param client the JACK client structure. - * @param timeout is delay (in microseconds) before the timeout expires. - * - * @return 0 on success, otherwise a non-zero error code. - */ -int jack_set_sync_timeout (jack_client_t *client, - jack_time_t timeout) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Register as timebase master for the JACK subsystem. - * - * The timebase master registers a callback that updates extended - * position information such as beats or timecode whenever necessary. - * Without this extended information, there is no need for this - * function. - * - * There is never more than one master at a time. When a new client - * takes over, the former @a timebase_callback is no longer called. - * Taking over the timebase may be done conditionally, so it fails if - * there was a master already. - * - * @param client the JACK client structure. - * @param conditional non-zero for a conditional request. - * @param timebase_callback is a realtime function that returns - * position information. - * @param arg an argument for the @a timebase_callback function. - * - * @return - * - 0 on success; - * - EBUSY if a conditional request fails because there was already a - * timebase master; - * - other non-zero error code. - */ -int jack_set_timebase_callback (jack_client_t *client, - int conditional, - JackTimebaseCallback timebase_callback, - void *arg) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Reposition the transport to a new frame number. - * - * May be called at any time by any client. The new position takes - * effect in two process cycles. If there are slow-sync clients and - * the transport is already rolling, it will enter the - * ::JackTransportStarting state and begin invoking their @a - * sync_callbacks until ready. This function is realtime-safe. - * - * @see jack_transport_reposition, jack_set_sync_callback - * - * @param client the JACK client structure. - * @param frame frame number of new transport position. - * - * @return 0 if valid request, non-zero otherwise. - */ -int jack_transport_locate (jack_client_t *client, - jack_nframes_t frame) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Query the current transport state and position. - * - * This function is realtime-safe, and can be called from any thread. - * If called from the process thread, @a pos corresponds to the first - * frame of the current cycle and the state returned is valid for the - * entire cycle. - * - * @param client the JACK client structure. - * @param pos pointer to structure for returning current transport - * position; @a pos->valid will show which fields contain valid data. - * If @a pos is NULL, do not return position information. - * - * @return Current transport state. - */ -jack_transport_state_t jack_transport_query (const jack_client_t *client, - jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Return an estimate of the current transport frame, - * including any time elapsed since the last transport - * positional update. - * - * @param client the JACK client structure - */ -jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Request a new transport position. - * - * May be called at any time by any client. The new position takes - * effect in two process cycles. If there are slow-sync clients and - * the transport is already rolling, it will enter the - * ::JackTransportStarting state and begin invoking their @a - * sync_callbacks until ready. This function is realtime-safe. - * - * @see jack_transport_locate, jack_set_sync_callback - * - * @param client the JACK client structure. - * @param pos requested new transport position. - * - * @return 0 if valid request, EINVAL if position structure rejected. - */ -int jack_transport_reposition (jack_client_t *client, - const jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Start the JACK transport rolling. - * - * Any client can make this request at any time. It takes effect no - * sooner than the next process cycle, perhaps later if there are - * slow-sync clients. This function is realtime-safe. - * - * @see jack_set_sync_callback - * - * @param client the JACK client structure. - */ -void jack_transport_start (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Stop the JACK transport. - * - * Any client can make this request at any time. It takes effect on - * the next process cycle. This function is realtime-safe. - * - * @param client the JACK client structure. - */ -void jack_transport_stop (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Gets the current transport info structure (deprecated). - * - * @param client the JACK client structure. - * @param tinfo current transport info structure. The "valid" field - * describes which fields contain valid data. - * - * @deprecated This is for compatibility with the earlier transport - * interface. Use jack_transport_query(), instead. - * - * @pre Must be called from the process thread. - */ -void jack_get_transport_info (jack_client_t *client, - jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; - -/** - * Set the transport info structure (deprecated). - * - * @deprecated This function still exists for compatibility with the - * earlier transport interface, but it does nothing. Instead, define - * a ::JackTimebaseCallback. - */ -void jack_set_transport_info (jack_client_t *client, - jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; - -/*@}*/ - -#ifdef __cplusplus -} -#endif - -#endif /* __jack_transport_h__ */ diff --git a/include/jack/types.h b/include/jack/types.h deleted file mode 100644 index 4641a99f281..00000000000 --- a/include/jack/types.h +++ /dev/null @@ -1,740 +0,0 @@ -/* - Copyright (C) 2001 Paul Davis - Copyright (C) 2004 Jack O'Quin - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __jack_types_h__ -#define __jack_types_h__ - -#include - -typedef uint64_t jack_uuid_t; - -typedef int32_t jack_shmsize_t; - -/** - * Type used to represent sample frame counts. - */ -typedef uint32_t jack_nframes_t; - -/** - * Maximum value that can be stored in jack_nframes_t - */ -#define JACK_MAX_FRAMES (4294967295U) /* This should be UINT32_MAX, but C++ has a problem with that. */ - -/** - * Type used to represent the value of free running - * monotonic clock with units of microseconds. - */ -typedef uint64_t jack_time_t; - -/** - * Maximum size of @a load_init string passed to an internal client - * jack_initialize() function via jack_internal_client_load(). - */ -#define JACK_LOAD_INIT_LIMIT 1024 - -/** - * jack_intclient_t is an opaque type representing a loaded internal - * client. You may only access it using the API provided in @ref - * intclient.h "". - */ -typedef uint64_t jack_intclient_t; - -/** - * jack_port_t is an opaque type. You may only access it using the - * API provided. - */ -typedef struct _jack_port jack_port_t; - -/** - * jack_client_t is an opaque type. You may only access it using the - * API provided. - */ -typedef struct _jack_client jack_client_t; - -/** - * Ports have unique ids. A port registration callback is the only - * place you ever need to know their value. - */ -typedef uint32_t jack_port_id_t; - -typedef uint32_t jack_port_type_id_t; - -/** - * @ref jack_options_t bits - */ -enum JackOptions { - - /** - * Null value to use when no option bits are needed. - */ - JackNullOption = 0x00, - - /** - * Do not automatically start the JACK server when it is not - * already running. This option is always selected if - * \$JACK_NO_START_SERVER is defined in the calling process - * environment. - */ - JackNoStartServer = 0x01, - - /** - * Use the exact client name requested. Otherwise, JACK - * automatically generates a unique one, if needed. - */ - JackUseExactName = 0x02, - - /** - * Open with optional (char *) server_name parameter. - */ - JackServerName = 0x04, - - /** - * Load internal client from optional (char *) - * load_name. Otherwise use the @a client_name. - */ - JackLoadName = 0x08, - - /** - * Pass optional (char *) load_init string to the - * jack_initialize() entry point of an internal client. - */ - JackLoadInit = 0x10, - - /** - * pass a SessionID Token this allows the sessionmanager to identify the client again. - */ - JackSessionID = 0x20 -}; - -/** Valid options for opening an external client. */ -#define JackOpenOptions (JackSessionID|JackServerName|JackNoStartServer|JackUseExactName) - -/** Valid options for loading an internal client. */ -#define JackLoadOptions (JackLoadInit|JackLoadName|JackUseExactName) - -/** - * Options for several JACK operations, formed by OR-ing together the - * relevant @ref JackOptions bits. - */ -typedef enum JackOptions jack_options_t; - -/** - * @ref jack_status_t bits - */ -enum JackStatus { - - /** - * Overall operation failed. - */ - JackFailure = 0x01, - - /** - * The operation contained an invalid or unsupported option. - */ - JackInvalidOption = 0x02, - - /** - * The desired client name was not unique. With the @ref - * JackUseExactName option this situation is fatal. Otherwise, - * the name was modified by appending a dash and a two-digit - * number in the range "-01" to "-99". The - * jack_get_client_name() function will return the exact string - * that was used. If the specified @a client_name plus these - * extra characters would be too long, the open fails instead. - */ - JackNameNotUnique = 0x04, - - /** - * The JACK server was started as a result of this operation. - * Otherwise, it was running already. In either case the caller - * is now connected to jackd, so there is no race condition. - * When the server shuts down, the client will find out. - */ - JackServerStarted = 0x08, - - /** - * Unable to connect to the JACK server. - */ - JackServerFailed = 0x10, - - /** - * Communication error with the JACK server. - */ - JackServerError = 0x20, - - /** - * Requested client does not exist. - */ - JackNoSuchClient = 0x40, - - /** - * Unable to load internal client - */ - JackLoadFailure = 0x80, - - /** - * Unable to initialize client - */ - JackInitFailure = 0x100, - - /** - * Unable to access shared memory - */ - JackShmFailure = 0x200, - - /** - * Client's protocol version does not match - */ - JackVersionError = 0x400, - - /** - * Backend error - */ - JackBackendError = 0x800, - - /** - * Client zombified failure - */ - JackClientZombie = 0x1000 -}; - -/** - * Status word returned from several JACK operations, formed by - * OR-ing together the relevant @ref JackStatus bits. - */ -typedef enum JackStatus jack_status_t; - -/** - * @ref jack_latency_callback_mode_t - */ -enum JackLatencyCallbackMode { - - /** - * Latency Callback for Capture Latency. - * Input Ports have their latency value setup. - * In the Callback the client needs to set the latency of the output ports - */ - JackCaptureLatency, - - /** - * Latency Callback for Playback Latency. - * Output Ports have their latency value setup. - * In the Callback the client needs to set the latency of the input ports - */ - JackPlaybackLatency - -}; - -/** - * Type of Latency Callback (Capture or Playback) - */ -typedef enum JackLatencyCallbackMode jack_latency_callback_mode_t; - -/** - * Prototype for the client supplied function that is called - * by the engine when port latencies need to be recalculated - * - * @param mode playback or capture latency - * @param arg pointer to a client supplied data - * - * @return zero on success, non-zero on error - */ -typedef void (*JackLatencyCallback)(jack_latency_callback_mode_t mode, void *arg); - -/** - * the new latency API operates on Ranges. - */ -PRE_PACKED_STRUCTURE -struct _jack_latency_range -{ - /** - * minimum latency - */ - jack_nframes_t min; - /** - * maximum latency - */ - jack_nframes_t max; -} POST_PACKED_STRUCTURE; - -typedef struct _jack_latency_range jack_latency_range_t; - -/** - * Prototype for the client supplied function that is called - * by the engine anytime there is work to be done. - * - * @pre nframes == jack_get_buffer_size() - * @pre nframes == pow(2,x) - * - * @param nframes number of frames to process - * @param arg pointer to a client supplied structure - * - * @return zero on success, non-zero on error - */ -typedef int (*JackProcessCallback)(jack_nframes_t nframes, void *arg); - -/** - * Prototype for the client thread routine called - * by the engine when the client is inserted in the graph. - * - * @param arg pointer to a client supplied structure - * - */ -typedef void *(*JackThreadCallback)(void* arg); - -/** - * Prototype for the client supplied function that is called - * once after the creation of the thread in which other - * callbacks will be made. Special thread characteristics - * can be set from this callback, for example. This is a - * highly specialized callback and most clients will not - * and should not use it. - * - * @param arg pointer to a client supplied structure - * - * @return void - */ -typedef void (*JackThreadInitCallback)(void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever the processing graph is reordered. - * - * @param arg pointer to a client supplied structure - * - * @return zero on success, non-zero on error - */ -typedef int (*JackGraphOrderCallback)(void *arg); - -/** - * Prototype for the client-supplied function that is called whenever - * an xrun has occured. - * - * @see jack_get_xrun_delayed_usecs() - * - * @param arg pointer to a client supplied structure - * - * @return zero on success, non-zero on error - */ -typedef int (*JackXRunCallback)(void *arg); - -/** - * Prototype for the @a bufsize_callback that is invoked whenever the - * JACK engine buffer size changes. Although this function is called - * in the JACK process thread, the normal process cycle is suspended - * during its operation, causing a gap in the audio flow. So, the @a - * bufsize_callback can allocate storage, touch memory not previously - * referenced, and perform other operations that are not realtime - * safe. - * - * @param nframes buffer size - * @param arg pointer supplied by jack_set_buffer_size_callback(). - * - * @return zero on success, non-zero on error - */ -typedef int (*JackBufferSizeCallback)(jack_nframes_t nframes, void *arg); - -/** - * Prototype for the client supplied function that is called - * when the engine sample rate changes. - * - * @param nframes new engine sample rate - * @param arg pointer to a client supplied structure - * - * @return zero on success, non-zero on error - */ -typedef int (*JackSampleRateCallback)(jack_nframes_t nframes, void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever a port is registered or unregistered. - * - * @param port the ID of the port - * @param arg pointer to a client supplied data - * @param register non-zero if the port is being registered, - * zero if the port is being unregistered - */ -typedef void (*JackPortRegistrationCallback)(jack_port_id_t port, int /* register */, void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever a client is registered or unregistered. - * - * @param name a null-terminated string containing the client name - * @param register non-zero if the client is being registered, - * zero if the client is being unregistered - * @param arg pointer to a client supplied structure - */ -typedef void (*JackClientRegistrationCallback)(const char* name, int /* register */, void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever a port is connected or disconnected. - * - * @param a one of two ports connected or disconnected - * @param b one of two ports connected or disconnected - * @param connect non-zero if ports were connected - * zero if ports were disconnected - * @param arg pointer to a client supplied data - */ -typedef void (*JackPortConnectCallback)(jack_port_id_t a, jack_port_id_t b, int connect, void* arg); - -/** - * Prototype for the client supplied function that is called - * whenever the port name has been changed. - * - * @param port the port that has been renamed - * @param new_name the new name - * @param arg pointer to a client supplied structure - */ -typedef void (*JackPortRenameCallback)(jack_port_id_t port, const char* old_name, const char* new_name, void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever jackd starts or stops freewheeling. - * - * @param starting non-zero if we start starting to freewheel, zero otherwise - * @param arg pointer to a client supplied structure - */ -typedef void (*JackFreewheelCallback)(int starting, void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever jackd is shutdown. Note that after server shutdown, - * the client pointer is *not* deallocated by libjack, - * the application is responsible to properly use jack_client_close() - * to release client ressources. Warning: jack_client_close() cannot be - * safely used inside the shutdown callback and has to be called outside of - * the callback context. - * - * @param arg pointer to a client supplied structure - */ -typedef void (*JackShutdownCallback)(void *arg); - -/** - * Prototype for the client supplied function that is called - * whenever jackd is shutdown. Note that after server shutdown, - * the client pointer is *not* deallocated by libjack, - * the application is responsible to properly use jack_client_close() - * to release client ressources. Warning: jack_client_close() cannot be - * safely used inside the shutdown callback and has to be called outside of - * the callback context. - - * @param code a status word, formed by OR-ing together the relevant @ref JackStatus bits. - * @param reason a string describing the shutdown reason (backend failure, server crash... etc...). - * Note that this string will not be available anymore after the callback returns, so possibly copy it. - * @param arg pointer to a client supplied structure - */ -typedef void (*JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg); - -/** - * Used for the type argument of jack_port_register() for default - * audio ports and midi ports. - */ -#define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" -#define JACK_DEFAULT_MIDI_TYPE "8 bit raw midi" - -/** - * For convenience, use this typedef if you want to be able to change - * between float and double. You may want to typedef sample_t to - * jack_default_audio_sample_t in your application. - */ -typedef float jack_default_audio_sample_t; - -/** - * A port has a set of flags that are formed by AND-ing together the - * desired values from the list below. The flags "JackPortIsInput" and - * "JackPortIsOutput" are mutually exclusive and it is an error to use - * them both. - */ -enum JackPortFlags { - - /** - * if JackPortIsInput is set, then the port can receive - * data. - */ - JackPortIsInput = 0x1, - - /** - * if JackPortIsOutput is set, then data can be read from - * the port. - */ - JackPortIsOutput = 0x2, - - /** - * if JackPortIsPhysical is set, then the port corresponds - * to some kind of physical I/O connector. - */ - JackPortIsPhysical = 0x4, - - /** - * if JackPortCanMonitor is set, then a call to - * jack_port_request_monitor() makes sense. - * - * Precisely what this means is dependent on the client. A typical - * result of it being called with TRUE as the second argument is - * that data that would be available from an output port (with - * JackPortIsPhysical set) is sent to a physical output connector - * as well, so that it can be heard/seen/whatever. - * - * Clients that do not control physical interfaces - * should never create ports with this bit set. - */ - JackPortCanMonitor = 0x8, - - /** - * JackPortIsTerminal means: - * - * for an input port: the data received by the port - * will not be passed on or made - * available at any other port - * - * for an output port: the data available at the port - * does not originate from any other port - * - * Audio synthesizers, I/O hardware interface clients, HDR - * systems are examples of clients that would set this flag for - * their ports. - */ - JackPortIsTerminal = 0x10, - -}; - -/** - * Transport states. - */ -typedef enum { - - /* the order matters for binary compatibility */ - JackTransportStopped = 0, /**< Transport halted */ - JackTransportRolling = 1, /**< Transport playing */ - JackTransportLooping = 2, /**< For OLD_TRANSPORT, now ignored */ - JackTransportStarting = 3, /**< Waiting for sync ready */ - JackTransportNetStarting = 4, /**< Waiting for sync ready on the network*/ - -} jack_transport_state_t; - -typedef uint64_t jack_unique_t; /**< Unique ID (opaque) */ - -/** - * Optional struct jack_position_t fields. - */ -typedef enum { - - JackPositionBBT = 0x10, /**< Bar, Beat, Tick */ - JackPositionTimecode = 0x20, /**< External timecode */ - JackBBTFrameOffset = 0x40, /**< Frame offset of BBT information */ - JackAudioVideoRatio = 0x80, /**< audio frames per video frame */ - JackVideoFrameOffset = 0x100 /**< frame offset of first video frame */ - -} jack_position_bits_t; - -/** all valid position bits */ -#define JACK_POSITION_MASK (JackPositionBBT|JackPositionTimecode) -#define EXTENDED_TIME_INFO - -PRE_PACKED_STRUCTURE -struct _jack_position { - - /* these four cannot be set from clients: the server sets them */ - jack_unique_t unique_1; /**< unique ID */ - jack_time_t usecs; /**< monotonic, free-rolling */ - jack_nframes_t frame_rate; /**< current frame rate (per second) */ - jack_nframes_t frame; /**< frame number, always present */ - - jack_position_bits_t valid; /**< which other fields are valid */ - - /* JackPositionBBT fields: */ - int32_t bar; /**< current bar */ - int32_t beat; /**< current beat-within-bar */ - int32_t tick; /**< current tick-within-beat */ - double bar_start_tick; - - float beats_per_bar; /**< time signature "numerator" */ - float beat_type; /**< time signature "denominator" */ - double ticks_per_beat; - double beats_per_minute; - - /* JackPositionTimecode fields: (EXPERIMENTAL: could change) */ - double frame_time; /**< current time in seconds */ - double next_time; /**< next sequential frame_time - (unless repositioned) */ - - /* JackBBTFrameOffset fields: */ - jack_nframes_t bbt_offset; /**< frame offset for the BBT fields - (the given bar, beat, and tick - values actually refer to a time - frame_offset frames before the - start of the cycle), should - be assumed to be 0 if - JackBBTFrameOffset is not - set. If JackBBTFrameOffset is - set and this value is zero, the BBT - time refers to the first frame of this - cycle. If the value is positive, - the BBT time refers to a frame that - many frames before the start of the - cycle. */ - - /* JACK video positional data (experimental) */ - - float audio_frames_per_video_frame; /**< number of audio frames - per video frame. Should be assumed - zero if JackAudioVideoRatio is not - set. If JackAudioVideoRatio is set - and the value is zero, no video - data exists within the JACK graph */ - - jack_nframes_t video_offset; /**< audio frame at which the first video - frame in this cycle occurs. Should - be assumed to be 0 if JackVideoFrameOffset - is not set. If JackVideoFrameOffset is - set, but the value is zero, there is - no video frame within this cycle. */ - - /* For binary compatibility, new fields should be allocated from - * this padding area with new valid bits controlling access, so - * the existing structure size and offsets are preserved. */ - int32_t padding[7]; - - /* When (unique_1 == unique_2) the contents are consistent. */ - jack_unique_t unique_2; /**< unique ID */ - -} POST_PACKED_STRUCTURE; - -typedef struct _jack_position jack_position_t; - -/** - * Prototype for the @a sync_callback defined by slow-sync clients. - * When the client is active, this callback is invoked just before - * process() in the same thread. This occurs once after registration, - * then subsequently whenever some client requests a new position, or - * the transport enters the ::JackTransportStarting state. This - * realtime function must not wait. - * - * The transport @a state will be: - * - * - ::JackTransportStopped when a new position is requested; - * - ::JackTransportStarting when the transport is waiting to start; - * - ::JackTransportRolling when the timeout has expired, and the - * position is now a moving target. - * - * @param state current transport state. - * @param pos new transport position. - * @param arg the argument supplied by jack_set_sync_callback(). - * - * @return TRUE (non-zero) when ready to roll. - */ -typedef int (*JackSyncCallback)(jack_transport_state_t state, - jack_position_t *pos, - void *arg); - - -/** - * Prototype for the @a timebase_callback used to provide extended - * position information. Its output affects all of the following - * process cycle. This realtime function must not wait. - * - * This function is called immediately after process() in the same - * thread whenever the transport is rolling, or when any client has - * requested a new position in the previous cycle. The first cycle - * after jack_set_timebase_callback() is also treated as a new - * position, or the first cycle after jack_activate() if the client - * had been inactive. - * - * The timebase master may not use its @a pos argument to set @a - * pos->frame. To change position, use jack_transport_reposition() or - * jack_transport_locate(). These functions are realtime-safe, the @a - * timebase_callback can call them directly. - * - * @param state current transport state. - * @param nframes number of frames in current period. - * @param pos address of the position structure for the next cycle; @a - * pos->frame will be its frame number. If @a new_pos is FALSE, this - * structure contains extended position information from the current - * cycle. If TRUE, it contains whatever was set by the requester. - * The @a timebase_callback's task is to update the extended - * information here. - * @param new_pos TRUE (non-zero) for a newly requested @a pos, or for - * the first cycle after the @a timebase_callback is defined. - * @param arg the argument supplied by jack_set_timebase_callback(). - */ -typedef void (*JackTimebaseCallback)(jack_transport_state_t state, - jack_nframes_t nframes, - jack_position_t *pos, - int new_pos, - void *arg); - -/********************************************************************* - * The following interfaces are DEPRECATED. They are only provided - * for compatibility with the earlier JACK transport implementation. - *********************************************************************/ - -/** - * Optional struct jack_transport_info_t fields. - * - * @see jack_position_bits_t. - */ -typedef enum { - - JackTransportState = 0x1, /**< Transport state */ - JackTransportPosition = 0x2, /**< Frame number */ - JackTransportLoop = 0x4, /**< Loop boundaries (ignored) */ - JackTransportSMPTE = 0x8, /**< SMPTE (ignored) */ - JackTransportBBT = 0x10 /**< Bar, Beat, Tick */ - -} jack_transport_bits_t; - -/** - * Deprecated struct for transport position information. - * - * @deprecated This is for compatibility with the earlier transport - * interface. Use the jack_position_t struct, instead. - */ -typedef struct { - - /* these two cannot be set from clients: the server sets them */ - - jack_nframes_t frame_rate; /**< current frame rate (per second) */ - jack_time_t usecs; /**< monotonic, free-rolling */ - - jack_transport_bits_t valid; /**< which fields are legal to read */ - jack_transport_state_t transport_state; - jack_nframes_t frame; - jack_nframes_t loop_start; - jack_nframes_t loop_end; - - long smpte_offset; /**< SMPTE offset (from frame 0) */ - float smpte_frame_rate; /**< 29.97, 30, 24 etc. */ - - int bar; - int beat; - int tick; - double bar_start_tick; - - float beats_per_bar; - float beat_type; - double ticks_per_beat; - double beats_per_minute; - -} jack_transport_info_t; - - -#endif /* __jack_types_h__ */ diff --git a/include/jack/weakjack.h b/include/jack/weakjack.h deleted file mode 100644 index 4a5e7d30939..00000000000 --- a/include/jack/weakjack.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - Copyright (C) 2010 Paul Davis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __weakjack_h__ -#define __weakjack_h__ - -/** - * @defgroup WeakLinkage Managing support for newer/older versions of JACK - * @{ One challenge faced by developers is that of taking - * advantage of new features introduced in new versions - * of [ JACK ] while still supporting older versions of - * the system. Normally, if an application uses a new - * feature in a library/API, it is unable to run on - * earlier versions of the library/API that do not - * support that feature. Such applications would either - * fail to launch or crash when an attempt to use the - * feature was made. This problem cane be solved using - * weakly-linked symbols. - * - * When a symbol in a framework is defined as weakly - * linked, the symbol does not have to be present at - * runtime for a process to continue running. The static - * linker identifies a weakly linked symbol as such in - * any code module that references the symbol. The - * dynamic linker uses this same information at runtime - * to determine whether a process can continue - * running. If a weakly linked symbol is not present in - * the framework, the code module can continue to run as - * long as it does not reference the symbol. However, if - * the symbol is present, the code can use it normally. - * - * (adapted from: http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html) - * - * A concrete example will help. Suppose that someone uses a version - * of a JACK client we'll call "Jill". Jill was linked against a version - * of JACK that contains a newer part of the API (say, jack_set_latency_callback()) - * and would like to use it if it is available. - * - * When Jill is run on a system that has a suitably "new" version of - * JACK, this function will be available entirely normally. But if Jill - * is run on a system with an old version of JACK, the function isn't - * available. - * - * With normal symbol linkage, this would create a startup error whenever - * someone tries to run Jill with the "old" version of JACK. However, functions - * added to JACK after version 0.116.2 are all declared to have "weak" linkage - * which means that their abscence doesn't cause an error during program - * startup. Instead, Jill can test whether or not the symbol jack_set_latency_callback - * is null or not. If its null, it means that the JACK installed on this machine - * is too old to support this function. If its not null, then Jill can use it - * just like any other function in the API. For example: - * - * \code - * if (jack_set_latency_callback) { - * jack_set_latency_callback (jill_client, jill_latency_callback, arg); - * } - * \endcode - * - * However, there are clients that may want to use this approach to parts of the - * the JACK API that predate 0.116.2. For example, they might want to see if even - * really old basic parts of the API like jack_client_open() exist at runtime. - * - * Such clients should include before any other JACK header. - * This will make the \b entire JACK API be subject to weak linkage, so that any - * and all functions can be checked for existence at runtime. It is important - * to understand that very few clients need to do this - if you use this - * feature you should have a clear reason to do so. - * - * - */ - -#ifdef __APPLE__ -#define WEAK_ATTRIBUTE weak_import -#else -#define WEAK_ATTRIBUTE __weak__ -#endif - -#ifndef JACK_OPTIONAL_WEAK_EXPORT -/* JACK_OPTIONAL_WEAK_EXPORT needs to be a macro which - expands into a compiler directive. If non-null, the directive - must tell the compiler to arrange for weak linkage of - the symbol it used with. For this to work fully may - require linker arguments for the client as well. -*/ -#ifdef __GNUC__ -#define JACK_OPTIONAL_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) -#else -/* Add other things here for non-gcc platforms */ -#endif -#endif - -#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT -/* JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT needs to be a macro - which expands into a compiler directive. If non-null, the directive - must tell the compiler to arrange for weak linkage of the - symbol it is used with AND optionally to mark the symbol - as deprecated. For this to work fully may require - linker arguments for the client as well. -*/ -#ifdef __GNUC__ -#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((WEAK_ATTRIBUTE,__deprecated__)) -#else -/* Add other things here for non-gcc platforms */ -#endif -#endif - -/*@}*/ - -#endif /* weakjack */ diff --git a/include/jack/weakmacros.h b/include/jack/weakmacros.h deleted file mode 100644 index e736a1ee28f..00000000000 --- a/include/jack/weakmacros.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - Copyright (C) 2010 Paul Davis - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -*/ - -#ifndef __weakmacros_h__ -#define __weakmacros_h__ - -/************************************************************* - * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function - * added to the JACK API after the 0.116.2 release. - * - * Functions that predate this release are marked with - * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile - * time in a variety of ways. The default definition is empty, - * so that these symbols get normal linkage. If you wish to - * use all JACK symbols with weak linkage, include - * before jack.h. - *************************************************************/ - -#ifdef __APPLE__ -#define WEAK_ATTRIBUTE weak_import -#else -#define WEAK_ATTRIBUTE __weak__ -#endif - -#ifndef JACK_WEAK_EXPORT -#ifdef __GNUC__ -/* JACK_WEAK_EXPORT needs to be a macro which - expands into a compiler directive. If non-null, the directive - must tell the compiler to arrange for weak linkage of - the symbol it used with. For this to work full may - require linker arguments in the client as well. -*/ - -#ifdef _WIN32 - /* - Not working with __declspec(dllexport) so normal linking - Linking with JackWeakAPI.cpp will be the preferred way. - */ - #define JACK_WEAK_EXPORT -#else - #define JACK_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) -#endif - -#else -/* Add other things here for non-gcc platforms */ - -#ifdef _WIN32 -#define JACK_WEAK_EXPORT -#endif - -#endif -#endif - -#ifndef JACK_WEAK_EXPORT -#define JACK_WEAK_EXPORT -#endif - -#ifndef JACK_OPTIONAL_WEAK_EXPORT -#define JACK_OPTIONAL_WEAK_EXPORT -#endif - -#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT -#ifdef __GNUC__ -#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((__deprecated__)) -#else -/* Add other things here for non-gcc platforms */ - -#ifdef _WIN32 -#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT -#endif - -#endif /* __GNUC__ */ - -#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT -#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT -#endif - -#endif - -#endif /* __weakmacros_h__ */ - diff --git a/src/3rdparty/weakjack/CMakeLists.txt b/src/3rdparty/weakjack/CMakeLists.txt index 7600c3915ed..c0146f12fc2 100644 --- a/src/3rdparty/weakjack/CMakeLists.txt +++ b/src/3rdparty/weakjack/CMakeLists.txt @@ -1,5 +1,5 @@ # Use weak jack library linking -IF(LMMS_HAVE_WEAKJACK) +IF(LMMS_HAVE_WEAKJACK AND LMMS_BUILD_LINUX) SET(CMAKE_C_FLAGS "-std=c11") # Enable weakjack, disable metadata support From 46d22335f451a6c039ae19587ff0edf3ff7efe40 Mon Sep 17 00:00:00 2001 From: Lukas W Date: Sun, 5 Aug 2018 21:10:39 +0200 Subject: [PATCH 076/112] CMake: Fix finding jack when cross-compiling with non-standard CMake Thanks to @-PhysSong. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99bacc86584..4f90d3d760f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -386,7 +386,7 @@ IF(WANT_JACK) # We only need Jack's header files, so finding the host's headers in the case # of cross-compiling is fine. - find_path(JACK_INCLUDE_DIR NAMES jack/jack.h CMAKE_FIND_ROOT_PATH_BOTH) + find_path(JACK_INCLUDE_DIR NAMES jack/jack.h PATHS /usr/include CMAKE_FIND_ROOT_PATH_BOTH) IF(JACK_INCLUDE_DIR) SET(STATUS_JACK "OK (weak linking enabled)") # use dlsym instead @@ -412,6 +412,7 @@ IF(WANT_JACK) ENDIF() ELSE() SET(STATUS_JACK "no jack headers found, please install libjack0.100.0-dev (or similar) ") + SET(LMMS_HAVE_JACK) ENDIF() ELSE() PKG_CHECK_MODULES(JACK jack>=0.77) From 67d5c77bd827ec8986698e607d5e0fbb9ff445ad Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Sat, 4 Aug 2018 16:46:58 +0900 Subject: [PATCH 077/112] Fix PatMan patch loading Fixes a regression in 84a1f359d7dfa58bd3432f6c36237fce93484008. --- plugins/patman/patman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index db4c3a6231a..d5ce89688b4 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -378,7 +378,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( void patmanInstrument::unloadCurrentPatch( void ) { - m_patchFile.clear (); + m_patchSamples.clear(); } From b6b61ebce8569819bca7625fd35189ce2fc5d983 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Sat, 4 Aug 2018 17:16:13 +0900 Subject: [PATCH 078/112] Fix setting sample buffer name Fixes a regression in 7bc00564b2802c795f56dbeccb4ffa8e53ee3805 that breaks showing file names. --- src/core/SampleBuffer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 57ab214a458..a795111c87e 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -81,7 +81,7 @@ SampleBuffer::SampleBuffer( const QString & _audio_file, } else { - changeAudioFile (_audio_file); + setAudioFile (_audio_file); } } @@ -1021,6 +1021,7 @@ QString & SampleBuffer::toBase64( QString & _dst ) const void SampleBuffer::setAudioFile( const QString & _audio_file ) { + m_audioFile = tryToMakeRelative(_audio_file); changeAudioFile (_audio_file); } From 7d8d13087036371baa6b13d978516c701808ece9 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Sun, 5 Aug 2018 15:39:03 +0900 Subject: [PATCH 079/112] Use CMake's file command to download JACK installer --- src/CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bca7e696bf2..72ec2f4499d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -398,9 +398,13 @@ IF(LMMS_BUILD_WIN32) SET(JACK_INSTALLER_VERSION "1.9.11") # Bundle jack IF (WANT_JACK) - ADD_CUSTOM_TARGET(jack_binary_installer ALL - COMMAND "curl" -OL "https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + INSTALL(CODE + "FILE(DOWNLOAD + \"https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/\ + Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" + \"${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" + )" + ) INSTALL(FILES "${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" DESTINATION .) SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " ExecWait '\\\"$INSTDIR\\\\Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\\\" /install' From 21b956966ebce727cfe359d34e87c5fdf6216939 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Sun, 5 Aug 2018 15:41:52 +0900 Subject: [PATCH 080/112] MmAllocator: add missing constructors --- include/MemoryManager.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index e4c5fa3efe4..3fdf5012ca8 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -46,8 +46,11 @@ class LMMS_EXPORT MemoryManager }; template -struct MmAllocator +class MmAllocator { +public: + MmAllocator() = default; + template MmAllocator(const MmAllocator& other) {} typedef T value_type; template struct rebind { typedef MmAllocator other; }; From 8200efb8a881dd9b51debb55848877d16e244310 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Sun, 5 Aug 2018 17:22:59 +0900 Subject: [PATCH 081/112] Improve JACK finding logic * Enable WeakJACK only if JACK is available. * Try `pkg_check_modules` first when using WeakJACK, too. --- CMakeLists.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f90d3d760f..396ce83fed0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -380,18 +380,27 @@ ENDIF(NOT LMMS_HAVE_ALSA) # check for JACK IF(WANT_JACK) + PKG_CHECK_MODULES(JACK jack>=0.77) + IF(JACK_FOUND) + SET(STATUS_JACK "OK") + SET(LMMS_HAVE_JACK TRUE) + ELSE() + SET(JACK_INCLUDE_DIRS "") + SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similar) " + "if you require JACK support or enable weak jack") + ENDIF() IF(WANT_WEAKJACK) - SET(LMMS_HAVE_WEAKJACK TRUE) SET(WEAKJACK_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/3rdparty/weakjack/weakjack) # We only need Jack's header files, so finding the host's headers in the case # of cross-compiling is fine. - find_path(JACK_INCLUDE_DIR NAMES jack/jack.h PATHS /usr/include CMAKE_FIND_ROOT_PATH_BOTH) + find_path(JACK_INCLUDE_DIR NAMES jack/jack.h PATHS /usr/include ${JACK_INCLUDE_DIRS} CMAKE_FIND_ROOT_PATH_BOTH) IF(JACK_INCLUDE_DIR) SET(STATUS_JACK "OK (weak linking enabled)") # use dlsym instead SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) SET(LMMS_HAVE_JACK TRUE) + SET(LMMS_HAVE_WEAKJACK TRUE) IF(NOT CMAKE_CROSSCOMPILING) SET(JACK_INCLUDE_DIRS "${JACK_INCLUDE_DIR}") @@ -408,22 +417,13 @@ IF(WANT_JACK) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -idirafter ${JACK_INCLUDE_DIR}") ELSE() SET(STATUS_JACK "Host's jack headers were found, but you're using an unknown cross-compiler. Try to use MinGW or supply a jack library for the target architecture at ${CMAKE_FIND_ROOT_PATHS}") - SET(LMMS_HAVE_JACK) + SET(LMMS_HAVE_JACK FALSE) + SET(LMMS_HAVE_WEAKJACK FALSE) ENDIF() ELSE() SET(STATUS_JACK "no jack headers found, please install libjack0.100.0-dev (or similar) ") SET(LMMS_HAVE_JACK) ENDIF() - ELSE() - PKG_CHECK_MODULES(JACK jack>=0.77) - IF(JACK_FOUND) - SET(STATUS_JACK "OK") - SET(LMMS_HAVE_JACK TRUE) - ELSE(JACK_FOUND) - SET(JACK_INCLUDE_DIRS "") - SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similar) " - "if you require JACK support or enable weak jack") - ENDIF(JACK_FOUND) ENDIF() ENDIF(WANT_JACK) From dd04ef72e8dcf9ddc48c02506ffdeda9470e5b1f Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Mon, 6 Aug 2018 09:45:06 +0900 Subject: [PATCH 082/112] Don't try to resample empty data --- src/core/SampleBuffer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index a795111c87e..fc2f8d0fd75 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -279,6 +279,11 @@ SampleBuffer::DataVector SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t requiredSampleRate) { + if (inputData.empty()) + { + // no need to resample empty data + return SampleBuffer::DataVector{}; + } const f_cnt_t dst_frames = static_cast( inputData.size ()/ (float) inputSampleRate * (float) requiredSampleRate ); DataVector outputData(dst_frames); From 60cf294da4cabef2cd06f00ed14ecd1fd0c99c4d Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Mon, 6 Aug 2018 10:19:22 +0900 Subject: [PATCH 083/112] Fix remaining file name reset issues --- include/SampleBuffer.h | 5 +++++ src/core/SampleBuffer.cpp | 2 +- src/core/SampleRecordHandle.cpp | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 40449ae03e0..0e58aa84af1 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -134,6 +134,11 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject QPair visualizeToPoly( const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0) const; + inline void resetAudioFile() + { + m_audioFile = ""; + } + inline const QString & audioFile() const { return m_audioFile; diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index fc2f8d0fd75..b94df22f406 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -148,7 +148,6 @@ void SampleBuffer::internalAddData(const DataVector &vector, sample_rate_t sampl void SampleBuffer::internalResetData(DataVector &&newData, sample_rate_t dataSampleRate) { Q_UNUSED(dataSampleRate); - m_audioFile = QString(); m_data = std::move (newData); } @@ -1045,6 +1044,7 @@ void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleR delete[] dst; + resetAudioFile(); resetData (std::move(input), sampleRate); } diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 232ef87f43d..e11f735a224 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -54,6 +54,7 @@ SampleRecordHandle::~SampleRecordHandle() // force-write it into the buffer. if (m_framesRecorded == 0) { + m_tco->sampleBuffer ()->resetAudioFile(); m_tco->sampleBuffer ()->resetData (std::move (m_currentBuffer), Engine::mixer ()->inputSampleRate (), false); @@ -93,6 +94,7 @@ void SampleRecordHandle::play( sampleFrame * /*_working_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 ()->resetAudioFile(); dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryResetData (std::move (m_currentBuffer), Engine::mixer ()->inputSampleRate (), false); From e98b1332c52d05437fa07b5d1f7321c8a2292cfa Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Tue, 14 Aug 2018 16:19:55 +0900 Subject: [PATCH 084/112] Fix JACK installer download Fixes a regression in 063b7c9186e3f501e25aefa1dab7b18c3aa9bffb. --- src/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 72ec2f4499d..3885ce96e95 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -400,8 +400,7 @@ IF(LMMS_BUILD_WIN32) IF (WANT_JACK) INSTALL(CODE "FILE(DOWNLOAD - \"https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/\ - Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" + \"https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" \"${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" )" ) From 2175eac0d7e26cdd62eabec79e0d23a1977e590e Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Tue, 14 Aug 2018 16:37:11 +0900 Subject: [PATCH 085/112] Improve JACK installation on Windows * Bundle 64-bit version of JACK for 64-bit installer. * Change some CMake logic for NSIS --- cmake/nsis/CMakeLists.txt | 24 ++++++++++++++++++++++-- src/CMakeLists.txt | 16 ---------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index 5a964d0c708..0b3f250f774 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -21,8 +21,7 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " \\\${registerExtension} \\\"$INSTDIR\\\\${CMAKE_PROJECT_NAME}.exe\\\" \\\".mmpz\\\" \\\"${PROJECT_NAME_UCASE} Project (compressed)\\\" \\\${IfNot} \\\${AtMostWin7} WriteRegDWORD HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\SideBySide\\\" \\\"PreferExternalManifest\\\" \\\"1\\\" - \\\${EndIf} - ExecWait '\\\"$INSTDIR\\\\Jack_v1.9.11_32_setup.exe\\\" /install'" PARENT_SCOPE) + \\\${EndIf}") SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " \\\${unregisterExtension} \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" \\\${unregisterExtension} \\\".mmpz\\\" \\\"${PROJECT_NAME_UCASE} Project (compressed)\\\" @@ -74,6 +73,27 @@ IF(LMMS_HAVE_STK) INSTALL(FILES ${RAWWAVES} DESTINATION "${DATA_DIR}/stk/rawwaves") ENDIF() +# Bundle jack +IF(LMMS_BUILD_WIN32 AND WANT_JACK) + SET(JACK_INSTALLER_TAG "1.9.11") + SET(JACK_INSTALLER_FILENAME "Jack_v1.9.11_32_setup.exe") + IF(LMMS_BUILD_WIN64) + SET(JACK_INSTALLER_FILENAME "Jack_v1.9.11_64_setup.exe") + ENDIF() + + INSTALL(CODE + "FILE(DOWNLOAD + \"https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_TAG}/${JACK_INSTALLER_FILENAME}\" + \"${CMAKE_BINARY_DIR}/${JACK_INSTALLER_FILENAME}\" + )" + ) + INSTALL(FILES "${CMAKE_BINARY_DIR}/${JACK_INSTALLER_FILENAME}" DESTINATION .) + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS} + ExecWait '\\\"$INSTDIR\\\\${JACK_INSTALLER_FILENAME}\\\" /install'") +ENDIF() + +SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}" PARENT_SCOPE) + INSTALL(FILES "lmms.exe.manifest" DESTINATION .) INSTALL(FILES "lmms.VisualElementsManifest.xml" DESTINATION .) INSTALL(DIRECTORY "assets" DESTINATION .) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3885ce96e95..a434ee37ec3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -394,19 +394,3 @@ ELSE(NOT MSVC) #ENDIF() ENDIF(NOT MSVC) -IF(LMMS_BUILD_WIN32) - SET(JACK_INSTALLER_VERSION "1.9.11") - # Bundle jack - IF (WANT_JACK) - INSTALL(CODE - "FILE(DOWNLOAD - \"https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" - \"${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\" - )" - ) - INSTALL(FILES "${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" DESTINATION .) - SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " - ExecWait '\\\"$INSTDIR\\\\Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\\\" /install' -") - ENDIF() -ENDIF() From 6e76182be68bc1d29c7aaa5bfa6c178fd51595cf Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 12 Aug 2018 10:30:56 +0300 Subject: [PATCH 086/112] SampleBuffer: Don't treat an empty file as an error --- include/SampleBuffer.h | 11 ++++++----- src/core/SampleBuffer.cpp | 37 ++++++++++++++++++++++++------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 0e58aa84af1..e1be88685ba 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -337,13 +337,14 @@ public slots: static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); - static DataVector decodeSampleSF(QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate, QString &loadingWarning); + static DataVector decodeSampleSF(QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate, QString &loadingWarning, bool &isError); #ifdef LMMS_HAVE_OGGVORBIS - static DataVector decodeSampleOGGVorbis( QString _f, - ch_cnt_t & _channels, - sample_rate_t & _sample_rate); + static DataVector decodeSampleOGGVorbis(QString _f, + ch_cnt_t & _channels, + sample_rate_t & _sample_rate, + bool &isError); #endif - static DataVector decodeSampleDS( QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); + static DataVector decodeSampleDS(QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); inline sampleFrame * data() { diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index b94df22f406..f8bc38a9bfa 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -207,29 +208,29 @@ void SampleBuffer::changeAudioFile(QString audioFile) // decoder first if filename extension matches "ogg" if( fileInfo.suffix() == "ogg" ) { - fileData = decodeSampleOGGVorbis( file, channels, samplerate ); + fileLoadError = false; + fileData = decodeSampleOGGVorbis( file, channels, samplerate, fileLoadError); } #endif - if(fileData.empty ()) + if( fileInfo.suffix() != "ogg" || fileLoadError) { - fileData = decodeSampleSF( file, channels, samplerate, loadingWarning ); + fileLoadError = false; + fileData = decodeSampleSF( file, channels, samplerate, loadingWarning, fileLoadError); } #ifdef LMMS_HAVE_OGGVORBIS - if( fileData.empty () ) + if( fileLoadError ) { - fileData = decodeSampleOGGVorbis( file, channels, samplerate ); + fileLoadError = false; + fileData = decodeSampleOGGVorbis( file, channels, samplerate, fileLoadError); } #endif - if( fileData.empty () ) + if( fileLoadError ) { - fileData = decodeSampleDS( file, channels, samplerate ); + fileLoadError = false; + fileData = decodeSampleDS( file, channels, samplerate); } } - if (fileData.empty ()) { - fileLoadError = true; - } - if (! fileLoadError) { resetData (std::move(fileData), samplerate); } else { @@ -319,7 +320,8 @@ SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSamp SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, ch_cnt_t & _channels, sample_rate_t &_samplerate, - QString &loadingWarning) + QString &loadingWarning, + bool &isError) { SNDFILE * snd_file; SF_INFO sf_info; @@ -372,6 +374,7 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, "sample %s: %s", _f, sf_strerror( NULL ) ); #endif loadingWarning = tr("SoundFile: Could not load: %1").arg(sf_strerror( NULL )); + isError = true; } f.close(); @@ -435,7 +438,8 @@ long qfileTellCallback( void * _udata ) SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(QString _f, ch_cnt_t & _channels, - sample_rate_t & _samplerate) + sample_rate_t & _samplerate, + bool &isError) { static ov_callbacks callbacks = { @@ -453,6 +457,7 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(QString _f, if( f->open( QFile::ReadOnly ) == false ) { delete f; + isError = true; return {}; } @@ -484,6 +489,8 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(QString _f, break; } delete f; + + isError = true; return {}; } @@ -520,6 +527,10 @@ SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(QString _f, { return convertIntToFloat ( _buf, frames, _channels); } + else if (frames < 0) + { + isError = true; + } return {}; } From ca9be24bdb11d4a84542cd8e9926e1f3d912d158 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 13 Aug 2018 22:24:20 +0300 Subject: [PATCH 087/112] Bugfix -> SampleBuffer: To avoid arithmetic exception, userWaveSample should return with empty buffer. --- include/SampleBuffer.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index e1be88685ba..9b0a5c3064c 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -229,6 +229,9 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject inline sample_t userWaveSample( const float _sample ) const { f_cnt_t dataFrames = internalFrames(); + if (dataFrames == 0) + return 0; + const sampleFrame * data = this->data(); const float frame = _sample * dataFrames; f_cnt_t f1 = static_cast( frame ) % dataFrames; From 1c9b53a2e1e4e713be4a1b630a3ddf0389b5aaca Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 13 Aug 2018 13:22:47 -0700 Subject: [PATCH 088/112] Bugfix -> SampleBuffer: Don't unlock the mixer when it has not been locked. --- src/core/SampleBuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index f8bc38a9bfa..d1b0c47203b 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -1184,7 +1184,7 @@ bool SampleBuffer::tryBeginBufferChange(bool shouldLock, bool shouldLockMixer) { if (shouldLock) { result = m_varLock.tryLockForWrite(); - if (! result) + if (!result && shouldLockMixer) Engine::mixer ()->doneChangeInModel(); } From 6ee074d79f6f29f28fbf0ddab405a4155c50e3cf Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Mon, 20 Aug 2018 00:12:05 +0300 Subject: [PATCH 089/112] SampleBuffer: Disable automatic up-sampling to the mixer's sample rate. Since it may cause quality loss, we have decided to remove it and just use on-the-fly resampling while playing. --- include/SampleBuffer.h | 1 - src/core/SampleBuffer.cpp | 19 ------------------- 2 files changed, 20 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 9b0a5c3064c..566bc9655c6 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -312,7 +312,6 @@ public slots: void setStartFrame( const f_cnt_t _s ); void setEndFrame( const f_cnt_t _e ); void setAmplification( float _a ); - void sampleRateChanged(); protected: void internalAddData(const DataVector &vector, sample_rate_t sampleRate); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index d1b0c47203b..6645ade5885 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -63,8 +63,6 @@ SampleBuffer::SampleBuffer() : m_frequency( BaseFreq ), m_sampleRate( mixerSampleRate () ) { - - connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); beginBufferChange (false); doneBufferChange (false, /* shouldLock */ m_sampleRate); @@ -124,23 +122,6 @@ void SampleBuffer::loadSettings(const QDomElement &_this) { } } -void SampleBuffer::sampleRateChanged() { - auto previousSampleRate = sampleRate (); - auto requiredSampleRate = mixerSampleRate (); - - if (requiredSampleRate == sampleRate ()) - return; - - // Only resample the buffer if the processing - // sample rate is higher than the SampleBuffer's - // sample rate. - if (requiredSampleRate > sampleRate ()) { - resetData (resampleData (m_data, previousSampleRate, requiredSampleRate), - requiredSampleRate, - true); - } -} - void SampleBuffer::internalAddData(const DataVector &vector, sample_rate_t sampleRate) { Q_ASSERT(sampleRate == m_sampleRate); From 87239ac062073e98f5c2e958f887ade7c2f0c957 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Thu, 20 Sep 2018 15:20:23 +0900 Subject: [PATCH 090/112] Fix TripleOscillator preset previewing due to nonexistent waveform files --- include/SampleBuffer.h | 4 ++-- plugins/triple_oscillator/TripleOscillator.cpp | 2 +- src/core/SampleBuffer.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 566bc9655c6..b7aca17a953 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -308,7 +308,7 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject public slots: - void setAudioFile( const QString & _audio_file ); + void setAudioFile(const QString & audioFile , bool ignoreError = false); void setStartFrame( const f_cnt_t _s ); void setEndFrame( const f_cnt_t _e ); void setAmplification( float _a ); @@ -335,7 +335,7 @@ public slots: return const_cast(ptr); } - void changeAudioFile (QString audioFile); + void changeAudioFile (QString audioFile, bool ignoreError); static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index ec929959594..53c3d06ff97 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -280,7 +280,7 @@ void TripleOscillator::loadSettings( const QDomElement & _this ) m_osc[i]->m_modulationAlgoModel.loadSettings( _this, "modalgo" + QString::number( i+1 ) ); m_osc[i]->m_sampleBuffer->setAudioFile( _this.attribute( - "userwavefile" + is ) ); + "userwavefile" + is ), true ); } } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 6645ade5885..4d51c59ab98 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -138,7 +138,7 @@ sample_rate_t SampleBuffer::mixerSampleRate() return Engine::mixer ()->processingSampleRate (); } -void SampleBuffer::changeAudioFile(QString audioFile) +void SampleBuffer::changeAudioFile(QString audioFile, bool ignoreError) { if (audioFile == "") return; @@ -214,7 +214,7 @@ void SampleBuffer::changeAudioFile(QString audioFile) if (! fileLoadError) { resetData (std::move(fileData), samplerate); - } else { + } else if (!ignoreError) { 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" @@ -1015,10 +1015,10 @@ QString & SampleBuffer::toBase64( QString & _dst ) const } -void SampleBuffer::setAudioFile( const QString & _audio_file ) +void SampleBuffer::setAudioFile(const QString & audioFile, bool ignoreError ) { - m_audioFile = tryToMakeRelative(_audio_file); - changeAudioFile (_audio_file); + m_audioFile = tryToMakeRelative(audioFile); + changeAudioFile (audioFile, ignoreError); } From 283625c2de71eb03d96b5ab3b3cb1294d888e564 Mon Sep 17 00:00:00 2001 From: Hyunjin Song Date: Thu, 22 Nov 2018 13:51:15 +0900 Subject: [PATCH 091/112] SampleBuffer: use the RAII pattern when changing its data Also renames misleading `shouldLockMixer` to `syncWithMixer`. --- include/SampleBuffer.h | 50 +++++++------ src/core/SampleBuffer.cpp | 153 ++++++++++++++++---------------------- 2 files changed, 90 insertions(+), 113 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index b7aca17a953..7d9c6459cca 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -265,44 +265,44 @@ class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject * @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? + * @param syncWithMixer Should we call requestChangeInModel? * @return */ - bool tryAddData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true); + bool tryAddData(const DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer=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? + * @param syncWithMixer Should we call requestChangeInModel? * @return */ - void addData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true); + void addData(const DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer=true); /** * @brief Reset the class and initialize it with @a newData. * @param newData mm, that's the new data. * @param dataSampleRate Sample rate for @a newData. - * @param shouldLockMixer Should we call requestChangeInModel? + * @param syncWithMixer Should we call requestChangeInModel? */ - void resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true); + void resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer=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? + * @param syncWithMixer Should we call requestChangeInModel? * @return */ - bool tryResetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true); + bool tryResetData(DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer=true); /** * @brief Just reverse the current buffer. - * @param shouldLockMixer Should we call requestChangeInModel? + * @param syncWithMixer Should we call requestChangeInModel? * * This function simply calls `std::reverse` on m_data. */ - void reverse(bool shouldLockMixer=true); + void reverse(bool syncWithMixer=true); void loadFromBase64(const QString & _data , sample_rate_t sampleRate); @@ -383,22 +383,24 @@ public slots: static DataVector resampleData(const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t requiredSampleRate); /** - * @brief Do the actions necessary before changing m_data. - * @param shouldLock Is anyone else might be using m_data? + * @brief A helper class used when changes to m_data is needed */ - void beginBufferChange (bool shouldLock, bool shouldLockMixer=true); - - bool tryBeginBufferChange(bool shouldLock, bool shouldLockMixer=true); + class DataChangeHelper + { + public: + enum LockType {NoLock, TryLock, LockNow}; + DataChangeHelper(SampleBuffer* buffer, sample_rate_t desiredSampleRate, LockType lockType, bool syncWithMixer = true); + bool isSuccessful() {return m_successful;} + void finish(); + ~DataChangeHelper() {finish();} + private: + SampleBuffer* m_buffer; + sample_rate_t m_desiredSampleRate; + bool m_successful; + bool m_bufferLocked; + bool m_syncedWithMixer; + }; - /** - * @brief Do some actions necessary after changing m_data. - * @param shouldUnlock The same value you've used on @a beginBufferChange. - * @param shouldKeepSettings Should we keep playback settings? - * @param bufferSampleRate The new m_data's sample rate. - */ - void doneBufferChange (bool shouldUnlock, - sample_rate_t bufferSampleRate, - bool shouldUnlockMixer=true); signals: void sampleUpdated(); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 4d51c59ab98..4dd7f125ae3 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -63,9 +63,7 @@ SampleBuffer::SampleBuffer() : m_frequency( BaseFreq ), m_sampleRate( mixerSampleRate () ) { - beginBufferChange (false); - doneBufferChange (false, /* shouldLock */ - m_sampleRate); + m_loopEndFrame = m_endFrame = internalFrames(); } @@ -1144,54 +1142,50 @@ SampleBuffer::handleState::~handleState() src_delete( m_resamplingData ); } -void SampleBuffer::beginBufferChange(bool shouldLock, bool shouldLockMixer) +SampleBuffer::DataChangeHelper::DataChangeHelper(SampleBuffer* buffer, + sample_rate_t desiredSampleRate, + LockType lockType, + bool syncWithMixer) : + m_buffer(buffer), + m_desiredSampleRate(desiredSampleRate), + m_successful(true), + m_bufferLocked(false), + m_syncedWithMixer(false) { - if (shouldLockMixer) { - Engine::mixer ()->requestChangeInModel (); - } - - if (shouldLock) { - m_varLock.lockForWrite (); - } -} - -bool SampleBuffer::tryBeginBufferChange(bool shouldLock, bool shouldLockMixer) { - bool result = true; - - if (shouldLockMixer) { - Engine::mixer ()->requestChangeInModel (); - } - - if (shouldLock) { - result = m_varLock.tryLockForWrite(); - - if (!result && shouldLockMixer) - Engine::mixer ()->doneChangeInModel(); + switch (lockType) + { + case LockNow: + m_buffer->m_varLock.lockForWrite(); + m_bufferLocked = true; + break; + case TryLock: + m_successful = m_bufferLocked = m_buffer->m_varLock.tryLockForWrite(); + break; + case NoLock: + break; + } + if (m_successful && syncWithMixer) + { + Engine::mixer()->requestChangeInModel(); + m_syncedWithMixer = true; } - - return result; } -void SampleBuffer::doneBufferChange(bool shouldUnlock, - sample_rate_t bufferSampleRate, - bool shouldUnlockMixer) { +void SampleBuffer::DataChangeHelper::finish() +{ + if (!m_successful) return; + m_buffer->setSampleRate(m_desiredSampleRate); - setSampleRate (bufferSampleRate); + m_buffer->m_loopStartFrame = m_buffer->m_startFrame = 0; + m_buffer->m_loopEndFrame = m_buffer->m_endFrame = m_buffer->internalFrames(); + if (m_bufferLocked) {m_buffer->m_varLock.unlock();} - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = internalFrames(); - if (shouldUnlock) { - m_varLock.unlock (); - } - - if (shouldUnlockMixer) { - Engine::mixer ()->doneChangeInModel (); - } + if (m_syncedWithMixer) {Engine::mixer()->doneChangeInModel();} - emit sampleUpdated(); + emit m_buffer->sampleUpdated(); } -bool SampleBuffer::tryAddData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer) { +bool SampleBuffer::tryAddData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer) { DataVector newVector; if (sampleRate != m_sampleRate) { @@ -1202,24 +1196,22 @@ bool SampleBuffer::tryAddData(const SampleBuffer::DataVector &vector, sample_rat } // First of all, don't let anyone read. - if (! tryBeginBufferChange (true, shouldLockMixer)) - return false; + DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::TryLock, syncWithMixer); + if (!helper.isSuccessful()) return false; + + if (newVector.empty()) { - if (newVector.empty()) - internalAddData(vector, - sampleRate); - else - internalAddData(newVector, - sampleRate); + 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) { +void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer) { DataVector newVector; if (sampleRate != m_sampleRate) { @@ -1230,49 +1222,32 @@ void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t } // First of all, don't let anyone read. - beginBufferChange (true, shouldLockMixer); + DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::LockNow, syncWithMixer); + + if (newVector.empty()) { - if (newVector.empty()) - internalAddData(vector, - sampleRate); - else - internalAddData(newVector, - sampleRate); + internalAddData(vector, sampleRate); } - doneBufferChange (true, /* lock */ - this->sampleRate(), - shouldLockMixer); -} - -void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer) { - beginBufferChange (true, shouldLockMixer); + else { - internalResetData(std::move(newData), dataSampleRate); + internalAddData(newVector, sampleRate); } - 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); +void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer) { + DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::LockNow, syncWithMixer); + internalResetData(std::move(newData), dataSampleRate); +} + +bool SampleBuffer::tryResetData(SampleBuffer::DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer) { + DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::TryLock, syncWithMixer); + if (!helper.isSuccessful()) return false; + internalResetData(std::move(newData), dataSampleRate); return true; } -void SampleBuffer::reverse(bool shouldLockMixer) { - beginBufferChange (true, shouldLockMixer); - { - std::reverse(m_data.begin (), m_data.end ()); - } - doneBufferChange (true, /* should(Un)Lock? yes! */ - sampleRate (), /* we have not made any change in the sample rate. */ - shouldLockMixer); +void SampleBuffer::reverse(bool syncWithMixer) { + DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::LockNow, syncWithMixer); + std::reverse(m_data.begin(), m_data.end()); } From 3a47ded84671626442b9b018e1f5c8b70e9bdbc2 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Tue, 7 May 2019 08:28:03 +0300 Subject: [PATCH 092/112] LmmsBasics: Move LoopMode here from SampleBuffer. --- include/lmms_basics.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/lmms_basics.h b/include/lmms_basics.h index cfaddfb2738..c06b30586c2 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -149,6 +149,12 @@ static_assert (sizeof(surroundSampleFrame) == sizeof(sample_t) * SURROUND_CHANNE #define STRINGIFY(s) STR(s) #define STR(PN) #PN +enum LoopMode { + LoopOff = 0, + LoopOn, + LoopPingPong +}; + // Abstract away GUI CTRL key (linux/windows) vs ⌘ (apple) #ifdef LMMS_BUILD_APPLE # define UI_CTRL_KEY "⌘" From e206254845705740a7ca76c8335f3ab32183abfa Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Tue, 7 May 2019 08:34:11 +0300 Subject: [PATCH 093/112] Mixer: Introduce `RequestChangesGuard`: a RAII verion of requestChangeInModel. Deprecate direct calls to requestChangeInModel. --- include/Mixer.h | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/include/Mixer.h b/include/Mixer.h index fab7f442c16..cb32f14fdc6 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -69,7 +69,47 @@ class MixerWorkerThread; class LMMS_EXPORT Mixer : public QObject { Q_OBJECT + public: + /** + * @brief RAII helper for requestChangesInModel. + * Used by Mixer::requestChangesGuard. + */ + class RequestChangesGuard { + friend class Mixer; + + private: + RequestChangesGuard(Mixer *mixer) + : m_mixer{mixer} + { + m_mixer->requestChangeInModel(); + } + public: + + RequestChangesGuard() + : m_mixer{nullptr} + { + } + + RequestChangesGuard(RequestChangesGuard &&other) + : RequestChangesGuard() + { + std::swap(other.m_mixer, m_mixer); + } + + // Disallow copy. + RequestChangesGuard(const RequestChangesGuard&) = delete; + RequestChangesGuard& operator =(const RequestChangesGuard&) = delete; + + ~RequestChangesGuard() { + if (m_mixer) + m_mixer->doneChangeInModel(); + } + + private: + Mixer *m_mixer; + }; + struct qualitySettings { enum Mode @@ -315,6 +355,20 @@ class LMMS_EXPORT Mixer : public QObject void requestChangeInModel(); void doneChangeInModel(); + RequestChangesGuard requestChangesGuard() + { + return RequestChangesGuard{this}; + } + + + /** + * Sets the current thread as a rendering thread, + * it means that call to requestChangeInModel will + * be ignored in order to avoid a dead lock. + */ + static void setCurrentThreadAsRendering(); + + static bool isAudioDevNameValid(QString name); static bool isMidiDevNameValid(QString name); From 5cfc4a34a8241a9698d1fad527c1386a2c78d3aa Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Tue, 7 May 2019 08:46:53 +0300 Subject: [PATCH 094/112] SampleBuffer: Seperate the class into 3 internal classes. - SampleBufferData: Responsible for audio data. - SampleBufferFileHelper: Responsible for loading audio files. - SampleBufferPlayInfo: Responsible for holding information such as startFrame, etc. This commit also removes `m_varLock` and guards all getters and setters with RequestChangesGuard. It will be replaced in the next commit with: - Setters: The same but async. - Getters: signal on change. --- include/SampleBuffer.h | 526 ++++--- include/internal/SampleBufferData.h | 108 ++ include/internal/SampleBufferFileHelper.h | 27 + include/internal/SampleBufferPlayInfo.h | 71 + .../audio_file_processor.cpp | 4 +- plugins/patman/patman.cpp | 2 +- src/core/CMakeLists.txt | 4 + src/core/SampleBuffer.cpp | 1218 ++++------------- src/core/SampleBufferVisualizer.cpp | 4 +- src/core/SampleRecordHandle.cpp | 31 +- src/core/samplebuffer/SampleBufferData.cpp | 166 +++ .../samplebuffer/SampleBufferFileHelper.cpp | 349 +++++ src/gui/widgets/EnvelopeAndLfoView.cpp | 2 - src/gui/widgets/Graph.cpp | 2 - src/tracks/SampleTrack.cpp | 2 +- 15 files changed, 1261 insertions(+), 1255 deletions(-) create mode 100644 include/internal/SampleBufferData.h create mode 100644 include/internal/SampleBufferFileHelper.h create mode 100644 include/internal/SampleBufferPlayInfo.h create mode 100644 src/core/samplebuffer/SampleBufferData.cpp create mode 100644 src/core/samplebuffer/SampleBufferFileHelper.cpp diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 7d9c6459cca..feb3e7aec7f 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -26,385 +26,383 @@ #ifndef SAMPLE_BUFFER_H #define SAMPLE_BUFFER_H -#include -#include -#include - -#include - -#include "lmms_export.h" -#include - -#include "interpolation.h" -#include "lmms_basics.h" -#include "lmms_math.h" -#include "shared_object.h" -#include "MemoryManager.h" +#include "internal/SampleBufferData.h" +#include "internal/SampleBufferPlayInfo.h" #include "JournallingObject.h" - +#include "Threading.h" +#include "UpdatingValue.h" class QPainter; + class QRect; // values for buffer margins, used for various libsamplerate interpolation modes // the array positions correspond to the converter_type parameter values in libsamplerate // if there appears problems with playback on some interpolation mode, then the value for that mode // may need to be higher - conversely, to optimize, some may work with lower values -const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; +const f_cnt_t MARGIN[] = {64, 64, 64, 4, 4}; -class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject -{ - Q_OBJECT - MM_OPERATORS -public: - typedef std::vector> DataVector; +class LMMS_EXPORT SampleBuffer : public QObject, public JournallingObject { +Q_OBJECT +MM_OPERATORS + + enum UpdateType : int { + Clear, + Append + }; + + typedef std::shared_ptr SharedData; + typedef std::shared_ptr SharedPlayInfo; + + template> + class _MixerGuardedSharedPtr { + public: + explicit _MixerGuardedSharedPtr(PtrType ptr) + : m_ptr(std::move(ptr)) { + } + + VarType *operator->() { + return m_ptr.get(); + } + + VarType &operator*() { + return (*m_ptr); + } + + + private: + PtrType m_ptr; + Mixer::RequestChangesGuard m_guard = Engine::mixer()->requestChangesGuard(); + }; + + using GuardedPlayInfo=_MixerGuardedSharedPtr; + using GuardedConstPlayInfo=_MixerGuardedSharedPtr; + + GuardedPlayInfo guardedPlayInfo() { + return GuardedPlayInfo(m_playInfo); + } + + GuardedConstPlayInfo guardedPlayInfo() const { + return GuardedConstPlayInfo(m_playInfo); + } + + + using GuardedData=_MixerGuardedSharedPtr; + using GuardedConstData=_MixerGuardedSharedPtr; + + GuardedData guardedData() { + return GuardedData(m_data); + } + + GuardedConstData guardedData() const { + return GuardedConstData(m_data); + } + + struct SampleBufferInfo { + SampleBufferInfo() = default; + + SampleBufferInfo(GuardedPlayInfo &playInfo, GuardedData &data) + : startFrame{playInfo->getStartFrame()}, + endFrame{playInfo->getEndFrame()}, + loopStartFrame{playInfo->getLoopStartFrame()}, + loopEndFrame{playInfo->getLoopEndFrame()}, + + amplification{data->getAmplification()}, + audioFile(playInfo->getMaybeAudioFile()), + frames{data->frames()}, + frequency{data->getFrequency()}, + sampleRate{data->getSampleRate()} { + } + + int sampleLength() const { + double playFrames = double(endFrame - startFrame); - enum LoopMode { - LoopOff = 0, - LoopOn, - LoopPingPong + return playFrames / sampleRate * 1000; + } + + f_cnt_t startFrame; + f_cnt_t endFrame; + f_cnt_t loopStartFrame; + f_cnt_t loopEndFrame; + + float amplification; + QString audioFile; + f_cnt_t frames; + float frequency; + sample_rate_t sampleRate; }; - class LMMS_EXPORT handleState - { - MM_OPERATORS + +public: + typedef UpdatingValue InfoUpdatingValue; + + SampleBufferInfo createInfo() { + auto playInfo = guardedPlayInfo(); + auto data = guardedData(); + + return SampleBufferInfo(playInfo, data); + } + + InfoUpdatingValue createUpdatingValue(QObject *parent) { + return InfoUpdatingValue{*m_infoChangeNotifier, createInfo(), parent}; + } + + using DataVector=internal::SampleBufferData::DataVector; + using LoopMode=::LoopMode; + + class LMMS_EXPORT handleState { + MM_OPERATORS + public: - handleState( bool _varying_pitch = false, int interpolation_mode = SRC_LINEAR ); + handleState(bool _varying_pitch = false, int interpolation_mode = SRC_LINEAR); + virtual ~handleState(); - const f_cnt_t frameIndex() const - { + f_cnt_t frameIndex() const { return m_frameIndex; } - void setFrameIndex( f_cnt_t _index ) - { + void setFrameIndex(f_cnt_t _index) { m_frameIndex = _index; } - bool isBackwards() const - { + bool isBackwards() const { return m_isBackwards; } - void setBackwards( bool _backwards ) - { + void setBackwards(bool _backwards) { m_isBackwards = _backwards; } - - int interpolationMode() const - { + + int interpolationMode() const { return m_interpolationMode; } - private: f_cnt_t m_frameIndex; const bool m_varyingPitch; bool m_isBackwards; - SRC_STATE * m_resamplingData; + SRC_STATE *m_resamplingData; int m_interpolationMode; + }; - friend class SampleBuffer; - } ; + explicit SampleBuffer(); - SampleBuffer(); - // constructor which either loads sample _audio_file or decodes - // base64-data out of string - SampleBuffer(const QString & _audio_file, bool _is_base64_data, sample_rate_t sampleRate=0); - SampleBuffer(DataVector &&movedData, sample_rate_t sampleRate); + explicit SampleBuffer(internal::SampleBufferData &&data); - inline virtual QString nodeName() const override - { - return "samplebuffer"; - } - virtual void saveSettings(QDomDocument& doc, QDomElement& _this ) override; - virtual void loadSettings(const QDomElement& _this ) override; - - bool play( sampleFrame * _ab, handleState * _state, - const fpp_t _frames, - const float _freq, - 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 ) - { - visualize( _p, _dr, _dr, _from_frame, _to_frame ); - } + /** + * Load this sample buffer from @a _audio_file, + * @param _audio_file path to an audio file. + */ + explicit SampleBuffer(const QString &_audio_file, bool ignoreError=false); - QPair visualizeToPoly( const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0) const; + /** + * Load the sample buffer from a base64 encoded string. + * @param base64Data The data. + * @param sample_rate The data's sample rate. + */ + SampleBuffer(const QString &base64Data, sample_rate_t sample_rate); - inline void resetAudioFile() - { - m_audioFile = ""; - } + SampleBuffer(DataVector &&movedData, sample_rate_t sampleRate); - inline const QString & audioFile() const - { - return m_audioFile; + inline virtual QString nodeName() const override { + return "samplebuffer"; } - inline f_cnt_t startFrame() const - { - return m_startFrame; - } + SampleBuffer(SampleBuffer &&sampleBuffer) noexcept; - inline f_cnt_t endFrame() const - { - return m_endFrame; - } + SampleBuffer &operator=(SampleBuffer &&other) noexcept; - inline f_cnt_t loopStartFrame() const - { - return m_loopStartFrame; - } + virtual void saveSettings(QDomDocument &doc, QDomElement &_this) override; - inline f_cnt_t loopEndFrame() const - { - return m_loopEndFrame; - } + virtual void loadSettings(const QDomElement &_this) override; - void setLoopStartFrame( f_cnt_t _start ) - { - m_loopStartFrame = _start; - } + bool play(sampleFrame *_ab, handleState *_state, + const fpp_t _frames, + const float _freq, + const LoopMode _loopmode = LoopMode::LoopOff); - void setLoopEndFrame( f_cnt_t _end ) - { - m_loopEndFrame = _end; + template + void setLoopStartFrame(f_cnt_t _start) { + runToSetPlayInfo([=](GuardedPlayInfo &playInfo) { + playInfo->setLoopEndFrame(_start); + }); } - void setAllPointFrames( f_cnt_t _start, f_cnt_t _end, f_cnt_t _loopstart, f_cnt_t _loopend ) - { - m_startFrame = _start; - m_endFrame = _end; - m_loopStartFrame = _loopstart; - m_loopEndFrame = _loopend; + template + void setLoopEndFrame(f_cnt_t _end) { + runToSetPlayInfo([=](GuardedPlayInfo &playInfo) { + playInfo->setLoopEndFrame(_end); + }); } - inline f_cnt_t frames() const - { - dataReadLock(); - auto size = internalFrames(); - dataUnlock(); - return size; - } - inline float amplification() const - { - return m_amplification; + template + void setAllPointFrames(f_cnt_t _start, f_cnt_t _end, f_cnt_t _loopstart, f_cnt_t _loopend) { + runToSetPlayInfo([=](GuardedPlayInfo &playInfo) { + playInfo->setStartFrame(_start); + playInfo->setEndFrame(_end); + playInfo->setLoopStartFrame(_loopstart); + playInfo->setLoopEndFrame(_loopend); + }); } - inline float frequency() const - { - return m_frequency; + template + void setStartFrame(const f_cnt_t _s) { + runToSetPlayInfo([=](GuardedPlayInfo &playInfo) { + playInfo->setStartFrame(_s); + }); } - sample_rate_t sampleRate() const - { - return m_sampleRate; + template + void setEndFrame(const f_cnt_t _e) { + runToSetPlayInfo([=](GuardedPlayInfo &playInfo) { + playInfo->setEndFrame(_e); + }); } - int sampleLength() const - { - return double( m_endFrame - m_startFrame ) / m_sampleRate * 1000; + template + void setAmplification(float _a) { + runToSetData([_a](GuardedData &data) { + data->setAmplification(_a); + }); } - inline void setFrequency( float _freq ) - { - m_frequency = _freq; + template + void setFrequency(float frequency) { + runToSetData([frequency](GuardedData &data) { + data->setFrequency(frequency); + }); } - inline const sampleFrame * data() const - { - return m_data.data (); - } + void setAudioFile(const QString &audioFile, bool ignoreError = false); - QString openAudioFile() const; - QString openAndSetAudioFile(); - QString openAndSetWaveformFile(); - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock(), out of loops for efficiency - inline sample_t userWaveSample( const float _sample ) const - { - f_cnt_t dataFrames = internalFrames(); - if (dataFrames == 0) - return 0; - - const sampleFrame * data = this->data(); - const float frame = _sample * dataFrames; - f_cnt_t f1 = static_cast( frame ) % dataFrames; - if( f1 < 0 ) - { - f1 += dataFrames; - } - return linearInterpolate( data[f1][0], data[ (f1 + 1) % dataFrames ][0], fraction( frame ) ); - } + static QString openAudioFile(const QString ¤tAudioFile = QString()); - bool tryDataReadLock () const - { - return m_varLock.tryLockForRead (); - } + QString openAndSetAudioFile(const QString ¤tAudioFile = QString()); + QString openAndSetWaveformFile(QString currentAudioFile = QString()); - void dataReadLock() const - { - m_varLock.lockForRead(); - } + sample_t userWaveSample(const float _sample) const; - void dataUnlock() const - { - m_varLock.unlock(); - } - static QString tryToMakeRelative( const QString & _file ); - static QString tryToMakeAbsolute(const QString & file); + static QString tryToMakeRelative(const QString &_file); - /** - * @brief Try to add data to the buffer, - * @param begin Beginning of an InputIterator. - * @param end End of an InputIterator. - * @param syncWithMixer Should we call requestChangeInModel? - * @return - */ - bool tryAddData(const DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer=true); + static QString tryToMakeAbsolute(const QString &file); /** * @brief Add data to the buffer, * @param begin Beginning of an InputIterator. * @param end End of an InputIterator. - * @param syncWithMixer Should we call requestChangeInModel? * @return */ - void addData(const DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer=true); + void addData(const DataVector &vector, sample_rate_t sampleRate); /** * @brief Reset the class and initialize it with @a newData. * @param newData mm, that's the new data. * @param dataSampleRate Sample rate for @a newData. - * @param syncWithMixer Should we call requestChangeInModel? - */ - void resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer=true); - - /** - * @brief A non-blocking version of resetData. - * @param newData mm, that's the new data. - * @param dataSampleRate Sample rate for @a newData. - * @param syncWithMixer Should we call requestChangeInModel? - * @return */ - bool tryResetData(DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer=true); + void resetData(DataVector &&newData, sample_rate_t dataSampleRate); /** * @brief Just reverse the current buffer. - * @param syncWithMixer Should we call requestChangeInModel? * * This function simply calls `std::reverse` on m_data. */ - void reverse(bool syncWithMixer=true); + void reverse(); - void loadFromBase64(const QString & _data , sample_rate_t sampleRate); + 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) { + visualize(_p, _dr, _dr, _from_frame, _to_frame); + } -public slots: - void setAudioFile(const QString & audioFile , bool ignoreError = false); - void setStartFrame( const f_cnt_t _s ); - void setEndFrame( const f_cnt_t _e ); - void setAmplification( float _a ); + std::pair + visualizeToPoly(const QRect &_dr, const QRect &_clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0) const; -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 ) - { - m_sampleRate = _rate; - } +private: - static sample_rate_t mixerSampleRate(); + QString &toBase64(QString &_dst) const; // HACK: libsamplerate < 0.1.8 doesn't get read-only variables // as const. It has been fixed in 0.1.9 but has not been // shipped for some distributions. // This function just returns a variable that should have // been `const` as non-const to bypass using 0.1.9. - inline static sampleFrame * libSampleRateSrc (const sampleFrame *ptr) - { - return const_cast(ptr); + inline static sampleFrame *libSampleRateSrc(const sampleFrame *ptr) { + return const_cast(ptr); } - void changeAudioFile (QString audioFile, bool ignoreError); + static f_cnt_t getLoopedIndex(f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf); - static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); - - static DataVector decodeSampleSF(QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate, QString &loadingWarning, bool &isError); -#ifdef LMMS_HAVE_OGGVORBIS - static DataVector decodeSampleOGGVorbis(QString _f, - ch_cnt_t & _channels, - sample_rate_t & _sample_rate, - bool &isError); -#endif - static DataVector decodeSampleDS(QString _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); - - inline sampleFrame * data() - { - return m_data.data (); - } - - size_t internalFrames() const - { - return m_data.size (); - } + static f_cnt_t getPingPongIndex(f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf); + /** + * @brief A helper class used when changes to m_data is needed + */ + class DataChangeHelper { + public: + explicit DataChangeHelper(SampleBuffer *buffer, UpdateType updateType); - QString m_audioFile; - DataVector m_data; - mutable QReadWriteLock m_varLock; - f_cnt_t m_startFrame; - f_cnt_t m_endFrame; - f_cnt_t m_loopStartFrame; - f_cnt_t m_loopEndFrame; - float m_amplification; - float m_frequency; - sample_rate_t m_sampleRate; + ~DataChangeHelper(); - 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; + private: + SampleBuffer *m_buffer; + UpdateType m_updateType; + Mixer::RequestChangesGuard m_mixerGuard; + }; + /** + * A little helper that will run a callable + * with async, and giving it a guarded play + * info as an argument and notifies about the + * changes in the end. + */ + template + void runToSetValue(Func &&func) { + runAccordingToLaunchType([func, this] { + func(); + m_infoChangeNotifier->onValueUpdated(createInfo()); + }, LaunchType{}); + } - static DataVector resampleData(const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t requiredSampleRate); + /** + * Run a function and give it a GuardedPlayInfo as a parameter. + * @param func The function to run. + */ + template + void runToSetPlayInfo(Func &&func) { + runToSetValue([func, this] { + auto playInfo = guardedPlayInfo(); + func(playInfo); + }); + } /** - * @brief A helper class used when changes to m_data is needed + * Run a function and give it a GuardedData as a parameter. + * @param func The function to run. */ - class DataChangeHelper - { - public: - enum LockType {NoLock, TryLock, LockNow}; - DataChangeHelper(SampleBuffer* buffer, sample_rate_t desiredSampleRate, LockType lockType, bool syncWithMixer = true); - bool isSuccessful() {return m_successful;} - void finish(); - ~DataChangeHelper() {finish();} - private: - SampleBuffer* m_buffer; - sample_rate_t m_desiredSampleRate; - bool m_successful; - bool m_bufferLocked; - bool m_syncedWithMixer; - }; + template + void runToSetData(Func &&func) { + runToSetValue([func, this] { + auto data = guardedData(); + func(data); + }); + } signals: - void sampleUpdated(); -} ; + void sampleUpdated(int updateType); + +private: + SharedData m_data; + SharedPlayInfo m_playInfo; + + UniqueQObjectPointer m_infoChangeNotifier = + makeUniqueQObject(); +}; #endif diff --git a/include/internal/SampleBufferData.h b/include/internal/SampleBufferData.h new file mode 100644 index 00000000000..a94a4d43f84 --- /dev/null +++ b/include/internal/SampleBufferData.h @@ -0,0 +1,108 @@ +#ifndef LMMS_SAMPLEBUFFERDATA_H +#define LMMS_SAMPLEBUFFERDATA_H + +#include +#include +#include "Mixer.h" +#include "Engine.h" +#include "MemoryManager.h" +#include "lmms_basics.h" + +namespace internal { + inline sample_rate_t mixerSampleRate() { + return Engine::mixer()->processingSampleRate(); + } + + class SampleBufferData { + public: + typedef std::vector> DataVector; + + SampleBufferData(DataVector &&data, + sample_rate_t sampleRate); + + SampleBufferData() = default; + + + static SampleBufferData loadFromBase64(const QString &_data, + sample_rate_t sampleRate); + + f_cnt_t frames() const { + return static_cast(m_data.size()); + } + + void addData(const DataVector &vector, sample_rate_t sampleRate) { + if (sampleRate != m_sampleRate) { + auto resampledVector = resampleData(vector, sampleRate, m_sampleRate); + m_data.insert(m_data.end(), resampledVector.cbegin(), resampledVector.cend()); + } else { + m_data.insert(m_data.end(), vector.cbegin(), vector.cend()); + } + } + + void resetData(DataVector &&newData, sample_rate_t dataSampleRate) { + m_sampleRate = dataSampleRate; + m_data = std::move(newData); + } + + void reverse() { + std::reverse(m_data.begin(), m_data.end()); + } + + static DataVector + resampleData(const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t desiredSampleRate); + + 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; + + sampleFrame *data() { + return m_data.data(); + } + + const + sampleFrame *data() const { + return m_data.data(); + } + + private: + // HACK: libsamplerate < 0.1.9's structs does not mark read-only variables + // as const. It has been fixed in 0.1.9 but has not been + // shipped for some distributions. + // This function just returns a variable that should have + // been `const` as non-const. + inline static sampleFrame *libSampleRateSrc(const sampleFrame *ptr) { + return const_cast(ptr); + } + + + DataVector m_data; + float m_frequency = BaseFreq; + float m_amplification = 1.0f; + sample_rate_t m_sampleRate = internal::GetMixerSampleRate(); + public: + float getFrequency() const { + return 0; + } + + sample_rate_t getSampleRate() const { + return m_sampleRate; + } + + void setFrequency(float frequency) { + m_frequency = frequency; + } + + float getAmplification() const { + return m_amplification; + } + + void setAmplification(float amplification) { + m_amplification = amplification; + } + }; +} +#endif //LMMS_SAMPLEBUFFERDATA_H diff --git a/include/internal/SampleBufferFileHelper.h b/include/internal/SampleBufferFileHelper.h new file mode 100644 index 00000000000..97d0b47bf14 --- /dev/null +++ b/include/internal/SampleBufferFileHelper.h @@ -0,0 +1,27 @@ +#ifndef LMMS_SAMPLEBUFFERFILEHELPER_H +#define LMMS_SAMPLEBUFFERFILEHELPER_H + +#include +#include "SampleBufferData.h" + +namespace internal { + class SampleBufferFileHelper { + public: + using FileName=QString; + + /** + * @brief Load file data + * @param fileName Path to the file. + * @param ignoreError Should we present an error to the user? + */ + static + SampleBufferData Load(FileName fileName, bool ignoreError = false); + + private: + static QString tryToMakeRelative(const QString &file); + + }; +} + + +#endif //LMMS_SAMPLEBUFFERFILEHELPER_H diff --git a/include/internal/SampleBufferPlayInfo.h b/include/internal/SampleBufferPlayInfo.h new file mode 100644 index 00000000000..325c72cc64e --- /dev/null +++ b/include/internal/SampleBufferPlayInfo.h @@ -0,0 +1,71 @@ +// +// Created by reflexe on 26/03/19. +// + +#ifndef LMMS_SAMPLEBUFFERPLAYINFO_H +#define LMMS_SAMPLEBUFFERPLAYINFO_H + +#include "Mixer.h" + +namespace internal { + class SampleBufferPlayInfo { + public: + explicit SampleBufferPlayInfo(f_cnt_t frames) + : m_endFrame{frames}, m_loopEndFrame{frames} { + } + + int sampleLength(sample_rate_t sampleRate) const { + return static_cast(double(m_endFrame - m_startFrame) / sampleRate * 1000); + } + + f_cnt_t getStartFrame() const { + return m_startFrame; + } + + f_cnt_t getEndFrame() const { + return m_endFrame; + } + + f_cnt_t getLoopStartFrame() const { + return m_loopStartFrame; + } + + f_cnt_t getLoopEndFrame() const { + return m_loopEndFrame; + } + + float getAmplification() const { + return m_amplification; + } + + void setStartFrame(f_cnt_t startFrame) { + m_startFrame = startFrame; + } + + void setEndFrame(f_cnt_t endFrame) { + m_endFrame = endFrame; + } + + void setLoopStartFrame(f_cnt_t loopStartFrame) { + m_loopStartFrame = loopStartFrame; + } + + void setLoopEndFrame(f_cnt_t loopEndFrame) { + m_loopEndFrame = loopEndFrame; + } + + void setAmplification(float amplification) { + m_amplification = amplification; + } + + private: + f_cnt_t m_startFrame = 0; + f_cnt_t m_endFrame = 0; + f_cnt_t m_loopStartFrame = 0; + f_cnt_t m_loopEndFrame = 0; + float m_amplification = 1.0f; + }; +} + + +#endif //LMMS_SAMPLEBUFFERPLAYINFO_H diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 2249ac72359..918d410dc74 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -241,7 +241,7 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } else if( _this.attribute( "sampledata" ) != "" ) { - m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) , + m_sampleBuffer = SampleBuffer( _this.attribute( "srcdata" ) , Engine::mixer ()->baseSampleRate ()); qWarning("Using default sampleRate. That could lead to invalid values"); } else { @@ -346,7 +346,7 @@ void audioFileProcessor::reverseModelChanged( void ) if (m_reverseModel.value () != m_isCurrentlyReversed) { m_isCurrentlyReversed = m_reverseModel.value (); - m_sampleBuffer.reverse (true); + m_sampleBuffer.reverse (); } m_nextPlayStartPoint = m_sampleBuffer.startFrame(); diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index d5ce89688b4..00083579bfe 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -151,7 +151,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n, hdata->sample->frequency(); if( hdata->sample->play( _working_buffer + offset, hdata->state, frames, - play_freq, m_loopedModel.value() ? SampleBuffer::LoopOn : SampleBuffer::LoopOff ) ) + play_freq, m_loopedModel.value() ? LoopOn : LoopOff ) ) { applyRelease( _working_buffer, _n ); instrumentTrack()->processAudioBuffer( _working_buffer, diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index debd56edbe6..618453bc24f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -101,5 +101,9 @@ set(LMMS_SRCS core/SampleBufferVisualizer.cpp + core/samplebuffer/SampleBufferData.cpp + core/samplebuffer/SampleBufferFileHelper.cpp + + PARENT_SCOPE ) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 4dd7f125ae3..f192909d01e 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -26,7 +26,6 @@ #include -#include #include #include #include @@ -34,537 +33,106 @@ #include -#include - #define OV_EXCLUDE_STATIC_CALLBACKS #ifdef LMMS_HAVE_OGGVORBIS -#include #endif #include "base64.h" #include "ConfigManager.h" #include "DrumSynth.h" -#include "endian_handling.h" -#include "Engine.h" -#include "GuiApplication.h" -#include "Mixer.h" #include "FileDialog.h" +#include "internal/SampleBufferFileHelper.h" +#include "lmms_math.h" +#include "interpolation.h" - -SampleBuffer::SampleBuffer() : - m_audioFile( "" ), - m_startFrame( 0 ), - m_endFrame( 0 ), - m_loopStartFrame( 0 ), - m_loopEndFrame( 0 ), - m_amplification( 1.0f ), - m_frequency( BaseFreq ), - m_sampleRate( mixerSampleRate () ) +SampleBuffer::SampleBuffer(const QString &_audio_file, bool ignoreError) + : SampleBuffer{internal::SampleBufferFileHelper::Load(_audio_file, ignoreError)} { - m_loopEndFrame = m_endFrame = internalFrames(); + m_maybeFileName = _audio_file; } +SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData, sample_rate_t sampleRate) + : SampleBuffer{internal::SampleBufferData{std::move(movedData), sampleRate}} +{ +} -SampleBuffer::SampleBuffer( const QString & _audio_file, - bool _is_base64_data, sample_rate_t sampleRate ) - : SampleBuffer() +SampleBuffer::SampleBuffer(SampleBuffer &&sampleBuffer) noexcept + : + m_data{std::move(sampleBuffer.m_data)}, + m_playInfo(std::move(sampleBuffer.m_playInfo)) { - if( _is_base64_data ) - { - loadFromBase64( _audio_file, sampleRate ); - } - else - { - setAudioFile (_audio_file); - } + emit sampleUpdated(UpdateType::Clear); } +SampleBuffer &SampleBuffer::operator=(SampleBuffer &&other) noexcept{ + DataChangeHelper helper(this, UpdateType::Clear); -SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t sampleRate) : - SampleBuffer() -{ - resetData (std::move(movedData), sampleRate, false); + m_data = std::move(other.m_data); + m_playInfo = std::move(other.m_playInfo); + + return *this; } void SampleBuffer::saveSettings(QDomDocument &doc, QDomElement &_this) { { QString string; - _this.setAttribute( "data", toBase64(string) ); + _this.setAttribute("data", toBase64(string)); } - _this.setAttribute ("sampleRate", sampleRate ()); - _this.setAttribute ("amplification", amplification ()); - _this.setAttribute ("frequency", frequency ()); + _this.setAttribute("sampleRate", sampleRate()); + _this.setAttribute("amplification", amplification()); + _this.setAttribute("frequency", frequency()); } void SampleBuffer::loadSettings(const QDomElement &_this) { - if (_this.hasAttribute ("sampleRate")) { - m_sampleRate = _this.attribute ("sampleRate").toUInt (); + sample_rate_t loadingSampleRate = Engine::mixer()->baseSampleRate(); + + if (_this.hasAttribute("sampleRate")) { + loadingSampleRate = _this.attribute("sampleRate").toUInt(); } else { qWarning("SampleBuffer::loadSettings: Using default sampleRate. That could lead to invalid values"); } - if (_this.hasAttribute ("amplification")) { - m_amplification = _this.attribute ("amplification").toFloat (); - } - - if (_this.hasAttribute ("frequency")) { - m_frequency = _this.attribute ("frequency").toFloat (); - } - - if (_this.hasAttribute ("data")) { - loadFromBase64 (_this.attribute("data"), m_sampleRate); - } -} - -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_data = std::move (newData); -} - -sample_rate_t SampleBuffer::mixerSampleRate() -{ - return Engine::mixer ()->processingSampleRate (); -} - -void SampleBuffer::changeAudioFile(QString audioFile, bool ignoreError) -{ - if (audioFile == "") - return; - - // File size and sample length limits - const int fileSizeMax = 300; // MB - const int sampleLengthMax = 90; // Minutes - - bool fileLoadError = false; - QString file = tryToMakeAbsolute( audioFile ); - - ch_cnt_t channels = DEFAULT_CHANNELS; - sample_rate_t samplerate = mixerSampleRate (); - DataVector fileData; - - const QFileInfo fileInfo( file ); - if( fileInfo.size() > fileSizeMax * 1024 * 1024 ) - { - fileLoadError = true; - } - - if (!fileLoadError) { - // Use QFile to handle unicode file names on Windows - QFile f(file); - f.open(QIODevice::ReadOnly); - SNDFILE * snd_file; - SF_INFO sf_info; - sf_info.format = 0; - if( ( snd_file = sf_open_fd( f.handle(), SFM_READ, &sf_info, false ) ) != NULL ) - { - f_cnt_t frames = sf_info.frames; - int rate = sf_info.samplerate; - if( frames / rate > sampleLengthMax * 60 ) - { - fileLoadError = true; - } - sf_close( snd_file ); - } - f.close(); - } - - QString loadingWarning; - 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( fileInfo.suffix() == "ogg" ) - { - fileLoadError = false; - fileData = decodeSampleOGGVorbis( file, channels, samplerate, fileLoadError); - } -#endif - if( fileInfo.suffix() != "ogg" || fileLoadError) - { - fileLoadError = false; - fileData = decodeSampleSF( file, channels, samplerate, loadingWarning, fileLoadError); - } -#ifdef LMMS_HAVE_OGGVORBIS - if( fileLoadError ) - { - fileLoadError = false; - fileData = decodeSampleOGGVorbis( file, channels, samplerate, fileLoadError); - } -#endif - if( fileLoadError ) - { - fileLoadError = false; - fileData = decodeSampleDS( file, channels, samplerate); - } - } - - if (! fileLoadError) { - resetData (std::move(fileData), samplerate); - } else if (!ignoreError) { - 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 (! loadingWarning.isEmpty()) - message = loadingWarning; - if( gui ) - { - QMessageBox::information( NULL, - title, message, QMessageBox::Warning ); - } - else - { - fprintf( stderr, "%s\n", message.toUtf8().constData() ); - } - } -} - -SampleBuffer::DataVector 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; - DataVector vector(_frames); - const int ch = ( _channels > 1 ) ? 1 : 0; - - int idx = 0; - for( f_cnt_t frame = 0; frame < _frames; - ++frame ) - { - vector[frame][0] = _ibuf[idx+0] * fac; - vector[frame][1] = _ibuf[idx+ch] * fac; - idx += _channels; - } - - delete[] _ibuf; - - return vector; -} - - -SampleBuffer::DataVector -SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSampleRate, - sample_rate_t requiredSampleRate) -{ - if (inputData.empty()) - { - // no need to resample empty data - return SampleBuffer::DataVector{}; + if (_this.hasAttribute("data")) { + m_data = internal::SampleBufferData::loadFromBase64(_this.attribute("data"), loadingSampleRate); } - const f_cnt_t dst_frames = static_cast( inputData.size ()/ - (float) inputSampleRate * (float) requiredSampleRate ); - DataVector outputData(dst_frames); - // yeah, libsamplerate, let's rock with sinc-interpolation! - int error; - SRC_STATE * state; - if( ( state = src_new( SRC_SINC_MEDIUM_QUALITY, - DEFAULT_CHANNELS, &error ) ) != NULL ) - { - SRC_DATA src_data; - src_data.end_of_input = 1; - src_data.data_in = libSampleRateSrc(inputData.data ())->data (); - src_data.data_out = outputData.data ()->data(); - src_data.input_frames = inputData.size (); - src_data.output_frames = dst_frames; - src_data.src_ratio = (double) requiredSampleRate / inputSampleRate; - if( ( error = src_process( state, &src_data ) ) ) - { - printf( "SampleBuffer: error while resampling: %s\n", - src_strerror( error ) ); - } - src_delete( state ); - } - else - { - printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); + if (_this.hasAttribute("amplification")) { + m_data.setAmplification(_this.attribute("amplification").toFloat()); } - return outputData; -} - - -SampleBuffer::DataVector SampleBuffer::decodeSampleSF( QString _f, - ch_cnt_t & _channels, - sample_rate_t &_samplerate, - QString &loadingWarning, - bool &isError) -{ - SNDFILE * snd_file; - SF_INFO sf_info; - sf_info.format = 0; - f_cnt_t frames = 0; - DataVector vector; - bool sf_rr = false; - - - // Use QFile to handle unicode file names on Windows - QFile f(_f); - f.open(QIODevice::ReadOnly); - if( ( snd_file = sf_open_fd( f.handle(), SFM_READ, &sf_info, false ) ) != NULL ) - { - frames = sf_info.frames; - const auto channels = sf_info.channels; - - MmAllocator::vector buffer(channels * frames); - sf_rr = sf_read_float( snd_file, buffer.data(), channels * frames); - - if (sf_info.channels > DEFAULT_CHANNELS) { - loadingWarning = tr("The file you've selected has %1 channels. LMMS support " - "Stereo and Mono.").arg(sf_info.channels); - } - - // Copy buffer using stereo - vector.resize (frames); - auto rightOffset = sf_info.channels > 1 ? 1 : 0; - for (size_t i = 0; i < frames; i++) { - vector[i][0] = buffer[i * channels]; - vector[i][1] = buffer[i * channels + rightOffset]; - } - - if( sf_rr < sf_info.channels * frames ) - { -#ifdef DEBUG_LMMS - qDebug( "SampleBuffer::decodeSampleSF(): could not read" - " sample %s: %s", _f, sf_strerror( NULL ) ); -#endif - } - _channels = sf_info.channels; - _samplerate = sf_info.samplerate; - - sf_close( snd_file ); - } - else - { -#ifdef DEBUG_LMMS - qDebug( "SampleBuffer::decodeSampleSF(): could not load " - "sample %s: %s", _f, sf_strerror( NULL ) ); -#endif - loadingWarning = tr("SoundFile: Could not load: %1").arg(sf_strerror( NULL )); - isError = true; - } - f.close(); - - return vector; -} - - - - -#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( _udata )->read( (char*) _ptr, - _size * _n ); -} - - - - -int qfileSeekCallback( void * _udata, ogg_int64_t _offset, int _whence ) -{ - QFile * f = static_cast( _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( _udata ); - return 0; -} - - - - -long qfileTellCallback( void * _udata ) -{ - return static_cast( _udata )->pos(); -} - - - - -SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(QString _f, - ch_cnt_t & _channels, - sample_rate_t & _samplerate, - bool &isError) -{ - static ov_callbacks callbacks = - { - qfileReadCallback, - qfileSeekCallback, - qfileCloseCallback, - qfileTellCallback - } ; - - OggVorbis_File vf; - - f_cnt_t frames = 0; - - QFile * f = new QFile( _f ); - if( f->open( QFile::ReadOnly ) == false ) - { - delete f; - isError = true; - return {}; - } - - int err = ov_open_callbacks( f, &vf, NULL, 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; - - isError = true; - return {}; - } - - 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 ); - - auto _buf = new int_sample_t[total * _channels]; - int bitstream = 0; - long bytes_read = 0; - - do - { - bytes_read = ov_read( &vf, (char *) &_buf[frames * _channels], - ( total - frames ) * _channels * - BYTES_PER_INT_SAMPLE, - isLittleEndian() ? 0 : 1, - BYTES_PER_INT_SAMPLE, 1, &bitstream ); - if( bytes_read < 0 ) - { - break; - } - frames += bytes_read / ( _channels * BYTES_PER_INT_SAMPLE ); - } - while( bytes_read != 0 && bitstream == 0 ); - - ov_clear( &vf ); - // if buffer isn't empty, convert it to float and write it down - - if ( frames > 0 && _buf != NULL ) - { - return convertIntToFloat ( _buf, frames, _channels); - } - else if (frames < 0) - { - isError = true; - } - - return {}; -} -#endif - - - - -SampleBuffer::DataVector SampleBuffer::decodeSampleDS(QString _f, ch_cnt_t & _channels, - sample_rate_t & _samplerate) -{ - DrumSynth ds; - int_sample_t *_buf = NULL; - f_cnt_t frames = ds.GetDSFileSamples( _f, _buf, _channels, _samplerate ); - - if ( frames > 0 && _buf != NULL ) - { - return convertIntToFloat ( _buf, frames, _channels); + if (_this.hasAttribute("frequency")) { + m_data.setFrequency(_this.attribute("frequency").toFloat()); } - - return {}; - } +bool SampleBuffer::play(sampleFrame *_ab, handleState *_state, + const fpp_t _frames, + const float _freq, + const LoopMode _loopmode) { + f_cnt_t startFrame = m_playInfo.getStartFrame(); + f_cnt_t endFrame = m_playInfo.getEndFrame(); + f_cnt_t loopStartFrame = m_playInfo.getLoopStartFrame(); + f_cnt_t loopEndFrame = m_playInfo.getLoopEndFrame(); - - -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 ) - { + 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 * - double(m_sampleRate) / double(Engine::mixer()->processingSampleRate()); + const double freq_factor = (double) _freq / (double) m_data.getFrequency() * + double(m_data.getSampleRate()) / double(Engine::mixer()->processingSampleRate()); // calculate how many frames we have in requested pitch - const f_cnt_t total_frames_for_current_pitch = static_cast( ( - endFrame - startFrame ) / - freq_factor ); + const auto total_frames_for_current_pitch = static_cast((endFrame - startFrame) / + freq_factor ); - if( total_frames_for_current_pitch == 0 ) - { + if (total_frames_for_current_pitch == 0) { return false; } @@ -572,306 +140,150 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, // this holds the index of the first frame to play f_cnt_t play_frame = qMax(_state->m_frameIndex, startFrame); - if( _loopmode == LoopOff ) - { - if( play_frame >= endFrame || ( endFrame - play_frame ) / freq_factor == 0 ) - { + if (_loopmode == LoopOff) { + if (play_frame >= endFrame || (endFrame - play_frame) / freq_factor == 0) { // the sample is done being played return false; } - } - else if( _loopmode == LoopOn ) - { - play_frame = getLoopedIndex( play_frame, loopStartFrame, loopEndFrame ); - } - else - { - play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame ); + } else if (_loopmode == LoopOn) { + play_frame = getLoopedIndex(play_frame, loopStartFrame, loopEndFrame); + } else { + play_frame = getPingPongIndex(play_frame, loopStartFrame, loopEndFrame); } - f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) + MARGIN[ _state->interpolationMode() ]; + f_cnt_t fragment_size = (f_cnt_t) (_frames * freq_factor) + MARGIN[_state->interpolationMode()]; - sampleFrame * tmp = NULL; + sampleFrame *tmp = nullptr; + f_cnt_t usedFrames; // check whether we have to change pitch... - if( freq_factor != 1.0 || _state->m_varyingPitch ) - { + if (freq_factor != 1.0 || _state->m_varyingPitch) { SRC_DATA src_data; // Generate output src_data.data_in = - libSampleRateSrc(getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame ))->data (); - src_data.data_out = _ab->data (); + libSampleRateSrc(m_data.getSampleFragment(play_frame, fragment_size, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame))->data(); + 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 = 1.0 / freq_factor; src_data.end_of_input = 0; - int error = src_process( _state->m_resamplingData, - &src_data ); - if( error ) - { - printf( "SampleBuffer: error while resampling: %s\n", - src_strerror( error ) ); + int error = src_process(_state->m_resamplingData, + &src_data); + if (error) { + printf("SampleBuffer: error while resampling: %s\n", + src_strerror(error)); } - if( src_data.output_frames_gen > _frames ) - { - printf( "SampleBuffer: not enough frames: %ld / %d\n", - src_data.output_frames_gen, _frames ); + if (src_data.output_frames_gen > _frames) { + printf("SampleBuffer: not enough frames: %ld / %d\n", + src_data.output_frames_gen, _frames); } - // Advance - switch( _loopmode ) - { - 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 - { + + usedFrames = src_data.input_frames_used; + } 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( play_frame, _frames, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame ), - _frames * BYTES_PER_FRAME ); - // Advance - switch( _loopmode ) - { - 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; + // TODO: make a new function: fillSampleFragment for this case. + memcpy(_ab, + m_data.getSampleFragment(play_frame, _frames, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame), + _frames * BYTES_PER_FRAME); + usedFrames = _frames; + } + + // Advance + switch (_loopmode) { + case LoopOff: + play_frame += usedFrames; + break; + case LoopOn: + play_frame += usedFrames; + play_frame = getLoopedIndex(play_frame, loopStartFrame, loopEndFrame); + break; + case LoopPingPong: { + f_cnt_t left = usedFrames; + if (_state->isBackwards()) { + play_frame -= usedFrames; + 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; } } - if( tmp != NULL ) - { - MM_FREE( tmp ); + if (tmp != nullptr) { + MM_FREE(tmp); } - _state->setBackwards( is_backwards ); - _state->setFrameIndex( play_frame ); + _state->setBackwards(is_backwards); + _state->setFrameIndex(play_frame); - for( fpp_t i = 0; i < _frames; ++i ) - { - _ab[i][0] *= m_amplification; - _ab[i][1] *= m_amplification; + // TODO: move that to another function. + for (fpp_t i = 0; i < _frames; ++i) { + _ab[i][0] *= m_playInfo.getAmplification(); + _ab[i][1] *= m_playInfo.getAmplification(); } return true; } - -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( _loopmode == LoopOff ) - { - if( _index + _frames <= _end ) - { - return data () + _index; - } - } - else if( _loopmode == LoopOn ) - { - if( _index + _frames <= _loopend ) - { - return data () + _index; - } - } - else - { - if( ! *_backwards && _index + _frames < _loopend ) - { - return data () + _index; - } - } - - *_tmp = MM_ALLOC( sampleFrame, _frames ); - - if( _loopmode == LoopOff ) - { - f_cnt_t available = _end - _index; - memcpy( *_tmp, 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, 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, data () + _loopstart, todo * BYTES_PER_FRAME ); - copied += todo; - } - } - else - { - f_cnt_t pos = _index; - bool backwards = pos < _loopstart - ? false - : *_backwards; - f_cnt_t copied = 0; - - - if( backwards ) - { - 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 ) backwards = false; - } - else - { - copied = qMin( _frames, _loopend - pos ); - memcpy( *_tmp, data () + pos, copied * BYTES_PER_FRAME ); - pos += copied; - if( pos == _loopend ) backwards = true; - } - - while( copied < _frames ) - { - if( backwards ) - { - 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 ) backwards = false; - } - else - { - f_cnt_t todo = qMin( _frames - copied, _loopend - pos ); - memcpy( *_tmp + copied, data () + pos, todo * BYTES_PER_FRAME ); - pos += todo; - copied += todo; - if( pos >= _loopend ) backwards = true; - } - } - *_backwards = backwards; - } - - return *_tmp; -} - - - - -f_cnt_t SampleBuffer::getLoopedIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const -{ - if( _index < _endf ) - { +f_cnt_t SampleBuffer::getLoopedIndex(f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf) { + if (_index < _endf) { return _index; } - return _startf + ( _index - _startf ) - % ( _endf - _startf ); + 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 ) - { +f_cnt_t SampleBuffer::getPingPongIndex(f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf) { + if (_index < _endf) { return _index; } const f_cnt_t looplen = _endf - _startf; - const f_cnt_t looppos = ( _index - _endf ) % ( looplen*2 ); + const f_cnt_t looppos = (_index - _endf) % (looplen * 2); - return ( looppos < looplen ) - ? _endf - looppos - : _startf + ( looppos - looplen ); + return (looppos < looplen) + ? _endf - looppos + : _startf + (looppos - looplen); } -void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, - const QRect & _clip, f_cnt_t _from_frame, f_cnt_t _to_frame ) -{ - auto polyPair = visualizeToPoly (_dr, _clip, _from_frame, _to_frame); +void SampleBuffer::visualize(QPainter &_p, const QRect &_dr, + const QRect &_clip, f_cnt_t _from_frame, f_cnt_t _to_frame) { + auto polyPair = visualizeToPoly(_dr, _clip, _from_frame, _to_frame); - _p.setRenderHint( QPainter::Antialiasing ); - _p.drawPolyline (polyPair.first); - _p.drawPolyline (polyPair.second); + _p.setRenderHint(QPainter::Antialiasing); + _p.drawPolyline(polyPair.first); + _p.drawPolyline(polyPair.second); } -QPair SampleBuffer::visualizeToPoly(const QRect &_dr, const QRect &_clip, - f_cnt_t _from_frame, f_cnt_t _to_frame) const -{ - if( internalFrames () == 0 ) return {}; +std::pair SampleBuffer::visualizeToPoly(const QRect &_dr, const QRect &_clip, + f_cnt_t _from_frame, f_cnt_t _to_frame) const { + if (m_data.frames() == 0) return {}; const bool focus_on_range = _from_frame < _to_frame; - if (_to_frame > frames()) - _to_frame = frames(); + if (_to_frame > m_data.frames()) + _to_frame = m_data.frames(); - //_p.setClipRect( _clip ); const int w = _dr.width(); const int h = _dr.height(); -// const int yb = h / 2 + _dr.y(); - int y_space = (h/2); + int y_space = (h / 2); - const int nb_frames = focus_on_range ? _to_frame - _from_frame : internalFrames(); + const int nb_frames = focus_on_range ? _to_frame - _from_frame : m_data.frames(); if (nb_frames == 0) return {}; - const int fpp = tLimit( nb_frames / w, 1, 20 ); + const int fpp = tLimit(nb_frames / w, 1, 20); - bool shouldAddAdditionalPoint = (nb_frames % fpp) != 0; + bool shouldAddAdditionalPoint = (nb_frames % fpp) != 0; int pointsCount = (nb_frames / fpp) + (shouldAddAdditionalPoint ? 1 : 0); auto l = QPolygonF(pointsCount); auto r = QPolygonF(pointsCount); @@ -879,19 +291,18 @@ QPair SampleBuffer::visualizeToPoly(const QRect &_dr, cons int n = 0; const int xb = _dr.x(); const int first = focus_on_range ? _from_frame : 0; - const int last = focus_on_range ? _to_frame : internalFrames(); + const int last = focus_on_range ? _to_frame : m_data.frames(); int zeroPoint = _dr.y() + y_space; if (h % 2 != 0) zeroPoint += 1; - for( int frame = first; frame < last; frame += fpp ) - { - double x = (xb + (frame - first) * double( w ) / nb_frames); + for (int frame = first; frame < last; frame += fpp) { + double x = (xb + (frame - first) * double(w) / nb_frames); l[n] = QPointF(x, - ( zeroPoint + ( m_data[frame][0] * y_space * m_amplification ) ) ); + (zeroPoint + (m_data.data()[frame][0] * y_space))); r[n] = QPointF(x, - ( zeroPoint + ( m_data[frame][1] * y_space * m_amplification ) ) ); + (zeroPoint + (m_data.data()[frame][1] * y_space))); ++n; } @@ -900,216 +311,171 @@ QPair SampleBuffer::visualizeToPoly(const QRect &_dr, cons } - - -QString SampleBuffer::openAudioFile() const -{ - FileDialog ofd( NULL, tr( "Open audio file" ) ); +QString SampleBuffer::openAudioFile() const { + FileDialog ofd(NULL, tr("Open audio file")); QString dir; - if( !m_audioFile.isEmpty() ) - { - QString f = m_audioFile; - if( QFileInfo( f ).isRelative() ) - { + if (!m_maybeFileName.isEmpty()) { + QString f = m_maybeFileName; + if (QFileInfo(f).isRelative()) { f = ConfigManager::inst()->userSamplesDir() + f; - if( QFileInfo( f ).exists() == false ) - { + if (QFileInfo(f).exists() == false) { f = ConfigManager::inst()->factorySamplesDir() + - m_audioFile; + m_maybeFileName; } } - dir = QFileInfo( f ).absolutePath(); - } - else - { + dir = QFileInfo(f).absolutePath(); + } else { dir = ConfigManager::inst()->userSamplesDir(); } // change dir to position of previously opened file - ofd.setDirectory( dir ); - ofd.setFileMode( FileDialog::ExistingFiles ); + ofd.setDirectory(dir); + ofd.setFileMode(FileDialog::ExistingFiles); // set filters QStringList types; - types << tr( "All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc " - "*.aif *.aiff *.au *.raw)" ) - << tr( "Wave-Files (*.wav)" ) - << tr( "OGG-Files (*.ogg)" ) - << tr( "DrumSynth-Files (*.ds)" ) - << tr( "FLAC-Files (*.flac)" ) - << tr( "SPEEX-Files (*.spx)" ) - //<< tr( "MP3-Files (*.mp3)" ) - //<< tr( "MIDI-Files (*.mid)" ) - << tr( "VOC-Files (*.voc)" ) - << tr( "AIFF-Files (*.aif *.aiff)" ) - << tr( "AU-Files (*.au)" ) - << tr( "RAW-Files (*.raw)" ) + 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() ) - { + ; + ofd.setNameFilters(types); + if (!m_maybeFileName.isEmpty()) { // select previously opened file - ofd.selectFile( QFileInfo( m_audioFile ).fileName() ); + ofd.selectFile(QFileInfo(m_maybeFileName).fileName()); } - if( ofd.exec () == QDialog::Accepted ) - { - if( ofd.selectedFiles().isEmpty() ) - { + if (ofd.exec() == QDialog::Accepted) { + if (ofd.selectedFiles().isEmpty()) { return QString(); } - return tryToMakeRelative( ofd.selectedFiles()[0] ); + return tryToMakeRelative(ofd.selectedFiles()[0]); } return QString(); } - - -QString SampleBuffer::openAndSetAudioFile() -{ +QString SampleBuffer::openAndSetAudioFile() { QString fileName = this->openAudioFile(); - if(!fileName.isEmpty()) - { - this->setAudioFile( fileName ); + if (!fileName.isEmpty()) { + this->setAudioFile(fileName); } return fileName; } -QString SampleBuffer::openAndSetWaveformFile() -{ - if( m_audioFile.isEmpty() ) - { - m_audioFile = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; +QString SampleBuffer::openAndSetWaveformFile() { + if (m_maybeFileName.isEmpty()) { + m_maybeFileName = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; } QString fileName = this->openAudioFile(); - if(!fileName.isEmpty()) - { - this->setAudioFile( fileName ); - } - else - { - m_audioFile = ""; + if (!fileName.isEmpty()) { + this->setAudioFile(fileName); + } else { + m_maybeFileName = ""; } return fileName; } +sample_t SampleBuffer::userWaveSample(const float _sample) const { + f_cnt_t dataFrames = frames(); + if (dataFrames == 0) + return 0; - -QString & SampleBuffer::toBase64( QString & _dst ) const -{ - base64::encode( (const char *) data (), - internalFrames() * sizeof( sampleFrame ), _dst ); - - return _dst; -} - - -void SampleBuffer::setAudioFile(const QString & audioFile, bool ignoreError ) -{ - m_audioFile = tryToMakeRelative(audioFile); - changeAudioFile (audioFile, ignoreError); + auto data = guardedData(); + const float frame = _sample * dataFrames; + f_cnt_t f1 = static_cast( frame ) % dataFrames; + if (f1 < 0) { + f1 += dataFrames; + } + return linearInterpolate(data->data()[f1][0], data->data()[(f1 + 1) % dataFrames][0], fraction(frame)); } +QString &SampleBuffer::toBase64(QString &_dst) const { + auto data = guardedData(); -void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleRate) -{ - char * dst = NULL; - int dsize = 0; - base64::decode( _data, &dst, &dsize ); - - DataVector input(dsize / sizeof(sampleFrame)); - memcpy (input.data (), - dst, - input.size () * sizeof (sampleFrame)); + base64::encode((const char *) data->data(), + data->frames() * sizeof(sampleFrame), _dst); - delete[] dst; - - resetAudioFile(); - resetData (std::move(input), - sampleRate); + return _dst; } - - - -void SampleBuffer::setStartFrame( const f_cnt_t _s ) -{ - m_startFrame = _s; +void SampleBuffer::setStartFrame(const f_cnt_t _s) { + guardedPlayInfo()->setStartFrame(_s); } - - -void SampleBuffer::setEndFrame( const f_cnt_t _e ) -{ - m_endFrame = _e; +void SampleBuffer::setEndFrame(const f_cnt_t _e) { + guardedPlayInfo()->setEndFrame(_e); } +void SampleBuffer::setAmplification(float _a) { + guardedData()->setAmplification(_a); + emit sampleUpdated(); +} -void SampleBuffer::setAmplification( float _a ) +void SampleBuffer::setFrequency(float frequency) { - m_amplification = _a; + guardedData()->setFrequency(frequency); emit sampleUpdated(); } -QString SampleBuffer::tryToMakeRelative( const QString & file ) -{ - if( QFileInfo( file ).isRelative() == false ) - { +QString SampleBuffer::tryToMakeRelative(const QString &file) { + if (QFileInfo(file).isRelative() == false) { // Normalize the path - QString f( QDir::cleanPath( file ) ); + QString f(QDir::cleanPath(file)); // First, look in factory samples // Isolate "samples/" from "data:/samples/" - QString samplesSuffix = ConfigManager::inst()->factorySamplesDir().mid( ConfigManager::inst()->dataDir().length() ); + QString samplesSuffix = ConfigManager::inst()->factorySamplesDir().mid( + ConfigManager::inst()->dataDir().length()); // Iterate over all valid "data:/" searchPaths - for ( const QString & path : QDir::searchPaths( "data" ) ) - { - QString samplesPath = QDir::cleanPath( path + samplesSuffix ) + "/"; - if ( f.startsWith( samplesPath ) ) - { - return QString( f ).mid( samplesPath.length() ); + for (const QString &path : QDir::searchPaths("data")) { + QString samplesPath = QDir::cleanPath(path + samplesSuffix) + "/"; + if (f.startsWith(samplesPath)) { + return QString(f).mid(samplesPath.length()); } } // Next, look in user samples QString usd = ConfigManager::inst()->userSamplesDir(); - usd.replace( QDir::separator(), '/' ); - if( f.startsWith( usd ) ) - { - return QString( f ).mid( usd.length() ); + usd.replace(QDir::separator(), '/'); + if (f.startsWith(usd)) { + return QString(f).mid(usd.length()); } } return file; } - - -QString SampleBuffer::tryToMakeAbsolute(const QString& file) -{ +QString SampleBuffer::tryToMakeAbsolute(const QString &file) { QFileInfo f(file); - if(f.isRelative()) - { + if (f.isRelative()) { f = QFileInfo(ConfigManager::inst()->userSamplesDir() + file); - if(! f.exists()) - { + if (!f.exists()) { f = QFileInfo(ConfigManager::inst()->factorySamplesDir() + file); } } @@ -1120,134 +486,68 @@ QString SampleBuffer::tryToMakeAbsolute(const QString& file) return file; } -SampleBuffer::handleState::handleState( bool _varying_pitch, int interpolation_mode ) : - m_frameIndex( 0 ), - m_varyingPitch( _varying_pitch ), - m_isBackwards( false ) -{ +SampleBuffer::handleState::handleState(bool _varying_pitch, int interpolation_mode) : + m_frameIndex(0), + m_varyingPitch(_varying_pitch), + m_isBackwards(false) { int error; m_interpolationMode = interpolation_mode; - - if( ( m_resamplingData = src_new( interpolation_mode, DEFAULT_CHANNELS, &error ) ) == NULL ) - { - qDebug( "Error: src_new() failed in sample_buffer.cpp!\n" ); + + if ((m_resamplingData = src_new(interpolation_mode, DEFAULT_CHANNELS, &error)) == NULL) { + qDebug("Error: src_new() failed in sample_buffer.cpp!\n"); } } - - -SampleBuffer::handleState::~handleState() -{ - src_delete( m_resamplingData ); +SampleBuffer::handleState::~handleState() { + src_delete(m_resamplingData); } -SampleBuffer::DataChangeHelper::DataChangeHelper(SampleBuffer* buffer, - sample_rate_t desiredSampleRate, - LockType lockType, - bool syncWithMixer) : - m_buffer(buffer), - m_desiredSampleRate(desiredSampleRate), - m_successful(true), - m_bufferLocked(false), - m_syncedWithMixer(false) +SampleBuffer::DataChangeHelper::DataChangeHelper(SampleBuffer *buffer) : + m_buffer(buffer) { - switch (lockType) - { - case LockNow: - m_buffer->m_varLock.lockForWrite(); - m_bufferLocked = true; - break; - case TryLock: - m_successful = m_bufferLocked = m_buffer->m_varLock.tryLockForWrite(); - break; - case NoLock: - break; - } - if (m_successful && syncWithMixer) - { - Engine::mixer()->requestChangeInModel(); - m_syncedWithMixer = true; - } } -void SampleBuffer::DataChangeHelper::finish() -{ - if (!m_successful) return; - m_buffer->setSampleRate(m_desiredSampleRate); - - m_buffer->m_loopStartFrame = m_buffer->m_startFrame = 0; - m_buffer->m_loopEndFrame = m_buffer->m_endFrame = m_buffer->internalFrames(); - if (m_bufferLocked) {m_buffer->m_varLock.unlock();} - - if (m_syncedWithMixer) {Engine::mixer()->doneChangeInModel();} +SampleBuffer::DataChangeHelper::~DataChangeHelper() { + m_buffer->m_playInfo = internal::SampleBufferPlayInfo(m_buffer->m_data.frames()); emit m_buffer->sampleUpdated(); } -bool SampleBuffer::tryAddData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer) { - DataVector newVector; - - if (sampleRate != m_sampleRate) { - // We should resample this data; - - newVector = resampleData (vector, sampleRate, m_sampleRate); - sampleRate = m_sampleRate; - } - +void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate) { // First of all, don't let anyone read. - DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::TryLock, syncWithMixer); - if (!helper.isSuccessful()) return false; + DataChangeHelper helper(this); - if (newVector.empty()) - { - internalAddData(vector, sampleRate); - } - else - { - internalAddData(newVector, sampleRate); - } - - return true; + m_data.addData(vector, sampleRate); } -void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool syncWithMixer) { - 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. - DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::LockNow, syncWithMixer); +void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate) { + DataChangeHelper helper(this); + m_data.resetData(std::move(newData), dataSampleRate); +} - if (newVector.empty()) - { - internalAddData(vector, sampleRate); - } - else - { - internalAddData(newVector, sampleRate); - } +void SampleBuffer::reverse() { + DataChangeHelper helper(this); + m_data.reverse(); } -void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer) { - DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::LockNow, syncWithMixer); - internalResetData(std::move(newData), dataSampleRate); +void SampleBuffer::setAudioFile(const QString &audioFile, bool ignoreError) +{ + *this = SampleBuffer(audioFile, ignoreError); } -bool SampleBuffer::tryResetData(SampleBuffer::DataVector &&newData, sample_rate_t dataSampleRate, bool syncWithMixer) { - DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::TryLock, syncWithMixer); - if (!helper.isSuccessful()) return false; +SampleBuffer::SampleBuffer() + : SampleBuffer{internal::SampleBufferData{}} +{ +} - internalResetData(std::move(newData), dataSampleRate); - return true; +SampleBuffer::SampleBuffer(internal::SampleBufferData &&data) + : m_data{std::move(data)}, + m_playInfo(m_data.frames()) +{ + emit sampleUpdated(); } -void SampleBuffer::reverse(bool syncWithMixer) { - DataChangeHelper helper(this, m_sampleRate, DataChangeHelper::LockNow, syncWithMixer); - std::reverse(m_data.begin(), m_data.end()); +SampleBuffer::SampleBuffer(const QString &base64Data, sample_rate_t sample_rate) + : SampleBuffer{internal::SampleBufferData::loadFromBase64(base64Data, sample_rate)} { } diff --git a/src/core/SampleBufferVisualizer.cpp b/src/core/SampleBufferVisualizer.cpp index 791284df44d..99f6d486779 100644 --- a/src/core/SampleBufferVisualizer.cpp +++ b/src/core/SampleBufferVisualizer.cpp @@ -158,13 +158,11 @@ bool SampleBufferVisualizer::appendTact(const SampleBuffer &sampleBuffer, // Generate the actual visualization. auto fromFrame = MidiTime(m_cachedTime + offsetFromTact).frames (m_framesPerTact); - if (! sampleBuffer.tryDataReadLock()) - return false; auto poly = sampleBuffer.visualizeToPoly (currentPaintInTact, QRect(), fromFrame, fromFrame + totalTime.frames(m_framesPerTact)); - sampleBuffer.dataUnlock (); + m_currentPixmap.totalTime += totalTime; diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index e11f735a224..3be0a9d7da4 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -54,15 +54,12 @@ SampleRecordHandle::~SampleRecordHandle() // force-write it into the buffer. if (m_framesRecorded == 0) { - m_tco->sampleBuffer ()->resetAudioFile(); m_tco->sampleBuffer ()->resetData (std::move (m_currentBuffer), - Engine::mixer ()->inputSampleRate (), - false); + Engine::mixer ()->inputSampleRate ()); m_tco->setStartTimeOffset (m_startRecordTimeOffset); } else { m_tco->sampleBuffer ()->addData(m_currentBuffer, - Engine::mixer ()->inputSampleRate (), - false); + Engine::mixer ()->inputSampleRate ()); } } @@ -88,30 +85,22 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) writeBuffer( recbuf, frames ); - bool dataWrittenIntoSampleBuffer = true; - - // Try to add data to the buffer. - // If we could not do that. We'll do that next time. + // Add data to the buffer. if (m_framesRecorded == 0) { // Make sure we don't have the previous data. - m_tco->sampleBuffer ()->resetAudioFile(); - dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryResetData (std::move (m_currentBuffer), - Engine::mixer ()->inputSampleRate (), - false); + m_tco->sampleBuffer ()->resetData(std::move (m_currentBuffer), + Engine::mixer ()->inputSampleRate ()); m_tco->setStartTimeOffset (m_startRecordTimeOffset); } else { if (! m_currentBuffer.empty ()) { - dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryAddData(m_currentBuffer, - Engine::mixer ()->inputSampleRate (), - false); + m_tco->sampleBuffer ()->addData(m_currentBuffer, + Engine::mixer ()->inputSampleRate ()); } } - if (dataWrittenIntoSampleBuffer) { - m_framesRecorded += frames; - m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ()); - m_currentBuffer.clear(); - } + m_framesRecorded += frames; + m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ()); + m_currentBuffer.clear(); } diff --git a/src/core/samplebuffer/SampleBufferData.cpp b/src/core/samplebuffer/SampleBufferData.cpp new file mode 100644 index 00000000000..76e5730f921 --- /dev/null +++ b/src/core/samplebuffer/SampleBufferData.cpp @@ -0,0 +1,166 @@ +#include +#include +#include +#include "internal/SampleBufferData.h" + +internal::SampleBufferData::SampleBufferData(internal::SampleBufferData::DataVector &&data, + sample_rate_t sampleRate) + : m_data{std::move(data)}, m_frequency{BaseFreq}, m_sampleRate{sampleRate} { +} + +internal::SampleBufferData +internal::SampleBufferData::loadFromBase64(const QString &_data, sample_rate_t sampleRate) { + char *dst = nullptr; + int dsize = 0; + base64::decode(_data, &dst, &dsize); + + SampleBufferData::DataVector input(dsize / sizeof(sampleFrame)); + memcpy(input.data(), + dst, + input.size() * sizeof(sampleFrame)); + + delete[] dst; + + return SampleBufferData(std::move(input), sampleRate); +} + + +internal::SampleBufferData::DataVector +internal::SampleBufferData::resampleData(const SampleBufferData::DataVector &inputData, sample_rate_t inputSampleRate, + sample_rate_t desiredSampleRate) { + if (inputData.empty()) { + // no need to resample empty data + return DataVector{}; + } + const auto dst_frames = static_cast( inputData.size() / + (float) inputSampleRate * (float) desiredSampleRate ); + DataVector outputData(dst_frames); + + // 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 src_data; + src_data.end_of_input = 1; + src_data.data_in = libSampleRateSrc(inputData.data())->data(); + src_data.data_out = outputData.data()->data(); + src_data.input_frames = inputData.size(); + src_data.output_frames = dst_frames; + src_data.src_ratio = (double) desiredSampleRate / inputSampleRate; + if ((error = src_process(state, &src_data))) { + printf("SampleBuffer: error while resampling: %s\n", + src_strerror(error)); + } + src_delete(state); + } else { + printf("Error: src_new() failed in sample_buffer.cpp!\n"); + } + + return outputData; +} + +float internal::SampleBufferData::getAmplification() const +{ + return m_amplification; +} + +void internal::SampleBufferData::setAmplification(float amplification) +{ + m_amplification = amplification; +} + +void internal::SampleBufferData::setFrequency(float frequency) +{ + m_frequency = frequency; +} + +const +sampleFrame *internal::SampleBufferData::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 data() + _index; + } + } else if (_loopmode == LoopOn) { + if (_index + _frames <= _loopend) { + return data() + _index; + } + } else { + if (!*_backwards && _index + _frames < _loopend) { + return data() + _index; + } + } + + *_tmp = MM_ALLOC(sampleFrame, _frames); + + if (_loopmode == LoopOff) { + f_cnt_t available = _end - _index; + memcpy(*_tmp, 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, 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, data() + _loopstart, todo * BYTES_PER_FRAME); + copied += todo; + } + } else { + f_cnt_t pos = _index; + bool backwards = pos < _loopstart + ? false + : *_backwards; + f_cnt_t copied = 0; + + + if (backwards) { + 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) backwards = false; + } else { + copied = qMin(_frames, _loopend - pos); + memcpy(*_tmp, data() + pos, copied * BYTES_PER_FRAME); + pos += copied; + if (pos == _loopend) backwards = true; + } + + while (copied < _frames) { + if (backwards) { + 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) backwards = false; + } else { + f_cnt_t todo = qMin(_frames - copied, _loopend - pos); + memcpy(*_tmp + copied, data() + pos, todo * BYTES_PER_FRAME); + pos += todo; + copied += todo; + if (pos >= _loopend) backwards = true; + } + } + *_backwards = backwards; + } + + return *_tmp; +} + +float internal::SampleBufferData::getFrequency() const { + return m_frequency; +} + +sample_rate_t internal::SampleBufferData::getSampleRate() const { + return m_sampleRate; +} diff --git a/src/core/samplebuffer/SampleBufferFileHelper.cpp b/src/core/samplebuffer/SampleBufferFileHelper.cpp new file mode 100644 index 00000000000..6b828f2c836 --- /dev/null +++ b/src/core/samplebuffer/SampleBufferFileHelper.cpp @@ -0,0 +1,349 @@ +#include "internal/SampleBufferFileHelper.h" + +#include + + +#include +#include +#include +#include "DrumSynth.h" +#include "FileDialog.h" + + +#ifdef LMMS_HAVE_OGGVORBIS +#define OV_EXCLUDE_STATIC_CALLBACKS + +#include + +#endif + +#include "internal/SampleBufferFileHelper.h" +#include "GuiApplication.h" +#include "ConfigManager.h" +#include "Mixer.h" +#include "endian_handling.h" + + +namespace internal { + // TODO: move these to interfaces. + // (for another commit) + typedef SampleBufferData::DataVector (FileDecoderFunc)(const QString &fileName, + ch_cnt_t &_channels, sample_rate_t &_samplerate, + QString &loadingWarning, bool &isError); + + namespace FileDecoders { + FileDecoderFunc decodeSampleSF; + FileDecoderFunc decodeSampleOGGVorbis; + FileDecoderFunc decodeSampleDS; + } + + + constexpr FileDecoderFunc *fileDecodingFunctions[] = { + FileDecoders::decodeSampleSF, + FileDecoders::decodeSampleOGGVorbis, + FileDecoders::decodeSampleDS + }; + + SampleBufferData::DataVector convertIntToFloat(int_sample_t *&_ibuf, f_cnt_t _frames, int _channels); +} + +internal::SampleBufferData::DataVector internal::FileDecoders::decodeSampleSF(const QString &_f, + ch_cnt_t &_channels, + sample_rate_t &_samplerate, + QString &loadingWarning, + bool &isError) { + SNDFILE *snd_file; + SF_INFO sf_info; + sf_info.format = 0; + f_cnt_t frames = 0; + SampleBufferData::DataVector vector; + bool sf_rr = false; + + + // Use QFile to handle unicode file names on Windows + QFile f(_f); + f.open(QIODevice::ReadOnly); + if ((snd_file = sf_open_fd(f.handle(), SFM_READ, &sf_info, false)) != nullptr) { + frames = sf_info.frames; + const auto channels = sf_info.channels; + + // TODO: remove that. + MmAllocator::vector buffer(channels * frames); + sf_rr = sf_read_float(snd_file, buffer.data(), channels * frames); + + if (sf_info.channels > DEFAULT_CHANNELS) { + loadingWarning = QObject::tr("The file you've selected has %1 channels. LMMS support " + "Stereo and Mono.").arg(sf_info.channels); + } + + // Copy buffer using stereo + vector.resize(frames); + auto rightOffset = sf_info.channels > 1 ? 1 : 0; + for (size_t i = 0; i < frames; i++) { + vector[i][0] = buffer[i * channels]; + vector[i][1] = buffer[i * channels + rightOffset]; + } + + if (sf_rr < sf_info.channels * frames) { +#ifdef DEBUG_LMMS + qDebug( "SampleBuffer::decodeSampleSF(): could not read" + " sample %s: %s", _f, sf_strerror( NULL ) ); +#endif + } + _channels = sf_info.channels; + _samplerate = sf_info.samplerate; + + sf_close(snd_file); + } else { +#ifdef DEBUG_LMMS + qDebug( "SampleBuffer::decodeSampleSF(): could not load " + "sample %s: %s", _f, sf_strerror( NULL ) ); +#endif + loadingWarning = QObject::tr("SoundFile: Could not load: %1").arg(sf_strerror(nullptr)); + isError = true; + } + f.close(); + + return vector; +} + + +#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(static_cast( _udata )->read((char *) _ptr, + _size * _n)); +} + + +int qfileSeekCallback(void *_udata, ogg_int64_t _offset, int _whence) { + auto *f = static_cast( _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( _udata ); + return 0; +} + + +long qfileTellCallback(void *_udata) { + return static_cast( _udata )->pos(); +} + + +internal::SampleBufferData::DataVector internal::FileDecoders::decodeSampleOGGVorbis(const QString &_f, + ch_cnt_t &_channels, + sample_rate_t &_samplerate, + QString &loadingWarning, + bool &isError) { + static ov_callbacks callbacks = + { + qfileReadCallback, + qfileSeekCallback, + qfileCloseCallback, + qfileTellCallback + }; + + OggVorbis_File vf; + + f_cnt_t frames = 0; + + auto *f = new QFile(_f); + if (!f->open(QFile::ReadOnly)) { + delete f; + isError = true; + return {}; + } + + 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; + + isError = true; + return {}; + } + + 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); + + auto _buf = new int_sample_t[total * _channels]; + int bitstream = 0; + long bytes_read = 0; + + do { + bytes_read = ov_read(&vf, (char *) &_buf[frames * _channels], + (total - frames) * _channels * + BYTES_PER_INT_SAMPLE, + isLittleEndian() ? 0 : 1, + BYTES_PER_INT_SAMPLE, 1, &bitstream); + if (bytes_read < 0) { + break; + } + frames += bytes_read / (_channels * BYTES_PER_INT_SAMPLE); + } while (bytes_read != 0 && bitstream == 0); + + ov_clear(&vf); + // if buffer isn't empty, convert it to float and write it down + + if (frames > 0) { + return convertIntToFloat(_buf, frames, _channels); + } else if (frames < 0) { + isError = true; + } + + return {}; +} + +#endif + +internal::SampleBufferData::DataVector +internal::FileDecoders::decodeSampleDS(const QString &_f, ch_cnt_t &_channels, + sample_rate_t &_samplerate, + QString &loadingWarning, + bool &isError) { + DrumSynth ds; + int_sample_t *_buf = nullptr; + f_cnt_t frames = ds.GetDSFileSamples(_f, _buf, _channels, _samplerate); + + if (frames > 0 && _buf != nullptr) { + return convertIntToFloat(_buf, frames, _channels); + } + + isError = true; + return {}; +} + +internal::SampleBufferData +internal::SampleBufferFileHelper::Load(internal::SampleBufferFileHelper::FileName fileName, bool ignoreError) { + fileName = tryToMakeRelative(fileName); + + sample_rate_t sampleRate = GetMixerSampleRate(); + if (fileName == "") + return {{}, sampleRate}; + + bool fileLoadError = false; + + ch_cnt_t channels = DEFAULT_CHANNELS; + + const QFileInfo fileInfo(fileName); + + SampleBufferData::DataVector fileData; + QString loadingWarning; + if (fileInfo.suffix() == "ogg") { + fileLoadError = false; + fileData = FileDecoders::decodeSampleOGGVorbis(fileName, channels, sampleRate, loadingWarning, + fileLoadError); + } + for (auto function : fileDecodingFunctions) { + fileData = function(fileName, channels, sampleRate, loadingWarning, fileLoadError); + if (!fileLoadError) + break; + } + + if (fileLoadError) { + if (!ignoreError) { + QString title = QObject::tr("Fail to open file"); + QString message = QObject::tr("No message"); + if (!loadingWarning.isEmpty()) + message = loadingWarning; + if (gui) { + QMessageBox::information(nullptr, + title, message, QMessageBox::Warning); + } else { + fprintf(stderr, "%s\n", message.toUtf8().constData()); + } + } + + return {{}, GetMixerSampleRate()}; + } else { + return {std::move(fileData), sampleRate}; + } +} + +QString internal::SampleBufferFileHelper::tryToMakeRelative(const QString &file) { + if (!QFileInfo(file).isRelative()) { + // Normalize the path + QString f(QDir::cleanPath(file)); + + // First, look in factory samples + // Isolate "samples/" from "data:/samples/" + QString samplesSuffix = ConfigManager::inst()->factorySamplesDir().mid( + ConfigManager::inst()->dataDir().length()); + + // Iterate over all valid "data:/" searchPaths + for (const QString &path : QDir::searchPaths("data")) { + QString samplesPath = QDir::cleanPath(path + samplesSuffix) + "/"; + if (f.startsWith(samplesPath)) { + return QString(f).mid(samplesPath.length()); + } + } + + // Next, look in user samples + QString usd = ConfigManager::inst()->userSamplesDir(); + usd.replace(QDir::separator(), '/'); + if (f.startsWith(usd)) { + return QString(f).mid(usd.length()); + } + } + return file; +} + +internal::SampleBufferData::DataVector +internal::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; + SampleBufferData::DataVector vector(_frames); + const int ch = (_channels > 1) ? 1 : 0; + + int idx = 0; + for (f_cnt_t frame = 0; frame < _frames; + ++frame) { + vector[frame][0] = _ibuf[idx + 0] * fac; + vector[frame][1] = _ibuf[idx + ch] * fac; + idx += _channels; + } + + delete[] _ibuf; + + return vector; +} diff --git a/src/gui/widgets/EnvelopeAndLfoView.cpp b/src/gui/widgets/EnvelopeAndLfoView.cpp index cdad83d4fe7..7b8a2d57100 100644 --- a/src/gui/widgets/EnvelopeAndLfoView.cpp +++ b/src/gui/widgets/EnvelopeAndLfoView.cpp @@ -443,7 +443,6 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) } // userWaveSample() may be used, called out of loop for efficiency - m_params->m_userWave.dataReadLock(); float old_y = 0; for( int x = 0; x <= LFO_GRAPH_W; ++x ) { @@ -494,7 +493,6 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) graph_y_base + cur_y ) ); old_y = cur_y; } - m_params->m_userWave.dataUnlock(); p.setPen( QColor( 201, 201, 225 ) ); int ms_per_osc = static_cast( SECS_PER_LFO_OSCILLATION * diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index 482e739a0d0..0a41ac1bdad 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -589,13 +589,11 @@ QString graphModel::setWaveToUser() QString fileName = sampleBuffer->openAndSetWaveformFile(); if( fileName.isEmpty() == false ) { - sampleBuffer->dataReadLock(); for( int i = 0; i < length(); i++ ) { m_samples[i] = sampleBuffer->userWaveSample( i / static_cast( length() ) ); } - sampleBuffer->dataUnlock(); } emit samplesChanged( 0, length() - 1 ); diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index e2b65ce4757..2d9dec89b86 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -255,7 +255,7 @@ void SampleTCO::loadSettings( const QDomElement & _this ) // Should not be happening after 1.3. if (_this.hasAttribute ("data") && _this.attribute ("data") != QString()) { qWarning("Using default sampleRate. That could lead to invalid values1"); - m_sampleBuffer->loadFromBase64 (_this.attribute ("data"), + *m_sampleBuffer = SampleBuffer(_this.attribute ("data"), Engine::mixer ()->baseSampleRate ()); } else { m_sampleBuffer->restoreState (_this.firstChildElement (m_sampleBuffer->nodeName ())); From d65c1d0f1fb14c90b82397b99309e18c3011db8c Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Tue, 7 May 2019 09:04:34 +0300 Subject: [PATCH 095/112] Oscillator: include lmms_math.h. --- include/Oscillator.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/Oscillator.h b/include/Oscillator.h index 408e69dbc6c..c4dd130aa90 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -35,6 +35,7 @@ #include "SampleBuffer.h" #include "lmms_constants.h" +#include "lmms_math.h" class IntModel; From 00acb14718e8360e0d67a7a7e9352992e503a94c Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 07:58:34 +0300 Subject: [PATCH 096/112] Introduce Threading.h and runAsync: std::async using QtConcurrent. --- CMakeLists.txt | 2 +- include/Threading.h | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 include/Threading.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 396ce83fed0..a4840855e47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,7 @@ CHECK_INCLUDE_FILES(locale.h LMMS_HAVE_LOCALE_H) LIST(APPEND CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}") -FIND_PACKAGE(Qt5 COMPONENTS Core Gui Widgets Xml REQUIRED) +FIND_PACKAGE(Qt5 COMPONENTS Core Gui Widgets Xml Concurrent REQUIRED) FIND_PACKAGE(Qt5 COMPONENTS LinguistTools QUIET) INCLUDE_DIRECTORIES( diff --git a/include/Threading.h b/include/Threading.h new file mode 100644 index 00000000000..48f7ac18a98 --- /dev/null +++ b/include/Threading.h @@ -0,0 +1,11 @@ +#ifndef THREADING_H +#define THREADING_H + +#include + +template +inline auto runAsync(Callable &&callable) -> QFuture { + return QtConcurrent::run(std::forward(callable)); +} + +#endif // THREADING_H From aac56003fb7e614d87db11367edf915e0179591e Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 08:06:49 +0300 Subject: [PATCH 097/112] Introduce UpdatingValue: thread-safe automatically updated value. --- include/MemoryUtils.h | 36 +++++++ include/UpdatingValue.h | 139 +++++++++++++++++++++++++++ src/core/MemoryUtils.cpp | 9 ++ tests/CMakeLists.txt | 2 + tests/src/core/UpdatingValueTest.cpp | 109 +++++++++++++++++++++ 5 files changed, 295 insertions(+) create mode 100644 include/MemoryUtils.h create mode 100644 include/UpdatingValue.h create mode 100644 src/core/MemoryUtils.cpp create mode 100644 tests/src/core/UpdatingValueTest.cpp diff --git a/include/MemoryUtils.h b/include/MemoryUtils.h new file mode 100644 index 00000000000..1c9bf3aff28 --- /dev/null +++ b/include/MemoryUtils.h @@ -0,0 +1,36 @@ +#ifndef MEMORYUTILS_H +#define MEMORYUTILS_H + +#include + +#include + +namespace internal{ + void qobjectDeleter(QObject *object); + + struct QObjectDeleter { + void operator () (QObject *object) + { + object->deleteLater(); + } + }; +} + +template +std::shared_ptr +makeSharedQObject(Args&&...args) +{ + return std::shared_ptr(new T(std::forward(args)...), internal::qobjectDeleter); +} + +template +using UniqueQObjectPointer=std::unique_ptr; + +template +UniqueQObjectPointer +makeUniqueQObject(Args&&...args) +{ + return UniqueQObjectPointer(new T(std::forward(args)...), internal::QObjectDeleter{}); +} + +#endif // MEMORYUTILS_H diff --git a/include/UpdatingValue.h b/include/UpdatingValue.h new file mode 100644 index 00000000000..7fc3a0dba73 --- /dev/null +++ b/include/UpdatingValue.h @@ -0,0 +1,139 @@ +#ifndef UPDATINGVALUE_HPP +#define UPDATINGVALUE_HPP + +#include + +#include "MemoryUtils.h" + +namespace internal { + class SignalArgument { + protected: + virtual ~SignalArgument() {} + }; +} + +Q_DECLARE_METATYPE(std::shared_ptr); + + +namespace internal { + class UpdatingValueNotifier_Untyped : public QObject { + Q_OBJECT + + signals: + + /** + * @brief Notify the listeners (UpdatingValue instances) that we've + * changed the value. + */ + void rawOnValueUpdated(std::shared_ptr value); + }; + + class UpdatingValue_Untyped : public QObject { + Q_OBJECT + + protected: + UpdatingValue_Untyped(UpdatingValueNotifier_Untyped ¬ifier, QObject *parent) + : QObject{parent} { + // Run in the parents thread. + this->moveToThread(parent->thread()); + + qRegisterMetaType>(); + + // Notify us about changes. + auto connection = QObject::connect( + ¬ifier, + &UpdatingValueNotifier_Untyped::rawOnValueUpdated, + this, + &UpdatingValue_Untyped::onValueUpdated); + } + + virtual void onValueUpdated(std::shared_ptr value) = 0; + }; + + template + class UpdatingValueImpl : public internal::UpdatingValue_Untyped { + public: + /** + * @brief Sender of updates for this UpdatingValue. + */ + class Notifier : public internal::UpdatingValueNotifier_Untyped { + friend class UpdatingValueImpl; + + struct TypedSignalArgument : public SignalArgument { + TypedSignalArgument(const T &value) + : value{value} { + } + + const T value; + }; + + public: + void onValueUpdated(const T &newValue) { + auto newArgument = std::make_shared(newValue); + emit rawOnValueUpdated(newArgument); + } + }; + + using TypedSignalArugment=typename Notifier::TypedSignalArgument; + + + UpdatingValueImpl(Notifier ¬ifier, const T &initialValue, QObject *parent) + : UpdatingValue_Untyped{notifier, parent}, + m_ourCopy{std::make_shared(std::move(initialValue))} { + } + + + const T *operator->() const { + return &(get()); + } + + const T &operator*() const { + return get(); + } + + const T &get() const { + return m_ourCopy->value; + } + + private: + std::shared_ptr m_ourCopy; + + private: + void onValueUpdated(std::shared_ptr value) override final { + m_ourCopy = std::static_pointer_cast(std::move(value)); + } + }; +} // namespace internal + +/** + UpdatingValue is a lockless method of getting info + from core components. + + Instead of locking for read and reading the value, + we'll get notified when we have any change in the value. + */ +template +class UpdatingValue { + typedef internal::UpdatingValueImpl Impl; + +public: + typedef typename Impl::Notifier Notifier; + + UpdatingValue(Notifier ¬ifier, const T &initialValue, QObject *parent) + : m_updatingValueImpl{makeUniqueQObject(notifier, initialValue, parent)} { + } + + const T *operator->() const { + return &(this->operator*()); + } + + const T &operator*() const { + return m_updatingValueImpl->get(); + } + +private: + UniqueQObjectPointer> m_updatingValueImpl; +}; + + +#endif // UPDATINGVALUE_HPP diff --git a/src/core/MemoryUtils.cpp b/src/core/MemoryUtils.cpp new file mode 100644 index 00000000000..e2d78cfc850 --- /dev/null +++ b/src/core/MemoryUtils.cpp @@ -0,0 +1,9 @@ +#include "MemoryUtils.h" + +namespace internal { + void qobjectDeleter(QObject *object) + { + object->deleteLater(); + } + +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ddebe116c6e..d156d13ad05 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,8 @@ ADD_EXECUTABLE(tests src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp + src/core/UpdatingValueTest.cpp + src/tracks/AutomationTrackTest.cpp ) TARGET_COMPILE_DEFINITIONS(tests diff --git a/tests/src/core/UpdatingValueTest.cpp b/tests/src/core/UpdatingValueTest.cpp new file mode 100644 index 00000000000..e0457748224 --- /dev/null +++ b/tests/src/core/UpdatingValueTest.cpp @@ -0,0 +1,109 @@ +#include "QTestSuite.h" + +#include + +#include "UpdatingValue.h" +#include "Threading.h" +typedef UpdatingValue Value; + +class _QThreadUpdatingValueTest : public QObject +{ + Q_OBJECT + +public: + _QThreadUpdatingValueTest(Value::Notifier *notifier) + : notifier{notifier} + { + } + +public slots: + void emitNotifier() { + notifier->onValueUpdated(1); + } + +private: + Value::Notifier *notifier; +}; + +class UpdatingValueTest : QTestSuite +{ + Q_OBJECT + + + + std::unique_ptr createNotifierOnExternalThread() { + auto future = runAsync([] { + return new Value::Notifier(); + }); + + future.waitForFinished(); + + std::unique_ptr ptr; + ptr.reset(future.result()); + + return ptr; + } +private slots: + + void SameThreadUpdatingValueTest() { + Value::Notifier notifier; + Value value{notifier, 0, this}; + Value value2{notifier, 0, this}; + + QCOMPARE(*value, 0); + QCOMPARE(*value2, 0); + + notifier.onValueUpdated(1); + QCOMPARE(*value, 1); + QCOMPARE(*value2, 1); + } + + void ExternalThreadUpdatingValueTest() { + auto notifier = createNotifierOnExternalThread(); + Value value{*notifier, 0, this}; + Value value2{*notifier, 0, this}; + + runAsync([¬ifier] { + notifier->onValueUpdated(1); + }).waitForFinished(); + + QCOMPARE(*value, 1); + QCOMPARE(*value2, 1); + } + void QThreadUpdatingValueTest() { + // First of all, create the notifier on another thread. + auto notifier = createNotifierOnExternalThread(); + + Value value{*notifier, 0, this}; + Value value2{*notifier, 0, this}; + + QThread *thread = new QThread; + + QObject *emittingThreadObject = new _QThreadUpdatingValueTest(notifier.get()); + emittingThreadObject->moveToThread(thread); + + QSignalSpy valueChangedSpy{notifier.get(), &internal::UpdatingValueNotifier_Untyped::rawOnValueUpdated}; + + thread->start(); + + // Invoke emitNotifier on the objects thread. + QMetaObject::invokeMethod(emittingThreadObject, + "emitNotifier"); + + + // Wait for the signal to come. + QVERIFY(valueChangedSpy.wait()); + + thread->quit(); + thread->wait(); + + QCOMPARE(*value, 1); + QCOMPARE(*value2, 1); + + delete thread; + delete emittingThreadObject; + + } +} updatingValueTest; + +#include "UpdatingValueTest.moc" From 4082d786962c23f9c9942dadd69d8763a41c50af Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 08:13:12 +0300 Subject: [PATCH 098/112] Mixer: Add runWhileNotRendering helper. --- include/Mixer.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/include/Mixer.h b/include/Mixer.h index cb32f14fdc6..06200706f14 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -360,14 +360,11 @@ class LMMS_EXPORT Mixer : public QObject return RequestChangesGuard{this}; } - - /** - * Sets the current thread as a rendering thread, - * it means that call to requestChangeInModel will - * be ignored in order to avoid a dead lock. - */ - static void setCurrentThreadAsRendering(); - + template()())> + ReturnT runWhileNotRendering(Callable &&callable) { + auto guard = this->requestChangesGuard(); + return callable(); + } static bool isAudioDevNameValid(QString name); static bool isMidiDevNameValid(QString name); From 67186e28c83ba982987dd34abe07b40e9a93d8e1 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 08:15:19 +0300 Subject: [PATCH 099/112] Engine: Delete the mixer last in order to be able to use requestChanges while exiting. --- src/core/Engine.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index 7f81c3f9395..94469c9972b 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -92,8 +92,7 @@ void LmmsCore::destroy() deleteHelper( &s_bbTrackContainer ); deleteHelper( &s_dummyTC ); - deleteHelper( &s_fxMixer ); - deleteHelper( &s_mixer ); + deleteHelper(&s_fxMixer); deleteHelper( &s_ladspaManager ); @@ -103,6 +102,8 @@ void LmmsCore::destroy() deleteHelper( &s_song ); delete ConfigManager::inst(); + + deleteHelper(&s_mixer); } float LmmsCore::framesPerTick(sample_rate_t sample_rate) { From 6f36515a868f92db92302eca7630aa6cceb032b5 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 10:10:28 +0300 Subject: [PATCH 100/112] SampleBufferPlayInfo: Add m_maybeAudiofile. --- include/internal/SampleBufferPlayInfo.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/include/internal/SampleBufferPlayInfo.h b/include/internal/SampleBufferPlayInfo.h index 325c72cc64e..ad2fe6382bc 100644 --- a/include/internal/SampleBufferPlayInfo.h +++ b/include/internal/SampleBufferPlayInfo.h @@ -58,12 +58,23 @@ namespace internal { m_amplification = amplification; } + const QString &getMaybeAudioFile() const + { + return m_maybeAudioFile; + } + + void setMaybeAudioFile(const QString &maybeAudioFile) + { + m_maybeAudioFile = maybeAudioFile; + } + private: f_cnt_t m_startFrame = 0; f_cnt_t m_endFrame = 0; f_cnt_t m_loopStartFrame = 0; f_cnt_t m_loopEndFrame = 0; float m_amplification = 1.0f; + QString m_maybeAudioFile; }; } From 0f8c6d67dea148aa77e040f9587bf479391d20f1 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 10:46:07 +0300 Subject: [PATCH 101/112] Track::createTCO: avoid creating tcos while rendering. This commit renames createTCO to unsafeCreateTCO and add a mixer guard on the new createTCO. --- include/AutomationTrack.h | 2 +- include/BBTrack.h | 2 +- include/EnvelopeAndLfoParameters.h | 1 + include/InstrumentTrack.h | 2 +- include/SampleTrack.h | 9 ++++----- include/Track.h | 4 +++- src/tracks/AutomationTrack.cpp | 2 +- src/tracks/BBTrack.cpp | 2 +- src/tracks/InstrumentTrack.cpp | 7 ++----- src/tracks/SampleTrack.cpp | 9 +++------ 10 files changed, 18 insertions(+), 22 deletions(-) diff --git a/include/AutomationTrack.h b/include/AutomationTrack.h index 89f0fa5deef..5e3ff596354 100644 --- a/include/AutomationTrack.h +++ b/include/AutomationTrack.h @@ -46,7 +46,7 @@ class AutomationTrack : public Track } virtual TrackView * createView( TrackContainerView* ); - virtual TrackContentObject * createTCO( const MidiTime & _pos ); + virtual TrackContentObject * unsafeCreateTCO( const MidiTime & _pos ); virtual void saveTrackSpecificSettings( QDomDocument & _doc, QDomElement & _parent ); diff --git a/include/BBTrack.h b/include/BBTrack.h index a906b54d259..b63cac45791 100644 --- a/include/BBTrack.h +++ b/include/BBTrack.h @@ -135,7 +135,7 @@ class LMMS_EXPORT BBTrack : public Track virtual bool play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _frame_base, int _tco_num = -1 ); virtual TrackView * createView( TrackContainerView* tcv ); - virtual TrackContentObject * createTCO( const MidiTime & _pos ); + virtual TrackContentObject * unsafeCreateTCO(const MidiTime &_pos); virtual void saveTrackSpecificSettings( QDomDocument & _doc, QDomElement & _parent ); diff --git a/include/EnvelopeAndLfoParameters.h b/include/EnvelopeAndLfoParameters.h index 4824062f3be..ffeebdbd94e 100644 --- a/include/EnvelopeAndLfoParameters.h +++ b/include/EnvelopeAndLfoParameters.h @@ -163,6 +163,7 @@ public slots: sample_t m_random; bool m_bad_lfoShapeData; SampleBuffer m_userWave; + SampleBuffer::InfoUpdatingValue m_userWaveInfo; enum LfoShapes { diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 27109e07607..3a394ce8b32 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -136,7 +136,7 @@ class LMMS_EXPORT InstrumentTrack : public Track, public MidiEventProcessor virtual TrackView * createView( TrackContainerView* tcv ); // create new track-content-object = pattern - virtual TrackContentObject * createTCO( const MidiTime & _pos ); + virtual TrackContentObject * unsafeCreateTCO(const MidiTime &_pos) override ; // called by track diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 40b6c93472a..34812b3bf61 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -149,11 +149,10 @@ class SampleTrack : public Track SampleTrack( TrackContainer* tc ); virtual ~SampleTrack(); - - virtual bool play( const MidiTime & _start, const fpp_t _frames, - const f_cnt_t _frame_base, int _tco_num = -1 ); - virtual TrackView * createView( TrackContainerView* tcv ); - virtual TrackContentObject * createTCO( const MidiTime & _pos ); + virtual bool play(const MidiTime &_start, const fpp_t _frames, + const f_cnt_t _frame_base, int _tco_num = -1); + virtual TrackView *createView(TrackContainerView *tcv); + virtual TrackContentObject *unsafeCreateTCO(const MidiTime &); virtual void saveTrackSpecificSettings( QDomDocument & _doc, diff --git a/include/Track.h b/include/Track.h index 51807b9e039..ceaebd014d3 100644 --- a/include/Track.h +++ b/include/Track.h @@ -513,7 +513,9 @@ class LMMS_EXPORT Track : public Model, public JournallingObject virtual TrackView * createView( TrackContainerView * view ) = 0; - virtual TrackContentObject * createTCO( const MidiTime & pos ) = 0; + virtual TrackContentObject * unsafeCreateTCO( const MidiTime & pos ) = 0; + + TrackContentObject * createTCO(const MidiTime &pos); virtual void saveTrackSpecificSettings( QDomDocument & doc, QDomElement & parent ) = 0; diff --git a/src/tracks/AutomationTrack.cpp b/src/tracks/AutomationTrack.cpp index 2ea170bb89f..5eadcb74d32 100644 --- a/src/tracks/AutomationTrack.cpp +++ b/src/tracks/AutomationTrack.cpp @@ -58,7 +58,7 @@ TrackView * AutomationTrack::createView( TrackContainerView* tcv ) -TrackContentObject * AutomationTrack::createTCO( const MidiTime & ) +TrackContentObject * AutomationTrack::unsafeCreateTCO( const MidiTime & ) { return new AutomationPattern( this ); } diff --git a/src/tracks/BBTrack.cpp b/src/tracks/BBTrack.cpp index 205a22087f8..aac6f44464a 100644 --- a/src/tracks/BBTrack.cpp +++ b/src/tracks/BBTrack.cpp @@ -474,7 +474,7 @@ TrackView * BBTrack::createView( TrackContainerView* tcv ) -TrackContentObject * BBTrack::createTCO( const MidiTime & _pos ) +TrackContentObject * BBTrack::unsafeCreateTCO(const MidiTime &_pos) { BBTCO * bbtco = new BBTCO( this ); if( s_lastTCOColor ) diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 977700bd27b..da4e716f7e1 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -707,11 +707,8 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, } - - -TrackContentObject * InstrumentTrack::createTCO( const MidiTime & ) -{ - return new Pattern( this ); +TrackContentObject *InstrumentTrack::unsafeCreateTCO(const MidiTime &) { + return new Pattern(this); } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 2d9dec89b86..7512c62ee3b 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -1,4 +1,4 @@ -/* +/* * SampleTrack.cpp - implementation of class SampleTrack, a track which * provides arrangement of samples * @@ -684,11 +684,8 @@ TrackView * SampleTrack::createView( TrackContainerView* tcv ) } - - -TrackContentObject * SampleTrack::createTCO( const MidiTime & ) -{ - return new SampleTCO( this ); +TrackContentObject *SampleTrack::unsafeCreateTCO(const MidiTime &) { + return new SampleTCO(this); } From 855b0708c49267370d8520644448a89cdcac45ce Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 20:34:21 +0300 Subject: [PATCH 102/112] SampleBuffer: Make setX functions async, remove get functions and add UpdatingVariable for SampleBuffer's info. --- include/LfoController.h | 1 + include/SamplePlayHandle.h | 2 +- include/SampleTrack.h | 9 +- .../audio_file_processor.cpp | 109 ++++++++-------- .../audio_file_processor.h | 7 +- plugins/patman/patman.cpp | 22 ++-- plugins/patman/patman.h | 15 ++- .../triple_oscillator/TripleOscillator.cpp | 3 +- plugins/triple_oscillator/TripleOscillator.h | 1 + src/core/CMakeLists.txt | 2 + src/core/EnvelopeAndLfoParameters.cpp | 5 +- src/core/LfoController.cpp | 5 +- src/core/SampleBuffer.cpp | 118 ++++++++---------- src/core/SamplePlayHandle.cpp | 5 +- src/gui/LfoControllerDialog.cpp | 6 +- src/gui/widgets/EnvelopeAndLfoView.cpp | 2 +- src/tracks/SampleTrack.cpp | 56 ++++----- 17 files changed, 185 insertions(+), 183 deletions(-) diff --git a/include/LfoController.h b/include/LfoController.h index 9dfbba6715a..432af1897b9 100644 --- a/include/LfoController.h +++ b/include/LfoController.h @@ -77,6 +77,7 @@ public slots: private: SampleBuffer * m_userDefSampleBuffer; + SampleBuffer::InfoUpdatingValue m_userDefSampleBufferInfo; protected slots: void updatePhase(); diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index cb31f4b8c6a..f167db3a58a 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -41,7 +41,6 @@ class AudioPort; class SamplePlayHandle : public PlayHandle { public: - SamplePlayHandle( SampleBuffer* sampleBuffer , bool ownAudioPort = true ); SamplePlayHandle( const std::shared_ptr &sampleBuffer, bool ownAudioPort = true ); SamplePlayHandle( const QString& sampleFile ); SamplePlayHandle( SampleTCO* tco ); @@ -81,6 +80,7 @@ class SamplePlayHandle : public PlayHandle private: std::shared_ptr m_sampleBuffer; + SampleBuffer::SampleBufferInfo m_sampleBufferInfo; bool m_doneMayReturnTrue; f_cnt_t m_frame; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 34812b3bf61..1331da36ba5 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -60,15 +60,13 @@ class SampleTCO : public TrackContentObject return "sampletco"; } - const std::shared_ptr sampleBuffer() - { + std::shared_ptr sampleBuffer() { return m_sampleBuffer; } MidiTime sampleLength() const; - void setSampleStartFrame( f_cnt_t startFrame ); - void setSamplePlayLength( f_cnt_t length ); - virtual TrackContentObjectView * createView( TrackView * _tv ); + + virtual TrackContentObjectView *createView(TrackView *_tv); bool isPlaying() const; @@ -90,6 +88,7 @@ private slots: private: std::shared_ptr m_sampleBuffer; + SampleBuffer::InfoUpdatingValue m_sampleBufferInfo; BoolModel m_recordModel; bool m_isPlaying; diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 918d410dc74..71d057b2cbc 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -74,6 +74,7 @@ Plugin::Descriptor PLUGIN_EXPORT audiofileprocessor_plugin_descriptor = audioFileProcessor::audioFileProcessor( InstrumentTrack * _instrument_track ) : Instrument( _instrument_track, &audiofileprocessor_plugin_descriptor ), m_sampleBuffer(), + m_sampleBufferInfo{m_sampleBuffer.createUpdatingValue(this)}, m_ampModel( 100, 0, 500, 1, this, tr( "Amplify" ) ), m_startPointModel( 0, 0, 1, 0.0000001f, this, tr( "Start of sample" ) ), m_endPointModel( 1, 0, 1, 0.0000001f, this, tr( "End of sample" ) ), @@ -128,18 +129,18 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, // played. if( m_stutterModel.value() == true && _n->frequency() < 20.0 ) { - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayStartPoint = m_sampleBufferInfo->startFrame; m_nextPlayBackwards = false; return; } if( !_n->m_pluginData ) { - if( m_stutterModel.value() == true && m_nextPlayStartPoint >= m_sampleBuffer.endFrame() ) + if( m_stutterModel.value() == true && m_nextPlayStartPoint >= m_sampleBufferInfo->endFrame ) { // 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_nextPlayStartPoint = m_sampleBufferInfo->startFrame; m_nextPlayBackwards = false; } // set interpolation mode for libsamplerate @@ -231,10 +232,10 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) { setAudioFile( _this.attribute( "src" ), false ); - QString absolutePath = m_sampleBuffer.tryToMakeAbsolute( m_sampleBuffer.audioFile() ); + QString absolutePath = m_sampleBuffer.tryToMakeAbsolute( m_sampleBufferInfo->audioFile ); if ( !QFileInfo( absolutePath ).exists() ) { - QString message = tr( "Sample not found: %1" ).arg( m_sampleBuffer.audioFile() ); + QString message = tr( "Sample not found: %1" ).arg( m_sampleBufferInfo->audioFile ); Engine::getSong()->collectError( message ); } @@ -305,7 +306,7 @@ int audioFileProcessor::getBeatLen( NotePlayHandle * _n ) const const float freq_factor = BaseFreq / _n->frequency() * Engine::mixer()->processingSampleRate() / Engine::mixer()->baseSampleRate(); - return static_cast( floorf( ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) * freq_factor ) ); + return static_cast( floorf( ( m_sampleBufferInfo->endFrame - m_sampleBufferInfo->startFrame ) * freq_factor ) ); } @@ -326,8 +327,8 @@ void audioFileProcessor::setAudioFile( const QString & _audio_file, // is current channel-name equal to previous-filename?? if( _rename && ( instrumentTrack()->name() == - QFileInfo( m_sampleBuffer.audioFile() ).fileName() || - m_sampleBuffer.audioFile().isEmpty() ) ) + QFileInfo( m_sampleBufferInfo->audioFile ).fileName() || + m_sampleBufferInfo->audioFile.isEmpty() ) ) { // then set it to new one instrumentTrack()->setName( QFileInfo( _audio_file).fileName() ); @@ -349,7 +350,7 @@ void audioFileProcessor::reverseModelChanged( void ) m_sampleBuffer.reverse (); } - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayStartPoint = m_sampleBufferInfo->startFrame; m_nextPlayBackwards = false; } @@ -364,7 +365,7 @@ void audioFileProcessor::ampModelChanged( void ) void audioFileProcessor::stutterModelChanged() { - m_nextPlayStartPoint = m_sampleBuffer.startFrame(); + m_nextPlayStartPoint = m_sampleBufferInfo->startFrame; m_nextPlayBackwards = false; } @@ -433,9 +434,9 @@ void audioFileProcessor::loopPointChanged( void ) void audioFileProcessor::pointChanged( void ) { - const f_cnt_t f_start = static_cast( m_startPointModel.value() * ( m_sampleBuffer.frames()-1 ) ); - const f_cnt_t f_end = static_cast( m_endPointModel.value() * ( m_sampleBuffer.frames()-1 ) ); - const f_cnt_t f_loop = static_cast( m_loopPointModel.value() * ( m_sampleBuffer.frames()-1 ) ); + const f_cnt_t f_start = static_cast( m_startPointModel.value() * ( m_sampleBufferInfo->frames-1 ) ); + const f_cnt_t f_end = static_cast( m_endPointModel.value() * ( m_sampleBufferInfo->frames-1 ) ); + const f_cnt_t f_loop = static_cast( m_loopPointModel.value() * ( m_sampleBufferInfo->frames-1 ) ); m_nextPlayStartPoint = f_start; m_nextPlayBackwards = false; @@ -607,7 +608,9 @@ void AudioFileProcessorView::newWaveView() delete m_waveView; m_waveView = 0; } - m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer, castModel()); + m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer, + castModel()->m_sampleBufferInfo, + castModel()); m_waveView->move( 2, 172 ); m_waveView->setKnobs( dynamic_cast( m_startKnob ), @@ -653,7 +656,7 @@ void AudioFileProcessorView::paintEvent( QPaintEvent * ) audioFileProcessor * a = castModel(); QString file_name = ""; - int idx = a->m_sampleBuffer.audioFile().length(); + int idx = a->m_sampleBufferInfo->audioFile.length(); p.setFont( pointSize<8>( font() ) ); @@ -664,7 +667,7 @@ void AudioFileProcessorView::paintEvent( QPaintEvent * ) while( idx > 0 && fm.size( Qt::TextSingleLine, file_name + "..." ).width() < 210 ) { - file_name = a->m_sampleBuffer.audioFile()[--idx] + file_name; + file_name = a->m_sampleBufferInfo->audioFile[--idx] + file_name; } if( idx > 0 ) @@ -726,22 +729,24 @@ void AudioFileProcessorView::modelChanged( void ) void AudioFileProcessorWaveView::updateSampleRange() { - if( m_sampleBuffer.frames() > 1 ) + if( m_sampleBufferInfo->frames > 1 ) { - const f_cnt_t marging = ( m_sampleBuffer.endFrame() - m_sampleBuffer.startFrame() ) * 0.1; - m_from = qMax( 0, m_sampleBuffer.startFrame() - marging ); - m_to = qMin( m_sampleBuffer.endFrame() + marging, m_sampleBuffer.frames() ); + const f_cnt_t marging = ( m_sampleBufferInfo->endFrame - m_sampleBufferInfo->startFrame ) * 0.1; + m_from = qMax( 0, m_sampleBufferInfo->startFrame - marging ); + m_to = qMin( m_sampleBufferInfo->endFrame + marging, m_sampleBufferInfo->frames ); } } AudioFileProcessorWaveView::AudioFileProcessorWaveView(QWidget * _parent, int _w, int _h, SampleBuffer& buf , - audioFileProcessor *fileProcessor) : + SampleBuffer::InfoUpdatingValue &sampleBufferInfo, + audioFileProcessor *fileProcessor) : QWidget( _parent ), m_audioFileProcessor{fileProcessor}, m_sampleBuffer( buf ), + m_sampleBufferInfo(sampleBufferInfo), m_graph( QPixmap( _w - 2 * s_padding, _h - 2 * s_padding ) ), m_from( 0 ), - m_to( m_sampleBuffer.frames() ), + m_to( m_sampleBufferInfo->frames ), m_last_from( 0 ), m_last_to( 0 ), m_last_amp( 0 ), @@ -889,11 +894,11 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) 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 ) * + m_startFrameX = graph_rect.x() + ( m_sampleBufferInfo->startFrame - m_from ) * double( graph_rect.width() ) / frames; - m_endFrameX = graph_rect.x() + ( m_sampleBuffer.endFrame() - m_from ) * + m_endFrameX = graph_rect.x() + ( m_sampleBufferInfo->endFrame - m_from ) * double( graph_rect.width() ) / frames; - m_loopFrameX = graph_rect.x() + ( m_sampleBuffer.loopStartFrame() - m_from ) * + m_loopFrameX = graph_rect.x() + ( m_sampleBufferInfo->loopStartFrame - m_from ) * double( graph_rect.width() ) / frames; const int played_width_px = ( m_framesPlayed - m_from ) * double( graph_rect.width() ) / frames; @@ -968,7 +973,7 @@ void AudioFileProcessorWaveView::paintEvent( QPaintEvent * _pe ) p.setFont( pointSize<8>( font() ) ); QString length_text; - const int length = m_sampleBuffer.sampleLength(); + const int length = m_sampleBufferInfo->sampleLength(); if( length > 20000 ) { @@ -997,31 +1002,31 @@ void AudioFileProcessorWaveView::updateGraph() { if( m_to == 1 ) { - m_to = m_sampleBuffer.frames() * 0.7; + m_to = m_sampleBufferInfo->frames * 0.7; slideSamplePointToFrames( end, m_to * 0.7 ); } - if( m_from > m_sampleBuffer.startFrame() ) + if( m_from > m_sampleBufferInfo->startFrame ) { - m_from = m_sampleBuffer.startFrame(); + m_from = m_sampleBufferInfo->startFrame; } - if( m_to < m_sampleBuffer.endFrame() ) + if( m_to < m_sampleBufferInfo->endFrame ) { - m_to = m_sampleBuffer.endFrame(); + m_to = m_sampleBufferInfo->endFrame; } if(m_audioFileProcessor->isReversed () != m_reversed) { reverse(); } - else if( m_last_from == m_from && m_last_to == m_to && m_sampleBuffer.amplification() == m_last_amp ) + else if( m_last_from == m_from && m_last_to == m_to && m_sampleBufferInfo->amplification == m_last_amp ) { return; } m_last_from = m_from; m_last_to = m_to; - m_last_amp = m_sampleBuffer.amplification(); + m_last_amp = m_sampleBufferInfo->amplification; m_graph.fill( Qt::transparent ); QPainter p( &m_graph ); @@ -1039,9 +1044,9 @@ void AudioFileProcessorWaveView::updateGraph() void AudioFileProcessorWaveView::zoom( const bool _out ) { - const f_cnt_t start = m_sampleBuffer.startFrame(); - const f_cnt_t end = m_sampleBuffer.endFrame(); - const f_cnt_t frames = m_sampleBuffer.frames(); + const f_cnt_t start = m_sampleBufferInfo->startFrame; + const f_cnt_t end = m_sampleBufferInfo->endFrame; + const f_cnt_t frames = m_sampleBufferInfo->frames; const f_cnt_t d_from = start - m_from; const f_cnt_t d_to = m_to - end; @@ -1074,7 +1079,7 @@ void AudioFileProcessorWaveView::zoom( const bool _out ) ); } - if( double( new_to - new_from ) / m_sampleBuffer.sampleRate() > 0.05 ) + if( double( new_to - new_from ) / m_sampleBufferInfo->sampleRate > 0.05 ) { m_from = new_from; m_to = new_to; @@ -1093,8 +1098,8 @@ void AudioFileProcessorWaveView::slide( int _px ) step = -step; } - f_cnt_t step_from = qBound( 0, m_from + step, m_sampleBuffer.frames() ) - m_from; - f_cnt_t step_to = qBound( m_from + 1, m_to + step, m_sampleBuffer.frames() ) - m_to; + f_cnt_t step_from = qBound( 0, m_from + step, m_sampleBufferInfo->frames ) - m_from; + f_cnt_t step_to = qBound( m_from + 1, m_to + step, m_sampleBufferInfo->frames ) - m_to; step = qAbs( step_from ) < qAbs( step_to ) ? step_from : step_to; @@ -1155,7 +1160,7 @@ void AudioFileProcessorWaveView::slideSamplePointByFrames( knobType _point, f_cn } else { - const double v = static_cast( _frames ) / m_sampleBuffer.frames(); + const double v = static_cast( _frames ) / m_sampleBufferInfo->frames; if( _slide_to ) { a_knob->slideTo( v ); @@ -1172,11 +1177,11 @@ void AudioFileProcessorWaveView::slideSamplePointByFrames( knobType _point, f_cn void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) { - if( m_sampleBuffer.frames() <= 1 ) + if( m_sampleBufferInfo->frames <= 1 ) { return; } - const double v = static_cast( _frames ) / m_sampleBuffer.frames(); + const double v = static_cast( _frames ) / m_sampleBufferInfo->frames; if( m_startKnob ) { m_startKnob->slideBy( v, false ); } @@ -1194,14 +1199,14 @@ void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) void AudioFileProcessorWaveView::reverse() { slideSampleByFrames( - m_sampleBuffer.frames() - - m_sampleBuffer.endFrame() - - m_sampleBuffer.startFrame() + m_sampleBufferInfo->frames + - m_sampleBufferInfo->endFrame + - m_sampleBufferInfo->startFrame ); const f_cnt_t from = m_from; - m_from = m_sampleBuffer.frames() - m_to; - m_to = m_sampleBuffer.frames() - from; + m_from = m_sampleBufferInfo->frames - m_to; + m_to = m_sampleBufferInfo->frames - from; m_reversed = ! m_reversed; } @@ -1244,7 +1249,7 @@ float AudioFileProcessorWaveView::knob::getValue( const QPoint & _p ) { const double dec_fact = ! m_waveView ? 1 : double( m_waveView->m_to - m_waveView->m_from ) - / m_waveView->m_sampleBuffer.frames(); + / m_waveView->m_sampleBufferInfo->frames; const float inc = ::Knob::getValue( _p ) * dec_fact; return inc; @@ -1265,12 +1270,12 @@ bool AudioFileProcessorWaveView::knob::checkBound( double _v ) const return false; const double d1 = qAbs( m_relatedKnob->model()->value() - model()->value() ) - * ( m_waveView->m_sampleBuffer.frames() ) - / m_waveView->m_sampleBuffer.sampleRate(); + * ( m_waveView->m_sampleBufferInfo->frames ) + / m_waveView->m_sampleBufferInfo->sampleRate; const double d2 = qAbs( m_relatedKnob->model()->value() - _v ) - * ( m_waveView->m_sampleBuffer.frames() ) - / m_waveView->m_sampleBuffer.sampleRate(); + * ( m_waveView->m_sampleBufferInfo->frames ) + / m_waveView->m_sampleBufferInfo->sampleRate; return d1 < d2 || d2 > 0.005; } diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index ef213ed2498..b2d8a89b9b0 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -93,6 +93,7 @@ private slots: typedef SampleBuffer::handleState handleState; SampleBuffer m_sampleBuffer; + SampleBuffer::InfoUpdatingValue m_sampleBufferInfo; FloatModel m_ampModel; FloatModel m_startPointModel; @@ -167,6 +168,8 @@ protected slots: class AudioFileProcessorWaveView : public QWidget { Q_OBJECT + + protected: virtual void enterEvent( QEvent * _e ); virtual void leaveEvent( QEvent * _e ); @@ -250,6 +253,7 @@ public slots: audioFileProcessor *m_audioFileProcessor; SampleBuffer& m_sampleBuffer; + SampleBuffer::InfoUpdatingValue &m_sampleBufferInfo; QPixmap m_graph; f_cnt_t m_from; f_cnt_t m_to; @@ -270,7 +274,8 @@ public slots: bool m_animation; public: - AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf, audioFileProcessor *fileProcessor); + AudioFileProcessorWaveView(QWidget *_parent, int _w, int _h, SampleBuffer &buf, + SampleBuffer::InfoUpdatingValue &sampleBufferInfo, audioFileProcessor *fileProcessor); void setKnobs(knob *_start, knob *_end, knob *_loop ); diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index 00083579bfe..0f9f76a0684 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -148,7 +148,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n, handle_data * hdata = (handle_data *)_n->m_pluginData; float play_freq = hdata->tuned ? _n->frequency() : - hdata->sample->frequency(); + hdata->sampleInfo->frequency; if( hdata->sample->play( _working_buffer + offset, hdata->state, frames, play_freq, m_loopedModel.value() ? LoopOn : LoopOff ) ) @@ -365,7 +365,8 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( psample->setLoopEndFrame( loop_end ); } - m_patchSamples.push_back( std::move(psample) ); + auto info = psample->createUpdatingValue(this); + m_patchSamples.push_back( {std::move(psample), std::move(info)}); delete[] wave_samples; } @@ -393,27 +394,24 @@ void patmanInstrument::selectSample( NotePlayHandle * _n ) for( auto &element : m_patchSamples ) { - float patch_freq = element->frequency(); + float patch_freq = element.second->frequency; float dist = freq >= patch_freq ? freq / patch_freq : patch_freq / freq; if( dist < min_dist ) { min_dist = dist; - sample = element; + sample = element.first; } } - handle_data * hdata = new handle_data; - hdata->tuned = m_tunedModel.value(); - if( sample ) - { - hdata->sample = sample; - } - else + if( !sample ) { - hdata->sample = std::make_shared(); + sample = std::make_shared(); } + + handle_data * hdata = new handle_data(std::move(sample), this); + hdata->tuned = m_tunedModel.value(); hdata->state = new SampleBuffer::handleState( _n->hasDetuningInfo() ); _n->m_pluginData = hdata; diff --git a/plugins/patman/patman.h b/plugins/patman/patman.h index ed653b2058f..a614fb8f8f1 100644 --- a/plugins/patman/patman.h +++ b/plugins/patman/patman.h @@ -78,16 +78,23 @@ public slots: private: - typedef struct - { + struct handle_data { MM_OPERATORS + + handle_data(std::shared_ptr &&buffer, QObject *parent) + : sample{std::move(buffer)}, + sampleInfo(sample->createUpdatingValue(parent)) + { + } + SampleBuffer::handleState* state; bool tuned; std::shared_ptr sample; - } handle_data; + SampleBuffer::InfoUpdatingValue sampleInfo; + }; QString m_patchFile; - QVector> m_patchSamples; + std::vector, SampleBuffer::InfoUpdatingValue>> m_patchSamples; BoolModel m_loopedModel; BoolModel m_tunedModel; diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index 53c3d06ff97..49712ab91e0 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -89,6 +89,7 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : tr( "Modulation type %1" ).arg( _idx+1 ) ), m_sampleBuffer( new SampleBuffer ), + m_sampleBufferInfo(m_sampleBuffer->createUpdatingValue(this)), m_volumeLeft( 0.0f ), m_volumeRight( 0.0f ), m_detuningLeft( 0.0f ), @@ -254,7 +255,7 @@ void TripleOscillator::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_osc[i]->m_modulationAlgoModel.saveSettings( _doc, _this, "modalgo" + QString::number( i+1 ) ); _this.setAttribute( "userwavefile" + is, - m_osc[i]->m_sampleBuffer->audioFile() ); + m_osc[i]->m_sampleBufferInfo->audioFile ); } } diff --git a/plugins/triple_oscillator/TripleOscillator.h b/plugins/triple_oscillator/TripleOscillator.h index fb2b48a410b..773be8b4439 100644 --- a/plugins/triple_oscillator/TripleOscillator.h +++ b/plugins/triple_oscillator/TripleOscillator.h @@ -61,6 +61,7 @@ class OscillatorObject : public Model IntModel m_waveShapeModel; IntModel m_modulationAlgoModel; SampleBuffer* m_sampleBuffer; + SampleBuffer::InfoUpdatingValue m_sampleBufferInfo; float m_volumeLeft; float m_volumeRight; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 618453bc24f..fa441dfc396 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -104,6 +104,8 @@ set(LMMS_SRCS core/samplebuffer/SampleBufferData.cpp core/samplebuffer/SampleBufferFileHelper.cpp + core/MemoryUtils.cpp + PARENT_SCOPE ) diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index 27a73b2c1a5..70159ea093a 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -118,7 +118,8 @@ EnvelopeAndLfoParameters::EnvelopeAndLfoParameters( m_controlEnvAmountModel( false, this, tr( "Modulate env amount" ) ), m_lfoFrame( 0 ), m_lfoAmountIsZero( false ), - m_lfoShapeData( NULL ) + m_lfoShapeData( NULL ), + m_userWaveInfo(m_userWave.createUpdatingValue(this)) { m_amountModel.setCenterValue( 0 ); m_lfoAmountModel.setCenterValue( 0 ); @@ -354,7 +355,7 @@ void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc, m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" ); m_x100Model.saveSettings( _doc, _parent, "x100" ); m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" ); - _parent.setAttribute( "userwavefile", m_userWave.audioFile() ); + _parent.setAttribute( "userwavefile", m_userWaveInfo->audioFile ); } diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 87ab2fc4559..786404ce0c6 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -45,7 +45,8 @@ LfoController::LfoController( Model * _parent ) : m_phaseOffset( 0 ), m_currentPhase( 0 ), m_sampleFunction( &Oscillator::sinSample ), - m_userDefSampleBuffer( new SampleBuffer ) + m_userDefSampleBuffer( new SampleBuffer ), + m_userDefSampleBufferInfo{m_userDefSampleBuffer->createUpdatingValue(this)} { setSampleExact( true ); connect( &m_waveModel, SIGNAL( dataChanged() ), @@ -192,7 +193,7 @@ void LfoController::saveSettings( QDomDocument & _doc, QDomElement & _this ) m_phaseModel.saveSettings( _doc, _this, "phase" ); m_waveModel.saveSettings( _doc, _this, "wave" ); m_multiplierModel.saveSettings( _doc, _this, "multiplier" ); - _this.setAttribute( "userwavefile" , m_userDefSampleBuffer->audioFile() ); + _this.setAttribute( "userwavefile" , m_userDefSampleBufferInfo->audioFile); } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index f192909d01e..78b056802a0 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -31,12 +31,6 @@ #include #include #include - - -#define OV_EXCLUDE_STATIC_CALLBACKS -#ifdef LMMS_HAVE_OGGVORBIS -#endif - #include "base64.h" #include "ConfigManager.h" #include "DrumSynth.h" @@ -50,7 +44,7 @@ SampleBuffer::SampleBuffer(const QString &_audio_file, bool ignoreError) : SampleBuffer{internal::SampleBufferFileHelper::Load(_audio_file, ignoreError)} { - m_maybeFileName = _audio_file; + m_playInfo->setMaybeAudioFile(_audio_file); } @@ -82,9 +76,10 @@ void SampleBuffer::saveSettings(QDomDocument &doc, QDomElement &_this) { _this.setAttribute("data", toBase64(string)); } - _this.setAttribute("sampleRate", sampleRate()); - _this.setAttribute("amplification", amplification()); - _this.setAttribute("frequency", frequency()); + auto data = guardedData(); + _this.setAttribute("sampleRate", data->getSampleRate()); + _this.setAttribute("amplification", data->getAmplification()); + _this.setAttribute("frequency", data->getFrequency()); } void SampleBuffer::loadSettings(const QDomElement &_this) { @@ -97,15 +92,15 @@ void SampleBuffer::loadSettings(const QDomElement &_this) { } if (_this.hasAttribute("data")) { - m_data = internal::SampleBufferData::loadFromBase64(_this.attribute("data"), loadingSampleRate); + *m_data = internal::SampleBufferData::loadFromBase64(_this.attribute("data"), loadingSampleRate); } if (_this.hasAttribute("amplification")) { - m_data.setAmplification(_this.attribute("amplification").toFloat()); + m_data->setAmplification(_this.attribute("amplification").toFloat()); } if (_this.hasAttribute("frequency")) { - m_data.setFrequency(_this.attribute("frequency").toFloat()); + m_data->setFrequency(_this.attribute("frequency").toFloat()); } } @@ -113,10 +108,10 @@ bool SampleBuffer::play(sampleFrame *_ab, handleState *_state, const fpp_t _frames, const float _freq, const LoopMode _loopmode) { - f_cnt_t startFrame = m_playInfo.getStartFrame(); - f_cnt_t endFrame = m_playInfo.getEndFrame(); - f_cnt_t loopStartFrame = m_playInfo.getLoopStartFrame(); - f_cnt_t loopEndFrame = m_playInfo.getLoopEndFrame(); + f_cnt_t startFrame = m_playInfo->getStartFrame(); + f_cnt_t endFrame = m_playInfo->getEndFrame(); + f_cnt_t loopStartFrame = m_playInfo->getLoopStartFrame(); + f_cnt_t loopEndFrame = m_playInfo->getLoopEndFrame(); if (endFrame == 0 || _frames == 0) { return false; @@ -125,8 +120,8 @@ bool SampleBuffer::play(sampleFrame *_ab, handleState *_state, // 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_data.getFrequency() * - double(m_data.getSampleRate()) / double(Engine::mixer()->processingSampleRate()); + const double freq_factor = (double) _freq / (double) m_data->getFrequency() * + double(m_data->getSampleRate()) / double(Engine::mixer()->processingSampleRate()); // calculate how many frames we have in requested pitch const auto total_frames_for_current_pitch = static_cast((endFrame - startFrame) / @@ -161,8 +156,8 @@ bool SampleBuffer::play(sampleFrame *_ab, handleState *_state, SRC_DATA src_data; // Generate output src_data.data_in = - libSampleRateSrc(m_data.getSampleFragment(play_frame, fragment_size, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame))->data(); + libSampleRateSrc(m_data->getSampleFragment(play_frame, fragment_size, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame))->data(); src_data.data_out = _ab->data(); src_data.input_frames = fragment_size; src_data.output_frames = _frames; @@ -185,10 +180,9 @@ bool SampleBuffer::play(sampleFrame *_ab, handleState *_state, // as is into pitched-copy-buffer // Generate output - // TODO: make a new function: fillSampleFragment for this case. memcpy(_ab, - m_data.getSampleFragment(play_frame, _frames, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame), + m_data->getSampleFragment(play_frame, _frames, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame), _frames * BYTES_PER_FRAME); usedFrames = _frames; } @@ -224,10 +218,9 @@ bool SampleBuffer::play(sampleFrame *_ab, handleState *_state, _state->setBackwards(is_backwards); _state->setFrameIndex(play_frame); - // TODO: move that to another function. for (fpp_t i = 0; i < _frames; ++i) { - _ab[i][0] *= m_playInfo.getAmplification(); - _ab[i][1] *= m_playInfo.getAmplification(); + _ab[i][0] *= m_data->getAmplification(); + _ab[i][1] *= m_data->getAmplification(); } return true; @@ -311,17 +304,17 @@ std::pair SampleBuffer::visualizeToPoly(const QRect &_dr, } -QString SampleBuffer::openAudioFile() const { +QString SampleBuffer::openAudioFile(const QString ¤tAudioFile) { FileDialog ofd(NULL, tr("Open audio file")); QString dir; - if (!m_maybeFileName.isEmpty()) { - QString f = m_maybeFileName; + if (!currentAudioFile.isEmpty()) { + QString f = currentAudioFile; if (QFileInfo(f).isRelative()) { f = ConfigManager::inst()->userSamplesDir() + f; if (QFileInfo(f).exists() == false) { f = ConfigManager::inst()->factorySamplesDir() + - m_maybeFileName; + currentAudioFile; } } dir = QFileInfo(f).absolutePath(); @@ -350,9 +343,9 @@ QString SampleBuffer::openAudioFile() const { //<< tr( "MOD-Files (*.mod)" ) ; ofd.setNameFilters(types); - if (!m_maybeFileName.isEmpty()) { + if (!currentAudioFile.isEmpty()) { // select previously opened file - ofd.selectFile(QFileInfo(m_maybeFileName).fileName()); + ofd.selectFile(QFileInfo(currentAudioFile).fileName()); } if (ofd.exec() == QDialog::Accepted) { @@ -366,8 +359,8 @@ QString SampleBuffer::openAudioFile() const { } -QString SampleBuffer::openAndSetAudioFile() { - QString fileName = this->openAudioFile(); +QString SampleBuffer::openAndSetAudioFile(const QString ¤tAudioFile) { + QString fileName = this->openAudioFile(currentAudioFile); if (!fileName.isEmpty()) { this->setAudioFile(fileName); @@ -377,33 +370,32 @@ QString SampleBuffer::openAndSetAudioFile() { } -QString SampleBuffer::openAndSetWaveformFile() { - if (m_maybeFileName.isEmpty()) { - m_maybeFileName = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; +QString SampleBuffer::openAndSetWaveformFile(QString currentAudioFile) { + if (currentAudioFile.isEmpty()) { + currentAudioFile = ConfigManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; } - QString fileName = this->openAudioFile(); + QString fileName = this->openAudioFile(currentAudioFile); if (!fileName.isEmpty()) { this->setAudioFile(fileName); - } else { - m_maybeFileName = ""; } return fileName; } sample_t SampleBuffer::userWaveSample(const float _sample) const { - f_cnt_t dataFrames = frames(); + auto data = guardedData(); + f_cnt_t dataFrames = data->frames(); if (dataFrames == 0) return 0; - auto data = guardedData(); const float frame = _sample * dataFrames; f_cnt_t f1 = static_cast( frame ) % dataFrames; if (f1 < 0) { - f1 += dataFrames; - } + f1 += dataFrames; + } + return linearInterpolate(data->data()[f1][0], data->data()[(f1 + 1) % dataFrames][0], fraction(frame)); } @@ -417,27 +409,16 @@ QString &SampleBuffer::toBase64(QString &_dst) const { return _dst; } -void SampleBuffer::setStartFrame(const f_cnt_t _s) { - guardedPlayInfo()->setStartFrame(_s); -} - - -void SampleBuffer::setEndFrame(const f_cnt_t _e) { - guardedPlayInfo()->setEndFrame(_e); +QFuture SampleBuffer::setAmplification(float _a) { + return runAsyncToSetData([_a](GuardedData &data) { + data->setAmplification(_a); + }); } - -void SampleBuffer::setAmplification(float _a) { - guardedData()->setAmplification(_a); - - emit sampleUpdated(); -} - -void SampleBuffer::setFrequency(float frequency) -{ - guardedData()->setFrequency(frequency); - - emit sampleUpdated(); +QFuture SampleBuffer::setFrequency(float frequency) { + return runAsyncToSetData([frequency](GuardedData &data) { + data->setFrequency(frequency); + }); } QString SampleBuffer::tryToMakeRelative(const QString &file) { @@ -509,16 +490,17 @@ SampleBuffer::DataChangeHelper::DataChangeHelper(SampleBuffer *buffer) : } SampleBuffer::DataChangeHelper::~DataChangeHelper() { - m_buffer->m_playInfo = internal::SampleBufferPlayInfo(m_buffer->m_data.frames()); + *m_buffer->m_playInfo = internal::SampleBufferPlayInfo(m_buffer->m_data->frames()); - emit m_buffer->sampleUpdated(); + m_buffer->m_infoChangeNotifier->onValueUpdated(m_buffer->createInfo()); + emit m_buffer->sampleUpdated(m_updateType); } void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate) { // First of all, don't let anyone read. - DataChangeHelper helper(this); + DataChangeHelper helper(this, UpdateType::Append); - m_data.addData(vector, sampleRate); + m_data->addData(vector, sampleRate); } void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate) { diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index c3bc07b260c..381e2b816c6 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -35,6 +35,7 @@ SamplePlayHandle::SamplePlayHandle(const std::shared_ptr &sampleBuffer, bool ownAudioPort) : PlayHandle( TypeSamplePlayHandle ), m_sampleBuffer( sampleBuffer ), + m_sampleBufferInfo(m_sampleBuffer->createInfo()), m_doneMayReturnTrue( true ), m_frame( 0 ), m_ownAudioPort( ownAudioPort ), @@ -139,8 +140,8 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const f_cnt_t SamplePlayHandle::totalFrames() const { - f_cnt_t total_frames = ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ); - qreal processingToSampleRateRatio = static_cast(Engine::mixer()->processingSampleRate()) / static_cast(m_sampleBuffer->sampleRate ()); + f_cnt_t total_frames = ( m_sampleBufferInfo.endFrame - m_sampleBufferInfo.startFrame ); + qreal processingToSampleRateRatio = static_cast(Engine::mixer()->processingSampleRate()) / static_cast(m_sampleBufferInfo.sampleRate); return static_cast(total_frames * processingToSampleRateRatio); } diff --git a/src/gui/LfoControllerDialog.cpp b/src/gui/LfoControllerDialog.cpp index 0a6a8edc7e0..78db0c1d740 100644 --- a/src/gui/LfoControllerDialog.cpp +++ b/src/gui/LfoControllerDialog.cpp @@ -227,13 +227,13 @@ LfoControllerDialog::~LfoControllerDialog() void LfoControllerDialog::askUserDefWave() { - SampleBuffer * sampleBuffer = dynamic_cast(this->model())-> - m_userDefSampleBuffer; + SampleBuffer * sampleBuffer = m_lfo->m_userDefSampleBuffer; + QString fileName = sampleBuffer->openAndSetWaveformFile(); if( fileName.isEmpty() == false ) { // TODO: - ToolTip::add( m_userWaveBtn, sampleBuffer->audioFile() ); + ToolTip::add( m_userWaveBtn, fileName ); } } diff --git a/src/gui/widgets/EnvelopeAndLfoView.cpp b/src/gui/widgets/EnvelopeAndLfoView.cpp index 7b8a2d57100..64dcb4e267f 100644 --- a/src/gui/widgets/EnvelopeAndLfoView.cpp +++ b/src/gui/widgets/EnvelopeAndLfoView.cpp @@ -513,7 +513,7 @@ void EnvelopeAndLfoView::lfoUserWaveChanged() if( m_params->m_lfoWaveModel.value() == EnvelopeAndLfoParameters::UserDefinedWave ) { - if( m_params->m_userWave.frames() <= 1 ) + if( m_params->m_userWaveInfo->frames <= 1 ) { TextFloat::displayMessage( tr( "Hint" ), tr( "Drag and drop a sample into this window." ), diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 7512c62ee3b..6fb2369d9b6 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -57,6 +57,7 @@ SampleTCO::SampleTCO( Track * _track ) : TrackContentObject( _track ), m_sampleBuffer( new SampleBuffer ), + m_sampleBufferInfo(m_sampleBuffer->createUpdatingValue(this)), m_isPlaying( false ) { saveJournallingState( false ); @@ -109,6 +110,9 @@ void SampleTCO::changeLength( const MidiTime & _length ) } +const QString &SampleTCO::sampleFile() const { + return m_sampleBufferInfo->audioFile; +} const QString & SampleTCO::sampleFile() const @@ -193,11 +197,8 @@ MidiTime SampleTCO::sampleLength() const } - - -void SampleTCO::setSampleStartFrame(f_cnt_t startFrame) -{ - m_sampleBuffer->setStartFrame( startFrame ); +MidiTime SampleTCO::sampleLength() const { + return (int) (m_sampleBufferInfo->frames / Engine::framesPerTick(m_sampleBufferInfo->sampleRate)); } @@ -301,8 +302,8 @@ void SampleTCOView::updateSample() update(); // set tooltip to filename so that user can see what sample this // sample-tco contains - ToolTip::add( this, ( m_tco->m_sampleBuffer->audioFile() != "" ) ? - m_tco->m_sampleBuffer->audioFile() : + ToolTip::add( this, ( m_tco->m_sampleBufferInfo->audioFile != "" ) ? + m_tco->m_sampleBufferInfo->audioFile : tr( "Double-click to open sample" ) ); setNeedsUpdate (true); } @@ -417,15 +418,7 @@ void SampleTCOView::mouseReleaseEvent(QMouseEvent *_me) void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) { QString af = m_tco->m_sampleBuffer->openAudioFile(); - - if ( af.isEmpty() ) {} //Don't do anything if no file is loaded - else if ( af == m_tco->m_sampleBuffer->audioFile() ) - { //Instead of reloading the existing file, just reset the size - int length = (int) ( m_tco->m_sampleBuffer->frames() / Engine::framesPerTick() ); - m_tco->changeLength(length); - } - else - { //Otherwise load the new file as ususal + if(af != "" && af != m_tco->m_sampleBufferInfo->audioFile) { m_tco->setSampleFile( af ); Engine::getSong()->setModified(); } @@ -616,23 +609,28 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, if( sTco->isPlaying() == false && (_start >= (sTco->startPosition() + sTco->startTimeOffset()) || sTco->isRecord ()) ) { - auto bufferFramesPerTick = Engine::framesPerTick (sTco->sampleBuffer ()->sampleRate ()); - f_cnt_t sampleStart = bufferFramesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); + auto sampleBufferInfo = sTco->sampleBuffer()->createInfo(); + + auto bufferFramesPerTick = Engine::framesPerTick(sampleBufferInfo.sampleRate); + f_cnt_t sampleStart = + bufferFramesPerTick * (_start - sTco->startPosition() - sTco->startTimeOffset()); - f_cnt_t tcoFrameLength = bufferFramesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); + f_cnt_t tcoFrameLength = bufferFramesPerTick * + (sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset()); - f_cnt_t sampleBufferLength = sTco->sampleBuffer()->frames(); + f_cnt_t sampleBufferLength = sampleBufferInfo.frames; //if the Tco smaller than the sample length we play only until Tco end //else we play the sample to the end but nothing more - f_cnt_t samplePlayLength = tcoFrameLength > sampleBufferLength ? sampleBufferLength : tcoFrameLength; + f_cnt_t samplePlayLength = + tcoFrameLength > sampleBufferLength ? sampleBufferLength : tcoFrameLength; //we only play within the sampleBuffer limits // anyway, "play" (record) this TCO if is recording. - if( sampleStart < sampleBufferLength || sTco->isRecord ()) - { - sTco->setSampleStartFrame( sampleStart ); - sTco->setSamplePlayLength( samplePlayLength ); - tcos.push_back( sTco ); - sTco->setIsPlaying( true ); + if (sampleStart < sampleBufferLength || sTco->isRecord()) { + auto sampleBuffer = sTco->sampleBuffer(); + sampleBuffer->setStartFrame(sampleStart).waitForFinished(); + sampleBuffer->setEndFrame(samplePlayLength).waitForFinished(); + tcos.push_back(sTco); + sTco->setIsPlaying(true); } } } @@ -768,6 +766,7 @@ void SampleTrack::beforeRecordOn(MidiTime time) } if (! isRecordTCOExist) { + // TODO: instead of this strange thing, move this code to SampleTrack::play. Engine::mixer()->requestChangeInModel(); auto fallbackRecordTCO = static_cast(createTCO (0)); @@ -776,8 +775,7 @@ void SampleTrack::beforeRecordOn(MidiTime time) fallbackRecordTCO->movePosition (time); // fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); fallbackRecordTCO->changeLength (1); - fallbackRecordTCO->setSampleStartFrame (0); - fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); + fallbackRecordTCO->sampleBuffer()->setEndFrame (Engine::framesPerTick()); fallbackRecordTCO->setIsPlaying (false); fallbackRecordTCO->setAutoResize (true); From 8dc14758532238405de0755d793ba2ce476af9fb Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 20:37:29 +0300 Subject: [PATCH 103/112] SampleBuffer & SampleTCO visualization: visualize async only after an update from the SampleBuffer. --- include/SampleBufferVisualizer.h | 5 +- include/SampleTrack.h | 27 +++-- src/core/SampleBuffer.cpp | 82 +++++++------- src/tracks/SampleTrack.cpp | 179 ++++++++++++++++--------------- 4 files changed, 154 insertions(+), 139 deletions(-) diff --git a/include/SampleBufferVisualizer.h b/include/SampleBufferVisualizer.h index 9a8d2113759..1b47245e27d 100644 --- a/include/SampleBufferVisualizer.h +++ b/include/SampleBufferVisualizer.h @@ -59,10 +59,7 @@ class SampleBufferVisualizer public: SampleBufferVisualizer(); - enum class Operation { - Clear, - Append - }; + using Operation=SampleBuffer::UpdateType; /** * @brief Update the cache before drawing it. diff --git a/include/SampleTrack.h b/include/SampleTrack.h index 1331da36ba5..bf48cdd853c 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -70,6 +70,7 @@ class SampleTCO : public TrackContentObject bool isPlaying() const; + void setIsPlaying(bool isPlaying); /** @@ -84,7 +85,8 @@ public slots: private slots: - void onSampleBufferChanged (); + + void onSampleBufferChanged(int updateType); private: std::shared_ptr m_sampleBuffer; @@ -92,11 +94,17 @@ private slots: BoolModel m_recordModel; bool m_isPlaying; + using Watcher=QFutureWatcher; + + Watcher m_loadingWatcher; + friend class SampleTCOView; + void onFileLoadingFinished(); + signals: - void sampleChanged(); + void sampleChanged(int updateType); } ; @@ -111,8 +119,8 @@ class SampleTCOView : public TrackContentObjectView virtual ~SampleTCOView() = default; public slots: - void updateSample(); + void updateSample(int updateType); protected: @@ -128,10 +136,14 @@ public slots: private: SampleTCO * m_tco; - SampleBufferVisualizer m_sampleBufferVisualizer; -} ; + std::shared_ptr m_sampleBufferVisualizerMutex = std::make_shared(); + std::shared_ptr m_sampleBufferVisualizer = std::make_shared(); + using Watcher=QFutureWatcher; + Watcher m_visualizationWatcher; + void updateVisualizer(QPen p, SampleBufferVisualizer::Operation operation); +}; class SampleTrack : public Track @@ -194,11 +206,10 @@ public slots: AudioPort m_audioPort; - friend class SampleTrackView; friend class SampleTrackWindow; -} ; +}; @@ -316,7 +327,7 @@ public slots: EffectRackView * m_effectRack; -} ; +}; #endif diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 78b056802a0..986bd90f9aa 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -260,47 +260,51 @@ void SampleBuffer::visualize(QPainter &_p, const QRect &_dr, std::pair SampleBuffer::visualizeToPoly(const QRect &_dr, const QRect &_clip, f_cnt_t _from_frame, f_cnt_t _to_frame) const { - if (m_data.frames() == 0) return {}; - - const bool focus_on_range = _from_frame < _to_frame; - if (_to_frame > m_data.frames()) - _to_frame = m_data.frames(); - const int w = _dr.width(); const int h = _dr.height(); - + const bool focus_on_range = _from_frame < _to_frame; int y_space = (h / 2); - const int nb_frames = focus_on_range ? _to_frame - _from_frame : m_data.frames(); - if (nb_frames == 0) return {}; + /* Don't visualize while rendering / doing after-rendering changes. */ + return Engine::mixer()->runWhileNotRendering([=]() -> std::pair { + if (m_data->frames() == 0) return {}; - const int fpp = tLimit(nb_frames / w, 1, 20); + auto to_frame = _to_frame; + if (to_frame > m_data->frames()) + to_frame = m_data->frames(); - bool shouldAddAdditionalPoint = (nb_frames % fpp) != 0; - int pointsCount = (nb_frames / fpp) + (shouldAddAdditionalPoint ? 1 : 0); - auto l = QPolygonF(pointsCount); - auto r = QPolygonF(pointsCount); - int n = 0; - const int xb = _dr.x(); - const int first = focus_on_range ? _from_frame : 0; - const int last = focus_on_range ? _to_frame : m_data.frames(); + const int nb_frames = focus_on_range ? to_frame - _from_frame : m_data->frames(); + if (nb_frames == 0) return {}; - int zeroPoint = _dr.y() + y_space; - if (h % 2 != 0) - zeroPoint += 1; - for (int frame = first; frame < last; frame += fpp) { - double x = (xb + (frame - first) * double(w) / nb_frames); + const int fpp = tLimit(nb_frames / w, 1, 20); - l[n] = QPointF(x, - (zeroPoint + (m_data.data()[frame][0] * y_space))); - r[n] = QPointF(x, - (zeroPoint + (m_data.data()[frame][1] * y_space))); + bool shouldAddAdditionalPoint = (nb_frames % fpp) != 0; + int pointsCount = (nb_frames / fpp) + (shouldAddAdditionalPoint ? 1 : 0); + auto l = QPolygonF(pointsCount); + auto r = QPolygonF(pointsCount); - ++n; - } + int n = 0; + const int xb = _dr.x(); + const int first = focus_on_range ? _from_frame : 0; + const int last = focus_on_range ? to_frame : m_data->frames(); + + int zeroPoint = _dr.y() + y_space; + if (h % 2 != 0) + zeroPoint += 1; + for (int frame = first; frame < last; frame += fpp) { + double x = (xb + (frame - first) * double(w) / nb_frames); - return {std::move(l), std::move(r)}; + l[n] = QPointF(x, + (zeroPoint + (m_data->data()[frame][0] * y_space))); + r[n] = QPointF(x, + (zeroPoint + (m_data->data()[frame][1] * y_space))); + + ++n; + } + + return {std::move(l), std::move(r)}; + }); } @@ -484,8 +488,8 @@ SampleBuffer::handleState::~handleState() { src_delete(m_resamplingData); } -SampleBuffer::DataChangeHelper::DataChangeHelper(SampleBuffer *buffer) : - m_buffer(buffer) +SampleBuffer::DataChangeHelper::DataChangeHelper(SampleBuffer *buffer, SampleBuffer::UpdateType updateType) + :m_buffer{buffer}, m_updateType{updateType} { } @@ -504,13 +508,13 @@ void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t } void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate) { - DataChangeHelper helper(this); - m_data.resetData(std::move(newData), dataSampleRate); + DataChangeHelper helper(this, UpdateType::Clear); + m_data->resetData(std::move(newData), dataSampleRate); } void SampleBuffer::reverse() { - DataChangeHelper helper(this); - m_data.reverse(); + DataChangeHelper helper(this, UpdateType::Clear); + m_data->reverse(); } void SampleBuffer::setAudioFile(const QString &audioFile, bool ignoreError) @@ -524,10 +528,10 @@ SampleBuffer::SampleBuffer() } SampleBuffer::SampleBuffer(internal::SampleBufferData &&data) - : m_data{std::move(data)}, - m_playInfo(m_data.frames()) + : m_data{std::make_shared(std::move(data))}, + m_playInfo(std::make_shared(m_data->frames())) { - emit sampleUpdated(); + emit sampleUpdated(UpdateType::Clear); } SampleBuffer::SampleBuffer(const QString &base64Data, sample_rate_t sample_rate) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 6fb2369d9b6..ffcb51203d7 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -1,4 +1,4 @@ -/* +/* * SampleTrack.cpp - implementation of class SampleTrack, a track which * provides arrangement of samples * @@ -70,7 +70,8 @@ SampleTCO::SampleTCO( Track * _track ) : connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), this, SLOT( updateLength() ) ); - connect (m_sampleBuffer.get (), SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); + connect(m_sampleBuffer.get(), + &SampleBuffer::sampleUpdated, this, &SampleTCO::onSampleBufferChanged); switch( getTrack()->trackContainer()->type() ) { @@ -86,7 +87,10 @@ SampleTCO::SampleTCO( Track * _track ) : } //care about TCO position - connect( this, SIGNAL( positionChanged() ), getTrack (), SLOT( updateTcos() ) ); + connect(this, SIGNAL(positionChanged()), getTrack(), SLOT(updateTcos())); + + // Care about finishing setAudioFile. + connect(&m_loadingWatcher, &Watcher::finished, this, &SampleTCO::onFileLoadingFinished); } @@ -115,34 +119,29 @@ const QString &SampleTCO::sampleFile() const { } -const QString & SampleTCO::sampleFile() const -{ - return m_sampleBuffer->audioFile(); -} - +void SampleTCO::setSampleFile(const QString &_sf) { + // We're in the process of loading another file. + if (m_loadingWatcher.isRunning()) + return; + auto sampleBuffer = m_sampleBuffer; -void SampleTCO::setSampleFile( const QString & _sf ) -{ - int length; - if ( _sf.isEmpty() ) - { //When creating an empty sample pattern make it a bar long - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - length = DefaultTicksPerTact * ( nom / den ); - } - else - { //Otherwise set it to the sample's length - m_sampleBuffer->setAudioFile( _sf ); - length = sampleLength(); - } - changeLength(length); + // Load the file. + m_loadingWatcher.setFuture(runAsync([sampleBuffer, _sf] { + sampleBuffer->setAudioFile(_sf); + return sampleBuffer->createInfo(); + })); - setStartTimeOffset( 0 ); + // onFileLoadingFinished will run when the future has been finished. // Already has been has been called sampleChanged from m_sampleBuffer. } +void SampleTCO::onFileLoadingFinished() { + setStartTimeOffset(0); + changeLength((int) (m_loadingWatcher.future().result().frames / Engine::framesPerTick())); +} + @@ -152,26 +151,13 @@ void SampleTCO::toggleRecord() emit dataChanged(); } - - -void SampleTCO::onSampleBufferChanged() -{ - emit sampleChanged (); -} - - - - -bool SampleTCO::isPlaying() const -{ +bool SampleTCO::isPlaying() const { return m_isPlaying; } - - -void SampleTCO::setIsPlaying(bool isPlaying) -{ +void SampleTCO::setIsPlaying(bool isPlaying) { + auto guard = Engine::mixer()->requestChangesGuard(); m_isPlaying = isPlaying; } @@ -181,19 +167,8 @@ bool SampleTCO::isEmpty() const } - - -void SampleTCO::updateLength() -{ - emit sampleChanged(); -} - - - - -MidiTime SampleTCO::sampleLength() const -{ - return (int)( m_sampleBuffer->frames() / Engine::framesPerTick(m_sampleBuffer->sampleRate ()) ); +void SampleTCO::updateLength() { + emit sampleChanged(SampleBuffer::UpdateType::Clear); } @@ -204,12 +179,6 @@ MidiTime SampleTCO::sampleLength() const { -void SampleTCO::setSamplePlayLength(f_cnt_t length) -{ - m_sampleBuffer->setEndFrame( length ); -} - - void SampleTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) @@ -280,7 +249,9 @@ TrackContentObjectView * SampleTCO::createView( TrackView * _tv ) return new SampleTCOView( this, _tv ); } - +void SampleTCO::onSampleBufferChanged(int updateType) { + emit sampleChanged(updateType); +} SampleTCOView::SampleTCOView( SampleTCO * _tco, TrackView * _tv ) : @@ -288,18 +259,22 @@ SampleTCOView::SampleTCOView( SampleTCO * _tco, TrackView * _tv ) : m_tco( _tco ) { // update UI and tooltip - updateSample(); + updateSample(SampleBuffer::UpdateType::Clear); + + // Update the view on each visualization. + QObject::connect(&m_visualizationWatcher, &Watcher::finished, this, &SampleTCOView::update); // track future changes of SampleTCO - connect( m_tco, SIGNAL( sampleChanged() ), - this, SLOT( updateSample() ) ); + connect(m_tco, &SampleTCO::sampleChanged, + this, &SampleTCOView::updateSample); setStyle( QApplication::style() ); } -void SampleTCOView::updateSample() -{ - update(); +void SampleTCOView::updateSample(int updateType) { + updateVisualizer(mutedColor(), + static_cast(updateType)); + // set tooltip to filename so that user can see what sample this // sample-tco contains ToolTip::add( this, ( m_tco->m_sampleBufferInfo->audioFile != "" ) ? @@ -429,6 +404,12 @@ void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) void SampleTCOView::paintEvent( QPaintEvent * pe ) { + std::unique_lock visualizerLock(*m_sampleBufferVisualizerMutex, std::try_to_lock); + if (! visualizerLock.owns_lock()) { + update(); + return; + } + QPainter p( this ); setNeedsUpdate( false ); @@ -461,30 +442,14 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.fillRect( rect(), c ); } - p.setPen( !muted ? p.pen().brush().color() : mutedColor() ); +// p.setPen(!muted ? p.pen().brush().color() : mutedColor()); - auto timeSig = TimeSig(Engine::getSong()->getTimeSigModel()); - auto realTicksPerDefaultTicks = float(float(MidiTime::ticksPerTact(timeSig) / MidiTime::ticksPerTact())); - auto normalizedPixelsPerTact = pixelsPerTact() * realTicksPerDefaultTicks; - auto normalizedFramesPerTick = Engine::framesPerTick(m_tco->sampleBuffer()->sampleRate()) * realTicksPerDefaultTicks; - - const int spacing = TCO_BORDER_WIDTH + 1; + { + m_sampleBufferVisualizer->draw(p); + visualizerLock.unlock(); + } - QMargins margins(spacing, - TCO_BORDER_WIDTH-1, - spacing, - TCO_BORDER_WIDTH); - m_sampleBufferVisualizer.update(*m_tco->sampleBuffer(), - m_tco->startTimeOffset(), - m_tco->sampleLength(), - rect()-margins, - normalizedPixelsPerTact, - f_cnt_t (normalizedFramesPerTick), - p.pen(), - SampleBufferVisualizer::Operation::Append); - m_sampleBufferVisualizer.draw(p); - - QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile()); + QFileInfo fileInfo(m_tco->m_sampleBufferInfo->audioFile); QString filename = fileInfo.fileName(); paintTextLabel(filename, p); @@ -512,9 +477,47 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.end(); } +void SampleTCOView::updateVisualizer(QPen p, SampleBufferVisualizer::Operation operation) { + auto timeSig = TimeSig(Engine::getSong()->getTimeSigModel()); + auto realTicksPerDefaultTicks = float(float(MidiTime::ticksPerTact(timeSig) / MidiTime::ticksPerTact())); + auto normalizedPixelsPerTact = pixelsPerTact() * realTicksPerDefaultTicks; + auto normalizedFramesPerTick = + Engine::framesPerTick(m_tco->m_sampleBufferInfo->sampleRate) * realTicksPerDefaultTicks; + const int spacing = TCO_BORDER_WIDTH + 1; + QMargins margins(spacing, + TCO_BORDER_WIDTH - 1, + spacing, + TCO_BORDER_WIDTH); + // Prepare values for the lambda. + auto rectMinusMargins = rect() - margins; + auto startTimeOffset = m_tco->startTimeOffset(); + auto sampleLength = m_tco->sampleLength(); + + // Prepare shared_ptr-s to be captured by the lambda. + auto sampleBuffer = m_tco->sampleBuffer(); + auto visualizer = m_sampleBufferVisualizer; + auto visualizerMutex = m_sampleBufferVisualizerMutex; + + // NOTE: we will use only local variables and won't + // touch `this`; as far as we know, this could be + // dangling. + m_visualizationWatcher.setFuture(runAsync([=] { + std::lock_guard lockGuard(*visualizerMutex); + visualizer->update( + *sampleBuffer, + startTimeOffset, + sampleLength, + rectMinusMargins, + normalizedPixelsPerTact, + f_cnt_t(normalizedFramesPerTick), + p, + operation + ); + })); +} SampleTrack::SampleTrack( TrackContainer* tc ) : From 1c42efab25afad463941d8de603523b3b45aeec0 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 20:39:06 +0300 Subject: [PATCH 104/112] Track: put mixer syncs in tco remove/add in order to avoid data races between the gui and the mixer. --- src/core/Track.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/core/Track.cpp b/src/core/Track.cpp index a10a929027c..ca63328613d 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -2211,9 +2211,10 @@ void Track::loadSettings( const QDomElement & element ) * * \param tco The TrackContentObject to attach to this track. */ -TrackContentObject * Track::addTCO( TrackContentObject * tco ) -{ - m_trackContentObjects.push_back( tco ); +TrackContentObject *Track::addTCO(TrackContentObject *tco) { + auto guard = Engine::mixer()->requestChangesGuard(); + + m_trackContentObjects.push_back(tco); emit trackContentObjectAdded( tco ); @@ -2227,16 +2228,15 @@ TrackContentObject * Track::addTCO( TrackContentObject * tco ) * * \param tco The TrackContentObject to remove from this track. */ -void Track::removeTCO( TrackContentObject * tco ) -{ - tcoVector::iterator it = std::find( m_trackContentObjects.begin(), - m_trackContentObjects.end(), - tco ); - if( it != m_trackContentObjects.end() ) - { - m_trackContentObjects.erase( it ); - if( Engine::getSong() ) - { +void Track::removeTCO(TrackContentObject *tco) { + auto guard = Engine::mixer()->requestChangesGuard(); + + tcoVector::iterator it = std::find(m_trackContentObjects.begin(), + m_trackContentObjects.end(), + tco); + if (it != m_trackContentObjects.end()) { + m_trackContentObjects.erase(it); + if (Engine::getSong()) { Engine::getSong()->updateLength(); Engine::getSong()->setModified(); } @@ -2245,10 +2245,9 @@ void Track::removeTCO( TrackContentObject * tco ) /*! \brief Remove all TCOs from this track */ -void Track::deleteTCOs() -{ - while( ! m_trackContentObjects.isEmpty() ) - { +void Track::deleteTCOs() { + auto guard = Engine::mixer()->requestChangesGuard(); + while (!m_trackContentObjects.isEmpty()) { delete m_trackContentObjects.first(); } } @@ -2521,6 +2520,11 @@ BoolModel *Track::getMutedModel() return &m_mutedModel; } +TrackContentObject *Track::createTCO(const MidiTime &pos) { + auto guard = Engine::mixer()->requestChangesGuard(); + return unsafeCreateTCO(pos); +} + From 98d1062637b6d280a32c9ee6d1c9fb1acc1fe85c Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Wed, 22 May 2019 20:43:38 +0300 Subject: [PATCH 105/112] SampleRecordHandle: move calls to SampleBuffer's methods out of the rendering threads in order to avoid deadlocks while trying to sync with the mixer. --- include/SampleRecordHandle.h | 3 ++ src/core/SampleRecordHandle.cpp | 54 +++++++++++++++++++++------------ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 4e5b74b30ef..563ca46bb77 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -66,6 +66,8 @@ class SampleRecordHandle : public PlayHandle sampleFrame *outputBuffer, const f_cnt_t _frames); + void addOrCreateBuffer(); + virtual void writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ); @@ -78,6 +80,7 @@ class SampleRecordHandle : public PlayHandle MidiTime m_timeRecorded; SampleBuffer::DataVector m_currentBuffer; + QFuture m_lastAsyncWork; Track * m_track; BBTrack * m_bbTrack; diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 3be0a9d7da4..3a011c99b72 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -48,19 +48,23 @@ SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTime SampleRecordHandle::~SampleRecordHandle() -{ +{ + // No problem to block here. + // this is not a renderer thread anymore + // so it will not cause a deadlock + // with requestChangesInModel. + + // Wait for the current async rendering job + // to finish. + m_lastAsyncWork.waitForFinished(); + 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 ()); - m_tco->setStartTimeOffset (m_startRecordTimeOffset); - } else { - m_tco->sampleBuffer ()->addData(m_currentBuffer, - Engine::mixer ()->inputSampleRate ()); - } + // Add a new rendering job and wait for it to finish. + addOrCreateBuffer(); + m_lastAsyncWork.waitForFinished(); } m_tco->updateLength (); @@ -86,17 +90,8 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) writeBuffer( recbuf, frames ); // Add data to the buffer. - if (m_framesRecorded == 0) { - // Make sure we don't have the previous data. - m_tco->sampleBuffer ()->resetData(std::move (m_currentBuffer), - Engine::mixer ()->inputSampleRate ()); - m_tco->setStartTimeOffset (m_startRecordTimeOffset); - } else { - if (! m_currentBuffer.empty ()) { - m_tco->sampleBuffer ()->addData(m_currentBuffer, - Engine::mixer ()->inputSampleRate ()); - } - } + if (m_lastAsyncWork.isFinished()) + addOrCreateBuffer(); m_framesRecorded += frames; m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ()); @@ -162,6 +157,25 @@ void SampleRecordHandle::copyBufferFromStereo(const sampleFrame *inputBuffer, } } +void SampleRecordHandle::addOrCreateBuffer() { + auto sampleBuffer = m_tco->sampleBuffer (); + auto currentBufferCopy = m_currentBuffer; + m_lastAsyncWork = runAsync(std::bind([this, currentBufferCopy, sampleBuffer] () mutable{ + if (m_framesRecorded == 0) { + // Make sure we don't have the previous data. + sampleBuffer->resetData(std::move (currentBufferCopy), + Engine::mixer ()->inputSampleRate ()); + m_tco->setStartTimeOffset (m_startRecordTimeOffset); + } else { + if (! currentBufferCopy.empty ()) { + sampleBuffer->addData(currentBufferCopy, + Engine::mixer ()->inputSampleRate ()); + } + } + })); + +} + From cd6ad7aa56a3d6cce5e2b6b40b39df139983d736 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 23 May 2019 10:27:28 +0300 Subject: [PATCH 106/112] SampleRecordHandle: Add guard before setting startRecordTimeOffset in external thread. --- src/core/SampleRecordHandle.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index 3a011c99b72..0cacaee50c0 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -162,6 +162,8 @@ void SampleRecordHandle::addOrCreateBuffer() { auto currentBufferCopy = m_currentBuffer; m_lastAsyncWork = runAsync(std::bind([this, currentBufferCopy, sampleBuffer] () mutable{ if (m_framesRecorded == 0) { + // Protect m_tco->setStartTimeOffset; + auto guard = Engine::mixer()->requestChangesGuard(); // Make sure we don't have the previous data. sampleBuffer->resetData(std::move (currentBufferCopy), Engine::mixer ()->inputSampleRate ()); @@ -169,7 +171,7 @@ void SampleRecordHandle::addOrCreateBuffer() { } else { if (! currentBufferCopy.empty ()) { sampleBuffer->addData(currentBufferCopy, - Engine::mixer ()->inputSampleRate ()); + Engine::mixer ()->inputSampleRate ()); } } })); From 7d8fdff60f068afb1369f1354138d72f2568d961 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 23 May 2019 10:28:52 +0300 Subject: [PATCH 107/112] Threading: Add runAccordingToLaunchType helper that will run a callable async / sync in the same thread according to a variable. --- include/Threading.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/include/Threading.h b/include/Threading.h index 48f7ac18a98..d3e29e6bfea 100644 --- a/include/Threading.h +++ b/include/Threading.h @@ -8,4 +8,24 @@ inline auto runAsync(Callable &&callable) -> QFuture { return QtConcurrent::run(std::forward(callable)); } +template +inline auto runSync(Callable &&callable) -> decltype(callable()) { + return callable(); +} + +namespace LaunchType { + struct Async {}; + struct Sync{}; +} + +template +inline auto runAccordingToLaunchType(Callable &&callable, LaunchType::Async) -> QFuture { + return runAsync(std::forward(callable)); +} + +template +inline auto runAccordingToLaunchType(Callable &&callable, LaunchType::Sync) -> decltype(callable()) { + return callable(); +} + #endif // THREADING_H From cde4baa4d5a927d2ef08455533dc9d665af168ae Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 23 May 2019 10:29:49 +0300 Subject: [PATCH 108/112] SampleBuffer: Support setX sync operation instead of only async. --- src/core/SampleBuffer.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 986bd90f9aa..381eaffeb24 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -413,18 +413,6 @@ QString &SampleBuffer::toBase64(QString &_dst) const { return _dst; } -QFuture SampleBuffer::setAmplification(float _a) { - return runAsyncToSetData([_a](GuardedData &data) { - data->setAmplification(_a); - }); -} - -QFuture SampleBuffer::setFrequency(float frequency) { - return runAsyncToSetData([frequency](GuardedData &data) { - data->setFrequency(frequency); - }); -} - QString SampleBuffer::tryToMakeRelative(const QString &file) { if (QFileInfo(file).isRelative() == false) { // Normalize the path From ab180b002f231e68bd01ccc3cd34e11d2df4ad92 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Thu, 23 May 2019 10:30:43 +0300 Subject: [PATCH 109/112] SampleTrack: use sync version of SampleBuffer's setX in order to avoid a deadlock. --- src/tracks/SampleTrack.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index ffcb51203d7..ccdc850f577 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -630,8 +630,8 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, // anyway, "play" (record) this TCO if is recording. if (sampleStart < sampleBufferLength || sTco->isRecord()) { auto sampleBuffer = sTco->sampleBuffer(); - sampleBuffer->setStartFrame(sampleStart).waitForFinished(); - sampleBuffer->setEndFrame(samplePlayLength).waitForFinished(); + sampleBuffer->setStartFrame(sampleStart);; + sampleBuffer->setEndFrame(samplePlayLength); tcos.push_back(sTco); sTco->setIsPlaying(true); } From 91c6e963dba604e36eacb8e3582794340d7ccee2 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 25 May 2019 00:27:45 +0300 Subject: [PATCH 110/112] Use LMMS_EXPORT in order to resolve MSVC problems. --- include/SampleBuffer.h | 4 ++-- include/UpdatingValue.h | 11 +++++---- include/internal/SampleBufferData.h | 16 ++++++++----- include/internal/SampleBufferPlayInfo.h | 3 ++- src/core/samplebuffer/SampleBufferData.cpp | 23 ++++--------------- .../samplebuffer/SampleBufferFileHelper.cpp | 4 ++-- 6 files changed, 27 insertions(+), 34 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index feb3e7aec7f..e4c432bacd4 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -55,7 +55,7 @@ MM_OPERATORS typedef std::shared_ptr SharedPlayInfo; template> - class _MixerGuardedSharedPtr { + class LMMS_EXPORT _MixerGuardedSharedPtr { public: explicit _MixerGuardedSharedPtr(PtrType ptr) : m_ptr(std::move(ptr)) { @@ -98,7 +98,7 @@ MM_OPERATORS return GuardedConstData(m_data); } - struct SampleBufferInfo { + struct LMMS_EXPORT SampleBufferInfo { SampleBufferInfo() = default; SampleBufferInfo(GuardedPlayInfo &playInfo, GuardedData &data) diff --git a/include/UpdatingValue.h b/include/UpdatingValue.h index 7fc3a0dba73..d40264d793f 100644 --- a/include/UpdatingValue.h +++ b/include/UpdatingValue.h @@ -4,9 +4,10 @@ #include #include "MemoryUtils.h" +#include "lmms_export.h" namespace internal { - class SignalArgument { + class LMMS_EXPORT SignalArgument { protected: virtual ~SignalArgument() {} }; @@ -16,7 +17,7 @@ Q_DECLARE_METATYPE(std::shared_ptr); namespace internal { - class UpdatingValueNotifier_Untyped : public QObject { + class LMMS_EXPORT UpdatingValueNotifier_Untyped : public QObject { Q_OBJECT signals: @@ -28,7 +29,7 @@ namespace internal { void rawOnValueUpdated(std::shared_ptr value); }; - class UpdatingValue_Untyped : public QObject { + class LMMS_EXPORT UpdatingValue_Untyped : public QObject { Q_OBJECT protected: @@ -51,7 +52,7 @@ namespace internal { }; template - class UpdatingValueImpl : public internal::UpdatingValue_Untyped { + class LMMS_EXPORT UpdatingValueImpl : public internal::UpdatingValue_Untyped { public: /** * @brief Sender of updates for this UpdatingValue. @@ -113,7 +114,7 @@ namespace internal { we'll get notified when we have any change in the value. */ template -class UpdatingValue { +class LMMS_EXPORT UpdatingValue { typedef internal::UpdatingValueImpl Impl; public: diff --git a/include/internal/SampleBufferData.h b/include/internal/SampleBufferData.h index a94a4d43f84..ef55fe5d2aa 100644 --- a/include/internal/SampleBufferData.h +++ b/include/internal/SampleBufferData.h @@ -7,13 +7,14 @@ #include "Engine.h" #include "MemoryManager.h" #include "lmms_basics.h" +#include "lmms_export.h" namespace internal { inline sample_rate_t mixerSampleRate() { return Engine::mixer()->processingSampleRate(); } - class SampleBufferData { + class LMMS_EXPORT SampleBufferData { public: typedef std::vector> DataVector; @@ -82,25 +83,28 @@ namespace internal { DataVector m_data; float m_frequency = BaseFreq; float m_amplification = 1.0f; - sample_rate_t m_sampleRate = internal::GetMixerSampleRate(); + sample_rate_t m_sampleRate = internal::mixerSampleRate(); public: float getFrequency() const { - return 0; + return m_frequency; } sample_rate_t getSampleRate() const { return m_sampleRate; } - void setFrequency(float frequency) { + void setFrequency(float frequency) + { m_frequency = frequency; } - float getAmplification() const { + float getAmplification() const + { return m_amplification; } - void setAmplification(float amplification) { + void setAmplification(float amplification) + { m_amplification = amplification; } }; diff --git a/include/internal/SampleBufferPlayInfo.h b/include/internal/SampleBufferPlayInfo.h index ad2fe6382bc..08a324952b9 100644 --- a/include/internal/SampleBufferPlayInfo.h +++ b/include/internal/SampleBufferPlayInfo.h @@ -6,9 +6,10 @@ #define LMMS_SAMPLEBUFFERPLAYINFO_H #include "Mixer.h" +#include "lmms_export.h" namespace internal { - class SampleBufferPlayInfo { + class LMMS_EXPORT SampleBufferPlayInfo { public: explicit SampleBufferPlayInfo(f_cnt_t frames) : m_endFrame{frames}, m_loopEndFrame{frames} { diff --git a/src/core/samplebuffer/SampleBufferData.cpp b/src/core/samplebuffer/SampleBufferData.cpp index 76e5730f921..3cc9afc4e31 100644 --- a/src/core/samplebuffer/SampleBufferData.cpp +++ b/src/core/samplebuffer/SampleBufferData.cpp @@ -60,20 +60,11 @@ internal::SampleBufferData::resampleData(const SampleBufferData::DataVector &inp return outputData; } -float internal::SampleBufferData::getAmplification() const -{ - return m_amplification; -} -void internal::SampleBufferData::setAmplification(float amplification) -{ - m_amplification = amplification; -} -void internal::SampleBufferData::setFrequency(float frequency) -{ - m_frequency = frequency; -} + + + const sampleFrame *internal::SampleBufferData::getSampleFragment(f_cnt_t _index, @@ -157,10 +148,6 @@ sampleFrame *internal::SampleBufferData::getSampleFragment(f_cnt_t _index, return *_tmp; } -float internal::SampleBufferData::getFrequency() const { - return m_frequency; -} -sample_rate_t internal::SampleBufferData::getSampleRate() const { - return m_sampleRate; -} + + diff --git a/src/core/samplebuffer/SampleBufferFileHelper.cpp b/src/core/samplebuffer/SampleBufferFileHelper.cpp index 6b828f2c836..79ac3a2921b 100644 --- a/src/core/samplebuffer/SampleBufferFileHelper.cpp +++ b/src/core/samplebuffer/SampleBufferFileHelper.cpp @@ -256,7 +256,7 @@ internal::SampleBufferData internal::SampleBufferFileHelper::Load(internal::SampleBufferFileHelper::FileName fileName, bool ignoreError) { fileName = tryToMakeRelative(fileName); - sample_rate_t sampleRate = GetMixerSampleRate(); + sample_rate_t sampleRate = mixerSampleRate(); if (fileName == "") return {{}, sampleRate}; @@ -293,7 +293,7 @@ internal::SampleBufferFileHelper::Load(internal::SampleBufferFileHelper::FileNam } } - return {{}, GetMixerSampleRate()}; + return {{}, mixerSampleRate()}; } else { return {std::move(fileData), sampleRate}; } From a7236e4456e11a03bf5d51191e81e336745e8b90 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sat, 25 May 2019 12:12:07 +0300 Subject: [PATCH 111/112] SampleTrack -> QMutex: use tryLock instead of tryLock. --- src/tracks/SampleTrack.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index ccdc850f577..fdcefb68e73 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -404,8 +404,7 @@ void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) void SampleTCOView::paintEvent( QPaintEvent * pe ) { - std::unique_lock visualizerLock(*m_sampleBufferVisualizerMutex, std::try_to_lock); - if (! visualizerLock.owns_lock()) { + if (! m_sampleBufferVisualizerMutex->tryLock()) { update(); return; } @@ -446,7 +445,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) { m_sampleBufferVisualizer->draw(p); - visualizerLock.unlock(); + m_sampleBufferVisualizerMutex->unlock(); } QFileInfo fileInfo(m_tco->m_sampleBufferInfo->audioFile); @@ -505,7 +504,7 @@ void SampleTCOView::updateVisualizer(QPen p, SampleBufferVisualizer::Operation o // touch `this`; as far as we know, this could be // dangling. m_visualizationWatcher.setFuture(runAsync([=] { - std::lock_guard lockGuard(*visualizerMutex); + QMutexLocker locker{visualizerMutex.get()}; visualizer->update( *sampleBuffer, startTimeOffset, From 444e638c6717f8582f0b13e837c402010b4566f9 Mon Sep 17 00:00:00 2001 From: Shmuel H Date: Sun, 26 May 2019 00:26:44 +0300 Subject: [PATCH 112/112] AudioFileProcessor: fix saving/loading problems. --- .../audio_file_processor/audio_file_processor.cpp | 13 +++++++++---- plugins/audio_file_processor/audio_file_processor.h | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 71d057b2cbc..851653cc1af 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -211,7 +211,7 @@ void audioFileProcessor::deleteNotePluginData( NotePlayHandle * _n ) void audioFileProcessor::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - m_sampleBuffer.saveState (_doc, _this); + m_sampleBuffer.saveSettings (_doc, _this); m_reverseModel.saveSettings( _doc, _this, "reversed" ); m_loopModel.saveSettings( _doc, _this, "looped" ); m_ampModel.saveSettings( _doc, _this, "amp" ); @@ -228,6 +228,8 @@ void audioFileProcessor::saveSettings( QDomDocument & _doc, void audioFileProcessor::loadSettings( const QDomElement & _this ) { + m_isLoadingSettings = true; + if( _this.attribute( "src" ) != "" ) { setAudioFile( _this.attribute( "src" ), false ); @@ -242,11 +244,11 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } else if( _this.attribute( "sampledata" ) != "" ) { - m_sampleBuffer = SampleBuffer( _this.attribute( "srcdata" ) , + m_sampleBuffer = SampleBuffer( _this.attribute( "sampledata" ) , Engine::mixer ()->baseSampleRate ()); qWarning("Using default sampleRate. That could lead to invalid values"); } else { - m_sampleBuffer.restoreState (_this.firstChildElement (m_sampleBuffer.nodeName ())); + m_sampleBuffer.loadSettings (_this); } m_loopModel.loadSettings( _this, "looped" ); @@ -280,6 +282,8 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } pointChanged(); + + m_isLoadingSettings = false; } @@ -344,7 +348,8 @@ void audioFileProcessor::setAudioFile( const QString & _audio_file, void audioFileProcessor::reverseModelChanged( void ) { - if (m_reverseModel.value () != m_isCurrentlyReversed) { + // Don't consider loading as a value change. + if (!m_isLoadingSettings && m_reverseModel.value () != m_isCurrentlyReversed) { m_isCurrentlyReversed = m_reverseModel.value (); m_sampleBuffer.reverse (); diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index b2d8a89b9b0..1beccdf091a 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -116,6 +116,11 @@ private slots: */ bool m_isCurrentlyReversed = false; + /** + * @brief Are we in the middle of loadSettings? + */ + bool m_isLoadingSettings = false; + friend class AudioFileProcessorView; } ;