lmms-1.0.0/0000755000175000017500000000000012313663627011205 5ustar tobytobylmms-1.0.0/CMakeLists.txt0000644000175000017500000005354212313663627013756 0ustar tobytobyCMAKE_MINIMUM_REQUIRED(VERSION 2.4.5) PROJECT(lmms) SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules" ${CMAKE_MODULE_PATH}) IF(COMMAND CMAKE_POLICY) CMAKE_POLICY(SET CMP0005 NEW) CMAKE_POLICY(SET CMP0003 NEW) ENDIF(COMMAND CMAKE_POLICY) INCLUDE(AddFileDependencies) INCLUDE(CheckIncludeFiles) INCLUDE(FindPkgConfig) SET(VERSION_MAJOR "1") SET(VERSION_MINOR "0") SET(VERSION_PATCH "0") #SET(VERSION_SUFFIX "") SET(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}") IF(VERSION_SUFFIX) SET(VERSION "${VERSION}-${VERSION_SUFFIX}") ENDIF(VERSION_SUFFIX) INCLUDE(DetectMachine) OPTION(WANT_ALSA "Include ALSA (Advanced Linux Sound Architecture) support" ON) OPTION(WANT_CALF "Include CALF LADSPA plugins" ON) OPTION(WANT_CAPS "Include C* Audio Plugin Suite (LADSPA plugins)" ON) OPTION(WANT_CMT "Include Computer Music Toolkit LADSPA plugins" ON) OPTION(WANT_JACK "Include JACK (Jack Audio Connection Kit) support" ON) OPTION(WANT_OGGVORBIS "Include OGG/Vorbis support" ON) OPTION(WANT_PULSEAUDIO "Include PulseAudio support" ON) OPTION(WANT_PORTAUDIO "Include PortAudio support" ON) OPTION(WANT_SDL "Include SDL (Simple DirectMedia Layer) support" ON) OPTION(WANT_SF2 "Include SoundFont2 player plugin" ON) OPTION(WANT_STK "Include Stk (Synthesis Toolkit) support" ON) OPTION(WANT_SWH "Include Steve Harris's LADSPA plugins" ON) OPTION(WANT_TAP "Include Tom's Audio Processing LADSPA plugins" ON) OPTION(WANT_VST "Include VST support" ON) OPTION(WANT_VST_NOWINE "Include partial VST support (without wine)" OFF) OPTION(WANT_WINMM "Include WinMM MIDI support" OFF) IF(LMMS_BUILD_WIN32) SET(WANT_ALSA OFF) SET(WANT_JACK OFF) SET(WANT_PULSEAUDIO OFF) SET(WANT_SYSTEM_SR OFF) SET(WANT_WINMM ON) SET(LMMS_HAVE_WINMM TRUE) SET(STATUS_ALSA "") SET(STATUS_JACK "") SET(STATUS_PULSEAUDIO "") SET(STATUS_WINMM "OK") ELSE(LMMS_BUILD_WIN32) SET(STATUS_WINMM "") ENDIF(LMMS_BUILD_WIN32) CHECK_INCLUDE_FILES(stdint.h LMMS_HAVE_STDINT_H) CHECK_INCLUDE_FILES(stdlib.h LMMS_HAVE_STDLIB_H) CHECK_INCLUDE_FILES(pthread.h LMMS_HAVE_PTHREAD_H) CHECK_INCLUDE_FILES(semaphore.h LMMS_HAVE_SEMAPHORE_H) CHECK_INCLUDE_FILES(unistd.h LMMS_HAVE_UNISTD_H) CHECK_INCLUDE_FILES(sys/types.h LMMS_HAVE_SYS_TYPES_H) CHECK_INCLUDE_FILES(sys/ipc.h LMMS_HAVE_SYS_IPC_H) CHECK_INCLUDE_FILES(sys/shm.h LMMS_HAVE_SYS_SHM_H) CHECK_INCLUDE_FILES(sys/time.h LMMS_HAVE_SYS_TIME_H) CHECK_INCLUDE_FILES(sys/wait.h LMMS_HAVE_SYS_WAIT_H) CHECK_INCLUDE_FILES(sys/select.h LMMS_HAVE_SYS_SELECT_H) CHECK_INCLUDE_FILES(stdarg.h LMMS_HAVE_STDARG_H) CHECK_INCLUDE_FILES(signal.h LMMS_HAVE_SIGNAL_H) CHECK_INCLUDE_FILES(sched.h LMMS_HAVE_SCHED_H) CHECK_INCLUDE_FILES(sys/soundcard.h LMMS_HAVE_SYS_SOUNDCARD_H) CHECK_INCLUDE_FILES(soundcard.h LMMS_HAVE_SOUNDCARD_H) CHECK_INCLUDE_FILES(fcntl.h LMMS_HAVE_FCNTL_H) CHECK_INCLUDE_FILES(sys/ioctl.h LMMS_HAVE_SYS_IOCTL_H) CHECK_INCLUDE_FILES(ctype.h LMMS_HAVE_CTYPE_H) CHECK_INCLUDE_FILES(string.h LMMS_HAVE_STRING_H) CHECK_INCLUDE_FILES(process.h LMMS_HAVE_PROCESS_H) CHECK_INCLUDE_FILES(locale.h LMMS_HAVE_LOCALE_H) LIST(APPEND CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}") # check for Qt4 SET(QT_MIN_VERSION "4.3.0" COMPONENTS QtCore QtGui QtXml) FIND_PACKAGE(Qt4 REQUIRED) SET(QT_USE_QTXML 1) EXEC_PROGRAM(${QT_QMAKE_EXECUTABLE} ARGS "-query QT_INSTALL_TRANSLATIONS" OUTPUT_VARIABLE QT_TRANSLATIONS_DIR) IF(WIN32) SET(QT_TRANSLATIONS_DIR "${MINGW_PREFIX}/share/qt4/translations/") ENDIF(WIN32) IF(EXISTS "${QT_TRANSLATIONS_DIR}") MESSAGE("-- Found Qt translations in ${QT_TRANSLATIONS_DIR}") ADD_DEFINITIONS(-D'QT_TRANSLATIONS_DIR="${QT_TRANSLATIONS_DIR}"') ENDIF(EXISTS "${QT_TRANSLATIONS_DIR}") IF(NOT WIN32) STRING(REPLACE "-DQT_DLL" "" QT_DEFINITIONS "${QT_DEFINITIONS}") ENDIF(NOT WIN32) INCLUDE("${QT_USE_FILE}") # check for libsndfile PKG_CHECK_MODULES(SNDFILE REQUIRED sndfile>=1.0.11) IF(NOT SNDFILE_FOUND) MESSAGE(FATAL_ERROR "LMMS requires libsndfile1 and libsndfile1-dev >= 1.0.11 - please install, remove CMakeCache.txt and try again!") ENDIF(NOT SNDFILE_FOUND) IF(WANT_CALF) SET(LMMS_HAVE_CALF TRUE) SET(STATUS_CALF "OK") ELSE(WANT_CALF) SET(STATUS_CALF "not built as requested") ENDIF(WANT_CALF) IF(WANT_CAPS) SET(LMMS_HAVE_CAPS TRUE) SET(STATUS_CAPS "OK") ELSE(WANT_CAPS) SET(STATUS_CAPS "not built as requested") ENDIF(WANT_CAPS) IF(WANT_CMT) SET(LMMS_HAVE_CMT TRUE) SET(STATUS_CMT "OK") ELSE(WANT_CMT) SET(STATUS_CMT "not built as requested") ENDIF(WANT_CMT) IF(WANT_SWH) SET(LMMS_HAVE_SWH TRUE) SET(STATUS_SWH "OK") ELSE(WANT_SWH) SET(STATUS_SWH "not built as requested") ENDIF(WANT_SWH) IF(WANT_TAP) SET(LMMS_HAVE_TAP TRUE) SET(STATUS_TAP "OK") ELSE(WANT_TAP) SET(STATUS_TAP "not built as requested") ENDIF(WANT_TAP) # check for SDL IF(WANT_SDL) SET(SDL_BUILDING_LIBRARY TRUE) FIND_PACKAGE(SDL) IF(SDL_FOUND) SET(LMMS_HAVE_SDL TRUE) SET(STATUS_SDL "OK") ELSE(SDL_FOUND) SET(STATUS_SDL "not found, please install libsdl1.2-dev (or similiar) " "if you require SDL support") ENDIF(SDL_FOUND) ENDIF(WANT_SDL) IF(NOT LMMS_HAVE_SDL) SET(SDL_INCLUDE_DIR "") ELSE(NOT LMMS_HAVE_SDL) IF(NOT SDL_INCLUDE_DIR) SET(SDL_INCLUDE_DIR "${CMAKE_FIND_ROOT_PATH}/include") ENDIF(NOT SDL_INCLUDE_DIR) ENDIF(NOT LMMS_HAVE_SDL) # check for Stk IF(WANT_STK) FIND_PACKAGE(STK) IF(STK_FOUND) SET(LMMS_HAVE_STK TRUE) SET(STATUS_STK "OK") ELSE(STK_FOUND) SET(STK_INCLUDE_DIR "") SET(STATUS_STK "not found, please install libstk0-dev (or similiar) " "if you require the Mallets instrument") ENDIF(STK_FOUND) ENDIF(WANT_STK) # check for PortAudio IF(WANT_PORTAUDIO) FIND_PACKAGE(Portaudio) IF(PORTAUDIO_FOUND) SET(LMMS_HAVE_PORTAUDIO TRUE) SET(STATUS_PORTAUDIO "OK") ELSE(PORTAUDIO_FOUND) SET(STATUS_PORTAUDIO "not found, please install portaudio19-dev (or similiar, version >= 1.9) " "if you require PortAudio support") ENDIF(PORTAUDIO_FOUND) ENDIF(WANT_PORTAUDIO) # check for PulseAudio IF(WANT_PULSEAUDIO) FIND_PACKAGE(PulseAudio) IF(PULSEAUDIO_FOUND) SET(LMMS_HAVE_PULSEAUDIO TRUE) SET(STATUS_PULSEAUDIO "OK") ELSE(PULSEAUDIO_FOUND) SET(STATUS_PULSEAUDIO "not found, please install libpulse-dev (or similiar) " "if you require PulseAudio support") ENDIF(PULSEAUDIO_FOUND) ENDIF(WANT_PULSEAUDIO) IF(NOT LMMS_HAVE_PULSEAUDIO) SET(PULSEAUDIO_INCLUDE_DIR "") SET(PULSEAUDIO_LIBRARIES "") ENDIF(NOT LMMS_HAVE_PULSEAUDIO) # check for OGG/Vorbis-libraries IF(WANT_OGGVORBIS) FIND_PACKAGE(OggVorbis) IF(OGGVORBIS_FOUND) SET(LMMS_HAVE_OGGVORBIS TRUE) SET(STATUS_OGGVORBIS "OK") ELSE(OGGVORBIS_FOUND) SET(STATUS_OGGVORBIS "not found, libogg-dev and libvorbis-dev (or similiar) " "is highly recommended") ENDIF(OGGVORBIS_FOUND) ENDIF(WANT_OGGVORBIS) # check whether to enable OSS-support IF(LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H) SET(LMMS_HAVE_OSS TRUE) SET(STATUS_OSS "OK") ELSE(LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H) SET(STATUS_OSS "") ENDIF(LMMS_HAVE_SOUNDCARD_H OR LMMS_HAVE_SYS_SOUNDCARD_H) # check for ALSA IF(WANT_ALSA) FIND_PACKAGE(Alsa) IF(ALSA_FOUND) SET(LMMS_HAVE_ALSA TRUE) SET(STATUS_ALSA "OK") ELSE(ALSA_FOUND) SET(STATUS_ALSA "not found, please install libasound2-dev (or similiar) " "if you require ALSA support") ENDIF(ALSA_FOUND) ENDIF(WANT_ALSA) IF(NOT LMMS_HAVE_ALSA) SET(ASOUND_LIBRARY "") ENDIF(NOT LMMS_HAVE_ALSA) # check for JACK IF(WANT_JACK) PKG_CHECK_MODULES(JACK jack>=0.77) IF(JACK_FOUND) SET(LMMS_HAVE_JACK TRUE) SET(STATUS_JACK "OK") ELSE(JACK_FOUND) SET(STATUS_JACK "not found, please install libjack0.100.0-dev (or similiar) " "if you require JACK support") ENDIF(JACK_FOUND) ENDIF(WANT_JACK) # check for FFTW3F-library PKG_CHECK_MODULES(FFTW3F REQUIRED fftw3f>=3.0.0) # check for Fluidsynth IF(WANT_SF2) PKG_CHECK_MODULES(FLUIDSYNTH fluidsynth>=1.0.7) IF(FLUIDSYNTH_FOUND) SET(LMMS_HAVE_FLUIDSYNTH TRUE) SET(STATUS_FLUIDSYNTH "OK") ELSE(FLUIDSYNTH_FOUND) SET(STATUS_FLUIDSYNTH "not found, libfluidsynth-dev (or similiar)" "is highly recommended") ENDIF(FLUIDSYNTH_FOUND) ENDIF(WANT_SF2) # check for pthreads IF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE) FIND_PACKAGE(Threads) ENDIF(LMMS_BUILD_LINUX OR LMMS_BUILD_APPLE) # check for WINE IF(WANT_VST) FIND_PACKAGE(Wine) IF(WINE_FOUND) SET(LMMS_SUPPORT_VST TRUE) SET(STATUS_VST "OK") ELSE(WINE_FOUND) SET(STATUS_VST "not found, please install (lib)wine-dev (or similiar) - 64 bit systems additionally need gcc-multilib and g++-multilib") ENDIF(WINE_FOUND) ENDIF(WANT_VST) IF(LMMS_BUILD_WIN32) SET(LMMS_SUPPORT_VST TRUE) SET(STATUS_VST "OK") ENDIF(LMMS_BUILD_WIN32) # check for libsamplerate PKG_CHECK_MODULES(SAMPLERATE REQUIRED samplerate>=0.1.8) CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/lmmsconfig.h.in" "${CMAKE_BINARY_DIR}/lmmsconfig.h") CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/lmmsversion.h.in" "${CMAKE_BINARY_DIR}/lmmsversion.h") CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/lmms.rc.in" "${CMAKE_BINARY_DIR}/lmms.rc") CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/plugins/zynaddsubfx/zynaddsubfx.rc.in" "${CMAKE_BINARY_DIR}/plugins/zynaddsubfx/zynaddsubfx.rc") # set compiler flags SET(WERROR_FLAGS "-Wall -Werror -Werror=unused-function -Wno-sign-compare -Wno-strict-overflow") SET(CMAKE_C_FLAGS "-O2 -g ${WERROR_FLAGS} ${CMAKE_C_FLAGS}") SET(CMAKE_CXX_FLAGS "-O2 -g -fno-exceptions ${WERROR_FLAGS} ${CMAKE_CXX_FLAGS}") set(CMAKE_C_FLAGS_DEBUG "-DLMMS_DEBUG") # people simply updating git will still have this and mess up build with it FILE(REMOVE include/lmmsconfig.h) FILE(GLOB lmms_INCLUDES "${CMAKE_SOURCE_DIR}/include/*.h") FILE(GLOB lmms_UI "${CMAKE_SOURCE_DIR}/src/gui/dialogs/*.ui" "${CMAKE_SOURCE_DIR}/src/gui/Forms/*.ui") FILE(GLOB_RECURSE lmms_SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp") SET(lmms_MOC ${lmms_INCLUDES}) # Get list of all committers from git history, ordered by number of commits FIND_PACKAGE(Git) IF(GIT_FOUND) SET(CONTRIBUTORS "${CMAKE_BINARY_DIR}/CONTRIBUTORS") EXECUTE_PROCESS( COMMAND "${GIT_EXECUTABLE}" shortlog -sne COMMAND cut -c8- OUTPUT_FILE "${CONTRIBUTORS}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 1) ENDIF(GIT_FOUND) SET(lmms_EMBEDDED_RESOURCES "${CMAKE_SOURCE_DIR}/AUTHORS" "${CMAKE_SOURCE_DIR}/COPYING" "${CONTRIBUTORS}") QT4_WRAP_CPP(lmms_MOC_out ${lmms_MOC} OPTIONS -nw) QT4_WRAP_UI(lmms_UI_out ${lmms_UI}) # embedded resources stuff IF(WIN32 OR WIN64) # compile buildtools native SET(BIN2RES_CPP "${CMAKE_SOURCE_DIR}/buildtools/bin2res.cpp") SET(BIN2RES "${CMAKE_BINARY_DIR}/bin2res") ADD_CUSTOM_COMMAND(OUTPUT "${BIN2RES}" COMMAND g++ ARGS "\"${BIN2RES_CPP}\"" -o "\"${BIN2RES}\"" DEPENDS "${BIN2RES_CPP}") ELSE(WIN32 OR WIN64) ADD_EXECUTABLE(bin2res buildtools/bin2res.cpp) GET_TARGET_PROPERTY(BIN2RES bin2res LOCATION) ENDIF(WIN32 OR WIN64) SET(LMMS_ER_H "${CMAKE_CURRENT_BINARY_DIR}/embedded_resources.h") # we somehow have to make LMMS-binary depend on MOC-files ADD_FILE_DEPENDENCIES("${CMAKE_BINARY_DIR}/lmmsconfig.h" ${lmms_MOC_out}) ADD_CUSTOM_COMMAND(OUTPUT "${LMMS_ER_H}" COMMAND "${BIN2RES}" ARGS ${lmms_EMBEDDED_RESOURCES} > "\"${LMMS_ER_H}\"" DEPENDS "${BIN2RES}") IF(WIN32) SET(WINRC "${CMAKE_BINARY_DIR}/lmmsrc.obj") ADD_CUSTOM_COMMAND(OUTPUT "${WINRC}" COMMAND "${WINDRES}" "-I\"${CMAKE_SOURCE_DIR}\"" "-o\"${CMAKE_BINARY_DIR}/lmmsrc.obj\"" "-i\"${CMAKE_BINARY_DIR}/lmms.rc\"" DEPENDS "${CMAKE_BINARY_DIR}/lmms.rc") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes") ELSE(WIN32) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -DPIC") ENDIF(WIN32) # make sub-directories ADD_SUBDIRECTORY(plugins) ADD_SUBDIRECTORY(data) ADD_SUBDIRECTORY(doc) # # build LMMS-binary # IF(LMMS_BUILD_WIN32) SET(EXTRA_LIBRARIES "-lwinmm") ENDIF() # Paths relative to lmms executable FILE(RELATIVE_PATH LIB_DIR_RELATIVE "/${BIN_DIR}" "/${LIB_DIR}") FILE(RELATIVE_PATH PLUGIN_DIR_RELATIVE "/${BIN_DIR}" "/${PLUGIN_DIR}") ADD_DEFINITIONS(-D'LIB_DIR="${LIB_DIR_RELATIVE}/"' -D'PLUGIN_DIR="${PLUGIN_DIR_RELATIVE}/"' ${PULSEAUDIO_DEFINITIONS} ${PORTAUDIO_DEFINITIONS}) INCLUDE_DIRECTORIES("${CMAKE_BINARY_DIR}" "${CMAKE_BINARY_DIR}/include" "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/include" ${JACK_INCLUDE_DIRS} ${SAMPLERATE_INCLUDE_DIRS} ${SNDFILE_INCLUDE_DIRS}) IF(NOT ("${SDL_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${SDL_INCLUDE_DIR}") ENDIF() IF(NOT ("${PORTAUDIO_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${PORTAUDIO_INCLUDE_DIR}") ENDIF() IF(NOT ("${PULSEAUDIO_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${PULSEAUDIO_INCLUDE_DIR}") ENDIF() IF(NOT ("${OGGVORBIS_INCLUDE_DIR}" STREQUAL "")) INCLUDE_DIRECTORIES("${OGGVORBIS_INCLUDE_DIR}") ENDIF() ADD_CUSTOM_COMMAND(OUTPUT "${CMAKE_BINARY_DIR}/lmms.1.gz" COMMAND gzip -c "\"${CMAKE_SOURCE_DIR}/lmms.1\"" > "\"${CMAKE_BINARY_DIR}/lmms.1.gz\"" DEPENDS "${CMAKE_SOURCE_DIR}/lmms.1" COMMENT "Generating lmms.1.gz") ADD_EXECUTABLE(lmms ${lmms_SOURCES} ${lmms_INCLUDES} "${LMMS_ER_H}" ${lmms_UI_out} lmmsconfig.h lmmsversion.h "${WINRC}" "${CMAKE_BINARY_DIR}/lmms.1.gz") TARGET_LINK_LIBRARIES(lmms ${CMAKE_THREAD_LIBS_INIT} ${QT_LIBRARIES} ${ASOUND_LIBRARY} ${SDL_LIBRARY} ${PORTAUDIO_LIBRARIES} ${PULSEAUDIO_LIBRARIES} ${JACK_LIBRARIES} ${OGGVORBIS_LIBRARIES} ${SAMPLERATE_LIBRARIES} ${SNDFILE_LIBRARIES} ${EXTRA_LIBRARIES}) IF(LMMS_BUILD_WIN32) SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -mwindows") ADD_CUSTOM_COMMAND(TARGET lmms POST_BUILD COMMAND "${STRIP}" "\"${CMAKE_BINARY_DIR}/lmms.exe\"") INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}") INSTALL(FILES "${MINGW_PREFIX}/bin/QtCore4.dll" "${MINGW_PREFIX}/bin/QtGui4.dll" "${MINGW_PREFIX}/bin/QtXml4.dll" "${MINGW_PREFIX}/bin/libsamplerate-0.dll" "${MINGW_PREFIX}/bin/libsndfile-1.dll" "${MINGW_PREFIX}/bin/libvorbis-0.dll" "${MINGW_PREFIX}/bin/libvorbisenc-2.dll" "${MINGW_PREFIX}/bin/libvorbisfile-3.dll" "${MINGW_PREFIX}/bin/libogg-0.dll" "${MINGW_PREFIX}/bin/libfluidsynth.dll" "${MINGW_PREFIX}/bin/libfftw3f-3.dll" "${MINGW_PREFIX}/bin/libFLAC-8.dll" "${MINGW_PREFIX}/bin/libportaudio-2.dll" "${MINGW_PREFIX}/bin/libpng16-16.dll" "${MINGW_PREFIX}/bin/SDL.dll" "${MINGW_PREFIX}/bin/libglib-2.0-0.dll" "${MINGW_PREFIX}/bin/libgthread-2.0-0.dll" "${MINGW_PREFIX}/bin/zlib1.dll" DESTINATION .) ELSE(LMMS_BUILD_WIN32) IF(NOT LMMS_BUILD_APPLE) SET_TARGET_PROPERTIES(lmms PROPERTIES LINK_FLAGS "${LINK_FLAGS} -Wl,-E") ENDIF(NOT LMMS_BUILD_APPLE) INSTALL(TARGETS lmms RUNTIME DESTINATION "${BIN_DIR}") INSTALL(FILES "${CMAKE_BINARY_DIR}/lmms.1.gz" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/man/man1/" PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) ENDIF(LMMS_BUILD_WIN32) # # rules for building localizations # FILE(GLOB lmms_LOCALES data/locale/*.ts) SET(ts_targets "") SET(qm_targets "") FOREACH(_ts_file ${lmms_LOCALES}) STRING(REPLACE "${CMAKE_SOURCE_DIR}/data/locale/" "" _ts_target "${_ts_file}") STRING(REPLACE ".ts" ".qm" _qm_file "${_ts_file}") STRING(REPLACE ".ts" ".qm" _qm_target "${_ts_target}") ADD_CUSTOM_TARGET(${_ts_target} COMMAND "${QT_LUPDATE_EXECUTABLE}" -locations none -no-obsolete ${lmms_SOURCES} ${lmms_UI} `find "\"${CMAKE_SOURCE_DIR}/plugins/\"" -type f -name '*.cpp'` -ts "\"${_ts_file}\"") ADD_CUSTOM_TARGET(${_qm_target} COMMAND "${QT_LRELEASE_EXECUTABLE}" "\"${_ts_file}\"" -qm "\"${_qm_file}\"") LIST(APPEND ts_targets "${_ts_target}") LIST(APPEND qm_targets "${_qm_target}") ENDFOREACH(_ts_file ${lmms_LOCALES}) ADD_CUSTOM_TARGET(update-locales) FOREACH(_item ${ts_targets}) ADD_DEPENDENCIES(update-locales "${_item}") ENDFOREACH(_item ${ts_targets}) ADD_CUSTOM_TARGET(finalize-locales) FOREACH(_item ${qm_targets}) ADD_DEPENDENCIES(finalize-locales "${_item}") ENDFOREACH(_item ${qm_targets}) # install headers IF(LMMS_BUILD_LINUX) INSTALL(FILES ${lmms_INCLUDES} "${CMAKE_BINARY_DIR}/lmmsconfig.h" "${CMAKE_BINARY_DIR}/lmmsversion.h" "${CMAKE_SOURCE_DIR}/src/gui/embed.cpp" DESTINATION "${CMAKE_INSTALL_PREFIX}/include/lmms/") ENDIF(LMMS_BUILD_LINUX) # package ZynAddSubFX into win32 build IF(LMMS_BUILD_WIN32) IF(EXISTS "${CMAKE_SOURCE_DIR}/extras") ADD_SUBDIRECTORY("${CMAKE_SOURCE_DIR}/extras/data/presets") FILE(GLOB ZASF_BINARIES "${CMAKE_SOURCE_DIR}/extras/plugins/zynaddsubfx/zynaddsubfx.dll" "${CMAKE_SOURCE_DIR}/extras/plugins/zynaddsubfx/remote_zynaddsubfx.exe") INSTALL(FILES "${ZASF_BINARIES}" DESTINATION "${PLUGIN_DIR}") ENDIF(EXISTS "${CMAKE_SOURCE_DIR}/extras") ENDIF(LMMS_BUILD_WIN32) # # add distclean-target # ADD_CUSTOM_TARGET(distclean COMMAND make clean COMMAND rm -rf `find -name cmake_install.cmake` `find -name Makefile` `find -type d -name CMakeFiles` CMakeCache.txt lmmsconfig.h lmms.1.gz) # # add tarball-target # SET(TMP "lmms-${VERSION}") ADD_CUSTOM_TARGET(dist COMMAND make clean COMMAND rm -rf "${TMP}" COMMAND mkdir -p "${TMP}" COMMAND cp AUTHORS build_mingw32 build_mingw64 CMakeLists.txt configure COPYING INSTALL lmms.1 lmms.rc.in lmms.spec.in lmmsconfig.h.in lmmsversion.h.in README TODO "${TMP}" COMMAND cp -r buildtools cmake data doc include plugins src "${TMP}" COMMAND rm -rf `find "${TMP}" -name cmake_install.cmake` `find "${TMP}" -name Makefile` `find "${TMP}" -type d -name CMakeFiles` "${TMP}/CMakeCache.txt" COMMAND tar cjf lmms-${VERSION}-src.tar.bz2 "${TMP}" COMMAND rm -rf "${TMP}") SET_DIRECTORY_PROPERTIES(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES "${LMMS_ER_H} ${lmms_MOC_out} ${lmms_UI_out} lmmsconfig.h lmms.1.gz") # # display configuration information # MESSAGE("\n" "Installation Summary\n" "--------------------\n" "* Install Directory : ${CMAKE_INSTALL_PREFIX}\n" ) MESSAGE( "Supported audio interfaces\n" "--------------------------\n" "* ALSA : ${STATUS_ALSA}\n" "* JACK : ${STATUS_JACK}\n" "* OSS : ${STATUS_OSS}\n" "* PortAudio : ${STATUS_PORTAUDIO}\n" "* PulseAudio : ${STATUS_PULSEAUDIO}\n" "* SDL : ${STATUS_SDL}\n" ) MESSAGE( "Supported MIDI interfaces\n" "-------------------------\n" "* ALSA : ${STATUS_ALSA}\n" "* OSS : ${STATUS_OSS}\n" "* WinMM : ${STATUS_WINMM}\n" ) MESSAGE( "Supported file formats for project export\n" "-----------------------------------------\n" "* WAVE : OK\n" "* OGG/VORBIS : ${STATUS_OGGVORBIS}\n" ) MESSAGE( "Optional plugins\n" "----------------\n" "* SoundFont2 player : ${STATUS_FLUIDSYNTH}\n" "* Stk Mallets : ${STATUS_STK}\n" "* VST-instrument hoster : ${STATUS_VST}\n" "* VST-effect hoster : ${STATUS_VST}\n" "* CALF LADSPA plugins : ${STATUS_CALF}\n" "* CAPS LADSPA plugins : ${STATUS_CAPS}\n" "* CMT LADSPA plugins : ${STATUS_CMT}\n" "* TAP LADSPA plugins : ${STATUS_TAP}\n" "* SWH LADSPA plugins : ${STATUS_SWH}\n" ) MESSAGE( "\n" "-----------------------------------------------------------------\n" "IMPORTANT:\n" "after installing missing packages, remove CMakeCache.txt before\n" "running cmake again!\n" "-----------------------------------------------------------------\n" "\n\n") INCLUDE(InstallRequiredSystemLibraries) SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "LMMS - easy music production for everyone!") SET(CPACK_PACKAGE_VENDOR "LMMS Developers") IF(LMMS_BUILD_APPLE) CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/README" "${CMAKE_BINARY_DIR}/README.txt" COPYONLY) CONFIGURE_FILE("${CMAKE_SOURCE_DIR}/COPYING" "${CMAKE_BINARY_DIR}/COPYING.txt" COPYONLY) SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_BINARY_DIR}/README.txt") SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_BINARY_DIR}/COPYING.txt") ELSE(LMMS_BUILD_APPLE) SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README") SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/COPYING") ENDIF(LMMS_BUILD_APPLE) SET(CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") SET(CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") SET(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}") IF(VERSION_SUFFIX) SET(CPACK_PACKAGE_VERSION_PATCH "${VERSION_PATCH}-${VERSION_SUFFIX}") ENDIF(VERSION_SUFFIX) SET(CPACK_PACKAGE_INSTALL_DIRECTORY "LMMS") IF(WIN32) SET(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/data/nsis_branding.bmp") SET(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/data/lmms.ico") SET(CPACK_NSIS_INSTALLED_ICON_NAME "lmms.exe") SET(CPACK_NSIS_DISPLAY_NAME "LMMS ${VERSION}") SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\lmms.sourceforge.net") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\lmms.sourceforge.net") SET(CPACK_NSIS_CONTACT "lmms-devel@lists.sourceforge.net") SET(CPACK_PACKAGE_EXECUTABLES "lmms.exe;LMMS") SET(CPACK_NSIS_MENU_LINKS "lmms.exe;LMMS") SET(CPACK_NSIS_DEFINES "!include ${CMAKE_SOURCE_DIR}/cmake/nsis/FileAssociation.nsh") SET(CPACK_PACKAGE_FILE_NAME "lmms-${VERSION}-win32") SET(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " \\\${registerExtension} \\\"$INSTDIR\\\\lmms.exe\\\" \\\".mmp\\\" \\\"LMMS Project\\\" \\\${registerExtension} \\\"$INSTDIR\\\\lmms.exe\\\" \\\".mmpz\\\" \\\"LMMS Project (compressed)\\\" ") SET(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " \\\${unregisterExtension} \\\".mmp\\\" \\\"LMMS Project\\\" \\\${unregisterExtension} \\\".mmpz\\\" \\\"LMMS Project (compressed)\\\" ") ELSE(WIN32) SET(CPACK_STRIP_FILES "bin/lmms;${PLUGIN_DIR}/*.so") SET(CPACK_PACKAGE_EXECUTABLES "lmms" "LMMS binary") ENDIF(WIN32) IF(WIN64) SET(CPACK_PACKAGE_FILE_NAME "lmms-${VERSION}-win64") SET(CPACK_NSIS_DEFINES "${CPACK_NSIS_DEFINES} InstallDir \\\"\\\$PROGRAMFILES64\\\\${CPACK_PACKAGE_INSTALL_DIRECTORY}\\\"") ENDIF(WIN64) SET(MACOSX_BUNDLE_ICON_FILE "${CMAKE_SOURCE_DIR}/data/themes/default/icon.png") SET(MACOSX_BUNDLE_GUI_IDENTIFIER "LMMS") SET(MACOSX_BUNDLE_LONG_VERSION_STRING "${VERSION}") SET(MACOSX_BUNDLE_BUNDLE_NAME "LMMS") SET(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VERSION}") SET(MACOSX_BUNDLE_BUNDLE_VERSION "${VERSION}") SET(MACOSX_BUNDLE_COPYRIGHT "Tobias Doerffel, 2008-2010") SET(CPACK_SOURCE_GENERATOR "TBZ2") SET(CPACK_SOURCE_PACKAGE_FILE_NAME "lmms-${VERSION}") INCLUDE(CPack) lmms-1.0.0/TODO0000644000175000017500000000270312313663627011677 0ustar tobytobyVersion 0.4.x ============= - save tco-settings in trackContentWidget::saveSettings() etc. instead of track::... - resample sample-track-tcos when exporting at different samplerate - message to user when importing unsupported MIDI-file (track-count = 0) - piano roll: mouse cursor isn't updated correctly in selection mode (from resizing note edit area) - when you add vestige, have it automatically pop the find VST plugin dialog - try to make vestige-plugin-dlls relative - select all MIDI devices by default when you bring up the "connect to controller" window and wait for first event - then uncheck all other MIDI devices that no events were detected from - load asdlol.mmpz. if you render it without playing it, or if you play it the first time, you hear unwanted artifacts. (solution: apply automation before playing) - autosave every 30s (configurable!) and offer recovery at startup after crash - speed up painting of sampleTCO - do not process effects when playing frozen patterns - copy-pasted automation patterns have to be manually linked back to their knob for some reason - improve TrackLabelButton: split 80%-20% (80%=name, 20%=button showing a popup menu with track operations, make the midi input a top-level menu item) - when you click and drag a mixer bar, it doesn't click and drag, it sets absolutely. this is annoying See TODO file in master branch and/or the TODO list in the Wiki for details regarding the development series. lmms-1.0.0/lmms.10000644000175000017500000000553412313663627012246 0ustar tobytoby.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH LMMS 1 "September 28, 2006" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME lmms \- software for easy music production .SH SYNOPSIS .B lmms .RB "[ \--\fBrender\fP \fIfile\fP ] [options]" .br .B lmms .RB "[ \--\fBupgrade\fP \fIin\fP \fIout\fP ]" .br .B lmms .RB "[ \--\fBdump\fP \fIin\fP ]" .br .B lmms .RI "[ file ]" .SH DESCRIPTION .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. .B LMMS LMMS is a free cross-platform alternative to commercial programs like FL Studio®, which allow you to produce music with your computer. This includes the creation of melodies and beats, the synthesis and mixing of sounds, and arranging of samples. You can have fun with your MIDI-keyboard and much more; all in a user-friendly and modern interface. LMMS features components such as a Song Editor, a Beat+Bassline Editor, a Piano Roll, an FX Mixer as well as many powerful instruments and effects. .SH OPTIONS .IP "\fB\-r, --render\fP \fIproject-file\fP Render given file to either a wav\- or ogg\-file. See \fB\-f\fP for details .IP "\fB\-o, --output\fP \fIfile\fP render into \fIfile\fP .IP "\fB\-f, --output-format\fP \fIformat\fP Specify format of render-output where \fIformat\fP is either 'wav' or 'ogg' .IP "\fB\-s, --samplerate\fP \fIsamplerate\fP Specify output samplerate in Hz - range is 44100 (default) to 192000 .IP "\fB\-b, --bitrate\fP \fIbitrate\fP Specify output bitrate in kHz (for OGG encoding only), default is 160 .IP "\fB\-i, --interpolation\fP \fImethod\fP Specify interpolation method - possible values are \fIlinear\fP, \fIsincfastest\fP (default), \fIsincmedium\fP, \fIsincbest\fP .IP "\fB\-x, --oversampling\fP \fIvalue\fP Specify oversampling, possible values: 1, 2 (default), 4, 8 .IP "\fB\-u, --upgrade\fP \fIin\fP \fIout\fP Upgrade file \fIin\fP and save as \fIout\fP .IP "\fB\-d, --dump\fP \fIin\fP Dump XML of compressed file \fIin\fP (i.e. MMPZ-file) .IP "\fB\-v, --version Show version information and exit. .IP "\fB\-h, --help Show usage information and exit. .SH SEE ALSO .BR http://lmms.sf.net/ .BR http://lmms.sf.net/wiki/ .SH AUTHORS .BR Tobias Doerffel , Paul Giblock and others. See AUTHORS for details. lmms-1.0.0/lmms.spec.in0000644000175000017500000001352712313663627013446 0ustar tobytoby# Configuration variables %define name lmms %define version 0.3.0 %define rel 1 %define release %{rel}%{?disttag}%{?repotag} # Define what you can/want to build: %define with_sdl 0%{nil} %define with_jack 1%{nil} %define with_vorbis 1%{nil} %define with_flac 1%{nil} %define with_sr 1%{nil} %define with_sf 1%{nil} # Build by setting these defines on the command line, for example: # rpmbuild --define 'disttag .EL' --define 'repotag .fc6' %{!?desktop_vendor: %{expand: %%define desktop_vendor rpmfarm}} # This can be changed at build time: # rpmbuild --define 'desktop_vendor RPMfarm' #-------------------------------------------------------------------- Name: %{name} Version: %{version} Release: %{release} Summary: powerful sequencer-, synthesizer- and sample-studio for Linux Summary(de): Leistungsfaehiges Sequenzer-, Synthesizer- und Sample-Studio fuer Linux License: GPL URL: http://lmms.sourceforge.net/ Group: Applications/Multimedia Provides: lmms = %{version}-%{release} BuildRequires: gcc gcc-c++ libstdc++-devel autoconf automake libtool make BuildRequires: alsa-lib-devel BuildRequires: qt-devel > 3.0 # ------------- BuildRequires: %if %{with_vorbis} BuildRequires: libvorbis libvorbis-devel %endif %if %{with_sdl} BuildRequires: SDL SDL-devel SDL_sound %endif %if %{with_sr} BuildRequires: libsamplerate libsamplerate-devel %endif %if %{with_sf} BuildRequires: libsndfile libsndfile-devel %endif %if %{with_jack} BuildRequires: jack-audio-connection-kit-devel %endif %if %{with_flac} BuildRequires: flac-devel %endif # ------------- Requires: Requires: qt >= 3.0 alsa-lib %if %{with_sr} Requires: libsamplerate %endif %if %{with_sf} Requires: libsndfile %endif %if %{with_jack} Requires: jack-audio-connection-kit %endif %if %{with_vorbis} Requires: libvorbis %endif %if %{with_flac} Requires: flac %endif %if %{with_sdl} Requires: SDL_sound %endif Requires: lmms-data Source: %{name}-%{version}.tar.bz2 Patch1: %{name}-%{version}-patch BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot Prefix: %{_prefix} %description LMMS aims to be a free alternative to popular (but commercial and closed- source) programs like FruityLoops, Cubase and Logic giving you the ability of producing music with your computer by creating/synthesizing sounds, arranging samples, playing live with keyboard and much more... LMMS combines the features of a tracker-/sequencer-program (pattern-/channel-/ sample-/song-/effect-management) and those of powerful synthesizers and samplers in a modern, user-friendly and easy to use graphical user-interface. LMMS is still in heavy development, so with this version please don't expect a complete, ready and bugfree program!! %description -l de LMMS ist eine freie Alternative zu bekannten (aber kommerziellen und Closed-Source-) Programmen wie FruityLoops, Cubase und Logic, die Ihnen die Moeglichkeit geben, mit Ihrem Computer Musik zu produzieren, indem Sie Klaenge kreieren/synthetisieren, Samples anordnen, mit dem Keyboard live spielen usw.... LMMS kombiniert die Funktionen eines Tracker-/Sequenzer-Programms (Pattern-/ Kanal-/Sample-/Song-/Effekt-Management) und die von leistungsfaehigen Synthesizern und Samplern in einer modernen, benutzerfreundlichen und einfach zu benutzenden grafischen Oberflaeche. Derzeit befindet sich LMMS komplett in Entwicklung. Also erwarten Sie bitte mit dieser Version nicht ein vollstaendiges, fertiges und fehlerfreies Programm!! %package data Summary: samples, presets, demo-projects and localization-files for LMMS Summary(de): Samples, Presets, Demo-Projekte und Lokalisierungsdateien fuer LMMS Group: Applications/Multimedia Provides: lmms-data = %{version}-%{release} %description data This package contains platform-independent data and resources for Linux MultiMedia Studio (LMMS), e.g. samples, presets and demo-projects. %description data -l de Dieses Paket beinhaltet plattform-unabhaengige Daten und Resourcen fuer Linux MultiMedia Studio (LMMS), wie z.B. Samples, Presets und Demo-Projekte. %prep %setup -n %{name}-%{version} %patch1 -p1 -b .EL %build %configure \ %if !%{with_jack} --without-jack \ %endif %if !%{with_sdl} --without-sdl \ --without-sdlsound \ %endif %if !%{with_vorbis} --without-vorbis \ %endif %if !%{with_flac} --without-flac \ %endif %if !%{with_sr} --without-libsrc \ %endif %if !%{with_sf} --without-libsf \ %endif %{__make} %{?_smp_mflags} # Create a desktop menu entry %{__cat} > %{name}.desktop << EOF [Desktop Entry] Name=Linux MultiMedia Studio Comment=Powerful sequencer-, synthesizer- and sample-studio for Linux Exec=lmms Icon=%{_datadir}/%{name}/themes/default/icon.png Terminal=false Type=Application Categories=Application;AudioVideo;X-Fedora;X-RPMfarm; Encoding=UTF-8 EOF %install %{__rm} -rf %{buildroot} make DESTDIR=$RPM_BUILD_ROOT install # Install menu entry %{__mkdir_p} %{buildroot}%{_datadir}/applications desktop-file-install \ --vendor %{desktop_vendor} \ --dir %{buildroot}%{_datadir}/applications \ %{name}.desktop || : %clean %{__rm} -rf %{buildroot} %files %defattr(-, root, root, 0755) %doc AUTHORS ChangeLog COPYING README TODO %{_bindir}/lmms %dir %{_libdir}/lmms %{_libdir}/lmms/* %{_mandir}/man*/lmms.* %{_datadir}/applications/%{desktop_vendor}-%{name}.desktop %files data %defattr(-,root,root) %dir %{_datadir}/lmms %{_datadir}/lmms/* %changelog * Fri Apr 13 2007 Eric Lassauge - 0.2.1-1 - build for FC6 - added dependencies and build configuration - added .desktop file * Tue Sep 20 2005 Tobias Doerffel - added JACK-dependencies * Fri Jul 22 2005 Tobias Doerffel - added more dependencies for builds under SuSE * Sat Jun 25 2005 Tobias Doerffel - splitted package into lmms and lmms-data - additional requirements - updated project-homepage and email-address of packager * Thu May 12 2005 Tobias Doerffel - created lmms.spec.in lmms-1.0.0/INSTALL0000644000175000017500000000224612313663627012242 0ustar tobytobyBuilding LMMS got quite simple since 0.4.0 as everything is managed by cmake now. Therefore make sure you have CMake (>= 2.6.0 recommended) and then run mkdir build cd build cmake ../ make sudo make install If your system does not have "sudo", become root with your preferred mechanism and run the "make install" command. With the above commands an out-of-tree build is performed. You can also run "cmake ." directly in the root of source tree although this is not recommended. When performing an out-of-tree build after there's already an in-tree build, make sure to run "make distclean" before running cmake inside build-directory. If you want to use custom compiler flags simply set the environment variables CFLAGS and CXXFLAGS. After running cmake (the 3rd command above) you can see a summary of things that are going to be built into LMMS or built as plugins. Install the according libraries and development files if a certain feature is not enabled. Then remove CMakeCache.txt and run cmake again. If you want to supply an install prefix to cmake, add the flag: -DCMAKE_INSTALL_PREFIX= Where can be /usr, /usr/local, /opt, etc. The default is /usr/local. lmms-1.0.0/src/0000755000175000017500000000000012313663627011774 5ustar tobytobylmms-1.0.0/src/gui/0000755000175000017500000000000012313663627012560 5ustar tobytobylmms-1.0.0/src/gui/EffectControlDialog.cpp0000644000175000017500000000307712313663627017150 0ustar tobytoby/* * EffectControlDialog.cpp - base-class for effect-dialogs for displaying * and editing control port values * * Copyright (c) 2006-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "EffectControlDialog.h" #include "EffectControls.h" #include "Effect.h" EffectControlDialog::EffectControlDialog( EffectControls * _controls ) : QWidget( NULL ), ModelView( _controls, this ), m_effectControls( _controls ) { setWindowTitle( m_effectControls->effect()->displayName() ); } EffectControlDialog::~EffectControlDialog() { } void EffectControlDialog::closeEvent( QCloseEvent * _ce ) { _ce->ignore(); emit closed(); } #include "moc_EffectControlDialog.cxx" lmms-1.0.0/src/gui/PianoRoll.cpp0000644000175000017500000027367312313663627015205 0ustar tobytoby/* * PianoRoll.cpp - implementation of piano roll which is used for actual * writing of melodies * * Copyright (c) 2004-2014 Tobias Doerffel * Copyright (c) 2008 Andrew Kelley * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include #include "config_mgr.h" #include "PianoRoll.h" #include "bb_track_container.h" #include "Clipboard.h" #include "combobox.h" #include "debug.h" #include "DetuningHelper.h" #include "embed.h" #include "gui_templates.h" #include "InstrumentTrack.h" #include "MainWindow.h" #include "MidiEvent.h" #include "DataFile.h" #include "pattern.h" #include "Piano.h" #include "pixmap_button.h" #include "song.h" #include "SongEditor.h" #include "templates.h" #include "text_float.h" #include "timeline.h" #include "tool_button.h" #include "tooltip.h" typedef AutomationPattern::timeMap timeMap; extern Keys whiteKeys[]; // defined in piano_widget.cpp // some constants... const int INITIAL_PIANOROLL_HEIGHT = 480; const int SCROLLBAR_SIZE = 16; const int PIANO_X = 0; const int WHITE_KEY_WIDTH = 64; const int BLACK_KEY_WIDTH = 41; const int WHITE_KEY_SMALL_HEIGHT = 18; const int WHITE_KEY_BIG_HEIGHT = 24; const int BLACK_KEY_HEIGHT = 16; const int C_KEY_LABEL_X = WHITE_KEY_WIDTH - 19; const int KEY_LINE_HEIGHT = 12; const int OCTAVE_HEIGHT = KEY_LINE_HEIGHT * KeysPerOctave; // = 12 * 12; const int NOTE_EDIT_RESIZE_BAR = 6; const int NOTE_EDIT_MIN_HEIGHT = 50; const int KEY_AREA_MIN_HEIGHT = 100; const int PR_BOTTOM_MARGIN = SCROLLBAR_SIZE; const int PR_TOP_MARGIN = 48; const int PR_RIGHT_MARGIN = SCROLLBAR_SIZE; // width of area used for resizing (the grip at the end of a note) const int RESIZE_AREA_WIDTH = 4; // width of line for setting volume/panning of note const int NE_LINE_WIDTH = 3; // key where to start const int INITIAL_START_KEY = Key_C + Octave_4 * KeysPerOctave; // number of each note to provide in quantization and note lengths const int NUM_EVEN_LENGTHS = 6; const int NUM_TRIPLET_LENGTHS = 5; QPixmap * PianoRoll::s_whiteKeySmallPm = NULL; QPixmap * PianoRoll::s_whiteKeySmallPressedPm = NULL; QPixmap * PianoRoll::s_whiteKeyBigPm = NULL; QPixmap * PianoRoll::s_whiteKeyBigPressedPm = NULL; QPixmap * PianoRoll::s_blackKeyPm = NULL; QPixmap * PianoRoll::s_blackKeyPressedPm = NULL; QPixmap * PianoRoll::s_toolDraw = NULL; QPixmap * PianoRoll::s_toolErase = NULL; QPixmap * PianoRoll::s_toolSelect = NULL; QPixmap * PianoRoll::s_toolMove = NULL; QPixmap * PianoRoll::s_toolOpen = NULL; // used for drawing of piano PianoRoll::PianoRollKeyTypes PianoRoll::prKeyOrder[] = { PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL, PR_WHITE_KEY_SMALL, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_BIG, PR_BLACK_KEY, PR_WHITE_KEY_SMALL } ; const int DEFAULT_PR_PPT = KEY_LINE_HEIGHT * DefaultStepsPerTact; PianoRoll::PianoRoll() : m_nemStr( QVector() ), m_noteEditMenu( NULL ), m_semiToneMarkerMenu( NULL ), m_zoomingModel(), m_quantizeModel(), m_noteLenModel(), m_pattern( NULL ), m_currentPosition(), m_recording( false ), m_currentNote( NULL ), m_action( ActionNone ), m_noteEditMode( NoteEditVolume ), m_moveBoundaryLeft( 0 ), m_moveBoundaryTop( 0 ), m_moveBoundaryRight( 0 ), m_moveBoundaryBottom( 0 ), m_mouseDownKey( 0 ), m_mouseDownTick( 0 ), m_lastMouseX( 0 ), m_lastMouseY( 0 ), m_oldNotesEditHeight( 100 ), m_notesEditHeight( 100 ), m_ppt( DEFAULT_PR_PPT ), m_lenOfNewNotes( MidiTime( 0, DefaultTicksPerTact/4 ) ), m_lastNoteVolume( DefaultVolume ), m_lastNotePanning( DefaultPanning ), m_startKey( INITIAL_START_KEY ), m_lastKey( 0 ), m_editMode( ModeDraw ), m_mouseDownLeft( false ), m_mouseDownRight( false ), m_scrollBack( false ) { // gui names of edit modes m_nemStr.push_back( tr( "Note Volume" ) ); m_nemStr.push_back( tr( "Note Panning" ) ); QSignalMapper * signalMapper = new QSignalMapper( this ); m_noteEditMenu = new QMenu( this ); m_noteEditMenu->clear(); for( int i=0; isetMapping( act, i ); m_noteEditMenu->addAction( act ); } connect( signalMapper, SIGNAL(mapped(int)), this, SLOT(changeNoteEditMode(int)) ); signalMapper = new QSignalMapper( this ); m_semiToneMarkerMenu = new QMenu( this ); QAction * act = new QAction( tr("Mark/unmark current semitone"), this ); connect( act, SIGNAL(triggered()), signalMapper, SLOT(map()) ); signalMapper->setMapping( act, static_cast( stmaMarkCurrentSemiTone ) ); m_semiToneMarkerMenu->addAction( act ); act = new QAction( tr("Mark current scale"), this ); act->setEnabled( false ); connect( act, SIGNAL(triggered()), signalMapper, SLOT(map()) ); connect( this, SIGNAL(semiToneMarkerMenuScaleSetEnabled(bool)), act, SLOT(setEnabled(bool)) ); signalMapper->setMapping( act, static_cast( stmaMarkCurrentScale ) ); m_semiToneMarkerMenu->addAction( act ); act = new QAction( tr("Mark current chord"), this ); act->setEnabled( false ); connect( act, SIGNAL(triggered()), signalMapper, SLOT(map()) ); connect( this, SIGNAL(semiToneMarkerMenuChordSetEnabled(bool)), act, SLOT(setEnabled(bool)) ); signalMapper->setMapping( act, static_cast( stmaMarkCurrentChord ) ); m_semiToneMarkerMenu->addAction( act ); act = new QAction( tr("Unmark all"), this ); connect( act, SIGNAL(triggered()), signalMapper, SLOT(map()) ); signalMapper->setMapping( act, static_cast( stmaUnmarkAll ) ); m_semiToneMarkerMenu->addAction( act ); connect( signalMapper, SIGNAL(mapped(int)), this, SLOT(markSemiTone(int)) ); // init pixmaps if( s_whiteKeySmallPm == NULL ) { s_whiteKeySmallPm = new QPixmap( embed::getIconPixmap( "pr_white_key_small" ) ); } if( s_whiteKeySmallPressedPm == NULL ) { s_whiteKeySmallPressedPm = new QPixmap( embed::getIconPixmap( "pr_white_key_small_pressed" ) ); } if( s_whiteKeyBigPm == NULL ) { s_whiteKeyBigPm = new QPixmap( embed::getIconPixmap( "pr_white_key_big" ) ); } if( s_whiteKeyBigPressedPm == NULL ) { s_whiteKeyBigPressedPm = new QPixmap( embed::getIconPixmap( "pr_white_key_big_pressed" ) ); } if( s_blackKeyPm == NULL ) { s_blackKeyPm = new QPixmap( embed::getIconPixmap( "pr_black_key" ) ); } if( s_blackKeyPressedPm == NULL ) { s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap( "pr_black_key_pressed" ) ); } if( s_toolDraw == NULL ) { s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) ); } if( s_toolErase == NULL ) { s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) ); } if( s_toolSelect == NULL ) { s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) ); } if( s_toolMove == NULL ) { s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) ); } if( s_toolOpen == NULL ) { s_toolOpen = new QPixmap( embed::getIconPixmap( "automation" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); // add time-line m_timeLine = new timeLine( WHITE_KEY_WIDTH, 32, m_ppt, engine::getSong()->getPlayPos( song::Mode_PlayPattern ), m_currentPosition, this ); connect( this, SIGNAL( positionChanged( const MidiTime & ) ), m_timeLine, SLOT( updatePosition( const MidiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePosition( const MidiTime & ) ) ); // update timeline when in record-accompany mode connect( engine::getSong()->getPlayPos( song::Mode_PlaySong ).m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePositionAccompany( const MidiTime & ) ) ); // TODO /* connect( engine::getSong()->getPlayPos( song::Mode_PlayBB ).m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePositionAccompany( const MidiTime & ) ) );*/ m_toolBar = new QWidget( this ); m_toolBar->setFixedHeight( 32 ); m_toolBar->move( 0, 0 ); m_toolBar->setAutoFillBackground( true ); QPalette pal; pal.setBrush( m_toolBar->backgroundRole(), embed::getIconPixmap( "toolbar_bg" ) ); m_toolBar->setPalette( pal ); QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar ); tb_layout->setMargin( 0 ); tb_layout->setSpacing( 0 ); // init control-buttons at the top m_playButton = new toolButton( embed::getIconPixmap( "play" ), tr( "Play/pause current pattern (Space)" ), this, SLOT( play() ), m_toolBar ); m_recordButton = new toolButton( embed::getIconPixmap( "record" ), tr( "Record notes from MIDI-device/channel-piano" ), this, SLOT( record() ), m_toolBar ); m_recordAccompanyButton = new toolButton( embed::getIconPixmap( "record_accompany" ), tr( "Record notes from MIDI-device/channel-piano while playing song or BB track" ), this, SLOT( recordAccompany() ), m_toolBar ); m_stopButton = new toolButton( embed::getIconPixmap( "stop" ), tr( "Stop playing of current pattern (Space)" ), this, SLOT( stop() ), m_toolBar ); m_playButton->setObjectName( "playButton" ); m_stopButton->setObjectName( "stopButton" ); m_recordButton->setObjectName( "recordButton" ); m_recordAccompanyButton->setObjectName( "recordAccompanyButton" ); m_playButton->setWhatsThis( tr( "Click here to play the current pattern. " "This is useful while editing it. The pattern is " "automatically looped when its end is reached." ) ); m_recordButton->setWhatsThis( tr( "Click here to record notes from a MIDI-" "device or the virtual test-piano of the according " "channel-window to the current pattern. When recording " "all notes you play will be written to this pattern " "and you can play and edit them afterwards." ) ); m_recordAccompanyButton->setWhatsThis( tr( "Click here to record notes from a MIDI-" "device or the virtual test-piano of the according " "channel-window to the current pattern. When recording " "all notes you play will be written to this pattern " "and you will hear the song or BB track in the background." ) ); m_stopButton->setWhatsThis( tr( "Click here to stop playback of current pattern." ) ); removeSelection(); // init scrollbars m_leftRightScroll = new QScrollBar( Qt::Horizontal, this ); m_leftRightScroll->setSingleStep( 1 ); connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this, SLOT( horScrolled( int ) ) ); m_topBottomScroll = new QScrollBar( Qt::Vertical, this ); m_topBottomScroll->setSingleStep( 1 ); m_topBottomScroll->setPageStep( 20 ); connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this, SLOT( verScrolled( int ) ) ); // init edit-buttons at the top m_drawButton = new toolButton( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ), this, SLOT( drawButtonToggled() ), m_toolBar ); m_drawButton->setCheckable( true ); m_drawButton->setChecked( true ); m_eraseButton = new toolButton( embed::getIconPixmap( "edit_erase" ), tr( "Erase mode (Shift+E)" ), this, SLOT( eraseButtonToggled() ), m_toolBar ); m_eraseButton->setCheckable( true ); m_selectButton = new toolButton( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ), this, SLOT( selectButtonToggled() ), m_toolBar ); m_selectButton->setCheckable( true ); m_detuneButton = new toolButton( embed::getIconPixmap( "automation"), tr( "Detune mode (Shift+T)" ), this, SLOT( detuneButtonToggled() ), m_toolBar ); m_detuneButton->setCheckable( true ); QButtonGroup * tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_drawButton ); tool_button_group->addButton( m_eraseButton ); tool_button_group->addButton( m_selectButton ); tool_button_group->addButton( m_detuneButton ); tool_button_group->setExclusive( true ); m_drawButton->setWhatsThis( tr( "Click here and draw mode will be activated. In this " "mode you can add, resize and move notes. This " "is the default mode which is used most of the time. " "You can also press 'Shift+D' on your keyboard to " "activate this mode. In this mode, hold Ctrl to " "temporarily go into select mode." ) ); m_eraseButton->setWhatsThis( tr( "Click here and erase mode will be activated. In this " "mode you can erase notes. You can also press " "'Shift+E' on your keyboard to activate this mode." ) ); m_selectButton->setWhatsThis( tr( "Click here and select mode will be activated. " "In this mode you can select notes. Alternatively, " "you can hold Ctrl in draw mode to temporarily use " "select mode." ) ); m_detuneButton->setWhatsThis( tr( "Click here and detune mode will be activated. " "In this mode you can click a note to open its " "automation detuning. You can utilize this to slide " "notes from one to another. You can also press " "'Shift+T' on your keyboard to activate this mode." ) ); m_cutButton = new toolButton( embed::getIconPixmap( "edit_cut" ), tr( "Cut selected notes (Ctrl+X)" ), this, SLOT( cutSelectedNotes() ), m_toolBar ); m_copyButton = new toolButton( embed::getIconPixmap( "edit_copy" ), tr( "Copy selected notes (Ctrl+C)" ), this, SLOT( copySelectedNotes() ), m_toolBar ); m_pasteButton = new toolButton( embed::getIconPixmap( "edit_paste" ), tr( "Paste notes from clipboard " "(Ctrl+V)" ), this, SLOT( pasteNotes() ), m_toolBar ); m_cutButton->setWhatsThis( tr( "Click here and the selected notes will be cut into the " "clipboard. You can paste them anywhere in any pattern " "by clicking on the paste button." ) ); m_copyButton->setWhatsThis( tr( "Click here and the selected notes will be copied into the " "clipboard. You can paste them anywhere in any pattern " "by clicking on the paste button." ) ); m_pasteButton->setWhatsThis( tr( "Click here and the notes from the clipboard will be " "pasted at the first visible measure." ) ); QLabel * zoom_lbl = new QLabel( m_toolBar ); zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) ); // setup zooming-stuff for( int i = 0; i < 6; ++i ) { m_zoomingModel.addItem( QString::number( 25 << i ) + "%" ); } m_zoomingModel.setValue( m_zoomingModel.findText( "100%" ) ); connect( &m_zoomingModel, SIGNAL( dataChanged() ), this, SLOT( zoomingChanged() ) ); m_zoomingComboBox = new comboBox( m_toolBar ); m_zoomingComboBox->setModel( &m_zoomingModel ); m_zoomingComboBox->setFixedSize( 64, 22 ); // setup quantize-stuff QLabel * quantize_lbl = new QLabel( m_toolBar ); quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) ); m_quantizeModel.addItem( tr( "Note lock" ) ); for( int i = 0; i <= NUM_EVEN_LENGTHS; ++i ) { m_quantizeModel.addItem( "1/" + QString::number( 1 << i ) ); } for( int i = 0; i < NUM_TRIPLET_LENGTHS; ++i ) { m_quantizeModel.addItem( "1/" + QString::number( (1 << i) * 3 ) ); } m_quantizeModel.addItem( "1/192" ); m_quantizeModel.setValue( m_quantizeModel.findText( "1/16" ) ); m_quantizeComboBox = new comboBox( m_toolBar ); m_quantizeComboBox->setModel( &m_quantizeModel ); m_quantizeComboBox->setFixedSize( 64, 22 ); connect( &m_quantizeModel, SIGNAL( dataChanged() ), this, SLOT( quantizeChanged() ) ); // setup note-len-stuff QLabel * note_len_lbl = new QLabel( m_toolBar ); note_len_lbl->setPixmap( embed::getIconPixmap( "note" ) ); m_noteLenModel.addItem( tr( "Last note" ), new PixmapLoader( "edit_draw" ) ); const QString pixmaps[] = { "whole", "half", "quarter", "eighth", "sixteenth", "thirtysecond", "triplethalf", "tripletquarter", "tripleteighth", "tripletsixteenth", "tripletthirtysecond" } ; for( int i = 0; i < NUM_EVEN_LENGTHS; ++i ) { m_noteLenModel.addItem( "1/" + QString::number( 1 << i ), new PixmapLoader( "note_" + pixmaps[i] ) ); } for( int i = 0; i < NUM_TRIPLET_LENGTHS; ++i ) { m_noteLenModel.addItem( "1/" + QString::number( (1 << i) * 3 ), new PixmapLoader( "note_" + pixmaps[i+NUM_EVEN_LENGTHS] ) ); } m_noteLenModel.setValue( 0 ); m_noteLenComboBox = new comboBox( m_toolBar ); m_noteLenComboBox->setModel( &m_noteLenModel ); m_noteLenComboBox->setFixedSize( 105, 22 ); // Note length change can cause a redraw if Q is set to lock connect( &m_noteLenModel, SIGNAL( dataChanged() ), this, SLOT( quantizeChanged() ) ); const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance(); // setup scale-stuff QLabel * scale_lbl = new QLabel( m_toolBar ); scale_lbl->setPixmap( embed::getIconPixmap( "scale" ) ); m_scaleModel.addItem( tr("No scale") ); for( int i = 0; i < chord_table.size(); ++i ) { if( chord_table[i].isScale() ) { m_scaleModel.addItem( chord_table[i].getName() ); } } m_scaleModel.setValue( 0 ); m_scaleComboBox = new comboBox( m_toolBar ); m_scaleComboBox->setModel( &m_scaleModel ); m_scaleComboBox->setFixedSize( 105, 22 ); // change can update m_semiToneMarkerMenu connect( &m_scaleModel, SIGNAL( dataChanged() ), this, SLOT( updateSemiToneMarkerMenu() ) ); // setup chord-stuff QLabel * chord_lbl = new QLabel( m_toolBar ); chord_lbl->setPixmap( embed::getIconPixmap( "chord" ) ); m_chordModel.addItem( tr("No chord") ); for( int i = 0; i < chord_table.size(); ++i ) { if( ! chord_table[i].isScale() ) { m_chordModel.addItem( chord_table[i].getName() ); } } m_chordModel.setValue( 0 ); m_chordComboBox = new comboBox( m_toolBar ); m_chordComboBox->setModel( &m_chordModel ); m_chordComboBox->setFixedSize( 105, 22 ); // change can update m_semiToneMarkerMenu connect( &m_chordModel, SIGNAL( dataChanged() ), this, SLOT( updateSemiToneMarkerMenu() ) ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_playButton ); tb_layout->addWidget( m_recordButton ); tb_layout->addWidget( m_recordAccompanyButton ); tb_layout->addWidget( m_stopButton ); tb_layout->addSpacing( 7 ); tb_layout->addWidget( m_drawButton ); tb_layout->addWidget( m_eraseButton ); tb_layout->addWidget( m_selectButton ); tb_layout->addWidget( m_detuneButton ); tb_layout->addSpacing( 7 ); tb_layout->addWidget( m_cutButton ); tb_layout->addWidget( m_copyButton ); tb_layout->addWidget( m_pasteButton ); tb_layout->addSpacing( 7 ); m_timeLine->addToolButtons( m_toolBar ); tb_layout->addSpacing( 7 ); tb_layout->addWidget( zoom_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_zoomingComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( quantize_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_quantizeComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( note_len_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_noteLenComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( scale_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_scaleComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( chord_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_chordComboBox ); tb_layout->addStretch(); // setup our actual window setFocusPolicy( Qt::StrongFocus ); setFocus(); setWindowIcon( embed::getIconPixmap( "piano" ) ); setCurrentPattern( NULL ); setMouseTracking( true ); setMinimumSize( tb_layout->minimumSize().width(), 160 ); // add us to workspace if( engine::mainWindow()->workspace() ) { engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->setMinimumSize( tb_layout->minimumSize().width()+10, 200 ); parentWidget()->resize( tb_layout->minimumSize().width()+10, INITIAL_PIANOROLL_HEIGHT ); parentWidget()->move( 5, 5 ); parentWidget()->hide(); } else { resize( tb_layout->minimumSize().width(), INITIAL_PIANOROLL_HEIGHT ); hide(); } connect( engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ), this, SLOT( update() ) ); } void PianoRoll::changeNoteEditMode( int i ) { m_noteEditMode = (noteEditMode) i; repaint(); } void PianoRoll::markSemiTone( int i ) { const int key = getKey( mapFromGlobal( m_semiToneMarkerMenu->pos() ).y() ); const InstrumentFunctionNoteStacking::Chord * chord = 0; switch( static_cast( i ) ) { case stmaUnmarkAll: m_markedSemiTones.clear(); break; case stmaMarkCurrentSemiTone: { QList::iterator i = qFind( m_markedSemiTones.begin(), m_markedSemiTones.end(), key ); if( i != m_markedSemiTones.end() ) { m_markedSemiTones.erase( i ); } else { m_markedSemiTones.push_back( key ); } break; } case stmaMarkCurrentScale: chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance() .getScaleByName( m_scaleModel.currentText() ); case stmaMarkCurrentChord: { if( ! chord ) { chord = & InstrumentFunctionNoteStacking::ChordTable::getInstance() .getChordByName( m_chordModel.currentText() ); } if( chord->isEmpty() ) { break; } else if( chord->isScale() ) { m_markedSemiTones.clear(); } const int first = chord->isScale() ? 0 : key; const int last = chord->isScale() ? NumKeys : key + chord->last(); const int cap = ( chord->isScale() || chord->last() == 0 ) ? KeysPerOctave : chord->last(); for( int i = first; i <= last; i++ ) { //if( chord->hasSemiTone( std::abs( key - i ) % cap ) ) if( chord->hasSemiTone( ( i + cap - ( key % cap ) ) % cap ) ) { m_markedSemiTones.push_back( i ); } } break; } default: ; } qSort( m_markedSemiTones.begin(), m_markedSemiTones.end(), qGreater() ); QList::iterator new_end = std::unique( m_markedSemiTones.begin(), m_markedSemiTones.end() ); m_markedSemiTones.erase( new_end, m_markedSemiTones.end() ); } PianoRoll::~PianoRoll() { } void PianoRoll::setCurrentPattern( pattern * _new_pattern ) { if( validPattern() ) { m_pattern->instrumentTrack()->disconnect( this ); } m_pattern = _new_pattern; m_currentPosition = 0; m_currentNote = NULL; m_startKey = INITIAL_START_KEY; if( validPattern() == false ) { //resizeEvent( NULL ); setWindowTitle( tr( "Piano-Roll - no pattern" ) ); update(); emit currentPatternChanged(); return; } m_leftRightScroll->setValue( 0 ); const NoteVector & notes = m_pattern->notes(); int central_key = 0; if( notes.empty() == false ) { // determine the central key so that we can scroll to it int total_notes = 0; for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { if( ( *it )->length() > 0 ) { central_key += ( *it )->key(); ++total_notes; } } if( total_notes > 0 ) { central_key = central_key / total_notes - ( KeysPerOctave * NumOctaves - m_totalKeysToScroll ) / 2; m_startKey = tLimit( central_key, 0, NumOctaves * KeysPerOctave ); } } // resizeEvent() does the rest for us (scrolling, range-checking // of start-notes and so on...) resizeEvent( NULL ); connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOn( const note& ) ), this, SLOT( startRecordNote( const note& ) ) ); connect( m_pattern->instrumentTrack(), SIGNAL( midiNoteOff( const note& ) ), this, SLOT( finishRecordNote( const note& ) ) ); connect( m_pattern->instrumentTrack()->pianoModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); setWindowTitle( tr( "Piano-Roll - %1" ).arg( m_pattern->name() ) ); update(); emit currentPatternChanged(); } void PianoRoll::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); } void PianoRoll::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); } void PianoRoll::setPauseIcon( bool pause ) { if( pause == true ) { m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } } inline void PianoRoll::drawNoteRect( QPainter & _p, int _x, int _y, int _width, note * _n ) { ++_x; ++_y; _width -= 2; if( _width <= 0 ) { _width = 2; } int volVal = qMin( 255, (int) ( ( (float)( _n->getVolume() - MinVolume ) ) / ( (float)( MaxVolume - MinVolume ) ) * 255.0f) ); float rightPercent = qMin( 1.0f, ( (float)( _n->getPanning() - PanningLeft ) ) / ( (float)( PanningRight - PanningLeft ) ) * 2.0f ); float leftPercent = qMin( 1.0f, ( (float)( PanningRight - _n->getPanning() ) ) / ( (float)( PanningRight - PanningLeft ) ) * 2.0f ); const QColor defaultNoteColor( 0x77, 0xC7, 0xD8 ); QColor col = defaultNoteColor; if( _n->length() < 0 ) { //step note col.setRgb( 0, 255, 0 ); _p.fillRect( _x, _y, _width, KEY_LINE_HEIGHT - 2, col ); } else if( _n->selected() ) { col.setRgb( 0x00, 0x40, 0xC0 ); _p.fillRect( _x, _y, _width, KEY_LINE_HEIGHT - 2, col ); } else { // adjust note to make it a bit faded if it has a lower volume // in stereo using gradients QColor lcol = QColor::fromHsv( col.hue(), col.saturation(), volVal * leftPercent ); QColor rcol = QColor::fromHsv( col.hue(), col.saturation(), volVal * rightPercent ); col = QColor::fromHsv( col.hue(), col.saturation(), volVal ); QLinearGradient gradient( _x, _y, _x+_width, _y+KEY_LINE_HEIGHT ); gradient.setColorAt( 0, lcol ); gradient.setColorAt( 1, rcol ); _p.setBrush( gradient ); _p.setPen( Qt::NoPen ); _p.drawRect( _x, _y, _width, KEY_LINE_HEIGHT-1 ); } // hilighting lines around the note _p.setPen( Qt::SolidLine ); _p.setBrush( Qt::NoBrush ); col = defaultNoteColor; _p.setPen( QColor::fromHsv( col.hue(), col.saturation(), qMin( 255, volVal*1.7f ) ) ); _p.drawLine( _x, _y, _x + _width, _y ); _p.drawLine( _x, _y, _x, _y + KEY_LINE_HEIGHT - 2 ); col = defaultNoteColor; _p.setPen( QColor::fromHsv( col.hue(), col.saturation(), volVal/1.7 ) ); _p.drawLine( _x + _width, _y, _x + _width, _y + KEY_LINE_HEIGHT - 2 ); _p.drawLine( _x, _y + KEY_LINE_HEIGHT - 2, _x + _width, _y + KEY_LINE_HEIGHT - 2 ); // that little tab thing on the end hinting at the user // to resize the note _p.setPen( defaultNoteColor.lighter( 200 ) ); if( _width > 2 ) { _p.drawLine( _x + _width - 3, _y + 2, _x + _width - 3, _y + KEY_LINE_HEIGHT - 4 ); } _p.drawLine( _x + _width - 1, _y + 2, _x + _width - 1, _y + KEY_LINE_HEIGHT - 4 ); _p.drawLine( _x + _width - 2, _y + 2, _x + _width - 2, _y + KEY_LINE_HEIGHT - 4 ); } inline void PianoRoll::drawDetuningInfo( QPainter & _p, note * _n, int _x, int _y ) { int middle_y = _y + KEY_LINE_HEIGHT / 2; _p.setPen( QColor( 0x99, 0xAF, 0xFF ) ); int old_x = 0; int old_y = 0; timeMap & map = _n->detuning()->automationPattern()->getTimeMap(); for( timeMap::ConstIterator it = map.begin(); it != map.end(); ++it ) { int pos_ticks = it.key(); if( pos_ticks > _n->length() ) { break; } int pos_x = _x + pos_ticks * m_ppt / MidiTime::ticksPerTact(); const float level = it.value(); int pos_y = (int)( middle_y - level * KEY_LINE_HEIGHT ); if( old_x != 0 && old_y != 0 ) { switch( _n->detuning()->automationPattern()->progressionType() ) { case AutomationPattern::DiscreteProgression: _p.drawLine( old_x, old_y, pos_x, old_y ); _p.drawLine( pos_x, old_y, pos_x, pos_y ); break; case AutomationPattern::CubicHermiteProgression: /* TODO */ case AutomationPattern::LinearProgression: _p.drawLine( old_x, old_y, pos_x, pos_y ); break; } } _p.drawLine( pos_x - 1, pos_y, pos_x + 1, pos_y ); _p.drawLine( pos_x, pos_y - 1, pos_x, pos_y + 1 ); old_x = pos_x; old_y = pos_y; } } void PianoRoll::removeSelection() { m_selectStartTick = 0; m_selectedTick = 0; m_selectStartKey = 0; m_selectedKeys = 0; } void PianoRoll::clearSelectedNotes() { if( m_pattern != NULL ) { // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin(); while( it != notes.end() ) { ( *it )->setSelected( false ); ++it; } } } void PianoRoll::closeEvent( QCloseEvent * _ce ) { QApplication::restoreOverrideCursor(); if( parentWidget() ) { parentWidget()->hide(); } else { hide(); } _ce->ignore(); } void PianoRoll::shiftSemiTone( int amount ) // shift notes by amount semitones { bool useAllNotes = ! isSelection(); const NoteVector & notes = m_pattern->notes(); for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { // if none are selected, move all notes, otherwise // only move selected notes if( useAllNotes || ( *it )->selected() ) { ( *it )->setKey( ( *it )->key() + amount ); } } // we modified the song update(); engine::songEditor()->update(); } void PianoRoll::shiftPos( int amount ) //shift notes pos by amount { bool useAllNotes = ! isSelection(); const NoteVector & notes = m_pattern->notes(); bool first = true; for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { // if none are selected, move all notes, otherwise // only move selected notes if( ( *it )->selected() || (useAllNotes && ( *it )->length() > 0) ) { // don't let notes go to out of bounds if( first ) { m_moveBoundaryLeft = ( *it )->pos(); if( m_moveBoundaryLeft + amount < 0 ) { amount += 0 - (amount + m_moveBoundaryLeft); } first = false; } ( *it )->setPos( ( *it )->pos() + amount ); } } // we modified the song update(); engine::songEditor()->update(); } bool PianoRoll::isSelection() const // are any notes selected? { const NoteVector & notes = m_pattern->notes(); for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { if( ( *it )->selected() ) { return true; } } return false; } int PianoRoll::selectionCount() const // how many notes are selected? { int sum = 0; const NoteVector & notes = m_pattern->notes(); for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { if( ( *it )->selected() ) { ++sum; } } return sum; } void PianoRoll::keyPressEvent( QKeyEvent* event ) { if( validPattern() && event->modifiers() == Qt::NoModifier ) { const int key_num = PianoView::getKeyFromKeyEvent( event ) + ( DefaultOctave - 1 ) * KeysPerOctave; if( event->isAutoRepeat() == false && key_num > -1 ) { m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num ); event->accept(); } } switch( event->key() ) { case Qt::Key_Up: if( ( event->modifiers() & Qt::ControlModifier ) && m_action == ActionNone ) { // shift selection up an octave // if nothing selected, shift _everything_ shiftSemiTone( +12 ); } else { // scroll m_topBottomScroll->setValue( m_topBottomScroll->value() - cm_scrollAmtVert ); // if they are moving notes around or resizing, // recalculate the note/resize position if( m_action == ActionMoveNote || m_action == ActionResizeNote ) { dragNotes( m_lastMouseX, m_lastMouseY, event->modifiers() & Qt::AltModifier, event->modifiers() & Qt::ShiftModifier ); } } event->accept(); break; case Qt::Key_Down: if( event->modifiers() & Qt::ControlModifier && m_action == ActionNone ) { // shift selection down an octave // if nothing selected, shift _everything_ shiftSemiTone( -12 ); } else { // scroll m_topBottomScroll->setValue( m_topBottomScroll->value() + cm_scrollAmtVert ); // if they are moving notes around or resizing, // recalculate the note/resize position if( m_action == ActionMoveNote || m_action == ActionResizeNote ) { dragNotes( m_lastMouseX, m_lastMouseY, event->modifiers() & Qt::AltModifier, event->modifiers() & Qt::ShiftModifier ); } } event->accept(); break; case Qt::Key_Left: if( event->modifiers() & Qt::ControlModifier && m_action == ActionNone ) { // move time ticker if( ( m_timeLine->pos() -= 16 ) < 0 ) { m_timeLine->pos().setTicks( 0 ); } m_timeLine->updatePosition(); } else if( event->modifiers() & Qt::ShiftModifier && m_action == ActionNone) { // move notes bool quantized = ! ( event->modifiers() & Qt::AltModifier ); int amt = quantized ? quantization() : 1; shiftPos( -amt ); } else { // scroll m_leftRightScroll->setValue( m_leftRightScroll->value() - cm_scrollAmtHoriz ); // if they are moving notes around or resizing, // recalculate the note/resize position if( m_action == ActionMoveNote || m_action == ActionResizeNote ) { dragNotes( m_lastMouseX, m_lastMouseY, event->modifiers() & Qt::AltModifier, event->modifiers() & Qt::ShiftModifier ); } } event->accept(); break; case Qt::Key_Right: if( event->modifiers() & Qt::ControlModifier && m_action == ActionNone) { // move time ticker m_timeLine->pos() += 16; m_timeLine->updatePosition(); } else if( event->modifiers() & Qt::ShiftModifier && m_action == ActionNone) { // move notes bool quantized = !( event->modifiers() & Qt::AltModifier ); int amt = quantized ? quantization() : 1; shiftPos( +amt ); } else { // scroll m_leftRightScroll->setValue( m_leftRightScroll->value() + cm_scrollAmtHoriz ); // if they are moving notes around or resizing, // recalculate the note/resize position if( m_action == ActionMoveNote || m_action == ActionResizeNote ) { dragNotes( m_lastMouseX, m_lastMouseY, event->modifiers() & Qt::AltModifier, event->modifiers() & Qt::ShiftModifier ); } } event->accept(); break; case Qt::Key_C: if( event->modifiers() & Qt::ControlModifier ) { event->accept(); copySelectedNotes(); } break; case Qt::Key_X: if( event->modifiers() & Qt::ControlModifier ) { event->accept(); cutSelectedNotes(); } break; case Qt::Key_V: if( event->modifiers() & Qt::ControlModifier ) { event->accept(); pasteNotes(); } break; case Qt::Key_A: if( event->modifiers() & Qt::ControlModifier ) { event->accept(); m_selectButton->setChecked( true ); selectAll(); update(); } break; case Qt::Key_D: if( event->modifiers() & Qt::ShiftModifier ) { event->accept(); m_drawButton->setChecked( true ); } break; case Qt::Key_E: if( event->modifiers() & Qt::ShiftModifier ) { event->accept(); m_eraseButton->setChecked( true ); } break; case Qt::Key_S: if( event->modifiers() & Qt::ShiftModifier ) { event->accept(); m_selectButton->setChecked( true ); } break; case Qt::Key_T: if( event->modifiers() & Qt::ShiftModifier ) { event->accept(); m_detuneButton->setChecked( true ); } break; case Qt::Key_Delete: deleteSelectedNotes(); event->accept(); break; case Qt::Key_Space: if( engine::getSong()->isPlaying() ) { stop(); } else { play(); } event->accept(); break; case Qt::Key_Home: m_timeLine->pos().setTicks( 0 ); m_timeLine->updatePosition(); event->accept(); break; case Qt::Key_0: case Qt::Key_1: case Qt::Key_2: case Qt::Key_3: case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: case Qt::Key_8: case Qt::Key_9: { int len = 1 + event->key() - Qt::Key_0; if( len == 10 ) { len = 0; } if( event->modifiers() & ( Qt::ControlModifier | Qt::KeypadModifier ) ) { m_noteLenModel.setValue( len ); event->accept(); } else if( event->modifiers() & Qt::AltModifier ) { m_quantizeModel.setValue( len ); event->accept(); } break; } case Qt::Key_Control: m_ctrlMode = m_editMode; m_editMode = ModeSelect; QApplication::changeOverrideCursor( Qt::ArrowCursor ); event->accept(); break; default: break; } update(); } void PianoRoll::keyReleaseEvent( QKeyEvent* event ) { if( validPattern() && event->modifiers() == Qt::NoModifier ) { const int key_num = PianoView::getKeyFromKeyEvent( event ) + ( DefaultOctave - 1 ) * KeysPerOctave; if( event->isAutoRepeat() == false && key_num > -1 ) { m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( key_num ); event->accept(); } } switch( event->key() ) { case Qt::Key_Control: computeSelectedNotes( event->modifiers() & Qt::ShiftModifier); m_editMode = m_ctrlMode; update(); break; } update(); } void PianoRoll::leaveEvent( QEvent * _e ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QWidget::leaveEvent( _e ); } inline int PianoRoll::noteEditTop() const { return height() - PR_BOTTOM_MARGIN - m_notesEditHeight + NOTE_EDIT_RESIZE_BAR; } inline int PianoRoll::noteEditBottom() const { return height() - PR_BOTTOM_MARGIN; } inline int PianoRoll::noteEditRight() const { return width() - PR_RIGHT_MARGIN; } inline int PianoRoll::noteEditLeft() const { return WHITE_KEY_WIDTH; } inline int PianoRoll::keyAreaTop() const { return PR_TOP_MARGIN; } inline int PianoRoll::keyAreaBottom() const { return height() - PR_BOTTOM_MARGIN - m_notesEditHeight; } void PianoRoll::mousePressEvent( QMouseEvent * _me ) { if( validPattern() == false ) { return; } if( m_editMode == ModeEditDetuning && noteUnderMouse() ) { noteUnderMouse()->editDetuningPattern(); return; } // if holding control, go to selection mode if( _me->modifiers() & Qt::ControlModifier && m_editMode != ModeSelect ) { m_ctrlMode = m_editMode; m_editMode = ModeSelect; QApplication::changeOverrideCursor( QCursor( Qt::ArrowCursor ) ); update(); } // keep track of the point where the user clicked down if( _me->button() == Qt::LeftButton ) { m_moveStartX = _me->x(); m_moveStartY = _me->y(); } if( _me->y() > keyAreaBottom() && _me->y() < noteEditTop() ) { // resizing the note edit area m_action = ActionResizeNoteEditArea; m_oldNotesEditHeight = m_notesEditHeight; return; } if( _me->y() > PR_TOP_MARGIN ) { bool edit_note = ( _me->y() > noteEditTop() ); int key_num = getKey( _me->y() ); int x = _me->x(); if( x > WHITE_KEY_WIDTH ) { // set, move or resize note x -= WHITE_KEY_WIDTH; // get tick in which the user clicked int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt + m_currentPosition; // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin()+notes.size()-1; // loop through whole note-vector... for( int i = 0; i < notes.size(); ++i ) { MidiTime len = ( *it )->length(); if( len < 0 ) { len = 4; } // and check whether the user clicked on an // existing note or an edit-line if( pos_ticks >= ( *it )->pos() && len > 0 && ( ( edit_note == false && pos_ticks <= ( *it )->pos() + len && ( *it )->key() == key_num ) || ( edit_note == true && pos_ticks <= ( *it )->pos() + NE_LINE_WIDTH * MidiTime::ticksPerTact() / m_ppt ) ) ) { break; } --it; } // first check whether the user clicked in note-edit- // area if( edit_note == true ) { // scribble note edit changes mouseMoveEvent( _me ); return; } // left button?? else if( _me->button() == Qt::LeftButton && m_editMode == ModeDraw ) { // whether this action creates new note(s) or not bool is_new_note = false; note * created_new_note = NULL; // did it reach end of vector because // there's no note?? if( it == notes.begin()-1 ) { is_new_note = true; m_pattern->setType( pattern::MelodyPattern ); // then set new note // clear selection and select this new note clearSelectedNotes(); // +32 to quanitize the note correctly when placing notes with // the mouse. We do this here instead of in note.quantized // because live notes should still be quantized at the half. MidiTime note_pos( pos_ticks - ( quantization() / 2 ) ); MidiTime note_len( newNoteLen() ); note new_note( note_len, note_pos, key_num ); new_note.setSelected( true ); new_note.setPanning( m_lastNotePanning ); new_note.setVolume( m_lastNoteVolume ); created_new_note = m_pattern->addNote( new_note ); const InstrumentFunctionNoteStacking::Chord & chord = InstrumentFunctionNoteStacking::ChordTable::getInstance() .getChordByName( m_chordModel.currentText() ); if( ! chord.isEmpty() ) { // if a chord is selected, create following notes in chord // or arpeggio mode const bool arpeggio = _me->modifiers() & Qt::ShiftModifier; for( int i = 1; i < chord.size(); i++ ) { if( arpeggio ) { note_pos += note_len; } note new_note( note_len, note_pos, key_num + chord[i] ); new_note.setSelected( true ); new_note.setPanning( m_lastNotePanning ); new_note.setVolume( m_lastNoteVolume ); m_pattern->addNote( new_note ); } } // reset it so that it can be used for // ops (move, resize) after this // code-block it = notes.begin(); while( it != notes.end() && *it != created_new_note ) { ++it; } } m_currentNote = *it; m_lastNotePanning = ( *it )->getPanning(); m_lastNoteVolume = ( *it )->getVolume(); m_lenOfNewNotes = ( *it )->length(); // remember which key and tick we started with m_mouseDownKey = m_startKey; m_mouseDownTick = m_currentPosition; bool first = true; it = notes.begin(); while( it != notes.end() ) { // remember note starting positions ( *it )->setOldKey( ( *it )->key() ); ( *it )->setOldPos( ( *it )->pos() ); ( *it )->setOldLength( ( *it )->length() ); if( ( *it )->selected() ) { // figure out the bounding box of all the selected notes if( first ) { m_moveBoundaryLeft = ( *it )->pos().getTicks(); m_moveBoundaryRight = ( *it )->pos() + ( *it )->length(); m_moveBoundaryBottom = ( *it )->key(); m_moveBoundaryTop = ( *it )->key(); first = false; } else { m_moveBoundaryLeft = qMin( ( *it )->pos().getTicks(), m_moveBoundaryLeft ); m_moveBoundaryRight = qMax( ( *it )->pos() + ( *it )->length(), m_moveBoundaryRight ); m_moveBoundaryBottom = qMin( ( *it )->key(), m_moveBoundaryBottom ); m_moveBoundaryTop = qMax( ( *it )->key(), m_moveBoundaryTop ); } } ++it; } // if clicked on an unselected note, remove selection // and select that new note if( ! m_currentNote->selected() ) { clearSelectedNotes(); m_currentNote->setSelected( true ); m_moveBoundaryLeft = m_currentNote->pos().getTicks(); m_moveBoundaryRight = m_currentNote->pos() + m_currentNote->length(); m_moveBoundaryBottom = m_currentNote->key(); m_moveBoundaryTop = m_currentNote->key(); } // clicked at the "tail" of the note? if( pos_ticks*m_ppt/MidiTime::ticksPerTact() > ( m_currentNote->pos() + m_currentNote->length() )*m_ppt/ MidiTime::ticksPerTact() - RESIZE_AREA_WIDTH && m_currentNote->length() > 0 ) { // then resize the note m_action = ActionResizeNote; // set resize-cursor QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); } else { // otherwise move it m_action = ActionMoveNote; // set move-cursor QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); // if they're holding shift, copy all selected notes if( //*it != created_new_note && ! is_new_note && _me->modifiers() & Qt::ShiftModifier ) { // vector to hold new notes until we're through the loop QVector newNotes; it = notes.begin(); while( it != notes.end() ) { if( ( *it )->selected() ) { // copy this note note noteCopy( (note) **it ); newNotes.push_back( noteCopy ); } ++it; } if( newNotes.size() != 0 ) { //put notes from vector into piano roll for( int i=0; iaddNote( newNotes[i] ); newNote->setSelected( false ); } // added new notes, so must update engine, song, etc engine::getSong()->setModified(); update(); engine::songEditor()->update(); } } // play the note testPlayNote( m_currentNote ); } engine::getSong()->setModified(); } else if( ( _me->buttons() == Qt::RightButton && m_editMode == ModeDraw ) || m_editMode == ModeErase ) { // erase single note m_mouseDownRight = true; if( it != notes.begin()-1 ) { if( ( *it )->length() > 0 ) { m_pattern->removeNote( *it ); } else { ( *it )->setLength( 0 ); m_pattern->dataChanged(); } engine::getSong()->setModified(); } } else if( _me->button() == Qt::LeftButton && m_editMode == ModeSelect ) { // select an area of notes m_selectStartTick = pos_ticks; m_selectedTick = 0; m_selectStartKey = key_num; m_selectedKeys = 1; m_action = ActionSelectNotes; // call mousemove to fix glitch where selection // appears in wrong spot on mousedown mouseMoveEvent( _me ); } update(); } else if( _me->y() < keyAreaBottom() ) { // clicked on keyboard on the left if( _me->buttons() == Qt::RightButton ) { // right click, tone marker contextual menu m_semiToneMarkerMenu->popup( mapToGlobal( QPoint( _me->x(), _me->y() ) ) ); } else { // left click - play the note m_lastKey = key_num; //if( ! m_recording && ! engine::getSong()->isPlaying() ) { int v = ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity; m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key_num, v ); } } } else { if( _me->buttons() == Qt::LeftButton ) { // clicked in the box below the keys to the left of note edit area m_noteEditMode = (noteEditMode)(((int)m_noteEditMode)+1); if( m_noteEditMode == NoteEditCount ) { m_noteEditMode = (noteEditMode)0; } repaint(); } else if( _me->buttons() == Qt::RightButton ) { // pop menu asking which one they want to edit m_noteEditMenu->popup( mapToGlobal( QPoint( _me->x(), _me->y() ) ) ); } } } } void PianoRoll::mouseDoubleClickEvent( QMouseEvent * _me ) { if( validPattern() == false ) { return; } // if they clicked in the note edit area, clear selection if( _me->x() > noteEditLeft() && _me->x() < noteEditRight() && _me->y() > noteEditTop() && _me->y() < noteEditBottom() ) { clearSelectedNotes(); } } void PianoRoll::testPlayNote( note * n ) { m_lastKey = n->key(); if( n->isPlaying() == false && m_recording == false ) { n->setIsPlaying( true ); const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity(); m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( n->key(), n->midiVelocity( baseVelocity ) ); MidiEvent event( MidiMetaEvent, 0, n->key(), panningToMidi( n->getPanning() ) ); event.setMetaEvent( MidiNotePanning ); m_pattern->instrumentTrack()->processInEvent( event, 0 ); } } void PianoRoll::pauseTestNotes( bool _pause ) { const NoteVector & notes = m_pattern->notes(); NoteVector::ConstIterator it = notes.begin(); while( it != notes.end() ) { if( ( *it )->isPlaying() ) { if( _pause ) { // stop note m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( ( *it )->key() ); } else { // start note ( *it )->setIsPlaying( false ); testPlayNote( *it ); } } ++it; } } void PianoRoll::testPlayKey( int key, int velocity, int pan ) { // turn off old key m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( m_lastKey ); // remember which one we're playing m_lastKey = key; // play new key m_pattern->instrumentTrack()->pianoModel()->handleKeyPress( key, velocity ); } void PianoRoll::computeSelectedNotes(bool shift) { if( m_selectStartTick == 0 && m_selectedTick == 0 && m_selectStartKey == 0 && m_selectedKeys == 0 ) { // don't bother, there's no selection return; } // setup selection-vars int sel_pos_start = m_selectStartTick; int sel_pos_end = m_selectStartTick+m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } int sel_key_start = m_selectStartKey - m_startKey + 1; int sel_key_end = sel_key_start + m_selectedKeys; if( sel_key_start > sel_key_end ) { qSwap( sel_key_start, sel_key_end ); } //int y_base = noteEditTop() - 1; if( validPattern() == true ) { const NoteVector & notes = m_pattern->notes(); for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { // make a new selection unless they're holding shift if( ! shift ) { ( *it )->setSelected( false ); } int len_ticks = ( *it )->length(); if( len_ticks == 0 ) { continue; } else if( len_ticks < 0 ) { len_ticks = 4; } const int key = ( *it )->key() - m_startKey + 1; int pos_ticks = ( *it )->pos(); // if the selection even barely overlaps the note if( key > sel_key_start && key <= sel_key_end && pos_ticks + len_ticks > sel_pos_start && pos_ticks < sel_pos_end ) { // remove from selection when holding shift if( shift && ( *it )->selected() ) { ( *it )->setSelected(false); } else { ( *it )->setSelected(true); } } } } removeSelection(); update(); } void PianoRoll::mouseReleaseEvent( QMouseEvent * _me ) { bool mustRepaint = false; if( _me->button() & Qt::LeftButton ) { m_mouseDownLeft = false; mustRepaint = true; } if( _me->button() & Qt::RightButton ) { m_mouseDownRight = false; mustRepaint = true; } if( _me->button() & Qt::LeftButton && m_editMode == ModeSelect && m_action == ActionSelectNotes ) { // select the notes within the selection rectangle and // then destroy the selection rectangle computeSelectedNotes( _me->modifiers() & Qt::ShiftModifier ); } else if( _me->button() & Qt::LeftButton && m_action == ActionMoveNote ) { // we moved one or more notes so they have to be // moved properly according to new starting- // time in the note-array of pattern m_pattern->rearrangeAllNotes(); } if( _me->button() & Qt::LeftButton && ( m_action == ActionMoveNote || m_action == ActionResizeNote ) ) { // if we only moved one note, deselect it so we can // edit the notes in the note edit area if( selectionCount() == 1 ) { clearSelectedNotes(); } } if( validPattern() == true ) { // turn off all notes that are playing const NoteVector & notes = m_pattern->notes(); NoteVector::ConstIterator it = notes.begin(); while( it != notes.end() ) { if( ( *it )->isPlaying() ) { m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( ( *it )->key() ); ( *it )->setIsPlaying( false ); } ++it; } // stop playing keys that we let go of m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( m_lastKey ); } m_currentNote = NULL; m_action = ActionNone; if( m_editMode == ModeDraw ) { QApplication::restoreOverrideCursor(); } if( mustRepaint ) { repaint(); } } void PianoRoll::mouseMoveEvent( QMouseEvent * _me ) { if( validPattern() == false ) { update(); return; } if( m_action == ActionNone && _me->buttons() == 0 ) { if( _me->y() > keyAreaBottom() && _me->y() < noteEditTop() ) { QApplication::setOverrideCursor( QCursor( Qt::SizeVerCursor ) ); return; } } else if( m_action == ActionResizeNoteEditArea ) { // change m_notesEditHeight and then repaint m_notesEditHeight = tLimit( m_oldNotesEditHeight - ( _me->y() - m_moveStartY ), NOTE_EDIT_MIN_HEIGHT, height() - PR_TOP_MARGIN - NOTE_EDIT_RESIZE_BAR - PR_BOTTOM_MARGIN - KEY_AREA_MIN_HEIGHT ); repaint(); return; } if( _me->y() > PR_TOP_MARGIN || m_action != ActionNone ) { bool edit_note = ( _me->y() > noteEditTop() ) && m_action != ActionSelectNotes; int key_num = getKey( _me->y() ); int x = _me->x(); // see if they clicked on the keyboard on the left if( x < WHITE_KEY_WIDTH && m_action == ActionNone && ! edit_note && key_num != m_lastKey && _me->buttons() & Qt::LeftButton ) { // clicked on a key, play the note testPlayKey( key_num, ( (float) x ) / ( (float) WHITE_KEY_WIDTH ) * MidiDefaultVelocity, 0 ); update(); return; } x -= WHITE_KEY_WIDTH; if( _me->buttons() & Qt::LeftButton && m_editMode == ModeDraw && (m_action == ActionMoveNote || m_action == ActionResizeNote ) ) { // handle moving notes and resizing them bool replay_note = key_num != m_lastKey && m_action == ActionMoveNote; if( replay_note ) { pauseTestNotes(); } dragNotes( _me->x(), _me->y(), _me->modifiers() & Qt::AltModifier, _me->modifiers() & Qt::ShiftModifier ); if( replay_note && m_action == ActionMoveNote ) { pauseTestNotes( false ); } } else if( ( edit_note == true || m_action == ActionChangeNoteProperty ) && _me->buttons() & Qt::LeftButton ) { // editing note properties // Change notes within a certain pixel range of where // the mouse cursor is int pixel_range = 14; // convert to ticks so that we can check which notes // are in the range int ticks_start = (x-pixel_range/2) * MidiTime::ticksPerTact() / m_ppt + m_currentPosition; int ticks_end = (x+pixel_range/2) * MidiTime::ticksPerTact() / m_ppt + m_currentPosition; // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // determine what volume/panning to set note to volume_t vol = tLimit( MinVolume + ( ( (float)noteEditBottom() ) - ( (float)_me->y() ) ) / ( (float)( noteEditBottom() - noteEditTop() ) ) * ( MaxVolume - MinVolume ), MinVolume, MaxVolume ); panning_t pan = tLimit( PanningLeft + ( (float)( noteEditBottom() - _me->y() ) ) / ( (float)( noteEditBottom() - noteEditTop() ) ) * ( (float)( PanningRight - PanningLeft ) ), PanningLeft, PanningRight); if( m_noteEditMode == NoteEditVolume ) { m_lastNoteVolume = vol; } else if( m_noteEditMode == NoteEditPanning ) { m_lastNotePanning = pan; } // loop through vector bool use_selection = isSelection(); NoteVector::ConstIterator it = notes.begin()+notes.size()-1; for( int i = 0; i < notes.size(); ++i ) { note * n = *it; if( n->pos().getTicks() >= ticks_start && n->pos().getTicks() <= ticks_end && n->length().getTicks() != 0 && ( n->selected() || ! use_selection ) ) { m_pattern->dataChanged(); // play the note so that the user can tell how loud it is // and where it is panned testPlayNote( n ); if( m_noteEditMode == NoteEditVolume ) { n->setVolume( vol ); const int baseVelocity = m_pattern->instrumentTrack()->midiPort()->baseVelocity(); m_pattern->instrumentTrack()->processInEvent( MidiEvent( MidiKeyPressure, 0, n->key(), n->midiVelocity( baseVelocity ) ) ); } else if( m_noteEditMode == NoteEditPanning ) { n->setPanning( pan ); MidiEvent evt( MidiMetaEvent, 0, n->key(), panningToMidi( pan ) ); evt.setMetaEvent( MidiNotePanning ); m_pattern->instrumentTrack()->processInEvent( evt ); } } else { if( n->isPlaying() ) { // mouse not over this note, stop playing it. m_pattern->instrumentTrack()->pianoModel()->handleKeyRelease( n->key() ); n->setIsPlaying( false ); } } --it; } } else if( _me->buttons() == Qt::NoButton && m_editMode == ModeDraw ) { // set move- or resize-cursor // get tick in which the cursor is posated int pos_ticks = ( x * MidiTime::ticksPerTact() ) / m_ppt + m_currentPosition; // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin()+notes.size()-1; // loop through whole note-vector... for( int i = 0; i < notes.size(); ++i ) { // and check whether the cursor is over an // existing note if( pos_ticks >= ( *it )->pos() && pos_ticks <= ( *it )->pos() + ( *it )->length() && ( *it )->key() == key_num && ( *it )->length() > 0 ) { break; } --it; } // did it reach end of vector because there's // no note?? if( it != notes.begin()-1 ) { // cursor at the "tail" of the note? if( ( *it )->length() > 0 && pos_ticks*m_ppt / MidiTime::ticksPerTact() > ( ( *it )->pos() + ( *it )->length() )*m_ppt/ MidiTime::ticksPerTact()- RESIZE_AREA_WIDTH ) { if( QApplication::overrideCursor() ) { if( QApplication::overrideCursor()->shape() != Qt::SizeHorCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); } } else { QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); } } else { if( QApplication::overrideCursor() ) { if( QApplication::overrideCursor()->shape() != Qt::SizeAllCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } else { QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } } else { // the cursor is over no note, so restore cursor while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } } } else if( _me->buttons() & Qt::LeftButton && m_editMode == ModeSelect && m_action == ActionSelectNotes ) { // change size of selection // get tick in which the cursor is posated int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -static_cast( m_selectStartTick ); } m_selectedKeys = key_num - m_selectStartKey; if( key_num <= m_selectStartKey ) { --m_selectedKeys; } } else if( m_editMode == ModeDraw && _me->buttons() & Qt::RightButton ) { // holding down right-click to delete notes // get tick in which the user clicked int pos_ticks = x * MidiTime::ticksPerTact() / m_ppt + m_currentPosition; // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin(); // loop through whole note-vector... while( it != notes.end() ) { MidiTime len = ( *it )->length(); if( len < 0 ) { len = 4; } // and check whether the user clicked on an // existing note or an edit-line if( pos_ticks >= ( *it )->pos() && len > 0 && ( ( edit_note == false && pos_ticks <= ( *it )->pos() + len && ( *it )->key() == key_num ) || ( edit_note == true && pos_ticks <= ( *it )->pos() + NE_LINE_WIDTH * MidiTime::ticksPerTact() / m_ppt ) ) ) { // delete this note if( it != notes.end() ) { if( ( *it )->length() > 0 ) { m_pattern->removeNote( *it ); } else { ( *it )->setLength( 0 ); m_pattern->dataChanged(); } engine::getSong()->setModified(); } } else { ++it; } } } } else { if( _me->buttons() & Qt::LeftButton && m_editMode == ModeSelect && m_action == ActionSelectNotes ) { int x = _me->x() - WHITE_KEY_WIDTH; if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( WHITE_KEY_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - WHITE_KEY_WIDTH ) { x = width() - WHITE_KEY_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * MidiTime::ticksPerTact()/ m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -static_cast( m_selectStartTick ); } int key_num = getKey( _me->y() ); int visible_keys = ( height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - m_notesEditHeight ) / KEY_LINE_HEIGHT + 2; const int s_key = m_startKey - 1; if( key_num <= s_key ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), keyAreaBottom() ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); key_num = s_key; } else if( key_num >= s_key + visible_keys ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), PR_TOP_MARGIN ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); key_num = s_key + visible_keys; } m_selectedKeys = key_num - m_selectStartKey; if( key_num <= m_selectStartKey ) { --m_selectedKeys; } } QApplication::restoreOverrideCursor(); } m_lastMouseX = _me->x(); m_lastMouseY = _me->y(); update(); } void PianoRoll::dragNotes( int x, int y, bool alt, bool shift ) { // dragging one or more notes around // convert pixels to ticks and keys int off_x = x - m_moveStartX; int off_ticks = off_x * MidiTime::ticksPerTact() / m_ppt; int off_key = getKey( y ) - getKey( m_moveStartY ); // handle scroll changes while dragging off_ticks -= m_mouseDownTick - m_currentPosition; off_key -= m_mouseDownKey - m_startKey; // if they're not holding alt, quantize the offset if( ! alt ) { off_ticks = floor( off_ticks / quantization() ) * quantization(); } // make sure notes won't go outside boundary conditions if( m_action == ActionMoveNote ) { if( m_moveBoundaryLeft + off_ticks < 0 ) { off_ticks += 0 - (off_ticks + m_moveBoundaryLeft); } if( m_moveBoundaryTop + off_key > NumKeys ) { off_key -= NumKeys - (m_moveBoundaryTop + off_key); } if( m_moveBoundaryBottom + off_key < 0 ) { off_key += 0 - (m_moveBoundaryBottom + off_key); } } int shift_offset = 0; int shift_ref_pos = -1; // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin(); while( it != notes.end() ) { const int pos = ( *it )->pos().getTicks(); // when resizing a note and holding shift: shift the following // notes to preserve the melody if( m_action == ActionResizeNote && shift ) { int shifted_pos = ( *it )->oldPos().getTicks() + shift_offset; if( shifted_pos && pos == shift_ref_pos ) { shifted_pos -= off_ticks; } ( *it )->setPos( MidiTime( shifted_pos ) ); } if( ( *it )->selected() ) { if( m_action == ActionMoveNote ) { // moving note int pos_ticks = ( *it )->oldPos().getTicks() + off_ticks; int key_num = ( *it )->oldKey() + off_key; if( pos_ticks < 0 ) { pos_ticks = 0; } // upper/lower bound checks on key_num if( key_num < 0 ) { key_num = 0; } else if( key_num > NumKeys ) { key_num = NumKeys; } ( *it )->setPos( MidiTime( pos_ticks ) ); ( *it )->setKey( key_num ); } else if( m_action == ActionResizeNote ) { // resizing note int ticks_new = ( *it )->oldLength().getTicks() + off_ticks; if( ticks_new <= 0 ) { ticks_new = 1; } else if( shift ) { // when holding shift: update the offset used to shift // the following notes if( pos > shift_ref_pos ) { shift_offset += off_ticks; shift_ref_pos = pos; } } ( *it )->setLength( MidiTime( ticks_new ) ); m_lenOfNewNotes = ( *it )->length(); } } ++it; } m_pattern->dataChanged(); engine::getSong()->setModified(); } static QString calculateNoteLabel(QString note, int octave) { if(note.isEmpty()) return ""; return note + QString::number(octave); } static void printNoteHeights(QPainter& p, int bottom, int width, int startKey) { assert(Key_C == 0); assert(Key_H == 11); struct KeyLabel { QString key, minor, major; }; const KeyLabel labels[12] = { {QObject::tr("C", "Note name")}, {"", QObject::tr("Db", "Note name"), QObject::tr("C#", "Note name")}, {QObject::tr("D", "Note name")}, {"", QObject::tr("Eb", "Note name"), QObject::tr("D#", "Note name")}, {QObject::tr("E", "Note name"), QObject::tr("Fb", "Note name")}, {"F"}, {"", QObject::tr("Gb", "Note name"), QObject::tr("F#", "Note name")}, {QObject::tr("G", "Note name")}, {"", QObject::tr("Ab", "Note name"),QObject::tr( "G#", "Note name")}, {QObject::tr("A", "Note name")}, {"", QObject::tr("Bb", "Note name"),QObject::tr( "A#", "Note name")}, {QObject::tr("B", "Note name")} }; p.setFont( pointSize( p.font() ) ); p.setPen( QColor( 255, 255, 255 ) ); for( int y = bottom, key = startKey; y > PR_TOP_MARGIN; y -= KEY_LINE_HEIGHT, key++) { const unsigned note = key % KeysPerOctave; assert( note < ( sizeof( labels ) / sizeof( *labels) )); const KeyLabel& noteLabel( labels[note] ); const int octave = key / KeysPerOctave; const KeyLabel notes = { calculateNoteLabel(noteLabel.key, octave), calculateNoteLabel(noteLabel.minor, octave), calculateNoteLabel(noteLabel.major, octave), }; const int drawWidth( width - WHITE_KEY_WIDTH ); const int hspace = 300; const int columnCount = drawWidth/hspace + 1; for(int col = 0; col < columnCount; col++) { const int subOffset = 42; const int x = subOffset + hspace/2 + hspace * col; p.drawText( WHITE_KEY_WIDTH + x, y, notes.key); p.drawText( WHITE_KEY_WIDTH + x - subOffset, y, notes.minor); p.drawText( WHITE_KEY_WIDTH + x + subOffset, y, notes.major); } } } void PianoRoll::paintEvent( QPaintEvent * _pe ) { QStyleOption opt; opt.initFrom( this ); QPainter p( this ); style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); // set font-size to 8 p.setFont( pointSize<8>( p.font() ) ); // y_offset is used to align the piano-keys on the key-lines int y_offset = 0; // calculate y_offset according to first key switch( prKeyOrder[m_startKey % KeysPerOctave] ) { case PR_BLACK_KEY: y_offset = KEY_LINE_HEIGHT/4; break; case PR_WHITE_KEY_BIG: y_offset = KEY_LINE_HEIGHT/2; break; case PR_WHITE_KEY_SMALL: if( prKeyOrder[( ( m_startKey + 1 ) % KeysPerOctave)] != PR_BLACK_KEY ) { y_offset = KEY_LINE_HEIGHT / 2; } break; } // start drawing at the bottom int key_line_y = keyAreaBottom() - 1; // used for aligning black-keys later int first_white_key_height = WHITE_KEY_SMALL_HEIGHT; // key-counter - only needed for finding out whether the processed // key is the first one int keys_processed = 0; int key = m_startKey; // display note marks before drawing other lines for( int i = 0; i < m_markedSemiTones.size(); i++ ) { const int key_num = m_markedSemiTones.at( i ); const int y = keyAreaBottom() + 5 - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ); if( y > keyAreaBottom() ) { break; } p.fillRect( WHITE_KEY_WIDTH+1, y-KEY_LINE_HEIGHT/2, width() - 10, KEY_LINE_HEIGHT, QColor( 0, 80 - ( key_num % KeysPerOctave ) * 3, 64 + key_num / 2) ); } // draw all white keys... for( int y = key_line_y + 1 + y_offset; y > PR_TOP_MARGIN; key_line_y -= KEY_LINE_HEIGHT, ++keys_processed ) { // check for white key that is only half visible on the // bottom of piano-roll if( keys_processed == 0 && prKeyOrder[m_startKey % KeysPerOctave] == PR_BLACK_KEY ) { // draw it! p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPm ); // update y-pos y -= WHITE_KEY_SMALL_HEIGHT / 2; // move first black key down (we didn't draw whole // white key so black key needs to be lifted down) // (default for first_white_key_height = // WHITE_KEY_SMALL_HEIGHT, so WHITE_KEY_SMALL_HEIGHT/2 // is smaller) first_white_key_height = WHITE_KEY_SMALL_HEIGHT / 2; } // check whether to draw a big or a small white key if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_SMALL ) { // draw a small one while checking if it is pressed or not if( validPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) ) { p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPressedPm ); } else { p.drawPixmap( PIANO_X, y - WHITE_KEY_SMALL_HEIGHT, *s_whiteKeySmallPm ); } // update y-pos y -= WHITE_KEY_SMALL_HEIGHT; } else if( prKeyOrder[key % KeysPerOctave] == PR_WHITE_KEY_BIG ) { // draw a big one while checking if it is pressed or not if( validPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) ) { p.drawPixmap( PIANO_X, y - WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPressedPm ); } else { p.drawPixmap( PIANO_X, y-WHITE_KEY_BIG_HEIGHT, *s_whiteKeyBigPm ); } // if a big white key has been the first key, // black keys needs to be lifted up if( keys_processed == 0 ) { first_white_key_height = WHITE_KEY_BIG_HEIGHT; } // update y-pos y -= WHITE_KEY_BIG_HEIGHT; } // label C-keys... if( static_cast( key % KeysPerOctave ) == Key_C ) { p.setPen( QColor( 240, 240, 240 ) ); p.drawText( C_KEY_LABEL_X + 1, y+14, "C" + QString::number( static_cast( key / KeysPerOctave ) ) ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( C_KEY_LABEL_X, y + 13, "C" + QString::number( static_cast( key / KeysPerOctave ) ) ); p.setPen( QColor( 0x4F, 0x4F, 0x4F ) ); } else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } // draw key-line p.drawLine( WHITE_KEY_WIDTH, key_line_y, width(), key_line_y ); ++key; } // reset all values, because now we're going to draw all black keys key = m_startKey; keys_processed = 0; int white_cnt = 0; // and go! for( int y = keyAreaBottom() + y_offset; y > PR_TOP_MARGIN; ++keys_processed ) { // check for black key that is only half visible on the bottom // of piano-roll if( keys_processed == 0 // current key may not be a black one && prKeyOrder[key % KeysPerOctave] != PR_BLACK_KEY // but the previous one must be black (we must check this // because there might be two white keys (E-F) && prKeyOrder[( key - 1 ) % KeysPerOctave] == PR_BLACK_KEY ) { // draw the black key! p.drawPixmap( PIANO_X, y - BLACK_KEY_HEIGHT / 2, *s_blackKeyPm ); // is the one after the start-note a black key?? if( prKeyOrder[( key + 1 ) % KeysPerOctave] != PR_BLACK_KEY ) { // no, then move it up! y -= KEY_LINE_HEIGHT / 2; } } // current key black? if( prKeyOrder[key % KeysPerOctave] == PR_BLACK_KEY) { // then draw it (calculation of y very complicated, // but that's the only working solution, sorry...) // check if the key is pressed or not if( validPattern() && m_pattern->instrumentTrack()->pianoModel()->isKeyPressed( key ) ) { p.drawPixmap( PIANO_X, y - ( first_white_key_height - WHITE_KEY_SMALL_HEIGHT ) - WHITE_KEY_SMALL_HEIGHT/2 - 1 - BLACK_KEY_HEIGHT, *s_blackKeyPressedPm ); } else { p.drawPixmap( PIANO_X, y - ( first_white_key_height - WHITE_KEY_SMALL_HEIGHT ) - WHITE_KEY_SMALL_HEIGHT/2 - 1 - BLACK_KEY_HEIGHT, *s_blackKeyPm ); } // update y-pos y -= WHITE_KEY_BIG_HEIGHT; // reset white-counter white_cnt = 0; } else { // simple workaround for increasing x if there were // two white keys (e.g. between E and F) ++white_cnt; if( white_cnt > 1 ) { y -= WHITE_KEY_BIG_HEIGHT/2; } } ++key; } // erase the area below the piano, because there might be keys that // should be only half-visible p.fillRect( QRect( 0, keyAreaBottom(), WHITE_KEY_WIDTH, noteEditBottom()-keyAreaBottom() ), QColor( 0, 0, 0 ) ); // display note editing info QFont f = p.font(); f.setBold( false ); p.setFont( pointSize<10>( f ) ); p.setPen( QColor( 255, 255, 255) ); p.drawText( QRect( 0, keyAreaBottom(), WHITE_KEY_WIDTH, noteEditBottom() - keyAreaBottom() ), Qt::AlignCenter | Qt::TextWordWrap, m_nemStr.at( m_noteEditMode ) + ":" ); // set clipping area, because we are not allowed to paint over // keyboard... p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN ); // draw vertical raster // triplet mode occurs if the note duration isn't a multiple of 3 bool triplets = ( quantization() % 3 != 0 ); int spt = MidiTime::stepsPerTact(); float pp16th = (float)m_ppt / spt; int bpt = DefaultBeatsPerTact; if ( triplets ) { spt = static_cast(1.5 * spt); bpt = static_cast(bpt * 2.0/3.0); pp16th *= 2.0/3.0; } int tact_16th = m_currentPosition / bpt; const int offset = ( m_currentPosition % bpt ) * m_ppt / MidiTime::ticksPerTact(); bool show32nds = ( m_zoomingModel.value() > 3 ); // we need float here as odd time signatures might produce rounding // errors else and thus an unusable grid for( float x = WHITE_KEY_WIDTH - offset; x < width(); x += pp16th, ++tact_16th ) { if( x >= WHITE_KEY_WIDTH ) { // every tact-start needs to be a bright line if( tact_16th % spt == 0 ) { p.setPen( QColor( 0x7F, 0x7F, 0x7F ) ); } // normal line else if( tact_16th % 4 == 0 ) { p.setPen( QColor( 0x5F, 0x5F, 0x5F ) ); } // weak line else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } p.drawLine( (int)x, PR_TOP_MARGIN, (int)x, height() - PR_BOTTOM_MARGIN ); // extra 32nd's line if( show32nds ) { p.setPen( QColor( 0x22, 0x22, 0x22 ) ); p.drawLine( (int)(x + pp16th/2) , PR_TOP_MARGIN, (int)(x + pp16th/2), height() - PR_BOTTOM_MARGIN ); } } } // following code draws all notes in visible area // and the note editing stuff (volume, panning, etc) // setup selection-vars int sel_pos_start = m_selectStartTick; int sel_pos_end = m_selectStartTick+m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } int sel_key_start = m_selectStartKey - m_startKey + 1; int sel_key_end = sel_key_start + m_selectedKeys; if( sel_key_start > sel_key_end ) { qSwap( sel_key_start, sel_key_end ); } int y_base = keyAreaBottom() - 1; if( validPattern() == true ) { p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN ); const NoteVector & notes = m_pattern->notes(); const int visible_keys = ( keyAreaBottom()-keyAreaTop() ) / KEY_LINE_HEIGHT + 2; QPolygon editHandles; for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { int len_ticks = ( *it )->length(); if( len_ticks == 0 ) { continue; } else if( len_ticks < 0 ) { len_ticks = 4; } const int key = ( *it )->key() - m_startKey + 1; int pos_ticks = ( *it )->pos(); int note_width = len_ticks * m_ppt / MidiTime::ticksPerTact(); const int x = ( pos_ticks - m_currentPosition ) * m_ppt / MidiTime::ticksPerTact(); // skip this note if not in visible area at all if( !( x + note_width >= 0 && x <= width() - WHITE_KEY_WIDTH ) ) { continue; } // is the note in visible area? if( key > 0 && key <= visible_keys ) { // we've done and checked all, let's draw the // note drawNoteRect( p, x + WHITE_KEY_WIDTH, y_base - key * KEY_LINE_HEIGHT, note_width, *it ); } // draw note editing stuff int editHandleTop = 0; if( m_noteEditMode == NoteEditVolume ) { QColor color = QColor::fromHsv( 140, 221, qMin(255, 60 + ( *it )->getVolume() ) ); if( ( *it )->selected() ) { color.setRgb( 0x00, 0x40, 0xC0 ); } p.setPen( QPen( color, NE_LINE_WIDTH ) ); editHandleTop = noteEditBottom() - ( (float)( ( *it )->getVolume() - MinVolume ) ) / ( (float)( MaxVolume - MinVolume ) ) * ( (float)( noteEditBottom() - noteEditTop() ) ); p.drawLine( noteEditLeft() + x, editHandleTop, noteEditLeft() + x, noteEditBottom() ); } else if( m_noteEditMode == NoteEditPanning ) { QColor color( 0x99, 0xAF, 0xFF ); if( ( *it )->selected() ) { color.setRgb( 0x00, 0x40, 0xC0 ); } p.setPen( QPen( color, NE_LINE_WIDTH ) ); editHandleTop = noteEditBottom() - ( (float)( ( *it )->getPanning() - PanningLeft ) ) / ( (float)( (PanningRight - PanningLeft ) ) ) * ( (float)( noteEditBottom() - noteEditTop() ) ); p.drawLine( noteEditLeft() + x, noteEditTop() + ( (float)( noteEditBottom() - noteEditTop() ) ) / 2.0f, noteEditLeft() + x, editHandleTop ); } editHandles << QPoint( x + noteEditLeft(), editHandleTop+1 ); if( ( *it )->hasDetuningInfo() ) { drawDetuningInfo( p, *it, x + WHITE_KEY_WIDTH, y_base - key * KEY_LINE_HEIGHT ); } } p.setPen( QPen( QColor( 0x99, 0xAF, 0xFF ), NE_LINE_WIDTH+2 ) ); p.drawPoints( editHandles ); } else { QFont f = p.font(); f.setBold( true ); p.setFont( pointSize<14>( f ) ); p.setPen( QColor( 0x4A, 0xFD, 0x85 ) ); p.drawText( WHITE_KEY_WIDTH + 20, PR_TOP_MARGIN + 40, tr( "Please open a pattern by double-clicking " "on it!" ) ); } p.setClipRect( WHITE_KEY_WIDTH, PR_TOP_MARGIN, width() - WHITE_KEY_WIDTH, height() - PR_TOP_MARGIN - m_notesEditHeight - PR_BOTTOM_MARGIN ); // now draw selection-frame int x = ( ( sel_pos_start - m_currentPosition ) * m_ppt ) / MidiTime::ticksPerTact(); int w = ( ( ( sel_pos_end - m_currentPosition ) * m_ppt ) / MidiTime::ticksPerTact() ) - x; int y = (int) y_base - sel_key_start * KEY_LINE_HEIGHT; int h = (int) y_base - sel_key_end * KEY_LINE_HEIGHT - y; p.setPen( QColor( 0, 64, 192 ) ); p.drawRect( x + WHITE_KEY_WIDTH, y, w, h ); // TODO: Get this out of paint event int l = ( validPattern() == true )? (int) m_pattern->length() : 0; // reset scroll-range if( m_leftRightScroll->maximum() != l ) { m_leftRightScroll->setRange( 0, l ); m_leftRightScroll->setPageStep( l ); } // horizontal line for the key under the cursor if( validPattern() == true ) { int key_num = getKey( mapFromGlobal( QCursor::pos() ).y() ); p.fillRect( 10, keyAreaBottom() + 3 - KEY_LINE_HEIGHT * ( key_num - m_startKey + 1 ), width() - 10, KEY_LINE_HEIGHT - 7, QColor( 64, 64, 64 ) ); } // bar to resize note edit area p.setClipRect( 0, 0, width(), height() ); p.fillRect( QRect( 0, keyAreaBottom(), width()-PR_RIGHT_MARGIN, NOTE_EDIT_RESIZE_BAR ), QColor( 64, 64, 64 ) ); const QPixmap * cursor = NULL; // draw current edit-mode-icon below the cursor switch( m_editMode ) { case ModeDraw: if( m_mouseDownRight ) { cursor = s_toolErase; } else if( m_action == ActionMoveNote ) { cursor = s_toolMove; } else { cursor = s_toolDraw; } break; case ModeErase: cursor = s_toolErase; break; case ModeSelect: cursor = s_toolSelect; break; case ModeEditDetuning: cursor = s_toolOpen; break; } if( cursor != NULL ) { p.drawPixmap( mapFromGlobal( QCursor::pos() ) + QPoint( 8, 8 ), *cursor ); } if( configManager::inst()->value( "ui", "printnotelabels").toInt() ) { printNoteHeights(p, keyAreaBottom(), width(), m_startKey); } } // responsible for moving/resizing scrollbars after window-resizing void PianoRoll::resizeEvent( QResizeEvent * ) { m_leftRightScroll->setGeometry( WHITE_KEY_WIDTH, height() - SCROLLBAR_SIZE, width()-WHITE_KEY_WIDTH, SCROLLBAR_SIZE ); m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, PR_TOP_MARGIN, SCROLLBAR_SIZE, height() - PR_TOP_MARGIN - SCROLLBAR_SIZE ); int total_pixels = OCTAVE_HEIGHT * NumOctaves - ( height() - PR_TOP_MARGIN - PR_BOTTOM_MARGIN - m_notesEditHeight ); m_totalKeysToScroll = total_pixels * KeysPerOctave / OCTAVE_HEIGHT; m_topBottomScroll->setRange( 0, m_totalKeysToScroll ); if( m_startKey > m_totalKeysToScroll ) { m_startKey = m_totalKeysToScroll; } m_topBottomScroll->setValue( m_totalKeysToScroll - m_startKey ); engine::getSong()->getPlayPos( song::Mode_PlayPattern ).m_timeLine->setFixedWidth( width() ); m_toolBar->setFixedWidth( width() ); update(); } void PianoRoll::wheelEvent( QWheelEvent * _we ) { _we->accept(); if( _we->modifiers() & Qt::ControlModifier ) { if( _we->delta() > 0 ) { m_ppt = qMin( m_ppt * 2, KEY_LINE_HEIGHT * DefaultStepsPerTact * 8 ); } else if( m_ppt >= 72 ) { m_ppt /= 2; } // update combobox with zooming-factor m_zoomingModel.setValue( m_zoomingModel.findText( QString::number( static_cast( m_ppt * 100 / DEFAULT_PR_PPT ) ) +"%" ) ); // update timeline m_timeLine->setPixelsPerTact( m_ppt ); update(); } else if( _we->modifiers() & Qt::ShiftModifier || _we->orientation() == Qt::Horizontal ) { m_leftRightScroll->setValue( m_leftRightScroll->value() - _we->delta() * 2 / 15 ); } else { m_topBottomScroll->setValue( m_topBottomScroll->value() - _we->delta() / 30 ); } } int PianoRoll::getKey( int _y ) const { int key_line_y = keyAreaBottom() - 1; // pressed key on piano int key_num = ( key_line_y - _y ) / KEY_LINE_HEIGHT; key_num += m_startKey; // some range-checking-stuff if( key_num < 0 ) { key_num = 0; } if( key_num >= KeysPerOctave * NumOctaves ) { key_num = KeysPerOctave * NumOctaves - 1; } return key_num; } song::PlayModes PianoRoll::desiredPlayModeForAccompany() const { if( m_pattern->getTrack()->trackContainer() == engine::getBBTrackContainer() ) { return song::Mode_PlayBB; } return song::Mode_PlaySong; } void PianoRoll::play() { if( validPattern() == false ) { return; } if( engine::getSong()->playMode() != song::Mode_PlayPattern ) { engine::getSong()->playPattern( m_pattern ); } else { engine::getSong()->togglePause(); } } void PianoRoll::record() { if( engine::getSong()->isPlaying() ) { stop(); } if( m_recording == true || validPattern() == false ) { return; } m_recording = true; engine::getSong()->playPattern( m_pattern, false ); } void PianoRoll::recordAccompany() { if( engine::getSong()->isPlaying() ) { stop(); } if( m_recording == true || validPattern() == false ) { return; } m_recording = true; if( m_pattern->getTrack()->trackContainer() == engine::getSong() ) { engine::getSong()->playSong(); } else { engine::getSong()->playBB(); } } void PianoRoll::stop() { engine::getSong()->stop(); m_recording = false; m_scrollBack = true; } void PianoRoll::startRecordNote( const note & _n ) { if( m_recording == true && validPattern() == true && engine::getSong()->isPlaying() && ( engine::getSong()->playMode() == desiredPlayModeForAccompany() || engine::getSong()->playMode() == song::Mode_PlayPattern ) ) { MidiTime sub; if( engine::getSong()->playMode() == song::Mode_PlaySong ) { sub = m_pattern->startPosition(); } note n( 1, engine::getSong()->getPlayPos( engine::getSong()->playMode() ) - sub, _n.key(), _n.getVolume(), _n.getPanning() ); if( n.pos() >= 0 ) { m_recordingNotes << n; } } } void PianoRoll::finishRecordNote( const note & _n ) { if( m_recording == true && validPattern() == true && engine::getSong()->isPlaying() && ( engine::getSong()->playMode() == desiredPlayModeForAccompany() || engine::getSong()->playMode() == song::Mode_PlayPattern ) ) { for( QList::Iterator it = m_recordingNotes.begin(); it != m_recordingNotes.end(); ++it ) { if( it->key() == _n.key() ) { note n( _n.length(), it->pos(), it->key(), it->getVolume(), it->getPanning() ); n.quantizeLength( quantization() ); m_pattern->addNote( n ); update(); m_recordingNotes.erase( it ); break; } } } } void PianoRoll::horScrolled( int _new_pos ) { m_currentPosition = _new_pos; emit positionChanged( m_currentPosition ); update(); } void PianoRoll::verScrolled( int _new_pos ) { // revert value m_startKey = m_totalKeysToScroll - _new_pos; update(); } void PianoRoll::drawButtonToggled() { m_editMode = ModeDraw; update(); } void PianoRoll::eraseButtonToggled() { m_editMode = ModeErase; update(); } void PianoRoll::selectButtonToggled() { m_editMode = ModeSelect; update(); } void PianoRoll::detuneButtonToggled() { m_editMode = ModeEditDetuning; update(); } void PianoRoll::selectAll() { if( validPattern() == false ) { return; } const NoteVector & notes = m_pattern->notes(); // if first_time = true, we HAVE to set the vars for select bool first_time = true; for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { int len_ticks = ( *it )->length(); if( len_ticks > 0 ) { const int key = ( *it )->key(); int pos_ticks = ( *it )->pos(); if( key <= m_selectStartKey || first_time ) { // if we move start-key down, we have to add // the difference between old and new start-key // to m_selectedKeys, otherwise the selection // is just moved down... m_selectedKeys += m_selectStartKey - ( key - 1 ); m_selectStartKey = key - 1; } if( key >= m_selectedKeys+m_selectStartKey || first_time ) { m_selectedKeys = key - m_selectStartKey; } if( pos_ticks < m_selectStartTick || first_time ) { m_selectStartTick = pos_ticks; } if( pos_ticks + len_ticks > m_selectStartTick + m_selectedTick || first_time ) { m_selectedTick = pos_ticks + len_ticks - m_selectStartTick; } first_time = false; } } } // returns vector with pointers to all selected notes void PianoRoll::getSelectedNotes( NoteVector & _selected_notes ) { if( validPattern() == false ) { return; } const NoteVector & notes = m_pattern->notes(); for( NoteVector::ConstIterator it = notes.begin(); it != notes.end(); ++it ) { if( ( *it )->selected() ) { _selected_notes.push_back( *it ); } } } void PianoRoll::copy_to_clipboard( const NoteVector & _notes ) const { DataFile dataFile( DataFile::ClipboardData ); QDomElement note_list = dataFile.createElement( "note-list" ); dataFile.content().appendChild( note_list ); MidiTime start_pos( _notes.front()->pos().getTact(), 0 ); for( NoteVector::ConstIterator it = _notes.begin(); it != _notes.end(); ++it ) { note clip_note( **it ); clip_note.setPos( clip_note.pos( start_pos ) ); clip_note.saveState( dataFile, note_list ); } QMimeData * clip_content = new QMimeData; clip_content->setData( Clipboard::mimeType(), dataFile.toString().toUtf8() ); QApplication::clipboard()->setMimeData( clip_content, QClipboard::Clipboard ); } void PianoRoll::copySelectedNotes() { NoteVector selected_notes; getSelectedNotes( selected_notes ); if( selected_notes.empty() == false ) { copy_to_clipboard( selected_notes ); } } void PianoRoll::cutSelectedNotes() { if( validPattern() == false ) { return; } NoteVector selected_notes; getSelectedNotes( selected_notes ); if( selected_notes.empty() == false ) { copy_to_clipboard( selected_notes ); engine::getSong()->setModified(); for( NoteVector::Iterator it = selected_notes.begin(); it != selected_notes.end(); ++it ) { // note (the memory of it) is also deleted by // pattern::removeNote(...) so we don't have to do that m_pattern->removeNote( *it ); } } update(); engine::songEditor()->update(); } void PianoRoll::pasteNotes() { if( validPattern() == false ) { return; } QString value = QApplication::clipboard() ->mimeData( QClipboard::Clipboard ) ->data( Clipboard::mimeType() ); if( !value.isEmpty() ) { DataFile dataFile( value.toUtf8() ); QDomNodeList list = dataFile.elementsByTagName( note::classNodeName() ); // remove selection and select the newly pasted notes clearSelectedNotes(); for( int i = 0; !list.item( i ).isNull(); ++i ) { // create the note note cur_note; cur_note.restoreState( list.item( i ).toElement() ); cur_note.setPos( cur_note.pos() + m_timeLine->pos() ); // select it cur_note.setSelected( true ); // add to pattern m_pattern->addNote( cur_note ); } // we only have to do the following lines if we pasted at // least one note... engine::getSong()->setModified(); m_ctrlMode = ModeDraw; m_drawButton->setChecked( true ); update(); engine::songEditor()->update(); } } void PianoRoll::deleteSelectedNotes() { if( validPattern() == false ) { return; } bool update_after_delete = false; // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin(); while( it != notes.end() ) { if( ( *it )->selected() ) { // delete this note m_pattern->removeNote( ( *it ) ); update_after_delete = true; // start over, make sure we get all the notes it = notes.begin(); } else { ++it; } } if( update_after_delete == true ) { engine::getSong()->setModified(); update(); engine::songEditor()->update(); } } void PianoRoll::autoScroll( const MidiTime & _t ) { const int w = width() - WHITE_KEY_WIDTH; if( _t > m_currentPosition + w * MidiTime::ticksPerTact() / m_ppt ) { m_leftRightScroll->setValue( _t.getTact() * MidiTime::ticksPerTact() ); } else if( _t < m_currentPosition ) { MidiTime t = qMax( _t - w * MidiTime::ticksPerTact() * MidiTime::ticksPerTact() / m_ppt, 0 ); m_leftRightScroll->setValue( t.getTact() * MidiTime::ticksPerTact() ); } m_scrollBack = false; } void PianoRoll::updatePosition( const MidiTime & _t ) { if( ( engine::getSong()->isPlaying() && engine::getSong()->playMode() == song::Mode_PlayPattern && m_timeLine->autoScroll() == timeLine::AutoScrollEnabled ) || m_scrollBack == true ) { autoScroll( _t ); } } void PianoRoll::updatePositionAccompany( const MidiTime & _t ) { song * s = engine::getSong(); if( m_recording && validPattern() && s->playMode() != song::Mode_PlayPattern ) { MidiTime pos = _t; if( s->playMode() != song::Mode_PlayBB ) { pos -= m_pattern->startPosition(); } if( (int) pos > 0 ) { s->getPlayPos( song::Mode_PlayPattern ).setTicks( pos ); autoScroll( pos ); } } } void PianoRoll::zoomingChanged() { const QString & zfac = m_zoomingModel.currentText(); m_ppt = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_PR_PPT / 100; #ifdef LMMS_DEBUG assert( m_ppt > 0 ); #endif m_timeLine->setPixelsPerTact( m_ppt ); update(); } void PianoRoll::quantizeChanged() { if( m_quantizeModel.value() == 0 && m_noteLenModel.value() == 0 ) { m_quantizeModel.setValue( m_quantizeModel.findText( "1/16" ) ); return; } // Could be smarter update(); } int PianoRoll::quantization() const { if( m_quantizeModel.value() == 0 ) { return newNoteLen(); } return DefaultTicksPerTact / m_quantizeModel.currentText().right( m_quantizeModel.currentText().length() - 2 ).toInt(); } void PianoRoll::updateSemiToneMarkerMenu() { const InstrumentFunctionNoteStacking::Chord & scale = InstrumentFunctionNoteStacking::ChordTable::getInstance() .getScaleByName( m_scaleModel.currentText() ); const InstrumentFunctionNoteStacking::Chord & chord = InstrumentFunctionNoteStacking::ChordTable::getInstance() .getChordByName( m_chordModel.currentText() ); emit semiToneMarkerMenuScaleSetEnabled( ! scale.isEmpty() ); emit semiToneMarkerMenuChordSetEnabled( ! chord.isEmpty() ); } MidiTime PianoRoll::newNoteLen() const { if( m_noteLenModel.value() == 0 ) { return m_lenOfNewNotes; } return DefaultTicksPerTact / m_noteLenModel.currentText().right( m_noteLenModel.currentText().length() - 2 ).toInt(); } bool PianoRoll::mouseOverNote() { return validPattern() && noteUnderMouse() != NULL; } note * PianoRoll::noteUnderMouse() { QPoint pos = mapFromGlobal( QCursor::pos() ); // get note-vector of current pattern const NoteVector & notes = m_pattern->notes(); if( pos.x() <= WHITE_KEY_WIDTH || pos.x() > width() - SCROLLBAR_SIZE || pos.y() < PR_TOP_MARGIN || pos.y() > keyAreaBottom() ) { return NULL; } int key_num = getKey( pos.y() ); int pos_ticks = ( pos.x() - WHITE_KEY_WIDTH ) * MidiTime::ticksPerTact() / m_ppt + m_currentPosition; // will be our iterator in the following loop NoteVector::ConstIterator it = notes.begin()+notes.size()-1; // loop through whole note-vector... int i; for( i = 0; i < notes.size(); ++i ) { // and check whether the cursor is over an // existing note if( pos_ticks >= ( *it )->pos() && pos_ticks <= ( *it )->pos() + ( *it )->length() && ( *it )->key() == key_num && ( *it )->length() > 0 ) { break; } --it; } if( i == notes.size() ) { return NULL; } return *it; } #include "moc_PianoRoll.cxx" lmms-1.0.0/src/gui/PeakControllerDialog.cpp0000644000175000017500000000442312313663627017333 0ustar tobytoby/* * PeakController_Dialog.cpp - per-controller-specific view for changing a * controller's settings * * Copyright (c) 2008-2009 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include "caption_menu.h" #include "gui_templates.h" #include "embed.h" #include "engine.h" #include "MainWindow.h" #include "tooltip.h" #include "PeakController.h" #include "ControllerDialog.h" #include "knob.h" #include "TempoSyncKnob.h" #include "pixmap_button.h" PeakControllerDialog::PeakControllerDialog( Controller * _model, QWidget * _parent ) : ControllerDialog( _model, _parent ) { setWindowTitle( tr( "PEAK" ) ); setWindowIcon( embed::getIconPixmap( "controller" ) ); setFixedSize( 256, 64 ); toolTip::add( this, tr( "LFO Controller" ) ); QLabel * l = new QLabel( this ); l->setText( "Use FX's controls" ); l->move(10, 10); setModel( _model ); } PeakControllerDialog::~PeakControllerDialog() { } /* void effectView::displayHelp() { QWhatsThis::showText( mapToGlobal( rect().bottomRight() ), whatsThis() ); } void effectView::closeEffects() { m_subWindow->hide(); m_show = true; } */ void PeakControllerDialog::contextMenuEvent( QContextMenuEvent * ) { } void PeakControllerDialog::paintEvent( QPaintEvent * ) { QPainter p( this ); } void PeakControllerDialog::modelChanged() { m_peakController = castModel(); } lmms-1.0.0/src/gui/about_dialog.cpp0000644000175000017500000000402012313663627015711 0ustar tobytoby/* * about_dialog.cpp - implementation of about-dialog * * Copyright (c) 2004-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "lmmsversion.h" #include "about_dialog.h" #include "embed.h" #include "engine.h" #include "MainWindow.h" #include "versioninfo.h" aboutDialog::aboutDialog() : QDialog( engine::mainWindow() ), Ui::AboutDialog() { setupUi( this ); iconLabel->setPixmap( embed::getIconPixmap( "icon", 64, 64 ) ); versionLabel->setText( versionLabel->text(). arg( LMMS_VERSION ). arg( PLATFORM ). arg( MACHINE ). arg( QT_VERSION_STR ). arg( GCC_VERSION ) ); authorLabel->setPlainText( embed::getText( "AUTHORS" ) ); licenseLabel->setPlainText( embed::getText( "COPYING" ) ); QString contText = embed::getText( "CONTRIBUTORS" ); if ( contText.length() >= 2 ) { QWidget *widget = new QWidget(); QVBoxLayout *layout = new QVBoxLayout(); QTextEdit *contWidget = new QTextEdit(); contWidget->setReadOnly(true); contWidget->setText( contText ); layout->addWidget( new QLabel( tr("Contributors ordered by number of commits:"), this ) ); layout->addWidget( contWidget ); widget->setLayout( layout ); tabWidget->insertTab( 2, widget, tr("Involved") ); } } lmms-1.0.0/src/gui/LmmsStyle.cpp0000644000175000017500000005622112313663627015223 0ustar tobytoby/* * LmmsStyle.cpp - the graphical style used by LMMS to create a consistent * interface * * Copyright (c) 2007-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "LmmsStyle.h" const int BUTTON_LENGTH = 24; static const char * const s_scrollbarArrowUpXpm[] = { "7 6 8 1", " g None", ". g #000000", "+ g #101010", "@ g #A0A0A0", "# g #C0C0C0", "$ g #FFFFFF", "% g #808080", "& g #202020", "..+@+..", "..#$#..", ".%$$$%.", "&$$$$$&", "@$$$$$@", "@#####@"}; static const char * const s_scrollbarArrowRightXpm[] = { "6 7 8 1", " c None", ". c #A0A0A0", "+ c #202020", "@ c #000000", "# c #C0C0C0", "$ c #FFFFFF", "% c #808080", "& c #101010", "..+@@@", "#$$%@@", "#$$$#&", "#$$$$.", "#$$$#&", "#$$%@@", "..+@@@"}; static const char * const s_scrollbarArrowDownXpm[] = { "7 6 8 1", " g None", ". g #000000", "+ g #101010", "@ g #A0A0A0", "# g #C0C0C0", "$ g #FFFFFF", "% g #808080", "& g #202020", "@#####@", "@$$$$$@", "&$$$$$&", ".%$$$%.", "..#$#..", "..+@+.."}; static const char * const s_scrollbarArrowLeftXpm[] = { "6 7 8 1", " g None", ". g #000000", "+ g #202020", "@ g #A0A0A0", "# g #808080", "$ g #FFFFFF", "% g #C0C0C0", "& g #101010", "...+@@", "..#$$%", "&%$$$%", "@$$$$%", "&%$$$%", "..#$$%", "...+@@"}; QLinearGradient getGradient( const QColor & _col, const QRectF & _rect ) { QLinearGradient g( _rect.topLeft(), _rect.bottomLeft() ); qreal hue = _col.hueF(); qreal value = _col.valueF(); qreal saturation = _col.saturationF(); QColor c = _col; c.setHsvF( hue, 0.42 * saturation, 0.98 * value ); // TODO: pattern: 1.08 g.setColorAt( 0, c ); c.setHsvF( hue, 0.58 * saturation, 0.95 * value ); // TODO: pattern: 1.05 g.setColorAt( 0.25, c ); c.setHsvF( hue, 0.70 * saturation, 0.93 * value ); // TODO: pattern: 1.03 g.setColorAt( 0.5, c ); c.setHsvF( hue, 0.95 * saturation, 0.9 * value ); g.setColorAt( 0.501, c ); c.setHsvF( hue * 0.95, 0.95 * saturation, 0.95 * value ); g.setColorAt( 0.75, c ); c.setHsvF( hue * 0.90, 0.95 * saturation, 1 * value ); g.setColorAt( 1.0, c ); return g; } QLinearGradient darken( const QLinearGradient & _gradient ) { QGradientStops stops = _gradient.stops(); for (int i = 0; i < stops.size(); ++i) { QColor color = stops.at(i).second; stops[i].second = color.lighter(133); } QLinearGradient g = _gradient; g.setStops(stops); return g; } void drawPath( QPainter *p, const QPainterPath &path, const QColor &col, const QColor &borderCol, bool dark = false ) { const QRectF pathRect = path.boundingRect(); const QLinearGradient baseGradient = getGradient(col, pathRect); const QLinearGradient darkGradient = darken(baseGradient); p->setOpacity(0.25); // glow if (dark) p->strokePath(path, QPen(darkGradient, 4)); else p->strokePath(path, QPen(baseGradient, 4)); p->setOpacity(1.0); // fill if (dark) p->fillPath(path, darkGradient); else p->fillPath(path, baseGradient); // TODO: Remove?? /* QLinearGradient g(pathRect.topLeft(), pathRect.topRight()); g.setCoordinateMode(QGradient::ObjectBoundingMode); p->setOpacity(0.2); p->fillPath(path, g);*/ // END: Remove?? p->setOpacity(0.5); // highlight (bb) if (dark) p->strokePath(path, QPen(borderCol.lighter(133), 2)); else p->strokePath(path, QPen(borderCol, 2)); } LmmsStyle::LmmsStyle() : QPlastiqueStyle() { QFile file( "resources:style.css" ); file.open( QIODevice::ReadOnly ); qApp->setStyleSheet( file.readAll() ); qApp->setPalette( standardPalette() ); } QPalette LmmsStyle::standardPalette( void ) const { QPalette pal = QPlastiqueStyle::standardPalette(); /* sane defaults in case fetching from stylesheet fails*/ pal.setColor( QPalette::Background, QColor( 91, 101, 113 ) ); pal.setColor( QPalette::WindowText, QColor( 240, 240, 240 ) ); pal.setColor( QPalette::Base, QColor( 128, 128, 128 ) ); pal.setColor( QPalette::Text, QColor( 224, 224, 224 ) ); pal.setColor( QPalette::Button, QColor( 201, 201, 201 ) ); pal.setColor( QPalette::Shadow, QColor( 0, 0, 0 ) ); pal.setColor( QPalette::ButtonText, QColor( 0, 0, 0 ) ); pal.setColor( QPalette::BrightText, QColor( 74, 253, 133 ) ); pal.setColor( QPalette::Highlight, QColor( 100, 100, 100 ) ); pal.setColor( QPalette::HighlightedText, QColor( 255, 255, 255 ) ); /* fetch from stylesheet using regexp */ QStringList paletteData = qApp->styleSheet().split( '\n' ).filter( QRegExp( "^palette:*" ) ); foreach( QString s, paletteData ) { if (s.contains(":background")) { pal.setColor( QPalette::Background, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":windowtext")) { pal.setColor( QPalette::WindowText, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":base")) { pal.setColor( QPalette::Base, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":buttontext")) { pal.setColor( QPalette::ButtonText, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":brighttext")) { pal.setColor( QPalette::BrightText, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":text")) { pal.setColor( QPalette::Text, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":button")) { pal.setColor( QPalette::Button, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":shadow")) { pal.setColor( QPalette::Shadow, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":highlightedtext")) { pal.setColor( QPalette::HighlightedText, QColor( s.mid( s.indexOf("#"), 7 ) ) ); } else if (s.contains(":highlight")) { pal.setColor( QPalette::Highlight, QColor( s.mid( s.indexOf("#"), 7 ) ) ); }; } return( pal ); } /* void LmmsStyle::drawControl( ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget ) const { switch( element ) { case CE_ScrollBarAddLine: if( const QStyleOptionSlider * scrollBar = qstyleoption_cast( option ) ) { bool horizontal = scrollBar->orientation == Qt::Horizontal; bool isEnabled = scrollBar->state & State_Enabled; bool sunken = scrollBar->state & State_Sunken; bool hover = scrollBar->state & State_MouseOver; QString pixmapName = getCacheKey( QLatin1String( "ScrollBarAddLine" ), option, option->rect.size() ); QPixmap cache; if( !QPixmapCache::find( pixmapName, cache ) ) { cache = QPixmap( option->rect.size() ); QPainter cachePainter( &cache ); cache.fill( QColor( 48, 48, 48 ) ); QColor sliderColor; QColor blurColor; hoverColors(sunken, hover, scrollBar->activeSubControls & SC_ScrollBarAddLine && isEnabled, sliderColor, blurColor); int scrollBarExtent = pixelMetric( PM_ScrollBarExtent, option, widget ); cachePainter.setPen( QPen( sliderColor, 0 ) ); if( horizontal ) { int r = cache.rect().right(); cachePainter.fillRect(0, 2, BUTTON_LENGTH-2, scrollBarExtent-4, sliderColor); cachePainter.drawLine( r, 4, r, scrollBarExtent - 5 ); cachePainter.drawLine( r - 1, 3, r - 1, scrollBarExtent - 4 ); const QPointF points[4] = { QPoint( r, 3 ), QPoint( r, scrollBarExtent - 4 ), QPoint( r - 1, 2 ), QPoint( r - 1, scrollBarExtent - 3 )}; cachePainter.setPen( QPen( blurColor, 0 ) ); cachePainter.drawPoints( points, 4 ); QImage arrow = colorizeXpm( s_scrollbarArrowRightXpm, option->palette.foreground().color() ); if( ( scrollBar->activeSubControls & SC_ScrollBarAddLine ) && sunken ) { cachePainter.translate( 1, 1 ); } cachePainter.drawImage( cache.rect().center() - QPoint( 3, 3 ), arrow ); } else { int b = cache.rect().bottom(); cachePainter.fillRect( 2, 0, scrollBarExtent - 4, BUTTON_LENGTH - 2, sliderColor ); cachePainter.drawLine( 4, b, scrollBarExtent - 5, b ); cachePainter.drawLine( 3, b - 1, scrollBarExtent - 4, b - 1 ); const QPointF points[4] = { QPoint( 3, b ), QPoint( scrollBarExtent - 4, b ), QPoint( 2, b - 1 ), QPoint( scrollBarExtent - 3, b - 1 )}; cachePainter.setPen( QPen( blurColor, 0 ) ); cachePainter.drawPoints( points, 4 ); QImage arrow = colorizeXpm( s_scrollbarArrowDownXpm, option->palette.foreground().color() ); if( ( scrollBar->activeSubControls & SC_ScrollBarAddLine ) && sunken ) { cachePainter.translate( 1, 1 ); } cachePainter.drawImage( cache.rect().center() - QPoint( 3, 3 ), arrow ); } QPixmapCache::insert( pixmapName, cache ); } painter->drawPixmap( option->rect.topLeft(), cache ); } break; case CE_ScrollBarSubLine: if(const QStyleOptionSlider *scrollBar = qstyleoption_cast( option ) ) { bool horizontal = scrollBar->orientation == Qt::Horizontal; bool isEnabled = scrollBar->state & State_Enabled; bool sunken = scrollBar->state & State_Sunken; bool hover = scrollBar->state & State_MouseOver; QString pixmapName = getCacheKey( QLatin1String( "ScrollBarSubLine" ), option, option->rect.size() ); QPixmap cache; if( !QPixmapCache::find( pixmapName, cache ) ) { cache = QPixmap( option->rect.size() ); QPainter cachePainter( &cache ); cache.fill( QColor( 48, 48, 48 ) ); // TODO: Fill with CSS background QColor sliderColor; QColor blurColor; hoverColors(sunken, hover, scrollBar->activeSubControls & SC_ScrollBarSubLine && isEnabled, sliderColor, blurColor); int scrollBarExtent = pixelMetric( PM_ScrollBarExtent, option, widget ); cachePainter.setPen( QPen( sliderColor, 0 ) ); if( horizontal ) { cachePainter.fillRect( 2, 2, BUTTON_LENGTH - 2, scrollBarExtent - 4, sliderColor ); cachePainter.drawLine( 0, 4, 0, scrollBarExtent - 5 ); cachePainter.drawLine( 1, 3, 1, scrollBarExtent - 4 ); const QPointF points[4] = { QPoint( 0, 3 ), QPoint( 0, scrollBarExtent - 4 ), QPoint( 1, 2 ), QPoint( 1, scrollBarExtent - 3 )}; cachePainter.setPen( QPen( blurColor, 0 ) ); cachePainter.drawPoints( points, 4 ); QImage arrow = colorizeXpm( s_scrollbarArrowLeftXpm, option->palette.foreground().color() ); if( ( scrollBar->activeSubControls & SC_ScrollBarSubLine ) && sunken ) { cachePainter.translate( 1, 1 ); } cachePainter.drawImage( cache.rect().center() - QPoint( 3, 3 ), arrow ); } else { cachePainter.fillRect( 2, 2, scrollBarExtent - 4, BUTTON_LENGTH - 2, sliderColor ); cachePainter.drawLine( 4, 0, scrollBarExtent - 5, 0 ); cachePainter.drawLine( 3, 1, scrollBarExtent - 4, 1 ); const QPointF points[4] = { QPoint( 3, 0 ), QPoint( scrollBarExtent - 4, 0 ), QPoint( 2, 1 ), QPoint( scrollBarExtent - 3, 1 )}; cachePainter.setPen( QPen( blurColor, 0 ) ); cachePainter.drawPoints( points, 4 ); QImage arrow = colorizeXpm( s_scrollbarArrowUpXpm, option->palette.foreground().color() ); if( ( scrollBar->activeSubControls & SC_ScrollBarSubLine ) && sunken ) { cachePainter.translate( 1, 1 ); } cachePainter.drawImage( cache.rect().center() - QPoint( 3, 3 ), arrow ); } QPixmapCache::insert( pixmapName, cache ); } painter->drawPixmap( option->rect.topLeft(), cache ); } break; case CE_ScrollBarSubPage: case CE_ScrollBarAddPage: break; case CE_ScrollBarSlider: if( const QStyleOptionSlider* scrollBar = qstyleoption_cast( option ) ) { bool horizontal = scrollBar->orientation == Qt::Horizontal; bool isEnabled = scrollBar->state & State_Enabled; bool sunken = scrollBar->state & State_Sunken; bool hover = scrollBar->state & State_MouseOver; QColor sliderColor, blendColor; hoverColors( sunken, hover, (scrollBar->activeSubControls & SC_ScrollBarSlider) && isEnabled, sliderColor, blendColor); QRect rc = scrollBar->rect; if( horizontal ) { painter->fillRect( rc.left(), rc.top() + 2, rc.width(), rc.height() - 4, sliderColor ); } else { painter->fillRect( rc.left() + 2, rc.top(), rc.width() - 4, rc.height(), sliderColor ); } } break; default: QPlastiqueStyle::drawControl( element, option, painter, widget ); break; } } */ void LmmsStyle::drawComplexControl( ComplexControl control, const QStyleOptionComplex * option, QPainter *painter, const QWidget *widget ) const { // fix broken titlebar styling on win32 if( control == CC_TitleBar ) { const QStyleOptionTitleBar * titleBar = qstyleoption_cast(option ); if( titleBar ) { QStyleOptionTitleBar so( *titleBar ); so.palette = standardPalette(); so.palette.setColor( QPalette::HighlightedText, ( titleBar->titleBarState & State_Active ) ? QColor( 255, 255, 255 ) : QColor( 192, 192, 192 ) ); so.palette.setColor( QPalette::Text, QColor( 64, 64, 64 ) ); QPlastiqueStyle::drawComplexControl( control, &so, painter, widget ); return; } } /* else if( control == CC_ScrollBar ) { painter->fillRect( option->rect, QApplication::palette().color( QPalette::Active, QPalette::Background ) ); }*/ QPlastiqueStyle::drawComplexControl( control, option, painter, widget ); } void LmmsStyle::drawPrimitive( PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if( element == QStyle::PE_Frame || element == QStyle::PE_FrameLineEdit || element == QStyle::PE_PanelLineEdit ) { const QRect rect = option->rect; QColor black = QColor( 0, 0, 0 ); QColor shadow = option->palette.shadow().color(); QColor highlight = option->palette.highlight().color(); int a100 = 165; int a75 = static_cast( a100 * .75 ); int a50 = static_cast( a100 * .6 ); int a25 = static_cast( a100 * .33 ); QLine lines[4]; QPoint points[4]; // black inside lines // 50% black.setAlpha(a100); painter->setPen(QPen(black, 0)); lines[0] = QLine(rect.left() + 2, rect.top() + 1, rect.right() - 2, rect.top() + 1); lines[1] = QLine(rect.left() + 2, rect.bottom() - 1, rect.right() - 2, rect.bottom() - 1); lines[2] = QLine(rect.left() + 1, rect.top() + 2, rect.left() + 1, rect.bottom() - 2); lines[3] = QLine(rect.right() - 1, rect.top() + 2, rect.right() - 1, rect.bottom() - 2); painter->drawLines(lines, 4); // black inside dots black.setAlpha(a50); painter->setPen(QPen(black, 0)); points[0] = QPoint(rect.left() + 2, rect.top() + 2); points[1] = QPoint(rect.left() + 2, rect.bottom() - 2); points[2] = QPoint(rect.right() - 2, rect.top() + 2); points[3] = QPoint(rect.right() - 2, rect.bottom() - 2); painter->drawPoints(points, 4); // outside lines - shadow // 100% shadow.setAlpha(a75); painter->setPen(QPen(shadow, 0)); lines[0] = QLine(rect.left() + 2, rect.top(), rect.right() - 2, rect.top()); lines[1] = QLine(rect.left(), rect.top() + 2, rect.left(), rect.bottom() - 2); painter->drawLines(lines, 2); // outside corner dots - shadow // 75% shadow.setAlpha(a50); painter->setPen(QPen(shadow, 0)); points[0] = QPoint(rect.left() + 1, rect.top() + 1); points[1] = QPoint(rect.right() - 1, rect.top() + 1); painter->drawPoints(points, 2); // outside end dots - shadow // 50% shadow.setAlpha(a25); painter->setPen(QPen(shadow, 0)); points[0] = QPoint(rect.left() + 1, rect.top()); points[1] = QPoint(rect.left(), rect.top() + 1); points[2] = QPoint(rect.right() - 1, rect.top()); points[3] = QPoint(rect.left(), rect.bottom() - 1); painter->drawPoints(points, 4); // outside lines - highlight // 100% highlight.setAlpha(a75); painter->setPen(QPen(highlight, 0)); lines[0] = QLine(rect.left() + 2, rect.bottom(), rect.right() - 2, rect.bottom()); lines[1] = QLine(rect.right(), rect.top() + 2, rect.right(), rect.bottom() - 2); painter->drawLines(lines, 2); // outside corner dots - highlight // 75% highlight.setAlpha(a50); painter->setPen(QPen(highlight, 0)); points[0] = QPoint(rect.left() + 1, rect.bottom() - 1); points[1] = QPoint(rect.right() - 1, rect.bottom() - 1); painter->drawPoints(points, 2); // outside end dots - highlight // 50% highlight.setAlpha(a25); painter->setPen(QPen(highlight, 0)); points[0] = QPoint(rect.right() - 1, rect.bottom()); points[1] = QPoint(rect.right(), rect.bottom() - 1); points[2] = QPoint(rect.left() + 1, rect.bottom()); points[3] = QPoint(rect.right(), rect.top() + 1); painter->drawPoints(points, 4); } else { QPlastiqueStyle::drawPrimitive( element, option, painter, widget ); } } int LmmsStyle::pixelMetric( PixelMetric _metric, const QStyleOption * _option, const QWidget * _widget ) const { switch( _metric ) { case QStyle::PM_ButtonMargin: return 3; case QStyle::PM_ButtonIconSize: return 20; case QStyle::PM_ToolBarItemMargin: return 1; case QStyle::PM_ToolBarItemSpacing: return 2; case QStyle::PM_TitleBarHeight: return 24; default: return QPlastiqueStyle::pixelMetric( _metric, _option, _widget ); } } // QStyle::SH_TitleBar_NoBorder /* QSize LmmsStyle::sizeFromContents( ContentsType type, const QStyleOption* option, const QSize& size, const QWidget* widget ) const { if( type == CT_ScrollBar ) { if( const QStyleOptionSlider * scrollBar = qstyleoption_cast( option ) ) { int scrollBarExtent = pixelMetric( PM_ScrollBarExtent, option, widget ); int scrollBarSliderMinimum = pixelMetric( PM_ScrollBarSliderMin, option, widget ); if( scrollBar->orientation == Qt::Horizontal ) { return QSize( BUTTON_LENGTH * 2 + scrollBarSliderMinimum, scrollBarExtent ); } else { return QSize( scrollBarExtent, BUTTON_LENGTH * 2 + scrollBarSliderMinimum ); } } } return QPlastiqueStyle::sizeFromContents( type, option, size, widget ); } */ /* QRect LmmsStyle::subControlRect( ComplexControl control, const QStyleOptionComplex* option, SubControl subControl, const QWidget* widget ) const { QRect rect = QPlastiqueStyle::subControlRect( control, option, subControl, widget ); switch( control ) { case CC_ScrollBar: if( const QStyleOptionSlider* scrollBar = qstyleoption_cast( option ) ) { int scrollBarExtent = pixelMetric( PM_ScrollBarExtent, scrollBar, widget ); int sliderMaxLength = ( ( scrollBar->orientation == Qt::Horizontal ) ? scrollBar->rect.width() : scrollBar->rect.height() ) - ( BUTTON_LENGTH * 2 + 6 ); int sliderMinLength = pixelMetric( PM_ScrollBarSliderMin, scrollBar, widget ); int sliderLength; // calculate slider length if( scrollBar->maximum != scrollBar->minimum ) { uint valueRange = scrollBar->maximum - scrollBar->minimum; sliderLength = ( scrollBar->pageStep * sliderMaxLength ) / ( valueRange + scrollBar->pageStep ); if( sliderLength < sliderMinLength || valueRange > ( INT_MAX ) / 2 ) { sliderLength = sliderMinLength; } if( sliderLength > sliderMaxLength ) { sliderLength = sliderMaxLength; } } else { sliderLength = sliderMaxLength; } int sliderStart = BUTTON_LENGTH + 3 + sliderPositionFromValue( scrollBar->minimum, scrollBar->maximum, scrollBar->sliderPosition, sliderMaxLength - sliderLength, scrollBar->upsideDown ); QRect scrollBarRect = scrollBar->rect; switch( subControl ) { case SC_ScrollBarSubLine: // top/left button if( scrollBar->orientation == Qt::Horizontal ) { rect.setRect( scrollBarRect.left() + 2, scrollBarRect.top(), BUTTON_LENGTH, scrollBarExtent ); } else { rect.setRect( scrollBarRect.left(), scrollBarRect.top() + 2, scrollBarExtent, BUTTON_LENGTH ); } break; case SC_ScrollBarAddLine: // bottom/right button if( scrollBar->orientation == Qt::Horizontal ) { rect.setRect( scrollBarRect.right() - 1 - BUTTON_LENGTH, scrollBarRect.top(), BUTTON_LENGTH, scrollBarExtent ); } else { rect.setRect( scrollBarRect.left(), scrollBarRect.bottom() - 1 - BUTTON_LENGTH, scrollBarExtent, BUTTON_LENGTH ); } break; case SC_ScrollBarSubPage: if( scrollBar->orientation == Qt::Horizontal ) { rect.setRect( scrollBarRect.left() + 2 + BUTTON_LENGTH, scrollBarRect.top(), sliderStart - ( scrollBarRect.left() + 2 + BUTTON_LENGTH ), scrollBarExtent ); } else { rect.setRect( scrollBarRect.left(), scrollBarRect.top() + 2 + BUTTON_LENGTH, scrollBarExtent, sliderStart - ( scrollBarRect.left() + 2 + BUTTON_LENGTH ) ); } break; case SC_ScrollBarAddPage: if( scrollBar->orientation == Qt::Horizontal ) { rect.setRect( sliderStart + sliderLength, 0, sliderMaxLength - sliderStart - sliderLength, scrollBarExtent ); } else rect.setRect( 0, sliderStart + sliderLength, scrollBarExtent, sliderMaxLength - sliderStart - sliderLength ); break; case SC_ScrollBarGroove: if( scrollBar->orientation == Qt::Horizontal ) { rect = scrollBarRect.adjusted( BUTTON_LENGTH, 0, -BUTTON_LENGTH, 0 ); } else { rect = scrollBarRect.adjusted( 0, BUTTON_LENGTH, 0, -BUTTON_LENGTH ); } break; case SC_ScrollBarSlider: if( scrollBar->orientation == Qt::Horizontal ) { rect.setRect( sliderStart, 0, sliderLength, scrollBarExtent ); } else { rect.setRect( 0, sliderStart, scrollBarExtent, sliderLength ); } break; default: break; } rect = visualRect( scrollBar->direction, scrollBarRect, rect ); } break; default: break; } return rect; } */ QImage LmmsStyle::colorizeXpm( const char * const * xpm, const QBrush& fill ) const { QImage arrowXpm( xpm ); QImage arrow( arrowXpm.size(), QImage::Format_ARGB32 ); QPainter arrowPainter( &arrow ); arrowPainter.fillRect( arrow.rect(), fill ); arrowPainter.end(); arrow.setAlphaChannel( arrowXpm ); return arrow; } void LmmsStyle::hoverColors( bool sunken, bool hover, bool active, QColor& color, QColor& blend ) const { if( active ) { if( sunken ) { color = QColor( 75, 75, 75 ); blend = QColor( 65, 65, 65 ); } else if( hover ) { color = QColor( 100, 100, 100 ); blend = QColor( 75, 75, 75 ); } else { color = QColor( 21, 21, 21 ); blend = QColor( 33, 33, 33 ); } } else { color = QColor( 21, 21, 21 ); blend = QColor( 33, 33, 33 ); } } lmms-1.0.0/src/gui/string_pair_drag.cpp0000644000175000017500000000522712313663627016610 0ustar tobytoby/* * string_pair_drag.cpp - class stringPairDrag which provides general support * for drag'n'drop of string-pairs and which is the base * for all drag'n'drop-actions within LMMS * * Copyright (c) 2005-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "string_pair_drag.h" #include "engine.h" #include "MainWindow.h" stringPairDrag::stringPairDrag( const QString & _key, const QString & _value, const QPixmap & _icon, QWidget * _w ) : QDrag( _w ) { if( _icon.isNull() && _w ) { setPixmap( QPixmap::grabWidget( _w ).scaled( 64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation ) ); } else { setPixmap( _icon ); } QString txt = _key + ":" + _value; QMimeData * m = new QMimeData(); m->setData( mimeType(), txt.toAscii() ); setMimeData( m ); start( Qt::IgnoreAction ); } stringPairDrag::~stringPairDrag() { // during a drag, we might have lost key-press-events, so reset // modifiers of main-win if( engine::mainWindow() ) { engine::mainWindow()->clearKeyModifiers(); } } bool stringPairDrag::processDragEnterEvent( QDragEnterEvent * _dee, const QString & _allowed_keys ) { if( !_dee->mimeData()->hasFormat( mimeType() ) ) { return( FALSE ); } QString txt = _dee->mimeData()->data( mimeType() ); if( _allowed_keys.split( ',' ).contains( txt.section( ':', 0, 0 ) ) ) { _dee->acceptProposedAction(); return( TRUE ); } _dee->ignore(); return( FALSE ); } QString stringPairDrag::decodeKey( QDropEvent * _de ) { return( QString( _de->mimeData()->data( mimeType() ) ).section( ':', 0, 0 ) ); } QString stringPairDrag::decodeValue( QDropEvent * _de ) { return( QString( _de->mimeData()->data( mimeType() ) ).section( ':', 1, -1 ) ); } lmms-1.0.0/src/gui/PianoView.cpp0000644000175000017500000006026412313663627015175 0ustar tobytoby/* * Piano.cpp - implementation of piano-widget used in instrument-track-window * for testing + according model class * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ /** \file piano.cpp * \brief A piano keyboard to play notes on in the instrument plugin window. */ /* * \mainpage Instrument plugin keyboard display classes * * \section introduction Introduction * * \todo fill this out * \todo write isWhite inline function and replace throughout */ #include #include #include #include #include #include "PianoView.h" #include "caption_menu.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "InstrumentTrack.h" #include "knob.h" #include "string_pair_drag.h" #include "MainWindow.h" #include "MidiEvent.h" #include "templates.h" #include "update_event.h" /*! The black / white order of keys as they appear on the keyboard. */ const Piano::KeyTypes KEY_ORDER[] = { // C CIS D DIS Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey, Piano::BlackKey, // E F FIS G Piano::WhiteKey, Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey, // GIS A B H Piano::BlackKey, Piano::WhiteKey, Piano::BlackKey, Piano::WhiteKey } ; /*! The scale of C Major - white keys only. */ Keys WhiteKeys[] = { Key_C, Key_D, Key_E, Key_F, Key_G, Key_A, Key_H } ; QPixmap * PianoView::s_whiteKeyPm = NULL; /*!< A white key released */ QPixmap * PianoView::s_blackKeyPm = NULL; /*!< A black key released */ QPixmap * PianoView::s_whiteKeyPressedPm = NULL; /*!< A white key pressed */ QPixmap * PianoView::s_blackKeyPressedPm = NULL; /*!< A black key pressed */ const int PIANO_BASE = 11; /*!< The height of the root note display */ const int PW_WHITE_KEY_WIDTH = 10; /*!< The width of a white key */ const int PW_BLACK_KEY_WIDTH = 8; /*!< The width of a black key */ const int PW_WHITE_KEY_HEIGHT = 57; /*!< The height of a white key */ const int PW_BLACK_KEY_HEIGHT = 38; /*!< The height of a black key */ const int LABEL_TEXT_SIZE = 7; /*!< The height of the key label text */ /*! \brief Create a new keyboard display view * * \param _parent the parent instrument plugin window * \todo are the descriptions of the m_startkey and m_lastkey properties correct? */ PianoView::PianoView( QWidget * _parent ) : QWidget( _parent ), /*!< Our parent */ ModelView( NULL, this ), /*!< Our view Model */ m_piano( NULL ), /*!< Our piano Model */ m_startKey( Key_C + Octave_3*KeysPerOctave ), /*!< The first key displayed? */ m_lastKey( -1 ) /*!< The last key displayed? */ { if( s_whiteKeyPm == NULL ) { s_whiteKeyPm = new QPixmap( embed::getIconPixmap( "white_key" ) ); } if( s_blackKeyPm == NULL ) { s_blackKeyPm = new QPixmap( embed::getIconPixmap( "black_key" ) ); } if( s_whiteKeyPressedPm == NULL ) { s_whiteKeyPressedPm = new QPixmap( embed::getIconPixmap( "white_key_pressed" ) ); } if ( s_blackKeyPressedPm == NULL ) { s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap( "black_key_pressed" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); setFocusPolicy( Qt::StrongFocus ); setMaximumWidth( WhiteKeysPerOctave * NumOctaves * PW_WHITE_KEY_WIDTH ); // create scrollbar at the bottom m_pianoScroll = new QScrollBar( Qt::Horizontal, this ); m_pianoScroll->setSingleStep( 1 ); m_pianoScroll->setPageStep( 20 ); m_pianoScroll->setValue( Octave_3 * WhiteKeysPerOctave ); // and connect it to this widget connect( m_pianoScroll, SIGNAL( valueChanged( int ) ), this, SLOT( pianoScrolled( int ) ) ); // create a layout for ourselves QVBoxLayout * layout = new QVBoxLayout( this ); layout->setSpacing( 0 ); layout->setMargin( 0 ); layout->addSpacing( PIANO_BASE+PW_WHITE_KEY_HEIGHT ); layout->addWidget( m_pianoScroll ); } /*! \brief Destroy this piano display view * */ PianoView::~PianoView() { } /*! \brief Map a keyboard key being pressed to a note in our keyboard view * * \param _k The keyboard scan code of the key being pressed. * \todo check the scan codes for ',' = c, 'L' = c#, '.' = d, ':' = d#, * '/' = d, '[' = f', '=' = f'#, ']' = g' - Paul's additions */ int PianoView::getKeyFromKeyEvent( QKeyEvent * _ke ) { #ifdef LMMS_BUILD_APPLE const int k = _ke->nativeVirtualKey(); #else const int k = _ke->nativeScanCode(); #endif #ifdef LMMS_BUILD_WIN32 switch( k ) { case 44: return 0; // Z = C case 31: return 1; // S = C# case 45: return 2; // X = D case 32: return 3; // D = D# case 46: return 4; // C = E case 47: return 5; // V = F case 34: return 6; // G = F# case 48: return 7; // B = G case 35: return 8; // H = G# case 49: return 9; // N = A case 36: return 10; // J = A# case 50: return 11; // M = B case 51: return 12; // , = c case 38: return 13; // L = c# case 52: return 14; // . = d case 39: return 15; // ; = d# //case 86: return 16; // / = e case 53: return 16; // / = e case 16: return 12; // Q = c case 3: return 13; // 2 = c# case 17: return 14; // W = d case 4: return 15; // 3 = d# case 18: return 16; // E = e case 19: return 17; // R = f case 6: return 18; // 5 = f# case 20: return 19; // T = g case 7: return 20; // 6 = g# case 21: return 21; // Y = a case 8: return 22; // 7 = a# case 22: return 23; // U = b case 23: return 24; // I = c' case 10: return 25; // 9 = c'# case 24: return 26; // O = d' case 11: return 27; // 0 = d'# case 25: return 28; // P = e' case 26: return 29; // [ case 13: return 30; // = case 27: return 31; // ] } #endif #ifdef LMMS_BUILD_LINUX switch( k ) { case 52: return 0; // Z = C case 39: return 1; // S = C# case 53: return 2; // X = D case 40: return 3; // D = D# case 54: return 4; // C = E case 55: return 5; // V = F case 42: return 6; // G = F# case 56: return 7; // B = G case 43: return 8; // H = G# case 57: return 9; // N = A case 44: return 10; // J = A# case 58: return 11; // M = B case 59: return 12; // , = c case 46: return 13; // L = c# case 60: return 14; // . = d case 47: return 15; // ; = d# case 61: return 16; // / = e case 24: return 12; // Q = c case 11: return 13; // 2 = c# case 25: return 14; // W = d case 12: return 15; // 3 = d# case 26: return 16; // E = e case 27: return 17; // R = f case 14: return 18; // 5 = f# case 28: return 19; // T = g case 15: return 20; // 6 = g# case 29: return 21; // Y = a case 16: return 22; // 7 = a# case 30: return 23; // U = b case 31: return 24; // I = c' case 18: return 25; // 9 = c'# case 32: return 26; // O = d' case 19: return 27; // 0 = d'# case 33: return 28; // P = e' case 34: return 29; // [ case 21: return 30; // = case 35: return 31; // ] } #endif #ifdef LMMS_BUILD_APPLE switch( k ) { case 6: return 0; // Z = C case 1: return 1; // S = C# case 7: return 2; // X = D case 2: return 3; // D = D# case 8: return 4; // C = E case 9: return 5; // V = F case 5: return 6; // G = F# case 11: return 7; // B = G case 4: return 8; // H = G# case 45: return 9; // N = A case 38: return 10; // J = A# case 46: return 11; // M = B case 43: return 12; // , = c case 37: return 13; // L = c# case 47: return 14; // . = d case 41: return 15; // ; = d# case 44: return 16; // / = e case 12: return 12; // Q = c case 19: return 13; // 2 = c# case 13: return 14; // W = d case 20: return 15; // 3 = d# case 14: return 16; // E = e case 15: return 17; // R = f case 23: return 18; // 5 = f# case 17: return 19; // T = g case 22: return 20; // 6 = g# case 16: return 21; // Y = a case 26: return 22; // 7 = a# case 32: return 23; // U = b case 34: return 24; // I = c' case 25: return 25; // 9 = c'# case 31: return 26; // O = d' case 29: return 27; // 0 = d'# case 35: return 28; // P = e' } #endif return -100; } /*! \brief Register a change to this piano display view * */ void PianoView::modelChanged() { m_piano = castModel(); if( m_piano != NULL ) { connect( m_piano->instrumentTrack()->baseNoteModel(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); } } // gets the key from the given mouse-position /*! \brief Get the key from the mouse position in the piano display * * First we determine it roughly by the position of the point given in * white key widths from our start. We then add in any black keys that * might have been skipped over (they take a key number, but no 'white * key' space). We then add in our starting key number. * * We then determine whether it was a black key that was pressed by * checking whether it was within the vertical range of black keys. * Black keys sit exactly between white keys on this keyboard, so * we then shift the note down or up if we were in the left or right * half of the white note. We only do this, of course, if the white * note has a black key on that side, so to speak. * * This function returns const because there is a linear mapping from * the point given to the key returned that never changes. * * \param _p The point that the mouse was pressed. */ int PianoView::getKeyFromMouse( const QPoint & _p ) const { int key_num = (int)( (float) _p.x() / (float) PW_WHITE_KEY_WIDTH ); for( int i = 0; i <= key_num; ++i ) { if( KEY_ORDER[( m_startKey+i ) % KeysPerOctave] == Piano::BlackKey ) { ++key_num; } } key_num += m_startKey; // is it a black key? if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT ) { // then do extra checking whether the mouse-cursor is over // a black key if( key_num > 0 && KEY_ORDER[(key_num-1 ) % KeysPerOctave] == Piano::BlackKey && _p.x() % PW_WHITE_KEY_WIDTH <= ( PW_WHITE_KEY_WIDTH / 2 ) - ( PW_BLACK_KEY_WIDTH / 2 ) ) { --key_num; } if( key_num < NumKeys - 1 && KEY_ORDER[( key_num + 1 ) % KeysPerOctave] == Piano::BlackKey && _p.x() % PW_WHITE_KEY_WIDTH >= ( PW_WHITE_KEY_WIDTH - PW_BLACK_KEY_WIDTH / 2 ) ) { ++key_num; } } // some range-checking-stuff return tLimit( key_num, 0, NumKeys - 1 ); } // handler for scrolling-event /*! \brief Handle the scrolling on the piano display view * * We need to update our start key position based on the new position. * * \param _new_pos the new key position. */ void PianoView::pianoScrolled( int _new_pos ) { m_startKey = WhiteKeys[_new_pos % WhiteKeysPerOctave]+ ( _new_pos / WhiteKeysPerOctave ) * KeysPerOctave; update(); } /*! \brief Handle a context menu selection on the piano display view * * \param _me the ContextMenuEvent to handle. * \todo Is this right, or does this create the context menu? */ void PianoView::contextMenuEvent( QContextMenuEvent * _me ) { if( _me->pos().y() > PIANO_BASE || m_piano == NULL ) { QWidget::contextMenuEvent( _me ); return; } captionMenu contextMenu( tr( "Base note" ) ); AutomatableModelView amv( m_piano->instrumentTrack()->baseNoteModel(), &contextMenu ); amv.addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); } // handler for mouse-click-event /*! \brief Handle a mouse click on this piano display view * * We first determine the key number using the getKeyFromMouse() method. * * If we're below the 'root key selection' area, * we set the volume of the note to be proportional to the vertical * position on the keyboard - lower down the key is louder, within the * boundaries of the (white or black) key pressed. We then tell the * instrument to play that note, scaling for MIDI max loudness = 127. * * If we're in the 'root key selection' area, of course, we set the * root key to be that key. * * We finally update ourselves to show the key press * * \param _me the mouse click to handle. */ void PianoView::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && m_piano != NULL ) { // get pressed key int key_num = getKeyFromMouse( _me->pos() ); if( _me->pos().y() > PIANO_BASE ) { int y_diff = _me->pos().y() - PIANO_BASE; int velocity = (int)( ( float ) y_diff / ( ( KEY_ORDER[key_num % KeysPerOctave] == Piano::WhiteKey ) ? PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) * (float) m_piano->instrumentTrack()->midiPort()->baseVelocity() ); if( y_diff < 0 ) { velocity = 0; } else if( y_diff > ( ( KEY_ORDER[key_num % KeysPerOctave] == Piano::WhiteKey ) ? PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) ) { velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity(); } // set note on m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, 0, key_num, velocity ) ); m_piano->setKeyState( key_num, true ); m_lastKey = key_num; emit keyPressed( key_num ); } else { if( _me->modifiers() & Qt::ControlModifier ) { new stringPairDrag( "automatable_model", QString::number( m_piano->instrumentTrack()->baseNoteModel()->id() ), QPixmap(), this ); _me->accept(); } else { m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num ); emit baseNoteChanged(); } } // and let the user see that he pressed a key... :) update(); } } // handler for mouse-release-event /*! \brief Handle a mouse release event on the piano display view * * If a key was pressed by the in the mousePressEvent() function, we * turn the note off. * * \param _me the mousePressEvent to handle. */ void PianoView::mouseReleaseEvent( QMouseEvent * ) { if( m_lastKey != -1 ) { if( m_piano != NULL ) { m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, 0, m_lastKey, 0 ) ); m_piano->setKeyState( m_lastKey, false ); } // and let the user see that he released a key... :) update(); m_lastKey = -1; } } // handler for mouse-move-event /*! \brief Handle a mouse move event on the piano display view * * This handles the user dragging the mouse across the keys. It uses * code from mousePressEvent() and mouseReleaseEvent(), also correcting * for if the mouse movement has stayed within one key and if the mouse * has moved outside the vertical area of the keyboard (which is still * allowed but won't make the volume go up to 11). * * \param _me the ContextMenuEvent to handle. * \todo Paul Wayper thinks that this code should be refactored to * reduce or remove the duplication between this, the mousePressEvent() * and mouseReleaseEvent() methods. */ void PianoView::mouseMoveEvent( QMouseEvent * _me ) { if( m_piano == NULL ) { return; } int key_num = getKeyFromMouse( _me->pos() ); int y_diff = _me->pos().y() - PIANO_BASE; int velocity = (int)( (float) y_diff / ( ( KEY_ORDER[key_num % KeysPerOctave] == Piano::WhiteKey ) ? PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) * (float) m_piano->instrumentTrack()->midiPort()->baseVelocity() ); // maybe the user moved the mouse-cursor above or under the // piano-widget while holding left button so check that and // correct volume if necessary if( y_diff < 0 ) { velocity = 0; } else if( y_diff > ( ( KEY_ORDER[key_num % KeysPerOctave] == Piano::WhiteKey ) ? PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) ) { velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity(); } // is the calculated key different from current key? (could be the // user just moved the cursor one pixel left but on the same key) if( key_num != m_lastKey ) { if( m_lastKey != -1 ) { m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, 0, m_lastKey, 0 ) ); m_piano->setKeyState( m_lastKey, false ); m_lastKey = -1; } if( _me->buttons() & Qt::LeftButton ) { if( _me->pos().y() > PIANO_BASE ) { m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, 0, key_num, velocity ) ); m_piano->setKeyState( key_num, true ); m_lastKey = key_num; } else { m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num ); } } // and let the user see that he pressed a key... :) update(); } else if( m_piano->isKeyPressed( key_num ) ) { m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiKeyPressure, 0, key_num, velocity ) ); } } /*! \brief Handle a key press event on the piano display view * * We determine our key number from the getKeyFromKeyEvent() method, * and pass the event on to the piano's handleKeyPress() method if * auto-repeat is off. * * \param _ke the KeyEvent to handle. */ void PianoView::keyPressEvent( QKeyEvent * _ke ) { const int key_num = getKeyFromKeyEvent( _ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; if( _ke->isAutoRepeat() == false && key_num > -1 ) { if( m_piano != NULL ) { m_piano->handleKeyPress( key_num ); _ke->accept(); update(); } } else { _ke->ignore(); } } /*! \brief Handle a key release event on the piano display view * * The same logic as the keyPressEvent() method. * * \param _ke the KeyEvent to handle. */ void PianoView::keyReleaseEvent( QKeyEvent * _ke ) { const int key_num = getKeyFromKeyEvent( _ke ) + ( DefaultOctave - 1 ) * KeysPerOctave; if( _ke->isAutoRepeat() == false && key_num > -1 ) { if( m_piano != NULL ) { m_piano->handleKeyRelease( key_num ); _ke->accept(); update(); } } else { _ke->ignore(); } } /*! \brief Handle the focus leaving the piano display view * * Turn off all notes if we lose focus. * * \todo Is there supposed to be a parameter given here? */ void PianoView::focusOutEvent( QFocusEvent * ) { if( m_piano == NULL ) { return; } // focus just switched to another control inside the instrument track // window we live in? if( parentWidget()->parentWidget()->focusWidget() != this && parentWidget()->parentWidget()->focusWidget() != NULL && !parentWidget()->parentWidget()-> focusWidget()->inherits( "QLineEdit" ) ) { // then reclaim keyboard focus! setFocus(); return; } // if we loose focus, we HAVE to note off all running notes because // we don't receive key-release-events anymore and so the notes would // hang otherwise for( int i = 0; i < NumKeys; ++i ) { m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, 0, i, 0 ) ); m_piano->setKeyState( i, false ); } update(); } /*! \brief update scrollbar range after resize * * After resizing we need to adjust range of scrollbar for not allowing * to scroll too far to the right. * * \param _event resize-event object (unused) */ void PianoView::resizeEvent( QResizeEvent * _event ) { QWidget::resizeEvent( _event ); m_pianoScroll->setRange( 0, WhiteKeysPerOctave * NumOctaves - (int) ceil( (float) width() / PW_WHITE_KEY_WIDTH ) ); } /*! \brief Convert a key number to an X coordinate in the piano display view * * We can immediately discard the trivial case of when the key number is * less than our starting key. We then iterate through the keys from the * start key to this key, adding the width of each key as we go. For * black keys, and the first white key if there is no black key between * two white keys, we add half a white key width; for that second white * key, we add a whole width. That takes us to the boundary of a white * key - subtract half a width to get to the middle. * * \param _key_num the keyboard key to translate * \todo is this description of what the method does correct? * \todo replace the final subtract with initialising x to width/2. */ int PianoView::getKeyX( int _key_num ) const { int k = m_startKey; if( _key_num < m_startKey ) { return ( _key_num - k ) * PW_WHITE_KEY_WIDTH / 2; } int x = 0; int white_cnt = 0; while( k <= _key_num ) { if( KEY_ORDER[k % KeysPerOctave] == Piano::WhiteKey ) { ++white_cnt; if( white_cnt > 1 ) { x += PW_WHITE_KEY_WIDTH; } else { x += PW_WHITE_KEY_WIDTH/2; } } else { white_cnt = 0; x += PW_WHITE_KEY_WIDTH/2; } ++k; } x -= PW_WHITE_KEY_WIDTH / 2; return x; } /*! \brief Paint the piano display view in response to an event * * This method draws the piano and the 'root note' base. It draws * the base first, then all the white keys, then all the black keys. * * \todo Is there supposed to be a parameter given here? */ void PianoView::paintEvent( QPaintEvent * ) { QPainter p( this ); // set smaller font for printing number of every octave p.setFont( pointSize( p.font() ) ); // draw blue bar above the actual keyboard (there will be the labels // for all C's) QLinearGradient g( 0, 0, 0, PIANO_BASE-3 ); g.setColorAt( 0, Qt::black ); g.setColorAt( 0.1, QColor( 96, 96, 96 ) ); g.setColorAt( 1, Qt::black ); p.fillRect( QRect( 0, 1, width(), PIANO_BASE-2 ), g ); // draw stuff above the actual keyboard p.setPen( Qt::black ); p.drawLine( 0, 0, width(), 0 ); p.drawLine( 0, PIANO_BASE-1, width(), PIANO_BASE-1 ); p.setPen( Qt::white ); const int base_key = ( m_piano != NULL ) ? m_piano->instrumentTrack()->baseNoteModel()->value() : 0; g.setColorAt( 0, QApplication::palette().color( QPalette::Active, QPalette::BrightText ).darker(220) ); g.setColorAt( 0.1, QApplication::palette().color( QPalette::Active, QPalette::BrightText ) ); g.setColorAt( 1, QApplication::palette().color( QPalette::Active, QPalette::BrightText ) ); if( KEY_ORDER[base_key % KeysPerOctave] == Piano::WhiteKey ) { p.fillRect( QRect( getKeyX( base_key ), 1, PW_WHITE_KEY_WIDTH-1, PIANO_BASE-2 ), g ); } else { p.fillRect( QRect( getKeyX( base_key ) + 1, 1, PW_BLACK_KEY_WIDTH - 1, PIANO_BASE - 2 ), g ); } int cur_key = m_startKey; // draw all white keys... for( int x = 0; x < width(); ) { while( KEY_ORDER[cur_key%KeysPerOctave] != Piano::WhiteKey ) { ++cur_key; } // draw pressed or not pressed key, depending on state of // current key if( m_piano && m_piano->isKeyPressed( cur_key ) ) { p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPressedPm ); } else { p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPm ); } x += PW_WHITE_KEY_WIDTH; if( (Keys) (cur_key%KeysPerOctave) == Key_C ) { // label key of note C with "C" and number of current // octave p.drawText( x - PW_WHITE_KEY_WIDTH, LABEL_TEXT_SIZE + 2, QString( "C" ) + QString::number( cur_key / KeysPerOctave, 10 ) ); } ++cur_key; } // reset all values, because now we're going to draw all black keys cur_key = m_startKey; int white_cnt = 0; int startKey = m_startKey; if( startKey > 0 && KEY_ORDER[(Keys)(--startKey) % KeysPerOctave] == Piano::BlackKey ) { if( m_piano && m_piano->isKeyPressed( startKey ) ) { p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm ); } else { p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm ); } } // now draw all black keys... for( int x = 0; x < width(); ) { if( KEY_ORDER[cur_key%KeysPerOctave] == Piano::BlackKey ) { // draw pressed or not pressed key, depending on // state of current key if( m_piano && m_piano->isKeyPressed( cur_key ) ) { p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm ); } else { p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm ); } x += PW_WHITE_KEY_WIDTH; white_cnt = 0; } else { // simple workaround for increasing x if there were two // white keys (e.g. between E and F) ++white_cnt; if( white_cnt > 1 ) { x += PW_WHITE_KEY_WIDTH; } } ++cur_key; } } #include "moc_PianoView.cxx" lmms-1.0.0/src/gui/SongEditor.cpp0000644000175000017500000005110012313663627015336 0ustar tobytoby/* * SongEditor.cpp - basic window for song-editing * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include "SongEditor.h" #include "automatable_slider.h" #include "combobox.h" #include "config_mgr.h" #include "cpuload_widget.h" #include "embed.h" #include "LcdSpinBox.h" #include "MainWindow.h" #include "MeterDialog.h" #include "text_float.h" #include "timeline.h" #include "tool_button.h" #include "tooltip.h" #include "visualization_widget.h" #include "TimeDisplayWidget.h" #include "AudioDevice.h" #include "PianoRoll.h" #include "config_mgr.h" positionLine::positionLine( QWidget * _parent ) : QWidget( _parent ) { setFixedWidth( 1 ); setAttribute( Qt::WA_NoSystemBackground, true ); } void positionLine::paintEvent( QPaintEvent * _pe ) { QPainter p( this ); p.fillRect( rect(), QColor( 255, 255, 255, 153 ) ); } SongEditor::SongEditor( song * _song ) : TrackContainerView( _song ), m_s( _song ), m_scrollBack( false ), m_smoothScroll( configManager::inst()->value( "ui", "smoothscroll" ).toInt() ) { setWindowTitle( tr( "Song-Editor" ) ); setWindowIcon( embed::getIconPixmap( "songeditor" ) ); setFocusPolicy( Qt::StrongFocus ); setFocus(); // create time-line int widgetTotal = configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt()==1 ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT : DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH; m_timeLine = new timeLine( widgetTotal, 32, pixelsPerTact(), m_s->m_playPos[song::Mode_PlaySong], m_currentPosition, this ); connect( this, SIGNAL( positionChanged( const MidiTime & ) ), m_s->m_playPos[song::Mode_PlaySong].m_timeLine, SLOT( updatePosition( const MidiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePosition( const MidiTime & ) ) ); m_positionLine = new positionLine( this ); // let's get notified when loading a project connect( m_s, SIGNAL( projectLoaded() ), this, SLOT( adjustUiAfterProjectLoad() ) ); // add some essential widgets to global tool-bar QWidget * tb = engine::mainWindow()->toolBar(); engine::mainWindow()->addSpacingToToolBar( 10 ); m_tempoSpinBox = new LcdSpinBox( 3, tb, tr( "Tempo" ) ); m_tempoSpinBox->setModel( &m_s->m_tempoModel ); m_tempoSpinBox->setLabel( tr( "TEMPO/BPM" ) ); toolTip::add( m_tempoSpinBox, tr( "tempo of song" ) ); m_tempoSpinBox->setWhatsThis( tr( "The tempo of a song is specified in beats per minute " "(BPM). If you want to change the tempo of your " "song, change this value. Every measure has four beats, " "so the tempo in BPM specifies, how many measures / 4 " "should be played within a minute (or how many measures " "should be played within four minutes)." ) ); int tempoSpinBoxCol = engine::mainWindow()->addWidgetToToolBar( m_tempoSpinBox, 0 ); #if 0 toolButton * hq_btn = new toolButton( embed::getIconPixmap( "hq_mode" ), tr( "High quality mode" ), NULL, NULL, tb ); hq_btn->setCheckable( TRUE ); connect( hq_btn, SIGNAL( toggled( bool ) ), this, SLOT( setHighQuality( bool ) ) ); hq_btn->setFixedWidth( 42 ); engine::mainWindow()->addWidgetToToolBar( hq_btn, 1, col ); #endif engine::mainWindow()->addWidgetToToolBar( new TimeDisplayWidget, 1, tempoSpinBoxCol ); engine::mainWindow()->addSpacingToToolBar( 10 ); m_timeSigDisplay = new MeterDialog( this, TRUE ); m_timeSigDisplay->setModel( &m_s->m_timeSigModel ); engine::mainWindow()->addWidgetToToolBar( m_timeSigDisplay ); engine::mainWindow()->addSpacingToToolBar( 10 ); QLabel * master_vol_lbl = new QLabel( tb ); master_vol_lbl->setPixmap( embed::getIconPixmap( "master_volume" ) ); m_masterVolumeSlider = new automatableSlider( tb, tr( "Master volume" ) ); m_masterVolumeSlider->setModel( &m_s->m_masterVolumeModel ); m_masterVolumeSlider->setOrientation( Qt::Vertical ); m_masterVolumeSlider->setPageStep( 1 ); m_masterVolumeSlider->setTickPosition( QSlider::TicksLeft ); m_masterVolumeSlider->setFixedSize( 26, 60 ); m_masterVolumeSlider->setTickInterval( 50 ); toolTip::add( m_masterVolumeSlider, tr( "master volume" ) ); connect( m_masterVolumeSlider, SIGNAL( logicValueChanged( int ) ), this, SLOT( masterVolumeChanged( int ) ) ); connect( m_masterVolumeSlider, SIGNAL( sliderPressed() ), this, SLOT( masterVolumePressed() ) ); connect( m_masterVolumeSlider, SIGNAL( logicSliderMoved( int ) ), this, SLOT( masterVolumeMoved( int ) ) ); connect( m_masterVolumeSlider, SIGNAL( sliderReleased() ), this, SLOT( masterVolumeReleased() ) ); m_mvsStatus = new textFloat; m_mvsStatus->setTitle( tr( "Master volume" ) ); m_mvsStatus->setPixmap( embed::getIconPixmap( "master_volume" ) ); engine::mainWindow()->addWidgetToToolBar( master_vol_lbl ); engine::mainWindow()->addWidgetToToolBar( m_masterVolumeSlider ); engine::mainWindow()->addSpacingToToolBar( 10 ); QLabel * master_pitch_lbl = new QLabel( tb ); master_pitch_lbl->setPixmap( embed::getIconPixmap( "master_pitch" ) ); master_pitch_lbl->setFixedHeight( 64 ); m_masterPitchSlider = new automatableSlider( tb, tr( "Master pitch" ) ); m_masterPitchSlider->setModel( &m_s->m_masterPitchModel ); m_masterPitchSlider->setOrientation( Qt::Vertical ); m_masterPitchSlider->setPageStep( 1 ); m_masterPitchSlider->setTickPosition( QSlider::TicksLeft ); m_masterPitchSlider->setFixedSize( 26, 60 ); m_masterPitchSlider->setTickInterval( 12 ); toolTip::add( m_masterPitchSlider, tr( "master pitch" ) ); connect( m_masterPitchSlider, SIGNAL( logicValueChanged( int ) ), this, SLOT( masterPitchChanged( int ) ) ); connect( m_masterPitchSlider, SIGNAL( sliderPressed() ), this, SLOT( masterPitchPressed() ) ); connect( m_masterPitchSlider, SIGNAL( logicSliderMoved( int ) ), this, SLOT( masterPitchMoved( int ) ) ); connect( m_masterPitchSlider, SIGNAL( sliderReleased() ), this, SLOT( masterPitchReleased() ) ); m_mpsStatus = new textFloat; m_mpsStatus->setTitle( tr( "Master pitch" ) ); m_mpsStatus->setPixmap( embed::getIconPixmap( "master_pitch" ) ); engine::mainWindow()->addWidgetToToolBar( master_pitch_lbl ); engine::mainWindow()->addWidgetToToolBar( m_masterPitchSlider ); engine::mainWindow()->addSpacingToToolBar( 10 ); // create widget for visualization- and cpu-load-widget QWidget * vc_w = new QWidget( tb ); QVBoxLayout * vcw_layout = new QVBoxLayout( vc_w ); vcw_layout->setMargin( 0 ); vcw_layout->setSpacing( 0 ); //vcw_layout->addStretch(); vcw_layout->addWidget( new visualizationWidget( embed::getIconPixmap( "output_graph" ), vc_w ) ); vcw_layout->addWidget( new cpuloadWidget( vc_w ) ); vcw_layout->addStretch(); engine::mainWindow()->addWidgetToToolBar( vc_w ); // create own toolbar m_toolBar = new QWidget( this ); m_toolBar->setFixedHeight( 32 ); m_toolBar->setAutoFillBackground( true ); QPalette pal; pal.setBrush( m_toolBar->backgroundRole(), embed::getIconPixmap( "toolbar_bg" ) ); m_toolBar->setPalette( pal ); static_cast( layout() )->insertWidget( 0, m_toolBar ); static_cast( layout() )->insertWidget( 1, m_timeLine ); QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar ); tb_layout->setMargin( 0 ); tb_layout->setSpacing( 0 ); // fill own tool-bar m_playButton = new toolButton( embed::getIconPixmap( "play" ), tr( "Play song (Space)" ), this, SLOT( play() ), m_toolBar ); m_playButton->setObjectName( "playButton" ); m_recordButton = new toolButton( embed::getIconPixmap( "record" ), tr( "Record samples from Audio-device" ), this, SLOT( record() ), m_toolBar ); m_recordButton->setObjectName( "recordButton" ); m_recordAccompanyButton = new toolButton( embed::getIconPixmap( "record_accompany" ), tr( "Record samples from Audio-device while playing " "song or BB track" ), this, SLOT( recordAccompany() ), m_toolBar ); m_recordAccompanyButton->setObjectName( "recordAccompanyButton" ); // FIXME: disable record button while it is not implemented m_recordButton->setDisabled( true ); // disable record buttons if capturing is not supported if( !engine::mixer()->audioDev()->supportsCapture() ) { m_recordButton->setDisabled( true ); m_recordAccompanyButton->setDisabled( true ); } m_stopButton = new toolButton( embed::getIconPixmap( "stop" ), tr( "Stop song (Space)" ), this, SLOT( stop() ), m_toolBar ); m_stopButton->setObjectName( "stopButton" ); m_addBBTrackButton = new toolButton( embed::getIconPixmap( "add_bb_track" ), tr( "Add beat/bassline" ), m_s, SLOT( addBBTrack() ), m_toolBar ); m_addSampleTrackButton = new toolButton( embed::getIconPixmap( "add_sample_track" ), tr( "Add sample-track" ), m_s, SLOT( addSampleTrack() ), m_toolBar ); m_addAutomationTrackButton = new toolButton( embed::getIconPixmap( "add_automation" ), tr( "Add automation-track" ), m_s, SLOT( addAutomationTrack() ), m_toolBar ); m_drawModeButton = new toolButton( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode" ), NULL, NULL, m_toolBar ); m_drawModeButton->setCheckable( true ); m_drawModeButton->setChecked( true ); m_editModeButton = new toolButton( embed::getIconPixmap( "edit_select" ), tr( "Edit mode (select and move)" ), NULL, NULL, m_toolBar ); m_editModeButton->setCheckable( true ); QButtonGroup * tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_drawModeButton ); tool_button_group->addButton( m_editModeButton ); tool_button_group->setExclusive( true ); #if 0 #warning TODO QWhatsThis::add( m_playButton, tr( "Click here, if you want to play " "your whole song. Playing will " "be started at the " "song-position-marker (green). " "You can also move it while " "playing." ) ); QWhatsThis::add( m_stopButton, tr ( "Click here, if you want to stop " "playing of your song. The " "song-position-marker will be " "set to the start of your song." ) ); /* QWhatsThis::add( m_insertBarButton, tr( "If you click here, a " "bar will " "be inserted at the " "current bar." ) ); QWhatsThis::add( m_removeBarButton, tr( "If you click here, the " "current bar will be " "removed." ) );*/ #endif QLabel * zoom_lbl = new QLabel( m_toolBar ); zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) ); // setup zooming-stuff m_zoomingComboBox = new comboBox( m_toolBar ); m_zoomingComboBox->setFixedSize( 80, 22 ); m_zoomingComboBox->move( 580, 4 ); for( int i = 0; i < 7; ++i ) { m_zoomingComboBox->model()->addItem( QString::number( 25 << i ) + "%" ); } m_zoomingComboBox->model()->setInitValue( m_zoomingComboBox->model()->findText( "100%" ) ); connect( m_zoomingComboBox->model(), SIGNAL( dataChanged() ), this, SLOT( zoomingChanged() ) ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_playButton ); tb_layout->addWidget( m_recordButton ); tb_layout->addWidget( m_recordAccompanyButton ); tb_layout->addWidget( m_stopButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_addBBTrackButton ); tb_layout->addWidget( m_addSampleTrackButton ); tb_layout->addWidget( m_addAutomationTrackButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_drawModeButton ); tb_layout->addWidget( m_editModeButton ); tb_layout->addSpacing( 10 ); m_timeLine->addToolButtons( m_toolBar ); tb_layout->addSpacing( 15 ); tb_layout->addWidget( zoom_lbl ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_zoomingComboBox ); tb_layout->addStretch(); m_leftRightScroll = new QScrollBar( Qt::Horizontal, this ); m_leftRightScroll->setMinimum( 0 ); m_leftRightScroll->setMaximum( 0 ); m_leftRightScroll->setSingleStep( 1 ); m_leftRightScroll->setPageStep( 20 ); static_cast( layout() )->addWidget( m_leftRightScroll ); connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this, SLOT( scrolled( int ) ) ); connect( m_s, SIGNAL( lengthChanged( int ) ), this, SLOT( updateScrollBar( int ) ) ); engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->resize( 600, 300 ); parentWidget()->move( 5, 5 ); parentWidget()->show(); } SongEditor::~SongEditor() { } void SongEditor::setHighQuality( bool _hq ) { engine::mixer()->changeQuality( Mixer::qualitySettings( _hq ? Mixer::qualitySettings::Mode_HighQuality : Mixer::qualitySettings::Mode_Draft ) ); } void SongEditor::scrolled( int _new_pos ) { update(); emit positionChanged( m_currentPosition = MidiTime( _new_pos, 0 ) ); } void SongEditor::setPauseIcon( bool pause ) { if( pause == true ) { m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } } void SongEditor::play() { if( engine::getSong()->playMode() != song::Mode_PlaySong ) { engine::getSong()->playSong(); } else { engine::getSong()->togglePause(); } } void SongEditor::record() { m_s->record(); } void SongEditor::recordAccompany() { m_s->playAndRecord(); } void SongEditor::stop() { m_s->stop(); engine::pianoRoll()->stopRecording(); } void SongEditor::keyPressEvent( QKeyEvent * _ke ) { if( /*_ke->modifiers() & Qt::ShiftModifier*/ engine::mainWindow()->isShiftPressed() == TRUE && _ke->key() == Qt::Key_Insert ) { m_s->insertBar(); } else if(/* _ke->modifiers() & Qt::ShiftModifier &&*/ engine::mainWindow()->isShiftPressed() == TRUE && _ke->key() == Qt::Key_Delete ) { m_s->removeBar(); } else if( _ke->key() == Qt::Key_Left ) { tick_t t = m_s->currentTick() - MidiTime::ticksPerTact(); if( t >= 0 ) { m_s->setPlayPos( t, song::Mode_PlaySong ); } } else if( _ke->key() == Qt::Key_Right ) { tick_t t = m_s->currentTick() + MidiTime::ticksPerTact(); if( t < MaxSongLength ) { m_s->setPlayPos( t, song::Mode_PlaySong ); } } else if( _ke->key() == Qt::Key_Space ) { if( m_s->isPlaying() ) { stop(); } else { play(); } } else if( _ke->key() == Qt::Key_Home ) { m_s->setPlayPos( 0, song::Mode_PlaySong ); } else { QWidget::keyPressEvent( _ke ); } } void SongEditor::wheelEvent( QWheelEvent * _we ) { if( engine::mainWindow()->isCtrlPressed() == TRUE ) { if( _we->delta() > 0 ) { setPixelsPerTact( (int) qMin( pixelsPerTact() * 2, 256.0f ) ); } else if( pixelsPerTact() >= 8 ) { setPixelsPerTact( (int) pixelsPerTact() / 2 ); } // update combobox with zooming-factor m_zoomingComboBox->model()->setValue( m_zoomingComboBox->model()->findText( QString::number( static_cast( pixelsPerTact() * 100 / DEFAULT_PIXELS_PER_TACT ) ) + "%" ) ); // update timeline m_s->m_playPos[song::Mode_PlaySong].m_timeLine-> setPixelsPerTact( pixelsPerTact() ); // and make sure, all TCO's are resized and relocated realignTracks(); } else if( engine::mainWindow()->isShiftPressed() == TRUE ) { m_leftRightScroll->setValue( m_leftRightScroll->value() - _we->delta() / 30 ); } else { _we->ignore(); return; } _we->accept(); } void SongEditor::masterVolumeChanged( int _new_val ) { masterVolumeMoved( _new_val ); if( m_mvsStatus->isVisible() == FALSE && m_s->m_loadingProject == FALSE && m_masterVolumeSlider->showStatus() ) { m_mvsStatus->moveGlobal( m_masterVolumeSlider, QPoint( m_masterVolumeSlider->width() + 2, -2 ) ); m_mvsStatus->setVisibilityTimeOut( 1000 ); } engine::mixer()->setMasterGain( _new_val / 100.0f ); } void SongEditor::masterVolumePressed( void ) { m_mvsStatus->moveGlobal( m_masterVolumeSlider, QPoint( m_masterVolumeSlider->width() + 2, -2 ) ); m_mvsStatus->show(); masterVolumeMoved( m_s->m_masterVolumeModel.value() ); } void SongEditor::masterVolumeMoved( int _new_val ) { m_mvsStatus->setText( tr( "Value: %1%" ).arg( _new_val ) ); } void SongEditor::masterVolumeReleased( void ) { m_mvsStatus->hide(); } void SongEditor::masterPitchChanged( int _new_val ) { masterPitchMoved( _new_val ); if( m_mpsStatus->isVisible() == FALSE && m_s->m_loadingProject == FALSE && m_masterPitchSlider->showStatus() ) { m_mpsStatus->moveGlobal( m_masterPitchSlider, QPoint( m_masterPitchSlider->width() + 2, -2 ) ); m_mpsStatus->setVisibilityTimeOut( 1000 ); } } void SongEditor::masterPitchPressed( void ) { m_mpsStatus->moveGlobal( m_masterPitchSlider, QPoint( m_masterPitchSlider->width() + 2, -2 ) ); m_mpsStatus->show(); masterPitchMoved( m_s->m_masterPitchModel.value() ); } void SongEditor::masterPitchMoved( int _new_val ) { m_mpsStatus->setText( tr( "Value: %1 semitones").arg( _new_val ) ); } void SongEditor::masterPitchReleased( void ) { m_mpsStatus->hide(); } void SongEditor::updateScrollBar( int _len ) { m_leftRightScroll->setMaximum( _len ); } static inline void animateScroll( QScrollBar *scrollBar, int newVal, bool smoothScroll ) { if( smoothScroll == false ) { scrollBar->setValue( newVal ); } else { // do smooth scroll animation using QTimeLine QTimeLine *t = scrollBar->findChild(); if( t == NULL ) { t = new QTimeLine( 600, scrollBar ); t->setFrameRange( scrollBar->value(), newVal ); t->connect( t, SIGNAL( finished() ), SLOT( deleteLater() ) ); scrollBar->connect( t, SIGNAL( frameChanged( int ) ), SLOT( setValue( int ) ) ); t->start(); } else { // smooth scrolling is still active, therefore just update the end frame t->setEndFrame( newVal ); } } } void SongEditor::updatePosition( const MidiTime & _t ) { int widgetWidth, trackOpWidth; if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT; trackOpWidth = TRACK_OP_WIDTH_COMPACT; } else { widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH; trackOpWidth = TRACK_OP_WIDTH; } if( ( m_s->isPlaying() && m_s->m_playMode == song::Mode_PlaySong && m_timeLine->autoScroll() == timeLine::AutoScrollEnabled) || m_scrollBack == true ) { const int w = width() - widgetWidth - trackOpWidth - 32; // rough estimation for width of right scrollbar if( _t > m_currentPosition + w * MidiTime::ticksPerTact() / pixelsPerTact() ) { animateScroll( m_leftRightScroll, _t.getTact(), m_smoothScroll ); } else if( _t < m_currentPosition ) { MidiTime t = qMax( (int)( _t - w * MidiTime::ticksPerTact() / pixelsPerTact() ), 0 ); animateScroll( m_leftRightScroll, t.getTact(), m_smoothScroll ); } m_scrollBack = false; } const int x = m_s->m_playPos[song::Mode_PlaySong].m_timeLine-> markerX( _t ) + 8; if( x >= trackOpWidth + widgetWidth -1 ) { m_positionLine->show(); m_positionLine->move( x, 50 ); } else { m_positionLine->hide(); } m_positionLine->setFixedHeight( height() ); } void SongEditor::zoomingChanged() { const QString & zfac = m_zoomingComboBox->model()->currentText(); setPixelsPerTact( zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_PIXELS_PER_TACT / 100 ); m_s->m_playPos[song::Mode_PlaySong].m_timeLine-> setPixelsPerTact( pixelsPerTact() ); realignTracks(); } void SongEditor::adjustUiAfterProjectLoad() { //if( isMaximized() ) { // make sure to bring us to front as the song editor is the central // widget in a song and when just opening a song in order to listen to // it, it's very annyoing to manually bring up the song editor each time engine::mainWindow()->workspace()->setActiveSubWindow( qobject_cast( parentWidget() ) ); } scrolled( 0 ); } bool SongEditor::allowRubberband() const { return( m_editModeButton->isChecked() ); } #include "moc_SongEditor.cxx" /* vim: set tw=0 noexpandtab: */ lmms-1.0.0/src/gui/TrackContainerView.cpp0000644000175000017500000002557212313663627017041 0ustar tobytoby/* * TrackContainerView.cpp - view-component for TrackContainer * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "TrackContainerView.h" #include "TrackContainer.h" #include "bb_track.h" #include "MainWindow.h" #include "debug.h" #include "file_browser.h" #include "ImportFilter.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "DataFile.h" #include "rubberband.h" #include "song.h" #include "string_pair_drag.h" #include "track.h" TrackContainerView::TrackContainerView( TrackContainer * _tc ) : QWidget(), ModelView( NULL, this ), JournallingObject(), SerializingObjectHook(), m_currentPosition( 0, 0 ), m_tc( _tc ), m_trackViews(), m_scrollArea( new scrollArea( this ) ), m_ppt( DEFAULT_PIXELS_PER_TACT ), m_rubberBand( new rubberBand( m_scrollArea ) ), m_origin() { m_tc->setHook( this ); QVBoxLayout * layout = new QVBoxLayout( this ); layout->setMargin( 0 ); layout->setSpacing( 0 ); layout->addWidget( m_scrollArea ); QWidget * scrollContent = new QWidget; m_scrollLayout = new QVBoxLayout( scrollContent ); m_scrollLayout->setMargin( 0 ); m_scrollLayout->setSpacing( 0 ); m_scrollLayout->setSizeConstraint( QLayout::SetMinAndMaxSize ); m_scrollArea->setWidget( scrollContent ); m_scrollArea->show(); m_rubberBand->hide(); setAcceptDrops( true ); connect( engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ), this, SLOT( realignTracks() ) ); connect( m_tc, SIGNAL( trackAdded( track * ) ), this, SLOT( createTrackView( track * ) ), Qt::QueuedConnection ); } TrackContainerView::~TrackContainerView() { while( !m_trackViews.empty() ) { delete m_trackViews.takeLast(); } } void TrackContainerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); } void TrackContainerView::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); } trackView * TrackContainerView::addTrackView( trackView * _tv ) { /* QMap map; map["id"] = _tv->getTrack()->id(); addJournalEntry( JournalEntry( AddTrack, map ) );*/ m_trackViews.push_back( _tv ); m_scrollLayout->addWidget( _tv ); connect( this, SIGNAL( positionChanged( const MidiTime & ) ), _tv->getTrackContentWidget(), SLOT( changePosition( const MidiTime & ) ) ); realignTracks(); return( _tv ); } void TrackContainerView::removeTrackView( trackView * _tv ) { int index = m_trackViews.indexOf( _tv ); if( index != -1 ) { /* QMap map; DataFile dataFile( DataFile::JournalData ); _tv->getTrack()->saveState( dataFile, dataFile.content() ); map["id"] = _tv->getTrack()->id(); map["state"] = dataFile.toString(); addJournalEntry( JournalEntry( RemoveTrack, map ) );*/ m_trackViews.removeAt( index ); disconnect( _tv ); m_scrollLayout->removeWidget( _tv ); realignTracks(); if( engine::getSong() ) { engine::getSong()->setModified(); } } } void TrackContainerView::moveTrackViewUp( trackView * _tv ) { for( int i = 1; i < m_trackViews.size(); ++i ) { trackView * t = m_trackViews[i]; if( t == _tv ) { bbTrack::swapBBTracks( t->getTrack(), m_trackViews[i - 1]->getTrack() ); m_scrollLayout->removeWidget( t ); m_scrollLayout->insertWidget( i - 1, t ); qSwap( m_tc->m_tracks[i-1], m_tc->m_tracks[i] ); m_trackViews.swap( i - 1, i ); realignTracks(); break; } } } void TrackContainerView::moveTrackViewDown( trackView * _tv ) { for( int i = 0; i < m_trackViews.size()-1; ++i ) { trackView * t = m_trackViews[i]; if( t == _tv ) { bbTrack::swapBBTracks( t->getTrack(), m_trackViews[i + 1]->getTrack() ); m_scrollLayout->removeWidget( t ); m_scrollLayout->insertWidget( i + 1, t ); qSwap( m_tc->m_tracks[i], m_tc->m_tracks[i+1] ); m_trackViews.swap( i, i + 1 ); realignTracks(); break; } } } void TrackContainerView::realignTracks() { QWidget * content = m_scrollArea->widget(); content->setFixedWidth( width() - m_scrollArea->verticalScrollBar()->width() ); content->setFixedHeight( content->minimumSizeHint().height() ); for( trackViewList::iterator it = m_trackViews.begin(); it != m_trackViews.end(); ++it ) { ( *it )->show(); ( *it )->update(); } } void TrackContainerView::createTrackView( track * _t ) { _t->createView( this ); } void TrackContainerView::deleteTrackView( trackView * _tv ) { track * t = _tv->getTrack(); removeTrackView( _tv ); delete _tv; engine::mixer()->lock(); delete t; engine::mixer()->unlock(); } const trackView * TrackContainerView::trackViewAt( const int _y ) const { const int abs_y = _y + m_scrollArea->verticalScrollBar()->value(); int y_cnt = 0; // debug code // qDebug( "abs_y %d", abs_y ); for( trackViewList::const_iterator it = m_trackViews.begin(); it != m_trackViews.end(); ++it ) { const int y_cnt1 = y_cnt; y_cnt += ( *it )->height(); if( abs_y >= y_cnt1 && abs_y < y_cnt ) { return( *it ); } } return( NULL ); } bool TrackContainerView::allowRubberband() const { return( false ); } void TrackContainerView::setPixelsPerTact( int _ppt ) { m_ppt = _ppt; // tell all TrackContentWidgets to update their background tile pixmap for( trackViewList::Iterator it = m_trackViews.begin(); it != m_trackViews.end(); ++it ) { ( *it )->getTrackContentWidget()->updateBackground(); } } void TrackContainerView::clearAllTracks() { while( !m_trackViews.empty() ) { trackView * tv = m_trackViews.takeLast(); track * t = tv->getTrack(); delete tv; delete t; } } void TrackContainerView::undoStep( JournalEntry & _je ) { #if 0 saveJournallingState( false ); switch( _je.actionID() ) { case AddTrack: { QMap map = _je.data().toMap(); track * t = dynamic_cast( engine::projectJournal()->getJournallingObject( map["id"].toInt() ) ); assert( t != NULL ); DataFile dataFile( DataFile::JournalData ); t->saveState( dataFile, dataFile.content() ); map["state"] = dataFile.toString(); _je.data() = map; t->deleteLater(); break; } case RemoveTrack: { DataFile dataFile( _je.data().toMap()["state"].toString().utf8() ); track::create( dataFile.content().firstChild().toElement(), m_tc ); break; } } restoreJournallingState(); #endif } void TrackContainerView::redoStep( JournalEntry & _je ) { #if 0 switch( _je.actionID() ) { case AddTrack: case RemoveTrack: _je.actionID() = ( _je.actionID() == AddTrack ) ? RemoveTrack : AddTrack; undoStep( _je ); _je.actionID() = ( _je.actionID() == AddTrack ) ? RemoveTrack : AddTrack; break; } #endif } void TrackContainerView::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, QString( "presetfile,pluginpresetfile,samplefile,instrument," "importedproject,track_%1,track_%2" ). arg( track::InstrumentTrack ). arg( track::SampleTrack ) ); } void TrackContainerView::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString value = stringPairDrag::decodeValue( _de ); engine::mixer()->lock(); if( type == "instrument" ) { InstrumentTrack * it = dynamic_cast( track::create( track::InstrumentTrack, m_tc ) ); it->loadInstrument( value ); //it->toggledInstrumentTrackButton( true ); _de->accept(); } else if( type == "samplefile" || type == "pluginpresetfile" ) { InstrumentTrack * it = dynamic_cast( track::create( track::InstrumentTrack, m_tc ) ); Instrument * i = it->loadInstrument( engine::pluginFileHandling()[fileItem::extension( value )]); i->loadFile( value ); //it->toggledInstrumentTrackButton( true ); _de->accept(); } else if( type == "presetfile" ) { DataFile dataFile( value ); InstrumentTrack * it = dynamic_cast( track::create( track::InstrumentTrack, m_tc ) ); it->setSimpleSerializing(); it->loadSettings( dataFile.content().toElement() ); //it->toggledInstrumentTrackButton( true ); _de->accept(); } else if( type == "importedproject" ) { ImportFilter::import( value, m_tc ); _de->accept(); } else if( type.left( 6 ) == "track_" ) { DataFile dataFile( value.toUtf8() ); track::create( dataFile.content().firstChild().toElement(), m_tc ); _de->accept(); } engine::mixer()->unlock(); } void TrackContainerView::mousePressEvent( QMouseEvent * _me ) { if( allowRubberband() == true ) { m_origin = m_scrollArea->mapFromParent( _me->pos() ); m_rubberBand->setGeometry( QRect( m_origin, QSize() ) ); m_rubberBand->show(); } QWidget::mousePressEvent( _me ); } void TrackContainerView::mouseMoveEvent( QMouseEvent * _me ) { if( rubberBandActive() == true ) { m_rubberBand->setGeometry( QRect( m_origin, m_scrollArea->mapFromParent( _me->pos() ) ). normalized() ); } QWidget::mouseMoveEvent( _me ); } void TrackContainerView::mouseReleaseEvent( QMouseEvent * _me ) { m_rubberBand->hide(); QWidget::mouseReleaseEvent( _me ); } void TrackContainerView::resizeEvent( QResizeEvent * _re ) { realignTracks(); QWidget::resizeEvent( _re ); } TrackContainerView::scrollArea::scrollArea( TrackContainerView * _parent ) : QScrollArea( _parent ), m_trackContainerView( _parent ) { setFrameStyle( QFrame::NoFrame ); setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); } TrackContainerView::scrollArea::~scrollArea() { } void TrackContainerView::scrollArea::wheelEvent( QWheelEvent * _we ) { // always pass wheel-event to parent-widget (song-editor // bb-editor etc.) because they might want to use it for zooming // or scrolling left/right if a modifier-key is pressed, otherwise // they do not accept it and we pass it up to QScrollArea m_trackContainerView->wheelEvent( _we ); if( !_we->isAccepted() ) { QScrollArea::wheelEvent( _we ); } } #include "moc_TrackContainerView.cxx" lmms-1.0.0/src/gui/AutomatableModelView.cpp0000644000175000017500000001412412313663627017340 0ustar tobytoby/* * AutomatableModelView.cpp - implementation of AutomatableModelView * * Copyright (c) 2011-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "AutomatableModelView.h" #include "AutomationPattern.h" #include "ControllerConnectionDialog.h" #include "ControllerConnection.h" #include "embed.h" #include "MainWindow.h" #include "string_pair_drag.h" AutomatableModelView::AutomatableModelView( ::Model* model, QWidget* _this ) : ModelView( model, _this ), m_description( QString::null ), m_unit( QString::null ) { widget()->setAcceptDrops( true ); widget()->setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); } AutomatableModelView::~AutomatableModelView() { } void AutomatableModelView::addDefaultActions( QMenu* menu ) { AutomatableModel* model = modelUntyped(); AutomatableModelViewSlots* amvSlots = new AutomatableModelViewSlots( this, menu ); menu->addAction( embed::getIconPixmap( "reload" ), AutomatableModel::tr( "&Reset (%1%2)" ). arg( model->displayValue( model->initValue() ) ). arg( m_unit ), model, SLOT( reset() ) ); menu->addSeparator(); menu->addAction( embed::getIconPixmap( "edit_copy" ), AutomatableModel::tr( "&Copy value (%1%2)" ). arg( model->displayValue( model->value() ) ). arg( m_unit ), model, SLOT( copyValue() ) ); menu->addAction( embed::getIconPixmap( "edit_paste" ), AutomatableModel::tr( "&Paste value (%1%2)"). arg( model->displayValue( AutomatableModel::copiedValue() ) ). arg( m_unit ), model, SLOT( pasteValue() ) ); menu->addSeparator(); menu->addAction( embed::getIconPixmap( "automation" ), AutomatableModel::tr( "Edit song-global automation" ), amvSlots, SLOT( editSongGlobalAutomation() ) ); menu->addAction( QPixmap(), AutomatableModel::tr( "Remove song-global automation" ), amvSlots, SLOT( removeSongGlobalAutomation() ) ); menu->addSeparator(); if( model->hasLinkedModels() ) { menu->addAction( embed::getIconPixmap( "edit-delete" ), AutomatableModel::tr( "Remove all linked controls" ), amvSlots, SLOT( unlinkAllModels() ) ); menu->addSeparator(); } QString controllerTxt; if( model->controllerConnection() ) { Controller* cont = model->controllerConnection()->getController(); if( cont ) { controllerTxt = AutomatableModel::tr( "Connected to %1" ).arg( cont->name() ); } else { controllerTxt = AutomatableModel::tr( "Connected to controller" ); } QMenu* contMenu = menu->addMenu( embed::getIconPixmap( "controller" ), controllerTxt ); contMenu->addAction( embed::getIconPixmap( "controller" ), AutomatableModel::tr("Edit connection..."), amvSlots, SLOT( execConnectionDialog() ) ); contMenu->addAction( embed::getIconPixmap( "cancel" ), AutomatableModel::tr("Remove connection"), amvSlots, SLOT( removeConnection() ) ); } else { menu->addAction( embed::getIconPixmap( "controller" ), AutomatableModel::tr("Connect to controller..."), amvSlots, SLOT( execConnectionDialog() ) ); } } void AutomatableModelView::setModel( Model* model, bool isOldModelValid ) { ModelView::setModel( model, isOldModelValid ); } void AutomatableModelView::mousePressEvent( QMouseEvent* event ) { if( event->button() == Qt::LeftButton && event->modifiers() & Qt::ControlModifier ) { new stringPairDrag( "automatable_model", QString::number( modelUntyped()->id() ), QPixmap(), widget() ); event->accept(); } else if( event->button() == Qt::MidButton ) { modelUntyped()->reset(); } } AutomatableModelViewSlots::AutomatableModelViewSlots( AutomatableModelView* amv, QObject* parent ) : QObject(), m_amv( amv ) { connect( parent, SIGNAL( destroyed() ), this, SLOT( deleteLater() ), Qt::QueuedConnection ); } void AutomatableModelViewSlots::execConnectionDialog() { // TODO[pg]: Display a dialog with list of controllers currently in the song // in addition to any system MIDI controllers AutomatableModel* m = m_amv->modelUntyped(); m->displayName(); ControllerConnectionDialog d( (QWidget*) engine::mainWindow(), m ); if( d.exec() == 1 ) { // Actually chose something if( d.chosenController() ) { // Update if( m->controllerConnection() ) { m->controllerConnection()->setController( d.chosenController() ); } // New else { ControllerConnection* cc = new ControllerConnection( d.chosenController() ); m->setControllerConnection( cc ); //cc->setTargetName( m->displayName() ); } } // no controller, so delete existing connection else { removeConnection(); } } } void AutomatableModelViewSlots::removeConnection() { AutomatableModel* m = m_amv->modelUntyped(); if( m->controllerConnection() ) { delete m->controllerConnection(); m->setControllerConnection( NULL ); } } void AutomatableModelViewSlots::editSongGlobalAutomation() { AutomationPattern::globalAutomationPattern( m_amv->modelUntyped() )->openInAutomationEditor(); } void AutomatableModelViewSlots::removeSongGlobalAutomation() { delete AutomationPattern::globalAutomationPattern( m_amv->modelUntyped() ); } void AutomatableModelViewSlots::unlinkAllModels() { m_amv->modelUntyped()->unlinkAllModels(); } #include "moc_AutomatableModelView.cxx" lmms-1.0.0/src/gui/LfoControllerDialog.cpp0000644000175000017500000002462412313663627017200 0ustar tobytoby/* * LfoControllerDialog.cpp - per-controller-specific view for changing a * controller's settings * * Copyright (c) 2008-2009 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include "caption_menu.h" #include "gui_templates.h" #include "embed.h" #include "engine.h" #include "led_checkbox.h" #include "MainWindow.h" #include "tooltip.h" #include "LfoController.h" #include "ControllerDialog.h" #include "knob.h" #include "TempoSyncKnob.h" #include "pixmap_button.h" const int CD_ENV_KNOBS_LBL_Y = 20; const int CD_KNOB_X_SPACING = 32; const int CD_LFO_SHAPES_X = 6; const int CD_LFO_SHAPES_Y = 36; const int CD_LFO_GRAPH_X = 6; const int CD_LFO_GRAPH_Y = CD_ENV_KNOBS_LBL_Y+15; const int CD_LFO_CD_KNOB_Y = CD_LFO_GRAPH_Y-2; const int CD_LFO_BASE_CD_KNOB_X = CD_LFO_SHAPES_X + 64; const int CD_LFO_SPEED_CD_KNOB_X = CD_LFO_BASE_CD_KNOB_X+CD_KNOB_X_SPACING; const int CD_LFO_AMOUNT_CD_KNOB_X = CD_LFO_SPEED_CD_KNOB_X+CD_KNOB_X_SPACING; const int CD_LFO_PHASE_CD_KNOB_X = CD_LFO_AMOUNT_CD_KNOB_X+CD_KNOB_X_SPACING; const int CD_LFO_MULTIPLIER_X = CD_LFO_PHASE_CD_KNOB_X+CD_KNOB_X_SPACING; LfoControllerDialog::LfoControllerDialog( Controller * _model, QWidget * _parent ) : ControllerDialog( _model, _parent ) { QString title = tr( "LFO" ); title.append( " (" ); title.append( _model->name() ); title.append( ")" ); setWindowTitle( title ); setWindowIcon( embed::getIconPixmap( "controller" ) ); setFixedSize( 240, 80 ); toolTip::add( this, tr( "LFO Controller" ) ); m_baseKnob = new knob( knobBright_26, this ); m_baseKnob->setLabel( tr( "BASE" ) ); m_baseKnob->move( CD_LFO_BASE_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_baseKnob->setHintText( tr( "Base amount:" ) + " ", "" ); m_baseKnob->setWhatsThis( tr("todo") ); m_speedKnob = new TempoSyncKnob( knobBright_26, this ); m_speedKnob->setLabel( tr( "SPD" ) ); m_speedKnob->move( CD_LFO_SPEED_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_speedKnob->setHintText( tr( "LFO-speed:" ) + " ", "" ); m_speedKnob->setWhatsThis( tr( "Use this knob for setting speed of the LFO. The " "bigger this value the faster the LFO oscillates and " "the faster the effect." ) ); m_amountKnob = new knob( knobBright_26, this ); m_amountKnob->setLabel( tr( "AMT" ) ); m_amountKnob->move( CD_LFO_AMOUNT_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_amountKnob->setHintText( tr( "Modulation amount:" ) + " ", "" ); m_amountKnob->setWhatsThis( tr( "Use this knob for setting modulation amount of the " "LFO. The bigger this value, the more the connected " "control (e.g. volume or cutoff-frequency) will " "be influenced by the LFO." ) ); m_phaseKnob = new knob( knobBright_26, this ); m_phaseKnob->setLabel( tr( "PHS" ) ); m_phaseKnob->move( CD_LFO_PHASE_CD_KNOB_X, CD_LFO_CD_KNOB_Y ); m_phaseKnob->setHintText( tr( "Phase offset:" ) + " ", "" + tr( "degrees" ) ); m_phaseKnob->setWhatsThis( tr( "With this knob you can set the phase offset of " "the LFO. That means you can move the " "point within an oscillation where the " "oscillator begins to oscillate. For example " "if you have a sine-wave and have a phase-" "offset of 180 degrees the wave will first go " "down. It's the same with a square-wave." ) ); pixmapButton * sin_wave_btn = new pixmapButton( this, NULL ); sin_wave_btn->move( CD_LFO_SHAPES_X, CD_LFO_SHAPES_Y ); sin_wave_btn->setActiveGraphic( embed::getIconPixmap( "sin_wave_active" ) ); sin_wave_btn->setInactiveGraphic( embed::getIconPixmap( "sin_wave_inactive" ) ); toolTip::add( sin_wave_btn, tr( "Click here for a sine-wave." ) ); pixmapButton * triangle_wave_btn = new pixmapButton( this, NULL ); triangle_wave_btn->move( CD_LFO_SHAPES_X + 15, CD_LFO_SHAPES_Y ); triangle_wave_btn->setActiveGraphic( embed::getIconPixmap( "triangle_wave_active" ) ); triangle_wave_btn->setInactiveGraphic( embed::getIconPixmap( "triangle_wave_inactive" ) ); toolTip::add( triangle_wave_btn, tr( "Click here for a triangle-wave." ) ); pixmapButton * saw_wave_btn = new pixmapButton( this, NULL ); saw_wave_btn->move( CD_LFO_SHAPES_X + 30, CD_LFO_SHAPES_Y ); saw_wave_btn->setActiveGraphic( embed::getIconPixmap( "saw_wave_active" ) ); saw_wave_btn->setInactiveGraphic( embed::getIconPixmap( "saw_wave_inactive" ) ); toolTip::add( saw_wave_btn, tr( "Click here for a saw-wave." ) ); pixmapButton * sqr_wave_btn = new pixmapButton( this, NULL ); sqr_wave_btn->move( CD_LFO_SHAPES_X + 45, CD_LFO_SHAPES_Y ); sqr_wave_btn->setActiveGraphic( embed::getIconPixmap( "square_wave_active" ) ); sqr_wave_btn->setInactiveGraphic( embed::getIconPixmap( "square_wave_inactive" ) ); toolTip::add( sqr_wave_btn, tr( "Click here for a square-wave." ) ); pixmapButton * moog_saw_wave_btn = new pixmapButton( this, NULL ); moog_saw_wave_btn->move( CD_LFO_SHAPES_X, CD_LFO_SHAPES_Y + 15 ); moog_saw_wave_btn->setActiveGraphic( embed::getIconPixmap( "moog_saw_wave_active" ) ); moog_saw_wave_btn->setInactiveGraphic( embed::getIconPixmap( "moog_saw_wave_inactive" ) ); toolTip::add( moog_saw_wave_btn, tr( "Click here for a a moog saw-wave." ) ); pixmapButton * exp_wave_btn = new pixmapButton( this, NULL ); exp_wave_btn->move( CD_LFO_SHAPES_X + 15, CD_LFO_SHAPES_Y + 15 ); exp_wave_btn->setActiveGraphic( embed::getIconPixmap( "exp_wave_active" ) ); exp_wave_btn->setInactiveGraphic( embed::getIconPixmap( "exp_wave_inactive" ) ); toolTip::add( exp_wave_btn, tr( "Click here for an exponential wave." ) ); pixmapButton * white_noise_btn = new pixmapButton( this, NULL ); white_noise_btn->move( CD_LFO_SHAPES_X + 30, CD_LFO_SHAPES_Y + 15 ); white_noise_btn->setActiveGraphic( embed::getIconPixmap( "white_noise_wave_active" ) ); white_noise_btn->setInactiveGraphic( embed::getIconPixmap( "white_noise_wave_inactive" ) ); toolTip::add( white_noise_btn, tr( "Click here for white-noise." ) ); m_userWaveBtn = new pixmapButton( this, NULL ); m_userWaveBtn->move( CD_LFO_SHAPES_X + 45, CD_LFO_SHAPES_Y + 15 ); m_userWaveBtn->setActiveGraphic( embed::getIconPixmap( "usr_wave_active" ) ); m_userWaveBtn->setInactiveGraphic( embed::getIconPixmap( "usr_wave_inactive" ) ); connect( m_userWaveBtn, SIGNAL( doubleClicked() ), this, SLOT( askUserDefWave() ) ); toolTip::add( m_userWaveBtn, tr( "Click here for a user-defined shape.\nDouble click to pick a file." ) ); m_waveBtnGrp = new automatableButtonGroup( this ); m_waveBtnGrp->addButton( sin_wave_btn ); m_waveBtnGrp->addButton( triangle_wave_btn ); m_waveBtnGrp->addButton( saw_wave_btn ); m_waveBtnGrp->addButton( sqr_wave_btn ); m_waveBtnGrp->addButton( moog_saw_wave_btn ); m_waveBtnGrp->addButton( exp_wave_btn ); m_waveBtnGrp->addButton( white_noise_btn ); m_waveBtnGrp->addButton( m_userWaveBtn ); pixmapButton * x1 = new pixmapButton( this, NULL ); x1->move( CD_LFO_MULTIPLIER_X, CD_LFO_SHAPES_Y ); x1->setActiveGraphic( embed::getIconPixmap( "lfo_x1_active" ) ); x1->setInactiveGraphic( embed::getIconPixmap( "lfo_x1_inactive" ) ); pixmapButton * x100 = new pixmapButton( this, NULL ); x100->move( CD_LFO_MULTIPLIER_X, CD_LFO_SHAPES_Y - 15 ); x100->setActiveGraphic( embed::getIconPixmap( "lfo_x100_active" ) ); x100->setInactiveGraphic( embed::getIconPixmap( "lfo_x100_inactive" ) ); pixmapButton * d100 = new pixmapButton( this, NULL ); d100->move( CD_LFO_MULTIPLIER_X, CD_LFO_SHAPES_Y + 15 ); d100->setActiveGraphic( embed::getIconPixmap( "lfo_d100_active" ) ); d100->setInactiveGraphic( embed::getIconPixmap( "lfo_d100_inactive" ) ); m_multiplierBtnGrp = new automatableButtonGroup( this ); m_multiplierBtnGrp->addButton( x1 ); m_multiplierBtnGrp->addButton( x100 ); m_multiplierBtnGrp->addButton( d100 ); setModel( _model ); setAutoFillBackground( true ); QPalette pal; pal.setBrush( backgroundRole(), embed::getIconPixmap( "lfo_controller_artwork" ) ); setPalette( pal ); } LfoControllerDialog::~LfoControllerDialog() { m_userWaveBtn->disconnect( this ); //delete m_subWindow; } void LfoControllerDialog::askUserDefWave() { SampleBuffer * sampleBuffer = dynamic_cast(this->model())-> m_userDefSampleBuffer; QString fileName = sampleBuffer->openAndSetWaveformFile(); if( fileName.isEmpty() == false ) { // TODO: toolTip::add( m_userWaveBtn, sampleBuffer->audioFile() ); } } void LfoControllerDialog::contextMenuEvent( QContextMenuEvent * ) { /* QPointer contextMenu = new captionMenu( getEffect()->publicName() ); contextMenu->addAction( embed::getIconPixmap( "arp_up_on" ), tr( "Move &up" ), this, SLOT( moveUp() ) ); contextMenu->addAction( embed::getIconPixmap( "arp_down_on" ), tr( "Move &down" ), this, SLOT( moveDown() ) ); contextMenu->addSeparator(); contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "&Remove this plugin" ), this, SLOT( deletePlugin() ) ); contextMenu->addSeparator(); contextMenu->addAction( embed::getIconPixmap( "help" ), tr( "&Help" ), this, SLOT( displayHelp() ) ); contextMenu->exec( QCursor::pos() ); delete contextMenu; */ } /* void lfoControllerDialog::paintEvent( QPaintEvent * _pe ) { QWidget::paintEvent( _pe ); } */ void LfoControllerDialog::modelChanged() { m_lfo = castModel(); m_baseKnob->setModel( &m_lfo->m_baseModel ); m_speedKnob->setModel( &m_lfo->m_speedModel ); m_amountKnob->setModel( &m_lfo->m_amountModel ); m_phaseKnob->setModel( &m_lfo->m_phaseModel ); m_waveBtnGrp->setModel( &m_lfo->m_waveModel ); m_multiplierBtnGrp->setModel( &m_lfo->m_multiplierModel ); } lmms-1.0.0/src/gui/Forms/0000755000175000017500000000000012313663627013646 5ustar tobytobylmms-1.0.0/src/gui/Forms/EffectSelectDialog.ui0000644000175000017500000000525412313663627017667 0ustar tobytoby EffectSelectDialog 0 0 585 547 Add effect true 10 500 250 Qt::ScrollBarAlwaysOff QAbstractItemView::SelectRows 0 200 16777215 210 Plugin description QFrame::NoFrame 0 0 497 109 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox rejected() EffectSelectDialog reject() 316 260 286 274 lmms-1.0.0/src/gui/AutomationPatternView.cpp0000644000175000017500000002361512313663627017604 0ustar tobytoby/* * AutomationPatternView.cpp - implementation of view for AutomationPattern * * Copyright (c) 2008-2010 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "AutomationPatternView.h" #include "AutomationEditor.h" #include "AutomationPattern.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "ProjectJournal.h" #include "rename_dialog.h" #include "string_pair_drag.h" #include "tooltip.h" AutomationPatternView::AutomationPatternView( AutomationPattern * _pattern, trackView * _parent ) : trackContentObjectView( _pattern, _parent ), m_pat( _pattern ), m_paintPixmap(), m_needsUpdate( true ) { connect( m_pat, SIGNAL( dataChanged() ), this, SLOT( update() ) ); connect( engine::automationEditor(), SIGNAL( currentPatternChanged() ), this, SLOT( update() ) ); setAttribute( Qt::WA_OpaquePaintEvent, true ); setFixedHeight( parentWidget()->height() - 2 ); setAutoResizeEnabled( false ); toolTip::add( this, tr( "double-click to open this pattern in " "automation editor" ) ); setStyle( QApplication::style() ); } AutomationPatternView::~AutomationPatternView() { } void AutomationPatternView::update() { m_needsUpdate = true; if( fixedTCOs() ) { m_pat->changeLength( m_pat->length() ); } trackContentObjectView::update(); } void AutomationPatternView::resetName() { m_pat->setName( QString::null ); } void AutomationPatternView::changeName() { QString s = m_pat->name(); renameDialog rename_dlg( s ); rename_dlg.exec(); m_pat->setName( s ); update(); } void AutomationPatternView::disconnectObject( QAction * _a ) { JournallingObject * j = engine::projectJournal()-> journallingObject( _a->data().toInt() ); if( j && dynamic_cast( j ) ) { float oldMin = m_pat->getMin(); float oldMax = m_pat->getMax(); m_pat->m_objects.erase( qFind( m_pat->m_objects.begin(), m_pat->m_objects.end(), dynamic_cast( j ) ) ); update(); //If automation editor is opened, update its display after disconnection if( engine::automationEditor() ) { engine::automationEditor()->updateAfterPatternChange(); } //if there is no more connection connected to the AutomationPattern if( m_pat->m_objects.size() == 0 ) { //scale the points to fit the new min. and max. value this->scaleTimemapToFit( oldMin, oldMax ); } } } void AutomationPatternView::constructContextMenu( QMenu * _cm ) { QAction * a = new QAction( embed::getIconPixmap( "automation" ), tr( "Open in Automation editor" ), _cm ); _cm->insertAction( _cm->actions()[0], a ); connect( a, SIGNAL( triggered( bool ) ), m_pat, SLOT( openInAutomationEditor() ) ); _cm->insertSeparator( _cm->actions()[1] ); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "edit_erase" ), tr( "Clear" ), m_pat, SLOT( clear() ) ); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ), this, SLOT( resetName() ) ); _cm->addAction( embed::getIconPixmap( "edit_rename" ), tr( "Change name" ), this, SLOT( changeName() ) ); if( !m_pat->m_objects.isEmpty() ) { _cm->addSeparator(); QMenu * m = new QMenu( tr( "%1 Connections" ). arg( m_pat->m_objects.count() ), _cm ); for( AutomationPattern::objectVector::iterator it = m_pat->m_objects.begin(); it != m_pat->m_objects.end(); ++it ) { if( *it ) { a = new QAction( tr( "Disconnect \"%1\"" ). arg( ( *it )->fullDisplayName() ), m ); a->setData( ( *it )->id() ); m->addAction( a ); } } connect( m, SIGNAL( triggered( QAction * ) ), this, SLOT( disconnectObject( QAction * ) ) ); _cm->addMenu( m ); } _cm->addSeparator(); } void AutomationPatternView::mouseDoubleClickEvent( QMouseEvent * _me ) { if( _me->button() != Qt::LeftButton ) { _me->ignore(); return; } m_pat->openInAutomationEditor(); } void AutomationPatternView::paintEvent( QPaintEvent * ) { if( m_needsUpdate == false ) { QPainter p( this ); p.drawPixmap( 0, 0, m_paintPixmap ); return; } QPainter _p( this ); const QColor styleColor = _p.pen().brush().color(); m_needsUpdate = false; if( m_paintPixmap.isNull() == true || m_paintPixmap.size() != size() ) { m_paintPixmap = QPixmap( size() ); } QPainter p( &m_paintPixmap ); QLinearGradient lingrad( 0, 0, 0, height() ); QColor c; if( !( m_pat->getTrack()->isMuted() || m_pat->isMuted() ) ) c = isSelected() ? QColor( 0, 0, 224 ) : styleColor; else c = QColor( 80,80,80 ); lingrad.setColorAt( 1, c.darker( 300 ) ); lingrad.setColorAt( 0, c ); p.setBrush( lingrad ); if( engine::automationEditor()->currentPattern() == m_pat ) p.setPen( c.lighter( 160 ) ); else p.setPen( c.lighter( 130 ) ); p.drawRect( 1, 1, width()-3, height()-3 ); p.setBrush( QBrush() ); if( engine::automationEditor()->currentPattern() == m_pat ) p.setPen( c.lighter( 130 ) ); else p.setPen( c.darker( 300 ) ); p.drawRect( 0, 0, width()-1, height()-1 ); const float ppt = fixedTCOs() ? ( parentWidget()->width() - 2 * TCO_BORDER_WIDTH ) / (float) m_pat->length().getTact() : pixelsPerTact(); const int x_base = TCO_BORDER_WIDTH; p.setPen( c.darker( 300 ) ); for( tact_t t = 1; t < m_pat->length().getTact(); ++t ) { const int tx = x_base + static_cast( ppt * t ) - 1; if( tx < ( width() - TCO_BORDER_WIDTH*2 ) ) { p.drawLine( tx, TCO_BORDER_WIDTH, tx, 5 ); p.drawLine( tx, height() - ( 4 + 2 * TCO_BORDER_WIDTH ), tx, height() - 2 * TCO_BORDER_WIDTH ); } } const float min = m_pat->firstObject()->minValue(); const float max = m_pat->firstObject()->maxValue(); const float y_scale = max - min; const float h = ( height() - 2*TCO_BORDER_WIDTH ) / y_scale; p.translate( 0.0f, max * height() / y_scale - TCO_BORDER_WIDTH ); p.scale( 1.0f, -h ); QLinearGradient lin2grad( 0, min, 0, max ); lin2grad.setColorAt( 1, c.lighter( 200 ) ); lin2grad.setColorAt( 0, c ); for( AutomationPattern::timeMap::const_iterator it = m_pat->getTimeMap().begin(); it != m_pat->getTimeMap().end(); ++it ) { if( it+1 == m_pat->getTimeMap().end() ) { const float x1 = x_base + it.key() * ppt / MidiTime::ticksPerTact(); const float x2 = (float)( width() - TCO_BORDER_WIDTH ); if( x1 > ( width() - TCO_BORDER_WIDTH ) ) break; p.fillRect( QRectF( x1, 0.0f, x2-x1, it.value() ), lin2grad ); break; } float *values = m_pat->valuesAfter( it.key() ); for( int i = it.key(); i < (it+1).key(); i++ ) { float value = values[i - it.key()]; const float x1 = x_base + i * ppt / MidiTime::ticksPerTact(); const float x2 = x_base + (i+1) * ppt / MidiTime::ticksPerTact(); if( x1 > ( width() - TCO_BORDER_WIDTH ) ) break; p.fillRect( QRectF( x1, 0.0f, x2-x1, value ), lin2grad ); } delete [] values; } p.resetMatrix(); p.setFont( pointSize<8>( p.font() ) ); QColor text_color = ( m_pat->isMuted() || m_pat->getTrack()->isMuted() ) ? QColor( 30, 30, 30 ) : QColor( 255, 255, 255 ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 4, p.fontMetrics().height()+1, m_pat->name() ); p.setPen( text_color ); p.drawText( 3, p.fontMetrics().height(), m_pat->name() ); if( m_pat->isMuted() ) { p.drawPixmap( 3, p.fontMetrics().height() + 1, embed::getIconPixmap( "muted", 16, 16 ) ); } p.end(); _p.drawPixmap( 0, 0, m_paintPixmap ); } void AutomationPatternView::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "automatable_model" ); if( !_dee->isAccepted() ) { trackContentObjectView::dragEnterEvent( _dee ); } } void AutomationPatternView::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString val = stringPairDrag::decodeValue( _de ); if( type == "automatable_model" ) { AutomatableModel * mod = dynamic_cast( engine::projectJournal()-> journallingObject( val.toInt() ) ); if( mod != NULL ) { m_pat->addObject( mod ); } update(); if( engine::automationEditor() && engine::automationEditor()->currentPattern() == m_pat ) { engine::automationEditor()->setCurrentPattern( m_pat ); } //This is the only model that's just added to AutomationPattern. if( m_pat->m_objects.size() == 1 ) { //scale the points to fit the new min. and max. value this->scaleTimemapToFit( AutomationPattern::DEFAULT_MIN_VALUE, AutomationPattern::DEFAULT_MAX_VALUE ); } } else { trackContentObjectView::dropEvent( _de ); } } /** * @brief Preserves the auto points over different scale */ void AutomationPatternView::scaleTimemapToFit( float oldMin, float oldMax ) { float newMin = m_pat->getMin(); float newMax = m_pat->getMax(); if( oldMin == newMin && oldMax == newMax ) { return; } for( AutomationPattern::timeMap::iterator it = m_pat->m_timeMap.begin(); it != m_pat->m_timeMap.end(); ++it ) { if( *it < oldMin ) { *it = oldMin; } else if( *it > oldMax ) { *it = oldMax; } *it = (*it-oldMin)*(newMax-newMin)/(oldMax-oldMin)+newMin; } m_pat->generateTangents(); } #include "moc_AutomationPatternView.cxx" lmms-1.0.0/src/gui/EffectSelectDialog.cpp0000644000175000017500000001271412313663627016745 0ustar tobytoby/* * EffectSelectDialog.cpp - dialog to choose effect plugin * * Copyright (c) 2006-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "EffectSelectDialog.h" #include "ui_EffectSelectDialog.h" #include "gui_templates.h" #include "embed.h" EffectSelectDialog::EffectSelectDialog( QWidget * _parent ) : QDialog( _parent ), ui( new Ui::EffectSelectDialog ), m_sourceModel(), m_model(), m_descriptionWidget( NULL ) { ui->setupUi( this ); setWindowIcon( embed::getIconPixmap( "setup_audio" ) ); // query effects Plugin::getDescriptorsOfAvailPlugins( m_pluginDescriptors ); EffectKeyList subPluginEffectKeys; for( Plugin::DescriptorList::ConstIterator it = m_pluginDescriptors.begin(); it != m_pluginDescriptors.end(); ++it ) { if( it->type != Plugin::Effect ) { continue; } if( it->subPluginFeatures ) { it->subPluginFeatures->listSubPluginKeys( // as iterators are always stated to be not // equal with pointers, we dereference the // iterator and take the address of the item, // so we're on the safe side and the compiler // likely will reduce that to just "it" &( *it ), subPluginEffectKeys ); } else { m_effectKeys << EffectKey( &( *it ), it->name ); } } m_effectKeys += subPluginEffectKeys; // and fill our source model QStringList pluginNames; for( EffectKeyList::ConstIterator it = m_effectKeys.begin(); it != m_effectKeys.end(); ++it ) { if( ( *it ).desc->subPluginFeatures ) { pluginNames += QString( "%1: %2" ).arg( ( *it ).desc->displayName, ( *it ).name ); } else { pluginNames += ( *it ).desc->displayName; } } int row = 0; for( QStringList::ConstIterator it = pluginNames.begin(); it != pluginNames.end(); ++it ) { m_sourceModel.setItem( row, 0, new QStandardItem( *it ) ); ++row; } // setup filtering m_model.setSourceModel( &m_sourceModel ); m_model.setFilterCaseSensitivity( Qt::CaseInsensitive ); connect( ui->filterEdit, SIGNAL( textChanged( const QString & ) ), &m_model, SLOT( setFilterRegExp( const QString & ) ) ); connect( ui->filterEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateSelection() ) ); ui->pluginList->setModel( &m_model ); // setup selection model QItemSelectionModel * selectionModel = new QItemSelectionModel( &m_model ); ui->pluginList->setSelectionModel( selectionModel ); connect( selectionModel, SIGNAL( currentRowChanged( const QModelIndex &, const QModelIndex & ) ), SLOT( rowChanged( const QModelIndex &, const QModelIndex & ) ) ); connect( ui->pluginList, SIGNAL( doubleClicked( const QModelIndex & ) ), SLOT( acceptSelection() ) ); // try to accept current selection when pressing "OK" connect( ui->buttonBox, SIGNAL( accepted() ), this, SLOT( acceptSelection() ) ); updateSelection(); show(); } EffectSelectDialog::~EffectSelectDialog() { } Effect * EffectSelectDialog::instantiateSelectedPlugin( EffectChain * _parent ) { if( !m_currentSelection.name.isEmpty() && m_currentSelection.desc ) { return Effect::instantiate( m_currentSelection.desc->name, _parent, &m_currentSelection ); } return NULL; } void EffectSelectDialog::acceptSelection() { if( m_currentSelection.isValid() ) { accept(); } } void EffectSelectDialog::rowChanged( const QModelIndex & _idx, const QModelIndex & ) { delete m_descriptionWidget; m_descriptionWidget = NULL; if( m_model.mapToSource( _idx ).row() < 0 ) { // invalidate current selection m_currentSelection = Plugin::Descriptor::SubPluginFeatures::Key(); } else { m_currentSelection = m_effectKeys[m_model.mapToSource( _idx ).row()]; } if( m_currentSelection.desc && m_currentSelection.desc->subPluginFeatures ) { m_descriptionWidget = new QWidget; QVBoxLayout * l = new QVBoxLayout( m_descriptionWidget ); l->setMargin( 4 ); l->setSpacing( 0 ); ui->scrollArea->setWidget( m_descriptionWidget ); m_currentSelection.desc->subPluginFeatures-> fillDescriptionWidget( m_descriptionWidget, &m_currentSelection ); foreach( QWidget * w, m_descriptionWidget->findChildren() ) { if( w->parent() == m_descriptionWidget ) { l->addWidget( w ); } } l->setSizeConstraint( QLayout::SetFixedSize ); m_descriptionWidget->show(); } } void EffectSelectDialog::updateSelection() { // no valid selection anymore due to changed filter? if( ui->pluginList->selectionModel()->selection().size() <= 0 ) { // then select our first item ui->pluginList->selectionModel()->select( m_model.index( 0, 0 ), QItemSelectionModel::ClearAndSelect ); rowChanged( m_model.index( 0, 0 ), QModelIndex() ); } } #include "moc_EffectSelectDialog.cxx" lmms-1.0.0/src/gui/bb_editor.cpp0000644000175000017500000001524112313663627015220 0ustar tobytoby/* * bb_editor.cpp - basic main-window for editing of beats and basslines * * Copyright (c) 2004-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "bb_editor.h" #include "bb_track_container.h" #include "embed.h" #include "MainWindow.h" #include "song.h" #include "tool_button.h" #include "config_mgr.h" #include "TrackContainer.h" #include "pattern.h" bbEditor::bbEditor( bbTrackContainer* tc ) : TrackContainerView( tc ), m_bbtc( tc ) { // create toolbar m_toolBar = new QWidget; m_toolBar->setFixedHeight( 32 ); m_toolBar->move( 0, 0 ); m_toolBar->setAutoFillBackground( true ); QPalette pal; pal.setBrush( m_toolBar->backgroundRole(), embed::getIconPixmap( "toolbar_bg" ) ); m_toolBar->setPalette( pal ); static_cast( layout() )->insertWidget( 0, m_toolBar ); QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar ); tb_layout->setSpacing( 0 ); tb_layout->setMargin( 0 ); setWindowIcon( embed::getIconPixmap( "bb_track_btn" ) ); setWindowTitle( tr( "Beat+Bassline Editor" ) ); // TODO: Use style sheet if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { setMinimumWidth( TRACK_OP_WIDTH_COMPACT + DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + 2 * TCO_BORDER_WIDTH + 264 ); } else { setMinimumWidth( TRACK_OP_WIDTH + DEFAULT_SETTINGS_WIDGET_WIDTH + 2 * TCO_BORDER_WIDTH + 264 ); } m_playButton = new toolButton( embed::getIconPixmap( "play" ), tr( "Play/pause current beat/bassline (Space)" ), this, SLOT( play() ), m_toolBar ); m_stopButton = new toolButton( embed::getIconPixmap( "stop" ), tr( "Stop playback of current beat/bassline (Space)" ), this, SLOT( stop() ), m_toolBar ); m_playButton->setObjectName( "playButton" ); m_stopButton->setObjectName( "stopButton" ); toolButton * add_bb_track = new toolButton( embed::getIconPixmap( "add_bb_track" ), tr( "Add beat/bassline" ), engine::getSong(), SLOT( addBBTrack() ), m_toolBar ); toolButton * add_automation_track = new toolButton( embed::getIconPixmap( "add_automation" ), tr( "Add automation-track" ), this, SLOT( addAutomationTrack() ), m_toolBar ); toolButton * remove_bar = new toolButton( embed::getIconPixmap( "step_btn_remove" ), tr( "Remove steps" ), this, SLOT( removeSteps() ), m_toolBar ); toolButton * add_bar = new toolButton( embed::getIconPixmap( "step_btn_add" ), tr( "Add steps" ), this, SLOT( addSteps() ), m_toolBar ); m_playButton->setWhatsThis( tr( "Click here to play the current " "beat/bassline. The beat/bassline is automatically " "looped when its end is reached." ) ); m_stopButton->setWhatsThis( tr( "Click here to stop playing of current " "beat/bassline." ) ); m_bbComboBox = new comboBox( m_toolBar ); m_bbComboBox->setFixedSize( 200, 22 ); m_bbComboBox->setModel( &tc->m_bbComboBoxModel ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_playButton ); tb_layout->addWidget( m_stopButton ); tb_layout->addSpacing( 20 ); tb_layout->addWidget( m_bbComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( add_bb_track ); tb_layout->addWidget( add_automation_track ); tb_layout->addStretch(); tb_layout->addWidget( remove_bar ); tb_layout->addWidget( add_bar ); tb_layout->addSpacing( 15 ); engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->layout()->setSizeConstraint( QLayout::SetMinimumSize ); parentWidget()->resize( minimumWidth(), 300 ); parentWidget()->move( 610, 5 ); parentWidget()->show(); setModel( tc ); connect( &tc->m_bbComboBoxModel, SIGNAL( dataChanged() ), this, SLOT( updatePosition() ) ); } bbEditor::~bbEditor() { } void bbEditor::removeBBView( int _bb ) { QList tl = trackViews(); for( int i = 0; i < tl.size(); ++i ) { tl[i]->getTrackContentWidget()->removeTCOView( _bb ); } } void bbEditor::setPauseIcon( bool pause ) { if( pause == true ) { m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } } void bbEditor::play() { if( engine::getSong()->playMode() != song::Mode_PlayBB ) { engine::getSong()->playBB(); } else { engine::getSong()->togglePause(); } } void bbEditor::stop() { engine::getSong()->stop(); } void bbEditor::updatePosition() { //realignTracks(); emit positionChanged( m_currentPosition ); } void bbEditor::addAutomationTrack() { (void) track::create( track::AutomationTrack, model() ); } void bbEditor::addSteps() { TrackContainer::TrackList tl = model()->tracks(); for( TrackContainer::TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { if( ( *it )->type() == track::InstrumentTrack ) { pattern * p = static_cast( ( *it )->getTCO( m_bbtc->currentBB() ) ); p->addSteps(); } } } void bbEditor::removeSteps() { TrackContainer::TrackList tl = model()->tracks(); for( TrackContainer::TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { if( ( *it )->type() == track::InstrumentTrack ) { pattern * p = static_cast( ( *it )->getTCO( m_bbtc->currentBB() ) ); p->removeSteps(); } } } void bbEditor::keyPressEvent( QKeyEvent * _ke ) { if ( _ke->key() == Qt::Key_Space ) { if( engine::getSong()->isPlaying() ) { stop(); } else { play(); } } else if ( _ke->key() == Qt::Key_Plus ) { if( m_bbtc->currentBB()+ 1 < m_bbtc->numOfBBs() ) { m_bbtc->setCurrentBB( m_bbtc->currentBB() + 1 ); } } else if ( _ke->key() == Qt::Key_Minus ) { if( m_bbtc->currentBB() > 0 ) { m_bbtc->setCurrentBB( m_bbtc->currentBB() - 1 ); } } else { // ignore event and pass to parent-widget _ke->ignore(); } } #include "moc_bb_editor.cxx" lmms-1.0.0/src/gui/file_browser.cpp0000644000175000017500000005320012313663627015746 0ustar tobytoby/* * file_browser.cpp - implementation of the project-, preset- and * sample-file-browser * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include "file_browser.h" #include "bb_track_container.h" #include "config_mgr.h" #include "debug.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "ImportFilter.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "MainWindow.h" #include "DataFile.h" #include "PresetPreviewPlayHandle.h" #include "SamplePlayHandle.h" #include "song.h" #include "string_pair_drag.h" #include "text_float.h" enum TreeWidgetItemTypes { TypeFileItem = QTreeWidgetItem::UserType, TypeDirectoryItem } ; fileBrowser::fileBrowser( const QString & _directories, const QString & _filter, const QString & _title, const QPixmap & _pm, QWidget * _parent, bool _dirs_as_items ) : SideBarWidget( _title, _pm, _parent ), m_directories( _directories ), m_filter( _filter ), m_dirsAsItems( _dirs_as_items ) { setWindowTitle( tr( "Browser" ) ); m_l = new fileBrowserTreeWidget( contentParent() ); addContentWidget( m_l ); QWidget * ops = new QWidget( contentParent() ); ops->setFixedHeight( 24 ); QHBoxLayout * opl = new QHBoxLayout( ops ); opl->setMargin( 0 ); opl->setSpacing( 0 ); m_filterEdit = new QLineEdit( ops ); connect( m_filterEdit, SIGNAL( textEdited( const QString & ) ), this, SLOT( filterItems( const QString & ) ) ); QPushButton * reload_btn = new QPushButton( embed::getIconPixmap( "reload" ), QString::null, ops ); connect( reload_btn, SIGNAL( clicked() ), this, SLOT( reloadTree() ) ); opl->addWidget( m_filterEdit ); opl->addSpacing( 5 ); opl->addWidget( reload_btn ); addContentWidget( ops ); reloadTree(); show(); } fileBrowser::~fileBrowser() { } void fileBrowser::filterItems( const QString & _filter ) { const bool show_all = _filter.isEmpty(); for( int i = 0; i < m_l->topLevelItemCount(); ++i ) { QTreeWidgetItem * it = m_l->topLevelItem( i ); // show all items if filter is empty if( show_all ) { it->setHidden( false ); if( it->childCount() ) { filterItems( it, _filter ); } } // is directory? else if( it->childCount() ) { // matches filter? if( it->text( 0 ). contains( _filter, Qt::CaseInsensitive ) ) { // yes, then show everything below it->setHidden( false ); filterItems( it, QString::null ); } else { // only show if item below matches filter it->setHidden( !filterItems( it, _filter ) ); } } // a standard item (i.e. no file or directory item?) else if( it->type() == QTreeWidgetItem::Type ) { // hide in every case when filtering it->setHidden( true ); } else { // file matches filter? it->setHidden( !it->text( 0 ). contains( _filter, Qt::CaseInsensitive ) ); } } } bool fileBrowser::filterItems( QTreeWidgetItem * _item, const QString & _filter ) { const bool show_all = _filter.isEmpty(); bool matched = false; for( int i = 0; i < _item->childCount(); ++i ) { QTreeWidgetItem * it = _item->child( i ); bool cm = false; // whether current item matched // show all items if filter is empty if( show_all ) { it->setHidden( false ); if( it->childCount() ) { filterItems( it, _filter ); } } // is directory? else if( it->childCount() ) { // matches filter? if( it->text( 0 ). contains( _filter, Qt::CaseInsensitive ) ) { // yes, then show everything below it->setHidden( false ); filterItems( it, QString::null ); cm = true; } else { // only show if item below matches filter cm = filterItems( it, _filter ); it->setHidden( !cm ); } } // a standard item (i.e. no file or directory item?) else if( it->type() == QTreeWidgetItem::Type ) { // hide in every case when filtering it->setHidden( true ); } else { // file matches filter? cm = it->text( 0 ). contains( _filter, Qt::CaseInsensitive ); it->setHidden( !cm ); } if( cm ) { matched = true; } } return matched; } void fileBrowser::reloadTree( void ) { m_filterEdit->clear(); m_l->clear(); QStringList paths = m_directories.split( '*' ); for( QStringList::iterator it = paths.begin(); it != paths.end(); ++it ) { addItems( *it ); } } void fileBrowser::addItems( const QString & _path ) { if( m_dirsAsItems ) { m_l->addTopLevelItem( new directory( _path, QString::null, m_filter ) ); return; } QDir cdir( _path ); QStringList files = cdir.entryList( QDir::Dirs, QDir::Name ); for( QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it ) { QString cur_file = *it; if( cur_file[0] != '.' ) { bool orphan = true; for( int i = 0; i < m_l->topLevelItemCount(); ++i ) { directory * d = dynamic_cast( m_l->topLevelItem( i ) ); if( d == NULL || cur_file < d->text( 0 ) ) { m_l->insertTopLevelItem( i, new directory( cur_file, _path, m_filter ) ); orphan = false; break; } else if( cur_file == d->text( 0 ) ) { d->addDirectory( _path ); orphan = false; break; } } if( orphan ) { m_l->addTopLevelItem( new directory( cur_file, _path, m_filter ) ); } } } files = cdir.entryList( QDir::Files, QDir::Name ); for( QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it ) { QString cur_file = *it; if( cur_file[0] != '.' ) { // TODO: don't insert instead of removing, order changed // remove existing file-items QList existing = m_l->findItems( cur_file, Qt::MatchFixedString ); if( !existing.empty() ) { delete existing.front(); } (void) new fileItem( m_l, cur_file, _path ); } } } void fileBrowser::keyPressEvent( QKeyEvent * _ke ) { if( _ke->key() == Qt::Key_F5 ) { reloadTree(); } else { _ke->ignore(); } } fileBrowserTreeWidget::fileBrowserTreeWidget( QWidget * _parent ) : QTreeWidget( _parent ), m_mousePressed( false ), m_pressPos(), m_previewPlayHandle( NULL ), m_pphMutex( QMutex::Recursive ), m_contextMenuItem( NULL ) { setColumnCount( 1 ); headerItem()->setHidden( true ); setSortingEnabled( false ); setFont( pointSizeF( font(), 7.5f ) ); connect( this, SIGNAL( itemDoubleClicked( QTreeWidgetItem *, int ) ), SLOT( activateListItem( QTreeWidgetItem *, int ) ) ); connect( this, SIGNAL( itemCollapsed( QTreeWidgetItem * ) ), SLOT( updateDirectory( QTreeWidgetItem * ) ) ); connect( this, SIGNAL( itemExpanded( QTreeWidgetItem * ) ), SLOT( updateDirectory( QTreeWidgetItem * ) ) ); } fileBrowserTreeWidget::~fileBrowserTreeWidget() { } void fileBrowserTreeWidget::contextMenuEvent( QContextMenuEvent * _e ) { fileItem * f = dynamic_cast( itemAt( _e->pos() ) ); if( f != NULL && ( f->handling() == fileItem::LoadAsPreset || f->handling() == fileItem::LoadByPlugin ) ) { m_contextMenuItem = f; QMenu contextMenu( this ); contextMenu.addAction( tr( "Send to active instrument-track" ), this, SLOT( sendToActiveInstrumentTrack() ) ); contextMenu.addAction( tr( "Open in new instrument-track/" "Song-Editor" ), this, SLOT( openInNewInstrumentTrackSE() ) ); contextMenu.addAction( tr( "Open in new instrument-track/" "B+B Editor" ), this, SLOT( openInNewInstrumentTrackBBE() ) ); contextMenu.exec( _e->globalPos() ); m_contextMenuItem = NULL; } } void fileBrowserTreeWidget::mousePressEvent( QMouseEvent * _me ) { QTreeWidget::mousePressEvent( _me ); if( _me->button() != Qt::LeftButton ) { return; } QTreeWidgetItem * i = itemAt( _me->pos() ); if ( i ) { // TODO: Restrict to visible selection // if ( _me->x() > header()->cellPos( header()->mapToActual( 0 ) ) // + treeStepSize() * ( i->depth() + ( rootIsDecorated() ? // 1 : 0 ) ) + itemMargin() || // _me->x() < header()->cellPos( // header()->mapToActual( 0 ) ) ) // { m_pressPos = _me->pos(); m_mousePressed = true; // } } fileItem * f = dynamic_cast( i ); if( f != NULL ) { m_pphMutex.lock(); if( m_previewPlayHandle != NULL ) { engine::mixer()->removePlayHandle( m_previewPlayHandle ); m_previewPlayHandle = NULL; } // in special case of sample-files we do not care about // handling() rather than directly creating a SamplePlayHandle if( f->type() == fileItem::SampleFile ) { textFloat * tf = textFloat::displayMessage( tr( "Loading sample" ), tr( "Please wait, loading sample for " "preview..." ), embed::getIconPixmap( "sample_file", 24, 24 ), 0 ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents ); SamplePlayHandle * s = new SamplePlayHandle( f->fullName() ); s->setDoneMayReturnTrue( false ); m_previewPlayHandle = s; delete tf; } else if( f->type() != fileItem::VstPluginFile && ( f->handling() == fileItem::LoadAsPreset || f->handling() == fileItem::LoadByPlugin ) ) { m_previewPlayHandle = new PresetPreviewPlayHandle( f->fullName(), f->handling() == fileItem::LoadByPlugin ); } if( m_previewPlayHandle != NULL ) { if( !engine::mixer()->addPlayHandle( m_previewPlayHandle ) ) { m_previewPlayHandle = NULL; } } m_pphMutex.unlock(); } } void fileBrowserTreeWidget::mouseMoveEvent( QMouseEvent * _me ) { if( m_mousePressed == true && ( m_pressPos - _me->pos() ).manhattanLength() > QApplication::startDragDistance() ) { // make sure any playback is stopped mouseReleaseEvent( NULL ); fileItem * f = dynamic_cast( itemAt( m_pressPos ) ); if( f != NULL ) { switch( f->type() ) { case fileItem::PresetFile: new stringPairDrag( f->handling() == fileItem::LoadAsPreset ? "presetfile" : "pluginpresetfile", f->fullName(), embed::getIconPixmap( "preset_file" ), this ); break; case fileItem::SampleFile: new stringPairDrag( "samplefile", f->fullName(), embed::getIconPixmap( "sample_file" ), this ); break; case fileItem::MidiFile: // don't allow dragging FLP-files as FLP import filter clears project // without asking // case fileItem::FlpFile: new stringPairDrag( "importedproject", f->fullName(), embed::getIconPixmap( "midi_file" ), this ); break; case fileItem::VstPluginFile: new stringPairDrag( "vstplugin", f->fullName(), embed::getIconPixmap( "sample_file" ), this ); break; default: break; } } } } void fileBrowserTreeWidget::mouseReleaseEvent( QMouseEvent * _me ) { m_mousePressed = false; m_pphMutex.lock(); if( m_previewPlayHandle != NULL ) { // if there're samples shorter than 3 seconds, we don't // stop them if the user releases mouse-button... if( m_previewPlayHandle->type() == PlayHandle::TypeSamplePlayHandle ) { SamplePlayHandle * s = dynamic_cast( m_previewPlayHandle ); if( s && s->totalFrames() - s->framesDone() <= static_cast( engine::mixer()-> processingSampleRate() * 3 ) ) { s->setDoneMayReturnTrue( true ); m_previewPlayHandle = NULL; m_pphMutex.unlock(); return; } } engine::mixer()->removePlayHandle( m_previewPlayHandle ); m_previewPlayHandle = NULL; } m_pphMutex.unlock(); } void fileBrowserTreeWidget::handleFile( fileItem * f, InstrumentTrack * _it ) { engine::mixer()->lock(); switch( f->handling() ) { case fileItem::LoadAsProject: if( engine::mainWindow()->mayChangeProject() ) { engine::getSong()->loadProject( f->fullName() ); } break; case fileItem::LoadByPlugin: { const QString e = f->extension(); Instrument * i = _it->instrument(); if( i == NULL || !i->descriptor()->supportsFileType( e ) ) { i = _it->loadInstrument( engine::pluginFileHandling()[e] ); } i->loadFile( f->fullName() ); break; } case fileItem::LoadAsPreset: { DataFile dataFile( f->fullName() ); InstrumentTrack::removeMidiPortNode( dataFile ); _it->setSimpleSerializing(); _it->loadSettings( dataFile.content().toElement() ); break; } case fileItem::ImportAsProject: if( f->type() == fileItem::FlpFile && !engine::mainWindow()->mayChangeProject() ) { break; } ImportFilter::import( f->fullName(), engine::getSong() ); break; case fileItem::NotSupported: default: break; } engine::mixer()->unlock(); } void fileBrowserTreeWidget::activateListItem( QTreeWidgetItem * _item, int _column ) { fileItem * f = dynamic_cast( _item ); if( f == NULL ) { return; } if( f->handling() == fileItem::LoadAsProject || f->handling() == fileItem::ImportAsProject ) { handleFile( f, NULL ); } else if( f->handling() != fileItem::NotSupported ) { engine::mixer()->lock(); InstrumentTrack * it = dynamic_cast( track::create( track::InstrumentTrack, engine::getBBTrackContainer() ) ); handleFile( f, it ); engine::mixer()->unlock(); } } void fileBrowserTreeWidget::openInNewInstrumentTrack( TrackContainer* tc ) { if( m_contextMenuItem->handling() == fileItem::LoadAsPreset || m_contextMenuItem->handling() == fileItem::LoadByPlugin ) { engine::mixer()->lock(); InstrumentTrack * it = dynamic_cast( track::create( track::InstrumentTrack, tc ) ); handleFile( m_contextMenuItem, it ); engine::mixer()->unlock(); } } void fileBrowserTreeWidget::openInNewInstrumentTrackBBE( void ) { openInNewInstrumentTrack( engine::getBBTrackContainer() ); } void fileBrowserTreeWidget::openInNewInstrumentTrackSE( void ) { openInNewInstrumentTrack( engine::getSong() ); } void fileBrowserTreeWidget::sendToActiveInstrumentTrack( void ) { // get all windows opened in the workspace QList pl = engine::mainWindow()->workspace()-> subWindowList( QMdiArea::StackingOrder ); QListIterator w( pl ); w.toBack(); // now we travel through the window-list until we find an // instrument-track while( w.hasPrevious() ) { InstrumentTrackWindow * itw = dynamic_cast( w.previous()->widget() ); if( itw != NULL && itw->isHidden() == false ) { handleFile( m_contextMenuItem, itw->model() ); break; } } } void fileBrowserTreeWidget::updateDirectory( QTreeWidgetItem * _item ) { directory * dir = dynamic_cast( _item ); if( dir != NULL ) { dir->update(); } } QPixmap * directory::s_folderPixmap = NULL; QPixmap * directory::s_folderOpenedPixmap = NULL; QPixmap * directory::s_folderLockedPixmap = NULL; directory::directory( const QString & _name, const QString & _path, const QString & _filter ) : QTreeWidgetItem( QStringList( _name ), TypeDirectoryItem ), m_directories( _path ), m_filter( _filter ) { initPixmaps(); setChildIndicatorPolicy( QTreeWidgetItem::ShowIndicator ); if( !QDir( fullName() ).isReadable() ) { setIcon( 0, *s_folderLockedPixmap ); } else { setIcon( 0, *s_folderPixmap ); } } void directory::initPixmaps( void ) { if( s_folderPixmap == NULL ) { s_folderPixmap = new QPixmap( embed::getIconPixmap( "folder" ) ); } if( s_folderOpenedPixmap == NULL ) { s_folderOpenedPixmap = new QPixmap( embed::getIconPixmap( "folder_opened" ) ); } if( s_folderLockedPixmap == NULL ) { s_folderLockedPixmap = new QPixmap( embed::getIconPixmap( "folder_locked" ) ); } } void directory::update( void ) { if( !isExpanded() ) { setIcon( 0, *s_folderPixmap ); return; } setIcon( 0, *s_folderOpenedPixmap ); if( !childCount() ) { for( QStringList::iterator it = m_directories.begin(); it != m_directories.end(); ++it ) { int top_index = childCount(); if( addItems( fullName( *it ) ) && ( *it ).contains( configManager::inst()->dataDir() ) ) { QTreeWidgetItem * sep = new QTreeWidgetItem; sep->setText( 0, fileBrowserTreeWidget::tr( "--- Factory files ---" ) ); sep->setIcon( 0, embed::getIconPixmap( "factory_files" ) ); insertChild( top_index, sep ); } } } } bool directory::addItems( const QString & _path ) { QDir thisDir( _path ); if( !thisDir.isReadable() ) { return false; } treeWidget()->setUpdatesEnabled( false ); bool added_something = false; QStringList files = thisDir.entryList( QDir::Dirs, QDir::Name ); for( QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it ) { QString cur_file = *it; if( cur_file[0] != '.' ) { bool orphan = true; for( int i = 0; i < childCount(); ++i ) { directory * d = dynamic_cast( child( i ) ); if( d == NULL || cur_file < d->text( 0 ) ) { insertChild( i, new directory( cur_file, _path, m_filter ) ); orphan = false; break; } else if( cur_file == d->text( 0 ) ) { d->addDirectory( _path ); orphan = false; break; } } if( orphan ) { addChild( new directory( cur_file, _path, m_filter ) ); } added_something = true; } } QList items; files = thisDir.entryList( QDir::Files, QDir::Name ); for( QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it ) { QString cur_file = *it; if( cur_file[0] != '.' && thisDir.match( m_filter, cur_file.toLower() ) ) { items << new fileItem( cur_file, _path ); added_something = true; } } addChildren( items ); treeWidget()->setUpdatesEnabled( true ); return added_something; } QPixmap * fileItem::s_projectFilePixmap = NULL; QPixmap * fileItem::s_presetFilePixmap = NULL; QPixmap * fileItem::s_sampleFilePixmap = NULL; QPixmap * fileItem::s_midiFilePixmap = NULL; QPixmap * fileItem::s_flpFilePixmap = NULL; QPixmap * fileItem::s_unknownFilePixmap = NULL; fileItem::fileItem( QTreeWidget * _parent, const QString & _name, const QString & _path ) : QTreeWidgetItem( _parent, QStringList( _name) , TypeFileItem ), m_path( _path ) { determineFileType(); initPixmaps(); } fileItem::fileItem( const QString & _name, const QString & _path ) : QTreeWidgetItem( QStringList( _name ), TypeFileItem ), m_path( _path ) { determineFileType(); initPixmaps(); } void fileItem::initPixmaps( void ) { if( s_projectFilePixmap == NULL ) { s_projectFilePixmap = new QPixmap( embed::getIconPixmap( "project_file", 16, 16 ) ); } if( s_presetFilePixmap == NULL ) { s_presetFilePixmap = new QPixmap( embed::getIconPixmap( "preset_file", 16, 16 ) ); } if( s_sampleFilePixmap == NULL ) { s_sampleFilePixmap = new QPixmap( embed::getIconPixmap( "sample_file", 16, 16 ) ); } if( s_midiFilePixmap == NULL ) { s_midiFilePixmap = new QPixmap( embed::getIconPixmap( "midi_file", 16, 16 ) ); } if( s_flpFilePixmap == NULL ) { s_flpFilePixmap = new QPixmap( embed::getIconPixmap( "midi_file", 16, 16 ) ); } if( s_unknownFilePixmap == NULL ) { s_unknownFilePixmap = new QPixmap( embed::getIconPixmap( "unknown_file" ) ); } switch( m_type ) { case ProjectFile: setIcon( 0, *s_projectFilePixmap ); break; case PresetFile: setIcon( 0, *s_presetFilePixmap ); break; case SampleFile: case SoundFontFile: // TODO case PatchFile: // TODO setIcon( 0, *s_sampleFilePixmap ); break; case MidiFile: setIcon( 0, *s_midiFilePixmap ); break; case FlpFile: setIcon( 0, *s_flpFilePixmap ); break; case UnknownFile: default: setIcon( 0, *s_unknownFilePixmap ); break; } } void fileItem::determineFileType( void ) { m_handling = NotSupported; const QString ext = extension(); if( ext == "mmp" || ext == "mpt" || ext == "mmpz" ) { m_type = ProjectFile; m_handling = LoadAsProject; } else if( ext == "xpf" || ext == "xml" ) { m_type = PresetFile; m_handling = LoadAsPreset; } else if( ext == "xiz" && engine::pluginFileHandling().contains( ext ) ) { m_type = PresetFile; m_handling = LoadByPlugin; } else if( ext == "sf2" ) { m_type = SoundFontFile; } else if( ext == "pat" ) { m_type = PatchFile; } else if( ext == "mid" ) { m_type = MidiFile; m_handling = ImportAsProject; } else if( ext == "flp" ) { m_type = FlpFile; m_handling = ImportAsProject; } else if( ext == "dll" ) { m_type = VstPluginFile; m_handling = LoadByPlugin; } else { m_type = UnknownFile; } if( m_handling == NotSupported && !ext.isEmpty() && engine::pluginFileHandling().contains( ext ) ) { m_handling = LoadByPlugin; // classify as sample if not classified by anything yet but can // be handled by a certain plugin if( m_type == UnknownFile ) { m_type = SampleFile; } } } QString fileItem::extension( void ) { return extension( fullName() ); } QString fileItem::extension( const QString & _file ) { return QFileInfo( _file ).suffix().toLower(); } #include "moc_file_browser.cxx" lmms-1.0.0/src/gui/dialogs/0000755000175000017500000000000012313663627014202 5ustar tobytobylmms-1.0.0/src/gui/dialogs/VersionedSaveDialog.cpp0000644000175000017500000000766312313663627020617 0ustar tobytoby/* * VersionedSaveDialog.cpp - implementation of class VersionedSaveDialog * * Copyright (c) 2014 Lukas W * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "VersionedSaveDialog.h" VersionedSaveDialog::VersionedSaveDialog( QWidget *parent, const QString &caption, const QString &directory, const QString &filter ) : FileDialog(parent, caption, directory, filter) { setAcceptMode( QFileDialog::AcceptSave ); setFileMode( QFileDialog::AnyFile ); // Create + and - buttons QPushButton *plusButton( new QPushButton( "+", this) ); plusButton->setToolTip( tr( "Increment version number" ) ); QPushButton *minusButton( new QPushButton( "-", this ) ); minusButton->setToolTip( tr( "Decrement version number" ) ); plusButton->setFixedWidth( plusButton->fontMetrics().width( "+" ) + 30 ); minusButton->setFixedWidth( minusButton->fontMetrics().width( "+" ) + 30 ); // Add buttons to grid layout. For doing this, remove the lineEdit and // replace it with a HBox containing lineEdit and the buttons. QGridLayout *layout = dynamic_cast( this->layout() ); QWidget *lineEdit = findChild(); layout->removeWidget( lineEdit ); QHBoxLayout* hLayout( new QHBoxLayout() ); hLayout->addWidget( lineEdit ); hLayout->addWidget( plusButton ); hLayout->addWidget( minusButton ); layout->addLayout( hLayout, 2, 1 ); // Connect + and - buttons connect( plusButton, SIGNAL( clicked() ), this, SLOT( incrementVersion() )); connect( minusButton, SIGNAL( clicked() ), this, SLOT( decrementVersion() )); } bool VersionedSaveDialog::changeFileNameVersion(QString &fileName, bool increment ) { static QRegExp regexp( "-\\d+(\\.\\w+)?$" ); int idx = regexp.indexIn( fileName ); // For file names without extension (no ".mmpz") int insertIndex = fileName.lastIndexOf( '.' ); if ( insertIndex < idx+1 ) insertIndex = fileName.size(); if ( idx == -1 ) { // Can't decrement if there is no version number if ( increment == false ) return false; else fileName.insert( insertIndex, "-01" ); } else { // Find current version number QString number = fileName.mid( idx+1, insertIndex - idx - 1 ); bool ok; ushort version = number.toUShort( &ok ); Q_ASSERT( ok ); // Can't decrement 0 if ( !increment and version == 0 ) return false; // Replace version number version = increment ? version + 1 : version - 1; QString newnumber = QString( "%1" ).arg( version, 2, 10, QChar( '0' ) ); fileName.replace( idx+1, number.length(), newnumber ); } return true; } void VersionedSaveDialog::incrementVersion() { const QStringList& selected = selectedFiles(); if ( selected.size() != 1 ) return; QString file = selected[0]; changeFileNameVersion( file, true ); clearSelection(); selectFile( file ); } void VersionedSaveDialog::decrementVersion() { const QStringList& selected = selectedFiles(); if ( selected.size() != 1 ) return; QString file = selected[0]; changeFileNameVersion( file, false ); clearSelection(); selectFile( file ); } #include "moc_VersionedSaveDialog.cxx" lmms-1.0.0/src/gui/dialogs/FileDialog.cpp0000644000175000017500000000425712313663627016715 0ustar tobytoby/* * FileDialog.cpp - implementation of class FileDialog * * Copyright (c) 2014 Lukas W * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "config_mgr.h" #include "FileDialog.h" FileDialog::FileDialog( QWidget *parent, const QString &caption, const QString &directory, const QString &filter ) : QFileDialog( parent, caption, directory, filter ) { #if QT_VERSION >= 0x040806 setOption( QFileDialog::DontUseCustomDirectoryIcons ); #endif // Add additional locations to the sidebar QList urls = sidebarUrls(); urls << QUrl::fromLocalFile( QDesktopServices::storageLocation( QDesktopServices::DesktopLocation ) ); // Find downloads directory QDir downloadDir( QDir::homePath() + "/Downloads" ); if ( ! downloadDir.exists() ) downloadDir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation ) + "/Downloads"; if ( downloadDir.exists() ) urls << QUrl::fromLocalFile( downloadDir.absolutePath() ); urls << QUrl::fromLocalFile( QDesktopServices::storageLocation( QDesktopServices::MusicLocation ) ); urls << QUrl::fromLocalFile( configManager::inst()->workingDir() ); setSidebarUrls(urls); } void FileDialog::clearSelection() { QListView *view = findChild(); Q_ASSERT( view ); view->clearSelection(); } #include "moc_FileDialog.cxx" lmms-1.0.0/src/gui/dialogs/about_dialog.ui0000644000175000017500000001674612313663627017210 0ustar tobytoby AboutDialog 0 0 558 357 About LMMS 8 8 64 64 font:12pt; font-weight:bold; LMMS (Linux MultiMedia Studio) Version %1 (%2/%3, Qt %4, %5) Qt::Horizontal 40 20 0 About Qt::Vertical QSizePolicy::Fixed 20 10 LMMS - easy music production for everyone true Qt::Vertical QSizePolicy::Fixed 20 10 Copyright (c) 2004-2014, LMMS developers true Qt::Vertical QSizePolicy::Fixed 20 10 <html><head/><body><p><a href="http://lmms.sourceforge.net"><span style=" text-decoration: underline; color:#0000ff;">http://lmms.sourceforge.net</span></a></p></body></html> true Qt::Vertical 20 40 Authors true Translation true Current language not translated (or native English). If you're interested in translating LMMS in another language or want to improve existing translations, you're welcome to help us! Simply contact the maintainer! License true Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() AboutDialog accept() 248 254 157 274 buttonBox rejected() AboutDialog reject() 316 260 286 274 lmms-1.0.0/src/gui/dialogs/export_project.ui0000644000175000017500000002374412313663627017622 0ustar tobytoby ExportProjectDialog 0 0 519 412 Export project Output -1 9 File format: Samplerate: 44100 Hz 48000 Hz 88200 Hz 96000 Hz 192000 Hz -1 0 Bitrate: 2 64 KBit/s 128 KBit/s 160 KBit/s 192 KBit/s 256 KBit/s 320 KBit/s 0 Depth: 16 Bit Integer 32 Bit Float Qt::Vertical QSizePolicy::Fixed 1 10 Please note that not all of the parameters above apply for all file formats. true Qt::Vertical 163 20 Quality settings Interpolation: 1 Zero Order Hold Sinc Fastest Sinc Medium (recommended) Sinc Best (very slow!) Oversampling (use with care!): 1x (None) 2x 4x 8x Sample-exact controllers Export as loop (remove end silence) Alias-free oscillators Qt::Vertical 20 40 Qt::Horizontal 40 20 Start Cancel false 0 cancelButton clicked() ExportProjectDialog reject() 357 293 202 175 lmms-1.0.0/src/gui/ControllerDialog.cpp0000644000175000017500000000261412313663627016532 0ustar tobytoby/* * ControllerDialog.cpp - per-controller-specific view for changing a * controller's settings * * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "ControllerDialog.h" #include "Controller.h" ControllerDialog::ControllerDialog( Controller * _controller, QWidget * _parent ) : QWidget( _parent ), ModelView( _controller, this ) { } ControllerDialog::~ControllerDialog() { } void ControllerDialog::closeEvent( QCloseEvent * _ce ) { _ce->ignore(); emit closed(); } #include "moc_ControllerDialog.cxx" lmms-1.0.0/src/gui/ModelView.cpp0000644000175000017500000000336612313663627015167 0ustar tobytoby/* * ModelView.cpp - implementation of ModelView * * Copyright (c) 2007-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "ModelView.h" ModelView::ModelView( Model* model, QWidget* widget ) : m_widget( widget ), m_model( model ) { } ModelView::~ModelView() { if( m_model != NULL && m_model->isDefaultConstructed() ) { delete m_model; } } void ModelView::setModel( Model* model, bool isOldModelValid ) { if( isOldModelValid && m_model != NULL ) { if( m_model->isDefaultConstructed() ) { delete m_model; } else { m_model->disconnect( widget() ); } } m_model = model; doConnections(); widget()->update(); modelChanged(); } void ModelView::doConnections() { if( m_model != NULL ) { QObject::connect( m_model, SIGNAL( dataChanged() ), widget(), SLOT( update() ) ); QObject::connect( m_model, SIGNAL( propertiesChanged() ), widget(), SLOT( update() ) ); } } lmms-1.0.0/src/gui/AutomationEditor.cpp0000644000175000017500000015307212313663627016563 0ustar tobytoby/* * AutomationEditor.cpp - implementation of AutomationEditor which is used for * actual setting of dynamic values * * Copyright (c) 2008-2014 Tobias Doerffel * Copyright (c) 2008-2013 Paul Giblock * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AutomationEditor.h" #include #include #include #include #include #include #include #include #include #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include "SongEditor.h" #include "MainWindow.h" #include "embed.h" #include "engine.h" #include "pixmap_button.h" #include "templates.h" #include "gui_templates.h" #include "timeline.h" #include "tooltip.h" #include "tool_button.h" #include "text_float.h" #include "combobox.h" #include "bb_track_container.h" #include "PianoRoll.h" #include "debug.h" QPixmap * AutomationEditor::s_toolDraw = NULL; QPixmap * AutomationEditor::s_toolErase = NULL; QPixmap * AutomationEditor::s_toolSelect = NULL; QPixmap * AutomationEditor::s_toolMove = NULL; const QColor DRAGGABLE_PIN_COLOR = QColor( 0xFF, 0x00, 0x00 ); const QColor DRAGGABLE_PIN_BORDER_COLOR = QColor( 0xFF, 0xFF, 0xFF ); AutomationEditor::AutomationEditor() : QWidget(), m_zoomingXModel(), m_zoomingYModel(), m_quantizeModel(), m_patternMutex( QMutex::Recursive ), m_pattern( NULL ), m_minLevel( 0 ), m_maxLevel( 0 ), m_step( 1 ), m_scrollLevel( 0 ), m_bottomLevel( 0 ), m_topLevel( 0 ), m_currentPosition(), m_action( NONE ), m_moveStartLevel( 0 ), m_moveStartTick( 0 ), m_drawLastLevel( 0.0f ), m_drawLastTick( 0 ), m_ppt( DEFAULT_PPT ), m_y_delta( DEFAULT_Y_DELTA ), m_y_auto( TRUE ), m_editMode( DRAW ), m_scrollBack( FALSE ) { connect( this, SIGNAL( currentPatternChanged() ), this, SLOT( updateAfterPatternChange() ), Qt::QueuedConnection ); // init pixmaps if( s_toolDraw == NULL ) { s_toolDraw = new QPixmap( embed::getIconPixmap( "edit_draw" ) ); } if( s_toolErase == NULL ) { s_toolErase= new QPixmap( embed::getIconPixmap( "edit_erase" ) ); } if( s_toolSelect == NULL ) { s_toolSelect = new QPixmap( embed::getIconPixmap( "edit_select" ) ); } if( s_toolMove == NULL ) { s_toolMove = new QPixmap( embed::getIconPixmap( "edit_move" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); // add time-line m_timeLine = new timeLine( VALUES_WIDTH, 32, m_ppt, engine::getSong()->getPlayPos( song::Mode_PlayAutomationPattern ), m_currentPosition, this ); connect( this, SIGNAL( positionChanged( const MidiTime & ) ), m_timeLine, SLOT( updatePosition( const MidiTime & ) ) ); connect( m_timeLine, SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( updatePosition( const MidiTime & ) ) ); m_toolBar = new QWidget( this ); m_toolBar->setFixedHeight( 32 ); m_toolBar->move( 0, 0 ); m_toolBar->setAutoFillBackground( TRUE ); QPalette pal; pal.setBrush( m_toolBar->backgroundRole(), embed::getIconPixmap( "toolbar_bg" ) ); m_toolBar->setPalette( pal ); QHBoxLayout * tb_layout = new QHBoxLayout( m_toolBar ); tb_layout->setMargin( 0 ); tb_layout->setSpacing( 0 ); // init control-buttons at the top m_playButton = new toolButton( embed::getIconPixmap( "play" ), tr( "Play/pause current pattern (Space)" ), this, SLOT( play() ), m_toolBar ); m_stopButton = new toolButton( embed::getIconPixmap( "stop" ), tr( "Stop playing of current pattern (Space)" ), this, SLOT( stop() ), m_toolBar ); m_playButton->setObjectName( "playButton" ); m_stopButton->setObjectName( "stopButton" ); m_playButton->setWhatsThis( tr( "Click here if you want to play the current pattern. " "This is useful while editing it. The pattern is " "automatically looped when the end is reached." ) ); m_stopButton->setWhatsThis( tr( "Click here if you want to stop playing of the " "current pattern." ) ); removeSelection(); // init scrollbars m_leftRightScroll = new QScrollBar( Qt::Horizontal, this ); m_leftRightScroll->setSingleStep( 1 ); connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ), this, SLOT( horScrolled( int ) ) ); m_topBottomScroll = new QScrollBar( Qt::Vertical, this ); m_topBottomScroll->setSingleStep( 1 ); m_topBottomScroll->setPageStep( 20 ); connect( m_topBottomScroll, SIGNAL( valueChanged( int ) ), this, SLOT( verScrolled( int ) ) ); // init edit-buttons at the top m_drawButton = new toolButton( embed::getIconPixmap( "edit_draw" ), tr( "Draw mode (Shift+D)" ), this, SLOT( drawButtonToggled() ), m_toolBar ); m_drawButton->setCheckable( TRUE ); m_drawButton->setChecked( TRUE ); m_eraseButton = new toolButton( embed::getIconPixmap( "edit_erase" ), tr( "Erase mode (Shift+E)" ), this, SLOT( eraseButtonToggled() ), m_toolBar ); m_eraseButton->setCheckable( TRUE ); //TODO: m_selectButton and m_moveButton are broken. /*m_selectButton = new toolButton( embed::getIconPixmap( "edit_select" ), tr( "Select mode (Shift+S)" ), this, SLOT( selectButtonToggled() ), m_toolBar ); m_selectButton->setCheckable( TRUE ); m_moveButton = new toolButton( embed::getIconPixmap( "edit_move" ), tr( "Move selection mode (Shift+M)" ), this, SLOT( moveButtonToggled() ), m_toolBar ); m_moveButton->setCheckable( TRUE );*/ QButtonGroup * tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_drawButton ); tool_button_group->addButton( m_eraseButton ); //tool_button_group->addButton( m_selectButton ); //tool_button_group->addButton( m_moveButton ); tool_button_group->setExclusive( TRUE ); m_drawButton->setWhatsThis( tr( "Click here and draw-mode will be activated. In this " "mode you can add and move single values. This " "is the default mode which is used most of the time. " "You can also press 'Shift+D' on your keyboard to " "activate this mode." ) ); m_eraseButton->setWhatsThis( tr( "Click here and erase-mode will be activated. In this " "mode you can erase single values. You can also press " "'Shift+E' on your keyboard to activate this mode." ) ); /*m_selectButton->setWhatsThis( tr( "Click here and select-mode will be activated. In this " "mode you can select values. This is necessary " "if you want to cut, copy, paste, delete, or move " "values. You can also press 'Shift+S' on your keyboard " "to activate this mode." ) ); m_moveButton->setWhatsThis( tr( "If you click here, move-mode will be activated. In this " "mode you can move the values you selected in select-" "mode. You can also press 'Shift+M' on your keyboard " "to activate this mode." ) );*/ m_discreteButton = new toolButton( embed::getIconPixmap( "progression_discrete" ), tr( "Discrete progression" ), this, SLOT( discreteButtonToggled() ), m_toolBar ); m_discreteButton->setCheckable( true ); m_discreteButton->setChecked( true ); m_linearButton = new toolButton( embed::getIconPixmap( "progression_linear" ), tr( "Linear progression" ), this, SLOT( linearButtonToggled() ), m_toolBar ); m_linearButton->setCheckable( true ); m_cubicHermiteButton = new toolButton( embed::getIconPixmap( "progression_cubic_hermite" ), tr( "Cubic Hermite progression" ), this, SLOT( cubicHermiteButtonToggled() ), m_toolBar ); m_cubicHermiteButton->setCheckable( true ); // setup tension-stuff m_tensionKnob = new knob( knobSmall_17, this, "Tension" ); m_tensionModel = new FloatModel(1.0, 0.0, 1.0, 0.01); connect( m_tensionModel, SIGNAL( dataChanged() ), this, SLOT( tensionChanged() ) ); QLabel * tension_lbl = new QLabel( m_toolBar ); tension_lbl->setText( tr("Tension: ") ); tool_button_group = new QButtonGroup( this ); tool_button_group->addButton( m_discreteButton ); tool_button_group->addButton( m_linearButton ); tool_button_group->addButton( m_cubicHermiteButton ); tool_button_group->setExclusive( true ); m_discreteButton->setWhatsThis( tr( "Click here to choose discrete progressions for this " "automation pattern. The value of the connected " "object will remain constant between control points " "and be set immediately to the new value when each " "control point is reached." ) ); m_linearButton->setWhatsThis( tr( "Click here to choose linear progressions for this " "automation pattern. The value of the connected " "object will change at a steady rate over time " "between control points to reach the correct value at " "each control point without a sudden change." ) ); m_cubicHermiteButton->setWhatsThis( tr( "Click here to choose cubic hermite progressions for this " "automation pattern. The value of the connected " "object will change in a smooth curve and ease in to " "the peaks and valleys." ) ); m_cutButton = new toolButton( embed::getIconPixmap( "edit_cut" ), tr( "Cut selected values (Ctrl+X)" ), this, SLOT( cutSelectedValues() ), m_toolBar ); m_copyButton = new toolButton( embed::getIconPixmap( "edit_copy" ), tr( "Copy selected values (Ctrl+C)" ), this, SLOT( copySelectedValues() ), m_toolBar ); m_pasteButton = new toolButton( embed::getIconPixmap( "edit_paste" ), tr( "Paste values from clipboard " "(Ctrl+V)" ), this, SLOT( pasteValues() ), m_toolBar ); m_cutButton->setWhatsThis( tr( "Click here and selected values will be cut into the " "clipboard. You can paste them anywhere in any pattern " "by clicking on the paste button." ) ); m_copyButton->setWhatsThis( tr( "Click here and selected values will be copied into " "the clipboard. You can paste them anywhere in any " "pattern by clicking on the paste button." ) ); m_pasteButton->setWhatsThis( tr( "Click here and the values from the clipboard will be " "pasted at the first visible measure." ) ); // setup zooming-stuff QLabel * zoom_x_lbl = new QLabel( m_toolBar ); zoom_x_lbl->setPixmap( embed::getIconPixmap( "zoom_x" ) ); m_zoomingXComboBox = new comboBox( m_toolBar ); m_zoomingXComboBox->setFixedSize( 80, 22 ); for( int i = 0; i < 6; ++i ) { m_zoomingXModel.addItem( QString::number( 25 << i ) + "%" ); } m_zoomingXModel.setValue( m_zoomingXModel.findText( "100%" ) ); m_zoomingXComboBox->setModel( &m_zoomingXModel ); connect( &m_zoomingXModel, SIGNAL( dataChanged() ), this, SLOT( zoomingXChanged() ) ); QLabel * zoom_y_lbl = new QLabel( m_toolBar ); zoom_y_lbl->setPixmap( embed::getIconPixmap( "zoom_y" ) ); m_zoomingYComboBox = new comboBox( m_toolBar ); m_zoomingYComboBox->setFixedSize( 80, 22 ); m_zoomingYModel.addItem( "Auto" ); for( int i = 0; i < 6; ++i ) { m_zoomingYModel.addItem( QString::number( 25 << i ) + "%" ); } m_zoomingYModel.setValue( m_zoomingYModel.findText( "Auto" ) ); m_zoomingYComboBox->setModel( &m_zoomingYModel ); connect( &m_zoomingYModel, SIGNAL( dataChanged() ), this, SLOT( zoomingYChanged() ) ); // setup quantize-stuff QLabel * quantize_lbl = new QLabel( m_toolBar ); quantize_lbl->setPixmap( embed::getIconPixmap( "quantize" ) ); m_quantizeComboBox = new comboBox( m_toolBar ); m_quantizeComboBox->setFixedSize( 60, 22 ); // TODO: leak ComboBoxModel * quantize_model = new ComboBoxModel( /* this */ ); for( int i = 0; i < 7; ++i ) { quantize_model->addItem( "1/" + QString::number( 1 << i ) ); } quantize_model->setValue( quantize_model->findText( "1/16" ) ); m_quantizeComboBox->setModel( quantize_model ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_playButton ); tb_layout->addWidget( m_stopButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_drawButton ); tb_layout->addWidget( m_eraseButton ); //tb_layout->addWidget( m_selectButton ); //tb_layout->addWidget( m_moveButton ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_discreteButton ); tb_layout->addWidget( m_linearButton ); tb_layout->addWidget( m_cubicHermiteButton ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( tension_lbl ); tb_layout->addSpacing( 5 ); tb_layout->addWidget( m_tensionKnob ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( m_cutButton ); tb_layout->addWidget( m_copyButton ); tb_layout->addWidget( m_pasteButton ); tb_layout->addSpacing( 10 ); m_timeLine->addToolButtons( m_toolBar ); tb_layout->addSpacing( 15 ); tb_layout->addWidget( zoom_x_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_zoomingXComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( zoom_y_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_zoomingYComboBox ); tb_layout->addSpacing( 10 ); tb_layout->addWidget( quantize_lbl ); tb_layout->addSpacing( 4 ); tb_layout->addWidget( m_quantizeComboBox ); tb_layout->addStretch(); // setup our actual window setFocusPolicy( Qt::StrongFocus ); setFocus(); setWindowIcon( embed::getIconPixmap( "automation" ) ); setCurrentPattern( NULL ); setMouseTracking( TRUE ); setMinimumSize( tb_layout->minimumSize().width(), 128 ); // add us to workspace if( engine::mainWindow()->workspace() ) { engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->resize( INITIAL_WIDTH, INITIAL_HEIGHT ); parentWidget()->move( 5, 5 ); parentWidget()->hide(); } else { resize( INITIAL_WIDTH, INITIAL_HEIGHT ); hide(); } } AutomationEditor::~AutomationEditor() { m_zoomingXModel.disconnect(); m_zoomingYModel.disconnect(); m_tensionModel->disconnect(); } void AutomationEditor::setCurrentPattern( AutomationPattern * _new_pattern ) { m_patternMutex.lock(); m_pattern = _new_pattern; m_patternMutex.unlock(); emit currentPatternChanged(); } void AutomationEditor::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); } void AutomationEditor::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); } void AutomationEditor::setPauseIcon( bool pause ) { if( pause == true ) { m_playButton->setIcon( embed::getIconPixmap( "pause" ) ); } else { m_playButton->setIcon( embed::getIconPixmap( "play" ) ); } } void AutomationEditor::updateAfterPatternChange() { QMutexLocker m( &m_patternMutex ); m_currentPosition = 0; if( !validPattern() ) { setWindowTitle( tr( "Automation Editor - no pattern" ) ); m_minLevel = m_maxLevel = m_scrollLevel = 0; m_step = 1; resizeEvent( NULL ); return; } if( m_pattern->progressionType() == AutomationPattern::DiscreteProgression && !m_discreteButton->isChecked() ) { m_discreteButton->setChecked( true ); } if( m_pattern->progressionType() == AutomationPattern::LinearProgression && !m_linearButton->isChecked() ) { m_linearButton->setChecked( true ); } if( m_pattern->progressionType() == AutomationPattern::CubicHermiteProgression && !m_cubicHermiteButton->isChecked() ) { m_cubicHermiteButton->setChecked( true ); } m_minLevel = m_pattern->firstObject()->minValue(); m_maxLevel = m_pattern->firstObject()->maxValue(); m_step = m_pattern->firstObject()->step(); m_scrollLevel = ( m_minLevel + m_maxLevel ) / 2; // resizeEvent() does the rest for us (scrolling, range-checking // of levels and so on...) resizeEvent( NULL ); setWindowTitle( tr( "Automation Editor - %1" ).arg( m_pattern->name() ) ); update(); } void AutomationEditor::update() { QWidget::update(); QMutexLocker m( &m_patternMutex ); // Note detuning? if( m_pattern && !m_pattern->getTrack() ) { engine::pianoRoll()->update(); } } void AutomationEditor::removeSelection() { m_selectStartTick = 0; m_selectedTick = 0; m_selectStartLevel = 0; m_selectedLevels = 0; } void AutomationEditor::closeEvent( QCloseEvent * _ce ) { QApplication::restoreOverrideCursor(); if( parentWidget() ) { parentWidget()->hide(); } else { hide(); } _ce->ignore(); } void AutomationEditor::keyPressEvent( QKeyEvent * _ke ) { switch( _ke->key() ) { case Qt::Key_Up: m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); _ke->accept(); break; case Qt::Key_Down: m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); _ke->accept(); break; case Qt::Key_Left: if( ( m_timeLine->pos() -= 16 ) < 0 ) { m_timeLine->pos().setTicks( 0 ); } m_timeLine->updatePosition(); _ke->accept(); break; case Qt::Key_Right: m_timeLine->pos() += 16; m_timeLine->updatePosition(); _ke->accept(); break; case Qt::Key_C: if( _ke->modifiers() & Qt::ControlModifier ) { copySelectedValues(); _ke->accept(); } break; case Qt::Key_X: if( _ke->modifiers() & Qt::ControlModifier ) { cutSelectedValues(); _ke->accept(); } break; case Qt::Key_V: if( _ke->modifiers() & Qt::ControlModifier ) { pasteValues(); _ke->accept(); } break; //TODO: m_selectButton and m_moveButton are broken. /*case Qt::Key_A: if( _ke->modifiers() & Qt::ControlModifier ) { m_selectButton->setChecked( TRUE ); selectAll(); update(); _ke->accept(); } break;*/ case Qt::Key_D: if( _ke->modifiers() & Qt::ShiftModifier ) { m_drawButton->setChecked( TRUE ); _ke->accept(); } break; case Qt::Key_E: if( _ke->modifiers() & Qt::ShiftModifier ) { m_eraseButton->setChecked( TRUE ); _ke->accept(); } break; //TODO: m_selectButton and m_moveButton are broken. /*case Qt::Key_S: if( _ke->modifiers() & Qt::ShiftModifier ) { m_selectButton->setChecked( TRUE ); _ke->accept(); } break; case Qt::Key_M: if( _ke->modifiers() & Qt::ShiftModifier ) { m_moveButton->setChecked( TRUE ); _ke->accept(); } break;*/ case Qt::Key_Delete: deleteSelectedValues(); _ke->accept(); break; case Qt::Key_Space: if( engine::getSong()->isPlaying() ) { stop(); } else { play(); } _ke->accept(); break; case Qt::Key_Home: m_timeLine->pos().setTicks( 0 ); m_timeLine->updatePosition(); _ke->accept(); break; default: break; } } void AutomationEditor::leaveEvent( QEvent * _e ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QWidget::leaveEvent( _e ); } void AutomationEditor::drawLine( int _x0, float _y0, int _x1, float _y1 ) { int deltax = qRound( qAbs( _x1 - _x0 ) ); float deltay = qAbs( _y1 - _y0 ); int x = _x0; float y = _y0; int xstep; int ystep; if( deltax < quantization() ) { return; } deltax /= quantization(); float yscale = deltay / ( deltax ); if( _x0 < _x1) { xstep = quantization(); } else { xstep = -( quantization() ); } if( _y0 < _y1 ) { ystep = 1; } else { ystep = -1; } int i = 0; while( i < deltax ) { y = _y0 + ( ystep * yscale * i ); x += xstep; i += 1; m_pattern->removeValue( MidiTime( x ) ); m_pattern->putValue( MidiTime( x ), y ); } } void AutomationEditor::disableTensionKnob() { m_tensionKnob->setEnabled( false ); } void AutomationEditor::mousePressEvent( QMouseEvent * _me ) { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } if( _me->y() > TOP_MARGIN ) { float level = getLevel( _me->y() ); int x = _me->x(); if( x > VALUES_WIDTH ) { // set or move value x -= VALUES_WIDTH; // get tick in which the user clicked int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; // get time map of current pattern timeMap & time_map = m_pattern->getTimeMap(); // will be our iterator in the following loop timeMap::iterator it = time_map.begin(); // loop through whole time-map... while( it != time_map.end() ) { MidiTime len = 4; // and check whether the user clicked on an // existing value if( pos_ticks >= it.key() && len > 0 && ( it+1==time_map.end() || pos_ticks <= (it+1).key() ) && ( pos_ticks<= it.key() + DefaultTicksPerTact *4 / m_ppt ) && level <= it.value() ) { break; } ++it; } // left button?? if( _me->button() == Qt::LeftButton && m_editMode == DRAW ) { // Connect the dots if( _me->modifiers() & Qt::ShiftModifier ) { drawLine( m_drawLastTick, m_drawLastLevel, pos_ticks, level ); } m_drawLastTick = pos_ticks; m_drawLastLevel = level; // did it reach end of map because // there's no value?? if( it == time_map.end() ) { // then set new value MidiTime value_pos( pos_ticks ); MidiTime new_time = m_pattern->setDragValue( value_pos, level ); // reset it so that it can be used for // ops (move, resize) after this // code-block it = time_map.find( new_time ); } // move it m_action = MOVE_VALUE; int aligned_x = (int)( (float)( ( it.key() - m_currentPosition ) * m_ppt ) / DefaultTicksPerTact ); m_moveXOffset = x - aligned_x - 1; // set move-cursor QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); engine::getSong()->setModified(); } else if( ( _me->button() == Qt::RightButton && m_editMode == DRAW ) || m_editMode == ERASE ) { // erase single value if( it != time_map.end() ) { m_pattern->removeValue( it.key() ); engine::getSong()->setModified(); } m_action = NONE; } else if( _me->button() == Qt::LeftButton && m_editMode == SELECT ) { // select an area of values m_selectStartTick = pos_ticks; m_selectedTick = 0; m_selectStartLevel = level; m_selectedLevels = 1; m_action = SELECT_VALUES; } else if( _me->button() == Qt::RightButton && m_editMode == SELECT ) { // when clicking right in select-move, we // switch to move-mode m_moveButton->setChecked( TRUE ); } else if( _me->button() == Qt::LeftButton && m_editMode == MOVE ) { // move selection (including selected values) // save position where move-process began m_moveStartTick = pos_ticks; m_moveStartLevel = level; m_action = MOVE_SELECTION; engine::getSong()->setModified(); } else if( _me->button() == Qt::RightButton && m_editMode == MOVE ) { // when clicking right in select-move, we // switch to draw-mode m_drawButton->setChecked( TRUE ); } update(); } } } void AutomationEditor::mouseReleaseEvent( QMouseEvent * _me ) { if( m_editMode == DRAW ) { if( m_action == MOVE_VALUE ) { m_pattern->applyDragValue(); } QApplication::restoreOverrideCursor(); } m_action = NONE; } #include void AutomationEditor::mouseMoveEvent( QMouseEvent * _me ) { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { update(); return; } if( _me->y() > TOP_MARGIN ) { float level = getLevel( _me->y() ); int x = _me->x(); if( _me->x() <= VALUES_WIDTH ) { update(); return; } x -= VALUES_WIDTH; if( m_action == MOVE_VALUE ) { x -= m_moveXOffset; } int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; if( _me->buttons() & Qt::LeftButton && m_editMode == DRAW ) { if( m_action == MOVE_VALUE ) { // moving value if( pos_ticks < 0 ) { pos_ticks = 0; } drawLine( m_drawLastTick, m_drawLastLevel, pos_ticks, level ); m_drawLastTick = pos_ticks; m_drawLastLevel = level; // we moved the value so the value has to be // moved properly according to new starting- // time in the time map of pattern m_pattern->setDragValue( MidiTime( pos_ticks ), level ); } engine::getSong()->setModified(); } else if( ( _me->buttons() & Qt::RightButton && m_editMode == DRAW ) || ( _me->buttons() & Qt::LeftButton && m_editMode == ERASE ) ) { m_pattern->removeValue( MidiTime( pos_ticks ) ); } else if( _me->buttons() & Qt::NoButton && m_editMode == DRAW ) { // set move- or resize-cursor // get time map of current pattern timeMap & time_map = m_pattern->getTimeMap(); // will be our iterator in the following loop timeMap::iterator it = time_map.begin(); // loop through whole time map... for( ; it != time_map.end(); ++it ) { // and check whether the cursor is over an // existing value if( pos_ticks >= it.key() && ( it+1==time_map.end() || pos_ticks <= (it+1).key() ) && level <= it.value() ) { break; } } // did it reach end of map because there's // no value?? if( it != time_map.end() ) { if( QApplication::overrideCursor() ) { if( QApplication::overrideCursor()->shape() != Qt::SizeAllCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } else { QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); } } else { // the cursor is over no value, so restore // cursor while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } } } else if( _me->buttons() & Qt::LeftButton && m_editMode == SELECT && m_action == SELECT_VALUES ) { // change size of selection if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( VALUES_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - VALUES_WIDTH ) { x = width() - VALUES_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -qRound( m_selectStartTick ); } m_selectedLevels = level - m_selectStartLevel; if( level <= m_selectStartLevel ) { --m_selectedLevels; } } else if( _me->buttons() & Qt::LeftButton && m_editMode == MOVE && m_action == MOVE_SELECTION ) { // move selection + selected values // do horizontal move-stuff int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; int ticks_diff = pos_ticks - m_moveStartTick; if( m_selectedTick > 0 ) { if( (int) m_selectStartTick + ticks_diff < 0 ) { ticks_diff = -m_selectStartTick; } } else { if( (int) m_selectStartTick + m_selectedTick + ticks_diff < 0 ) { ticks_diff = -( m_selectStartTick + m_selectedTick ); } } m_selectStartTick += ticks_diff; int tact_diff = ticks_diff / DefaultTicksPerTact; ticks_diff = ticks_diff % DefaultTicksPerTact; // do vertical move-stuff float level_diff = level - m_moveStartLevel; if( m_selectedLevels > 0 ) { if( m_selectStartLevel + level_diff < m_minLevel ) { level_diff = m_minLevel - m_selectStartLevel; } else if( m_selectStartLevel + m_selectedLevels + level_diff > m_maxLevel ) { level_diff = m_maxLevel - m_selectStartLevel - m_selectedLevels; } } else { if( m_selectStartLevel + m_selectedLevels + level_diff < m_minLevel ) { level_diff = m_minLevel - m_selectStartLevel - m_selectedLevels; } else if( m_selectStartLevel + level_diff > m_maxLevel ) { level_diff = m_maxLevel - m_selectStartLevel; } } m_selectStartLevel += level_diff; timeMap new_selValuesForMove; for( timeMap::iterator it = m_selValuesForMove.begin(); it != m_selValuesForMove.end(); ++it ) { MidiTime new_value_pos; if( it.key() ) { int value_tact = ( it.key() / DefaultTicksPerTact ) + tact_diff; int value_ticks = ( it.key() % DefaultTicksPerTact ) + ticks_diff; // ensure value_ticks range if( value_ticks / DefaultTicksPerTact ) { value_tact += value_ticks / DefaultTicksPerTact; value_ticks %= DefaultTicksPerTact; } m_pattern->removeValue( it.key() ); new_value_pos = MidiTime( value_tact, value_ticks ); } new_selValuesForMove[ m_pattern->putValue( new_value_pos, it.value () + level_diff, FALSE )] = it.value() + level_diff; } m_selValuesForMove = new_selValuesForMove; m_moveStartTick = pos_ticks; m_moveStartLevel = level; } } else { if( _me->buttons() & Qt::LeftButton && m_editMode == SELECT && m_action == SELECT_VALUES ) { int x = _me->x() - VALUES_WIDTH; if( x < 0 && m_currentPosition > 0 ) { x = 0; QCursor::setPos( mapToGlobal( QPoint( VALUES_WIDTH, _me->y() ) ) ); if( m_currentPosition >= 4 ) { m_leftRightScroll->setValue( m_currentPosition - 4 ); } else { m_leftRightScroll->setValue( 0 ); } } else if( x > width() - VALUES_WIDTH ) { x = width() - VALUES_WIDTH; QCursor::setPos( mapToGlobal( QPoint( width(), _me->y() ) ) ); m_leftRightScroll->setValue( m_currentPosition + 4 ); } // get tick in which the cursor is posated int pos_ticks = x * DefaultTicksPerTact / m_ppt + m_currentPosition; m_selectedTick = pos_ticks - m_selectStartTick; if( (int) m_selectStartTick + m_selectedTick < 0 ) { m_selectedTick = -qRound( m_selectStartTick ); } float level = getLevel( _me->y() ); if( level <= m_bottomLevel ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), height() - SCROLLBAR_SIZE ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() + 1 ); level = m_bottomLevel; } else if( level >= m_topLevel ) { QCursor::setPos( mapToGlobal( QPoint( _me->x(), TOP_MARGIN ) ) ); m_topBottomScroll->setValue( m_topBottomScroll->value() - 1 ); level = m_topLevel; } m_selectedLevels = level - m_selectStartLevel; if( level <= m_selectStartLevel ) { --m_selectedLevels; } } QApplication::restoreOverrideCursor(); } update(); } inline void AutomationEditor::drawCross( QPainter & _p ) { QPoint mouse_pos = mapFromGlobal( QCursor::pos() ); float level = getLevel( mouse_pos.y() ); int grid_bottom = height() - SCROLLBAR_SIZE - 1; float cross_y = m_y_auto ? grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( level - m_minLevel ) / (float)( m_maxLevel - m_minLevel ) ) : grid_bottom - ( level - m_bottomLevel ) * m_y_delta; _p.setPen( QColor( 0xFF, 0x33, 0x33 ) ); _p.drawLine( VALUES_WIDTH, (int) cross_y, width(), (int) cross_y ); _p.drawLine( mouse_pos.x(), TOP_MARGIN, mouse_pos.x(), height() - SCROLLBAR_SIZE ); QPoint tt_pos = QCursor::pos(); tt_pos.ry() -= 64; tt_pos.rx() += 32; QToolTip::showText( tt_pos,QString::number( level ),this); } inline void AutomationEditor::drawAutomationPoint( QPainter & p, timeMap::iterator it ) { int x = xCoordOfTick( it.key() ); int y = yCoordOfLevel( it.value() ); int outerRadius = qMin( 8, m_ppt/quantization() ); int innerRadius = qMax( 0, outerRadius-2 ); p.setBrush( QBrush( DRAGGABLE_PIN_BORDER_COLOR ) ); p.drawEllipse( x-outerRadius/2, y-outerRadius/2, outerRadius, outerRadius ); p.setBrush( QBrush( DRAGGABLE_PIN_COLOR ) ); p.drawEllipse( x-innerRadius/2, y-innerRadius/2, innerRadius, innerRadius ); p.setBrush( QBrush() ); } void AutomationEditor::paintEvent( QPaintEvent * _pe ) { QMutexLocker m( &m_patternMutex ); QStyleOption opt; opt.initFrom( this ); QPainter p( this ); style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); // set font-size to 8 p.setFont( pointSize<8>( p.font() ) ); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; // start drawing at the bottom int grid_bottom = height() - SCROLLBAR_SIZE - 1; p.fillRect( 0, TOP_MARGIN, VALUES_WIDTH, height() - TOP_MARGIN, QColor( 0x33, 0x33, 0x33 ) ); // print value numbers int font_height = p.fontMetrics().height(); Qt::Alignment text_flags = (Qt::Alignment)( Qt::AlignRight | Qt::AlignVCenter ); if( validPattern() ) { if( m_y_auto ) { int y[] = { grid_bottom, TOP_MARGIN + font_height / 2 }; float level[] = { m_minLevel, m_maxLevel }; for( int i = 0; i < 2; ++i ) { const QString & label = m_pattern->firstObject() ->displayValue( level[i] ); p.setPen( QColor( 240, 240, 240 ) ); p.drawText( 1, y[i] - font_height + 1, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 0, y[i] - font_height, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); } } else { int y; int level = (int) m_bottomLevel; int printable = qMax( 1, 5 * DEFAULT_Y_DELTA / m_y_delta ); int module = level % printable; if( module ) { int inv_module = ( printable - module ) % printable; level += inv_module; } for( ; level <= m_topLevel; level += printable ) { const QString & label = m_pattern->firstObject() ->displayValue( level ); y = yCoordOfLevel( level ); p.setPen( QColor( 240, 240, 240 ) ); p.drawText( 1, y - font_height + 1, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 0, y - font_height, VALUES_WIDTH - 10, 2 * font_height, text_flags, label ); } } } // set clipping area, because we are not allowed to paint over // keyboard... p.setClipRect( VALUES_WIDTH, TOP_MARGIN, width() - VALUES_WIDTH, grid_height ); // draw vertical raster int tact_16th = m_currentPosition / ( DefaultTicksPerTact / 16 ); const int offset = ( m_currentPosition % (DefaultTicksPerTact/16) ) * m_ppt / DEFAULT_STEPS_PER_TACT / 8; if( m_pattern ) { int x_line_end = (int)( m_y_auto || m_topLevel < m_maxLevel ? TOP_MARGIN : grid_bottom - ( m_topLevel - m_bottomLevel ) * m_y_delta ); for( int x = VALUES_WIDTH - offset; x < width(); x += m_ppt / DEFAULT_STEPS_PER_TACT, ++tact_16th ) { if( x >= VALUES_WIDTH ) { // every tact-start needs to be a bright line if( tact_16th % 16 == 0 ) { p.setPen( QColor( 0x7F, 0x7F, 0x7F ) ); } // normal line else if( tact_16th % 4 == 0 ) { p.setPen( QColor( 0x5F, 0x5F, 0x5F ) ); } // weak line else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } p.drawLine( x, grid_bottom, x, x_line_end ); } } // TODO: move this horizontal line drawing code into the same loop as // the value ticks? if( m_y_auto ) { QPen pen( QColor( 0x4F, 0x4F, 0x4F ) ); p.setPen( pen ); p.drawLine( VALUES_WIDTH, grid_bottom, width(), grid_bottom ); pen.setStyle( Qt::DotLine ); p.setPen( pen ); float y_delta = ( grid_bottom - TOP_MARGIN ) / 8.0f; for( int i = 1; i < 8; ++i ) { int y = (int)( grid_bottom - i * y_delta ); p.drawLine( VALUES_WIDTH, y, width(), y ); } } else { float y; for( int level = (int)m_bottomLevel; level <= m_topLevel; level++) { y = yCoordOfLevel( (float)level ); if( level % 5 == 0 ) { p.setPen( QColor( 0x4F, 0x4F, 0x4F ) ); } else { p.setPen( QColor( 0x3F, 0x3F, 0x3F ) ); } // draw level line p.drawLine( VALUES_WIDTH, (int) y, width(), (int) y ); } } } // following code draws all visible values // setup selection-vars int sel_pos_start = m_selectStartTick; int sel_pos_end = m_selectStartTick + m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } float selLevel_start = m_selectStartLevel; float selLevel_end = selLevel_start + m_selectedLevels; if( selLevel_start > selLevel_end ) { qSwap( selLevel_start, selLevel_end ); } if( validPattern() ) { int len_ticks = 4; timeMap & time_map = m_pattern->getTimeMap(); //Don't bother doing/rendering anything if there is no automation points if( time_map.size() > 0 ) { timeMap::iterator it = time_map.begin(); p.setPen( QColor( 0xCF, 0xD9, 0xFF ) ); while( it+1 != time_map.end() ) { // skip this section if it occurs completely before the // visible area int next_x = xCoordOfTick( (it+1).key() ); if( next_x < 0 ) { ++it; continue; } int x = xCoordOfTick( it.key() ); if( x > width() ) { break; } bool is_selected = FALSE; // if we're in move-mode, we may only draw // values in selected area, that have originally // been selected and not values that are now in // selection because the user moved it... if( m_editMode == MOVE ) { if( m_selValuesForMove.contains( it.key() ) ) { is_selected = TRUE; } } else if( it.value() >= selLevel_start && it.value() <= selLevel_end && it.key() >= sel_pos_start && it.key() + len_ticks <= sel_pos_end ) { is_selected = TRUE; } float *values = m_pattern->valuesAfter( it.key() ); for( int i = 0; i < (it+1).key() - it.key(); i++ ) { drawLevelTick( p, it.key() + i, values[i], is_selected ); } delete [] values; // Draw circle drawAutomationPoint(p, it); ++it; } Q_ASSERT( it == time_map.end()-1 ); for( int i = it.key(), x = xCoordOfTick( i ); x <= width(); i++, x = xCoordOfTick( i ) ) { // TODO: Find out if the section after the last control // point is able to be selected and if so set this // boolean correctly drawLevelTick( p, i, it.value(), false ); } // Draw circle(the last one) drawAutomationPoint(p, it); } } else { QFont f = p.font(); f.setBold( TRUE ); p.setFont( pointSize<14>( f ) ); p.setPen( QColor( 74, 253, 133 ) ); p.drawText( VALUES_WIDTH + 20, TOP_MARGIN + 40, width() - VALUES_WIDTH - 20 - SCROLLBAR_SIZE, grid_height - 40, Qt::TextWordWrap, tr( "Please open an automation pattern with " "the context menu of a control!" ) ); } // now draw selection-frame int x = ( sel_pos_start - m_currentPosition ) * m_ppt / DefaultTicksPerTact; int w = ( sel_pos_end - sel_pos_start ) * m_ppt / DefaultTicksPerTact; int y, h; if( m_y_auto ) { y = (int)( grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( selLevel_start - m_minLevel ) / (float)( m_maxLevel - m_minLevel ) ) ); h = (int)( grid_bottom - ( ( grid_bottom - TOP_MARGIN ) * ( selLevel_end - m_minLevel ) / (float)( m_maxLevel - m_minLevel ) ) - y ); } else { y = (int)( grid_bottom - ( selLevel_start - m_bottomLevel ) * m_y_delta ); h = (int)( ( selLevel_start - selLevel_end ) * m_y_delta ); } p.setPen( QColor( 0, 64, 192 ) ); p.drawRect( x + VALUES_WIDTH, y, w, h ); // TODO: Get this out of paint event int l = validPattern() ? (int) m_pattern->length() : 0; // reset scroll-range if( m_leftRightScroll->maximum() != l ) { m_leftRightScroll->setRange( 0, l ); m_leftRightScroll->setPageStep( l ); } if( validPattern() ) { drawCross( p ); } const QPixmap * cursor = NULL; // draw current edit-mode-icon below the cursor switch( m_editMode ) { case DRAW: cursor = s_toolDraw; break; case ERASE: cursor = s_toolErase; break; case SELECT: cursor = s_toolSelect; break; case MOVE: cursor = s_toolMove; break; } p.drawPixmap( mapFromGlobal( QCursor::pos() ) + QPoint( 8, 8 ), *cursor ); } int AutomationEditor::xCoordOfTick( int _tick ) { return VALUES_WIDTH + ( ( _tick - m_currentPosition ) * m_ppt / DefaultTicksPerTact ); } int AutomationEditor::yCoordOfLevel( float _level ) { int grid_bottom = height() - SCROLLBAR_SIZE - 1; if( m_y_auto ) { return (int)( grid_bottom - ( grid_bottom - TOP_MARGIN ) * ( _level - m_minLevel ) / ( m_maxLevel - m_minLevel ) ); } else { return (int)( grid_bottom - ( _level - m_bottomLevel ) * m_y_delta ); } } void AutomationEditor::drawLevelTick( QPainter & _p, int _tick, float _level, bool _is_selected ) { int grid_bottom = height() - SCROLLBAR_SIZE - 1; const int x = xCoordOfTick( _tick ); int rect_width = xCoordOfTick( _tick+1 ) - x; // is the level in visible area? if( ( _level >= m_bottomLevel && _level <= m_topLevel ) || ( _level > m_topLevel && m_topLevel >= 0 ) || ( _level < m_bottomLevel && m_bottomLevel <= 0 ) ) { int y_start = yCoordOfLevel( _level ); int rect_height; if( m_y_auto ) { int y_end = (int)( grid_bottom + ( grid_bottom - TOP_MARGIN ) * m_minLevel / ( m_maxLevel - m_minLevel ) ); rect_height = y_end - y_start; } else { rect_height = (int)( _level * m_y_delta ); } QColor current_color( 0x9F, 0xAF, 0xFF ); if( _is_selected == TRUE ) { current_color.setRgb( 0x00, 0x40, 0xC0 ); } _p.fillRect( x, y_start, rect_width, rect_height, current_color ); } else { printf("not in range\n"); } } // responsible for moving/resizing scrollbars after window-resizing void AutomationEditor::resizeEvent( QResizeEvent * ) { m_leftRightScroll->setGeometry( VALUES_WIDTH, height() - SCROLLBAR_SIZE, width() - VALUES_WIDTH, SCROLLBAR_SIZE ); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; m_topBottomScroll->setGeometry( width() - SCROLLBAR_SIZE, TOP_MARGIN, SCROLLBAR_SIZE, grid_height ); int half_grid = grid_height / 2; int total_pixels = (int)( ( m_maxLevel - m_minLevel ) * m_y_delta + 1 ); if( !m_y_auto && grid_height < total_pixels ) { int min_scroll = (int)( m_minLevel + floorf( half_grid / (float)m_y_delta ) ); int max_scroll = (int)( m_maxLevel - (int)floorf( ( grid_height - half_grid ) / (float)m_y_delta ) ); m_topBottomScroll->setRange( min_scroll, max_scroll ); } else { m_topBottomScroll->setRange( (int) m_scrollLevel, (int) m_scrollLevel ); } m_topBottomScroll->setValue( (int) m_scrollLevel ); if( engine::getSong() ) { engine::getSong()->getPlayPos( song::Mode_PlayAutomationPattern ).m_timeLine->setFixedWidth( width() ); } m_toolBar->setFixedWidth( width() ); updateTopBottomLevels(); update(); } void AutomationEditor::wheelEvent( QWheelEvent * _we ) { _we->accept(); if( _we->modifiers() & Qt::ControlModifier ) { if( _we->delta() > 0 ) { m_ppt = qMin( m_ppt * 2, m_y_delta * DEFAULT_STEPS_PER_TACT * 8 ); } else if( m_ppt >= 72 ) { m_ppt /= 2; } // update combobox with zooming-factor m_zoomingXComboBox->model()->setValue( m_zoomingXComboBox->model()->findText( QString::number( qRound( m_ppt * 100 / DEFAULT_PPT ) ) +"%" ) ); // update timeline m_timeLine->setPixelsPerTact( m_ppt ); update(); } else if( _we->modifiers() & Qt::ShiftModifier || _we->orientation() == Qt::Horizontal ) { m_leftRightScroll->setValue( m_leftRightScroll->value() - _we->delta() * 2 / 15 ); } else { m_topBottomScroll->setValue( m_topBottomScroll->value() - _we->delta() / 30 ); } } float AutomationEditor::getLevel( int _y ) { int level_line_y = height() - SCROLLBAR_SIZE - 1; // pressed level float level = roundf( ( m_bottomLevel + ( m_y_auto ? ( m_maxLevel - m_minLevel ) * ( level_line_y - _y ) / (float)( level_line_y - TOP_MARGIN ) : ( level_line_y - _y ) / (float)m_y_delta ) ) / m_step ) * m_step; // some range-checking-stuff if( level < m_bottomLevel ) { level = m_bottomLevel; } else if( level > m_topLevel ) { level = m_topLevel; } return( level ); } inline bool AutomationEditor::inBBEditor() { QMutexLocker m( &m_patternMutex ); return( validPattern() && m_pattern->getTrack()->trackContainer() == engine::getBBTrackContainer() ); } void AutomationEditor::play() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } if( !m_pattern->getTrack() ) { if( engine::getSong()->playMode() != song::Mode_PlayPattern ) { engine::getSong()->stop(); engine::getSong()->playPattern( (pattern *) engine::pianoRoll()->currentPattern() ); } else if( engine::getSong()->isStopped() == false ) { engine::getSong()->togglePause(); } else { engine::getSong()->playPattern( (pattern *) engine::pianoRoll()->currentPattern() ); } } else if( inBBEditor() ) { engine::getBBTrackContainer()->play(); } else { if( engine::getSong()->isStopped() == true ) { engine::getSong()->playSong(); } else { engine::getSong()->togglePause(); } } setPauseIcon( engine::getSong()->isPlaying() ); } void AutomationEditor::stop() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } if( m_pattern->getTrack() && inBBEditor() ) { engine::getBBTrackContainer()->stop(); } else { engine::getSong()->stop(); } m_scrollBack = TRUE; } void AutomationEditor::horScrolled( int _new_pos ) { m_currentPosition = _new_pos; emit positionChanged( m_currentPosition ); update(); } void AutomationEditor::verScrolled( int _new_pos ) { m_scrollLevel = _new_pos; updateTopBottomLevels(); update(); } void AutomationEditor::drawButtonToggled() { m_editMode = DRAW; removeSelection(); update(); } void AutomationEditor::eraseButtonToggled() { m_editMode = ERASE; removeSelection(); update(); } void AutomationEditor::selectButtonToggled() { m_editMode = SELECT; removeSelection(); update(); } void AutomationEditor::moveButtonToggled() { m_editMode = MOVE; m_selValuesForMove.clear(); getSelectedValues( m_selValuesForMove ); update(); } void AutomationEditor::discreteButtonToggled() { if ( validPattern() ) { QMutexLocker m( &m_patternMutex ); disableTensionKnob(); m_pattern->setProgressionType( AutomationPattern::DiscreteProgression ); engine::getSong()->setModified(); update(); } } void AutomationEditor::linearButtonToggled() { if ( validPattern() ) { QMutexLocker m( &m_patternMutex ); disableTensionKnob(); m_pattern->setProgressionType( AutomationPattern::LinearProgression ); engine::getSong()->setModified(); update(); } } void AutomationEditor::cubicHermiteButtonToggled() { if ( validPattern() ) { m_tensionKnob->setModel( m_tensionModel ); m_tensionKnob->setEnabled( true ); toolTip::add( m_tensionKnob, tr( "Tension value for spline" ) ); m_tensionKnob->setWhatsThis( tr( "A higher tension value may make a smoother curve " "but overshoot some values. A low tension " "value will cause the slope of the curve to " "level off at each control point." ) ); m_pattern->setProgressionType( AutomationPattern::CubicHermiteProgression ); engine::getSong()->setModified(); update(); } } void AutomationEditor::tensionChanged() { m_pattern->setTension( QString::number( m_tensionModel->value() ) ); update(); } void AutomationEditor::selectAll() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } timeMap & time_map = m_pattern->getTimeMap(); timeMap::iterator it = time_map.begin(); m_selectStartTick = 0; m_selectedTick = m_pattern->length(); m_selectStartLevel = it.value(); m_selectedLevels = 1; while( ++it != time_map.end() ) { const float level = it.value(); if( level < m_selectStartLevel ) { // if we move start-level down, we have to add // the difference between old and new start-level // to m_selectedLevels, otherwise the selection // is just moved down... m_selectedLevels += m_selectStartLevel - level; m_selectStartLevel = level; } else if( level >= m_selectStartLevel + m_selectedLevels ) { m_selectedLevels = level - m_selectStartLevel + 1; } } } // returns vector with pointers to all selected values void AutomationEditor::getSelectedValues( timeMap & _selected_values ) { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } int sel_pos_start = m_selectStartTick; int sel_pos_end = sel_pos_start + m_selectedTick; if( sel_pos_start > sel_pos_end ) { qSwap( sel_pos_start, sel_pos_end ); } float selLevel_start = m_selectStartLevel; float selLevel_end = selLevel_start + m_selectedLevels; if( selLevel_start > selLevel_end ) { qSwap( selLevel_start, selLevel_end ); } timeMap & time_map = m_pattern->getTimeMap(); for( timeMap::iterator it = time_map.begin(); it != time_map.end(); ++it ) { //TODO: Add constant tick_t len_ticks = DefaultTicksPerTact / 16; float level = it.value(); tick_t pos_ticks = it.key(); if( level >= selLevel_start && level <= selLevel_end && pos_ticks >= sel_pos_start && pos_ticks + len_ticks <= sel_pos_end ) { _selected_values[it.key()] = level; } } } void AutomationEditor::copySelectedValues() { m_valuesToCopy.clear(); timeMap selected_values; getSelectedValues( selected_values ); if( !selected_values.isEmpty() ) { for( timeMap::iterator it = selected_values.begin(); it != selected_values.end(); ++it ) { m_valuesToCopy[it.key()] = it.value(); } textFloat::displayMessage( tr( "Values copied" ), tr( "All selected values were copied to the " "clipboard." ), embed::getIconPixmap( "edit_copy" ), 2000 ); } } void AutomationEditor::cutSelectedValues() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } m_valuesToCopy.clear(); timeMap selected_values; getSelectedValues( selected_values ); if( !selected_values.isEmpty() ) { engine::getSong()->setModified(); for( timeMap::iterator it = selected_values.begin(); it != selected_values.end(); ++it ) { m_valuesToCopy[it.key()] = it.value(); m_pattern->removeValue( it.key() ); } } update(); engine::songEditor()->update(); } void AutomationEditor::pasteValues() { QMutexLocker m( &m_patternMutex ); if( validPattern() && !m_valuesToCopy.isEmpty() ) { for( timeMap::iterator it = m_valuesToCopy.begin(); it != m_valuesToCopy.end(); ++it ) { m_pattern->putValue( it.key() + m_currentPosition, it.value() ); } // we only have to do the following lines if we pasted at // least one value... engine::getSong()->setModified(); update(); engine::songEditor()->update(); } } void AutomationEditor::deleteSelectedValues() { QMutexLocker m( &m_patternMutex ); if( !validPattern() ) { return; } timeMap selected_values; getSelectedValues( selected_values ); const bool update_after_delete = !selected_values.empty(); for( timeMap::iterator it = selected_values.begin(); it != selected_values.end(); ++it ) { m_pattern->removeValue( it.key() ); } if( update_after_delete == TRUE ) { engine::getSong()->setModified(); update(); engine::songEditor()->update(); } } void AutomationEditor::updatePosition( const MidiTime & _t ) { if( ( engine::getSong()->isPlaying() && engine::getSong()->playMode() == song::Mode_PlayAutomationPattern ) || m_scrollBack == TRUE ) { const int w = width() - VALUES_WIDTH; if( _t > m_currentPosition + w * DefaultTicksPerTact / m_ppt ) { m_leftRightScroll->setValue( _t.getTact() * DefaultTicksPerTact ); } else if( _t < m_currentPosition ) { MidiTime t = qMax( _t - w * DefaultTicksPerTact * DefaultTicksPerTact / m_ppt, 0 ); m_leftRightScroll->setValue( t.getTact() * DefaultTicksPerTact ); } m_scrollBack = FALSE; } } void AutomationEditor::zoomingXChanged() { const QString & zfac = m_zoomingXModel.currentText(); m_ppt = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_PPT / 100; #ifdef LMMS_DEBUG assert( m_ppt > 0 ); #endif m_timeLine->setPixelsPerTact( m_ppt ); update(); } void AutomationEditor::zoomingYChanged() { const QString & zfac = m_zoomingYModel.currentText(); m_y_auto = zfac == "Auto"; if( !m_y_auto ) { m_y_delta = zfac.left( zfac.length() - 1 ).toInt() * DEFAULT_Y_DELTA / 100; } #ifdef LMMS_DEBUG assert( m_y_delta > 0 ); #endif resizeEvent( NULL ); } int AutomationEditor::quantization() const { return( DefaultTicksPerTact / m_quantizeComboBox->model()->currentText().right( m_quantizeComboBox->model()->currentText().length() - 2 ).toInt() ); } void AutomationEditor::updateTopBottomLevels() { if( m_y_auto ) { m_bottomLevel = m_minLevel; m_topLevel = m_maxLevel; return; } int total_pixels = (int)( ( m_maxLevel - m_minLevel ) * m_y_delta + 1 ); int grid_height = height() - TOP_MARGIN - SCROLLBAR_SIZE; int half_grid = grid_height / 2; if( total_pixels > grid_height ) { int centralLevel = (int)( m_minLevel + m_maxLevel - m_scrollLevel ); m_bottomLevel = centralLevel - ( half_grid / (float)m_y_delta ); if( m_bottomLevel < m_minLevel ) { m_bottomLevel = m_minLevel; m_topLevel = m_minLevel + (int)floorf( grid_height / (float)m_y_delta ); } else { m_topLevel = m_bottomLevel + (int)floorf( grid_height / (float)m_y_delta ); if( m_topLevel > m_maxLevel ) { m_topLevel = m_maxLevel; m_bottomLevel = m_maxLevel - (int)floorf( grid_height / (float)m_y_delta ); } } } else { m_bottomLevel = m_minLevel; m_topLevel = m_maxLevel; } } #include "moc_AutomationEditor.cxx" lmms-1.0.0/src/gui/widgets/0000755000175000017500000000000012313663627014226 5ustar tobytobylmms-1.0.0/src/gui/widgets/ControllerView.cpp0000644000175000017500000001073512313663627017716 0ustar tobytoby/* * ControllerView.cpp - view-component for an controller * * Copyright (c) 2008-2009 Paul Giblock * Copyright (c) 2011-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include "ControllerView.h" #include "caption_menu.h" #include "ControllerDialog.h" #include "gui_templates.h" #include "embed.h" #include "engine.h" #include "led_checkbox.h" #include "MainWindow.h" #include "tooltip.h" ControllerView::ControllerView( Controller * _model, QWidget * _parent ) : QWidget( _parent ), ModelView( _model, this ), m_bg( embed::getIconPixmap( "controller_bg" ) ), m_subWindow( NULL ), m_controllerDlg( NULL ), m_show( true ) { setFixedSize( 210, 32 ); QPushButton * ctls_btn = new QPushButton( tr( "Controls" ), this ); QFont f = ctls_btn->font(); ctls_btn->setFont( pointSize<8>( f ) ); ctls_btn->setGeometry( 140, 2, 50, 14 ); connect( ctls_btn, SIGNAL( clicked() ), this, SLOT( editControls() ) ); m_controllerDlg = getController()->createDialog( engine::mainWindow()->workspace() ); m_subWindow = engine::mainWindow()->workspace()->addSubWindow( m_controllerDlg ); Qt::WindowFlags flags = m_subWindow->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; m_subWindow->setWindowFlags( flags ); m_subWindow->setFixedSize( m_subWindow->size() ); m_subWindow->setWindowIcon( m_controllerDlg->windowIcon() ); connect( m_controllerDlg, SIGNAL( closed() ), this, SLOT( closeControls() ) ); m_subWindow->hide(); setWhatsThis( tr( "Controllers are able to automate the value of a knob, " "slider, and other controls." ) ); setModel( _model ); } ControllerView::~ControllerView() { delete m_subWindow; } void ControllerView::editControls() { if( m_show ) { m_subWindow->show(); m_subWindow->raise(); m_show = false; } else { m_subWindow->hide(); m_show = true; } } void ControllerView::closeControls() { m_subWindow->hide(); m_show = true; } void ControllerView::deleteController() { emit( deleteController( this ) ); } void ControllerView::paintEvent( QPaintEvent * ) { QPainter p( this ); p.drawPixmap( 0, 0, m_bg ); QFont f = pointSizeF( font(), 7.5f ); f.setBold( true ); p.setFont( f ); Controller * c = castModel(); p.setPen( QColor( 64, 64, 64 ) ); p.drawText( 7, 13, c->displayName() ); p.setPen( Qt::white ); p.drawText( 6, 12, c->displayName() ); f.setBold( false ); p.setFont( f ); p.drawText( 8, 26, c->name() ); } void ControllerView::mouseDoubleClickEvent( QMouseEvent * event ) { bool ok; Controller * c = castModel(); QString new_name = QInputDialog::getText( this, tr( "Rename controller" ), tr( "Enter the new name for this controller" ), QLineEdit::Normal, c->name() , &ok ); if( ok && !new_name.isEmpty() ) { c->setName( new_name ); update(); } } void ControllerView::modelChanged() { } void ControllerView::contextMenuEvent( QContextMenuEvent * ) { QPointer contextMenu = new captionMenu( model()->displayName() ); contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "&Remove this plugin" ), this, SLOT( deleteController() ) ); contextMenu->addSeparator(); contextMenu->addAction( embed::getIconPixmap( "help" ), tr( "&Help" ), this, SLOT( displayHelp() ) ); contextMenu->exec( QCursor::pos() ); delete contextMenu; } void ControllerView::displayHelp() { QWhatsThis::showText( mapToGlobal( rect().center() ), whatsThis() ); } #include "moc_ControllerView.cxx" lmms-1.0.0/src/gui/widgets/cpuload_widget.cpp0000644000175000017500000000505712313663627017733 0ustar tobytoby/* * cpuload_widget.cpp - widget for displaying CPU-load (partly based on * Hydrogen's CPU-load-widget) * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "cpuload_widget.h" #include "embed.h" #include "engine.h" #include "Mixer.h" cpuloadWidget::cpuloadWidget( QWidget * _parent ) : QWidget( _parent ), m_currentLoad( 0 ), m_temp(), m_background( embed::getIconPixmap( "cpuload_bg" ) ), m_leds( embed::getIconPixmap( "cpuload_leds" ) ), m_changed( true ), m_updateTimer() { setAttribute( Qt::WA_OpaquePaintEvent, true ); setFixedSize( m_background.width(), m_background.height() ); m_temp = QPixmap( width(), height() ); connect( &m_updateTimer, SIGNAL( timeout() ), this, SLOT( updateCpuLoad() ) ); m_updateTimer.start( 100 ); // update cpu-load at 10 fps } cpuloadWidget::~cpuloadWidget() { } void cpuloadWidget::paintEvent( QPaintEvent * ) { if( m_changed == true ) { m_changed = false; m_temp.fill( QColor(0,0,0,0) ); QPainter p( &m_temp ); p.drawPixmap( 0, 0, m_background ); // as load-indicator consists of small 2-pixel wide leds with // 1 pixel spacing, we have to make sure, only whole leds are // shown which we achieve by the following formula int w = ( m_leds.width() * m_currentLoad / 300 ) * 3; if( w > 0 ) { p.drawPixmap( 23, 3, m_leds, 0, 0, w, m_leds.height() ); } } QPainter p( this ); p.drawPixmap( 0, 0, m_temp ); } void cpuloadWidget::updateCpuLoad() { // smooth load-values a bit int new_load = ( m_currentLoad + engine::mixer()->cpuLoad() ) / 2; if( new_load != m_currentLoad ) { m_currentLoad = new_load; m_changed = true; update(); } } #include "moc_cpuload_widget.cxx" lmms-1.0.0/src/gui/widgets/rubberband.cpp0000644000175000017500000000441612313663627017045 0ustar tobytoby/* * rubberband.cpp - rubberband - either own implementation for Qt3 or wrapper * for Qt4 * * Copyright (c) 2006-2011 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "rubberband.h" rubberBand::rubberBand( QWidget * _parent ) : QRubberBand( Rectangle, _parent ) { } rubberBand::~rubberBand() { } QVector rubberBand::selectedObjects() const { QVector so = selectableObjects(); for( QVector::iterator it = so.begin(); it != so.end(); ) { if( ( *it )->isSelected() == false ) { it = so.erase( it ); } else { ++it; } } return( so ); } void rubberBand::resizeEvent( QResizeEvent * _re ) { QRubberBand::resizeEvent( _re ); QVector so = selectableObjects(); for( QVector::iterator it = so.begin(); it != so.end(); ++it ) { ( *it )->setSelected( QRect( pos(), size() ).intersects( QRect( ( *it )->mapTo( parentWidget(), QPoint() ), ( *it )->size() ) ) ); } } QVector rubberBand::selectableObjects() const { QVector so; if( parentWidget() == NULL ) { return( so ); } QList l = parentWidget()->findChildren(); for( QList::iterator it = l.begin(); it != l.end(); ++it ) { so.push_back( *it ); } return( so ); } #include "moc_rubberband.cxx" lmms-1.0.0/src/gui/widgets/caption_menu.cpp0000644000175000017500000000232712313663627017417 0ustar tobytoby/* * caption_menu.cpp - context menu with a caption * * Copyright (c) 2007 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "caption_menu.h" captionMenu::captionMenu( const QString & _title, QWidget * _parent ) : QMenu( _title, _parent ) { QAction * caption = addAction( _title ); caption->setEnabled( false ); } captionMenu::~captionMenu() { } #include "moc_caption_menu.cxx" lmms-1.0.0/src/gui/widgets/graph.cpp0000644000175000017500000003436312313663627016044 0ustar tobytoby/* * graph.cpp - a QT widget for displaying and manipulating waveforms * * Copyright (c) 2006-2007 Andreas Brandmaier * 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "graph.h" #include "string_pair_drag.h" #include "SampleBuffer.h" #include "Oscillator.h" #include "engine.h" graph::graph( QWidget * _parent, graphStyle _style, int _width, int _height ) : QWidget( _parent ), /* TODO: size, background? */ ModelView( new graphModel( -1.0, 1.0, 128, NULL, true ), this ), m_graphStyle( _style ) { m_mouseDown = false; m_graphColor = QColor( 0xFF, 0xAA, 0x00 ); resize( _width, _height ); setAcceptDrops( true ); setCursor( Qt::CrossCursor ); graphModel * gModel = castModel(); QObject::connect( gModel, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateGraph( int, int ) ) ); QObject::connect( gModel, SIGNAL( lengthChanged( ) ), this, SLOT( updateGraph( ) ) ); } graph::~graph() { } void graph::setForeground( const QPixmap &_pixmap ) { m_foreground = _pixmap; } void graph::setGraphColor( QColor _graphcol ) { m_graphColor = _graphcol; } /* void graph::loadSampleFromFile( const QString & _filename ) { int i; // zero sample_shape for( i = 0; i < sampleLength; i++ ) { samplePointer[i] = 0; } // load user shape sampleBuffer buffer( _filename ); // copy buffer data int trimSize = fmin( size(), static_cast(buffer.frames()) ); for( i = 0; i < trimSize; i++ ) { samplePointer[i] = (float)*buffer.data()[i]; } } */ void graph::mouseMoveEvent ( QMouseEvent * _me ) { // get position int x = _me->x(); int y = _me->y(); /* static bool skip = false; if( skip ) { skip = false; return; } */ // avoid mouse leaps int diff = x - m_lastCursorX; /* if( diff >= 1 ) { x = qMin( width() - 3, m_lastCursorX + 1 ); } else if( diff <= -1 ) { x = qMax( 2, m_lastCursorX - 1 ); }*/ x = qMax( 2, qMin( x, width()-3 ) ); y = qMax( 2, qMin( y, height()-3 ) ); if( qAbs( diff ) > 1 ) { drawLineAt( x, y, m_lastCursorX ); } else { changeSampleAt( x, y ); } // update mouse if( diff != 0 ) { m_lastCursorX = x; QPoint pt = mapToGlobal( QPoint( x, y ) ); QCursor::setPos( pt.x(), pt.y() ); } // skip = true; } void graph::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton ) { if ( !( _me->modifiers() & Qt::ShiftModifier ) ) { // get position int x = _me->x(); int y = _me->y(); changeSampleAt( x, y ); // toggle mouse state m_mouseDown = true; setCursor( Qt::BlankCursor ); m_lastCursorX = x; } else { //when shift-clicking, draw a line from last position to current //position int x = _me->x(); int y = _me->y(); drawLineAt( x, y, m_lastCursorX ); m_mouseDown = true; setCursor( Qt::BlankCursor ); m_lastCursorX = x; } } } void graph::drawLineAt( int _x, int _y, int _lastx ) { float minVal = model()->minValue(); float maxVal = model()->maxValue(); if ( width() <= 4 ) { return; } float xscale = static_cast( model()->length() ) / ( width()-4 ); //consider border _x -= 2; _y -= 2; _lastx -= 2; _lastx = qMax( 0, qMin( _lastx, width()-5 ) ); float range = minVal - maxVal; float val = ( _y*range/( height()-5 ) ) + maxVal; float lastval = model() -> m_samples[ (int)( _lastx * xscale ) ]; // calculate line drawing variables int linelen = qAbs( _x - _lastx ) + 1; int xstep = _x > _lastx ? -1 : 1; float ystep = ( lastval - val ) / linelen; // draw a line for ( int i = 0; i < linelen; i++ ) { int x = (_x + (i * xstep)); // get x value model()->setSampleAt( (int)( x * xscale ), val + (i * ystep)); } } void graph::changeSampleAt( int _x, int _y ) { float minVal = model()->minValue(); float maxVal = model()->maxValue(); if ( width() <= 4 ) { return; } float xscale = static_cast( model()->length() ) / ( width()-4 ); // consider border of background image _x -= 2; _y -= 2; // subtract max from min because Qt's Y-axis is backwards float range = minVal - maxVal; float val = ( _y*range/( height()-5 ) ) + maxVal; model()->setSampleAt( (int)( _x*xscale ), val ); } void graph::mouseReleaseEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton ) { // toggle mouse state m_mouseDown = false; setCursor( Qt::CrossCursor ); update(); } } void graph::paintEvent( QPaintEvent * ) { QPainter p( this ); p.setPen( QPen( m_graphColor, 1 ) ); QColor gcol = QColor( m_graphColor.red(), m_graphColor.green(), m_graphColor.blue(), 100 ); QVector * samps = &(model()->m_samples); int length = model()->length(); const float maxVal = model()->maxValue(); const float minVal = model()->minValue(); float xscale = (float)( width()-4 ) / length; float yscale = (float)( height()-4 ) / ( minVal - maxVal ); // Max index, more useful below length--; switch( m_graphStyle ) { case graph::LinearStyle: p.setRenderHints( QPainter::Antialiasing, true ); for( int i=0; i < length; i++ ) { // Needs to be rewritten p.drawLine( 2+static_cast(i*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), 2+static_cast((i+1)*xscale), 2+static_cast( ( (*samps)[i+1] - maxVal ) * yscale ) ); } // Draw last segment wrapped around p.drawLine(2+static_cast(length*xscale), 2+static_cast( ( (*samps)[length] - maxVal ) * yscale ), width()-3, 2+static_cast( ( (*samps)[0] - maxVal ) * yscale ) ); p.setRenderHints( QPainter::Antialiasing, false ); break; case graph::NearestStyle: for( int i=0; i < length; i++ ) { p.drawLine(2+static_cast(i*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), 2+static_cast((i+1)*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ) ); p.drawLine(2+static_cast((i+1)*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), 2+static_cast((i+1)*xscale), 2+static_cast( ( (*samps)[i+1] - maxVal ) * yscale ) ); } p.drawLine(2+static_cast(length*xscale), 2+static_cast( ( (*samps)[length] - maxVal ) * yscale ), width()-3, 2+static_cast( ( (*samps)[length] - maxVal ) * yscale ) ); break; case graph::LinearNonCyclicStyle: p.setRenderHints( QPainter::Antialiasing, true ); for( int i=0; i < length; i++ ) { // Needs to be rewritten p.drawLine( 2+static_cast(i*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), 2+static_cast((i+1)*xscale), 2+static_cast( ( (*samps)[i+1] - maxVal ) * yscale ) ); } // Do not draw last segment wrapped around - hence, "non-cyclic" p.setRenderHints( QPainter::Antialiasing, false ); break; case graph::BarStyle: for( int i=0; i <= length; i++ ) { p.fillRect( 2+static_cast( i*xscale ), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), qMax( static_cast(xscale) - 1, 1 ), qMax( static_cast( ( minVal - maxVal ) * yscale ) - static_cast( ( (*samps)[i] - maxVal ) * yscale ), 1 ), gcol ); p.setPen( QPen( m_graphColor, 1 ) ); p.drawLine( 2+static_cast(i*xscale), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ), qMax( static_cast(i*xscale) + static_cast(xscale), 2+static_cast(i*xscale) ), 2+static_cast( ( (*samps)[i] - maxVal ) * yscale ) ); } break; default: break; } // draw Pointer if( m_mouseDown ) { QPoint cursor = mapFromGlobal( QCursor::pos() ); p.setPen( QColor( 0xAA, 0xFF, 0x00, 0x70 ) ); p.drawLine( 2, cursor.y(), width()-2, cursor.y() ); p.drawLine( cursor.x(), 2, cursor.x(), height()-2 ); } p.drawPixmap( 0, 0, m_foreground ); } void graph::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString value = stringPairDrag::decodeValue( _de ); if( type == "samplefile" ) { // TODO: call setWaveToUser // loadSampleFromFile( value ); _de->accept(); } } void graph::dragEnterEvent( QDragEnterEvent * _dee ) { if( stringPairDrag::processDragEnterEvent( _dee, QString( "samplefile" ) ) == false ) { _dee->ignore(); } } void graph::modelChanged() { graphModel * gModel = castModel(); QObject::connect( gModel, SIGNAL( samplesChanged( int, int ) ), this, SLOT( updateGraph( int, int ) ) ); QObject::connect( gModel, SIGNAL( lengthChanged( ) ), this, SLOT( updateGraph( ) ) ); } void graph::updateGraph( int _startPos, int _endPos ) { // Can optimize by only drawing changed position update(); } void graph::updateGraph() { updateGraph( 0, model()->length() - 1 ); } graphModel::graphModel( float _min, float _max, int _length, ::Model * _parent, bool _default_constructed, float _step ) : Model( _parent, tr( "Graph" ), _default_constructed ), m_samples( _length ), m_minValue( _min ), m_maxValue( _max ), m_step( _step ) { } graphModel::~graphModel() { } void graphModel::setRange( float _min, float _max ) { if( _min != m_minValue || _max != m_maxValue ) { m_minValue = _min; m_maxValue = _max; if( !m_samples.isEmpty() ) { // Trim existing values for( int i=0; i < length(); i++ ) { m_samples[i] = fmaxf( _min, fminf( m_samples[i], _max ) ); } } emit rangeChanged(); } } void graphModel::setLength( int _length ) { if( _length != length() ) { m_samples.resize( _length ); emit lengthChanged(); } } void graphModel::setSampleAt( int _x, float _val ) { //snap to the grid _val -= ( m_step != 0.0 ) ? fmod( _val, m_step ) * m_step : 0; // boundary crop _x = qMax( 0, qMin( length()-1, _x ) ); _val = qMax( minValue(), qMin( maxValue(), _val ) ); // change sample shape m_samples[_x] = _val; emit samplesChanged( _x, _x ); } void graphModel::setSamples( const float * _samples ) { qCopy( _samples, _samples + length(), m_samples.begin()); emit samplesChanged( 0, length()-1 ); } void graphModel::setWaveToSine() { for( int i = 0; i < length(); i++ ) { m_samples[i] = Oscillator::sinSample( i / static_cast( length() ) ); } emit samplesChanged( 0, length() - 1 ); }; void graphModel::setWaveToTriangle() { for( int i = 0; i < length(); i++ ) { m_samples[i] = Oscillator::triangleSample( i / static_cast( length() ) ); } emit samplesChanged( 0, length() - 1 ); }; void graphModel::setWaveToSaw() { for( int i = 0; i < length(); i++ ) { m_samples[i] = Oscillator::sawSample( i / static_cast( length() ) ); } emit samplesChanged( 0, length() - 1 ); }; void graphModel::setWaveToSquare() { for( int i = 0; i < length(); i++ ) { m_samples[i] = Oscillator::squareSample( i / static_cast( length() ) ); } emit samplesChanged( 0, length() - 1 ); }; void graphModel::setWaveToNoise() { for( int i = 0; i < length(); i++ ) { m_samples[i] = Oscillator::noiseSample( i / static_cast( length() ) ); } emit samplesChanged( 0, length() - 1 ); }; QString graphModel::setWaveToUser() { SampleBuffer * sampleBuffer = new SampleBuffer; QString fileName = sampleBuffer->openAndSetWaveformFile(); if( fileName.isEmpty() == false ) { for( int i = 0; i < length(); i++ ) { m_samples[i] = sampleBuffer->userWaveSample( i / static_cast( length() ) ); } } sharedObject::unref( sampleBuffer ); emit samplesChanged( 0, length() - 1 ); return fileName; }; void graphModel::smooth() { // store values in temporary array QVector temp = m_samples; // Smoothing m_samples[0] = ( temp[length()-1] + ( temp[0] * 2 ) + temp[1] ) * 0.25f; for ( int i=1; i < ( length()-1 ); i++ ) { m_samples[i] = ( temp[i-1] + ( temp[i] * 2 ) + temp[i+1] ) * 0.25f; } m_samples[length()-1] = ( temp[length()-2] + ( temp[length()-1] * 2 ) + temp[0] ) * 0.25f; emit samplesChanged(0, length()-1); } void graphModel::smoothNonCyclic() { // store values in temporary array QVector temp = m_samples; // Smoothing m_samples[0] = ( ( temp[0] * 3 ) + temp[1] ) * 0.25f; for ( int i=1; i < ( length()-1 ); i++ ) { m_samples[i] = ( temp[i-1] + ( temp[i] * 2 ) + temp[i+1] ) * 0.25f; } m_samples[length()-1] = ( temp[length()-2] + ( temp[length()-1] * 3 ) ) * 0.25f; emit samplesChanged(0, length()-1); } void graphModel::normalize() { float max = 0.0001f; float avg = 0.0f; // first correct dc offset by normalizing to average for( int i = 0; i < length(); i++ ) avg += m_samples[i]; avg /= length(); for( int i = 0; i < length(); i++ ) m_samples[i] -= avg; // then maximize for( int i = 0; i < length(); i++ ) max = qMax( max, qAbs( m_samples[i] ) ); for( int i = 0; i < length(); i++ ) m_samples[i] = qBound( m_minValue, m_samples[i] / max, m_maxValue ); // signal changes if any if( max != 1.0f || avg != 0.0f ) emit samplesChanged( 0, length()-1 ); } void graphModel::invert() { const float range = m_maxValue - m_minValue; for( int i = 0; i < length(); i++ ) m_samples[i] = m_minValue + ( range - ( m_samples[i] - m_minValue ) ); emit samplesChanged( 0, length()-1 ); } void graphModel::shiftPhase( int _deg ) { // calculate offset in samples int offset = ( _deg * length() ) / 360; //multiply first because integers // store values in temporary array QVector temp = m_samples; // shift phase for( int i = 0; i < length(); i++ ) m_samples[i] = temp[ ( i + offset ) % length() ]; emit samplesChanged( 0, length()-1 ); } #include "moc_graph.cxx" lmms-1.0.0/src/gui/widgets/rename_dialog.cpp0000644000175000017500000000355412313663627017527 0ustar tobytoby/* * rename_dialog.cpp - implementation of dialog for renaming something * * Copyright (c) 2004-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "rename_dialog.h" renameDialog::renameDialog( QString & _string ) : QDialog(), m_stringToEdit( _string ), m_originalString( _string ) { setWindowTitle( tr("Rename...") ); m_stringLE = new QLineEdit( this ); m_stringLE->setText( _string ); m_stringLE->setGeometry ( 10, 5, 220, 20 ); m_stringLE->selectAll(); connect( m_stringLE, SIGNAL( textChanged( const QString & ) ), this, SLOT( textChanged( const QString & ) ) ); connect( m_stringLE, SIGNAL( returnPressed() ), this, SLOT( accept() ) ); } renameDialog::~renameDialog() { } void renameDialog::keyPressEvent( QKeyEvent * _ke ) { if( _ke->key() == Qt::Key_Escape ) { m_stringLE->setText( m_originalString ); accept(); } } void renameDialog::textChanged( const QString & _new_string ) { m_stringToEdit = _new_string; } #include "moc_rename_dialog.cxx" lmms-1.0.0/src/gui/widgets/visualization_widget.cpp0000644000175000017500000001105212313663627021175 0ustar tobytoby/* * visualization_widget.cpp - widget for visualization of sound-data * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "visualization_widget.h" #include "gui_templates.h" #include "MainWindow.h" #include "embed.h" #include "engine.h" #include "tooltip.h" #include "song.h" #include "config_mgr.h" visualizationWidget::visualizationWidget( const QPixmap & _bg, QWidget * _p, visualizationTypes _vtype ) : QWidget( _p ), s_background( _bg ), m_points( new QPointF[engine::mixer()->framesPerPeriod()] ), m_active( false ) { setFixedSize( s_background.width(), s_background.height() ); setAttribute( Qt::WA_OpaquePaintEvent, true ); setActive( configManager::inst()->value( "ui", "displaywaveform").toInt() ); const fpp_t frames = engine::mixer()->framesPerPeriod(); m_buffer = new sampleFrame[frames]; engine::mixer()->clearAudioBuffer( m_buffer, frames ); toolTip::add( this, tr( "click to enable/disable visualization of " "master-output" ) ); } visualizationWidget::~visualizationWidget() { delete[] m_buffer; } void visualizationWidget::updateAudioBuffer() { if( !engine::getSong()->isExporting() ) { engine::mixer()->lock(); const surroundSampleFrame * c = engine::mixer()-> currentReadBuffer(); const fpp_t fpp = engine::mixer()->framesPerPeriod(); memcpy( m_buffer, c, sizeof( surroundSampleFrame ) * fpp ); engine::mixer()->unlock(); } } void visualizationWidget::setActive( bool _active ) { m_active = _active; if( m_active ) { connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) ); connect( engine::mixer(), SIGNAL( nextAudioBuffer() ), this, SLOT( updateAudioBuffer() ) ); } else { disconnect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( update() ) ); disconnect( engine::mixer(), SIGNAL( nextAudioBuffer() ), this, SLOT( updateAudioBuffer() ) ); // we have to update (remove last waves), // because timer doesn't do that anymore update(); } } void visualizationWidget::paintEvent( QPaintEvent * ) { QPainter p( this ); p.drawPixmap( 0, 0, s_background ); if( m_active && !engine::getSong()->isExporting() ) { float master_output = engine::mixer()->masterGain(); int w = width()-4; const float half_h = -( height() - 6 ) / 3.0 * master_output - 1; int x_base = 2; const float y_base = height()/2 - 0.5f; // p.setClipRect( 2, 2, w, height()-4 ); const fpp_t frames = engine::mixer()->framesPerPeriod(); const float max_level = qMax( Mixer::peakValueLeft( m_buffer, frames ), Mixer::peakValueRight( m_buffer, frames ) ); // and set color according to that... if( max_level * master_output < 0.9 ) { p.setPen( QColor( 71, 253, 133 ) ); } else if( max_level * master_output < 1.0 ) { p.setPen( QColor( 255, 192, 64 ) ); } else { p.setPen( QColor( 255, 64, 64 ) ); } p.setPen( QPen( p.pen().color(), 0.7 ) ); const float xd = (float) w / frames; p.setRenderHint( QPainter::Antialiasing ); // now draw all that stuff for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { for( int frame = 0; frame < frames; ++frame ) { m_points[frame] = QPointF( x_base + (float) frame * xd, y_base + ( Mixer::clip( m_buffer[frame][ch] ) * half_h ) ); } p.drawPolyline( m_points, frames ); } } else { p.setPen( QColor( 192, 192, 192 ) ); p.setFont( pointSize<7>( p.font() ) ); p.drawText( 6, height()-5, tr( "Click to enable" ) ); } } void visualizationWidget::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton ) { setActive( !m_active ); } } #include "moc_visualization_widget.cxx" lmms-1.0.0/src/gui/widgets/SideBarWidget.cpp0000644000175000017500000000425712313663627017417 0ustar tobytoby/* * SideBarWidget.cpp - implementation of base-widget for side-bar * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "SideBarWidget.h" #include "gui_templates.h" SideBarWidget::SideBarWidget( const QString & _title, const QPixmap & _icon, QWidget * _parent ) : QWidget( _parent ), m_title( _title ), m_icon( _icon ) { m_contents = new QWidget( this ); m_layout = new QVBoxLayout( m_contents ); m_layout->setSpacing( 5 ); m_layout->setMargin( 0 ); } SideBarWidget::~SideBarWidget() { } void SideBarWidget::paintEvent( QPaintEvent * ) { const int TITLE_FONT_HEIGHT = 13; QPainter p( this ); p.fillRect( 0, 0, width(), 27, palette().highlight().color() ); QFont f = p.font(); f.setBold( true ); p.setFont( pointSize( f ) ); p.setPen( palette().highlightedText().color() ); const int tx = m_icon.width()+4; const int ty = 2+TITLE_FONT_HEIGHT; p.drawText( tx, ty, m_title ); p.drawLine( tx, ty+4, width()-4, ty+4 ); p.drawPixmap( 2, 2, m_icon.transformed( QTransform().rotate( -90 ) ) ); } void SideBarWidget::resizeEvent( QResizeEvent * ) { const int MARGIN = 6; m_contents->setGeometry( MARGIN, 40 + MARGIN, width() - MARGIN * 2, height() - MARGIN * 2 - 40 ); } #include "moc_SideBarWidget.cxx" lmms-1.0.0/src/gui/widgets/group_box.cpp0000644000175000017500000000645212313663627016745 0ustar tobytoby/* * group_box.cpp - groupbox for LMMS * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include "group_box.h" #include "embed.h" #include "gui_templates.h" groupBox::groupBox( const QString & _caption, QWidget * _parent ) : QWidget( _parent ), BoolModelView( NULL, this ), m_caption( _caption ), m_titleBarHeight( 11 ) { updatePixmap(); m_led = new pixmapButton( this, _caption ); m_led->setCheckable( true ); m_led->move( 3, 0 ); m_led->setActiveGraphic( embed::getIconPixmap( "led_green" ) ); m_led->setInactiveGraphic( embed::getIconPixmap( "led_off" ) ); setModel( new BoolModel( false, NULL, _caption, true ) ); setAutoFillBackground( true ); unsetCursor(); } groupBox::~groupBox() { delete m_led; } void groupBox::modelChanged() { m_led->setModel( model() ); } void groupBox::mousePressEvent( QMouseEvent * _me ) { if( _me->y() > 1 && _me->y() < 13 && _me->button() == Qt::LeftButton ) { model()->setValue( !model()->value() ); } } void groupBox::resizeEvent( QResizeEvent * _ev ) { updatePixmap(); QWidget::resizeEvent( _ev ); } void groupBox::updatePixmap() { QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Background ); QPixmap pm( size() ); pm.fill( bg_color/*.dark( 132 )*/ ); QPainter p( &pm ); // outer rect p.setPen( bg_color.dark( 150 ) ); p.drawRect( 0, 0, width() - 1, height() - 1 ); // brighter line at bottom/right p.setPen( bg_color.light( 150 ) ); p.drawLine( width() - 1, 0, width() - 1, height() - 1 ); p.drawLine( 0, height() - 1, width() - 1, height() - 1 ); // draw groupbox-titlebar QLinearGradient g( 0, 0, 0, m_titleBarHeight ); g.setColorAt( 0, bg_color.darker( 250 ) ); g.setColorAt( 0.1, bg_color.lighter( 120 ) ); g.setColorAt( 1, bg_color.darker( 250 ) ); p.fillRect( 2, 2, width() - 4, m_titleBarHeight, g ); // draw line below titlebar p.setPen( bg_color.dark( 400 ) ); p.drawLine( 1, m_titleBarHeight + 1, width() - 3, m_titleBarHeight + 1 ); // black inner rect p.drawRect( 1, 1, width() - 3, height() - 3 ); //p.setPen( QColor( 255, 255, 255 ) ); p.setPen( palette().color( QPalette::Active, QPalette::Text ) ); p.setFont( pointSize<8>( font() ) ); p.drawText( 22, m_titleBarHeight, m_caption ); QPalette pal = palette(); pal.setBrush( backgroundRole(), QBrush( pm ) ); setPalette( pal ); } #include "moc_group_box.cxx" lmms-1.0.0/src/gui/widgets/automatable_slider.cpp0000644000175000017500000000601012313663627020567 0ustar tobytoby/* * automatable_slider.cpp - implementation of class automatableSlider * * Copyright (c) 2006-2007 Javier Serrano Polo * Copyright (c) 2007-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "automatable_slider.h" #include #include #include "caption_menu.h" #include "embed.h" #include "engine.h" #include "MainWindow.h" automatableSlider::automatableSlider( QWidget * _parent, const QString & _name ) : QSlider( _parent ), IntModelView( new IntModel( 0, 0, 0, NULL, _name, true ), this ), m_showStatus( false ) { setWindowTitle( _name ); connect( this, SIGNAL( valueChanged( int ) ), this, SLOT( changeValue( int ) ) ); connect( this, SIGNAL( sliderMoved( int ) ), this, SLOT( moveSlider( int ) ) ); } automatableSlider::~automatableSlider() { } void automatableSlider::contextMenuEvent( QContextMenuEvent * _me ) { captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); } void automatableSlider::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && ! ( _me->modifiers() & Qt::ControlModifier ) ) { m_showStatus = true; QSlider::mousePressEvent( _me ); } else { IntModelView::mousePressEvent( _me ); } } void automatableSlider::mouseReleaseEvent( QMouseEvent * _me ) { m_showStatus = false; QSlider::mouseReleaseEvent( _me ); } void automatableSlider::wheelEvent( QWheelEvent * _me ) { bool old_status = m_showStatus; m_showStatus = true; QSlider::wheelEvent( _me ); m_showStatus = old_status; } void automatableSlider::modelChanged() { QSlider::setRange( model()->minValue(), model()->maxValue() ); updateSlider(); connect( model(), SIGNAL( dataChanged() ), this, SLOT( updateSlider() ) ); } void automatableSlider::changeValue( int _value ) { model()->setValue( _value ); emit logicValueChanged( model()->value() ); } void automatableSlider::moveSlider( int _value ) { model()->setValue( _value ); emit logicSliderMoved( model()->value() ); } void automatableSlider::updateSlider() { QSlider::setValue( model()->value() ); } #include "moc_automatable_slider.cxx" lmms-1.0.0/src/gui/widgets/TimeDisplayWidget.cpp0000644000175000017500000000630512313663627020326 0ustar tobytoby/* * TimeDisplayWidget.cpp - widget for displaying current playback time * * Copyright (c) 2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "TimeDisplayWidget.h" #include "MainWindow.h" #include "engine.h" #include "tooltip.h" #include "song.h" TimeDisplayWidget::TimeDisplayWidget() : QWidget(), m_displayMode( MinutesSeconds ), m_spinBoxesLayout( this ), m_majorLCD( 3, this ), m_minorLCD( 3, this ), m_milliSecondsLCD( 3, this ) { m_spinBoxesLayout.setSpacing( 0 ); m_spinBoxesLayout.setMargin( 0 ); m_spinBoxesLayout.addWidget( &m_majorLCD ); m_spinBoxesLayout.addWidget( &m_minorLCD ); m_spinBoxesLayout.addWidget( &m_milliSecondsLCD ); setMaximumHeight( 32 ); toolTip::add( this, tr( "click to change time units" ) ); // update labels of LCD spinboxes setDisplayMode( m_displayMode ); connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( updateTime() ) ); } TimeDisplayWidget::~TimeDisplayWidget() { } void TimeDisplayWidget::setDisplayMode( DisplayMode displayMode ) { m_displayMode = displayMode; switch( m_displayMode ) { case MinutesSeconds: m_majorLCD.setLabel( "MIN" ); m_minorLCD.setLabel( "SEC" ); m_milliSecondsLCD.setLabel( "MSEC" ); break; case BarsTicks: m_majorLCD.setLabel( "BAR" ); m_minorLCD.setLabel( "BEAT" ); m_milliSecondsLCD.setLabel( "TICK" ); break; default: break; } } void TimeDisplayWidget::updateTime() { song* s = engine::getSong(); switch( m_displayMode ) { case MinutesSeconds: m_majorLCD.setValue( s->getMilliseconds() / 60000 ); m_minorLCD.setValue( ( s->getMilliseconds() / 1000 ) % 60 ); m_milliSecondsLCD.setValue( s->getMilliseconds() % 1000 ); break; case BarsTicks: m_majorLCD.setValue( s->getTacts() + 1 ); m_minorLCD.setValue( ( s->getTicks() % s->ticksPerTact() ) / ( s->ticksPerTact() / s->getTimeSigModel().getNumerator() ) +1 ); ; m_milliSecondsLCD.setValue( ( s->getTicks() % s->ticksPerTact() ) % ( s->ticksPerTact() / s->getTimeSigModel().getNumerator() ) ); break; default: break; } } void TimeDisplayWidget::mousePressEvent( QMouseEvent* mouseEvent ) { if( mouseEvent->button() == Qt::LeftButton ) { if( m_displayMode == MinutesSeconds ) { setDisplayMode( BarsTicks ); } else { setDisplayMode( MinutesSeconds ); } } } #include "moc_TimeDisplayWidget.cxx" lmms-1.0.0/src/gui/widgets/SideBar.cpp0000644000175000017500000000703112313663627016244 0ustar tobytoby/* * SideBar.cpp - side-bar in LMMS' MainWindow * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "SideBar.h" #include "SideBarWidget.h" #include "tooltip.h" // internal helper class allowing to create QToolButtons with // vertical orientation class SideBarButton : public QToolButton { public: SideBarButton( Qt::Orientation _orientation, QWidget * _parent ) : QToolButton( _parent ), m_orientation( _orientation ) { } virtual ~SideBarButton() { } Qt::Orientation orientation() const { return m_orientation; } virtual QSize sizeHint() const { QSize s = QToolButton::sizeHint(); s.setWidth( s.width() + 8 ); if( orientation() == Qt::Horizontal ) { return s; } return QSize( s.height(), s.width() ); } protected: virtual void paintEvent( QPaintEvent * ) { QStylePainter p( this ); QStyleOptionToolButton opt; initStyleOption( &opt ); if( orientation() == Qt::Vertical ) { const QSize s = sizeHint(); p.rotate( 270 ); p.translate( -s.height(), 0 ); opt.rect = QRect( 0, 0, s.height(), s.width() ); } p.drawComplexControl( QStyle::CC_ToolButton, opt ); } private: Qt::Orientation m_orientation; } ; SideBar::SideBar( Qt::Orientation _orientation, QWidget * _parent ) : QToolBar( _parent ), m_btnGroup( this ) { setOrientation( _orientation ); setIconSize( QSize( 16, 16 ) ); m_btnGroup.setExclusive( false ); connect( &m_btnGroup, SIGNAL( buttonClicked( QAbstractButton * ) ), this, SLOT( toggleButton( QAbstractButton * ) ) ); } SideBar::~SideBar() { } void SideBar::appendTab( SideBarWidget * _sbw ) { SideBarButton * btn = new SideBarButton( orientation(), this ); btn->setText( _sbw->title() ); btn->setIcon( _sbw->icon() ); btn->setCheckable( true ); m_widgets[btn] = _sbw; m_btnGroup.addButton( btn ); addWidget( btn ); _sbw->hide(); _sbw->setMinimumWidth( 200 ); toolTip::add( btn, _sbw->title() ); } void SideBar::toggleButton( QAbstractButton * _btn ) { QToolButton * toolButton = NULL; QWidget * activeWidget = NULL; for( ButtonMap::Iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) { QToolButton * curBtn = it.key(); if( curBtn != _btn ) { curBtn->setChecked( false ); curBtn->setToolButtonStyle( Qt::ToolButtonIconOnly ); } else { toolButton = it.key(); activeWidget = it.value(); } if( it.value() ) { it.value()->hide(); } } if( toolButton && activeWidget ) { activeWidget->setVisible( _btn->isChecked() ); toolButton->setToolButtonStyle( _btn->isChecked() ? Qt::ToolButtonTextBesideIcon : Qt::ToolButtonIconOnly ); } } #include "moc_SideBar.cxx" lmms-1.0.0/src/gui/widgets/text_float.cpp0000644000175000017500000000754712313663627017120 0ustar tobytoby/* * text_float.cpp - class textFloat, a floating text-label * * Copyright (c) 2005-2010 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "text_float.h" #include "gui_templates.h" #include "MainWindow.h" #include "engine.h" textFloat::textFloat() : QWidget( engine::mainWindow(), Qt::ToolTip ), m_title(), m_text(), m_pixmap() { resize( 20, 20 ); hide(); setFont( pointSize<8>( font() ) ); } void textFloat::setTitle( const QString & _title ) { m_title = _title; updateSize(); } void textFloat::setText( const QString & _text ) { m_text = _text; updateSize(); } void textFloat::setPixmap( const QPixmap & _pixmap ) { m_pixmap = _pixmap; updateSize(); } void textFloat::setVisibilityTimeOut( int _msecs ) { QTimer::singleShot( _msecs, this, SLOT( hide() ) ); show(); } textFloat * textFloat::displayMessage( const QString & _msg, int _timeout, QWidget * _parent, int _add_y_margin ) { QWidget * mw = engine::mainWindow(); textFloat * tf = new textFloat; if( _parent != NULL ) { tf->moveGlobal( _parent, QPoint( _parent->width() + 2, 0 ) ); } else { tf->moveGlobal( mw, QPoint( 32, mw->height() - tf->height() - 8 - _add_y_margin ) ); } tf->setText( _msg ); tf->show(); if( _timeout > 0 ) { tf->setAttribute( Qt::WA_DeleteOnClose, true ); QTimer::singleShot( _timeout, tf, SLOT( close() ) ); } return( tf ); } textFloat * textFloat::displayMessage( const QString & _title, const QString & _msg, const QPixmap & _pixmap, int _timeout, QWidget * _parent ) { textFloat * tf = displayMessage( _msg, _timeout, _parent, 16 ); tf->setTitle( _title ); tf->setPixmap( _pixmap ); return( tf ); } void textFloat::paintEvent( QPaintEvent * _pe ) { QPainter p( this ); p.setPen( QColor( 0, 0, 0 ) ); p.setBrush( QColor( 224, 224, 224 ) ); p.setFont( pointSize<8>( p.font() ) ); p.drawRect( 0, 0, rect().right(), rect().bottom() ); // p.setPen( Qt::black ); // small message? if( m_title.isEmpty() ) { p.drawText( 2, p.fontMetrics().height()-2, m_text ); } else { int text_x = 2; if( m_pixmap.isNull() == false ) { p.drawPixmap( 5, 5, m_pixmap ); text_x += m_pixmap.width() + 8; } p.drawText( text_x, 28, m_text ); QFont f = p.font(); f.setBold( true ); p.setFont( f ); p.drawText( text_x, 12, m_title ); } } void textFloat::mousePressEvent( QMouseEvent * ) { close(); } void textFloat::updateSize() { QFontMetrics metrics( pointSize<8>( font() ) ); QRect textBound = metrics.boundingRect( m_text ); if( !m_title.isEmpty() ) { QFont f = pointSize<8>( font() ); f.setBold( true ); int title_w = QFontMetrics( f ).boundingRect( m_title ).width(); if( title_w > textBound.width() ) { textBound.setWidth( title_w ); } textBound.setHeight( textBound.height() * 2 + 8 ); } if( m_pixmap.isNull() == false ) { textBound.setWidth( textBound.width() + m_pixmap.width() + 10 ); } resize( textBound.width() + 5, textBound.height()+2 ); //move( QPoint( parentWidget()->width() + 5, 5 ) ); update(); } lmms-1.0.0/src/gui/widgets/project_notes.cpp0000644000175000017500000002400112313663627017605 0ustar tobytoby/* * project_notes.cpp - implementation of project-notes-editor * * Copyright (c) 2005-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "project_notes.h" #include #include #include #include #include #include #include #include #include #include #include #include "embed.h" #include "engine.h" #include "MainWindow.h" #include "song.h" projectNotes::projectNotes() : QMainWindow( engine::mainWindow()->workspace() ) { m_edit = new QTextEdit( this ); m_edit->setAutoFillBackground( TRUE ); QPalette pal; pal.setColor( m_edit->backgroundRole(), QColor( 64, 64, 64 ) ); m_edit->setPalette( pal ); m_edit->show(); clear(); connect( m_edit, SIGNAL( currentCharFormatChanged( const QTextCharFormat & ) ), this, SLOT( formatChanged( const QTextCharFormat & ) ) ); // connect( m_edit, SIGNAL( currentAlignmentChanged( int ) ), // this, SLOT( alignmentChanged( int ) ) ); connect( m_edit, SIGNAL( textChanged() ), engine::getSong(), SLOT( setModified() ) ); setupActions(); setCentralWidget( m_edit ); setWindowTitle( tr( "Project notes" ) ); setWindowIcon( embed::getIconPixmap( "project_notes" ) ); engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 700, 10 ); parentWidget()->resize( 400, 300 ); parentWidget()->hide(); } projectNotes::~projectNotes() { } void projectNotes::clear() { m_edit->setHtml( tr( "Put down your project notes here." ) ); m_edit->selectAll(); m_edit->setTextColor( QColor( 224, 224, 224 ) ); QTextCursor cursor = m_edit->textCursor(); cursor.clearSelection(); m_edit->setTextCursor( cursor ); } void projectNotes::setText( const QString & _text ) { m_edit->setHtml( _text ); } void projectNotes::setupActions() { QToolBar * tb = addToolBar( tr( "Edit Actions" ) ); QAction * a; a = new QAction( embed::getIconPixmap( "edit_undo" ), tr( "&Undo" ), this ); a->setShortcut( tr( "Ctrl+Z" ) ); connect( a, SIGNAL( triggered() ), m_edit, SLOT( undo() ) ); tb->addAction( a ); a = new QAction( embed::getIconPixmap( "edit_redo" ), tr( "&Redo" ), this ); a->setShortcut( tr( "Ctrl+Y" ) ); connect( a, SIGNAL( triggered() ), m_edit, SLOT( redo() ) ); tb->addAction( a ); a = new QAction( embed::getIconPixmap( "edit_copy" ), tr( "&Copy" ), this ); a->setShortcut( tr( "Ctrl+C" ) ); connect( a, SIGNAL( triggered() ), m_edit, SLOT( copy() ) ); tb->addAction( a ); a = new QAction( embed::getIconPixmap( "edit_cut" ), tr( "Cu&t" ), this ); a->setShortcut( tr( "Ctrl+X" ) ); connect( a, SIGNAL( triggered() ), m_edit, SLOT( cut() ) ); tb->addAction( a ); a = new QAction( embed::getIconPixmap( "edit_paste" ), tr( "&Paste" ), this ); a->setShortcut( tr( "Ctrl+V" ) ); connect( a, SIGNAL( triggered() ), m_edit, SLOT( paste() ) ); tb->addAction( a ); tb = addToolBar( tr( "Format Actions" ) ); m_comboFont = new QComboBox( tb ); m_comboFont->setEditable( true ); QFontDatabase db; m_comboFont->addItems( db.families() ); connect( m_comboFont, SIGNAL( activated( const QString & ) ), m_edit, SLOT( setFontFamily( const QString & ) ) ); m_comboFont->lineEdit()->setText( QApplication::font().family() ); m_comboSize = new QComboBox( tb ); m_comboSize->setEditable( true ); QList sizes = db.standardSizes(); QList::Iterator it = sizes.begin(); for ( ; it != sizes.end(); ++it ) { m_comboSize->addItem( QString::number( *it ) ); } connect( m_comboSize, SIGNAL( activated( const QString & ) ), this, SLOT( textSize( const QString & ) ) ); m_comboSize->lineEdit()->setText( QString::number( QApplication::font().pointSize() ) ); m_actionTextBold = new QAction( embed::getIconPixmap( "text_bold" ), tr( "&Bold" ), this ); m_actionTextBold->setShortcut( tr( "Ctrl+B" ) ); m_actionTextBold->setCheckable( true ); connect( m_actionTextBold, SIGNAL( triggered() ), this, SLOT( textBold() ) ); m_actionTextItalic = new QAction( embed::getIconPixmap( "text_italic" ), tr( "&Italic" ), this ); m_actionTextItalic->setShortcut( tr( "Ctrl+I" ) ); m_actionTextItalic->setCheckable( true ); connect( m_actionTextItalic, SIGNAL( triggered() ), this, SLOT( textItalic() ) ); m_actionTextUnderline = new QAction( embed::getIconPixmap( "text_under" ), tr( "&Underline" ), this ); m_actionTextUnderline->setShortcut( tr( "Ctrl+U" ) ); m_actionTextUnderline->setCheckable( true ); connect( m_actionTextUnderline, SIGNAL( triggered() ), this, SLOT( textUnderline() ) ); QActionGroup * grp = new QActionGroup( tb ); connect( grp, SIGNAL( triggered( QAction * ) ), this, SLOT( textAlign( QAction * ) ) ); m_actionAlignLeft = new QAction( embed::getIconPixmap( "text_left" ), tr( "&Left" ), m_edit ); m_actionAlignLeft->setShortcut( tr( "Ctrl+L" ) ); m_actionAlignLeft->setCheckable( true ); grp->addAction( m_actionAlignLeft ); m_actionAlignCenter = new QAction( embed::getIconPixmap( "text_center" ), tr( "C&enter" ), m_edit ); m_actionAlignCenter->setShortcutContext( Qt::WidgetShortcut ); m_actionAlignCenter->setShortcut( tr( "Ctrl+E" ) ); m_actionAlignCenter->setCheckable( true ); grp->addAction( m_actionAlignCenter ); m_actionAlignRight = new QAction( embed::getIconPixmap( "text_right" ), tr( "&Right" ), m_edit ); m_actionAlignRight->setShortcutContext( Qt::WidgetShortcut ); m_actionAlignRight->setShortcut( tr( "Ctrl+R" ) ); m_actionAlignRight->setCheckable( true ); grp->addAction( m_actionAlignRight ); m_actionAlignJustify = new QAction( embed::getIconPixmap( "text_block" ), tr( "&Justify" ), m_edit ); m_actionAlignJustify->setShortcut( tr( "Ctrl+J" ) ); m_actionAlignJustify->setCheckable( true ); grp->addAction( m_actionAlignJustify ); QPixmap pix( 16, 16 ); pix.fill( Qt::black ); m_actionTextColor = new QAction( pix, tr( "&Color..." ), this ); connect( m_actionTextColor, SIGNAL( triggered() ), this, SLOT( textColor() ) ); tb->addWidget( m_comboFont ); tb->addWidget( m_comboSize ); tb->addAction( m_actionTextBold ); tb->addAction( m_actionTextItalic ); tb->addAction( m_actionTextUnderline ); tb->addAction( m_actionAlignLeft ); tb->addAction( m_actionAlignCenter ); tb->addAction( m_actionAlignRight ); tb->addAction( m_actionAlignJustify ); tb->addAction( m_actionTextColor ); } void projectNotes::textBold() { m_edit->setFontWeight( m_actionTextBold->isChecked() ? QFont::Bold : QFont::Normal ); engine::getSong()->setModified(); } void projectNotes::textUnderline() { m_edit->setFontUnderline( m_actionTextUnderline->isChecked() ); engine::getSong()->setModified(); } void projectNotes::textItalic() { m_edit->setFontItalic( m_actionTextItalic->isChecked() ); engine::getSong()->setModified(); } void projectNotes::textFamily( const QString & _f ) { m_edit->setFontFamily( _f ); m_edit->viewport()->setFocus(); engine::getSong()->setModified(); } void projectNotes::textSize( const QString & _p ) { m_edit->setFontPointSize( _p.toInt() ); m_edit->viewport()->setFocus(); engine::getSong()->setModified(); } void projectNotes::textColor() { QColor col = QColorDialog::getColor( m_edit->textColor(), this ); if ( !col.isValid() ) { return; } m_edit->setTextColor( col ); QPixmap pix( 16, 16 ); pix.fill( Qt::black ); m_actionTextColor->setIcon( pix ); } void projectNotes::textAlign( QAction * _a ) { if( _a == m_actionAlignLeft ) { m_edit->setAlignment( Qt::AlignLeft ); } else if( _a == m_actionAlignCenter ) { m_edit->setAlignment( Qt::AlignHCenter ); } else if( _a == m_actionAlignRight ) { m_edit->setAlignment( Qt::AlignRight ); } else if( _a == m_actionAlignJustify ) { m_edit->setAlignment( Qt::AlignJustify ); } } void projectNotes::formatChanged( const QTextCharFormat & _f ) { QFont font = _f.font(); m_comboFont->lineEdit()->setText( font.family() ); m_comboSize->lineEdit()->setText( QString::number( font.pointSize() ) ); m_actionTextBold->setChecked( font.bold() ); m_actionTextItalic->setChecked( font.italic() ); m_actionTextUnderline->setChecked( font.underline() ); QPixmap pix( 16, 16 ); pix.fill( _f.foreground().color() ); m_actionTextColor->setIcon( pix ); engine::getSong()->setModified(); } void projectNotes::alignmentChanged( int _a ) { if ( _a & Qt::AlignLeft ) { m_actionAlignLeft->setChecked( true ); } else if ( ( _a & Qt::AlignHCenter ) ) { m_actionAlignCenter->setChecked( true ); } else if ( ( _a & Qt::AlignRight ) ) { m_actionAlignRight->setChecked( true ); } else if ( ( _a & Qt::AlignJustify ) ) { m_actionAlignJustify->setChecked( true ); } engine::getSong()->setModified(); } void projectNotes::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); QDomCDATASection ds = _doc.createCDATASection( m_edit->toHtml() ); _this.appendChild( ds ); } void projectNotes::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); m_edit->setHtml( _this.text() ); } #include "moc_project_notes.cxx" lmms-1.0.0/src/gui/widgets/EffectView.cpp0000644000175000017500000001674512313663627016776 0ustar tobytoby/* * EffectView.cpp - view-component for an effect * * Copyright (c) 2006-2007 Danny McRae * Copyright (c) 2007-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "EffectView.h" #include "caption_menu.h" #include "EffectControls.h" #include "EffectControlDialog.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "knob.h" #include "led_checkbox.h" #include "MainWindow.h" #include "TempoSyncKnob.h" #include "tooltip.h" EffectView::EffectView( Effect * _model, QWidget * _parent ) : PluginView( _model, _parent ), m_bg( embed::getIconPixmap( "effect_plugin" ) ), m_subWindow( NULL ), m_controlView( NULL ) { setFixedSize( 210, 60 ); m_bypass = new ledCheckBox( "", this ); m_bypass->move( 3, 3 ); m_bypass->setWhatsThis( tr( "Toggles the effect on or off." ) ); toolTip::add( m_bypass, tr( "On/Off" ) ); m_wetDry = new knob( knobBright_26, this ); m_wetDry->setLabel( tr( "W/D" ) ); m_wetDry->move( 27, 5 ); m_wetDry->setHintText( tr( "Wet Level:" ) + " ", "" ); m_wetDry->setWhatsThis( tr( "The Wet/Dry knob sets the ratio between " "the input signal and the effect signal that " "forms the output." ) ); m_autoQuit = new TempoSyncKnob( knobBright_26, this ); m_autoQuit->setLabel( tr( "DECAY" ) ); m_autoQuit->move( 60, 5 ); m_autoQuit->setHintText( tr( "Time:" ) + " ", "ms" ); m_autoQuit->setWhatsThis( tr( "The Decay knob controls how many buffers of silence must pass before the " "plugin stops processing. Smaller values will reduce the CPU overhead but " "run the risk of clipping the tail on delay and reverb effects." ) ); m_gate = new knob( knobBright_26, this ); m_gate->setLabel( tr( "GATE" ) ); m_gate->move( 93, 5 ); m_gate->setHintText( tr( "Gate:" ) + " ", "" ); m_gate->setWhatsThis( tr( "The Gate knob controls the signal level that is considered to be 'silence' " "while deciding when to stop processing signals." ) ); setModel( _model ); if( effect()->controls()->controlCount() > 0 ) { QPushButton * ctls_btn = new QPushButton( tr( "Controls" ), this ); QFont f = ctls_btn->font(); ctls_btn->setFont( pointSize<8>( f ) ); ctls_btn->setGeometry( 140, 14, 50, 20 ); connect( ctls_btn, SIGNAL( clicked() ), this, SLOT( editControls() ) ); m_controlView = effect()->controls()->createView(); if( m_controlView ) { m_subWindow = engine::mainWindow()->workspace()->addSubWindow( m_controlView, Qt::SubWindow | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint ); m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); m_subWindow->setFixedSize( m_subWindow->size() ); connect( m_controlView, SIGNAL( closed() ), this, SLOT( closeEffects() ) ); m_subWindow->hide(); } } setWhatsThis( tr( "Effect plugins function as a chained series of effects where the signal will " "be processed from top to bottom.\n\n" "The On/Off switch allows you to bypass a given plugin at any point in " "time.\n\n" "The Wet/Dry knob controls the balance between the input signal and the " "effected signal that is the resulting output from the effect. The input " "for the stage is the output from the previous stage. So, the 'dry' signal " "for effects lower in the chain contains all of the previous effects.\n\n" "The Decay knob controls how long the signal will continue to be processed " "after the notes have been released. The effect will stop processing signals " "when the volume has dropped below a given threshold for a given length of " "time. This knob sets the 'given length of time'. Longer times will require " "more CPU, so this number should be set low for most effects. It needs to be " "bumped up for effects that produce lengthy periods of silence, e.g. " "delays.\n\n" "The Gate knob controls the 'given threshold' for the effect's auto shutdown. " "The clock for the 'given length of time' will begin as soon as the processed " "signal level drops below the level specified with this knob.\n\n" "The Controls button opens a dialog for editing the effect's parameters.\n\n" "Right clicking will bring up a context menu where you can change the order " "in which the effects are processed or delete an effect altogether." ) ); //move above vst effect view creation //setModel( _model ); } EffectView::~EffectView() { #ifdef LMMS_BUILD_LINUX delete m_subWindow; #else if( m_subWindow ) { // otherwise on win32 build VST GUI can get lost m_subWindow->hide(); } #endif } void EffectView::editControls() { if( m_subWindow ) { if( !m_subWindow->isVisible() ) { m_subWindow->show(); m_subWindow->raise(); effect()->controls()->setViewVisible( true ); } else { m_subWindow->hide(); effect()->controls()->setViewVisible( false ); } } } void EffectView::moveUp() { emit moveUp( this ); } void EffectView::moveDown() { emit moveDown( this ); } void EffectView::deletePlugin() { emit deletePlugin( this ); } void EffectView::displayHelp() { QWhatsThis::showText( mapToGlobal( rect().bottomRight() ), whatsThis() ); } void EffectView::closeEffects() { if( m_subWindow ) { m_subWindow->hide(); } effect()->controls()->setViewVisible( false ); } void EffectView::contextMenuEvent( QContextMenuEvent * ) { QPointer contextMenu = new captionMenu( model()->displayName() ); contextMenu->addAction( embed::getIconPixmap( "arp_up" ), tr( "Move &up" ), this, SLOT( moveUp() ) ); contextMenu->addAction( embed::getIconPixmap( "arp_down" ), tr( "Move &down" ), this, SLOT( moveDown() ) ); contextMenu->addSeparator(); contextMenu->addAction( embed::getIconPixmap( "cancel" ), tr( "&Remove this plugin" ), this, SLOT( deletePlugin() ) ); contextMenu->addSeparator(); contextMenu->addAction( embed::getIconPixmap( "help" ), tr( "&Help" ), this, SLOT( displayHelp() ) ); contextMenu->exec( QCursor::pos() ); delete contextMenu; } void EffectView::paintEvent( QPaintEvent * ) { QPainter p( this ); p.drawPixmap( 0, 0, m_bg ); QFont f = pointSizeF( font(), 7.5f ); f.setBold( true ); p.setFont( f ); p.setPen( palette().shadow().color() ); p.drawText( 6, 55, model()->displayName() ); p.setPen( palette().text().color() ); p.drawText( 5, 54, model()->displayName() ); } void EffectView::modelChanged() { m_bypass->setModel( &effect()->m_enabledModel ); m_wetDry->setModel( &effect()->m_wetDryModel ); m_autoQuit->setModel( &effect()->m_autoQuitModel ); m_gate->setModel( &effect()->m_gateModel ); } #include "moc_EffectView.cxx" lmms-1.0.0/src/gui/widgets/tab_widget.cpp0000644000175000017500000001203112313663627017040 0ustar tobytoby/* * tab_widget.cpp - tabwidget for LMMS * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "tab_widget.h" #include #include #include #include #include "gui_templates.h" tabWidget::tabWidget( const QString & _caption, QWidget * _parent ) : QWidget( _parent ), m_activeTab( 0 ), m_caption( _caption ) { setFont( pointSize<8>( font() ) ); setAutoFillBackground( true ); QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Background ). darker( 132 ); QPalette pal = palette(); pal.setColor( QPalette::Background, bg_color ); setPalette( pal ); } tabWidget::~tabWidget() { } void tabWidget::addTab( QWidget * _w, const QString & _name, int _idx ) { setFont( pointSize<8>( font() ) ); widgetDesc d = { _w, _name, fontMetrics().width( _name ) + 10 } ; if( _idx < 0/* || m_widgets.contains( _idx ) == true*/ ) { while( m_widgets.contains( ++_idx ) == true ) { } } m_widgets[_idx] = d; _w->setFixedSize( width() - 4, height() - 14 ); _w->move( 2, 13 ); _w->hide(); if( m_widgets.contains( m_activeTab ) ) { // make sure new tab doesn't overlap current widget m_widgets[m_activeTab].w->show(); m_widgets[m_activeTab].w->raise(); } } void tabWidget::setActiveTab( int _idx ) { if( m_widgets.contains( _idx ) ) { int old_active = m_activeTab; m_activeTab = _idx; m_widgets[m_activeTab].w->raise(); m_widgets[m_activeTab].w->show(); if( old_active != _idx && m_widgets.contains( old_active ) ) { m_widgets[old_active].w->hide(); } update(); } } void tabWidget::mousePressEvent( QMouseEvent * _me ) { if( _me->y() > 1 && _me->y() < 13 ) { int cx = ( ( m_caption == "" ) ? 4 : 14 ) + fontMetrics().width( m_caption ); for( widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) { if( _me->x() >= cx && _me->x() <= cx + ( *it ).nwidth ) { setActiveTab( it.key() ); update(); return; } cx += ( *it ).nwidth; } } } void tabWidget::resizeEvent( QResizeEvent * ) { for( widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) { ( *it ).w->setFixedSize( width() - 4, height() - 14 ); } } void tabWidget::paintEvent( QPaintEvent * _pe ) { setFont( pointSize<8>( font() ) ); QPainter p( this ); QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Background ); QLinearGradient g( 0, 0, 0, 10 ); g.setColorAt( 0, bg_color.darker( 250 ) ); g.setColorAt( 0.1, bg_color.lighter( 120 ) ); g.setColorAt( 1, bg_color.darker( 250 ) ); p.fillRect( 0, 0, width() - 1, height() - 1, bg_color ); bool big_tab_captions = ( m_caption == "" ); int add = big_tab_captions ? 1 : 0; p.setPen( bg_color.darker( 150 ) ); p.drawRect( 0, 0, width() - 1, height() - 1 ); p.setPen( bg_color.light( 150 ) ); p.drawLine( width() - 1, 0, width() - 1, height() - 1 ); p.drawLine( 0, height() - 1, width() - 1, height() - 1 ); p.setPen( QColor( 0, 0, 0 ) ); p.drawRect( 1, 1, width() - 3, height() - 3 ); p.fillRect( 2, 2, width() - 4, 10 + add, g ); p.drawLine( 2, 12 + add, width() - 3, 12 + add ); if( !big_tab_captions ) { p.setPen( QColor( 255, 255, 255 ) ); p.drawText( 5, 11, m_caption ); } int cx = ( big_tab_captions ? 4 : 14 ) + fontMetrics().width( m_caption ); QColor cap_col( 160, 160, 160 ); if( big_tab_captions ) { p.setFont( pointSize<8>( p.font() ) ); cap_col = QColor( 224, 224, 224 ); } else { p.setFont( pointSize<7>( p.font() ) ); } p.setPen( cap_col ); for( widgetStack::iterator it = m_widgets.begin(); it != m_widgets.end(); ++it ) { if( it.key() == m_activeTab ) { p.setPen( QColor( 32, 48, 64 ) ); p.fillRect( cx, 2, ( *it ).nwidth - 6, 10, cap_col ); } p.drawText( cx + 3, 10 + add, ( *it ).name ); p.setPen( cap_col ); cx += ( *it ).nwidth; } } void tabWidget::wheelEvent( QWheelEvent * _we ) { _we->accept(); int dir = ( _we->delta() < 0 ) ? 1 : -1; int tab = m_activeTab; while( tab > -1 && static_cast( tab ) < m_widgets.count() ) { tab += dir; if( m_widgets.contains( tab ) ) { break; } } setActiveTab( tab ); } #include "moc_tab_widget.cxx" lmms-1.0.0/src/gui/widgets/automatable_button.cpp0000644000175000017500000001272412313663627020631 0ustar tobytoby/* * automatable_button.cpp - implementation of class automatableButton and * automatableButtonGroup * * Copyright (c) 2006-2011 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "automatable_button.h" #include #include #include "caption_menu.h" #include "engine.h" #include "embed.h" #include "MainWindow.h" #include "string_pair_drag.h" automatableButton::automatableButton( QWidget * _parent, const QString & _name ) : QPushButton( _parent ), BoolModelView( new BoolModel( false, NULL, _name, true ), this ), m_group( NULL ) { setWindowTitle( _name ); doConnections(); setFocusPolicy( Qt::NoFocus ); } automatableButton::~automatableButton() { if( m_group != NULL ) { m_group->removeButton( this ); } } void automatableButton::modelChanged() { if( QPushButton::isChecked() != model()->value() ) { QPushButton::setChecked( model()->value() ); } } void automatableButton::update() { if( QPushButton::isChecked() != model()->value() ) { QPushButton::setChecked( model()->value() ); } QPushButton::update(); } void automatableButton::contextMenuEvent( QContextMenuEvent * _me ) { // for the case, the user clicked right while pressing left mouse- // button, the context-menu appears while mouse-cursor is still hidden // and it isn't shown again until user does something which causes // an QApplication::restoreOverrideCursor()-call... mouseReleaseEvent( NULL ); if ( m_group != NULL ) { captionMenu contextMenu( m_group->model()->displayName() ); m_group->addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); } else { captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); } } void automatableButton::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && ! ( _me->modifiers() & Qt::ControlModifier ) ) { // User simply clicked, toggle if needed if( isCheckable() ) { toggle(); } _me->accept(); } else { // Ctrl-clicked, need to prepare drag-drop if( m_group ) { // A group, we must get process it instead AutomatableModelView* groupView = (AutomatableModelView*)m_group; new stringPairDrag( "automatable_model", QString::number( groupView->modelUntyped()->id() ), QPixmap(), widget() ); // TODO: ^^ Maybe use a predefined icon instead of the button they happened to select _me->accept(); } else { // Otherwise, drag the standalone button AutomatableModelView::mousePressEvent( _me ); QPushButton::mousePressEvent( _me ); } } } void automatableButton::mouseReleaseEvent( QMouseEvent * _me ) { if( _me && _me->button() == Qt::LeftButton ) { emit clicked(); } } void automatableButton::toggle() { if( isCheckable() && m_group != NULL ) { if( model()->value() == false ) { m_group->activateButton( this ); } } else { model()->setValue( !model()->value() ); } } automatableButtonGroup::automatableButtonGroup( QWidget * _parent, const QString & _name ) : QWidget( _parent ), IntModelView( new IntModel( 0, 0, 0, NULL, _name, true ), this ) { hide(); setWindowTitle( _name ); } automatableButtonGroup::~automatableButtonGroup() { for( QList::iterator it = m_buttons.begin(); it != m_buttons.end(); ++it ) { ( *it )->m_group = NULL; } } void automatableButtonGroup::addButton( automatableButton * _btn ) { _btn->m_group = this; _btn->setCheckable( true ); _btn->model()->setValue( false ); // disable journalling as we're recording changes of states of // button-group members on our own _btn->model()->setJournalling( false ); m_buttons.push_back( _btn ); model()->setRange( 0, m_buttons.size() - 1 ); updateButtons(); } void automatableButtonGroup::removeButton( automatableButton * _btn ) { m_buttons.erase( qFind( m_buttons.begin(), m_buttons.end(), _btn ) ); _btn->m_group = NULL; } void automatableButtonGroup::activateButton( automatableButton * _btn ) { if( _btn != m_buttons[model()->value()] && m_buttons.indexOf( _btn ) != -1 ) { model()->setValue( m_buttons.indexOf( _btn ) ); foreach( automatableButton * btn, m_buttons ) { btn->update(); } } } void automatableButtonGroup::modelChanged() { connect( model(), SIGNAL( dataChanged() ), this, SLOT( updateButtons() ) ); IntModelView::modelChanged(); updateButtons(); } void automatableButtonGroup::updateButtons() { model()->setRange( 0, m_buttons.size() - 1 ); int i = 0; foreach( automatableButton * btn, m_buttons ) { btn->model()->setValue( i == model()->value() ); ++i; } } #include "moc_automatable_button.cxx" lmms-1.0.0/src/gui/widgets/InstrumentSoundShapingView.cpp0000644000175000017500000001324012313663627022260 0ustar tobytoby/* * InstrumentSoundShapingView.cpp - view for InstrumentSoundShaping class * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "InstrumentSoundShapingView.h" #include "EnvelopeAndLfoParameters.h" #include "EnvelopeAndLfoView.h" #include "combobox.h" #include "group_box.h" #include "gui_templates.h" #include "knob.h" #include "tab_widget.h" const int TARGETS_TABWIDGET_X = 4; const int TARGETS_TABWIDGET_Y = 5; const int TARGETS_TABWIDGET_WIDTH = 242; const int TARGETS_TABWIDGET_HEIGTH = 175; const int FILTER_GROUPBOX_X = TARGETS_TABWIDGET_X; const int FILTER_GROUPBOX_Y = TARGETS_TABWIDGET_Y+TARGETS_TABWIDGET_HEIGTH+5; const int FILTER_GROUPBOX_WIDTH = TARGETS_TABWIDGET_WIDTH; const int FILTER_GROUPBOX_HEIGHT = 245-FILTER_GROUPBOX_Y; InstrumentSoundShapingView::InstrumentSoundShapingView( QWidget * _parent ) : QWidget( _parent ), ModelView( NULL, this ), m_ss( NULL ) { m_targetsTabWidget = new tabWidget( tr( "TARGET" ), this ); m_targetsTabWidget->setGeometry( TARGETS_TABWIDGET_X, TARGETS_TABWIDGET_Y, TARGETS_TABWIDGET_WIDTH, TARGETS_TABWIDGET_HEIGTH ); m_targetsTabWidget->setWhatsThis( tr( "These tabs contain envelopes. They're very important for " "modifying a sound, in that they are almost " "always necessary for substractive synthesis. For " "example if you have a volume envelope, you can set " "when the sound should have a specific volume. " "If you want to create some soft strings then your " "sound has to fade in and out very softly. This can be " "done by setting large attack and release times. " "It's the same for other envelope targets like " "panning, cutoff frequency for the used filter and so on. " "Just monkey around with it! You can really make cool " "sounds out of a saw-wave with just some " "envelopes...!" ) ); for( int i = 0; i < InstrumentSoundShaping::NumTargets; ++i ) { m_envLfoViews[i] = new EnvelopeAndLfoView( m_targetsTabWidget ); m_targetsTabWidget->addTab( m_envLfoViews[i], tr( __targetNames[i][0].toUtf8().constData() ) ); } m_filterGroupBox = new groupBox( tr( "FILTER" ), this ); m_filterGroupBox->setGeometry( FILTER_GROUPBOX_X, FILTER_GROUPBOX_Y, FILTER_GROUPBOX_WIDTH, FILTER_GROUPBOX_HEIGHT ); m_filterComboBox = new comboBox( m_filterGroupBox ); m_filterComboBox->setGeometry( 14, 22, 120, 22 ); m_filterComboBox->setFont( pointSize<8>( m_filterComboBox->font() ) ); m_filterComboBox->setWhatsThis( tr( "Here you can select the built-in filter you want to use " "for this instrument-track. Filters are very important " "for changing the characteristics of a sound." ) ); m_filterCutKnob = new knob( knobBright_26, m_filterGroupBox ); m_filterCutKnob->setLabel( tr( "FREQ" ) ); m_filterCutKnob->move( 140, 18 ); m_filterCutKnob->setHintText( tr( "cutoff frequency:" ) + " ", " " + tr( "Hz" ) ); m_filterCutKnob->setWhatsThis( tr( "Use this knob for setting the cutoff frequency for the " "selected filter. The cutoff frequency specifies the " "frequency for cutting the signal by a filter. For " "example a lowpass-filter cuts all frequencies above " "the cutoff frequency. A highpass-filter cuts all " "frequencies below cutoff frequency, and so on..." ) ); m_filterResKnob = new knob( knobBright_26, m_filterGroupBox ); m_filterResKnob->setLabel( tr( "RESO" ) ); m_filterResKnob->move( 196, 18 ); m_filterResKnob->setHintText( tr( "Resonance:" ) + " ", "" ); m_filterResKnob->setWhatsThis( tr( "Use this knob for setting Q/Resonance for the selected " "filter. Q/Resonance tells the filter how much it " "should amplify frequencies near Cutoff-frequency." ) ); m_singleStreamInfoLabel = new QLabel( tr( "Envelopes, LFOs and filters are not supported by the current instrument." ), this ); m_singleStreamInfoLabel->setWordWrap( true ); m_singleStreamInfoLabel->setFont( pointSize<8>( m_singleStreamInfoLabel->font() ) ); m_singleStreamInfoLabel->setGeometry( TARGETS_TABWIDGET_X, TARGETS_TABWIDGET_Y, TARGETS_TABWIDGET_WIDTH, TARGETS_TABWIDGET_HEIGTH ); } InstrumentSoundShapingView::~InstrumentSoundShapingView() { delete m_targetsTabWidget; } void InstrumentSoundShapingView::setFunctionsHidden( bool hidden ) { m_targetsTabWidget->setHidden( hidden ); m_filterGroupBox->setHidden( hidden ); m_singleStreamInfoLabel->setHidden( !hidden ); } void InstrumentSoundShapingView::modelChanged() { m_ss = castModel(); m_filterGroupBox->setModel( &m_ss->m_filterEnabledModel ); m_filterComboBox->setModel( &m_ss->m_filterModel ); m_filterCutKnob->setModel( &m_ss->m_filterCutModel ); m_filterResKnob->setModel( &m_ss->m_filterResModel ); for( int i = 0; i < InstrumentSoundShaping::NumTargets; ++i ) { m_envLfoViews[i]->setModel( m_ss->m_envLfoParameters[i] ); } } #include "moc_InstrumentSoundShapingView.cxx" lmms-1.0.0/src/gui/widgets/LadspaControlView.cpp0000644000175000017500000000570012313663627020334 0ustar tobytoby/* * LadspaControlView.cpp - widget for controlling a LADSPA port * * Copyright (c) 2006-2008 Danny McRae * Copyright (c) 2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "LadspaControl.h" #include "LadspaControlView.h" #include "LadspaBase.h" #include "led_checkbox.h" #include "TempoSyncKnob.h" #include "tooltip.h" LadspaControlView::LadspaControlView( QWidget * _parent, LadspaControl * _ctl ) : QWidget( _parent ), ModelView( _ctl, this ), m_ctl( _ctl ) { QHBoxLayout * layout = new QHBoxLayout( this ); layout->setMargin( 0 ); layout->setSpacing( 0 ); ledCheckBox * link = NULL; if( m_ctl->m_link ) { link = new ledCheckBox( "", this ); link->setModel( &m_ctl->m_linkEnabledModel ); toolTip::add( link, tr( "Link channels" ) ); layout->addWidget( link ); } knob * knb = NULL; switch( m_ctl->port()->data_type ) { case TOGGLED: { ledCheckBox * toggle = new ledCheckBox( m_ctl->port()->name, this, QString::null, ledCheckBox::Green ); toggle->setModel( m_ctl->toggledModel() ); layout->addWidget( toggle ); if( link != NULL ) { setFixedSize( link->width() + toggle->width(), toggle->height() ); } else { setFixedSize( toggle->width(), toggle->height() ); } break; } case INTEGER: case FLOATING: knb = new knob( knobBright_26, this, m_ctl->port()->name ); break; case TIME: knb = new TempoSyncKnob( knobBright_26, this, m_ctl->port()->name ); break; default: break; } if( knb != NULL ) { if( m_ctl->port()->data_type != TIME ) { knb->setModel( m_ctl->knobModel() ); } else { knb->setModel( m_ctl->tempoSyncKnobModel() ); } knb->setLabel( m_ctl->port()->name ); knb->setHintText( tr( "Value:" ) + " ", "" ); knb->setWhatsThis( tr( "Sorry, no help available." ) ); layout->addWidget( knb ); if( link != NULL ) { setFixedSize( link->width() + knb->width(), knb->height() ); } else { setFixedSize( knb->width(), knb->height() ); } } } LadspaControlView::~LadspaControlView() { } #include "moc_LadspaControlView.cxx" lmms-1.0.0/src/gui/widgets/LcdSpinBox.cpp0000644000175000017500000001027112313663627016740 0ustar tobytoby/* * lcd_spinbox.cpp - class LcdSpinBox, an improved QLCDNumber * * Copyright (c) 2005-2014 Tobias Doerffel * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include "LcdSpinBox.h" #include "caption_menu.h" #include "engine.h" #include "embed.h" #include "gui_templates.h" #include "templates.h" #include "MainWindow.h" LcdSpinBox::LcdSpinBox( int numDigits, QWidget* parent, const QString& name ) : LcdWidget( numDigits, parent, name ), IntModelView( new IntModel( 0, 0, 0, NULL, name, true ), this ), m_mouseMoving( false ), m_origMousePos(), m_displayOffset( 0 ) { } LcdSpinBox::LcdSpinBox( int numDigits, const QString& style, QWidget* parent, const QString& name ) : LcdWidget( numDigits, parent, name ), IntModelView( new IntModel( 0, 0, 0, NULL, name, true ), this ), m_mouseMoving( false ), m_origMousePos(), m_displayOffset( 0 ) { } LcdSpinBox::~LcdSpinBox() { } void LcdSpinBox::update() { setValue( model()->value() + m_displayOffset ); QWidget::update(); } void LcdSpinBox::contextMenuEvent( QContextMenuEvent* event ) { // for the case, the user clicked right while pressing left mouse- // button, the context-menu appears while mouse-cursor is still hidden // and it isn't shown again until user does something which causes // an QApplication::restoreOverrideCursor()-call... mouseReleaseEvent( NULL ); captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); } void LcdSpinBox::mousePressEvent( QMouseEvent* event ) { if( event->button() == Qt::LeftButton && ! ( event->modifiers() & Qt::ControlModifier ) && event->y() < cellHeight() + 2 ) { m_mouseMoving = true; m_origMousePos = event->globalPos(); QApplication::setOverrideCursor( Qt::BlankCursor ); model()->prepareJournalEntryFromOldVal(); } else { IntModelView::mousePressEvent( event ); } } void LcdSpinBox::mouseMoveEvent( QMouseEvent* event ) { if( m_mouseMoving ) { int dy = event->globalY() - m_origMousePos.y(); if( engine::mainWindow()->isShiftPressed() ) dy = qBound( -4, dy/4, 4 ); if( dy > 1 || dy < -1 ) { model()->setInitValue( model()->value() - dy / 2 * model()->step() ); emit manualChange(); QCursor::setPos( m_origMousePos ); } } } void LcdSpinBox::mouseReleaseEvent( QMouseEvent* ) { if( m_mouseMoving ) { model()->addJournalEntryFromOldToCurVal(); QCursor::setPos( m_origMousePos ); QApplication::restoreOverrideCursor(); m_mouseMoving = false; } } void LcdSpinBox::wheelEvent( QWheelEvent * _we ) { _we->accept(); model()->setInitValue( model()->value() + ( ( _we->delta() > 0 ) ? 1 : -1 ) * model()->step() ); emit manualChange(); } void LcdSpinBox::mouseDoubleClickEvent( QMouseEvent * ) { enterValue(); } void LcdSpinBox::enterValue() { bool ok; int new_val; new_val = QInputDialog::getInt( this, windowTitle(), tr( "Please enter a new value between %1 and %2:" ). arg( model()->minValue() ). arg( model()->maxValue() ), model()->value(), model()->minValue(), model()->maxValue(), 4, &ok ); if( ok ) { model()->setValue( new_val ); } } #include "moc_LcdSpinBox.cxx" lmms-1.0.0/src/gui/widgets/tool_button.cpp0000644000175000017500000000506312313663627017306 0ustar tobytoby/* * tool_button.cpp - implementation of LMMS-tool-button for common (cool) look * * Copyright (c) 2005-2006 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "tool_button.h" #include "tooltip.h" const QColor toolButton::s_stdColor = QColor( 216, 216, 216 ); const QColor toolButton::s_hlColor = QColor( 240, 240, 240 ); toolButton::toolButton( const QPixmap & _pixmap, const QString & _tooltip, QObject * _receiver, const char * _slot, QWidget * _parent ) : QToolButton( _parent ), m_colorStandard( s_stdColor ), m_colorHighlighted( s_hlColor ) { setAutoFillBackground( false ); QPalette pal = palette(); pal.setColor( backgroundRole(), m_colorStandard ); pal.setColor( QPalette::Window, m_colorStandard ); pal.setColor( QPalette::Button, m_colorStandard ); setPalette( pal ); if( _receiver != NULL && _slot != NULL ) { connect( this, SIGNAL( clicked() ), _receiver, _slot ); } toolTip::add( this, _tooltip ); setFixedSize( 30, 30 ); setIcon( _pixmap ); leaveEvent( NULL ); connect( this, SIGNAL( toggled( bool ) ), this, SLOT( toggledBool( bool ) ) ); } toolButton::~toolButton() { } void toolButton::enterEvent( QEvent * ) { QPalette pal = palette(); pal.setColor( backgroundRole(), m_colorHighlighted ); pal.setColor( QPalette::Window, m_colorHighlighted ); pal.setColor( QPalette::Button, m_colorHighlighted ); setPalette( pal ); } void toolButton::leaveEvent( QEvent * ) { QPalette pal = palette(); pal.setColor( backgroundRole(), m_colorStandard ); pal.setColor( QPalette::Window, m_colorStandard ); pal.setColor( QPalette::Button, m_colorStandard ); setPalette( pal ); } void toolButton::toggledBool( bool _on ) { if( _on == true ) { emit( clicked() ); } } #include "moc_tool_button.cxx" lmms-1.0.0/src/gui/widgets/pixmap_button.cpp0000644000175000017500000000514012313663627017623 0ustar tobytoby/* * pixmap_button.cpp - implementation of pixmap-button (often used as "themed" * checkboxes/radiobuttons etc) * * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "pixmap_button.h" #include "MainWindow.h" #include "embed.h" pixmapButton::pixmapButton( QWidget * _parent, const QString & _name ) : automatableButton( _parent, _name ), m_activePixmap(), m_inactivePixmap(), m_pressed( false ) { setActiveGraphic( embed::getIconPixmap( "led_yellow" ) ); setInactiveGraphic( embed::getIconPixmap( "led_off" ), false ); } pixmapButton::~pixmapButton() { } void pixmapButton::paintEvent( QPaintEvent * ) { QPainter p( this ); if( ( model() != NULL && model()->value() ) || m_pressed ) { if( !m_activePixmap.isNull() ) { p.drawPixmap( 0, 0, m_activePixmap ); } } else if( !m_inactivePixmap.isNull() ) { p.drawPixmap( 0, 0, m_inactivePixmap ); } } void pixmapButton::mousePressEvent( QMouseEvent * _me ) { // Show pressing graphics if this isn't checkable if( !isCheckable() ) { m_pressed = true; update(); } automatableButton::mousePressEvent( _me ); } void pixmapButton::mouseReleaseEvent( QMouseEvent * _me ) { automatableButton::mouseReleaseEvent( _me ); if( !isCheckable() ) { m_pressed = false; update(); } } void pixmapButton::mouseDoubleClickEvent( QMouseEvent * _me ) { emit doubleClicked(); _me->accept(); } void pixmapButton::setActiveGraphic( const QPixmap & _pm ) { m_activePixmap = _pm; resize( m_activePixmap.width(), m_activePixmap.height() ); } void pixmapButton::setInactiveGraphic( const QPixmap & _pm, bool _update ) { m_inactivePixmap = _pm; if( _update ) { update(); } } #include "moc_pixmap_button.cxx" lmms-1.0.0/src/gui/widgets/tab_bar.cpp0000644000175000017500000001127112313663627016326 0ustar tobytoby/* * tab_bar.cpp - implementation of tab-bar * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "tab_bar.h" #include "tab_button.h" #include "gui_templates.h" #include "tooltip.h" tabBar::tabBar( QWidget * _parent, QBoxLayout::Direction _dir ) : QWidget( _parent ), m_layout( new QBoxLayout( _dir, this ) ), m_exclusive( false ) { m_layout->setMargin( 8 ); m_layout->setSpacing( 0 ); setLayout( m_layout ); } tabBar::~tabBar() { } tabButton * tabBar::addTab( QWidget * _w, const QString & _text, int _id, bool _add_stretch, bool _text_is_tooltip ) { // already tab with id? if( m_tabs.contains( _id ) ) { // then remove it removeTab( _id ); } QString caption = ( _text_is_tooltip ) ? QString( "" ) : _text; // create tab-button tabButton * b = new tabButton( caption, _id, this ); connect( b, SIGNAL( clicked( int ) ), this, SLOT( tabClicked( int ) ) ); b->setIconSize( QSize( 48, 48 ) ); b->setFixedSize( 64, 64 ); b->show(); if( _text_is_tooltip ) { toolTip::add( b, _text ); } // small workaround, because QBoxLayout::addWidget(...) doesn't // work properly, so we first have to remove all tabs from the // layout and them add them in the correct order QMap >::iterator it; for( it = m_tabs.begin(); it != m_tabs.end(); ++it ) { m_layout->removeWidget( it.value().first ); } m_tabs.insert( _id, qMakePair( b, _w ) ); for( it = m_tabs.begin(); it != m_tabs.end(); ++it ) { m_layout->addWidget( it.value().first ); } if( _add_stretch ) { m_layout->addStretch(); } // we assume, parent-widget is a widget acting as widget-stack so all // widgets have the same size and only the one on the top is visible _w->setFixedSize( _w->parentWidget()->size() ); b->setFont( pointSize<8>( b->font() ) ); return( b ); } void tabBar::removeTab( int _id ) { // find tab-button and delete it if( m_tabs.find( _id ) != m_tabs.end() ) { delete m_tabs[_id].first; m_tabs.erase( m_tabs.find( _id ) ); } } void tabBar::setActiveTab( int _id ) { setTabState( _id, true ); hideAll( _id ); if( allHidden() ) { emit allWidgetsHidden(); } else { emit widgetShown(); } } int tabBar::activeTab() { QMap >::iterator it; for( it = m_tabs.begin(); it != m_tabs.end(); ++it ) { if( tabState( it.key() ) == true ) { return( it.key() ); } } return( -1 ); } bool tabBar::tabState( int _id ) { if( m_tabs.find( _id ) == m_tabs.end() ) { return( false ); } return( m_tabs[_id].first->isChecked() ); } void tabBar::setTabState( int _id, bool _checked ) { if( m_tabs.find( _id ) != m_tabs.end() ) { m_tabs[_id].first->setChecked( _checked ); } } void tabBar::hideAll( int _exception ) { QMap >::iterator it; for( it = m_tabs.begin(); it != m_tabs.end(); ++it ) { if( it.key() != _exception ) { setTabState( it.key(), false ); } it.value().second->hide(); } if( m_tabs.find( _exception ) != m_tabs.end() ) { if( tabState( _exception ) ) { m_tabs[_exception].second->show(); } else { m_tabs[_exception].second->hide(); } } } void tabBar::tabClicked( int _id ) { if( m_exclusive == true && activeTab() == -1 ) { setActiveTab( _id ); } else { bool all_hidden_before = allHidden(); // disable tabbar-buttons except the one clicked hideAll( _id ); bool now_hidden = allHidden(); if( all_hidden_before == true && now_hidden == false ) { emit widgetShown(); } else if( all_hidden_before == false && now_hidden == true ) { emit allWidgetsHidden(); } } } bool tabBar::allHidden() { QMap >::iterator it; for( it = m_tabs.begin(); it != m_tabs.end(); ++it ) { if( !it.value().second->isHidden() ) { return( false ); } } return( true ); } #include "moc_tab_bar.cxx" #include "moc_tab_button.cxx" lmms-1.0.0/src/gui/widgets/EnvelopeAndLfoView.cpp0000644000175000017500000004520012313663627020427 0ustar tobytoby/* * EnvelopeAndLfoView.cpp - widget which is m_used by envelope/lfo/filter- * tab of instrument track window * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "EnvelopeAndLfoView.h" #include "EnvelopeAndLfoParameters.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "knob.h" #include "led_checkbox.h" #include "Mixer.h" #include "DataFile.h" #include "Oscillator.h" #include "pixmap_button.h" #include "string_pair_drag.h" #include "TempoSyncKnob.h" #include "text_float.h" #include "tooltip.h" #include "track.h" extern const float SECS_PER_ENV_SEGMENT; extern const float SECS_PER_LFO_OSCILLATION; const int ENV_GRAPH_X = 6; const int ENV_GRAPH_Y = 6; const int ENV_KNOBS_Y = 43; const int ENV_KNOBS_LBL_Y = ENV_KNOBS_Y+35; const int KNOB_X_SPACING = 32; const int PREDELAY_KNOB_X = 6; const int ATTACK_KNOB_X = PREDELAY_KNOB_X+KNOB_X_SPACING; const int HOLD_KNOB_X = ATTACK_KNOB_X+KNOB_X_SPACING; const int DECAY_KNOB_X = HOLD_KNOB_X+KNOB_X_SPACING; const int SUSTAIN_KNOB_X = DECAY_KNOB_X+KNOB_X_SPACING; const int RELEASE_KNOB_X = SUSTAIN_KNOB_X+KNOB_X_SPACING; const int AMOUNT_KNOB_X = RELEASE_KNOB_X+KNOB_X_SPACING; const int TIME_UNIT_WIDTH = 40; const int LFO_GRAPH_X = 6; const int LFO_GRAPH_Y = ENV_KNOBS_LBL_Y+14; const int LFO_KNOB_Y = LFO_GRAPH_Y-2; const int LFO_PREDELAY_KNOB_X = LFO_GRAPH_X + 100; const int LFO_ATTACK_KNOB_X = LFO_PREDELAY_KNOB_X+KNOB_X_SPACING; const int LFO_SPEED_KNOB_X = LFO_ATTACK_KNOB_X+KNOB_X_SPACING; const int LFO_AMOUNT_KNOB_X = LFO_SPEED_KNOB_X+KNOB_X_SPACING; const int LFO_SHAPES_X = LFO_GRAPH_X;//PREDELAY_KNOB_X; const int LFO_SHAPES_Y = LFO_GRAPH_Y + 50; QPixmap * EnvelopeAndLfoView::s_envGraph = NULL; QPixmap * EnvelopeAndLfoView::s_lfoGraph = NULL; EnvelopeAndLfoView::EnvelopeAndLfoView( QWidget * _parent ) : QWidget( _parent ), ModelView( NULL, this ), m_params( NULL ) { if( s_envGraph == NULL ) { s_envGraph = new QPixmap( embed::getIconPixmap( "envelope_graph" ) ); } if( s_lfoGraph == NULL ) { s_lfoGraph = new QPixmap( embed::getIconPixmap( "lfo_graph" ) ); } m_predelayKnob = new knob( knobBright_26, this ); m_predelayKnob->setLabel( tr( "DEL" ) ); m_predelayKnob->move( PREDELAY_KNOB_X, ENV_KNOBS_Y ); m_predelayKnob->setHintText( tr( "Predelay:" ) + " ", "" ); m_predelayKnob->setWhatsThis( tr( "Use this knob for setting predelay of the current " "envelope. The bigger this value the longer the time " "before start of actual envelope." ) ); m_attackKnob = new knob( knobBright_26, this ); m_attackKnob->setLabel( tr( "ATT" ) ); m_attackKnob->move( ATTACK_KNOB_X, ENV_KNOBS_Y ); m_attackKnob->setHintText( tr( "Attack:" )+" ", "" ); m_attackKnob->setWhatsThis( tr( "Use this knob for setting attack-time of the current " "envelope. The bigger this value the longer the " "envelope needs to increase to attack-level. " "Choose a small value for instruments like pianos " "and a big value for strings." ) ); m_holdKnob = new knob( knobBright_26, this ); m_holdKnob->setLabel( tr( "HOLD" ) ); m_holdKnob->move( HOLD_KNOB_X, ENV_KNOBS_Y ); m_holdKnob->setHintText( tr( "Hold:" ) + " ", "" ); m_holdKnob->setWhatsThis( tr( "Use this knob for setting hold-time of the current " "envelope. The bigger this value the longer the " "envelope holds attack-level before it begins to " "decrease to sustain-level." ) ); m_decayKnob = new knob( knobBright_26, this ); m_decayKnob->setLabel( tr( "DEC" ) ); m_decayKnob->move( DECAY_KNOB_X, ENV_KNOBS_Y ); m_decayKnob->setHintText( tr( "Decay:" ) + " ", "" ); m_decayKnob->setWhatsThis( tr( "Use this knob for setting decay-time of the current " "envelope. The bigger this value the longer the " "envelope needs to decrease from attack-level to " "sustain-level. Choose a small value for instruments " "like pianos." ) ); m_sustainKnob = new knob( knobBright_26, this ); m_sustainKnob->setLabel( tr( "SUST" ) ); m_sustainKnob->move( SUSTAIN_KNOB_X, ENV_KNOBS_Y ); m_sustainKnob->setHintText( tr( "Sustain:" ) + " ", "" ); m_sustainKnob->setWhatsThis( tr( "Use this knob for setting sustain-level of the current " "envelope. The bigger this value the higher the level " "on which the envelope stays before going down to " "zero." ) ); m_releaseKnob = new knob( knobBright_26, this ); m_releaseKnob->setLabel( tr( "REL" ) ); m_releaseKnob->move( RELEASE_KNOB_X, ENV_KNOBS_Y ); m_releaseKnob->setHintText( tr( "Release:" ) + " ", "" ); m_releaseKnob->setWhatsThis( tr( "Use this knob for setting release-time of the current " "envelope. The bigger this value the longer the " "envelope needs to decrease from sustain-level to " "zero. Choose a big value for soft instruments like " "strings." ) ); m_amountKnob = new knob( knobBright_26, this ); m_amountKnob->setLabel( tr( "AMT" ) ); m_amountKnob->move( AMOUNT_KNOB_X, ENV_GRAPH_Y ); m_amountKnob->setHintText( tr( "Modulation amount:" ) + " ", "" ); m_amountKnob->setWhatsThis( tr( "Use this knob for setting modulation amount of the " "current envelope. The bigger this value the more the " "according size (e.g. volume or cutoff-frequency) " "will be influenced by this envelope." ) ); m_lfoPredelayKnob = new knob( knobBright_26, this ); m_lfoPredelayKnob->setLabel( tr( "DEL" ) ); m_lfoPredelayKnob->move( LFO_PREDELAY_KNOB_X, LFO_KNOB_Y ); m_lfoPredelayKnob->setHintText( tr( "LFO predelay:" ) + " ", "" ); m_lfoPredelayKnob->setWhatsThis( tr( "Use this knob for setting predelay-time of the current " "LFO. The bigger this value the the time until the " "LFO starts to oscillate." ) ); m_lfoAttackKnob = new knob( knobBright_26, this ); m_lfoAttackKnob->setLabel( tr( "ATT" ) ); m_lfoAttackKnob->move( LFO_ATTACK_KNOB_X, LFO_KNOB_Y ); m_lfoAttackKnob->setHintText( tr( "LFO- attack:" ) + " ", "" ); m_lfoAttackKnob->setWhatsThis( tr( "Use this knob for setting attack-time of the current LFO. " "The bigger this value the longer the LFO needs to " "increase its amplitude to maximum." ) ); m_lfoSpeedKnob = new TempoSyncKnob( knobBright_26, this ); m_lfoSpeedKnob->setLabel( tr( "SPD" ) ); m_lfoSpeedKnob->move( LFO_SPEED_KNOB_X, LFO_KNOB_Y ); m_lfoSpeedKnob->setHintText( tr( "LFO speed:" ) + " ", "" ); m_lfoSpeedKnob->setWhatsThis( tr( "Use this knob for setting speed of the current LFO. The " "bigger this value the faster the LFO oscillates and " "the faster will be your effect." ) ); m_lfoAmountKnob = new knob( knobBright_26, this ); m_lfoAmountKnob->setLabel( tr( "AMT" ) ); m_lfoAmountKnob->move( LFO_AMOUNT_KNOB_X, LFO_KNOB_Y ); m_lfoAmountKnob->setHintText( tr( "Modulation amount:" ) + " ", "" ); m_lfoAmountKnob->setWhatsThis( tr( "Use this knob for setting modulation amount of the " "current LFO. The bigger this value the more the " "selected size (e.g. volume or cutoff-frequency) will " "be influenced by this LFO." ) ); pixmapButton * sin_lfo_btn = new pixmapButton( this, NULL ); sin_lfo_btn->move( LFO_SHAPES_X, LFO_SHAPES_Y ); sin_lfo_btn->setActiveGraphic( embed::getIconPixmap( "sin_wave_active" ) ); sin_lfo_btn->setInactiveGraphic( embed::getIconPixmap( "sin_wave_inactive" ) ); sin_lfo_btn->setWhatsThis( tr( "Click here for a sine-wave." ) ); pixmapButton * triangle_lfo_btn = new pixmapButton( this, NULL ); triangle_lfo_btn->move( LFO_SHAPES_X+15, LFO_SHAPES_Y ); triangle_lfo_btn->setActiveGraphic( embed::getIconPixmap( "triangle_wave_active" ) ); triangle_lfo_btn->setInactiveGraphic( embed::getIconPixmap( "triangle_wave_inactive" ) ); triangle_lfo_btn->setWhatsThis( tr( "Click here for a triangle-wave." ) ); pixmapButton * saw_lfo_btn = new pixmapButton( this, NULL ); saw_lfo_btn->move( LFO_SHAPES_X+30, LFO_SHAPES_Y ); saw_lfo_btn->setActiveGraphic( embed::getIconPixmap( "saw_wave_active" ) ); saw_lfo_btn->setInactiveGraphic( embed::getIconPixmap( "saw_wave_inactive" ) ); saw_lfo_btn->setWhatsThis( tr( "Click here for a saw-wave for current." ) ); pixmapButton * sqr_lfo_btn = new pixmapButton( this, NULL ); sqr_lfo_btn->move( LFO_SHAPES_X+45, LFO_SHAPES_Y ); sqr_lfo_btn->setActiveGraphic( embed::getIconPixmap( "square_wave_active" ) ); sqr_lfo_btn->setInactiveGraphic( embed::getIconPixmap( "square_wave_inactive" ) ); sqr_lfo_btn->setWhatsThis( tr( "Click here for a square-wave." ) ); m_userLfoBtn = new pixmapButton( this, NULL ); m_userLfoBtn->move( LFO_SHAPES_X+60, LFO_SHAPES_Y ); m_userLfoBtn->setActiveGraphic( embed::getIconPixmap( "usr_wave_active" ) ); m_userLfoBtn->setInactiveGraphic( embed::getIconPixmap( "usr_wave_inactive" ) ); m_userLfoBtn->setWhatsThis( tr( "Click here for a user-defined wave. " "Afterwards, drag an according sample-" "file onto the LFO graph." ) ); connect( m_userLfoBtn, SIGNAL( toggled( bool ) ), this, SLOT( lfoUserWaveChanged() ) ); m_lfoWaveBtnGrp = new automatableButtonGroup( this ); m_lfoWaveBtnGrp->addButton( sin_lfo_btn ); m_lfoWaveBtnGrp->addButton( triangle_lfo_btn ); m_lfoWaveBtnGrp->addButton( saw_lfo_btn ); m_lfoWaveBtnGrp->addButton( sqr_lfo_btn ); m_lfoWaveBtnGrp->addButton( m_userLfoBtn ); m_x100Cb = new ledCheckBox( tr( "FREQ x 100" ), this ); m_x100Cb->setFont( pointSizeF( m_x100Cb->font(), 6.5 ) ); m_x100Cb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 36 ); m_x100Cb->setWhatsThis( tr( "Click here if the frequency of this LFO should be " "multiplied by 100." ) ); toolTip::add( m_x100Cb, tr( "multiply LFO-frequency by 100" ) ); m_controlEnvAmountCb = new ledCheckBox( tr( "MODULATE ENV-AMOUNT" ), this ); m_controlEnvAmountCb->move( LFO_PREDELAY_KNOB_X, LFO_GRAPH_Y + 54 ); m_controlEnvAmountCb->setFont( pointSizeF( m_controlEnvAmountCb->font(), 6.5 ) ); m_controlEnvAmountCb ->setWhatsThis( tr( "Click here to make the envelope-amount controlled by this " "LFO." ) ); toolTip::add( m_controlEnvAmountCb, tr( "control envelope-amount by this LFO" ) ); setAcceptDrops( true ); } EnvelopeAndLfoView::~EnvelopeAndLfoView() { delete m_lfoWaveBtnGrp; } void EnvelopeAndLfoView::modelChanged() { m_params = castModel(); m_predelayKnob->setModel( &m_params->m_predelayModel ); m_attackKnob->setModel( &m_params->m_attackModel ); m_holdKnob->setModel( &m_params->m_holdModel ); m_decayKnob->setModel( &m_params->m_decayModel ); m_sustainKnob->setModel( &m_params->m_sustainModel ); m_releaseKnob->setModel( &m_params->m_releaseModel ); m_amountKnob->setModel( &m_params->m_amountModel ); m_lfoPredelayKnob->setModel( &m_params->m_lfoPredelayModel ); m_lfoAttackKnob->setModel( &m_params->m_lfoAttackModel ); m_lfoSpeedKnob->setModel( &m_params->m_lfoSpeedModel ); m_lfoAmountKnob->setModel( &m_params->m_lfoAmountModel ); m_lfoWaveBtnGrp->setModel( &m_params->m_lfoWaveModel ); m_x100Cb->setModel( &m_params->m_x100Model ); m_controlEnvAmountCb->setModel( &m_params->m_controlEnvAmountModel ); } void EnvelopeAndLfoView::mousePressEvent( QMouseEvent * _me ) { if( _me->button() != Qt::LeftButton ) { return; } if( QRect( ENV_GRAPH_X, ENV_GRAPH_Y, s_envGraph->width(), s_envGraph->height() ).contains( _me->pos() ) == true ) { if( m_params->m_amountModel.value() < 1.0f ) { m_params->m_amountModel.setValue( 1.0f ); } else { m_params->m_amountModel.setValue( 0.0f ); } } else if( QRect( LFO_GRAPH_X, LFO_GRAPH_Y, s_lfoGraph->width(), s_lfoGraph->height() ).contains( _me->pos() ) == true ) { if( m_params->m_lfoAmountModel.value() < 1.0f ) { m_params->m_lfoAmountModel.setValue( 1.0f ); } else { m_params->m_lfoAmountModel.setValue( 0.0f ); } } } void EnvelopeAndLfoView::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, QString( "samplefile,tco_%1" ).arg( track::SampleTrack ) ); } void EnvelopeAndLfoView::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString value = stringPairDrag::decodeValue( _de ); if( type == "samplefile" ) { m_params->m_userWave.setAudioFile( stringPairDrag::decodeValue( _de ) ); m_userLfoBtn->model()->setValue( true ); _de->accept(); } else if( type == QString( "tco_%1" ).arg( track::SampleTrack ) ) { DataFile dataFile( value.toUtf8() ); m_params->m_userWave.setAudioFile( dataFile.content().firstChild().toElement(). attribute( "src" ) ); m_userLfoBtn->model()->setValue( true ); _de->accept(); } } void EnvelopeAndLfoView::paintEvent( QPaintEvent * ) { QPainter p( this ); p.setRenderHint( QPainter::Antialiasing ); // draw envelope-graph p.drawPixmap( ENV_GRAPH_X, ENV_GRAPH_Y, *s_envGraph ); // draw LFO-graph p.drawPixmap( LFO_GRAPH_X, LFO_GRAPH_Y, *s_lfoGraph ); p.setFont( pointSize<8>( p.font() ) ); const float gray_amount = 1.0f - fabsf( m_amountKnob->value() ); p.setPen( QPen( QColor( static_cast( 96 * gray_amount ), static_cast( 255 - 159 * gray_amount ), static_cast( 128 - 32 * gray_amount ) ), 2 ) ); const QColor end_points_color( 0x99, 0xAF, 0xFF ); const QColor end_points_bg_color( 0, 0, 2 ); const int y_base = ENV_GRAPH_Y + s_envGraph->height() - 3; const int avail_height = s_envGraph->height() - 6; int x1 = static_cast( m_predelayKnob->value() * TIME_UNIT_WIDTH ); int x2 = x1 + static_cast( m_attackKnob->value() * TIME_UNIT_WIDTH ); int x3 = x2 + static_cast( m_holdKnob->value() * TIME_UNIT_WIDTH ); int x4 = x3 + static_cast( ( m_decayKnob->value() * ( 1 - m_sustainKnob->value() ) ) * TIME_UNIT_WIDTH ); int x5 = x4 + static_cast( m_releaseKnob->value() * TIME_UNIT_WIDTH ); if( x5 > 174 ) { x1 = ( x1 * 174 ) / x5; x2 = ( x2 * 174 ) / x5; x3 = ( x3 * 174 ) / x5; x4 = ( x4 * 174 ) / x5; x5 = ( x5 * 174 ) / x5; } x1 += ENV_GRAPH_X + 2; x2 += ENV_GRAPH_X + 2; x3 += ENV_GRAPH_X + 2; x4 += ENV_GRAPH_X + 2; x5 += ENV_GRAPH_X + 2; p.drawLine( x1, y_base, x2, y_base - avail_height ); p.fillRect( x1 - 1, y_base - 2, 4, 4, end_points_bg_color ); p.fillRect( x1, y_base - 1, 2, 2, end_points_color ); p.drawLine( x2, y_base - avail_height, x3, y_base - avail_height ); p.fillRect( x2 - 1, y_base - 2 - avail_height, 4, 4, end_points_bg_color ); p.fillRect( x2, y_base - 1 - avail_height, 2, 2, end_points_color ); p.drawLine( x3, y_base-avail_height, x4, static_cast( y_base - avail_height + ( 1 - m_sustainKnob->value() ) * avail_height ) ); p.fillRect( x3 - 1, y_base - 2 - avail_height, 4, 4, end_points_bg_color ); p.fillRect( x3, y_base - 1 - avail_height, 2, 2, end_points_color ); p.drawLine( x4, static_cast( y_base - avail_height + ( 1 - m_sustainKnob->value() ) * avail_height ), x5, y_base ); p.fillRect( x4 - 1, static_cast( y_base - avail_height + ( 1 - m_sustainKnob->value() ) * avail_height ) - 2, 4, 4, end_points_bg_color ); p.fillRect( x4, static_cast( y_base - avail_height + ( 1 - m_sustainKnob->value() ) * avail_height ) - 1, 2, 2, end_points_color ); p.fillRect( x5 - 1, y_base - 2, 4, 4, end_points_bg_color ); p.fillRect( x5, y_base - 1, 2, 2, end_points_color ); int LFO_GRAPH_W = s_lfoGraph->width() - 6; // substract border int LFO_GRAPH_H = s_lfoGraph->height() - 6; // substract border int graph_x_base = LFO_GRAPH_X + 3; int graph_y_base = LFO_GRAPH_Y + 3 + LFO_GRAPH_H / 2; const float frames_for_graph = SECS_PER_LFO_OSCILLATION * engine::mixer()->baseSampleRate() / 10; const float lfo_gray_amount = 1.0f - fabsf( m_lfoAmountKnob->value() ); p.setPen( QPen( QColor( static_cast( 96 * lfo_gray_amount ), static_cast( 255 - 159 * lfo_gray_amount ), static_cast( 128 - 32 * lfo_gray_amount ) ), 1.5 ) ); float osc_frames = m_params->m_lfoOscillationFrames; if( m_params->m_x100Model.value() ) { osc_frames *= 100.0f; } float old_y = 0; for( int x = 0; x <= LFO_GRAPH_W; ++x ) { float val = 0.0; float cur_sample = x * frames_for_graph / LFO_GRAPH_W; if( static_cast( cur_sample ) > m_params->m_lfoPredelayFrames ) { float phase = ( cur_sample -= m_params->m_lfoPredelayFrames ) / osc_frames; switch( m_params->m_lfoWaveModel.value() ) { case EnvelopeAndLfoParameters::SineWave: val = Oscillator::sinSample( phase ); break; case EnvelopeAndLfoParameters::TriangleWave: val = Oscillator::triangleSample( phase ); break; case EnvelopeAndLfoParameters::SawWave: val = Oscillator::sawSample( phase ); break; case EnvelopeAndLfoParameters::SquareWave: val = Oscillator::squareSample( phase ); break; case EnvelopeAndLfoParameters::UserDefinedWave: val = m_params->m_userWave. userWaveSample( phase ); } if( static_cast( cur_sample ) <= m_params->m_lfoAttackFrames ) { val *= cur_sample / m_params->m_lfoAttackFrames; } } float cur_y = -LFO_GRAPH_H / 2.0f * val; p.drawLine( QLineF( graph_x_base + x - 1, graph_y_base + old_y, graph_x_base + x, graph_y_base + cur_y ) ); old_y = cur_y; } p.setPen( QColor( 201, 201, 225 ) ); int ms_per_osc = static_cast( SECS_PER_LFO_OSCILLATION * m_lfoSpeedKnob->value() * 1000.0f ); p.drawText( LFO_GRAPH_X + 4, LFO_GRAPH_Y + s_lfoGraph->height() - 6, tr( "ms/LFO:" ) ); p.drawText( LFO_GRAPH_X + 52, LFO_GRAPH_Y + s_lfoGraph->height() - 6, QString::number( ms_per_osc ) ); } void EnvelopeAndLfoView::lfoUserWaveChanged() { if( m_params->m_lfoWaveModel.value() == EnvelopeAndLfoParameters::UserDefinedWave ) { if( m_params->m_userWave.frames() <= 1 ) { textFloat::displayMessage( tr( "Hint" ), tr( "Drag a sample from somewhere and drop " "it in this window." ), embed::getIconPixmap( "hint" ), 3000 ); } } } #include "moc_EnvelopeAndLfoView.cxx" lmms-1.0.0/src/gui/widgets/InstrumentMidiIOView.cpp0000644000175000017500000001636112313663627020777 0ustar tobytoby/* * InstrumentMidiIOView.cpp - MIDI-IO-View * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "InstrumentMidiIOView.h" #include "MidiPortMenu.h" #include "engine.h" #include "embed.h" #include "group_box.h" #include "gui_templates.h" #include "LcdSpinBox.h" #include "MidiClient.h" #include "Mixer.h" #include "tooltip.h" InstrumentMidiIOView::InstrumentMidiIOView( QWidget* parent ) : QWidget( parent ), ModelView( NULL, this ), m_rpBtn( NULL ), m_wpBtn( NULL ) { QVBoxLayout* layout = new QVBoxLayout( this ); layout->setMargin( 5 ); m_midiInputGroupBox = new groupBox( tr( "ENABLE MIDI INPUT" ) ); layout->addWidget( m_midiInputGroupBox ); QHBoxLayout* midiInputLayout = new QHBoxLayout( m_midiInputGroupBox ); midiInputLayout->setContentsMargins( 8, 18, 8, 8 ); midiInputLayout->setSpacing( 6 ); m_inputChannelSpinBox = new LcdSpinBox( 2, m_midiInputGroupBox ); m_inputChannelSpinBox->addTextForValue( 0, "--" ); m_inputChannelSpinBox->setLabel( tr( "CHANNEL" ) ); m_inputChannelSpinBox->setEnabled( false ); midiInputLayout->addWidget( m_inputChannelSpinBox ); m_fixedInputVelocitySpinBox = new LcdSpinBox( 3, m_midiInputGroupBox ); m_fixedInputVelocitySpinBox->setDisplayOffset( 1 ); m_fixedInputVelocitySpinBox->addTextForValue( 0, "---" ); m_fixedInputVelocitySpinBox->setLabel( tr( "VELOCITY" ) ); m_fixedInputVelocitySpinBox->setEnabled( false ); midiInputLayout->addWidget( m_fixedInputVelocitySpinBox ); midiInputLayout->addStretch(); connect( m_midiInputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_inputChannelSpinBox, SLOT( setEnabled( bool ) ) ); connect( m_midiInputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_fixedInputVelocitySpinBox, SLOT( setEnabled( bool ) ) ); m_midiOutputGroupBox = new groupBox( tr( "ENABLE MIDI OUTPUT" ) ); layout->addWidget( m_midiOutputGroupBox ); QHBoxLayout* midiOutputLayout = new QHBoxLayout( m_midiOutputGroupBox ); midiOutputLayout->setContentsMargins( 8, 18, 8, 8 ); midiOutputLayout->setSpacing( 6 ); m_outputChannelSpinBox = new LcdSpinBox( 2, m_midiOutputGroupBox ); m_outputChannelSpinBox->setLabel( tr( "CHANNEL" ) ); m_outputChannelSpinBox->setEnabled( false ); midiOutputLayout->addWidget( m_outputChannelSpinBox ); m_fixedOutputVelocitySpinBox = new LcdSpinBox( 3, m_midiOutputGroupBox ); m_fixedOutputVelocitySpinBox->setDisplayOffset( 1 ); m_fixedOutputVelocitySpinBox->addTextForValue( 0, "---" ); m_fixedOutputVelocitySpinBox->setLabel( tr( "VELOCITY" ) ); m_fixedOutputVelocitySpinBox->setEnabled( false ); midiOutputLayout->addWidget( m_fixedOutputVelocitySpinBox ); m_outputProgramSpinBox = new LcdSpinBox( 3, m_midiOutputGroupBox ); m_outputProgramSpinBox->setLabel( tr( "PROGRAM" ) ); m_outputProgramSpinBox->setEnabled( false ); midiOutputLayout->addWidget( m_outputProgramSpinBox ); m_fixedOutputNoteSpinBox = new LcdSpinBox( 3, m_midiOutputGroupBox ); m_fixedOutputNoteSpinBox->setDisplayOffset( 1 ); m_fixedOutputNoteSpinBox->addTextForValue( 0, "---" ); m_fixedOutputNoteSpinBox->setLabel( tr( "NOTE" ) ); m_fixedOutputNoteSpinBox->setEnabled( false ); midiOutputLayout->addWidget( m_fixedOutputNoteSpinBox ); midiOutputLayout->addStretch(); connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_outputChannelSpinBox, SLOT( setEnabled( bool ) ) ); connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_fixedOutputVelocitySpinBox, SLOT( setEnabled( bool ) ) ); connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_outputProgramSpinBox, SLOT( setEnabled( bool ) ) ); connect( m_midiOutputGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_fixedOutputNoteSpinBox, SLOT( setEnabled( bool ) ) ); if( !engine::mixer()->midiClient()->isRaw() ) { m_rpBtn = new QToolButton; m_rpBtn->setMinimumSize( 32, 32 ); m_rpBtn->setText( tr( "MIDI devices to receive MIDI events from" ) ); m_rpBtn->setIcon( embed::getIconPixmap( "piano" ) ); m_rpBtn->setPopupMode( QToolButton::InstantPopup ); midiInputLayout->insertSpacing( 0, 4 ); midiInputLayout->insertWidget( 0, m_rpBtn ); m_wpBtn = new QToolButton; m_wpBtn->setMinimumSize( 32, 32 ); m_wpBtn->setText( tr( "MIDI devices to send MIDI events to" ) ); m_wpBtn->setIcon( embed::getIconPixmap( "piano" ) ); m_wpBtn->setPopupMode( QToolButton::InstantPopup ); midiOutputLayout->insertSpacing( 0, 4 ); midiOutputLayout->insertWidget( 0, m_wpBtn ); } #define PROVIDE_CUSTOM_BASE_VELOCITY_UI #ifdef PROVIDE_CUSTOM_BASE_VELOCITY_UI groupBox* baseVelocityGroupBox = new groupBox( tr( "CUSTOM BASE VELOCITY" ) ); layout->addWidget( baseVelocityGroupBox ); QVBoxLayout* baseVelocityLayout = new QVBoxLayout( baseVelocityGroupBox ); baseVelocityLayout->setContentsMargins( 8, 18, 8, 8 ); baseVelocityLayout->setSpacing( 6 ); QLabel* baseVelocityHelp = new QLabel( tr( "Specify the velocity normalization base for MIDI-based instruments at note volume 100%" ) ); baseVelocityHelp->setWordWrap( true ); baseVelocityHelp->setFont( pointSize<8>( baseVelocityHelp->font() ) ); baseVelocityLayout->addWidget( baseVelocityHelp ); m_baseVelocitySpinBox = new LcdSpinBox( 3, baseVelocityGroupBox ); m_baseVelocitySpinBox->setLabel( tr( "BASE VELOCITY" ) ); m_baseVelocitySpinBox->setEnabled( false ); baseVelocityLayout->addWidget( m_baseVelocitySpinBox ); connect( baseVelocityGroupBox->ledButton(), SIGNAL( toggled( bool ) ), m_baseVelocitySpinBox, SLOT( setEnabled( bool ) ) ); #endif layout->addStretch(); } InstrumentMidiIOView::~InstrumentMidiIOView() { } void InstrumentMidiIOView::modelChanged() { MidiPort * mp = castModel(); m_midiInputGroupBox->setModel( &mp->m_readableModel ); m_inputChannelSpinBox->setModel( &mp->m_inputChannelModel ); m_fixedInputVelocitySpinBox->setModel( &mp->m_fixedInputVelocityModel ); m_midiOutputGroupBox->setModel( &mp->m_writableModel ); m_outputChannelSpinBox->setModel( &mp->m_outputChannelModel ); m_fixedOutputVelocitySpinBox->setModel( &mp->m_fixedOutputVelocityModel ); m_fixedOutputNoteSpinBox->setModel( &mp->m_fixedOutputNoteModel ); m_outputProgramSpinBox->setModel( &mp->m_outputProgramModel ); #ifdef PROVIDE_CUSTOM_BASE_VELOCITY_UI m_baseVelocitySpinBox->setModel( &mp->m_baseVelocityModel ); #endif if( m_rpBtn ) { m_rpBtn->setMenu( mp->m_readablePortsMenu ); } if( m_wpBtn ) { m_wpBtn->setMenu( mp->m_writablePortsMenu ); } } lmms-1.0.0/src/gui/widgets/tempo_sync_knob.cpp0000644000175000017500000001750412313663627020132 0ustar tobytoby/* * TempoSyncKnob.cpp - adds bpm to ms conversion for knob class * * Copyright (c) 2005-2007 Danny McRae * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "TempoSyncKnob.h" #include "engine.h" #include "caption_menu.h" #include "embed.h" #include "MainWindow.h" #include "MeterDialog.h" #include "song.h" TempoSyncKnob::TempoSyncKnob( int _knob_num, QWidget * _parent, const QString & _name ) : knob( _knob_num, _parent, _name ), m_tempoSyncIcon( embed::getIconPixmap( "tempo_sync" ) ), m_tempoSyncDescription( tr( "Tempo Sync" ) ), m_custom( NULL ) { } TempoSyncKnob::~TempoSyncKnob() { if( m_custom ) { delete m_custom->parentWidget(); } } void TempoSyncKnob::modelChanged() { if( model() == NULL ) { qWarning( "no TempoSyncKnobModel has been set!" ); } if( m_custom != NULL ) { m_custom->setModel( &model()->m_custom ); } connect( model(), SIGNAL( syncModeChanged( TempoSyncMode ) ), this, SLOT( updateDescAndIcon() ) ); connect( this, SIGNAL( sliderMoved( float ) ), model(), SLOT( disableSync() ) ); updateDescAndIcon(); } void TempoSyncKnob::contextMenuEvent( QContextMenuEvent * ) { mouseReleaseEvent( NULL ); captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.addSeparator(); float limit = 60000.0f / ( engine::getSong()->getTempo() * model()->m_scale ); QMenu * syncMenu = contextMenu.addMenu( m_tempoSyncIcon, m_tempoSyncDescription ); if( limit / 8.0f <= model()->maxValue() ) { connect( syncMenu, SIGNAL( triggered( QAction * ) ), model(), SLOT( setTempoSync( QAction * ) ) ); syncMenu->addAction( embed::getIconPixmap( "note_none" ), tr( "No Sync" ) )->setData( (int) TempoSyncKnobModel::SyncNone ); if( limit / 0.125f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_double_whole" ), tr( "Eight beats" ) )->setData( (int) TempoSyncKnobModel::SyncDoubleWholeNote ); } if( limit / 0.25f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_whole" ), tr( "Whole note" ) )->setData( (int) TempoSyncKnobModel::SyncWholeNote ); } if( limit / 0.5f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_half" ), tr( "Half note" ) )->setData( (int) TempoSyncKnobModel::SyncHalfNote ); } if( limit <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_quarter" ), tr( "Quarter note" ) )->setData( (int) TempoSyncKnobModel::SyncQuarterNote ); } if( limit / 2.0f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_eighth" ), tr( "8th note" ) )->setData( (int) TempoSyncKnobModel::SyncEighthNote ); } if( limit / 4.0f <= model()->maxValue() ) { syncMenu->addAction( embed::getIconPixmap( "note_sixteenth" ), tr( "16th note" ) )->setData( (int) TempoSyncKnobModel::SyncSixteenthNote ); } syncMenu->addAction( embed::getIconPixmap( "note_thirtysecond" ), tr( "32nd note" ) )->setData( (int) TempoSyncKnobModel::SyncThirtysecondNote ); syncMenu->addAction( embed::getIconPixmap( "dont_know" ), tr( "Custom..." ), this, SLOT( showCustom() ) )->setData( (int) TempoSyncKnobModel::SyncCustom ); contextMenu.addSeparator(); } contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "help" ), tr( "&Help" ), this, SLOT( displayHelp() ) ); contextMenu.exec( QCursor::pos() ); delete syncMenu; } void TempoSyncKnob::updateDescAndIcon() { if( model()->m_tempoSyncMode ) { switch( model()->m_tempoSyncMode ) { case TempoSyncKnobModel::SyncCustom: m_tempoSyncDescription = tr( "Custom " ) + "(" + QString::number( model()->m_custom.numeratorModel().value() ) + "/" + QString::number( model()->m_custom.denominatorModel().value() ) + ")"; break; case TempoSyncKnobModel::SyncDoubleWholeNote: m_tempoSyncDescription = tr( "Synced to Eight Beats" ); break; case TempoSyncKnobModel::SyncWholeNote: m_tempoSyncDescription = tr( "Synced to Whole Note" ); break; case TempoSyncKnobModel::SyncHalfNote: m_tempoSyncDescription = tr( "Synced to Half Note" ); break; case TempoSyncKnobModel::SyncQuarterNote: m_tempoSyncDescription = tr( "Synced to Quarter Note" ); break; case TempoSyncKnobModel::SyncEighthNote: m_tempoSyncDescription = tr( "Synced to 8th Note" ); break; case TempoSyncKnobModel::SyncSixteenthNote: m_tempoSyncDescription = tr( "Synced to 16th Note" ); break; case TempoSyncKnobModel::SyncThirtysecondNote: m_tempoSyncDescription = tr( "Synced to 32nd Note" ); break; default: ; } } else { m_tempoSyncDescription = tr( "Tempo Sync" ); } if( m_custom != NULL && model()->m_tempoSyncMode != TempoSyncKnobModel::SyncCustom ) { m_custom->parentWidget()->hide(); } switch( model()->m_tempoSyncMode ) { case TempoSyncKnobModel::SyncNone: m_tempoSyncIcon = embed::getIconPixmap( "tempo_sync" ); break; case TempoSyncKnobModel::SyncCustom: m_tempoSyncIcon = embed::getIconPixmap( "dont_know" ); break; case TempoSyncKnobModel::SyncDoubleWholeNote: m_tempoSyncIcon = embed::getIconPixmap( "note_double_whole" ); break; case TempoSyncKnobModel::SyncWholeNote: m_tempoSyncIcon = embed::getIconPixmap( "note_whole" ); break; case TempoSyncKnobModel::SyncHalfNote: m_tempoSyncIcon = embed::getIconPixmap( "note_half" ); break; case TempoSyncKnobModel::SyncQuarterNote: m_tempoSyncIcon = embed::getIconPixmap( "note_quarter" ); break; case TempoSyncKnobModel::SyncEighthNote: m_tempoSyncIcon = embed::getIconPixmap( "note_eighth" ); break; case TempoSyncKnobModel::SyncSixteenthNote: m_tempoSyncIcon = embed::getIconPixmap( "note_sixteenth" ); break; case TempoSyncKnobModel::SyncThirtysecondNote: m_tempoSyncIcon = embed::getIconPixmap( "note_thirtysecond" ); break; default: qWarning( "TempoSyncKnob::calculateTempoSyncTime:" "invalid TempoSyncMode" ); break; } emit syncDescriptionChanged( m_tempoSyncDescription ); emit syncIconChanged(); } const QString & TempoSyncKnob::syncDescription() { return m_tempoSyncDescription; } void TempoSyncKnob::setSyncDescription( const QString & _new_description ) { m_tempoSyncDescription = _new_description; emit syncDescriptionChanged( _new_description ); } const QPixmap & TempoSyncKnob::syncIcon() { return m_tempoSyncIcon; } void TempoSyncKnob::setSyncIcon( const QPixmap & _new_icon ) { m_tempoSyncIcon = _new_icon; emit syncIconChanged(); } void TempoSyncKnob::showCustom() { if( m_custom == NULL ) { m_custom = new MeterDialog( engine::mainWindow()->workspace() ); engine::mainWindow()->workspace()->addSubWindow( m_custom ); m_custom->setWindowTitle( "Meter" ); m_custom->setModel( &model()->m_custom ); } m_custom->parentWidget()->show(); model()->setTempoSync( TempoSyncKnobModel::SyncCustom ); } #include "moc_TempoSyncKnob.cxx" lmms-1.0.0/src/gui/widgets/combobox.cpp0000644000175000017500000001327712313663627016554 0ustar tobytoby/* * combobox.cpp - implementation of LMMS combobox * * Copyright (c) 2006-2014 Tobias Doerffel * Copyright (c) 2008-2009 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "combobox.h" #include #include #include #include #include #include #include #include "caption_menu.h" #include "engine.h" #include "embed.h" #include "gui_templates.h" #include "MainWindow.h" QPixmap * comboBox::s_background = NULL; QPixmap * comboBox::s_arrow = NULL; QPixmap * comboBox::s_arrowSelected = NULL; const int CB_ARROW_BTN_WIDTH = 20; comboBox::comboBox( QWidget * _parent, const QString & _name ) : QWidget( _parent ), IntModelView( new ComboBoxModel( NULL, QString::null, true ), this ), m_menu( this ), m_pressed( false ) { if( s_background == NULL ) { s_background = new QPixmap( embed::getIconPixmap( "combobox_bg" ) ); } if( s_arrow == NULL ) { s_arrow = new QPixmap( embed::getIconPixmap( "combobox_arrow" ) ); } if( s_arrowSelected == NULL ) { s_arrowSelected = new QPixmap( embed::getIconPixmap( "combobox_arrow_selected" ) ); } setFont( pointSize<9>( font() ) ); m_menu.setFont( pointSize<8>( m_menu.font() ) ); connect( &m_menu, SIGNAL( triggered( QAction * ) ), this, SLOT( setItem( QAction * ) ) ); setWindowTitle( _name ); doConnections(); } comboBox::~comboBox() { } QSize comboBox::sizeHint() const { int maxTextWidth = 0; for( int i = 0; model() && i < model()->size(); ++i ) { int w = fontMetrics().width( model()->itemText( i ) ); if( w > maxTextWidth ) { maxTextWidth = w; } } return QSize( 32 + maxTextWidth, 22 ); } void comboBox::contextMenuEvent( QContextMenuEvent * event ) { if( model() == NULL || event->x() <= width() - CB_ARROW_BTN_WIDTH ) { QWidget::contextMenuEvent( event ); return; } captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); } void comboBox::mousePressEvent( QMouseEvent* event ) { if( model() == NULL ) { return; } if( event->button() == Qt::LeftButton && ! ( event->modifiers() & Qt::ControlModifier ) ) { if( event->x() > width() - CB_ARROW_BTN_WIDTH ) { m_pressed = true; update(); m_menu.clear(); for( int i = 0; i < model()->size(); ++i ) { QAction * a = m_menu.addAction( model()->itemPixmap( i ) ? model()->itemPixmap( i )->pixmap() : QPixmap(), model()->itemText( i ) ); a->setData( i ); } QPoint gpos = mapToGlobal( QPoint( 0, height() ) ); if( gpos.y() + m_menu.sizeHint().height() < qApp->desktop()->height() ) { m_menu.exec( gpos ); } else { m_menu.exec( mapToGlobal( QPoint( width(), 0 ) ) ); } m_pressed = false; update(); } else if( event->button() == Qt::LeftButton ) { model()->setInitValue( model()->value() + 1 ); update(); } } else if( event->button() == Qt::RightButton ) { model()->setInitValue( model()->value() - 1 ); update(); } else { IntModelView::mousePressEvent( event ); } } void comboBox::paintEvent( QPaintEvent * _pe ) { QPainter p( this ); p.fillRect( 2, 2, width()-2, height()-4, *s_background ); QColor shadow = palette().shadow().color(); QColor highlight = palette().highlight().color(); shadow.setAlpha( 124 ); highlight.setAlpha( 124 ); // button-separator p.setPen( shadow ); p.drawLine( width() - CB_ARROW_BTN_WIDTH - 1, 1, width() - CB_ARROW_BTN_WIDTH - 1, height() - 3 ); p.setPen( highlight ); p.drawLine( width() - CB_ARROW_BTN_WIDTH, 1, width() - CB_ARROW_BTN_WIDTH, height() - 3 ); // Border QStyleOptionFrame opt; opt.initFrom( this ); opt.state = 0; style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this ); QPixmap * arrow = m_pressed ? s_arrowSelected : s_arrow; p.drawPixmap( width() - CB_ARROW_BTN_WIDTH + 5, 4, *arrow ); if( model() && model()->size() > 0 ) { p.setFont( font() ); p.setClipRect( QRect( 4, 2, width() - CB_ARROW_BTN_WIDTH - 8, height() - 2 ) ); QPixmap pm = model()->currentData() ? model()->currentData()->pixmap() : QPixmap(); int tx = 5; if( !pm.isNull() ) { if( pm.height() > 16 ) { pm = pm.scaledToHeight( 16, Qt::SmoothTransformation ); } p.drawPixmap( tx, 3, pm ); tx += pm.width() + 3; } const int y = ( height()+p.fontMetrics().height() ) /2; p.setPen( QColor( 64, 64, 64 ) ); p.drawText( tx+1, y-3, model()->currentText() ); p.setPen( QColor( 224, 224, 224 ) ); p.drawText( tx, y-4, model()->currentText() ); } } void comboBox::wheelEvent( QWheelEvent* event ) { if( model() ) { model()->setInitValue( model()->value() + ( ( event->delta() < 0 ) ? 1 : -1 ) ); update(); event->accept(); } } void comboBox::setItem( QAction* item ) { if( model() ) { model()->setInitValue( item->data().toInt() ); } } #include "moc_combobox.cxx" lmms-1.0.0/src/gui/widgets/MeterDialog.cpp0000644000175000017500000000557512313663627017142 0ustar tobytoby/* * MeterDialog.cpp - dialog for entering meter settings * * Copyright (c) 2008-2009 Tobias Doerffel * Copyright (c) 2006-2008 Danny McRae * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "MeterDialog.h" #include "MeterModel.h" #include "embed.h" #include "gui_templates.h" #include "LcdSpinBox.h" MeterDialog::MeterDialog( QWidget * _parent, bool _simple ) : QWidget( _parent ), ModelView( NULL, this ) { QVBoxLayout * vlayout = new QVBoxLayout( this ); vlayout->setSpacing( 0 ); vlayout->setMargin( 0 ); QWidget * num = new QWidget( this ); QHBoxLayout * num_layout = new QHBoxLayout( num ); num_layout->setSpacing( 0 ); num_layout->setMargin( 0 ); m_numerator = new LcdSpinBox( 2, num, tr( "Meter Numerator" ) ); num_layout->addWidget( m_numerator ); if( !_simple ) { QLabel * num_label = new QLabel( tr( "Meter Numerator" ), num ); QFont f = num_label->font(); num_label->setFont( pointSize<7>( f ) ); num_layout->addSpacing( 5 ); num_layout->addWidget( num_label ); } num_layout->addStretch(); QWidget * den = new QWidget( this ); QHBoxLayout * den_layout = new QHBoxLayout( den ); den_layout->setSpacing( 0 ); den_layout->setMargin( 0 ); m_denominator = new LcdSpinBox( 2, den, tr( "Meter Denominator" ) ); if( _simple ) { m_denominator->setLabel( tr( "TIME SIG" ) ); } den_layout->addWidget( m_denominator ); if( !_simple ) { QLabel * den_label = new QLabel( tr( "Meter Denominator" ), den ); QFont f = den_label->font(); den_label->setFont( pointSize<7>( f ) ); den_layout->addSpacing( 5 ); den_layout->addWidget( den_label ); } den_layout->addStretch(); vlayout->addSpacing( _simple ? 1 : 3 ); vlayout->addWidget( num ); vlayout->addSpacing( 2 ); vlayout->addWidget( den ); vlayout->addStretch(); } MeterDialog::~MeterDialog() { } void MeterDialog::modelChanged() { MeterModel * mm = castModel(); m_numerator->setModel( &mm->numeratorModel() ); m_denominator->setModel( &mm->denominatorModel() ); } lmms-1.0.0/src/gui/widgets/track_label_button.cpp0000644000175000017500000000660712313663627020601 0ustar tobytoby/* * track_label_button.cpp - implementation of class trackLabelButton, a label * which is renamable by double-clicking it * * Copyright (c) 2004-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "track_label_button.h" #include "embed.h" #include "rename_dialog.h" #include "InstrumentTrack.h" #include "Instrument.h" #include "config_mgr.h" #include "engine.h" trackLabelButton::trackLabelButton( trackView * _tv, QWidget * _parent ) : QToolButton( _parent ), m_trackView( _tv ), m_iconName() { setAttribute( Qt::WA_OpaquePaintEvent, true ); setAcceptDrops( true ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); setToolButtonStyle( Qt::ToolButtonTextBesideIcon ); if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { setFixedSize( 32, 29 ); } else { setFixedSize( 160, 29 ); } setIconSize( QSize( 24, 24 ) ); setText( " " ); connect( m_trackView->getTrack(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); } trackLabelButton::~trackLabelButton() { } void trackLabelButton::rename() { QString txt = m_trackView->getTrack()->name(); renameDialog rename_dlg( txt ); rename_dlg.exec(); if( txt != text() ) { m_trackView->getTrack()->setName( txt ); } } void trackLabelButton::dragEnterEvent( QDragEnterEvent * _dee ) { m_trackView->dragEnterEvent( _dee ); } void trackLabelButton::dropEvent( QDropEvent * _de ) { m_trackView->dropEvent( _de ); setChecked( true ); } void trackLabelButton::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::RightButton ) { rename(); } else { QToolButton::mousePressEvent( _me ); } } void trackLabelButton::mouseDoubleClickEvent( QMouseEvent * _me ) { rename(); } void trackLabelButton::paintEvent( QPaintEvent * _pe ) { if( m_trackView->getTrack()->type() == track::InstrumentTrack ) { InstrumentTrack * it = dynamic_cast( m_trackView->getTrack() ); const PixmapLoader * pl; if( it && it->instrument() && it->instrument()->descriptor() && ( pl = it->instrument()->descriptor()->logo ) ) { if( pl->pixmapName() != m_iconName ) { m_iconName = pl->pixmapName(); setIcon( pl->pixmap() ); } } } if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { setText(""); setToolTip( m_trackView->getTrack()->displayName() ); } else { setText( m_trackView->getTrack()->displayName() ); } QToolButton::paintEvent( _pe ); } #include "moc_track_label_button.cxx" lmms-1.0.0/src/gui/widgets/nstate_button.cpp0000644000175000017500000000430512313663627017625 0ustar tobytoby/* * nstate_button.cpp - implementation of n-state-button * * Copyright (c) 2005-2006 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "nstate_button.h" #include "embed.h" #include "tooltip.h" nStateButton::nStateButton( QWidget * _parent ) : toolButton( _parent ), m_generalToolTip( "" ), m_curState( -1 ) { } nStateButton::~nStateButton() { while( m_states.size() ) { m_states.erase( m_states.begin() ); } } void nStateButton::addState( const QPixmap & _pm, const QString & _tooltip ) { m_states.push_back( qMakePair( _pm, _tooltip ) ); // first inserted pixmap? if( m_states.size() == 1 ) { // then resize ourself setFixedSize( _pm.width() + 6, _pm.height() + 6 ); // and set state to first pixmap changeState( 0 ); } } void nStateButton::changeState( int _n ) { if( _n >= 0 && _n < (int) m_states.size() ) { m_curState = _n; const QString & _tooltip = ( m_states[m_curState].second != "" ) ? m_states[m_curState].second : m_generalToolTip; toolTip::add( this, _tooltip ); setIcon( m_states[m_curState].first ); emit changedState( m_curState ); } } void nStateButton::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && m_states.size() ) { changeState( ( ++m_curState ) % m_states.size() ); } toolButton::mousePressEvent( _me ); } #include "moc_nstate_button.cxx" lmms-1.0.0/src/gui/widgets/led_checkbox.cpp0000644000175000017500000000444412313663627017352 0ustar tobytoby/* * led_checkbox.cpp - class ledCheckBox, an improved QCheckBox * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "led_checkbox.h" #include "embed.h" #include "gui_templates.h" static const QString names[ledCheckBox::NumColors] = { "led_yellow", "led_green", "led_red" } ; ledCheckBox::ledCheckBox( const QString & _text, QWidget * _parent, const QString & _name, LedColors _color ) : automatableButton( _parent, _name ), m_text( _text ) { setCheckable( true ); if( _color >= NumColors || _color < Yellow ) { _color = Yellow; } m_ledOnPixmap = new QPixmap( embed::getIconPixmap( names[_color].toUtf8().constData() ) ); m_ledOffPixmap = new QPixmap( embed::getIconPixmap( "led_off" ) ); setFont( pointSize<7>( font() ) ); setFixedSize( m_ledOffPixmap->width() + 5 + QFontMetrics( font() ).width( text() ), m_ledOffPixmap->height() ); } ledCheckBox::~ledCheckBox() { delete m_ledOnPixmap; delete m_ledOffPixmap; } void ledCheckBox::paintEvent( QPaintEvent * ) { QPainter p( this ); p.setFont( pointSize<7>( font() ) ); if( model()->value() == true ) { p.drawPixmap( 0, 0, *m_ledOnPixmap ); } else { p.drawPixmap( 0, 0, *m_ledOffPixmap ); } p.setPen( QColor( 64, 64, 64 ) ); p.drawText( m_ledOffPixmap->width() + 4, 11, text() ); p.setPen( QColor( 255, 255, 255 ) ); p.drawText( m_ledOffPixmap->width() + 3, 10, text() ); } #include "moc_led_checkbox.cxx" lmms-1.0.0/src/gui/widgets/knob.cpp0000644000175000017500000003304212313663627015665 0ustar tobytoby/* * knob.cpp - powerful knob-widget * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #ifndef __USE_XOPEN #define __USE_XOPEN #endif #include #include "knob.h" #include "caption_menu.h" #include "config_mgr.h" #include "ControllerConnection.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "MainWindow.h" #include "ProjectJournal.h" #include "song.h" #include "string_pair_drag.h" #include "templates.h" #include "text_float.h" textFloat * knob::s_textFloat = NULL; knob::knob( int _knob_num, QWidget * _parent, const QString & _name ) : QWidget( _parent ), FloatModelView( new FloatModel( 0, 0, 0, 1, NULL, _name, true ), this ), m_knobNum( _knob_num ), m_label( "" ), m_knobPixmap( NULL ), m_volumeKnob( false ), m_volumeRatio( 100.0, 0.0, 1000000.0 ), m_buttonPressed( false ), m_angle( -10 ), m_outerColor( NULL ) { if( s_textFloat == NULL ) { s_textFloat = new textFloat; } setWindowTitle( _name ); if( m_knobNum != knobStyled ) { m_knobPixmap = new QPixmap( embed::getIconPixmap( QString( "knob0" + QString::number( m_knobNum + 1 ) ).toUtf8().constData() ) ); setFixedSize( m_knobPixmap->width(), m_knobPixmap->height() ); } setTotalAngle( 270.0f ); setInnerRadius( 1.0f ); setOuterRadius( 10.0f ); setFocusPolicy( Qt::ClickFocus ); doConnections(); } knob::~knob() { if( m_knobPixmap ) { delete m_knobPixmap; } } void knob::setLabel( const QString & _txt ) { m_label = _txt; if( m_knobPixmap ) { setFixedSize( qMax( m_knobPixmap->width(), QFontMetrics( pointSizeF( font(), 6.5) ).width( m_label ) ), m_knobPixmap->height() + 10 ); } update(); } void knob::setTotalAngle( float _angle ) { if( _angle < 10.0 ) { m_totalAngle = 10.0; } else { m_totalAngle = _angle; } update(); } float knob::innerRadius() const { return m_innerRadius; } void knob::setInnerRadius( float _r ) { m_innerRadius = _r; } float knob::outerRadius() const { return m_outerRadius; } void knob::setOuterRadius( float _r ) { m_outerRadius = _r; } QPointF knob::centerPoint() const { return m_centerPoint; } float knob::centerPointX() const { return m_centerPoint.x(); } void knob::setCenterPointX( float _c ) { m_centerPoint.setX( _c ); } float knob::centerPointY() const { return m_centerPoint.y(); } void knob::setCenterPointY( float _c ) { m_centerPoint.setY( _c ); } float knob::lineWidth() const { return m_lineWidth; } void knob::setLineWidth( float _w ) { m_lineWidth = _w; } QColor knob::outerColor() const { if( m_outerColor ) { return *m_outerColor; } else { return QColor(); } } void knob::setOuterColor( const QColor & _c ) { if( m_outerColor ) { *m_outerColor = _c; } else { m_outerColor = new QColor( _c ); } } QLineF knob::calculateLine( const QPointF & _mid, float _radius, float _innerRadius ) const { const float rarc = m_angle * M_PI / 180.0; const float ca = cos( rarc ); const float sa = -sin( rarc ); return QLineF( _mid.x() - sa*_innerRadius, _mid.y() - ca*_innerRadius, _mid.x() - sa*_radius, _mid.y() - ca*_radius ); } bool knob::updateAngle() { int angle = 0; if( model() && model()->maxValue() != model()->minValue() ) { angle = angleFromValue( model()->value(), model()->minValue(), model()->maxValue(), m_totalAngle ); } if( qAbs( angle - m_angle ) > 3 ) { m_angle = angle; return true; } return false; } void knob::drawKnob( QPainter * _p ) { if( updateAngle() == false && !m_cache.isNull() ) { _p->drawImage( 0, 0, m_cache ); return; } m_cache = QImage( size(), QImage::Format_ARGB32 ); m_cache.fill( qRgba( 0, 0, 0, 0 ) ); QPainter p( &m_cache ); QPoint mid; if( m_knobNum == knobStyled ) { p.setRenderHint( QPainter::Antialiasing ); // Perhaps this can move to setOuterRadius() if( m_outerColor ) { QRadialGradient gradient( centerPoint(), outerRadius() ); gradient.setColorAt(0.4, _p->pen().brush().color() ); gradient.setColorAt(1, *m_outerColor ); p.setPen( QPen( gradient, lineWidth(), Qt::SolidLine, Qt::RoundCap ) ); } else { QPen pen = p.pen(); pen.setWidth( (int) lineWidth() ); pen.setCapStyle( Qt::RoundCap ); p.setPen( pen ); } p.drawLine( calculateLine( centerPoint(), outerRadius(), innerRadius() ) ); p.end(); _p->drawImage( 0, 0, m_cache ); return; } // Old-skool knobs const float radius = m_knobPixmap->width() / 2.0f - 1; mid = QPoint( width() / 2, m_knobPixmap->height() / 2 ); p.drawPixmap( static_cast( width() / 2 - m_knobPixmap->width() / 2 ), 0, *m_knobPixmap ); p.setRenderHint( QPainter::Antialiasing ); const int centerAngle = angleFromValue( model()->centerValue(), model()->minValue(), model()->maxValue(), m_totalAngle ); const int arcLineWidth = 2; const int arcRectSize = m_knobPixmap->width() - arcLineWidth; QColor col; if( m_knobNum == knobVintage_32 ) { col = QApplication::palette().color( QPalette::Active, QPalette::Shadow ); } else { col = QApplication::palette().color( QPalette::Active, QPalette::WindowText ); } col.setAlpha( 70 ); p.setPen( QPen( col, 2 ) ); p.drawArc( mid.x() - arcRectSize/2, 1, arcRectSize, arcRectSize, 315*16, 16*m_totalAngle ); switch( m_knobNum ) { case knobSmall_17: { p.setPen( QPen( QApplication::palette().color( QPalette::Active, QPalette::WindowText ), 2 ) ); p.drawLine( calculateLine( mid, radius-2 ) ); break; } case knobBright_26: { p.setPen( QPen( QApplication::palette().color( QPalette::Active, QPalette::WindowText ), 2 ) ); p.drawLine( calculateLine( mid, radius-5 ) ); break; } case knobDark_28: { p.setPen( QPen( QApplication::palette().color( QPalette::Active, QPalette::WindowText ), 2 ) ); const float rb = qMax( ( radius - 10 ) / 3.0, 0.0 ); const float re = qMax( ( radius - 4 ), 0.0 ); QLineF ln = calculateLine( mid, re, rb ); ln.translate( 1, 1 ); p.drawLine( ln ); break; } case knobGreen_17: { p.setPen( QPen( QApplication::palette().color( QPalette::Active, QPalette::BrightText), 2 ) ); p.drawLine( calculateLine( mid, radius ) ); break; } case knobVintage_32: { p.setPen( QPen( QApplication::palette().color( QPalette::Active, QPalette::Shadow), 2 ) ); p.drawLine( calculateLine( mid, radius-2, 2 ) ); break; } } p.drawArc( mid.x() - arcRectSize/2, 1, arcRectSize, arcRectSize, (90-centerAngle)*16, -16*(m_angle-centerAngle) ); p.end(); _p->drawImage( 0, 0, m_cache ); } float knob::getValue( const QPoint & _p ) { float value; // arcane mathemagicks for calculating knob movement value = ( ( _p.y() + _p.y() * qMin( qAbs( _p.y() / 2.5f ), 6.0f ) ) ) / 12.0f; // if shift pressed we want slower movement if( engine::mainWindow()->isShiftPressed() ) { value /= 4.0f; value = qBound( -4.0f, value, 4.0f ); } return value * pageSize(); } void knob::contextMenuEvent( QContextMenuEvent * ) { // for the case, the user clicked right while pressing left mouse- // button, the context-menu appears while mouse-cursor is still hidden // and it isn't shown again until user does something which causes // an QApplication::restoreOverrideCursor()-call... mouseReleaseEvent( NULL ); captionMenu contextMenu( model()->displayName() ); addDefaultActions( &contextMenu ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "help" ), tr( "&Help" ), this, SLOT( displayHelp() ) ); contextMenu.exec( QCursor::pos() ); } void knob::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "float_value," "automatable_model" ); } void knob::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString val = stringPairDrag::decodeValue( _de ); if( type == "float_value" ) { model()->setValue( val.toFloat() ); _de->accept(); } else if( type == "automatable_model" ) { AutomatableModel * mod = dynamic_cast( engine::projectJournal()-> journallingObject( val.toInt() ) ); if( mod != NULL ) { AutomatableModel::linkModels( model(), mod ); mod->setValue( model()->value() ); } } } void knob::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && ! ( _me->modifiers() & Qt::ControlModifier ) && ! ( _me->modifiers() & Qt::ShiftModifier ) ) { model()->prepareJournalEntryFromOldVal(); const QPoint & p = _me->pos(); m_origMousePos = p; m_mouseOffset = QPoint(0, 0); m_leftOver = 0.0f; emit sliderPressed(); QApplication::setOverrideCursor( Qt::BlankCursor ); s_textFloat->setText( displayValue() ); s_textFloat->moveGlobal( this, QPoint( width() + 2, 0 ) ); s_textFloat->show(); m_buttonPressed = true; } else if( _me->button() == Qt::LeftButton && engine::mainWindow()->isShiftPressed() == true ) { new stringPairDrag( "float_value", QString::number( model()->value() ), QPixmap(), this ); } else { FloatModelView::mousePressEvent( _me ); } } void knob::mouseMoveEvent( QMouseEvent * _me ) { if( m_buttonPressed && _me->pos() != m_origMousePos ) { m_mouseOffset = _me->pos() - m_origMousePos; setPosition( m_mouseOffset ); emit sliderMoved( model()->value() ); QCursor::setPos( mapToGlobal( m_origMousePos ) ); } s_textFloat->setText( displayValue() ); } void knob::mouseReleaseEvent( QMouseEvent * /* _me*/ ) { model()->addJournalEntryFromOldToCurVal(); m_buttonPressed = false; emit sliderReleased(); QApplication::restoreOverrideCursor(); s_textFloat->hide(); } void knob::focusOutEvent( QFocusEvent * _fe ) { // make sure we don't loose mouse release event mouseReleaseEvent( NULL ); QWidget::focusOutEvent( _fe ); } void knob::mouseDoubleClickEvent( QMouseEvent * ) { enterValue(); } void knob::paintEvent( QPaintEvent * _me ) { QPainter p( this ); drawKnob( &p ); if( !m_label.isEmpty() ) { p.setFont( pointSizeF( p.font(), 6.5 ) ); /* p.setPen( QColor( 64, 64, 64 ) ); p.drawText( width() / 2 - p.fontMetrics().width( m_label ) / 2 + 1, height() - 1, m_label );*/ p.setPen( QColor( 255, 255, 255 ) ); p.drawText( width() / 2 - p.fontMetrics().width( m_label ) / 2, height() - 2, m_label ); } } void knob::wheelEvent( QWheelEvent * _we ) { _we->accept(); const int inc = ( _we->delta() > 0 ) ? 1 : -1; model()->incValue( inc ); s_textFloat->setText( displayValue() ); s_textFloat->moveGlobal( this, QPoint( width() + 2, 0 ) ); s_textFloat->setVisibilityTimeOut( 1000 ); emit sliderMoved( model()->value() ); } void knob::setPosition( const QPoint & _p ) { const float value = getValue( _p ) + m_leftOver; const float step = model()->step(); if( qAbs( value ) >= step ) { model()->setValue( model()->value() - value ); m_leftOver = 0.0f; } else { m_leftOver = value; } } void knob::enterValue() { bool ok; float new_val; if( isVolumeKnob() && configManager::inst()->value( "app", "displaydbv" ).toInt() ) { new_val = QInputDialog::getDouble( this, windowTitle(), tr( "Please enter a new value between " "-96.0 dBV and 6.0 dBV:" ), 20.0 * log10( model()->value() / 100.0 ), -96.0, 6.0, 4, &ok ); if( new_val <= -96.0 ) { new_val = 0.0f; } else { new_val = pow( 10.0, ( new_val / 20.0 ) ) * 100.0; } } else { new_val = QInputDialog::getDouble( this, windowTitle(), tr( "Please enter a new value between " "%1 and %2:" ). arg( model()->minValue() ). arg( model()->maxValue() ), model()->value(), model()->minValue(), model()->maxValue(), 4, &ok ); } if( ok ) { model()->setValue( new_val ); } } void knob::friendlyUpdate() { if( model()->controllerConnection() == NULL || model()->controllerConnection()->getController()->frequentUpdates() == false || Controller::runningFrames() % (256*4) == 0 ) { update(); } } QString knob::displayValue() const { if( isVolumeKnob() && configManager::inst()->value( "app", "displaydbv" ).toInt() ) { return m_description.trimmed() + QString( " %1 dBV" ). arg( 20.0 * log10( model()->value() / volumeRatio() ), 3, 'f', 2 ); } return m_description.trimmed() + QString( " %1" ). arg( model()->value() ) + m_unit; } void knob::doConnections() { if( model() != NULL ) { QObject::connect( model(), SIGNAL( dataChanged() ), this, SLOT( friendlyUpdate() ) ); QObject::connect( model(), SIGNAL( propertiesChanged() ), this, SLOT( update() ) ); } } void knob::displayHelp() { QWhatsThis::showText( mapToGlobal( rect().bottomRight() ), whatsThis() ); } #include "moc_knob.cxx" lmms-1.0.0/src/gui/widgets/fader.cpp0000644000175000017500000001645512313663627016026 0ustar tobytoby/* * fader.cpp - fader-widget used in mixer - partly taken from Hydrogen * * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ /* * Hydrogen * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net] * * http://www.hydrogen-music.org * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY, without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include "fader.h" #include "embed.h" #include "engine.h" #include "caption_menu.h" #include "config_mgr.h" #include "text_float.h" #include "MainWindow.h" textFloat * fader::s_textFloat = NULL; fader::fader( FloatModel * _model, const QString & _name, QWidget * _parent ) : QWidget( _parent ), FloatModelView( _model, this ), m_model( _model ), m_fPeakValue_L( 0.0 ), m_fPeakValue_R( 0.0 ), m_persistentPeak_L( 0.0 ), m_persistentPeak_R( 0.0 ), m_fMinPeak( 0.01f ), m_fMaxPeak( 1.1 ), m_back( embed::getIconPixmap( "fader_background" ) ), m_leds( embed::getIconPixmap( "fader_leds" ) ), m_knob( embed::getIconPixmap( "fader_knob" ) ), m_moveStartPoint( -1 ), m_startValue( 0 ) { if( s_textFloat == NULL ) { s_textFloat = new textFloat; } setWindowTitle( _name ); setAttribute( Qt::WA_OpaquePaintEvent, true ); setMinimumSize( 23, 116 ); setMaximumSize( 23, 116); resize( 23, 116 ); setModel( _model ); } fader::~fader() { } void fader::contextMenuEvent( QContextMenuEvent * _ev ) { captionMenu contextMenu( windowTitle() ); addDefaultActions( &contextMenu ); contextMenu.exec( QCursor::pos() ); _ev->accept(); } void fader::mouseMoveEvent( QMouseEvent *mouseEvent ) { if( m_moveStartPoint >= 0 ) { int dy = m_moveStartPoint - mouseEvent->globalY(); float delta = dy * ( m_model->maxValue() - m_model->minValue() ) / (float) ( height() - m_knob.height() ); model()->setValue( m_startValue + delta ); updateTextFloat(); } } void fader::mousePressEvent( QMouseEvent* mouseEvent ) { if( mouseEvent->button() == Qt::LeftButton && ! ( mouseEvent->modifiers() & Qt::ControlModifier ) ) { if( mouseEvent->y() >= knobPosY() - m_knob.height() && mouseEvent->y() < knobPosY() ) { updateTextFloat(); s_textFloat->show(); m_moveStartPoint = mouseEvent->globalY(); m_startValue = model()->value(); mouseEvent->accept(); } else { m_moveStartPoint = -1; } } else { AutomatableModelView::mousePressEvent( mouseEvent ); } } void fader::mouseDoubleClickEvent( QMouseEvent* mouseEvent ) { bool ok; // TODO: dbV handling int newValue = QInputDialog::getInteger( this, windowTitle(), tr( "Please enter a new value between %1 and %2:" ). arg( model()->minValue()*100 ). arg( model()->maxValue()*100 ), model()->value()*100, model()->minValue()*100, model()->maxValue()*100, 1, &ok ); if( ok ) { model()->setValue( newValue / 100.0f ); } } void fader::mouseReleaseEvent( QMouseEvent * _me ) { s_textFloat->hide(); } void fader::wheelEvent ( QWheelEvent *ev ) { ev->accept(); if ( ev->delta() > 0 ) { m_model->incValue( 1 ); } else { m_model->incValue( -1 ); } updateTextFloat(); s_textFloat->setVisibilityTimeOut( 1000 ); } /// /// Set peak value (0.0 .. 1.0) /// void fader::setPeak( float fPeak, float &targetPeak, float &persistentPeak, QTime &lastPeakTime ) { if( fPeak < m_fMinPeak ) { fPeak = m_fMinPeak; } else if( fPeak > m_fMaxPeak ) { fPeak = m_fMaxPeak; } if( targetPeak != fPeak) { targetPeak = fPeak; if( targetPeak >= persistentPeak ) { persistentPeak = targetPeak; lastPeakTime.restart(); } update(); } if( persistentPeak > 0 && lastPeakTime.elapsed() > 1500 ) { persistentPeak = qMax( 0, persistentPeak-0.05 ); update(); } } void fader::setPeak_L( float fPeak ) { setPeak( fPeak, m_fPeakValue_L, m_persistentPeak_L, m_lastPeakTime_L ); } void fader::setPeak_R( float fPeak ) { setPeak( fPeak, m_fPeakValue_R, m_persistentPeak_R, m_lastPeakTime_R ); } // update tooltip showing value and adjust position while changing fader value void fader::updateTextFloat() { if( configManager::inst()->value( "app", "displaydbv" ).toInt() ) { s_textFloat->setText( QString("Volume: %1 dBV"). arg( 20.0 * log10( model()->value() ), 3, 'f', 2 ) ); } else { s_textFloat->setText( QString("Volume: %1 %").arg( m_model->value() * 100 ) ); } s_textFloat->moveGlobal( this, QPoint( width() - m_knob.width() - 5, knobPosY() - 46 ) ); } inline int fader::calculateDisplayPeak( float fPeak ) { int peak = (int)( 116 - ( fPeak / ( m_fMaxPeak - m_fMinPeak ) ) * 116.0 ); if ( peak > 116 ) return 116; else return peak; } void fader::paintEvent( QPaintEvent * ev) { QPainter painter(this); // background // painter.drawPixmap( rect(), m_back, QRect( 0, 0, 23, 116 ) ); painter.drawPixmap( ev->rect(), m_back, ev->rect() ); // peak leds //float fRange = abs( m_fMaxPeak ) + abs( m_fMinPeak ); int peak_L = calculateDisplayPeak( m_fPeakValue_L - m_fMinPeak ); int persistentPeak_L = qMax( 3, calculateDisplayPeak( m_persistentPeak_L - m_fMinPeak ) ); painter.drawPixmap( QRect( 0, peak_L, 11, 116 - peak_L ), m_leds, QRect( 0, peak_L, 11, 116 - peak_L ) ); if( m_persistentPeak_L > 0.05 ) { painter.fillRect( QRect( 2, persistentPeak_L, 7, 1 ), (m_persistentPeak_L < 1.0 )? QColor( 74, 253, 133) : QColor( 255, 100, 100)); } int peak_R = calculateDisplayPeak( m_fPeakValue_R - m_fMinPeak ); int persistentPeak_R = qMax( 3, calculateDisplayPeak( m_persistentPeak_R - m_fMinPeak ) ); painter.drawPixmap( QRect( 11, peak_R, 11, 116 - peak_R ), m_leds, QRect( 11, peak_R, 11, 116 - peak_R ) ); if( m_persistentPeak_R > 0.05 ) { painter.fillRect( QRect( 14, persistentPeak_R, 7, 1 ), (m_persistentPeak_R < 1.0 )? QColor( 74, 253, 133) : QColor( 255, 100, 100)); } // knob painter.drawPixmap( 0, knobPosY() - m_knob.height(), m_knob ); } #include "moc_fader.cxx" lmms-1.0.0/src/gui/widgets/ControllerRackView.cpp0000644000175000017500000001140512313663627020512 0ustar tobytoby/* * ControllerRackView.cpp - view for song's controllers * * Copyright (c) 2008-2009 Paul Giblock * Copyright (c) 2010-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include "song.h" #include "embed.h" #include "MainWindow.h" #include "group_box.h" #include "ControllerRackView.h" #include "ControllerView.h" #include "LfoController.h" ControllerRackView::ControllerRackView( ) : QWidget() { setMinimumWidth( 250 ); setMaximumWidth( 250 ); resize( 250, 160 ); setWindowIcon( embed::getIconPixmap( "controller" ) ); setWindowTitle( tr( "Controller Rack" ) ); m_scrollArea = new QScrollArea( this ); m_scrollArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); m_scrollArea->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_scrollArea->setPalette( QApplication::palette( m_scrollArea ) ); m_scrollArea->setMinimumHeight( 64 ); m_addButton = new QPushButton( this ); m_addButton->setText( tr( "Add" ) ); QWidget * w = new QWidget(); m_scrollArea->setWidget( w ); connect( m_addButton, SIGNAL( clicked() ), this, SLOT( addController() ) ); connect( engine::getSong(), SIGNAL( dataChanged() ), this, SLOT( update() ) ); QVBoxLayout * layout = new QVBoxLayout(); layout->addWidget( m_scrollArea ); layout->addWidget( m_addButton ); this->setLayout( layout ); QMdiSubWindow * subWin = engine::mainWindow()->workspace()->addSubWindow( this ); // No maximize button Qt::WindowFlags flags = subWin->windowFlags(); flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 880, 310 ); } ControllerRackView::~ControllerRackView() { // delete scroll-area with all children delete m_scrollArea; } void ControllerRackView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); } void ControllerRackView::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); } void ControllerRackView::deleteController( ControllerView * _view ) { Controller * c = _view->getController(); int connectionCount = c->connectionCount(); if( connectionCount > 0 ) { QMessageBox msgBox; msgBox.setIcon( QMessageBox::Question ); msgBox.setWindowTitle( tr("Confirm Delete") ); msgBox.setText( tr("Confirm delete? There are existing connection(s) " "associted with this controller. There is no way to undo.") ); msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); if( msgBox.exec() != QMessageBox::Ok ) { return; } } m_controllerViews.erase( qFind( m_controllerViews.begin(), m_controllerViews.end(), _view ) ); delete _view; delete c; update(); } void ControllerRackView::update() { QWidget * w = m_scrollArea->widget(); song * s = engine::getSong(); setUpdatesEnabled( false ); int i = 0; for( i = 0; i < m_controllerViews.size(); ++i ) { delete m_controllerViews[i]; } m_controllerViews.clear(); for( i = 0; i < s->m_controllers.size(); ++i ) { ControllerView * v = new ControllerView( s->m_controllers[i], w ); connect( v, SIGNAL( deleteController( ControllerView * ) ), this, SLOT( deleteController( ControllerView * ) ), Qt::QueuedConnection ); m_controllerViews.append( v ); v->move( 0, i*32 ); v->show(); } w->setFixedSize( 210, i*32 ); setUpdatesEnabled( true ); QWidget::update(); } void ControllerRackView::addController() { // TODO: Eventually let the user pick from available controller types engine::getSong()->addController( new LfoController( engine::getSong() ) ); update(); // fix bug which always made ControllerRackView loose focus when adding // new controller setFocus(); } #include "moc_ControllerRackView.cxx" lmms-1.0.0/src/gui/widgets/fade_button.cpp0000644000175000017500000000527712313663627017237 0ustar tobytoby/* * fade_button.cpp - implementation of fade-button * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "embed.h" #include "fade_button.h" #include "update_event.h" const float FadeDuration = 300; fadeButton::fadeButton( const QColor & _normal_color, const QColor & _activated_color, QWidget * _parent ) : QAbstractButton( _parent ), m_stateTimer(), m_normalColor( _normal_color ), m_activatedColor( _activated_color ) { setAttribute( Qt::WA_OpaquePaintEvent, true ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); setFocusPolicy( Qt::NoFocus ); } fadeButton::~fadeButton() { } void fadeButton::activate() { m_stateTimer.restart(); signalUpdate(); } void fadeButton::customEvent( QEvent * ) { update(); } void fadeButton::paintEvent( QPaintEvent * _pe ) { QColor col = m_normalColor; if( m_stateTimer.elapsed() < FadeDuration ) { const float state = 1 - m_stateTimer.elapsed() / FadeDuration; const int r = (int)( m_normalColor.red() * ( 1.0f - state ) + m_activatedColor.red() * state ); const int g = (int)( m_normalColor.green() * ( 1.0f - state ) + m_activatedColor.green() * state ); const int b = (int)( m_normalColor.blue() * ( 1.0f - state ) + m_activatedColor.blue() * state ); col.setRgb( r, g, b ); QTimer::singleShot( 20, this, SLOT( update() ) ); } QPainter p( this ); p.fillRect( rect(), col ); int w = rect().right(); int h = rect().bottom(); p.setPen( m_normalColor.darker(130) ); p.drawLine( w, 1, w, h ); p.drawLine( 1, h, w, h ); p.setPen( m_normalColor.lighter(130) ); p.drawLine( 0, 0, 0, h-1 ); p.drawLine( 0, 0, w, 0 ); } void fadeButton::signalUpdate() { QApplication::postEvent( this, new updateEvent() ); } #include "moc_fade_button.cxx" lmms-1.0.0/src/gui/widgets/MidiPortMenu.cpp0000644000175000017500000000461512313663627017314 0ustar tobytoby/* * MidiPortMenu.cpp - a menu for subscribing a MidiPort to several external * MIDI ports * * Copyright (c) 2008-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "MidiPortMenu.h" #include "gui_templates.h" MidiPortMenu::MidiPortMenu( MidiPort::Modes _mode ) : ModelView( NULL, this ), m_mode( _mode ) { setFont( pointSize<9>( font() ) ); connect( this, SIGNAL( triggered( QAction * ) ), this, SLOT( activatedPort( QAction * ) ) ); } MidiPortMenu::~MidiPortMenu() { } void MidiPortMenu::modelChanged() { MidiPort * mp = castModel(); if( m_mode == MidiPort::Input ) { connect( mp, SIGNAL( readablePortsChanged() ), this, SLOT( updateMenu() ) ); } else if( m_mode == MidiPort::Output ) { connect( mp, SIGNAL( writablePortsChanged() ), this, SLOT( updateMenu() ) ); } updateMenu(); } void MidiPortMenu::activatedPort( QAction * _item ) { if( m_mode == MidiPort::Input ) { castModel()->subscribeReadablePort( _item->text(), _item->isChecked() ); } else if( m_mode == MidiPort::Output ) { castModel()->subscribeWritablePort( _item->text(), _item->isChecked() ); } } void MidiPortMenu::updateMenu() { MidiPort * mp = castModel(); const MidiPort::Map & map = ( m_mode == MidiPort::Input ) ? mp->readablePorts() : mp->writablePorts(); clear(); for( MidiPort::Map::ConstIterator it = map.begin(); it != map.end(); ++it ) { QAction * a = addAction( it.key() ); a->setCheckable( true ); a->setChecked( it.value() ); } } #include "moc_MidiPortMenu.cxx" lmms-1.0.0/src/gui/widgets/LcdWidget.cpp0000644000175000017500000001224312313663627016602 0ustar tobytoby/* * LcdWidget.cpp - a widget for displaying numbers in LCD style * * Copyright (c) 2005-2014 Tobias Doerffel * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "LcdWidget.h" #include "engine.h" #include "embed.h" #include "gui_templates.h" #include "MainWindow.h" LcdWidget::LcdWidget( int numDigits, QWidget* parent, const QString& name ) : QWidget( parent ), m_label(), m_numDigits( numDigits ) { setEnabled( true ); setWindowTitle( name ); m_lcdPixmap = new QPixmap( embed::getIconPixmap( "lcd_19green" ) ); m_cellWidth = m_lcdPixmap->size().width() / LcdWidget::charsPerPixmap; m_cellHeight = m_lcdPixmap->size().height() / 2; m_marginWidth = m_cellWidth / 2; updateSize(); } LcdWidget::LcdWidget( int numDigits, const QString& style, QWidget* parent, const QString& name ) : QWidget( parent ), m_label(), m_numDigits( numDigits ) { setEnabled( true ); setWindowTitle( name ); // We should make a factory for these or something. m_lcdPixmap = new QPixmap( embed::getIconPixmap( QString( "lcd_" + style ).toUtf8().constData() ) ); m_cellWidth = m_lcdPixmap->size().width() / LcdWidget::charsPerPixmap; m_cellHeight = m_lcdPixmap->size().height() / 2; m_marginWidth = m_cellWidth / 2; updateSize(); } LcdWidget::~LcdWidget() { delete m_lcdPixmap; } void LcdWidget::setValue( int value ) { QString s = m_textForValue[value]; if( s.isEmpty() ) { s = QString::number( value ); // TODO: if pad == true /* while( (int) s.length() < m_numDigits ) { s = "0" + s; } */ } m_display = s; update(); } void LcdWidget::paintEvent( QPaintEvent* ) { QPainter p( this ); QSize cellSize( m_cellWidth, m_cellHeight ); QRect cellRect( 0, 0, m_cellWidth, m_cellHeight ); int margin = 1; // QStyle::PM_DefaultFrameWidth; //int lcdWidth = m_cellWidth * m_numDigits + (margin*m_marginWidth)*2; // p.translate( width() / 2 - lcdWidth / 2, 0 ); p.save(); p.translate( margin, margin ); // Left Margin p.drawPixmap( cellRect, *m_lcdPixmap, QRect( QPoint( charsPerPixmap*m_cellWidth, isEnabled()?0:m_cellHeight ), cellSize ) ); p.translate( m_marginWidth, 0 ); // Padding for( int i=0; i < m_numDigits - m_display.length(); i++ ) { p.drawPixmap( cellRect, *m_lcdPixmap, QRect( QPoint( 10 * m_cellWidth, isEnabled()?0:m_cellHeight) , cellSize ) ); p.translate( m_cellWidth, 0 ); } // Digits for( int i=0; i < m_display.length(); i++ ) { int val = m_display[i].digitValue(); if( val < 0 ) { if( m_display[i] == '-' ) val = 11; else val = 10; } p.drawPixmap( cellRect, *m_lcdPixmap, QRect( QPoint( val*m_cellWidth, isEnabled()?0:m_cellHeight ), cellSize ) ); p.translate( m_cellWidth, 0 ); } // Right Margin p.drawPixmap( QRect( 0, 0, m_marginWidth-1, m_cellHeight ), *m_lcdPixmap, QRect( charsPerPixmap*m_cellWidth, isEnabled()?0:m_cellHeight, m_cellWidth / 2, m_cellHeight ) ); p.restore(); // Border QStyleOptionFrame opt; opt.initFrom( this ); opt.state = QStyle::State_Sunken; opt.rect = QRect( 0, 0, m_cellWidth * m_numDigits + (margin+m_marginWidth)*2 - 1, m_cellHeight + (margin*2) ); style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this ); p.resetTransform(); // Label if( !m_label.isEmpty() ) { p.setFont( pointSizeF( p.font(), 6.5 ) ); p.setPen( QColor( 64, 64, 64 ) ); p.drawText( width() / 2 - p.fontMetrics().width( m_label ) / 2 + 1, height(), m_label ); p.setPen( QColor( 255, 255, 255 ) ); p.drawText( width() / 2 - p.fontMetrics().width( m_label ) / 2, height() - 1, m_label ); } } void LcdWidget::setLabel( const QString & _txt ) { m_label = _txt; updateSize(); } void LcdWidget::setMarginWidth( int _width ) { m_marginWidth = _width; updateSize(); } void LcdWidget::updateSize() { int margin = 1; if (m_label.isEmpty()) { setFixedSize( m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), m_cellHeight + (2*margin) ); } else { setFixedSize( qMax( m_cellWidth * m_numDigits + 2*(margin+m_marginWidth), QFontMetrics( pointSizeF( font(), 6.5 ) ).width( m_label ) ), m_cellHeight + (2*margin) + 9 ); } update(); } #include "moc_LcdWidget.cxx" lmms-1.0.0/src/gui/widgets/EffectRackView.cpp0000644000175000017500000001346612313663627017574 0ustar tobytoby/* * EffectRackView.cpp - view for effectChain model * * Copyright (c) 2006-2007 Danny McRae * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include "EffectRackView.h" #include "EffectSelectDialog.h" #include "EffectView.h" #include "group_box.h" EffectRackView::EffectRackView( EffectChain* model, QWidget* parent ) : QWidget( parent ), ModelView( NULL, this ) { QVBoxLayout* mainLayout = new QVBoxLayout( this ); mainLayout->setMargin( 5 ); m_effectsGroupBox = new groupBox( tr( "EFFECTS CHAIN" ) ); mainLayout->addWidget( m_effectsGroupBox ); QVBoxLayout* effectsLayout = new QVBoxLayout( m_effectsGroupBox ); effectsLayout->setSpacing( 0 ); effectsLayout->setContentsMargins( 2, m_effectsGroupBox->titleBarHeight() + 2, 2, 2 ); m_scrollArea = new QScrollArea; m_scrollArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn ); m_scrollArea->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_scrollArea->setPalette( QApplication::palette( m_scrollArea ) ); m_scrollArea->setFrameStyle( QFrame::NoFrame ); m_scrollArea->setWidget( new QWidget ); effectsLayout->addWidget( m_scrollArea ); QPushButton* addButton = new QPushButton; addButton->setText( tr( "Add effect" ) ); effectsLayout->addWidget( addButton ); connect( addButton, SIGNAL( clicked() ), this, SLOT( addEffect() ) ); m_lastY = 0; setModel( model ); } EffectRackView::~EffectRackView() { clearViews(); } void EffectRackView::clearViews() { for( QVector::Iterator it = m_effectViews.begin(); it != m_effectViews.end(); ++it ) { delete *it; } m_effectViews.clear(); } void EffectRackView::moveUp( EffectView* view ) { fxChain()->moveUp( view->effect() ); if( view != m_effectViews.first() ) { int i = 0; for( QVector::Iterator it = m_effectViews.begin(); it != m_effectViews.end(); it++, i++ ) { if( *it == view ) { break; } } EffectView * temp = m_effectViews[ i - 1 ]; m_effectViews[i - 1] = view; m_effectViews[i] = temp; update(); } } void EffectRackView::moveDown( EffectView* view ) { if( view != m_effectViews.last() ) { // moving next effect up is the same moveUp( *( qFind( m_effectViews.begin(), m_effectViews.end(), view ) + 1 ) ); } } void EffectRackView::deletePlugin( EffectView* view ) { Effect * e = view->effect(); m_effectViews.erase( qFind( m_effectViews.begin(), m_effectViews.end(), view ) ); delete view; fxChain()->removeEffect( e ); e->deleteLater(); update(); } void EffectRackView::update() { QWidget * w = m_scrollArea->widget(); QVector view_map( qMax( fxChain()->m_effects.size(), m_effectViews.size() ), false ); for( QVector::Iterator it = fxChain()->m_effects.begin(); it != fxChain()->m_effects.end(); ++it ) { int i = 0; for( QVector::Iterator vit = m_effectViews.begin(); vit != m_effectViews.end(); ++vit, ++i ) { if( ( *vit )->model() == *it ) { view_map[i] = true; break; } } if( i >= m_effectViews.size() ) { EffectView * view = new EffectView( *it, w ); connect( view, SIGNAL( moveUp( EffectView * ) ), this, SLOT( moveUp( EffectView * ) ) ); connect( view, SIGNAL( moveDown( EffectView * ) ), this, SLOT( moveDown( EffectView * ) ) ); connect( view, SIGNAL( deletePlugin( EffectView * ) ), this, SLOT( deletePlugin( EffectView * ) ), Qt::QueuedConnection ); view->show(); m_effectViews.append( view ); if( i < view_map.size() ) { view_map[i] = true; } else { view_map.append( true ); } } } int i = 0, nView = 0; const int EffectViewMargin = 3; m_lastY = EffectViewMargin; for( QVector::Iterator it = m_effectViews.begin(); it != m_effectViews.end(); i++ ) { if( i < view_map.size() && view_map[i] == false ) { delete m_effectViews[nView]; it = m_effectViews.erase( it ); } else { ( *it )->move( EffectViewMargin, m_lastY ); m_lastY += ( *it )->height(); ++nView; ++it; } } w->setFixedSize( 210 + 2*EffectViewMargin, m_lastY ); QWidget::update(); } void EffectRackView::addEffect() { EffectSelectDialog esd( this ); esd.exec(); if( esd.result() == QDialog::Rejected ) { return; } Effect * fx = esd.instantiateSelectedPlugin( fxChain() ); fxChain()->m_enabledModel.setValue( true ); fxChain()->appendEffect( fx ); update(); // Find the effectView, and show the controls for( QVector::Iterator vit = m_effectViews.begin(); vit != m_effectViews.end(); ++vit ) { if( ( *vit )->effect() == fx ) { ( *vit )->editControls(); break; } } } void EffectRackView::modelChanged() { //clearViews(); m_effectsGroupBox->setModel( &fxChain()->m_enabledModel ); connect( fxChain(), SIGNAL( aboutToClear() ), this, SLOT( clearViews() ) ); update(); } #include "moc_EffectRackView.cxx" lmms-1.0.0/src/gui/widgets/InstrumentFunctionViews.cpp0000644000175000017500000001535312313663627021635 0ustar tobytoby/* * InstrumentFunctionViews.cpp - view for instrument-functions-tab * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "InstrumentFunctions.h" #include "InstrumentFunctionViews.h" #include "combobox.h" #include "embed.h" #include "engine.h" #include "group_box.h" #include "gui_templates.h" #include "knob.h" #include "pixmap_button.h" #include "TempoSyncKnob.h" #include "tooltip.h" InstrumentFunctionNoteStackingView::InstrumentFunctionNoteStackingView( InstrumentFunctionNoteStacking* cc, QWidget* parent ) : QWidget( parent ), ModelView( NULL, this ), m_cc( cc ), m_chordsGroupBox( new groupBox( tr( "STACKING" ) ) ), m_chordsComboBox( new comboBox() ), m_chordRangeKnob( new knob( knobBright_26 ) ) { QHBoxLayout* topLayout = new QHBoxLayout( this ); topLayout->setMargin( 0 ); topLayout->addWidget( m_chordsGroupBox ); QGridLayout* mainLayout = new QGridLayout( m_chordsGroupBox ); mainLayout->setContentsMargins( 8, 18, 8, 8 ); mainLayout->setColumnStretch( 0, 1 ); mainLayout->setHorizontalSpacing( 20 ); mainLayout->setVerticalSpacing( 1 ); QLabel* chordLabel = new QLabel( tr( "Chord:" ) ); chordLabel->setFont( pointSize<8>( chordLabel->font() ) ); m_chordRangeKnob->setLabel( tr( "RANGE" ) ); m_chordRangeKnob->setHintText( tr( "Chord range:" ) + " ", " " + tr( "octave(s)" ) ); m_chordRangeKnob->setWhatsThis( tr( "Use this knob for setting the chord range in octaves. " "The selected chord will be played within specified " "number of octaves." ) ); mainLayout->addWidget( chordLabel, 0, 0 ); mainLayout->addWidget( m_chordsComboBox, 1, 0 ); mainLayout->addWidget( m_chordRangeKnob, 0, 1, 2, 1, Qt::AlignHCenter ); } InstrumentFunctionNoteStackingView::~InstrumentFunctionNoteStackingView() { delete m_chordsGroupBox; } void InstrumentFunctionNoteStackingView::modelChanged() { m_cc = castModel(); m_chordsGroupBox->setModel( &m_cc->m_chordsEnabledModel ); m_chordsComboBox->setModel( &m_cc->m_chordsModel ); m_chordRangeKnob->setModel( &m_cc->m_chordRangeModel ); } InstrumentFunctionArpeggioView::InstrumentFunctionArpeggioView( InstrumentFunctionArpeggio* arp, QWidget* parent ) : QWidget( parent ), ModelView( NULL, this ), m_a( arp ), m_arpGroupBox( new groupBox( tr( "ARPEGGIO" ) ) ), m_arpComboBox( new comboBox() ), m_arpRangeKnob( new knob( knobBright_26 ) ), m_arpTimeKnob( new TempoSyncKnob( knobBright_26 ) ), m_arpGateKnob( new knob( knobBright_26 ) ), m_arpDirectionComboBox( new comboBox() ), m_arpModeComboBox( new comboBox() ) { QHBoxLayout* topLayout = new QHBoxLayout( this ); topLayout->setMargin( 0 ); topLayout->addWidget( m_arpGroupBox ); QGridLayout* mainLayout = new QGridLayout( m_arpGroupBox ); mainLayout->setContentsMargins( 8, 18, 8, 8 ); mainLayout->setColumnStretch( 0, 1 ); mainLayout->setHorizontalSpacing( 20 ); mainLayout->setVerticalSpacing( 1 ); m_arpGroupBox->setWhatsThis( tr( "An arpeggio is a method playing (especially plucked) " "instruments, which makes the music much livelier. " "The strings of such instruments (e.g. harps) are " "plucked like chords. The only difference is that " "this is done in a sequential order, so the notes are " "not played at the same time. Typical arpeggios are " "major or minor triads, but there are a lot of other " "possible chords, you can select." ) ); m_arpRangeKnob->setLabel( tr( "RANGE" ) ); m_arpRangeKnob->setHintText( tr( "Arpeggio range:" ) + " ", " " + tr( "octave(s)" ) ); m_arpRangeKnob->setWhatsThis( tr( "Use this knob for setting the arpeggio range in octaves. " "The selected arpeggio will be played within specified " "number of octaves." ) ); m_arpTimeKnob->setLabel( tr( "TIME" ) ); m_arpTimeKnob->setHintText( tr( "Arpeggio time:" ) + " ", " " + tr( "ms" ) ); m_arpTimeKnob->setWhatsThis( tr( "Use this knob for setting the arpeggio time in " "milliseconds. The arpeggio time specifies how long " "each arpeggio-tone should be played." ) ); m_arpGateKnob->setLabel( tr( "GATE" ) ); m_arpGateKnob->setHintText( tr( "Arpeggio gate:" ) + " ", tr( "%" ) ); m_arpGateKnob->setWhatsThis( tr( "Use this knob for setting the arpeggio gate. The " "arpeggio gate specifies the percent of a whole " "arpeggio-tone that should be played. With this you " "can make cool staccato arpeggios." ) ); QLabel* arpChordLabel = new QLabel( tr( "Chord:" ) ); arpChordLabel->setFont( pointSize<8>( arpChordLabel->font() ) ); QLabel* arpDirectionLabel = new QLabel( tr( "Direction:" ) ); arpDirectionLabel->setFont( pointSize<8>( arpDirectionLabel->font() ) ); QLabel* arpModeLabel = new QLabel( tr( "Mode:" ) ); arpModeLabel->setFont( pointSize<8>( arpModeLabel->font() ) ); mainLayout->addWidget( arpChordLabel, 0, 0 ); mainLayout->addWidget( m_arpComboBox, 1, 0 ); mainLayout->addWidget( arpDirectionLabel, 3, 0 ); mainLayout->addWidget( m_arpDirectionComboBox, 4, 0 ); mainLayout->addWidget( arpModeLabel, 6, 0 ); mainLayout->addWidget( m_arpModeComboBox, 7, 0 ); mainLayout->addWidget( m_arpRangeKnob, 0, 1, 2, 1, Qt::AlignHCenter ); mainLayout->addWidget( m_arpTimeKnob, 3, 1, 2, 1, Qt::AlignHCenter ); mainLayout->addWidget( m_arpGateKnob, 6, 1, 2, 1, Qt::AlignHCenter ); mainLayout->setRowMinimumHeight( 2, 10 ); mainLayout->setRowMinimumHeight( 5, 10 ); } InstrumentFunctionArpeggioView::~InstrumentFunctionArpeggioView() { delete m_arpGroupBox; } void InstrumentFunctionArpeggioView::modelChanged() { m_a = castModel(); m_arpGroupBox->setModel( &m_a->m_arpEnabledModel ); m_arpComboBox->setModel( &m_a->m_arpModel ); m_arpRangeKnob->setModel( &m_a->m_arpRangeModel ); m_arpTimeKnob->setModel( &m_a->m_arpTimeModel ); m_arpGateKnob->setModel( &m_a->m_arpGateModel ); m_arpDirectionComboBox->setModel( &m_a->m_arpDirectionModel ); m_arpModeComboBox->setModel( &m_a->m_arpModeModel ); } #include "moc_InstrumentFunctionViews.cxx" lmms-1.0.0/src/gui/widgets/tooltip.cpp0000644000175000017500000000227612313663627016433 0ustar tobytoby/* * tooltip.cpp - namespace toolTip, a tooltip-wrapper for LMMS * * Copyright (c) 2005-2006 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "tooltip.h" #include "config_mgr.h" void toolTip::add( QWidget * _w, const QString & _txt ) { if( !configManager::inst()->value( "tooltips", "disabled" ).toInt() ) { _w->setToolTip( _txt ); } } lmms-1.0.0/src/gui/export_project_dialog.cpp0000644000175000017500000001763212313663627017663 0ustar tobytoby/* * export_project_dialog.cpp - implementation of dialog for exporting project * * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "export_project_dialog.h" #include "song.h" #include "engine.h" #include "MainWindow.h" #include "bb_track_container.h" #include "bb_track.h" exportProjectDialog::exportProjectDialog( const QString & _file_name, QWidget * _parent, bool multi_export=false ) : QDialog( _parent ), Ui::ExportProjectDialog(), m_fileName( _file_name ), m_fileExtension(), m_multiExport( multi_export ), m_activeRenderer( NULL ) { setupUi( this ); setWindowTitle( tr( "Export project to %1" ).arg( QFileInfo( _file_name ).fileName() ) ); // get the extension of the chosen file QStringList parts = _file_name.split( '.' ); QString fileExt; if( parts.size() > 0 ) { fileExt = "." + parts[parts.size()-1]; } int cbIndex = 0; for( int i = 0; i < ProjectRenderer::NumFileFormats; ++i ) { if( __fileEncodeDevices[i].m_getDevInst != NULL ) { // get the extension of this format QString renderExt = __fileEncodeDevices[i].m_extension; // add to combo box fileFormatCB->addItem( ProjectRenderer::tr( __fileEncodeDevices[i].m_description ) ); // if this is our extension, select it if( QString::compare( renderExt, fileExt, Qt::CaseInsensitive ) == 0 ) { fileFormatCB->setCurrentIndex( cbIndex ); } cbIndex++; } } connect( startButton, SIGNAL( clicked() ), this, SLOT( startBtnClicked() ) ); } exportProjectDialog::~exportProjectDialog() { for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { delete (*it); } } void exportProjectDialog::reject() { for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { (*it)->abortProcessing(); } if( m_activeRenderer ) { m_activeRenderer->abortProcessing(); } QDialog::reject(); } void exportProjectDialog::accept() { // If more to render, kick off next render job if( m_renderers.isEmpty() == false ) { popRender(); } else { // If done, then reset mute states while( m_unmuted.isEmpty() == false ) { track* restoreTrack = m_unmuted.back(); m_unmuted.pop_back(); restoreTrack->setMuted( false ); } QDialog::accept(); } } void exportProjectDialog::closeEvent( QCloseEvent * _ce ) { for( RenderVector::ConstIterator it = m_renderers.begin(); it != m_renderers.end(); ++it ) { if( (*it)->isRunning() ) { (*it)->abortProcessing(); } } if( m_activeRenderer && m_activeRenderer->isRunning() ) { m_activeRenderer->abortProcessing(); } QDialog::closeEvent( _ce ); } void exportProjectDialog::popRender() { if( m_multiExport && m_tracksToRender.isEmpty() == false ) { track* renderTrack = m_tracksToRender.back(); m_tracksToRender.pop_back(); // Set must states for song tracks for( TrackVector::ConstIterator it = m_unmuted.begin(); it != m_unmuted.end(); ++it ) { if( (*it) == renderTrack ) { (*it)->setMuted( false ); } else { (*it)->setMuted( true ); } } } // Pop next render job and start m_activeRenderer = m_renderers.back(); m_renderers.pop_back(); render( m_activeRenderer ); } void exportProjectDialog::multiRender() { m_dirName = m_fileName; QString path = QDir(m_fileName).filePath("text.txt"); int x = 1; const TrackContainer::TrackList & tl = engine::getSong()->tracks(); // Check for all unmuted tracks. Remember list. for( TrackContainer::TrackList::ConstIterator it = tl.begin(); it != tl.end(); ++it ) { track* tk = (*it); track::TrackTypes type = tk->type(); // Don't mute automation tracks if ( tk->isMuted() == false && ( type == track::InstrumentTrack || type == track::SampleTrack ) ) { m_unmuted.push_back(tk); QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension ); m_fileName = QDir(m_dirName).filePath(name); prepRender(); } else if (! tk->isMuted() && type == track::BBTrack ) { m_unmutedBB.push_back(tk); } } const TrackContainer::TrackList t2 = engine::getBBTrackContainer()->tracks(); for( TrackContainer::TrackList::ConstIterator it = t2.begin(); it != t2.end(); ++it ) { track* tk = (*it); if ( tk->isMuted() == false ) { m_unmuted.push_back(tk); QString nextName = tk->name(); nextName = nextName.remove(QRegExp("[^a-zA-Z]")); QString name = QString( "%1_%2%3" ).arg( x++ ).arg( nextName ).arg( m_fileExtension ); m_fileName = QDir(m_dirName).filePath(name); prepRender(); } } m_tracksToRender = m_unmuted; popRender(); } ProjectRenderer* exportProjectDialog::prepRender() { Mixer::qualitySettings qs = Mixer::qualitySettings( static_cast(interpolationCB->currentIndex()), static_cast(oversamplingCB->currentIndex()), sampleExactControllersCB->isChecked(), aliasFreeOscillatorsCB->isChecked() ); ProjectRenderer::OutputSettings os = ProjectRenderer::OutputSettings( samplerateCB->currentText().section(" ", 0, 0).toUInt(), false, bitrateCB->currentText().section(" ", 0, 0).toUInt(), static_cast( depthCB->currentIndex() ) ); engine::getSong()->setExportLoop( exportLoopCB->isChecked() ); ProjectRenderer* renderer = new ProjectRenderer( qs, os, m_ft, m_fileName ); m_renderers.push_back(renderer); return renderer; } void exportProjectDialog::render( ProjectRenderer* renderer ) { if( renderer->isReady() ) { connect( renderer, SIGNAL( progressChanged( int ) ), progressBar, SLOT( setValue( int ) ) ); connect( renderer, SIGNAL( progressChanged( int ) ), this, SLOT( updateTitleBar( int ) )) ; connect( renderer, SIGNAL( finished() ), this, SLOT( accept() ) ); connect( renderer, SIGNAL( finished() ), engine::mainWindow(), SLOT( resetWindowTitle() ) ); renderer->startProcessing(); } else { accept(); } } void exportProjectDialog::startBtnClicked() { m_ft = ProjectRenderer::NumFileFormats; for( int i = 0; i < ProjectRenderer::NumFileFormats; ++i ) { if( fileFormatCB->currentText() == ProjectRenderer::tr( __fileEncodeDevices[i].m_description ) ) { m_ft = __fileEncodeDevices[i].m_fileFormat; m_fileExtension = QString( QLatin1String( __fileEncodeDevices[i].m_extension ) ); break; } } if( m_ft == ProjectRenderer::NumFileFormats ) { QMessageBox::information( this, tr( "Error" ), tr( "Error while determining file-encoder device. " "Please try to choose a different output " "format." ) ); reject(); return; } startButton->setEnabled( false ); progressBar->setEnabled( true ); updateTitleBar( 0 ); if (m_multiExport==true) { multiRender(); } else { prepRender(); popRender(); } } void exportProjectDialog::updateTitleBar( int _prog ) { engine::mainWindow()->setWindowTitle( tr( "Rendering: %1%" ).arg( _prog ) ); } #include "moc_export_project_dialog.cxx" /* vim: set tw=0 noexpandtab: */ lmms-1.0.0/src/gui/embed.cpp0000644000175000017500000000444312313663627014345 0ustar tobytoby/* * embed.cpp - misc stuff for using embedded resources (linked into binary) * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "embed.h" #include "config_mgr.h" #ifndef PLUGIN_NAME namespace embed #else namespace PLUGIN_NAME #endif { #include "embedded_resources.h" QPixmap getIconPixmap( const char * _name, int _w, int _h ) { if( _w == -1 || _h == -1 ) { QString name = QString( _name ) + ".png"; #ifdef PLUGIN_NAME QPixmap p( configManager::inst()->artworkDir() + "plugins/" + STRINGIFY( PLUGIN_NAME ) + "_" + name ); if( p.isNull() ) { p = QPixmap( configManager::inst()->artworkDir() + name ); } #else // look whether icon is in artwork-dir QPixmap p( configManager::inst()->artworkDir() + name ); #endif if( p.isNull() ) { // nothing found, so look in default-artwork-dir p = QPixmap( configManager::inst()->defaultArtworkDir() + name ); } if( p.isNull() ) { const embed::descriptor & e = findEmbeddedData( name.toUtf8().constData() ); // found? if( QString( e.name ) == name ) { p.loadFromData( e.data, e.size ); } else { p = QPixmap( 1, 1 ); } } return p; } return getIconPixmap( _name ). scaled( _w, _h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); } QString getText( const char * _name ) { const embed::descriptor & e = findEmbeddedData( _name ); return QString::fromUtf8( (const char *) e.data, e.size ); } } lmms-1.0.0/src/gui/ControllerConnectionDialog.cpp0000644000175000017500000002604012313663627020551 0ustar tobytoby/* * ControllerConnectionDialog.cpp - dialog allowing the user to create and * modify links between controllers and models * * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include "ControllerConnectionDialog.h" #include "ControllerConnection.h" #include "MidiController.h" #include "MidiClient.h" #include "MidiPortMenu.h" #include "LcdSpinBox.h" #include "led_checkbox.h" #include "combobox.h" #include "tab_widget.h" #include "group_box.h" #include "song.h" #include "tool_button.h" #include "gui_templates.h" #include "embed.h" class AutoDetectMidiController : public MidiController { public: AutoDetectMidiController( Model* parent ) : MidiController( parent ), m_detectedMidiChannel( 0 ), m_detectedMidiController( 0 ) { updateName(); } virtual ~AutoDetectMidiController() { } virtual void processInEvent( const MidiEvent& event, const MidiTime& time ) { if( event.type() == MidiControlChange && ( m_midiPort.inputChannel() == 0 || m_midiPort.inputChannel() == event.channel() + 1 ) ) { m_detectedMidiChannel = event.channel() + 1; m_detectedMidiController = event.controllerNumber() + 1; m_detectedMidiPort = engine::mixer()->midiClient()->sourcePortName( event ); emit valueChanged(); } } // Would be a nice copy ctor, but too hard to add copy ctor because // model has none. MidiController* copyToMidiController( Model* parent ) { MidiController* c = new MidiController( parent ); c->m_midiPort.setInputChannel( m_midiPort.inputChannel() ); c->m_midiPort.setInputController( m_midiPort.inputController() ); c->subscribeReadablePorts( m_midiPort.readablePorts() ); c->updateName(); return c; } void useDetected() { m_midiPort.setInputChannel( m_detectedMidiChannel ); m_midiPort.setInputController( m_detectedMidiController ); const MidiPort::Map& map = m_midiPort.readablePorts(); for( MidiPort::Map::ConstIterator it = map.begin(); it != map.end(); ++it ) { m_midiPort.subscribeReadablePort( it.key(), m_detectedMidiPort.isEmpty() || ( it.key() == m_detectedMidiPort ) ); } } public slots: void reset() { m_midiPort.setInputChannel( 0 ); m_midiPort.setInputController( 0 ); } private: int m_detectedMidiChannel; int m_detectedMidiController; QString m_detectedMidiPort; } ; ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent, const AutomatableModel * _target_model ) : QDialog( _parent ), m_readablePorts( NULL ), m_midiAutoDetect( false ), m_controller( NULL ), m_targetModel( _target_model ), m_midiController( NULL ) { setWindowIcon( embed::getIconPixmap( "setup_audio" ) ); setWindowTitle( tr( "Connection Settings" ) ); setModal( true ); // Midi stuff m_midiGroupBox = new groupBox( tr( "MIDI CONTROLLER" ), this ); m_midiGroupBox->setGeometry( 8, 10, 240, 80 ); connect( m_midiGroupBox->model(), SIGNAL( dataChanged() ), this, SLOT( midiToggled() ) ); m_midiChannelSpinBox = new LcdSpinBox( 2, m_midiGroupBox, tr( "Input channel" ) ); m_midiChannelSpinBox->addTextForValue( 0, "--" ); m_midiChannelSpinBox->setLabel( tr( "CHANNEL" ) ); m_midiChannelSpinBox->move( 8, 24 ); m_midiControllerSpinBox = new LcdSpinBox( 3, m_midiGroupBox, tr( "Input controller" ) ); m_midiControllerSpinBox->addTextForValue( 0, "---" ); m_midiControllerSpinBox->setLabel( tr( "CONTROLLER" ) ); m_midiControllerSpinBox->move( 68, 24 ); m_midiAutoDetectCheckBox = new ledCheckBox( tr("Auto Detect"), m_midiGroupBox, tr("Auto Detect") ); m_midiAutoDetectCheckBox->setModel( &m_midiAutoDetect ); m_midiAutoDetectCheckBox->move( 8, 60 ); connect( &m_midiAutoDetect, SIGNAL( dataChanged() ), this, SLOT( autoDetectToggled() ) ); // when using with non-raw-clients we can provide buttons showing // our port-menus when being clicked if( !engine::mixer()->midiClient()->isRaw() ) { m_readablePorts = new MidiPortMenu( MidiPort::Input ); connect( m_readablePorts, SIGNAL( triggered( QAction * ) ), this, SLOT( enableAutoDetect( QAction * ) ) ); toolButton * rp_btn = new toolButton( m_midiGroupBox ); rp_btn->setText( tr( "MIDI-devices to receive " "MIDI-events from" ) ); rp_btn->setIcon( embed::getIconPixmap( "piano" ) ); rp_btn->setGeometry( 160, 24, 32, 32 ); rp_btn->setMenu( m_readablePorts ); rp_btn->setPopupMode( QToolButton::InstantPopup ); } // User stuff m_userGroupBox = new groupBox( tr( "USER CONTROLLER" ), this ); m_userGroupBox->setGeometry( 8, 100, 240, 60 ); connect( m_userGroupBox->model(), SIGNAL( dataChanged() ), this, SLOT( userToggled() ) ); m_userController = new comboBox( m_userGroupBox, "Controller" ); m_userController->setGeometry( 10, 24, 200, 22 ); for( int i = 0; i < engine::getSong()->controllers().size(); ++i ) { Controller * c = engine::getSong()->controllers().at( i ); m_userController->model()->addItem( c->name() ); } // Mapping functions m_mappingBox = new tabWidget( tr( "MAPPING FUNCTION" ), this ); m_mappingBox->setGeometry( 8, 170, 240, 64 ); m_mappingFunction = new QLineEdit( m_mappingBox ); m_mappingFunction->setGeometry( 10, 20, 170, 16 ); m_mappingFunction->setText( "input" ); m_mappingFunction->setReadOnly( true ); // Buttons QWidget * buttons = new QWidget( this ); buttons->setGeometry( 8, 240, 240, 32 ); QHBoxLayout * btn_layout = new QHBoxLayout( buttons ); btn_layout->setSpacing( 0 ); btn_layout->setMargin( 0 ); QPushButton * select_btn = new QPushButton( embed::getIconPixmap( "add" ), tr( "OK" ), buttons ); connect( select_btn, SIGNAL( clicked() ), this, SLOT( selectController() ) ); QPushButton * cancel_btn = new QPushButton( embed::getIconPixmap( "cancel" ), tr( "Cancel" ), buttons ); connect( cancel_btn, SIGNAL( clicked() ), this, SLOT( reject() ) ); btn_layout->addStretch(); btn_layout->addSpacing( 10 ); btn_layout->addWidget( select_btn ); btn_layout->addSpacing( 10 ); btn_layout->addWidget( cancel_btn ); btn_layout->addSpacing( 10 ); setFixedSize( 256, 280 ); // Crazy MIDI View stuff // TODO, handle by making this a model for the Dialog "view" ControllerConnection * cc = NULL; if( m_targetModel ) { cc = m_targetModel->controllerConnection(); if( cc && cc->getController()->type() != Controller::DummyController && engine::getSong() ) { if ( cc->getController()->type() == Controller::MidiController ) { m_midiGroupBox->model()->setValue( true ); // ensure controller is created midiToggled(); MidiController * cont = (MidiController*)( cc->getController() ); m_midiChannelSpinBox->model()->setValue( cont->m_midiPort.inputChannel() ); m_midiControllerSpinBox->model()->setValue( cont->m_midiPort.inputController() ); m_midiController->subscribeReadablePorts( static_cast( cc->getController() )->m_midiPort.readablePorts() ); } else { int idx = engine::getSong()->controllers().indexOf( cc->getController() ); if( idx >= 0 ) { m_userGroupBox->model()->setValue( true ); m_userController->model()->setValue( idx ); } } } } if( !cc ) { m_midiGroupBox->model()->setValue( true ); } show(); } ControllerConnectionDialog::~ControllerConnectionDialog() { delete m_readablePorts; delete m_midiController; } void ControllerConnectionDialog::selectController() { // Midi if( m_midiGroupBox->model()->value() > 0 ) { if( m_midiControllerSpinBox->model()->value() > 0 ) { MidiController * mc; mc = m_midiController->copyToMidiController( engine::getSong() ); /* if( m_targetModel->getTrack() && !m_targetModel->getTrack()->displayName().isEmpty() ) { mc->m_midiPort.setName( QString( "%1 (%2)" ). arg( m_targetModel->getTrack()->displayName() ). arg( m_targetModel->displayName() ) ); } else { mc->m_midiPort.setName( m_targetModel->displayName() ); } */ mc->m_midiPort.setName( m_targetModel->fullDisplayName() ); m_controller = mc; } } // User else { if( m_userGroupBox->model()->value() > 0 && engine::getSong()->controllers().size() ) { m_controller = engine::getSong()->controllers().at( m_userController->model()->value() ); } if( m_controller && m_controller->hasModel( m_targetModel ) ) { QMessageBox::warning(this, tr("LMMS"), tr("Cycle Detected.")); return; } } accept(); } void ControllerConnectionDialog::midiToggled() { int enabled = m_midiGroupBox->model()->value(); if( enabled != 0 ) { if( m_userGroupBox->model()->value() != 0 ) { m_userGroupBox->model()->setValue( 0 ); } if( !m_midiController ) { m_midiController = new AutoDetectMidiController( engine::getSong() ); MidiPort::Map map = m_midiController->m_midiPort.readablePorts(); for( MidiPort::Map::Iterator it = map.begin(); it != map.end(); ++it ) { it.value() = true; } m_midiController->subscribeReadablePorts( map ); m_midiChannelSpinBox->setModel( &m_midiController->m_midiPort.m_inputChannelModel ); m_midiControllerSpinBox->setModel( &m_midiController->m_midiPort.m_inputControllerModel ); if( m_readablePorts ) { m_readablePorts->setModel( &m_midiController->m_midiPort ); } connect( m_midiController, SIGNAL( valueChanged() ), this, SLOT( midiValueChanged() ) ); } } m_midiAutoDetect.setValue( enabled ); m_midiChannelSpinBox->setEnabled( enabled ); m_midiControllerSpinBox->setEnabled( enabled ); m_midiAutoDetectCheckBox->setEnabled( enabled ); } void ControllerConnectionDialog::userToggled() { int enabled = m_userGroupBox->model()->value(); if( enabled != 0 && m_midiGroupBox->model()->value() != 0 ) { m_midiGroupBox->model()->setValue( 0 ); } m_userController->setEnabled( enabled ); } void ControllerConnectionDialog::autoDetectToggled() { if( m_midiAutoDetect.value() ) { m_midiController->reset(); } } void ControllerConnectionDialog::midiValueChanged() { if( m_midiAutoDetect.value() ) { m_midiController->useDetected(); if( m_readablePorts ) { m_readablePorts->updateMenu(); } } } void ControllerConnectionDialog::enableAutoDetect( QAction * _a ) { if( _a->isChecked() ) { m_midiAutoDetectCheckBox->model()->setValue( true ); } } #include "moc_ControllerConnectionDialog.cxx" lmms-1.0.0/src/gui/plugin_browser.cpp0000644000175000017500000001171712313663627016334 0ustar tobytoby/* * plugin_browser.cpp - implementation of the plugin-browser * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "plugin_browser.h" #include "embed.h" #include "debug.h" #include "templates.h" #include "gui_templates.h" #include "string_pair_drag.h" bool pluginBefore( const Plugin::Descriptor& d1, const Plugin::Descriptor& d2 ) { return qstricmp( d1.displayName, d2.displayName ) < 0 ? true : false; } pluginBrowser::pluginBrowser( QWidget * _parent ) : SideBarWidget( tr( "Instrument plugins" ), embed::getIconPixmap( "plugins" ).transformed( QTransform().rotate( 90 ) ), _parent ) { setWindowTitle( tr( "Instrument browser" ) ); m_view = new QWidget( contentParent() ); //m_view->setFrameShape( QFrame::NoFrame ); addContentWidget( m_view ); QVBoxLayout * view_layout = new QVBoxLayout( m_view ); view_layout->setMargin( 5 ); view_layout->setSpacing( 5 ); QLabel * hint = new QLabel( tr( "Drag an instrument " "into either the Song-Editor, the " "Beat+Bassline Editor or into an " "existing instrument track." ), m_view ); hint->setFont( pointSize<8>( hint->font() ) ); hint->setWordWrap( true ); view_layout->addWidget( hint ); Plugin::getDescriptorsOfAvailPlugins( m_pluginDescriptors ); qSort( m_pluginDescriptors.begin(), m_pluginDescriptors.end(), pluginBefore ); for( Plugin::DescriptorList::ConstIterator it = m_pluginDescriptors.begin(); it != m_pluginDescriptors.end(); ++it ) { if( it->type == Plugin::Instrument ) { pluginDescWidget * p = new pluginDescWidget( *it, m_view ); p->show(); view_layout->addWidget( p ); } } view_layout->addStretch(); show(); } pluginBrowser::~pluginBrowser() { } pluginDescWidget::pluginDescWidget( const Plugin::Descriptor & _pd, QWidget * _parent ) : QWidget( _parent ), m_updateTimer( this ), m_pluginDescriptor( _pd ), m_logo( _pd.logo->pixmap() ), m_mouseOver( false ), m_targetHeight( 24 ) { connect( &m_updateTimer, SIGNAL( timeout() ), SLOT( updateHeight() ) ); setFixedHeight( m_targetHeight ); setMouseTracking( true ); setCursor( Qt::PointingHandCursor ); } pluginDescWidget::~pluginDescWidget() { } void pluginDescWidget::paintEvent( QPaintEvent * ) { const QColor fill_color = m_mouseOver ? QColor( 224, 224, 224 ) : QColor( 192, 192, 192 ); QPainter p( this ); p.fillRect( rect(), fill_color ); const int s = 16 + ( 32 * ( tLimit( height(), 24, 60 ) - 24 ) ) / ( 60 - 24 ); const QSize logo_size( s, s ); QPixmap logo = m_logo.scaled( logo_size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); p.setPen( QColor( 64, 64, 64 ) ); p.drawRect( 0, 0, rect().right(), rect().bottom() ); p.drawPixmap( 4, 4, logo ); QFont f = pointSize<8>( p.font() ); f.setBold( true ); p.setFont( f ); p.drawText( 10 + logo_size.width(), 15, m_pluginDescriptor.displayName ); if( height() > 24 || m_mouseOver ) { f.setBold( false ); p.setFont( pointSize<8>( f ) ); QRect br; p.drawText( 10 + logo_size.width(), 20, width() - 58 - 5, 999, Qt::TextWordWrap, pluginBrowser::tr( m_pluginDescriptor.description ), &br ); if( m_mouseOver ) { m_targetHeight = qMax( 60, 25 + br.height() ); } } } void pluginDescWidget::enterEvent( QEvent * _e ) { m_mouseOver = true; m_targetHeight = height() + 1; updateHeight(); QWidget::enterEvent( _e ); } void pluginDescWidget::leaveEvent( QEvent * _e ) { m_mouseOver = false; m_targetHeight = 24; updateHeight(); QWidget::leaveEvent( _e ); } void pluginDescWidget::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton ) { new stringPairDrag( "instrument", m_pluginDescriptor.name, m_logo, this ); leaveEvent( _me ); } } void pluginDescWidget::updateHeight() { if( m_targetHeight > height() ) { setFixedHeight( height() + 1 ); } else if( m_targetHeight < height() ) { setFixedHeight( height() - 1 ); } else { m_updateTimer.stop(); return; } if( !m_updateTimer.isActive() ) { m_updateTimer.start( 15 ); } } #include "moc_plugin_browser.cxx" lmms-1.0.0/src/gui/InstrumentView.cpp0000644000175000017500000000365212313663627016275 0ustar tobytoby/* * InstrumentView.cpp - base-class for views of all Instruments * * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "InstrumentView.h" #include "embed.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "string_pair_drag.h" InstrumentView::InstrumentView( Instrument * _Instrument, QWidget * _parent ) : PluginView( _Instrument, _parent ) { setModel( _Instrument ); setFixedSize( 250, 250 ); setAttribute( Qt::WA_DeleteOnClose, TRUE ); } InstrumentView::~InstrumentView() { if( instrumentTrackWindow() ) { instrumentTrackWindow()->m_instrumentView = NULL; } } void InstrumentView::setModel( Model * _model, bool ) { if( dynamic_cast( _model ) != NULL ) { ModelView::setModel( _model ); instrumentTrackWindow()->setWindowIcon( model()->descriptor()->logo->pixmap() ); connect( model(), SIGNAL( destroyed( QObject * ) ), this, SLOT( close() ) ); } } InstrumentTrackWindow * InstrumentView::instrumentTrackWindow( void ) { return( dynamic_cast( parentWidget()->parentWidget() ) ); } lmms-1.0.0/src/gui/ToolPluginView.cpp0000644000175000017500000000300412313663627016210 0ustar tobytoby/* * ToolPluginView.cpp - implementation of ToolPluginView * * Copyright (c) 2006-2008 Javier Serrano Polo * Copyright (c) 2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "ToolPlugin.h" #include "ToolPluginView.h" #include #include #include "embed.h" #include "engine.h" #include "MainWindow.h" ToolPluginView::ToolPluginView( ToolPlugin * _toolPlugin ) : PluginView( _toolPlugin, NULL ) { engine::mainWindow()->workspace()->addSubWindow( this ); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); setWindowTitle( _toolPlugin->displayName() ); setWindowIcon( _toolPlugin->descriptor()->logo->pixmap() ); } lmms-1.0.0/src/gui/FxMixerView.cpp0000644000175000017500000002140512313663627015503 0ustar tobytoby/* * FxMixerView.cpp - effect-mixer-view for LMMS * * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include "FxMixerView.h" #include "fader.h" #include "EffectRackView.h" #include "engine.h" #include "embed.h" #include "MainWindow.h" #include "LcdWidget.h" #include "gui_templates.h" #include "tooltip.h" #include "pixmap_button.h" class FxLine : public QWidget { public: FxLine( QWidget * _parent, FxMixerView * _mv, QString & _name ) : QWidget( _parent ), m_mv( _mv ), m_name( _name ) { setFixedSize( 32, 232 ); setAttribute( Qt::WA_OpaquePaintEvent, true ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); } virtual void paintEvent( QPaintEvent * ) { QPainter p( this ); QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Background ); QColor sh_color = QApplication::palette().color( QPalette::Active, QPalette::Shadow ); QColor te_color = QApplication::palette().color( QPalette::Active, QPalette::Text ); QColor bt_color = QApplication::palette().color( QPalette::Active, QPalette::BrightText ); p.fillRect( rect(), m_mv->currentFxLine() == this ? bg_color.lighter(130) : bg_color ); p.setPen( bg_color.darker(130) ); p.drawRect( 0, 0, width()-2, height()-2 ); p.setPen( bg_color.lighter(150) ); p.drawRect( 1, 1, width()-2, height()-2 ); p.setPen( m_mv->currentFxLine() == this ? sh_color : bg_color.darker(130) ); p.drawRect( 0, 0, width()-1, height()-1 ); p.rotate( -90 ); p.setFont( pointSizeF( font(), 7.5f ) ); p.setPen( sh_color ); p.drawText( -91, 21, m_name ); p.setPen( m_mv->currentFxLine() == this ? bt_color : te_color ); p.drawText( -90, 20, m_name ); } virtual void mousePressEvent( QMouseEvent * ) { m_mv->setCurrentFxLine( this ); } virtual void mouseDoubleClickEvent( QMouseEvent * ) { bool ok; QString new_name = QInputDialog::getText( this, FxMixerView::tr( "Rename FX channel" ), FxMixerView::tr( "Enter the new name for this " "FX channel" ), QLineEdit::Normal, m_name, &ok ); if( ok && !new_name.isEmpty() ) { m_name = new_name; update(); } } private: FxMixerView * m_mv; QString & m_name; } ; FxMixerView::FxMixerView() : QWidget(), ModelView( NULL, this ), SerializingObjectHook() { FxMixer * m = engine::fxMixer(); m->setHook( this ); /* QPalette pal = palette(); pal.setColor( QPalette::Background, QColor( 72, 76, 88 ) ); setPalette( pal );*/ setAutoFillBackground( true ); setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Minimum ); setWindowTitle( tr( "FX-Mixer" ) ); setWindowIcon( embed::getIconPixmap( "fx_mixer" ) ); m_fxLineBanks = new QStackedLayout; m_fxLineBanks->setSpacing( 0 ); m_fxLineBanks->setMargin( 1 ); m_fxRacksLayout = new QStackedLayout; m_fxRacksLayout->setSpacing( 0 ); m_fxRacksLayout->setMargin( 0 ); // main-layout QHBoxLayout * ml = new QHBoxLayout; ml->setMargin( 0 ); ml->setSpacing( 0 ); ml->addSpacing( 6 ); QHBoxLayout * banks[NumFxChannels/16]; for( int i = 0; i < NumFxChannels/16; ++i ) { QWidget * w = new QWidget( this ); banks[i] = new QHBoxLayout( w ); banks[i]->setMargin( 5 ); banks[i]->setSpacing( 1 ); m_fxLineBanks->addWidget( w ); } for( int i = 0; i < NumFxChannels+1; ++i ) { FxChannelView * cv = &m_fxChannelViews[i]; if( i == 0 ) { cv->m_fxLine = new FxLine( NULL, this, m->m_fxChannels[i]->m_name ); ml->addWidget( cv->m_fxLine ); ml->addSpacing( 10 ); } else { const int bank = (i-1) / 16; cv->m_fxLine = new FxLine( NULL, this, m->m_fxChannels[i]->m_name ); banks[bank]->addWidget( cv->m_fxLine ); } LcdWidget* l = new LcdWidget( 2, cv->m_fxLine ); l->setValue( i ); l->move( 3, 4 ); l->setMarginWidth( 1 ); cv->m_fader = new fader( &m->m_fxChannels[i]->m_volumeModel, tr( "FX Fader %1" ).arg( i ), cv->m_fxLine ); cv->m_fader->move( 15-cv->m_fader->width()/2, cv->m_fxLine->height()- cv->m_fader->height()-5 ); cv->m_muteBtn = new pixmapButton( cv->m_fxLine, tr( "Mute" ) ); cv->m_muteBtn->setModel( &m->m_fxChannels[i]->m_muteModel ); cv->m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) ); cv->m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) ); cv->m_muteBtn->setCheckable( true ); cv->m_muteBtn->move( 9, cv->m_fader->y()-16); toolTip::add( cv->m_muteBtn, tr( "Mute this FX channel" ) ); cv->m_rackView = new EffectRackView( &m->m_fxChannels[i]->m_fxChain, this ); cv->m_rackView->setMinimumWidth( 244 ); m_fxRacksLayout->addWidget( cv->m_rackView ); if( i == 0 ) { QVBoxLayout * l = new QVBoxLayout; l->addSpacing( 10 ); QButtonGroup * g = new QButtonGroup( this ); m_bankButtons = g; g->setExclusive( true ); for( int j = 0; j < 4; ++j ) { QToolButton * btn = new QToolButton; btn->setText( QString( 'A'+j ) ); btn->setCheckable( true ); btn->setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding ); l->addWidget( btn ); g->addButton( btn, j ); btn->setChecked( j == 0); } l->addSpacing( 10 ); ml->addLayout( l ); connect( g, SIGNAL( buttonClicked( int ) ), m_fxLineBanks, SLOT( setCurrentIndex( int ) ) ); } } ml->addLayout( m_fxLineBanks ); ml->addLayout( m_fxRacksLayout ); setLayout( ml ); updateGeometry(); m_fxLineBanks->setCurrentIndex( 0 ); setCurrentFxLine( m_fxChannelViews[0].m_fxLine ); // timer for updating faders connect( engine::mainWindow(), SIGNAL( periodicUpdate() ), this, SLOT( updateFaders() ) ); // add ourself to workspace QMdiSubWindow * subWin = engine::mainWindow()->workspace()->addSubWindow( this ); Qt::WindowFlags flags = subWin->windowFlags(); flags |= Qt::MSWindowsFixedSizeDialogHint; flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); //subWin->layout()->setSizeConstraint(QLayout::SetFixedSize); parentWidget()->setAttribute( Qt::WA_DeleteOnClose, false ); parentWidget()->move( 5, 310 ); // we want to receive dataChanged-signals in order to update setModel( m ); } FxMixerView::~FxMixerView() { } void FxMixerView::saveSettings( QDomDocument & _doc, QDomElement & _this ) { MainWindow::saveWidgetState( this, _this ); } void FxMixerView::loadSettings( const QDomElement & _this ) { MainWindow::restoreWidgetState( this, _this ); } void FxMixerView::setCurrentFxLine( FxLine * _line ) { m_currentFxLine = _line; for( int i = 0; i < NumFxChannels+1; ++i ) { if( m_fxChannelViews[i].m_fxLine == _line ) { m_fxRacksLayout->setCurrentIndex( i ); } m_fxChannelViews[i].m_fxLine->update(); } } void FxMixerView::setCurrentFxLine( int _line ) { if ( _line >= 0 && _line < NumFxChannels+1 ) { setCurrentFxLine( m_fxChannelViews[_line].m_fxLine ); m_bankButtons->button( (_line-1) / 16 )->click(); } } void FxMixerView::clear() { for( int i = 0; i <= NumFxChannels; ++i ) { m_fxChannelViews[i].m_rackView->clearViews(); } } void FxMixerView::updateFaders() { FxMixer * m = engine::fxMixer(); for( int i = 0; i < NumFxChannels+1; ++i ) { const float opl = m_fxChannelViews[i].m_fader->getPeak_L(); const float opr = m_fxChannelViews[i].m_fader->getPeak_R(); const float fall_off = 1.2; if( m->m_fxChannels[i]->m_peakLeft > opl ) { m_fxChannelViews[i].m_fader->setPeak_L( m->m_fxChannels[i]->m_peakLeft ); m->m_fxChannels[i]->m_peakLeft = 0; } else { m_fxChannelViews[i].m_fader->setPeak_L( opl/fall_off ); } if( m->m_fxChannels[i]->m_peakRight > opr ) { m_fxChannelViews[i].m_fader->setPeak_R( m->m_fxChannels[i]->m_peakRight ); m->m_fxChannels[i]->m_peakRight = 0; } else { m_fxChannelViews[i].m_fader->setPeak_R( opr/fall_off ); } } } #include "moc_FxMixerView.cxx" lmms-1.0.0/src/gui/setup_dialog.cpp0000644000175000017500000010313512313663627015746 0ustar tobytoby/* * setup_dialog.cpp - dialog for setting up LMMS * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include "setup_dialog.h" #include "tab_bar.h" #include "tab_button.h" #include "tab_widget.h" #include "gui_templates.h" #include "Mixer.h" #include "ProjectJournal.h" #include "config_mgr.h" #include "embed.h" #include "engine.h" #include "debug.h" #include "tooltip.h" #include "led_checkbox.h" #include "LcdSpinBox.h" #include "FileDialog.h" // platform-specific audio-interface-classes #include "AudioAlsa.h" #include "AudioJack.h" #include "AudioOss.h" #include "AudioPortAudio.h" #include "AudioPulseAudio.h" #include "AudioSdl.h" #include "AudioDummy.h" // platform-specific midi-interface-classes #include "MidiAlsaRaw.h" #include "MidiAlsaSeq.h" #include "MidiOss.h" #include "MidiWinMM.h" #include "MidiDummy.h" inline void labelWidget( QWidget * _w, const QString & _txt ) { QLabel * title = new QLabel( _txt, _w ); QFont f = title->font(); f.setBold( true ); title->setFont( pointSize<12>( f ) ); #ifdef LMMS_DEBUG assert( dynamic_cast( _w->layout() ) != NULL ); #endif dynamic_cast( _w->layout() )->addSpacing( 5 ); dynamic_cast( _w->layout() )->addWidget( title ); dynamic_cast( _w->layout() )->addSpacing( 10 ); } setupDialog::setupDialog( ConfigTabs _tab_to_open ) : m_bufferSize( configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt() ), m_toolTips( !configManager::inst()->value( "tooltips", "disabled" ).toInt() ), m_warnAfterSetup( !configManager::inst()->value( "app", "nomsgaftersetup" ).toInt() ), m_displaydBV( configManager::inst()->value( "app", "displaydbv" ).toInt() ), m_MMPZ( !configManager::inst()->value( "app", "nommpz" ).toInt() ), m_hqAudioDev( configManager::inst()->value( "mixer", "hqaudio" ).toInt() ), m_workingDir( configManager::inst()->workingDir() ), m_vstDir( configManager::inst()->vstDir() ), m_artworkDir( configManager::inst()->artworkDir() ), m_flDir( configManager::inst()->flDir() ), m_ladDir( configManager::inst()->ladspaDir() ), #ifdef LMMS_HAVE_FLUIDSYNTH m_defaultSoundfont( configManager::inst()->defaultSoundfont() ), #endif #ifdef LMMS_HAVE_STK m_stkDir( configManager::inst()->stkDir() ), #endif m_backgroundArtwork( configManager::inst()->backgroundArtwork() ), m_smoothScroll( configManager::inst()->value( "ui", "smoothscroll" ).toInt() ), m_enableAutoSave( configManager::inst()->value( "ui", "enableautosave" ).toInt() ), m_oneInstrumentTrackWindow( configManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() ), m_compactTrackButtons( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ), m_syncVSTPlugins( configManager::inst()->value( "ui", "syncvstplugins" ).toInt() ), m_animateAFP(configManager::inst()->value( "ui", "animateafp").toInt() ), m_printNoteLabels(configManager::inst()->value( "ui", "printnotelabels").toInt() ), m_displayWaveform(configManager::inst()->value( "ui", "displaywaveform").toInt() ) { setWindowIcon( embed::getIconPixmap( "setup_general" ) ); setWindowTitle( tr( "Setup LMMS" ) ); setModal( true ); engine::projectJournal()->setJournalling( false ); QVBoxLayout * vlayout = new QVBoxLayout( this ); vlayout->setSpacing( 0 ); vlayout->setMargin( 0 ); QWidget * settings = new QWidget( this ); QHBoxLayout * hlayout = new QHBoxLayout( settings ); hlayout->setSpacing( 0 ); hlayout->setMargin( 0 ); m_tabBar = new tabBar( settings, QBoxLayout::TopToBottom ); m_tabBar->setExclusive( true ); m_tabBar->setFixedWidth( 72 ); QWidget * ws = new QWidget( settings ); int wsHeight = 336; #ifdef LMMS_HAVE_STK wsHeight += 50; #endif #ifdef LMMS_HAVE_FLUIDSYNTH wsHeight += 50; #endif ws->setFixedSize( 360, wsHeight ); QWidget * general = new QWidget( ws ); general->setFixedSize( 360, 240 ); QVBoxLayout * gen_layout = new QVBoxLayout( general ); gen_layout->setSpacing( 0 ); gen_layout->setMargin( 0 ); labelWidget( general, tr( "General settings" ) ); tabWidget * bufsize_tw = new tabWidget( tr( "BUFFER SIZE" ), general ); bufsize_tw->setFixedHeight( 80 ); m_bufSizeSlider = new QSlider( Qt::Horizontal, bufsize_tw ); m_bufSizeSlider->setRange( 1, 256 ); m_bufSizeSlider->setTickPosition( QSlider::TicksBelow ); m_bufSizeSlider->setPageStep( 8 ); m_bufSizeSlider->setTickInterval( 8 ); m_bufSizeSlider->setGeometry( 10, 16, 340, 18 ); m_bufSizeSlider->setValue( m_bufferSize / 64 ); connect( m_bufSizeSlider, SIGNAL( valueChanged( int ) ), this, SLOT( setBufferSize( int ) ) ); m_bufSizeLbl = new QLabel( bufsize_tw ); m_bufSizeLbl->setGeometry( 10, 40, 200, 24 ); setBufferSize( m_bufSizeSlider->value() ); QPushButton * bufsize_reset_btn = new QPushButton( embed::getIconPixmap( "reload" ), "", bufsize_tw ); bufsize_reset_btn->setGeometry( 290, 40, 28, 28 ); connect( bufsize_reset_btn, SIGNAL( clicked() ), this, SLOT( resetBufSize() ) ); toolTip::add( bufsize_reset_btn, tr( "Reset to default-value" ) ); QPushButton * bufsize_help_btn = new QPushButton( embed::getIconPixmap( "help" ), "", bufsize_tw ); bufsize_help_btn->setGeometry( 320, 40, 28, 28 ); connect( bufsize_help_btn, SIGNAL( clicked() ), this, SLOT( displayBufSizeHelp() ) ); tabWidget * misc_tw = new tabWidget( tr( "MISC" ), general ); const int XDelta = 10; const int YDelta = 18; const int HeaderSize = 30; int labelNumber = 0; ledCheckBox * enable_tooltips = new ledCheckBox( tr( "Enable tooltips" ), misc_tw ); labelNumber++; enable_tooltips->move( XDelta, YDelta*labelNumber ); enable_tooltips->setChecked( m_toolTips ); connect( enable_tooltips, SIGNAL( toggled( bool ) ), this, SLOT( toggleToolTips( bool ) ) ); ledCheckBox * restart_msg = new ledCheckBox( tr( "Show restart warning after changing settings" ), misc_tw ); labelNumber++; restart_msg->move( XDelta, YDelta*labelNumber ); restart_msg->setChecked( m_warnAfterSetup ); connect( restart_msg, SIGNAL( toggled( bool ) ), this, SLOT( toggleWarnAfterSetup( bool ) ) ); ledCheckBox * dbv = new ledCheckBox( tr( "Display volume as dBV " ), misc_tw ); labelNumber++; dbv->move( XDelta, YDelta*labelNumber ); dbv->setChecked( m_displaydBV ); connect( dbv, SIGNAL( toggled( bool ) ), this, SLOT( toggleDisplaydBV( bool ) ) ); ledCheckBox * mmpz = new ledCheckBox( tr( "Compress project files per default" ), misc_tw ); labelNumber++; mmpz->move( XDelta, YDelta*labelNumber ); mmpz->setChecked( m_MMPZ ); connect( mmpz, SIGNAL( toggled( bool ) ), this, SLOT( toggleMMPZ( bool ) ) ); ledCheckBox * oneitw = new ledCheckBox( tr( "One instrument track window mode" ), misc_tw ); labelNumber++; oneitw->move( XDelta, YDelta*labelNumber ); oneitw->setChecked( m_oneInstrumentTrackWindow ); connect( oneitw, SIGNAL( toggled( bool ) ), this, SLOT( toggleOneInstrumentTrackWindow( bool ) ) ); ledCheckBox * hqaudio = new ledCheckBox( tr( "HQ-mode for output audio-device" ), misc_tw ); labelNumber++; hqaudio->move( XDelta, YDelta*labelNumber ); hqaudio->setChecked( m_hqAudioDev ); connect( hqaudio, SIGNAL( toggled( bool ) ), this, SLOT( toggleHQAudioDev( bool ) ) ); ledCheckBox * compacttracks = new ledCheckBox( tr( "Compact track buttons" ), misc_tw ); labelNumber++; compacttracks->move( XDelta, YDelta*labelNumber ); compacttracks->setChecked( m_compactTrackButtons ); connect( compacttracks, SIGNAL( toggled( bool ) ), this, SLOT( toggleCompactTrackButtons( bool ) ) ); ledCheckBox * syncVST = new ledCheckBox( tr( "Sync VST plugins to host playback" ), misc_tw ); labelNumber++; syncVST->move( XDelta, YDelta*labelNumber ); syncVST->setChecked( m_syncVSTPlugins ); connect( syncVST, SIGNAL( toggled( bool ) ), this, SLOT( toggleSyncVSTPlugins( bool ) ) ); ledCheckBox * noteLabels = new ledCheckBox( tr( "Enable note labels in piano roll" ), misc_tw ); labelNumber++; noteLabels->move( XDelta, YDelta*labelNumber ); noteLabels->setChecked( m_printNoteLabels ); connect( noteLabels, SIGNAL( toggled( bool ) ), this, SLOT( toggleNoteLabels( bool ) ) ); ledCheckBox * displayWaveform = new ledCheckBox( tr( "Enable waveform display by default" ), misc_tw ); labelNumber++; displayWaveform->move( XDelta, YDelta*labelNumber ); displayWaveform->setChecked( m_displayWaveform ); connect( displayWaveform, SIGNAL( toggled( bool ) ), this, SLOT( toggleDisplayWaveform( bool ) ) ); misc_tw->setFixedHeight( YDelta*labelNumber + HeaderSize ); gen_layout->addWidget( bufsize_tw ); gen_layout->addSpacing( 10 ); gen_layout->addWidget( misc_tw ); gen_layout->addStretch(); QWidget * paths = new QWidget( ws ); int pathsHeight = 356; #ifdef LMMS_HAVE_STK pathsHeight += 55; #endif #ifdef LMMS_HAVE_FLUIDSYNTH pathsHeight += 55; #endif paths->setFixedSize( 360, pathsHeight ); QVBoxLayout * dir_layout = new QVBoxLayout( paths ); dir_layout->setSpacing( 0 ); dir_layout->setMargin( 0 ); labelWidget( paths, tr( "Paths" ) ); // working-dir tabWidget * lmms_wd_tw = new tabWidget( tr( "LMMS working directory" ).toUpper(), paths ); lmms_wd_tw->setFixedHeight( 48 ); m_wdLineEdit = new QLineEdit( m_workingDir, lmms_wd_tw ); m_wdLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_wdLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setWorkingDir( const QString & ) ) ); QPushButton * workingdir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", lmms_wd_tw ); workingdir_select_btn->setFixedSize( 24, 24 ); workingdir_select_btn->move( 320, 16 ); connect( workingdir_select_btn, SIGNAL( clicked() ), this, SLOT( openWorkingDir() ) ); // vst-dir tabWidget * vst_tw = new tabWidget( tr( "VST-plugin directory" ).toUpper(), paths ); vst_tw->setFixedHeight( 48 ); m_vdLineEdit = new QLineEdit( m_vstDir, vst_tw ); m_vdLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_vdLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setVSTDir( const QString & ) ) ); QPushButton * vstdir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", vst_tw ); vstdir_select_btn->setFixedSize( 24, 24 ); vstdir_select_btn->move( 320, 16 ); connect( vstdir_select_btn, SIGNAL( clicked() ), this, SLOT( openVSTDir() ) ); // artwork-dir tabWidget * artwork_tw = new tabWidget( tr( "Artwork directory" ).toUpper(), paths ); artwork_tw->setFixedHeight( 48 ); m_adLineEdit = new QLineEdit( m_artworkDir, artwork_tw ); m_adLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_adLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setArtworkDir( const QString & ) ) ); QPushButton * artworkdir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", artwork_tw ); artworkdir_select_btn->setFixedSize( 24, 24 ); artworkdir_select_btn->move( 320, 16 ); connect( artworkdir_select_btn, SIGNAL( clicked() ), this, SLOT( openArtworkDir() ) ); // background artwork file tabWidget * backgroundArtwork_tw = new tabWidget( tr( "Background artwork" ).toUpper(), paths ); backgroundArtwork_tw->setFixedHeight( 48 ); m_baLineEdit = new QLineEdit( m_backgroundArtwork, backgroundArtwork_tw ); m_baLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_baLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setBackgroundArtwork( const QString & ) ) ); QPushButton * backgroundartworkdir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", backgroundArtwork_tw ); backgroundartworkdir_select_btn->setFixedSize( 24, 24 ); backgroundartworkdir_select_btn->move( 320, 16 ); connect( backgroundartworkdir_select_btn, SIGNAL( clicked() ), this, SLOT( openBackgroundArtwork() ) ); // FL Studio-dir tabWidget * fl_tw = new tabWidget( tr( "FL Studio installation directory" ).toUpper(), paths ); fl_tw->setFixedHeight( 48 ); m_fdLineEdit = new QLineEdit( m_flDir, fl_tw ); m_fdLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_fdLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setFLDir( const QString & ) ) ); QPushButton * fldir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", fl_tw ); fldir_select_btn->setFixedSize( 24, 24 ); fldir_select_btn->move( 320, 16 ); connect( fldir_select_btn, SIGNAL( clicked() ), this, SLOT( openFLDir() ) ); // LADSPA-dir tabWidget * lad_tw = new tabWidget( tr( "LADSPA plugin paths" ).toUpper(), paths ); lad_tw->setFixedHeight( 48 ); m_ladLineEdit = new QLineEdit( m_ladDir, lad_tw ); m_ladLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_ladLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setLADSPADir( const QString & ) ) ); QPushButton * laddir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", lad_tw ); laddir_select_btn->setFixedSize( 24, 24 ); laddir_select_btn->move( 320, 16 ); connect( laddir_select_btn, SIGNAL( clicked() ), this, SLOT( openLADSPADir() ) ); #ifdef LMMS_HAVE_STK // STK-dir tabWidget * stk_tw = new tabWidget( tr( "STK rawwave directory" ).toUpper(), paths ); stk_tw->setFixedHeight( 48 ); m_stkLineEdit = new QLineEdit( m_stkDir, stk_tw ); m_stkLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_stkLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setSTKDir( const QString & ) ) ); QPushButton * stkdir_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", stk_tw ); stkdir_select_btn->setFixedSize( 24, 24 ); stkdir_select_btn->move( 320, 16 ); connect( stkdir_select_btn, SIGNAL( clicked() ), this, SLOT( openSTKDir() ) ); #endif #ifdef LMMS_HAVE_FLUIDSYNTH // Soundfont tabWidget * sf_tw = new tabWidget( tr( "Default Soundfont File" ).toUpper(), paths ); sf_tw->setFixedHeight( 48 ); m_sfLineEdit = new QLineEdit( m_defaultSoundfont, sf_tw ); m_sfLineEdit->setGeometry( 10, 20, 300, 16 ); connect( m_sfLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( setDefaultSoundfont( const QString & ) ) ); QPushButton * sf_select_btn = new QPushButton( embed::getIconPixmap( "project_open", 16, 16 ), "", sf_tw ); sf_select_btn->setFixedSize( 24, 24 ); sf_select_btn->move( 320, 16 ); connect( sf_select_btn, SIGNAL( clicked() ), this, SLOT( openDefaultSoundfont() ) ); #endif dir_layout->addWidget( lmms_wd_tw ); dir_layout->addSpacing( 10 ); dir_layout->addWidget( vst_tw ); dir_layout->addSpacing( 10 ); dir_layout->addWidget( artwork_tw ); dir_layout->addSpacing( 10 ); dir_layout->addWidget( backgroundArtwork_tw ); dir_layout->addSpacing( 10 ); dir_layout->addWidget( fl_tw ); dir_layout->addSpacing( 10 ); dir_layout->addWidget( lad_tw ); #ifdef LMMS_HAVE_STK dir_layout->addSpacing( 10 ); dir_layout->addWidget( stk_tw ); #endif #ifdef LMMS_HAVE_FLUIDSYNTH dir_layout->addSpacing( 10 ); dir_layout->addWidget( sf_tw ); #endif dir_layout->addStretch(); QWidget * performance = new QWidget( ws ); performance->setFixedSize( 360, 240 ); QVBoxLayout * perf_layout = new QVBoxLayout( performance ); perf_layout->setSpacing( 0 ); perf_layout->setMargin( 0 ); labelWidget( performance, tr( "Performance settings" ) ); tabWidget * ui_fx_tw = new tabWidget( tr( "UI effects vs. " "performance" ).toUpper(), performance ); ui_fx_tw->setFixedHeight( 80 ); ledCheckBox * smoothScroll = new ledCheckBox( tr( "Smooth scroll in Song Editor" ), ui_fx_tw ); smoothScroll->move( 10, 20 ); smoothScroll->setChecked( m_smoothScroll ); connect( smoothScroll, SIGNAL( toggled( bool ) ), this, SLOT( toggleSmoothScroll( bool ) ) ); ledCheckBox * autoSave = new ledCheckBox( tr( "Enable auto save feature" ), ui_fx_tw ); autoSave->move( 10, 40 ); autoSave->setChecked( m_enableAutoSave ); connect( autoSave, SIGNAL( toggled( bool ) ), this, SLOT( toggleAutoSave( bool ) ) ); ledCheckBox * animAFP = new ledCheckBox( tr( "Show playback cursor in AudioFileProcessor" ), ui_fx_tw ); animAFP->move( 10, 60 ); animAFP->setChecked( m_animateAFP ); connect( animAFP, SIGNAL( toggled( bool ) ), this, SLOT( toggleAnimateAFP( bool ) ) ); perf_layout->addWidget( ui_fx_tw ); perf_layout->addStretch(); QWidget * audio = new QWidget( ws ); audio->setFixedSize( 360, 200 ); QVBoxLayout * audio_layout = new QVBoxLayout( audio ); audio_layout->setSpacing( 0 ); audio_layout->setMargin( 0 ); labelWidget( audio, tr( "Audio settings" ) ); tabWidget * audioiface_tw = new tabWidget( tr( "AUDIO INTERFACE" ), audio ); audioiface_tw->setFixedHeight( 60 ); m_audioInterfaces = new QComboBox( audioiface_tw ); m_audioInterfaces->setGeometry( 10, 20, 240, 22 ); QPushButton * audio_help_btn = new QPushButton( embed::getIconPixmap( "help" ), "", audioiface_tw ); audio_help_btn->setGeometry( 320, 20, 28, 28 ); connect( audio_help_btn, SIGNAL( clicked() ), this, SLOT( displayAudioHelp() ) ); // create ifaces-settings-widget QWidget * asw = new QWidget( audio ); asw->setFixedHeight( 60 ); QHBoxLayout * asw_layout = new QHBoxLayout( asw ); asw_layout->setSpacing( 0 ); asw_layout->setMargin( 0 ); //asw_layout->setAutoAdd( true ); #ifdef LMMS_HAVE_JACK m_audioIfaceSetupWidgets[AudioJack::name()] = new AudioJack::setupWidget( asw ); #endif #ifdef LMMS_HAVE_ALSA m_audioIfaceSetupWidgets[AudioAlsa::name()] = new AudioAlsa::setupWidget( asw ); #endif #ifdef LMMS_HAVE_PULSEAUDIO m_audioIfaceSetupWidgets[AudioPulseAudio::name()] = new AudioPulseAudio::setupWidget( asw ); #endif #ifdef LMMS_HAVE_PORTAUDIO m_audioIfaceSetupWidgets[AudioPortAudio::name()] = new AudioPortAudio::setupWidget( asw ); #endif #ifdef LMMS_HAVE_SDL m_audioIfaceSetupWidgets[AudioSdl::name()] = new AudioSdl::setupWidget( asw ); #endif #ifdef LMMS_HAVE_OSS m_audioIfaceSetupWidgets[AudioOss::name()] = new AudioOss::setupWidget( asw ); #endif m_audioIfaceSetupWidgets[AudioDummy::name()] = new AudioDummy::setupWidget( asw ); for( AswMap::iterator it = m_audioIfaceSetupWidgets.begin(); it != m_audioIfaceSetupWidgets.end(); ++it ) { m_audioIfaceNames[tr( it.key().toAscii())] = it.key(); } for( trMap::iterator it = m_audioIfaceNames.begin(); it != m_audioIfaceNames.end(); ++it ) { QWidget * audioWidget = m_audioIfaceSetupWidgets[it.value()]; audioWidget->hide(); asw_layout->addWidget( audioWidget ); m_audioInterfaces->addItem( it.key() ); } m_audioInterfaces->setCurrentIndex( m_audioInterfaces->findText( tr( engine::mixer()->audioDevName().toAscii() ) ) ); m_audioIfaceSetupWidgets[engine::mixer()->audioDevName()]->show(); connect( m_audioInterfaces, SIGNAL( activated( const QString & ) ), this, SLOT( audioInterfaceChanged( const QString & ) ) ); audio_layout->addWidget( audioiface_tw ); audio_layout->addSpacing( 20 ); audio_layout->addWidget( asw ); audio_layout->addStretch(); QWidget * midi = new QWidget( ws ); QVBoxLayout * midi_layout = new QVBoxLayout( midi ); midi_layout->setSpacing( 0 ); midi_layout->setMargin( 0 ); labelWidget( midi, tr( "MIDI settings" ) ); tabWidget * midiiface_tw = new tabWidget( tr( "MIDI INTERFACE" ), midi ); midiiface_tw->setFixedHeight( 60 ); m_midiInterfaces = new QComboBox( midiiface_tw ); m_midiInterfaces->setGeometry( 10, 20, 240, 22 ); QPushButton * midi_help_btn = new QPushButton( embed::getIconPixmap( "help" ), "", midiiface_tw ); midi_help_btn->setGeometry( 320, 20, 28, 28 ); connect( midi_help_btn, SIGNAL( clicked() ), this, SLOT( displayMIDIHelp() ) ); // create ifaces-settings-widget QWidget * msw = new QWidget( midi ); msw->setFixedHeight( 60 ); QHBoxLayout * msw_layout = new QHBoxLayout( msw ); msw_layout->setSpacing( 0 ); msw_layout->setMargin( 0 ); //msw_layout->setAutoAdd( true ); #ifdef LMMS_HAVE_ALSA m_midiIfaceSetupWidgets[MidiAlsaSeq::name()] = new MidiAlsaSeq::setupWidget( msw ); m_midiIfaceSetupWidgets[MidiAlsaRaw::name()] = new MidiAlsaRaw::setupWidget( msw ); #endif #ifdef LMMS_HAVE_OSS m_midiIfaceSetupWidgets[MidiOss::name()] = new MidiOss::setupWidget( msw ); #endif #ifdef LMMS_BUILD_WIN32 m_midiIfaceSetupWidgets[MidiWinMM::name()] = new MidiWinMM::setupWidget( msw ); #endif m_midiIfaceSetupWidgets[MidiDummy::name()] = new MidiDummy::setupWidget( msw ); for( MswMap::iterator it = m_midiIfaceSetupWidgets.begin(); it != m_midiIfaceSetupWidgets.end(); ++it ) { m_midiIfaceNames[tr( it.key().toAscii())] = it.key(); } for( trMap::iterator it = m_midiIfaceNames.begin(); it != m_midiIfaceNames.end(); ++it ) { QWidget * midiWidget = m_midiIfaceSetupWidgets[it.value()]; midiWidget->hide(); msw_layout->addWidget( midiWidget ); m_midiInterfaces->addItem( it.key() ); } m_midiInterfaces->setCurrentIndex( m_midiInterfaces->findText( tr( engine::mixer()->midiClientName().toAscii() ) ) ); m_midiIfaceSetupWidgets[engine::mixer()->midiClientName()]->show(); connect( m_midiInterfaces, SIGNAL( activated( const QString & ) ), this, SLOT( midiInterfaceChanged( const QString & ) ) ); midi_layout->addWidget( midiiface_tw ); midi_layout->addSpacing( 20 ); midi_layout->addWidget( msw ); midi_layout->addStretch(); m_tabBar->addTab( general, tr( "General settings" ), 0, false, true )->setIcon( embed::getIconPixmap( "setup_general" ) ); m_tabBar->addTab( paths, tr( "Paths" ), 1, false, true )->setIcon( embed::getIconPixmap( "setup_directories" ) ); m_tabBar->addTab( performance, tr( "Performance settings" ), 2, false, true )->setIcon( embed::getIconPixmap( "setup_performance" ) ); m_tabBar->addTab( audio, tr( "Audio settings" ), 3, false, true )->setIcon( embed::getIconPixmap( "setup_audio" ) ); m_tabBar->addTab( midi, tr( "MIDI settings" ), 4, true, true )->setIcon( embed::getIconPixmap( "setup_midi" ) ); m_tabBar->setActiveTab( _tab_to_open ); hlayout->addWidget( m_tabBar ); hlayout->addSpacing( 10 ); hlayout->addWidget( ws ); hlayout->addSpacing( 10 ); hlayout->addStretch(); QWidget * buttons = new QWidget( this ); QHBoxLayout * btn_layout = new QHBoxLayout( buttons ); btn_layout->setSpacing( 0 ); btn_layout->setMargin( 0 ); QPushButton * ok_btn = new QPushButton( embed::getIconPixmap( "apply" ), tr( "OK" ), buttons ); connect( ok_btn, SIGNAL( clicked() ), this, SLOT( accept() ) ); QPushButton * cancel_btn = new QPushButton( embed::getIconPixmap( "cancel" ), tr( "Cancel" ), buttons ); connect( cancel_btn, SIGNAL( clicked() ), this, SLOT( reject() ) ); btn_layout->addStretch(); btn_layout->addSpacing( 10 ); btn_layout->addWidget( ok_btn ); btn_layout->addSpacing( 10 ); btn_layout->addWidget( cancel_btn ); btn_layout->addSpacing( 10 ); vlayout->addWidget( settings ); vlayout->addSpacing( 10 ); vlayout->addWidget( buttons ); vlayout->addSpacing( 10 ); vlayout->addStretch(); show(); } setupDialog::~setupDialog() { engine::projectJournal()->setJournalling( true ); } void setupDialog::accept() { configManager::inst()->setValue( "mixer", "framesperaudiobuffer", QString::number( m_bufferSize ) ); configManager::inst()->setValue( "mixer", "audiodev", m_audioIfaceNames[m_audioInterfaces->currentText()] ); configManager::inst()->setValue( "mixer", "mididev", m_midiIfaceNames[m_midiInterfaces->currentText()] ); configManager::inst()->setValue( "tooltips", "disabled", QString::number( !m_toolTips ) ); configManager::inst()->setValue( "app", "nomsgaftersetup", QString::number( !m_warnAfterSetup ) ); configManager::inst()->setValue( "app", "displaydbv", QString::number( m_displaydBV ) ); configManager::inst()->setValue( "app", "nommpz", QString::number( !m_MMPZ ) ); configManager::inst()->setValue( "mixer", "hqaudio", QString::number( m_hqAudioDev ) ); configManager::inst()->setValue( "ui", "smoothscroll", QString::number( m_smoothScroll ) ); configManager::inst()->setValue( "ui", "enableautosave", QString::number( m_enableAutoSave ) ); configManager::inst()->setValue( "ui", "oneinstrumenttrackwindow", QString::number( m_oneInstrumentTrackWindow ) ); configManager::inst()->setValue( "ui", "compacttrackbuttons", QString::number( m_compactTrackButtons ) ); configManager::inst()->setValue( "ui", "syncvstplugins", QString::number( m_syncVSTPlugins ) ); configManager::inst()->setValue( "ui", "animateafp", QString::number( m_animateAFP ) ); configManager::inst()->setValue( "ui", "printnotelabels", QString::number( m_printNoteLabels ) ); configManager::inst()->setValue( "ui", "displaywaveform", QString::number( m_displayWaveform ) ); configManager::inst()->setWorkingDir( m_workingDir ); configManager::inst()->setVSTDir( m_vstDir ); configManager::inst()->setArtworkDir( m_artworkDir ); configManager::inst()->setFLDir( m_flDir ); configManager::inst()->setLADSPADir( m_ladDir ); #ifdef LMMS_HAVE_FLUIDSYNTH configManager::inst()->setDefaultSoundfont( m_defaultSoundfont ); #endif #ifdef LMMS_HAVE_STK configManager::inst()->setSTKDir( m_stkDir ); #endif configManager::inst()->setBackgroundArtwork( m_backgroundArtwork ); // tell all audio-settings-widget to save their settings for( AswMap::iterator it = m_audioIfaceSetupWidgets.begin(); it != m_audioIfaceSetupWidgets.end(); ++it ) { it.value()->saveSettings(); } // tell all MIDI-settings-widget to save their settings for( MswMap::iterator it = m_midiIfaceSetupWidgets.begin(); it != m_midiIfaceSetupWidgets.end(); ++it ) { it.value()->saveSettings(); } configManager::inst()->saveConfigFile(); QDialog::accept(); if( m_warnAfterSetup ) { QMessageBox::information( NULL, tr( "Restart LMMS" ), tr( "Please note that most changes " "won't take effect until " "you restart LMMS!" ), QMessageBox::Ok ); } } void setupDialog::setBufferSize( int _value ) { const int step = DEFAULT_BUFFER_SIZE / 64; if( _value > step && _value % step ) { int mod_value = _value % step; if( mod_value < step / 2 ) { m_bufSizeSlider->setValue( _value - mod_value ); } else { m_bufSizeSlider->setValue( _value + step - mod_value ); } return; } if( m_bufSizeSlider->value() != _value ) { m_bufSizeSlider->setValue( _value ); } m_bufferSize = _value * 64; m_bufSizeLbl->setText( tr( "Frames: %1\nLatency: %2 ms" ).arg( m_bufferSize ).arg( 1000.0f * m_bufferSize / engine::mixer()->processingSampleRate(), 0, 'f', 1 ) ); } void setupDialog::resetBufSize() { setBufferSize( DEFAULT_BUFFER_SIZE / 64 ); } void setupDialog::displayBufSizeHelp() { QWhatsThis::showText( QCursor::pos(), tr( "Here you can setup the internal buffer-size " "used by LMMS. Smaller values result " "in a lower latency but also may cause " "unusable sound or bad performance, " "especially on older computers or " "systems with a non-realtime " "kernel." ) ); } void setupDialog::toggleToolTips( bool _enabled ) { m_toolTips = _enabled; } void setupDialog::toggleWarnAfterSetup( bool _enabled ) { m_warnAfterSetup = _enabled; } void setupDialog::toggleDisplaydBV( bool _enabled ) { m_displaydBV = _enabled; } void setupDialog::toggleMMPZ( bool _enabled ) { m_MMPZ = _enabled; } void setupDialog::toggleHQAudioDev( bool _enabled ) { m_hqAudioDev = _enabled; } void setupDialog::toggleSmoothScroll( bool _enabled ) { m_smoothScroll = _enabled; } void setupDialog::toggleAutoSave( bool _enabled ) { m_enableAutoSave = _enabled; } void setupDialog::toggleCompactTrackButtons( bool _enabled ) { m_compactTrackButtons = _enabled; } void setupDialog::toggleSyncVSTPlugins( bool _enabled ) { m_syncVSTPlugins = _enabled; } void setupDialog::toggleAnimateAFP( bool _enabled ) { m_animateAFP = _enabled; } void setupDialog::toggleNoteLabels( bool en ) { m_printNoteLabels = en; } void setupDialog::toggleDisplayWaveform( bool en ) { m_displayWaveform = en; } void setupDialog::toggleOneInstrumentTrackWindow( bool _enabled ) { m_oneInstrumentTrackWindow = _enabled; } void setupDialog::openWorkingDir() { QString new_dir = FileDialog::getExistingDirectory( this, tr( "Choose LMMS working directory" ), m_workingDir ); if( new_dir != QString::null ) { m_wdLineEdit->setText( new_dir ); } } void setupDialog::setWorkingDir( const QString & _wd ) { m_workingDir = _wd; } void setupDialog::openVSTDir() { QString new_dir = FileDialog::getExistingDirectory( this, tr( "Choose your VST-plugin directory" ), m_vstDir ); if( new_dir != QString::null ) { m_vdLineEdit->setText( new_dir ); } } void setupDialog::setVSTDir( const QString & _vd ) { m_vstDir = _vd; } void setupDialog::openArtworkDir() { QString new_dir = FileDialog::getExistingDirectory( this, tr( "Choose artwork-theme directory" ), m_artworkDir ); if( new_dir != QString::null ) { m_adLineEdit->setText( new_dir ); } } void setupDialog::setArtworkDir( const QString & _ad ) { m_artworkDir = _ad; } void setupDialog::openFLDir() { QString new_dir = FileDialog::getExistingDirectory( this, tr( "Choose FL Studio installation directory" ), m_flDir ); if( new_dir != QString::null ) { m_fdLineEdit->setText( new_dir ); } } void setupDialog::openLADSPADir() { QString new_dir = FileDialog::getExistingDirectory( this, tr( "Choose LADSPA plugin directory" ), m_ladDir ); if( new_dir != QString::null ) { if( m_ladLineEdit->text() == "" ) { m_ladLineEdit->setText( new_dir ); } else { m_ladLineEdit->setText( m_ladLineEdit->text() + "," + new_dir ); } } } void setupDialog::openSTKDir() { #ifdef LMMS_HAVE_STK QString new_dir = FileDialog::getExistingDirectory( this, tr( "Choose STK rawwave directory" ), m_stkDir ); if( new_dir != QString::null ) { m_stkLineEdit->setText( new_dir ); } #endif } void setupDialog::openDefaultSoundfont() { #ifdef LMMS_HAVE_FLUIDSYNTH QString new_file = FileDialog::getOpenFileName( this, tr( "Choose default SoundFont" ), m_defaultSoundfont, "SoundFont2 Files (*.sf2)" ); if( new_file != QString::null ) { m_sfLineEdit->setText( new_file ); } #endif } void setupDialog::openBackgroundArtwork() { QList fileTypesList = QImageReader::supportedImageFormats(); QString fileTypes; for( int i = 0; i < fileTypesList.count(); i++ ) { if( fileTypesList[i] != fileTypesList[i].toUpper() ) { if( !fileTypes.isEmpty() ) { fileTypes += " "; } fileTypes += "*." + QString( fileTypesList[i] ); } } QString dir = ( m_backgroundArtwork.isEmpty() ) ? m_artworkDir : m_backgroundArtwork; QString new_file = FileDialog::getOpenFileName( this, tr( "Choose background artwork" ), dir, "Image Files (" + fileTypes + ")" ); if( new_file != QString::null ) { m_baLineEdit->setText( new_file ); } } void setupDialog::setFLDir( const QString & _fd ) { m_flDir = _fd; } void setupDialog::setLADSPADir( const QString & _fd ) { m_ladDir = _fd; } void setupDialog::setSTKDir( const QString & _fd ) { #ifdef LMMS_HAVE_STK m_stkDir = _fd; #endif } void setupDialog::setDefaultSoundfont( const QString & _sf ) { #ifdef LMMS_HAVE_FLUIDSYNTH m_defaultSoundfont = _sf; #endif } void setupDialog::setBackgroundArtwork( const QString & _ba ) { #ifdef LMMS_HAVE_FLUIDSYNTH m_backgroundArtwork = _ba; #endif } void setupDialog::audioInterfaceChanged( const QString & _iface ) { for( AswMap::iterator it = m_audioIfaceSetupWidgets.begin(); it != m_audioIfaceSetupWidgets.end(); ++it ) { it.value()->hide(); } m_audioIfaceSetupWidgets[m_audioIfaceNames[_iface]]->show(); } void setupDialog::displayAudioHelp() { QWhatsThis::showText( QCursor::pos(), tr( "Here you can select your preferred " "audio-interface. Depending on the " "configuration of your system during " "compilation time you can choose " "between ALSA, JACK, OSS and more. " "Below you see a box which offers " "controls to setup the selected " "audio-interface." ) ); } void setupDialog::midiInterfaceChanged( const QString & _iface ) { for( MswMap::iterator it = m_midiIfaceSetupWidgets.begin(); it != m_midiIfaceSetupWidgets.end(); ++it ) { it.value()->hide(); } m_midiIfaceSetupWidgets[m_midiIfaceNames[_iface]]->show(); } void setupDialog::displayMIDIHelp() { QWhatsThis::showText( QCursor::pos(), tr( "Here you can select your preferred " "MIDI-interface. Depending on the " "configuration of your system during " "compilation time you can choose " "between ALSA, OSS and more. " "Below you see a box which offers " "controls to setup the selected " "MIDI-interface." ) ); } #include "moc_setup_dialog.cxx" lmms-1.0.0/src/gui/MainWindow.cpp0000644000175000017500000006731012313663627015347 0ustar tobytoby/* * MainWindow.cpp - implementation of LMMS-main-window * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include "lmmsversion.h" #include "MainWindow.h" #include "bb_editor.h" #include "SongEditor.h" #include "song.h" #include "PianoRoll.h" #include "embed.h" #include "engine.h" #include "FxMixerView.h" #include "InstrumentTrack.h" #include "PianoView.h" #include "about_dialog.h" #include "ControllerRackView.h" #include "file_browser.h" #include "plugin_browser.h" #include "SideBar.h" #include "config_mgr.h" #include "Mixer.h" #include "PluginView.h" #include "project_notes.h" #include "setup_dialog.h" #include "AudioDummy.h" #include "ToolPlugin.h" #include "tool_button.h" #include "ProjectJournal.h" #include "AutomationEditor.h" #include "templates.h" #include "FileDialog.h" #include "VersionedSaveDialog.h" MainWindow::MainWindow() : m_workspace( NULL ), m_templatesMenu( NULL ), m_recentlyOpenedProjectsMenu( NULL ), m_toolsMenu( NULL ), m_autoSaveTimer( this ) { setAttribute( Qt::WA_DeleteOnClose ); QWidget * main_widget = new QWidget( this ); QVBoxLayout * vbox = new QVBoxLayout( main_widget ); vbox->setSpacing( 0 ); vbox->setMargin( 0 ); QWidget * w = new QWidget( main_widget ); QHBoxLayout * hbox = new QHBoxLayout( w ); hbox->setSpacing( 0 ); hbox->setMargin( 0 ); SideBar * sideBar = new SideBar( Qt::Vertical, w ); QSplitter * splitter = new QSplitter( Qt::Horizontal, w ); splitter->setChildrenCollapsible( FALSE ); QString wdir = configManager::inst()->workingDir(); sideBar->appendTab( new pluginBrowser( splitter ) ); sideBar->appendTab( new fileBrowser( configManager::inst()->userProjectsDir() + "*" + configManager::inst()->factoryProjectsDir(), "*.mmp *.mmpz *.xml *.mid *.flp", tr( "My projects" ), embed::getIconPixmap( "project_file" ).transformed( QTransform().rotate( 90 ) ), splitter ) ); sideBar->appendTab( new fileBrowser( configManager::inst()->userSamplesDir() + "*" + configManager::inst()->factorySamplesDir(), "*", tr( "My samples" ), embed::getIconPixmap( "sample_file" ).transformed( QTransform().rotate( 90 ) ), splitter ) ); sideBar->appendTab( new fileBrowser( configManager::inst()->userPresetsDir() + "*" + configManager::inst()->factoryPresetsDir(), "*.xpf *.cs.xml *.xiz", tr( "My presets" ), embed::getIconPixmap( "preset_file" ).transformed( QTransform().rotate( 90 ) ), splitter ) ); sideBar->appendTab( new fileBrowser( QDir::homePath(), "*", tr( "My home" ), embed::getIconPixmap( "home" ).transformed( QTransform().rotate( 90 ) ), splitter ) ); QFileInfoList drives = QDir::drives(); QStringList root_paths; foreach( const QFileInfo & drive, drives ) { root_paths += drive.absolutePath(); } sideBar->appendTab( new fileBrowser( root_paths.join( "*" ), "*", #ifdef LMMS_BUILD_WIN32 tr( "My computer" ), #else tr( "Root directory" ), #endif embed::getIconPixmap( "computer" ).transformed( QTransform().rotate( 90 ) ), splitter, #ifdef LMMS_BUILD_WIN32 true #else false #endif ) ); m_workspace = new QMdiArea( splitter ); // Load background QString bgArtwork = configManager::inst()->backgroundArtwork(); QImage bgImage; if( !bgArtwork.isEmpty() ) { bgImage = QImage( bgArtwork ); } if( !bgImage.isNull() ) { m_workspace->setBackground( bgImage ); } else { m_workspace->setBackground( Qt::NoBrush ); } m_workspace->setOption( QMdiArea::DontMaximizeSubWindowOnActivation ); m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); hbox->addWidget( sideBar ); hbox->addWidget( splitter ); // create global-toolbar at the top of our window m_toolBar = new QWidget( main_widget ); m_toolBar->setObjectName( "mainToolbar" ); m_toolBar->setFixedHeight( 64 ); m_toolBar->move( 0, 0 ); // add layout for organizing quite complex toolbar-layouting m_toolBarLayout = new QGridLayout( m_toolBar/*, 2, 1*/ ); m_toolBarLayout->setMargin( 0 ); m_toolBarLayout->setSpacing( 0 ); vbox->addWidget( m_toolBar ); vbox->addWidget( w ); setCentralWidget( main_widget ); m_updateTimer.start( 1000 / 20, this ); // 20 fps if( configManager::inst()->value( "ui", "enableautosave" ).toInt() ) { // connect auto save connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave())); m_autoSaveTimer.start(1000 * 60); // 1 minute } connect( engine::getSong(), SIGNAL( playbackStateChanged() ), this, SLOT( updatePlayPauseIcons() ) ); } MainWindow::~MainWindow() { for( QList::iterator it = m_tools.begin(); it != m_tools.end(); ++it ) { Model * m = ( *it )->model(); delete *it; delete m; } // TODO: Close tools // destroy engine which will do further cleanups etc. engine::destroy(); } void MainWindow::finalize() { resetWindowTitle(); setWindowIcon( embed::getIconPixmap( "icon" ) ); // project-popup-menu QMenu * project_menu = new QMenu( this ); menuBar()->addMenu( project_menu )->setText( tr( "&Project" ) ); project_menu->addAction( embed::getIconPixmap( "project_new" ), tr( "&New" ), this, SLOT( createNewProject() ), Qt::CTRL + Qt::Key_N ); project_menu->addAction( embed::getIconPixmap( "project_open" ), tr( "&Open..." ), this, SLOT( openProject() ), Qt::CTRL + Qt::Key_O ); m_recentlyOpenedProjectsMenu = project_menu->addMenu( embed::getIconPixmap( "project_open_recent" ), tr( "Recently opened projects" ) ); connect( m_recentlyOpenedProjectsMenu, SIGNAL( aboutToShow() ), this, SLOT( updateRecentlyOpenedProjectsMenu() ) ); connect( m_recentlyOpenedProjectsMenu, SIGNAL( triggered( QAction * ) ), this, SLOT( openRecentlyOpenedProject( QAction * ) ) ); project_menu->addAction( embed::getIconPixmap( "project_save" ), tr( "&Save" ), this, SLOT( saveProject() ), Qt::CTRL + Qt::Key_S ); project_menu->addAction( embed::getIconPixmap( "project_save" ), tr( "Save as new &version" ), this, SLOT( saveProjectAsNewVersion() ), Qt::CTRL + Qt::ALT + Qt::Key_S ); project_menu->addAction( embed::getIconPixmap( "project_saveas" ), tr( "Save &As..." ), this, SLOT( saveProjectAs() ), Qt::CTRL + Qt::SHIFT + Qt::Key_S ); project_menu->addSeparator(); project_menu->addAction( embed::getIconPixmap( "project_import" ), tr( "Import..." ), engine::getSong(), SLOT( importProject() ) ); project_menu->addAction( embed::getIconPixmap( "project_export" ), tr( "E&xport..." ), engine::getSong(), SLOT( exportProject() ), Qt::CTRL + Qt::Key_E ); project_menu->addAction( embed::getIconPixmap( "project_export" ), tr( "E&xport tracks..." ), engine::getSong(), SLOT( exportProjectTracks() ), Qt::CTRL + Qt::SHIFT + Qt::Key_E ); project_menu->addSeparator(); project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ), qApp, SLOT( closeAllWindows() ), Qt::CTRL + Qt::Key_Q ); QMenu * edit_menu = new QMenu( this ); menuBar()->addMenu( edit_menu )->setText( tr( "&Edit" ) ); /* edit_menu->addAction( embed::getIconPixmap( "edit_undo" ), tr( "Undo" ), this, SLOT( undo() ), Qt::CTRL + Qt::Key_Z ); edit_menu->addAction( embed::getIconPixmap( "edit_redo" ), tr( "Redo" ), this, SLOT( redo() ), Qt::CTRL + Qt::Key_R ); edit_menu->addSeparator();*/ edit_menu->addAction( embed::getIconPixmap( "setup_general" ), tr( "Settings" ), this, SLOT( showSettingsDialog() ) ); m_toolsMenu = new QMenu( this ); Plugin::DescriptorList pluginDescriptors; Plugin::getDescriptorsOfAvailPlugins( pluginDescriptors ); for( Plugin::DescriptorList::ConstIterator it = pluginDescriptors.begin(); it != pluginDescriptors.end(); ++it ) { if( it->type == Plugin::Tool ) { m_toolsMenu->addAction( it->logo->pixmap(), it->displayName ); m_tools.push_back( ToolPlugin::instantiate( it->name, /*this*/NULL )->createView( this ) ); } } if( !m_toolsMenu->isEmpty() ) { menuBar()->addMenu( m_toolsMenu )->setText( tr( "&Tools" ) ); connect( m_toolsMenu, SIGNAL( triggered( QAction * ) ), this, SLOT( showTool( QAction * ) ) ); } // help-popup-menu QMenu * help_menu = new QMenu( this ); menuBar()->addMenu( help_menu )->setText( tr( "&Help" ) ); // May use offline help if( TRUE ) { help_menu->addAction( embed::getIconPixmap( "help" ), tr( "Online help" ), this, SLOT( browseHelp() ) ); } else { help_menu->addAction( embed::getIconPixmap( "help" ), tr( "Help" ), this, SLOT( help() ) ); } help_menu->addAction( embed::getIconPixmap( "whatsthis" ), tr( "What's this?" ), this, SLOT( enterWhatsThisMode() ) ); help_menu->addSeparator(); help_menu->addAction( embed::getIconPixmap( "icon" ), tr( "About" ), this, SLOT( aboutLMMS() ) ); // create tool-buttons toolButton * project_new = new toolButton( embed::getIconPixmap( "project_new" ), tr( "Create new project" ), this, SLOT( createNewProject() ), m_toolBar ); toolButton * project_new_from_template = new toolButton( embed::getIconPixmap( "project_new_from_template" ), tr( "Create new project from template" ), this, SLOT( emptySlot() ), m_toolBar ); m_templatesMenu = new QMenu( project_new_from_template ); connect( m_templatesMenu, SIGNAL( aboutToShow() ), this, SLOT( fillTemplatesMenu() ) ); connect( m_templatesMenu, SIGNAL( triggered( QAction * ) ), this, SLOT( createNewProjectFromTemplate( QAction * ) ) ); project_new_from_template->setMenu( m_templatesMenu ); project_new_from_template->setPopupMode( toolButton::InstantPopup ); toolButton * project_open = new toolButton( embed::getIconPixmap( "project_open" ), tr( "Open existing project" ), this, SLOT( openProject() ), m_toolBar ); toolButton * project_open_recent = new toolButton( embed::getIconPixmap( "project_open_recent" ), tr( "Recently opened project" ), this, SLOT( emptySlot() ), m_toolBar ); project_open_recent->setMenu( m_recentlyOpenedProjectsMenu ); project_open_recent->setPopupMode( toolButton::InstantPopup ); toolButton * project_save = new toolButton( embed::getIconPixmap( "project_save" ), tr( "Save current project" ), this, SLOT( saveProject() ), m_toolBar ); toolButton * project_export = new toolButton( embed::getIconPixmap( "project_export" ), tr( "Export current project" ), engine::getSong(), SLOT( exportProject() ), m_toolBar ); m_toolBarLayout->setColumnMinimumWidth( 0, 5 ); m_toolBarLayout->addWidget( project_new, 0, 1 ); m_toolBarLayout->addWidget( project_new_from_template, 0, 2 ); m_toolBarLayout->addWidget( project_open, 0, 3 ); m_toolBarLayout->addWidget( project_open_recent, 0, 4 ); m_toolBarLayout->addWidget( project_save, 0, 5 ); m_toolBarLayout->addWidget( project_export, 0, 6 ); // window-toolbar toolButton * song_editor_window = new toolButton( embed::getIconPixmap( "songeditor" ), tr( "Show/hide Song-Editor" ) + " (F5)", this, SLOT( toggleSongEditorWin() ), m_toolBar ); song_editor_window->setShortcut( Qt::Key_F5 ); song_editor_window->setWhatsThis( tr( "By pressing this button, you can show or hide the " "Song-Editor. With the help of the Song-Editor you can " "edit song-playlist and specify when which track " "should be played. " "You can also insert and move samples (e.g. " "rap samples) directly into the playlist." ) ); toolButton * bb_editor_window = new toolButton( embed::getIconPixmap( "bb_track_btn" ), tr( "Show/hide Beat+Bassline Editor" ) + " (F6)", this, SLOT( toggleBBEditorWin() ), m_toolBar ); bb_editor_window->setShortcut( Qt::Key_F6 ); bb_editor_window->setWhatsThis( tr( "By pressing this button, you can show or hide the " "Beat+Bassline Editor. The Beat+Bassline Editor is " "needed for creating beats, and for opening, adding, and " "removing channels, and for cutting, copying and pasting " "beat and bassline-patterns, and for other things like " "that." ) ); toolButton * piano_roll_window = new toolButton( embed::getIconPixmap( "piano" ), tr( "Show/hide Piano-Roll" ) + " (F7)", this, SLOT( togglePianoRollWin() ), m_toolBar ); piano_roll_window->setShortcut( Qt::Key_F7 ); piano_roll_window->setWhatsThis( tr( "Click here to show or hide the " "Piano-Roll. With the help of the Piano-Roll " "you can edit melodies in an easy way." ) ); toolButton * automation_editor_window = new toolButton( embed::getIconPixmap( "automation" ), tr( "Show/hide Automation Editor" ) + " (F8)", this, SLOT( toggleAutomationEditorWin() ), m_toolBar ); automation_editor_window->setShortcut( Qt::Key_F8 ); automation_editor_window->setWhatsThis( tr( "Click here to show or hide the " "Automation Editor. With the help of the " "Automation Editor you can edit dynamic values " "in an easy way." ) ); toolButton * fx_mixer_window = new toolButton( embed::getIconPixmap( "fx_mixer" ), tr( "Show/hide FX Mixer" ) + " (F9)", this, SLOT( toggleFxMixerWin() ), m_toolBar ); fx_mixer_window->setShortcut( Qt::Key_F9 ); fx_mixer_window->setWhatsThis( tr( "Click here to show or hide the " "FX Mixer. The FX Mixer is a very powerful tool " "for managing effects for your song. You can insert " "effects into different effect-channels." ) ); toolButton * project_notes_window = new toolButton( embed::getIconPixmap( "project_notes" ), tr( "Show/hide project notes" ) + " (F10)", this, SLOT( toggleProjectNotesWin() ), m_toolBar ); project_notes_window->setShortcut( Qt::Key_F10 ); project_notes_window->setWhatsThis( tr( "Click here to show or hide the " "project notes window. In this window you can put " "down your project notes.") ); toolButton * controllers_window = new toolButton( embed::getIconPixmap( "controller" ), tr( "Show/hide controller rack" ) + " (F11)", this, SLOT( toggleControllerRack() ), m_toolBar ); controllers_window->setShortcut( Qt::Key_F11 ); m_toolBarLayout->addWidget( song_editor_window, 1, 1 ); m_toolBarLayout->addWidget( bb_editor_window, 1, 2 ); m_toolBarLayout->addWidget( piano_roll_window, 1, 3 ); m_toolBarLayout->addWidget( automation_editor_window, 1, 4 ); m_toolBarLayout->addWidget( fx_mixer_window, 1, 5 ); m_toolBarLayout->addWidget( project_notes_window, 1, 6 ); m_toolBarLayout->addWidget( controllers_window, 1, 7 ); m_toolBarLayout->setColumnStretch( 100, 1 ); // setup-dialog opened before? if( !configManager::inst()->value( "app", "configured" ).toInt() ) { configManager::inst()->setValue( "app", "configured", "1" ); // no, so show it that user can setup everything setupDialog sd; sd.exec(); } // look whether mixer could use a audio-interface beside AudioDummy else if( engine::mixer()->audioDevName() == AudioDummy::name() ) { // no, so we offer setup-dialog with audio-settings... setupDialog sd( setupDialog::AudioSettings ); sd.exec(); } } int MainWindow::addWidgetToToolBar( QWidget * _w, int _row, int _col ) { int col = ( _col == -1 ) ? m_toolBarLayout->columnCount() + 7 : _col; if( _w->height() > 32 || _row == -1 ) { m_toolBarLayout->addWidget( _w, 0, col, 2, 1 ); } else { m_toolBarLayout->addWidget( _w, _row, col ); } return( col ); } void MainWindow::addSpacingToToolBar( int _size ) { m_toolBarLayout->setColumnMinimumWidth( m_toolBarLayout->columnCount() + 7, _size ); } void MainWindow::resetWindowTitle() { QString title = ""; if( engine::getSong()->projectFileName() != "" ) { title = QFileInfo( engine::getSong()->projectFileName() ).completeBaseName(); } if( title == "" ) { title = tr( "Untitled" ); } if( engine::getSong()->isModified() ) { title += '*'; } setWindowTitle( title + " - " + tr( "LMMS %1" ).arg( LMMS_VERSION ) ); } bool MainWindow::mayChangeProject() { engine::getSong()->stop(); if( !engine::getSong()->isModified() ) { return( TRUE ); } QMessageBox mb( tr( "Project not saved" ), tr( "The current project was modified since " "last saving. Do you want to save it " "now?" ), QMessageBox::Question, QMessageBox::Save, QMessageBox::Discard, QMessageBox::Cancel, this ); int answer = mb.exec(); if( answer == QMessageBox::Save ) { return( saveProject() ); } else if( answer == QMessageBox::Discard ) { return( TRUE ); } return( FALSE ); } void MainWindow::clearKeyModifiers() { m_keyMods.m_ctrl = FALSE; m_keyMods.m_shift = FALSE; m_keyMods.m_alt = FALSE; } void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de ) { if( _w->parentWidget() != NULL && _w->parentWidget()->inherits( "QMdiSubWindow" ) ) { _w = _w->parentWidget(); } _de.setAttribute( "x", _w->x() ); _de.setAttribute( "y", _w->y() ); _de.setAttribute( "visible", _w->isVisible() ); _de.setAttribute( "minimized", _w->isMinimized() ); _de.setAttribute( "maximized", _w->isMaximized() ); _de.setAttribute( "width", _w->width() ); _de.setAttribute( "height", _w->height() ); } void MainWindow::restoreWidgetState( QWidget * _w, const QDomElement & _de ) { QRect r( qMax( 0, _de.attribute( "x" ).toInt() ), qMax( 0, _de.attribute( "y" ).toInt() ), qMax( 100, _de.attribute( "width" ).toInt() ), qMax( 100, _de.attribute( "height" ).toInt() ) ); if( _de.hasAttribute( "visible" ) && !r.isNull() ) { if ( _w->parentWidget() != NULL && _w->parentWidget()->inherits( "QMdiSubWindow" ) ) { _w = _w->parentWidget(); } _w->resize( r.size() ); _w->move( r.topLeft() ); _w->setVisible( _de.attribute( "visible" ).toInt() ); _w->setWindowState( _de.attribute( "minimized" ).toInt() ? ( _w->windowState() | Qt::WindowMinimized ) : ( _w->windowState() & ~Qt::WindowMinimized ) ); _w->setWindowState( _de.attribute( "maximized" ).toInt() ? ( _w->windowState() | Qt::WindowMaximized ) : ( _w->windowState() & ~Qt::WindowMaximized ) ); } } void MainWindow::emptySlot() { } void MainWindow::enterWhatsThisMode() { QWhatsThis::enterWhatsThisMode(); } void MainWindow::createNewProject() { if( mayChangeProject() ) { engine::getSong()->createNewProject(); } } void MainWindow::createNewProjectFromTemplate( QAction * _idx ) { if( m_templatesMenu != NULL && mayChangeProject() ) { QString dir_base = m_templatesMenu->actions().indexOf( _idx ) >= m_custom_templates_count ? configManager::inst()->factoryProjectsDir() : configManager::inst()->userProjectsDir(); engine::getSong()->createNewProjectFromTemplate( dir_base + "templates/" + _idx->text() + ".mpt" ); } } void MainWindow::openProject() { if( mayChangeProject() ) { FileDialog ofd( this, tr( "Open project" ), "", tr( "LMMS (*.mmp *.mmpz)" ) ); ofd.setDirectory( configManager::inst()->userProjectsDir() ); ofd.setFileMode( FileDialog::ExistingFiles ); if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() ) { setCursor( Qt::WaitCursor ); engine::getSong()->loadProject( ofd.selectedFiles()[0] ); setCursor( Qt::ArrowCursor ); } } } void MainWindow::updateRecentlyOpenedProjectsMenu() { m_recentlyOpenedProjectsMenu->clear(); QStringList rup = configManager::inst()->recentlyOpenedProjects(); for( QStringList::iterator it = rup.begin(); it != rup.end(); ++it ) { m_recentlyOpenedProjectsMenu->addAction( embed::getIconPixmap( "project_file" ), *it ); } } void MainWindow::openRecentlyOpenedProject( QAction * _action ) { const QString & f = _action->text(); setCursor( Qt::WaitCursor ); engine::getSong()->loadProject( f ); configManager::inst()->addRecentlyOpenedProject( f ); setCursor( Qt::ArrowCursor ); } bool MainWindow::saveProject() { if( engine::getSong()->projectFileName() == "" ) { return( saveProjectAs() ); } else { engine::getSong()->guiSaveProject(); } return( TRUE ); } bool MainWindow::saveProjectAs() { VersionedSaveDialog sfd( this, tr( "Save project" ), "", tr( "LMMS Project (*.mmpz *.mmp);;" "LMMS Project Template (*.mpt)" ) ); QString f = engine::getSong()->projectFileName(); if( f != "" ) { sfd.setDirectory( QFileInfo( f ).absolutePath() ); sfd.selectFile( QFileInfo( f ).fileName() ); } else { sfd.setDirectory( configManager::inst()->userProjectsDir() ); } if( sfd.exec () == FileDialog::Accepted && !sfd.selectedFiles().isEmpty() && sfd.selectedFiles()[0] != "" ) { engine::getSong()->guiSaveProjectAs( sfd.selectedFiles()[0] ); return( TRUE ); } return( FALSE ); } bool MainWindow::saveProjectAsNewVersion() { QString fileName = engine::getSong()->projectFileName(); if( fileName == "" ) { return saveProjectAs(); } else { do VersionedSaveDialog::changeFileNameVersion( fileName, true ); while ( QFile( fileName ).exists() ); engine::getSong()->guiSaveProjectAs( fileName ); return true; } } void MainWindow::showSettingsDialog() { setupDialog sd; sd.exec(); } void MainWindow::aboutLMMS() { aboutDialog().exec(); } void MainWindow::help() { QMessageBox::information( this, tr( "Help not available" ), tr( "Currently there's no help " "available in LMMS.\n" "Please visit " "http://lmms.sf.net/wiki " "for documentation on LMMS." ), QMessageBox::Ok ); } void MainWindow::toggleWindow( QWidget *window, bool forceShow ) { QWidget *parent = window->parentWidget(); if( forceShow || m_workspace->activeSubWindow() != parent || parent->isHidden() ) { parent->show(); window->show(); window->setFocus(); } else { parent->hide(); } // Workaround for Qt Bug #260116 m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); } void MainWindow::toggleBBEditorWin( bool forceShow ) { toggleWindow( engine::getBBEditor(), forceShow ); } void MainWindow::toggleSongEditorWin() { toggleWindow( engine::songEditor() ); } void MainWindow::toggleProjectNotesWin() { toggleWindow( engine::getProjectNotes() ); } void MainWindow::togglePianoRollWin() { toggleWindow( engine::pianoRoll() ); } void MainWindow::toggleAutomationEditorWin() { toggleWindow( engine::automationEditor() ); } void MainWindow::toggleFxMixerWin() { toggleWindow( engine::fxMixerView() ); } void MainWindow::toggleControllerRack() { toggleWindow( engine::getControllerRackView() ); } void MainWindow::updatePlayPauseIcons() { engine::songEditor()->setPauseIcon( false ); engine::automationEditor()->setPauseIcon( false ); engine::getBBEditor()->setPauseIcon( false ); engine::pianoRoll()->setPauseIcon( false ); if( engine::getSong()->isPlaying() ) { switch( engine::getSong()->playMode() ) { case song::Mode_PlaySong: engine::songEditor()->setPauseIcon( true ); break; case song::Mode_PlayAutomationPattern: engine::automationEditor()->setPauseIcon( true ); break; case song::Mode_PlayBB: engine::getBBEditor()->setPauseIcon( true ); break; case song::Mode_PlayPattern: engine::pianoRoll()->setPauseIcon( true ); break; default: break; } } } void MainWindow::undo() { engine::projectJournal()->undo(); } void MainWindow::redo() { engine::projectJournal()->redo(); } void MainWindow::closeEvent( QCloseEvent * _ce ) { if( mayChangeProject() ) { // delete recovery file QDir working(configManager::inst()->workingDir()); working.remove("recover.mmp"); _ce->accept(); } else { _ce->ignore(); } } void MainWindow::focusOutEvent( QFocusEvent * _fe ) { // when loosing focus we do not receive key-(release!)-events anymore, // so we might miss release-events of one the modifiers we're watching! clearKeyModifiers(); QMainWindow::leaveEvent( _fe ); } void MainWindow::keyPressEvent( QKeyEvent * _ke ) { switch( _ke->key() ) { case Qt::Key_Control: m_keyMods.m_ctrl = TRUE; break; case Qt::Key_Shift: m_keyMods.m_shift = TRUE; break; case Qt::Key_Alt: m_keyMods.m_alt = TRUE; break; default: { InstrumentTrackWindow * w = InstrumentTrackView::topLevelInstrumentTrackWindow(); if( w ) { w->pianoView()->keyPressEvent( _ke ); } if( !_ke->isAccepted() ) { QMainWindow::keyPressEvent( _ke ); } } } } void MainWindow::keyReleaseEvent( QKeyEvent * _ke ) { switch( _ke->key() ) { case Qt::Key_Control: m_keyMods.m_ctrl = FALSE; break; case Qt::Key_Shift: m_keyMods.m_shift = FALSE; break; case Qt::Key_Alt: m_keyMods.m_alt = FALSE; break; default: if( InstrumentTrackView::topLevelInstrumentTrackWindow() ) { InstrumentTrackView::topLevelInstrumentTrackWindow()-> pianoView()->keyReleaseEvent( _ke ); } if( !_ke->isAccepted() ) { QMainWindow::keyReleaseEvent( _ke ); } } } void MainWindow::timerEvent( QTimerEvent * _te) { emit periodicUpdate(); } void MainWindow::fillTemplatesMenu() { m_templatesMenu->clear(); QDir user_d( configManager::inst()->userProjectsDir() + "templates" ); QStringList templates = user_d.entryList( QStringList( "*.mpt" ), QDir::Files | QDir::Readable ); m_custom_templates_count = templates.count(); for( QStringList::iterator it = templates.begin(); it != templates.end(); ++it ) { m_templatesMenu->addAction( embed::getIconPixmap( "project_file" ), ( *it ).left( ( *it ).length() - 4 ) ); } QDir d( configManager::inst()->factoryProjectsDir() + "templates" ); templates = d.entryList( QStringList( "*.mpt" ), QDir::Files | QDir::Readable ); if( m_custom_templates_count > 0 && !templates.isEmpty() ) { m_templatesMenu->addSeparator(); } for( QStringList::iterator it = templates.begin(); it != templates.end(); ++it ) { m_templatesMenu->addAction( embed::getIconPixmap( "project_file" ), ( *it ).left( ( *it ).length() - 4 ) ); } } void MainWindow::showTool( QAction * _idx ) { PluginView * p = m_tools[m_toolsMenu->actions().indexOf( _idx )]; p->show(); p->parentWidget()->show(); p->setFocus(); } void MainWindow::browseHelp() { // file:// alternative for offline help QString url = "http://lmms.sf.net/wiki/index.php?title=Main_Page"; QDesktopServices::openUrl( url ); // TODO: Handle error } void MainWindow::autoSave() { if( !( engine::getSong()->isPlaying() || engine::getSong()->isExporting() ) ) { QDir work(configManager::inst()->workingDir()); engine::getSong()->saveProjectFile(work.absoluteFilePath("recover.mmp")); } else { // try again in 10 seconds QTimer::singleShot( 10*1000, this, SLOT( autoSave() ) ); } } #include "moc_MainWindow.cxx" lmms-1.0.0/src/core/0000755000175000017500000000000012313663627012724 5ustar tobytobylmms-1.0.0/src/core/main.cpp0000644000175000017500000003222212313663627014355 0ustar tobytoby/* * main.cpp - just main.cpp which is starting up app... * * Copyright (c) 2004-2014 Tobias Doerffel * Copyright (c) 2012-2013 Paul Giblock

* * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "lmmsconfig.h" #include "lmmsversion.h" #include "versioninfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LMMS_HAVE_SCHED_H #include #endif #ifdef LMMS_HAVE_SYS_TIME_H #include #endif #ifdef LMMS_HAVE_PROCESS_H #include #endif #ifdef LMMS_HAVE_UNISTD_H #include #endif #include "config_mgr.h" #include "embed.h" #include "engine.h" #include "LmmsStyle.h" #include "ImportFilter.h" #include "MainWindow.h" #include "ProjectRenderer.h" #include "DataFile.h" #include "song.h" static inline QString baseName( const QString & _file ) { return( QFileInfo( _file ).absolutePath() + "/" + QFileInfo( _file ).completeBaseName() ); } inline void loadTranslation( const QString & _tname, const QString & _dir = configManager::inst()->localeDir() ) { QTranslator * t = new QTranslator( QCoreApplication::instance() ); QString name = _tname + ".qm"; t->load( name, _dir ); QCoreApplication::instance()->installTranslator( t ); } int main( int argc, char * * argv ) { // intialize RNG srand( getpid() + time( 0 ) ); bool core_only = false; bool fullscreen = true; bool exit_after_import = false; QString file_to_load, file_to_save, file_to_import, render_out; for( int i = 1; i < argc; ++i ) { if( argc > i && ( ( QString( argv[i] ) == "--render" || QString( argv[i] ) == "-r" ) || ( QString( argv[i] ) == "--help" || QString( argv[i] ) == "-h" ) ) ) { core_only = true; } else if( argc > i && QString( argv[i] ) == "-geometry" ) { // option -geometry is filtered by Qt later, // so we need to check its presence now to // determine, if the application should run in // fullscreen mode (default, no -geometry given). fullscreen = false; } } QCoreApplication * app = core_only ? new QCoreApplication( argc, argv ) : new QApplication( argc, argv ) ; Mixer::qualitySettings qs( Mixer::qualitySettings::Mode_HighQuality ); ProjectRenderer::OutputSettings os( 44100, false, 160, ProjectRenderer::Depth_16Bit ); ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile; for( int i = 1; i < argc; ++i ) { if( QString( argv[i] ) == "--version" || QString( argv[i] ) == "-v" ) { printf( "\nLinux MultiMedia Studio %s\n(%s %s, Qt %s, %s)\n\n" "Copyright (c) 2004-2014 LMMS developers.\n\n" "This program is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU General Public\n" "License as published by the Free Software Foundation; either\n" "version 2 of the License, or (at your option) any later version.\n\n" "Try \"%s --help\" for more information.\n\n", LMMS_VERSION, PLATFORM, MACHINE, QT_VERSION_STR, GCC_VERSION, argv[0] ); return( EXIT_SUCCESS ); } else if( argc > i && ( QString( argv[i] ) == "--help" || QString( argv[i] ) == "-h" ) ) { printf( "\nLinux MultiMedia Studio %s\n" "Copyright (c) 2004-2014 LMMS developers.\n\n" "usage: lmms [ -r ] [ options ]\n" " [ -u ]\n" " [ -d ]\n" " [ -h ]\n" " [ ]\n\n" "-r, --render render given project file\n" "-o, --output render into \n" "-f, --output-format specify format of render-output where\n" " format is either 'wav' or 'ogg'.\n" "-s, --samplerate specify output samplerate in Hz\n" " range: 44100 (default) to 192000\n" "-b, --bitrate specify output bitrate in kHz\n" " default: 160.\n" "-i, --interpolation specify interpolation method\n" " possible values:\n" " - linear\n" " - sincfastest (default)\n" " - sincmedium\n" " - sincbest\n" "-x, --oversampling specify oversampling\n" " possible values: 1, 2, 4, 8\n" " default: 2\n" "-u, --upgrade [out] upgrade file and save as \n" " standard out is used if no output file is specifed\n" "-d, --dump dump XML of compressed file \n" "-v, --version show version information and exit.\n" "-h, --help show this usage information and exit.\n\n", LMMS_VERSION ); return( EXIT_SUCCESS ); } else if( argc > i+1 && ( QString( argv[i] ) == "--upgrade" || QString( argv[i] ) == "-u" ) ) { QString inFile( argv[i + 1] ); DataFile dataFile( inFile ); if (argc > i+2) { const QString outFile = argv[i + 2]; dataFile.writeFile( outFile ); } else { QTextStream ts( stdout ); dataFile.write( ts ); fflush( stdout ); } return( EXIT_SUCCESS ); } else if( argc > i && ( QString( argv[i] ) == "--dump" || QString( argv[i] ) == "-d" ) ) { QFile f( argv[i + 1] ); f.open( QIODevice::ReadOnly ); QString d = qUncompress( f.readAll() ); printf( "%s\n", d.toUtf8().constData() ); return( EXIT_SUCCESS ); } else if( argc > i && ( QString( argv[i] ) == "--render" || QString( argv[i] ) == "-r" ) ) { file_to_load = QString( argv[i + 1] ); render_out = baseName( file_to_load ) + "."; ++i; } else if( argc > i && ( QString( argv[i] ) == "--output" || QString( argv[i] ) == "-o" ) ) { render_out = baseName( QString( argv[i + 1] ) ) + "."; ++i; } else if( argc > i && ( QString( argv[i] ) == "--format" || QString( argv[i] ) == "-f" ) ) { const QString ext = QString( argv[i + 1] ); if( ext == "wav" ) { eff = ProjectRenderer::WaveFile; } #ifdef LMMS_HAVE_OGGVORBIS else if( ext == "ogg" ) { eff = ProjectRenderer::OggFile; } #endif else { printf( "\nInvalid output format %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); return( EXIT_FAILURE ); } ++i; } else if( argc > i && ( QString( argv[i] ) == "--samplerate" || QString( argv[i] ) == "-s" ) ) { sample_rate_t sr = QString( argv[i + 1] ).toUInt(); if( sr >= 44100 && sr <= 192000 ) { os.samplerate = sr; } else { printf( "\nInvalid samplerate %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); return( EXIT_FAILURE ); } ++i; } else if( argc > i && ( QString( argv[i] ) == "--bitrate" || QString( argv[i] ) == "-b" ) ) { int br = QString( argv[i + 1] ).toUInt(); if( br >= 64 && br <= 384 ) { os.bitrate = br; } else { printf( "\nInvalid bitrate %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); return( EXIT_FAILURE ); } ++i; } else if( argc > i && ( QString( argv[i] ) == "--interpolation" || QString( argv[i] ) == "-i" ) ) { const QString ip = QString( argv[i + 1] ); if( ip == "linear" ) { qs.interpolation = Mixer::qualitySettings::Interpolation_Linear; } else if( ip == "sincfastest" ) { qs.interpolation = Mixer::qualitySettings::Interpolation_SincFastest; } else if( ip == "sincmedium" ) { qs.interpolation = Mixer::qualitySettings::Interpolation_SincMedium; } else if( ip == "sincbest" ) { qs.interpolation = Mixer::qualitySettings::Interpolation_SincBest; } else { printf( "\nInvalid interpolation method %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); return( EXIT_FAILURE ); } ++i; } else if( argc > i && ( QString( argv[i] ) == "--oversampling" || QString( argv[i] ) == "-x" ) ) { int o = QString( argv[i + 1] ).toUInt(); switch( o ) { case 1: qs.oversampling = Mixer::qualitySettings::Oversampling_None; break; case 2: qs.oversampling = Mixer::qualitySettings::Oversampling_2x; break; case 4: qs.oversampling = Mixer::qualitySettings::Oversampling_4x; break; case 8: qs.oversampling = Mixer::qualitySettings::Oversampling_8x; break; default: printf( "\nInvalid oversampling %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i + 1], argv[0] ); return( EXIT_FAILURE ); } ++i; } else if( argc > i && ( QString( argv[i] ) == "--import" ) ) { file_to_import = argv[i+1]; ++i; // exit after import? (only for debugging) if( argc > i && QString( argv[i+1] ) == "-e" ) { exit_after_import = true; } } else { if( argv[i][0] == '-' ) { printf( "\nInvalid option %s.\n\n" "Try \"%s --help\" for more information.\n\n", argv[i], argv[0] ); return( EXIT_FAILURE ); } file_to_load = argv[i]; } } QString pos = QLocale::system().name().left( 2 ); #ifdef LMMS_BUILD_WIN32 #undef QT_TRANSLATIONS_DIR #define QT_TRANSLATIONS_DIR configManager::inst()->localeDir() #endif #ifdef QT_TRANSLATIONS_DIR // load translation for Qt-widgets/-dialogs loadTranslation( QString( "qt_" ) + pos, QString( QT_TRANSLATIONS_DIR ) ); #endif // load actual translation for LMMS loadTranslation( pos ); // try to set realtime priority #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_SCHED_H #ifndef __OpenBSD__ struct sched_param sparam; sparam.sched_priority = ( sched_get_priority_max( SCHED_FIFO ) + sched_get_priority_min( SCHED_FIFO ) ) / 2; if( sched_setscheduler( 0, SCHED_FIFO, &sparam ) == -1 ) { printf( "Notice: could not set realtime priority.\n" ); } #endif #endif #endif configManager::inst()->loadConfigFile(); if( render_out.isEmpty() ) { // init style and palette QApplication::setStyle( new LmmsStyle() ); // show splash screen QSplashScreen splashScreen( embed::getIconPixmap( "splash" ) ); splashScreen.show(); splashScreen.showMessage( MainWindow::tr( "Version %1" ).arg( LMMS_VERSION ), Qt::AlignRight | Qt::AlignBottom, Qt::white ); qApp->processEvents(); // init central engine which handles all components of LMMS engine::init(); splashScreen.hide(); // re-intialize RNG - shared libraries might have srand() or // srandom() calls in their init procedure srand( getpid() + time( 0 ) ); // recover a file? QString recoveryFile = QDir(configManager::inst()->workingDir()).absoluteFilePath("recover.dataFile"); if( QFileInfo(recoveryFile).exists() && QMessageBox::question( engine::mainWindow(), MainWindow::tr( "Project recovery" ), MainWindow::tr( "It looks like the last session did not end properly. " "Do you want to recover the project of this session?" ), QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes ) { file_to_load = recoveryFile; } // we try to load given file if( !file_to_load.isEmpty() ) { engine::mainWindow()->show(); if( fullscreen ) { engine::mainWindow()->showMaximized(); } if( file_to_load == recoveryFile ) { engine::getSong()->createNewProjectFromTemplate( file_to_load ); } else { engine::getSong()->loadProject( file_to_load ); } } else if( !file_to_import.isEmpty() ) { ImportFilter::import( file_to_import, engine::getSong() ); if( exit_after_import ) { return 0; } engine::mainWindow()->show(); if( fullscreen ) { engine::mainWindow()->showMaximized(); } } else { engine::getSong()->createNewProject(); // [Settel] workaround: showMaximized() doesn't work with // FVWM2 unless the window is already visible -> show() first engine::mainWindow()->show(); if( fullscreen ) { engine::mainWindow()->showMaximized(); } } } else { // we're going to render our song engine::init( false ); printf( "loading project...\n" ); engine::getSong()->loadProject( file_to_load ); printf( "done\n" ); // create renderer ProjectRenderer * r = new ProjectRenderer( qs, os, eff, render_out + QString( ( eff == ProjectRenderer::WaveFile ) ? "wav" : "ogg" ) ); QCoreApplication::instance()->connect( r, SIGNAL( finished() ), SLOT( quit() ) ); // timer for progress-updates QTimer * t = new QTimer( r ); r->connect( t, SIGNAL( timeout() ), SLOT( updateConsoleProgress() ) ); t->start( 200 ); // start now! r->startProcessing(); } const int ret = app->exec(); delete app; return( ret ); } /* vim: set tw=0 noexpandtab: */ lmms-1.0.0/src/core/PresetPreviewPlayHandle.cpp0000644000175000017500000001251612313663627020203 0ustar tobytoby/* * PresetPreviewPlayHandle.cpp - implementation of class PresetPreviewPlayHandle * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "PresetPreviewPlayHandle.h" #include "debug.h" #include "engine.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "MidiPort.h" #include "DataFile.h" #include "NotePlayHandle.h" #include "ProjectJournal.h" #include "TrackContainer.h" // invisible track-container which is needed as parent for preview-channels class PreviewTrackContainer : public TrackContainer { public: PreviewTrackContainer() : m_previewInstrumentTrack( NULL ), m_previewNote( NULL ), m_dataMutex() { setJournalling( FALSE ); m_previewInstrumentTrack = dynamic_cast( track::create( track::InstrumentTrack, this ) ); m_previewInstrumentTrack->setJournalling( FALSE ); } virtual ~PreviewTrackContainer() { } virtual QString nodeName() const { return "previewtrackcontainer"; } InstrumentTrack* previewInstrumentTrack() { return m_previewInstrumentTrack; } NotePlayHandle* previewNote() { return m_previewNote; } void setPreviewNote( NotePlayHandle * _note ) { m_previewNote = _note; } void lockData() { m_dataMutex.lock(); } void unlockData() { m_dataMutex.unlock(); } bool isPreviewing() { bool ret = !m_dataMutex.tryLock(); if( ret == false ) { m_dataMutex.unlock(); } return ret; } private: InstrumentTrack* m_previewInstrumentTrack; NotePlayHandle* m_previewNote; QMutex m_dataMutex; friend class PresetPreviewPlayHandle; } ; PreviewTrackContainer * PresetPreviewPlayHandle::s_previewTC; PresetPreviewPlayHandle::PresetPreviewPlayHandle( const QString & _preset_file, bool _load_by_plugin ) : PlayHandle( TypePresetPreviewHandle ), m_previewNote( NULL ) { s_previewTC->lockData(); if( s_previewTC->previewNote() != NULL ) { s_previewTC->previewNote()->mute(); } const bool j = engine::projectJournal()->isJournalling(); engine::projectJournal()->setJournalling( FALSE ); engine::setSuppressMessages( true ); if( _load_by_plugin ) { Instrument * i = s_previewTC->previewInstrumentTrack()->instrument(); const QString ext = QFileInfo( _preset_file ). suffix().toLower(); if( i == NULL || !i->descriptor()->supportsFileType( ext ) ) { i = s_previewTC->previewInstrumentTrack()-> loadInstrument( engine::pluginFileHandling()[ext] ); } if( i != NULL ) { i->loadFile( _preset_file ); } } else { DataFile dataFile( _preset_file ); s_previewTC->previewInstrumentTrack()-> loadTrackSpecificSettings( dataFile.content().firstChild().toElement() ); } engine::setSuppressMessages( false ); // make sure, our preset-preview-track does not appear in any MIDI- // devices list, so just disable receiving/sending MIDI-events at all s_previewTC->previewInstrumentTrack()-> midiPort()->setMode( MidiPort::Disabled ); // create note-play-handle for it m_previewNote = new NotePlayHandle( s_previewTC->previewInstrumentTrack(), 0, typeInfo::max() / 2, note( 0, 0, DefaultKey, 100 ) ); s_previewTC->setPreviewNote( m_previewNote ); s_previewTC->unlockData(); engine::projectJournal()->setJournalling( j ); } PresetPreviewPlayHandle::~PresetPreviewPlayHandle() { s_previewTC->lockData(); // not muted by other preset-preview-handle? if( !m_previewNote->isMuted() ) { // then set according state s_previewTC->setPreviewNote( NULL ); } delete m_previewNote; s_previewTC->unlockData(); } void PresetPreviewPlayHandle::play( sampleFrame * _working_buffer ) { m_previewNote->play( _working_buffer ); } bool PresetPreviewPlayHandle::isFinished() const { return m_previewNote->isMuted(); } bool PresetPreviewPlayHandle::isFromTrack( const track * _track ) const { return s_previewTC->previewInstrumentTrack() == _track; } void PresetPreviewPlayHandle::init() { if( !s_previewTC ) { s_previewTC = new PreviewTrackContainer; } } void PresetPreviewPlayHandle::cleanup() { delete s_previewTC; s_previewTC = NULL; } ConstNotePlayHandleList PresetPreviewPlayHandle::nphsOfInstrumentTrack( const InstrumentTrack * _it ) { ConstNotePlayHandleList cnphv; s_previewTC->lockData(); if( s_previewTC->previewNote() != NULL && s_previewTC->previewNote()->instrumentTrack() == _it ) { cnphv.push_back( s_previewTC->previewNote() ); } s_previewTC->unlockData(); return cnphv; } bool PresetPreviewPlayHandle::isPreviewing() { return s_previewTC->isPreviewing(); } lmms-1.0.0/src/core/bb_track_container.cpp0000644000175000017500000001212412313663627017241 0ustar tobytoby/* * bb_track_container.cpp - model-component of BB-Editor * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "bb_track_container.h" #include "bb_track.h" #include "combobox.h" #include "embed.h" #include "engine.h" #include "song.h" bbTrackContainer::bbTrackContainer() : TrackContainer(), m_bbComboBoxModel( this ) { connect( &m_bbComboBoxModel, SIGNAL( dataChanged() ), this, SLOT( currentBBChanged() ) ); // we *always* want to receive updates even in case BB actually did // not change upon setCurrentBB()-call connect( &m_bbComboBoxModel, SIGNAL( dataUnchanged() ), this, SLOT( currentBBChanged() ) ); } bbTrackContainer::~bbTrackContainer() { } bool bbTrackContainer::play( MidiTime _start, fpp_t _frames, f_cnt_t _offset, int _tco_num ) { bool played_a_note = false; if( lengthOfBB( _tco_num ) <= 0 ) { return false; } _start = _start % ( lengthOfBB( _tco_num ) * MidiTime::ticksPerTact() ); TrackList tl = tracks(); for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { if( ( *it )->play( _start, _frames, _offset, _tco_num ) ) { played_a_note = true; } } return played_a_note; } void bbTrackContainer::updateAfterTrackAdd() { if( numOfBBs() == 0 && !engine::getSong()->isLoadingProject() ) { engine::getSong()->addBBTrack(); } // make sure, new track(s) have TCOs for every beat/bassline for( int i = 0; i < qMax( 1, numOfBBs() ); ++i ) { createTCOsForBB( i ); } } tact_t bbTrackContainer::lengthOfBB( int _bb ) { MidiTime max_length = MidiTime::ticksPerTact(); const TrackList & tl = tracks(); for( TrackList::const_iterator it = tl.begin(); it != tl.end(); ++it ) { max_length = qMax( max_length, ( *it )->getTCO( _bb )->length() ); } return max_length.nextFullTact(); } int bbTrackContainer::numOfBBs() const { return engine::getSong()->countTracks( track::BBTrack ); } void bbTrackContainer::removeBB( int _bb ) { TrackList tl = tracks(); for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { delete ( *it )->getTCO( _bb ); ( *it )->removeTact( _bb * DefaultTicksPerTact ); } if( _bb <= currentBB() ) { setCurrentBB( qMax( currentBB() - 1, 0 ) ); } } void bbTrackContainer::swapBB( int _bb1, int _bb2 ) { TrackList tl = tracks(); for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { ( *it )->swapPositionOfTCOs( _bb1, _bb2 ); } updateComboBox(); } void bbTrackContainer::updateBBTrack( trackContentObject * _tco ) { bbTrack * t = bbTrack::findBBTrack( _tco->startPosition() / DefaultTicksPerTact ); if( t != NULL ) { t->dataChanged(); } } void bbTrackContainer::fixIncorrectPositions() { TrackList tl = tracks(); for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { for( int i = 0; i < numOfBBs(); ++i ) { ( *it )->getTCO( i )->movePosition( MidiTime( i, 0 ) ); } } } void bbTrackContainer::play() { if( engine::getSong()->playMode() != song::Mode_PlayBB ) { engine::getSong()->playBB(); } else { engine::getSong()->togglePause(); } } void bbTrackContainer::stop() { engine::getSong()->stop(); } void bbTrackContainer::updateComboBox() { const int cur_bb = currentBB(); m_bbComboBoxModel.clear(); for( int i = 0; i < numOfBBs(); ++i ) { bbTrack * bbt = bbTrack::findBBTrack( i ); m_bbComboBoxModel.addItem( bbt->name() ); } setCurrentBB( cur_bb ); } void bbTrackContainer::currentBBChanged() { // first make sure, all channels have a TCO at current BB createTCOsForBB( currentBB() ); // now update all track-labels (the current one has to become white, // the others gray) TrackList tl = engine::getSong()->tracks(); for( TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { if( ( *it )->type() == track::BBTrack ) { ( *it )->dataChanged(); } } } void bbTrackContainer::createTCOsForBB( int _bb ) { if( numOfBBs() == 0 || engine::getSong()->isLoadingProject() ) { return; } TrackList tl = tracks(); for( int i = 0; i < tl.size(); ++i ) { while( tl[i]->numOfTCOs() < _bb + 1 ) { MidiTime position = MidiTime( tl[i]->numOfTCOs(), 0 ); trackContentObject * tco = tl[i]->createTCO( position ); tco->movePosition( position ); tco->changeLength( MidiTime( 1, 0 ) ); } } } #include "moc_bb_track_container.cxx" lmms-1.0.0/src/core/LfoController.cpp0000644000175000017500000001446412313663627016225 0ustar tobytoby/* * LfoController.cpp - implementation of class controller which handles * remote-control of AutomatableModels * * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "song.h" #include "engine.h" #include "Mixer.h" #include "LfoController.h" #include "ControllerDialog.h" //const float TWO_PI = 6.28318531f; LfoController::LfoController( Model * _parent ) : Controller( Controller::LfoController, _parent, tr( "LFO Controller" ) ), m_baseModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Base value" ) ), m_speedModel( 2.0, 0.01, 20.0, 0.0001, 20000.0, this, tr( "Oscillator speed" ) ), m_amountModel( 1.0, -1.0, 1.0, 0.005, this, tr( "Oscillator amount" ) ), m_phaseModel( 0.0, 0.0, 360.0, 4.0, this, tr( "Oscillator phase" ) ), m_waveModel( Oscillator::SineWave, 0, Oscillator::NumWaveShapes, this, tr( "Oscillator waveform" ) ), m_multiplierModel( 0, 0, 2, this, tr( "Frequency Multiplier" ) ), m_duration( 1000 ), m_phaseCorrection( 0 ), m_phaseOffset( 0 ), m_sampleFunction( &Oscillator::sinSample ), m_userDefSampleBuffer( new SampleBuffer ) { connect( &m_waveModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleFunction() ) ); } LfoController::~LfoController() { sharedObject::unref( m_userDefSampleBuffer ); m_baseModel.disconnect( this ); m_speedModel.disconnect( this ); m_amountModel.disconnect( this ); m_phaseModel.disconnect( this ); m_waveModel.disconnect( this ); m_multiplierModel.disconnect( this ); } // This code took forever to get right. It can // definately be optimized. // The code should probably be integrated with the oscillator class. But I // don't know how to use oscillator because it is so confusing float LfoController::value( int _offset ) { int frame = runningFrames() + _offset + m_phaseCorrection; //If the song is playing, sync the value with the time of the song. if( engine::getSong()->isPlaying() || engine::getSong()->isExporting() ) { // The new duration in frames // (Samples/Second) / (periods/second) = (Samples/cycle) float newDurationF = engine::mixer()->processingSampleRate() * m_speedModel.value(); switch(m_multiplierModel.value() ) { case 1: newDurationF /= 100.0; break; case 2: newDurationF *= 100.0; break; default: break; } m_phaseOffset = qRound( m_phaseModel.value() * newDurationF / 360.0 ); int newDuration = static_cast( newDurationF ); m_phaseCorrection = static_cast(engine::getSong()->getTicks()*engine::framesPerTick())%newDuration + m_phaseOffset; // re-run the first calculation again frame = m_phaseCorrection + _offset; } // speedModel 0..1 fast..slow 0ms..20000ms // duration m_duration // // frames / (20seconds of frames) float sampleFrame = float( frame+m_phaseOffset ) / (engine::mixer()->processingSampleRate() * m_speedModel.value() ); switch(m_multiplierModel.value() ) { case 1: sampleFrame *= 100.0; break; case 2: sampleFrame /= 100.0; break; default: break; } // 44100 frames/sec return m_baseModel.value() + ( m_amountModel.value() * ( m_sampleFunction != NULL ? m_sampleFunction(sampleFrame): m_userDefSampleBuffer->userWaveSample(sampleFrame) ) / 2.0f ); } void LfoController::updateSampleFunction() { switch( m_waveModel.value() ) { case Oscillator::SineWave: m_sampleFunction = &Oscillator::sinSample; break; case Oscillator::TriangleWave: m_sampleFunction = &Oscillator::triangleSample; break; case Oscillator::SawWave: m_sampleFunction = &Oscillator::sawSample; break; case Oscillator::SquareWave: m_sampleFunction = &Oscillator::squareSample; break; case Oscillator::MoogSawWave: m_sampleFunction = &Oscillator::moogSawSample; break; case Oscillator::ExponentialWave: m_sampleFunction = &Oscillator::expSample; break; case Oscillator::WhiteNoise: m_sampleFunction = &Oscillator::noiseSample; break; case Oscillator::UserDefinedWave: m_sampleFunction = NULL; /*TODO: If C++11 is allowed, should change the type of m_sampleFunction be std::function and use the line below: */ //m_sampleFunction = &(m_userDefSampleBuffer->userWaveSample) break; } } void LfoController::saveSettings( QDomDocument & _doc, QDomElement & _this ) { Controller::saveSettings( _doc, _this ); m_baseModel.saveSettings( _doc, _this, "base" ); m_speedModel.saveSettings( _doc, _this, "speed" ); m_amountModel.saveSettings( _doc, _this, "amount" ); m_phaseModel.saveSettings( _doc, _this, "phase" ); m_waveModel.saveSettings( _doc, _this, "wave" ); m_multiplierModel.saveSettings( _doc, _this, "multiplier" ); _this.setAttribute( "userwavefile" , m_userDefSampleBuffer->audioFile() ); } void LfoController::loadSettings( const QDomElement & _this ) { Controller::loadSettings( _this ); m_baseModel.loadSettings( _this, "base" ); m_speedModel.loadSettings( _this, "speed" ); m_amountModel.loadSettings( _this, "amount" ); m_phaseModel.loadSettings( _this, "phase" ); m_waveModel.loadSettings( _this, "wave" ); m_multiplierModel.loadSettings( _this, "multiplier" ); m_userDefSampleBuffer->setAudioFile( _this.attribute("userwavefile" ) ); updateSampleFunction(); } QString LfoController::nodeName() const { return( "lfocontroller" ); } ControllerDialog * LfoController::createDialog( QWidget * _parent ) { return new LfoControllerDialog( this, _parent ); } #include "moc_LfoController.cxx" lmms-1.0.0/src/core/NotePlayHandle.cpp0000644000175000017500000003161412313663627016304 0ustar tobytoby/* * NotePlayHandle.cpp - implementation of class NotePlayHandle which manages * playback of a single note by an instrument * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "NotePlayHandle.h" #include "basic_filters.h" #include "config_mgr.h" #include "DetuningHelper.h" #include "InstrumentSoundShaping.h" #include "InstrumentTrack.h" #include "MidiEvent.h" #include "MidiPort.h" #include "song.h" NotePlayHandle::BaseDetuning::BaseDetuning( DetuningHelper *detuning ) : m_value( detuning ? detuning->automationPattern()->valueAt( 0 ) : 0 ) { } NotePlayHandle::NotePlayHandle( InstrumentTrack* instrumentTrack, const f_cnt_t _offset, const f_cnt_t _frames, const note& n, NotePlayHandle *parent, const bool _part_of_arp, int midiEventChannel, Origin origin ) : PlayHandle( TypeNotePlayHandle, _offset ), note( n.length(), n.pos(), n.key(), n.getVolume(), n.getPanning(), n.detuning() ), m_pluginData( NULL ), m_filter( NULL ), m_instrumentTrack( instrumentTrack ), m_frames( 0 ), m_totalFramesPlayed( 0 ), m_framesBeforeRelease( 0 ), m_releaseFramesToDo( 0 ), m_releaseFramesDone( 0 ), m_released( false ), m_topNote( parent == NULL ), m_partOfArpeggio( _part_of_arp ), m_muted( false ), m_bbTrack( NULL ), m_origTempo( engine::getSong()->getTempo() ), m_origBaseNote( instrumentTrack->baseNoteModel()->value() ), m_frequency( 0 ), m_unpitchedFrequency( 0 ), m_baseDetuning( NULL ), m_songGlobalParentOffset( 0 ), m_midiChannel( midiEventChannel >= 0 ? midiEventChannel : instrumentTrack->midiPort()->realOutputChannel() ), m_origin( origin ) { if( isTopNote() ) { m_baseDetuning = new BaseDetuning( detuning() ); m_instrumentTrack->m_processHandles.push_back( this ); } else { m_baseDetuning = parent->m_baseDetuning; parent->m_subNotes.push_back( this ); // if there was an arp-note added and parent is a base-note // we set arp-note-flag for indicating that parent is an // arpeggio-base-note parent->m_partOfArpeggio = isPartOfArpeggio() && parent->isTopNote(); m_bbTrack = parent->m_bbTrack; } updateFrequency(); setFrames( _frames ); // inform attached components about new MIDI note (used for recording in Piano Roll) if( m_origin == OriginMidiInput ) { m_instrumentTrack->midiNoteOn( *this ); } if( !isTopNote() || !instrumentTrack->isArpeggioEnabled() ) { const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); // send MidiNoteOn event m_instrumentTrack->processOutEvent( MidiEvent( MidiNoteOn, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ), MidiTime::fromFrames( offset(), engine::framesPerTick() ) ); } } NotePlayHandle::~NotePlayHandle() { noteOff( 0 ); if( isTopNote() ) { delete m_baseDetuning; m_instrumentTrack->m_processHandles.removeAll( this ); } if( m_pluginData != NULL ) { m_instrumentTrack->deleteNotePluginData( this ); } if( m_instrumentTrack->m_notes[key()] == this ) { m_instrumentTrack->m_notes[key()] = NULL; } for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it ) { delete *it; } m_subNotes.clear(); delete m_filter; } void NotePlayHandle::setVolume( volume_t _volume ) { note::setVolume( _volume ); const int baseVelocity = m_instrumentTrack->midiPort()->baseVelocity(); m_instrumentTrack->processOutEvent( MidiEvent( MidiKeyPressure, midiChannel(), midiKey(), midiVelocity( baseVelocity ) ) ); } void NotePlayHandle::setPanning( panning_t panning ) { note::setPanning( panning ); MidiEvent event( MidiMetaEvent, midiChannel(), midiKey(), panningToMidi( panning ) ); event.setMetaEvent( MidiNotePanning ); m_instrumentTrack->processOutEvent( event ); } int NotePlayHandle::midiKey() const { return key() - m_origBaseNote + instrumentTrack()->baseNoteModel()->value(); } void NotePlayHandle::play( sampleFrame * _working_buffer ) { if( m_muted ) { return; } if( m_released == false && instrumentTrack()->isSustainPedalPressed() == false && m_totalFramesPlayed + engine::mixer()->framesPerPeriod() >= m_frames ) { noteOff( m_frames - m_totalFramesPlayed ); } // under some circumstances we're called even if there's nothing to play // therefore do an additional check which fixes crash e.g. when // decreasing release of an instrument-track while the note is active if( framesLeft() > 0 ) { // play note! m_instrumentTrack->playNote( this, _working_buffer ); } if( m_released ) { f_cnt_t todo = engine::mixer()->framesPerPeriod(); // if this note is base-note for arpeggio, always set // m_releaseFramesToDo to bigger value than m_releaseFramesDone // because we do not allow NotePlayHandle::isFinished() to be true // until all sub-notes are completely played and no new ones // are inserted by arpAndChordsTabWidget::processNote() if( isArpeggioBaseNote() ) { m_releaseFramesToDo = m_releaseFramesDone + 2 * engine::mixer()->framesPerPeriod(); } // look whether we have frames left to be done before release if( m_framesBeforeRelease ) { // yes, then look whether these samples can be played // within one audio-buffer if( m_framesBeforeRelease <= engine::mixer()->framesPerPeriod() ) { // yes, then we did less releaseFramesDone todo -= m_framesBeforeRelease; m_framesBeforeRelease = 0; } else { // no, then just decrease framesBeforeRelease // and wait for next loop... (we're not in // release-phase yet) todo = 0; m_framesBeforeRelease -= engine::mixer()->framesPerPeriod(); } } // look whether we're in release-phase if( todo && m_releaseFramesDone < m_releaseFramesToDo ) { // check whether we have to do more frames in current // loop than left in current loop if( m_releaseFramesToDo - m_releaseFramesDone >= todo ) { // yes, then increase number of release-frames // done m_releaseFramesDone += todo; } else { // no, we did all in this loop! m_releaseFramesDone = m_releaseFramesToDo; } } } // play sub-notes (e.g. chords) for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ) { ( *it )->play( _working_buffer ); if( ( *it )->isFinished() ) { delete *it; it = m_subNotes.erase( it ); } else { ++it; } } // if this note is a base-note and there're no more sub-notes left we // can set m_releaseFramesDone to m_releaseFramesToDo so that // NotePlayHandle::isFinished() returns true and also this base-note is // removed from mixer's active note vector if( m_released && isArpeggioBaseNote() && m_subNotes.size() == 0 ) { m_releaseFramesDone = m_releaseFramesToDo; m_frames = 0; } // update internal data m_totalFramesPlayed += engine::mixer()->framesPerPeriod(); } f_cnt_t NotePlayHandle::framesLeft() const { if( instrumentTrack()->isSustainPedalPressed() ) { return 4*engine::mixer()->framesPerPeriod(); } else if( m_released && actualReleaseFramesToDo() == 0 ) { return m_framesBeforeRelease; } else if( m_released && actualReleaseFramesToDo() >= m_releaseFramesDone ) { return m_framesBeforeRelease + actualReleaseFramesToDo() - m_releaseFramesDone; } return m_frames+actualReleaseFramesToDo()-m_totalFramesPlayed; } fpp_t NotePlayHandle::framesLeftForCurrentPeriod() const { return (fpp_t) qMin( framesLeft(), engine::mixer()->framesPerPeriod() ); } bool NotePlayHandle::isFromTrack( const track * _track ) const { return m_instrumentTrack == _track || m_bbTrack == _track; } void NotePlayHandle::noteOff( const f_cnt_t _s ) { if( m_released ) { return; } // first note-off all sub-notes for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it ) { ( *it )->noteOff( _s ); } // then set some variables indicating release-state m_framesBeforeRelease = _s; m_releaseFramesToDo = qMax( 0, m_instrumentTrack->m_soundShaping.releaseFrames() ); if( !isTopNote() || !instrumentTrack()->isArpeggioEnabled() ) { // send MidiNoteOff event m_instrumentTrack->processOutEvent( MidiEvent( MidiNoteOff, midiChannel(), midiKey(), 0 ), MidiTime::fromFrames( m_framesBeforeRelease, engine::framesPerTick() ) ); } // inform attached components about MIDI finished (used for recording in Piano Roll) if( m_origin == OriginMidiInput ) { setLength( MidiTime( static_cast( totalFramesPlayed() / engine::framesPerTick() ) ) ); m_instrumentTrack->midiNoteOff( *this ); } m_released = true; } f_cnt_t NotePlayHandle::actualReleaseFramesToDo() const { return m_instrumentTrack->m_soundShaping.releaseFrames(/* isArpeggioBaseNote()*/ ); } void NotePlayHandle::setFrames( const f_cnt_t _frames ) { m_frames = _frames; if( m_frames == 0 ) { m_frames = m_instrumentTrack->beatLen( this ); } m_origFrames = m_frames; } float NotePlayHandle::volumeLevel( const f_cnt_t _frame ) { return m_instrumentTrack->m_soundShaping.volumeLevel( this, _frame ); } bool NotePlayHandle::isArpeggioBaseNote() const { return isTopNote() && ( m_partOfArpeggio || m_instrumentTrack->isArpeggioEnabled() ); } void NotePlayHandle::mute() { // mute all sub-notes for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it ) { ( *it )->mute(); } m_muted = true; } int NotePlayHandle::index() const { const PlayHandleList & playHandles = engine::mixer()->playHandles(); int idx = 0; for( PlayHandleList::ConstIterator it = playHandles.begin(); it != playHandles.end(); ++it ) { const NotePlayHandle * nph = dynamic_cast( *it ); if( nph == NULL || nph->m_instrumentTrack != m_instrumentTrack || nph->isReleased() ) { continue; } if( nph == this ) { break; } ++idx; } return idx; } ConstNotePlayHandleList NotePlayHandle::nphsOfInstrumentTrack( const InstrumentTrack * _it, bool _all_ph ) { const PlayHandleList & playHandles = engine::mixer()->playHandles(); ConstNotePlayHandleList cnphv; for( PlayHandleList::ConstIterator it = playHandles.begin(); it != playHandles.end(); ++it ) { const NotePlayHandle * nph = dynamic_cast( *it ); if( nph != NULL && nph->m_instrumentTrack == _it && ( nph->isReleased() == false || _all_ph == true ) ) { cnphv.push_back( nph ); } } return cnphv; } bool NotePlayHandle::operator==( const NotePlayHandle & _nph ) const { return length() == _nph.length() && pos() == _nph.pos() && key() == _nph.key() && getVolume() == _nph.getVolume() && getPanning() == _nph.getPanning() && m_instrumentTrack == _nph.m_instrumentTrack && m_frames == _nph.m_frames && offset() == _nph.offset() && m_totalFramesPlayed == _nph.m_totalFramesPlayed && m_released == _nph.m_released && m_topNote == _nph.m_topNote && m_partOfArpeggio == _nph.m_partOfArpeggio && m_origBaseNote == _nph.m_origBaseNote && m_muted == _nph.m_muted; } void NotePlayHandle::updateFrequency() { const float pitch = ( key() - m_instrumentTrack->baseNoteModel()->value() + engine::getSong()->masterPitch() + m_baseDetuning->value() ) / 12.0f; m_frequency = BaseFreq * powf( 2.0f, pitch + m_instrumentTrack->pitchModel()->value() / ( 100 * 12.0f ) ); m_unpitchedFrequency = BaseFreq * powf( 2.0f, pitch ); for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it ) { ( *it )->updateFrequency(); } } void NotePlayHandle::processMidiTime( const MidiTime& time ) { if( detuning() && time >= songGlobalParentOffset()+pos() ) { const float v = detuning()->automationPattern()->valueAt( time - songGlobalParentOffset() - pos() ); if( !typeInfo::isEqual( v, m_baseDetuning->value() ) ) { m_baseDetuning->setValue( v ); updateFrequency(); } } } void NotePlayHandle::resize( const bpm_t _new_tempo ) { double completed = m_totalFramesPlayed / (double) m_frames; double new_frames = m_origFrames * m_origTempo / (double) _new_tempo; m_frames = (f_cnt_t)new_frames; m_totalFramesPlayed = (f_cnt_t)( completed * new_frames ); for( NotePlayHandleList::Iterator it = m_subNotes.begin(); it != m_subNotes.end(); ++it ) { ( *it )->resize( _new_tempo ); } } lmms-1.0.0/src/core/song.cpp0000644000175000017500000006456612313663627014417 0ustar tobytoby/* * song.cpp - root of the model tree * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "song.h" #include "AutomationTrack.h" #include "AutomationEditor.h" #include "bb_editor.h" #include "bb_track.h" #include "bb_track_container.h" #include "config_mgr.h" #include "ControllerRackView.h" #include "ControllerConnection.h" #include "embed.h" #include "EnvelopeAndLfoParameters.h" #include "export_project_dialog.h" #include "FxMixer.h" #include "FxMixerView.h" #include "ImportFilter.h" #include "InstrumentTrack.h" #include "MainWindow.h" #include "FileDialog.h" #include "MidiClient.h" #include "DataFile.h" #include "NotePlayHandle.h" #include "pattern.h" #include "PianoRoll.h" #include "ProjectJournal.h" #include "project_notes.h" #include "ProjectRenderer.h" #include "rename_dialog.h" #include "SongEditor.h" #include "templates.h" #include "text_float.h" #include "timeline.h" #include "PeakController.h" tick_t MidiTime::s_ticksPerTact = DefaultTicksPerTact; song::song() : TrackContainer(), m_globalAutomationTrack( dynamic_cast( track::create( track::HiddenAutomationTrack, this ) ) ), m_tempoModel( DefaultTempo, MinTempo, MaxTempo, this, tr( "Tempo" ) ), m_timeSigModel( this ), m_oldTicksPerTact( DefaultTicksPerTact ), m_masterVolumeModel( 100, 0, 200, this, tr( "Master volume" ) ), m_masterPitchModel( 0, -12, 12, this, tr( "Master pitch" ) ), m_fileName(), m_oldFileName(), m_modified( false ), m_recording( false ), m_exporting( false ), m_exportLoop( false ), m_playing( false ), m_paused( false ), m_loadingProject( false ), m_playMode( Mode_None ), m_length( 0 ), m_trackToPlay( NULL ), m_patternToPlay( NULL ), m_loopPattern( false ), m_elapsedMilliSeconds( 0 ), m_elapsedTicks( 0 ), m_elapsedTacts( 0 ) { connect( &m_tempoModel, SIGNAL( dataChanged() ), this, SLOT( setTempo() ) ); connect( &m_tempoModel, SIGNAL( dataUnchanged() ), this, SLOT( setTempo() ) ); connect( &m_timeSigModel, SIGNAL( dataChanged() ), this, SLOT( setTimeSignature() ) ); connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateFramesPerTick() ) ); connect( &m_masterVolumeModel, SIGNAL( dataChanged() ), this, SLOT( masterVolumeChanged() ) ); /* connect( &m_masterPitchModel, SIGNAL( dataChanged() ), this, SLOT( masterPitchChanged() ) );*/ qRegisterMetaType( "note" ); } song::~song() { m_playing = false; delete m_globalAutomationTrack; } void song::masterVolumeChanged() { engine::mixer()->setMasterGain( m_masterVolumeModel.value() / 100.0f ); } void song::setTempo() { const bpm_t tempo = (bpm_t) m_tempoModel.value(); engine::mixer()->lock(); PlayHandleList & playHandles = engine::mixer()->playHandles(); for( PlayHandleList::Iterator it = playHandles.begin(); it != playHandles.end(); ++it ) { NotePlayHandle * nph = dynamic_cast( *it ); if( nph && !nph->isReleased() ) { nph->resize( tempo ); } } engine::mixer()->unlock(); engine::updateFramesPerTick(); m_vstSyncController.setTempo( tempo ); emit tempoChanged( tempo ); } void song::setTimeSignature() { MidiTime::setTicksPerTact( ticksPerTact() ); emit timeSignatureChanged( m_oldTicksPerTact, ticksPerTact() ); emit dataChanged(); m_oldTicksPerTact = ticksPerTact(); m_vstSyncController.setTimeSignature( getTimeSigModel().getNumerator(), getTimeSigModel().getDenominator() ); } void song::savePos() { timeLine * tl = m_playPos[m_playMode].m_timeLine; if( tl != NULL ) { tl->savePos( m_playPos[m_playMode] ); } } void song::processNextBuffer() { if( m_playing == false ) { return; } TrackList track_list; int tco_num = -1; switch( m_playMode ) { case Mode_PlaySong: track_list = tracks(); // at song-start we have to reset the LFOs if( m_playPos[Mode_PlaySong] == 0 ) { EnvelopeAndLfoParameters::instances()->reset(); } break; case Mode_PlayTrack: track_list.push_back( m_trackToPlay ); break; case Mode_PlayBB: if( engine::getBBTrackContainer()->numOfBBs() > 0 ) { tco_num = engine::getBBTrackContainer()-> currentBB(); track_list.push_back( bbTrack::findBBTrack( tco_num ) ); } break; case Mode_PlayPattern: if( m_patternToPlay != NULL ) { tco_num = m_patternToPlay->getTrack()-> getTCONum( m_patternToPlay ); track_list.push_back( m_patternToPlay->getTrack() ); } break; default: return; } if( track_list.empty() == true ) { return; } // check for looping-mode and act if necessary timeLine * tl = m_playPos[m_playMode].m_timeLine; bool check_loop = tl != NULL && m_exporting == false && tl->loopPointsEnabled(); if( check_loop ) { if( m_playPos[m_playMode] < tl->loopBegin() || m_playPos[m_playMode] >= tl->loopEnd() ) { m_elapsedMilliSeconds = (tl->loopBegin().getTicks()*60*1000/48)/getTempo(); m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); } } f_cnt_t total_frames_played = 0; const float frames_per_tick = engine::framesPerTick(); while( total_frames_played < engine::mixer()->framesPerPeriod() ) { m_vstSyncController.update(); f_cnt_t played_frames = engine::mixer()->framesPerPeriod() - total_frames_played; float current_frame = m_playPos[m_playMode].currentFrame(); // did we play a tick? if( current_frame >= frames_per_tick ) { int ticks = m_playPos[m_playMode].getTicks() + (int)( current_frame / frames_per_tick ); m_vstSyncController.setAbsolutePosition( ticks ); // did we play a whole tact? if( ticks >= MidiTime::ticksPerTact() ) { // per default we just continue playing even if // there's no more stuff to play // (song-play-mode) int max_tact = m_playPos[m_playMode].getTact() + 2; // then decide whether to go over to next tact // or to loop back to first tact if( m_playMode == Mode_PlayBB ) { max_tact = engine::getBBTrackContainer() ->lengthOfCurrentBB(); } else if( m_playMode == Mode_PlayPattern && m_loopPattern == true && tl != NULL && tl->loopPointsEnabled() == false ) { max_tact = m_patternToPlay->length() .getTact(); } // end of played object reached? if( m_playPos[m_playMode].getTact() + 1 >= max_tact ) { // then start from beginning and keep // offset ticks = ticks % ( max_tact * MidiTime::ticksPerTact() ); // wrap milli second counter m_elapsedMilliSeconds = ( ticks * 60 * 1000 / 48 ) / getTempo(); m_vstSyncController.setAbsolutePosition( ticks ); } } m_playPos[m_playMode].setTicks( ticks ); if( check_loop ) { m_vstSyncController.startCycle( tl->loopBegin().getTicks(), tl->loopEnd().getTicks() ); if( m_playPos[m_playMode] >= tl->loopEnd() ) { m_playPos[m_playMode].setTicks( tl->loopBegin().getTicks() ); m_elapsedMilliSeconds = ((tl->loopBegin().getTicks())*60*1000/48)/getTempo(); } } else { m_vstSyncController.stopCycle(); } current_frame = fmodf( current_frame, frames_per_tick ); m_playPos[m_playMode].setCurrentFrame( current_frame ); } f_cnt_t last_frames = (f_cnt_t)frames_per_tick - (f_cnt_t) current_frame; // skip last frame fraction if( last_frames == 0 ) { ++total_frames_played; m_playPos[m_playMode].setCurrentFrame( current_frame + 1.0f ); continue; } // do we have some samples left in this tick but these are // less then samples we have to play? if( last_frames < played_frames ) { // then set played_samples to remaining samples, the // rest will be played in next loop played_frames = last_frames; } if( (f_cnt_t) current_frame == 0 ) { if( m_playMode == Mode_PlaySong ) { m_globalAutomationTrack->play( m_playPos[m_playMode], played_frames, total_frames_played, tco_num ); } // loop through all tracks and play them for( int i = 0; i < track_list.size(); ++i ) { track_list[i]->play( m_playPos[m_playMode], played_frames, total_frames_played, tco_num ); } } // update frame-counters total_frames_played += played_frames; m_playPos[m_playMode].setCurrentFrame( played_frames + current_frame ); m_elapsedMilliSeconds += (((played_frames/frames_per_tick)*60*1000/48)/getTempo()); m_elapsedTacts = m_playPos[Mode_PlaySong].getTact(); m_elapsedTicks = (m_playPos[Mode_PlaySong].getTicks()%ticksPerTact())/48; } } void song::playSong() { m_recording = false; if( isStopped() == false ) { stop(); } m_playMode = Mode_PlaySong; m_playing = true; m_paused = false; m_vstSyncController.setPlaybackState( true ); savePos(); emit playbackStateChanged(); } void song::record() { m_recording = true; // TODO: Implement } void song::playAndRecord() { playSong(); m_recording = true; } void song::playTrack( track * _trackToPlay ) { if( isStopped() == false ) { stop(); } m_trackToPlay = _trackToPlay; m_playMode = Mode_PlayTrack; m_playing = true; m_paused = false; m_vstSyncController.setPlaybackState( true ); savePos(); emit playbackStateChanged(); } void song::playBB() { if( isStopped() == false ) { stop(); } m_playMode = Mode_PlayBB; m_playing = true; m_paused = false; m_vstSyncController.setPlaybackState( true ); savePos(); emit playbackStateChanged(); } void song::playPattern( pattern * _patternToPlay, bool _loop ) { if( isStopped() == false ) { stop(); } m_patternToPlay = _patternToPlay; m_loopPattern = _loop; if( m_patternToPlay != NULL ) { m_playMode = Mode_PlayPattern; m_playing = true; m_paused = false; } savePos(); emit playbackStateChanged(); } void song::updateLength() { m_length = 0; m_tracksMutex.lockForRead(); for( TrackList::const_iterator it = tracks().begin(); it != tracks().end(); ++it ) { const tact_t cur = ( *it )->length(); if( cur > m_length ) { m_length = cur; } } m_tracksMutex.unlock(); emit lengthChanged( m_length ); } void song::setPlayPos( tick_t _ticks, PlayModes _play_mode ) { m_elapsedTicks += m_playPos[_play_mode].getTicks() - _ticks; m_elapsedMilliSeconds += (((( _ticks - m_playPos[_play_mode].getTicks()))*60*1000/48)/getTempo()); m_playPos[_play_mode].setTicks( _ticks ); m_playPos[_play_mode].setCurrentFrame( 0.0f ); } void song::togglePause() { if( m_paused == true ) { m_playing = true; m_paused = false; } else { m_playing = false; m_paused = true; } m_vstSyncController.setPlaybackState( m_playing ); emit playbackStateChanged(); } void song::stop() { // do not stop/reset things again if we're stopped already if( m_playMode == Mode_None ) { return; } timeLine * tl = m_playPos[m_playMode].m_timeLine; m_playing = false; m_paused = false; m_recording = true; if( tl != NULL ) { switch( tl->behaviourAtStop() ) { case timeLine::BackToZero: m_playPos[m_playMode].setTicks( 0 ); m_elapsedMilliSeconds = 0; break; case timeLine::BackToStart: if( tl->savedPos() >= 0 ) { m_playPos[m_playMode].setTicks( tl->savedPos().getTicks() ); m_elapsedMilliSeconds = (((tl->savedPos().getTicks())*60*1000/48)/getTempo()); tl->savePos( -1 ); } break; case timeLine::KeepStopPosition: default: break; } } else { m_playPos[m_playMode].setTicks( 0 ); m_elapsedMilliSeconds = 0; } m_playPos[m_playMode].setCurrentFrame( 0 ); m_vstSyncController.setPlaybackState( m_exporting ); m_vstSyncController.setAbsolutePosition( m_playPos[m_playMode].getTicks() ); // remove all note-play-handles that are active engine::mixer()->clear(); m_playMode = Mode_None; emit playbackStateChanged(); } void song::startExport() { stop(); playSong(); m_exporting = true; m_vstSyncController.setPlaybackState( true ); } void song::stopExport() { stop(); m_exporting = false; m_exportLoop = false; m_vstSyncController.setPlaybackState( m_playing ); } void song::insertBar() { m_tracksMutex.lockForRead(); for( TrackList::const_iterator it = tracks().begin(); it != tracks().end(); ++it ) { ( *it )->insertTact( m_playPos[Mode_PlaySong] ); } m_tracksMutex.unlock(); } void song::removeBar() { m_tracksMutex.lockForRead(); for( TrackList::const_iterator it = tracks().begin(); it != tracks().end(); ++it ) { ( *it )->removeTact( m_playPos[Mode_PlaySong] ); } m_tracksMutex.unlock(); } void song::addBBTrack() { engine::mixer()->lock(); track * t = track::create( track::BBTrack, this ); engine::getBBTrackContainer()->setCurrentBB( bbTrack::numOfBBTrack( t ) ); engine::mixer()->unlock(); } void song::addSampleTrack() { engine::mixer()->lock(); (void) track::create( track::SampleTrack, this ); engine::mixer()->unlock(); } void song::addAutomationTrack() { engine::mixer()->lock(); (void) track::create( track::AutomationTrack, this ); engine::mixer()->unlock(); } bpm_t song::getTempo() { return (bpm_t) m_tempoModel.value(); } AutomationPattern * song::tempoAutomationPattern() { return AutomationPattern::globalAutomationPattern( &m_tempoModel ); } void song::clearProject() { engine::projectJournal()->setJournalling( false ); if( m_playing ) { stop(); } for( int i = 0; i < Mode_Count; i++ ) { setPlayPos( 0, ( PlayModes )i ); } engine::mixer()->lock(); if( engine::getBBEditor() ) { engine::getBBEditor()->clearAllTracks(); } if( engine::songEditor() ) { engine::songEditor()->clearAllTracks(); } if( engine::fxMixerView() ) { engine::fxMixerView()->clear(); } QCoreApplication::sendPostedEvents(); engine::getBBTrackContainer()->clearAllTracks(); clearAllTracks(); engine::fxMixer()->clear(); if( engine::automationEditor() ) { engine::automationEditor()->setCurrentPattern( NULL ); } m_tempoModel.reset(); m_masterVolumeModel.reset(); m_masterPitchModel.reset(); m_timeSigModel.reset(); AutomationPattern::globalAutomationPattern( &m_tempoModel )->clear(); AutomationPattern::globalAutomationPattern( &m_masterVolumeModel )-> clear(); AutomationPattern::globalAutomationPattern( &m_masterPitchModel )-> clear(); engine::mixer()->unlock(); if( engine::getProjectNotes() ) { engine::getProjectNotes()->clear(); } // Move to function while( !m_controllers.empty() ) { delete m_controllers.last(); } emit dataChanged(); engine::projectJournal()->clearJournal(); engine::projectJournal()->setJournalling( true ); InstrumentTrackView::cleanupWindowCache(); } // create new file void song::createNewProject() { QString default_template = configManager::inst()->userProjectsDir() + "templates/default.mpt"; if( QFile::exists( default_template ) ) { createNewProjectFromTemplate( default_template ); return; } default_template = configManager::inst()->factoryProjectsDir() + "templates/default.mpt"; if( QFile::exists( default_template ) ) { createNewProjectFromTemplate( default_template ); return; } m_loadingProject = true; clearProject(); engine::projectJournal()->setJournalling( false ); m_fileName = m_oldFileName = ""; track * t; t = track::create( track::InstrumentTrack, this ); dynamic_cast( t )->loadInstrument( "tripleoscillator" ); t = track::create( track::InstrumentTrack, engine::getBBTrackContainer() ); dynamic_cast( t )->loadInstrument( "kicker" ); track::create( track::SampleTrack, this ); track::create( track::BBTrack, this ); track::create( track::AutomationTrack, this ); m_tempoModel.setInitValue( DefaultTempo ); m_timeSigModel.reset(); m_masterVolumeModel.setInitValue( 100 ); m_masterPitchModel.setInitValue( 0 ); QCoreApplication::instance()->processEvents(); m_loadingProject = false; engine::getBBTrackContainer()->updateAfterTrackAdd(); engine::projectJournal()->setJournalling( true ); QCoreApplication::sendPostedEvents(); m_modified = false; if( engine::mainWindow() ) { engine::mainWindow()->resetWindowTitle(); } } void song::createNewProjectFromTemplate( const QString & _template ) { loadProject( _template ); // clear file-name so that user doesn't overwrite template when // saving... m_fileName = m_oldFileName = ""; // update window title if( engine::mainWindow() ) { engine::mainWindow()->resetWindowTitle(); } } // load given song void song::loadProject( const QString & _file_name ) { m_loadingProject = true; clearProject(); engine::projectJournal()->setJournalling( false ); m_fileName = _file_name; m_oldFileName = _file_name; DataFile dataFile( m_fileName ); // if file could not be opened, head-node is null and we create // new project if( dataFile.head().isNull() ) { createNewProject(); return; } engine::mixer()->lock(); // get the header information from the DOM m_tempoModel.loadSettings( dataFile.head(), "bpm" ); m_timeSigModel.loadSettings( dataFile.head(), "timesig" ); m_masterVolumeModel.loadSettings( dataFile.head(), "mastervol" ); m_masterPitchModel.loadSettings( dataFile.head(), "masterpitch" ); if( m_playPos[Mode_PlaySong].m_timeLine ) { // reset loop-point-state m_playPos[Mode_PlaySong].m_timeLine->toggleLoopPoints( 0 ); } if( !dataFile.content().firstChildElement( "track" ).isNull() ) { m_globalAutomationTrack->restoreState( dataFile.content(). firstChildElement( "track" ) ); } //Backward compatibility for LMMS <= 0.4.15 PeakController::initGetControllerBySetting(); QDomNode node = dataFile.content().firstChild(); while( !node.isNull() ) { if( node.isElement() ) { if( node.nodeName() == "trackcontainer" ) { ( (JournallingObject *)( this ) )-> restoreState( node.toElement() ); } else if( node.nodeName() == "controllers" ) { restoreControllerStates( node.toElement() ); } else if( node.nodeName() == engine::fxMixer()->nodeName() ) { engine::fxMixer()->restoreState( node.toElement() ); } else if( engine::hasGUI() ) { if( node.nodeName() == engine::getControllerRackView()->nodeName() ) { engine::getControllerRackView()-> restoreState( node.toElement() ); } else if( node.nodeName() == engine::pianoRoll()->nodeName() ) { engine::pianoRoll()->restoreState( node.toElement() ); } else if( node.nodeName() == engine::automationEditor()-> nodeName() ) { engine::automationEditor()-> restoreState( node.toElement() ); } else if( node.nodeName() == engine::getProjectNotes()-> nodeName() ) { engine::getProjectNotes()-> SerializingObject::restoreState( node.toElement() ); } else if( node.nodeName() == m_playPos[Mode_PlaySong]. m_timeLine->nodeName() ) { m_playPos[Mode_PlaySong]. m_timeLine->restoreState( node.toElement() ); } } } node = node.nextSibling(); } // quirk for fixing projects with broken positions of TCOs inside // BB-tracks engine::getBBTrackContainer()->fixIncorrectPositions(); // Connect controller links to their controllers // now that everything is loaded ControllerConnection::finalizeConnections(); // resolve all IDs so that autoModels are automated AutomationPattern::resolveAllIDs(); engine::mixer()->unlock(); configManager::inst()->addRecentlyOpenedProject( _file_name ); engine::projectJournal()->setJournalling( true ); emit projectLoaded(); m_loadingProject = false; m_modified = false; if( engine::mainWindow() ) { engine::mainWindow()->resetWindowTitle(); } } // only save current song as _filename and do nothing else bool song::saveProjectFile( const QString & _filename ) { DataFile dataFile( DataFile::SongProject ); m_tempoModel.saveSettings( dataFile, dataFile.head(), "bpm" ); m_timeSigModel.saveSettings( dataFile, dataFile.head(), "timesig" ); m_masterVolumeModel.saveSettings( dataFile, dataFile.head(), "mastervol" ); m_masterPitchModel.saveSettings( dataFile, dataFile.head(), "masterpitch" ); saveState( dataFile, dataFile.content() ); m_globalAutomationTrack->saveState( dataFile, dataFile.content() ); engine::fxMixer()->saveState( dataFile, dataFile.content() ); if( engine::hasGUI() ) { engine::getControllerRackView()->saveState( dataFile, dataFile.content() ); engine::pianoRoll()->saveState( dataFile, dataFile.content() ); engine::automationEditor()->saveState( dataFile, dataFile.content() ); engine::getProjectNotes()-> SerializingObject::saveState( dataFile, dataFile.content() ); m_playPos[Mode_PlaySong].m_timeLine->saveState( dataFile, dataFile.content() ); } saveControllerStates( dataFile, dataFile.content() ); return dataFile.writeFile( _filename ); } // save current song and update the gui bool song::guiSaveProject() { DataFile dataFile( DataFile::SongProject ); m_fileName = dataFile.nameWithExtension( m_fileName ); if( saveProjectFile( m_fileName ) && engine::hasGUI() ) { textFloat::displayMessage( tr( "Project saved" ), tr( "The project %1 is now saved." ).arg( m_fileName ), embed::getIconPixmap( "project_save", 24, 24 ), 2000 ); configManager::inst()->addRecentlyOpenedProject( m_fileName ); m_modified = false; engine::mainWindow()->resetWindowTitle(); } else if( engine::hasGUI() ) { textFloat::displayMessage( tr( "Project NOT saved." ), tr( "The project %1 was not saved!" ).arg( m_fileName ), embed::getIconPixmap( "error" ), 4000 ); return false; } return true; } // save current song in given filename bool song::guiSaveProjectAs( const QString & _file_name ) { QString o = m_oldFileName; m_oldFileName = m_fileName; m_fileName = _file_name; if( guiSaveProject() == false ) { m_fileName = m_oldFileName; m_oldFileName = o; return false; } m_oldFileName = m_fileName; return true; } void song::importProject() { FileDialog ofd( NULL, tr( "Import file" ), configManager::inst()->userProjectsDir(), tr("MIDI sequences") + " (*.mid *.midi *.rmi);;" + tr("FL Studio projects") + " (*.flp);;" + tr("Hydrogen projects") + " (*.h2song);;" + tr("All file types") + " (*.*)"); ofd.setFileMode( FileDialog::ExistingFiles ); if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() ) { ImportFilter::import( ofd.selectedFiles()[0], this ); } } void song::saveControllerStates( QDomDocument & _doc, QDomElement & _this ) { // save settings of controllers QDomElement controllersNode =_doc.createElement( "controllers" ); _this.appendChild( controllersNode ); for( int i = 0; i < m_controllers.size(); ++i ) { m_controllers[i]->saveState( _doc, controllersNode ); } } void song::restoreControllerStates( const QDomElement & _this ) { QDomNode node = _this.firstChild(); while( !node.isNull() ) { Controller * c = Controller::create( node.toElement(), this ); Q_ASSERT( c != NULL ); /* For PeakController, addController() was called in * PeakControllerEffect::PeakControllerEffect(). * This line removes the previously added controller for PeakController * without affecting the order of controllers in Controller Rack */ engine::getSong()->removeController( c ); addController( c ); node = node.nextSibling(); } } void song::exportProjectTracks() { exportProject(true); } void song::exportProject(bool multiExport) { if( isEmpty() ) { QMessageBox::information( engine::mainWindow(), tr( "Empty project" ), tr( "This project is empty so exporting makes " "no sense. Please put some items into " "Song Editor first!" ) ); return; } FileDialog efd( engine::mainWindow() ); if (multiExport) { efd.setFileMode( FileDialog::Directory); efd.setWindowTitle( tr( "Select directory for writing exported tracks..." ) ); if( !m_fileName.isEmpty() ) { efd.setDirectory( QFileInfo( m_fileName ).absolutePath() ); } } else { efd.setFileMode( FileDialog::AnyFile ); int idx = 0; QStringList types; while( __fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::NumFileFormats ) { types << tr( __fileEncodeDevices[idx].m_description ); ++idx; } efd.setFilters( types ); QString base_filename; if( !m_fileName.isEmpty() ) { efd.setDirectory( QFileInfo( m_fileName ).absolutePath() ); base_filename = QFileInfo( m_fileName ).completeBaseName(); } else { efd.setDirectory( configManager::inst()->userProjectsDir() ); base_filename = tr( "untitled" ); } efd.selectFile( base_filename + __fileEncodeDevices[0].m_extension ); efd.setWindowTitle( tr( "Select file for project-export..." ) ); } efd.setAcceptMode( FileDialog::AcceptSave ); if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() ) { const QString export_file_name = efd.selectedFiles()[0]; exportProjectDialog epd( export_file_name, engine::mainWindow(), multiExport ); epd.exec(); } } void song::updateFramesPerTick() { engine::updateFramesPerTick(); } void song::setModified() { if( !m_loadingProject ) { m_modified = true; if( engine::mainWindow() && QThread::currentThread() == engine::mainWindow()->thread() ) { engine::mainWindow()->resetWindowTitle(); } } } void song::addController( Controller * _c ) { if( _c != NULL && !m_controllers.contains( _c ) ) { m_controllers.append( _c ); emit dataChanged(); } } void song::removeController( Controller * _controller ) { int index = m_controllers.indexOf( _controller ); if( index != -1 ) { m_controllers.remove( index ); if( engine::getSong() ) { engine::getSong()->setModified(); } emit dataChanged(); } } bool song::isLoadingProject() { return m_loadingProject; } #include "moc_song.cxx" lmms-1.0.0/src/core/ProjectRenderer.cpp0000644000175000017500000001173412313663627016533 0ustar tobytoby/* * ProjectRenderer.cpp - ProjectRenderer-class for easily rendering projects * * Copyright (c) 2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "ProjectRenderer.h" #include "song.h" #include "engine.h" #include "AudioFileWave.h" #include "AudioFileOgg.h" #ifdef LMMS_HAVE_SCHED_H #include #endif #include FileEncodeDevice __fileEncodeDevices[] = { { ProjectRenderer::WaveFile, QT_TRANSLATE_NOOP( "ProjectRenderer", "WAV-File (*.wav)" ), ".wav", &AudioFileWave::getInst }, { ProjectRenderer::OggFile, QT_TRANSLATE_NOOP( "ProjectRenderer", "Compressed OGG-File (*.ogg)" ), ".ogg", #ifdef LMMS_HAVE_OGGVORBIS &AudioFileOgg::getInst #else NULL #endif }, // ... insert your own file-encoder-infos here... may be one day the // user can add own encoders inside the program... { ProjectRenderer::NumFileFormats, NULL, NULL, NULL } } ; ProjectRenderer::ProjectRenderer( const Mixer::qualitySettings & _qs, const OutputSettings & _os, ExportFileFormats _file_format, const QString & _out_file ) : QThread( engine::mixer() ), m_fileDev( NULL ), m_qualitySettings( _qs ), m_oldQualitySettings( engine::mixer()->currentQualitySettings() ), m_progress( 0 ), m_abort( false ) { if( __fileEncodeDevices[_file_format].m_getDevInst == NULL ) { return; } bool success_ful = false; m_fileDev = __fileEncodeDevices[_file_format].m_getDevInst( _os.samplerate, DEFAULT_CHANNELS, success_ful, _out_file, _os.vbr, _os.bitrate, _os.bitrate - 64, _os.bitrate + 64, _os.depth == Depth_32Bit ? 32 : 16, engine::mixer() ); if( success_ful == false ) { delete m_fileDev; m_fileDev = NULL; } } ProjectRenderer::~ProjectRenderer() { } // little help-function for getting file-format from a file-extension (only for // registered file-encoders) ProjectRenderer::ExportFileFormats ProjectRenderer::getFileFormatFromExtension( const QString & _ext ) { int idx = 0; while( __fileEncodeDevices[idx].m_fileFormat != NumFileFormats ) { if( QString( __fileEncodeDevices[idx].m_extension ) == _ext ) { return( __fileEncodeDevices[idx].m_fileFormat ); } ++idx; } return( WaveFile ); // default } void ProjectRenderer::startProcessing() { if( isReady() ) { // have to do mixer stuff with GUI-thread-affinity in order to // make slots connected to sampleRateChanged()-signals being // called immediately engine::mixer()->setAudioDevice( m_fileDev, m_qualitySettings, false ); start( #ifndef LMMS_BUILD_WIN32 QThread::HighPriority #endif ); } } void ProjectRenderer::run() { #if 0 #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_SCHED_H cpu_set_t mask; CPU_ZERO( &mask ); CPU_SET( 0, &mask ); sched_setaffinity( 0, sizeof( mask ), &mask ); #endif #endif #endif engine::getSong()->startExport(); song::playPos & pp = engine::getSong()->getPlayPos( song::Mode_PlaySong ); m_progress = 0; const int sl = ( engine::getSong()->length() + 1 ) * 192; while( engine::getSong()->isExportDone() == false && engine::getSong()->isExporting() == true && !m_abort ) { m_fileDev->processNextBuffer(); const int nprog = pp * 100 / sl; if( m_progress != nprog ) { m_progress = nprog; emit progressChanged( m_progress ); } } engine::getSong()->stopExport(); const QString f = m_fileDev->outputFile(); engine::mixer()->restoreAudioDevice(); // also deletes audio-dev engine::mixer()->changeQuality( m_oldQualitySettings ); // if the user aborted export-process, the file has to be deleted if( m_abort ) { QFile( f ).remove(); } } void ProjectRenderer::abortProcessing() { m_abort = true; } void ProjectRenderer::updateConsoleProgress() { const int cols = 50; static int rot = 0; char buf[80]; char prog[cols+1]; for( int i = 0; i < cols; ++i ) { prog[i] = ( i*100/cols <= m_progress ? '-' : ' ' ); } prog[cols] = 0; const char * activity = (const char *) "|/-\\"; memset( buf, 0, sizeof( buf ) ); sprintf( buf, "\r|%s| %3d%% %c ", prog, m_progress, activity[rot] ); rot = ( rot+1 ) % 4; fprintf( stderr, "%s", buf ); fflush( stderr ); } #include "moc_ProjectRenderer.cxx" lmms-1.0.0/src/core/SamplePlayHandle.cpp0000644000175000017500000000717412313663627016624 0ustar tobytoby/* * SamplePlayHandle.cpp - implementation of class SamplePlayHandle * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "SamplePlayHandle.h" #include "AudioPort.h" #include "bb_track.h" #include "engine.h" #include "InstrumentTrack.h" #include "pattern.h" #include "SampleBuffer.h" #include "SampleTrack.h" SamplePlayHandle::SamplePlayHandle( const QString& sampleFile ) : PlayHandle( TypeSamplePlayHandle ), m_sampleBuffer( new SampleBuffer( sampleFile ) ), m_doneMayReturnTrue( true ), m_frame( 0 ), m_audioPort( new AudioPort( "SamplePlayHandle", false ) ), m_ownAudioPort( true ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( NULL ), m_bbTrack( NULL ) { } SamplePlayHandle::SamplePlayHandle( SampleBuffer* sampleBuffer ) : PlayHandle( TypeSamplePlayHandle ), m_sampleBuffer( sharedObject::ref( sampleBuffer ) ), m_doneMayReturnTrue( true ), m_frame( 0 ), m_audioPort( new AudioPort( "SamplePlayHandle", false ) ), m_ownAudioPort( true ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( NULL ), m_bbTrack( NULL ) { } SamplePlayHandle::SamplePlayHandle( SampleTCO* tco ) : PlayHandle( TypeSamplePlayHandle ), m_sampleBuffer( sharedObject::ref( tco->sampleBuffer() ) ), m_doneMayReturnTrue( true ), m_frame( 0 ), m_audioPort( ( (SampleTrack *)tco->getTrack() )->audioPort() ), m_ownAudioPort( false ), m_defaultVolumeModel( DefaultVolume, MinVolume, MaxVolume, 1 ), m_volumeModel( &m_defaultVolumeModel ), m_track( tco->getTrack() ), m_bbTrack( NULL ) { } SamplePlayHandle::~SamplePlayHandle() { sharedObject::unref( m_sampleBuffer ); if( m_ownAudioPort ) { delete m_audioPort; } } void SamplePlayHandle::play( sampleFrame * _working_buffer ) { //play( 0, _try_parallelizing ); if( framesDone() >= totalFrames() ) { return; } const fpp_t frames = engine::mixer()->framesPerPeriod(); if( !( m_track && m_track->isMuted() ) && !( m_bbTrack && m_bbTrack->isMuted() ) ) { stereoVolumeVector v = { { m_volumeModel->value() / DefaultVolume, m_volumeModel->value() / DefaultVolume } }; m_sampleBuffer->play( _working_buffer, &m_state, frames, BaseFreq ); engine::mixer()->bufferToPort( _working_buffer, frames, offset(), v, m_audioPort ); } m_frame += frames; } bool SamplePlayHandle::isFinished() const { return framesDone() >= totalFrames() && m_doneMayReturnTrue == true; } bool SamplePlayHandle::isFromTrack( const track * _track ) const { return m_track == _track || m_bbTrack == _track; } f_cnt_t SamplePlayHandle::totalFrames() const { return ( m_sampleBuffer->endFrame() - m_sampleBuffer->startFrame() ) * ( engine::mixer()->processingSampleRate() / engine::mixer()->baseSampleRate() ); } lmms-1.0.0/src/core/audio/0000755000175000017500000000000012313663627014025 5ustar tobytobylmms-1.0.0/src/core/audio/AudioSampleRecorder.cpp0000644000175000017500000000545312313663627020431 0ustar tobytoby/* * AudioSampleRecorder.cpp - device-class that implements recording * audio-buffers into RAM * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioSampleRecorder.h" #include "SampleBuffer.h" #include "debug.h" AudioSampleRecorder::AudioSampleRecorder( const ch_cnt_t _channels, bool & _success_ful, Mixer * _mixer ) : AudioDevice( _channels, _mixer ), m_buffers() { _success_ful = true; } AudioSampleRecorder::~AudioSampleRecorder() { while( !m_buffers.empty() ) { delete[] m_buffers.front().first; m_buffers.erase( m_buffers.begin() ); } } f_cnt_t AudioSampleRecorder::framesRecorded() const { f_cnt_t frames = 0; for( BufferList::ConstIterator it = m_buffers.begin(); it != m_buffers.end(); ++it ) { frames += ( *it ).second; } return frames; } void AudioSampleRecorder::createSampleBuffer( SampleBuffer** sampleBuf ) { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in sampleFrame * data = new sampleFrame[frames]; // make sure buffer is cleaned up properly at the end... sampleFrame * data_ptr = data; #ifdef LMMS_DEBUG assert( data != NULL ); #endif // now copy all buffers into big buffer for( BufferList::ConstIterator it = m_buffers.begin(); it != m_buffers.end(); ++it ) { memcpy( data_ptr, ( *it ).first, ( *it ).second * sizeof( sampleFrame ) ); data_ptr += ( *it ).second; } // create according sample-buffer out of big buffer *sampleBuf = new SampleBuffer( data, frames ); ( *sampleBuf )->setSampleRate( sampleRate() ); delete[] data; } void AudioSampleRecorder::writeBuffer( const surroundSampleFrame * _ab, const fpp_t _frames, const float ) { sampleFrame * buf = new sampleFrame[_frames]; for( fpp_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) { buf[frame][chnl] = _ab[frame][chnl]; } } m_buffers.push_back( qMakePair( buf, _frames ) ); } lmms-1.0.0/src/core/audio/AudioFileOgg.cpp0000644000175000017500000001420412313663627017030 0ustar tobytoby/* * AudioFileOgg.cpp - audio-device which encodes wave-stream and writes it * into an OGG-file. This is used for song-export. * * This file is based on encode.c from vorbis-tools-source, for more information * see below. * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioFileOgg.h" #ifdef LMMS_HAVE_OGGVORBIS #include #include AudioFileOgg::AudioFileOgg( const sample_rate_t _sample_rate, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, Mixer* _mixer ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, _depth, _mixer ) { m_ok = _success_ful = outputFileOpened() && startEncoding(); } AudioFileOgg::~AudioFileOgg() { finishEncoding(); } inline int AudioFileOgg::writePage() { int written = writeData( m_og.header, m_og.header_len ); written += writeData( m_og.body, m_og.body_len ); return written; } bool AudioFileOgg::startEncoding() { vorbis_comment vc; const char * comments = "Cool=This song has been made using Linux " "MultiMedia Studio"; int comment_length = strlen( comments ); char * user_comments = new char[comment_length + 1]; strcpy( user_comments, comments ); vc.user_comments = &user_comments; vc.comment_lengths = &comment_length; vc.comments = 1; vc.vendor = NULL; m_channels = channels(); // vbr enabled? if( useVBR() == 0 ) { m_minBitrate = nominalBitrate(); // min for vbr m_maxBitrate = nominalBitrate(); // max for vbr } else { m_minBitrate = minBitrate(); // min for vbr m_maxBitrate = maxBitrate(); // max for vbr } m_rate = sampleRate(); // default-samplerate if( m_rate > 48000 ) { m_rate = 48000; setSampleRate( 48000 ); } m_serialNo = 0; // track-num? m_comments = &vc; // comments for ogg-file // Have vorbisenc choose a mode for us vorbis_info_init( &m_vi ); if( vorbis_encode_setup_managed( &m_vi, m_channels, m_rate, ( m_maxBitrate > 0 )? m_maxBitrate * 1000 : -1, nominalBitrate() * 1000, ( m_minBitrate > 0 )? m_minBitrate * 1000 : -1 ) ) { printf( "Mode initialization failed: invalid parameters for " "bitrate\n" ); vorbis_info_clear( &m_vi ); return false; } if( useVBR() == false ) { vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_AVG, NULL ); } else if( useVBR() == true ) { // Turn off management entirely (if it was turned on). vorbis_encode_ctl( &m_vi, OV_ECTL_RATEMANAGE_SET, NULL ); } vorbis_encode_setup_init( &m_vi ); // Now, set up the analysis engine, stream encoder, and other // preparation before the encoding begins. vorbis_analysis_init( &m_vd, &m_vi ); vorbis_block_init( &m_vd, &m_vb ); ogg_stream_init( &m_os, m_serialNo ); // Now, build the three header packets and send through to the stream // output stage (but defer actual file output until the main encode // loop) ogg_packet header_main; ogg_packet header_comments; ogg_packet header_codebooks; int result; // Build the packets vorbis_analysis_headerout( &m_vd, m_comments, &header_main, &header_comments, &header_codebooks ); // And stream them out ogg_stream_packetin( &m_os, &header_main ); ogg_stream_packetin( &m_os, &header_comments ); ogg_stream_packetin( &m_os, &header_codebooks ); while( ( result = ogg_stream_flush( &m_os, &m_og ) ) ) { if( !result ) { break; } int ret = writePage(); if( ret != m_og.header_len + m_og.body_len ) { // clean up finishEncoding(); delete[] user_comments; return false; } } delete[] user_comments; return true; } void AudioFileOgg::writeBuffer( const surroundSampleFrame * _ab, const fpp_t _frames, const float _master_gain ) { int eos = 0; float * * buffer = vorbis_analysis_buffer( &m_vd, _frames * BYTES_PER_SAMPLE * channels() ); for( fpp_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { buffer[chnl][frame] = _ab[frame][chnl] * _master_gain; } } vorbis_analysis_wrote( &m_vd, _frames ); // While we can get enough data from the library to analyse, // one block at a time... while( vorbis_analysis_blockout( &m_vd, &m_vb ) == 1 ) { // Do the main analysis, creating a packet vorbis_analysis( &m_vb, NULL ); vorbis_bitrate_addblock( &m_vb ); while( vorbis_bitrate_flushpacket( &m_vd, &m_op ) ) { // Add packet to bitstream ogg_stream_packetin( &m_os, &m_op ); // If we've gone over a page boundary, we can do // actual output, so do so (for however many pages // are available) while( !eos ) { int result = ogg_stream_pageout( &m_os, &m_og ); if( !result ) { break; } int ret = writePage(); if( ret != m_og.header_len + m_og.body_len ) { printf( "failed writing to " "outstream\n" ); return; } if( ogg_page_eos( &m_og ) ) { eos = 1; } } } } } void AudioFileOgg::finishEncoding() { if( m_ok ) { // just for flushing buffers... writeBuffer( NULL, 0, 0.0f ); // clean up ogg_stream_clear( &m_os ); vorbis_block_clear( &m_vb ); vorbis_dsp_clear( &m_vd ); vorbis_info_clear( &m_vi ); } } #endif lmms-1.0.0/src/core/audio/AudioPortAudio.cpp0000644000175000017500000002734112313663627017430 0ustar tobytoby/* * AudioPortAudio.cpp - device-class that performs PCM-output via PortAudio * * Copyright (c) 2008 Csaba Hruska * Copyright (c) 2010 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioPortAudio.h" #ifndef LMMS_HAVE_PORTAUDIO void AudioPortAudioSetupUtil::updateDevices() { } void AudioPortAudioSetupUtil::updateChannels() { } #endif #ifdef LMMS_HAVE_PORTAUDIO #include #include #include "engine.h" #include "debug.h" #include "config_mgr.h" #include "gui_templates.h" #include "templates.h" #include "combobox.h" #include "LcdSpinBox.h" AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) : AudioDevice( tLimit( configManager::inst()->value( "audioportaudio", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_paStream( NULL ), m_wasPAInitError( false ), m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), m_outBufPos( 0 ), m_stopSemaphore( 1 ) { _success_ful = false; m_outBufSize = mixer()->framesPerPeriod(); PaError err = Pa_Initialize(); if( err != paNoError ) { printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); m_wasPAInitError = true; return; } if( Pa_GetDeviceCount() <= 0 ) { return; } const QString& backend = configManager::inst()->value( "audioportaudio", "backend" ); const QString& device = configManager::inst()->value( "audioportaudio", "device" ); PaDeviceIndex inDevIdx = -1; PaDeviceIndex outDevIdx = -1; const PaDeviceInfo * di; for( int i = 0; i < Pa_GetDeviceCount(); ++i ) { di = Pa_GetDeviceInfo( i ); if( di->name == device && Pa_GetHostApiInfo( di->hostApi )->name == backend ) { inDevIdx = i; outDevIdx = i; } } if( inDevIdx < 0 ) { inDevIdx = Pa_GetDefaultInputDevice(); } if( outDevIdx < 0 ) { outDevIdx = Pa_GetDefaultOutputDevice(); } if( inDevIdx < 0 || outDevIdx < 0 ) { return; } double inLatency = 0;//(double)mixer()->framesPerPeriod() / (double)sampleRate(); double outLatency = 0;//(double)mixer()->framesPerPeriod() / (double)sampleRate(); //inLatency = Pa_GetDeviceInfo( inDevIdx )->defaultLowInputLatency; //outLatency = Pa_GetDeviceInfo( outDevIdx )->defaultLowOutputLatency; const int samples = mixer()->framesPerPeriod(); // Configure output parameters. m_outputParameters.device = outDevIdx; m_outputParameters.channelCount = channels(); m_outputParameters.sampleFormat = paFloat32; // 32 bit floating point output m_outputParameters.suggestedLatency = outLatency; m_outputParameters.hostApiSpecificStreamInfo = NULL; // Configure input parameters. m_inputParameters.device = inDevIdx; m_inputParameters.channelCount = DEFAULT_CHANNELS; m_inputParameters.sampleFormat = paFloat32; // 32 bit floating point input m_inputParameters.suggestedLatency = inLatency; m_inputParameters.hostApiSpecificStreamInfo = NULL; // Open an audio I/O stream. err = Pa_OpenStream( &m_paStream, supportsCapture() ? &m_inputParameters : NULL, // The input parameter &m_outputParameters, // The outputparameter sampleRate(), samples, paNoFlag, // Don't use any flags _process_callback, // our callback function this ); if( err == paInvalidDevice && sampleRate() < 48000 ) { printf("Pa_OpenStream() failed with 44,1 KHz, trying again with 48 KHz\n"); // some backends or drivers do not allow 32 bit floating point data // with a samplerate of 44100 Hz setSampleRate( 48000 ); err = Pa_OpenStream( &m_paStream, supportsCapture() ? &m_inputParameters : NULL, // The input parameter &m_outputParameters, // The outputparameter sampleRate(), samples, paNoFlag, // Don't use any flags _process_callback, // our callback function this ); } if( err != paNoError ) { printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) ); return; } printf( "Input device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( inDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( inDevIdx )->hostApi )->name ); printf( "Output device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( outDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( outDevIdx )->hostApi )->name ); m_stopSemaphore.acquire(); // TODO: debug Mixer::pushInputFrames() //m_supportsCapture = true; _success_ful = true; } AudioPortAudio::~AudioPortAudio() { stopProcessing(); m_stopSemaphore.release(); if( !m_wasPAInitError ) { Pa_Terminate(); } delete[] m_outBuf; } void AudioPortAudio::startProcessing() { m_stopped = false; PaError err = Pa_StartStream( m_paStream ); if( err != paNoError ) { m_stopped = true; printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) ); } } void AudioPortAudio::stopProcessing() { if( m_paStream && Pa_IsStreamActive( m_paStream ) ) { m_stopSemaphore.acquire(); PaError err = Pa_StopStream( m_paStream ); if( err != paNoError ) { printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) ); } } } void AudioPortAudio::applyQualitySettings() { if( hqAudio() ) { setSampleRate( engine::mixer()->processingSampleRate() ); int samples = mixer()->framesPerPeriod(); PaError err = Pa_OpenStream( &m_paStream, supportsCapture() ? &m_inputParameters : NULL, // The input parameter &m_outputParameters, // The outputparameter sampleRate(), samples, paNoFlag, // Don't use any flags _process_callback, // our callback function this ); if( err != paNoError ) { printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) ); return; } } AudioDevice::applyQualitySettings(); } int AudioPortAudio::process_callback( const float *_inputBuffer, float * _outputBuffer, unsigned long _framesPerBuffer ) { if( supportsCapture() ) { mixer()->pushInputFrames( (sampleFrame*)_inputBuffer, _framesPerBuffer ); } if( m_stopped ) { memset( _outputBuffer, 0, _framesPerBuffer * channels() * sizeof(float) ); return paComplete; } while( _framesPerBuffer ) { if( m_outBufPos == 0 ) { // frames depend on the sample rate const fpp_t frames = getNextBuffer( m_outBuf ); if( !frames ) { m_stopped = true; m_stopSemaphore.release(); memset( _outputBuffer, 0, _framesPerBuffer * channels() * sizeof(float) ); return paComplete; } m_outBufSize = frames; } const int min_len = qMin( (int)_framesPerBuffer, m_outBufSize - m_outBufPos ); float master_gain = mixer()->masterGain(); for( fpp_t frame = 0; frame < min_len; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { ( _outputBuffer + frame * channels() )[chnl] = Mixer::clip( m_outBuf[frame][chnl] * master_gain ); } } _outputBuffer += min_len * channels(); _framesPerBuffer -= min_len; m_outBufPos += min_len; m_outBufPos %= m_outBufSize; } return paContinue; } int AudioPortAudio::_process_callback( const void *_inputBuffer, void * _outputBuffer, unsigned long _framesPerBuffer, const PaStreamCallbackTimeInfo * _timeInfo, PaStreamCallbackFlags _statusFlags, void * _arg ) { Q_UNUSED(_timeInfo); Q_UNUSED(_statusFlags); AudioPortAudio * _this = static_cast (_arg); return _this->process_callback( (const float*)_inputBuffer, (float*)_outputBuffer, _framesPerBuffer ); } void AudioPortAudioSetupUtil::updateDevices() { PaError err = Pa_Initialize(); if( err != paNoError ) { printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); return; } // get active backend const QString& backend = m_backendModel.currentText(); int hostApi = 0; const PaHostApiInfo * hi; for( int i = 0; i < Pa_GetHostApiCount(); ++i ) { hi = Pa_GetHostApiInfo( i ); if( backend == hi->name ) { hostApi = i; break; } } // get devices for selected backend m_deviceModel.clear(); const PaDeviceInfo * di; for( int i = 0; i < Pa_GetDeviceCount(); ++i ) { di = Pa_GetDeviceInfo( i ); if( di->hostApi == hostApi ) { m_deviceModel.addItem( di->name ); } } Pa_Terminate(); } void AudioPortAudioSetupUtil::updateChannels() { PaError err = Pa_Initialize(); if( err != paNoError ) { printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); return; } // get active backend Pa_Terminate(); } AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) : AudioDevice::setupWidget( AudioPortAudio::name(), _parent ) { m_backend = new comboBox( this, "BACKEND" ); m_backend->setGeometry( 64, 15, 260, 20 ); QLabel * backend_lbl = new QLabel( tr( "BACKEND" ), this ); backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) ); backend_lbl->move( 8, 18 ); m_device = new comboBox( this, "DEVICE" ); m_device->setGeometry( 64, 35, 260, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->move( 8, 38 ); /* LcdSpinBoxModel * m = new LcdSpinBoxModel( ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( configManager::inst()->value( "audioportaudio", "channels" ).toInt() ); m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); m_channels->setLabel( tr( "CHANNELS" ) ); m_channels->move( 308, 20 );*/ // Setup models PaError err = Pa_Initialize(); if( err != paNoError ) { printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) ); return; } // todo: setup backend model const PaHostApiInfo * hi; for( int i = 0; i < Pa_GetHostApiCount(); ++i ) { hi = Pa_GetHostApiInfo( i ); m_setupUtil.m_backendModel.addItem( hi->name ); } Pa_Terminate(); const QString& backend = configManager::inst()->value( "audioportaudio", "backend" ); const QString& device = configManager::inst()->value( "audioportaudio", "device" ); int i = qMax( 0, m_setupUtil.m_backendModel.findText( backend ) ); m_setupUtil.m_backendModel.setValue( i ); m_setupUtil.updateDevices(); i = qMax( 0, m_setupUtil.m_deviceModel.findText( device ) ); m_setupUtil.m_deviceModel.setValue( i ); connect( &m_setupUtil.m_backendModel, SIGNAL( dataChanged() ), &m_setupUtil, SLOT( updateDevices() ) ); connect( &m_setupUtil.m_deviceModel, SIGNAL( dataChanged() ), &m_setupUtil, SLOT( updateChannels() ) ); m_backend->setModel( &m_setupUtil.m_backendModel ); m_device->setModel( &m_setupUtil.m_deviceModel ); } AudioPortAudio::setupWidget::~setupWidget() { disconnect( &m_setupUtil.m_backendModel, SIGNAL( dataChanged() ), &m_setupUtil, SLOT( updateDevices() ) ); disconnect( &m_setupUtil.m_deviceModel, SIGNAL( dataChanged() ), &m_setupUtil, SLOT( updateChannels() ) ); } void AudioPortAudio::setupWidget::saveSettings() { configManager::inst()->setValue( "audioportaudio", "backend", m_setupUtil.m_backendModel.currentText() ); configManager::inst()->setValue( "audioportaudio", "device", m_setupUtil.m_deviceModel.currentText() ); /* configManager::inst()->setValue( "audioportaudio", "channels", QString::number( m_channels->value() ) );*/ } #endif #include "moc_AudioPortAudio.cxx" lmms-1.0.0/src/core/audio/AudioSdl.cpp0000644000175000017500000001152712313663627016243 0ustar tobytoby/* * AudioSdl.cpp - device-class that performs PCM-output via SDL * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioSdl.h" #ifdef LMMS_HAVE_SDL #include #include #include "engine.h" #include "debug.h" #include "config_mgr.h" #include "gui_templates.h" #include "templates.h" AudioSdl::AudioSdl( bool & _success_ful, Mixer* _mixer ) : AudioDevice( DEFAULT_CHANNELS, _mixer ), m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), m_convertedBufPos( 0 ), m_convertEndian( false ), m_stopSemaphore( 1 ) { _success_ful = false; m_convertedBufSize = mixer()->framesPerPeriod() * channels() * sizeof( int_sample_t ); m_convertedBuf = new Uint8[m_convertedBufSize]; if( SDL_Init( SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE ) < 0 ) { qCritical( "Couldn't initialize SDL: %s\n", SDL_GetError() ); return; } m_audioHandle.freq = sampleRate(); m_audioHandle.format = AUDIO_S16SYS; // we want it in byte-order // of system, so we don't have // to convert the buffers m_audioHandle.channels = channels(); m_audioHandle.samples = qMax( 1024, mixer()->framesPerPeriod()*2 ); m_audioHandle.callback = sdlAudioCallback; m_audioHandle.userdata = this; SDL_AudioSpec actual; // open the audio device, forcing the desired format if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 ) { qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); return; } m_convertEndian = ( m_audioHandle.format != actual.format ); m_stopSemaphore.acquire(); _success_ful = true; } AudioSdl::~AudioSdl() { stopProcessing(); m_stopSemaphore.release(); SDL_CloseAudio(); SDL_Quit(); delete[] m_convertedBuf; delete[] m_outBuf; } void AudioSdl::startProcessing() { m_stopped = false; SDL_PauseAudio( 0 ); } void AudioSdl::stopProcessing() { if( SDL_GetAudioStatus() == SDL_AUDIO_PLAYING ) { m_stopSemaphore.acquire(); SDL_LockAudio(); SDL_PauseAudio( 1 ); SDL_UnlockAudio(); } } void AudioSdl::applyQualitySettings() { if( 0 )//hqAudio() ) { SDL_CloseAudio(); setSampleRate( engine::mixer()->processingSampleRate() ); m_audioHandle.freq = sampleRate(); SDL_AudioSpec actual; // open the audio device, forcing the desired format if( SDL_OpenAudio( &m_audioHandle, &actual ) < 0 ) { qCritical( "Couldn't open SDL-audio: %s\n", SDL_GetError() ); } } AudioDevice::applyQualitySettings(); } void AudioSdl::sdlAudioCallback( void * _udata, Uint8 * _buf, int _len ) { AudioSdl * _this = static_cast( _udata ); _this->sdlAudioCallback( _buf, _len ); } void AudioSdl::sdlAudioCallback( Uint8 * _buf, int _len ) { if( m_stopped ) { memset( _buf, 0, _len ); return; } while( _len ) { if( m_convertedBufPos == 0 ) { // frames depend on the sample rate const fpp_t frames = getNextBuffer( m_outBuf ); if( !frames ) { m_stopped = true; m_stopSemaphore.release(); memset( _buf, 0, _len ); return; } m_convertedBufSize = frames * channels() * sizeof( int_sample_t ); convertToS16( m_outBuf, frames, mixer()->masterGain(), (int_sample_t *)m_convertedBuf, m_convertEndian ); } const int min_len = qMin( _len, m_convertedBufSize - m_convertedBufPos ); memcpy( _buf, m_convertedBuf + m_convertedBufPos, min_len ); _buf += min_len; _len -= min_len; m_convertedBufPos += min_len; m_convertedBufPos %= m_convertedBufSize; } } AudioSdl::setupWidget::setupWidget( QWidget * _parent ) : AudioDevice::setupWidget( AudioSdl::name(), _parent ) { QString dev = configManager::inst()->value( "audiosdl", "device" ); m_device = new QLineEdit( dev, this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); } AudioSdl::setupWidget::~setupWidget() { } void AudioSdl::setupWidget::saveSettings() { configManager::inst()->setValue( "audiosdl", "device", m_device->text() ); } #endif lmms-1.0.0/src/core/audio/AudioPulseAudio.cpp0000644000175000017500000001601312313663627017566 0ustar tobytoby/* * AudioPulseAudio.cpp - device-class which implements PulseAudio-output * * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "AudioPulseAudio.h" #ifdef LMMS_HAVE_PULSEAUDIO #include "endian_handling.h" #include "config_mgr.h" #include "LcdSpinBox.h" #include "gui_templates.h" #include "templates.h" #include "engine.h" static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { static_cast( userdata )->streamWriteCallback( s, length ); } AudioPulseAudio::AudioPulseAudio( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( configManager::inst()->value( "audiopa", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_s( NULL ), m_quit( false ), m_convertEndian( false ) { _success_ful = false; m_sampleSpec.format = PA_SAMPLE_S16LE; m_sampleSpec.rate = sampleRate(); m_sampleSpec.channels = channels(); _success_ful = true; } AudioPulseAudio::~AudioPulseAudio() { stopProcessing(); } QString AudioPulseAudio::probeDevice() { QString dev = configManager::inst()->value( "audiopa", "device" ); if( dev.isEmpty() ) { if( getenv( "AUDIODEV" ) != NULL ) { return getenv( "AUDIODEV" ); } return "default"; } return dev; } void AudioPulseAudio::startProcessing() { if( !isRunning() ) { start( QThread::HighPriority ); } } void AudioPulseAudio::stopProcessing() { if( isRunning() ) { wait( 1000 ); terminate(); } } void AudioPulseAudio::applyQualitySettings() { if( hqAudio() ) { // setSampleRate( engine::mixer()->processingSampleRate() ); } AudioDevice::applyQualitySettings(); } /* This routine is called whenever the stream state changes */ static void stream_state_callback( pa_stream *s, void * userdata ) { switch( pa_stream_get_state( s ) ) { case PA_STREAM_CREATING: case PA_STREAM_TERMINATED: break; case PA_STREAM_READY: qDebug( "Stream successfully created\n" ); break; case PA_STREAM_FAILED: default: qCritical( "Stream errror: %s\n", pa_strerror(pa_context_errno( pa_stream_get_context( s ) ) ) ); } } /* This is called whenever the context status changes */ static void context_state_callback(pa_context *c, void *userdata) { AudioPulseAudio * _this = static_cast( userdata ); switch( pa_context_get_state( c ) ) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: { qDebug( "Connection established.\n" ); _this->m_s = pa_stream_new( c, "lmms", &_this->m_sampleSpec, NULL); pa_stream_set_state_callback( _this->m_s, stream_state_callback, _this ); pa_stream_set_write_callback( _this->m_s, stream_write_callback, _this ); pa_buffer_attr buffer_attr; buffer_attr.maxlength = (uint32_t)(-1); // play silence in case of buffer underun instead of using default rewind buffer_attr.prebuf = 0; buffer_attr.minreq = (uint32_t)(-1); buffer_attr.fragsize = (uint32_t)(-1); double latency = (double)( engine::mixer()->framesPerPeriod() ) / (double)_this->sampleRate(); // ask PulseAudio for the desired latency (which might not be approved) buffer_attr.tlength = pa_usec_to_bytes( latency * PA_USEC_PER_MSEC, &_this->m_sampleSpec ); pa_stream_connect_playback( _this->m_s, NULL, &buffer_attr, PA_STREAM_ADJUST_LATENCY, NULL, // volume NULL ); break; } case PA_CONTEXT_TERMINATED: break; case PA_CONTEXT_FAILED: default: qCritical( "Connection failure: %s\n", pa_strerror( pa_context_errno( c ) ) ); } } void AudioPulseAudio::run() { pa_mainloop * mainLoop = pa_mainloop_new(); if( !mainLoop ) { qCritical( "pa_mainloop_new() failed.\n" ); return; } pa_mainloop_api * mainloop_api = pa_mainloop_get_api( mainLoop ); pa_context *context = pa_context_new( mainloop_api, "lmms" ); if ( context == NULL ) { qCritical( "pa_context_new() failed." ); return; } pa_context_set_state_callback( context, context_state_callback, this ); // connect the context pa_context_connect( context, NULL, (pa_context_flags) 0, NULL ); // run the main loop int ret = 0; m_quit = false; while( m_quit == false && pa_mainloop_iterate( mainLoop, 1, &ret ) >= 0 ) { } pa_stream_disconnect( m_s ); pa_stream_unref( m_s ); pa_context_disconnect( context ); pa_context_unref( context ); pa_mainloop_free( mainLoop ); } void AudioPulseAudio::streamWriteCallback( pa_stream *s, size_t length ) { const fpp_t fpp = mixer()->framesPerPeriod(); surroundSampleFrame * temp = new surroundSampleFrame[fpp]; int_sample_t* pcmbuf = (int_sample_t *)pa_xmalloc( fpp * channels() * sizeof(int_sample_t) ); size_t fd = 0; while( fd < length/4 && m_quit == false ) { const fpp_t frames = getNextBuffer( temp ); if( !frames ) { m_quit = true; break; } int bytes = convertToS16( temp, frames, mixer()->masterGain(), pcmbuf, m_convertEndian ); if( bytes > 0 ) { pa_stream_write( m_s, pcmbuf, bytes, NULL, 0, PA_SEEK_RELATIVE ); } fd += frames; } pa_xfree( pcmbuf ); delete[] temp; } AudioPulseAudio::setupWidget::setupWidget( QWidget * _parent ) : AudioDevice::setupWidget( AudioPulseAudio::name(), _parent ) { m_device = new QLineEdit( AudioPulseAudio::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( configManager::inst()->value( "audiopa", "channels" ).toInt() ); m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); m_channels->setLabel( tr( "CHANNELS" ) ); m_channels->move( 180, 20 ); } AudioPulseAudio::setupWidget::~setupWidget() { } void AudioPulseAudio::setupWidget::saveSettings() { configManager::inst()->setValue( "audiopa", "device", m_device->text() ); configManager::inst()->setValue( "audiopa", "channels", QString::number( m_channels->value() ) ); } #endif lmms-1.0.0/src/core/audio/AudioFileWave.cpp0000644000175000017500000000573312313663627017225 0ustar tobytoby/* * AudioFileWave.cpp - audio-device which encodes wave-stream and writes it * into a WAVE-file. This is used for song-export. * * Copyright (c) 2004-2013 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioFileWave.h" #include "endian_handling.h" AudioFileWave::AudioFileWave( const sample_rate_t _sample_rate, const ch_cnt_t _channels, bool & _success_ful, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, Mixer* _mixer ) : AudioFileDevice( _sample_rate, _channels, _file, _use_vbr, _nom_bitrate, _min_bitrate, _max_bitrate, _depth, _mixer ), m_sf( NULL ) { _success_ful = outputFileOpened() && startEncoding(); } AudioFileWave::~AudioFileWave() { finishEncoding(); } bool AudioFileWave::startEncoding() { m_si.samplerate = sampleRate(); m_si.channels = channels(); m_si.frames = mixer()->framesPerPeriod(); m_si.sections = 1; m_si.seekable = 0; switch( depth() ) { case 32: m_si.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; break; case 16: default: m_si.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; break; } m_sf = sf_open( #ifdef LMMS_BUILD_WIN32 outputFile().toLocal8Bit().constData(), #else outputFile().toUtf8().constData(), #endif SFM_WRITE, &m_si ); sf_set_string ( m_sf, SF_STR_SOFTWARE, "LMMS" ); return true; } void AudioFileWave::writeBuffer( const surroundSampleFrame * _ab, const fpp_t _frames, const float _master_gain ) { if( depth() == 32 ) { float * buf = new float[_frames*channels()]; for( fpp_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { buf[frame*channels()+chnl] = _ab[frame][chnl] * _master_gain; } } sf_writef_float( m_sf, buf, _frames ); delete[] buf; } else { int_sample_t * buf = new int_sample_t[_frames * channels()]; convertToS16( _ab, _frames, _master_gain, buf, !isLittleEndian() ); sf_writef_short( m_sf, buf, _frames ); delete[] buf; } } void AudioFileWave::finishEncoding() { if( m_sf ) { sf_close( m_sf ); } } lmms-1.0.0/src/core/audio/AudioOss.cpp0000644000175000017500000001677312313663627016275 0ustar tobytoby/* * AudioOss.cpp - device-class that implements OSS-PCM-output * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioOss.h" #ifdef LMMS_HAVE_OSS #include #include #include #include "endian_handling.h" #include "LcdSpinBox.h" #include "engine.h" #include "gui_templates.h" #include "templates.h" #ifdef LMMS_HAVE_UNISTD_H #include #endif #ifdef LMMS_HAVE_FCNTL_H #include #endif #ifdef LMMS_HAVE_SYS_IOCTL_H #include #endif #ifdef LMMS_HAVE_STDLIB_H #include #endif #ifdef LMMS_HAVE_SYS_SOUNDCARD_H // This is recommended by OSS #include #elif defined(LMMS_HAVE_SOUNDCARD_H) // This is installed on some systems #include #endif #include "config_mgr.h" #ifndef _PATH_DEV_DSP #ifdef __OpenBSD__ #define _PATH_DEV_DSP "/dev/audio" #else #define _PATH_DEV_DSP "/dev/dsp" #endif #endif AudioOss::AudioOss( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( configManager::inst()->value( "audiooss", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_convertEndian( false ) { _success_ful = false; m_audioFD = open( probeDevice().toAscii().constData(), O_WRONLY, 0 ); if( m_audioFD == -1 ) { printf( "AudioOss: failed opening audio-device\n" ); return; } // Make the file descriptor use blocking writes with fcntl() if ( fcntl( m_audioFD, F_SETFL, fcntl( m_audioFD, F_GETFL ) & ~O_NONBLOCK ) < 0 ) { printf( "could not set audio blocking mode\n" ); return; } // set FD_CLOEXEC flag for file descriptor so forked processes // do not inherit it fcntl( m_audioFD, F_SETFD, fcntl( m_audioFD, F_GETFD ) | FD_CLOEXEC ); int frag_spec; for( frag_spec = 0; static_cast( 0x01 << frag_spec ) < mixer()->framesPerPeriod() * channels() * BYTES_PER_INT_SAMPLE; ++frag_spec ) { } frag_spec |= 0x00020000; // two fragments, for low latency if ( ioctl( m_audioFD, SNDCTL_DSP_SETFRAGMENT, &frag_spec ) < 0 ) { perror( "SNDCTL_DSP_SETFRAGMENT" ); printf( "Warning: Couldn't set audio fragment size\n" ); } unsigned int value; // Get a list of supported hardware formats if ( ioctl( m_audioFD, SNDCTL_DSP_GETFMTS, &value ) < 0 ) { perror( "SNDCTL_DSP_GETFMTS" ); printf( "Couldn't get audio format list\n" ); return; } // Set the audio format if( value & AFMT_S16_LE ) { value = AFMT_S16_LE; } else if( value & AFMT_S16_BE ) { value = AFMT_S16_BE; } else { printf(" Soundcard doesn't support signed 16-bit-data\n"); } if ( ioctl( m_audioFD, SNDCTL_DSP_SETFMT, &value ) < 0 ) { perror( "SNDCTL_DSP_SETFMT" ); printf( "Couldn't set audio format\n" ); return; } if( ( isLittleEndian() && ( value == AFMT_S16_BE ) ) || ( !isLittleEndian() && ( value == AFMT_S16_LE ) ) ) { m_convertEndian = true; } // Set the number of channels of output value = channels(); if ( ioctl( m_audioFD, SNDCTL_DSP_CHANNELS, &value ) < 0 ) { perror( "SNDCTL_DSP_CHANNELS" ); printf( "Cannot set the number of channels\n" ); return; } if( value != channels() ) { printf( "Couldn't set number of channels\n" ); return; } // Set the DSP frequency value = sampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); printf( "Couldn't set audio frequency\n" ); return; } if( value != sampleRate() ) { value = mixer()->baseSampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); printf( "Couldn't set audio frequency\n" ); return; } setSampleRate( value ); } _success_ful = true; } AudioOss::~AudioOss() { stopProcessing(); close( m_audioFD ); } QString AudioOss::probeDevice() { QString dev = configManager::inst()->value( "AudioOss", "Device" ); if( dev.isEmpty() ) { char * adev = getenv( "AUDIODEV" ); // Is there a standard // variable name? if( adev != NULL ) { dev = adev; } else { dev = _PATH_DEV_DSP; // default device } } // if the first open fails, look for other devices if( QFileInfo( dev ).isWritable() == false ) { int instance = -1; while( 1 ) { dev = _PATH_DEV_DSP + QString::number( ++instance ); if( !QFileInfo( dev ).exists() ) { dev = _PATH_DEV_DSP; break; } if( QFileInfo( dev ).isWritable() ) { break; } } } return dev; } void AudioOss::startProcessing() { if( !isRunning() ) { start( QThread::HighPriority ); } } void AudioOss::stopProcessing() { if( isRunning() ) { wait( 1000 ); terminate(); } } void AudioOss::applyQualitySettings() { if( hqAudio() ) { setSampleRate( engine::mixer()->processingSampleRate() ); unsigned int value = sampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); printf( "Couldn't set audio frequency\n" ); return; } if( value != sampleRate() ) { value = mixer()->baseSampleRate(); if ( ioctl( m_audioFD, SNDCTL_DSP_SPEED, &value ) < 0 ) { perror( "SNDCTL_DSP_SPEED" ); printf( "Couldn't set audio frequency\n" ); return; } setSampleRate( value ); } } AudioDevice::applyQualitySettings(); } void AudioOss::run() { surroundSampleFrame * temp = new surroundSampleFrame[mixer()->framesPerPeriod()]; int_sample_t * outbuf = new int_sample_t[mixer()->framesPerPeriod() * channels()]; while( true ) { const fpp_t frames = getNextBuffer( temp ); if( !frames ) { break; } int bytes = convertToS16( temp, frames, mixer()->masterGain(), outbuf, m_convertEndian ); if( write( m_audioFD, outbuf, bytes ) != bytes ) { break; } } delete[] temp; delete[] outbuf; } AudioOss::setupWidget::setupWidget( QWidget * _parent ) : AudioDevice::setupWidget( AudioOss::name(), _parent ) { m_device = new QLineEdit( probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( configManager::inst()->value( "audiooss", "channels" ).toInt() ); m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); m_channels->setLabel( tr( "CHANNELS" ) ); m_channels->move( 180, 20 ); } AudioOss::setupWidget::~setupWidget() { } void AudioOss::setupWidget::saveSettings() { configManager::inst()->setValue( "audiooss", "device", m_device->text() ); configManager::inst()->setValue( "audiooss", "channels", QString::number( m_channels->value() ) ); } #endif lmms-1.0.0/src/core/audio/AudioJack.cpp0000644000175000017500000002403412313663627016366 0ustar tobytoby/* * AudioJack.cpp - support for JACK transport * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioJack.h" #ifdef LMMS_HAVE_JACK #include #include #include #include #include "debug.h" #include "engine.h" #include "templates.h" #include "gui_templates.h" #include "config_mgr.h" #include "LcdSpinBox.h" #include "AudioPort.h" #include "MainWindow.h" AudioJack::AudioJack( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( configManager::inst()->value( "audiojack", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_client( NULL ), m_active( false ), m_stopSemaphore( 1 ), m_tempOutBufs( new jack_default_audio_sample_t *[channels()] ), m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ), m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) { _success_ful = initJackClient(); if( _success_ful ) { m_stopSemaphore.acquire(); connect( this, SIGNAL( zombified() ), this, SLOT( restartAfterZombified() ), Qt::QueuedConnection ); } } AudioJack::~AudioJack() { m_stopSemaphore.release(); #ifdef AUDIO_PORT_SUPPORT while( m_portMap.size() ) { unregisterPort( m_portMap.begin().key() ); } #endif if( m_client != NULL ) { if( m_active ) { jack_deactivate( m_client ); } jack_client_close( m_client ); } delete[] m_tempOutBufs; delete[] m_outBuf; } void AudioJack::restartAfterZombified() { if( initJackClient() ) { m_active = false; startProcessing(); QMessageBox::information( engine::mainWindow(), tr( "JACK client restarted" ), tr( "LMMS was kicked by JACK for some reason. " "Therefore the JACK backend of LMMS has been " "restarted. You will have to make manual " "connections again." ) ); } else { QMessageBox::information( engine::mainWindow(), tr( "JACK server down" ), tr( "The JACK server seems to have been shutdown " "and starting a new instance failed. " "Therefore LMMS is unable to proceed. " "You should save your project and restart " "JACK and LMMS." ) ); } } bool AudioJack::initJackClient() { QString clientName = configManager::inst()->value( "audiojack", "clientname" ); if( clientName.isEmpty() ) { clientName = "lmms"; } const char * serverName = NULL; jack_status_t status; m_client = jack_client_open( clientName.toAscii().constData(), JackNullOption, &status, serverName ); if( m_client == NULL ) { printf( "jack_client_open() failed, status 0x%2.0x\n", status ); if( status & JackServerFailed ) { printf( "Could not connect to JACK server.\n" ); } return false; } if( status & JackNameNotUnique ) { printf( "there's already a client with name '%s', so unique " "name '%s' was assigned\n", clientName. toAscii().constData(), jack_get_client_name( m_client ) ); } // set process-callback jack_set_process_callback( m_client, staticProcessCallback, this ); // set shutdown-callback jack_on_shutdown( m_client, shutdownCallback, this ); if( jack_get_sample_rate( m_client ) != sampleRate() ) { setSampleRate( jack_get_sample_rate( m_client ) ); } for( ch_cnt_t ch = 0; ch < channels(); ++ch ) { QString name = QString( "master out " ) + ( ( ch % 2 ) ? "R" : "L" ) + QString::number( ch / 2 + 1 ); m_outputPorts.push_back( jack_port_register( m_client, name.toAscii().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ) ); if( m_outputPorts.back() == NULL ) { printf( "no more JACK-ports available!\n" ); return false; } } return true; } void AudioJack::startProcessing() { m_stopped = false; if( m_active || m_client == NULL ) { return; } if( jack_activate( m_client ) ) { printf( "cannot activate client\n" ); return; } m_active = true; // try to sync JACK's and LMMS's buffer-size // jack_set_buffer_size( m_client, mixer()->framesPerPeriod() ); const char * * ports = jack_get_ports( m_client, NULL, NULL, JackPortIsPhysical | JackPortIsInput ); if( ports == NULL ) { printf( "no physical playback ports. you'll have to do " "connections at your own!\n" ); } else { for( ch_cnt_t ch = 0; ch < channels(); ++ch ) { if( jack_connect( m_client, jack_port_name( m_outputPorts[ch] ), ports[ch] ) ) { printf( "cannot connect output ports. you'll " "have to do connections at your own!\n" ); } } } free( ports ); } void AudioJack::stopProcessing() { m_stopSemaphore.acquire(); } void AudioJack::applyQualitySettings() { if( hqAudio() ) { setSampleRate( engine::mixer()->processingSampleRate() ); if( jack_get_sample_rate( m_client ) != sampleRate() ) { setSampleRate( jack_get_sample_rate( m_client ) ); } } AudioDevice::applyQualitySettings(); } void AudioJack::registerPort( AudioPort * _port ) { #ifdef AUDIO_PORT_SUPPORT // make sure, port is not already registered unregisterPort( _port ); const QString name[2] = { _port->name() + " L", _port->name() + " R" } ; for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { m_portMap[_port].ports[ch] = jack_port_register( m_client, name[ch].toAscii().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); } #endif } void AudioJack::unregisterPort( AudioPort * _port ) { #ifdef AUDIO_PORT_SUPPORT if( m_portMap.contains( _port ) ) { for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { if( m_portMap[_port].ports[ch] != NULL ) { jack_port_unregister( m_client, m_portMap[_port].ports[ch] ); } } m_portMap.erase( m_portMap.find( _port ) ); } #endif } void AudioJack::renamePort( AudioPort * _port ) { #ifdef AUDIO_PORT_SUPPORT if( m_portMap.contains( _port ) ) { const QString name[2] = { _port->name() + " L", _port->name() + " R" }; for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { jack_port_set_name( m_portMap[_port].ports[ch], name[ch].toAscii().constData() ); } } #endif } int AudioJack::processCallback( jack_nframes_t _nframes, void * _udata ) { for( int c = 0; c < channels(); ++c ) { m_tempOutBufs[c] = (jack_default_audio_sample_t *) jack_port_get_buffer( m_outputPorts[c], _nframes ); } #ifdef AUDIO_PORT_SUPPORT const int frames = qMin( _nframes, mixer()->framesPerPeriod() ); for( jackPortMap::iterator it = m_portMap.begin(); it != m_portMap.end(); ++it ) { for( ch_cnt_t ch = 0; ch < channels(); ++ch ) { if( it.data().ports[ch] == NULL ) { continue; } jack_default_audio_sample_t * buf = (jack_default_audio_sample_t *) jack_port_get_buffer( it.data().ports[ch], _nframes ); for( int frame = 0; frame < frames; ++frame ) { buf[frame] = it.key()->firstBuffer()[frame][ch]; } } } #endif jack_nframes_t done = 0; while( done < _nframes && m_stopped == false ) { jack_nframes_t todo = qMin( _nframes, m_framesToDoInCurBuf - m_framesDoneInCurBuf ); const float gain = mixer()->masterGain(); for( int c = 0; c < channels(); ++c ) { jack_default_audio_sample_t * o = m_tempOutBufs[c]; for( jack_nframes_t frame = 0; frame < todo; ++frame ) { o[done+frame] = m_outBuf[m_framesDoneInCurBuf+frame][c] * gain; } } done += todo; m_framesDoneInCurBuf += todo; if( m_framesDoneInCurBuf == m_framesToDoInCurBuf ) { m_framesToDoInCurBuf = getNextBuffer( m_outBuf ); if( !m_framesToDoInCurBuf ) { m_stopped = true; m_stopSemaphore.release(); } m_framesDoneInCurBuf = 0; } } if( m_stopped == true ) { for( int c = 0; c < channels(); ++c ) { jack_default_audio_sample_t * b = m_tempOutBufs[c] + done; memset( b, 0, sizeof( *b ) * ( _nframes - done ) ); } } return 0; } int AudioJack::staticProcessCallback( jack_nframes_t _nframes, void * _udata ) { return static_cast( _udata )-> processCallback( _nframes, _udata ); } void AudioJack::shutdownCallback( void * _udata ) { AudioJack * _this = static_cast( _udata ); _this->m_client = NULL; _this->zombified(); } AudioJack::setupWidget::setupWidget( QWidget * _parent ) : AudioDevice::setupWidget( AudioJack::name(), _parent ) { QString cn = configManager::inst()->value( "audiojack", "clientname" ); if( cn.isEmpty() ) { cn = "lmms"; } m_clientName = new QLineEdit( cn, this ); m_clientName->setGeometry( 10, 20, 160, 20 ); QLabel * cn_lbl = new QLabel( tr( "CLIENT-NAME" ), this ); cn_lbl->setFont( pointSize<7>( cn_lbl->font() ) ); cn_lbl->setGeometry( 10, 40, 160, 10 ); LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( configManager::inst()->value( "audiojack", "channels" ).toInt() ); m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); m_channels->setLabel( tr( "CHANNELS" ) ); m_channels->move( 180, 20 ); } AudioJack::setupWidget::~setupWidget() { } void AudioJack::setupWidget::saveSettings() { configManager::inst()->setValue( "audiojack", "clientname", m_clientName->text() ); configManager::inst()->setValue( "audiojack", "channels", QString::number( m_channels->value() ) ); } #include "moc_AudioJack.cxx" #endif lmms-1.0.0/src/core/audio/AudioPort.cpp0000644000175000017500000000572012313663627016443 0ustar tobytoby/* * AudioPort.cpp - base-class for objects providing sound at a port * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AudioPort.h" #include "AudioDevice.h" #include "EffectChain.h" #include "engine.h" AudioPort::AudioPort( const QString & _name, bool _has_effect_chain ) : m_bufferUsage( NoUsage ), m_firstBuffer( new sampleFrame[engine::mixer()->framesPerPeriod()] ), m_secondBuffer( new sampleFrame[ engine::mixer()->framesPerPeriod()] ), m_extOutputEnabled( false ), m_nextFxChannel( 0 ), m_name( "unnamed port" ), m_effects( _has_effect_chain ? new EffectChain( NULL ) : NULL ) { engine::mixer()->clearAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod() ); engine::mixer()->clearAudioBuffer( m_secondBuffer, engine::mixer()->framesPerPeriod() ); engine::mixer()->addAudioPort( this ); setExtOutputEnabled( true ); } AudioPort::~AudioPort() { setExtOutputEnabled( false ); engine::mixer()->removeAudioPort( this ); delete[] m_firstBuffer; delete[] m_secondBuffer; delete m_effects; } void AudioPort::nextPeriod() { m_firstBufferLock.lock(); engine::mixer()->clearAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod() ); qSwap( m_firstBuffer, m_secondBuffer ); // this is how we decrease state of buffer-usage ;-) m_bufferUsage = ( m_bufferUsage != NoUsage ) ? ( ( m_bufferUsage == FirstBuffer ) ? NoUsage : FirstBuffer ) : NoUsage; m_firstBufferLock.unlock(); } void AudioPort::setExtOutputEnabled( bool _enabled ) { if( _enabled != m_extOutputEnabled ) { m_extOutputEnabled = _enabled; if( m_extOutputEnabled ) { engine::mixer()->audioDev()->registerPort( this ); } else { engine::mixer()->audioDev()->unregisterPort( this ); } } } void AudioPort::setName( const QString & _name ) { m_name = _name; engine::mixer()->audioDev()->renamePort( this ); } bool AudioPort::processEffects() { if( m_effects ) { lockFirstBuffer(); bool hasInputNoise = m_bufferUsage != NoUsage; bool more = m_effects->processAudioBuffer( m_firstBuffer, engine::mixer()->framesPerPeriod(), hasInputNoise ); unlockFirstBuffer(); return more; } return false; } lmms-1.0.0/src/core/audio/AudioDevice.cpp0000644000175000017500000001174212313663627016717 0ustar tobytoby/* * AudioDevice.cpp - base-class for audio-devices used by LMMS-mixer * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "AudioDevice.h" #include "config_mgr.h" #include "debug.h" AudioDevice::AudioDevice( const ch_cnt_t _channels, Mixer* _mixer ) : m_supportsCapture( false ), m_sampleRate( _mixer->processingSampleRate() ), m_channels( _channels ), m_mixer( _mixer ), m_buffer( new surroundSampleFrame[mixer()->framesPerPeriod()] ) { int error; if( ( m_srcState = src_new( mixer()->currentQualitySettings().libsrcInterpolation(), SURROUND_CHANNELS, &error ) ) == NULL ) { printf( "Error: src_new() failed in audio_device.cpp!\n" ); } } AudioDevice::~AudioDevice() { src_delete( m_srcState ); delete[] m_buffer; m_devMutex.tryLock(); unlock(); } void AudioDevice::processNextBuffer() { const fpp_t frames = getNextBuffer( m_buffer ); if( frames ) { writeBuffer( m_buffer, frames, mixer()->masterGain() ); } else { m_inProcess = false; } } fpp_t AudioDevice::getNextBuffer( surroundSampleFrame * _ab ) { fpp_t frames = mixer()->framesPerPeriod(); const surroundSampleFrame * b = mixer()->nextBuffer(); if( !b ) { return 0; } // make sure, no other thread is accessing device lock(); // resample if necessary if( mixer()->processingSampleRate() != m_sampleRate ) { resample( b, frames, _ab, mixer()->processingSampleRate(), m_sampleRate ); frames = frames * m_sampleRate / mixer()->processingSampleRate(); } else { memcpy( _ab, b, frames * sizeof( surroundSampleFrame ) ); } // release lock unlock(); if( mixer()->hasFifoWriter() ) { delete[] b; } return frames; } void AudioDevice::stopProcessing() { if( mixer()->hasFifoWriter() ) { while( m_inProcess ) { processNextBuffer(); } } } void AudioDevice::applyQualitySettings() { src_delete( m_srcState ); int error; if( ( m_srcState = src_new( mixer()->currentQualitySettings().libsrcInterpolation(), SURROUND_CHANNELS, &error ) ) == NULL ) { printf( "Error: src_new() failed in audio_device.cpp!\n" ); } } void AudioDevice::registerPort( AudioPort * ) { } void AudioDevice::unregisterPort( AudioPort * _port ) { } void AudioDevice::renamePort( AudioPort * ) { } void AudioDevice::resample( const surroundSampleFrame * _src, const fpp_t _frames, surroundSampleFrame * _dst, const sample_rate_t _src_sr, const sample_rate_t _dst_sr ) { if( m_srcState == NULL ) { return; } m_srcData.input_frames = _frames; m_srcData.output_frames = _frames; m_srcData.data_in = (float *) _src[0]; m_srcData.data_out = _dst[0]; m_srcData.src_ratio = (double) _dst_sr / _src_sr; m_srcData.end_of_input = 0; int error; if( ( error = src_process( m_srcState, &m_srcData ) ) ) { printf( "AudioDevice::resample(): error while resampling: %s\n", src_strerror( error ) ); } } int AudioDevice::convertToS16( const surroundSampleFrame * _ab, const fpp_t _frames, const float _master_gain, int_sample_t * _output_buffer, const bool _convert_endian ) { if( _convert_endian ) { int_sample_t temp; for( fpp_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { temp = static_cast( Mixer::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER ); ( _output_buffer + frame * channels() )[chnl] = ( temp & 0x00ff ) << 8 | ( temp & 0xff00 ) >> 8; } } } else { for( fpp_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { ( _output_buffer + frame * channels() )[chnl] = static_cast( Mixer::clip( _ab[frame][chnl] * _master_gain ) * OUTPUT_SAMPLE_MULTIPLIER ); } } } return _frames * channels() * BYTES_PER_INT_SAMPLE; } void AudioDevice::clearS16Buffer( int_sample_t * _outbuf, const fpp_t _frames ) { #ifdef LMMS_DEBUG assert( _outbuf != NULL ); #endif memset( _outbuf, 0, _frames * channels() * BYTES_PER_INT_SAMPLE ); } bool AudioDevice::hqAudio() const { return configManager::inst()->value( "mixer", "hqaudio" ).toInt(); } lmms-1.0.0/src/core/audio/AudioAlsa.cpp0000644000175000017500000002657312313663627016410 0ustar tobytoby/* * audio_alsa.cpp - device-class which implements ALSA-PCM-output * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "AudioAlsa.h" #ifdef LMMS_HAVE_ALSA #include "endian_handling.h" #include "config_mgr.h" #include "engine.h" #include "LcdSpinBox.h" #include "gui_templates.h" #include "templates.h" AudioAlsa::AudioAlsa( bool & _success_ful, Mixer* _mixer ) : AudioDevice( tLimit( configManager::inst()->value( "audioalsa", "channels" ).toInt(), DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_handle( NULL ), m_hwParams( NULL ), m_swParams( NULL ), m_convertEndian( false ) { _success_ful = false; int err; if( ( err = snd_pcm_open( &m_handle, probeDevice().toAscii().constData(), SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 ) { printf( "Playback open error: %s\n", snd_strerror( err ) ); return; } snd_pcm_hw_params_malloc( &m_hwParams ); snd_pcm_sw_params_malloc( &m_swParams ); if( ( err = setHWParams( channels(), SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) { printf( "Setting of hwparams failed: %s\n", snd_strerror( err ) ); return; } if( ( err = setSWParams() ) < 0 ) { printf( "Setting of swparams failed: %s\n", snd_strerror( err ) ); return; } // set FD_CLOEXEC flag for all file descriptors so forked processes // do not inherit them struct pollfd * ufds; int count = snd_pcm_poll_descriptors_count( m_handle ); ufds = new pollfd[count]; snd_pcm_poll_descriptors( m_handle, ufds, count ); for( int i = 0; i < qMax( 3, count ); ++i ) { const int fd = ( i >= count ) ? ufds[0].fd+i : ufds[i].fd; int oldflags = fcntl( fd, F_GETFD, 0 ); if( oldflags < 0 ) continue; oldflags |= FD_CLOEXEC; fcntl( fd, F_SETFD, oldflags ); } _success_ful = true; } AudioAlsa::~AudioAlsa() { stopProcessing(); if( m_handle != NULL ) { snd_pcm_close( m_handle ); } if( m_hwParams != NULL ) { snd_pcm_hw_params_free( m_hwParams ); } if( m_swParams != NULL ) { snd_pcm_sw_params_free( m_swParams ); } } QString AudioAlsa::probeDevice() { QString dev = configManager::inst()->value( "audioalsa", "device" ); if( dev == "" ) { if( getenv( "AUDIODEV" ) != NULL ) { return getenv( "AUDIODEV" ); } return "default"; } return dev; } int AudioAlsa::handleError( int _err ) { if( _err == -EPIPE ) { // under-run _err = snd_pcm_prepare( m_handle ); if( _err < 0 ) printf( "Can't recovery from underrun, prepare " "failed: %s\n", snd_strerror( _err ) ); return ( 0 ); } else if( _err == -ESTRPIPE ) { while( ( _err = snd_pcm_resume( m_handle ) ) == -EAGAIN ) { sleep( 1 ); // wait until the suspend flag // is released } if( _err < 0 ) { _err = snd_pcm_prepare( m_handle ); if( _err < 0 ) printf( "Can't recovery from suspend, prepare " "failed: %s\n", snd_strerror( _err ) ); } return ( 0 ); } return _err; } void AudioAlsa::startProcessing() { if( !isRunning() ) { start( QThread::HighPriority ); } } void AudioAlsa::stopProcessing() { if( isRunning() ) { wait( 1000 ); terminate(); } } void AudioAlsa::applyQualitySettings() { if( hqAudio() ) { setSampleRate( engine::mixer()->processingSampleRate() ); if( m_handle != NULL ) { snd_pcm_close( m_handle ); } int err; if( ( err = snd_pcm_open( &m_handle, probeDevice().toAscii().constData(), SND_PCM_STREAM_PLAYBACK, 0 ) ) < 0 ) { printf( "Playback open error: %s\n", snd_strerror( err ) ); return; } if( ( err = setHWParams( channels(), SND_PCM_ACCESS_RW_INTERLEAVED ) ) < 0 ) { printf( "Setting of hwparams failed: %s\n", snd_strerror( err ) ); return; } if( ( err = setSWParams() ) < 0 ) { printf( "Setting of swparams failed: %s\n", snd_strerror( err ) ); return; } } AudioDevice::applyQualitySettings(); } void AudioAlsa::run() { surroundSampleFrame * temp = new surroundSampleFrame[mixer()->framesPerPeriod()]; int_sample_t * outbuf = new int_sample_t[mixer()->framesPerPeriod() * channels()]; int_sample_t * pcmbuf = new int_sample_t[m_periodSize * channels()]; int outbuf_size = mixer()->framesPerPeriod() * channels(); int outbuf_pos = 0; int pcmbuf_size = m_periodSize * channels(); bool quit = false; while( quit == false ) { int_sample_t * ptr = pcmbuf; int len = pcmbuf_size; while( len ) { if( outbuf_pos == 0 ) { // frames depend on the sample rate const fpp_t frames = getNextBuffer( temp ); if( !frames ) { quit = true; memset( ptr, 0, len * sizeof( int_sample_t ) ); break; } outbuf_size = frames * channels(); convertToS16( temp, frames, mixer()->masterGain(), outbuf, m_convertEndian ); } int min_len = qMin( len, outbuf_size - outbuf_pos ); memcpy( ptr, outbuf + outbuf_pos, min_len * sizeof( int_sample_t ) ); ptr += min_len; len -= min_len; outbuf_pos += min_len; outbuf_pos %= outbuf_size; } f_cnt_t frames = m_periodSize; ptr = pcmbuf; while( frames ) { int err = snd_pcm_writei( m_handle, ptr, frames ); if( err == -EAGAIN ) { continue; } if( err < 0 ) { if( handleError( err ) < 0 ) { printf( "Write error: %s\n", snd_strerror( err ) ); } break; // skip this buffer } ptr += err * channels(); frames -= err; } } delete[] temp; delete[] outbuf; delete[] pcmbuf; } int AudioAlsa::setHWParams( const ch_cnt_t _channels, snd_pcm_access_t _access ) { int err, dir; // choose all parameters if( ( err = snd_pcm_hw_params_any( m_handle, m_hwParams ) ) < 0 ) { printf( "Broken configuration for playback: no configurations " "available: %s\n", snd_strerror( err ) ); return err; } // set the interleaved read/write format if( ( err = snd_pcm_hw_params_set_access( m_handle, m_hwParams, _access ) ) < 0 ) { printf( "Access type not available for playback: %s\n", snd_strerror( err ) ); return err; } // set the sample format if( ( snd_pcm_hw_params_set_format( m_handle, m_hwParams, SND_PCM_FORMAT_S16_LE ) ) < 0 ) { if( ( snd_pcm_hw_params_set_format( m_handle, m_hwParams, SND_PCM_FORMAT_S16_BE ) ) < 0 ) { printf( "Neither little- nor big-endian available for " "playback: %s\n", snd_strerror( err ) ); return err; } m_convertEndian = isLittleEndian(); } else { m_convertEndian = !isLittleEndian(); } // set the count of channels if( ( err = snd_pcm_hw_params_set_channels( m_handle, m_hwParams, _channels ) ) < 0 ) { printf( "Channel count (%i) not available for playbacks: %s\n" "(Does your soundcard not support surround?)\n", _channels, snd_strerror( err ) ); return err; } // set the sample rate if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams, sampleRate(), 0 ) ) < 0 ) { if( ( err = snd_pcm_hw_params_set_rate( m_handle, m_hwParams, mixer()->baseSampleRate(), 0 ) ) < 0 ) { printf( "Could not set sample rate: %s\n", snd_strerror( err ) ); return err; } } m_periodSize = mixer()->framesPerPeriod(); m_bufferSize = m_periodSize * 8; dir = 0; err = snd_pcm_hw_params_set_period_size_near( m_handle, m_hwParams, &m_periodSize, &dir ); if( err < 0 ) { printf( "Unable to set period size %lu for playback: %s\n", m_periodSize, snd_strerror( err ) ); return err; } dir = 0; err = snd_pcm_hw_params_get_period_size( m_hwParams, &m_periodSize, &dir ); if( err < 0 ) { printf( "Unable to get period size for playback: %s\n", snd_strerror( err ) ); } dir = 0; err = snd_pcm_hw_params_set_buffer_size_near( m_handle, m_hwParams, &m_bufferSize ); if( err < 0 ) { printf( "Unable to set buffer size %lu for playback: %s\n", m_bufferSize, snd_strerror( err ) ); return ( err ); } err = snd_pcm_hw_params_get_buffer_size( m_hwParams, &m_bufferSize ); if( 2 * m_periodSize > m_bufferSize ) { printf( "buffer to small, could not use\n" ); return ( err ); } // write the parameters to device err = snd_pcm_hw_params( m_handle, m_hwParams ); if( err < 0 ) { printf( "Unable to set hw params for playback: %s\n", snd_strerror( err ) ); return ( err ); } return ( 0 ); // all ok } int AudioAlsa::setSWParams() { int err; // get the current swparams if( ( err = snd_pcm_sw_params_current( m_handle, m_swParams ) ) < 0 ) { printf( "Unable to determine current swparams for playback: %s" "\n", snd_strerror( err ) ); return err; } // start the transfer when a period is full if( ( err = snd_pcm_sw_params_set_start_threshold( m_handle, m_swParams, m_periodSize ) ) < 0 ) { printf( "Unable to set start threshold mode for playback: %s\n", snd_strerror( err ) ); return err; } // allow the transfer when at least m_periodSize samples can be // processed if( ( err = snd_pcm_sw_params_set_avail_min( m_handle, m_swParams, m_periodSize ) ) < 0 ) { printf( "Unable to set avail min for playback: %s\n", snd_strerror( err ) ); return err; } // align all transfers to 1 sample #if SND_LIB_VERSION < ((1<<16)|(0)|16) if( ( err = snd_pcm_sw_params_set_xfer_align( m_handle, m_swParams, 1 ) ) < 0 ) { printf( "Unable to set transfer align for playback: %s\n", snd_strerror( err ) ); return err; } #endif // write the parameters to the playback device if( ( err = snd_pcm_sw_params( m_handle, m_swParams ) ) < 0 ) { printf( "Unable to set sw params for playback: %s\n", snd_strerror( err ) ); return err; } return 0; // all ok } AudioAlsa::setupWidget::setupWidget( QWidget * _parent ) : AudioDevice::setupWidget( AudioAlsa::name(), _parent ) { m_device = new QLineEdit( AudioAlsa::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ ); m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS ); m->setStep( 2 ); m->setValue( configManager::inst()->value( "audioalsa", "channels" ).toInt() ); m_channels = new LcdSpinBox( 1, this ); m_channels->setModel( m ); m_channels->setLabel( tr( "CHANNELS" ) ); m_channels->move( 180, 20 ); } AudioAlsa::setupWidget::~setupWidget() { } void AudioAlsa::setupWidget::saveSettings() { configManager::inst()->setValue( "audioalsa", "device", m_device->text() ); configManager::inst()->setValue( "audioalsa", "channels", QString::number( m_channels->value() ) ); } #endif lmms-1.0.0/src/core/audio/AudioFileDevice.cpp0000644000175000017500000000447512313663627017524 0ustar tobytoby/* * AudioFileDevice.cpp - base-class for audio-device-classes which write * their output into a file * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "AudioFileDevice.h" #include "export_project_dialog.h" AudioFileDevice::AudioFileDevice( const sample_rate_t _sample_rate, const ch_cnt_t _channels, const QString & _file, const bool _use_vbr, const bitrate_t _nom_bitrate, const bitrate_t _min_bitrate, const bitrate_t _max_bitrate, const int _depth, Mixer* _mixer ) : AudioDevice( _channels, _mixer ), m_outputFile( _file ), m_useVbr( _use_vbr ), m_nomBitrate( _nom_bitrate ), m_minBitrate( _min_bitrate ), m_maxBitrate( _max_bitrate ), m_depth( _depth ) { setSampleRate( _sample_rate ); if( m_outputFile.open( QFile::WriteOnly | QFile::Truncate ) == false ) { QMessageBox::critical( NULL, exportProjectDialog::tr( "Could not open file" ), exportProjectDialog::tr( "Could not open file %1 " "for writing.\nPlease make " "sure you have write-" "permission to the file and " "the directory containing the " "file and try again!" ).arg( _file ), QMessageBox::Ok, QMessageBox::NoButton ); } } AudioFileDevice::~AudioFileDevice() { m_outputFile.close(); } int AudioFileDevice::writeData( const void* data, int len ) { if( m_outputFile.isOpen() ) { return m_outputFile.write( (const char *) data, len ); } return -1; } lmms-1.0.0/src/core/ladspa_2_lmms.cpp0000644000175000017500000000601612313663627016150 0ustar tobytoby/* * ladspa_2_lmms.cpp - class that identifies and instantiates LADSPA effects * for use with LMMS * * Copyright (c) 2005-2008 Danny McRae * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "ladspa_2_lmms.h" ladspa2LMMS::ladspa2LMMS() { l_sortable_plugin_t plugins = getSortedPlugins(); for( l_sortable_plugin_t::iterator it = plugins.begin(); it != plugins.end(); it++ ) { ladspa_key_t key = (*it).second; ladspaManagerDescription * desc = getDescription( key ); if( desc->type == SOURCE ) { m_instruments.append( qMakePair( getName( key ), key ) ); } else if( desc->type == TRANSFER && ( desc->inputChannels == desc->outputChannels && ( desc->inputChannels == 1 || desc->inputChannels == 2 || desc->inputChannels == 4 )/* && isRealTimeCapable( key )*/ ) ) { m_validEffects.append( qMakePair( getName( key ), key ) ); } else if( desc->type == TRANSFER && ( desc->inputChannels != desc->outputChannels || ( desc->inputChannels != 1 && desc->inputChannels != 2 && desc->inputChannels != 4 ) || !isRealTimeCapable( key ) ) ) { m_invalidEffects.append( qMakePair( getName( key ), key ) ); } else if( desc->type == SINK ) { m_analysisTools.append( qMakePair( getName( key ), key ) ); } else if( desc->type == OTHER ) { m_otherPlugins.append( qMakePair( getName( key ), key ) ); } } } ladspa2LMMS::~ladspa2LMMS() { } QString ladspa2LMMS::getShortName( const ladspa_key_t & _key ) { QString name = getName( _key ); if( name.indexOf( "(" ) > 0 ) { name = name.left( name.indexOf( "(" ) ); } if( name.indexOf( " - " ) > 0 ) { name = name.left( name.indexOf( " - " ) ); } if( name.indexOf( " " ) > 0 ) { name = name.left( name.indexOf( " " ) ); } Qt::CaseSensitivity cs = Qt::CaseInsensitive; if( name.indexOf( " with ", 0, cs ) > 0 ) { name = name.left( name.indexOf( " with ", 0, cs ) ); } if( name.indexOf( ",", 0, cs ) > 0 ) { name = name.left( name.indexOf( ",", 0, cs ) ); } if( name.length() > 40 ) { int i = 40; while( name[i] != ' ' && i > 0 ) { i--; } name = name.left( i ); } if( name.length() == 0 ) { name = "LADSPA Plugin"; } return name; } lmms-1.0.0/src/core/VstSyncController.cpp0000644000175000017500000001060412313663627017106 0ustar tobytoby/* * VstSyncController.cpp - manage synchronization between LMMS and VST plugins * * Copyright (c) 2014 Tobias Doerffel * Copyright (c) 2013 Mike Choi * * This file is part of LMMS - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "config_mgr.h" #include "engine.h" #include "lmmsconfig.h" #include "Mixer.h" #include "VstSyncController.h" #ifdef LMMS_BUILD_WIN32 #ifndef USE_QT_SHMEM #define USE_QT_SHMEM #endif #endif #ifndef USE_QT_SHMEM #include #include #include #include #include #include #endif VstSyncController::VstSyncController() : m_syncData( NULL ), m_shmID( -1 ), m_shm( "/usr/bin/lmms" ) { if( configManager::inst()->value( "ui", "syncvstplugins" ).toInt() ) { connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) ); #ifdef USE_QT_SHMEM if ( m_shm.create( sizeof( VstSyncData ) ) ) { m_syncData = (VstSyncData*) m_shm.data(); } else { qWarning() << QString( "Failed to allocate shared memory for VST sync: %1" ).arg( m_shm.errorString() ); } #else key_t key; // make the key: if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 ) { qWarning( "VstSyncController: ftok() failed" ); } else { // connect to shared memory segment if( ( m_shmID = shmget( key, sizeof( VstSyncData ), 0644 | IPC_CREAT ) ) == -1 ) { qWarning( "VstSyncController: shmget() failed" ); } else { // attach segment m_syncData = (VstSyncData *)shmat( m_shmID, 0, 0 ); if( m_syncData == (VstSyncData *)( -1 ) ) { qWarning( "VstSyncController: shmat() failed" ); } } } #endif } else { qWarning( "VST sync support disabled in your configuration" ); } if( m_syncData == NULL ) { m_syncData = new VstSyncData; m_syncData->hasSHM = false; } else { m_syncData->hasSHM = true; } m_syncData->isPlaying = false; m_syncData->m_bufferSize = engine::mixer()->framesPerPeriod(); m_syncData->timeSigNumer = 4; m_syncData->timeSigDenom = 4; updateSampleRate(); } VstSyncController::~VstSyncController() { if( m_syncData->hasSHM == false ) { delete m_syncData; } else { #ifdef USE_QT_SHMEM if( m_shm.data() ) { // detach shared memory, delete it: m_shm.detach(); } #else if( shmdt( m_syncData ) != -1 ) { shmctl( m_shmID, IPC_RMID, NULL ); } else { qWarning( "VstSyncController: shmdt() failed" ); } #endif } } void VstSyncController::setAbsolutePosition( int ticks ) { #ifdef VST_SNC_LATENCY m_syncData->ppqPos = ( ( ticks + 0 ) / (float)48 ) - m_syncData->m_latency; #else m_syncData->ppqPos = ( ( ticks + 0 ) / (float)48 ); #endif } void VstSyncController::setTempo( int newTempo ) { m_syncData->m_bpm = newTempo; #ifdef VST_SNC_LATENCY m_syncData->m_latency = m_syncData->m_bufferSize * newTempo / ( (float) m_syncData->m_sampleRate * 60 ); #endif } void VstSyncController::startCycle( int startTick, int endTick ) { m_syncData->isCycle = true; m_syncData->cycleStart = startTick / (float)48; m_syncData->cycleEnd = endTick / (float)48; } void VstSyncController::update() { m_syncData->m_bufferSize = engine::mixer()->framesPerPeriod(); #ifdef VST_SNC_LATENCY m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 ); #endif } void VstSyncController::updateSampleRate() { m_syncData->m_sampleRate = engine::mixer()->processingSampleRate(); #ifdef VST_SNC_LATENCY m_syncData->m_latency = m_syncData->m_bufferSize * m_syncData->m_bpm / ( (float) m_syncData->m_sampleRate * 60 ); #endif } #include "moc_VstSyncController.cxx" lmms-1.0.0/src/core/LadspaControl.cpp0000644000175000017500000001544012313663627016201 0ustar tobytoby/* * LadspaControl.cpp - model for controlling a LADSPA port * * Copyright (c) 2008-2014 Tobias Doerffel * Copyright (c) 2006-2008 Danny McRae * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "LadspaControl.h" #include "LadspaBase.h" LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, bool _link ) : Model( _parent ), m_link( _link ), m_port( _port ), m_linkEnabledModel( _link, this, tr( "Link channels" ) ), m_toggledModel( false, this, m_port->name ), m_knobModel( 0, 0, 0, 1, this, m_port->name ), m_tempoSyncKnobModel( 0, 0, 0, 1, m_port->max, this, m_port->name ) { if( m_link ) { connect( &m_linkEnabledModel, SIGNAL( dataChanged() ), this, SLOT( linkStateChanged() ) ); } switch( m_port->data_type ) { case TOGGLED: connect( &m_toggledModel, SIGNAL( dataChanged() ), this, SLOT( ledChanged() ) ); if( m_port->def == 1.0f ) { m_toggledModel.setValue( true ); } break; case INTEGER: m_knobModel.setRange( static_cast( m_port->max ), static_cast( m_port->min ), 1 + static_cast( m_port->max - m_port->min ) / 400 ); m_knobModel.setInitValue( static_cast( m_port->def ) ); connect( &m_knobModel, SIGNAL( dataChanged() ), this, SLOT( knobChanged() ) ); break; case FLOATING: m_knobModel.setRange( m_port->min, m_port->max, ( m_port->max - m_port->min ) / ( m_port->name.toUpper() == "GAIN" && m_port->max == 10.0f ? 4000.0f : 400.0f ) ); m_knobModel.setInitValue( m_port->def ); connect( &m_knobModel, SIGNAL( dataChanged() ), this, SLOT( knobChanged() ) ); break; case TIME: m_tempoSyncKnobModel.setRange( m_port->min, m_port->max, ( m_port->max - m_port->min ) / 400.0f ); m_tempoSyncKnobModel.setInitValue( m_port->def ); connect( &m_tempoSyncKnobModel, SIGNAL( dataChanged() ), this, SLOT( tempoKnobChanged() ) ); break; default: break; } } LadspaControl::~LadspaControl() { } LADSPA_Data LadspaControl::value() { switch( m_port->data_type ) { case TOGGLED: return static_cast( m_toggledModel.value() ); case INTEGER: case FLOATING: return static_cast( m_knobModel.value() ); case TIME: return static_cast( m_tempoSyncKnobModel.value() ); default: qWarning( "LadspaControl::value(): BAD BAD BAD\n" ); break; } return 0; } void LadspaControl::setValue( LADSPA_Data _value ) { switch( m_port->data_type ) { case TOGGLED: m_toggledModel.setValue( static_cast( _value ) ); break; case INTEGER: m_knobModel.setValue( static_cast( _value ) ); break; case FLOATING: m_knobModel.setValue( static_cast( _value ) ); break; case TIME: m_tempoSyncKnobModel.setValue( static_cast( _value ) ); break; default: printf("LadspaControl::setValue BAD BAD BAD\n"); break; } } void LadspaControl::saveSettings( QDomDocument& doc, QDomElement& parent, const QString& name ) { QDomElement e = doc.createElement( name ); if( m_link ) { m_linkEnabledModel.saveSettings( doc, e, "link" ); } switch( m_port->data_type ) { case TOGGLED: m_toggledModel.saveSettings( doc, e, "data" ); break; case INTEGER: case FLOATING: m_knobModel.saveSettings( doc, e, "data" ); break; case TIME: m_tempoSyncKnobModel.saveSettings( doc, e, "data" ); break; default: printf("LadspaControl::saveSettings BAD BAD BAD\n"); break; } parent.appendChild( e ); } void LadspaControl::loadSettings( const QDomElement& parent, const QString& name ) { QString dataModelName = "data"; QString linkModelName = "link"; QDomElement e = parent.namedItem( name ).toElement(); // COMPAT < 1.0.0: detect old data format where there's either no dedicated sub // element or there's a direct sub element with automation link information if( e.isNull() || e.hasAttribute( "id" ) ) { dataModelName = name; linkModelName = name + "link"; e = parent; } if( m_link ) { m_linkEnabledModel.loadSettings( e, linkModelName ); } switch( m_port->data_type ) { case TOGGLED: m_toggledModel.loadSettings( e, dataModelName ); break; case INTEGER: case FLOATING: m_knobModel.loadSettings( e, dataModelName ); break; case TIME: m_tempoSyncKnobModel.loadSettings( e, dataModelName ); break; default: printf("LadspaControl::loadSettings BAD BAD BAD\n"); break; } } void LadspaControl::linkControls( LadspaControl * _control ) { switch( m_port->data_type ) { case TOGGLED: BoolModel::linkModels( &m_toggledModel, _control->toggledModel() ); break; case INTEGER: case FLOATING: FloatModel::linkModels( &m_knobModel, _control->knobModel() ); break; case TIME: TempoSyncKnobModel::linkModels( &m_tempoSyncKnobModel, _control->tempoSyncKnobModel() ); break; default: break; } } void LadspaControl::ledChanged() { emit changed( m_port->port_id, static_cast( m_toggledModel.value() ) ); } void LadspaControl::knobChanged() { emit changed( m_port->port_id, static_cast( m_knobModel.value() ) ); } void LadspaControl::tempoKnobChanged() { emit changed( m_port->port_id, static_cast( m_tempoSyncKnobModel.value() ) ); } void LadspaControl::unlinkControls( LadspaControl * _control ) { switch( m_port->data_type ) { case TOGGLED: BoolModel::unlinkModels( &m_toggledModel, _control->toggledModel() ); break; case INTEGER: case FLOATING: FloatModel::unlinkModels( &m_knobModel, _control->knobModel() ); break; case TIME: TempoSyncKnobModel::unlinkModels( &m_tempoSyncKnobModel, _control->tempoSyncKnobModel() ); break; default: break; } } void LadspaControl::linkStateChanged() { emit linkChanged( m_port->control_id, m_linkEnabledModel.value() ); } void LadspaControl::setLink( bool _state ) { m_linkEnabledModel.setValue( _state ); } #include "moc_LadspaControl.cxx" lmms-1.0.0/src/core/timeline.cpp0000644000175000017500000002270212313663627015241 0ustar tobytoby/* * timeline.cpp - class timeLine, representing a time-line with position marker * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "timeline.h" #include "embed.h" #include "engine.h" #include "templates.h" #include "nstate_button.h" #include "MainWindow.h" #include "text_float.h" QPixmap * timeLine::s_timeLinePixmap = NULL; QPixmap * timeLine::s_posMarkerPixmap = NULL; QPixmap * timeLine::s_loopPointPixmap = NULL; timeLine::timeLine( const int _xoff, const int _yoff, const float _ppt, song::playPos & _pos, const MidiTime & _begin, QWidget * _parent ) : QWidget( _parent ), m_autoScroll( AutoScrollEnabled ), m_loopPoints( LoopPointsDisabled ), m_behaviourAtStop( BackToZero ), m_changedPosition( true ), m_xOffset( _xoff ), m_posMarkerX( 0 ), m_ppt( _ppt ), m_pos( _pos ), m_begin( _begin ), m_savedPos( -1 ), m_hint( NULL ), m_action( NoAction ), m_moveXOff( 0 ) { m_loopPos[0] = 0; m_loopPos[1] = DefaultTicksPerTact; if( s_timeLinePixmap == NULL ) { s_timeLinePixmap = new QPixmap( embed::getIconPixmap( "timeline" ) ); } if( s_posMarkerPixmap == NULL ) { s_posMarkerPixmap = new QPixmap( embed::getIconPixmap( "playpos_marker" ) ); } if( s_loopPointPixmap == NULL ) { s_loopPointPixmap = new QPixmap( embed::getIconPixmap( "loop_point" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); move( 0, _yoff ); setFixedHeight( s_timeLinePixmap->height() ); m_xOffset -= s_posMarkerPixmap->width() / 2; m_pos.m_timeLine = this; QTimer * update_timer = new QTimer( this ); connect( update_timer, SIGNAL( timeout() ), this, SLOT( updatePosition() ) ); update_timer->start( 50 ); } timeLine::~timeLine() { if( engine::songEditor() ) { m_pos.m_timeLine = NULL; } delete m_hint; } void timeLine::addToolButtons( QWidget * _tool_bar ) { nStateButton * autoScroll = new nStateButton( _tool_bar ); autoScroll->setGeneralToolTip( tr( "Enable/disable auto-scrolling" ) ); autoScroll->addState( embed::getIconPixmap( "autoscroll_on" ) ); autoScroll->addState( embed::getIconPixmap( "autoscroll_off" ) ); connect( autoScroll, SIGNAL( changedState( int ) ), this, SLOT( toggleAutoScroll( int ) ) ); nStateButton * loopPoints = new nStateButton( _tool_bar ); loopPoints->setGeneralToolTip( tr( "Enable/disable loop-points" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_off" ) ); loopPoints->addState( embed::getIconPixmap( "loop_points_on" ) ); connect( loopPoints, SIGNAL( changedState( int ) ), this, SLOT( toggleLoopPoints( int ) ) ); connect( this, SIGNAL( loopPointStateLoaded( int ) ), loopPoints, SLOT( changeState( int ) ) ); nStateButton * behaviourAtStop = new nStateButton( _tool_bar ); behaviourAtStop->addState( embed::getIconPixmap( "back_to_zero" ), tr( "After stopping go back to begin" ) ); behaviourAtStop->addState( embed::getIconPixmap( "back_to_start" ), tr( "After stopping go back to " "position at which playing was " "started" ) ); behaviourAtStop->addState( embed::getIconPixmap( "keep_stop_position" ), tr( "After stopping keep position" ) ); connect( behaviourAtStop, SIGNAL( changedState( int ) ), this, SLOT( toggleBehaviourAtStop( int ) ) ); QBoxLayout * layout = dynamic_cast( _tool_bar->layout() ); layout->addWidget( autoScroll ); layout->addWidget( loopPoints ); layout->addWidget( behaviourAtStop ); } void timeLine::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "lp0pos", (int) loopBegin() ); _this.setAttribute( "lp1pos", (int) loopEnd() ); _this.setAttribute( "lpstate", m_loopPoints ); } void timeLine::loadSettings( const QDomElement & _this ) { m_loopPos[0] = _this.attribute( "lp0pos" ).toInt(); m_loopPos[1] = _this.attribute( "lp1pos" ).toInt(); m_loopPoints = static_cast( _this.attribute( "lpstate" ).toInt() ); update(); emit loopPointStateLoaded( m_loopPoints ); } void timeLine::updatePosition( const MidiTime & ) { const int new_x = markerX( m_pos ); if( new_x != m_posMarkerX ) { m_posMarkerX = new_x; m_changedPosition = true; emit positionChanged( m_pos ); update(); } } void timeLine::toggleAutoScroll( int _n ) { m_autoScroll = static_cast( _n ); } void timeLine::toggleLoopPoints( int _n ) { m_loopPoints = static_cast( _n ); update(); } void timeLine::toggleBehaviourAtStop( int _n ) { m_behaviourAtStop = static_cast( _n ); } void timeLine::paintEvent( QPaintEvent * ) { QPainter p( this ); QColor bg_color = QApplication::palette().color( QPalette::Active, QPalette::Background ); QLinearGradient g( 0, 0, 0, height() ); g.setColorAt( 0, bg_color.lighter( 150 ) ); g.setColorAt( 1, bg_color.darker( 150 ) ); p.fillRect( 0, 0, width(), height(), g ); p.setClipRect( m_xOffset, 0, width() - m_xOffset, height() ); p.setPen( QColor( 0, 0, 0 ) ); p.setOpacity( loopPointsEnabled() ? 0.9 : 0.2 ); p.drawPixmap( markerX( loopBegin() )+2, 2, *s_loopPointPixmap ); p.drawPixmap( markerX( loopEnd() )+2, 2, *s_loopPointPixmap ); p.setOpacity( 1.0 ); tact_t tact_num = m_begin.getTact(); int x = m_xOffset + s_posMarkerPixmap->width() / 2 - ( ( static_cast( m_begin * m_ppt ) / MidiTime::ticksPerTact() ) % static_cast( m_ppt ) ); p.setPen( QColor( 192, 192, 192 ) ); for( int i = 0; x + i * m_ppt < width(); ++i ) { const int cx = x + qRound( i * m_ppt ); p.drawLine( cx, 5, cx, height() - 6 ); ++tact_num; if( ( tact_num - 1 ) % qMax( 1, qRound( 1.0f / 3.0f * MidiTime::ticksPerTact() / m_ppt ) ) == 0 ) { const QString s = QString::number( tact_num ); p.drawText( cx + qRound( ( m_ppt - p.fontMetrics(). width( s ) ) / 2 ), height() - p.fontMetrics().height() / 2, s ); } } p.setOpacity( 0.6 ); p.drawPixmap( m_posMarkerX, height() - s_posMarkerPixmap->height(), *s_posMarkerPixmap ); } void timeLine::mousePressEvent( QMouseEvent* event ) { if( event->x() < m_xOffset ) { return; } if( event->button() == Qt::LeftButton ) { m_action = MovePositionMarker; if( event->x() - m_xOffset < s_posMarkerPixmap->width() ) { m_moveXOff = event->x() - m_xOffset; } else { m_moveXOff = s_posMarkerPixmap->width() / 2; } } else if( event->button() == Qt::RightButton || event->button() == Qt::MiddleButton ) { const MidiTime t = m_begin + static_cast( event->x() * MidiTime::ticksPerTact() / m_ppt ); if( m_loopPos[0] > m_loopPos[1] ) { qSwap( m_loopPos[0], m_loopPos[1] ); } if( ( event->modifiers() & Qt::ShiftModifier ) || event->button() == Qt::MiddleButton ) { m_action = MoveLoopBegin; } else { m_action = MoveLoopEnd; } m_loopPos[( m_action == MoveLoopBegin ) ? 0 : 1] = t; } if( m_action == MoveLoopBegin ) { delete m_hint; m_hint = textFloat::displayMessage( tr( "Hint" ), tr( "Press to disable magnetic loop points." ), embed::getIconPixmap( "hint" ), 0 ); } else if( m_action == MoveLoopEnd ) { delete m_hint; m_hint = textFloat::displayMessage( tr( "Hint" ), tr( "Hold to move the begin loop point; Press to disable magnetic loop points." ), embed::getIconPixmap( "hint" ), 0 ); } mouseMoveEvent( event ); } void timeLine::mouseMoveEvent( QMouseEvent* event ) { const MidiTime t = m_begin + static_cast( qMax( event->x() - m_xOffset - m_moveXOff, 0 ) * MidiTime::ticksPerTact() / m_ppt ); switch( m_action ) { case MovePositionMarker: m_pos.setTicks( t.getTicks() ); engine::getSong()->setMilliSeconds(((((t.getTicks()))*60*1000/48)/engine::getSong()->getTempo())); m_pos.setCurrentFrame( 0 ); updatePosition(); break; case MoveLoopBegin: case MoveLoopEnd: { const int i = m_action - MoveLoopBegin; if( event->modifiers() & Qt::ControlModifier ) { // no ctrl-press-hint when having ctrl pressed delete m_hint; m_hint = NULL; m_loopPos[i] = t; } else { m_loopPos[i] = t.toNearestTact(); } // Catch begin == end if( m_loopPos[0] == m_loopPos[1] ) { // Note, swap 1 and 0 below and the behavior "skips" the other // marking instead of pushing it. if( m_action == MoveLoopBegin ) m_loopPos[0] -= MidiTime::ticksPerTact(); else m_loopPos[1] += MidiTime::ticksPerTact(); } update(); break; } default: break; } } void timeLine::mouseReleaseEvent( QMouseEvent* event ) { delete m_hint; m_hint = NULL; m_action = NoAction; } #include "moc_timeline.cxx" lmms-1.0.0/src/core/config_mgr.cpp0000644000175000017500000002504712313663627015552 0ustar tobytoby/* * config_mgr.cpp - implementation of class configManager * * Copyright (c) 2005-2011 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include "lmmsversion.h" #include "config_mgr.h" #include "MainWindow.h" static inline QString ensureTrailingSlash( const QString & _s ) { if( _s.right( 1 ) != QDir::separator() ) { return( _s + QDir::separator() ); } return( _s ); } configManager * configManager::s_instanceOfMe = NULL; configManager::configManager() : m_lmmsRcFile( QDir::home().absolutePath() + QDir::separator() + ".lmmsrc.xml" ), m_workingDir( QDir::home().absolutePath() + QDir::separator() + "lmms" + QDir::separator() ), m_dataDir( qApp->applicationDirPath() #ifdef LMMS_BUILD_WIN32 + QDir::separator() + "data" + QDir::separator() #else .section( '/', 0, -2 ) + "/share/lmms/" #endif ), m_artworkDir( defaultArtworkDir() ), #ifdef LMMS_BUILD_WIN32 m_pluginDir( qApp->applicationDirPath() + QDir::separator() + "plugins" + QDir::separator() ), #else m_pluginDir( qApp->applicationDirPath() + '/' + PLUGIN_DIR ), #endif m_vstDir( m_workingDir + "vst" + QDir::separator() ), m_flDir( QDir::home().absolutePath() ) { } configManager::~configManager() { saveConfigFile(); } void configManager::setWorkingDir( const QString & _wd ) { m_workingDir = ensureTrailingSlash( _wd ); } void configManager::setVSTDir( const QString & _vd ) { m_vstDir = ensureTrailingSlash( _vd ); } void configManager::setArtworkDir( const QString & _ad ) { m_artworkDir = ensureTrailingSlash( _ad ); } void configManager::setFLDir( const QString & _fd ) { m_flDir = ensureTrailingSlash( _fd ); } void configManager::setLADSPADir( const QString & _fd ) { m_ladDir = _fd; } void configManager::setSTKDir( const QString & _fd ) { #ifdef LMMS_HAVE_STK m_stkDir = ensureTrailingSlash( _fd ); #endif } void configManager::setDefaultSoundfont( const QString & _sf ) { #ifdef LMMS_HAVE_FLUIDSYNTH m_defaultSoundfont = _sf; #endif } void configManager::setBackgroundArtwork( const QString & _ba ) { #ifdef LMMS_HAVE_FLUIDSYNTH m_backgroundArtwork = _ba; #endif } void configManager::addRecentlyOpenedProject( const QString & _file ) { m_recentlyOpenedProjects.removeAll( _file ); if( m_recentlyOpenedProjects.size() > 15 ) { m_recentlyOpenedProjects.removeLast(); } m_recentlyOpenedProjects.push_front( _file ); } const QString & configManager::value( const QString & _class, const QString & _attribute ) const { if( m_settings.contains( _class ) ) { for( stringPairVector::const_iterator it = m_settings[_class].begin(); it != m_settings[_class].end(); ++it ) { if( ( *it ).first == _attribute ) { return( ( *it ).second ); } } } static QString empty; return( empty ); } void configManager::setValue( const QString & _class, const QString & _attribute, const QString & _value ) { if( m_settings.contains( _class ) ) { for( stringPairVector::iterator it = m_settings[_class].begin(); it != m_settings[_class].end(); ++it ) { if( ( *it ).first == _attribute ) { ( *it ).second = _value; return; } } } // not in map yet, so we have to add it... m_settings[_class].push_back( qMakePair( _attribute, _value ) ); } #ifdef LMMS_BUILD_WIN32 #include #include // taken from qt-win-opensource-src-4.2.2/src/corelib/io/qsettings.cpp static QString windowsConfigPath( int _type ) { QString result; QLibrary library( "shell32" ); typedef BOOL( WINAPI* GetSpecialFolderPath )( HWND, char *, int, BOOL ); GetSpecialFolderPath SHGetSpecialFolderPath = (GetSpecialFolderPath) library.resolve( "SHGetSpecialFolderPathA" ); if( SHGetSpecialFolderPath ) { char path[MAX_PATH]; SHGetSpecialFolderPath( 0, path, _type, false ); result = QString::fromLocal8Bit( path ); } return result; } #endif void configManager::loadConfigFile() { // read the XML file and create DOM tree QFile cfg_file( m_lmmsRcFile ); QDomDocument dom_tree; if( cfg_file.open( QIODevice::ReadOnly ) ) { QString errorString; int errorLine, errorCol; if( dom_tree.setContent( &cfg_file, false, &errorString, &errorLine, &errorCol ) ) { // get the head information from the DOM QDomElement root = dom_tree.documentElement(); QDomNode node = root.firstChild(); // create the settings-map out of the DOM while( !node.isNull() ) { if( node.isElement() && node.toElement().hasAttributes () ) { stringPairVector attr; QDomNamedNodeMap node_attr = node.toElement().attributes(); for( int i = 0; i < node_attr.count(); ++i ) { QDomNode n = node_attr.item( i ); if( n.isAttr() ) { attr.push_back( qMakePair( n.toAttr().name(), n.toAttr().value() ) ); } } m_settings[node.nodeName()] = attr; } else if( node.nodeName() == "recentfiles" ) { m_recentlyOpenedProjects.clear(); QDomNode n = node.firstChild(); while( !n.isNull() ) { if( n.isElement() && n.toElement().hasAttributes() ) { m_recentlyOpenedProjects << n.toElement().attribute( "path" ); } n = n.nextSibling(); } } node = node.nextSibling(); } if( value( "paths", "artwork" ) != "" ) { m_artworkDir = value( "paths", "artwork" ); if( !QDir( m_artworkDir ).exists() ) { m_artworkDir = defaultArtworkDir(); } if( m_artworkDir.right( 1 ) != QDir::separator() ) { m_artworkDir += QDir::separator(); } } setWorkingDir( value( "paths", "workingdir" ) ); setVSTDir( value( "paths", "vstdir" ) ); setFLDir( value( "paths", "fldir" ) ); setLADSPADir( value( "paths", "laddir" ) ); #ifdef LMMS_HAVE_STK setSTKDir( value( "paths", "stkdir" ) ); #endif #ifdef LMMS_HAVE_FLUIDSYNTH setDefaultSoundfont( value( "paths", "defaultsf2" ) ); #endif setBackgroundArtwork( value( "paths", "backgroundartwork" ) ); } else { QMessageBox::warning( NULL, MainWindow::tr( "Configuration file" ), MainWindow::tr( "Error while parsing configuration file at line %1:%2: %3" ). arg( errorLine ). arg( errorCol ). arg( errorString ) ); } cfg_file.close(); } if( m_vstDir.isEmpty() || m_vstDir == QDir::separator() || !QDir( m_vstDir ).exists() ) { #ifdef LMMS_BUILD_WIN32 m_vstDir = windowsConfigPath( CSIDL_PROGRAM_FILES ) + QDir::separator() + "VstPlugins"; #else m_vstDir = ensureTrailingSlash( QDir::home().absolutePath() ); #endif } if( m_flDir.isEmpty() || m_flDir == QDir::separator() ) { m_flDir = ensureTrailingSlash( QDir::home().absolutePath() ); } if( m_ladDir.isEmpty() || m_ladDir == QDir::separator() || ( !m_ladDir.contains( ':' ) && !QDir( m_ladDir ).exists() ) ) { #ifdef LMMS_BUILD_WIN32 m_ladDir = m_pluginDir + "ladspa" + QDir::separator(); #else m_ladDir = qApp->applicationDirPath() + '/' + LIB_DIR + "/ladspa/"; #endif } #ifdef LMMS_HAVE_STK if( m_stkDir.isEmpty() || m_stkDir == QDir::separator() || !QDir( m_stkDir ).exists() ) { #ifdef LMMS_BUILD_WIN32 m_stkDir = m_dataDir + "stk/rawwaves/"; #else m_stkDir = "/usr/share/stk/rawwaves/"; #endif } #endif QDir::setSearchPaths( "resources", QStringList() << artworkDir() << defaultArtworkDir() ); if( !QDir( m_workingDir ).exists() ) { if( QMessageBox::question( 0, MainWindow::tr( "Working directory" ), MainWindow::tr( "The LMMS working directory %1 does not " "exist. Create it now? You can change the directory " "later via Edit -> Settings." ).arg( m_workingDir ), QMessageBox::Yes, QMessageBox::No ) == QMessageBox::Yes ) { QDir().mkpath( m_workingDir ); } } if( QDir( m_workingDir ).exists() ) { QDir().mkpath( userProjectsDir() ); QDir().mkpath( userSamplesDir() ); QDir().mkpath( userPresetsDir() ); } } void configManager::saveConfigFile() { setValue( "paths", "artwork", m_artworkDir ); setValue( "paths", "workingdir", m_workingDir ); setValue( "paths", "vstdir", m_vstDir ); setValue( "paths", "fldir", m_flDir ); setValue( "paths", "laddir", m_ladDir ); #ifdef LMMS_HAVE_STK setValue( "paths", "stkdir", m_stkDir ); #endif #ifdef LMMS_HAVE_FLUIDSYNTH setValue( "paths", "defaultsf2", m_defaultSoundfont ); #endif setValue( "paths", "backgroundartwork", m_backgroundArtwork ); QDomDocument doc( "lmms-config-file" ); QDomElement lmms_config = doc.createElement( "lmms" ); lmms_config.setAttribute( "version", LMMS_VERSION ); doc.appendChild( lmms_config ); for( settingsMap::iterator it = m_settings.begin(); it != m_settings.end(); ++it ) { QDomElement n = doc.createElement( it.key() ); for( stringPairVector::iterator it2 = ( *it ).begin(); it2 != ( *it ).end(); ++it2 ) { n.setAttribute( ( *it2 ).first, ( *it2 ).second ); } lmms_config.appendChild( n ); } QDomElement recent_files = doc.createElement( "recentfiles" ); for( QStringList::iterator it = m_recentlyOpenedProjects.begin(); it != m_recentlyOpenedProjects.end(); ++it ) { QDomElement n = doc.createElement( "file" ); n.setAttribute( "path", *it ); recent_files.appendChild( n ); } lmms_config.appendChild( recent_files ); QString xml = "\n" + doc.toString( 2 ); QFile outfile( m_lmmsRcFile ); if( !outfile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { QMessageBox::critical( NULL, MainWindow::tr( "Could not save config-file" ), MainWindow::tr( "Could not save configuration file %1. " "You're probably not permitted to " "write to this file.\n" "Please make sure you have write-" "access to the file and try again." ). arg( m_lmmsRcFile ) ); return; } outfile.write( xml.toUtf8() ); outfile.close(); } lmms-1.0.0/src/core/AutomationPattern.cpp0000644000175000017500000003722612313663627017120 0ustar tobytoby/* * AutomationPattern.cpp - implementation of class AutomationPattern which * holds dynamic values * * Copyright (c) 2008-2014 Tobias Doerffel * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "AutomationPattern.h" #include "AutomationPatternView.h" #include "AutomationEditor.h" #include "AutomationTrack.h" #include "ProjectJournal.h" #include "bb_track_container.h" #include "song.h" const float AutomationPattern::DEFAULT_MIN_VALUE = 0; const float AutomationPattern::DEFAULT_MAX_VALUE = 1; AutomationPattern::AutomationPattern( AutomationTrack * _auto_track ) : trackContentObject( _auto_track ), m_autoTrack( _auto_track ), m_objects(), m_tension( 1.0 ), m_progressionType( DiscreteProgression ), m_dragging( false ) { changeLength( MidiTime( 1, 0 ) ); } AutomationPattern::AutomationPattern( const AutomationPattern & _pat_to_copy ) : trackContentObject( _pat_to_copy.m_autoTrack ), m_autoTrack( _pat_to_copy.m_autoTrack ), m_objects( _pat_to_copy.m_objects ), m_tension( _pat_to_copy.m_tension ), m_progressionType( _pat_to_copy.m_progressionType ) { for( timeMap::const_iterator it = _pat_to_copy.m_timeMap.begin(); it != _pat_to_copy.m_timeMap.end(); ++it ) { m_timeMap[it.key()] = it.value(); m_tangents[it.key()] = _pat_to_copy.m_tangents[it.key()]; } } AutomationPattern::~AutomationPattern() { if( engine::automationEditor() && engine::automationEditor()->currentPattern() == this ) { engine::automationEditor()->setCurrentPattern( NULL ); } } void AutomationPattern::addObject( AutomatableModel * _obj, bool _search_dup ) { if( _search_dup ) { for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); ++it ) { if( *it == _obj ) { // Already exists // TODO: Maybe let the user know in some non-annoying way return; } } } // the automation track is unconnected and there is nothing in the track if( m_objects.isEmpty() && hasAutomation() == false ) { // then initialize first value putValue( MidiTime(0), _obj->value(), false ); } m_objects += _obj; connect( _obj, SIGNAL( destroyed( jo_id_t ) ), this, SLOT( objectDestroyed( jo_id_t ) ), Qt::DirectConnection ); emit dataChanged(); } void AutomationPattern::setProgressionType( ProgressionTypes _new_progression_type ) { if ( _new_progression_type == DiscreteProgression || _new_progression_type == LinearProgression || _new_progression_type == CubicHermiteProgression ) { m_progressionType = _new_progression_type; emit dataChanged(); } } void AutomationPattern::setTension( QString _new_tension ) { bool ok; float nt = _new_tension.toFloat( & ok ); if( ok && nt > -0.01 && nt < 1.01 ) { m_tension = _new_tension.toFloat(); } } const AutomatableModel * AutomationPattern::firstObject() const { AutomatableModel * m; if( !m_objects.isEmpty() && ( m = m_objects.first() ) != NULL ) { return m; } static FloatModel _fm( 0, DEFAULT_MIN_VALUE, DEFAULT_MAX_VALUE, 0.001 ); return &_fm; } MidiTime AutomationPattern::length() const { if( m_timeMap.isEmpty() ) return 0; timeMap::const_iterator it = m_timeMap.end(); return MidiTime( qMax( MidiTime( (it-1).key() ).getTact() + 1, 1 ), 0 ); } MidiTime AutomationPattern::putValue( const MidiTime & _time, const float _value, const bool _quant_pos ) { cleanObjects(); MidiTime newTime = _quant_pos && engine::automationEditor() ? note::quantized( _time, engine::automationEditor()->quantization() ) : _time; m_timeMap[newTime] = _value; timeMap::const_iterator it = m_timeMap.find( newTime ); if( it != m_timeMap.begin() ) { it--; } generateTangents(it, 3); // we need to maximize our length in case we're part of a hidden // automation track as the user can't resize this pattern if( getTrack() && getTrack()->type() == track::HiddenAutomationTrack ) { changeLength( length() ); } emit dataChanged(); return newTime; } void AutomationPattern::removeValue( const MidiTime & _time, const bool _quant_pos ) { cleanObjects(); MidiTime newTime = _quant_pos && engine::automationEditor() ? note::quantized( _time, engine::automationEditor()->quantization() ) : _time; m_timeMap.remove( newTime ); m_tangents.remove( newTime ); timeMap::const_iterator it = m_timeMap.lowerBound( newTime ); if( it != m_timeMap.begin() ) { it--; } generateTangents(it, 3); if( getTrack() && getTrack()->type() == track::HiddenAutomationTrack ) { changeLength( length() ); } emit dataChanged(); } /** * @brief Set the position of the point that is being draged. * Calling this function will also automatically set m_dragging to true, * which applyDragValue() have to be called to m_dragging. * @param the time(x position) of the point being dragged * @param the value(y position) of the point being dragged * @param true to snip x position * @return */ MidiTime AutomationPattern::setDragValue( const MidiTime & _time, const float _value, const bool _quant_pos ) { if( m_dragging == false ) { MidiTime newTime = _quant_pos && engine::automationEditor() ? note::quantized( _time, engine::automationEditor()->quantization() ) : _time; this->removeValue( newTime ); m_oldTimeMap = m_timeMap; m_dragging = true; } //Restore to the state before it the point were being dragged m_timeMap = m_oldTimeMap; for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); it++ ) { generateTangents(it, 3); } return this->putValue( _time, _value, _quant_pos ); } /** * @brief After the point is dragged, this function is called to apply the change. */ void AutomationPattern::applyDragValue() { m_dragging = false; } float AutomationPattern::valueAt( const MidiTime & _time ) const { if( m_timeMap.isEmpty() ) { return 0; } if( m_timeMap.contains( _time ) ) { return m_timeMap[_time]; } // lowerBound returns next value with greater key, therefore we take // the previous element to get the current value timeMap::ConstIterator v = m_timeMap.lowerBound( _time ); if( v == m_timeMap.begin() ) { return 0; } if( v == m_timeMap.end() ) { return (v-1).value(); } return valueAt( v-1, _time - (v-1).key() ); } float AutomationPattern::valueAt( timeMap::const_iterator v, int offset ) const { if( m_progressionType == DiscreteProgression || v == m_timeMap.end() ) { return v.value(); } else if( m_progressionType == LinearProgression ) { float slope = ((v+1).value() - v.value()) / ((v+1).key() - v.key()); return v.value() + offset * slope; } else /* CubicHermiteProgression */ { // Implements a Cubic Hermite spline as explained at: // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_.280.2C_1.29 // // Note that we are not interpolating a 2 dimensional point over // time as the article describes. We are interpolating a single // value: y. To make this work we map the values of x that this // segment spans to values of t for t = 0.0 -> 1.0 and scale the // tangents _m1 and _m2 int numValues = ((v+1).key() - v.key()); float t = (float) offset / (float) numValues; float m1 = (m_tangents[v.key()]) * numValues * m_tension; float m2 = (m_tangents[(v+1).key()]) * numValues * m_tension; return ( 2*pow(t,3) - 3*pow(t,2) + 1 ) * v.value() + ( pow(t,3) - 2*pow(t,2) + t) * m1 + ( -2*pow(t,3) + 3*pow(t,2) ) * (v+1).value() + ( pow(t,3) - pow(t,2) ) * m2; } } float *AutomationPattern::valuesAfter( const MidiTime & _time ) const { timeMap::ConstIterator v = m_timeMap.lowerBound( _time ); if( v == m_timeMap.end() || (v+1) == m_timeMap.end() ) { return NULL; } int numValues = (v+1).key() - v.key(); float *ret = new float[numValues]; for( int i = 0; i < numValues; i++ ) { ret[i] = valueAt( v, i ); } return ret; } void AutomationPattern::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "pos", startPosition() ); _this.setAttribute( "len", trackContentObject::length() ); _this.setAttribute( "name", name() ); _this.setAttribute( "prog", QString::number( progressionType() ) ); _this.setAttribute( "tens", QString::number( getTension() ) ); for( timeMap::const_iterator it = m_timeMap.begin(); it != m_timeMap.end(); ++it ) { QDomElement element = _doc.createElement( "time" ); element.setAttribute( "pos", it.key() ); element.setAttribute( "value", it.value() ); _this.appendChild( element ); } for( objectVector::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it ) { if( *it ) { QDomElement element = _doc.createElement( "object" ); element.setAttribute( "id", ( *it )->id() ); _this.appendChild( element ); } } } void AutomationPattern::loadSettings( const QDomElement & _this ) { clear(); movePosition( _this.attribute( "pos" ).toInt() ); setName( _this.attribute( "name" ) ); setProgressionType( static_cast( _this.attribute( "prog" ).toInt() ) ); setTension( _this.attribute( "tens" ) ); for( QDomNode node = _this.firstChild(); !node.isNull(); node = node.nextSibling() ) { QDomElement element = node.toElement(); if( element.isNull() ) { continue; } if( element.tagName() == "time" ) { m_timeMap[element.attribute( "pos" ).toInt()] = element.attribute( "value" ).toFloat(); } else if( element.tagName() == "object" ) { m_idsToResolve << element.attribute( "id" ).toInt(); } } int len = _this.attribute( "len" ).toInt(); if( len <= 0 ) { len = length(); } changeLength( len ); generateTangents(); } const QString AutomationPattern::name() const { if( !trackContentObject::name().isEmpty() ) { return trackContentObject::name(); } if( !m_objects.isEmpty() && m_objects.first() != NULL ) { return m_objects.first()->fullDisplayName(); } return tr( "Drag a control while pressing " ); } void AutomationPattern::processMidiTime( const MidiTime & _time ) { if( _time >= 0 && hasAutomation() ) { const float val = valueAt( _time ); for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); ++it ) { if( *it ) { ( *it )->setAutomatedValue( val ); } } } } trackContentObjectView * AutomationPattern::createView( trackView * _tv ) { return new AutomationPatternView( this, _tv ); } bool AutomationPattern::isAutomated( const AutomatableModel * _m ) { TrackContainer::TrackList l; l += engine::getSong()->tracks(); l += engine::getBBTrackContainer()->tracks(); l += engine::getSong()->globalAutomationTrack(); for( TrackContainer::TrackList::ConstIterator it = l.begin(); it != l.end(); ++it ) { if( ( *it )->type() == track::AutomationTrack || ( *it )->type() == track::HiddenAutomationTrack ) { const track::tcoVector & v = ( *it )->getTCOs(); for( track::tcoVector::ConstIterator j = v.begin(); j != v.end(); ++j ) { const AutomationPattern * a = dynamic_cast( *j ); if( a && a->hasAutomation() ) { for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k ) { if( *k == _m ) { return true; } } } } } } return false; } AutomationPattern * AutomationPattern::globalAutomationPattern( AutomatableModel * _m ) { AutomationTrack * t = engine::getSong()->globalAutomationTrack(); track::tcoVector v = t->getTCOs(); for( track::tcoVector::const_iterator j = v.begin(); j != v.end(); ++j ) { AutomationPattern * a = dynamic_cast( *j ); if( a ) { for( objectVector::const_iterator k = a->m_objects.begin(); k != a->m_objects.end(); ++k ) { if( *k == _m ) { return a; } } } } AutomationPattern * a = new AutomationPattern( t ); a->addObject( _m, false ); return a; } void AutomationPattern::resolveAllIDs() { TrackContainer::TrackList l = engine::getSong()->tracks() + engine::getBBTrackContainer()->tracks(); l += engine::getSong()->globalAutomationTrack(); for( TrackContainer::TrackList::iterator it = l.begin(); it != l.end(); ++it ) { if( ( *it )->type() == track::AutomationTrack || ( *it )->type() == track::HiddenAutomationTrack ) { track::tcoVector v = ( *it )->getTCOs(); for( track::tcoVector::iterator j = v.begin(); j != v.end(); ++j ) { AutomationPattern * a = dynamic_cast( *j ); if( a ) { for( QVector::Iterator k = a->m_idsToResolve.begin(); k != a->m_idsToResolve.end(); ++k ) { JournallingObject * o = engine::projectJournal()-> journallingObject( *k ); if( o && dynamic_cast( o ) ) { a->addObject( dynamic_cast( o ), false ); } } a->m_idsToResolve.clear(); a->dataChanged(); } } } } } void AutomationPattern::clear() { m_timeMap.clear(); m_tangents.clear(); emit dataChanged(); if( engine::automationEditor() && engine::automationEditor()->currentPattern() == this ) { engine::automationEditor()->update(); } } void AutomationPattern::openInAutomationEditor() { engine::automationEditor()->setCurrentPattern( this ); engine::automationEditor()->parentWidget()->show(); engine::automationEditor()->setFocus(); } void AutomationPattern::objectDestroyed( jo_id_t _id ) { // TODO: distict between temporary removal (e.g. LADSPA controls // when switching samplerate) and real deletions because in the latter // case we had to remove ourselves if we're the global automation // pattern of the destroyed object m_idsToResolve += _id; for( objectVector::Iterator objIt = m_objects.begin(); objIt != m_objects.end(); objIt++ ) { Q_ASSERT( !(*objIt).isNull() ); if( (*objIt)->id() == _id ) { //Assign to objIt so that this loop work even break; is removed. objIt = m_objects.erase( objIt ); break; } } emit dataChanged(); } void AutomationPattern::cleanObjects() { for( objectVector::iterator it = m_objects.begin(); it != m_objects.end(); ) { if( *it ) { ++it; } else { it = m_objects.erase( it ); } } } void AutomationPattern::generateTangents() { generateTangents(m_timeMap.begin(), m_timeMap.size()); } void AutomationPattern::generateTangents( timeMap::const_iterator it, int numToGenerate ) { if( m_timeMap.size() < 2 ) { m_tangents[it.key()] = 0; return; } for( int i = 0; i < numToGenerate; i++ ) { if( it == m_timeMap.begin() ) { m_tangents[it.key()] = ( (it+1).value() - (it).value() ) / ( (it+1).key() - (it).key() ); } else if( it+1 == m_timeMap.end() ) { m_tangents[it.key()] = 0; return; } else { m_tangents[it.key()] = ( (it+1).value() - (it-1).value() ) / ( (it+1).key() - (it-1).key() ); } it++; } } #include "moc_AutomationPattern.cxx" lmms-1.0.0/src/core/Model.cpp0000644000175000017500000000244612313663627014476 0ustar tobytoby/* * Model.cpp - implementation of Model base class * * Copyright (c) 2007-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "Model.h" QString Model::fullDisplayName() const { const QString & n = displayName(); if( parentModel() ) { const QString p = parentModel()->fullDisplayName(); if( n.isEmpty() && p.isEmpty() ) { return QString::null; } else if( p.isEmpty() ) { return n; } return p + ">" + n; } return n; } #include "moc_Model.cxx" lmms-1.0.0/src/core/JournallingObject.cpp0000644000175000017500000001216412313663627017047 0ustar tobytoby/* * JournallingObject.cpp - implementation of journalling-object related stuff * * Copyright (c) 2006-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "JournallingObject.h" #include "AutomatableModel.h" #include "ProjectJournal.h" #include "base64.h" #include "engine.h" JournallingObject::JournallingObject() : SerializingObject(), m_id( engine::projectJournal()->allocID( this ) ), m_journalEntries(), m_currentJournalEntry( m_journalEntries.end() ), m_journalling( true ), m_journallingStateStack() { } JournallingObject::~JournallingObject() { if( engine::projectJournal() ) { engine::projectJournal()->freeID( id() ); } } void JournallingObject::undo() { if( m_journalEntries.empty() == true ) { return; } if( m_currentJournalEntry - 1 >= m_journalEntries.begin() ) { undoStep( *--m_currentJournalEntry ); } } void JournallingObject::redo() { if( m_journalEntries.empty() == true ) { return; } if( m_currentJournalEntry < m_journalEntries.end() ) { redoStep( *m_currentJournalEntry++ ); } } QDomElement JournallingObject::saveState( QDomDocument & _doc, QDomElement & _parent ) { QDomElement _this = SerializingObject::saveState( _doc, _parent ); saveJournal( _doc, _this ); return _this; } void JournallingObject::restoreState( const QDomElement & _this ) { SerializingObject::restoreState( _this ); saveJournallingState( false ); // search for journal-node QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() && node.nodeName() == "journal" ) { loadJournal( node.toElement() ); } node = node.nextSibling(); } restoreJournallingState(); } void JournallingObject::addJournalEntry( const JournalEntry & _je ) { if( engine::projectJournal()->isJournalling() && isJournalling() ) { m_journalEntries.erase( m_currentJournalEntry, m_journalEntries.end() ); m_journalEntries.push_back( _je ); m_currentJournalEntry = m_journalEntries.end(); engine::projectJournal()->journalEntryAdded( id() ); } } void JournallingObject::changeID( jo_id_t _id ) { if( id() != _id ) { JournallingObject * jo = engine::projectJournal()-> journallingObject( _id ); if( jo != NULL ) { QString used_by = jo->nodeName(); if( used_by == "automatablemodel" && dynamic_cast( jo ) ) { used_by += ":" + dynamic_cast( jo )-> displayName(); } fprintf( stderr, "JO-ID %d already in use by %s!\n", (int) _id, used_by.toUtf8().constData() ); return; } engine::projectJournal()->forgetAboutID( id() ); engine::projectJournal()->reallocID( _id, this ); m_id = _id; } } void JournallingObject::saveJournal( QDomDocument & _doc, QDomElement & _parent ) { /* // avoid creating empty journal-nodes if( m_journalEntries.size() == 0 ) { return; }*/ QDomElement journal_de = _doc.createElement( "journal" ); journal_de.setAttribute( "id", id() ); journal_de.setAttribute( "entries", m_journalEntries.size() ); journal_de.setAttribute( "curentry", (int)( m_currentJournalEntry - m_journalEntries.begin() ) ); journal_de.setAttribute( "metadata", true ); for( JournalEntryVector::const_iterator it = m_journalEntries.begin(); it != m_journalEntries.end(); ++it ) { QDomElement je_de = _doc.createElement( "entry" ); je_de.setAttribute( "pos", (int)( it - m_journalEntries.begin() ) ); je_de.setAttribute( "actionid", it->actionID() ); je_de.setAttribute( "data", base64::encode( it->data() ) ); journal_de.appendChild( je_de ); } _parent.appendChild( journal_de ); } void JournallingObject::loadJournal( const QDomElement & _this ) { clear(); const jo_id_t new_id = _this.attribute( "id" ).toInt(); if( new_id == 0 ) { return; } changeID( new_id ); m_journalEntries.resize( _this.attribute( "entries" ).toInt() ); QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { const QDomElement & je = node.toElement(); m_journalEntries[je.attribute( "pos" ).toInt()] = JournalEntry( je.attribute( "actionid" ).toInt(), base64::decode( je.attribute( "data" ) ) ); } node = node.nextSibling(); } m_currentJournalEntry = m_journalEntries.begin() + _this.attribute( "curentry" ).toInt(); } lmms-1.0.0/src/core/TrackContainer.cpp0000644000175000017500000001064012313663627016340 0ustar tobytoby/* * TrackContainer.cpp - implementation of base class for all trackcontainers * like Song-Editor, BB-Editor... * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "TrackContainer.h" #include "InstrumentTrack.h" #include "engine.h" #include "MainWindow.h" #include "song.h" TrackContainer::TrackContainer() : Model( NULL ), JournallingObject(), m_tracksMutex(), m_tracks() { } TrackContainer::~TrackContainer() { clearAllTracks(); } void TrackContainer::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setTagName( classNodeName() ); _this.setAttribute( "type", nodeName() ); // save settings of each track m_tracksMutex.lockForRead(); for( int i = 0; i < m_tracks.size(); ++i ) { m_tracks[i]->saveState( _doc, _this ); } m_tracksMutex.unlock(); } void TrackContainer::loadSettings( const QDomElement & _this ) { static QProgressDialog * pd = NULL; bool was_null = ( pd == NULL ); int start_val = 0; if( engine::hasGUI() ) { if( pd == NULL ) { pd = new QProgressDialog( tr( "Loading project..." ), tr( "Cancel" ), 0, _this.childNodes().count(), engine::mainWindow() ); pd->setWindowModality( Qt::ApplicationModal ); pd->setWindowTitle( tr( "Please wait..." ) ); pd->show(); } else { start_val = pd->value(); pd->setMaximum( pd->maximum() + _this.childNodes().count() ); } } QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( pd != NULL ) { pd->setValue( pd->value() + 1 ); QCoreApplication::instance()->processEvents( QEventLoop::AllEvents, 100 ); if( pd->wasCanceled() ) { break; } } if( node.isElement() && !node.toElement().attribute( "metadata" ).toInt() ) { track::create( node.toElement(), this ); } node = node.nextSibling(); } if( pd != NULL ) { pd->setValue( start_val + _this.childNodes().count() ); if( was_null ) { delete pd; pd = NULL; } } } int TrackContainer::countTracks( track::TrackTypes _tt ) const { int cnt = 0; m_tracksMutex.lockForRead(); for( int i = 0; i < m_tracks.size(); ++i ) { if( m_tracks[i]->type() == _tt || _tt == track::NumTrackTypes ) { ++cnt; } } m_tracksMutex.unlock(); return( cnt ); } void TrackContainer::addTrack( track * _track ) { if( _track->type() != track::HiddenAutomationTrack ) { m_tracksMutex.lockForWrite(); m_tracks.push_back( _track ); m_tracksMutex.unlock(); emit trackAdded( _track ); } } void TrackContainer::removeTrack( track * _track ) { int index = m_tracks.indexOf( _track ); if( index != -1 ) { m_tracksMutex.lockForWrite(); m_tracks.remove( index ); m_tracksMutex.unlock(); if( engine::getSong() ) { engine::getSong()->setModified(); } } } void TrackContainer::updateAfterTrackAdd() { } void TrackContainer::clearAllTracks() { //m_tracksMutex.lockForWrite(); while( !m_tracks.isEmpty() ) { delete m_tracks.first(); } //m_tracksMutex.unlock(); } bool TrackContainer::isEmpty() const { for( TrackList::const_iterator it = m_tracks.begin(); it != m_tracks.end(); ++it ) { if( !( *it )->getTCOs().isEmpty() ) { return false; } } return true; } DummyTrackContainer::DummyTrackContainer() : TrackContainer(), m_dummyInstrumentTrack( NULL ) { setJournalling( false ); m_dummyInstrumentTrack = dynamic_cast( track::create( track::InstrumentTrack, this ) ); m_dummyInstrumentTrack->setJournalling( false ); } #include "moc_TrackContainer.cxx" lmms-1.0.0/src/core/SerializingObject.cpp0000644000175000017500000000377712313663627017055 0ustar tobytoby/* * SerializingObject.cpp - implementation of SerializingObject * * Copyright (c) 2008-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "SerializingObject.h" SerializingObject::SerializingObject() : m_hook( NULL ) { } SerializingObject::~SerializingObject() { if( m_hook ) { m_hook->m_hookedIn = NULL; } } QDomElement SerializingObject::saveState( QDomDocument& doc, QDomElement& parent ) { QDomElement element = doc.createElement( nodeName() ); parent.appendChild( element ); saveSettings( doc, element ); if( hook() ) { hook()->saveSettings( doc, element ); } return element; } void SerializingObject::restoreState( const QDomElement& element ) { loadSettings( element ); if( hook() ) { hook()->loadSettings( element ); } } void SerializingObject::setHook( SerializingObjectHook* hook ) { if( m_hook ) { m_hook->m_hookedIn = NULL; } m_hook = hook; if( m_hook ) { m_hook->m_hookedIn = this; } } void SerializingObject::saveSettings( QDomDocument& doc, QDomElement& element ) { Q_UNUSED(doc) Q_UNUSED(element) } void SerializingObject::loadSettings( const QDomElement& element ) { Q_UNUSED(element) } lmms-1.0.0/src/core/ToolPlugin.cpp0000644000175000017500000000313012313663627015521 0ustar tobytoby/* * ToolPlugin.cpp - base class for all tool plugins (graphs, extensions, etc) * * Copyright (c) 2006-2008 Javier Serrano Polo * Copyright (c) 2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "ToolPlugin.h" ToolPlugin::ToolPlugin( const Descriptor * _descriptor, Model * _parent ) : Plugin( _descriptor, _parent ) { } ToolPlugin::~ToolPlugin() { } ToolPlugin * ToolPlugin::instantiate( const QString & _plugin_name, Model * _parent ) { Plugin * p = Plugin::instantiate( _plugin_name, _parent, NULL ); // check whether instantiated plugin is a tool if( p->type() == Plugin::Tool ) { // everything ok, so return pointer return dynamic_cast( p ); } // not quite... so delete plugin delete p; return NULL; } lmms-1.0.0/src/core/track.cpp0000644000175000017500000017265112313663627014550 0ustar tobytoby/* * track.cpp - implementation of classes concerning tracks -> necessary for * all track-like objects (beat/bassline, sample-track...) * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ /** \file track.cpp * \brief All classes concerning tracks and track-like objects */ /* * \mainpage Track classes * * \section introduction Introduction * * \todo fill this out */ #include "track.h" #include #include #include #include #include #include #include #include "AutomationPattern.h" #include "AutomationTrack.h" #include "bb_editor.h" #include "bb_track.h" #include "bb_track_container.h" #include "config_mgr.h" #include "Clipboard.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "InstrumentTrack.h" #include "MainWindow.h" #include "DataFile.h" #include "pixmap_button.h" #include "ProjectJournal.h" #include "SampleTrack.h" #include "song.h" #include "string_pair_drag.h" #include "templates.h" #include "text_float.h" #include "tooltip.h" #include "TrackContainer.h" /*! The width of the resize grip in pixels */ const int RESIZE_GRIP_WIDTH = 4; /*! The size of the track buttons in pixels */ const int TRACK_OP_BTN_WIDTH = 20; const int TRACK_OP_BTN_HEIGHT = 14; /*! A pointer for that text bubble used when moving segments, etc. * * In a number of situations, LMMS displays a floating text bubble * beside the cursor as you move or resize elements of a track about. * This pointer keeps track of it, as you only ever need one at a time. */ textFloat * trackContentObjectView::s_textFloat = NULL; // =========================================================================== // trackContentObject // =========================================================================== /*! \brief Create a new trackContentObject * * Creates a new track content object for the given track. * * \param _track The track that will contain the new object */ trackContentObject::trackContentObject( track * _track ) : Model( _track ), m_track( _track ), m_name( QString::null ), m_startPosition(), m_length(), m_mutedModel( false, this, tr( "Muted" ) ) { if( getTrack() ) { getTrack()->addTCO( this ); } setJournalling( false ); movePosition( 0 ); changeLength( 0 ); setJournalling( true ); } /*! \brief Destroy a trackContentObject * * Destroys the given track content object. * */ trackContentObject::~trackContentObject() { emit destroyedTCO(); if( getTrack() ) { getTrack()->removeTCO( this ); } } /*! \brief Move this trackContentObject's position in time * * If the track content object has moved, update its position. We * also add a journal entry for undo and update the display. * * \param _pos The new position of the track content object. */ void trackContentObject::movePosition( const MidiTime & _pos ) { if( m_startPosition != _pos ) { addJournalEntry( JournalEntry( Move, m_startPosition - _pos ) ); m_startPosition = _pos; engine::getSong()->updateLength(); } emit positionChanged(); } /*! \brief Change the length of this trackContentObject * * If the track content object's length has chaanged, update it. We * also add a journal entry for undo and update the display. * * \param _length The new length of the track content object. */ void trackContentObject::changeLength( const MidiTime & _length ) { if( m_length != _length ) { addJournalEntry( JournalEntry( Resize, m_length - _length ) ); m_length = _length; engine::getSong()->updateLength(); } emit lengthChanged(); } /*! \brief Undo one journal entry of this trackContentObject * * Restore the previous state of this track content object. This will * restore the position or the length of the track content object * depending on what was changed. * * \param _je The journal entry to undo */ void trackContentObject::undoStep( JournalEntry & _je ) { saveJournallingState( false ); switch( _je.actionID() ) { case Move: movePosition( startPosition() + _je.data().toInt() ); break; case Resize: changeLength( length() + _je.data().toInt() ); break; } restoreJournallingState(); } /*! \brief Redo one journal entry of this trackContentObject * * Undoes one 'undo' of this track content object. * * \param _je The journal entry to redo */ void trackContentObject::redoStep( JournalEntry & _je ) { JournalEntry je( _je.actionID(), -_je.data().toInt() ); undoStep( je ); } /*! \brief Copy this trackContentObject to the clipboard. * * Copies this track content object to the clipboard. */ void trackContentObject::copy() { Clipboard::copy( this ); } /*! \brief Pastes this trackContentObject into a track. * * Pastes this track content object into a track. * * \param _je The journal entry to undo */ void trackContentObject::paste() { if( Clipboard::getContent( nodeName() ) != NULL ) { const MidiTime pos = startPosition(); restoreState( *( Clipboard::getContent( nodeName() ) ) ); movePosition( pos ); } } /*! \brief Mutes this trackContentObject * * Restore the previous state of this track content object. This will * restore the position or the length of the track content object * depending on what was changed. * * \param _je The journal entry to undo */ void trackContentObject::toggleMute() { m_mutedModel.setValue( !m_mutedModel.value() ); emit dataChanged(); } // =========================================================================== // trackContentObjectView // =========================================================================== /*! \brief Create a new trackContentObjectView * * Creates a new track content object view for the given * track content object in the given track view. * * \param _tco The track content object to be displayed * \param _tv The track view that will contain the new object */ trackContentObjectView::trackContentObjectView( trackContentObject * _tco, trackView * _tv ) : selectableObject( _tv->getTrackContentWidget() ), ModelView( NULL, this ), m_tco( _tco ), m_trackView( _tv ), m_action( NoAction ), m_autoResize( false ), m_initialMouseX( 0 ), m_hint( NULL ) { if( s_textFloat == NULL ) { s_textFloat = new textFloat; s_textFloat->setPixmap( embed::getIconPixmap( "clock" ) ); } setAttribute( Qt::WA_OpaquePaintEvent, true ); setAttribute( Qt::WA_DeleteOnClose, true ); setFocusPolicy( Qt::StrongFocus ); setCursor( QCursor( embed::getIconPixmap( "hand" ), 0, 0 ) ); move( 0, 1 ); show(); setFixedHeight( _tv->getTrackContentWidget()->height() - 2 ); setAcceptDrops( true ); setMouseTracking( true ); connect( m_tco, SIGNAL( lengthChanged() ), this, SLOT( updateLength() ) ); connect( m_tco, SIGNAL( positionChanged() ), this, SLOT( updatePosition() ) ); connect( m_tco, SIGNAL( destroyedTCO() ), this, SLOT( close() ) ); setModel( m_tco ); m_trackView->getTrackContentWidget()->addTCOView( this ); } /*! \brief Destroy a trackContentObjectView * * Destroys the given track content object view. * */ trackContentObjectView::~trackContentObjectView() { delete m_hint; // we have to give our track-container the focus because otherwise the // op-buttons of our track-widgets could become focus and when the user // presses space for playing song, just one of these buttons is pressed // which results in unwanted effects m_trackView->trackContainerView()->setFocus(); } /*! \brief Does this trackContentObjectView have a fixed TCO? * * Returns whether the containing trackView has fixed * TCOs. * * \todo What the hell is a TCO here - track content object? And in * what circumstance are they fixed? */ bool trackContentObjectView::fixedTCOs() { return m_trackView->trackContainerView()->fixedTCOs(); } /*! \brief Close a trackContentObjectView * * Closes a track content object view by asking the track * view to remove us and then asking the QWidget to close us. * * \return Boolean state of whether the QWidget was able to close. */ bool trackContentObjectView::close() { m_trackView->getTrackContentWidget()->removeTCOView( this ); return QWidget::close(); } /*! \brief Removes a trackContentObjectView from its track view. * * Like the close() method, this asks the track view to remove this * track content object view. However, the track content object is * scheduled for later deletion rather than closed immediately. * */ void trackContentObjectView::remove() { // delete ourself close(); m_tco->deleteLater(); } /*! \brief Cut this trackContentObjectView from its track to the clipboard. * * Perform the 'cut' action of the clipboard - copies the track content * object to the clipboard and then removes it from the track. */ void trackContentObjectView::cut() { m_tco->copy(); remove(); } /*! \brief Updates a trackContentObjectView's length * * If this track content object view has a fixed TCO, then we must * keep the width of our parent. Otherwise, calculate our width from * the track content object's length in pixels adding in the border. * */ void trackContentObjectView::updateLength() { if( fixedTCOs() ) { setFixedWidth( parentWidget()->width() ); } else { setFixedWidth( static_cast( m_tco->length() * pixelsPerTact() / MidiTime::ticksPerTact() ) + 1 /*+ TCO_BORDER_WIDTH * 2-1*/ ); } m_trackView->trackContainerView()->update(); } /*! \brief Updates a trackContentObjectView's position. * * Ask our track view to change our position. Then make sure that the * track view is updated in case this position has changed the track * view's length. * */ void trackContentObjectView::updatePosition() { m_trackView->getTrackContentWidget()->changePosition(); // moving a TCO can result in change of song-length etc., // therefore we update the track-container m_trackView->trackContainerView()->update(); } /*! \brief Change the trackContentObjectView's display when something * being dragged enters it. * * We need to notify Qt to change our display if something being * dragged has entered our 'airspace'. * * \param _dee The QDragEnterEvent to watch. */ void trackContentObjectView::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "tco_" + QString::number( m_tco->getTrack()->type() ) ); } /*! \brief Handle something being dropped on this trackContentObjectView. * * When something has been dropped on this trackContentObjectView, and * it's a track content object, then use an instance of our dataFile reader * to take the xml of the track content object and turn it into something * we can write over our current state. * * \param _de The QDropEvent to handle. */ void trackContentObjectView::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString value = stringPairDrag::decodeValue( _de ); if( type == ( "tco_" + QString::number( m_tco->getTrack()->type() ) ) ) { // value contains our XML-data so simply create a // DataFile which does the rest for us... DataFile dataFile( value.toUtf8() ); // at least save position before getting to moved to somewhere // the user doesn't expect... MidiTime pos = m_tco->startPosition(); m_tco->restoreState( dataFile.content().firstChild().toElement() ); m_tco->movePosition( pos ); AutomationPattern::resolveAllIDs(); _de->accept(); } } /*! \brief Handle a dragged selection leaving our 'airspace'. * * \param _e The QEvent to watch. */ void trackContentObjectView::leaveEvent( QEvent * _e ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } if( _e != NULL ) { QWidget::leaveEvent( _e ); } } /*! \brief Handle a mouse press on this trackContentObjectView. * * Handles the various ways in which a trackContentObjectView can be * used with a click of a mouse button. * * * If our container supports rubber band selection then handle * selection events. * * or if shift-left button, add this object to the selection * * or if ctrl-left button, start a drag-copy event * * or if just plain left button, resize if we're resizeable * * or if ctrl-middle button, mute the track content object * * or if middle button, maybe delete the track content object. * * \param _me The QMouseEvent to handle. */ void trackContentObjectView::mousePressEvent( QMouseEvent * _me ) { if( m_trackView->trackContainerView()->allowRubberband() == true && _me->button() == Qt::LeftButton ) { // if rubberband is active, we can be selected if( !m_trackView->trackContainerView()->rubberBandActive() ) { if( _me->modifiers() & Qt::ControlModifier ) { setSelected( !isSelected() ); } else if( isSelected() == true ) { m_action = MoveSelection; m_initialMouseX = _me->x(); } } else { selectableObject::mousePressEvent( _me ); } return; } else if( _me->modifiers() & Qt::ShiftModifier ) { // add/remove object to/from selection selectableObject::mousePressEvent( _me ); } else if( _me->button() == Qt::LeftButton && _me->modifiers() & Qt::ControlModifier ) { // start drag-action DataFile dataFile( DataFile::DragNDropData ); m_tco->saveState( dataFile, dataFile.content() ); QPixmap thumbnail = QPixmap::grabWidget( this ).scaled( 128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation ); new stringPairDrag( QString( "tco_%1" ).arg( m_tco->getTrack()->type() ), dataFile.toString(), thumbnail, this ); } else if( _me->button() == Qt::LeftButton && /* engine::mainWindow()->isShiftPressed() == false &&*/ fixedTCOs() == false ) { // move or resize m_tco->setJournalling( false ); m_initialMouseX = _me->x(); if( _me->x() < width() - RESIZE_GRIP_WIDTH ) { m_action = Move; m_oldTime = m_tco->startPosition(); QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); s_textFloat->setTitle( tr( "Current position" ) ); delete m_hint; m_hint = textFloat::displayMessage( tr( "Hint" ), tr( "Press and drag to make " "a copy." ), embed::getIconPixmap( "hint" ), 0 ); } else if( m_autoResize == false ) { m_action = Resize; m_oldTime = m_tco->length(); QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); s_textFloat->setTitle( tr( "Current length" ) ); delete m_hint; m_hint = textFloat::displayMessage( tr( "Hint" ), tr( "Press for free " "resizing." ), embed::getIconPixmap( "hint" ), 0 ); } // s_textFloat->reparent( this ); // setup text-float as if TCO was already moved/resized mouseMoveEvent( _me ); s_textFloat->show(); } else if( _me->button() == Qt::MidButton ) { if( _me->modifiers() & Qt::ControlModifier ) { m_tco->toggleMute(); } else if( fixedTCOs() == false ) { remove(); } } } /*! \brief Handle a mouse movement (drag) on this trackContentObjectView. * * Handles the various ways in which a trackContentObjectView can be * used with a mouse drag. * * * If in move mode, move ourselves in the track, * * or if in move-selection mode, move the entire selection, * * or if in resize mode, resize ourselves, * * otherwise ??? * * \param _me The QMouseEvent to handle. * \todo what does the final else case do here? */ void trackContentObjectView::mouseMoveEvent( QMouseEvent * _me ) { if( _me->modifiers() & Qt::ControlModifier ) { delete m_hint; m_hint = NULL; } const float ppt = m_trackView->trackContainerView()->pixelsPerTact(); if( m_action == Move ) { const int x = mapToParent( _me->pos() ).x() - m_initialMouseX; MidiTime t = qMax( 0, (int) m_trackView->trackContainerView()->currentPosition()+ static_cast( x * MidiTime::ticksPerTact() / ppt ) ); if( ! ( _me->modifiers() & Qt::ControlModifier ) && _me->button() == Qt::NoButton ) { t = t.toNearestTact(); } m_tco->movePosition( t ); m_trackView->getTrackContentWidget()->changePosition(); s_textFloat->setText( QString( "%1:%2" ). arg( m_tco->startPosition().getTact() + 1 ). arg( m_tco->startPosition().getTicks() % MidiTime::ticksPerTact() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2 ) ); } else if( m_action == MoveSelection ) { const int dx = _me->x() - m_initialMouseX; QVector so = m_trackView->trackContainerView()->selectedObjects(); QVector tcos; MidiTime smallest_pos, t; // find out smallest position of all selected objects for not // moving an object before zero for( QVector::iterator it = so.begin(); it != so.end(); ++it ) { trackContentObjectView * tcov = dynamic_cast( *it ); if( tcov == NULL ) { continue; } trackContentObject * tco = tcov->m_tco; tcos.push_back( tco ); smallest_pos = qMin( smallest_pos, (int)tco->startPosition() + static_cast( dx * MidiTime::ticksPerTact() / ppt ) ); } for( QVector::iterator it = tcos.begin(); it != tcos.end(); ++it ) { t = ( *it )->startPosition() + static_cast( dx *MidiTime::ticksPerTact() / ppt )-smallest_pos; if( ! ( _me->modifiers() & Qt::AltModifier ) && _me->button() == Qt::NoButton ) { t = t.toNearestTact(); } ( *it )->movePosition( t ); } } else if( m_action == Resize ) { MidiTime t = qMax( MidiTime::ticksPerTact() / 16, static_cast( _me->x() * MidiTime::ticksPerTact() / ppt ) ); if( ! ( _me->modifiers() & Qt::ControlModifier ) && _me->button() == Qt::NoButton ) { t = qMax( MidiTime::ticksPerTact(), t.toNearestTact() ); } m_tco->changeLength( t ); s_textFloat->setText( tr( "%1:%2 (%3:%4 to %5:%6)" ). arg( m_tco->length().getTact() ). arg( m_tco->length().getTicks() % MidiTime::ticksPerTact() ). arg( m_tco->startPosition().getTact() + 1 ). arg( m_tco->startPosition().getTicks() % MidiTime::ticksPerTact() ). arg( m_tco->endPosition().getTact() + 1 ). arg( m_tco->endPosition().getTicks() % MidiTime::ticksPerTact() ) ); s_textFloat->moveGlobal( this, QPoint( width() + 2, height() + 2) ); } else { if( _me->x() > width() - RESIZE_GRIP_WIDTH ) { if( QApplication::overrideCursor() != NULL && QApplication::overrideCursor()->shape() != Qt::SizeHorCursor ) { while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } } QCursor c( Qt::SizeHorCursor ); QApplication::setOverrideCursor( c ); } else { leaveEvent( NULL ); } } } /*! \brief Handle a mouse release on this trackContentObjectView. * * If we're in move or resize mode, journal the change as appropriate. * Then tidy up. * * \param _me The QMouseEvent to handle. */ void trackContentObjectView::mouseReleaseEvent( QMouseEvent * _me ) { if( m_action == Move || m_action == Resize ) { m_tco->setJournalling( true ); m_tco->addJournalEntry( JournalEntry( m_action, m_oldTime - ( ( m_action == Move ) ? m_tco->startPosition() : m_tco->length() ) ) ); } m_action = NoAction; delete m_hint; m_hint = NULL; s_textFloat->hide(); leaveEvent( NULL ); selectableObject::mouseReleaseEvent( _me ); } /*! \brief Set up the context menu for this trackContentObjectView. * * Set up the various context menu events that can apply to a * track content object view. * * \param _cme The QContextMenuEvent to add the actions to. */ void trackContentObjectView::contextMenuEvent( QContextMenuEvent * _cme ) { QMenu contextMenu( this ); if( fixedTCOs() == false ) { contextMenu.addAction( embed::getIconPixmap( "cancel" ), tr( "Delete (middle mousebutton)" ), this, SLOT( remove() ) ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), tr( "Cut" ), this, SLOT( cut() ) ); } contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), tr( "Copy" ), m_tco, SLOT( copy() ) ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), tr( "Paste" ), m_tco, SLOT( paste() ) ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "muted" ), tr( "Mute/unmute ( + middle click)" ), m_tco, SLOT( toggleMute() ) ); constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); } /*! \brief How many pixels a tact (bar) takes for this trackContentObjectView. * * \return the number of pixels per tact (bar). */ float trackContentObjectView::pixelsPerTact() { return m_trackView->trackContainerView()->pixelsPerTact(); } /*! \brief Set whether this trackContentObjectView can resize. * * \param _e The boolean state of whether this track content object view * is allowed to resize. */ void trackContentObjectView::setAutoResizeEnabled( bool _e ) { m_autoResize = _e; } // =========================================================================== // trackContentWidget // =========================================================================== /*! \brief Create a new trackContentWidget * * Creates a new track content widget for the given track. * The content widget comprises the 'grip bar' and the 'tools' button * for the track's context menu. * * \param _track The parent track. */ trackContentWidget::trackContentWidget( trackView * _parent ) : QWidget( _parent ), m_trackView( _parent ) { setAcceptDrops( true ); connect( _parent->trackContainerView(), SIGNAL( positionChanged( const MidiTime & ) ), this, SLOT( changePosition( const MidiTime & ) ) ); updateBackground(); } /*! \brief Destroy this trackContentWidget * * Destroys the trackContentWidget. */ trackContentWidget::~trackContentWidget() { } void trackContentWidget::updateBackground() { const int tactsPerBar = 4; const TrackContainerView * tcv = m_trackView->trackContainerView(); // Assume even-pixels-per-tact. Makes sense, should be like this anyways int ppt = static_cast( tcv->pixelsPerTact() ); int w = ppt * tactsPerBar; int h = height(); m_background = QPixmap( w * 2, height() ); QPainter pmp( &m_background ); QLinearGradient grad( 0,0, 0, h ); grad.setColorAt( 0.0, QColor( 50, 50, 50 ) ); grad.setColorAt( 0.33, QColor( 20, 20, 20 ) ); grad.setColorAt( 1.0, QColor( 15, 15, 15 ) ); pmp.fillRect( 0, 0, w, h, grad ); QLinearGradient grad2( 0,0, 0, h ); grad2.setColorAt( 0.0, QColor( 50, 50, 50 ) ); grad2.setColorAt( 0.33, QColor( 40, 40, 40 ) ); grad2.setColorAt( 1.0, QColor( 30, 30, 30 ) ); pmp.fillRect( w, 0, w , h, grad2 ); // draw lines pmp.setPen( QPen( QColor( 0, 0, 0, 160 ), 1 ) ); // horizontal line pmp.drawLine( 0, 0, w*2, 0 ); // vertical lines for( float x = 0; x < w * 2; x += ppt ) { pmp.drawLine( QLineF( x, 0.0, x, h ) ); } pmp.setPen( QPen( QColor( 140, 140, 140, 64 ), 1 ) ); for( float x = 1.0; x < w * 2; x += ppt ) { pmp.drawLine( QLineF( x, 0.0, x, h ) ); } pmp.end(); // Force redraw update(); } /*! \brief Adds a trackContentObjectView to this widget. * * Adds a(nother) trackContentObjectView to our list of views. We also * check that our position is up-to-date. * * \param _tcov The trackContentObjectView to add. */ void trackContentWidget::addTCOView( trackContentObjectView * _tcov ) { trackContentObject * tco = _tcov->getTrackContentObject(); /* QMap map; map["id"] = tco->id(); addJournalEntry( JournalEntry( AddTrackContentObject, map ) );*/ m_tcoViews.push_back( _tcov ); tco->saveJournallingState( false ); changePosition(); tco->restoreJournallingState(); } /*! \brief Removes the given trackContentObjectView to this widget. * * Removes the given trackContentObjectView from our list of views. * * \param _tcov The trackContentObjectView to add. */ void trackContentWidget::removeTCOView( trackContentObjectView * _tcov ) { tcoViewVector::iterator it = qFind( m_tcoViews.begin(), m_tcoViews.end(), _tcov ); if( it != m_tcoViews.end() ) { /* QMap map; DataFile dataFile( DataFile::JournalData ); _tcov->getTrackContentObject()->saveState( dataFile, dataFile.content() ); map["id"] = _tcov->getTrackContentObject()->id(); map["state"] = dataFile.toString(); addJournalEntry( JournalEntry( RemoveTrackContentObject, map ) );*/ m_tcoViews.erase( it ); engine::getSong()->setModified(); } } /*! \brief Update ourselves by updating all the tCOViews attached. * */ void trackContentWidget::update() { for( tcoViewVector::iterator it = m_tcoViews.begin(); it != m_tcoViews.end(); ++it ) { ( *it )->setFixedHeight( height() - 2 ); ( *it )->update(); } QWidget::update(); } // resposible for moving track-content-widgets to appropriate position after // change of visible viewport /*! \brief Move the trackContentWidget to a new place in time * * \param _new_pos The MIDI time to move to. */ void trackContentWidget::changePosition( const MidiTime & _new_pos ) { if( m_trackView->trackContainerView() == engine::getBBEditor() ) { const int cur_bb = engine::getBBTrackContainer()->currentBB(); setUpdatesEnabled( false ); // first show TCO for current BB... for( tcoViewVector::iterator it = m_tcoViews.begin(); it != m_tcoViews.end(); ++it ) { if( ( *it )->getTrackContentObject()-> startPosition().getTact() == cur_bb ) { ( *it )->move( 0, ( *it )->y() ); ( *it )->raise(); ( *it )->show(); } else { ( *it )->lower(); } } // ...then hide others to avoid flickering for( tcoViewVector::iterator it = m_tcoViews.begin(); it != m_tcoViews.end(); ++it ) { if( ( *it )->getTrackContentObject()-> startPosition().getTact() != cur_bb ) { ( *it )->hide(); } } setUpdatesEnabled( true ); return; } MidiTime pos = _new_pos; if( pos < 0 ) { pos = m_trackView->trackContainerView()->currentPosition(); } const int begin = pos; const int end = endPosition( pos ); const float ppt = m_trackView->trackContainerView()->pixelsPerTact(); setUpdatesEnabled( false ); for( tcoViewVector::iterator it = m_tcoViews.begin(); it != m_tcoViews.end(); ++it ) { trackContentObjectView * tcov = *it; trackContentObject * tco = tcov->getTrackContentObject(); tco->changeLength( tco->length() ); const int ts = tco->startPosition(); const int te = tco->endPosition()-3; if( ( ts >= begin && ts <= end ) || ( te >= begin && te <= end ) || ( ts <= begin && te >= end ) ) { tcov->move( static_cast( ( ts - begin ) * ppt / MidiTime::ticksPerTact() ), tcov->y() ); if( !tcov->isVisible() ) { tcov->show(); } } else { tcov->move( -tcov->width()-10, tcov->y() ); } } setUpdatesEnabled( true ); // redraw background // update(); } /*! \brief Respond to a drag enter event on the trackContentWidget * * \param _dee the Drag Enter Event to respond to */ void trackContentWidget::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "tco_" + QString::number( getTrack()->type() ) ); } /*! \brief Respond to a drop event on the trackContentWidget * * \param _de the Drop Event to respond to */ void trackContentWidget::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString value = stringPairDrag::decodeValue( _de ); if( type == ( "tco_" + QString::number( getTrack()->type() ) ) && m_trackView->trackContainerView()->fixedTCOs() == false ) { const MidiTime pos = getPosition( _de->pos().x() ).toNearestTact(); trackContentObject * tco = getTrack()->createTCO( pos ); // value contains our XML-data so simply create a // DataFile which does the rest for us... DataFile dataFile( value.toUtf8() ); // at least save position before getting moved to somewhere // the user doesn't expect... tco->restoreState( dataFile.content().firstChild().toElement() ); tco->movePosition( pos ); AutomationPattern::resolveAllIDs(); _de->accept(); } } /*! \brief Respond to a mouse press on the trackContentWidget * * \param _me the mouse press event to respond to */ void trackContentWidget::mousePressEvent( QMouseEvent * _me ) { if( m_trackView->trackContainerView()->allowRubberband() == true ) { QWidget::mousePressEvent( _me ); } else if( _me->modifiers() & Qt::ShiftModifier ) { QWidget::mousePressEvent( _me ); } else if( _me->button() == Qt::LeftButton && !m_trackView->trackContainerView()->fixedTCOs() ) { const MidiTime pos = getPosition( _me->x() ).getTact() * MidiTime::ticksPerTact(); trackContentObject * tco = getTrack()->createTCO( pos ); tco->saveJournallingState( false ); tco->movePosition( pos ); tco->restoreJournallingState(); } } /*! \brief Repaint the trackContentWidget on command * * \param _pe the Paint Event to respond to */ void trackContentWidget::paintEvent( QPaintEvent * _pe ) { // Assume even-pixels-per-tact. Makes sense, should be like this anyways const TrackContainerView * tcv = m_trackView->trackContainerView(); int ppt = static_cast( tcv->pixelsPerTact() ); QPainter p( this ); // Don't draw background on BB-Editor if( m_trackView->trackContainerView() != engine::getBBEditor() ) { p.drawTiledPixmap( rect(), m_background, QPoint( tcv->currentPosition().getTact() * ppt, 0 ) ); } } /*! \brief Updates the background tile pixmap on size changes. * * \param resizeEvent the resize event to pass to base class */ void trackContentWidget::resizeEvent( QResizeEvent * resizeEvent ) { // Update backgroud updateBackground(); // Force redraw QWidget::resizeEvent( resizeEvent ); } /*! \brief Undo an action on the trackContentWidget * * \param _je the details of the edit journal */ void trackContentWidget::undoStep( JournalEntry & _je ) { saveJournallingState( false ); switch( _je.actionID() ) { case AddTrackContentObject: { QMap map = _je.data().toMap(); trackContentObject * tco = dynamic_cast( engine::projectJournal()->journallingObject( map["id"].toInt() ) ); DataFile dataFile( DataFile::JournalData ); tco->saveState( dataFile, dataFile.content() ); map["state"] = dataFile.toString(); _je.data() = map; tco->deleteLater(); break; } case RemoveTrackContentObject: { trackContentObject * tco = getTrack()->createTCO( MidiTime( 0 ) ); DataFile dataFile( _je.data().toMap()["state"]. toString().toUtf8() ); tco->restoreState( dataFile.content().firstChild().toElement() ); break; } } restoreJournallingState(); } /*! \brief Redo an action of the trackContentWidget * * \param _je the entry in the edit journal to redo. */ void trackContentWidget::redoStep( JournalEntry & _je ) { switch( _je.actionID() ) { case AddTrackContentObject: case RemoveTrackContentObject: _je.actionID() = ( _je.actionID() == AddTrackContentObject ) ? RemoveTrackContentObject : AddTrackContentObject; undoStep( _je ); _je.actionID() = ( _je.actionID() == AddTrackContentObject ) ? RemoveTrackContentObject : AddTrackContentObject; break; } } /*! \brief Return the track shown by the trackContentWidget * */ track * trackContentWidget::getTrack() { return m_trackView->getTrack(); } /*! \brief Return the position of the trackContentWidget in Tacts. * * \param _mouse_x the mouse's current X position in pixels. */ MidiTime trackContentWidget::getPosition( int _mouse_x ) { return MidiTime( m_trackView->trackContainerView()-> currentPosition() + _mouse_x * MidiTime::ticksPerTact() / static_cast( m_trackView-> trackContainerView()->pixelsPerTact() ) ); } /*! \brief Return the end position of the trackContentWidget in Tacts. * * \param _pos_start the starting position of the Widget (from getPosition()) */ MidiTime trackContentWidget::endPosition( const MidiTime & _pos_start ) { const float ppt = m_trackView->trackContainerView()->pixelsPerTact(); const int w = width(); return _pos_start + static_cast( w * MidiTime::ticksPerTact() / ppt ); } // =========================================================================== // trackOperationsWidget // =========================================================================== QPixmap * trackOperationsWidget::s_grip = NULL; /*!< grip pixmap */ /*! \brief Create a new trackOperationsWidget * * The trackOperationsWidget is the grip and the mute button of a track. * * \param _parent the trackView to contain this widget */ trackOperationsWidget::trackOperationsWidget( trackView * _parent ) : QWidget( _parent ), /*!< The parent widget */ m_trackView( _parent ) /*!< The parent track view */ { if( s_grip == NULL ) { s_grip = new QPixmap( embed::getIconPixmap( "track_op_grip" ) ); } toolTip::add( this, tr( "Press while clicking on move-grip " "to begin a new drag'n'drop-action." ) ); QMenu * to_menu = new QMenu( this ); to_menu->setFont( pointSize<9>( to_menu->font() ) ); connect( to_menu, SIGNAL( aboutToShow() ), this, SLOT( updateMenu() ) ); setObjectName( "automationEnabled" ); m_trackOps = new QPushButton( this ); m_trackOps->move( 12, 1 ); m_trackOps->setFocusPolicy( Qt::NoFocus ); m_trackOps->setMenu( to_menu ); toolTip::add( m_trackOps, tr( "Actions for this track" ) ); m_muteBtn = new pixmapButton( this, tr( "Mute" ) ); m_muteBtn->setActiveGraphic( embed::getIconPixmap( "led_off" ) ); m_muteBtn->setInactiveGraphic( embed::getIconPixmap( "led_green" ) ); m_muteBtn->setCheckable( true ); m_soloBtn = new pixmapButton( this, tr( "Solo" ) ); m_soloBtn->setActiveGraphic( embed::getIconPixmap( "led_red" ) ); m_soloBtn->setInactiveGraphic( embed::getIconPixmap( "led_off" ) ); m_soloBtn->setCheckable( true ); if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { m_muteBtn->move( 46, 0 ); m_soloBtn->move( 46, 16 ); } else { m_muteBtn->move( 46, 8 ); m_soloBtn->move( 62, 8 ); } m_muteBtn->show(); toolTip::add( m_muteBtn, tr( "Mute this track" ) ); m_soloBtn->show(); toolTip::add( m_soloBtn, tr( "Solo" ) ); connect( this, SIGNAL( trackRemovalScheduled( trackView * ) ), m_trackView->trackContainerView(), SLOT( deleteTrackView( trackView * ) ), Qt::QueuedConnection ); } /*! \brief Destroy an existing trackOperationsWidget * */ trackOperationsWidget::~trackOperationsWidget() { } /*! \brief Respond to trackOperationsWidget mouse events * * If it's the left mouse button, and Ctrl is held down, and we're * not a Beat+Bassline Editor track, then start a new drag event to * copy this track. * * Otherwise, ignore all other events. * * \param _me The mouse event to respond to. */ void trackOperationsWidget::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && _me->modifiers() & Qt::ControlModifier && m_trackView->getTrack()->type() != track::BBTrack ) { DataFile dataFile( DataFile::DragNDropData ); m_trackView->getTrack()->saveState( dataFile, dataFile.content() ); new stringPairDrag( QString( "track_%1" ).arg( m_trackView->getTrack()->type() ), dataFile.toString(), QPixmap::grabWidget( m_trackView->getTrackSettingsWidget() ), this ); } else if( _me->button() == Qt::LeftButton ) { // track-widget (parent-widget) initiates track-move _me->ignore(); } } /*! \brief Repaint the trackOperationsWidget * * If we're not moving, and in the Beat+Bassline Editor, then turn * automation on or off depending on its previous state and show * ourselves. * * Otherwise, hide ourselves. * * \todo Flesh this out a bit - is it correct? * \param _pe The paint event to respond to */ void trackOperationsWidget::paintEvent( QPaintEvent * _pe ) { QPainter p( this ); p.fillRect( rect(), palette().brush(QPalette::Background) ); if( m_trackView->isMovingTrack() == false ) { p.drawPixmap( 2, 2, *s_grip ); m_trackOps->show(); m_muteBtn->show(); } else { m_trackOps->hide(); m_muteBtn->hide(); } } /*! \brief Clone this track * */ void trackOperationsWidget::cloneTrack() { engine::mixer()->lock(); m_trackView->getTrack()->clone(); engine::mixer()->unlock(); } /*! \brief Remove this track from the track list * */ void trackOperationsWidget::removeTrack() { emit trackRemovalScheduled( m_trackView ); } /*! \brief Update the trackOperationsWidget context menu * * For all track types, we have the Clone and Remove options. * For instrument-tracks we also offer the MIDI-control-menu */ void trackOperationsWidget::updateMenu() { QMenu * to_menu = m_trackOps->menu(); to_menu->clear(); to_menu->addAction( embed::getIconPixmap( "edit_copy", 16, 16 ), tr( "Clone this track" ), this, SLOT( cloneTrack() ) ); to_menu->addAction( embed::getIconPixmap( "cancel", 16, 16 ), tr( "Remove this track" ), this, SLOT( removeTrack() ) ); if( dynamic_cast( m_trackView ) ) { to_menu->addSeparator(); to_menu->addMenu( dynamic_cast( m_trackView )->midiMenu() ); } } // =========================================================================== // track // =========================================================================== /*! \brief Create a new (empty) track object * * The track object is the whole track, linking its contents, its * automation, name, type, and so forth. * * \param _type The type of track (Song Editor or Beat+Bassline Editor) * \param _tc The track Container object to encapsulate in this track. * * \todo check the definitions of all the properties - are they OK? */ track::track( TrackTypes _type, TrackContainer * _tc ) : Model( _tc ), /*!< The track Model */ m_trackContainer( _tc ), /*!< The track container object */ m_type( _type ), /*!< The track type */ m_name(), /*!< The track's name */ m_mutedModel( false, this, tr( "Muted" ) ), /*!< For controlling track muting */ m_soloModel( false, this, tr( "Solo" ) ), /*!< For controlling track soloing */ m_simpleSerializingMode( false ), m_trackContentObjects() /*!< The track content objects (segments) */ { m_trackContainer->addTrack( this ); m_height = -1; } /*! \brief Destroy this track * * If the track container is a Beat+Bassline container, step through * its list of tracks and remove us. * * Then delete the trackContentObject's contents, remove this track from * the track container. * * Finally step through this track's automation and forget all of them. */ track::~track() { emit destroyedTrack(); while( !m_trackContentObjects.isEmpty() ) { delete m_trackContentObjects.last(); } m_trackContainer->removeTrack( this ); } /*! \brief Create a track based on the given track type and container. * * \param _tt The type of track to create * \param _tc The track container to attach to */ track * track::create( TrackTypes _tt, TrackContainer * _tc ) { track * t = NULL; switch( _tt ) { case InstrumentTrack: t = new ::InstrumentTrack( _tc ); break; case BBTrack: t = new bbTrack( _tc ); break; case SampleTrack: t = new ::SampleTrack( _tc ); break; // case EVENT_TRACK: // case VIDEO_TRACK: case AutomationTrack: t = new ::AutomationTrack( _tc ); break; case HiddenAutomationTrack: t = new ::AutomationTrack( _tc, true ); break; default: break; } _tc->updateAfterTrackAdd(); return t; } /*! \brief Create a track inside TrackContainer from track type in a QDomElement and restore state from XML * * \param _this The QDomElement containing the type of track to create * \param _tc The track container to attach to */ track * track::create( const QDomElement & _this, TrackContainer * _tc ) { track * t = create( static_cast( _this.attribute( "type" ).toInt() ), _tc ); if( t != NULL ) { t->restoreState( _this ); } return t; } /*! \brief Clone a track from this track * */ void track::clone() { QDomDocument doc; QDomElement parent = doc.createElement( "clone" ); saveState( doc, parent ); create( parent.firstChild().toElement(), m_trackContainer ); } /*! \brief Save this track's settings to file * * We save the track type and its muted state and solo state, then append the track- * specific settings. Then we iterate through the trackContentObjects * and save all their states in turn. * * \param _doc The QDomDocument to use to save * \param _this The The QDomElement to save into * \todo Does this accurately describe the parameters? I think not!? * \todo Save the track height */ void track::saveSettings( QDomDocument & _doc, QDomElement & _this ) { if( !m_simpleSerializingMode ) { _this.setTagName( "track" ); } _this.setAttribute( "type", type() ); _this.setAttribute( "name", name() ); _this.setAttribute( "muted", isMuted() ); _this.setAttribute( "solo", isSolo() ); if( m_height >= MINIMAL_TRACK_HEIGHT ) { _this.setAttribute( "height", m_height ); } QDomElement ts_de = _doc.createElement( nodeName() ); // let actual track (InstrumentTrack, bbTrack, sampleTrack etc.) save // its settings _this.appendChild( ts_de ); saveTrackSpecificSettings( _doc, ts_de ); if( m_simpleSerializingMode ) { m_simpleSerializingMode = false; return; } // now save settings of all TCO's for( tcoVector::const_iterator it = m_trackContentObjects.begin(); it != m_trackContentObjects.end(); ++it ) { ( *it )->saveState( _doc, _this ); } } /*! \brief Load the settings from a file * * We load the track's type and muted state and solo state, then clear out our * current trackContentObject. * * Then we step through the QDomElement's children and load the * track-specific settings and trackContentObjects states from it * one at a time. * * \param _this the QDomElement to load track settings from * \todo Load the track height. */ void track::loadSettings( const QDomElement & _this ) { if( _this.attribute( "type" ).toInt() != type() ) { qWarning( "Current track-type does not match track-type of " "settings-node!\n" ); } setName( _this.hasAttribute( "name" ) ? _this.attribute( "name" ) : _this.firstChild().toElement().attribute( "name" ) ); setMuted( _this.attribute( "muted" ).toInt() ); setSolo( _this.attribute( "solo" ).toInt() ); if( m_simpleSerializingMode ) { QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() && node.nodeName() == nodeName() ) { loadTrackSpecificSettings( node.toElement() ); break; } node = node.nextSibling(); } m_simpleSerializingMode = false; return; } while( !m_trackContentObjects.empty() ) { delete m_trackContentObjects.front(); // m_trackContentObjects.erase( m_trackContentObjects.begin() ); } QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { if( node.nodeName() == nodeName() ) { loadTrackSpecificSettings( node.toElement() ); } else if( !node.toElement().attribute( "metadata" ).toInt() ) { trackContentObject * tco = createTCO( MidiTime( 0 ) ); tco->restoreState( node.toElement() ); saveJournallingState( false ); restoreJournallingState(); } } node = node.nextSibling(); } if( _this.attribute( "height" ).toInt() >= MINIMAL_TRACK_HEIGHT && _this.attribute( "height" ).toInt() <= DEFAULT_TRACK_HEIGHT ) // workaround for #3585927, tobydox/2012-11-11 { m_height = _this.attribute( "height" ).toInt(); } } /*! \brief Add another trackContentObject into this track * * \param _tco The trackContentObject to attach to this track. */ trackContentObject * track::addTCO( trackContentObject * _tco ) { m_trackContentObjects.push_back( _tco ); emit trackContentObjectAdded( _tco ); return _tco; // just for convenience } /*! \brief Remove a given trackContentObject from this track * * \param _tco The trackContentObject to remove from this track. */ void track::removeTCO( trackContentObject * _tco ) { tcoVector::iterator it = qFind( m_trackContentObjects.begin(), m_trackContentObjects.end(), _tco ); if( it != m_trackContentObjects.end() ) { m_trackContentObjects.erase( it ); if( engine::getSong() ) { engine::getSong()->updateLength(); engine::getSong()->setModified(); } } } /*! \brief Return the number of trackContentObjects we contain * * \return the number of trackContentObjects we currently contain. */ int track::numOfTCOs() { return m_trackContentObjects.size(); } /*! \brief Get a trackContentObject by number * * If the TCO number is less than our TCO array size then fetch that * numbered object from the array. Otherwise we warn the user that * we've somehow requested a TCO that is too large, and create a new * TCO for them. * \param _tco_number The number of the trackContentObject to fetch. * \return the given trackContentObject or a new one if out of range. * \todo reject TCO numbers less than zero. * \todo if we create a TCO here, should we somehow attach it to the * track? */ trackContentObject * track::getTCO( int _tco_num ) { if( _tco_num < m_trackContentObjects.size() ) { return m_trackContentObjects[_tco_num]; } printf( "called track::getTCO( %d ), " "but TCO %d doesn't exist\n", _tco_num, _tco_num ); return createTCO( _tco_num * MidiTime::ticksPerTact() ); } /*! \brief Determine the given trackContentObject's number in our array. * * \param _tco The trackContentObject to search for. * \return its number in our array. */ int track::getTCONum( trackContentObject * _tco ) { // for( int i = 0; i < getTrackContentWidget()->numOfTCOs(); ++i ) tcoVector::iterator it = qFind( m_trackContentObjects.begin(), m_trackContentObjects.end(), _tco ); if( it != m_trackContentObjects.end() ) { /* if( getTCO( i ) == _tco ) { return i; }*/ return it - m_trackContentObjects.begin(); } qWarning( "track::getTCONum(...) -> _tco not found!\n" ); return 0; } /*! \brief Retrieve a list of trackContentObjects that fall within a period. * * Here we're interested in a range of trackContentObjects that fall * completely within a given time period - their start must be no earlier * than the given start time and their end must be no later than the given * end time. * * We return the TCOs we find in order by time, earliest TCOs first. * * \param _tco_c The list to contain the found trackContentObjects. * \param _start The MIDI start time of the range. * \param _end The MIDI endi time of the range. */ void track::getTCOsInRange( tcoVector & _tco_v, const MidiTime & _start, const MidiTime & _end ) { for( tcoVector::iterator it_o = m_trackContentObjects.begin(); it_o != m_trackContentObjects.end(); ++it_o ) { trackContentObject * tco = ( *it_o ); int s = tco->startPosition(); int e = tco->endPosition(); if( ( s <= _end ) && ( e >= _start ) ) { // ok, TCO is posated within given range // now let's search according position for TCO in list // -> list is ordered by TCO's position afterwards bool inserted = false; for( tcoVector::iterator it = _tco_v.begin(); it != _tco_v.end(); ++it ) { if( ( *it )->startPosition() >= s ) { _tco_v.insert( it, tco ); inserted = true; break; } } if( inserted == false ) { // no TCOs found posated behind current TCO... _tco_v.push_back( tco ); } } } } /*! \brief Swap the position of two trackContentObjects. * * First, we arrange to swap the positions of the two TCOs in the * trackContentObjects list. Then we swap their start times as well. * * \param _tco_num1 The first trackContentObject to swap. * \param _tco_num2 The second trackContentObject to swap. */ void track::swapPositionOfTCOs( int _tco_num1, int _tco_num2 ) { qSwap( m_trackContentObjects[_tco_num1], m_trackContentObjects[_tco_num2] ); const MidiTime pos = m_trackContentObjects[_tco_num1]->startPosition(); m_trackContentObjects[_tco_num1]->movePosition( m_trackContentObjects[_tco_num2]->startPosition() ); m_trackContentObjects[_tco_num2]->movePosition( pos ); } /*! \brief Move all the trackContentObjects after a certain time later by one bar. * * \param _pos The time at which we want to insert the bar. * \todo if we stepped through this list last to first, and the list was * in ascending order by TCO time, once we hit a TCO that was earlier * than the insert time, we could fall out of the loop early. */ void track::insertTact( const MidiTime & _pos ) { // we'll increase the position of every TCO, positioned behind _pos, by // one tact for( tcoVector::iterator it = m_trackContentObjects.begin(); it != m_trackContentObjects.end(); ++it ) { if( ( *it )->startPosition() >= _pos ) { ( *it )->movePosition( (*it)->startPosition() + MidiTime::ticksPerTact() ); } } } /*! \brief Move all the trackContentObjects after a certain time earlier by one bar. * * \param _pos The time at which we want to remove the bar. */ void track::removeTact( const MidiTime & _pos ) { // we'll decrease the position of every TCO, positioned behind _pos, by // one tact for( tcoVector::iterator it = m_trackContentObjects.begin(); it != m_trackContentObjects.end(); ++it ) { if( ( *it )->startPosition() >= _pos ) { ( *it )->movePosition( qMax( ( *it )->startPosition() - MidiTime::ticksPerTact(), 0 ) ); } } } /*! \brief Return the length of the entire track in bars * * We step through our list of TCOs and determine their end position, * keeping track of the latest time found in ticks. Then we return * that in bars by dividing by the number of ticks per bar. */ tact_t track::length() const { // find last end-position tick_t last = 0; for( tcoVector::const_iterator it = m_trackContentObjects.begin(); it != m_trackContentObjects.end(); ++it ) { const tick_t cur = ( *it )->endPosition(); if( cur > last ) { last = cur; } } return last / MidiTime::ticksPerTact(); } /*! \brief Invert the track's solo state. * * We have to go through all the tracks determining if any other track * is already soloed. Then we have to save the mute state of all tracks, * and set our mute state to on and all the others to off. */ void track::toggleSolo() { const TrackContainer::TrackList & tl = m_trackContainer->tracks(); bool solo_before = false; for( TrackContainer::TrackList::const_iterator it = tl.begin(); it != tl.end(); ++it ) { if( *it != this ) { if( ( *it )->m_soloModel.value() ) { solo_before = true; break; } } } const bool solo = m_soloModel.value(); for( TrackContainer::TrackList::const_iterator it = tl.begin(); it != tl.end(); ++it ) { if( solo ) { // save mute-state in case no track was solo before if( !solo_before ) { ( *it )->m_mutedBeforeSolo = ( *it )->isMuted(); } ( *it )->setMuted( *it == this ? false : true ); if( *it != this ) { ( *it )->m_soloModel.setValue( false ); } } else if( !solo_before ) { ( *it )->setMuted( ( *it )->m_mutedBeforeSolo ); } } } // =========================================================================== // trackView // =========================================================================== /*! \brief Create a new track View. * * The track View is handles the actual display of the track, including * displaying its various widgets and the track segments. * * \param _track The track to display. * \param _tcv The track Container View for us to be displayed in. * \todo Is my description of these properties correct? */ trackView::trackView( track * _track, TrackContainerView * _tcv ) : QWidget( _tcv->contentWidget() ), /*!< The Track Container View's content widget. */ ModelView( NULL, this ), /*!< The model view of this track */ m_track( _track ), /*!< The track we're displaying */ m_trackContainerView( _tcv ), /*!< The track Container View we're displayed in */ m_trackOperationsWidget( this ), /*!< Our trackOperationsWidget */ m_trackSettingsWidget( this ), /*!< Our trackSettingsWidget */ m_trackContentWidget( this ), /*!< Our trackContentWidget */ m_action( NoAction ) /*!< The action we're currently performing */ { setAutoFillBackground( true ); QPalette pal; pal.setColor( backgroundRole(), QColor( 32, 36, 40 ) ); setPalette( pal ); m_trackSettingsWidget.setAutoFillBackground( true ); QHBoxLayout * layout = new QHBoxLayout( this ); layout->setMargin( 0 ); layout->setSpacing( 0 ); layout->addWidget( &m_trackOperationsWidget ); layout->addWidget( &m_trackSettingsWidget ); layout->addWidget( &m_trackContentWidget, 1 ); setFixedHeight( m_track->getHeight() ); resizeEvent( NULL ); setAcceptDrops( true ); setAttribute( Qt::WA_DeleteOnClose, true ); connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); connect( m_track, SIGNAL( trackContentObjectAdded( trackContentObject * ) ), this, SLOT( createTCOView( trackContentObject * ) ), Qt::QueuedConnection ); connect( &m_track->m_mutedModel, SIGNAL( dataChanged() ), &m_trackContentWidget, SLOT( update() ) ); connect( &m_track->m_soloModel, SIGNAL( dataChanged() ), m_track, SLOT( toggleSolo() ) ); // create views for already existing TCOs for( track::tcoVector::iterator it = m_track->m_trackContentObjects.begin(); it != m_track->m_trackContentObjects.end(); ++it ) { createTCOView( *it ); } m_trackContainerView->addTrackView( this ); } /*! \brief Destroy this track View. * */ trackView::~trackView() { } /*! \brief Resize this track View. * * \param _re the Resize Event to handle. */ void trackView::resizeEvent( QResizeEvent * _re ) { if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH_COMPACT, height() - 1 ); m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT, height() - 1 ); } else { m_trackOperationsWidget.setFixedSize( TRACK_OP_WIDTH, height() - 1 ); m_trackSettingsWidget.setFixedSize( DEFAULT_SETTINGS_WIDGET_WIDTH, height() - 1 ); } m_trackContentWidget.setFixedHeight( height() ); } /*! \brief Update this track View and all its content objects. * */ void trackView::update() { m_trackContentWidget.update(); if( !m_trackContainerView->fixedTCOs() ) { m_trackContentWidget.changePosition(); } QWidget::update(); } /*! \brief Close this track View. * */ bool trackView::close() { m_trackContainerView->removeTrackView( this ); return QWidget::close(); } /*! \brief Register that the model of this track View has changed. * */ void trackView::modelChanged() { m_track = castModel(); assert( m_track != NULL ); connect( m_track, SIGNAL( destroyedTrack() ), this, SLOT( close() ) ); m_trackOperationsWidget.m_muteBtn->setModel( &m_track->m_mutedModel ); m_trackOperationsWidget.m_soloBtn->setModel( &m_track->m_soloModel ); ModelView::modelChanged(); setFixedHeight( m_track->getHeight() ); } /*! \brief Undo a change to this track View. * * \param _je the Journal Entry to undo. */ void trackView::undoStep( JournalEntry & _je ) { saveJournallingState( false ); switch( _je.actionID() ) { case MoveTrack: if( _je.data().toInt() > 0 ) { m_trackContainerView->moveTrackViewUp( this ); } else { m_trackContainerView->moveTrackViewDown( this ); } break; case ResizeTrack: setFixedHeight( qMax( height() + _je.data().toInt(), MINIMAL_TRACK_HEIGHT ) ); m_trackContainerView->realignTracks(); break; /*case RestoreTrack: setFixedHeight( DEFAULT_TRACK_HEIGHT ); m_trackContainerView->realignTracks(); break; */ } restoreJournallingState(); } /*! \brief Redo a change to this track View. * * \param _je the Journal Event to redo. */ void trackView::redoStep( JournalEntry & _je ) { JournalEntry je( _je.actionID(), -_je.data().toInt() ); undoStep( je ); } /*! \brief Start a drag event on this track View. * * \param _dee the DragEnterEvent to start. */ void trackView::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "track_" + QString::number( m_track->type() ) ); } /*! \brief Accept a drop event on this track View. * * We only accept drop events that are of the same type as this track. * If so, we decode the data from the drop event by just feeding it * back into the engine as a state. * * \param _de the DropEvent to handle. */ void trackView::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString value = stringPairDrag::decodeValue( _de ); if( type == ( "track_" + QString::number( m_track->type() ) ) ) { // value contains our XML-data so simply create a // DataFile which does the rest for us... DataFile dataFile( value.toUtf8() ); engine::mixer()->lock(); m_track->restoreState( dataFile.content().firstChild().toElement() ); engine::mixer()->unlock(); _de->accept(); } } /*! \brief Handle a mouse press event on this track View. * * If this track container supports rubber band selection, let the * widget handle that and don't bother with any other handling. * * If the left mouse button is pressed, we handle two things. If * SHIFT is pressed, then we resize vertically. Otherwise we start * the process of moving this track to a new position. * * Otherwise we let the widget handle the mouse event as normal. * * \param _me the MouseEvent to handle. */ void trackView::mousePressEvent( QMouseEvent * _me ) { // If previously dragged too small, restore on shift-leftclick if( height() < DEFAULT_TRACK_HEIGHT && _me->modifiers() & Qt::ShiftModifier && _me->button() == Qt::LeftButton ) { setFixedHeight( DEFAULT_TRACK_HEIGHT ); m_track->setHeight( DEFAULT_TRACK_HEIGHT ); } if( m_trackContainerView->allowRubberband() == true ) { QWidget::mousePressEvent( _me ); } else if( _me->button() == Qt::LeftButton ) { if( _me->modifiers() & Qt::ShiftModifier ) { m_action = ResizeTrack; QCursor::setPos( mapToGlobal( QPoint( _me->x(), height() ) ) ); QCursor c( Qt::SizeVerCursor); QApplication::setOverrideCursor( c ); } else { m_action = MoveTrack; QCursor c( Qt::SizeAllCursor ); QApplication::setOverrideCursor( c ); // update because in move-mode, all elements in // track-op-widgets are hidden as a visual feedback m_trackOperationsWidget.update(); } _me->accept(); } else { QWidget::mousePressEvent( _me ); } } /*! \brief Handle a mouse move event on this track View. * * If this track container supports rubber band selection, let the * widget handle that and don't bother with any other handling. * * Otherwise if we've started the move process (from mousePressEvent()) * then move ourselves into that position, reordering the track list * with moveTrackViewUp() and moveTrackViewDown() to suit. We make a * note of this in the undo journal in case the user wants to undo this * move. * * Likewise if we've started a resize process, handle this too, making * sure that we never go below the minimum track height. * * \param _me the MouseEvent to handle. */ void trackView::mouseMoveEvent( QMouseEvent * _me ) { if( m_trackContainerView->allowRubberband() == true ) { QWidget::mouseMoveEvent( _me ); } else if( m_action == MoveTrack ) { // look which track-widget the mouse-cursor is over const int y_pos = m_trackContainerView->contentWidget()->mapFromGlobal( _me->globalPos() ).y(); const trackView * track_at_y = m_trackContainerView->trackViewAt( y_pos ); // debug code // qDebug( "y position %d", y_pos ); // a track-widget not equal to ourself? if( track_at_y != NULL && track_at_y != this ) { // then move us up/down there! if( _me->y() < 0 ) { m_trackContainerView->moveTrackViewUp( this ); } else { m_trackContainerView->moveTrackViewDown( this ); } addJournalEntry( JournalEntry( MoveTrack, _me->y() ) ); } } else if( m_action == ResizeTrack ) { setFixedHeight( qMax( _me->y(), MINIMAL_TRACK_HEIGHT ) ); m_trackContainerView->realignTracks(); m_track->setHeight( height() ); } if( height() < DEFAULT_TRACK_HEIGHT ) { toolTip::add( this, m_track->m_name ); } } /*! \brief Handle a mouse release event on this track View. * * \param _me the MouseEvent to handle. */ void trackView::mouseReleaseEvent( QMouseEvent * _me ) { m_action = NoAction; while( QApplication::overrideCursor() != NULL ) { QApplication::restoreOverrideCursor(); } m_trackOperationsWidget.update(); QWidget::mouseReleaseEvent( _me ); } /*! \brief Repaint this track View. * * \param _pe the PaintEvent to start. */ void trackView::paintEvent( QPaintEvent * _pe ) { QStyleOption opt; opt.initFrom( this ); QPainter p( this ); style()->drawPrimitive( QStyle::PE_Widget, &opt, &p, this ); } /*! \brief Create a trackContentObject View in this track View. * * \param _tco the trackContentObject to create the view for. * \todo is this a good description for what this method does? */ void trackView::createTCOView( trackContentObject * _tco ) { _tco->createView( this ); } #include "moc_track.cxx" lmms-1.0.0/src/core/PeakController.cpp0000644000175000017500000001222012313663627016351 0ustar tobytoby/* * PeakController.cpp - implementation of class controller which handles * remote-control of AutomatableModels * * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "song.h" #include "engine.h" #include "Mixer.h" #include "PeakController.h" #include "EffectChain.h" #include "ControllerDialog.h" #include "plugins/peak_controller_effect/peak_controller_effect.h" #include "PresetPreviewPlayHandle.h" PeakControllerEffectVector PeakController::s_effects; int PeakController::m_getCount; int PeakController::m_loadCount; bool PeakController::m_buggedFile; PeakController::PeakController( Model * _parent, PeakControllerEffect * _peak_effect ) : Controller( Controller::PeakController, _parent, tr( "Peak Controller" ) ), m_peakEffect( _peak_effect ) { if( m_peakEffect ) { connect( m_peakEffect, SIGNAL( destroyed( ) ), this, SLOT( handleDestroyedEffect( ) ) ); } } PeakController::~PeakController() { //EffectChain::loadSettings() appends effect to EffectChain::m_effects //When it's previewing, EffectChain::loadSettings() is not called //Therefore, we shouldn't call removeEffect() as it is not even appended. //NB: Most XML setting are loaded on preview, except controller fx. if( m_peakEffect != NULL && m_peakEffect->effectChain() != NULL && PresetPreviewPlayHandle::isPreviewing() == false ) { m_peakEffect->effectChain()->removeEffect( m_peakEffect ); } } float PeakController::value( int _offset ) { if( m_peakEffect ) { return m_peakEffect->lastSample(); } return( 0 ); } void PeakController::handleDestroyedEffect( ) { // possible race condition... //printf("disconnecting effect\n"); disconnect( m_peakEffect ); m_peakEffect = NULL; //deleteLater(); delete this; } void PeakController::saveSettings( QDomDocument & _doc, QDomElement & _this ) { if( m_peakEffect ) { Controller::saveSettings( _doc, _this ); _this.setAttribute( "effectId", m_peakEffect->m_effectId ); } } void PeakController::loadSettings( const QDomElement & _this ) { Controller::loadSettings( _this ); int effectId = _this.attribute( "effectId" ).toInt(); if( m_buggedFile == true ) { effectId = m_loadCount++; } PeakControllerEffectVector::Iterator i; for( i = s_effects.begin(); i != s_effects.end(); ++i ) { if( (*i)->m_effectId == effectId ) { m_peakEffect = *i; return; } } } //Backward compatibility function for bug in <= 0.4.15 void PeakController::initGetControllerBySetting() { m_loadCount = 0; m_getCount = 0; m_buggedFile = false; } PeakController * PeakController::getControllerBySetting(const QDomElement & _this ) { int effectId = _this.attribute( "effectId" ).toInt(); PeakControllerEffectVector::Iterator i; //Backward compatibility for bug in <= 0.4.15 . For >= 1.0.0 , //foundCount should always be 1 because m_effectId is initialized with rand() int foundCount = 0; if( m_buggedFile == false ) { for( i = s_effects.begin(); i != s_effects.end(); ++i ) { if( (*i)->m_effectId == effectId ) { foundCount++; } } if( foundCount >= 2 ) { m_buggedFile = true; int newEffectId = 0; for( i = s_effects.begin(); i != s_effects.end(); ++i ) { (*i)->m_effectId = newEffectId++; } QMessageBox msgBox; msgBox.setIcon( QMessageBox::Information ); msgBox.setWindowTitle( tr("Peak Controller Bug") ); msgBox.setText( tr("Due to a bug in older version of LMMS, the peak " "controllers may not be connect properly. " "Please ensure that peak controllers are connected " "properly and re-save this file. " "Sorry for any inconvenience caused.") ); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.exec(); } } if( m_buggedFile == true ) { effectId = m_getCount; } m_getCount++; //NB: m_getCount should be increased even m_buggedFile is false for( i = s_effects.begin(); i != s_effects.end(); ++i ) { if( (*i)->m_effectId == effectId ) { return (*i)->controller(); } } return NULL; } QString PeakController::nodeName() const { return( "Peakcontroller" ); } ControllerDialog * PeakController::createDialog( QWidget * _parent ) { return new PeakControllerDialog( this, _parent ); } #include "moc_PeakController.cxx" lmms-1.0.0/src/core/midi/0000755000175000017500000000000012313663627013646 5ustar tobytobylmms-1.0.0/src/core/midi/MidiOss.cpp0000644000175000017500000000512412313663627015723 0ustar tobytoby/* * MidiOss.cpp - OSS raw MIDI client * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "MidiOss.h" #ifdef LMMS_HAVE_OSS #include #include #ifdef LMMS_HAVE_STDLIB_H #include #endif #include "config_mgr.h" #include "gui_templates.h" MidiOss::MidiOss() : MidiClientRaw(), m_midiDev( probeDevice() ), m_quit( false ) { // only start thread, if opening of MIDI-device is successful, // otherwise isRunning()==false indicates error if( m_midiDev.open( QIODevice::ReadWrite ) || m_midiDev.open( QIODevice::ReadOnly ) ) { start( QThread::LowPriority ); } } MidiOss::~MidiOss() { if( isRunning() ) { m_quit = true; wait( 1000 ); terminate(); } } QString MidiOss::probeDevice() { QString dev = configManager::inst()->value( "midioss", "device" ); if( dev.isEmpty() ) { if( getenv( "MIDIDEV" ) != NULL ) { return getenv( "MIDIDEV" ); } return "/dev/midi"; } return dev; } void MidiOss::sendByte( const unsigned char c ) { m_midiDev.putChar( c ); } void MidiOss::run() { while( m_quit == false && m_midiDev.isOpen() ) { char c; if( !m_midiDev.getChar( &c ) ) { continue; } parseData( c ); } } MidiOss::setupWidget::setupWidget( QWidget * _parent ) : MidiClientRaw::setupWidget( MidiOss::name(), _parent ) { m_device = new QLineEdit( MidiOss::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); } MidiOss::setupWidget::~setupWidget() { } void MidiOss::setupWidget::saveSettings() { configManager::inst()->setValue( "midioss", "device", m_device->text() ); } #endif lmms-1.0.0/src/core/midi/MidiController.cpp0000644000175000017500000000624712313663627017311 0ustar tobytoby/* * MidiController.cpp - implementation of class midi-controller which handles * MIDI control change messages * * Copyright (c) 2008 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "song.h" #include "engine.h" #include "Mixer.h" #include "MidiClient.h" #include "MidiController.h" MidiController::MidiController( Model * _parent ) : Controller( Controller::MidiController, _parent, tr( "MIDI Controller" ) ), MidiEventProcessor(), m_midiPort( tr( "unnamed_midi_controller" ), engine::mixer()->midiClient(), this, this, MidiPort::Input ), m_lastValue( 0.0f ) { connect( &m_midiPort, SIGNAL( modeChanged() ), this, SLOT( updateName() ) ); } MidiController::~MidiController() { } float MidiController::value( int _offset ) { return m_lastValue; } void MidiController::updateName() { setName( QString("MIDI ch%1 ctrl%2"). arg( m_midiPort.inputChannel() ). arg( m_midiPort.inputController() ) ); } void MidiController::processInEvent( const MidiEvent& event, const MidiTime& time ) { unsigned char controllerNum; switch( event.type() ) { case MidiControlChange: controllerNum = event.controllerNumber(); if( m_midiPort.inputController() == controllerNum + 1 && ( m_midiPort.inputChannel() == event.channel() + 1 || m_midiPort.inputChannel() == 0 ) ) { unsigned char val = event.controllerValue(); m_lastValue = (float)( val ) / 127.0f; emit valueChanged(); } break; default: // Don't care - maybe add special cases for pitch and mod later break; } } void MidiController::subscribeReadablePorts( const MidiPort::Map & _map ) { for( MidiPort::Map::ConstIterator it = _map.constBegin(); it != _map.constEnd(); ++it ) { m_midiPort.subscribeReadablePort( it.key(), *it ); } } void MidiController::saveSettings( QDomDocument & _doc, QDomElement & _this ) { Controller::saveSettings( _doc, _this ); m_midiPort.saveSettings( _doc, _this ); } void MidiController::loadSettings( const QDomElement & _this ) { Controller::loadSettings( _this ); m_midiPort.loadSettings( _this ); updateName(); } QString MidiController::nodeName() const { return( "Midicontroller" ); } ControllerDialog * MidiController::createDialog( QWidget * _parent ) { return NULL; } #include "moc_MidiController.cxx" lmms-1.0.0/src/core/midi/MidiAlsaSeq.cpp0000644000175000017500000004042312313663627016511 0ustar tobytoby/* * MidiAlsaSeq.cpp - ALSA sequencer client * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "MidiAlsaSeq.h" #include "config_mgr.h" #include "engine.h" #include "gui_templates.h" #include "song.h" #include "MidiPort.h" #include "MidiTime.h" #include "note.h" #ifdef LMMS_HAVE_ALSA const int EventPollTimeOut = 250; // static helper functions static QString __portName( snd_seq_client_info_t * _cinfo, snd_seq_port_info_t * _pinfo ) { return QString( "%1:%2 %3:%4" ). arg( snd_seq_port_info_get_client( _pinfo ) ). arg( snd_seq_port_info_get_port( _pinfo ) ). arg( snd_seq_client_info_get_name( _cinfo ) ). arg( snd_seq_port_info_get_name( _pinfo ) ); } static QString __portName( snd_seq_t * _seq, const snd_seq_addr_t * _addr ) { snd_seq_client_info_t * cinfo; snd_seq_port_info_t * pinfo; snd_seq_client_info_malloc( &cinfo ); snd_seq_port_info_malloc( &pinfo ); snd_seq_get_any_port_info( _seq, _addr->client, _addr->port, pinfo ); snd_seq_get_any_client_info( _seq, _addr->client, cinfo ); const QString name = __portName( cinfo, pinfo ); snd_seq_client_info_free( cinfo ); snd_seq_port_info_free( pinfo ); return name; } MidiAlsaSeq::MidiAlsaSeq() : MidiClient(), m_seqMutex(), m_seqHandle( NULL ), m_queueID( -1 ), m_quit( false ), m_portListUpdateTimer( this ) { int err; if( ( err = snd_seq_open( &m_seqHandle, probeDevice().toAscii().constData(), SND_SEQ_OPEN_DUPLEX, 0 ) ) < 0 ) { fprintf( stderr, "cannot open sequencer: %s\n", snd_strerror( err ) ); return; } snd_seq_set_client_name( m_seqHandle, "LMMS" ); m_queueID = snd_seq_alloc_queue( m_seqHandle ); snd_seq_queue_tempo_t * tempo; snd_seq_queue_tempo_malloc( &tempo ); snd_seq_queue_tempo_set_tempo( tempo, 6000000 / engine::getSong()->getTempo() ); snd_seq_queue_tempo_set_ppq( tempo, 16 ); snd_seq_set_queue_tempo( m_seqHandle, m_queueID, tempo ); snd_seq_queue_tempo_free( tempo ); snd_seq_start_queue( m_seqHandle, m_queueID, NULL ); changeQueueTempo( engine::getSong()->getTempo() ); connect( engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), this, SLOT( changeQueueTempo( bpm_t ) ) ); // initial list-update updatePortList(); connect( &m_portListUpdateTimer, SIGNAL( timeout() ), this, SLOT( updatePortList() ) ); // we check for port-changes every second m_portListUpdateTimer.start( 1000 ); // use a pipe to detect shutdown if( pipe( m_pipe ) == -1 ) { perror( __FILE__ ": pipe" ); } start( QThread::IdlePriority ); } MidiAlsaSeq::~MidiAlsaSeq() { if( isRunning() ) { m_quit = true; wait( EventPollTimeOut*2 ); m_seqMutex.lock(); snd_seq_stop_queue( m_seqHandle, m_queueID, NULL ); snd_seq_free_queue( m_seqHandle, m_queueID ); snd_seq_close( m_seqHandle ); m_seqMutex.unlock(); } } QString MidiAlsaSeq::probeDevice() { QString dev = configManager::inst()->value( "Midialsaseq", "device" ); if( dev.isEmpty() ) { if( getenv( "MIDIDEV" ) != NULL ) { return getenv( "MIDIDEV" ); } return "default"; } return dev; } void MidiAlsaSeq::processOutEvent( const MidiEvent& event, const MidiTime& time, const MidiPort* port ) { // HACK!!! - need a better solution which isn't that easy since we // cannot store const-ptrs in our map because we need to call non-const // methods of MIDI-port - it's a mess... MidiPort* p = const_cast( port ); snd_seq_event_t ev; snd_seq_ev_clear( &ev ); snd_seq_ev_set_source( &ev, ( m_portIDs[p][1] != -1 ) ? m_portIDs[p][1] : m_portIDs[p][0] ); snd_seq_ev_set_subs( &ev ); snd_seq_ev_schedule_tick( &ev, m_queueID, 1, static_cast( time ) ); ev.queue = m_queueID; switch( event.type() ) { case MidiNoteOn: snd_seq_ev_set_noteon( &ev, event.channel(), event.key() + KeysPerOctave, event.velocity() ); break; case MidiNoteOff: snd_seq_ev_set_noteoff( &ev, event.channel(), event.key() + KeysPerOctave, event.velocity() ); break; case MidiKeyPressure: snd_seq_ev_set_keypress( &ev, event.channel(), event.key() + KeysPerOctave, event.velocity() ); break; case MidiControlChange: snd_seq_ev_set_controller( &ev, event.channel(), event.controllerNumber(), event.controllerValue() ); break; case MidiProgramChange: snd_seq_ev_set_pgmchange( &ev, event.channel(), event.program() ); break; case MidiChannelPressure: snd_seq_ev_set_chanpress( &ev, event.channel(), event.channelPressure() ); break; case MidiPitchBend: snd_seq_ev_set_pitchbend( &ev, event.channel(), event.param( 0 ) - 8192 ); break; default: qWarning( "MidiAlsaSeq: unhandled output event %d\n", (int) event.type() ); return; } m_seqMutex.lock(); snd_seq_event_output( m_seqHandle, &ev ); snd_seq_drain_output( m_seqHandle ); m_seqMutex.unlock(); } void MidiAlsaSeq::applyPortMode( MidiPort * _port ) { m_seqMutex.lock(); // determine port-capabilities unsigned int caps[2] = { 0, 0 }; switch( _port->mode() ) { case MidiPort::Duplex: caps[1] |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; case MidiPort::Input: caps[0] |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE; break; case MidiPort::Output: caps[1] |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; break; default: break; } for( int i = 0; i < 2; ++i ) { if( caps[i] != 0 ) { // no port there yet? if( m_portIDs[_port][i] == -1 ) { // then create one; m_portIDs[_port][i] = snd_seq_create_simple_port( m_seqHandle, _port->displayName().toUtf8().constData(), caps[i], SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION ); continue; } snd_seq_port_info_t * port_info; snd_seq_port_info_malloc( &port_info ); snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_set_capability( port_info, caps[i] ); snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_free( port_info ); } // still a port there although no caps? ( = dummy port) else if( m_portIDs[_port][i] != -1 ) { // then remove this port snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][i] ); m_portIDs[_port][i] = -1; } } m_seqMutex.unlock(); } void MidiAlsaSeq::applyPortName( MidiPort * _port ) { m_seqMutex.lock(); for( int i = 0; i < 2; ++i ) { if( m_portIDs[_port][i] == -1 ) { continue; } snd_seq_port_info_t * port_info; snd_seq_port_info_malloc( &port_info ); snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_set_name( port_info, _port->displayName().toUtf8().constData() ); snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i], port_info ); snd_seq_port_info_free( port_info ); } m_seqMutex.unlock(); } void MidiAlsaSeq::removePort( MidiPort * _port ) { if( m_portIDs.contains( _port ) ) { m_seqMutex.lock(); snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][0] ); snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][1] ); m_seqMutex.unlock(); m_portIDs.remove( _port ); } MidiClient::removePort( _port ); } QString MidiAlsaSeq::sourcePortName( const MidiEvent & _event ) const { if( _event.sourcePort() ) { const snd_seq_addr_t * addr = static_cast( _event.sourcePort() ); return __portName( m_seqHandle, addr ); } return MidiClient::sourcePortName( _event ); } void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port, const QString & _dest, bool _subscribe ) { if( !m_portIDs.contains( _port ) || m_portIDs[_port][0] < 0 ) { return; } m_seqMutex.lock(); snd_seq_addr_t sender; if( snd_seq_parse_address( m_seqHandle, &sender, _dest.section( ' ', 0, 0 ).toAscii().constData() ) ) { fprintf( stderr, "error parsing sender-address!\n" ); m_seqMutex.unlock(); return; } snd_seq_port_info_t * port_info; snd_seq_port_info_malloc( &port_info ); snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][0], port_info ); const snd_seq_addr_t * dest = snd_seq_port_info_get_addr( port_info ); snd_seq_port_subscribe_t * subs; snd_seq_port_subscribe_malloc( &subs ); snd_seq_port_subscribe_set_sender( subs, &sender ); snd_seq_port_subscribe_set_dest( subs, dest ); if( _subscribe ) { snd_seq_subscribe_port( m_seqHandle, subs ); } else { snd_seq_unsubscribe_port( m_seqHandle, subs ); } snd_seq_port_subscribe_free( subs ); snd_seq_port_info_free( port_info ); m_seqMutex.unlock(); } void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port, const QString & _dest, bool _subscribe ) { if( !m_portIDs.contains( _port ) ) { return; } const int pid = m_portIDs[_port][1] < 0 ? m_portIDs[_port][0] : m_portIDs[_port][1]; if( pid < 0 ) { return; } m_seqMutex.lock(); snd_seq_addr_t dest; if( snd_seq_parse_address( m_seqHandle, &dest, _dest.section( ' ', 0, 0 ).toAscii().constData() ) ) { fprintf( stderr, "error parsing dest-address!\n" ); m_seqMutex.unlock(); return; } snd_seq_port_info_t * port_info; snd_seq_port_info_malloc( &port_info ); snd_seq_get_port_info( m_seqHandle, pid, port_info ); const snd_seq_addr_t * sender = snd_seq_port_info_get_addr( port_info ); snd_seq_port_subscribe_t * subs; snd_seq_port_subscribe_malloc( &subs ); snd_seq_port_subscribe_set_sender( subs, sender ); snd_seq_port_subscribe_set_dest( subs, &dest ); if( _subscribe ) { snd_seq_subscribe_port( m_seqHandle, subs ); } else { snd_seq_unsubscribe_port( m_seqHandle, subs ); } snd_seq_port_subscribe_free( subs ); snd_seq_port_info_free( port_info ); m_seqMutex.unlock(); } void MidiAlsaSeq::run() { // watch the pipe and sequencer input events int pollfd_count = snd_seq_poll_descriptors_count( m_seqHandle, POLLIN ); struct pollfd * pollfd_set = new struct pollfd[pollfd_count + 1]; snd_seq_poll_descriptors( m_seqHandle, pollfd_set + 1, pollfd_count, POLLIN ); pollfd_set[0].fd = m_pipe[0]; pollfd_set[0].events = POLLIN; ++pollfd_count; while( m_quit == false ) { int pollRet = poll( pollfd_set, pollfd_count, EventPollTimeOut ); if( pollRet == 0 ) { continue; } else if( pollRet == -1 ) { // gdb may interrupt the poll if( errno == EINTR ) { continue; } qCritical( "error while polling ALSA sequencer handle" ); break; } // shutdown? if( m_quit ) { break; } m_seqMutex.lock(); // while event queue is not empty while( snd_seq_event_input_pending( m_seqHandle, true ) > 0 ) { snd_seq_event_t * ev; if( snd_seq_event_input( m_seqHandle, &ev ) < 0 ) { m_seqMutex.unlock(); qCritical( "error while fetching MIDI event from sequencer" ); break; } m_seqMutex.unlock(); snd_seq_addr_t * source = NULL; MidiPort * dest = NULL; for( int i = 0; i < m_portIDs.size(); ++i ) { if( m_portIDs.values()[i][0] == ev->dest.port ) { dest = m_portIDs.keys()[i]; } if( ( m_portIDs.values()[i][1] != -1 && m_portIDs.values()[i][1] == ev->source.port ) || m_portIDs.values()[i][0] == ev->source.port ) { source = &ev->source; } } if( dest == NULL ) { continue; } switch( ev->type ) { case SND_SEQ_EVENT_NOTEON: dest->processInEvent( MidiEvent( MidiNoteOn, ev->data.note.channel, ev->data.note.note - KeysPerOctave, ev->data.note.velocity, source ), MidiTime( ev->time.tick ) ); break; case SND_SEQ_EVENT_NOTEOFF: dest->processInEvent( MidiEvent( MidiNoteOff, ev->data.note.channel, ev->data.note.note - KeysPerOctave, ev->data.note.velocity, source ), MidiTime( ev->time.tick) ); break; case SND_SEQ_EVENT_KEYPRESS: dest->processInEvent( MidiEvent( MidiKeyPressure, ev->data.note.channel, ev->data.note.note - KeysPerOctave, ev->data.note.velocity, source ), MidiTime() ); break; case SND_SEQ_EVENT_CONTROLLER: dest->processInEvent( MidiEvent( MidiControlChange, ev->data.control.channel, ev->data.control.param, ev->data.control.value, source ), MidiTime() ); break; case SND_SEQ_EVENT_PGMCHANGE: dest->processInEvent( MidiEvent( MidiProgramChange, ev->data.control.channel, ev->data.control.param, ev->data.control.value, source ), MidiTime() ); break; case SND_SEQ_EVENT_CHANPRESS: dest->processInEvent( MidiEvent( MidiChannelPressure, ev->data.control.channel, ev->data.control.param, ev->data.control.value, source ), MidiTime() ); break; case SND_SEQ_EVENT_PITCHBEND: dest->processInEvent( MidiEvent( MidiPitchBend, ev->data.control.channel, ev->data.control.value + 8192, 0, source ), MidiTime() ); break; case SND_SEQ_EVENT_SENSING: case SND_SEQ_EVENT_CLOCK: break; default: fprintf( stderr, "ALSA-sequencer: unhandled input " "event %d\n", ev->type ); break; } // end switch m_seqMutex.lock(); } // end while m_seqMutex.unlock(); } delete[] pollfd_set; } void MidiAlsaSeq::changeQueueTempo( bpm_t _bpm ) { m_seqMutex.lock(); snd_seq_change_queue_tempo( m_seqHandle, m_queueID, 60000000 / (int) _bpm, NULL ); snd_seq_drain_output( m_seqHandle ); m_seqMutex.unlock(); } void MidiAlsaSeq::updatePortList() { QStringList readablePorts; QStringList writablePorts; // get input- and output-ports snd_seq_client_info_t * cinfo; snd_seq_port_info_t * pinfo; snd_seq_client_info_malloc( &cinfo ); snd_seq_port_info_malloc( &pinfo ); snd_seq_client_info_set_client( cinfo, -1 ); m_seqMutex.lock(); while( snd_seq_query_next_client( m_seqHandle, cinfo ) >= 0 ) { int client = snd_seq_client_info_get_client( cinfo ); snd_seq_port_info_set_client( pinfo, client ); snd_seq_port_info_set_port( pinfo, -1 ); while( snd_seq_query_next_port( m_seqHandle, pinfo ) >= 0 ) { // we need both READ and SUBS_READ if( ( snd_seq_port_info_get_capability( pinfo ) & ( SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ ) ) == ( SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ ) ) { readablePorts.push_back( __portName( cinfo, pinfo ) ); } if( ( snd_seq_port_info_get_capability( pinfo ) & ( SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ) ) == ( SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE ) ) { writablePorts.push_back( __portName( cinfo, pinfo ) ); } } } m_seqMutex.unlock(); snd_seq_client_info_free( cinfo ); snd_seq_port_info_free( pinfo ); if( m_readablePorts != readablePorts ) { m_readablePorts = readablePorts; emit readablePortsChanged(); } if( m_writablePorts != writablePorts ) { m_writablePorts = writablePorts; emit writablePortsChanged(); } } MidiAlsaSeq::setupWidget::setupWidget( QWidget * _parent ) : MidiClient::setupWidget( MidiAlsaSeq::name(), _parent ) { m_device = new QLineEdit( MidiAlsaSeq::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); } MidiAlsaSeq::setupWidget::~setupWidget() { } void MidiAlsaSeq::setupWidget::saveSettings() { configManager::inst()->setValue( "Midialsaseq", "device", m_device->text() ); } #include "moc_MidiAlsaSeq.cxx" #endif lmms-1.0.0/src/core/midi/MidiClient.cpp0000644000175000017500000002006712313663627016400 0ustar tobytoby/* * MidiClient.cpp - base-class for MIDI-clients like ALSA-sequencer-client * * Copyright (c) 2005-2014 Tobias Doerffel * This file partly contains code from Fluidsynth, Peter Hanappe * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "MidiClient.h" #include "MidiPort.h" #include "note.h" MidiClient::MidiClient() { } MidiClient::~MidiClient() { //TODO: noteOffAll(); / clear all ports } void MidiClient::applyPortMode( MidiPort* ) { } void MidiClient::applyPortName( MidiPort* ) { } void MidiClient::addPort( MidiPort* port ) { m_midiPorts.push_back( port ); } void MidiClient::removePort( MidiPort* port ) { QVector::Iterator it = qFind( m_midiPorts.begin(), m_midiPorts.end(), port ); if( it != m_midiPorts.end() ) { m_midiPorts.erase( it ); } } void MidiClient::subscribeReadablePort( MidiPort*, const QString& , bool ) { } void MidiClient::subscribeWritablePort( MidiPort* , const QString& , bool ) { } MidiClientRaw::MidiClientRaw() { } MidiClientRaw::~MidiClientRaw() { } void MidiClientRaw::parseData( const unsigned char c ) { /*********************************************************************/ /* 'Process' system real-time messages */ /*********************************************************************/ /* There are not too many real-time messages that are of interest here. * They can occur anywhere, even in the middle of a noteon message! * Real-time range: 0xF8 .. 0xFF * Note: Real-time does not affect (running) status. */ if( c >= 0xF8 ) { if( c == MidiSystemReset ) { m_midiParseData.m_midiEvent.setType( MidiSystemReset ); m_midiParseData.m_status = 0; processParsedEvent(); } return; } /*********************************************************************/ /* 'Process' system common messages (again, just skip them) */ /*********************************************************************/ /* There are no system common messages that are of interest here. * System common range: 0xF0 .. 0xF7 */ if( c > 0xF0 ) { /* MIDI spec say: To ignore a non-real-time message, just discard all * data up to the next status byte. And our parser will ignore data * that is received without a valid status. * Note: system common cancels running status. */ m_midiParseData.m_status = 0; return; } /*********************************************************************/ /* Process voice category messages: */ /*********************************************************************/ /* Now that we have handled realtime and system common messages, only * voice messages are left. * Only a status byte has bit # 7 set. * So no matter the status of the parser (in case we have lost sync), * as soon as a byte >= 0x80 comes in, we are dealing with a status byte * and start a new event. */ if( c & 0x80 ) { m_midiParseData.m_channel = c & 0x0F; m_midiParseData.m_status = c & 0xF0; /* The event consumes x bytes of data... (subtract 1 for the status byte) */ m_midiParseData.m_bytesTotal = eventLength( m_midiParseData.m_status ) - 1; /* of which we have read 0 at this time. */ m_midiParseData.m_bytes = 0; return; } /*********************************************************************/ /* Process data */ /*********************************************************************/ /* If we made it this far, then the received char belongs to the data * of the last event. */ if( m_midiParseData.m_status == 0 ) { /* We are not interested in the event currently received. Discard the data. */ return; } /* Store the first couple of bytes */ if( m_midiParseData.m_bytes < RAW_MIDI_PARSE_BUF_SIZE ) { m_midiParseData.m_buffer[m_midiParseData.m_bytes] = c; } ++m_midiParseData.m_bytes; /* Do we still need more data to get this event complete? */ if( m_midiParseData.m_bytes < m_midiParseData.m_bytesTotal ) { return; } /*********************************************************************/ /* Send the event */ /*********************************************************************/ /* The event is ready-to-go. About 'running status': * * The MIDI protocol has a built-in compression mechanism. If several * similar events are sent in-a-row, for example note-ons, then the * event type is only sent once. For this case, the last event type * (status) is remembered. * We simply keep the status as it is, just reset the parameter counter. * If another status byte comes in, it will overwrite the status. */ m_midiParseData.m_midiEvent.setType( static_cast( m_midiParseData.m_status ) ); m_midiParseData.m_midiEvent.setChannel( m_midiParseData.m_channel ); m_midiParseData.m_bytes = 0; /* Related to running status! */ switch( m_midiParseData.m_midiEvent.type() ) { case MidiNoteOff: case MidiNoteOn: case MidiKeyPressure: case MidiProgramChange: case MidiChannelPressure: m_midiParseData.m_midiEvent.setKey( m_midiParseData.m_buffer[0] - KeysPerOctave ); m_midiParseData.m_midiEvent.setVelocity( m_midiParseData.m_buffer[1] ); break; case MidiControlChange: m_midiParseData.m_midiEvent.setControllerNumber( m_midiParseData.m_buffer[0] ); m_midiParseData.m_midiEvent.setControllerValue( m_midiParseData.m_buffer[1] ); break; case MidiPitchBend: // Pitch-bend is transmitted with 14-bit precision. // Note: '|' does here the same as '+' (no common bits), // but might be faster m_midiParseData.m_midiEvent.setPitchBend( ( m_midiParseData.m_buffer[1] * 128 ) | m_midiParseData.m_buffer[0] ); break; default: // Unlikely return; } processParsedEvent(); } void MidiClientRaw::processParsedEvent() { for( int i = 0; i < m_midiPorts.size(); ++i ) { m_midiPorts[i]->processInEvent( m_midiParseData.m_midiEvent ); } } void MidiClientRaw::processOutEvent( const MidiEvent& event, const MidiTime & , const MidiPort* port ) { // TODO: also evaluate _time and queue event if necessary switch( event.type() ) { case MidiNoteOn: case MidiNoteOff: case MidiKeyPressure: sendByte( event.type() | event.channel() ); sendByte( event.key() + KeysPerOctave ); sendByte( event.velocity() ); break; default: qWarning( "MidiClientRaw: unhandled MIDI-event %d\n", (int) event.type() ); break; } } // Taken from Nagano Daisuke's USB-MIDI driver static const unsigned char REMAINS_F0F6[] = { 0, /* 0xF0 */ 2, /* 0XF1 */ 3, /* 0XF2 */ 2, /* 0XF3 */ 2, /* 0XF4 (Undefined by MIDI Spec, and subject to change) */ 2, /* 0XF5 (Undefined by MIDI Spec, and subject to change) */ 1 /* 0XF6 */ } ; static const unsigned char REMAINS_80E0[] = { 3, /* 0x8X Note Off */ 3, /* 0x9X Note On */ 3, /* 0xAX Poly-key pressure */ 3, /* 0xBX Control Change */ 2, /* 0xCX Program Change */ 2, /* 0xDX Channel pressure */ 3 /* 0xEX PitchBend Change */ } ; // Returns the length of the MIDI message starting with _event. // Taken from Nagano Daisuke's USB-MIDI driver int MidiClientRaw::eventLength( const unsigned char event ) { if ( event < 0xF0 ) { return REMAINS_80E0[( ( event - 0x80 ) >> 4 ) & 0x0F]; } else if ( event < 0xF7 ) { return REMAINS_F0F6[event - 0xF0]; } return 1; } lmms-1.0.0/src/core/midi/MidiAlsaRaw.cpp0000644000175000017500000001035512313663627016513 0ustar tobytoby/* * MidiAlsaRaw.cpp - MIDI client for RawMIDI via ALSA * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "MidiAlsaRaw.h" #include "config_mgr.h" #include "gui_templates.h" #ifdef LMMS_HAVE_ALSA MidiAlsaRaw::MidiAlsaRaw() : MidiClientRaw(), m_inputp( &m_input ), m_outputp( &m_output ), m_quit( false ) { int err; if( ( err = snd_rawmidi_open( m_inputp, m_outputp, probeDevice().toAscii().constData(), 0 ) ) < 0 ) { printf( "cannot open MIDI-device: %s\n", snd_strerror( err ) ); return; } snd_rawmidi_read( m_input, NULL, 0 ); snd_rawmidi_nonblock( m_input, 1 ); m_npfds = snd_rawmidi_poll_descriptors_count( m_input ); m_pfds = new pollfd[m_npfds]; snd_rawmidi_poll_descriptors( m_input, m_pfds, m_npfds ); start( QThread::LowPriority ); } MidiAlsaRaw::~MidiAlsaRaw() { if( isRunning() ) { m_quit = true; wait( 1000 ); terminate(); snd_rawmidi_close( m_input ); snd_rawmidi_close( m_output ); delete[] m_pfds; } } QString MidiAlsaRaw::probeDevice() { QString dev = configManager::inst()->value( "MidiAlsaRaw", "Device" ); if( dev == "" ) { if( getenv( "MIDIDEV" ) != NULL ) { return getenv( "MIDIDEV" ); } return "default"; } return dev; } void MidiAlsaRaw::sendByte( unsigned char c ) { snd_rawmidi_write( m_output, &c, sizeof( c ) ); } void MidiAlsaRaw::run() { unsigned char buf[128]; //int cnt = 0; while( m_quit == false ) { msleep( 5 ); // must do that, otherwise this thread takes // too much CPU-time, even with LowPriority... int err = poll( m_pfds, m_npfds, 10000 ); if( err < 0 && errno == EINTR ) { printf( "MidiAlsaRaw::run(): Got EINTR while " "polling. Will stop polling MIDI-events from " "MIDI-port.\n" ); break; } if( err < 0 ) { printf( "poll failed: %s\nWill stop polling " "MIDI-events from MIDI-port.\n", strerror( errno ) ); break; } if( err == 0 ) { //printf( "there seems to be no active MIDI-device %d\n", ++cnt ); continue; } unsigned short revents; if( ( err = snd_rawmidi_poll_descriptors_revents( m_input, m_pfds, m_npfds, &revents ) ) < 0 ) { printf( "cannot get poll events: %s\nWill stop polling " "MIDI-events from MIDI-port.\n", snd_strerror( errno ) ); break; } if( revents & ( POLLERR | POLLHUP ) ) { printf( "POLLERR or POLLHUP\n" ); break; } if( !( revents & POLLIN ) ) { continue; } err = snd_rawmidi_read( m_input, buf, sizeof( buf ) ); if( err == -EAGAIN ) { continue; } if( err < 0 ) { printf( "cannot read from port \"%s\": %s\nWill stop " "polling MIDI-events from MIDI-port.\n", /*port_name*/"default", snd_strerror( err ) ); break; } if( err == 0 ) { continue; } for( int i = 0; i < err; ++i ) { parseData( buf[i] ); } } } MidiAlsaRaw::setupWidget::setupWidget( QWidget * _parent ) : MidiClientRaw::setupWidget( MidiAlsaRaw::name(), _parent ) { m_device = new QLineEdit( MidiAlsaRaw::probeDevice(), this ); m_device->setGeometry( 10, 20, 160, 20 ); QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this ); dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) ); dev_lbl->setGeometry( 10, 40, 160, 10 ); } MidiAlsaRaw::setupWidget::~setupWidget() { } void MidiAlsaRaw::setupWidget::saveSettings() { configManager::inst()->setValue( "MidiAlsaRaw", "Device", m_device->text() ); } #endif lmms-1.0.0/src/core/midi/MidiWinMM.cpp0000644000175000017500000001477512313663627016162 0ustar tobytoby/* * MidiWinMM.cpp - WinMM MIDI client * * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "MidiWinMM.h" #include "config_mgr.h" #include "engine.h" #include "gui_templates.h" #include "MidiPort.h" #include "note.h" #ifdef LMMS_BUILD_WIN32 MidiWinMM::MidiWinMM() : MidiClient(), m_inputDevices(), m_outputDevices(), m_inputSubs(), m_outputSubs() { openDevices(); } MidiWinMM::~MidiWinMM() { closeDevices(); } void MidiWinMM::processOutEvent( const MidiEvent& event, const MidiTime& time, const MidiPort* port ) { const DWORD shortMsg = ( event.type() + event.channel() ) + ( ( event.param( 0 ) & 0xff ) << 8 ) + ( ( event.param( 1 ) & 0xff ) << 16 ); QStringList outDevs; for( SubMap::ConstIterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it ) { for( MidiPortList::ConstIterator jt = it.value().begin(); jt != it.value().end(); ++jt ) { if( *jt == port ) { outDevs += it.key(); break; } } } for( QMap::Iterator it = m_outputDevices.begin(); it != m_outputDevices.end(); ++it ) { if( outDevs.contains( *it ) ) { midiOutShortMsg( it.key(), shortMsg ); } } } void MidiWinMM::applyPortMode( MidiPort* port ) { // make sure no subscriptions exist which are not possible with // current port-mode if( !port->isInputEnabled() ) { for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it ) { it.value().removeAll( port ); } } if( !port->isOutputEnabled() ) { for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it ) { it.value().removeAll( port ); } } } void MidiWinMM::removePort( MidiPort* port ) { for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it ) { it.value().removeAll( port ); } for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it ) { it.value().removeAll( port ); } MidiClient::removePort( port ); } QString MidiWinMM::sourcePortName( const MidiEvent& event ) const { if( event.sourcePort() ) { return m_inputDevices.value( *static_cast( event.sourcePort() ) ); } return MidiClient::sourcePortName( event ); } void MidiWinMM::subscribeReadablePort( MidiPort* port, const QString& dest, bool subscribe ) { if( subscribe && port->isInputEnabled() == false ) { qWarning( "port %s can't be (un)subscribed!\n", port->displayName().toAscii().constData() ); return; } m_inputSubs[dest].removeAll( port ); if( subscribe ) { m_inputSubs[dest].push_back( port ); } } void MidiWinMM::subscribeWritablePort( MidiPort* port, const QString& dest, bool subscribe ) { if( subscribe && port->isOutputEnabled() == false ) { qWarning( "port %s can't be (un)subscribed!\n", port->displayName().toAscii().constData() ); return; } m_outputSubs[dest].removeAll( port ); if( subscribe ) { m_outputSubs[dest].push_back( port ); } } void WINAPI CALLBACK MidiWinMM::inputCallback( HMIDIIN hm, UINT msg, DWORD_PTR inst, DWORD_PTR param1, DWORD_PTR param2 ) { if( msg == MIM_DATA ) { ( (MidiWinMM *) inst )->handleInputEvent( hm, param1 ); } } void MidiWinMM::handleInputEvent( HMIDIIN hm, DWORD ev ) { const int cmd = ev & 0xff; if( cmd == MidiActiveSensing ) { return; } const int par1 = ( ev >> 8 ) & 0xff; const int par2 = ev >> 16; const MidiEventTypes cmdtype = static_cast( cmd & 0xf0 ); const int chan = cmd & 0x0f; const QString d = m_inputDevices.value( hm ); if( d.isEmpty() || !m_inputSubs.contains( d ) ) { return; } const MidiPortList & l = m_inputSubs[d]; for( MidiPortList::ConstIterator it = l.begin(); it != l.end(); ++it ) { switch( cmdtype ) { case MidiNoteOn: case MidiNoteOff: case MidiKeyPressure: ( *it )->processInEvent( MidiEvent( cmdtype, chan, par1 - KeysPerOctave, par2 & 0xff, &hm ) ); break; case MidiControlChange: case MidiProgramChange: case MidiChannelPressure: ( *it )->processInEvent( MidiEvent( cmdtype, chan, par1, par2 & 0xff, &hm ) ); break; case MidiPitchBend: ( *it )->processInEvent( MidiEvent( cmdtype, chan, par1 + par2*128, 0, &hm ) ); break; default: qWarning( "MidiWinMM: unhandled input event %d\n", cmdtype ); break; } } } void MidiWinMM::updateDeviceList() { closeDevices(); openDevices(); emit readablePortsChanged(); emit writablePortsChanged(); } void MidiWinMM::closeDevices() { m_inputSubs.clear(); m_outputSubs.clear(); QMapIterator i( m_inputDevices ); while( i.hasNext() ) { midiInClose( i.next().key() ); } QMapIterator o( m_outputDevices ); while( o.hasNext() ) { midiOutClose( o.next().key() ); } m_inputDevices.clear(); m_outputDevices.clear(); } void MidiWinMM::openDevices() { m_inputDevices.clear(); for( unsigned int i = 0; i < midiInGetNumDevs(); ++i ) { MIDIINCAPS c; midiInGetDevCaps( i, &c, sizeof( c ) ); HMIDIIN hm = 0; MMRESULT res = midiInOpen( &hm, i, (DWORD_PTR) &inputCallback, (DWORD_PTR) this, CALLBACK_FUNCTION ); if( res == MMSYSERR_NOERROR ) { m_inputDevices[hm] = qstrdup( c.szPname ); midiInStart( hm ); } } m_outputDevices.clear(); for( unsigned int i = 0; i < midiOutGetNumDevs(); ++i ) { MIDIOUTCAPS c; midiOutGetDevCaps( i, &c, sizeof( c ) ); HMIDIOUT hm = 0; MMRESULT res = midiOutOpen( &hm, i, 0, 0, CALLBACK_NULL ); if( res == MMSYSERR_NOERROR ) { m_outputDevices[hm] = qstrdup( c.szPname ); } } } MidiWinMM::setupWidget::setupWidget( QWidget* parent ) : MidiClient::setupWidget( MidiWinMM::name(), parent ) { } MidiWinMM::setupWidget::~setupWidget() { } #include "moc_MidiWinMM.cxx" #endif lmms-1.0.0/src/core/midi/MidiPort.cpp0000644000175000017500000002512712313663627016110 0ustar tobytoby/* * MidiPort.cpp - abstraction of MIDI-ports which are part of LMMS's MIDI- * sequencing system * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "MidiPort.h" #include "MidiClient.h" #include "song.h" MidiPort::MidiPort( const QString& name, MidiClient* client, MidiEventProcessor* eventProcessor, Model* parent, Mode mode ) : Model( parent ), m_readablePortsMenu( NULL ), m_writablePortsMenu( NULL ), m_midiClient( client ), m_midiEventProcessor( eventProcessor ), m_mode( mode ), m_inputChannelModel( 0, 0, MidiChannelCount, this, tr( "Input channel" ) ), m_outputChannelModel( 1, 1, MidiChannelCount, this, tr( "Output channel" ) ), m_inputControllerModel( 0, 0, MidiControllerCount, this, tr( "Input controller" ) ), m_outputControllerModel( 0, 0, MidiControllerCount, this, tr( "Output controller" ) ), m_fixedInputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed input velocity" ) ), m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ), m_fixedOutputNoteModel( -1, -1, MidiMaxNote, this, tr( "Fixed output note" ) ), m_outputProgramModel( 1, 1, MidiProgramCount, this, tr( "Output MIDI program" ) ), m_baseVelocityModel( MidiMaxVelocity/2, 1, MidiMaxVelocity, this, tr( "Base velocity" ) ), m_readableModel( false, this, tr( "Receive MIDI-events" ) ), m_writableModel( false, this, tr( "Send MIDI-events" ) ) { m_midiClient->addPort( this ); m_readableModel.setValue( m_mode == Input || m_mode == Duplex ); m_writableModel.setValue( m_mode == Output || m_mode == Duplex ); connect( &m_readableModel, SIGNAL( dataChanged() ), this, SLOT( updateMidiPortMode() ) ); connect( &m_writableModel, SIGNAL( dataChanged() ), this, SLOT( updateMidiPortMode() ) ); connect( &m_outputProgramModel, SIGNAL( dataChanged() ), this, SLOT( updateOutputProgram() ) ); // when using with non-raw-clients we can provide buttons showing // our port-menus when being clicked if( m_midiClient->isRaw() == false ) { updateReadablePorts(); updateWritablePorts(); // we want to get informed about port-changes! m_midiClient->connectRPChanged( this, SLOT( updateReadablePorts() ) ); m_midiClient->connectWPChanged( this, SLOT( updateWritablePorts() ) ); } updateMidiPortMode(); } MidiPort::~MidiPort() { // unsubscribe ports m_readableModel.setValue( false ); m_writableModel.setValue( false ); // and finally unregister ourself m_midiClient->removePort( this ); } void MidiPort::setName( const QString& name ) { setDisplayName( name ); m_midiClient->applyPortName( this ); } void MidiPort::setMode( Mode mode ) { m_mode = mode; m_midiClient->applyPortMode( this ); } void MidiPort::processInEvent( const MidiEvent& event, const MidiTime& time ) { // mask event if( isInputEnabled() && ( inputChannel() == 0 || inputChannel()-1 == event.channel() ) ) { MidiEvent inEvent = event; if( event.type() == MidiNoteOn || event.type() == MidiNoteOff || event.type() == MidiKeyPressure ) { if( inEvent.key() < 0 || inEvent.key() >= NumKeys ) { return; } } if( fixedInputVelocity() >= 0 && inEvent.velocity() > 0 ) { inEvent.setVelocity( fixedInputVelocity() ); } m_midiEventProcessor->processInEvent( inEvent, time ); } } void MidiPort::processOutEvent( const MidiEvent& event, const MidiTime& time ) { // mask event if( isOutputEnabled() && realOutputChannel() == event.channel() ) { MidiEvent outEvent = event; if( fixedOutputVelocity() >= 0 && event.velocity() > 0 && ( event.type() == MidiNoteOn || event.type() == MidiKeyPressure ) ) { outEvent.setVelocity( fixedOutputVelocity() ); } m_midiClient->processOutEvent( outEvent, time, this ); } } void MidiPort::saveSettings( QDomDocument& doc, QDomElement& thisElement ) { m_inputChannelModel.saveSettings( doc, thisElement, "inputchannel" ); m_outputChannelModel.saveSettings( doc, thisElement, "outputchannel" ); m_inputControllerModel.saveSettings( doc, thisElement, "inputcontroller" ); m_outputControllerModel.saveSettings( doc, thisElement, "outputcontroller" ); m_fixedInputVelocityModel.saveSettings( doc, thisElement, "fixedinputvelocity" ); m_fixedOutputVelocityModel.saveSettings( doc, thisElement, "fixedoutputvelocity" ); m_fixedOutputNoteModel.saveSettings( doc, thisElement, "fixedoutputnote" ); m_outputProgramModel.saveSettings( doc, thisElement, "outputprogram" ); m_baseVelocityModel.saveSettings( doc, thisElement, "basevelocity" ); m_readableModel.saveSettings( doc, thisElement, "readable" ); m_writableModel.saveSettings( doc, thisElement, "writable" ); if( isInputEnabled() ) { QString rp; for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it ) { if( it.value() ) { rp += it.key() + ","; } } // cut off comma if( rp.length() > 0 ) { rp.truncate( rp.length() - 1 ); } thisElement.setAttribute( "inports", rp ); } if( isOutputEnabled() ) { QString wp; for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it ) { if( it.value() ) { wp += it.key() + ","; } } // cut off comma if( wp.length() > 0 ) { wp.truncate( wp.length() - 1 ); } thisElement.setAttribute( "outports", wp ); } } void MidiPort::loadSettings( const QDomElement& thisElement ) { m_inputChannelModel.loadSettings( thisElement, "inputchannel" ); m_outputChannelModel.loadSettings( thisElement, "outputchannel" ); m_inputControllerModel.loadSettings( thisElement, "inputcontroller" ); m_outputControllerModel.loadSettings( thisElement, "outputcontroller" ); m_fixedInputVelocityModel.loadSettings( thisElement, "fixedinputvelocity" ); m_fixedOutputVelocityModel.loadSettings( thisElement, "fixedoutputvelocity" ); m_outputProgramModel.loadSettings( thisElement, "outputprogram" ); m_baseVelocityModel.loadSettings( thisElement, "basevelocity" ); m_readableModel.loadSettings( thisElement, "readable" ); m_writableModel.loadSettings( thisElement, "writable" ); // restore connections if( isInputEnabled() ) { QStringList rp = thisElement.attribute( "inports" ).split( ',' ); for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it ) { if( it.value() != ( rp.indexOf( it.key() ) != -1 ) ) { subscribeReadablePort( it.key() ); } } emit readablePortsChanged(); } if( isOutputEnabled() ) { QStringList wp = thisElement.attribute( "outports" ).split( ',' ); for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it ) { if( it.value() != ( wp.indexOf( it.key() ) != -1 ) ) { subscribeWritablePort( it.key() ); } } emit writablePortsChanged(); } if( thisElement.hasAttribute( "basevelocity" ) == false ) { // for projects created by LMMS < 0.9.92 there's no value for the base // velocity and for compat reasons we have to stick with maximum velocity // which did not allow note volumes > 100% m_baseVelocityModel.setValue( MidiMaxVelocity ); } } void MidiPort::subscribeReadablePort( const QString& port, bool subscribe ) { m_readablePorts[port] = subscribe; // make sure, MIDI-port is configured for input if( subscribe == true && !isInputEnabled() ) { m_readableModel.setValue( true ); } m_midiClient->subscribeReadablePort( this, port, subscribe ); } void MidiPort::subscribeWritablePort( const QString& port, bool subscribe ) { m_writablePorts[port] = subscribe; // make sure, MIDI-port is configured for output if( subscribe == true && !isOutputEnabled() ) { m_writableModel.setValue( true ); } m_midiClient->subscribeWritablePort( this, port, subscribe ); } void MidiPort::updateMidiPortMode() { // this small lookup-table makes everything easier static const Modes modeTable[2][2] = { { Disabled, Output }, { Input, Duplex } } ; setMode( modeTable[m_readableModel.value()][m_writableModel.value()] ); // check whether we have to dis-check items in connection-menu if( !isInputEnabled() ) { for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it ) { // subscribed? if( it.value() ) { subscribeReadablePort( it.key(), false ); } } } if( !isOutputEnabled() ) { for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it ) { // subscribed? if( it.value() ) { subscribeWritablePort( it.key(), false ); } } } emit readablePortsChanged(); emit writablePortsChanged(); emit modeChanged(); engine::getSong()->setModified(); } void MidiPort::updateReadablePorts() { // first save all selected ports QStringList selectedPorts; for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it ) { if( it.value() ) { selectedPorts.push_back( it.key() ); } } m_readablePorts.clear(); const QStringList& wp = m_midiClient->readablePorts(); // now insert new ports and restore selections for( QStringList::ConstIterator it = wp.begin(); it != wp.end(); ++it ) { m_readablePorts[*it] = ( selectedPorts.indexOf( *it ) != -1 ); } emit readablePortsChanged(); } void MidiPort::updateWritablePorts() { // first save all selected ports QStringList selectedPorts; for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it ) { if( it.value() ) { selectedPorts.push_back( it.key() ); } } m_writablePorts.clear(); const QStringList & wp = m_midiClient->writablePorts(); // now insert new ports and restore selections for( QStringList::ConstIterator it = wp.begin(); it != wp.end(); ++it ) { m_writablePorts[*it] = ( selectedPorts.indexOf( *it ) != -1 ); } emit writablePortsChanged(); } void MidiPort::updateOutputProgram() { processOutEvent( MidiEvent( MidiProgramChange, realOutputChannel(), outputProgram()-1 ) ); } #include "moc_MidiPort.cxx" lmms-1.0.0/src/core/engine.cpp0000644000175000017500000001066012313663627014700 0ustar tobytoby/* * engine.cpp - implementation of LMMS' engine-system * * Copyright (c) 2006-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "engine.h" #include "AutomationEditor.h" #include "bb_editor.h" #include "bb_track_container.h" #include "config_mgr.h" #include "ControllerRackView.h" #include "FxMixer.h" #include "FxMixerView.h" #include "InstrumentTrack.h" #include "ladspa_2_lmms.h" #include "MainWindow.h" #include "Mixer.h" #include "pattern.h" #include "PianoRoll.h" #include "PresetPreviewPlayHandle.h" #include "ProjectJournal.h" #include "project_notes.h" #include "Plugin.h" #include "SongEditor.h" #include "song.h" bool engine::s_hasGUI = true; bool engine::s_suppressMessages = false; float engine::s_framesPerTick; Mixer* engine::s_mixer = NULL; FxMixer * engine::s_fxMixer = NULL; FxMixerView * engine::s_fxMixerView = NULL; MainWindow * engine::s_mainWindow = NULL; bbTrackContainer * engine::s_bbTrackContainer = NULL; song * engine::s_song = NULL; SongEditor* engine::s_songEditor = NULL; AutomationEditor * engine::s_automationEditor = NULL; bbEditor * engine::s_bbEditor = NULL; PianoRoll* engine::s_pianoRoll = NULL; projectNotes * engine::s_projectNotes = NULL; ProjectJournal * engine::s_projectJournal = NULL; ladspa2LMMS * engine::s_ladspaManager = NULL; DummyTrackContainer * engine::s_dummyTC = NULL; ControllerRackView * engine::s_controllerRackView = NULL; QMap engine::s_pluginFileHandling; void engine::init( const bool _has_gui ) { s_hasGUI = _has_gui; initPluginFileHandling(); s_projectJournal = new ProjectJournal; s_mixer = new Mixer; s_song = new song; s_fxMixer = new FxMixer; s_bbTrackContainer = new bbTrackContainer; s_ladspaManager = new ladspa2LMMS; s_projectJournal->setJournalling( true ); s_mixer->initDevices(); if( s_hasGUI ) { s_mainWindow = new MainWindow; s_songEditor = new SongEditor( s_song ); s_fxMixerView = new FxMixerView; s_controllerRackView = new ControllerRackView; s_projectNotes = new projectNotes; s_bbEditor = new bbEditor( s_bbTrackContainer ); s_pianoRoll = new PianoRoll; s_automationEditor = new AutomationEditor; s_mainWindow->finalize(); } PresetPreviewPlayHandle::init(); s_dummyTC = new DummyTrackContainer; s_mixer->startProcessing(); } void engine::destroy() { s_mixer->stopProcessing(); deleteHelper( &s_projectNotes ); deleteHelper( &s_songEditor ); deleteHelper( &s_bbEditor ); deleteHelper( &s_pianoRoll ); deleteHelper( &s_automationEditor ); deleteHelper( &s_fxMixerView ); PresetPreviewPlayHandle::cleanup(); InstrumentTrackView::cleanupWindowCache(); s_song->clearProject(); deleteHelper( &s_bbTrackContainer ); deleteHelper( &s_dummyTC ); deleteHelper( &s_mixer ); deleteHelper( &s_fxMixer ); deleteHelper( &s_ladspaManager ); //delete configManager::inst(); deleteHelper( &s_projectJournal ); s_mainWindow = NULL; deleteHelper( &s_song ); delete configManager::inst(); } void engine::updateFramesPerTick() { s_framesPerTick = s_mixer->processingSampleRate() * 60.0f * 4 / DefaultTicksPerTact / s_song->getTempo(); } void engine::initPluginFileHandling() { Plugin::DescriptorList pluginDescriptors; Plugin::getDescriptorsOfAvailPlugins( pluginDescriptors ); for( Plugin::DescriptorList::ConstIterator it = pluginDescriptors.begin(); it != pluginDescriptors.end(); ++it ) { if( it->type == Plugin::Instrument ) { const QStringList & ext = QString( it->supportedFileTypes ). split( QChar( ',' ) ); for( QStringList::const_iterator itExt = ext.begin(); itExt != ext.end(); ++itExt ) { s_pluginFileHandling[*itExt] = it->name; } } } } lmms-1.0.0/src/core/FxMixer.cpp0000644000175000017500000001404312313663627015014 0ustar tobytoby/* * FxMixer.cpp - effect mixer for LMMS * * Copyright (c) 2008-2011 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "FxMixer.h" #include "Effect.h" #include "song.h" FxChannel::FxChannel( Model * _parent ) : m_fxChain( NULL ), m_used( false ), m_stillRunning( false ), m_peakLeft( 0.0f ), m_peakRight( 0.0f ), m_buffer( new sampleFrame[engine::mixer()->framesPerPeriod()] ), m_muteModel( false, _parent ), m_volumeModel( 1.0, 0.0, 2.0, 0.01, _parent ), m_name(), m_lock() { engine::mixer()->clearAudioBuffer( m_buffer, engine::mixer()->framesPerPeriod() ); } FxChannel::~FxChannel() { delete[] m_buffer; } FxMixer::FxMixer() : JournallingObject(), Model( NULL ) { for( int i = 0; i < NumFxChannels+1; ++i ) { m_fxChannels[i] = new FxChannel( this ); } // reset name etc. clear(); } FxMixer::~FxMixer() { for( int i = 0; i < NumFxChannels+1; ++i ) { delete m_fxChannels[i]; } } void FxMixer::mixToChannel( const sampleFrame * _buf, fx_ch_t _ch ) { if( m_fxChannels[_ch]->m_muteModel.value() == false ) { m_fxChannels[_ch]->m_lock.lock(); sampleFrame * buf = m_fxChannels[_ch]->m_buffer; for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); ++f ) { buf[f][0] += _buf[f][0]; buf[f][1] += _buf[f][1]; } m_fxChannels[_ch]->m_used = true; m_fxChannels[_ch]->m_lock.unlock(); } } void FxMixer::processChannel( fx_ch_t _ch, sampleFrame * _buf ) { if( m_fxChannels[_ch]->m_muteModel.value() == false && ( m_fxChannels[_ch]->m_used || m_fxChannels[_ch]->m_stillRunning || _ch == 0 ) ) { if( _buf == NULL ) { _buf = m_fxChannels[_ch]->m_buffer; } const fpp_t f = engine::mixer()->framesPerPeriod(); // only start effects if sound was mixed to this FX channel before if( m_fxChannels[_ch]->m_used ) { m_fxChannels[_ch]->m_fxChain.startRunning(); } // process FX chain m_fxChannels[_ch]->m_stillRunning = m_fxChannels[_ch]->m_fxChain.processAudioBuffer( _buf, f, m_fxChannels[_ch]->m_used ); float peakLeft = engine::mixer()->peakValueLeft( _buf, f ) * m_fxChannels[_ch]->m_volumeModel.value(); float peakRight = engine::mixer()->peakValueRight( _buf, f ) * m_fxChannels[_ch]->m_volumeModel.value(); if( peakLeft > m_fxChannels[_ch]->m_peakLeft ) { m_fxChannels[_ch]->m_peakLeft = peakLeft; } if( peakRight > m_fxChannels[_ch]->m_peakRight ) { m_fxChannels[_ch]->m_peakRight = peakRight; } m_fxChannels[_ch]->m_used = true; } else { m_fxChannels[_ch]->m_peakLeft = m_fxChannels[_ch]->m_peakRight = 0.0f; } } void FxMixer::prepareMasterMix() { engine::mixer()->clearAudioBuffer( m_fxChannels[0]->m_buffer, engine::mixer()->framesPerPeriod() ); } void FxMixer::masterMix( sampleFrame * _buf ) { const int fpp = engine::mixer()->framesPerPeriod(); memcpy( _buf, m_fxChannels[0]->m_buffer, sizeof( sampleFrame ) * fpp ); for( int i = 1; i < NumFxChannels+1; ++i ) { if( m_fxChannels[i]->m_used ) { sampleFrame * ch_buf = m_fxChannels[i]->m_buffer; const float v = m_fxChannels[i]->m_volumeModel.value(); for( f_cnt_t f = 0; f < fpp; ++f ) { _buf[f][0] += ch_buf[f][0] * v; _buf[f][1] += ch_buf[f][1] * v; } engine::mixer()->clearAudioBuffer( ch_buf, engine::mixer()->framesPerPeriod() ); m_fxChannels[i]->m_used = false; } } processChannel( 0, _buf ); if( m_fxChannels[0]->m_muteModel.value() ) { engine::mixer()->clearAudioBuffer( _buf, engine::mixer()->framesPerPeriod() ); return; } const float v = m_fxChannels[0]->m_volumeModel.value(); for( f_cnt_t f = 0; f < engine::mixer()->framesPerPeriod(); ++f ) { _buf[f][0] *= v; _buf[f][1] *= v; } m_fxChannels[0]->m_peakLeft *= engine::mixer()->masterGain(); m_fxChannels[0]->m_peakRight *= engine::mixer()->masterGain(); } void FxMixer::clear() { for( int i = 0; i <= NumFxChannels; ++i ) { m_fxChannels[i]->m_fxChain.clear(); m_fxChannels[i]->m_volumeModel.setValue( 1.0f ); m_fxChannels[i]->m_muteModel.setValue( false ); m_fxChannels[i]->m_name = ( i == 0 ) ? tr( "Master" ) : tr( "FX %1" ).arg( i ); m_fxChannels[i]->m_volumeModel.setDisplayName( m_fxChannels[i]->m_name ); } } void FxMixer::saveSettings( QDomDocument & _doc, QDomElement & _this ) { for( int i = 0; i <= NumFxChannels; ++i ) { QDomElement fxch = _doc.createElement( QString( "fxchannel" ) ); _this.appendChild( fxch ); m_fxChannels[i]->m_fxChain.saveState( _doc, fxch ); m_fxChannels[i]->m_volumeModel.saveSettings( _doc, fxch, "volume" ); m_fxChannels[i]->m_muteModel.saveSettings( _doc, fxch, "muted" ); fxch.setAttribute( "num", i ); fxch.setAttribute( "name", m_fxChannels[i]->m_name ); } } void FxMixer::loadSettings( const QDomElement & _this ) { clear(); QDomNode node = _this.firstChild(); for( int i = 0; i <= NumFxChannels; ++i ) { QDomElement fxch = node.toElement(); int num = fxch.attribute( "num" ).toInt(); m_fxChannels[num]->m_fxChain.restoreState( fxch.firstChildElement( m_fxChannels[num]->m_fxChain.nodeName() ) ); m_fxChannels[num]->m_volumeModel.loadSettings( fxch, "volume" ); m_fxChannels[num]->m_muteModel.loadSettings( fxch, "muted" ); m_fxChannels[num]->m_name = fxch.attribute( "name" ); node = node.nextSibling(); } emit dataChanged(); } lmms-1.0.0/src/core/Instrument.cpp0000644000175000017500000000551612313663627015607 0ustar tobytoby/* * Instrument.cpp - base-class for all instrument-plugins (synths, samplers etc) * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "Instrument.h" #include "InstrumentTrack.h" #include "DummyInstrument.h" #include "NotePlayHandle.h" #include "embed.h" #include "engine.h" Instrument::Instrument( InstrumentTrack * _instrument_track, const Descriptor * _descriptor ) : Plugin( _descriptor, NULL/* _instrument_track*/ ), m_instrumentTrack( _instrument_track ) { } Instrument::~Instrument() { } void Instrument::play( sampleFrame * ) { } void Instrument::deleteNotePluginData( NotePlayHandle * ) { } f_cnt_t Instrument::beatLen( NotePlayHandle * ) const { return( 0 ); } Instrument * Instrument::instantiate( const QString & _plugin_name, InstrumentTrack * _instrument_track ) { Plugin * p = Plugin::instantiate( _plugin_name, _instrument_track, _instrument_track ); // check whether instantiated plugin is an instrument if( dynamic_cast( p ) != NULL ) { // everything ok, so return pointer return dynamic_cast( p ); } // not quite... so delete plugin and return dummy instrument delete p; return( new DummyInstrument( _instrument_track ) ); } bool Instrument::isFromTrack( const track * _track ) const { return( m_instrumentTrack == _track ); } void Instrument::applyRelease( sampleFrame * buf, const NotePlayHandle * _n ) { const fpp_t frames = _n->framesLeftForCurrentPeriod(); const fpp_t fpp = engine::mixer()->framesPerPeriod(); const f_cnt_t fl = _n->framesLeft(); if( fl <= desiredReleaseFrames()+fpp ) { for( fpp_t f = (fpp_t)( ( fl > desiredReleaseFrames() ) ? ( qMax( fpp - desiredReleaseFrames(), 0 ) + fl % fpp ) : 0 ); f < frames; ++f ) { const float fac = (float)( fl-f-1 ) / desiredReleaseFrames(); for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { buf[f][ch] *= fac; } } } } QString Instrument::fullDisplayName() const { return instrumentTrack()->displayName(); } lmms-1.0.0/src/core/Piano.cpp0000644000175000017500000000547112313663627014505 0ustar tobytoby/* * Piano.cpp - implementation of piano-widget used in instrument-track-window * for testing + according model class * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ /** \file Piano.cpp * \brief A piano keyboard to play notes on in the instrument plugin window. */ /* * \mainpage Instrument plugin keyboard display classes * * \section introduction Introduction * * \todo fill this out * \todo write isWhite inline function and replace throughout */ #include "Piano.h" #include "InstrumentTrack.h" #include "MidiEvent.h" #include "MidiEventProcessor.h" /*! \brief Create a new keyboard display * * \param _it the InstrumentTrack window to attach to */ Piano::Piano( InstrumentTrack* track ) : Model( NULL ), /*!< base class ctor */ m_instrumentTrack( track ), m_midiEvProc( track ) /*!< the InstrumentTrack Model */ { for( int i = 0; i < NumKeys; ++i ) { m_pressedKeys[i] = false; } } /*! \brief Destroy this new keyboard display * */ Piano::~Piano() { } /*! \brief Turn a key on or off * * \param key the key number to change * \param state the state to set the key to */ void Piano::setKeyState( int key, bool state ) { if( isValidKey( key ) ) { m_pressedKeys[key] = state; emit dataChanged(); } } /*! \brief Handle a note being pressed on our keyboard display * * \param key the key being pressed */ void Piano::handleKeyPress( int key, int midiVelocity ) { if( midiVelocity == -1 ) { midiVelocity = m_instrumentTrack->midiPort()->baseVelocity(); } if( isValidKey( key ) ) { m_midiEvProc->processInEvent( MidiEvent( MidiNoteOn, 0, key, midiVelocity ) ); m_pressedKeys[key] = true; } } /*! \brief Handle a note being released on our keyboard display * * \param key the key being releassed */ void Piano::handleKeyRelease( int key ) { if( isValidKey( key ) ) { m_midiEvProc->processInEvent( MidiEvent( MidiNoteOff, 0, key, 0 ) ); m_pressedKeys[key] = false; } } #include "moc_Piano.cxx" lmms-1.0.0/src/core/AutomatableModel.cpp0000644000175000017500000002347412313663627016661 0ustar tobytoby/* * AutomatableModel.cpp - some implementations of AutomatableModel-class * * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "AutomatableModel.h" #include "AutomationPattern.h" #include "ControllerConnection.h" float AutomatableModel::s_copiedValue = 0; AutomatableModel::AutomatableModel( DataType type, const float val, const float min, const float max, const float step, Model* parent, const QString & displayName, bool defaultConstructed ) : Model( parent, displayName, defaultConstructed ), m_dataType( type ), m_value( val ), m_initValue( val ), m_minValue( min ), m_maxValue( max ), m_step( step ), m_range( max - min ), m_centerValue( m_minValue ), m_journalEntryReady( false ), m_setValueDepth( 0 ), m_hasLinkedModels( false ), m_controllerConnection( NULL ) { setInitValue( val ); } AutomatableModel::~AutomatableModel() { while( m_linkedModels.empty() == false ) { m_linkedModels.last()->unlinkModel( this ); m_linkedModels.erase( m_linkedModels.end() - 1 ); } if( m_controllerConnection ) { delete m_controllerConnection; } emit destroyed( id() ); } bool AutomatableModel::isAutomated() const { return AutomationPattern::isAutomated( this ); } void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name ) { if( isAutomated() ) { QDomElement me = doc.createElement( name ); me.setAttribute( "id", id() ); me.setAttribute( "value", m_value ); element.appendChild( me ); } else { element.setAttribute( name, m_value ); } if( m_controllerConnection ) { QDomElement controllerElement; QDomNode node = element.namedItem( "connection" ); if( node.isElement() ) { controllerElement = node.toElement(); } else { controllerElement = doc.createElement( "connection" ); element.appendChild( controllerElement ); } QDomElement element = doc.createElement( name ); m_controllerConnection->saveSettings( doc, element ); controllerElement.appendChild( element ); } } void AutomatableModel::loadSettings( const QDomElement& element, const QString& name ) { // compat code QDomNode node = element.namedItem( AutomationPattern::classNodeName() ); if( node.isElement() ) { node = node.namedItem( name ); if( node.isElement() ) { AutomationPattern * p = AutomationPattern::globalAutomationPattern( this ); p->loadSettings( node.toElement() ); setValue( p->valueAt( 0 ) ); // in older projects we sometimes have odd automations // with just one value in - eliminate if necessary if( !p->hasAutomation() ) { delete p; } return; } } QDomNode connectionNode = element.namedItem( "connection" ); if( connectionNode.isElement() ) { QDomNode thisConnection = connectionNode.toElement().namedItem( name ); if( thisConnection.isElement() ) { setControllerConnection( new ControllerConnection( (Controller*)NULL ) ); m_controllerConnection->loadSettings( thisConnection.toElement() ); //m_controllerConnection->setTargetName( displayName() ); } } node = element.namedItem( name ); if( node.isElement() ) { changeID( node.toElement().attribute( "id" ).toInt() ); setValue( node.toElement().attribute( "value" ).toFloat() ); } else if( element.hasAttribute( name ) ) { setInitValue( element.attribute( name ).toFloat() ); } else { reset(); } } void AutomatableModel::setValue( const float value ) { ++m_setValueDepth; const float old_val = m_value; m_value = fittedValue( value ); if( old_val != m_value ) { // add changes to history so user can undo it addJournalEntry( JournalEntry( 0, m_value - old_val ) ); // notify linked models for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it ) { if( (*it)->m_setValueDepth < 1 && (*it)->fittedValue( value ) != (*it)->m_value ) { bool journalling = (*it)->testAndSetJournalling( isJournalling() ); (*it)->setValue( value ); (*it)->setJournalling( journalling ); } } emit dataChanged(); } else { emit dataUnchanged(); } --m_setValueDepth; } void AutomatableModel::setAutomatedValue( const float value ) { ++m_setValueDepth; const float oldValue = m_value; m_value = fittedValue( value ); if( oldValue != m_value ) { // notify linked models for( AutoModelVector::Iterator it = m_linkedModels.begin(); it != m_linkedModels.end(); ++it ) { if( (*it)->m_setValueDepth < 1 && !(*it)->fittedValue( m_value ) != (*it)->m_value ) { (*it)->setAutomatedValue( m_value ); } } emit dataChanged(); } --m_setValueDepth; } void AutomatableModel::setRange( const float min, const float max, const float step ) { if( ( m_maxValue != max ) || ( m_minValue != min ) ) { m_minValue = min; m_maxValue = max; if( m_minValue > m_maxValue ) { qSwap( m_minValue, m_maxValue ); } m_range = m_maxValue - m_minValue; setStep( step ); // re-adjust value setValue( value() ); emit propertiesChanged(); } } void AutomatableModel::setStep( const float step ) { if( m_step != step ) { m_step = step; emit propertiesChanged(); } } float AutomatableModel::fittedValue( float value ) const { value = tLimit( value, m_minValue, m_maxValue ); if( m_step != 0 ) { value = nearbyintf( value / m_step ) * m_step; } // correct rounding error at the border if( qAbs( value - m_maxValue ) < typeInfo::minEps() * qAbs( m_step ) ) { value = m_maxValue; } // correct rounding error if value = 0 if( qAbs( value ) < typeInfo::minEps() * qAbs( m_step ) ) { value = 0; } if( value < m_minValue ) { return m_minValue; } else if( value > m_maxValue ) { return m_maxValue; } return value; } void AutomatableModel::redoStep( JournalEntry& je ) { bool journalling = testAndSetJournalling( false ); setValue( value() + (float) je.data().toDouble() ); setJournalling( journalling ); } void AutomatableModel::undoStep( JournalEntry& je ) { JournalEntry inv( je.actionID(), -je.data().toDouble() ); redoStep( inv ); } void AutomatableModel::prepareJournalEntryFromOldVal() { m_oldValue = value(); saveJournallingState( false ); m_journalEntryReady = true; } void AutomatableModel::addJournalEntryFromOldToCurVal() { if( m_journalEntryReady ) { restoreJournallingState(); if( value() != m_oldValue ) { addJournalEntry( JournalEntry( 0, value() - m_oldValue ) ); } m_journalEntryReady = false; } } void AutomatableModel::linkModel( AutomatableModel* model ) { if( !m_linkedModels.contains( model ) ) { m_linkedModels.push_back( model ); m_hasLinkedModels = true; if( !model->hasLinkedModels() ) { QObject::connect( this, SIGNAL( dataChanged() ), model, SIGNAL( dataChanged() ) ); } } } void AutomatableModel::unlinkModel( AutomatableModel* model ) { AutoModelVector::Iterator it = qFind( m_linkedModels.begin(), m_linkedModels.end(), model ); if( it != m_linkedModels.end() ) { m_linkedModels.erase( it ); } m_hasLinkedModels = !m_linkedModels.isEmpty(); } void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) { model1->linkModel( model2 ); model2->linkModel( model1 ); } void AutomatableModel::unlinkModels( AutomatableModel* model1, AutomatableModel* model2 ) { model1->unlinkModel( model2 ); model2->unlinkModel( model1 ); } void AutomatableModel::unlinkAllModels() { foreach( AutomatableModel* model, m_linkedModels ) { unlinkModels( this, model ); } m_hasLinkedModels = false; } void AutomatableModel::setControllerConnection( ControllerConnection* c ) { m_controllerConnection = c; if( c ) { QObject::connect( m_controllerConnection, SIGNAL( valueChanged() ), this, SIGNAL( dataChanged() ) ); QObject::connect( m_controllerConnection, SIGNAL( destroyed() ), this, SLOT( unlinkControllerConnection() ) ); emit dataChanged(); } } float AutomatableModel::controllerValue( int frameOffset ) const { if( m_controllerConnection ) { const float v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); if( typeInfo::isEqual( m_step, 1 ) ) { return qRound( v ); } return v; } AutomatableModel* lm = m_linkedModels.first(); if( lm->controllerConnection() ) { return lm->controllerValue( frameOffset ); } return lm->m_value; } void AutomatableModel::unlinkControllerConnection() { if( m_controllerConnection ) { m_controllerConnection->disconnect( this ); } m_controllerConnection = NULL; } void AutomatableModel::setInitValue( const float value ) { m_initValue = fittedValue( value ); bool journalling = testAndSetJournalling( false ); setValue( value ); setJournalling( journalling ); emit initValueChanged( value ); } void AutomatableModel::reset() { setValue( initValue() ); } void AutomatableModel::copyValue() { s_copiedValue = value(); } void AutomatableModel::pasteValue() { setValue( copiedValue() ); } #include "moc_AutomatableModel.cxx" lmms-1.0.0/src/core/Controller.cpp0000644000175000017500000001372612313663627015564 0ustar tobytoby/* * Controller.cpp - implementation of class controller which handles * remote-control of AutomatableModels * * Copyright (c) 2008 Paul Giblock * Copyright (c) 2014 Lukas W * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "song.h" #include "engine.h" #include "Mixer.h" #include "Controller.h" #include "ControllerConnection.h" #include "ControllerDialog.h" #include "LfoController.h" #include "MidiController.h" #include "PeakController.h" unsigned int Controller::s_frames = 0; QVector Controller::s_controllers; Controller::Controller( ControllerTypes _type, Model * _parent, const QString & _display_name ) : Model( _parent, _display_name ), JournallingObject(), m_connectionCount( 0 ), m_type( _type ) { if( _type != DummyController && _type != MidiController ) { s_controllers.append( this ); // Determine which name to use for ( uint i=s_controllers.size(); ; i++ ) { QString new_name = QString( tr( "Controller %1" ) ) .arg( i ); // Check if name is already in use bool name_used = false; QVector::const_iterator it; for ( it = s_controllers.constBegin(); it != s_controllers.constEnd(); ++it ) { if ( (*it)->name() == new_name ) { name_used = true; break; } } if ( ! name_used ) { m_name = new_name; break; } } } } Controller::~Controller() { int idx = s_controllers.indexOf( this ); if( idx >= 0 ) { s_controllers.remove( idx ); } if( engine::getSong() ) { engine::getSong()->removeController( this ); } // Remove connections by destroyed signal } // Get current value, with an offset into the current buffer for sample exactness float Controller::currentValue( int _offset ) { if( _offset == 0 || isSampleExact() ) { m_currentValue = fittedValue( value( _offset ) ); } return m_currentValue; } float Controller::value( int _offset ) { return 0.5f; } // Get position in frames unsigned int Controller::runningFrames() { return s_frames; } // Get position in seconds float Controller::runningTime() { return s_frames / engine::mixer()->processingSampleRate(); } void Controller::triggerFrameCounter() { for( int i = 0; i < s_controllers.size(); ++i ) { // This signal is for updating values for both stubborn knobs and for // painting. If we ever get all the widgets to use or at least check // currentValue() then we can throttle the signal and only use it for // GUI. emit s_controllers.at(i)->valueChanged(); } s_frames += engine::mixer()->framesPerPeriod(); //emit s_signaler.triggerValueChanged(); } void Controller::resetFrameCounter() { s_frames = 0; } Controller * Controller::create( ControllerTypes _ct, Model * _parent ) { static Controller * dummy = NULL; Controller * c = NULL; switch( _ct ) { case Controller::DummyController: if( dummy ) c = dummy; else c = new Controller( DummyController, NULL, QString() ); break; case Controller::LfoController: c = new ::LfoController( _parent ); break; case Controller::PeakController: //Already instantiated in EffectChain::loadSettings() Q_ASSERT( false ); break; case Controller::MidiController: c = new ::MidiController( _parent ); break; default: break; } return( c ); } Controller * Controller::create( const QDomElement & _this, Model * _parent ) { Controller * c; if( _this.attribute( "type" ).toInt() == Controller::PeakController ) { c = PeakController::getControllerBySetting( _this ); } else { c = create( static_cast( _this.attribute( "type" ).toInt() ), _parent ); } if( c != NULL ) { c->restoreState( _this ); } return( c ); } bool Controller::hasModel( const Model * m ) { QObjectList chldren = children(); for( int i = 0; i < chldren.size(); ++i ) { QObject * c = chldren.at(i); AutomatableModel * am = qobject_cast(c); if( am != NULL ) { if( am == m ) { return true; } ControllerConnection * cc = am->controllerConnection(); if( cc != NULL ) { if( cc->getController()->hasModel( m ) ) { return true; } } } } return false; } void Controller::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "type", type() ); _this.setAttribute( "name", name() ); } void Controller::loadSettings( const QDomElement & _this ) { if( _this.attribute( "type" ).toInt() != type() ) { qWarning( "controller-type does not match controller-type of " "settings-node!\n" ); } setName( _this.attribute( "name" ) ); } QString Controller::nodeName() const { return( "Controller" ); } ControllerDialog * Controller::createDialog( QWidget * _parent ) { ControllerDialog * d = new ControllerDialog( this, _parent ); return d; } void Controller::addConnection( ControllerConnection * ) { m_connectionCount++; } void Controller::removeConnection( ControllerConnection * ) { m_connectionCount--; Q_ASSERT( m_connectionCount >= 0 ); } int Controller::connectionCount() const{ return m_connectionCount; } #include "moc_Controller.cxx" lmms-1.0.0/src/core/InstrumentFunctions.cpp0000644000175000017500000005204412313663627017476 0ustar tobytoby/* * InstrumentFunctions.cpp - models for instrument-function-tab * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "InstrumentFunctions.h" #include "embed.h" #include "engine.h" #include "InstrumentTrack.h" #include "NotePlayHandle.h" #include "PresetPreviewPlayHandle.h" InstrumentFunctionNoteStacking::ChordTable::Init InstrumentFunctionNoteStacking::ChordTable::s_initTable[] = { { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "octave" ), { 0, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Major" ), { 0, 4, 7, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Majb5" ), { 0, 4, 6, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "minor" ), { 0, 3, 7, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "minb5" ), { 0, 3, 6, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "sus2" ), { 0, 2, 7, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "sus4" ), { 0, 5, 7, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "aug" ), { 0, 4, 8, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "augsus4" ), { 0, 5, 8, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "tri" ), { 0, 3, 6, 9, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "6" ), { 0, 4, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "6sus4" ), { 0, 5, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "6add9" ), { 0, 4, 7, 9, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m6" ), { 0, 3, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m6add9" ), { 0, 3, 7, 9, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7" ), { 0, 4, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7sus4" ), { 0, 5, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7#5" ), { 0, 4, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7b5" ), { 0, 4, 6, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7#9" ), { 0, 4, 7, 10, 15, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7b9" ), { 0, 4, 7, 10, 13, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7#5#9" ), { 0, 4, 8, 10, 15, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7#5b9" ), { 0, 4, 8, 10, 13, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7b5b9" ), { 0, 4, 6, 10, 13, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7add11" ), { 0, 4, 7, 10, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7add13" ), { 0, 4, 7, 10, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "7#11" ), { 0, 4, 7, 10, 18, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj7" ), { 0, 4, 7, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj7b5" ), { 0, 4, 6, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj7#5" ), { 0, 4, 8, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj7#11" ), { 0, 4, 7, 11, 18, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj7add13" ), { 0, 4, 7, 11, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m7" ), { 0, 3, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m7b5" ), { 0, 3, 6, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m7b9" ), { 0, 3, 7, 10, 13, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m7add11" ), { 0, 3, 7, 10, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m7add13" ), { 0, 3, 7, 10, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m-Maj7" ), { 0, 3, 7, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m-Maj7add11" ), { 0, 3, 7, 11, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m-Maj7add13" ), { 0, 3, 7, 11, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "9" ), { 0, 4, 7, 10, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "9sus4" ), { 0, 5, 7, 10, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "add9" ), { 0, 4, 7, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "9#5" ), { 0, 4, 8, 10, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "9b5" ), { 0, 4, 6, 10, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "9#11" ), { 0, 4, 7, 10, 14, 18, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "9b13" ), { 0, 4, 7, 10, 14, 20, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj9" ), { 0, 4, 7, 11, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj9sus4" ), { 0, 5, 7, 11, 15, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj9#5" ), { 0, 4, 8, 11, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj9#11" ), { 0, 4, 7, 11, 14, 18, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m9" ), { 0, 3, 7, 10, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "madd9" ), { 0, 3, 7, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m9b5" ), { 0, 3, 6, 10, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m9-Maj7" ), { 0, 3, 7, 11, 14, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "11" ), { 0, 4, 7, 10, 14, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "11b9" ), { 0, 4, 7, 10, 13, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj11" ), { 0, 4, 7, 11, 14, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m11" ), { 0, 3, 7, 10, 14, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m-Maj11" ), { 0, 3, 7, 11, 14, 17, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "13" ), { 0, 4, 7, 10, 14, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "13#9" ), { 0, 4, 7, 10, 15, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "13b9" ), { 0, 4, 7, 10, 13, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "13b5b9" ), { 0, 4, 6, 10, 13, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Maj13" ), { 0, 4, 7, 11, 14, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m13" ), { 0, 3, 7, 10, 14, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "m-Maj13" ), { 0, 3, 7, 11, 14, 21, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Major" ), { 0, 2, 4, 5, 7, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Harmonic minor" ), { 0, 2, 3, 5, 7, 8, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Melodic minor" ), { 0, 2, 3, 5, 7, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Whole tone" ), { 0, 2, 4, 6, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Diminished" ), { 0, 2, 3, 5, 6, 8, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Major pentatonic" ), { 0, 2, 4, 7, 9, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Minor pentatonic" ), { 0, 3, 5, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Jap in sen" ), { 0, 1, 5, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Major bebop" ), { 0, 2, 4, 5, 7, 8, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Dominant bebop" ), { 0, 2, 4, 5, 7, 9, 10, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Blues" ), { 0, 3, 5, 6, 7, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Arabic" ), { 0, 1, 4, 5, 7, 8, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Enigmatic" ), { 0, 1, 4, 6, 8, 10, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Neopolitan" ), { 0, 1, 3, 5, 7, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Neopolitan minor" ), { 0, 1, 3, 5, 7, 8, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Hungarian minor" ), { 0, 2, 3, 6, 7, 8, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Dorian" ), { 0, 2, 3, 5, 7, 9, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Phrygolydian" ), { 0, 1, 3, 5, 7, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Lydian" ), { 0, 2, 4, 6, 7, 9, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Mixolydian" ), { 0, 2, 4, 5, 7, 9, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Aeolian" ), { 0, 2, 3, 5, 7, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Locrian" ), { 0, 1, 3, 5, 6, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Minor" ), { 0, 2, 3, 5, 7, 8, 10, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Chromatic" ), { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, -1 } }, { QT_TRANSLATE_NOOP( "InstrumentFunctionNoteStacking", "Half-Whole Diminished" ), { 0, 1, 3, 4, 6, 7, 9, 10, -1 } }, } ; InstrumentFunctionNoteStacking::Chord::Chord( const char * n, const ChordSemiTones & semi_tones ) : m_name( InstrumentFunctionNoteStacking::tr( n ) ) { for( m_size = 0; m_size < MAX_CHORD_POLYPHONY; m_size++ ) { if( semi_tones[m_size] == -1 ) { break; } m_semiTones[m_size] = semi_tones[m_size]; } } bool InstrumentFunctionNoteStacking::Chord::hasSemiTone( int8_t semi_tone ) const { for( int i = 0; i < size(); ++i ) { if( semi_tone == m_semiTones[i] ) { return true; } } return false; } InstrumentFunctionNoteStacking::ChordTable::ChordTable() : QVector() { for( int i = 0; i < static_cast( sizeof s_initTable / sizeof *s_initTable ); i++ ) { push_back( Chord( s_initTable[i].m_name, s_initTable[i].m_semiTones ) ); } } const InstrumentFunctionNoteStacking::Chord & InstrumentFunctionNoteStacking::ChordTable::getByName( const QString & name, bool is_scale ) const { for( int i = 0; i < size(); i++ ) { if( at( i ).getName() == name && is_scale == at( i ).isScale() ) return at( i ); } static Chord empty; return empty; } InstrumentFunctionNoteStacking::InstrumentFunctionNoteStacking( Model * _parent ) : Model( _parent, tr( "Chords" ) ), m_chordsEnabledModel( false, this ), m_chordsModel( this, tr( "Chord type" ) ), m_chordRangeModel( 1.0f, 1.0f, 9.0f, 1.0f, this, tr( "Chord range" ) ) { const ChordTable & chord_table = ChordTable::getInstance(); for( int i = 0; i < chord_table.size(); ++i ) { m_chordsModel.addItem( chord_table[i].getName() ); } } InstrumentFunctionNoteStacking::~InstrumentFunctionNoteStacking() { } void InstrumentFunctionNoteStacking::processNote( NotePlayHandle * _n ) { const int base_note_key = _n->key(); const ChordTable & chord_table = ChordTable::getInstance(); // we add chord-subnotes to note if either note is a base-note and // arpeggio is not used or note is part of an arpeggio // at the same time we only add sub-notes if nothing of the note was // played yet, because otherwise we would add chord-subnotes every // time an audio-buffer is rendered... if( ( ( _n->isTopNote() && _n->instrumentTrack()->isArpeggioEnabled() == false ) || _n->isPartOfArpeggio() ) && _n->totalFramesPlayed() == 0 && m_chordsEnabledModel.value() == true ) { // then insert sub-notes for chord const int selected_chord = m_chordsModel.value(); for( int octave_cnt = 0; octave_cnt < m_chordRangeModel.value(); ++octave_cnt ) { const int sub_note_key_base = base_note_key + octave_cnt * KeysPerOctave; // if octave_cnt == 1 we're in the first octave and // the base-note is already done, so we don't have to // create it in the following loop, then we loop until // there's a -1 in the interval-array for( int i = ( octave_cnt == 0 ) ? 1 : 0; i < chord_table[selected_chord].size(); ++i ) { // add interval to sub-note-key const int sub_note_key = sub_note_key_base + (int) chord_table[ selected_chord][i]; // maybe we're out of range -> let's get outta // here! if( sub_note_key > NumKeys ) { break; } // create copy of base-note note note_copy( _n->length(), 0, sub_note_key, _n->getVolume(), _n->getPanning(), _n->detuning() ); // create sub-note-play-handle, only note is // different new NotePlayHandle( _n->instrumentTrack(), _n->offset(), _n->frames(), note_copy, _n ); } } } } void InstrumentFunctionNoteStacking::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_chordsEnabledModel.saveSettings( _doc, _this, "chord-enabled" ); m_chordsModel.saveSettings( _doc, _this, "chord" ); m_chordRangeModel.saveSettings( _doc, _this, "chordrange" ); } void InstrumentFunctionNoteStacking::loadSettings( const QDomElement & _this ) { m_chordsEnabledModel.loadSettings( _this, "chord-enabled" ); m_chordsModel.loadSettings( _this, "chord" ); m_chordRangeModel.loadSettings( _this, "chordrange" ); } InstrumentFunctionArpeggio::InstrumentFunctionArpeggio( Model * _parent ) : Model( _parent, tr( "Arpeggio" ) ), m_arpEnabledModel( false ), m_arpModel( this, tr( "Arpeggio type" ) ), m_arpRangeModel( 1.0f, 1.0f, 9.0f, 1.0f, this, tr( "Arpeggio range" ) ), m_arpTimeModel( 100.0f, 25.0f, 2000.0f, 1.0f, 2000, this, tr( "Arpeggio time" ) ), m_arpGateModel( 100.0f, 1.0f, 200.0f, 1.0f, this, tr( "Arpeggio gate" ) ), m_arpDirectionModel( this, tr( "Arpeggio direction" ) ), m_arpModeModel( this, tr( "Arpeggio mode" ) ) { const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance(); for( int i = 0; i < chord_table.size(); ++i ) { m_arpModel.addItem( chord_table[i].getName() ); } m_arpDirectionModel.addItem( tr( "Up" ), new PixmapLoader( "arp_up" ) ); m_arpDirectionModel.addItem( tr( "Down" ), new PixmapLoader( "arp_down" ) ); m_arpDirectionModel.addItem( tr( "Up and down" ), new PixmapLoader( "arp_up_and_down" ) ); m_arpDirectionModel.addItem( tr( "Random" ), new PixmapLoader( "arp_random" ) ); m_arpDirectionModel.setInitValue( ArpDirUp ); m_arpModeModel.addItem( tr( "Free" ), new PixmapLoader( "arp_free" ) ); m_arpModeModel.addItem( tr( "Sort" ), new PixmapLoader( "arp_sort" ) ); m_arpModeModel.addItem( tr( "Sync" ), new PixmapLoader( "arp_sync" ) ); } InstrumentFunctionArpeggio::~InstrumentFunctionArpeggio() { } void InstrumentFunctionArpeggio::processNote( NotePlayHandle * _n ) { const int base_note_key = _n->key(); if( _n->isTopNote() == false || !m_arpEnabledModel.value() || ( _n->isReleased() && _n->releaseFramesDone() >= _n->actualReleaseFramesToDo() ) ) { return; } const int selected_arp = m_arpModel.value(); ConstNotePlayHandleList cnphv = NotePlayHandle::nphsOfInstrumentTrack( _n->instrumentTrack() ); if( m_arpModeModel.value() != FreeMode && cnphv.size() == 0 ) { // maybe we're playing only a preset-preview-note? cnphv = PresetPreviewPlayHandle::nphsOfInstrumentTrack( _n->instrumentTrack() ); if( cnphv.size() == 0 ) { // still nothing found here, so lets return //return; cnphv.push_back( _n ); } } const InstrumentFunctionNoteStacking::ChordTable & chord_table = InstrumentFunctionNoteStacking::ChordTable::getInstance(); const int cur_chord_size = chord_table[selected_arp].size(); const int range = (int)( cur_chord_size * m_arpRangeModel.value() ); const int total_range = range * cnphv.size(); // number of frames that every note should be played const f_cnt_t arp_frames = (f_cnt_t)( m_arpTimeModel.value() / 1000.0f * engine::mixer()->processingSampleRate() ); const f_cnt_t gated_frames = (f_cnt_t)( m_arpGateModel.value() * arp_frames / 100.0f ); // used for calculating remaining frames for arp-note, we have to add // arp_frames-1, otherwise the first arp-note will not be setup // correctly... -> arp_frames frames silence at the start of every note! int cur_frame = ( ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->totalFramesPlayed() : _n->totalFramesPlayed() ) + arp_frames - 1; // used for loop f_cnt_t frames_processed = 0; while( frames_processed < engine::mixer()->framesPerPeriod() ) { const f_cnt_t remaining_frames_for_cur_arp = arp_frames - ( cur_frame % arp_frames ); // does current arp-note fill whole audio-buffer? if( remaining_frames_for_cur_arp > engine::mixer()->framesPerPeriod() ) { // then we don't have to do something! break; } frames_processed += remaining_frames_for_cur_arp; // init with zero int cur_arp_idx = 0; // in sorted mode: is it our turn or do we have to be quiet for // now? if( m_arpModeModel.value() == SortMode && ( ( cur_frame / arp_frames ) % total_range ) / range != (f_cnt_t) _n->index() ) { // update counters frames_processed += arp_frames; cur_frame += arp_frames; continue; } const int dir = m_arpDirectionModel.value(); // process according to arpeggio-direction... if( dir == ArpDirUp ) { cur_arp_idx = ( cur_frame / arp_frames ) % range; } else if( dir == ArpDirDown ) { cur_arp_idx = range - ( cur_frame / arp_frames ) % range - 1; } else if( dir == ArpDirUpAndDown && range > 1 ) { // imagine, we had to play the arp once up and then // once down -> makes 2 * range possible notes... // because we don't play the lower and upper notes // twice, we have to subtract 2 cur_arp_idx = ( cur_frame / arp_frames ) % ( range * 2 - 2 ); // if greater than range, we have to play down... // looks like the code for arp_dir==DOWN... :) if( cur_arp_idx >= range ) { cur_arp_idx = range - cur_arp_idx % ( range - 1 ) - 1; } } else if( dir == ArpDirRandom ) { // just pick a random chord-index cur_arp_idx = (int)( range * ( (float) rand() / (float) RAND_MAX ) ); } // now calculate final key for our arp-note const int sub_note_key = base_note_key + (cur_arp_idx / cur_chord_size ) * KeysPerOctave + chord_table[selected_arp][cur_arp_idx % cur_chord_size]; // range-checking if( sub_note_key >= NumKeys || sub_note_key < 0 || engine::mixer()->criticalXRuns() ) { continue; } float vol_level = 1.0f; if( _n->isReleased() ) { vol_level = _n->volumeLevel( cur_frame + gated_frames ); } // create new arp-note note new_note( MidiTime( 0 ), MidiTime( 0 ), sub_note_key, (volume_t) qRound( _n->getVolume() * vol_level ), _n->getPanning(), _n->detuning() ); // create sub-note-play-handle, only ptr to note is different // and is_arp_note=true new NotePlayHandle( _n->instrumentTrack(), ( ( m_arpModeModel.value() != FreeMode ) ? cnphv.first()->offset() : _n->offset() ) + frames_processed, gated_frames, new_note, _n, true ); // update counters frames_processed += arp_frames; cur_frame += arp_frames; } // make sure, note is handled as arp-base-note, even if we didn't add a // sub-note so far if( m_arpModeModel.value() != FreeMode ) { _n->setPartOfArpeggio( true ); } } void InstrumentFunctionArpeggio::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_arpEnabledModel.saveSettings( _doc, _this, "arp-enabled" ); m_arpModel.saveSettings( _doc, _this, "arp" ); m_arpRangeModel.saveSettings( _doc, _this, "arprange" ); m_arpTimeModel.saveSettings( _doc, _this, "arptime" ); m_arpGateModel.saveSettings( _doc, _this, "arpgate" ); m_arpDirectionModel.saveSettings( _doc, _this, "arpdir" ); m_arpModeModel.saveSettings( _doc, _this, "arpmode" ); } void InstrumentFunctionArpeggio::loadSettings( const QDomElement & _this ) { m_arpEnabledModel.loadSettings( _this, "arp-enabled" ); m_arpModel.loadSettings( _this, "arp" ); m_arpRangeModel.loadSettings( _this, "arprange" ); m_arpTimeModel.loadSettings( _this, "arptime" ); m_arpGateModel.loadSettings( _this, "arpgate" ); m_arpDirectionModel.loadSettings( _this, "arpdir" ); /* // Keep compatibility with version 0.2.1 file format if( _this.hasAttribute( "arpsyncmode" ) ) { m_arpTimeKnob->setSyncMode( ( tempoSyncKnob::tempoSyncMode ) _this.attribute( "arpsyncmode" ).toInt() ); }*/ m_arpModeModel.loadSettings( _this, "arpmode" ); } #include "moc_InstrumentFunctions.cxx" lmms-1.0.0/src/core/ControllerConnection.cpp0000644000175000017500000001204412313663627017574 0ustar tobytoby/* * ControllerConnection.cpp - implementation of class controller connection * which handles the link between AutomatableModels and controllers * * Copyright (c) 2008 Paul Giblock * Copyright (c) 2010 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "song.h" #include "engine.h" #include "Mixer.h" #include "ControllerConnection.h" ControllerConnectionVector ControllerConnection::s_connections; ControllerConnection::ControllerConnection( Controller * _controller ) : m_controller( NULL ), m_controllerId( -1 ), m_ownsController( false ) { if( _controller != NULL ) { setController( _controller ); } else { m_controller = Controller::create( Controller::DummyController, NULL ); } s_connections.append( this ); } ControllerConnection::ControllerConnection( int _controllerId ) : m_controller( Controller::create( Controller::DummyController, NULL ) ), m_controllerId( _controllerId ), m_ownsController( false ) { s_connections.append( this ); } ControllerConnection::~ControllerConnection() { if( m_controller && m_controller->type() != Controller::DummyController ) { m_controller->removeConnection( this ); } s_connections.remove( s_connections.indexOf( this ) ); if( m_ownsController ) { delete m_controller; } } void ControllerConnection::setController( int /*_controllerId*/ ) { } void ControllerConnection::setController( Controller * _controller ) { if( m_ownsController && m_controller ) { delete m_controller; m_controller = NULL; } if( m_controller && m_controller->type() != Controller::DummyController ) { m_controller->removeConnection( this ); } if( !_controller ) { m_controller = Controller::create( Controller::DummyController, NULL ); } else { m_controller = _controller; } m_controllerId = -1; if( _controller->type() != Controller::DummyController ) { _controller->addConnection( this ); QObject::connect( _controller, SIGNAL( valueChanged() ), this, SIGNAL( valueChanged() ) ); } m_ownsController = ( _controller->type() == Controller::MidiController ); // If we don't own the controller, allow deletion of controller // to delete the connection if( !m_ownsController ) { QObject::connect( _controller, SIGNAL( destroyed() ), this, SLOT( deleteConnection() ) ); } } inline void ControllerConnection::setTargetName( const QString & _name ) { m_targetName = _name; if( m_controller ) { // m_controller->getMidiPort()->setName( _name ); } } /* * A connection may not be finalized. This means, the connection should exist, * but the controller does not yet exist. This happens when loading. Even * loading connections last won't help, since there can be connections BETWEEN * controllers. So, we remember the controller-ID and use a dummyController * instead. Once the song is loaded, finalizeConnections() connects to the proper controllers */ void ControllerConnection::finalizeConnections() { for( int i = 0; i < s_connections.size(); ++i ) { ControllerConnection * c = s_connections[i]; if ( !c->isFinalized() && c->m_controllerId < engine::getSong()->controllers().size() ) { c->setController( engine::getSong()-> controllers().at( c->m_controllerId ) ); } } } void ControllerConnection::saveSettings( QDomDocument & _doc, QDomElement & _this ) { if( engine::getSong() ) { if( m_ownsController ) { m_controller->saveState( _doc, _this ); } else { int id = engine::getSong()->controllers().indexOf( m_controller ); if( id >= 0 ) { _this.setAttribute( "id", id ); } } } } void ControllerConnection::loadSettings( const QDomElement & _this ) { QDomNode node = _this.firstChild(); if( !node.isNull() ) { setController( Controller::create( node.toElement(), engine::getSong() ) ); } else { if( _this.attribute( "id" ).toInt() >= 0 ) { m_controllerId = _this.attribute( "id" ).toInt(); } else { qWarning( "controller index invalid\n" ); m_controllerId = -1; } m_controller = Controller::create( Controller::DummyController, NULL ); } } void ControllerConnection::deleteConnection() { delete this; } #include "moc_ControllerConnection.cxx" lmms-1.0.0/src/core/Plugin.cpp0000644000175000017500000001172112313663627014670 0ustar tobytoby#ifndef SINGLE_SOURCE_COMPILE /* * Plugin.cpp - implementation of plugin-class including plugin-loader * * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include "Plugin.h" #include "embed.h" #include "engine.h" #include "Mixer.h" #include "config_mgr.h" #include "DummyPlugin.h" #include "AutomatableModel.h" static PixmapLoader __dummy_loader; static Plugin::Descriptor dummy_plugin_descriptor = { "dummy", "dummy", QT_TRANSLATE_NOOP( "pluginBrowser", "no description" ), "Tobias Doerffel ", 0x0100, Plugin::Undefined, &__dummy_loader, NULL } ; Plugin::Plugin( const Descriptor * _descriptor, Model * _parent ) : JournallingObject(), Model( _parent ), m_descriptor( _descriptor ) { if( m_descriptor == NULL ) { m_descriptor = &dummy_plugin_descriptor; } } Plugin::~Plugin() { } void Plugin::loadFile( const QString & ) { } AutomatableModel * Plugin::childModel( const QString & ) { static FloatModel fm; return &fm; } Plugin * Plugin::instantiate( const QString & _plugin_name, Model * _parent, void * _data ) { QLibrary plugin_lib( configManager::inst()->pluginDir() + _plugin_name ); if( plugin_lib.load() == false ) { if( engine::hasGUI() ) { QMessageBox::information( NULL, tr( "Plugin not found" ), tr( "The plugin \"%1\" wasn't found " "or could not be loaded!\n" "Reason: \"%2\"" ).arg( _plugin_name ). arg( plugin_lib.errorString() ), QMessageBox::Ok | QMessageBox::Default ); } return new DummyPlugin(); } instantiationHook inst_hook = ( instantiationHook ) plugin_lib.resolve( "lmms_plugin_main" ); if( inst_hook == NULL ) { if( engine::hasGUI() ) { QMessageBox::information( NULL, tr( "Error while loading plugin" ), tr( "Failed to load plugin \"%1\"!" ).arg( _plugin_name ), QMessageBox::Ok | QMessageBox::Default ); } return new DummyPlugin(); } Plugin * inst = inst_hook( _parent, _data ); return inst; } void Plugin::getDescriptorsOfAvailPlugins( DescriptorList & _plugin_descs ) { QDir directory( configManager::inst()->pluginDir() ); #ifdef LMMS_BUILD_WIN32 QFileInfoList list = directory.entryInfoList( QStringList( "*.dll" ) ); #else QFileInfoList list = directory.entryInfoList( QStringList( "lib*.so" ) ); #endif foreach( const QFileInfo & f, list ) { QLibrary( f.absoluteFilePath() ).load(); } foreach( const QFileInfo & f, list ) { QLibrary plugin_lib( f.absoluteFilePath() ); if( plugin_lib.load() == false || plugin_lib.resolve( "lmms_plugin_main" ) == NULL ) { continue; } QString desc_name = f.fileName().section( '.', 0, 0 ) + "_plugin_descriptor"; if( desc_name.left( 3 ) == "lib" ) { desc_name = desc_name.mid( 3 ); } Descriptor * plugin_desc = (Descriptor *) plugin_lib.resolve( desc_name.toUtf8().constData() ); if( plugin_desc == NULL ) { printf( "LMMS plugin %s does not have a " "plugin descriptor named %s!\n", f.absoluteFilePath().toUtf8().constData(), desc_name.toUtf8().constData() ); continue; } _plugin_descs.push_back( *plugin_desc ); } } PluginView * Plugin::createView( QWidget * _parent ) { PluginView * pv = instantiateView( _parent ); if( pv != NULL ) { pv->setModel( this ); } return pv; } Plugin::Descriptor::SubPluginFeatures::Key::Key( const QDomElement & _key ) : desc( NULL ), name( _key.attribute( "key" ) ), attributes() { QDomNodeList l = _key.elementsByTagName( "attribute" ); for( int i = 0; !l.item( i ).isNull(); ++i ) { QDomElement e = l.item( i ).toElement(); attributes[e.attribute( "name" )] = e.attribute( "value" ); } } QDomElement Plugin::Descriptor::SubPluginFeatures::Key::saveXML( QDomDocument & _doc ) const { QDomElement e = _doc.createElement( "key" ); for( AttributeMap::ConstIterator it = attributes.begin(); it != attributes.end(); ++it ) { QDomElement a = _doc.createElement( "attribute" ); a.setAttribute( "name", it.key() ); a.setAttribute( "value", it.value() ); e.appendChild( a ); } return e; } #endif lmms-1.0.0/src/core/project_version.cpp0000644000175000017500000000356112313663627016650 0ustar tobytoby/* * project_version.cpp - compare versions in import upgrades * * Copyright (c) 2007 Javier Serrano Polo * Copyright (c) 2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "project_version.h" int projectVersion::compare( const projectVersion & _v1, const projectVersion & _v2 ) { int n1, n2; // Major n1 = _v1.section( '.', 0, 0 ).toInt(); n2 = _v2.section( '.', 0, 0 ).toInt(); if( n1 != n2 ) { return n1 - n2; } // Minor n1 = _v1.section( '.', 1, 1 ).toInt(); n2 = _v2.section( '.', 1, 1 ).toInt(); if( n1 != n2 ) { return n1 - n2; } // Release n1 = _v1.section( '.', 2 ).section( '-', 0, 0 ).toInt(); n2 = _v2.section( '.', 2 ).section( '-', 0, 0 ).toInt(); if( n1 != n2 ) { return n1 - n2; } // Build const QString b1 = _v1.section( '.', 2 ).section( '-', 1 ); const QString b2 = _v2.section( '.', 2 ).section( '-', 1 ); // make sure 0.x.y > 0.x.y-patch if( b1.isEmpty() ) { return 1; } if( b2.isEmpty() ) { return -1; } return QString::compare( b1, b2 ); } lmms-1.0.0/src/core/ImportFilter.cpp0000644000175000017500000000600212313663627016046 0ustar tobytoby/* * ImportFilter.cpp - base-class for all import-filters (MIDI, FLP etc) * * Copyright (c) 2006-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "ImportFilter.h" #include "engine.h" #include "TrackContainer.h" #include "ProjectJournal.h" ImportFilter::ImportFilter( const QString & _file_name, const Descriptor * _descriptor ) : Plugin( _descriptor, NULL ), m_file( _file_name ) { } ImportFilter::~ImportFilter() { } void ImportFilter::import( const QString & _file_to_import, TrackContainer* tc ) { DescriptorList d; Plugin::getDescriptorsOfAvailPlugins( d ); bool successful = false; char * s = qstrdup( _file_to_import.toUtf8().constData() ); // do not record changes while importing files const bool j = engine::projectJournal()->isJournalling(); engine::projectJournal()->setJournalling( false ); for( Plugin::DescriptorList::ConstIterator it = d.begin(); it != d.end(); ++it ) { if( it->type == Plugin::ImportFilter ) { Plugin * p = Plugin::instantiate( it->name, NULL, s ); if( dynamic_cast( p ) != NULL && dynamic_cast( p )->tryImport( tc ) == true ) { delete p; successful = true; break; } delete p; } } engine::projectJournal()->setJournalling( j ); delete[] s; if( successful == false ) { QMessageBox::information( NULL, TrackContainer::tr( "Couldn't import file" ), TrackContainer::tr( "Couldn't find a filter for " "importing file %1.\n" "You should convert this file " "into a format supported by " "LMMS using another software." ).arg( _file_to_import ), QMessageBox::Ok, QMessageBox::NoButton ); } } bool ImportFilter::openFile() { if( m_file.open( QFile::ReadOnly ) == false ) { QMessageBox::critical( NULL, TrackContainer::tr( "Couldn't open file" ), TrackContainer::tr( "Couldn't open file %1 " "for reading.\nPlease make " "sure you have read-" "permission to the file and " "the directory containing the " "file and try again!" ).arg( m_file.fileName() ), QMessageBox::Ok, QMessageBox::NoButton ); return false; } return true; } lmms-1.0.0/src/core/ladspa_manager.cpp0000644000175000017500000005440012313663627016371 0ustar tobytoby/* * ladspa_manager.cpp - a class to manage loading and instantiation * of ladspa plugins * * Copyright (c) 2005-2008 Danny McRae * Copyright (c) 2011-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include "config_mgr.h" #include "ladspa_manager.h" ladspaManager::ladspaManager() { QStringList ladspaDirectories = QString( getenv( "LADSPA_PATH" ) ). split( LADSPA_PATH_SEPERATOR ); ladspaDirectories += configManager::inst()->ladspaDir().split( ',' ); ladspaDirectories.push_back( configManager::inst()->pluginDir() + "ladspa" ); #ifndef LMMS_BUILD_WIN32 ladspaDirectories.push_back( qApp->applicationDirPath() + '/' + LIB_DIR + "ladspa" ); ladspaDirectories.push_back( "/usr/lib/lmms/ladspa" ); ladspaDirectories.push_back( "/usr/local/lib/lmms/ladspa" ); ladspaDirectories.push_back( "/usr/lib/ladspa" ); ladspaDirectories.push_back( "/usr/local/lib/ladspa" ); #endif for( QStringList::iterator it = ladspaDirectories.begin(); it != ladspaDirectories.end(); ++it ) { QDir directory( ( *it ) ); QFileInfoList list = directory.entryInfoList(); for( QFileInfoList::iterator file = list.begin(); file != list.end(); ++file ) { const QFileInfo & f = *file; if( !f.isFile() || f.fileName().right( 3 ).toLower() != #ifdef LMMS_BUILD_WIN32 "dll" #else ".so" #endif ) { continue; } QLibrary plugin_lib( f.absoluteFilePath() ); if( plugin_lib.load() == true ) { LADSPA_Descriptor_Function descriptorFunction = ( LADSPA_Descriptor_Function ) plugin_lib.resolve( "ladspa_descriptor" ); if( descriptorFunction != NULL ) { addPlugins( descriptorFunction, f.fileName() ); } } else { qWarning() << plugin_lib.errorString(); } } } l_ladspa_key_t keys = m_ladspaManagerMap.keys(); for( l_ladspa_key_t::iterator it = keys.begin(); it != keys.end(); it++ ) { m_sortedPlugins.append( qMakePair( getName( *it ), *it ) ); } qSort( m_sortedPlugins ); } ladspaManager::~ladspaManager() { for( ladspaManagerMapType::iterator it = m_ladspaManagerMap.begin(); it != m_ladspaManagerMap.end(); ++it ) { delete it.value(); } } ladspaManagerDescription * ladspaManager::getDescription( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { return( m_ladspaManagerMap[_plugin] ); } else { return( NULL ); } } void ladspaManager::addPlugins( LADSPA_Descriptor_Function _descriptor_func, const QString & _file ) { const LADSPA_Descriptor * descriptor; for( long pluginIndex = 0; ( descriptor = _descriptor_func( pluginIndex ) ) != NULL; ++pluginIndex ) { ladspa_key_t key( _file, QString( descriptor->Label ) ); if( m_ladspaManagerMap.contains( key ) ) { continue; } ladspaManagerDescription * plugIn = new ladspaManagerDescription; plugIn->descriptorFunction = _descriptor_func; plugIn->index = pluginIndex; plugIn->inputChannels = getPluginInputs( descriptor ); plugIn->outputChannels = getPluginOutputs( descriptor ); if( plugIn->inputChannels == 0 && plugIn->outputChannels > 0 ) { plugIn->type = SOURCE; } else if( plugIn->inputChannels > 0 && plugIn->outputChannels > 0 ) { plugIn->type = TRANSFER; } else if( plugIn->inputChannels > 0 && plugIn->outputChannels == 0 ) { plugIn->type = SINK; } else { plugIn->type = OTHER; } m_ladspaManagerMap[key] = plugIn; } } uint16_t ladspaManager::getPluginInputs( const LADSPA_Descriptor * _descriptor ) { uint16_t inputs = 0; for( uint16_t port = 0; port < _descriptor->PortCount; port++ ) { if( LADSPA_IS_PORT_INPUT( _descriptor->PortDescriptors[port] ) && LADSPA_IS_PORT_AUDIO( _descriptor->PortDescriptors[port] ) ) { QString name = QString( _descriptor->PortNames[port] ); if( name.toUpper().contains( "IN" ) ) { inputs++; } } } return inputs; } uint16_t ladspaManager::getPluginOutputs( const LADSPA_Descriptor * _descriptor ) { uint16_t outputs = 0; for( uint16_t port = 0; port < _descriptor->PortCount; port++ ) { if( LADSPA_IS_PORT_OUTPUT( _descriptor->PortDescriptors[port] ) && LADSPA_IS_PORT_AUDIO( _descriptor->PortDescriptors[port] ) ) { QString name = QString( _descriptor->PortNames[port] ); if( name.toUpper().contains( "OUT" ) ) { outputs++; } } } return outputs; } l_sortable_plugin_t ladspaManager::getSortedPlugins() { return( m_sortedPlugins ); } QString ladspaManager::getLabel( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( QString( descriptor->Label ) ); } else { return( QString( "" ) ); } } bool ladspaManager::hasRealTimeDependency( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_REALTIME( descriptor->Properties ) ); } else { return( false ); } } bool ladspaManager::isInplaceBroken( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_INPLACE_BROKEN( descriptor->Properties ) ); } else { return( false ); } } bool ladspaManager::isRealTimeCapable( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_HARD_RT_CAPABLE( descriptor->Properties ) ); } else { return( false ); } } QString ladspaManager::getName( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( QString( descriptor->Name ) ); } else { return( QString( "" ) ); } } QString ladspaManager::getMaker( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( QString( descriptor->Maker ) ); } else { return( QString( "" ) ); } } QString ladspaManager::getCopyright( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( QString( descriptor->Copyright ) ); } else { return( QString( "" ) ); } } uint32_t ladspaManager::getPortCount( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( descriptor->PortCount ); } else { return( 0 ); } } bool ladspaManager::isPortInput( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_PORT_INPUT ( descriptor->PortDescriptors[_port] ) ); } else { return( false ); } } bool ladspaManager::isPortOutput( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_PORT_OUTPUT ( descriptor->PortDescriptors[_port] ) ); } else { return( false ); } } bool ladspaManager::isPortAudio( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_PORT_AUDIO ( descriptor->PortDescriptors[_port] ) ); } else { return( false ); } } bool ladspaManager::isPortControl( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( LADSPA_IS_PORT_CONTROL ( descriptor->PortDescriptors[_port] ) ); } else { return( false ); } } bool ladspaManager::areHintsSampleRateDependent( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; return( LADSPA_IS_HINT_SAMPLE_RATE ( hintDescriptor ) ); } else { return( false ); } } float ladspaManager::getLowerBound( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; if( LADSPA_IS_HINT_BOUNDED_BELOW( hintDescriptor ) ) { return( descriptor->PortRangeHints[_port].LowerBound ); } else { return( NOHINT ); } } else { return( NOHINT ); } } float ladspaManager::getUpperBound( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; if( LADSPA_IS_HINT_BOUNDED_ABOVE( hintDescriptor ) ) { return( descriptor->PortRangeHints[_port].UpperBound ); } else { return( NOHINT ); } } else { return( NOHINT ); } } bool ladspaManager::isPortToggled( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; return( LADSPA_IS_HINT_TOGGLED( hintDescriptor ) ); } else { return( false ); } } float ladspaManager::getDefaultSetting( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; switch( hintDescriptor & LADSPA_HINT_DEFAULT_MASK ) { case LADSPA_HINT_DEFAULT_NONE: return( NOHINT ); case LADSPA_HINT_DEFAULT_MINIMUM: return( descriptor->PortRangeHints[_port]. LowerBound ); case LADSPA_HINT_DEFAULT_LOW: if( LADSPA_IS_HINT_LOGARITHMIC ( hintDescriptor ) ) { return( exp( log( descriptor->PortRangeHints[_port].LowerBound ) * 0.75 + log( descriptor->PortRangeHints[_port].UpperBound ) * 0.25 ) ); } else { return( descriptor->PortRangeHints[_port].LowerBound * 0.75 + descriptor->PortRangeHints[_port].UpperBound * 0.25 ); } case LADSPA_HINT_DEFAULT_MIDDLE: if( LADSPA_IS_HINT_LOGARITHMIC ( hintDescriptor ) ) { return( sqrt( descriptor->PortRangeHints[_port].LowerBound * descriptor->PortRangeHints[_port].UpperBound ) ); } else { return( 0.5 * ( descriptor->PortRangeHints[_port].LowerBound + descriptor->PortRangeHints[_port].UpperBound ) ); } case LADSPA_HINT_DEFAULT_HIGH: if( LADSPA_IS_HINT_LOGARITHMIC ( hintDescriptor ) ) { return( exp( log( descriptor->PortRangeHints[_port].LowerBound ) * 0.25 + log( descriptor->PortRangeHints[_port].UpperBound ) * 0.75 ) ); } else { return( descriptor->PortRangeHints[_port].LowerBound * 0.25 + descriptor->PortRangeHints[_port].UpperBound * 0.75 ); } case LADSPA_HINT_DEFAULT_MAXIMUM: return( descriptor->PortRangeHints[_port].UpperBound ); case LADSPA_HINT_DEFAULT_0: return( 0.0 ); case LADSPA_HINT_DEFAULT_1: return( 1.0 ); case LADSPA_HINT_DEFAULT_100: return( 100.0 ); case LADSPA_HINT_DEFAULT_440: return( 440.0 ); default: return( NOHINT ); } } else { return( NOHINT ); } } bool ladspaManager::isLogarithmic( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; return( LADSPA_IS_HINT_LOGARITHMIC( hintDescriptor ) ); } else { return( false ); } } bool ladspaManager::isInteger( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); LADSPA_PortRangeHintDescriptor hintDescriptor = descriptor->PortRangeHints[_port].HintDescriptor; return( LADSPA_IS_HINT_INTEGER( hintDescriptor ) ); } else { return( false ); } } QString ladspaManager::getPortName( const ladspa_key_t & _plugin, uint32_t _port ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( QString( descriptor->PortNames[_port] ) ); } else { return( QString( "" ) ); } } const void * ladspaManager::getImplementationData( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( descriptor->ImplementationData ); } else { return( NULL ); } } const LADSPA_Descriptor * ladspaManager::getDescriptor( const ladspa_key_t & _plugin ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( descriptor ); } else { return( NULL ); } } LADSPA_Handle ladspaManager::instantiate( const ladspa_key_t & _plugin, uint32_t _sample_rate ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); return( ( descriptor->instantiate ) ( descriptor, _sample_rate ) ); } else { return( NULL ); } } bool ladspaManager::connectPort( const ladspa_key_t & _plugin, LADSPA_Handle _instance, uint32_t _port, LADSPA_Data * _data_location ) { if( m_ladspaManagerMap.contains( _plugin ) && _port < getPortCount( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->connect_port != NULL ) { ( descriptor->connect_port ) ( _instance, _port, _data_location ); return( true ); } } return( false ); } bool ladspaManager::activate( const ladspa_key_t & _plugin, LADSPA_Handle _instance ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->activate != NULL ) { ( descriptor->activate ) ( _instance ); return( true ); } } return( false ); } bool ladspaManager::run( const ladspa_key_t & _plugin, LADSPA_Handle _instance, uint32_t _sample_count ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->run != NULL ) { ( descriptor->run ) ( _instance, _sample_count ); return( true ); } } return( false ); } bool ladspaManager::runAdding( const ladspa_key_t & _plugin, LADSPA_Handle _instance, uint32_t _sample_count ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->run_adding != NULL && descriptor->set_run_adding_gain != NULL ) { ( descriptor->run_adding ) ( _instance, _sample_count ); return( true ); } } return( false ); } bool ladspaManager::setRunAddingGain( const ladspa_key_t & _plugin, LADSPA_Handle _instance, LADSPA_Data _gain ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->run_adding != NULL && descriptor->set_run_adding_gain != NULL ) { ( descriptor->set_run_adding_gain ) ( _instance, _gain ); return( true ); } } return( false ); } bool ladspaManager::deactivate( const ladspa_key_t & _plugin, LADSPA_Handle _instance ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->deactivate != NULL ) { ( descriptor->deactivate ) ( _instance ); return( true ); } } return( false ); } bool ladspaManager::cleanup( const ladspa_key_t & _plugin, LADSPA_Handle _instance ) { if( m_ladspaManagerMap.contains( _plugin ) ) { LADSPA_Descriptor_Function descriptorFunction = m_ladspaManagerMap[_plugin]->descriptorFunction; const LADSPA_Descriptor * descriptor = descriptorFunction( m_ladspaManagerMap[_plugin]->index ); if( descriptor->cleanup != NULL ) { ( descriptor->cleanup ) ( _instance ); return( true ); } } return( false ); } lmms-1.0.0/src/core/EnvelopeAndLfoParameters.cpp0000644000175000017500000003333712313663627020326 0ustar tobytoby/* * EnvelopeAndLfoParameters.cpp - class EnvelopeAndLfoParameters * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "EnvelopeAndLfoParameters.h" #include "debug.h" #include "engine.h" #include "Mixer.h" #include "Oscillator.h" // how long should be each envelope-segment maximal (e.g. attack)? extern const float SECS_PER_ENV_SEGMENT = 5.0f; // how long should be one LFO-oscillation maximal? extern const float SECS_PER_LFO_OSCILLATION = 20.0f; EnvelopeAndLfoParameters::LfoInstances * EnvelopeAndLfoParameters::s_lfoInstances = NULL; void EnvelopeAndLfoParameters::LfoInstances::trigger() { QMutexLocker m( &m_lfoListMutex ); for( LfoList::Iterator it = m_lfos.begin(); it != m_lfos.end(); ++it ) { ( *it )->m_lfoFrame += engine::mixer()->framesPerPeriod(); ( *it )->m_bad_lfoShapeData = true; } } void EnvelopeAndLfoParameters::LfoInstances::reset() { QMutexLocker m( &m_lfoListMutex ); for( LfoList::Iterator it = m_lfos.begin(); it != m_lfos.end(); ++it ) { ( *it )->m_lfoFrame = 0; ( *it )->m_bad_lfoShapeData = true; } } void EnvelopeAndLfoParameters::LfoInstances::add( EnvelopeAndLfoParameters * lfo ) { QMutexLocker m( &m_lfoListMutex ); m_lfos.append( lfo ); } void EnvelopeAndLfoParameters::LfoInstances::remove( EnvelopeAndLfoParameters * lfo ) { QMutexLocker m( &m_lfoListMutex ); m_lfos.removeAll( lfo ); } EnvelopeAndLfoParameters::EnvelopeAndLfoParameters( float _value_for_zero_amount, Model * _parent ) : Model( _parent ), m_used( false ), m_predelayModel( 0.0, 0.0, 2.0, 0.001, this, tr( "Predelay" ) ), m_attackModel( 0.0, 0.0, 2.0, 0.001, this, tr( "Attack" ) ), m_holdModel( 0.5, 0.0, 2.0, 0.001, this, tr( "Hold" ) ), m_decayModel( 0.5, 0.0, 2.0, 0.001, this, tr( "Decay" ) ), m_sustainModel( 0.5, 0.0, 1.0, 0.001, this, tr( "Sustain" ) ), m_releaseModel( 0.1, 0.0, 2.0, 0.001, this, tr( "Release" ) ), m_amountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "Modulation" ) ), m_valueForZeroAmount( _value_for_zero_amount ), m_pahdEnv( NULL ), m_rEnv( NULL ), m_lfoPredelayModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO Predelay" ) ), m_lfoAttackModel( 0.0, 0.0, 1.0, 0.001, this, tr( "LFO Attack" ) ), m_lfoSpeedModel( 0.1, 0.001, 1.0, 0.0001, SECS_PER_LFO_OSCILLATION * 1000.0, this, tr( "LFO speed" ) ), m_lfoAmountModel( 0.0, -1.0, 1.0, 0.005, this, tr( "LFO Modulation" ) ), m_lfoWaveModel( SineWave, 0, NumLfoShapes, this, tr( "LFO Wave Shape" ) ), m_x100Model( false, this, tr( "Freq x 100" ) ), m_controlEnvAmountModel( false, this, tr( "Modulate Env-Amount" ) ), m_lfoFrame( 0 ), m_lfoAmountIsZero( false ), m_lfoShapeData( NULL ) { m_amountModel.setCenterValue( 0 ); m_lfoAmountModel.setCenterValue( 0 ); if( s_lfoInstances == NULL ) { s_lfoInstances = new LfoInstances(); } instances()->add( this ); connect( &m_predelayModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_attackModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_holdModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_decayModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_sustainModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_releaseModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_amountModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_lfoPredelayModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_lfoAttackModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_lfoSpeedModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_lfoAmountModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_lfoWaveModel, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( &m_x100Model, SIGNAL( dataChanged() ), this, SLOT( updateSampleVars() ) ); connect( engine::mixer(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleVars() ) ); m_lfoShapeData = new sample_t[engine::mixer()->framesPerPeriod()]; updateSampleVars(); } EnvelopeAndLfoParameters::~EnvelopeAndLfoParameters() { m_predelayModel.disconnect( this ); m_attackModel.disconnect( this ); m_holdModel.disconnect( this ); m_decayModel.disconnect( this ); m_sustainModel.disconnect( this ); m_releaseModel.disconnect( this ); m_amountModel.disconnect( this ); m_lfoPredelayModel.disconnect( this ); m_lfoAttackModel.disconnect( this ); m_lfoSpeedModel.disconnect( this ); m_lfoAmountModel.disconnect( this ); m_lfoWaveModel.disconnect( this ); m_x100Model.disconnect( this ); delete[] m_pahdEnv; delete[] m_rEnv; delete[] m_lfoShapeData; instances()->remove( this ); if( instances()->isEmpty() ) { delete instances(); s_lfoInstances = NULL; } } inline sample_t EnvelopeAndLfoParameters::lfoShapeSample( fpp_t _frame_offset ) { f_cnt_t frame = ( m_lfoFrame + _frame_offset ) % m_lfoOscillationFrames; const float phase = frame / static_cast( m_lfoOscillationFrames ); sample_t shape_sample; switch( m_lfoWaveModel.value() ) { case TriangleWave: shape_sample = Oscillator::triangleSample( phase ); break; case SquareWave: shape_sample = Oscillator::squareSample( phase ); break; case SawWave: shape_sample = Oscillator::sawSample( phase ); break; case UserDefinedWave: shape_sample = m_userWave.userWaveSample( phase ); break; case SineWave: default: shape_sample = Oscillator::sinSample( phase ); break; } return shape_sample * m_lfoAmount; } void EnvelopeAndLfoParameters::updateLfoShapeData() { const fpp_t frames = engine::mixer()->framesPerPeriod(); for( fpp_t offset = 0; offset < frames; ++offset ) { m_lfoShapeData[offset] = lfoShapeSample( offset ); } m_bad_lfoShapeData = false; } inline void EnvelopeAndLfoParameters::fillLfoLevel( float * _buf, f_cnt_t _frame, const fpp_t _frames ) { if( m_lfoAmountIsZero || _frame <= m_lfoPredelayFrames ) { for( fpp_t offset = 0; offset < _frames; ++offset ) { *_buf++ = 0.0f; } return; } _frame -= m_lfoPredelayFrames; if( m_bad_lfoShapeData ) { updateLfoShapeData(); } fpp_t offset = 0; const float lafI = 1.0f / m_lfoAttackFrames; for( ; offset < _frames && _frame < m_lfoAttackFrames; ++offset, ++_frame ) { *_buf++ = m_lfoShapeData[offset] * _frame * lafI; } for( ; offset < _frames; ++offset ) { *_buf++ = m_lfoShapeData[offset]; } } void EnvelopeAndLfoParameters::fillLevel( float * _buf, f_cnt_t _frame, const f_cnt_t _release_begin, const fpp_t _frames ) { if( _frame < 0 || _release_begin < 0 ) { return; } fillLfoLevel( _buf, _frame, _frames ); for( fpp_t offset = 0; offset < _frames; ++offset, ++_buf, ++_frame ) { float env_level; if( _frame < _release_begin ) { if( _frame < m_pahdFrames ) { env_level = m_pahdEnv[_frame]; } else { env_level = m_sustainLevel; } } else if( ( _frame - _release_begin ) < m_rFrames ) { env_level = m_rEnv[_frame - _release_begin] * ( ( _release_begin < m_pahdFrames ) ? m_pahdEnv[_release_begin] : m_sustainLevel ); } else { env_level = 0.0f; } // at this point, *_buf is LFO level *_buf = m_controlEnvAmountModel.value() ? env_level * ( 0.5f + *_buf ) : env_level + *_buf; } } void EnvelopeAndLfoParameters::saveSettings( QDomDocument & _doc, QDomElement & _parent ) { m_predelayModel.saveSettings( _doc, _parent, "pdel" ); m_attackModel.saveSettings( _doc, _parent, "att" ); m_holdModel.saveSettings( _doc, _parent, "hold" ); m_decayModel.saveSettings( _doc, _parent, "dec" ); m_sustainModel.saveSettings( _doc, _parent, "sustain" ); m_releaseModel.saveSettings( _doc, _parent, "rel" ); m_amountModel.saveSettings( _doc, _parent, "amt" ); m_lfoWaveModel.saveSettings( _doc, _parent, "lshp" ); m_lfoPredelayModel.saveSettings( _doc, _parent, "lpdel" ); m_lfoAttackModel.saveSettings( _doc, _parent, "latt" ); m_lfoSpeedModel.saveSettings( _doc, _parent, "lspd" ); m_lfoAmountModel.saveSettings( _doc, _parent, "lamt" ); m_x100Model.saveSettings( _doc, _parent, "x100" ); m_controlEnvAmountModel.saveSettings( _doc, _parent, "ctlenvamt" ); _parent.setAttribute( "userwavefile", m_userWave.audioFile() ); } void EnvelopeAndLfoParameters::loadSettings( const QDomElement & _this ) { m_predelayModel.loadSettings( _this, "pdel" ); m_attackModel.loadSettings( _this, "att" ); m_holdModel.loadSettings( _this, "hold" ); m_decayModel.loadSettings( _this, "dec" ); m_sustainModel.loadSettings( _this, "sustain" ); m_releaseModel.loadSettings( _this, "rel" ); m_amountModel.loadSettings( _this, "amt" ); m_lfoWaveModel.loadSettings( _this, "lshp" ); m_lfoPredelayModel.loadSettings( _this, "lpdel" ); m_lfoAttackModel.loadSettings( _this, "latt" ); m_lfoSpeedModel.loadSettings( _this, "lspd" ); m_lfoAmountModel.loadSettings( _this, "lamt" ); m_x100Model.loadSettings( _this, "x100" ); m_controlEnvAmountModel.loadSettings( _this, "ctlenvamt" ); /* ### TODO: Old reversed sustain kept for backward compatibility with 4.15 file format*/ if( _this.hasAttribute( "sus" ) ) { m_sustainModel.loadSettings( _this, "sus" ); m_sustainModel.setValue( 1.0 - m_sustainModel.value() ); } // ### TODO: /* // Keep compatibility with version 2.1 file format if( _this.hasAttribute( "lfosyncmode" ) ) { m_lfoSpeedKnob->setSyncMode( ( TempoSyncKnob::TtempoSyncMode ) _this.attribute( "lfosyncmode" ).toInt() ); }*/ m_userWave.setAudioFile( _this.attribute( "userwavefile" ) ); updateSampleVars(); } void EnvelopeAndLfoParameters::updateSampleVars() { engine::mixer()->lock(); const float frames_per_env_seg = SECS_PER_ENV_SEGMENT * engine::mixer()->processingSampleRate(); // TODO: Remove the expKnobVals, time should be linear const f_cnt_t predelay_frames = static_cast( frames_per_env_seg * expKnobVal( m_predelayModel.value() ) ); const f_cnt_t attack_frames = static_cast( frames_per_env_seg * expKnobVal( m_attackModel.value() ) ); const f_cnt_t hold_frames = static_cast( frames_per_env_seg * expKnobVal( m_holdModel.value() ) ); const f_cnt_t decay_frames = static_cast( frames_per_env_seg * expKnobVal( m_decayModel.value() * ( 1 - m_sustainModel.value() ) ) ); m_sustainLevel = m_sustainModel.value(); m_amount = m_amountModel.value(); if( m_amount >= 0 ) { m_amountAdd = ( 1.0f - m_amount ) * m_valueForZeroAmount; } else { m_amountAdd = m_valueForZeroAmount; } m_pahdFrames = predelay_frames + attack_frames + hold_frames + decay_frames; m_rFrames = static_cast( frames_per_env_seg * expKnobVal( m_releaseModel.value() ) ); if( static_cast( floorf( m_amount * 1000.0f ) ) == 0 ) { //m_pahdFrames = 0; m_rFrames = 0; } delete[] m_pahdEnv; delete[] m_rEnv; m_pahdEnv = new sample_t[m_pahdFrames]; m_rEnv = new sample_t[m_rFrames]; const float aa = m_amountAdd; for( f_cnt_t i = 0; i < predelay_frames; ++i ) { m_pahdEnv[i] = aa; } f_cnt_t add = predelay_frames; const float afI = ( 1.0f / attack_frames ) * m_amount; for( f_cnt_t i = 0; i < attack_frames; ++i ) { m_pahdEnv[add+i] = i * afI + aa; } add += attack_frames; const float amsum = m_amount + m_amountAdd; for( f_cnt_t i = 0; i < hold_frames; ++i ) { m_pahdEnv[add + i] = amsum; } add += hold_frames; const float dfI = ( 1.0 / decay_frames ) * ( m_sustainLevel -1 ) * m_amount; for( f_cnt_t i = 0; i < decay_frames; ++i ) { /* m_pahdEnv[add + i] = ( m_sustainLevel + ( 1.0f - (float)i / decay_frames ) * ( 1.0f - m_sustainLevel ) ) * m_amount + m_amountAdd; */ m_pahdEnv[add + i] = amsum + i*dfI; } const float rfI = ( 1.0f / m_rFrames ) * m_amount; for( f_cnt_t i = 0; i < m_rFrames; ++i ) { m_rEnv[i] = (float)( m_rFrames - i ) * rfI; } // save this calculation in real-time-part m_sustainLevel = m_sustainLevel * m_amount + m_amountAdd; const float frames_per_lfo_oscillation = SECS_PER_LFO_OSCILLATION * engine::mixer()->processingSampleRate(); m_lfoPredelayFrames = static_cast( frames_per_lfo_oscillation * expKnobVal( m_lfoPredelayModel.value() ) ); m_lfoAttackFrames = static_cast( frames_per_lfo_oscillation * expKnobVal( m_lfoAttackModel.value() ) ); m_lfoOscillationFrames = static_cast( frames_per_lfo_oscillation * m_lfoSpeedModel.value() ); if( m_x100Model.value() ) { m_lfoOscillationFrames /= 100; } m_lfoAmount = m_lfoAmountModel.value() * 0.5f; m_used = true; if( static_cast( floorf( m_lfoAmount * 1000.0f ) ) == 0 ) { m_lfoAmountIsZero = true; if( static_cast( floorf( m_amount * 1000.0f ) ) == 0 ) { m_used = false; } } else { m_lfoAmountIsZero = false; } m_bad_lfoShapeData = true; emit dataChanged(); engine::mixer()->unlock(); } #include "moc_EnvelopeAndLfoParameters.cxx" lmms-1.0.0/src/core/ProjectJournal.cpp0000644000175000017500000000641212313663627016374 0ustar tobytoby/* * ProjectJournal.cpp - implementation of ProjectJournal * * Copyright (c) 2006-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "ProjectJournal.h" #include "engine.h" #include "JournallingObject.h" #include "song.h" ProjectJournal::ProjectJournal() : m_joIDs(), m_journalEntries(), m_currentJournalEntry( m_journalEntries.end() ), m_journalling( false ) { } ProjectJournal::~ProjectJournal() { } void ProjectJournal::undo() { if( m_journalEntries.empty() == true ) { return; } JournallingObject * jo; if( m_currentJournalEntry - 1 >= m_journalEntries.begin() && ( jo = m_joIDs[*--m_currentJournalEntry] ) != NULL ) { jo->undo(); engine::getSong()->setModified(); } } void ProjectJournal::redo() { if( m_journalEntries.empty() == true ) { return; } JournallingObject * jo; //printf("%d\n", m_joIDs[*(m_currentJournalEntry+1)] ); if( m_currentJournalEntry < m_journalEntries.end() && ( jo = m_joIDs[*m_currentJournalEntry++] ) != NULL ) { jo->redo(); engine::getSong()->setModified(); } } void ProjectJournal::journalEntryAdded( const jo_id_t _id ) { m_journalEntries.erase( m_currentJournalEntry, m_journalEntries.end() ); m_journalEntries.push_back( _id ); m_currentJournalEntry = m_journalEntries.end(); engine::getSong()->setModified(); } jo_id_t ProjectJournal::allocID( JournallingObject * _obj ) { const jo_id_t EO_ID_MAX = (1 << 23)-1; jo_id_t id; while( m_joIDs.contains( id = static_cast( (jo_id_t)rand()*(jo_id_t)rand() % EO_ID_MAX ) ) ) { } m_joIDs[id] = _obj; //printf("new id: %d\n", id ); return id; } void ProjectJournal::reallocID( const jo_id_t _id, JournallingObject * _obj ) { //printf("realloc %d %d\n", _id, _obj ); // if( m_joIDs.contains( _id ) ) { m_joIDs[_id] = _obj; } } void ProjectJournal::forgetAboutID( const jo_id_t _id ) { //printf("forget about %d\n", _id ); JournalEntryVector::Iterator it; while( ( it = qFind( m_journalEntries.begin(), m_journalEntries.end(), _id ) ) != m_journalEntries.end() ) { if( m_currentJournalEntry >= it ) { --m_currentJournalEntry; } m_journalEntries.erase( it ); } m_joIDs.remove( _id ); } void ProjectJournal::clearJournal() { m_journalEntries.clear(); m_currentJournalEntry = m_journalEntries.end(); for( JoIdMap::Iterator it = m_joIDs.begin(); it != m_joIDs.end(); ) { if( it.value() == NULL ) { it = m_joIDs.erase( it ); } else { ++it; } } } lmms-1.0.0/src/core/base64.cpp0000644000175000017500000000350012313663627014512 0ustar tobytoby/* * base64.cpp - namespace base64 with methods for encoding/decoding binary data * to/from base64 * * Copyright (c) 2006-2008 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "base64.h" #include "lmms_basics.h" #include #include namespace base64 { QString encode( const QVariant & _data ) { QBuffer buf; buf.open( QBuffer::WriteOnly ); QDataStream out( &buf ); out << _data; QByteArray data = buf.buffer(); QString dst; encode( data.constData(), data.size(), dst ); return( dst ); } QVariant decode( const QString & _b64, QVariant::Type _force_type ) { char * dst = NULL; int dsize = 0; base64::decode( _b64, &dst, &dsize ); QByteArray ba( dst, dsize ); QBuffer buf( &ba ); buf.open( QBuffer::ReadOnly ); QDataStream in( &buf ); QVariant ret; in >> ret; if( _force_type != QVariant::Invalid && ret.type() != _force_type ) { buf.reset(); in.setVersion( QDataStream::Qt_3_3 ); in >> ret; } delete[] dst; return( ret ); } } ; lmms-1.0.0/src/core/EffectChain.cpp0000644000175000017500000001165112313663627015573 0ustar tobytoby/* * EffectChain.cpp - class for processing and effects chain * * Copyright (c) 2006-2008 Danny McRae * Copyright (c) 2008-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "EffectChain.h" #include "Effect.h" #include "engine.h" #include "debug.h" #include "DummyEffect.h" EffectChain::EffectChain( Model * _parent ) : Model( _parent ), SerializingObject(), m_enabledModel( false, NULL, tr( "Effects enabled" ) ) { } EffectChain::~EffectChain() { clear(); } void EffectChain::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "enabled", m_enabledModel.value() ); _this.setAttribute( "numofeffects", m_effects.count() ); for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); it++ ) { QDomElement ef = ( *it )->saveState( _doc, _this ); ef.setAttribute( "name", ( *it )->descriptor()->name ); ef.appendChild( ( *it )->key().saveXML( _doc ) ); } } void EffectChain::loadSettings( const QDomElement & _this ) { clear(); m_enabledModel.setValue( _this.attribute( "enabled" ).toInt() ); const int plugin_cnt = _this.attribute( "numofeffects" ).toInt(); QDomNode node = _this.firstChild(); int fx_loaded = 0; while( !node.isNull() && fx_loaded < plugin_cnt ) { if( node.isElement() && node.nodeName() == "effect" ) { QDomElement cn = node.toElement(); const QString name = cn.attribute( "name" ); EffectKey key( cn.elementsByTagName( "key" ). item( 0 ).toElement() ); Effect * e = Effect::instantiate( name, this, &key ); if( e->isOkay() ) { if( node.isElement() ) { if( e->nodeName() == node.nodeName() ) { e->restoreState( node.toElement() ); } } } else { delete e; e = new DummyEffect( parentModel() ); } m_effects.push_back( e ); ++fx_loaded; } node = node.nextSibling(); } emit dataChanged(); } void EffectChain::appendEffect( Effect * _effect ) { engine::mixer()->lock(); m_effects.append( _effect ); engine::mixer()->unlock(); emit dataChanged(); } void EffectChain::removeEffect( Effect * _effect ) { engine::mixer()->lock(); m_effects.erase( qFind( m_effects.begin(), m_effects.end(), _effect ) ); engine::mixer()->unlock(); emit dataChanged(); } void EffectChain::moveDown( Effect * _effect ) { if( _effect != m_effects.last() ) { int i = 0; for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); it++, i++ ) { if( *it == _effect ) { break; } } Effect * temp = m_effects[i + 1]; m_effects[i + 1] = _effect; m_effects[i] = temp; } } void EffectChain::moveUp( Effect * _effect ) { if( _effect != m_effects.first() ) { int i = 0; for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); it++, i++ ) { if( *it == _effect ) { break; } } Effect * temp = m_effects[i - 1]; m_effects[i - 1] = _effect; m_effects[i] = temp; } } bool EffectChain::processAudioBuffer( sampleFrame * _buf, const fpp_t _frames, bool hasInputNoise ) { if( m_enabledModel.value() == false ) { return false; } bool moreEffects = false; for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); ++it ) { if( hasInputNoise || ( *it )->isRunning() ) { moreEffects |= ( *it )->processAudioBuffer( _buf, _frames ); } #ifdef LMMS_DEBUG for( int f = 0; f < _frames; ++f ) { if( fabs( _buf[f][0] ) > 5 || fabs( _buf[f][1] ) > 5 ) { it = m_effects.end()-1; printf( "numerical overflow after processing " "plugin \"%s\"\n", ( *it )-> publicName().toUtf8().constData() ); break; } } #endif } return moreEffects; } void EffectChain::startRunning() { if( m_enabledModel.value() == false ) { return; } for( EffectList::Iterator it = m_effects.begin(); it != m_effects.end(); it++ ) { ( *it )->startRunning(); } } void EffectChain::clear() { emit aboutToClear(); m_enabledModel.setValue( false ); for( int i = 0; i < m_effects.count(); ++i ) { delete m_effects[i]; } m_effects.clear(); } #include "moc_EffectChain.cxx" lmms-1.0.0/src/core/SampleBuffer.cpp0000644000175000017500000007246012313663627016014 0ustar tobytoby/* * SampleBuffer.cpp - container-class SampleBuffer * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "SampleBuffer.h" #include "Mixer.h" #include #include #include #include #include #include #include #define OV_EXCLUDE_STATIC_CALLBACKS #ifdef LMMS_HAVE_OGGVORBIS #include #endif #ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H #include #endif #ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H #include #endif #include "base64.h" #include "config_mgr.h" #include "debug.h" #include "drumsynth.h" #include "endian_handling.h" #include "engine.h" #include "interpolation.h" #include "templates.h" #include "FileDialog.h" SampleBuffer::SampleBuffer( const QString & _audio_file, bool _is_base64_data ) : m_audioFile( ( _is_base64_data == true ) ? "" : _audio_file ), m_origData( NULL ), m_origFrames( 0 ), m_data( NULL ), m_frames( 0 ), m_startFrame( 0 ), m_endFrame( 0 ), m_loopStartFrame( 0 ), m_loopEndFrame( 0 ), m_amplification( 1.0f ), m_reversed( false ), m_frequency( BaseFreq ), m_sampleRate( engine::mixer()->baseSampleRate() ) { if( _is_base64_data == true ) { loadFromBase64( _audio_file ); } update(); } SampleBuffer::SampleBuffer( const sampleFrame * _data, const f_cnt_t _frames ) : m_audioFile( "" ), m_origData( NULL ), m_origFrames( 0 ), m_data( NULL ), m_frames( 0 ), m_startFrame( 0 ), m_endFrame( 0 ), m_loopStartFrame( 0 ), m_loopEndFrame( 0 ), m_amplification( 1.0f ), m_reversed( false ), m_frequency( BaseFreq ), m_sampleRate( engine::mixer()->baseSampleRate() ) { if( _frames > 0 ) { m_origData = new sampleFrame[_frames]; memcpy( m_origData, _data, _frames * BYTES_PER_FRAME ); m_origFrames = _frames; } update(); } SampleBuffer::SampleBuffer( const f_cnt_t _frames ) : m_audioFile( "" ), m_origData( NULL ), m_origFrames( 0 ), m_data( NULL ), m_frames( 0 ), m_startFrame( 0 ), m_endFrame( 0 ), m_loopStartFrame( 0 ), m_loopEndFrame( 0 ), m_amplification( 1.0f ), m_reversed( false ), m_frequency( BaseFreq ), m_sampleRate( engine::mixer()->baseSampleRate() ) { if( _frames > 0 ) { m_origData = new sampleFrame[_frames]; memset( m_origData, 0, _frames * BYTES_PER_FRAME ); m_origFrames = _frames; } update(); } SampleBuffer::~SampleBuffer() { delete[] m_origData; delete[] m_data; } void SampleBuffer::update( bool _keep_settings ) { const bool lock = ( m_data != NULL ); if( lock ) { engine::mixer()->lock(); delete[] m_data; } if( m_audioFile.isEmpty() && m_origData != NULL && m_origFrames > 0 ) { // TODO: reverse- and amplification-property is not covered // by following code... m_data = new sampleFrame[m_origFrames]; memcpy( m_data, m_origData, m_origFrames * BYTES_PER_FRAME ); if( _keep_settings == false ) { m_frames = m_origFrames; m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = m_frames; } } else if( !m_audioFile.isEmpty() ) { QString file = tryToMakeAbsolute( m_audioFile ); #ifdef LMMS_BUILD_WIN32 char * f = qstrdup( file.toLocal8Bit().constData() ); #else char * f = qstrdup( file.toUtf8().constData() ); #endif int_sample_t * buf = NULL; ch_cnt_t channels = DEFAULT_CHANNELS; sample_rate_t samplerate = engine::mixer()->baseSampleRate(); m_frames = 0; const QFileInfo fileInfo( file ); if( fileInfo.size() > 100*1024*1024 ) { qWarning( "refusing to load sample files bigger " "than 100 MB" ); } else { #ifdef LMMS_HAVE_OGGVORBIS // workaround for a bug in libsndfile or our libsndfile decoder // causing some OGG files to be distorted -> try with OGG Vorbis // decoder first if filename extension matches "ogg" if( m_frames == 0 && fileInfo.suffix() == "ogg" ) { m_frames = decodeSampleOGGVorbis( f, buf, channels, samplerate ); } #endif if( m_frames == 0 ) { m_frames = decodeSampleSF( f, buf, channels, samplerate ); } #ifdef LMMS_HAVE_OGGVORBIS if( m_frames == 0 ) { m_frames = decodeSampleOGGVorbis( f, buf, channels, samplerate ); } #endif if( m_frames == 0 ) { m_frames = decodeSampleDS( f, buf, channels, samplerate ); } delete[] f; if ( m_frames == 0 ) // if still no frames, bail { // sample couldn't be decoded, create buffer containing // one sample-frame m_data = new sampleFrame[1]; memset( m_data, 0, sizeof( *m_data ) ); m_frames = 1; m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = 1; } else // otherwise normalize sample rate { normalizeSampleRate( samplerate, _keep_settings ); } } } else { // neither an audio-file nor a buffer to copy from, so create // buffer containing one sample-frame m_data = new sampleFrame[1]; memset( m_data, 0, sizeof( *m_data ) ); m_frames = 1; m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = 1; } if( lock ) { engine::mixer()->unlock(); } emit sampleUpdated(); } void SampleBuffer::convertIntToFloat ( int_sample_t * & _ibuf, f_cnt_t _frames, int _channels) { // following code transforms int-samples into // float-samples and does amplifying & reversing const float fac = m_amplification / OUTPUT_SAMPLE_MULTIPLIER; m_data = new sampleFrame[_frames]; const int ch = ( _channels > 1 ) ? 1 : 0; // if reversing is on, we also reverse when // scaling if( m_reversed ) { int idx = ( _frames - 1 ) * _channels; for( f_cnt_t frame = 0; frame < _frames; ++frame ) { m_data[frame][0] = _ibuf[idx+0] * fac; m_data[frame][1] = _ibuf[idx+ch] * fac; idx -= _channels; } } else { int idx = 0; for( f_cnt_t frame = 0; frame < _frames; ++frame ) { m_data[frame][0] = _ibuf[idx+0] * fac; m_data[frame][1] = _ibuf[idx+ch] * fac; idx += _channels; } } delete[] _ibuf; } void SampleBuffer::directFloatWrite ( sample_t * & _fbuf, f_cnt_t _frames, int _channels) { m_data = new sampleFrame[_frames]; const int ch = ( _channels > 1 ) ? 1 : 0; // if reversing is on, we also reverse when // scaling if( m_reversed ) { int idx = ( _frames - 1 ) * _channels; for( f_cnt_t frame = 0; frame < _frames; ++frame ) { m_data[frame][0] = _fbuf[idx+0]; m_data[frame][1] = _fbuf[idx+ch]; idx -= _channels; } } else { int idx = 0; for( f_cnt_t frame = 0; frame < _frames; ++frame ) { m_data[frame][0] = _fbuf[idx+0]; m_data[frame][1] = _fbuf[idx+ch]; idx += _channels; } } delete[] _fbuf; } void SampleBuffer::normalizeSampleRate( const sample_rate_t _src_sr, bool _keep_settings ) { // do samplerate-conversion to our default-samplerate if( _src_sr != engine::mixer()->baseSampleRate() ) { SampleBuffer * resampled = resample( this, _src_sr, engine::mixer()->baseSampleRate() ); delete[] m_data; m_frames = resampled->frames(); m_data = new sampleFrame[m_frames]; memcpy( m_data, resampled->data(), m_frames * sizeof( sampleFrame ) ); delete resampled; } if( _keep_settings == false ) { // update frame-variables m_loopStartFrame = m_startFrame = 0; m_loopEndFrame = m_endFrame = m_frames; } } f_cnt_t SampleBuffer::decodeSampleSF( const char * _f, int_sample_t * & _buf, ch_cnt_t & _channels, sample_rate_t & _samplerate ) { SNDFILE * snd_file; SF_INFO sf_info; f_cnt_t frames = 0; bool sf_rr = false; sample_t * fbuf = 0; if( ( snd_file = sf_open( _f, SFM_READ, &sf_info ) ) != NULL ) { frames = sf_info.frames; // check if float if ( (sf_info.format & SF_FORMAT_SUBMASK) == SF_FORMAT_FLOAT ) // if yes, use float format for buffer { fbuf = new sample_t[sf_info.channels * frames]; sf_rr = sf_read_float( snd_file, fbuf, sf_info.channels * frames ); } else // otherwise, use int { _buf = new int_sample_t[sf_info.channels * frames]; sf_rr = sf_read_short( snd_file, _buf, sf_info.channels * frames ); } if( sf_rr < sf_info.channels * frames ) { #ifdef DEBUG_LMMS printf( "SampleBuffer::decodeSampleSF(): could not read" " sample %s: %s\n", _f, sf_strerror( NULL ) ); #endif } _channels = sf_info.channels; _samplerate = sf_info.samplerate; sf_close( snd_file ); } else { #ifdef DEBUG_LMMS printf( "SampleBuffer::decodeSampleSF(): could not load " "sample %s: %s\n", _f, sf_strerror( NULL ) ); #endif } //write down either directly or convert i->f depending on file type if ( frames > 0 && fbuf != NULL ) { directFloatWrite ( fbuf, frames, _channels); } else if ( frames > 0 && _buf != NULL ) { convertIntToFloat ( _buf, frames, _channels); } return frames; } #ifdef LMMS_HAVE_OGGVORBIS // callback-functions for reading ogg-file size_t qfileReadCallback( void * _ptr, size_t _size, size_t _n, void * _udata ) { return static_cast( _udata )->read( (char*) _ptr, _size * _n ); } int qfileSeekCallback( void * _udata, ogg_int64_t _offset, int _whence ) { QFile * f = static_cast( _udata ); if( _whence == SEEK_CUR ) { f->seek( f->pos() + _offset ); } else if( _whence == SEEK_END ) { f->seek( f->size() + _offset ); } else { f->seek( _offset ); } return 0; } int qfileCloseCallback( void * _udata ) { delete static_cast( _udata ); return 0; } long qfileTellCallback( void * _udata ) { return static_cast( _udata )->pos(); } f_cnt_t SampleBuffer::decodeSampleOGGVorbis( const char * _f, int_sample_t * & _buf, ch_cnt_t & _channels, sample_rate_t & _samplerate ) { static ov_callbacks callbacks = { qfileReadCallback, qfileSeekCallback, qfileCloseCallback, qfileTellCallback } ; OggVorbis_File vf; f_cnt_t frames = 0; QFile * f = new QFile( _f ); if( f->open( QFile::ReadOnly ) == false ) { delete f; return 0; } int err = ov_open_callbacks( f, &vf, NULL, 0, callbacks ); if( err < 0 ) { switch( err ) { case OV_EREAD: printf( "SampleBuffer::decodeSampleOGGVorbis():" " media read error\n" ); break; case OV_ENOTVORBIS: /* printf( "SampleBuffer::decodeSampleOGGVorbis():" " not an Ogg Vorbis file\n" );*/ break; case OV_EVERSION: printf( "SampleBuffer::decodeSampleOGGVorbis():" " vorbis version mismatch\n" ); break; case OV_EBADHEADER: printf( "SampleBuffer::decodeSampleOGGVorbis():" " invalid Vorbis bitstream header\n" ); break; case OV_EFAULT: printf( "SampleBuffer::decodeSampleOgg(): " "internal logic fault\n" ); break; } delete f; return 0; } ov_pcm_seek( &vf, 0 ); _channels = ov_info( &vf, -1 )->channels; _samplerate = ov_info( &vf, -1 )->rate; ogg_int64_t total = ov_pcm_total( &vf, -1 ); _buf = new int_sample_t[total * _channels]; int bitstream = 0; long bytes_read = 0; do { bytes_read = ov_read( &vf, (char *) &_buf[frames * _channels], ( total - frames ) * _channels * BYTES_PER_INT_SAMPLE, isLittleEndian() ? 0 : 1, BYTES_PER_INT_SAMPLE, 1, &bitstream ); if( bytes_read < 0 ) { break; } frames += bytes_read / ( _channels * BYTES_PER_INT_SAMPLE ); } while( bytes_read != 0 && bitstream == 0 ); ov_clear( &vf ); // if buffer isn't empty, convert it to float and write it down if ( frames > 0 && _buf != NULL ) { convertIntToFloat ( _buf, frames, _channels); } return frames; } #endif f_cnt_t SampleBuffer::decodeSampleDS( const char * _f, int_sample_t * & _buf, ch_cnt_t & _channels, sample_rate_t & _samplerate ) { DrumSynth ds; f_cnt_t frames = ds.GetDSFileSamples( _f, _buf, _channels, _samplerate ); if ( frames > 0 && _buf != NULL ) { convertIntToFloat ( _buf, frames, _channels); } return frames; } bool SampleBuffer::play( sampleFrame * _ab, handleState * _state, const fpp_t _frames, const float _freq, const bool _looped ) { QMutexLocker ml( &m_varLock ); engine::mixer()->clearAudioBuffer( _ab, _frames ); if( m_endFrame == 0 || _frames == 0 ) { return false; } const double freq_factor = (double) _freq / (double) m_frequency * m_sampleRate / engine::mixer()->processingSampleRate(); // calculate how many frames we have in requested pitch const f_cnt_t total_frames_for_current_pitch = static_cast( ( m_endFrame - m_startFrame ) / freq_factor ); if( total_frames_for_current_pitch == 0 ) { return false; } // this holds the number of the first frame to play f_cnt_t play_frame = _state->m_frameIndex; if( play_frame < m_startFrame ) { play_frame = m_startFrame; } // this holds the number of remaining frames in current loop f_cnt_t frames_for_loop; if( _looped ) { play_frame = getLoopedIndex( play_frame ); frames_for_loop = static_cast( ( m_loopEndFrame - play_frame ) / freq_factor ); } else { if( play_frame >= m_endFrame ) { return false; } frames_for_loop = static_cast( ( m_endFrame - play_frame ) / freq_factor ); if( frames_for_loop == 0 ) { return false; } } sampleFrame * tmp = NULL; // check whether we have to change pitch... if( freq_factor != 1.0 || _state->m_varyingPitch ) { SRC_DATA src_data; // Generate output const f_cnt_t margin = 64; f_cnt_t fragment_size = (f_cnt_t)( _frames * freq_factor ) + margin; src_data.data_in = getSampleFragment( play_frame, fragment_size, _looped, &tmp )[0]; src_data.data_out = _ab[0]; src_data.input_frames = fragment_size; src_data.output_frames = _frames; src_data.src_ratio = 1.0 / freq_factor; src_data.end_of_input = 0; int error = src_process( _state->m_resamplingData, &src_data ); if( error ) { printf( "SampleBuffer: error while resampling: %s\n", src_strerror( error ) ); } if( src_data.output_frames_gen > _frames ) { printf( "SampleBuffer: not enough frames: %ld / %d\n", src_data.output_frames_gen, _frames ); } // Advance play_frame += src_data.input_frames_used; if( _looped ) { play_frame = getLoopedIndex( play_frame ); } } else { // we don't have to pitch, so we just copy the sample-data // as is into pitched-copy-buffer // Generate output memcpy( _ab, getSampleFragment( play_frame, _frames, _looped, &tmp ), _frames * BYTES_PER_FRAME ); // Advance play_frame += _frames; if( _looped ) { play_frame = getLoopedIndex( play_frame ); } } delete[] tmp; _state->m_frameIndex = play_frame; return true; } sampleFrame * SampleBuffer::getSampleFragment( f_cnt_t _start, f_cnt_t _frames, bool _looped, sampleFrame * * _tmp ) const { if( _looped ) { if( _start + _frames <= m_loopEndFrame ) { return m_data + _start; } } else { if( _start + _frames <= m_endFrame ) { return m_data + _start; } } *_tmp = new sampleFrame[_frames]; if( _looped ) { f_cnt_t copied = m_loopEndFrame - _start; memcpy( *_tmp, m_data + _start, copied * BYTES_PER_FRAME ); f_cnt_t loop_frames = m_loopEndFrame - m_loopStartFrame; while( _frames - copied > 0 ) { f_cnt_t todo = qMin( _frames - copied, loop_frames ); memcpy( *_tmp + copied, m_data + m_loopStartFrame, todo * BYTES_PER_FRAME ); copied += todo; } } else { f_cnt_t available = m_endFrame - _start; memcpy( *_tmp, m_data + _start, available * BYTES_PER_FRAME ); memset( *_tmp + available, 0, ( _frames - available ) * BYTES_PER_FRAME ); } return *_tmp; } f_cnt_t SampleBuffer::getLoopedIndex( f_cnt_t _index ) const { if( _index < m_loopEndFrame ) { return _index; } return m_loopStartFrame + ( _index - m_loopStartFrame ) % ( m_loopEndFrame - m_loopStartFrame ); } void SampleBuffer::visualize( QPainter & _p, const QRect & _dr, const QRect & _clip, f_cnt_t _from_frame, f_cnt_t _to_frame ) { const bool focus_on_range = _to_frame <= m_frames && 0 <= _from_frame && _from_frame < _to_frame; // _p.setClipRect( _clip ); // _p.setPen( QColor( 0x22, 0xFF, 0x44 ) ); //_p.setPen( QColor( 64, 224, 160 ) ); const int w = _dr.width(); const int h = _dr.height(); const int yb = h / 2 + _dr.y(); const float y_space = h*0.25f; const int nb_frames = focus_on_range ? _to_frame - _from_frame : m_frames; if( nb_frames < 60000 ) { _p.setRenderHint( QPainter::Antialiasing ); QColor c = _p.pen().color(); _p.setPen( QPen( c, 0.7 ) ); } const int fpp = tLimit( nb_frames / w, 1, 20 ); QPoint * l = new QPoint[nb_frames / fpp + 1]; int n = 0; const int xb = _dr.x(); const int first = focus_on_range ? _from_frame : 0; const int last = focus_on_range ? _to_frame : m_frames; for( int frame = first; frame < last; frame += fpp ) { l[n] = QPoint( xb + ( (frame - first) * double( w ) / nb_frames ), (int)( yb - ( ( m_data[frame][0]+m_data[frame][1] ) * y_space ) ) ); ++n; } _p.drawPolyline( l, nb_frames / fpp ); delete[] l; } QString SampleBuffer::openAudioFile() const { FileDialog ofd( NULL, tr( "Open audio file" ) ); QString dir; if( !m_audioFile.isEmpty() ) { QString f = m_audioFile; if( QFileInfo( f ).isRelative() ) { f = configManager::inst()->userSamplesDir() + f; if( QFileInfo( f ).exists() == false ) { f = configManager::inst()->factorySamplesDir() + m_audioFile; } } dir = QFileInfo( f ).absolutePath(); } else { dir = configManager::inst()->userSamplesDir(); } // change dir to position of previously opened file ofd.setDirectory( dir ); ofd.setFileMode( FileDialog::ExistingFiles ); // set filters QStringList types; types << tr( "All Audio-Files (*.wav *.ogg *.ds *.flac *.spx *.voc " "*.aif *.aiff *.au *.raw)" ) << tr( "Wave-Files (*.wav)" ) << tr( "OGG-Files (*.ogg)" ) << tr( "DrumSynth-Files (*.ds)" ) << tr( "FLAC-Files (*.flac)" ) << tr( "SPEEX-Files (*.spx)" ) //<< tr( "MP3-Files (*.mp3)" ) //<< tr( "MIDI-Files (*.mid)" ) << tr( "VOC-Files (*.voc)" ) << tr( "AIFF-Files (*.aif *.aiff)" ) << tr( "AU-Files (*.au)" ) << tr( "RAW-Files (*.raw)" ) //<< tr( "MOD-Files (*.mod)" ) ; ofd.setFilters( types ); if( !m_audioFile.isEmpty() ) { // select previously opened file ofd.selectFile( QFileInfo( m_audioFile ).fileName() ); } if( ofd.exec () == QDialog::Accepted ) { if( ofd.selectedFiles().isEmpty() ) { return QString::null; } return tryToMakeRelative( ofd.selectedFiles()[0] ); } return QString::null; } QString SampleBuffer::openAndSetAudioFile() { QString fileName = this->openAudioFile(); if(!fileName.isEmpty()) { this->setAudioFile( fileName ); } return fileName; } QString SampleBuffer::openAndSetWaveformFile() { if( m_audioFile.isEmpty() ) { m_audioFile = configManager::inst()->factorySamplesDir() + "waveforms/10saw.flac"; } QString fileName = this->openAudioFile(); if(!fileName.isEmpty()) { this->setAudioFile( fileName ); } else { m_audioFile = ""; } return fileName; } #undef LMMS_HAVE_FLAC_STREAM_ENCODER_H /* not yet... */ #undef LMMS_HAVE_FLAC_STREAM_DECODER_H #ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H FLAC__StreamEncoderWriteStatus flacStreamEncoderWriteCallback( const FLAC__StreamEncoder * /*_encoder*/, const FLAC__byte _buffer[], unsigned int/* _samples*/, unsigned int _bytes, unsigned int/* _current_frame*/, void * _client_data ) { /* if( _bytes == 0 ) { return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; }*/ return ( static_cast( _client_data )->write( (const char *) _buffer, _bytes ) == (int) _bytes ) ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; } void flacStreamEncoderMetadataCallback( const FLAC__StreamEncoder *, const FLAC__StreamMetadata * _metadata, void * _client_data ) { QBuffer * b = static_cast( _client_data ); b->seek( 0 ); b->write( (const char *) _metadata, sizeof( *_metadata ) ); } #endif QString & SampleBuffer::toBase64( QString & _dst ) const { #ifdef LMMS_HAVE_FLAC_STREAM_ENCODER_H const f_cnt_t FRAMES_PER_BUF = 1152; FLAC__StreamEncoder * flac_enc = FLAC__stream_encoder_new(); FLAC__stream_encoder_set_channels( flac_enc, DEFAULT_CHANNELS ); FLAC__stream_encoder_set_blocksize( flac_enc, FRAMES_PER_BUF ); /* FLAC__stream_encoder_set_do_exhaustive_model_search( flac_enc, true ); FLAC__stream_encoder_set_do_mid_side_stereo( flac_enc, true );*/ FLAC__stream_encoder_set_sample_rate( flac_enc, engine::mixer()->sampleRate() ); QBuffer ba_writer; ba_writer.open( QBuffer::WriteOnly ); FLAC__stream_encoder_set_write_callback( flac_enc, flacStreamEncoderWriteCallback ); FLAC__stream_encoder_set_metadata_callback( flac_enc, flacStreamEncoderMetadataCallback ); FLAC__stream_encoder_set_client_data( flac_enc, &ba_writer ); if( FLAC__stream_encoder_init( flac_enc ) != FLAC__STREAM_ENCODER_OK ) { printf( "error within FLAC__stream_encoder_init()!\n" ); } f_cnt_t frame_cnt = 0; while( frame_cnt < m_frames ) { f_cnt_t remaining = qMin( FRAMES_PER_BUF, m_frames - frame_cnt ); FLAC__int32 buf[FRAMES_PER_BUF * DEFAULT_CHANNELS]; for( f_cnt_t f = 0; f < remaining; ++f ) { for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { buf[f*DEFAULT_CHANNELS+ch] = (FLAC__int32)( Mixer::clip( m_data[f+frame_cnt][ch] ) * OUTPUT_SAMPLE_MULTIPLIER ); } } FLAC__stream_encoder_process_interleaved( flac_enc, buf, remaining ); frame_cnt += remaining; } FLAC__stream_encoder_finish( flac_enc ); FLAC__stream_encoder_delete( flac_enc ); printf("%d %d\n", frame_cnt, (int)ba_writer.size() ); ba_writer.close(); base64::encode( ba_writer.buffer().data(), ba_writer.buffer().size(), _dst ); #else /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ base64::encode( (const char *) m_data, m_frames * sizeof( sampleFrame ), _dst ); #endif /* LMMS_HAVE_FLAC_STREAM_ENCODER_H */ return _dst; } SampleBuffer * SampleBuffer::resample( sampleFrame * _data, const f_cnt_t _frames, const sample_rate_t _src_sr, const sample_rate_t _dst_sr ) { const f_cnt_t dst_frames = static_cast( _frames / (float) _src_sr * (float) _dst_sr ); SampleBuffer * dst_sb = new SampleBuffer( dst_frames ); sampleFrame * dst_buf = dst_sb->m_origData; // yeah, libsamplerate, let's rock with sinc-interpolation! int error; SRC_STATE * state; if( ( state = src_new( SRC_SINC_MEDIUM_QUALITY, DEFAULT_CHANNELS, &error ) ) != NULL ) { SRC_DATA src_data; src_data.end_of_input = 1; src_data.data_in = _data[0]; src_data.data_out = dst_buf[0]; src_data.input_frames = _frames; src_data.output_frames = dst_frames; src_data.src_ratio = (double) _dst_sr / _src_sr; if( ( error = src_process( state, &src_data ) ) ) { printf( "SampleBuffer: error while resampling: %s\n", src_strerror( error ) ); } src_delete( state ); } else { printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); } dst_sb->update(); return dst_sb; } void SampleBuffer::setAudioFile( const QString & _audio_file ) { m_audioFile = tryToMakeRelative( _audio_file ); update(); } #ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H struct flacStreamDecoderClientData { QBuffer * read_buffer; QBuffer * write_buffer; } ; FLAC__StreamDecoderReadStatus flacStreamDecoderReadCallback( const FLAC__StreamDecoder * /*_decoder*/, FLAC__byte * _buffer, unsigned int * _bytes, void * _client_data ) { int res = static_cast( _client_data )->read_buffer->read( (char *) _buffer, *_bytes ); if( res > 0 ) { *_bytes = res; return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } *_bytes = 0; return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; } FLAC__StreamDecoderWriteStatus flacStreamDecoderWriteCallback( const FLAC__StreamDecoder * /*_decoder*/, const FLAC__Frame * _frame, const FLAC__int32 * const _buffer[], void * _client_data ) { if( _frame->header.channels != 2 ) { printf( "channels != 2 in " "flacStreamDecoderWriteCallback()\n" ); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } if( _frame->header.bits_per_sample != 16 ) { printf( "bits_per_sample != 16 in " "flacStreamDecoderWriteCallback()\n" ); return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; } const f_cnt_t frames = _frame->header.blocksize; for( f_cnt_t frame = 0; frame < frames; ++frame ) { sampleFrame sframe = { _buffer[0][frame] / OUTPUT_SAMPLE_MULTIPLIER, _buffer[1][frame] / OUTPUT_SAMPLE_MULTIPLIER } ; static_cast( _client_data )->write_buffer->write( (const char *) sframe, sizeof( sframe ) ); } return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } void flacStreamDecoderMetadataCallback( const FLAC__StreamDecoder *, const FLAC__StreamMetadata *, void * /*_client_data*/ ) { printf("stream decoder metadata callback\n"); /* QBuffer * b = static_cast( _client_data ); b->seek( 0 ); b->write( (const char *) _metadata, sizeof( *_metadata ) );*/ } void flacStreamDecoderErrorCallback( const FLAC__StreamDecoder *, FLAC__StreamDecoderErrorStatus _status, void * /*_client_data*/ ) { printf("error callback! %d\n", _status); // what to do now?? } #endif void SampleBuffer::loadFromBase64( const QString & _data ) { char * dst = NULL; int dsize = 0; base64::decode( _data, &dst, &dsize ); #ifdef LMMS_HAVE_FLAC_STREAM_DECODER_H QByteArray orig_data = QByteArray::fromRawData( dst, dsize ); QBuffer ba_reader( &orig_data ); ba_reader.open( QBuffer::ReadOnly ); QBuffer ba_writer; ba_writer.open( QBuffer::WriteOnly ); flacStreamDecoderClientData cdata = { &ba_reader, &ba_writer } ; FLAC__StreamDecoder * flac_dec = FLAC__stream_decoder_new(); FLAC__stream_decoder_set_read_callback( flac_dec, flacStreamDecoderReadCallback ); FLAC__stream_decoder_set_write_callback( flac_dec, flacStreamDecoderWriteCallback ); FLAC__stream_decoder_set_error_callback( flac_dec, flacStreamDecoderErrorCallback ); FLAC__stream_decoder_set_metadata_callback( flac_dec, flacStreamDecoderMetadataCallback ); FLAC__stream_decoder_set_client_data( flac_dec, &cdata ); FLAC__stream_decoder_init( flac_dec ); FLAC__stream_decoder_process_until_end_of_stream( flac_dec ); FLAC__stream_decoder_finish( flac_dec ); FLAC__stream_decoder_delete( flac_dec ); ba_reader.close(); orig_data = ba_writer.buffer(); printf("%d\n", (int) orig_data.size() ); m_origFrames = orig_data.size() / sizeof( sampleFrame ); delete[] m_origData; m_origData = new sampleFrame[m_origFrames]; memcpy( m_origData, orig_data.data(), orig_data.size() ); #else /* LMMS_HAVE_FLAC_STREAM_DECODER_H */ m_origFrames = dsize / sizeof( sampleFrame ); delete[] m_origData; m_origData = new sampleFrame[m_origFrames]; memcpy( m_origData, dst, dsize ); #endif delete[] dst; m_audioFile = QString(); update(); } void SampleBuffer::setStartFrame( const f_cnt_t _s ) { m_varLock.lock(); m_loopStartFrame = m_startFrame = _s; m_varLock.unlock(); } void SampleBuffer::setEndFrame( const f_cnt_t _e ) { m_varLock.lock(); m_loopEndFrame = m_endFrame = _e; m_varLock.unlock(); } void SampleBuffer::setAmplification( float _a ) { m_amplification = _a; update( true ); } void SampleBuffer::setReversed( bool _on ) { m_reversed = _on; update( true ); } QString SampleBuffer::tryToMakeRelative( const QString & _file ) { if( QFileInfo( _file ).isRelative() == false ) { QString f = QString( _file ).replace( QDir::separator(), '/' ); QString fsd = configManager::inst()->factorySamplesDir(); QString usd = configManager::inst()->userSamplesDir(); fsd.replace( QDir::separator(), '/' ); usd.replace( QDir::separator(), '/' ); if( f.startsWith( fsd ) ) { return QString( f ).mid( fsd.length() ); } else if( f.startsWith( usd ) ) { return QString( f ).mid( usd.length() ); } } return _file; } QString SampleBuffer::tryToMakeAbsolute( const QString & _file ) { if( QFileInfo( _file ).isAbsolute() ) { return _file; } QString f = configManager::inst()->userSamplesDir() + _file; if( QFileInfo( f ).exists() ) { return f; } return configManager::inst()->factorySamplesDir() + _file; } SampleBuffer::handleState::handleState( bool _varying_pitch ) : m_frameIndex( 0 ), m_varyingPitch( _varying_pitch ) { int error; if( ( m_resamplingData = src_new(/* ( engine::mixer()->highQuality() == true ) ? SRC_SINC_FASTEST :*/ SRC_LINEAR, DEFAULT_CHANNELS, &error ) ) == NULL ) { printf( "Error: src_new() failed in sample_buffer.cpp!\n" ); } } SampleBuffer::handleState::~handleState() { src_delete( m_resamplingData ); } #include "moc_SampleBuffer.cxx" /* vim: set tw=0 noexpandtab: */ lmms-1.0.0/src/core/Mixer.cpp0000644000175000017500000005664712313663627014536 0ustar tobytoby/* * Mixer.cpp - audio-device-independent mixer for LMMS * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "Mixer.h" #include "FxMixer.h" #include "MixHelpers.h" #include "song.h" #include "templates.h" #include "EnvelopeAndLfoParameters.h" #include "NotePlayHandle.h" #include "InstrumentTrack.h" #include "debug.h" #include "engine.h" #include "config_mgr.h" #include "SamplePlayHandle.h" #include "PianoRoll.h" #include "MicroTimer.h" #include "atomic_int.h" // platform-specific audio-interface-classes #include "AudioAlsa.h" #include "AudioJack.h" #include "AudioOss.h" #include "AudioPortAudio.h" #include "AudioPulseAudio.h" #include "AudioSdl.h" #include "AudioDummy.h" // platform-specific midi-interface-classes #include "MidiAlsaRaw.h" #include "MidiAlsaSeq.h" #include "MidiOss.h" #include "MidiWinMM.h" #include "MidiDummy.h" static QVector __fx_channel_jobs( NumFxChannels ); static void aligned_free( void * _buf ) { if( _buf != NULL ) { int *ptr2=(int *)_buf - 1; _buf = (char *)_buf- *ptr2; free(_buf); } } static void * aligned_malloc( int _bytes ) { char *ptr,*ptr2,*aligned_ptr; int align_mask = ALIGN_SIZE- 1; ptr=(char *)malloc(_bytes +ALIGN_SIZE+ sizeof(int)); if(ptr==NULL) return(NULL); ptr2 = ptr + sizeof(int); aligned_ptr = ptr2 + (ALIGN_SIZE- ((size_t)ptr2 & align_mask)); ptr2 = aligned_ptr - sizeof(int); *((int *)ptr2)=(int)(aligned_ptr - ptr); return(aligned_ptr); } class MixerWorkerThread : public QThread { public: enum JobTypes { InvalidJob, PlayHandle, AudioPortEffects, EffectChannel, NumJobTypes } ; struct JobQueueItem { JobQueueItem() : type( InvalidJob ), job( NULL ), param( 0 ), done( false ) { } JobQueueItem( JobTypes _type, void * _job, int _param = 0 ) : type( _type ), job( _job ), param( _param ), done( false ) { } JobTypes type; void * job; int param; AtomicInt done; } ; struct JobQueue { #define JOB_QUEUE_SIZE 1024 JobQueue() : queueSize( 0 ) { } JobQueueItem items[JOB_QUEUE_SIZE]; int queueSize; AtomicInt itemsDone; } ; static JobQueue s_jobQueue; MixerWorkerThread( int _worker_num, Mixer* mixer ) : QThread( mixer ), m_workingBuf( (sampleFrame *) aligned_malloc( mixer->framesPerPeriod() * sizeof( sampleFrame ) ) ), m_workerNum( _worker_num ), m_quit( false ), m_mixer( mixer ), m_queueReadyWaitCond( &m_mixer->m_queueReadyWaitCond ) { } virtual ~MixerWorkerThread() { aligned_free( m_workingBuf ); } virtual void quit() { m_quit = true; } void processJobQueue(); int workerNum() const { return m_workerNum; } private: virtual void run() { #if 0 #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_SCHED_H cpu_set_t mask; CPU_ZERO( &mask ); CPU_SET( m_workerNum, &mask ); sched_setaffinity( 0, sizeof( mask ), &mask ); #endif #endif #endif QMutex m; while( m_quit == false ) { m.lock(); m_queueReadyWaitCond->wait( &m ); processJobQueue(); m.unlock(); } } sampleFrame * m_workingBuf; int m_workerNum; volatile bool m_quit; Mixer* m_mixer; QWaitCondition * m_queueReadyWaitCond; } ; MixerWorkerThread::JobQueue MixerWorkerThread::s_jobQueue; void MixerWorkerThread::processJobQueue() { for( int i = 0; i < s_jobQueue.queueSize; ++i ) { JobQueueItem * it = &s_jobQueue.items[i]; if( it->done.fetchAndStoreOrdered( 1 ) == 0 ) { switch( it->type ) { case PlayHandle: ( (::PlayHandle *) it->job )->play( m_workingBuf ); break; case AudioPortEffects: { AudioPort * a = (AudioPort *) it->job; const bool me = a->processEffects(); if( me || a->m_bufferUsage != AudioPort::NoUsage ) { engine::fxMixer()->mixToChannel( a->firstBuffer(), a->nextFxChannel() ); a->nextPeriod(); } } break; case EffectChannel: engine::fxMixer()->processChannel( (fx_ch_t) it->param ); break; default: break; } s_jobQueue.itemsDone.fetchAndAddOrdered( 1 ); } } } #define FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ MixerWorkerThread::s_jobQueue.queueSize = 0; \ MixerWorkerThread::s_jobQueue.itemsDone = 0; \ for( _vec_type::Iterator it = _vec.begin(); \ it != _vec.end(); ++it ) \ { \ if( _condition ) \ { #define FILL_JOB_QUEUE_END() \ ++MixerWorkerThread::s_jobQueue.queueSize; \ } \ } #define FILL_JOB_QUEUE(_vec_type,_vec,_job_type,_condition) \ FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ MixerWorkerThread::s_jobQueue.items \ [MixerWorkerThread::s_jobQueue.queueSize] = \ MixerWorkerThread::JobQueueItem( _job_type, \ (void *) *it ); \ FILL_JOB_QUEUE_END() #define FILL_JOB_QUEUE_PARAM(_vec_type,_vec,_job_type,_condition) \ FILL_JOB_QUEUE_BEGIN(_vec_type,_vec,_condition) \ MixerWorkerThread::s_jobQueue.items \ [MixerWorkerThread::s_jobQueue.queueSize] = \ MixerWorkerThread::JobQueueItem( _job_type, \ NULL, *it ); \ FILL_JOB_QUEUE_END() #define START_JOBS() \ m_queueReadyWaitCond.wakeAll(); // define a pause instruction for spinlock-loop - merely useful on // HyperThreading systems with just one physical core (e.g. Intel Atom) #ifdef LMMS_HOST_X86 #define SPINLOCK_PAUSE() asm( "pause" ) #else #ifdef LMMS_HOST_X86_64 #define SPINLOCK_PAUSE() asm( "pause" ) #else #define SPINLOCK_PAUSE() #endif #endif #define WAIT_FOR_JOBS() \ m_workers[m_numWorkers]->processJobQueue(); \ while( MixerWorkerThread::s_jobQueue.itemsDone < \ MixerWorkerThread::s_jobQueue.queueSize ) \ { \ SPINLOCK_PAUSE(); \ } \ Mixer::Mixer() : m_framesPerPeriod( DEFAULT_BUFFER_SIZE ), m_workingBuf( NULL ), m_inputBufferRead( 0 ), m_inputBufferWrite( 1 ), m_readBuf( NULL ), m_writeBuf( NULL ), m_cpuLoad( 0 ), m_workers(), m_numWorkers( QThread::idealThreadCount()-1 ), m_queueReadyWaitCond(), m_qualitySettings( qualitySettings::Mode_Draft ), m_masterGain( 1.0f ), m_audioDev( NULL ), m_oldAudioDev( NULL ), m_globalMutex( QMutex::Recursive ) { for( int i = 0; i < 2; ++i ) { m_inputBufferFrames[i] = 0; m_inputBufferSize[i] = DEFAULT_BUFFER_SIZE * 100; m_inputBuffer[i] = new sampleFrame[ DEFAULT_BUFFER_SIZE * 100 ]; clearAudioBuffer( m_inputBuffer[i], m_inputBufferSize[i] ); } for( int i = 1; i < NumFxChannels+1; ++i ) { __fx_channel_jobs[i-1] = (fx_ch_t) i; } // just rendering? if( !engine::hasGUI() ) { m_framesPerPeriod = DEFAULT_BUFFER_SIZE; m_fifo = new fifo( 1 ); } else if( configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt() >= 32 ) { m_framesPerPeriod = (fpp_t) configManager::inst()->value( "mixer", "framesperaudiobuffer" ).toInt(); if( m_framesPerPeriod > DEFAULT_BUFFER_SIZE ) { m_fifo = new fifo( m_framesPerPeriod / DEFAULT_BUFFER_SIZE ); m_framesPerPeriod = DEFAULT_BUFFER_SIZE; } else { m_fifo = new fifo( 1 ); } } else { configManager::inst()->setValue( "mixer", "framesperaudiobuffer", QString::number( m_framesPerPeriod ) ); m_fifo = new fifo( 1 ); } m_workingBuf = (sampleFrame*) aligned_malloc( m_framesPerPeriod * sizeof( sampleFrame ) ); for( int i = 0; i < 3; i++ ) { m_readBuf = (surroundSampleFrame*) aligned_malloc( m_framesPerPeriod * sizeof( surroundSampleFrame ) ); clearAudioBuffer( m_readBuf, m_framesPerPeriod ); m_bufferPool.push_back( m_readBuf ); } for( int i = 0; i < m_numWorkers+1; ++i ) { MixerWorkerThread * wt = new MixerWorkerThread( i, this ); if( i < m_numWorkers ) { wt->start( QThread::TimeCriticalPriority ); } m_workers.push_back( wt ); } m_poolDepth = 2; m_readBuffer = 0; m_writeBuffer = 1; } Mixer::~Mixer() { // distribute an empty job-queue so that worker-threads // get out of their processing-loop MixerWorkerThread::s_jobQueue.queueSize = 0; for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->quit(); } START_JOBS(); for( int w = 0; w < m_numWorkers; ++w ) { m_workers[w]->wait( 500 ); } while( m_fifo->available() ) { delete[] m_fifo->read(); } delete m_fifo; delete m_audioDev; delete m_midiClient; for( int i = 0; i < 3; i++ ) { aligned_free( m_bufferPool[i] ); } aligned_free( m_workingBuf ); for( int i = 0; i < 2; ++i ) { delete[] m_inputBuffer[i]; } } void Mixer::initDevices() { m_audioDev = tryAudioDevices(); m_midiClient = tryMidiClients(); } void Mixer::startProcessing( bool _needs_fifo ) { if( _needs_fifo ) { m_fifoWriter = new fifoWriter( this, m_fifo ); m_fifoWriter->start( QThread::HighPriority ); } else { m_fifoWriter = NULL; } m_audioDev->startProcessing(); } void Mixer::stopProcessing() { if( m_fifoWriter != NULL ) { m_fifoWriter->finish(); m_audioDev->stopProcessing(); m_fifoWriter->wait( 1000 ); m_fifoWriter->terminate(); delete m_fifoWriter; m_fifoWriter = NULL; } else { m_audioDev->stopProcessing(); } } sample_rate_t Mixer::baseSampleRate() const { sample_rate_t sr = configManager::inst()->value( "mixer", "samplerate" ).toInt(); if( sr < 44100 ) { sr = 44100; } return sr; } sample_rate_t Mixer::outputSampleRate() const { return m_audioDev != NULL ? m_audioDev->sampleRate() : baseSampleRate(); } sample_rate_t Mixer::inputSampleRate() const { return m_audioDev != NULL ? m_audioDev->sampleRate() : baseSampleRate(); } sample_rate_t Mixer::processingSampleRate() const { return outputSampleRate() * m_qualitySettings.sampleRateMultiplier(); } bool Mixer::criticalXRuns() const { return m_cpuLoad >= 99 && engine::getSong()->isExporting() == false; } void Mixer::pushInputFrames( sampleFrame * _ab, const f_cnt_t _frames ) { lockInputFrames(); f_cnt_t frames = m_inputBufferFrames[ m_inputBufferWrite ]; int size = m_inputBufferSize[ m_inputBufferWrite ]; sampleFrame * buf = m_inputBuffer[ m_inputBufferWrite ]; if( frames + _frames > size ) { size = qMax( size * 2, frames + _frames ); sampleFrame * ab = new sampleFrame[ size ]; memcpy( ab, buf, frames * sizeof( sampleFrame ) ); delete [] buf; m_inputBufferSize[ m_inputBufferWrite ] = size; m_inputBuffer[ m_inputBufferWrite ] = ab; buf = ab; } memcpy( &buf[ frames ], _ab, _frames * sizeof( sampleFrame ) ); m_inputBufferFrames[ m_inputBufferWrite ] += _frames; unlockInputFrames(); } const surroundSampleFrame * Mixer::renderNextBuffer() { MicroTimer timer; static song::playPos last_metro_pos = -1; song::playPos p = engine::getSong()->getPlayPos( song::Mode_PlayPattern ); if( engine::getSong()->playMode() == song::Mode_PlayPattern && engine::pianoRoll()->isRecording() == true && p != last_metro_pos && p.getTicks() % (DefaultTicksPerTact / 4 ) == 0 ) { addPlayHandle( new SamplePlayHandle( "misc/metronome01.ogg" ) ); last_metro_pos = p; } lockInputFrames(); // swap buffer m_inputBufferWrite = ( m_inputBufferWrite + 1 ) % 2; m_inputBufferRead = ( m_inputBufferRead + 1 ) % 2; // clear new write buffer m_inputBufferFrames[ m_inputBufferWrite ] = 0; unlockInputFrames(); // now we have to make sure no other thread does anything bad // while we're acting... lock(); // remove all play-handles that have to be deleted and delete // them if they still exist... // maybe this algorithm could be optimized... ConstPlayHandleList::Iterator it_rem = m_playHandlesToRemove.begin(); while( it_rem != m_playHandlesToRemove.end() ) { PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), *it_rem ); if( it != m_playHandles.end() ) { delete *it; m_playHandles.erase( it ); } it_rem = m_playHandlesToRemove.erase( it_rem ); } // rotate buffers m_writeBuffer = ( m_writeBuffer + 1 ) % m_poolDepth; m_readBuffer = ( m_readBuffer + 1 ) % m_poolDepth; m_writeBuf = m_bufferPool[m_writeBuffer]; m_readBuf = m_bufferPool[m_readBuffer]; // clear last audio-buffer clearAudioBuffer( m_writeBuf, m_framesPerPeriod ); // prepare master mix (clear internal buffers etc.) engine::fxMixer()->prepareMasterMix(); // create play-handles for new notes, samples etc. engine::getSong()->processNextBuffer(); // STAGE 1: run and render all play handles FILL_JOB_QUEUE(PlayHandleList,m_playHandles,MixerWorkerThread::PlayHandle, !( *it )->isFinished()); START_JOBS(); WAIT_FOR_JOBS(); // removed all play handles which are done for( PlayHandleList::Iterator it = m_playHandles.begin(); it != m_playHandles.end(); ) { if( ( *it )->affinityMatters() && ( *it )->affinity() != QThread::currentThread() ) { ++it; continue; } if( ( *it )->isFinished() ) { delete *it; it = m_playHandles.erase( it ); } else { ++it; } } // STAGE 2: process effects of all instrument- and sampletracks FILL_JOB_QUEUE(QVector,m_audioPorts, MixerWorkerThread::AudioPortEffects,1); START_JOBS(); WAIT_FOR_JOBS(); // STAGE 3: process effects in FX mixer FILL_JOB_QUEUE_PARAM(QVector,__fx_channel_jobs, MixerWorkerThread::EffectChannel,1); START_JOBS(); WAIT_FOR_JOBS(); // STAGE 4: do master mix in FX mixer engine::fxMixer()->masterMix( m_writeBuf ); unlock(); emit nextAudioBuffer(); // and trigger LFOs EnvelopeAndLfoParameters::instances()->trigger(); Controller::triggerFrameCounter(); const float new_cpu_load = timer.elapsed() / 10000.0f * processingSampleRate() / m_framesPerPeriod; m_cpuLoad = tLimit( (int) ( new_cpu_load * 0.1f + m_cpuLoad * 0.9f ), 0, 100 ); return m_readBuf; } // removes all play-handles. this is necessary, when the song is stopped -> // all remaining notes etc. would be played until their end void Mixer::clear() { // TODO: m_midiClient->noteOffAll(); lock(); for( PlayHandleList::Iterator it = m_playHandles.begin(); it != m_playHandles.end(); ++it ) { // we must not delete instrument-play-handles as they exist // during the whole lifetime of an instrument if( ( *it )->type() != PlayHandle::TypeInstrumentPlayHandle ) { m_playHandlesToRemove.push_back( *it ); } } unlock(); } void Mixer::bufferToPort( const sampleFrame * _buf, const fpp_t _frames, const f_cnt_t _offset, stereoVolumeVector _vv, AudioPort * _port ) { const int start_frame = _offset % m_framesPerPeriod; int end_frame = start_frame + _frames; const int loop1_frame = qMin( end_frame, m_framesPerPeriod ); _port->lockFirstBuffer(); MixHelpers::addMultipliedStereo( _port->firstBuffer()+start_frame, // dst _buf, // src _vv.vol[0], _vv.vol[1], // coeff left/right loop1_frame - start_frame ); // frame count _port->unlockFirstBuffer(); _port->lockSecondBuffer(); if( end_frame > m_framesPerPeriod ) { const int frames_done = m_framesPerPeriod - start_frame; end_frame -= m_framesPerPeriod; end_frame = qMin( end_frame, m_framesPerPeriod ); MixHelpers::addMultipliedStereo( _port->secondBuffer(), // dst _buf+frames_done, // src _vv.vol[0], _vv.vol[1], // coeff left/right end_frame ); // frame count // we used both buffers so set flags _port->m_bufferUsage = AudioPort::BothBuffers; } else if( _port->m_bufferUsage == AudioPort::NoUsage ) { // only first buffer touched _port->m_bufferUsage = AudioPort::FirstBuffer; } _port->unlockSecondBuffer(); } void Mixer::clearAudioBuffer( sampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { memset( _ab+_offset, 0, sizeof( *_ab ) * _frames ); } #ifndef LMMS_DISABLE_SURROUND void Mixer::clearAudioBuffer( surroundSampleFrame * _ab, const f_cnt_t _frames, const f_cnt_t _offset ) { memset( _ab+_offset, 0, sizeof( *_ab ) * _frames ); } #endif float Mixer::peakValueLeft( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) { if( _ab[f][0] > p ) { p = _ab[f][0]; } else if( -_ab[f][0] > p ) { p = -_ab[f][0]; } } return p; } float Mixer::peakValueRight( sampleFrame * _ab, const f_cnt_t _frames ) { float p = 0.0f; for( f_cnt_t f = 0; f < _frames; ++f ) { if( _ab[f][1] > p ) { p = _ab[f][1]; } else if( -_ab[f][1] > p ) { p = -_ab[f][1]; } } return p; } void Mixer::changeQuality( const struct qualitySettings & _qs ) { // don't delete the audio-device stopProcessing(); m_qualitySettings = _qs; m_audioDev->applyQualitySettings(); emit sampleRateChanged(); emit qualitySettingsChanged(); startProcessing(); } void Mixer::setAudioDevice( AudioDevice * _dev ) { stopProcessing(); m_oldAudioDev = m_audioDev; if( _dev == NULL ) { printf( "param _dev == NULL in Mixer::setAudioDevice(...). " "Trying any working audio-device\n" ); m_audioDev = tryAudioDevices(); } else { m_audioDev = _dev; } emit sampleRateChanged(); startProcessing(); } void Mixer::setAudioDevice( AudioDevice * _dev, const struct qualitySettings & _qs, bool _needs_fifo ) { // don't delete the audio-device stopProcessing(); m_qualitySettings = _qs; m_oldAudioDev = m_audioDev; if( _dev == NULL ) { printf( "param _dev == NULL in Mixer::setAudioDevice(...). " "Trying any working audio-device\n" ); m_audioDev = tryAudioDevices(); } else { m_audioDev = _dev; } emit qualitySettingsChanged(); emit sampleRateChanged(); startProcessing( _needs_fifo ); } void Mixer::restoreAudioDevice() { if( m_oldAudioDev != NULL ) { stopProcessing(); delete m_audioDev; m_audioDev = m_oldAudioDev; emit sampleRateChanged(); m_oldAudioDev = NULL; startProcessing(); } } void Mixer::removeAudioPort( AudioPort * _port ) { QVector::Iterator it = qFind( m_audioPorts.begin(), m_audioPorts.end(), _port ); if( it != m_audioPorts.end() ) { lock(); m_audioPorts.erase( it ); unlock(); } } void Mixer::removePlayHandle( PlayHandle * _ph ) { lock(); // check thread affinity as we must not delete play-handles // which were created in a thread different than mixer thread if( _ph->affinityMatters() && _ph->affinity() == QThread::currentThread() ) { PlayHandleList::Iterator it = qFind( m_playHandles.begin(), m_playHandles.end(), _ph ); if( it != m_playHandles.end() ) { m_playHandles.erase( it ); delete _ph; } } else { m_playHandlesToRemove.push_back( _ph ); } unlock(); } void Mixer::removePlayHandles( track * _track ) { lock(); PlayHandleList::Iterator it = m_playHandles.begin(); while( it != m_playHandles.end() ) { if( ( *it )->isFromTrack( _track ) ) { delete *it; it = m_playHandles.erase( it ); } else { ++it; } } unlock(); } bool Mixer::hasNotePlayHandles() { lock(); for( PlayHandleList::Iterator it = m_playHandles.begin(); it != m_playHandles.end(); ++it ) { if( (*it)->type() == PlayHandle::TypeNotePlayHandle ) { unlock(); return true; } } unlock(); return false; } AudioDevice * Mixer::tryAudioDevices() { bool success_ful = false; AudioDevice * dev = NULL; QString dev_name = configManager::inst()->value( "mixer", "audiodev" ); if( dev_name == AudioDummy::name() ) { dev_name = ""; } #ifdef LMMS_HAVE_ALSA if( dev_name == AudioAlsa::name() || dev_name == "" ) { dev = new AudioAlsa( success_ful, this ); if( success_ful ) { m_audioDevName = AudioAlsa::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_PULSEAUDIO if( dev_name == AudioPulseAudio::name() || dev_name == "" ) { dev = new AudioPulseAudio( success_ful, this ); if( success_ful ) { m_audioDevName = AudioPulseAudio::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_OSS if( dev_name == AudioOss::name() || dev_name == "" ) { dev = new AudioOss( success_ful, this ); if( success_ful ) { m_audioDevName = AudioOss::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_JACK if( dev_name == AudioJack::name() || dev_name == "" ) { dev = new AudioJack( success_ful, this ); if( success_ful ) { m_audioDevName = AudioJack::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_SDL if( dev_name == AudioSdl::name() || dev_name == "" ) { dev = new AudioSdl( success_ful, this ); if( success_ful ) { m_audioDevName = AudioSdl::name(); return dev; } delete dev; } #endif #ifdef LMMS_HAVE_PORTAUDIO if( dev_name == AudioPortAudio::name() || dev_name == "" ) { dev = new AudioPortAudio( success_ful, this ); if( success_ful ) { m_audioDevName = AudioPortAudio::name(); return dev; } delete dev; } #endif // add more device-classes here... //dev = new audioXXXX( SAMPLE_RATES[m_qualityLevel], success_ful, this ); //if( sucess_ful ) //{ // return dev; //} //delete dev printf( "No audio-driver working - falling back to dummy-audio-" "driver\nYou can render your songs and listen to the output " "files...\n" ); m_audioDevName = AudioDummy::name(); return new AudioDummy( success_ful, this ); } MidiClient * Mixer::tryMidiClients() { QString client_name = configManager::inst()->value( "mixer", "mididev" ); #ifdef LMMS_HAVE_ALSA if( client_name == MidiAlsaSeq::name() || client_name == "" ) { MidiAlsaSeq * malsas = new MidiAlsaSeq; if( malsas->isRunning() ) { m_midiClientName = MidiAlsaSeq::name(); return malsas; } delete malsas; } if( client_name == MidiAlsaRaw::name() || client_name == "" ) { MidiAlsaRaw * malsar = new MidiAlsaRaw; if( malsar->isRunning() ) { m_midiClientName = MidiAlsaRaw::name(); return malsar; } delete malsar; } #endif #ifdef LMMS_HAVE_OSS if( client_name == MidiOss::name() || client_name == "" ) { MidiOss * moss = new MidiOss; if( moss->isRunning() ) { m_midiClientName = MidiOss::name(); return moss; } delete moss; } #endif #ifdef LMMS_BUILD_WIN32 if( client_name == MidiWinMM::name() || client_name == "" ) { MidiWinMM * mwmm = new MidiWinMM; // if( moss->isRunning() ) { m_midiClientName = MidiWinMM::name(); return mwmm; } delete mwmm; } #endif printf( "Couldn't create MIDI-client, neither with ALSA nor with " "OSS. Will use dummy-MIDI-client.\n" ); m_midiClientName = MidiDummy::name(); return new MidiDummy; } Mixer::fifoWriter::fifoWriter( Mixer* mixer, fifo * _fifo ) : m_mixer( mixer ), m_fifo( _fifo ), m_writing( true ) { } void Mixer::fifoWriter::finish() { m_writing = false; } void Mixer::fifoWriter::run() { #if 0 #ifdef LMMS_BUILD_LINUX #ifdef LMMS_HAVE_SCHED_H cpu_set_t mask; CPU_ZERO( &mask ); CPU_SET( 0, &mask ); sched_setaffinity( 0, sizeof( mask ), &mask ); #endif #endif #endif const fpp_t frames = m_mixer->framesPerPeriod(); while( m_writing ) { surroundSampleFrame * buffer = new surroundSampleFrame[frames]; const surroundSampleFrame * b = m_mixer->renderNextBuffer(); memcpy( buffer, b, frames * sizeof( surroundSampleFrame ) ); m_fifo->write( buffer ); } m_fifo->write( NULL ); } #include "moc_Mixer.cxx" lmms-1.0.0/src/core/Oscillator.cpp0000644000175000017500000002731212313663627015550 0ustar tobytoby/* * Oscillator.cpp - implementation of powerful oscillator-class * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "Oscillator.h" #include "engine.h" #include "Mixer.h" #include "AutomatableModel.h" Oscillator::Oscillator( const IntModel * _wave_shape_model, const IntModel * _mod_algo_model, const float & _freq, const float & _detuning, const float & _phase_offset, const float & _volume, Oscillator * _sub_osc ) : m_waveShapeModel( _wave_shape_model ), m_modulationAlgoModel( _mod_algo_model ), m_freq( _freq ), m_detuning( _detuning ), m_volume( _volume ), m_ext_phaseOffset( _phase_offset ), m_subOsc( _sub_osc ), m_phaseOffset( _phase_offset ), m_phase( _phase_offset ), m_userWave( NULL ) { } void Oscillator::update( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { if( m_freq >= engine::mixer()->processingSampleRate() / 2 ) { Mixer::clearAudioBuffer( _ab, _frames ); return; } if( m_subOsc != NULL ) { switch( m_modulationAlgoModel->value() ) { case PhaseModulation: updatePM( _ab, _frames, _chnl ); break; case AmplitudeModulation: updateAM( _ab, _frames, _chnl ); break; case SignalMix: updateMix( _ab, _frames, _chnl ); break; case SynchronizedBySubOsc: updateSync( _ab, _frames, _chnl ); break; case FrequencyModulation: updateFM( _ab, _frames, _chnl ); } } else { updateNoSub( _ab, _frames, _chnl ); } } void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { switch( m_waveShapeModel->value() ) { case SineWave: default: updateNoSub( _ab, _frames, _chnl ); break; case TriangleWave: updateNoSub( _ab, _frames, _chnl ); break; case SawWave: updateNoSub( _ab, _frames, _chnl ); break; case SquareWave: updateNoSub( _ab, _frames, _chnl ); break; case MoogSawWave: updateNoSub( _ab, _frames, _chnl ); break; case ExponentialWave: updateNoSub( _ab, _frames, _chnl ); break; case WhiteNoise: updateNoSub( _ab, _frames, _chnl ); break; case UserDefinedWave: updateNoSub( _ab, _frames, _chnl ); break; } } void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { switch( m_waveShapeModel->value() ) { case SineWave: default: updatePM( _ab, _frames, _chnl ); break; case TriangleWave: updatePM( _ab, _frames, _chnl ); break; case SawWave: updatePM( _ab, _frames, _chnl ); break; case SquareWave: updatePM( _ab, _frames, _chnl ); break; case MoogSawWave: updatePM( _ab, _frames, _chnl ); break; case ExponentialWave: updatePM( _ab, _frames, _chnl ); break; case WhiteNoise: updatePM( _ab, _frames, _chnl ); break; case UserDefinedWave: updatePM( _ab, _frames, _chnl ); break; } } void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { switch( m_waveShapeModel->value() ) { case SineWave: default: updateAM( _ab, _frames, _chnl ); break; case TriangleWave: updateAM( _ab, _frames, _chnl ); break; case SawWave: updateAM( _ab, _frames, _chnl ); break; case SquareWave: updateAM( _ab, _frames, _chnl ); break; case MoogSawWave: updateAM( _ab, _frames, _chnl ); break; case ExponentialWave: updateAM( _ab, _frames, _chnl ); break; case WhiteNoise: updateAM( _ab, _frames, _chnl ); break; case UserDefinedWave: updateAM( _ab, _frames, _chnl ); break; } } void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { switch( m_waveShapeModel->value() ) { case SineWave: default: updateMix( _ab, _frames, _chnl ); break; case TriangleWave: updateMix( _ab, _frames, _chnl ); break; case SawWave: updateMix( _ab, _frames, _chnl ); break; case SquareWave: updateMix( _ab, _frames, _chnl ); break; case MoogSawWave: updateMix( _ab, _frames, _chnl ); break; case ExponentialWave: updateMix( _ab, _frames, _chnl ); break; case WhiteNoise: updateMix( _ab, _frames, _chnl ); break; case UserDefinedWave: updateMix( _ab, _frames, _chnl ); break; } } void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { switch( m_waveShapeModel->value() ) { case SineWave: default: updateSync( _ab, _frames, _chnl ); break; case TriangleWave: updateSync( _ab, _frames, _chnl ); break; case SawWave: updateSync( _ab, _frames, _chnl ); break; case SquareWave: updateSync( _ab, _frames, _chnl ); break; case MoogSawWave: updateSync( _ab, _frames, _chnl ); break; case ExponentialWave: updateSync( _ab, _frames, _chnl ); break; case WhiteNoise: updateSync( _ab, _frames, _chnl ); break; case UserDefinedWave: updateSync( _ab, _frames, _chnl ); break; } } void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { switch( m_waveShapeModel->value() ) { case SineWave: default: updateFM( _ab, _frames, _chnl ); break; case TriangleWave: updateFM( _ab, _frames, _chnl ); break; case SawWave: updateFM( _ab, _frames, _chnl ); break; case SquareWave: updateFM( _ab, _frames, _chnl ); break; case MoogSawWave: updateFM( _ab, _frames, _chnl ); break; case ExponentialWave: updateFM( _ab, _frames, _chnl ); break; case WhiteNoise: updateFM( _ab, _frames, _chnl ); break; case UserDefinedWave: updateFM( _ab, _frames, _chnl ); break; } } // should be called every time phase-offset is changed... inline void Oscillator::recalcPhase() { if( !typeInfo::isEqual( m_phaseOffset, m_ext_phaseOffset ) ) { m_phase -= m_phaseOffset; m_phaseOffset = m_ext_phaseOffset; m_phase += m_phaseOffset; } m_phase = absFraction( m_phase )+2; // make sure we're not running // negative when doing PM } inline bool Oscillator::syncOk( float _osc_coeff ) { const float v1 = m_phase; m_phase += _osc_coeff; // check whether m_phase is in next period return( floorf( m_phase ) > floorf( v1 ) ); } float Oscillator::syncInit( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { if( m_subOsc != NULL ) { m_subOsc->update( _ab, _frames, _chnl ); } recalcPhase(); return( m_freq * m_detuning ); } // if we have no sub-osc, we can't do any modulation... just get our samples template void Oscillator::updateNoSub( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { recalcPhase(); const float osc_coeff = m_freq * m_detuning; for( fpp_t frame = 0; frame < _frames; ++frame ) { _ab[frame][_chnl] = getSample( m_phase ) * m_volume; m_phase += osc_coeff; } } // do pm by using sub-osc as modulator template void Oscillator::updatePM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { m_subOsc->update( _ab, _frames, _chnl ); recalcPhase(); const float osc_coeff = m_freq * m_detuning; for( fpp_t frame = 0; frame < _frames; ++frame ) { _ab[frame][_chnl] = getSample( m_phase + _ab[frame][_chnl] ) * m_volume; m_phase += osc_coeff; } } // do am by using sub-osc as modulator template void Oscillator::updateAM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { m_subOsc->update( _ab, _frames, _chnl ); recalcPhase(); const float osc_coeff = m_freq * m_detuning; for( fpp_t frame = 0; frame < _frames; ++frame ) { _ab[frame][_chnl] *= getSample( m_phase ) * m_volume; m_phase += osc_coeff; } } // do mix by using sub-osc as mix-sample template void Oscillator::updateMix( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { m_subOsc->update( _ab, _frames, _chnl ); recalcPhase(); const float osc_coeff = m_freq * m_detuning; for( fpp_t frame = 0; frame < _frames; ++frame ) { _ab[frame][_chnl] += getSample( m_phase ) * m_volume; m_phase += osc_coeff; } } // sync with sub-osc (every time sub-osc starts new period, we also start new // period) template void Oscillator::updateSync( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { const float sub_osc_coeff = m_subOsc->syncInit( _ab, _frames, _chnl ); recalcPhase(); const float osc_coeff = m_freq * m_detuning; for( fpp_t frame = 0; frame < _frames ; ++frame ) { if( m_subOsc->syncOk( sub_osc_coeff ) ) { m_phase = m_phaseOffset; } _ab[frame][_chnl] = getSample( m_phase ) * m_volume; m_phase += osc_coeff; } } // do fm by using sub-osc as modulator template void Oscillator::updateFM( sampleFrame * _ab, const fpp_t _frames, const ch_cnt_t _chnl ) { m_subOsc->update( _ab, _frames, _chnl ); recalcPhase(); const float osc_coeff = m_freq * m_detuning; const float sampleRateCorrection = 44100.0f / engine::mixer()->processingSampleRate(); for( fpp_t frame = 0; frame < _frames; ++frame ) { m_phase += _ab[frame][_chnl] * sampleRateCorrection; _ab[frame][_chnl] = getSample( m_phase ) * m_volume; m_phase += osc_coeff; } } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( sinSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( triangleSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( sawSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( squareSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( moogSawSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( expSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( noiseSample( _sample ) ); } template<> inline sample_t Oscillator::getSample( const float _sample ) { return( userWaveSample( _sample ) ); } lmms-1.0.0/src/core/fft_helpers.cpp0000644000175000017500000001221112313663627015726 0ustar tobytoby/* * fft_helpers.cpp - some functions around FFT analysis * * Copyright (c) 2008-2012 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "fft_helpers.h" #include /* returns biggest value from abs_spectrum[spec_size] array returns -1 on error */ float maximum(float *abs_spectrum, unsigned int spec_size) { float maxi=0; unsigned int i; if ( abs_spectrum==NULL ) return -1; if (spec_size<=0) return -1; for ( i=0; imaxi ) maxi=abs_spectrum[i]; } return maxi; } /* apply hanning or hamming window to channel returns -1 on error */ int hanming(float *timebuffer, int length, WINDOWS type) { int i; float alpha; if ( (timebuffer==NULL)||(length<=0) ) return -1; switch (type) { case HAMMING: alpha=0.54; break; case HANNING: default: alpha=0.5; break; } for ( i=0; i num_new returns 0 on success, else -1 */ int compressbands(float *absspec_buffer, float *compressedband, int num_old, int num_new, int bottom, int top) { float ratio; int i, usefromold; float j; float j_min, j_max; if ( (absspec_buffer==NULL)||(compressedband==NULL) ) return -1; if ( num_old=num_old ) top=num_old-1; usefromold=num_old-(num_old-top)-bottom; ratio=(float)usefromold/(float)num_new; // foreach new subband for ( i=0; i * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "SampleRecordHandle.h" #include "bb_track.h" #include "engine.h" #include "InstrumentTrack.h" #include "pattern.h" #include "SampleBuffer.h" #include "SampleTrack.h" SampleRecordHandle::SampleRecordHandle( SampleTCO* tco ) : PlayHandle( TypeSamplePlayHandle ), m_framesRecorded( 0 ), m_minLength( tco->length() ), m_track( tco->getTrack() ), m_bbTrack( NULL ), m_tco( tco ) { } SampleRecordHandle::~SampleRecordHandle() { if( !m_buffers.empty() ) { SampleBuffer* sb; createSampleBuffer( &sb ); m_tco->setSampleBuffer( sb ); } while( !m_buffers.empty() ) { delete[] m_buffers.front().first; m_buffers.erase( m_buffers.begin() ); } m_tco->setRecord( false ); } void SampleRecordHandle::play( sampleFrame * /*_working_buffer*/ ) { const sampleFrame * recbuf = engine::mixer()->inputBuffer(); const f_cnt_t frames = engine::mixer()->inputBufferFrames(); writeBuffer( recbuf, frames ); m_framesRecorded += frames; MidiTime len = (tick_t)( m_framesRecorded / engine::framesPerTick() ); if( len > m_minLength ) { // m_tco->changeLength( len ); m_minLength = len; } } bool SampleRecordHandle::isFinished() const { return false; } bool SampleRecordHandle::isFromTrack( const track * _track ) const { return( m_track == _track || m_bbTrack == _track ); } f_cnt_t SampleRecordHandle::framesRecorded() const { return( m_framesRecorded ); } void SampleRecordHandle::createSampleBuffer( SampleBuffer** sampleBuf ) { const f_cnt_t frames = framesRecorded(); // create buffer to store all recorded buffers in sampleFrame * data = new sampleFrame[frames]; // make sure buffer is cleaned up properly at the end... sampleFrame * data_ptr = data; #ifdef LMMS_DEBUG assert( data != NULL ); #endif // now copy all buffers into big buffer for( bufferList::const_iterator it = m_buffers.begin(); it != m_buffers.end(); ++it ) { memcpy( data_ptr, ( *it ).first, ( *it ).second * sizeof( sampleFrame ) ); data_ptr += ( *it ).second; } // create according sample-buffer out of big buffer *sampleBuf = new SampleBuffer( data, frames ); ( *sampleBuf)->setSampleRate( engine::mixer()->inputSampleRate() ); delete[] data; } void SampleRecordHandle::writeBuffer( const sampleFrame * _ab, const f_cnt_t _frames ) { sampleFrame * buf = new sampleFrame[_frames]; for( f_cnt_t frame = 0; frame < _frames; ++frame ) { for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) { buf[frame][chnl] = _ab[frame][chnl]; } } m_buffers.push_back( qMakePair( buf, _frames ) ); } lmms-1.0.0/src/core/Clipboard.cpp0000644000175000017500000000267512313663627015341 0ustar tobytoby/* * Clipboard.cpp - the clipboard for patterns, notes etc. * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "Clipboard.h" #include "JournallingObject.h" Clipboard::Map Clipboard::content; void Clipboard::copy( JournallingObject * _obj ) { QDomDocument doc; QDomElement parent = doc.createElement( "Clipboard" ); _obj->saveState( doc, parent ); content[_obj->nodeName()] = parent.firstChild().toElement(); } const QDomElement * Clipboard::getContent( const QString & _node_name ) { if( content.find( _node_name ) != content.end() ) { return &content[_node_name]; } return NULL; } lmms-1.0.0/src/core/InlineAutomation.cpp0000644000175000017500000000325312313663627016712 0ustar tobytoby/* * InlineAutomation.cpp - class for automating something "inline" * * Copyright (c) 2008-2010 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "InlineAutomation.h" void InlineAutomation::saveSettings( QDomDocument & _doc, QDomElement & _parent ) { if( hasAutomation() ) { QDomElement ap = _doc.createElement( AutomationPattern::classNodeName() ); QDomElement v = _doc.createElement( nodeName() ); automationPattern()->saveSettings( _doc, v ); ap.appendChild( v ); _parent.appendChild( ap ); } } void InlineAutomation::loadSettings( const QDomElement & _this ) { QDomNode node = _this.namedItem( AutomationPattern::classNodeName() ); if( node.isElement() ) { node = node.namedItem( nodeName() ); if( node.isElement() ) { automationPattern()->loadSettings( node.toElement() ); } } } lmms-1.0.0/src/core/drumsynth.cpp0000644000175000017500000005366312313663627015502 0ustar tobytoby/* * drumsynth.cpp - DrumSynth DS file renderer * * Copyright (c) 1998-2000 Paul Kellett (mda-vst.com) * Copyright (c) 2007 Paul Giblock * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "drumsynth.h" #include "lmmsconfig.h" #include #include #include #include //sin(), exp(), etc. #include //sscanf(), sprintf() #include //RAND_MAX #ifdef LMMS_BUILD_WIN32 #define powf pow #endif using namespace std; #define WORD __u16 #define DWORD __u32 #define WAVE_FORMAT_PCM 0x0001 // const int Fs = 44100; const float TwoPi = 6.2831853f; const int MAX = 0; const int ENV = 1; const int PNT = 2; const int dENV = 3; const int NEXTT = 4; // Bah, I'll move these into the class once I sepearate DrumsynthFile from DrumSynth // llama float envpts[8][3][32]; //envelope/time-level/point float envData[8][6]; //envelope running status int chkOn[8], sliLev[8]; //section on/off and level float timestretch; //overall time scaling short DD[1200], clippoint; float DF[1200]; float phi[1200]; long wavewords, wavemode=0; float mem_t=1.0f, mem_o=1.0f, mem_n=1.0f, mem_b=1.0f, mem_tune=1.0f, mem_time=1.0f; int DrumSynth::LongestEnv(void) { long e, eon, p; float l=0.f; for(e=1; e<7; e++) //3 { eon = e - 1; if(eon>2) eon=eon-1; p = 0; while (envpts[e][0][p + 1] >= 0.f) p++; envData[e][MAX] = envpts[e][0][p] * timestretch; if(chkOn[eon]==1) if(envData[e][MAX]>l) l=envData[e][MAX]; } //l *= timestretch; return 2400 + (1200 * (int)(l / 1200)); } float DrumSynth::LoudestEnv(void) { float loudest=0.f; int i=0; while (i<5) //2 { if(chkOn[i]==1) if(sliLev[i]>loudest) loudest=(float)sliLev[i]; i++; } return (loudest * loudest); } void DrumSynth::UpdateEnv(int e, long t) { float endEnv, dT; //0.2's added envData[e][NEXTT] = envpts[e][0][(long)(envData[e][PNT] + 1.f)] * timestretch; //get next point if(envData[e][NEXTT] < 0) envData[e][NEXTT] = 442000 * timestretch; //if end point, hold envData[e][ENV] = envpts[e][1][(long)(envData[e][PNT] + 0.f)] * 0.01f; //this level endEnv = envpts[e][1][(long)(envData[e][PNT] + 1.f)] * 0.01f; //next level dT = envData[e][NEXTT] - (float)t; if(dT < 1.0) dT = 1.0; envData[e][dENV] = (endEnv - envData[e][ENV]) / dT; envData[e][PNT] = envData[e][PNT] + 1.0f; } void DrumSynth::GetEnv(int env, const char *sec, const char *key, const char *ini) { char en[256], s[8]; int i=0, o=0, ep=0; GetPrivateProfileString(sec, key, "0,0 100,0", en, sizeof(en), ini); en[255]=0; //be safe! while(en[i]!=0) { if(en[i] == ',') { if(sscanf(s, "%f", &envpts[env][0][ep])==0) envpts[env][0][ep] = 0.f; o=0; } else if(en[i] == ' ') { if(sscanf(s, "%f", &envpts[env][1][ep])==0) envpts[env][1][ep] = 0.f; o=0; ep++; } else { s[o]=en[i]; o++; s[o]=0; } i++; } if(sscanf(s, "%f", &envpts[env][1][ep])==0) envpts[env][1][ep] = 0.f; envpts[env][0][ep + 1] = -1; envData[env][MAX] = envpts[env][0][ep]; } float DrumSynth::waveform(float ph, int form) { float w; switch (form) { case 0: w = (float)sin(fmod(ph,TwoPi)); break; //sine case 1: w = (float)fabs(2.0f*(float)sin(fmod(0.5f*ph,TwoPi)))-1.f; break; //sine^2 case 2: while(ph1.f) w=2.f-w; break; case 3: w = ph - TwoPi * (float)(int)(ph / TwoPi); //saw w = (0.3183098f * w) - 1.f; break; default: w = (sin(fmod(ph,TwoPi))>0.0)? 1.f: -1.f; break; //square } return w; } int DrumSynth::GetPrivateProfileString(const char *sec, const char *key, const char *def, char *buffer, int size, const char *file) { ifstream is; bool inSection = false; char *line; char *k, *b; int len = 0; line = (char*)malloc(200); is.open (file, ifstream::in); while (is.good()) { if (!inSection) { is.ignore( numeric_limits::max(), '['); if (!is.eof()) { is.getline(line, 200, ']'); if (strcasecmp(line, sec)==0) { inSection = true; } } } else if (!is.eof()) { is.getline(line, 200); if (line[0] == '[') break; k = strtok(line, " \t="); b = strtok(NULL, "\n\r\0"); if (k != 0 && strcasecmp(k, key)==0) { if (b==0) { len = 0; buffer[0] = 0; } else { k = (char *)(b + strlen(b)-1); while ( (k>=b) && (*k==' ' || *k=='\t') ) --k; *(k+1) = '\0'; len = strlen(b); if (len > size-1) len = size-1; strncpy(buffer, b, len+1); } break; } } } if (len == 0) { len = strlen(def); strncpy(buffer, def, size); } is.close(); free(line); return len; } int DrumSynth::GetPrivateProfileInt(const char *sec, const char *key, int def, const char *file) { char tmp[16]; int i=0; GetPrivateProfileString(sec, key, "", tmp, sizeof(tmp), file); sscanf(tmp, "%d", &i); if(tmp[0]==0) i=def; return i; } float DrumSynth::GetPrivateProfileFloat(const char *sec, const char *key, float def, const char *file) { char tmp[16]; float f=0.f; GetPrivateProfileString(sec, key, "", tmp, sizeof(tmp), file); sscanf(tmp, "%f", &f); if(tmp[0]==0) f=def; return f; } // Constantly opening and scanning each file for the setting really sucks. // But, the original did this, and we know it works. Will store file into // an associative array or something once we have a datastructure to load in to. // llama int DrumSynth::GetDSFileSamples(const char *dsfile, int16_t *&wave, int channels, sample_rate_t Fs) { //input file char sec[32]; char ver[32]; char comment[256]; int commentLen=0; //generation long Length, tpos=0, tplus, totmp, t, i, j; float x[3] = {0.f, 0.f, 0.f}; float MasterTune, randmax, randmax2; int MainFilter, HighPass; long NON, NT, TON, DiON, TDroop=0, DStep; float a, b=0.f, c=0.f, d=0.f, g, TT=0.f, TL, NL, F1, F2; float TphiStart=0.f, Tphi, TDroopRate, ddF, DAtten, DGain; long BON, BON2, BFStep, BFStep2, botmp; float BdF=0.f, BdF2=0.f, BPhi, BPhi2, BF, BF2, BQ, BQ2, BL, BL2; long OON, OF1Sync=0, OF2Sync=0, OMode, OW1, OW2; float Ophi1, Ophi2, OF1, OF2, OL, Ot=0 /*PG: init */, OBal1, OBal2, ODrive; float Ocf1, Ocf2, OcF, OcQ, OcA, Oc[6][2]; //overtone cymbal mode float Oc0=0.0f, Oc1=0.0f, Oc2=0.0f; float MFfb, MFtmp, MFres, MFin=0.f, MFout=0.f; float DownAve; long DownStart, DownEnd, jj; if(wavemode==0) //semi-real-time adjustments if working in memory!! { mem_t = 1.0f; mem_o = 1.0f; mem_n = 1.0f; mem_b = 1.0f; mem_tune = 0.0f; mem_time = 1.0f; } //try to read version from input file strcpy(sec, "General"); GetPrivateProfileString(sec,"Version","",ver,sizeof(ver),dsfile); ver[9]=0; if(strcasecmp(ver, "DrumSynth") != 0) {return 0;} //input fail if(ver[11] != '1' && ver[11] != '2') {return 0;} //version fail //read master parameters GetPrivateProfileString(sec,"Comment","",comment,sizeof(comment),dsfile); while((comment[commentLen]!=0) && (commentLen<254)) commentLen++; if(commentLen==0) { comment[0]=32; comment[1]=0; commentLen=1;} comment[commentLen+1]=0; commentLen++; if((commentLen % 2)==1) commentLen++; timestretch = .01f * mem_time * GetPrivateProfileFloat(sec,"Stretch",100.0,dsfile); if(timestretch<0.2f) timestretch=0.2f; if(timestretch>10.f) timestretch=10.f; DGain = 1.0f; //leave this here! DGain = (float)powf(10.0, 0.05 * GetPrivateProfileFloat(sec,"Level",0,dsfile)); MasterTune = GetPrivateProfileFloat(sec,"Tuning",0.0,dsfile); MasterTune = (float)powf(1.0594631f, MasterTune + mem_tune); MainFilter = 2 * GetPrivateProfileInt(sec,"Filter",0,dsfile); MFres = 0.0101f * GetPrivateProfileFloat(sec,"Resonance",0.0,dsfile); MFres = (float)powf(MFres, 0.5f); HighPass = GetPrivateProfileInt(sec,"HighPass",0,dsfile); GetEnv(7, sec, "FilterEnv", dsfile); //read noise parameters strcpy(sec, "Noise"); chkOn[1] = GetPrivateProfileInt(sec,"On",0,dsfile); sliLev[1] = GetPrivateProfileInt(sec,"Level",0,dsfile); NT = GetPrivateProfileInt(sec,"Slope",0,dsfile); GetEnv(2, sec, "Envelope", dsfile); NON = chkOn[1]; NL = (float)(sliLev[1] * sliLev[1]) * mem_n; if(NT<0) { a = 1.f + (NT / 105.f); d = -NT / 105.f; g = (1.f + 0.0005f * NT * NT) * NL; } else { a = 1.f; b = -NT / 50.f; c = (float)fabs((float)NT) / 100.f; g = NL; } //if(GetPrivateProfileInt(sec,"FixedSeq",0,dsfile)!=0) //srand(1); //fixed random sequence //read tone parameters strcpy(sec, "Tone"); chkOn[0] = GetPrivateProfileInt(sec,"On",0,dsfile); TON = chkOn[0]; sliLev[0] = GetPrivateProfileInt(sec,"Level",128,dsfile); TL = (float)(sliLev[0] * sliLev[0]) * mem_t; GetEnv(1, sec, "Envelope", dsfile); F1 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F1",200.0,dsfile) / Fs; if(fabs(F1)<0.001f) F1=0.001f; //to prevent overtone ratio div0 F2 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F2",120.0,dsfile) / Fs; TDroopRate = GetPrivateProfileFloat(sec,"Droop",0.f,dsfile); if(TDroopRate>0.f) { TDroopRate = (float)powf(10.0f, (TDroopRate - 20.0f) / 30.0f); TDroopRate = TDroopRate * -4.f / envData[1][MAX]; TDroop = 1; F2 = F1+((F2-F1)/(1.f-(float)exp(TDroopRate * envData[1][MAX]))); ddF = F1 - F2; } else ddF = F2-F1; Tphi = GetPrivateProfileFloat(sec,"Phase",90.f,dsfile) / 57.29578f; //degrees>radians //read overtone parameters strcpy(sec, "Overtones"); chkOn[2] = GetPrivateProfileInt(sec,"On",0,dsfile); OON = chkOn[2]; sliLev[2] = GetPrivateProfileInt(sec,"Level",128,dsfile); OL = (float)(sliLev[2] * sliLev[2]) * mem_o; GetEnv(3, sec, "Envelope1", dsfile); GetEnv(4, sec, "Envelope2", dsfile); OMode = GetPrivateProfileInt(sec,"Method",2,dsfile); OF1 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F1",200.0,dsfile) / Fs; OF2 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F2",120.0,dsfile) / Fs; OW1 = GetPrivateProfileInt(sec,"Wave1",0,dsfile); OW2 = GetPrivateProfileInt(sec,"Wave2",0,dsfile); OBal2 = (float)GetPrivateProfileInt(sec,"Param",50,dsfile); ODrive = (float)powf(OBal2, 3.0f) / (float)powf(50.0f, 3.0f); OBal2 *= 0.01f; OBal1 = 1.f - OBal2; Ophi1 = Tphi; Ophi2 = Tphi; if(MainFilter==0) MainFilter = GetPrivateProfileInt(sec,"Filter",0,dsfile); if((GetPrivateProfileInt(sec,"Track1",0,dsfile)==1) && (TON==1)) { OF1Sync = 1; OF1 = OF1 / F1; } if((GetPrivateProfileInt(sec,"Track2",0,dsfile)==1) && (TON==1)) { OF2Sync = 1; OF2 = OF2 / F1; } OcA = 0.28f + OBal1 * OBal1; //overtone cymbal mode OcQ = OcA * OcA; OcF = (1.8f - 0.7f * OcQ) * 0.92f; //multiply by env 2 OcA *= 1.0f + 4.0f * OBal1; //level is a compromise! Ocf1 = TwoPi / OF1; Ocf2 = TwoPi / OF2; for(i=0; i<6; i++) Oc[i][0] = Oc[i][1] = Ocf1 + (Ocf2 - Ocf1) * 0.2f * (float)i; //read noise band parameters strcpy(sec, "NoiseBand"); chkOn[3] = GetPrivateProfileInt(sec,"On",0,dsfile); BON = chkOn[3]; sliLev[3] = GetPrivateProfileInt(sec,"Level",128,dsfile); BL = (float)(sliLev[3] * sliLev[3]) * mem_b; BF = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F",1000.0,dsfile) / Fs; BPhi = TwoPi / 8.f; GetEnv(5, sec, "Envelope", dsfile); BFStep = GetPrivateProfileInt(sec,"dF",50,dsfile); BQ = (float)BFStep; BQ = BQ * BQ / (10000.f-6600.f*((float)sqrt(BF)-0.19f)); BFStep = 1 + (int)((40.f - (BFStep / 2.5f)) / (BQ + 1.f + (1.f * BF))); strcpy(sec, "NoiseBand2"); chkOn[4] = GetPrivateProfileInt(sec,"On",0,dsfile); BON2 = chkOn[4]; sliLev[4] = GetPrivateProfileInt(sec,"Level",128,dsfile); BL2 = (float)(sliLev[4] * sliLev[4]) * mem_b; BF2 = MasterTune * TwoPi * GetPrivateProfileFloat(sec,"F",1000.0,dsfile) / Fs; BPhi2 = TwoPi / 8.f; GetEnv(6, sec, "Envelope", dsfile); BFStep2 = GetPrivateProfileInt(sec,"dF",50,dsfile); BQ2 = (float)BFStep2; BQ2 = BQ2 * BQ2 / (10000.f-6600.f*((float)sqrt(BF2)-0.19f)); BFStep2 = 1 + (int)((40 - (BFStep2 / 2.5)) / (BQ2 + 1 + (1 * BF2))); //read distortion parameters strcpy(sec, "Distortion"); chkOn[5] = GetPrivateProfileInt(sec,"On",0,dsfile); DiON = chkOn[5]; DStep = 1 + GetPrivateProfileInt(sec,"Rate",0,dsfile); if(DStep==7) DStep=20; if(DStep==6) DStep=10; if(DStep==5) DStep=8; clippoint = 32700; DAtten = 1.0f; if(DiON==1) { DAtten = DGain * (short)LoudestEnv(); if(DAtten>32700) clippoint=32700; else clippoint=(short)DAtten; DAtten = (float)powf(2.0, 2.0 * GetPrivateProfileInt(sec,"Bits",0,dsfile)); DGain = DAtten * DGain * (float)powf(10.0, 0.05 * GetPrivateProfileInt(sec,"Clipping",0,dsfile)); } //prepare envelopes randmax = 1.f / RAND_MAX; randmax2 = 2.f * randmax; for (i=1;i<8;i++) { envData[i][NEXTT]=0; envData[i][PNT]=0; } Length = LongestEnv(); //allocate the buffer //if(wave!=NULL) free(wave); //wave = new int16_t[channels * (Length + 1280)]; //wave memory buffer wave = new int16_t[channels * Length]; //wave memory buffer if(wave==NULL) {return 0;} wavewords = 0; /* if(wavemode==0) { //open output file fp = fopen(wavfile, "wb"); if(!fp) {return 3;} //output fail //set up INFO chunk WI.list = 0x5453494C; WI.listLength = 36 + commentLen; WI.info = 0x4F464E49; WI.isft = 0x54465349; WI.isftLength = 16; strcpy(WI.software, "DrumSynth v2.0 "); WI.software[15]=0; WI.icmt = 0x544D4349; WI.icmtLength = commentLen; //write WAV header WH.riff = 0x46464952; WH.riffLength = 36 + (2 * Length) + 44 + commentLen; WH.wave = 0x45564157; WH.fmt = 0x20746D66; WH.waveLength = 16; WH.wFormatTag = WAVE_FORMAT_PCM; WH.nChannels = 1; WH.nSamplesPerSec = Fs; WH.nAvgBytesPerSec = 2 * Fs; WH.nBlockAlign = 2; WH.wBitsPerSample = 16; WH.data = 0x61746164; WH.dataLength = 2 * Length; fwrite(&WH, 1, 44, fp); } */ //generate tpos = 0; while(tpos=envData[2][MAX]) NON=0; } else { for(j=0; j<1200; j++) DF[j]=0.f; } if(TON==1) //tone { TphiStart = Tphi; if(TDroop==1) { for(t=tpos; t<=tplus; t++) phi[t - tpos] = F2 + (ddF * (float)exp(t * TDroopRate)); } else { for(t=tpos; t<=tplus; t++) phi[t - tpos] = F1 + (t / envData[1][MAX]) * ddF; } for(t=tpos; t<=tplus; t++) { totmp = t - tpos; if(t < envData[1][NEXTT]) envData[1][ENV] = envData[1][ENV] + envData[1][dENV]; else UpdateEnv(1, t); Tphi = Tphi + phi[totmp]; DF[totmp] += TL * envData[1][ENV] * (float)sin(fmod(Tphi,TwoPi));//overflow? } if(t>=envData[1][MAX]) TON=0; } else for(j=0; j<1200; j++) phi[j]=F2; //for overtone sync if(BON==1) //noise band 1 { for(t=tpos; t<=tplus; t++) { if(t < envData[5][NEXTT]) envData[5][ENV] = envData[5][ENV] + envData[5][dENV]; else UpdateEnv(5, t); if((t % BFStep) == 0) BdF = randmax * (float)rand() - 0.5f; BPhi = BPhi + BF + BQ * BdF; botmp = t - tpos; DF[botmp] = DF[botmp] + (float)cos(fmod(BPhi,TwoPi)) * envData[5][ENV] * BL; } if(t>=envData[5][MAX]) BON=0; } if(BON2==1) //noise band 2 { for(t=tpos; t<=tplus; t++) { if(t < envData[6][NEXTT]) envData[6][ENV] = envData[6][ENV] + envData[6][dENV]; else UpdateEnv(6, t); if((t % BFStep2) == 0) BdF2 = randmax * (float)rand() - 0.5f; BPhi2 = BPhi2 + BF2 + BQ2 * BdF2; botmp = t - tpos; DF[botmp] = DF[botmp] + (float)cos(fmod(BPhi2,TwoPi)) * envData[6][ENV] * BL2; } if(t>=envData[6][MAX]) BON2=0; } for (t=tpos; t<=tplus; t++) { if(OON==1) //overtones { if(t=envData[3][MAX]) //wait for OT2 { envData[3][ENV] = 0; envData[3][dENV] = 0; envData[3][NEXTT] = 999999; } else UpdateEnv(3, t); } // if(t=envData[4][MAX]) //wait for OT1 { envData[4][ENV] = 0; envData[4][dENV] = 0; envData[4][NEXTT] = 999999; } else UpdateEnv(4, t); } // TphiStart = TphiStart + phi[t - tpos]; if(OF1Sync==1) Ophi1 = TphiStart * OF1; else Ophi1 = Ophi1 + OF1; if(OF2Sync==1) Ophi2 = TphiStart * OF2; else Ophi2 = Ophi2 + OF2; Ot=0.0f; switch (OMode) { case 0: //add Ot = OBal1 * envData[3][ENV] * waveform(Ophi1, OW1); Ot = OL * (Ot + OBal2 * envData[4][ENV] * waveform(Ophi2, OW2)); break; case 1: //FM Ot = ODrive * envData[4][ENV] * waveform(Ophi2, OW2); Ot = OL * envData[3][ENV] * waveform(Ophi1 + Ot, OW1); break; case 2: //RM Ot = (1 - ODrive / 8) + (((ODrive / 8) * envData[4][ENV]) * waveform(Ophi2, OW2)); Ot = OL * envData[3][ENV] * waveform(Ophi1, OW1) * Ot; break; case 3: //808 Cymbal for(j=0; j<6; j++) { Oc[j][0] += 1.0f; if(Oc[j][0]>Oc[j][1]) { Oc[j][0] -= Oc[j][1]; Ot = OL * envData[3][ENV]; } } Ocf1 = envData[4][ENV] * OcF; //filter freq Oc0 += Ocf1 * Oc1; Oc1 += Ocf1 * (Ot + Oc2 - OcQ * Oc1 - Oc0); //bpf Oc2 = Ot; Ot = Oc1; break; } } if(MainFilter==1) //filter overtones { if(t0.2f) MFfb = 1.001f - (float)powf(10.0f, MFtmp - 1); else MFfb = 0.999f - 0.7824f * MFtmp; MFtmp = Ot + MFres * (1.f + (1.f/MFfb)) * (MFin - MFout); MFin = MFfb * (MFin - MFtmp) + MFtmp; MFout = MFfb * (MFout - MFin) + MFin; DF[t - tpos] = DF[t - tpos] + (MFout - (HighPass * Ot)); } else if(MainFilter==2) //filter all { if(t0.2f) MFfb = 1.001f - (float)powf(10.0f, MFtmp - 1); else MFfb = 0.999f - 0.7824f * MFtmp; MFtmp = DF[t - tpos] + Ot + MFres * (1.f + (1.f/MFfb)) * (MFin - MFout); MFin = MFfb * (MFin - MFtmp) + MFtmp; MFout = MFfb * (MFout - MFin) + MFin; DF[t - tpos] = MFout - (HighPass * (DF[t - tpos] + Ot)); } // PG: Ot is uninitialized else DF[t - tpos] = DF[t - tpos] + Ot; //no filter } if(DiON==1) //bit resolution { for(j=0; j<1200; j++) DF[j] = DGain * (int)(DF[j] / DAtten); for(j=0; j<1200; j+=DStep) //downsampling { DownAve = 0; DownStart = j; DownEnd = j + DStep - 1; for(jj = DownStart; jj<=DownEnd; jj++) DownAve = DownAve + DF[jj]; DownAve = DownAve / DStep; for(jj = DownStart; jj<=DownEnd; jj++) DF[jj] = DownAve; } } else for(j=0; j<1200; j++) DF[j] *= DGain; for(j = 0; j<1200; j++) //clipping + output { if(DF[j] > clippoint) wave[wavewords++] = clippoint; else if(DF[j] < -clippoint) wave[wavewords++] = -clippoint; else wave[wavewords++] = (short)DF[j]; for (int c = 1; c < channels; c++) { wave[wavewords] = wave[wavewords-1]; wavewords++; } } tpos = tpos + 1200; } /* if(wavemode==0) { fwrite(wave, 2, Length, fp); //write data fwrite(&WI, 1, 44, fp); //write INFO chunk fwrite(&comment, 1, commentLen, fp); fclose(fp); } wavemode = 0; //force compatibility!! */ return Length; } lmms-1.0.0/src/core/Effect.cpp0000644000175000017500000001143612313663627014631 0ustar tobytoby/* * Effect.cpp - base-class for effects * * Copyright (c) 2006-2007 Danny McRae * Copyright (c) 2006-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "Effect.h" #include "engine.h" #include "DummyEffect.h" #include "EffectChain.h" #include "EffectView.h" Effect::Effect( const Plugin::Descriptor * _desc, Model * _parent, const Descriptor::SubPluginFeatures::Key * _key ) : Plugin( _desc, _parent ), m_parent( NULL ), m_key( _key ? *_key : Descriptor::SubPluginFeatures::Key() ), m_processors( 1 ), m_okay( true ), m_noRun( false ), m_running( false ), m_bufferCount( 0 ), m_enabledModel( true, this, tr( "Effect enabled" ) ), m_wetDryModel( 1.0f, -1.0f, 1.0f, 0.01f, this, tr( "Wet/Dry mix" ) ), m_gateModel( 0.0f, 0.0f, 1.0f, 0.01f, this, tr( "Gate" ) ), m_autoQuitModel( 1.0f, 1.0f, 8000.0f, 100.0f, 1.0f, this, tr( "Decay" ) ) { m_srcState[0] = m_srcState[1] = NULL; reinitSRC(); } Effect::~Effect() { for( int i = 0; i < 2; ++i ) { if( m_srcState[i] != NULL ) { src_delete( m_srcState[i] ); } } } void Effect::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_enabledModel.saveSettings( _doc, _this, "on" ); m_wetDryModel.saveSettings( _doc, _this, "wet" ); m_autoQuitModel.saveSettings( _doc, _this, "autoquit" ); m_gateModel.saveSettings( _doc, _this, "gate" ); controls()->saveState( _doc, _this ); } void Effect::loadSettings( const QDomElement & _this ) { m_enabledModel.loadSettings( _this, "on" ); m_wetDryModel.loadSettings( _this, "wet" ); m_autoQuitModel.loadSettings( _this, "autoquit" ); m_gateModel.loadSettings( _this, "gate" ); QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { if( controls()->nodeName() == node.nodeName() ) { controls()->restoreState( node.toElement() ); } } node = node.nextSibling(); } } Effect * Effect::instantiate( const QString & _plugin_name, Model * _parent, Descriptor::SubPluginFeatures::Key * _key ) { Plugin * p = Plugin::instantiate( _plugin_name, _parent, _key ); // check whether instantiated plugin is an effect if( dynamic_cast( p ) != NULL ) { // everything ok, so return pointer Effect * effect = dynamic_cast( p ); effect->m_parent = dynamic_cast(_parent); return effect; } // not quite... so delete plugin and return dummy effect delete p; return new DummyEffect( _parent ); } void Effect::checkGate( double _out_sum ) { // Check whether we need to continue processing input. Restart the // counter if the threshold has been exceeded. if( _out_sum <= gate()+0.000001 ) { incrementBufferCount(); if( bufferCount() > timeout() ) { stopRunning(); resetBufferCount(); } } else { resetBufferCount(); } } PluginView * Effect::instantiateView( QWidget * _parent ) { return new EffectView( this, _parent ); } void Effect::reinitSRC() { for( int i = 0; i < 2; ++i ) { if( m_srcState[i] != NULL ) { src_delete( m_srcState[i] ); } int error; if( ( m_srcState[i] = src_new( engine::mixer()->currentQualitySettings(). libsrcInterpolation(), DEFAULT_CHANNELS, &error ) ) == NULL ) { fprintf( stderr, "Error: src_new() failed in effect.cpp!\n" ); } } } void Effect::resample( int _i, const sampleFrame * _src_buf, sample_rate_t _src_sr, sampleFrame * _dst_buf, sample_rate_t _dst_sr, f_cnt_t _frames ) { if( m_srcState[_i] == NULL ) { return; } m_srcData[_i].input_frames = _frames; m_srcData[_i].output_frames = engine::mixer()->framesPerPeriod(); m_srcData[_i].data_in = (float *) _src_buf[0]; m_srcData[_i].data_out = _dst_buf[0]; m_srcData[_i].src_ratio = (double) _dst_sr / _src_sr; m_srcData[_i].end_of_input = 0; int error; if( ( error = src_process( m_srcState[_i], &m_srcData[_i] ) ) ) { fprintf( stderr, "Effect::resample(): error while resampling: %s\n", src_strerror( error ) ); } } lmms-1.0.0/src/core/note.cpp0000644000175000017500000001305412313663627014400 0ustar tobytoby/* * note.cpp - implementation of class note * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "note.h" #include "DetuningHelper.h" #include "templates.h" note::note( const MidiTime & _length, const MidiTime & _pos, int _key, volume_t _volume, panning_t _panning, DetuningHelper * _detuning ) : m_selected( false ), m_oldKey( qBound( 0, _key, NumKeys ) ), m_oldPos( _pos ), m_oldLength( _length ), m_isPlaying( false ), m_key( qBound( 0, _key, NumKeys ) ), m_volume( qBound( MinVolume, _volume, MaxVolume ) ), m_panning( qBound( PanningLeft, _panning, PanningRight ) ), m_length( _length ), m_pos( _pos ), m_detuning( NULL ) { //saveJournallingState( false ); // setJournalling( false ); if( _detuning ) { m_detuning = sharedObject::ref( _detuning ); } //restoreJournallingState(); } note::note( const note & _note ) : SerializingObject( _note ), m_selected( _note.m_selected ), m_oldKey( _note.m_oldKey ), m_oldPos( _note.m_oldPos ), m_oldLength( _note.m_oldLength ), m_isPlaying( _note.m_isPlaying ), m_key( _note.m_key), m_volume( _note.m_volume ), m_panning( _note.m_panning ), m_length( _note.m_length ), m_pos( _note.m_pos ), m_detuning( NULL ) { if( _note.m_detuning ) { m_detuning = sharedObject::ref( _note.m_detuning ); } } note::~note() { if( m_detuning ) { sharedObject::unref( m_detuning ); } } void note::setLength( const MidiTime & _length ) { // addJournalEntry( journalEntry( ChangeLength, m_length - _length ) ); m_length = _length; } void note::setPos( const MidiTime & _pos ) { // addJournalEntry( journalEntry( ChangePosition, m_pos - _pos ) ); m_pos = _pos; } void note::setKey( const int _key ) { const int k = qBound( 0, _key, NumKeys ); // addJournalEntry( journalEntry( ChangeKey, m_key - k ) ); m_key = k; } void note::setVolume( volume_t _volume ) { const volume_t v = qBound( MinVolume, _volume, MaxVolume ); // addJournalEntry( journalEntry( ChangeVolume, (int) m_volume - v ) ); m_volume = v; } void note::setPanning( panning_t _panning ) { const panning_t p = qBound( PanningLeft, _panning, PanningRight ); // addJournalEntry( journalEntry( ChangePanning, (int) m_panning - p ) ); m_panning = p; } MidiTime note::quantized( const MidiTime & _m, const int _q_grid ) { float p = ( (float) _m / _q_grid ); if( p - floorf( p ) < 0.5f ) { return( static_cast( p ) * _q_grid ); } return( static_cast( p + 1 ) * _q_grid ); } void note::quantizeLength( const int _q_grid ) { setLength( quantized( length(), _q_grid ) ); if( length() == 0 ) { setLength( _q_grid ); } } void note::quantizePos( const int _q_grid ) { setPos( quantized( pos(), _q_grid ) ); } void note::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "key", m_key ); _this.setAttribute( "vol", m_volume ); _this.setAttribute( "pan", m_panning ); _this.setAttribute( "len", m_length ); _this.setAttribute( "pos", m_pos ); if( m_detuning && m_length ) { m_detuning->saveSettings( _doc, _this ); } } void note::loadSettings( const QDomElement & _this ) { const int oldKey = _this.attribute( "tone" ).toInt() + _this.attribute( "oct" ).toInt() * KeysPerOctave; m_key = qMax( oldKey, _this.attribute( "key" ).toInt() ); m_volume = _this.attribute( "vol" ).toInt(); m_panning = _this.attribute( "pan" ).toInt(); m_length = _this.attribute( "len" ).toInt(); m_pos = _this.attribute( "pos" ).toInt(); if( _this.hasChildNodes() ) { createDetuning(); m_detuning->loadSettings( _this ); } } /*void note::undoStep( journalEntry & _je ) { saveJournallingState( false ); switch( static_cast( _je.actionID() ) ) { case ChangeKey: setKey( key() - _je.data().toInt() ); break; case ChangeVolume: setVolume( getVolume() - _je.data().toInt() ); break; case ChangePanning: setPanning( getPanning() - _je.data().toInt() ); break; case ChangeLength: setLength( length() - _je.data().toInt() ); break; case ChangePosition: setPos( pos() - _je.data().toInt() ); break; } restoreJournallingState(); } void note::redoStep( journalEntry & _je ) { journalEntry je( _je.actionID(), -_je.data().toInt() ); undoStep( je ); }*/ void note::editDetuningPattern() { createDetuning(); m_detuning->automationPattern()->openInAutomationEditor(); } void note::createDetuning() { if( m_detuning == NULL ) { m_detuning = new DetuningHelper; (void) m_detuning->automationPattern(); m_detuning->setRange( -MaxDetuning, MaxDetuning, 0.5f ); m_detuning->automationPattern()->setProgressionType( AutomationPattern::LinearProgression ); } } bool note::hasDetuningInfo() const { return m_detuning && m_detuning->hasAutomation(); } lmms-1.0.0/src/core/InstrumentSoundShaping.cpp0000644000175000017500000002615212313663627020131 0ustar tobytoby/* * InstrumentSoundShaping.cpp - implementation of class InstrumentSoundShaping * * Copyright (c) 2004-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "InstrumentSoundShaping.h" #include "basic_filters.h" #include "embed.h" #include "engine.h" #include "EnvelopeAndLfoParameters.h" #include "Instrument.h" #include "InstrumentTrack.h" #include "NotePlayHandle.h" const float CUT_FREQ_MULTIPLIER = 6000.0f; const float RES_MULTIPLIER = 2.0f; const float RES_PRECISION = 1000.0f; // names for env- and lfo-targets - first is name being displayed to user // and second one is used internally, e.g. for saving/restoring settings const QString __targetNames[InstrumentSoundShaping::NumTargets][3] = { { InstrumentSoundShaping::tr( "VOLUME" ), "vol", InstrumentSoundShaping::tr( "Volume" ) }, /* InstrumentSoundShaping::tr( "Pan" ), InstrumentSoundShaping::tr( "Pitch" ),*/ { InstrumentSoundShaping::tr( "CUTOFF" ), "cut", InstrumentSoundShaping::tr( "Cutoff frequency" ) }, { InstrumentSoundShaping::tr( "RESO" ), "res", InstrumentSoundShaping::tr( "Resonance" ) } } ; InstrumentSoundShaping::InstrumentSoundShaping( InstrumentTrack * _instrument_track ) : Model( _instrument_track, tr( "Envelopes/LFOs" ) ), m_instrumentTrack( _instrument_track ), m_filterEnabledModel( false, this ), m_filterModel( this, tr( "Filter type" ) ), m_filterCutModel( 14000.0, 1.0, 14000.0, 1.0, this, tr( "Cutoff frequency" ) ), m_filterResModel( 0.5, basicFilters<>::minQ(), 10.0, 0.01, this, tr( "Q/Resonance" ) ) { for( int i = 0; i < NumTargets; ++i ) { float value_for_zero_amount = 0.0; if( i == Volume ) { value_for_zero_amount = 1.0; } m_envLfoParameters[i] = new EnvelopeAndLfoParameters( value_for_zero_amount, this ); m_envLfoParameters[i]->setDisplayName( tr( __targetNames[i][2].toUtf8().constData() ) ); } m_filterModel.addItem( tr( "LowPass" ), new PixmapLoader( "filter_lp" ) ); m_filterModel.addItem( tr( "HiPass" ), new PixmapLoader( "filter_hp" ) ); m_filterModel.addItem( tr( "BandPass csg" ), new PixmapLoader( "filter_bp" ) ); m_filterModel.addItem( tr( "BandPass czpg" ), new PixmapLoader( "filter_bp" ) ); m_filterModel.addItem( tr( "Notch" ), new PixmapLoader( "filter_notch" ) ); m_filterModel.addItem( tr( "Allpass" ), new PixmapLoader( "filter_ap" ) ); m_filterModel.addItem( tr( "Moog" ), new PixmapLoader( "filter_lp" ) ); m_filterModel.addItem( tr( "2x LowPass" ), new PixmapLoader( "filter_2lp" ) ); m_filterModel.addItem( tr( "RC LowPass 12dB" ), new PixmapLoader( "filter_lp" ) ); m_filterModel.addItem( tr( "RC BandPass 12dB" ), new PixmapLoader( "filter_bp" ) ); m_filterModel.addItem( tr( "RC HighPass 12dB" ), new PixmapLoader( "filter_hp" ) ); m_filterModel.addItem( tr( "RC LowPass 24dB" ), new PixmapLoader( "filter_lp" ) ); m_filterModel.addItem( tr( "RC BandPass 24dB" ), new PixmapLoader( "filter_bp" ) ); m_filterModel.addItem( tr( "RC HighPass 24dB" ), new PixmapLoader( "filter_hp" ) ); m_filterModel.addItem( tr( "Vocal Formant Filter" ), new PixmapLoader( "filter_hp" ) ); } InstrumentSoundShaping::~InstrumentSoundShaping() { } float InstrumentSoundShaping::volumeLevel( NotePlayHandle* n, const f_cnt_t frame ) { f_cnt_t envReleaseBegin = frame - n->releaseFramesDone() + n->framesBeforeRelease(); if( n->isReleased() == false ) { envReleaseBegin += engine::mixer()->framesPerPeriod(); } float level; m_envLfoParameters[Volume]->fillLevel( &level, frame, envReleaseBegin, 1 ); return level; } void InstrumentSoundShaping::processAudioBuffer( sampleFrame* buffer, const fpp_t frames, NotePlayHandle* n ) { const f_cnt_t envTotalFrames = n->totalFramesPlayed(); f_cnt_t envReleaseBegin = envTotalFrames - n->releaseFramesDone() + n->framesBeforeRelease(); if( n->isReleased() == false ) { envReleaseBegin += engine::mixer()->framesPerPeriod(); } // because of optimizations, there's special code for several cases: // - cut- and res-lfo/envelope active // - cut-lfo/envelope active // - res-lfo/envelope active // - no lfo/envelope active but filter is used // only use filter, if it is really needed if( m_filterEnabledModel.value() ) { int old_filter_cut = 0; int old_filter_res = 0; if( n->m_filter == NULL ) { n->m_filter = new basicFilters<>( engine::mixer()->processingSampleRate() ); } n->m_filter->setFilterType( m_filterModel.value() ); #ifdef __GNUC__ float cut_buf[frames]; float res_buf[frames]; #else float * cut_buf = NULL; float * res_buf = NULL; #endif if( m_envLfoParameters[Cut]->isUsed() ) { #ifndef __GNUC__ cut_buf = new float[frames]; #endif m_envLfoParameters[Cut]->fillLevel( cut_buf, envTotalFrames, envReleaseBegin, frames ); } if( m_envLfoParameters[Resonance]->isUsed() ) { #ifndef __GNUC__ res_buf = new float[frames]; #endif m_envLfoParameters[Resonance]->fillLevel( res_buf, envTotalFrames, envReleaseBegin, frames ); } const float fcv = m_filterCutModel.value(); const float frv = m_filterResModel.value(); if( m_envLfoParameters[Cut]->isUsed() && m_envLfoParameters[Resonance]->isUsed() ) { for( fpp_t frame = 0; frame < frames; ++frame ) { const float new_cut_val = EnvelopeAndLfoParameters::expKnobVal( cut_buf[frame] ) * CUT_FREQ_MULTIPLIER + fcv; const float new_res_val = frv + RES_MULTIPLIER * res_buf[frame]; if( static_cast( new_cut_val ) != old_filter_cut || static_cast( new_res_val*RES_PRECISION ) != old_filter_res ) { n->m_filter->calcFilterCoeffs( new_cut_val, new_res_val ); old_filter_cut = static_cast( new_cut_val ); old_filter_res = static_cast( new_res_val*RES_PRECISION ); } buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 ); buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 ); } } else if( m_envLfoParameters[Cut]->isUsed() ) { for( fpp_t frame = 0; frame < frames; ++frame ) { float new_cut_val = EnvelopeAndLfoParameters::expKnobVal( cut_buf[frame] ) * CUT_FREQ_MULTIPLIER + fcv; if( static_cast( new_cut_val ) != old_filter_cut ) { n->m_filter->calcFilterCoeffs( new_cut_val, frv ); old_filter_cut = static_cast( new_cut_val ); } buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 ); buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 ); } } else if( m_envLfoParameters[Resonance]->isUsed() ) { for( fpp_t frame = 0; frame < frames; ++frame ) { float new_res_val = frv + RES_MULTIPLIER * res_buf[frame]; if( static_cast( new_res_val*RES_PRECISION ) != old_filter_res ) { n->m_filter->calcFilterCoeffs( fcv, new_res_val ); old_filter_res = static_cast( new_res_val*RES_PRECISION ); } buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 ); buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 ); } } else { n->m_filter->calcFilterCoeffs( fcv, frv ); for( fpp_t frame = 0; frame < frames; ++frame ) { buffer[frame][0] = n->m_filter->update( buffer[frame][0], 0 ); buffer[frame][1] = n->m_filter->update( buffer[frame][1], 1 ); } } #ifndef __GNUC__ delete[] cut_buf; delete[] res_buf; #endif } if( m_envLfoParameters[Volume]->isUsed() ) { #ifdef __GNUC__ float vol_buf[frames]; #else float * vol_buf = new float[frames]; #endif m_envLfoParameters[Volume]->fillLevel( vol_buf, envTotalFrames, envReleaseBegin, frames ); for( fpp_t frame = 0; frame < frames; ++frame ) { float vol_level = vol_buf[frame]; vol_level = vol_level * vol_level; buffer[frame][0] = vol_level * buffer[frame][0]; buffer[frame][1] = vol_level * buffer[frame][1]; } #ifndef __GNUC__ delete[] vol_buf; #endif } /* else if( m_envLfoParameters[Volume]->isUsed() == false && m_envLfoParameters[PANNING]->isUsed() ) { // only use panning-envelope... for( fpp_t frame = 0; frame < frames; ++frame ) { float vol_level = pan_buf[frame]; vol_level = vol_level*vol_level; for( ch_cnt_t chnl = 0; chnl < DEFAULT_CHANNELS; ++chnl ) { buffer[frame][chnl] = vol_level * buffer[frame][chnl]; } } }*/ } f_cnt_t InstrumentSoundShaping::envFrames( const bool _only_vol ) const { f_cnt_t ret_val = m_envLfoParameters[Volume]->PAHD_Frames(); if( _only_vol == false ) { for( int i = Volume+1; i < NumTargets; ++i ) { if( m_envLfoParameters[i]->isUsed() && m_envLfoParameters[i]->PAHD_Frames() > ret_val ) { ret_val = m_envLfoParameters[i]->PAHD_Frames(); } } } return ret_val; } f_cnt_t InstrumentSoundShaping::releaseFrames() const { f_cnt_t ret_val = m_envLfoParameters[Volume]->isUsed() ? m_envLfoParameters[Volume]->releaseFrames() : 0; if( m_instrumentTrack->instrument() && m_instrumentTrack->instrument()->desiredReleaseFrames() > ret_val ) { ret_val = m_instrumentTrack->instrument()->desiredReleaseFrames(); } if( m_envLfoParameters[Volume]->isUsed() == false ) { for( int i = Volume+1; i < NumTargets; ++i ) { if( m_envLfoParameters[i]->isUsed() && m_envLfoParameters[i]->releaseFrames() > ret_val ) { ret_val = m_envLfoParameters[i]->releaseFrames(); } } } return ret_val; } void InstrumentSoundShaping::saveSettings( QDomDocument & _doc, QDomElement & _this ) { m_filterModel.saveSettings( _doc, _this, "ftype" ); m_filterCutModel.saveSettings( _doc, _this, "fcut" ); m_filterResModel.saveSettings( _doc, _this, "fres" ); m_filterEnabledModel.saveSettings( _doc, _this, "fwet" ); for( int i = 0; i < NumTargets; ++i ) { m_envLfoParameters[i]->saveState( _doc, _this ).setTagName( m_envLfoParameters[i]->nodeName() + QString( __targetNames[i][1] ).toLower() ); } } void InstrumentSoundShaping::loadSettings( const QDomElement & _this ) { m_filterModel.loadSettings( _this, "ftype" ); m_filterCutModel.loadSettings( _this, "fcut" ); m_filterResModel.loadSettings( _this, "fres" ); m_filterEnabledModel.loadSettings( _this, "fwet" ); QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { for( int i = 0; i < NumTargets; ++i ) { if( node.nodeName() == m_envLfoParameters[i]->nodeName() + QString( __targetNames[i][1] ). toLower() ) { m_envLfoParameters[i]->restoreState( node.toElement() ); } } } node = node.nextSibling(); } } #include "moc_InstrumentSoundShaping.cxx" lmms-1.0.0/src/core/TempoSyncKnobModel.cpp0000644000175000017500000001020612313663627017143 0ustar tobytoby/* * TempoSyncKnobModel.cpp - adds bpm to ms conversion for knob class * * Copyright (c) 2005-2007 Danny McRae * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include "TempoSyncKnobModel.h" #include "engine.h" #include "song.h" TempoSyncKnobModel::TempoSyncKnobModel( const float _val, const float _min, const float _max, const float _step, const float _scale, Model * _parent, const QString & _display_name ) : FloatModel( _val, _min, _max, _step, _parent, _display_name ), m_tempoSyncMode( SyncNone ), m_tempoLastSyncMode( SyncNone ), m_scale( _scale ), m_custom( _parent ) { connect( engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), this, SLOT( calculateTempoSyncTime( bpm_t ) ) ); } TempoSyncKnobModel::~TempoSyncKnobModel() { } void TempoSyncKnobModel::setTempoSync( QAction * _item ) { setTempoSync( _item->data().toInt() ); } void TempoSyncKnobModel::setTempoSync( int _note_type ) { setSyncMode( ( TempoSyncMode ) _note_type ); engine::getSong()->setModified(); } void TempoSyncKnobModel::calculateTempoSyncTime( bpm_t _bpm ) { float conversionFactor = 1.0; if( m_tempoSyncMode ) { switch( m_tempoSyncMode ) { case SyncCustom: conversionFactor = static_cast( m_custom.getDenominator() ) / static_cast( m_custom.getNumerator() ); break; case SyncDoubleWholeNote: conversionFactor = 0.125; break; case SyncWholeNote: conversionFactor = 0.25; break; case SyncHalfNote: conversionFactor = 0.5; break; case SyncQuarterNote: conversionFactor = 1.0; break; case SyncEighthNote: conversionFactor = 2.0; break; case SyncSixteenthNote: conversionFactor = 4.0; break; case SyncThirtysecondNote: conversionFactor = 8.0; break; default: ; } bool journalling = testAndSetJournalling( false ); float oneUnit = 60000.0 / ( _bpm * conversionFactor * m_scale ); setValue( oneUnit * maxValue() ); setJournalling( journalling ); } if( m_tempoSyncMode != m_tempoLastSyncMode ) { emit syncModeChanged( m_tempoSyncMode ); m_tempoLastSyncMode = m_tempoSyncMode; } } void TempoSyncKnobModel::saveSettings( QDomDocument & _doc, QDomElement & _this, const QString & _name ) { _this.setAttribute( "syncmode", (int) syncMode() ); m_custom.saveSettings( _doc, _this, _name ); FloatModel::saveSettings( _doc, _this, _name ); } void TempoSyncKnobModel::loadSettings( const QDomElement & _this, const QString & _name ) { setSyncMode( ( TempoSyncMode ) _this.attribute( "syncmode" ).toInt() ); m_custom.loadSettings( _this, _name ); FloatModel::loadSettings( _this, _name ); } void TempoSyncKnobModel::setSyncMode( TempoSyncMode _new_mode ) { if( m_tempoSyncMode != _new_mode ) { m_tempoSyncMode = _new_mode; if( _new_mode == SyncCustom ) { connect( &m_custom, SIGNAL( dataChanged() ), this, SLOT( updateCustom() ) ); } } calculateTempoSyncTime( engine::getSong()->getTempo() ); } void TempoSyncKnobModel::setScale( float _new_scale ) { m_scale = _new_scale; calculateTempoSyncTime( engine::getSong()->getTempo() ); emit scaleChanged( _new_scale ); } void TempoSyncKnobModel::updateCustom() { setSyncMode( SyncCustom ); } #include "moc_TempoSyncKnobModel.cxx" lmms-1.0.0/src/core/ComboBoxModel.cpp0000644000175000017500000000312412313663627016121 0ustar tobytoby/* * ComboBoxModel.cpp - implementation of ComboBoxModel * * Copyright (c) 2008-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "ComboBoxModel.h" #include "embed.h" void ComboBoxModel::addItem( const QString & _item, PixmapLoader * _pl ) { m_items.push_back( qMakePair( _item, _pl ) ); setRange( 0, m_items.size() - 1 ); } void ComboBoxModel::clear() { setRange( 0, 0 ); foreach( const Item & _i, m_items ) { delete _i.second; } m_items.clear(); emit propertiesChanged(); } int ComboBoxModel::findText( const QString & _txt ) const { for( QVector::ConstIterator it = m_items.begin(); it != m_items.end(); ++it ) { if( ( *it ).first == _txt ) { return it - m_items.begin(); } } return -1; } #include "moc_ComboBoxModel.cxx" lmms-1.0.0/src/core/MeterModel.cpp0000644000175000017500000000422112313663627015464 0ustar tobytoby/* * MeterModel.cpp - model for meter specification * * Copyright (c) 2008-2010 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "MeterModel.h" #include "AutomationPattern.h" MeterModel::MeterModel( ::Model * _parent ) : Model( _parent ), m_numeratorModel( 4, 1, 32, this, tr( "Numerator" ) ), m_denominatorModel( 4, 1, 32, this, tr( "Denominator" ) ) { connect( &m_numeratorModel, SIGNAL( dataChanged() ), this, SIGNAL( dataChanged() ) ); connect( &m_denominatorModel, SIGNAL( dataChanged() ), this, SIGNAL( dataChanged() ) ); } MeterModel::~MeterModel() { } void MeterModel::reset() { m_numeratorModel.setValue( 4 ); m_denominatorModel.setValue( 4 ); AutomationPattern::globalAutomationPattern( &m_numeratorModel )->clear(); AutomationPattern::globalAutomationPattern( &m_denominatorModel )->clear(); } void MeterModel::saveSettings( QDomDocument & _doc, QDomElement & _this, const QString & _name ) { m_numeratorModel.saveSettings( _doc, _this, _name + "_numerator" ); m_denominatorModel.saveSettings( _doc, _this, _name + "_denominator" ); } void MeterModel::loadSettings( const QDomElement & _this, const QString & _name ) { m_numeratorModel.loadSettings( _this, _name + "_numerator" ); m_denominatorModel.loadSettings( _this, _name + "_denominator" ); } #include "moc_MeterModel.cxx" lmms-1.0.0/src/core/MixHelpers.cpp0000644000175000017500000000751712313663627015522 0ustar tobytoby/* * MixHelpers.cpp - helper functions for mixing buffers * * Copyright (c) 2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include "MixHelpers.h" namespace MixHelpers { /*! \brief Function for applying MIXOP on all sample frames */ template static inline void run( sampleFrame* dst, const sampleFrame* src, int frames, const MIXOP& OP ) { for( int i = 0; i < frames; ++i ) { OP( dst[i], src[i] ); } } /*! \brief Function for applying MIXOP on all sample frames - split source */ template static inline void run( sampleFrame* dst, const sample_t* srcLeft, const sample_t* srcRight, int frames, const MIXOP& OP ) { for( int i = 0; i < frames; ++i ) { const sampleFrame src = { srcLeft[i], srcRight[i] }; OP( dst[i], src ); } } bool isSilent( const sampleFrame* src, int frames ) { const float silenceThreshold = 0.0000001f; for( int i = 0; i < frames; ++i ) { if( fabsf( src[i][0] ) >= silenceThreshold || fabsf( src[i][1] ) >= silenceThreshold ) { return false; } } return true; } struct AddOp { void operator()( sampleFrame& dst, const sampleFrame& src ) const { dst[0] += src[0]; dst[1] += src[1]; } } ; void add( sampleFrame* dst, const sampleFrame* src, int frames ) { run<>( dst, src, frames, AddOp() ); } struct AddMultipliedOp { AddMultipliedOp( float coeff ) : m_coeff( coeff ) { } void operator()( sampleFrame& dst, const sampleFrame& src ) const { dst[0] += src[0] * m_coeff; dst[1] += src[1] * m_coeff; } const float m_coeff; } ; void addMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffSrc, int frames ) { run<>( dst, src, frames, AddMultipliedOp(coeffSrc) ); } struct AddMultipliedStereoOp { AddMultipliedStereoOp( float coeffLeft, float coeffRight ) { m_coeffs[0] = coeffLeft; m_coeffs[1] = coeffRight; } void operator()( sampleFrame& dst, const sampleFrame& src ) const { dst[0] += src[0] * m_coeffs[0]; dst[1] += src[1] * m_coeffs[1]; } float m_coeffs[2]; } ; void addMultipliedStereo( sampleFrame* dst, const sampleFrame* src, float coeffSrcLeft, float coeffSrcRight, int frames ) { run<>( dst, src, frames, AddMultipliedStereoOp(coeffSrcLeft, coeffSrcRight) ); } struct MultiplyAndAddMultipliedOp { MultiplyAndAddMultipliedOp( float coeffDst, float coeffSrc ) { m_coeffs[0] = coeffDst; m_coeffs[1] = coeffSrc; } void operator()( sampleFrame& dst, const sampleFrame& src ) const { dst[0] = dst[0]*m_coeffs[0] + src[0]*m_coeffs[1]; dst[1] = dst[1]*m_coeffs[0] + src[1]*m_coeffs[1]; } float m_coeffs[2]; } ; void multiplyAndAddMultiplied( sampleFrame* dst, const sampleFrame* src, float coeffDst, float coeffSrc, int frames ) { run<>( dst, src, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) ); } void multiplyAndAddMultipliedJoined( sampleFrame* dst, const sample_t* srcLeft, const sample_t* srcRight, float coeffDst, float coeffSrc, int frames ) { run<>( dst, srcLeft, srcRight, frames, MultiplyAndAddMultipliedOp(coeffDst, coeffSrc) ); } } lmms-1.0.0/src/core/RemotePlugin.cpp0000644000175000017500000001736512313663627016056 0ustar tobytoby/* * RemotePlugin.cpp - base class providing RPC like mechanisms * * Copyright (c) 2008-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #define COMPILE_REMOTE_PLUGIN_BASE //#define DEBUG_REMOTE_PLUGIN #ifdef DEBUG_REMOTE_PLUGIN #include #endif #include "RemotePlugin.h" #include "Mixer.h" #include "engine.h" #include "config_mgr.h" #include #ifdef LMMS_HAVE_UNISTD_H #include #endif // simple helper thread monitoring our RemotePlugin - if process terminates // unexpectedly invalidate plugin so LMMS doesn't lock up ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) : QThread(), m_plugin( _p ), m_quit( false ) { } void ProcessWatcher::run() { while( !m_quit && m_plugin->isRunning() ) { msleep( 200 ); } if( !m_quit ) { fprintf( stderr, "remote plugin died! invalidating now.\n" ); m_plugin->invalidate(); } } RemotePlugin::RemotePlugin() : RemotePluginBase( new shmFifo(), new shmFifo() ), m_failed( true ), m_process(), m_watcher( this ), m_commMutex( QMutex::Recursive ), m_splitChannels( false ), #ifdef USE_QT_SHMEM m_shmObj(), #else m_shmID( 0 ), #endif m_shmSize( 0 ), m_shm( NULL ), m_inputCount( DEFAULT_CHANNELS ), m_outputCount( DEFAULT_CHANNELS ) { } RemotePlugin::~RemotePlugin() { m_watcher.quit(); m_watcher.wait(); if( m_failed == false ) { if( isRunning() ) { lock(); sendMessage( IdQuit ); m_process.waitForFinished( 1000 ); if( m_process.state() != QProcess::NotRunning ) { m_process.terminate(); m_process.kill(); } unlock(); } #ifndef USE_QT_SHMEM shmdt( m_shm ); shmctl( m_shmID, IPC_RMID, NULL ); #endif } } bool RemotePlugin::init( const QString &pluginExecutable, bool waitForInitDoneMsg ) { lock(); if( m_failed ) { reset( new shmFifo(), new shmFifo() ); m_failed = false; } QString exec = configManager::inst()->pluginDir() + QDir::separator() + pluginExecutable; QStringList args; // swap in and out for bidirectional communication args << QString::number( out()->shmKey() ); args << QString::number( in()->shmKey() ); #ifndef DEBUG_REMOTE_PLUGIN m_process.setProcessChannelMode( QProcess::ForwardedChannels ); m_process.setWorkingDirectory( QCoreApplication::applicationDirPath() ); m_process.start( exec, args ); m_watcher.start( QThread::LowestPriority ); #else qDebug() << exec << args; #endif resizeSharedProcessingMemory(); if( waitForInitDoneMsg ) { waitForInitDone(); } unlock(); return failed(); } bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf ) { const fpp_t frames = engine::mixer()->framesPerPeriod(); if( m_failed || !isRunning() ) { if( _out_buf != NULL ) { engine::mixer()->clearAudioBuffer( _out_buf, frames ); } return false; } if( m_shm == NULL ) { // m_shm being zero means we didn't initialize everything so // far so process one message each time (and hope we get // information like SHM-key etc.) until we process messages // in a later stage of this procedure if( m_shmSize == 0 ) { lock(); fetchAndProcessAllMessages(); unlock(); } if( _out_buf != NULL ) { engine::mixer()->clearAudioBuffer( _out_buf, frames ); } return false; } memset( m_shm, 0, m_shmSize ); ch_cnt_t inputs = qMin( m_inputCount, DEFAULT_CHANNELS ); if( _in_buf != NULL && inputs > 0 ) { if( m_splitChannels ) { for( ch_cnt_t ch = 0; ch < inputs; ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) { m_shm[ch * frames + frame] = _in_buf[frame][ch]; } } } else if( inputs == DEFAULT_CHANNELS ) { memcpy( m_shm, _in_buf, frames * BYTES_PER_FRAME ); } else { sampleFrame * o = (sampleFrame *) m_shm; for( ch_cnt_t ch = 0; ch < inputs; ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) { o[frame][ch] = _in_buf[frame][ch]; } } } } lock(); sendMessage( IdStartProcessing ); if( m_failed || _out_buf == NULL || m_outputCount == 0 ) { unlock(); return false; } waitForMessage( IdProcessingDone ); unlock(); const ch_cnt_t outputs = qMin( m_outputCount, DEFAULT_CHANNELS ); if( m_splitChannels ) { for( ch_cnt_t ch = 0; ch < outputs; ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) { _out_buf[frame][ch] = m_shm[( m_inputCount+ch )* frames + frame]; } } } else if( outputs == DEFAULT_CHANNELS ) { memcpy( _out_buf, m_shm + m_inputCount * frames, frames * BYTES_PER_FRAME ); } else { sampleFrame * o = (sampleFrame *) ( m_shm + m_inputCount*frames ); // clear buffer, if plugin didn't fill up both channels engine::mixer()->clearAudioBuffer( _out_buf, frames ); for( ch_cnt_t ch = 0; ch < qMin( DEFAULT_CHANNELS, outputs ); ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) { _out_buf[frame][ch] = o[frame][ch]; } } } return true; } void RemotePlugin::processMidiEvent( const MidiEvent & _e, const f_cnt_t _offset ) { message m( IdMidiEvent ); m.addInt( _e.type() ); m.addInt( _e.channel() ); m.addInt( _e.param( 0 ) ); m.addInt( _e.param( 1 ) ); m.addInt( _offset ); lock(); sendMessage( m ); unlock(); } void RemotePlugin::resizeSharedProcessingMemory() { const size_t s = ( m_inputCount+m_outputCount ) * engine::mixer()->framesPerPeriod() * sizeof( float ); if( m_shm != NULL ) { #ifdef USE_QT_SHMEM m_shmObj.detach(); #else shmdt( m_shm ); shmctl( m_shmID, IPC_RMID, NULL ); #endif } static int shm_key = 0; #ifdef USE_QT_SHMEM do { m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) ); m_shmObj.create( s ); } while( m_shmObj.error() != QSharedMemory::NoError ); m_shm = (float *) m_shmObj.data(); #else while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL | 0600 ) ) == -1 ) { } m_shm = (float *) shmat( m_shmID, 0, 0 ); #endif m_shmSize = s; sendMessage( message( IdChangeSharedMemoryKey ). addInt( shm_key ).addInt( m_shmSize ) ); } bool RemotePlugin::processMessage( const message & _m ) { lock(); message reply_message( _m.id ); bool reply = false; switch( _m.id ) { case IdUndefined: return false; case IdInitDone: reply = true; break; case IdSampleRateInformation: reply = true; reply_message.addInt( engine::mixer()->processingSampleRate() ); break; case IdBufferSizeInformation: reply = true; reply_message.addInt( engine::mixer()->framesPerPeriod() ); break; case IdChangeInputCount: m_inputCount = _m.getInt( 0 ); resizeSharedProcessingMemory(); break; case IdChangeOutputCount: m_outputCount = _m.getInt( 0 ); resizeSharedProcessingMemory(); break; case IdDebugMessage: fprintf( stderr, "RemotePlugin::DebugMessage: %s", _m.getString( 0 ).c_str() ); break; case IdProcessingDone: case IdQuit: default: break; } if( reply ) { sendMessage( reply_message ); } unlock(); return true; } #include "moc_RemotePlugin.cxx" lmms-1.0.0/src/core/DataFile.cpp0000644000175000017500000004242512313663627015110 0ustar tobytoby/* * DataFile.cpp - implementation of class DataFile * * Copyright (c) 2004-2014 Tobias Doerffel * Copyright (c) 2012-2013 Paul Giblock

* * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "DataFile.h" #include #include #include #include #include #include #include "config_mgr.h" #include "project_version.h" #include "SongEditor.h" #include "Effect.h" #include "lmmsversion.h" // bbTCO::defaultColor() #include "bb_track.h" DataFile::typeDescStruct DataFile::s_types[DataFile::TypeCount] = { { DataFile::UnknownType, "unknown" }, { DataFile::SongProject, "song" }, { DataFile::SongProjectTemplate, "songtemplate" }, { DataFile::InstrumentTrackSettings, "instrumenttracksettings" }, { DataFile::DragNDropData, "dnddata" }, { DataFile::ClipboardData, "clipboard-data" }, { DataFile::JournalData, "journaldata" }, { DataFile::EffectSettings, "effectsettings" } } ; DataFile::DataFile( Type type ) : QDomDocument( "lmms-project" ), m_content(), m_head(), m_type( type ) { appendChild( createProcessingInstruction("xml", "version=\"1.0\"")); QDomElement root = createElement( "lmms-project" ); root.setAttribute( "version", LDF_VERSION_STRING ); root.setAttribute( "type", typeName( type ) ); root.setAttribute( "creator", "LMMS" ); root.setAttribute( "creatorversion", LMMS_VERSION ); appendChild( root ); m_head = createElement( "head" ); root.appendChild( m_head ); m_content = createElement( typeName( type ) ); root.appendChild( m_content ); } DataFile::DataFile( const QString & _fileName ) : QDomDocument(), m_content(), m_head() { QFile inFile( _fileName ); if( !inFile.open( QIODevice::ReadOnly ) ) { QMessageBox::critical( NULL, SongEditor::tr( "Could not open file" ), SongEditor::tr( "Could not open file %1. You probably " "have no permissions to read this " "file.\n Please make sure to have at " "least read permissions to the file " "and try again." ).arg( _fileName ) ); return; } loadData( inFile.readAll(), _fileName ); } DataFile::DataFile( const QByteArray & _data ) : QDomDocument(), m_content(), m_head() { loadData( _data, "" ); } DataFile::~DataFile() { } QString DataFile::nameWithExtension( const QString & _fn ) const { switch( type() ) { case SongProject: if( _fn.section( '.', -1 ) != "mmp" && _fn.section( '.', -1 ) != "mpt" && _fn.section( '.', -1 ) != "mmpz" ) { if( configManager::inst()->value( "app", "nommpz" ).toInt() == 0 ) { return _fn + ".mmpz"; } return _fn + ".mmp"; } break; case SongProjectTemplate: if( _fn.section( '.',-1 ) != "mpt" ) { return _fn + ".mpt"; } break; case InstrumentTrackSettings: if( _fn.section( '.', -1 ) != "xpf" ) { return _fn + ".xpf"; } break; default: ; } return _fn; } void DataFile::write( QTextStream & _strm ) { if( type() == SongProject || type() == SongProjectTemplate || type() == InstrumentTrackSettings ) { cleanMetaNodes( documentElement() ); } save(_strm, 2); } bool DataFile::writeFile( const QString& filename ) { const QString fullName = nameWithExtension( filename ); const QString fullNameTemp = fullName + ".new"; const QString fullNameBak = fullName + ".bak"; QFile outfile( fullNameTemp ); if( !outfile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { QMessageBox::critical( NULL, SongEditor::tr( "Could not write file" ), SongEditor::tr( "Could not open %1 for writing. You probably are not permitted to " "write to this file. Please make sure you have write-access to " "the file and try again." ).arg( fullName ) ); return false; } if( fullName.section( '.', -1 ) == "mmpz" ) { QString xml; QTextStream ts( &xml ); write( ts ); outfile.write( qCompress( xml.toUtf8() ) ); } else { QTextStream ts( &outfile ); write( ts ); } outfile.close(); // make sure the file has been written correctly if( QFileInfo( outfile.fileName() ).size() > 0 ) { // remove old backup file QFile::remove( fullNameBak ); // move current file to backup file QFile::rename( fullName, fullNameBak ); // move temporary file to current file QFile::rename( fullNameTemp, fullName ); return true; } return false; } DataFile::Type DataFile::type( const QString& typeName ) { for( int i = 0; i < TypeCount; ++i ) { if( s_types[i].m_name == typeName ) { return static_cast( i ); } } // compat code if( typeName == "channelsettings" ) { return DataFile::InstrumentTrackSettings; } return UnknownType; } QString DataFile::typeName( Type type ) { if( type >= UnknownType && type < TypeCount ) { return s_types[type].m_name; } return s_types[UnknownType].m_name; } void DataFile::cleanMetaNodes( QDomElement _de ) { QDomNode node = _de.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { if( node.toElement().attribute( "metadata" ).toInt() ) { QDomNode ns = node.nextSibling(); _de.removeChild( node ); node = ns; continue; } if( node.hasChildNodes() ) { cleanMetaNodes( node.toElement() ); } } node = node.nextSibling(); } } void DataFile::upgrade() { projectVersion version = documentElement().attribute( "creatorversion" ). replace( "svn", "" ); if( version < "0.2.1-20070501" ) { QDomNodeList list = elementsByTagName( "arpandchords" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); if( el.hasAttribute( "arpdir" ) ) { int arpdir = el.attribute( "arpdir" ).toInt(); if( arpdir > 0 ) { el.setAttribute( "arpdir", arpdir - 1 ); } else { el.setAttribute( "arpdisabled", "1" ); } } } list = elementsByTagName( "sampletrack" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); if( el.attribute( "vol" ) != "" ) { el.setAttribute( "vol", el.attribute( "vol" ).toFloat() * 100.0f ); } else { QDomNode node = el.namedItem( "automation-pattern" ); if( !node.isElement() || !node.namedItem( "vol" ).isElement() ) { el.setAttribute( "vol", 100.0f ); } } } list = elementsByTagName( "ladspacontrols" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); QDomNode anode = el.namedItem( "automation-pattern" ); QDomNode node = anode.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { QString name = node.nodeName(); if( name.endsWith( "link" ) ) { el.setAttribute( name, node.namedItem( "time" ) .toElement() .attribute( "value" ) ); QDomNode oldNode = node; node = node.nextSibling(); anode.removeChild( oldNode ); continue; } } node = node.nextSibling(); } } QDomNode node = m_head.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { if( node.nodeName() == "bpm" ) { int value = node.toElement().attribute( "value" ).toInt(); if( value > 0 ) { m_head.setAttribute( "bpm", value ); QDomNode oldNode = node; node = node.nextSibling(); m_head.removeChild( oldNode ); continue; } } else if( node.nodeName() == "mastervol" ) { int value = node.toElement().attribute( "value" ).toInt(); if( value > 0 ) { m_head.setAttribute( "mastervol", value ); QDomNode oldNode = node; node = node.nextSibling(); m_head.removeChild( oldNode ); continue; } } else if( node.nodeName() == "masterpitch" ) { m_head.setAttribute( "masterpitch", -node.toElement().attribute( "value" ).toInt() ); QDomNode oldNode = node; node = node.nextSibling(); m_head.removeChild( oldNode ); continue; } } node = node.nextSibling(); } } if( version < "0.2.1-20070508" ) { QDomNodeList list = elementsByTagName( "arpandchords" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); if( el.hasAttribute( "chorddisabled" ) ) { el.setAttribute( "chord-enabled", !el.attribute( "chorddisabled" ) .toInt() ); el.setAttribute( "arp-enabled", !el.attribute( "arpdisabled" ) .toInt() ); } else if( !el.hasAttribute( "chord-enabled" ) ) { el.setAttribute( "chord-enabled", true ); el.setAttribute( "arp-enabled", el.attribute( "arpdir" ).toInt() != 0 ); } } while( !( list = elementsByTagName( "channeltrack" ) ).isEmpty() ) { QDomElement el = list.item( 0 ).toElement(); el.setTagName( "instrumenttrack" ); } list = elementsByTagName( "instrumenttrack" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); if( el.hasAttribute( "vol" ) ) { float value = el.attribute( "vol" ).toFloat(); value = roundf( value * 0.585786438f ); el.setAttribute( "vol", value ); } else { QDomNodeList vol_list = el.namedItem( "automation-pattern" ) .namedItem( "vol" ).toElement() .elementsByTagName( "time" ); for( int j = 0; !vol_list.item( j ).isNull(); ++j ) { QDomElement timeEl = list.item( j ) .toElement(); int value = timeEl.attribute( "value" ) .toInt(); value = (int)roundf( value * 0.585786438f ); timeEl.setAttribute( "value", value ); } } } } if( version < "0.3.0-rc2" ) { QDomNodeList list = elementsByTagName( "arpandchords" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); if( el.attribute( "arpdir" ).toInt() > 0 ) { el.setAttribute( "arpdir", el.attribute( "arpdir" ).toInt() - 1 ); } } } if( version < "0.3.0" ) { QDomNodeList list; while( !( list = elementsByTagName( "pluckedstringsynth" ) ).isEmpty() ) { QDomElement el = list.item( 0 ).toElement(); el.setTagName( "vibedstrings" ); el.setAttribute( "active0", 1 ); } while( !( list = elementsByTagName( "lb303" ) ).isEmpty() ) { QDomElement el = list.item( 0 ).toElement(); el.setTagName( "lb302" ); } while( !( list = elementsByTagName( "channelsettings" ) ). isEmpty() ) { QDomElement el = list.item( 0 ).toElement(); el.setTagName( "instrumenttracksettings" ); } } if( version < "0.4.0-20080104" ) { QDomNodeList list = elementsByTagName( "fx" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); if( el.hasAttribute( "fxdisabled" ) && el.attribute( "fxdisabled" ).toInt() == 0 ) { el.setAttribute( "enabled", 1 ); } } } if( version < "0.4.0-20080118" ) { QDomNodeList list; while( !( list = elementsByTagName( "fx" ) ).isEmpty() ) { QDomElement fxchain = list.item( 0 ).toElement(); fxchain.setTagName( "fxchain" ); QDomNode rack = list.item( 0 ).firstChild(); QDomNodeList effects = rack.childNodes(); // move items one level up while( effects.count() ) { fxchain.appendChild( effects.at( 0 ) ); } fxchain.setAttribute( "numofeffects", rack.toElement().attribute( "numofeffects" ) ); fxchain.removeChild( rack ); } } if( version < "0.4.0-20080129" ) { QDomNodeList list; while( !( list = elementsByTagName( "arpandchords" ) ).isEmpty() ) { QDomElement aac = list.item( 0 ).toElement(); aac.setTagName( "arpeggiator" ); QDomNode cloned = aac.cloneNode(); cloned.toElement().setTagName( "chordcreator" ); aac.parentNode().appendChild( cloned ); } } if( version < "0.4.0-20080409" ) { QStringList s; s << "note" << "pattern" << "bbtco" << "sampletco" << "time"; for( QStringList::iterator it = s.begin(); it < s.end(); ++it ) { QDomNodeList list = elementsByTagName( *it ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); el.setAttribute( "pos", el.attribute( "pos" ).toInt()*3 ); el.setAttribute( "len", el.attribute( "len" ).toInt()*3 ); } } QDomNodeList list = elementsByTagName( "timeline" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); el.setAttribute( "lp0pos", el.attribute( "lp0pos" ).toInt()*3 ); el.setAttribute( "lp1pos", el.attribute( "lp1pos" ).toInt()*3 ); } } if( version < "0.4.0-20080607" ) { QDomNodeList list; while( !( list = elementsByTagName( "midi" ) ).isEmpty() ) { QDomElement el = list.item( 0 ).toElement(); el.setTagName( "midiport" ); } } if( version < "0.4.0-20080622" ) { QDomNodeList list; while( !( list = elementsByTagName( "automation-pattern" ) ).isEmpty() ) { QDomElement el = list.item( 0 ).toElement(); el.setTagName( "automationpattern" ); } list = elementsByTagName( "bbtrack" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); QString s = el.attribute( "name" ); s.replace( QRegExp( "^Beat/Baseline " ), "Beat/Bassline " ); el.setAttribute( "name", s ); } } if( version < "0.4.0-beta1" ) { // convert binary effect-key-blobs to XML QDomNodeList list; list = elementsByTagName( "effect" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); QString k = el.attribute( "key" ); if( !k.isEmpty() ) { const QList l = base64::decode( k, QVariant::List ).toList(); if( !l.isEmpty() ) { QString name = l[0].toString(); QVariant u = l[1]; EffectKey::AttributeMap m; // VST-effect? if( u.type() == QVariant::String ) { m["file"] = u.toString(); } // LADSPA-effect? else if( u.type() == QVariant::StringList ) { const QStringList sl = u.toStringList(); m["plugin"] = sl.value( 0 ); m["file"] = sl.value( 1 ); } EffectKey key( NULL, name, m ); el.appendChild( key.saveXML( *this ) ); } } } } if( version < "0.4.0-rc2" ) { QDomNodeList list = elementsByTagName( "audiofileprocessor" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); QString s = el.attribute( "src" ); s.replace( "drumsynth/misc ", "drumsynth/misc_" ); s.replace( "drumsynth/r&b", "drumsynth/r_n_b" ); s.replace( "drumsynth/r_b", "drumsynth/r_n_b" ); el.setAttribute( "src", s ); } list = elementsByTagName( "lb302" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); int s = el.attribute( "shape" ).toInt(); if( s >= 1 ) { s--; } el.setAttribute( "shape", QString("%1").arg(s) ); } } // new default colour for B&B tracks QDomNodeList list = elementsByTagName( "bbtco" ); for( int i = 0; !list.item( i ).isNull(); ++i ) { QDomElement el = list.item( i ).toElement(); unsigned int rgb = el.attribute( "color" ).toUInt(); if( rgb == qRgb( 64, 128, 255 ) ) { el.setAttribute( "color", bbTCO::defaultColor() ); } } // Time-signature if ( !m_head.hasAttribute( "timesig_numerator" ) ) { m_head.setAttribute( "timesig_numerator", 4 ); m_head.setAttribute( "timesig_denominator", 4 ); } if( !m_head.hasAttribute( "mastervol" ) ) { m_head.setAttribute( "mastervol", 100 ); } //printf("%s\n", toString( 2 ).toUtf8().constData()); } void DataFile::loadData( const QByteArray & _data, const QString & _sourceFile ) { QString errorMsg; int line = -1, col = -1; if( !setContent( _data, &errorMsg, &line, &col ) ) { // parsing failed? then try to uncompress data QByteArray uncompressed = qUncompress( _data ); if( !uncompressed.isEmpty() ) { if( setContent( uncompressed, &errorMsg, &line, &col ) ) { line = col = -1; } } if( line >= 0 && col >= 0 ) { qWarning() << "at line" << line << "column" << errorMsg; QMessageBox::critical( NULL, SongEditor::tr( "Error in file" ), SongEditor::tr( "The file %1 seems to contain " "errors and therefore can't be " "loaded." ). arg( _sourceFile ) ); return; } } QDomElement root = documentElement(); m_type = type( root.attribute( "type" ) ); m_head = root.elementsByTagName( "head" ).item( 0 ).toElement(); if( root.hasAttribute( "creatorversion" ) && root.attribute( "creatorversion" ) != LMMS_VERSION ) { upgrade(); } m_content = root.elementsByTagName( typeName( m_type ) ). item( 0 ).toElement(); } lmms-1.0.0/src/tracks/0000755000175000017500000000000012313663627013263 5ustar tobytobylmms-1.0.0/src/tracks/InstrumentTrack.cpp0000644000175000017500000011347212313663627017134 0ustar tobytoby/* * InstrumentTrack.cpp - implementation of instrument-track-class * (window + data-structures) * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FileDialog.h" #include "InstrumentTrack.h" #include "AudioPort.h" #include "AutomationPattern.h" #include "bb_track.h" #include "config_mgr.h" #include "ControllerConnection.h" #include "debug.h" #include "EffectChain.h" #include "EffectRackView.h" #include "embed.h" #include "engine.h" #include "file_browser.h" #include "FxMixer.h" #include "FxMixerView.h" #include "InstrumentSoundShaping.h" #include "InstrumentSoundShapingView.h" #include "fade_button.h" #include "gui_templates.h" #include "Instrument.h" #include "InstrumentFunctionViews.h" #include "InstrumentMidiIOView.h" #include "knob.h" #include "LcdSpinBox.h" #include "led_checkbox.h" #include "MainWindow.h" #include "MidiClient.h" #include "MidiPortMenu.h" #include "MixHelpers.h" #include "DataFile.h" #include "NotePlayHandle.h" #include "pattern.h" #include "PluginView.h" #include "SamplePlayHandle.h" #include "song.h" #include "string_pair_drag.h" #include "tab_widget.h" #include "tooltip.h" #include "track_label_button.h" const char * volume_help = QT_TRANSLATE_NOOP( "InstrumentTrack", "With this knob you can set " "the volume of the opened " "channel."); const int INSTRUMENT_WIDTH = 254; const int INSTRUMENT_HEIGHT = INSTRUMENT_WIDTH; const int PIANO_HEIGHT = 84; const int INSTRUMENT_WINDOW_CACHE_SIZE = 8; // #### IT: InstrumentTrack::InstrumentTrack( TrackContainer* tc ) : track( track::InstrumentTrack, tc ), MidiEventProcessor(), m_audioPort( tr( "unnamed_track" ) ), m_midiPort( tr( "unnamed_track" ), engine::mixer()->midiClient(), this, this ), m_notes(), m_sustainPedalPressed( false ), m_silentBuffersProcessed( false ), m_baseNoteModel( 0, 0, KeysPerOctave * NumOctaves - 1, this, tr( "Base note" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 0.1f, this, tr( "Volume" ) ), m_panningModel( DefaultPanning, PanningLeft, PanningRight, 0.1f, this, tr( "Panning" ) ), m_pitchModel( 0, MinPitchDefault, MaxPitchDefault, 1, this, tr( "Pitch" ) ), m_pitchRangeModel( 1, 1, 24, this, tr( "Pitch range" ) ), m_effectChannelModel( 0, 0, NumFxChannels, this, tr( "FX channel" ) ), m_instrument( NULL ), m_soundShaping( this ), m_arpeggio( this ), m_noteStacking( this ), m_piano( this ) { m_pitchModel.setCenterValue( 0 ); m_panningModel.setCenterValue( DefaultPanning ); m_baseNoteModel.setInitValue( DefaultKey ); connect( &m_baseNoteModel, SIGNAL( dataChanged() ), this, SLOT( updateBaseNote() ) ); connect( &m_pitchModel, SIGNAL( dataChanged() ), this, SLOT( updatePitch() ) ); connect( &m_pitchRangeModel, SIGNAL( dataChanged() ), this, SLOT( updatePitchRange() ) ); for( int i = 0; i < NumKeys; ++i ) { m_notes[i] = NULL; m_runningMidiNotes[i] = 0; } setName( tr( "Default preset" ) ); } InstrumentTrack::~InstrumentTrack() { // kill all running notes silenceAllNotes(); // now we're save deleting the instrument delete m_instrument; } void InstrumentTrack::processAudioBuffer( sampleFrame* buf, const fpp_t frames, NotePlayHandle* n ) { // we must not play the sound if this InstrumentTrack is muted... if( isMuted() || ( n && n->isBbTrackMuted() ) ) { return; } // Test for silent input data if instrument provides a single stream only (i.e. driven by InstrumentPlayHandle) // We could do that in all other cases as well but the overhead for silence test is bigger than // what we potentially save. While playing a note, a NotePlayHandle-driven instrument will produce sound in // 99 of 100 cases so that test would be a waste of time. if( m_instrument->flags().testFlag( Instrument::IsSingleStreamed ) && MixHelpers::isSilent( buf, frames ) ) { // at least pass one silent buffer to allow if( m_silentBuffersProcessed ) { // skip further processing return; } m_silentBuffersProcessed = true; } else { m_silentBuffersProcessed = false; } // if effects "went to sleep" because there was no input, wake them up // now m_audioPort.effects()->startRunning(); float v_scale = (float) getVolume() / DefaultVolume; // instruments using instrument-play-handles will call this method // without any knowledge about notes, so they pass NULL for n, which // is no problem for us since we just bypass the envelopes+LFOs if( m_instrument->flags().testFlag( Instrument::IsSingleStreamed ) == false && n != NULL ) { m_soundShaping.processAudioBuffer( buf, frames, n ); v_scale *= ( (float) n->getVolume() / DefaultVolume ); } m_audioPort.setNextFxChannel( m_effectChannelModel.value() ); int framesToMix = frames; int offset = 0; int panning = m_panningModel.value(); if( n ) { framesToMix = qMin( n->framesLeftForCurrentPeriod(), framesToMix ); offset = n->offset(); panning += n->getPanning(); panning = tLimit( panning, PanningLeft, PanningRight ); } engine::mixer()->bufferToPort( buf, framesToMix, offset, panningToVolumeVector( panning, v_scale ), &m_audioPort ); } MidiEvent InstrumentTrack::applyMasterKey( const MidiEvent& event ) { MidiEvent copy( event ); switch( event.type() ) { case MidiNoteOn: case MidiNoteOff: case MidiKeyPressure: copy.setKey( masterKey( event.key() ) ); break; default: break; } return copy; } void InstrumentTrack::processInEvent( const MidiEvent& event, const MidiTime& time ) { engine::mixer()->lock(); bool eventHandled = false; switch( event.type() ) { // we don't send MidiNoteOn, MidiNoteOff and MidiKeyPressure // events to instrument as NotePlayHandle will send them on its // own case MidiNoteOn: if( event.velocity() > 0 ) { if( m_notes[event.key()] == NULL ) { // create (timed) note-play-handle NotePlayHandle* nph = new NotePlayHandle( this, time.frames( engine::framesPerTick() ), typeInfo::max() / 2, note( MidiTime(), MidiTime(), event.key(), event.volume( midiPort()->baseVelocity() ) ), NULL, false, event.channel(), NotePlayHandle::OriginMidiInput ); if( engine::mixer()->addPlayHandle( nph ) ) { m_notes[event.key()] = nph; } } eventHandled = true; break; } case MidiNoteOff: if( m_notes[event.key()] != NULL ) { // do actual note off and remove internal reference to NotePlayHandle (which itself will // be deleted later automatically) m_notes[event.key()]->noteOff(); m_notes[event.key()] = NULL; } eventHandled = true; break; case MidiKeyPressure: if( m_notes[event.key()] != NULL ) { // setVolume() calls processOutEvent() with MidiKeyPressure so the // attached instrument will receive the event as well m_notes[event.key()]->setVolume( event.volume( midiPort()->baseVelocity() ) ); } eventHandled = true; break; case MidiPitchBend: // updatePitch() is connected to m_pitchModel::dataChanged() which will send out // MidiPitchBend events m_pitchModel.setValue( m_pitchModel.minValue() + event.pitchBend() * m_pitchModel.range() / MidiMaxPitchBend ); break; case MidiControlChange: if( event.controllerNumber() == MidiControllerSustain ) { if( event.controllerValue() > MidiMaxControllerValue/2 ) { m_sustainPedalPressed = true; } else { m_sustainPedalPressed = false; } } if( event.controllerNumber() == MidiControllerAllSoundOff || event.controllerNumber() == MidiControllerAllNotesOff || event.controllerNumber() == MidiControllerOmniOn || event.controllerNumber() == MidiControllerOmniOff || event.controllerNumber() == MidiControllerMonoOn || event.controllerNumber() == MidiControllerPolyOn ) { silenceAllNotes(); } break; case MidiMetaEvent: // handle special cases such as note panning switch( event.metaEvent() ) { case MidiNotePanning: if( m_notes[event.key()] != NULL ) { eventHandled = true; m_notes[event.key()]->setPanning( event.panning() ); } break; default: qWarning( "InstrumentTrack: unhandled MIDI meta event: %i", event.metaEvent() ); break; } break; default: break; } if( eventHandled == false && instrument()->handleMidiEvent( event, time ) == false ) { qWarning( "InstrumentTrack: unhandled MIDI event %d", event.type() ); } engine::mixer()->unlock(); } void InstrumentTrack::processOutEvent( const MidiEvent& event, const MidiTime& time ) { // do nothing if we do not have an instrument instance (e.g. when loading settings) if( m_instrument == NULL ) { return; } const MidiEvent transposedEvent = applyMasterKey( event ); const int key = transposedEvent.key(); switch( event.type() ) { case MidiNoteOn: m_piano.setKeyState( event.key(), true ); // event.key() = original key if( key >= 0 && key < NumKeys ) { if( m_runningMidiNotes[key] > 0 ) { m_instrument->handleMidiEvent( MidiEvent( MidiNoteOff, midiPort()->realOutputChannel(), key, 0 ), time ); } ++m_runningMidiNotes[key]; m_instrument->handleMidiEvent( MidiEvent( MidiNoteOn, midiPort()->realOutputChannel(), key, event.velocity() ), time ); emit newNote(); } break; case MidiNoteOff: m_piano.setKeyState( event.key(), false ); // event.key() = original key if( key >= 0 && key < NumKeys && --m_runningMidiNotes[key] <= 0 ) { m_runningMidiNotes[key] = qMax( 0, m_runningMidiNotes[key] ); m_instrument->handleMidiEvent( MidiEvent( MidiNoteOff, midiPort()->realOutputChannel(), key, 0 ), time ); } break; default: m_instrument->handleMidiEvent( transposedEvent, time ); break; } // if appropriate, midi-port does futher routing m_midiPort.processOutEvent( event, time ); } void InstrumentTrack::silenceAllNotes() { engine::mixer()->lock(); for( int i = 0; i < NumKeys; ++i ) { m_notes[i] = NULL; m_runningMidiNotes[i] = 0; } // invalidate all NotePlayHandles linked to this track m_processHandles.clear(); engine::mixer()->removePlayHandles( this ); engine::mixer()->unlock(); } f_cnt_t InstrumentTrack::beatLen( NotePlayHandle * _n ) const { if( m_instrument != NULL ) { const f_cnt_t len = m_instrument->beatLen( _n ); if( len > 0 ) { return len; } } return m_soundShaping.envFrames(); } void InstrumentTrack::playNote( NotePlayHandle * _n, sampleFrame * _working_buffer ) { // arpeggio- and chord-widget has to do its work -> adding sub-notes // for chords/arpeggios m_noteStacking.processNote( _n ); m_arpeggio.processNote( _n ); if( !_n->isArpeggioBaseNote() && m_instrument != NULL ) { // all is done, so now lets play the note! m_instrument->playNote( _n, _working_buffer ); } } QString InstrumentTrack::instrumentName() const { if( m_instrument != NULL ) { return m_instrument->displayName(); } return QString::null; } void InstrumentTrack::deleteNotePluginData( NotePlayHandle* n ) { if( m_instrument != NULL ) { m_instrument->deleteNotePluginData( n ); } } void InstrumentTrack::setName( const QString & _new_name ) { // when changing name of track, also change name of those patterns, // which have the same name as the instrument-track for( int i = 0; i < numOfTCOs(); ++i ) { pattern * p = dynamic_cast( getTCO( i ) ); if( ( p != NULL && p->name() == name() ) || p->name() == "" ) { p->setName( _new_name ); } } track::setName( _new_name ); m_midiPort.setName( name() ); m_audioPort.setName( name() ); emit nameChanged(); } void InstrumentTrack::updateBaseNote() { engine::mixer()->lock(); for( NotePlayHandleList::Iterator it = m_processHandles.begin(); it != m_processHandles.end(); ++it ) { ( *it )->updateFrequency(); } engine::mixer()->unlock(); } void InstrumentTrack::updatePitch() { updateBaseNote(); processOutEvent( MidiEvent( MidiPitchBend, midiPort()->realOutputChannel(), midiPitch() ) ); } void InstrumentTrack::updatePitchRange() { const int r = m_pitchRangeModel.value(); m_pitchModel.setRange( MinPitchDefault * r, MaxPitchDefault * r ); processOutEvent( MidiEvent( MidiControlChange, midiPort()->realOutputChannel(), MidiControllerRegisteredParameterNumberLSB, MidiPitchBendSensitivityRPN & 0x7F ) ); processOutEvent( MidiEvent( MidiControlChange, midiPort()->realOutputChannel(), MidiControllerRegisteredParameterNumberMSB, ( MidiPitchBendSensitivityRPN >> 8 ) & 0x7F ) ); processOutEvent( MidiEvent( MidiControlChange, midiPort()->realOutputChannel(), MidiControllerDataEntry, midiPitchRange() ) ); } int InstrumentTrack::masterKey( int _midi_key ) const { int key = m_baseNoteModel.value() - engine::getSong()->masterPitch(); return tLimit( _midi_key - ( key - DefaultKey ), 0, NumKeys ); } void InstrumentTrack::removeMidiPortNode( DataFile & _dataFile ) { QDomNodeList n = _dataFile.elementsByTagName( "midiport" ); n.item( 0 ).parentNode().removeChild( n.item( 0 ) ); } bool InstrumentTrack::play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _offset, int _tco_num ) { const float frames_per_tick = engine::framesPerTick(); tcoVector tcos; bbTrack * bb_track = NULL; if( _tco_num >= 0 ) { trackContentObject * tco = getTCO( _tco_num ); tcos.push_back( tco ); bb_track = bbTrack::findBBTrack( _tco_num ); } else { getTCOsInRange( tcos, _start, _start + static_cast( _frames / frames_per_tick ) ); } // Handle automation: detuning for( NotePlayHandleList::Iterator it = m_processHandles.begin(); it != m_processHandles.end(); ++it ) { ( *it )->processMidiTime( _start ); } if ( tcos.size() == 0 ) { return false; } bool played_a_note = false; // will be return variable for( tcoVector::Iterator it = tcos.begin(); it != tcos.end(); ++it ) { pattern * p = dynamic_cast( *it ); // everything which is not a pattern or muted won't be played if( p == NULL || ( *it )->isMuted() ) { continue; } MidiTime cur_start = _start; if( _tco_num < 0 ) { cur_start -= p->startPosition(); } // get all notes from the given pattern... const NoteVector & notes = p->notes(); // ...and set our index to zero NoteVector::ConstIterator nit = notes.begin(); // very effective algorithm for playing notes that are // posated within the current sample-frame if( cur_start > 0 ) { // skip notes which are posated before start-tact while( nit != notes.end() && ( *nit )->pos() < cur_start ) { ++nit; } } note * cur_note; while( nit != notes.end() && ( cur_note = *nit )->pos() == cur_start ) { if( cur_note->length() != 0 ) { const f_cnt_t note_frames = cur_note->length().frames( frames_per_tick ); NotePlayHandle* notePlayHandle = new NotePlayHandle( this, _offset, note_frames, *cur_note ); notePlayHandle->setBBTrack( bb_track ); // are we playing global song? if( _tco_num < 0 ) { // then set song-global offset of pattern in order to // properly perform the note detuning notePlayHandle->setSongGlobalParentOffset( p->startPosition() ); } engine::mixer()->addPlayHandle( notePlayHandle ); played_a_note = true; } ++nit; } } return played_a_note; } trackContentObject * InstrumentTrack::createTCO( const MidiTime & ) { return new pattern( this ); } trackView * InstrumentTrack::createView( TrackContainerView* tcv ) { return new InstrumentTrackView( this, tcv ); } void InstrumentTrack::saveTrackSpecificSettings( QDomDocument& doc, QDomElement & thisElement ) { m_volumeModel.saveSettings( doc, thisElement, "vol" ); m_panningModel.saveSettings( doc, thisElement, "pan" ); m_pitchModel.saveSettings( doc, thisElement, "pitch" ); m_pitchRangeModel.saveSettings( doc, thisElement, "pitchrange" ); m_effectChannelModel.saveSettings( doc, thisElement, "fxch" ); m_baseNoteModel.saveSettings( doc, thisElement, "basenote" ); if( m_instrument != NULL ) { QDomElement i = doc.createElement( "instrument" ); i.setAttribute( "name", m_instrument->descriptor()->name ); m_instrument->saveState( doc, i ); thisElement.appendChild( i ); } m_soundShaping.saveState( doc, thisElement ); m_noteStacking.saveState( doc, thisElement ); m_arpeggio.saveState( doc, thisElement ); m_midiPort.saveState( doc, thisElement ); m_audioPort.effects()->saveState( doc, thisElement ); } void InstrumentTrack::loadTrackSpecificSettings( const QDomElement & thisElement ) { silenceAllNotes(); engine::mixer()->lock(); m_volumeModel.loadSettings( thisElement, "vol" ); m_panningModel.loadSettings( thisElement, "pan" ); m_pitchRangeModel.loadSettings( thisElement, "pitchrange" ); m_pitchModel.loadSettings( thisElement, "pitch" ); m_effectChannelModel.loadSettings( thisElement, "fxch" ); m_baseNoteModel.loadSettings( thisElement, "basenote" ); // clear effect-chain just in case we load an old preset without FX-data m_audioPort.effects()->clear(); QDomNode node = thisElement.firstChild(); while( !node.isNull() ) { if( node.isElement() ) { if( m_soundShaping.nodeName() == node.nodeName() ) { m_soundShaping.restoreState( node.toElement() ); } else if( m_noteStacking.nodeName() == node.nodeName() ) { m_noteStacking.restoreState( node.toElement() ); } else if( m_arpeggio.nodeName() == node.nodeName() ) { m_arpeggio.restoreState( node.toElement() ); } else if( m_midiPort.nodeName() == node.nodeName() ) { m_midiPort.restoreState( node.toElement() ); } else if( m_audioPort.effects()->nodeName() == node.nodeName() ) { m_audioPort.effects()->restoreState( node.toElement() ); } else if( node.nodeName() == "instrument" ) { delete m_instrument; m_instrument = NULL; m_instrument = Instrument::instantiate( node.toElement().attribute( "name" ), this ); m_instrument->restoreState( node.firstChildElement() ); emit instrumentChanged(); } // compat code - if node-name doesn't match any known // one, we assume that it is an instrument-plugin // which we'll try to load else if( AutomationPattern::classNodeName() != node.nodeName() && ControllerConnection::classNodeName() != node.nodeName() && !node.toElement().hasAttribute( "id" ) ) { delete m_instrument; m_instrument = NULL; m_instrument = Instrument::instantiate( node.nodeName(), this ); if( m_instrument->nodeName() == node.nodeName() ) { m_instrument->restoreState( node.toElement() ); } emit instrumentChanged(); } } node = node.nextSibling(); } engine::mixer()->unlock(); } Instrument * InstrumentTrack::loadInstrument( const QString & _plugin_name ) { silenceAllNotes(); engine::mixer()->lock(); delete m_instrument; m_instrument = Instrument::instantiate( _plugin_name, this ); engine::mixer()->unlock(); setName( m_instrument->displayName() ); emit instrumentChanged(); return m_instrument; } // #### ITV: QQueue InstrumentTrackView::s_windowCache; InstrumentTrackView::InstrumentTrackView( InstrumentTrack * _it, TrackContainerView* tcv ) : trackView( _it, tcv ), m_window( NULL ), m_lastPos( -1, -1 ) { setAcceptDrops( true ); setFixedHeight( 32 ); m_tlb = new trackLabelButton( this, getTrackSettingsWidget() ); m_tlb->setCheckable( true ); m_tlb->setIcon( embed::getIconPixmap( "instrument_track" ) ); m_tlb->move( 3, 1 ); m_tlb->show(); connect( m_tlb, SIGNAL( toggled( bool ) ), this, SLOT( toggleInstrumentWindow( bool ) ) ); connect( _it, SIGNAL( nameChanged() ), m_tlb, SLOT( update() ) ); // creation of widgets for track-settings-widget int widgetWidth; if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT; } else { widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH; } m_volumeKnob = new knob( knobSmall_17, getTrackSettingsWidget(), tr( "Volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_it->m_volumeModel ); m_volumeKnob->setHintText( tr( "Volume:" ) + " ", "%" ); m_volumeKnob->move( widgetWidth-2*24, 2 ); m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); m_volumeKnob->setWhatsThis( tr( volume_help ) ); m_panningKnob = new knob( knobSmall_17, getTrackSettingsWidget(), tr( "Panning" ) ); m_panningKnob->setModel( &_it->m_panningModel ); m_panningKnob->setHintText( tr( "Panning:" ) + " ", "%" ); m_panningKnob->move( widgetWidth-24, 2 ); m_panningKnob->setLabel( tr( "PAN" ) ); m_panningKnob->show(); m_midiMenu = new QMenu( tr( "MIDI" ), this ); // sequenced MIDI? if( !engine::mixer()->midiClient()->isRaw() ) { _it->m_midiPort.m_readablePortsMenu = new MidiPortMenu( MidiPort::Input ); _it->m_midiPort.m_writablePortsMenu = new MidiPortMenu( MidiPort::Output ); _it->m_midiPort.m_readablePortsMenu->setModel( &_it->m_midiPort ); _it->m_midiPort.m_writablePortsMenu->setModel( &_it->m_midiPort ); m_midiInputAction = m_midiMenu->addMenu( _it->m_midiPort.m_readablePortsMenu ); m_midiOutputAction = m_midiMenu->addMenu( _it->m_midiPort.m_writablePortsMenu ); } else { m_midiInputAction = m_midiMenu->addAction( "" ); m_midiOutputAction = m_midiMenu->addAction( "" ); m_midiInputAction->setCheckable( true ); m_midiOutputAction->setCheckable( true ); connect( m_midiInputAction, SIGNAL( changed() ), this, SLOT( midiInSelected() ) ); connect( m_midiOutputAction, SIGNAL( changed() ), this, SLOT( midiOutSelected() ) ); connect( &_it->m_midiPort, SIGNAL( modeChanged() ), this, SLOT( midiConfigChanged() ) ); } m_midiInputAction->setText( tr( "Input" ) ); m_midiOutputAction->setText( tr( "Output" ) ); m_activityIndicator = new fadeButton( QApplication::palette().color( QPalette::Active, QPalette::Background), QApplication::palette().color( QPalette::Active, QPalette::BrightText ), getTrackSettingsWidget() ); m_activityIndicator->setGeometry( widgetWidth-2*24-11, 2, 8, 28 ); m_activityIndicator->show(); connect( m_activityIndicator, SIGNAL( pressed() ), this, SLOT( activityIndicatorPressed() ) ); connect( m_activityIndicator, SIGNAL( released() ), this, SLOT( activityIndicatorReleased() ) ); connect( _it, SIGNAL( newNote() ), m_activityIndicator, SLOT( activate() ) ); setModel( _it ); } InstrumentTrackView::~InstrumentTrackView() { freeInstrumentTrackWindow(); delete model()->m_midiPort.m_readablePortsMenu; delete model()->m_midiPort.m_writablePortsMenu; } InstrumentTrackWindow * InstrumentTrackView::topLevelInstrumentTrackWindow() { InstrumentTrackWindow * w = NULL; foreach( QMdiSubWindow * sw, engine::mainWindow()->workspace()->subWindowList( QMdiArea::ActivationHistoryOrder ) ) { if( sw->isVisible() && sw->widget()->inherits( "InstrumentTrackWindow" ) ) { w = qobject_cast( sw->widget() ); } } return w; } // TODO: Add windows to free list on freeInstrumentTrackWindow. // But, don't NULL m_window or disconnect signals. This will allow windows // that are being show/hidden frequently to stay connected. void InstrumentTrackView::freeInstrumentTrackWindow() { if( m_window != NULL ) { m_lastPos = m_window->parentWidget()->pos(); if( configManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() || s_windowCache.count() < INSTRUMENT_WINDOW_CACHE_SIZE ) { model()->setHook( NULL ); m_window->setInstrumentTrackView( NULL ); m_window->parentWidget()->hide(); //m_window->setModel( // engine::dummyTrackContainer()-> // dummyInstrumentTrack() ); m_window->updateInstrumentView(); s_windowCache << m_window; } else { delete m_window; } m_window = NULL; } } void InstrumentTrackView::cleanupWindowCache() { while( !s_windowCache.isEmpty() ) { delete s_windowCache.dequeue(); } } InstrumentTrackWindow * InstrumentTrackView::getInstrumentTrackWindow() { if( m_window != NULL ) { } else if( !s_windowCache.isEmpty() ) { m_window = s_windowCache.dequeue(); m_window->setInstrumentTrackView( this ); m_window->setModel( model() ); m_window->updateInstrumentView(); model()->setHook( m_window ); if( configManager::inst()-> value( "ui", "oneinstrumenttrackwindow" ).toInt() ) { s_windowCache << m_window; } else if( m_lastPos.x() > 0 || m_lastPos.y() > 0 ) { m_window->parentWidget()->move( m_lastPos ); } } else { m_window = new InstrumentTrackWindow( this ); if( configManager::inst()-> value( "ui", "oneinstrumenttrackwindow" ).toInt() ) { // first time, an InstrumentTrackWindow is opened s_windowCache << m_window; } } return m_window; } void InstrumentTrackView::dragEnterEvent( QDragEnterEvent * _dee ) { InstrumentTrackWindow::dragEnterEventGeneric( _dee ); if( !_dee->isAccepted() ) { trackView::dragEnterEvent( _dee ); } } void InstrumentTrackView::dropEvent( QDropEvent * _de ) { getInstrumentTrackWindow()->dropEvent( _de ); trackView::dropEvent( _de ); } void InstrumentTrackView::toggleInstrumentWindow( bool _on ) { getInstrumentTrackWindow()->toggleVisibility( _on ); if( !_on ) { freeInstrumentTrackWindow(); } } void InstrumentTrackView::activityIndicatorPressed() { model()->processInEvent( MidiEvent( MidiNoteOn, 0, DefaultKey, MidiDefaultVelocity ) ); } void InstrumentTrackView::activityIndicatorReleased() { model()->processInEvent( MidiEvent( MidiNoteOff, 0, DefaultKey, 0 ) ); } void InstrumentTrackView::midiInSelected() { if( model() ) { model()->m_midiPort.setReadable( m_midiInputAction->isChecked() ); } } void InstrumentTrackView::midiOutSelected() { if( model() ) { model()->m_midiPort.setWritable( m_midiOutputAction->isChecked() ); } } void InstrumentTrackView::midiConfigChanged() { m_midiInputAction->setChecked( model()->m_midiPort.isReadable() ); m_midiOutputAction->setChecked( model()->m_midiPort.isWritable() ); } class fxLineLcdSpinBox : public LcdSpinBox { public: fxLineLcdSpinBox( int _num_digits, QWidget * _parent, const QString & _name ) : LcdSpinBox( _num_digits, _parent, _name ) {} protected: virtual void mouseDoubleClickEvent ( QMouseEvent * _me ) { engine::fxMixerView()->setCurrentFxLine( model()->value() ); engine::fxMixerView()->show();// show fxMixer window engine::fxMixerView()->setFocus();// set focus to fxMixer window //engine::getFxMixerView()->raise(); } }; // #### ITW: InstrumentTrackWindow::InstrumentTrackWindow( InstrumentTrackView * _itv ) : QWidget(), ModelView( NULL, this ), m_track( _itv->model() ), m_itv( _itv ), m_instrumentView( NULL ) { setAcceptDrops( true ); // init own layout + widgets setFocusPolicy( Qt::StrongFocus ); QVBoxLayout * vlayout = new QVBoxLayout( this ); vlayout->setMargin( 0 ); vlayout->setSpacing( 0 ); tabWidget* generalSettingsWidget = new tabWidget( tr( "GENERAL SETTINGS" ), this ); QVBoxLayout* generalSettingsLayout = new QVBoxLayout( generalSettingsWidget ); generalSettingsLayout->setContentsMargins( 8, 18, 8, 8 ); generalSettingsLayout->setSpacing( 6 ); // setup line edit for changing instrument track name m_nameLineEdit = new QLineEdit; m_nameLineEdit->setFont( pointSize<9>( m_nameLineEdit->font() ) ); connect( m_nameLineEdit, SIGNAL( textChanged( const QString & ) ), this, SLOT( textChanged( const QString & ) ) ); generalSettingsLayout->addWidget( m_nameLineEdit ); QHBoxLayout* basicControlsLayout = new QHBoxLayout; basicControlsLayout->setSpacing( 3 ); // set up volume knob m_volumeKnob = new knob( knobBright_26, NULL, tr( "Instrument volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setHintText( tr( "Volume:" ) + " ", "%" ); m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->setWhatsThis( tr( volume_help ) ); basicControlsLayout->addWidget( m_volumeKnob ); // set up panning knob m_panningKnob = new knob( knobBright_26, NULL, tr( "Panning" ) ); m_panningKnob->setHintText( tr( "Panning:" ) + " ", "" ); m_panningKnob->setLabel( tr( "PAN" ) ); basicControlsLayout->addWidget( m_panningKnob ); basicControlsLayout->addStretch(); // set up pitch knob m_pitchKnob = new knob( knobBright_26, NULL, tr( "Pitch" ) ); m_pitchKnob->setHintText( tr( "Pitch:" ) + " ", " " + tr( "cents" ) ); m_pitchKnob->setLabel( tr( "PITCH" ) ); basicControlsLayout->addWidget( m_pitchKnob ); // set up pitch range knob m_pitchRangeSpinBox= new LcdSpinBox( 2, NULL, tr( "Pitch range (semitones)" ) ); m_pitchRangeSpinBox->setLabel( tr( "RANGE" ) ); basicControlsLayout->addWidget( m_pitchRangeSpinBox ); basicControlsLayout->addStretch(); // setup spinbox for selecting FX-channel m_effectChannelNumber = new fxLineLcdSpinBox( 2, NULL, tr( "FX channel" ) ); m_effectChannelNumber->setLabel( tr( "FX" ) ); basicControlsLayout->addWidget( m_effectChannelNumber ); basicControlsLayout->addStretch(); QPushButton* saveSettingsBtn = new QPushButton( embed::getIconPixmap( "project_save" ), QString() ); saveSettingsBtn->setMinimumSize( 32, 32 ); connect( saveSettingsBtn, SIGNAL( clicked() ), this, SLOT( saveSettingsBtnClicked() ) ); toolTip::add( saveSettingsBtn, tr( "Save current channel settings in a preset-file" ) ); saveSettingsBtn->setWhatsThis( tr( "Click here, if you want to save current channel settings " "in a preset-file. Later you can load this preset by " "double-clicking it in the preset-browser." ) ); basicControlsLayout->addWidget( saveSettingsBtn ); generalSettingsLayout->addLayout( basicControlsLayout ); m_tabWidget = new tabWidget( "", this ); m_tabWidget->setFixedHeight( INSTRUMENT_HEIGHT + 10 ); // create tab-widgets m_ssView = new InstrumentSoundShapingView( m_tabWidget ); // FUNC tab QWidget* instrumentFunctions = new QWidget( m_tabWidget ); QVBoxLayout* instrumentFunctionsLayout = new QVBoxLayout( instrumentFunctions ); instrumentFunctionsLayout->setMargin( 5 ); m_noteStackingView = new InstrumentFunctionNoteStackingView( &m_track->m_noteStacking ); m_arpeggioView = new InstrumentFunctionArpeggioView( &m_track->m_arpeggio ); instrumentFunctionsLayout->addWidget( m_noteStackingView ); instrumentFunctionsLayout->addWidget( m_arpeggioView ); instrumentFunctionsLayout->addStretch(); // MIDI tab m_midiView = new InstrumentMidiIOView( m_tabWidget ); // FX tab m_effectView = new EffectRackView( m_track->m_audioPort.effects(), m_tabWidget ); m_tabWidget->addTab( m_ssView, tr( "ENV/LFO" ), 1 ); m_tabWidget->addTab( instrumentFunctions, tr( "FUNC" ), 2 ); m_tabWidget->addTab( m_effectView, tr( "FX" ), 3 ); m_tabWidget->addTab( m_midiView, tr( "MIDI" ), 4 ); // setup piano-widget m_pianoView = new PianoView( this ); m_pianoView->setFixedSize( INSTRUMENT_WIDTH, PIANO_HEIGHT ); vlayout->addWidget( generalSettingsWidget ); vlayout->addWidget( m_tabWidget ); vlayout->addWidget( m_pianoView ); setModel( _itv->model() ); updateInstrumentView(); setFixedWidth( INSTRUMENT_WIDTH ); resize( sizeHint() ); QMdiSubWindow * subWin = engine::mainWindow()->workspace()->addSubWindow( this ); Qt::WindowFlags flags = subWin->windowFlags(); flags |= Qt::MSWindowsFixedSizeDialogHint; flags &= ~Qt::WindowMaximizeButtonHint; subWin->setWindowFlags( flags ); subWin->setWindowIcon( embed::getIconPixmap( "instrument_track" ) ); subWin->setFixedSize( subWin->size() ); subWin->hide(); } InstrumentTrackWindow::~InstrumentTrackWindow() { InstrumentTrackView::s_windowCache.removeAll( this ); delete m_instrumentView; if( engine::mainWindow()->workspace() ) { parentWidget()->hide(); parentWidget()->deleteLater(); } } void InstrumentTrackWindow::setInstrumentTrackView( InstrumentTrackView* view ) { if( m_itv && view ) { m_itv->m_tlb->setChecked( false ); } m_itv = view; } void InstrumentTrackWindow::modelChanged() { m_track = castModel(); m_nameLineEdit->setText( m_track->name() ); m_track->disconnect( SIGNAL( nameChanged() ), this ); m_track->disconnect( SIGNAL( instrumentChanged() ), this ); connect( m_track, SIGNAL( nameChanged() ), this, SLOT( updateName() ) ); connect( m_track, SIGNAL( instrumentChanged() ), this, SLOT( updateInstrumentView() ) ); m_volumeKnob->setModel( &m_track->m_volumeModel ); m_panningKnob->setModel( &m_track->m_panningModel ); m_effectChannelNumber->setModel( &m_track->m_effectChannelModel ); m_pianoView->setModel( &m_track->m_piano ); if( m_track->instrument() && m_track->instrument()->flags().testFlag( Instrument::IsNotBendable ) == false ) { m_pitchKnob->setModel( &m_track->m_pitchModel ); m_pitchRangeSpinBox->setModel( &m_track->m_pitchRangeModel ); m_pitchKnob->show(); } else { m_pitchKnob->hide(); m_pitchKnob->setModel( NULL ); m_pitchRangeSpinBox->hide(); } m_ssView->setModel( &m_track->m_soundShaping ); m_noteStackingView->setModel( &m_track->m_noteStacking ); m_arpeggioView->setModel( &m_track->m_arpeggio ); m_midiView->setModel( &m_track->m_midiPort ); m_effectView->setModel( m_track->m_audioPort.effects() ); updateName(); } void InstrumentTrackWindow::saveSettingsBtnClicked() { FileDialog sfd( this, tr( "Save preset" ), "", tr( "XML preset file (*.xpf)" ) ); QString presetRoot = configManager::inst()->userPresetsDir(); if( !QDir( presetRoot ).exists() ) { QDir().mkdir( presetRoot ); } if( !QDir( presetRoot + m_track->instrumentName() ).exists() ) { QDir( presetRoot ).mkdir( m_track->instrumentName() ); } sfd.setAcceptMode( FileDialog::AcceptSave ); sfd.setDirectory( presetRoot + m_track->instrumentName() ); sfd.setFileMode( FileDialog::AnyFile ); if( sfd.exec() == QDialog::Accepted && !sfd.selectedFiles().isEmpty() && !sfd.selectedFiles().first().isEmpty() ) { DataFile dataFile( DataFile::InstrumentTrackSettings ); m_track->setSimpleSerializing(); m_track->saveSettings( dataFile, dataFile.content() ); QString f = sfd.selectedFiles()[0]; dataFile.writeFile( f ); } } void InstrumentTrackWindow::updateName() { setWindowTitle( m_track->name().length() > 25 ? ( m_track->name().left(24)+"..." ) : m_track->name() ); if( m_nameLineEdit->text() != m_track->name() ) { m_nameLineEdit->setText( m_track->name() ); } } void InstrumentTrackWindow::updateInstrumentView() { delete m_instrumentView; if( m_track->m_instrument != NULL ) { m_instrumentView = m_track->m_instrument->createView( m_tabWidget ); m_tabWidget->addTab( m_instrumentView, tr( "PLUGIN" ), 0 ); m_tabWidget->setActiveTab( 0 ); m_ssView->setFunctionsHidden( m_track->m_instrument->flags().testFlag( Instrument::IsSingleStreamed ) ); modelChanged(); // Get the instrument window to refresh m_track->dataChanged(); // Get the text on the trackButton to change } } void InstrumentTrackWindow::textChanged( const QString& newName ) { m_track->setName( newName ); engine::getSong()->setModified(); } void InstrumentTrackWindow::toggleVisibility( bool on ) { if( on ) { show(); parentWidget()->show(); parentWidget()->raise(); } else { parentWidget()->hide(); } } void InstrumentTrackWindow::closeEvent( QCloseEvent* event ) { event->ignore(); if( engine::mainWindow()->workspace() ) { parentWidget()->hide(); } else { hide(); } m_itv->m_tlb->setFocus(); m_itv->m_tlb->setChecked( false ); } void InstrumentTrackWindow::focusInEvent( QFocusEvent* ) { m_pianoView->setFocus(); } void InstrumentTrackWindow::dragEnterEventGeneric( QDragEnterEvent* event ) { stringPairDrag::processDragEnterEvent( event, "instrument,presetfile,pluginpresetfile" ); } void InstrumentTrackWindow::dragEnterEvent( QDragEnterEvent* event ) { dragEnterEventGeneric( event ); } void InstrumentTrackWindow::dropEvent( QDropEvent* event ) { QString type = stringPairDrag::decodeKey( event ); QString value = stringPairDrag::decodeValue( event ); if( type == "instrument" ) { m_track->loadInstrument( value ); engine::getSong()->setModified(); event->accept(); } else if( type == "presetfile" ) { DataFile dataFile( value ); InstrumentTrack::removeMidiPortNode( dataFile ); m_track->setSimpleSerializing(); m_track->loadSettings( dataFile.content().toElement() ); engine::getSong()->setModified(); event->accept(); } else if( type == "pluginpresetfile" ) { const QString ext = fileItem::extension( value ); Instrument * i = m_track->instrument(); if( !i->descriptor()->supportsFileType( ext ) ) { i = m_track->loadInstrument( engine::pluginFileHandling()[ext] ); } i->loadFile( value ); event->accept(); } } void InstrumentTrackWindow::saveSettings( QDomDocument& doc, QDomElement & thisElement ) { thisElement.setAttribute( "tab", m_tabWidget->activeTab() ); MainWindow::saveWidgetState( this, thisElement ); } void InstrumentTrackWindow::loadSettings( const QDomElement& thisElement ) { m_tabWidget->setActiveTab( thisElement.attribute( "tab" ).toInt() ); MainWindow::restoreWidgetState( this, thisElement ); if( isVisible() ) { m_itv->m_tlb->setChecked( true ); } } #include "moc_InstrumentTrack.cxx" lmms-1.0.0/src/tracks/SampleTrack.cpp0000644000175000017500000003053612313663627016204 0ustar tobytoby/* * SampleTrack.cpp - implementation of class SampleTrack, a track which * provides arrangement of samples * * Copyright (c) 2005-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include "gui_templates.h" #include "SampleTrack.h" #include "song.h" #include "embed.h" #include "engine.h" #include "tooltip.h" #include "AudioPort.h" #include "SamplePlayHandle.h" #include "SampleRecordHandle.h" #include "string_pair_drag.h" #include "knob.h" #include "MainWindow.h" #include "EffectRackView.h" #include "track_label_button.h" #include "config_mgr.h" SampleTCO::SampleTCO( track * _track ) : trackContentObject( _track ), m_sampleBuffer( new SampleBuffer ) { saveJournallingState( false ); setSampleFile( "" ); restoreJournallingState(); // we need to receive bpm-change-events, because then we have to // change length of this TCO connect( engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ), this, SLOT( updateLength( bpm_t ) ) ); } SampleTCO::~SampleTCO() { sharedObject::unref( m_sampleBuffer ); } void SampleTCO::changeLength( const MidiTime & _length ) { trackContentObject::changeLength( qMax( static_cast( _length ), DefaultTicksPerTact ) ); } const QString & SampleTCO::sampleFile() const { return m_sampleBuffer->audioFile(); } void SampleTCO::setSampleBuffer( SampleBuffer* sb ) { sharedObject::unref( m_sampleBuffer ); m_sampleBuffer = sb; updateLength(); emit sampleChanged(); } void SampleTCO::setSampleFile( const QString & _sf ) { m_sampleBuffer->setAudioFile( _sf ); updateLength(); emit sampleChanged(); } void SampleTCO::toggleRecord() { m_recordModel.setValue( !m_recordModel.value() ); emit dataChanged(); } void SampleTCO::updateLength( bpm_t ) { changeLength( sampleLength() ); } MidiTime SampleTCO::sampleLength() const { return (int)( m_sampleBuffer->frames() / engine::framesPerTick() ); } void SampleTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) { if( _this.parentNode().nodeName() == "clipboard" ) { _this.setAttribute( "pos", -1 ); } else { _this.setAttribute( "pos", startPosition() ); } _this.setAttribute( "len", length() ); _this.setAttribute( "muted", isMuted() ); _this.setAttribute( "src", sampleFile() ); if( sampleFile() == "" ) { QString s; _this.setAttribute( "data", m_sampleBuffer->toBase64( s ) ); } // TODO: start- and end-frame } void SampleTCO::loadSettings( const QDomElement & _this ) { if( _this.attribute( "pos" ).toInt() >= 0 ) { movePosition( _this.attribute( "pos" ).toInt() ); } setSampleFile( _this.attribute( "src" ) ); if( sampleFile().isEmpty() && _this.hasAttribute( "data" ) ) { m_sampleBuffer->loadFromBase64( _this.attribute( "data" ) ); } changeLength( _this.attribute( "len" ).toInt() ); setMuted( _this.attribute( "muted" ).toInt() ); } trackContentObjectView * SampleTCO::createView( trackView * _tv ) { return new SampleTCOView( this, _tv ); } SampleTCOView::SampleTCOView( SampleTCO * _tco, trackView * _tv ) : trackContentObjectView( _tco, _tv ), m_tco( _tco ) { // update UI and tooltip updateSample(); // track future changes of SampleTCO connect( m_tco, SIGNAL( sampleChanged() ), this, SLOT( updateSample() ) ); setStyle( QApplication::style() ); } SampleTCOView::~SampleTCOView() { } void SampleTCOView::updateSample() { update(); // set tooltip to filename so that user can see what sample this // sample-tco contains toolTip::add( this, ( m_tco->m_sampleBuffer->audioFile() != "" ) ? m_tco->m_sampleBuffer->audioFile() : tr( "double-click to select sample" ) ); } void SampleTCOView::contextMenuEvent( QContextMenuEvent * _cme ) { QMenu contextMenu( this ); if( fixedTCOs() == false ) { contextMenu.addAction( embed::getIconPixmap( "cancel" ), tr( "Delete (middle mousebutton)" ), this, SLOT( remove() ) ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "edit_cut" ), tr( "Cut" ), this, SLOT( cut() ) ); } contextMenu.addAction( embed::getIconPixmap( "edit_copy" ), tr( "Copy" ), m_tco, SLOT( copy() ) ); contextMenu.addAction( embed::getIconPixmap( "edit_paste" ), tr( "Paste" ), m_tco, SLOT( paste() ) ); contextMenu.addSeparator(); contextMenu.addAction( embed::getIconPixmap( "muted" ), tr( "Mute/unmute ( + middle click)" ), m_tco, SLOT( toggleMute() ) ); contextMenu.addAction( embed::getIconPixmap( "record" ), tr( "Set/clear record" ), m_tco, SLOT( toggleRecord() ) ); constructContextMenu( &contextMenu ); contextMenu.exec( QCursor::pos() ); } void SampleTCOView::dragEnterEvent( QDragEnterEvent * _dee ) { if( stringPairDrag::processDragEnterEvent( _dee, "samplefile,sampledata" ) == false ) { trackContentObjectView::dragEnterEvent( _dee ); } } void SampleTCOView::dropEvent( QDropEvent * _de ) { if( stringPairDrag::decodeKey( _de ) == "samplefile" ) { m_tco->setSampleFile( stringPairDrag::decodeValue( _de ) ); _de->accept(); } else if( stringPairDrag::decodeKey( _de ) == "sampledata" ) { m_tco->m_sampleBuffer->loadFromBase64( stringPairDrag::decodeValue( _de ) ); m_tco->updateLength(); update(); _de->accept(); engine::getSong()->setModified(); } else { trackContentObjectView::dropEvent( _de ); } } void SampleTCOView::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && _me->modifiers() & Qt::ControlModifier && _me->modifiers() & Qt::ShiftModifier ) { m_tco->toggleRecord(); } else { trackContentObjectView::mousePressEvent( _me ); } } void SampleTCOView::mouseDoubleClickEvent( QMouseEvent * ) { QString af = m_tco->m_sampleBuffer->openAudioFile(); if( af != "" && af != m_tco->m_sampleBuffer->audioFile() ) { m_tco->setSampleFile( af ); engine::getSong()->setModified(); } } void SampleTCOView::paintEvent( QPaintEvent * _pe ) { QPainter p( this ); const QColor styleColor = p.pen().brush().color(); QColor c; if( !( m_tco->getTrack()->isMuted() || m_tco->isMuted() ) ) c = isSelected() ? QColor( 0, 0, 224 ) : styleColor; else c = QColor( 80, 80, 80 ); QLinearGradient grad( 0, 0, 0, height() ); grad.setColorAt( 1, c.darker( 300 ) ); grad.setColorAt( 0, c ); p.setBrush( grad ); p.setPen( c.lighter( 160 ) ); p.drawRect( 1, 1, width()-3, height()-3 ); p.setBrush( QBrush() ); p.setPen( c.darker( 300 ) ); p.drawRect( 0, 0, width()-1, height()-1 ); if( m_tco->getTrack()->isMuted() || m_tco->isMuted() ) { p.setPen( QColor( 128, 128, 128 ) ); } else { p.setPen( c.lighter( 200 ) ); } QRect r = QRect( 1, 1, qMax( static_cast( m_tco->sampleLength() * pixelsPerTact() / DefaultTicksPerTact ), 1 ), height() - 4 ); p.setClipRect( QRect( 1, 1, width() - 2, height() - 2 ) ); m_tco->m_sampleBuffer->visualize( p, r, _pe->rect() ); if( r.width() < width() - 1 ) { p.drawLine( r.x() + r.width(), r.y() + r.height() / 2, width() - 2, r.y() + r.height() / 2 ); } p.translate( 0, 0 ); if( m_tco->isMuted() ) { p.drawPixmap( 3, 8, embed::getIconPixmap( "muted", 16, 16 ) ); } if( m_tco->isRecord() ) { p.setFont( pointSize<7>( p.font() ) ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 10, p.fontMetrics().height()+1, "Rec" ); p.setPen( QColor( 255, 60, 60 ) ); p.drawText( 9, p.fontMetrics().height(), "Rec" ); p.setBrush( QBrush( QColor( 255, 60, 60 ) ) ); p.drawEllipse( 4, 5, 4, 4 ); } } SampleTrack::SampleTrack( TrackContainer* tc ) : track( track::SampleTrack, tc ), m_audioPort( tr( "Sample track" ) ), m_volumeModel( DefaultVolume, MinVolume, MaxVolume, 1.0, this, tr( "Volume" ) ) { setName( tr( "Sample track" ) ); } SampleTrack::~SampleTrack() { engine::mixer()->removePlayHandles( this ); } bool SampleTrack::play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _offset, int /*_tco_num*/ ) { m_audioPort.effects()->startRunning(); bool played_a_note = false; // will be return variable for( int i = 0; i < numOfTCOs(); ++i ) { trackContentObject * tco = getTCO( i ); if( tco->startPosition() != _start ) { continue; } SampleTCO * st = dynamic_cast( tco ); if( !st->isMuted() ) { PlayHandle* handle; if( st->isRecord() ) { if( !engine::getSong()->isRecording() ) { return played_a_note; } SampleRecordHandle* smpHandle = new SampleRecordHandle( st ); handle = smpHandle; } else { SamplePlayHandle* smpHandle = new SamplePlayHandle( st ); smpHandle->setVolumeModel( &m_volumeModel ); handle = smpHandle; } //TODO: check whether this works // handle->setBBTrack( _tco_num ); handle->setOffset( _offset ); // send it to the mixer engine::mixer()->addPlayHandle( handle ); played_a_note = true; } } return played_a_note; } trackView * SampleTrack::createView( TrackContainerView* tcv ) { return new SampleTrackView( this, tcv ); } trackContentObject * SampleTrack::createTCO( const MidiTime & ) { return new SampleTCO( this ); } void SampleTrack::saveTrackSpecificSettings( QDomDocument & _doc, QDomElement & _this ) { m_audioPort.effects()->saveState( _doc, _this ); #if 0 _this.setAttribute( "icon", tlb->pixmapFile() ); #endif m_volumeModel.saveSettings( _doc, _this, "vol" ); } void SampleTrack::loadTrackSpecificSettings( const QDomElement & _this ) { QDomNode node = _this.firstChild(); m_audioPort.effects()->clear(); while( !node.isNull() ) { if( node.isElement() ) { if( m_audioPort.effects()->nodeName() == node.nodeName() ) { m_audioPort.effects()->restoreState( node.toElement() ); } } node = node.nextSibling(); } m_volumeModel.loadSettings( _this, "vol" ); } SampleTrackView::SampleTrackView( SampleTrack * _t, TrackContainerView* tcv ) : trackView( _t, tcv ) { setFixedHeight( 32 ); trackLabelButton * tlb = new trackLabelButton( this, getTrackSettingsWidget() ); connect( tlb, SIGNAL( clicked( bool ) ), this, SLOT( showEffects() ) ); tlb->setIcon( embed::getIconPixmap( "sample_track" ) ); tlb->move( 3, 1 ); tlb->show(); m_volumeKnob = new knob( knobSmall_17, getTrackSettingsWidget(), tr( "Track volume" ) ); m_volumeKnob->setVolumeKnob( true ); m_volumeKnob->setModel( &_t->m_volumeModel ); m_volumeKnob->setHintText( tr( "Channel volume:" ) + " ", "%" ); if( configManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() ) { m_volumeKnob->move( DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT-2*24, 2 ); } else { m_volumeKnob->move( DEFAULT_SETTINGS_WIDGET_WIDTH-2*24, 2 ); } m_volumeKnob->setLabel( tr( "VOL" ) ); m_volumeKnob->show(); m_effectRack = new EffectRackView( _t->audioPort()->effects() ); m_effectRack->setFixedSize( 240, 242 ); m_effWindow = engine::mainWindow()->workspace()->addSubWindow( m_effectRack ); m_effWindow->setAttribute( Qt::WA_DeleteOnClose, false ); m_effWindow->layout()->setSizeConstraint( QLayout::SetFixedSize ); m_effWindow->setWindowTitle( _t->name() ); m_effWindow->hide(); setModel( _t ); } SampleTrackView::~SampleTrackView() { m_effWindow->deleteLater(); } void SampleTrackView::showEffects() { if( m_effWindow->isHidden() ) { m_effectRack->show(); m_effWindow->show(); m_effWindow->raise(); } else { m_effWindow->hide(); } } void SampleTrackView::modelChanged() { SampleTrack * st = castModel(); m_volumeKnob->setModel( &st->m_volumeModel ); trackView::modelChanged(); } #include "moc_SampleTrack.cxx" lmms-1.0.0/src/tracks/bb_track.cpp0000644000175000017500000003075312313663627015546 0ustar tobytoby/* * bb_track.cpp - implementation of class bbTrack and bbTCO * * Copyright (c) 2004-2014 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include "bb_editor.h" #include "bb_track.h" #include "bb_track_container.h" #include "embed.h" #include "engine.h" #include "gui_templates.h" #include "MainWindow.h" #include "Mixer.h" #include "rename_dialog.h" #include "song.h" #include "SongEditor.h" #include "templates.h" #include "track_label_button.h" bbTrack::infoMap bbTrack::s_infoMap; bbTCO::bbTCO( track * _track, unsigned int _color ) : trackContentObject( _track ), m_color( _color > 0 ? _color : defaultColor() ) { tact_t t = engine::getBBTrackContainer()->lengthOfBB( bbTrack::numOfBBTrack( getTrack() ) ); if( t > 0 ) { saveJournallingState( false ); changeLength( MidiTime( t, 0 ) ); restoreJournallingState(); } } bbTCO::~bbTCO() { } void bbTCO::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "name", name() ); if( _this.parentNode().nodeName() == "clipboard" ) { _this.setAttribute( "pos", -1 ); } else { _this.setAttribute( "pos", startPosition() ); } _this.setAttribute( "len", length() ); _this.setAttribute( "muted", isMuted() ); _this.setAttribute( "color", m_color ); } void bbTCO::loadSettings( const QDomElement & _this ) { setName( _this.attribute( "name" ) ); if( _this.attribute( "pos" ).toInt() >= 0 ) { movePosition( _this.attribute( "pos" ).toInt() ); } changeLength( _this.attribute( "len" ).toInt() ); if( _this.attribute( "muted" ).toInt() != isMuted() ) { toggleMute(); } if( _this.attribute( "color" ).toUInt() != 0 ) { m_color = _this.attribute( "color" ).toUInt(); } } trackContentObjectView * bbTCO::createView( trackView * _tv ) { return new bbTCOView( this, _tv ); } bbTCOView::bbTCOView( trackContentObject * _tco, trackView * _tv ) : trackContentObjectView( _tco, _tv ), m_bbTCO( dynamic_cast( _tco ) ) { } bbTCOView::~bbTCOView() { } void bbTCOView::constructContextMenu( QMenu * _cm ) { QAction * a = new QAction( embed::getIconPixmap( "bb_track" ), tr( "Open in Beat+Bassline-Editor" ), _cm ); _cm->insertAction( _cm->actions()[0], a ); connect( a, SIGNAL( triggered( bool ) ), this, SLOT( openInBBEditor() ) ); _cm->insertSeparator( _cm->actions()[1] ); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ), this, SLOT( resetName() ) ); _cm->addAction( embed::getIconPixmap( "edit_rename" ), tr( "Change name" ), this, SLOT( changeName() ) ); _cm->addAction( embed::getIconPixmap( "colorize" ), tr( "Change color" ), this, SLOT( changeColor() ) ); } void bbTCOView::mouseDoubleClickEvent( QMouseEvent * ) { openInBBEditor(); } void bbTCOView::paintEvent( QPaintEvent * ) { QColor col( m_bbTCO->m_color ); if( m_bbTCO->getTrack()->isMuted() || m_bbTCO->isMuted() ) { col = QColor( 160, 160, 160 ); } if( isSelected() == true ) { col = QColor( qMax( col.red() - 128, 0 ), qMax( col.green() - 128, 0 ), 255 ); } QPainter p( this ); QLinearGradient lingrad( 0, 0, 0, height() ); lingrad.setColorAt( 0, col.light( 130 ) ); lingrad.setColorAt( 1, col.light( 70 ) ); p.fillRect( rect(), lingrad ); tact_t t = engine::getBBTrackContainer()->lengthOfBB( bbTrack::numOfBBTrack( m_bbTCO->getTrack() ) ); if( m_bbTCO->length() > MidiTime::ticksPerTact() && t > 0 ) { for( int x = static_cast( t * pixelsPerTact() ); x < width()-2; x += static_cast( t * pixelsPerTact() ) ) { p.setPen( col.light( 80 ) ); p.drawLine( x, 1, x, 5 ); p.setPen( col.light( 120 ) ); p.drawLine( x, height() - 6, x, height() - 2 ); } } p.setPen( col.lighter( 130 ) ); p.drawRect( 1, 1, rect().right()-2, rect().bottom()-2 ); p.setPen( col.darker( 300 ) ); p.drawRect( 0, 0, rect().right(), rect().bottom() ); p.setFont( pointSize<8>( p.font() ) ); p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 4, p.fontMetrics().height()+1, m_bbTCO->name() ); p.setPen( QColor( 255, 255, 255 ) ); p.drawText( 3, p.fontMetrics().height(), m_bbTCO->name() ); if( m_bbTCO->isMuted() ) { p.drawPixmap( 3, p.fontMetrics().height() + 1, embed::getIconPixmap( "muted", 16, 16 ) ); } } void bbTCOView::openInBBEditor() { engine::getBBTrackContainer()->setCurrentBB( bbTrack::numOfBBTrack( m_bbTCO->getTrack() ) ); engine::mainWindow()->toggleBBEditorWin( true ); } void bbTCOView::resetName() { m_bbTCO->setName( m_bbTCO->getTrack()->name() ); } void bbTCOView::changeName() { QString s = m_bbTCO->name(); renameDialog rename_dlg( s ); rename_dlg.exec(); m_bbTCO->setName( s ); } void bbTCOView::changeColor() { QColor _new_color = QColorDialog::getColor( m_bbTCO->m_color ); if( !_new_color.isValid() ) { return; } if( isSelected() ) { QVector selected = engine::songEditor()->selectedObjects(); for( QVector::iterator it = selected.begin(); it != selected.end(); ++it ) { bbTCOView * bb_tcov = dynamic_cast( *it ); if( bb_tcov ) { bb_tcov->setColor( _new_color ); } } } else { setColor( _new_color ); } } void bbTCOView::setColor( QColor _new_color ) { if( _new_color.rgb() != m_bbTCO->m_color ) { m_bbTCO->m_color = _new_color.rgb(); engine::getSong()->setModified(); update(); } } bbTrack::bbTrack( TrackContainer* tc ) : track( BBTrack, tc ) { int bbNum = s_infoMap.size(); s_infoMap[this] = bbNum; setName( tr( "Beat/Bassline %1" ).arg( bbNum ) ); engine::getBBTrackContainer()->setCurrentBB( bbNum ); engine::getBBTrackContainer()->updateComboBox(); connect( this, SIGNAL( nameChanged() ), engine::getBBTrackContainer(), SLOT( updateComboBox() ) ); } bbTrack::~bbTrack() { engine::mixer()->removePlayHandles( this ); const int bb = s_infoMap[this]; engine::getBBTrackContainer()->removeBB( bb ); for( infoMap::iterator it = s_infoMap.begin(); it != s_infoMap.end(); ++it ) { if( it.value() > bb ) { --it.value(); } } s_infoMap.remove( this ); // remove us from TC so bbTrackContainer::numOfBBs() returns a smaller // value and thus combobox-updating in bbTrackContainer works well trackContainer()->removeTrack( this ); engine::getBBTrackContainer()->updateComboBox(); } // play _frames frames of given TCO within starting with _start bool bbTrack::play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _offset, int _tco_num ) { if( isMuted() ) { return false; } if( _tco_num >= 0 ) { return engine::getBBTrackContainer()->play( _start, _frames, _offset, s_infoMap[this] ); } tcoVector tcos; getTCOsInRange( tcos, _start, _start + static_cast( _frames / engine::framesPerTick() ) ); if( tcos.size() == 0 ) { return false; } MidiTime lastPosition; MidiTime lastLen; for( tcoVector::iterator it = tcos.begin(); it != tcos.end(); ++it ) { if( !( *it )->isMuted() && ( *it )->startPosition() >= lastPosition ) { lastPosition = ( *it )->startPosition(); lastLen = ( *it )->length(); } } if( _start - lastPosition < lastLen ) { return engine::getBBTrackContainer()->play( _start - lastPosition, _frames, _offset, s_infoMap[this] ); } return false; } trackView * bbTrack::createView( TrackContainerView* tcv ) { return new bbTrackView( this, tcv ); } trackContentObject * bbTrack::createTCO( const MidiTime & _pos ) { // if we're creating a new bbTCO, we colorize it according to the // previous bbTCO, so we have to get all TCOs from 0 to _pos and // pickup the last and take the color if it tcoVector tcos; getTCOsInRange( tcos, 0, _pos ); if( tcos.size() > 0 && dynamic_cast( tcos.back() ) != NULL ) { return new bbTCO( this, dynamic_cast( tcos.back() )->color() ); } return new bbTCO( this ); } void bbTrack::saveTrackSpecificSettings( QDomDocument & _doc, QDomElement & _this ) { // _this.setAttribute( "icon", m_trackLabel->pixmapFile() ); /* _this.setAttribute( "current", s_infoMap[this] == engine::getBBEditor()->currentBB() );*/ if( s_infoMap[this] == 0 && _this.parentNode().parentNode().nodeName() != "clone" && _this.parentNode().nodeName() != "journaldata" ) { ( (JournallingObject *)( engine::getBBTrackContainer() ) )-> saveState( _doc, _this ); } if( _this.parentNode().parentNode().nodeName() == "clone" ) { _this.setAttribute( "clonebbt", s_infoMap[this] ); } } void bbTrack::loadTrackSpecificSettings( const QDomElement & _this ) { /* if( _this.attribute( "icon" ) != "" ) { m_trackLabel->setPixmapFile( _this.attribute( "icon" ) ); }*/ if( _this.hasAttribute( "clonebbt" ) ) { const int src = _this.attribute( "clonebbt" ).toInt(); const int dst = s_infoMap[this]; engine::getBBTrackContainer()->createTCOsForBB( dst ); TrackContainer::TrackList tl = engine::getBBTrackContainer()->tracks(); // copy TCOs of all tracks from source BB (at bar "src") to destination // TCOs (which are created if they do not exist yet) for( TrackContainer::TrackList::iterator it = tl.begin(); it != tl.end(); ++it ) { ( *it )->getTCO( src )->copy(); ( *it )->getTCO( dst )->paste(); } setName( tr( "Clone of %1" ).arg( _this.parentNode().toElement().attribute( "name" ) ) ); } else { QDomNode node = _this.namedItem( TrackContainer::classNodeName() ); if( node.isElement() ) { ( (JournallingObject *)engine::getBBTrackContainer() )-> restoreState( node.toElement() ); } } /* doesn't work yet because bbTrack-ctor also sets current bb so if bb-tracks are created after this function is called, this doesn't help at all.... if( _this.attribute( "current" ).toInt() ) { engine::getBBEditor()->setCurrentBB( s_infoMap[this] ); }*/ } // return pointer to bbTrack specified by _bb_num bbTrack * bbTrack::findBBTrack( int _bb_num ) { for( infoMap::iterator it = s_infoMap.begin(); it != s_infoMap.end(); ++it ) { if( it.value() == _bb_num ) { return it.key(); } } return NULL; } int bbTrack::numOfBBTrack( track * _track ) { if( dynamic_cast( _track ) != NULL ) { return s_infoMap[dynamic_cast( _track )]; } return 0; } void bbTrack::swapBBTracks( track * _track1, track * _track2 ) { bbTrack * t1 = dynamic_cast( _track1 ); bbTrack * t2 = dynamic_cast( _track2 ); if( t1 != NULL && t2 != NULL ) { qSwap( s_infoMap[t1], s_infoMap[t2] ); engine::getBBTrackContainer()->swapBB( s_infoMap[t1], s_infoMap[t2] ); engine::getBBTrackContainer()->setCurrentBB( s_infoMap[t1] ); } } bbTrackView::bbTrackView( bbTrack * _bbt, TrackContainerView* tcv ) : trackView( _bbt, tcv ), m_bbTrack( _bbt ) { setFixedHeight( 32 ); // drag'n'drop with bb-tracks only causes troubles (and makes no sense // too), so disable it setAcceptDrops( false ); m_trackLabel = new trackLabelButton( this, getTrackSettingsWidget() ); m_trackLabel->setIcon( embed::getIconPixmap( "bb_track" ) ); m_trackLabel->move( 3, 1 ); m_trackLabel->show(); connect( m_trackLabel, SIGNAL( clicked( bool ) ), this, SLOT( clickedTrackLabel() ) ); setModel( _bbt ); } bbTrackView::~bbTrackView() { engine::getBBEditor()->removeBBView( bbTrack::s_infoMap[m_bbTrack] ); } bool bbTrackView::close() { engine::getBBEditor()->removeBBView( bbTrack::s_infoMap[m_bbTrack] ); return trackView::close(); } void bbTrackView::clickedTrackLabel() { engine::getBBTrackContainer()->setCurrentBB( bbTrack::numOfBBTrack( m_bbTrack ) ); engine::getBBEditor()->show(); /* foreach( bbTrackView * tv, trackContainerView()->findChildren() ) { tv->m_trackLabel->update(); }*/ } #include "moc_bb_track.cxx" lmms-1.0.0/src/tracks/AutomationTrack.cpp0000644000175000017500000001030412313663627017072 0ustar tobytoby/* * AutomationTrack.cpp - AutomationTrack handles automation of objects without * a track * * Copyright (c) 2008-2014 Tobias Doerffel * Copyright (c) 2006-2008 Javier Serrano Polo * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "AutomationTrack.h" #include "AutomationPattern.h" #include "engine.h" #include "embed.h" #include "ProjectJournal.h" #include "string_pair_drag.h" #include "TrackContainerView.h" #include "track_label_button.h" AutomationTrack::AutomationTrack( TrackContainer* tc, bool _hidden ) : track( _hidden ? HiddenAutomationTrack : track::AutomationTrack, tc ) { setName( tr( "Automation track" ) ); } AutomationTrack::~AutomationTrack() { } bool AutomationTrack::play( const MidiTime & _start, const fpp_t _frames, const f_cnt_t _frame_base, int _tco_num ) { if( isMuted() ) { return false; } tcoVector tcos; if( _tco_num >= 0 ) { trackContentObject * tco = getTCO( _tco_num ); tcos.push_back( tco ); } else { getTCOsInRange( tcos, _start, _start + static_cast( _frames / engine::framesPerTick()) ); } for( tcoVector::iterator it = tcos.begin(); it != tcos.end(); ++it ) { AutomationPattern * p = dynamic_cast( *it ); if( p == NULL || ( *it )->isMuted() ) { continue; } MidiTime cur_start = _start; if( _tco_num < 0 ) { cur_start -= p->startPosition(); } p->processMidiTime( cur_start ); } return false; } trackView * AutomationTrack::createView( TrackContainerView* tcv ) { return new AutomationTrackView( this, tcv ); } trackContentObject * AutomationTrack::createTCO( const MidiTime & ) { return new AutomationPattern( this ); } void AutomationTrack::saveTrackSpecificSettings( QDomDocument & _doc, QDomElement & _this ) { } void AutomationTrack::loadTrackSpecificSettings( const QDomElement & _this ) { // just in case something somehow wrent wrong... if( type() == HiddenAutomationTrack ) { setMuted( false ); } } AutomationTrackView::AutomationTrackView( AutomationTrack * _at, TrackContainerView* tcv ) : trackView( _at, tcv ) { setFixedHeight( 32 ); trackLabelButton * tlb = new trackLabelButton( this, getTrackSettingsWidget() ); tlb->setIcon( embed::getIconPixmap( "automation_track" ) ); tlb->move( 3, 1 ); tlb->show(); setModel( _at ); } AutomationTrackView::~AutomationTrackView() { } void AutomationTrackView::dragEnterEvent( QDragEnterEvent * _dee ) { stringPairDrag::processDragEnterEvent( _dee, "automatable_model" ); } void AutomationTrackView::dropEvent( QDropEvent * _de ) { QString type = stringPairDrag::decodeKey( _de ); QString val = stringPairDrag::decodeValue( _de ); if( type == "automatable_model" ) { AutomatableModel * mod = dynamic_cast( engine::projectJournal()-> journallingObject( val.toInt() ) ); if( mod != NULL ) { MidiTime pos = MidiTime( trackContainerView()-> currentPosition() + ( _de->pos().x() - getTrackContentWidget()->x() ) * MidiTime::ticksPerTact() / static_cast( trackContainerView()->pixelsPerTact() ) ) .toNearestTact(); if( pos.getTicks() < 0 ) { pos.setTicks( 0 ); } trackContentObject * tco = getTrack()->createTCO( pos ); AutomationPattern * pat = dynamic_cast( tco ); pat->addObject( mod ); pat->movePosition( pos ); } } update(); } lmms-1.0.0/src/tracks/pattern.cpp0000644000175000017500000006241012313663627015447 0ustar tobytoby/* * pattern.cpp - implementation of class pattern which holds notes * * Copyright (c) 2004-2014 Tobias Doerffel * Copyright (c) 2005-2007 Danny McRae * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include "pattern.h" #include "InstrumentTrack.h" #include "templates.h" #include "gui_templates.h" #include "embed.h" #include "engine.h" #include "PianoRoll.h" #include "TrackContainer.h" #include "rename_dialog.h" #include "SampleBuffer.h" #include "AudioSampleRecorder.h" #include "song.h" #include "tooltip.h" #include "bb_track_container.h" #include "string_pair_drag.h" #include "MainWindow.h" QPixmap * patternView::s_stepBtnOn = NULL; QPixmap * patternView::s_stepBtnOverlay = NULL; QPixmap * patternView::s_stepBtnOff = NULL; QPixmap * patternView::s_stepBtnOffLight = NULL; pattern::pattern( InstrumentTrack * _instrument_track ) : trackContentObject( _instrument_track ), m_instrumentTrack( _instrument_track ), m_patternType( BeatPattern ), m_steps( MidiTime::stepsPerTact() ) { setName( _instrument_track->name() ); init(); } pattern::pattern( const pattern & _pat_to_copy ) : trackContentObject( _pat_to_copy.m_instrumentTrack ), m_instrumentTrack( _pat_to_copy.m_instrumentTrack ), m_patternType( _pat_to_copy.m_patternType ), m_steps( _pat_to_copy.m_steps ) { for( NoteVector::ConstIterator it = _pat_to_copy.m_notes.begin(); it != _pat_to_copy.m_notes.end(); ++it ) { m_notes.push_back( new note( **it ) ); } init(); } pattern::~pattern() { for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { delete *it; } m_notes.clear(); } void pattern::init() { connect( engine::getSong(), SIGNAL( timeSignatureChanged( int, int ) ), this, SLOT( changeTimeSignature() ) ); saveJournallingState( false ); ensureBeatNotes(); changeLength( length() ); restoreJournallingState(); } MidiTime pattern::length() const { if( m_patternType == BeatPattern ) { return beatPatternLength(); } tick_t max_length = MidiTime::ticksPerTact(); for( NoteVector::ConstIterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->length() > 0 ) { max_length = qMax( max_length, ( *it )->endPos() ); } } return MidiTime( max_length ).nextFullTact() * MidiTime::ticksPerTact(); } MidiTime pattern::beatPatternLength() const { tick_t max_length = MidiTime::ticksPerTact(); for( NoteVector::ConstIterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->length() < 0 ) { max_length = qMax( max_length, ( *it )->pos() + MidiTime::ticksPerTact() / MidiTime::stepsPerTact() ); } } if( m_steps != MidiTime::stepsPerTact() ) { max_length = m_steps * MidiTime::ticksPerTact() / MidiTime::stepsPerTact() ; } return MidiTime( max_length ).nextFullTact() * MidiTime::ticksPerTact(); } note * pattern::addNote( const note & _new_note, const bool _quant_pos ) { note * new_note = new note( _new_note ); if( _quant_pos && engine::pianoRoll() ) { new_note->quantizePos( engine::pianoRoll()->quantization() ); } engine::mixer()->lock(); if( m_notes.size() == 0 || m_notes.back()->pos() <= new_note->pos() ) { m_notes.push_back( new_note ); } else { // simple algorithm for inserting the note between two // notes with smaller and greater position // maybe it could be optimized by starting in the middle and // going forward or backward but note-inserting isn't that // time-critical since it is usually not done while playing... long new_note_abs_time = new_note->pos(); NoteVector::Iterator it = m_notes.begin(); while( it != m_notes.end() && ( *it )->pos() < new_note_abs_time ) { ++it; } m_notes.insert( it, new_note ); } engine::mixer()->unlock(); checkType(); changeLength( length() ); emit dataChanged(); updateBBTrack(); return new_note; } void pattern::removeNote( const note * _note_to_del ) { engine::mixer()->lock(); NoteVector::Iterator it = m_notes.begin(); while( it != m_notes.end() ) { if( *it == _note_to_del ) { delete *it; m_notes.erase( it ); break; } ++it; } engine::mixer()->unlock(); checkType(); changeLength( length() ); emit dataChanged(); updateBBTrack(); } // returns a pointer to the note at specified step, or NULL if note doesn't exist note * pattern::noteAtStep( int _step ) { for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->pos() == ( _step * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() ) { return *it; } } return NULL; } note * pattern::rearrangeNote( const note * _note_to_proc, const bool _quant_pos ) { // just rearrange the position of the note by removing it and adding // a copy of it -> addNote inserts it at the correct position note copy_of_note( *_note_to_proc ); removeNote( _note_to_proc ); return addNote( copy_of_note, _quant_pos ); } void pattern::rearrangeAllNotes() { // sort notes by start time qSort(m_notes.begin(), m_notes.end(), note::lessThan ); } void pattern::clearNotes() { engine::mixer()->lock(); for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { delete *it; } m_notes.clear(); engine::mixer()->unlock(); checkType(); emit dataChanged(); } void pattern::setStep( int _step, bool _enabled ) { for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->pos() == ( _step * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() && ( *it )->length() <= 0 ) { ( *it )->setLength( _enabled ? -DefaultTicksPerTact : 0 ); } } } void pattern::setType( PatternTypes _new_pattern_type ) { if( _new_pattern_type == BeatPattern || _new_pattern_type == MelodyPattern ) { m_patternType = _new_pattern_type; } } void pattern::checkType() { NoteVector::Iterator it = m_notes.begin(); while( it != m_notes.end() ) { if( ( *it )->length() > 0 ) { setType( pattern::MelodyPattern ); return; } ++it; } setType( pattern::BeatPattern ); } void pattern::saveSettings( QDomDocument & _doc, QDomElement & _this ) { _this.setAttribute( "type", m_patternType ); _this.setAttribute( "name", name() ); // as the target of copied/dragged pattern is always an existing // pattern, we must not store actual position, instead we store -1 // which tells loadSettings() not to mess around with position if( _this.parentNode().nodeName() == "clipboard" || _this.parentNode().nodeName() == "dnddata" ) { _this.setAttribute( "pos", -1 ); } else { _this.setAttribute( "pos", startPosition() ); } _this.setAttribute( "len", length() ); _this.setAttribute( "muted", isMuted() ); _this.setAttribute( "steps", m_steps ); // now save settings of all notes for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->length() ) { ( *it )->saveState( _doc, _this ); } } } void pattern::loadSettings( const QDomElement & _this ) { m_patternType = static_cast( _this.attribute( "type" ).toInt() ); setName( _this.attribute( "name" ) ); if( _this.attribute( "pos" ).toInt() >= 0 ) { movePosition( _this.attribute( "pos" ).toInt() ); } changeLength( MidiTime( _this.attribute( "len" ).toInt() ) ); if( _this.attribute( "muted" ).toInt() != isMuted() ) { toggleMute(); } clearNotes(); QDomNode node = _this.firstChild(); while( !node.isNull() ) { if( node.isElement() && !node.toElement().attribute( "metadata" ).toInt() ) { note * n = new note; n->restoreState( node.toElement() ); m_notes.push_back( n ); } node = node.nextSibling(); } m_steps = _this.attribute( "steps" ).toInt(); if( m_steps == 0 ) { m_steps = MidiTime::stepsPerTact(); } ensureBeatNotes(); checkType(); emit dataChanged(); updateBBTrack(); } void pattern::clear() { clearNotes(); ensureBeatNotes(); } void pattern::addSteps() { m_steps += MidiTime::stepsPerTact(); ensureBeatNotes(); emit dataChanged(); updateBBTrack(); } void pattern::removeSteps() { int _n = MidiTime::stepsPerTact(); if( _n < m_steps ) { for( int i = m_steps - _n; i < m_steps; ++i ) { for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->pos() == ( i * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() && ( *it )->length() <= 0 ) { removeNote( *it ); break; } } } m_steps -= _n; emit dataChanged(); } updateBBTrack(); } trackContentObjectView * pattern::createView( trackView * _tv ) { return new patternView( this, _tv ); } void pattern::ensureBeatNotes() { // make sure, that all step-note exist for( int i = 0; i < m_steps; ++i ) { bool found = false; for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ++it ) { // if a note in this position is the one we want if( ( *it )->pos() == ( i * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() && ( *it )->length() <= 0 ) { found = true; break; } } if( found == false ) { addNote( note( MidiTime( 0 ), MidiTime( ( i * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() ) ), false ); } } // remove notes we no longer need: // that is, disabled notes that no longer fall to the steps of the new time sig for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ) { bool needed = false; for( int i = 0; i < m_steps; ++i ) { if( ( *it )->pos() == ( i * MidiTime::ticksPerTact() ) / MidiTime::stepsPerTact() || ( *it )->length() != 0 ) { needed = true; break; } } if( needed == false ) { delete *it; it = m_notes.erase( it ); } else ++it; } } void pattern::updateBBTrack() { if( getTrack()->trackContainer() == engine::getBBTrackContainer() ) { engine::getBBTrackContainer()->updateBBTrack( this ); } if( engine::pianoRoll()->currentPattern() == this ) { engine::pianoRoll()->update(); } } bool pattern::empty() { for( NoteVector::ConstIterator it = m_notes.begin(); it != m_notes.end(); ++it ) { if( ( *it )->length() != 0 ) { return false; } } return true; } void pattern::changeTimeSignature() { MidiTime last_pos = MidiTime::ticksPerTact(); for( NoteVector::ConstIterator cit = m_notes.begin(); cit != m_notes.end(); ++cit ) { if( ( *cit )->length() < 0 && ( *cit )->pos() > last_pos ) { last_pos = ( *cit )->pos()+MidiTime::ticksPerTact() / MidiTime::stepsPerTact(); } } last_pos = last_pos.nextFullTact() * MidiTime::ticksPerTact(); for( NoteVector::Iterator it = m_notes.begin(); it != m_notes.end(); ) { if( ( *it )->length() == 0 && ( *it )->pos() >= last_pos ) { delete *it; it = m_notes.erase( it ); --m_steps; } else { ++it; } } m_steps = qMax( qMax( m_steps, MidiTime::stepsPerTact() ), last_pos.getTact() * MidiTime::stepsPerTact() ); ensureBeatNotes(); updateBBTrack(); } patternView::patternView( pattern * _pattern, trackView * _parent ) : trackContentObjectView( _pattern, _parent ), m_pat( _pattern ), m_paintPixmap(), m_needsUpdate( true ) { connect( engine::pianoRoll(), SIGNAL( currentPatternChanged() ), this, SLOT( update() ) ); if( s_stepBtnOn == NULL ) { s_stepBtnOn = new QPixmap( embed::getIconPixmap( "step_btn_on_100" ) ); } if( s_stepBtnOverlay == NULL ) { s_stepBtnOverlay = new QPixmap( embed::getIconPixmap( "step_btn_on_yellow" ) ); } if( s_stepBtnOff == NULL ) { s_stepBtnOff = new QPixmap( embed::getIconPixmap( "step_btn_off" ) ); } if( s_stepBtnOffLight == NULL ) { s_stepBtnOffLight = new QPixmap( embed::getIconPixmap( "step_btn_off_light" ) ); } setFixedHeight( parentWidget()->height() - 2 ); setAutoResizeEnabled( false ); toolTip::add( this, tr( "double-click to open this pattern in piano-roll\n" "use mouse wheel to set volume of a step" ) ); setStyle( QApplication::style() ); } patternView::~patternView() { if( engine::pianoRoll()->currentPattern() == m_pat ) { engine::pianoRoll()->setCurrentPattern( NULL ); // we have to have the song-editor to stop playing if it played // us before if( engine::getSong()->isPlaying() && engine::getSong()->playMode() == song::Mode_PlayPattern ) { engine::getSong()->playPattern( NULL ); } } } void patternView::update() { m_needsUpdate = true; m_pat->changeLength( m_pat->length() ); trackContentObjectView::update(); } void patternView::openInPianoRoll() { engine::pianoRoll()->setCurrentPattern( m_pat ); engine::pianoRoll()->parentWidget()->show(); engine::pianoRoll()->setFocus(); } void patternView::resetName() { m_pat->setName( m_pat->m_instrumentTrack->name() ); } void patternView::changeName() { QString s = m_pat->name(); renameDialog rename_dlg( s ); rename_dlg.exec(); m_pat->setName( s ); } void patternView::constructContextMenu( QMenu * _cm ) { QAction * a = new QAction( embed::getIconPixmap( "piano" ), tr( "Open in piano-roll" ), _cm ); _cm->insertAction( _cm->actions()[0], a ); connect( a, SIGNAL( triggered( bool ) ), this, SLOT( openInPianoRoll() ) ); _cm->insertSeparator( _cm->actions()[1] ); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "edit_erase" ), tr( "Clear all notes" ), m_pat, SLOT( clear() ) ); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ), this, SLOT( resetName() ) ); _cm->addAction( embed::getIconPixmap( "edit_rename" ), tr( "Change name" ), this, SLOT( changeName() ) ); _cm->addSeparator(); _cm->addAction( embed::getIconPixmap( "step_btn_add" ), tr( "Add steps" ), m_pat, SLOT( addSteps() ) ); _cm->addAction( embed::getIconPixmap( "step_btn_remove" ), tr( "Remove steps" ), m_pat, SLOT( removeSteps() ) ); } void patternView::mouseDoubleClickEvent( QMouseEvent * _me ) { if( _me->button() != Qt::LeftButton ) { _me->ignore(); return; } if( m_pat->type() == pattern::MelodyPattern || !( m_pat->type() == pattern::BeatPattern && ( pixelsPerTact() >= 192 || m_pat->m_steps != MidiTime::stepsPerTact() ) && _me->y() > height() - s_stepBtnOff->height() ) ) { openInPianoRoll(); } } void patternView::mousePressEvent( QMouseEvent * _me ) { if( _me->button() == Qt::LeftButton && m_pat->m_patternType == pattern::BeatPattern && ( fixedTCOs() || pixelsPerTact() >= 96 || m_pat->m_steps != MidiTime::stepsPerTact() ) && _me->y() > height() - s_stepBtnOff->height() ) // when mouse button is pressed in beat/bassline -mode { // get the step number that was clicked on and // do calculations in floats to prevent rounding errors... float tmp = ( ( float(_me->x()) - TCO_BORDER_WIDTH ) * float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2); int step = int( tmp ); // debugging to ensure we get the correct step... // qDebug( "Step (%f) %d", tmp, step ); if( step >= m_pat->m_steps ) { qDebug( "Something went wrong in pattern.cpp: step %d doesn't exist in pattern!", step ); return; } note * n = m_pat->noteAtStep( step ); // if note at step not found, ensureBeatNotes and try again if( n == NULL ) { m_pat -> ensureBeatNotes(); n = m_pat->noteAtStep( step ); if( n == NULL ) // still can't find a note? bail! { qDebug( "Something went wrong in pattern.cpp: couldn't add note at step %d!", step ); return; } } else // note at step found { if( n->length() < 0 ) { n->setLength( 0 ); // set note as enabled beat note } else { n->setLength( -DefaultTicksPerTact ); // set note as disabled beat note } } engine::getSong()->setModified(); update(); if( engine::pianoRoll()->currentPattern() == m_pat ) { engine::pianoRoll()->update(); } } else // if not in beat/bassline -mode, let parent class handle the event { trackContentObjectView::mousePressEvent( _me ); } } void patternView::wheelEvent( QWheelEvent * _we ) { if( m_pat->m_patternType == pattern::BeatPattern && ( fixedTCOs() || pixelsPerTact() >= 96 || m_pat->m_steps != MidiTime::stepsPerTact() ) && _we->y() > height() - s_stepBtnOff->height() ) { // get the step number that was wheeled on and // do calculations in floats to prevent rounding errors... float tmp = ( ( float(_we->x()) - TCO_BORDER_WIDTH ) * float( m_pat -> m_steps ) ) / float(width() - TCO_BORDER_WIDTH*2); int step = int( tmp ); if( step >= m_pat->m_steps ) { return; } int vol = 0; int len = 0; note * n = m_pat->noteAtStep( step ); if( n != NULL ) { vol = n->getVolume(); len = n->length(); } if( len == 0 && _we->delta() > 0 ) { n->setLength( -DefaultTicksPerTact ); n->setVolume( 5 ); } else if( _we->delta() > 0 ) { n->setVolume( qMin( 100, vol + 5 ) ); } else { n->setVolume( qMax( 0, vol - 5 ) ); } engine::getSong()->setModified(); update(); if( engine::pianoRoll()->currentPattern() == m_pat ) { engine::pianoRoll()->update(); } _we->accept(); } else { trackContentObjectView::wheelEvent( _we ); } } void patternView::paintEvent( QPaintEvent * ) { if( m_needsUpdate == false ) { QPainter p( this ); p.drawPixmap( 0, 0, m_paintPixmap ); return; } QPainter _p( this ); const QColor styleColor = _p.pen().brush().color(); m_pat->changeLength( m_pat->length() ); m_needsUpdate = false; if( m_paintPixmap.isNull() == true || m_paintPixmap.size() != size() ) { m_paintPixmap = QPixmap( size() ); } QPainter p( &m_paintPixmap ); QLinearGradient lingrad( 0, 0, 0, height() ); QColor c; if(( m_pat->m_patternType != pattern::BeatPattern ) && !( m_pat->getTrack()->isMuted() || m_pat->isMuted() )) c = isSelected() ? QColor( 0, 0, 224 ) : styleColor; else c = QColor( 80, 80, 80 ); if( m_pat->m_patternType != pattern::BeatPattern ) { lingrad.setColorAt( 1, c.darker( 300 ) ); lingrad.setColorAt( 0, c ); } else { lingrad.setColorAt( 0, c.darker( 300 ) ); lingrad.setColorAt( 1, c ); } p.setBrush( lingrad ); if( engine::pianoRoll()->currentPattern() == m_pat && m_pat->m_patternType != pattern::BeatPattern ) p.setPen( c.lighter( 130 ) ); else p.setPen( c.darker( 300 ) ); p.drawRect( QRect( 0, 0, width() - 1, height() - 1 ) ); p.setBrush( QBrush() ); if( m_pat->m_patternType != pattern::BeatPattern ) { if( engine::pianoRoll()->currentPattern() == m_pat ) p.setPen( c.lighter( 160 ) ); else p.setPen( c.lighter( 130 ) ); p.drawRect( QRect( 1, 1, width() - 3, height() - 3 ) ); } const float ppt = fixedTCOs() ? ( parentWidget()->width() - 2 * TCO_BORDER_WIDTH ) / (float) m_pat->length().getTact() : pixelsPerTact(); const int x_base = TCO_BORDER_WIDTH; p.setPen( c.darker( 300 ) ); for( tact_t t = 1; t < m_pat->length().getTact(); ++t ) { p.drawLine( x_base + static_cast( ppt * t ) - 1, TCO_BORDER_WIDTH, x_base + static_cast( ppt * t ) - 1, 5 ); p.drawLine( x_base + static_cast( ppt * t ) - 1, height() - ( 4 + 2 * TCO_BORDER_WIDTH ), x_base + static_cast( ppt * t ) - 1, height() - 2 * TCO_BORDER_WIDTH ); } // melody pattern paint event if( m_pat->m_patternType == pattern::MelodyPattern ) { if( m_pat->m_notes.size() > 0 ) { // first determine the central tone so that we can // display the area where most of the m_notes are // also calculate min/max tones so the tonal range can be // properly stretched accross the pattern vertically int central_key = 0; int max_key = 0; int min_key = 9999999; int total_notes = 0; for( NoteVector::Iterator it = m_pat->m_notes.begin(); it != m_pat->m_notes.end(); ++it ) { if( ( *it )->length() > 0 ) { max_key = qMax( max_key, ( *it )->key() ); min_key = qMin( min_key, ( *it )->key() ); central_key += ( *it )->key(); ++total_notes; } } if( total_notes > 0 ) { central_key = central_key / total_notes; const int keyrange = qMax( qMax( max_key - central_key, central_key - min_key ), 1 ); // debug code // qDebug( "keyrange: %d", keyrange ); // determine height of the pattern view, sans borders const int ht = height() - 1 - TCO_BORDER_WIDTH * 2; // determine maximum height value for drawing bounds checking const int max_ht = height() - 1 - TCO_BORDER_WIDTH; // set colour based on mute status if( m_pat->getTrack()->isMuted() || m_pat->isMuted() ) { p.setPen( QColor( 160, 160, 160 ) ); } else { p.setPen( QColor( 255, 255, 255 ) ); /// \todo make this a qproperty } // scan through all the notes and draw them on the pattern for( NoteVector::Iterator it = m_pat->m_notes.begin(); it != m_pat->m_notes.end(); ++it ) { // calculate relative y-position const float y_key = ( float( central_key - ( *it )->key() ) / keyrange + 1.0f ) / 2; // multiply that by pattern height const int y_pos = static_cast( TCO_BORDER_WIDTH + y_key * ht ); // debug code // if( ( *it )->length() > 0 ) qDebug( "key %d, central_key %d, y_key %f, y_pos %d", ( *it )->key(), central_key, y_key, y_pos ); // check that note isn't out of bounds, and has a length if( ( *it )->length() > 0 && y_pos >= TCO_BORDER_WIDTH && y_pos <= max_ht ) { // calculate start and end x-coords of the line to be drawn const int x1 = x_base + static_cast ( ( *it )->pos() * ( ppt / MidiTime::ticksPerTact() ) ); const int x2 = x_base + static_cast ( ( ( *it )->pos() + ( *it )->length() ) * ( ppt / MidiTime::ticksPerTact() ) ); // check bounds, draw line if( x1 < width() - TCO_BORDER_WIDTH ) p.drawLine( x1, y_pos, qMin( x2, width() - TCO_BORDER_WIDTH ), y_pos ); } } } } } // beat pattern paint event else if( m_pat->m_patternType == pattern::BeatPattern && ( fixedTCOs() || ppt >= 96 || m_pat->m_steps != MidiTime::stepsPerTact() ) ) { QPixmap stepon; QPixmap stepoverlay; QPixmap stepoff; QPixmap stepoffl; const int steps = qMax( 1, m_pat->m_steps ); const int w = width() - 2 * TCO_BORDER_WIDTH; // scale step graphics to fit the beat pattern length stepon = s_stepBtnOn->scaled( w / steps, s_stepBtnOn->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); stepoverlay = s_stepBtnOverlay->scaled( w / steps, s_stepBtnOn->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); stepoff = s_stepBtnOff->scaled( w / steps, s_stepBtnOff->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); stepoffl = s_stepBtnOffLight->scaled( w / steps, s_stepBtnOffLight->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); for( int it = 0; it < steps; it++ ) // go through all the steps in the beat pattern { note * n = m_pat->noteAtStep( it ); // figure out x and y coordinates for step graphic const int x = TCO_BORDER_WIDTH + static_cast( it * w / steps ); const int y = height() - s_stepBtnOff->height() - 1; // get volume and length of note, if noteAtStep returned null // (meaning, note at step doesn't exist for some reason) // then set both at zero, ie. treat as an off step const int vol = ( n != NULL ? n->getVolume() : 0 ); const int len = ( n != NULL ? int( n->length() ) : 0 ); if( len < 0 ) { p.drawPixmap( x, y, stepoff ); for( int i = 0; i < vol / 5 + 1; ++i ) { p.drawPixmap( x, y, stepon ); } for( int i = 0; i < ( 25 + ( vol - 75 ) ) / 5; ++i ) { p.drawPixmap( x, y, stepoverlay ); } } else if( ( it / 4 ) % 2 ) { p.drawPixmap( x, y, stepoffl ); } else { p.drawPixmap( x, y, stepoff ); } } // end for loop } p.setFont( pointSize<8>( p.font() ) ); QColor text_color = ( m_pat->isMuted() || m_pat->getTrack()->isMuted() ) ? QColor( 30, 30, 30 ) : QColor( 255, 255, 255 ); if( m_pat->name() != m_pat->instrumentTrack()->name() ) { p.setPen( QColor( 0, 0, 0 ) ); p.drawText( 4, p.fontMetrics().height()+1, m_pat->name() ); p.setPen( text_color ); p.drawText( 3, p.fontMetrics().height(), m_pat->name() ); } if( m_pat->isMuted() ) { p.drawPixmap( 3, p.fontMetrics().height() + 1, embed::getIconPixmap( "muted", 16, 16 ) ); } p.end(); _p.drawPixmap( 0, 0, m_paintPixmap ); } #include "moc_pattern.cxx" lmms-1.0.0/README0000644000175000017500000000703112313663627012066 0ustar tobytobyLMMS 1.0.0 =========== Copyright (c) 2004-2014 by LMMS developers This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. What is LMMS? -------------- LMMS is a free cross-platform alternative to commercial programs like FL Studio (R), which allow you to produce music with your computer. This includes the creation of melodies and beats, the synthesis and mixing of sounds, and arranging of samples. You can have fun with your MIDI-keyboard and much more; all in a user-friendly and modern interface. Features --------- * Song-Editor for composing songs * A Beat+Bassline-Editor for creating beats and basslines * An easy-to-use Piano-Roll for editing patterns and melodies * An FX mixer with 64 FX channels and arbitrary number of effects allow unlimited mixing possibilities * Many powerful instrument and effect-plugins out of the box * Full user-defined track-based automation and computer-controlled automation sources * Compatible with many standards such as SoundFont2, VST(i), LADSPA, GUS Patches, and full MIDI support * Import of MIDI and FLP (Fruityloops(R) Project) files Requirements ------------ The most important requirement is for sure a fast computer, so don't try to get LMMS working on a pentium I with 60 MHz... ;-) Therefore you should have at least 500 MHz, but for really enjoying LMMS less than 1 GHz makes no sense... Required libraries: - Qt >= 4.3.0 with devel-files (4.4.x recommended) Optional, but strongly recommended: - JACK with devel-files - libvorbis & libogg with devel-files - libalsa with devel-files - SDL with devel-files - libsamplerate >= 0.1.7 with devel-files - libsndfile with devel-files - WINE + WINE-devel-files - stk, libstk + libstk-dev - libfluidsynth with devel files - fftw3 with devel-files - libfltk1.3 with devel-files (needed by ZynAddSubFx) For compiling you should have an up to date GCC with g++. If you have problems with compiling or running LMMS, find any bug or have suggestions and so on, please feel free to e-mail me (for mail-address see below)! Building --------- See INSTALL for information on how to build LMMS. Join LMMS-development ---------------------- If you are interested in LMMS, it's programming, artwork, testing, writing demo-songs, (and improving this README...) or something like that, you're welcome to participate on the development of LMMS! The project-homepage of LMMS, mailingslists and a list of things you can do can be found at http://lmms.sourceforge.net Details on development can be found at http://lmms.sourceforge.net/development.php or in the Wiki: http://lmms.sourceforge.net/wiki/ Before coding a new big feature, please ALWAYS post your idea and suggestions about your feature and about the intended implementation to the LMMS-devel-mailinglist (lmms-devel@lists.sourceforge.net) and wait for replies! Maybe there're different ideas, improvements, hints or maybe your feature is not welcome/needed at the moment. lmms-1.0.0/COPYING0000644000175000017500000004310312313663627012241 0ustar tobytoby GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. lmms-1.0.0/data/0000755000175000017500000000000012313663627012116 5ustar tobytobylmms-1.0.0/data/lmms.ico0000644000175000017500000041502612313663627013572 0ustar tobytoby (f`` @@ (B600 %^   h( @/6Z 6S6O 2I 3L 7O?V(N i-Q=P4N3J 2K 3O:T/Y.W=S1H *@(; &8(&8%);.C3OCZ>'MAY3L,@(:%8'&8'(<,B2KAXEEAX3L-B&8 / 3( F% P& M+ ="3))<0G 7S5Z *@ UC[3P.C'9!0/) B% O% O* A!1-(:,E7K@]$P$]@]7K,E(:!1 -+ > Yh r oc" O, 9#5$*>1H #5$, 8" O | rb# M, 8%7#*@1F 8R7e7T8R1F *@%7#, 7$ K 6G xkY' D!1 .':-D6N>Z$WEAW1J,A&8. 3) B 4E  z n]% H . 1&9,B4L:["0"0;]4J,B&8 . 2+ A _ c z!:W  wj W' A"1,(;-D 6L=c4?=c7L,C (;!1,+ > r):kmm pE7m#  } rb# M- 7%6#*?1I 8Q6bU4^ 7P/G )<#3(+ <_#W] V^{1x&o%7 ~ uf R* ;#5')>0E 5P:_%%:Y5P0E )>#4'+ :X^oll"n*t+]M@3Vy z n]% H / 2&9-B5K8Y"C5b9S1F *@$7#, 7$ L Gna WSN\A|5y,y$_ | qa# L- 6%7",A3I 7S6M$ 6M7S3I ,A$6"- 5& HKo rml!m&m,n1v.peWJ<0?Y wj X( B"2,(;-C 6N:`&5#09Z4L,B&8 . 2+ A 6Rb YTOLH[ME~<{2x){!Ko z m\& F 0 0'8-B1M?S#2TC?]0M-A'8 0 1+ @ 8Qqml m%m+n/m/m0v.pomaTF9z))9  ~ uf! R* <#4'*>0E 6O8[  -:W5M,C (:!1,+ = s%8^ ZUPLIHG[MJIC}9z0x'w6O  wi V( @!2+(<.E 6Q#4'+ ;YT^ VRMJHGHG[MJIJH@}7y.x%l 0 ~ te! Q+ :$5&*?1H 6\#L:S/H&9!- 8 U[pll"m'm-m/m.l.l.k-k.v/pooooohZL?3Qq z m\' F"2.+?3MC] !2:Y0H (</4& HDgb XSNKHGHHHG[MJJJIJF>|5y+z#[  | p_% J 0 2)<2L 2\5O)=$55 Gh rml!m'm,n/m/l.l.k-j,j,j-u.poooonondVI;.:Q wg% N#4-.E;WATCK 4P+@(<. 3Ma YUPLIGHHHHHG[MJJJJJJID~;{2x){!Fh yk" S"23,B9gR a3Komll$m*n/m/l.l.k-k-j,i,i+i,u.poooooonolaSE8v($1  zc, >)>9TJFUVi0A"2\ [ UQMIHGHHHHHHG[MJJJJJJIJIB}8z/w'u1I  }i) E(;  .fnml$m)m.m/m.l.k.k-j,i,i+h+h*g+v.pooooooooooj]OA5cl' E':4P71  Q|_ WRNJGGHHHHHHHHG[LJJJJJJJJIJG?|6y-y$i) q# N%6# Ux qml!m'n-m/m.l.l.k-j-j,i,h+h*g*f)f*v.pooooooooooongYL>4F\  l% G%82M82BZa XSOKHGHHHHHHHHHHG[MJJJJJJJJJJJJF={4y*{#Ty n! O#5$9Tiel$m,n/m/l.l.k-j,j,i,i+h+g*g*f)e(d)u.pooooooooooonomeKn|"% l% G%82M82,b ^SKIGHHHHHHHHHHHHG[MJJJJJJJJJJJJJID}9ip4Q n! O#5$7Qll%dem0l.k-k-j,i,i+h*g*g*f)e)d(b(a(u.pooooooooooooonx2}7M0# l% G%82M82)` STUHGHHHHHHHHHHHHG[MJJJJJJJJJJJJILq)n$>z,2M n! O#5$7Plk$m.j+bf k.i,i+h+h*g*f)f(e(c(b'`'^(u.poooooooooons@w/mfJ0# l% G%82M82)` TLHUSGHHHHHHHHHHHG[MJJJJJJJJJJILx5jCK>z,2M n! O#5$7Pkj#l-k-k.f#ag&h+g*f)f)e(d(b'a&_&^&\&u.poooooooonrTr(_pofJ0# l% G%82M82)` ULHHJWNGHHHHHHHHHG[LJJJJJJJJIKAjy7LIJ>z,2M n! O#5$6Pki#j,j,i+h+h,caf(e)c(b'a'`&^&]%[$Y%u.pooooooopes*KsnoofJ0# l% G%82M82(b UMIIIGNWJGHHHHHHHG[LJJJJJJJJIm"r*LIJJJ>z,2M n! O#5$6Oji"i+i+h+g*g)f)e(``a(_'^&]%\%Z$Y$V%u.poooooooz4}9rnoooofJ0# l% G%82M82(b UNJJIIIGSTHGHHHHHG[LJJJJJILs+m!HJJJJJJ>z,2M n! O#5$6Ojh!h*h*g*f)f)e(c'b'`#^^[&Z%Y$X#V#T$t.pooonsDu-jooooooofJ0# l% G%82M82'b UNJJJJIHHHUQFHHHHG[LJJJILz8j@KIJJJJJJ>z,2M n! O#5$6Nig!g)f)f)e(d(b'a'_&]%\&[\X V#U#T"Q#t.ponqXq'\qnooooooofJ0# l% G%82M82'b VOKKKJIJIIHKWMGHHG[LJJJDjw3LIJJJJJJJJ>z,2M n! O#5$5Mif f(e)d(c'a'`&^&]%[%Y$X#V$YZR!Q!O"t.poht,GsnooooooooofJ0# l% G%82M82'b VOLLKJKJJIIIGOVIGG[LJJn$q(LIJJJJJJJJJJ>z,2M n! O#5$5Mhed'c(b'`'_&]%\%Z$Y$W#U#T"R"R XUK"t.s|7{6qoooooooooooofJ0# l% G%82M82&b WPL L KLKKJJJIHIGSTG[Ot-l GJJJJJJJJJJJJJ>z,2M n! O#5$4Mgca'`'_&^&\%[%Y$X#V#U"S"Q!P!N M NVhv/goooooooooooooofJ0# l% G%82M82&c WPM L ML KKKKJIJIHHIU]m }>KIJJJJJJJJJJJJJ>z,2M n! O#5$4Lfa_&]%\%[%Z$X$W#U#T"R!P!O M LJKdlv-epooooooooooooofJ0# l% G%82M82%c XQM N M L LLLKJKJIJHJVY `~=KIJJJJJJJJJJJJJ>z,2M n! O#5$3Ld_\$[%Y$X#W#V#T"S"Q!P!N LKJXFxx2s}9z4ooooooooooooofJ0# l% G%82M82%c XQN NM M M M L KLKJKIUUHVF MjFJJJJJJJJJJJJJ>z,2M n! O#5$3Kc]Z$X$W#U#T"S!R!P!O M LIP{5ootx2poju-DsnooooooooofJ0# l% G%82M82%c XRO!N N NN M L M L KK RWLJIUGIHRr(KIJJJJJJJJJJ>z,2M n! O#5$2Kb[W#V#T"S"Q!P!O NLJLm$epnotx2ponq[q'XqnooooooofJ0# l% G%82M82%c YSO!O!O!O N M N ML PXPKKKJUI IHHIYy4LIJJJJJJJJ>z,2M n! O#5$2J`YU"T"R"P!O M LJK`Urnoootx2pooonsGt,hooooooofJ0# l% G%82M82$e YSP!P!P!O!N!O N NXTL!LLLKJVI IIIIHLb?KIJJJJJJ>z,2M n! O#5$1J_XR!Q!O!N L KJUBsnoooootx2pooooooq{6|7qooooofJ0# l% G%82M82#d ZTQ!Q!P!O!P O!VWN M M M M L KKVJ JJJIIIGOk HJJJJJJ>z,2M n! O#5$1J]UP O M KIOx1nooooooootx2poooooooogt+HsnoofJ0# l% G%82M82#d ZTQ"Q!P P"UYQO!O N NN M L ML WJKKJJJIHIHSs*LIJJJ>z,2M n! O#5$0I\TMLJLi!bpnooooooootx2poooooooonrWr']qofJ0# k& E'94N81#e ZVR"Q"SZTP!P P!O!O!O NM N M L WK!LKKKJIJIHHJ[{6LIJ>z,2M n! O#5$0I[RKK]Psnooooooooootx2poooooooooonsCv.kfJ0#d+ @)=9RGC"e [US!ZXQ#R!Q!Q P!P!P!O N!O NM M WL!L LLKJKJIIIIHLdBK>z,2M n! O#5$/IZPT>rnooooooooooootx2pooooooooooooooy4{5M0#$ R"21-C5WEj"f [ZZS!R"R"R"R"Q!Q!Q!P!O!P!O N!N M WM!M M L KLKJJJJIHIGOm"=z,2M n! O#5$.I[r$koooooooooooooootx2pooooooooooonomcMn{ #"23(>2J C]"g ^ WT"S"S!S"S"R"R"R"Q!P Q!P!O!O!O!N!XM!N M L ML KKKKJIJIHIHWq3N n! O#5$5E5Ynoooooooooooooootx2pooooooooooonfXJ=1~/F0G >[3j _[XVT"S!S"S"S"R"Q!R"Q!P P!P!P!O!WN!NM N M L LL LKJKJIJIHKT): n! O#5$5ErXnoooooooooooooootx2pooooooooooi[N@3%`nDc =^%?` Jqe^ZXU!T"T!S"R"S"R"Q"Q!Q!Q P!O!YN"N!O NM M M M L KLKJKJJIK\ *: n! O#5$;T9q#Dsnoooooooooooootx2poooooonol_RD6)r". !)M$Z b]YWU!S"T!S"S"R"R"R"Q!Q P!XO"P!O N N NN M L M L KLKKI QXV'C n! O#5$;S=ZZr'Yqnoooooooooootx2poooonomcUH:,}8L I*J"8,Bd `\YWT"S"S"S"S"R"Q!R"Q"XP"P!O!O!O!O N M N M L ML K OXOLU(B n! O#5$;S=XnsFu,ioooooooooootx2poooonfYK>0#Niv8O  R h?_f_[XV T"T!S"R"S"R"Q"YQ"P P!P!P!O!N!O NM N M MWSK KMU(B n! O#5$;S=Xnooq{6|7qoooooooootx2poooi\OA4&d- g!:aR} d^ZXU!T"T!S"R"R"YQ#Q!Q!Q!P!O!P!O N!O M VWM L L KNV(B n! O#5$;S=Xnoooogt+Isnooooootx2pol`RE7*u(5 )4?#!0_ a\YWU!S"T"R"YR#R"R"Q!P Q!P!O O"TYPN N M L M OV(B n! O#5$;S=XnoooonrWr']qnooootx2odVI;. =R S3H$L4Mf _\YVT!S"YR#S"R"Q!R"Q!P!RYSO!O!O NM N M OV(B n! O#5$:R=XnoooooonsBv.kooootv.\L?1$Tp ;X B| Gkg^[XV ZS#S"R"S"R"R!YWP!P P!P!O N!O NM PW'C n! O#5$:R=Xnooooooooooy3~;rojar#C5'iu-#0S X c^Z\U"T"T!R#XYR!Q"Q!Q!Q!P!O!P!O N!N PW(C n! O#5$:R=Xnooooooooonpds)EVIp,x-; 15N/)<c `` YWW[US"S!R"R"R"Q!P Q!P!O!O!O!QX(C n! O#5$:R=Xnooooooooooonp>n~*tCY  e+L_;[i__ [T"T"T!S"S"S"R"Q!R"Q!P Q!P!O!RX(C n! O#5$:Q=XnoooooooooooolS;d #6,,F.U ^~_ZU!U"T#T"T!S"R"S"R"Q"R!Q!Q P!RY(C n! O#5$:Q=XnoooooooooooolUrQg  X 0/,F6['Tm`ZV!U#U#U"T#S"T!S"S"R"R"R"Q!Q SY)C n! O#5$9Q=XnoooooooooonsFp 5Yz wZ 0/,F5[Sya ^ W V#V#U#T#U#T"T"S!S"S"R"Q!R"SY)C n! O#5$9P=Xnoooooooonq[q'WV8Yz wZ 0/,F5[Sxa[ZZV#U#V#U#T#T#T"T!S"R"S"R"TZ)C n! O#5$9P=Xnooooooooju-DslS9Yz wZ 0/,F5[Sxa[X"X"[ZV$U"U#U#U"T#S"T!S"R"UZ)C n! O#5$9P=Xnooooonr}9z4ooolS9Yz wZ 0/,F5[Txa\X"W#W$Y[XV#V"U#T#U"T"S"T"U[)C n! O#5$9P=XnooonsKs*epooolS9Yz wZ 0/,F5[Txb\Y"Y$X$W#W%Z[W"U#V#U#T#U#T"U[)C n! O#5$9P=Xnonp_r(TrnoooolS9Yz wZ 0/,F5[Txb\Z#Y#X$X$X$W$W$[ZV#U"V#U#T#V[*C n! O#5$9O=Xnolw/@snoooooolS9Yz wZ 0/,F5[Txb^Z#Y%Y$Y#Y$X$W$W$X![YU$V"U#W\)D n! O#5$8O=Wr=x1nooooooooolS9Yz wZ 0/,F5[Tyc^[#Z%Z$Z$Y$X#Y$X$W#W$Y\WV#X\)D n! O#5$8O??r(bpnooooooooolS9Yz wZ 0/,F5[Tyc_[#[%[%Z%Y%Z$Y$X$Y$X$X#W%Z[X])D n! O#5$6I n?rnooooooooooolS9Yz wZ 0/,F5[Tyc_\#\$[%Z%[%Z%Y%Z$Y#Y$X$X$W$W#\`*D n! O#5$1@AWnoooooooooooolS9Yz wZ 0/,F5[Tyc_]$\%[%\$[%Z%[%Z$Z%Y$Y#Y$X$W$Y!^+6 n! O#5%5H nCqnooooooooooolS9Yz wZ 0/,F5[Tyc`]$\&]&\%[$\%[%[%Z%Z$Z$Y$X#X$[a *? n! O#5$8O?;s*epnooooooooolS9Yz wZ 0/,F5[Tye`^$^&]&\%]&\%\$[%[%[%Z%Y%Z"\[^*D n! O#5$9P=Wq}:z3pooooooooolS9Yz wZ 0/,F5[Txda^$^&]&^&]&]%\%\%\$[%[%\\Y%[ _*D n! O#5$9P=Xnoju-CsnoooooolS9Yz wZ 0/,F5[Uyda_%^&_%^&^&]&]&]%\'\][#Z%Z%[!`*D n! O#5$9P=Xnonq[r'XqnoooolS9Yz wZ 0/,F5[Uxea_%`&_&_&^%^%^&] ]]\&[$[%[%[!`*D n! O#5$:Q=XnooonsGt,hoooolS9Yz wZ 0/,F5[Uyeba%`'`'_'_'_$^]^']%\%]&\%\%\!`*D n! O#5$:R=Xnooooooq{6|7qoolS9Yz wZ 0/+E-T@Uyeba%a'`&`'_^_'_%^&]&^&]&]%\%]!a+D n! O#5$:R=Xnooooooooht+HslS9Yz x^ 04-F8TAUyfca&a(__`$_'`&_'^&_%^&^&]&]&^"a+D n! O#5$;S=XnoooooooonqXq'ZU8Yz {f+ C&9#-E6J<`$\ ?7[Vzfcb"_aa'`&a'`'_'`'_&_&^%^&^&^"a+D n! O#5$;S=XnoooooooooonsAr#6Yz s[) @"4((</F 7P:h U&T9]7L.E " +Vzfb`c)b'a(b'a&`'a'`'`'_'_&_%^&^"b+D n! O#5$;S=XnoooooooooooomTqQi  tb# M, 7$5#*@1G :T2d6] 6P/E )<#4( IUpe dd&c(b(c(b'a'b'a&a'`'`'`&_'^&_"b+E n! O#5$ r%9fe fe abe*d(e(d'd(c(c(c(b'a(b'a&`'a#c+E n! O#5$W=Xnooooiu,Esnoooooosy2poneWJV 4P/D ';!1++ =m#4djgg"h(g+h+h+bg+f*g*g*g)g*f)e)e&`be*d)d(d'd(c(b$e,E n! O#5$>WW=XnsJt*fpoooooooooosx2pooonoj^PC7Su z n]% H. 2&8-C4J9Y!?!(L;Y3J*@%6!- 5' FEi nii h&h*h,i+i+i+h+h+bh,h+h*g)g*f*g*g*f*g)f(f*bae'e)e(d$f,Ek& K&8">XW2)*OA[3M+?%7 . 4( D A[pjj!j'k,j-k-k-k-k-j-j,i-j,j+i,j,i,bi-i+i*i+h+g+h+h*h*h*g)g*g(g$fghnHhڈ64S9T"l-;HVcmonooooooosx2qooooooooooooooiSo{,|$:O wj W( A"2 +(;-C 5N=[ B l /Djhc i"l+l.k.l.k.j-k,k-k-k-j-i-j,j,j,j,ci-h+i,i+h*i+h+g+h+h+g*h%g ghlZ 7M/o/.? z"1>LZgnooooooosx2qooooooooooooopz6}8fW}23x+%5  ~ td! R+ ;#4&)>1H 5N:Q$@,6\ 8N/F (<"2), <c+bjgk$l,ecl.l.l.k.l-k.j-k,k-j-k-j-i-j,j,cj-i,h,i+i,i+h*h+h*g(g"ghjg &5 -4J ZC[(4BO]joooooosy2qoooooooooonsFu,ioolBFE9f# | p`# K- 5%6!+@2J9V5@#.W=S0H +>$5$, 8" N S~okigl.l/m/l-cel/k-l.l.k.k-k-k,k-j-j-j,j-cj-i,j,i,h,i,i+h)g#ghio9PW2R% Yy,8FSalonoosx2qoooooooonqZq'ZqnoooKY]PB6No z m[& F 00'9.A2N?X2bDAX2K,A&8. 3* B ?` pkk"l)j&h"m.l/m/l.m/j(bh%l/k-l.l.k.j-k,k-k-k-k-cj.j,j+i+i,i+h&g hioKl  y,Blll l&m-n0k(h"n/m.l/m/m/l.m0g bk+l.l.k.l-k.j-k,k-k-ck.i-j,j+i'h"iil] y+.,0/? z#2?MZhosy2qooooonq|8z5pooooooooK]nomdVI;u* -  } sd! P+ :#5%*?3H 7R8R.H )<#3(, <^%anl l&m,n/o0n0k(h"n/m0n/m.l/m/m/m/m0edl.l.l.k.k-k.j-k-ck.k-i*h$hijj )9 34O[D\)5CP^ox2qooonsJs*fpoooooooooK]oonol`SE9a"  { p_% J/ 4&7"+@*?%5$- 7$ K Oxqlm$m*n/o1o1o0o/k(h"n0n0n/m/m/m.l/m/m/m/l,cfl/k-l.l.k.k.ck,i&i ijo02G vk_ |(<jmm!n'o.p1p2p1p0p1o1o1n1k)i#n0o/n0m0n0n/n0n/m.l/m/m/l.m0gbk+k*j$ejl` #9H1/@ |&qCy,s'hoooooooooooooooL\ooooooonomcVH :1b\>Pr0EQ_lonoooooooooooM[ooooooooonok`RE9\ Ksrnn&p,q1q3q2q1q2p2p2p2o1p1p1o0l(j$n1o0o1o0o/n0n0n0n0n0m/m/m/l-k(k"kjm>Qf1S  U Z|-:HUcmonoooooooooM[oooooooooooooi\OB1:I5Dpnn$o*q0r3q2r3q2p3q2q1q2q2p2p2p2p1l(j$o1o1n1o0o0o0o/n0m0n/n0m/m)k$kkpSw OjJm#W%o0>KYfnoooooooooM[oooooooooooooooiu)v(Apohfr0r4r3q2r3r3q3r3q3p2q2q1q2q2p2o1l)j$p0p1o1o1o1o1o0n0o0n/l,l%k kmc ( ~D32D }&4AO\iooooooooMZoooooooooooonsFt,fdXkp(q.r1ej!s5r4q3r2r3r3q3q3q3p2q1q2p2q2l(j%p2p1p0p1o1n1o1o1m-m(l"klm1DB*O/aIc+7ER`lonooooNZoooooooooonq[q'Yqnoom!r3s4r3s5o+dn*s5r3q3r2r3r3r3q2q3q2q1q2m(k%p2o2p1p1o0p1o/m*l$llrC`o/P ,@ _.;IVdnonooNZooooooooooju-Dsnooorn$s5s4s4s4r3t5k#eq1r4r3q2r3r3r3r3q2p2q2l(k%q2p2p2p2o0n+m%llpW} (> _;!,r!1?LZgoooNYooooooonr}9z4ooooooorn$s5s4s4s4s4s4s4s4ggs4r4r3r2r3r3q3r3q3l)k%q2p2q1o.m'm"lmg!. #'L=7K '5BP]joNYooooonsKs*epoooooooro%s5s4s4s4s4s4s4s4s4q1ek#t5r4q3r2r3r3r3m)k&q2p/n)m#mmo5KL)L:kMj,8FSaMYnoonp_r'Trnooooooooro%s5s4s4s4s4s4s4s4s4r3s5n*do,s5r3q3r2r2m)l%p,n&nmsHg y7Q Sd/[k>x1noooooooooooooro%s5s4s4s4s4s4s4s4s4s4s4s4s4s4s4fhr1o*kjnj%5 ,4?$Hg'U "m1>LYgnoooro&s5s4s4r4r3r/p)o#noc%MG//A |%4BO]joorp's5r3s4r1q+o$non.A>*Q]F`+7ESalrp's5r2q,o&nnsB^k/P ,@ \/;IVhp&q/p'n!nrV{ 4PPt l)q!2?Omp$nof+ !)O:4H ~'7lop3HI*LhKhtGf t9N  R&e!#nP!#_?_???_???/????(` 4 A4R2J /E/F5K1P  68(S#[*X8L0G /E/H 5O*H2)O*X N7S4K*="3*- ;. 7$5#,B8R8^/@8,?5T0F ':!02- < 01&9/G 8Q)D &&h$F-M9S0F )<'9* lBa Lg"| t`% D"2)+?3N=W> -F 6Q-B(;* < 1Ki:X  x mV* :%6 -C6P,L 6:[3I*>#5(" J>[ fn~1}4Nm | pY* =$6"-C5P7`)I C8T0G (9#3/eKuX^z-i(< yi O. 2(;0G 6TI/F&F,K3M+@%7"# D ,epm#n*HTCx,%4  yj Q, 5&9/D 5R0O+> $6+6X 1F)>!1+S 1L^ VNYB|4}(Z vc$ G!1 +)=2H6P 4-Q 5Q.B (< 03 ~)Nvqm"m)n.n/QmaO>^"#1  ve# J 0 .)<2J 7P3[,g/:T3M+?)<"" G(?P \QLGXMF<}0s#Cg y q\( ?#4%+A6N<^ 41S2J (<$5+Y Nt imm'm.m/l.m.Qonk\J7\ } s^' C"4'+A6P-S#P+ @!u6Q/D %7 / 6 %P XSMHHFXLJIE~9z-m:X  { m U+ 8%8/E 5U/O 5O (;  L*>knm%m,m0l.l.j-k,QonoohWE37N  } nW- 8*?7S849X/G!1.] @daUNJHGHFXLJIJIA}6}*g' xf$ L"3+.E(;&8Xsm#m*n.m/l.k-j-i,j+PoonoomcSBj(/B v^!211J GS 4 1LV YPKHGGHHFXLJJIJJG?}3z&Oy&} q$ O':.Ykm!m(n.m/m.k-k-i,i+h+h*Poooooonl`N;g$ o* =-C;Cu(<T XRLHHGHHHHFXLJJJIJJIF<{0r"Mt_"2(6Jomm&m-m0l.l-j-j,i,h*g*g)g)Qoooooooooj[I3CY  z' @+@8AS No_SNIGHHHHHHHFXLJJJJJJIJJC}8z+m&\ 0 * Wihl+m/l.k-j,i,i+g*g*f)d(d'QooooooonnqjE~4t%  z' A+A8AN%d VQHGGHHHHHHHFXLJJJJJJJJJL|;u,z(@d] 0 * Tl"k+f f k.i,i+h+g*f)e)b(a'a'QoooooooosN>cOt)  z' A+A8AN"a PKSLGHHHHHHHFXLJJJJJJIJJs,{9E7>b] 0 * Tk"k-k.g&cg&g)f)e(c'a'_&]%_&Qoooooond?YslOt)  z' A+A8AM"a PIHPPIHGHHHHFXLJJJIIJH~>p'DKH7>b] 0 * Sj!j,i,h,g*dcc"b(`&^&\%Z$[%QoonnqaLNipolOt)  z' A+A8AM!b QJIHKPOIGHHHFXLJJIJL{9w2{9LJIH7>b] 0 * Rh i*h*g*f)d(b&`^\&[%X$V#X$QonosFJgoonolOt)  z' A+A8AL!b RKJIIHJRNFGHFXLJIJDp&}=HJIIJH7>b] 0 * R~hg)f)d(c'`'_&]&["[XU"S"U"Ppm_|6arooooolOt)  z' A+A8AL!b RLKKJIIHMRJHFXLJF{:s,JJIJJJJH7>b] 0 * Q~ge)c(a'_&]&[%Z$X$V$VWSR"TXLRppnooooolOt)  z' A+A8AL b SLLKKJJIIJOOIWLy6w3}b] 0 * Q}eb'`'^&\%Z$X$V#T"R"Q!OPVt+XjooooooooolOt)  z' A+A8AK c TNL LKKJJIIHJN[t+@IJIIJJJJJJH7>b] 0 * O| b_&]%Z$Y$W#T#S!Q!O MKQo)u-WioonoooooolOt)  z' A+A8AKcTNNM MLLKKJJJPW`}?IJIIJJJJJJH7>b] 0 * N{ `[$Y$W#U#S"Q!O!M INn2XmSYKQopnooooolOt)  z' A+A8AJdUO N NMM L LKLPQKTFOh(=LJJJJJJJH7>b] 0 * Mz ]X#V#T"R"P!N K Hb[mogPpm_|8_rooooolOt)  z' A+A8AJcUP O N N MM L QTMJJTIIIQo(JJIJJJJH7>b] 0 * Lz [U"S"P!O LKW?ponogPonotHIgoonolOt)  z' A+A8AIeVQ P P O!O PVRL!L LJTJJIHHZz:HJJIJH7>b] 0 * Jy XQ O!M!ITv:`soooogPooonqbLNhpolOt)  z' A+A8AHeWQ!Q!P!RTTON MML K UJJJIIGLb#{9LJIH7>b] 0 * Iy WMJ Jj(`nonnooogPoooooone@WslOt)  y( ?,A8@HeXS!Q"VUQO!O!N N NM L UK KKJJIIHNf EKH7>b] 0 * Hx TL\NtonoooooogPoooooooosP>cOt) q. 70FENGfXTXTQ"Q!Q!P P!O!O!N M UL L LKKKJIIHTu4E7>b] 0 * Er^}AhqnoooooooogPooooooonnqjE5p# S&9#5N%;Gg [XS"S"R!R"Q!Q!P P!P O N!VM M M LLKJJJIGI\v'?a] 0 *Z~MoonooooooooogPoooooooooiYH2n ! ,/F 7U Hk ^XV!T"S"R!R"R"Q!Q P O!O!VN!NMM L LKKJJIHIU3M] 0 * WvGkooooooooooogPoooooonl_M;{(Ur " 7\. Ny _\XT!T!S"S!R"R"Q!Q!P!WO!O!O NMML LLKJIKW2I] 0 * d(>KWqonooooooogPoonoomcR?-Xx*M,F_$6N} b[VT!S"S!R!R"Q!Q"WP!P O!N N NM M L KKPQW/T] 0 * c'TlX|8jqnoooooogPonoofWD2q +T 0 p *@c^YV T"S"S!R"Q"XQ!P P!O!O N NML OTMLT0S] 0 * c'Sopp=QioonooogPonj[I7w$G`]&P Hn _]XU!T!S"R"XQ"Q!Q!P P P!O!OUTL M LU0T] 0 * c'Soooq\LPmpnongQl`N<)Kg5M*Ir d\XT!S#XR"R"R"Q!Q!P TTPM!NMMU0T] 0 * c'Sooooolb~;bqnogGS@/c6 S/ V-\ `ZVYS"S!R!R"UWQP O!O N N NV0T] 0 * c'SooooonoqDFihS}04p 4E y$F@b]]\U!T!T XUQ"Q!Q!Q!P!P O!OW0T] 0 * c'SoooooonoreD63oAW i);8 Bg e \ZXT!S"S!R"R"Q!Q P P O X0T] 0 * b'Soooooooono`;[y C(B H:` ]V!T#T"S"S"R!R"Q!Q!P Q X0T] 0 * b'Sooooooonoo[{&BV ["2)1P Wu_W U#U"U#T"S"S!R"R"R"Q Y1T] 0 * b'SoooooooqhKB8Id_"2)1P ##V `ZWU#U#U#T"T"S!S!R!R!Z1U] 0 * b'SoooonoqOCh`>Ic_"2)1P  V `Y YZW"U#U"T#T"S"S!S!Z1U ] 0 * b'Soonomg>Uqna=Ic_"2)1P  V `Y"W$X"ZXV"V#U#U#T"T![2U ] 0 * b'SoorcJLlqona=Ic_"2)1P  W `Z"Y#X$W%YYXV#U#U#T"[2U ] 0 * b'Sop@Ijoonona=Ic_"2)1P  W a["Y$Y$X#X$X#YZWU#U"\3U ] 0 * b'Q`~;eqnnooona=Ic_"2)1P  W a\"Z%Z%Z$Y$Y#X$W$ZXV!]3U ] 0 * [9Qoonooooona=Ic_"2)1P  X b\"[%[%Z%Z$Y$Y$X#W%XY_3T] 0 *\Qponoooooona=Ic_"2)1P  X c^#\%[$[$Z%Z%Y$Y$X#X$X#_4I] 0 * Vt:ioonooooona=Ic_"2)1P  X c^#]&]&\%[%[$[%Z%Z$Y#Y `4M] 0 * c)HKMkqnnooona=Ic_"2)1P  X d^#^&]&]&\&\%[$[%[!\[`3V ] 0 * b'Tne=Psoooona=Ic_"2)1P  X d`$^&^%]&]%\&\'\\["Z$a4U ] 0 * b'SoosVAdnonna=Ic_"2)1P  Y e`$_'_&_&^%^!^]!\&\%[$a4V ] 0 * b'SoonqhNMbqna=Ic_"2*0P  Y ea$`'`(_ ^^^&]&]&\&\%b4V ] 0 * c'SooonoogHIta=Icb"3-3O &J*Y!Y fa%a&___%_'_&^%]&]%\%b4V ] 0 * c'Sooooooor^|7S3L6P,m :]6Q.B&8!Y{fbc)b(b(a'a&`&`'_'_&_&d4V ] 0 * c'Soooooooonnb#5#,B7O3] *Y3O6R/F &9!1 0 pX| gd&c'c(c'b'a'a'`&`'`'_&d5V ] 0 * d'SoooooonnolSu&v*&3 zk S, 7%6.C 5S/X6 &6R1G )< / -T 7Lged%e)d(d(c'c'b(b'a&a&a&e5V ] 0 * d'SooooonorWKReP.b'(8  vf" L / 0';0H 6R4h0@5U5O*>';"! I-E[gg#d"ccd(d(d(c'c(b(b'b'a&e5V ] 0 * d'Soooonpm>XjoofEQ<_# } u`& E!2**?4L:[ *Y$;"9X6O.E%7 !14 .[gg"bg*g*e(e%bd$e*d(d'c(c(b(a'g5V ] 0 * d'SooookX|9ipnoohQk]K9@[  ~ pZ( >#4#+A5M1W 1 9"G+F8M-E (;+ 4c Gn mhh&h*cg*f)g*f*f*bbe'e)d(d(c'c'g 5W ] 0 * e'SoprRJWsonooogPpngXGq/6L x k R, 7&7.E 7S3W  0;V8U1I )<%7([ 8V dkh%h)g+h,ch+g)f*g*g)f*e&ccd&d)d(c'g 6W ] 0 * e(Sqb}8_lonnoooogPoooneTAo+"/ | xf" L/ 0(;2I 8V (@.7Z 6N*@$6%, ;0Hbii#h*i+i+h*h,ch,g*g)f*f*g*g)f)e'bce)d(h 7W Y#4 ( e(PPCoonooooooogPooonom`O>Tx  t`% E!2))=3J5S5T $=\$H7N-B&9' = wWlh"i(j,i,i+i,h+i,ch,g+g+h*g*g)f*g*g*f)d!bd&i 7W ) G'9 `|0LnoonooooooogPoooooooj]K6C`  { pY( =$5#+B5N2O 1$<4Y8U/F );$5, pCh kk j'j+j-i,j,j+i,i,i,di+h*h*g+h*h*g)f*f)g*f*e%cj9V /E!0E7J +>OamoooooooogQooooonoonhXF|44E } z l R, 6&8/G 7S&?$F'`/>[3L)=$4*" I=Wgkk&k,j,k,j-j-i-i,j+j,i-ci,i+h*h+g+g+g*g)g*f(f"gjb 7P^0BUfmoonooogPooooooonnomYx-2c  wf" J 0 .(;0G 9T'H$/ Ai 0Z 3M,@$5#$ D !/djfj(l.l.l.k-j,j,k-j-j-i,j-di-i,h,i+i*h+g+g*h%gidA] 4 U%2 ^6HZhoonoogPoooooooor^KOi^::Qu { s_% C"3(*>4L1S3: 7Y4R/C )< 0 3 .Nzmhl+k*h"g!k+l.k-k-k-j,k,k,j-j.dj-i,i+h,i+i*g'g!jkCa7 [&6 w&:L^loongQooooonopENioojMZJ6Ih | pX) <$6".B5R5N .=U0H ';"3-_ Luhm"l&h"m/m/m.k+fh$l/l.k.k-j-j,j-k.dj-j+j,i,h)h#hoX 7o/Nlz,?QcmogPooonola}9`qnoojQmgWF|1'7  zj P- 4&9/F 9Q-K3T3H)?"3) M/Hnn m'm.l,h"n0m/m/l/m0i$ej*k-l.l.k.j-k.ek.j-i*h%iia/C|$F7"Pp1DVfgQonosYJQqpnooojQlomcSA^%#2 | ud$ I 0 -*=1I ,A(;!' >'<Ypm&n-o0o0m,h#n0n0n/m.l.m/m.h"g!i'l/l.k-k.ek-j'i le8R  $;5!h 7HTNopk}9UkoonoooojQloonl`N:Z! } s^( D!14#45 v Zjm%o-o0o0n1o1m,i#o0m/m0m/m/m.l/m/l,h"fl.l.l/ej#jqKm " 0|7Lt'7:^Y=lpnooooooojQloooooj[J77O | p u >_ pn"o*p1p2o1p1p1o1m-i#o0o0n/m0m0n0m/m.l.m/k)di'k&gk[ )R2@Xp}+=gnoonoooooojQlooonoongWFl,/C2L dr"o*p0q2q2q1p2o2o1p1n,i$o1o0o0n/n0m/n/n/m/l/m.l)gn[| /Cw(M Vw4EWgoonoooookQlooooonoondRAg"#-bn o(p/q2q3q3p2p1p2p1p2o1n-j$p1n1n1o0n/n/m/n0n/l*k"lo9Q  j L"-l"7J]knoooookQlooooooonooe~9~4,qil!q1r4r3q3r3r3q3p2p2q1p2n-j$p1p0o0o1n1o0n/m-k%ljV~  +)S t6Jo&>OaloonokQloooooooqhKLgk@p+r2o-l%l&r2r4q2r2r2q3q2p2q2n-j%p2o1p1p1o1n.m(l rU$3`5O B\/BSdnookQloooonoqOChnooKr1s4s4r3p.hp-s5q3q2r3q3q3p2o-k%q2p2o2p1n*m"ni(9l @&d4FXhnkQloonomg>UqnnooKr1s4s4s4s4s5j"j!q1r4r3q2q2q3o.k%q2q1o-m%mhLm  A(6 \)9 d":L^gQlnorbKLlqoooooKr1s4s4s4r3r3s5q0k$l$q1s4r3r3o.k&p/n(oqLp &Hi.@ |*>MGkop@IjoonoooooLr2s4s4s4s4s4r3s4r3q1j!l's4r4o,kn#qc(I @ Wx|.~0QZ~;eqnnoooooooKr2s4s4s4s4s4s4s4s4r3s4n(io+m!kf#1[ %[ Q@??????(@ 7Y 1K,@-A5O$H8(T4d5M+A,A2N6O*P'%H7Q*= /5\Y. 1,A6S M@#O:U*?!03Y[+ 6*?9SI 15S+B&8*  s P"3'0F 3N6V.B(;%   tU!1+.D 7W5M/F )<! oFi st0%| l% E&83L3U )I9^ 2H )<b7Va p" 1 o# J$6!3K 7YG0R4M(< O2Jom%p+dF\!  xc* ;*=5O9c $6+F5O)<# E&:Z QQE~0\ yg( >(;5O"?5Q)=, 7-fn"m,m/o-slY>D_ vY //,B5Q5+ @5P*>"20!R UKHOMI=},Fl w]- 3+@4S4O6PVpm*m/l.j-n+rnoiR5,>  q$ K*?8W5P:V } DmZMHGHPMIJG~8x&1I  s" P)=1Ab rm'm/m.k-i,i+m*rooooeKn* s 1.AU %3P]PIGHHHPMJJJJD}3k) x- 4km%m-l/l-j,i,g*g)k)rooooona68L - 29O `RJGHHHHHPMJJJJJJAz'Cb ) 8%p!h#h$k.i+h+g*e(b(i(rooooofKPOp- 29O ]ONGHHHHHPMJJJJJGw2}KIJJJJEY) 8#f]%Z%W#T#R!O JZ |:PapnooooYOp- 29O aONMLKKIPS[!@KIJJJJEY) 8#cX#U#R"O K PGsPt]KlooooYOp- 29O aQO N ML PRJPHHg'GJJJJEY) 8#_S"P!M Jt8mooPrnsRNrnoYOp- 29O cRP O!PTNL LPJIHMq/LIJEY) 8"\N Ie)cpnooProonpLXrYOp- 2:P cS Q!URO!N NM RKKJIGTz8LEY) 8"XWSrnooooProoooofKOOp $5'C] dWVQ#Q!P P!O!N RL LKKJIG^ =Y) 8 |/rnooooooProoooon_6Ke f0H #4 j WT"S"R!R"Q!P O!SN!MM LKJJHJKw) 8$聹3goooooooProooocI.=S "L[R ^VT"S!R"Q!Q!TO!O NM L LKIOCn ) 8&NYKpnooooPrnohQ6Ut F!2^[U S"S!R"UP!P!O!N NL ORLAx ) 8&KorNRsnooPrlX=l s4OcXU!S"VQ"Q!P P!PTOM MBx ) 8&KooolJ^poPcD|(,; / 0F Gp bWWS"R!R"USO!O N NBx ) 8&Kooonp`J`92B[%$Fv"X _V VWR"Q!Q!P!O!OBx ) 8&Kooooonr7Yv a *:a WT"S"S"R"Q!P Q Cx ) 8%Kooooooh/$0c%8"=^ YU#U#T"S!R"R"R Cy ) 8%KooonpLWI'4 f%8"=^ ZYW!U#T#T"S!S!Dz) 8%KonrSMrnG'4 f%8"=^ [X$YYV#U"T"T!Dy) 8%Kq^KkoooG'4 f%8"=_ [Y$X#W%YXU#V"Ez) 8%HK`pnoooG'4 f%8"=_ ]Z%Z$Y$X$W#YXF{) 8a a"_'_'^!^]%\%[#H|) 8&KoonrNSsF'4 g&: -R*Ya a"a%__"_&^%]%\$H|) 8&KooooolJ;'5 r+ ?*?8VJ ' FS}bba a(a&`'_&^&^$I|) 8'KooooonpA ) uU!1+-D6V15Q+@'9(a d#c'c'b'a'`&`'_%I}) 8'Koooonq[;p'%3  o" J$5"1J :c 5N/G );! oB^gce*d(c'b(b'a&`%J|) 8'KoooogJcpJPg) yg' @&94P.Q*Y$<4U4M(<O0Hidg*f(cd#d)d'c(b(b&K|) 8(KonpLWrnoQq`FPq w_, 5+?7V J 99V7O)=, 7-ah!h)e g+g*f*e%cd&d(c'c&K~) 8(KsSNrnoooProlZ>8P tU!1+.D7U87T,A'9(Skh(i+h*e h+g)f*g*g*d!ce)d&L}, 6) ?JkooooooPronoiSz4#1  o" J$5"2I ;Y:[1G )< i@c ni'j,i+i,h+e h,g+h*g)f*g*f*ccL%7$|(KdooooooProooooeMg* yg( ?':5P(D$F'a2T5N)< J-Dkj%j,k-j-i,j+i,e!i,h*h+g+g*g*f(f l:Pv3L$D^6RionooProooonpaA8Ll w]- 4+?8S60 J7T*> 0 3)`jgl/l-k-j,k-j-i,f!i-h,i+h+g*g$j]  vM \>YlonProooomJ]q[Q=6M tS"1*/E 4R6R-C);%  Qp!h m/m/h!h%l/k.k-j,j-f"j-j+i,h'hi(9& 0w%q&E`nPronrORrno]giRx2, ~ m$ H%7 4L7X1I (=b>_ pm)n0i$n0m/m/l,g!j)l.k.j-g"k.i*h n;U&$F0A 0LIrqYKpnooo]fooeLb'  xe) =%8'$ E*@kn'o/n1o1j$n0m0m/m.m/j(g k-l.g"j%nOt Q$E`~(NHgoooooo]eooon_EIi u#`o%p/p2o1p1o1j%o1n/m0n0m/m.m0h#gja$}M WwB[monooo^eooonolX=.? O{s#p.q3q2p1p2p2p2j%o1n1o0n/m0n0l+k l*6  y's(Gaoooo^eoooooohFKql!m&s5q3r3q3p2q1q2j%p1o0o1o1n.k$p?\-*T 2E 2Ngoo^dooonpLWrsn)s4r3k$o,r4q2r3q3q2k&p2o1o0m(pT| Y)If;Vk_donrSMrnoso*s4s4s4p.k$q1r3q2r3k&q2o,nd))Vb BNcp^Jkooooso+s4s4s4r3s5n)l%s4r4l#n$o0E  A!-r#?D`pnooooso*s4s4s4s4s4r3s5k$l pEe 6$<2A7Phonooosp+s4s4s4s4s4r3p+qYc3On`6R=_"2E 4WWQDt**B   |N!2 -D9Y ' 3M Zp(m/k,hn_s99RvI#42N70OMPGNMH=^!,C  l# @':>.\l%n.l.j-i)goolYq2,  K.Bg(=QNIGGNLJIG{7_' 9-qm,l/k-h+g*f'gonophA4G e,? 4NWJGGHGNLJIJJBu&&7H(>o%h'h&h)e)b'a%gnoo]^YGcd,? 8_OLKGGGNLIJI~>~?|=1OG'<n%h+e(b _#Z%[#fpdSko_Ead,? 9_OIJNIFNLK~?{:HJ~@0LG&;k#d(`'\'YWV c[^pnp_Ead,? 8^PKJHMKOI|:DKIJ~@0LG%:g!]&Z$V#Q"L_ Geroop_Ead,? 9_QM LKIKRp.HKIJJ~@0LG$:aW#S"M N~F\h[]pnp_Ead,?  9_SN NL OMOL]%CKIJ~@0LG#9[O P h1hq_kpdTko_Ead,? :^UQ!PSNL PJHJn0GJ~@0LG"8Ue1Tpoo_knop]_XFc_0D:` XSSP!O!N QLKJGV!q5~>1NG&8Ikoono_konopgAC\9@_Llonno^joojPKn  Dnp&p0p2p1o/l+o0n/n/m/l.i cJn  ,BF,:['Phpoo^joooq^s5@`bt-r4q3q2p2o0m+o1n0n0n,m Hp$/? F): z3Xmp_ioopV\ow3r2o+o,r4q3q2p0n,p2o0q%[( CKi?b^jmhSlprx6s4s4p/m(q0q2q1n+p+d9T/"/Qt *<fWKKGJMG~>Gp2nCnc)^&X#S{A]png-> *<eXKILMBCJJo2n@l Y%T"Kr>T]ong-> *<e[N L NOZ%BJJo2n=j Lc.dr_q^`i,> )<d\QRM OJHd,Ho2nAjWrnp_onpN1C DdgcS Q!P QM KJK^!nU{$^pnp_pi>8N-+A /NW R"SO!N L MInX*s]ar^NJgZ,.HYVR"RRNInX)qomV[)E\;\[T Q!Q!KnX)qofO;Z 9\V T#S"MnX*n]h^!/G @]Y#XV!NnTz#dpo^!/G @_[%Z$X!QnT{$^no^!/G @`]&\&[ RnX)s`^`!1L ,L@b_$^!]&SnX*qnpO" E/G 1IBXdb&a'_&VnX*qpc[n3' w( 2<[5P [!0Fpf"d$d(b'WnX*l]jq`d],  b / #]5N\*s9\ k#g&g*f)e#d&YnMn\qnp_ooZGhQ':)E7J,Ddk,i,g&h+h*g*f%XI0_ [[LmV`k]ipajr:$t) 0(=Gus+l+n0m/j(j*i)n'Ly)KZ Uqnp`opc[, 9[q(p2p1m+o1n/m0m)[&6,C+; v8hq`oof^Bp-p.r4q2n,p2o0j5N64>W݋J^m\goNs4r3o,p0o-t(Cg dbLjLqooNs4r3s5s*Qz"1U "e,cpOs4s1c*=' 0B{8N  m5_???(  )9 xF2L'Qi,B*GQ}=R/E " 7j JB?kCYg)q9mf +wAw JGLGJ}QQ r9hn#.tA}K Lz

TobyDox - "Psycho"

This is the first song written with LMMS being longer than only some tacts. Maybe you like it...

]]> lmms-1.0.0/data/projects/OldStuff/TobyDox-Confused.mmp0000644000175000017500000017637412313663627021366 0ustar tobytoby

]]>
lmms-1.0.0/data/projects/OldStuff/Siegel-DreamWave.mmp0000644000175000017500000026072312313663627021303 0ustar tobytoby