diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..e54db6e465
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: [ mcallegari ]
+custom: ["https://www.paypal.me/mcallegariqlcplus"]
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000000..adf35871ea
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,596 @@
+name: QLCplus Github Actions CI Build
+
+on: ["push", "pull_request"]
+
+jobs:
+ build-linux:
+ #if: false
+ runs-on: ubuntu-20.04
+ name: QLCplus Linux ${{matrix.task}}
+ strategy:
+ #fail-fast: false
+ matrix:
+ task: [compile-qt5, compile-qt5qml, coverage-qt5]
+ env:
+ CI_REPO_SLUG: ${{ github.repository }}
+ CI_BRANCH: ${{ github.head_ref }}
+ CI_PULL_REQUEST: ${{ github.event.number }}
+ CC: gcc
+ CXX: g++
+ PACKAGES_BASE:
+ gdb
+ libasound2-dev
+ libusb-1.0-0-dev
+ libftdi1-dev
+ shared-mime-info
+ libudev-dev
+ libmad0-dev
+ libsndfile1-dev
+ liblo-dev
+ libfftw3-dev
+ libgl1-mesa-dev
+ libxml2-utils
+ xvfb
+ ccache
+ wget
+ PACKAGES_QML_BASE:
+ libpulse-dev
+ PACKAGES_COVERAGE_BASE:
+ lcov
+ QT_MODULES:
+ qtscript
+ defaults:
+ run:
+ shell: bash
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false
+
+ - name: Set General ENV variables
+ run: |
+ echo "PACKAGES_QT5=$(echo ${PACKAGES_BASE})" >> $GITHUB_ENV
+ echo "PACKAGES_QML=$(echo ${PACKAGES_BASE} ${PACKAGES_QML_BASE})" >> $GITHUB_ENV
+ echo "PACKAGES_COVERAGE=$(echo ${PACKAGES_BASE} ${PACKAGES_QT5_BASE} ${PACKAGES_COVERAGE_BASE})" >> $GITHUB_ENV
+ echo "PACKAGES_ALL=$(echo ${PACKAGES_BASE} ${PACKAGES_QML_BASE} ${PACKAGES_COVERAGE_BASE})" >> $GITHUB_ENV
+ echo "CI_BRANCH=$(echo $GITHUB_REF | cut -d '/' -f 3)" >> $GITHUB_ENV
+ echo "CI_SECURE_ENV_VARS=$(if [ -z '${{ secrets.something }}' ]; then echo 'false'; else echo 'true'; fi)" >> $GITHUB_ENV
+ echo "CI_EVENT_TYPE=$(if [ 'schedule' == '${{ github.event_name }}' ]; then echo 'cron'; else echo '${{ github.event_name }}'; fi)" >> $GITHUB_ENV
+ echo "NPROC=$(nproc)" >> $GITHUB_ENV
+ echo "TASK=$(echo '${{matrix.task}}' | cut -d '-' -f 2)" >> $GITHUB_ENV
+ echo "INSTALL_ROOT=`pwd`/install_root" >> $GITHUB_ENV
+ echo "BUILD_DATE=`date -u '+%Y%m%d'`" >> $GITHUB_ENV
+ echo "GIT_REV=`git rev-parse --short HEAD`" >> $GITHUB_ENV
+
+ - name: Set QT ENV variables (qt5)
+ if: ${{ endsWith( matrix.task, 'qt5') }}
+ run: |
+ echo "QT=${QT:-$(echo '${{matrix.task}}' | cut -d '-' -f 2)}" >> $GITHUB_ENV
+ echo "QT_INSTALL_DIR=/opt" >> $GITHUB_ENV
+ echo "QT_VERSION=5.14.2" >> $GITHUB_ENV
+ echo "QT_MODULES_INSTALL=$(echo ${QT_MODULES})" >> $GITHUB_ENV
+ echo "APPVERSION=`grep '^!qmlui' variables.pri | grep APPVERSION | sed 's/^.*= *//;s/ /_/g'`" >> $GITHUB_ENV
+ source $GITHUB_ENV && echo "QTDIR=${QT_INSTALL_DIR}/Qt/${QT_VERSION}/gcc_64" >> $GITHUB_ENV
+ source $GITHUB_ENV && echo "QMAKE=${QTDIR}/bin/qmake" >> $GITHUB_ENV
+
+ - name: Set QT ENV variables (qt5qml)
+ if: ${{ endsWith( matrix.task, 'qt5qml') }}
+ run: |
+ echo "QT=${QT:-$(echo '${{matrix.task}}' | cut -d '-' -f 2)}" >> $GITHUB_ENV
+ echo "QT_INSTALL_DIR=/opt" >> $GITHUB_ENV
+ echo "QT_VERSION=5.14.2" >> $GITHUB_ENV
+ echo "QT_MODULES_INSTALL=$(echo ${QT_MODULES})" >> $GITHUB_ENV
+ echo "APPVERSION=`grep '^qmlui' variables.pri | grep APPVERSION | sed 's/^.*= *//;s/ /_/g'`" >> $GITHUB_ENV
+ source $GITHUB_ENV && echo "QTDIR=${QT_INSTALL_DIR}/Qt/${QT_VERSION}/gcc_64" >> $GITHUB_ENV
+ source $GITHUB_ENV && echo "QMAKE=${QTDIR}/bin/qmake" >> $GITHUB_ENV
+
+ - name: Print ENV vars
+ run: |
+ echo "CI_BRANCH: ${CI_BRANCH}"
+ echo "CI_PULL_REQUEST: ${CI_PULL_REQUEST}"
+ echo "CI_REPO_SLUG: ${CI_REPO_SLUG}"
+ echo "CI_EVENT_TYPE: ${CI_EVENT_TYPE}"
+ echo "CI_SECURE_ENV_VARS: ${CI_SECURE_ENV_VARS}"
+ echo "PACKAGES_QT5: ${PACKAGES_QT5}"
+ echo "PACKAGES_QML: ${PACKAGES_QML}"
+ echo "PACKAGES_COVERAGE: ${PACKAGES_COVERAGE}"
+ echo "TASK: ${TASK}"
+ echo "QT: ${QT}"
+ echo "NPROC: ${NPROC}"
+
+ - name: Download, cache and install packages
+ uses: awalsh128/cache-apt-pkgs-action@latest
+ with:
+ packages: ${{env.PACKAGES_ALL}}
+ version: ${{runner.os}}-apt
+
+ - name: Install Qt through aqt (and python)
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ cache: true
+ dir: ${{ env.QT_INSTALL_DIR }}
+ modules: ${{ env.QT_MODULES_INSTALL }}
+
+ - name: Install python-lxml for fixtures-tool # with the now-registered aqt python
+ run: pip install lxml
+
+ - name: Setup ccache
+ uses: Chocobo1/setup-ccache-action@v1
+ with:
+ update_packager_index: false
+ install_ccache: false
+ ccache_options: |
+ max_size=5G
+ compression=false
+
+ - name: Setup ruby cache
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '2.7'
+ bundler-cache: true
+
+ - name: Install coveralls-lcov
+ if: ${{ startsWith( matrix.task, 'coverage') }}
+ run: sudo gem install coveralls-lcov
+
+ - name: Print program versions and username
+ run: |
+ echo "CXX:"
+ ${CXX} --version
+ echo "QMAKE:"
+ ${QMAKE} -v
+ echo "Python:"
+ python --version
+ echo "WHOAMI:"
+ whoami
+
+ - name: Configure for QT5 build
+ if: ${{ matrix.task == 'compile-qt5' }}
+ run: |
+ ${QMAKE} QMAKE_CXX="${CXX}" QMAKE_CC="${CC}" QMAKE_LINK="${CXX}" QMAKE_LINK_SHLIB="${CXX}" CONFIG+=appimage FORCECONFIG=release
+
+ - name: Configure for QT5QML build
+ if: ${{ matrix.task == 'compile-qt5qml' }}
+ run: |
+ $QMAKE QMAKE_CXX="$CXX" QMAKE_CC="$CC" QMAKE_LINK="$CXX" QMAKE_LINK_SHLIB="$CXX" CONFIG+=appimage CONFIG+=qmlui FORCECONFIG=release
+
+ - name: Configure for QT5 coverage build
+ if: ${{ matrix.task == 'coverage-qt5' }}
+ run: |
+ $QMAKE QMAKE_CXX="$CXX" QMAKE_CC="$CC" QMAKE_LINK="$CXX" QMAKE_LINK_SHLIB="$CXX" CONFIG+=coverage
+ #QMAKE_CXXFLAGS+="-fno-sized-deallocation"
+
+ - name: Build
+ run: make -j $NPROC
+
+ - name: Test
+ if: ${{ ! startsWith( matrix.task, 'coverage') }}
+ run: make check
+
+ - name: Test with Coverage
+ if: ${{ startsWith( matrix.task, 'coverage') }}
+ run: make lcov
+
+ - name: Coveralls
+ if: ${{ startsWith( matrix.task, 'coverage') }}
+ uses: coverallsapp/github-action@v2
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ file: coverage/coverage.info
+ format: lcov
+
+ - name: Install
+ if: ${{ ! startsWith( matrix.task, 'coverage') }}
+ run: |
+ make INSTALL_ROOT=${INSTALL_ROOT} install
+ cp -v resources/icons/svg/qlcplus.svg ${INSTALL_ROOT}
+ cp -v platforms/linux/qlcplus.desktop ${INSTALL_ROOT}
+
+ - name: Adapt qlcplus for AppImage (qt5)
+ if: ${{ matrix.task == 'compile-qt5' }}
+ run: |
+ chrpath -r "../lib" ${INSTALL_ROOT}/usr/bin/qlcplus
+
+ - name: Adapt qlcplus for AppImage (qt5qml)
+ if: ${{ matrix.task == 'compile-qt5qml' }}
+ run: |
+ chrpath -r "../lib" ${INSTALL_ROOT}/usr/bin/qlcplus-qml
+ sed -i -e 's/Exec=qlcplus --open %f/Exec=qlcplus-qml/g' ${INSTALL_ROOT}/qlcplus.desktop
+
+ - name: Store original install artifacts before stripping and AppImage
+ # Activate for debugging
+ if: ${{ false && ! startsWith( matrix.task, 'coverage') }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.task }}-files
+ path: ${{ env.INSTALL_ROOT }}
+
+ - name: Strip Binaries (qt5)
+ if: ${{ matrix.task == 'compile-qt5' }}
+ run: |
+ strip -v ${INSTALL_ROOT}/usr/bin/qlcplus
+ find ${INSTALL_ROOT}/usr/lib/ -name libqlcplusengine.so.1.0.0 -exec strip -v {} \;
+
+ - name: Strip Binaries (qt5qml)
+ if: ${{ matrix.task == 'compile-qt5qml' }}
+ run: |
+ strip -v ${INSTALL_ROOT}/usr/bin/qlcplus-qml
+ find ${INSTALL_ROOT}/usr/lib/ -name libqlcplusengine.so.1.0.0 -exec strip -v {} \;
+
+ - name: Delete unused files for AppImage
+ if: ${{ ! startsWith( matrix.task, 'coverage') }}
+ run: |
+ find ${INSTALL_ROOT}/usr/bin/ -name plugins.qmltypes -type f -delete
+ find ${INSTALL_ROOT}/usr/bin -name *.qmlc -type f -delete
+ rm -rf ${INSTALL_ROOT}/usr/bin/QtQuick/Extras QtQuick/Particles.2 QtQuick/XmlListModel
+ rm -rf ${INSTALL_ROOT}/usr/bin/QtQuick/Controls.2/designer QtQuick/Controls.2/Material
+ rm -rf ${INSTALL_ROOT}/usr/bin/QtQuick/Controls.2/Universal QtQuick/Controls.2/Fusion
+ rm -rf ${INSTALL_ROOT}/usr/bin/QtQuick/Controls.2/Imagine QtQuick/Controls.2/Scene2D
+
+ - name: Build AppImage
+ if: ${{ ! startsWith( matrix.task, 'coverage') }}
+ run: |
+ wget -c https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-x86_64 -O ${INSTALL_ROOT}/AppRun
+ chmod a+x ${INSTALL_ROOT}/AppRun
+ wget -c https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool-x86_64.AppImage
+ chmod a+x ./appimagetool-x86_64.AppImage
+ ./appimagetool-x86_64.AppImage -v ${INSTALL_ROOT}
+ mv -v Q_Light_Controller_Plus-x86_64.AppImage qlcplus-${{env.TASK}}-${{env.APPVERSION}}-${{env.BUILD_DATE}}-${{env.GIT_REV}}.AppImage
+
+ - name: Test Load AppImage
+ if: ${{ matrix.task == 'compile-qt5qml' }}
+ run: |
+ ./qlcplus-${{env.TASK}}-${{env.APPVERSION}}-${{env.BUILD_DATE}}-${{env.GIT_REV}}.AppImage --platform offscreen --version
+
+ - name: Store AppImage artifacts
+ if: ${{ ! startsWith( matrix.task, 'coverage') }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.task }}-AppImage
+ path: qlcplus-${{env.TASK}}-${{env.APPVERSION}}-${{env.BUILD_DATE}}-${{env.GIT_REV}}.AppImage
+
+ build-windows:
+ runs-on: windows-latest
+ name: QLCplus Windows ${{matrix.task}}
+ strategy:
+ fail-fast: false
+ matrix:
+ task: [compile-qt5, compile-qt5-32bit, compile-qt5qml]
+ env:
+ CI_REPO_SLUG: ${{ github.repository }}
+ CI_BRANCH: ${{ github.head_ref }}
+ CI_PULL_REQUEST: ${{ github.event.number }}
+ QMAKESPEC: win32-g++
+ QT_MODULES:
+ qtscript
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false
+
+ - name: Set ENV variables
+ shell: bash
+ run: |
+ echo "CI_BRANCH=$(echo $GITHUB_REF | cut -d '/' -f 3)" >> $GITHUB_ENV
+ echo "CI_SECURE_ENV_VARS=$(if [ -z '${{ secrets.something }}' ]; then echo 'false'; else echo 'true'; fi)" >> $GITHUB_ENV
+ echo "CI_EVENT_TYPE=$(if [ 'schedule' == '${{ github.event_name }}' ]; then echo 'cron'; else echo '${{ github.event_name }}'; fi)" >> $GITHUB_ENV
+ echo "NPROC=$(nproc)" >> $GITHUB_ENV
+ echo "TASK=$(echo '${{matrix.task}}' | cut -d '-' -f 2)" >> $GITHUB_ENV
+ echo "QT=${QT:-$(echo '${{matrix.task}}' | cut -d '-' -f 2)}" >> $GITHUB_ENV
+ echo "INSTALL_ROOT=/c/" >> $GITHUB_ENV
+ echo "BUILD_DATE=`date -u '+%Y%m%d'`" >> $GITHUB_ENV
+ echo "GIT_REV=`git rev-parse --short HEAD`" >> $GITHUB_ENV
+
+ - name: Set v4 ENV variables
+ shell: bash
+ if: ${{ matrix.task == 'compile-qt5' || matrix.task == 'compile-qt5-32bit' }}
+ run: |
+ echo "OUTFILE=`grep 'OutFile' platforms/windows/qlcplus4Qt5.nsi | cut -d'"' -f 2`" >> $GITHUB_ENV
+ echo "APPVERSION=`grep '^!qmlui' variables.pri | grep APPVERSION | sed 's/^.*= *//' | cut -d ' ' -f 1`" >> $GITHUB_ENV
+ echo "NSIS_SCRIPT=qlcplus4Qt5.nsi" >> $GITHUB_ENV
+
+ - name: Set v5 ENV variables
+ shell: bash
+ if: ${{ matrix.task == 'compile-qt5qml' }}
+ run: |
+ echo "OUTFILE=`grep 'OutFile' platforms/windows/qlcplus5Qt5.nsi | cut -d'"' -f 2`" >> $GITHUB_ENV
+ echo "APPVERSION=`grep '^qmlui' variables.pri | grep APPVERSION | sed 's/^.*= *//' | cut -d ' ' -f 1`" >> $GITHUB_ENV
+ echo "NSIS_SCRIPT=qlcplus5Qt5.nsi" >> $GITHUB_ENV
+
+ - name: Print ENV vars
+ shell: bash
+ run: |
+ echo "CI_BRANCH: ${CI_BRANCH}"
+ echo "CI_PULL_REQUEST: ${CI_PULL_REQUEST}"
+ echo "CI_REPO_SLUG: ${CI_REPO_SLUG}"
+ echo "CI_EVENT_TYPE: ${CI_EVENT_TYPE}"
+ echo "CI_SECURE_ENV_VARS: ${CI_SECURE_ENV_VARS}"
+ echo "TASK: ${TASK}"
+ echo "QT: ${QT}"
+ echo "NPROC: ${NPROC}"
+
+ - name: Update and install MSYS2 (64bit)
+ uses: msys2/setup-msys2@v2
+ if: ${{ matrix.task == 'compile-qt5' || matrix.task == 'compile-qt5qml' }}
+ with:
+ msystem: mingw64
+ release: true
+ update: false
+ path-type: inherit
+ install: >-
+ wget
+ unzip
+ mingw-w64-x86_64-gcc
+ mingw-w64-x86_64-gcc-libs
+ mingw-w64-x86_64-cmake
+ mingw-w64-x86_64-libmad
+ mingw-w64-x86_64-libsndfile
+ mingw-w64-x86_64-flac
+ mingw-w64-x86_64-fftw
+ mingw-w64-x86_64-libusb
+ mingw-w64-x86_64-python-lxml
+ mingw-w64-x86_64-qt5-base
+ mingw-w64-x86_64-qt5-multimedia
+ mingw-w64-x86_64-qt5-serialport
+ mingw-w64-x86_64-qt5-script
+ mingw-w64-x86_64-qt5-tools
+ mingw-w64-x86_64-qt5-imageformats
+ mingw-w64-x86_64-qt5-svg
+ mingw-w64-x86_64-qt5-declarative
+ mingw-w64-x86_64-qt5-quickcontrols
+ mingw-w64-x86_64-qt5-quickcontrols2
+ mingw-w64-x86_64-qt5-3d
+ mingw-w64-x86_64-qt5-quick3d
+ mingw-w64-x86_64-nsis
+
+ - name: Update and install MSYS2 (32 bit)
+ uses: msys2/setup-msys2@v2
+ if: ${{ matrix.task == 'compile-qt5-32bit' }}
+ with:
+ msystem: mingw32
+ release: true
+ update: false
+ path-type: inherit
+ install: >-
+ wget
+ unzip
+ mingw-w64-i686-gcc
+ mingw-w64-i686-gcc-libs
+ mingw-w64-i686-cmake
+ mingw-w64-i686-libmad
+ mingw-w64-i686-libsndfile
+ mingw-w64-i686-flac
+ mingw-w64-i686-fftw
+ mingw-w64-i686-python-lxml
+ mingw-w64-i686-qt5-base
+ mingw-w64-i686-qt5-multimedia
+ mingw-w64-i686-qt5-serialport
+ mingw-w64-i686-qt5-script
+ mingw-w64-i686-qt5-tools
+ mingw-w64-i686-qt5-imageformats
+ mingw-w64-i686-qt5-svg
+ mingw-w64-i686-qt5-declarative
+ mingw-w64-i686-nsis
+
+ - name: Install legacy libusb (32 bit)
+ shell: msys2 {0}
+ if: ${{ matrix.task == 'compile-qt5-32bit' }}
+ run: |
+ wget https://www.qlcplus.org/misc/mingw-w64-i686-libusb-1.0.26-1-any.pkg.tar.zst
+ pacman --noconfirm --needed -U mingw-w64-i686-libusb-1.0.26-1-any.pkg.tar.zst
+
+ - name: D2XX SDK (64 bit)
+ shell: msys2 {0}
+ if: ${{ matrix.task == 'compile-qt5' || matrix.task == 'compile-qt5qml' }}
+ run: |
+ mkdir -p /c/projects/D2XXSDK
+ wget https://ftdichip.com/wp-content/uploads/2023/09/CDM-v2.12.36.4-WHQL-Certified.zip -O /c/projects/D2XXSDK/cdm.zip
+ cd /c/projects/D2XXSDK
+ unzip cdm.zip
+ cd amd64
+ gendef.exe - ftd2xx64.dll > ftd2xx.def
+ dlltool -k --input-def ftd2xx.def --dllname ftd2xx64.dll --output-lib libftd2xx.a
+
+ - name: D2XX SDK (32 bit)
+ shell: msys2 {0}
+ if: ${{ matrix.task == 'compile-qt5-32bit' }}
+ run: |
+ mkdir -p /c/projects/D2XXSDK
+ wget https://ftdichip.com/wp-content/uploads/2023/09/CDM-v2.12.36.4-WHQL-Certified.zip -O /c/projects/D2XXSDK/cdm.zip
+ cd /c/projects/D2XXSDK
+ unzip cdm.zip
+ cd i386
+ gendef.exe - ftd2xx.dll > ftd2xx.def
+ dlltool -k --input-def ftd2xx.def --dllname ftd2xx.dll --output-lib libftd2xx.a
+
+ - name: Print program versions
+ shell: msys2 {0}
+ run: |
+ echo "pwd:"
+ pwd
+ echo "CXX:"
+ which ${CXX} || true
+ ${CXX} -v || true
+ echo "cmake:"
+ which cmake || true
+ cmake --version || true
+ pkg-config --modversion libusb-1.0
+
+ - name: Fix build
+ shell: msys2 {0}
+ run: |
+ # force a release build
+ sed -i -e 's/Debug/Release/g' CMakeLists.txt
+ # disable Velleman plugin
+ sed -i -e 's/ add_subdirectory(velleman)/# add_subdirectory(velleman)/g' plugins/CMakeLists.txt
+ # fix MSYS2 system path
+ sed -i -e 's/$ENV{SystemDrive}\/msys64/D:\/a\/_temp\/msys64/g' platforms/windows/CMakeLists.txt
+ # fix project path in NSIS script
+ sed -i -e 's/c\:\\projects/d:\\a\\qlcplus/g' platforms/windows/${{env.NSIS_SCRIPT}}
+
+ - name: Fix 32 bit build
+ if: ${{ matrix.task == 'compile-qt5-32bit' }}
+ shell: msys2 {0}
+ run: |
+ # fix system libs path
+ sed -i -e 's/mingw64/mingw32/g' platforms/windows/CMakeLists.txt
+ # fix gcc lib
+ sed -i -e 's/libgcc_s_seh/libgcc_s_dw2/g' platforms/windows/CMakeLists.txt
+ # fix DMX USB path
+ sed -i -e 's/amd64/i386/g' plugins/dmxusb/src/CMakeLists.txt
+ sed -i -e 's/ftd2xx64.dll/ftd2xx.dll/g' plugins/dmxusb/src/CMakeLists.txt
+
+ - name: Configure v4 build for Windows
+ shell: msys2 {0}
+ if: ${{ matrix.task == 'compile-qt5' || matrix.task == 'compile-qt5-32bit' }}
+ run: |
+ mkdir build
+ cd build
+ cmake -G "Unix Makefiles" ..
+
+ - name: Configure v5 build for Windows
+ shell: msys2 {0}
+ if: ${{ matrix.task == 'compile-qt5qml' }}
+ run: |
+ mkdir build
+ cd build
+ cmake -G "Unix Makefiles" -Dqmlui=ON ..
+
+ - name: Build for Windows
+ shell: msys2 {0}
+ run: |
+ cd build
+ make -j${NPROC}
+
+ - name: Install on Windows
+ shell: msys2 {0}
+ run: |
+ cd build
+ make install/fast
+ cd ..
+ cp *.qm /c/qlcplus
+
+ - name: Build installation package
+ shell: msys2 {0}
+ run: |
+ cd /c/qlcplus
+ echo 'Creating package...'
+ makensis -X'SetCompressor /FINAL lzma' ${{env.NSIS_SCRIPT}}
+ mv /c/qlcplus/${{env.OUTFILE}} /d/a/qlcplus/qlcplus/${{matrix.task}}-${{env.OUTFILE}}
+
+ - name: Store executable artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: QLC+-${{matrix.task}}-${{env.APPVERSION}}-${{env.BUILD_DATE}}-${{env.GIT_REV}}.exe
+ path: ${{matrix.task}}-${{env.OUTFILE}}
+
+ build-macos:
+ runs-on: macos-12
+ name: QLCplus macOS ${{matrix.task}}
+ strategy:
+ fail-fast: false
+ matrix:
+ task: [ compile-qt5 ]
+ env:
+ CI_REPO_SLUG: ${{ github.repository }}
+ CI_BRANCH: ${{ github.head_ref }}
+ CI_PULL_REQUEST: ${{ github.event.number }}
+ QT_VERSION: "5.15.2"
+ QT_MODULES:
+ qtscript
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ submodules: false
+
+ - name: Add more ENV variables
+ shell: bash
+ run: |
+ echo "APPVERSION=`grep '^!qmlui' variables.pri | grep APPVERSION | sed 's/^.*= *//;s/ /_/g'`" >> $GITHUB_ENV
+ echo "BUILD_DATE=`date -u '+%Y%m%d'`" >> $GITHUB_ENV
+ echo "GIT_REV=`git rev-parse --short HEAD`" >> $GITHUB_ENV
+ echo "QTDIR=${{ github.workspace }}/qt/Qt/${{ env.QT_VERSION }}/clang_64" >> $GITHUB_ENV
+ echo "NPROC=`sysctl -n hw.ncpu`" >> $GITHUB_ENV
+
+ - name: Print ENV vars
+ shell: bash
+ run: |
+ echo "CI_BRANCH: ${CI_BRANCH}"
+ echo "CI_PULL_REQUEST: ${CI_PULL_REQUEST}"
+ echo "CI_REPO_SLUG: ${CI_REPO_SLUG}"
+ echo "CI_EVENT_TYPE: ${CI_EVENT_TYPE}"
+ echo "CI_SECURE_ENV_VARS: ${CI_SECURE_ENV_VARS}"
+ echo "TASK: ${TASK}"
+ echo "QTDIR: ${QTDIR}"
+ echo "NPROC: ${NPROC}"
+
+ - name: Dependencies
+ run: |
+ brew update
+ brew install fftw libftdi
+
+ - name: Install Qt
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ dir: "${{ github.workspace }}/qt/"
+ modules: ${{ env.QT_MODULES }}
+
+ - name: Configure build
+ shell: bash
+ run: |-
+ mkdir build
+ cd build
+ cmake -DCMAKE_PREFIX_PATH="${{ env.QTDIR }}/lib/cmake" ..
+
+ - name: Build
+ shell: bash
+ run: |-
+ cd build
+ make -j${{ env.NPROC }}
+
+ - name: Install
+ shell: bash
+ run: |-
+ cd build
+ make install/fast
+
+ - name: Run macosdeploy
+ shell: bash
+ run: |-
+ ${{ env.QTDIR }}/bin/macdeployqt ~/QLC+.app
+
+ - name: Fix mode deps
+ shell: bash
+ run: |-
+ install_name_tool -change /usr/local/opt/fftw/lib/libfftw3.3.dylib @executable_path/../Frameworks/libfftw3.3.dylib ~/QLC+.app/Contents/MacOS/qlcplus
+ install_name_tool -change /usr/local/opt/fftw/lib/libfftw3.3.dylib @executable_path/../Frameworks/libfftw3.3.dylib ~/QLC+.app/Contents/MacOS/qlcplus-fixtureeditor
+
+ - name: create DMG
+ shell: bash
+ run: |-
+ OUTDIR=$PWD
+ cd platforms/macos/dmg
+ ./create-dmg --volname "Q Light Controller Plus ${{env.APPVERSION}}" \
+ --volicon $OUTDIR/resources/icons/qlcplus.icns \
+ --background background.png \
+ --window-size 400 300 \
+ --window-pos 200 100 \
+ --icon-size 64 \
+ --icon "QLC+" 0 150 \
+ --app-drop-link 200 150 \
+ $OUTDIR/QLC+_${{ matrix.task }}.dmg \
+ ~/QLC+.app
+
+ - name: Store DMG artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: QLC+-${{env.APPVERSION}}-${{env.BUILD_DATE}}-${{env.GIT_REV}}.dmg
+ path: QLC+_${{ matrix.task }}.dmg
diff --git a/.gitignore b/.gitignore
index 9babd309f9..f2c2c9f3c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,3 +48,7 @@ coverage/
# Development IDEs
.vscode/
+
+# CMake build
+CMakeLists.txt.user
+build*
diff --git a/.travis-ci.sh b/.travis-ci.sh
deleted file mode 100755
index 22df5f121e..0000000000
--- a/.travis-ci.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-
-# This script is triggered from the script section of .travis.yml
-# It runs the appropriate commands depending on the task requested.
-
-export QMAKE=/opt/qt514/bin/qmake
-
-if [ "$TASK" = "coverage" ]; then
- gem install coveralls-lcov
-fi
-
-# Report the compiler version
-$CXX --version
-
-# Report the qmake version
-$QMAKE -v
-
-# Otherwise compile and check as normal
-if [ "$TASK" = "compile" ]; then
- if [ "$QT" = "qt5" ]; then
- $QMAKE QMAKE_CXX=$CXX QMAKE_CC=$CC QMAKE_LINK=$CXX QMAKE_LINK_SHLIB=$CXX && make && make check
- exit $?
- fi
- if [ "$QT" = "qt5qml" ]; then
- $QMAKE QMAKE_CXX=$CXX QMAKE_CC=$CC QMAKE_LINK=$CXX QMAKE_LINK_SHLIB=$CXX CONFIG+=qmlui && make && make check
- exit $?
- fi
-fi
-if [ "$TASK" = "coverage" ]; then
- $QMAKE CONFIG+=coverage QMAKE_CXX=$CXX QMAKE_CC=$CC QMAKE_LINK=$CXX QMAKE_LINK_SHLIB=$CXX && make && make lcov
- exit $?
-fi
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7807ccc756..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-language: cpp
-sudo: required
-
-script: ./.travis-ci.sh
-
-addons:
- apt:
- packages: &base_pkg
- #base packages for all builds
- - gdb
- - libasound2-dev
- - libusb-1.0-0-dev
- - libftdi1-dev
- - shared-mime-info
- - libudev-dev
- - libmad0-dev
- - libsndfile1-dev
- - liblo-dev
- - libfftw3-dev
- - libgl1-mesa-dev
- # Needed for `xmllint`
- - libxml2-utils
- packages: &qt5_pkg
- - *base_pkg
- - qt514-meta-minimal
- - qt514script
- - qt514multimedia
- - qt514serialport
- packages: &qmlui_pkg
- - *base_pkg
- - libpulse-dev
- - qt514-meta-minimal
- - qt514declarative
- - qt514quickcontrols2
- - qt5143d
- - qt514svg
- - qt514multimedia
- - qt514serialport
-
-matrix:
- fast_finish: true
- include:
- - os: linux
- dist: bionic
- compiler: gcc
- env: TASK='compile' QT='qt5'
- services: xvfb
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
- packages:
- - *qt5_pkg
- - os: linux
- dist: bionic
- compiler: gcc
- env: TASK='compile' QT='qt5qml'
- services: xvfb
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
- packages:
- - *qmlui_pkg
- - os: linux
- dist: bionic
- compiler: gcc
- env: TASK='coverage' QT='qt5'
- services: xvfb
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic'
- packages:
- - *qt5_pkg
- - lcov
-
-cache:
- apt: true
-
-after_success:
- - if [ "$TASK" = "coverage" ]; then coveralls-lcov --repo-token ${COVERALLS_TOKEN} coverage/coverage.info; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000000..874b02f6dc
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,221 @@
+cmake_minimum_required(VERSION 3.16)
+project(qlcplus VERSION 4.13.1 LANGUAGES C CXX)
+
+# Set Release build type by default
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+endif()
+
+# Prevent CMake make install strips off non-standard build paths
+# Refer to https://stackoverflow.com/questions/7970544/cmake-make-install-output-cant-find-shared-qt-libraries-under-redhat
+SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+
+SET(INSTALL_ROOT "/" CACHE STRING "Installation root directory")
+
+if(UNIX)
+ if (APPLE)
+ set(iokit ON)
+ else()
+ set(udev ON)
+ endif()
+endif()
+
+if (ANDROID OR IOS)
+ set(qmlui ON)
+endif()
+
+if (ANDROID)
+ if(QT_VERSION_MAJOR GREATER 5)
+ set(QT_ANDROID_PACKAGE_SOURCE_DIR ${PROJECT_SOURCE_DIR}/platforms/android CACHE INTERNAL "")
+ else()
+ set(ANDROID_PACKAGE_SOURCE_DIR ${PROJECT_SOURCE_DIR}/platforms/android CACHE INTERNAL "")
+ endif()
+endif()
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+# Set up AUTOMOC and some sensible defaults for runtime execution
+# When using Qt 6.3, you can replace the code block below with
+# qt_standard_project_setup()
+set(CMAKE_AUTOMOC ON)
+include(GNUInstallDirs)
+
+find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)
+find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Multimedia MultimediaWidgets Network PrintSupport Qml Quick Svg Test Widgets LinguistTools)
+if(qmlui)
+ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS 3DCore 3DInput 3DQuick 3DQuickExtras 3DRender)
+ if(ANDROID)
+ find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Concurrent OpenGL)
+ endif()
+endif()
+
+message("Found Qt version ${QT_VERSION_MAJOR}: ${QT_DIR}")
+
+if(QT_VERSION_MAJOR EQUAL 5)
+ find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS Script)
+endif()
+
+find_package(PkgConfig REQUIRED)
+
+include(./variables.cmake)
+include(./coverage.cmake)
+
+if((((QT_VERSION_MAJOR LESS 5)) AND (APPLE)))
+ pkg_check_modules(PORTAUDIO_2 IMPORTED_TARGET portaudio-2.0)
+endif()
+
+if(WIN32)
+ # Prefix all shared libraries with ''.
+ set(CMAKE_SHARED_LIBRARY_PREFIX "")
+endif()
+
+add_subdirectory(hotplugmonitor)
+add_subdirectory(engine)
+add_subdirectory(resources)
+add_subdirectory(plugins)
+if(qmlui)
+ message("Building QLC+ 5 QML UI")
+ add_subdirectory(qmlui)
+else()
+ message("Building QLC+ 4 QtWidget UI")
+ add_subdirectory(ui)
+ add_subdirectory(webaccess)
+ add_subdirectory(main)
+ add_subdirectory(fixtureeditor)
+endif()
+if(APPLE AND NOT qmlui)
+ add_subdirectory(launcher)
+endif()
+
+# Unit testing thru "make check"
+if(qmlui)
+ if(WIN32)
+ add_custom_target(unittests
+ COMMAND unittest_cmake.bat "qmlui" ${CMAKE_CURRENT_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ else()
+ add_custom_target(unittests
+ COMMAND ./unittest_cmake.sh "qmlui" ${CMAKE_CURRENT_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ endif()
+else()
+ if(WIN32)
+ add_custom_target(unittests
+ COMMAND unittest_cmake.bat "ui" ${CMAKE_CURRENT_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ else()
+ add_custom_target(unittests
+ COMMAND ./unittest_cmake.sh "ui" ${CMAKE_CURRENT_BINARY_DIR}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ endif()
+endif()
+
+add_custom_target(check
+ DEPENDS unittests
+)
+
+# Unit test coverage measurement
+if(WIN32)
+ add_custom_target(coverage
+ COMMAND @echo Get a better OS.
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+else()
+ if(qmlui)
+ add_custom_target(coverage
+ COMMAND ./coverage_cmake.sh "qmlui"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ else()
+ add_custom_target(coverage
+ COMMAND ./coverage_cmake.sh "ui"
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+ endif()
+endif()
+
+add_custom_target(lcov
+ DEPENDS coverage
+)
+
+# Translations
+if (qmlui)
+ add_custom_target(translations ALL
+ COMMAND ./translate.sh "qmlui"
+ COMMENT "Translating qmlui translations..."
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+else ()
+ add_custom_target(translations ALL
+ COMMAND ./translate.sh "ui"
+ COMMENT "Translating ui translations..."
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+endif()
+
+## Add the generated qm files to the list
+file(GLOB translations_qm_files "${CMAKE_CURRENT_SOURCE_DIR}/*.qm")
+
+## Install the qm files
+if (appimage)
+ set(translations_install_path "${TARGET_DIR}/${INSTALLROOT}/${TRANSLATIONDIR}")
+else ()
+ set(translations_install_path "${INSTALLROOT}/${TRANSLATIONDIR}")
+endif()
+
+install(FILES ${translations_qm_files} DESTINATION ${translations_install_path})
+
+## Clean the generated files
+set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${CMAKE_CURRENT_BINARY_DIR}/*.qm")
+
+# run
+if(UNIX)
+ if(qmlui)
+ add_custom_target(run
+ COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=engine/src:$ENV{LD_LIBRARY_PATH} qmlui/qlcplus-qml
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ else()
+ add_custom_target(run
+ COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=engine/src:ui/src:webaccess/src:$ENV{LD_LIBRARY_PATH} main/qlcplus
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ endif()
+endif()
+
+# run-fxe
+if(UNIX AND NOT qmlui)
+ add_custom_target(run-fxe
+ COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=engine/src:ui/src:webaccess/src:$ENV{LD_LIBRARY_PATH} ./fixtureeditor/qlcplus-fixtureeditor
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+ )
+endif()
+
+# doxygen
+if(UNIX)
+ add_custom_target(doxygen
+ COMMAND cd resources/doxygen && rm -rf html/ && doxygen qlcplus.dox
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+endif()
+
+# uninstall target
+if(NOT TARGET uninstall)
+ configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+ IMMEDIATE @ONLY)
+
+ add_custom_target(uninstall
+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
+endif()
+
+SET(CPACK_GENERATOR "DEB")
+set(CPACK_PACKAGE_NAME "qlcplus")
+SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Massimo Callegari") #required
+INCLUDE(CPack)
+
+# Leave this on the last row of this file
+add_subdirectory(platforms)
diff --git a/README.md b/README.md
index ed1a5f690c..7586247a78 100644
--- a/README.md
+++ b/README.md
@@ -1,106 +1,111 @@
-Q Light Controller Plus 4
-=========================
-
-![QLC+ LOGO](resources/icons/png/qlcplus.png)
-
-Copyright (c) Heikki Junnila, Massimo Callegari
-
-QLC+ homepage: https://www.qlcplus.org/
-
-QLC+ on GitHub: https://github.com/mcallegari/qlcplus
-
-DEVELOPERS AT WORK
-------------------
-
-If you're compiling QLC+ from sources and you regularly do "git pull"
-to get the latest sources, you probably end up seeing some
-compiler warnings and errors from time to time. Since the whole source package
-is under development, you might even encounter unresolved symbols etc. that
-halt the compiler immediately. If such a thing occurs, you should do a "make
-distclean" on qlcplus (top-most source directory) and then "qmake" and "make"
-again. We attempt to keep the GIT master free of fatal errors and it should
-compile all the time. However, some inter-object dependencies do get mixed up
-sometimes and you need to compile the whole package instead of just the latest
-changes. Sometimes even that doesn't work, because QLC+ installs its common
-libraries to system directories, where (at least unixes) fetch them instead
-of the source directory. In those cases, you might try going to the libs
-directory, compile it with "make" and install with "make install" and then
-attempt to re-compile the whole package with "make".
-
-Apache 2.0 License
-------------------
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-Requirements - Linux
---------------------
-
-* Qt >= 5.0 development libraries & tools
-* libudev-dev, libmad0-dev, libsndfile1-dev, libfftw3-dev
-* DMX USB plugin: libftdi-dev, pkg-config
-* HID plugin: No additional requirements
-* MIDI plugin: libasound, libasound-dev, pkg-config
-* ENTTEC Wing plugin: No additional requirements
-* OLA plugin: libola, ola-dev, pkg-config (see libs/olaout/README)
-* uDMX plugin: libusb, libusb-dev, pkg-config
-* Peperoni plugin: libusb, libusb-dev, pkg-config
-* Velleman plugin: Not available for Linux
-* OSC plugin: No additional requirements
-* ArtNet plugin: No additional requirements
-* E1.31 plugin: No additional requirements
-* Loopback plugin: No additional requirements
-
-Requirements - Windows
-----------------------
-
-* MSYS2 environment (https://msys2.github.io/)
-* DMX USB plugin: D2XX driver & development package (http://www.ftdichip.com/Drivers/D2XX.htm)
-* HID plugin: No additional requirements
-* MIDI plugin: No additional requirements
-* ENTTEC Wing plugin: D2XX driver & development package (http://www.ftdichip.com/Drivers/D2XX.htm)
-* OLA plugin: Not available
-* uDMX plugin: No additional requirements
-* Peperoni plugin: No additional requirements
-* Velleman plugin: K8062 SDK from www.velleman.eu
-* OSC plugin: No additional requirements
-* ArtNet plugin: No additional requirements
-* E1.31 plugin: No additional requirements
-* Loopback plugin: No additional requirements
-
-Requirements - Mac OS X
------------------------
-
-* XCode (http://developer.apple.com/technologies/tools/xcode.html)
-* Qt >= 5.0.x (http://download.qt.io/official_releases/qt/)
-* macports (https://www.macports.org/)
-* DMX USB plugin: macports, libftdi-dev, pkg-config
-* HID plugin: No additional requirements
-* MIDI plugin: No additional requirements
-* ENTTEC Wing plugin: No additional requirements
-* OLA plugin: libola, ola-dev, pkg-config (see libs/olaout/README)
-* uDMX plugin: macports, libusb-compat, pkg-config
-* Peperoni plugin: macports, libusb-compat, pkg-config
-* Velleman plugin: Not available
-* OSC plugin: No additional requirements
-* ArtNet plugin: No additional requirements
-* E1.31 plugin: No additional requirements
-* Loopback plugin: No additional requirements
-
-Compiling & Installation
-------------------------
+
+
+
+
+# Q Light Controller Plus
+[![GitHub release](https://img.shields.io/github/v/release/mcallegari/qlcplus)
+![GitHub Release Date - Published_At](https://img.shields.io/github/release-date/mcallegari/qlcplus)](https://github.com/mcallegari/qlcplus/releases/latest)
+
+https://www.qlcplus.org/download
+
+## Introduction
+QLC+ is powerful and user-friendly software designed to control lighting. Whether you're an experienced lighting professional or just getting started, QLC+ empowers you to take control of your lighting fixtures with ease. The primary goal of this project is to bring QLC+ to the level of available commercial software.
+QLC+ runs on Linux, Windows (7+), macOS (10.12+) and the Raspberry Pi.
+
+Copyright © Heikki Junnila, Massimo Callegari
+### Supported Protocols
+[![MIDI](https://img.shields.io/badge/MIDI-%23323330.svg?style=for-the-badge&logo=midi&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/midi)
+[![OSC](https://img.shields.io/badge/OSC-%23323330.svg?style=for-the-badge&logo=aiohttp&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/osc)
+[![HID](https://img.shields.io/badge/HID-%23323330.svg?style=for-the-badge&logo=applearcade&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/hid)
+[![DMX](https://img.shields.io/badge/DMX-%23323330.svg?style=for-the-badge&logo=amazonec2&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/dmx-usb)
+[![ArtNet](https://img.shields.io/badge/ArtNet-%23323330.svg?style=for-the-badge&logo=aiohttp&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/art-net)
+[![E1.31/S.ACN](https://img.shields.io/badge/E1.31%20S.ACN-%23323330.svg?style=for-the-badge&logo=aiohttp&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/e1-31-sacn)
+[![OS2L](https://img.shields.io/badge/OS2L-%23323330.svg?style=for-the-badge&logo=aiohttp&logoColor=%23F7DF1E)](https://docs.qlcplus.org/v4/plugins/os2l)
+
+### Key Resources
+
+
+
+
+
+Home Page |
+ |
+
+
+Documentation |
+ |
+
+
+Official Forum |
+ |
+
+
+GitHub Sponsorship |
+ |
+
+
+Official Merch |
+ |
+
+
+
+
+### QLC+ Social Media
+
+[![Instagram](https://img.shields.io/badge/Instagram-%23E4405F.svg?style=for-the-badge&logo=Instagram&logoColor=white)](https://www.instagram.com/qlcplus/) [![YouTube](https://img.shields.io/badge/YouTube-%23FF0000.svg?style=for-the-badge&logo=YouTube&logoColor=white)](https://www.youtube.com/watch?v=I9bccwcYQpM&list=PLHT-wIriuitDiW4A9oKSDr__Z_jcmMVdi) [![Facebook](https://img.shields.io/badge/Facebook-%231877F2.svg?style=for-the-badge&logo=Facebook&logoColor=white)](https://www.facebook.com/qlcplus)
+
+## Contributing
+We welcome contributions from the community to help make QLC+ even better. Before diving into coding, we encourage you to start a discussion in our [Software Development](https://www.qlcplus.org/forum/viewforum.php?f=12) forum if you're considering adding a new feature or making significant changes. This provides an opportunity for feedback, collaboration, and ensuring alignment with the project's goals.
+
+Further guidelines are available in the [CONTRIBUTING.md](CONTRIBUTING.md) document.
+
+### Help wanted
+Click the badge below to see the currently confirmed issues with QLC+. Perhaps you can find a solution?
+
+[![GitHub issues by-label](https://img.shields.io/github/issues/mcallegari/qlcplus/issue%20confirmed?logo=github&color=red)](https://github.com/mcallegari/qlcplus/issues?q=is%3Aopen+is%3Aissue+label%3A%22issue+confirmed%22)
+
+### 🚧 Developers at work 🚧
+
+If you're regularly updating QLC+ sources with git pull, you may encounter compiler warnings, errors, or unresolved symbols. This is because the source package is still in development. We strive to keep the GIT master branch free of critical errors; However, dependencies between objects can sometimes cause issues, requiring a full package recompilation rather than just updating recent changes.
+
+[![QLC+ Github Actions CI Build](https://github.com/mcallegari/qlcplus/actions/workflows/build.yml/badge.svg)](https://github.com/mcallegari/qlcplus/actions) [![Coverage Status](https://coveralls.io/repos/github/mcallegari/qlcplus/badge.svg?branch=master)](https://coveralls.io/github/mcallegari/qlcplus?branch=master)
+[![GitHub commits since latest release (by SemVer including pre-releases)](https://img.shields.io/github/commits-since/mcallegari/qlcplus/latest/master)](https://github.com/mcallegari/qlcplus/commits/master/) ![GitHub commit activity (branch)](https://img.shields.io/github/commit-activity/w/mcallegari/qlcplus)
+
+## Compiling and installation
Please refer to the online wiki pages: https://github.com/mcallegari/qlcplus/wiki
-
-Support & Bug Reports
----------------------
+## Requirements
+### Linux
+
+* Qt >= 5.0 development libraries & tools
+* libudev-dev, libmad0-dev, libsndfile1-dev, libfftw3-dev
+* DMX USB plugin: libftdi-dev, pkg-config
+* MIDI plugin: libasound, libasound-dev, pkg-config
+* OLA plugin: libola, ola-dev, pkg-config (see libs/olaout/README)
+* uDMX plugin: libusb, libusb-dev, pkg-config
+* Peperoni plugin: libusb, libusb-dev, pkg-config
+* Velleman plugin: **Not available**
+
+### Windows
+
+* MSYS2 environment (https://msys2.github.io/)
+* DMX USB plugin: D2XX driver & development package (http://www.ftdichip.com/Drivers/D2XX.htm)
+* ENTTEC Wing plugin: D2XX driver & development package (http://www.ftdichip.com/Drivers/D2XX.htm)
+* OLA plugin: **Not available**
+* Velleman plugin: K8062 SDK from www.velleman.eu
+
+### macOS
+
+* XCode (https://developer.apple.com/xcode/)
+* Qt >= 5.15.x (https://www.qt.io/download-open-source)
+* homebrew (https://brew.sh/)
+* DMX USB plugin: libftdi, pkg-config
+* OLA plugin: ola, pkg-config (see libs/olaout/README)
+* uDMX plugin: libusb, pkg-config
+* Peperoni plugin: libusb, pkg-config
+* Velleman plugin: **Not available**
+
+## Support & Bug Reports
For discussions, feedbacks, ideas and new fixtures, go to:
https://www.qlcplus.org/forum/index.php
@@ -108,57 +113,71 @@ https://www.qlcplus.org/forum/index.php
For developers wiki and code patches, go to:
https://github.com/mcallegari/qlcplus
-Contributors
-------------
-
-### QLC+ 5:
-
-* Eric Arnebäck (3D preview features)
-* Santiago Benejam Torres (Catalan translation)
-* Luis García Tornel (Spanish translation)
-* Nils Van Zuijlen, Jérôme Lebleu (French translation)
-* Felix Edelmann, Florian Edelmann (fixture definitions, German translation)
-* Jannis Achstetter (German translation)
-* Dai Suetake (Japanese translation)
-* Hannes Bossuyt (Dutch translation)
-* Aleksandr Gusarov (Russian translation)
-* Vadim Syniuhin (Ukrainian translation)
-* Mateusz Kędzierski (Polish translation)
-
-### QLC+ 4:
-
-* Jano Svitok (bugfix, new features and improvements)
-* David Garyga (bugfix, new features and improvements)
-* Lukas Jähn (bugfix, new features)
-* Robert Box (fixtures review)
-* Thomas Achtner (ENTTEC wing improvements)
-* Joep Admiraal (MIDI SysEx init messages, Dutch translation)
-* Florian Euchner (FX5 USB DMX support)
-* Stefan Riemens (new features)
-* Bartosz Grabias (new features)
-* Simon Newton, Peter Newman (OLA plugin)
-* Janosch Frank (webaccess improvements)
-* Karri Kaksonen (DMX USB Eurolite USB DMX512 Pro support)
-* Stefan Krupop (HID DMXControl Projects e.V. Nodle U1 support)
-* Nathan Durnan (RGB scripts, new features)
-* Giorgio Rebecchi (new features)
-* Florian Edelmann (code cleanup, German translation)
-* Heiko Fanieng, Jannis Achstetter (German translation)
-* NiKoyes, Jérôme Lebleu, Olivier Humbert, Nils Van Zuijlen (French translation)
-* Raymond Van Laake (Dutch translation)
-* Luis García Tornel (Spanish translation)
-* Jan Lachman (Czech translation)
-* Nuno Almeida, Carlos Eduardo Porto de Oliveira (Portuguese translation)
-* Santiago Benejam Torres (Catalan translation)
-* Koichiro Saito, Dai Suetake (Japanese translation)
-
-### QLC:
-
-* Stefan Krumm (Bugfixes, new features)
-* Christian Suehs (Bugfixes, new features)
-* Christopher Staite (Bugfixes)
-* Klaus Weidenbach (Bugfixes, German translation)
-* Lutz Hillebrand (uDMX plugin)
-* Matthew Jaggard (Velleman plugin)
-* Ptit Vachon (French translation)
-
+## Contributors
+
+QLC+ owes its success to the dedication and expertise of numerous individuals who have generously contributed their time and skills. The following list recognizes those whose remarkable contributions have played a pivotal role in shaping QLC+ into what it is today.
+
+![GitHub contributors](https://img.shields.io/github/contributors/mcallegari/qlcplus)
+### QLC+ 5
+
+* Eric Arnebäck (3D preview features)
+* Santiago Benejam Torres (Catalan translation)
+* Luis García Tornel (Spanish translation)
+* Nils Van Zuijlen, Jérôme Lebleu (French translation)
+* Felix Edelmann, Florian Edelmann (fixture definitions, German translation)
+* Jannis Achstetter (German translation)
+* Dai Suetake (Japanese translation)
+* Hannes Bossuyt (Dutch translation)
+* Aleksandr Gusarov (Russian translation)
+* Vadim Syniuhin (Ukrainian translation)
+* Mateusz Kędzierski (Polish translation)
+
+### QLC+ 4
+
+* Jano Svitok (bugfix, new features and improvements)
+* David Garyga (bugfix, new features and improvements)
+* Lukas Jähn (bugfix, new features)
+* Robert Box (fixtures review)
+* Thomas Achtner (ENTTEC wing improvements)
+* Joep Admiraal (MIDI SysEx init messages, Dutch translation)
+* Florian Euchner (FX5 USB DMX support)
+* Stefan Riemens (new features)
+* Bartosz Grabias (new features)
+* Simon Newton, Peter Newman (OLA plugin)
+* Janosch Frank (webaccess improvements)
+* Karri Kaksonen (DMX USB Eurolite USB DMX512 Pro support)
+* Stefan Krupop (HID DMXControl Projects e.V. Nodle U1 support)
+* Nathan Durnan (RGB scripts, new features)
+* Giorgio Rebecchi (new features)
+* Florian Edelmann (code cleanup, German translation)
+* Heiko Fanieng, Jannis Achstetter (German translation)
+* NiKoyes, Jérôme Lebleu, Olivier Humbert, Nils Van Zuijlen (French translation)
+* Raymond Van Laake (Dutch translation)
+* Luis García Tornel (Spanish translation)
+* Jan Lachman (Czech translation)
+* Nuno Almeida, Carlos Eduardo Porto de Oliveira (Portuguese translation)
+* Santiago Benejam Torres (Catalan translation)
+* Koichiro Saito, Dai Suetake (Japanese translation)
+
+### Q Light Controller
+
+* Stefan Krumm (Bugfixes, new features)
+* Christian Suehs (Bugfixes, new features)
+* Christopher Staite (Bugfixes)
+* Klaus Weidenbach (Bugfixes, German translation)
+* Lutz Hillebrand (uDMX plugin)
+* Matthew Jaggard (Velleman plugin)
+* Ptit Vachon (French translation)
+
+
+
+
+
+## Apache 2.0
+![GitHub License](https://img.shields.io/github/license/mcallegari/qlcplus)
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+
+----
+![C++](https://img.shields.io/badge/c++-%2300599C.svg?style=for-the-badge&logo=c%2B%2B&logoColor=white) ![Qt](https://img.shields.io/badge/Qt-%23217346.svg?style=for-the-badge&logo=Qt&logoColor=white) ![CMake](https://img.shields.io/badge/CMake-%23008FBA.svg?style=for-the-badge&logo=cmake&logoColor=white) ![JavaScript](https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E)
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index dfd8c658f6..0000000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-version: 4.12.5.{build}
-
-image: Visual Studio 2019
-
-environment:
- MSYSTEM: MSYS
- PATH: C:\msys64\mingw32\bin;C:\msys64\usr\bin;C:\Windows\System32;C:\Windows;%PATH%
- QMAKESPEC: win32-g++
-
-platform:
- - x86
-
-configuration:
- - Release
-
-install:
- #- pacman --noconfirm -Sy
- #- pacman --noconfirm --needed -S pacman-mirrors
- - pacman --noconfirm -Syu
- - pacman --noconfirm --needed -Sy unzip mingw32/mingw-w64-i686-libmad mingw32/mingw-w64-i686-libsndfile mingw32/mingw-w64-i686-flac mingw32/mingw-w64-i686-fftw mingw32/mingw-w64-i686-libusb mingw32/mingw-w64-i686-nsis
- - wget http://repo.msys2.org/mingw/i686/mingw-w64-i686-gcc-libs-11.3.0-2-any.pkg.tar.zst -P /c/projects
- - wget http://repo.msys2.org/mingw/i686/mingw-w64-i686-gcc-11.3.0-2-any.pkg.tar.zst -P /c/projects
- - wget http://www.qlcplus.org/misc/mingw-w64-i686-qt5-5.15.2-7-any.pkg.tar.zst -P /c/projects
- - pacman --noconfirm -Rdd mingw-w64-i686-gcc
- - pacman --noconfirm -Rdd mingw-w64-i686-gcc-libs
- - pacman --noconfirm --needed -U /c/projects/mingw-w64-i686-gcc-libs-11.3.0-2-any.pkg.tar.zst
- - pacman --noconfirm --needed -U /c/projects/mingw-w64-i686-gcc-11.3.0-2-any.pkg.tar.zst
- - pacman --noconfirm --needed -U /c/projects/mingw-w64-i686-qt5-5.15.2-7-any.pkg.tar.zst
-
-build_script:
- - ps: >-
- bash -c @'
- set -e
- # stdin seems to be invalid on appveyor, so set it to null
- exec 0&1
- export
- gcc -v
- qmake -v
- # get and prepare the D2XX SDK
- mkdir -p /c/Qt/D2XXSDK
- wget http://www.ftdichip.com/Drivers/CDM/CDM%20v2.12.36.4%20WHQL%20Certified.zip -O /c/Qt/D2XXSDK/cdm.zip
- cd /c/Qt/D2XXSDK
- unzip cdm.zip
- cd i386
- gendef.exe - ftd2xx.dll > ftd2xx.def
- dlltool -k --input-def ftd2xx.def --dllname ftd2xx.dll --output-lib libftd2xx.a
- cd /c/projects/qlcplus
- # disable the test units, since we won't run them
- sed -i -e 's/ SUBDIRS += test/#SUBDIRS += test/g' engine/engine.pro
- sed -i -e 's/SUBDIRS += test/#SUBDIRS += test/g' ui/ui.pro
- sed -i -e 's/ SUBDIRS += velleman/#SUBDIRS += velleman/g' plugins/plugins.pro
- sed -i -e 's/ SUBDIRS += test/#SUBDIRS += test/g' plugins/artnet/artnet.pro
- sed -i -e 's/SUBDIRS += test/#SUBDIRS += test/g' plugins/enttecwing/enttecwing.pro
- sed -i -e 's/SUBDIRS += test/#SUBDIRS += test/g' plugins/midi/midi.pro
- qmake FORCECONFIG=release
- make
- echo 'Silently installing QLC+...'
- make install -s
- cp *.qm /c/qlcplus
- cd /c/qlcplus
- echo 'Workaround Harfbuzz bug'
- rm libharfbuzz-0.dll
- wget http://www.qlcplus.org/misc/libharfbuzz-0.dll
- sed -i -e 's/Qt/projects/g' qlcplus4Qt5.nsi
- echo 'Creating package...'
- makensis -X'SetCompressor /FINAL lzma' qlcplus4Qt5.nsi
- #set CURRDATE=`date +%Y%m%d`
- mv QLC+_*.exe /c/projects/qlcplus/QLC+_$APPVEYOR_BUILD_VERSION.exe
- ls /c/projects/qlcplus/*.exe
- exit 0
- '@
-
-artifacts:
- - path: QLC+_$(APPVEYOR_BUILD_VERSION).exe
- name: qlcplus_4_12_5
diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in
new file mode 100644
index 0000000000..c2d34d4796
--- /dev/null
+++ b/cmake_uninstall.cmake.in
@@ -0,0 +1,21 @@
+if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt")
+ message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt")
+endif()
+
+file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files)
+string(REGEX REPLACE "\n" ";" files "${files}")
+foreach(file ${files})
+ message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
+ if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+ exec_program(
+ "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
+ OUTPUT_VARIABLE rm_out
+ RETURN_VALUE rm_retval
+ )
+ if(NOT "${rm_retval}" STREQUAL 0)
+ message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
+ endif()
+ else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+ message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
+ endif()
+endforeach()
diff --git a/coverage.cmake b/coverage.cmake
new file mode 100644
index 0000000000..f14ad4e4a3
--- /dev/null
+++ b/coverage.cmake
@@ -0,0 +1,6 @@
+if(coverage)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov")
+ file(GLOB_RECURSE gcov_files "${CMAKE_BINARY_DIR}/*.gcno" "${CMAKE_BINARY_DIR}/*.gcda")
+ set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${gcov_files})
+endif()
diff --git a/coverage.sh b/coverage.sh
index f02214cb06..e86a3f4184 100755
--- a/coverage.sh
+++ b/coverage.sh
@@ -5,7 +5,9 @@
# 1. qmake
# 2. make distclean
# 3. qmake CONFIG+=coverage
-# 4. ./coverage.sh
+# 4. make -j8
+# 5. ./coverage.sh ui|qmlui # OR
+# 5. make lcov
#
# Human-readable HTML results are written under coverage/html.
#
@@ -54,6 +56,7 @@ tlen=${#test[@]}
# arg1:srcdir arg2:testname
function prepare {
lcov -d ${1} -z || exit $?
+ rm -f ${1}/moc_*.gcno ${1}/moc_*.gcda
lcov -d ${1} -c -i -o coverage/${2}-base.info
}
diff --git a/coverage_cmake.sh b/coverage_cmake.sh
new file mode 100755
index 0000000000..095807ef02
--- /dev/null
+++ b/coverage_cmake.sh
@@ -0,0 +1,136 @@
+#!/bin/bash
+#
+# To measure unit test coverage, perform these steps:
+# 0. export CCACHE_DISABLE=1 # (only if you use compiler cache)
+# 1. rm -rf build && mkdir build # Remove original build directory and recreate it. You can skip this step if you want to preserve the build directory.
+# 2. cd ./build && cmake -DCMAKE_PREFIX_PATH="/home//Qt/5.15.2/gcc_64/lib/cmake|/usr/lib/x86_64-linux-gnu/cmake/Qt5" [-Dqmlui=ON] -Dcoverage=ON ..
+# 3. make -j8
+# 4. make lcov
+#
+# Human-readable HTML results are written under coverage/html.
+#
+
+set -e
+ARCH=$(uname)
+THISCMD=`basename "$0"`
+
+TARGET=${1:-}
+
+if [ "$TARGET" != "ui" ] && [ "$TARGET" != "qmlui" ]; then
+ echo >&2 "Usage: $THISCMD ui|qmlui"
+ exit 1
+fi
+
+
+#############################################################################
+# Test directories to find coverage measurements from
+#############################################################################
+
+DEST_DIR="build" # Do NOT change to "./build" or "build/"
+
+COUNT=0
+test[$COUNT]="$DEST_DIR/engine/src"
+COUNT=$((COUNT+1))
+if [ "$TARGET" == "ui" ]; then
+ test[$COUNT]="$DEST_DIR/ui/src"
+COUNT=$((COUNT+1))
+fi
+test[$COUNT]="$DEST_DIR/plugins/artnet/test"
+COUNT=$((COUNT+1))
+test[$COUNT]="$DEST_DIR/plugins/enttecwing/src"
+COUNT=$((COUNT+1))
+#test[$COUNT]="$DEST_DIR/plugins/midiinput/common/src"
+#COUNT=$((COUNT+1))
+if [ ${ARCH} != "Darwin" ]; then
+ test[$COUNT]="$DEST_DIR/plugins/velleman/src"
+ COUNT=$((COUNT+1))
+fi
+
+# Number of tests
+tlen=${#test[@]}
+
+#############################################################################
+# Functions
+#############################################################################
+
+# arg1:srcdir arg2:testname
+function prepare {
+ lcov -d ${1} -z || exit $?
+ lcov -d ${1} -c -i -o coverage/${2}-base.info
+}
+
+# arg1:srcdir arg2:testname
+function gather_data {
+ lcov -d ${1} -c -o coverage/${2}-test.info
+ lcov -a coverage/${2}-base.info -a coverage/${2}-test.info \
+ -o coverage/${2}-merge.info
+}
+
+#############################################################################
+# Initialization
+#############################################################################
+
+# Check if lcov is installed
+if [ -z "$(which lcov)" ]; then
+ echo "Unable to produce coverage results; can't find lcov."
+fi
+
+# Remove previous data
+if [ -d coverage ]; then
+ rm -rf coverage
+fi
+
+# Create directories for new coverage data
+mkdir -p coverage/html
+
+#############################################################################
+# Preparation
+#############################################################################
+
+for ((i = 0; i < tlen; i++))
+do
+ prepare ${test[i]} $i || exit $?
+done
+
+#############################################################################
+# Run unit tests
+#############################################################################
+
+./unittest_cmake.sh $TARGET
+FAILED=$?
+if [ ${FAILED} != 0 ]; then
+ echo "Will not measure coverage because ${FAILED} unit tests failed."
+ exit ${FAILED}
+fi
+
+#############################################################################
+# Gather results
+#############################################################################
+
+for ((i = 0; i < tlen; i++))
+do
+ gather_data ${test[i]} $i
+done
+
+#############################################################################
+# All combined and HTMLized
+#############################################################################
+
+for ((i = 0; i < tlen; i++))
+do
+ mergeargs="${mergeargs} -a coverage/${i}-merge.info"
+done
+
+lcov ${mergeargs} -o coverage/coverage.info
+
+# Remove stuff that isn't part of QLC sources
+lcov -r coverage/coverage.info *.h -o coverage/coverage.info # Q_OBJECT etc.
+lcov -r coverage/coverage.info *moc_* -o coverage/coverage.info
+lcov -r coverage/coverage.info *usr* -o coverage/coverage.info
+lcov -r coverage/coverage.info *_test* -o coverage/coverage.info
+lcov -r coverage/coverage.info */ui_* -o coverage/coverage.info
+lcov -r coverage/coverage.info */$DEST_DIR/* -o coverage/coverage.info
+lcov -r coverage/coverage.info *Library* -o coverage/coverage.info # OSX
+
+# Generate HTML report
+genhtml -o coverage/html coverage/coverage.info
diff --git a/create-appimage-cmake.sh b/create-appimage-cmake.sh
new file mode 100755
index 0000000000..66312c2211
--- /dev/null
+++ b/create-appimage-cmake.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Script to create a self contained AppImage using CMake
+# Requires wget and chrpath
+# If you want to use the official Qt packages, please export QTDIR before running this, like:
+# export QTDIR=/home/user/Qt/5.15.2/gcc_64
+# Or you can use the system Qt libraries instead by not specifying QTDIR.
+
+# Exit on error
+set -e
+
+TARGET_DIR=$HOME/qlcplus.AppDir
+
+# Compile translations
+./translate.sh "qmlui"
+
+# Build
+if [ -d build ]; then
+ rm -rf build
+fi
+mkdir build
+cd build
+
+if [ -n "$QTDIR" ]; then
+ cmake -DCMAKE_PREFIX_PATH="$QTDIR/lib/cmake/" -Dqmlui=ON -Dappimage=ON -DINSTALL_ROOT=$TARGET_DIR ..
+else
+ cmake -DCMAKE_PREFIX_PATH="/usr/lib/x86_64-linux-gnu/cmake/Qt5" -Dqmlui=ON -Dappimage=ON -DINSTALL_ROOT=$TARGET_DIR ..
+fi
+
+NUM_CPUS=$(nproc) || true
+if [ -z "$NUM_CPUS" ]; then
+ NUM_CPUS=8
+fi
+
+make -j$NUM_CPUS
+make check
+
+if [ ! -d "$TARGET_DIR" ]; then
+ mkdir $TARGET_DIR
+fi
+make install
+
+strip $TARGET_DIR/usr/bin/qlcplus-qml
+# see variables.pri, where to find the LIBSDIR
+find $TARGET_DIR/usr/lib/ -name 'libqlcplusengine.so*' -exec strip -v {} \;
+
+# FIXME: no rpath or runpath tag found.
+chrpath -r "../lib" $TARGET_DIR/usr/bin/qlcplus-qml || true
+
+pushd $TARGET_DIR/usr/bin
+find . -name plugins.qmltypes -type f -delete
+find . -name *.qmlc -type f -delete
+rm -rf QtQuick/Extras QtQuick/Particles.2 QtQuick/XmlListModel
+rm -rf QtQuick/Controls.2/designer QtQuick/Controls.2/Material
+rm -rf QtQuick/Controls.2/Universal QtQuick/Controls.2/Fusion
+rm -rf QtQuick/Controls.2/Imagine QtQuick/Controls.2/Scene2D
+popd
+
+# There might be a new version of the tool available.
+wget -c https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-x86_64 -O $TARGET_DIR/AppRun
+chmod a+x $TARGET_DIR/AppRun
+
+cp -v ../resources/icons/svg/qlcplus.svg $TARGET_DIR
+cp -v ../platforms/linux/qlcplus.desktop $TARGET_DIR
+sed -i -e 's/Exec=qlcplus --open %f/Exec=qlcplus-qml/g' $TARGET_DIR/qlcplus.desktop
+
+# There might be a new version of the tool available.
+wget -c https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O /tmp/appimagetool-x86_64.AppImage
+chmod a+x /tmp/appimagetool-x86_64.AppImage
+
+pushd $TARGET_DIR/..
+/tmp/appimagetool-x86_64.AppImage -v $TARGET_DIR
+popd
+
+echo "The application is now available at ~/Q_Light_Controller_Plus-x86_64.AppImage"
diff --git a/create-dmg-cmake.sh b/create-dmg-cmake.sh
new file mode 100755
index 0000000000..7f028a039a
--- /dev/null
+++ b/create-dmg-cmake.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#VERSION=$(head -1 debian/changelog | sed 's/.*(\(.*\)).*/\1/')
+VERSION=$(grep -m 1 APPVERSION variables.pri | cut -d '=' -f 2 | sed -e 's/^[[:space:]]*//' | tr ' ' _ | tr -d '\r\n')
+
+rm -rf build
+mkdir build
+cd build
+
+# Build
+if [ -n "$QTDIR" ]; then
+ cmake -DCMAKE_PREFIX_PATH="$QTDIR/lib/cmake" ..
+else
+ echo "QTDIR not set. Aborting."
+ exit 1
+fi
+
+NUM_CPUS=`sysctl -n hw.ncpu` || true
+if [ -z "$NUM_CPUS" ]; then
+ NUM_CPUS=4
+fi
+
+make -j$NUM_CPUS
+
+if [ ! $? -eq 0 ]; then
+ echo Compiler error. Aborting package creation.
+ exit $?
+fi
+
+# Install to ~/QLC+.app/
+make install/fast
+if [ ! $? -eq 0 ]; then
+ echo Installation error. Aborting package creation.
+ exit $?
+fi
+
+cd ..
+
+echo "Run macdeployqt..."
+$QTDIR/bin/macdeployqt ~/QLC+.app
+
+echo "Fix some more dependencies..."
+install_name_tool -change /usr/local/opt/fftw/lib/libfftw3.3.dylib @executable_path/../Frameworks/libfftw3.3.dylib ~/QLC+.app/Contents/MacOS/qlcplus
+install_name_tool -change /usr/local/opt/fftw/lib/libfftw3.3.dylib @executable_path/../Frameworks/libfftw3.3.dylib ~/QLC+.app/Contents/MacOS/qlcplus-fixtureeditor
+
+# Create Apple Disk iMaGe from ~/QLC+.app/
+OUTDIR=$PWD
+cd platforms/macos/dmg
+./create-dmg --volname "Q Light Controller Plus $VERSION" \
+ --volicon $OUTDIR/resources/icons/qlcplus.icns \
+ --background background.png \
+ --window-size 400 300 \
+ --window-pos 200 100 \
+ --icon-size 64 \
+ --icon "QLC+" 0 150 \
+ --app-drop-link 200 150 \
+ $OUTDIR/QLC+_$VERSION.dmg \
+ ~/QLC+.app
+cd -
diff --git a/create-dmg.sh b/create-dmg.sh
index 4e14a3edd8..9aba03f771 100755
--- a/create-dmg.sh
+++ b/create-dmg.sh
@@ -42,13 +42,13 @@ fi
OUTDIR=$PWD
cd platforms/macos/dmg
./create-dmg --volname "Q Light Controller Plus $VERSION" \
- --volicon $OUTDIR/resources/icons/qlcplus.icns \
- --background background.png \
- --window-size 400 300 \
- --window-pos 200 100 \
- --icon-size 64 \
- --icon "QLC+" 0 150 \
- --app-drop-link 200 150 \
- $OUTDIR/QLC+_$VERSION.dmg \
- ~/QLC+.app
+ --volicon $OUTDIR/resources/icons/qlcplus.icns \
+ --background background.png \
+ --window-size 400 300 \
+ --window-pos 200 100 \
+ --icon-size 64 \
+ --icon "QLC+" 0 150 \
+ --app-drop-link 200 150 \
+ $OUTDIR/QLC+_$VERSION.dmg \
+ ~/QLC+.app
cd -
diff --git a/debian/changelog b/debian/changelog
index d2b1b49e90..41cd3a2961 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,227 @@
+qlcplus (4.13.2) stable; urgency=low
+
+ * New fixtures: GLP Impression X5, Ayrton Rivale Profile (thanks to Masatoshi Fujino)
+ * New fixture: Eurolite LED Mini Strobe Cluster SMD 48 (thanks to Oliver)
+ * New fixture: Ayra Compar Kit 3 (thanks to Robert)
+ * New fixtures: Acme Pixel Line IP, Ayrton Domino LT (thanks to Yestalgia)
+ * New fixture: GLP JDC1 (thanks to Flo Edelmann)
+ * New fixture: Shehds 2 Eyes 200W LED COB Cool Warm White (thanks to Devsider)
+
+ -- Massimo Callegari Sun, 21 Sep 2024 18:19:20 +0200
+
+qlcplus (4.13.1) stable; urgency=low
+
+ * engine: fix blackout not working
+ * engine: include relative EFX in blackout
+ * engine: fix RGB Matrix clone control mode
+ * Show Manager: improve resume after pause
+ * Show Manager: don't freeze on infinite duration Chasers/Sequences
+ * Virtual Console: fix OSC feedback regression
+ * Virtual Console/Slider: add an optional button to flash in playback mode
+ * Virtual Console/XY Pad: copy presets when cloning (thanks to Hans-Jürgen Tappe)
+ * Plugins/DMX USB: restore Vince DMX512 output (thanks to Jérôme Lebleu)
+ * Plugins/HID: add merger mode for DMX devices (thanks to qfulmina)
+ * Plugins/HID: improve devices naming (thanks to qfulmina)
+ * Web Access: added getWidgetSubIdList API and Animation widget sub-control example (API test page updated)
+ * New fixtures: beamZ BAC500 and BAC506 (thanks to Olivier Michel)
+ * New fixtures: American DJ Par Z4, beamZ SB400, OXO ColorBeam 7 FCW IR, Pro-Lights Pixie Spot (thanks to Dmitry Kolesnikov)
+ * New fixtures: BoomToneDJ LED PAR 7X10W 5in1, BoomToneDJ Maxi Spot 60, Mac Mah FLAT PAR 7x12W 6in1, Eurolite LED PIX-16 QCL Bar (thanks to Cédric Monféfoul)
+ * New fixture: Showtec ACT PC 60 RGBW (thanks to Michel Sliepenbeek)
+ * New fixtures: Robe LEDBeam 350, Robe LEDBeam 350 RGBA, Briteq COB Blinder 2x100W, Ayrton MiniPanel FX (thanks to Giacomo Gorini)
+ * New fixture: Elation ELED B48 (thanks to Xoneoo)
+ * New fixture: beamZ PS10W (thanks to Jesper Korsen)
+ * New fixtures: Ayra ComPar 10 and ERO 406 (thanks to René Knuvers)
+ * New fixtures: Mac Mah Moving-FX Bar, Varytec LED Pad Bar Compact ST RGB, Laserworld EL-400RGB MK2 (thanks to Clément Delabroye)
+ * New fixtures: Electroconcept Club Scan 30, Club Scan 120, LED Blinder, Profile 120 Spot LED, Micro Spot 60 LED (thanks to Clément Delabroye)
+ * New fixture: Ayrton Mistral (thanks to Masatoshi Fujino)
+ * New fixture: Chauvet COLORtube 3.0 EQ Controller (thanks to Fede79)
+ * New fixture: Shehds Big Bee Eyes LED Wash 19x40W RGBW (thanks to István Király)
+ * New fixture: Fun-Generation Mr. Beam 120 W (thanks to Mariano)
+
+ -- Massimo Callegari Thu, 30 May 2024 18:19:20 +0200
+
+qlcplus (4.13.0) stable; urgency=low
+
+ * engine: fix Chaser random startup (thanks to Dennis Suermann)
+ * engine: do not fade out looped audio
+ * engine: further rework to properly handle 16bit fading
+ * engine: fix stopping audio with fade in and fade out while fading in
+ * engine: new EFX algorithm: SquareTrue (thanks to Justin Hornsby)
+ * engine: handle 'string' and 'float' types in RGB Scripts
+ * UI: save the geometry of all the dialogs (thanks to Nils Tijtgat)
+ * UI: add color lookup table to input profiles and a dedicated dialog for custom feedback
+ * Virtual Console/Slider: fix switching from playback to submaster mode
+ * Virtual Console/Slider: fix submaster @0 not affecting function intensity
+ * Virtual Console/XY Pad: fix Scene preset controlling wrong channels
+ * Virtual Console/Clock: fix running a schedule the day after
+ * Virtual Console/Button: Scene flashing can force LTP and override (thanks to Dennis Suermann)
+ * Virtual Console/Button: add monitoring feedback value to custom feedback (thanks to ditcheshurt)
+ * Virtual Console/Cue List: fix off by one offset error in steps mode (thanks to kpr0th)
+ * Virtual Console/Audio Triggers: fix attached VC Slider not updating values
+ * Virtual Console/Audio Triggers: fix loading a project with DMX bars with no channels set
+ * Virtual Console/Audio Triggers: fix enable button feedback to external controllers
+ * Plugins/ArtNet: add default standard transmission mode as per protocol specifications
+ * Plugins/ArtNet,E1.31,OSC: add a parameter to wait for interfaces to be ready
+ * Plugins/DMX USB: add support for DMXKing MAX products
+ * Plugins/DMX USB: FTDI USB device no longer disappear after closing QLC+ on Linux
+ * Fixture Editor: fix aliases not updated when renaming a mode
+ * Web Access: add support for Cue List side fader and buttons layout (thanks to Itay Lifshitz)
+ * Web Access: add support for Cue List note editing (thanks to Itay Lifshitz)
+ * Web Access: add support for Slider knob appearance (thanks to Itay Lifshitz)
+ * Web Access: add support for VC Frame disable button (thanks to Itay Lifshitz)
+ * Web Access: add Virtual Console Animation widget support (thanks to Itay Lifshitz)
+ * Web Access: add Virtual Console Grand Master (thanks to Itay Lifshitz)
+ * Web Access: add event to notify Function start/stop
+ * Input profiles: added PMJ 9 Faders Controller, Circus and MidiKey
+ * Input profiles: added Worlde Easypad.12 (thanks to Christoph Müllner)
+ * Input profiles: added Worlde Orca PAD16
+ * New fixture: Ibiza Mini Moving Star Wash (thanks to Chris Shucksmith)
+ * New fixtures: FOS Technologies IQ Par, IQ 28x12 Wash, Iridium 75W Spot (thanks to Maurizio Aru)
+ * New fixture: Varytec Hero Spot 60 (thanks to Hans-Jürgen Tappe)
+ * New fixture: beamZ BAC503 (thanks to archlinette)
+ * New fixtures: Cameo Flat Pro 7, 12 and 18 (thanks to Janosch Frank)
+ * New fixtures: Eurolite LED TMH-X4, lightmaXX Vector ARC Flood II (thanks to Tolmino Muccitelli)
+ * New fixtures: Cameo Q-Spot 40 RGBW, Varytec LED PAR 14x8W, Varytec LED Typhoon PAR Outdoor (12x10) (thanks to Jochen Becker)
+ * New fixtures: Audibax Iowa 70, Pro-Lights CromoWash100 (thanks to Cristian)
+ * New fixtures: Showtec Spectral M1000 Q4, Showtec Kanjo Wash RGB (thanks to Michel Sliepenbeek)
+ * New fixtures: Laserworld CS-1000RGB Mk3, Chauvet Gobozap (thanks to Federico)
+ * New fixture: Eurolite LED Bar 2 RGBA 252/10 40° Indoor (thanks to Edgar Aichinger)
+ * New fixture: Rockville Battery Strip 24 (thanks to Ryan Lindsey)
+ * New fixture: beamZ LCB244 (thanks to Bjorn Roesbeke)
+ * New fixture: Involight NL410 (thanks to Vorona)
+ * New fixture: Flash-Butrym LED PAR 64 7x10W RGBW (thanks to Paolo Betti)
+ * New fixture: Eurolite LED KLS-180 (thanks to Claudio Filieri)
+ * New fixture: Shehds LED Flat Par 7x18W RGBWA+UV (thanks to Tiago)
+ * New fixtures: Martin Atomic 3000 LED, Ayrton NandoBeam S3 (thanks to Yestalgia)
+ * New fixture: lightmaXX Vega Arc Pro II MkII (thanks to João Gonçalves)
+ * New fixture: Shehds LED Flat Par 18x18W RGBWA+UV (thanks to Santiago Benejam)
+ * New fixture: Eurolite TMH-15 (thanks to Nicolas Rasor)
+ * New fixtures: Shehds JMS WEBB LED Wash Big Bee Eye 19X40W, LED 230W Beam Moving Head (thanks to István Király, Feiyu Shehds)
+ * New fixture: Shehds GalaxyJet Waterproof IP65 380W 19R Beam Moving Head (thanks to István Király, Feiyu Shehds)
+ * New fixture: Martin Ego X6 (thanks to Michael Tosatto)
+ * New fixture: Blizzard Lighting LB Hex Unplugged (thanks to David Sparks)
+ * New fixtures: Eurolite LED Strobe SMD PRO 132 DMX RGB, Briteq BT Theatre HD2, Eurolite KLS-180-6, BoomToneDJ KUB 500 RGB (thanks to Fede79)
+ * New fixture: Eurolite LED PLL-480 CW/WW (thanks to Benjamin Drung)
+ * New fixture: Robe Spiider (thanks to Nicolò)
+ * New fixture: Betopper LB230 (thanks to Viktor)
+ * New fixtures: EK R3 Wash, Chauvet Intimidator Spot 475ZX, Chauvet Intimidator Wash Zoom 450 IRC (thanks to Harrison Bostock)
+ * New fixtures: Varytec Typhoon True Kid 720Z RGBW IP65, Showtec Performer 2000 RGBAL (thanks to Clément Delabroye)
+ * New fixtures: Showtec LED Par 64 Short V2, Bright XBAR (thanks to Øystein Steimler)
+ * New fixtures: AFX CLUB-MIX3 19x10W RGBW, Eurolite LED Theatre COB 200 RGB+WW (thanks to Florian Faber)
+ * New fixture: Chauvet COLORado Batten 72x (thanks to Greg Perrone)
+ * New fixtures: Talent SSL2, Cameo P2 FC
+ * New fixture: Tecshow Nebula 6 (thanks to Federico)
+ * New fixture: beamZ Radical II (thanks to Matt Muller)
+ * New fixtures: Eurolite LED PARty TCL spot, Expolite TourSpot 50 Mini, Fun Generation LED Pot 12x1W QCL RGB WW (thanks to Christian Prison)
+ * New fixtures: Chauvet COLORband Q3BT, Shehds LED Beam+Wash 19x15W RGBW Zoom (thanks to Paul Schuh)
+ * New fixtures: Eliminator Lighting Stealth Beam and Stealth Wash Zoom Lighting (thanks to Paul Schuh)
+ * New fixture: Varytec Blitz Bar 240 (thanks to Stefan Lohmann)
+ * New fixture: Eurolite LED KLS Scan Pro Next FX Light (thanks to Kevin)
+ * New fixtures: Elumen8 MP 60 Mk1, MP 60 Mk2, MP 120 (thanks to Keith Baker)
+ * New fixture: Elation Paladin (thanks to Nicholas Harvey)
+ * New fixtures: Acme Oxygen, Dotline180, Dotline260 and Super Dotline, Chauvet Intimidator Spot Duo 155 (thank to Michael Tosatto)
+ * New fixtures: Laserworld EL-900RGB, Chauvet COLORdash Par-Quad 18, Event Lighting StrobeX and StrobeX RGB (thank to Michael Tosatto)
+ * New fixture: Chauvet Wash FX Hex (thanks to Clément Delabroye)
+ * New fixture: Shehds Wash Zoom LED 36x18W RGBWA+UV (thanks to Ioannis Iliopoulos)
+ * New fixture: UKing ZQ-02319 (thanks to Mike Ubl)
+ * New fixtures: DTS Jack, Robe LEDBeam 350 (thanks to Tomas Hastings)
+
+ -- Massimo Callegari Sun, 17 Mar 2024 12:13:14 +0200
+
+qlcplus (4.12.7) stable; urgency=low
+
+ * engine: improve audio fade in/out
+ * engine: consider EFX fade in
+ * engine: handle LTP channels fade out
+ * engine: make sure input/output device names are unique
+ * engine: fix crash when no IO plugin is found
+ * engine: fix blackout to leave LTP channels untouched
+ * engine: add volume control to audio function
+ * UI/Monitor: fix 2D view multiple heads not showing correctly
+ * UI/Audio Editor: do not stop audio function if not previewing
+ * UI/Fixture Remap: add button to import fixture lists (.qxfl)
+ * UI/Fixture Remap: fix wrong widgets remapping
+ * UI/Wizard: improved generated function names (thanks to netmindz)
+ * UI: remember state of Function selection dialog
+ * Plugins/OSC: fix broadcast packets reception (thanks to Jannis Achstetter)
+ * Plugins/E1.31: allow to set 2 digits of the IP address
+ * Plugins/MIDI: fix consecutive notes not notified on macOS (thanks to Nils Tijtgat)
+ * Virtual Console/Audio Triggers: stop sending DMX values on deactivation
+ * Virtual Console/Animation: include all available presets
+ * Virtual Console: submaster now affects widgets on all pages but only on active frames
+ * Web Access: add support for widget background images
+ * Web Access: improve button layout and text overflow (thanks to pomowunk)
+ * Application: fix qxf file association on Windows (if installed as admin)
+ * RGB scripts: added 'Marquee' script (thanks to Branson Matheson)
+ * Input profiles: added ADJ MIDICON-2 (thanks to David Thomas)
+ * Input profiles: added Akai APC Mini MK2 (thanks to Michael Mertens)
+ * Fixture updated: Blizzard Lighting Pixellicious (thanks to Yestalgia)
+ * New fixture: Vari-Lite VL4000 Spot (thanks to Håvard Ose Nordstrand)
+ * New fixtures: Clay Paky Tambora Batten, Tambora Flash, Tambora Linear 100, Sharpy X Frame, Volero Wave (thanks to Gianluca Baggi)
+ * New fixture: beamZ BAC302 (thanks to Matej Lazar)
+ * New fixture: Varytec Giga Bar HEX 3 (thanks to Niklas Larsson)
+ * New fixtures: Equinox Fusion Orbit MKII, QTX PAR-180 (thanks to Wilbur)
+ * New fixtures: Cameo ROOT Par 6, AFX BARLED200-FX (thanks to Florian Faber)
+ * New fixtures: BoomToneDJ Moving Wash 5X15 Speed, lightmaXX Vega Spot 60 (thanks to memrex)
+ * New fixture: Eurolite TSL-150 (thanks to Samuel Mueller)
+ * New fixtures: Ayra ALO Micro Scan, Eurolite EDX-4RT, JB Systems MINI-PAR 12RGBW, N-Gear Light Spotlight 12 (thanks to Frank Rosquin)
+ * New fixture: Elation E Spot III (thanks to Ludovic Martinez)
+ * New fixtures: Chauvet Intimidator Spot 375Z IRC, Eliminator Lighting LP 12 HEX (thanks to Andrew Pavlin)
+ * New fixtures: Chauvet SlimPAR T12BT, Q12ILS and Q12BT (thanks to Andrew Pavlin)
+ * New fixture: Lumeri Eco COB 15 (thanks to Robert Rieks)
+ * New fixtures: Martin ERA 400 Performance, ENTTEC SMART PXL 40/60 Dot, ENTTEC CVC4, UKing ZQ-B243 (thanks to Yestalgia)
+ * New fixtures: Varytec Hero Wash 712 Z RGBW Zoom, Cameo Studio PAR 64 Q 8W (thanks to Anton Luka Šijanec)
+ * New fixtures: American DJ Jolt 300, Showtec Star Dream 144 LED White (thanks to David Gouronc)
+ * New fixture: Antari Z-1000 MKII (thanks to Jérôme)
+ * New fixtures: Event Lighting PAR19x12O, PAR6x12OB2, PAR6x12OB, Pixbar 12x12W (thanks to Michael Tosatto)
+ * New fixture: U'King ZQ-B93 Pinspot RGBW (thanks to Jarosław Biernacki)
+ * New fixtures: Stage Right 200W COB LED Ellipsoidal, Stage Right 30W LED Spot (thanks to Dave Vecchio)
+ * New fixture: Rockville Rockwedge LED (thanks to Ryan Carter)
+ * New fixtures: Showtec Starforce LED, Equinox Fusion Spot MK III (thanks to gnomesenpai)
+ * New fixture: Chauvet SlimPAR Pro Pix (thanks to Ryan Carter)
+ * New fixture: DTS Scena LED 200 (thanks to Freddy Hoogstoel)
+ * New fixture: Chauvet Intimidator Beam Q60 (thanks to Nathan)
+ * New fixtures: Eurolite LED T-36 RGB Spot, Eurolite LED SLS-183/10 RGB, Stairville Stage PAR CX-2 RGBAW (thanks to Felix Hartnagel)
+ * New fixture: American DJ WiFly Bar QA5 (thanks to Edgar Aichinger)
+ * New fixtures: Laserworld EL-230RGB MK2, Eurolite LED Multi FX Laser Bar, UKing Mini Double Sided Moving Head (thanks to e-shock)
+ * New fixture: lightmaXX Vega Shiggy Beam Wash (thanks to Jarada)
+ * New fixture: Eurolite LED Super Strobe ABL (thanks to Sebastian Moeckel)
+ * New fixture: beamZ Fuze75B Beam (thanks to Marcin Kwiecien)
+ * New fixture: Eurolite LED FE-800 (thanks to Clausi4711)
+ * New fixtures: Varytec LED Bar 240/8 CW/WW, Equinox SpectraPix Batten, Betopper LPC-017 (thanks to Michel Sliepenbeek)
+ * New fixtures: Chauvet Intimidator Scan LED 100, Equinox Fusion Spot Max MKIII (thanks to Michel Sliepenbeek)
+ * New fixture: Elation SIX PAR Z19 IP (thanks to Sophia Rodriguez)
+ * New fixture: Betopper LPC007 (thanks to Romil Barticulo)
+ * New fixture: Betopper LM108 Wash Moving Head (thanks to Luca Giovannesi)
+ * New fixture: Starway Servo Beam 10R (thanks to David Talet)
+ * New fixture: Cameo Zenit B200 (thanks to ikr)
+ * New fixture: Stairville BEL4 - Battery Event Light 4x15W (thanks to Pascal)
+ * New fixture: U'King B117 Par Can 4in1 RGBW (thanks to Turning Point)
+ * New fixture: Chauvet Followspot 120ST (thanks to Scott Yarbrough)
+ * New fixture: Chauvet COLORband T3 BT (thanks to William Todd)
+
+ -- Massimo Callegari Fri, 19 May 2023 12:13:14 +0200
+
+qlcplus (4.12.6) stable; urgency=low
+
+ * Engine: start Script functions detached (thanks to Thierry)
+ * Plugins/E1.31: fix CID on loopback device
+ * Plugins/uDMX: fix output not working correctly
+ * Web Access: fix VC Slider values disappearing on change (thanks to Thierry)
+ * RGB scripts: added 'Circular' script (thanks to Hans-Jürgen Tappe)
+ * Channel modifiers: added 'S-Curve" modifier (thanks to Giacomo Gorini)
+ * New fixture: Cameo F2 T PO (thanks to Hans-Jürgen Tappe)
+ * New fixtures: Ibiza PAR LED 710, AFX Spot 60 LED, Ghost Venum 12W RGBW (thanks to erdnaxe)
+ * New fixtures: ETC Source Four LED Series 2 Lustr, Robert Juliat D'Artagnan 934SNX (thanks to Giacomo Gorini)
+ * New fixture: DTS Katana (thanks to Federico)
+ * New fixture: beamZ SB200 Stage Blinder 2x50W (thanks to Tolmino Muccitelli)
+ * New fixture: U'King ZQ-B370 (thanks to Sidde Persson)
+ * New fixture: Equinox Butterfly Quad EQLED100 (thanks to Yestalgia)
+ * New fixture: Varytec Hero Spot 90 (thanks to Chris de Rock)
+ * New fixtures: Eurolite LED H2O Water Effect, Stairville All FX Bar (thanks to Rami Toivola)
+
+ -- Massimo Callegari Sun, 28 Aug 2022 12:13:14 +0200
+
qlcplus (4.12.5) stable; urgency=low
* engine: add support for 16bit fading
@@ -337,538 +561,3 @@ qlcplus (4.12.0) stable; urgency=low
* New fixtures: Pro-Lights Genesis, BB5 Pix (thanks to Lorenzo Andreani)
-- Massimo Callegari Sat, 10 Nov 2018 12:13:14 +0200
-
-qlcplus (4.11.2) stable; urgency=low
-
- * engine: fix crash caused by an invalid IO mapping
- * engine: fix intensity override not considered during fade outs
- * UI/Function Manager: fixed keyboard shortcut conflicts and document them
- * UI/Function Manager: allow to import multiple of audio/video files at once
- * UI/Channel Groups: added expand/collapse all button helpers
- * UI/Chaser Editor: add a button to shuffle the selected Chaser steps (thanks to Felix Edelmann)
- * UI/RGBMatrix Editor: fix preview not updating on pattern change when play button is on
- * Show Manager: fix crash when adding a Sequence after deleting one
- * Show Manager: fix crash when editing a Sequence bound to a deleted Scene
- * Show Manager: fix items start time indication when dragging
- * Virtual Console/Slider: fix submaster initial value not applied and inverted mode
- * Virtual Console/Slider: added 'value catching' option for external controller faders (Lukas Jähn proposal)
- * Virtual Console/Slider: fix values range when switching between Slider and Knob appearance
- * Virtual Console/Slider: react on Scene flashing when in playback mode
- * Virtual Console/Knob: fix DMX values not updated when interacting with the mouse wheel
- * Virtual Console/Cue List: allow to select a step with next/previous buttons during pause
- * Virtual Console/Cue List: go to the right chaser step after a pause (thanks to Krzysztof Walo)
- * Virtual Console/Frame: fix regression preventing to send the disable feedback
- * Web Access: added support for VC Buttons in Flash mode (Sylvain Laugié)
- * Web Access: added support for VC Frame circular page scrolling (Sylvain Laugié)
- * Web Access: update AudioTriggers state when changed from QLC+ (Sylvain Laugié)
- * plugins/udmx: added 'channels' configuration parameter (see documentation)
- * plugins/E1.31: fix crash on wrong packet length (David Garyga)
- * New fixture: DTS XR7 Spot (thanks to Nicolò Zanon)
- * New fixture: Ledj Slimline 12Q5 Batten (thanks to Dean Clough)
- * New fixture: Ayra Compar Kit 1 (thanks to eigenaardiger)
- * New fixtures: GLP Impression X4 S, Eurolite LED KLS-2500 (thanks to Mitsch)
- * New fixture: Chauvet Intimidator Spot 255 IRC (thanks to Ham Sadler)
- * New fixtures: Chauvet Geyser RGB, Geyser P6 (thanks to Andrew)
- * New fixture: Chauvet Rotosphere Q3 (thanks to Eric Sherlock)
- * New fixture: Showtec Compact Par 7 Q4 (thanks to Alexander)
- * New fixture: Contest Delirium (thanks to Vincent)
- * New fixture: Solena Mini Par 12, Max Bar 28 RGB (thanks to Nathan Durnan)
- * New fixtures: Showtec Phantom 65, Laserworld PRO-800RGB (thanks to Piotr Nowik)
- * New fixture: Chauvet MiN Spot RGBW (thanks to Jungle Jim)
- * New fixtures: Showtec Shark Wash One, American DJ Vizi Hex Wash7 (thanks to Georg Müller)
- * New fixture: Showtec Shark Beam FX One (thanks to Mats Lourenco)
- * New fixture: Stairville novaWash Quad LED (thanks to Luke Bonett)
- * New fixtures: Eurolite Party TCL Spot RGB, Expolite TourSpot 60, Expolite TourStick 72 RGBWA (thanks to Dirk J)
- * New fixture: Chauvet Hemisphere 5.1, Trident, Scorpion Storm RGX (thanks to Francois Blanchette)
- * New fixture: Briteq COB Slim 100-RGB (thanks to Thierry)
- * New fixture: American DJ UB 12H (thanks to Jason R Johnston)
- * New fixture: American DJ Mega Hex Par (thanks to Ben C)
- * New fixture: Cameo Q SPOT 15 RGBW (thanks to Antoine Houbron)
- * New fixture: lightmaXX Platinum Line Flat Par COB (thanks to Leonardo)
- * New fixture: Stairville LED Blinder 2 COB 2x65W (thanks to chritoep)
- * New fixture: lightmaXX LED PAR 64 (thanks to Johannes Felber)
- * New fixture: Cameo Thunder Wash Series (thanks to JP)
- * New fixtures: Briteq BT 575S, Stairville MH-x30 LED Beam (thanks to Andres Robles)
- * New fixture: beamZ LED FlatPAR-154 (thanks to Jászberényi Szabolcs)
- * New fixtures: Eurolite THA-100F COB, Cameo Tribar 200 IR (thanks to David Morgenschweis)
- * New fixture: beamZ BT310 LED FlatPAR 12x8W 4-1 DMX IR (thanks to Mark)
- * New fixture: Fun Generation PicoWash 40 Pixel Quad LED (thanks to Harm Aldick)
- * New fixtures: American DJ Entourage, Elumen8 MS-700PE, Ibiza PAR LED 712IR (thanks to Tim Cullingworth)
- * New fixtures: Martin MAC 401 Dual RGB Zoom, MAC 401 Dual CT Zoom, Stairville MH-z720 (thanks to Tim Cullingworth)
- * New fixture: Fun Generation SePar Quad UV (thanks to Helmet)
-
- -- Massimo Callegari Thu, 19 Apr 2018 20:21:22 +0200
-
-qlcplus (4.11.1) stable; urgency=low
-
- * engine: fixed audio files detection by prioritizing sndfile over mad
- * engine: fixed HTP/LTP forced channels not set correctly
- * engine: keep track of input/output device lines even if they are disconnected
- * engine/Script: add blackout:on and blackout:off commands (Jano Svitok)
- * engine/Script: do not keep empty trailing lines when saving a workspace
- * UI: it is now possible to detach a QLC+ context tab on a separate window by double clicking on it
- * UI/RGB Panel: added RBG pixel type (thanks to Peter Marks)
- * UI/Remap: fixed RGB Panels remapping
- * UI/Input Output Manager: added a button to enable/disable USB hotplugging (disabled by default)
- * UI/Function Live Edit: restore basic live editing of Sequences
- * UI/RGB Matrix Editor: fixed save to Sequence feature
- * UI/Function Manager: when cloning a Sequence, clone the bound Scene too
- * Virtual Console/Button: highlight border with orange color when in "monitoring" state
- * Virtual Console/Slider: fix DMX values not updated when interacting with the mouse wheel, keyboard or Click And Go button
- * Virtual Console/Slider: fix level mode values range scaling
- * Virtual Console/XYPad: the speed of a running EFX preset can now be controlled by a Speed Dial widget
- * RGB Scripts: added "Noise", "3D Starfield", "Random pixel per row" and "Random pixel per row multicolor" (thanks to Doug Puckett)
- * Web access: added basic authentication support (thanks to Bartosz Grabias)
- * Web access: fixed solo frames collapse state
- * Web access: update feedbacks when a slider is moved
- * New fixtures: IMG Stageline BEAM-40 WS/RGBW, Fun-Generation LED Diamond Dome (thanks to Tolmino Muccitelli)
- * New fixture: Elation Cuepix Batten (thanks to Saul Vielmetti)
- * New fixture: Clay Paky Tiger Scan HMI 575/1200 (thanks to Daris Tomasoni)
- * New fixture: Litecraft WashX.21 (thanks to Hannes Braun)
- * New fixtures: Briteq Stagepainter 12, Nicols IP Wash 120, Showtec LED Powerline 16 Bar (thanks to Fredje Gallon)
- * New fixtures: Nicols Movelight, Nicols Birdy Wash 122, Briteq Giga Flash RGB (thanks to Fredje Gallon)
- * New fixtures: Litecraft PowerBar AT10.sx, Stairville MH-z1915 (thanks to Thorben / Fredje)
- * New fixtures: Martin MAC 700 Wash, ADB Warp M (thanks to Thorben)
- * New fixture: Laserworld CS-1000RGB Mk II (thanks to Piotr Nowik)
- * New fixture: Chauvet COLORrail IRC (thanks to Lane Parsons)
- * New fixtures: American DJ COB Cannon Wash DW, lightmaXX Vega Zoom Wash Beam (thanks to Florian Gerstenlauer)
- * New fixture: Martin Rush MH5 Profile (thanks to Falko)
- * New fixture: Cameo Flash Bar 150 (thanks to Kevin Wimmer)
- * New fixtures: Chauvet FXpar 9, IMG Stageline Wash-40 LED (thanks to PeterK)
- * New fixtures: JB Systems iRock 5C, JB Systems LED Devil (thanks to Andres Robles)
- * New fixtures: beamZ BAC406, Geni Mojo Color Moc (thanks to Mark Sy)
- * New fixtures: Stairville MH-250 S, Chauvet GigBAR 2, Pro-Lights Onyx (thanks to Freasy)
- * New fixtures: Coemar ProSpot 250 LX, Showtec Kanjo Spot 60 (thanks to Flo Edelmann)
-
- -- Massimo Callegari Sat, 28 Oct 2017 12:13:14 +0200
-
-qlcplus (4.11.0) stable; urgency=low
-
- * engine: fixed setting start/end color while a RGB Matrix is running
- * engine: fixed crash when pausing a Show with an unavailable audio file
- * engine: major rework of Sequences. Projects using them need to be migrated
- * UI: enabled Alt key combinations on macOS to behave like other platforms (thanks to Matt Mayfield)
- * UI/RGB Panel: added panel direction (thanks to Raivis Rengelis)
- * UI/Fixture Manager: added weight and power consumption information on fixtures/universe selection (Chris de Rock idea)
- * UI/Scene Editor: preserve fixture tab order when fixtures with no channels set are present
- * UI/RGB Matrix Editor: allow the preview to run even in operate mode
- * UI/Audio Editor: added the possibility to loop an audio file (thanks to Raivis Rengelis)
- * UI/Simple Desk: fixed crash when changing values from a channel group in "fixtures view" mode
- * Virtual Console: prevent unwanted feedbacks from widgets in inactive Frame pages (thanks to Lukas Jähn)
- * Virtual Console: fixed manual selection of input channels not considering Frame pages (thanks to Lukas Jähn)
- * Virtual Console: fixed input profiles channels not honored on frame pages other than the first (thanks to Lukas Jähn)
- * Virtual Console/Slider: improved level monitoring with the possibility to act like a Simple Desk slider (see documentation)
- * Virtual Console/Frame: fixed 4.10.5b regression disabling widgets when switching page in design mode
- * Virtual Console/Frame: fixed key controls not copied when cloning a frame (thanks to Lukas Jähn)
- * Virtual Console/Frame: added the possibility to jump directly to a page and assign page names (thanks to Lukas Jähn)
- * Virtual Console/Cue List: improved linked crossfade to perform an additive blending between steps (see documentation)
- * Virtual Console/Speed Dial: improved tap button blinking and feedbacks (thanks to Lukas Jähn)
- * Virtual Console/Speed Dial: it is now possible to copy/paste factors (thanks to Jan Dahms)
- * Virtual Console/Clock: added external input support for countdown and stopwatch modes (thanks to Lukas Jähn)
- * Plugins/OSC: added channel number calculator in configuration page to help integrating new controllers
- * Plugins/Loopback: fixed spurious values emitted when a lot of channels are looped
- * Web access: fixed VC Slider in percentage mode and inverted appearance (thanks to Bartosz Grabias)
- * Web access: support VC Slider reduced range when in level mode
- * Web access: improved getChannelsValues and added Simple Desk reset per-channel (sdResetChannel API)
- * Web access: implemented keypad increase/decrease buttons (thanks to Santiago Benejam Torres)
- * New input profile: Zoom R16 (thanks to Benedict Stein)
- * New MIDI template: Akai APC40 MK2 Ableton mode (thanks to Branson Matheson)
- * New fixture: American DJ FREQ 5 Strobe (thanks to Martin Bochenek)
- * New fixture: Martin Rush MH3 (thanks to Ed Middlebrooks)
- * New fixture: Eurolite LED ACS BAR-12 (thanks to Michael Horber)
- * New fixtures: Martin Rush MH6 Wash, Cameo Studio PAR 64 RGBWA UV 12W (thanks to Piotr Nowik)
- * New fixtures: ETEC Moving Spot 60E, Cameo CLM PAR COB 1, Showtec Compact Power Lightset COB (thanks to Freasy)
- * New fixture: Stairville Tri Flat PAR Profile 5x3W RGB (thanks to Freasy)
- * New fixture: American DJ Punch LED Pro (thanks to Benedict Stein)
- * New fixtures: Contest Mini-Head 10W, Contest Evora B2R (thanks to Fredje Gallon)
- * New fixture: Robe DJ Scan 150 XT (thanks to Allan Madsen)
- * New fixtures: Futurelight PRO Slim PAR-12 HCL, PRO Slim PAR-12 MK2 HCL, Showtec Power Spot 9 Q5 (thanks to Lukas Jähn)
- * New fixture: Showtec XS-1W Mini Moving Beam (thanks to Habefaro)
- * New fixtures: Stage Right Stage Wash 18Wx18 LED PAR, Stage Right 7x20W COB LED Theater PAR (thanks to Collin Ong)
- * New fixture: Cameo CLPIXBAR450PRO, CLPIXBAR650PRO (thanks to Jean-Daniel Garcia & Jeremie Odermatt)
- * New fixture: Clay Paky Alpha Beam 1500 (thanks to Louis Gutenschwager)
- * New fixture: Stairville AFH-600 (thanks to Hannes Braun)
- * New fixture: Involight LED MH77S (thanks to Jászberényi Szabolcs)
- * New fixtures: ETEC LED PAR 64 18x10W RGBWA, LED PAR 64 18x15W RGBWA Zoom (thanks to Simon Orlob)
- * New fixture: Chauvet Swarm Wash FX (thanks to Stephen Olah)
- * New fixture: Clay Paky Alpha Spot HPE 575 (thanks to Rohmer)
- * New fixture: Robe LED Blinder 196LT (thanks to Tim Cullingworth)
- * New fixture: Chauvet COLORband T3 USB (thanks to Ian Nault)
- * New fixtures: American DJ Dotz Matrix, Martin Jem Compact Hazer Pro,Geni Mojo Spin Master Series (thanks to Sam Brooks)
- * New fixture: American DJ XS 400 (thanks to Jared)
- * New fixture: Velleman VDP1500SM (thanks to Freddy Hoogstoel)
- * New fixture: Chauvet Intimidator Spot 355Z IRC (thanks to Michael Clements)
- * New fixture: CLF Tricolor Mini Par (thanks to Jaron Blazer)
- * New fixture: Varytec LED Easy Move Mini Beam & Wash RGBW (thanks to Erik)
- * New fixtures: Smoke Factory Tour-Hazer II, JB Systems Panther, Robe Spot 160 XT (thanks to Thierry Rodolfo)
- * New fixture: American DJ LED Trispot (thanks to Patrick)
- * New fixtures: Contest STB-520 1500W Strobe, Elumen8 COB Tri 4 Pixel Batten, Briteq Tornado 7 (thanks to Robert Box)
- * New fixtures: American DJ 5P Hex, Pro-Lights Moonstone, Chauvet Intimidator Hybrid 140SR (thanks to Robert Box)
- * New fixtures: Robe Robin DLX Spot (thanks to Robert Box)
- * New fixture: ETC ColorSource PAR (thanks to Jon Rosen)
- * New fixture: lightmaXX 5ive STAR LED (thanks to Thomas Weber)
- * New fixture: Talent BL252A (thanks to Massimiliano Palmieri)
- * New fixtures: Showtec: Infinity iW-1915, Infinity XPLO-15 LED Strobe (thanks to Daniele Fogale)
- * New fixtures: Showtec: Infinity iB-5R, Compact Par 18 MKII, Phantom 20 LED Beam (thanks to Nicolò Zanon)
- * New fixture: Griven Gobostorm Plus MK2 (thanks to Attilio Bongiorni)
- * New fixture: Chauvet Freedom Stick (thanks to Jay Szewczyk)
- * New fixture: Eurolite TMH-14, Chauvet Intimidator Trio (thanks to Chris de Rock)
- * New fixture: Chauvet Scorpion Dual (thanks to Alan Chavis)
- * New fixture: American DJ Ultra Hex Bar 12 (thanks to Rhavin)
- * New fixture: Equinox Photon
- * New fixture: QTX MHS-60 (thanks to Nerijus Mongirdas)
- * New fixture: Eurolite LED TMH FE-600, MARQ Colormax Par64, Stairville CLB2.4 Compact LED PAR System (thanks to Klaus Muth)
- * New fixture: Chauvet SlimPar Hex 6 (thanks to Yinon Sahar)
- * New fixture: IMG Stageline PARL 20 DMX (thanks to Felix Pickenäcker)
- * New fixtures: Pro-Lights SmartBatHEX, Fury FY250W, Fury FY250S (thanks to Lorenzo Andreani)
- * New fixture: American DJ Ikon Profile (thanks to Ham Sadler)
- * New fixture: HQ Power Aeron Wash 575, JB Systems Space Color Laser (thanks to Ricardo Mendes)
- * New fixture: American DJ VPar (thanks to Eric Eskam)
- * New fixtures: MARQ Gesture Beam/Wash 102, Colormax Bat, Gesture Spot 100 (thanks to John Yiannikakis)
- * New fixtures: Chauvet COLORado 3P, Legend 330SR Spot, SlimPar HEX 3 (thanks to Kevin Zepp)
- * New fixture: American DJ Mini Dekker (thanks to Chris Davis)
- * New fixtures: American DJ Vizi BSW 300, Blizzard Lighting Flurry 5 (thanks to George Qualley)
- * New fixtures: Pro-Lights PIXIEWASH, Accent1Q, CromoSpot300 (thanks to Tolmino Muccitelli)
- * New fixtures: Involight LED MH50S, LED PAR 180, SBL 2000 (thanks to Facek)
- * New fixture: Pro-Lights Miniruby (thanks to Dario Gonzalez)
- * New fixture: Sagitter Smart DL Wash (thanks to Simu)
- * New fixtures: Eurolite LED THA-250F, Pro-Lights StudioCOBFC (thanks to Andrea Ugolini)
- * New fixture: American DJ Stinger Spot (thanks to Jason R. Johnston)
- * New fixture: Stairville Blade Sting 8 RGBW Beam Mover
-
- -- Massimo Callegari Sat, 24 Jun 2017 12:13:14 +0200
-
-qlcplus (4.10.5b) stable; urgency=high
-
- * engine: fixed 4.10.5 regression on RGB Matrix preset step color calculation
- * Virtual Console/Frame: fixed widgets disable state when switching pages
- * Virtual Console: fixed SpeedDial and Animation widget presets feedbacks, and allow to use custom feedbacks
- * Plugins/DMX USB: fixed 4.10.5 regression preventing to receive data from PRO devices
- * Plugins/DMX USB: [Windows] fixed a long standing bug causing random crashes when receiving DMX data
- * Plugins/MIDI: [macOS] further changes to support virtual ports
- * New fixtures: Stairville M-Fog 1000 DMX, Cameo Superfly XS (thanks to Konni)
- * New fixture: ColorKey WaferPar Quad-W 12 (thanks to Taylor)
- * New fixture: Eurolite LED PARty RGBW (thanks to Heiko Fanieng)
- * New fixture: lightmaXX Platinum CLS-1 (thanks to Marc Geonet)
-
- -- Massimo Callegari Mon, 26 Dec 2016 12:13:14 +0200
-
-qlcplus (4.10.5a) stable; urgency=high
-
- * engine: fixed playback of a chaser within a chaser
-
- -- Massimo Callegari Mon, 12 Dec 2016 12:13:14 +0200
-
-qlcplus (4.10.5) stable; urgency=low
-
- * Engine: added indigo to fixture channel colors (thanks to Axel Metzke)
- * Engine: properly handle RGB Matrices with generic dimmers (Jano Svitok)
- * UI/Function Manager: fix crash when trying to clone a folder (David Garyga)
- * UI/RGB Matrix Editor: editor preview doesn't stop when testing the Function
- * UI/Collection Editor: allow multiple selection and added Function reordering buttons
- * UI/Remap: fixed universes list in target mapping
- * UI/Remap: fixed wrong Scene remapping when mixing cloned and new fixtures
- * UI/Remap: added remapping also of Fixture Groups
- * Virtual Console/Frame: Show page number when collapsed (thanks to Matthias Gubisch)
- * Virtual Console/Cue List: allow to choose playback buttons layout (Play/Pause + Stop or Play/Stop + Pause)
- * Plugins/DMX USB: fixed crash happening on PRO devices when receiving a full universe
- * Plugins/DMX USB: [MacOS] fixed regression caused by the Qt libraries on PRO devices
- * Plugins/MIDI: [MacOS] added support for virtual ports, show only active devices and properly handle hotplug
- * Fixture Editor: fixed minimum value of a new capability not updating correctly
- * RGB Scripts: added One by one (Jano Svitok)
- * New fixtures: Pro-Lights LumiPIX 12Q, Proel PLLEDMLBG (thanks to Andrea Ugolini)
- * New fixtures: Stairville DCL Flat Par 18x4W CW/WW, Cameo LED MultiPAR CLM-PAR-COB1 (thanks to Freasy)
- * New fixtures: High End Systems Studio Beam, lightmaXX EASY Wash 5IVE LED (thanks to Freasy)
- * New fixture: iSolution iColor 4 (thanks to withlime)
- * New fixtures: ETC ColorSource Spot, Blizzard Lighting LB-Par Hex (thanks to Robert Box)
- * New fixtures: American DJ: Chameleon QBar Pro,DJ Vizi Beam RXONE, XS 600, Focus Spot Three Z (thanks to Robert Box)
- * New fixture: JB-Lighting Varyscan P6, Cameo Wookie series, Cameo Hydrabeam series (thanks to Andres Robles)
- * New fixture: Chauvet RotoSphere LED (thanks to Carl Eisenbeis)
- * New fixture: Briteq Spectra 3D Laser (thanks to Robert Box + Freasy)
- * New fixture: Martin MH2 Wash (thanks to John Yiannikakis + Freasy)
- * New fixture: American DJ Flat Par Tri7X (thanks to Brian)
- * New fixtures: Ledj Stage Color 24, 59 7Q5 RGBW, 59 7Q5 RGBA (thanks to Paul Wilton)
- * New fixtures: American DJ: Inno Spot Elite, Stinger, Tri Phase (thanks to Piotr Nowik)
- * New fixtures: Showtec Phantom 95 LED Spot, Futurelight PHS-260 (thanks to Piotr Nowik)
- * New fixture: Blizzard Lighting Rocklite RGBAW (thanks to Larry Wall)
- * New fixture: American DJ Comscan LED (thanks to Chris)
- * New fixtures: PR Lighting XL 250/XL 700 Wash/XL 700 Spot, American DJ Accu Fog 1000 (thanks to István Király)
- * New fixtures: Equinox Ultra Scan LED, Kam Powercan84W, QTX HZ-3 (thanks to Chris Moses)
- * New fixture: Eurolite TMH-10 (thank to exmatrikulator)
- * New fixture: Eurolite LED SLS 5 BCL, Robe Fog 1500 FT (thanks to Christian Hollbjär)
- * New fixture: SGM Giotto Spot 400 (thanks to Mihai Andrei)
- * New fixture: Pulse LEDBAR 320 (thanks to Allan Rhynas)
- * New fixture: Equinox Swing Batten (thanks to Dean Clough)
- * New fixture: Cameo Pixbar 600 PRO, Chauvet COLORado 1 Quad Zoom Tour (thanks to Andrew Hallmark)
- * New fixture: Involight FM900 DMX (thanks to Jászberény Szabolcs)
- * New fixture: Showtec Stage Blinder Series (thanks to Antoni J. Canós)
- * New fixture: MARQ Gamut PAR H7 (thanks to Lance Lyda)
- * New fixtures: Chauvet: SlimPAR QUV12 USB, SlimPAR PRO H USB, Scorpion Bar RG (thanks to Pete Mueller)
- * New fixture: Stairville CLB8 Compact LED PAR System (thanks to Detlef Fossan)
- * New fixture: Chauvet Cubix 2.0 (thanks to Jungle Jim)
- * New fixture: Showtec Giant XL LED (thanks to Samuel Hofmann)
- * New fixtures: SGM: Idea Beam 300, Idea Led Bar 100, Idea Spot 700, Newton 1200 (thanks to Oscar Cervesato)
- * New fixtures: Pro Lights LumiPAR18QTour, Elation SIXPAR 200IP (thanks to Oscar Cervesato)
- * New fixture: Stairville Beam Moving Head B5R, American DJ Flat Par TW12, Varytec Easy Scan XT Mini (thanks to Thierry Rodolfo)
-
- -- Massimo Callegari Sat, 3 Dec 2016 12:13:14 +0200
-
-qlcplus (4.10.4) stable; urgency=low
-
- * Scripts: Fix 4.10.3a regression that breaks values parsing (David Garyga)
- * Engine: fix relative paths when opening a project from the command line
- * Engine: improved the start/stop mechanism of Functions within a Show
- * Chaser Editor: a newly created step is now selected automatically
- * Scene Editor: fixed the tab order of the fixtures
- * Show Manager: added the possibility to pause a Show leaving the lights on
- * Show Manager/Audio: allow to display the waveform preview while playing a file
- * UI/Function Selection: fix crash on workspaces where a scene ID is bigger than its sequence ID (David Garyga)
- * UI/Video: fixed the fullscreen positioning on Windows
- * Virtual Console/Animation: fix behavior issue when changing the associated function (David Garyga)
- * Virtual Console/Frames: send feedbacks for the enable button
- * Virtual Console/Frames: fix 4.10.3 regression causing frames to resize after configuration
- * Virtual Console/Cue List: playback can now be paused and resumed (see documentation)
- * Virtual Console/Cue List: added a dedicated stop button, with external controls
- * Virtual Console/XYPad: fixed computation of reversed fixture position (Luca Ugolini)
- * Plugins/OSC: fixed regression of receiving data from the wrong interface (David Garyga)
- * Plugins/OSC: fixed regression causing not receiving data anymore when changing the input profile (David Garyga)
- * Plugins/MIDI: distinguish MIDI beat clock start and stop (see documentation)
- * Input Profiles Editor: it is now possible to define button custom feedbacks in a profile (see documentation)
- * New input profile: Novation Launchpad Pro (thanks to David Giardi)
- * New RGB script: Balls (color) (thanks to Rob Nieuwenhuizen)
- * Fixture updated: Starway MaxKolor-18 (thanks to Thierry Rodolfo and Robert Box)
- * Fixture updated: Cameo LED RGBW PAR64 18x8W (thanks to Lukas)
- * New fixture: American DJ Mega QA Par38 (thanks to Nathan Durnan)
- * New fixture: Martin MAC 250 Wash (thanks to Robert Box)
- * New fixture: Luxibel LX161 (thanks to Freddy Hoogstoel)
- * New fixture: Stairville MH-X60th LED Spot (thanks to Jasper Zevering)
- * New fixture: Cameo CLHB400RGBW (thanks to Mihai Andrei)
- * New fixture: Showlite Flood Light Panel 144x10mm LED RGBW (thanks to Ex)
- * New fixture: Color Imagination LedSpot 90 (SI-052), Robe Spot 575 XT (thanks to DJ Ladonin)
- * New fixture: Chauvet Mini Kinta (thanks to Jonathan Wilson)
- * New fixture: Eurolite LED ML-56 QCL RGBW-RGBA 18x8W (thanks to Matthijs ten Berge)
- * New fixture: High End Systems TechnoSpot (thanks to Tom Moeller)
- * New fixtures: American DJ Inno Pocket Spot Twins, Fog Fury 3000 WiFly, Event Bar Pro (thanks to MaBonzo)
- * New fixtures: American DJ Galaxian Gem IR, Vizi Roller Beam 2R (thanks to MaBonzo)
- * New fixture: Ayra ERO 506 (thanks to Bert Heikamp)
- * New fixture: Ayrton Arcaline 100 RGB, Martin Magnum Hazer (thanks to Thierry Rodolfo)
- * New fixtures: American DJ Asteroid 1200, Eurolite GKF-60, Eurolite LED FE-700 (thanks to Flox Garden)
- * New fixtures: Antari X-310 Pro Fazer, lightmaXX CLS-2 (thanks to Flox Garden)
- * New fixture: Beamz MHL90 Wash 5x18W RGBAW-UV (thanks to Hans Erik Tjelum)
- * New fixture: PR Lighting Pilot 150 (thanks to David Read)
- * New fixture: lightmaXX Platinum CLS-1 (thanks to Marc Geonet)
-
- -- Massimo Callegari Sun, 29 May 2016 12:13:14 +0200
-
-qlcplus (4.10.3a) stable; urgency=low
-
- * Scripts: Fix 4.10.3 regression that breaks time values parsing (David Garyga)
- * RGBMatrix Editor: Fix 4.10.3 regression where QLC+ hangs on duration < 20ms (David Garyga)
-
- -- Massimo Callegari Wed, 9 Mar 2016 22:03:14 +0200
-
-qlcplus (4.10.3) stable; urgency=low
-
- * Engine: Fix intensity channels forced back to HTP after LTP not working correctly (David Garyga)
- * Engine: Fix functions with low intensity killing current fade outs (David Garyga)
- * Audio Capture: Fix crash when selecting another audio input while a capture is running (David Garyga)
- * Audio Capture: Fix crash when trying to use a wrongly configured audio input (David Garyga)
- * Scene Editor: Remember Channels Groups values when saving and loading a workspace (David Garyga)
- * Scene Editor: Remember fixtures even with no activated channel (David Garyga)
- * RGBMatrix Editor: Fix preview now working when fade in > 0 (David Garyga)
- * RGBMatrix Editor: Fix length of fadeout on the preview (David Garyga)
- * Show Manager: Fix crash when editing the total time of an empty chaser (David Garyga)
- * Show Manager/Function Selection: Fix sequences always displayed even with Chasers and Scenes both filtered out (David Garyga)
- * Speed Dials: Fix display and input of the milliseconds field, update precision from 10ms to 1ms (David Garyga)
- * Input/Output Manager: Forbid deleting universes in the middle of the list, this prevents a lot of bugs and crashes (David Garyga)
- * Simple Desk: the number of faders is now dynamic depending on the window size (unless forced via config file)
- * Plugins/ArtNet: Fix input and output initialization conflict that results in no input (David Garyga)
- * Plugins/ArtNet: Allow sending and receiving ArtNet on several different interfaces (David Garyga)
- * Plugins/ArtNet: Allow selecting a different ArtNet input universe (David Garyga)
- * Plugins/ArtNet: Fix configuration issue that prevents setting a parameter back to its default value (David Garyga)
- * Plugins/OSC: Fix configuration issue that prevents setting a parameter back to its default value (David Garyga)
- * Plugins/OSC: OSC Output values range from 0.0 to 1.0
- * Plugins/OSC: Properly handle OSC bundles (restores Lemur compatibility)
- * Virtual Console: Fix copy of a frame containing a submaster slider resulting in a broken submaster (David Garyga)
- * Virtual Console/Slider: Enable function filters in playback function selection (David Garyga)
- * Virtual Console/Slider: Allow to force to LTP color channels controlled by a Click & Go button
- * Virtual Console/Solo Frame: Fix sliders in playback mode not actually stopping the attached function when the slider reaches 0 (David Garyga, thanks to Tubby)
- * Virtual Console/Animation: Can now be used in solo frames (David Garyga)
- * Virtual Console/Frames: fix page cloning of a nested multipage frame
- * Virtual Console/Frames: fix disabling frame pages. Now widgets get actually deleted
- * Web access: fixed custom fixtures loading
- * Web access: added a DMX keypad that can be accessed from the Simple Desk (thanks to Santiago Benejam Torres)
- * Input profiles: added Behringer BCR2000 (thanks to Michael Trojacher)
- * Input profiles: added Lemur iPad Studio Combo
- * RGB Scripts: added Strobe script (thanks to Rob Nieuwenhuizen)
- * New fixtures: Stellar Labs ECO LED PAR56, Chauvet Colorpalette II (thanks to Jimmy Traylor)
- * New fixtures: Chauvet: COLORado 1 Solo, Ovation FD-165WW, Rogue RH1 Hybrid, COLORdash Par Hex 12, COLORdash Accent Quad (thanks to Robert Box)
- * New fixtures: Chauvet: Vue 1.1, Intimidator Spot 100 IRC, Abyss USB, COREpar 40 USB, COREpar UV USB (thanks to Robert Box)
- * New fixtures: Chauvet: Intimidator Scan 305 IRC, Intimidator Barrel 305 IRC, SlimPAR T6 USB, SlimBANK TRI-18 (thanks to Robert Box)
- * New fixtures: Eurolite LED CLS-9 QCL RGBW 9x8W 12, JB-Lighting A12 Tunable White, SGM G-Profile (thanks to Robert Box)
- * New fixtures: Coemar Par Lite LED RGB, OXO LED Funstrip DMX, American DJ Stinger II (thanks to Robert Box)
- * New fixture: Chauvet LED PAR 64 Tri-C (thanks to Jungle Jim and Robert Box)
- * New fixtures: American DJ VBar, American DJ Jellydome, Briteq LDP Powerbar 6TC/12TC (thanks to Thierry Rodolfo)
- * New fixture: Sagitter Slimpar 18 RGB (thanks to Daniele Fogale)
- * New fixture: Microh LED Tri Bar (thanks to Michael Tughan)
- * New fixture: American DJ 12P Hex Pearl (thanks to Ethan Moses)
- * New fixture: JB-Lighting JBLED A7 (thanks to BLACKsun)
- * New fixture: Chauvet COREpar 80 USB (thanks to Chris Gill)
- * New fixture: Stairville DJ Lase 25+25-G MK-II (thanks to galaris)
- * New fixtures: PR Lighting XR 230 Spot, PR Lighting XLED 1037 (thanks to Ovidijus Cepukas)
- * New fixtures: Futurelight DJ-Scan 600, Eurolite LED PAR-64 RGBW+UV (thanks to Ovidijus Cepukas)
- * New fixtures: Varytec LED Pad 7 BA-D, American DJ X-Scan LED Plus, Showtec Blade Runner (thanks to DjProWings)
- * New fixture: Involight LED CC60S (thanks to Stephane Hofman)
- * New fixture: Stairville MH-x200 Pro Spot (thanks to Mirek Škop)
- * New fixtures: Varytec LED Giga Bar 4 MKII, Eurolite LED KLS Laser Bar FX Light Set (thanks to Daniel Schauder)
- * New fixture: Chauvet Mayhem (thanks to Jonathan Wilson)
- * New fixture: Ayra TDC Agaricus (thanks to Rob Nieuwenhuizen)
- * New fixture: American DJ Pinspot LED Quad DMX (thanks to Christian Polzer)
- * New fixture: Stairville AF-180 LED Fogger Co2 FX (thanks to Johannes Uhl)
-
- -- Massimo Callegari Sun, 6 Mar 2016 20:21:22 +0200
-
-qlcplus (4.10.2) stable; urgency=low
-
- * Engine: added support for devices hotplug (DMX USB, MIDI, HID, Peperoni)
- * Engine: Universe passthrough data is now merged with QLC+ output, it is not affected by QLC+ processing
- (except for blackout) and it appears in the DMX monitor (Jano Svitok)
- * Audio: fixed playback of 24/32 bit wave files and Show Manager waveform preview
- * DMX Dump: it is now possible to dump DMX values on an existing Scene
- * ClickAndGo Widgets: Preset widgets now display the channel name on top of the capability list (David Garyga)
- * Function Manager: fix startup Function not cleared when deleting it (David Garyga)
- * Function Manager: highlight current startup Function when opening the Function selection dialog (David Garyga)
- * Function Selection: Don't lose the current selection when changing the function type filter (David Garyga)
- * Show Manager: fixed looped functions never stopping with certain durations (Jano Svitok)
- * Show Manager: fixed copy/paste of an existing Chaser
- * Show Manager: fix crashes when copying a sequence on an empty track (David Garyga)
- * Show Manager: repair conflicting sequences when loading a broken workspace (David Garyga)
- * EFX Editor: removed the intensity control. Please use separate Scenes for that
- * Virtual Console/Slider: fixed copy of the channel monitor mode (David Garyga)
- * Virtual Console/Slider: in level mode, activate only in operate mode and don't hold forced LTP channels (David Garyga)
- * Virtual Console/Slider: in playback mode, ignore the fade in/fade out of the attached Function (David Garyga)
- * Virtual Console/XYPad: added Fixture Group preset, to control a subgroup of Fixtures (see documentation)
- * Virtual Console/XYPad Properties: fixture ranges can now be set in degrees, percentage or DMX values
- * Virtual Console/XYPad Properties: fix manual input selection for presets (David Garyga)
- * Virtual Console/Cue List: improved mixed usage of crossfader and next/previous buttons (David Garyga)
- * Virtual Console/Cue List: fix effect of a submaster slider on a Cue List in crossfader mode (David Garyga)
- * Virtual Console/Cue List: fix crash when adding steps to the chaser being run by a Cue List (David Garyga)
- * Virtual Console/Audio Triggers: fix virtual console buttons triggering (David Garyga)
- * Virtual Console/Input Selection: allow custom feedbacks only on an assigned source and don't crash (David Garyga)
- * DMX Monitor: Fix strobing in 2D view (Jano Svitok)
- * Fixture Editor: Fix crash in the Channel Editor (David Garyga)
- * Plugins/uDMX: added support for AVLdiy.cn clone (thanks to Vitalii Husach)
- * Plugins/DMXUSB: (Linux) fixed data transmission of DMX4ALL NanoDMX
- * Input Profiles: added an option for Buttons to always generate a press/release event
- * New input profile: Touch OSC Automat5
- * New input profile: Novation Launch Control (thanks to Giacomo Gorini)
- * Updated fixture: Stairville xBrick Full-Colour 16X3W (thanks to Rico Hansen)
- * Updated fixture: Stairville MH-100 Beam 36x3 LED (thanks to Antoni J. Canos)
- * Updated fixture: American DJ Revo 3 (thanks to David Pilato)
- * New fixture: American DJ Dotz Flood
- * New fixture: Chauvet COLORdash Par Quad-7
- * New fixtures: Robe ColorWash 1200E AT, American DJ Starburst, Chauvet LED PAR 64 Tri-B (thanks to Robert Box)
- * New fixtures: Cameo Multi Par 3, HQ Power VDPL110CC LED Tri Spot, Showtec LED Pixel Track Pro (thanks to Robert Box)
- * New fixtures: American DJ: Inno Pocket Z4, On-X, WiFly EXR Dotz Par, WiFly EXR HEX5 IP, COB Cannon Wash Pearl (thanks to Robert Box)
- * New fixtures: BoomTone DJ Sky bar 288 LED, BoomToneDJ Strob LED 18, BoomToneDJ Froggy LED RGBW (thanks to Didou)
- * New fixture: iSolution iMove 250W (thanks to Thierry Rodolfo)
- * New fixture: Talent BL63 10" LED Bar (thanks to FooSchnickens)
- * New fixtures: Contest Oz-37x15QC, Evora DUO B2R, Evora Beam 5R, Evora Beam 15R (thanks to Jan Lachman)
- * New fixtures: Blizzard Lighting Lil G, Pixellicious, Lo-Pro CSI (thanks to Alton Olson)
- * New fixtures: Stairville LED Matrix Blinder 5x5, Showtec Power Spot 9 Q6 Tour V1 (thanks to Samuel)
- * New fixture: Blizzard Lighting StormChaser (thanks to Brent)
- * New fixtures: Showtec Explorer 250 Pro MKII, Showtec Pixel Bar 12 (thanks to Henk de Gunst)
- * New fixture: Philips Selecon PLProfile1 MkII (thanks to Freasy)
- * New fixture: PSL Strip Led RGB code K2014 (thanks to Lorenzo Andreani)
- * New fixture: Chauvet SlimPar Pro Tri (thank to Bulle)
- * New fixture: Chauvet GigBar IRC (thanks to JD-HP-DV7 and Jungle Jim)
- * New fixtures: Ghost Green 30, KOOLlight 3D RGB Laser, Mac Mah Mac FOG DMX (thanks to David Pilato)
-
- -- Massimo Callegari Sun, 13 Dec 2015 20:21:22 +0200
-
-qlcplus (4.10.1) stable; urgency=high
-
- * Virtual Console/Cue List: improved step fader behaviour (David Garyga)
- * Plugins/DMXUSB: Fixed regression affecting Linux users and OSX users using the libFTDI interface
- * Plugins/ArtNet/E1.31/OSC: Improved network interfaces detection
- * New fixture: Enterius EC-133DMX (thanks to Krzysztof Ratynski)
- * New fixture: Showtec Dragon F-350 (thanks to Jasper Zevering)
- * New fixtures: JB Systems Lounge Laser DMX, JB Systems Super Solar RGBW (thanks to Robert Box)
-
- -- Massimo Callegari Wed, 21 Oct 2015 20:21:22 +0200
-
-qlcplus (4.10.0) stable; urgency=low
-
- * Channel Groups: Fix crashes related to invalid channels (David Garyga)
- * Chaser: Fix flickering issue when chaser order is Random (David Garyga)
- * Engine: some more fixes on forced HTP/LTP channels
- * Engine: fixed 4.9.x regression causing QLC+ to hang at the end of audio playback
- * RGB Matrix Audio Spectrum: Fix crash when audio input volume is set to zero (David Garyga)
- * RGB Matrix: Fix 4.9.1 regression which makes fading of green and blue colors not smooth (David Garyga)
- * RGB Matrix: Introduced blending mode between matrices (see documentation)
- * Audio Input: Fix crashes when selecting another audio input device while an audio input driven function/widget is running (David Garyga)
- * Audio Input: It is now possible to select the audio input format (sample rate and channels)
- * Video: fixed playback from a time offset (where possible)
- * Video: (Windows) Videos now have a black background, like all the other platforms
- * Add Fixture dialog: Generic fixtures don't take the number of channels of the previously selected fixture (David Garyga)
- * Add Fixture dialog: Fix address 512 not usable by adding several fixtures at a time (David Garyga)
- * Scene Editor: correctly select the fixture tab when switching from tabbed/all channels view
- * EFX Editor: it is now possible to use an EFX on RGB channels (thanks to Giorgio Rebecchi)
- * Collection Editor: added preview button (thanks to Giorgio Rebecchi)
- * Fixture Remap: fixed remapping of EFX functions
- * Function Wizard: improved creation of color scenes for RGB panels
- * Function Wizard: automatically set a gobo picture (if available) on buttons attached to gobo Scenes
- * Show Manager: Fix some cursor teleportation issues (David Garyga)
- * Simple Desk: Fix cue playback on universes 2+ (David Garyga)
- * Simple Desk: Fix crash when selecting recently added universe (David Garyga)
- * Simple Desk: Added reset buttons to reset a single channel
- * Simple Desk: Fix page count when channels per page does not divide 512 (Jano Svitok, reported by Florian)
- * Virtual Console: fixed Grand Master not sending feedbacks
- * Virtual Console/Input Controls: implemented custom feebacks. For now used only by VC buttons
- * Virtual Console/Solo Frame: Option to allow mixing of sliders in playback mode (David Garyga)
- * Virtual Console/Speed Dial: Introduced multiplier/divisor, apply and presets buttons (see documentation)
- * Virtual Console/Button: allow to set a background picture
- * Virtual Console/Cue List: Options for the Next/Previous buttons behavior (David Garyga)
- * Virtual Console/Cue List: Fixed playback of a Chaser in reverse order (David Garyga)
- * Virtual Console/Cue List: Added a new "Steps" mode for the side faders (see documentation)
- * Virtual Console/Cue List: Allow to resize columns to 0 pixels, to completely hide them
- * Virtual Console/XYPad: Introduced presets, including the usage of existing EFX and Scenes (see documentation)
- * Virtual Console/XYPad: Fix DMX output not working when going to Operate mode while the XYPad is disabled (David Garyga, thanks to bestdani)
- * Input Profiles: added an option for MIDI profiles to feedback a Note Off or a Note On with 0 velocity. APCMini now works out of the box. (Jano Svitok)
- * Input Profiles: Improved BCF2000 Input profile (thanks to Lorenzo Andreani)
- * Plugins/MIDI: (Linux) Fixed data transmission to multiple devices (thanks to Adrian Kapka)
- * Plugins/MIDI: fixed Program Change handling on OSX and Windows
- * Plugins/MIDI: (Windows) do not close the device when sending SysEx data
- * Plugins/ArtNet: it is now possible to enter an arbitrary output IP
- * Plugins/OSC: it is now possible to enter an arbitrary output IP
- * Plugins/E1.31: added stream priority to configuration (thanks to Nathan Durnan)
- * Plugins/E1.31: added unicast support (David Garyga)
- * Plugins/DMXUSB: fixed close/open sequence on a Enttec Pro input line
- * Web Access: implemented frames collapse functionality
- * RGB Scripts: added Plasma Colors script (thanks to Nathan Durnan)
- * Fixture Editor: Channel capability editing is now done in a single window
- * Updated fixture: Showtec Indigo 6500 (thanks to Jochen Becker)
- * New fixture: Ayra ComPar 20 (thanks to Rob Nieuwenhuizen)
- * New fixture: XStatic X-240Bar RGB (thanks to Nathan Durnan)
- * New fixture: Venue ThinPAR 38 (thanks to Thierry Rodolfo)
- * New fixtures: Contest MiniCube-6TCb, Eurolite LED FE-1500, Lightronics FXLD618C2I, JB Systems COB-4BAR (thanks to Robert Box)
- * New fixtures: Eurolite LED KLS-401, Chauvet Intimidator Wash Zoom 350 IRC, Equinox Party Par LED PAR 56 (thanks to Robert Box)
- * New fixtures: Robe Robin MiniMe, PR Lighting Pilot 575, Stairville DJ Lase 150-RGY MkII, JB Systems Dynaspot (thanks to Robert Box)
- * New fixtures: American DJ Hyper Gem LED, Robe ColorSpot 575 AT, Kam iLink All Colour Models (thanks to Robert Box)
- * New fixtures: Chauvet Intimidator Wave 360 IRC, Varytec LED PAR56 (thanks to Habefaro)
- * New fixture: American DJ Fog Fury Jett (thanks to Dean Clough)
- * New fixtures: Eurolite TB-250, Futurelight DJ-HEAD 575 SPOT, GLX Lighting Power LED Beam 38 Narrow (thanks to Ovidijus Cepukas)
- * New fixture: Pro-Lights UVStrip18 (thanks to Alessandro Grechi)
- * New fixtures: American DJ Inno Pocket Beam Q4, Martin ZR24/7 Hazer, Blizzard Lighting Stimul-Eye (thanks to George Qualley)
- * New fixture: American DJ Mega TriPar Profile Plus (thanks to George Qualley)
- * New fixtures: Pro-Lights SmartBat, Robe ClubWash 600 CT (thanks to Lorenzo Andreani)
- * New fixture: Stairville Show Bar Tri 18x3W RGB (thanks to Udo Besenreuther)
- * New fixtures: Blizzard Lighting Rokbox Infiniwhite, Chauvet COREpar 80 (thanks to Chris Gill)
- * New fixture: Cameo CL Superfly HP (thanks to Stuart Brown)
- * New fixture: American DJ Event Bar Q4 (thanks to Maxime Bissonnette-Théorêt)
- * New fixture: Cameo LED Moving Head 60W CLMHR60W (thanks to Jasper Zevering)
- * New fixtures: Proel PLLED64RGB, Litecraft LED PAR 64 AT3, Robe Robin 300E Beam (thanks to Mihai Andrei)
- * New fixture: Electroconcept SPC029 (thanks to Bulle)
- * New fixture: Microh Plasmawave 1 RGB (thanks to Rommel)
-
- -- Massimo Callegari Sun, 18 Oct 2015 20:21:22 +0200
diff --git a/debian/changelog-old b/debian/changelog-old
index 118149f319..e02a5031a2 100644
--- a/debian/changelog-old
+++ b/debian/changelog-old
@@ -1,3 +1,538 @@
+qlcplus (4.11.2) stable; urgency=low
+
+ * engine: fix crash caused by an invalid IO mapping
+ * engine: fix intensity override not considered during fade outs
+ * UI/Function Manager: fixed keyboard shortcut conflicts and document them
+ * UI/Function Manager: allow to import multiple of audio/video files at once
+ * UI/Channel Groups: added expand/collapse all button helpers
+ * UI/Chaser Editor: add a button to shuffle the selected Chaser steps (thanks to Felix Edelmann)
+ * UI/RGBMatrix Editor: fix preview not updating on pattern change when play button is on
+ * Show Manager: fix crash when adding a Sequence after deleting one
+ * Show Manager: fix crash when editing a Sequence bound to a deleted Scene
+ * Show Manager: fix items start time indication when dragging
+ * Virtual Console/Slider: fix submaster initial value not applied and inverted mode
+ * Virtual Console/Slider: added 'value catching' option for external controller faders (Lukas Jähn proposal)
+ * Virtual Console/Slider: fix values range when switching between Slider and Knob appearance
+ * Virtual Console/Slider: react on Scene flashing when in playback mode
+ * Virtual Console/Knob: fix DMX values not updated when interacting with the mouse wheel
+ * Virtual Console/Cue List: allow to select a step with next/previous buttons during pause
+ * Virtual Console/Cue List: go to the right chaser step after a pause (thanks to Krzysztof Walo)
+ * Virtual Console/Frame: fix regression preventing to send the disable feedback
+ * Web Access: added support for VC Buttons in Flash mode (Sylvain Laugié)
+ * Web Access: added support for VC Frame circular page scrolling (Sylvain Laugié)
+ * Web Access: update AudioTriggers state when changed from QLC+ (Sylvain Laugié)
+ * plugins/udmx: added 'channels' configuration parameter (see documentation)
+ * plugins/E1.31: fix crash on wrong packet length (David Garyga)
+ * New fixture: DTS XR7 Spot (thanks to Nicolò Zanon)
+ * New fixture: Ledj Slimline 12Q5 Batten (thanks to Dean Clough)
+ * New fixture: Ayra Compar Kit 1 (thanks to eigenaardiger)
+ * New fixtures: GLP Impression X4 S, Eurolite LED KLS-2500 (thanks to Mitsch)
+ * New fixture: Chauvet Intimidator Spot 255 IRC (thanks to Ham Sadler)
+ * New fixtures: Chauvet Geyser RGB, Geyser P6 (thanks to Andrew)
+ * New fixture: Chauvet Rotosphere Q3 (thanks to Eric Sherlock)
+ * New fixture: Showtec Compact Par 7 Q4 (thanks to Alexander)
+ * New fixture: Contest Delirium (thanks to Vincent)
+ * New fixture: Solena Mini Par 12, Max Bar 28 RGB (thanks to Nathan Durnan)
+ * New fixtures: Showtec Phantom 65, Laserworld PRO-800RGB (thanks to Piotr Nowik)
+ * New fixture: Chauvet MiN Spot RGBW (thanks to Jungle Jim)
+ * New fixtures: Showtec Shark Wash One, American DJ Vizi Hex Wash7 (thanks to Georg Müller)
+ * New fixture: Showtec Shark Beam FX One (thanks to Mats Lourenco)
+ * New fixture: Stairville novaWash Quad LED (thanks to Luke Bonett)
+ * New fixtures: Eurolite Party TCL Spot RGB, Expolite TourSpot 60, Expolite TourStick 72 RGBWA (thanks to Dirk J)
+ * New fixture: Chauvet Hemisphere 5.1, Trident, Scorpion Storm RGX (thanks to Francois Blanchette)
+ * New fixture: Briteq COB Slim 100-RGB (thanks to Thierry)
+ * New fixture: American DJ UB 12H (thanks to Jason R Johnston)
+ * New fixture: American DJ Mega Hex Par (thanks to Ben C)
+ * New fixture: Cameo Q SPOT 15 RGBW (thanks to Antoine Houbron)
+ * New fixture: lightmaXX Platinum Line Flat Par COB (thanks to Leonardo)
+ * New fixture: Stairville LED Blinder 2 COB 2x65W (thanks to chritoep)
+ * New fixture: lightmaXX LED PAR 64 (thanks to Johannes Felber)
+ * New fixture: Cameo Thunder Wash Series (thanks to JP)
+ * New fixtures: Briteq BT 575S, Stairville MH-x30 LED Beam (thanks to Andres Robles)
+ * New fixture: beamZ LED FlatPAR-154 (thanks to Jászberényi Szabolcs)
+ * New fixtures: Eurolite THA-100F COB, Cameo Tribar 200 IR (thanks to David Morgenschweis)
+ * New fixture: beamZ BT310 LED FlatPAR 12x8W 4-1 DMX IR (thanks to Mark)
+ * New fixture: Fun Generation PicoWash 40 Pixel Quad LED (thanks to Harm Aldick)
+ * New fixtures: American DJ Entourage, Elumen8 MS-700PE, Ibiza PAR LED 712IR (thanks to Tim Cullingworth)
+ * New fixtures: Martin MAC 401 Dual RGB Zoom, MAC 401 Dual CT Zoom, Stairville MH-z720 (thanks to Tim Cullingworth)
+ * New fixture: Fun Generation SePar Quad UV (thanks to Helmet)
+
+ -- Massimo Callegari Thu, 19 Apr 2018 20:21:22 +0200
+
+qlcplus (4.11.1) stable; urgency=low
+
+ * engine: fixed audio files detection by prioritizing sndfile over mad
+ * engine: fixed HTP/LTP forced channels not set correctly
+ * engine: keep track of input/output device lines even if they are disconnected
+ * engine/Script: add blackout:on and blackout:off commands (Jano Svitok)
+ * engine/Script: do not keep empty trailing lines when saving a workspace
+ * UI: it is now possible to detach a QLC+ context tab on a separate window by double clicking on it
+ * UI/RGB Panel: added RBG pixel type (thanks to Peter Marks)
+ * UI/Remap: fixed RGB Panels remapping
+ * UI/Input Output Manager: added a button to enable/disable USB hotplugging (disabled by default)
+ * UI/Function Live Edit: restore basic live editing of Sequences
+ * UI/RGB Matrix Editor: fixed save to Sequence feature
+ * UI/Function Manager: when cloning a Sequence, clone the bound Scene too
+ * Virtual Console/Button: highlight border with orange color when in "monitoring" state
+ * Virtual Console/Slider: fix DMX values not updated when interacting with the mouse wheel, keyboard or Click And Go button
+ * Virtual Console/Slider: fix level mode values range scaling
+ * Virtual Console/XYPad: the speed of a running EFX preset can now be controlled by a Speed Dial widget
+ * RGB Scripts: added "Noise", "3D Starfield", "Random pixel per row" and "Random pixel per row multicolor" (thanks to Doug Puckett)
+ * Web access: added basic authentication support (thanks to Bartosz Grabias)
+ * Web access: fixed solo frames collapse state
+ * Web access: update feedbacks when a slider is moved
+ * New fixtures: IMG Stageline BEAM-40 WS/RGBW, Fun-Generation LED Diamond Dome (thanks to Tolmino Muccitelli)
+ * New fixture: Elation Cuepix Batten (thanks to Saul Vielmetti)
+ * New fixture: Clay Paky Tiger Scan HMI 575/1200 (thanks to Daris Tomasoni)
+ * New fixture: Litecraft WashX.21 (thanks to Hannes Braun)
+ * New fixtures: Briteq Stagepainter 12, Nicols IP Wash 120, Showtec LED Powerline 16 Bar (thanks to Fredje Gallon)
+ * New fixtures: Nicols Movelight, Nicols Birdy Wash 122, Briteq Giga Flash RGB (thanks to Fredje Gallon)
+ * New fixtures: Litecraft PowerBar AT10.sx, Stairville MH-z1915 (thanks to Thorben / Fredje)
+ * New fixtures: Martin MAC 700 Wash, ADB Warp M (thanks to Thorben)
+ * New fixture: Laserworld CS-1000RGB Mk II (thanks to Piotr Nowik)
+ * New fixture: Chauvet COLORrail IRC (thanks to Lane Parsons)
+ * New fixtures: American DJ COB Cannon Wash DW, lightmaXX Vega Zoom Wash Beam (thanks to Florian Gerstenlauer)
+ * New fixture: Martin Rush MH5 Profile (thanks to Falko)
+ * New fixture: Cameo Flash Bar 150 (thanks to Kevin Wimmer)
+ * New fixtures: Chauvet FXpar 9, IMG Stageline Wash-40 LED (thanks to PeterK)
+ * New fixtures: JB Systems iRock 5C, JB Systems LED Devil (thanks to Andres Robles)
+ * New fixtures: beamZ BAC406, Geni Mojo Color Moc (thanks to Mark Sy)
+ * New fixtures: Stairville MH-250 S, Chauvet GigBAR 2, Pro-Lights Onyx (thanks to Freasy)
+ * New fixtures: Coemar ProSpot 250 LX, Showtec Kanjo Spot 60 (thanks to Flo Edelmann)
+
+ -- Massimo Callegari Sat, 28 Oct 2017 12:13:14 +0200
+
+qlcplus (4.11.0) stable; urgency=low
+
+ * engine: fixed setting start/end color while a RGB Matrix is running
+ * engine: fixed crash when pausing a Show with an unavailable audio file
+ * engine: major rework of Sequences. Projects using them need to be migrated
+ * UI: enabled Alt key combinations on macOS to behave like other platforms (thanks to Matt Mayfield)
+ * UI/RGB Panel: added panel direction (thanks to Raivis Rengelis)
+ * UI/Fixture Manager: added weight and power consumption information on fixtures/universe selection (Chris de Rock idea)
+ * UI/Scene Editor: preserve fixture tab order when fixtures with no channels set are present
+ * UI/RGB Matrix Editor: allow the preview to run even in operate mode
+ * UI/Audio Editor: added the possibility to loop an audio file (thanks to Raivis Rengelis)
+ * UI/Simple Desk: fixed crash when changing values from a channel group in "fixtures view" mode
+ * Virtual Console: prevent unwanted feedbacks from widgets in inactive Frame pages (thanks to Lukas Jähn)
+ * Virtual Console: fixed manual selection of input channels not considering Frame pages (thanks to Lukas Jähn)
+ * Virtual Console: fixed input profiles channels not honored on frame pages other than the first (thanks to Lukas Jähn)
+ * Virtual Console/Slider: improved level monitoring with the possibility to act like a Simple Desk slider (see documentation)
+ * Virtual Console/Frame: fixed 4.10.5b regression disabling widgets when switching page in design mode
+ * Virtual Console/Frame: fixed key controls not copied when cloning a frame (thanks to Lukas Jähn)
+ * Virtual Console/Frame: added the possibility to jump directly to a page and assign page names (thanks to Lukas Jähn)
+ * Virtual Console/Cue List: improved linked crossfade to perform an additive blending between steps (see documentation)
+ * Virtual Console/Speed Dial: improved tap button blinking and feedbacks (thanks to Lukas Jähn)
+ * Virtual Console/Speed Dial: it is now possible to copy/paste factors (thanks to Jan Dahms)
+ * Virtual Console/Clock: added external input support for countdown and stopwatch modes (thanks to Lukas Jähn)
+ * Plugins/OSC: added channel number calculator in configuration page to help integrating new controllers
+ * Plugins/Loopback: fixed spurious values emitted when a lot of channels are looped
+ * Web access: fixed VC Slider in percentage mode and inverted appearance (thanks to Bartosz Grabias)
+ * Web access: support VC Slider reduced range when in level mode
+ * Web access: improved getChannelsValues and added Simple Desk reset per-channel (sdResetChannel API)
+ * Web access: implemented keypad increase/decrease buttons (thanks to Santiago Benejam Torres)
+ * New input profile: Zoom R16 (thanks to Benedict Stein)
+ * New MIDI template: Akai APC40 MK2 Ableton mode (thanks to Branson Matheson)
+ * New fixture: American DJ FREQ 5 Strobe (thanks to Martin Bochenek)
+ * New fixture: Martin Rush MH3 (thanks to Ed Middlebrooks)
+ * New fixture: Eurolite LED ACS BAR-12 (thanks to Michael Horber)
+ * New fixtures: Martin Rush MH6 Wash, Cameo Studio PAR 64 RGBWA UV 12W (thanks to Piotr Nowik)
+ * New fixtures: ETEC Moving Spot 60E, Cameo CLM PAR COB 1, Showtec Compact Power Lightset COB (thanks to Freasy)
+ * New fixture: Stairville Tri Flat PAR Profile 5x3W RGB (thanks to Freasy)
+ * New fixture: American DJ Punch LED Pro (thanks to Benedict Stein)
+ * New fixtures: Contest Mini-Head 10W, Contest Evora B2R (thanks to Fredje Gallon)
+ * New fixture: Robe DJ Scan 150 XT (thanks to Allan Madsen)
+ * New fixtures: Futurelight PRO Slim PAR-12 HCL, PRO Slim PAR-12 MK2 HCL, Showtec Power Spot 9 Q5 (thanks to Lukas Jähn)
+ * New fixture: Showtec XS-1W Mini Moving Beam (thanks to Habefaro)
+ * New fixtures: Stage Right Stage Wash 18Wx18 LED PAR, Stage Right 7x20W COB LED Theater PAR (thanks to Collin Ong)
+ * New fixture: Cameo CLPIXBAR450PRO, CLPIXBAR650PRO (thanks to Jean-Daniel Garcia & Jeremie Odermatt)
+ * New fixture: Clay Paky Alpha Beam 1500 (thanks to Louis Gutenschwager)
+ * New fixture: Stairville AFH-600 (thanks to Hannes Braun)
+ * New fixture: Involight LED MH77S (thanks to Jászberényi Szabolcs)
+ * New fixtures: ETEC LED PAR 64 18x10W RGBWA, LED PAR 64 18x15W RGBWA Zoom (thanks to Simon Orlob)
+ * New fixture: Chauvet Swarm Wash FX (thanks to Stephen Olah)
+ * New fixture: Clay Paky Alpha Spot HPE 575 (thanks to Rohmer)
+ * New fixture: Robe LED Blinder 196LT (thanks to Tim Cullingworth)
+ * New fixture: Chauvet COLORband T3 USB (thanks to Ian Nault)
+ * New fixtures: American DJ Dotz Matrix, Martin Jem Compact Hazer Pro,Geni Mojo Spin Master Series (thanks to Sam Brooks)
+ * New fixture: American DJ XS 400 (thanks to Jared)
+ * New fixture: Velleman VDP1500SM (thanks to Freddy Hoogstoel)
+ * New fixture: Chauvet Intimidator Spot 355Z IRC (thanks to Michael Clements)
+ * New fixture: CLF Tricolor Mini Par (thanks to Jaron Blazer)
+ * New fixture: Varytec LED Easy Move Mini Beam & Wash RGBW (thanks to Erik)
+ * New fixtures: Smoke Factory Tour-Hazer II, JB Systems Panther, Robe Spot 160 XT (thanks to Thierry Rodolfo)
+ * New fixture: American DJ LED Trispot (thanks to Patrick)
+ * New fixtures: Contest STB-520 1500W Strobe, Elumen8 COB Tri 4 Pixel Batten, Briteq Tornado 7 (thanks to Robert Box)
+ * New fixtures: American DJ 5P Hex, Pro-Lights Moonstone, Chauvet Intimidator Hybrid 140SR (thanks to Robert Box)
+ * New fixtures: Robe Robin DLX Spot (thanks to Robert Box)
+ * New fixture: ETC ColorSource PAR (thanks to Jon Rosen)
+ * New fixture: lightmaXX 5ive STAR LED (thanks to Thomas Weber)
+ * New fixture: Talent BL252A (thanks to Massimiliano Palmieri)
+ * New fixtures: Showtec: Infinity iW-1915, Infinity XPLO-15 LED Strobe (thanks to Daniele Fogale)
+ * New fixtures: Showtec: Infinity iB-5R, Compact Par 18 MKII, Phantom 20 LED Beam (thanks to Nicolò Zanon)
+ * New fixture: Griven Gobostorm Plus MK2 (thanks to Attilio Bongiorni)
+ * New fixture: Chauvet Freedom Stick (thanks to Jay Szewczyk)
+ * New fixture: Eurolite TMH-14, Chauvet Intimidator Trio (thanks to Chris de Rock)
+ * New fixture: Chauvet Scorpion Dual (thanks to Alan Chavis)
+ * New fixture: American DJ Ultra Hex Bar 12 (thanks to Rhavin)
+ * New fixture: Equinox Photon
+ * New fixture: QTX MHS-60 (thanks to Nerijus Mongirdas)
+ * New fixture: Eurolite LED TMH FE-600, MARQ Colormax Par64, Stairville CLB2.4 Compact LED PAR System (thanks to Klaus Muth)
+ * New fixture: Chauvet SlimPar Hex 6 (thanks to Yinon Sahar)
+ * New fixture: IMG Stageline PARL 20 DMX (thanks to Felix Pickenäcker)
+ * New fixtures: Pro-Lights SmartBatHEX, Fury FY250W, Fury FY250S (thanks to Lorenzo Andreani)
+ * New fixture: American DJ Ikon Profile (thanks to Ham Sadler)
+ * New fixture: HQ Power Aeron Wash 575, JB Systems Space Color Laser (thanks to Ricardo Mendes)
+ * New fixture: American DJ VPar (thanks to Eric Eskam)
+ * New fixtures: MARQ Gesture Beam/Wash 102, Colormax Bat, Gesture Spot 100 (thanks to John Yiannikakis)
+ * New fixtures: Chauvet COLORado 3P, Legend 330SR Spot, SlimPar HEX 3 (thanks to Kevin Zepp)
+ * New fixture: American DJ Mini Dekker (thanks to Chris Davis)
+ * New fixtures: American DJ Vizi BSW 300, Blizzard Lighting Flurry 5 (thanks to George Qualley)
+ * New fixtures: Pro-Lights PIXIEWASH, Accent1Q, CromoSpot300 (thanks to Tolmino Muccitelli)
+ * New fixtures: Involight LED MH50S, LED PAR 180, SBL 2000 (thanks to Facek)
+ * New fixture: Pro-Lights Miniruby (thanks to Dario Gonzalez)
+ * New fixture: Sagitter Smart DL Wash (thanks to Simu)
+ * New fixtures: Eurolite LED THA-250F, Pro-Lights StudioCOBFC (thanks to Andrea Ugolini)
+ * New fixture: American DJ Stinger Spot (thanks to Jason R. Johnston)
+ * New fixture: Stairville Blade Sting 8 RGBW Beam Mover
+
+ -- Massimo Callegari Sat, 24 Jun 2017 12:13:14 +0200
+
+qlcplus (4.10.5b) stable; urgency=high
+
+ * engine: fixed 4.10.5 regression on RGB Matrix preset step color calculation
+ * Virtual Console/Frame: fixed widgets disable state when switching pages
+ * Virtual Console: fixed SpeedDial and Animation widget presets feedbacks, and allow to use custom feedbacks
+ * Plugins/DMX USB: fixed 4.10.5 regression preventing to receive data from PRO devices
+ * Plugins/DMX USB: [Windows] fixed a long standing bug causing random crashes when receiving DMX data
+ * Plugins/MIDI: [macOS] further changes to support virtual ports
+ * New fixtures: Stairville M-Fog 1000 DMX, Cameo Superfly XS (thanks to Konni)
+ * New fixture: ColorKey WaferPar Quad-W 12 (thanks to Taylor)
+ * New fixture: Eurolite LED PARty RGBW (thanks to Heiko Fanieng)
+ * New fixture: lightmaXX Platinum CLS-1 (thanks to Marc Geonet)
+
+ -- Massimo Callegari Mon, 26 Dec 2016 12:13:14 +0200
+
+qlcplus (4.10.5a) stable; urgency=high
+
+ * engine: fixed playback of a chaser within a chaser
+
+ -- Massimo Callegari Mon, 12 Dec 2016 12:13:14 +0200
+
+qlcplus (4.10.5) stable; urgency=low
+
+ * Engine: added indigo to fixture channel colors (thanks to Axel Metzke)
+ * Engine: properly handle RGB Matrices with generic dimmers (Jano Svitok)
+ * UI/Function Manager: fix crash when trying to clone a folder (David Garyga)
+ * UI/RGB Matrix Editor: editor preview doesn't stop when testing the Function
+ * UI/Collection Editor: allow multiple selection and added Function reordering buttons
+ * UI/Remap: fixed universes list in target mapping
+ * UI/Remap: fixed wrong Scene remapping when mixing cloned and new fixtures
+ * UI/Remap: added remapping also of Fixture Groups
+ * Virtual Console/Frame: Show page number when collapsed (thanks to Matthias Gubisch)
+ * Virtual Console/Cue List: allow to choose playback buttons layout (Play/Pause + Stop or Play/Stop + Pause)
+ * Plugins/DMX USB: fixed crash happening on PRO devices when receiving a full universe
+ * Plugins/DMX USB: [MacOS] fixed regression caused by the Qt libraries on PRO devices
+ * Plugins/MIDI: [MacOS] added support for virtual ports, show only active devices and properly handle hotplug
+ * Fixture Editor: fixed minimum value of a new capability not updating correctly
+ * RGB Scripts: added One by one (Jano Svitok)
+ * New fixtures: Pro-Lights LumiPIX 12Q, Proel PLLEDMLBG (thanks to Andrea Ugolini)
+ * New fixtures: Stairville DCL Flat Par 18x4W CW/WW, Cameo LED MultiPAR CLM-PAR-COB1 (thanks to Freasy)
+ * New fixtures: High End Systems Studio Beam, lightmaXX EASY Wash 5IVE LED (thanks to Freasy)
+ * New fixture: iSolution iColor 4 (thanks to withlime)
+ * New fixtures: ETC ColorSource Spot, Blizzard Lighting LB-Par Hex (thanks to Robert Box)
+ * New fixtures: American DJ: Chameleon QBar Pro,DJ Vizi Beam RXONE, XS 600, Focus Spot Three Z (thanks to Robert Box)
+ * New fixture: JB-Lighting Varyscan P6, Cameo Wookie series, Cameo Hydrabeam series (thanks to Andres Robles)
+ * New fixture: Chauvet RotoSphere LED (thanks to Carl Eisenbeis)
+ * New fixture: Briteq Spectra 3D Laser (thanks to Robert Box + Freasy)
+ * New fixture: Martin MH2 Wash (thanks to John Yiannikakis + Freasy)
+ * New fixture: American DJ Flat Par Tri7X (thanks to Brian)
+ * New fixtures: Ledj Stage Color 24, 59 7Q5 RGBW, 59 7Q5 RGBA (thanks to Paul Wilton)
+ * New fixtures: American DJ: Inno Spot Elite, Stinger, Tri Phase (thanks to Piotr Nowik)
+ * New fixtures: Showtec Phantom 95 LED Spot, Futurelight PHS-260 (thanks to Piotr Nowik)
+ * New fixture: Blizzard Lighting Rocklite RGBAW (thanks to Larry Wall)
+ * New fixture: American DJ Comscan LED (thanks to Chris)
+ * New fixtures: PR Lighting XL 250/XL 700 Wash/XL 700 Spot, American DJ Accu Fog 1000 (thanks to István Király)
+ * New fixtures: Equinox Ultra Scan LED, Kam Powercan84W, QTX HZ-3 (thanks to Chris Moses)
+ * New fixture: Eurolite TMH-10 (thank to exmatrikulator)
+ * New fixture: Eurolite LED SLS 5 BCL, Robe Fog 1500 FT (thanks to Christian Hollbjär)
+ * New fixture: SGM Giotto Spot 400 (thanks to Mihai Andrei)
+ * New fixture: Pulse LEDBAR 320 (thanks to Allan Rhynas)
+ * New fixture: Equinox Swing Batten (thanks to Dean Clough)
+ * New fixture: Cameo Pixbar 600 PRO, Chauvet COLORado 1 Quad Zoom Tour (thanks to Andrew Hallmark)
+ * New fixture: Involight FM900 DMX (thanks to Jászberény Szabolcs)
+ * New fixture: Showtec Stage Blinder Series (thanks to Antoni J. Canós)
+ * New fixture: MARQ Gamut PAR H7 (thanks to Lance Lyda)
+ * New fixtures: Chauvet: SlimPAR QUV12 USB, SlimPAR PRO H USB, Scorpion Bar RG (thanks to Pete Mueller)
+ * New fixture: Stairville CLB8 Compact LED PAR System (thanks to Detlef Fossan)
+ * New fixture: Chauvet Cubix 2.0 (thanks to Jungle Jim)
+ * New fixture: Showtec Giant XL LED (thanks to Samuel Hofmann)
+ * New fixtures: SGM: Idea Beam 300, Idea Led Bar 100, Idea Spot 700, Newton 1200 (thanks to Oscar Cervesato)
+ * New fixtures: Pro Lights LumiPAR18QTour, Elation SIXPAR 200IP (thanks to Oscar Cervesato)
+ * New fixture: Stairville Beam Moving Head B5R, American DJ Flat Par TW12, Varytec Easy Scan XT Mini (thanks to Thierry Rodolfo)
+
+ -- Massimo Callegari Sat, 3 Dec 2016 12:13:14 +0200
+
+qlcplus (4.10.4) stable; urgency=low
+
+ * Scripts: Fix 4.10.3a regression that breaks values parsing (David Garyga)
+ * Engine: fix relative paths when opening a project from the command line
+ * Engine: improved the start/stop mechanism of Functions within a Show
+ * Chaser Editor: a newly created step is now selected automatically
+ * Scene Editor: fixed the tab order of the fixtures
+ * Show Manager: added the possibility to pause a Show leaving the lights on
+ * Show Manager/Audio: allow to display the waveform preview while playing a file
+ * UI/Function Selection: fix crash on workspaces where a scene ID is bigger than its sequence ID (David Garyga)
+ * UI/Video: fixed the fullscreen positioning on Windows
+ * Virtual Console/Animation: fix behavior issue when changing the associated function (David Garyga)
+ * Virtual Console/Frames: send feedbacks for the enable button
+ * Virtual Console/Frames: fix 4.10.3 regression causing frames to resize after configuration
+ * Virtual Console/Cue List: playback can now be paused and resumed (see documentation)
+ * Virtual Console/Cue List: added a dedicated stop button, with external controls
+ * Virtual Console/XYPad: fixed computation of reversed fixture position (Luca Ugolini)
+ * Plugins/OSC: fixed regression of receiving data from the wrong interface (David Garyga)
+ * Plugins/OSC: fixed regression causing not receiving data anymore when changing the input profile (David Garyga)
+ * Plugins/MIDI: distinguish MIDI beat clock start and stop (see documentation)
+ * Input Profiles Editor: it is now possible to define button custom feedbacks in a profile (see documentation)
+ * New input profile: Novation Launchpad Pro (thanks to David Giardi)
+ * New RGB script: Balls (color) (thanks to Rob Nieuwenhuizen)
+ * Fixture updated: Starway MaxKolor-18 (thanks to Thierry Rodolfo and Robert Box)
+ * Fixture updated: Cameo LED RGBW PAR64 18x8W (thanks to Lukas)
+ * New fixture: American DJ Mega QA Par38 (thanks to Nathan Durnan)
+ * New fixture: Martin MAC 250 Wash (thanks to Robert Box)
+ * New fixture: Luxibel LX161 (thanks to Freddy Hoogstoel)
+ * New fixture: Stairville MH-X60th LED Spot (thanks to Jasper Zevering)
+ * New fixture: Cameo CLHB400RGBW (thanks to Mihai Andrei)
+ * New fixture: Showlite Flood Light Panel 144x10mm LED RGBW (thanks to Ex)
+ * New fixture: Color Imagination LedSpot 90 (SI-052), Robe Spot 575 XT (thanks to DJ Ladonin)
+ * New fixture: Chauvet Mini Kinta (thanks to Jonathan Wilson)
+ * New fixture: Eurolite LED ML-56 QCL RGBW-RGBA 18x8W (thanks to Matthijs ten Berge)
+ * New fixture: High End Systems TechnoSpot (thanks to Tom Moeller)
+ * New fixtures: American DJ Inno Pocket Spot Twins, Fog Fury 3000 WiFly, Event Bar Pro (thanks to MaBonzo)
+ * New fixtures: American DJ Galaxian Gem IR, Vizi Roller Beam 2R (thanks to MaBonzo)
+ * New fixture: Ayra ERO 506 (thanks to Bert Heikamp)
+ * New fixture: Ayrton Arcaline 100 RGB, Martin Magnum Hazer (thanks to Thierry Rodolfo)
+ * New fixtures: American DJ Asteroid 1200, Eurolite GKF-60, Eurolite LED FE-700 (thanks to Flox Garden)
+ * New fixtures: Antari X-310 Pro Fazer, lightmaXX CLS-2 (thanks to Flox Garden)
+ * New fixture: Beamz MHL90 Wash 5x18W RGBAW-UV (thanks to Hans Erik Tjelum)
+ * New fixture: PR Lighting Pilot 150 (thanks to David Read)
+ * New fixture: lightmaXX Platinum CLS-1 (thanks to Marc Geonet)
+
+ -- Massimo Callegari Sun, 29 May 2016 12:13:14 +0200
+
+qlcplus (4.10.3a) stable; urgency=low
+
+ * Scripts: Fix 4.10.3 regression that breaks time values parsing (David Garyga)
+ * RGBMatrix Editor: Fix 4.10.3 regression where QLC+ hangs on duration < 20ms (David Garyga)
+
+ -- Massimo Callegari Wed, 9 Mar 2016 22:03:14 +0200
+
+qlcplus (4.10.3) stable; urgency=low
+
+ * Engine: Fix intensity channels forced back to HTP after LTP not working correctly (David Garyga)
+ * Engine: Fix functions with low intensity killing current fade outs (David Garyga)
+ * Audio Capture: Fix crash when selecting another audio input while a capture is running (David Garyga)
+ * Audio Capture: Fix crash when trying to use a wrongly configured audio input (David Garyga)
+ * Scene Editor: Remember Channels Groups values when saving and loading a workspace (David Garyga)
+ * Scene Editor: Remember fixtures even with no activated channel (David Garyga)
+ * RGBMatrix Editor: Fix preview now working when fade in > 0 (David Garyga)
+ * RGBMatrix Editor: Fix length of fadeout on the preview (David Garyga)
+ * Show Manager: Fix crash when editing the total time of an empty chaser (David Garyga)
+ * Show Manager/Function Selection: Fix sequences always displayed even with Chasers and Scenes both filtered out (David Garyga)
+ * Speed Dials: Fix display and input of the milliseconds field, update precision from 10ms to 1ms (David Garyga)
+ * Input/Output Manager: Forbid deleting universes in the middle of the list, this prevents a lot of bugs and crashes (David Garyga)
+ * Simple Desk: the number of faders is now dynamic depending on the window size (unless forced via config file)
+ * Plugins/ArtNet: Fix input and output initialization conflict that results in no input (David Garyga)
+ * Plugins/ArtNet: Allow sending and receiving ArtNet on several different interfaces (David Garyga)
+ * Plugins/ArtNet: Allow selecting a different ArtNet input universe (David Garyga)
+ * Plugins/ArtNet: Fix configuration issue that prevents setting a parameter back to its default value (David Garyga)
+ * Plugins/OSC: Fix configuration issue that prevents setting a parameter back to its default value (David Garyga)
+ * Plugins/OSC: OSC Output values range from 0.0 to 1.0
+ * Plugins/OSC: Properly handle OSC bundles (restores Lemur compatibility)
+ * Virtual Console: Fix copy of a frame containing a submaster slider resulting in a broken submaster (David Garyga)
+ * Virtual Console/Slider: Enable function filters in playback function selection (David Garyga)
+ * Virtual Console/Slider: Allow to force to LTP color channels controlled by a Click & Go button
+ * Virtual Console/Solo Frame: Fix sliders in playback mode not actually stopping the attached function when the slider reaches 0 (David Garyga, thanks to Tubby)
+ * Virtual Console/Animation: Can now be used in solo frames (David Garyga)
+ * Virtual Console/Frames: fix page cloning of a nested multipage frame
+ * Virtual Console/Frames: fix disabling frame pages. Now widgets get actually deleted
+ * Web access: fixed custom fixtures loading
+ * Web access: added a DMX keypad that can be accessed from the Simple Desk (thanks to Santiago Benejam Torres)
+ * Input profiles: added Behringer BCR2000 (thanks to Michael Trojacher)
+ * Input profiles: added Lemur iPad Studio Combo
+ * RGB Scripts: added Strobe script (thanks to Rob Nieuwenhuizen)
+ * New fixtures: Stellar Labs ECO LED PAR56, Chauvet Colorpalette II (thanks to Jimmy Traylor)
+ * New fixtures: Chauvet: COLORado 1 Solo, Ovation FD-165WW, Rogue RH1 Hybrid, COLORdash Par Hex 12, COLORdash Accent Quad (thanks to Robert Box)
+ * New fixtures: Chauvet: Vue 1.1, Intimidator Spot 100 IRC, Abyss USB, COREpar 40 USB, COREpar UV USB (thanks to Robert Box)
+ * New fixtures: Chauvet: Intimidator Scan 305 IRC, Intimidator Barrel 305 IRC, SlimPAR T6 USB, SlimBANK TRI-18 (thanks to Robert Box)
+ * New fixtures: Eurolite LED CLS-9 QCL RGBW 9x8W 12, JB-Lighting A12 Tunable White, SGM G-Profile (thanks to Robert Box)
+ * New fixtures: Coemar Par Lite LED RGB, OXO LED Funstrip DMX, American DJ Stinger II (thanks to Robert Box)
+ * New fixture: Chauvet LED PAR 64 Tri-C (thanks to Jungle Jim and Robert Box)
+ * New fixtures: American DJ VBar, American DJ Jellydome, Briteq LDP Powerbar 6TC/12TC (thanks to Thierry Rodolfo)
+ * New fixture: Sagitter Slimpar 18 RGB (thanks to Daniele Fogale)
+ * New fixture: Microh LED Tri Bar (thanks to Michael Tughan)
+ * New fixture: American DJ 12P Hex Pearl (thanks to Ethan Moses)
+ * New fixture: JB-Lighting JBLED A7 (thanks to BLACKsun)
+ * New fixture: Chauvet COREpar 80 USB (thanks to Chris Gill)
+ * New fixture: Stairville DJ Lase 25+25-G MK-II (thanks to galaris)
+ * New fixtures: PR Lighting XR 230 Spot, PR Lighting XLED 1037 (thanks to Ovidijus Cepukas)
+ * New fixtures: Futurelight DJ-Scan 600, Eurolite LED PAR-64 RGBW+UV (thanks to Ovidijus Cepukas)
+ * New fixtures: Varytec LED Pad 7 BA-D, American DJ X-Scan LED Plus, Showtec Blade Runner (thanks to DjProWings)
+ * New fixture: Involight LED CC60S (thanks to Stephane Hofman)
+ * New fixture: Stairville MH-x200 Pro Spot (thanks to Mirek Škop)
+ * New fixtures: Varytec LED Giga Bar 4 MKII, Eurolite LED KLS Laser Bar FX Light Set (thanks to Daniel Schauder)
+ * New fixture: Chauvet Mayhem (thanks to Jonathan Wilson)
+ * New fixture: Ayra TDC Agaricus (thanks to Rob Nieuwenhuizen)
+ * New fixture: American DJ Pinspot LED Quad DMX (thanks to Christian Polzer)
+ * New fixture: Stairville AF-180 LED Fogger Co2 FX (thanks to Johannes Uhl)
+
+ -- Massimo Callegari Sun, 6 Mar 2016 20:21:22 +0200
+
+qlcplus (4.10.2) stable; urgency=low
+
+ * Engine: added support for devices hotplug (DMX USB, MIDI, HID, Peperoni)
+ * Engine: Universe passthrough data is now merged with QLC+ output, it is not affected by QLC+ processing
+ (except for blackout) and it appears in the DMX monitor (Jano Svitok)
+ * Audio: fixed playback of 24/32 bit wave files and Show Manager waveform preview
+ * DMX Dump: it is now possible to dump DMX values on an existing Scene
+ * ClickAndGo Widgets: Preset widgets now display the channel name on top of the capability list (David Garyga)
+ * Function Manager: fix startup Function not cleared when deleting it (David Garyga)
+ * Function Manager: highlight current startup Function when opening the Function selection dialog (David Garyga)
+ * Function Selection: Don't lose the current selection when changing the function type filter (David Garyga)
+ * Show Manager: fixed looped functions never stopping with certain durations (Jano Svitok)
+ * Show Manager: fixed copy/paste of an existing Chaser
+ * Show Manager: fix crashes when copying a sequence on an empty track (David Garyga)
+ * Show Manager: repair conflicting sequences when loading a broken workspace (David Garyga)
+ * EFX Editor: removed the intensity control. Please use separate Scenes for that
+ * Virtual Console/Slider: fixed copy of the channel monitor mode (David Garyga)
+ * Virtual Console/Slider: in level mode, activate only in operate mode and don't hold forced LTP channels (David Garyga)
+ * Virtual Console/Slider: in playback mode, ignore the fade in/fade out of the attached Function (David Garyga)
+ * Virtual Console/XYPad: added Fixture Group preset, to control a subgroup of Fixtures (see documentation)
+ * Virtual Console/XYPad Properties: fixture ranges can now be set in degrees, percentage or DMX values
+ * Virtual Console/XYPad Properties: fix manual input selection for presets (David Garyga)
+ * Virtual Console/Cue List: improved mixed usage of crossfader and next/previous buttons (David Garyga)
+ * Virtual Console/Cue List: fix effect of a submaster slider on a Cue List in crossfader mode (David Garyga)
+ * Virtual Console/Cue List: fix crash when adding steps to the chaser being run by a Cue List (David Garyga)
+ * Virtual Console/Audio Triggers: fix virtual console buttons triggering (David Garyga)
+ * Virtual Console/Input Selection: allow custom feedbacks only on an assigned source and don't crash (David Garyga)
+ * DMX Monitor: Fix strobing in 2D view (Jano Svitok)
+ * Fixture Editor: Fix crash in the Channel Editor (David Garyga)
+ * Plugins/uDMX: added support for AVLdiy.cn clone (thanks to Vitalii Husach)
+ * Plugins/DMXUSB: (Linux) fixed data transmission of DMX4ALL NanoDMX
+ * Input Profiles: added an option for Buttons to always generate a press/release event
+ * New input profile: Touch OSC Automat5
+ * New input profile: Novation Launch Control (thanks to Giacomo Gorini)
+ * Updated fixture: Stairville xBrick Full-Colour 16X3W (thanks to Rico Hansen)
+ * Updated fixture: Stairville MH-100 Beam 36x3 LED (thanks to Antoni J. Canos)
+ * Updated fixture: American DJ Revo 3 (thanks to David Pilato)
+ * New fixture: American DJ Dotz Flood
+ * New fixture: Chauvet COLORdash Par Quad-7
+ * New fixtures: Robe ColorWash 1200E AT, American DJ Starburst, Chauvet LED PAR 64 Tri-B (thanks to Robert Box)
+ * New fixtures: Cameo Multi Par 3, HQ Power VDPL110CC LED Tri Spot, Showtec LED Pixel Track Pro (thanks to Robert Box)
+ * New fixtures: American DJ: Inno Pocket Z4, On-X, WiFly EXR Dotz Par, WiFly EXR HEX5 IP, COB Cannon Wash Pearl (thanks to Robert Box)
+ * New fixtures: BoomTone DJ Sky bar 288 LED, BoomToneDJ Strob LED 18, BoomToneDJ Froggy LED RGBW (thanks to Didou)
+ * New fixture: iSolution iMove 250W (thanks to Thierry Rodolfo)
+ * New fixture: Talent BL63 10" LED Bar (thanks to FooSchnickens)
+ * New fixtures: Contest Oz-37x15QC, Evora DUO B2R, Evora Beam 5R, Evora Beam 15R (thanks to Jan Lachman)
+ * New fixtures: Blizzard Lighting Lil G, Pixellicious, Lo-Pro CSI (thanks to Alton Olson)
+ * New fixtures: Stairville LED Matrix Blinder 5x5, Showtec Power Spot 9 Q6 Tour V1 (thanks to Samuel)
+ * New fixture: Blizzard Lighting StormChaser (thanks to Brent)
+ * New fixtures: Showtec Explorer 250 Pro MKII, Showtec Pixel Bar 12 (thanks to Henk de Gunst)
+ * New fixture: Philips Selecon PLProfile1 MkII (thanks to Freasy)
+ * New fixture: PSL Strip Led RGB code K2014 (thanks to Lorenzo Andreani)
+ * New fixture: Chauvet SlimPar Pro Tri (thank to Bulle)
+ * New fixture: Chauvet GigBar IRC (thanks to JD-HP-DV7 and Jungle Jim)
+ * New fixtures: Ghost Green 30, KOOLlight 3D RGB Laser, Mac Mah Mac FOG DMX (thanks to David Pilato)
+
+ -- Massimo Callegari Sun, 13 Dec 2015 20:21:22 +0200
+
+qlcplus (4.10.1) stable; urgency=high
+
+ * Virtual Console/Cue List: improved step fader behaviour (David Garyga)
+ * Plugins/DMXUSB: Fixed regression affecting Linux users and OSX users using the libFTDI interface
+ * Plugins/ArtNet/E1.31/OSC: Improved network interfaces detection
+ * New fixture: Enterius EC-133DMX (thanks to Krzysztof Ratynski)
+ * New fixture: Showtec Dragon F-350 (thanks to Jasper Zevering)
+ * New fixtures: JB Systems Lounge Laser DMX, JB Systems Super Solar RGBW (thanks to Robert Box)
+
+ -- Massimo Callegari Wed, 21 Oct 2015 20:21:22 +0200
+
+qlcplus (4.10.0) stable; urgency=low
+
+ * Channel Groups: Fix crashes related to invalid channels (David Garyga)
+ * Chaser: Fix flickering issue when chaser order is Random (David Garyga)
+ * Engine: some more fixes on forced HTP/LTP channels
+ * Engine: fixed 4.9.x regression causing QLC+ to hang at the end of audio playback
+ * RGB Matrix Audio Spectrum: Fix crash when audio input volume is set to zero (David Garyga)
+ * RGB Matrix: Fix 4.9.1 regression which makes fading of green and blue colors not smooth (David Garyga)
+ * RGB Matrix: Introduced blending mode between matrices (see documentation)
+ * Audio Input: Fix crashes when selecting another audio input device while an audio input driven function/widget is running (David Garyga)
+ * Audio Input: It is now possible to select the audio input format (sample rate and channels)
+ * Video: fixed playback from a time offset (where possible)
+ * Video: (Windows) Videos now have a black background, like all the other platforms
+ * Add Fixture dialog: Generic fixtures don't take the number of channels of the previously selected fixture (David Garyga)
+ * Add Fixture dialog: Fix address 512 not usable by adding several fixtures at a time (David Garyga)
+ * Scene Editor: correctly select the fixture tab when switching from tabbed/all channels view
+ * EFX Editor: it is now possible to use an EFX on RGB channels (thanks to Giorgio Rebecchi)
+ * Collection Editor: added preview button (thanks to Giorgio Rebecchi)
+ * Fixture Remap: fixed remapping of EFX functions
+ * Function Wizard: improved creation of color scenes for RGB panels
+ * Function Wizard: automatically set a gobo picture (if available) on buttons attached to gobo Scenes
+ * Show Manager: Fix some cursor teleportation issues (David Garyga)
+ * Simple Desk: Fix cue playback on universes 2+ (David Garyga)
+ * Simple Desk: Fix crash when selecting recently added universe (David Garyga)
+ * Simple Desk: Added reset buttons to reset a single channel
+ * Simple Desk: Fix page count when channels per page does not divide 512 (Jano Svitok, reported by Florian)
+ * Virtual Console: fixed Grand Master not sending feedbacks
+ * Virtual Console/Input Controls: implemented custom feebacks. For now used only by VC buttons
+ * Virtual Console/Solo Frame: Option to allow mixing of sliders in playback mode (David Garyga)
+ * Virtual Console/Speed Dial: Introduced multiplier/divisor, apply and presets buttons (see documentation)
+ * Virtual Console/Button: allow to set a background picture
+ * Virtual Console/Cue List: Options for the Next/Previous buttons behavior (David Garyga)
+ * Virtual Console/Cue List: Fixed playback of a Chaser in reverse order (David Garyga)
+ * Virtual Console/Cue List: Added a new "Steps" mode for the side faders (see documentation)
+ * Virtual Console/Cue List: Allow to resize columns to 0 pixels, to completely hide them
+ * Virtual Console/XYPad: Introduced presets, including the usage of existing EFX and Scenes (see documentation)
+ * Virtual Console/XYPad: Fix DMX output not working when going to Operate mode while the XYPad is disabled (David Garyga, thanks to bestdani)
+ * Input Profiles: added an option for MIDI profiles to feedback a Note Off or a Note On with 0 velocity. APCMini now works out of the box. (Jano Svitok)
+ * Input Profiles: Improved BCF2000 Input profile (thanks to Lorenzo Andreani)
+ * Plugins/MIDI: (Linux) Fixed data transmission to multiple devices (thanks to Adrian Kapka)
+ * Plugins/MIDI: fixed Program Change handling on OSX and Windows
+ * Plugins/MIDI: (Windows) do not close the device when sending SysEx data
+ * Plugins/ArtNet: it is now possible to enter an arbitrary output IP
+ * Plugins/OSC: it is now possible to enter an arbitrary output IP
+ * Plugins/E1.31: added stream priority to configuration (thanks to Nathan Durnan)
+ * Plugins/E1.31: added unicast support (David Garyga)
+ * Plugins/DMXUSB: fixed close/open sequence on a Enttec Pro input line
+ * Web Access: implemented frames collapse functionality
+ * RGB Scripts: added Plasma Colors script (thanks to Nathan Durnan)
+ * Fixture Editor: Channel capability editing is now done in a single window
+ * Updated fixture: Showtec Indigo 6500 (thanks to Jochen Becker)
+ * New fixture: Ayra ComPar 20 (thanks to Rob Nieuwenhuizen)
+ * New fixture: XStatic X-240Bar RGB (thanks to Nathan Durnan)
+ * New fixture: Venue ThinPAR 38 (thanks to Thierry Rodolfo)
+ * New fixtures: Contest MiniCube-6TCb, Eurolite LED FE-1500, Lightronics FXLD618C2I, JB Systems COB-4BAR (thanks to Robert Box)
+ * New fixtures: Eurolite LED KLS-401, Chauvet Intimidator Wash Zoom 350 IRC, Equinox Party Par LED PAR 56 (thanks to Robert Box)
+ * New fixtures: Robe Robin MiniMe, PR Lighting Pilot 575, Stairville DJ Lase 150-RGY MkII, JB Systems Dynaspot (thanks to Robert Box)
+ * New fixtures: American DJ Hyper Gem LED, Robe ColorSpot 575 AT, Kam iLink All Colour Models (thanks to Robert Box)
+ * New fixtures: Chauvet Intimidator Wave 360 IRC, Varytec LED PAR56 (thanks to Habefaro)
+ * New fixture: American DJ Fog Fury Jett (thanks to Dean Clough)
+ * New fixtures: Eurolite TB-250, Futurelight DJ-HEAD 575 SPOT, GLX Lighting Power LED Beam 38 Narrow (thanks to Ovidijus Cepukas)
+ * New fixture: Pro-Lights UVStrip18 (thanks to Alessandro Grechi)
+ * New fixtures: American DJ Inno Pocket Beam Q4, Martin ZR24/7 Hazer, Blizzard Lighting Stimul-Eye (thanks to George Qualley)
+ * New fixture: American DJ Mega TriPar Profile Plus (thanks to George Qualley)
+ * New fixtures: Pro-Lights SmartBat, Robe ClubWash 600 CT (thanks to Lorenzo Andreani)
+ * New fixture: Stairville Show Bar Tri 18x3W RGB (thanks to Udo Besenreuther)
+ * New fixtures: Blizzard Lighting Rokbox Infiniwhite, Chauvet COREpar 80 (thanks to Chris Gill)
+ * New fixture: Cameo CL Superfly HP (thanks to Stuart Brown)
+ * New fixture: American DJ Event Bar Q4 (thanks to Maxime Bissonnette-Théorêt)
+ * New fixture: Cameo LED Moving Head 60W CLMHR60W (thanks to Jasper Zevering)
+ * New fixtures: Proel PLLED64RGB, Litecraft LED PAR 64 AT3, Robe Robin 300E Beam (thanks to Mihai Andrei)
+ * New fixture: Electroconcept SPC029 (thanks to Bulle)
+ * New fixture: Microh Plasmawave 1 RGB (thanks to Rommel)
+
+ -- Massimo Callegari Sun, 18 Oct 2015 20:21:22 +0200
+
qlcplus (4.9.1) stable; urgency=high
* RGBMatrix: SingleShot RGBMatrix make use of FadeOut time (David Garyga)
diff --git a/debian/control b/debian/control
index 59c59aede8..70fbdddc2c 100644
--- a/debian/control
+++ b/debian/control
@@ -9,8 +9,9 @@ Build-Depends:
libfftw3-dev,
libftdi1-dev (>= 0.17),
libmad0-dev,
- qtbase5-dev, qtscript5-dev, qtmultimedia5-dev,
libsndfile1-dev,
+ qtbase5-dev, qtscript5-dev,
+ qtmultimedia5-dev, libqt5serialport5-dev,
libstdc++-dev,
libudev-dev,
libusb-1.0-0-dev (>= 2:0.1.12),
diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt
new file mode 100644
index 0000000000..b1208fb0cc
--- /dev/null
+++ b/engine/CMakeLists.txt
@@ -0,0 +1,9 @@
+project(engine VERSION 1.0.0)
+
+pkg_check_modules(FFTW3 IMPORTED_TARGET fftw3)
+
+add_subdirectory(audio)
+add_subdirectory(src)
+if(NOT ANDROID AND NOT IOS)
+ add_subdirectory(test)
+endif()
diff --git a/engine/audio/CMakeLists.txt b/engine/audio/CMakeLists.txt
new file mode 100644
index 0000000000..79e341e634
--- /dev/null
+++ b/engine/audio/CMakeLists.txt
@@ -0,0 +1,4 @@
+project(audio)
+
+add_subdirectory(plugins)
+add_subdirectory(src)
diff --git a/engine/audio/plugins/CMakeLists.txt b/engine/audio/plugins/CMakeLists.txt
new file mode 100644
index 0000000000..e9a54a0e5e
--- /dev/null
+++ b/engine/audio/plugins/CMakeLists.txt
@@ -0,0 +1,13 @@
+project(plugins)
+
+if((((NOT ANDROID AND NOT IOS))))
+ pkg_check_modules(MAD IMPORTED_TARGET mad)
+ if(${MAD_FOUND})
+ add_subdirectory(mad)
+ endif()
+
+ pkg_check_modules(SNDFILE IMPORTED_TARGET sndfile)
+ if(${SNDFILE_FOUND})
+ add_subdirectory(sndfile)
+ endif()
+endif()
diff --git a/engine/audio/plugins/mad/CMakeLists.txt b/engine/audio/plugins/mad/CMakeLists.txt
new file mode 100644
index 0000000000..3595d197de
--- /dev/null
+++ b/engine/audio/plugins/mad/CMakeLists.txt
@@ -0,0 +1,32 @@
+if(NOT MAD_INCLUDE_DIRS)
+ find_path(MAD_INCLUDE_DIR mad.h PATH_SUFFIXES include)
+ if(NOT MAD_INCLUDE_DIR)
+ message(FATAL_ERROR "[ERROR] Could not find mad.h header file!")
+ endif()
+ set(MAD_INCLUDE_DIRS ${MAD_INCLUDE_DIR})
+endif()
+
+set(module_name "madplugin")
+
+add_library(${module_name} SHARED)
+target_sources(${module_name} PRIVATE
+ ../../src/audiodecoder.cpp ../../src/audiodecoder.h
+ ../../src/audioparameters.cpp ../../src/audioparameters.h
+ audiodecoder_mad.cpp audiodecoder_mad.h
+)
+
+target_include_directories(${module_name} PRIVATE
+ ../../src
+ ${MAD_INCLUDE_DIRS}
+)
+
+target_link_libraries(${module_name} PRIVATE
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Gui
+ ${MAD_LINK_LIBRARIES}
+)
+
+install(TARGETS ${module_name}
+ LIBRARY DESTINATION ${INSTALLROOT}/${AUDIOPLUGINDIR}
+ RUNTIME DESTINATION ${INSTALLROOT}/${AUDIOPLUGINDIR}
+)
\ No newline at end of file
diff --git a/engine/audio/plugins/mad/audiodecoder_mad.cpp b/engine/audio/plugins/mad/audiodecoder_mad.cpp
index 003527fa83..a90a9d533d 100644
--- a/engine/audio/plugins/mad/audiodecoder_mad.cpp
+++ b/engine/audio/plugins/mad/audiodecoder_mad.cpp
@@ -249,7 +249,7 @@ bool AudioDecoderMAD::findHeader()
if (mad_header_decode(&header, &m_stream) < 0)
{
- if(m_stream.error == MAD_ERROR_LOSTSYNC)
+ if (m_stream.error == MAD_ERROR_LOSTSYNC)
{
uint tagSize = findID3v2((uchar *)m_stream.this_frame,
(ulong) (m_stream.bufend - m_stream.this_frame));
@@ -360,11 +360,11 @@ qint64 AudioDecoderMAD::read(char *data, qint64 size)
{
forever
{
- if(((m_stream.error == MAD_ERROR_BUFLEN) || !m_stream.buffer) && !m_eof)
+ if (((m_stream.error == MAD_ERROR_BUFLEN) || !m_stream.buffer) && !m_eof)
{
m_eof = !fillBuffer();
}
- if(mad_frame_decode(&m_frame, &m_stream) < 0)
+ if (mad_frame_decode(&m_frame, &m_stream) < 0)
{
switch((int) m_stream.error)
{
@@ -381,7 +381,7 @@ qint64 AudioDecoderMAD::read(char *data, qint64 size)
continue;
}
case MAD_ERROR_BUFLEN:
- if(m_eof)
+ if (m_eof)
return 0;
continue;
default:
@@ -391,7 +391,7 @@ qint64 AudioDecoderMAD::read(char *data, qint64 size)
continue;
}
}
- if(m_skip_frames)
+ if (m_skip_frames)
{
m_skip_frames--;
continue;
@@ -402,7 +402,7 @@ qint64 AudioDecoderMAD::read(char *data, qint64 size)
}
void AudioDecoderMAD::seek(qint64 pos)
{
- if(m_totalTime > 0)
+ if (m_totalTime > 0)
{
qint64 seek_pos = qint64(pos * m_input.size() / m_totalTime);
m_input.seek(seek_pos);
@@ -437,7 +437,7 @@ bool AudioDecoderMAD::fillBuffer()
qDebug("DecoderMAD: end of file");
return false;
}
- else if(len < 0)
+ else if (len < 0)
{
qWarning("DecoderMAD: error");
return false;
@@ -546,7 +546,7 @@ qint64 AudioDecoderMAD::madOutput(char *data, qint64 size)
m_output_at = 0;
m_output_bytes = 0;
- if(samples * channels * 2 > size)
+ if (samples * channels * 2 > size)
{
qWarning() << "DecoderMad: input buffer is too small. Required: " << (samples * channels * 2) << ", available: " << size;
samples = size / channels / 2;
diff --git a/engine/audio/plugins/sndfile/CMakeLists.txt b/engine/audio/plugins/sndfile/CMakeLists.txt
new file mode 100644
index 0000000000..5a7430094e
--- /dev/null
+++ b/engine/audio/plugins/sndfile/CMakeLists.txt
@@ -0,0 +1,23 @@
+set(module_name "sndfileplugin")
+
+add_library(${module_name} SHARED)
+target_sources(${module_name} PRIVATE
+ ../../src/audiodecoder.cpp ../../src/audiodecoder.h
+ ../../src/audioparameters.cpp ../../src/audioparameters.h
+ audiodecoder_sndfile.cpp audiodecoder_sndfile.h
+)
+target_include_directories(${module_name} PRIVATE
+ ../../src
+ ${SNDFILE_INCLUDE_DIRS}
+)
+
+target_link_libraries(${module_name} PRIVATE
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Gui
+ ${SNDFILE_LINK_LIBRARIES}
+)
+
+install(TARGETS ${module_name}
+ LIBRARY DESTINATION ${INSTALLROOT}/${AUDIOPLUGINDIR}
+ RUNTIME DESTINATION ${INSTALLROOT}/${AUDIOPLUGINDIR}
+)
\ No newline at end of file
diff --git a/engine/audio/plugins/sndfile/audiodecoder_sndfile.cpp b/engine/audio/plugins/sndfile/audiodecoder_sndfile.cpp
index 69ab00ae88..3eb960b870 100644
--- a/engine/audio/plugins/sndfile/audiodecoder_sndfile.cpp
+++ b/engine/audio/plugins/sndfile/audiodecoder_sndfile.cpp
@@ -78,7 +78,7 @@ bool AudioDecoderSndFile::initialize(const QString &path)
m_totalTime = snd_info.frames * 1000 / m_freq;
m_bitrate = QFileInfo(m_path).size () * 8.0 / m_totalTime + 0.5;
- if((snd_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT)
+ if ((snd_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT)
{
qDebug() << "DecoderSndFile: Float audio format";
sf_command (m_sndfile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE);
diff --git a/engine/audio/src/CMakeLists.txt b/engine/audio/src/CMakeLists.txt
new file mode 100644
index 0000000000..cd51c39823
--- /dev/null
+++ b/engine/audio/src/CMakeLists.txt
@@ -0,0 +1,102 @@
+set(module_name "qlcplusaudio")
+
+add_library(${module_name}
+ STATIC
+ audio.cpp audio.h
+ audiocapture.cpp audiocapture.h
+ audiodecoder.cpp audiodecoder.h
+ audioparameters.cpp audioparameters.h
+ audioplugincache.cpp audioplugincache.h
+ audiorenderer.cpp audiorenderer.h
+)
+set_property(TARGET ${module_name} PROPERTY POSITION_INDEPENDENT_CODE ON)
+target_include_directories(${module_name} PUBLIC
+ ../../../plugins/interfaces
+ ../../src
+)
+
+target_link_libraries(${module_name} PUBLIC
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Gui
+ Qt${QT_MAJOR_VERSION}::Multimedia
+)
+
+if(APPLE)
+ set_target_properties(${module_name} PROPERTIES
+ MACOSX_BUNDLE FALSE
+ )
+endif()
+
+if(WIN32)
+ target_link_libraries(${module_name} PUBLIC
+ Qt${QT_MAJOR_VERSION}::Widgets
+ )
+endif()
+
+if(((QT_VERSION_MAJOR LESS 5)) AND (UNIX AND NOT APPLE))
+ target_sources(${module_name} PRIVATE
+ audiocapture_alsa.cpp audiocapture_alsa.h
+ audiorenderer_alsa.cpp audiorenderer_alsa.h
+ )
+endif()
+
+if(((QT_VERSION_MAJOR LESS 5)) AND (WIN32))
+ target_sources(${module_name} PRIVATE
+ audiocapture_wavein.cpp audiocapture_wavein.h
+ audiorenderer_waveout.cpp audiorenderer_waveout.h
+ )
+endif()
+
+if((QT_VERSION_MAJOR LESS 6))
+ target_sources(${module_name} PRIVATE
+ audiocapture_qt5.cpp audiocapture_qt5.h
+ audiorenderer_qt5.cpp audiorenderer_qt5.h
+ )
+endif()
+
+if(NOT ((QT_VERSION_MAJOR LESS 6)))
+ target_sources(${module_name} PRIVATE
+ audiocapture_qt6.cpp audiocapture_qt6.h
+ audiorenderer_qt6.cpp audiorenderer_qt6.h
+ )
+endif()
+
+if((((QT_VERSION_MAJOR LESS 5)) AND (APPLE)))
+ if (${PORTAUDIO_2_FOUND})
+ target_sources(${module_name} PRIVATE
+ audiocapture_portaudio.cpp audiocapture_portaudio.h
+ audiorenderer_portaudio.cpp audiorenderer_portaudio.h
+ )
+
+ target_compile_definitions(${module_name} PUBLIC
+ HAS_PORTAUDIO
+ )
+
+ target_include_directories(${module_name} PUBLIC
+ ${PORTAUDIO_2_INCLUDE_DIRS}
+ )
+
+ target_link_libraries(${module_name} PUBLIC
+ ${PORTAUDIO_2_LIBRARIES}
+ )
+ endif()
+endif()
+
+if((NOT ANDROID AND NOT IOS) AND (${FFTW3_FOUND}))
+ target_compile_definitions(${module_name} PUBLIC
+ HAS_FFTW3
+ )
+
+ target_include_directories(${module_name} PUBLIC
+ ${FFTW3_INCLUDE_DIRS}
+ )
+ target_link_libraries(${module_name} PUBLIC
+ ${FFTW3_LINK_LIBRARIES}
+ )
+endif()
+
+if(UNIX AND NOT ANDROID AND NOT IOS AND NOT APPLE)
+ target_link_libraries(${module_name} PUBLIC
+ asound
+ )
+endif()
diff --git a/engine/audio/src/audio.cpp b/engine/audio/src/audio.cpp
index c847b001a2..558f59384a 100644
--- a/engine/audio/src/audio.cpp
+++ b/engine/audio/src/audio.cpp
@@ -46,6 +46,7 @@
#define KXMLQLCAudioSource QString("Source")
#define KXMLQLCAudioDevice QString("Device")
+#define KXMLQLCAudioVolume QString("Volume")
/*****************************************************************************
* Initialization
@@ -59,6 +60,7 @@ Audio::Audio(Doc* doc)
, m_audioDevice(QString())
, m_sourceFileName("")
, m_audioDuration(0)
+ , m_volume(1.0)
{
setName(tr("New Audio"));
setRunOrder(Audio::SingleShot);
@@ -173,6 +175,7 @@ bool Audio::setSourceFileName(QString filename)
if (m_decoder == NULL)
return false;
+ setDuration(m_decoder->totalTime());
setTotalDuration(m_decoder->totalTime());
emit changed(id());
@@ -195,6 +198,16 @@ void Audio::setAudioDevice(QString dev)
m_audioDevice = dev;
}
+qreal Audio::volume() const
+{
+ return m_volume;
+}
+
+void Audio::setVolume(qreal volume)
+{
+ m_volume = volume;
+}
+
QString Audio::audioDevice()
{
return m_audioDevice;
@@ -205,7 +218,7 @@ int Audio::adjustAttribute(qreal fraction, int attributeId)
int attrIndex = Function::adjustAttribute(fraction, attributeId);
if (m_audio_out != NULL && attrIndex == Intensity)
- m_audio_out->adjustIntensity(getAttributeValue(Function::Intensity));
+ m_audio_out->adjustIntensity(m_volume * getAttributeValue(Function::Intensity));
return attrIndex;
}
@@ -253,6 +266,9 @@ bool Audio::saveXML(QXmlStreamWriter *doc)
if (m_audioDevice.isEmpty() == false)
doc->writeAttribute(KXMLQLCAudioDevice, m_audioDevice);
+ if (m_volume != 1.0)
+ doc->writeAttribute(KXMLQLCAudioVolume, QString::number(m_volume));
+
doc->writeCharacters(m_doc->normalizeComponentPath(m_sourceFileName));
doc->writeEndElement();
@@ -288,6 +304,8 @@ bool Audio::loadXML(QXmlStreamReader &root)
if (attrs.hasAttribute(KXMLQLCAudioDevice))
setAudioDevice(attrs.value(KXMLQLCAudioDevice).toString());
+ if (attrs.hasAttribute(KXMLQLCAudioVolume))
+ setVolume(attrs.value(KXMLQLCAudioVolume).toString().toDouble());
setSourceFileName(m_doc->denormalizeComponentPath(root.readElementText()));
}
@@ -322,6 +340,15 @@ void Audio::preRun(MasterTimer* timer)
{
if (m_decoder != NULL)
{
+ uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
+
+ if (m_audio_out != NULL && m_audio_out->isRunning())
+ {
+ m_audio_out->stop();
+ m_audio_out->deleteLater();
+ m_audio_out = NULL;
+ }
+
m_decoder->seek(elapsed());
AudioParameters ap = m_decoder->audioParameters();
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
@@ -341,8 +368,8 @@ void Audio::preRun(MasterTimer* timer)
#endif
m_audio_out->setDecoder(m_decoder);
m_audio_out->initialize(ap.sampleRate(), ap.channels(), ap.format());
- m_audio_out->adjustIntensity(getAttributeValue(Intensity));
- m_audio_out->setFadeIn(fadeInSpeed());
+ m_audio_out->adjustIntensity(m_volume * getAttributeValue(Intensity));
+ m_audio_out->setFadeIn(elapsed() ? 0 : fadeIn);
m_audio_out->setLooped(runOrder() == Audio::Loop);
m_audio_out->start();
connect(m_audio_out, SIGNAL(endOfStreamReached()),
@@ -378,10 +405,15 @@ void Audio::write(MasterTimer* timer, QList universes)
incrementElapsed();
- if (overrideFadeOutSpeed() == defaultSpeed())
+ if (m_audio_out && !m_audio_out->isLooped())
{
- if (m_audio_out != NULL && totalDuration() - elapsed() <= fadeOutSpeed())
- m_audio_out->setFadeOut(fadeOutSpeed());
+ uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
+
+ if (fadeout)
+ {
+ if (m_audio_out != NULL && totalDuration() - elapsed() <= fadeOutSpeed())
+ m_audio_out->setFadeOut(fadeOutSpeed());
+ }
}
}
@@ -389,14 +421,16 @@ void Audio::postRun(MasterTimer* timer, QList universes)
{
// Check whether a fade out is needed "outside" of the natural playback
// This is the case of a Chaser step
- if (overrideFadeOutSpeed() == defaultSpeed())
+ uint fadeout = overrideFadeOutSpeed() == defaultSpeed() ? fadeOutSpeed() : overrideFadeOutSpeed();
+
+ if (fadeout == 0)
{
slotEndOfStream();
}
else
{
if (m_audio_out != NULL)
- m_audio_out->setFadeOut(overrideFadeOutSpeed());
+ m_audio_out->setFadeOut(fadeout);
}
Function::postRun(timer, universes);
diff --git a/engine/audio/src/audio.h b/engine/audio/src/audio.h
index da0c09f093..b1263438d6 100644
--- a/engine/audio/src/audio.h
+++ b/engine/audio/src/audio.h
@@ -109,6 +109,10 @@ public slots:
*/
void setAudioDevice(QString dev);
+ /** Get/Set the audio function startup volume */
+ qreal volume() const;
+ void setVolume(qreal volume);
+
/**
* Retrieve the audio device set for this function
*/
@@ -129,16 +133,12 @@ protected slots:
AudioRenderer *m_audio_out;
/** Audio device to use for rendering */
QString m_audioDevice;
- /** Absolute start time of Audio over a timeline (in milliseconds) */
- quint32 m_startTime;
- /** Color to use when displaying the audio object in the Show manager */
- QColor m_color;
- /** Flag to indicate if a Audio item is locked in the Show Manager timeline */
- bool m_locked;
/** Name of the source audio file */
QString m_sourceFileName;
/** Duration of the media object */
qint64 m_audioDuration;
+ /** Startup volume of the audio file */
+ qreal m_volume;
/*********************************************************************
* Save & Load
diff --git a/engine/audio/src/audiocapture.cpp b/engine/audio/src/audiocapture.cpp
index 17e6d4245d..809c5c5eba 100644
--- a/engine/audio/src/audiocapture.cpp
+++ b/engine/audio/src/audiocapture.cpp
@@ -235,7 +235,7 @@ void AudioCapture::processData()
#endif
// 5 ********* Calculate the average signal power
- foreach(int barsNumber, m_fftMagnitudeMap.keys())
+ foreach (int barsNumber, m_fftMagnitudeMap.keys())
{
maxMagnitude = fillBandsData(barsNumber);
pwrSum = 0.;
diff --git a/engine/audio/src/audiocapture.h b/engine/audio/src/audiocapture.h
index 58a9484101..f6fefd73c0 100644
--- a/engine/audio/src/audiocapture.h
+++ b/engine/audio/src/audiocapture.h
@@ -135,6 +135,7 @@ class AudioCapture : public QThread
signals:
void dataProcessed(double *spectrumBands, int size, double maxMagnitude, quint32 power);
+ void volumeChanged(int volume);
protected:
/*!
diff --git a/engine/audio/src/audiocapture_portaudio.cpp b/engine/audio/src/audiocapture_portaudio.cpp
index 47c46acdce..a10840ee92 100644
--- a/engine/audio/src/audiocapture_portaudio.cpp
+++ b/engine/audio/src/audiocapture_portaudio.cpp
@@ -45,7 +45,7 @@ bool AudioCapturePortAudio::initialize()
PaStreamParameters inputParameters;
err = Pa_Initialize();
- if( err != paNoError )
+ if (err != paNoError)
return false;
QSettings settings;
@@ -64,18 +64,18 @@ bool AudioCapturePortAudio::initialize()
inputParameters.channelCount = m_channels;
inputParameters.sampleFormat = paInt16;
- inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
+ inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL;
// ensure initialize() has not been called multiple times
Q_ASSERT(stream == NULL);
/* -- setup stream -- */
- err = Pa_OpenStream( &stream, &inputParameters, NULL, m_sampleRate, paFramesPerBufferUnspecified,
+ err = Pa_OpenStream(&stream, &inputParameters, NULL, m_sampleRate, paFramesPerBufferUnspecified,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
NULL, /* no callback, use blocking API */
- NULL ); /* no callback, so no callback userData */
- if( err != paNoError )
+ NULL); /* no callback, so no callback userData */
+ if (err != paNoError)
{
qWarning("Cannot open audio input stream (%s)\n", Pa_GetErrorText(err));
Pa_Terminate();
@@ -83,11 +83,11 @@ bool AudioCapturePortAudio::initialize()
}
/* -- start capture -- */
- err = Pa_StartStream( stream );
- if( err != paNoError )
+ err = Pa_StartStream(stream);
+ if (err != paNoError)
{
qWarning("Cannot start stream capture (%s)\n", Pa_GetErrorText(err));
- Pa_CloseStream( stream );
+ Pa_CloseStream(stream);
stream = NULL;
Pa_Terminate();
return false;
@@ -103,19 +103,19 @@ void AudioCapturePortAudio::uninitialize()
PaError err;
/* -- Now we stop the stream -- */
- err = Pa_StopStream( stream );
- if( err != paNoError )
- qDebug() << "PortAudio error: " << Pa_GetErrorText( err );
+ err = Pa_StopStream(stream);
+ if (err != paNoError)
+ qDebug() << "PortAudio error: " << Pa_GetErrorText(err);
/* -- don't forget to cleanup! -- */
- err = Pa_CloseStream( stream );
- if( err != paNoError )
- qDebug() << "PortAudio error: " << Pa_GetErrorText( err );
+ err = Pa_CloseStream(stream);
+ if (err != paNoError)
+ qDebug() << "PortAudio error: " << Pa_GetErrorText(err);
stream = NULL;
err = Pa_Terminate();
- if( err != paNoError )
- qDebug() << "PortAudio error: " << Pa_GetErrorText( err );
+ if (err != paNoError)
+ qDebug() << "PortAudio error: " << Pa_GetErrorText(err);
}
qint64 AudioCapturePortAudio::latency()
@@ -135,10 +135,10 @@ bool AudioCapturePortAudio::readAudio(int maxSize)
{
Q_ASSERT(stream != NULL);
- int err = Pa_ReadStream( stream, m_audioBuffer, maxSize );
- if( err )
+ int err = Pa_ReadStream(stream, m_audioBuffer, maxSize);
+ if (err)
{
- qWarning("read from audio interface failed (%s)\n", Pa_GetErrorText (err));
+ qWarning("read from audio interface failed (%s)\n", Pa_GetErrorText(err));
return false;
}
diff --git a/engine/audio/src/audiocapture_qt5.cpp b/engine/audio/src/audiocapture_qt5.cpp
index 79920b42e3..cd9bb3fb78 100644
--- a/engine/audio/src/audiocapture_qt5.cpp
+++ b/engine/audio/src/audiocapture_qt5.cpp
@@ -113,9 +113,14 @@ qint64 AudioCaptureQt6::latency()
void AudioCaptureQt6::setVolume(qreal volume)
{
+ if (volume == m_volume)
+ return;
+
m_volume = volume;
if (m_audioInput != NULL)
m_audioInput->setVolume(volume);
+
+ emit volumeChanged(volume * 100.0);
}
void AudioCaptureQt6::suspend()
diff --git a/engine/audio/src/audiocapture_qt6.cpp b/engine/audio/src/audiocapture_qt6.cpp
index 3911c90301..6e3db68a61 100644
--- a/engine/audio/src/audiocapture_qt6.cpp
+++ b/engine/audio/src/audiocapture_qt6.cpp
@@ -113,9 +113,14 @@ qint64 AudioCaptureQt6::latency()
void AudioCaptureQt6::setVolume(qreal volume)
{
+ if (volume == m_volume)
+ return;
+
m_volume = volume;
if (m_audioSource != NULL)
m_audioSource->setVolume(volume);
+
+ emit volumeChanged(volume * 100.0);
}
void AudioCaptureQt6::suspend()
diff --git a/engine/audio/src/audiocapture_wavein.cpp b/engine/audio/src/audiocapture_wavein.cpp
index c89fd85d8d..4c50fbd680 100644
--- a/engine/audio/src/audiocapture_wavein.cpp
+++ b/engine/audio/src/audiocapture_wavein.cpp
@@ -154,7 +154,7 @@ bool AudioCaptureWaveIn::readAudio(int maxSize)
return false;
}
- while ( (waveHeaders[m_currentBufferIndex].dwFlags & WHDR_DONE) == 0)
+ while ((waveHeaders[m_currentBufferIndex].dwFlags & WHDR_DONE) == 0)
usleep(100);
memcpy(m_audioBuffer, m_internalBuffers[m_currentBufferIndex], maxSize * 2);
diff --git a/engine/audio/src/audioplugincache.cpp b/engine/audio/src/audioplugincache.cpp
index 617eaa0f8b..b8d6b0430d 100644
--- a/engine/audio/src/audioplugincache.cpp
+++ b/engine/audio/src/audioplugincache.cpp
@@ -28,7 +28,7 @@
#include "qlcfile.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
- #if defined( __APPLE__) || defined(Q_OS_MAC)
+ #if defined(__APPLE__) || defined(Q_OS_MAC)
#include "audiorenderer_portaudio.h"
#elif defined(WIN32) || defined(Q_OS_WIN)
#include "audiorenderer_waveout.h"
@@ -44,32 +44,33 @@
AudioPluginCache::AudioPluginCache(QObject *parent)
: QObject(parent)
{
+}
+
+AudioPluginCache::~AudioPluginCache()
+{
+}
+
+void AudioPluginCache::load(const QDir &dir)
+{
+ qDebug() << Q_FUNC_INFO << dir.path();
+
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
- #if defined( __APPLE__) || defined(Q_OS_MAC)
+#if defined(__APPLE__) || defined(Q_OS_MAC)
m_audioDevicesList = AudioRendererPortAudio::getDevicesInfo();
- #elif defined(WIN32) || defined(Q_OS_WIN)
+#elif defined(WIN32) || defined(Q_OS_WIN)
m_audioDevicesList = AudioRendererWaveOut::getDevicesInfo();
- #else
+#else
m_audioDevicesList = AudioRendererAlsa::getDevicesInfo();
- #endif
+#endif
#else
- #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
m_audioDevicesList = AudioRendererQt5::getDevicesInfo();
m_outputDevicesList = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
- #else
+#else
m_audioDevicesList = AudioRendererQt6::getDevicesInfo();
m_outputDevicesList = QMediaDevices::audioOutputs();
- #endif
#endif
-}
-
-AudioPluginCache::~AudioPluginCache()
-{
-}
-
-void AudioPluginCache::load(const QDir &dir)
-{
- qDebug() << Q_FUNC_INFO << dir.path();
+#endif
/* Check that we can access the directory */
if (dir.exists() == false || dir.isReadable() == false)
@@ -102,7 +103,7 @@ void AudioPluginCache::load(const QDir &dir)
QStringList AudioPluginCache::getSupportedFormats()
{
QStringList caps;
- foreach(QString path, m_pluginsMap.values())
+ foreach (QString path, m_pluginsMap.values())
{
QPluginLoader loader(path, this);
AudioDecoder* ptr = qobject_cast (loader.instance());
@@ -123,7 +124,7 @@ AudioDecoder *AudioPluginCache::getDecoderForFile(const QString &filename)
if (fn.exists() == false)
return NULL;
- foreach(QString path, m_pluginsMap.values())
+ foreach (QString path, m_pluginsMap.values())
{
QPluginLoader loader(path, this);
AudioDecoder* ptr = qobject_cast (loader.instance());
diff --git a/engine/audio/src/audiorenderer.cpp b/engine/audio/src/audiorenderer.cpp
index ccfdda4921..37e2f5421a 100644
--- a/engine/audio/src/audiorenderer.cpp
+++ b/engine/audio/src/audiorenderer.cpp
@@ -25,6 +25,7 @@
AudioRenderer::AudioRenderer (QObject* parent)
: QThread (parent)
+ , m_looped(false)
, m_fadeStep(0.0)
, m_userStop(true)
, m_pause(false)
@@ -33,7 +34,6 @@ AudioRenderer::AudioRenderer (QObject* parent)
, m_adec(NULL)
, audioDataRead(0)
, pendingAudioBytes(0)
- , m_looped(false)
{
}
@@ -47,8 +47,25 @@ void AudioRenderer::adjustIntensity(qreal fraction)
m_intensity = CLAMP(fraction, 0.0, 1.0);
}
+bool AudioRenderer::isLooped()
+{
+ return m_looped;
+}
+
+void AudioRenderer::setLooped(bool looped)
+{
+ m_looped = looped;
+}
+
+/*********************************************************************
+ * Fade sequences
+ *********************************************************************/
+
void AudioRenderer::setFadeIn(uint fadeTime)
{
+ m_fadeStep = 0;
+ m_currentIntensity = 1.0;
+
if (fadeTime == 0 || m_adec == NULL)
return;
@@ -63,14 +80,13 @@ void AudioRenderer::setFadeIn(uint fadeTime)
void AudioRenderer::setFadeOut(uint fadeTime)
{
- if (fadeTime == 0 || m_fadeStep != 0 || m_adec == NULL)
+ if (fadeTime == 0 || m_adec == NULL)
return;
quint32 sampleRate = m_adec->audioParameters().sampleRate();
int channels = m_adec->audioParameters().channels();
qreal stepsCount = (qreal)fadeTime * ((qreal)(sampleRate * channels) / 1000);
m_fadeStep = -(m_intensity / stepsCount);
- m_currentIntensity = m_intensity;
qDebug() << Q_FUNC_INFO << "stepsCount:" << stepsCount << ", fadeStep:" << m_fadeStep;
}
@@ -84,10 +100,16 @@ void AudioRenderer::stop()
m_currentIntensity = 1.0;
}
+/*********************************************************************
+ * Thread functions
+ *********************************************************************/
+
void AudioRenderer::run()
{
+ qint64 audioDataWritten;
m_userStop = false;
audioDataRead = 0;
+
int sampleSize = m_adec->audioParameters().sampleSize();
if (sampleSize > 2)
sampleSize = 2;
@@ -95,7 +117,7 @@ void AudioRenderer::run()
while (!m_userStop)
{
QMutexLocker locker(&m_mutex);
- qint64 audioDataWritten = 0;
+
if (m_pause == false)
{
//qDebug() << "Pending audio bytes: " << pendingAudioBytes;
@@ -117,6 +139,8 @@ void AudioRenderer::run()
}
if (m_intensity != 1.0 || m_fadeStep != 0)
{
+ //qDebug() << "Intensity" << m_intensity << ", current" << m_currentIntensity << ", fadeStep" << m_fadeStep;
+
for (int i = 0; i < audioDataRead; i+=sampleSize)
{
qreal scaleFactor = m_intensity;
@@ -175,7 +199,7 @@ void AudioRenderer::run()
if (audioDataWritten == 0)
usleep(15000);
}
- //qDebug() << "[Cycle] read: " << audioDataRead << ", written: " << audioDataWritten;
+ //qDebug() << "[Cycle] read:" << audioDataRead << ", written:" << audioDataWritten << ", pending:" << pendingAudioBytes;
}
else
{
@@ -186,7 +210,3 @@ void AudioRenderer::run()
reset();
}
-void AudioRenderer::setLooped(bool looped)
-{
- m_looped = looped;
-}
diff --git a/engine/audio/src/audiorenderer.h b/engine/audio/src/audiorenderer.h
index 6c49e4a1e9..719965f33d 100644
--- a/engine/audio/src/audiorenderer.h
+++ b/engine/audio/src/audiorenderer.h
@@ -93,8 +93,12 @@ class AudioRenderer : public QThread
void adjustIntensity(qreal fraction);
+ /* Get/Set the looping flag */
+ bool isLooped();
void setLooped(bool looped);
+private:
+ bool m_looped;
/*********************************************************************
* Fade sequences
@@ -145,7 +149,6 @@ class AudioRenderer : public QThread
unsigned char audioData[8 * 1024];
qint64 audioDataRead;
qint64 pendingAudioBytes;
- bool m_looped;
};
/** @} */
diff --git a/engine/audio/src/audiorenderer_alsa.cpp b/engine/audio/src/audiorenderer_alsa.cpp
index 043fdccaf1..6155a5f545 100644
--- a/engine/audio/src/audiorenderer_alsa.cpp
+++ b/engine/audio/src/audiorenderer_alsa.cpp
@@ -220,7 +220,7 @@ QList AudioRendererAlsa::getDevicesInfo()
QList devList;
int cardIdx = -1;
- while( snd_card_next( &cardIdx ) == 0 && cardIdx >= 0 )
+ while (snd_card_next(&cardIdx) == 0 && cardIdx >= 0)
{
snd_ctl_t *cardHandle;
snd_ctl_card_info_t *cardInfo;
@@ -249,31 +249,31 @@ QList AudioRendererAlsa::getDevicesInfo()
qDebug() << "[getDevicesInfo] Card" << cardIdx << "=" << snd_ctl_card_info_get_name(cardInfo);
- while( snd_ctl_pcm_next_device( cardHandle, &devIdx ) == 0 && devIdx >= 0 )
+ while (snd_ctl_pcm_next_device(cardHandle, &devIdx) == 0 && devIdx >= 0)
{
snd_pcm_info_t *pcmInfo;
int tmpCaps = 0;
- snd_pcm_info_alloca( &pcmInfo );
+ snd_pcm_info_alloca(&pcmInfo);
- snprintf( str, sizeof (str), "plughw:%d,%d", cardIdx, devIdx );
+ snprintf(str, sizeof (str), "plughw:%d,%d", cardIdx, devIdx);
/* Obtain info about this particular device */
- snd_pcm_info_set_device( pcmInfo, devIdx );
- snd_pcm_info_set_subdevice( pcmInfo, 0 );
- snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_CAPTURE );
- if( snd_ctl_pcm_info( cardHandle, pcmInfo ) >= 0 )
+ snd_pcm_info_set_device(pcmInfo, devIdx);
+ snd_pcm_info_set_subdevice(pcmInfo, 0);
+ snd_pcm_info_set_stream(pcmInfo, SND_PCM_STREAM_CAPTURE);
+ if (snd_ctl_pcm_info(cardHandle, pcmInfo) >= 0)
tmpCaps |= AUDIO_CAP_INPUT;
- snd_pcm_info_set_stream( pcmInfo, SND_PCM_STREAM_PLAYBACK );
- if( snd_ctl_pcm_info( cardHandle, pcmInfo ) >= 0 )
+ snd_pcm_info_set_stream(pcmInfo, SND_PCM_STREAM_PLAYBACK);
+ if (snd_ctl_pcm_info(cardHandle, pcmInfo) >= 0)
tmpCaps |= AUDIO_CAP_OUTPUT;
if (tmpCaps != 0)
{
AudioDeviceInfo info;
info.deviceName = QString(snd_ctl_card_info_get_name(cardInfo)) + " - " +
- QString (snd_pcm_info_get_name( pcmInfo ));
+ QString(snd_pcm_info_get_name(pcmInfo));
info.privateName = QString(str);
info.capabilities = tmpCaps;
devList.append(info);
@@ -297,7 +297,7 @@ qint64 AudioRendererAlsa::writeAudio(unsigned char *data, qint64 maxSize)
if (pcm_handle == NULL || m_prebuf == NULL)
return 0;
- if((maxSize = qMin(maxSize, m_prebuf_size - m_prebuf_fill)) > 0)
+ if ((maxSize = qMin(maxSize, m_prebuf_size - m_prebuf_fill)) > 0)
{
memmove(m_prebuf + m_prebuf_fill, data, maxSize);
m_prebuf_fill += maxSize;
@@ -325,7 +325,7 @@ qint64 AudioRendererAlsa::writeAudio(unsigned char *data, qint64 maxSize)
long AudioRendererAlsa::alsa_write(unsigned char *data, long size)
{
long m = snd_pcm_avail_update(pcm_handle);
- if(m >= 0 && m < size)
+ if (m >= 0 && m < size)
{
snd_pcm_wait(pcm_handle, 500);
return 0;
diff --git a/engine/audio/src/audiorenderer_coreaudio.cpp b/engine/audio/src/audiorenderer_coreaudio.cpp
index 7797bd7fa8..9570cd9d2f 100644
--- a/engine/audio/src/audiorenderer_coreaudio.cpp
+++ b/engine/audio/src/audiorenderer_coreaudio.cpp
@@ -106,7 +106,7 @@ qint64 AudioRendererCoreAudio::latency()
qint64 AudioRendererCoreAudio::writeAudio(unsigned char *data, qint64 maxSize)
{
- if(m_buffersFilled == AUDIO_BUFFERS_NUM)
+ if (m_buffersFilled == AUDIO_BUFFERS_NUM)
return 0;
qint64 size = maxSize;
diff --git a/engine/audio/src/audiorenderer_portaudio.cpp b/engine/audio/src/audiorenderer_portaudio.cpp
index 25ea4484f6..9456966945 100644
--- a/engine/audio/src/audiorenderer_portaudio.cpp
+++ b/engine/audio/src/audiorenderer_portaudio.cpp
@@ -41,15 +41,15 @@ AudioRendererPortAudio::~AudioRendererPortAudio()
PaError err;
err = Pa_Terminate();
- if( err != paNoError )
- qDebug() << "PortAudio error: " << Pa_GetErrorText( err );
+ if (err != paNoError)
+ qDebug() << "PortAudio error: " << Pa_GetErrorText(err);
}
-int AudioRendererPortAudio::dataCallback ( const void *, void *outputBuffer,
- unsigned long frameCount,
- const PaStreamCallbackTimeInfo*,
- PaStreamCallbackFlags ,
- void *userData )
+int AudioRendererPortAudio::dataCallback(const void *, void *outputBuffer,
+ unsigned long frameCount,
+ const PaStreamCallbackTimeInfo*,
+ PaStreamCallbackFlags ,
+ void *userData)
{
AudioRendererPortAudio *PAobj = (AudioRendererPortAudio *)userData;
@@ -81,7 +81,7 @@ bool AudioRendererPortAudio::initialize(quint32 freq, int chan, AudioFormat form
PaStreamFlags flags = paNoFlag;
err = Pa_Initialize();
- if( err != paNoError )
+ if (err != paNoError)
return false;
if (m_device.isEmpty())
@@ -105,7 +105,7 @@ bool AudioRendererPortAudio::initialize(quint32 freq, int chan, AudioFormat form
m_channels = chan;
outputParameters.channelCount = chan; /* stereo output */
- outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
+ outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
switch (format)
@@ -131,15 +131,15 @@ bool AudioRendererPortAudio::initialize(quint32 freq, int chan, AudioFormat form
return false;
}
- err = Pa_OpenStream( &m_paStream, NULL, &outputParameters,
- freq, paFramesPerBufferUnspecified, flags, dataCallback, this );
+ err = Pa_OpenStream(&m_paStream, NULL, &outputParameters,
+ freq, paFramesPerBufferUnspecified, flags, dataCallback, this);
- if( err != paNoError )
+ if (err != paNoError)
return false;
- err = Pa_StartStream( m_paStream );
+ err = Pa_StartStream(m_paStream);
- if( err != paNoError )
+ if (err != paNoError)
return false;
return true;
@@ -157,19 +157,19 @@ QList AudioRendererPortAudio::getDevicesInfo()
int numDevices, err, i;
err = Pa_Initialize();
- if( err != paNoError )
+ if (err != paNoError)
return devList;
numDevices = Pa_GetDeviceCount();
- if( numDevices < 0 )
+ if (numDevices < 0)
{
- qWarning("ERROR: Pa_CountDevices returned 0x%x\n", numDevices );
+ qWarning("ERROR: Pa_CountDevices returned 0x%x\n", numDevices);
return devList;
}
for (i = 0; i < numDevices; i++)
{
- const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo( i );
+ const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (deviceInfo != NULL)
{
AudioDeviceInfo info;
@@ -185,8 +185,8 @@ QList AudioRendererPortAudio::getDevicesInfo()
}
err = Pa_Terminate();
- if( err != paNoError )
- qDebug() << "PortAudio error: " << Pa_GetErrorText( err );
+ if (err != paNoError)
+ qDebug() << "PortAudio error: " << Pa_GetErrorText(err);
return devList;
}
@@ -211,16 +211,16 @@ void AudioRendererPortAudio::drain()
void AudioRendererPortAudio::reset()
{
QMutexLocker locker(&m_paMutex);
- if ( m_paStream == NULL)
+ if (m_paStream == NULL)
return;
PaError err;
- err = Pa_StopStream( m_paStream );
- if( err != paNoError )
+ err = Pa_StopStream(m_paStream);
+ if (err != paNoError)
qDebug() << "PortAudio Error: Stop stream failed!";
- err = Pa_CloseStream( m_paStream );
- if( err != paNoError )
+ err = Pa_CloseStream(m_paStream);
+ if (err != paNoError)
qDebug() << "PortAudio Error: Close stream failed!";
m_buffer.clear();
m_paStream = NULL;
@@ -232,5 +232,4 @@ void AudioRendererPortAudio::suspend()
void AudioRendererPortAudio::resume()
{
-
}
diff --git a/engine/audio/src/audiorenderer_portaudio.h b/engine/audio/src/audiorenderer_portaudio.h
index 40a6bb2136..b8d1c99059 100644
--- a/engine/audio/src/audiorenderer_portaudio.h
+++ b/engine/audio/src/audiorenderer_portaudio.h
@@ -63,11 +63,11 @@ class AudioRendererPortAudio : public AudioRenderer
void resume();
private:
- static int dataCallback ( const void *inputBuffer, void *outputBuffer,
- unsigned long framesPerBuffer,
- const PaStreamCallbackTimeInfo* timeInfo,
- PaStreamCallbackFlags statusFlags,
- void *userData );
+ static int dataCallback (const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer,
+ const PaStreamCallbackTimeInfo* timeInfo,
+ PaStreamCallbackFlags statusFlags,
+ void *userData );
PaStream *m_paStream;
QMutex m_paMutex;
diff --git a/engine/audio/src/audiorenderer_qt5.cpp b/engine/audio/src/audiorenderer_qt5.cpp
index cfa3928b16..165ce3efec 100644
--- a/engine/audio/src/audiorenderer_qt5.cpp
+++ b/engine/audio/src/audiorenderer_qt5.cpp
@@ -22,11 +22,10 @@
#include
#include "doc.h"
-#include "audiodecoder.h"
#include "audiorenderer_qt5.h"
#include "audioplugincache.h"
-AudioRendererQt5::AudioRendererQt5(QString device, QObject * parent)
+AudioRendererQt5::AudioRendererQt5(QString device, Doc *doc, QObject *parent)
: AudioRenderer(parent)
, m_audioOutput(NULL)
, m_output(NULL)
@@ -34,7 +33,6 @@ AudioRendererQt5::AudioRendererQt5(QString device, QObject * parent)
{
QSettings settings;
QString devName = "";
- Doc *doc = qobject_cast(parent);
QVariant var;
if (m_device.isEmpty())
@@ -94,7 +92,6 @@ bool AudioRendererQt5::initialize(quint32 freq, int chan, AudioFormat format)
{
m_format = m_deviceInfo.nearestFormat(m_format);
qWarning() << "Default format not supported - trying to use nearest" << m_format.sampleRate();
-
}
return true;
@@ -108,7 +105,6 @@ qint64 AudioRendererQt5::latency()
QList AudioRendererQt5::getDevicesInfo()
{
QList devList;
- int i = 0;
QStringList outDevs, inDevs;
// create a preliminary list of input devices only
@@ -121,7 +117,7 @@ QList AudioRendererQt5::getDevicesInfo()
outDevs.append(deviceInfo.deviceName());
AudioDeviceInfo info;
info.deviceName = deviceInfo.deviceName();
- info.privateName = deviceInfo.deviceName(); //QString::number(i);
+ info.privateName = deviceInfo.deviceName();
info.capabilities = 0;
info.capabilities |= AUDIO_CAP_OUTPUT;
if (inDevs.contains(deviceInfo.deviceName()))
@@ -130,19 +126,17 @@ QList AudioRendererQt5::getDevicesInfo()
inDevs.removeOne(deviceInfo.deviceName());
}
devList.append(info);
- i++;
}
// add the devices left in the input list. These don't have output capabilities
- foreach(QString dev, inDevs)
+ foreach (QString dev, inDevs)
{
AudioDeviceInfo info;
info.deviceName = dev;
- info.privateName = dev; //QString::number(i);
+ info.privateName = dev;
info.capabilities = 0;
info.capabilities |= AUDIO_CAP_INPUT;
devList.append(info);
- i++;
}
return devList;
@@ -188,7 +182,7 @@ void AudioRendererQt5::run()
{
m_audioOutput = new QAudioOutput(m_deviceInfo, m_format);
- if(m_audioOutput == NULL)
+ if (m_audioOutput == NULL)
{
qWarning() << "Cannot open audio output stream from device" << m_deviceInfo.deviceName();
return;
@@ -197,7 +191,7 @@ void AudioRendererQt5::run()
m_audioOutput->setBufferSize(8192 * 8);
m_output = m_audioOutput->start();
- if(m_audioOutput->error() != QAudio::NoError)
+ if (m_audioOutput->error() != QAudio::NoError)
{
qWarning() << "Cannot start audio output stream. Error:" << m_audioOutput->error();
return;
diff --git a/engine/audio/src/audiorenderer_qt5.h b/engine/audio/src/audiorenderer_qt5.h
index 335829b30f..df6377f408 100644
--- a/engine/audio/src/audiorenderer_qt5.h
+++ b/engine/audio/src/audiorenderer_qt5.h
@@ -21,11 +21,12 @@
#define AUDIORENDERER_QT5_H
#include "audiorenderer.h"
-#include "audiodecoder.h"
#include
#include
+class Doc;
+
/** @addtogroup engine_audio Audio
* @{
*/
@@ -34,7 +35,7 @@ class AudioRendererQt5 : public AudioRenderer
{
Q_OBJECT
public:
- AudioRendererQt5(QString device, QObject * parent = 0);
+ AudioRendererQt5(QString device, Doc *doc, QObject *parent = 0);
~AudioRendererQt5();
/** @reimpl */
diff --git a/engine/audio/src/audiorenderer_qt6.cpp b/engine/audio/src/audiorenderer_qt6.cpp
index 7fd49b44b2..dd12676de3 100644
--- a/engine/audio/src/audiorenderer_qt6.cpp
+++ b/engine/audio/src/audiorenderer_qt6.cpp
@@ -23,11 +23,10 @@
#include
#include "doc.h"
-#include "audiodecoder.h"
#include "audiorenderer_qt6.h"
#include "audioplugincache.h"
-AudioRendererQt6::AudioRendererQt6(QString device, QObject * parent)
+AudioRendererQt6::AudioRendererQt6(QString device, Doc *doc, QObject *parent)
: AudioRenderer(parent)
, m_audioSink(NULL)
, m_output(NULL)
@@ -35,7 +34,6 @@ AudioRendererQt6::AudioRendererQt6(QString device, QObject * parent)
{
QSettings settings;
QString devName = "";
- Doc *doc = qobject_cast(parent);
QVariant var;
if (m_device.isEmpty())
@@ -99,7 +97,6 @@ qint64 AudioRendererQt6::latency()
QList AudioRendererQt6::getDevicesInfo()
{
QList devList;
- int i = 0;
QStringList outDevs, inDevs;
// create a preliminary list of input devices only
@@ -121,11 +118,10 @@ QList AudioRendererQt6::getDevicesInfo()
inDevs.removeOne(deviceInfo.description());
}
devList.append(info);
- i++;
}
// add the devices left in the input list. These don't have output capabilities
- foreach(QString dev, inDevs)
+ foreach (QString dev, inDevs)
{
AudioDeviceInfo info;
info.deviceName = dev;
@@ -133,7 +129,6 @@ QList AudioRendererQt6::getDevicesInfo()
info.capabilities = 0;
info.capabilities |= AUDIO_CAP_INPUT;
devList.append(info);
- i++;
}
return devList;
@@ -141,16 +136,25 @@ QList AudioRendererQt6::getDevicesInfo()
qint64 AudioRendererQt6::writeAudio(unsigned char *data, qint64 maxSize)
{
- if (m_audioSink == NULL || m_audioSink->bytesFree() < maxSize)
+ qsizetype bFree = m_audioSink->bytesFree();
+
+ if (m_audioSink == NULL || bFree < maxSize)
return 0;
- //qDebug() << "writeAudio called !! - " << maxSize;
- qint64 written = m_output->write((const char *)data, maxSize);
+ //qDebug() << "writeAudio called !! - " << maxSize << m_outputBuffer.length() << bFree;
+
+ m_outputBuffer.append((char *)data, maxSize);
+
+ if (m_outputBuffer.length() >= bFree)
+ {
+ qint64 written = m_output->write(m_outputBuffer.data(), bFree);
- if (written != maxSize)
- qDebug() << "[writeAudio] expexcted to write" << maxSize << "but wrote" << written;
+ if (written != bFree)
+ qDebug() << "[writeAudio] expexcted to write" << bFree << "but wrote" << written;
- return written;
+ m_outputBuffer.remove(0, written);
+ }
+ return maxSize;
}
void AudioRendererQt6::drain()
@@ -177,6 +181,8 @@ void AudioRendererQt6::run()
{
if (m_audioSink == NULL)
{
+ qDebug() << "Creating audio sink on" << m_deviceInfo.description();
+
m_audioSink = new QAudioSink(m_deviceInfo, m_format);
if (m_audioSink == NULL)
@@ -188,7 +194,7 @@ void AudioRendererQt6::run()
m_audioSink->setBufferSize(8192 * 8);
m_output = m_audioSink->start();
- if(m_audioSink->error() != QAudio::NoError)
+ if (m_audioSink->error() != QAudio::NoError)
{
qWarning() << "Cannot start audio output stream. Error:" << m_audioSink->error();
return;
diff --git a/engine/audio/src/audiorenderer_qt6.h b/engine/audio/src/audiorenderer_qt6.h
index 0a24992efb..5573844a8f 100644
--- a/engine/audio/src/audiorenderer_qt6.h
+++ b/engine/audio/src/audiorenderer_qt6.h
@@ -21,13 +21,14 @@
#define AUDIORENDERER_QT6_H
#include "audiorenderer.h"
-#include "audiodecoder.h"
#include
#include
#include
#include
+class Doc;
+
/** @addtogroup engine_audio Audio
* @{
*/
@@ -36,7 +37,7 @@ class AudioRendererQt6 : public AudioRenderer
{
Q_OBJECT
public:
- AudioRendererQt6(QString device, QObject * parent = 0);
+ AudioRendererQt6(QString device, Doc *doc, QObject * parent = 0);
~AudioRendererQt6();
/** @reimpl */
@@ -76,6 +77,7 @@ class AudioRendererQt6 : public AudioRenderer
QAudioFormat m_format;
QString m_device;
QAudioDevice m_deviceInfo;
+ QByteArray m_outputBuffer;
};
/** @} */
diff --git a/engine/src/CMakeLists.txt b/engine/src/CMakeLists.txt
new file mode 100644
index 0000000000..54597e5a91
--- /dev/null
+++ b/engine/src/CMakeLists.txt
@@ -0,0 +1,200 @@
+set(module_name "qlcplusengine")
+
+add_library(${module_name} SHARED
+ ../../plugins/interfaces/qlcioplugin.cpp ../../plugins/interfaces/qlcioplugin.h
+ avolitesd4parser.cpp avolitesd4parser.h
+ bus.cpp bus.h
+ channelmodifier.cpp channelmodifier.h
+ channelsgroup.cpp channelsgroup.h
+ chaser.cpp chaser.h
+ chaseraction.h
+ chaserrunner.cpp chaserrunner.h
+ chaserstep.cpp chaserstep.h
+ collection.cpp collection.h
+ cue.cpp cue.h
+ cuestack.cpp cuestack.h
+ dmxdumpfactoryproperties.cpp dmxdumpfactoryproperties.h
+ dmxsource.h
+ doc.cpp doc.h
+ efx.cpp efx.h
+ efxfixture.cpp efxfixture.h
+ fadechannel.cpp fadechannel.h
+ fixture.cpp fixture.h
+ fixturegroup.cpp fixturegroup.h
+ function.cpp function.h
+ genericdmxsource.cpp genericdmxsource.h
+ genericfader.cpp genericfader.h
+ gradient.cpp gradient.h
+ grandmaster.cpp grandmaster.h
+ grouphead.cpp grouphead.h
+ inputoutputmap.cpp inputoutputmap.h
+ inputpatch.cpp inputpatch.h
+ ioplugincache.cpp ioplugincache.h
+ keypadparser.cpp keypadparser.h
+ mastertimer.cpp mastertimer.h
+ monitorproperties.cpp monitorproperties.h
+ outputpatch.cpp outputpatch.h
+ qlccapability.cpp qlccapability.h
+ qlcchannel.cpp qlcchannel.h
+ qlcclipboard.cpp qlcclipboard.h
+ qlcfile.cpp qlcfile.h
+ qlcfixturedef.cpp qlcfixturedef.h
+ qlcfixturedefcache.cpp qlcfixturedefcache.h
+ qlcfixturehead.cpp qlcfixturehead.h
+ qlcfixturemode.cpp qlcfixturemode.h
+ qlci18n.cpp qlci18n.h
+ qlcinputchannel.cpp qlcinputchannel.h
+ qlcinputfeedback.cpp qlcinputfeedback.h
+ qlcinputprofile.cpp qlcinputprofile.h
+ qlcinputsource.cpp qlcinputsource.h
+ qlcmodifierscache.cpp qlcmodifierscache.h
+ qlcpalette.cpp qlcpalette.h
+ qlcphysical.cpp qlcphysical.h
+ qlcpoint.cpp qlcpoint.h
+ rgbalgorithm.cpp rgbalgorithm.h
+ rgbaudio.cpp rgbaudio.h
+ rgbimage.cpp rgbimage.h
+ rgbmatrix.cpp rgbmatrix.h
+ rgbplain.cpp rgbplain.h
+ rgbscriptproperty.h
+ rgbscriptscache.cpp rgbscriptscache.h
+ rgbtext.cpp rgbtext.h
+ scene.cpp scene.h
+ scenevalue.cpp scenevalue.h
+ scriptwrapper.h
+ sequence.cpp sequence.h
+ show.cpp show.h
+ showfunction.cpp showfunction.h
+ showrunner.cpp showrunner.h
+ track.cpp track.h
+ universe.cpp universe.h
+ utils.h
+ video.cpp video.h
+)
+target_include_directories(${module_name} PUBLIC
+ ../../plugins/interfaces
+ ../audio/src
+)
+
+target_link_libraries(${module_name} PUBLIC
+ Qt${QT_MAJOR_VERSION}::Core
+ Qt${QT_MAJOR_VERSION}::Gui
+ Qt${QT_MAJOR_VERSION}::Multimedia
+)
+
+target_link_libraries(${module_name} PRIVATE
+ qlcplusaudio
+)
+
+if(NOT ANDROID AND NOT IOS)
+ target_include_directories(${module_name} PUBLIC
+ ../../hotplugmonitor/src
+ )
+
+ target_link_libraries(${module_name} PUBLIC
+ hotplugmonitor
+ )
+
+ target_link_directories(${module_name} PUBLIC
+ ${CMAKE_CURRENT_BINARY_DIR}/../../hotplugmonitor/src
+ )
+endif()
+
+if(UNIX AND NOT ANDROID AND NOT IOS AND NOT APPLE)
+ target_link_libraries(${module_name} PUBLIC
+ asound
+ )
+endif()
+
+if(WIN32)
+ target_sources(${module_name} PRIVATE
+ mastertimer-win32.cpp mastertimer-win32.h
+ )
+
+ target_include_directories(${module_name} PUBLIC
+ /
+ )
+
+ target_link_libraries(${module_name} PUBLIC
+ Qt${QT_MAJOR_VERSION}::Widgets
+ winmm
+ )
+endif()
+
+if((NOT ANDROID AND NOT IOS) AND (iokit))
+ target_link_libraries(${module_name} PUBLIC
+ "-framework CoreFoundation"
+ "-framework IOKit"
+ )
+endif()
+
+if(APPLE)
+ set_target_properties(${module_name} PROPERTIES
+ MACOSX_BUNDLE FALSE
+ )
+ target_link_libraries(${module_name} PUBLIC
+ "-framework AudioToolbox"
+ "-framework CoreAudio"
+ "-framework CoreFoundation"
+ )
+endif()
+
+if(qmlui OR (QT_VERSION_MAJOR GREATER 5))
+ target_sources(${module_name} PRIVATE
+ rgbscriptv4.cpp rgbscriptv4.h
+ scriptrunner.cpp scriptrunner.h
+ scriptv4.cpp scriptv4.h
+ )
+
+ target_link_libraries(${module_name} PUBLIC
+ Qt${QT_MAJOR_VERSION}::Qml
+ )
+endif()
+
+if(NOT (qmlui OR (QT_VERSION_MAJOR GREATER 5)))
+ target_sources(${module_name} PRIVATE
+ rgbscript.cpp rgbscript.h
+ script.cpp script.h
+ )
+
+ target_link_libraries(${module_name} PUBLIC
+ Qt${QT_MAJOR_VERSION}::Script
+ )
+endif()
+
+if(UNIX)
+ target_sources(${module_name} PRIVATE
+ mastertimer-unix.cpp mastertimer-unix.h
+ )
+endif()
+
+if((NOT ANDROID AND NOT IOS) AND (${FFTW3_FOUND}))
+ target_compile_definitions(${module_name} PUBLIC
+ HAS_FFTW3
+ )
+endif()
+
+if(((NOT ANDROID AND NOT IOS) AND (${FFTW3_FOUND})))
+ target_link_directories(${module_name} PUBLIC
+ ${FFTW3_LIBRARY_DIRS}
+ )
+ target_link_libraries(${module_name} PUBLIC
+ ${FFTW3_LIBRARIES}
+ )
+endif()
+
+set(CONFIGFILE ${CMAKE_SOURCE_DIR}/engine/src/qlcconfig.h)
+set(LITERAL_HASH "#")
+
+if(WIN32 OR APPLE OR appimage)
+ configure_file(qlcconfig.h.noroot.in ${CONFIGFILE})
+elseif(UNIX OR ANDROID OR IOS)
+ configure_file(qlcconfig.h.in ${CONFIGFILE})
+endif()
+
+set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${CONFIGFILE})
+
+install(TARGETS ${module_name}
+ LIBRARY DESTINATION ${INSTALLROOT}/${LIBSDIR}
+ RUNTIME DESTINATION ${INSTALLROOT}/${LIBSDIR}
+)
diff --git a/engine/src/avolitesd4parser.cpp b/engine/src/avolitesd4parser.cpp
index b465c6a0b0..98f2baf224 100644
--- a/engine/src/avolitesd4parser.cpp
+++ b/engine/src/avolitesd4parser.cpp
@@ -816,6 +816,8 @@ QLCFixtureDef::FixtureType AvolitesD4Parser::guessType(QLCFixtureDef *def) const
return QLCFixtureDef::Strobe; // Duh.
else if (smoke > 0)
return QLCFixtureDef::Smoke; // Duh.
+ else if (haze > 0)
+ return QLCFixtureDef::Hazer; // Duh.
else if (nocol > 0)
return QLCFixtureDef::Dimmer; // Kinda..mmmmh..
else
diff --git a/engine/src/channelmodifier.cpp b/engine/src/channelmodifier.cpp
index 2524a8f156..6d23657b6b 100644
--- a/engine/src/channelmodifier.cpp
+++ b/engine/src/channelmodifier.cpp
@@ -16,14 +16,13 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
-
-#include "channelmodifier.h"
-#include "qlcfile.h"
-
#include
#include
#include
+#include "channelmodifier.h"
+#include "qlcfile.h"
+
ChannelModifier::ChannelModifier()
{
m_values.fill(0, 256);
@@ -120,7 +119,7 @@ QFile::FileError ChannelModifier::saveXML(const QString &fileName)
doc.writeTextElement(KXMLQLCChannelModName, m_name);
qDebug() << "Got map with" << m_map.count() << "handlers";
- for(int i = 0; i < m_map.count(); i++)
+ for (int i = 0; i < m_map.count(); i++)
{
QPair mapElement = m_map.at(i);
doc.writeStartElement(KXMLQLCChannelModHandler);
@@ -177,7 +176,7 @@ QFile::FileError ChannelModifier::loadXML(const QString &fileName, Type type)
{
setName(doc->readElementText());
}
- else if(doc->name() == KXMLQLCChannelModHandler)
+ else if (doc->name() == KXMLQLCChannelModHandler)
{
QPair dmxPair(0, 0);
QXmlStreamAttributes attrs = doc->attributes();
diff --git a/engine/src/channelsgroup.cpp b/engine/src/channelsgroup.cpp
index 7b165e25e7..62283a9005 100644
--- a/engine/src/channelsgroup.cpp
+++ b/engine/src/channelsgroup.cpp
@@ -263,7 +263,7 @@ bool ChannelsGroup::saveXML(QXmlStreamWriter *doc)
Q_ASSERT(doc != NULL);
QString str;
- foreach(SceneValue value, this->getChannels())
+ foreach (SceneValue value, this->getChannels())
{
if (str.isEmpty() == false)
str.append(",");
diff --git a/engine/src/chaser.cpp b/engine/src/chaser.cpp
index 1328248671..a00b3b3f76 100644
--- a/engine/src/chaser.cpp
+++ b/engine/src/chaser.cpp
@@ -25,16 +25,11 @@
#include
#include
-#include "qlcfixturedef.h"
-#include "qlcfile.h"
-
#include "chaserrunner.h"
#include "mastertimer.h"
#include "chaserstep.h"
#include "function.h"
-#include "fixture.h"
#include "chaser.h"
-#include "scene.h"
#include "doc.h"
#include "bus.h"
@@ -137,6 +132,7 @@ bool Chaser::addStep(const ChaserStep& step, int index)
}
emit changed(this->id());
+ emit stepsListChanged(this->id());
return true;
}
else
@@ -155,6 +151,7 @@ bool Chaser::removeStep(int index)
}
emit changed(this->id());
+ emit stepsListChanged(this->id());
return true;
}
else
@@ -173,6 +170,7 @@ bool Chaser::replaceStep(const ChaserStep& step, int index)
}
emit changed(this->id());
+ emit stepChanged(index);
return true;
}
else
@@ -236,7 +234,7 @@ void Chaser::setTotalDuration(quint32 msec)
{
uint origDuration = m_steps[i].duration;
m_steps[i].duration = ((double)m_steps[i].duration * msec) / dtDuration;
- if(m_steps[i].hold)
+ if (m_steps[i].hold)
m_steps[i].hold = ((double)m_steps[i].hold * (double)m_steps[i].duration) / (double)origDuration;
m_steps[i].fadeIn = m_steps[i].duration - m_steps[i].hold;
if (m_steps[i].fadeOut)
@@ -580,7 +578,7 @@ bool Chaser::contains(quint32 functionId)
Doc *doc = this->doc();
Q_ASSERT(doc != NULL);
- foreach(ChaserStep step, m_steps)
+ foreach (ChaserStep step, m_steps)
{
Function *function = doc->function(step.fid);
// contains() can be called during init, function may be NULL
@@ -600,7 +598,7 @@ QList Chaser::components()
{
QList ids;
- foreach(ChaserStep step, m_steps)
+ foreach (ChaserStep step, m_steps)
ids.append(step.fid);
return ids;
diff --git a/engine/src/chaser.h b/engine/src/chaser.h
index 01f1cf823d..15bf706173 100644
--- a/engine/src/chaser.h
+++ b/engine/src/chaser.h
@@ -155,6 +155,10 @@ public slots:
*/
void slotFunctionRemoved(quint32 fid);
+signals:
+ void stepChanged(int index);
+ void stepsListChanged(quint32 fid);
+
protected:
QList m_steps;
QMutex m_stepListMutex;
diff --git a/engine/src/chaserrunner.cpp b/engine/src/chaserrunner.cpp
index 487c7ee4f8..ee2a495668 100644
--- a/engine/src/chaserrunner.cpp
+++ b/engine/src/chaserrunner.cpp
@@ -26,12 +26,9 @@
#include
#include "chaserrunner.h"
-#include "genericfader.h"
#include "mastertimer.h"
-#include "fadechannel.h"
#include "chaserstep.h"
#include "qlcmacros.h"
-#include "fixture.h"
#include "chaser.h"
#include "scene.h"
#include "doc.h"
@@ -55,12 +52,12 @@ ChaserRunner::ChaserRunner(const Doc *doc, const Chaser *chaser, quint32 startTi
m_pendingAction.m_fadeMode = Chaser::FromFunction;
m_pendingAction.m_stepIndex = -1;
- if (m_chaser->type() == Function::SequenceType && startTime > 0)
+ if (startTime > 0)
{
qDebug() << "[ChaserRunner] startTime:" << startTime;
int idx = 0;
quint32 stepsTime = 0;
- foreach(ChaserStep step, chaser->steps())
+ foreach (ChaserStep step, chaser->steps())
{
uint duration = m_chaser->durationMode() == Chaser::Common ? m_chaser->duration() : step.duration;
@@ -99,7 +96,7 @@ void ChaserRunner::slotChaserChanged()
// Handle (possible) speed change on the next write() pass
m_updateOverrideSpeeds = true;
QList delList;
- foreach(ChaserRunnerStep *step, m_runnerSteps)
+ foreach (ChaserRunnerStep *step, m_runnerSteps)
{
if (!m_chaser->steps().contains(ChaserStep(step->m_function->id())))
{
@@ -114,11 +111,11 @@ void ChaserRunner::slotChaserChanged()
step->m_duration = stepDuration(step->m_index);
}
}
- foreach(ChaserRunnerStep *step, delList)
+ foreach (ChaserRunnerStep *step, delList)
{
step->m_function->stop(functionParent());
- delete step;
m_runnerSteps.removeAll(step);
+ delete step;
}
}
@@ -245,7 +242,7 @@ void ChaserRunner::setAction(ChaserAction &action)
{
bool stopped = false;
- foreach(ChaserRunnerStep *step, m_runnerSteps)
+ foreach (ChaserRunnerStep *step, m_runnerSteps)
{
if (action.m_stepIndex == step->m_index)
{
@@ -431,7 +428,7 @@ void ChaserRunner::adjustStepIntensity(qreal fraction, int requestedStepIndex, i
m_pendingAction.m_masterIntensity = fraction;
}
- foreach(ChaserRunnerStep *step, m_runnerSteps)
+ foreach (ChaserRunnerStep *step, m_runnerSteps)
{
if (stepIndex == step->m_index && step->m_function != NULL)
{
@@ -467,7 +464,7 @@ void ChaserRunner::adjustStepIntensity(qreal fraction, int requestedStepIndex, i
void ChaserRunner::clearRunningList()
{
// empty the running queue
- foreach(ChaserRunnerStep *step, m_runnerSteps)
+ foreach (ChaserRunnerStep *step, m_runnerSteps)
{
if (step->m_function)
{
@@ -580,7 +577,7 @@ void ChaserRunner::startNewStep(int index, MasterTimer *timer, qreal mIntensity,
newStep->m_intensityOverrideId = func->requestAttributeOverride(Function::Intensity, mIntensity * sIntensity);
}
- // Start the fire up !
+ // Start the fire up!
func->start(timer, functionParent(), 0, newStep->m_fadeIn, newStep->m_fadeOut,
func->defaultSpeed(), m_chaser->tempoType());
m_runnerSteps.append(newStep);
@@ -605,6 +602,16 @@ int ChaserRunner::getNextStepIndex()
m_chaser->direction() == Function::Backward)
currentStepIndex = m_chaser->stepsCount();
+ // Handle reverse Ping Pong at boundaries
+ if (m_chaser->runOrder() == Function::PingPong &&
+ m_pendingAction.m_action == ChaserPreviousStep)
+ {
+ if (currentStepIndex == 0)
+ m_direction = Function::Backward;
+ else if (currentStepIndex == m_chaser->stepsCount() - 1)
+ m_direction = Function::Forward;
+ }
+
// Next step
if (m_direction == Function::Forward)
{
@@ -705,7 +712,7 @@ void ChaserRunner::setPause(bool enable, QList universes)
qDebug() << "[ChaserRunner] processing pause request:" << enable;
- foreach(ChaserRunnerStep *step, m_runnerSteps)
+ foreach (ChaserRunnerStep *step, m_runnerSteps)
step->m_function->setPause(enable);
// there might be a Scene fading out, so request pause
@@ -740,7 +747,11 @@ bool ChaserRunner::write(MasterTimer *timer, QList universes)
if (m_pendingAction.m_stepIndex != -1)
{
clearRunningList();
- m_lastRunStepIdx = m_pendingAction.m_stepIndex;
+ if (m_chaser->runOrder() == Function::Random)
+ m_lastRunStepIdx = randomStepIndex(m_pendingAction.m_stepIndex);
+ else
+ m_lastRunStepIdx = m_pendingAction.m_stepIndex;
+
qDebug() << "[ChaserRunner] Starting from step" << m_lastRunStepIdx << "@ offset" << m_startOffset;
startNewStep(m_lastRunStepIdx, timer, m_pendingAction.m_masterIntensity,
m_pendingAction.m_stepIntensity, m_pendingAction.m_fadeMode);
@@ -756,7 +767,7 @@ bool ChaserRunner::write(MasterTimer *timer, QList universes)
quint32 prevStepRoundElapsed = 0;
- foreach(ChaserRunnerStep *step, m_runnerSteps)
+ foreach (ChaserRunnerStep *step, m_runnerSteps)
{
if (m_chaser->tempoType() == Function::Beats && timer->isBeat())
{
@@ -773,8 +784,8 @@ bool ChaserRunner::write(MasterTimer *timer, QList universes)
m_lastFunctionID = step->m_function->type() == Function::SceneType ? step->m_function->id() : Function::invalidId();
step->m_function->stop(functionParent(), m_chaser->type() == Function::SequenceType);
- delete step;
m_runnerSteps.removeOne(step);
+ delete step;
}
else
{
diff --git a/engine/src/chaserstep.cpp b/engine/src/chaserstep.cpp
index 750b9fb4af..78cbae3cd1 100644
--- a/engine/src/chaserstep.cpp
+++ b/engine/src/chaserstep.cpp
@@ -284,7 +284,7 @@ bool ChaserStep::saveXML(QXmlStreamWriter *doc, int stepNumber, bool isSequence)
doc->writeAttribute(KXMLQLCSequenceSceneValues, QString::number(values.count()));
QString stepValues;
quint32 fixtureID = Fixture::invalidId();
- foreach(SceneValue scv, values)
+ foreach (SceneValue scv, values)
{
// step values are saved as a string with the following syntax:
// fixtureID:channel,value,channel,value:fixtureID:channel,value ... etc
diff --git a/engine/src/collection.cpp b/engine/src/collection.cpp
index 818a6e8abd..894eb3acfd 100644
--- a/engine/src/collection.cpp
+++ b/engine/src/collection.cpp
@@ -26,8 +26,6 @@
#include
#include
-#include "qlcfile.h"
-
#include "mastertimer.h"
#include "collection.h"
#include "function.h"
@@ -63,7 +61,7 @@ quint32 Collection::totalDuration()
{
quint32 totalDuration = 0;
- foreach(QVariant fid, functions())
+ foreach (QVariant fid, functions())
{
Function* function = doc()->function(fid.toUInt());
totalDuration += function->totalDuration();
diff --git a/engine/src/doc.cpp b/engine/src/doc.cpp
index ef21c04e00..9e285771cc 100644
--- a/engine/src/doc.cpp
+++ b/engine/src/doc.cpp
@@ -32,7 +32,6 @@
#include "qlcfixturemode.h"
#include "qlcfixturedef.h"
-#include "qlcfile.h"
#include "monitorproperties.h"
#include "audioplugincache.h"
@@ -45,9 +44,7 @@
#include "sequence.h"
#include "fixture.h"
#include "chaser.h"
-#include "scene.h"
#include "show.h"
-#include "efx.h"
#include "doc.h"
#include "bus.h"
@@ -538,7 +535,7 @@ bool Doc::replaceFixtures(QList newFixturesList)
m_latestFixtureId = 0;
m_addresses.clear();
- foreach(Fixture *fixture, newFixturesList)
+ foreach (Fixture *fixture, newFixturesList)
{
quint32 id = fixture->id();
// create a copy of the original cause remapping will
@@ -579,6 +576,9 @@ bool Doc::replaceFixtures(QList newFixturesList)
}
newFixture->setExcludeFadeChannels(fixture->excludeFadeChannels());
+ newFixture->setForcedHTPChannels(fixture->forcedHTPChannels());
+ newFixture->setForcedLTPChannels(fixture->forcedLTPChannels());
+
m_fixtures.insert(id, newFixture);
m_fixturesListCacheUpToDate = false;
@@ -1046,7 +1046,7 @@ QList Doc::functions() const
QList Doc::functionsByType(Function::Type type) const
{
QList list;
- foreach(Function *f, m_functions)
+ foreach (Function *f, m_functions)
{
if (f != NULL && f->type() == type)
list.append(f);
@@ -1054,6 +1054,16 @@ QList Doc::functionsByType(Function::Type type) const
return list;
}
+Function *Doc::functionByName(QString name)
+{
+ foreach (Function *f, m_functions)
+ {
+ if (f != NULL && f->name() == name)
+ return f;
+ }
+ return NULL;
+}
+
bool Doc::deleteFunction(quint32 id)
{
if (m_functions.contains(id) == true)
@@ -1177,7 +1187,7 @@ QList Doc::getUsage(quint32 fid)
Show *s = qobject_cast(f);
foreach (Track *t, s->tracks())
{
- foreach(ShowFunction *sf, t->showFunctions())
+ foreach (ShowFunction *sf, t->showFunctions())
{
if (sf->functionID() == fid)
{
@@ -1224,7 +1234,7 @@ MonitorProperties *Doc::monitorProperties()
* Load & Save
*****************************************************************************/
-bool Doc::loadXML(QXmlStreamReader &doc)
+bool Doc::loadXML(QXmlStreamReader &doc, bool loadIO)
{
clearErrorLog();
@@ -1274,7 +1284,7 @@ bool Doc::loadXML(QXmlStreamReader &doc)
/* LEGACY */
Bus::instance()->loadXML(doc);
}
- else if (doc.name() == KXMLIOMap)
+ else if (doc.name() == KXMLIOMap && loadIO)
{
m_ioMap->loadXML(doc);
}
diff --git a/engine/src/doc.h b/engine/src/doc.h
index 858f06be4f..4da3ee6804 100644
--- a/engine/src/doc.h
+++ b/engine/src/doc.h
@@ -500,6 +500,13 @@ private slots:
*/
QList functionsByType(Function::Type type) const;
+ /**
+ * Get a pointer to a Function with the given name
+ * @param name lookup Function name
+ * @return pointer to Function or null if not found
+ */
+ Function *functionByName(QString name);
+
/**
* Delete the given function
*
@@ -605,9 +612,10 @@ private slots:
* Load contents from the given XML document
*
* @param root The Engine XML root node to load from
+ * @param loadIO Parse the InputOutputMap tag too
* @return true if successful, otherwise false
*/
- bool loadXML(QXmlStreamReader &doc);
+ bool loadXML(QXmlStreamReader &doc, bool loadIO = true);
/**
* Save contents to the given XML file.
diff --git a/engine/src/efx.cpp b/engine/src/efx.cpp
index 8046edf194..3a3d408874 100644
--- a/engine/src/efx.cpp
+++ b/engine/src/efx.cpp
@@ -26,16 +26,9 @@
#include
-#include "qlcfixturemode.h"
-#include "qlcfixturedef.h"
#include "genericfader.h"
-#include "qlcchannel.h"
-#include "qlcmacros.h"
-#include "qlcfile.h"
-
#include "mastertimer.h"
-#include "fixture.h"
-#include "scene.h"
+#include "qlcmacros.h"
#include "doc.h"
#include "efx.h"
#include "bus.h"
@@ -142,12 +135,19 @@ void EFX::setDuration(uint ms)
{
Function::setDuration(ms);
- for(int i = 0; i < m_fixtures.size(); ++i)
+ for (int i = 0; i < m_fixtures.size(); ++i)
m_fixtures[i]->durationChanged();
emit durationChanged(ms);
}
+uint EFX::loopDuration() const
+{
+ uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
+
+ return duration() - fadeIn;
+}
+
/*****************************************************************************
* Algorithm
*****************************************************************************/
@@ -180,6 +180,7 @@ QStringList EFX::algorithmList()
list << algorithmToString(EFX::Diamond);
list << algorithmToString(EFX::Square);
list << algorithmToString(EFX::SquareChoppy);
+ list << algorithmToString(EFX::SquareTrue);
list << algorithmToString(EFX::Leaf);
list << algorithmToString(EFX::Lissajous);
return list;
@@ -204,6 +205,8 @@ QString EFX::algorithmToString(EFX::Algorithm algo)
return QString(KXMLQLCEFXSquareAlgorithmName);
case EFX::SquareChoppy:
return QString(KXMLQLCEFXSquareChoppyAlgorithmName);
+ case EFX::SquareTrue:
+ return QString(KXMLQLCEFXSquareTrueAlgorithmName);
case EFX::Leaf:
return QString(KXMLQLCEFXLeafAlgorithmName);
case EFX::Lissajous:
@@ -225,6 +228,8 @@ EFX::Algorithm EFX::stringToAlgorithm(const QString& str)
return EFX::Square;
else if (str == QString(KXMLQLCEFXSquareChoppyAlgorithmName))
return EFX::SquareChoppy;
+ else if (str == QString(KXMLQLCEFXSquareTrueAlgorithmName))
+ return EFX::SquareTrue;
else if (str == QString(KXMLQLCEFXLeafAlgorithmName))
return EFX::Leaf;
else if (str == QString(KXMLQLCEFXLissajousAlgorithmName))
@@ -267,7 +272,7 @@ void EFX::preview(QPolygonF &polygon, Function::Direction direction, int startOf
}
}
-void EFX::calculatePoint(Function::Direction direction, int startOffset, float iterator, float* x, float* y) const
+void EFX::calculatePoint(Function::Direction direction, int startOffset, float iterator, float *x, float *y) const
{
iterator = calculateDirection(direction, iterator);
iterator += convertOffset(startOffset + getAttributeValue(StartOffset));
@@ -278,15 +283,27 @@ void EFX::calculatePoint(Function::Direction direction, int startOffset, float i
calculatePoint(iterator, x, y);
}
-void EFX::rotateAndScale(float* x, float* y) const
+void EFX::rotateAndScale(float *x, float *y) const
{
float xx = *x;
float yy = *y;
float w = getAttributeValue(Width);
float h = getAttributeValue(Height);
+ float fadeScale = 1.0;
- *x = (getAttributeValue(XOffset)) + xx * m_cosR * w + yy * m_sinR * h;
- *y = (getAttributeValue(YOffset)) + -xx * m_sinR * w + yy * m_cosR * h;
+ if (isRunning())
+ {
+ uint fadeIn = overrideFadeInSpeed() == defaultSpeed() ? fadeInSpeed() : overrideFadeInSpeed();
+ if (fadeIn > 0 && elapsed() <= fadeIn)
+ {
+ fadeScale = SCALE(float(elapsed()),
+ float(0), float(fadeIn),
+ float(0), float(1.0));
+ }
+ }
+
+ *x = (getAttributeValue(XOffset)) + xx * m_cosR * (w * fadeScale) + yy * m_sinR * (h * fadeScale);
+ *y = (getAttributeValue(YOffset)) + -xx * m_sinR * (w * fadeScale) + yy * m_cosR * (h * fadeScale);
}
float EFX::calculateDirection(Function::Direction direction, float iterator) const
@@ -303,6 +320,7 @@ float EFX::calculateDirection(Function::Direction direction, float iterator) con
case Diamond:
case Square:
case SquareChoppy:
+ case SquareTrue:
case Leaf:
case Lissajous:
return (M_PI * 2.0) - iterator;
@@ -312,7 +330,7 @@ float EFX::calculateDirection(Function::Direction direction, float iterator) con
}
// this function should map from 0..M_PI * 2 -> -1..1
-void EFX::calculatePoint(float iterator, float* x, float* y) const
+void EFX::calculatePoint(float iterator, float *x, float *y) const
{
switch (algorithm())
{
@@ -369,6 +387,29 @@ void EFX::calculatePoint(float iterator, float* x, float* y) const
*x = round(cos(iterator));
*y = round(sin(iterator));
break;
+
+ case SquareTrue:
+ if (iterator < M_PI / 2)
+ {
+ *x = 1;
+ *y = 1;
+ }
+ else if (M_PI / 2 <= iterator && iterator < M_PI)
+ {
+ *x = 1;
+ *y = -1;
+ }
+ else if (M_PI <= iterator && iterator < M_PI * 3 / 2)
+ {
+ *x = -1;
+ *y = -1;
+ }
+ else // M_PI * 3 / 2 <= iterator
+ {
+ *x = -1;
+ *y = 1;
+ }
+ break;
case Leaf:
*x = pow(cos(iterator + M_PI_2), 5);
@@ -602,7 +643,7 @@ bool EFX::addFixture(EFXFixture* ef)
* not prevent multiple entries because a fixture can have multiple efx. */
//! @todo Prevent multiple entries using head & mode
int i;
- for(i = 0; i < m_fixtures.size (); i++)
+ for (i = 0; i < m_fixtures.size (); i++)
{
if (m_fixtures[i]->head() == ef->head())
{
@@ -612,7 +653,7 @@ bool EFX::addFixture(EFXFixture* ef)
}
/* If not inserted, put the EFXFixture object into our list */
- if(i >= m_fixtures.size())
+ if (i >= m_fixtures.size())
m_fixtures.append(ef);
emit changed(this->id());
@@ -620,6 +661,15 @@ bool EFX::addFixture(EFXFixture* ef)
return true;
}
+bool EFX::addFixture(quint32 fxi, int head)
+{
+ EFXFixture *ef = new EFXFixture(this);
+ GroupHead gHead(fxi, head);
+ ef->setHead(gHead);
+
+ return addFixture(ef);
+}
+
bool EFX::removeFixture(EFXFixture* ef)
{
Q_ASSERT(ef != NULL);
@@ -1031,6 +1081,7 @@ QSharedPointer EFX::getFader(QList universes, quint32
fader->setBlendMode(blendMode());
fader->setName(name());
fader->setParentFunctionID(id());
+ fader->setHandleSecondary(true);
m_fadersMap[universeID] = fader;
}
@@ -1044,16 +1095,11 @@ void EFX::preRun(MasterTimer* timer)
QListIterator it(m_fixtures);
while (it.hasNext() == true)
{
- EFXFixture* ef = it.next();
+ EFXFixture *ef = it.next();
Q_ASSERT(ef != NULL);
ef->setSerialNumber(serialNumber++);
}
- //Q_ASSERT(m_fader == NULL);
- //m_fader = new GenericFader(doc());
- //m_fader->adjustIntensity(getAttributeValue(Intensity));
- //m_fader->setBlendMode(blendMode());
-
Function::preRun(timer);
}
@@ -1061,32 +1107,31 @@ void EFX::write(MasterTimer *timer, QList universes)
{
Q_UNUSED(timer);
- int ready = 0;
-
if (isPaused())
return;
+ int done = 0;
+
QListIterator it(m_fixtures);
while (it.hasNext() == true)
{
EFXFixture *ef = it.next();
- if (ef->isReady() == false)
+ if (ef->isDone() == false)
{
QSharedPointer fader = getFader(universes, ef->universe());
ef->nextStep(universes, fader);
}
else
{
- ready++;
+ done++;
}
}
incrementElapsed();
/* Check for stop condition */
- if (ready == m_fixtures.count())
+ if (done == m_fixtures.count())
stop(FunctionParent::master());
- //m_fader->write(universes);
}
void EFX::postRun(MasterTimer *timer, QList universes)
diff --git a/engine/src/efx.h b/engine/src/efx.h
index 6d92327a0d..1c7d3e6767 100644
--- a/engine/src/efx.h
+++ b/engine/src/efx.h
@@ -64,6 +64,7 @@ class Fixture;
#define KXMLQLCEFXDiamondAlgorithmName QString("Diamond")
#define KXMLQLCEFXSquareAlgorithmName QString("Square")
#define KXMLQLCEFXSquareChoppyAlgorithmName QString("SquareChoppy")
+#define KXMLQLCEFXSquareTrueAlgorithmName QString("SquareTrue")
#define KXMLQLCEFXLeafAlgorithmName QString("Leaf")
#define KXMLQLCEFXLissajousAlgorithmName QString("Lissajous")
@@ -116,6 +117,8 @@ class EFX : public Function
/** Set the duration in milliseconds */
virtual void setDuration(uint ms);
+ uint loopDuration() const;
+
signals:
void durationChanged(uint ms);
@@ -132,6 +135,7 @@ class EFX : public Function
Diamond,
Square,
SquareChoppy,
+ SquareTrue,
Leaf,
Lissajous
};
@@ -179,7 +183,7 @@ class EFX : public Function
* @param x Used to store the calculated X coordinate (output)
* @param y Used to store the calculated Y coordinate (output)
*/
- void calculatePoint(Function::Direction direction, int startOffset, float iterator, float* x, float* y) const;
+ void calculatePoint(Function::Direction direction, int startOffset, float iterator, float *x, float *y) const;
private:
@@ -477,6 +481,9 @@ class EFX : public Function
/** Add a new fixture to this EFX */
bool addFixture(EFXFixture *ef);
+ /** Add the provided fixture id and head to this EFX */
+ bool addFixture(quint32 fxi, int head = 0);
+
/** Remove the designated fixture from this EFX but don't delete it */
bool removeFixture(EFXFixture *ef);
@@ -514,9 +521,9 @@ public slots:
public:
enum PropagationMode
{
- Parallel, /**< All fixtures move in unison (el-cheapo) */
- Serial, /**< Pattern propagates to the next fixture after a delay */
- Asymmetric /**< All fixtures move with an offset */
+ Parallel, /**< All fixtures move in unison (el-cheapo) */
+ Serial, /**< Pattern propagates to the next fixture after a delay */
+ Asymmetric /**< All fixtures move with an offset */
};
/** Set the EFX's fixture propagation mode (see the enum above) */
diff --git a/engine/src/efxfixture.cpp b/engine/src/efxfixture.cpp
index 9c652b11f8..45420be61e 100644
--- a/engine/src/efxfixture.cpp
+++ b/engine/src/efxfixture.cpp
@@ -24,16 +24,15 @@
#include
#include "genericfader.h"
+#include "fadechannel.h"
#include "mastertimer.h"
#include "efxfixture.h"
#include "qlcmacros.h"
#include "function.h"
#include "universe.h"
-#include "scene.h"
+#include "gradient.h"
#include "efx.h"
#include "doc.h"
-#include "gradient.h"
-
/*****************************************************************************
* Initialization
@@ -50,14 +49,19 @@ EFXFixture::EFXFixture(const EFX* parent)
, m_serialNumber(0)
, m_runTimeDirection(Function::Forward)
- , m_ready(false)
+ , m_done(false)
, m_started(false)
, m_elapsed(0)
, m_currentAngle(0)
+
+ , m_firstMsbChannel(QLCChannel::invalid())
+ , m_firstLsbChannel(QLCChannel::invalid())
+ , m_secondMsbChannel(QLCChannel::invalid())
+ , m_secondLsbChannel(QLCChannel::invalid())
{
Q_ASSERT(parent != NULL);
- if(m_rgbGradient.isNull ())
+ if (m_rgbGradient.isNull ())
m_rgbGradient = Gradient::getRGBGradient (256, 256);
}
@@ -73,7 +77,7 @@ void EFXFixture::copyFrom(const EFXFixture* ef)
m_serialNumber = ef->m_serialNumber;
m_runTimeDirection = ef->m_runTimeDirection;
- m_ready = ef->m_ready;
+ m_done = ef->m_done;
m_started = ef->m_started;
m_elapsed = ef->m_elapsed;
m_currentAngle = ef->m_currentAngle;
@@ -187,14 +191,14 @@ void EFXFixture::durationChanged()
// new duration.
m_elapsed = SCALE(float(m_currentAngle),
float(0), float(M_PI * 2),
- float(0), float(m_parent->duration()));
+ float(0), float(m_parent->loopDuration()));
// Serial or Asymmetric propagation mode:
// we must substract the offset from the current position
if (timeOffset())
{
if (m_elapsed < timeOffset())
- m_elapsed += m_parent->duration();
+ m_elapsed += m_parent->loopDuration();
m_elapsed -= timeOffset();
}
}
@@ -206,15 +210,15 @@ QStringList EFXFixture::modeList()
QStringList modes;
- if(fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head) != QLCChannel::invalid() ||
+ if (fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head) != QLCChannel::invalid() ||
fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head().head) != QLCChannel::invalid())
modes << KXMLQLCEFXFixtureModePanTilt;
- if(fxi->masterIntensityChannel() != QLCChannel::invalid() ||
+ if (fxi->masterIntensityChannel() != QLCChannel::invalid() ||
fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head().head) != QLCChannel::invalid())
modes << KXMLQLCEFXFixtureModeDimmer;
- if(fxi->rgbChannels(head().head).size() >= 3)
+ if (fxi->rgbChannels(head().head).size() >= 3)
modes << KXMLQLCEFXFixtureModeRGB;
return modes;
@@ -354,16 +358,16 @@ int EFXFixture::serialNumber() const
void EFXFixture::reset()
{
- m_ready = false;
+ m_done = false;
m_runTimeDirection = m_direction;
m_started = false;
m_elapsed = 0;
m_currentAngle = 0;
}
-bool EFXFixture::isReady() const
+bool EFXFixture::isDone() const
{
- return m_ready;
+ return m_done;
}
uint EFXFixture::timeOffset() const
@@ -371,7 +375,7 @@ uint EFXFixture::timeOffset() const
if (m_parent->propagationMode() == EFX::Asymmetric ||
m_parent->propagationMode() == EFX::Serial)
{
- return m_parent->duration() / (m_parent->fixtures().size() + 1) * serialNumber();
+ return m_parent->loopDuration() / (m_parent->fixtures().size() + 1) * serialNumber();
}
else
{
@@ -383,8 +387,52 @@ uint EFXFixture::timeOffset() const
* Running
*****************************************************************************/
-void EFXFixture::start()
+void EFXFixture::start(QSharedPointer fader)
{
+ Fixture *fxi = doc()->fixture(head().fxi);
+
+ /* Cache channels to reduce processing while running */
+ switch (m_mode)
+ {
+ case PanTilt:
+ {
+ m_firstMsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head);
+ m_firstLsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::LSB, head().head);
+ m_secondMsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head().head);
+ m_secondLsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::LSB, head().head);
+
+ /* Check for non-contiguous channels */
+ if ((m_firstLsbChannel != QLCChannel::invalid() && m_firstLsbChannel - m_firstMsbChannel != 1) ||
+ (m_secondLsbChannel != QLCChannel::invalid() && m_secondLsbChannel - m_secondMsbChannel != 1))
+ {
+ fader->setHandleSecondary(false);
+ }
+ }
+ break;
+
+ case RGB:
+ break;
+
+ case Dimmer:
+ {
+ m_firstMsbChannel = fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head().head);
+ if (m_firstMsbChannel != QLCChannel::invalid())
+ {
+ m_firstLsbChannel = fxi->channelNumber(QLCChannel::Intensity, QLCChannel::LSB, head().head);
+
+ /* Check for non-contiguous channels */
+ if (m_firstLsbChannel != QLCChannel::invalid() && m_firstLsbChannel - m_firstMsbChannel != 1)
+ {
+ fader->setHandleSecondary(false);
+ }
+ }
+ else
+ {
+ m_firstMsbChannel = fxi->masterIntensityChannel();
+ }
+ }
+ break;
+ }
m_started = true;
}
@@ -395,79 +443,76 @@ void EFXFixture::stop()
void EFXFixture::nextStep(QList universes, QSharedPointer fader)
{
- m_elapsed += MasterTimer::tick();
+ // Nothing to do
+ if (m_parent->loopDuration() == 0)
+ return;
// Bail out without doing anything if this fixture is ready (after single-shot)
// or it has no pan&tilt channels (not valid).
- if (m_ready == true || isValid() == false)
+ if (m_done == true || isValid() == false)
return;
+ m_elapsed += MasterTimer::tick();
+
+ // Check time wrapping
+ if (m_elapsed > m_parent->loopDuration())
+ {
+ if (m_parent->runOrder() == Function::PingPong)
+ {
+ /* Reverse direction for ping-pong EFX. */
+ if (m_runTimeDirection == Function::Forward)
+ m_runTimeDirection = Function::Backward;
+ else
+ m_runTimeDirection = Function::Forward;
+ }
+ else if (m_parent->runOrder() == Function::SingleShot)
+ {
+ /* De-initialize the fixture and mark as ready. */
+ m_done = true;
+ stop();
+ }
+
+ m_elapsed = 0;
+ }
+
// Bail out without doing anything if this fixture is waiting for its turn.
if (m_parent->propagationMode() == EFX::Serial && m_elapsed < timeOffset() && !m_started)
return;
// Fade in
if (m_started == false)
- start();
-
- // Nothing to do
- if (m_parent->duration() == 0)
- return;
+ start(fader);
// Scale from elapsed time in relation to overall duration to a point in a circle
- uint pos = (m_elapsed + timeOffset()) % m_parent->duration();
+ uint pos = (m_elapsed + timeOffset()) % m_parent->loopDuration();
m_currentAngle = SCALE(float(pos),
- float(0), float(m_parent->duration()),
+ float(0), float(m_parent->loopDuration()),
float(0), float(M_PI * 2));
float valX = 0;
float valY = 0;
- if ((m_parent->propagationMode() == EFX::Serial &&
- m_elapsed < (m_parent->duration() + timeOffset()))
- || m_elapsed < m_parent->duration())
- {
- m_parent->calculatePoint(m_runTimeDirection, m_startOffset, m_currentAngle, &valX, &valY);
+ m_parent->calculatePoint(m_runTimeDirection, m_startOffset, m_currentAngle, &valX, &valY);
- /* Prepare faders on universes */
- switch(m_mode)
- {
- case PanTilt:
- setPointPanTilt(universes, fader, valX, valY);
- break;
-
- case RGB:
- setPointRGB(universes, fader, valX, valY);
- break;
-
- case Dimmer:
- //Use Y for coherence with RGB gradient.
- setPointDimmer(universes, fader, valY);
- break;
- }
- }
- else
+ /* Set target values on faders/universes */
+ switch (m_mode)
{
- if (m_parent->runOrder() == Function::PingPong)
- {
- /* Reverse direction for ping-pong EFX. */
- if (m_runTimeDirection == Function::Forward)
- m_runTimeDirection = Function::Backward;
- else
- m_runTimeDirection = Function::Forward;
- }
- else if (m_parent->runOrder() == Function::SingleShot)
- {
- /* De-initialize the fixture and mark as ready. */
- m_ready = true;
- stop();
- }
+ case PanTilt:
+ setPointPanTilt(universes, fader, valX, valY);
+ break;
- m_elapsed %= m_parent->duration();
+ case RGB:
+ setPointRGB(universes, fader, valX, valY);
+ break;
+
+ case Dimmer:
+ //Use Y for coherence with RGB gradient.
+ setPointDimmer(universes, fader, valY);
+ break;
}
}
-void EFXFixture::updateFaderValues(FadeChannel *fc, uchar value)
+void EFXFixture::updateFaderValues(FadeChannel *fc, quint32 value)
{
fc->setStart(fc->current());
fc->setTarget(value);
@@ -479,80 +524,97 @@ void EFXFixture::updateFaderValues(FadeChannel *fc, uchar value)
void EFXFixture::setPointPanTilt(QList universes, QSharedPointer fader,
float pan, float tilt)
{
- Fixture* fxi = doc()->fixture(head().fxi);
- Q_ASSERT(fxi != NULL);
+ if (fader.isNull())
+ return;
+
Universe *uni = universes[universe()];
//qDebug() << "Pan value: " << pan << ", tilt value:" << tilt;
- /* Write coarse point data to universes */
- quint32 panMsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::MSB, head().head);
- quint32 panLsbChannel = fxi->channelNumber(QLCChannel::Pan, QLCChannel::LSB, head().head);
- quint32 tiltMsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::MSB, head().head);
- quint32 tiltLsbChannel = fxi->channelNumber(QLCChannel::Tilt, QLCChannel::LSB, head().head);
+ /* Check for outbound values */
+ if (pan < 0)
+ pan = 0;
+
+ if (tilt < 0)
+ tilt = 0;
- if (panMsbChannel != QLCChannel::invalid() && !fader.isNull())
+ /* Write full 16bit point data to universes */
+ if (m_firstMsbChannel != QLCChannel::invalid())
{
- FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), panMsbChannel);
+ quint32 panValue = quint32(pan);
+ FadeChannel *fc = fader->getChannelFader(doc(), uni, head().fxi, m_firstMsbChannel);
+ if (m_firstLsbChannel != QLCChannel::invalid())
+ {
+ if (fader->handleSecondary())
+ {
+ fc = fader->getChannelFader(doc(), uni, head().fxi, m_firstLsbChannel);
+ panValue = (panValue << 8) + quint32((pan - floor(pan)) * float(UCHAR_MAX));
+ }
+ else
+ {
+ FadeChannel *lsbFc = fader->getChannelFader(doc(), uni, head().fxi, m_firstLsbChannel);
+ updateFaderValues(lsbFc, quint32((pan - floor(pan)) * float(UCHAR_MAX)));
+ }
+ }
if (m_parent->isRelative())
fc->addFlag(FadeChannel::Relative);
- updateFaderValues(fc, static_cast(pan));
+
+ updateFaderValues(fc, panValue);
}
- if (tiltMsbChannel != QLCChannel::invalid() && !fader.isNull())
+ if (m_secondMsbChannel != QLCChannel::invalid())
{
- FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), tiltMsbChannel);
+ quint32 tiltValue = quint32(tilt);
+ FadeChannel *fc = fader->getChannelFader(doc(), uni, head().fxi, m_secondMsbChannel);
+ if (m_secondLsbChannel != QLCChannel::invalid())
+ {
+ if (fader->handleSecondary())
+ {
+ fc = fader->getChannelFader(doc(), uni, head().fxi, m_secondLsbChannel);
+ tiltValue = (tiltValue << 8) + quint32((tilt - floor(tilt)) * float(UCHAR_MAX));
+ }
+ else
+ {
+ FadeChannel *lsbFc = fader->getChannelFader(doc(), uni, head().fxi, m_secondLsbChannel);
+ updateFaderValues(lsbFc, quint32((tilt - floor(tilt)) * float(UCHAR_MAX)));
+ }
+ }
if (m_parent->isRelative())
fc->addFlag(FadeChannel::Relative);
- updateFaderValues(fc, static_cast(tilt));
- }
-
- /* Write fine point data to universes if applicable */
- if (panLsbChannel != QLCChannel::invalid() && !fader.isNull())
- {
- /* Leave only the fraction */
- float value = ((pan - floor(pan)) * float(UCHAR_MAX));
- FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), panLsbChannel);
- updateFaderValues(fc, static_cast(value));
- }
- if (tiltLsbChannel != QLCChannel::invalid() && !fader.isNull())
- {
- /* Leave only the fraction */
- float value = ((tilt - floor(tilt)) * float(UCHAR_MAX));
- FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), tiltLsbChannel);
- updateFaderValues(fc, static_cast(value));
+ updateFaderValues(fc, tiltValue);
}
}
void EFXFixture::setPointDimmer(QList universes, QSharedPointer fader, float dimmer)
{
- Fixture *fxi = doc()->fixture(head().fxi);
- Q_ASSERT(fxi != NULL);
- Universe *uni = universes[universe()];
+ if (fader.isNull())
+ return;
- quint32 intChannel = fxi->channelNumber(QLCChannel::Intensity, QLCChannel::MSB, head().head);
+ Universe *uni = universes[universe()];
/* Don't write dimmer data directly to universes but use FadeChannel to avoid steps at EFX loop restart */
- if (intChannel != QLCChannel::invalid())
+ if (m_firstMsbChannel != QLCChannel::invalid())
{
- if (!fader.isNull())
- {
- FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), intChannel);
- updateFaderValues(fc, dimmer);
- }
- }
- else if (fxi->masterIntensityChannel() != QLCChannel::invalid())
- {
- if (!fader.isNull())
+ quint32 dimmerValue = quint32(dimmer);
+ FadeChannel *fc = fader->getChannelFader(doc(), uni, head().fxi, m_firstMsbChannel);
+
+ if (m_firstLsbChannel != QLCChannel::invalid())
{
- FadeChannel *fc = fader->getChannelFader(doc(), uni, fxi->id(), fxi->masterIntensityChannel());
- updateFaderValues(fc, dimmer);
+ if (fader->handleSecondary())
+ {
+ fc = fader->getChannelFader(doc(), uni, head().fxi, m_firstLsbChannel);
+ dimmerValue = (dimmerValue << 8) + quint32((dimmer - floor(dimmer)) * float(UCHAR_MAX));
+ }
}
+ updateFaderValues(fc, dimmerValue);
}
}
void EFXFixture::setPointRGB(QList universes, QSharedPointer fader, float x, float y)
{
+ if (fader.isNull())
+ return;
+
Fixture* fxi = doc()->fixture(head().fxi);
Q_ASSERT(fxi != NULL);
Universe *uni = universes[universe()];
diff --git a/engine/src/efxfixture.h b/engine/src/efxfixture.h
index 25a602b83c..d98c8fa36a 100644
--- a/engine/src/efxfixture.h
+++ b/engine/src/efxfixture.h
@@ -157,9 +157,9 @@ class EFXFixture
/** Reset the fixture when the EFX is stopped */
void reset();
- /** Check, whether this EFXFixture is ready (no more events).
+ /** Check, whether this EFXFixture is done (no more events).
This can happen basically only if SingleShot mode is enabled. */
- bool isReady() const;
+ bool isDone() const;
/** Get this fixture's time offset (in serial and asymmetric modes) */
uint timeOffset() const;
@@ -171,9 +171,9 @@ class EFXFixture
/** This fixture's current run-time direction */
Function::Direction m_runTimeDirection;
- /** When running in single shot mode, the fixture is marked ready
+ /** When running in single shot mode, the fixture is marked done
after it has completed a full cycle. */
- bool m_ready;
+ bool m_done;
/** Indicates, whether start() has been called for this fixture */
bool m_started;
@@ -188,19 +188,26 @@ class EFXFixture
* Running
*************************************************************************/
private:
- void start();
+ void start(QSharedPointer fader);
void stop();
/** Calculate the next step data for this fixture */
void nextStep(QList universes, QSharedPointer fader);
- void updateFaderValues(FadeChannel *fc, uchar value);
+ /** Set a 16bit value on a fader gotten from the engine */
+ void updateFaderValues(FadeChannel *fc, quint32 value);
/** Write this EFXFixture's channel data to universe faders */
void setPointPanTilt(QList universes, QSharedPointer fader, float pan, float tilt);
void setPointDimmer(QList universes, QSharedPointer fader, float dimmer);
void setPointRGB (QList universes, QSharedPointer fader, float x, float y);
+private:
+ quint32 m_firstMsbChannel;
+ quint32 m_firstLsbChannel;
+ quint32 m_secondMsbChannel;
+ quint32 m_secondLsbChannel;
+
private:
static QImage m_rgbGradient;
};
diff --git a/engine/src/fadechannel.cpp b/engine/src/fadechannel.cpp
index 537b0fd9fe..2e5a8755ee 100644
--- a/engine/src/fadechannel.cpp
+++ b/engine/src/fadechannel.cpp
@@ -20,9 +20,9 @@
#include
#include
+#include "qlcfixturemode.h"
#include "fadechannel.h"
#include "qlcchannel.h"
-#include "qlcmacros.h"
#include "universe.h"
#include "fixture.h"
@@ -30,8 +30,9 @@ FadeChannel::FadeChannel()
: m_flags(0)
, m_fixture(Fixture::invalidId())
, m_universe(Universe::invalid())
- , m_channel(QLCChannel::invalid())
+ , m_primaryChannel(QLCChannel::invalid())
, m_address(QLCChannel::invalid())
+ , m_channelRef(NULL)
, m_start(0)
, m_target(0)
, m_current(0)
@@ -45,8 +46,10 @@ FadeChannel::FadeChannel(const FadeChannel& ch)
: m_flags(ch.m_flags)
, m_fixture(ch.m_fixture)
, m_universe(ch.m_universe)
- , m_channel(ch.m_channel)
+ , m_primaryChannel(ch.m_primaryChannel)
+ , m_channels(ch.m_channels)
, m_address(ch.m_address)
+ , m_channelRef(ch.m_channelRef)
, m_start(ch.m_start)
, m_target(ch.m_target)
, m_current(ch.m_current)
@@ -60,7 +63,7 @@ FadeChannel::FadeChannel(const FadeChannel& ch)
FadeChannel::FadeChannel(const Doc *doc, quint32 fxi, quint32 channel)
: m_flags(0)
, m_fixture(fxi)
- , m_channel(channel)
+ , m_channelRef(NULL)
, m_start(0)
, m_target(0)
, m_current(0)
@@ -68,6 +71,7 @@ FadeChannel::FadeChannel(const Doc *doc, quint32 fxi, quint32 channel)
, m_fadeTime(0)
, m_elapsed(0)
{
+ m_channels.append(channel);
autoDetect(doc);
}
@@ -82,7 +86,9 @@ FadeChannel &FadeChannel::operator=(const FadeChannel &fc)
m_flags = fc.m_flags;
m_fixture = fc.m_fixture;
m_universe = fc.m_universe;
- m_channel = fc.m_channel;
+ m_primaryChannel = fc.m_primaryChannel;
+ m_channels = fc.m_channels;
+ m_channelRef = fc.m_channelRef;
m_address = fc.m_address;
m_start = fc.m_start;
m_target = fc.m_target;
@@ -97,7 +103,7 @@ FadeChannel &FadeChannel::operator=(const FadeChannel &fc)
bool FadeChannel::operator==(const FadeChannel& ch) const
{
- return (m_fixture == ch.m_fixture && m_channel == ch.m_channel);
+ return (m_fixture == ch.m_fixture && channel() == ch.channel());
}
int FadeChannel::flags() const
@@ -144,54 +150,48 @@ void FadeChannel::autoDetect(const Doc *doc)
}
else
{
+ QLCFixtureMode *mode = fixture->fixtureMode();
m_universe = fixture->universe();
m_address = fixture->address();
// if the fixture was invalid at the beginning of this method
// it means channel was an absolute address, so, fix it
if (fixtureWasInvalid)
- m_channel -= fixture->address();
+ m_channels[0] -= fixture->address();
- const QLCChannel *channel = fixture->channel(m_channel);
+ quint32 chIndex = channel();
+ m_primaryChannel = mode ? mode->primaryChannel(chIndex) : QLCChannel::invalid();
+ m_channelRef = fixture->channel(chIndex);
// non existing channel within fixture
- if (channel == NULL)
+ if (m_channelRef == NULL)
{
addFlag(FadeChannel::HTP | FadeChannel::Intensity | FadeChannel::CanFade);
return;
}
// autodetect the channel type
- if (fixture->channelCanFade(m_channel))
+ if (fixture->channelCanFade(chIndex))
addFlag(FadeChannel::CanFade);
- if (channel != NULL && channel->group() == QLCChannel::Intensity)
+ if (m_channelRef != NULL && m_channelRef->group() == QLCChannel::Intensity)
addFlag(FadeChannel::HTP | FadeChannel::Intensity);
else
addFlag(FadeChannel::LTP);
- if (fixture->forcedHTPChannels().contains(int(m_channel)))
+ if (fixture->forcedHTPChannels().contains(int(chIndex)))
{
removeFlag(FadeChannel::LTP);
addFlag(FadeChannel::HTP);
}
- else if (fixture->forcedLTPChannels().contains(int(m_channel)))
+ else if (fixture->forcedLTPChannels().contains(int(chIndex)))
{
removeFlag(FadeChannel::HTP);
addFlag(FadeChannel::LTP);
}
-
- if (channel != NULL && channel->controlByte() == QLCChannel::LSB)
- addFlag(FadeChannel::Fine);
}
}
-void FadeChannel::setFixture(const Doc *doc, quint32 id)
-{
- m_fixture = id;
- autoDetect(doc);
-}
-
quint32 FadeChannel::fixture() const
{
return m_fixture;
@@ -204,15 +204,42 @@ quint32 FadeChannel::universe() const
return m_universe;
}
-void FadeChannel::setChannel(const Doc *doc, quint32 num)
+void FadeChannel::addChannel(quint32 num)
{
- m_channel = num;
- autoDetect(doc);
+ m_channels.append(num);
+ qDebug() << "[FadeChannel] ADD channel" << num << "count:" << m_channels.count();
+
+ // on secondary channel, shift values 8bits up
+ if (m_channels.count() > 1)
+ {
+ m_start = m_start << 8;
+ m_target = m_target << 8;
+ m_current = m_current << 8;
+ }
+}
+
+int FadeChannel::channelCount() const
+{
+ if (m_channels.isEmpty())
+ return 1;
+
+ return m_channels.count();
}
quint32 FadeChannel::channel() const
{
- return m_channel;
+ return m_channels.isEmpty() ? QLCChannel::invalid() : m_channels.first();
+}
+
+int FadeChannel::channelIndex(quint32 channel)
+{
+ int idx = m_channels.indexOf(channel);
+ return idx < 0 ? 0 : idx;
+}
+
+quint32 FadeChannel::primaryChannel() const
+{
+ return m_primaryChannel;
}
quint32 FadeChannel::address() const
@@ -225,42 +252,85 @@ quint32 FadeChannel::address() const
quint32 FadeChannel::addressInUniverse() const
{
- return address() % UNIVERSE_SIZE;
+ quint32 addr = address();
+ if (addr == QLCChannel::invalid())
+ return QLCChannel::invalid();
+
+ return addr % UNIVERSE_SIZE;
+}
+
+/************************************************************************
+ * Values
+ ************************************************************************/
+
+void FadeChannel::setStart(uchar value, int index)
+{
+ ((uchar *)&m_start)[channelCount() - 1 - index] = value;
}
-void FadeChannel::setStart(uchar value)
+void FadeChannel::setStart(quint32 value)
{
m_start = value;
}
-uchar FadeChannel::start() const
+uchar FadeChannel::start(int index) const
{
- return uchar(m_start);
+ return ((uchar *)&m_start)[channelCount() - 1 - index];
}
-void FadeChannel::setTarget(uchar value)
+quint32 FadeChannel::start() const
+{
+ return m_start;
+}
+
+void FadeChannel::setTarget(uchar value, int index)
+{
+ ((uchar *)&m_target)[channelCount() - 1 - index] = value;
+}
+
+void FadeChannel::setTarget(quint32 value)
{
m_target = value;
}
-uchar FadeChannel::target() const
+uchar FadeChannel::target(int index) const
+{
+ return ((uchar *)&m_target)[channelCount() - 1 - index];
+}
+
+quint32 FadeChannel::target() const
+{
+ return m_target;
+}
+
+void FadeChannel::setCurrent(uchar value, int index)
{
- return uchar(m_target);
+ ((uchar *)&m_current)[channelCount() - 1 - index] = value;
}
-void FadeChannel::setCurrent(uchar value)
+void FadeChannel::setCurrent(quint32 value)
{
m_current = value;
}
-uchar FadeChannel::current() const
+uchar FadeChannel::current(int index) const
{
- return uchar(m_current);
+ return ((uchar *)&m_current)[channelCount() - 1 - index];
}
-uchar FadeChannel::current(qreal intensity) const
+quint32 FadeChannel::current() const
{
- return uchar(floor((qreal(m_current) * intensity) + 0.5));
+ return m_current;
+}
+
+uchar FadeChannel::current(qreal intensity, int index) const
+{
+ return uchar(floor((qreal(current(index)) * intensity) + 0.5));
+}
+
+quint32 FadeChannel::current(qreal intensity) const
+{
+ return quint32(floor((qreal(m_current) * intensity) + 0.5));
}
void FadeChannel::setReady(bool rdy)
@@ -302,6 +372,7 @@ uchar FadeChannel::nextStep(uint ms)
{
if (elapsed() < UINT_MAX)
setElapsed(elapsed() + ms);
+
return calculateCurrent(fadeTime(), elapsed());
}
@@ -320,17 +391,11 @@ uchar FadeChannel::calculateCurrent(uint fadeTime, uint elapsedTime)
}
else
{
- // 16 bit fading works as long as MSB and LSB channels
- // are targeting the same value. E.g. Red and Red Fine both at 158
- float val = (float(m_target - m_start) * (float(elapsedTime) / float(fadeTime))) + float(m_start);
- if (m_flags & Fine)
- {
- m_current = ((val - floor(val)) * float(UCHAR_MAX));
- }
- else
- {
- m_current = val;
- }
+ bool rampUp = m_target > m_start ? true : false;
+ m_current = rampUp ? m_target - m_start : m_start - m_target;
+ m_current = m_current * (qreal(elapsedTime) / qreal(fadeTime));
+ m_current = rampUp ? m_start + m_current : m_start - m_current;
+ //qDebug() << "channel" << channel() << "start" << m_start << "target" << m_target << "current" << m_current << "fade" << fadeTime << "elapsed" << elapsedTime ;
}
return uchar(m_current);
diff --git a/engine/src/fadechannel.h b/engine/src/fadechannel.h
index 5d1450bcd8..01d129077f 100644
--- a/engine/src/fadechannel.h
+++ b/engine/src/fadechannel.h
@@ -22,8 +22,6 @@
#include
-#include "qlcchannel.h"
-#include "fixture.h"
#include "doc.h"
/** @addtogroup engine Engine
@@ -52,8 +50,10 @@ class FadeChannel
Flashing = (1 << 5), /** Is flashing */
Relative = (1 << 6), /** Relative position */
Override = (1 << 7), /** Override the current universe value */
- Autoremove = (1 << 8), /** Automatically remove the channel once value is written */
- CrossFade = (1 << 9) /** Channel subject to crossfade */
+ SetTarget = (1 << 8), /** Set target to current universe value */
+ AutoRemove = (1 << 9), /** Automatically remove the channel once target is reached */
+ CrossFade = (1 << 10), /** Channel subject to crossfade */
+ ForceLTP = (1 << 11) /** Force LTP for flashing scenes */
};
/** Create a new FadeChannel with empty/invalid values */
@@ -81,59 +81,84 @@ class FadeChannel
void addFlag(int flag);
void removeFlag(int flag);
-protected:
- void autoDetect(const Doc *doc);
-
-private:
- /** Bitmask including the channel type
- * and, if needed, more flags */
- int m_flags;
-
- /************************************************************************
- * Values
- ************************************************************************/
-public:
- /** Set the Fixture that is being controlled. */
- void setFixture(const Doc *doc, quint32 id);
-
/** Get the Fixture that is being controlled. */
quint32 fixture() const;
/** Get the universe of the Fixture that is being controlled. */
quint32 universe() const;
- /** Set channel within the Fixture. */
- void setChannel(const Doc* doc, quint32 num);
+ /** Add another channel to be handled by this fader */
+ void addChannel(quint32 num);
- /** Get channel within the Fixture. */
+ /** Get the number of channels handled by this fader */
+ int channelCount() const;
+
+ /** Get the first (or master) channel handled by this fader */
quint32 channel() const;
+ /** Get the index of the provided $channel. This is useful only
+ * when multiple channels are handled and caller doesn't know
+ * if it is targeting primary or secondary */
+ int channelIndex(quint32 channel);
+
+ /** Get (if present) the index of the primary channel this fader relate to */
+ quint32 primaryChannel() const;
+
/** Get the absolute address for this channel. */
quint32 address() const;
/** Get the absolute address in its universe for this channel. */
quint32 addressInUniverse() const;
+protected:
+ void autoDetect(const Doc *doc);
+
+private:
+ /** Bitmask representing all the channel specificities
+ * such as fading, overriding, flashing, etc. */
+ int m_flags;
+
+ quint32 m_fixture;
+ quint32 m_universe;
+ quint32 m_primaryChannel;
+ QVector m_channels;
+ quint32 m_address;
+
+ /** Cache channel reference for faster lookup */
+ const QLCChannel *m_channelRef;
+
+ /************************************************************************
+ * Values
+ ************************************************************************/
+public:
+
/** Set starting value. */
- void setStart(uchar value);
+ void setStart(uchar value, int index);
+ void setStart(quint32 value);
/** Get starting value. */
- uchar start() const;
+ uchar start(int index) const;
+ quint32 start() const;
/** Set target value. */
- void setTarget(uchar value);
+ void setTarget(uchar value, int index);
+ void setTarget(quint32 value);
/** Get target value. */
- uchar target() const;
+ uchar target(int index) const;
+ quint32 target() const;
/** Set the current value. */
- void setCurrent(uchar value);
+ void setCurrent(uchar value, int index);
+ void setCurrent(quint32 value);
/** Get the current value. */
- uchar current() const;
+ uchar current(int index) const;
+ quint32 current() const;
/** Get the current value, modified by $intensity. */
- uchar current(qreal intensity) const;
+ uchar current(qreal intensity, int index) const;
+ quint32 current(qreal intensity) const;
/** Mark this channel as ready (useful for writing LTP values only once). */
void setReady(bool rdy);
@@ -177,14 +202,9 @@ class FadeChannel
uchar calculateCurrent(uint fadeTime, uint elapsedTime);
private:
- quint32 m_fixture;
- quint32 m_universe;
- quint32 m_channel;
- quint32 m_address;
-
- int m_start;
- int m_target;
- int m_current;
+ quint32 m_start;
+ quint32 m_target;
+ quint32 m_current;
bool m_ready;
uint m_fadeTime;
diff --git a/engine/src/fixture.cpp b/engine/src/fixture.cpp
index ec3cdeb17c..792ace2f47 100644
--- a/engine/src/fixture.cpp
+++ b/engine/src/fixture.cpp
@@ -310,7 +310,7 @@ QVector Fixture::cmyChannels(int head) const
return m_fixtureMode->heads().at(head).cmyChannels();
}
-QList Fixture::positionToValues(int type, int degrees) const
+QList Fixture::positionToValues(int type, int degrees, bool isRelative)
{
QList posList;
// cache a list of channels processed, to avoid duplicates
@@ -320,7 +320,9 @@ QList Fixture::positionToValues(int type, int degrees) const
return posList;
QLCPhysical phy = fixtureMode()->physical();
- float maxDegrees;
+ qreal headDegrees = degrees, maxDegrees;
+ float msbValue = 0, lsbValue = 0;
+
if (type == QLCChannel::Pan)
{
maxDegrees = phy.focusPanMax();
@@ -331,22 +333,29 @@ QList Fixture::positionToValues(int type, int degrees) const
quint32 panMSB = channelNumber(QLCChannel::Pan, QLCChannel::MSB, i);
if (panMSB == QLCChannel::invalid() || chDone.contains(panMSB))
continue;
+ quint32 panLSB = channelNumber(QLCChannel::Pan, QLCChannel::LSB, i);
- float dmxValue = (float)(degrees * UCHAR_MAX) / maxDegrees;
- posList.append(SceneValue(id(), panMSB, static_cast(qFloor(dmxValue))));
+ if (isRelative)
+ {
+ // degrees is a relative value upon the current value.
+ // Recalculate absolute degrees here
+ float chDegrees = (qreal(phy.focusPanMax()) / 256.0) * channelValueAt(panMSB);
+ headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
- qDebug() << "[getFixturePosition] Pan MSB:" << dmxValue;
+ if (panLSB != QLCChannel::invalid())
+ {
+ chDegrees = (qreal(phy.focusPanMax()) / 65536.0) * channelValueAt(panLSB);
+ headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
+ }
+ }
- quint32 panLSB = channelNumber(QLCChannel::Pan, QLCChannel::LSB, i);
+ quint16 degToDmx = (headDegrees * 65535.0) / qreal(phy.focusPanMax());
+ posList.append(SceneValue(id(), panMSB, static_cast(degToDmx >> 8)));
if (panLSB != QLCChannel::invalid())
- {
- float lsbDegrees = (float)maxDegrees / (float)UCHAR_MAX;
- float lsbValue = (float)((dmxValue - qFloor(dmxValue)) * UCHAR_MAX) / lsbDegrees;
- posList.append(SceneValue(id(), panLSB, static_cast(lsbValue)));
+ posList.append(SceneValue(id(), panLSB, static_cast(degToDmx & 0x00FF)));
- qDebug() << "[getFixturePosition] Pan LSB:" << lsbValue;
- }
+ qDebug() << "[positionToValues] Pan MSB:" << msbValue << "LSB:" << lsbValue;
chDone.append(panMSB);
}
@@ -361,22 +370,29 @@ QList Fixture::positionToValues(int type, int degrees) const
quint32 tiltMSB = channelNumber(QLCChannel::Tilt, QLCChannel::MSB, i);
if (tiltMSB == QLCChannel::invalid() || chDone.contains(tiltMSB))
continue;
+ quint32 tiltLSB = channelNumber(QLCChannel::Tilt, QLCChannel::LSB, i);
- float dmxValue = (float)(degrees * UCHAR_MAX) / maxDegrees;
- posList.append(SceneValue(id(), tiltMSB, static_cast(qFloor(dmxValue))));
+ if (isRelative)
+ {
+ // degrees is a relative value upon the current value.
+ // Recalculate absolute degrees here
+ float chDegrees = (qreal(phy.focusTiltMax()) / 256.0) * channelValueAt(tiltMSB);
+ headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
- qDebug() << "[getFixturePosition] Tilt MSB:" << dmxValue;
+ if (tiltLSB != QLCChannel::invalid())
+ {
+ chDegrees = (qreal(phy.focusPanMax()) / 65536.0) * channelValueAt(tiltLSB);
+ headDegrees = qBound(0.0, chDegrees + headDegrees, maxDegrees);
+ }
+ }
- quint32 tiltLSB = channelNumber(QLCChannel::Tilt, QLCChannel::LSB, i);
+ quint16 degToDmx = (headDegrees * 65535.0) / qreal(phy.focusTiltMax());
+ posList.append(SceneValue(id(), tiltMSB, static_cast(degToDmx >> 8)));
if (tiltLSB != QLCChannel::invalid())
- {
- float lsbDegrees = (float)maxDegrees / (float)UCHAR_MAX;
- float lsbValue = (float)((dmxValue - qFloor(dmxValue)) * UCHAR_MAX) / lsbDegrees;
- posList.append(SceneValue(id(), tiltLSB, static_cast(lsbValue)));
+ posList.append(SceneValue(id(), tiltLSB, static_cast(degToDmx & 0x00FF)));
- qDebug() << "[getFixturePosition] Tilt LSB:" << lsbValue;
- }
+ qDebug() << "[positionToValues] Tilt MSB:" << msbValue << "LSB:" << lsbValue;
chDone.append(tiltMSB);
}
@@ -386,7 +402,7 @@ QList Fixture::positionToValues(int type, int degrees) const
return posList;
}
-QList Fixture::zoomToValues(float degrees) const
+QList Fixture::zoomToValues(float degrees, bool isRelative)
{
QList chList;
@@ -394,8 +410,13 @@ QList Fixture::zoomToValues(float degrees) const
return chList;
QLCPhysical phy = fixtureMode()->physical();
- float msbValue = ((degrees - phy.lensDegreesMin()) * (float)UCHAR_MAX) /
- (phy.lensDegreesMax() - phy.lensDegreesMin());
+ if (!isRelative)
+ degrees = qBound(float(phy.lensDegreesMin()), degrees, float(phy.lensDegreesMax()));
+
+ float deltaDegrees = phy.lensDegreesMax() - phy.lensDegreesMin();
+ // delta : 0xFFFF = deg : x
+ quint16 degToDmx = ((degrees - (isRelative ? 0 : float(phy.lensDegreesMin()))) * 65535.0) / deltaDegrees;
+ //qDebug() << "Degrees" << degrees << "DMX" << QString::number(degToDmx, 16);
for (quint32 i = 0; i < quint32(m_fixtureMode->channels().size()); i++)
{
@@ -409,19 +430,29 @@ QList Fixture::zoomToValues(float degrees) const
ch->preset() != QLCChannel::BeamZoomFine)
continue;
+ if (isRelative)
+ {
+ // degrees is a relative value upon the current value.
+ // Recalculate absolute degrees here
+ qreal divider = ch->controlByte() == QLCChannel::MSB ? 256.0 : 65536.0;
+ float chDegrees = float((phy.lensDegreesMax() - phy.lensDegreesMin()) / divider) * float(channelValueAt(i));
+
+ //qDebug() << "Relative channel degrees:" << chDegrees << "MSB?" << ch->controlByte();
+
+ quint16 currDmxVal = (chDegrees * 65535.0) / deltaDegrees;
+ degToDmx += currDmxVal;
+ }
+
if (ch->controlByte() == QLCChannel::MSB)
{
if (ch->preset() == QLCChannel::BeamZoomBigSmall)
- chList.append(SceneValue(id(), i, static_cast(qFloor((float)UCHAR_MAX - msbValue))));
+ chList.append(SceneValue(id(), i, static_cast(UCHAR_MAX - (degToDmx >> 8))));
else
- chList.append(SceneValue(id(), i, static_cast(qFloor(msbValue))));
+ chList.append(SceneValue(id(), i, static_cast(degToDmx >> 8)));
}
-
- if (ch->controlByte() == QLCChannel::LSB)
+ else if (ch->controlByte() == QLCChannel::LSB)
{
- float lsbDegrees = (float)(phy.lensDegreesMax() - phy.lensDegreesMin()) / (float)UCHAR_MAX;
- float lsbValue = (float)((msbValue - qFloor(msbValue)) * (float)UCHAR_MAX) / lsbDegrees;
- chList.append(SceneValue(id(), i, static_cast(lsbValue)));
+ chList.append(SceneValue(id(), i, static_cast(degToDmx & 0x00FF)));
}
}
@@ -572,7 +603,7 @@ void Fixture::checkAlias(int chIndex, uchar value)
// If the channel @chIndex has aliases, check
// if replacements are to be done
QLCCapability *cap = m_fixtureMode->channel(chIndex)->searchCapability(value);
- if (cap == m_aliasInfo[chIndex].m_currCap)
+ if (cap == NULL || cap == m_aliasInfo[chIndex].m_currCap)
return;
// first, revert any channel replaced to the original channel set
diff --git a/engine/src/fixture.h b/engine/src/fixture.h
index cbea1e810c..8dc2a49a42 100644
--- a/engine/src/fixture.h
+++ b/engine/src/fixture.h
@@ -277,10 +277,10 @@ class Fixture : public QObject
/** Return a list of DMX values based on the given position degrees
* and the provided type (Pan or Tilt) */
- QList positionToValues(int type, int degrees) const;
+ QList positionToValues(int type, int degrees, bool isRelative = false);
/** Return a list of DMX values based on the given zoom degrees */
- QList zoomToValues(float degrees) const;
+ QList zoomToValues(float degrees, bool isRelative);
/** Set a list of channel indices to exclude from fade transitions */
void setExcludeFadeChannels(QList indices);
diff --git a/engine/src/fixturegroup.cpp b/engine/src/fixturegroup.cpp
index 55d478e7f8..94b20fde91 100644
--- a/engine/src/fixturegroup.cpp
+++ b/engine/src/fixturegroup.cpp
@@ -379,7 +379,7 @@ bool FixtureGroup::saveXML(QXmlStreamWriter *doc)
/* Fixture heads */
QList pointsList = m_heads.keys();
- foreach(QLCPoint pt, pointsList)
+ foreach (QLCPoint pt, pointsList)
{
GroupHead head = m_heads[pt];
doc->writeStartElement(KXMLQLCFixtureGroupHead);
diff --git a/engine/src/function.cpp b/engine/src/function.cpp
index 518ff70774..f201115e58 100644
--- a/engine/src/function.cpp
+++ b/engine/src/function.cpp
@@ -26,7 +26,6 @@
#include
#include "qlcmacros.h"
-#include "qlcfile.h"
#include "scriptwrapper.h"
#include "mastertimer.h"
@@ -521,6 +520,7 @@ void Function::setTempoType(const Function::TempoType &type)
}
emit changed(m_id);
+ emit tempoTypeChanged();
}
Function::TempoType Function::tempoType() const
@@ -959,12 +959,16 @@ QList Function::components()
* Flash
*****************************************************************************/
-void Function::flash(MasterTimer *timer)
+void Function::flash(MasterTimer *timer, bool shouldOverride, bool forceLTP)
{
Q_UNUSED(timer);
+ Q_UNUSED(shouldOverride);
+ Q_UNUSED(forceLTP);
if (m_flashing == false)
+ {
emit flashing(m_id, true);
+ }
m_flashing = true;
}
@@ -1267,7 +1271,7 @@ int Function::requestAttributeOverride(int attributeIndex, qreal value)
attributeID = m_lastOverrideAttributeId;
m_overrideMap[attributeID] = override;
- qDebug() << name() << "Override requested for attribute" << attributeIndex << "value" << value << "new ID" << attributeID;
+ qDebug() << name() << "Override requested for new attribute" << attributeIndex << "value" << value << "new ID" << attributeID;
calculateOverrideValue(attributeIndex);
@@ -1275,7 +1279,7 @@ int Function::requestAttributeOverride(int attributeIndex, qreal value)
}
else
{
- qDebug() << name() << "Override requested for attribute" << attributeIndex << "value" << value << "single ID" << attributeID;
+ qDebug() << name() << "Override requested for existing attribute" << attributeIndex << "value" << value << "single ID" << attributeID;
}
// actually apply the new override value
@@ -1334,13 +1338,13 @@ int Function::adjustAttribute(qreal value, int attributeId)
if (attributeId >= m_attributes.count() || m_attributes[attributeId].m_value == value)
return -1;
- // Adjust the original value of an attribute. Only Function editors should do this !
+ // Adjust the original value of an attribute. Only Function editors should do this!
m_attributes[attributeId].m_value = CLAMP(value, m_attributes[attributeId].m_min, m_attributes[attributeId].m_max);
attrIndex = attributeId;
}
else
{
- if (m_overrideMap.contains(attributeId) == false || m_overrideMap[attributeId].m_value == value)
+ if (m_overrideMap.contains(attributeId) == false)
return -1;
// Adjust an attribute override value and recalculate the final overridden value
@@ -1382,7 +1386,7 @@ int Function::getAttributeIndex(QString name) const
for (int i = 0; i < m_attributes.count(); i++)
{
Attribute attr = m_attributes.at(i);
- if(attr.m_name == name)
+ if (attr.m_name == name)
return i;
}
return -1;
diff --git a/engine/src/function.h b/engine/src/function.h
index 1c48e63388..2200ec9b23 100644
--- a/engine/src/function.h
+++ b/engine/src/function.h
@@ -99,6 +99,7 @@ class Function : public QObject
Q_PROPERTY(Type type READ type CONSTANT)
Q_PROPERTY(quint32 totalDuration READ totalDuration WRITE setTotalDuration NOTIFY totalDurationChanged)
Q_PROPERTY(RunOrder runOrder READ runOrder WRITE setRunOrder NOTIFY runOrderChanged)
+ Q_PROPERTY(TempoType tempoType READ tempoType WRITE setTempoType NOTIFY tempoTypeChanged FINAL)
public:
/**
@@ -404,8 +405,18 @@ class Function : public QObject
* Tempo type
*********************************************************************/
public:
- enum TempoType { Original = -1, Time = 0, Beats = 1 };
- enum FractionsType { NoFractions = 0, ByTwoFractions, AllFractions };
+ enum TempoType
+ {
+ Original = -1,
+ Time = 0,
+ Beats = 1
+ };
+ enum FractionsType
+ {
+ NoFractions = 0,
+ ByTwoFractions,
+ AllFractions
+ };
#if QT_VERSION >= 0x050500
Q_ENUM(TempoType)
Q_ENUM(FractionsType)
@@ -452,6 +463,9 @@ class Function : public QObject
/** Set the override speed type (done by a Chaser) */
void setOverrideTempoType(TempoType type);
+signals:
+ void tempoTypeChanged();
+
protected slots:
/**
* This slot is connected to the Master Timer and it is invoked
@@ -628,7 +642,7 @@ public slots:
*********************************************************************/
public:
/** Flash the function */
- virtual void flash(MasterTimer* timer);
+ virtual void flash(MasterTimer* timer, bool shouldOverride, bool forceLTP);
/** UnFlash the function */
virtual void unFlash(MasterTimer* timer);
diff --git a/engine/src/genericdmxsource.cpp b/engine/src/genericdmxsource.cpp
index fe667f8be0..0a61691dd0 100644
--- a/engine/src/genericdmxsource.cpp
+++ b/engine/src/genericdmxsource.cpp
@@ -21,7 +21,6 @@
#include "genericfader.h"
#include "mastertimer.h"
#include "fadechannel.h"
-#include "qlcchannel.h"
#include "universe.h"
#include "doc.h"
diff --git a/engine/src/genericfader.cpp b/engine/src/genericfader.cpp
index caf7e6e770..f1f5cece85 100644
--- a/engine/src/genericfader.cpp
+++ b/engine/src/genericfader.cpp
@@ -17,7 +17,6 @@
limitations under the License.
*/
-#include
#include
#include "genericfader.h"
@@ -28,6 +27,7 @@ GenericFader::GenericFader(QObject *parent)
: QObject(parent)
, m_fid(Function::invalidId())
, m_priority(Universe::Auto)
+ , m_handleSecondary(false)
, m_intensity(1.0)
, m_parentIntensity(1.0)
, m_paused(false)
@@ -73,6 +73,16 @@ void GenericFader::setPriority(int priority)
m_priority = priority;
}
+bool GenericFader::handleSecondary()
+{
+ return m_handleSecondary;
+}
+
+void GenericFader::setHandleSecondary(bool enable)
+{
+ m_handleSecondary = enable;
+}
+
quint32 GenericFader::channelHash(quint32 fixtureID, quint32 channel)
{
return ((fixtureID & 0x0000FFFF) << 16) | (channel & 0x0000FFFF);
@@ -130,15 +140,41 @@ void GenericFader::requestDelete()
FadeChannel *GenericFader::getChannelFader(const Doc *doc, Universe *universe, quint32 fixtureID, quint32 channel)
{
FadeChannel fc(doc, fixtureID, channel);
- quint32 hash = channelHash(fc.fixture(), fc.channel());
+ quint32 primary = fc.primaryChannel();
+ quint32 hash;
+
+ // calculate hash depending on primary channel presence
+ if (handleSecondary() && primary != QLCChannel::invalid())
+ hash = channelHash(fc.fixture(), primary);
+ else
+ hash = channelHash(fc.fixture(), fc.channel());
+
+ // search for existing FadeChannel
QHash::iterator channelIterator = m_channels.find(hash);
if (channelIterator != m_channels.end())
- return &channelIterator.value();
+ {
+ FadeChannel *fcFound = &channelIterator.value();
+
+ if (handleSecondary() &&
+ fcFound->channelCount() == 1 &&
+ primary != QLCChannel::invalid())
+ {
+ qDebug() << "Adding channel to primary" << channel;
+ fcFound->addChannel(channel);
+ if (universe)
+ fcFound->setCurrent(universe->preGMValue(fcFound->address() + 1), 1);
+ }
+ return fcFound;
+ }
- fc.setCurrent(universe->preGMValue(fc.address()));
+ // set current universe value
+ if (universe)
+ fc.setCurrent(universe->preGMValue(fc.address()));
+ // new channel. Add to GenericFader
m_channels[hash] = fc;
//qDebug() << "Added new fader with hash" << hash;
+
return &m_channels[hash];
}
@@ -159,19 +195,31 @@ void GenericFader::write(Universe *universe)
qreal compIntensity = intensity() * parentIntensity();
+ //qDebug() << "[GenericFader] writing channels: " << this << m_channels.count();
+
QMutableHashIterator it(m_channels);
while (it.hasNext() == true)
{
FadeChannel& fc(it.next().value());
int flags = fc.flags();
int address = int(fc.addressInUniverse());
- uchar value;
+ int channelCount = fc.channelCount();
+
+ // iterate through all the channels handled by this fader
+
+ if (flags & FadeChannel::SetTarget)
+ {
+ fc.removeFlag(FadeChannel::SetTarget);
+ fc.addFlag(FadeChannel::AutoRemove);
+ for (int i = 0; i < channelCount; i++)
+ fc.setTarget(universe->preGMValue(address + i), i);
+ }
// Calculate the next step
- if (m_paused)
- value = fc.current();
- else
- value = fc.nextStep(MasterTimer::tick());
+ if (m_paused == false)
+ fc.nextStep(MasterTimer::tick());
+
+ quint32 value = fc.current();
// Apply intensity to channels that can fade
if (fc.canFade())
@@ -179,7 +227,10 @@ void GenericFader::write(Universe *universe)
if ((flags & FadeChannel::CrossFade) && fc.fadeTime() == 0)
{
// morph start <-> target depending on intensities
- value = uchar(((qreal(fc.target() - fc.start()) * intensity()) + fc.start()) * parentIntensity());
+ bool rampUp = fc.target() > fc.start() ? true : false;
+ value = rampUp ? fc.target() - fc.start() : fc.start() - fc.target();
+ value = qreal(value) * intensity();
+ value = qreal(rampUp ? fc.start() + value : fc.start() - value) * parentIntensity();
}
else if (flags & FadeChannel::Intensity)
{
@@ -195,11 +246,17 @@ void GenericFader::write(Universe *universe)
}
else if (flags & FadeChannel::Relative)
{
- universe->writeRelative(address, value);
+ universe->writeRelative(address, value, channelCount);
+ }
+ else if (flags & FadeChannel::Flashing)
+ {
+ universe->writeMultiple(address, value, channelCount);
+ continue;
}
else
{
- universe->writeBlended(address, value, m_blendMode);
+ // treat value as a whole, so do this just once per FadeChannel
+ universe->writeBlended(address, value, channelCount, m_blendMode);
}
if (((flags & FadeChannel::Intensity) &&
@@ -212,7 +269,7 @@ void GenericFader::write(Universe *universe)
it.remove();
}
- if (flags & FadeChannel::Autoremove)
+ if (flags & FadeChannel::AutoRemove && value == fc.target())
it.remove();
}
@@ -275,25 +332,24 @@ void GenericFader::setFadeOut(bool enable, uint fadeTime)
{
m_fadeOut = enable;
- if (fadeTime)
+ if (fadeTime == 0)
+ return;
+
+ QMutableHashIterator it(m_channels);
+ while (it.hasNext() == true)
{
- QMutableHashIterator it(m_channels);
- while (it.hasNext() == true)
- {
- FadeChannel& fc(it.next().value());
+ FadeChannel& fc(it.next().value());
- if ((fc.flags() & FadeChannel::Intensity) == 0)
- {
- fc.addFlag(FadeChannel::Autoremove);
- continue;
- }
+ // non-intensity channels (eg LTP) should fade
+ // to the current universe value
+ if ((fc.flags() & FadeChannel::Intensity) == 0)
+ fc.addFlag(FadeChannel::SetTarget);
- fc.setStart(fc.current());
- fc.setTarget(0);
- fc.setElapsed(0);
- fc.setReady(false);
- fc.setFadeTime(fc.canFade() ? fadeTime : 0);
- }
+ fc.setStart(fc.current());
+ fc.setTarget(0);
+ fc.setElapsed(0);
+ fc.setReady(false);
+ fc.setFadeTime(fc.canFade() ? fadeTime : 0);
}
}
diff --git a/engine/src/genericfader.h b/engine/src/genericfader.h
index 7eba714329..4177c8d8fa 100644
--- a/engine/src/genericfader.h
+++ b/engine/src/genericfader.h
@@ -25,6 +25,7 @@
#include
#include "universe.h"
+#include "scenevalue.h"
class FadeChannel;
@@ -32,6 +33,14 @@ class FadeChannel;
* @{
*/
+/**
+ * GenericFader represents all the fading channels for one Function (or feature)
+ * and one Universe. For example a Scene will request one GenericFader for all the
+ * channels of all the fixtures on a specific Universe.
+ * In this way, Universes will handle a list of dedicated faders, without
+ * any lookup
+ */
+
class GenericFader : public QObject
{
Q_OBJECT
@@ -54,6 +63,11 @@ class GenericFader : public QObject
int priority() const;
void setPriority(int priority);
+ /** Get/Set if this fader should handle primary/secondary channels
+ * when a caller requests a FadeChannel */
+ bool handleSecondary();
+ void setHandleSecondary(bool enable);
+
/** Build a hash for a fader channel which is unique in a Universe.
* This is used to map channels and access them quickly */
static quint32 channelHash(quint32 fixtureID, quint32 channel);
@@ -137,6 +151,7 @@ class GenericFader : public QObject
/** Enable/disable universe monitoring before writing new data */
void setMonitoring(bool enable);
+ /** Remove the Crossfade flag from every fader handled by this class */
void resetCrossfade();
signals:
@@ -148,6 +163,7 @@ class GenericFader : public QObject
QString m_name;
quint32 m_fid;
int m_priority;
+ bool m_handleSecondary;
QHash m_channels;
qreal m_intensity;
qreal m_parentIntensity;
diff --git a/engine/src/gradient.cpp b/engine/src/gradient.cpp
index 562d4e9535..bd06cb2579 100644
--- a/engine/src/gradient.cpp
+++ b/engine/src/gradient.cpp
@@ -17,13 +17,13 @@
limitations under the License.
*/
-#include "gradient.h"
-
#include
#include
#include
#include
+#include "gradient.h"
+
QImage Gradient::m_rgb = QImage();
QImage Gradient::getRGBGradient()
@@ -59,7 +59,7 @@ void Gradient::fillWithGradient(int r, int g, int b, QPainter *painter, int x)
void Gradient::initialize()
{
- if( m_rgb.isNull() == false )
+ if (m_rgb.isNull() == false)
return;
m_rgb = QImage(256, 256, QImage::Format_RGB32);
diff --git a/engine/src/grandmaster.cpp b/engine/src/grandmaster.cpp
index aee6d73627..a60f8c6077 100644
--- a/engine/src/grandmaster.cpp
+++ b/engine/src/grandmaster.cpp
@@ -17,6 +17,11 @@
limitations under the License.
*/
+#include
+
+#include "grandmaster.h"
+#include "qlcmacros.h"
+
#define KXMLQLCGMValueModeLimit "Limit"
#define KXMLQLCGMValueModeReduce "Reduce"
#define KXMLQLCGMChannelModeAllChannels "All"
@@ -24,11 +29,6 @@
#define KXMLQLCGMSliderModeNormal "Normal"
#define KXMLQLCGMSliderModeInverted "Inverted"
-#include
-
-#include "grandmaster.h"
-#include "qlcmacros.h"
-
GrandMaster::GrandMaster(QObject *parent)
: QObject(parent)
, m_valueMode(Reduce)
diff --git a/engine/src/inputoutputmap.cpp b/engine/src/inputoutputmap.cpp
index ffe6d89a0d..1cf53b67f5 100644
--- a/engine/src/inputoutputmap.cpp
+++ b/engine/src/inputoutputmap.cpp
@@ -41,12 +41,11 @@
#include "qlcfile.h"
#include "doc.h"
-#include "../../plugins/midi/src/common/midiprotocol.h"
-
InputOutputMap::InputOutputMap(Doc *doc, quint32 universes)
: QObject(doc)
, m_blackout(false)
, m_universeChanged(false)
+ , m_currentBPM(0)
, m_beatTime(new QElapsedTimer())
{
m_grandMaster = new GrandMaster(this);
@@ -102,6 +101,9 @@ bool InputOutputMap::setBlackout(bool blackout)
if (op != NULL)
op->setBlackout(blackout);
}
+
+ const QByteArray postGM = universe->postGMValues()->mid(0, universe->usedChannels());
+ universe->dumpOutput(postGM, true);
}
emit blackoutChanged(m_blackout);
@@ -323,7 +325,7 @@ void InputOutputMap::setGrandMasterChannelMode(GrandMaster::ChannelMode mode)
{
Q_ASSERT(m_grandMaster != NULL);
- if(m_grandMaster->channelMode() != mode)
+ if (m_grandMaster->channelMode() != mode)
{
m_grandMaster->setChannelMode(mode);
m_universeChanged = true;
@@ -341,7 +343,7 @@ void InputOutputMap::setGrandMasterValueMode(GrandMaster::ValueMode mode)
{
Q_ASSERT(m_grandMaster != NULL);
- if(m_grandMaster->valueMode() != mode)
+ if (m_grandMaster->valueMode() != mode)
{
m_grandMaster->setValueMode(mode);
m_universeChanged = true;
@@ -408,16 +410,16 @@ bool InputOutputMap::setInputPatch(quint32 universe, const QString &pluginName,
currProfile = currInPatch->profile();
disconnect(currInPatch, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
this, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)));
- if (currInPatch->pluginName() == "MIDI")
+ if (currInPatch->plugin()->capabilities() & QLCIOPlugin::Beats)
{
disconnect(currInPatch, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
- this, SLOT(slotMIDIBeat(quint32,quint32,uchar)));
+ this, SLOT(slotPluginBeat(quint32,quint32,uchar,const QString&)));
}
}
InputPatch *ip = NULL;
QLCIOPlugin *plugin = doc()->ioPluginCache()->plugin(pluginName);
- if (!inputUID.isEmpty())
+ if (!inputUID.isEmpty() && plugin != NULL)
{
QStringList inputs = plugin->inputs();
int lIdx = inputs.indexOf(inputUID);
@@ -441,10 +443,10 @@ bool InputOutputMap::setInputPatch(quint32 universe, const QString &pluginName,
{
connect(ip, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
this, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)));
- if (ip->pluginName() == "MIDI")
+ if (ip->plugin()->capabilities() & QLCIOPlugin::Beats)
{
connect(ip, SIGNAL(inputValueChanged(quint32,quint32,uchar,const QString&)),
- this, SLOT(slotMIDIBeat(quint32,quint32,uchar)));
+ this, SLOT(slotPluginBeat(quint32,quint32,uchar,const QString&)));
}
}
}
@@ -491,7 +493,7 @@ bool InputOutputMap::setOutputPatch(quint32 universe, const QString &pluginName,
QMutexLocker locker(&m_universeMutex);
QLCIOPlugin *plugin = doc()->ioPluginCache()->plugin(pluginName);
- if (!outputUID.isEmpty())
+ if (!outputUID.isEmpty() && plugin != NULL)
{
QStringList inputs = plugin->outputs();
int lIdx = inputs.indexOf(outputUID);
@@ -581,9 +583,13 @@ quint32 InputOutputMap::outputMapping(const QString &pluginName, quint32 output)
{
for (quint32 uni = 0; uni < universesCount(); uni++)
{
- const OutputPatch* p = m_universeArray.at(uni)->outputPatch();
- if (p != NULL && p->pluginName() == pluginName && p->output() == output)
- return uni;
+ Universe *universe = m_universeArray.at(uni);
+ for (int i = 0; i < universe->outputPatchesCount(); i++)
+ {
+ const OutputPatch* p = universe->outputPatch(i);
+ if (p != NULL && p->pluginName() == pluginName && p->output() == output)
+ return uni;
+ }
}
return QLCIOPlugin::invalidLine();
@@ -608,6 +614,26 @@ QString InputOutputMap::pluginDescription(const QString &pluginName)
return "";
}
+void InputOutputMap::removeDuplicates(QStringList &list)
+{
+ if (list.count() == 1)
+ return;
+
+ int c = 2;
+
+ for (int i = 1; i < list.count(); i++)
+ {
+ for (int j = 0; j < i; j++)
+ {
+ if (list.at(i) == list.at(j))
+ {
+ list.replace(i, QString("%1 %2").arg(list.at(j)).arg(c));
+ c++;
+ }
+ }
+ }
+}
+
QStringList InputOutputMap::inputPluginNames()
{
QStringList list;
@@ -640,7 +666,11 @@ QStringList InputOutputMap::pluginInputs(const QString& pluginName)
if (ip == NULL)
return QStringList();
else
- return ip->inputs();
+ {
+ QStringList iList = ip->inputs();
+ removeDuplicates(iList);
+ return iList;
+ }
}
QStringList InputOutputMap::pluginOutputs(const QString& pluginName)
@@ -649,7 +679,11 @@ QStringList InputOutputMap::pluginOutputs(const QString& pluginName)
if (op == NULL)
return QStringList();
else
- return op->outputs();
+ {
+ QStringList oList = op->outputs();
+ removeDuplicates(oList);
+ return oList;
+ }
}
bool InputOutputMap::pluginSupportsFeedback(const QString& pluginName)
@@ -717,7 +751,7 @@ QString InputOutputMap::outputPluginStatus(const QString& pluginName, quint32 ou
}
}
-bool InputOutputMap::sendFeedBack(quint32 universe, quint32 channel, uchar value, const QString& key)
+bool InputOutputMap::sendFeedBack(quint32 universe, quint32 channel, uchar value, const QVariant ¶ms)
{
if (universe >= universesCount())
return false;
@@ -726,7 +760,7 @@ bool InputOutputMap::sendFeedBack(quint32 universe, quint32 channel, uchar value
if (patch != NULL && patch->isPatched())
{
- patch->plugin()->sendFeedBack(universe, patch->output(), channel, value, key);
+ patch->plugin()->sendFeedBack(universe, patch->output(), channel, value, params);
return true;
}
else
@@ -741,11 +775,15 @@ void InputOutputMap::slotPluginConfigurationChanged(QLCIOPlugin* plugin)
bool success = true;
for (quint32 i = 0; i < universesCount(); i++)
{
- OutputPatch* op = m_universeArray.at(i)->outputPatch();
-
- if (op != NULL && op->plugin() == plugin)
+ Universe *universe = m_universeArray.at(i);
+ for (int oi = 0; oi < universe->outputPatchesCount(); oi++)
{
- /*success = */ op->reconnect();
+ OutputPatch* op = universe->outputPatch(oi);
+
+ if (op != NULL && op->plugin() == plugin)
+ {
+ /*success = */ op->reconnect();
+ }
}
InputPatch* ip = m_universeArray.at(i)->inputPatch();
@@ -961,7 +999,7 @@ void InputOutputMap::setBeatGeneratorType(InputOutputMap::BeatGeneratorType type
doc()->masterTimer()->setBeatSourceType(MasterTimer::Internal);
setBpmNumber(doc()->masterTimer()->bpmNumber());
break;
- case MIDI:
+ case Plugin:
doc()->masterTimer()->setBeatSourceType(MasterTimer::External);
// reset the current BPM number and detect it from the MIDI beats
setBpmNumber(0);
@@ -988,6 +1026,29 @@ InputOutputMap::BeatGeneratorType InputOutputMap::beatGeneratorType() const
return m_beatGeneratorType;
}
+QString InputOutputMap::beatTypeToString(BeatGeneratorType type) const
+{
+ switch (type)
+ {
+ case Internal: return "Internal";
+ case Plugin: return "Plugin";
+ case Audio: return "Audio";
+ default: return "Disabled";
+ }
+}
+
+InputOutputMap::BeatGeneratorType InputOutputMap::stringToBeatType(QString str)
+{
+ if (str == "Internal")
+ return Internal;
+ else if (str == "Plugin")
+ return Plugin;
+ else if (str == "Audio")
+ return Audio;
+
+ return Disabled;
+}
+
void InputOutputMap::setBpmNumber(int bpm)
{
if (m_beatGeneratorType == Disabled || bpm == m_currentBPM)
@@ -1018,35 +1079,32 @@ void InputOutputMap::slotMasterTimerBeat()
emit beat();
}
-void InputOutputMap::slotMIDIBeat(quint32 universe, quint32 channel, uchar value)
+void InputOutputMap::slotPluginBeat(quint32 universe, quint32 channel, uchar value, const QString &key)
{
Q_UNUSED(universe)
- // not interested in synthetic release event or non-MBC ones
- if (m_beatGeneratorType != MIDI || value == 0 || channel < CHANNEL_OFFSET_MBC_PLAYBACK)
+ // not interested in synthetic release or non-beat event
+ if (m_beatGeneratorType != Plugin || value == 0 || key != "beat")
return;
- qDebug() << "MIDI MBC:" << channel << m_beatTime->elapsed();
+ qDebug() << "Plugin beat:" << channel << m_beatTime->elapsed();
// process the timer as first thing, to avoid wasting time
// with the operations below
int elapsed = m_beatTime->elapsed();
m_beatTime->restart();
- if (channel == CHANNEL_OFFSET_MBC_BEAT)
- {
- int bpm = qRound(60000.0 / (float)elapsed);
- float currBpmTime = 60000.0 / (float)m_currentBPM;
- // here we check if the difference between the current BPM duration
- // and the current time elapsed is within a range of +/-1ms.
- // If it isn't, then the BPM number has really changed, otherwise
- // it's just a tiny time drift
- if (qAbs((float)elapsed - currBpmTime) > 1)
- setBpmNumber(bpm);
-
- doc()->masterTimer()->requestBeat();
- emit beat();
- }
+ int bpm = qRound(60000.0 / (float)elapsed);
+ float currBpmTime = 60000.0 / (float)m_currentBPM;
+ // here we check if the difference between the current BPM duration
+ // and the current time elapsed is within a range of +/-1ms.
+ // If it isn't, then the BPM number has really changed, otherwise
+ // it's just a tiny time drift
+ if (qAbs((float)elapsed - currBpmTime) > 1)
+ setBpmNumber(bpm);
+
+ doc()->masterTimer()->requestBeat();
+ emit beat();
}
void InputOutputMap::slotAudioSpectrum(double *spectrumBands, int size, double maxMagnitude, quint32 power)
@@ -1234,6 +1292,18 @@ bool InputOutputMap::loadXML(QXmlStreamReader &root)
uni->loadXML(root, m_universeArray.count() - 1, this);
}
}
+ else if (root.name() == KXMLIOBeatGenerator)
+ {
+ QXmlStreamAttributes attrs = root.attributes();
+
+ if (attrs.hasAttribute(KXMLIOBeatType))
+ setBeatGeneratorType(stringToBeatType(attrs.value(KXMLIOBeatType).toString()));
+
+ if (attrs.hasAttribute(KXMLIOBeatsPerMinute))
+ setBpmNumber(attrs.value(KXMLIOBeatsPerMinute).toInt());
+
+ root.skipCurrentElement();
+ }
else
{
qWarning() << Q_FUNC_INFO << "Unknown IO Map tag:" << root.name();
@@ -1251,12 +1321,15 @@ bool InputOutputMap::saveXML(QXmlStreamWriter *doc) const
/* IO Map Instance entry */
doc->writeStartElement(KXMLIOMap);
- foreach(Universe *uni, m_universeArray)
+ doc->writeStartElement(KXMLIOBeatGenerator);
+ doc->writeAttribute(KXMLIOBeatType, beatTypeToString(m_beatGeneratorType));
+ doc->writeAttribute(KXMLIOBeatsPerMinute, QString::number(m_currentBPM));
+ doc->writeEndElement();
+
+ foreach (Universe *uni, m_universeArray)
uni->saveXML(doc);
doc->writeEndElement();
return true;
}
-
-
diff --git a/engine/src/inputoutputmap.h b/engine/src/inputoutputmap.h
index 296834ebc7..2fa0b4c153 100644
--- a/engine/src/inputoutputmap.h
+++ b/engine/src/inputoutputmap.h
@@ -42,7 +42,10 @@ class Doc;
* @{
*/
-#define KXMLIOMap QString("InputOutputMap")
+#define KXMLIOMap QString("InputOutputMap")
+#define KXMLIOBeatGenerator QString("BeatGenerator")
+#define KXMLIOBeatType QString("BeatType")
+#define KXMLIOBeatsPerMinute QString("BPM")
class InputOutputMap : public QObject
{
@@ -355,7 +358,7 @@ class InputOutputMap : public QObject
* @param inputUID Unique plugin output line identifier as string
* @param output A universe provided by the plugin to patch to
* @param isFeedback Determine if this line is a feedback output
- * @param index the output patch index
+ * @param index The output patch index
*
* @return true if successful, otherwise false
*/
@@ -499,7 +502,11 @@ class InputOutputMap : public QObject
* Send feedback value to the input profile e.g. to move a motorized
* sliders & knobs, set indicator leds etc.
*/
- bool sendFeedBack(quint32 universe, quint32 channel, uchar value, const QString& key = 0);
+ bool sendFeedBack(quint32 universe, quint32 channel, uchar value, const QVariant ¶ms);
+
+private:
+ /** In case of duplicate strings, append a number to make them unique */
+ void removeDuplicates(QStringList &list);
private slots:
/** Slot that catches plugin configuration change notifications from UIPluginCache */
@@ -576,19 +583,22 @@ private slots:
{
Disabled, //! No one is generating beats
Internal, //! MasterTimer is the beat generator
- MIDI, //! A MIDI plugin is the beat generator
+ Plugin, //! A plugin is the beat generator
Audio //! An audio input device is the beat generator
};
void setBeatGeneratorType(BeatGeneratorType type);
BeatGeneratorType beatGeneratorType() const;
+ QString beatTypeToString(BeatGeneratorType type) const;
+ BeatGeneratorType stringToBeatType(QString str);
+
void setBpmNumber(int bpm);
int bpmNumber() const;
protected slots:
void slotMasterTimerBeat();
- void slotMIDIBeat(quint32 universe, quint32 channel, uchar value);
+ void slotPluginBeat(quint32 universe, quint32 channel, uchar value, const QString &key);
void slotAudioSpectrum(double *spectrumBands, int size, double maxMagnitude, quint32 power);
signals:
diff --git a/engine/src/inputpatch.cpp b/engine/src/inputpatch.cpp
index 3481003c5d..b0878dcf72 100644
--- a/engine/src/inputpatch.cpp
+++ b/engine/src/inputpatch.cpp
@@ -140,7 +140,7 @@ bool InputPatch::reconnect()
bool ret = m_plugin->openInput(m_pluginLine, m_universe);
if (ret == true)
{
- foreach(QString par, m_parametersCache.keys())
+ foreach (QString par, m_parametersCache.keys())
{
qDebug() << "[InputPatch] restoring parameter:" << par << m_parametersCache[par];
m_plugin->setParameter(m_universe, m_pluginLine, QLCIOPlugin::Input, par, m_parametersCache[par]);
diff --git a/engine/src/keypadparser.cpp b/engine/src/keypadparser.cpp
index bc2d64cbc6..d22ef00eef 100644
--- a/engine/src/keypadparser.cpp
+++ b/engine/src/keypadparser.cpp
@@ -17,8 +17,11 @@
limitations under the License.
*/
+#include
+
#include "keypadparser.h"
#include "qlcmacros.h"
+#include "universe.h"
KeyPadParser::KeyPadParser()
{
@@ -111,6 +114,9 @@ QList KeyPadParser::parseCommand(Doc *doc, QString command,
{
case CommandNone:
// no command: this is a channel number
+ if (number <= 0)
+ break;
+
fromChannel = number;
toChannel = fromChannel;
channelSet = true;
@@ -185,7 +191,10 @@ QList KeyPadParser::parseCommand(Doc *doc, QString command,
uchar uniValue = 0;
SceneValue scv;
- if (quint32(uniData.length()) >= i)
+ if (i >= UNIVERSE_SIZE)
+ continue;
+
+ if (quint32(uniData.length()) > i)
uniValue = uchar(uniData.at(i));
scv.channel = i;
@@ -194,9 +203,9 @@ QList KeyPadParser::parseCommand(Doc *doc, QString command,
else if (lastCommand == CommandMinus)
scv.value = CLAMP(uniValue - toValue, 0, 255);
else if (lastCommand == CommandPlusPercent)
- scv.value = CLAMP(uniValue * (1.0 + toValue), 0, 255);
+ scv.value = CLAMP(lrintf(uniValue * (1.0 + toValue)), 0, 255);
else if (lastCommand == CommandMinusPercent)
- scv.value = CLAMP(uniValue - (float(uniValue) * toValue), 0, 255);
+ scv.value = CLAMP(lrintf(uniValue - (float(uniValue) * toValue)), 0, 255);
else if (lastCommand == CommandZERO)
scv.value = 0;
else if (lastCommand == CommandFULL)
diff --git a/engine/src/mastertimer-unix.cpp b/engine/src/mastertimer-unix.cpp
index fb69458a7d..f994ae836d 100644
--- a/engine/src/mastertimer-unix.cpp
+++ b/engine/src/mastertimer-unix.cpp
@@ -40,7 +40,7 @@ MasterTimerPrivate::MasterTimerPrivate(MasterTimer* masterTimer)
, m_run(false)
{
Q_ASSERT(masterTimer != NULL);
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
#endif
}
@@ -56,7 +56,7 @@ void MasterTimerPrivate::stop()
wait();
}
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
int MasterTimerPrivate::compareTime(mach_timespec_t *time1, mach_timespec_t *time2)
#else
int MasterTimerPrivate::compareTime(struct timespec *time1, struct timespec *time2)
@@ -97,7 +97,7 @@ void MasterTimerPrivate::run()
int ret = 0;
/* Allocate all the memory at the start so we don't waste any time */
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
mach_timespec_t* finish = static_cast (malloc(sizeof(mach_timespec_t)));
mach_timespec_t* current = static_cast (malloc(sizeof(mach_timespec_t)));
#else
@@ -110,7 +110,7 @@ void MasterTimerPrivate::run()
sleepTime->tv_sec = 0;
/* This is the start time for the timer */
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
ret = clock_get_time(cclock, finish);
#else
ret = clock_gettime(CLOCK_MONOTONIC, finish);
@@ -132,7 +132,7 @@ void MasterTimerPrivate::run()
finish->tv_sec += (finish->tv_nsec + nsTickTime) / 1000000000L;
finish->tv_nsec = (finish->tv_nsec + nsTickTime) % 1000000000L;
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
ret = clock_get_time(cclock, current);
#else
ret = clock_gettime(CLOCK_MONOTONIC, current);
@@ -153,7 +153,7 @@ void MasterTimerPrivate::run()
/* No need to sleep. Immediately process the next tick */
mt->timerTick();
/* Now the finish time needs to be recalibrated */
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
clock_get_time(cclock, finish);
#else
clock_gettime(CLOCK_MONOTONIC, finish);
@@ -185,7 +185,7 @@ void MasterTimerPrivate::run()
#if 0
/* Now take full CPU for precision (only a few nanoseconds,
at maximum 100 nanoseconds) */
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
ret = clock_get_time(cclock, current);
#else
ret = clock_gettime(CLOCK_MONOTONIC, current);
@@ -194,7 +194,7 @@ void MasterTimerPrivate::run()
while (sleepTime->tv_nsec > 5)
{
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
ret = clock_get_time(cclock, current);
#else
ret = clock_gettime(CLOCK_MONOTONIC, current);
diff --git a/engine/src/mastertimer-unix.h b/engine/src/mastertimer-unix.h
index 78aded770e..a377801268 100644
--- a/engine/src/mastertimer-unix.h
+++ b/engine/src/mastertimer-unix.h
@@ -23,7 +23,7 @@
#include
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
#include
#include
#endif
@@ -44,7 +44,7 @@ class MasterTimerPrivate : public QThread
private:
void run();
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
int compareTime(mach_timespec_t *time1, mach_timespec_t *time2);
#else
int compareTime(struct timespec *time1, struct timespec *time2);
@@ -52,7 +52,7 @@ class MasterTimerPrivate : public QThread
private:
bool m_run;
-#if defined(Q_OS_OSX) || defined(Q_OS_IOS)
+#if defined(Q_OS_MACOS) || defined(Q_OS_IOS)
clock_serv_t cclock;
#endif
};
diff --git a/engine/src/mastertimer.cpp b/engine/src/mastertimer.cpp
index f51fc85b3d..a2c170e86a 100644
--- a/engine/src/mastertimer.cpp
+++ b/engine/src/mastertimer.cpp
@@ -32,10 +32,8 @@
#include "inputoutputmap.h"
#include "genericfader.h"
-#include "fadechannel.h"
#include "mastertimer.h"
#include "dmxsource.h"
-#include "qlcmacros.h"
#include "function.h"
#include "universe.h"
#include "doc.h"
@@ -266,6 +264,8 @@ void MasterTimer::timerTickFunctions(QList universes)
removeList << i; // Don't remove the item from the list just yet.
functionListHasChanged = true;
stoppedAFunction = true;
+
+ emit functionStopped(function->id());
}
}
}
diff --git a/engine/src/mastertimer.h b/engine/src/mastertimer.h
index 4fa3e78417..2a840e4ec3 100644
--- a/engine/src/mastertimer.h
+++ b/engine/src/mastertimer.h
@@ -114,6 +114,9 @@ class MasterTimer : public QObject
/** Emitted when a Function is started */
void functionStarted(quint32 id);
+ /** Emitted when a Function has just been stopped */
+ void functionStopped(quint32 id);
+
private:
/** Execute one timer tick for each registered Function */
void timerTickFunctions(QList universes);
diff --git a/engine/src/monitorproperties.cpp b/engine/src/monitorproperties.cpp
index 11d376257e..8ee1bee6e2 100644
--- a/engine/src/monitorproperties.cpp
+++ b/engine/src/monitorproperties.cpp
@@ -707,7 +707,7 @@ bool MonitorProperties::saveXML(QXmlStreamWriter *doc, const Doc *mainDocument)
doc->writeTextElement(KXMLQLCMonitorCommonBackground,
mainDocument->normalizeComponentPath(commonBackgroundImage()));
}
- else if(customBackgroundList().isEmpty() == false)
+ else if (customBackgroundList().isEmpty() == false)
{
QMapIterator it(customBackgroundList());
while (it.hasNext() == true)
@@ -801,7 +801,7 @@ bool MonitorProperties::saveXML(QXmlStreamWriter *doc, const Doc *mainDocument)
// * write generic items information *
// ***********************************************************
QMapIterator it(m_genericItems);
- while(it.hasNext())
+ while (it.hasNext())
{
it.next();
quint32 itemID = it.key();
diff --git a/engine/src/outputpatch.cpp b/engine/src/outputpatch.cpp
index 5823bb8730..bce2ae056b 100644
--- a/engine/src/outputpatch.cpp
+++ b/engine/src/outputpatch.cpp
@@ -97,7 +97,7 @@ bool OutputPatch::reconnect()
bool ret = m_plugin->openOutput(m_pluginLine, m_universe);
if (ret == true)
{
- foreach(QString par, m_parametersCache.keys())
+ foreach (QString par, m_parametersCache.keys())
m_plugin->setParameter(m_universe, m_pluginLine, QLCIOPlugin::Output, par, m_parametersCache[par]);
}
return ret;
@@ -191,7 +191,7 @@ void OutputPatch::setBlackout(bool blackout)
emit blackoutChanged(m_blackout);
}
-void OutputPatch::dump(quint32 universe, const QByteArray& data)
+void OutputPatch::dump(quint32 universe, const QByteArray& data, bool dataChanged)
{
/* Don't do anything if there is no plugin and/or output line. */
if (m_plugin != NULL && m_pluginLine != QLCIOPlugin::invalidLine())
@@ -201,11 +201,11 @@ void OutputPatch::dump(quint32 universe, const QByteArray& data)
if (m_pauseBuffer.isNull())
m_pauseBuffer.append(data);
- m_plugin->writeUniverse(universe, m_pluginLine, m_pauseBuffer);
+ m_plugin->writeUniverse(universe, m_pluginLine, m_pauseBuffer, dataChanged);
}
else
{
- m_plugin->writeUniverse(universe, m_pluginLine, data);
+ m_plugin->writeUniverse(universe, m_pluginLine, data, dataChanged);
}
}
}
diff --git a/engine/src/outputpatch.h b/engine/src/outputpatch.h
index f85067cce2..43d57ca187 100644
--- a/engine/src/outputpatch.h
+++ b/engine/src/outputpatch.h
@@ -118,7 +118,7 @@ class OutputPatch : public QObject
/** Write the contents of a 512 channel value buffer to the plugin.
* Called periodically by OutputMap. No need to call manually. */
- void dump(quint32 universe, const QByteArray &data);
+ void dump(quint32 universe, const QByteArray &data, bool dataChanged);
signals:
void pausedChanged(bool paused);
diff --git a/engine/src/qlccapability.cpp b/engine/src/qlccapability.cpp
index 8a89bd311e..f3b456ab4c 100644
--- a/engine/src/qlccapability.cpp
+++ b/engine/src/qlccapability.cpp
@@ -469,7 +469,7 @@ bool QLCCapability::loadXML(QXmlStreamReader &doc)
/* ************************* LEGACY ATTRIBUTES ************************* */
/* Get (optional) resource name for gobo/effect/... */
- if(attrs.hasAttribute(KXMLQLCCapabilityResource))
+ if (attrs.hasAttribute(KXMLQLCCapabilityResource))
{
QString path = attrs.value(KXMLQLCCapabilityResource).toString();
if (QFileInfo(path).isRelative())
diff --git a/engine/src/qlcchannel.h b/engine/src/qlcchannel.h
index 20763c9da4..b9e714b585 100644
--- a/engine/src/qlcchannel.h
+++ b/engine/src/qlcchannel.h
@@ -22,6 +22,7 @@
#define QLCCHANNEL_H
#include
+#include
#include
#include
#include
diff --git a/engine/src/qlcclipboard.cpp b/engine/src/qlcclipboard.cpp
index 508ee40b02..f93b073e60 100644
--- a/engine/src/qlcclipboard.cpp
+++ b/engine/src/qlcclipboard.cpp
@@ -18,8 +18,7 @@
*/
#include "qlcclipboard.h"
-#include "chaser.h"
-#include "scene.h"
+#include "doc.h"
QLCClipboard::QLCClipboard(Doc *doc)
: m_doc(doc)
diff --git a/engine/src/qlcconfig.h.in b/engine/src/qlcconfig.h.in
new file mode 100644
index 0000000000..f9a0fe79fe
--- /dev/null
+++ b/engine/src/qlcconfig.h.in
@@ -0,0 +1,31 @@
+#ifndef QLCCONFIG_H
+#define QLCCONFIG_H
+
+#define APPNAME "${APPNAME}"
+#define FXEDNAME "${FXEDNAME}"
+#define APPVERSION "${APPVERSION}"
+#define DOCSDIR "${INSTALLROOT}/${DOCSDIR}"
+#define INPUTPROFILEDIR "${INSTALLROOT}/${INPUTPROFILEDIR}"
+#define USERQLCPLUSDIR "${USERDATADIR}"
+#define USERINPUTPROFILEDIR "${USERINPUTPROFILEDIR}"
+#define MIDITEMPLATEDIR "${INSTALLROOT}/${MIDITEMPLATEDIR}"
+#define USERMIDITEMPLATEDIR "${USERMIDITEMPLATEDIR}"
+#define MODIFIERSTEMPLATEDIR "${INSTALLROOT}/${MODIFIERSTEMPLATEDIR}"
+#define USERMODIFIERSTEMPLATEDIR "${USERMODIFIERSTEMPLATEDIR}"
+#define FIXTUREDIR "${INSTALLROOT}/${FIXTUREDIR}"
+#define USERFIXTUREDIR "${USERFIXTUREDIR}"
+#define PLUGINDIR "${INSTALLROOT}/${PLUGINDIR}"
+#define AUDIOPLUGINDIR "${INSTALLROOT}/${AUDIOPLUGINDIR}"
+#define TRANSLATIONDIR "${INSTALLROOT}/${TRANSLATIONDIR}"
+#define RGBSCRIPTDIR "${INSTALLROOT}/${RGBSCRIPTDIR}"
+#define USERRGBSCRIPTDIR "${USERRGBSCRIPTDIR}"
+#define GOBODIR "${INSTALLROOT}/${GOBODIR}"
+#define WEBFILESDIR "${INSTALLROOT}/${WEBFILESDIR}"
+
+#ifdef QMLUI
+#define MESHESDIR "${INSTALLROOT}/${MESHESDIR}"
+#define COLORFILTERSDIR "${INSTALLROOT}/${COLORFILTERSDIR}"
+#define USERCOLORFILTERSDIR "${USERCOLORFILTERSDIR}"
+#endif /* QMLUI */
+
+#endif /* QLCCONFIG_H */
diff --git a/engine/src/qlcconfig.h.noroot.in b/engine/src/qlcconfig.h.noroot.in
new file mode 100644
index 0000000000..e52c284f40
--- /dev/null
+++ b/engine/src/qlcconfig.h.noroot.in
@@ -0,0 +1,31 @@
+#ifndef QLCCONFIG_H
+#define QLCCONFIG_H
+
+#define APPNAME "${APPNAME}"
+#define FXEDNAME "${FXEDNAME}"
+#define APPVERSION "${APPVERSION}"
+#define DOCSDIR "${DOCSDIR}"
+#define INPUTPROFILEDIR "${INPUTPROFILEDIR}"
+#define USERQLCPLUSDIR "${USERDATADIR}"
+#define USERINPUTPROFILEDIR "${USERINPUTPROFILEDIR}"
+#define MIDITEMPLATEDIR "${MIDITEMPLATEDIR}"
+#define USERMIDITEMPLATEDIR "${USERMIDITEMPLATEDIR}"
+#define MODIFIERSTEMPLATEDIR "${MODIFIERSTEMPLATEDIR}"
+#define USERMODIFIERSTEMPLATEDIR "${USERMODIFIERSTEMPLATEDIR}"
+#define FIXTUREDIR "${FIXTUREDIR}"
+#define USERFIXTUREDIR "${USERFIXTUREDIR}"
+#define PLUGINDIR "${PLUGINDIR}"
+#define AUDIOPLUGINDIR "${AUDIOPLUGINDIR}"
+#define TRANSLATIONDIR "${TRANSLATIONDIR}"
+#define RGBSCRIPTDIR "${RGBSCRIPTDIR}"
+#define USERRGBSCRIPTDIR "${USERRGBSCRIPTDIR}"
+#define GOBODIR "${GOBODIR}"
+#define WEBFILESDIR "${WEBFILESDIR}"
+
+#ifdef QMLUI
+#define MESHESDIR "${MESHESDIR}"
+#define COLORFILTERSDIR "${COLORFILTERSDIR}"
+#define USERCOLORFILTERSDIR "${USERCOLORFILTERSDIR}"
+#endif /* QMLUI */
+
+#endif /* QLCCONFIG_H */
diff --git a/engine/src/qlcfile.cpp b/engine/src/qlcfile.cpp
index cc68eeb092..051a16c80c 100644
--- a/engine/src/qlcfile.cpp
+++ b/engine/src/qlcfile.cpp
@@ -149,7 +149,7 @@ QString QLCFile::currentUserName()
DWORD length = UNLEN + 1;
TCHAR name[length];
if (GetUserName(name, &length))
- return QString::fromUtf16((ushort*) name);
+ return QString::fromUtf16(reinterpret_cast(name));
else
return QString("Unknown windows user");
#else
@@ -224,7 +224,7 @@ QDir QLCFile::userDirectory(QString path, QString fallBackPath, QStringList exte
LPTSTR home = (LPTSTR) malloc(256 * sizeof(TCHAR));
GetEnvironmentVariable(TEXT("UserProfile"), home, 256);
dir.setPath(QString("%1/%2")
- .arg(QString::fromUtf16(reinterpret_cast (home)))
+ .arg(QString::fromUtf16(reinterpret_cast(home)))
.arg(path));
free(home);
#endif
diff --git a/engine/src/qlcfile.h b/engine/src/qlcfile.h
index d70f85ed4f..8c53e6e476 100644
--- a/engine/src/qlcfile.h
+++ b/engine/src/qlcfile.h
@@ -60,6 +60,9 @@ class QString;
#define KXMLQLCCreatorVersion QString("Version")
#define KXMLQLCCreatorAuthor QString("Author")
+// share fixture list tag
+#define KXMLQLCFixturesList QString("FixtureList")
+
// True and false
#define KXMLQLCTrue "True"
#define KXMLQLCFalse "False"
@@ -126,7 +129,7 @@ class QLCFile
* @param extension
* @return
*/
- static QDir systemDirectory(QString path, QString extension = QString() );
+ static QDir systemDirectory(QString path, QString extension = QString());
/**
* @brief systemDirectory returns a system dependant QDir based
diff --git a/engine/src/qlcfixturedef.cpp b/engine/src/qlcfixturedef.cpp
index 31c22d8be8..cba214b395 100644
--- a/engine/src/qlcfixturedef.cpp
+++ b/engine/src/qlcfixturedef.cpp
@@ -27,9 +27,7 @@
#include "qlcfixturemode.h"
#include "qlcfixturedef.h"
-#include "qlccapability.h"
#include "qlcchannel.h"
-#include "qlcconfig.h"
#include "qlcfile.h"
#include "fixture.h"
@@ -206,18 +204,24 @@ void QLCFixtureDef::checkLoaded(QString mapPath)
}
if (m_fileAbsolutePath.isEmpty())
{
- qWarning() << Q_FUNC_INFO << "Empty file path provided ! This is a trouble.";
+ qWarning() << Q_FUNC_INFO << "Empty file path provided! This is a trouble.";
return;
}
- QString absPath = QString("%1%2%3").arg(mapPath).arg(QDir::separator()).arg(m_fileAbsolutePath);
- qDebug() << "Loading fixture definition now... " << absPath;
- bool error = loadXML(absPath);
+ // check if path is relative (from map) or absolute (user def)
+ QDir defPath(m_fileAbsolutePath);
+ if (defPath.isRelative())
+ m_fileAbsolutePath = QString("%1%2%3").arg(mapPath).arg(QDir::separator()).arg(m_fileAbsolutePath);
+
+ qDebug() << "Loading fixture definition now... " << m_fileAbsolutePath;
+ bool error = loadXML(m_fileAbsolutePath);
if (error == false)
- {
m_isLoaded = true;
- m_fileAbsolutePath = QString();
- }
+}
+
+void QLCFixtureDef::setLoaded(bool loaded)
+{
+ m_isLoaded = loaded;
}
bool QLCFixtureDef::isUser() const
diff --git a/engine/src/qlcfixturedef.h b/engine/src/qlcfixturedef.h
index 8b187c3af5..183341fa75 100644
--- a/engine/src/qlcfixturedef.h
+++ b/engine/src/qlcfixturedef.h
@@ -149,6 +149,7 @@ class QLCFixtureDef
/** Check if the full definition has been loaded */
void checkLoaded(QString mapPath);
+ void setLoaded(bool loaded);
/** Get/Set if the definition is user-made */
bool isUser() const;
diff --git a/engine/src/qlcfixturedefcache.cpp b/engine/src/qlcfixturedefcache.cpp
index 4f11a16ee6..83fb3a9fc3 100644
--- a/engine/src/qlcfixturedefcache.cpp
+++ b/engine/src/qlcfixturedefcache.cpp
@@ -149,6 +149,23 @@ bool QLCFixtureDefCache::storeFixtureDef(QString filename, QString data)
return true;
}
+bool QLCFixtureDefCache::reloadFixtureDef(QLCFixtureDef *fixtureDef)
+{
+ int idx = m_defs.indexOf(fixtureDef);
+ if (idx == -1)
+ return false;
+
+ QLCFixtureDef *def = m_defs.takeAt(idx);
+ QString absPath = def->definitionSourceFile();
+ delete def;
+
+ QLCFixtureDef *origDef = new QLCFixtureDef();
+ origDef->loadXML(absPath);
+ m_defs << origDef;
+
+ return true;
+}
+
bool QLCFixtureDefCache::load(const QDir& dir)
{
qDebug() << Q_FUNC_INFO << dir.path();
@@ -361,6 +378,8 @@ bool QLCFixtureDefCache::loadQXF(const QString& path, bool isUser)
if (error == QFile::NoError)
{
fxi->setIsUser(isUser);
+ fxi->setDefinitionSourceFile(path);
+ fxi->setLoaded(true);
/* Delete the def if it's a duplicate. */
if (addFixtureDef(fxi) == false)
@@ -392,6 +411,8 @@ bool QLCFixtureDefCache::loadD4(const QString& path)
// a D4 personality is always a user-made fixture
fxi->setIsUser(true);
+ fxi->setDefinitionSourceFile(path);
+ fxi->setLoaded(true);
/* Delete the def if it's a duplicate. */
if (addFixtureDef(fxi) == false)
diff --git a/engine/src/qlcfixturedefcache.h b/engine/src/qlcfixturedefcache.h
index 316d75eedd..e195827dad 100644
--- a/engine/src/qlcfixturedefcache.h
+++ b/engine/src/qlcfixturedefcache.h
@@ -97,7 +97,7 @@ class QLCFixtureDefCache
* @param fixtureDef The fixture definition to add
* @return true, if $fixtureDef was added, otherwise false
*/
- bool addFixtureDef(QLCFixtureDef* fixtureDef);
+ bool addFixtureDef(QLCFixtureDef *fixtureDef);
/**
* Store a fixture in the fixtures user data folder
@@ -110,6 +110,14 @@ class QLCFixtureDefCache
*/
bool storeFixtureDef(QString filename, QString data);
+ /**
+ * Realod from file a definition with the provided reference
+ *
+ * @param fixtureDef The fixture definition to remove
+ * @return true, if $fixtureDef was found and removed, otherwise false
+ */
+ bool reloadFixtureDef(QLCFixtureDef *fixtureDef);
+
/**
* Load fixture definitions from the given path. Ignores duplicates.
* Returns true even if $fixturePath doesn't contain any fixtures,
diff --git a/engine/src/qlcfixturehead.cpp b/engine/src/qlcfixturehead.cpp
index 1173abe22d..11e5d47637 100644
--- a/engine/src/qlcfixturehead.cpp
+++ b/engine/src/qlcfixturehead.cpp
@@ -174,7 +174,7 @@ void QLCFixtureHead::cacheChannels(const QLCFixtureMode* mode)
m_shutterChannels.clear();
m_channelsMap.clear();
- foreach(quint32 i, m_channels)
+ foreach (quint32 i, m_channels)
{
if ((int)i >= mode->channels().size())
{
@@ -264,7 +264,7 @@ bool QLCFixtureHead::saveXML(QXmlStreamWriter *doc) const
doc->writeStartElement(KXMLQLCFixtureHead);
- foreach(quint32 index, m_channels)
+ foreach (quint32 index, m_channels)
doc->writeTextElement(KXMLQLCFixtureHeadChannel, QString::number(index));
doc->writeEndElement();
diff --git a/engine/src/qlcfixturemode.cpp b/engine/src/qlcfixturemode.cpp
index 1a5a7546eb..9ae993c882 100644
--- a/engine/src/qlcfixturemode.cpp
+++ b/engine/src/qlcfixturemode.cpp
@@ -30,7 +30,7 @@
#include "qlcchannel.h"
#include "qlcphysical.h"
-QLCFixtureMode::QLCFixtureMode(QLCFixtureDef* fixtureDef)
+QLCFixtureMode::QLCFixtureMode(QLCFixtureDef *fixtureDef)
: m_fixtureDef(fixtureDef)
, m_masterIntensityChannel(QLCChannel::invalid())
, m_useGlobalPhysical(true)
@@ -38,9 +38,8 @@ QLCFixtureMode::QLCFixtureMode(QLCFixtureDef* fixtureDef)
Q_ASSERT(fixtureDef != NULL);
}
-QLCFixtureMode::QLCFixtureMode(QLCFixtureDef* fixtureDef, const QLCFixtureMode* mode)
+QLCFixtureMode::QLCFixtureMode(QLCFixtureDef *fixtureDef, const QLCFixtureMode *mode)
: m_fixtureDef(fixtureDef)
- , m_actsOnChannelsList(mode->actsOnChannelsList())
, m_masterIntensityChannel(QLCChannel::invalid())
, m_useGlobalPhysical(true)
{
@@ -64,7 +63,14 @@ QLCFixtureMode& QLCFixtureMode::operator=(const QLCFixtureMode& mode)
m_physical = mode.m_physical;
m_heads = mode.m_heads;
m_masterIntensityChannel = QLCChannel::invalid();
- m_actsOnChannelsList = mode.actsOnChannelsList();
+
+ m_actsOnMap.clear();
+ QMapIterator ait(mode.m_actsOnMap);
+ while (ait.hasNext())
+ {
+ ait.next();
+ m_actsOnMap.insert(ait.key(), ait.value());
+ }
/* Clear the existing list of channels */
m_channels.clear();
@@ -220,7 +226,7 @@ QVector QLCFixtureMode::channels() const
return m_channels;
}
-quint32 QLCFixtureMode::channelNumber(QLCChannel* channel) const
+quint32 QLCFixtureMode::channelNumber(QLCChannel *channel) const
{
if (channel == NULL)
return QLCChannel::invalid();
@@ -246,14 +252,23 @@ quint32 QLCFixtureMode::masterIntensityChannel() const
return m_masterIntensityChannel;
}
-void QLCFixtureMode::updateActsOnChannel(QLCChannel *mainChannel, QLCChannel *actsOnChannel)
+quint32 QLCFixtureMode::primaryChannel(quint32 chIndex)
{
- m_actsOnChannelsList.insert(mainChannel, actsOnChannel);
+ return m_secondaryMap.value(chIndex, QLCChannel::invalid());
}
-QHash QLCFixtureMode::actsOnChannelsList() const
+quint32 QLCFixtureMode::channelActsOn(quint32 chIndex)
{
- return m_actsOnChannelsList;
+ return m_actsOnMap.value(chIndex, QLCChannel::invalid());
+}
+
+void QLCFixtureMode::setChannelActsOn(quint32 chIndex, quint32 actsOnIndex)
+{
+ if (m_actsOnMap.contains(chIndex))
+ m_actsOnMap.remove(chIndex);
+
+ if (actsOnIndex != QLCChannel::invalid())
+ m_actsOnMap[chIndex] = actsOnIndex;
}
/*****************************************************************************
@@ -298,22 +313,39 @@ int QLCFixtureMode::headForChannel(quint32 chnum) const
void QLCFixtureMode::cacheHeads()
{
+ QLCChannel *lastChannel = NULL;
+
for (int i = 0; i < m_heads.size(); i++)
{
QLCFixtureHead& head(m_heads[i]);
head.cacheChannels(this);
}
- for (int i = 0; i < m_channels.size(); i++)
+ for (quint32 i = 0; i < quint32(m_channels.size()); i++)
{
- if (m_channels.at(i)->group() == QLCChannel::Intensity &&
- m_channels.at(i)->controlByte() == QLCChannel::MSB &&
- m_channels.at(i)->colour() == QLCChannel::NoColour &&
+ QLCChannel *channel = m_channels.at(i);
+
+ /** Auto-detect master intensity channel */
+ if (m_masterIntensityChannel == QLCChannel::invalid() &&
+ channel->group() == QLCChannel::Intensity &&
+ channel->controlByte() == QLCChannel::MSB &&
+ channel->colour() == QLCChannel::NoColour &&
headForChannel(i) == -1)
{
m_masterIntensityChannel = i;
- break;
}
+
+ /** Map secondary channels */
+ if (lastChannel != NULL &&
+ channel->group() == lastChannel->group() &&
+ lastChannel->controlByte() == QLCChannel::MSB &&
+ channel->controlByte() == QLCChannel::LSB)
+ {
+ //qDebug() << "Channel" << lastChannel->name() << "is primary and" << channel->name() << "is secondary";
+ m_secondaryMap[i] = i - 1;
+ }
+
+ lastChannel = channel;
}
}
@@ -369,9 +401,6 @@ bool QLCFixtureMode::loadXML(QXmlStreamReader &doc)
setName(str);
}
- /* Temporary list with mode's channels pointer and acts on indexes. */
- QList listChannelsWithActsOnIndex;
-
/* Subtags */
while (doc.readNextStartElement())
{
@@ -381,21 +410,17 @@ bool QLCFixtureMode::loadXML(QXmlStreamReader &doc)
Q_ASSERT(m_fixtureDef != NULL);
str = doc.attributes().value(KXMLQLCFixtureModeChannelNumber).toString();
- int actsOnChannelIndex = -1;
+ quint32 actsOnChannelIndex = QLCChannel::invalid();
if (doc.attributes().hasAttribute(KXMLQLCFixtureModeChannelActsOn))
- {
- actsOnChannelIndex = doc.attributes().value(KXMLQLCFixtureModeChannelActsOn).toInt();
- }
+ actsOnChannelIndex = doc.attributes().value(KXMLQLCFixtureModeChannelActsOn).toUInt();
QLCChannel *currentChannel = m_fixtureDef->channel(doc.readElementText());
- ChannelActsOnData channelActsData(currentChannel, actsOnChannelIndex);
+ if (actsOnChannelIndex != QLCChannel::invalid())
+ m_actsOnMap[str.toInt()] = actsOnChannelIndex;
- listChannelsWithActsOnIndex.append(channelActsData);
-
- insertChannel(currentChannel,
- str.toInt());
+ insertChannel(currentChannel, str.toInt());
}
else if (doc.name() == KXMLQLCFixtureHead)
{
@@ -418,19 +443,6 @@ bool QLCFixtureMode::loadXML(QXmlStreamReader &doc)
}
}
- // Set acts on channels
-
- foreach (ChannelActsOnData channelSctsOnData, listChannelsWithActsOnIndex)
- {
- if(m_channels.contains(channelSctsOnData.channel) &&
- channelSctsOnData.actsOnIndex >= 0 &&
- m_channels.size() > channelSctsOnData.actsOnIndex)
- {
- m_actsOnChannelsList.insert(channelSctsOnData.channel,
- m_channels.at(channelSctsOnData.actsOnIndex));
- }
- }
-
// Cache all head channels
cacheHeads();
@@ -454,18 +466,13 @@ bool QLCFixtureMode::saveXML(QXmlStreamWriter *doc)
QVectorIterator it(m_channels);
while (it.hasNext() == true)
{
- QLCChannel* channel = it.next();
+ QLCChannel *channel = it.next();
+ quint32 actsOnIndex = m_actsOnMap.value(i, QLCChannel::invalid());
doc->writeStartElement(KXMLQLCFixtureModeChannel);
doc->writeAttribute(KXMLQLCFixtureModeChannelNumber, QString::number(i++));
-
- if (m_actsOnChannelsList.contains(channel))
- {
- QLCChannel *ChannelActsOn = m_actsOnChannelsList.value(channel);
- if(ChannelActsOn != NULL){
- doc->writeAttribute(KXMLQLCFixtureModeChannelActsOn, QString::number(m_channels.indexOf(ChannelActsOn)));
- }
- }
+ if (actsOnIndex != QLCChannel::invalid())
+ doc->writeAttribute(KXMLQLCFixtureModeChannelActsOn, QString::number(actsOnIndex));
doc->writeCharacters(channel->name());
doc->writeEndElement();
@@ -480,8 +487,3 @@ bool QLCFixtureMode::saveXML(QXmlStreamWriter *doc)
return true;
}
-
-QLCFixtureMode::ChannelActsOnData::ChannelActsOnData(QLCChannel *newChannel, int newAcsOnIndex) :
- channel(newChannel),
- actsOnIndex(newAcsOnIndex)
-{}
diff --git a/engine/src/qlcfixturemode.h b/engine/src/qlcfixturemode.h
index bfb1ff60d8..54485f3e1a 100644
--- a/engine/src/qlcfixturemode.h
+++ b/engine/src/qlcfixturemode.h
@@ -206,31 +206,30 @@ class QLCFixtureMode
*/
quint32 channelNumber(QLCChannel::Group group, QLCChannel::ControlByte cByte = QLCChannel::MSB) const;
+ /** Return the auto-detected channel index of the Fixture master dimmer for this mode */
quint32 masterIntensityChannel() const;
- /*!
- * \brief The ChannelActsOnData struct
- *
- * Contains channel pointer and acts on channel index.
- *
- */
+ /** Return the index of the primary channel $chIndex relates to.
+ * Return invalid if not present */
+ quint32 primaryChannel(quint32 chIndex);
- struct ChannelActsOnData
- {
- QLCChannel *channel;
- int actsOnIndex;
-
- ChannelActsOnData(QLCChannel *newChannel, int newAcsOnIndex);
- };
-
- void updateActsOnChannel(QLCChannel *mainChannel, QLCChannel *actsOnChannel);
+ /** Return the channel index on which the given $chIndex acts on.
+ * Return invalid if not present */
+ quint32 channelActsOn(quint32 chIndex);
+ void setChannelActsOn(quint32 chIndex, quint32 actsOnIndex);
protected:
/** List of channels (pointers are not owned) */
QVector m_channels;
- /** List of acts on channels */
- QHash m_actsOnChannelsList;
+ /** Map of channel indices that act on other channels.
+ * These are stored as: */
+ QMap m_actsOnMap;
+
+ /** Map of channel indices that relate to some other primary channel.
+ * For example Pan Fine vs Pan, Red Fine vs Red, etc
+ * These are stored as: */
+ QMap m_secondaryMap;
quint32 m_masterIntensityChannel;
diff --git a/engine/src/qlcinputchannel.cpp b/engine/src/qlcinputchannel.cpp
index 9cb952c967..db99554c19 100644
--- a/engine/src/qlcinputchannel.cpp
+++ b/engine/src/qlcinputchannel.cpp
@@ -24,7 +24,6 @@
#include
#include "qlcinputchannel.h"
-#include "qlcinputprofile.h"
/****************************************************************************
* Initialization
@@ -35,8 +34,9 @@ QLCInputChannel::QLCInputChannel()
, m_movementType(Absolute)
, m_movementSensitivity(20)
, m_sendExtraPress(false)
- , m_lower(0)
- , m_upper(UCHAR_MAX)
+ , m_lowerValue(0)
+ , m_upperValue(UCHAR_MAX)
+ , m_lowerChannel(-1)
{
}
@@ -48,6 +48,7 @@ QLCInputChannel *QLCInputChannel::createCopy()
copy->setMovementType(this->movementType());
copy->setMovementSensitivity(this->movementSensitivity());
copy->setSendExtraPress(this->sendExtraPress());
+ copy->setLowerChannel(this->lowerChannel());
copy->setRange(this->lowerValue(), this->upperValue());
return copy;
@@ -63,11 +64,16 @@ QLCInputChannel::~QLCInputChannel()
void QLCInputChannel::setType(Type type)
{
+ if (type == m_type)
+ return;
+
m_type = type;
if (type == Encoder)
m_movementSensitivity = 1;
else
m_movementSensitivity = 20;
+
+ emit typeChanged();
}
QLCInputChannel::Type QLCInputChannel::type() const
@@ -95,7 +101,12 @@ QString QLCInputChannel::typeToString(Type type)
return KXMLQLCInputChannelPageSet;
default:
return KXMLQLCInputChannelNone;
- }
+ }
+}
+
+QString QLCInputChannel::typeString()
+{
+ return typeToString(type());
}
QLCInputChannel::Type QLCInputChannel::stringToType(const QString& type)
@@ -133,17 +144,7 @@ QStringList QLCInputChannel::types()
QIcon QLCInputChannel::typeToIcon(Type type)
{
- switch (type)
- {
- case Button: return QIcon(":/button.png");
- case Knob: return QIcon(":/knob.png");
- case Encoder: return QIcon(":/knob.png");
- case Slider: return QIcon(":/slider.png");
- case PrevPage: return QIcon(":/forward.png");
- case NextPage: return QIcon(":/back.png");
- case PageSet: return QIcon(":/star.png");
- default: return QIcon();
- }
+ return QIcon(iconResource(type));
}
QIcon QLCInputChannel::stringToIcon(const QString& str)
@@ -151,6 +152,26 @@ QIcon QLCInputChannel::stringToIcon(const QString& str)
return typeToIcon(stringToType(str));
}
+QString QLCInputChannel::iconResource(Type type, bool svg)
+{
+ QString prefix = svg ? "qrc" : "";
+ QString ext = svg ? "svg" : "png";
+
+ switch(type)
+ {
+ case Button: return QString("%1:/button.%2").arg(prefix, ext);
+ case Knob: return QString("%1:/knob.%2").arg(prefix, ext);
+ case Encoder: return QString("%1:/knob.%2").arg(prefix, ext);
+ case Slider: return QString("%1:/slider.%2").arg(prefix, ext);
+ case PrevPage: return QString("%1:/forward.%2").arg(prefix, ext);
+ case NextPage: return QString("%1:/back.%2").arg(prefix, ext);
+ case PageSet: return QString("%1:/star.%2").arg(prefix, ext);
+ default: return QString();
+ }
+
+ return QString("%1:/other.%2").arg(prefix, ext);
+}
+
QIcon QLCInputChannel::icon() const
{
return typeToIcon(type());
@@ -162,7 +183,12 @@ QIcon QLCInputChannel::icon() const
void QLCInputChannel::setName(const QString& name)
{
+ if (name == m_name)
+ return;
+
m_name = name;
+
+ emit nameChanged();
}
QString QLCInputChannel::name() const
@@ -200,7 +226,11 @@ void QLCInputChannel::setMovementSensitivity(int value)
void QLCInputChannel::setSendExtraPress(bool enable)
{
+ if (enable == m_sendExtraPress)
+ return;
+
m_sendExtraPress = enable;
+ emit sendExtraPressChanged();
}
bool QLCInputChannel::sendExtraPress() const
@@ -210,18 +240,50 @@ bool QLCInputChannel::sendExtraPress() const
void QLCInputChannel::setRange(uchar lower, uchar upper)
{
- m_lower = lower;
- m_upper = upper;
+ setLowerValue(lower);
+ setUpperValue(upper);
}
uchar QLCInputChannel::lowerValue() const
{
- return m_lower;
+ return m_lowerValue;
+}
+
+void QLCInputChannel::setLowerValue(const uchar value)
+{
+ if (value == m_lowerValue)
+ return;
+
+ m_lowerValue = value;
+ emit lowerValueChanged();
}
uchar QLCInputChannel::upperValue() const
{
- return m_upper;
+ return m_upperValue;
+}
+
+void QLCInputChannel::setUpperValue(const uchar value)
+{
+ if (value == m_upperValue)
+ return;
+
+ m_upperValue = value;
+ emit upperValueChanged();
+}
+
+int QLCInputChannel::lowerChannel() const
+{
+ return m_lowerChannel;
+}
+
+void QLCInputChannel::setLowerChannel(const int channel)
+{
+ if (channel == m_lowerChannel)
+ return;
+
+ m_lowerChannel = channel;
+ emit midiChannelChanged();
}
/****************************************************************************
@@ -259,16 +321,21 @@ bool QLCInputChannel::loadXML(QXmlStreamReader &root)
if (root.readElementText() == KXMLQLCInputChannelRelative)
setMovementType(Relative);
}
- else if (root.name() == KXMLQLCInputChannelFeedbacks)
+ else if (root.name() == KXMLQLCInputChannelFeedback)
{
+ QXmlStreamAttributes attrs = root.attributes();
uchar min = 0, max = UCHAR_MAX;
+ int fbChannel = -1;
- if (root.attributes().hasAttribute(KXMLQLCInputChannelLowerValue))
- min = uchar(root.attributes().value(KXMLQLCInputChannelLowerValue).toString().toUInt());
- if (root.attributes().hasAttribute(KXMLQLCInputChannelUpperValue))
- max = uchar(root.attributes().value(KXMLQLCInputChannelUpperValue).toString().toUInt());
+ if (attrs.hasAttribute(KXMLQLCInputChannelLowerValue))
+ min = uchar(attrs.value(KXMLQLCInputChannelLowerValue).toString().toUInt());
+ if (attrs.hasAttribute(KXMLQLCInputChannelUpperValue))
+ max = uchar(attrs.value(KXMLQLCInputChannelUpperValue).toString().toUInt());
+ if (attrs.hasAttribute(KXMLQLCInputChannelMidiChannel))
+ fbChannel = attrs.value(KXMLQLCInputChannelMidiChannel).toInt();
setRange(min, max);
+ setLowerChannel(fbChannel);
root.skipCurrentElement();
}
else
@@ -311,11 +378,13 @@ bool QLCInputChannel::saveXML(QXmlStreamWriter *doc, quint32 channelNumber) cons
}
else if (type() == Button && (lowerValue() != 0 || upperValue() != UCHAR_MAX))
{
- doc->writeStartElement(KXMLQLCInputChannelFeedbacks);
+ doc->writeStartElement(KXMLQLCInputChannelFeedback);
if (lowerValue() != 0)
doc->writeAttribute(KXMLQLCInputChannelLowerValue, QString::number(lowerValue()));
if (upperValue() != UCHAR_MAX)
doc->writeAttribute(KXMLQLCInputChannelUpperValue, QString::number(upperValue()));
+ if (lowerChannel() != -1)
+ doc->writeAttribute(KXMLQLCInputChannelMidiChannel, QString::number(lowerChannel()));
doc->writeEndElement();
}
diff --git a/engine/src/qlcinputchannel.h b/engine/src/qlcinputchannel.h
index 91ffb06594..88dad0bc73 100644
--- a/engine/src/qlcinputchannel.h
+++ b/engine/src/qlcinputchannel.h
@@ -21,6 +21,7 @@
#define QLCINPUTCHANNEL_H
#include
+#include
class QXmlStreamWriter;
class QXmlStreamReader;
@@ -31,32 +32,41 @@ class QString;
* @{
*/
-#define KXMLQLCInputChannel QString("Channel")
-#define KXMLQLCInputChannelName QString("Name")
-#define KXMLQLCInputChannelType QString("Type")
-#define KXMLQLCInputChannelNumber QString("Number")
-#define KXMLQLCInputChannelSlider QString("Slider")
-#define KXMLQLCInputChannelKnob QString("Knob")
-#define KXMLQLCInputChannelEncoder QString("Encoder")
-#define KXMLQLCInputChannelButton QString("Button")
-#define KXMLQLCInputChannelPageUp QString("Next Page")
-#define KXMLQLCInputChannelPageDown QString("Previous Page")
-#define KXMLQLCInputChannelPageSet QString("Page Set")
-#define KXMLQLCInputChannelNone QString("None")
-#define KXMLQLCInputChannelMovement QString("Movement")
-#define KXMLQLCInputChannelRelative QString("Relative")
-#define KXMLQLCInputChannelSensitivity QString("Sensitivity")
-#define KXMLQLCInputChannelExtraPress QString("ExtraPress")
-#define KXMLQLCInputChannelFeedbacks QString("Feedbacks")
-#define KXMLQLCInputChannelLowerValue QString("LowerValue")
-#define KXMLQLCInputChannelUpperValue QString("UpperValue")
+#define KXMLQLCInputChannel QString("Channel")
+#define KXMLQLCInputChannelName QString("Name")
+#define KXMLQLCInputChannelType QString("Type")
+#define KXMLQLCInputChannelNumber QString("Number")
+#define KXMLQLCInputChannelSlider QString("Slider")
+#define KXMLQLCInputChannelKnob QString("Knob")
+#define KXMLQLCInputChannelEncoder QString("Encoder")
+#define KXMLQLCInputChannelButton QString("Button")
+#define KXMLQLCInputChannelPageUp QString("Next Page")
+#define KXMLQLCInputChannelPageDown QString("Previous Page")
+#define KXMLQLCInputChannelPageSet QString("Page Set")
+#define KXMLQLCInputChannelNone QString("None")
+#define KXMLQLCInputChannelMovement QString("Movement")
+#define KXMLQLCInputChannelRelative QString("Relative")
+#define KXMLQLCInputChannelSensitivity QString("Sensitivity")
+#define KXMLQLCInputChannelExtraPress QString("ExtraPress")
+#define KXMLQLCInputChannelFeedback QString("Feedback")
+#define KXMLQLCInputChannelLowerValue QString("LowerValue")
+#define KXMLQLCInputChannelUpperValue QString("UpperValue")
+#define KXMLQLCInputChannelMidiChannel QString("MidiChannel")
class QLCInputChannel : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(QLCInputChannel)
- Q_PROPERTY(Type type READ type CONSTANT)
+ Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged FINAL)
+ Q_PROPERTY(QString typeString READ typeString CONSTANT)
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
+
+ Q_PROPERTY(bool sendExtraPress READ sendExtraPress WRITE setSendExtraPress NOTIFY sendExtraPressChanged FINAL)
+ Q_PROPERTY(MovementType movementType READ movementType WRITE setMovementType NOTIFY movementTypeChanged FINAL)
+ Q_PROPERTY(int movementSensitivity READ movementSensitivity WRITE setMovementSensitivity NOTIFY movementSensitivityChanged FINAL)
+ Q_PROPERTY(uchar lowerValue READ lowerValue WRITE setLowerValue NOTIFY lowerValueChanged FINAL)
+ Q_PROPERTY(uchar upperValue READ upperValue WRITE setUpperValue NOTIFY upperValueChanged FINAL)
/********************************************************************
* Initialization
@@ -99,6 +109,7 @@ class QLCInputChannel : public QObject
/** Convert the given QLCInputChannel::Type to a QString */
static QString typeToString(Type type);
+ QString typeString();
/** Convert the given QString to a QLCInputChannel::Type */
static Type stringToType(const QString& type);
@@ -112,8 +123,13 @@ class QLCInputChannel : public QObject
/** Get icon for a type */
static QIcon stringToIcon(const QString& str);
+ Q_INVOKABLE static QString iconResource(QLCInputChannel::Type type, bool svg = false);
+
QIcon icon() const;
+signals:
+ void typeChanged();
+
protected:
Type m_type;
@@ -127,6 +143,9 @@ class QLCInputChannel : public QObject
/** Get the name of this channel */
QString name() const;
+signals:
+ void nameChanged();
+
protected:
QString m_name;
@@ -135,10 +154,14 @@ class QLCInputChannel : public QObject
*********************************************************************/
public:
/** Movement behaviour */
- enum MovementType {
+ enum MovementType
+ {
Absolute = 0,
Relative = 1
};
+#if QT_VERSION >= 0x050500
+ Q_ENUM(MovementType)
+#endif
MovementType movementType() const;
void setMovementType(MovementType type);
@@ -146,6 +169,10 @@ class QLCInputChannel : public QObject
int movementSensitivity() const;
void setMovementSensitivity(int value);
+signals:
+ void movementTypeChanged();
+ void movementSensitivityChanged();
+
protected:
MovementType m_movementType;
int m_movementSensitivity;
@@ -156,13 +183,30 @@ class QLCInputChannel : public QObject
public:
void setSendExtraPress(bool enable);
bool sendExtraPress() const;
+
void setRange(uchar lower, uchar upper);
uchar lowerValue() const;
+ void setLowerValue(const uchar value);
+
uchar upperValue() const;
+ void setUpperValue(const uchar value);
+
+ int lowerChannel() const;
+ void setLowerChannel(const int channel);
+
+ int upperChannel() const;
+ void setUpperChannel(const int channel);
+
+signals:
+ void sendExtraPressChanged();
+ void lowerValueChanged();
+ void upperValueChanged();
+ void midiChannelChanged();
protected:
bool m_sendExtraPress;
- uchar m_lower, m_upper;
+ uchar m_lowerValue, m_upperValue;
+ int m_lowerChannel, m_upperChannel;
/********************************************************************
* Load & Save
diff --git a/engine/src/qlcinputfeedback.cpp b/engine/src/qlcinputfeedback.cpp
new file mode 100644
index 0000000000..48980fc2bb
--- /dev/null
+++ b/engine/src/qlcinputfeedback.cpp
@@ -0,0 +1,71 @@
+/*
+ Q Light Controller Plus
+ qlcinputfeedback.cpp
+
+ Copyright (c) Massimo Callegari
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#include "qlcinputfeedback.h"
+
+
+QLCInputFeedback::QLCInputFeedback()
+ : m_type(Undefinded)
+ , m_value(0)
+{
+}
+
+QLCInputFeedback *QLCInputFeedback::createCopy()
+{
+ QLCInputFeedback *copy = new QLCInputFeedback();
+ copy->setType(this->type());
+ copy->setValue(this->value());
+ copy->setExtraParams(this->extraParams());
+
+ return copy;
+}
+
+QLCInputFeedback::~QLCInputFeedback()
+{
+}
+
+QLCInputFeedback::FeedbackType QLCInputFeedback::type() const
+{
+ return m_type;
+}
+
+void QLCInputFeedback::setType(FeedbackType type)
+{
+ m_type = type;
+}
+
+uchar QLCInputFeedback::value() const
+{
+ return m_value;
+}
+
+void QLCInputFeedback::setValue(uchar value)
+{
+ m_value = value;
+}
+
+QVariant QLCInputFeedback::extraParams() const
+{
+ return m_extraParams;
+}
+
+void QLCInputFeedback::setExtraParams(QVariant params)
+{
+ m_extraParams = params;
+}
diff --git a/engine/src/qlcinputfeedback.h b/engine/src/qlcinputfeedback.h
new file mode 100644
index 0000000000..360ff98e56
--- /dev/null
+++ b/engine/src/qlcinputfeedback.h
@@ -0,0 +1,70 @@
+/*
+ Q Light Controller Plus
+ qlcinputfeedback.h
+
+ Copyright (c) Massimo Callegari
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+#ifndef QLCINPUTFEEDBACK_H
+#define QLCINPUTFEEDBACK_H
+
+#include
+#include
+
+class QLCInputFeedback : public QObject
+{
+ Q_OBJECT
+
+ /********************************************************************
+ * Initialization
+ ********************************************************************/
+public:
+ /** Standard constructor */
+ QLCInputFeedback();
+
+ /** Copy constructor */
+ QLCInputFeedback *createCopy();
+
+ /** Destructor */
+ virtual ~QLCInputFeedback();
+
+ /** Feedback type */
+ enum FeedbackType
+ {
+ Undefinded = -1,
+ LowerValue = 0,
+ UpperValue = 1,
+ MonitorValue = 2
+ };
+#if QT_VERSION >= 0x050500
+ Q_ENUM(FeedbackType)
+#endif
+
+ FeedbackType type() const;
+ void setType(FeedbackType type);
+
+ uchar value() const;
+ void setValue(uchar value);
+
+ QVariant extraParams() const;
+ void setExtraParams(QVariant params);
+
+protected:
+ FeedbackType m_type;
+ uchar m_value;
+ QVariant m_extraParams;
+};
+
+#endif /* QLCINPUTFEEDBACK_H */
diff --git a/engine/src/qlcinputprofile.cpp b/engine/src/qlcinputprofile.cpp
index 1511dce9ab..e0377de014 100644
--- a/engine/src/qlcinputprofile.cpp
+++ b/engine/src/qlcinputprofile.cpp
@@ -35,6 +35,9 @@
#define KXMLQLCInputProfileTypeDmx "DMX"
#define KXMLQLCInputProfileTypeEnttec "Enttec"
+#define KXMLQLCInputProfileValue "Value"
+#define KXMLQLCInputProfileLabel "Label"
+#define KXMLQLCInputProfileColorRGB "RGB"
/****************************************************************************
* Initialization
@@ -49,14 +52,46 @@ QLCInputProfile::QLCInputProfile()
{
}
-QLCInputProfile::QLCInputProfile(const QLCInputProfile& profile)
+QLCInputProfile::~QLCInputProfile()
{
- *this = profile;
+ destroyChannels();
}
-QLCInputProfile::~QLCInputProfile()
+QLCInputProfile *QLCInputProfile::createCopy()
{
- destroyChannels();
+ QLCInputProfile *copy = new QLCInputProfile();
+ copy->setManufacturer(this->manufacturer());
+ copy->setModel(this->model());
+ copy->setType(this->type());
+ copy->setPath(this->path());
+ copy->setMidiSendNoteOff(this->midiSendNoteOff());
+
+ /* Copy the other profile's channels */
+ QMapIterator it(this->channels());
+ while (it.hasNext() == true)
+ {
+ it.next();
+ copy->insertChannel(it.key(), it.value()->createCopy());
+ }
+
+ /* Copy the other profile's color table */
+ QMapIterator > it2(this->colorTable());
+ while (it2.hasNext() == true)
+ {
+ it2.next();
+ QPair lc = it2.value();
+ copy->addColor(it2.key(), lc.first, lc.second);
+ }
+
+ /* Copy the other profile's MIDI channel tabel */
+ QMapIterator it3(this->midiChannelTable());
+ while (it3.hasNext() == true)
+ {
+ it3.next();
+ copy->addMidiChannel(it3.key(), it3.value());
+ }
+
+ return copy;
}
QLCInputProfile& QLCInputProfile::operator=(const QLCInputProfile& profile)
@@ -75,12 +110,29 @@ QLCInputProfile& QLCInputProfile::operator=(const QLCInputProfile& profile)
destroyChannels();
/* Copy the other profile's channels */
- QMapIterator it(profile.m_channels);
+ QMapIterator it(profile.m_channels);
while (it.hasNext() == true)
{
it.next();
insertChannel(it.key(), it.value()->createCopy());
}
+
+ /* Copy the other profile's color table */
+ QMapIterator > it2(profile.m_colorTable);
+ while (it2.hasNext() == true)
+ {
+ it2.next();
+ QPair lc = it2.value();
+ addColor(it2.key(), lc.first, lc.second);
+ }
+
+ /* Copy the other profile's MIDI channel tabel */
+ QMapIterator it3(profile.m_midiChannelTable);
+ while (it3.hasNext() == true)
+ {
+ it3.next();
+ addMidiChannel(it3.key(), it3.value());
+ }
}
return *this;
@@ -115,6 +167,11 @@ QString QLCInputProfile::name() const
return QString("%1 %2").arg(m_manufacturer).arg(m_model);
}
+void QLCInputProfile::setPath(QString path)
+{
+ m_path = path;
+}
+
QString QLCInputProfile::path() const
{
return m_path;
@@ -280,6 +337,19 @@ QMap QLCInputProfile::channels() const
return m_channels;
}
+QVariant QLCInputProfile::channelExtraParams(const QLCInputChannel* channel) const
+{
+ if (channel == NULL)
+ return QVariant();
+
+ switch (m_type)
+ {
+ case OSC: return channel->name();
+ case MIDI: return channel->lowerChannel();
+ default: return QVariant();
+ }
+}
+
void QLCInputProfile::destroyChannels()
{
/* Delete existing channels but leave the pointers there */
@@ -291,6 +361,53 @@ void QLCInputProfile::destroyChannels()
m_channels.clear();
}
+bool QLCInputProfile::hasColorTable()
+{
+ return m_colorTable.isEmpty() ? false : true;
+}
+
+void QLCInputProfile::addColor(uchar value, QString label, QColor color)
+{
+ QPair lc;
+ lc.first = label;
+ lc.second = color;
+ m_colorTable.insert(value, lc);
+}
+
+void QLCInputProfile::removeColor(uchar value)
+{
+ m_colorTable.remove(value);
+}
+
+QMap > QLCInputProfile::colorTable()
+{
+ return m_colorTable;
+}
+
+/********************************************************************
+ * MIDI Channel table
+ ********************************************************************/
+
+bool QLCInputProfile::hasMidiChannelTable()
+{
+ return m_midiChannelTable.isEmpty() ? false : true;
+}
+
+void QLCInputProfile::addMidiChannel(uchar channel, QString label)
+{
+ m_midiChannelTable.insert(channel, label);
+}
+
+void QLCInputProfile::removeMidiChannel(uchar channel)
+{
+ m_midiChannelTable.remove(channel);
+}
+
+QMap QLCInputProfile::midiChannelTable()
+{
+ return m_midiChannelTable;
+}
+
/****************************************************************************
* Load & Save
****************************************************************************/
@@ -325,6 +442,65 @@ QLCInputProfile* QLCInputProfile::loader(const QString& path)
return profile;
}
+bool QLCInputProfile::loadColorTableXML(QXmlStreamReader &tableRoot)
+{
+ if (tableRoot.name() != KXMLQLCInputProfileColorTable)
+ {
+ qWarning() << Q_FUNC_INFO << "Color table node not found";
+ return false;
+ }
+
+ tableRoot.readNextStartElement();
+
+ do
+ {
+ if (tableRoot.name() == KXMLQLCInputProfileColor)
+ {
+ /* get value & color */
+ uchar value = tableRoot.attributes().value(KXMLQLCInputProfileValue).toInt();
+ QString label = tableRoot.attributes().value(KXMLQLCInputProfileLabel).toString();
+ QColor color = QColor(tableRoot.attributes().value(KXMLQLCInputProfileColorRGB).toString());
+ addColor(value, label, color);
+ }
+ else
+ {
+ qWarning() << Q_FUNC_INFO << "Unknown color table tag:" << tableRoot.name().toString();
+ }
+ tableRoot.skipCurrentElement();
+ } while (tableRoot.readNextStartElement());
+
+ return true;
+}
+
+bool QLCInputProfile::loadMidiChannelTableXML(QXmlStreamReader &tableRoot)
+{
+ if (tableRoot.name() != KXMLQLCInputProfileMidiChannelTable)
+ {
+ qWarning() << Q_FUNC_INFO << "MIDI channel table node not found";
+ return false;
+ }
+
+ tableRoot.readNextStartElement();
+
+ do
+ {
+ if (tableRoot.name() == KXMLQLCInputProfileMidiChannel)
+ {
+ /* get value & color */
+ uchar value = tableRoot.attributes().value(KXMLQLCInputProfileValue).toInt();
+ QString label = tableRoot.attributes().value(KXMLQLCInputProfileLabel).toString();
+ addMidiChannel(value, label);
+ }
+ else
+ {
+ qWarning() << Q_FUNC_INFO << "Unknown MIDI channel table tag:" << tableRoot.name().toString();
+ }
+ tableRoot.skipCurrentElement();
+ } while (tableRoot.readNextStartElement());
+
+ return true;
+}
+
bool QLCInputProfile::loadXML(QXmlStreamReader& doc)
{
if (doc.readNextStartElement() == false)
@@ -373,6 +549,19 @@ bool QLCInputProfile::loadXML(QXmlStreamReader& doc)
else
doc.skipCurrentElement();
}
+ else if (doc.name() == KXMLQLCInputProfileColorTable)
+ {
+ loadColorTableXML(doc);
+ }
+ else if (doc.name() == KXMLQLCInputProfileMidiChannelTable)
+ {
+ loadMidiChannelTableXML(doc);
+ }
+ else
+ {
+ qWarning() << Q_FUNC_INFO << "Unknown input profile tag:" << doc.name().toString();
+ doc.skipCurrentElement();
+ }
}
return true;
@@ -413,10 +602,48 @@ bool QLCInputProfile::saveXML(const QString& fileName)
it.value()->saveXML(&doc, it.key());
}
+ if (hasColorTable())
+ {
+ doc.writeStartElement(KXMLQLCInputProfileColorTable);
+
+ QMapIterator > it(m_colorTable);
+ while (it.hasNext() == true)
+ {
+ it.next();
+ QPair lc = it.value();
+ doc.writeStartElement(KXMLQLCInputProfileColor);
+ doc.writeAttribute(KXMLQLCInputProfileValue, QString::number(it.key()));
+ doc.writeAttribute(KXMLQLCInputProfileLabel, lc.first);
+ doc.writeAttribute(KXMLQLCInputProfileColorRGB, lc.second.name());
+ doc.writeEndElement();
+ }
+
+ doc.writeEndElement();
+ }
+
+ if (hasMidiChannelTable())
+ {
+ doc.writeStartElement(KXMLQLCInputProfileMidiChannelTable);
+
+ QMapIterator it(m_midiChannelTable);
+ while (it.hasNext() == true)
+ {
+ it.next();
+ doc.writeStartElement(KXMLQLCInputProfileMidiChannel);
+ doc.writeAttribute(KXMLQLCInputProfileValue, QString::number(it.key()));
+ doc.writeAttribute(KXMLQLCInputProfileLabel, it.value());
+ doc.writeEndElement();
+
+ }
+ doc.writeEndElement();
+ }
+
m_path = fileName;
+
/* End the document and close all the open elements */
doc.writeEndDocument();
file.close();
return true;
}
+
diff --git a/engine/src/qlcinputprofile.h b/engine/src/qlcinputprofile.h
index f58a0465fb..69c6aaa200 100644
--- a/engine/src/qlcinputprofile.h
+++ b/engine/src/qlcinputprofile.h
@@ -22,6 +22,7 @@
#include
#include
+#include
#include
#include
#include
@@ -39,9 +40,15 @@ class QXmlStreamReader;
#define KXMLQLCInputProfileModel QString("Model")
#define KXMLQLCInputProfileType QString("Type")
#define KXMLQLCInputProfileMidiSendNoteOff QString("MIDISendNoteOff")
+#define KXMLQLCInputProfileColorTable QString("ColorTable")
+#define KXMLQLCInputProfileColor QString("Color")
+#define KXMLQLCInputProfileMidiChannelTable QString("MidiChannelTable")
+#define KXMLQLCInputProfileMidiChannel QString("Channel")
-class QLCInputProfile
+class QLCInputProfile : public QObject
{
+ Q_OBJECT
+
/********************************************************************
* Initialization
********************************************************************/
@@ -49,12 +56,11 @@ class QLCInputProfile
/** Standard constructor */
QLCInputProfile();
- /** Copy constructor */
- QLCInputProfile(const QLCInputProfile& profile);
-
/** Destructor */
virtual ~QLCInputProfile();
+ QLCInputProfile *createCopy();
+
/** Assignment operator */
QLCInputProfile& operator=(const QLCInputProfile& profile);
@@ -73,17 +79,21 @@ class QLCInputProfile
/** Get the path where the profile is stored in. Don't use
this as a unique ID since this varies between platforms. */
+ void setPath(QString path);
QString path() const;
enum Type
{
- MIDI,
+ MIDI = 0,
OS2L,
OSC,
HID,
DMX,
Enttec,
};
+#if QT_VERSION >= 0x050500
+ Q_ENUM(Type)
+#endif
void setType(Type type);
@@ -159,7 +169,7 @@ class QLCInputProfile
* @param channel The number of the channel to get.
* @return A QLCInputChannel* or NULL if not found.
*/
- QLCInputChannel* channel(quint32 channel) const;
+ QLCInputChannel *channel(quint32 channel) const;
/**
* Get the channel number for the given input channel.
@@ -174,6 +184,12 @@ class QLCInputProfile
*/
QMap channels() const;
+ /**
+ * Retrieve additional parameters to be passed to plugins
+ * when sending feedback.
+ */
+ QVariant channelExtraParams(const QLCInputChannel *channel) const;
+
private:
/** Delete and remove all channels */
void destroyChannels();
@@ -183,6 +199,32 @@ class QLCInputProfile
QList because not all channels might be present. */
QMap m_channels;
+ /********************************************************************
+ * Color Translation Table
+ ********************************************************************/
+public:
+ bool hasColorTable();
+ void addColor(uchar value, QString label, QColor color);
+ void removeColor(uchar value);
+
+ QMap> colorTable();
+
+protected:
+ QMap> m_colorTable;
+
+ /********************************************************************
+ * MIDI Channel table
+ ********************************************************************/
+public:
+ bool hasMidiChannelTable();
+ void addMidiChannel(uchar channel, QString label);
+ void removeMidiChannel(uchar channel);
+
+ QMap midiChannelTable();
+
+protected:
+ QMap m_midiChannelTable;
+
/********************************************************************
* Load & Save
********************************************************************/
@@ -193,6 +235,12 @@ class QLCInputProfile
/** Save an input profile into a given file name */
bool saveXML(const QString& fileName);
+ /** Load an optional color table for RGB LED feedback */
+ bool loadColorTableXML(QXmlStreamReader &tableRoot);
+
+ /** Load an optional MIDI channel table */
+ bool loadMidiChannelTableXML(QXmlStreamReader &tableRoot);
+
/** Load an input profile from the given document */
bool loadXML(QXmlStreamReader &doc);
};
diff --git a/engine/src/qlcinputsource.cpp b/engine/src/qlcinputsource.cpp
index 05b9a5432c..ad7eb151a2 100644
--- a/engine/src/qlcinputsource.cpp
+++ b/engine/src/qlcinputsource.cpp
@@ -24,7 +24,6 @@
#include
#endif
-#include "qlcinputchannel.h"
#include "qlcinputsource.h"
#include "qlcmacros.h"
@@ -37,8 +36,6 @@ QLCInputSource::QLCInputSource(QThread *parent)
, m_universe(invalidUniverse)
, m_channel(invalidChannel)
, m_id(invalidID)
- , m_lower(0)
- , m_upper(255)
, m_workingMode(Absolute)
, m_sensitivity(20)
, m_emitExtraPressRelease(false)
@@ -46,14 +43,18 @@ QLCInputSource::QLCInputSource(QThread *parent)
, m_outputValue(0)
, m_running(false)
{
+ m_lower.setType(QLCInputFeedback::LowerValue);
+ m_lower.setValue(0);
+ m_upper.setType(QLCInputFeedback::UpperValue);
+ m_upper.setValue(UCHAR_MAX);
+ m_monitor.setType(QLCInputFeedback::MonitorValue);
+ m_monitor.setValue(UCHAR_MAX);
}
QLCInputSource::QLCInputSource(quint32 universe, quint32 channel, QThread *parent)
: QThread(parent)
, m_universe(universe)
, m_channel(channel)
- , m_lower(0)
- , m_upper(255)
, m_workingMode(Absolute)
, m_sensitivity(20)
, m_emitExtraPressRelease(false)
@@ -61,6 +62,12 @@ QLCInputSource::QLCInputSource(quint32 universe, quint32 channel, QThread *paren
, m_outputValue(0)
, m_running(false)
{
+ m_lower.setType(QLCInputFeedback::LowerValue);
+ m_lower.setValue(0);
+ m_upper.setType(QLCInputFeedback::UpperValue);
+ m_upper.setValue(UCHAR_MAX);
+ m_monitor.setType(QLCInputFeedback::MonitorValue);
+ m_monitor.setValue(UCHAR_MAX);
}
QLCInputSource::~QLCInputSource()
@@ -121,20 +128,66 @@ quint32 QLCInputSource::id() const
return m_id;
}
-void QLCInputSource::setRange(uchar lower, uchar upper)
+/*********************************************************************
+ * Custom feedback
+ *********************************************************************/
+
+uchar QLCInputSource::feedbackValue(QLCInputFeedback::FeedbackType type) const
+{
+ switch (type)
+ {
+ case QLCInputFeedback::LowerValue: return m_lower.value();
+ case QLCInputFeedback::UpperValue: return m_upper.value();
+ case QLCInputFeedback::MonitorValue: return m_monitor.value();
+ default: return 0;
+ }
+}
+
+void QLCInputSource::setFeedbackValue(QLCInputFeedback::FeedbackType type, uchar value)
{
- m_lower = lower;
- m_upper = upper;
+ switch (type)
+ {
+ case QLCInputFeedback::LowerValue:
+ m_lower.setValue(value);
+ break;
+ case QLCInputFeedback::UpperValue:
+ m_upper.setValue(value);
+ break;
+ case QLCInputFeedback::MonitorValue:
+ m_monitor.setValue(value);
+ break;
+ default:
+ break;
+ }
}
-uchar QLCInputSource::lowerValue() const
+QVariant QLCInputSource::feedbackExtraParams(QLCInputFeedback::FeedbackType type) const
{
- return m_lower;
+ switch (type)
+ {
+ case QLCInputFeedback::LowerValue: return m_lower.extraParams();
+ case QLCInputFeedback::UpperValue: return m_upper.extraParams();
+ case QLCInputFeedback::MonitorValue: return m_monitor.extraParams();
+ default: return 0;
+ }
}
-uchar QLCInputSource::upperValue() const
+void QLCInputSource::setFeedbackExtraParams(QLCInputFeedback::FeedbackType type, QVariant params)
{
- return m_upper;
+ switch (type)
+ {
+ case QLCInputFeedback::LowerValue:
+ m_lower.setExtraParams(params);
+ break;
+ case QLCInputFeedback::UpperValue:
+ m_upper.setExtraParams(params);
+ break;
+ case QLCInputFeedback::MonitorValue:
+ m_monitor.setExtraParams(params);
+ break;
+ default:
+ break;
+ }
}
/*********************************************************************
@@ -211,8 +264,8 @@ void QLCInputSource::updateInputValue(uchar value)
else if (m_emitExtraPressRelease == true)
{
locker.unlock();
- emit inputValueChanged(m_universe, m_channel, m_upper);
- emit inputValueChanged(m_universe, m_channel, m_lower);
+ emit inputValueChanged(m_universe, m_channel, m_upper.value());
+ emit inputValueChanged(m_universe, m_channel, m_lower.value());
}
else
m_inputValue = value;
diff --git a/engine/src/qlcinputsource.h b/engine/src/qlcinputsource.h
index 42b762a98d..b1b7b52829 100644
--- a/engine/src/qlcinputsource.h
+++ b/engine/src/qlcinputsource.h
@@ -20,9 +20,12 @@
#ifndef QLCINPUTSOURCE_H
#define QLCINPUTSOURCE_H
+#include
#include
#include
+#include "qlcinputfeedback.h"
+
/** @addtogroup engine Engine
* @{
*/
@@ -76,12 +79,20 @@ class QLCInputSource: public QThread
* Custom feedback
*********************************************************************/
public:
- void setRange(uchar lower, uchar upper);
- uchar lowerValue() const;
- uchar upperValue() const;
+ uchar feedbackValue(QLCInputFeedback::FeedbackType type) const;
+ void setFeedbackValue(QLCInputFeedback::FeedbackType type, uchar value);
+
+ /** Get/set specific plugins params.
+ * OSC: a string with the command path
+ * MIDI: a channel modifier
+ */
+ QVariant feedbackExtraParams(QLCInputFeedback::FeedbackType type) const;
+ void setFeedbackExtraParams(QLCInputFeedback::FeedbackType type, QVariant params);
protected:
- uchar m_lower, m_upper;
+ QLCInputFeedback m_lower;
+ QLCInputFeedback m_upper;
+ QLCInputFeedback m_monitor;
/*********************************************************************
* Working mode
diff --git a/engine/src/qlcpalette.cpp b/engine/src/qlcpalette.cpp
index cf7ebda7c8..14f4e85f3d 100644
--- a/engine/src/qlcpalette.cpp
+++ b/engine/src/qlcpalette.cpp
@@ -22,6 +22,7 @@
#include
#include
+#include "monitorproperties.h"
#include "qlcpalette.h"
#include "qlcchannel.h"
#include "scenevalue.h"
@@ -40,7 +41,7 @@ QLCPalette::QLCPalette(QLCPalette::PaletteType type, QObject *parent)
, m_id(QLCPalette::invalidId())
, m_type(type)
, m_fanningType(Flat)
- , m_fanningLayout(LeftToRight)
+ , m_fanningLayout(XAscending)
, m_fanningAmount(100)
{
}
@@ -243,10 +244,30 @@ QList QLCPalette::valuesFromFixtures(Doc *doc, QList fixtur
QList list;
int fxCount = fixtures.count();
- // nomralized progress in [ 0.0, 1.0 ] range
+ // normalized progress in [ 0.0, 1.0 ] range
qreal progress = 0.0;
int intFanValue = fanningValue().toInt();
FanningType fType = fanningType();
+ FanningLayout fLayout = fanningLayout();
+ MonitorProperties *mProps = doc->monitorProperties();
+
+ // sort the fixtures list based on selected layout
+ std::sort(fixtures.begin(), fixtures.end(),
+ [fLayout, mProps](quint32 a, quint32 b) {
+ QVector3D posA = mProps->fixturePosition(a, 0, 0);
+ QVector3D posB = mProps->fixturePosition(b, 0, 0);
+
+ switch(fLayout)
+ {
+ case XAscending: return posA.x() < posB.x();
+ case XDescending: return posB.x() < posA.x();
+ case YAscending: return posA.y() < posB.y();
+ case YDescending: return posB.y() < posA.y();
+ case ZAscending: return posA.z() < posB.z();
+ case ZDescending: return posB.z() < posA.z();
+ default: return false;
+ }
+ });
foreach (quint32 id, fixtures)
{
@@ -261,7 +282,8 @@ QList QLCPalette::valuesFromFixtures(Doc *doc, QList fixtur
case Dimmer:
{
int dValue = value().toInt();
- quint32 intCh = fixture->masterIntensityChannel();
+ quint32 intCh = fixture->type() == QLCFixtureDef::Dimmer ?
+ 0 : fixture->masterIntensityChannel();
if (intCh != QLCChannel::invalid())
{
@@ -371,7 +393,7 @@ QList QLCPalette::valuesFromFixtures(Doc *doc, QList fixtur
QList QLCPalette::valuesFromFixtureGroups(Doc *doc, QList groups)
{
- QList list;
+ QList fixturesList;
foreach (quint32 id, groups)
{
@@ -379,10 +401,10 @@ QList QLCPalette::valuesFromFixtureGroups(Doc *doc, QList