diff --git a/data/themes/default/mixer_send_off.png b/data/themes/default/mixer_send_off.png new file mode 100644 index 000000000..6f426c36f Binary files /dev/null and b/data/themes/default/mixer_send_off.png differ diff --git a/data/themes/default/mixer_send_on.png b/data/themes/default/mixer_send_on.png new file mode 100644 index 000000000..6861c7acd Binary files /dev/null and b/data/themes/default/mixer_send_on.png differ diff --git a/data/themes/default/send_bg_arrow.png b/data/themes/default/send_bg_arrow.png new file mode 100644 index 000000000..fde514da6 Binary files /dev/null and b/data/themes/default/send_bg_arrow.png differ diff --git a/include/AudioAlsa.h b/include/AudioAlsa.h index 6e194cf77..a2a645c9f 100644 --- a/include/AudioAlsa.h +++ b/include/AudioAlsa.h @@ -34,17 +34,17 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" class lcdSpinBox; class QComboBox; -class AudioAlsa : public AudioDevice, public QThread +class AudioAlsa : public AudioBackend, public QThread { public: - AudioAlsa( bool & _success_ful, mixer * _mixer ); + AudioAlsa( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioAlsa(); inline static QString name() @@ -56,7 +56,7 @@ public: static QString probeDevice(); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioDevice.h b/include/AudioBackend.h similarity index 60% rename from include/AudioDevice.h rename to include/AudioBackend.h index 660e2ca01..10d17c2da 100644 --- a/include/AudioDevice.h +++ b/include/AudioBackend.h @@ -1,5 +1,5 @@ /* - * AudioDevice.h - base-class for audio-devices, used by LMMS-mixer + * AudioBackend.h - base-class for audio-devices, used by LMMS-mixer * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -26,37 +26,31 @@ #define _AUDIO_DEVICE_H #include -#include #include -#include "mixer.h" +#include "Mixer.h" #include "tab_widget.h" class AudioPort; - -class AudioDevice +/*! \brief The AudioBackend class is the base class for all kinds of AudioBackends. + * + * All classes derived from AudioBackend receive audio data so they can output + * it. + */ +class AudioBackend { public: - AudioDevice( const ch_cnt_t _channels, mixer * _mixer ); - virtual ~AudioDevice(); + /*! \brief Constructs an AudioBackend object for the given AudioOutputContext. */ + AudioBackend( const ch_cnt_t _channels, AudioOutputContext * context ); + virtual ~AudioBackend(); - inline void lock() - { - m_devMutex.lock(); - } - - inline void unlock() - { - m_devMutex.unlock(); - } - - - // if audio-driver supports ports, classes inherting AudioPort - // (e.g. channel-tracks) can register themselves for making - // audio-driver able to collect their individual output and provide - // them at a specific port - currently only supported by JACK + /*! If the audio backend supports ports, classes creating an AudioPort + * (e.g. InstrumentTrack) can register themselves for making + * audio backend able to collect their individual output and provide + * them at a specific port - currently only supported by JACK + */ virtual void registerPort( AudioPort * _port ); virtual void unregisterPort( AudioPort * _port ); virtual void renamePort( AudioPort * _port ); @@ -77,11 +71,14 @@ public: return m_channels; } - void processNextBuffer(); + /*! \brief Fetches one buffer and writes it to output device. + * + * \return Number of frames processed + */ + int processNextBuffer(); virtual void startProcessing() { - m_inProcess = true; } virtual void stopProcessing(); @@ -115,41 +112,47 @@ public: } ; + /*! \brief Returns const pointer to AudioOutputContext this AudioBackend acts for. */ + const AudioOutputContext * outputContext() const + { + return m_context; + } + + /*! \brief Returns const pointer to Mixer this AudioBackend acts for. */ + const Mixer * mixer() const; + protected: - // subclasses can re-implement this for being used in conjunction with - // processNextBuffer() + /*! \brief Writes given buffer to actual device. + * + * Subclasses can reimplement this for being used in conjunction with + * processNextBuffer() + */ virtual void writeBuffer( const sampleFrameA * /* _buf*/, const fpp_t /*_frames*/, const float /*_master_gain*/ ) { } - // called by according driver for fetching new sound-data - fpp_t getNextBuffer( sampleFrameA * _ab ); + /*! \brief Called by according backend for fetching new audio data. */ + int getNextBuffer( sampleFrameA * _ab ); - // clear given signed-int-16-buffer + /*! \brief Clears given signed-int-16-buffer. */ void clearS16Buffer( intSampleFrameA * _outbuf, const fpp_t _frames ); - // resample given buffer from samplerate _src_sr to samplerate _dst_sr - void resample( const sampleFrameA * _src, - const fpp_t _frames, - sampleFrameA * _dst, - const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ); - inline void setSampleRate( const sample_rate_t _new_sr ) { m_sampleRate = _new_sr; } - mixer * getMixer() - { - return m_mixer; - } - bool hqAudio() const; + AudioOutputContext * outputContext() + { + return m_context; + } + + Mixer * mixer(); protected: @@ -157,15 +160,9 @@ protected: private: + AudioOutputContext * m_context; sample_rate_t m_sampleRate; ch_cnt_t m_channels; - mixer * m_mixer; - bool m_inProcess; - - QMutex m_devMutex; - - SRC_DATA m_srcData; - SRC_STATE * m_srcState; sampleFrameA * m_buffer; diff --git a/include/AudioDummy.h b/include/AudioDummy.h index 2a38bd622..324b08c85 100644 --- a/include/AudioDummy.h +++ b/include/AudioDummy.h @@ -25,16 +25,16 @@ #ifndef _AUDIO_DUMMY_H #define _AUDIO_DUMMY_H -#include "AudioDevice.h" +#include "AudioBackend.h" #include "Cpu.h" #include "MicroTimer.h" -class AudioDummy : public AudioDevice, public QThread +class AudioDummy : public AudioBackend, public QThread { public: - AudioDummy( bool & _success_ful, mixer * _mixer ) : - AudioDevice( DEFAULT_CHANNELS, _mixer ) + AudioDummy( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( DEFAULT_CHANNELS, context ) { _success_ful = true; } @@ -50,11 +50,11 @@ public: } - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioDummy::name(), _parent ) + AudioBackend::setupWidget( AudioDummy::name(), _parent ) { } @@ -93,27 +93,25 @@ private: virtual void run() { MicroTimer timer; + sampleFrameA * buf = CPU::allocFrames( mixer()->framesPerPeriod() ); while( true ) { timer.reset(); - surroundSampleFrame * b = - getMixer()->nextBuffer(); - if( !b ) + int frames = getNextBuffer( buf ); + if( frames == 0 ) { break; } - CPU::freeFrames( b ); const Sint32 microseconds = static_cast( - getMixer()->framesPerPeriod() * - 1000000.0f / - getMixer()->processingSampleRate() - - timer.elapsed() ); + mixer()->framesPerPeriod() * 1000000.0f / + mixer()->processingSampleRate() - timer.elapsed() ); if( microseconds > 0 ) { usleep( microseconds ); } } + CPU::freeFrames( buf ); } } ; diff --git a/include/AudioFileDevice.h b/include/AudioFileDevice.h index cbc30b28f..b3ea33968 100644 --- a/include/AudioFileDevice.h +++ b/include/AudioFileDevice.h @@ -28,10 +28,10 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" -class AudioFileDevice : public AudioDevice +class AudioFileDevice : public AudioBackend { public: AudioFileDevice( const sample_rate_t _sample_rate, @@ -41,7 +41,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileDevice(); QString outputFile() const @@ -108,7 +108,7 @@ typedef AudioFileDevice * ( * AudioFileDeviceInstantiaton ) const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * mixer ); #endif diff --git a/include/AudioFileFlac.h b/include/AudioFileFlac.h index dd8ec6643..aaa3b2eeb 100644 --- a/include/AudioFileFlac.h +++ b/include/AudioFileFlac.h @@ -48,7 +48,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileFlac(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -60,13 +60,12 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileFlac( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - _mixer ); + _max_bitrate, _depth, context ); } diff --git a/include/AudioFileMp3.h b/include/AudioFileMp3.h index 7379fa4fa..6b5b6d8ea 100644 --- a/include/AudioFileMp3.h +++ b/include/AudioFileMp3.h @@ -44,7 +44,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileMp3(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -56,13 +56,12 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileMp3( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - _mixer ); + _max_bitrate, _depth, context ); } diff --git a/include/AudioFileOgg.h b/include/AudioFileOgg.h index 8ea9b9d51..363fbf8ab 100644 --- a/include/AudioFileOgg.h +++ b/include/AudioFileOgg.h @@ -47,7 +47,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileOgg(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -59,12 +59,11 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileOgg( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, - _depth, _mixer ); + _min_bitrate, _max_bitrate, _depth, context ); } diff --git a/include/AudioFileWave.h b/include/AudioFileWave.h index 6446e35f6..eabe56d4a 100644 --- a/include/AudioFileWave.h +++ b/include/AudioFileWave.h @@ -1,5 +1,5 @@ /* - * AudioFileWave.h - AudioDevice which encodes wave-stream and writes it + * AudioFileWave.h - AudioBackend which encodes wave-stream and writes it * into a WAVE-file. This is used for song-export. * * Copyright (c) 2004-2009 Tobias Doerffel @@ -44,7 +44,7 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioFileWave(); static AudioFileDevice * getInst( const sample_rate_t _sample_rate, @@ -56,13 +56,12 @@ public: const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) + AudioOutputContext * context ) { return new AudioFileWave( _sample_rate, _channels, _success_ful, _file, _use_vbr, _nom_bitrate, _min_bitrate, - _max_bitrate, _depth, - _mixer ); + _max_bitrate, _depth, context ); } diff --git a/include/AudioJack.h b/include/AudioJack.h index e9c018a95..86217d1ae 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -31,22 +31,22 @@ #include #endif -#include -#include #include +#include +#include -#include "AudioDevice.h" +#include "AudioBackend.h" class QLineEdit; class lcdSpinBox; -class AudioJack : public QObject, public AudioDevice +class AudioJack : public QObject, public AudioBackend { Q_OBJECT public: - AudioJack( bool & _success_ful, mixer * _mixer ); + AudioJack( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioJack(); inline static QString name() @@ -56,7 +56,7 @@ public: } - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioOss.h b/include/AudioOss.h index 293694fe4..997b60657 100644 --- a/include/AudioOss.h +++ b/include/AudioOss.h @@ -29,17 +29,17 @@ #ifdef LMMS_HAVE_OSS -#include "AudioDevice.h" +#include "AudioBackend.h" class lcdSpinBox; class QLineEdit; -class AudioOss : public AudioDevice, public QThread +class AudioOss : public AudioBackend, public QThread { public: - AudioOss( bool & _success_ful, mixer * _mixer ); + AudioOss( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioOss(); inline static QString name() @@ -50,7 +50,7 @@ public: static QString probeDevice(); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioOutputContext.h b/include/AudioOutputContext.h new file mode 100644 index 000000000..8bd57ac38 --- /dev/null +++ b/include/AudioOutputContext.h @@ -0,0 +1,377 @@ +/* + * AudioOutputContext.h - centralize all audio output related functionality + * + * Copyright (c) 2009 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 _AUDIO_OUTPUT_CONTEXT_H +#define _AUDIO_OUTPUT_CONTEXT_H + +#include +#include + +#include "Mixer.h" + +class AudioBackend; + +/*! \brief The AudioOutputContext class centralizes all functionality + * and data related to output of audio data. + * + * The process of audio output is rather complicated due to different kinds of + * AudioBackend implementations, FIFO buffering and dedicated quality settings. + * The AudioOutputContext class handles all this so the Mixer class can just + * deal with actual audio rendering and processing. + */ +class AudioOutputContext +{ +public: + /*! \brief The QualitySettings class holds quality related settings. + * + * There's nothing special about it. It's just a data aggregration class. + */ + class QualitySettings + { + public: + /*! Lists all quality presets. */ + enum Preset + { + Preset_Draft, /*!< Draft quality - used for editing project */ + Preset_HighQuality, /*!< High quality - standard setting for project export */ + Preset_FinalMix, /*!< Final mix quality - very slow, best quality */ + NumPresets + } ; + + /*! Lists all supported interpolation types. */ + enum Interpolation + { + Interpolation_Linear, /*!< Linear interpolation - fast */ + Interpolation_SincFastest, /*!< Fastest Sinc interpolation - good quality */ + Interpolation_SincMedium, /*!< Medium Sinc interpolation - better quality */ + Interpolation_SincBest /*!< High quality interpolation */ + } ; + + /*! Lists all supported oversampling ratios. */ + enum Oversampling + { + Oversampling_None, /*!< No oversampling - fast */ + Oversampling_2x, /*!< 2x oversampling - good quality */ + Oversampling_4x, /*!< 4x oversampling - better quality */ + Oversampling_8x /*!< 8x oversampling - best quality but might break some filters */ + } ; + + /*! \brief Constructs a QualitySettings object based on a given preset. */ + QualitySettings( Preset m ) + { + switch( m ) + { + case Preset_Draft: + m_interpolation = Interpolation_Linear; + m_oversampling = Oversampling_None; + m_sampleExactControllers = false; + m_aliasFreeOscillators = false; + break; + case Preset_HighQuality: + m_interpolation = Interpolation_SincFastest; + m_oversampling = Oversampling_2x; + m_sampleExactControllers = true; + m_aliasFreeOscillators = false; + break; + case Preset_FinalMix: + m_interpolation = Interpolation_SincBest; + m_oversampling = Oversampling_8x; + m_sampleExactControllers = true; + m_aliasFreeOscillators = true; + break; + default: + break; + } + } + + /*! \brief Constructs a QualitySettings object based on specific quality settings. */ + QualitySettings( Interpolation _i, Oversampling _o, bool _sec, + bool _afo ) : + m_interpolation( _i ), + m_oversampling( _o ), + m_sampleExactControllers( _sec ), + m_aliasFreeOscillators( _afo ) + { + } + + /*! \brief Returns multiplier for sample rate based on oversampling settings. */ + int sampleRateMultiplier() const + { + switch( oversampling() ) + { + case Oversampling_None: return 1; + case Oversampling_2x: return 2; + case Oversampling_4x: return 4; + case Oversampling_8x: return 8; + } + return 1; + } + + /*! \brief Maps interpolation setting to libsamplerate constants. */ + int libsrcInterpolation() const + { + switch( interpolation() ) + { + case Interpolation_Linear: + return SRC_ZERO_ORDER_HOLD; + case Interpolation_SincFastest: + return SRC_SINC_FASTEST; + case Interpolation_SincMedium: + return SRC_SINC_MEDIUM_QUALITY; + case Interpolation_SincBest: + return SRC_SINC_BEST_QUALITY; + } + return SRC_LINEAR; + } + + /*! \brief Returns current interpolation setting. */ + Interpolation interpolation() const + { + return m_interpolation; + } + + /*! \brief Sets a new interpolation method. */ + void setInterpolation( Interpolation interpolation ) + { + m_interpolation = interpolation; + } + + /*! \brief Returns current oversampling setting. */ + Oversampling oversampling() const + { + return m_oversampling; + } + + /*! \brief Sets a new oversampling factor. */ + void setOversampling( Oversampling oversampling ) + { + m_oversampling = oversampling; + } + + /*! \brief Returns whether to use sample exact controllers. */ + bool sampleExactControllers() const + { + return m_sampleExactControllers; + } + + /*! \brief Returns whether to use alias free oscillators. */ + bool aliasFreeOscillators() const + { + return m_aliasFreeOscillators; + } + + private: + Interpolation m_interpolation; + Oversampling m_oversampling; + bool m_sampleExactControllers; + bool m_aliasFreeOscillators; + + } ; + + /*! \brief The BufferFifo class provides an internal FIFO for rendered buffers. + * + * When working with buffer sizes greater than the default buffer size, one + * big output buffer is still splitted into smaller chunks. This is + * especially neccessary for automation which takes place once a buffer + * period. Transitions would be anything else but smooth when adjusting a + * control just 20 times per second. BufferFifo handles the queueing of + * rendered buffers. */ + class BufferFifo + { + public: + /*! Each buffer in the FIFO can have a special state. This is used + * by FifoWriter to inject NULL buffers to indicate, the FIFO has + * been emptied after FifoWriter was told to finish. */ + enum BufferStates + { + Running, /*!< Regular buffer */ + NullBuffer /*!< Even if the buffer returned by currentReadBuffer() + * is not NULL, the FIFO input was NULL. FIFO reader can + * use this information for own purposes. */ + } ; + typedef BufferStates BufferState; + + /*! \brief Constructs a new BufferFifo object. + * + * \param size The number of buffers in the FIFO + * \param bufferSize The size of each buffer in the FIFO + */ + BufferFifo( int size, int bufferSize ); + ~BufferFifo(); + + /*! \brief Pushes a new buffer into the FIFO. + * + * You can also push NULL which will set the according buffer state + * to HasNullBuffer. */ + void write( sampleFrameA * buffer ); + + /*! \brief Prepares for reading next buffer (might block until one is available). */ + void startRead(); + + /*! \brief Returns current front buffer for reading. */ + sampleFrameA * currentReadBuffer() const + { + return m_buffers[m_readerIndex]; + } + + /*! \brief Returns state of current front buffer. */ + BufferState currentReadBufferState() const + { + return m_bufferStates[m_readerIndex]; + } + + /*! \brief Finish the current buffer read operation. + * + * The buffer returned by currentReadBuffer() is not guaranteed to + * be valid anymore after calling this function. */ + void finishRead(); + + /*! \brief Returns whether FIFO is empty. */ + bool isEmpty() const + { + return m_readerSem.available() == false; + } + + + private: + QSemaphore m_readerSem; + QSemaphore m_writerSem; + int m_readerIndex; + int m_writerIndex; + int m_size; + int m_bufferSize; + sampleFrameA * * m_buffers; + BufferState * m_bufferStates; + + } ; + + /*! \brief The FifoWriter class provides an internal thread for feeding + * the FIFO read by the active AudioBackend */ + class FifoWriter : public QThread + { + public: + FifoWriter( AudioOutputContext * context ); + + void finish(); + + + private: + AudioOutputContext * m_context; + volatile bool m_writing; + + virtual void run(); + + } ; + + /*! \brief Constructs an AudioOutputContext object for given AudioBackend. + * + * \param mixer The Mixer instance to fetch audio data from + * \param audioBackend The AudioBackend to write audio data to + * \param qualitySettings A QualitySettings object describing desired quality + */ + AudioOutputContext( Mixer * mixer, + AudioBackend * audioBackend, + const QualitySettings & qualitySettings ); + ~AudioOutputContext(); + + /*! \brief Sets an AudioBackend for this context. */ + void setAudioBackend( AudioBackend * backend ) + { + m_audioBackend = backend; + } + + /*! \brief Returns AudioBackend used by this context. */ + AudioBackend * audioBackend() + { + return m_audioBackend; + } + + /*! \brief Returns const AudioBackend used by this context. */ + const AudioBackend * audioBackend() const + { + return m_audioBackend; + } + + /*! \brief Returns Mixer used by this context. */ + Mixer * mixer() + { + return m_mixer; + } + + /*! \brief Returns const Mixer used by this context. */ + const Mixer * mixer() const + { + return m_mixer; + } + + /*! \brief Returns BufferFifo object used by this context. */ + BufferFifo * fifo() + { + return m_fifo; + } + + /*! \brief Returns current quality settings. */ + const QualitySettings & qualitySettings() const + { + return m_qualitySettings; + } + + /*! \brief Starts audio processing in this context. */ + void startProcessing(); + + /*! \brief Stops audio processing in this context. */ + void stopProcessing(); + + /*! \brief Returns whether audio processing in this context is running. */ + bool isProcessing() const; + + /*! \brief Copies current output buffer to destination buffer and optionally + * does resampling. + * + * If the Mixer has a running FifoWriter, it will make the FifoWriter start + * rendering the next buffer so it can be read from the Fifo next period + * without any delay. If the desired sample rate does not match current + * processing sample rate, resampling will be done. + * \param destBuffer The (aligned) destination buffer + * \param destSampleRate The desired output sample rate */ + int getCurrentOutputBuffer( sampleFrameA * destBuffer, + sample_rate_t destSampleRate ); + + +private: + Mixer * m_mixer; + QualitySettings m_qualitySettings; + AudioBackend * m_audioBackend; + BufferFifo * m_fifo; + FifoWriter * m_fifoWriter; + + // resample data + SRC_DATA m_srcData; + SRC_STATE * m_srcState; + + +} ; + + +#endif diff --git a/include/AudioPort.h b/include/AudioPort.h index 7157f729b..c4c47ec6d 100644 --- a/include/AudioPort.h +++ b/include/AudioPort.h @@ -27,13 +27,12 @@ #include #include -#include -#include "mixer.h" +#include "Mixer.h" class EffectChain; -class AudioPort +class AudioPort : public ThreadableJob { public: AudioPort( const QString & _name, bool _has_effect_chain = true ); @@ -109,6 +108,13 @@ public: bool processEffects(); + // ThreadableJob stuff + virtual void doProcessing( sampleFrame * ); + virtual bool requiresProcessing() const + { + return true; + } + enum bufferUsages { @@ -134,7 +140,7 @@ private: EffectChain * m_effects; - friend class mixer; + friend class Mixer; friend class MixerWorkerThread; } ; diff --git a/include/AudioPortAudio.h b/include/AudioPortAudio.h index a3f968729..3b2083168 100644 --- a/include/AudioPortAudio.h +++ b/include/AudioPortAudio.h @@ -47,7 +47,7 @@ public: #include -#include "AudioDevice.h" +#include "AudioBackend.h" #if defined paNeverDropInput || defined paNonInterleaved # define PORTAUDIO_V19 @@ -60,7 +60,7 @@ class comboBox; class lcdSpinBox; -class AudioPortAudio : public AudioDevice +class AudioPortAudio : public AudioBackend { public: AudioPortAudio( bool & _success_ful, mixer * _mixer ); @@ -77,7 +77,7 @@ public: unsigned long _framesPerBuffer ); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index 33a11da1f..0a3a9513f 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -31,17 +31,17 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" class lcdSpinBox; class QLineEdit; -class AudioPulseAudio : public AudioDevice, public QThread +class AudioPulseAudio : public AudioBackend, public QThread { public: - AudioPulseAudio( bool & _success_ful, mixer * _mixer ); + AudioPulseAudio( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioPulseAudio(); inline static QString name() @@ -52,7 +52,7 @@ public: static QString probeDevice(); - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h index e1e732f80..94a30e180 100644 --- a/include/AudioSampleRecorder.h +++ b/include/AudioSampleRecorder.h @@ -29,16 +29,16 @@ #include #include -#include "AudioDevice.h" +#include "AudioBackend.h" class sampleBuffer; -class AudioSampleRecorder : public AudioDevice +class AudioSampleRecorder : public AudioBackend { public: AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, - mixer * _mixer ); + AudioOutputContext * context ); virtual ~AudioSampleRecorder(); f_cnt_t framesRecorded() const; @@ -46,11 +46,11 @@ public: private: - virtual void writeBuffer( const surroundSampleFrame * _ab, + virtual void writeBuffer( const sampleFrameA * _ab, const fpp_t _frames, const float _master_gain ); - typedef QList > BufferList; + typedef QList > BufferList; BufferList m_buffers; } ; diff --git a/include/AudioSdl.h b/include/AudioSdl.h index 19260829d..7826fcc69 100644 --- a/include/AudioSdl.h +++ b/include/AudioSdl.h @@ -29,18 +29,20 @@ #ifdef LMMS_HAVE_SDL +#include + #include #include -#include "AudioDevice.h" +#include "AudioBackend.h" class QLineEdit; -class AudioSdl : public AudioDevice +class AudioSdl : public AudioBackend { public: - AudioSdl( bool & _success_ful, mixer * _mixer ); + AudioSdl( bool & _success_ful, AudioOutputContext * context ); virtual ~AudioSdl(); inline static QString name() @@ -50,7 +52,7 @@ public: } - class setupWidget : public AudioDevice::setupWidget + class setupWidget : public AudioBackend::setupWidget { public: setupWidget( QWidget * _parent ); diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index e3c4a9859..0f9308975 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -225,6 +225,9 @@ protected: float fittedValue( float _value ) const; + float m_minValue; + float m_maxValue; + float m_value; private: void linkModel( AutomatableModel * _model ); @@ -232,10 +235,7 @@ private: DataType m_dataType; - float m_value; float m_initValue; - float m_minValue; - float m_maxValue; float m_step; float m_range; @@ -280,8 +280,7 @@ signals: inline type maxValue() const \ { \ return AutomatableModel::maxValue(); \ - } \ - + } // some typed AutomatableModel-definitions diff --git a/include/Controller.h b/include/Controller.h index 5ca110a53..129c44c18 100644 --- a/include/Controller.h +++ b/include/Controller.h @@ -23,13 +23,13 @@ * */ - #ifndef _CONTROLLER_H #define _CONTROLLER_H #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "Model.h" +//#include "AudioOutputContext.h" #include "JournallingObject.h" class ControllerDialog; @@ -64,9 +64,9 @@ public: inline bool isSampleExact() const { - return m_sampleExact || - engine::getMixer()->currentQualitySettings(). - sampleExactControllers; + return m_sampleExact /*|| + engine::mixer()->audioOutputContext()-> + qualitySettings().sampleExactControllers()*/; } void setSampleExact( bool _exact ) @@ -76,7 +76,7 @@ public: inline ControllerTypes type() const { - return( m_type ); + return m_type; } // return whether this controller updates models frequently - used for @@ -85,17 +85,17 @@ public: { switch( m_type ) { - case LfoController: return( true ); - case PeakController: return( true ); + case LfoController: return true; + case PeakController: return true; default: break; } - return( false ); + return false; } virtual const QString & name() const { - return( m_name ); + return m_name; } diff --git a/include/Cpu.h b/include/Cpu.h index f4e956690..b4ad59ba0 100644 --- a/include/Cpu.h +++ b/include/Cpu.h @@ -56,6 +56,9 @@ typedef void (*BufApplyGainFunc)( sampleFrameA * RP _dst, typedef void (*BufMixFunc)( sampleFrameA * RP _dst, const sampleFrameA * RP _src, int _frames ); +typedef void (*BufMixCoeffFunc)( sampleFrameA * RP _dst, + const sampleFrameA * RP _src, + float _coeff, int _frames ); typedef void (*BufMixLRCoeffFunc)( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, @@ -81,6 +84,7 @@ extern MemCpyFunc memCpy; extern MemClearFunc memClear; extern BufApplyGainFunc bufApplyGain; extern BufMixFunc bufMix; +extern BufMixCoeffFunc bufMixCoeff; extern BufMixLRCoeffFunc bufMixLRCoeff; extern UnalignedBufMixLRCoeffFunc unalignedBufMixLRCoeff; extern BufWetDryMixFunc bufWetDryMix; diff --git a/include/Effect.h b/include/Effect.h index 4fff6b178..610d4f998 100644 --- a/include/Effect.h +++ b/include/Effect.h @@ -28,7 +28,7 @@ #include "Plugin.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "AutomatableModel.h" #include "TempoSyncKnobModel.h" diff --git a/include/EffectChain.h b/include/EffectChain.h index bf949f2bd..b09863896 100644 --- a/include/EffectChain.h +++ b/include/EffectChain.h @@ -28,7 +28,7 @@ #include "Model.h" #include "SerializingObject.h" -#include "mixer.h" +#include "Mixer.h" #include "AutomatableModel.h" class Effect; diff --git a/include/FxLine.h b/include/FxLine.h new file mode 100644 index 000000000..89e0321f3 --- /dev/null +++ b/include/FxLine.h @@ -0,0 +1,41 @@ +#ifndef FXLINE_H +#define FXLINE_H + +#include +#include + +#include "knob.h" +#include "lcd_spinbox.h" +#include "SendButtonIndicator.h" + +class FxMixerView; +class SendButtonIndicator; + +class FxLine : public QWidget +{ + Q_OBJECT +public: + FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex); + ~FxLine(); + + virtual void paintEvent( QPaintEvent * ); + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseDoubleClickEvent( QMouseEvent * ); + + inline int channelIndex() { return m_channelIndex; } + void setChannelIndex(int index); + + knob * m_sendKnob; + SendButtonIndicator * m_sendBtn; + +private: + FxMixerView * m_mv; + lcdSpinBox * m_lcd; + + + int m_channelIndex; + +} ; + + +#endif // FXLINE_H diff --git a/include/FxMixer.h b/include/FxMixer.h index 2a6bd84e0..6e8e29bd5 100644 --- a/include/FxMixer.h +++ b/include/FxMixer.h @@ -26,31 +26,46 @@ #define _FX_MIXER_H #include "Model.h" -#include "mixer.h" +#include "Mixer.h" #include "EffectChain.h" #include "JournallingObject.h" -const int NumFxChannels = 64; -struct FxChannel +class FxChannel : public ThreadableJob { - FxChannel( Model * _parent ); - ~FxChannel(); + public: + FxChannel( Model * _parent ); + ~FxChannel(); - EffectChain m_fxChain; - bool m_used; - bool m_stillRunning; - float m_peakLeft; - float m_peakRight; - sampleFrame * m_buffer; - BoolModel m_muteModel; - FloatModel m_volumeModel; - QString m_name; - QMutex m_lock; + EffectChain m_fxChain; -} ; + // set to true if any effect in the channel is enabled and running + bool m_stillRunning; + + float m_peakLeft; + float m_peakRight; + sampleFrame * m_buffer; + BoolModel m_muteModel; + FloatModel m_volumeModel; + QString m_name; + QMutex m_lock; + int m_channelIndex; // what channel index are we + bool m_queued; // are we queued up for rendering yet? + + // pointers to other channels that this one sends to + QVector m_sends; + QVector m_sendAmount; + + // pointers to other channels that send to this one + QVector m_receives; + + virtual bool requiresProcessing() const { return true; } + + private: + virtual void doProcessing( sampleFrame * _working_buffer ); +}; @@ -61,14 +76,10 @@ public: virtual ~FxMixer(); void mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ); - void processChannel( fx_ch_t _ch, sampleFrame * _buf = NULL ); void prepareMasterMix(); void masterMix( sampleFrame * _buf ); - - void clear(); - virtual void saveSettings( QDomDocument & _doc, QDomElement & _parent ); virtual void loadSettings( const QDomElement & _this ); @@ -79,17 +90,55 @@ public: FxChannel * effectChannel( int _ch ) { - if( _ch >= 0 && _ch <= NumFxChannels ) - { - return m_fxChannels[_ch]; - } - return NULL; + return m_fxChannels[_ch]; } + // make the output of channel fromChannel go to the input of channel toChannel + // it is safe to call even if the send already exists + void createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel, + float amount = 1.0f); + + // delete the connection made by createChannelSend + void deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel); + + // determine if adding a send from sendFrom to + // sendTo would result in an infinite mixer loop. + bool isInfiniteLoop(fx_ch_t fromChannel, fx_ch_t toChannel); + + // return the FloatModel of fromChannel sending its output to the input of + // toChannel. NULL if there is no send. + FloatModel * channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel); + + // add a new channel to the Fx Mixer. + // returns the index of the channel that was just added + int createChannel(); + + // delete a channel from the FX mixer. + void deleteChannel(int index); + + // delete all the mixer channels except master and remove all effects + void clear(); + + // re-arrange channels + void moveChannelLeft(int index); + void moveChannelRight(int index); + + // reset a channel's name, fx, sends, etc + void clearChannel(fx_ch_t channelIndex); + + inline fx_ch_t numChannels() const + { + return m_fxChannels.size(); + } private: - FxChannel * m_fxChannels[NumFxChannels+1]; // +1 = master + // the fx channels in the mixer. index 0 is always master. + QVector m_fxChannels; + // make sure we have at least num channels + void allocateChannelsTo(int num); + + void addChannelLeaf( int _ch, sampleFrame * _buf ); friend class mixerWorkerThread; friend class FxMixerView; diff --git a/include/FxMixerView.h b/include/FxMixerView.h index dab34caaf..1dc08a6e5 100644 --- a/include/FxMixerView.h +++ b/include/FxMixerView.h @@ -26,59 +26,91 @@ #define _FX_MIXER_VIEW_H #include +#include +#include +#include "FxLine.h" #include "FxMixer.h" #include "ModelView.h" +#include "engine.h" +#include "fader.h" +#include "pixmap_button.h" +#include "tooltip.h" +#include "embed.h" +#include "EffectRackView.h" -class QStackedLayout; class QButtonGroup; -class fader; class FxLine; -class EffectRackView; -class pixmapButton; - class FxMixerView : public QWidget, public ModelView, public SerializingObjectHook { Q_OBJECT public: + struct FxChannelView + { + FxChannelView(QWidget * _parent, FxMixerView * _mv, int _chIndex ); + + FxLine * m_fxLine; + pixmapButton * m_muteBtn; + fader * m_fader; + }; + + FxMixerView(); virtual ~FxMixerView(); + virtual void keyPressEvent(QKeyEvent * e); + virtual void saveSettings( QDomDocument & _doc, QDomElement & _this ); virtual void loadSettings( const QDomElement & _this ); - FxLine * currentFxLine() + inline FxLine * currentFxLine() { return m_currentFxLine; } + + inline FxChannelView * channelView(int index) + { + return m_fxChannelViews[index]; + } + void setCurrentFxLine( FxLine * _line ); void setCurrentFxLine( int _line ); void clear(); + // display the send button and knob correctly + void updateFxLine(int index); + + // notify the view that an fx channel was deleted + void deleteChannel(int index); + + // move the channel to the left or right + void moveChannelLeft(int index); + void moveChannelRight(int index); + + // make sure the display syncs up with the fx mixer. + // useful for loading projects + void refreshDisplay(); + private slots: void updateFaders(); - + void addNewChannel(); private: - struct FxChannelView - { - FxLine * m_fxLine; - EffectRackView * m_rackView; - pixmapButton * m_muteBtn; - fader * m_fader; - } ; - FxChannelView m_fxChannelViews[NumFxChannels+1]; + QVector m_fxChannelViews; - QStackedLayout * m_fxRacksLayout; - QStackedLayout * m_fxLineBanks; - QButtonGroup * m_bankButtons; FxLine * m_currentFxLine; + QScrollArea * channelArea; + QHBoxLayout * chLayout; + QWidget * m_channelAreaWidget; + EffectRackView * m_rackView; + + void updateMaxChannelSelector(); } ; #endif diff --git a/include/Instrument.h b/include/Instrument.h index 09a60df38..8621dffa5 100644 --- a/include/Instrument.h +++ b/include/Instrument.h @@ -29,7 +29,7 @@ #include #include "Plugin.h" -#include "mixer.h" +#include "Mixer.h" // forward-declarations diff --git a/include/InstrumentSoundShaping.h b/include/InstrumentSoundShaping.h index 398df0688..eae4b9b69 100644 --- a/include/InstrumentSoundShaping.h +++ b/include/InstrumentSoundShaping.h @@ -25,7 +25,7 @@ #ifndef _INSTRUMENT_SOUND_SHAPING_H #define _INSTRUMENT_SOUND_SHAPING_H -#include "mixer.h" +#include "Mixer.h" #include "ComboBoxModel.h" diff --git a/include/MainWindow.h b/include/MainWindow.h index 80357e8d5..f021076b7 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -26,6 +26,7 @@ #define _MAIN_WINDOW_H #include +#include #include #include #include @@ -208,6 +209,7 @@ private: QList m_tools; QBasicTimer m_updateTimer; + QTimer m_autoSaveTimer; ResourceBrowser * m_resourceBrowser; @@ -244,6 +246,8 @@ private slots: void playAndRecord(); void stop(); + void autoSave(); + signals: void periodicUpdate(); diff --git a/include/mixer.h b/include/Mixer.h similarity index 58% rename from include/mixer.h rename to include/Mixer.h index 3562db3f1..b0eb11e0b 100644 --- a/include/mixer.h +++ b/include/Mixer.h @@ -1,5 +1,5 @@ /* - * mixer.h - audio-device-independent mixer for LMMS + * Mixer.h - Mixer for audio processing and rendering * * Copyright (c) 2004-2009 Tobias Doerffel * @@ -39,19 +39,18 @@ #include -#include #include #include #include "lmms_basics.h" #include "note.h" -#include "fifo_buffer.h" -class AudioDevice; -class MidiClient; +class AudioBackend; +class AudioOutputContext; class AudioPort; +class MidiClient; const fpp_t DEFAULT_BUFFER_SIZE = 256; @@ -62,110 +61,17 @@ const Keys BaseKey = Key_A; const Octaves BaseOctave = DefaultOctave; +class MixerWorkerThread; + +#include "ThreadableJob.h" #include "play_handle.h" -class MixerWorkerThread; - - -class EXPORT mixer : public QObject +/*! \brief The Mixer class is responsible for processing and rendering audio chunks. */ +class EXPORT Mixer : public QObject { Q_OBJECT public: - struct qualitySettings - { - enum Mode - { - Mode_Draft, - Mode_HighQuality, - Mode_FinalMix - } ; - - enum Interpolation - { - Interpolation_Linear, - Interpolation_SincFastest, - Interpolation_SincMedium, - Interpolation_SincBest - } ; - - enum Oversampling - { - Oversampling_None, - Oversampling_2x, - Oversampling_4x, - Oversampling_8x - } ; - - Interpolation interpolation; - Oversampling oversampling; - bool sampleExactControllers; - bool aliasFreeOscillators; - - qualitySettings( Mode _m ) - { - switch( _m ) - { - case Mode_Draft: - interpolation = Interpolation_Linear; - oversampling = Oversampling_None; - sampleExactControllers = false; - aliasFreeOscillators = false; - break; - case Mode_HighQuality: - interpolation = - Interpolation_SincFastest; - oversampling = Oversampling_2x; - sampleExactControllers = true; - aliasFreeOscillators = false; - break; - case Mode_FinalMix: - interpolation = Interpolation_SincBest; - oversampling = Oversampling_8x; - sampleExactControllers = true; - aliasFreeOscillators = true; - break; - } - } - - qualitySettings( Interpolation _i, Oversampling _o, bool _sec, - bool _afo ) : - interpolation( _i ), - oversampling( _o ), - sampleExactControllers( _sec ), - aliasFreeOscillators( _afo ) - { - } - - int sampleRateMultiplier() const - { - switch( oversampling ) - { - case Oversampling_None: return 1; - case Oversampling_2x: return 2; - case Oversampling_4x: return 4; - case Oversampling_8x: return 8; - } - return 1; - } - - int libsrcInterpolation() const - { - switch( interpolation ) - { - case Interpolation_Linear: - return SRC_ZERO_ORDER_HOLD; - case Interpolation_SincFastest: - return SRC_SINC_FASTEST; - case Interpolation_SincMedium: - return SRC_SINC_MEDIUM_QUALITY; - case Interpolation_SincBest: - return SRC_SINC_BEST_QUALITY; - } - return SRC_LINEAR; - } - } ; - void initDevices(); void clear(); @@ -176,16 +82,20 @@ public: return m_audioDevName; } - void setAudioDevice( AudioDevice * _dev ); - void setAudioDevice( AudioDevice * _dev, - const struct qualitySettings & _qs, - bool _needs_fifo ); - void restoreAudioDevice(); - inline AudioDevice * audioDev() + /*! \brief Sets a specific AudioOutputContext to be the active context. */ + void setAudioOutputContext( AudioOutputContext * context ); + const AudioOutputContext * audioOutputContext() const { - return m_audioDev; + return m_audioOutputContext; + } + AudioOutputContext * audioOutputContext() + { + return m_audioOutputContext; + } + AudioOutputContext * defaultAudioOutputContext() + { + return m_defaultAudioOutputContext; } - // audio-port-stuff inline void addAudioPort( AudioPort * _port ) @@ -246,7 +156,7 @@ public: return m_framesPerPeriod; } - inline const surroundSampleFrame * currentReadBuffer() const + inline const sampleFrameA * currentReadBuffer() const { return m_readBuf; } @@ -257,11 +167,6 @@ public: return m_cpuLoad; } - const qualitySettings & currentQualitySettings() const - { - return m_qualitySettings; - } - sample_rate_t baseSampleRate() const; sample_rate_t outputSampleRate() const; @@ -325,11 +230,6 @@ public: static void clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset = 0 ); -#ifndef LMMS_DISABLE_SURROUND - static void clearAudioBuffer( surroundSampleFrame * _ab, - const f_cnt_t _frames, - const f_cnt_t _offset = 0 ); -#endif static float peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ); static float peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ); @@ -337,13 +237,8 @@ public: bool criticalXRuns() const; - inline bool hasFifoWriter() const - { - return m_fifoWriter != NULL; - } - void pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ); - + inline const sampleFrame * inputBuffer() { return m_inputBuffer[ m_inputBufferRead ]; @@ -354,56 +249,27 @@ public: return m_inputBufferFrames[ m_inputBufferRead ]; } - inline surroundSampleFrame * nextBuffer() - { - return hasFifoWriter() ? m_fifo->read() : renderNextBuffer(); - } - - void changeQuality( const struct qualitySettings & _qs ); + /*! \brief Processes and renders next chunk of audio. */ + sampleFrameA * renderNextBuffer(); signals: - void qualitySettingsChanged(); void sampleRateChanged(); void nextAudioBuffer(); private: - typedef fifoBuffer fifo; + Mixer(); + virtual ~Mixer(); - class fifoWriter : public QThread - { - public: - fifoWriter( mixer * _mixer, fifo * _fifo ); - - void finish(); - - - private: - mixer * m_mixer; - fifo * m_fifo; - volatile bool m_writing; - - virtual void run(); - - } ; - - - mixer(); - virtual ~mixer(); - - void startProcessing( bool _needs_fifo = true ); + void startProcessing(); void stopProcessing(); - AudioDevice * tryAudioDevices(); + AudioBackend * tryAudioBackends(); MidiClient * tryMidiClients(); - surroundSampleFrame * renderNextBuffer(); - - - QVector m_audioPorts; fpp_t m_framesPerPeriod; @@ -415,21 +281,21 @@ private: f_cnt_t m_inputBufferSize[2]; int m_inputBufferRead; int m_inputBufferWrite; - - surroundSampleFrame * m_readBuf; - surroundSampleFrame * m_writeBuf; - - QVector m_bufferPool; + + sampleFrameA * m_readBuf; + sampleFrameA * m_writeBuf; + + QVector m_bufferPool; int m_readBuffer; int m_writeBuffer; int m_poolDepth; - surroundSampleFrame m_maxClip; - surroundSampleFrame m_previousSample; + sampleFrame m_maxClip; + sampleFrame m_previousSample; fpp_t m_halfStart[SURROUND_CHANNELS]; bool m_oldBuffer[SURROUND_CHANNELS]; bool m_newBuffer[SURROUND_CHANNELS]; - + int m_cpuLoad; QVector m_workers; int m_numWorkers; @@ -439,12 +305,11 @@ private: PlayHandleList m_playHandles; ConstPlayHandleList m_playHandlesToRemove; - struct qualitySettings m_qualitySettings; float m_masterGain; - AudioDevice * m_audioDev; - AudioDevice * m_oldAudioDev; + AudioOutputContext * m_audioOutputContext; + AudioOutputContext * m_defaultAudioOutputContext; QString m_audioDevName; @@ -456,14 +321,8 @@ private: QMutex m_inputFramesMutex; - fifo * m_fifo; - fifoWriter * m_fifoWriter; - - friend class engine; - friend class MixerWorkerThread; } ; - #endif diff --git a/include/MixerWorkerThread.h b/include/MixerWorkerThread.h new file mode 100644 index 000000000..736d5d992 --- /dev/null +++ b/include/MixerWorkerThread.h @@ -0,0 +1,117 @@ +/* + * MixerWorkerThread.h - declaration of class MixerWorkerThread + * + * Copyright (c) 2009 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 _MIXER_WORKER_THREAD_H +#define _MIXER_WORKER_THREAD_H + +#include +#include + +#include "Mixer.h" + + +class MixerWorkerThread : public QThread +{ +public: + // internal representation of the job queue - all functions are thread-safe + class JobQueue + { + public: + enum OperationMode + { + Static, // no jobs added while processing queue + Dynamic // jobs can be added while processing queue + } ; + + JobQueue() : + m_items(), + m_queueSize( 0 ), + m_itemsDone( 0 ), + m_opMode( Static ) + { + } + + void reset( OperationMode _opMode ); + + void addJob( ThreadableJob * _job ); + + void run( sampleFrame * _buffer ); + void wait(); + + private: +#define JOB_QUEUE_SIZE 1024 + QAtomicPointer m_items[JOB_QUEUE_SIZE]; + QAtomicInt m_queueSize; + QAtomicInt m_itemsDone; + OperationMode m_opMode; + + } ; + + + MixerWorkerThread( Mixer * _mixer ); + virtual ~MixerWorkerThread(); + + virtual void quit(); + + static void resetJobQueue( JobQueue::OperationMode _opMode = + JobQueue::Static ) + { + globalJobQueue.reset( _opMode ); + } + + static void addJob( ThreadableJob * _job ) + { + globalJobQueue.addJob( _job ); + } + + // a convenient helper function allowing to pass a container with pointers + // to ThreadableJob objects + template + static void fillJobQueue( const T & _vec, + JobQueue::OperationMode _opMode = JobQueue::Static ) + { + resetJobQueue( _opMode ); + for( typename T::ConstIterator it = _vec.begin(); it != _vec.end(); ++it ) + { + addJob( *it ); + } + } + + static void startAndWaitForJobs(); + + +private: + virtual void run(); + + static JobQueue globalJobQueue; + static QWaitCondition * queueReadyWaitCond; + static QList workerThreads; + + sampleFrame * m_workingBuf; + volatile bool m_quit; + +} ; + + +#endif diff --git a/include/ProjectRenderer.h b/include/ProjectRenderer.h index f72b55c9c..4c01d809a 100644 --- a/include/ProjectRenderer.h +++ b/include/ProjectRenderer.h @@ -26,42 +26,46 @@ #define _PROJECT_RENDERER_H #include "AudioFileDevice.h" +#include "AudioOutputContext.h" #include "lmmsconfig.h" class QTimer; +/*! \brief The ProjectRenderer class provides functionality to render current Song into a file. */ class ProjectRenderer : public QThread { Q_OBJECT public: + /*! Lists all supported output file formats. */ enum ExportFileFormats { - WaveFile, - OggFile, - Mp3File, - FlacFile, + WaveFile, /*!< Uncompressed WAV file */ + OggFile, /*!< Vorbis-encoded OGG file */ + Mp3File, /*!< MP3 file encoded via LAME */ + FlacFile, /*!< Free Lossless Audio Codec */ NumFileFormats } ; static const char * EFF_ext[]; + /*! Lists all supported sample type depths. */ enum Depths { - Depth_16Bit, - Depth_24Bit, - Depth_32Bit, + Depth_16Bit, /*!< 16 bit signed integer */ + Depth_24Bit, /*!< 24 bit floating point */ + Depth_32Bit, /*!< 32 bit floating point */ NumDepths } ; - struct OutputSettings + /*! Settings for the output file encoder. */ + struct EncoderSettings { - sample_rate_t samplerate; - bool vbr; - int bitrate; - Depths depth; + sample_rate_t samplerate; /*!< Desired output sample rate */ + bool vbr; /*!< Use variable bitrate encoding */ + int bitrate; /*!< Desired bitrate (kbps) */ + Depths depth; /*!< Depth of samples */ - OutputSettings( sample_rate_t _sr, bool _vbr, int _bitrate, - Depths _d ) : + EncoderSettings( sample_rate_t _sr, bool _vbr, int _bitrate, Depths _d ) : samplerate( _sr ), vbr( _vbr ), bitrate( _bitrate ), @@ -70,30 +74,40 @@ public: } } ; - - ProjectRenderer( const mixer::qualitySettings & _qs, - const OutputSettings & _os, - ExportFileFormats _file_format, - const QString & _out_file ); + /*! \brief Constructs a ProjectRenderer object with given settings. + * + * \param qualitySettings The desired quality settings for the AudioOutputContext + * \param encoderSettings The desired settings for the output file encoder + * \param fileFormat One of the file formats listed in the ExportFileFormats enumeration + * \param outFile The output file name + */ + ProjectRenderer( const AudioOutputContext::QualitySettings & qualitySettings, + const EncoderSettings & encoderSettings, + ExportFileFormats fileFormat, + const QString & outFile ); virtual ~ProjectRenderer(); + /*! \brief Returns whether the ProjectRenderer was initialized properly. */ bool isReady() const { return m_fileDev != NULL; } - static ExportFileFormats getFileFormatFromExtension( - const QString & _ext ); + static ExportFileFormats getFileFormatFromExtension( const QString & _ext ); - void setConsoleUpdateTimer(QTimer * t) + void setConsoleUpdateTimer( QTimer * t ) { m_consoleUpdateTimer = t; } + public slots: + /*! \brief Sets according AudioOutputContext for Mixer and starts render thread. */ void startProcessing(); + /*! \brief Aborts the processing and cleans up resources. */ void abortProcessing(); + /*! \brief Prints current render progress to the console. */ void updateConsoleProgress(); @@ -101,12 +115,16 @@ signals: void progressChanged( int ); +private slots: + /*! \brief Finalizes the render process and restores Mixer's AudioOutputContext. */ + void finishProcessing(); + + private: virtual void run(); AudioFileDevice * m_fileDev; - mixer::qualitySettings m_qualitySettings; - mixer::qualitySettings m_oldQualitySettings; + AudioOutputContext * m_context; volatile int m_progress; volatile bool m_abort; @@ -116,6 +134,7 @@ private: } ; +/*! \brief Holds information about a certain file encoder. */ struct FileEncodeDevice { ProjectRenderer::ExportFileFormats m_fileFormat; diff --git a/include/SendButtonIndicator.h b/include/SendButtonIndicator.h new file mode 100644 index 000000000..2b0819791 --- /dev/null +++ b/include/SendButtonIndicator.h @@ -0,0 +1,32 @@ +#ifndef SENDBUTTONINDICATOR_H +#define SENDBUTTONINDICATOR_H + +#include +#include +#include + +#include "FxLine.h" +#include "FxMixerView.h" + +class FxLine; +class FxMixerView; + +class SendButtonIndicator : public QLabel { + public: + SendButtonIndicator( QWidget * _parent, FxLine * _owner, + FxMixerView * _mv); + + virtual void mousePressEvent( QMouseEvent * e ); + void updateLightStatus(); + + private: + + FxLine * m_parent; + FxMixerView * m_mv; + QPixmap qpmOn; + QPixmap qpmOff; + + FloatModel * getSendModel(); +}; + +#endif // SENDBUTTONINDICATOR_H diff --git a/include/ThreadableJob.h b/include/ThreadableJob.h new file mode 100644 index 000000000..40627d792 --- /dev/null +++ b/include/ThreadableJob.h @@ -0,0 +1,84 @@ +/* + * ThreadableJob.h - declaration of class ThreadableJob + * + * Copyright (c) 2009 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 _THREADABLE_JOB_H +#define _THREADABLE_JOB_H + +#include + +#include "Mixer.h" + + +class ThreadableJob +{ +public: + + enum ProcessingState + { + Unstarted, + Queued, + InProgress, + Done + }; + + ThreadableJob() : + m_state( ThreadableJob::Unstarted ) + { + } + + inline ProcessingState state() const + { + return static_cast( (int) m_state ); + } + + inline void reset() + { + m_state = Unstarted; + } + + inline void queue() + { + m_state = Queued; + } + + void process( sampleFrame * _working_buffer ) + { + if( m_state.testAndSetOrdered( Queued, InProgress ) ) + { + doProcessing( _working_buffer ); + m_state = Done; + } + } + + virtual bool requiresProcessing() const = 0; + + +protected: + virtual void doProcessing( sampleFrame * _working_buffer ) = 0; + + QAtomicInt m_state; + +} ; + +#endif diff --git a/include/basic_filters.h b/include/basic_filters.h index ee986b0e2..2462978ee 100644 --- a/include/basic_filters.h +++ b/include/basic_filters.h @@ -36,7 +36,7 @@ #include #include "lmms_basics.h" -#include "mixer.h" +#include "Mixer.h" #include "templates.h" #include "lmms_constants.h" diff --git a/include/classic_style.h b/include/classic_style.h index ccd1b5482..629960133 100644 --- a/include/classic_style.h +++ b/include/classic_style.h @@ -59,7 +59,7 @@ public: // LMMS Stuff virtual void drawFxLine(QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active); + const QString & _name, bool _active, bool _sendToThis); virtual void drawTrackContentBackground(QPainter * _painter, const QSize & _size, const int _pixelsPerTact); diff --git a/include/cpuload_widget.h b/include/cpuload_widget.h index 7a2d27696..4653f300d 100644 --- a/include/cpuload_widget.h +++ b/include/cpuload_widget.h @@ -51,7 +51,7 @@ protected slots: private: - Uint8 m_currentLoad; + int m_currentLoad; QPixmap m_temp; QPixmap m_background; diff --git a/include/cusis_style.h b/include/cusis_style.h index 3e359cfd1..f000b520f 100644 --- a/include/cusis_style.h +++ b/include/cusis_style.h @@ -66,7 +66,7 @@ public: virtual void unpolish( QWidget * widget ); virtual void drawFxLine( QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active ); + const QString & _name, bool _active, bool _sendToThis ); virtual void drawTrackContentBackground( QPainter * _painter, const QSize & _size, const int _pixelsPerTact ); diff --git a/include/engine.h b/include/engine.h index 0f9d471c3..4a17cee6b 100644 --- a/include/engine.h +++ b/include/engine.h @@ -41,7 +41,7 @@ class FxMixer; class FxMixerView; class ProjectJournal; class MainWindow; -class mixer; +class Mixer; class pianoRoll; class projectNotes; class ResourceDB; @@ -75,7 +75,12 @@ public: } // core - static mixer * getMixer() + static Mixer * getMixer() + { + return s_mixer; + } + + static Mixer * mixer() { return s_mixer; } @@ -207,7 +212,7 @@ private: static float s_framesPerTick; // core - static mixer * s_mixer; + static Mixer * s_mixer; static FxMixer * s_fxMixer; static song * s_song; static ResourceDB * s_workingDirResourceDB; diff --git a/include/fifo_buffer.h b/include/fifo_buffer.h index 4bf908611..e69de29bb 100644 --- a/include/fifo_buffer.h +++ b/include/fifo_buffer.h @@ -1,89 +0,0 @@ -/* - * fifo_buffer.h - FIFO fixed-size buffer - * - * Copyright (c) 2007 Javier Serrano Polo - * Copyright (c) 2008 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 _FIFO_BUFFER_H -#define _FIFO_BUFFER_H - -#include - - -template -class fifoBuffer -{ -public: - fifoBuffer( int _size ) : - m_readerSem( _size ), - m_writerSem( _size ), - m_readerIndex( 0 ), - m_writerIndex( 0 ), - m_size( _size ) - { - m_buffer = new T[_size]; - m_readerSem.acquire( _size ); - } - - ~fifoBuffer() - { - delete[] m_buffer; - m_readerSem.release( m_size ); - } - - void write( T _element ) - { - m_writerSem.acquire(); - m_buffer[m_writerIndex++] = _element; - m_writerIndex %= m_size; - m_readerSem.release(); - } - - T read() - { - m_readerSem.acquire(); - T element = m_buffer[m_readerIndex++]; - m_readerIndex %= m_size; - m_writerSem.release(); - return element; - } - - bool available() - { - return m_readerSem.available(); - } - - -private: - QSemaphore m_readerSem; - QSemaphore m_writerSem; - int m_readerIndex; - int m_writerIndex; - int m_size; - T * m_buffer; - -} ; - - - - -#endif diff --git a/include/lmms_style.h b/include/lmms_style.h index eb6b0635c..3018e27c0 100644 --- a/include/lmms_style.h +++ b/include/lmms_style.h @@ -91,7 +91,7 @@ public: virtual void drawFxLine(QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active) = 0; + const QString & _name, bool _active, bool _sendToThis) = 0; virtual void drawTrackContentBackground(QPainter * _painter, const QSize & _size, const int _pixelsPerTact) = 0; diff --git a/include/note_play_handle.h b/include/note_play_handle.h index 3341058dc..40b0c1d84 100644 --- a/include/note_play_handle.h +++ b/include/note_play_handle.h @@ -28,7 +28,7 @@ #define _NOTE_PLAY_HANDLE_H #include "lmmsconfig.h" -#include "mixer.h" +#include "Mixer.h" #include "note.h" #include "engine.h" #include "track.h" diff --git a/include/pch.h b/include/pch.h index 67aa73033..aa334c07d 100644 --- a/include/pch.h +++ b/include/pch.h @@ -31,6 +31,6 @@ #include -#include "mixer.h" +#include "Mixer.h" #endif diff --git a/include/play_handle.h b/include/play_handle.h index 365da3f19..a69713377 100644 --- a/include/play_handle.h +++ b/include/play_handle.h @@ -26,14 +26,14 @@ #define _PLAY_HANDLE_H #include -#include -#include "lmms_basics.h" +#include "ThreadableJob.h" +#include "Mixer.h" class track; -class playHandle +class playHandle : public ThreadableJob { public: enum Types @@ -71,6 +71,17 @@ public: return m_type; } + // required for ThreadableJob + virtual void doProcessing( sampleFrame * _working_buffer ) + { + play( _working_buffer ); + } + + virtual bool requiresProcessing() const + { + return !done(); + } + virtual void play( sampleFrame * _working_buffer ) = 0; virtual bool done() const = 0; diff --git a/include/sample_play_handle.h b/include/sample_play_handle.h index 2c1161737..2b590e892 100644 --- a/include/sample_play_handle.h +++ b/include/sample_play_handle.h @@ -26,7 +26,7 @@ #ifndef _SAMPLE_PLAY_HANDLE_H #define _SAMPLE_PLAY_HANDLE_H -#include "mixer.h" +#include "Mixer.h" #include "sample_buffer.h" #include "AutomatableModel.h" diff --git a/include/sample_record_handle.h b/include/sample_record_handle.h index f07cfd350..5afeb08f8 100644 --- a/include/sample_record_handle.h +++ b/include/sample_record_handle.h @@ -30,7 +30,7 @@ #include #include -#include "mixer.h" +#include "Mixer.h" #include "sample_buffer.h" class bbTrack; diff --git a/include/setup_dialog.h b/include/setup_dialog.h index 6f0dcb0ab..d0cd46b6b 100644 --- a/include/setup_dialog.h +++ b/include/setup_dialog.h @@ -29,7 +29,7 @@ #include #include "lmmsconfig.h" -#include "AudioDevice.h" +#include "AudioBackend.h" #include "MidiClient.h" #include "MidiPort.h" #include "MidiPortMenu.h" @@ -171,7 +171,7 @@ private: bool m_disableChActInd; bool m_manualChPiano; - typedef QMap AswMap; + typedef QMap AswMap; typedef QMap MswMap; typedef QMap trMap; diff --git a/include/song.h b/include/song.h index 858f577f5..7406bdc22 100644 --- a/include/song.h +++ b/include/song.h @@ -151,9 +151,10 @@ public: // file management void createNewProject(); void createNewProjectFromTemplate( const QString & _template ); - void loadProject( const QString & _file_name ); - bool saveProject(); - bool saveProjectAs( const QString & _file_name ); + void loadProject( const QString & _filename ); + bool guiSaveProject(); + bool guiSaveProjectAs( const QString & _filename ); + bool saveProjectFile( const QString & _filename ); inline const QString & projectFileName() const { return m_fileName; diff --git a/include/surround_area.h b/include/surround_area.h index 6e1774728..986a1de49 100644 --- a/include/surround_area.h +++ b/include/surround_area.h @@ -31,7 +31,7 @@ #include #include "AutomatableModel.h" -#include "mixer.h" +#include "Mixer.h" class QPixmap; diff --git a/include/visualization_widget.h b/include/visualization_widget.h index 863a0ecca..8b8c3fd16 100644 --- a/include/visualization_widget.h +++ b/include/visualization_widget.h @@ -29,7 +29,7 @@ #include #include -#include "mixer.h" +#include "Mixer.h" class visualizationWidget : public QWidget diff --git a/plugins/flp_import/FlpImport.cpp b/plugins/flp_import/FlpImport.cpp index 6567b2f58..a4672511f 100644 --- a/plugins/flp_import/FlpImport.cpp +++ b/plugins/flp_import/FlpImport.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "FlpImport.h" #include "note_play_handle.h" @@ -40,6 +41,7 @@ #include "Effect.h" #include "engine.h" #include "FxMixer.h" +#include "FxMixerView.h" #include "group_box.h" #include "Instrument.h" #include "InstrumentTrack.h" @@ -104,7 +106,7 @@ extern QString outstring; } - +const int NumFLFxChannels = 64; static void dump_mem( const void * buffer, uint n_bytes ) { @@ -542,7 +544,7 @@ struct FL_Project int currentPattern; int activeEditPattern; - FL_EffectChannel effectChannels[NumFxChannels+1]; + FL_EffectChannel effectChannels[NumFLFxChannels+1]; int currentEffectChannel; QString projectNotes; @@ -903,7 +905,6 @@ bool FlpImport::tryFLPImport( trackContainer * _tc ) const bool is_journ = engine::projectJournal()->isJournalling(); engine::projectJournal()->setJournalling( false ); - while( file().atEnd() == false ) { FLP_Events ev = static_cast( readByte() ); @@ -1022,7 +1023,7 @@ bool FlpImport::tryFLPImport( trackContainer * _tc ) break; case FLP_EffectChannelMuted: -if( p.currentEffectChannel <= NumFxChannels ) +if( p.currentEffectChannel <= NumFLFxChannels ) { p.effectChannels[p.currentEffectChannel].isMuted = ( data & 0x08 ) > 0 ? false : true; @@ -1274,7 +1275,7 @@ if( p.currentEffectChannel <= NumFxChannels ) case FLP_Text_EffectChanName: ++p.currentEffectChannel; - if( p.currentEffectChannel <= NumFxChannels ) + if( p.currentEffectChannel <= NumFLFxChannels ) { p.effectChannels[p.currentEffectChannel].name = text; } @@ -1497,7 +1498,7 @@ if( p.currentEffectChannel <= NumFxChannels ) const int param = pi[i*3+1] & 0xffff; const int ch = ( pi[i*3+1] >> 22 ) & 0x7f; - if( ch < 0 || ch > NumFxChannels ) + if( ch < 0 || ch > NumFLFxChannels ) { continue; } @@ -1558,9 +1559,15 @@ else // now create a project from FL_Project data structure - engine::getSong()->clearProject(); + // configure the mixer + for( int i=0; icreateChannel(); + } + engine::fxMixerView()->refreshDisplay(); + // set global parameters engine::getSong()->setMasterVolume( p.mainVolume ); engine::getSong()->setMasterPitch( p.mainPitch ); @@ -1797,7 +1804,7 @@ p->putValue( jt->pos, value, false ); } } - for( int fx_ch = 0; fx_ch <= NumFxChannels ; ++fx_ch ) + for( int fx_ch = 0; fx_ch <= NumFLFxChannels ; ++fx_ch ) { FxChannel * ch = engine::fxMixer()->effectChannel( fx_ch ); if( !ch ) @@ -1857,7 +1864,7 @@ p->putValue( jt->pos, value, false ); break; } if( effName.isEmpty() || it->fxChannel < 0 || - it->fxChannel > NumFxChannels ) + it->fxChannel > NumFLFxChannels ) { continue; } diff --git a/plugins/ladspa_browser/ladspa_description.cpp b/plugins/ladspa_browser/ladspa_description.cpp index 209d0b89a..004a9996d 100644 --- a/plugins/ladspa_browser/ladspa_description.cpp +++ b/plugins/ladspa_browser/ladspa_description.cpp @@ -2,6 +2,7 @@ * ladspa_description.cpp - LADSPA plugin description * * Copyright (c) 2007 Javier Serrano Polo + * Copyright (c) 2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -30,10 +31,11 @@ #include #include -#include "AudioDevice.h" +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "engine.h" #include "ladspa_2_lmms.h" -#include "mixer.h" +#include "Mixer.h" @@ -73,8 +75,8 @@ ladspaDescription::ladspaDescription( QWidget * _parent, it != plugins.end(); it++ ) { if( _type != VALID || - manager->getDescription( ( *it ).second )->inputChannels - <= engine::getMixer()->audioDev()->channels() ) + manager->getDescription( ( *it ).second )->inputChannels <= + engine::mixer()->audioOutputContext()->audioBackend()->channels() ) { pluginNames.push_back( ( *it ).first ); m_pluginKeys.push_back( ( *it ).second ); diff --git a/plugins/ladspa_browser/ladspa_port_dialog.cpp b/plugins/ladspa_browser/ladspa_port_dialog.cpp index 3f324dbf4..0f2db8c28 100644 --- a/plugins/ladspa_browser/ladspa_port_dialog.cpp +++ b/plugins/ladspa_browser/ladspa_port_dialog.cpp @@ -2,7 +2,8 @@ * ladspa_port_dialog.cpp - dialog to test a LADSPA plugin * * Copyright (c) 2006-2008 Danny McRae - * + * Copyright (c) 2009 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 @@ -22,7 +23,6 @@ * */ - #include "ladspa_port_dialog.h" #include @@ -31,7 +31,7 @@ #include "embed.h" #include "engine.h" #include "ladspa_2_lmms.h" -#include "mixer.h" +#include "Mixer.h" ladspaPortDialog::ladspaPortDialog( const ladspa_key_t & _key ) @@ -95,11 +95,11 @@ ladspaPortDialog::ladspaPortDialog( const ladspa_key_t & _key ) { if( min != NOHINT ) { - min *= engine::getMixer()->processingSampleRate(); + min *= engine::mixer()->processingSampleRate(); } if( max != NOHINT ) { - max *= engine::getMixer()->processingSampleRate(); + max *= engine::mixer()->processingSampleRate(); } } diff --git a/plugins/ladspa_effect/LadspaEffect.cpp b/plugins/ladspa_effect/LadspaEffect.cpp index ea5d53e99..5dbff0ff6 100644 --- a/plugins/ladspa_effect/LadspaEffect.cpp +++ b/plugins/ladspa_effect/LadspaEffect.cpp @@ -25,14 +25,15 @@ #include +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "LadspaEffect.h" #include "mmp.h" -#include "AudioDevice.h" #include "config_mgr.h" #include "ladspa_2_lmms.h" #include "LadspaControl.h" #include "LadspaSubPluginFeatures.h" -#include "mixer.h" +#include "Mixer.h" #include "EffectChain.h" #include "Cpu.h" #include "automation_pattern.h" @@ -87,7 +88,7 @@ LadspaEffect::LadspaEffect( Model * _parent, pluginInstantiation(); - connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( changeSampleRate() ) ); } @@ -144,13 +145,13 @@ bool LadspaEffect::processAudioBuffer( sampleFrame * _buf, int frames = _frames; sampleFrame * o_buf = NULL; - if( m_maxSampleRate < engine::getMixer()->processingSampleRate() ) + if( m_maxSampleRate < engine::mixer()->processingSampleRate() ) { o_buf = _buf; _buf = CPU::allocFrames( _frames ); sampleDown( o_buf, _buf, m_maxSampleRate ); frames = _frames * m_maxSampleRate / - engine::getMixer()->processingSampleRate(); + engine::mixer()->processingSampleRate(); } // Copy the LMMS audio buffer to the LADSPA input buffer and initialize @@ -298,7 +299,8 @@ void LadspaEffect::pluginInstantiation() ladspa2LMMS * manager = engine::getLADSPAManager(); // Calculate how many processing units are needed. - const ch_cnt_t lmms_chnls = engine::getMixer()->audioDev()->channels(); + const ch_cnt_t lmms_chnls = engine::mixer()->audioOutputContext()-> + audioBackend()->channels(); int effect_channels = manager->getDescription( m_key )->inputChannels; setProcessorCount( lmms_chnls / effect_channels ); @@ -325,7 +327,7 @@ void LadspaEffect::pluginInstantiation() // during cleanup. It was easier to troubleshoot with the // memory management all taking place in one file. p->buffer = - new LADSPA_Data[engine::getMixer()->framesPerPeriod()]; + new LADSPA_Data[engine::mixer()->framesPerPeriod()]; if( p->name.toUpper().contains( "IN" ) && manager->isPortInput( m_key, port ) ) @@ -566,7 +568,7 @@ sample_rate_t LadspaEffect::maxSamplerate( const QString & _name ) { return __buggy_plugins[_name]; } - return engine::getMixer()->processingSampleRate(); + return engine::mixer()->processingSampleRate(); } diff --git a/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp b/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp index bb6cba447..35d49749e 100644 --- a/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp +++ b/plugins/ladspa_effect/LadspaSubPluginFeatures.cpp @@ -28,12 +28,13 @@ #include #include +#include "AudioOutputContext.h" +#include "AudioBackend.h" #include "LadspaSubPluginFeatures.h" -#include "AudioDevice.h" #include "engine.h" #include "ladspa_2_lmms.h" #include "LadspaBase.h" -#include "mixer.h" +#include "Mixer.h" LadspaSubPluginFeatures::LadspaSubPluginFeatures( Plugin::PluginTypes _type ) : @@ -142,7 +143,7 @@ void LadspaSubPluginFeatures::listSubPluginKeys( it != plugins.end(); ++it ) { if( lm->getDescription( ( *it ).second )->inputChannels <= - engine::getMixer()->audioDev()->channels() ) + engine::mixer()->audioOutputContext()->audioBackend()->channels() ) { _kl.push_back( ladspaKeyToSubPluginKey( _desc, ( *it ).first, ( *it ).second ) ); } diff --git a/plugins/lb302/lb302.h b/plugins/lb302/lb302.h index 6094c4c53..7d4422769 100644 --- a/plugins/lb302/lb302.h +++ b/plugins/lb302/lb302.h @@ -37,7 +37,6 @@ #include "InstrumentView.h" #include "led_checkbox.h" #include "knob.h" -#include "mixer.h" class lb302SynthView; class notePlayHandle; diff --git a/plugins/lv2_browser/lv2_description.cpp b/plugins/lv2_browser/lv2_description.cpp index ffb4b1480..f0c9175b7 100644 --- a/plugins/lv2_browser/lv2_description.cpp +++ b/plugins/lv2_browser/lv2_description.cpp @@ -33,9 +33,10 @@ #include -#include "AudioDevice.h" +#include "AudioBackend.h" +#include "AudioOutputContext.h" +#include "Mixer.h" #include "engine.h" -#include "mixer.h" @@ -81,7 +82,7 @@ lv2Description::lv2Description( QWidget * _parent, if( description->type == _type && ( _type != VALID || - description->inputChannels <= engine::getMixer()->audioDev()->channels() + description->inputChannels <= engine::mixer()->audioOutputContext()->audioBackend()->channels() ) ) { diff --git a/plugins/lv2_browser/lv2_port_dialog.cpp b/plugins/lv2_browser/lv2_port_dialog.cpp index 295a376a2..5c0e37887 100644 --- a/plugins/lv2_browser/lv2_port_dialog.cpp +++ b/plugins/lv2_browser/lv2_port_dialog.cpp @@ -30,7 +30,7 @@ #include "embed.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" lv2PortDialog::lv2PortDialog( const lv2_key_t & _key ) diff --git a/plugins/sf2_player/sf2_player.cpp b/plugins/sf2_player/sf2_player.cpp index a93a125fd..56479236d 100644 --- a/plugins/sf2_player/sf2_player.cpp +++ b/plugins/sf2_player/sf2_player.cpp @@ -30,6 +30,8 @@ #include #include +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "ResourceFileMapper.h" #include "sf2_player.h" #include "engine.h" @@ -122,14 +124,14 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : m_settings = new_fluid_settings(); fluid_settings_setint( m_settings, (char *) "audio.period-size", - engine::getMixer()->framesPerPeriod() ); + engine::mixer()->framesPerPeriod() ); // This is just our starting instance of synth. It is recreated // everytime we load a new soundfont. m_synth = new_fluid_synth( m_settings ); InstrumentPlayHandle * iph = new InstrumentPlayHandle( this ); - engine::getMixer()->addPlayHandle( iph ); + engine::mixer()->addPlayHandle( iph ); //loadFile( configManager::inst()->defaultSoundfont() ); @@ -147,7 +149,7 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : connect( &m_patchNum, SIGNAL( dataChanged() ), this, SLOT( updatePatch() ) ); - connect( engine::getMixer(), SIGNAL( sampleRateChanged() ), + connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); // Gain @@ -192,7 +194,7 @@ sf2Instrument::sf2Instrument( InstrumentTrack * _instrument_track ) : sf2Instrument::~sf2Instrument() { - engine::getMixer()->removePlayHandles( instrumentTrack() ); + engine::mixer()->removePlayHandles( instrumentTrack() ); freeFont(); delete_fluid_synth( m_synth ); delete_fluid_settings( m_settings ); @@ -498,7 +500,7 @@ void sf2Instrument::updateSampleRate() // Set & get, returns the true sample rate fluid_settings_setnum( m_settings, (char *) "synth.sample-rate", - engine::getMixer()->processingSampleRate() ); + engine::mixer()->processingSampleRate() ); fluid_settings_getnum( m_settings, (char *) "synth.sample-rate", &tempRate ); m_internalSampleRate = static_cast( tempRate ); @@ -529,8 +531,9 @@ void sf2Instrument::updateSampleRate() } m_synthMutex.lock(); - if( engine::getMixer()->currentQualitySettings().interpolation >= - mixer::qualitySettings::Interpolation_SincFastest ) + if( engine::mixer()->audioOutputContext()->qualitySettings(). + interpolation() >= + AudioOutputContext::QualitySettings::Interpolation_SincFastest ) { fluid_synth_set_interp_method( m_synth, -1, FLUID_INTERP_7THORDER ); @@ -541,7 +544,7 @@ void sf2Instrument::updateSampleRate() FLUID_INTERP_DEFAULT ); } m_synthMutex.unlock(); - if( m_internalSampleRate < engine::getMixer()->processingSampleRate() ) + if( m_internalSampleRate < engine::mixer()->processingSampleRate() ) { m_synthMutex.lock(); if( m_srcState != NULL ) @@ -549,9 +552,9 @@ void sf2Instrument::updateSampleRate() src_delete( m_srcState ); } int error; - m_srcState = src_new( engine::getMixer()-> - currentQualitySettings().libsrcInterpolation(), - DEFAULT_CHANNELS, &error ); + m_srcState = src_new( engine::mixer()->audioOutputContext()-> + qualitySettings().libsrcInterpolation(), + DEFAULT_CHANNELS, &error ); if( m_srcState == NULL || error ) { printf( "error while creating SRC-data-" diff --git a/plugins/vibed/vibrating_string.cpp b/plugins/vibed/vibrating_string.cpp index cdffd2caf..fc91a9036 100644 --- a/plugins/vibed/vibrating_string.cpp +++ b/plugins/vibed/vibrating_string.cpp @@ -26,7 +26,7 @@ #include "vibrating_string.h" #include "templates.h" #include "interpolation.h" -#include "mixer.h" +#include "Mixer.h" #include "engine.h" @@ -42,7 +42,7 @@ vibratingString::vibratingString( float _pitch, float _detune, bool _state ) : m_oversample( 2 * _oversample / (int)( _sample_rate / - engine::getMixer()->baseSampleRate() ) ), + engine::mixer()->baseSampleRate() ) ), m_randomize( _randomize ), m_stringLoss( 1.0f - _string_loss ), m_state( 0.1f ) diff --git a/plugins/vst_base/VstPlugin.h b/plugins/vst_base/VstPlugin.h index c365b9253..6d0fb7b10 100644 --- a/plugins/vst_base/VstPlugin.h +++ b/plugins/vst_base/VstPlugin.h @@ -29,7 +29,6 @@ #include #include -#include "mixer.h" #include "JournallingObject.h" #include "communication.h" diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 2864f266c..7762edcb8 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -44,11 +44,11 @@ AutomatableModel::AutomatableModel( DataType _type, const QString & _display_name, bool _default_constructed ) : Model( _parent, _display_name, _default_constructed ), + m_minValue( _min ), + m_maxValue( _max ), m_dataType( _type ), m_value( _val ), m_initValue( _val ), - m_minValue( _min ), - m_maxValue( _max ), m_step( _step ), m_range( _max - _min ), m_journalEntryReady( false ), diff --git a/src/core/Controller.cpp b/src/core/Controller.cpp index f65d9bee5..6bcde8f35 100644 --- a/src/core/Controller.cpp +++ b/src/core/Controller.cpp @@ -30,7 +30,7 @@ #include "song.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "Controller.h" #include "ControllerConnection.h" #include "ControllerDialog.h" diff --git a/src/core/ControllerConnection.cpp b/src/core/ControllerConnection.cpp index 0b346fd83..31c4ee4c5 100644 --- a/src/core/ControllerConnection.cpp +++ b/src/core/ControllerConnection.cpp @@ -30,7 +30,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "ControllerConnection.h" diff --git a/src/core/Cpu.cpp b/src/core/Cpu.cpp index 726ffae32..48792e4a7 100644 --- a/src/core/Cpu.cpp +++ b/src/core/Cpu.cpp @@ -179,6 +179,25 @@ void bufMixNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src, +void bufMixCoeffNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src, + float _coeff, int _frames ) +{ + for( int i = 0; i < _frames; ) + { + _dst[i+0][0] += _src[i+0][0]*_coeff; + _dst[i+0][1] += _src[i+0][1]*_coeff; + _dst[i+1][0] += _src[i+1][0]*_coeff; + _dst[i+1][1] += _src[i+1][1]*_coeff; + _dst[i+2][0] += _src[i+2][0]*_coeff; + _dst[i+2][1] += _src[i+2][1]*_coeff; + _dst[i+3][0] += _src[i+3][0]*_coeff; + _dst[i+3][1] += _src[i+3][1]*_coeff; + i += 4; + } +} + + + void bufMixLRCoeffNoOpt( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames ) @@ -306,6 +325,7 @@ MemCpyFunc memCpy = memCpyNoOpt; MemClearFunc memClear = memClearNoOpt; BufApplyGainFunc bufApplyGain = bufApplyGainNoOpt; BufMixFunc bufMix = bufMixNoOpt; +BufMixCoeffFunc bufMixCoeff = bufMixCoeffNoOpt; BufMixLRCoeffFunc bufMixLRCoeff = bufMixLRCoeffNoOpt; UnalignedBufMixLRCoeffFunc unalignedBufMixLRCoeff = unalignedBufMixLRCoeffNoOpt; BufWetDryMixFunc bufWetDryMix = bufWetDryMixNoOpt; @@ -337,6 +357,7 @@ void memCpySSE( void * RP _dst, const void * RP _src, int _size ); void memClearSSE( void * RP _dst, int _size ); void bufApplyGainSSE( sampleFrameA * RP _dst, float _gain, int _frames ); void bufMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, int _frames ); +void bufMixCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _coeff, int _frames ); void bufMixLRCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames ); void unalignedBufMixLRCoeffSSE( sampleFrame * RP _dst, const sampleFrame * RP _src, const float _left, const float _right, int _frames ); void bufWetDryMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _wet, float _dry, int _frames ); @@ -447,6 +468,7 @@ void init() memClear = memClearSSE; bufApplyGain = bufApplyGainSSE; bufMix = bufMixSSE; + bufMixCoeff = bufMixCoeffSSE; bufMixLRCoeff = bufMixLRCoeffSSE; unalignedBufMixLRCoeff = unalignedBufMixLRCoeffSSE; bufWetDryMix = bufWetDryMixSSE; diff --git a/src/core/CpuX86.c b/src/core/CpuX86.c index 4c5e7217c..6c9c5a1c0 100644 --- a/src/core/CpuX86.c +++ b/src/core/CpuX86.c @@ -229,6 +229,29 @@ void bufMixSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, } +void bufMixCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, + float _coeff, int _frames ) +{ + int i; + + PREFETCH_READ(_src); + PREFETCH_WRITE(_dst); + + for( i = 0; i < _frames; ) + { + _dst[i+0][0] += _src[i+0][0]*_coeff; + _dst[i+0][1] += _src[i+0][1]*_coeff; + _dst[i+1][0] += _src[i+1][0]*_coeff; + _dst[i+1][1] += _src[i+1][1]*_coeff; + _dst[i+2][0] += _src[i+2][0]*_coeff; + _dst[i+2][1] += _src[i+2][1]*_coeff; + _dst[i+3][0] += _src[i+3][0]*_coeff; + _dst[i+3][1] += _src[i+3][1]*_coeff; + i += 4; + } +} + + void bufMixLRCoeffSSE( sampleFrameA * RP _dst, const sampleFrameA * RP _src, float _left, float _right, int _frames ) diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index f848aacea..ebd595bbf 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -23,11 +23,11 @@ * */ - #include #include +#include "AudioOutputContext.h" #include "Effect.h" #include "engine.h" #include "DummyEffect.h" @@ -168,8 +168,8 @@ void Effect::reinitSRC() } int error; if( ( m_srcState[i] = src_new( - engine::getMixer()->currentQualitySettings(). - libsrcInterpolation(), + engine::mixer()->audioOutputContext()-> + qualitySettings().libsrcInterpolation(), DEFAULT_CHANNELS, &error ) ) == NULL ) { fprintf( stderr, "Error: src_new() failed in effect.cpp!\n" ); diff --git a/src/core/EnvelopeAndLfoParameters.cpp b/src/core/EnvelopeAndLfoParameters.cpp index dbf3c37d4..28209cb06 100644 --- a/src/core/EnvelopeAndLfoParameters.cpp +++ b/src/core/EnvelopeAndLfoParameters.cpp @@ -27,7 +27,7 @@ #include "EnvelopeAndLfoParameters.h" #include "debug.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "mmp.h" #include "Oscillator.h" diff --git a/src/core/FxMixer.cpp b/src/core/FxMixer.cpp index c7cfff902..ce43d7196 100644 --- a/src/core/FxMixer.cpp +++ b/src/core/FxMixer.cpp @@ -22,18 +22,20 @@ * */ - #include #include "FxMixer.h" +#include "MixerWorkerThread.h" #include "Cpu.h" #include "Effect.h" #include "song.h" +#include "InstrumentTrack.h" +#include "bb_track_container.h" + FxChannel::FxChannel( Model * _parent ) : m_fxChain( NULL ), - m_used( false ), m_stillRunning( false ), m_peakLeft( 0.0f ), m_peakRight( 0.0f ), @@ -41,7 +43,8 @@ FxChannel::FxChannel( Model * _parent ) : m_muteModel( false, _parent ), m_volumeModel( 1.0, 0.0, 2.0, 0.01, _parent ), m_name(), - m_lock() + m_lock(), + m_queued( false ) { engine::getMixer()->clearAudioBuffer( m_buffer, engine::getMixer()->framesPerPeriod() ); @@ -57,34 +60,361 @@ FxChannel::~FxChannel() +void FxChannel::doProcessing( sampleFrame * _buf ) +{ + FxMixer * fxm = engine::fxMixer(); + const fpp_t fpp = engine::getMixer()->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_muteModel.value() ) + { + // do mixer sends. loop through the channels that send to this one + for( int i = 0; i < m_receives.size(); ++i) + { + fx_ch_t senderIndex = m_receives[i]; + FxChannel * sender = fxm->effectChannel(senderIndex); + + // mix it with this one + float amt = fxm->channelSendModel(senderIndex, + m_channelIndex)->value(); + CPU::bufMixCoeff( _buf, sender->m_buffer, + sender->m_volumeModel.value() * amt, fpp ); + } + + const float v = m_volumeModel.value(); + + m_fxChain.startRunning(); + m_stillRunning = m_fxChain.processAudioBuffer( _buf, fpp ); + m_peakLeft = engine::getMixer()->peakValueLeft( _buf, fpp ) * v; + m_peakRight = engine::getMixer()->peakValueRight( _buf, fpp ) * v; + } + else + { + m_peakLeft = m_peakRight = 0.0f; + } + + m_state = ThreadableJob::Done; + + // check if any of its parents are now able to be processed + for(int i=0; ieffectChannel(m_sends[i]); + if( parent->state() == ThreadableJob::Unstarted ) + { + bool everyLeafDone = true; + for( int j=0; jm_receives.size(); ++j ) + { + if( fxm->effectChannel( parent->m_receives[j] )->state() != + ThreadableJob::Done ) + { + everyLeafDone = false; + break; + } + } + if( everyLeafDone ) + { + MixerWorkerThread::addJob(parent); + } + } + } + +} FxMixer::FxMixer() : JournallingObject(), - Model( NULL ) + Model( NULL ), + m_fxChannels() { - for( int i = 0; i < NumFxChannels+1; ++i ) - { - m_fxChannels[i] = new FxChannel( this ); - } - // reset name etc. - clear(); + // create master channel + createChannel(); } - FxMixer::~FxMixer() { - for( int i = 0; i < NumFxChannels+1; ++i ) + for( int i = 0; i < m_fxChannels.size(); ++i ) { + for( int j = 0; j < m_fxChannels[i]->m_sendAmount.size(); ++j) + { + delete m_fxChannels[i]->m_sendAmount[j]; + } delete m_fxChannels[i]; } } +int FxMixer::createChannel() +{ + // create new channel + m_fxChannels.push_back(new FxChannel( this )); + + // reset channel state + int index = m_fxChannels.size() - 1; + clearChannel(index); + + return index; +} + + +void FxMixer::deleteChannel(int index) +{ + m_fxChannels[index]->m_lock.lock(); + + // go through every instrument and adjust for the channel index change + QVector songTrackList = engine::getSong()->tracks(); + QVector bbTrackList = engine::getBBTrackContainer()->tracks(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) + { + QVector trackList = trackLists[tl]; + for(int i=0; itype() == track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; + int val = inst->effectChannelModel()->value(0); + if( val == index ) + { + // we are deleting this track's fx send + // send to master + inst->effectChannelModel()->setValue(0); + } + else if( val > index ) + { + // subtract 1 to make up for the missing channel + inst->effectChannelModel()->setValue(val-1); + } + + } + } + } + + // delete all of this channel's sends and receives + for(int i=0; im_sends.size(); ++i) + { + deleteChannelSend(index, m_fxChannels[index]->m_sends[i]); + } + for(int i=0; im_receives.size(); ++i) + { + deleteChannelSend(m_fxChannels[index]->m_receives[i], index); + } + + for(int i=0; im_sends.size(); ++j) + { + if( m_fxChannels[i]->m_sends[j] > index ) + { + // subtract 1 to make up for the missing channel + --m_fxChannels[i]->m_sends[j]; + } + } + for(int j=0; jm_receives.size(); ++j) + { + if( m_fxChannels[i]->m_receives[j] > index ) + { + // subtract 1 to make up for the missing channel + --m_fxChannels[i]->m_receives[j]; + } + } + + } + + // actually delete the channel + delete m_fxChannels[index]; + m_fxChannels.remove(index); +} + + + +void FxMixer::moveChannelLeft(int index) +{ + // can't move master or first channel + if( index <= 1 || index >= m_fxChannels.size() ) + { + return; + } + + // channels to swap + int a = index - 1, b = index; + + // go through every instrument and adjust for the channel index change + QVector songTrackList = engine::getSong()->tracks(); + QVector bbTrackList = engine::getBBTrackContainer()->tracks(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) + { + QVector trackList = trackLists[tl]; + for(int i=0; itype() == track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; + int val = inst->effectChannelModel()->value(0); + if( val == a ) + { + inst->effectChannelModel()->setValue(b); + } + else if( val == b ) + { + inst->effectChannelModel()->setValue(a); + } + + } + } + } + + for(int i=0; im_sends.size(); ++j) + { + if( m_fxChannels[i]->m_sends[j] == a ) + { + m_fxChannels[i]->m_sends[j] = b; + } + else if( m_fxChannels[i]->m_sends[j] == b ) + { + m_fxChannels[i]->m_sends[j] = a; + } + } + for(int j=0; jm_receives.size(); ++j) + { + if( m_fxChannels[i]->m_receives[j] == a ) + { + m_fxChannels[i]->m_receives[j] = b; + } + else if( m_fxChannels[i]->m_receives[j] == b ) + { + m_fxChannels[i]->m_receives[j] = a; + } + } + } + + // actually do the swap + FxChannel * tmpChannel = m_fxChannels[a]; + m_fxChannels[a] = m_fxChannels[b]; + m_fxChannels[b] = tmpChannel; +} + + + +void FxMixer::moveChannelRight(int index) +{ + moveChannelLeft(index+1); +} + + + +void FxMixer::createChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel, + float amount) +{ + // find the existing connection + FxChannel * from = m_fxChannels[fromChannel]; + for(int i=0; im_sends.size(); ++i){ + if( from->m_sends[i] == toChannel ) + { + // simply adjust the amount + from->m_sendAmount[i]->setValue(amount); + return; + } + } + + // connection does not exist. create a new one + + // add to from's sends + from->m_sends.push_back(toChannel); + from->m_sendAmount.push_back(new FloatModel(amount, 0, 1, 0.001, NULL, + tr("Amount to send"))); + + // add to to's receives + m_fxChannels[toChannel]->m_receives.push_back(fromChannel); + +} + + + +// delete the connection made by createChannelSend +void FxMixer::deleteChannelSend(fx_ch_t fromChannel, fx_ch_t toChannel) +{ + // delete the send + FxChannel * from = m_fxChannels[fromChannel]; + FxChannel * to = m_fxChannels[toChannel]; + + // find and delete the send entry + for(int i=0; im_sends.size(); ++i) { + if( from->m_sends[i] == toChannel ) + { + // delete this index + delete from->m_sendAmount[i]; + from->m_sendAmount.remove(i); + from->m_sends.remove(i); + break; + } + } + + // find and delete the receive entry + for(int i=0; im_receives.size(); ++i) + { + if( to->m_receives[i] == fromChannel ) + { + // delete this index + to->m_receives.remove(i); + break; + } + } +} + + +bool FxMixer::isInfiniteLoop(fx_ch_t sendFrom, fx_ch_t sendTo) { + // can't send master to anything + if( sendFrom == 0 ) return true; + + // can't send channel to itself + if( sendFrom == sendTo ) return true; + + // follow sendTo's outputs recursively looking for something that sends + // to sendFrom + for(int i=0; im_sends.size(); ++i) + { + if( isInfiniteLoop( sendFrom, m_fxChannels[sendTo]->m_sends[i] ) ) + { + return true; + } + } + + return false; +} + + +// how much does fromChannel send its output to the input of toChannel? +FloatModel * FxMixer::channelSendModel(fx_ch_t fromChannel, fx_ch_t toChannel) +{ + FxChannel * from = m_fxChannels[fromChannel]; + for(int i=0; im_sends.size(); ++i){ + if( from->m_sends[i] == toChannel ) + return from->m_sendAmount[i]; + } + return NULL; +} + + void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) { @@ -93,7 +423,6 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) m_fxChannels[_ch]->m_lock.lock(); CPU::bufMix( m_fxChannels[_ch]->m_buffer, _buf, engine::getMixer()->framesPerPeriod() ); - m_fxChannels[_ch]->m_used = true; m_fxChannels[_ch]->m_lock.unlock(); } } @@ -101,40 +430,6 @@ void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) -void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) -{ - if( m_fxChannels[_ch]->m_muteModel.value() == false && - ( m_fxChannels[_ch]->m_used || - m_fxChannels[_ch]->m_stillRunning || - _ch == 0 ) ) - { - if( _buf == NULL ) - { - _buf = m_fxChannels[_ch]->m_buffer; - } - const fpp_t f = engine::getMixer()->framesPerPeriod(); - m_fxChannels[_ch]->m_fxChain.startRunning(); - m_fxChannels[_ch]->m_stillRunning = - m_fxChannels[_ch]->m_fxChain.processAudioBuffer( - _buf, f ); - m_fxChannels[_ch]->m_peakLeft = - engine::getMixer()->peakValueLeft( _buf, f ) * - m_fxChannels[_ch]->m_volumeModel.value(); - m_fxChannels[_ch]->m_peakRight = - engine::getMixer()->peakValueRight( _buf, f ) * - m_fxChannels[_ch]->m_volumeModel.value(); - m_fxChannels[_ch]->m_used = true; - } - else - { - m_fxChannels[_ch]->m_peakLeft = - m_fxChannels[_ch]->m_peakRight = 0.0f; - } -} - - - - void FxMixer::prepareMasterMix() { engine::getMixer()->clearAudioBuffer( m_fxChannels[0]->m_buffer, @@ -143,38 +438,53 @@ void FxMixer::prepareMasterMix() +void FxMixer::addChannelLeaf( int _ch, sampleFrame * _buf ) +{ + FxChannel * thisCh = m_fxChannels[_ch]; + + // remember what channel number we are, 'cause we need it later + thisCh->m_channelIndex = _ch; + + // if we're muted or this channel is seen already, discount it + if( thisCh->m_muteModel.value() || thisCh->m_queued ) + return; + + int numDeps = thisCh->m_receives.size(); + if( numDeps > 0 ) + { + for(int i=0; im_receives[i], _buf ); + } + } + else + { + // add this channel to job list + thisCh->m_queued = true; + MixerWorkerThread::addJob( thisCh ); + } + +} + + void FxMixer::masterMix( sampleFrame * _buf ) { const int fpp = engine::getMixer()->framesPerPeriod(); + + // recursively loop through channel dependency chain + // and add all channels to job list that have no dependencies + // when the channel completes it will check its parent to see if it needs + // to be processed. + MixerWorkerThread::resetJobQueue( MixerWorkerThread::JobQueue::Dynamic ); + addChannelLeaf( 0, _buf ); + while( m_fxChannels[0]->state() != ThreadableJob::Done ) + { + MixerWorkerThread::startAndWaitForJobs(); + } + memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); - for( int i = 1; i < NumFxChannels+1; ++i ) - { - if( m_fxChannels[i]->m_used ) - { - sampleFrame * ch_buf = m_fxChannels[i]->m_buffer; - const float v = m_fxChannels[i]->m_volumeModel.value(); - for( f_cnt_t f = 0; f < fpp; ++f ) - { - _buf[f][0] += ch_buf[f][0] * v; - _buf[f][1] += ch_buf[f][1] * v; - } - engine::getMixer()->clearAudioBuffer( ch_buf, - engine::getMixer()->framesPerPeriod() ); - m_fxChannels[i]->m_used = false; - } - } - - processChannel( 0, _buf ); - - if( m_fxChannels[0]->m_muteModel.value() ) - { - engine::getMixer()->clearAudioBuffer( _buf, - engine::getMixer()->framesPerPeriod() ); - return; - } - const float v = m_fxChannels[0]->m_volumeModel.value(); for( f_cnt_t f = 0; f < engine::getMixer()->framesPerPeriod(); ++f ) { @@ -184,6 +494,16 @@ void FxMixer::masterMix( sampleFrame * _buf ) m_fxChannels[0]->m_peakLeft *= engine::getMixer()->masterGain(); m_fxChannels[0]->m_peakRight *= engine::getMixer()->masterGain(); + + // clear all channel buffers and + // reset channel process state + for( int i = 0; i < numChannels(); ++i) + { + engine::getMixer()->clearAudioBuffer( m_fxChannels[i]->m_buffer, + engine::getMixer()->framesPerPeriod() ); + m_fxChannels[i]->reset(); + m_fxChannels[i]->m_queued = false; + } } @@ -191,58 +511,138 @@ void FxMixer::masterMix( sampleFrame * _buf ) void FxMixer::clear() { - for( int i = 0; i <= NumFxChannels; ++i ) + while( m_fxChannels.size() > 1 ) { - m_fxChannels[i]->m_fxChain.clear(); - m_fxChannels[i]->m_volumeModel.setValue( 1.0f ); - m_fxChannels[i]->m_muteModel.setValue( false ); - m_fxChannels[i]->m_name = ( i == 0 ) ? - tr( "Master" ) : tr( "FX %1" ).arg( i ); - m_fxChannels[i]->m_volumeModel.setDisplayName( - m_fxChannels[i]->m_name ); - + deleteChannel(1); } + + clearChannel(0); } +void FxMixer::clearChannel(fx_ch_t index) +{ + FxChannel * ch = m_fxChannels[index]; + ch->m_fxChain.clear(); + ch->m_volumeModel.setValue( 1.0f ); + ch->m_muteModel.setValue( false ); + ch->m_name = ( index == 0 ) ? tr( "Master" ) : tr( "FX %1" ).arg( index ); + ch->m_volumeModel.setDisplayName(ch->m_name ); + + // send only to master + if( index > 0) + { + // delete existing sends + for( int i=0; im_sends.size(); ++i) + { + deleteChannelSend(index, ch->m_sends[i]); + } + + // add send to master + createChannelSend(index, 0); + } + + // delete receives + for( int i=0; im_receives.size(); ++i) + { + deleteChannelSend(ch->m_receives[i], index); + } + +} void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - for( int i = 0; i <= NumFxChannels; ++i ) + for( int i = 0; i < m_fxChannels.size(); ++i ) { + FxChannel * ch = m_fxChannels[i]; + QDomElement fxch = _doc.createElement( QString( "fxchannel" ) ); _this.appendChild( fxch ); - m_fxChannels[i]->m_fxChain.saveState( _doc, fxch ); - m_fxChannels[i]->m_volumeModel.saveSettings( _doc, fxch, - "volume" ); - m_fxChannels[i]->m_muteModel.saveSettings( _doc, fxch, - "muted" ); + + ch->m_fxChain.saveState( _doc, fxch ); + ch->m_volumeModel.saveSettings( _doc, fxch, "volume" ); + ch->m_muteModel.saveSettings( _doc, fxch, "muted" ); fxch.setAttribute( "num", i ); - fxch.setAttribute( "name", m_fxChannels[i]->m_name ); + fxch.setAttribute( "name", ch->m_name ); + + // add the channel sends + for( int si = 0; si < ch->m_sends.size(); ++si ) + { + QDomElement sendsDom = _doc.createElement( QString( "send" ) ); + fxch.appendChild( sendsDom ); + + sendsDom.setAttribute( "channel", ch->m_sends[si] ); + ch->m_sendAmount[si]->saveSettings( _doc, sendsDom, "amount"); + } } } +// make sure we have at least num channels +void FxMixer::allocateChannelsTo(int num) +{ + while( num > m_fxChannels.size() - 1 ) + { + createChannel(); + // delete the default send to master + deleteChannelSend(m_fxChannels.size()-1, 0); + } +} void FxMixer::loadSettings( const QDomElement & _this ) { clear(); QDomNode node = _this.firstChild(); - for( int i = 0; i <= NumFxChannels; ++i ) + bool thereIsASend = false; + + while( ! node.isNull() ) { QDomElement fxch = node.toElement(); + + // index of the channel we are about to load int num = fxch.attribute( "num" ).toInt(); - m_fxChannels[num]->m_fxChain.restoreState( - fxch.firstChildElement( - m_fxChannels[num]->m_fxChain.nodeName() ) ); + + // allocate enough channels + allocateChannelsTo( num ); + m_fxChannels[num]->m_volumeModel.loadSettings( fxch, "volume" ); m_fxChannels[num]->m_muteModel.loadSettings( fxch, "muted" ); m_fxChannels[num]->m_name = fxch.attribute( "name" ); + + m_fxChannels[num]->m_fxChain.restoreState( fxch.firstChildElement( + m_fxChannels[num]->m_fxChain.nodeName() ) ); + + // mixer sends + QDomNodeList chData = fxch.childNodes(); + for( unsigned int i=0; i * @@ -24,8 +24,10 @@ #include -#include "mixer.h" +#include "AudioOutputContext.h" +#include "Mixer.h" #include "FxMixer.h" +#include "MixerWorkerThread.h" #include "play_handle.h" #include "song.h" #include "templates.h" @@ -61,215 +63,10 @@ #endif -static QVector __fx_channel_jobs( NumFxChannels ); - - - -class MixerWorkerThread : public QThread -{ -public: - enum JobTypes - { - InvalidJob, - PlayHandle, - AudioPortEffects, - EffectChannel, - NumJobTypes - } ; - - struct JobQueueItem - { - JobQueueItem() : - type( InvalidJob ), - job( NULL ), - param( 0 ), - done( false ) - { - } - JobQueueItem( JobTypes _type, void * _job, int _param = 0 ) : - type( _type ), - job( _job ), - param( _param ), - done( false ) - { - } - - JobTypes type; - void * job; - int param; - - QAtomicInt done; - - } ; - - struct JobQueue - { -#define JOB_QUEUE_SIZE 1024 - JobQueue() : - queueSize( 0 ) - { - } - - JobQueueItem items[JOB_QUEUE_SIZE]; - int queueSize; - QAtomicInt itemsDone; - } ; - - static JobQueue s_jobQueue; - - MixerWorkerThread( int _worker_num, mixer * _mixer ) : - QThread( _mixer ), - m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), - m_workerNum( _worker_num ), - m_quit( false ), - m_mixer( _mixer ), - m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) - { - } - - virtual ~MixerWorkerThread() - { - CPU::freeFrames( m_workingBuf ); - } - - virtual void quit() - { - m_quit = true; - } - - void processJobQueue(); - - -private: - virtual void run() - { -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_PTHREAD_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( m_workerNum, &mask ); - pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); -#endif -#endif -#endif - QMutex m; - while( m_quit == false ) - { - m.lock(); - m_queueReadyWaitCond->wait( &m ); - processJobQueue(); - m.unlock(); - } - } - - sampleFrame * m_workingBuf; - int m_workerNum; - volatile bool m_quit; - mixer * m_mixer; - QWaitCondition * m_queueReadyWaitCond; - -} ; - - -MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; - - - -void MixerWorkerThread::processJobQueue() -{ - for( int i = 0; i < s_jobQueue.queueSize; ++i ) - { - JobQueueItem * it = &s_jobQueue.items[i]; - if( it->done.fetchAndStoreOrdered( 1 ) == 0 ) - { - switch( it->type ) - { - case PlayHandle: - ( (playHandle *) it->job )-> - play( m_workingBuf ); - break; - case AudioPortEffects: - { - AudioPort * a = (AudioPort *) it->job; - const bool me = a->processEffects(); - if( me || a->m_bufferUsage != AudioPort::NoUsage ) - { - engine::fxMixer()->mixToChannel( a->firstBuffer(), - a->nextFxChannel() ); - a->nextPeriod(); - } - } - break; - case EffectChannel: - engine::fxMixer()->processChannel( (fx_ch_t) it->param ); - break; - default: - break; - } - s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); - } - } -} - -#define FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ - MixerWorkerThread::s_jobQueue.queueSize = 0; \ - MixerWorkerThread::s_jobQueue.itemsDone = 0; \ - for( _vec_type::Iterator it = _vec.begin(); \ - it != _vec.end(); ++it ) \ - { \ - if( _condition ) \ - { - -#define FILL_JOB_QUEUE_END() \ - ++MixerWorkerThread::s_jobQueue.queueSize; \ - } \ - } - -#define FILL_JOB_QUEUE(_vec_type,_vec,_job_type,_condition) \ - FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ - MixerWorkerThread::s_jobQueue.items \ - [MixerWorkerThread::s_jobQueue.queueSize] = \ - MixerWorkerThread::JobQueueItem( _job_type, \ - (void *) *it ); \ - FILL_JOB_QUEUE_END() - -#define FILL_JOB_QUEUE_PARAM(_vec_type,_vec,_job_type,_condition) \ - FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ - MixerWorkerThread::s_jobQueue.items \ - [MixerWorkerThread::s_jobQueue.queueSize] = \ - MixerWorkerThread::JobQueueItem( _job_type, \ - NULL, *it ); \ - FILL_JOB_QUEUE_END() - -#define START_JOBS() \ - m_queueReadyWaitCond.wakeAll(); - -// define a pause instruction for spinlock-loop - merely useful on -// HyperThreading systems with just one physical core (e.g. Intel Atom) -#ifdef LMMS_HOST_X86 -#define SPINLOCK_PAUSE() asm( "pause" ) -#else -#ifdef LMMS_HOST_X86_64 -#define SPINLOCK_PAUSE() asm( "pause" ) -#else -#define SPINLOCK_PAUSE() -#endif -#endif - -#define WAIT_FOR_JOBS() \ - m_workers[m_numWorkers]->processJobQueue(); \ - while( MixerWorkerThread::s_jobQueue.itemsDone < \ - MixerWorkerThread::s_jobQueue.queueSize ) \ - { \ - SPINLOCK_PAUSE(); \ - } \ - - - - -mixer::mixer() : - m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), +Mixer::Mixer() : + m_framesPerPeriod( qBound( 32, + configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt(), + DEFAULT_BUFFER_SIZE ) ), m_workingBuf( NULL ), m_inputBufferRead( 0 ), m_inputBufferWrite( 1 ), @@ -279,58 +76,20 @@ mixer::mixer() : m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), m_queueReadyWaitCond(), - m_qualitySettings( qualitySettings::Mode_Draft ), m_masterGain( 1.0f ), - m_audioDev( NULL ), - m_oldAudioDev( NULL ), + m_audioOutputContext( NULL ), + m_defaultAudioOutputContext( NULL ), m_globalMutex( QMutex::Recursive ) { for( int i = 0; i < 2; ++i ) { m_inputBufferFrames[i] = 0; m_inputBufferSize[i] = DEFAULT_BUFFER_SIZE * 100; - m_inputBuffer[i] = CPU::allocFrames( + m_inputBuffer[i] = CPU::allocFrames( DEFAULT_BUFFER_SIZE * 100 ); clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] ); } - for( int i = 1; i < NumFxChannels+1; ++i ) - { - __fx_channel_jobs[i-1] = (fx_ch_t) i; - } - - // just rendering? - if( !engine::hasGUI() ) - { - m_framesPerPeriod = DEFAULT_BUFFER_SIZE; - m_fifo = new fifo( 1 ); - } - else if( configManager::inst()->value( "mixer", "framesperaudiobuffer" - ).toInt() >= 32 ) - { - m_framesPerPeriod = - (fpp_t) configManager::inst()->value( "mixer", - "framesperaudiobuffer" ).toInt(); - - if( m_framesPerPeriod > DEFAULT_BUFFER_SIZE ) - { - m_fifo = new fifo( m_framesPerPeriod - / DEFAULT_BUFFER_SIZE ); - m_framesPerPeriod = DEFAULT_BUFFER_SIZE; - } - else - { - m_fifo = new fifo( 1 ); - } - } - else - { - configManager::inst()->setValue( "mixer", - "framesperaudiobuffer", - QString::number( m_framesPerPeriod ) ); - m_fifo = new fifo( 1 ); - } - m_workingBuf = CPU::allocFrames( m_framesPerPeriod ); for( Uint8 i = 0; i < 3; i++ ) { @@ -341,7 +100,7 @@ mixer::mixer() : for( int i = 0; i < m_numWorkers+1; ++i ) { - MixerWorkerThread * wt = new MixerWorkerThread( i, this ); + MixerWorkerThread * wt = new MixerWorkerThread( this ); if( i < m_numWorkers ) { wt->start( QThread::TimeCriticalPriority ); @@ -352,33 +111,29 @@ mixer::mixer() : m_poolDepth = 2; m_readBuffer = 0; m_writeBuffer = 1; + + // initialize default AudioOutputContext + m_defaultAudioOutputContext = new AudioOutputContext( this, NULL, + AudioOutputContext::QualitySettings::Preset_Draft ); + m_audioOutputContext = m_defaultAudioOutputContext; } -mixer::~mixer() +Mixer::~Mixer() { - // distribute an empty job-queue so that worker-threads - // get out of their processing-loop - MixerWorkerThread::s_jobQueue.queueSize = 0; for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->quit(); } - START_JOBS(); + MixerWorkerThread::startAndWaitForJobs(); for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->wait( 500 ); } - while( m_fifo->available() ) - { - delete[] m_fifo->read(); - } - delete m_fifo; - - delete m_audioDev; + delete m_audioOutputContext; delete m_midiClient; for( Uint8 i = 0; i < 3; i++ ) @@ -392,54 +147,54 @@ mixer::~mixer() -void mixer::initDevices() +void Mixer::initDevices() { - m_audioDev = tryAudioDevices(); + audioOutputContext()->setAudioBackend( tryAudioBackends() ); m_midiClient = tryMidiClients(); } -void mixer::startProcessing( bool _needs_fifo ) +void Mixer::setAudioOutputContext( AudioOutputContext * context ) { - if( _needs_fifo ) - { - m_fifoWriter = new fifoWriter( this, m_fifo ); - m_fifoWriter->start( QThread::HighPriority ); - } - else - { - m_fifoWriter = NULL; - } + stopProcessing(); - m_audioDev->startProcessing(); + m_audioOutputContext = context; + + //m_audioDev->applyQualitySettings(); + + emit sampleRateChanged(); + + startProcessing(); } -void mixer::stopProcessing() +void Mixer::startProcessing() { - if( m_fifoWriter != NULL ) + if( m_audioOutputContext ) { - m_fifoWriter->finish(); - m_audioDev->stopProcessing(); - m_fifoWriter->wait( 1000 ); - m_fifoWriter->terminate(); - delete m_fifoWriter; - m_fifoWriter = NULL; - } - else - { - m_audioDev->stopProcessing(); + m_audioOutputContext->startProcessing(); } } -sample_rate_t mixer::baseSampleRate() const +void Mixer::stopProcessing() +{ + if( m_audioOutputContext ) + { + m_audioOutputContext->stopProcessing(); + } +} + + + + +sample_rate_t Mixer::baseSampleRate() const { sample_rate_t sr = configManager::inst()->value( "mixer", "samplerate" ).toInt(); @@ -453,33 +208,36 @@ sample_rate_t mixer::baseSampleRate() const -sample_rate_t mixer::outputSampleRate() const +sample_rate_t Mixer::outputSampleRate() const { - return m_audioDev != NULL ? m_audioDev->sampleRate() : - baseSampleRate(); + if( audioOutputContext()->audioBackend() ) + { + return audioOutputContext()->audioBackend()->sampleRate(); + } + return baseSampleRate(); } -sample_rate_t mixer::inputSampleRate() const +sample_rate_t Mixer::inputSampleRate() const { - return m_audioDev != NULL ? m_audioDev->sampleRate() : - baseSampleRate(); + return outputSampleRate(); } -sample_rate_t mixer::processingSampleRate() const +sample_rate_t Mixer::processingSampleRate() const { - return outputSampleRate() * m_qualitySettings.sampleRateMultiplier(); + return outputSampleRate() * + audioOutputContext()->qualitySettings().sampleRateMultiplier(); } -bool mixer::criticalXRuns() const +bool Mixer::criticalXRuns() const { return m_cpuLoad >= 99 && engine::getSong()->realTimeTask() == true; } @@ -487,14 +245,14 @@ bool mixer::criticalXRuns() const -void mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) +void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) { lockInputFrames(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; int size = m_inputBufferSize[ m_inputBufferWrite ]; sampleFrame * buf = m_inputBuffer[ m_inputBufferWrite ]; - + if( frames + _frames > size ) { size = qMax( size * 2, frames + _frames ); @@ -507,23 +265,24 @@ void mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) buf = ab; } - + CPU::memCpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); m_inputBufferFrames[ m_inputBufferWrite ] += _frames; - + unlockInputFrames(); } -sampleFrameA * mixer::renderNextBuffer() +sampleFrameA * Mixer::renderNextBuffer() { MicroTimer timer; static song::playPos last_metro_pos = -1; - song::playPos p = engine::getSong()->getPlayPos( - song::Mode_PlayPattern ); + FxMixer * fxm = engine::fxMixer(); + + song::playPos p = engine::getSong()->getPlayPos( song::Mode_PlayPattern ); if( engine::getSong()->playMode() == song::Mode_PlayPattern && engine::getPianoRoll()->isRecording() == true && p != last_metro_pos && p.getTicks() % @@ -575,18 +334,15 @@ sampleFrameA * mixer::renderNextBuffer() clearAudioBuffer( m_writeBuf, m_framesPerPeriod ); // prepare master mix (clear internal buffers etc.) - engine::fxMixer()->prepareMasterMix(); + fxm->prepareMasterMix(); // create play-handles for new notes, samples etc. engine::getSong()->processNextBuffer(); // STAGE 1: run and render all play handles - FILL_JOB_QUEUE(PlayHandleList,m_playHandles, - MixerWorkerThread::PlayHandle, - !( *it )->done()); - START_JOBS(); - WAIT_FOR_JOBS(); + MixerWorkerThread::fillJobQueue( m_playHandles ); + MixerWorkerThread::startAndWaitForJobs(); // removed all play handles which are done for( PlayHandleList::Iterator it = m_playHandles.begin(); @@ -611,21 +367,11 @@ sampleFrameA * mixer::renderNextBuffer() // STAGE 2: process effects of all instrument- and sampletracks - FILL_JOB_QUEUE(QVector,m_audioPorts, - MixerWorkerThread::AudioPortEffects,1); - START_JOBS(); - WAIT_FOR_JOBS(); + MixerWorkerThread::fillJobQueue >( m_audioPorts ); + MixerWorkerThread::startAndWaitForJobs(); - - // STAGE 3: process effects in FX mixer - FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, - MixerWorkerThread::EffectChannel,1); - START_JOBS(); - WAIT_FOR_JOBS(); - - - // STAGE 4: do master mix in FX mixer - engine::fxMixer()->masterMix( m_writeBuf ); + // STAGE 3: do master mix in FX mixer + fxm->masterMix( m_writeBuf ); unlock(); @@ -649,7 +395,7 @@ sampleFrameA * mixer::renderNextBuffer() // removes all play-handles. this is neccessary, when the song is stopped -> // all remaining notes etc. would be played until their end -void mixer::clear() +void Mixer::clear() { // TODO: m_midiClient->noteOffAll(); lock(); @@ -669,7 +415,7 @@ void mixer::clear() -void mixer::bufferToPort( const sampleFrame * _buf, +void Mixer::bufferToPort( const sampleFrame * _buf, const fpp_t _frames, const f_cnt_t _offset, stereoVolumeVector _vv, @@ -709,7 +455,7 @@ void mixer::bufferToPort( const sampleFrame * _buf, -void mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, +void Mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { if( likely( (size_t)( _ab+_offset ) % 16 == 0 && _frames % 8 == 0 ) ) @@ -724,18 +470,8 @@ void mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, -#ifndef LMMS_DISABLE_SURROUND -void mixer::clearAudioBuffer( surroundSampleFrame * _ab, const f_cnt_t _frames, - const f_cnt_t _offset ) -{ - memset( _ab+_offset, 0, sizeof( *_ab ) * _frames ); -} -#endif - - - -float mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) +float Mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) @@ -755,7 +491,7 @@ float mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) -float mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) +float Mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) @@ -775,97 +511,7 @@ float mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) -void mixer::changeQuality( const struct qualitySettings & _qs ) -{ - // don't delete the audio-device - stopProcessing(); - - m_qualitySettings = _qs; - m_audioDev->applyQualitySettings(); - - emit sampleRateChanged(); - emit qualitySettingsChanged(); - - startProcessing(); -} - - - - -void mixer::setAudioDevice( AudioDevice * _dev ) -{ - stopProcessing(); - - m_oldAudioDev = m_audioDev; - - if( _dev == NULL ) - { - printf( "param _dev == NULL in mixer::setAudioDevice(...). " - "Trying any working audio-device\n" ); - m_audioDev = tryAudioDevices(); - } - else - { - m_audioDev = _dev; - } - - emit sampleRateChanged(); - - startProcessing(); -} - - - - -void mixer::setAudioDevice( AudioDevice * _dev, - const struct qualitySettings & _qs, - bool _needs_fifo ) -{ - // don't delete the audio-device - stopProcessing(); - - m_qualitySettings = _qs; - m_oldAudioDev = m_audioDev; - - if( _dev == NULL ) - { - printf( "param _dev == NULL in mixer::setAudioDevice(...). " - "Trying any working audio-device\n" ); - m_audioDev = tryAudioDevices(); - } - else - { - m_audioDev = _dev; - } - - emit qualitySettingsChanged(); - emit sampleRateChanged(); - - startProcessing( _needs_fifo ); -} - - - - -void mixer::restoreAudioDevice() -{ - if( m_oldAudioDev != NULL ) - { - stopProcessing(); - delete m_audioDev; - - m_audioDev = m_oldAudioDev; - emit sampleRateChanged(); - - m_oldAudioDev = NULL; - startProcessing(); - } -} - - - - -void mixer::removeAudioPort( AudioPort * _port ) +void Mixer::removeAudioPort( AudioPort * _port ) { QVector::Iterator it = qFind( m_audioPorts.begin(), m_audioPorts.end(), @@ -881,7 +527,7 @@ void mixer::removeAudioPort( AudioPort * _port ) -void mixer::removePlayHandle( playHandle * _ph ) +void Mixer::removePlayHandle( playHandle * _ph ) { lock(); // check thread affinity as we must not delete play-handles @@ -908,7 +554,7 @@ void mixer::removePlayHandle( playHandle * _ph ) -void mixer::removePlayHandles( track * _track, playHandle::Type _type ) +void Mixer::removePlayHandles( track * _track, playHandle::Type _type ) { lock(); PlayHandleList::Iterator it = m_playHandles.begin(); @@ -932,10 +578,10 @@ void mixer::removePlayHandles( track * _track, playHandle::Type _type ) -AudioDevice * mixer::tryAudioDevices() +AudioBackend * Mixer::tryAudioBackends() { bool success_ful = false; - AudioDevice * dev = NULL; + AudioBackend * dev = NULL; QString dev_name = configManager::inst()->value( "mixer", "audiodev" ); if( dev_name == AudioDummy::name() ) @@ -946,7 +592,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_ALSA if( dev_name == AudioAlsa::name() || dev_name == "" ) { - dev = new AudioAlsa( success_ful, this ); + dev = new AudioAlsa( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioAlsa::name(); @@ -960,7 +606,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_PORTAUDIO if( dev_name == AudioPortAudio::name() || dev_name == "" ) { - dev = new AudioPortAudio( success_ful, this ); + dev = new AudioPortAudio( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioPortAudio::name(); @@ -974,7 +620,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_PULSEAUDIO if( dev_name == AudioPulseAudio::name() || dev_name == "" ) { - dev = new AudioPulseAudio( success_ful, this ); + dev = new AudioPulseAudio( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioPulseAudio::name(); @@ -988,7 +634,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_OSS if( dev_name == AudioOss::name() || dev_name == "" ) { - dev = new AudioOss( success_ful, this ); + dev = new AudioOss( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioOss::name(); @@ -1002,7 +648,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_JACK if( dev_name == AudioJack::name() || dev_name == "" ) { - dev = new AudioJack( success_ful, this ); + dev = new AudioJack( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioJack::name(); @@ -1016,7 +662,7 @@ AudioDevice * mixer::tryAudioDevices() #ifdef LMMS_HAVE_SDL if( dev_name == AudioSdl::name() || dev_name == "" ) { - dev = new AudioSdl( success_ful, this ); + dev = new AudioSdl( success_ful, audioOutputContext() ); if( success_ful ) { m_audioDevName = AudioSdl::name(); @@ -1027,7 +673,7 @@ AudioDevice * mixer::tryAudioDevices() #endif // add more device-classes here... - //dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, this ); + //dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, audioOutputContext() ); //if( sucess_ful ) //{ // return dev; @@ -1040,13 +686,13 @@ AudioDevice * mixer::tryAudioDevices() m_audioDevName = AudioDummy::name(); - return new AudioDummy( success_ful, this ); + return new AudioDummy( success_ful, audioOutputContext() ); } -MidiClient * mixer::tryMidiClients() +MidiClient * Mixer::tryMidiClients() { QString client_name = configManager::inst()->value( "mixer", "mididev" ); @@ -1111,57 +757,5 @@ MidiClient * mixer::tryMidiClients() - - - - - - -mixer::fifoWriter::fifoWriter( mixer * _mixer, fifo * _fifo ) : - m_mixer( _mixer ), - m_fifo( _fifo ), - m_writing( true ) -{ -} - - - - -void mixer::fifoWriter::finish() -{ - m_writing = false; -} - - - - -void mixer::fifoWriter::run() -{ -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_PTHREAD_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( 0, &mask ); - pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); -#endif -#endif -#endif - - const fpp_t frames = m_mixer->framesPerPeriod(); - while( m_writing ) - { - sampleFrameA * buffer = CPU::allocFrames( frames ); - const sampleFrameA * b = m_mixer->renderNextBuffer(); - CPU::memCpy( buffer, b, frames * sizeof( sampleFrameA ) ); - m_fifo->write( buffer ); - } - - m_fifo->write( NULL ); -} - - - - -#include "moc_mixer.cxx" +#include "moc_Mixer.cxx" diff --git a/src/core/MixerWorkerThread.cpp b/src/core/MixerWorkerThread.cpp new file mode 100644 index 000000000..a6ab3ab59 --- /dev/null +++ b/src/core/MixerWorkerThread.cpp @@ -0,0 +1,166 @@ +/* + * MixerWorkerThread.cpp - implementation of MixerWorkerThread + * + * Copyright (c) 2009 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 "MixerWorkerThread.h" +#include "Cpu.h" +#include "engine.h" + + +MixerWorkerThread::JobQueue MixerWorkerThread::globalJobQueue; +QWaitCondition * MixerWorkerThread::queueReadyWaitCond = NULL; +QList MixerWorkerThread::workerThreads; + + + +// implementation of internal JobQueue +void MixerWorkerThread::JobQueue::reset( OperationMode _opMode ) +{ + m_queueSize = 0; + m_itemsDone = 0; + m_opMode = _opMode; +} + + + + +void MixerWorkerThread::JobQueue::addJob( ThreadableJob * _job ) +{ + if( _job->requiresProcessing() ) + { + // update job state + _job->queue(); + // actually queue the job via atomic operations + m_items[m_queueSize.fetchAndAddOrdered(1)] = _job; + } +} + + + +void MixerWorkerThread::JobQueue::run( sampleFrame * _buffer ) +{ + bool processedJob = true; + while( processedJob && (int) m_itemsDone < (int) m_queueSize ) + { + processedJob = false; + for( int i = 0; i < m_queueSize; ++i ) + { + ThreadableJob * job = m_items[i].fetchAndStoreOrdered( NULL ); + if( job ) + { + job->process( _buffer ); + processedJob = true; + m_itemsDone.fetchAndAddOrdered( 1 ); + } + } + // always exit loop if we're not in dynamic mode + processedJob = processedJob && ( m_opMode == Dynamic ); + } +} + + + + +void MixerWorkerThread::JobQueue::wait() +{ + while( (int) m_itemsDone < (int) m_queueSize ) + { +#if defined(LMMS_HOST_X86) || defined(LMMS_HOST_X86_64) + asm( "pause" ); +#endif + } +} + + + + + +// implementation of worker threads + +MixerWorkerThread::MixerWorkerThread( Mixer * _mixer ) : + QThread( _mixer ), + m_workingBuf( CPU::allocFrames( _mixer->framesPerPeriod() ) ), + m_quit( false ) +{ + // initialize global static data + if( queueReadyWaitCond == NULL ) + { + queueReadyWaitCond = new QWaitCondition; + } + + // keep track of all instantiated worker threads - this is used for + // processing the last worker thread "inline", see comments in + // MixerWorkerThread::startAndWaitForJobs() for details + workerThreads << this; + + resetJobQueue(); +} + + + + +MixerWorkerThread::~MixerWorkerThread() +{ + CPU::freeFrames( m_workingBuf ); + + workerThreads.removeAll( this ); +} + + + + +void MixerWorkerThread::quit() +{ + m_quit = true; + resetJobQueue(); +} + + + + +void MixerWorkerThread::startAndWaitForJobs() +{ + queueReadyWaitCond->wakeAll(); + // 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.wait(); +} + + + + +void MixerWorkerThread::run() +{ + QMutex m; + while( m_quit == false ) + { + m.lock(); + queueReadyWaitCond->wait( &m ); + globalJobQueue.run( m_workingBuf ); + m.unlock(); + } +} + + diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index ed00ef643..7b5c8a60c 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -24,7 +24,7 @@ #include "Oscillator.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" #include "AutomatableModel.h" @@ -55,9 +55,9 @@ Oscillator::Oscillator( const IntModel * _wave_shape_model, void Oscillator::update( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { - if( m_freq >= engine::getMixer()->processingSampleRate() / 2 ) + if( m_freq >= engine::mixer()->processingSampleRate() / 2 ) { - mixer::clearAudioBuffer( _ab, _frames ); + Mixer::clearAudioBuffer( _ab, _frames ); return; } if( m_subOsc != NULL ) @@ -456,7 +456,7 @@ void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, recalcPhase(); const float osc_coeff = m_freq * m_detuning; const float sampleRateCorrection = 44100.0f / - engine::getMixer()->processingSampleRate(); + engine::mixer()->processingSampleRate(); for( fpp_t frame = 0; frame < _frames; ++frame ) { diff --git a/src/core/PeakController.cpp b/src/core/PeakController.cpp index 208304fb2..10ab65acb 100644 --- a/src/core/PeakController.cpp +++ b/src/core/PeakController.cpp @@ -32,7 +32,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "PeakController.h" #include "ControllerDialog.h" #include "plugins/peak_controller_effect/peak_controller_effect.h" diff --git a/src/core/Plugin.cpp b/src/core/Plugin.cpp index 9836bdef2..c50521c37 100644 --- a/src/core/Plugin.cpp +++ b/src/core/Plugin.cpp @@ -29,7 +29,6 @@ #include "Plugin.h" #include "embed.h" #include "engine.h" -#include "mixer.h" #include "config_mgr.h" #include "DummyPlugin.h" #include "AutomatableModel.h" diff --git a/src/core/ProjectRenderer.cpp b/src/core/ProjectRenderer.cpp index 63de13509..1633a4ec1 100644 --- a/src/core/ProjectRenderer.cpp +++ b/src/core/ProjectRenderer.cpp @@ -22,7 +22,6 @@ * */ - #include #include @@ -60,9 +59,9 @@ FileEncodeDevice __fileEncodeDevices[] = ".mp3", &AudioFileMp3::getInst }, { ProjectRenderer::FlacFile, QT_TRANSLATE_NOOP( "ProjectRenderer", "FLAC File (*.flac)" ), - ".flac", + ".flac", #ifdef LMMS_HAVE_FLAC - &AudioFileFlac::getInst + &AudioFileFlac::getInst #else NULL #endif @@ -77,36 +76,40 @@ FileEncodeDevice __fileEncodeDevices[] = const char * ProjectRenderer::EFF_ext[] = {"wav", "ogg", "mp3", "flac"}; -ProjectRenderer::ProjectRenderer( const mixer::qualitySettings & _qs, - const OutputSettings & _os, - ExportFileFormats _file_format, - const QString & _out_file ) : +ProjectRenderer::ProjectRenderer( + const AudioOutputContext::QualitySettings & _qs, + const EncoderSettings & es, + ExportFileFormats fileFormat, + const QString & outFile ) : QThread( engine::getMixer() ), m_fileDev( NULL ), - m_qualitySettings( _qs ), - m_oldQualitySettings( engine::getMixer()->currentQualitySettings() ), m_progress( 0 ), m_abort( false ) { - if( __fileEncodeDevices[_file_format].m_getDevInst == NULL ) + m_context = new AudioOutputContext( engine::getMixer(), + NULL, + _qs ); + if( __fileEncodeDevices[fileFormat].m_getDevInst == NULL ) { return; } bool success_ful = false; - m_fileDev = __fileEncodeDevices[_file_format].m_getDevInst( - _os.samplerate, DEFAULT_CHANNELS, success_ful, - _out_file, _os.vbr, - _os.bitrate, _os.bitrate - 64, _os.bitrate + 64, - _os.depth == Depth_32Bit ? 32 : - ( _os.depth == Depth_24Bit ? 24 : 16 ), - engine::getMixer() ); + m_fileDev = __fileEncodeDevices[fileFormat].m_getDevInst( + es.samplerate, DEFAULT_CHANNELS, success_ful, + outFile, es.vbr, + es.bitrate, es.bitrate - 64, es.bitrate + 64, + es.depth == Depth_32Bit ? 32 : + ( es.depth == Depth_24Bit ? 24 : 16 ), + m_context ); if( success_ful == false ) { delete m_fileDev; m_fileDev = NULL; } + m_context->setAudioBackend( m_fileDev ); + } @@ -114,6 +117,8 @@ ProjectRenderer::ProjectRenderer( const mixer::qualitySettings & _qs, ProjectRenderer::~ProjectRenderer() { + delete m_fileDev; + delete m_context; } @@ -129,12 +134,12 @@ ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( { if( QString( __fileEncodeDevices[idx].m_extension ) == _ext ) { - return( __fileEncodeDevices[idx].m_fileFormat ); + return __fileEncodeDevices[idx].m_fileFormat; } ++idx; } - return( WaveFile ); // default + return WaveFile; // default } @@ -144,66 +149,14 @@ void ProjectRenderer::startProcessing() { if( isReady() ) { + connect( this, SIGNAL( finished() ), this, SLOT( finishProcessing() ) ); + // have to do mixer stuff with GUI-thread-affinity in order to // make slots connected to sampleRateChanged()-signals being // called immediately - engine::getMixer()->setAudioDevice( m_fileDev, - m_qualitySettings, false ); + engine::mixer()->setAudioOutputContext( m_context ); - start( -#ifndef LMMS_BUILD_WIN32 - QThread::HighPriority -#endif - ); - } -} - - - -void ProjectRenderer::run() -{ -#if 0 -#ifdef LMMS_BUILD_LINUX -#ifdef LMMS_HAVE_PTHREAD_H - cpu_set_t mask; - CPU_ZERO( &mask ); - CPU_SET( 0, &mask ); - pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); -#endif -#endif -#endif - - engine::getSong()->startExport(); - - song::playPos & pp = engine::getSong()->getPlayPos( - song::Mode_PlaySong ); - m_progress = 0; - const int sl = ( engine::getSong()->length() + 1 ) * 192; - - while( engine::getSong()->isExportDone() == false && - engine::getSong()->isExporting() == true - && !m_abort ) - { - m_fileDev->processNextBuffer(); - const int nprog = pp * 100 / sl; - if( m_progress != nprog ) - { - m_progress = nprog; - emit progressChanged( m_progress ); - } - } - - engine::getSong()->stopExport(); - - const QString f = m_fileDev->outputFile(); - - engine::getMixer()->restoreAudioDevice(); // also deletes audio-dev - engine::getMixer()->changeQuality( m_oldQualitySettings ); - - // if the user aborted export-process, the file has to be deleted - if( m_abort ) - { - QFile( f ).remove(); + start(); } } @@ -217,6 +170,7 @@ void ProjectRenderer::abortProcessing() + void ProjectRenderer::updateConsoleProgress() { const int cols = 50; @@ -224,7 +178,8 @@ void ProjectRenderer::updateConsoleProgress() char buf[80]; char prog[cols+1]; - if( m_fileDev == NULL ){ + if( m_fileDev == NULL ) + { qWarning("Error occured. Aborting render."); m_consoleUpdateTimer->stop(); delete m_consoleUpdateTimer; @@ -250,5 +205,68 @@ void ProjectRenderer::updateConsoleProgress() + +void ProjectRenderer::run() +{ +#if 0 +#ifdef LMMS_BUILD_LINUX +#ifdef LMMS_HAVE_PTHREAD_H + cpu_set_t mask; + CPU_ZERO( &mask ); + CPU_SET( 0, &mask ); + pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); +#endif +#endif +#endif + + // have to lock Mixer when touching Song's state as the FIFO writer thread + // may call Mixer::renderNextBuffer() (which calls Song::doActions()) + // simultaneously + engine::mixer()->lock(); + engine::getSong()->startExport(); + engine::mixer()->unlock(); + + song::playPos & pp = engine::getSong()->getPlayPos( + song::Mode_PlaySong ); + m_progress = 0; + const int sl = ( engine::getSong()->length() + 1 ) * 192; + + while( engine::getSong()->isExportDone() == false && + engine::getSong()->isExporting() == true + && !m_abort ) + { + m_fileDev->processNextBuffer(); + const int nprog = pp * 100 / sl; + if( m_progress != nprog ) + { + m_progress = nprog; + emit progressChanged( m_progress ); + } + } + + engine::mixer()->lock(); + engine::getSong()->stopExport(); + engine::mixer()->unlock(); +} + + + + +void ProjectRenderer::finishProcessing() +{ + const QString f = m_fileDev->outputFile(); + + engine::mixer()->setAudioOutputContext( + engine::mixer()->defaultAudioOutputContext() ); + + // if the user aborted export-process, the file has to be deleted + if( m_abort ) + { + QFile( f ).remove(); + } +} + + + #include "moc_ProjectRenderer.cxx" diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index e712cda80..a711d27cd 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -29,7 +29,7 @@ #endif #include "RemotePlugin.h" -#include "mixer.h" +#include "Mixer.h" #include "engine.h" #include "config_mgr.h" diff --git a/src/core/audio/AudioAlsa.cpp b/src/core/audio/AudioAlsa.cpp index b119e619a..077f5cfef 100644 --- a/src/core/audio/AudioAlsa.cpp +++ b/src/core/audio/AudioAlsa.cpp @@ -40,11 +40,11 @@ -AudioAlsa::AudioAlsa( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( +AudioAlsa::AudioAlsa( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audioalsa", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_handle( NULL ), m_hwParams( NULL ), m_swParams( NULL ), @@ -201,7 +201,7 @@ void AudioAlsa::applyQualitySettings() { if( hqAudio() ) { - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); if( m_handle != NULL ) { @@ -233,8 +233,6 @@ void AudioAlsa::applyQualitySettings() return; } } - - AudioDevice::applyQualitySettings(); } @@ -242,16 +240,15 @@ void AudioAlsa::applyQualitySettings() void AudioAlsa::run() { - sampleFrameA * temp = CPU::allocFrames( - getMixer()->framesPerPeriod() ); + sampleFrameA * temp = CPU::allocFrames( mixer()->framesPerPeriod() ); intSampleFrameA * outbuf = (intSampleFrameA *) CPU::memAlloc( sizeof( intSampleFrameA ) * channels() / - DEFAULT_CHANNELS * getMixer()->framesPerPeriod() ); + DEFAULT_CHANNELS * mixer()->framesPerPeriod() ); int_sample_t * pcmbuf = new int_sample_t[m_periodSize * channels()]; - int outbuf_size = getMixer()->framesPerPeriod() * channels(); + int outbuf_size = mixer()->framesPerPeriod() * channels(); int outbuf_pos = 0; int pcmbuf_size = m_periodSize * channels(); @@ -274,7 +271,7 @@ void AudioAlsa::run() outbuf_size = frames * channels(); CPU::convertToS16( temp, outbuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); } int min_len = qMin( len, outbuf_size - outbuf_pos ); @@ -374,7 +371,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) sampleRate(), 0 ) ) < 0 ) { if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams, - getMixer()->baseSampleRate(), 0 ) ) < 0 ) + mixer()->baseSampleRate(), 0 ) ) < 0 ) { printf( "Could not set sample rate: %s\n", snd_strerror( err ) ); @@ -382,7 +379,7 @@ int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) } } - m_periodSize = getMixer()->framesPerPeriod(); + m_periodSize = mixer()->framesPerPeriod(); m_bufferSize = m_periodSize * 8; dir = 0; err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams, @@ -493,7 +490,7 @@ int AudioAlsa::setSWParams() AudioAlsa::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioAlsa::name(), _parent ) + AudioBackend::setupWidget( AudioAlsa::name(), _parent ) { m_device = new QComboBox( this ); diff --git a/src/core/audio/AudioBackend.cpp b/src/core/audio/AudioBackend.cpp new file mode 100644 index 000000000..0b3607049 --- /dev/null +++ b/src/core/audio/AudioBackend.cpp @@ -0,0 +1,144 @@ +/* + * AudioBackend.cpp - base-class for audio-devices used by LMMS-mixer + * + * Copyright (c) 2004-2009 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 "AudioBackend.h" +#include "AudioOutputContext.h" +#include "config_mgr.h" +#include "debug.h" +#include "Cpu.h" + + + +AudioBackend::AudioBackend( const ch_cnt_t _channels, + AudioOutputContext * context ) : + m_supportsCapture( false ), + m_context( context ), + m_sampleRate( mixer()->processingSampleRate() ), + m_channels( _channels ), + m_buffer( CPU::allocFrames( mixer()->framesPerPeriod() ) ) +{ +} + + + + +AudioBackend::~AudioBackend() +{ + CPU::freeFrames( m_buffer ); +} + + + + +int AudioBackend::processNextBuffer() +{ + const int frames = getNextBuffer( m_buffer ); + if( frames ) + { + writeBuffer( m_buffer, frames, mixer()->masterGain() ); + } + return frames; +} + + + + +int AudioBackend::getNextBuffer( sampleFrameA * _ab ) +{ + return outputContext()->getCurrentOutputBuffer( _ab, sampleRate() ); +} + + + + +void AudioBackend::stopProcessing() +{ + // flush AudioOutputContext's FIFO + while( processNextBuffer() ) + { + } +} + + + + +void AudioBackend::applyQualitySettings() +{ +} + + + + +void AudioBackend::registerPort( AudioPort * ) +{ +} + + + + +void AudioBackend::unregisterPort( AudioPort * _port ) +{ +} + + + + +void AudioBackend::renamePort( AudioPort * ) +{ +} + + + + +void AudioBackend::clearS16Buffer( intSampleFrameA * _outbuf, const fpp_t _frames ) +{ + CPU::memClear( _outbuf, _frames * sizeof( *_outbuf ) ); +// memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE ); +} + + + + +bool AudioBackend::hqAudio() const +{ + return configManager::inst()->value( "mixer", "hqaudio" ).toInt(); +} + + + + +const Mixer * AudioBackend::mixer() const +{ + return outputContext()->mixer(); +} + + + + +Mixer * AudioBackend::mixer() +{ + return outputContext()->mixer(); +} + + diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp deleted file mode 100644 index ba38a227f..000000000 --- a/src/core/audio/AudioDevice.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* - * AudioDevice.cpp - base-class for audio-devices used by LMMS-mixer - * - * Copyright (c) 2004-2009 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 "AudioDevice.h" -#include "config_mgr.h" -#include "debug.h" -#include "Cpu.h" - - - -AudioDevice::AudioDevice( const ch_cnt_t _channels, mixer * _mixer ) : - m_supportsCapture( false ), - m_sampleRate( _mixer->processingSampleRate() ), - m_channels( _channels ), - m_mixer( _mixer ), - m_buffer( CPU::allocFrames( getMixer()->framesPerPeriod() ) ) -{ - int error; - if( ( m_srcState = src_new( - getMixer()->currentQualitySettings().libsrcInterpolation(), - SURROUND_CHANNELS, &error ) ) == NULL ) - { - printf( "Error: src_new() failed in audio_device.cpp!\n" ); - } -} - - - - -AudioDevice::~AudioDevice() -{ - src_delete( m_srcState ); - CPU::freeFrames( m_buffer ); - - m_devMutex.tryLock(); - unlock(); -} - - - - -void AudioDevice::processNextBuffer() -{ - const fpp_t frames = getNextBuffer( m_buffer ); - if( frames ) - { - writeBuffer( m_buffer, frames, getMixer()->masterGain() ); - } - else - { - m_inProcess = false; - } -} - - - - -fpp_t AudioDevice::getNextBuffer( sampleFrameA * _ab ) -{ - fpp_t frames = getMixer()->framesPerPeriod(); - sampleFrameA * b = getMixer()->nextBuffer(); - if( !b ) - { - return 0; - } - - // make sure, no other thread is accessing device - lock(); - - // resample if neccessary - if( getMixer()->processingSampleRate() != m_sampleRate ) - { - resample( b, frames, _ab, getMixer()->processingSampleRate(), - m_sampleRate ); - frames = frames * m_sampleRate / - getMixer()->processingSampleRate(); - } - else - { - CPU::memCpy( _ab, b, frames * sizeof( surroundSampleFrame ) ); - } - - // release lock - unlock(); - - if( getMixer()->hasFifoWriter() ) - { - CPU::freeFrames( b ); - } - - return frames; -} - - - - -void AudioDevice::stopProcessing() -{ - if( getMixer()->hasFifoWriter() ) - { - while( m_inProcess ) - { - processNextBuffer(); - } - } -} - - - - -void AudioDevice::applyQualitySettings() -{ - src_delete( m_srcState ); - - int error; - if( ( m_srcState = src_new( - getMixer()->currentQualitySettings().libsrcInterpolation(), - SURROUND_CHANNELS, &error ) ) == NULL ) - { - printf( "Error: src_new() failed in audio_device.cpp!\n" ); - } -} - - - - -void AudioDevice::registerPort( AudioPort * ) -{ -} - - - - -void AudioDevice::unregisterPort( AudioPort * _port ) -{ -} - - - - -void AudioDevice::renamePort( AudioPort * ) -{ -} - - - - -void AudioDevice::resample( const sampleFrame * _src, const fpp_t _frames, - sampleFrame * _dst, - const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ) -{ - if( m_srcState == NULL ) - { - return; - } - m_srcData.input_frames = _frames; - m_srcData.output_frames = _frames; - m_srcData.data_in = (float *) _src[0]; - m_srcData.data_out = _dst[0]; - m_srcData.src_ratio = (double) _dst_sr / _src_sr; - m_srcData.end_of_input = 0; - int error; - if( ( error = src_process( m_srcState, &m_srcData ) ) ) - { - printf( "AudioDevice::resample(): error while resampling: %s\n", - src_strerror( error ) ); - } -} - - - -void AudioDevice::clearS16Buffer( intSampleFrameA * _outbuf, const fpp_t _frames ) -{ - CPU::memClear( _outbuf, _frames * sizeof( *_outbuf ) ); -// memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE ); -} - - - - -bool AudioDevice::hqAudio() const -{ - return configManager::inst()->value( "mixer", "hqaudio" ).toInt(); -} - - diff --git a/src/core/audio/AudioFileDevice.cpp b/src/core/audio/AudioFileDevice.cpp index e57c2a883..e92f41b47 100644 --- a/src/core/audio/AudioFileDevice.cpp +++ b/src/core/audio/AudioFileDevice.cpp @@ -39,8 +39,8 @@ AudioFileDevice::AudioFileDevice( const sample_rate_t _sample_rate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) : - AudioDevice( _channels, _mixer ), + AudioOutputContext * context ) : + AudioBackend( _channels, context ), m_outputFile( _file ), m_useVbr( _use_vbr ), m_nomBitrate( _nom_bitrate ), diff --git a/src/core/audio/AudioFileFlac.cpp b/src/core/audio/AudioFileFlac.cpp index cfcc7fc51..77e2fa731 100644 --- a/src/core/audio/AudioFileFlac.cpp +++ b/src/core/audio/AudioFileFlac.cpp @@ -37,9 +37,9 @@ AudioFileFlac::AudioFileFlac( const sample_rate_t _sample_rate, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, - const int _depth, mixer * _mixer ) : + const int _depth, AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, _depth, _mixer ) + _min_bitrate, _max_bitrate, _depth, context ) { _success_ful = startEncoding(); } diff --git a/src/core/audio/AudioFileMp3.cpp b/src/core/audio/AudioFileMp3.cpp index e9569c99b..72252066e 100644 --- a/src/core/audio/AudioFileMp3.cpp +++ b/src/core/audio/AudioFileMp3.cpp @@ -38,9 +38,9 @@ AudioFileMp3::AudioFileMp3( const sample_rate_t _sample_rate, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, - const int _depth, mixer * _mixer ) : + const int _depth, AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, - _min_bitrate, _max_bitrate, _depth, _mixer ), + _min_bitrate, _max_bitrate, _depth, context ), m_lgf( NULL ), m_lame( LameLibrary() ), m_outfile( NULL ), diff --git a/src/core/audio/AudioFileOgg.cpp b/src/core/audio/AudioFileOgg.cpp index 3ef2856e7..781a3468a 100644 --- a/src/core/audio/AudioFileOgg.cpp +++ b/src/core/audio/AudioFileOgg.cpp @@ -44,10 +44,10 @@ AudioFileOgg::AudioFileOgg( const sample_rate_t _sample_rate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) : + AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ) + _depth, context ) { m_ok = _success_ful = startEncoding(); } diff --git a/src/core/audio/AudioFileWave.cpp b/src/core/audio/AudioFileWave.cpp index b15ab339e..f52b784dd 100644 --- a/src/core/audio/AudioFileWave.cpp +++ b/src/core/audio/AudioFileWave.cpp @@ -36,10 +36,10 @@ AudioFileWave::AudioFileWave( const sample_rate_t _sample_rate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, - mixer * _mixer ) : + AudioOutputContext * context ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, - _depth, _mixer ) + _depth, context ) { _success_ful = startEncoding(); } @@ -59,7 +59,7 @@ bool AudioFileWave::startEncoding() { m_si.samplerate = sampleRate(); m_si.channels = channels(); - m_si.frames = getMixer()->framesPerPeriod(); + m_si.frames = mixer()->framesPerPeriod(); m_si.sections = 1; m_si.seekable = 0; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 737f5d462..9eb0856f5 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -45,15 +45,15 @@ -AudioJack::AudioJack( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( configManager::inst()->value( +AudioJack::AudioJack( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audiojack", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_client( NULL ), m_active( false ), m_stopSemaphore( 1 ), - m_outBuf( CPU::allocFrames( getMixer()->framesPerPeriod() ) ), + m_outBuf( CPU::allocFrames( mixer()->framesPerPeriod() ) ), m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) { @@ -210,7 +210,7 @@ void AudioJack::startProcessing() // try to sync JACK's and LMMS's buffer-size -// jack_set_buffer_size( m_client, getMixer()->framesPerPeriod() ); +// jack_set_buffer_size( m_client, mixer()->framesPerPeriod() ); @@ -255,15 +255,13 @@ void AudioJack::applyQualitySettings() { if( hqAudio() ) { - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); if( jack_get_sample_rate( m_client ) != sampleRate() ) { setSampleRate( jack_get_sample_rate( m_client ) ); } } - - AudioDevice::applyQualitySettings(); } @@ -343,7 +341,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) #ifdef AUDIO_PORT_SUPPORT const Uint32 frames = qMin( _nframes, - getMixer()->framesPerPeriod() ); + mixer()->framesPerPeriod() ); for( jackPortMap::iterator it = m_portMap.begin(); it != m_portMap.end(); ++it ) { @@ -372,7 +370,7 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) _nframes, m_framesToDoInCurBuf - m_framesDoneInCurBuf ); - const float gain = getMixer()->masterGain(); + const float gain = mixer()->masterGain(); for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { jack_default_audio_sample_t * o = outbufs[chnl]; @@ -434,7 +432,7 @@ void AudioJack::shutdownCallback( void * _udata ) AudioJack::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioJack::name(), _parent ) + AudioBackend::setupWidget( AudioJack::name(), _parent ) { QString cn = configManager::inst()->value( "audiojack", "clientname" ); if( cn.isEmpty() ) diff --git a/src/core/audio/AudioOss.cpp b/src/core/audio/AudioOss.cpp index 81cbfc05d..05dbf560b 100644 --- a/src/core/audio/AudioOss.cpp +++ b/src/core/audio/AudioOss.cpp @@ -74,11 +74,11 @@ -AudioOss::AudioOss( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( +AudioOss::AudioOss( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audiooss", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_convertEndian( false ) { _success_ful = false; @@ -106,7 +106,7 @@ AudioOss::AudioOss( bool & _success_ful, mixer * _mixer ) : int frag_spec; for( frag_spec = 0; static_cast( 0x01 << frag_spec ) < - getMixer()->framesPerPeriod() * channels() * + mixer()->framesPerPeriod() * channels() * BYTES_PER_INT_SAMPLE; ++frag_spec ) { @@ -178,7 +178,7 @@ AudioOss::AudioOss( bool & _success_ful, mixer * _mixer ) : } if( value != sampleRate() ) { - value = getMixer()->baseSampleRate(); + value = mixer()->baseSampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); @@ -271,7 +271,7 @@ void AudioOss::applyQualitySettings() { if( hqAudio() ) { - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); unsigned int value = sampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) @@ -282,7 +282,7 @@ void AudioOss::applyQualitySettings() } if( value != sampleRate() ) { - value = getMixer()->baseSampleRate(); + value = mixer()->baseSampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); @@ -292,8 +292,6 @@ void AudioOss::applyQualitySettings() setSampleRate( value ); } } - - AudioDevice::applyQualitySettings(); } @@ -302,10 +300,10 @@ void AudioOss::applyQualitySettings() void AudioOss::run() { sampleFrameA * temp = CPU::allocFrames( - getMixer()->framesPerPeriod() ); + mixer()->framesPerPeriod() ); intSampleFrameA * outbuf = (intSampleFrameA *) CPU::memAlloc( sizeof( intSampleFrameA ) * - getMixer()->framesPerPeriod() ); + mixer()->framesPerPeriod() ); while( true ) { @@ -316,7 +314,7 @@ void AudioOss::run() } int bytes = CPU::convertToS16( temp, outbuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); if( write( m_audioFD, outbuf, bytes ) != bytes ) { @@ -332,7 +330,7 @@ void AudioOss::run() AudioOss::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioOss::name(), _parent ) + AudioBackend::setupWidget( AudioOss::name(), _parent ) { m_device = new QLineEdit( probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); diff --git a/src/core/audio/AudioOutputContext.cpp b/src/core/audio/AudioOutputContext.cpp new file mode 100644 index 000000000..ae3187e60 --- /dev/null +++ b/src/core/audio/AudioOutputContext.cpp @@ -0,0 +1,311 @@ +/* + * AudioOutputContext.cpp - centralize all audio output related functionality + * + * Copyright (c) 2009 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 "AudioBackend.h" +#include "AudioOutputContext.h" +#include "Cpu.h" + +#include "config_mgr.h" +#include "engine.h" + +AudioOutputContext::BufferFifo::BufferFifo( int _size, int _bufferSize ) : + m_readerSem( _size ), + m_writerSem( _size ), + m_readerIndex( 0 ), + m_writerIndex( 0 ), + m_size( _size ), + m_bufferSize( _bufferSize ) +{ + m_buffers = new sampleFrameA *[m_size]; + for( int i = 0; i < m_size; ++i ) + { + m_buffers[i] = CPU::allocFrames( m_bufferSize ); + } + + m_bufferStates = new BufferState[m_size]; + + m_readerSem.acquire( _size ); +} + + + +AudioOutputContext::BufferFifo::~BufferFifo() +{ + for( int i = 0; i < m_size; ++i ) + { + CPU::freeFrames( m_buffers[i] ); + } + + delete[] m_buffers; + delete[] m_bufferStates; + + m_readerSem.release( m_size ); +} + + + + +void AudioOutputContext::BufferFifo::write( sampleFrameA * _buffer ) +{ + m_writerSem.acquire(); + + if( _buffer != NULL ) + { + CPU::memCpy( m_buffers[m_writerIndex], _buffer, + m_bufferSize * sizeof( sampleFrameA ) ); + m_bufferStates[m_writerIndex] = Running; + } + else + { + m_bufferStates[m_writerIndex] = NullBuffer; + } + + m_writerIndex = ( m_writerIndex + 1 ) % m_size; + + m_readerSem.release(); +} + + + + +void AudioOutputContext::BufferFifo::startRead() +{ + m_readerSem.acquire(); +} + + + + +void AudioOutputContext::BufferFifo::finishRead() +{ + m_readerIndex = ( m_readerIndex + 1 ) % m_size; + m_writerSem.release(); +} + + + + + + + +AudioOutputContext::AudioOutputContext( Mixer * mixer, + AudioBackend * audioBackend, + const QualitySettings & qualitySettings ) : + m_mixer( mixer ), + m_qualitySettings( qualitySettings ), + m_audioBackend( audioBackend ), + m_fifo( NULL ), + m_fifoWriter( NULL ) +{ + int error; + if( ( m_srcState = src_new( + qualitySettings.libsrcInterpolation(), + SURROUND_CHANNELS, &error ) ) == NULL ) + { + qWarning( "src_new() failed in AudioOutputContext::AudioOutputContext()" ); + } + //m_audioBackend->applyQualitySettings(); + + int framesPerPeriod = m_mixer->framesPerPeriod(); + + // just rendering? + if( !engine::hasGUI() ) + { + m_fifo = new BufferFifo( 1, framesPerPeriod ); + } + else if( configManager::inst()->value( "mixer", "framesperaudiobuffer" + ).toInt() >= 32 ) + { + framesPerPeriod = + (fpp_t) configManager::inst()->value( "mixer", + "framesperaudiobuffer" ).toInt(); + + if( framesPerPeriod > DEFAULT_BUFFER_SIZE ) + { + m_fifo = new BufferFifo( framesPerPeriod / DEFAULT_BUFFER_SIZE, + DEFAULT_BUFFER_SIZE ); + } + else + { + m_fifo = new BufferFifo( 1, framesPerPeriod ); + } + } + else + { + configManager::inst()->setValue( "mixer", + "framesperaudiobuffer", + QString::number( framesPerPeriod ) ); + m_fifo = new BufferFifo( 1, framesPerPeriod ); + } + +} + + + + +AudioOutputContext::~AudioOutputContext() +{ + while( m_fifo->isEmpty() == false ) + { + m_fifo->startRead(); + m_fifo->finishRead(); + } + delete m_fifo; + + src_delete( m_srcState ); +} + + + + +void AudioOutputContext::startProcessing() +{ + if( !isProcessing() ) + { + m_fifoWriter = new FifoWriter( this ); + m_fifoWriter->start( QThread::HighPriority ); + + m_audioBackend->startProcessing(); + } +} + + + + +void AudioOutputContext::stopProcessing() +{ + if( isProcessing() ) + { + m_fifoWriter->finish(); + m_audioBackend->stopProcessing(); + m_fifoWriter->wait(); + + delete m_fifoWriter; + m_fifoWriter = NULL; + } +} + + + + +bool AudioOutputContext::isProcessing() const +{ + return m_fifoWriter && m_fifoWriter->isRunning(); +} + + + + +int AudioOutputContext::getCurrentOutputBuffer( sampleFrameA * _destBuf, + sample_rate_t _destSampleRate ) +{ + int frames = mixer()->framesPerPeriod(); + m_fifo->startRead(); + if( m_fifo->currentReadBufferState() == BufferFifo::NullBuffer ) + { + m_fifo->finishRead(); + return 0; + } + sampleFrameA * srcBuf = m_fifo->currentReadBuffer(); + + if( mixer()->processingSampleRate() != _destSampleRate ) + { + if( m_srcState == NULL ) + { + m_fifo->finishRead(); + return 0; + } + m_srcData.input_frames = frames; + m_srcData.output_frames = frames; + m_srcData.data_in = (float *) srcBuf; + m_srcData.data_out = (float *) _destBuf; + m_srcData.src_ratio = (double) _destSampleRate / + mixer()->processingSampleRate(); + m_srcData.end_of_input = 0; + int error; + if( ( error = src_process( m_srcState, &m_srcData ) ) ) + { + qWarning( "AudioBackend::resample(): error while resampling: %s", + src_strerror( error ) ); + } + frames = frames * _destSampleRate / mixer()->processingSampleRate(); + } + else + { + CPU::memCpy( _destBuf, srcBuf, frames * sizeof( sampleFrameA ) ); + } + + // tell BufferFifo to release current read buffer + m_fifo->finishRead(); + + return frames; +} + + + + + + +AudioOutputContext::FifoWriter::FifoWriter( AudioOutputContext * context ) : + m_context( context ), + m_writing( true ) +{ +} + + + + +void AudioOutputContext::FifoWriter::finish() +{ + m_writing = false; +} + + + + +void AudioOutputContext::FifoWriter::run() +{ +#if 0 +#ifdef LMMS_BUILD_LINUX +#ifdef LMMS_HAVE_PTHREAD_H + cpu_set_t mask; + CPU_ZERO( &mask ); + CPU_SET( 0, &mask ); + pthread_setaffinity_np( pthread_self(), sizeof( mask ), &mask ); +#endif +#endif +#endif + + while( m_writing ) + { + m_context->fifo()->write( m_context->mixer()->renderNextBuffer() ); + } + + // write a NULL in order to signal the AudioBackend that the FifoWriter has + // finished + m_context->fifo()->write( NULL ); +} + + + diff --git a/src/core/audio/AudioPort.cpp b/src/core/audio/AudioPort.cpp index 2b4301178..0f62e7e90 100644 --- a/src/core/audio/AudioPort.cpp +++ b/src/core/audio/AudioPort.cpp @@ -22,11 +22,13 @@ * */ +#include "AudioBackend.h" +#include "AudioOutputContext.h" #include "AudioPort.h" -#include "AudioDevice.h" -#include "EffectChain.h" -#include "engine.h" #include "Cpu.h" +#include "EffectChain.h" +#include "FxMixer.h" +#include "engine.h" AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) : @@ -88,11 +90,13 @@ void AudioPort::setExtOutputEnabled( bool _enabled ) m_extOutputEnabled = _enabled; if( m_extOutputEnabled ) { - engine::getMixer()->audioDev()->registerPort( this ); + engine::mixer()->audioOutputContext()-> + audioBackend()->registerPort( this ); } else { - engine::getMixer()->audioDev()->unregisterPort( this ); + engine::mixer()->audioOutputContext()-> + audioBackend()->unregisterPort( this ); } } } @@ -103,7 +107,7 @@ void AudioPort::setExtOutputEnabled( bool _enabled ) void AudioPort::setName( const QString & _name ) { m_name = _name; - engine::getMixer()->audioDev()->renamePort( this ); + engine::mixer()->audioOutputContext()->audioBackend()->renamePort( this ); } @@ -123,3 +127,15 @@ bool AudioPort::processEffects() } + + +void AudioPort::doProcessing( sampleFrame * ) +{ + const bool me = processEffects(); + if( me || m_bufferUsage != NoUsage ) + { + engine::fxMixer()->mixToChannel( firstBuffer(), nextFxChannel() ); + nextPeriod(); + } +} + diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index 3e0713252..e2742d9b5 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -50,7 +50,7 @@ void AudioPortAudioSetupUtil::updateChannels() AudioPortAudio::AudioPortAudio( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( + AudioBackend( tLimit( configManager::inst()->value( "audioportaudio", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), @@ -284,8 +284,6 @@ void AudioPortAudio::applyQualitySettings() return; } } - - audioDevice::applyQualitySettings(); } int AudioPortAudio::process_callback( diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 539b1fba5..6bc621b20 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -46,11 +46,11 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) -AudioPulseAudio::AudioPulseAudio( bool & _success_ful, mixer * _mixer ) : - AudioDevice( tLimit( +AudioPulseAudio::AudioPulseAudio( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( tLimit( configManager::inst()->value( "audiopa", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + context ), m_s( NULL ), m_quit( false ), m_convertEndian( false ) @@ -119,11 +119,9 @@ void AudioPulseAudio::applyQualitySettings() { if( hqAudio() ) { -// setSampleRate( engine::getMixer()->processingSampleRate() ); +// setSampleRate( mixer()->processingSampleRate() ); } - - AudioDevice::applyQualitySettings(); } @@ -139,12 +137,12 @@ static void stream_state_callback( pa_stream *s, void * userdata ) break; case PA_STREAM_READY: - qDebug( "Stream successfully created\n" ); + qDebug( "Stream successfully created" ); break; case PA_STREAM_FAILED: default: - qCritical( "Stream errror: %s\n", + qCritical( "Stream errror: %s", pa_strerror(pa_context_errno( pa_stream_get_context( s ) ) ) ); } @@ -166,7 +164,7 @@ static void context_state_callback(pa_context *c, void *userdata) case PA_CONTEXT_READY: { pa_cvolume cv; - qDebug( "Connection established.\n" ); + qDebug( "Connection established." ); _this->m_s = pa_stream_new( c, "lmms", &_this->m_sampleSpec, NULL); pa_stream_set_state_callback( _this->m_s, stream_state_callback, _this ); pa_stream_set_write_callback( _this->m_s, stream_write_callback, _this ); @@ -181,7 +179,8 @@ static void context_state_callback(pa_context *c, void *userdata) buffer_attr.minreq = (uint32_t)(-1); buffer_attr.fragsize = (uint32_t)(-1); - double latency = (double)( engine::getMixer()->framesPerPeriod() ) / + double latency = (double)( ( (const AudioPulseAudio *) _this )-> + mixer()->framesPerPeriod() ) / (double)_this->sampleRate(); // ask PulseAudio for the desired latency (which might not be approved) @@ -201,7 +200,7 @@ static void context_state_callback(pa_context *c, void *userdata) case PA_CONTEXT_FAILED: default: - qCritical( "Connection failure: %s\n", pa_strerror( pa_context_errno( c ) ) ); + qCritical( "Connection failure: %s", pa_strerror( pa_context_errno( c ) ) ); } } @@ -213,7 +212,7 @@ void AudioPulseAudio::run() pa_mainloop * mainLoop = pa_mainloop_new(); if( !mainLoop ) { - qCritical( "pa_mainloop_new() failed.\n" ); + qCritical( "pa_mainloop_new() failed." ); return; } pa_mainloop_api * mainloop_api = pa_mainloop_get_api( mainLoop ); @@ -250,7 +249,7 @@ void AudioPulseAudio::run() void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) { - const fpp_t fpp = getMixer()->framesPerPeriod(); + const fpp_t fpp = mixer()->framesPerPeriod(); sampleFrameA * temp = CPU::allocFrames( fpp ); Sint16 * pcmbuf = (Sint16*)CPU::memAlloc( fpp * channels() * sizeof(Sint16) ); @@ -267,7 +266,7 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) int bytes = CPU::convertToS16( temp, (intSampleFrameA *) pcmbuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); if( bytes > 0 ) { @@ -285,7 +284,7 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioPulseAudio::name(), _parent ) + AudioBackend::setupWidget( AudioPulseAudio::name(), _parent ) { m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp index 4601182a5..d280c0468 100644 --- a/src/core/audio/AudioSampleRecorder.cpp +++ b/src/core/audio/AudioSampleRecorder.cpp @@ -26,14 +26,14 @@ #include "AudioSampleRecorder.h" #include "sample_buffer.h" -#include "debug.h" +#include "Cpu.h" AudioSampleRecorder::AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, - mixer * _mixer ) : - AudioDevice( _channels, _mixer ), + AudioOutputContext * context ) : + AudioBackend( _channels, context ), m_buffers() { _success_ful = true; @@ -46,7 +46,7 @@ AudioSampleRecorder::~AudioSampleRecorder() { while( !m_buffers.empty() ) { - delete[] m_buffers.front().first; + CPU::freeFrames( m_buffers.front().first ); m_buffers.erase( m_buffers.begin() ); } } @@ -72,9 +72,9 @@ void AudioSampleRecorder::createSampleBuffer( sampleBuffer * * _sample_buf ) { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in - sampleFrame * data = new sampleFrame[frames]; + sampleFrameA * data = CPU::allocFrames( frames ); // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; + sampleFrameA * data_ptr = data; #ifdef LMMS_DEBUG assert( data != NULL ); @@ -83,30 +83,24 @@ void AudioSampleRecorder::createSampleBuffer( sampleBuffer * * _sample_buf ) for( BufferList::ConstIterator it = m_buffers.begin(); it != m_buffers.end(); ++it ) { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); + CPU::memCpy( data_ptr, ( *it ).first, ( *it ).second * + sizeof( sampleFrameA ) ); data_ptr += ( *it ).second; } // create according sample-buffer out of big buffer *_sample_buf = new sampleBuffer( data, frames ); ( *_sample_buf )->setSampleRate( sampleRate() ); - delete[] data; + CPU::freeFrames( data ); } -void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab, +void AudioSampleRecorder::writeBuffer( const sampleFrameA * srcBuf, const fpp_t _frames, const float ) { - sampleFrame * buf = new sampleFrame[_frames]; - for( fpp_t frame = 0; frame < _frames; ++frame ) - { - for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) - { - buf[frame][chnl] = _ab[frame][chnl]; - } - } + sampleFrameA * buf = CPU::allocFrames( _frames ); + CPU::memCpy( buf, srcBuf, _frames*sizeof( sampleFrameA ) ); m_buffers.push_back( qMakePair( buf, _frames ) ); } diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 78affdb9a..bc62ffee0 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -38,16 +38,16 @@ -AudioSdl::AudioSdl( bool & _success_ful, mixer * _mixer ) : - AudioDevice( DEFAULT_CHANNELS, _mixer ), - m_outBuf( CPU::allocFrames( getMixer()->framesPerPeriod() ) ), +AudioSdl::AudioSdl( bool & _success_ful, AudioOutputContext * context ) : + AudioBackend( DEFAULT_CHANNELS, context ), + m_outBuf( CPU::allocFrames( mixer()->framesPerPeriod() ) ), m_convertedBufPos( 0 ), m_convertEndian( false ), m_stopSemaphore( 1 ) { _success_ful = false; - m_convertedBufSize = getMixer()->framesPerPeriod() * + m_convertedBufSize = mixer()->framesPerPeriod() * sizeof( intSampleFrameA ); m_convertedBuf = (intSampleFrameA *) CPU::memAlloc( m_convertedBufSize ); @@ -63,7 +63,7 @@ AudioSdl::AudioSdl( bool & _success_ful, mixer * _mixer ) : // of system, so we don't have // to convert the buffers m_audioHandle.channels = channels(); - m_audioHandle.samples = qMax( 1024, getMixer()->framesPerPeriod()*2 ); + m_audioHandle.samples = qMax( 1024, mixer()->framesPerPeriod()*2 ); m_audioHandle.callback = sdlAudioCallback; m_audioHandle.userdata = this; @@ -131,7 +131,7 @@ void AudioSdl::applyQualitySettings() { SDL_CloseAudio(); - setSampleRate( engine::getMixer()->processingSampleRate() ); + setSampleRate( mixer()->processingSampleRate() ); m_audioHandle.freq = sampleRate(); @@ -143,8 +143,6 @@ void AudioSdl::applyQualitySettings() qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); } } - - AudioDevice::applyQualitySettings(); } @@ -186,7 +184,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) CPU::convertToS16( m_outBuf, m_convertedBuf, frames, - getMixer()->masterGain(), + mixer()->masterGain(), m_convertEndian ); } const int min_len = qMin( _len, m_convertedBufSize @@ -203,7 +201,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) AudioSdl::setupWidget::setupWidget( QWidget * _parent ) : - AudioDevice::setupWidget( AudioSdl::name(), _parent ) + AudioBackend::setupWidget( AudioSdl::name(), _parent ) { QString dev = configManager::inst()->value( "audiosdl", "device" ); m_device = new QLineEdit( dev, this ); diff --git a/src/core/engine.cpp b/src/core/engine.cpp index 6606619c9..0bb323b5e 100644 --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -37,7 +37,7 @@ #include "InstrumentTrack.h" #include "ladspa_2_lmms.h" #include "MainWindow.h" -#include "mixer.h" +#include "Mixer.h" #include "pattern.h" #include "piano_roll.h" #include "ProjectJournal.h" @@ -56,7 +56,7 @@ bool engine::s_hasGUI = true; bool engine::s_suppressMessages = false; float engine::s_framesPerTick; -mixer * engine::s_mixer = NULL; +Mixer * engine::s_mixer = NULL; FxMixer * engine::s_fxMixer = NULL; FxMixerView * engine::s_fxMixerView = NULL; MainWindow * engine::s_mainWindow = NULL; @@ -89,9 +89,11 @@ void engine::init( const bool _has_gui ) initPluginFileHandling(); s_projectJournal = new ProjectJournal; - s_mixer = new mixer; + s_mixer = new Mixer; + s_song = new song; + s_mixer->initDevices(); // init resource framework s_workingDirResourceDB = @@ -119,8 +121,6 @@ void engine::init( const bool _has_gui ) s_projectJournal->setJournalling( true ); - s_mixer->initDevices(); - s_midiControlListener = new MidiControlListener(); s_automationRecorder = new AutomationRecorder; diff --git a/src/core/main.cpp b/src/core/main.cpp index 41227025f..77a456ded 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -22,7 +22,7 @@ * */ - +#include #include #include #include @@ -126,9 +126,10 @@ int main( int argc, char * * argv ) new QApplication( argc, argv ) ; - mixer::qualitySettings qs( mixer::qualitySettings::Mode_HighQuality ); - ProjectRenderer::OutputSettings os( 44100, false, 160, - ProjectRenderer::Depth_16Bit ); + AudioOutputContext::QualitySettings qs( + AudioOutputContext::QualitySettings::Preset_HighQuality ); + ProjectRenderer::EncoderSettings es( 44100, false, 160, + ProjectRenderer::Depth_16Bit ); ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; @@ -250,7 +251,7 @@ int main( int argc, char * * argv ) sample_rate_t sr = QString( argv[i + 1] ).toUInt(); if( sr >= 44100 && sr <= 192000 ) { - os.samplerate = sr; + es.samplerate = sr; } else { @@ -267,7 +268,7 @@ int main( int argc, char * * argv ) int br = QString( argv[i + 1] ).toUInt(); if( br >= 64 && br <= 384 ) { - os.bitrate = br; + es.bitrate = br; } else { @@ -284,19 +285,23 @@ int main( int argc, char * * argv ) const QString ip = QString( argv[i + 1] ); if( ip == "linear" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_Linear; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_Linear ); } else if( ip == "sincfastest" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_SincFastest; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_SincFastest ); } else if( ip == "sincmedium" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_SincMedium; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_SincMedium ); } else if( ip == "sincbest" ) { - qs.interpolation = mixer::qualitySettings::Interpolation_SincBest; + qs.setInterpolation( AudioOutputContext::QualitySettings:: + Interpolation_SincBest ); } else { @@ -314,21 +319,25 @@ int main( int argc, char * * argv ) switch( o ) { case 1: - qs.oversampling = mixer::qualitySettings::Oversampling_None; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_None ); + break; case 2: - qs.oversampling = mixer::qualitySettings::Oversampling_2x; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_2x ); + break; case 4: - qs.oversampling = mixer::qualitySettings::Oversampling_4x; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_4x ); + break; case 8: - qs.oversampling = mixer::qualitySettings::Oversampling_8x; - break; + qs.setOversampling( AudioOutputContext::QualitySettings:: + Oversampling_8x ); + break; default: - printf( "\nInvalid oversampling %s.\n\n" + printf( "\nInvalid oversampling %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); - return( EXIT_FAILURE ); + return EXIT_FAILURE; } ++i; } @@ -449,6 +458,13 @@ int main( int argc, char * * argv ) // srandom() calls in their init procedure srand( getpid() + time( 0 ) ); + // recover a file? + QString recoveryFile = QDir(configManager::inst()->workingDir()).absoluteFilePath("recover.mmp"); + if( QFileInfo(recoveryFile).exists() ) + { + file_to_load = recoveryFile; + } + // we try to load given file if( !file_to_load.isEmpty() ) { @@ -506,7 +522,7 @@ int main( int argc, char * * argv ) if( !render_out.isEmpty() ) { // create renderer - ProjectRenderer * r = new ProjectRenderer( qs, os, eff, + ProjectRenderer * r = new ProjectRenderer( qs, es, eff, render_out + QString( ProjectRenderer::EFF_ext[eff] ) ); QCoreApplication::instance()->connect( r, SIGNAL( finished() ), SLOT( quit() ) ); @@ -523,7 +539,7 @@ int main( int argc, char * * argv ) } else { - engine::getSong()->saveProjectAs( file_to_save ); + engine::getSong()->saveProjectFile( file_to_save ); return( 0 ); } } diff --git a/src/core/midi/MidiControlListener.cpp b/src/core/midi/MidiControlListener.cpp index f014cfc56..4a089f888 100644 --- a/src/core/midi/MidiControlListener.cpp +++ b/src/core/midi/MidiControlListener.cpp @@ -30,7 +30,6 @@ #include #include "MidiControlListener.h" -#include "mixer.h" #include "MidiClient.h" #include "MidiPort.h" #include "engine.h" diff --git a/src/core/midi/MidiController.cpp b/src/core/midi/MidiController.cpp index ba74a9e62..5cb60c61e 100644 --- a/src/core/midi/MidiController.cpp +++ b/src/core/midi/MidiController.cpp @@ -29,7 +29,6 @@ #include "song.h" #include "engine.h" -#include "mixer.h" #include "MidiClient.h" #include "MidiController.h" #include "automation_recorder.h" diff --git a/src/core/sample_buffer.cpp b/src/core/sample_buffer.cpp index 5958ea82c..4051524fa 100644 --- a/src/core/sample_buffer.cpp +++ b/src/core/sample_buffer.cpp @@ -24,7 +24,7 @@ #include "sample_buffer.h" -#include "mixer.h" +#include "Mixer.h" #include diff --git a/src/core/song.cpp b/src/core/song.cpp index 11392cda8..9b421a052 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -729,6 +729,8 @@ void song::clearProject() } engine::getMixer()->lock(); + engine::fxMixer()->clear(); + if( engine::getBBEditor() ) { engine::getBBEditor()->clearAllTracks(); @@ -737,15 +739,18 @@ void song::clearProject() { engine::getSongEditor()->clearAllTracks(); } + + // depends on the fxMixer being cleared if( engine::fxMixerView() ) { engine::fxMixerView()->clear(); } + QCoreApplication::sendPostedEvents(); engine::getBBTrackContainer()->clearAllTracks(); clearAllTracks(); - engine::fxMixer()->clear(); + if( engine::getAutomationEditor() ) { @@ -904,6 +909,26 @@ void song::loadProject( const QString & _file_name ) firstChildElement( "track" ) ); } QDomNode node = mmp.content().firstChild(); + + // walk through and fix up the mixer + while( !node.isNull() ) + { + if( node.nodeName() == engine::fxMixer()->nodeName() ) + { + engine::fxMixer()->restoreState( node.toElement() ); + + if( engine::hasGUI() ) + { + // refresh FxMixerView + engine::fxMixerView()->refreshDisplay(); + } + } + + node = node.nextSibling(); + } + + node = mmp.content().firstChild(); + while( !node.isNull() ) { if( node.isElement() ) @@ -917,10 +942,6 @@ void song::loadProject( const QString & _file_name ) { restoreControllerStates( node.toElement() ); } - else if( node.nodeName() == engine::fxMixer()->nodeName() ) - { - engine::fxMixer()->restoreState( node.toElement() ); - } else if( engine::hasGUI() ) { if( node.nodeName() == @@ -973,7 +994,6 @@ void song::loadProject( const QString & _file_name ) // resolve all IDs so that autoModels are automated automationPattern::resolveAllIDs(); - engine::getMixer()->unlock(); configManager::inst()->addRecentlyOpenedProject( _file_name ); @@ -994,10 +1014,8 @@ void song::loadProject( const QString & _file_name ) } - - -// save current song -bool song::saveProject() +// only save current song as _filename and do nothing else +bool song::saveProjectFile( const QString & _filename ) { multimediaProject mmp( multimediaProject::SongProject ); @@ -1006,7 +1024,6 @@ bool song::saveProject() m_masterVolumeModel.saveSettings( mmp, mmp.head(), "mastervol" ); m_masterPitchModel.saveSettings( mmp, mmp.head(), "masterpitch" ); - saveState( mmp, mmp.content() ); m_globalAutomationTrack->saveState( mmp, mmp.content() ); @@ -1024,8 +1041,17 @@ bool song::saveProject() saveControllerStates( mmp, mmp.content() ); + return mmp.writeFile( _filename ); +} + + + +// save current song and update the gui +bool song::guiSaveProject() +{ + multimediaProject mmp( multimediaProject::SongProject ); m_fileName = mmp.nameWithExtension( m_fileName ); - if( mmp.writeFile( m_fileName ) == true && engine::hasGUI() ) + if( saveProjectFile( m_fileName ) && engine::hasGUI() ) { textFloat::displayMessage( tr( "Project saved" ), tr( "The project %1 is now saved." @@ -1052,12 +1078,12 @@ bool song::saveProject() // save current song in given filename -bool song::saveProjectAs( const QString & _file_name ) +bool song::guiSaveProjectAs( const QString & _file_name ) { QString o = m_oldFileName; m_oldFileName = m_fileName; m_fileName = _file_name; - if( saveProject() == false ) + if( guiSaveProject() == false ) { m_fileName = m_oldFileName; m_oldFileName = o; diff --git a/src/gui/ExportProjectDialog.cpp b/src/gui/ExportProjectDialog.cpp index 6ac5f6d7f..3d898e93e 100644 --- a/src/gui/ExportProjectDialog.cpp +++ b/src/gui/ExportProjectDialog.cpp @@ -146,21 +146,22 @@ void ExportProjectDialog::startBtnClicked() ui->progressBar->setEnabled( true ); - mixer::qualitySettings qs = mixer::qualitySettings( - static_cast( - ui->interpolationCB->currentIndex() ), - static_cast( - ui->oversamplingCB->currentIndex() ), + AudioOutputContext::QualitySettings qs = + AudioOutputContext::QualitySettings( + static_cast( + ui->interpolationCB->currentIndex() ), + static_cast( + ui->oversamplingCB->currentIndex() ), ui->sampleExactControllersCB->isChecked(), ui->aliasFreeOscillatorsCB->isChecked() ); - ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( + ProjectRenderer::EncoderSettings es = ProjectRenderer::EncoderSettings( ui->samplerateCB->currentText().section( " ", 0, 0 ).toUInt(), false, ui->bitrateCB->currentText().section( " ", 0, 0 ).toUInt(), static_cast( ui->depthCB->currentIndex() ) ); - m_renderer = new ProjectRenderer( qs, os, ft, m_fileName ); + m_renderer = new ProjectRenderer( qs, es, ft, m_fileName ); if( m_renderer->isReady() ) { updateTitleBar( 0 ); diff --git a/src/gui/FxLine.cpp b/src/gui/FxLine.cpp new file mode 100644 index 000000000..172d30b17 --- /dev/null +++ b/src/gui/FxLine.cpp @@ -0,0 +1,92 @@ +#include "FxLine.h" + +#include +#include +#include +#include + +#include "FxMixer.h" +#include "FxMixerView.h" +#include "embed.h" +#include "engine.h" +#include "SendButtonIndicator.h" + +FxLine::FxLine( QWidget * _parent, FxMixerView * _mv, int _channelIndex) : + QWidget( _parent ), + m_mv( _mv ), + m_channelIndex( _channelIndex ) +{ + setFixedSize( 32, 287 ); + setAttribute( Qt::WA_OpaquePaintEvent, true ); + setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); + + // mixer sends knob + m_sendKnob = new knob(0, this, tr("Channel send amount")); + m_sendKnob->move(0, 22); + m_sendKnob->setVisible(false); + + // send button indicator + m_sendBtn = new SendButtonIndicator(this, this, m_mv); + m_sendBtn->setPixmap(embed::getIconPixmap("mixer_send_off", 23, 16)); + m_sendBtn->move(4,4); + + // channel number + m_lcd = new lcdSpinBox( 2, this ); + m_lcd->model()->setRange( m_channelIndex, m_channelIndex ); + m_lcd->model()->setValue( m_channelIndex ); + m_lcd->move( 2, 58 ); + m_lcd->setMarginWidth( 1 ); +} + +FxLine::~FxLine() +{ + delete m_sendKnob; + delete m_sendBtn; + delete m_lcd; +} + + +void FxLine::setChannelIndex(int index) { + m_channelIndex = index; + + m_lcd->model()->setRange( m_channelIndex, m_channelIndex ); + m_lcd->model()->setValue( m_channelIndex ); + m_lcd->update(); +} + + +void FxLine::paintEvent( QPaintEvent * ) +{ + FxMixer * mix = engine::fxMixer(); + bool sendToThis = mix->channelSendModel( + m_mv->currentFxLine()->m_channelIndex, m_channelIndex) != NULL; + QPainter painter; + painter.begin( this ); + engine::getLmmsStyle()->drawFxLine( &painter, this, + mix->effectChannel(m_channelIndex)->m_name, + m_mv->currentFxLine() == this, sendToThis ); + painter.end(); +} + +void FxLine::mousePressEvent( QMouseEvent * ) +{ + m_mv->setCurrentFxLine( this ); +} + +void FxLine::mouseDoubleClickEvent( QMouseEvent * ) +{ + bool ok; + FxMixer * mix = engine::fxMixer(); + QString new_name = QInputDialog::getText( this, + FxMixerView::tr( "Rename FX channel" ), + FxMixerView::tr( "Enter the new name for this " + "FX channel" ), + QLineEdit::Normal, mix->effectChannel(m_channelIndex)->m_name, &ok ); + if( ok && !new_name.isEmpty() ) + { + mix->effectChannel(m_channelIndex)->m_name = new_name; + update(); + } +} + +#include "moc_FxLine.cxx" diff --git a/src/gui/FxMixerView.cpp b/src/gui/FxMixerView.cpp index bde8e5a04..b61bcbf7b 100644 --- a/src/gui/FxMixerView.cpp +++ b/src/gui/FxMixerView.cpp @@ -22,6 +22,9 @@ * */ +#include +#include + #include #include #include @@ -31,71 +34,20 @@ #include #include #include +#include +#include +#include #include "FxMixerView.h" -#include "fader.h" -#include "EffectRackView.h" +#include "knob.h" #include "engine.h" #include "embed.h" #include "MainWindow.h" #include "lcd_spinbox.h" #include "gui_templates.h" -#include "tooltip.h" -#include "pixmap_button.h" - - - -class FxLine : public QWidget -{ -public: - FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name ) : - QWidget( _parent ), - m_mv( _mv ), - m_name( _name ) - { - setFixedSize( 32, 232 ); - setAttribute( Qt::WA_OpaquePaintEvent, true ); - setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); - } - - virtual void paintEvent( QPaintEvent * ) - { - QPainter painter; - painter.begin( this ); - engine::getLmmsStyle()->drawFxLine( &painter, - this, m_name, m_mv->currentFxLine() == this ); - painter.end(); - } - - virtual void mousePressEvent( QMouseEvent * ) - { - m_mv->setCurrentFxLine( this ); - } - - virtual void mouseDoubleClickEvent( QMouseEvent * ) - { - bool ok; - QString new_name = QInputDialog::getText( this, - FxMixerView::tr( "Rename FX channel" ), - FxMixerView::tr( "Enter the new name for this " - "FX channel" ), - QLineEdit::Normal, m_name, &ok ); - if( ok && !new_name.isEmpty() ) - { - m_name = new_name; - update(); - } - } - - -private: - FxMixerView * m_mv; - QString & m_name; - -} ; - - - +#include "InstrumentTrack.h" +#include "song.h" +#include "bb_track_container.h" FxMixerView::FxMixerView() : QWidget(), @@ -105,118 +57,81 @@ FxMixerView::FxMixerView() : FxMixer * m = engine::fxMixer(); m->setHook( this ); - QPalette pal = palette(); + //QPalette pal = palette(); //pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) ); //setPalette( pal ); setAutoFillBackground( true ); - setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum ); setWindowTitle( tr( "FX-Mixer" ) ); setWindowIcon( embed::getIconPixmap( "fx_mixer" ) ); - m_fxLineBanks = new QStackedLayout; - m_fxLineBanks->setSpacing( 0 ); - m_fxLineBanks->setMargin( 1 ); - - m_fxRacksLayout = new QStackedLayout; - m_fxRacksLayout->setSpacing( 0 ); - m_fxRacksLayout->setMargin( 0 ); - // main-layout QHBoxLayout * ml = new QHBoxLayout; - ml->setMargin( 0 ); - ml->setSpacing( 0 ); - ml->addSpacing( 6 ); + // Channel area + m_channelAreaWidget = new QWidget; + chLayout = new QHBoxLayout(m_channelAreaWidget); + chLayout->setSizeConstraint(QLayout::SetMinimumSize); + chLayout->setSpacing( 0 ); + chLayout->setMargin( 0 ); + m_channelAreaWidget->setLayout(chLayout); - QHBoxLayout * banks[NumFxChannels/16]; - for( int i = 0; i < NumFxChannels/16; ++i ) + // add master channel + m_fxChannelViews.resize(m->numChannels()); + m_fxChannelViews[0] = new FxChannelView(this, this, 0); + + FxChannelView * masterView = m_fxChannelViews[0]; + ml->addWidget( masterView->m_fxLine, 0, Qt::AlignTop ); + + QSize fxLineSize = masterView->m_fxLine->size(); + + // add mixer channels + for( int i = 1; i < m_fxChannelViews.size(); ++i ) { - QWidget * w = new QWidget( this ); - banks[i] = new QHBoxLayout( w ); - banks[i]->setMargin( 5 ); - banks[i]->setSpacing( 1 ); - m_fxLineBanks->addWidget( w ); + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); + chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); } - for( int i = 0; i < NumFxChannels+1; ++i ) + // add the scrolling section to the main layout + // class solely for scroll area to pass key presses down + class ChannelArea : public QScrollArea { - FxChannelView * cv = &m_fxChannelViews[i]; - if( i == 0 ) - { - cv->m_fxLine = new FxLine( NULL, this, - m->m_fxChannels[i]->m_name ); - ml->addWidget( cv->m_fxLine ); - ml->addSpacing( 10 ); - } - else - { - const int bank = (i-1) / 16; - cv->m_fxLine = new FxLine( NULL, this, - m->m_fxChannels[i]->m_name ); - banks[bank]->addWidget( cv->m_fxLine ); - } - lcdSpinBox * l = new lcdSpinBox( 2, cv->m_fxLine ); - l->model()->setRange( i, i ); - l->model()->setValue( i ); - l->move( 2, 4 ); - l->setMarginWidth( 1 ); - - - cv->m_fader = new fader( &m->m_fxChannels[i]->m_volumeModel, - tr( "FX Fader %1" ).arg( i ), - cv->m_fxLine ); - cv->m_fader->move( 15-cv->m_fader->width()/2, - cv->m_fxLine->height()- - cv->m_fader->height()-5 ); - - cv->m_muteBtn = new pixmapButton( cv->m_fxLine, tr( "Mute" ) ); - cv->m_muteBtn->setModel( &m->m_fxChannels[i]->m_muteModel ); - cv->m_muteBtn->setActiveGraphic( - embed::getIconPixmap( "led_off" ) ); - cv->m_muteBtn->setInactiveGraphic( - embed::getIconPixmap( "led_green" ) ); - cv->m_muteBtn->setCheckable( true ); - cv->m_muteBtn->move( 9, cv->m_fader->y()-16); - toolTip::add( cv->m_muteBtn, tr( "Mute this FX channel" ) ); - - cv->m_rackView = new EffectRackView( - &m->m_fxChannels[i]->m_fxChain, this ); - m_fxRacksLayout->addWidget( cv->m_rackView ); - if( i == 0 ) - { - QVBoxLayout * l = new QVBoxLayout; - l->addSpacing( 10 ); - QButtonGroup * g = new QButtonGroup( this ); - m_bankButtons = g; - g->setExclusive( true ); - for( int j = 0; j < 4; ++j ) + public: + ChannelArea(QWidget * parent, FxMixerView * mv) : + QScrollArea(parent), m_mv(mv) {} + ~ChannelArea() {} + virtual void keyPressEvent(QKeyEvent * e) { - QToolButton * btn = new QToolButton; - btn->setText( QString( 'A'+j ) ); - btn->setCheckable( true ); - btn->setSizePolicy( QSizePolicy::Preferred, - QSizePolicy::Expanding ); - l->addWidget( btn ); - g->addButton( btn, j ); - btn->setChecked( j == 0); + m_mv->keyPressEvent(e); } - l->addSpacing( 10 ); - ml->addLayout( l ); - connect( g, SIGNAL( buttonClicked( int ) ), - m_fxLineBanks, SLOT( setCurrentIndex( int ) ) ); - } - } + private: + FxMixerView * m_mv; + }; + channelArea = new ChannelArea(this, this); + channelArea->setWidget(m_channelAreaWidget); + channelArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + channelArea->setFrameStyle( QFrame::NoFrame ); + channelArea->setMinimumWidth( fxLineSize.width() * 6 ); + channelArea->setFixedHeight( fxLineSize.height() + + style()->pixelMetric( QStyle::PM_ScrollBarExtent ) ); + ml->addWidget(channelArea); - ml->addLayout( m_fxLineBanks ); - ml->addLayout( m_fxRacksLayout ); + // show the add new effect channel button + QPushButton * newChannelBtn = new QPushButton("new", this ); + newChannelBtn->setFont(QFont("sans-serif", 10, 1, false)); + newChannelBtn->setFixedSize(fxLineSize); + connect( newChannelBtn, SIGNAL(clicked()), this, SLOT(addNewChannel())); + ml->addWidget( newChannelBtn, 0, Qt::AlignTop ); + + + // Create EffectRack and set initial index to master channel + m_rackView = new EffectRackView( &m->m_fxChannels[0]->m_fxChain, this ); + ml->addWidget( m_rackView, 0, Qt::AlignTop ); + setCurrentFxLine( m_fxChannelViews[0]->m_fxLine ); setLayout( ml ); updateGeometry(); - m_fxLineBanks->setCurrentIndex( 0 ); - setCurrentFxLine( m_fxChannelViews[0].m_fxLine ); - // timer for updating faders connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( updateFaders() ) ); @@ -226,10 +141,10 @@ FxMixerView::FxMixerView() : QMdiSubWindow * subWin = engine::mainWindow()->workspace()->addSubWindow( this ); Qt::WindowFlags flags = subWin->windowFlags(); - flags |= Qt::MSWindowsFixedSizeDialogHint; flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); - subWin->layout()->setSizeConstraint(QLayout::SetFixedSize); + layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); + subWin->layout()->setSizeConstraint( QLayout::SetMinAndMaxSize ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); @@ -238,15 +153,75 @@ FxMixerView::FxMixerView() : setModel( m ); } - - - FxMixerView::~FxMixerView() { } +void FxMixerView::addNewChannel() +{ + // add new fx mixer channel and redraw the form. + FxMixer * mix = engine::fxMixer(); + + int newChannelIndex = mix->createChannel(); + m_fxChannelViews.push_back(new FxChannelView(m_channelAreaWidget, this, + newChannelIndex)); + chLayout->addWidget(m_fxChannelViews[newChannelIndex]->m_fxLine); + + updateFxLine(newChannelIndex); + + updateMaxChannelSelector(); +} + + +void FxMixerView::refreshDisplay() +{ + // delete all views and re-add them + for( int i = 1; iremoveWidget(m_fxChannelViews[i]->m_fxLine); + delete m_fxChannelViews[i]->m_fader; + delete m_fxChannelViews[i]->m_muteBtn; + delete m_fxChannelViews[i]->m_fxLine; + delete m_fxChannelViews[i]; + } + m_channelAreaWidget->adjustSize(); + + // re-add the views + m_fxChannelViews.resize(engine::fxMixer()->numChannels()); + for( int i = 1; i < m_fxChannelViews.size(); ++i ) + { + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); + chLayout->addWidget(m_fxChannelViews[i]->m_fxLine); + } + + updateMaxChannelSelector(); +} + + +// update the and max. channel number for every instrument +void FxMixerView::updateMaxChannelSelector() +{ + QVector songTrackList = engine::getSong()->tracks(); + QVector bbTrackList = engine::getBBTrackContainer()->tracks(); + + QVector trackLists[] = {songTrackList, bbTrackList}; + for(int tl=0; tl<2; ++tl) + { + QVector trackList = trackLists[tl]; + for(int i=0; itype() == track::InstrumentTrack ) + { + InstrumentTrack * inst = (InstrumentTrack *) trackList[i]; + inst->effectChannelModel()->setRange(0, + m_fxChannelViews.size()-1,1); + } + } + } +} + void FxMixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { @@ -262,18 +237,189 @@ void FxMixerView::loadSettings( const QDomElement & _this ) } +FxMixerView::FxChannelView::FxChannelView(QWidget * _parent, FxMixerView * _mv, + int _chIndex ) +{ + m_fxLine = new FxLine(_parent, _mv, _chIndex); + + FxMixer * m = engine::fxMixer(); + m_fader = new fader( &m->effectChannel(_chIndex)->m_volumeModel, + tr( "FX Fader %1" ).arg( _chIndex ), m_fxLine ); + m_fader->move( 15-m_fader->width()/2, + m_fxLine->height()- + m_fader->height()-5 ); + + m_muteBtn = new pixmapButton( m_fxLine, tr( "Mute" ) ); + m_muteBtn->setModel( &m->effectChannel(_chIndex)->m_muteModel ); + m_muteBtn->setActiveGraphic( + embed::getIconPixmap( "led_off" ) ); + m_muteBtn->setInactiveGraphic( + embed::getIconPixmap( "led_green" ) ); + m_muteBtn->setCheckable( true ); + m_muteBtn->move( 9, m_fader->y()-16); + toolTip::add( m_muteBtn, tr( "Mute this FX channel" ) ); +} void FxMixerView::setCurrentFxLine( FxLine * _line ) { + // select m_currentFxLine = _line; - for( int i = 0; i < NumFxChannels+1; ++i ) + m_rackView->setModel( &engine::fxMixer()->m_fxChannels[_line->channelIndex()]->m_fxChain ); + + // set up send knob + for(int i = 0; i < m_fxChannelViews.size(); ++i) { - if( m_fxChannelViews[i].m_fxLine == _line ) + updateFxLine(i); + } +} + + +void FxMixerView::updateFxLine(int index) +{ + FxMixer * mix = engine::fxMixer(); + + // does current channel send to this channel? + int selIndex = m_currentFxLine->channelIndex(); + FxLine * thisLine = m_fxChannelViews[index]->m_fxLine; + FloatModel * sendModel = mix->channelSendModel(selIndex, index); + if( sendModel == NULL ) + { + // does not send, hide send knob + thisLine->m_sendKnob->setVisible(false); + } + else + { + // it does send, show knob and connect + thisLine->m_sendKnob->setVisible(true); + thisLine->m_sendKnob->setModel(sendModel); + } + + // disable the send button if it would cause an infinite loop + thisLine->m_sendBtn->setVisible(! mix->isInfiniteLoop(selIndex, index)); + thisLine->m_sendBtn->updateLightStatus(); + thisLine->update(); +} + + +void FxMixerView::deleteChannel(int index) +{ + // can't delete master + if( index == 0 ) return; + + // remember selected line + int selLine = m_currentFxLine->channelIndex(); + + // delete the real channel + engine::fxMixer()->deleteChannel(index); + + // delete the view + chLayout->removeWidget(m_fxChannelViews[index]->m_fxLine); + delete m_fxChannelViews[index]->m_fader; + delete m_fxChannelViews[index]->m_muteBtn; + delete m_fxChannelViews[index]->m_fxLine; + delete m_fxChannelViews[index]; + m_channelAreaWidget->adjustSize(); + + // make sure every channel knows what index it is + for(int i=0; i index ) { - m_fxRacksLayout->setCurrentIndex( i ); + m_fxChannelViews[i]->m_fxLine->setChannelIndex(i-1); } - m_fxChannelViews[i].m_fxLine->update(); + } + m_fxChannelViews.remove(index); + + // select the next channel + if( selLine >= m_fxChannelViews.size() ) + { + selLine = m_fxChannelViews.size()-1; + } + setCurrentFxLine(selLine); + + updateMaxChannelSelector(); +} + + + +void FxMixerView::moveChannelLeft(int index) +{ + // can't move master or first channel left or last channel right + if( index <= 1 || index >= m_fxChannelViews.size() ) return; + + int selIndex = m_currentFxLine->channelIndex(); + + FxMixer * mix = engine::fxMixer(); + mix->moveChannelLeft(index); + + // refresh the two mixer views + for( int i = index-1; i <= index; ++i ) + { + // delete the mixer view + int replaceIndex = chLayout->indexOf(m_fxChannelViews[i]->m_fxLine); + + chLayout->removeWidget(m_fxChannelViews[i]->m_fxLine); + delete m_fxChannelViews[i]->m_fader; + delete m_fxChannelViews[i]->m_muteBtn; + delete m_fxChannelViews[i]->m_fxLine; + delete m_fxChannelViews[i]; + + // add it again + m_fxChannelViews[i] = new FxChannelView(m_channelAreaWidget, this, i); + chLayout->insertWidget(replaceIndex, m_fxChannelViews[i]->m_fxLine); + } + + // keep selected channel + if( selIndex == index ) + { + selIndex = index-1; + } + else if( selIndex == index - 1 ) + { + selIndex = index; + } + setCurrentFxLine(selIndex); +} + + + +void FxMixerView::moveChannelRight(int index) +{ + moveChannelLeft(index+1); +} + + + +void FxMixerView::keyPressEvent(QKeyEvent * e) +{ + switch(e->key()) + { + case Qt::Key_Delete: + deleteChannel(m_currentFxLine->channelIndex()); + break; + case Qt::Key_Left: + if( e->modifiers() & Qt::AltModifier ) + { + moveChannelLeft( m_currentFxLine->channelIndex() ); + } + else + { + // select channel to the left + setCurrentFxLine( m_currentFxLine->channelIndex()-1 ); + } + break; + case Qt::Key_Right: + if( e->modifiers() & Qt::AltModifier ) + { + moveChannelRight( m_currentFxLine->channelIndex() ); + } + else + { + // select channel to the right + setCurrentFxLine( m_currentFxLine->channelIndex()+1 ); + } + break; } } @@ -281,23 +427,18 @@ void FxMixerView::setCurrentFxLine( FxLine * _line ) void FxMixerView::setCurrentFxLine( int _line ) { - if ( _line >= 0 && _line < NumFxChannels+1 ) + if( _line >= 0 && _line < m_fxChannelViews.size() ) { - setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); - - m_bankButtons->button( (_line-1) / 16 )->click(); + setCurrentFxLine( m_fxChannelViews[_line]->m_fxLine ); } } - void FxMixerView::clear() { - for( int i = 0; i <= NumFxChannels; ++i ) - { - m_fxChannelViews[i].m_rackView->clearViews(); - } + m_rackView->clearViews(); + refreshDisplay(); } @@ -306,28 +447,28 @@ void FxMixerView::clear() void FxMixerView::updateFaders() { FxMixer * m = engine::fxMixer(); - for( int i = 0; i < NumFxChannels+1; ++i ) + for( int i = 0; i < m_fxChannelViews.size(); ++i ) { - const float opl = m_fxChannelViews[i].m_fader->getPeak_L(); - const float opr = m_fxChannelViews[i].m_fader->getPeak_R(); + const float opl = m_fxChannelViews[i]->m_fader->getPeak_L(); + const float opr = m_fxChannelViews[i]->m_fader->getPeak_R(); const float fall_off = 1.2; if( m->m_fxChannels[i]->m_peakLeft > opl ) { - m_fxChannelViews[i].m_fader->setPeak_L( + m_fxChannelViews[i]->m_fader->setPeak_L( m->m_fxChannels[i]->m_peakLeft ); } else { - m_fxChannelViews[i].m_fader->setPeak_L( opl/fall_off ); + m_fxChannelViews[i]->m_fader->setPeak_L( opl/fall_off ); } if( m->m_fxChannels[i]->m_peakRight > opr ) { - m_fxChannelViews[i].m_fader->setPeak_R( + m_fxChannelViews[i]->m_fader->setPeak_R( m->m_fxChannels[i]->m_peakRight ); } else { - m_fxChannelViews[i].m_fader->setPeak_R( opr/fall_off ); + m_fxChannelViews[i]->m_fader->setPeak_R( opr/fall_off ); } } } diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index 453193dbd..adfb00918 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -56,7 +56,7 @@ #include "plugin_browser.h" #include "SideBar.h" #include "config_mgr.h" -#include "mixer.h" +#include "Mixer.h" #include "project_notes.h" #include "setup_dialog.h" #include "AudioDummy.h" @@ -84,7 +84,8 @@ MainWindow::MainWindow() : m_workspace( NULL ), m_templatesMenu( NULL ), m_recentlyOpenedProjectsMenu( NULL ), - m_toolsMenu( NULL ) + m_toolsMenu( NULL ), + m_autoSaveTimer( this ) { setAttribute( Qt::WA_DeleteOnClose ); @@ -154,6 +155,10 @@ MainWindow::MainWindow() : m_updateTimer.start( 1000 / 20, this ); // 20 fps + // connect auto save + connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave())); + m_autoSaveTimer.start(1000 * 60); // 1 minute + m_welcomeScreen = new WelcomeScreen( this ); m_welcomeScreen->setVisible( false ); } @@ -1007,7 +1012,7 @@ bool MainWindow::saveProject() } else { - engine::getSong()->saveProject(); + engine::getSong()->guiSaveProject(); } return true; } @@ -1036,7 +1041,7 @@ bool MainWindow::saveProjectAs() if( sfd.exec () == QFileDialog::Accepted && !sfd.selectedFiles().isEmpty() && sfd.selectedFiles()[0] != "" ) { - engine::getSong()->saveProjectAs( + engine::getSong()->guiSaveProjectAs( sfd.selectedFiles()[0] ); return true; } @@ -1195,6 +1200,9 @@ void MainWindow::closeEvent( QCloseEvent * _ce ) { if( mayChangeProject() ) { + // delete recovery file + QDir working(configManager::inst()->workingDir()); + working.remove("recover.mmp"); _ce->accept(); } else @@ -1400,7 +1408,7 @@ void MainWindow::keyReleaseEvent( QKeyEvent * _ke ) -void MainWindow::timerEvent( QTimerEvent * ) +void MainWindow::timerEvent( QTimerEvent * _te) { emit periodicUpdate(); } @@ -1470,9 +1478,9 @@ void MainWindow::browseHelp() void MainWindow::setHighQuality( bool _hq ) { - engine::getMixer()->changeQuality( mixer::qualitySettings( - _hq ? mixer::qualitySettings::Mode_HighQuality : - mixer::qualitySettings::Mode_Draft ) ); + /*engine::getMixer()->changeQuality( Mixer::qualitySettings( + _hq ? Mixer::qualitySettings::Mode_HighQuality : + Mixer::qualitySettings::Mode_Draft ) );*/ } @@ -1570,6 +1578,13 @@ void MainWindow::toggleRecordAutomation( bool _recording ) +void MainWindow::autoSave() +{ + QDir work(configManager::inst()->workingDir()); + engine::getSong()->saveProjectFile(work.absoluteFilePath("recover.mmp")); +} + + #include "moc_MainWindow.cxx" /* vim: set tw=0 noexpandtab: */ diff --git a/src/gui/SendButtonIndicator.cpp b/src/gui/SendButtonIndicator.cpp new file mode 100644 index 000000000..a932f136a --- /dev/null +++ b/src/gui/SendButtonIndicator.cpp @@ -0,0 +1,53 @@ +#include "SendButtonIndicator.h" + +#include "engine.h" +#include "FxMixer.h" +#include "Model.h" + +SendButtonIndicator:: SendButtonIndicator( QWidget * _parent, FxLine * _owner, + FxMixerView * _mv) : + QLabel( _parent ), + m_parent( _owner ), + m_mv( _mv ) +{ + qpmOff = embed::getIconPixmap("mixer_send_off", 23, 16); + qpmOn = embed::getIconPixmap("mixer_send_on", 23, 16); + + // don't do any initializing yet, because the FxMixerView and FxLine + // that were passed to this constructor are not done with their constructors + // yet. + +} + +void SendButtonIndicator::mousePressEvent( QMouseEvent * e ) +{ + FxMixer * mix = engine::fxMixer(); + int from = m_mv->currentFxLine()->channelIndex(); + int to = m_parent->channelIndex(); + FloatModel * sendModel = mix->channelSendModel(from, to); + if( sendModel == NULL ) + { + // not sending. create a mixer send. + mix->createChannelSend( from, to ); + } + else + { + // sending. delete the mixer send. + mix->deleteChannelSend( from, to ); + } + + m_mv->updateFxLine(m_parent->channelIndex()); + updateLightStatus(); +} + +FloatModel * SendButtonIndicator::getSendModel() +{ + FxMixer * mix = engine::fxMixer(); + return mix->channelSendModel( + m_mv->currentFxLine()->channelIndex(), m_parent->channelIndex()); +} + +void SendButtonIndicator::updateLightStatus() +{ + setPixmap( getSendModel() == NULL ? qpmOff : qpmOn ); +} diff --git a/src/gui/classic_style.cpp b/src/gui/classic_style.cpp index 602fcdf78..9160c9028 100644 --- a/src/gui/classic_style.cpp +++ b/src/gui/classic_style.cpp @@ -279,7 +279,7 @@ int ClassicStyle::pixelMetric( PixelMetric _metric, void ClassicStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active ) + const QString & _name, bool _active, bool _sendToThis ) { int width = _fxLine->rect().width(); int height = _fxLine->rect().height(); @@ -293,10 +293,18 @@ void ClassicStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine, p->setPen( QColor( 20, 24, 32 ) ); p->drawRect( 0, 0, width-1, height-1 ); + // draw the mixer send background + if( _sendToThis ) + { + p->drawPixmap(2, 0, 28, 56, + embed::getIconPixmap("send_bg_arrow", 28, 56)); + } + + // draw the channel name p->rotate( -90 ); p->setPen( _active ? QColor( 0, 255, 0 ) : Qt::white ); p->setFont( pointSizeF( _fxLine->font(), 7.5f ) ); - p->drawText( -90, 20, _name ); + p->drawText( -145, 20, _name ); } void ClassicStyle::drawTrackContentBackground(QPainter * _painter, diff --git a/src/gui/cusis_style.cpp b/src/gui/cusis_style.cpp index 87a739a27..a78d14762 100644 --- a/src/gui/cusis_style.cpp +++ b/src/gui/cusis_style.cpp @@ -881,7 +881,7 @@ int CusisStyle::pixelMetric( PixelMetric _metric, const QStyleOption * _option, void CusisStyle::drawFxLine( QPainter * _painter, const QWidget *_fxLine, - const QString & _name, bool _active ) + const QString & _name, bool _active, bool _sendToThis ) { int width = _fxLine->rect().width(); int height = _fxLine->rect().height(); diff --git a/src/gui/setup_dialog.cpp b/src/gui/setup_dialog.cpp index 10d783f69..05c813101 100644 --- a/src/gui/setup_dialog.cpp +++ b/src/gui/setup_dialog.cpp @@ -39,7 +39,6 @@ #include "tab_button.h" #include "tab_widget.h" #include "gui_templates.h" -#include "mixer.h" #include "ProjectJournal.h" #include "config_mgr.h" #include "embed.h" diff --git a/src/gui/song_editor.cpp b/src/gui/song_editor.cpp index 5bf826549..697b2b8af 100644 --- a/src/gui/song_editor.cpp +++ b/src/gui/song_editor.cpp @@ -35,6 +35,7 @@ #include +#include "AudioOutputContext.h" #include "song_editor.h" #include "combobox.h" #include "embed.h" @@ -43,7 +44,7 @@ #include "timeline.h" #include "tool_button.h" #include "tooltip.h" -#include "AudioDevice.h" +#include "AudioBackend.h" #include "piano_roll.h" @@ -130,7 +131,7 @@ songEditor::songEditor( song * _song, songEditor * & _engine_ptr ) : m_recordButton->setDisabled( true ); // disable record buttons if capturing is not supported - if( !engine::getMixer()->audioDev()->supportsCapture() ) + if( !engine::mixer()->audioOutputContext()->audioBackend()->supportsCapture() ) { m_recordButton->setDisabled( true ); m_recordAccompanyButton->setDisabled( true ); diff --git a/src/gui/widgets/EffectRackView.cpp b/src/gui/widgets/EffectRackView.cpp index f78c3d7e1..bd59656b6 100644 --- a/src/gui/widgets/EffectRackView.cpp +++ b/src/gui/widgets/EffectRackView.cpp @@ -43,7 +43,7 @@ EffectRackView::EffectRackView( EffectChain * _model, QWidget * _parent ) : m_mainLayout = new QVBoxLayout( this ); m_mainLayout->setSpacing( 0 ); - m_mainLayout->setMargin( 5 ); + m_mainLayout->setMargin( 0 ); m_effectsGroupBox = new groupBox( tr( "EFFECTS CHAIN" ) ); m_mainLayout->addWidget( m_effectsGroupBox ); diff --git a/src/gui/widgets/EnvelopeAndLfoView.cpp b/src/gui/widgets/EnvelopeAndLfoView.cpp index cd68ad36c..dbc1f48c7 100644 --- a/src/gui/widgets/EnvelopeAndLfoView.cpp +++ b/src/gui/widgets/EnvelopeAndLfoView.cpp @@ -33,7 +33,7 @@ #include "gui_templates.h" #include "knob.h" #include "led_checkbox.h" -#include "mixer.h" +#include "Mixer.h" #include "mmp.h" #include "Oscillator.h" #include "pixmap_button.h" @@ -482,7 +482,7 @@ void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) int graphYBase = LFO_GRAPH_Y + 3 + lfoGraphHeight / 2; const float framesForGraph = SECS_PER_LFO_OSCILLATION * - engine::getMixer()->baseSampleRate() / 10; + engine::mixer()->baseSampleRate() / 10; const float lfoGrayAmount = fabsf( m_lfoAmountKnob->value() ); p.setPen( QPen( QColor::fromHsvF( diff --git a/src/gui/widgets/InstrumentMidiIOView.cpp b/src/gui/widgets/InstrumentMidiIOView.cpp index 7db26499b..d8572a03d 100644 --- a/src/gui/widgets/InstrumentMidiIOView.cpp +++ b/src/gui/widgets/InstrumentMidiIOView.cpp @@ -33,7 +33,7 @@ #include "gui_templates.h" #include "lcd_spinbox.h" #include "MidiClient.h" -#include "mixer.h" +#include "Mixer.h" #include "tooltip.h" @@ -93,7 +93,7 @@ InstrumentMidiIOView::InstrumentMidiIOView( QWidget * _parent ) : m_outputProgramSpinBox, SLOT( setEnabled( bool ) ) ); - if( !engine::getMixer()->midiClient()->isRaw() ) + if( !engine::mixer()->midiClient()->isRaw() ) { m_rpBtn = new QToolButton( m_midiInputGroupBox ); m_rpBtn->setText( tr( "MIDI devices to receive MIDI events from" ) ); diff --git a/src/gui/widgets/cpuload_widget.cpp b/src/gui/widgets/cpuload_widget.cpp index 5856bfd6f..7b4db3a29 100644 --- a/src/gui/widgets/cpuload_widget.cpp +++ b/src/gui/widgets/cpuload_widget.cpp @@ -2,8 +2,8 @@ * cpuload_widget.cpp - widget for displaying CPU-load (partly based on * Hydrogen's CPU-load-widget) * - * Copyright (c) 2005-2007 Tobias Doerffel - * + * Copyright (c) 2005-2009 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 @@ -29,7 +29,7 @@ #include "cpuload_widget.h" #include "embed.h" #include "engine.h" -#include "mixer.h" +#include "Mixer.h" cpuloadWidget::cpuloadWidget( QWidget * _parent ) : @@ -90,7 +90,7 @@ void cpuloadWidget::paintEvent( QPaintEvent * ) void cpuloadWidget::updateCpuLoad() { // smooth load-values a bit - Uint8 new_load = ( m_currentLoad + engine::getMixer()->cpuLoad() ) / 2; + int new_load = ( m_currentLoad + engine::mixer()->cpuLoad() ) / 2; if( new_load != m_currentLoad ) { m_currentLoad = new_load; diff --git a/src/gui/widgets/knob.cpp b/src/gui/widgets/knob.cpp index 31a772c1b..72a57c735 100644 --- a/src/gui/widgets/knob.cpp +++ b/src/gui/widgets/knob.cpp @@ -492,7 +492,11 @@ void knob::mouseMoveEvent( QMouseEvent * _me ) void knob::mouseReleaseEvent( QMouseEvent * /* _me*/ ) { - model()->addJournalEntryFromOldToCurVal(); + AutomatableModel * thisModel = model(); + if( thisModel ) + { + thisModel->addJournalEntryFromOldToCurVal(); + } m_buttonPressed = false; diff --git a/src/gui/widgets/visualization_widget.cpp b/src/gui/widgets/visualization_widget.cpp index dc017300f..e1de1d384 100644 --- a/src/gui/widgets/visualization_widget.cpp +++ b/src/gui/widgets/visualization_widget.cpp @@ -29,6 +29,7 @@ #include "visualization_widget.h" #include "gui_templates.h" #include "MainWindow.h" +#include "Mixer.h" #include "embed.h" #include "engine.h" #include "tooltip.h" @@ -40,16 +41,16 @@ visualizationWidget::visualizationWidget( const QPixmap & _bg, QWidget * _p, visualizationTypes _vtype ) : QWidget( _p ), s_background( _bg ), - m_points( new QPointF[engine::getMixer()->framesPerPeriod()] ), + m_points( new QPointF[engine::mixer()->framesPerPeriod()] ), m_active( false ) { setFixedSize( s_background.width(), s_background.height() ); setAttribute( Qt::WA_OpaquePaintEvent, true ); - const fpp_t frames = engine::getMixer()->framesPerPeriod(); + const fpp_t frames = engine::mixer()->framesPerPeriod(); m_buffer = new sampleFrame[frames]; - engine::getMixer()->clearAudioBuffer( m_buffer, frames ); + engine::mixer()->clearAudioBuffer( m_buffer, frames ); toolTip::add( this, tr( "click to enable/disable visualization of " @@ -72,12 +73,11 @@ void visualizationWidget::updateAudioBuffer() { if( !engine::getSong()->isExporting() ) { - engine::getMixer()->lock(); - const surroundSampleFrame * c = engine::getMixer()-> - currentReadBuffer(); - const fpp_t fpp = engine::getMixer()->framesPerPeriod(); + engine::mixer()->lock(); + const surroundSampleFrame * c = engine::mixer()->currentReadBuffer(); + const fpp_t fpp = engine::mixer()->framesPerPeriod(); memcpy( m_buffer, c, sizeof( surroundSampleFrame ) * fpp ); - engine::getMixer()->unlock(); + engine::mixer()->unlock(); } } @@ -92,7 +92,7 @@ void visualizationWidget::setActive( bool _active ) connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) ); - connect( engine::getMixer(), + connect( engine::mixer(), SIGNAL( nextAudioBuffer() ), this, SLOT( updateAudioBuffer() ) ); } @@ -101,7 +101,7 @@ void visualizationWidget::setActive( bool _active ) disconnect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) ); - disconnect( engine::getMixer(), + disconnect( engine::mixer(), SIGNAL( nextAudioBuffer() ), this, SLOT( updateAudioBuffer() ) ); // we have to update (remove last waves), @@ -121,7 +121,7 @@ void visualizationWidget::paintEvent( QPaintEvent * ) if( m_active && !engine::getSong()->isExporting() ) { - float master_output = engine::getMixer()->masterGain(); + float master_output = engine::mixer()->masterGain(); int w = width()-4; const float half_h = -( height() - 6 ) / 3.0 * master_output - 1; int x_base = 2; @@ -131,10 +131,10 @@ void visualizationWidget::paintEvent( QPaintEvent * ) const fpp_t frames = - engine::getMixer()->framesPerPeriod(); + engine::mixer()->framesPerPeriod(); const float max_level = qMax( - mixer::peakValueLeft( m_buffer, frames ), - mixer::peakValueRight( m_buffer, frames ) ); + Mixer::peakValueLeft( m_buffer, frames ), + Mixer::peakValueRight( m_buffer, frames ) ); // and set color according to that... LmmsStyle::ColorRole levelColor; @@ -165,7 +165,7 @@ void visualizationWidget::paintEvent( QPaintEvent * ) { m_points[frame] = QPointF( x_base + (float) frame * xd, - y_base + ( mixer::clip( + y_base + ( Mixer::clip( m_buffer[frame][ch] ) * half_h ) ); } diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index cbddc519e..252980448 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -103,13 +103,14 @@ InstrumentTrack::InstrumentTrack( trackContainer * _tc ) : this, tr( "Panning" ) ), m_pitchModel( 0, -100, 100, 1, this, tr( "Pitch" ) ), m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ), - m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ), + m_effectChannelModel( 0, 0, 0, this, tr( "FX channel" ) ), // change this so it's a combo box, all the channels and then new. m_instrument( NULL ), m_soundShaping( this ), m_arpeggiator( this ), m_chordCreator( this ), m_piano( this ) { + m_effectChannelModel.setRange( 0, engine::fxMixer()->numChannels()-1, 1); connect( baseNoteModel(), SIGNAL( dataChanged() ), this, SLOT( updateBaseNote() ) ); connect( &m_pitchModel, SIGNAL( dataChanged() ), diff --git a/src/tracks/bb_track.cpp b/src/tracks/bb_track.cpp index 11a933bb2..96e920efa 100644 --- a/src/tracks/bb_track.cpp +++ b/src/tracks/bb_track.cpp @@ -33,7 +33,7 @@ #include "embed.h" #include "engine.h" #include "gui_templates.h" -#include "mixer.h" +#include "Mixer.h" #include "rename_dialog.h" #include "song.h" #include "song_editor.h" diff --git a/src/tracks/pattern.cpp b/src/tracks/pattern.cpp index 35a7e42d5..e713a6f90 100644 --- a/src/tracks/pattern.cpp +++ b/src/tracks/pattern.cpp @@ -33,6 +33,7 @@ #include #include +#include "AudioOutputContext.h" #include "pattern.h" #include "InstrumentTrack.h" #include "templates.h" @@ -735,15 +736,14 @@ patternFreezeThread::~patternFreezeThread() void patternFreezeThread::run() { - // create and install audio-sample-recorder + AudioOutputContext context( engine::mixer(), NULL, + engine::mixer()->defaultAudioOutputContext()->qualitySettings() ); + + // create and install AudioSampleRecorder bool b; - // we cannot create local copy, because at a later stage - // mixer::restoreAudioDevice(...) deletes old audio-dev and thus - // AudioSampleRecorder would be destroyed two times... - AudioSampleRecorder * freeze_recorder = new AudioSampleRecorder( - DEFAULT_CHANNELS, b, - engine::getMixer() ); - engine::getMixer()->setAudioDevice( freeze_recorder ); + AudioSampleRecorder freezeRecorder( DEFAULT_CHANNELS, b, &context ); + context.setAudioBackend( &freezeRecorder ); + engine::mixer()->setAudioOutputContext( &context ); // prepare stuff for playing correct things later engine::getSong()->playPattern( m_pattern, false ); @@ -761,7 +761,7 @@ void patternFreezeThread::run() while( ppp < m_pattern->length() && m_pattern->m_freezeAborted == false ) { - freeze_recorder->processNextBuffer(); + freezeRecorder.processNextBuffer(); m_statusDlg->setProgress( ppp * 100 / m_pattern->length() ); } m_statusDlg->setProgress( 100 ); @@ -769,7 +769,7 @@ void patternFreezeThread::run() while( engine::getMixer()->hasPlayHandles() && m_pattern->m_freezeAborted == false ) { - freeze_recorder->processNextBuffer(); + freezeRecorder.processNextBuffer(); } @@ -782,12 +782,13 @@ void patternFreezeThread::run() // create final sample-buffer if freezing was successful if( m_pattern->m_freezeAborted == false ) { - freeze_recorder->createSampleBuffer( + freezeRecorder.createSampleBuffer( &m_pattern->m_frozenPattern ); } // restore original audio-device - engine::getMixer()->restoreAudioDevice(); + engine::mixer()->setAudioOutputContext( + engine::mixer()->defaultAudioOutputContext() ); m_statusDlg->setProgress( -1 ); // we're finished