From 9c25be1bde33247a8fb0c435950864e1d8345e51 Mon Sep 17 00:00:00 2001 From: Vesa Date: Fri, 22 Aug 2014 20:52:30 +0300 Subject: [PATCH 01/28] LMMS Memory Manager --- include/AudioPort.h | 6 +- include/ConfigManager.h | 3 +- include/DetuningHelper.h | 7 +- include/Effect.h | 2 +- include/MemoryManager.h | 130 ++++++++++++++++++ include/MidiEvent.h | 2 + include/MidiEventProcessor.h | 3 +- include/NotePlayHandle.h | 3 +- include/Plugin.h | 3 + include/SampleBuffer.h | 2 + include/basic_filters.h | 2 + include/engine.h | 5 +- plugins/bit_invader/bit_invader.h | 6 +- plugins/kicker/KickerOsc.h | 2 + plugins/monstro/Monstro.h | 1 + plugins/nes/Nes.h | 2 + plugins/papu/Basic_Gb_Apu.h | 2 + plugins/patman/patman.h | 7 +- plugins/sf2_player/sf2_player.h | 2 + plugins/sfxr/sfxr.h | 2 + plugins/vibed/string_container.h | 3 +- plugins/watsyn/Watsyn.h | 2 + src/core/MemoryManager.cpp | 210 ++++++++++++++++++++++++++++++ src/core/SampleBuffer.cpp | 33 ++--- src/core/main.cpp | 8 ++ 25 files changed, 415 insertions(+), 33 deletions(-) create mode 100644 include/MemoryManager.h create mode 100644 src/core/MemoryManager.cpp diff --git a/include/AudioPort.h b/include/AudioPort.h index 63a9e90ee..8d70f8306 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -22,19 +22,21 @@ * */ -#ifndef _AUDIO_PORT_H -#define _AUDIO_PORT_H +#ifndef AUDIO_PORT_H +#define AUDIO_PORT_H #include #include #include #include "Mixer.h" +#include "MemoryManager.h" class EffectChain; class AudioPort : public ThreadableJob { + MM_OPERATORS public: AudioPort( const QString & _name, bool _has_effect_chain = true ); virtual ~AudioPort(); diff --git a/include/ConfigManager.h b/include/ConfigManager.h index 716c1dc9d..567dbd1d9 100644 --- a/include/ConfigManager.h +++ b/include/ConfigManager.h @@ -34,7 +34,7 @@ #include #include "export.h" - +#include "MemoryManager.h" class engine; @@ -49,6 +49,7 @@ const QString LOCALE_PATH = "locale/"; class EXPORT ConfigManager { + MM_OPERATORS public: static inline ConfigManager * inst() { diff --git a/include/DetuningHelper.h b/include/DetuningHelper.h index 7aa1d39d4..7f702ac4a 100644 --- a/include/DetuningHelper.h +++ b/include/DetuningHelper.h @@ -23,14 +23,15 @@ * */ -#ifndef _DETUNING_HELPER_H -#define _DETUNING_HELPER_H +#ifndef DETUNING_HELPER_H +#define DETUNING_HELPER_H #include "InlineAutomation.h" - +#include "MemoryManager.h" class DetuningHelper : public InlineAutomation { + MM_OPERATORS public: DetuningHelper() : InlineAutomation() diff --git a/include/Effect.h b/include/Effect.h index 8f261d4e4..44aabbacb 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -31,7 +31,7 @@ #include "Mixer.h" #include "AutomatableModel.h" #include "TempoSyncKnobModel.h" - +#include "MemoryManager.h" class EffectChain; class EffectControls; diff --git a/include/MemoryManager.h b/include/MemoryManager.h new file mode 100644 index 000000000..ddc5596e7 --- /dev/null +++ b/include/MemoryManager.h @@ -0,0 +1,130 @@ +/* + * MemoryManager.h - A lightweight, generic memory manager for LMMS + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2007-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 MEMORY_MANAGER_H +#define MEMORY_MANAGER_H + +#include +#include +#include +#include +#include "MemoryHelper.h" + + +const int MM_CHUNK_SIZE = 64; // granularity of managed memory +const int MM_INITIAL_CHUNKS = 1024 * 1024; // how many chunks to allocate at startup - TODO: make configurable +const int MM_INCREMENT_CHUNKS = 16 * 1024; // min. amount of chunks to increment at a time + +struct MemoryPool +{ + void * m_pool; + char * m_free; + int m_chunks; + QMutex m_mutex; + + MemoryPool() : + m_pool( NULL ), + m_free( NULL ), + m_chunks( 0 ) + {} + + MemoryPool( int chunks ) : + m_chunks( chunks ) + { + m_free = (char*) MemoryHelper::alignedMalloc( chunks ); + memset( m_free, 1, chunks ); + } + + MemoryPool( const MemoryPool & mp ) : + m_pool( mp.m_pool ), + m_free( mp.m_free ), + m_chunks( mp.m_chunks ), + m_mutex() + {} + + MemoryPool & operator = ( const MemoryPool & mp ) + { + m_pool = mp.m_pool; + m_free = mp.m_free; + m_chunks = mp.m_chunks; + return *this; + } + + void * getChunks( int chunksNeeded ); + void releaseChunks( void * ptr, int chunks ); +}; + +struct PtrInfo +{ + int chunks; + MemoryPool * memPool; +}; + +typedef QVector MemoryPoolVector; +typedef QMap PointerInfoMap; + +class MemoryManager +{ +public: + static bool init(); + static void * alloc( size_t size ); + static void free( void * ptr ); + static int extend( int chunks ); // returns index of created pool (for use by alloc) + static void cleanup(); + +private: + static MemoryPoolVector s_memoryPools; + static QMutex s_poolMutex; + + static PointerInfoMap s_pointerInfo; + static QMutex s_pointerMutex; +}; + + +#define MM_OPERATORS \ +public: \ +static void * operator new ( size_t size ) \ +{ \ + return MemoryManager::alloc( size ); \ +} \ +static void * operator new[] ( size_t size ) \ +{ \ + return MemoryManager::alloc( size ); \ +} \ +static void operator delete ( void * ptr ) \ +{ \ + MemoryManager::free( ptr ); \ +} \ +static void operator delete[] ( void * ptr ) \ +{ \ + MemoryManager::free( ptr ); \ +} + +// for use in cases where overriding new/delete isn't a possibility +#define MM_ALLOC( type, count ) (type*) MemoryManager::alloc( sizeof( type ) * count ) +// and just for symmetry... +#define MM_FREE( ptr ) MemoryManager::free( ptr ) + +#endif diff --git a/include/MidiEvent.h b/include/MidiEvent.h index 1ead0d56f..82eef85cc 100644 --- a/include/MidiEvent.h +++ b/include/MidiEvent.h @@ -29,9 +29,11 @@ #include "Midi.h" #include "panning_constants.h" #include "volume.h" +#include "MemoryManager.h" class MidiEvent { + MM_OPERATORS public: MidiEvent( MidiEventTypes type = MidiActiveSensing, int8_t channel = 0, diff --git a/include/MidiEventProcessor.h b/include/MidiEventProcessor.h index 9da114577..46eacea0b 100644 --- a/include/MidiEventProcessor.h +++ b/include/MidiEventProcessor.h @@ -27,11 +27,12 @@ #include "MidiEvent.h" #include "MidiTime.h" - +#include "MemoryManager.h" // all classes being able to process MIDI-events should inherit from this class MidiEventProcessor { + MM_OPERATORS public: MidiEventProcessor() { diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 6585efc11..b1cee4190 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -30,7 +30,7 @@ #include "note.h" #include "PlayHandle.h" #include "track.h" - +#include "MemoryManager.h" class InstrumentTrack; class NotePlayHandle; @@ -42,6 +42,7 @@ typedef QList ConstNotePlayHandleList; class EXPORT NotePlayHandle : public PlayHandle, public note { + MM_OPERATORS public: void * m_pluginData; basicFilters<> * m_filter; diff --git a/include/Plugin.h b/include/Plugin.h index 4887ef0fb..a6c344b44 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -31,6 +31,8 @@ #include "JournallingObject.h" #include "Model.h" +#include "base64.h" +#include "MemoryManager.h" class QWidget; @@ -42,6 +44,7 @@ class AutomatableModel; class EXPORT Plugin : public JournallingObject, public Model { + MM_OPERATORS public: enum PluginTypes { diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 1cb5864ca..b1b8e5fd3 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -38,6 +38,7 @@ #include "lmms_math.h" #include "shared_object.h" #include "Mixer.h" +#include "MemoryManager.h" class QPainter; @@ -59,6 +60,7 @@ public: }; class EXPORT handleState { + MM_OPERATORS public: handleState( bool _varying_pitch = false, int interpolation_mode = SRC_LINEAR ); virtual ~handleState(); diff --git a/include/basic_filters.h b/include/basic_filters.h index 76b782f6d..0a8553a47 100644 --- a/include/basic_filters.h +++ b/include/basic_filters.h @@ -43,6 +43,7 @@ #include "templates.h" #include "lmms_constants.h" #include "interpolation.h" +#include "MemoryManager.h" //#include //#include @@ -50,6 +51,7 @@ template class basicFilters { + MM_OPERATORS public: enum FilterTypes { diff --git a/include/engine.h b/include/engine.h index 0b21fe74a..7ca7e78f2 100644 --- a/include/engine.h +++ b/include/engine.h @@ -23,10 +23,11 @@ */ -#ifndef _ENGINE_H -#define _ENGINE_H +#ifndef ENGINE_H +#define ENGINE_H #include "lmmsconfig.h" +#include "MemoryManager.h" #include diff --git a/plugins/bit_invader/bit_invader.h b/plugins/bit_invader/bit_invader.h index e7fff611f..d4eae0352 100644 --- a/plugins/bit_invader/bit_invader.h +++ b/plugins/bit_invader/bit_invader.h @@ -24,8 +24,8 @@ */ -#ifndef _BIT_INVADER_H -#define _BIT_INVADER_H +#ifndef BIT_INVADER_H +#define BIT_INVADER_H #include "Instrument.h" #include "InstrumentView.h" @@ -33,12 +33,14 @@ #include "knob.h" #include "pixmap_button.h" #include "led_checkbox.h" +#include "MemoryManager.h" class oscillator; class bitInvaderView; class bSynth { + MM_OPERATORS public: bSynth( float * sample, int length, NotePlayHandle * _nph, bool _interpolation, float factor, diff --git a/plugins/kicker/KickerOsc.h b/plugins/kicker/KickerOsc.h index a2b20aee8..bbe0dd584 100644 --- a/plugins/kicker/KickerOsc.h +++ b/plugins/kicker/KickerOsc.h @@ -31,11 +31,13 @@ #include "lmms_math.h" #include "interpolation.h" +#include "MemoryManager.h" template class KickerOsc { + MM_OPERATORS public: KickerOsc( const FX & fx, const float start, const float end, const float noise, const float offset, const float slope, const float env, const float diststart, const float distend, const float length ) : diff --git a/plugins/monstro/Monstro.h b/plugins/monstro/Monstro.h index fe9a1508c..8bbdcb83e 100644 --- a/plugins/monstro/Monstro.h +++ b/plugins/monstro/Monstro.h @@ -189,6 +189,7 @@ class MonstroView; class MonstroSynth { + MM_OPERATORS public: MonstroSynth( MonstroInstrument * _i, NotePlayHandle * _nph ); virtual ~MonstroSynth(); diff --git a/plugins/nes/Nes.h b/plugins/nes/Nes.h index da321642a..337d31c69 100644 --- a/plugins/nes/Nes.h +++ b/plugins/nes/Nes.h @@ -32,6 +32,7 @@ #include "TempoSyncKnob.h" #include "NotePlayHandle.h" #include "pixmap_button.h" +#include "MemoryManager.h" #define makeknob( name, x, y, hint, unit, oname ) \ @@ -80,6 +81,7 @@ class NesInstrument; class NesObject { + MM_OPERATORS public: NesObject( NesInstrument * nes, const sample_rate_t samplerate, NotePlayHandle * nph ); virtual ~NesObject(); diff --git a/plugins/papu/Basic_Gb_Apu.h b/plugins/papu/Basic_Gb_Apu.h index ee01ed578..24b9dc774 100644 --- a/plugins/papu/Basic_Gb_Apu.h +++ b/plugins/papu/Basic_Gb_Apu.h @@ -8,8 +8,10 @@ #include "gb_apu/Gb_Apu.h" #include "gb_apu/Multi_Buffer.h" +#include "MemoryManager.h" class Basic_Gb_Apu { + MM_OPERATORS public: Basic_Gb_Apu(); ~Basic_Gb_Apu(); diff --git a/plugins/patman/patman.h b/plugins/patman/patman.h index 3568663d2..9bf61db8e 100644 --- a/plugins/patman/patman.h +++ b/plugins/patman/patman.h @@ -23,14 +23,14 @@ */ -#ifndef _PATMAN_H_ -#define _PATMAN_H_ +#ifndef PATMAN_H_ +#define PATMAN_H_ #include "Instrument.h" #include "InstrumentView.h" #include "SampleBuffer.h" #include "AutomatableModel.h" - +#include "MemoryManager.h" class pixmapButton; @@ -79,6 +79,7 @@ public slots: private: typedef struct { + MM_OPERATORS SampleBuffer::handleState* state; bool tuned; SampleBuffer* sample; diff --git a/plugins/sf2_player/sf2_player.h b/plugins/sf2_player/sf2_player.h index 574bd81ed..a7b9262b6 100644 --- a/plugins/sf2_player/sf2_player.h +++ b/plugins/sf2_player/sf2_player.h @@ -37,6 +37,7 @@ #include "led_checkbox.h" #include "fluidsynth.h" #include "SampleBuffer.h" +#include "MemoryManager.h" class sf2InstrumentView; class sf2Font; @@ -173,6 +174,7 @@ signals: // A soundfont in our font-map class sf2Font { + MM_OPERATORS public: sf2Font( fluid_sfont_t * f ) : fluidFont( f ), diff --git a/plugins/sfxr/sfxr.h b/plugins/sfxr/sfxr.h index c2da90caa..d83c98283 100644 --- a/plugins/sfxr/sfxr.h +++ b/plugins/sfxr/sfxr.h @@ -34,6 +34,7 @@ #include "graph.h" #include "pixmap_button.h" #include "led_checkbox.h" +#include "MemoryManager.h" enum SfxrWaves @@ -69,6 +70,7 @@ class sfxrInstrument; class SfxrSynth { + MM_OPERATORS public: SfxrSynth( const sfxrInstrument * s ); virtual ~SfxrSynth(); diff --git a/plugins/vibed/string_container.h b/plugins/vibed/string_container.h index 1e1864a0d..7d15e3dfe 100644 --- a/plugins/vibed/string_container.h +++ b/plugins/vibed/string_container.h @@ -27,11 +27,12 @@ #include #include "vibrating_string.h" - +#include "MemoryManager.h" class stringContainer { + MM_OPERATORS public: stringContainer(const float _pitch, const sample_rate_t _sample_rate, diff --git a/plugins/watsyn/Watsyn.h b/plugins/watsyn/Watsyn.h index c4fb63a55..7b2a09bb4 100644 --- a/plugins/watsyn/Watsyn.h +++ b/plugins/watsyn/Watsyn.h @@ -35,6 +35,7 @@ #include "NotePlayHandle.h" #include "pixmap_button.h" #include +#include "MemoryManager.h" #define makeknob( name, x, y, hint, unit, oname ) \ @@ -80,6 +81,7 @@ class WatsynInstrument; class WatsynObject { + MM_OPERATORS public: WatsynObject( float * _A1wave, float * _A2wave, float * _B1wave, float * _B2wave, diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp new file mode 100644 index 000000000..09a3215c3 --- /dev/null +++ b/src/core/MemoryManager.cpp @@ -0,0 +1,210 @@ +/* + * MemoryManager.cpp - A lightweight, generic memory manager for LMMS + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2007-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 "MemoryManager.h" +#include + + +MemoryPoolVector MemoryManager::s_memoryPools; +QMutex MemoryManager::s_poolMutex; +PointerInfoMap MemoryManager::s_pointerInfo; +QMutex MemoryManager::s_pointerMutex; + + +bool MemoryManager::init() +{ + // construct first MemoryPool and allocate memory + MemoryPool m ( MM_INITIAL_CHUNKS ); + m.m_pool = MemoryHelper::alignedMalloc( MM_INITIAL_CHUNKS * MM_CHUNK_SIZE ); + s_memoryPools.append( m ); + return true; +} + + +void * MemoryManager::alloc( size_t size ) +{ + int requiredChunks = size / MM_CHUNK_SIZE + ( size % MM_CHUNK_SIZE > 0 ? 1 : 0 ); + + MemoryPool * mp = NULL; + void * ptr = NULL; + + MemoryPoolVector::iterator it = s_memoryPools.begin(); + + s_poolMutex.lock(); + while( it != s_memoryPools.end() && !ptr ) + { + ptr = ( *it ).getChunks( requiredChunks ); + if( ptr ) + { + mp = &( *it ); + } + } + s_poolMutex.unlock(); + + if( ptr ) + { + s_pointerMutex.lock(); + PtrInfo p; + p.chunks = requiredChunks; + p.memPool = mp; + s_pointerInfo[ptr] = p; + s_pointerMutex.unlock(); + return ptr; + } + + // can't find enough chunks in existing pools, so + // create a new pool that is guaranteed to have enough chunks + int moreChunks = qMax( requiredChunks, MM_INCREMENT_CHUNKS ); + int i = MemoryManager::extend( moreChunks ); + + mp = &s_memoryPools[i]; + ptr = s_memoryPools[i].getChunks( requiredChunks ); + if( ptr ) + { + s_pointerMutex.lock(); + PtrInfo p; + p.chunks = requiredChunks; + p.memPool = mp; + s_pointerInfo[ptr] = p; + s_pointerMutex.unlock(); + return ptr; + } + // still no luck? something is horribly wrong + qFatal( "MemoryManager.cpp: Couldn't allocate memory: %d chunks asked", requiredChunks ); + return NULL; +} + + +void MemoryManager::free( void * ptr ) +{ + if( ptr == NULL ) return; // let's not try to deallocate null pointers, ok? + + // fetch info on the ptr and remove + s_pointerMutex.lock(); + if( ! s_pointerInfo.contains( ptr ) ) // if we have no info on ptr, fail loudly + { + qFatal( "MemoryManager.cpp: Couldn't find pointer info for pointer: %d", (int)ptr ); + } + PtrInfo p = s_pointerInfo[ptr]; + s_pointerInfo.remove( ptr ); + s_pointerMutex.unlock(); + + p.memPool->releaseChunks( ptr, p.chunks ); +} + + +int MemoryManager::extend( int chunks ) +{ + MemoryPool m ( chunks ); + m.m_pool = MemoryHelper::alignedMalloc( chunks * MM_CHUNK_SIZE ); + + s_poolMutex.lock(); + s_memoryPools.append( m ); + int i = s_memoryPools.size() - 1; + s_poolMutex.unlock(); + + return i; +} + + +void MemoryManager::cleanup() +{ + for( MemoryPoolVector::iterator it = s_memoryPools.begin(); it != s_memoryPools.end(); ++it ) + { + MemoryHelper::alignedFree( ( *it ).m_pool ); + MemoryHelper::alignedFree( ( *it ).m_free ); + } +} + + +void * MemoryPool::getChunks( int chunksNeeded ) +{ + if( chunksNeeded > m_chunks ) // not enough chunks in this pool? + { + return NULL; + } + + m_mutex.lock(); + + // now find out if we have a long enough sequence of chunks in this pool + char last = 0; + int n = 0; + int index = -1; + bool found = false; + + for( int i = 0; i < m_chunks; ++i ) + { + if( m_free[i] ) + { + if( !last ) + { + index = i; + } + + ++n; + if( n >= chunksNeeded ) + { + found = true; + break; + } + } + else + { + n = 0; + } + + last = m_free[i]; + } + + if( found ) // if enough chunks found, return pointer to chunks + { + // set chunk flags to false so we know the chunks are in use + for( int i = 0; i < chunksNeeded; ++i ) + { + m_free[ index + i ] = 0; + } + m_mutex.unlock(); + return (char*)m_pool + ( index * MM_CHUNK_SIZE ); + } + m_mutex.unlock(); + return NULL; // out of stock, come again tomorrow! +} + + +void MemoryPool::releaseChunks( void * ptr, int chunks ) +{ + m_mutex.lock(); + + int start = ( (int)ptr - (int)m_pool ) / MM_CHUNK_SIZE; + if( start < 0 ) + { + qFatal( "MemoryManager: error at releaseChunks() - corrupt pointer info?" ); + } + + memset( &m_free[ start ], 1, chunks ); + + m_mutex.unlock(); +} diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 2f69eb7ac..957e638ae 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -61,6 +61,7 @@ #include "templates.h" #include "FileDialog.h" +#include "MemoryManager.h" SampleBuffer::SampleBuffer( const QString & _audio_file, @@ -106,7 +107,7 @@ SampleBuffer::SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ) : { if( _frames > 0 ) { - m_origData = new sampleFrame[_frames]; + m_origData = MM_ALLOC( sampleFrame, _frames ); memcpy( m_origData, _data, _frames * BYTES_PER_FRAME ); m_origFrames = _frames; } @@ -133,7 +134,7 @@ SampleBuffer::SampleBuffer( const f_cnt_t _frames ) : { if( _frames > 0 ) { - m_origData = new sampleFrame[_frames]; + m_origData = MM_ALLOC( sampleFrame, _frames ); memset( m_origData, 0, _frames * BYTES_PER_FRAME ); m_origFrames = _frames; } @@ -145,8 +146,8 @@ SampleBuffer::SampleBuffer( const f_cnt_t _frames ) : SampleBuffer::~SampleBuffer() { - delete[] m_origData; - delete[] m_data; + MM_FREE( m_origData ); + MM_FREE( m_data ); } @@ -160,14 +161,14 @@ void SampleBuffer::update( bool _keep_settings ) if( lock ) { engine::mixer()->lock(); - delete[] m_data; + MM_FREE( m_data ); } if( m_audioFile.isEmpty() && m_origData != NULL && m_origFrames > 0 ) { // TODO: reverse- and amplification-property is not covered // by following code... - m_data = new sampleFrame[m_origFrames]; + m_data = MM_ALLOC( sampleFrame, m_origFrames ); memcpy( m_data, m_origData, m_origFrames * BYTES_PER_FRAME ); if( _keep_settings == false ) { @@ -232,7 +233,7 @@ void SampleBuffer::update( bool _keep_settings ) { // sample couldn't be decoded, create buffer containing // one sample-frame - m_data = new sampleFrame[1]; + m_data = MM_ALLOC( sampleFrame, 1 ); memset( m_data, 0, sizeof( *m_data ) ); m_frames = 1; m_loopStartFrame = m_startFrame = 0; @@ -252,7 +253,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 = new sampleFrame[1]; + m_data = MM_ALLOC( sampleFrame, 1 ); memset( m_data, 0, sizeof( *m_data ) ); m_frames = 1; m_loopStartFrame = m_startFrame = 0; @@ -273,7 +274,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 = new sampleFrame[_frames]; + m_data = MM_ALLOC( sampleFrame, _frames ); const int ch = ( _channels > 1 ) ? 1 : 0; // if reversing is on, we also reverse when @@ -313,7 +314,7 @@ void SampleBuffer::directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _ { - m_data = new sampleFrame[_frames]; + m_data = MM_ALLOC( sampleFrame, _frames ); const int ch = ( _channels > 1 ) ? 1 : 0; // if reversing is on, we also reverse when @@ -356,9 +357,9 @@ void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, { SampleBuffer * resampled = resample( this, _src_sr, engine::mixer()->baseSampleRate() ); - delete[] m_data; + MM_FREE( m_data ); m_frames = resampled->frames(); - m_data = new sampleFrame[m_frames]; + m_data = MM_ALLOC( sampleFrame, m_frames ); memcpy( m_data, resampled->data(), m_frames * sizeof( sampleFrame ) ); delete resampled; @@ -1336,15 +1337,15 @@ void SampleBuffer::loadFromBase64( const QString & _data ) printf("%d\n", (int) orig_data.size() ); m_origFrames = orig_data.size() / sizeof( sampleFrame ); - delete[] m_origData; - m_origData = new sampleFrame[m_origFrames]; + 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_origFrames = dsize / sizeof( sampleFrame ); - delete[] m_origData; - m_origData = new sampleFrame[m_origFrames]; + MM_FREE( m_origData ); + m_origData = MM_ALLOC( sampleFrame, m_origFrames ); memcpy( m_origData, dst, dsize ); #endif diff --git a/src/core/main.cpp b/src/core/main.cpp index d33bf118c..8626c20f9 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -64,6 +64,7 @@ #include #endif +#include "MemoryManager.h" #include "ConfigManager.h" #include "embed.h" #include "engine.h" @@ -97,6 +98,9 @@ inline void loadTranslation( const QString & _tname, int main( int argc, char * * argv ) { + // initialize memory manager + MemoryManager::init(); + // intialize RNG srand( getpid() + time( 0 ) ); @@ -529,6 +533,10 @@ int main( int argc, char * * argv ) const int ret = app->exec(); delete app; + + // cleanup memory manager + MemoryManager::cleanup(); + return( ret ); } From 75770b4d2e23e3171cf820894b32589135f66889 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sat, 23 Aug 2014 00:52:18 +0300 Subject: [PATCH 02/28] Fix 64 bit, increase mm usage --- include/AutomatableModel.h | 3 ++- include/DataFile.h | 3 ++- include/Effect.h | 1 + include/Instrument.h | 1 + include/RingBuffer.h | 2 ++ include/SampleBuffer.h | 1 + include/ValueBuffer.h | 2 ++ plugins/organic/organic.h | 1 + plugins/sid/sid_instrument.h | 1 + plugins/triple_oscillator/TripleOscillator.h | 1 + src/core/MemoryManager.cpp | 4 ++-- src/core/SampleBuffer.cpp | 7 +++++-- 12 files changed, 21 insertions(+), 6 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 49e66c762..92f7df68e 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -32,7 +32,7 @@ #include "Model.h" #include "MidiTime.h" #include "ValueBuffer.h" - +#include "MemoryManager.h" // simple way to map a property of a view to a model #define mapPropertyFromModelPtr(type,getfunc,setfunc,modelname) \ @@ -66,6 +66,7 @@ class ControllerConnection; class EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT + MM_OPERATORS public: typedef QVector AutoModelVector; diff --git a/include/DataFile.h b/include/DataFile.h index 651bd275e..57db8905f 100644 --- a/include/DataFile.h +++ b/include/DataFile.h @@ -32,10 +32,11 @@ #include "export.h" #include "lmms_basics.h" - +#include "MemoryManager.h" class EXPORT DataFile : public QDomDocument { + MM_OPERATORS public: enum Types { diff --git a/include/Effect.h b/include/Effect.h index 44aabbacb..ded15c14e 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -39,6 +39,7 @@ class EffectControls; class EXPORT Effect : public Plugin { + MM_OPERATORS public: Effect( const Plugin::Descriptor * _desc, Model * _parent, diff --git a/include/Instrument.h b/include/Instrument.h index 904cf01fd..7d367e5ce 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -43,6 +43,7 @@ class track; class EXPORT Instrument : public Plugin { + MM_OPERATORS public: enum Flag { diff --git a/include/RingBuffer.h b/include/RingBuffer.h index c668d1c33..ec4aa1619 100644 --- a/include/RingBuffer.h +++ b/include/RingBuffer.h @@ -31,10 +31,12 @@ #include #include "lmms_basics.h" #include "lmms_math.h" +#include "MemoryManager.h" class RingBuffer : public QObject { Q_OBJECT + MM_OPERATORS public: /** \brief Constructs a ringbuffer of specified size, will not care about samplerate changes * \param size The size of the buffer in frames. The actual size will be size + period size diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index b1b8e5fd3..cb5d53c90 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -52,6 +52,7 @@ const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; class EXPORT SampleBuffer : public QObject, public sharedObject { Q_OBJECT + MM_OPERATORS public: enum LoopMode { LoopOff = 0, diff --git a/include/ValueBuffer.h b/include/ValueBuffer.h index 7d9778721..9c5e8bf45 100644 --- a/include/ValueBuffer.h +++ b/include/ValueBuffer.h @@ -29,9 +29,11 @@ #include #include "interpolation.h" #include +#include "MemoryManager.h" class ValueBuffer { + MM_OPERATORS public: ValueBuffer() { diff --git a/plugins/organic/organic.h b/plugins/organic/organic.h index 92bd79cff..14418d283 100644 --- a/plugins/organic/organic.h +++ b/plugins/organic/organic.h @@ -75,6 +75,7 @@ const float CENT = 1.0f / 1200.0f; class OscillatorObject : public Model { Q_OBJECT + MM_OPERATORS private: int m_numOscillators; IntModel m_waveShape; diff --git a/plugins/sid/sid_instrument.h b/plugins/sid/sid_instrument.h index e6e733743..45b6e7d4d 100644 --- a/plugins/sid/sid_instrument.h +++ b/plugins/sid/sid_instrument.h @@ -41,6 +41,7 @@ class pixmapButton; class voiceObject : public Model { Q_OBJECT + MM_OPERATORS public: enum WaveForm { SquareWave = 0, diff --git a/plugins/triple_oscillator/TripleOscillator.h b/plugins/triple_oscillator/TripleOscillator.h index a271c23e5..c10b733bb 100644 --- a/plugins/triple_oscillator/TripleOscillator.h +++ b/plugins/triple_oscillator/TripleOscillator.h @@ -43,6 +43,7 @@ const int NUM_OF_OSCILLATORS = 3; class OscillatorObject : public Model { + MM_OPERATORS Q_OBJECT public: OscillatorObject( Model * _parent, int _idx ); diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp index 09a3215c3..72310a9a0 100644 --- a/src/core/MemoryManager.cpp +++ b/src/core/MemoryManager.cpp @@ -106,7 +106,7 @@ void MemoryManager::free( void * ptr ) s_pointerMutex.lock(); if( ! s_pointerInfo.contains( ptr ) ) // if we have no info on ptr, fail loudly { - qFatal( "MemoryManager.cpp: Couldn't find pointer info for pointer: %d", (int)ptr ); + qFatal( "MemoryManager.cpp: Couldn't find pointer info for pointer: %d", (intptr_t)ptr ); } PtrInfo p = s_pointerInfo[ptr]; s_pointerInfo.remove( ptr ); @@ -198,7 +198,7 @@ void MemoryPool::releaseChunks( void * ptr, int chunks ) { m_mutex.lock(); - int start = ( (int)ptr - (int)m_pool ) / MM_CHUNK_SIZE; + int start = ( (intptr_t)ptr - (intptr_t)m_pool ) / MM_CHUNK_SIZE; if( start < 0 ) { qFatal( "MemoryManager: error at releaseChunks() - corrupt pointer info?" ); diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 957e638ae..3612554f3 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -753,7 +753,10 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, } } - if( tmp != NULL ) delete[] tmp; + if( tmp != NULL ) + { + MM_FREE( tmp ); + } _state->setBackwards( is_backwards ); _state->setFrameIndex( play_frame ); @@ -795,7 +798,7 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, return m_data + _index; } - *_tmp = new sampleFrame[_frames]; + *_tmp = MM_ALLOC( sampleFrame, _frames ); if( _loopmode == LoopOff ) { From 9972cb3d4df800e5dd48d638ec972f65fc2cd3e8 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sat, 23 Aug 2014 11:57:13 +0300 Subject: [PATCH 03/28] Fixes --- src/core/MemoryManager.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp index 72310a9a0..34de7de59 100644 --- a/src/core/MemoryManager.cpp +++ b/src/core/MemoryManager.cpp @@ -61,6 +61,7 @@ void * MemoryManager::alloc( size_t size ) { mp = &( *it ); } + ++it; } s_poolMutex.unlock(); @@ -68,8 +69,8 @@ void * MemoryManager::alloc( size_t size ) { s_pointerMutex.lock(); PtrInfo p; - p.chunks = requiredChunks; - p.memPool = mp; + p.chunks = requiredChunks; + p.memPool = mp; s_pointerInfo[ptr] = p; s_pointerMutex.unlock(); return ptr; @@ -86,8 +87,8 @@ void * MemoryManager::alloc( size_t size ) { s_pointerMutex.lock(); PtrInfo p; - p.chunks = requiredChunks; - p.memPool = mp; + p.chunks = requiredChunks; + p.memPool = mp; s_pointerInfo[ptr] = p; s_pointerMutex.unlock(); return ptr; @@ -100,13 +101,17 @@ void * MemoryManager::alloc( size_t size ) void MemoryManager::free( void * ptr ) { - if( ptr == NULL ) return; // let's not try to deallocate null pointers, ok? + if( ptr == NULL ) + { + qDebug( "MemoryManager: Null pointer deallocation attempted" ); + return; // let's not try to deallocate null pointers, ok? + } // fetch info on the ptr and remove s_pointerMutex.lock(); if( ! s_pointerInfo.contains( ptr ) ) // if we have no info on ptr, fail loudly { - qFatal( "MemoryManager.cpp: Couldn't find pointer info for pointer: %d", (intptr_t)ptr ); + qFatal( "MemoryManager: Couldn't find pointer info for pointer: %p", ptr ); } PtrInfo p = s_pointerInfo[ptr]; s_pointerInfo.remove( ptr ); @@ -151,8 +156,8 @@ void * MemoryPool::getChunks( int chunksNeeded ) // now find out if we have a long enough sequence of chunks in this pool char last = 0; - int n = 0; - int index = -1; + intptr_t n = 0; + intptr_t index = -1; bool found = false; for( int i = 0; i < m_chunks; ++i ) @@ -182,7 +187,7 @@ void * MemoryPool::getChunks( int chunksNeeded ) if( found ) // if enough chunks found, return pointer to chunks { // set chunk flags to false so we know the chunks are in use - for( int i = 0; i < chunksNeeded; ++i ) + for( intptr_t i = 0; i < chunksNeeded; ++i ) { m_free[ index + i ] = 0; } @@ -198,7 +203,7 @@ void MemoryPool::releaseChunks( void * ptr, int chunks ) { m_mutex.lock(); - int start = ( (intptr_t)ptr - (intptr_t)m_pool ) / MM_CHUNK_SIZE; + intptr_t start = ( (intptr_t)ptr - (intptr_t)m_pool ) / MM_CHUNK_SIZE; if( start < 0 ) { qFatal( "MemoryManager: error at releaseChunks() - corrupt pointer info?" ); From 5e4308507bee7130e24671b4e9d16515978e8375 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sat, 23 Aug 2014 16:04:50 +0300 Subject: [PATCH 04/28] More fixes --- include/InstrumentTrack.h | 1 + include/MemoryManager.h | 28 +++++++++++++ include/NotePlayHandle.h | 10 ++++- include/Oscillator.h | 5 ++- include/track.h | 2 + plugins/organic/organic.h | 2 + plugins/triple_oscillator/TripleOscillator.h | 2 + src/core/MemoryManager.cpp | 1 + src/core/NotePlayHandle.cpp | 41 +++++++++++--------- src/tracks/InstrumentTrack.cpp | 6 +-- 10 files changed, 71 insertions(+), 27 deletions(-) diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index e648a00e3..eba606f00 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -60,6 +60,7 @@ class trackLabelButton; class EXPORT InstrumentTrack : public track, public MidiEventProcessor { Q_OBJECT + MM_OPERATORS mapPropertyFromModel(int,getVolume,setVolume,m_volumeModel); public: InstrumentTrack( TrackContainer* tc ); diff --git a/include/MemoryManager.h b/include/MemoryManager.h index ddc5596e7..e1df35e20 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -127,4 +127,32 @@ static void operator delete[] ( void * ptr ) \ // and just for symmetry... #define MM_FREE( ptr ) MemoryManager::free( ptr ) + + +// for debugging purposes + +#define MM_OPERATORS_DEBUG \ +public: \ +static void * operator new ( size_t size ) \ +{ \ + qDebug( "MM_OPERATORS_DEBUG: new called for %d bytes", size ); \ + return MemoryManager::alloc( size ); \ +} \ +static void * operator new[] ( size_t size ) \ +{ \ + qDebug( "MM_OPERATORS_DEBUG: new[] called for %d bytes", size ); \ + return MemoryManager::alloc( size ); \ +} \ +static void operator delete ( void * ptr ) \ +{ \ + qDebug( "MM_OPERATORS_DEBUG: delete called for %p", ptr ); \ + MemoryManager::free( ptr ); \ +} \ +static void operator delete[] ( void * ptr ) \ +{ \ + qDebug( "MM_OPERATORS_DEBUG: delete[] called for %p", ptr ); \ + MemoryManager::free( ptr ); \ +} + + #endif diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index b1cee4190..11479f368 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -91,8 +91,6 @@ public: return m_frequency; } - void updateFrequency(); - /*! Returns frequency without pitch wheel influence */ float unpitchedFrequency() const { @@ -240,10 +238,15 @@ public: return m_songGlobalParentOffset; } + void setFrequencyUpdate() + { + m_frequencyNeedsUpdate = true; + } private: class BaseDetuning { + MM_OPERATORS public: BaseDetuning( DetuningHelper* detuning ); @@ -263,6 +266,8 @@ private: } ; + void updateFrequency(); + InstrumentTrack* m_instrumentTrack; // needed for calling // InstrumentTrack::playNote f_cnt_t m_frames; // total frames to play @@ -298,6 +303,7 @@ private: const int m_midiChannel; const Origin m_origin; + bool m_frequencyNeedsUpdate; // used to update pitch } ; #endif diff --git a/include/Oscillator.h b/include/Oscillator.h index da3e8ceae..de7aee66b 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -22,8 +22,8 @@ * */ -#ifndef _OSCILLATOR_H -#define _OSCILLATOR_H +#ifndef OSCILLATOR_H +#define OSCILLATOR_H #include "lmmsconfig.h" @@ -43,6 +43,7 @@ class IntModel; class EXPORT Oscillator { + MM_OPERATORS public: enum WaveShapes { diff --git a/include/track.h b/include/track.h index d57f01110..3c9812f62 100644 --- a/include/track.h +++ b/include/track.h @@ -76,6 +76,7 @@ const int TCO_BORDER_WIDTH = 2; class trackContentObject : public Model, public JournallingObject { Q_OBJECT + MM_OPERATORS mapPropertyFromModel(bool,isMuted,setMuted,m_mutedModel); mapPropertyFromModel(bool,isSolo,setSolo,m_soloModel); public: @@ -406,6 +407,7 @@ signals: class EXPORT track : public Model, public JournallingObject { Q_OBJECT + MM_OPERATORS mapPropertyFromModel(bool,isMuted,setMuted,m_mutedModel); mapPropertyFromModel(bool,isSolo,setSolo,m_soloModel); public: diff --git a/plugins/organic/organic.h b/plugins/organic/organic.h index 14418d283..e41349763 100644 --- a/plugins/organic/organic.h +++ b/plugins/organic/organic.h @@ -150,6 +150,7 @@ private: struct oscPtr { + MM_OPERATORS Oscillator * oscLeft; Oscillator * oscRight; } ; @@ -181,6 +182,7 @@ private: struct OscillatorKnobs { + MM_OPERATORS OscillatorKnobs( knob * h, knob * v, diff --git a/plugins/triple_oscillator/TripleOscillator.h b/plugins/triple_oscillator/TripleOscillator.h index c10b733bb..622472080 100644 --- a/plugins/triple_oscillator/TripleOscillator.h +++ b/plugins/triple_oscillator/TripleOscillator.h @@ -124,6 +124,7 @@ private: struct oscPtr { + MM_OPERATORS Oscillator * oscLeft; Oscillator * oscRight; } ; @@ -151,6 +152,7 @@ private: struct OscillatorKnobs { + MM_OPERATORS OscillatorKnobs( knob * v, knob * p, knob * c, diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp index 34de7de59..c901d9bf2 100644 --- a/src/core/MemoryManager.cpp +++ b/src/core/MemoryManager.cpp @@ -26,6 +26,7 @@ #include "MemoryManager.h" #include +#include MemoryPoolVector MemoryManager::s_memoryPools; diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 5b428d26a..89e472a26 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -76,7 +76,8 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, m_baseDetuning( NULL ), m_songGlobalParentOffset( 0 ), m_midiChannel( midiEventChannel >= 0 ? midiEventChannel : instrumentTrack->midiPort()->realOutputChannel() ), - m_origin( origin ) + m_origin( origin ), + m_frequencyNeedsUpdate( false ) { lock(); if( hasParent() == false ) @@ -97,6 +98,24 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, updateFrequency(); setFrames( _frames ); + + // inform attached components about new MIDI note (used for recording in Piano Roll) + if( m_origin == OriginMidiInput ) + { + m_instrumentTrack->midiNoteOn( *this ); + } + + if( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) + { + const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); + + // send MidiNoteOn event + m_instrumentTrack->processOutEvent( + MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ), + MidiTime::fromFrames( offset(), engine::framesPerTick() ), + offset() ); + } + unlock(); } @@ -189,25 +208,9 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) } lock(); - - if( m_totalFramesPlayed == 0 ) + if( m_frequencyNeedsUpdate ) { - // inform attached components about new MIDI note (used for recording in Piano Roll) - if( m_origin == OriginMidiInput ) - { - m_instrumentTrack->midiNoteOn( *this ); - } - - if( hasParent() || ! m_instrumentTrack->isArpeggioEnabled() ) - { - const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); - - // send MidiNoteOn event - m_instrumentTrack->processOutEvent( - MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ), - MidiTime::fromFrames( offset(), engine::framesPerTick() ), - offset() ); - } + updateFrequency(); } // number of frames that can be played this period diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index dfc4e9278..c57603d28 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -148,7 +148,7 @@ InstrumentTrack::~InstrumentTrack() silenceAllNotes( true ); // now we're save deleting the instrument - delete m_instrument; + if( m_instrument ) delete m_instrument; } @@ -538,9 +538,7 @@ void InstrumentTrack::updateBaseNote() for( NotePlayHandleList::Iterator it = m_processHandles.begin(); it != m_processHandles.end(); ++it ) { - ( *it )->lock(); - ( *it )->updateFrequency(); - ( *it )->unlock(); + ( *it )->setFrequencyUpdate(); } } From 42e67d27a14ae0dda7287e100c0808e184e8fee0 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sat, 23 Aug 2014 20:37:21 +0300 Subject: [PATCH 05/28] Add dedicated manager for noteplayhandles This caches and reuses nph's independently of the generic memory manager. --- include/Mixer.h | 15 +---- include/NotePlayHandle.h | 43 ++++++++++++-- include/PlayHandle.h | 10 +++- src/core/InstrumentFunctions.cpp | 4 +- src/core/Mixer.cpp | 42 ++++++++++++-- src/core/NotePlayHandle.cpp | 83 ++++++++++++++++++++++++++-- src/core/PresetPreviewPlayHandle.cpp | 4 +- src/core/main.cpp | 7 ++- src/tracks/InstrumentTrack.cpp | 4 +- 9 files changed, 175 insertions(+), 37 deletions(-) diff --git a/include/Mixer.h b/include/Mixer.h index 15c23e54e..d9de461eb 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -216,20 +216,7 @@ public: // play-handle stuff - bool addPlayHandle( PlayHandle* handle ) - { - if( criticalXRuns() == false ) - { - m_playHandleMutex.lock(); - m_newPlayHandles.append( handle ); - m_playHandleMutex.unlock(); - return true; - } - - delete handle; - - return false; - } + bool addPlayHandle( PlayHandle* handle ); void removePlayHandle( PlayHandle* handle ); diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 11479f368..94581f82e 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -57,7 +57,7 @@ public: OriginCount }; typedef Origins Origin; - + NotePlayHandle( InstrumentTrack* instrumentTrack, const f_cnt_t offset, const f_cnt_t frames, @@ -65,7 +65,13 @@ public: NotePlayHandle* parent = NULL, int midiEventChannel = -1, Origin origin = OriginPattern ); - virtual ~NotePlayHandle(); + virtual ~NotePlayHandle() {} + void done(); + + void * operator new ( size_t size, void * p ) + { + return p; + } virtual void setVolume( volume_t volume ); virtual void setPanning( panning_t panning ); @@ -292,7 +298,7 @@ private: bpm_t m_origTempo; // original tempo f_cnt_t m_origFrames; // original m_frames - const int m_origBaseNote; + int m_origBaseNote; float m_frequency; float m_unpitchedFrequency; @@ -300,10 +306,37 @@ private: BaseDetuning* m_baseDetuning; MidiTime m_songGlobalParentOffset; - const int m_midiChannel; - const Origin m_origin; + int m_midiChannel; + Origin m_origin; bool m_frequencyNeedsUpdate; // used to update pitch } ; + +const int INITIAL_NPH_CACHE = 256; +const int NPH_CACHE_INCREMENT = 16; + +class NotePlayHandleManager +{ + MM_OPERATORS +public: + static void init(); + static NotePlayHandle * acquire( InstrumentTrack* instrumentTrack, + const f_cnt_t offset, + const f_cnt_t frames, + const note& noteToPlay, + NotePlayHandle* parent = NULL, + int midiEventChannel = -1, + NotePlayHandle::Origin origin = NotePlayHandle::OriginPattern ); + static void release( NotePlayHandle * nph ); + static void extend( int i ); + static void cleanup(); + +private: + static NotePlayHandleList s_nphCache; + static NotePlayHandleList s_available; + static QMutex s_mutex; +}; + + #endif diff --git a/include/PlayHandle.h b/include/PlayHandle.h index ba7609e42..22849c9d0 100644 --- a/include/PlayHandle.h +++ b/include/PlayHandle.h @@ -55,6 +55,14 @@ public: { } + PlayHandle & operator = ( PlayHandle & p ) + { + m_type = p.m_type; + m_offset = p.m_offset; + m_affinity = p.m_affinity; + return *this; + } + virtual ~PlayHandle() { } @@ -119,7 +127,7 @@ public: private: Type m_type; f_cnt_t m_offset; - const QThread* m_affinity; + QThread* m_affinity; QMutex m_processingLock; } ; diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 035f309ff..3d1149c01 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -260,7 +260,7 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) // create sub-note-play-handle, only note is // different - new NotePlayHandle( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, + NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, _n, -1, NotePlayHandle::OriginNoteStacking ); } } @@ -471,7 +471,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // create sub-note-play-handle, only ptr to note is different // and is_arp_note=true - new NotePlayHandle( _n->instrumentTrack(), + NotePlayHandleManager::acquire( _n->instrumentTrack(), ( ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->offset() : _n->offset() ) + frames_processed, gated_frames, note( MidiTime( 0 ), MidiTime( 0 ), sub_note_key, (volume_t) qRound( _n->getVolume() * vol_level ), diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 1f19999e3..e2b80ba42 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -355,7 +355,11 @@ const surroundSampleFrame * Mixer::renderNextBuffer() if( it != m_playHandles.end() ) { - delete *it; + if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) + { + NotePlayHandleManager::release( (NotePlayHandle*) *it ); + } + else delete *it; m_playHandles.erase( it ); } @@ -406,7 +410,11 @@ const surroundSampleFrame * Mixer::renderNextBuffer() } if( ( *it )->isFinished() ) { - delete *it; + if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) + { + NotePlayHandleManager::release( (NotePlayHandle*) *it ); + } + else delete *it; it = m_playHandles.erase( it ); } else @@ -652,6 +660,24 @@ void Mixer::removeAudioPort( AudioPort * _port ) } +bool Mixer::addPlayHandle( PlayHandle* handle ) +{ + if( criticalXRuns() == false ) + { + m_playHandleMutex.lock(); + m_newPlayHandles.append( handle ); + m_playHandleMutex.unlock(); + return true; + } + + if( handle->type() == PlayHandle::TypeNotePlayHandle ) + { + NotePlayHandleManager::release( (NotePlayHandle*)handle ); + } + else delete handle; + + return false; +} void Mixer::removePlayHandle( PlayHandle * _ph ) @@ -668,7 +694,11 @@ void Mixer::removePlayHandle( PlayHandle * _ph ) if( it != m_playHandles.end() ) { m_playHandles.erase( it ); - delete _ph; + if( _ph->type() == PlayHandle::TypeNotePlayHandle ) + { + NotePlayHandleManager::release( (NotePlayHandle*) _ph ); + } + else delete _ph; } unlockPlayHandleRemoval(); } @@ -689,7 +719,11 @@ void Mixer::removePlayHandles( track * _track, bool removeIPHs ) { if( ( *it )->isFromTrack( _track ) && ( removeIPHs || ( *it )->type() != PlayHandle::TypeInstrumentPlayHandle ) ) { - delete *it; + if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) + { + NotePlayHandleManager::release( (NotePlayHandle*) *it ); + } + else delete *it; it = m_playHandles.erase( it ); } else diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 89e472a26..694703571 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -120,9 +120,7 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, } - - -NotePlayHandle::~NotePlayHandle() +void NotePlayHandle::done() { lock(); noteOff( 0 ); @@ -149,7 +147,7 @@ NotePlayHandle::~NotePlayHandle() foreach( NotePlayHandle * n, m_subNotes ) { - delete n; + NotePlayHandleManager::release( n ); } m_subNotes.clear(); @@ -302,7 +300,7 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) n->play( _working_buffer ); if( n->isFinished() ) { - delete n; + NotePlayHandleManager::release( n ); } } @@ -548,3 +546,78 @@ void NotePlayHandle::resize( const bpm_t _new_tempo ) } +NotePlayHandleList NotePlayHandleManager::s_nphCache; +NotePlayHandleList NotePlayHandleManager::s_available; +QMutex NotePlayHandleManager::s_mutex; + + +void NotePlayHandleManager::init() +{ + // make sure the containers have more room than we need so that they don't need to do reallocations + s_nphCache.reserve( 1024 ); + s_available.reserve( 1024 ); + + NotePlayHandle * n = MM_ALLOC( NotePlayHandle, INITIAL_NPH_CACHE ); + + for( int i=0; i < INITIAL_NPH_CACHE; ++i ) + { + s_nphCache += n; + s_available += n; + ++n; + } +} + + +NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrack, + const f_cnt_t offset, + const f_cnt_t frames, + const note& noteToPlay, + NotePlayHandle* parent, + int midiEventChannel, + NotePlayHandle::Origin origin ) +{ + if( s_available.isEmpty() ) + { + extend( NPH_CACHE_INCREMENT ); + } + + s_mutex.lock(); + NotePlayHandle * nph = s_available.takeFirst(); + s_mutex.unlock(); + + new( (void*)nph ) NotePlayHandle( instrumentTrack, offset, frames, noteToPlay, parent, midiEventChannel, origin ); + return nph; +} + + +void NotePlayHandleManager::release( NotePlayHandle * nph ) +{ + nph->done(); + s_mutex.lock(); + s_available += nph; + s_mutex.unlock(); +} + + +void NotePlayHandleManager::extend( int i ) +{ + NotePlayHandle * n = MM_ALLOC( NotePlayHandle, i ); + + s_mutex.lock(); + for( int j=0; j < i; ++j ) + { + s_nphCache += n; + s_available += n; + ++n; + } + s_mutex.unlock(); +} + + +void NotePlayHandleManager::cleanup() +{ + foreach( NotePlayHandle * n, s_nphCache ) + { + delete n; + } +} diff --git a/src/core/PresetPreviewPlayHandle.cpp b/src/core/PresetPreviewPlayHandle.cpp index b518a1283..4847b8e97 100644 --- a/src/core/PresetPreviewPlayHandle.cpp +++ b/src/core/PresetPreviewPlayHandle.cpp @@ -160,7 +160,7 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, midiPort()->setMode( MidiPort::Disabled ); // create note-play-handle for it - m_previewNote = new NotePlayHandle( + m_previewNote = NotePlayHandleManager::acquire( s_previewTC->previewInstrumentTrack(), 0, typeInfo::max() / 2, note( 0, 0, DefaultKey, 100 ) ); @@ -184,7 +184,7 @@ PresetPreviewPlayHandle::~PresetPreviewPlayHandle() // then set according state s_previewTC->setPreviewNote( NULL ); } - delete m_previewNote; + NotePlayHandleManager::release( m_previewNote ); s_previewTC->unlockData(); } diff --git a/src/core/main.cpp b/src/core/main.cpp index 8626c20f9..6b4481f78 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -66,6 +66,7 @@ #include "MemoryManager.h" #include "ConfigManager.h" +#include "NotePlayHandle.h" #include "embed.h" #include "engine.h" #include "LmmsStyle.h" @@ -98,8 +99,9 @@ inline void loadTranslation( const QString & _tname, int main( int argc, char * * argv ) { - // initialize memory manager + // initialize memory managers MemoryManager::init(); + NotePlayHandleManager::init(); // intialize RNG srand( getpid() + time( 0 ) ); @@ -534,8 +536,9 @@ int main( int argc, char * * argv ) const int ret = app->exec(); delete app; - // cleanup memory manager + // cleanup memory managers MemoryManager::cleanup(); + NotePlayHandleManager::cleanup(); return( ret ); } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index c57603d28..98bdd817f 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -278,7 +278,7 @@ void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& ti m_notesMutex.lock(); if( m_notes[event.key()] == NULL ) { - nph = new NotePlayHandle( this, offset, + nph = NotePlayHandleManager::acquire( this, offset, typeInfo::max() / 2, note( MidiTime(), MidiTime(), event.key(), event.volume( midiPort()->baseVelocity() ) ), NULL, event.channel(), @@ -668,7 +668,7 @@ bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, cur_note->length().frames( frames_per_tick ); - NotePlayHandle* notePlayHandle = new NotePlayHandle( this, _offset, note_frames, *cur_note ); + NotePlayHandle* notePlayHandle = NotePlayHandleManager::acquire( this, _offset, note_frames, *cur_note ); notePlayHandle->setBBTrack( bb_track ); // are we playing global song? if( _tco_num < 0 ) From 8fb8c683f9bd6cb3a4871c77663f215580b5a8a1 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 13:38:48 +0300 Subject: [PATCH 06/28] Changing and fixing some stuff - QHash is better to use than QMap in MemoryManager: faster lookups, able to reserve memory in advance - Also: reserve memory in advance for the QVector and QHash, so we don't get needles allocs for them - No need to do cleanup for the nph manager, as it uses the generic manager for allocs, and that already gets cleaned up --- include/MemoryManager.h | 4 ++-- include/NotePlayHandle.h | 1 - src/core/MemoryManager.cpp | 2 ++ src/core/NotePlayHandle.cpp | 9 --------- src/core/main.cpp | 1 - 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index e1df35e20..5ff2d34ff 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include "MemoryHelper.h" @@ -83,7 +83,7 @@ struct PtrInfo }; typedef QVector MemoryPoolVector; -typedef QMap PointerInfoMap; +typedef QHash PointerInfoMap; class MemoryManager { diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 94581f82e..15efb82aa 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -330,7 +330,6 @@ public: NotePlayHandle::Origin origin = NotePlayHandle::OriginPattern ); static void release( NotePlayHandle * nph ); static void extend( int i ); - static void cleanup(); private: static NotePlayHandleList s_nphCache; diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp index c901d9bf2..83a7942ae 100644 --- a/src/core/MemoryManager.cpp +++ b/src/core/MemoryManager.cpp @@ -37,6 +37,8 @@ QMutex MemoryManager::s_pointerMutex; bool MemoryManager::init() { + s_memoryPools.reserve( 64 ); + s_pointerInfo.reserve( 4096 ); // construct first MemoryPool and allocate memory MemoryPool m ( MM_INITIAL_CHUNKS ); m.m_pool = MemoryHelper::alignedMalloc( MM_INITIAL_CHUNKS * MM_CHUNK_SIZE ); diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 694703571..fc8e33f4c 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -612,12 +612,3 @@ void NotePlayHandleManager::extend( int i ) } s_mutex.unlock(); } - - -void NotePlayHandleManager::cleanup() -{ - foreach( NotePlayHandle * n, s_nphCache ) - { - delete n; - } -} diff --git a/src/core/main.cpp b/src/core/main.cpp index 6b4481f78..5b3b72a4a 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -538,7 +538,6 @@ int main( int argc, char * * argv ) // cleanup memory managers MemoryManager::cleanup(); - NotePlayHandleManager::cleanup(); return( ret ); } From 3a9e9cc075ee8f6e34c77587ca47cdfada2c7a6f Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 14:42:09 +0300 Subject: [PATCH 07/28] Use ReadWriteLock for the pools container instead of Mutex --- include/MemoryManager.h | 3 ++- src/core/MemoryManager.cpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index 5ff2d34ff..aa154baff 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "MemoryHelper.h" @@ -96,7 +97,7 @@ public: private: static MemoryPoolVector s_memoryPools; - static QMutex s_poolMutex; + static QReadWriteLock s_poolMutex; static PointerInfoMap s_pointerInfo; static QMutex s_pointerMutex; diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp index 83a7942ae..0464a40d8 100644 --- a/src/core/MemoryManager.cpp +++ b/src/core/MemoryManager.cpp @@ -30,7 +30,7 @@ MemoryPoolVector MemoryManager::s_memoryPools; -QMutex MemoryManager::s_poolMutex; +QReadWriteLock MemoryManager::s_poolMutex; PointerInfoMap MemoryManager::s_pointerInfo; QMutex MemoryManager::s_pointerMutex; @@ -56,7 +56,7 @@ void * MemoryManager::alloc( size_t size ) MemoryPoolVector::iterator it = s_memoryPools.begin(); - s_poolMutex.lock(); + s_poolMutex.lockForRead(); while( it != s_memoryPools.end() && !ptr ) { ptr = ( *it ).getChunks( requiredChunks ); @@ -129,7 +129,7 @@ int MemoryManager::extend( int chunks ) MemoryPool m ( chunks ); m.m_pool = MemoryHelper::alignedMalloc( chunks * MM_CHUNK_SIZE ); - s_poolMutex.lock(); + s_poolMutex.lockForWrite(); s_memoryPools.append( m ); int i = s_memoryPools.size() - 1; s_poolMutex.unlock(); From a8211873b56ae5decd23e6894d437038120f0325 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 15:44:36 +0300 Subject: [PATCH 08/28] Fix arpeggio to work better with the new way to handle note offsets Ok, not really related to memory management, but was something that needed doing and it's easier to test things when things work properly --- include/NotePlayHandle.h | 2 ++ src/core/InstrumentFunctions.cpp | 16 ++++++++++------ src/core/NotePlayHandle.cpp | 27 +++++++++++---------------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 15efb82aa..9c4c1d2b4 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -31,6 +31,7 @@ #include "PlayHandle.h" #include "track.h" #include "MemoryManager.h" +#include class InstrumentTrack; class NotePlayHandle; @@ -335,6 +336,7 @@ private: static NotePlayHandleList s_nphCache; static NotePlayHandleList s_available; static QMutex s_mutex; + static QAtomicInt s_availableIndex; }; diff --git a/src/core/InstrumentFunctions.cpp b/src/core/InstrumentFunctions.cpp index 3d1149c01..1afc1cb23 100644 --- a/src/core/InstrumentFunctions.cpp +++ b/src/core/InstrumentFunctions.cpp @@ -260,8 +260,10 @@ void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) // create sub-note-play-handle, only note is // different - NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, - _n, -1, NotePlayHandle::OriginNoteStacking ); + engine::mixer()->addPlayHandle( + NotePlayHandleManager::acquire( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, + _n, -1, NotePlayHandle::OriginNoteStacking ) + ); } } } @@ -377,7 +379,7 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) cnphv.first()->totalFramesPlayed() : _n->totalFramesPlayed() ) + arp_frames - 1; // used for loop - f_cnt_t frames_processed = 0; + f_cnt_t frames_processed = ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->noteOffset() : _n->noteOffset(); while( frames_processed < engine::mixer()->framesPerPeriod() ) { @@ -471,12 +473,14 @@ void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) // create sub-note-play-handle, only ptr to note is different // and is_arp_note=true - NotePlayHandleManager::acquire( _n->instrumentTrack(), - ( ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->offset() : _n->offset() ) + frames_processed, + engine::mixer()->addPlayHandle( + NotePlayHandleManager::acquire( _n->instrumentTrack(), + frames_processed, gated_frames, note( MidiTime( 0 ), MidiTime( 0 ), sub_note_key, (volume_t) qRound( _n->getVolume() * vol_level ), _n->getPanning(), _n->detuning() ), - _n, -1, NotePlayHandle::OriginArpeggio ); + _n, -1, NotePlayHandle::OriginArpeggio ) + ); // update counters frames_processed += arp_frames; diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index fc8e33f4c..2be424f72 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -145,10 +145,6 @@ void NotePlayHandle::done() m_instrumentTrack->m_notes[key()] = NULL; } - foreach( NotePlayHandle * n, m_subNotes ) - { - NotePlayHandleManager::release( n ); - } m_subNotes.clear(); delete m_filter; @@ -295,14 +291,15 @@ void NotePlayHandle::play( sampleFrame * _working_buffer ) } // play sub-notes (e.g. chords) - foreach( NotePlayHandle * n, m_subNotes ) + // handled by mixer now +/* foreach( NotePlayHandle * n, m_subNotes ) { n->play( _working_buffer ); if( n->isFinished() ) { NotePlayHandleManager::release( n ); } - } + }*/ // update internal data m_totalFramesPlayed += framesThisPeriod; @@ -363,7 +360,9 @@ void NotePlayHandle::noteOff( const f_cnt_t _s ) // first note-off all sub-notes foreach( NotePlayHandle * n, m_subNotes ) { + n->lock(); n->noteOff( _s ); + n->unlock(); } // then set some variables indicating release-state @@ -549,7 +548,7 @@ void NotePlayHandle::resize( const bpm_t _new_tempo ) NotePlayHandleList NotePlayHandleManager::s_nphCache; NotePlayHandleList NotePlayHandleManager::s_available; QMutex NotePlayHandleManager::s_mutex; - +QAtomicInt NotePlayHandleManager::s_availableIndex; void NotePlayHandleManager::init() { @@ -565,6 +564,7 @@ void NotePlayHandleManager::init() s_available += n; ++n; } + s_availableIndex = INITIAL_NPH_CACHE - 1; } @@ -576,14 +576,12 @@ NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrac int midiEventChannel, NotePlayHandle::Origin origin ) { - if( s_available.isEmpty() ) + if( s_availableIndex < 0 ) { extend( NPH_CACHE_INCREMENT ); } - s_mutex.lock(); - NotePlayHandle * nph = s_available.takeFirst(); - s_mutex.unlock(); + NotePlayHandle * nph = s_available.at( s_availableIndex.fetchAndAddOrdered( -1 ) ); new( (void*)nph ) NotePlayHandle( instrumentTrack, offset, frames, noteToPlay, parent, midiEventChannel, origin ); return nph; @@ -593,9 +591,7 @@ NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrac void NotePlayHandleManager::release( NotePlayHandle * nph ) { nph->done(); - s_mutex.lock(); - s_available += nph; - s_mutex.unlock(); + s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = nph; } @@ -603,12 +599,11 @@ void NotePlayHandleManager::extend( int i ) { NotePlayHandle * n = MM_ALLOC( NotePlayHandle, i ); - s_mutex.lock(); for( int j=0; j < i; ++j ) { s_nphCache += n; s_available += n; + s_availableIndex.ref(); ++n; } - s_mutex.unlock(); } From 3d9a7fbf75556cf948d0c64f15492688081f9015 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 17:18:20 +0300 Subject: [PATCH 09/28] remove tr.whitespace --- include/MemoryManager.h | 18 +++++++-------- src/core/MemoryManager.cpp | 46 +++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index aa154baff..7ef371904 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -34,7 +34,7 @@ #include "MemoryHelper.h" -const int MM_CHUNK_SIZE = 64; // granularity of managed memory +const int MM_CHUNK_SIZE = 64; // granularity of managed memory const int MM_INITIAL_CHUNKS = 1024 * 1024; // how many chunks to allocate at startup - TODO: make configurable const int MM_INCREMENT_CHUNKS = 16 * 1024; // min. amount of chunks to increment at a time @@ -44,27 +44,27 @@ struct MemoryPool char * m_free; int m_chunks; QMutex m_mutex; - + MemoryPool() : m_pool( NULL ), m_free( NULL ), m_chunks( 0 ) {} - - MemoryPool( int chunks ) : + + MemoryPool( int chunks ) : m_chunks( chunks ) { m_free = (char*) MemoryHelper::alignedMalloc( chunks ); memset( m_free, 1, chunks ); } - + MemoryPool( const MemoryPool & mp ) : m_pool( mp.m_pool ), m_free( mp.m_free ), m_chunks( mp.m_chunks ), m_mutex() {} - + MemoryPool & operator = ( const MemoryPool & mp ) { m_pool = mp.m_pool; @@ -72,7 +72,7 @@ struct MemoryPool m_chunks = mp.m_chunks; return *this; } - + void * getChunks( int chunksNeeded ); void releaseChunks( void * ptr, int chunks ); }; @@ -86,7 +86,7 @@ struct PtrInfo typedef QVector MemoryPoolVector; typedef QHash PointerInfoMap; -class MemoryManager +class MemoryManager { public: static bool init(); @@ -98,7 +98,7 @@ public: private: static MemoryPoolVector s_memoryPools; static QReadWriteLock s_poolMutex; - + static PointerInfoMap s_pointerInfo; static QMutex s_pointerMutex; }; diff --git a/src/core/MemoryManager.cpp b/src/core/MemoryManager.cpp index 0464a40d8..21cdf01ba 100644 --- a/src/core/MemoryManager.cpp +++ b/src/core/MemoryManager.cpp @@ -29,10 +29,10 @@ #include -MemoryPoolVector MemoryManager::s_memoryPools; -QReadWriteLock MemoryManager::s_poolMutex; -PointerInfoMap MemoryManager::s_pointerInfo; -QMutex MemoryManager::s_pointerMutex; +MemoryPoolVector MemoryManager::s_memoryPools; +QReadWriteLock MemoryManager::s_poolMutex; +PointerInfoMap MemoryManager::s_pointerInfo; +QMutex MemoryManager::s_pointerMutex; bool MemoryManager::init() @@ -50,12 +50,12 @@ bool MemoryManager::init() void * MemoryManager::alloc( size_t size ) { int requiredChunks = size / MM_CHUNK_SIZE + ( size % MM_CHUNK_SIZE > 0 ? 1 : 0 ); - + MemoryPool * mp = NULL; void * ptr = NULL; - + MemoryPoolVector::iterator it = s_memoryPools.begin(); - + s_poolMutex.lockForRead(); while( it != s_memoryPools.end() && !ptr ) { @@ -68,7 +68,7 @@ void * MemoryManager::alloc( size_t size ) } s_poolMutex.unlock(); - if( ptr ) + if( ptr ) { s_pointerMutex.lock(); PtrInfo p; @@ -78,15 +78,15 @@ void * MemoryManager::alloc( size_t size ) s_pointerMutex.unlock(); return ptr; } - - // can't find enough chunks in existing pools, so + + // can't find enough chunks in existing pools, so // create a new pool that is guaranteed to have enough chunks int moreChunks = qMax( requiredChunks, MM_INCREMENT_CHUNKS ); int i = MemoryManager::extend( moreChunks ); - + mp = &s_memoryPools[i]; ptr = s_memoryPools[i].getChunks( requiredChunks ); - if( ptr ) + if( ptr ) { s_pointerMutex.lock(); PtrInfo p; @@ -104,7 +104,7 @@ void * MemoryManager::alloc( size_t size ) void MemoryManager::free( void * ptr ) { - if( ptr == NULL ) + if( ptr == NULL ) { qDebug( "MemoryManager: Null pointer deallocation attempted" ); return; // let's not try to deallocate null pointers, ok? @@ -119,7 +119,7 @@ void MemoryManager::free( void * ptr ) PtrInfo p = s_pointerInfo[ptr]; s_pointerInfo.remove( ptr ); s_pointerMutex.unlock(); - + p.memPool->releaseChunks( ptr, p.chunks ); } @@ -128,12 +128,12 @@ int MemoryManager::extend( int chunks ) { MemoryPool m ( chunks ); m.m_pool = MemoryHelper::alignedMalloc( chunks * MM_CHUNK_SIZE ); - + s_poolMutex.lockForWrite(); s_memoryPools.append( m ); int i = s_memoryPools.size() - 1; s_poolMutex.unlock(); - + return i; } @@ -154,15 +154,15 @@ void * MemoryPool::getChunks( int chunksNeeded ) { return NULL; } - + m_mutex.lock(); - + // now find out if we have a long enough sequence of chunks in this pool char last = 0; intptr_t n = 0; intptr_t index = -1; bool found = false; - + for( int i = 0; i < m_chunks; ++i ) { if( m_free[i] ) @@ -183,10 +183,10 @@ void * MemoryPool::getChunks( int chunksNeeded ) { n = 0; } - + last = m_free[i]; } - + if( found ) // if enough chunks found, return pointer to chunks { // set chunk flags to false so we know the chunks are in use @@ -205,13 +205,13 @@ void * MemoryPool::getChunks( int chunksNeeded ) void MemoryPool::releaseChunks( void * ptr, int chunks ) { m_mutex.lock(); - + intptr_t start = ( (intptr_t)ptr - (intptr_t)m_pool ) / MM_CHUNK_SIZE; if( start < 0 ) { qFatal( "MemoryManager: error at releaseChunks() - corrupt pointer info?" ); } - + memset( &m_free[ start ], 1, chunks ); m_mutex.unlock(); From f3ed39a9fecba5d03a80784879f4588918c5d369 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 18:31:08 +0300 Subject: [PATCH 10/28] Fix weird issue with remotevstplugin --- include/MemoryManager.h | 1 - include/MidiEvent.h | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index 7ef371904..300b25a34 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -26,7 +26,6 @@ #ifndef MEMORY_MANAGER_H #define MEMORY_MANAGER_H -#include #include #include #include diff --git a/include/MidiEvent.h b/include/MidiEvent.h index 82eef85cc..aa53bbeb3 100644 --- a/include/MidiEvent.h +++ b/include/MidiEvent.h @@ -22,18 +22,16 @@ * */ -#ifndef _MIDI_EVENT_H -#define _MIDI_EVENT_H +#ifndef MIDI_EVENT_H +#define MIDI_EVENT_H #include #include "Midi.h" #include "panning_constants.h" #include "volume.h" -#include "MemoryManager.h" class MidiEvent { - MM_OPERATORS public: MidiEvent( MidiEventTypes type = MidiActiveSensing, int8_t channel = 0, From daa5f6c26d64c6876d18423753efa5d510f2a254 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 20:09:25 +0300 Subject: [PATCH 11/28] Use memory management in LADSPA effects Also optimize non-inplacebroken plugins by using the same buffer for input/output --- plugins/LadspaEffect/LadspaEffect.cpp | 41 +++++++++++++++++++-------- plugins/LadspaEffect/LadspaEffect.h | 1 + 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index c2e413653..9996d28b2 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -37,6 +37,7 @@ #include "EffectChain.h" #include "AutomationPattern.h" #include "ControllerConnection.h" +#include "MemoryManager.h" #include "embed.cpp" @@ -155,9 +156,7 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, } // Copy the LMMS audio buffer to the LADSPA input buffer and initialize - // the control ports. Need to change this to handle non-in-place-broken - // plugins--would speed things up to use the same buffer for both - // LMMS and LADSPA. + // the control ports. ch_cnt_t channel = 0; for( ch_cnt_t proc = 0; proc < processorCount(); ++proc ) { @@ -208,6 +207,7 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, } } + // Process the buffers. for( ch_cnt_t proc = 0; proc < processorCount(); ++proc ) { @@ -287,9 +287,15 @@ void LadspaEffect::pluginInstantiation() int effect_channels = manager->getDescription( m_key )->inputChannels; setProcessorCount( lmms_chnls / effect_channels ); + // get inPlaceBroken property + m_inPlaceBroken = manager->isInplaceBroken( m_key ); + // Categorize the ports, and create the buffers. m_portCount = manager->getPortCount( m_key ); + int inputch = 0; + int outputch = 0; + LADSPA_Data * inbuf [2]; for( ch_cnt_t proc = 0; proc < processorCount(); proc++ ) { multi_proc_t ports; @@ -301,39 +307,47 @@ void LadspaEffect::pluginInstantiation() p->proc = proc; p->port_id = port; p->control = NULL; + p->buffer = NULL; // Determine the port's category. if( manager->isPortAudio( m_key, port ) ) { - // Nasty manual memory management--was having difficulty - // with some prepackaged plugins that were segfaulting - // during cleanup. It was easier to troubleshoot with the - // memory management all taking place in one file. - p->buffer = - new LADSPA_Data[engine::mixer()->framesPerPeriod()]; - if( p->name.toUpper().contains( "IN" ) && manager->isPortInput( m_key, port ) ) { p->rate = CHANNEL_IN; + p->buffer = MM_ALLOC( LADSPA_Data, engine::mixer()->framesPerPeriod() ); + inbuf[ inputch ] = p->buffer; + inputch++; } else if( p->name.toUpper().contains( "OUT" ) && manager->isPortOutput( m_key, port ) ) { p->rate = CHANNEL_OUT; + if( ! m_inPlaceBroken ) + { + p->buffer = inbuf[ outputch ]; + outputch++; + } + else + { + p->buffer = MM_ALLOC( LADSPA_Data, engine::mixer()->framesPerPeriod() ); + } } else if( manager->isPortInput( m_key, port ) ) { p->rate = AUDIO_RATE_INPUT; + p->buffer = MM_ALLOC( LADSPA_Data, engine::mixer()->framesPerPeriod() ); } else { p->rate = AUDIO_RATE_OUTPUT; + p->buffer = MM_ALLOC( LADSPA_Data, engine::mixer()->framesPerPeriod() ); } } else { - p->buffer = new LADSPA_Data[1]; + p->buffer = MM_ALLOC( LADSPA_Data, 1 ); if( manager->isPortInput( m_key, port ) ) { @@ -526,7 +540,10 @@ void LadspaEffect::pluginDestruction() for( int port = 0; port < m_portCount; port++ ) { port_desc_t * pp = m_ports.at( proc ).at( port ); - delete[] pp->buffer; + if( m_inPlaceBroken || pp->rate != CHANNEL_OUT ) + { + if( pp->buffer) MM_FREE( pp->buffer ); + } delete pp; } m_ports[proc].clear(); diff --git a/plugins/LadspaEffect/LadspaEffect.h b/plugins/LadspaEffect/LadspaEffect.h index edc6fc840..fbfcaa8d6 100644 --- a/plugins/LadspaEffect/LadspaEffect.h +++ b/plugins/LadspaEffect/LadspaEffect.h @@ -76,6 +76,7 @@ private: sample_rate_t m_maxSampleRate; ladspa_key_t m_key; int m_portCount; + bool m_inPlaceBroken; const LADSPA_Descriptor * m_descriptor; QVector m_handles; From af60402078055cadd745869efd03a230cbc1c55b Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 20:29:43 +0300 Subject: [PATCH 12/28] Make it possible to use sample-exact controls in LADSPA plugins I don't think we currently have any that would support this functionality, but in case someone has a LADSPA plugin that has audiorate control ports, this allows them to be used with the new sample-exact models Again... not strictly related to memory management, but since I was in that part of the codebase already... --- include/LadspaControl.h | 2 ++ plugins/LadspaEffect/LadspaEffect.cpp | 29 ++++++++++++++++++--------- src/core/LadspaControl.cpp | 19 ++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/include/LadspaControl.h b/include/LadspaControl.h index 4e50379f7..e9fc424de 100644 --- a/include/LadspaControl.h +++ b/include/LadspaControl.h @@ -30,6 +30,7 @@ #include "AutomatableModel.h" #include "TempoSyncKnobModel.h" +#include "ValueBuffer.h" typedef struct PortDescription port_desc_t; @@ -44,6 +45,7 @@ public: ~LadspaControl(); LADSPA_Data value(); + ValueBuffer * valueBuffer(); void setValue( LADSPA_Data _value ); void setLink( bool _state ); diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index 9996d28b2..891a49438 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -38,6 +38,7 @@ #include "AutomationPattern.h" #include "ControllerConnection.h" #include "MemoryManager.h" +#include "ValueBuffer.h" #include "embed.cpp" @@ -175,18 +176,28 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, ++channel; break; case AUDIO_RATE_INPUT: - pp->value = static_cast( - pp->control->value() / pp->scale ); - // This only supports control rate ports, so the audio rates are - // treated as though they were control rate by setting the - // port buffer to all the same value. - for( fpp_t frame = 0; - frame < frames; ++frame ) + { + ValueBuffer * vb = pp->control->valueBuffer(); + if( vb ) { - pp->buffer[frame] = - pp->value; + memcpy( pp->buffer, vb->values(), frames * sizeof(float) ); + } + else + { + pp->value = static_cast( + pp->control->value() / pp->scale ); + // This only supports control rate ports, so the audio rates are + // treated as though they were control rate by setting the + // port buffer to all the same value. + for( fpp_t frame = 0; + frame < frames; ++frame ) + { + pp->buffer[frame] = + pp->value; + } } break; + } case CONTROL_RATE_INPUT: if( pp->control == NULL ) { diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 42e74141e..2559fe3d9 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -130,6 +130,25 @@ LADSPA_Data LadspaControl::value() } +ValueBuffer * LadspaControl::valueBuffer() +{ + switch( m_port->data_type ) + { + case TOGGLED: + case INTEGER: + return NULL; + case FLOATING: + return m_knobModel.valueBuffer(); + case TIME: + return m_tempoSyncKnobModel.valueBuffer(); + default: + qWarning( "LadspaControl::valueBuffer(): BAD BAD BAD\n" ); + break; + } + + return NULL; +} + void LadspaControl::setValue( LADSPA_Data _value ) From 311d33d648f77fcdf29d2e348b134234d2067c11 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 24 Aug 2014 23:23:58 +0300 Subject: [PATCH 13/28] Implement BufferManager Also, apply things learned while writing BufferManager to the similar NotePlayHandleManager --- include/BufferManager.h | 55 ++++++++++++++++++++++ include/NotePlayHandle.h | 7 +-- src/core/BufferManager.cpp | 94 +++++++++++++++++++++++++++++++++++++ src/core/NotePlayHandle.cpp | 41 +++++++++------- src/core/main.cpp | 4 ++ 5 files changed, 181 insertions(+), 20 deletions(-) create mode 100644 include/BufferManager.h create mode 100644 src/core/BufferManager.cpp diff --git a/include/BufferManager.h b/include/BufferManager.h new file mode 100644 index 000000000..f2905b194 --- /dev/null +++ b/include/BufferManager.h @@ -0,0 +1,55 @@ +/* + * BufferManager.h - A buffer caching/memory management system + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2006-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 BUFFER_MANAGER_H +#define BUFFER_MANAGER_H + +#include "MemoryManager.h" +#include "lmms_basics.h" +#include "engine.h" +#include "Mixer.h" +#include +#include + + +const int BM_INITIAL_BUFFERS = 256; +const int BM_INCREMENT = 16; + +class BufferManager +{ +public: + static void init(); + static sampleFrame * acquire(); + static void release( sampleFrame * buf ); + static void extend( int c ); + +private: + static sampleFrame ** s_available; + static QAtomicInt s_availableIndex; + static QReadWriteLock s_mutex; + static int s_size; +}; + +#endif diff --git a/include/NotePlayHandle.h b/include/NotePlayHandle.h index 9c4c1d2b4..e11ddf457 100644 --- a/include/NotePlayHandle.h +++ b/include/NotePlayHandle.h @@ -32,6 +32,7 @@ #include "track.h" #include "MemoryManager.h" #include +#include class InstrumentTrack; class NotePlayHandle; @@ -333,10 +334,10 @@ public: static void extend( int i ); private: - static NotePlayHandleList s_nphCache; - static NotePlayHandleList s_available; - static QMutex s_mutex; + static NotePlayHandle ** s_available; + static QReadWriteLock s_mutex; static QAtomicInt s_availableIndex; + static int s_size; }; diff --git a/src/core/BufferManager.cpp b/src/core/BufferManager.cpp new file mode 100644 index 000000000..1646caf81 --- /dev/null +++ b/src/core/BufferManager.cpp @@ -0,0 +1,94 @@ +/* + * BufferManager.cpp - A buffer caching/memory management system + * + * Copyright (c) 2014 Vesa Kivimäki + * Copyright (c) 2006-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 "BufferManager.h" + + +sampleFrame ** BufferManager::s_available; +QAtomicInt BufferManager::s_availableIndex = 0; +QReadWriteLock BufferManager::s_mutex; +int BufferManager::s_size; + + +void BufferManager::init() +{ + s_available = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); + + int c = engine::mixer()->framesPerPeriod() * BM_INITIAL_BUFFERS; + sampleFrame * b = MM_ALLOC( sampleFrame, c ); + + for( int i = 0; i < BM_INITIAL_BUFFERS; ++i ) + { + s_available[ i ] = b; + b += engine::mixer()->framesPerPeriod(); + } + s_availableIndex = BM_INITIAL_BUFFERS - 1; + s_size = BM_INITIAL_BUFFERS; +} + + +sampleFrame * BufferManager::acquire() +{ + if( s_availableIndex < 0 ) + { + s_mutex.lockForWrite(); + if( s_availableIndex < 0 ) extend( BM_INCREMENT ); + s_mutex.unlock(); + } + s_mutex.lockForRead(); + + sampleFrame * b = s_available[ s_availableIndex.fetchAndAddOrdered( -1 ) ]; + + //qDebug( "acquired buffer: %p - index %d", b, int(s_availableIndex) ); + s_mutex.unlock(); + return b; +} + + +void BufferManager::release( sampleFrame * buf ) +{ + s_mutex.lockForRead(); + s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = buf; + //qDebug( "released buffer: %p - index %d", buf, int(s_availableIndex) ); + s_mutex.unlock(); +} + + +void BufferManager::extend( int c ) +{ + s_size += c; + sampleFrame ** tmp = MM_ALLOC( sampleFrame*, s_size ); + MM_FREE( s_available ); + s_available = tmp; + + int cc = c * engine::mixer()->framesPerPeriod(); + sampleFrame * b = MM_ALLOC( sampleFrame, cc ); + + for( int i = 0; i < c; ++i ) + { + s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = b; + b += engine::mixer()->framesPerPeriod(); + } +} diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 2be424f72..21e1e93f2 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -545,26 +545,25 @@ void NotePlayHandle::resize( const bpm_t _new_tempo ) } -NotePlayHandleList NotePlayHandleManager::s_nphCache; -NotePlayHandleList NotePlayHandleManager::s_available; -QMutex NotePlayHandleManager::s_mutex; +NotePlayHandle ** NotePlayHandleManager::s_available; +QReadWriteLock NotePlayHandleManager::s_mutex; QAtomicInt NotePlayHandleManager::s_availableIndex; +int NotePlayHandleManager::s_size; + void NotePlayHandleManager::init() { - // make sure the containers have more room than we need so that they don't need to do reallocations - s_nphCache.reserve( 1024 ); - s_available.reserve( 1024 ); + s_available = MM_ALLOC( NotePlayHandle*, INITIAL_NPH_CACHE ); NotePlayHandle * n = MM_ALLOC( NotePlayHandle, INITIAL_NPH_CACHE ); for( int i=0; i < INITIAL_NPH_CACHE; ++i ) { - s_nphCache += n; - s_available += n; + s_available[ i ] = n; ++n; } s_availableIndex = INITIAL_NPH_CACHE - 1; + s_size = INITIAL_NPH_CACHE; } @@ -578,10 +577,13 @@ NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrac { if( s_availableIndex < 0 ) { - extend( NPH_CACHE_INCREMENT ); + s_mutex.lockForWrite(); + if( s_availableIndex < 0 ) extend( NPH_CACHE_INCREMENT ); + s_mutex.unlock(); } - - NotePlayHandle * nph = s_available.at( s_availableIndex.fetchAndAddOrdered( -1 ) ); + s_mutex.lockForRead(); + NotePlayHandle * nph = s_available[ s_availableIndex.fetchAndAddOrdered( -1 ) ]; + s_mutex.unlock(); new( (void*)nph ) NotePlayHandle( instrumentTrack, offset, frames, noteToPlay, parent, midiEventChannel, origin ); return nph; @@ -591,19 +593,24 @@ NotePlayHandle * NotePlayHandleManager::acquire( InstrumentTrack* instrumentTrac void NotePlayHandleManager::release( NotePlayHandle * nph ) { nph->done(); + s_mutex.lockForRead(); s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = nph; + s_mutex.unlock(); } -void NotePlayHandleManager::extend( int i ) +void NotePlayHandleManager::extend( int c ) { - NotePlayHandle * n = MM_ALLOC( NotePlayHandle, i ); + s_size += c; + NotePlayHandle ** tmp = MM_ALLOC( NotePlayHandle*, s_size ); + MM_FREE( s_available ); + s_available = tmp; + + NotePlayHandle * n = MM_ALLOC( NotePlayHandle, c ); - for( int j=0; j < i; ++j ) + for( int i=0; i < c; ++i ) { - s_nphCache += n; - s_available += n; - s_availableIndex.ref(); + s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = n; ++n; } } diff --git a/src/core/main.cpp b/src/core/main.cpp index 5b3b72a4a..9d3670966 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -76,6 +76,7 @@ #include "DataFile.h" #include "song.h" #include "LmmsPalette.h" +#include "BufferManager.h" static inline QString baseName( const QString & _file ) { @@ -438,6 +439,9 @@ int main( int argc, char * * argv ) // init central engine which handles all components of LMMS engine::init(); + + // init buffer manager - has to be done after fpp is known + BufferManager::init(); splashScreen.hide(); From 857de8d2c829dc688745f41ba8eddbe148a63a20 Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 27 Aug 2014 00:59:49 +0300 Subject: [PATCH 14/28] Huge structural changes Well, this commit got a bit out of hand, what with 26 files changed. Oh well. Basically, we're using the buffermanager to dispense temporary buffers for playhandles and audioports to use. This allows us to change the way playhandles work. Earlier, playhandles of the same track were waiting in line to push their output to the audioport. This was of course inefficient, so now they just register themselves to the port, then the port handles mixing the buffers. Caveat: this is still a work in progress, the vol/pan knobs on instruments are temporarily non-functional - will be fixed in the next commit, but I have to get some sleep now. --- include/AudioPort.h | 52 +++++----------- include/BufferManager.h | 12 ++-- include/FxMixer.h | 2 +- include/InstrumentPlayHandle.h | 7 +-- include/Mixer.h | 5 -- include/MixerWorkerThread.h | 3 +- include/PlayHandle.h | 49 +++++++++++----- include/PresetPreviewPlayHandle.h | 4 +- include/SamplePlayHandle.h | 1 - include/ThreadableJob.h | 10 ++-- plugins/lb302/lb302.cpp | 2 +- plugins/opl2/opl2instrument.cpp | 2 +- plugins/sf2_player/sf2_player.cpp | 2 +- plugins/vestige/vestige.cpp | 2 +- plugins/zynaddsubfx/ZynAddSubFx.cpp | 2 +- src/core/BufferManager.cpp | 42 +++++++++---- src/core/FxMixer.cpp | 19 ++---- src/core/InstrumentPlayHandle.cpp | 35 +++++++++++ src/core/Mixer.cpp | 52 ++++------------ src/core/MixerWorkerThread.cpp | 11 ++-- src/core/NotePlayHandle.cpp | 12 ++++ src/core/PlayHandle.cpp | 62 ++++++++++++++++++++ src/core/PresetPreviewPlayHandle.cpp | 1 + src/core/SamplePlayHandle.cpp | 14 ++--- src/core/audio/AudioPort.cpp | 88 +++++++++++++++++----------- src/tracks/InstrumentTrack.cpp | 2 +- 26 files changed, 292 insertions(+), 201 deletions(-) create mode 100644 src/core/InstrumentPlayHandle.cpp create mode 100644 src/core/PlayHandle.cpp diff --git a/include/AudioPort.h b/include/AudioPort.h index 8d70f8306..0971878d4 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -31,6 +31,7 @@ #include "Mixer.h" #include "MemoryManager.h" +#include "PlayHandle.h" class EffectChain; @@ -41,38 +42,21 @@ public: AudioPort( const QString & _name, bool _has_effect_chain = true ); virtual ~AudioPort(); - inline sampleFrame * firstBuffer() + inline sampleFrame * buffer() { - return m_firstBuffer; + return m_portBuffer; } - inline sampleFrame * secondBuffer() + inline void lockBuffer() { - return m_secondBuffer; + m_portBufferLock.lock(); } - inline void lockFirstBuffer() + inline void unlockBuffer() { - m_firstBufferLock.lock(); + m_portBufferLock.unlock(); } - inline void lockSecondBuffer() - { - m_secondBufferLock.lock(); - } - - inline void unlockFirstBuffer() - { - m_firstBufferLock.unlock(); - } - - inline void unlockSecondBuffer() - { - m_secondBufferLock.unlock(); - } - - void nextPeriod(); - // indicate whether JACK & Co should provide output-buffer at ext. port inline bool extOutputEnabled() const @@ -112,28 +96,20 @@ public: bool processEffects(); // ThreadableJob stuff - virtual void doProcessing( sampleFrame * ); + virtual void doProcessing(); virtual bool requiresProcessing() const { return true; } - - enum bufferUsages - { - NoUsage, - FirstBuffer, - BothBuffers - } ; - + void addPlayHandle( PlayHandle * handle ); + void removePlayHandle( PlayHandle * handle ); private: - volatile bufferUsages m_bufferUsage; + volatile bool m_bufferUsage; - sampleFrame * m_firstBuffer; - sampleFrame * m_secondBuffer; - QMutex m_firstBufferLock; - QMutex m_secondBufferLock; + sampleFrame * m_portBuffer; + QMutex m_portBufferLock; bool m_extOutputEnabled; fx_ch_t m_nextFxChannel; @@ -142,6 +118,8 @@ private: EffectChain * m_effects; + PlayHandleList m_playHandles; + QMutex m_playHandleLock; friend class Mixer; friend class MixerWorkerThread; diff --git a/include/BufferManager.h b/include/BufferManager.h index f2905b194..f605e38b8 100644 --- a/include/BufferManager.h +++ b/include/BufferManager.h @@ -34,8 +34,8 @@ #include -const int BM_INITIAL_BUFFERS = 256; -const int BM_INCREMENT = 16; +const int BM_INITIAL_BUFFERS = 512; +//const int BM_INCREMENT = 64; class BufferManager { @@ -43,12 +43,16 @@ public: static void init(); static sampleFrame * acquire(); static void release( sampleFrame * buf ); - static void extend( int c ); + static void refresh(); +// static void extend( int c ); private: static sampleFrame ** s_available; static QAtomicInt s_availableIndex; - static QReadWriteLock s_mutex; + + static sampleFrame ** s_released; + static QAtomicInt s_releasedIndex; +// static QReadWriteLock s_mutex; static int s_size; }; diff --git a/include/FxMixer.h b/include/FxMixer.h index be1d2486e..25c1a8c06 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -76,7 +76,7 @@ class FxChannel : public ThreadableJob void processed(); private: - virtual void doProcessing( sampleFrame * _working_buffer ); + virtual void doProcessing(); }; diff --git a/include/InstrumentPlayHandle.h b/include/InstrumentPlayHandle.h index 331695a7e..1b4d3d12a 100644 --- a/include/InstrumentPlayHandle.h +++ b/include/InstrumentPlayHandle.h @@ -33,11 +33,7 @@ class InstrumentPlayHandle : public PlayHandle { public: - InstrumentPlayHandle( Instrument* instrument ) : - PlayHandle( TypeInstrumentPlayHandle ), - m_instrument( instrument ) - { - } + InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ); virtual ~InstrumentPlayHandle() { @@ -88,6 +84,7 @@ public: private: Instrument* m_instrument; + InstrumentTrack * m_instrumentTrack; } ; diff --git a/include/Mixer.h b/include/Mixer.h index d9de461eb..a17e7e7ed 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -321,11 +321,6 @@ public: } // audio-buffer-mgm - void bufferToPort( const sampleFrame * _buf, - const fpp_t _frames, - stereoVolumeVector _volume_vector, - AudioPort * _port ); - static void clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset = 0 ); diff --git a/include/MixerWorkerThread.h b/include/MixerWorkerThread.h index 51da6a91e..5f5c18c84 100644 --- a/include/MixerWorkerThread.h +++ b/include/MixerWorkerThread.h @@ -63,7 +63,7 @@ public: void addJob( ThreadableJob * _job ); - void run( sampleFrame * _buffer ); + void run(); void wait(); private: @@ -115,7 +115,6 @@ private: static QWaitCondition * queueReadyWaitCond; static QList workerThreads; - sampleFrame * m_workingBuf; volatile bool m_quit; } ; diff --git a/include/PlayHandle.h b/include/PlayHandle.h index 22849c9d0..51130d21a 100644 --- a/include/PlayHandle.h +++ b/include/PlayHandle.h @@ -33,7 +33,7 @@ #include "lmms_basics.h" class track; - +class AudioPort; class PlayHandle : public ThreadableJob { @@ -48,12 +48,7 @@ public: } ; typedef Types Type; - PlayHandle( const Type type, f_cnt_t offset = 0 ) : - m_type( type ), - m_offset( offset ), - m_affinity( QThread::currentThread() ) - { - } + PlayHandle( const Type type, f_cnt_t offset = 0 ); PlayHandle & operator = ( PlayHandle & p ) { @@ -63,9 +58,7 @@ public: return *this; } - virtual ~PlayHandle() - { - } + virtual ~PlayHandle(); virtual bool affinityMatters() const { @@ -83,10 +76,7 @@ public: } // required for ThreadableJob - virtual void doProcessing( sampleFrame* buffer ) - { - play( buffer ); - } + virtual void doProcessing(); virtual bool requiresProcessing() const { @@ -106,7 +96,7 @@ public: return m_processingLock.tryLock(); } virtual void play( sampleFrame* buffer ) = 0; - virtual bool isFinished( void ) const = 0; + virtual bool isFinished() const = 0; // returns the frameoffset at the start of the playhandle, // ie. how many empty frames should be inserted at the start of the first period @@ -123,12 +113,41 @@ public: virtual bool isFromTrack( const track * _track ) const = 0; + bool usesBuffer() const + { + return m_usesBuffer; + } + + void setUsesBuffer( const bool b ) + { + m_usesBuffer = b; + } + + AudioPort * audioPort() + { + return m_audioPort; + } + + void setAudioPort( AudioPort * port ) + { + m_audioPort = port; + } + + void releaseBuffer(); + + sampleFrame * buffer() + { + return m_playHandleBuffer; + } private: Type m_type; f_cnt_t m_offset; QThread* m_affinity; QMutex m_processingLock; + sampleFrame * m_playHandleBuffer; + bool m_usesBuffer; + AudioPort * m_audioPort; } ; diff --git a/include/PresetPreviewPlayHandle.h b/include/PresetPreviewPlayHandle.h index 673fa1f5f..731a830cf 100644 --- a/include/PresetPreviewPlayHandle.h +++ b/include/PresetPreviewPlayHandle.h @@ -23,8 +23,8 @@ * */ -#ifndef _PRESET_PREVIEW_PLAY_HANDLE_H -#define _PRESET_PREVIEW_PLAY_HANDLE_H +#ifndef PRESET_PREVIEW_PLAY_HANDLE_H +#define PRESET_PREVIEW_PLAY_HANDLE_H #include "NotePlayHandle.h" diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index 61bee7f50..9721f04b4 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -82,7 +82,6 @@ private: f_cnt_t m_frame; SampleBuffer::handleState m_state; - AudioPort * m_audioPort; const bool m_ownAudioPort; FloatModel m_defaultVolumeModel; diff --git a/include/ThreadableJob.h b/include/ThreadableJob.h index c3da4c547..f116a9324 100644 --- a/include/ThreadableJob.h +++ b/include/ThreadableJob.h @@ -22,8 +22,8 @@ * */ -#ifndef _THREADABLE_JOB_H -#define _THREADABLE_JOB_H +#ifndef THREADABLE_JOB_H +#define THREADABLE_JOB_H #include @@ -67,11 +67,11 @@ public: m_state = Done; } - void process( sampleFrame* workingBuffer = NULL ) + void process() { if( m_state.testAndSetOrdered( Queued, InProgress ) ) { - doProcessing( workingBuffer ); + doProcessing(); m_state = Done; } } @@ -80,7 +80,7 @@ public: protected: - virtual void doProcessing( sampleFrame* workingBuffer) = 0; + virtual void doProcessing() = 0; QAtomicInt m_state; diff --git a/plugins/lb302/lb302.cpp b/plugins/lb302/lb302.cpp index b5e89499d..306273b11 100644 --- a/plugins/lb302/lb302.cpp +++ b/plugins/lb302/lb302.cpp @@ -353,7 +353,7 @@ lb302Synth::lb302Synth( InstrumentTrack * _instrumentTrack ) : filterChanged(); - InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrumentTrack ); engine::mixer()->addPlayHandle( iph ); } diff --git a/plugins/opl2/opl2instrument.cpp b/plugins/opl2/opl2instrument.cpp index 786fe2483..4f1e810ef 100644 --- a/plugins/opl2/opl2instrument.cpp +++ b/plugins/opl2/opl2instrument.cpp @@ -138,7 +138,7 @@ opl2instrument::opl2instrument( InstrumentTrack * _instrument_track ) : trem_depth_mdl(false, this, tr( "Tremolo Depth" ) ) { // Connect the plugin to the mixer... - InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track ); engine::mixer()->addPlayHandle( iph ); // Voices are laid out in a funny way... diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index e6264cc26..b6aefc123 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -118,7 +118,7 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : // everytime we load a new soundfont. m_synth = new_fluid_synth( m_settings ); - InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track ); engine::mixer()->addPlayHandle( iph ); loadFile( ConfigManager::inst()->defaultSoundfont() ); diff --git a/plugins/vestige/vestige.cpp b/plugins/vestige/vestige.cpp index f59a2906b..7a165d6ac 100644 --- a/plugins/vestige/vestige.cpp +++ b/plugins/vestige/vestige.cpp @@ -82,7 +82,7 @@ vestigeInstrument::vestigeInstrument( InstrumentTrack * _instrument_track ) : p_subWindow( NULL ) { // now we need a play-handle which cares for calling play() - InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrument_track ); engine::mixer()->addPlayHandle( iph ); } diff --git a/plugins/zynaddsubfx/ZynAddSubFx.cpp b/plugins/zynaddsubfx/ZynAddSubFx.cpp index 00d3277df..91b8f43b5 100644 --- a/plugins/zynaddsubfx/ZynAddSubFx.cpp +++ b/plugins/zynaddsubfx/ZynAddSubFx.cpp @@ -129,7 +129,7 @@ ZynAddSubFxInstrument::ZynAddSubFxInstrument( connect( &m_resBandwidthModel, SIGNAL( dataChanged() ), this, SLOT( updateResBandwidth() ) ); // now we need a play-handle which cares for calling play() - InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, _instrumentTrack ); engine::mixer()->addPlayHandle( iph ); connect( engine::mixer(), SIGNAL( sampleRateChanged() ), diff --git a/src/core/BufferManager.cpp b/src/core/BufferManager.cpp index 1646caf81..f5925acfc 100644 --- a/src/core/BufferManager.cpp +++ b/src/core/BufferManager.cpp @@ -28,13 +28,16 @@ sampleFrame ** BufferManager::s_available; QAtomicInt BufferManager::s_availableIndex = 0; -QReadWriteLock BufferManager::s_mutex; +sampleFrame ** BufferManager::s_released; +QAtomicInt BufferManager::s_releasedIndex = 0; +//QReadWriteLock BufferManager::s_mutex; int BufferManager::s_size; void BufferManager::init() { s_available = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); + s_released = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); int c = engine::mixer()->framesPerPeriod() * BM_INITIAL_BUFFERS; sampleFrame * b = MM_ALLOC( sampleFrame, c ); @@ -53,29 +56,42 @@ sampleFrame * BufferManager::acquire() { if( s_availableIndex < 0 ) { - s_mutex.lockForWrite(); - if( s_availableIndex < 0 ) extend( BM_INCREMENT ); - s_mutex.unlock(); + qFatal( "BufferManager: out of buffers" ); } - s_mutex.lockForRead(); - sampleFrame * b = s_available[ s_availableIndex.fetchAndAddOrdered( -1 ) ]; + int i = s_availableIndex.fetchAndAddOrdered( -1 ); + sampleFrame * b = s_available[ i ]; - //qDebug( "acquired buffer: %p - index %d", b, int(s_availableIndex) ); - s_mutex.unlock(); + //qDebug( "acquired buffer: %p - index %d", b, i ); return b; } void BufferManager::release( sampleFrame * buf ) { - s_mutex.lockForRead(); - s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = buf; - //qDebug( "released buffer: %p - index %d", buf, int(s_availableIndex) ); - s_mutex.unlock(); + int i = s_releasedIndex.fetchAndAddOrdered( 1 ); + s_released[ i ] = buf; + //qDebug( "released buffer: %p - index %d", buf, i ); } +void BufferManager::refresh() // non-threadsafe, hence it's called periodically from mixer at a time when no other threads can interfere +{ + if( s_releasedIndex == 0 ) return; + //qDebug( "refresh: %d buffers", int( s_releasedIndex ) ); + + int j = s_availableIndex; + for( int i = 0; i < s_releasedIndex; ++i ) + { + ++j; + s_available[ j ] = s_released[ i ]; + } + s_availableIndex = j; + s_releasedIndex = 0; +} + + +/* // non-extensible for now void BufferManager::extend( int c ) { s_size += c; @@ -91,4 +107,4 @@ void BufferManager::extend( int c ) s_available[ s_availableIndex.fetchAndAddOrdered( 1 ) + 1 ] = b; b += engine::mixer()->framesPerPeriod(); } -} +}*/ diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index 9c52700d1..fcd1cc959 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -116,19 +116,10 @@ void FxChannel::unmuteForSolo() -void FxChannel::doProcessing( sampleFrame * _buf ) +void FxChannel::doProcessing() { const fpp_t fpp = engine::mixer()->framesPerPeriod(); - // ignore the passed _buf - // always use m_buffer - // this is just an auxilliary buffer if doProcessing() - // needs one for processing while running - // particularly important for playHandles, so Instruments - // can operate on this buffer the whole time - // this improves cache hit rate - _buf = m_buffer; - if( m_muted == false ) { foreach( FxRoute * senderRoute, m_receives ) @@ -150,21 +141,21 @@ void FxChannel::doProcessing( sampleFrame * _buf ) if( ! volBuf && ! sendBuf ) // neither volume nor send has sample-exact data... { const float v = sender->m_volumeModel.value() * sendModel->value(); - MixHelpers::addMultiplied( _buf, ch_buf, v, fpp ); + MixHelpers::addMultiplied( m_buffer, ch_buf, v, fpp ); } else if( volBuf && sendBuf ) // both volume and send have sample-exact data { - MixHelpers::addMultipliedByBuffers( _buf, ch_buf, volBuf, sendBuf, fpp ); + MixHelpers::addMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); } else if( volBuf ) // volume has sample-exact data but send does not { const float v = sendModel->value(); - MixHelpers::addMultipliedByBuffer( _buf, ch_buf, v, volBuf, fpp ); + MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); } else // vice versa { const float v = sender->m_volumeModel.value(); - MixHelpers::addMultipliedByBuffer( _buf, ch_buf, v, sendBuf, fpp ); + MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); } m_hasInput = true; } diff --git a/src/core/InstrumentPlayHandle.cpp b/src/core/InstrumentPlayHandle.cpp new file mode 100644 index 000000000..11a4fb5b8 --- /dev/null +++ b/src/core/InstrumentPlayHandle.cpp @@ -0,0 +1,35 @@ +/* + * InstrumentPlayHandle.cpp - play-handle for driving an instrument + * + * Copyright (c) 2005-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 "InstrumentPlayHandle.h" +#include "InstrumentTrack.h" + +InstrumentPlayHandle::InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ) : + PlayHandle( TypeInstrumentPlayHandle ), + m_instrument( instrument ), + m_instrumentTrack( instrumentTrack ) +{ + setAudioPort( instrumentTrack->audioPort() ); +} diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index e2b80ba42..74abde8c7 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -57,7 +57,7 @@ #include "MidiDummy.h" #include "MemoryHelper.h" - +#include "BufferManager.h" @@ -355,6 +355,7 @@ const surroundSampleFrame * Mixer::renderNextBuffer() if( it != m_playHandles.end() ) { + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); @@ -410,6 +411,7 @@ const surroundSampleFrame * Mixer::renderNextBuffer() } if( ( *it )->isFinished() ) { + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); @@ -424,7 +426,6 @@ const surroundSampleFrame * Mixer::renderNextBuffer() } unlockPlayHandleRemoval(); - // STAGE 2: process effects of all instrument- and sampletracks MixerWorkerThread::fillJobQueue >( m_audioPorts ); MixerWorkerThread::startAndWaitForJobs(); @@ -442,6 +443,9 @@ const surroundSampleFrame * Mixer::renderNextBuffer() EnvelopeAndLfoParameters::instances()->trigger(); Controller::triggerFrameCounter(); AutomatableModel::incrementPeriodCounter(); + + // refresh buffer pool + BufferManager::refresh(); m_profiler.finishPeriod( processingSampleRate(), m_framesPerPeriod ); @@ -472,45 +476,6 @@ void Mixer::clear() -void Mixer::bufferToPort( const sampleFrame * buf, - const fpp_t frames, - stereoVolumeVector vv, - AudioPort * port ) -{ - const int loop1_frame = qMin( frames, m_framesPerPeriod ); - - port->lockFirstBuffer(); - MixHelpers::addMultipliedStereo( port->firstBuffer(), // dst - buf, // src - vv.vol[0], vv.vol[1], // coeff left/right - loop1_frame ); // frame count - port->unlockFirstBuffer(); - - if( frames > m_framesPerPeriod ) - { - port->lockSecondBuffer(); - - const fpp_t framesLeft = qMin( frames - m_framesPerPeriod, m_framesPerPeriod ); - - MixHelpers::addMultipliedStereo( port->secondBuffer(), // dst - buf + m_framesPerPeriod, // src - vv.vol[0], vv.vol[1], // coeff left/right - framesLeft ); // frame count - - // we used both buffers so set flags - port->m_bufferUsage = AudioPort::BothBuffers; - port->unlockSecondBuffer(); - } - else if( port->m_bufferUsage == AudioPort::NoUsage ) - { - // only first buffer touched - port->m_bufferUsage = AudioPort::FirstBuffer; - } -} - - - - void Mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { @@ -665,7 +630,8 @@ bool Mixer::addPlayHandle( PlayHandle* handle ) if( criticalXRuns() == false ) { m_playHandleMutex.lock(); - m_newPlayHandles.append( handle ); + m_newPlayHandles.append( handle ); + handle->audioPort()->addPlayHandle( handle ); m_playHandleMutex.unlock(); return true; } @@ -688,6 +654,7 @@ void Mixer::removePlayHandle( PlayHandle * _ph ) _ph->affinity() == QThread::currentThread() ) { lockPlayHandleRemoval(); + _ph->audioPort()->removePlayHandle( _ph ); PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), _ph ); @@ -719,6 +686,7 @@ void Mixer::removePlayHandles( track * _track, bool removeIPHs ) { if( ( *it )->isFromTrack( _track ) && ( removeIPHs || ( *it )->type() != PlayHandle::TypeInstrumentPlayHandle ) ) { + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp index c82f9e035..9cc65c4c4 100644 --- a/src/core/MixerWorkerThread.cpp +++ b/src/core/MixerWorkerThread.cpp @@ -56,7 +56,7 @@ void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job ) -void MixerWorkerThread::JobQueue::run( sampleFrame * _buffer ) +void MixerWorkerThread::JobQueue::run() { bool processedJob = true; while( processedJob && (int) m_itemsDone < (int) m_queueSize ) @@ -67,7 +67,7 @@ void MixerWorkerThread::JobQueue::run( sampleFrame * _buffer ) ThreadableJob * job = m_items[i].fetchAndStoreOrdered( NULL ); if( job ) { - job->process( _buffer ); + job->process(); processedJob = true; m_itemsDone.fetchAndAddOrdered( 1 ); } @@ -98,7 +98,6 @@ void MixerWorkerThread::JobQueue::wait() MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) : QThread( mixer ), - m_workingBuf( new sampleFrame[mixer->framesPerPeriod()] ), m_quit( false ) { // initialize global static data @@ -120,8 +119,6 @@ MixerWorkerThread::MixerWorkerThread( Mixer* mixer ) : MixerWorkerThread::~MixerWorkerThread() { - delete[] m_workingBuf; - workerThreads.removeAll( this ); } @@ -143,7 +140,7 @@ void MixerWorkerThread::startAndWaitForJobs() // The last worker-thread is never started. Instead it's processed "inline" // i.e. within the global Mixer thread. This way we can reduce latencies // that otherwise would be caused by synchronizing with another thread. - globalJobQueue.run( workerThreads.last()->m_workingBuf ); + globalJobQueue.run(); globalJobQueue.wait(); } @@ -166,7 +163,7 @@ void MixerWorkerThread::run() { m.lock(); queueReadyWaitCond->wait( &m ); - globalJobQueue.run( m_workingBuf ); + globalJobQueue.run(); m.unlock(); } } diff --git a/src/core/NotePlayHandle.cpp b/src/core/NotePlayHandle.cpp index 21e1e93f2..c4f3971a3 100644 --- a/src/core/NotePlayHandle.cpp +++ b/src/core/NotePlayHandle.cpp @@ -93,6 +93,8 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, parent->m_hadChildren = true; m_bbTrack = parent->m_bbTrack; + + parent->setUsesBuffer( false ); } updateFrequency(); @@ -115,6 +117,13 @@ NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, MidiTime::fromFrames( offset(), engine::framesPerTick() ), offset() ); } + + if( m_instrumentTrack->instrument()->flags() & Instrument::IsSingleStreamed ) + { + setUsesBuffer( false ); + } + + setAudioPort( instrumentTrack->audioPort() ); unlock(); } @@ -148,6 +157,9 @@ void NotePlayHandle::done() m_subNotes.clear(); delete m_filter; + + if( buffer() ) releaseBuffer(); + unlock(); } diff --git a/src/core/PlayHandle.cpp b/src/core/PlayHandle.cpp new file mode 100644 index 000000000..b32a19c94 --- /dev/null +++ b/src/core/PlayHandle.cpp @@ -0,0 +1,62 @@ +/* + * PlayHandle.cpp - base class PlayHandle - core of rendering engine + * + * Copyright (c) 2004-2014 Tobias Doerffel + * + * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net + * + * 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 "PlayHandle.h" +#include "BufferManager.h" + + +PlayHandle::PlayHandle( const Type type, f_cnt_t offset ) : + m_type( type ), + m_offset( offset ), + m_affinity( QThread::currentThread() ), + m_playHandleBuffer( NULL ), + m_usesBuffer( true ) +{ +} + + +PlayHandle::~PlayHandle() +{ +} + + +void PlayHandle::doProcessing() +{ + if( m_usesBuffer ) + { + if( ! m_playHandleBuffer ) m_playHandleBuffer = BufferManager::acquire(); + play( m_playHandleBuffer ); + } + else + { + play( m_playHandleBuffer ); + } +} + + +void PlayHandle::releaseBuffer() +{ + BufferManager::release( m_playHandleBuffer ); + m_playHandleBuffer = NULL; +} diff --git a/src/core/PresetPreviewPlayHandle.cpp b/src/core/PresetPreviewPlayHandle.cpp index 4847b8e97..c6d5c7132 100644 --- a/src/core/PresetPreviewPlayHandle.cpp +++ b/src/core/PresetPreviewPlayHandle.cpp @@ -165,6 +165,7 @@ PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, typeInfo::max() / 2, note( 0, 0, DefaultKey, 100 ) ); + setAudioPort( s_previewTC->previewInstrumentTrack()->audioPort() ); s_previewTC->setPreviewNote( m_previewNote ); diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 423f88784..188002ed2 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -38,13 +38,13 @@ SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : m_sampleBuffer( new SampleBuffer( sampleFile ) ), m_doneMayReturnTrue( true ), m_frame( 0 ), - m_audioPort( new AudioPort( "SamplePlayHandle", false ) ), m_ownAudioPort( true ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( NULL ), m_bbTrack( NULL ) { + setAudioPort( new AudioPort( "SamplePlayHandle", false ) ); } @@ -55,13 +55,13 @@ SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer ) : m_sampleBuffer( sharedObject::ref( sampleBuffer ) ), m_doneMayReturnTrue( true ), m_frame( 0 ), - m_audioPort( new AudioPort( "SamplePlayHandle", false ) ), m_ownAudioPort( true ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( NULL ), m_bbTrack( NULL ) { + setAudioPort( new AudioPort( "SamplePlayHandle", false ) ); } @@ -72,13 +72,13 @@ SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) : m_sampleBuffer( sharedObject::ref( tco->sampleBuffer() ) ), m_doneMayReturnTrue( true ), m_frame( 0 ), - m_audioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() ), m_ownAudioPort( false ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( tco->getTrack() ), m_bbTrack( NULL ) { + setAudioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() ); } @@ -89,7 +89,7 @@ SamplePlayHandle::~SamplePlayHandle() sharedObject::unref( m_sampleBuffer ); if( m_ownAudioPort ) { - delete m_audioPort; + delete audioPort(); } } @@ -119,13 +119,11 @@ void SamplePlayHandle::play( sampleFrame * buffer ) if( !( m_track && m_track->isMuted() ) && !( m_bbTrack && m_bbTrack->isMuted() ) ) { - stereoVolumeVector v = +/* stereoVolumeVector v = { { m_volumeModel->value() / DefaultVolume, - m_volumeModel->value() / DefaultVolume } }; + m_volumeModel->value() / DefaultVolume } };*/ m_sampleBuffer->play( workingBuffer, &m_state, frames, BaseFreq ); - engine::mixer()->bufferToPort( buffer, fpp, - v, m_audioPort ); } m_frame += frames; diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 26c1d7074..93b447f0a 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -27,20 +27,18 @@ #include "EffectChain.h" #include "FxMixer.h" #include "engine.h" +#include "MixHelpers.h" +#include "BufferManager.h" AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) : - m_bufferUsage( NoUsage ), - m_firstBuffer( new sampleFrame[engine::mixer()->framesPerPeriod()] ), - m_secondBuffer( new sampleFrame[ - engine::mixer()->framesPerPeriod()] ), + m_bufferUsage( false ), + m_portBuffer( NULL ), m_extOutputEnabled( false ), m_nextFxChannel( 0 ), m_name( "unnamed port" ), m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL ) { - engine::mixer()->clearAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod() ); - engine::mixer()->clearAudioBuffer( m_secondBuffer, engine::mixer()->framesPerPeriod() ); engine::mixer()->addAudioPort( this ); setExtOutputEnabled( true ); } @@ -52,31 +50,12 @@ AudioPort::~AudioPort() { setExtOutputEnabled( false ); engine::mixer()->removeAudioPort( this ); - delete[] m_firstBuffer; - delete[] m_secondBuffer; delete m_effects; } -void AudioPort::nextPeriod() -{ - m_firstBufferLock.lock(); - engine::mixer()->clearAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod() ); - qSwap( m_firstBuffer, m_secondBuffer ); - - // this is how we decrease state of buffer-usage ;-) - m_bufferUsage = ( m_bufferUsage != NoUsage ) ? - ( ( m_bufferUsage == FirstBuffer ) ? - NoUsage : FirstBuffer ) : NoUsage; - - m_firstBufferLock.unlock(); -} - - - - void AudioPort::setExtOutputEnabled( bool _enabled ) { if( _enabled != m_extOutputEnabled ) @@ -109,23 +88,64 @@ bool AudioPort::processEffects() { if( m_effects ) { - lockFirstBuffer(); - bool hasInputNoise = m_bufferUsage != NoUsage; - bool more = m_effects->processAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod(), hasInputNoise ); - unlockFirstBuffer(); + bool more = m_effects->processAudioBuffer( m_portBuffer, engine::mixer()->framesPerPeriod(), m_bufferUsage ); return more; } return false; } -void AudioPort::doProcessing( sampleFrame * ) +void AudioPort::doProcessing() { - const bool me = processEffects(); - if( me || m_bufferUsage != NoUsage ) + const fpp_t fpp = engine::mixer()->framesPerPeriod(); + + if( m_playHandles.isEmpty() ) return; // skip processing if no playhandles are connected + + m_portBuffer = BufferManager::acquire(); // get buffer for processing + + engine::mixer()->clearAudioBuffer( m_portBuffer, fpp ); // clear the audioport buffer so we can use it + + //qDebug( "Playhandles: %d", m_playHandles.size() ); + foreach( PlayHandle * ph, m_playHandles ) // now we mix all playhandle buffers into the audioport buffer { - engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() ); - nextPeriod(); + if( ph->buffer() ) + { + if( ph->usesBuffer() ) + { + m_bufferUsage = true; + MixHelpers::add( m_portBuffer, ph->buffer(), fpp ); + } + ph->releaseBuffer(); // gets rid of playhandle's buffer and sets + // pointer to null, so if it doesn't get re-acquired we know to skip it next time + } } + + const bool me = processEffects(); + if( me || m_bufferUsage ) + { + engine::fxMixer()->mixToChannel( m_portBuffer, m_nextFxChannel ); + m_bufferUsage = false; + } + + BufferManager::release( m_portBuffer ); // release buffer, we don't need it anymore } + +void AudioPort::addPlayHandle( PlayHandle * handle ) +{ + m_playHandleLock.lock(); + m_playHandles.append( handle ); + m_playHandleLock.unlock(); +} + + +void AudioPort::removePlayHandle( PlayHandle * handle ) +{ + m_playHandleLock.lock(); + PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), handle ); + if( it != m_playHandles.end() ) + { + m_playHandles.erase( it ); + } + m_playHandleLock.unlock(); +} diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 98bdd817f..bbbe8ba1b 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -237,7 +237,7 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, } } - engine::mixer()->bufferToPort( buf, frames, panningToVolumeVector( panning, v_scale ), &m_audioPort ); + //engine::mixer()->bufferToPort( buf, frames, panningToVolumeVector( panning, v_scale ), &m_audioPort ); } From 1deb80acc38c4f42a5d7859a8251a9b09caed65a Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 27 Aug 2014 23:08:22 +0300 Subject: [PATCH 15/28] Finish audioport rehaul, get vol/pan knobs working again, also some bugfixes We're now doing the vol/pan stuff in audioport, since this way we avoid the pointless repetition of doing it in the playhandles --- include/AudioPort.h | 7 +- include/InstrumentTrack.h | 4 +- include/SampleBuffer.h | 14 +-- include/SampleTrack.h | 2 +- .../audio_file_processor.cpp | 1 + src/core/SampleBuffer.cpp | 17 +-- src/core/audio/AudioPort.cpp | 101 +++++++++++++++++- src/tracks/InstrumentTrack.cpp | 52 +++------ src/tracks/SampleTrack.cpp | 4 +- 9 files changed, 142 insertions(+), 60 deletions(-) diff --git a/include/AudioPort.h b/include/AudioPort.h index 0971878d4..0de61ca4d 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -34,12 +34,14 @@ #include "PlayHandle.h" class EffectChain; +class FloatModel; class AudioPort : public ThreadableJob { MM_OPERATORS public: - AudioPort( const QString & _name, bool _has_effect_chain = true ); + AudioPort( const QString & _name, bool _has_effect_chain = true, + FloatModel * volumeModel = NULL, FloatModel * panningModel = NULL ); virtual ~AudioPort(); inline sampleFrame * buffer() @@ -120,6 +122,9 @@ private: PlayHandleList m_playHandles; QMutex m_playHandleLock; + + FloatModel * m_volumeModel; + FloatModel * m_panningModel; friend class Mixer; friend class MixerWorkerThread; diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index eba606f00..13a87b49c 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -225,7 +225,6 @@ protected slots: private: - AudioPort m_audioPort; MidiPort m_midiPort; NotePlayHandle* m_notes[NumKeys]; @@ -244,6 +243,9 @@ private: FloatModel m_volumeModel; FloatModel m_panningModel; + + AudioPort m_audioPort; + FloatModel m_pitchModel; IntModel m_pitchRangeModel; IntModel m_effectChannelModel; diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index cb5d53c90..dd832281b 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -26,7 +26,7 @@ #ifndef SAMPLE_BUFFER_H #define SAMPLE_BUFFER_H -#include +#include #include #include @@ -151,21 +151,21 @@ public: void setLoopStartFrame( f_cnt_t _start ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_loopStartFrame = _start; m_varLock.unlock(); } void setLoopEndFrame( f_cnt_t _end ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_loopEndFrame = _end; m_varLock.unlock(); } void setAllPointFrames( f_cnt_t _start, f_cnt_t _end, f_cnt_t _loopstart, f_cnt_t _loopend ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_startFrame = _start; m_endFrame = _end; m_loopStartFrame = _loopstart; @@ -205,14 +205,14 @@ public: inline void setFrequency( float _freq ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_frequency = _freq; m_varLock.unlock(); } inline void setSampleRate( sample_rate_t _rate ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_sampleRate = _rate; m_varLock.unlock(); } @@ -291,7 +291,7 @@ private: sampleFrame * m_origData; f_cnt_t m_origFrames; sampleFrame * m_data; - QMutex m_varLock; + QReadWriteLock m_varLock; f_cnt_t m_frames; f_cnt_t m_startFrame; f_cnt_t m_endFrame; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index a3cf4571c..8673b74ec 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -147,8 +147,8 @@ public: private: - AudioPort m_audioPort; FloatModel m_volumeModel; + AudioPort m_audioPort; friend class SampleTrackView; diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index c2403e670..33c238aa3 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -179,6 +179,7 @@ void audioFileProcessor::playNote( NotePlayHandle * _n, } else { + memset( _working_buffer, 0, ( frames + offset ) * sizeof( sampleFrame ) ); emit isPlaying( 0 ); } } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 3612554f3..5d218ab06 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -160,7 +160,7 @@ void SampleBuffer::update( bool _keep_settings ) const bool lock = ( m_data != NULL ); if( lock ) { - engine::mixer()->lock(); + m_varLock.lockForWrite(); MM_FREE( m_data ); } @@ -262,7 +262,7 @@ void SampleBuffer::update( bool _keep_settings ) if( lock ) { - engine::mixer()->unlock(); + m_varLock.unlock(); } emit sampleUpdated(); @@ -598,7 +598,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, const float _freq, const LoopMode _loopmode ) { - QMutexLocker ml( &m_varLock ); + m_varLock.lockForRead(); f_cnt_t startFrame = m_startFrame; f_cnt_t endFrame = m_endFrame; @@ -607,6 +607,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, if( endFrame == 0 || _frames == 0 ) { + m_varLock.unlock(); return false; } @@ -623,6 +624,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, if( total_frames_for_current_pitch == 0 ) { + m_varLock.unlock(); return false; } @@ -639,6 +641,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, { if( play_frame >= endFrame ) { + m_varLock.unlock(); return false; } @@ -655,6 +658,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, play_frame = getPingPongIndex( play_frame, loopStartFrame, loopEndFrame ); } + f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) + MARGIN[ _state->interpolationMode() ]; sampleFrame * tmp = NULL; @@ -663,7 +667,6 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, { SRC_DATA src_data; // Generate output - f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) + MARGIN[ _state->interpolationMode() ]; src_data.data_in = getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, loopStartFrame, loopEndFrame, endFrame )[0]; @@ -767,8 +770,8 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, _ab[i][1] *= m_amplification; } + m_varLock.unlock(); return true; - } @@ -1364,7 +1367,7 @@ void SampleBuffer::loadFromBase64( const QString & _data ) void SampleBuffer::setStartFrame( const f_cnt_t _s ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_startFrame = _s; m_varLock.unlock(); } @@ -1374,7 +1377,7 @@ void SampleBuffer::setStartFrame( const f_cnt_t _s ) void SampleBuffer::setEndFrame( const f_cnt_t _e ) { - m_varLock.lock(); + m_varLock.lockForWrite(); m_endFrame = _e; m_varLock.unlock(); } diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 93b447f0a..85d99c910 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -29,15 +29,20 @@ #include "engine.h" #include "MixHelpers.h" #include "BufferManager.h" +#include "ValueBuffer.h" +#include "panning.h" -AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) : +AudioPort::AudioPort( const QString & _name, bool _has_effect_chain, + FloatModel * volumeModel, FloatModel * panningModel ) : m_bufferUsage( false ), m_portBuffer( NULL ), m_extOutputEnabled( false ), m_nextFxChannel( 0 ), m_name( "unnamed port" ), - m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL ) + m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL ), + m_volumeModel( volumeModel ), + m_panningModel( panningModel ) { engine::mixer()->addAudioPort( this ); setExtOutputEnabled( true ); @@ -120,10 +125,100 @@ void AudioPort::doProcessing() } } + if( m_bufferUsage ) + { + // handle volume and panning + // has both vol and pan models + if( m_volumeModel && m_panningModel ) + { + ValueBuffer * volBuf = m_volumeModel->valueBuffer(); + ValueBuffer * panBuf = m_panningModel->valueBuffer(); + + // both vol and pan have s.ex.data: + if( volBuf && panBuf ) + { + for( f_cnt_t f = 0; f < fpp; ++f ) + { + float v = volBuf->values()[ f ] * 0.01f; + float p = panBuf->values()[ f ] * 0.01f; + m_portBuffer[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ) * v; + m_portBuffer[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ) * v; + } + } + + // only vol has s.ex.data: + else if( volBuf ) + { + float p = m_panningModel->value() * 0.01f; + float l = ( p <= 0 ? 1.0f : 1.0f - p ); + float r = ( p >= 0 ? 1.0f : 1.0f + p ); + for( f_cnt_t f = 0; f < fpp; ++f ) + { + float v = volBuf->values()[ f ] * 0.01f; + m_portBuffer[f][0] *= v * l; + m_portBuffer[f][1] *= v * r; + } + } + + // only pan has s.ex.data: + else if( panBuf ) + { + float v = m_volumeModel->value() * 0.01f; + for( f_cnt_t f = 0; f < fpp; ++f ) + { + float p = panBuf->values()[ f ] * 0.01f; + m_portBuffer[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ) * v; + m_portBuffer[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ) * v; + } + } + + // neither has s.ex.data: + else + { + float p = m_panningModel->value() * 0.01f; + float v = m_volumeModel->value() * 0.01f; + for( f_cnt_t f = 0; f < fpp; ++f ) + { + m_portBuffer[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ) * v; + m_portBuffer[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ) * v; + } + } + } + + // has vol model only + else if( m_volumeModel ) + { + ValueBuffer * volBuf = m_volumeModel->valueBuffer(); + + if( volBuf ) + { + for( f_cnt_t f = 0; f < fpp; ++f ) + { + float v = volBuf->values()[ f ] * 0.01f; + m_portBuffer[f][0] *= v; + m_portBuffer[f][1] *= v; + } + } + else + { + float v = m_volumeModel->value() * 0.01f; + for( f_cnt_t f = 0; f < fpp; ++f ) + { + m_portBuffer[f][0] *= v; + m_portBuffer[f][1] *= v; + } + } + } + } + // as of now there's no situation where we only have panning model but no volume model + // if we have neither, we don't have to do anything here - just pass the audio as is + + // handle effects const bool me = processEffects(); if( me || m_bufferUsage ) { - engine::fxMixer()->mixToChannel( m_portBuffer, m_nextFxChannel ); + engine::fxMixer()->mixToChannel( m_portBuffer, m_nextFxChannel ); // send output to fx mixer + // TODO: improve the flow here - convert to pull model m_bufferUsage = false; } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index bbbe8ba1b..604090433 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -77,6 +77,7 @@ #include "tooltip.h" #include "track_label_button.h" #include "ValueBuffer.h" +#include "volume.h" const char * volume_help = QT_TRANSLATE_NOOP( "InstrumentTrack", @@ -94,7 +95,6 @@ const int INSTRUMENT_WINDOW_CACHE_SIZE = 8; InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : track( track::InstrumentTrack, tc ), MidiEventProcessor(), - m_audioPort( tr( "unnamed_track" ) ), m_midiPort( tr( "unnamed_track" ), engine::mixer()->midiClient(), this, this ), m_notes(), @@ -104,6 +104,7 @@ InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : tr( "Base note" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), + m_audioPort( tr( "unnamed_track" ), true, &m_volumeModel, &m_panningModel ), m_pitchModel( 0, MinPitchDefault, MaxPitchDefault, 1, this, tr( "Pitch" ) ), m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ), m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), @@ -188,10 +189,10 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, // get volume knob data static const float DefaultVolumeRatio = 1.0f / DefaultVolume; - ValueBuffer * volBuf = m_volumeModel.valueBuffer(); + /*ValueBuffer * volBuf = m_volumeModel.valueBuffer(); float v_scale = volBuf ? 1.0f - : getVolume() * DefaultVolumeRatio; + : getVolume() * DefaultVolumeRatio;*/ // instruments using instrument-play-handles will call this method // without any knowledge about notes, so they pass NULL for n, which @@ -200,44 +201,19 @@ void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, { const f_cnt_t offset = n->noteOffset(); m_soundShaping.processAudioBuffer( buf + offset, frames - offset, n ); - v_scale *= ( (float) n->getVolume() * DefaultVolumeRatio ); + const float vol = ( (float) n->getVolume() * DefaultVolumeRatio ); + const panning_t pan = qBound( PanningLeft, n->getPanning(), PanningRight ); + stereoVolumeVector vv = panningToVolumeVector( pan, vol ); + for( f_cnt_t f = offset; f < frames; ++f ) + { + for( int c = 0; c < 2; ++c ) + { + buf[f][c] *= vv.vol[c]; + } + } } m_audioPort.setNextFxChannel( m_effectChannelModel.value() ); - - // get panning knob data - ValueBuffer * panBuf = m_panningModel.valueBuffer(); - int panning = panBuf - ? 0 - : m_panningModel.value(); - - if( n ) - { - panning += n->getPanning(); - panning = tLimit( panning, PanningLeft, PanningRight ); - } - - // apply sample-exact volume/panning data - if( volBuf ) - { - for( f_cnt_t f = 0; f < frames; ++f ) - { - float v = volBuf->values()[ f ] * 0.01f; - buf[f][0] *= v; - buf[f][1] *= v; - } - } - if( panBuf ) - { - for( f_cnt_t f = 0; f < frames; ++f ) - { - float p = panBuf->values()[ f ] * 0.01f; - buf[f][0] *= ( p <= 0 ? 1.0f : 1.0f - p ); - buf[f][1] *= ( p >= 0 ? 1.0f : 1.0f + p ); - } - } - - //engine::mixer()->bufferToPort( buf, frames, panningToVolumeVector( panning, v_scale ), &m_audioPort ); } diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 0861428a2..f2a2dca53 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -403,9 +403,9 @@ void SampleTCOView::paintEvent( QPaintEvent * _pe ) SampleTrack::SampleTrack( TrackContainer* tc ) : track( track::SampleTrack, tc ), - m_audioPort( tr( "Sample track" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 1.0, this, - tr( "Volume" ) ) + tr( "Volume" ) ), + m_audioPort( tr( "Sample track" ), true, &m_volumeModel, NULL ) { setName( tr( "Sample track" ) ); } From 1864dcfaa1260e2659c85d1fb63cb2d428e9f1ab Mon Sep 17 00:00:00 2001 From: Vesa Date: Fri, 29 Aug 2014 20:24:24 +0300 Subject: [PATCH 16/28] Fix bugs --- include/MemoryManager.h | 4 ++-- src/core/audio/AudioPort.cpp | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/MemoryManager.h b/include/MemoryManager.h index 300b25a34..44ef58c60 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -31,7 +31,7 @@ #include #include #include "MemoryHelper.h" - +#include "export.h" const int MM_CHUNK_SIZE = 64; // granularity of managed memory const int MM_INITIAL_CHUNKS = 1024 * 1024; // how many chunks to allocate at startup - TODO: make configurable @@ -85,7 +85,7 @@ struct PtrInfo typedef QVector MemoryPoolVector; typedef QHash PointerInfoMap; -class MemoryManager +class EXPORT MemoryManager { public: static bool init(); diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 85d99c910..1fecc8d9e 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -104,8 +104,6 @@ void AudioPort::doProcessing() { const fpp_t fpp = engine::mixer()->framesPerPeriod(); - if( m_playHandles.isEmpty() ) return; // skip processing if no playhandles are connected - m_portBuffer = BufferManager::acquire(); // get buffer for processing engine::mixer()->clearAudioBuffer( m_portBuffer, fpp ); // clear the audioport buffer so we can use it From 9a3d3cb3064e38c22b60d092a32edef1959673b9 Mon Sep 17 00:00:00 2001 From: Vesa Date: Fri, 29 Aug 2014 20:55:40 +0300 Subject: [PATCH 17/28] Fix windows compiling --- include/InstrumentPlayHandle.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/InstrumentPlayHandle.h b/include/InstrumentPlayHandle.h index 1b4d3d12a..239e08d1f 100644 --- a/include/InstrumentPlayHandle.h +++ b/include/InstrumentPlayHandle.h @@ -28,9 +28,9 @@ #include "PlayHandle.h" #include "Instrument.h" #include "NotePlayHandle.h" +#include "export.h" - -class InstrumentPlayHandle : public PlayHandle +class EXPORT InstrumentPlayHandle : public PlayHandle { public: InstrumentPlayHandle( Instrument * instrument, InstrumentTrack* instrumentTrack ); From 7bc97f5d5bd9fcb43971f62e546ba464be1e818f Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 31 Aug 2014 13:44:28 +0300 Subject: [PATCH 18/28] Fixes --- include/BufferManager.h | 2 +- src/core/PlayHandle.cpp | 4 ++-- src/core/SamplePlayHandle.cpp | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/BufferManager.h b/include/BufferManager.h index f605e38b8..b9964bbfc 100644 --- a/include/BufferManager.h +++ b/include/BufferManager.h @@ -37,7 +37,7 @@ const int BM_INITIAL_BUFFERS = 512; //const int BM_INCREMENT = 64; -class BufferManager +class EXPORT BufferManager { public: static void init(); diff --git a/src/core/PlayHandle.cpp b/src/core/PlayHandle.cpp index b32a19c94..1e3666c08 100644 --- a/src/core/PlayHandle.cpp +++ b/src/core/PlayHandle.cpp @@ -50,13 +50,13 @@ void PlayHandle::doProcessing() } else { - play( m_playHandleBuffer ); + play( NULL ); } } void PlayHandle::releaseBuffer() { - BufferManager::release( m_playHandleBuffer ); + if( m_playHandleBuffer ) BufferManager::release( m_playHandleBuffer ); m_playHandleBuffer = NULL; } diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 188002ed2..88fdd1dda 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -122,8 +122,11 @@ void SamplePlayHandle::play( sampleFrame * buffer ) /* stereoVolumeVector v = { { m_volumeModel->value() / DefaultVolume, m_volumeModel->value() / DefaultVolume } };*/ - m_sampleBuffer->play( workingBuffer, &m_state, frames, - BaseFreq ); + if( ! m_sampleBuffer->play( workingBuffer, &m_state, frames, + BaseFreq ) ) + { + memset( workingBuffer, 0, frames * sizeof( sampleFrame ) ); + } } m_frame += frames; From 68b5a21d14352188175b4f95f9041cbaa45b837e Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 2 Sep 2014 23:25:05 +0200 Subject: [PATCH 19/28] Initialize BufferManager from within Mixer Avoid crashes caused by worker threads accessing the buffer manager before it is initialized. Therefore initialize it from within the Mixer constructor which has the side effect that it gets initialized in console-only rendering mode as well. --- include/BufferManager.h | 2 +- src/core/BufferManager.cpp | 6 +++--- src/core/Mixer.cpp | 3 +++ src/core/main.cpp | 5 +---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/BufferManager.h b/include/BufferManager.h index b9964bbfc..b437ae05e 100644 --- a/include/BufferManager.h +++ b/include/BufferManager.h @@ -40,7 +40,7 @@ const int BM_INITIAL_BUFFERS = 512; class EXPORT BufferManager { public: - static void init(); + static void init( fpp_t framesPerPeriod ); static sampleFrame * acquire(); static void release( sampleFrame * buf ); static void refresh(); diff --git a/src/core/BufferManager.cpp b/src/core/BufferManager.cpp index f5925acfc..b82b94e8f 100644 --- a/src/core/BufferManager.cpp +++ b/src/core/BufferManager.cpp @@ -34,18 +34,18 @@ QAtomicInt BufferManager::s_releasedIndex = 0; int BufferManager::s_size; -void BufferManager::init() +void BufferManager::init( fpp_t framesPerPeriod ) { s_available = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); s_released = MM_ALLOC( sampleFrame*, BM_INITIAL_BUFFERS ); - int c = engine::mixer()->framesPerPeriod() * BM_INITIAL_BUFFERS; + int c = framesPerPeriod * BM_INITIAL_BUFFERS; sampleFrame * b = MM_ALLOC( sampleFrame, c ); for( int i = 0; i < BM_INITIAL_BUFFERS; ++i ) { s_available[ i ] = b; - b += engine::mixer()->framesPerPeriod(); + b += framesPerPeriod; } s_availableIndex = BM_INITIAL_BUFFERS - 1; s_size = BM_INITIAL_BUFFERS; diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 74abde8c7..3bc852260 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -118,6 +118,9 @@ Mixer::Mixer() : m_fifo = new fifo( 1 ); } + // now that framesPerPeriod is fixed initialize global BufferManager + BufferManager::init( m_framesPerPeriod ); + m_workingBuf = (sampleFrame*) MemoryHelper::alignedMalloc( m_framesPerPeriod * sizeof( sampleFrame ) ); for( int i = 0; i < 3; i++ ) diff --git a/src/core/main.cpp b/src/core/main.cpp index 9d3670966..dd95eaf64 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -76,7 +76,6 @@ #include "DataFile.h" #include "song.h" #include "LmmsPalette.h" -#include "BufferManager.h" static inline QString baseName( const QString & _file ) { @@ -440,9 +439,6 @@ int main( int argc, char * * argv ) // init central engine which handles all components of LMMS engine::init(); - // init buffer manager - has to be done after fpp is known - BufferManager::init(); - splashScreen.hide(); // re-intialize RNG - shared libraries might have srand() or @@ -509,6 +505,7 @@ int main( int argc, char * * argv ) { // we're going to render our song engine::init( false ); + printf( "loading project...\n" ); engine::getSong()->loadProject( file_to_load ); printf( "done\n" ); From 9fe55161a83defb76d33ea751a03103362fbb607 Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 3 Sep 2014 15:43:04 +0300 Subject: [PATCH 20/28] Remove base64.h again (was re-added accidentally at merge resolution) --- include/Plugin.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/Plugin.h b/include/Plugin.h index a6c344b44..f5c1ab046 100644 --- a/include/Plugin.h +++ b/include/Plugin.h @@ -31,7 +31,6 @@ #include "JournallingObject.h" #include "Model.h" -#include "base64.h" #include "MemoryManager.h" From dc4bfdc60defdd9b47a21848af70fd350ca7ef28 Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 12 Oct 2014 00:59:50 +0300 Subject: [PATCH 21/28] Various fixes and precautions Samplebuffer: reload all samples when samplerate changes. This is because of the way LMMS uses samples: we always resample all samples t$ LadspaEffect: some safeguards for the non-inplacebroken plugins which use the same buffer for input and output. Theoretically, if some p$ FxMixer: fix effect processing in multichannel-chains --- include/SampleBuffer.h | 2 +- plugins/LadspaEffect/LadspaEffect.cpp | 5 ++++- src/core/SampleBuffer.cpp | 8 +++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index dd832281b..ef97f84ee 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -267,7 +267,7 @@ public slots: void setEndFrame( const f_cnt_t _e ); void setAmplification( float _a ); void setReversed( bool _on ); - + void sampleRateChanged(); private: void update( bool _keep_settings = false ); diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index 891a49438..a2336f519 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -307,6 +307,8 @@ void LadspaEffect::pluginInstantiation() int inputch = 0; int outputch = 0; LADSPA_Data * inbuf [2]; + inbuf[0] = NULL; + inbuf[1] = NULL; for( ch_cnt_t proc = 0; proc < processorCount(); proc++ ) { multi_proc_t ports; @@ -335,7 +337,7 @@ void LadspaEffect::pluginInstantiation() manager->isPortOutput( m_key, port ) ) { p->rate = CHANNEL_OUT; - if( ! m_inPlaceBroken ) + if( ! m_inPlaceBroken && inbuf[ outputch ] ) { p->buffer = inbuf[ outputch ]; outputch++; @@ -343,6 +345,7 @@ void LadspaEffect::pluginInstantiation() else { p->buffer = MM_ALLOC( LADSPA_Data, engine::mixer()->framesPerPeriod() ); + m_inPlaceBroken = true; } } else if( manager->isPortInput( m_key, port ) ) diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 5d218ab06..d0e0397fc 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -84,6 +84,7 @@ SampleBuffer::SampleBuffer( const QString & _audio_file, { loadFromBase64( _audio_file ); } + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); update(); } @@ -111,6 +112,7 @@ SampleBuffer::SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ) : memcpy( m_origData, _data, _frames * BYTES_PER_FRAME ); m_origFrames = _frames; } + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); update(); } @@ -138,6 +140,7 @@ SampleBuffer::SampleBuffer( const f_cnt_t _frames ) : memset( m_origData, 0, _frames * BYTES_PER_FRAME ); m_origFrames = _frames; } + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); update(); } @@ -152,7 +155,10 @@ SampleBuffer::~SampleBuffer() - +void SampleBuffer::sampleRateChanged() +{ + update(); +} void SampleBuffer::update( bool _keep_settings ) From f25da35c244be9fe3af3d4c73b44658bcecb10ae Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 15 Oct 2014 20:31:42 +0300 Subject: [PATCH 22/28] Sanitize all channel outputs when exporting --- include/MixHelpers.h | 6 ++++++ src/core/FxMixer.cpp | 13 +++++++++---- src/core/MixHelpers.cpp | 25 ++++++++++++++++++++++++- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/include/MixHelpers.h b/include/MixHelpers.h index 9b953ffc7..376b0bd61 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -49,6 +49,12 @@ void addMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuff /*! \brief Same as addMultiplied, but sanitize output (strip out infs/nans) */ void addSanitizedMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ); +/*! \brief Add samples from src multiplied by coeffSrc and coeffSrcBuf to dst - sanitized version */ +void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ); + +/*! \brief Add samples from src multiplied by coeffSrc and coeffSrcBuf to dst - sanitized version */ +void addSanitizedMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ); + /*! \brief Add samples from src multiplied by coeffSrcLeft/coeffSrcRight to dst */ void addMultipliedStereo( sampleFrame* dst, const sampleFrame* src, float coeffSrcLeft, float coeffSrcRight, int frames ); diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index fcd1cc959..f599ae521 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -119,6 +119,7 @@ void FxChannel::unmuteForSolo() void FxChannel::doProcessing() { const fpp_t fpp = engine::mixer()->framesPerPeriod(); + const bool exporting = engine::getSong()->isExporting(); if( m_muted == false ) { @@ -141,21 +142,25 @@ void FxChannel::doProcessing() if( ! volBuf && ! sendBuf ) // neither volume nor send has sample-exact data... { const float v = sender->m_volumeModel.value() * sendModel->value(); - MixHelpers::addMultiplied( m_buffer, ch_buf, v, fpp ); + if( exporting ) { MixHelpers::addSanitizedMultiplied( m_buffer, ch_buf, v, fpp ); } + else { MixHelpers::addMultiplied( m_buffer, ch_buf, v, fpp ); } } else if( volBuf && sendBuf ) // both volume and send have sample-exact data { - MixHelpers::addMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); + if( exporting ) { MixHelpers::addSanitizedMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); } + else { MixHelpers::addMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); } } else if( volBuf ) // volume has sample-exact data but send does not { const float v = sendModel->value(); - MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); + if( exporting ) { MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); } + else { MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); } } else // vice versa { const float v = sender->m_volumeModel.value(); - MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); + if( exporting ) { MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); } + else { MixHelpers::addMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); } } m_hasInput = true; } diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index 154ec6024..da5f34c55 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -124,11 +124,34 @@ void addMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuff } +void addSanitizedMultipliedByBuffer( sampleFrame* dst, const sampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ) +{ + for( int f = 0; f < frames; ++f ) + { + dst[f][0] += ( isinff( src[f][0] ) || isnanf( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f]; + dst[f][1] += ( isinff( src[f][1] ) || isnanf( src[f][1] ) ) ? 0.0f : src[f][1] * coeffSrc * coeffSrcBuf->values()[f]; + } +} + +void addSanitizedMultipliedByBuffers( sampleFrame* dst, const sampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ) +{ + for( int f = 0; f < frames; ++f ) + { + dst[f][0] += ( isinff( src[f][0] ) || isnanf( src[f][0] ) ) + ? 0.0f + : src[f][0] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f]; + dst[f][1] += ( isinff( src[f][1] ) || isnanf( src[f][1] ) ) + ? 0.0f + : src[f][1] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f]; + } + +} + struct AddSanitizedMultipliedOp { AddSanitizedMultipliedOp( float coeff ) : m_coeff( coeff ) { } - + void operator()( sampleFrame& dst, const sampleFrame& src ) const { dst[0] += ( isinff( src[0] ) || isnanf( src[0] ) ) ? 0.0f : src[0] * m_coeff; From 50bfed7180b2554d80f7109c2a52b3f9a7a936ee Mon Sep 17 00:00:00 2001 From: Vesa Date: Wed, 15 Oct 2014 20:48:22 +0300 Subject: [PATCH 23/28] Fix Carla in memmgr branch --- plugins/carlabase/carla.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/carlabase/carla.cpp b/plugins/carlabase/carla.cpp index b07e7119d..167a476d2 100644 --- a/plugins/carlabase/carla.cpp +++ b/plugins/carlabase/carla.cpp @@ -180,7 +180,7 @@ CarlaInstrument::CarlaInstrument(InstrumentTrack* const instrumentTrack, const D fDescriptor->activate(fHandle); // we need a play-handle which cares for calling play() - InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); + InstrumentPlayHandle * iph = new InstrumentPlayHandle( this, instrumentTrack ); engine::mixer()->addPlayHandle( iph ); connect(engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(sampleRateChanged())); From 8a596b0898bdc12568c8b3670b52ba93bdb93e4e Mon Sep 17 00:00:00 2001 From: Vesa Date: Tue, 21 Oct 2014 00:09:42 +0300 Subject: [PATCH 24/28] Sanitize output of all effects when exporting --- include/MixHelpers.h | 2 ++ src/core/EffectChain.cpp | 12 +++++++++++- src/core/MixHelpers.cpp | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/include/MixHelpers.h b/include/MixHelpers.h index 376b0bd61..4a2f510ee 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -33,6 +33,8 @@ namespace MixHelpers bool isSilent( const sampleFrame* src, int frames ); +bool sanitize( sampleFrame * src, int frames ); + /*! \brief Add samples from src to dst */ void add( sampleFrame* dst, const sampleFrame* src, int frames ); diff --git a/src/core/EffectChain.cpp b/src/core/EffectChain.cpp index 1d57de940..708cd9698 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -31,7 +31,8 @@ #include "engine.h" #include "debug.h" #include "DummyEffect.h" - +#include "MixHelpers.h" +#include "song.h" EffectChain::EffectChain( Model * _parent ) : @@ -194,6 +195,11 @@ bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, b { return false; } + const bool exporting = engine::getSong()->isExporting(); + if( exporting ) // strip infs/nans if exporting + { + MixHelpers::sanitize( _buf, _frames ); + } bool moreEffects = false; for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); ++it ) @@ -201,6 +207,10 @@ bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, b if( hasInputNoise || ( *it )->isRunning() ) { moreEffects |= ( *it )->processAudioBuffer( _buf, _frames ); + if( exporting ) // strip infs/nans if exporting + { + MixHelpers::sanitize( _buf, _frames ); + } } #ifdef LMMS_DEBUG diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index da5f34c55..a6e43e05a 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -69,6 +69,25 @@ bool isSilent( const sampleFrame* src, int frames ) } +/*! \brief Function for sanitizing a buffer of infs/nans - returns true if those are found */ +bool sanitize( sampleFrame * src, int frames ) +{ + bool found = false; + for( int f = 0; f < frames; ++f ) + { + for( int c = 0; c < 2; ++c ) + { + if( isinff( src[f][c] ) || isnanf( src[f][c] ) ) + { + src[f][c] = 0.0f; + found = true; + } + } + } + return found; +} + + struct AddOp { void operator()( sampleFrame& dst, const sampleFrame& src ) const From f207613d5f98c4f657a3c21d8ac79872a8ffa071 Mon Sep 17 00:00:00 2001 From: Vesa Date: Fri, 31 Oct 2014 17:46:11 +0200 Subject: [PATCH 25/28] Some attention on peak controller --- include/PeakController.h | 7 +++- .../peak_controller_effect.cpp | 2 +- src/core/PeakController.cpp | 35 ++++++++++++------- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/include/PeakController.h b/include/PeakController.h index 15f72f07f..fc9ea92d8 100644 --- a/include/PeakController.h +++ b/include/PeakController.h @@ -61,7 +61,8 @@ public: public slots: virtual ControllerDialog * createDialog( QWidget * _parent ); - void handleDestroyedEffect( ); + void handleDestroyedEffect( ); + void updateCoeffs(); protected: // The internal per-controller get-value function @@ -77,6 +78,10 @@ private: static int m_getCount; static int m_loadCount; static bool m_buggedFile; + + float m_attackCoeff; + float m_decayCoeff; + bool m_coeffNeedsUpdate; } ; diff --git a/plugins/peak_controller_effect/peak_controller_effect.cpp b/plugins/peak_controller_effect/peak_controller_effect.cpp index 9903bb635..aeb3677ad 100644 --- a/plugins/peak_controller_effect/peak_controller_effect.cpp +++ b/plugins/peak_controller_effect/peak_controller_effect.cpp @@ -131,7 +131,7 @@ bool PeakControllerEffect::processAudioBuffer( sampleFrame * _buf, float curRMS = sqrt_neg( sum / _frames ); const float amount = c.m_amountModel.value() * c.m_amountMultModel.value(); - m_lastSample = c.m_baseModel.value() + amount * curRMS; + m_lastSample = qBound( 0.0f, c.m_baseModel.value() + amount * curRMS, 1.0f ); return isRunning(); } diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 53798ea83..25aaec456 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -60,6 +60,10 @@ PeakController::PeakController( Model * _parent, connect( m_peakEffect, SIGNAL( destroyed( ) ), this, SLOT( handleDestroyedEffect( ) ) ); } + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateCoeffs() ) ); + connect( m_peakEffect->attackModel(), SIGNAL( dataChanged() ), this, SLOT( updateCoeffs() ) ); + connect( m_peakEffect->decayModel(), SIGNAL( dataChanged() ), this, SLOT( updateCoeffs() ) ); + m_coeffNeedsUpdate = true; } @@ -80,6 +84,14 @@ PeakController::~PeakController() void PeakController::updateValueBuffer() { + if( m_coeffNeedsUpdate ) + { + const float ratio = 44100.0f / engine::mixer()->processingSampleRate(); + m_attackCoeff = 1.0f - powf( 10.0f, -0.5f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio ); + m_decayCoeff = 1.0f - powf( 10.0f, -0.5f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio ); + m_coeffNeedsUpdate = false; + } + if( m_peakEffect ) { float targetSample = m_peakEffect->lastSample(); @@ -87,24 +99,17 @@ void PeakController::updateValueBuffer() { const f_cnt_t frames = engine::mixer()->framesPerPeriod(); float * values = m_valueBuffer.values(); - - const float ratio = ( 44100.0 / 256.0 ) / engine::mixer()->processingSampleRate(); - const float v = m_currentSample >= targetSample - ? m_peakEffect->decayModel()->value() - : m_peakEffect->attackModel()->value(); - const float diff = ( targetSample - m_currentSample ) * ratio; - const float a = ( 1.0f - sqrt_neg( sqrt_neg( v ) ) ) * diff; - const bool att = m_currentSample < targetSample; for( f_cnt_t f = 0; f < frames; ++f ) { - if( att ) // going up... + const float diff = ( targetSample - m_currentSample ); + if( m_currentSample < targetSample ) // going up... { - m_currentSample = qMin( targetSample, m_currentSample + a ); // qmin prevents overshoot + m_currentSample += diff * m_attackCoeff; } - else + else if( m_currentSample > targetSample ) // going down { - m_currentSample = qMax( targetSample, m_currentSample + a ); // qmax prevents overshoot + m_currentSample += diff * m_decayCoeff; } values[f] = m_currentSample; } @@ -122,6 +127,12 @@ void PeakController::updateValueBuffer() } +void PeakController::updateCoeffs() +{ + m_coeffNeedsUpdate = true; +} + + void PeakController::handleDestroyedEffect( ) { // possible race condition... From b441bdae15446f1965e38ef26a81cdad9c8da61a Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 9 Nov 2014 21:01:31 +0200 Subject: [PATCH 26/28] Freeverb3: make it work properly on all samplerates --- plugins/LadspaEffect/LadspaEffect.cpp | 1 - .../cmt/src/freeverb/Components/revmodel.cpp | 51 +++++++++--------- .../cmt/src/freeverb/Components/revmodel.h | 54 ++++++++++--------- .../cmt/src/freeverb/freeverb.cpp | 9 ++-- 4 files changed, 59 insertions(+), 56 deletions(-) diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index a2336f519..15b5c284f 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -581,7 +581,6 @@ sample_rate_t LadspaEffect::maxSamplerate( const QString & _name ) __buggy_plugins["C* AmpVTS"] = 88200; __buggy_plugins["Chorus2"] = 44100; __buggy_plugins["Notch Filter"] = 96000; - __buggy_plugins["Freeverb"] = 44100; __buggy_plugins["TAP Reflector"] = 192000; } if( __buggy_plugins.contains( _name ) ) diff --git a/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.cpp b/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.cpp index f848944d4..4f7fcd4d1 100644 --- a/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.cpp +++ b/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.cpp @@ -6,33 +6,34 @@ #include "revmodel.h" -revmodel::revmodel() +revmodel::revmodel( float sampleRatio ) : + m_sampleRatio( sampleRatio ) { // Tie the components to their buffers - combL[0].setbuffer(bufcombL1,combtuningL1); - combR[0].setbuffer(bufcombR1,combtuningR1); - combL[1].setbuffer(bufcombL2,combtuningL2); - combR[1].setbuffer(bufcombR2,combtuningR2); - combL[2].setbuffer(bufcombL3,combtuningL3); - combR[2].setbuffer(bufcombR3,combtuningR3); - combL[3].setbuffer(bufcombL4,combtuningL4); - combR[3].setbuffer(bufcombR4,combtuningR4); - combL[4].setbuffer(bufcombL5,combtuningL5); - combR[4].setbuffer(bufcombR5,combtuningR5); - combL[5].setbuffer(bufcombL6,combtuningL6); - combR[5].setbuffer(bufcombR6,combtuningR6); - combL[6].setbuffer(bufcombL7,combtuningL7); - combR[6].setbuffer(bufcombR7,combtuningR7); - combL[7].setbuffer(bufcombL8,combtuningL8); - combR[7].setbuffer(bufcombR8,combtuningR8); - allpassL[0].setbuffer(bufallpassL1,allpasstuningL1); - allpassR[0].setbuffer(bufallpassR1,allpasstuningR1); - allpassL[1].setbuffer(bufallpassL2,allpasstuningL2); - allpassR[1].setbuffer(bufallpassR2,allpasstuningR2); - allpassL[2].setbuffer(bufallpassL3,allpasstuningL3); - allpassR[2].setbuffer(bufallpassR3,allpasstuningR3); - allpassL[3].setbuffer(bufallpassL4,allpasstuningL4); - allpassR[3].setbuffer(bufallpassR4,allpasstuningR4); + combL[0].setbuffer(bufcombL1,static_cast( combtuningL1 * m_sampleRatio )); + combR[0].setbuffer(bufcombR1,static_cast( combtuningR1 * m_sampleRatio )); + combL[1].setbuffer(bufcombL2,static_cast( combtuningL2 * m_sampleRatio )); + combR[1].setbuffer(bufcombR2,static_cast( combtuningR2 * m_sampleRatio )); + combL[2].setbuffer(bufcombL3,static_cast( combtuningL3 * m_sampleRatio )); + combR[2].setbuffer(bufcombR3,static_cast( combtuningR3 * m_sampleRatio )); + combL[3].setbuffer(bufcombL4,static_cast( combtuningL4 * m_sampleRatio )); + combR[3].setbuffer(bufcombR4,static_cast( combtuningR4 * m_sampleRatio )); + combL[4].setbuffer(bufcombL5,static_cast( combtuningL5 * m_sampleRatio )); + combR[4].setbuffer(bufcombR5,static_cast( combtuningR5 * m_sampleRatio )); + combL[5].setbuffer(bufcombL6,static_cast( combtuningL6 * m_sampleRatio )); + combR[5].setbuffer(bufcombR6,static_cast( combtuningR6 * m_sampleRatio )); + combL[6].setbuffer(bufcombL7,static_cast( combtuningL7 * m_sampleRatio )); + combR[6].setbuffer(bufcombR7,static_cast( combtuningR7 * m_sampleRatio )); + combL[7].setbuffer(bufcombL8,static_cast( combtuningL8 * m_sampleRatio )); + combR[7].setbuffer(bufcombR8,static_cast( combtuningR8 * m_sampleRatio )); + allpassL[0].setbuffer(bufallpassL1,static_cast( allpasstuningL1 * m_sampleRatio )); + allpassR[0].setbuffer(bufallpassR1,static_cast( allpasstuningR1 * m_sampleRatio )); + allpassL[1].setbuffer(bufallpassL2,static_cast( allpasstuningL2 * m_sampleRatio )); + allpassR[1].setbuffer(bufallpassR2,static_cast( allpasstuningR2 * m_sampleRatio )); + allpassL[2].setbuffer(bufallpassL3,static_cast( allpasstuningL3 * m_sampleRatio )); + allpassR[2].setbuffer(bufallpassR3,static_cast( allpasstuningR3 * m_sampleRatio )); + allpassL[3].setbuffer(bufallpassL4,static_cast( allpasstuningL4 * m_sampleRatio )); + allpassR[3].setbuffer(bufallpassR4,static_cast( allpasstuningR4 * m_sampleRatio )); // Set default values allpassL[0].setfeedback(0.5f); diff --git a/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.h b/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.h index aec39dfee..9846eb40d 100644 --- a/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.h +++ b/plugins/LadspaEffect/cmt/src/freeverb/Components/revmodel.h @@ -11,10 +11,12 @@ #include "allpass.h" #include "tuning.h" +const int maxSampleRatio = 18; // enough for largest possible samplerate, 8 * 96000 + class revmodel { public: - revmodel(); + revmodel( float sampleRatio ); void mute(); void processmix(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); void processreplace(float *inputL, float *inputR, float *outputL, float *outputR, long numsamples, int skip); @@ -40,6 +42,8 @@ private: float dry; float width; float mode; + + float m_sampleRatio; // The following are all declared inline // to remove the need for dynamic allocation @@ -54,32 +58,32 @@ private: allpass allpassR[numallpasses]; // Buffers for the combs - float bufcombL1[combtuningL1]; - float bufcombR1[combtuningR1]; - float bufcombL2[combtuningL2]; - float bufcombR2[combtuningR2]; - float bufcombL3[combtuningL3]; - float bufcombR3[combtuningR3]; - float bufcombL4[combtuningL4]; - float bufcombR4[combtuningR4]; - float bufcombL5[combtuningL5]; - float bufcombR5[combtuningR5]; - float bufcombL6[combtuningL6]; - float bufcombR6[combtuningR6]; - float bufcombL7[combtuningL7]; - float bufcombR7[combtuningR7]; - float bufcombL8[combtuningL8]; - float bufcombR8[combtuningR8]; + float bufcombL1[combtuningL1 * maxSampleRatio]; + float bufcombR1[combtuningR1 * maxSampleRatio]; + float bufcombL2[combtuningL2 * maxSampleRatio]; + float bufcombR2[combtuningR2 * maxSampleRatio]; + float bufcombL3[combtuningL3 * maxSampleRatio]; + float bufcombR3[combtuningR3 * maxSampleRatio]; + float bufcombL4[combtuningL4 * maxSampleRatio]; + float bufcombR4[ combtuningR4 * maxSampleRatio ]; + float bufcombL5[ combtuningL5 * maxSampleRatio ]; + float bufcombR5[ combtuningR5 * maxSampleRatio ]; + float bufcombL6[ combtuningL6 * maxSampleRatio ]; + float bufcombR6[ combtuningR6 * maxSampleRatio ]; + float bufcombL7[ combtuningL7 * maxSampleRatio ]; + float bufcombR7[ combtuningR7 * maxSampleRatio ]; + float bufcombL8[ combtuningL8 * maxSampleRatio ]; + float bufcombR8[ combtuningR8 * maxSampleRatio ]; // Buffers for the allpasses - float bufallpassL1[allpasstuningL1]; - float bufallpassR1[allpasstuningR1]; - float bufallpassL2[allpasstuningL2]; - float bufallpassR2[allpasstuningR2]; - float bufallpassL3[allpasstuningL3]; - float bufallpassR3[allpasstuningR3]; - float bufallpassL4[allpasstuningL4]; - float bufallpassR4[allpasstuningR4]; + float bufallpassL1[ allpasstuningL1 * maxSampleRatio ]; + float bufallpassR1[ allpasstuningR1 * maxSampleRatio ]; + float bufallpassL2[ allpasstuningL2 * maxSampleRatio ]; + float bufallpassR2[ allpasstuningR2 * maxSampleRatio ]; + float bufallpassL3[ allpasstuningL3 * maxSampleRatio ]; + float bufallpassR3[ allpasstuningR3 * maxSampleRatio ]; + float bufallpassL4[ allpasstuningL4 * maxSampleRatio ]; + float bufallpassR4[ allpasstuningR4 * maxSampleRatio ]; }; #endif//_revmodel_ diff --git a/plugins/LadspaEffect/cmt/src/freeverb/freeverb.cpp b/plugins/LadspaEffect/cmt/src/freeverb/freeverb.cpp index 760578630..418e4d91c 100644 --- a/plugins/LadspaEffect/cmt/src/freeverb/freeverb.cpp +++ b/plugins/LadspaEffect/cmt/src/freeverb/freeverb.cpp @@ -57,14 +57,13 @@ class Freeverb3 : public CMT_PluginInstance, public revmodel { public: Freeverb3(const LADSPA_Descriptor *, unsigned long lSampleRate) - : CMT_PluginInstance(FV_NumPorts) { - /* Richard's note 17/5/2000. Hmm - not sure I like the fact that - lSampleRate isn't actually used in this function! */ - } + : CMT_PluginInstance(FV_NumPorts), + revmodel( (float) lSampleRate / 44100.0f ) + {} + friend void activateFreeverb3(LADSPA_Handle Instance); friend void runFreeverb3(LADSPA_Handle Instance, unsigned long SampleCount); - }; /*****************************************************************************/ From ba05b7523d4fe1e757b74df7c8c12e3e532a09a7 Mon Sep 17 00:00:00 2001 From: Vesa Date: Mon, 10 Nov 2014 22:53:32 +0200 Subject: [PATCH 27/28] More peak controller changes: Add treshold knob to peak controller This causes the peak controller to react only when the measured peaks are above the set treshold Might be useful for finetuning your sidechains --- plugins/peak_controller_effect/artwork.png | Bin 26354 -> 29188 bytes .../peak_controller_effect.cpp | 2 ++ .../peak_controller_effect_control_dialog.cpp | 8 +++++++- .../peak_controller_effect_control_dialog.h | 1 + .../peak_controller_effect_controls.cpp | 5 +++++ .../peak_controller_effect_controls.h | 1 + src/core/PeakController.cpp | 4 ++-- 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/plugins/peak_controller_effect/artwork.png b/plugins/peak_controller_effect/artwork.png index 31296ca909354ca9680d46b0fbbcd12de5cfaa15..66fe2c95226289e02a9fb04352d7ea89f3f1c45e 100644 GIT binary patch literal 29188 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�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 5, 30, 8, 8 ); + tl->setContentsMargins( 5, 30, 5, 8 ); QHBoxLayout * l = new QHBoxLayout; l->setSpacing( 4 ); @@ -74,12 +74,18 @@ PeakControllerEffectControlDialog::PeakControllerEffectControlDialog( m_decayKnob->setLabel( tr( "DCAY" ) ); m_decayKnob->setModel( &_controls->m_decayModel ); m_decayKnob->setHintText( tr( "Release:" ) + " ", "" ); + + m_tresholdKnob = new knob( knobBright_26, this ); + m_tresholdKnob->setLabel( tr( "TRES" ) ); + m_tresholdKnob->setModel( &_controls->m_tresholdModel ); + m_tresholdKnob->setHintText( tr( "Treshold:" ) + " ", "" ); l->addWidget( m_baseKnob ); l->addWidget( m_amountKnob ); l->addWidget( m_amountMultKnob ); l->addWidget( m_attackKnob ); l->addWidget( m_decayKnob ); + l->addWidget( m_tresholdKnob ); l->addStretch(); // expand, so other widgets have minimum width tl->addLayout( l ); diff --git a/plugins/peak_controller_effect/peak_controller_effect_control_dialog.h b/plugins/peak_controller_effect/peak_controller_effect_control_dialog.h index b161a64c5..64e876e37 100644 --- a/plugins/peak_controller_effect/peak_controller_effect_control_dialog.h +++ b/plugins/peak_controller_effect/peak_controller_effect_control_dialog.h @@ -48,6 +48,7 @@ protected: knob * m_amountKnob; knob * m_attackKnob; knob * m_decayKnob; + knob * m_tresholdKnob; ledCheckBox * m_muteLed; ledCheckBox * m_absLed; diff --git a/plugins/peak_controller_effect/peak_controller_effect_controls.cpp b/plugins/peak_controller_effect/peak_controller_effect_controls.cpp index 3fdeecc34..78109366b 100644 --- a/plugins/peak_controller_effect/peak_controller_effect_controls.cpp +++ b/plugins/peak_controller_effect/peak_controller_effect_controls.cpp @@ -41,6 +41,7 @@ PeakControllerEffectControls( PeakControllerEffect * _eff ) : m_amountModel( 1.0, -1.0, 1.0, 0.005, this, tr( "Modulation amount" ) ), m_attackModel( 0, 0, 0.999, 0.001, this, tr( "Attack" ) ), m_decayModel( 0, 0, 0.999, 0.001, this, tr( "Release" ) ), + m_tresholdModel( 0, 0, 1.0, 0.001, this, tr( "Treshold" ) ), m_muteModel( false, this, tr( "Mute output" ) ), m_absModel( true, this, tr("Abs Value") ), m_amountMultModel( 1.0, 0, 32, 0.2, this, tr("Amount Multiplicator") ) @@ -61,6 +62,8 @@ void PeakControllerEffectControls::loadSettings( const QDomElement & _this ) m_absModel.loadSettings( _this, "abs" ); m_amountMultModel.loadSettings( _this, "amountmult" ); + + m_tresholdModel.loadSettings( _this, "treshold" ); /*If the peak controller effect is NOT loaded from project, * m_effectId stored is useless. @@ -102,6 +105,8 @@ void PeakControllerEffectControls::saveSettings( QDomDocument & _doc, m_absModel.saveSettings( _doc, _this, "abs" ); m_amountMultModel.saveSettings( _doc, _this, "amountmult" ); + + m_tresholdModel.saveSettings( _doc, _this, "treshold" ); } diff --git a/plugins/peak_controller_effect/peak_controller_effect_controls.h b/plugins/peak_controller_effect/peak_controller_effect_controls.h index 6f9cca92e..ad56fa99a 100644 --- a/plugins/peak_controller_effect/peak_controller_effect_controls.h +++ b/plugins/peak_controller_effect/peak_controller_effect_controls.h @@ -65,6 +65,7 @@ private: FloatModel m_amountModel; FloatModel m_attackModel; FloatModel m_decayModel; + FloatModel m_tresholdModel; BoolModel m_muteModel; BoolModel m_absModel; FloatModel m_amountMultModel; diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 25aaec456..87ea7a948 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -87,8 +87,8 @@ void PeakController::updateValueBuffer() if( m_coeffNeedsUpdate ) { const float ratio = 44100.0f / engine::mixer()->processingSampleRate(); - m_attackCoeff = 1.0f - powf( 10.0f, -0.5f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio ); - m_decayCoeff = 1.0f - powf( 10.0f, -0.5f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio ); + m_attackCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->attackModel()->value() ) * ratio ); + m_decayCoeff = 1.0f - powf( 2.0f, -0.3f * ( 1.0f - m_peakEffect->decayModel()->value() ) * ratio ); m_coeffNeedsUpdate = false; } From 815a70a68246f5f842d5ba5232e7134e0a8cd91c Mon Sep 17 00:00:00 2001 From: Vesa Date: Sun, 16 Nov 2014 16:27:09 +0200 Subject: [PATCH 28/28] Sync --- src/core/FxMixer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index f599ae521..3437b431b 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -175,10 +175,10 @@ void FxChannel::doProcessing() m_fxChain.startRunning(); } - m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp, m_hasInput ); + m_stillRunning = m_fxChain.processAudioBuffer( m_buffer, fpp, m_hasInput ); - m_peakLeft = qMax( m_peakLeft, engine::mixer()->peakValueLeft( _buf, fpp ) * v ); - m_peakRight = qMax( m_peakRight, engine::mixer()->peakValueRight( _buf, fpp ) * v ); + m_peakLeft = qMax( m_peakLeft, engine::mixer()->peakValueLeft( m_buffer, fpp ) * v ); + m_peakRight = qMax( m_peakRight, engine::mixer()->peakValueRight( m_buffer, fpp ) * v ); } else {