diff --git a/.travis/linux..install.sh b/.travis/linux..install.sh index ab4fba26395..efb8a5ada21 100755 --- a/.travis/linux..install.sh +++ b/.travis/linux..install.sh @@ -3,7 +3,7 @@ set -e PACKAGES="cmake libsndfile-dev fftw3-dev libvorbis-dev libogg-dev libmp3lame-dev - libasound2-dev libjack-jackd2-dev libsdl-dev libsamplerate0-dev libstk0-dev stk + libasound2-dev libjack-jackd2-dev libsdl2-dev libsamplerate0-dev libstk0-dev stk libfluidsynth-dev portaudio19-dev g++-multilib libfltk1.3-dev libgig-dev libsoundio-dev qt59base qt59translations qt59tools" diff --git a/.travis/linux.win.install_raw.sh b/.travis/linux.win.install_raw.sh new file mode 100755 index 00000000000..67568ac5057 --- /dev/null +++ b/.travis/linux.win.install_raw.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +while read -r PACKAGE_URL_AND_OPTS; do + pushd "$PWD" + IFS=',' read -ra PACKAGE_URL_AND_OPTS <<< "$PACKAGE_URL_AND_OPTS" + + PACKAGE_URL="${PACKAGE_URL_AND_OPTS[0]}" + OPTS="${PACKAGE_URL_AND_OPTS[1]}" + + echo "Downloading $PACKAGE_URL ..." + + mkdir PACKAGE_URL_TEMP + cd PACKAGE_URL_TEMP || exit 1 + + curl "$PACKAGE_URL" | tar xfz - + dir_name=$(ls) + cd "$dir_name" || exit 1 + + echo "Installing package $dir_name (make ${OPTS}) ..." + + # shellcheck disable=SC2086 + sudo make $OPTS + + popd + rm -rf PACKAGE_URL_TEMP +done <<< "$MANUAL_PACKAGES_URLS" + diff --git a/.travis/linux.win32.install.sh b/.travis/linux.win32.install.sh index 6e73e7abea5..fd83e6968c3 100755 --- a/.travis/linux.win32.install.sh +++ b/.travis/linux.win32.install.sh @@ -9,7 +9,9 @@ MINGW_PACKAGES="mingw32-x-sdl mingw32-x-libvorbis mingw32-x-fluidsynth mingw32-x mingw32-x-libgig mingw32-x-libsoundio mingw32-x-lame mingw32-x-qt5base" # swh build dependencies -SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl" +SWH_PACKAGES="perl libxml2-utils libxml-perl liblist-moreutils-perl libjack-dev" + +export MANUAL_PACKAGES_URLS="https://www.libsdl.org/release/SDL2-devel-2.0.7-mingw.tar.gz,install-package arch=i686-w64-mingw32 prefix=/opt/mingw32" export MINGW_PACKAGES @@ -20,6 +22,8 @@ PACKAGES="nsis cloog-isl libmpc3 qt4-linguist-tools mingw32 $MINGW_PACKAGES $SWH # shellcheck disable=SC2086 sudo apt-get install -y $PACKAGES +"$TRAVIS_BUILD_DIR/.travis/linux.win.install_raw.sh" + # ccache 3.2 is needed because mingw32-x-gcc is version 4.9, which causes cmake # to use @file command line passing, which in turn ccache 3.1.9 doesn't support pushd /tmp diff --git a/.travis/linux.win64.install.sh b/.travis/linux.win64.install.sh index 99ef7187f02..dc84601c488 100755 --- a/.travis/linux.win64.install.sh +++ b/.travis/linux.win64.install.sh @@ -10,6 +10,8 @@ MINGW_PACKAGES="mingw64-x-sdl mingw64-x-libvorbis mingw64-x-fluidsynth mingw64-x mingw64-x-fftw mingw64-x-flac mingw64-x-fltk mingw64-x-libsamplerate mingw64-x-pkgconfig mingw64-x-binutils mingw64-x-gcc mingw64-x-runtime mingw64-x-libgig mingw64-x-libsoundio mingw64-x-lame mingw64-x-qt5base" + +export MANUAL_PACKAGES_URLS="https://www.libsdl.org/release/SDL2-devel-2.0.7-mingw.tar.gz,install-package arch=x86_64-w64-mingw32 prefix=/opt/mingw64" export MINGW_PACKAGES @@ -17,3 +19,5 @@ export MINGW_PACKAGES # shellcheck disable=SC2086 sudo apt-get install -y $MINGW_PACKAGES + +"$TRAVIS_BUILD_DIR/.travis/linux.win.install_raw.sh" diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c67e9316c7..0c9703985ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,7 @@ ENDIF(LMMS_BUILD_APPLE) IF(LMMS_BUILD_WIN32) SET(WANT_ALSA OFF) - SET(WANT_JACK OFF) + SET(WANT_JACK ON) SET(WANT_PULSEAUDIO OFF) SET(WANT_PORTAUDIO OFF) SET(WANT_SOUNDIO OFF) @@ -154,8 +154,8 @@ SET(QT_LIBRARIES ) IF(LMMS_BUILD_LINUX) - FIND_PACKAGE(Qt5 COMPONENTS X11Extras REQUIRED) - LIST(APPEND QT_LIBRARIES Qt5::X11Extras) + FIND_PACKAGE(Qt5 COMPONENTS X11Extras REQUIRED) + LIST(APPEND QT_LIBRARIES Qt5::X11Extras) ENDIF() # Resolve Qt5::qmake to full path for use in packaging scripts @@ -372,23 +372,25 @@ ENDIF(NOT LMMS_HAVE_ALSA) # check for JACK IF(WANT_JACK) - PKG_CHECK_MODULES(JACK jack>=0.77) - IF(JACK_FOUND) - IF(WANT_WEAKJACK) - SET(LMMS_HAVE_WEAKJACK TRUE) - SET(WEAKJACK_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/src/3rdparty/weakjack/weakjack) - SET(STATUS_JACK "OK (weak linking enabled)") - SET(JACK_INCLUDE_DIRS "") - # use dlsym instead - SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) - ELSE() - SET(STATUS_JACK "OK") - ENDIF() + IF(WANT_WEAKJACK) + SET(LMMS_HAVE_WEAKJACK TRUE) + SET(WEAKJACK_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/src/3rdparty/weakjack/weakjack") + INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include") + SET(STATUS_JACK "OK (weak linking enabled)") + # use dlsym instead + SET(JACK_LIBRARIES ${CMAKE_DL_LIBS}) SET(LMMS_HAVE_JACK TRUE) - ELSE(JACK_FOUND) - SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similar) " - "if you require JACK support") - ENDIF(JACK_FOUND) + ELSE() + PKG_CHECK_MODULES(JACK jack>=0.77) + + IF(JACK_FOUND) + SET(STATUS_JACK "OK") + SET(LMMS_HAVE_JACK TRUE) + ELSE(JACK_FOUND) + SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similar) " + "if you require JACK support or enable weak jack") + ENDIF(JACK_FOUND) + ENDIF() ENDIF(WANT_JACK) diff --git a/cmake/nsis/CMakeLists.txt b/cmake/nsis/CMakeLists.txt index b21c920e5c3..6dcea4b8010 100644 --- a/cmake/nsis/CMakeLists.txt +++ b/cmake/nsis/CMakeLists.txt @@ -18,7 +18,7 @@ SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " \\\${IfNot} \\\${AtMostWin7} WriteRegDWORD HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\SideBySide\\\" \\\"PreferExternalManifest\\\" \\\"1\\\" \\\${EndIf} - " PARENT_SCOPE) + ExecWait '\\\"$INSTDIR\\\\Jack_v1.9.11_32_setup.exe\\\" /install'" PARENT_SCOPE) SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " \\\${unregisterExtension} \\\".mmp\\\" \\\"${PROJECT_NAME_UCASE} Project\\\" \\\${unregisterExtension} \\\".mmpz\\\" \\\"${PROJECT_NAME_UCASE} Project (compressed)\\\" diff --git a/data/themes/default/style.css b/data/themes/default/style.css index a4a31ab6b1a..9ce38b5e729 100644 --- a/data/themes/default/style.css +++ b/data/themes/default/style.css @@ -638,6 +638,7 @@ TrackContentObjectView { qproperty-textColor: #fff; qproperty-textBackgroundColor: rgba(0, 0, 0, 75); qproperty-textShadowColor: rgba(0,0,0,200); + qproperty-recordingBackgroundColor: rgba(139,25,25,0.38); qproperty-gradient: false; /* boolean property, set true to have a gradient */ font-size: 11px; diff --git a/include/AudioJack.h b/include/AudioJack.h index 83717f251ee..f310906473a 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -39,6 +39,8 @@ #include #include +#include + #include "AudioDevice.h" #include "AudioDeviceSetupWidget.h" @@ -103,6 +105,9 @@ private slots: void * _udata ); static void shutdownCallback( void * _udata ); + void connectJackPort (const char *outputPort, + const char *inputPort, + const char *portName); jack_client_t * m_client; @@ -112,7 +117,10 @@ private slots: MidiJack *m_midiClient; QVector m_outputPorts; + QVector m_inputPorts; jack_default_audio_sample_t * * m_tempOutBufs; + jack_default_audio_sample_t * * m_tempInBufs; + std::vector m_inBuffer; surroundSampleFrame * m_outBuf; f_cnt_t m_framesDoneInCurBuf; diff --git a/include/AudioPulseAudio.h b/include/AudioPulseAudio.h index 49674669134..4b15d1bb282 100644 --- a/include/AudioPulseAudio.h +++ b/include/AudioPulseAudio.h @@ -72,12 +72,16 @@ class AudioPulseAudio : public QThread, public AudioDevice void streamWriteCallback( pa_stream * s, size_t length ); + void streamReadCallback(pa_stream *s, size_t length); void signalConnected( bool connected ); pa_stream * m_s; pa_sample_spec m_sampleSpec; + pa_stream * m_recordStream; + pa_sample_spec m_recordSampleSpec; + private: virtual void startProcessing(); @@ -87,8 +91,6 @@ class AudioPulseAudio : public QThread, public AudioDevice volatile bool m_quit; - bool m_convertEndian; - bool m_connected; QSemaphore m_connectedSemaphore; diff --git a/include/AudioSampleRecorder.h b/include/AudioSampleRecorder.h deleted file mode 100644 index 69ac1949047..00000000000 --- a/include/AudioSampleRecorder.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * AudioSampleRecorder.h - device-class that implements recording - * audio-buffers into RAM - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - -#ifndef AUDIO_SAMPLE_RECORDER_H -#define AUDIO_SAMPLE_RECORDER_H - -#include -#include - -#include "AudioDevice.h" - -class SampleBuffer; - - -class AudioSampleRecorder : public AudioDevice -{ -public: - AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, - Mixer* mixer ); - virtual ~AudioSampleRecorder(); - - f_cnt_t framesRecorded() const; - void createSampleBuffer( SampleBuffer** sampleBuffer ); - - -private: - virtual void writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, - const float _master_gain ); - - typedef QList > BufferList; - BufferList m_buffers; - -} ; - - -#endif diff --git a/include/AudioSdl.h b/include/AudioSdl.h index 11942efda88..19d9606eb8b 100644 --- a/include/AudioSdl.h +++ b/include/AudioSdl.h @@ -78,17 +78,35 @@ class AudioSdl : public AudioDevice static void sdlAudioCallback( void * _udata, Uint8 * _buf, int _len ); void sdlAudioCallback( Uint8 * _buf, int _len ); +#ifdef LMMS_HAVE_SDL2 + static void sdlInputAudioCallback( void * _udata, Uint8 * _buf, int _len ); + void sdlInputAudioCallback( Uint8 * _buf, int _len ); +#endif + SDL_AudioSpec m_audioHandle; surroundSampleFrame * m_outBuf; + +#ifdef LMMS_HAVE_SDL2 + uint64_t m_currentBufferFramePos; + uint64_t m_currentBufferFramesCount; +#else Uint8 * m_convertedBuf; int m_convertedBufPos; int m_convertedBufSize; + bool m_outConvertEndian; +#endif - bool m_convertEndian; bool m_stopped; +#ifdef LMMS_HAVE_SDL2 + SDL_AudioDeviceID m_outputDevice; + + SDL_AudioSpec m_inputAudioHandle; + SDL_AudioDeviceID m_inputDevice; +#endif + } ; #endif diff --git a/include/AutomationTrack.h b/include/AutomationTrack.h index 195c21e9d4b..89f0fa5deef 100644 --- a/include/AutomationTrack.h +++ b/include/AutomationTrack.h @@ -61,6 +61,7 @@ class AutomationTrack : public Track class AutomationTrackView : public TrackView { + Q_OBJECT public: AutomationTrackView( AutomationTrack* at, TrackContainerView* tcv ); virtual ~AutomationTrackView() = default; @@ -68,6 +69,11 @@ class AutomationTrackView : public TrackView virtual void dragEnterEvent( QDragEnterEvent * _dee ); virtual void dropEvent( QDropEvent * _de ); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations) override; + +public slots: + void recordingOn(); + void recordingOff(); } ; diff --git a/include/Engine.h b/include/Engine.h index 2f940dfbf20..7c5ba5b01c1 100644 --- a/include/Engine.h +++ b/include/Engine.h @@ -31,6 +31,7 @@ #include "export.h" +#include "lmms_basics.h" class BBTrackContainer; class DummyTrackContainer; @@ -100,6 +101,9 @@ class EXPORT LmmsCore : public QObject { return s_framesPerTick; } + + static float framesPerTick (sample_rate_t sample_rate); + static void updateFramesPerTick(); static inline LmmsCore * inst() diff --git a/include/InstrumentTrack.h b/include/InstrumentTrack.h index 5ef604defb8..2a348374763 100644 --- a/include/InstrumentTrack.h +++ b/include/InstrumentTrack.h @@ -315,6 +315,8 @@ class InstrumentTrackView : public TrackView QMenu * createFxMenu( QString title, QString newFxLabel ); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations) override; + protected: virtual void dragEnterEvent( QDragEnterEvent * _dee ); virtual void dropEvent( QDropEvent * _de ); diff --git a/include/MemoryManager.h b/include/MemoryManager.h index 23561e4c0ac..1e4ada61780 100644 --- a/include/MemoryManager.h +++ b/include/MemoryManager.h @@ -65,6 +65,18 @@ struct MmAllocator }; +template< class T1, class T2 > +constexpr bool operator==( const MmAllocator&, const MmAllocator&) +{ + return true; +} + +template< class T1, class T2 > +constexpr bool operator!=( const MmAllocator&, const MmAllocator&) +{ + return false; +} + #define MM_OPERATORS \ public: \ static void * operator new ( size_t size ) \ diff --git a/include/Mixer.h b/include/Mixer.h index 49920589250..5433fa7e794 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -283,7 +283,7 @@ class EXPORT Mixer : public QObject return m_fifoWriter != NULL; } - void pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ); + void pushInputFrames(const sampleFrame *_ab, const f_cnt_t _frames); inline const sampleFrame * inputBuffer() { diff --git a/include/SampleBuffer.h b/include/SampleBuffer.h index 01c685fda1c..c0163991a2e 100644 --- a/include/SampleBuffer.h +++ b/include/SampleBuffer.h @@ -28,15 +28,19 @@ #include #include +#include #include +#include + #include "export.h" #include "interpolation.h" #include "lmms_basics.h" #include "lmms_math.h" #include "shared_object.h" #include "MemoryManager.h" +#include "JournallingObject.h" class QPainter; @@ -48,11 +52,14 @@ class QRect; // may need to be higher - conversely, to optimize, some may work with lower values const f_cnt_t MARGIN[] = { 64, 64, 64, 4, 4 }; -class EXPORT SampleBuffer : public QObject, public sharedObject +class EXPORT SampleBuffer : public QObject, + public JournallingObject { Q_OBJECT MM_OPERATORS public: + typedef std::vector> DataVector; + enum LoopMode { LoopOff = 0, LoopOn, @@ -102,15 +109,18 @@ class EXPORT SampleBuffer : public QObject, public sharedObject } ; - SampleBuffer(); // constructor which either loads sample _audio_file or decodes // base64-data out of string - SampleBuffer( const QString & _audio_file, bool _is_base64_data = false ); - SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ); - explicit SampleBuffer( const f_cnt_t _frames ); + SampleBuffer(const QString & _audio_file, bool _is_base64_data, sample_rate_t sampleRate=0); + SampleBuffer(DataVector &&movedData, sample_rate_t sampleRate); - virtual ~SampleBuffer(); + inline virtual QString nodeName() const override + { + return "samplebuffer"; + } + virtual void saveSettings(QDomDocument& doc, QDomElement& _this ) override; + virtual void loadSettings(const QDomElement& _this ) override; bool play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, @@ -123,6 +133,8 @@ class EXPORT SampleBuffer : public QObject, public sharedObject visualize( _p, _dr, _dr, _from_frame, _to_frame ); } + QPair visualizeToPoly( const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame = 0, f_cnt_t _to_frame = 0) const; + inline const QString & audioFile() const { return m_audioFile; @@ -168,7 +180,10 @@ class EXPORT SampleBuffer : public QObject, public sharedObject inline f_cnt_t frames() const { - return m_frames; + dataReadLock(); + auto size = internalFrames(); + dataUnlock(); + return size; } inline float amplification() const @@ -176,11 +191,6 @@ class EXPORT SampleBuffer : public QObject, public sharedObject return m_amplification; } - inline bool reversed() const - { - return m_reversed; - } - inline float frequency() const { return m_frequency; @@ -201,52 +211,42 @@ class EXPORT SampleBuffer : public QObject, public sharedObject m_frequency = _freq; } - inline void setSampleRate( sample_rate_t _rate ) - { - m_sampleRate = _rate; - } - inline const sampleFrame * data() const { - return m_data; + return m_data.data (); } QString openAudioFile() const; QString openAndSetAudioFile(); QString openAndSetWaveformFile(); - QString & toBase64( QString & _dst ) const; - - - // protect calls from the GUI to this function with dataReadLock() and - // dataUnlock() - SampleBuffer * resample( const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ); - - void normalizeSampleRate( const sample_rate_t _src_sr, - bool _keep_settings = false ); - // protect calls from the GUI to this function with dataReadLock() and // dataUnlock(), out of loops for efficiency inline sample_t userWaveSample( const float _sample ) const { - f_cnt_t frames = m_frames; - sampleFrame * data = m_data; - const float frame = _sample * frames; - f_cnt_t f1 = static_cast( frame ) % frames; + f_cnt_t dataFrames = internalFrames(); + const sampleFrame * data = this->data(); + const float frame = _sample * dataFrames; + f_cnt_t f1 = static_cast( frame ) % dataFrames; if( f1 < 0 ) { - f1 += frames; + f1 += dataFrames; } - return linearInterpolate( data[f1][0], data[ (f1 + 1) % frames ][0], fraction( frame ) ); + return linearInterpolate( data[f1][0], data[ (f1 + 1) % dataFrames ][0], fraction( frame ) ); + } + + bool tryDataReadLock () const + { + return m_varLock.tryLockForRead (); } - void dataReadLock() + + void dataReadLock() const { m_varLock.lockForRead(); } - void dataUnlock() + void dataUnlock() const { m_varLock.unlock(); } @@ -254,49 +254,116 @@ class EXPORT SampleBuffer : public QObject, public sharedObject static QString tryToMakeRelative( const QString & _file ); static QString tryToMakeAbsolute(const QString & file); + /** + * @brief Try to add data to the buffer, + * @param begin Beginning of an InputIterator. + * @param end End of an InputIterator. + * @param shouldLockMixer Should we call requestChangeInModel? + * @return + */ + bool tryAddData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true); + + /** + * @brief Add data to the buffer, + * @param begin Beginning of an InputIterator. + * @param end End of an InputIterator. + * @param shouldLockMixer Should we call requestChangeInModel? + * @return + */ + void addData(const DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer=true); + + /** + * @brief Reset the class and initialize it with @a newData. + * @param newData mm, that's the new data. + * @param dataSampleRate Sample rate for @a newData. + * @param shouldLockMixer Should we call requestChangeInModel? + */ + void resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true); + + /** + * @brief A non-blocking version of resetData. + * @param newData mm, that's the new data. + * @param dataSampleRate Sample rate for @a newData. + * @param shouldLockMixer Should we call requestChangeInModel? + * @return + */ + bool tryResetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer=true); + + /** + * @brief Just reverse the current buffer. + * @param shouldLockMixer Should we call requestChangeInModel? + * + * This function simply calls `std::reverse` on m_data. + */ + void reverse(bool shouldLockMixer=true); + + void loadFromBase64(const QString & _data , sample_rate_t sampleRate); + public slots: void setAudioFile( const QString & _audio_file ); - void loadFromBase64( const QString & _data ); void setStartFrame( const f_cnt_t _s ); void setEndFrame( const f_cnt_t _e ); void setAmplification( float _a ); - void setReversed( bool _on ); void sampleRateChanged(); -private: - void update( bool _keep_settings = false ); +protected: + void internalAddData(const DataVector &vector, sample_rate_t sampleRate); + void internalResetData(DataVector &&newData, sample_rate_t dataSampleRate); + + QString & toBase64( QString & _dst ) const; + inline void setSampleRate( sample_rate_t _rate ) + { + m_sampleRate = _rate; + } + + static sample_rate_t mixerSampleRate(); + + // HACK: libsamplerate < 0.1.8 doesn't get read-only variables + // as const. It has been fixed in 0.1.9 but has not been + // shipped for some distributions. + // This function just returns a variable that should have + // been `const` as non-const to bypass using 0.1.9. + inline static sampleFrame * libSampleRateSrc (const sampleFrame *ptr) + { + return const_cast(ptr); + } - void convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); - void directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _channels); + void changeAudioFile (QString audioFile); - f_cnt_t decodeSampleSF( const char * _f, sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _sample_rate ); + static DataVector convertIntToFloat(int_sample_t * & _ibuf, f_cnt_t _frames, int _channels); + + static DataVector decodeSampleSF(const char * _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate, QString &loadingWarning); #ifdef LMMS_HAVE_OGGVORBIS - f_cnt_t decodeSampleOGGVorbis( const char * _f, int_sample_t * & _buf, + static DataVector decodeSampleOGGVorbis( const char * _f, ch_cnt_t & _channels, - sample_rate_t & _sample_rate ); + sample_rate_t & _sample_rate); #endif - f_cnt_t decodeSampleDS( const char * _f, int_sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _sample_rate ); + static DataVector decodeSampleDS( const char * _f, ch_cnt_t & _channels, sample_rate_t & _sample_rate); + + inline sampleFrame * data() + { + return m_data.data (); + } + + size_t internalFrames() const + { + return m_data.size (); + } + QString m_audioFile; - sampleFrame * m_origData; - f_cnt_t m_origFrames; - sampleFrame * m_data; - QReadWriteLock m_varLock; - f_cnt_t m_frames; + DataVector m_data; + mutable QReadWriteLock m_varLock; f_cnt_t m_startFrame; f_cnt_t m_endFrame; f_cnt_t m_loopStartFrame; f_cnt_t m_loopEndFrame; float m_amplification; - bool m_reversed; float m_frequency; sample_rate_t m_sampleRate; + const sampleFrame * getSampleFragment( f_cnt_t _index, f_cnt_t _frames, LoopMode _loopmode, sampleFrame * * _tmp, @@ -306,6 +373,25 @@ public slots: f_cnt_t getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t _endf ) const; + static DataVector resampleData(const DataVector &inputData, sample_rate_t inputSampleRate, sample_rate_t requiredSampleRate); + + /** + * @brief Do the actions necessary before changing m_data. + * @param shouldLock Is anyone else might be using m_data? + */ + void beginBufferChange (bool shouldLock, bool shouldLockMixer=true); + + bool tryBeginBufferChange(bool shouldLock, bool shouldLockMixer=true); + + /** + * @brief Do some actions necessary after changing m_data. + * @param shouldUnlock The same value you've used on @a beginBufferChange. + * @param shouldKeepSettings Should we keep playback settings? + * @param bufferSampleRate The new m_data's sample rate. + */ + void doneBufferChange (bool shouldUnlock, + sample_rate_t bufferSampleRate, + bool shouldUnlockMixer=true); signals: void sampleUpdated(); diff --git a/include/SampleBufferVisualizer.h b/include/SampleBufferVisualizer.h new file mode 100644 index 00000000000..9a8d2113759 --- /dev/null +++ b/include/SampleBufferVisualizer.h @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2018 Shmuel H. (shmuelhazan0/at/gmail.com) + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef SAMPLEBUFFERVISUALIZER_H +#define SAMPLEBUFFERVISUALIZER_H + +#include +#include + +#include "MidiTime.h" +#include "SampleBuffer.h" + + +/** + * @brief A caching implementation of a visualizer for SampleBuffer. + * + * This implementation splits the tco into fragments of + * 192 ticks. Then, it draws them into @a PixmapCacheLine. + */ +class SampleBufferVisualizer +{ + struct PixmapCacheLine + { + /** + * @brief The visualization of this cache line. + */ + QPixmap pixmap; + + /** + * @brief The rectangle of @a pixmap. + */ + QRect rect; + + /** + * @brief How much time @a pixmap is representing. + */ + MidiTime totalTime; + }; + +public: + SampleBufferVisualizer(); + + enum class Operation { + Clear, + Append + }; + + /** + * @brief Update the cache before drawing it. + * @param sampleBuffer The sample buffer to visualize. + * @param sampleStartOffset Offset from the sample buffer's beginning. + * @param sampleLength The time we should + * visualize (can be different that the sample buffer's length). + * @param parentRect The rectangle we should paint in. + * @param pixelsPerTact Current value of pixels per tact. + * @param framesPerTact Current value of frames per tact. + * @param pen The pen setting we should paint with. + * @param operation Should we clear the cache or just append in the end? + */ + void update(const SampleBuffer &sampleBuffer, + MidiTime sampleStartOffset, + MidiTime sampleLength, + const QRect &parentRect, + float pixelsPerTact, f_cnt_t framesPerTact, + const QPen &pen, + Operation operation); + + /** + * @brief draw Draw the current cache into @a painter. + */ + void draw(QPainter &painter); + +private: + /** + * @brief appendMultipleTacts Add one or more cache lines or + * just add data to an existing + * cache line. + * @param sampleBuffer The sample buffer to visualize. + * @param sampleLength Time to visualize. + * @param parentRect The rectangle we should paint in. + * @param pen The pen setting we should paint with. + */ + void appendMultipleTacts(const SampleBuffer &sampleBuffer, + MidiTime sampleLength, + const QRect &parentRect, const QPen &pen); + + /** + * @brief appendTact Add data to m_currentPixmap. + * @param sampleBuffer The sample buffer to visualize. + * @param totalTime How much time we should visualize from + * m_currentPixmap.totalTime. + * @param parentRect The rectangle we should paint in. + * @param pen + * @param pen The pen setting we should paint with. + * @return true if we have painted at least one + * pixel; false otherwise. + */ + bool appendTact(const SampleBuffer &sampleBuffer, + const MidiTime &totalTime, + const QRect &parentRect, const QPen &pen, bool isLastInTact); + + /** + * @brief getRectForSampleFragment Construct a rectangle for + * visualization of a given + * MidiTime range. + * @param parentRect The rectangle we should be in. + * @param beginOffset Offset of the rectangle from @a + * parentRect's left. + * @param totalTime The actual time to be painted. + * @param forceNotZeroWidth Should we make sure rect.width != 0? + */ + QRect getRectForSampleFragment(QRect parentRect, + MidiTime beginOffset, + MidiTime totalTime, + bool forceNotZeroWidth=false); + + /** + * @brief pixelsPerTime Calculate how much pixels a visualization of + * @a time would take. + */ + float pixelsPerTime (const MidiTime &time) + { + return ((time * m_pixelsPerTact) / MidiTime::ticksPerTact()); + } + + /** + * How much time have we cached already (not including + * the cache in m_currentPixmap). + */ + MidiTime m_cachedTime{0}; + + /** + * X time drawing offset from the beginning. + */ + MidiTime m_generalPaintOffset{0}; + /** + * X pixel drawing offset from the beginning. + */ + int m_drawPixelOffset = 0; + + QList m_cachedPixmaps; + + /** + * The current cache line we've not finished yet. + */ + PixmapCacheLine m_currentPixmap; + + float m_pixelsPerTact = 0; + f_cnt_t m_framesPerTact = 0; +}; + +#endif // SAMPLEBUFFERVISUALIZER_H diff --git a/include/SamplePlayHandle.h b/include/SamplePlayHandle.h index 9a051ec7b1a..90537d4754d 100644 --- a/include/SamplePlayHandle.h +++ b/include/SamplePlayHandle.h @@ -29,6 +29,8 @@ #include "AutomatableModel.h" #include "PlayHandle.h" +#include + class BBTrack; class SampleTCO; class Track; @@ -38,7 +40,7 @@ class AudioPort; class SamplePlayHandle : public PlayHandle { public: - SamplePlayHandle( SampleBuffer* sampleBuffer ); + SamplePlayHandle(const std::shared_ptr &sampleBuffer, bool shouldCreateAudioPort=true); SamplePlayHandle( const QString& sampleFile ); SamplePlayHandle( SampleTCO* tco ); virtual ~SamplePlayHandle(); @@ -76,7 +78,7 @@ class SamplePlayHandle : public PlayHandle private: - SampleBuffer * m_sampleBuffer; + std::shared_ptr m_sampleBuffer; bool m_doneMayReturnTrue; f_cnt_t m_frame; diff --git a/include/SampleRecordHandle.h b/include/SampleRecordHandle.h index 22d9bf3156c..4e5b74b30ef 100644 --- a/include/SampleRecordHandle.h +++ b/include/SampleRecordHandle.h @@ -28,12 +28,15 @@ #include #include +#include #include "MidiTime.h" #include "PlayHandle.h" +#include "SampleTrack.h" +#include "SampleBuffer.h" + class BBTrack; -class SampleBuffer; class SampleTCO; class Track; @@ -41,7 +44,7 @@ class Track; class SampleRecordHandle : public PlayHandle { public: - SampleRecordHandle( SampleTCO* tco ); + SampleRecordHandle( SampleTCO* tco , MidiTime startRecordTimeOffset); virtual ~SampleRecordHandle(); virtual void play( sampleFrame * _working_buffer ); @@ -50,22 +53,43 @@ class SampleRecordHandle : public PlayHandle virtual bool isFromTrack( const Track * _track ) const; f_cnt_t framesRecorded() const; - void createSampleBuffer( SampleBuffer * * _sample_buf ); private: + void copyBufferFromMonoLeft( const sampleFrame * inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames); + void copyBufferFromMonoRight( const sampleFrame * inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames); + void copyBufferFromStereo( const sampleFrame * inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames); + + virtual void writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ); - typedef QList > bufferList; - bufferList m_buffers; f_cnt_t m_framesRecorded; - MidiTime m_minLength; + + /** + * @brief Total of ticks we've recorded. + */ + MidiTime m_timeRecorded; + + SampleBuffer::DataVector m_currentBuffer; Track * m_track; BBTrack * m_bbTrack; SampleTCO * m_tco; + // The recording type as it was when we started + // recording. + SampleTrack::RecordingChannel m_recordingChannel; + + // The offset from the start of m_track that the record has + // started from. + MidiTime m_startRecordTimeOffset; } ; diff --git a/include/SampleTrack.h b/include/SampleTrack.h index decf52f3f15..9d69d0ab3c8 100644 --- a/include/SampleTrack.h +++ b/include/SampleTrack.h @@ -29,6 +29,7 @@ #include "AudioPort.h" #include "Track.h" +#include "SampleBufferVisualizer.h" class EffectRackView; class Knob; @@ -53,7 +54,7 @@ class SampleTCO : public TrackContentObject return "sampletco"; } - SampleBuffer* sampleBuffer() + const std::shared_ptr sampleBuffer() { return m_sampleBuffer; } @@ -67,21 +68,25 @@ class SampleTCO : public TrackContentObject bool isPlaying() const; void setIsPlaying(bool isPlaying); + /** + * @brief isEmpty Check if this TCO has not content. + */ + bool isEmpty() const; + public slots: - void setSampleBuffer( SampleBuffer* sb ); void setSampleFile( const QString & _sf ); void updateLength(); void toggleRecord(); - void playbackPositionChanged(); - void updateTrackTcos(); +private slots: + void onSampleBufferChanged (); + private: - SampleBuffer* m_sampleBuffer; + std::shared_ptr m_sampleBuffer; BoolModel m_recordModel; bool m_isPlaying; - friend class SampleTCOView; @@ -117,7 +122,8 @@ public slots: private: SampleTCO * m_tco; - QPixmap m_paintPixmap; + + SampleBufferVisualizer m_sampleBufferVisualizer; } ; @@ -126,7 +132,15 @@ public slots: class SampleTrack : public Track { Q_OBJECT + mapPropertyFromModel(bool,isRecord,setRecord,m_recordModel); public: + enum RecordingChannel : int { + None, + MonoRight, + MonoLeft, + Stereo, + }; + SampleTrack( TrackContainer* tc ); virtual ~SampleTrack(); @@ -150,11 +164,20 @@ class SampleTrack : public Track return "sampletrack"; } + RecordingChannel recordingChannel() const; + void setRecordingChannel(const RecordingChannel &recordingChannel); + public slots: void updateTcos(); void setPlayingTcos( bool isPlaying ); + void beforeRecordOn (MidiTime time); + void toggleRecord(); + void playbackPositionChanged(); private: + IntModel m_recordingChannelModel; + + BoolModel m_recordModel; FloatModel m_volumeModel; FloatModel m_panningModel; AudioPort m_audioPort; @@ -175,6 +198,8 @@ class SampleTrackView : public TrackView virtual ~SampleTrackView(); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations) override; + public slots: void showEffects(); @@ -187,7 +212,11 @@ public slots: } +private slots: + void onRecordActionSelected (QAction *action); + private: + QAction *m_toggleRecordAction; EffectRackView * m_effectRack; QWidget * m_effWindow; Knob * m_volumeKnob; diff --git a/include/Song.h b/include/Song.h index 8b2c2131677..fdcf0085d04 100644 --- a/include/Song.h +++ b/include/Song.h @@ -321,6 +321,8 @@ public slots: void addBBTrack(); +signals: + void beforeRecordOn (MidiTime time); private slots: void insertBar(); diff --git a/include/SongEditor.h b/include/SongEditor.h index e31d0f862d8..7a7392b9698 100644 --- a/include/SongEditor.h +++ b/include/SongEditor.h @@ -32,6 +32,7 @@ #include "ActionGroup.h" #include "Editor.h" #include "TrackContainerView.h" +#include "SampleTrack.h" class QLabel; class QScrollBar; @@ -157,6 +158,8 @@ class SongEditorWindow : public Editor SongEditor* m_editor; + SampleTrack::RecordingChannel globalRecordChannel() const; + protected: virtual void resizeEvent( QResizeEvent * event ); @@ -169,6 +172,8 @@ protected slots: void lostFocus(); void adjustUiAfterProjectLoad(); + void onRecordChannelSelected(QAction *action); + signals: void playTriggered(); void resized(); @@ -187,6 +192,8 @@ protected slots: QAction* m_crtlAction; ComboBox * m_zoomingComboBox; + + SampleTrack::RecordingChannel m_globalRecordChannel = SampleTrack::RecordingChannel::Stereo; }; #endif diff --git a/include/Track.h b/include/Track.h index 6ac6e390f18..2b3c8ee7a7c 100644 --- a/include/Track.h +++ b/include/Track.h @@ -152,6 +152,9 @@ class TrackContentObject : public Model, public JournallingObject MidiTime startTimeOffset() const; void setStartTimeOffset( const MidiTime &startTimeOffset ); + bool isRecording() const; + void setIsRecording(bool value); + public slots: void copy(); void paste(); @@ -183,6 +186,8 @@ public slots: BoolModel m_soloModel; bool m_autoResize; + bool m_isRecording{false}; + bool m_selectViewOnCreate; friend class TrackContentObjectView; @@ -203,6 +208,7 @@ class TrackContentObjectView : public selectableObject, public ModelView Q_PROPERTY( QColor textBackgroundColor READ textBackgroundColor WRITE setTextBackgroundColor ) Q_PROPERTY( QColor textShadowColor READ textShadowColor WRITE setTextShadowColor ) Q_PROPERTY( QColor BBPatternBackground READ BBPatternBackground WRITE setBBPatternBackground ) + Q_PROPERTY( QColor recordingBackgroundColor READ recordingBackgroundColor WRITE setRecordingBackgroundColor ) Q_PROPERTY( bool gradient READ gradient WRITE setGradient ) public: @@ -223,6 +229,8 @@ class TrackContentObjectView : public selectableObject, public ModelView QColor textBackgroundColor() const; QColor textShadowColor() const; QColor BBPatternBackground() const; + QColor recordingBackgroundColor() const; + bool gradient() const; void setMutedColor( const QColor & c ); void setMutedBackgroundColor( const QColor & c ); @@ -232,11 +240,12 @@ class TrackContentObjectView : public selectableObject, public ModelView void setTextShadowColor( const QColor & c ); void setBBPatternBackground( const QColor & c ); void setGradient( const bool & b ); + void setRecordingBackgroundColor (const QColor & c ); // access needsUpdate member variable bool needsUpdate(); void setNeedsUpdate( bool b ); - + public slots: virtual bool close(); void cut(); @@ -308,6 +317,7 @@ protected slots: QColor m_textBackgroundColor; QColor m_textShadowColor; QColor m_BBPatternBackground; + QColor m_recordingBackgroundColor; bool m_gradient; bool m_needsUpdate; @@ -427,6 +437,8 @@ class TrackOperationsWidget : public QWidget ~TrackOperationsWidget(); + QPushButton *trackOps() const; + protected: virtual void mousePressEvent( QMouseEvent * me ); virtual void paintEvent( QPaintEvent * pe ); @@ -436,9 +448,6 @@ private slots: void cloneTrack(); void removeTrack(); void updateMenu(); - void toggleRecording(bool on); - void recordingOn(); - void recordingOff(); void clearTrack(); private: @@ -675,6 +684,8 @@ class TrackView : public QWidget, public ModelView, public JournallingObject virtual void update(); + virtual void updateTrackOperationsWidgetMenu (TrackOperationsWidget *trackOperations); + public slots: virtual bool close(); diff --git a/include/jack/control.h b/include/jack/control.h new file mode 100644 index 00000000000..bacc9ac6805 --- /dev/null +++ b/include/jack/control.h @@ -0,0 +1,631 @@ +/* -*- Mode: C ; c-basic-offset: 4 -*- */ +/* + JACK control API + + Copyright (C) 2008 Nedko Arnaudov + Copyright (C) 2008 GRAME + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ +/** + * @file jack/control.h + * @ingroup publicheader + * @brief JACK control API + * + */ + +#ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED +#define JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED + +#include +#include +#include +#if !defined(sun) && !defined(__sun__) +#include +#endif + +/** Parameter types, intentionally similar to jack_driver_param_type_t */ +typedef enum +{ + JackParamInt = 1, /**< @brief value type is a signed integer */ + JackParamUInt, /**< @brief value type is an unsigned integer */ + JackParamChar, /**< @brief value type is a char */ + JackParamString, /**< @brief value type is a string with max size of ::JACK_PARAM_STRING_MAX+1 chars */ + JackParamBool, /**< @brief value type is a boolean */ +} jackctl_param_type_t; + +/** Driver types */ +typedef enum +{ + JackMaster = 1, /**< @brief master driver */ + JackSlave /**< @brief slave driver */ +} jackctl_driver_type_t; + +/** @brief Max value that jackctl_param_type_t type can have */ +#define JACK_PARAM_MAX (JackParamBool + 1) + +/** @brief Max length of string parameter value, excluding terminating null char */ +#define JACK_PARAM_STRING_MAX 127 + +/** @brief Type for parameter value */ +/* intentionally similar to jack_driver_param_value_t */ +union jackctl_parameter_value +{ + uint32_t ui; /**< @brief member used for ::JackParamUInt */ + int32_t i; /**< @brief member used for ::JackParamInt */ + char c; /**< @brief member used for ::JackParamChar */ + char str[JACK_PARAM_STRING_MAX + 1]; /**< @brief member used for ::JackParamString */ + bool b; /**< @brief member used for ::JackParamBool */ +}; + +/** opaque type for server object */ +typedef struct jackctl_server jackctl_server_t; + +/** opaque type for driver object */ +typedef struct jackctl_driver jackctl_driver_t; + +/** opaque type for internal client object */ +typedef struct jackctl_internal jackctl_internal_t; + +/** opaque type for parameter object */ +typedef struct jackctl_parameter jackctl_parameter_t; + +/** opaque type for sigmask object */ +typedef struct jackctl_sigmask jackctl_sigmask_t; + +#ifdef __cplusplus +extern "C" { +#endif +#if 0 +} /* Adjust editor indent */ +#endif + +/** + * @defgroup ControlAPI The API for starting and controlling a JACK server + * @{ + */ + +/** + * Call this function to setup process signal handling. As a general + * rule, it is required for proper operation for the server object. + * + * @param flags signals setup flags, use 0 for none. Currently no + * flags are defined + * + * @return the configurated signal set. + */ +jackctl_sigmask_t * +jackctl_setup_signals( + unsigned int flags); + +/** + * Call this function to wait on a signal set. + * + * @param signals signals set to wait on + */ +void +jackctl_wait_signals( + jackctl_sigmask_t * signals); + +/** + * Call this function to create server object. + * + * @param on_device_acquire - Optional callback to be called before device is acquired. If false is returned, device usage will fail + * @param on_device_release - Optional callback to be called after device is released. + * + * @return server object handle, NULL if creation of server object + * failed. Successfully created server object must be destroyed with + * paired call to ::jackctl_server_destroy + */ +jackctl_server_t * +jackctl_server_create( + bool (* on_device_acquire)(const char * device_name), + void (* on_device_release)(const char * device_name)); + +/** + * Call this function to destroy server object. + * + * @param server server object handle to destroy + */ +void +jackctl_server_destroy( + jackctl_server_t * server); + +/** + * Call this function to open JACK server + * + * @param server server object handle + * @param driver driver to use + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_open( + jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to start JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_start( + jackctl_server_t * server); + +/** + * Call this function to stop JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_stop( + jackctl_server_t * server); + +/** + * Call this function to close JACK server + * + * @param server server object handle + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_close( + jackctl_server_t * server); + +/** + * Call this function to get list of available drivers. List node data + * pointers is a driver object handle (::jackctl_driver_t). + * + * @param server server object handle to get drivers for + * + * @return Single linked list of driver object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_drivers_list( + jackctl_server_t * server); + +/** + * Call this function to get list of server parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param server server object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_parameters( + jackctl_server_t * server); + +/** + * Call this function to get list of available internal clients. List node data + * pointers is a internal client object handle (::jackctl_internal_t). + * + * @param server server object handle to get internal clients for + * + * @return Single linked list of internal client object handles. Must not be + * modified. Always same for same server object. + */ +const JSList * +jackctl_server_get_internals_list( + jackctl_server_t * server); + +/** + * Call this function to load one internal client. + * (can be used when the server is running) + * + * @param server server object handle + * @param internal internal to use + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_load_internal( + jackctl_server_t * server, + jackctl_internal_t * internal); + +/** + * Call this function to unload one internal client. + * (can be used when the server is running) + * + * @param server server object handle + * @param internal internal to unload + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_unload_internal( + jackctl_server_t * server, + jackctl_internal_t * internal); + +/** + * Call this function to add a slave in the driver slave list. + * (cannot be used when the server is running that is between + * jackctl_server_start and jackctl_server_stop) + * + * @param server server object handle + * @param driver driver to add in the driver slave list. + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_add_slave(jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to remove a slave from the driver slave list. + * (cannot be used when the server is running that is between + * jackctl_server_start and jackctl_server_stop) + * + * @param server server object handle + * @param driver driver to remove from the driver slave list. + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_remove_slave(jackctl_server_t * server, + jackctl_driver_t * driver); + +/** + * Call this function to switch master driver. + * + * @param server server object handle + * @param driver driver to switch to + * + * @return success status: true - success, false - fail + */ +bool +jackctl_server_switch_master(jackctl_server_t * server, + jackctl_driver_t * driver); + + +/** + * Call this function to get name of driver. + * + * @param driver driver object handle to get name of + * + * @return driver name. Must not be modified. Always same for same + * driver object. + */ +const char * +jackctl_driver_get_name( + jackctl_driver_t * driver); + +/** + * Call this function to get type of driver. + * + * @param driver driver object handle to get name of + * + * @return driver type. Must not be modified. Always same for same + * driver object. + */ +jackctl_driver_type_t +jackctl_driver_get_type( + jackctl_driver_t * driver); + +/** + * Call this function to get list of driver parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param driver driver object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same driver object. + */ +const JSList * +jackctl_driver_get_parameters( + jackctl_driver_t * driver); + +/** + * Call this function to parse parameters for a driver. + * + * @param driver driver object handle + * @param argc parameter list len + * @param argv parameter list, as an array of char* + * + * @return success status: true - success, false - fail + */ +int +jackctl_driver_params_parse( + jackctl_driver_t * driver, + int argc, + char* argv[]); + +/** + * Call this function to get name of internal client. + * + * @param internal internal object handle to get name of + * + * @return internal name. Must not be modified. Always same for same + * internal object. + */ +const char * +jackctl_internal_get_name( + jackctl_internal_t * internal); + +/** + * Call this function to get list of internal parameters. List node data + * pointers is a parameter object handle (::jackctl_parameter_t). + * + * @param internal internal object handle to get parameters for + * + * @return Single linked list of parameter object handles. Must not be + * modified. Always same for same internal object. + */ +const JSList * +jackctl_internal_get_parameters( + jackctl_internal_t * internal); + +/** + * Call this function to get parameter name. + * + * @param parameter parameter object handle to get name of + * + * @return parameter name. Must not be modified. Always same for same + * parameter object. + */ +const char * +jackctl_parameter_get_name( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter short description. + * + * @param parameter parameter object handle to get short description of + * + * @return parameter short description. Must not be modified. Always + * same for same parameter object. + */ +const char * +jackctl_parameter_get_short_description( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter long description. + * + * @param parameter parameter object handle to get long description of + * + * @return parameter long description. Must not be modified. Always + * same for same parameter object. + */ +const char * +jackctl_parameter_get_long_description( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter type. + * + * @param parameter parameter object handle to get type of + * + * @return parameter type. Always same for same parameter object. + */ +jackctl_param_type_t +jackctl_parameter_get_type( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter character. + * + * @param parameter parameter object handle to get character of + * + * @return character. + */ +char +jackctl_parameter_get_id( + jackctl_parameter_t * parameter); + +/** + * Call this function to check whether parameter has been set, or its + * default value is being used. + * + * @param parameter parameter object handle to check + * + * @return true - parameter is set, false - parameter is using default + * value. + */ +bool +jackctl_parameter_is_set( + jackctl_parameter_t * parameter); + +/** + * Call this function to reset parameter to its default value. + * + * @param parameter parameter object handle to reset value of + * + * @return success status: true - success, false - fail + */ +bool +jackctl_parameter_reset( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter value. + * + * @param parameter parameter object handle to get value of + * + * @return parameter value. + */ +union jackctl_parameter_value +jackctl_parameter_get_value( + jackctl_parameter_t * parameter); + +/** + * Call this function to set parameter value. + * + * @param parameter parameter object handle to get value of + * @param value_ptr pointer to variable containing parameter value + * + * @return success status: true - success, false - fail + */ +bool +jackctl_parameter_set_value( + jackctl_parameter_t * parameter, + const union jackctl_parameter_value * value_ptr); + +/** + * Call this function to get parameter default value. + * + * @param parameter parameter object handle to get default value of + * + * @return parameter default value. + */ +union jackctl_parameter_value +jackctl_parameter_get_default_value( + jackctl_parameter_t * parameter); + +/** + * Call this function check whether parameter has range constraint. + * + * @param parameter object handle of parameter to check + * + * @return whether parameter has range constraint. + */ +bool +jackctl_parameter_has_range_constraint( + jackctl_parameter_t * parameter); + +/** + * Call this function check whether parameter has enumeration constraint. + * + * @param parameter object handle of parameter to check + * + * @return whether parameter has enumeration constraint. + */ +bool +jackctl_parameter_has_enum_constraint( + jackctl_parameter_t * parameter); + +/** + * Call this function get how many enumeration values parameter has. + * + * @param parameter object handle of parameter + * + * @return number of enumeration values + */ +uint32_t +jackctl_parameter_get_enum_constraints_count( + jackctl_parameter_t * parameter); + +/** + * Call this function to get parameter enumeration value. + * + * @param parameter object handle of parameter + * @param index index of parameter enumeration value + * + * @return enumeration value. + */ +union jackctl_parameter_value +jackctl_parameter_get_enum_constraint_value( + jackctl_parameter_t * parameter, + uint32_t index); + +/** + * Call this function to get parameter enumeration value description. + * + * @param parameter object handle of parameter + * @param index index of parameter enumeration value + * + * @return enumeration value description. + */ +const char * +jackctl_parameter_get_enum_constraint_description( + jackctl_parameter_t * parameter, + uint32_t index); + +/** + * Call this function to get parameter range. + * + * @param parameter object handle of parameter + * @param min_ptr pointer to variable receiving parameter minimum value + * @param max_ptr pointer to variable receiving parameter maximum value + */ +void +jackctl_parameter_get_range_constraint( + jackctl_parameter_t * parameter, + union jackctl_parameter_value * min_ptr, + union jackctl_parameter_value * max_ptr); + +/** + * Call this function to check whether parameter constraint is strict, + * i.e. whether supplying non-matching value will not work for sure. + * + * @param parameter parameter object handle to check + * + * @return whether parameter constraint is strict. + */ +bool +jackctl_parameter_constraint_is_strict( + jackctl_parameter_t * parameter); + +/** + * Call this function to check whether parameter has fake values, + * i.e. values have no user meaningful meaning and only value + * description is meaningful to user. + * + * @param parameter parameter object handle to check + * + * @return whether parameter constraint is strict. + */ +bool +jackctl_parameter_constraint_is_fake_value( + jackctl_parameter_t * parameter); + +/** + * Call this function to log an error message. + * + * @param format string + */ +void +jack_error( + const char *format, + ...); + +/** + * Call this function to log an information message. + * + * @param format string + */ +void +jack_info( + const char *format, + ...); + +/** + * Call this function to log an information message but only when + * verbose mode is enabled. + * + * @param format string + */ +void +jack_log( + const char *format, + ...); + +/* @} */ + +#if 0 +{ /* Adjust editor indent */ +#endif +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* #ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED */ diff --git a/include/jack/intclient.h b/include/jack/intclient.h new file mode 100644 index 00000000000..740c0f318ac --- /dev/null +++ b/include/jack/intclient.h @@ -0,0 +1,130 @@ +/* +* Copyright (C) 2004 Jack O'Quin +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation; either version 2.1 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +* +*/ + +#ifndef __jack_intclient_h__ +#define __jack_intclient_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/** + * Get an internal client's name. This is useful when @ref + * JackUseExactName was not specified on jack_internal_client_load() + * and @ref JackNameNotUnique status was returned. In that case, the + * actual name will differ from the @a client_name requested. + * + * @param client requesting JACK client's handle. + * + * @param intclient handle returned from jack_internal_client_load() + * or jack_internal_client_handle(). + * + * @return NULL if unsuccessful, otherwise pointer to the internal + * client name obtained from the heap via malloc(). The caller should + * jack_free() this storage when no longer needed. + */ +char *jack_get_internal_client_name (jack_client_t *client, + jack_intclient_t intclient); + +/** + * Return the @ref jack_intclient_t handle for an internal client + * running in the JACK server. + * + * @param client requesting JACK client's handle. + * + * @param client_name for the internal client of no more than + * jack_client_name_size() characters. The name scope is local to the + * current server. + * + * @param status (if non-NULL) an address for JACK to return + * information from this operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * @return Opaque internal client handle if successful. If 0, the + * internal client was not found, and @a *status includes the @ref + * JackNoSuchClient and @ref JackFailure bits. + */ +jack_intclient_t jack_internal_client_handle (jack_client_t *client, + const char *client_name, + jack_status_t *status); + +/** + * Load an internal client into the JACK server. + * + * Internal clients run inside the JACK server process. They can use + * most of the same functions as external clients. Each internal + * client is built as a shared object module, which must declare + * jack_initialize() and jack_finish() entry points called at load and + * unload times. See @ref inprocess.c for an example. + * + * @param client loading JACK client's handle. + * + * @param client_name of at most jack_client_name_size() characters + * for the internal client to load. The name scope is local to the + * current server. + * + * @param options formed by OR-ing together @ref JackOptions bits. + * Only the @ref JackLoadOptions bits are valid. + * + * @param status (if non-NULL) an address for JACK to return + * information from the load operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * Optional parameters: depending on corresponding [@a options + * bits] additional parameters may follow @a status (in this order). + * + * @arg [@ref JackLoadName] (char *) load_name is the shared + * object file from which to load the new internal client (otherwise + * use the @a client_name). + * + * @arg [@ref JackLoadInit] (char *) load_init an arbitary + * string passed to the internal client's jack_initialize() routine + * (otherwise NULL), of no more than @ref JACK_LOAD_INIT_LIMIT bytes. + * + * @return Opaque internal client handle if successful. If this is 0, + * the load operation failed, the internal client was not loaded, and + * @a *status includes the @ref JackFailure bit. + */ +jack_intclient_t jack_internal_client_load (jack_client_t *client, + const char *client_name, + jack_options_t options, + jack_status_t *status, ...); +/** + * Unload an internal client from a JACK server. This calls the + * intclient's jack_finish() entry point then removes it. See @ref + * inprocess.c for an example. + * + * @param client unloading JACK client's handle. + * + * @param intclient handle returned from jack_internal_client_load() or + * jack_internal_client_handle(). + * + * @return 0 if successful, otherwise @ref JackStatus bits. + */ +jack_status_t jack_internal_client_unload (jack_client_t *client, + jack_intclient_t intclient); + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_intclient_h__ */ diff --git a/include/jack/jack.h b/include/jack/jack.h new file mode 100644 index 00000000000..b147bab3b6f --- /dev/null +++ b/include/jack/jack.h @@ -0,0 +1,1470 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_h__ +#define __jack_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include + +/** + * Note: More documentation can be found in jack/types.h. + */ + + /************************************************************* + * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function + * added to the JACK API after the 0.116.2 release. + * + * Functions that predate this release are marked with + * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile + * time in a variety of ways. The default definition is empty, + * so that these symbols get normal linkage. If you wish to + * use all JACK symbols with weak linkage, include + * before jack.h. + *************************************************************/ + +#include + +/** + * Call this function to get version of the JACK, in form of several numbers + * + * @param major_ptr pointer to variable receiving major version of JACK. + * + * @param minor_ptr pointer to variable receiving minor version of JACK. + * + * @param major_ptr pointer to variable receiving micro version of JACK. + * + * @param major_ptr pointer to variable receiving protocol version of JACK. + * + */ +void +jack_get_version( + int *major_ptr, + int *minor_ptr, + int *micro_ptr, + int *proto_ptr) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Call this function to get version of the JACK, in form of a string + * + * @return Human readable string describing JACK version being used. + * + */ +const char * +jack_get_version_string(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @defgroup ClientFunctions Creating & manipulating clients + * @{ + */ + +/** + * Open an external client session with a JACK server. This interface + * is more complex but more powerful than jack_client_new(). With it, + * clients may choose which of several servers to connect, and control + * whether and how to start the server automatically, if it was not + * already running. There is also an option for JACK to generate a + * unique client name, when necessary. + * + * @param client_name of at most jack_client_name_size() characters. + * The name scope is local to each server. Unless forbidden by the + * @ref JackUseExactName option, the server will modify this name to + * create a unique variant, if needed. + * + * @param options formed by OR-ing together @ref JackOptions bits. + * Only the @ref JackOpenOptions bits are allowed. + * + * @param status (if non-NULL) an address for JACK to return + * information from the open operation. This status word is formed by + * OR-ing together the relevant @ref JackStatus bits. + * + * + * Optional parameters: depending on corresponding [@a options + * bits] additional parameters may follow @a status (in this order). + * + * @arg [@ref JackServerName] (char *) server_name selects + * from among several possible concurrent server instances. Server + * names are unique to each user. If unspecified, use "default" + * unless \$JACK_DEFAULT_SERVER is defined in the process environment. + * + * @return Opaque client handle if successful. If this is NULL, the + * open operation failed, @a *status includes @ref JackFailure and the + * caller is not a JACK client. + */ +jack_client_t * jack_client_open (const char *client_name, + jack_options_t options, + jack_status_t *status, ...) JACK_OPTIONAL_WEAK_EXPORT; + +/** +* \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN +* NEW JACK CLIENTS +* +* @deprecated Please use jack_client_open(). +*/ +jack_client_t * jack_client_new (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Disconnects an external client from a JACK server. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_client_close (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a JACK client name + * including the final NULL character. This value is a constant. + */ +int jack_client_name_size (void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return pointer to actual client name. This is useful when @ref + * JackUseExactName is not specified on open and @ref + * JackNameNotUnique status was returned. In that case, the actual + * name will differ from the @a client_name requested. + */ +char * jack_get_client_name (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Get the session ID for a client name. + * + * The session manager needs this to reassociate a client name to the session_id. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_get_uuid_for_client_name (jack_client_t *client, + const char *client_name) JACK_WEAK_EXPORT; + +/** + * Get the client name for a session_id. + * + * In order to snapshot the graph connections, the session manager needs to map + * session_ids to client names. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_get_client_name_by_uuid (jack_client_t *client, + const char *client_uuid ) JACK_WEAK_EXPORT; + +/** + * Load an internal client into the Jack server. + * + * Internal clients run inside the JACK server process. They can use + * most of the same functions as external clients. Each internal + * client must declare jack_initialize() and jack_finish() entry + * points, called at load and unload times. See inprocess.c for an + * example of how to write an internal client. + * + * @deprecated Please use jack_internal_client_load(). + * + * @param client_name of at most jack_client_name_size() characters. + * + * @param load_name of a shared object file containing the code for + * the new client. + * + * @param load_init an arbitary string passed to the jack_initialize() + * routine of the new client (may be NULL). + * + * @return 0 if successful. + */ +int jack_internal_client_new (const char *client_name, + const char *load_name, + const char *load_init) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Remove an internal client from a JACK server. + * + * @deprecated Please use jack_internal_client_unload(). + */ +void jack_internal_client_close (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Tell the Jack server that the program is ready to start processing + * audio. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_activate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to remove this @a client from the process + * graph. Also, disconnect all ports belonging to it, since inactive + * clients have no port connections. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_deactivate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return pid of client. If not available, 0 will be returned. + */ +int jack_get_client_pid (const char *name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the pthread ID of the thread running the JACK client side + * real-time code. + */ +jack_native_thread_t jack_client_thread_id (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @param client pointer to JACK client structure. + * + * Check if the JACK subsystem is running with -R (--realtime). + * + * @return 1 if JACK is running realtime, 0 otherwise + */ +int jack_is_realtime (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @defgroup NonCallbackAPI The non-callback API + * @{ + */ + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK CLIENTS. + * + * @deprecated Please use jack_cycle_wait() and jack_cycle_signal() functions. + */ +jack_nframes_t jack_thread_wait (jack_client_t *client, int status) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Wait until this JACK client should process data. + * + * @param client - pointer to a JACK client structure + * + * @return the number of frames of data to process + */ +jack_nframes_t jack_cycle_wait (jack_client_t* client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Signal next clients in the graph. + * + * @param client - pointer to a JACK client structure + * @param status - if non-zero, calling thread should exit + */ +void jack_cycle_signal (jack_client_t* client, int status) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a thread_callback in the RT thread. + * Typical use are in conjunction with @a jack_cycle_wait and @a jack_cycle_signal functions. + * The code in the supplied function must be suitable for real-time + * execution. That means that it cannot call functions that might + * block for a long time. This includes malloc, free, printf, + * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, + * pthread_cond_wait, etc, etc. See + * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 + * for more information. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. +*/ +int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup ClientCallbacks Setting Client Callbacks + * @{ + */ + +/** + * Tell JACK to call @a thread_init_callback once just after + * the creation of the thread in which all other callbacks + * will be handled. + * + * The code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code, causing JACK + * to remove that client from the process() graph. + */ +int jack_set_thread_init_callback (jack_client_t *client, + JackThreadInitCallback thread_init_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @param client pointer to JACK client structure. + * @param function The jack_shutdown function pointer. + * @param arg The arguments for the jack_shutdown function. + * + * Register a function (and argument) to be called if and when the + * JACK server shuts down the client thread. The function must + * be written as if it were an asynchonrous POSIX signal + * handler --- use only async-safe functions, and remember that it + * is executed from another thread. A typical function might + * set a flag or write to a pipe so that the rest of the + * application knows that the JACK client thread has shut + * down. + * + * NOTE: clients do not need to call this. It exists only + * to help more complex clients understand what is going + * on. It should be called before jack_client_activate(). + * + * NOTE: if a client calls this AND jack_on_info_shutdown(), then + * in case of a client thread shutdown, the callback + * passed to this function will not be called, and the one passed to + * jack_on_info_shutdown() will. + * + * NOTE: application should typically signal another thread to correctly + * finish cleanup, that is by calling "jack_client_close" + * (since "jack_client_close" cannot be called directly in the context + * of the thread that calls the shutdown callback). + */ +void jack_on_shutdown (jack_client_t *client, + JackShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @param client pointer to JACK client structure. + * @param function The jack_info_shutdown function pointer. + * @param arg The arguments for the jack_info_shutdown function. + * + * Register a function (and argument) to be called if and when the + * JACK server shuts down the client thread. The function must + * be written as if it were an asynchonrous POSIX signal + * handler --- use only async-safe functions, and remember that it + * is executed from another thread. A typical function might + * set a flag or write to a pipe so that the rest of the + * application knows that the JACK client thread has shut + * down. + * + * NOTE: clients do not need to call this. It exists only + * to help more complex clients understand what is going + * on. It should be called before jack_client_activate(). + * + * NOTE: if a client calls this AND jack_on_shutdown(), then + * in case of a client thread shutdown, the callback passed to + * jack_on_info_shutdown() will be called. + * + * NOTE: application should typically signal another thread to correctly + * finish cleanup, that is by calling "jack_client_close" + * (since "jack_client_close" cannot be called directly in the context + * of the thread that calls the shutdown callback). + */ +void jack_on_info_shutdown (jack_client_t *client, + JackInfoShutdownCallback shutdown_callback, void *arg) JACK_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a process_callback whenever there is + * work be done, passing @a arg as the second argument. + * + * The code in the supplied function must be suitable for real-time + * execution. That means that it cannot call functions that might + * block for a long time. This includes malloc, free, printf, + * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, + * pthread_cond_wait, etc, etc. See + * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 + * for more information. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_process_callback (jack_client_t *client, + JackProcessCallback process_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a freewheel_callback + * whenever we enter or leave "freewheel" mode, passing @a + * arg as the second argument. The first argument to the + * callback will be non-zero if JACK is entering freewheel + * mode, and zero otherwise. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_freewheel_callback (jack_client_t *client, + JackFreewheelCallback freewheel_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell JACK to call @a bufsize_callback whenever the size of the the + * buffer that will be passed to the @a process_callback is about to + * change. Clients that depend on knowing the buffer size must supply + * a @a bufsize_callback before activating themselves. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @param client pointer to JACK client structure. + * @param bufsize_callback function to call when the buffer size changes. + * @param arg argument for @a bufsize_callback. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_buffer_size_callback (jack_client_t *client, + JackBufferSizeCallback bufsize_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the Jack server to call @a srate_callback whenever the system + * sample rate changes. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_sample_rate_callback (jack_client_t *client, + JackSampleRateCallback srate_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a client_registration_callback whenever a + * client is registered or unregistered, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_client_registration_callback (jack_client_t *client, + JackClientRegistrationCallback + registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a registration_callback whenever a + * port is registered or unregistered, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ + int jack_set_port_registration_callback (jack_client_t *client, + JackPortRegistrationCallback + registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * Tell the JACK server to call @a connect_callback whenever a + * port is connected or disconnected, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_port_connect_callback (jack_client_t *client, + JackPortConnectCallback + connect_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * Tell the JACK server to call @a rename_callback whenever a + * port is renamed, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_port_rename_callback (jack_client_t *client, + JackPortRenameCallback + rename_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a graph_callback whenever the + * processing graph is reordered, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_graph_order_callback (jack_client_t *client, + JackGraphOrderCallback graph_callback, + void *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Tell the JACK server to call @a xrun_callback whenever there is a + * xrun, passing @a arg as a parameter. + * + * All "notification events" are received in a seperated non RT thread, + * the code in the supplied function does not need to be + * suitable for real-time execution. + * + * NOTE: this function cannot be called while the client is activated + * (after jack_activate has been called.) + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_xrun_callback (jack_client_t *client, + JackXRunCallback xrun_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * Tell the Jack server to call @a latency_callback whenever it + * is necessary to recompute the latencies for some or all + * Jack ports. + * + * @a latency_callback will be called twice each time it is + * needed, once being passed JackCaptureLatency and once + * JackPlaybackLatency. See @ref LatencyFunctions for + * the definition of each type of latency and related functions. + * + * IMPORTANT: Most JACK clients do NOT need to register a latency + * callback. + * + * Clients that meet any of the following conditions do NOT + * need to register a latency callback: + * + * - have only input ports + * - have only output ports + * - their output is totally unrelated to their input + * - their output is not delayed relative to their input + * (i.e. data that arrives in a given process() + * callback is processed and output again in the + * same callback) + * + * Clients NOT registering a latency callback MUST also + * satisfy this condition: + * + * - have no multiple distinct internal signal pathways + * + * This means that if your client has more than 1 input and + * output port, and considers them always "correlated" + * (e.g. as a stereo pair), then there is only 1 (e.g. stereo) + * signal pathway through the client. This would be true, + * for example, of a stereo FX rack client that has a + * left/right input pair and a left/right output pair. + * + * However, this is somewhat a matter of perspective. The + * same FX rack client could be connected so that its + * two input ports were connected to entirely separate + * sources. Under these conditions, the fact that the client + * does not register a latency callback MAY result + * in port latency values being incorrect. + * + * Clients that do not meet any of those conditions SHOULD + * register a latency callback. + * + * See the documentation for @ref jack_port_set_latency_range() + * on how the callback should operate. Remember that the @a mode + * argument given to the latency callback will need to be + * passed into @ref jack_port_set_latency_range() + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_latency_callback (jack_client_t *client, + JackLatencyCallback latency_callback, + void *) JACK_WEAK_EXPORT; +/*@}*/ + +/** + * @defgroup ServerClientControl Controlling & querying JACK server operation + * @{ + */ + +/** + * Start/Stop JACK's "freewheel" mode. + * + * When in "freewheel" mode, JACK no longer waits for + * any external event to begin the start of the next process + * cycle. + * + * As a result, freewheel mode causes "faster than realtime" + * execution of a JACK graph. If possessed, real-time + * scheduling is dropped when entering freewheel mode, and + * if appropriate it is reacquired when stopping. + * + * IMPORTANT: on systems using capabilities to provide real-time + * scheduling (i.e. Linux kernel 2.4), if onoff is zero, this function + * must be called from the thread that originally called jack_activate(). + * This restriction does not apply to other systems (e.g. Linux kernel 2.6 + * or OS X). + * + * @param client pointer to JACK client structure + * @param onoff if non-zero, freewheel mode starts. Otherwise + * freewheel mode ends. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_freewheel(jack_client_t* client, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Change the buffer size passed to the @a process_callback. + * + * This operation stops the JACK engine process cycle, then calls all + * registered @a bufsize_callback functions before restarting the + * process cycle. This will cause a gap in the audio flow, so it + * should only be done at appropriate stopping points. + * + * @see jack_set_buffer_size_callback() + * + * @param client pointer to JACK client structure. + * @param nframes new buffer size. Must be a power of two. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the sample rate of the jack system, as set by the user when + * jackd was started. + */ +jack_nframes_t jack_get_sample_rate (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the current maximum size that will ever be passed to the @a + * process_callback. It should only be used *before* the client has + * been activated. This size may change, clients that depend on it + * must register a @a bufsize_callback so they will be notified if it + * does. + * + * @see jack_set_buffer_size_callback() + */ +jack_nframes_t jack_get_buffer_size (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Old-style interface to become the timebase for the entire JACK + * subsystem. + * + * @deprecated This function still exists for compatibility with the + * earlier transport interface, but it does nothing. Instead, see + * transport.h and use jack_set_timebase_callback(). + * + * @return ENOSYS, function not implemented. + */ +int jack_engine_takeover_timebase (jack_client_t *) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * @return the current CPU load estimated by JACK. This is a running + * average of the time it takes to execute a full process cycle for + * all clients as a percentage of the real time available per cycle + * determined by the buffer size and sample rate. + */ +float jack_cpu_load (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup PortFunctions Creating & manipulating ports + * @{ + */ + +/** + * Create a new port for the client. This is an object used for moving + * data of any type in or out of the client. Ports may be connected + * in various ways. + * + * Each port has a short name. The port's full name contains the name + * of the client concatenated with a colon (:) followed by its short + * name. The jack_port_name_size() is the maximum length of this full + * name. Exceeding that will cause the port registration to fail and + * return NULL. + * + * The @a port_name must be unique among all ports owned by this client. + * If the name is not unique, the registration will fail. + * + * All ports have a type, which may be any non-NULL and non-zero + * length string, passed as an argument. Some port types are built + * into the JACK API, currently only JACK_DEFAULT_AUDIO_TYPE. + * + * @param client pointer to JACK client structure. + * @param port_name non-empty short name for the new port (not + * including the leading @a "client_name:"). Must be unique. + * @param port_type port type name. If longer than + * jack_port_type_size(), only that many characters are significant. + * @param flags @ref JackPortFlags bit mask. + * @param buffer_size must be non-zero if this is not a built-in @a + * port_type. Otherwise, it is ignored. + * + * @return jack_port_t pointer on success, otherwise NULL. + */ +jack_port_t * jack_port_register (jack_client_t *client, + const char *port_name, + const char *port_type, + unsigned long flags, + unsigned long buffer_size) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove the port from the client, disconnecting any existing + * connections. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_port_unregister (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * This returns a pointer to the memory area associated with the + * specified port. For an output port, it will be a memory area + * that can be written to; for an input port, it will be an area + * containing the data from the port's connection(s), or + * zero-filled. if there are multiple inbound connections, the data + * will be mixed appropriately. + * + * FOR OUTPUT PORTS ONLY : DEPRECATED in Jack 2.0 !! + * --------------------------------------------------- + * You may cache the value returned, but only between calls to + * your "blocksize" callback. For this reason alone, you should + * either never cache the return value or ensure you have + * a "blocksize" callback and be sure to invalidate the cached + * address from there. + * + * Caching output ports is DEPRECATED in Jack 2.0, due to some new optimization (like "pipelining"). + * Port buffers have to be retrieved in each callback for proper functionning. + */ +void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the UUID of the jack_port_t + * + * @see jack_uuid_to_string() to convert into a string representation + */ +jack_uuid_t jack_port_uuid (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the full name of the jack_port_t (including the @a + * "client_name:" prefix). + * + * @see jack_port_name_size(). + */ +const char * jack_port_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the short name of the jack_port_t (not including the @a + * "client_name:" prefix). + * + * @see jack_port_name_size(). + */ +const char * jack_port_short_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the @ref JackPortFlags of the jack_port_t. + */ +int jack_port_flags (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the @a port type, at most jack_port_type_size() characters + * including a final NULL. + */ +const char * jack_port_type (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + + /** + * @return the @a port type id. + */ +jack_port_type_id_t jack_port_type_id (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if the jack_port_t belongs to the jack_client_t. + */ +int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return number of connections to or from @a port. + * + * @pre The calling client must own @a port. + */ +int jack_port_connected (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if the locally-owned @a port is @b directly connected + * to the @a port_name. + * + * @see jack_port_name_size() + */ +int jack_port_connected_to (const jack_port_t *port, + const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return a null-terminated array of full port names to which the @a + * port is connected. If none, returns NULL. + * + * The caller is responsible for calling jack_free() on any non-NULL + * returned value. + * + * @param port locally owned jack_port_t pointer. + * + * @see jack_port_name_size(), jack_port_get_all_connections() + */ +const char ** jack_port_get_connections (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return a null-terminated array of full port names to which the @a + * port is connected. If none, returns NULL. + * + * The caller is responsible for calling jack_free() on any non-NULL + * returned value. + * + * This differs from jack_port_get_connections() in two important + * respects: + * + * 1) You may not call this function from code that is + * executed in response to a JACK event. For example, + * you cannot use it in a GraphReordered handler. + * + * 2) You need not be the owner of the port to get information + * about its connections. + * + * @see jack_port_name_size() + */ +const char ** jack_port_get_all_connections (const jack_client_t *client, + const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * + * @deprecated This function will be removed from a future version + * of JACK. Do not use it. There is no replacement. It has + * turned out to serve essentially no purpose in real-life + * JACK clients. + */ +int jack_port_tie (jack_port_t *src, jack_port_t *dst) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * + * @deprecated This function will be removed from a future version + * of JACK. Do not use it. There is no replacement. It has + * turned out to serve essentially no purpose in real-life + * JACK clients. + */ +int jack_port_untie (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN + * NEW JACK CLIENTS + * + * Modify a port's short name. May be called at any time. If the + * resulting full name (including the @a "client_name:" prefix) is + * longer than jack_port_name_size(), it will be truncated. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_set_name (jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Modify a port's short name. May NOT be called from a callback handling a server event. + * If the resulting full name (including the @a "client_name:" prefix) is + * longer than jack_port_name_size(), it will be truncated. + * + * @return 0 on success, otherwise a non-zero error code. + * + * This differs from jack_port_set_name() by triggering PortRename notifications to + * clients that have registered a port rename handler. + */ +int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set @a alias as an alias for @a port. May be called at any time. + * If the alias is longer than jack_port_name_size(), it will be truncated. + * + * After a successful call, and until JACK exits or + * @function jack_port_unset_alias() is called, @alias may be + * used as a alternate name for the port. + * + * Ports can have up to two aliases - if both are already + * set, this function will return an error. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_set_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove @a alias as an alias for @a port. May be called at any time. + * + * After a successful call, @a alias can no longer be + * used as a alternate name for the port. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_port_unset_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Get any aliases known for @port. + * + * @return the number of aliases discovered for the port + */ +int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for this @a port, turn input + * monitoring on or off. Otherwise, do nothing. + */ +int jack_port_request_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for this @a port_name, turn input + * monitoring on or off. Otherwise, do nothing. + * + * @return 0 on success, otherwise a non-zero error code. + * + * @see jack_port_name_size() + */ +int jack_port_request_monitor_by_name (jack_client_t *client, + const char *port_name, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * If @ref JackPortCanMonitor is set for a port, this function turns + * on input monitoring if it was off, and turns it off if only one + * request has been made to turn it on. Otherwise it does nothing. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_port_ensure_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return TRUE if input monitoring has been requested for @a port. + */ +int jack_port_monitoring_input (jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Establish a connection between two ports. + * + * When a connection exists, data written to the source port will + * be available to be read at the destination port. + * + * @pre The port types must be identical. + * + * @pre The @ref JackPortFlags of the @a source_port must include @ref + * JackPortIsOutput. + * + * @pre The @ref JackPortFlags of the @a destination_port must include + * @ref JackPortIsInput. + * + * @return 0 on success, EEXIST if the connection is already made, + * otherwise a non-zero error code + */ +int jack_connect (jack_client_t *client, + const char *source_port, + const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Remove a connection between two ports. + * + * @pre The port types must be identical. + * + * @pre The @ref JackPortFlags of the @a source_port must include @ref + * JackPortIsOutput. + * + * @pre The @ref JackPortFlags of the @a destination_port must include + * @ref JackPortIsInput. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_disconnect (jack_client_t *client, + const char *source_port, + const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Perform the same function as jack_disconnect() using port handles + * rather than names. This avoids the name lookup inherent in the + * name-based version. + * + * Clients connecting their own ports are likely to use this function, + * while generic connection clients (e.g. patchbays) would use + * jack_disconnect(). + */ +int jack_port_disconnect (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a full JACK port name + * including the final NULL character. This value is a constant. + * + * A port's full name contains the owning client name concatenated + * with a colon (:) followed by its short name and a NULL + * character. + */ +int jack_port_name_size(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the maximum number of characters in a JACK port type name + * including the final NULL character. This value is a constant. + */ +int jack_port_type_size(void) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the buffersize of a port of type @arg port_type. + * + * this function may only be called in a buffer_size callback. + */ +size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) JACK_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup LatencyFunctions Managing and determining latency + * + * The purpose of JACK's latency API is to allow clients to + * easily answer two questions: + * + * - How long has it been since the data read from a port arrived + * at the edge of the JACK graph (either via a physical port + * or being synthesized from scratch)? + * + * - How long will it be before the data written to a port arrives + * at the edge of a JACK graph? + + * To help answering these two questions, all JACK ports have two + * latency values associated with them, both measured in frames: + * + * capture latency: how long since the data read from + * the buffer of a port arrived at + * a port marked with JackPortIsTerminal. + * The data will have come from the "outside + * world" if the terminal port is also + * marked with JackPortIsPhysical, or + * will have been synthesized by the client + * that owns the terminal port. + * + * playback latency: how long until the data + * written to the buffer of port will reach a port + * marked with JackPortIsTerminal. + * + * Both latencies might potentially have more than one value + * because there may be multiple pathways to/from a given port + * and a terminal port. Latency is therefore generally + * expressed a min/max pair. + * + * In most common setups, the minimum and maximum latency + * are the same, but this design accomodates more complex + * routing, and allows applications (and thus users) to + * detect cases where routing is creating an anomalous + * situation that may either need fixing or more + * sophisticated handling by clients that care about + * latency. + * + * See also @ref jack_set_latency_callback for details on how + * clients that add latency to the signal path should interact + * with JACK to ensure that the correct latency figures are + * used. + * @{ + */ + +/** + * The port latency is zero by default. Clients that control + * physical hardware with non-zero latency should call this + * to set the latency to its correct value. Note that the value + * should include any systemic latency present "outside" the + * physical hardware controlled by the client. For example, + * for a client controlling a digital audio interface connected + * to an external digital converter, the latency setting should + * include both buffering by the audio interface *and* the converter. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by a latency callback that calls @ref + * jack_port_set_latency_range(). + */ +void jack_port_set_latency (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * return the latency range defined by @a mode for + * @a port, in frames. + * + * See @ref LatencyFunctions for the definition of each latency value. + * + * This is normally used in the LatencyCallback. + * and therefor safe to execute from callbacks. + */ +void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; + +/** + * set the minimum and maximum latencies defined by + * @a mode for @a port, in frames. + * + * See @ref LatencyFunctions for the definition of each latency value. + * + * This function should ONLY be used inside a latency + * callback. The client should determine the current + * value of the latency using @ref jack_port_get_latency_range() + * (called using the same mode as @a mode) + * and then add some number of frames to that reflects + * latency added by the client. + * + * How much latency a client adds will vary + * dramatically. For most clients, the answer is zero + * and there is no reason for them to register a latency + * callback and thus they should never call this + * function. + * + * More complex clients that take an input signal, + * transform it in some way and output the result but + * not during the same process() callback will + * generally know a single constant value to add + * to the value returned by @ref jack_port_get_latency_range(). + * + * Such clients would register a latency callback (see + * @ref jack_set_latency_callback) and must know what input + * ports feed which output ports as part of their + * internal state. Their latency callback will update + * the ports' latency values appropriately. + * + * A pseudo-code example will help. The @a mode argument to the latency + * callback will determine whether playback or capture + * latency is being set. The callback will use + * @ref jack_port_set_latency_range() as follows: + * + * \code + * jack_latency_range_t range; + * if (mode == JackPlaybackLatency) { + * foreach input_port in (all self-registered port) { + * jack_port_get_latency_range (port_feeding_input_port, JackPlaybackLatency, &range); + * range.min += min_delay_added_as_signal_flows_from port_feeding to input_port; + * range.max += max_delay_added_as_signal_flows_from port_feeding to input_port; + * jack_port_set_latency_range (input_port, JackPlaybackLatency, &range); + * } + * } else if (mode == JackCaptureLatency) { + * foreach output_port in (all self-registered port) { + * jack_port_get_latency_range (port_fed_by_output_port, JackCaptureLatency, &range); + * range.min += min_delay_added_as_signal_flows_from_output_port_to_fed_by_port; + * range.max += max_delay_added_as_signal_flows_from_output_port_to_fed_by_port; + * jack_port_set_latency_range (output_port, JackCaptureLatency, &range); + * } + * } + * \endcode + * + * In this relatively simple pseudo-code example, it is assumed that + * each input port or output is connected to only 1 output or input + * port respectively. + * + * If a port is connected to more than 1 other port, then the + * range.min and range.max values passed to @ref + * jack_port_set_latency_range() should reflect the minimum and + * maximum values across all connected ports. + * + * See the description of @ref jack_set_latency_callback for more + * information. + */ +void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; + +/** + * Request a complete recomputation of all port latencies. This + * can be called by a client that has just changed the internal + * latency of its port using jack_port_set_latency + * and wants to ensure that all signal pathways in the graph + * are updated with respect to the values that will be returned + * by jack_port_get_total_latency. It allows a client + * to change multiple port latencies without triggering a + * recompute for each change. + * + * @return zero for successful execution of the request. non-zero + * otherwise. + */ +int jack_recompute_total_latencies (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the time (in frames) between data being available or + * delivered at/to a port, and the time at which it arrived at or is + * delivered to the "other side" of the port. E.g. for a physical + * audio output port, this is the time between writing to the port and + * when the signal will leave the connector. For a physical audio + * input port, this is the time between the sound arriving at the + * connector and the corresponding frames being readable from the + * port. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_port_get_latency_range() in any existing + * use cases. + */ +jack_nframes_t jack_port_get_latency (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * The maximum of the sum of the latencies in every + * connection path that can be drawn between the port and other + * ports with the @ref JackPortIsTerminal flag set. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_port_get_latency_range() in any existing + * use cases. + */ +jack_nframes_t jack_port_get_total_latency (jack_client_t *client, + jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/** + * Request a complete recomputation of a port's total latency. This + * can be called by a client that has just changed the internal + * latency of its port using jack_port_set_latency + * and wants to ensure that all signal pathways in the graph + * are updated with respect to the values that will be returned + * by jack_port_get_total_latency. + * + * @return zero for successful execution of the request. non-zero + * otherwise. + * + * @deprecated This method will be removed in the next major + * release of JACK. It should not be used in new code, and should + * be replaced by jack_recompute_total_latencies() in any existing + * use cases. + */ +int jack_recompute_total_latency (jack_client_t*, jack_port_t* port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; + +/*@}*/ + +/** + * @defgroup PortSearching Looking up ports + * @{ + */ + +/** + * @param port_name_pattern A regular expression used to select + * ports by name. If NULL or of zero length, no selection based + * on name will be carried out. + * @param type_name_pattern A regular expression used to select + * ports by type. If NULL or of zero length, no selection based + * on type will be carried out. + * @param flags A value used to select ports by their flags. + * If zero, no selection based on flags will be carried out. + * + * @return a NULL-terminated array of ports that match the specified + * arguments. The caller is responsible for calling jack_free() any + * non-NULL returned value. + * + * @see jack_port_name_size(), jack_port_type_size() + */ +const char ** jack_get_ports (jack_client_t *client, + const char *port_name_pattern, + const char *type_name_pattern, + unsigned long flags) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return address of the jack_port_t named @a port_name. + * + * @see jack_port_name_size() + */ +jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return address of the jack_port_t of a @a port_id. + */ +jack_port_t * jack_port_by_id (jack_client_t *client, + jack_port_id_t port_id) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup TimeFunctions Handling time + * @{ + * + * JACK time is in units of 'frames', according to the current sample rate. + * The absolute value of frame times is meaningless, frame times have meaning + * only relative to each other. + */ + +/** + * @return the estimated time in frames that has passed since the JACK + * server began the current process cycle. + */ +jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated current time in frames. + * This function is intended for use in other threads (not the process + * callback). The return value can be compared with the value of + * jack_last_frame_time to relate time in other threads to JACK time. + */ +jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the precise time at the start of the current process cycle. + * This function may only be used from the process callback, and can + * be used to interpret timestamps generated by jack_frame_time() in + * other threads with respect to the current process cycle. + * + * This is the only jack time function that returns exact time: + * when used during the process callback it always returns the same + * value (until the next process callback, where it will return + * that value + nframes, etc). The return value is guaranteed to be + * monotonic and linear in this fashion unless an xrun occurs. + * If an xrun occurs, clients must check this value again, as time + * may have advanced in a non-linear way (e.g. cycles may have been skipped). + */ +jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * This function may only be used from the process callback. + * It provides the internal cycle timing information as used by + * most of the other time related functions. This allows the + * caller to map between frame counts and microseconds with full + * precision (i.e. without rounding frame times to integers), + * and also provides e.g. the microseconds time of the start of + * the current cycle directly (it has to be computed otherwise). + * + * If the return value is zero, the following information is + * provided in the variables pointed to by the arguments: + * + * current_frames: the frame time counter at the start of the + * current cycle, same as jack_last_frame_time(). + * current_usecs: the microseconds time at the start of the + * current cycle. + * next_usecs: the microseconds time of the start of the next + * next cycle as computed by the DLL. + * period_usecs: the current best estimate of the period time in + * microseconds. + * + * NOTES: + * + * Because of the types used, all the returned values except period_usecs + * are unsigned. In computations mapping between frames and microseconds + * *signed* differences are required. The easiest way is to compute those + * separately and assign them to the appropriate signed variables, + * int32_t for frames and int64_t for usecs. See the implementation of + * jack_frames_to_time() and Jack_time_to_frames() for an example. + * + * Unless there was an xrun, skipped cycles, or the current cycle is the + * first after freewheeling or starting Jack, the value of current_usecs + * will always be the value of next_usecs of the previous cycle. + * + * The value of period_usecs will in general NOT be exactly equal to + * the difference of next_usecs and current_usecs. This is because to + * ensure stability of the DLL and continuity of the mapping, a fraction + * of the loop error must be included in next_usecs. For an accurate + * mapping between frames and microseconds, the difference of next_usecs + * and current_usecs should be used, and not period_usecs. + * + * @return zero if OK, non-zero otherwise. + */ +int jack_get_cycle_times(const jack_client_t *client, + jack_nframes_t *current_frames, + jack_time_t *current_usecs, + jack_time_t *next_usecs, + float *period_usecs) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated time in microseconds of the specified frame time + */ +jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return the estimated time in frames for the specified system time. + */ +jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @return return JACK's current system time in microseconds, + * using the JACK clock source. + * + * The value returned is guaranteed to be monotonic, but not linear. + */ +jack_time_t jack_get_time(void) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * @defgroup ErrorOutput Controlling error/information output + */ +/*@{*/ + +/** + * Display JACK error message. + * + * Set via jack_set_error_function(), otherwise a JACK-provided + * default will print @a msg (plus a newline) to stderr. + * + * @param msg error message text (no newline at end). + */ +extern void (*jack_error_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the @ref jack_error_callback for error message display. + * Set it to NULL to restore default_jack_error_callback function. + * + * The JACK library provides two built-in callbacks for this purpose: + * default_jack_error_callback() and silent_jack_error_callback(). + */ +void jack_set_error_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Display JACK info message. + * + * Set via jack_set_info_function(), otherwise a JACK-provided + * default will print @a msg (plus a newline) to stdout. + * + * @param msg info message text (no newline at end). + */ +extern void (*jack_info_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the @ref jack_info_callback for info message display. + * Set it to NULL to restore default_jack_info_callback function. + * + * The JACK library provides two built-in callbacks for this purpose: + * default_jack_info_callback() and silent_jack_info_callback(). + */ +void jack_set_info_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +/** + * The free function to be used on memory returned by jack_port_get_connections, + * jack_port_get_all_connections, jack_get_ports and jack_get_internal_client_name functions. + * This is MANDATORY on Windows when otherwise all nasty runtime version related crashes can occur. + * Developers are strongly encouraged to use this function instead of the standard "free" function in new code. + * + * @param ptr the memory pointer to be deallocated. + */ +void jack_free(void* ptr) JACK_OPTIONAL_WEAK_EXPORT; + + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_h__ */ diff --git a/include/jack/jslist.h b/include/jack/jslist.h new file mode 100644 index 00000000000..4a2bf7067ff --- /dev/null +++ b/include/jack/jslist.h @@ -0,0 +1,293 @@ +/* + Based on gslist.c from glib-1.2.9 (LGPL). + + Adaption to JACK, Copyright (C) 2002 Kai Vehmanen. + - replaced use of gtypes with normal ANSI C types + - glib's memory allocation routines replaced with + malloc/free calls + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_jslist_h__ +#define __jack_jslist_h__ + +#include +#include + +#ifdef sun +#define __inline__ +#endif + +typedef struct _JSList JSList; + +typedef int (*JCompareFunc) (void* a, void* b); +struct _JSList +{ + void *data; + JSList *next; +}; + +static __inline__ +JSList* +jack_slist_alloc (void) +{ + JSList *new_list; + + new_list = (JSList*)malloc(sizeof(JSList)); + if (new_list) { + new_list->data = NULL; + new_list->next = NULL; + } + + return new_list; +} + +static __inline__ +JSList* +jack_slist_prepend (JSList* list, void* data) +{ + JSList *new_list; + + new_list = (JSList*)malloc(sizeof(JSList)); + if (new_list) { + new_list->data = data; + new_list->next = list; + } + + return new_list; +} + +#define jack_slist_next(slist) ((slist) ? (((JSList *)(slist))->next) : NULL) +static __inline__ +JSList* +jack_slist_last (JSList *list) +{ + if (list) { + while (list->next) + list = list->next; + } + + return list; +} + +static __inline__ +JSList* +jack_slist_remove_link (JSList *list, + JSList *link) +{ + JSList *tmp; + JSList *prev; + + prev = NULL; + tmp = list; + + while (tmp) { + if (tmp == link) { + if (prev) + prev->next = tmp->next; + if (list == tmp) + list = list->next; + + tmp->next = NULL; + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static __inline__ +void +jack_slist_free (JSList *list) +{ + while (list) { + JSList *next = list->next; + free(list); + list = next; + } +} + +static __inline__ +void +jack_slist_free_1 (JSList *list) +{ + if (list) { + free(list); + } +} + +static __inline__ +JSList* +jack_slist_remove (JSList *list, + void *data) +{ + JSList *tmp; + JSList *prev; + + prev = NULL; + tmp = list; + + while (tmp) { + if (tmp->data == data) { + if (prev) + prev->next = tmp->next; + if (list == tmp) + list = list->next; + + tmp->next = NULL; + jack_slist_free (tmp); + + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static __inline__ +unsigned int +jack_slist_length (JSList *list) +{ + unsigned int length; + + length = 0; + while (list) { + length++; + list = list->next; + } + + return length; +} + +static __inline__ +JSList* +jack_slist_find (JSList *list, + void *data) +{ + while (list) { + if (list->data == data) + break; + list = list->next; + } + + return list; +} + +static __inline__ +JSList* +jack_slist_copy (JSList *list) +{ + JSList *new_list = NULL; + + if (list) { + JSList *last; + + new_list = jack_slist_alloc (); + new_list->data = list->data; + last = new_list; + list = list->next; + while (list) { + last->next = jack_slist_alloc (); + last = last->next; + last->data = list->data; + list = list->next; + } + } + + return new_list; +} + +static __inline__ +JSList* +jack_slist_append (JSList *list, + void *data) +{ + JSList *new_list; + JSList *last; + + new_list = jack_slist_alloc (); + new_list->data = data; + + if (list) { + last = jack_slist_last (list); + last->next = new_list; + + return list; + } else + return new_list; +} + +static __inline__ +JSList* +jack_slist_sort_merge (JSList *l1, + JSList *l2, + JCompareFunc compare_func) +{ + JSList list, *l; + + l = &list; + + while (l1 && l2) { + if (compare_func(l1->data, l2->data) < 0) { + l = l->next = l1; + l1 = l1->next; + } else { + l = l->next = l2; + l2 = l2->next; + } + } + l->next = l1 ? l1 : l2; + + return list.next; +} + +static __inline__ +JSList* +jack_slist_sort (JSList *list, + JCompareFunc compare_func) +{ + JSList *l1, *l2; + + if (!list) + return NULL; + if (!list->next) + return list; + + l1 = list; + l2 = list->next; + + while ((l2 = l2->next) != NULL) { + if ((l2 = l2->next) == NULL) + break; + l1 = l1->next; + } + l2 = l1->next; + l1->next = NULL; + + return jack_slist_sort_merge (jack_slist_sort (list, compare_func), + jack_slist_sort (l2, compare_func), + compare_func); +} + +#endif /* __jack_jslist_h__ */ + diff --git a/include/jack/midiport.h b/include/jack/midiport.h new file mode 100644 index 00000000000..af16e0d76a6 --- /dev/null +++ b/include/jack/midiport.h @@ -0,0 +1,184 @@ +/* + Copyright (C) 2004 Ian Esten + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +#ifndef __JACK_MIDIPORT_H +#define __JACK_MIDIPORT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + + +/** Type for raw event data contained in @ref jack_midi_event_t. */ +typedef unsigned char jack_midi_data_t; + + +/** A Jack MIDI event. */ +typedef struct _jack_midi_event +{ + jack_nframes_t time; /**< Sample index at which event is valid */ + size_t size; /**< Number of bytes of data in \a buffer */ + jack_midi_data_t *buffer; /**< Raw MIDI data */ +} jack_midi_event_t; + + +/** + * @defgroup MIDIAPI Reading and writing MIDI data + * @{ + */ + +/** Get number of events in a port buffer. + * + * @param port_buffer Port buffer from which to retrieve event. + * @return number of events inside @a port_buffer + */ +uint32_t +jack_midi_get_event_count(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get a MIDI event from an event port buffer. + * + * Jack MIDI is normalised, the MIDI event returned by this function is + * guaranteed to be a complete MIDI event (the status byte will always be + * present, and no realtime events will interspered with the event). + * + * @param event Event structure to store retrieved event in. + * @param port_buffer Port buffer from which to retrieve event. + * @param event_index Index of event to retrieve. + * @return 0 on success, ENODATA if buffer is empty. + */ +int +jack_midi_event_get(jack_midi_event_t *event, + void *port_buffer, + uint32_t event_index) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Clear an event buffer. + * + * This should be called at the beginning of each process cycle before calling + * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This + * function may not be called on an input port's buffer. + * + * @param port_buffer Port buffer to clear (must be an output port buffer). + */ +void +jack_midi_clear_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + +/** Reset an event buffer (from data allocated outside of JACK). + * + * This should be called at the beginning of each process cycle before calling + * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This + * function may not be called on an input port's buffer. + * + * @param port_buffer Port buffer to resetted. + */ +void +jack_midi_reset_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get the size of the largest event that can be stored by the port. + * + * This function returns the current space available, taking into account + * events already stored in the port. + * + * @param port_buffer Port buffer to check size of. + */ +size_t +jack_midi_max_event_size(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Allocate space for an event to be written to an event port buffer. + * + * Clients are to write the actual event data to be written starting at the + * pointer returned by this function. Clients must not write more than + * @a data_size bytes into this buffer. Clients must write normalised + * MIDI data to the port - no running status and no (1-byte) realtime + * messages interspersed with other messages (realtime messages are fine + * when they occur on their own, like other messages). + * + * Events must be written in order, sorted by their sample offsets. + * JACK will not sort the events for you, and will refuse to store + * out-of-order events. + * + * @param port_buffer Buffer to write event to. + * @param time Sample offset of event. + * @param data_size Length of event's raw data in bytes. + * @return Pointer to the beginning of the reserved event's data buffer, or + * NULL on error (ie not enough space). + */ +jack_midi_data_t* +jack_midi_event_reserve(void *port_buffer, + jack_nframes_t time, + size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Write an event into an event port buffer. + * + * This function is simply a wrapper for @ref jack_midi_event_reserve + * which writes the event data into the space reserved in the buffer. + * + * Clients must not write more than + * @a data_size bytes into this buffer. Clients must write normalised + * MIDI data to the port - no running status and no (1-byte) realtime + * messages interspersed with other messages (realtime messages are fine + * when they occur on their own, like other messages). + * + * Events must be written in order, sorted by their sample offsets. + * JACK will not sort the events for you, and will refuse to store + * out-of-order events. + * + * @param port_buffer Buffer to write event to. + * @param time Sample offset of event. + * @param data Message data to be written. + * @param data_size Length of @a data in bytes. + * @return 0 on success, ENOBUFS if there's not enough space in buffer for event. + */ +int +jack_midi_event_write(void *port_buffer, + jack_nframes_t time, + const jack_midi_data_t *data, + size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; + + +/** Get the number of events that could not be written to @a port_buffer. + * + * This function returning a non-zero value implies @a port_buffer is full. + * Currently the only way this can happen is if events are lost on port mixdown. + * + * @param port_buffer Port to receive count for. + * @returns Number of events that could not be written to @a port_buffer. + */ +uint32_t +jack_midi_get_lost_event_count(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +#ifdef __cplusplus +} +#endif + + +#endif /* __JACK_MIDIPORT_H */ + + diff --git a/include/jack/ringbuffer.h b/include/jack/ringbuffer.h new file mode 100644 index 00000000000..74be11769ac --- /dev/null +++ b/include/jack/ringbuffer.h @@ -0,0 +1,243 @@ +/* + Copyright (C) 2000 Paul Davis + Copyright (C) 2003 Rohan Drape + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef _RINGBUFFER_H +#define _RINGBUFFER_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/** @file ringbuffer.h + * + * A set of library functions to make lock-free ringbuffers available + * to JACK clients. The `capture_client.c' (in the example_clients + * directory) is a fully functioning user of this API. + * + * The key attribute of a ringbuffer is that it can be safely accessed + * by two threads simultaneously -- one reading from the buffer and + * the other writing to it -- without using any synchronization or + * mutual exclusion primitives. For this to work correctly, there can + * only be a single reader and a single writer thread. Their + * identities cannot be interchanged. + */ + +typedef struct { + char *buf; + size_t len; +} +jack_ringbuffer_data_t ; + +typedef struct { + char *buf; + volatile size_t write_ptr; + volatile size_t read_ptr; + size_t size; + size_t size_mask; + int mlocked; +} +jack_ringbuffer_t ; + +/** + * Allocates a ringbuffer data structure of a specified size. The + * caller must arrange for a call to jack_ringbuffer_free() to release + * the memory associated with the ringbuffer. + * + * @param sz the ringbuffer size in bytes. + * + * @return a pointer to a new jack_ringbuffer_t, if successful; NULL + * otherwise. + */ +jack_ringbuffer_t *jack_ringbuffer_create(size_t sz); + +/** + * Frees the ringbuffer data structure allocated by an earlier call to + * jack_ringbuffer_create(). + * + * @param rb a pointer to the ringbuffer structure. + */ +void jack_ringbuffer_free(jack_ringbuffer_t *rb); + +/** + * Fill a data structure with a description of the current readable + * data held in the ringbuffer. This description is returned in a two + * element array of jack_ringbuffer_data_t. Two elements are needed + * because the data to be read may be split across the end of the + * ringbuffer. + * + * The first element will always contain a valid @a len field, which + * may be zero or greater. If the @a len field is non-zero, then data + * can be read in a contiguous fashion using the address given in the + * corresponding @a buf field. + * + * If the second element has a non-zero @a len field, then a second + * contiguous stretch of data can be read from the address given in + * its corresponding @a buf field. + * + * @param rb a pointer to the ringbuffer structure. + * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. + * + */ +void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec); + +/** + * Fill a data structure with a description of the current writable + * space in the ringbuffer. The description is returned in a two + * element array of jack_ringbuffer_data_t. Two elements are needed + * because the space available for writing may be split across the end + * of the ringbuffer. + * + * The first element will always contain a valid @a len field, which + * may be zero or greater. If the @a len field is non-zero, then data + * can be written in a contiguous fashion using the address given in + * the corresponding @a buf field. + * + * If the second element has a non-zero @a len field, then a second + * contiguous stretch of data can be written to the address given in + * the corresponding @a buf field. + * + * @param rb a pointer to the ringbuffer structure. + * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. + */ +void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, + jack_ringbuffer_data_t *vec); + +/** + * Read data from the ringbuffer. + * + * @param rb a pointer to the ringbuffer structure. + * @param dest a pointer to a buffer where data read from the + * ringbuffer will go. + * @param cnt the number of bytes to read. + * + * @return the number of bytes read, which may range from 0 to cnt. + */ +size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt); + +/** + * Read data from the ringbuffer. Opposed to jack_ringbuffer_read() + * this function does not move the read pointer. Thus it's + * a convenient way to inspect data in the ringbuffer in a + * continous fashion. The price is that the data is copied + * into a user provided buffer. For "raw" non-copy inspection + * of the data in the ringbuffer use jack_ringbuffer_get_read_vector(). + * + * @param rb a pointer to the ringbuffer structure. + * @param dest a pointer to a buffer where data read from the + * ringbuffer will go. + * @param cnt the number of bytes to read. + * + * @return the number of bytes read, which may range from 0 to cnt. + */ +size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt); + +/** + * Advance the read pointer. + * + * After data have been read from the ringbuffer using the pointers + * returned by jack_ringbuffer_get_read_vector(), use this function to + * advance the buffer pointers, making that space available for future + * write operations. + * + * @param rb a pointer to the ringbuffer structure. + * @param cnt the number of bytes read. + */ +void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt); + +/** + * Return the number of bytes available for reading. + * + * @param rb a pointer to the ringbuffer structure. + * + * @return the number of bytes available to read. + */ +size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb); + +/** + * Lock a ringbuffer data block into memory. + * + * Uses the mlock() system call. This is not a realtime operation. + * + * @param rb a pointer to the ringbuffer structure. + */ +int jack_ringbuffer_mlock(jack_ringbuffer_t *rb); + +/** + * Reset the read and write pointers, making an empty buffer. + * + * This is not thread safe. + * + * @param rb a pointer to the ringbuffer structure. + */ +void jack_ringbuffer_reset(jack_ringbuffer_t *rb); + +/** + * Reset the internal "available" size, and read and write pointers, making an empty buffer. + * + * This is not thread safe. + * + * @param rb a pointer to the ringbuffer structure. + * @param sz the new size, that must be less than allocated size. + */ +void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz); + +/** + * Write data into the ringbuffer. + * + * @param rb a pointer to the ringbuffer structure. + * @param src a pointer to the data to be written to the ringbuffer. + * @param cnt the number of bytes to write. + * + * @return the number of bytes write, which may range from 0 to cnt + */ +size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, + size_t cnt); + +/** + * Advance the write pointer. + * + * After data have been written the ringbuffer using the pointers + * returned by jack_ringbuffer_get_write_vector(), use this function + * to advance the buffer pointer, making the data available for future + * read operations. + * + * @param rb a pointer to the ringbuffer structure. + * @param cnt the number of bytes written. + */ +void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt); + +/** + * Return the number of bytes available for writing. + * + * @param rb a pointer to the ringbuffer structure. + * + * @return the amount of free space (in bytes) available for writing. + */ +size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/jack/session.h b/include/jack/session.h new file mode 100644 index 00000000000..cb75cea74ed --- /dev/null +++ b/include/jack/session.h @@ -0,0 +1,270 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + Copyright (C) 2010 Torben Hohn + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef __jack_session_h__ +#define __jack_session_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @defgroup SessionClientFunctions Session API for clients. + * @{ + */ + + +/** + * Session event type. + * + * If a client cant save templates, i might just do a normal save. + * + * There is no "quit without saving" event because a client might refuse to + * quit when it has unsaved data, but other clients may have already quit. + * This results in too much confusion, so it is unsupported. + */ +enum JackSessionEventType { + /** + * Save the session completely. + * + * The client may save references to data outside the provided directory, + * but it must do so by creating a link inside the provided directory and + * referring to that in any save files. The client must not refer to data + * files outside the provided directory directly in save files, because + * this makes it impossible for the session manager to create a session + * archive for distribution or archival. + */ + JackSessionSave = 1, + + /** + * Save the session completly, then quit. + * + * The rules for saving are exactly the same as for JackSessionSave. + */ + JackSessionSaveAndQuit = 2, + + /** + * Save a session template. + * + * A session template is a "skeleton" of the session, but without any data. + * Clients must save a session that, when restored, will create the same + * ports as a full save would have. However, the actual data contained in + * the session may not be saved (e.g. a DAW would create the necessary + * tracks, but not save the actual recorded data). + */ + JackSessionSaveTemplate = 3 +}; + +typedef enum JackSessionEventType jack_session_event_type_t; + +/** + * @ref jack_session_flags_t bits + */ +enum JackSessionFlags { + /** + * An error occured while saving. + */ + JackSessionSaveError = 0x01, + + /** + * Client needs to be run in a terminal. + */ + JackSessionNeedTerminal = 0x02 +}; + +/** + * Session flags. + */ +typedef enum JackSessionFlags jack_session_flags_t; + +struct _jack_session_event { + /** + * The type of this session event. + */ + jack_session_event_type_t type; + + /** + * Session directory path, with trailing separator. + * + * This directory is exclusive to the client; when saving the client may + * create any files it likes in this directory. + */ + const char *session_dir; + + /** + * Client UUID which must be passed to jack_client_open on session load. + * + * The client can specify this in the returned command line, or save it + * in a state file within the session directory. + */ + const char *client_uuid; + + /** + * Reply (set by client): the command line needed to restore the client. + * + * This is a platform dependent command line. It must contain + * ${SESSION_DIR} instead of the actual session directory path. More + * generally, just as in session files, clients should not include any + * paths outside the session directory here as this makes + * archival/distribution impossible. + * + * This field is set to NULL by Jack when the event is delivered to the + * client. The client must set to allocated memory that is safe to + * free(). This memory will be freed by jack_session_event_free. + */ + char *command_line; + + /** + * Reply (set by client): Session flags. + */ + jack_session_flags_t flags; + + /** + * Future flags. Set to zero for now. + */ + uint32_t future; +}; + +typedef struct _jack_session_event jack_session_event_t; + +/** + * Prototype for the client supplied function that is called + * whenever a session notification is sent via jack_session_notify(). + * + * Ownership of the memory of @a event is passed to the application. + * It must be freed using jack_session_event_free when its not used anymore. + * + * The client must promptly call jack_session_reply for this event. + * + * @param event The event structure. + * @param arg Pointer to a client supplied structure. + */ +typedef void (*JackSessionCallback)(jack_session_event_t *event, + void *arg); + +/** + * Tell the JACK server to call @a session_callback when a session event + * is to be delivered. + * + * setting more than one session_callback per process is probably a design + * error. if you have a multiclient application its more sensible to create + * a jack_client with only a session callback set. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_session_callback (jack_client_t *client, + JackSessionCallback session_callback, + void *arg) JACK_WEAK_EXPORT; + +/** + * Reply to a session event. + * + * This can either be called directly from the callback, or later from a + * different thread. For example, it is possible to push the event through a + * queue and execute the save code from the GUI thread. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_session_reply (jack_client_t *client, + jack_session_event_t *event) JACK_WEAK_EXPORT; + + +/** + * Free memory used by a jack_session_event_t. + * + * This also frees the memory used by the command_line pointer, if its non NULL. + */ +void jack_session_event_free (jack_session_event_t *event) JACK_WEAK_EXPORT; + + +/** + * Get the assigned uuid for client. + * Safe to call from callback and all other threads. + * + * The caller is responsible for calling jack_free(3) on any non-NULL + * returned value. + */ +char *jack_client_get_uuid (jack_client_t *client) JACK_WEAK_EXPORT; + +/** + * @} + */ + +/** + * @defgroup JackSessionManagerAPI API for a session manager. + * + * @{ + */ + +typedef struct { + const char *uuid; + const char *client_name; + const char *command; + jack_session_flags_t flags; +} jack_session_command_t; + +/** + * Send an event to all clients listening for session callbacks. + * + * The returned strings of the clients are accumulated and returned as an array + * of jack_session_command_t. its terminated by ret[i].uuid == NULL target == + * NULL means send to all interested clients. otherwise a clientname + */ +jack_session_command_t *jack_session_notify ( + jack_client_t* client, + const char *target, + jack_session_event_type_t type, + const char *path) JACK_WEAK_EXPORT; + +/** + * Free the memory allocated by a session command. + */ +void jack_session_commands_free (jack_session_command_t *cmds) JACK_WEAK_EXPORT; + +/** + * Reserve a client name and associate it with a UUID. + * + * When a client later calls jack_client_open() and specifies the UUID, jackd + * will assign the reserved name. This allows a session manager to know in + * advance under which client name its managed clients will appear. + * + * @return 0 on success, otherwise a non-zero error code + */ +int +jack_reserve_client_name (jack_client_t *client, + const char *name, + const char *uuid) JACK_WEAK_EXPORT; + +/** + * Find out whether a client has set up a session callback. + * + * @return 0 when the client has no session callback, 1 when it has one. + * -1 on error. + */ +int +jack_client_has_session_callback (jack_client_t *client, const char *client_name) JACK_WEAK_EXPORT; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/jack/statistics.h b/include/jack/statistics.h new file mode 100644 index 00000000000..36b6f88fcba --- /dev/null +++ b/include/jack/statistics.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2004 Rui Nuno Capela, Lee Revell +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public License +* as published by the Free Software Foundation; either version 2.1 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this program; if not, write to the Free +* Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +* 02111-1307, USA. +* +*/ + +#ifndef __statistics_h__ +#define __statistics_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +/** + * @return the maximum delay reported by the backend since + * startup or reset. When compared to the period size in usecs, this + * can be used to estimate the ideal period size for a given setup. + */ +float jack_get_max_delayed_usecs (jack_client_t *client); + +/** + * @return the delay in microseconds due to the most recent XRUN + * occurrence. This probably only makes sense when called from a @ref + * JackXRunCallback defined using jack_set_xrun_callback(). + */ +float jack_get_xrun_delayed_usecs (jack_client_t *client); + +/** + * Reset the maximum delay counter. This would be useful + * to estimate the effect that a change to the configuration of a running + * system (e.g. toggling kernel preemption) has on the delay + * experienced by JACK, without having to restart the JACK engine. + */ +void jack_reset_max_delayed_usecs (jack_client_t *client); + +#ifdef __cplusplus +} +#endif + +#endif /* __statistics_h__ */ diff --git a/include/jack/systemdeps.h b/include/jack/systemdeps.h new file mode 100644 index 00000000000..7cf144aab67 --- /dev/null +++ b/include/jack/systemdeps.h @@ -0,0 +1,128 @@ +/* +Copyright (C) 2004-2012 Grame + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef __jack_systemdeps_h__ +#define __jack_systemdeps_h__ + +#ifndef POST_PACKED_STRUCTURE + + #ifdef __GNUC__ + /* POST_PACKED_STRUCTURE needs to be a macro which + expands into a compiler directive. The directive must + tell the compiler to arrange the preceding structure + declaration so that it is packed on byte-boundaries rather + than use the natural alignment of the processor and/or + compiler. + */ + + #define PRE_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE __attribute__((__packed__)) + + #else + + #ifdef _MSC_VER + #define PRE_PACKED_STRUCTURE1 __pragma(pack(push,1)) + #define PRE_PACKED_STRUCTURE PRE_PACKED_STRUCTURE1 + /* PRE_PACKED_STRUCTURE needs to be a macro which + expands into a compiler directive. The directive must + tell the compiler to arrange the following structure + declaration so that it is packed on byte-boundaries rather + than use the natural alignment of the processor and/or + compiler. + */ + #define POST_PACKED_STRUCTURE ;__pragma(pack(pop)) + /* and POST_PACKED_STRUCTURE needs to be a macro which + restores the packing to its previous setting */ + #else + #define PRE_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE + #endif /* _MSC_VER */ + + #endif /* __GNUC__ */ + +#endif + +#if defined(_WIN32) && !defined(__CYGWIN__) && !defined(GNU_WIN32) + + #include + + #ifdef _MSC_VER /* Microsoft compiler */ + #define __inline__ inline + #if (!defined(int8_t) && !defined(_STDINT_H)) + #define __int8_t_defined + typedef char int8_t; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef long int32_t; + typedef unsigned long uint32_t; + typedef LONGLONG int64_t; + typedef ULONGLONG uint64_t; + #endif + #elif __MINGW32__ /* MINGW */ + #include + #include + #else /* other compilers ...*/ + #include + #include + #include + #endif + + #if !defined(_PTHREAD_H) && !defined(PTHREAD_WIN32) + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to HANDLE here. + */ + typedef HANDLE jack_native_thread_t; + #else + #ifdef PTHREAD_WIN32 // Added by JE - 10-10-2011 + #include // Makes sure we #include the ptw32 version ! + #endif + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to pthread_t here. + */ + typedef pthread_t jack_native_thread_t; + #endif + +#endif /* _WIN32 && !__CYGWIN__ && !GNU_WIN32 */ + +#if defined(__APPLE__) || defined(__linux__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) + + #if defined(__CYGWIN__) || defined(GNU_WIN32) + #include + #endif + #include + #include + #include + + /** + * to make jack API independent of different thread implementations, + * we define jack_native_thread_t to pthread_t here. + */ + typedef pthread_t jack_native_thread_t; + +#endif /* __APPLE__ || __linux__ || __sun__ || sun */ + +#if defined(__arm__) || defined(__ppc__) || defined(__powerpc__) + #undef POST_PACKED_STRUCTURE + #define POST_PACKED_STRUCTURE +#endif /* __arm__ || __ppc__ || __powerpc__ */ + +#endif /* __jack_systemdeps_h__ */ diff --git a/include/jack/thread.h b/include/jack/thread.h new file mode 100644 index 00000000000..8b2c3944bda --- /dev/null +++ b/include/jack/thread.h @@ -0,0 +1,160 @@ +/* + Copyright (C) 2004 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_thread_h__ +#define __jack_thread_h__ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +/* use 512KB stack per thread - the default is way too high to be feasible + * with mlockall() on many systems */ +#define THREAD_STACK 524288 + +/** @file thread.h + * + * Library functions to standardize thread creation for JACK and its + * clients. These interfaces hide some system variations in the + * handling of realtime scheduling and associated privileges. + */ + +/** + * @defgroup ClientThreads Creating and managing client threads + * @{ + */ + + /** + * @returns if JACK is running with realtime scheduling, this returns + * the priority that any JACK-created client threads will run at. + * Otherwise returns -1. + */ + +int jack_client_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * @returns if JACK is running with realtime scheduling, this returns + * the maximum priority that a JACK client thread should use if the thread + * is subject to realtime scheduling. Otherwise returns -1. + */ + +int jack_client_max_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Attempt to enable realtime scheduling for a thread. On some + * systems that may require special privileges. + * + * @param thread POSIX thread ID. + * @param priority requested thread priority. + * + * @returns 0, if successful; EPERM, if the calling process lacks + * required realtime privileges; otherwise some other error number. + */ +int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Create a thread for JACK or one of its clients. The thread is + * created executing @a start_routine with @a arg as its sole + * argument. + * + * @param client the JACK client for whom the thread is being created. May be + * NULL if the client is being created within the JACK server. + * @param thread place to return POSIX thread ID. + * @param priority thread priority, if realtime. + * @param realtime true for the thread to use realtime scheduling. On + * some systems that may require special privileges. + * @param start_routine function the thread calls when it starts. + * @param arg parameter passed to the @a start_routine. + * + * @returns 0, if successful; otherwise some error number. + */ +int jack_client_create_thread (jack_client_t* client, + jack_native_thread_t *thread, + int priority, + int realtime, /* boolean */ + void *(*start_routine)(void*), + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Drop realtime scheduling for a thread. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ +int jack_drop_real_time_scheduling (jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Stop the thread, waiting for the thread handler to terminate. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ +int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Kill the thread. + * + * @param thread POSIX thread ID. + * + * @returns 0, if successful; otherwise an error number. + */ + int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; + +#ifndef _WIN32 + + typedef int (*jack_thread_creator_t)(pthread_t*, + const pthread_attr_t*, + void* (*function)(void*), + void* arg); +/** + * This function can be used in very very specialized cases + * where it is necessary that client threads created by JACK + * are created by something other than pthread_create(). After + * it is used, any threads that JACK needs for the client will + * will be created by calling the function passed to this + * function. + * + * No normal application/client should consider calling this. + * The specific case for which it was created involves running + * win32/x86 plugins under Wine on Linux, where it is necessary + * that all threads that might call win32 functions are known + * to Wine. + * + * Set it to NULL to restore thread creation function. + * + * @param creator a function that creates a new thread + * + */ +void jack_set_thread_creator (jack_thread_creator_t creator) JACK_OPTIONAL_WEAK_EXPORT; + +#endif + +/* @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_thread_h__ */ diff --git a/include/jack/transport.h b/include/jack/transport.h new file mode 100644 index 00000000000..9f18ef68876 --- /dev/null +++ b/include/jack/transport.h @@ -0,0 +1,247 @@ +/* + Copyright (C) 2002 Paul Davis + Copyright (C) 2003 Jack O'Quin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_transport_h__ +#define __jack_transport_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @defgroup TransportControl Transport and Timebase control + * @{ + */ + +/** + * Called by the timebase master to release itself from that + * responsibility. + * + * If the timebase master releases the timebase or leaves the JACK + * graph for any reason, the JACK engine takes over at the start of + * the next process cycle. The transport state does not change. If + * rolling, it continues to play, with frame numbers as the only + * available position information. + * + * @see jack_set_timebase_callback + * + * @param client the JACK client structure. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_release_timebase (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Register (or unregister) as a slow-sync client, one that cannot + * respond immediately to transport position changes. + * + * The @a sync_callback will be invoked at the first available + * opportunity after its registration is complete. If the client is + * currently active this will be the following process cycle, + * otherwise it will be the first cycle after calling jack_activate(). + * After that, it runs according to the ::JackSyncCallback rules. + * Clients that don't set a @a sync_callback are assumed to be ready + * immediately any time the transport wants to start. + * + * @param client the JACK client structure. + * @param sync_callback is a realtime function that returns TRUE when + * the client is ready. Setting @a sync_callback to NULL declares that + * this client no longer requires slow-sync processing. + * @param arg an argument for the @a sync_callback function. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_sync_callback (jack_client_t *client, + JackSyncCallback sync_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the timeout value for slow-sync clients. + * + * This timeout prevents unresponsive slow-sync clients from + * completely halting the transport mechanism. The default is two + * seconds. When the timeout expires, the transport starts rolling, + * even if some slow-sync clients are still unready. The @a + * sync_callbacks of these clients continue being invoked, giving them + * a chance to catch up. + * + * @see jack_set_sync_callback + * + * @param client the JACK client structure. + * @param timeout is delay (in microseconds) before the timeout expires. + * + * @return 0 on success, otherwise a non-zero error code. + */ +int jack_set_sync_timeout (jack_client_t *client, + jack_time_t timeout) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Register as timebase master for the JACK subsystem. + * + * The timebase master registers a callback that updates extended + * position information such as beats or timecode whenever necessary. + * Without this extended information, there is no need for this + * function. + * + * There is never more than one master at a time. When a new client + * takes over, the former @a timebase_callback is no longer called. + * Taking over the timebase may be done conditionally, so it fails if + * there was a master already. + * + * @param client the JACK client structure. + * @param conditional non-zero for a conditional request. + * @param timebase_callback is a realtime function that returns + * position information. + * @param arg an argument for the @a timebase_callback function. + * + * @return + * - 0 on success; + * - EBUSY if a conditional request fails because there was already a + * timebase master; + * - other non-zero error code. + */ +int jack_set_timebase_callback (jack_client_t *client, + int conditional, + JackTimebaseCallback timebase_callback, + void *arg) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Reposition the transport to a new frame number. + * + * May be called at any time by any client. The new position takes + * effect in two process cycles. If there are slow-sync clients and + * the transport is already rolling, it will enter the + * ::JackTransportStarting state and begin invoking their @a + * sync_callbacks until ready. This function is realtime-safe. + * + * @see jack_transport_reposition, jack_set_sync_callback + * + * @param client the JACK client structure. + * @param frame frame number of new transport position. + * + * @return 0 if valid request, non-zero otherwise. + */ +int jack_transport_locate (jack_client_t *client, + jack_nframes_t frame) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Query the current transport state and position. + * + * This function is realtime-safe, and can be called from any thread. + * If called from the process thread, @a pos corresponds to the first + * frame of the current cycle and the state returned is valid for the + * entire cycle. + * + * @param client the JACK client structure. + * @param pos pointer to structure for returning current transport + * position; @a pos->valid will show which fields contain valid data. + * If @a pos is NULL, do not return position information. + * + * @return Current transport state. + */ +jack_transport_state_t jack_transport_query (const jack_client_t *client, + jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Return an estimate of the current transport frame, + * including any time elapsed since the last transport + * positional update. + * + * @param client the JACK client structure + */ +jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Request a new transport position. + * + * May be called at any time by any client. The new position takes + * effect in two process cycles. If there are slow-sync clients and + * the transport is already rolling, it will enter the + * ::JackTransportStarting state and begin invoking their @a + * sync_callbacks until ready. This function is realtime-safe. + * + * @see jack_transport_locate, jack_set_sync_callback + * + * @param client the JACK client structure. + * @param pos requested new transport position. + * + * @return 0 if valid request, EINVAL if position structure rejected. + */ +int jack_transport_reposition (jack_client_t *client, + const jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Start the JACK transport rolling. + * + * Any client can make this request at any time. It takes effect no + * sooner than the next process cycle, perhaps later if there are + * slow-sync clients. This function is realtime-safe. + * + * @see jack_set_sync_callback + * + * @param client the JACK client structure. + */ +void jack_transport_start (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Stop the JACK transport. + * + * Any client can make this request at any time. It takes effect on + * the next process cycle. This function is realtime-safe. + * + * @param client the JACK client structure. + */ +void jack_transport_stop (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Gets the current transport info structure (deprecated). + * + * @param client the JACK client structure. + * @param tinfo current transport info structure. The "valid" field + * describes which fields contain valid data. + * + * @deprecated This is for compatibility with the earlier transport + * interface. Use jack_transport_query(), instead. + * + * @pre Must be called from the process thread. + */ +void jack_get_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; + +/** + * Set the transport info structure (deprecated). + * + * @deprecated This function still exists for compatibility with the + * earlier transport interface, but it does nothing. Instead, define + * a ::JackTimebaseCallback. + */ +void jack_set_transport_info (jack_client_t *client, + jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; + +/*@}*/ + +#ifdef __cplusplus +} +#endif + +#endif /* __jack_transport_h__ */ diff --git a/include/jack/types.h b/include/jack/types.h new file mode 100644 index 00000000000..4641a99f281 --- /dev/null +++ b/include/jack/types.h @@ -0,0 +1,740 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_types_h__ +#define __jack_types_h__ + +#include + +typedef uint64_t jack_uuid_t; + +typedef int32_t jack_shmsize_t; + +/** + * Type used to represent sample frame counts. + */ +typedef uint32_t jack_nframes_t; + +/** + * Maximum value that can be stored in jack_nframes_t + */ +#define JACK_MAX_FRAMES (4294967295U) /* This should be UINT32_MAX, but C++ has a problem with that. */ + +/** + * Type used to represent the value of free running + * monotonic clock with units of microseconds. + */ +typedef uint64_t jack_time_t; + +/** + * Maximum size of @a load_init string passed to an internal client + * jack_initialize() function via jack_internal_client_load(). + */ +#define JACK_LOAD_INIT_LIMIT 1024 + +/** + * jack_intclient_t is an opaque type representing a loaded internal + * client. You may only access it using the API provided in @ref + * intclient.h "". + */ +typedef uint64_t jack_intclient_t; + +/** + * jack_port_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_port jack_port_t; + +/** + * jack_client_t is an opaque type. You may only access it using the + * API provided. + */ +typedef struct _jack_client jack_client_t; + +/** + * Ports have unique ids. A port registration callback is the only + * place you ever need to know their value. + */ +typedef uint32_t jack_port_id_t; + +typedef uint32_t jack_port_type_id_t; + +/** + * @ref jack_options_t bits + */ +enum JackOptions { + + /** + * Null value to use when no option bits are needed. + */ + JackNullOption = 0x00, + + /** + * Do not automatically start the JACK server when it is not + * already running. This option is always selected if + * \$JACK_NO_START_SERVER is defined in the calling process + * environment. + */ + JackNoStartServer = 0x01, + + /** + * Use the exact client name requested. Otherwise, JACK + * automatically generates a unique one, if needed. + */ + JackUseExactName = 0x02, + + /** + * Open with optional (char *) server_name parameter. + */ + JackServerName = 0x04, + + /** + * Load internal client from optional (char *) + * load_name. Otherwise use the @a client_name. + */ + JackLoadName = 0x08, + + /** + * Pass optional (char *) load_init string to the + * jack_initialize() entry point of an internal client. + */ + JackLoadInit = 0x10, + + /** + * pass a SessionID Token this allows the sessionmanager to identify the client again. + */ + JackSessionID = 0x20 +}; + +/** Valid options for opening an external client. */ +#define JackOpenOptions (JackSessionID|JackServerName|JackNoStartServer|JackUseExactName) + +/** Valid options for loading an internal client. */ +#define JackLoadOptions (JackLoadInit|JackLoadName|JackUseExactName) + +/** + * Options for several JACK operations, formed by OR-ing together the + * relevant @ref JackOptions bits. + */ +typedef enum JackOptions jack_options_t; + +/** + * @ref jack_status_t bits + */ +enum JackStatus { + + /** + * Overall operation failed. + */ + JackFailure = 0x01, + + /** + * The operation contained an invalid or unsupported option. + */ + JackInvalidOption = 0x02, + + /** + * The desired client name was not unique. With the @ref + * JackUseExactName option this situation is fatal. Otherwise, + * the name was modified by appending a dash and a two-digit + * number in the range "-01" to "-99". The + * jack_get_client_name() function will return the exact string + * that was used. If the specified @a client_name plus these + * extra characters would be too long, the open fails instead. + */ + JackNameNotUnique = 0x04, + + /** + * The JACK server was started as a result of this operation. + * Otherwise, it was running already. In either case the caller + * is now connected to jackd, so there is no race condition. + * When the server shuts down, the client will find out. + */ + JackServerStarted = 0x08, + + /** + * Unable to connect to the JACK server. + */ + JackServerFailed = 0x10, + + /** + * Communication error with the JACK server. + */ + JackServerError = 0x20, + + /** + * Requested client does not exist. + */ + JackNoSuchClient = 0x40, + + /** + * Unable to load internal client + */ + JackLoadFailure = 0x80, + + /** + * Unable to initialize client + */ + JackInitFailure = 0x100, + + /** + * Unable to access shared memory + */ + JackShmFailure = 0x200, + + /** + * Client's protocol version does not match + */ + JackVersionError = 0x400, + + /** + * Backend error + */ + JackBackendError = 0x800, + + /** + * Client zombified failure + */ + JackClientZombie = 0x1000 +}; + +/** + * Status word returned from several JACK operations, formed by + * OR-ing together the relevant @ref JackStatus bits. + */ +typedef enum JackStatus jack_status_t; + +/** + * @ref jack_latency_callback_mode_t + */ +enum JackLatencyCallbackMode { + + /** + * Latency Callback for Capture Latency. + * Input Ports have their latency value setup. + * In the Callback the client needs to set the latency of the output ports + */ + JackCaptureLatency, + + /** + * Latency Callback for Playback Latency. + * Output Ports have their latency value setup. + * In the Callback the client needs to set the latency of the input ports + */ + JackPlaybackLatency + +}; + +/** + * Type of Latency Callback (Capture or Playback) + */ +typedef enum JackLatencyCallbackMode jack_latency_callback_mode_t; + +/** + * Prototype for the client supplied function that is called + * by the engine when port latencies need to be recalculated + * + * @param mode playback or capture latency + * @param arg pointer to a client supplied data + * + * @return zero on success, non-zero on error + */ +typedef void (*JackLatencyCallback)(jack_latency_callback_mode_t mode, void *arg); + +/** + * the new latency API operates on Ranges. + */ +PRE_PACKED_STRUCTURE +struct _jack_latency_range +{ + /** + * minimum latency + */ + jack_nframes_t min; + /** + * maximum latency + */ + jack_nframes_t max; +} POST_PACKED_STRUCTURE; + +typedef struct _jack_latency_range jack_latency_range_t; + +/** + * Prototype for the client supplied function that is called + * by the engine anytime there is work to be done. + * + * @pre nframes == jack_get_buffer_size() + * @pre nframes == pow(2,x) + * + * @param nframes number of frames to process + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackProcessCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client thread routine called + * by the engine when the client is inserted in the graph. + * + * @param arg pointer to a client supplied structure + * + */ +typedef void *(*JackThreadCallback)(void* arg); + +/** + * Prototype for the client supplied function that is called + * once after the creation of the thread in which other + * callbacks will be made. Special thread characteristics + * can be set from this callback, for example. This is a + * highly specialized callback and most clients will not + * and should not use it. + * + * @param arg pointer to a client supplied structure + * + * @return void + */ +typedef void (*JackThreadInitCallback)(void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever the processing graph is reordered. + * + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackGraphOrderCallback)(void *arg); + +/** + * Prototype for the client-supplied function that is called whenever + * an xrun has occured. + * + * @see jack_get_xrun_delayed_usecs() + * + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackXRunCallback)(void *arg); + +/** + * Prototype for the @a bufsize_callback that is invoked whenever the + * JACK engine buffer size changes. Although this function is called + * in the JACK process thread, the normal process cycle is suspended + * during its operation, causing a gap in the audio flow. So, the @a + * bufsize_callback can allocate storage, touch memory not previously + * referenced, and perform other operations that are not realtime + * safe. + * + * @param nframes buffer size + * @param arg pointer supplied by jack_set_buffer_size_callback(). + * + * @return zero on success, non-zero on error + */ +typedef int (*JackBufferSizeCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client supplied function that is called + * when the engine sample rate changes. + * + * @param nframes new engine sample rate + * @param arg pointer to a client supplied structure + * + * @return zero on success, non-zero on error + */ +typedef int (*JackSampleRateCallback)(jack_nframes_t nframes, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a port is registered or unregistered. + * + * @param port the ID of the port + * @param arg pointer to a client supplied data + * @param register non-zero if the port is being registered, + * zero if the port is being unregistered + */ +typedef void (*JackPortRegistrationCallback)(jack_port_id_t port, int /* register */, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a client is registered or unregistered. + * + * @param name a null-terminated string containing the client name + * @param register non-zero if the client is being registered, + * zero if the client is being unregistered + * @param arg pointer to a client supplied structure + */ +typedef void (*JackClientRegistrationCallback)(const char* name, int /* register */, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever a port is connected or disconnected. + * + * @param a one of two ports connected or disconnected + * @param b one of two ports connected or disconnected + * @param connect non-zero if ports were connected + * zero if ports were disconnected + * @param arg pointer to a client supplied data + */ +typedef void (*JackPortConnectCallback)(jack_port_id_t a, jack_port_id_t b, int connect, void* arg); + +/** + * Prototype for the client supplied function that is called + * whenever the port name has been changed. + * + * @param port the port that has been renamed + * @param new_name the new name + * @param arg pointer to a client supplied structure + */ +typedef void (*JackPortRenameCallback)(jack_port_id_t port, const char* old_name, const char* new_name, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd starts or stops freewheeling. + * + * @param starting non-zero if we start starting to freewheel, zero otherwise + * @param arg pointer to a client supplied structure + */ +typedef void (*JackFreewheelCallback)(int starting, void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client ressources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + * + * @param arg pointer to a client supplied structure + */ +typedef void (*JackShutdownCallback)(void *arg); + +/** + * Prototype for the client supplied function that is called + * whenever jackd is shutdown. Note that after server shutdown, + * the client pointer is *not* deallocated by libjack, + * the application is responsible to properly use jack_client_close() + * to release client ressources. Warning: jack_client_close() cannot be + * safely used inside the shutdown callback and has to be called outside of + * the callback context. + + * @param code a status word, formed by OR-ing together the relevant @ref JackStatus bits. + * @param reason a string describing the shutdown reason (backend failure, server crash... etc...). + * Note that this string will not be available anymore after the callback returns, so possibly copy it. + * @param arg pointer to a client supplied structure + */ +typedef void (*JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg); + +/** + * Used for the type argument of jack_port_register() for default + * audio ports and midi ports. + */ +#define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" +#define JACK_DEFAULT_MIDI_TYPE "8 bit raw midi" + +/** + * For convenience, use this typedef if you want to be able to change + * between float and double. You may want to typedef sample_t to + * jack_default_audio_sample_t in your application. + */ +typedef float jack_default_audio_sample_t; + +/** + * A port has a set of flags that are formed by AND-ing together the + * desired values from the list below. The flags "JackPortIsInput" and + * "JackPortIsOutput" are mutually exclusive and it is an error to use + * them both. + */ +enum JackPortFlags { + + /** + * if JackPortIsInput is set, then the port can receive + * data. + */ + JackPortIsInput = 0x1, + + /** + * if JackPortIsOutput is set, then data can be read from + * the port. + */ + JackPortIsOutput = 0x2, + + /** + * if JackPortIsPhysical is set, then the port corresponds + * to some kind of physical I/O connector. + */ + JackPortIsPhysical = 0x4, + + /** + * if JackPortCanMonitor is set, then a call to + * jack_port_request_monitor() makes sense. + * + * Precisely what this means is dependent on the client. A typical + * result of it being called with TRUE as the second argument is + * that data that would be available from an output port (with + * JackPortIsPhysical set) is sent to a physical output connector + * as well, so that it can be heard/seen/whatever. + * + * Clients that do not control physical interfaces + * should never create ports with this bit set. + */ + JackPortCanMonitor = 0x8, + + /** + * JackPortIsTerminal means: + * + * for an input port: the data received by the port + * will not be passed on or made + * available at any other port + * + * for an output port: the data available at the port + * does not originate from any other port + * + * Audio synthesizers, I/O hardware interface clients, HDR + * systems are examples of clients that would set this flag for + * their ports. + */ + JackPortIsTerminal = 0x10, + +}; + +/** + * Transport states. + */ +typedef enum { + + /* the order matters for binary compatibility */ + JackTransportStopped = 0, /**< Transport halted */ + JackTransportRolling = 1, /**< Transport playing */ + JackTransportLooping = 2, /**< For OLD_TRANSPORT, now ignored */ + JackTransportStarting = 3, /**< Waiting for sync ready */ + JackTransportNetStarting = 4, /**< Waiting for sync ready on the network*/ + +} jack_transport_state_t; + +typedef uint64_t jack_unique_t; /**< Unique ID (opaque) */ + +/** + * Optional struct jack_position_t fields. + */ +typedef enum { + + JackPositionBBT = 0x10, /**< Bar, Beat, Tick */ + JackPositionTimecode = 0x20, /**< External timecode */ + JackBBTFrameOffset = 0x40, /**< Frame offset of BBT information */ + JackAudioVideoRatio = 0x80, /**< audio frames per video frame */ + JackVideoFrameOffset = 0x100 /**< frame offset of first video frame */ + +} jack_position_bits_t; + +/** all valid position bits */ +#define JACK_POSITION_MASK (JackPositionBBT|JackPositionTimecode) +#define EXTENDED_TIME_INFO + +PRE_PACKED_STRUCTURE +struct _jack_position { + + /* these four cannot be set from clients: the server sets them */ + jack_unique_t unique_1; /**< unique ID */ + jack_time_t usecs; /**< monotonic, free-rolling */ + jack_nframes_t frame_rate; /**< current frame rate (per second) */ + jack_nframes_t frame; /**< frame number, always present */ + + jack_position_bits_t valid; /**< which other fields are valid */ + + /* JackPositionBBT fields: */ + int32_t bar; /**< current bar */ + int32_t beat; /**< current beat-within-bar */ + int32_t tick; /**< current tick-within-beat */ + double bar_start_tick; + + float beats_per_bar; /**< time signature "numerator" */ + float beat_type; /**< time signature "denominator" */ + double ticks_per_beat; + double beats_per_minute; + + /* JackPositionTimecode fields: (EXPERIMENTAL: could change) */ + double frame_time; /**< current time in seconds */ + double next_time; /**< next sequential frame_time + (unless repositioned) */ + + /* JackBBTFrameOffset fields: */ + jack_nframes_t bbt_offset; /**< frame offset for the BBT fields + (the given bar, beat, and tick + values actually refer to a time + frame_offset frames before the + start of the cycle), should + be assumed to be 0 if + JackBBTFrameOffset is not + set. If JackBBTFrameOffset is + set and this value is zero, the BBT + time refers to the first frame of this + cycle. If the value is positive, + the BBT time refers to a frame that + many frames before the start of the + cycle. */ + + /* JACK video positional data (experimental) */ + + float audio_frames_per_video_frame; /**< number of audio frames + per video frame. Should be assumed + zero if JackAudioVideoRatio is not + set. If JackAudioVideoRatio is set + and the value is zero, no video + data exists within the JACK graph */ + + jack_nframes_t video_offset; /**< audio frame at which the first video + frame in this cycle occurs. Should + be assumed to be 0 if JackVideoFrameOffset + is not set. If JackVideoFrameOffset is + set, but the value is zero, there is + no video frame within this cycle. */ + + /* For binary compatibility, new fields should be allocated from + * this padding area with new valid bits controlling access, so + * the existing structure size and offsets are preserved. */ + int32_t padding[7]; + + /* When (unique_1 == unique_2) the contents are consistent. */ + jack_unique_t unique_2; /**< unique ID */ + +} POST_PACKED_STRUCTURE; + +typedef struct _jack_position jack_position_t; + +/** + * Prototype for the @a sync_callback defined by slow-sync clients. + * When the client is active, this callback is invoked just before + * process() in the same thread. This occurs once after registration, + * then subsequently whenever some client requests a new position, or + * the transport enters the ::JackTransportStarting state. This + * realtime function must not wait. + * + * The transport @a state will be: + * + * - ::JackTransportStopped when a new position is requested; + * - ::JackTransportStarting when the transport is waiting to start; + * - ::JackTransportRolling when the timeout has expired, and the + * position is now a moving target. + * + * @param state current transport state. + * @param pos new transport position. + * @param arg the argument supplied by jack_set_sync_callback(). + * + * @return TRUE (non-zero) when ready to roll. + */ +typedef int (*JackSyncCallback)(jack_transport_state_t state, + jack_position_t *pos, + void *arg); + + +/** + * Prototype for the @a timebase_callback used to provide extended + * position information. Its output affects all of the following + * process cycle. This realtime function must not wait. + * + * This function is called immediately after process() in the same + * thread whenever the transport is rolling, or when any client has + * requested a new position in the previous cycle. The first cycle + * after jack_set_timebase_callback() is also treated as a new + * position, or the first cycle after jack_activate() if the client + * had been inactive. + * + * The timebase master may not use its @a pos argument to set @a + * pos->frame. To change position, use jack_transport_reposition() or + * jack_transport_locate(). These functions are realtime-safe, the @a + * timebase_callback can call them directly. + * + * @param state current transport state. + * @param nframes number of frames in current period. + * @param pos address of the position structure for the next cycle; @a + * pos->frame will be its frame number. If @a new_pos is FALSE, this + * structure contains extended position information from the current + * cycle. If TRUE, it contains whatever was set by the requester. + * The @a timebase_callback's task is to update the extended + * information here. + * @param new_pos TRUE (non-zero) for a newly requested @a pos, or for + * the first cycle after the @a timebase_callback is defined. + * @param arg the argument supplied by jack_set_timebase_callback(). + */ +typedef void (*JackTimebaseCallback)(jack_transport_state_t state, + jack_nframes_t nframes, + jack_position_t *pos, + int new_pos, + void *arg); + +/********************************************************************* + * The following interfaces are DEPRECATED. They are only provided + * for compatibility with the earlier JACK transport implementation. + *********************************************************************/ + +/** + * Optional struct jack_transport_info_t fields. + * + * @see jack_position_bits_t. + */ +typedef enum { + + JackTransportState = 0x1, /**< Transport state */ + JackTransportPosition = 0x2, /**< Frame number */ + JackTransportLoop = 0x4, /**< Loop boundaries (ignored) */ + JackTransportSMPTE = 0x8, /**< SMPTE (ignored) */ + JackTransportBBT = 0x10 /**< Bar, Beat, Tick */ + +} jack_transport_bits_t; + +/** + * Deprecated struct for transport position information. + * + * @deprecated This is for compatibility with the earlier transport + * interface. Use the jack_position_t struct, instead. + */ +typedef struct { + + /* these two cannot be set from clients: the server sets them */ + + jack_nframes_t frame_rate; /**< current frame rate (per second) */ + jack_time_t usecs; /**< monotonic, free-rolling */ + + jack_transport_bits_t valid; /**< which fields are legal to read */ + jack_transport_state_t transport_state; + jack_nframes_t frame; + jack_nframes_t loop_start; + jack_nframes_t loop_end; + + long smpte_offset; /**< SMPTE offset (from frame 0) */ + float smpte_frame_rate; /**< 29.97, 30, 24 etc. */ + + int bar; + int beat; + int tick; + double bar_start_tick; + + float beats_per_bar; + float beat_type; + double ticks_per_beat; + double beats_per_minute; + +} jack_transport_info_t; + + +#endif /* __jack_types_h__ */ diff --git a/include/jack/weakjack.h b/include/jack/weakjack.h new file mode 100644 index 00000000000..4a5e7d30939 --- /dev/null +++ b/include/jack/weakjack.h @@ -0,0 +1,125 @@ +/* + Copyright (C) 2010 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __weakjack_h__ +#define __weakjack_h__ + +/** + * @defgroup WeakLinkage Managing support for newer/older versions of JACK + * @{ One challenge faced by developers is that of taking + * advantage of new features introduced in new versions + * of [ JACK ] while still supporting older versions of + * the system. Normally, if an application uses a new + * feature in a library/API, it is unable to run on + * earlier versions of the library/API that do not + * support that feature. Such applications would either + * fail to launch or crash when an attempt to use the + * feature was made. This problem cane be solved using + * weakly-linked symbols. + * + * When a symbol in a framework is defined as weakly + * linked, the symbol does not have to be present at + * runtime for a process to continue running. The static + * linker identifies a weakly linked symbol as such in + * any code module that references the symbol. The + * dynamic linker uses this same information at runtime + * to determine whether a process can continue + * running. If a weakly linked symbol is not present in + * the framework, the code module can continue to run as + * long as it does not reference the symbol. However, if + * the symbol is present, the code can use it normally. + * + * (adapted from: http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html) + * + * A concrete example will help. Suppose that someone uses a version + * of a JACK client we'll call "Jill". Jill was linked against a version + * of JACK that contains a newer part of the API (say, jack_set_latency_callback()) + * and would like to use it if it is available. + * + * When Jill is run on a system that has a suitably "new" version of + * JACK, this function will be available entirely normally. But if Jill + * is run on a system with an old version of JACK, the function isn't + * available. + * + * With normal symbol linkage, this would create a startup error whenever + * someone tries to run Jill with the "old" version of JACK. However, functions + * added to JACK after version 0.116.2 are all declared to have "weak" linkage + * which means that their abscence doesn't cause an error during program + * startup. Instead, Jill can test whether or not the symbol jack_set_latency_callback + * is null or not. If its null, it means that the JACK installed on this machine + * is too old to support this function. If its not null, then Jill can use it + * just like any other function in the API. For example: + * + * \code + * if (jack_set_latency_callback) { + * jack_set_latency_callback (jill_client, jill_latency_callback, arg); + * } + * \endcode + * + * However, there are clients that may want to use this approach to parts of the + * the JACK API that predate 0.116.2. For example, they might want to see if even + * really old basic parts of the API like jack_client_open() exist at runtime. + * + * Such clients should include before any other JACK header. + * This will make the \b entire JACK API be subject to weak linkage, so that any + * and all functions can be checked for existence at runtime. It is important + * to understand that very few clients need to do this - if you use this + * feature you should have a clear reason to do so. + * + * + */ + +#ifdef __APPLE__ +#define WEAK_ATTRIBUTE weak_import +#else +#define WEAK_ATTRIBUTE __weak__ +#endif + +#ifndef JACK_OPTIONAL_WEAK_EXPORT +/* JACK_OPTIONAL_WEAK_EXPORT needs to be a macro which + expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of + the symbol it used with. For this to work fully may + require linker arguments for the client as well. +*/ +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) +#else +/* Add other things here for non-gcc platforms */ +#endif +#endif + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +/* JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT needs to be a macro + which expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of the + symbol it is used with AND optionally to mark the symbol + as deprecated. For this to work fully may require + linker arguments for the client as well. +*/ +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((WEAK_ATTRIBUTE,__deprecated__)) +#else +/* Add other things here for non-gcc platforms */ +#endif +#endif + +/*@}*/ + +#endif /* weakjack */ diff --git a/include/jack/weakmacros.h b/include/jack/weakmacros.h new file mode 100644 index 00000000000..e736a1ee28f --- /dev/null +++ b/include/jack/weakmacros.h @@ -0,0 +1,97 @@ +/* + Copyright (C) 2010 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __weakmacros_h__ +#define __weakmacros_h__ + +/************************************************************* + * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function + * added to the JACK API after the 0.116.2 release. + * + * Functions that predate this release are marked with + * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile + * time in a variety of ways. The default definition is empty, + * so that these symbols get normal linkage. If you wish to + * use all JACK symbols with weak linkage, include + * before jack.h. + *************************************************************/ + +#ifdef __APPLE__ +#define WEAK_ATTRIBUTE weak_import +#else +#define WEAK_ATTRIBUTE __weak__ +#endif + +#ifndef JACK_WEAK_EXPORT +#ifdef __GNUC__ +/* JACK_WEAK_EXPORT needs to be a macro which + expands into a compiler directive. If non-null, the directive + must tell the compiler to arrange for weak linkage of + the symbol it used with. For this to work full may + require linker arguments in the client as well. +*/ + +#ifdef _WIN32 + /* + Not working with __declspec(dllexport) so normal linking + Linking with JackWeakAPI.cpp will be the preferred way. + */ + #define JACK_WEAK_EXPORT +#else + #define JACK_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) +#endif + +#else +/* Add other things here for non-gcc platforms */ + +#ifdef _WIN32 +#define JACK_WEAK_EXPORT +#endif + +#endif +#endif + +#ifndef JACK_WEAK_EXPORT +#define JACK_WEAK_EXPORT +#endif + +#ifndef JACK_OPTIONAL_WEAK_EXPORT +#define JACK_OPTIONAL_WEAK_EXPORT +#endif + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#ifdef __GNUC__ +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((__deprecated__)) +#else +/* Add other things here for non-gcc platforms */ + +#ifdef _WIN32 +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#endif + +#endif /* __GNUC__ */ + +#ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT +#endif + +#endif + +#endif /* __weakmacros_h__ */ + diff --git a/include/lmms_basics.h b/include/lmms_basics.h index 637867b4d34..1de4add96ae 100644 --- a/include/lmms_basics.h +++ b/include/lmms_basics.h @@ -34,6 +34,7 @@ #include #endif +#include typedef int32_t tact_t; typedef int32_t tick_t; @@ -59,7 +60,7 @@ typedef uint32_t jo_id_t; // (unique) ID of a journalling object #define likely(x) Q_LIKELY(x) #define unlikely(x) Q_UNLIKELY(x) -// windows headers define "min" and "max" macros, breaking the methods bwloe +// windows headers define "min" and "max" macros, breaking the methods below #undef min #undef max @@ -121,6 +122,9 @@ const ch_cnt_t SURROUND_CHANNELS = 2; #endif +// "In audio 1 is always the left" - umcaruje, 2017 +const ch_cnt_t RIGHT_CHANNEL_INDEX = 1; +const ch_cnt_t LEFT_CHANNEL_INDEX = 0; #ifdef LMMS_BUILD_WIN32 #define LADSPA_PATH_SEPERATOR ';' @@ -128,16 +132,20 @@ const ch_cnt_t SURROUND_CHANNELS = #define LADSPA_PATH_SEPERATOR ':' #endif +typedef std::array sampleFrame; - -typedef sample_t sampleFrame[DEFAULT_CHANNELS]; -typedef sample_t surroundSampleFrame[SURROUND_CHANNELS]; +typedef std::array surroundSampleFrame; #define ALIGN_SIZE 16 #if __GNUC__ -typedef sample_t sampleFrameA[DEFAULT_CHANNELS] __attribute__((__aligned__(ALIGN_SIZE))); +typedef std::array sampleFrameA __attribute__((__aligned__(ALIGN_SIZE))); #endif +static_assert (sizeof(sampleFrame) == sizeof(sample_t) * DEFAULT_CHANNELS, + "sampleFrame's size is not equal to the sum of its parts"); +static_assert (sizeof(surroundSampleFrame) == sizeof(sample_t) * SURROUND_CHANNELS, + "surroundSampleFrame's size is not equal to the sum of its parts"); + #define STRINGIFY(s) STR(s) #define STR(PN) #PN diff --git a/plugins/audio_file_processor/audio_file_processor.cpp b/plugins/audio_file_processor/audio_file_processor.cpp index 278e681949c..9513aa55cc8 100644 --- a/plugins/audio_file_processor/audio_file_processor.cpp +++ b/plugins/audio_file_processor/audio_file_processor.cpp @@ -1,4 +1,4 @@ -/* +/* * audio_file_processor.cpp - instrument for using audio-files * * Copyright (c) 2004-2014 Tobias Doerffel @@ -210,13 +210,7 @@ void audioFileProcessor::deleteNotePluginData( NotePlayHandle * _n ) void audioFileProcessor::saveSettings( QDomDocument & _doc, QDomElement & _this ) { - _this.setAttribute( "src", m_sampleBuffer.audioFile() ); - if( m_sampleBuffer.audioFile() == "" ) - { - QString s; - _this.setAttribute( "sampledata", - m_sampleBuffer.toBase64( s ) ); - } + m_sampleBuffer.saveState (_doc, _this); m_reverseModel.saveSettings( _doc, _this, "reversed" ); m_loopModel.saveSettings( _doc, _this, "looped" ); m_ampModel.saveSettings( _doc, _this, "amp" ); @@ -247,7 +241,11 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } else if( _this.attribute( "sampledata" ) != "" ) { - m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) ); + m_sampleBuffer.loadFromBase64( _this.attribute( "srcdata" ) , + Engine::mixer ()->baseSampleRate ()); + qWarning("Using default sampleRate. That could lead to invalid values"); + } else { + m_sampleBuffer.restoreState (_this.firstChildElement (m_sampleBuffer.nodeName ())); } m_loopModel.loadSettings( _this, "looped" ); @@ -267,6 +265,8 @@ void audioFileProcessor::loadSettings( const QDomElement & _this ) } m_reverseModel.loadSettings( _this, "reversed" ); + // The current state of m_sampleBuffer. + m_isCurrentlyReversed = m_reverseModel.value (); m_stutterModel.loadSettings( _this, "stutter" ); if( _this.hasAttribute( "interp" ) ) @@ -343,7 +343,12 @@ void audioFileProcessor::setAudioFile( const QString & _audio_file, void audioFileProcessor::reverseModelChanged( void ) { - m_sampleBuffer.setReversed( m_reverseModel.value() ); + if (m_reverseModel.value () != m_isCurrentlyReversed) { + m_isCurrentlyReversed = m_reverseModel.value (); + + m_sampleBuffer.reverse (true); + } + m_nextPlayStartPoint = m_sampleBuffer.startFrame(); m_nextPlayBackwards = false; } @@ -644,7 +649,7 @@ void AudioFileProcessorView::newWaveView() delete m_waveView; m_waveView = 0; } - m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer ); + m_waveView = new AudioFileProcessorWaveView( this, 245, 75, castModel()->m_sampleBuffer, castModel()); m_waveView->move( 2, 172 ); m_waveView->setKnobs( dynamic_cast( m_startKnob ), @@ -771,8 +776,10 @@ void AudioFileProcessorWaveView::updateSampleRange() } } -AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf ) : +AudioFileProcessorWaveView::AudioFileProcessorWaveView(QWidget * _parent, int _w, int _h, SampleBuffer& buf , + audioFileProcessor *fileProcessor) : QWidget( _parent ), + m_audioFileProcessor{fileProcessor}, m_sampleBuffer( buf ), m_graph( QPixmap( _w - 2 * s_padding, _h - 2 * s_padding ) ), m_from( 0 ), @@ -784,7 +791,7 @@ AudioFileProcessorWaveView::AudioFileProcessorWaveView( QWidget * _parent, int _ m_endKnob( 0 ), m_loopKnob( 0 ), m_isDragging( false ), - m_reversed( false ), + m_reversed(false), m_framesPlayed( 0 ), m_animation(ConfigManager::inst()->value("ui", "animateafp").toInt()) { @@ -1063,11 +1070,9 @@ void AudioFileProcessorWaveView::updateGraph() m_to = m_sampleBuffer.endFrame(); } - if( m_sampleBuffer.reversed() != m_reversed ) - { + if(m_audioFileProcessor->isReversed () != m_reversed) { reverse(); - } - else if( m_last_from == m_from && m_last_to == m_to && m_sampleBuffer.amplification() == m_last_amp ) + } else if( m_last_from == m_from && m_last_to == m_to && m_sampleBuffer.amplification() == m_last_amp ) { return; } @@ -1084,11 +1089,23 @@ void AudioFileProcessorWaveView::updateGraph() p, QRect( 0, 0, m_graph.width(), m_graph.height() ), m_from, m_to - ); + ); } +void AudioFileProcessorWaveView::reverse() +{ + slideSampleByFrames( + m_sampleBuffer.frames() + - m_sampleBuffer.endFrame() + - m_sampleBuffer.startFrame() + ); + const f_cnt_t from = m_from; + m_from = m_sampleBuffer.frames() - m_to; + m_to = m_sampleBuffer.frames() - from; + m_reversed = ! m_reversed; +} void AudioFileProcessorWaveView::zoom( const bool _out ) { @@ -1244,24 +1261,6 @@ void AudioFileProcessorWaveView::slideSampleByFrames( f_cnt_t _frames ) -void AudioFileProcessorWaveView::reverse() -{ - slideSampleByFrames( - m_sampleBuffer.frames() - - m_sampleBuffer.endFrame() - - m_sampleBuffer.startFrame() - ); - - const f_cnt_t from = m_from; - m_from = m_sampleBuffer.frames() - m_to; - m_to = m_sampleBuffer.frames() - from; - - m_reversed = ! m_reversed; -} - - - - void AudioFileProcessorWaveView::knob::slideTo( double _v, bool _check_bound ) { if( _check_bound && ! checkBound( _v ) ) diff --git a/plugins/audio_file_processor/audio_file_processor.h b/plugins/audio_file_processor/audio_file_processor.h index 150807686e9..cb12b3631a0 100644 --- a/plugins/audio_file_processor/audio_file_processor.h +++ b/plugins/audio_file_processor/audio_file_processor.h @@ -66,6 +66,10 @@ class audioFileProcessor : public Instrument virtual PluginView * instantiateView( QWidget * _parent ); + bool isReversed () const + { + return m_isCurrentlyReversed; + } public slots: void setAudioFile( const QString & _audio_file, bool _rename = true ); @@ -102,6 +106,15 @@ private slots: f_cnt_t m_nextPlayStartPoint; bool m_nextPlayBackwards; + /** + * @brief Is currently m_sampleBuffer reversed? + * + * Since m_sampleBuffer has no longer the `reversed` + * attribute, we track it here; m_reverseModel is + * only what the user want it to be. + */ + bool m_isCurrentlyReversed = false; + friend class AudioFileProcessorView; } ; @@ -236,6 +249,7 @@ public slots: sample_loop } ; + audioFileProcessor *m_audioFileProcessor; SampleBuffer& m_sampleBuffer; QPixmap m_graph; f_cnt_t m_from; @@ -257,7 +271,7 @@ public slots: bool m_animation; public: - AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf ); + AudioFileProcessorWaveView( QWidget * _parent, int _w, int _h, SampleBuffer& buf, audioFileProcessor *fileProcessor); void setKnobs(knob *_start, knob *_end, knob *_loop ); diff --git a/plugins/patman/patman.cpp b/plugins/patman/patman.cpp index 2a15303bb46..6a56062b922 100644 --- a/plugins/patman/patman.cpp +++ b/plugins/patman/patman.cpp @@ -170,7 +170,7 @@ void patmanInstrument::playNote( NotePlayHandle * _n, void patmanInstrument::deleteNotePluginData( NotePlayHandle * _n ) { handle_data * hdata = (handle_data *)_n->m_pluginData; - sharedObject::unref( hdata->sample ); + delete hdata->state; delete hdata; } @@ -346,7 +346,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( } } - sampleFrame * data = new sampleFrame[frames]; + SampleBuffer::DataVector data(frames); for( f_cnt_t frame = 0; frame < frames; ++frame ) { @@ -357,9 +357,8 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( } } - SampleBuffer* psample = new SampleBuffer( data, frames ); + auto psample = std::make_shared( std::move(data), sample_rate ); psample->setFrequency( root_freq / 1000.0f ); - psample->setSampleRate( sample_rate ); if( modes & MODES_LOOPING ) { @@ -367,10 +366,9 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( psample->setLoopEndFrame( loop_end ); } - m_patchSamples.push_back( psample ); + m_patchSamples.push_back( std::move(psample) ); delete[] wave_samples; - delete[] data; } fclose( fd ); return( LoadOK ); @@ -381,11 +379,7 @@ patmanInstrument::LoadErrors patmanInstrument::loadPatch( void patmanInstrument::unloadCurrentPatch( void ) { - while( !m_patchSamples.empty() ) - { - sharedObject::unref( m_patchSamples.back() ); - m_patchSamples.pop_back(); - } + m_patchFile.clear (); } @@ -396,18 +390,18 @@ void patmanInstrument::selectSample( NotePlayHandle * _n ) const float freq = _n->frequency(); float min_dist = HUGE_VALF; - SampleBuffer* sample = NULL; + std::shared_ptr sample; - for( QVector::iterator it = m_patchSamples.begin(); it != m_patchSamples.end(); ++it ) + for( auto &element : m_patchSamples ) { - float patch_freq = ( *it )->frequency(); + float patch_freq = element->frequency(); float dist = freq >= patch_freq ? freq / patch_freq : patch_freq / freq; if( dist < min_dist ) { min_dist = dist; - sample = *it; + sample = element; } } @@ -415,11 +409,11 @@ void patmanInstrument::selectSample( NotePlayHandle * _n ) hdata->tuned = m_tunedModel.value(); if( sample ) { - hdata->sample = sharedObject::ref( sample ); + hdata->sample = sample; } else { - hdata->sample = new SampleBuffer( NULL, 0 ); + hdata->sample = std::make_shared(); } hdata->state = new SampleBuffer::handleState( _n->hasDetuningInfo() ); diff --git a/plugins/patman/patman.h b/plugins/patman/patman.h index a3b5a39b898..ed653b2058f 100644 --- a/plugins/patman/patman.h +++ b/plugins/patman/patman.h @@ -31,6 +31,7 @@ #include "SampleBuffer.h" #include "AutomatableModel.h" #include "MemoryManager.h" +#include class PixmapButton; @@ -82,11 +83,11 @@ public slots: MM_OPERATORS SampleBuffer::handleState* state; bool tuned; - SampleBuffer* sample; + std::shared_ptr sample; } handle_data; QString m_patchFile; - QVector m_patchSamples; + QVector> m_patchSamples; BoolModel m_loopedModel; BoolModel m_tunedModel; diff --git a/plugins/triple_oscillator/TripleOscillator.cpp b/plugins/triple_oscillator/TripleOscillator.cpp index 0b7ff6bbe64..ded96c41190 100644 --- a/plugins/triple_oscillator/TripleOscillator.cpp +++ b/plugins/triple_oscillator/TripleOscillator.cpp @@ -130,7 +130,7 @@ OscillatorObject::OscillatorObject( Model * _parent, int _idx ) : OscillatorObject::~OscillatorObject() { - sharedObject::unref( m_sampleBuffer ); + delete m_sampleBuffer; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff0ca8a8ee8..cace0379098 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -199,7 +199,7 @@ IF(LMMS_BUILD_WIN32) "${MINGW_PREFIX}/bin/libfftw3f-3.dll" "${MINGW_PREFIX}/bin/libFLAC-8.dll" "${MINGW_PREFIX}/bin/libpng16-16.dll" - "${MINGW_PREFIX}/bin/SDL.dll" + "${MINGW_PREFIX}/bin/SDL2.dll" "${MINGW_PREFIX}/bin/libglib-2.0-0.dll" "${MINGW_PREFIX}/bin/libgthread-2.0-0.dll" "${MINGW_PREFIX}/bin/zlib1.dll" @@ -244,6 +244,18 @@ IF(LMMS_BUILD_WIN32) DESTINATION .) ENDIF() + + SET(JACK_INSTALLER_VERSION "1.9.11") + # Bundle jack + IF (WANT_JACK) + ADD_CUSTOM_TARGET(jack_binary_installer ALL + COMMAND "curl" -OL "https://github.com/jackaudio/jackaudio.github.com/releases/download/${JACK_INSTALLER_VERSION}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + INSTALL(FILES "${CMAKE_BINARY_DIR}/Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe" DESTINATION .) + SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " + ExecWait '\\\"$INSTDIR\\\\Jack_v${JACK_INSTALLER_VERSION}_32_setup.exe\\\" /install' +") + ENDIF() ELSE(LMMS_BUILD_WIN32) IF(NOT LMMS_BUILD_APPLE) SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E") diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 85a00780b10..645502cea89 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -82,7 +82,6 @@ set(LMMS_SRCS core/audio/AudioPortAudio.cpp core/audio/AudioSoundIo.cpp core/audio/AudioPulseAudio.cpp - core/audio/AudioSampleRecorder.cpp core/audio/AudioSdl.cpp core/midi/MidiAlsaRaw.cpp @@ -97,5 +96,7 @@ set(LMMS_SRCS core/midi/MidiTime.cpp core/midi/MidiWinMM.cpp + core/SampleBufferVisualizer.cpp + PARENT_SCOPE ) diff --git a/src/core/Effect.cpp b/src/core/Effect.cpp index 8cdcc9c600a..d3a3aeaed12 100644 --- a/src/core/Effect.cpp +++ b/src/core/Effect.cpp @@ -204,8 +204,8 @@ void Effect::resample( int _i, const sampleFrame * _src_buf, } m_srcData[_i].input_frames = _frames; m_srcData[_i].output_frames = Engine::mixer()->framesPerPeriod(); - m_srcData[_i].data_in = (float *) _src_buf[0]; - m_srcData[_i].data_out = _dst_buf[0]; + m_srcData[_i].data_in = (float *) _src_buf[0].data (); + m_srcData[_i].data_out = _dst_buf[0].data (); m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr; m_srcData[_i].end_of_input = 0; int error; diff --git a/src/core/Engine.cpp b/src/core/Engine.cpp index a53abbe5d45..81484acfe71 100644 --- a/src/core/Engine.cpp +++ b/src/core/Engine.cpp @@ -103,6 +103,11 @@ void LmmsCore::destroy() delete ConfigManager::inst(); } +float LmmsCore::framesPerTick(sample_rate_t sample_rate) { + return sample_rate * 60.0f * 4 / + DefaultTicksPerTact / s_song->getTempo(); +} + diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index c1c81119f64..54306dfc65c 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -71,7 +71,7 @@ LfoController::LfoController( Model * _parent ) : LfoController::~LfoController() { - sharedObject::unref( m_userDefSampleBuffer ); + delete m_userDefSampleBuffer; m_baseModel.disconnect( this ); m_speedModel.disconnect( this ); m_amountModel.disconnect( this ); diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 3298d3dd09c..7571112262f 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -309,8 +309,7 @@ bool Mixer::criticalXRuns() const -void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) -{ +void Mixer::pushInputFrames(const sampleFrame * _ab, const f_cnt_t _frames) { requestChangeInModel(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; @@ -331,6 +330,7 @@ void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) } memcpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); + m_inputBufferFrames[ m_inputBufferWrite ] += _frames; doneChangeInModel(); @@ -397,7 +397,8 @@ const surroundSampleFrame * Mixer::renderNextBuffer() if( it != m_playHandles.end() ) { - ( *it )->audioPort()->removePlayHandle( ( *it ) ); + if ((*it )->audioPort()) + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); @@ -451,7 +452,8 @@ const surroundSampleFrame * Mixer::renderNextBuffer() } if( ( *it )->isFinished() ) { - ( *it )->audioPort()->removePlayHandle( ( *it ) ); + if (( *it )->audioPort()) + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); @@ -574,7 +576,6 @@ void Mixer::changeQuality( const struct qualitySettings & _qs ) - void Mixer::setAudioDevice( AudioDevice * _dev ) { stopProcessing(); @@ -675,7 +676,8 @@ bool Mixer::addPlayHandle( PlayHandle* handle ) if( criticalXRuns() == false ) { m_newPlayHandles.push( handle ); - handle->audioPort()->addPlayHandle( handle ); + if (handle->audioPort()) + handle->audioPort()->addPlayHandle( handle ); return true; } @@ -697,7 +699,8 @@ void Mixer::removePlayHandle( PlayHandle * _ph ) if( _ph->affinityMatters() && _ph->affinity() == QThread::currentThread() ) { - _ph->audioPort()->removePlayHandle( _ph ); + if (_ph->audioPort()) + _ph->audioPort()->removePlayHandle( _ph ); bool removedFromList = false; // Check m_newPlayHandles first because doing it the other way around // creates a race condition @@ -757,7 +760,8 @@ void Mixer::removePlayHandlesOfTypes( Track * _track, const quint8 types ) { if( ( *it )->isFromTrack( _track ) && ( ( *it )->type() & types ) ) { - ( *it )->audioPort()->removePlayHandle( ( *it ) ); + if (( *it )->audioPort()) + ( *it )->audioPort()->removePlayHandle( ( *it ) ); if( ( *it )->type() == PlayHandle::TypeNotePlayHandle ) { NotePlayHandleManager::release( (NotePlayHandle*) *it ); @@ -1058,15 +1062,15 @@ MidiClient * Mixer::tryMidiClients() #endif #ifdef LMMS_BUILD_APPLE - printf( "trying midi apple...\n" ); - if( client_name == MidiApple::name() || client_name == "" ) - { - MidiApple * mapple = new MidiApple; - m_midiClientName = MidiApple::name(); - printf( "Returning midi apple\n" ); - return mapple; - } - printf( "midi apple didn't work: client_name=%s\n", client_name.toUtf8().constData()); + printf( "trying midi apple...\n" ); + if( client_name == MidiApple::name() || client_name == "" ) + { + MidiApple * mapple = new MidiApple; + m_midiClientName = MidiApple::name(); + printf( "Returning midi apple\n" ); + return mapple; + } + printf( "midi apple didn't work: client_name=%s\n", client_name.toUtf8().constData()); #endif printf( "Couldn't create MIDI-client, neither with ALSA nor with " diff --git a/src/core/PlayHandle.cpp b/src/core/PlayHandle.cpp index 9e92019a6b6..f62e25e6bb4 100644 --- a/src/core/PlayHandle.cpp +++ b/src/core/PlayHandle.cpp @@ -38,7 +38,8 @@ PlayHandle::PlayHandle(const Type type, f_cnt_t offset) : m_affinity(QThread::currentThread()), m_playHandleBuffer(BufferManager::acquire()), m_bufferReleased(true), - m_usesBuffer(true) + m_usesBuffer(true), + m_audioPort{nullptr} { } diff --git a/src/core/SampleBuffer.cpp b/src/core/SampleBuffer.cpp index 9cde88cf550..414d7f2c7bb 100644 --- a/src/core/SampleBuffer.cpp +++ b/src/core/SampleBuffer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -39,15 +40,6 @@ #include #endif -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -#include -#endif - -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H -#include -#endif - - #include "base64.h" #include "ConfigManager.h" #include "DrumSynth.h" @@ -58,232 +50,201 @@ #include "FileDialog.h" - - SampleBuffer::SampleBuffer() : m_audioFile( "" ), - m_origData( NULL ), - m_origFrames( 0 ), - m_data( NULL ), - m_frames( 0 ), m_startFrame( 0 ), m_endFrame( 0 ), m_loopStartFrame( 0 ), m_loopEndFrame( 0 ), m_amplification( 1.0f ), - m_reversed( false ), m_frequency( BaseFreq ), - m_sampleRate( Engine::mixer()->baseSampleRate() ) + m_sampleRate( mixerSampleRate () ) { connect( Engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( sampleRateChanged() ) ); - update(); + beginBufferChange (false); + doneBufferChange (false, /* shouldLock */ + m_sampleRate); } SampleBuffer::SampleBuffer( const QString & _audio_file, - bool _is_base64_data ) + bool _is_base64_data, sample_rate_t sampleRate ) : SampleBuffer() { if( _is_base64_data ) { - loadFromBase64( _audio_file ); + loadFromBase64( _audio_file, sampleRate ); } else { - m_audioFile = _audio_file; - update(); + changeAudioFile (_audio_file); } } - - -SampleBuffer::SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ) - : SampleBuffer() +SampleBuffer::SampleBuffer(SampleBuffer::DataVector &&movedData , sample_rate_t sampleRate) : + SampleBuffer() { - if( _frames > 0 ) + resetData (std::move(movedData), sampleRate, false); +} + +void SampleBuffer::saveSettings(QDomDocument &doc, QDomElement &_this) { { - m_origData = MM_ALLOC( sampleFrame, _frames ); - memcpy( m_origData, _data, _frames * BYTES_PER_FRAME ); - m_origFrames = _frames; - update(); + QString string; + _this.setAttribute( "data", toBase64(string) ); } + + _this.setAttribute ("sampleRate", sampleRate ()); + _this.setAttribute ("amplification", amplification ()); + _this.setAttribute ("frequency", frequency ()); } +void SampleBuffer::loadSettings(const QDomElement &_this) { + if (_this.hasAttribute ("sampleRate")) { + m_sampleRate = _this.attribute ("sampleRate").toUInt (); + } else { + qWarning("SampleBuffer::loadSettings: Using default sampleRate. That could lead to invalid values"); + } + if (_this.hasAttribute ("amplification")) { + m_amplification = _this.attribute ("amplification").toFloat (); + } + if (_this.hasAttribute ("frequency")) { + m_frequency = _this.attribute ("frequency").toFloat (); + } -SampleBuffer::SampleBuffer( const f_cnt_t _frames ) - : SampleBuffer() -{ - if( _frames > 0 ) - { - m_origData = MM_ALLOC( sampleFrame, _frames ); - memset( m_origData, 0, _frames * BYTES_PER_FRAME ); - m_origFrames = _frames; - update(); + if (_this.hasAttribute ("data")) { + loadFromBase64 (_this.attribute("data"), m_sampleRate); } } +void SampleBuffer::sampleRateChanged() { + auto previousSampleRate = sampleRate (); + auto requiredSampleRate = mixerSampleRate (); + if (requiredSampleRate == sampleRate ()) + return; + // Only resample the buffer if the processing + // sample rate is higher than the SampleBuffer's + // sample rate. + if (requiredSampleRate > sampleRate ()) { + resetData (resampleData (m_data, previousSampleRate, requiredSampleRate), + requiredSampleRate, + true); + } +} -SampleBuffer::~SampleBuffer() +void SampleBuffer::internalAddData(const DataVector &vector, sample_rate_t sampleRate) { - MM_FREE( m_origData ); - MM_FREE( m_data ); + Q_ASSERT(sampleRate == m_sampleRate); + m_data.insert (m_data.end (), vector.cbegin (), vector.cend ()); } +void SampleBuffer::internalResetData(DataVector &&newData, sample_rate_t dataSampleRate) { + Q_UNUSED(dataSampleRate); + m_audioFile = QString(); + m_data = std::move (newData); +} - -void SampleBuffer::sampleRateChanged() +sample_rate_t SampleBuffer::mixerSampleRate() { - update( true ); + return Engine::mixer ()->processingSampleRate (); } - -void SampleBuffer::update( bool _keep_settings ) +void SampleBuffer::changeAudioFile(QString audioFile) { - const bool lock = ( m_data != NULL ); - if( lock ) - { - Engine::mixer()->requestChangeInModel(); - m_varLock.lockForWrite(); - MM_FREE( m_data ); - } + if (audioFile == "") + return; // File size and sample length limits const int fileSizeMax = 300; // MB const int sampleLengthMax = 90; // Minutes bool fileLoadError = false; - if( m_audioFile.isEmpty() && m_origData != NULL && m_origFrames > 0 ) - { - // TODO: reverse- and amplification-property is not covered - // by following code... - m_data = MM_ALLOC( sampleFrame, m_origFrames ); - memcpy( m_data, m_origData, m_origFrames * BYTES_PER_FRAME ); - if( _keep_settings == false ) - { - m_frames = m_origFrames; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; - } - } - else if( !m_audioFile.isEmpty() ) - { - QString file = tryToMakeAbsolute( m_audioFile ); -#ifdef LMMS_BUILD_WIN32 + QString file = tryToMakeAbsolute( audioFile ); + #ifdef LMMS_BUILD_WIN32 char * f = qstrdup( file.toLocal8Bit().constData() ); -#else + #else char * f = qstrdup( file.toUtf8().constData() ); -#endif - int_sample_t * buf = NULL; - sample_t * fbuf = NULL; - ch_cnt_t channels = DEFAULT_CHANNELS; - sample_rate_t samplerate = Engine::mixer()->baseSampleRate(); - m_frames = 0; - - const QFileInfo fileInfo( file ); - if( fileInfo.size() > fileSizeMax * 1024 * 1024 ) - { - fileLoadError = true; - } - else + #endif + + ch_cnt_t channels = DEFAULT_CHANNELS; + sample_rate_t samplerate = mixerSampleRate (); + DataVector fileData; + + const QFileInfo fileInfo( file ); + if( fileInfo.size() > fileSizeMax * 1024 * 1024 ) + { + fileLoadError = true; + } + + if (!fileLoadError) { + SNDFILE * snd_file; + SF_INFO sf_info; + sf_info.format = 0; + if( ( snd_file = sf_open( f, SFM_READ, &sf_info ) ) != NULL ) { - SNDFILE * snd_file; - SF_INFO sf_info; - sf_info.format = 0; - if( ( snd_file = sf_open( f, SFM_READ, &sf_info ) ) != NULL ) + f_cnt_t frames = sf_info.frames; + int rate = sf_info.samplerate; + if( frames / rate > sampleLengthMax * 60 ) { - f_cnt_t frames = sf_info.frames; - int rate = sf_info.samplerate; - if( frames / rate > sampleLengthMax * 60 ) - { - fileLoadError = true; - } - sf_close( snd_file ); + fileLoadError = true; } + sf_close( snd_file ); } + } + + QString loadingWarning; + if( !fileLoadError ) { - if( !fileLoadError ) - { -#ifdef LMMS_HAVE_OGGVORBIS - // workaround for a bug in libsndfile or our libsndfile decoder - // causing some OGG files to be distorted -> try with OGG Vorbis - // decoder first if filename extension matches "ogg" - if( m_frames == 0 && fileInfo.suffix() == "ogg" ) - { - m_frames = decodeSampleOGGVorbis( f, buf, channels, samplerate ); - } -#endif - if( m_frames == 0 ) - { - m_frames = decodeSampleSF( f, fbuf, channels, - samplerate ); - } #ifdef LMMS_HAVE_OGGVORBIS - if( m_frames == 0 ) - { - m_frames = decodeSampleOGGVorbis( f, buf, channels, - samplerate ); - } + // workaround for a bug in libsndfile or our libsndfile decoder + // causing some OGG files to be distorted -> try with OGG Vorbis + // decoder first if filename extension matches "ogg" + if( fileInfo.suffix() == "ogg" ) + { + fileData = decodeSampleOGGVorbis( f, channels, samplerate ); + } #endif - if( m_frames == 0 ) - { - m_frames = decodeSampleDS( f, buf, channels, - samplerate ); - } - - delete[] f; + if(fileData.empty ()) + { + fileData = decodeSampleSF( f, channels, samplerate, loadingWarning ); } - - if ( m_frames == 0 || fileLoadError ) // if still no frames, bail +#ifdef LMMS_HAVE_OGGVORBIS + if( fileData.empty () ) { - // sample couldn't be decoded, create buffer containing - // one sample-frame - m_data = MM_ALLOC( sampleFrame, 1 ); - memset( m_data, 0, sizeof( *m_data ) ); - m_frames = 1; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; + fileData = decodeSampleOGGVorbis( f, channels, samplerate ); } - else // otherwise normalize sample rate +#endif + if( fileData.empty () ) { - normalizeSampleRate( samplerate, _keep_settings ); + fileData = decodeSampleDS( f, channels, samplerate ); } - } - else - { - // neither an audio-file nor a buffer to copy from, so create - // buffer containing one sample-frame - m_data = MM_ALLOC( sampleFrame, 1 ); - memset( m_data, 0, sizeof( *m_data ) ); - m_frames = 1; - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = 1; - } - if( lock ) - { - m_varLock.unlock(); - Engine::mixer()->doneChangeInModel(); + delete[] f; } - emit sampleUpdated(); + if (fileData.empty ()) { + fileLoadError = true; + } - if( fileLoadError ) - { + if (! fileLoadError) { + resetData (std::move(fileData), samplerate); + } else { QString title = tr( "Fail to open file" ); QString message = tr( "Audio files are limited to %1 MB " - "in size and %2 minutes of playing time" - ).arg( fileSizeMax ).arg( sampleLengthMax ); + "in size and %2 minutes of playing time" + ).arg( fileSizeMax ).arg( sampleLengthMax ); + if (! loadingWarning.isEmpty()) + message = loadingWarning; if( gui ) { QMessageBox::information( NULL, - title, message, QMessageBox::Ok ); + title, message, QMessageBox::Warning ); } else { @@ -293,123 +254,92 @@ void SampleBuffer::update( bool _keep_settings ) } } - -void SampleBuffer::convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, int _channels) +SampleBuffer::DataVector SampleBuffer::convertIntToFloat (int_sample_t * & _ibuf, f_cnt_t _frames, int _channels) { // following code transforms int-samples into // float-samples and does amplifying & reversing const float fac = 1 / OUTPUT_SAMPLE_MULTIPLIER; - m_data = MM_ALLOC( sampleFrame, _frames ); + DataVector vector(_frames); const int ch = ( _channels > 1 ) ? 1 : 0; - // if reversing is on, we also reverse when - // scaling - if( m_reversed ) + int idx = 0; + for( f_cnt_t frame = 0; frame < _frames; + ++frame ) { - int idx = ( _frames - 1 ) * _channels; - for( f_cnt_t frame = 0; frame < _frames; - ++frame ) - { - m_data[frame][0] = _ibuf[idx+0] * fac; - m_data[frame][1] = _ibuf[idx+ch] * fac; - idx -= _channels; - } - } - else - { - int idx = 0; - for( f_cnt_t frame = 0; frame < _frames; - ++frame ) - { - m_data[frame][0] = _ibuf[idx+0] * fac; - m_data[frame][1] = _ibuf[idx+ch] * fac; - idx += _channels; - } + vector[frame][0] = _ibuf[idx+0] * fac; + vector[frame][1] = _ibuf[idx+ch] * fac; + idx += _channels; } delete[] _ibuf; + + return vector; } -void SampleBuffer::directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _channels) +SampleBuffer::DataVector +SampleBuffer::resampleData (const DataVector &inputData, sample_rate_t inputSampleRate, + sample_rate_t requiredSampleRate) { + const f_cnt_t dst_frames = static_cast( inputData.size ()/ + (float) inputSampleRate * (float) requiredSampleRate ); + DataVector outputData(dst_frames); - m_data = MM_ALLOC( sampleFrame, _frames ); - const int ch = ( _channels > 1 ) ? 1 : 0; - - // if reversing is on, we also reverse when - // scaling - if( m_reversed ) + // yeah, libsamplerate, let's rock with sinc-interpolation! + int error; + SRC_STATE * state; + if( ( state = src_new( SRC_SINC_MEDIUM_QUALITY, + DEFAULT_CHANNELS, &error ) ) != NULL ) { - int idx = ( _frames - 1 ) * _channels; - for( f_cnt_t frame = 0; frame < _frames; - ++frame ) + SRC_DATA src_data; + src_data.end_of_input = 1; + src_data.data_in = libSampleRateSrc(inputData.data ())->data (); + src_data.data_out = outputData.data ()->data(); + src_data.input_frames = inputData.size (); + src_data.output_frames = dst_frames; + src_data.src_ratio = (double) requiredSampleRate / inputSampleRate; + if( ( error = src_process( state, &src_data ) ) ) { - m_data[frame][0] = _fbuf[idx+0]; - m_data[frame][1] = _fbuf[idx+ch]; - idx -= _channels; + printf( "SampleBuffer: error while resampling: %s\n", + src_strerror( error ) ); } + src_delete( state ); } else { - int idx = 0; - for( f_cnt_t frame = 0; frame < _frames; - ++frame ) - { - m_data[frame][0] = _fbuf[idx+0]; - m_data[frame][1] = _fbuf[idx+ch]; - idx += _channels; - } - } - - delete[] _fbuf; -} - - -void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, - bool _keep_settings ) -{ - // do samplerate-conversion to our default-samplerate - if( _src_sr != Engine::mixer()->baseSampleRate() ) - { - SampleBuffer * resampled = resample( _src_sr, - Engine::mixer()->baseSampleRate() ); - MM_FREE( m_data ); - m_frames = resampled->frames(); - m_data = MM_ALLOC( sampleFrame, m_frames ); - memcpy( m_data, resampled->data(), m_frames * - sizeof( sampleFrame ) ); - delete resampled; + printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); } - if( _keep_settings == false ) - { - // update frame-variables - m_loopStartFrame = m_startFrame = 0; - m_loopEndFrame = m_endFrame = m_frames; - } + return outputData; } - - - -f_cnt_t SampleBuffer::decodeSampleSF( const char * _f, - sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _samplerate ) +SampleBuffer::DataVector SampleBuffer::decodeSampleSF( const char * _f, + ch_cnt_t & _channels, + sample_rate_t &_samplerate, + QString &loadingWarning) { SNDFILE * snd_file; SF_INFO sf_info; sf_info.format = 0; f_cnt_t frames = 0; + DataVector vector; bool sf_rr = false; if( ( snd_file = sf_open( _f, SFM_READ, &sf_info ) ) != NULL ) { frames = sf_info.frames; + vector.resize (frames); + sf_rr = sf_read_float( snd_file, vector.data ()->data (), min(DEFAULT_CHANNELS * frames, sf_info.channels * frames)); - _buf = new sample_t[sf_info.channels * frames]; - sf_rr = sf_read_float( snd_file, _buf, sf_info.channels * frames ); + if (sf_info.channels == 1) { +#ifdef DEBUG_LMMS + qDebug( "SampleBuffer::decodeSampleSF(): Not a stereo file: %s: %s", _f, sf_strerror( NULL ) ); +#endif + vector.resize(frames / 2); + } else if (sf_info.channels > DEFAULT_CHANNELS) { + loadingWarning = tr("The file you've selected has %1 channels. LMMS support " + "Stereo and Mono.").arg(sf_info.channels); + } if( sf_rr < sf_info.channels * frames ) { @@ -429,15 +359,10 @@ f_cnt_t SampleBuffer::decodeSampleSF( const char * _f, qDebug( "SampleBuffer::decodeSampleSF(): could not load " "sample %s: %s", _f, sf_strerror( NULL ) ); #endif - } - //write down either directly or convert i->f depending on file type - - if ( frames > 0 && _buf != NULL ) - { - directFloatWrite ( _buf, frames, _channels); + loadingWarning = tr("SoundFile: Could not load: %1").arg(sf_strerror( NULL )); } - return frames; + return vector; } @@ -495,10 +420,9 @@ long qfileTellCallback( void * _udata ) -f_cnt_t SampleBuffer::decodeSampleOGGVorbis( const char * _f, - int_sample_t * & _buf, +SampleBuffer::DataVector SampleBuffer::decodeSampleOGGVorbis(const char * _f, ch_cnt_t & _channels, - sample_rate_t & _samplerate ) + sample_rate_t & _samplerate) { static ov_callbacks callbacks = { @@ -516,7 +440,7 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( const char * _f, if( f->open( QFile::ReadOnly ) == false ) { delete f; - return 0; + return {}; } int err = ov_open_callbacks( f, &vf, NULL, 0, callbacks ); @@ -547,7 +471,7 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( const char * _f, break; } delete f; - return 0; + return {}; } ov_pcm_seek( &vf, 0 ); @@ -557,7 +481,7 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( const char * _f, ogg_int64_t total = ov_pcm_total( &vf, -1 ); - _buf = new int_sample_t[total * _channels]; + auto _buf = new int_sample_t[total * _channels]; int bitstream = 0; long bytes_read = 0; @@ -581,30 +505,29 @@ f_cnt_t SampleBuffer::decodeSampleOGGVorbis( const char * _f, if ( frames > 0 && _buf != NULL ) { - convertIntToFloat ( _buf, frames, _channels); + return convertIntToFloat ( _buf, frames, _channels); } - return frames; + return {}; } #endif -f_cnt_t SampleBuffer::decodeSampleDS( const char * _f, - int_sample_t * & _buf, - ch_cnt_t & _channels, - sample_rate_t & _samplerate ) +SampleBuffer::DataVector SampleBuffer::decodeSampleDS(const char * _f, ch_cnt_t & _channels, + sample_rate_t & _samplerate) { DrumSynth ds; + int_sample_t *_buf = NULL; f_cnt_t frames = ds.GetDSFileSamples( _f, _buf, _channels, _samplerate ); if ( frames > 0 && _buf != NULL ) { - convertIntToFloat ( _buf, frames, _channels); + return convertIntToFloat ( _buf, frames, _channels); } - return frames; + return {}; } @@ -630,7 +553,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, bool is_backwards = _state->isBackwards(); const double freq_factor = (double) _freq / (double) m_frequency * - m_sampleRate / Engine::mixer()->processingSampleRate(); + double(m_sampleRate) / double(Engine::mixer()->processingSampleRate()); // calculate how many frames we have in requested pitch const f_cnt_t total_frames_for_current_pitch = static_cast( ( @@ -673,12 +596,12 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, SRC_DATA src_data; // Generate output src_data.data_in = - getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, - loopStartFrame, loopEndFrame, endFrame )[0]; - src_data.data_out = _ab[0]; + libSampleRateSrc(getSampleFragment( play_frame, fragment_size, _loopmode, &tmp, &is_backwards, + loopStartFrame, loopEndFrame, endFrame ))->data (); + src_data.data_out = _ab->data (); src_data.input_frames = fragment_size; src_data.output_frames = _frames; - src_data.src_ratio = 1.0 / freq_factor; + src_data.src_ratio = double(Engine::mixer()->processingSampleRate())/ double(m_sampleRate); src_data.end_of_input = 0; int error = src_process( _state->m_resamplingData, &src_data ); @@ -780,7 +703,7 @@ bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, - +const sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, f_cnt_t _frames, LoopMode _loopmode, sampleFrame * * _tmp, bool * _backwards, f_cnt_t _loopstart, f_cnt_t _loopend, f_cnt_t _end ) const @@ -789,21 +712,21 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, { if( _index + _frames <= _end ) { - return m_data + _index; + return data () + _index; } } else if( _loopmode == LoopOn ) { if( _index + _frames <= _loopend ) { - return m_data + _index; + return data () + _index; } } else { if( ! *_backwards && _index + _frames < _loopend ) { - return m_data + _index; + return data () + _index; } } @@ -812,19 +735,19 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, if( _loopmode == LoopOff ) { f_cnt_t available = _end - _index; - memcpy( *_tmp, m_data + _index, available * BYTES_PER_FRAME ); + memcpy( *_tmp, data () + _index, available * BYTES_PER_FRAME ); memset( *_tmp + available, 0, ( _frames - available ) * BYTES_PER_FRAME ); } else if( _loopmode == LoopOn ) { f_cnt_t copied = qMin( _frames, _loopend - _index ); - memcpy( *_tmp, m_data + _index, copied * BYTES_PER_FRAME ); + memcpy( *_tmp, data () + _index, copied * BYTES_PER_FRAME ); f_cnt_t loop_frames = _loopend - _loopstart; while( copied < _frames ) { f_cnt_t todo = qMin( _frames - copied, loop_frames ); - memcpy( *_tmp + copied, m_data + _loopstart, todo * BYTES_PER_FRAME ); + memcpy( *_tmp + copied, data () + _loopstart, todo * BYTES_PER_FRAME ); copied += todo; } } @@ -851,7 +774,7 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, else { copied = qMin( _frames, _loopend - pos ); - memcpy( *_tmp, m_data + pos, copied * BYTES_PER_FRAME ); + memcpy( *_tmp, data () + pos, copied * BYTES_PER_FRAME ); pos += copied; if( pos == _loopend ) backwards = true; } @@ -873,7 +796,7 @@ sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _index, else { f_cnt_t todo = qMin( _frames - copied, _loopend - pos ); - memcpy( *_tmp + copied, m_data + pos, todo * BYTES_PER_FRAME ); + memcpy( *_tmp + copied, data () + pos, todo * BYTES_PER_FRAME ); pos += todo; copied += todo; if( pos >= _loopend ) backwards = true; @@ -917,38 +840,60 @@ f_cnt_t SampleBuffer::getPingPongIndex( f_cnt_t _index, f_cnt_t _startf, f_cnt_t void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame, f_cnt_t _to_frame ) { - if( m_frames == 0 ) return; + auto polyPair = visualizeToPoly (_dr, _clip, _from_frame, _to_frame); + + _p.setRenderHint( QPainter::Antialiasing ); + _p.drawPolyline (polyPair.first); + _p.drawPolyline (polyPair.second); +} + +QPair SampleBuffer::visualizeToPoly(const QRect &_dr, const QRect &_clip, + f_cnt_t _from_frame, f_cnt_t _to_frame) const +{ + if( internalFrames () == 0 ) return {}; + + const bool focus_on_range = _from_frame < _to_frame; + if (_to_frame > frames()) + _to_frame = frames(); - const bool focus_on_range = _to_frame <= m_frames - && 0 <= _from_frame && _from_frame < _to_frame; //_p.setClipRect( _clip ); const int w = _dr.width(); const int h = _dr.height(); - const int yb = h / 2 + _dr.y(); - const float y_space = h*0.5f; - const int nb_frames = focus_on_range ? _to_frame - _from_frame : m_frames; +// const int yb = h / 2 + _dr.y(); + int y_space = (h/2); + + const int nb_frames = focus_on_range ? _to_frame - _from_frame : internalFrames(); + if (nb_frames == 0) return {}; const int fpp = tLimit( nb_frames / w, 1, 20 ); - QPointF * l = new QPointF[nb_frames / fpp + 1]; - QPointF * r = new QPointF[nb_frames / fpp + 1]; + + bool shouldAddAdditionalPoint = (nb_frames % fpp) != 0; + int pointsCount = (nb_frames / fpp) + (shouldAddAdditionalPoint ? 1 : 0); + auto l = QPolygonF(pointsCount); + auto r = QPolygonF(pointsCount); + int n = 0; const int xb = _dr.x(); const int first = focus_on_range ? _from_frame : 0; - const int last = focus_on_range ? _to_frame : m_frames; + const int last = focus_on_range ? _to_frame : internalFrames(); + + int zeroPoint = _dr.y() + y_space; + if (h % 2 != 0) + zeroPoint += 1; for( int frame = first; frame < last; frame += fpp ) { - l[n] = QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), - ( yb - ( m_data[frame][0] * y_space * m_amplification ) ) ); - r[n] = QPointF( xb + ( (frame - first) * double( w ) / nb_frames ), - ( yb - ( m_data[frame][1] * y_space * m_amplification ) ) ); + double x = (xb + (frame - first) * double( w ) / nb_frames); + + l[n] = QPointF(x, + ( zeroPoint + ( m_data[frame][0] * y_space * m_amplification ) ) ); + r[n] = QPointF(x, + ( zeroPoint + ( m_data[frame][1] * y_space * m_amplification ) ) ); + ++n; } - _p.setRenderHint( QPainter::Antialiasing ); - _p.drawPolyline( l, nb_frames / fpp ); - _p.drawPolyline( r, nb_frames / fpp ); - delete[] l; - delete[] r; + + return {std::move(l), std::move(r)}; } @@ -1056,310 +1001,37 @@ QString SampleBuffer::openAndSetWaveformFile() -#undef LMMS_HAVE_FLAC_STREAM_ENCODER_H /* not yet... */ -#undef LMMS_HAVE_FLAC_STREAM_DECODER_H - -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H -FLAC__StreamEncoderWriteStatus flacStreamEncoderWriteCallback( - const FLAC__StreamEncoder * - /*_encoder*/, - const FLAC__byte _buffer[], - unsigned int/* _samples*/, - unsigned int _bytes, - unsigned int/* _current_frame*/, - void * _client_data ) -{ -/* if( _bytes == 0 ) - { - return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; - }*/ - return ( static_cast( _client_data )->write( - (const char *) _buffer, _bytes ) == - (int) _bytes ) ? - FLAC__STREAM_ENCODER_WRITE_STATUS_OK : - FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; -} - - -void flacStreamEncoderMetadataCallback( const FLAC__StreamEncoder *, - const FLAC__StreamMetadata * _metadata, - void * _client_data ) -{ - QBuffer * b = static_cast( _client_data ); - b->seek( 0 ); - b->write( (const char *) _metadata, sizeof( *_metadata ) ); -} - -#endif - - - QString & SampleBuffer::toBase64( QString & _dst ) const { -#ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H - const f_cnt_t FRAMES_PER_BUF = 1152; - - FLAC__StreamEncoder * flac_enc = FLAC__stream_encoder_new(); - FLAC__stream_encoder_set_channels( flac_enc, DEFAULT_CHANNELS ); - FLAC__stream_encoder_set_blocksize( flac_enc, FRAMES_PER_BUF ); -/* FLAC__stream_encoder_set_do_exhaustive_model_search( flac_enc, true ); - FLAC__stream_encoder_set_do_mid_side_stereo( flac_enc, true );*/ - FLAC__stream_encoder_set_sample_rate( flac_enc, - Engine::mixer()->sampleRate() ); - QBuffer ba_writer; - ba_writer.open( QBuffer::WriteOnly ); - - FLAC__stream_encoder_set_write_callback( flac_enc, - flacStreamEncoderWriteCallback ); - FLAC__stream_encoder_set_metadata_callback( flac_enc, - flacStreamEncoderMetadataCallback ); - FLAC__stream_encoder_set_client_data( flac_enc, &ba_writer ); - if( FLAC__stream_encoder_init( flac_enc ) != FLAC__STREAM_ENCODER_OK ) - { - printf( "error within FLAC__stream_encoder_init()!\n" ); - } - f_cnt_t frame_cnt = 0; - while( frame_cnt < m_frames ) - { - f_cnt_t remaining = qMin( FRAMES_PER_BUF, - m_frames - frame_cnt ); - FLAC__int32 buf[FRAMES_PER_BUF * DEFAULT_CHANNELS]; - for( f_cnt_t f = 0; f < remaining; ++f ) - { - for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) - { - buf[f*DEFAULT_CHANNELS+ch] = (FLAC__int32)( - Mixer::clip( m_data[f+frame_cnt][ch] ) * - OUTPUT_SAMPLE_MULTIPLIER ); - } - } - FLAC__stream_encoder_process_interleaved( flac_enc, buf, - remaining ); - frame_cnt += remaining; - } - FLAC__stream_encoder_finish( flac_enc ); - FLAC__stream_encoder_delete( flac_enc ); - printf("%d %d\n", frame_cnt, (int)ba_writer.size() ); - ba_writer.close(); - - base64::encode( ba_writer.buffer().data(), ba_writer.buffer().size(), - _dst ); - - -#else /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ - - base64::encode( (const char *) m_data, - m_frames * sizeof( sampleFrame ), _dst ); - -#endif /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ + base64::encode( (const char *) data (), + internalFrames() * sizeof( sampleFrame ), _dst ); return _dst; } - - -SampleBuffer * SampleBuffer::resample( const sample_rate_t _src_sr, - const sample_rate_t _dst_sr ) -{ - sampleFrame * data = m_data; - const f_cnt_t frames = m_frames; - const f_cnt_t dst_frames = static_cast( frames / - (float) _src_sr * (float) _dst_sr ); - SampleBuffer * dst_sb = new SampleBuffer( dst_frames ); - sampleFrame * dst_buf = dst_sb->m_origData; - - // yeah, libsamplerate, let's rock with sinc-interpolation! - int error; - SRC_STATE * state; - if( ( state = src_new( SRC_SINC_MEDIUM_QUALITY, - DEFAULT_CHANNELS, &error ) ) != NULL ) - { - SRC_DATA src_data; - src_data.end_of_input = 1; - src_data.data_in = data[0]; - src_data.data_out = dst_buf[0]; - src_data.input_frames = frames; - src_data.output_frames = dst_frames; - src_data.src_ratio = (double) _dst_sr / _src_sr; - if( ( error = src_process( state, &src_data ) ) ) - { - printf( "SampleBuffer: error while resampling: %s\n", - src_strerror( error ) ); - } - src_delete( state ); - } - else - { - printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); - } - dst_sb->update(); - return dst_sb; -} - - - - void SampleBuffer::setAudioFile( const QString & _audio_file ) { - m_audioFile = tryToMakeRelative( _audio_file ); - update(); + changeAudioFile (_audio_file); } -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - -struct flacStreamDecoderClientData -{ - QBuffer * read_buffer; - QBuffer * write_buffer; -} ; - - - -FLAC__StreamDecoderReadStatus flacStreamDecoderReadCallback( - const FLAC__StreamDecoder * - /*_decoder*/, - FLAC__byte * _buffer, - unsigned int * _bytes, - void * _client_data ) -{ - int res = static_cast( - _client_data )->read_buffer->read( - (char *) _buffer, *_bytes ); - - if( res > 0 ) - { - *_bytes = res; - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; - - } - *_bytes = 0; - return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; -} - - - - -FLAC__StreamDecoderWriteStatus flacStreamDecoderWriteCallback( - const FLAC__StreamDecoder * - /*_decoder*/, - const FLAC__Frame * _frame, - const FLAC__int32 * const _buffer[], - void * _client_data ) -{ - if( _frame->header.channels != 2 ) - { - printf( "channels != 2 in " - "flacStreamDecoderWriteCallback()\n" ); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - if( _frame->header.bits_per_sample != 16 ) - { - printf( "bits_per_sample != 16 in " - "flacStreamDecoderWriteCallback()\n" ); - return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - } - - const f_cnt_t frames = _frame->header.blocksize; - for( f_cnt_t frame = 0; frame < frames; ++frame ) - { - sampleFrame sframe = { _buffer[0][frame] / - OUTPUT_SAMPLE_MULTIPLIER, - _buffer[1][frame] / - OUTPUT_SAMPLE_MULTIPLIER - } ; - static_cast( - _client_data )->write_buffer->write( - (const char *) sframe, sizeof( sframe ) ); - } - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} - - -void flacStreamDecoderMetadataCallback( const FLAC__StreamDecoder *, - const FLAC__StreamMetadata *, - void * /*_client_data*/ ) -{ - printf("stream decoder metadata callback\n"); -/* QBuffer * b = static_cast( _client_data ); - b->seek( 0 ); - b->write( (const char *) _metadata, sizeof( *_metadata ) );*/ -} - - -void flacStreamDecoderErrorCallback( const FLAC__StreamDecoder *, - FLAC__StreamDecoderErrorStatus _status, - void * /*_client_data*/ ) -{ - printf("error callback! %d\n", _status); - // what to do now?? -} - -#endif - - -void SampleBuffer::loadFromBase64( const QString & _data ) +void SampleBuffer::loadFromBase64( const QString & _data , sample_rate_t sampleRate) { char * dst = NULL; int dsize = 0; base64::decode( _data, &dst, &dsize ); -#ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H - - QByteArray orig_data = QByteArray::fromRawData( dst, dsize ); - QBuffer ba_reader( &orig_data ); - ba_reader.open( QBuffer::ReadOnly ); - - QBuffer ba_writer; - ba_writer.open( QBuffer::WriteOnly ); - - flacStreamDecoderClientData cdata = { &ba_reader, &ba_writer } ; - - FLAC__StreamDecoder * flac_dec = FLAC__stream_decoder_new(); - - FLAC__stream_decoder_set_read_callback( flac_dec, - flacStreamDecoderReadCallback ); - FLAC__stream_decoder_set_write_callback( flac_dec, - flacStreamDecoderWriteCallback ); - FLAC__stream_decoder_set_error_callback( flac_dec, - flacStreamDecoderErrorCallback ); - FLAC__stream_decoder_set_metadata_callback( flac_dec, - flacStreamDecoderMetadataCallback ); - FLAC__stream_decoder_set_client_data( flac_dec, &cdata ); - - FLAC__stream_decoder_init( flac_dec ); - - FLAC__stream_decoder_process_until_end_of_stream( flac_dec ); - - FLAC__stream_decoder_finish( flac_dec ); - FLAC__stream_decoder_delete( flac_dec ); - - ba_reader.close(); - - orig_data = ba_writer.buffer(); - printf("%d\n", (int) orig_data.size() ); - - m_origFrames = orig_data.size() / sizeof( sampleFrame ); - MM_FREE( m_origData ); - m_origData = MM_ALLOC( sampleFrame, m_origFrames ); - memcpy( m_origData, orig_data.data(), orig_data.size() ); - -#else /* LMMS_HAVE_FLAC_STREAM_DECODER_H */ - - m_origFrames = dsize / sizeof( sampleFrame ); - MM_FREE( m_origData ); - m_origData = MM_ALLOC( sampleFrame, m_origFrames ); - memcpy( m_origData, dst, dsize ); - -#endif + DataVector input(dsize / sizeof(sampleFrame)); + memcpy (input.data (), + dst, + input.size () * sizeof (sampleFrame)); delete[] dst; - m_audioFile = QString(); - update(); + resetData (std::move(input), + sampleRate); } @@ -1384,21 +1056,10 @@ void SampleBuffer::setEndFrame( const f_cnt_t _e ) void SampleBuffer::setAmplification( float _a ) { m_amplification = _a; - emit sampleUpdated(); -} - - - -void SampleBuffer::setReversed( bool _on ) -{ - m_reversed = _on; - update( true ); + emit sampleUpdated(); } - - - QString SampleBuffer::tryToMakeRelative( const QString & file ) { if( QFileInfo( file ).isRelative() == false ) @@ -1454,13 +1115,6 @@ QString SampleBuffer::tryToMakeAbsolute(const QString& file) return file; } - - - - - - - SampleBuffer::handleState::handleState( bool _varying_pitch, int interpolation_mode ) : m_frameIndex( 0 ), m_varyingPitch( _varying_pitch ), @@ -1482,3 +1136,136 @@ SampleBuffer::handleState::~handleState() { src_delete( m_resamplingData ); } + +void SampleBuffer::beginBufferChange(bool shouldLock, bool shouldLockMixer) +{ + if (shouldLockMixer) { + Engine::mixer ()->requestChangeInModel (); + } + + if (shouldLock) { + m_varLock.lockForWrite (); + } +} + +bool SampleBuffer::tryBeginBufferChange(bool shouldLock, bool shouldLockMixer) { + bool result = true; + + if (shouldLockMixer) { + Engine::mixer ()->requestChangeInModel (); + } + + if (shouldLock) { + result = m_varLock.tryLockForWrite(); + + if (! result) + Engine::mixer ()->doneChangeInModel(); + } + + return result; +} + +void SampleBuffer::doneBufferChange(bool shouldUnlock, + sample_rate_t bufferSampleRate, + bool shouldUnlockMixer) { + + setSampleRate (bufferSampleRate); + + m_loopStartFrame = m_startFrame = 0; + m_loopEndFrame = m_endFrame = internalFrames(); + if (shouldUnlock) { + m_varLock.unlock (); + } + + if (shouldUnlockMixer) { + Engine::mixer ()->doneChangeInModel (); + } + + emit sampleUpdated(); +} + +bool SampleBuffer::tryAddData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer) { + DataVector newVector; + + if (sampleRate != m_sampleRate) { + // We should resample this data; + + newVector = resampleData (vector, sampleRate, m_sampleRate); + sampleRate = m_sampleRate; + } + + // First of all, don't let anyone read. + if (! tryBeginBufferChange (true, shouldLockMixer)) + return false; + { + if (newVector.empty()) + internalAddData(vector, + sampleRate); + else + internalAddData(newVector, + sampleRate); + } + doneBufferChange (true, /* lock */ + this->sampleRate(), + shouldLockMixer); + + return true; +} + +void SampleBuffer::addData(const SampleBuffer::DataVector &vector, sample_rate_t sampleRate, bool shouldLockMixer) { + DataVector newVector; + + if (sampleRate != m_sampleRate) { + // We should resample this data; + + newVector = resampleData (vector, sampleRate, m_sampleRate); + sampleRate = m_sampleRate; + } + + // First of all, don't let anyone read. + beginBufferChange (true, shouldLockMixer); + { + if (newVector.empty()) + internalAddData(vector, + sampleRate); + else + internalAddData(newVector, + sampleRate); + } + doneBufferChange (true, /* lock */ + this->sampleRate(), + shouldLockMixer); +} + +void SampleBuffer::resetData(DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer) { + beginBufferChange (true, shouldLockMixer); + { + internalResetData(std::move(newData), dataSampleRate); + } + doneBufferChange (true, /* lock */ + dataSampleRate, + shouldLockMixer); +} + +bool SampleBuffer::tryResetData(SampleBuffer::DataVector &&newData, sample_rate_t dataSampleRate, bool shouldLockMixer) { + if (! tryBeginBufferChange (true, shouldLockMixer)) + return false; + { + internalResetData(std::move(newData), dataSampleRate); + } + doneBufferChange (true, /* lock */ + dataSampleRate, + shouldLockMixer); + + return true; +} + +void SampleBuffer::reverse(bool shouldLockMixer) { + beginBufferChange (true, shouldLockMixer); + { + std::reverse(m_data.begin (), m_data.end ()); + } + doneBufferChange (true, /* should(Un)Lock? yes! */ + sampleRate (), /* we have not made any change in the sample rate. */ + shouldLockMixer); +} diff --git a/src/core/SampleBufferVisualizer.cpp b/src/core/SampleBufferVisualizer.cpp new file mode 100644 index 00000000000..791284df44d --- /dev/null +++ b/src/core/SampleBufferVisualizer.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018 Shmuel H. (shmuelhazan0/at/gmail.com) + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ +#include "include/SampleBufferVisualizer.h" + +#include "Track.h" +#include "Engine.h" + +#include +#include + +SampleBufferVisualizer::SampleBufferVisualizer() +{ + +} + +void SampleBufferVisualizer::update(const SampleBuffer &sampleBuffer, + MidiTime sampleStartOffset, + MidiTime sampleLength, + const QRect &parentRect, + float pixelsPerTact, + f_cnt_t framesPerTact, + const QPen &pen, + SampleBufferVisualizer::Operation operation) +{ + // != + if (pixelsPerTact < m_pixelsPerTact || pixelsPerTact > m_pixelsPerTact + || framesPerTact != m_framesPerTact + || sampleLength < m_cachedTime) + operation = Operation::Clear; + + m_pixelsPerTact = pixelsPerTact; + m_generalPaintOffset = sampleStartOffset; + m_framesPerTact = framesPerTact; + m_drawPixelOffset = parentRect.left(); + + switch (operation) { + case Operation::Clear: + m_cachedPixmaps.clear(); + m_currentPixmap.pixmap = QPixmap(); + m_currentPixmap.totalTime = 0; + m_cachedTime = 0; + +#ifdef Q_FALLTHROUGH + Q_FALLTHROUGH(); +#endif + case Operation::Append: + appendMultipleTacts(sampleBuffer, + sampleLength, + parentRect.translated(-parentRect.x(), + 0), + pen); + break; + } +} + +void SampleBufferVisualizer::draw(QPainter &painter) +{ + float pixelOffset = pixelsPerTime(m_generalPaintOffset) + m_drawPixelOffset; + for (const auto &pixmap : m_cachedPixmaps) { + auto targetRect = pixmap.rect.translated(int(pixelOffset), + 0); + + painter.drawPixmap(targetRect, pixmap.pixmap); + pixelOffset += pixelsPerTime(pixmap.totalTime); + } + + auto targetRect = m_currentPixmap.rect.translated(int(pixelOffset), + 0); + painter.drawPixmap(targetRect, m_currentPixmap.pixmap); +} + +void SampleBufferVisualizer::appendMultipleTacts(const SampleBuffer &sampleBuffer, + MidiTime sampleLength, + const QRect &parentRect, + const QPen &pen) +{ + for (; (m_cachedTime+m_currentPixmap.totalTime) < sampleLength;) + { + MidiTime totalTime; + MidiTime offsetFromTact = m_currentPixmap.totalTime; + bool isCompleteTact = false; + + // we have more tacts to draw. finish the current one. + if (MidiTime(m_cachedTime+offsetFromTact).getTact() < sampleLength.getTact()) { + // Paint what left from the current tact. + totalTime = MidiTime::ticksPerTact() - offsetFromTact; + isCompleteTact = true; + } else { + // Draw only the ticks left in the current tact. + totalTime = sampleLength - m_cachedTime - m_currentPixmap.totalTime; + } + + Q_ASSERT((offsetFromTact + totalTime) <= MidiTime::ticksPerTact()); + + if (pixelsPerTime(totalTime) < 1) { + // We can't paint it. + // totalTime is too short. skip it. + // or just wait until we have enough frames. + if (isCompleteTact) { + // Skip it and continue to the next tact. + m_currentPixmap.totalTime += totalTime; + } else { + // Wait until we have enough frames. + break; + } + } + + auto result = appendTact(sampleBuffer, + totalTime, + parentRect, + pen, + isCompleteTact); + if (! result) + break; + + if (isCompleteTact) { + m_cachedTime += ( m_currentPixmap.totalTime ); + m_cachedPixmaps.push_back(std::move(m_currentPixmap)); + m_currentPixmap.pixmap = QPixmap(); + m_currentPixmap.totalTime = 0; + } + } +} + +bool SampleBufferVisualizer::appendTact(const SampleBuffer &sampleBuffer, + const MidiTime &totalTime, + const QRect &parentRect, + const QPen &pen, + bool isLastInTact) +{ + auto offsetFromTact = m_currentPixmap.totalTime; + + auto currentPaintInTact = getRectForSampleFragment (parentRect, + offsetFromTact, + totalTime, + isLastInTact); + Q_ASSERT(currentPaintInTact.width() > 0); + + // Generate the actual visualization. + auto fromFrame = MidiTime(m_cachedTime + offsetFromTact).frames (m_framesPerTact); + + if (! sampleBuffer.tryDataReadLock()) + return false; + auto poly = sampleBuffer.visualizeToPoly (currentPaintInTact, + QRect(), + fromFrame, + fromFrame + totalTime.frames(m_framesPerTact)); + sampleBuffer.dataUnlock (); + + m_currentPixmap.totalTime += totalTime; + + m_currentPixmap.rect = getRectForSampleFragment (parentRect, + 0, + MidiTime::ticksPerTact()); + if (m_currentPixmap.pixmap.isNull()) { + m_currentPixmap.pixmap = QPixmap(m_currentPixmap.rect.size()); + m_currentPixmap.pixmap.fill(Qt::transparent); + } + + // Draw the points into the pixmap. + QPainter pixmapPainter (&m_currentPixmap.pixmap); + pixmapPainter.setPen(pen); + + pixmapPainter.setRenderHint( QPainter::Antialiasing ); + + pixmapPainter.drawPolyline (poly.first); + pixmapPainter.drawPolyline (poly.second); + pixmapPainter.end(); + + // Continue to the next tact or stop. + return true; +} + +QRect SampleBufferVisualizer::getRectForSampleFragment(QRect parentRect, MidiTime beginOffset, + MidiTime totalTime, + bool forceNotZeroWidth) { + int offset = pixelsPerTime(beginOffset); + + float top = parentRect.top (); + float height = parentRect.height (); + + QRect r = QRect( int(parentRect.x ()) + int(offset), + top, + int(qMax( int(pixelsPerTime(totalTime)) , (forceNotZeroWidth ? 1 : 0) )), + int(height)); + + + return r; +} diff --git a/src/core/SamplePlayHandle.cpp b/src/core/SamplePlayHandle.cpp index 549a6c3a549..ec32461af21 100644 --- a/src/core/SamplePlayHandle.cpp +++ b/src/core/SamplePlayHandle.cpp @@ -32,35 +32,34 @@ -SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer ) : +SamplePlayHandle::SamplePlayHandle(const std::shared_ptr &sampleBuffer , bool shouldCreateAudioPort) : PlayHandle( TypeSamplePlayHandle ), - m_sampleBuffer( sharedObject::ref( sampleBuffer ) ), + m_sampleBuffer( sampleBuffer ), m_doneMayReturnTrue( true ), m_frame( 0 ), - m_ownAudioPort( true ), + m_ownAudioPort( shouldCreateAudioPort ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( NULL ), m_bbTrack( NULL ) { - setAudioPort( new AudioPort( "SamplePlayHandle", false ) ); + if (shouldCreateAudioPort) + setAudioPort( new AudioPort( "SamplePlayHandle", false ) ); } SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : - SamplePlayHandle( new SampleBuffer( sampleFile ) ) + SamplePlayHandle( std::make_shared( sampleFile, false ) , true) { - sharedObject::unref( m_sampleBuffer ); - setAudioPort( new AudioPort( "SamplePlayHandle", false ) ); } SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) : - SamplePlayHandle( tco->sampleBuffer() ) + SamplePlayHandle( tco->sampleBuffer() , false) { m_track = tco->getTrack(); setAudioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() ); @@ -71,8 +70,7 @@ SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) : SamplePlayHandle::~SamplePlayHandle() { - sharedObject::unref( m_sampleBuffer ); - if( m_ownAudioPort ) + if( m_ownAudioPort && audioPort ()) { delete audioPort(); } @@ -139,7 +137,10 @@ bool SamplePlayHandle::isFromTrack( const Track * _track ) const f_cnt_t SamplePlayHandle::totalFrames() const { - return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( Engine::mixer()->processingSampleRate() / Engine::mixer()->baseSampleRate() ); + f_cnt_t total_frames = ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ); + qreal processingToSampleRateRatio = static_cast(Engine::mixer()->processingSampleRate()) / static_cast(m_sampleBuffer->sampleRate ()); + + return static_cast(total_frames * processingToSampleRateRatio); } diff --git a/src/core/SampleRecordHandle.cpp b/src/core/SampleRecordHandle.cpp index cf9342b9e08..232ef87f43d 100644 --- a/src/core/SampleRecordHandle.cpp +++ b/src/core/SampleRecordHandle.cpp @@ -29,18 +29,19 @@ #include "InstrumentTrack.h" #include "Mixer.h" #include "SampleBuffer.h" -#include "SampleTrack.h" #include "debug.h" -SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) : +SampleRecordHandle::SampleRecordHandle(SampleTCO* tco , MidiTime startRecordTimeOffset) : PlayHandle( TypeSamplePlayHandle ), m_framesRecorded( 0 ), - m_minLength( tco->length() ), m_track( tco->getTrack() ), m_bbTrack( NULL ), - m_tco( tco ) + m_tco( tco ), + m_recordingChannel{dynamic_cast(m_track)->recordingChannel ()}, + m_startRecordTimeOffset{startRecordTimeOffset} { + m_tco->setIsRecording(true); } @@ -48,19 +49,32 @@ SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) : SampleRecordHandle::~SampleRecordHandle() { - if( !m_buffers.empty() ) - { - SampleBuffer* sb; - createSampleBuffer( &sb ); - m_tco->setSampleBuffer( sb ); + if (! m_currentBuffer.empty()) { + // We have data that has not been written into the buffer. + // force-write it into the buffer. + + if (m_framesRecorded == 0) { + m_tco->sampleBuffer ()->resetData (std::move (m_currentBuffer), + Engine::mixer ()->inputSampleRate (), + false); + m_tco->setStartTimeOffset (m_startRecordTimeOffset); + } else { + m_tco->sampleBuffer ()->addData(m_currentBuffer, + Engine::mixer ()->inputSampleRate (), + false); + } } - - while( !m_buffers.empty() ) - { - delete[] m_buffers.front().first; - m_buffers.erase( m_buffers.begin() ); + + m_tco->updateLength (); + + // If this is an automatically created tco, + // enable resizing. + if (m_framesRecorded != 0) { + m_tco->setAutoResize (false); + m_tco->setRecord( false ); } - m_tco->setRecord( false ); + + m_tco->setIsRecording(false); } @@ -70,14 +84,31 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) { const sampleFrame * recbuf = Engine::mixer()->inputBuffer(); const f_cnt_t frames = Engine::mixer()->inputBufferFrames(); + writeBuffer( recbuf, frames ); - m_framesRecorded += frames; - MidiTime len = (tick_t)( m_framesRecorded / Engine::framesPerTick() ); - if( len > m_minLength ) - { -// m_tco->changeLength( len ); - m_minLength = len; + bool dataWrittenIntoSampleBuffer = true; + + // Try to add data to the buffer. + // If we could not do that. We'll do that next time. + if (m_framesRecorded == 0) { + // Make sure we don't have the previous data. + dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryResetData (std::move (m_currentBuffer), + Engine::mixer ()->inputSampleRate (), + false); + m_tco->setStartTimeOffset (m_startRecordTimeOffset); + } else { + if (! m_currentBuffer.empty ()) { + dataWrittenIntoSampleBuffer = m_tco->sampleBuffer ()->tryAddData(m_currentBuffer, + Engine::mixer ()->inputSampleRate (), + false); + } + } + + if (dataWrittenIntoSampleBuffer) { + m_framesRecorded += frames; + m_timeRecorded = m_framesRecorded / Engine::framesPerTick (Engine::mixer ()->inputSampleRate ()); + m_currentBuffer.clear(); } } @@ -86,7 +117,7 @@ void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) bool SampleRecordHandle::isFinished() const { - return false; + return !m_tco->getAutoResize () && (m_startRecordTimeOffset + m_timeRecorded) >= m_tco->length (); } @@ -105,32 +136,39 @@ f_cnt_t SampleRecordHandle::framesRecorded() const return( m_framesRecorded ); } - - - -void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf ) +void SampleRecordHandle::copyBufferFromMonoLeft(const sampleFrame *inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames) { - const f_cnt_t frames = framesRecorded(); - // create buffer to store all recorded buffers in - sampleFrame * data = new sampleFrame[frames]; - // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; - + for( f_cnt_t frame = 0; frame < _frames; ++frame ) { + // Copy every first sample to the first and the second in the output buffer. + outputBuffer[frame][LEFT_CHANNEL_INDEX] = inputBuffer[frame][LEFT_CHANNEL_INDEX]; + outputBuffer[frame][RIGHT_CHANNEL_INDEX] = inputBuffer[frame][LEFT_CHANNEL_INDEX]; + } +} - assert( data != NULL ); +void SampleRecordHandle::copyBufferFromMonoRight(const sampleFrame *inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames) +{ + for( f_cnt_t frame = 0; frame < _frames; ++frame ) { + // Copy every second sample to the first and the second in the output buffer. + outputBuffer[frame][LEFT_CHANNEL_INDEX] = inputBuffer[frame][RIGHT_CHANNEL_INDEX]; + outputBuffer[frame][RIGHT_CHANNEL_INDEX] = inputBuffer[frame][RIGHT_CHANNEL_INDEX]; + } +} - // now copy all buffers into big buffer - for( bufferList::const_iterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) +void SampleRecordHandle::copyBufferFromStereo(const sampleFrame *inputBuffer, + sampleFrame *outputBuffer, + const f_cnt_t _frames) +{ + for( f_cnt_t frame = 0; frame < _frames; ++frame ) { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); - data_ptr += ( *it ).second; + for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) + { + outputBuffer[frame][chnl] = inputBuffer[frame][chnl]; + } } - // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer( data, frames ); - ( *sampleBuf)->setSampleRate( Engine::mixer()->inputSampleRate() ); - delete[] data; } @@ -139,15 +177,32 @@ void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf ) void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ) { - sampleFrame * buf = new sampleFrame[_frames]; - for( f_cnt_t frame = 0; frame < _frames; ++frame ) - { - for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) - { - buf[frame][chnl] = _ab[frame][chnl]; - } + auto framesInBuffer = m_currentBuffer.size(); + // Add _frames elements to the buffer. + m_currentBuffer.resize (framesInBuffer + _frames); + + // Depending on the recording channel, copy the buffer as a + // mono-right, mono-left or stereo. + + // Note that mono doesn't mean single channel, it means + // empty other channel. Therefore, we would just duplicate + // every frame from the mono channel + // to the empty channel. + + switch(m_recordingChannel) { + case SampleTrack::RecordingChannel::MonoLeft: + copyBufferFromMonoLeft(_ab, m_currentBuffer.data () + framesInBuffer, _frames); + break; + case SampleTrack::RecordingChannel::MonoRight: + copyBufferFromMonoRight(_ab, m_currentBuffer.data () + framesInBuffer, _frames); + break; + case SampleTrack::RecordingChannel::Stereo: + copyBufferFromStereo(_ab, m_currentBuffer.data () + framesInBuffer, _frames); + break; + default: + Q_ASSERT(false); + break; } - m_buffers.push_back( qMakePair( buf, _frames ) ); } diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 775c0c58890..9fdb61a3f6d 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -106,6 +106,7 @@ Song::Song() : this, SLOT( masterPitchChanged() ) );*/ qRegisterMetaType( "Note" ); + qRegisterMetaType("MidiTime"); setType( SongContainer ); } @@ -252,13 +253,16 @@ void Song::processNextBuffer() if( m_playPos[m_playMode] < tl->loopBegin() || m_playPos[m_playMode] >= tl->loopEnd() ) { - setToTime(tl->loopBegin()); m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); + setToTime(tl->loopBegin()); + emit updateSampleTracks(); } } + + f_cnt_t framesPlayed = 0; const float framesPerTick = Engine::framesPerTick(); @@ -328,10 +332,16 @@ void Song::processNextBuffer() { m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); setToTime(tl->loopBegin()); + + if (isRecording()) + emit beforeRecordOn(tl->loopBegin()); } else if( m_playPos[m_playMode] == tl->loopEnd() - 1 ) { emit updateSampleTracks(); + + if (isRecording()) + emit beforeRecordOn(tl->loopBegin()); } } else @@ -519,6 +529,17 @@ void Song::playAndRecord() { playSong(); m_recording = true; + + MidiTime time = getPlayPos(); + auto tl = getPlayPos().m_timeLine; + bool checkLoop = + tl != NULL && tl->loopPointsEnabled(); + + if (checkLoop && (time >= tl->loopEnd() || time < tl->loopBegin())) { + time = tl->loopBegin(); + } + + emit beforeRecordOn(time); } diff --git a/src/core/Track.cpp b/src/core/Track.cpp index 15de5a983e6..3bc967be3ad 100644 --- a/src/core/Track.cpp +++ b/src/core/Track.cpp @@ -242,6 +242,15 @@ void TrackContentObject::setStartTimeOffset( const MidiTime &startTimeOffset ) m_startTimeOffset = startTimeOffset; } +bool TrackContentObject::isRecording() const +{ + return m_isRecording; +} + +void TrackContentObject::setIsRecording(bool value) +{ + m_isRecording = value; +} @@ -369,6 +378,9 @@ QColor TrackContentObjectView::mutedColor() const QColor TrackContentObjectView::mutedBackgroundColor() const { return m_mutedBackgroundColor; } +QColor TrackContentObjectView::recordingBackgroundColor() const +{ return m_recordingBackgroundColor; } + QColor TrackContentObjectView::selectedColor() const { return m_selectedColor; } @@ -413,6 +425,9 @@ void TrackContentObjectView::setTextShadowColor( const QColor & c ) void TrackContentObjectView::setBBPatternBackground( const QColor & c ) { m_BBPatternBackground = QColor( c ); } +void TrackContentObjectView::setRecordingBackgroundColor (const QColor & c ) +{ m_recordingBackgroundColor = QColor( c ); } + void TrackContentObjectView::setGradient( const bool & b ) { m_gradient = b; } @@ -513,7 +528,6 @@ void TrackContentObjectView::updatePosition() } - /*! \brief Change the trackContentObjectView's display when something * being dragged enters it. * @@ -708,6 +722,10 @@ void TrackContentObjectView::paintTextLabel(QString const & text, QPainter & pai */ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) { + // Disallow changes to a track that is being recorded into. + if (m_tco->isRecording()) + return; + setInitialMousePos( me->pos() ); if( !fixedTCOs() && me->button() == Qt::LeftButton ) { @@ -722,7 +740,7 @@ void TrackContentObjectView::mousePressEvent( QMouseEvent * me ) m_action = ToggleSelected; } } - else if( !me->modifiers() ) + else if( !me->modifiers()) { if( isSelected() ) { @@ -1901,6 +1919,11 @@ void TrackOperationsWidget::clearTrack() t->unlock(); } +QPushButton *TrackOperationsWidget::trackOps() const +{ + return m_trackOps; +} + /*! \brief Remove this track from the track list @@ -1924,63 +1947,9 @@ void TrackOperationsWidget::removeTrack() */ void TrackOperationsWidget::updateMenu() { - QMenu * toMenu = m_trackOps->menu(); - toMenu->clear(); - toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), - tr( "Clone this track" ), - this, SLOT( cloneTrack() ) ); - toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), - tr( "Remove this track" ), - this, SLOT( removeTrack() ) ); - - if( ! m_trackView->trackContainerView()->fixedTCOs() ) - { - toMenu->addAction( tr( "Clear this track" ), this, SLOT( clearTrack() ) ); - } - if( InstrumentTrackView * trackView = dynamic_cast( m_trackView ) ) - { - QMenu *fxMenu = trackView->createFxMenu( tr( "FX %1: %2" ), tr( "Assign to new FX Channel" )); - toMenu->addMenu(fxMenu); - - toMenu->addSeparator(); - toMenu->addMenu( trackView->midiMenu() ); - } - if( dynamic_cast( m_trackView ) ) - { - toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) ); - toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) ); - } -} - - -void TrackOperationsWidget::toggleRecording( bool on ) -{ - AutomationTrackView * atv = dynamic_cast( m_trackView ); - if( atv ) - { - for( TrackContentObject * tco : atv->getTrack()->getTCOs() ) - { - AutomationPattern * ap = dynamic_cast( tco ); - if( ap ) { ap->setRecording( on ); } - } - atv->update(); - } -} - - - -void TrackOperationsWidget::recordingOn() -{ - toggleRecording( true ); -} - - -void TrackOperationsWidget::recordingOff() -{ - toggleRecording( false ); + return m_trackView->updateTrackOperationsWidgetMenu (this); } - // =========================================================================== // track // =========================================================================== @@ -2685,6 +2654,24 @@ void TrackView::update() QWidget::update(); } +void TrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) +{ + QMenu * toMenu = trackOperations->m_trackOps->menu(); + toMenu->clear(); + toMenu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), + tr( "Clone this track" ), + trackOperations, SLOT( cloneTrack() ) ); + toMenu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), + tr( "Remove this track" ), + trackOperations, SLOT( removeTrack() ) ); + + if( ! trackContainerView()->fixedTCOs() ) + { + toMenu->addAction( tr( "Clear this track" ), trackOperations, SLOT( clearTrack() ) ); + } + +} + diff --git a/src/core/audio/AudioDevice.cpp b/src/core/audio/AudioDevice.cpp index ab69e144b12..efb933a3249 100644 --- a/src/core/audio/AudioDevice.cpp +++ b/src/core/audio/AudioDevice.cpp @@ -196,8 +196,8 @@ void AudioDevice::resample( const surroundSampleFrame * _src, } m_srcData.input_frames = _frames; m_srcData.output_frames = _frames; - m_srcData.data_in = (float *) _src[0]; - m_srcData.data_out = _dst[0]; + m_srcData.data_in = (float *) _src[0].data (); + m_srcData.data_out = _dst[0].data (); m_srcData.src_ratio = (double) _dst_sr / _src_sr; m_srcData.end_of_input = 0; int error; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 4d730eed412..8aab8bf1e61 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -47,15 +47,18 @@ AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( ConfigManager::inst()->value( "audiojack", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), - _mixer ), + _mixer), m_client( NULL ), m_active( false ), m_midiClient( NULL ), m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ), + m_tempInBufs( new jack_default_audio_sample_t *[channels()] ), m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) { + m_supportsCapture = true; + _success_ful = initJackClient(); if( _success_ful ) { @@ -88,6 +91,7 @@ AudioJack::~AudioJack() jack_client_close( m_client ); } + delete[] m_tempInBufs; delete[] m_tempOutBufs; delete[] m_outBuf; @@ -188,7 +192,24 @@ bool AudioJack::initJackClient() JackPortIsOutput, 0 ) ); if( m_outputPorts.back() == NULL ) { - printf( "no more JACK-ports available!\n" ); + printf( "no more out JACK-ports available!\n" ); + return false; + } + } + + // Register In ports + for( ch_cnt_t ch = 0; ch < channels(); ++ch ) + { + QString name = QString( "master in " ) + + ( ( ch % 2 ) ? "R" : "L" ) + + QString::number( ch / 2 + 1 ); + m_inputPorts.push_back( jack_port_register( m_client, + name.toLatin1().constData(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0 ) ); + if( m_inputPorts.back() == NULL ) + { + printf( "no more in JACK-ports available!\n" ); return false; } } @@ -223,30 +244,30 @@ void AudioJack::startProcessing() - const char * * ports = jack_get_ports( m_client, NULL, NULL, + const char * * inputPorts = jack_get_ports( m_client, NULL, NULL, JackPortIsPhysical | JackPortIsInput ); - if( ports == NULL ) - { - printf( "no physical playback ports. you'll have to do " - "connections at your own!\n" ); + // Connect lmms to the default playback device. + if (inputPorts) { + for (uint i = 0; i < channels (); ++i) { + connectJackPort (jack_port_name (m_outputPorts[i]), inputPorts[i], "playback"); + } + + free( inputPorts ); } - else - { - for( ch_cnt_t ch = 0; ch < channels(); ++ch ) - { - if( jack_connect( m_client, jack_port_name( - m_outputPorts[ch] ), - ports[ch] ) ) - { - printf( "cannot connect output ports. you'll " - "have to do connections at your own!\n" - ); - } + + const char * * outputPorts = jack_get_ports( m_client, NULL, NULL, + JackPortIsPhysical | + JackPortIsOutput ); + // Connect lmms to the default capture device. + if (outputPorts) { + for (uint i = 0; i < channels (); ++i) { + connectJackPort (outputPorts[i], jack_port_name (m_inputPorts[i]), "capture"); } + + free( outputPorts ); } - free( ports ); } @@ -357,6 +378,10 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) m_tempOutBufs[c] = (jack_default_audio_sample_t *) jack_port_get_buffer( m_outputPorts[c], _nframes ); + m_tempInBufs[c] = + (jack_default_audio_sample_t *) jack_port_get_buffer( + m_inputPorts[c], _nframes ); + } #ifdef AUDIO_PORT_SUPPORT @@ -411,6 +436,21 @@ int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) } } + if (! m_stopped) { + m_inBuffer.resize (_nframes); + + for( int c = 0; c < channels(); ++c ) { + jack_default_audio_sample_t *channel_buffer = m_tempInBufs[c]; + + for( jack_nframes_t frame = 0; frame < _nframes; ++frame ) + { + m_inBuffer[frame][c] = channel_buffer[frame]; + } + } + + mixer()->pushInputFrames (m_inBuffer.data (), _nframes); + } + if( _nframes != done ) { for( int c = 0; c < channels(); ++c ) @@ -442,6 +482,27 @@ void AudioJack::shutdownCallback( void * _udata ) _this->zombified(); } +void AudioJack::connectJackPort(const char *outputPort, + const char *inputPort, + const char *portName) { + if(outputPort == NULL || inputPort == NULL) + { + printf( "no physical %s port. you'll have to do " + "connections at your own!\n", portName); + } + else + { + if( jack_connect( m_client, outputPort, + inputPort) ) + { + printf( "cannot connect %s port. you'll " + "have to do connections at your own!\n", + portName); + } + } + +} + diff --git a/src/core/audio/AudioPortAudio.cpp b/src/core/audio/AudioPortAudio.cpp index b14c1e79dea..f000e92733c 100644 --- a/src/core/audio/AudioPortAudio.cpp +++ b/src/core/audio/AudioPortAudio.cpp @@ -61,6 +61,7 @@ AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) : m_stopSemaphore( 1 ) { _success_ful = false; + m_supportsCapture = true; m_outBufSize = mixer()->framesPerPeriod(); @@ -169,9 +170,6 @@ AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) : m_stopSemaphore.acquire(); - // TODO: debug Mixer::pushInputFrames() - //m_supportsCapture = true; - _success_ful = true; } @@ -261,7 +259,7 @@ int AudioPortAudio::process_callback( float * _outputBuffer, unsigned long _framesPerBuffer ) { - if( supportsCapture() ) + if( supportsCapture() && _inputBuffer) { mixer()->pushInputFrames( (sampleFrame*)_inputBuffer, _framesPerBuffer ); diff --git a/src/core/audio/AudioPulseAudio.cpp b/src/core/audio/AudioPulseAudio.cpp index 23031b10c40..8b77e0f05e3 100644 --- a/src/core/audio/AudioPulseAudio.cpp +++ b/src/core/audio/AudioPulseAudio.cpp @@ -42,7 +42,10 @@ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) } - +static void stream_read_callback(pa_stream *s, size_t length, void *userdata) +{ + static_cast( userdata )->streamReadCallback( s, length ); +} AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( @@ -50,15 +53,22 @@ AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer* _mixer ) : DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_s( NULL ), - m_quit( false ), - m_convertEndian( false ) + m_recordStream{nullptr}, + m_quit( false ) { _success_ful = false; - m_sampleSpec.format = PA_SAMPLE_S16LE; + setSampleRate (Engine::mixer ()->baseSampleRate ()); + m_sampleSpec.format = PA_SAMPLE_FLOAT32; m_sampleSpec.rate = sampleRate(); m_sampleSpec.channels = channels(); + m_recordSampleSpec = m_sampleSpec; + + // It does work. But it has clicking noises every few seconds. + // have no idea way. + m_supportsCapture = false; + _success_ful = true; } @@ -144,6 +154,27 @@ static void stream_state_callback( pa_stream *s, void * userdata ) } } +/* This routine is called whenever the stream state changes */ +static void record_stream_state_callback( pa_stream *s, void * userdata ) +{ + switch( pa_stream_get_state( s ) ) + { + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: + qDebug( "Record stream successfully created\n" ); + break; + + case PA_STREAM_FAILED: + default: + qCritical( "record stream errror: %s\n", + pa_strerror(pa_context_errno( + pa_stream_get_context( s ) ) ) ); + } +} + /* This is called whenever the context status changes */ @@ -164,6 +195,10 @@ static void context_state_callback(pa_context *c, void *userdata) pa_stream_set_state_callback( _this->m_s, stream_state_callback, _this ); pa_stream_set_write_callback( _this->m_s, stream_write_callback, _this ); + _this->m_recordStream = pa_stream_new( c, "lmms sample track record", &_this->m_sampleSpec, NULL); + pa_stream_set_state_callback( _this->m_recordStream, record_stream_state_callback, _this ); + pa_stream_set_read_callback ( _this->m_recordStream, stream_read_callback , _this ); + pa_buffer_attr buffer_attr; buffer_attr.maxlength = (uint32_t)(-1); @@ -181,10 +216,17 @@ static void context_state_callback(pa_context *c, void *userdata) buffer_attr.tlength = pa_usec_to_bytes( latency * PA_USEC_PER_MSEC, &_this->m_sampleSpec ); - pa_stream_connect_playback( _this->m_s, NULL, &buffer_attr, + pa_stream_connect_playback( _this->m_s, + NULL, + &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, // volume NULL ); + + pa_stream_connect_record (_this->m_recordStream, + NULL, + &buffer_attr, + PA_STREAM_ADJUST_LATENCY); _this->signalConnected( true ); break; } @@ -241,6 +283,8 @@ void AudioPulseAudio::run() pa_stream_disconnect( m_s ); pa_stream_unref( m_s ); + pa_stream_disconnect( m_recordStream ); + pa_stream_unref( m_recordStream ); } else { @@ -265,10 +309,9 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) { const fpp_t fpp = mixer()->framesPerPeriod(); surroundSampleFrame * temp = new surroundSampleFrame[fpp]; - int_sample_t* pcmbuf = (int_sample_t *)pa_xmalloc( fpp * channels() * sizeof(int_sample_t) ); size_t fd = 0; - while( fd < length/4 && m_quit == false ) + while( fd < length && m_quit == false ) { const fpp_t frames = getNextBuffer( temp ); if( !frames ) @@ -276,24 +319,48 @@ void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) m_quit = true; break; } - int bytes = convertToS16( temp, frames, - mixer()->masterGain(), - pcmbuf, - m_convertEndian ); - if( bytes > 0 ) - { - pa_stream_write( m_s, pcmbuf, bytes, NULL, 0, - PA_SEEK_RELATIVE ); + + auto gain = mixer()->masterGain(); + for (fpp_t f = 0; f < frames; ++f) { + temp[f][0] *= gain; + temp[f][1] *= gain; } - fd += frames; + + + + pa_stream_write( m_s, static_cast(temp), + frames * sizeof(surroundSampleFrame), NULL, 0, + PA_SEEK_RELATIVE ); + + fd += (frames * sizeof(surroundSampleFrame)); } - pa_xfree( pcmbuf ); delete[] temp; } +void AudioPulseAudio::streamReadCallback(pa_stream *s, size_t length) { + const sampleFrame *buffer = nullptr; + size_t buffer_size = 0; + + while (length > 0) { + + pa_stream_peek (m_recordStream, + reinterpret_cast (const_cast(&buffer)), + &buffer_size); + + fpp_t frames = buffer_size / sizeof (sampleFrame); + + pa_stream_drop (m_recordStream); + if (buffer_size && buffer) { + mixer()->pushInputFrames (buffer, + frames); + } + + length -= buffer_size; + } +} void AudioPulseAudio::signalConnected( bool connected ) { diff --git a/src/core/audio/AudioSampleRecorder.cpp b/src/core/audio/AudioSampleRecorder.cpp deleted file mode 100644 index a36e0e9da06..00000000000 --- a/src/core/audio/AudioSampleRecorder.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * AudioSampleRecorder.cpp - device-class that implements recording - * audio-buffers into RAM - * - * Copyright (c) 2004-2014 Tobias Doerffel - * - * This file is part of LMMS - https://lmms.io - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program (see COPYING); if not, write to the - * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301 USA. - * - */ - - -#include "AudioSampleRecorder.h" -#include "SampleBuffer.h" -#include "debug.h" - - - -AudioSampleRecorder::AudioSampleRecorder( const ch_cnt_t _channels, - bool & _success_ful, - Mixer * _mixer ) : - AudioDevice( _channels, _mixer ), - m_buffers() -{ - _success_ful = true; -} - - - - -AudioSampleRecorder::~AudioSampleRecorder() -{ - while( !m_buffers.empty() ) - { - delete[] m_buffers.front().first; - m_buffers.erase( m_buffers.begin() ); - } -} - - - - -f_cnt_t AudioSampleRecorder::framesRecorded() const -{ - f_cnt_t frames = 0; - for( BufferList::ConstIterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) - { - frames += ( *it ).second; - } - return frames; -} - - - - -void AudioSampleRecorder::createSampleBuffer( SampleBuffer** sampleBuf ) -{ - const f_cnt_t frames = framesRecorded(); - // create buffer to store all recorded buffers in - sampleFrame * data = new sampleFrame[frames]; - // make sure buffer is cleaned up properly at the end... - sampleFrame * data_ptr = data; - - - assert( data != NULL ); - - // now copy all buffers into big buffer - for( BufferList::ConstIterator it = m_buffers.begin(); - it != m_buffers.end(); ++it ) - { - memcpy( data_ptr, ( *it ).first, ( *it ).second * - sizeof( sampleFrame ) ); - data_ptr += ( *it ).second; - } - // create according sample-buffer out of big buffer - *sampleBuf = new SampleBuffer( data, frames ); - ( *sampleBuf )->setSampleRate( sampleRate() ); - delete[] data; -} - - - - -void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab, - const fpp_t _frames, const float ) -{ - sampleFrame * buf = new sampleFrame[_frames]; - for( fpp_t frame = 0; frame < _frames; ++frame ) - { - for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) - { - buf[frame][chnl] = _ab[frame][chnl]; - } - } - m_buffers.push_back( qMakePair( buf, _frames ) ); -} - - - diff --git a/src/core/audio/AudioSdl.cpp b/src/core/audio/AudioSdl.cpp index 2b810e91374..743aabf73ad 100644 --- a/src/core/audio/AudioSdl.cpp +++ b/src/core/audio/AudioSdl.cpp @@ -34,19 +34,21 @@ #include "gui_templates.h" #include "Mixer.h" - AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : AudioDevice( DEFAULT_CHANNELS, _mixer ), - m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), - m_convertedBufPos( 0 ), - m_convertEndian( false ) + m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ) { _success_ful = false; +#ifdef LMMS_HAVE_SDL2 + m_currentBufferFramesCount = 0; + m_currentBufferFramePos = 0; +#else m_convertedBufSize = mixer()->framesPerPeriod() * channels() * sizeof( int_sample_t ); + m_convertedBufPos = 0; m_convertedBuf = new Uint8[m_convertedBufSize]; - +#endif if( SDL_Init( SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE ) < 0 ) { @@ -55,9 +57,15 @@ AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : } m_audioHandle.freq = sampleRate(); +#ifdef LMMS_HAVE_SDL2 + m_audioHandle.format = AUDIO_F32SYS; // we want it in byte-order + // of system, so we don't have + // to convert the buffers +#else m_audioHandle.format = AUDIO_S16SYS; // we want it in byte-order // of system, so we don't have // to convert the buffers +#endif m_audioHandle.channels = channels(); m_audioHandle.samples = qMax( 1024, mixer()->framesPerPeriod()*2 ); @@ -66,15 +74,52 @@ AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : SDL_AudioSpec actual; +#ifdef LMMS_HAVE_SDL2 + m_outputDevice = SDL_OpenAudioDevice (NULL, + 0, + &m_audioHandle, + &actual, + 0); + if (m_outputDevice == 0) { + qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); + return; + } +#else // open the audio device, forcing the desired format if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 ) { qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); return; } - m_convertEndian = ( m_audioHandle.format != actual.format ); + + m_outConvertEndian = ( m_audioHandle.format != actual.format ); +#endif + _success_ful = true; + +#ifdef LMMS_HAVE_SDL2 +#ifdef LMMS_BUILD_WIN32 + m_inputAudioHandle = m_audioHandle; + m_inputAudioHandle.freq = mixer ()->inputSampleRate (); + m_inputAudioHandle.callback = sdlInputAudioCallback; + + m_inputDevice = SDL_OpenAudioDevice (NULL, + 1, + &m_inputAudioHandle, + &actual, + 0); + if (m_inputDevice != 0 && actual.freq == mixer ()->inputSampleRate ()) { + m_supportsCapture = true; + } else { + m_supportsCapture = false; + qWarning ( "Couldn't open SDL capture device: %s\n", SDL_GetError ()); + } +#else + qWarning("SDL: Recording has been disabled on Linux since it " + "appears to have a growing latency"); +#endif +#endif } @@ -84,9 +129,18 @@ AudioSdl::~AudioSdl() { stopProcessing(); +#ifdef LMMS_HAVE_SDL2 + if (m_inputDevice != 0) + SDL_CloseAudioDevice(m_inputDevice); + if (m_outputDevice != 0) + SDL_CloseAudioDevice(m_outputDevice); +#else SDL_CloseAudio(); - SDL_Quit(); delete[] m_convertedBuf; +#endif + + SDL_Quit(); + delete[] m_outBuf; } @@ -97,7 +151,12 @@ void AudioSdl::startProcessing() { m_stopped = false; +#ifdef LMMS_HAVE_SDL2 + SDL_PauseAudioDevice (m_outputDevice, 0); + SDL_PauseAudioDevice (m_inputDevice, 0); +#else SDL_PauseAudio( 0 ); +#endif } @@ -105,12 +164,30 @@ void AudioSdl::startProcessing() void AudioSdl::stopProcessing() { +#ifdef LMMS_HAVE_SDL2 + if( SDL_GetAudioDeviceStatus(m_outputDevice) == SDL_AUDIO_PLAYING ) +#else if( SDL_GetAudioStatus() == SDL_AUDIO_PLAYING ) +#endif { +#ifdef LMMS_HAVE_SDL2 + SDL_LockAudioDevice (m_inputDevice); + SDL_LockAudioDevice (m_outputDevice); + + m_stopped = true; + + SDL_PauseAudioDevice (m_inputDevice, 1); + SDL_PauseAudioDevice (m_outputDevice, 1); + + SDL_UnlockAudioDevice (m_inputDevice); + SDL_UnlockAudioDevice (m_outputDevice); +#else SDL_LockAudio(); m_stopped = true; SDL_PauseAudio( 1 ); SDL_UnlockAudio(); +#endif + } } @@ -119,6 +196,8 @@ void AudioSdl::stopProcessing() void AudioSdl::applyQualitySettings() { + // Better than if (0) +#if 0 if( 0 )//hqAudio() ) { SDL_CloseAudio(); @@ -135,6 +214,7 @@ void AudioSdl::applyQualitySettings() qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); } } +#endif AudioDevice::applyQualitySettings(); } @@ -160,6 +240,41 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) return; } + // SDL2: process float samples +#ifdef LMMS_HAVE_SDL2 + while( _len ) + { + if( m_currentBufferFramePos == 0 ) + { + // frames depend on the sample rate + const fpp_t frames = getNextBuffer( m_outBuf ); + if( !frames ) + { + memset( _buf, 0, _len ); + return; + } + m_currentBufferFramesCount = frames; + + } + const uint min_frames_count = qMin( _len/sizeof(sampleFrame), + m_currentBufferFramesCount + - m_currentBufferFramePos ); + + const float gain = mixer()->masterGain(); + for (uint f = 0; f < min_frames_count; f++) + { + (m_outBuf + m_currentBufferFramePos)[f][0] *= gain; + (m_outBuf + m_currentBufferFramePos)[f][1] *= gain; + } + + memcpy( _buf, m_outBuf + m_currentBufferFramePos, min_frames_count*sizeof(sampleFrame) ); + _buf += min_frames_count*sizeof(sampleFrame); + _len -= min_frames_count*sizeof(sampleFrame); + m_currentBufferFramePos += min_frames_count; + + m_currentBufferFramePos %= m_currentBufferFramesCount; + } +#else while( _len ) { if( m_convertedBufPos == 0 ) @@ -177,7 +292,7 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) convertToS16( m_outBuf, frames, mixer()->masterGain(), (int_sample_t *)m_convertedBuf, - m_convertEndian ); + m_outConvertEndian ); } const int min_len = qMin( _len, m_convertedBufSize - m_convertedBufPos ); @@ -187,10 +302,25 @@ void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) m_convertedBufPos += min_len; m_convertedBufPos %= m_convertedBufSize; } +#endif } +#ifdef LMMS_HAVE_SDL2 +void AudioSdl::sdlInputAudioCallback(void *_udata, Uint8 *_buf, int _len) { + AudioSdl * _this = static_cast( _udata ); + + _this->sdlInputAudioCallback( _buf, _len ); +} + +void AudioSdl::sdlInputAudioCallback(Uint8 *_buf, int _len) { + sampleFrame *samples_buffer = (sampleFrame *) _buf; + fpp_t frames = _len / sizeof ( sampleFrame ); + mixer()->pushInputFrames (samples_buffer, frames); +} + +#endif AudioSdl::setupWidget::setupWidget( QWidget * _parent ) : AudioDeviceSetupWidget( AudioSdl::name(), _parent ) diff --git a/src/gui/editors/Editor.cpp b/src/gui/editors/Editor.cpp index bdc3e55d4bb..600199b5c65 100644 --- a/src/gui/editors/Editor.cpp +++ b/src/gui/editors/Editor.cpp @@ -25,6 +25,8 @@ #include "Editor.h" #include "Song.h" +#include "ComboBox.h" +#include "ToolButton.h" #include "MainWindow.h" #include "embed.h" @@ -103,12 +105,16 @@ Editor::Editor(bool record) : // Add actions to toolbar addButton(m_playAction, "playButton"); + addButton(m_stopAction, "stopButton"); + + // Seperate playback buttons and recording buttons. + m_toolBar->addSeparator (); + if (record) { - addButton(m_recordAction, "recordButton"); addButton(m_recordAccompanyAction, "recordAccompanyButton"); + addButton(m_recordAction, "m_recordAction"); } - addButton(m_stopAction, "stopButton"); } Editor::~Editor() diff --git a/src/gui/editors/SongEditor.cpp b/src/gui/editors/SongEditor.cpp index 52e91a26061..7bc46050cb6 100644 --- a/src/gui/editors/SongEditor.cpp +++ b/src/gui/editors/SongEditor.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "AutomatableSlider.h" #include "ComboBox.h" @@ -666,6 +667,8 @@ SongEditorWindow::SongEditorWindow(Song* song) : m_editor(new SongEditor(song)), m_crtlAction( NULL ) { + bool isRecordSupported = Engine::mixer()->audioDev()->supportsCapture(); + setWindowTitle( tr( "Song-Editor" ) ); setWindowIcon( embed::getIconPixmap( "songeditor" ) ); @@ -677,7 +680,11 @@ SongEditorWindow::SongEditorWindow(Song* song) : // Set up buttons m_playAction->setToolTip(tr("Play song (Space)")); + + // Remove the record action. m_recordAction->setToolTip(tr("Record samples from Audio-device")); + m_toolBar->removeAction (m_recordAction); + m_recordAccompanyAction->setToolTip(tr( "Record samples from Audio-device while playing song or BB track")); m_stopAction->setToolTip(tr( "Stop song (Space)" )); @@ -689,6 +696,34 @@ SongEditorWindow::SongEditorWindow(Song* song) : tr("Click here, if you want to stop playing of your song. " "The song-position-marker will be set to the start of your song.")); + if (isRecordSupported) { + auto *recordMenu = new QMenu(this); + auto *recordGroup = new QActionGroup(this); + recordGroup->addAction (new QAction(tr("Stereo"), recordGroup))->setData (SampleTrack::RecordingChannel::Stereo); + recordGroup->addAction (new QAction(tr("Mono left"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoLeft); + recordGroup->addAction (new QAction(tr("Mono right"), recordGroup))->setData (SampleTrack::RecordingChannel::MonoRight); + + recordGroup->setExclusive (true); + + for (auto *action : recordGroup->actions ()) { + action->setCheckable (true); + } + + // Check stereo by default. + recordGroup->actions ().first ()->setChecked (true); + + connect (recordGroup, SIGNAL(triggered(QAction*)), SLOT(onRecordChannelSelected(QAction*))); + + recordMenu->addActions (recordGroup->actions ()); + + QToolButton * recordTool = new QToolButton(this); + recordTool->setMenu(recordMenu); + recordTool->setToolTip (tr ("Select default channels to record.")); + recordTool->setPopupMode( QToolButton::InstantPopup ); + recordTool->setFixedWidth (17); + + m_toolBar->addWidget (recordTool); + } // Track actions DropToolBar *trackActionsToolBar = addDropToolBarToTop(tr("Track actions")); @@ -823,6 +858,13 @@ void SongEditorWindow::adjustUiAfterProjectLoad() m_editor->scrolled(0); } +void SongEditorWindow::onRecordChannelSelected(QAction *action) { + action->setChecked (true); + + // Set the recording channel according to the action's data. + m_globalRecordChannel = static_cast(action->data ().value()); +} + @@ -850,3 +892,8 @@ void SongEditorWindow::keyReleaseEvent( QKeyEvent *ke ) } } } + +SampleTrack::RecordingChannel SongEditorWindow::globalRecordChannel() const +{ + return m_globalRecordChannel; +} diff --git a/src/gui/widgets/Graph.cpp b/src/gui/widgets/Graph.cpp index ed57dcea639..572d6f163bf 100644 --- a/src/gui/widgets/Graph.cpp +++ b/src/gui/widgets/Graph.cpp @@ -23,6 +23,8 @@ * */ +#include + #include #include #include @@ -582,7 +584,8 @@ void graphModel::setWaveToNoise() QString graphModel::setWaveToUser() { - SampleBuffer * sampleBuffer = new SampleBuffer; + auto sampleBuffer = std::unique_ptr{new SampleBuffer}; + QString fileName = sampleBuffer->openAndSetWaveformFile(); if( fileName.isEmpty() == false ) { @@ -595,8 +598,6 @@ QString graphModel::setWaveToUser() sampleBuffer->dataUnlock(); } - sharedObject::unref( sampleBuffer ); - emit samplesChanged( 0, length() - 1 ); return fileName; }; diff --git a/src/tracks/AutomationTrack.cpp b/src/tracks/AutomationTrack.cpp index 11c919f0e42..2ea170bb89f 100644 --- a/src/tracks/AutomationTrack.cpp +++ b/src/tracks/AutomationTrack.cpp @@ -33,6 +33,7 @@ #include "TrackContainerView.h" #include "TrackLabelButton.h" +#include AutomationTrack::AutomationTrack( TrackContainer* tc, bool _hidden ) : Track( _hidden ? HiddenAutomationTrack : Track::AutomationTrack, tc ) @@ -140,4 +141,34 @@ void AutomationTrackView::dropEvent( QDropEvent * _de ) update(); } +void AutomationTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) { + TrackView::updateTrackOperationsWidgetMenu (trackOperations); + + auto toMenu = trackOperations->trackOps ()->menu (); + + toMenu->addAction( tr( "Turn all recording on" ), this, SLOT( recordingOn() ) ); + toMenu->addAction( tr( "Turn all recording off" ), this, SLOT( recordingOff() ) ); +} + +void AutomationTrackView::recordingOn() { + const Track::tcoVector & tcov = getTrack()->getTCOs(); + for( Track::tcoVector::const_iterator it = tcov.begin(); it != tcov.end(); ++it ) + { + AutomationPattern * ap = dynamic_cast( *it ); + if( ap ) { ap->setRecording( true ); } + } + update(); +} + + +void AutomationTrackView::recordingOff() { + const Track::tcoVector & tcov = getTrack()->getTCOs(); + for( Track::tcoVector::const_iterator it = tcov.begin(); it != tcov.end(); ++it ) + { + AutomationPattern * ap = dynamic_cast( *it ); + if( ap ) { ap->setRecording( false ); } + } + update(); +} + diff --git a/src/tracks/InstrumentTrack.cpp b/src/tracks/InstrumentTrack.cpp index 90dbf11a684..44fae54e041 100644 --- a/src/tracks/InstrumentTrack.cpp +++ b/src/tracks/InstrumentTrack.cpp @@ -1243,6 +1243,18 @@ QMenu * InstrumentTrackView::createFxMenu(QString title, QString newFxLabel) return fxMenu; } +void InstrumentTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) +{ + TrackView::updateTrackOperationsWidgetMenu (trackOperations); + + auto toMenu = trackOperations->trackOps ()->menu (); + QMenu *fxMenu = createFxMenu( tr( "FX %1: %2" ), tr( "Assign to new FX Channel" )); + toMenu->addMenu(fxMenu); + + toMenu->addSeparator(); + toMenu->addMenu( midiMenu() ); +} + diff --git a/src/tracks/Pattern.cpp b/src/tracks/Pattern.cpp index 8218d16f631..72199a8c78e 100644 --- a/src/tracks/Pattern.cpp +++ b/src/tracks/Pattern.cpp @@ -37,7 +37,6 @@ #include "PianoRoll.h" #include "RenameDialog.h" #include "SampleBuffer.h" -#include "AudioSampleRecorder.h" #include "BBTrackContainer.h" #include "StringPairDrag.h" #include "MainWindow.h" diff --git a/src/tracks/SampleTrack.cpp b/src/tracks/SampleTrack.cpp index 7fda6512e4a..bff64d9b67c 100644 --- a/src/tracks/SampleTrack.cpp +++ b/src/tracks/SampleTrack.cpp @@ -1,4 +1,4 @@ -/* +/* * SampleTrack.cpp - implementation of class SampleTrack, a track which * provides arrangement of samples * @@ -49,6 +49,7 @@ #include "Mixer.h" #include "EffectRackView.h" #include "TrackLabelButton.h" +#include "SampleBuffer.h" SampleTCO::SampleTCO( Track * _track ) : TrackContentObject( _track ), @@ -56,7 +57,6 @@ SampleTCO::SampleTCO( Track * _track ) : m_isPlaying( false ) { saveJournallingState( false ); - setSampleFile( "" ); restoreJournallingState(); // we need to receive bpm-change-events, because then we have to @@ -66,22 +66,7 @@ SampleTCO::SampleTCO( Track * _track ) : connect( Engine::getSong(), SIGNAL( timeSignatureChanged( int,int ) ), this, SLOT( updateLength() ) ); - //care about positionmarker - TimeLineWidget * timeLine = Engine::getSong()->getPlayPos( Engine::getSong()->Mode_PlaySong ).m_timeLine; - if( timeLine ) - { - connect( timeLine, SIGNAL( positionMarkerMoved() ), this, SLOT( playbackPositionChanged() ) ); - } - //playbutton clicked or space key / on Export Song set isPlaying to false - connect( Engine::getSong(), SIGNAL( playbackStateChanged() ), this, SLOT( playbackPositionChanged() ) ); - //care about loops - connect( Engine::getSong(), SIGNAL( updateSampleTracks() ), this, SLOT( playbackPositionChanged() ) ); - //care about mute TCOs - connect( this, SIGNAL( dataChanged() ), this, SLOT( playbackPositionChanged() ) ); - //care about mute track - connect( getTrack()->getMutedModel(), SIGNAL( dataChanged() ),this, SLOT( playbackPositionChanged() ) ); - //care about TCO position - connect( this, SIGNAL( positionChanged() ), this, SLOT( updateTrackTcos() ) ); + connect (m_sampleBuffer.get (), SIGNAL(sampleUpdated()), this, SLOT(onSampleBufferChanged())); switch( getTrack()->trackContainer()->type() ) { @@ -95,7 +80,9 @@ SampleTCO::SampleTCO( Track * _track ) : setAutoResize( false ); break; } - updateTrackTcos(); + + //care about TCO position + connect( this, SIGNAL( positionChanged() ), getTrack (), SLOT( updateTcos() ) ); } @@ -108,7 +95,6 @@ SampleTCO::~SampleTCO() { sampletrack->updateTcos(); } - sharedObject::unref( m_sampleBuffer ); } @@ -132,25 +118,13 @@ const QString & SampleTCO::sampleFile() const -void SampleTCO::setSampleBuffer( SampleBuffer* sb ) -{ - sharedObject::unref( m_sampleBuffer ); - m_sampleBuffer = sb; - updateLength(); - - emit sampleChanged(); -} - - - void SampleTCO::setSampleFile( const QString & _sf ) { m_sampleBuffer->setAudioFile( _sf ); setStartTimeOffset( 0 ); changeLength( (int) ( m_sampleBuffer->frames() / Engine::framesPerTick() ) ); - emit sampleChanged(); - emit playbackPositionChanged(); + // Already has been has been called sampleChanged from m_sampleBuffer. } @@ -164,24 +138,9 @@ void SampleTCO::toggleRecord() - -void SampleTCO::playbackPositionChanged() +void SampleTCO::onSampleBufferChanged() { - Engine::mixer()->removePlayHandlesOfTypes( getTrack(), PlayHandle::TypeSamplePlayHandle ); - SampleTrack * st = dynamic_cast( getTrack() ); - st->setPlayingTcos( false ); -} - - - - -void SampleTCO::updateTrackTcos() -{ - SampleTrack * sampletrack = dynamic_cast( getTrack() ); - if( sampletrack) - { - sampletrack->updateTcos(); - } + emit sampleChanged (); } @@ -200,6 +159,11 @@ void SampleTCO::setIsPlaying(bool isPlaying) m_isPlaying = isPlaying; } +bool SampleTCO::isEmpty() const +{ + return (sampleLength () == 0); +} + @@ -213,7 +177,7 @@ void SampleTCO::updateLength() MidiTime SampleTCO::sampleLength() const { - return (int)( m_sampleBuffer->frames() / Engine::framesPerTick() ); + return (int)( m_sampleBuffer->frames() / Engine::framesPerTick(m_sampleBuffer->sampleRate ()) ); } @@ -247,13 +211,11 @@ void SampleTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) } _this.setAttribute( "len", length() ); _this.setAttribute( "muted", isMuted() ); - _this.setAttribute( "src", sampleFile() ); _this.setAttribute( "off", startTimeOffset() ); - if( sampleFile() == "" ) - { - QString s; - _this.setAttribute( "data", m_sampleBuffer->toBase64( s ) ); - } + + m_sampleBuffer->saveState (_doc, _this); + + _this.setAttribute ("is_record", isRecord ()); // TODO: start- and end-frame } @@ -266,14 +228,35 @@ void SampleTCO::loadSettings( const QDomElement & _this ) { movePosition( _this.attribute( "pos" ).toInt() ); } - setSampleFile( _this.attribute( "src" ) ); - if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) ) - { - m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) ); + + // Should not be happening after 1.3. + if (_this.hasAttribute ("src")) { + auto audioFile = _this.attribute ("src"); + + if (! audioFile.isEmpty ()) { + setSampleFile( _this.attribute( "src" ) ); + } } + + if (sampleFile () == QString()) { + // Data without any other info. + // Should not be happening after 1.3. + if (_this.hasAttribute ("data") && _this.attribute ("data") != QString()) { + qWarning("Using default sampleRate. That could lead to invalid values1"); + m_sampleBuffer->loadFromBase64 (_this.attribute ("data"), + Engine::mixer ()->baseSampleRate ()); + } else { + m_sampleBuffer->restoreState (_this.firstChildElement (m_sampleBuffer->nodeName ())); + } + } + changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); setStartTimeOffset( _this.attribute( "off" ).toInt() ); + + if (_this.hasAttribute ("is_record")) { + setRecord (_this.attribute ("is_record").toInt ()); + } } @@ -289,8 +272,7 @@ TrackContentObjectView * SampleTCO::createView( TrackView * _tv ) SampleTCOView::SampleTCOView( SampleTCO * _tco, TrackView * _tv ) : TrackContentObjectView( _tco, _tv ), - m_tco( _tco ), - m_paintPixmap() + m_tco( _tco ) { // update UI and tooltip updateSample(); @@ -310,10 +292,10 @@ void SampleTCOView::updateSample() ToolTip::add( this, ( m_tco->m_sampleBuffer->audioFile() != "" ) ? m_tco->m_sampleBuffer->audioFile() : tr( "double-click to select sample" ) ); -} - + setNeedsUpdate (true); +} void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) { @@ -345,9 +327,10 @@ void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) "Ctrl"), #endif m_tco, SLOT( toggleMute() ) ); - /*contextMenu.addAction( embed::getIconPixmap( "record" ), - tr( "Set/clear record" ), - m_tco, SLOT( toggleRecord() ) );*/ + contextMenu.addAction( embed::getIconPixmap( "record" ), + tr( "Set/clear record (Shift + Ctrl + left click)" ), + m_tco, SLOT( toggleRecord() ) ); + constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); @@ -377,15 +360,6 @@ void SampleTCOView::dropEvent( QDropEvent * _de ) m_tco->setSampleFile( StringPairDrag::decodeValue( _de ) ); _de->accept(); } - else if( StringPairDrag::decodeKey( _de ) == "sampledata" ) - { - m_tco->m_sampleBuffer->loadFromBase64( - StringPairDrag::decodeValue( _de ) ); - m_tco->updateLength(); - update(); - _de->accept(); - Engine::getSong()->setModified(); - } else { TrackContentObjectView::dropEvent( _de ); @@ -410,7 +384,7 @@ void SampleTCOView::mousePressEvent( QMouseEvent * _me ) SampleTCO * sTco = dynamic_cast( getTrackContentObject() ); if( sTco ) { - sTco->updateTrackTcos(); + static_cast(sTco->getTrack ())->updateTcos (); } } TrackContentObjectView::mousePressEvent( _me ); @@ -427,7 +401,7 @@ void SampleTCOView::mouseReleaseEvent(QMouseEvent *_me) SampleTCO * sTco = dynamic_cast( getTrackContentObject() ); if( sTco ) { - sTco->playbackPositionChanged(); + static_cast(sTco->getTrack ())->playbackPositionChanged(); } } TrackContentObjectView::mouseReleaseEvent( _me ); @@ -451,30 +425,22 @@ void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) void SampleTCOView::paintEvent( QPaintEvent * pe ) { - QPainter painter( this ); - - if( !needsUpdate() ) - { - painter.drawPixmap( 0, 0, m_paintPixmap ); - return; - } + QPainter p( this ); setNeedsUpdate( false ); - if (m_paintPixmap.isNull() || m_paintPixmap.size() != size()) - { - m_paintPixmap = QPixmap(size()); - } - - QPainter p( &m_paintPixmap ); - QLinearGradient lingrad( 0, 0, 0, height() ); QColor c; bool muted = m_tco->getTrack()->isMuted() || m_tco->isMuted(); - // state: selected, muted, normal - c = isSelected() ? selectedColor() : ( muted ? mutedBackgroundColor() - : painter.background().color() ); + if (isSelected ()) + c = selectedColor (); + else if (muted) + c = mutedBackgroundColor (); + else if (m_tco->isRecord ()) + c = recordingBackgroundColor (); + else + c = p.background ().color (); lingrad.setColorAt( 1, c.darker( 300 ) ); lingrad.setColorAt( 0, c ); @@ -491,22 +457,28 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) p.fillRect( rect(), c ); } - p.setPen( !muted ? painter.pen().brush().color() : mutedColor() ); + p.setPen( !muted ? p.pen().brush().color() : mutedColor() ); + + auto timeSig = TimeSig(Engine::getSong()->getTimeSigModel()); + auto realTicksPerDefaultTicks = float(float(MidiTime::ticksPerTact(timeSig) / MidiTime::ticksPerTact())); + auto normalizedPixelsPerTact = pixelsPerTact() * realTicksPerDefaultTicks; + auto normalizedFramesPerTick = Engine::framesPerTick(m_tco->sampleBuffer()->sampleRate()) * realTicksPerDefaultTicks; const int spacing = TCO_BORDER_WIDTH + 1; - const float ppt = fixedTCOs() ? - ( parentWidget()->width() - 2 * TCO_BORDER_WIDTH ) - / (float) m_tco->length().getTact() : - pixelsPerTact(); - float nom = Engine::getSong()->getTimeSigModel().getNumerator(); - float den = Engine::getSong()->getTimeSigModel().getDenominator(); - float ticksPerTact = DefaultTicksPerTact * nom / den; - - float offset = m_tco->startTimeOffset() / ticksPerTact * pixelsPerTact(); - QRect r = QRect( TCO_BORDER_WIDTH + offset, spacing, - qMax( static_cast( m_tco->sampleLength() * ppt / ticksPerTact ), 1 ), rect().bottom() - 2 * spacing ); - m_tco->m_sampleBuffer->visualize( p, r, pe->rect() ); + QMargins margins(spacing, + TCO_BORDER_WIDTH-1, + spacing, + TCO_BORDER_WIDTH); + m_sampleBufferVisualizer.update(*m_tco->sampleBuffer(), + m_tco->startTimeOffset(), + m_tco->sampleLength(), + rect()-margins, + normalizedPixelsPerTact, + f_cnt_t (normalizedFramesPerTick), + p.pen(), + SampleBufferVisualizer::Operation::Append); + m_sampleBufferVisualizer.draw(p); QFileInfo fileInfo(m_tco->m_sampleBuffer->audioFile()); QString filename = fileInfo.fileName(); @@ -517,7 +489,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) // inner border p.setPen( c.lighter( 160 ) ); - p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH, + p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH, rect().bottom() - TCO_BORDER_WIDTH ); // outer border @@ -533,24 +505,7 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) embed::getIconPixmap( "muted", size, size ) ); } - // recording sample tracks is not possible at the moment - - /* if( m_tco->isRecord() ) - { - p.setFont( pointSize<7>( p.font() ) ); - - p.setPen( textShadowColor() ); - p.drawText( 10, p.fontMetrics().height()+1, "Rec" ); - p.setPen( textColor() ); - p.drawText( 9, p.fontMetrics().height(), "Rec" ); - - p.setBrush( QBrush( textColor() ) ); - p.drawEllipse( 4, 5, 4, 4 ); - }*/ - p.end(); - - painter.drawPixmap( 0, 0, m_paintPixmap ); } @@ -560,14 +515,37 @@ void SampleTCOView::paintEvent( QPaintEvent * pe ) SampleTrack::SampleTrack( TrackContainer* tc ) : Track( Track::SampleTrack, tc ), + m_recordingChannelModel(RecordingChannel::None, + RecordingChannel::None, + RecordingChannel::Stereo, + this, + tr ("Record channel")), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, - tr( "Volume" ) ), + tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_audioPort( tr( "Sample track" ), true, &m_volumeModel, &m_panningModel, &m_mutedModel ) + { setName( tr( "Sample track" ) ); m_panningModel.setCenterValue( DefaultPanning ); + connect (Engine::getSong (), SIGNAL(beforeRecordOn(MidiTime)), this, SLOT(beforeRecordOn(MidiTime))); + + + //care about positionmarker + TimeLineWidget * timeLine = Engine::getSong()->getPlayPos( Engine::getSong()->Mode_PlaySong ).m_timeLine; + if( timeLine ) + { + connect( timeLine, SIGNAL( positionMarkerMoved() ), this, SLOT( playbackPositionChanged() ) ); + } + //playbutton clicked or space key / on Export Song set isPlaying to false + connect( Engine::getSong(), SIGNAL( playbackStateChanged() ), this, SLOT( playbackPositionChanged() ) ); + //care about loops + connect( Engine::getSong(), SIGNAL( updateSampleTracks() ), this, SLOT( playbackPositionChanged() ) ); + //care about mute TCOs + connect( this, SIGNAL( dataChanged() ), this, SLOT( playbackPositionChanged() ) ); + //care about mute track + connect( getMutedModel(), SIGNAL( dataChanged() ),this, SLOT( playbackPositionChanged() ) ); } @@ -584,7 +562,8 @@ SampleTrack::~SampleTrack() bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _offset, int _tco_num ) { - m_audioPort.effects()->startRunning(); + if (m_audioPort.effects()) + m_audioPort.effects()->startRunning(); bool played_a_note = false; // will be return variable tcoVector tcos; @@ -607,19 +586,30 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, { TrackContentObject * tco = getTCO( i ); SampleTCO * sTco = dynamic_cast( tco ); - float framesPerTick = Engine::framesPerTick(); + + // If this is an automatically created record track, resize it to the current + // position. + if (sTco->isRecord () && !sTco->isMuted () && sTco->getAutoResize ()) { + sTco->changeLength (_start - sTco->startPosition()); + } + if( _start >= sTco->startPosition() && _start < sTco->endPosition() ) { - if( sTco->isPlaying() == false && _start > sTco->startPosition() + sTco->startTimeOffset() ) + if( sTco->isPlaying() == false && (_start >= (sTco->startPosition() + sTco->startTimeOffset()) + || sTco->isRecord ()) ) { - f_cnt_t sampleStart = framesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); - f_cnt_t tcoFrameLength = framesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); + auto bufferFramesPerTick = Engine::framesPerTick (sTco->sampleBuffer ()->sampleRate ()); + f_cnt_t sampleStart = bufferFramesPerTick * ( _start - sTco->startPosition() - sTco->startTimeOffset() ); + + f_cnt_t tcoFrameLength = bufferFramesPerTick * ( sTco->endPosition() - sTco->startPosition() - sTco->startTimeOffset() ); + f_cnt_t sampleBufferLength = sTco->sampleBuffer()->frames(); //if the Tco smaller than the sample length we play only until Tco end //else we play the sample to the end but nothing more f_cnt_t samplePlayLength = tcoFrameLength > sampleBufferLength ? sampleBufferLength : tcoFrameLength; //we only play within the sampleBuffer limits - if( sampleStart < sampleBufferLength ) + // anyway, "play" (record) this TCO if is recording. + if( sampleStart < sampleBufferLength || sTco->isRecord ()) { sTco->setSampleStartFrame( sampleStart ); sTco->setSamplePlayLength( samplePlayLength ); @@ -647,7 +637,7 @@ bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, { return played_a_note; } - SampleRecordHandle* smpHandle = new SampleRecordHandle( st ); + SampleRecordHandle* smpHandle = new SampleRecordHandle( st , _start - st->startPosition ()); handle = smpHandle; } else @@ -695,6 +685,8 @@ void SampleTrack::saveTrackSpecificSettings( QDomDocument & _doc, #endif m_volumeModel.saveSettings( _doc, _this, "vol" ); m_panningModel.saveSettings( _doc, _this, "pan" ); + m_recordModel.saveSettings(_doc, _this, "record"); + m_recordingChannelModel.saveSettings (_doc, _this, "record_channel"); } @@ -715,8 +707,11 @@ void SampleTrack::loadTrackSpecificSettings( const QDomElement & _this ) } node = node.nextSibling(); } + m_volumeModel.loadSettings( _this, "vol" ); m_panningModel.loadSettings( _this, "pan" ); + m_recordModel.loadSettings (_this, "record"); + m_recordingChannelModel.loadSettings (_this, "record_channel"); } @@ -741,8 +736,65 @@ void SampleTrack::setPlayingTcos( bool isPlaying ) } } +void SampleTrack::beforeRecordOn(MidiTime time) +{ + if (isRecord ()) { + bool isRecordTCOExist = false; + + for(auto &track : getTCOs ()) { + auto sampleTCO = static_cast(track); + + if (sampleTCO->isRecord() && !sampleTCO->isMuted()) { + isRecordTCOExist = true; + } + } + + if (! isRecordTCOExist) { + Engine::mixer()->requestChangeInModel(); + + auto fallbackRecordTCO = static_cast(createTCO (0)); + + fallbackRecordTCO->setRecord (true); + fallbackRecordTCO->movePosition (time); +// fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); + fallbackRecordTCO->changeLength (1); + fallbackRecordTCO->setSampleStartFrame (0); + fallbackRecordTCO->setSamplePlayLength (Engine::framesPerTick()); + fallbackRecordTCO->setIsPlaying (false); + + fallbackRecordTCO->setAutoResize (true); + + Engine::mixer()->doneChangeInModel(); + + } + } +} + +void SampleTrack::toggleRecord() { + setRecord (! isRecord ()); +} +void SampleTrack::playbackPositionChanged() +{ + Engine::mixer()->removePlayHandlesOfTypes( this, PlayHandle::TypeSamplePlayHandle ); + setPlayingTcos( false ); +} + +SampleTrack::RecordingChannel SampleTrack::recordingChannel() const{ + // If we had defined a recording channel for this track, use + // it. Otherwise, use the global setting. + if (m_recordingChannelModel.value () != static_cast(RecordingChannel::None)) { + return static_cast(m_recordingChannelModel.value ()); + } else { + return gui->songEditor ()->globalRecordChannel (); + } +} + +void SampleTrack::setRecordingChannel(const RecordingChannel &recordingChannel) +{ + m_recordingChannelModel.setValue (recordingChannel); +} @@ -752,7 +804,7 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : setFixedHeight( 32 ); TrackLabelButton * tlb = new TrackLabelButton( this, - getTrackSettingsWidget() ); + getTrackSettingsWidget() ); connect( tlb, SIGNAL( clicked( bool ) ), this, SLOT( showEffects() ) ); tlb->setIcon( embed::getIconPixmap( "sample_track" ) ); @@ -760,7 +812,7 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : tlb->show(); m_volumeKnob = new Knob( knobSmall_17, getTrackSettingsWidget(), - tr( "Track volume" ) ); + tr( "Track volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_t->m_volumeModel ); m_volumeKnob->setHintText( tr( "Channel volume:" ), "%" ); @@ -790,7 +842,7 @@ SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : m_effWindow = gui->mainWindow()->addWindowedWidget( m_effectRack ); m_effWindow->setAttribute( Qt::WA_DeleteOnClose, false ); m_effWindow->layout()->setSizeConstraint( QLayout::SetFixedSize ); - m_effWindow->setWindowTitle( _t->name() ); + m_effWindow->setWindowTitle( _t->name() ); m_effWindow->hide(); setModel( _t ); @@ -804,6 +856,39 @@ SampleTrackView::~SampleTrackView() m_effWindow->deleteLater(); } +void SampleTrackView::updateTrackOperationsWidgetMenu(TrackOperationsWidget *trackOperations) { + TrackView::updateTrackOperationsWidgetMenu (trackOperations); + + SampleTrack * st = castModel(); + auto toMenu = trackOperations->trackOps ()->menu (); + + QMenu *recordMenu = toMenu->addMenu (tr ("Set record channel")); + auto *recordChannels = new QActionGroup(recordMenu); + + recordChannels->setExclusive (true); + + recordChannels->addAction(tr( "Stereo" ))->setData (SampleTrack::RecordingChannel::Stereo); + recordChannels->addAction(tr( "Mono left" ))->setData (SampleTrack::RecordingChannel::MonoLeft); + recordChannels->addAction(tr( "Mono right" ))->setData (SampleTrack::RecordingChannel::MonoRight); + + for (auto *action : recordChannels->actions ()) { + action->setCheckable (true); + + if (action->data ().value() == st->m_recordingChannelModel.value ()) + { + action->setChecked (true); + } + } + + recordMenu->addActions (recordChannels->actions ()); + + connect (recordChannels, SIGNAL(triggered(QAction*)), SLOT(onRecordActionSelected(QAction*))); + + auto recordAction = toMenu->addAction( tr( "Toggle record" ), st, SLOT( toggleRecord() ) ); + recordAction->setCheckable (true); + recordAction->setChecked (st->isRecord ()); +} + @@ -830,3 +915,19 @@ void SampleTrackView::modelChanged() TrackView::modelChanged(); } + +void SampleTrackView::onRecordActionSelected(QAction *action) { + SampleTrack * st = castModel(); + auto selectedRecordingChannel = static_cast(action->data ().value()); + + // If we've selected the current recording channel again, we should undo it. + if (selectedRecordingChannel == static_cast(st->m_recordingChannelModel.value ())) { + st->setRecordingChannel (SampleTrack::RecordingChannel::None); + action->setChecked (false); + } else { + st->setRecordingChannel (selectedRecordingChannel); + action->setChecked (true); + } + + +}