pax_global_header00006660000000000000000000000064136223100400014501gustar00rootroot0000000000000052 comment=3b851da6f721fcf15ec0b935eba72707bdb53d7b fluidsynth-2.1.1/000077500000000000000000000000001362231004000136735ustar00rootroot00000000000000fluidsynth-2.1.1/.appveyor-vcpkg.yml000066400000000000000000000025331362231004000174540ustar00rootroot00000000000000image: - Visual Studio 2017 build: parallel: true verbosity: detailed configuration: - Release environment: # update the vcpkg cache even if build fails APPVEYOR_SAVE_CACHE_ON_ERROR: true matrix: - platform: x86 - platform: x64 cache: - c:\Tools\vcpkg\installed init: - set TARGET_PLATFORM= - if "%platform%"=="x64" ( set TARGET_PLATFORM= Win64) - if "%platform%"=="ARM" ( set TARGET_PLATFORM= ARM) - echo %TARGET_PLATFORM% - echo %APPVEYOR_BUILD_WORKER_IMAGE% - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set "generator=Visual Studio 15 2017%TARGET_PLATFORM%" && set "toolset=v141_xp" ) - echo %generator% - echo %toolset% install: # make sure the latest version of git is installed - choco upgrade git -y - vcpkg install glib:%platform%-windows build_script: - mkdir build - cd build - cmake -Werror=dev -G "%generator%" -T "%toolset%" -Denable-pkgconfig=0 -DCMAKE_TOOLCHAIN_FILE=c:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DNO_GUI=1 .. - cmake --build . --config Release # build libfluidsynth and fluidsynth exec - cmake --build . --config Release --target check # build and exec unittests after_build: - 7z a fluidsynth-vcpkg-%platform%.zip %APPVEYOR_BUILD_FOLDER%\build\src\Release\* artifacts: - path: build/fluidsynth-vcpkg-%platform%.zip name: FluidSynth fluidsynth-2.1.1/.azure-pipelines-mac.yml000066400000000000000000000015731362231004000203540ustar00rootroot00000000000000# C/C++ with GCC # Build your C/C++ project with GCC using make. # Add steps that publish test results, save build artifacts, deploy, and more: # https://docs.microsoft.com/azure/devops/pipelines/apps/c-cpp/gcc jobs: - job: macOS pool: vmImage: 'macOS-10.14' steps: - script: | brew update brew install glib gobject-introspection libsndfile pkg-config jack dbus-glib pulseaudio portaudio sdl2 displayName: 'Prerequisites' - script: | mkdir build && cd build export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig" cmake -Werror=dev -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory) -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=1 -DNO_GUI=1 .. make displayName: 'Compile fluidsynth' - script: | cd build || exit -1 make check || exit -1 displayName: 'Execute Unittests' fluidsynth-2.1.1/.azure-pipelines.yml000066400000000000000000000203371362231004000176150ustar00rootroot00000000000000# C/C++ with GCC # Build your C/C++ project with GCC using make. # Add steps that publish test results, save build artifacts, deploy, and more: # https://docs.microsoft.com/azure/devops/pipelines/apps/c-cpp/gcc jobs: - job: Windows strategy: matrix: XP_x86: platform: Win32 toolset: v141_xp gtk-bundle: $(gtk-bundle-x86) libsndfile-url: $(libsndfile-url-x86) artifact-prefix: "fluidsynth" imageName: 'vs2017-win2016' XP_x64: platform: x64 toolset: v141_xp gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) artifact-prefix: "fluidsynth" imageName: 'vs2017-win2016' pool: vmImage: $(imageName) steps: - task: DownloadBuildArtifacts@0 inputs: buildType: specific # https://dev.azure.com/tommbrt/_apis/projects?api-version=5.0 project: 'd3638885-de4a-4ce7-afe7-f237ae461c07' pipeline: 1 artifactName: libinstpatch-$(platform) downloadPath: '$(Build.ArtifactStagingDirectory)' displayName: 'Get libinstpatch' - script: | @ECHO ON mkdir d:\deps || exit -1 cd d:\deps || exit -1 curl -LfsS -o gtk-bundle-dev.zip $(gtk-bundle) || exit -1 curl -LfsS -o libsndfile-dev.zip $(libsndfile-url) || exit -1 7z x -aos -- gtk-bundle-dev.zip > NUL || exit -1 7z x -aos -- libsndfile-dev.zip > NUL || exit -1 REM need to fix the naming of libsndfile otherwise the linker won't find it mv lib\libsndfile-1.lib lib\sndfile.lib || exit -1 mv lib\libsndfile-1.def lib\sndfile.def || exit -1 cd $(Build.ArtifactStagingDirectory)\libinstpatch-$(platform) cp -rf * d:\deps\ mv -f * .. cd .. rmdir $(Build.ArtifactStagingDirectory)\libinstpatch-$(platform)\ displayName: 'Prerequisites' - script: | @ECHO ON SET "PATH=d:\deps\bin;%PATH%" pkg-config --list-all mkdir build && cd build || exit -1 cmake -Werror=dev -A $(platform) -T $(toolset) -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory) -Denable-readline=0 -Denable-floats=1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=1 -DNO_GUI=1 .. || exit -1 cmake --build . --config Release || exit -1 displayName: 'Compile fluidsynth' - script: | @ECHO ON SET "PATH=d:\deps\bin;%PATH%" cd build || exit -1 cmake --build . --config Release --target check || exit -1 displayName: 'Execute Unittests' - script: | @ECHO ON cd build cmake --build . --config Release --target install || exit -1 del $(Build.ArtifactStagingDirectory)\bin\concrt*.dll del $(Build.ArtifactStagingDirectory)\bin\vcruntime*.dll del $(Build.ArtifactStagingDirectory)\bin\msvcp*.dll del $(Build.ArtifactStagingDirectory)\lib\instpatch*.lib del $(Build.ArtifactStagingDirectory)\lib\pkgconfig\libinstpatch*.pc rd $(Build.ArtifactStagingDirectory)\include\libinstpatch-2 /s /q displayName: 'Copy Artifacts' - task: PublishBuildArtifacts@1 inputs: pathtoPublish: $(Build.ArtifactStagingDirectory) artifactName: $(artifact-prefix)-$(platform) - job: WindowsCI strategy: matrix: default: CMAKE_FLAGS: CMAKE_CONFIG: Release gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) debug_prof: CMAKE_FLAGS: -Denable-profiling=1 -Denable-trap-on-fpe=1 -Denable-fpe-check=1 CMAKE_CONFIG: Debug gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) no_network: CMAKE_FLAGS: -Denable-network=0 CMAKE_CONFIG: Release gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) static_lib: CMAKE_FLAGS: -DBUILD_SHARED_LIBS=0 CMAKE_CONFIG: Release gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) minimal: CMAKE_FLAGS: -Denable-ipv6=0 -Denable-network=0 -Denable-aufile=0 -Denable-dbus=0 -Denable-threads=0 -Denable-winmidi=0 -Denable-waveout=0 -Denable-dsound=0 -Denable-libsndfile=0 -Denable-floats=1 CMAKE_CONFIG: Release gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) pool: vmImage: 'windows-2019' steps: - script: | @ECHO ON mkdir d:\deps || exit -1 cd d:\deps || exit -1 curl -LfsS -o gtk-bundle-dev.zip $(gtk-bundle) || exit -1 curl -LfsS -o libsndfile-dev.zip $(libsndfile-url) || exit -1 7z x -aos -- gtk-bundle-dev.zip > NUL || exit -1 7z x -aos -- libsndfile-dev.zip > NUL || exit -1 REM need to fix the naming of libsndfile otherwise the linker won't find it mv lib\libsndfile-1.lib lib\sndfile.lib || exit -1 mv lib\libsndfile-1.def lib\sndfile.def || exit -1 displayName: 'Prerequisites' - script: | @ECHO ON SET "PATH=d:\deps\bin;%PATH%" mkdir build && cd build || exit -1 cmake -Werror=dev -A x64 -DCMAKE_BUILD_TYPE=$(CMAKE_CONFIG) -DCMAKE_VERBOSE_MAKEFILE=1 $(CMAKE_FLAGS) -DNO_GUI=1 .. || exit -1 cmake --build . --config $(CMAKE_CONFIG) || exit -1 displayName: 'Compile fluidsynth' - script: | @ECHO ON SET "PATH=d:\deps\bin;%PATH%" cd build || exit -1 cmake --build . --config $(CMAKE_CONFIG) --target check || exit -1 displayName: 'Execute Unittests' - job: WindowsMinGW strategy: matrix: x86: CMAKE_FLAGS: -DCMAKE_C_FLAGS="-m32" platform: Win32 gtk-bundle: $(gtk-bundle-x86) libsndfile-url: $(libsndfile-url-x86) mingw-url: $(mingw-url-x86) x64: CMAKE_FLAGS: platform: x64 gtk-bundle: $(gtk-bundle-x64) libsndfile-url: $(libsndfile-url-x64) mingw-url: $(mingw-url-x64) pool: vmImage: 'vs2017-win2016' steps: - task: DownloadBuildArtifacts@0 inputs: buildType: specific # https://dev.azure.com/tommbrt/_apis/projects?api-version=5.0 project: 'd3638885-de4a-4ce7-afe7-f237ae461c07' pipeline: 1 artifactName: libinstpatch-$(platform) downloadPath: '$(Build.ArtifactStagingDirectory)' displayName: 'Get libinstpatch' - script: | @ECHO ON mkdir d:\deps || exit -1 cd d:\deps || exit -1 curl -LfsS -o gtk-bundle-dev.zip $(gtk-bundle) || exit -1 curl -LfsS -o libsndfile-dev.zip $(libsndfile-url) || exit -1 curl -LfsS -o mingw.zip $(mingw-url) || exit -1 7z x -aos -- gtk-bundle-dev.zip > NUL || exit -1 7z x -aos -- libsndfile-dev.zip > NUL || exit -1 7z x -aos -- mingw.zip > NUL || exit -1 rm *.zip REM need to fix the naming of libsndfile otherwise the linker won't find it mv lib\libsndfile-1.lib lib\sndfile.lib || exit -1 mv lib\libsndfile-1.def lib\sndfile.def || exit -1 cd mingw*\ && cp -rf * .. && cd .. && rm -rf mingw* || exit -1 cd $(Build.ArtifactStagingDirectory)\libinstpatch-$(platform) && cp -rf * d:\deps\ && mv -f * .. && cd .. && rmdir $(Build.ArtifactStagingDirectory)\libinstpatch-$(platform)\ || exit -1 displayName: 'Prerequisites' - script: | @ECHO ON SET "PATH=d:\deps\bin;%PATH%" REM remove that path from PATH to make sure sh.exe is not found (cmake will complain otherwise) set PATH=%PATH:C:\Program Files\Git\bin;=% set PATH=%PATH:C:\Program Files\Git\usr\bin;=% pkg-config --list-all mkdir build && cd build || exit -1 cmake -Werror=dev -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=$(Build.ArtifactStagingDirectory) $(CMAKE_FLAGS) -Denable-readline=0 -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=1 -DNO_GUI=1 .. || exit -1 mingw32-make.exe all || exit -1 displayName: 'Compile fluidsynth' - script: | @ECHO ON SET "PATH=d:\deps\bin;%PATH%" REM remove that path from PATH to make sure sh.exe is not found (cmake will complain otherwise) set PATH=%PATH:C:\Program Files\Git\bin;=% set PATH=%PATH:C:\Program Files\Git\usr\bin;=% cd build || exit -1 mingw32-make.exe check || exit -1 displayName: 'Execute Unittests' fluidsynth-2.1.1/.circleci/000077500000000000000000000000001362231004000155265ustar00rootroot00000000000000fluidsynth-2.1.1/.circleci/config.yml000066400000000000000000000043561362231004000175260ustar00rootroot00000000000000version: 2.1 orbs: android: circleci/android@0.2.0 jobs: build: working_directory: ~/code docker: - image: circleci/android:api-29 environment: JVM_OPTS: -Xmx3200m steps: - run: name: Setup Git email and user for Cerbero command: git config --global user.email "ci@beatscratch.io" && git config --global user.name "CI testing" - android/install-ndk: ndk-version: android-ndk-r18b ndk-sha: 500679655da3a86aecf67007e8ab230ea9b4dd7b - run: name: Link NDK for Cerbero command: | mkdir -p /home/circleci/android-sdk-linux ln -s /opt/android/android-ndk-r18b /home/circleci/android-sdk-linux/ndk-bundle - run: name: Install FluidSynth build dependencies command: sudo apt-get update && sudo apt-get install autotools-dev automake autoconf libtool g++ autopoint make cmake bison flex yasm pkg-config gtk-doc-tools libxv-dev libx11-dev libpulse-dev python3-dev texinfo gettext build-essential pkg-config doxygen curl libxext-dev libxi-dev x11proto-record-dev libxrender-dev libgl1-mesa-dev libxfixes-dev libxdamage-dev libxcomposite-dev libasound2-dev libxml-simple-perl dpkg-dev debhelper build-essential devscripts fakeroot transfig gperf libdbus-glib-1-dev wget glib-networking libxtst-dev libxrandr-dev libglu1-mesa-dev libegl1-mesa-dev git subversion xutils-dev intltool ccache python3-setuptools autogen maven make - checkout - run: name: Prepare FluidSynth Android working_directory: doc/android command: | make -f Makefile.android prepare - run: name: Build FluidSynth Android working_directory: doc/android command: | make -f Makefile.android - run: name: Show directory contents working_directory: doc/android command: | ls -R - run: name: Zip FluidSnyth Android Distribution working_directory: doc/android command: zip -r android-dist.zip dist - store_artifacts: path: doc/android/android-dist.zip destination: android-dist.zipfluidsynth-2.1.1/.cirrus.yml000066400000000000000000000012101362231004000157750ustar00rootroot00000000000000 task: name: FreeBSD freebsd_instance: matrix: # There isn't a stable 13.0 image yet (2019-09) image_family: freebsd-13-0-snap image_family: freebsd-12-0 install_script: pwd && ls -la && pkg install -y cmake glib alsa-lib ladspa portaudio pulseaudio pkgconf sdl2 compile_script: pwd && ls -la && mkdir $HOME/fluidsynth_install/ && mkdir build && cd build && cmake -Werror=dev -DCMAKE_INSTALL_PREFIX=$HOME/fluidsynth_install -Denable-portaudio=1 -Denable-ladspa=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_VERBOSE_MAKEFILE=0 -DNO_GUI=1 .. && make -j4 && make check && make install fluidsynth-2.1.1/.clang-format000066400000000000000000000031651362231004000162530ustar00rootroot00000000000000AccessModifierOffset: 0 AlignEscapedNewlinesLeft: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortFunctionsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: false BinPackParameters: false BreakBeforeBinaryOperators: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakBeforeBraces: Custom BraceWrapping: AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: true AfterStruct: true AfterUnion: true BeforeCatch: true BeforeElse: true IndentBraces: false ColumnLimit: 100 CommentPragmas: '' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 0 ContinuationIndentWidth: 0 Cpp11BracedListStyle: false DerivePointerAlignment: false IndentCaseLabels: true IndentWidth: 4 Language: Cpp MaxEmptyLinesToKeep: 2 NamespaceIndentation: All PenaltyBreakBeforeFirstCallParameter: 100 PenaltyBreakComment: 100 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 100 PenaltyExcessCharacter: 1 PenaltyReturnTypeOnItsOwnLine: 20 SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: false SpacesInParentheses: false SpacesInSquareBrackets: false SortIncludes: false Standard: Cpp11 TabWidth: 4 UseTab: Never fluidsynth-2.1.1/.clang-tidy000066400000000000000000000052541362231004000157350ustar00rootroot00000000000000--- Checks: '-*,clang-diagnostic-*,clang-analyzer-*,-clang-analyzer-security.insecureAPI.strcpy,performance-*,readability-avoid-const-params-in-decls,readability-braces-around-statements,readability-delete-null-pointer,readability-implicit-bool-conversion,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-redundant-control-flow,readability-redundant-declaration,readability-redundant-function-ptr-dereference,readability-simplify-boolean-expr' WarningsAsErrors: '' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false FormatStyle: file User: tom CheckOptions: - key: cert-dcl59-cpp.HeaderFileExtensions value: h,hh,hpp,hxx - key: cert-err09-cpp.CheckThrowTemporaries value: '1' - key: cert-err61-cpp.CheckThrowTemporaries value: '1' - key: cert-oop11-cpp.IncludeStyle value: llvm - key: google-readability-braces-around-statements.ShortStatementLines value: '1' - key: google-readability-function-size.StatementThreshold value: '800' - key: google-readability-namespace-comments.ShortNamespaceLines value: '10' - key: google-readability-namespace-comments.SpacesBeforeComments value: '2' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence value: reasonable - key: modernize-loop-convert.NamingStyle value: CamelCase - key: modernize-pass-by-value.IncludeStyle value: llvm - key: modernize-replace-auto-ptr.IncludeStyle value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' - key: performance-faster-string-find.StringLikeClasses value: 'std::basic_string' - key: performance-for-range-copy.WarnOnAllAutoCopies value: '0' - key: performance-inefficient-string-concatenation.StrictMode value: '0' - key: performance-type-promotion-in-math-fn.IncludeStyle value: llvm - key: performance-unnecessary-value-param.IncludeStyle value: llvm - key: readability-braces-around-statements.ShortStatementLines value: '0' - key: readability-simplify-boolean-expr.ChainedConditionalAssignment value: '0' - key: readability-simplify-boolean-expr.ChainedConditionalReturn value: '0' ... fluidsynth-2.1.1/.github/000077500000000000000000000000001362231004000152335ustar00rootroot00000000000000fluidsynth-2.1.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001362231004000174165ustar00rootroot00000000000000fluidsynth-2.1.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000031731362231004000221140ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- ### FluidSynth version _Execute `fluidsynth --version` and provide the output._ ### Describe the bug _Provide a clear and concise description of the current situation, e.g. how the bug manifests._ ### Expected behavior _Provide a clear and concise description of what you expected to happen._ ### Steps to reproduce _Please explain the steps required to duplicate the issue, esp. if you are able to provide a sample application. E.g. how to start fluidsynth, what shell commands to enter, what midi events to send, etc._ ### Additional context _If you are able to illustrate the bug with an example, please provide simple source code below or as attached file. List any other information that is relevant to your issue, e.g. stack traces, related issues, build logs, suggestions on how to fix, links to related discussions at fluid-dev, etc._ ``` insert code snippets, soundfonts or anything relevant here, or attach it as extra file(s) if it's too much ``` fluidsynth-2.1.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000025351362231004000231500ustar00rootroot00000000000000--- name: Feature request about: Suggest a concrete feature title: '' labels: enhancement assignees: '' --- ### Related discussion on the mailing list _Features should be discussed by the community. Historically, our community lives at the mailing list. Bring up your ideas there, before opening tickets. In case you already did that, provide a link to the thread._ ### Is your feature request related to a problem? _A clear and concise description of what the problem is._ ### Describe the solution you'd like _A clear and concise description of what you want to happen._ ### Describe alternatives you've considered _A clear and concise description of any alternative solutions or features you've considered._ ### Additional context _Add any other context or screenshots about the feature request here._ fluidsynth-2.1.1/.github/issue_template.md000066400000000000000000000031021362231004000205740ustar00rootroot00000000000000_This issue tracker is only for bug reports and concrete feature requests. DO NOT SUBMIT SUPPORT REQUESTS OR "HOW TO" QUESTIONS HERE! Else it might be closed without further notice._ _If you have a question look into our wiki ( https://github.com/FluidSynth/fluidsynth/wiki ) or the developer resources ( http://www.fluidsynth.org/api/ )_ _If you still have a question, need support or want to discuss ideas, contact our mailing list: https://lists.nongnu.org/mailman/listinfo/fluid-dev_ _Below is a form that shall help getting relevant information for bugs and feature requests together. We strongly recommend to use it! Feel free to edit or remove inapplicable/unneeded parts._ ### FluidSynth version 2.0.x ### Current behavior ### Expected behavior ### Steps to reproduce ### Other information fluidsynth-2.1.1/.gitignore000066400000000000000000000005201362231004000156600ustar00rootroot00000000000000build/ CMakeCache.txt CMakeFiles Makefile cmake_install.cmake install_manifest.txt # Object files *.o *.ko *.obj *.elf # Precompiled Headers *.gch *.pch # Libraries *.lib *.a *.la *.lo # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex # ProjectFiles *.pro.user* fluidsynth-2.1.1/.travis.yml000066400000000000000000000044031362231004000160050ustar00rootroot00000000000000language: c sudo: false os: linux dist: bionic addons: apt: update: true sources: - ubuntu-toolchain-r-test - llvm-toolchain-bionic-7 - llvm-toolchain-bionic-8 - llvm-toolchain-bionic-9 packages: - cmake-data - cmake - libglib2.0-0 - libsndfile-dev - libasound2-dev - libjack-dev - portaudio19-dev - libpulse-dev - libdbus-glib-1-dev - ladspa-sdk - libsdl2-dev env: - CMAKE_FLAGS="-Denable-profiling=1" - CMAKE_FLAGS="-Denable-floats=1 -Denable-profiling=1" - CMAKE_FLAGS="-Denable-floats=0" - CMAKE_FLAGS="-Denable-trap-on-fpe=1" - CMAKE_FLAGS="-Denable-fpe-check=1" - CMAKE_FLAGS="-Denable-ipv6=0" - CMAKE_FLAGS="-Denable-network=0" - CMAKE_FLAGS="-Denable-aufile=0" - CMAKE_FLAGS="-DBUILD_SHARED_LIBS=0" - CMAKE_FLAGS="-Denable-ubsan=1" matrix: include: - arch: arm64 env: - MATRIX_EVAL="CC=gcc-7 && CXX=g++-7 && sudo apt-get install gcc-7" - env: - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8 && sudo apt-get install gcc-8" - CMAKE_FLAGS="-Denable-debug=1 -DCMAKE_C_FLAGS_DEBUG=-fuse-ld=gold" - env: - MATRIX_EVAL="CC=clang-7 && CXX=clang++-7 && sudo apt-get install clang-7" - env: - MATRIX_EVAL="CC=clang-8 && CXX=clang++-8 && sudo rm -f /usr/local/clang-7.0.0/bin/clang-tidy && sudo ln -s /usr/bin/clang-tidy-8 /usr/bin/clang-tidy && sudo apt-get install clang-8 clang-tidy-8" - CMAKE_FLAGS="-Denable-profiling=1 -DCMAKE_C_FLAGS_DEBUG=-fuse-ld=gold" - env: - MATRIX_EVAL="CC=clang-9 && CXX=clang++-9 && sudo apt-get install clang-9" - os: linux-ppc64le env: - CMAKE_FLAGS="" before_install: - eval "${MATRIX_EVAL}" - which clang-tidy || true - ls -la `which clang-tidy` || true - echo $PATH before_script: - mkdir $HOME/fluidsynth_install/ - mkdir build && cd build script: - cmake -Werror=dev -DCMAKE_INSTALL_PREFIX=$HOME/fluidsynth_install ${CMAKE_FLAGS} -Denable-portaudio=1 -Denable-ladspa=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_VERBOSE_MAKEFILE=1 -DNO_GUI=1 .. - make -j4 - make check - make install # install only on linux, as CMAKE_INSTALL_PREFIX is ignored for frameworks on macosx and I cant tell whether that's correct or a bug. fluidsynth-2.1.1/AUTHORS000066400000000000000000000126141362231004000147470ustar00rootroot00000000000000[:Team:] Current development team Tom Moebert Former development team Josh Green Pedro Lopez-Cabanillas David Henningsson [:Idea:] * Samuel Bianchini, Peter Hanappe and Johnathan Lee [:Development:] Many people contributed to FluidSynth, sent suggestions or bug fixes. The project was started by Peter Hanappe who is the main author. Josh Green is the current maintainer. Below you'll find a summary of contributions. * Peter Hanappe. Initiated the project. files: stuck his nose in all files. * Josh Green is the former maintainer and contributed a lot of code directly or indirectly through the Swami and Smurf code base. The SoundFont loader is completely based on his code. He also wrote the alsa sequencer driver. He made many changes and bug fixes, but above all, he's one of the driving forces behind the synthesizer. He also created the current FluidSynth graphic logo with Blender (the blue waves with FluidSynth letters partially submerged). * Markus Nentwig (re-)designed the resonant filter, the chorus, the LADSPA subsystem, the MIDI router, optimized for SSE, made many changes and bug fixes and got the synthesizer to actually work. Most importantly, he used it on stage to make music. * S. Christian Collins did much testing of FluidSynth in regards to EMU10K1 compatibility and provided many synthesis fixes in that regard. * Stephane Letz from Grame wrote most of the MidiShare driver, all of the PortAudio driver, ported iiwusynth to MacOS X, and sent in many fixes. files: iiwu_midishare.c, iiwu_portaudio.c * Antoine Schmitt added the sequencer support, support for sample loading (RAM Sfont), developed the MacroMedia Director Xtra, and send in many many bug reports. Thanks to Antoine, the synthesizer finds its way to multi-media developers. files: in bindings/director/ and iiwu_seq.{c,h}, iiwu_event.{c,h}, iiwu_event_priv.h, iiwu_seqbind.{c,h}, iiwu_ramsfont.{c,h} * Bob Ham added the code for "bank select" MIDI messages and send code to define the synth's ALSA sequencer client name. files: iiwu_midi.c, iiwu_alsa.c, iiwusynth.c, iiwusynth.h. * Tim Goetze sent many patches and implemented the all_notes_off. He also sent his code for the new ALSA driver. files: iiwu_synth.c, iiwu_chan.c, iiwu_voice.c, iiwu_alsa.c * Norbert Schnell from Ircam's jMax Team wrote most of the jMax/FTS interface in a record time. He also pointed me to the technique of using a lookup table for the interpolation coefficients. file: iiwu_fts.c, iiwu_synth.c * The initial alsa driver was based on the jMax alsa driver by Francois Dechelle and his Real-time Team at Ircam (http://www.ircam.fr/jmax). The jMax code was based upon Ardour's alsa_device.cc by Paul Barton-Davis. file: iiwu_alsa.c * Code was borrowed from the glib library to the smurf files. The goal was to make iiwusynth independent from any library for maximum portability. * David Henningsson added code for fast rendering of MIDI files, rewrote the thread safety for 1.1.2, and fixed many bugs. * The midi device uses code from jMax's alsarawmidi.c file and from Smurf's midi_alsaraw.c by Josh Green. file: iiwu_alsa.c * The reverb algorithm was written by Jezar (http://www.dreampoint.co.uk). His code is public domain. The code was translated to C by Peter Hanappe. file: iiwu_synth.c * The original code for the chorus effect was written by Juergen Mueller and sundry contributors. * Bob Ham added LADCCA support. * Ebrahim Mayat made big efforts for compiling and running FluidSynth on MacOS X. He also wrote the README-OSX file. * Martin Uddén's midi package was used. His files are integrated into the iiwu_midi file. Martin Uddén file: iiwu_midi.c * Ken Ellinwood send in a patch to add bank offsets to SoundFonts. An adapted version was integrated in the source code. files: fluid_cmd.c, fluidsynth/synth.h, fluid_synth.c. * Some interpolation algorithms were used that were found in the music-dsp archives (http://www.smartelectronix.com/musicdsp). They were written by Joshua Scholar and others. file: iiwu_synth.c * Macros to {increment,decrement} the 64-bit fixed point phase were borrowed from Mozilla's macros to handle the Long-long type (64-bit signed integer type). Mozilla NSPR library, www.mozilla.org. file: iiwu_phase.h * KO Myung-Hun for OS/2 support with Dart audio driver. * Pedro Lopez-Cabanillas wrote the CoreMIDI driver for MacOSX, the CMake based build system, revised the doxygen documentation, sequencer examples, fixes. * Matt Giuca improved the midi player by letting it load midi files from RAM, and by making it handle EOT events. * Tom Moebert (fluidsynth's maintainer since Jun 2017) cleaned up and refactored fluidsynth's API and revised its documentation, added support for 24 bit sample soundfonts, added support for DLS soundfonts, fixed various bugs, implemented unit tests and CI builds for Windows, Linux, MacOSX and BSD. * Growing list of individuals who contributed bug fixes, corrections and minor features: Nicolas Boulicault for ALSA sequencer midi.portname setting. Werner Schweer Dave Philips Anthony Green Jake Commander Fernando Pablo Lopez-Lezcano Raoul Bonisch Sergey Pavlishin Eric Van Buggenhaut Ken Ellinwood Takashi Iwai Bob Ham Gerald Pye Rui Nuno Capela Frieder Bürzele Henri Manson Mihail Zenkov Paul Millar Nick Daly David Hilvert Bernat Arlandis i Mañó Sven Meier Marcus Weseloh Jean-jacques Ceresa fluidsynth-2.1.1/CMakeLists.txt000066400000000000000000000703771362231004000164510ustar00rootroot00000000000000# FluidSynth - A Software Synthesizer # # Copyright (C) 2003-2011 Peter Hanappe and others. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2.1 of # the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307, USA # CMake based build system. Pedro Lopez-Cabanillas cmake_minimum_required ( VERSION 3.1.0 ) # because of CMAKE_C_STANDARD project ( FluidSynth C ) set ( CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake_admin ) # FluidSynth package name set ( PACKAGE "fluidsynth" ) # FluidSynth package version set ( FLUIDSYNTH_VERSION_MAJOR 2 ) set ( FLUIDSYNTH_VERSION_MINOR 1 ) set ( FLUIDSYNTH_VERSION_MICRO 1 ) set ( VERSION "${FLUIDSYNTH_VERSION_MAJOR}.${FLUIDSYNTH_VERSION_MINOR}.${FLUIDSYNTH_VERSION_MICRO}" ) set ( FLUIDSYNTH_VERSION "\"${VERSION}\"" ) # libfluidsynth - Library version # *** NOTICE *** # Update library version upon each release (follow these steps in order) # if any source code changes: REVISION++ # if any interfaces added/removed/changed: REVISION=0 # if any interfaces removed/changed (compatibility broken): CURRENT++ # if any interfaces have been added: AGE++ # if any interfaces have been removed/changed (compatibility broken): AGE=0 # This is not exactly the same algorithm as the libtool one, but the results are the same. set ( LIB_VERSION_CURRENT 2 ) set ( LIB_VERSION_AGE 3 ) set ( LIB_VERSION_REVISION 1 ) set ( LIB_VERSION_INFO "${LIB_VERSION_CURRENT}.${LIB_VERSION_AGE}.${LIB_VERSION_REVISION}" ) # Options disabled by default option ( enable-debug "enable debugging (default=no)" off ) option ( enable-floats "enable type float instead of double for DSP samples" off ) option ( enable-fpe-check "enable Floating Point Exception checks and debug messages" off ) option ( enable-portaudio "compile PortAudio support" off ) option ( enable-profiling "profile the dsp code" off ) option ( enable-trap-on-fpe "enable SIGFPE trap on Floating Point Exceptions" off ) option ( enable-ubsan "compile and link against UBSan (for debugging fluidsynth internals)" off ) # Options enabled by default option ( enable-aufile "compile support for sound file output" on ) option ( BUILD_SHARED_LIBS "Build a shared object or DLL" on ) option ( enable-dbus "compile DBUS support (if it is available)" on ) option ( enable-ipv6 "enable ipv6 support" on ) option ( enable-jack "compile JACK support (if it is available)" on ) option ( enable-ladspa "enable LADSPA effect units" on ) option ( enable-libinstpatch "use libinstpatch (if available) to load DLS and GIG files" on ) option ( enable-libsndfile "compile libsndfile support (if it is available)" on ) option ( enable-midishare "compile MidiShare support (if it is available)" on ) option ( enable-opensles "compile OpenSLES support (if it is available)" off ) option ( enable-oboe "compile Oboe support (requires OpenSLES and/or AAudio)" off ) option ( enable-network "enable network support (requires BSD sockets)" on ) option ( enable-oss "compile OSS support (if it is available)" on ) option ( enable-dsound "compile DirectSound support (if it is available)" on ) option ( enable-waveout "compile Windows WaveOut support (if it is available)" on ) option ( enable-winmidi "compile Windows MIDI support (if it is available)" on ) option ( enable-sdl2 "compile SDL2 audio support (if it is available)" on ) option ( enable-pkgconfig "use pkg-config to locate fluidsynth's (mostly optional) dependencies" on ) option ( enable-pulseaudio "compile PulseAudio support (if it is available)" on ) option ( enable-readline "compile readline lib line editing (if it is available)" on ) option ( enable-threads "enable multi-threading support (such as parallel voice synthesis)" on ) # Platform specific options if ( CMAKE_SYSTEM MATCHES "Linux|FreeBSD|DragonFly" ) option ( enable-lash "compile LASH support (if it is available)" on ) option ( enable-alsa "compile ALSA support (if it is available)" on ) endif ( CMAKE_SYSTEM MATCHES "Linux|FreeBSD|DragonFly" ) if ( CMAKE_SYSTEM MATCHES "Linux" ) option ( enable-systemd "compile systemd support (if it is available)" on ) endif ( CMAKE_SYSTEM MATCHES "Linux" ) if ( CMAKE_SYSTEM MATCHES "Darwin" ) option ( enable-coreaudio "compile CoreAudio support (if it is available)" on ) option ( enable-coremidi "compile CoreMIDI support (if it is available)" on ) option ( enable-framework "create a Mac OSX style FluidSynth.framework" on ) endif ( CMAKE_SYSTEM MATCHES "Darwin" ) if ( CMAKE_SYSTEM MATCHES "OS2" ) option ( enable-dart "compile DART support (if it is available)" on ) set ( enable-ipv6 off ) endif ( CMAKE_SYSTEM MATCHES "OS2" ) # Initialize the library directory name suffix. if (NOT MINGW AND NOT MSVC AND NOT CMAKE_SYSTEM_NAME MATCHES "FreeBSD|DragonFly") if ( CMAKE_SIZEOF_VOID_P EQUAL 8 ) set ( _init_lib_suffix "64" ) else ( CMAKE_SIZEOF_VOID_P EQUAL 8 ) set ( _init_lib_suffix "" ) endif ( CMAKE_SIZEOF_VOID_P EQUAL 8 ) else () set ( _init_lib_suffix "" ) endif() set ( LIB_SUFFIX ${_init_lib_suffix} CACHE STRING "library directory name suffix (32/64/nothing)" ) mark_as_advanced ( LIB_SUFFIX ) # the default C standard to use for all targets set(CMAKE_C_STANDARD 90) # Compile with position independent code if the user requested a shared lib, i.e. no PIC if static requested. # This is cmakes default behavior, but here it's explicitly required due to the use of libfluidsynth-OBJ as object library, # which would otherwise always be compiled without PIC. if ( NOT CMAKE_POSITION_INDEPENDENT_CODE ) set ( CMAKE_POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS} ) endif ( NOT CMAKE_POSITION_INDEPENDENT_CODE ) # the default global visibility level for all target # no visibility support on OS2 if ( NOT OS2 ) set ( CMAKE_C_VISIBILITY_PRESET hidden ) endif ( NOT OS2 ) # enforce visibility control for all types of cmake targets if ( POLICY CMP0063 ) cmake_policy ( SET CMP0063 NEW ) endif ( POLICY CMP0063 ) # Default install directory names include ( DefaultDirs ) # Basic C library checks include ( CheckSTDC ) include ( CheckIncludeFile ) include ( CheckFunctionExists ) check_include_file ( string.h HAVE_STRING_H ) check_include_file ( stdlib.h HAVE_STDLIB_H ) check_include_file ( stdio.h HAVE_STDIO_H ) check_include_file ( math.h HAVE_MATH_H ) check_include_file ( errno.h HAVE_ERRNO_H ) check_include_file ( stdarg.h HAVE_STDARG_H ) check_include_file ( unistd.h HAVE_UNISTD_H ) check_include_file ( sys/mman.h HAVE_SYS_MMAN_H ) check_include_file ( sys/types.h HAVE_SYS_TYPES_H ) check_include_file ( sys/time.h HAVE_SYS_TIME_H ) check_include_file ( sys/stat.h HAVE_SYS_STAT_H ) check_include_file ( fcntl.h HAVE_FCNTL_H ) check_include_file ( sys/socket.h HAVE_SYS_SOCKET_H ) check_include_file ( netinet/in.h HAVE_NETINET_IN_H ) check_include_file ( netinet/tcp.h HAVE_NETINET_TCP_H ) check_include_file ( arpa/inet.h HAVE_ARPA_INET_H ) check_include_file ( limits.h HAVE_LIMITS_H ) check_include_file ( pthread.h HAVE_PTHREAD_H ) check_include_file ( signal.h HAVE_SIGNAL_H ) check_include_file ( getopt.h HAVE_GETOPT_H ) check_include_file ( stdint.h HAVE_STDINT_H ) include ( TestInline ) include ( TestVLA ) include ( TestBigEndian ) test_big_endian ( WORDS_BIGENDIAN ) unset ( LIBFLUID_CPPFLAGS CACHE ) unset ( LIBFLUID_LIBS CACHE ) unset ( FLUID_CPPFLAGS CACHE ) unset ( FLUID_LIBS CACHE ) unset ( ENABLE_UBSAN CACHE ) if ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "Intel" ) if ( NOT APPLE AND NOT OS2 ) set ( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed" ) set ( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined" ) endif ( NOT APPLE AND NOT OS2 ) # define some warning flags set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wpointer-arith -Wcast-qual -Wstrict-prototypes -Wno-unused-parameter -Wdeclaration-after-statement -Werror=implicit-function-declaration" ) # prepend to build type specific flags, to allow users to override set ( CMAKE_C_FLAGS_DEBUG "-g ${CMAKE_C_FLAGS_DEBUG}" ) if ( CMAKE_C_COMPILER_ID STREQUAL "Intel" ) # icc needs the restrict flag to recognize C99 restrict pointers set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -restrict" ) else () # not intel # gcc and clang support bad function cast and alignment warnings; add them as well. set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wbad-function-cast -Wcast-align" ) if ( enable-ubsan ) set ( CMAKE_C_FLAGS "-fsanitize=address,undefined ${CMAKE_C_FLAGS}" ) set ( CMAKE_EXE_LINKER_FLAGS "-fsanitize=address,undefined ${CMAKE_EXE_LINKER_FLAGS}" ) set ( CMAKE_SHARED_LINKER_FLAGS "-fsanitize=address,undefined ${CMAKE_SHARED_LINKER_FLAGS}" ) set ( ENABLE_UBSAN 1 ) endif ( enable-ubsan ) endif (CMAKE_C_COMPILER_ID STREQUAL "Intel" ) endif ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID STREQUAL "Intel" ) # Windows unset ( WINDOWS_LIBS CACHE ) unset ( DSOUND_SUPPORT CACHE ) unset ( WAVEOUT_SUPPORT CACHE ) unset ( WINMIDI_SUPPORT CACHE ) unset ( MINGW32 CACHE ) if ( WIN32 ) include ( CheckIncludeFiles ) # Check presence of MS include files check_include_file ( windows.h HAVE_WINDOWS_H ) check_include_file ( io.h HAVE_IO_H ) check_include_file ( dsound.h HAVE_DSOUND_H ) check_include_files ( "windows.h;mmsystem.h" HAVE_MMSYSTEM_H ) if ( enable-network ) set ( WINDOWS_LIBS "${WINDOWS_LIBS};ws2_32" ) endif ( enable-network ) if ( enable-dsound AND HAVE_DSOUND_H ) set ( WINDOWS_LIBS "${WINDOWS_LIBS};dsound" ) set ( DSOUND_SUPPORT 1 ) endif () if ( enable-winmidi AND HAVE_MMSYSTEM_H ) set ( WINDOWS_LIBS "${WINDOWS_LIBS};winmm" ) set ( WINMIDI_SUPPORT 1 ) endif () if ( enable-waveout AND HAVE_MMSYSTEM_H ) set ( WINDOWS_LIBS "${WINDOWS_LIBS};winmm" ) set ( WAVEOUT_SUPPORT 1 ) endif () set ( LIBFLUID_CPPFLAGS "-DFLUIDSYNTH_DLL_EXPORTS" ) set ( FLUID_CPPFLAGS "-DFLUIDSYNTH_NOT_A_DLL" ) if ( MSVC ) # statically link in the CRT library to avoid a bunch of runtime DLL dependencies and allow # the CI windows builds to be run under WinXP foreach ( flag_var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO ) if ( ${flag_var} MATCHES "/MD" ) string ( REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}" ) endif ( ${flag_var} MATCHES "/MD" ) endforeach ( flag_var ) else ( MSVC ) # only set debug postfix if not MSVS building set ( CMAKE_DEBUG_POSTFIX "_debug" ) endif ( MSVC ) # MinGW compiler (a Windows GCC port) if ( MINGW ) set ( MINGW32 1 ) add_compile_options ( -mms-bitfields ) endif ( MINGW ) else ( WIN32 ) # Check PThreads, but not in Windows find_package ( Threads REQUIRED ) set ( LIBFLUID_LIBS "m" ${CMAKE_THREAD_LIBS_INIT} ) endif ( WIN32 ) # IBM OS/2 unset ( DART_SUPPORT CACHE ) unset ( DART_LIBS CACHE ) unset ( DART_INCLUDE_DIRS CACHE ) if ( CMAKE_SYSTEM MATCHES "OS2" ) set ( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Zbin-files" ) set ( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Zbin-files" ) if ( enable-dart ) check_include_files ( "os2.h;os2me.h" HAVE_DART_H ) set ( DART_SUPPORT ${HAVE_DART_H} ) unset ( DART_INCLUDE_DIRS CACHE ) endif ( enable-dart ) endif ( CMAKE_SYSTEM MATCHES "OS2" ) # Solaris / SunOS if ( CMAKE_SYSTEM MATCHES "SunOS" ) set ( FLUID_LIBS "${FLUID_LIBS};nsl;socket" ) set ( LIBFLUID_LIBS "${LIBFLUID_LIBS};nsl;socket" ) endif ( CMAKE_SYSTEM MATCHES "SunOS" ) # Apple Mac OSX unset ( COREAUDIO_SUPPORT CACHE ) unset ( COREAUDIO_LIBS CACHE ) unset ( COREMIDI_SUPPORT CACHE ) unset ( COREMIDI_LIBS CACHE ) unset ( DARWIN CACHE ) unset ( MACOSX_FRAMEWORK CACHE ) if ( CMAKE_SYSTEM MATCHES "Darwin" ) set ( DARWIN 1 ) set ( CMAKE_INSTALL_NAME_DIR ${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} ) if ( enable-coreaudio ) check_include_file ( CoreAudio/AudioHardware.h COREAUDIO_FOUND ) if ( COREAUDIO_FOUND ) set ( COREAUDIO_SUPPORT ${COREAUDIO_FOUND} ) set ( COREAUDIO_LIBS "-Wl,-framework,CoreAudio,-framework,AudioUnit" ) endif ( COREAUDIO_FOUND ) endif ( enable-coreaudio ) if ( enable-coremidi ) check_include_file ( CoreMIDI/MIDIServices.h COREMIDI_FOUND ) if ( COREMIDI_FOUND ) set ( COREMIDI_SUPPORT ${COREMIDI_FOUND} ) set ( COREMIDI_LIBS "-Wl,-framework,CoreMIDI,-framework,CoreServices" ) endif ( COREMIDI_FOUND ) endif ( enable-coremidi ) if ( enable-framework ) set ( MACOSX_FRAMEWORK 1 ) endif ( enable-framework ) endif ( CMAKE_SYSTEM MATCHES "Darwin" ) unset ( HAVE_INETNTOP CACHE ) unset ( IPV6_SUPPORT CACHE ) CHECK_FUNCTION_EXISTS ( "inet_ntop" HAVE_INETNTOP ) if ( enable-ipv6 ) if ( HAVE_INETNTOP ) set ( IPV6_SUPPORT 1 ) endif ( HAVE_INETNTOP ) endif ( enable-ipv6 ) unset ( NETWORK_SUPPORT ) if ( enable-network ) set ( NETWORK_SUPPORT 1 ) endif ( enable-network ) unset ( WITH_FLOAT CACHE ) if ( enable-floats ) set ( WITH_FLOAT 1 ) endif ( enable-floats ) unset ( WITH_PROFILING CACHE ) if ( enable-profiling ) set ( WITH_PROFILING 1 ) if ( CMAKE_C_COMPILER_ID STREQUAL "Clang" ) set ( OPT_FLAGS "-Rpass=loop-vectorize" ) # -Rpass-analysis=loop-vectorize" ) elseif ( CMAKE_C_COMPILER_ID STREQUAL "Intel" ) set ( OPT_FLAGS "-qopt-report=3" ) elseif ( CMAKE_C_COMPILER_ID STREQUAL "GNU" ) set ( OPT_FLAGS "" ) endif ( ) set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OPT_FLAGS}" ) if ( CMAKE_VERSION VERSION_GREATER "3.6.0" ) find_program( CLANG_TIDY NAMES "clang-tidy" DOC "Path to clang-tidy executable" ) if ( CLANG_TIDY ) message ( STATUS "Found clang-tidy at ${CLANG_TIDY}" ) execute_process ( COMMAND ${CLANG_TIDY} "--version" ) set ( CMAKE_C_CLANG_TIDY ${CLANG_TIDY} ) endif ( CLANG_TIDY ) endif ( CMAKE_VERSION VERSION_GREATER "3.6.0" ) endif ( enable-profiling ) unset ( ENABLE_TRAPONFPE CACHE ) unset ( TRAP_ON_FPE CACHE ) if ( enable-trap-on-fpe AND NOT APPLE AND NOT WIN32 ) set ( ENABLE_TRAPONFPE 1 ) set ( TRAP_ON_FPE 1 ) endif ( enable-trap-on-fpe AND NOT APPLE AND NOT WIN32 ) unset ( ENABLE_FPECHECK CACHE ) unset ( FPE_CHECK CACHE ) if ( enable-fpe-check AND NOT APPLE AND NOT WIN32 ) set ( ENABLE_FPECHECK 1 ) set ( FPE_CHECK 1 ) endif ( enable-fpe-check AND NOT APPLE AND NOT WIN32 ) if ( enable-debug ) set ( CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the build type, options: Debug Release RelWithDebInfo MinSizeRel" FORCE ) endif ( enable-debug ) if ( NOT CMAKE_BUILD_TYPE ) set ( CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the build type, options: Debug Release RelWithDebInfo MinSizeRel" FORCE ) endif ( NOT CMAKE_BUILD_TYPE ) unset ( ENABLE_DEBUG CACHE ) if ( CMAKE_BUILD_TYPE MATCHES "Debug" ) set ( ENABLE_DEBUG 1 ) add_definitions(-DDEBUG) endif ( CMAKE_BUILD_TYPE MATCHES "Debug" ) # Additional targets to perform clang-format/clang-tidy # Get all project files file(GLOB_RECURSE ALL_SOURCE_FILES LIST_DIRECTORIES false ${CMAKE_SOURCE_DIR}/*.[chi] ${CMAKE_SOURCE_DIR}/*.[chi]pp ${CMAKE_SOURCE_DIR}/*.[chi]xx ${CMAKE_SOURCE_DIR}/*.cc ${CMAKE_SOURCE_DIR}/*.hh ${CMAKE_SOURCE_DIR}/*.ii ${CMAKE_SOURCE_DIR}/*.[CHI] ) find_program ( ASTYLE "astyle" ) if ( ASTYLE ) add_custom_target( format COMMAND ${ASTYLE} -A1 -xb -j -k3 -p -f -n -U ${ALL_SOURCE_FILES} ) endif(ASTYLE) if(NOT enable-pkgconfig) FIND_LIBRARY( GLIB_LIB NAMES glib glib-2.0 PATH GLIB_LIBRARY_DIR ) FIND_LIBRARY( GTHREAD_LIB NAMES gthread gthread-2.0 PATH GTHREAD_LIBRARY_DIR ) FIND_PATH( GLIBH_DIR glib.h PATH GLIB_INCLUDE_DIR ) FIND_PATH( GLIBCONF_DIR glibconfig.h PATH GLIBCONF_INCLUDE_DIR ) IF( GLIB_LIB MATCHES "GLIB_LIB-NOTFOUND" OR GTHREAD_LIB MATCHES "GTHREAD_LIB-NOTFOUND" OR GLIBH_DIR MATCHES "GLIBH_DIR-NOTFOUND" OR GLIBCONF_DIR MATCHES "GLIBCONF_DIR-NOTFOUND") message( WARNING "Not sure if I found GLIB, continuing anyway.") ENDIF() SET( GLIB_INCLUDE_DIRS ${GLIBH_DIR} ${GLIBCONF_DIR} ) SET( GLIB_LIBRARIES ${GLIB_LIB} ${GTHREAD_LIB} ) message( STATUS "GLIB_INCLUDE_DIRS: " ${GLIB_INCLUDE_DIRS} ) message( STATUS "GLIB_LIBRARIES: " ${GLIB_LIBRARIES} ) else(NOT enable-pkgconfig) find_package ( PkgConfig REQUIRED ) # Mandatory libraries: glib and gthread pkg_check_modules ( GLIB REQUIRED glib-2.0>=2.6.5 gthread-2.0>=2.6.5 ) if ( GLIB_glib-2.0_VERSION AND GLIB_glib-2.0_VERSION VERSION_LESS "2.26.0" ) message ( WARNING "Your version of glib is very old. This may cause problems with fluidsynth's sample cache on Windows. Consider updating to glib 2.26 or newer!" ) endif ( GLIB_glib-2.0_VERSION AND GLIB_glib-2.0_VERSION VERSION_LESS "2.26.0" ) include ( UnsetPkgConfig ) # Optional features unset ( LIBSNDFILE_SUPPORT CACHE ) unset ( LIBSNDFILE_HASVORBIS CACHE ) if ( enable-libsndfile ) pkg_check_modules ( LIBSNDFILE sndfile>=1.0.0 ) set ( LIBSNDFILE_SUPPORT ${LIBSNDFILE_FOUND} ) if ( LIBSNDFILE_SUPPORT ) pkg_check_modules ( LIBSNDFILE_VORBIS sndfile>=1.0.18 ) set ( LIBSNDFILE_HASVORBIS ${LIBSNDFILE_VORBIS_FOUND} ) endif ( LIBSNDFILE_SUPPORT ) else ( enable-libsndfile ) unset_pkg_config ( LIBSNDFILE ) unset_pkg_config ( LIBSNDFILE_VORBIS ) endif ( enable-libsndfile ) unset ( PULSE_SUPPORT CACHE ) if ( enable-pulseaudio ) pkg_check_modules ( PULSE libpulse-simple>=0.9.8 ) set ( PULSE_SUPPORT ${PULSE_FOUND} ) else ( enable-pulseaudio ) unset_pkg_config ( PULSE ) endif ( enable-pulseaudio ) unset ( ALSA_SUPPORT CACHE ) if ( enable-alsa ) pkg_check_modules ( ALSA alsa>=0.9.1 ) set ( ALSA_SUPPORT ${ALSA_FOUND} ) else ( enable-alsa ) unset_pkg_config ( ALSA ) endif ( enable-alsa ) unset ( PORTAUDIO_SUPPORT CACHE ) if ( enable-portaudio ) pkg_check_modules ( PORTAUDIO portaudio-2.0>=19 ) set ( PORTAUDIO_SUPPORT ${PORTAUDIO_FOUND} ) else ( enable-portaudio ) unset_pkg_config ( PORTAUDIO ) endif ( enable-portaudio ) unset ( JACK_SUPPORT CACHE ) if ( enable-jack ) pkg_check_modules ( JACK jack ) set ( JACK_SUPPORT ${JACK_FOUND} ) else ( enable-jack ) unset_pkg_config ( JACK ) endif ( enable-jack ) unset ( LASH_SUPPORT CACHE ) if ( enable-lash ) pkg_check_modules ( LASH lash-1.0>=0.3 ) if ( LASH_FOUND ) set ( LASH_SUPPORT 1 ) add_definitions ( -DHAVE_LASH ) endif ( LASH_FOUND ) else ( enable-lash ) unset_pkg_config ( LASH ) remove_definitions( -DHAVE_LASH ) endif ( enable-lash ) unset ( SYSTEMD_SUPPORT CACHE ) if ( enable-systemd ) pkg_check_modules ( SYSTEMD libsystemd ) set ( SYSTEMD_SUPPORT ${SYSTEMD_FOUND} ) else ( enable-systemd ) unset_pkg_config ( SYSTEMD ) endif ( enable-systemd ) unset ( DBUS_SUPPORT CACHE ) if ( enable-dbus ) pkg_check_modules ( DBUS dbus-1>=1.0.0 ) set ( DBUS_SUPPORT ${DBUS_FOUND} ) else ( enable-dbus ) unset_pkg_config ( DBUS ) endif ( enable-dbus ) unset ( LADSPA_SUPPORT CACHE ) if ( enable-ladspa ) check_include_file ( ladspa.h LADSPA_SUPPORT ) if ( LADSPA_SUPPORT ) pkg_check_modules ( GMODULE REQUIRED gmodule-2.0>=2.6.5 ) set ( LADSPA 1 ) endif ( LADSPA_SUPPORT ) endif ( enable-ladspa ) unset ( LIBINSTPATCH_SUPPORT CACHE ) if ( enable-libinstpatch ) pkg_check_modules ( LIBINSTPATCH libinstpatch-1.0>=1.1.0 ) set ( LIBINSTPATCH_SUPPORT ${LIBINSTPATCH_FOUND} ) endif ( enable-libinstpatch ) unset ( SDL2_SUPPORT CACHE ) if ( enable-sdl2 ) pkg_check_modules ( SDL2 sdl2 ) set ( SDL2_SUPPORT ${SDL2_FOUND} ) else ( enable-sdl2 ) unset_pkg_config ( SDL2 ) endif ( enable-sdl2 ) unset ( OBOE_SUPPORT CACHE ) unset ( OBOE_LIBS CACHE ) if ( enable-oboe ) # enable C++ as it's needed for oboe enable_language ( CXX ) pkg_check_modules ( OBOE oboe-1.0 ) if ( OBOE_FOUND ) set ( OBOE_SUPPORT 1 ) set ( OBOE_LIBS ${OBOE_LIBRARIES} ) endif ( OBOE_FOUND ) endif ( enable-oboe ) unset ( WITH_READLINE CACHE ) unset ( READLINE_LIBS CACHE ) if ( enable-readline ) pkg_check_modules ( READLINE readline ) if ( NOT READLINE_FOUND ) find_package ( Readline ) set ( READLINE_FOUND ${HAVE_READLINE} ) endif ( NOT READLINE_FOUND ) if ( READLINE_FOUND ) set ( WITH_READLINE 1 ) set ( READLINE_LIBS ${READLINE_LIBRARIES} ) endif ( READLINE_FOUND ) endif ( enable-readline ) endif(NOT enable-pkgconfig) unset ( AUFILE_SUPPORT CACHE ) if ( enable-aufile ) set ( AUFILE_SUPPORT 1 ) endif ( enable-aufile ) unset ( OSS_SUPPORT CACHE ) if ( enable-oss ) find_package ( OSS QUIET ) set ( OSS_SUPPORT ${OSS_FOUND} ) endif ( enable-oss ) unset ( MIDISHARE_SUPPORT CACHE ) if ( enable-midishare ) find_package ( MidiShare QUIET ) set ( MIDISHARE_SUPPORT ${MidiShare_FOUND} ) if ( MidiShare_FOUND ) set ( MidiShare_LIBS ${MidiShare_LIBRARIES} ) else ( MidiShare_FOUND ) unset ( MidiShare_LIBS CACHE ) endif ( MidiShare_FOUND ) else ( enable-midishare ) unset ( MidiShare_LIBS CACHE ) endif ( enable-midishare ) unset ( OPENSLES_SUPPORT CACHE ) unset ( OpenSLES_LIBS CACHE ) if ( enable-opensles ) check_include_file ( SLES/OpenSLES.h OPENSLES_SUPPORT ) if ( OPENSLES_SUPPORT ) find_library ( OpenSLES_LIBS OpenSLES ) if ( NOT OpenSLES_LIBS ) unset ( OPENSLES_SUPPORT ) endif ( NOT OpenSLES_LIBS ) endif ( OPENSLES_SUPPORT ) endif ( enable-opensles ) unset ( ENABLE_MIXER_THREADS CACHE ) if ( enable-threads ) set ( ENABLE_MIXER_THREADS 1 ) endif ( enable-threads ) unset ( HAVE_OPENMP CACHE ) find_package ( OpenMP QUIET ) if ( OpenMP_FOUND OR OpenMP_C_FOUND ) message(STATUS "Found OpenMP ${OpenMP_C_SPEC_DATE}") # require at least OMP 4.0 if ( ( NOT OpenMP_C_SPEC_DATE LESS "201307" ) OR NOT ( OpenMP_C_VERSION VERSION_LESS "4.0" ) ) set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}" ) set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}" ) # currently no need to link against openMP runtime lib(s). If need be, uncomment below. # set ( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}" ) # set ( LIBFLUID_LIBS "${OpenMP_C_LIBRARIES};${LIBFLUID_LIBS}" ) set ( HAVE_OPENMP 1 ) endif() endif() # manipulate some variables to setup a proper test env set(TEST_SOUNDFONT "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf2") set(TEST_SOUNDFONT_SF3 "${CMAKE_SOURCE_DIR}/sf2/VintageDreamsWaves-v2.sf3") # Check for C99 float math unset ( HAVE_SINF CACHE ) CHECK_FUNCTION_EXISTS ( "sinf" HAVE_SINF ) if ( HAVE_SINF ) set ( HAVE_SINF 1 ) endif ( HAVE_SINF ) unset ( HAVE_COSF CACHE ) CHECK_FUNCTION_EXISTS ( "cosf" HAVE_COSF ) if ( HAVE_COSF ) set ( HAVE_COSF 1 ) endif ( HAVE_COSF ) unset ( HAVE_FABSF CACHE ) CHECK_FUNCTION_EXISTS ( "fabsf" HAVE_FABSF ) if ( HAVE_FABSF ) set ( HAVE_FABSF 1 ) endif ( HAVE_FABSF ) unset ( HAVE_POWF CACHE ) CHECK_FUNCTION_EXISTS ( "powf" HAVE_POWF ) if ( HAVE_POWF ) set ( HAVE_POWF 1 ) endif ( HAVE_POWF ) unset ( HAVE_SQRTF CACHE ) CHECK_FUNCTION_EXISTS ( "sqrtf" HAVE_SQRTF ) if ( HAVE_SQRTF ) set ( HAVE_SQRTF 1 ) endif ( HAVE_SQRTF ) unset ( HAVE_LOGF CACHE ) CHECK_FUNCTION_EXISTS ( "logf" HAVE_LOGF ) if ( HAVE_LOGF ) set ( HAVE_LOGF 1 ) endif ( HAVE_LOGF ) # General configuration file configure_file ( ${CMAKE_SOURCE_DIR}/src/config.cmake ${CMAKE_BINARY_DIR}/config.h ) # Setup linker directories NOW, as the command will apply only to targets created after it has been called. link_directories ( ${GLIB_LIBRARY_DIRS} ${LASH_LIBRARY_DIRS} ${JACK_LIBRARY_DIRS} ${ALSA_LIBRARY_DIRS} ${PULSE_LIBRARY_DIRS} ${PORTAUDIO_LIBRARY_DIRS} ${LIBSNDFILE_LIBRARY_DIRS} ${DBUS_LIBRARY_DIRS} ${SDL2_LIBRARY_DIRS} ${OBOE_LIBRARY_DIRS} ${LIBINSTPATCH_LIBRARY_DIRS} ) # Process subdirectories add_subdirectory ( src ) add_subdirectory ( test ) add_subdirectory ( doc ) # pkg-config support set ( prefix "${CMAKE_INSTALL_PREFIX}" ) set ( exec_prefix "\${prefix}" ) if ( IS_ABSOLUTE "${LIB_INSTALL_DIR}" ) set ( libdir "${LIB_INSTALL_DIR}" ) else () set ( libdir "\${exec_prefix}/${LIB_INSTALL_DIR}" ) endif () if ( IS_ABSOLUTE "${INCLUDE_INSTALL_DIR}" ) set ( includedir "${INCLUDE_INSTALL_DIR}" ) else () set ( includedir "\${prefix}/${INCLUDE_INSTALL_DIR}" ) endif () configure_file ( fluidsynth.pc.in ${CMAKE_BINARY_DIR}/fluidsynth.pc IMMEDIATE @ONLY ) install ( FILES ${CMAKE_BINARY_DIR}/fluidsynth.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) # Extra targets for Unix build environments if ( UNIX ) # RPM spec configure_file ( fluidsynth.spec.in ${CMAKE_BINARY_DIR}/fluidsynth.spec IMMEDIATE @ONLY ) if ( DEFINED FLUID_DAEMON_ENV_FILE) configure_file ( fluidsynth.service.in ${CMAKE_BINARY_DIR}/fluidsynth.service @ONLY ) configure_file ( fluidsynth.conf.in ${CMAKE_BINARY_DIR}/fluidsynth.conf @ONLY ) endif ( DEFINED FLUID_DAEMON_ENV_FILE ) # uninstall custom target configure_file ( "${CMAKE_SOURCE_DIR}/cmake_admin/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target ( uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") # tarball custom target add_custom_target ( tarball COMMAND mkdir -p ${PACKAGE}-${VERSION} COMMAND cp -r bindings ${PACKAGE}-${VERSION} COMMAND cp -r cmake_admin ${PACKAGE}-${VERSION} COMMAND cp -r doc ${PACKAGE}-${VERSION} COMMAND cp -r include ${PACKAGE}-${VERSION} COMMAND cp -r src ${PACKAGE}-${VERSION} COMMAND cp AUTHORS ChangeLog CMakeLists.txt LICENSE ${PACKAGE}.* INSTALL NEWS README* THANKS TODO ${PACKAGE}-${VERSION} # COMMAND tar -cj --exclude .svn --exclude Makefile.am -f ${PACKAGE}-${VERSION}.tar.bz2 ${PACKAGE}-${VERSION} # COMMAND tar -cz --exclude .svn --exclude Makefile.am -f ${PACKAGE}-${VERSION}.tar.gz ${PACKAGE}-${VERSION} # COMMAND zip -qr ${PACKAGE}-${VERSION}.zip ${PACKAGE}-${VERSION} -x '*.svn*' -x '*Makefile.am' COMMAND tar -cj --exclude .svn -f ${PACKAGE}-${VERSION}.tar.bz2 ${PACKAGE}-${VERSION} COMMAND tar -cz --exclude .svn -f ${PACKAGE}-${VERSION}.tar.gz ${PACKAGE}-${VERSION} COMMAND zip -qr ${PACKAGE}-${VERSION}.zip ${PACKAGE}-${VERSION} -x '*.svn*' COMMAND rm -rf ${PACKAGE}-${VERSION} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) endif ( UNIX ) include ( report ) # CPack support set ( CPACK_PACKAGE_DESCRIPTION_SUMMARY "FluidSynth real-time synthesizer" ) set ( CPACK_PACKAGE_VENDOR "fluidsynth.org" ) set ( CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md" ) set ( CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE" ) set ( CPACK_PACKAGE_VERSION_MAJOR ${FLUIDSYNTH_VERSION_MAJOR} ) set ( CPACK_PACKAGE_VERSION_MINOR ${FLUIDSYNTH_VERSION_MINOR} ) set ( CPACK_PACKAGE_VERSION_PATCH ${FLUIDSYNTH_VERSION_MICRO} ) set ( CPACK_PACKAGE_EXECUTABLES "fluidsynth" "FluidSynth CLI" ) # source packages set ( CPACK_SOURCE_GENERATOR TGZ;TBZ2;ZIP ) set ( CPACK_SOURCE_IGNORE_FILES "/.svn/;/build/;~$;.cproject;.project;/.settings/;${CPACK_SOURCE_IGNORE_FILES}" ) set ( CPACK_SOURCE_PACKAGE_FILE_NAME "${PACKAGE}-${VERSION}" ) set ( CPACK_SOURCE_STRIP_FILES OFF ) # binary packages include ( InstallRequiredSystemLibraries ) set ( CPACK_GENERATOR STGZ;TGZ;TBZ2;ZIP ) set ( CPACK_PACKAGE_NAME ${PACKAGE} ) set ( CPACK_STRIP_FILES ON ) include ( CPack ) fluidsynth-2.1.1/CONTRIBUTING.md000066400000000000000000000031521362231004000161250ustar00rootroot00000000000000# Contributing Thanks for considering to contribute to FluidSynth. Before implementing any huge new feature, consider bringing up your ideas on our mailing list: https://lists.nongnu.org/mailman/listinfo/fluid-dev Contributing can be done by * [submitting pull requests on Github]( https://help.github.com/articles/proposing-changes-to-your-work-with-pull-requests/) or * submitting patches to the mailing list. Patches should be created with `git format-patch`, so in every case you should be familiar with the basics of git. Make sure you develop against the master branch, i.e. **not** against any FluidSynth release. Some things that will increase the chance that your pull request or patch is accepted: * Give a reasoning / motivation for any changes or proposals you make. * Follow our style guide. * Keep your commits "atomic". * Write meaningful commit messages. ## Style Guide Find FluidSynth's style guide below. Syntax related issues, like missing braces, can be taken care of by calling `make format` (provided that cmake has found `astyle` on your system). #### General * Every function should have a short comment explaining it's purpose * Every public API function **must** be documented with purpose, params and return value * Prefer signed integer types to unsigned ones * Use spaces rather than tabs * Avoid macros #### Naming Conventions * Words separated by underscores * Macros always UPPER_CASE * Function and Variable names always lower_case, (e.g. `fluid_componentname_purpose()`) #### Bracing * Every block after an if, else, while or for should be enclosed in braces * **Allman-Style** braces everywhere fluidsynth-2.1.1/ChangeLog000066400000000000000000002164211362231004000154530ustar00rootroot00000000000000This file is no longer used. For detailed Changelog information, please refer to the version control system's commits. For an overview of differences between versions, see: http://sourceforge.net/apps/trac/fluidsynth/wiki/ChangeLog1_1_2 http://sourceforge.net/apps/trac/fluidsynth/wiki/ChangeLog1_1_1 etc. For developer related "What's new"-information, doc/fluidsynth-v11-devdoc contains valuable information. === OLD === 2009-05-01 Pedro Lopez-Cabanillas * configure.ac: fix for win32 build. 2009-05-01 Pedro Lopez-Cabanillas * doc/Doxyfile: added fluid_filerenderer.c to Doxygen documentation. * doc/fluidsynth-v11-devdoc.txt: license changed to CC-BY-SA 3.0 * doc/fluidsynth_arpeggio.c: new example added. * doc/fluidsynth_metronome.c: new example added. * include/fluidsynth.h, include/fluidsynth/audio.h, include/fluidsynth/settings.h: Doxygen documentation. * src/fluid_settings.c: Doxygen documentation. 2009-04-27 Josh Green * include/fluidsynth/audio.h: Moved new filerenderer documentation to source file. * src/config_win32.h.in: Added 'typedef int socklen_t;' to the correct place. * src/fluid_filerenderer.c: Removed 2 extra pasted duplicates of the file, moved Doxygen documentation from header file and added "API 1.1.0" designators. * src/config_win32.h: Removed from subversion, since it is generated from config_win32.h.in. 2009-04-26 Josh Green * configure.ac: Added glib 2.10 as a dependency, added notes in output for LASH, LADCCA and READLINE that they are GPL. * src/fluid_io.c: Moved code to fluid_sys.c and removed. * src/config_win32.h: Added "typedef int socklen_t" definition. * src/fluid_defsfont.h: Removed glib ripped code. * src/fluid_oss.c: Fixed warnings where return value of write() was being ignored. * src/fluid_sys.c: Re-organized, implemented portable fluid_curtime() and fluid_utime() using glib functions and removed old platform specific code, implemented fluid_thread functionality using glib and removed old platform specific code, fluid_istream_readline(), fluid_istream_gets() and fluid_ostream_printf() should now work on WIN32 also, added code for WIN32 for TCP sockets (not yet tested). * src/fluid_sys.h: Added fluid_gerror_message() macro to extract message safely from GError structures, replaced fluid_mutex macros with portable implementations using glib, removed new_fluid_client_socket() and delete_fluid_client_socket() which were never implemented or used. * src/fluidsynth.c: Added call to g_thread_init(). * src/fluidsynth_priv.h: Integer types now use glib integer types. 2009-04-11 Josh Green * FluidSynth release 1.0.9 "A Sound Future" * configure.ac: Bumped version, no library interfaces added, removed or changed. * doc/Makefile.am: Removed html and api folders from EXTRA_DIST. * src/fluid_synth.c (fluid_synth_program_change): Preset substitute warning now outputs MIDI channel. 2009-04-02 Pedro Lopez-Cabanillas * src/config_win32.h: fix compilation under MSVC 2008 and older 2009-03-15 Josh Green * ltconfig: Removed obsolete ltconfig script by suggestion of Sven Hoexter. * doc/fluidsynth.1: Some fixes from Sven Hoexter. * src/fluid_adriver.c: Re-order of default drivers to jack, alsa, pulse. * src/fluidsynth.c (fluid_synth_program_change): Added preset selection fallback logic: Melodic - Fallback to Bank0:prognum followed by Bank0:Program0, Percussion - Fallback to 128:0, code re-organization. 2009-03-08 Josh Green * src/fluid_jack.c: Added support for Jack MIDI. * src/fluid_mdriver.c: Registered Jack MIDI driver. * README-OSX: Update from Ebrahim Mayat. 2009-02-28 Pedro Lopez-Cabanillas * src/fluid_midi.c: Fix for ticket #22 (Wrong tempo changes) * src/fluid_midi.h: delta-time accumulator moved to fluid_midi_file struct. 2009-02-03 Josh Green * Applied patch from KO Myung-Hun for OS/2 support including Dart audio driver. 2009-01-29 Josh Green * src/Makefile.am: Added PortAudio driver conditional build. * src/fluid_adriver.c: Registered fluid_portaudio_driver_settings. * src/fluid_portaudio.c: Completely overhauled for Portaudio 19. This driver appears to have been unbuildable before. 2009-01-08 Pedro Lopez-Cabanillas * configure.ac: detection of CoreMIDI support. Ticket #18. * src/Makefile.am: conditional build of CoreMIDI driver. * src/fluid_coremidi.c: Basic CoreMIDI driver. * src/fluid_mdriver.c: added CoreMIDI driver. 2009-01-08 Josh Green * configure.ac: Followed GTK's lead for some unexplained magic for stupid libtool version parameters (fixes autogen.sh bomb on undefined macro LT_REVISION/LT_CURRENT/LT_AGE). Added AC_CONFIG_MACRO_DIR([m4]) as suggested by libtoolize. * Makefile.am: Added ACLOCAL_AMFLAGS=-I m4 as suggested by libtoolize. 2008-12-23 Josh Green * configure.ac: Added detection of PulseAudio driver. * src/Makefile.am: Added conditional build of PulseAudio driver. * src/fluid_adriver.c: Added PulseAudio driver and re-sorted drivers by use preference. * src/fluid_chan.c: Using MIDI enums for initializing channel CC values, added supported for RPN GM MIDI messages Bend Range, Fine Tune and Coarse Tune, added check for out of range NRPN parameters. * src/fluid_midi.h: Added RPN enum midi_rpn_event. * src/fluid_pulse.c: New PulseAudio driver. 2008-09-22 Pedro Lopez-Cabanillas * src/fluid_dsound.c: Fix for ticket #16 - dsound device can't be selected. 2008-09-07 Josh Green * src/fluid_alsa.c (new_fluid_alsa_seq_driver): Patch from Nicolas Boulicault to add ALSA sequencer midi.portname setting. * src/fluid_conv.h: S. Christian Collins' patch - changed FLUID_ATTEN_POWER_FACTOR from -531.509 to -200.0. * src/fluid_defsfont.c (fluid_defpreset_noteon): S. Christian Collins' patch - crash bug fix related to using certain modulators in a preset. * src/fluid_mdriver.c: Pedro Lopez-Cabanillas' patch which adds a midi.winmidi.device setting. * src/fluid_mod.c: S. Christian Collins' patch - Stop forcing velocity based filtering and a couple of calculation fixes to transform functions. * src/fluid_synth.c: Nicolas Boulicault's patch to add midi.portname setting. (fluid_synth_program_change): added fix to properly search for a percussion instrument * src/fluid_synth.h: Changed FLUID_NUM_PROGRAMS to 128 and set DRUM_INST_BANK to 128. * src/fluid_voice.c (fluid_voice_write): S. Christian Collins' patch - force velocity envelope value to be that of the previous stage when switching from decay to sustain and filter calculation now uses synthesizer baud rate rather than fixed at 44100. (fluid_voice_update_param): S. Christian Collins' patch - Use multiplier for GEN_ATTENUATION to be compatible with EMU10K1 cards. * src/fluid_winmidi.c: Pedro Lopez-Cabanillas' patch which adds a midi.winmidi.device option. * src/fluidsynth.c: Nicolas Boulicault's patch which adds midi.portname setting. Pedro Lopez-Cabanillas' patch which breaks out of argument processing loop for non getopt option argument handling when a non option is encountered and not using Readline. 2007-11-17 Josh Green * FluidSynth release 1.0.8 "Its about funky time!" * configure.ac: Bumped LT_REVISION and added call to AM_PROG_CC_C_O macro. * Makefile: Updated fluidsynth.prj to fluidsynth.anjuta * README-OSX: Update from Ebrahim Mayat for OS X Leopard * acinclude.m4: Fixed embedded main function in AM_PATH_READLINE macro. 2007-11-11 Josh Green * configure.ac: Added --enable-trap-on-fpe and --enable-fpe-check to assist with Floating Point Exception debugging. * src/fluid_chorus.c: Reverted the rest of the chorus "Effect level clip" patch, until something better is devised. * src/fluid_synth.c: Added support for trapping on Floating Point Exceptions on GLIBC systems, to aid developers in tracking down FPEs with gdb, removed buffer alignment hacks since they are no longer needed (not using SSE currently). * src/fluid_sys.c (fluid_time_config): Added check for a CPU freq calculation of 0.0, since this test is inadequate to begin with and was coming up as 0.0 on my laptop, causing a FPE. Will replace with real timer functions, in the future. * src/fluid_voice.c: Removed zap_almost_zero macro as it was buggy and had issues which went away when gcc optimization was turned off and in the case of !WITH_FLOAT was using abs() which is integer based and would cause FPEs. (fluid_voice_write): Removed a memory alignment hack and moved a call to fluid_fpe_check() to a better location. (fluid_voice_effects): Replaced zap_almost_zero with a call to fabs(), added fluid_fpe_check() call. * src/fluidsynth_priv.h: Removed FLUID_ALIGN16BYTE hack, as it is no longer needed. 2007-11-10 Josh Green * doc/fluidsynth.1: Updated man page with current command line options and other changes (minor). * include/fluidsynth/synth.h: Reverted "Effect level clip" patch as it seems to cause chorus count to have a much lessor effect. 2007-09-20 Josh Green * Doc updates to AUTHORS and latest README-OSX from Ebrahim Mayat. * src/config_win32.h.in: VERSION is now filled in at configure time. * src/fluid_alsa.c (fluid_alsa_audio_run_s16): Fixed bug which was causing weird crashes with QSynth when new_fluid_audio_driver2() when audio meters were enabled (user data parameter was being used as a fluid_synth_t instance). Synth instance is now no longer used in this case (it was only used for 16 bit dithering before). * src/fluid_oss.c: Fixed the same bug that was affecting ALSA driver. * src/fluid_rev.c: Reverted to old commented out code in regards to reverb level. * src/fluid_synth.c (fluid_synth_dither_s16): Now no longer uses fluid_synth_t instance, but accepts a pointer to an integer instead for keeping track of dithering buffer index (all that the synth instance was being used for). * src/fluid_synth.c (fluid_synth_one_block): Reverted patch which performs assignment of chorus and reverb levels in synthesis loop, until a better scheme is devised (unnecessary CPU consumption). * Added Visual Studio .sln and .vcproj files and some minor source changes to get FluidSynth to build with it. * Back-converted Visual Studio project to VC++ 6 project for users using that build platform (not tested). 2007-09-02 Josh Green * configure.ac: Removed SSE and longlong related switches (SSE support removed for now and longlong is now always used). * : Applied effect level clip patch from David Hilvert see http://fluidsynth.resonance.org/trac/ticket/2. * : Applied reverb damp scaling patch from David Hilvert see http://fluidsynth.resonance.org/trac/ticket/3. * src/fluid_dsp_float.c: No longer being #include'd and all interpolation functionality has been re-written as separate functions, interpolating around loops is now supported, effect (reverb/chorus/pan/filter) stuff moved to fluid_voice.c. * src/fluid_phase.h: 64 bit unsigned integers are now used for phase index/fraction sample pointers, modified macros accordingly. * src/fluid_voice.c: Removed SSE code, fluid_voice_init() renamed to fluid_dsp_float_init() and moved to fluid_dsp_float.c. Effect related functionality (reverb/chorus/pan/filter) moved from fluid_dsp_float.c to fluid_voice.c. Some code re-formatting and comment cleanup. Loop no longer requires padding surrounding it (fixes bug related to loop point right on the end of the sample). 2007-08-18 Josh Green * src/fluid_alsa.c: Added SND_SEQ_PORT_TYPE_MIDI_GENERIC back into the ALSA sequencer port registration as it broke the use of playmidi (thanks to Dave Serls for providing a patch and pointing this out). 2007-01-14 Josh Green * src/fluid_alsa.c: Fixed evil bugs in ALSA driver where return value of new fluid_alsa_handle_write_error() was not being checked correctly causing successfully handled ALSA errors (underruns for example) to terminate audio thread. * src/fluid_synth.c: Using an inline roundi function to replace roundf as per suggestion by Mihail Zenkov, 16 bit for dithering. 2006-12-10 Josh Green Lots of documentation updates. * doc/Doxyfile: No longer including functions by default, only those listed in the listed header files. * src/fluid_strtok.[ch]: Removed, since it was crap. Replaced with fluid_strtok() in fluid_sys.c which doesn't require an allocated tokenizing instance. * src/fluid_alsa.c: Audio processing is more optimized in the case where no user defined audio callback is used (removal of unneeded buffer copy), fluid_alsa_handle_write_error() added for centralized ALSA audio error handling, * src/fluid_aufile.c: Now also doing 16 bit dithering. * src/fluid_cmd.c: Removed use of old tokenizer instance. * src/fluid_coreaudio.c: User defined callback function is now honored. * src/fluid_defsfont.c: More leaks plugged (thanks to Paul Millar for the patch), removed sfont_free_data() since sfont_close() should be used instead (don't want to leak a file handle). * src/fluid_midi_router.c: Took out uses of fflush() since sending a line of text (with newline) should display it. * src/fluid_oss.c: Using fluid_synth_dither_s16() in place of old 16 bit conversion code. * src/fluid_settings.c: Replaced strtok stuff with new function, some other improvements. * src/fluid_synth.c (delete_fluid_synth): Turning off all voices so that SoundFont data will be freed correctly (thanks to patch from Paul Millar). * src/fluid_sys.c (fluid_strtok): New function to replace old tokenizing functions which required a token instance. * src/fluidsynth.c: Warning message printed if a non option is not a valid SoundFont or MIDI file (thanks to Nick Daly for the patch). 2006-11-22 Josh Green * src/fluid_alsa.c (new_fluid_alsa_audio_driver2): Removed some ALSA lib calls to set software parameters, which was likely causing the 100% CPU usage problem (not actually fixed in last update, not sure which one is the culprit). (fluid_alsa_seq_run): More changes in ALSA sequencer code, hopefully it is right this time! (delete_fluid_alsa_seq_driver): Memory leak fixed - wasn't freeing array of sequencer file descriptors. * src/fluid_chan.c: Memory leak fixes: Now deleting preset from channel when channel is destroyed. * src/fluid_cmd.c: Memory leak fix: strtok being deleted from command shell when shell is destroyed. * src/fluid_defsfont.c: Memory leak fixes: Freeing modulator lists in preset and instrument zones, freeing zone names, freeing instruments linked from preset zones, replaced use of "safe_malloc" with FLUID_MALLOC macro, deleting instrument list in SFData, deleting samples in SFData, freeing SFData structure. * src/fluid_settings.c: Memory leak fix: freeing options in option type settings. * src/fluid_synth.c: Memory leak fixes: Freeing FX buffers and right/left_buf. 2006-11-21 Josh Green * src/fluid_alsa.c (new_fluid_alsa_audio_driver2): Modified all ALSA calls to check return code error as "< 0" as per ALSA examples, sample rate is now compared with what was expected and warning message displays both values, if target sample rate wasn't set update the local period_size variable (was causing 100% CPU consumption by ALSA, from the resultant erroneous sw_params calls). (fluid_alsa_audio_run_float): Using case statement for error codes from snd_pcm_writen() for the sake of tidiness. (fluid_alsa_audio_run_s16): Using case statement for error codes from snd_pcm_writei() for the sake of tidiness, re-instated call of device callback function that was broken with the dither patch (don't want to break the API), now using new fluid_synth_dither_s16() to convert floating point sample data to 16 bit with dithering. (fluid_alsa_seq_run): Timeout in poll() call set to 100ms (from 1ms!), snd_seq_event_input_pending is used to check if events are available before calling snd_seq_event_input to prevent blocking, check of snd_seq_event_input error code moved to the right location (bug fix). * src/fluid_synth.h: Added dither_index parameter to fluid_synth_t structure to allow for per synth dithering continuity. * src/fluid_synth.c: Modified dithering to use new dither_index field for per synth dithering continuity, fixed off by 1 error with dithering index comparison, removed usage of roundf in dithering (is it sufficient to just integer truncate?). (fluid_synth_dither_s16): New function to perform dithering on buffers of floating point sample data. 2006-11-20 Josh Green * src/fluid_alsa.c: Applied dithering patch from Mihail Zenkov. * src/fluid_synth.c: Applied dithering patch from Mihail Zenkov. 2006-03-04 Josh Green * src/fluid_alsa.c (delete_fluid_alsa_audio_driver): Now calling snd_pcm_close() to close the ALSA audio driver handle. (fluid_alsa_seq_run): Check for -ENOSPC error was logicly inverted. (new_fluid_alsa_seq_driver): Sequencer is now opened in blocking mode. 2006-02-20 Josh Green * Fixed build error that occured when neither LASH or LADCCA are present. * Updated README-OSX from Ebrahim Mayat. 2006-02-18 Josh Green * FluidSynth release 1.0.7 "Increasing Fluidity.." * Removed spurious newlines from FLUID_LOG statements throughout. * AUTHORS: Some cleanup and additions. * src/fluid_lash.[ch]: Moved LADCCA related code from fluidsynth.c here and added new LASH support (both old LADCCA and LASH are supported exclusively). Used patches sent by Frieder Bürzele as a guide. * src/fluidsynth.c: Removed LADCCA code (now in fluid_lash.c), re-organized command line parsing and removed duplicate WIN32 switch statement, re-organized help output and added missing entries, added "-o help" switch for listing settings, welcome message now printed whenever FluidSynth is run and simplified, (print_usage): hard coded application name as "fluidsynth". * configure.ac: Changed --enable-SSE option to --enable-broken-SSE and --enable-SSE now just displays a fat warning about not using it. * src/fluid_jack.c: Warning is now displayed if synth sample rate doesn't match jackd. * src/fluid_alsa.c: Added detection for ALSA sequencer buffer overrun (-ENOSPC) and interrupted poll() call (-1??). * src/fluid_voice.c: Applied patch from Henri Manson which adds a fluid_ct2hz_real() function which does not have the filter cutoff limits that fluid_ct2hz() does, new function being used for calculations that may include non-audible frequencies. * src/fluid_dsound.c: Applied patch from Henri Manson which only creates the directsound window once. 2005-09-04 Josh Green * src/fluid_ramsfont.c (fluid_ramsfont_remove_izone): Applied crash bug fix from Antoine Schmitt. 2005-07-05 Josh Green * src/fluidsynth_priv.h: FLUID_ALIGN16BYTE is broken on AMD64 so now only enabled if SSE is being used. If SSE code becomes more useful in the future this should be fixed. 2005-06-29 Josh Green * Applied LASH patch that is included with ladcca-0.4.0. 2005-06-11 * Released FluidSynth 1.0.6 "Music to my ears" * README-OSX: Update from Ebrahim Mayat. * acinclude.m4: Midishare support now defaults to auto. * configure.ac: Added LT_CURRENT, LT_REVISION and LT_AGE in place of LIBFLUIDSYNTH_MAJ and LIBFLUIDSYNTH_MIN to make better use of libtool library versioning. Fixed use of AC_ARG_ENABLE (was setting variables to yes even when disable was specified), fixes --disable-SSE which was reported by Mikhail Yakshin, added warning when SSE is enabled to let users know that this feature isn't really desirable currently. * src/Makefile.am: Now using LT_VERSION_INFO to substitute the libtool version. * src/fluid_cmd.c (fluid_cmd_handler_handle): Modified to avoid GCC "type-punned" cast warning. * src/fluid_defsfont.c (fluid_preset_zone_import_sfont): Fixed assignment of modulator amtsrc flags (should be assigned to flags2 not flags1), thanks to Stephan Tassart for reporting this. (fluid_inst_zone_import_sfont): Same fixes as for above. * src/fluid_sys.c (fluid_log): Now using vsnprintf for formatting error messages to fix buffer overflow as reported by Axioplase. (fluid_debug): Same as above. 2005-06-11 * fluidsynth.prj: Added Anjuta project file. * src/fluid_conv.c: fluid_cb2amp conversion set back to real centibels and added a new fluid_atten2amp table conversion for non-standard EMU 8k/10k attenuation. * src/fluid_voice.c (fluid_voice_write): Updated volume calculations to use fluid_cb2amp for envelope and LFO, but use fluid_atten2amp for initial attenuation. (fluid_voice_noteoff): Re-coded volenv_val attack conversion and verified. 2005-06-10 * src/fluid_phase.h: Patch from Sean Bolton to fix big endian long long phase combined 64 bit value type fluid_phase_t * src/fluid_voice.c (fluid_voice_update_param): case GEN_OVERRIDEROOTKEY was incorrectly adding pitchadj fine tune amount instead of subtracting it. Also, fine tuning should be applied to root key override as well. 2005-06-07 * Applied Sean Bolton's DSSI patch (SB patch) which adds the ability to change polyphony at runtime and fixes a bug (see below). * README-OSX: Update from Ebrahim Mayat for OSX Panther. * include/fluidsynth/synth.h: Sean Bolton's DSSI patch adds two new functions fluid_synth_set_polyphony and fluid_synth_get_polyphony. * src/fluid_conv.c: Centibel to amplitude conversion now follows EMU 8k/10k which is contrary to SoundFont specification (TiMidity++ used as an example). * src/fluid_conv.h: FLUID_CB_POWER_FACTOR defined for the centibel->amp conversion table equation. * src/fluid_defsfont.c (load_pgen): Fixed 'use of cast expressions as lvalues is deprecated' warning by casting the value being assigned instead of the variable assigned to and removed code warrior specific code to work around this. (load_igen): Same as for load_pgen. * src/fluid_synth.c: SB patch - uses synth->polyphony instead of synth->nvoice when iterating over the synth's voices. (fluid_synth_update_polyphony): SB patch (new) - runtime update (fluid_synth_set_polyphony): SB patch (new) (fluid_synth_get_polyphony): SB patch (new) (fluid_synth_nwrite_float): SB patch - fixes bug where the use of arbitrary values of the 'len' parameter was broken. * src/fluid_voice.c (fluid_voice_write): modlfo_to_vol (modulation LFO to volume) was being calculated inverted (should be negative attenuation, gain, for a positive rise in LFO). (fluid_voice_noteoff): Updated centibel to amplitude conversion used when voice off during attack to use the new FLUID_CB_POWER_FACTOR. 2004-11-11 * README-OSX: Update from Ebrahim Mayat. 2004-08-18 * src/fluid_synth.c (fluid_synth_set_bank_offset): (fluid_synth_get_bank_offset): New API to set a bank offset in a SoundFont (proposition made by Ken Ellinwood). 2004-08-06 * src/fluid_synth.c (fluid_synth_noteon): fluid_synth_release_voice_on_same_note() is now called in the noteon() function instead of in fluid_synth_start(). This fixes the silent note problem! 2004-07-29 * src/fluid_chan.c (fluid_channel_cc): Applied Ken Ellinwood's fix for the bank select (MSB) message. * src/fluid_jack.c (fluid_jack_audio_driver_settings): Applied Rui Nuno Capela's patch 2004-05-14 * doc/fluidsynth.1 (option): Fixed typo noted by Gerald Pye. 2004-05-14 Peter Hanappe * src/fluid_dsound.c (fluid_dsound_enum_callback): Applied Sergey Pavlishin's patch. This path fix stack overflow during DirectSound audio driver initialization. 2004-05-07 Peter Hanappe * src/fluid_synth.c (fluid_synth_remove_sfont): Added new function 2004-05-05 Peter Hanappe * src/fluid_alsa.c (new_fluid_alsa_seq_driver): The alsa driver now opens several ports if the synthesizer is configured for more than 16 MIDI channels. * src/fluid_voice.c (fluid_voice_write): I removed the filter on/off optimization. The filter is always on and serves as an anti-aliasing filter. 2004-05-04 Peter Hanappe * src/fluid_synth.c (new_fluid_synth): The number of MIDI channels now has to be a multiple of 16. The synth checks that this is the case and changes the settings accordingly. I removed the sanity checks for the min/max value of the number of MIDI channels since this is already done by the settings object. 2004-03-30 Josh Green * src/fluid_voice.c (fluid_voice_write): Altered filter turn-off optimization to not turn filter off once it has been enabled. There is still a potential for a click when it gets turned on though, which needs to be dealt with. 2004-03-30 Peter Hanappe * src/fluid_dsp_core.c: I've split up the dsp core file in three files: fluid_dsp_simple.c, fluid_dsp_float.c, and fluid_dsp_sse.c. This improves the readability. 2004-03-29 Peter Hanappe * src/fluid_jack.c (new_fluid_jack_audio_driver2): Testing the number of ports before allocating them. (fluid_jack_audio_driver_settings): Registering the "audio.jack.autoconnect" setting. * src/fluid_midi.c (fluid_player_set_midi_tempo): Tempo changes handled correctly. Was broken after fix on [2004-03-22] (see below). * src/fluid_strtok.c (fluid_strtok_char_index): Removed printf's from fluid_strtok.c 2004-03-26 Peter Hanappe * bindings/README: Imported the fluidsynth_jni and fluidmax projects. 2004-03-25 Peter Hanappe * src/fluid_rev.c (new_fluid_revmodel): Added 'gain', similar as in Freeverb 3. Using same 'wetscale' as Freeverb 3, but fixing 'wet' to 3. fluid_revmodel_setlevel() does not change the value of 'wet': The 'wet' level can be controlled with the 'reverb send'. (fluid_revmodel_processreplace): The input is multiplied by 2 and by the gain. This corresponds to the channel mixing and scaling that Freeverb 3 does. 2004-03-24 Peter Hanappe * src/fluidsynth.c (main): Added the -f switch. Passing "-f file" on the command line tells fluidsynth to read parse the file and execute and commands. (main): User config and system config file are now loaded correctly * src/fluid_cmd.c (fluid_shell_run): the shell doesn't get stuck and loop on an emtpy string when the end of the stream is reached. * src/fluid_io.c (fluid_istream_gets): fluid_istream_gets() returns 0 if the end of the stream was reached and -1 on error. * src/fluid_cmd.c (fluid_source): Fixed bug in "file = open(filename, FLAGS);" (I shouldn't pass O_WRONLY when what I want is O_RDONLY!) 2004-03-23 Peter Hanappe * src/fluid_aufile.c (new_fluid_file_audio_driver): Added fluid_aufile.c. This file implements a audio driver that writes the audio output to a file. This driver is NOT real-time and is currently useful for testing purposes only (not even useful to play MIDI files). 2004-03-22 Peter Hanappe * src/fluid_synth.c (new_fluid_synth): Removed the synth->busy mutex. I don't think it is necessary; to be discussed. * src/fluid_midi.c (fluid_player_callback): Fixed the timing in the MIDI playback. The current MIDI tick in every timer callback was calculated as an increment to the previous number of ticks. This introduces a growing error due to rounding errors and timer variations. The current tick is now calculated according to the absolute time at the beginning of the file. (Beginners error ...) * doc/FluidSynth-LADSPA.pdf: Added Markus' LADSPA design document. * doc/xtrafluid.txt: Added Antoine's Xtra API documentation. * doc/midi_time.txt: Added a memo on midi timing. 2004-03-19 Peter Hanappe * src/fluid_midishare.c: Applied Stephane Letz patch: MidiShare is now connected to fluidsynth by default so that received MIDI events directly trigger the synth 2004-02-28 Peter Hanappe * src/fluid_synth.c: Added fluid_synth_program_select2() and fluid_synth_get_sfont_by_name() in fluid_synth.c. These functions are not in the public API, yet. 2004-02-25 Peter Hanappe * src/fluid_voice.c: Fixed bug in volume envelope (in fluid_voice_update_param(), case GEN_VOLENVDECAY): the minimum value was converted to linear amplitude instead of a normalized value of the cB (1-cB/1000). Because of that, the decay section went on for too long. 2004-12-xx Peter Hanappe * src/fluid_seq.c: Inserting events in the queueLater list was incomplete. It didn't check if the event was the last in the list, and the looping through the list didn't update the prev pointer. I added muteces to the sequencer. Events are dynamically allocated if no free events are available. The sequencer is protected by a mutex. 2003-11-14 Josh Green * src/fluidsynth.c: Removed CCA_Use_Jack and CCA_Use_Alsa flags since LADCCA no longer uses them. 2003-08-31 Josh Green * acinclude.m4: Renamed AC_SOUND macro to AC_OSS_AUDIO and removed the ALSA check from it since pkg-config is now being used to check for ALSA. Also fixed --enable-alsa-support and --enable-oss-support which were disabling support instead (reported by Bart Massey). * configure.ac: pkg-config is now being used to check for ALSA. ALSA and OSS now use automake conditionals to conditionally compile source files. * Makefile.am: Re-arranged SUBDIRS so build output looks nicer. * src/Makefile.am: ALSA and OSS are now conditionally compiled using automake conditionals. 2003-08-29 Josh Green * src/fluid_sys.c: Patch from Eric Van Buggenhaut to make i386 asm code not compile for all non-i386 archs rather than just DARWIN. * src/fluidsynth_priv.h: Patch from Sergey Pavlishin to fix FLUID_REALLOC macro. * src/fluid_cmd.c: Ken Ellinwood's patch to add -verbose to "channels" command, and print settings values with 3 decimal places. * src/fluid_defsfont.c (fluid_defsfont_sfont_get_preset): Ken Ellinwood's patch to initialize sfont field of preset. * src/fluid_ramsfont.c (fluid_ramsfont_sfont_get_preset): Ken Ellinwood's patch to initialize sfont field of preset. * src/fluid_midi.c (fluid_midi_file_read_event): Fixed a crash bug with zero length MIDI meta events that was pointed out by Sergey Pavlishin. (delete_fluid_midi_event): Fixed a stack overflow problem pointed out by Sergey Pavlishin that was caused by recursively deleting MIDI event linked list, now just using a while loop. 2003-08-25 Josh Green * src/fluidsynth.c: MIDI channels switch should be -K not -L as was listed in "Usage" output, also -K was setting audio.channels for non getopt case statement - changed to midi.channels. Added a new option "-l, --disable-ladcca" to disable LADCCA server connection. 2003-08-25 Josh Green Release version 1.0.3 * doc/fluidsynth.1: Applied typo patch from Eric Van Buggenhaut. * TODO: Restructuring TODO file (removing old stuff). * doc/Doxyfile: Disabled Tex doxygen generation and changed OUTPUT_DIRECTORY to api/. * doc/Makefile.am: Added an update-docs target and related for updating developer doc and doxygen reference HTML. Also added update-docs to dist-hook for updating before distribution packaging. * include/fluidsynth/synth.h: Some fixes to doxygen documentation. * fluidsynth.spec.in: New RPM spec file which is generated at configure time. * Makefile.am: Added fluidsynth.spec(.in) to EXTRA_DIST. 2003-08-19 Josh Green * src/fluid_alsa.c: Added some calls to snd_strerror() to print out details of ALSA routine failures. * src/fluid_defsfont.c: Put a message about SoundFont loading code being borrowed from Smurf SoundFont Editor. * src/fluid_rev.c: Valgrind found that some values were being used uninitialized because fluid_revmodel_update() was being called before all reverb parameters were set, now setting manually and then calling update routine. * src/fluid_voice.c: Increased FLUID_MAX_AUDIBLE_FILTER_FC to minimize clicks from filter toggling. Added a FLUID_MIN_VOLENVRELEASE constant to set the minimum volume envelope release to minimize clicks. 2003-07-22 Josh Green * src/fluid_midishare.c: Added include of header "config.h" as per Albert Graef's request. * src/fluid_voice.c (fluid_voice_optimize_sample): Moved a variable declaration to the beginning of function, it was causing problems with at least one user. 2003-06-28 Josh Green * src/fluid_defsfont.c: Moved call to fluid_voice_optimize_sample from fluid_inst_zone_import_sfont to fluid_defsfont_load. Also reduced minimum sample size before rejection from 48 to 8 (could be lower?). * src/fluid_voice.c (fluid_voice_optimize_sample): Added a check for sample->valid to ignore ROM samples which was causing a crash with Vintage Dreams and other SoundFont files with ROM samples. 2003-06-17 Josh Green Release version 1.0.2 Added Makefile.am files where lacking. * Makefile.am: Fixes to "make dist" target by adding macbuild, sf2 and winbuild to SUBDIRS also removed acconfig.h from EXTRA_DIST. * acinclude.m4: Removed AC_JACK, now using pkgconfig. * configure.ac: Updated to version 1.0.2, Jack test now using pkgconfig and built by default if found, coreaudio driver now built by default if found. * doc/Makefile.am: Added Doxyfile, example.c, example.sf2, fluidsynth.1 and fluidsynth-v10-devdoc.xml to EXTRA_DIST. * src/Makefile.am: fluid_jack.c now conditionally built, fluid_sse.h added to EXTRA_DIST. * src/fluid_jack.c: #if JACK_SUPPORT removed as its not needed. 2003-06-15 Josh Green * configure.ac: Fixed detection of CoreAudio by looking for CoreAudio/AudioHardware.h. * src/Makefile.am: Added COREAUDIO_CFLAGS and COREAUDIO_LIBS. * src/fluid_coreaudio.c: Added CoreAudio prefix to #include headers (fluid_core_audio_callback): Fixed declarition to match that of the typedef in CoreAudio header to stop warnings. * fluidsynth.c: Now including fluidsynth_priv.h to include the arch specific definitions in there (perhaps should be done in configure script though). * fluidsynth_priv.h: Added "#define WITHOUT_SERVER 1" to Darwin build. 2003-06-12 Josh Green * Makefile.am: Added autogen.sh to EXTRA_DIST * acinclude.m4: Added AM_PATH_READLINE macro for readline detection and prefix configuration. * configure.ac: Support for MinGW32 build, Darwin build fixes, configure CFLAGS input value now honored, fixes to CoreAudio support, and better readline detection and config. * src/Makefile.am: Now conditionally compiling CoreAudio and Windows sources, added config_*.h files to EXTRA_DIST, some stuff for MinGW32 build, READLINE_LIBS and READLINE_CFLAGS now used. * src/fluid_dsound.c: Fixed some warnings by adding "void" for empty parameter procedure declarations. * src/fluidsynth.c: Don't include config_win32.h if MinGW32. * src/fluidsynth_priv.h: Stuff for MinGW32 and Darwin builds. * doc/fluidsynth-v10-devdoc.xml: Applied a diff from Alexandre Prokoudine. 2003-06-09 Josh Green * src/fluid_alsa.c: Added calls to pthread_attr_setschedparam to properly create SCHED_FIFO threads. * src/fluid_oss.c: pthread_attr_setschedparam calls added. * src/fluid_midishare.c: Patch update from Stephane Letz. 2003-05-29 root * src/fluid_synth.c (fluid_synth_one_block): Added a mutex that provides a small degree of protection against noteons / noteoffs, when the audio thread is working. * src/fluid_synth.h (struct _fluid_synth_t): * src/fluid_voice.c (fluid_voice_optimize_sample): 2003-05-29 Markus Nentwig * include/fluidsynth/voice.h: added fluid_voice_gen_incr to api * src/fluidsynth.c: Added error message for command line parameter handling * src/fluid_voice.c (fluid_voice_optimize_sample): Removed loop peak detection at run time, because it caused dropouts. Now the sound font loader or application is responsible to call fluid_voice_optimize_sample (if it doesn't, the turnoff optimization is simply disabled). 1999-11-30 Antoine Schmitt * src/fluid_defsfont.c: inst_zone lokey is now properly inialized to 0 (it was not, leading to random lost noteons depending on memory initialization) 2003-04-03 Peter Hanappe * src/fluid_rev.c: reverb parameters are clipped to their valid range. * src/fluid_alsa.c: using fluid_alsa_audio_run_s16 as default function. This reduces the high CPU usage. * src/fluid_voice.c (fluid_voice_write): filter interpolation done over only 1 buffer to avoid filter instability * src/fluid_chan.c (fluid_channel_init): bank number set to 128 for the drum channel * src/fluid_midi.c (fluid_midi_file_read_event): Correctly reading pitchbend value 2003-02-27 Josh Green Updated automake files (automake 1.6). * configure.ac: New version autoconf variables which get substituted into include/iiwusynth/version.h.in. * include/iiwusynth/version.h.in: Version defines that are filled in by autoconf. * src/Makefile.am: Fixed SOURCES including removing headers that are now in include/iiwusynth/, added missing sources (iiwu_ramsfont.[ch], iiwu_sfont.h) and added iiwu_dsp_core.c to EXTRA_DIST. * doc/Makefile.am: Added iiwusynth.1 to EXTRA_DIST. * include/iiwusynth.h: Added version.h. * iiwusynth/Makefile.am: Added version.h to the installed headers. 2003-02-08 Markus Nentwig * src/iiwu_ladspa.c: Added a very small signal at Nyquist frequency. This fixes denormal number problems in some plugins. * src/iiwu_cmd.c (iiwu_shell_run): Now also invalid input lines are added to the command line history. So the user can just scroll up and fix them. * src/iiwu_ladspa.c: Cleaned up error messages * src/iiwu_dsp_core.c: Disabled SSE interpolation, because it is slower than the normal code * autogen.sh: Added a line, that checks for the presence of pkg-config in autogen.sh. Motivation: It took me some time to figure out what was wrong... It produces some error message instead of an obscure error later during ./configure, if pkg-config is not installed. 2003-02-07 Josh Green Applied another Bob Ham LADCCA patch. * src/iiwu_alsa.c: LADCCA patch: Now using a ladcca.enable setting. * src/iiwu_jack.c: LADCCA patch: ladcca.enable setting and jack ports are no longer auto connected unless audio.jack.autoconnect is set. * src/iiwusynth.c: LADCCA patch: ladcca.enable and command line options -j and --connect-jack-outputs to enable Jack autoconnect. 2003-02-05 Josh Green Applied Bob Ham's LADCCA and pkgconfig patches. * Makefile.am: pkgconfig patch. * configure.ac: Renamed from configure.in as per new autoconf standards. LADCCA configure switch and detection. FluidSynth.pc pkgconfig file output. * src/Makefile.am: LADCCA patch. * src/iiwu_alsa.c [HAVE_LADCCA]: LADCCA patch: reports ALSA sequencer client ID. * src/iiwu_jack.c [HAVE_LADCCA]: LADCCA patch: reports JACK client name. * src/iiwusynth.c [HAVE_LADCCA]: LADCCA patch: connects to LADCCA server, creates client thread, saves/restores SoundFont file state. Used iiwu_sfont_get_name macro to get SoundFont file names contrary to the patch. Should these macros be public? Included unistd.h for usleep call (within HAVE_LADCCA). 2003-01-23 Josh Green * src/iiwu_jack.c: Fixed a segfault bug caused by freeing jack port names, when really only the port array should be freed, jack reference docs are confusing on this matter! * src/iiwu_voice.c (iiwu_voice_check_sample_sanity): Min loop size and padding now set via constants IIWU_MIN_LOOP_SIZE and IIWU_MIN_LOOP_PAD defined at top of iiwu_voice.c, and the values were lowered to exceed SF spec requirements rather then just meet. (iiwu_voice_write): Now using a constant IIWU_MAX_AUDIBLE_FILTER_FC defined at the top of iiwu_voice.c to control the filter cutoff optimization. Also added IIWU_MIN_AUDIBLE_FILTER_Q so filter will only turn off if both cutoff and q are determined to be inaudible. Filter optimization is much less noticeable when modulating. 2003-01-14 Markus Nentwig * src/iiwu_ladspa.c: Adapted new command handler * src/iiwu_midi_router.c (midi_dump_prerouter): Added forgotten 'flush' for event dump 'fprintf's 2003-01-01 Markus Nentwig * src/iiwu_oss.c (new_iiwu_oss_audio_driver): Changed to callback function * src/iiwu_alsa.c (new_iiwu_alsa_midi_driver): Changed to callback function * src/iiwu_midishare.c (iiwu_midishare_midi_driver_receive): Partly done the same * src/iiwu_winmidi.c (new_iiwu_winmidi_driver): To be done... * src/iiwu_midi_router.c: Added * src/iiwu_ladspa.c: Adapted to new settings system * src/iiwu_adriver.c (iiwu_audio_driver_settings): Uses getint instead of getnum for audio.period-size and audio.periods settings. * src/iiwu_voice.c (iiwu_voice_write): Ignore the valid flag for samples. Otherwise no sound is produced. * src/iiwu_chan.c (iiwu_channel_cc): Fixed bank select (7-bit instead of 8 bit) 2002-12-23 Peter Hanappe * src/iiwu_io.c (iiwu_istream_readline): new file (iiwu_io.c and iiwu_io.c) to handle IO in the shell. * src/iiwusynth.c (main): options to start TCP server. * src/iiwu_cmd.c (new_iiwu_server): New structure and functions (new_iiwu_shell): New structure and functions to improve command interface. (new_iiwu_cmd_handler): New structure and functions to improve command interface. * src/iiwu_sys.c (new_iiwu_server_socket): New structure and functions (new_iiwu_thread): New structure and functions 2002-12-14 Peter Hanappe * src/iiwu_chan.c (iiwu_channel_cc): Handling NRPN messages (NRPN system). * src/iiwu_voice.c (iiwu_voice_update_param): Does more extensive range checking because the NPRN system may produce out-of-range values (NRPN system). (iiwu_voice_set_param): New function to change generator values (NRPN system). * src/iiwusynth_priv.h (iiwu_clip): New macro * src/iiwu_synth.c (iiwu_synth_set_gen): New function to change generator values (NRPN system). * src/iiwu_gen.c (iiwu_gen_map_nrpn): New function to map the NRPN data input to the parameter range (NRPN system). * src/iiwu_midi.c (iiwu_midi_file_read_event): Fixed metadata buffer bug (alloc size 1 too small). 2002-12-10 Peter Hanappe * src/iiwu_dsound.c (iiwu_win32_destroy_window): Filled in the empty lines... * src/iiwusynth.h: Changes in the definition for iiwu_synth_sfload and iiwu_synth_sfunload, New functions: iiwu_synth_sfreload, iiwu_synth_get_sfont_by_id, and iiwu_list_insert_at. New 'id' field in iiwu_font_t. 2002-12-08 Markus Nentwig * src/Makefile.am: added iiwu_hash.c and iiwu_strtok.c to libiiwusynth_la_SOURCES * src/iiwu_settings.c (iiwu_settings_init): Removed multi_channel from the settings (replaced with audio_channels > 1) * src/iiwu_settings.c (iiwu_settings_init): added audio_groups setting. This is the number of individual channels generated from the synth, and always equal to audio_channels, as long as the LADSPA Fx unit is disabled. Otherwise it can be used (for example) to separate even and odd MIDI channels, apply different Fx and mix together to one stereo output. src/iiwu_ladspa.c: Extended Fx unit to multigroup input, fx sends and multiple audio output channels 2002-12-04 Peter Hanappe * src/iiwu_midi.c (iiwu_midi_file_read_event): the metadata buffer is now dynamically allocated. What! Dynamic memory management already existed in the sixties! * src/iiwu_cmd.c (iiwu_handle_reset): New shell command. Sends system reset. * src/iiwu_cmd.c (iiwu_expand_path): New function to handle filenames starting with '~'. * src/iiwu_cmd.c: Added commands for working with tunings. Added 'source' command. * src/iiwu_chan.h (struct _iiwu_channel_t): added tuning * src/iiwusynth.h: new tuning functions * src/iiwu_synth.c (iiwu_synth_reset_tuning): new tuning functions * src/iiwu_voice.c: Added tuning 2002-12-03 Peter Hanappe * doc/iiwusynth.1: new man page * src/iiwu_midi.c (iiwu_player_load): the player now handles a playlist. * src/iiwusynth.h: 'iiwu_player_add' replaces 'iiwu_player_load' * src/iiwusynth.c (main): iiwusynth can now play midifiles. 2002-12-02 Peter Hanappe * src/iiwu_sys.c (new_iiwu_timer): New argument 'auto_destroy' to specify whether the timer should delete it's structure when the timer is finished. * src/iiwu_synth.c (iiwu_synth_sfunload): If the soundfont can not be unloaded immediately, a timer thread is spinned of to unload it later. On MacOS 9, the unload is tried at a subsequent 'load' or 'unload' request. * src/iiwusynth.h (struct _iiwu_sample_t): Added 'refcount' field to test when a soundfont can be unloaded. * src/iiwu_synth.c (iiwu_synth_nwrite_float): New function allowing multi-channel audio output. (iiwu_synth_init): Fixed 'amount' for pan. Now set to 500. * src/iiwu_cmd.c (iiwu_synth_cmdshell): Added little prompt. * src/iiwusynth.c (print_welcome): iiwusynth prints out a welcome message as an well-behaved, interactive application should. * src/iiwu_synth.c (iiwu_synth_all_sounds_off): New function to implement the 'All Sound Off' MIDI messages (CC 120). (iiwu_synth_system_reset): This function now also resets the default controller values on the MIDI channels, and clears the reverb and chorus delay lines. (iiwu_synth_count_midi_channels): New function to retreive the number of available midi channels. (iiwu_synth_count_audio_channels): New function to retreive the number of available midi channels. (iiwu_synth_count_effects_channels): New function to retreive the number of available effects channels. (iiwu_synth_get_cpu_load): New function to retreive an estimation of the CPU load. * src/iiwusynth.h: Added fields to handle multi-channel audio and a variable number of midi-channels. The 'flags' has been expanded/replaced with several variables. * src/iiwu_chan.c (iiwu_channel_cc): Implemented the 'All Control Off' MIDI message (CC 121). * src/iiwu_chorus.c (iiwu_chorus_update): iiwu_chorus_update (called after the iiwu_chorus_set_xxx function) no longer returns an error of out-of-range values. It clips the value the the [min-max] range. 2002-11-22 Markus Nentwig * src/iiwu_voice.c (iiwu_voice_write): Fixed compilation problem without --enable-SSE (Pentium II and Mac) 2002-11-17 Markus Nentwig * src/iiwu_voice.c (iiwu_voice_write): Fixed nonlooped samples-bug. * TODO (TODO): Updated * src/iiwu_cmd.c (iiwu_handle_reverbsetlevel): Changed command line command 'rev_setwet' to 'rev_setlevel'. Replaced the word 'wet' by 'level' in most places. Added a command line option --dump, which provides 'machine-readable' output from stdout to hook up a user interface. * src/iiwusynth.h: Moved the default values for gain, chorus and reverb here. Might be useful as an example... * src/iiwu_voice.c (iiwu_voice_calculate_runtime_synthesis_parameters): Added 'scale tuning' modulator, centered around C3. * src/iiwusynth.h: Added API functions to read the reverb state Moved iiwu_synth_system_reset to the API 2002-11-08 Markus Nentwig * src/iiwu_voice.c (iiwu_voice_write): Fixed Volume envelope delay bug * src/iiwu_voice.c (FILTER_TRANSITION_SAMPLES): Doubled filter fading time * src/iiwu_mod.c (iiwu_mod_get_value): Changed convex unipolar negative definition * src/iiwu_voice.c (iiwu_voice_off): Cleaned up a bit, uses now calls to iiwu_voice_off, when a voice is finished. * src/iiwu_midi.c (iiwu_midi_parser_parse): Reimplemented New parser should be able to cope with realtime, system common and resynchronize. 2002-10-31 Markus Nentwig * src/iiwu_alsa.c (iiwu_alsa_midi_run): Increased MIDI timeout from 1 to 100 ms * src/iiwu_dsp_core.c: Merged identical filter coefficients b0 and b2 into b02 Implemented smooth filter transitions * src/iiwu_sys.c (iiwu_check_fpe): Added verbose FPE reporting and systematic FPE checks. * src/iiwu_rev.c: Added a constant DC offset to avoid slowdown caused by denormal numbers * src/iiwu_synth.c (delete_iiwu_synth): Fixed segv during shutdown * src/iiwu_dsp_core.c: Fixed buffer bug (aligned-unaligned) * src/iiwu_synth.c (iiwu_synth_damp_voices): Commented out unused code * src/iiwu_dsp_core.c: Optimized, added SSE code, which is this time actually faster than the default code. Well. Part of it. * src/iiwu_voice.c: Minor clean-up * configure.in: Added switch --enable-longlong * configure.in: Added switch --enable-SSE * src/iiwu_phase.h: Added 64 bit operations, documented * src/iiwu_sse.h: Check to avoid #including the file more than once 2002-10-29 Markus Nentwig * src/iiwu_voice.c: Added experimental SSE support for Pentium III. Comment out #define SSE from iiwu_voice.c to get back to the standard version. 2002-10-26 Markus Nentwig * src/iiwu_seq.c: Fixed a couple of warnings * src/iiwu_voice.c (new_iiwu_voice): Removed iiwu_voice_init. * src/iiwu_dsp_core.c: New 7th order interpolation. 2002-10-24 Markus Nentwig * src/iiwu_voice.c(iiwu_voice_determine_amplitude_that_reaches_noise_floor_for_sample): Added checking for invalid sample. * src/iiwu_voice.c (iiwu_voice_write): Moved the DSP core functions into iiwu_dsp_core.c. Optimized, cleaned up, documented. Amplitude scaling short => floating point is now done as the last operation in the DSP loop (voice->amp does not include the scaling factor anymore). * src/iiwu_synth.c (iiwu_synth_one_block): Saved a couple of multiplications per sample by moving the master gain into iiwu_voice_write * src/iiwu_synth.c (iiwu_synth_free_voice_by_kill): Modified the algorithm * src/iiwu_synth.c (iiwu_synth_alloc_voice): Noteon algorithm will now turn off retriggered running voices ('sustain pedal problem') 2002-10-18 Markus Nentwig * src/iiwu_alsa.c (new_iiwu_alsa_midi_driver): Disabled high-priority scheduling for the MIDI thread to get rid of audio dropouts. * src/iiwu_synth.c (iiwu_synth_free_voice_by_kill): Modified voice killing algorithm, so that recently started voices are not killed * src/iiwu_voice.c (iiwu_voice_run_dsp): Changed some variable names. Extensive loop point checking, when loop points are modulated. * src/iiwusynth.h: Added functions to read the state of the chorus. * src/iiwu_chorus.c: Rewrote chorus setup logic (if a parameter is out-of-range, all other parameter changes are discarded). * src/iiwu_voice.c: Added caching for loop peak detection: The amplitude of the loop is only detected once for each sample. Exception only, if the resulting loop differs from the original loop settings of the sample (in this case, the peak detection is still run for each noteon event). * src/iiwusynth.h (struct _iiwu_sample_t): Added 'amplitude_that_reaches_noise_floor_is_valid' and 'amplitude_that_reaches_noise_floor' * src/iiwu_voice.c(iiwu_voice_calculate_runtime_synthesis_parameters): Renamed 'iiwu_voice_optimize' 2002-07-21 Peter Hanappe * src/Makefile.am (libiiwusynth_la_SOURCES): Followed Bob Ham's suggestion for the Makefile.am to fix the problems with automake 1.6 1999-11-30 Tim Goetze * src/iiwu_synth.c (iiwu_synth_alloc_voice): New algorithm for voice allocation, when all voice processes are in use 1999-11-30 Markus Nentwig * src/iiwu_synth.c (iiwu_synth_alloc_voice): Applied above patch, 2002-07-08 Markus Nentwig * src/iiwu_synth.c (iiwu_synth_noteoff): Changed noteoff strategy: Noteoff now turns off all voice processes with the same channel / key, regardless of the voice ID (avoids stuck notes). 2002-07-13 Peter Hanappe * src/Makefile.am (EXTRA_libiiwusynth_la_SOURCES): Applied Takashi Iwai's patch. The configure stuff in iiwusynth-0.2 cannot be rebuilt with the latest automake 1.6. You cannot use substitution for *_SOURCES in Makefile.am. This fixes this problem. 1999-11-30 Markus Nentwig * src/iiwusynth.h: Added documentation, removed GEN_CHANGED (it was unused). * src/iiwu_mod.c (iiwu_dump_modulator): Cleaned up * src/iiwu_cmd.c (iiwu_handle_help): Restructured command line help system 2002-06-14 Markus Nentwig * src/iiwu_chorus.c (iiwu_chorus_processmix): Turning off chorus now, when parameters are wrong (avoid FPE) * src/iiwu_voice.c (iiwu_voice_write): Optimized turnoff condition for voice 2002-06-11 Markus Nentwig * src/iiwu_voice.c (iiwu_voice_add_mod): Fixed bug that prevented non-default modulators from being added. (iiwu_voice_config): Added peak detection for the sample loop, and a condition turning off the voice, if loop peak volume and amplitude envelope combined fall below the noise floor. 2002-06-06 Peter Hanappe * acinclude.m4: Fixed problems with enable/disable jack and midishare 2002-06-06 Tim Goetze * src/iiwu_synth.c (iiwu_synth_all_notes_off): Added handling of all-notes-off midi message 2002-06-03 Markus Nentwig * src/iiwu_chorus.c: Fixed bug in initial phase calculation 2002-06-02 Peter Hanappe * src/iiwu_jack.c: updated for new JACK types. 2002-06-02 Bob Ham * acinclude.m4: Changed acinclude.m4 for configure to ignore jack. 2002-06-02 Markus Nentwig * autogen.sh: Added libtoolize -f to prevent error message 'libtool: ltconfig version does not match ltmain.sh version ...' * src/iiwusynth.h: Changed iiwu_voice_add_mod_t to iiwu_voice_add_mod * src/iiwu_synth.c: Added NULL termination to list returned by iiwu_synth_get_voicelist * src/iiwusynth.h: Added iiwu_synth_set_chorus (API function) * src/iiwu_synth.c: Added iiwu_synth_set_(reverb|chorus)_on (API functions) * src/iiwu_cmd.c: Added control commands for chorus (see help) 2002-05-26 Tim Goetze * src/iiwu_voice.c (iiwu_voice_noteoff): Fixed conversion between volenv-values from attack segment to later envelope segments 2002-05-22 Markus Nentwig * src/iiwu_voice.c (iiwu_voice_query_ID): Added, API function (iiwu_voice_query_playing): Added, API function (iiwu_voice_write): Fixed problem with filter caused 05-18 * src/iiwusynth.h: Moved iiwu_voice_update_param into the API 2002-05-19 Markus Nentwig * src/iiwusynth.h (iiwu_synth_get_voicelist): Added. * src/iiwu_voice.c (iiwu_voice_noteoff): Added a conversion for linear to cB amplitude, when a note is turned off during the attack phase of the volume envelope * src/iiwu_gen.h: Moved the generator definition to API. Changed the fields to 'double'. * src/iiwu_mod.c: Moved the modulator definitions to API. Changed the data type of amount to 'double'. * src/iiwu_voice.c (iiwu_voice_write): The condition, that quits a voice, when the amplitude falls below a threshold now uses only the volume envelope instead of the voice amplitude. Previously, turning a volume pedal briefly to 0 would quit all voices playing. * src/iiwu_rev.c (iiwu_revmodel_processreplace): Removed 'dry' path from reverb unit Motivation: This saves a couple of multiplications, the dry signal goes through the ordinary output anyway. * src/iiwusynth.h (iiwu_synth_kill_by_exclusive_class): added to API * src/iiwu_synth.c (iiwu_synth_kill_by_exclusive_class): Extended the exclusive class function to work with stereo samples (iiwu_synth_set_reverb): Renamed iwu_synth_set_reverb to iiwu_synth_set_reverb_preset iiwu_synth_set_reverb is now an API function, that allows to set all reverb parameters. 2002-05-18 Markus Nentwig * src/iiwu_chorus.c: Implemented variable delay line with bandlimited interpolation. Documentation, error handling. Removed unneeded and broken features * src/iiwusynth_priv.h: Moved typedef struct iiwu_mod_t iiwu_mod_t into iiwusynth.h * src/iiwusynth.h: Moved iiwu_voice_add_mod from iiwu_voice.h into iiwusynth.h (now API function). * src/iiwu_voice.c (iiwu_voice_update_param): Inserted chorus send into DSP loop (iiwu_voice_write): Added flag 'voice->update_filter'. Now Q can be modulated. 2002-05-12 Markus Nentwig * src/iiwu_synth.c (iiwu_synth_pitch_wheel_sens): added * src/iiwu_chan.c (iiwu_channel_pitch_wheel_sens): added * src/iiwu_cmd.c (iiwu_handle_reverbsetwidth): changed 'wet' to * 'width' 2002-05-11 Markus Nentwig * src/iiwu_conv.c (iiwu_tc2sec): Added more conversion functions with range check for different ranges: (iiwu_tc2sec_attack): (iiwu_tc2sec_hold): (iiwu_tc2sec_release): * src/iiwu_voice.c (iiwu_voice_add_mod): implemented modulator src 0 (constant mod offset) * src/iiwu_voice.c (iiwu_voice_update_param): sample-and envelope related voice parameters are now handled together with other voice parameters. Implemented generators: GEN_KEYTOVOLENVDECAY GEN_KEYTOVOLENVHOLD GEN_KEYTOMODENVDECAY GEN_KEYTOMODENVHOLD 2002-05-10 Peter Hanappe * src/iiwu_synth.c (iiwu_synth_start_voice): added iiwu_synth_start_voice() to handle exclusive classes. 2002-05-09 Peter Hanappe * src/iiwu_conv.h: removed velocity to cB conversion. No longer used. * src/iiwu_synth.c (iiwu_synth_write_float): removed limiter * src/iiwu_synth.h (IIWU_NUM_CHANNELS): set the number of channels to 64. * src/iiwu_synth.c (iiwu_synth_get_internal_bufsize): added * src/iiwu_ladspa.h: lower-cased ladspa files * src/iiwusynth.h: prefixed log levels with IIWU_... Updated all references. * src/iiwu_cmd.c (iiwu_handle_reverb): renamed 'rev_enable' to 'reverb' in correspondance with the long command line arguments * src/iiwusynth.c (main): checking if files on command line are valid * src/iiwuplay.c (main): checking if files on command line are valid * src/iiwusynth.h: New log level for verbose messages: IIWU_INFO 2002-04-30 Markus Nentwig * src: Added iiwu_LADSPA.c, iiwu_LADSPA.h (support for LADSPA effect plugins). * src/iiwusynth.c (main): Changed default gain to 0.2. * src/iiwu_voice.c: Restructured the voice initialization as follows: (iiwu_voice_init): sample position, IIR filter history, envelopes etc. are reset. (iiwu_voice_optimize): The generators (nominal value) have been set by the sound font. Now each modulator is calculated once to obtain the 'final' initial value for each generator, which consists of nominal value and modulator-contributed part. (iiwu_voice_update_param): Calculates all voice parameters, which depend on one particular generator. This is called once for each voice parameter during voice_optimize and further each time, when a modulator changes a generator. (iiwu_voice_update_param): Added a voice parameter filter_gain to avoid recalculating the filter gain each time the center frequency changes (it depends only on Q) (iiwu_voice_write): Voice is now turned off, when the amplitude falls below -100 dB, even during the sustain phase (happens, when holding a piano key for a very long time) * src/iiwu_voice.c (iiwu_voice_noteoff): Moved voice->chan = NO_CHANNEL into iiwu_voice_off. Previously a released note was not modulated anymore, for example pitch bend stopped working as soon as the key was released. * src/iiwu_voice.h: Changed _ON macro to figure out the state of a key from the position in the envelope, instead of using a cleared channel number as indicator. * src/iiwu_synth.c: Implemented all default modulators Added LADSPA support. Added digital clipping. Moved master gain factor ahead of LADSPA Fx. * src/iiwu_mod.c: 'Hardcoded' GM default modulator vel => filter. Replaced 128 with 127 in (127-x) * src/iiwu_midi.c: Fixed sysex for realtime MIDI. Fixed pitch bend bug. * src/iiwu_gen.c (iiwu_gen_set_default_values): Using float instead of int for default values. Added references to specifications (doc). Changed 'init array' function name to 'set_default' . * src/iiwu_defsfont.c (iiwu_preset_zone_import_sfont): Import of modulators (iiwu_inst_zone_import_sfont): Import of modulators (iiwu_defpreset_noteon): Added modulators, fixed generator problem (local zone overwrites global zone, previously it added) * src/iiwu_conv.c: Using now oncave / convex equation from SF specs. Removed ct2hz functions and tables. (iiwu_ct2hz): Limit checking (iiwu_cb2amp): Removed 'magic number' (iiwu_tc2sec): Avoided == for iiwu_real_t * src/iiwu_cmd.c: Increased number of tokens. Using WORKLINELENGTH constant. Changed max. gain to 5. Added LADSPA commands. Renamed misleading rev_bypass command to rev_enable * src/iiwu_chan.c: Centered pitch wheel. Added 'expression' modulator (CC 11). * configure.in: Added LADSPA support 2002-04-03 Peter Hanappe * src/iiwu_voice.c (iiwu_voice_run_dsp): Integrated Markus Nentwig's new filter design 2002-03-12 Peter Hanappe * src/iiwusynth.h: the preset iteration in a soundfont now takes a pointer to a preset structure * src/iiwu_sys.c (iiwu_profile_data): added support for profiling * src/iiwu_voice.c (iiwu_voice_write): turns off voice if amplitude < -100 dB in release phase. Set filter gain back to old value (0.25f * ...) * src/iiwuplay.c (main): added gain, interactive, and reverb options * src/iiwusynth.c (main): added gain and reverb options * src/iiwu_synth.c (iiwu_synth_write_s16): added brickwall limiter for s16 samples 2002-01-29 Stephane Letz * src/iiwu_midishare.c : Compilation on MacOSX, use a task for typeNote management * src/iiwu_sys.c : Compilation on MacOSX * src/iiwu_sys.h : Compilation on MacOSX * src/iiwu_sfont.c : Use the flag MACINTOSH instead of MACOS * config_macos.h : Cleanup * config_macosx.h : New file, compilation on MacOSX 2002-01-21 Stephane Letz * src/iiwu_midi.c (delete_iiwu_midi_handler): Desallocation of heap allocated strings * src/iiwusynth_priv.h : Definition of strdup if not available (Macintosh) 2002-01-16 Peter Hanappe * src/iiwu_alsa.c (new_iiwu_alsa_seq_driver): Applied and adjusted Bob Ham's patch: support for configurable ALSA sequencer client name. * src/iiwu_chan.c (iiwu_channel_cc): Applied Bob Ham's patch: added bank select midi message. 2001-12-31 Peter Hanappe * src/iiwu_synth.c (iiwu_synth_damp_voices): Sustain messages are now handled. Updated iiwu_channel and iiwu_voice. (delete_iiwu_synth): SoundFonts are deleted. 2001-12-21 Stephane Letz * src/iiwu_midishare.c (new_iiwu_midishare_midi_driver, delete_iiwu_midishare_midi_driver): Updated to be compiled either in driver or application mode with the flag MIDISHARE_DRIVER. 2001-12-20 Stephane Letz * src/iiwu_portaudio.c (iiwu_portaudio_run , new_iiwu_portaudio_driver): Adaptation for new audio drivers * src/iiwu_synth.c (audio driver definition): Adaptation for PortAudio driver * src/iiwu_sys.c (header): Adaptation for compilation on MacOS9 * src/iiwu_sys.h (header): Adaptation for compilation on MacOS9 2001-12-16 Peter Hanappe * src/iiwuplay.c (main): The .iiwusynth file is loaded *before* the soundfonts on the command lines are loaded * src/iiwusynth.c (main): idem. 2001-12-16 Peter Hanappe * src/iiwu_midi.c (iiwu_player_callback): Fixed error in midi timing after a tempo change * src/iiwu_jack.c (new_iiwu_jack_audio_driver): Added first version of JACK driver 2001-12-14 Peter Hanappe * src/iiwu_synth.c (iiwu_synth_noteoff): noteon/notoff events can print a clear message, useful for debugging. * src/iiwu_sys.c (struct _iiwu_timer_t ): timer moved from iiwu_midi.c to iiwu_sys.c * src/iiwusynth.h: New organization of settings; using bit flags; added verbose option * src/iiwusynth.c (main): Added the verbose option * src/iiwuplay.c (main): Added the verbose option 2001-10-05 Stephane Letz * src/iiwu_portaudio.c (new_iiwu_portaudio_driver): imported new driver for the PortAudio library. 2001-10-04 Stephane Letz * src/iiwu_synth.c (new_iiwu_synth): Fixed bug in synth initialisation 2001-10-02 Peter Hanappe * src/iiwu_cmd.c (iiwu_get_userconf): returns default user configuration (iiwu_get_sysconf): returns default system configuration (iiwu_synth_cmdline): Fixed bug with argument offset. Empty lines are skipped correctly. * src/iiwusynth.c (main): loads the user or system config * src/iiwuplay.c (main): loads the user or system config * src/iiwu_synth.c (iiwu_sp_write): Using new envelope model for modulation envelope 2001-09-29 Peter Hanappe * src/iiwu_synth.c (iiwu_sp_write): redesigned the envelopes. 2001-09-20 Peter Hanappe * src/iiwu_synth.c (iiwu_sp_write): redesigned the dsp loop. it's faster and it sounds better (!) 2001-09-19 Peter Hanappe * src/iiwu_sfont.c (iiwu_sample_import_sfont): better checking for minimum sample size, loop start and loop end offsets. 2001-09-17 Peter Hanappe * src/iiwu_synth.c (iiwu_sp_write): improved calculation of filter coefficients (new_iiwu_synth): using settings structure 2001-09-09 Peter Hanappe * src/iiwu_synth.h (iiwu_phase_decr): fixed bug * src/iiwu_synth.c (iiwu_synth_noteoff): noteoff now turns off the oldest note only (instead of all notes with the given channel and key) 2001-07-10 Peter Hanappe * src/iiwu_midi.h: removed midi driver join function. updated all structures, implementations and callers. 2001-07-04 Peter Hanappe * src/iiwuplay.c (print_help): corrected errors in the help and usage display. 2001-06-29 Peter Hanappe * src/iiwu_synth.c (iiwu_synth_one_block): new function. fills the buffer with fresh samples. (iiwu_synth_write_lr): now calls iiwu_synth_one_block. the synthesizer uses fixed synthesis buffer size, independent of the requested buffer length passed to iiwu_synth_write_lr. (iiwu_revmodel_processreplace): new uses fixed IIWU_BUFSIZE value for buffer length. (iiwu_revmodel_processmix): uses fixed IIWU_BUFSIZE value 2001-06-22 Peter Hanappe * src/iiwusynth.c (iiwu_handle_fonts): new shell command to list the loaded fonts. (iiwu_handle_mstat): new shell command to list the statistics of the midi driver. 2001-06-19 Peter Hanappe * src/iiwusynth.c (main): Several command line options are available to select the midi and audio driver and device. Using the getopt function on posix machines. 2001-06-16 Peter Hanappe * src/iiwu_synth.h: new iiwu_revmodel_presets_t structure to store reverb presets (concert hall, room, ...) * src/iiwu_synth.c (iiwu_synth_write_lr): now using 1 reverb for all synthesis processes. the synthesis processes now receive a left and right buffer, a reverb buffer, a chorus buffer, and a monobuffer for their temporarry storage. reverb now always on. (new_iiwu_sp): no longer allocating a reverb module nor a monobuf. only one reverb model and monobuffer allocated by the synth object (read: much less memory usage). * src/iiwu_midi.c (iiwu_player_callback): fixed timing errors. midi should play correctly now. 2001-06-09 Peter Hanappe * src/iiwu_synth.c (iiwu_sp_write_lr): now using a 64-bits fixed-point number to calculate the phase of the wavetable. because of rounding erros, the float value I used before gave terrible tuning problems. I updated all the intepolation macros. * src/iiwusynth_priv.h: included the iiwu_phase_t data type. This type represents a 64-bits fixed-point number. It's used to hold the phase in the wavetable. 2001-06-08 Peter Hanappe * src/iiwu_midi.c (new_iiwu_midi_handler): Better support for runtime selection of the MIDI driver (using the iiwu_mdriver_definition_t structure) * src/iiwu_auport.c (new_iiwu_auport): Better support for runtime selection of the audio driver (using the iiwu_adriver_definition_t structure) 2001-06-07 Peter Hanappe * src/iiwu_synth.c (iiwu_sp_write_lr): rewrote the dsp function to accept a seperate left and right channel buffer. (iiwu_sp_write_lr): using cubic hermite interpolation by default. (iiwu_synth_write_lr): added a dsp function to accept a seperate left and right channel buffer. 2001-05-26 Peter Hanappe * src/iiwu_midi.c (iiwu_midi_parser_parse): Fixed a bug in the midi parser (running status should not be split in a status and channel part for system messages). (iiwu_midi_send_event): pitch bend events are now handled 2001-05-25 Peter Hanappe * src/iiwu_midi.c (iiwu_midi_file_getc): Fixed bug when pushed back byte equals zero (mf->c >= 0) * src/iiwu_midi.c (iiwu_midi_file_getc): Fixed bug when pushed back byte equals zero (mf->c >= 0) 2001-05-24 Peter Hanappe * src/iiwusynth.c: added the stupidly simple interpreter * src/iiwu_synth.c: removed all param strcutures. * src/iiwu_synth.c (iiwu_channel_get_banknum): new function 2001-05-23 Peter Hanappe * src/iiwu_synth.c (iiwu_sp_write): Fixed devide by zero in filter * src/smurf.c (gerr): applied Josh's patch: using va_list now (as it should). 2001-05-22 Peter Hanappe * src/iiwu_midi.c: the midi handler is now devided in a dummy iiwu_midi_handler_t and a "low level" driver. This allows for multiple midi drivers to be compiled in. * src/iiwusynth.h: renamed iiwu_midi_driver_t to iiwu_midi_handler_t * src/iiwu_auport.c (new_iiwu_auport): new "driver" argument to select between alsa, oss, midishare, directx, ... * configure.in: preparing for the first pre-release, version 0.0.1 fluidsynth-2.1.1/LICENSE000066400000000000000000000635351362231004000147140ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 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. (This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.) Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. 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. {description} Copyright (C) {year} {fullname} This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; 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. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. {signature of Ty Coon}, 1 April 1990 Ty Coon, President of Vice That's all there is to it! fluidsynth-2.1.1/README.cmake.md000066400000000000000000000063001362231004000162300ustar00rootroot00000000000000## For users - how to compile FluidSynth The latest information on how to compile FluidSynth using the cmake build system can be found in our wiki: https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake ## For developers - how to add a new feature to the CMake build system Let's explain this issue with an example. We are adding D-Bus support to FluidSynth as an optional feature, conditionally adding source files that require this feature. The first step is to add a macro "option()" to the main CMakeLists.txt file, the one that is located at the fluidsynth root directory. file CMakeLists.txt, line 64: ```cmake option ( enable-dbus "compile DBUS support (if it is available)" on ) ``` Now, let's check if the dbus-1 library and headers are installed, using pkg-config: file CMakeLists.txt, lines 371-377: ```cmake unset ( DBUS_SUPPORT CACHE ) if ( enable-dbus ) pkg_check_modules ( DBUS dbus-1>=1.0.0 ) set ( DBUS_SUPPORT ${DBUS_FOUND} ) else ( enable-dbus ) unset_pkg_config ( DBUS ) endif ( enable-dbus ) ``` The first line clears the value of the CMake variable DBUS_SUPPORT. If the value of the option "enable-dbus" is true, then the macro pkg_check_modules() is used to test a package named "dbus-1" with version 1.0.0 or later. This macro automatically defines the variables DBUS_LIBRARIES, DBUS_INCLUDEDIR, DBUS_FOUND and others. The value of the last one is assigned to our variable DBUS_SUPPORT for later use. There is a report to summarize the performed checks and the enabled features after the configuration steps, so let's add a line in this report regarding the D-Bus support. file cmake_admin/report.cmake, lines 14-18: ```cmake if ( DBUS_SUPPORT ) message ( "D-Bus: yes" ) else ( DBUS_SUPPORT ) message ( "D-Bus: no" ) endif ( DBUS_SUPPORT ) ``` The variable DBUS_SUPPORT is available for the CMake files, but we want to make it available to the compilers as well, to conditionally build code using "#ifdef DBUS_SUPPORT". This can be done adding a line to the config.cmake file: file src/config.cmake, lines 22-23: ```c /* Define if D-Bus support is enabled */ #cmakedefine DBUS_SUPPORT @DBUS_SUPPORT@ ``` The file config.cmake will be processed at configure time, producing a header file "config.h" in the build directory with this content, if the dbus support has been enabled and found: ```c /* Define if D-Bus support is enabled */ #define DBUS_SUPPORT 1 ``` Finally, we can add the new source files to the build system for the compiler target with the macro add_library(), and the libraries for the linker target with the macros link_directories() and target_link_libraries(). file src/CMakeLists.txt, lines 57-60 ```cmake if ( DBUS_SUPPORT ) set ( fluid_dbus_SOURCES fluid_rtkit.c fluid_rtkit.h ) include_directories ( ${DBUS_INCLUDEDIR} ${DBUS_INCLUDE_DIRS} ) endif ( DBUS_SUPPORT ) ``` file src/CMakeLists.txt, lines 163-197 ```cmake link_directories ( ... ${DBUS_LIBDIR} ${DBUS_LIBRARY_DIRS} ) add_library ( libfluidsynth ... ${fluid_dbus_SOURCES} ... ) ``` file src/CMakeLists.txt, lines 163-197 ```cmake target_link_libraries ( libfluidsynth ... ${DBUS_LIBRARIES} ... ) ``` fluidsynth-2.1.1/README.md000066400000000000000000000131141362231004000151520ustar00rootroot00000000000000# FluidSynth | Build Status | glib < 2.30 | glib >= 2.30 | |---|---|---| | **Linux** | n.a. | [![Build Status Travis](https://travis-ci.org/FluidSynth/fluidsynth.svg?branch=master)](https://travis-ci.org/FluidSynth/fluidsynth/branches) | | **FreeBSD** | n.a. | [![Build Status](https://api.cirrus-ci.com/github/FluidSynth/fluidsynth.svg?branch=master)](https://cirrus-ci.com/github/FluidSynth/fluidsynth) | | **Windows** | [![Build Status](https://dev.azure.com/tommbrt/tommbrt/_apis/build/status/FluidSynth.fluidsynth.Win?branchName=master)](https://dev.azure.com/tommbrt/tommbrt/_build/latest?definitionId=3&branchName=master) | [![Build status](https://ci.appveyor.com/api/projects/status/anbmtebt5uk4q1it/branch/master?svg=true)](https://ci.appveyor.com/project/derselbst/fluidsynth-g2ouw/branch/master) | | **MacOSX** | n.a. | [![Build Status](https://dev.azure.com/tommbrt/tommbrt/_apis/build/status/FluidSynth.fluidsynth.macOS?branchName=master)](https://dev.azure.com/tommbrt/tommbrt/_build/latest?definitionId=5&branchName=master) | | **Android** | n.a. | [![CircleCI](https://circleci.com/gh/FluidSynth/fluidsynth/tree/master.svg?style=shield)](https://circleci.com/gh/FluidSynth/fluidsynth) | #### FluidSynth is a cross-platform, real-time software synthesizer based on the Soundfont 2 specification. FluidSynth generates audio by reading and handling MIDI events from MIDI input devices by using a [SoundFont](https://github.com/FluidSynth/fluidsynth/wiki/SoundFont). It is the software analogue of a MIDI synthesizer. FluidSynth can also play MIDI files. [![OHLOH Project Stats](https://www.openhub.net/p/fluidsynth/widgets/project_thin_badge?format=gif)](https://www.openhub.net/p/fluidsynth) ## Documentation The central place for documentation and further links is our **wiki** here at GitHub: **https://github.com/FluidSynth/fluidsynth/wiki** If you are missing parts of the documentation, let us know by writing to our mailing list. Of course, you are welcome to edit and improve the wiki yourself. All you need is an account at GitHub. Alternatively, you may send an EMail to our mailing list along with your suggested changes. Further information about the mailing list is available in the wiki as well. Latest information about FluidSynth is also available on the web site at http://www.fluidsynth.org/. ## License The source code for FluidSynth is distributed under the terms of the [GNU Lesser General Public License](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html), see the [LICENSE](https://github.com/FluidSynth/fluidsynth/blob/master/LICENSE) file. To better understand the conditions how FluidSynth can be used in e.g. commercial or closed-source projects, please refer to the [LicensingFAQ in our wiki](https://github.com/FluidSynth/fluidsynth/wiki/LicensingFAQ). ## Building from source For information on how to build FluidSynth from source, please [refer to our wiki](https://github.com/FluidSynth/fluidsynth/wiki/BuildingWithCMake). ## Links - FluidSynth's Home Page, http://www.fluidsynth.org - FluidSynth's wiki, https://github.com/FluidSynth/fluidsynth/wiki - FluidSynth's API documentation, http://www.fluidsynth.org/api/ --- ## Historical background ### Why did we do it The synthesizer grew out of a project, started by Samuel Bianchini and Peter Hanappe, and later joined by Johnathan Lee, that aimed at developing a networked multi-user game. Sound (and music) was considered a very important part of the game. In addition, users had to be able to extend the game with their own sounds and images. Johnathan Lee proposed to use the Soundfont standard combined with intelligent use of midifiles. The arguments were: - Wavetable synthesis is low on CPU usage, it is intuitive and it can produce rich sounds - Hardware acceleration is possible if the user owns a Soundfont compatible soundcard (important for games!) - MIDI files are small and Soundfont2 files can be made small thru the intelligent use of loops and wavetables. Together, they are easier to downloaded than MP3 or audio files. - Graphical editors are available for both file format: various Soundfont editors are available on PC and on Linux (Smurf!), and MIDI sequencers are available on all platforms. It seemed like a good combination to use for an (online) game. In order to make Soundfonts available on all platforms (Linux, Mac, and Windows) and for all sound cards, we needed a software Soundfont synthesizer. That is why we developed FluidSynth. ### Design decisions The synthesizer was designed to be as self-contained as possible for several reasons: - It had to be multi-platform (Linux, macOS, Win32). It was therefore important that the code didn't rely on any platform-specific library. - It had to be easy to integrate the synthesizer modules in various environments, as a plugin or as a dynamically loadable object. I wanted to make the synthesizer available as a plugin (jMax, LADSPA, Xmms, WinAmp, Director, ...); develop language bindings (Python, Java, Perl, ...); and integrate it into (game) frameworks (Crystal Space, SDL, ...). For these reasons I've decided it would be easiest if the project stayed very focused on its goal (a Soundfont synthesizer), stayed small (ideally one file) and didn't dependent on external code. fluidsynth-2.1.1/THANKS000066400000000000000000000030761362231004000146140ustar00rootroot00000000000000For the list of authors that contributed to the code, please read the file AUTHORS. We would like to thank the Fondation Daniel Langlois for their funding. Their help made this project to get of the ground. Without it would simply not exist. Many thanks! (http://www.fondation-langlois.org) In alphabetic order: Paul Barton-Davis Samuel Bianchini Raoul Bonisch Rui Nuno Capela Jake Commander Francois Dechelle Ken Ellinwood Tim Goetze Anthony Green Josh Green Bob Ham Peter Hanappe Jezar Fernando Pablo Lopez-Lezcano Johnathan Lee Stephane Letz Ebrahim Mayat Sven Meier Juergen Mueller Markus Nentwig David Olofson Sergey Pavlishin Dave Phillips Daniel Pressnitzer Gerald Pye Norbert Schnell Joshua Scholar Antoine Schmitt Werner Schweer Stephan Tassart Martin Uddén fluidsynth-2.1.1/TODO000066400000000000000000000024471362231004000143720ustar00rootroot00000000000000New features ------------ - Audio level metering - Active voice count monitoring Synthesis --------- - Improve voice stealing algorithm - Dynamic voice killing (based on CPU usage) - Batch voice activation (stereo synch. as per SoundFont spec) - Pitch control on stereo samples not managed as should Drivers ------- - libao audio output driver - Windows DirectMusic component - ASIO driver - DirectSound 3D and EAX Bugs to mash ------------ - Investigate why MIDI rendering causes burst of notes at start Documentation ------------- - Write documentation on tuning - User and system configuration file Misc ---- - Remove dependency of settings on audio driver and other (see fluid_settings_init()) - set loop on/off on a sample (set_gen GEN_SAMPLEMODE?) FluidSynth Next Generation -------------------------------------------- Top of the list - 3D audio output MIDI player - Add API to manipulate and query MIDI file list - generalize use of fluid_event_t, remove fluid_midi_event_t Shell & command handler - Add "note" command that plays a note with a duration (sequencer) - MIDI file player commands (load/play/stop) - Allow settings to be loaded before the synthesizer is created MIDI Specs - sample dump - MIDI thru - Scalable Polyphony MIDI (SP-MIDI) Unsorted - rewrite midi file using new sequencer fluidsynth-2.1.1/cmake_admin/000077500000000000000000000000001362231004000161235ustar00rootroot00000000000000fluidsynth-2.1.1/cmake_admin/CheckDIRSymbolExists.cmake000066400000000000000000000066671362231004000231060ustar00rootroot00000000000000# - Check if the DIR symbol exists like in AC_HEADER_DIRENT. # CHECK_DIRSYMBOL_EXISTS(FILES VARIABLE) # # FILES - include files to check # VARIABLE - variable to return result # # This module is a small but important variation on CheckSymbolExists.cmake. # The symbol always searched for is DIR, and the test programme follows # the AC_HEADER_DIRENT test programme rather than the CheckSymbolExists.cmake # test programme which always fails since DIR tends to be typedef'd # rather than #define'd. # # The following variables may be set before calling this macro to # modify the way the check is run: # # CMAKE_REQUIRED_FLAGS = string of compile command line flags # CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) # CMAKE_REQUIRED_INCLUDES = list of include directories # CMAKE_REQUIRED_LIBRARIES = list of libraries to link MACRO(CHECK_DIRSYMBOL_EXISTS FILES VARIABLE) IF(NOT DEFINED ${VARIABLE}) SET(CMAKE_CONFIGURABLE_FILE_CONTENT "/* */\n") SET(MACRO_CHECK_DIRSYMBOL_EXISTS_FLAGS ${CMAKE_REQUIRED_FLAGS}) IF(CMAKE_REQUIRED_LIBRARIES) SET(CHECK_DIRSYMBOL_EXISTS_LIBS "-DLINK_LIBRARIES:STRING=${CMAKE_REQUIRED_LIBRARIES}") ELSE(CMAKE_REQUIRED_LIBRARIES) SET(CHECK_DIRSYMBOL_EXISTS_LIBS) ENDIF(CMAKE_REQUIRED_LIBRARIES) IF(CMAKE_REQUIRED_INCLUDES) SET(CMAKE_DIRSYMBOL_EXISTS_INCLUDES "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") ELSE(CMAKE_REQUIRED_INCLUDES) SET(CMAKE_DIRSYMBOL_EXISTS_INCLUDES) ENDIF(CMAKE_REQUIRED_INCLUDES) FOREACH(FILE ${FILES}) SET(CMAKE_CONFIGURABLE_FILE_CONTENT "${CMAKE_CONFIGURABLE_FILE_CONTENT}#include <${FILE}>\n") ENDFOREACH(FILE) SET(CMAKE_CONFIGURABLE_FILE_CONTENT "${CMAKE_CONFIGURABLE_FILE_CONTENT}\nint main()\n{if ((DIR *) 0) return 0;}\n") CONFIGURE_FILE("${CMAKE_ROOT}/Modules/CMakeConfigurableFile.in" "${CMAKE_BINARY_DIR}/CMakeFiles/CMakeTmp/CheckDIRSymbolExists.c" @ONLY) MESSAGE(STATUS "Looking for DIR in ${FILES}") TRY_COMPILE(${VARIABLE} ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeTmp/CheckDIRSymbolExists.c COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_DIRSYMBOL_EXISTS_FLAGS} "${CHECK_DIRSYMBOL_EXISTS_LIBS}" "${CMAKE_DIRSYMBOL_EXISTS_INCLUDES}" OUTPUT_VARIABLE OUTPUT) IF(${VARIABLE}) MESSAGE(STATUS "Looking for DIR in ${FILES} - found") SET(${VARIABLE} 1 CACHE INTERNAL "Have symbol DIR") FILE(APPEND ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeOutput.log "Determining if the DIR symbol is defined as in AC_HEADER_DIRENT " "passed with the following output:\n" "${OUTPUT}\nFile ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeTmp/CheckDIRSymbolExists.c:\n" "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n") ELSE(${VARIABLE}) MESSAGE(STATUS "Looking for DIR in ${FILES} - not found.") SET(${VARIABLE} "" CACHE INTERNAL "Have symbol DIR") FILE(APPEND ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeError.log "Determining if the DIR symbol is defined as in AC_HEADER_DIRENT " "failed with the following output:\n" "${OUTPUT}\nFile ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeTmp/CheckDIRSymbolExists.c:\n" "${CMAKE_CONFIGURABLE_FILE_CONTENT}\n") ENDIF(${VARIABLE}) ENDIF(NOT DEFINED ${VARIABLE}) ENDMACRO(CHECK_DIRSYMBOL_EXISTS) fluidsynth-2.1.1/cmake_admin/CheckPrototypeExists.cmake000066400000000000000000000023741362231004000232760ustar00rootroot00000000000000# - Check if the prototype for a function exists. # CHECK_PROTOTYPE_EXISTS (FUNCTION HEADER VARIABLE) # # FUNCTION - the name of the function you are looking for # HEADER - the header(s) where the prototype should be declared # VARIABLE - variable to store the result # # The following variables may be set before calling this macro to # modify the way the check is run: # # CMAKE_REQUIRED_FLAGS = string of compile command line flags # CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) # CMAKE_REQUIRED_INCLUDES = list of include directories # Copyright (c) 2006, Alexander Neundorf, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. INCLUDE(CheckCSourceCompiles) MACRO (CHECK_PROTOTYPE_EXISTS _SYMBOL _HEADER _RESULT) SET(_INCLUDE_FILES) FOREACH (it ${_HEADER}) SET(_INCLUDE_FILES "${_INCLUDE_FILES}#include <${it}>\n") ENDFOREACH (it) SET(_CHECK_PROTO_EXISTS_SOURCE_CODE " ${_INCLUDE_FILES} int main() { #ifndef ${_SYMBOL} int i = sizeof(&${_SYMBOL}); #endif return 0; } ") CHECK_C_SOURCE_COMPILES("${_CHECK_PROTO_EXISTS_SOURCE_CODE}" ${_RESULT}) ENDMACRO (CHECK_PROTOTYPE_EXISTS _SYMBOL _HEADER _RESULT) fluidsynth-2.1.1/cmake_admin/CheckSTDC.cmake000066400000000000000000000022651362231004000206250ustar00rootroot00000000000000message(STATUS "Checking whether system has ANSI C header files") include(CheckPrototypeExists) include(CheckIncludeFiles) check_include_files("dlfcn.h;stdint.h;stddef.h;inttypes.h;stdlib.h;strings.h;string.h;float.h" StandardHeadersExist) if(StandardHeadersExist) check_prototype_exists(memchr string.h memchrExists) if(memchrExists) check_prototype_exists(free stdlib.h freeExists) if(freeExists) message(STATUS "ANSI C header files - found") set(STDC_HEADERS 1 CACHE INTERNAL "System has ANSI C header files") set(HAVE_STRINGS_H 1) set(HAVE_STRING_H 1) set(HAVE_FLOAT_H 1) set(HAVE_STDLIB_H 1) set(HAVE_STDDEF_H 1) set(HAVE_STDINT_H 1) set(HAVE_INTTYPES_H 1) endif(freeExists) endif(memchrExists) endif(StandardHeadersExist) if(NOT STDC_HEADERS) message(STATUS "ANSI C header files - not found") set(STDC_HEADERS 0 CACHE INTERNAL "System has ANSI C header files") endif(NOT STDC_HEADERS) check_include_files(unistd.h HAVE_UNISTD_H) include(CheckDIRSymbolExists) check_dirsymbol_exists("sys/stat.h;sys/types.h;dirent.h" HAVE_DIRENT_H) if (HAVE_DIRENT_H) set(HAVE_SYS_STAT_H 1) set(HAVE_SYS_TYPES_H 1) endif (HAVE_DIRENT_H) fluidsynth-2.1.1/cmake_admin/DefaultDirs.cmake000066400000000000000000000074171362231004000213440ustar00rootroot00000000000000# Several directory names used by FluidSynth to install files # the variable names are similar to the KDE4 build system # DEFAULT_SOUNDFONT - automatically loaded in some use cases if ( WIN32 ) set (DEFAULT_SOUNDFONT "C:\\\\soundfonts\\\\default.sf2" CACHE STRING "Default soundfont file") else ( WIN32 ) set (DEFAULT_SOUNDFONT "${CMAKE_INSTALL_PREFIX}/share/soundfonts/default.sf2" CACHE STRING "Default soundfont file") endif ( WIN32 ) mark_as_advanced (DEFAULT_SOUNDFONT) # BUNDLE_INSTALL_DIR - Mac only: the directory for application bundles set (BUNDLE_INSTALL_DIR "/Applications" CACHE STRING "The install dir for application bundles") mark_as_advanced (BUNDLE_INSTALL_DIR) # FRAMEWORK_INSTALL_DIR - Mac only: the directory for framework bundles set (FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "The install dir for framework bundles") mark_as_advanced (FRAMEWORK_INSTALL_DIR) # BIN_INSTALL_DIR - the directory where executables will be installed set (BIN_INSTALL_DIR "bin" CACHE STRING "The install dir for executables") mark_as_advanced (BIN_INSTALL_DIR) # SBIN_INSTALL_DIR - the directory where system executables will be installed set (SBIN_INSTALL_DIR "sbin" CACHE STRING "The install dir for system executables") mark_as_advanced (SBIN_INSTALL_DIR) # LIB_INSTALL_DIR - the directory where libraries will be installed set (LIB_INSTALL_DIR "lib${LIB_SUFFIX}" CACHE STRING "The install dir for libraries") mark_as_advanced (LIB_INSTALL_DIR) # INCLUDE_INSTALL_DIR - the install dir for header files set (INCLUDE_INSTALL_DIR "include" CACHE STRING "The install dir for headers") mark_as_advanced (INCLUDE_INSTALL_DIR) # DATA_INSTALL_DIR - the base install directory for data files set (DATA_INSTALL_DIR "share" CACHE STRING "The base install dir for data files") mark_as_advanced (DATA_INSTALL_DIR) # DOC_INSTALL_DIR - the install dir for documentation set (DOC_INSTALL_DIR "share/doc" CACHE STRING "The install dir for documentation") mark_as_advanced (DOC_INSTALL_DIR) # INFO_INSTALL_DIR - the info install dir set (INFO_INSTALL_DIR "share/info" CACHE STRING "The info install dir") mark_as_advanced (INFO_INSTALL_DIR) # MAN_INSTALL_DIR - the man pages install dir if ( CMAKE_SYSTEM_NAME MATCHES "FreeBSD|DragonFly") set (MAN_INSTALL_DIR "man/man1" CACHE STRING "The man pages install dir") else() set (MAN_INSTALL_DIR "share/man/man1" CACHE STRING "The man pages install dir") endif() mark_as_advanced (MAN_INSTALL_DIR) # SYSCONF_INSTALL_DIR - the config file install dir set (SYSCONF_INSTALL_DIR "/etc" CACHE PATH "The sysconfig install dir") mark_as_advanced (SYSCONF_INSTALL_DIR) # XDG_APPS_INSTALL_DIR - the XDG apps dir, where .desktop files are installed set (XDG_APPS_INSTALL_DIR "share/applications" CACHE STRING "The XDG apps dir") mark_as_advanced (XDG_APPS_INSTALL_DIR) # XDG_MIME_INSTALL_DIR - the XDG mimetypes install dir set (XDG_MIME_INSTALL_DIR "share/mime/packages" CACHE STRING "The install dir for the xdg mimetypes") mark_as_advanced (XDG_MIME_INSTALL_DIR) # DBUS_INTERFACES_INSTALL_DIR - the directory where dbus interfaces are # installed set (DBUS_INTERFACES_INSTALL_DIR "share/dbus-1/interfaces" CACHE STRING "The dbus interfaces install dir") mark_as_advanced (DBUS_INTERFACES_INSTALL_DIR) # DBUS_SERVICES_INSTALL_DIR - the directory where dbus services are installed set (DBUS_SERVICES_INSTALL_DIR "share/dbus-1/services" CACHE STRING "The dbus services install dir") mark_as_advanced (DBUS_SERVICES_INSTALL_DIR) # DBUS_SYSTEM_SERVICES_INSTALL_DIR - the directory where dbus system services # are installed set (DBUS_SYSTEM_SERVICES_INSTALL_DIR "share/dbus-1/system-services" CACHE STRING "The dbus system services install dir") mark_as_advanced (DBUS_SYSTEM_SERVICES_INSTALL_DIR) fluidsynth-2.1.1/cmake_admin/FindMidiShare.cmake000066400000000000000000000013301362231004000215700ustar00rootroot00000000000000# Try to find the READLINE library # MidiShare_FOUND - system has MidiShare # MidiShare_INCLUDE_DIR - MidiShare include directory # MidiShare_LIBRARIES - Libraries needed to use MidiShare if ( MidiShare_INCLUDE_DIR AND MidiShare_LIBRARIES ) set ( MidiShare_FIND_QUIETLY TRUE ) endif ( MidiShare_INCLUDE_DIR AND MidiShare_LIBRARIES ) find_path ( MidiShare_INCLUDE_DIR NAMES MidiShare.h ) find_library ( MidiShare_LIBRARIES NAMES MidiShare ) include ( FindPackageHandleStandardArgs ) find_package_handle_standard_args( MidiShare DEFAULT_MSG MidiShare_INCLUDE_DIR MidiShare_LIBRARIES ) mark_as_advanced( MidiShare_INCLUDE_DIR MidiShare_LIBRARIES ) fluidsynth-2.1.1/cmake_admin/FindOSS.cmake000066400000000000000000000020431362231004000203710ustar00rootroot00000000000000# - Find Oss # Find Oss headers and libraries. # # OSS_INCLUDE_DIR - where to find soundcard.h, etc. # OSS_FOUND - True if Oss found. FIND_PATH(LINUX_OSS_INCLUDE_DIR "linux/soundcard.h" "/usr/include" "/usr/local/include" ) FIND_PATH(SYS_OSS_INCLUDE_DIR "sys/soundcard.h" "/usr/include" "/usr/local/include" ) FIND_PATH(MACHINE_OSS_INCLUDE_DIR "machine/soundcard.h" "/usr/include" "/usr/local/include" ) SET(OSS_FOUND FALSE) if ( NOT WIN32 ) IF(LINUX_OSS_INCLUDE_DIR) SET(OSS_FOUND TRUE) SET(OSS_INCLUDE_DIR ${LINUX_OSS_INCLUDE_DIR}) SET(HAVE_LINUX_SOUNDCARD_H 1) ENDIF() IF(SYS_OSS_INCLUDE_DIR) SET(OSS_FOUND TRUE) SET(OSS_INCLUDE_DIR ${SYS_OSS_INCLUDE_DIR}) SET(HAVE_SYS_SOUNDCARD_H 1) ENDIF() IF(MACHINE_OSS_INCLUDE_DIR) SET(OSS_FOUND TRUE) SET(OSS_INCLUDE_DIR ${MACHINE_OSS_INCLUDE_DIR}) SET(HAVE_MACHINE_SOUNDCARD_H 1) ENDIF() ENDIF(NOT WIN32) MARK_AS_ADVANCED ( OSS_FOUND OSS_INCLUDE_DIR ) fluidsynth-2.1.1/cmake_admin/FindReadline.cmake000066400000000000000000000016351362231004000214560ustar00rootroot00000000000000# Try to find the READLINE library # HAVE_READLINE - system has READLINE # READLINE_INCLUDE_DIR - READLINE include directory # READLINE_LIBRARIES - Libraries needed to use READLINE if ( READLINE_INCLUDE_DIR AND READLINE_LIBRARIES ) set ( READLINE_FIND_QUIETLY TRUE ) endif ( READLINE_INCLUDE_DIR AND READLINE_LIBRARIES ) find_path ( READLINE_INCLUDE_DIR NAMES history.h readline/history.h ) find_library ( READLINE_LIBRARIES NAMES readline ) if ( READLINE_INCLUDE_DIR AND READLINE_LIBRARIES ) set ( HAVE_READLINE TRUE CACHE BOOL "Found readline header and lib" FORCE ) endif ( READLINE_INCLUDE_DIR AND READLINE_LIBRARIES ) include ( FindPackageHandleStandardArgs ) FIND_PACKAGE_HANDLE_STANDARD_ARGS( READLINE DEFAULT_MSG READLINE_INCLUDE_DIR READLINE_LIBRARIES ) mark_as_advanced( READLINE_INCLUDE_DIR READLINE_LIBRARIES HAVE_READLINE ) fluidsynth-2.1.1/cmake_admin/FluidUnitTest.cmake000066400000000000000000000045231362231004000216740ustar00rootroot00000000000000macro ( ADD_FLUID_TEST _test ) ADD_EXECUTABLE(${_test} ${_test}.c $ ) # only build this unit test when explicitly requested by "make check" set_target_properties(${_test} PROPERTIES EXCLUDE_FROM_ALL TRUE) # import necessary compile flags and dependency libraries if ( FLUID_CPPFLAGS ) set_target_properties ( ${_test} PROPERTIES COMPILE_FLAGS ${FLUID_CPPFLAGS} ) endif ( FLUID_CPPFLAGS ) TARGET_LINK_LIBRARIES(${_test} $) # use the local include path to look for fluidsynth.h, as we cannot be sure fluidsynth is already installed target_include_directories(${_test} PUBLIC $ # include auto generated headers $ # include "normal" public (sub-)headers $ # include private headers $ # include all other header search paths needed by libfluidsynth (esp. glib) ) # add the test to ctest ADD_TEST(NAME ${_test} COMMAND ${_test}) # append the current unit test to check-target as dependency add_dependencies(check ${_test}) endmacro ( ADD_FLUID_TEST ) macro ( ADD_FLUID_DEMO _demo ) ADD_EXECUTABLE(${_demo} ${_demo}.c ) # only build this unit test when explicitly requested by "make check" set_target_properties(${_demo} PROPERTIES EXCLUDE_FROM_ALL TRUE) # import necessary compile flags and dependency libraries if ( FLUID_CPPFLAGS ) set_target_properties ( ${_demo} PROPERTIES COMPILE_FLAGS ${FLUID_CPPFLAGS} ) endif ( FLUID_CPPFLAGS ) TARGET_LINK_LIBRARIES(${_demo} libfluidsynth) # use the local include path to look for fluidsynth.h, as we cannot be sure fluidsynth is already installed target_include_directories(${_demo} PUBLIC $ # include auto generated headers $ # include "normal" public (sub-)headers $ # include all other header search paths needed by libfluidsynth (esp. glib) ) # append the current unit test to check-target as dependency add_dependencies(demo ${_demo}) endmacro ( ADD_FLUID_DEMO ) fluidsynth-2.1.1/cmake_admin/TestInline.cmake000066400000000000000000000011501362231004000212000ustar00rootroot00000000000000include ( CheckCSourceCompiles ) foreach ( _keyword "inline" "__inline__" "__inline" ) if ( NOT INLINE_KEYWORD ) set ( CMAKE_REQUIRED_DEFINITIONS "-DTESTKEYWORD=${_keyword}" ) check_c_source_compiles ( "typedef int foo_t; static TESTKEYWORD foo_t static_foo(){return 0;} foo_t foo(){return 0;} int main(int argc, char *argv[]){return 0;}" _have_${_keyword} ) if ( _have_${_keyword} ) set ( INLINE_KEYWORD ${_keyword} ) endif ( _have_${_keyword} ) endif ( NOT INLINE_KEYWORD ) endforeach ( _keyword ) fluidsynth-2.1.1/cmake_admin/TestVLA.cmake000066400000000000000000000004301362231004000204040ustar00rootroot00000000000000include ( CheckCSourceCompiles ) if ( NOT SUPPORTS_VLA ) check_c_source_compiles ( "int main(int argc, char *argv[]){int arr[argc]; return 0;}" _have_vla ) if ( _have_vla ) set ( SUPPORTS_VLA 1 ) endif ( _have_vla ) endif ( NOT SUPPORTS_VLA ) fluidsynth-2.1.1/cmake_admin/UnsetPkgConfig.cmake000066400000000000000000000010411362231004000220070ustar00rootroot00000000000000macro ( unset_pkg_config _prefix ) unset ( ${_prefix}_VERSION CACHE ) unset ( ${_prefix}_PREFIX CACHE ) unset ( ${_prefix}_CFLAGS CACHE ) unset ( ${_prefix}_CFLAGS_OTHER CACHE ) unset ( ${_prefix}_LDFLAGS CACHE ) unset ( ${_prefix}_LDFLAGS_OTHER CACHE ) unset ( ${_prefix}_LIBRARIES CACHE ) unset ( ${_prefix}_INCLUDEDIR CACHE ) unset ( ${_prefix}_INCLUDE_DIRS CACHE ) unset ( ${_prefix}_LIBDIR CACHE ) unset ( ${_prefix}_LIBRARY_DIRS CACHE ) unset ( __pkg_config_checked_${_prefix} CACHE ) endmacro ( unset_pkg_config ) fluidsynth-2.1.1/cmake_admin/VersionInfo.in000066400000000000000000000051101362231004000207110ustar00rootroot00000000000000#pragma once #ifndef PRODUCT_VERSION_MAJOR #define PRODUCT_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@ #endif #ifndef PRODUCT_VERSION_MINOR #define PRODUCT_VERSION_MINOR @PRODUCT_VERSION_MINOR@ #endif #ifndef PRODUCT_VERSION_PATCH #define PRODUCT_VERSION_PATCH @PRODUCT_VERSION_PATCH@ #endif #ifndef PRODUCT_VERSION_BUILD #define PRODUCT_VERSION_BUILD @PRODUCT_VERSION_REVISION@ #endif #ifndef FILE_VERSION_MAJOR #define FILE_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@ #endif #ifndef FILE_VERSION_MINOR #define FILE_VERSION_MINOR @PRODUCT_VERSION_MINOR@ #endif #ifndef FILE_VERSION_PATCH #define FILE_VERSION_PATCH @PRODUCT_VERSION_PATCH@ #endif #ifndef FILE_VERSION_BUILD #define FILE_VERSION_BUILD @PRODUCT_VERSION_REVISION@ #endif #ifndef __TO_STRING #define __TO_STRING_IMPL(x) #x #define __TO_STRING(x) __TO_STRING_IMPL(x) #endif #define PRODUCT_VERSION_MAJOR_MINOR_STR __TO_STRING(PRODUCT_VERSION_MAJOR) "." __TO_STRING(PRODUCT_VERSION_MINOR) #define PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR PRODUCT_VERSION_MAJOR_MINOR_STR "." __TO_STRING(PRODUCT_VERSION_PATCH) #define PRODUCT_VERSION_FULL_STR PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(PRODUCT_VERSION_BUILD) #define PRODUCT_VERSION_RESOURCE PRODUCT_VERSION_MAJOR,PRODUCT_VERSION_MINOR,PRODUCT_VERSION_PATCH,PRODUCT_VERSION_BUILD #define PRODUCT_VERSION_RESOURCE_STR PRODUCT_VERSION_FULL_STR "\0" #define FILE_VERSION_MAJOR_MINOR_STR __TO_STRING(FILE_VERSION_MAJOR) "." __TO_STRING(FILE_VERSION_MINOR) #define FILE_VERSION_MAJOR_MINOR_PATCH_STR FILE_VERSION_MAJOR_MINOR_STR "." __TO_STRING(FILE_VERSION_PATCH) #define FILE_VERSION_FULL_STR FILE_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(FILE_VERSION_BUILD) #define FILE_VERSION_RESOURCE FILE_VERSION_MAJOR,FILE_VERSION_MINOR,FILE_VERSION_PATCH,FILE_VERSION_BUILD #define FILE_VERSION_RESOURCE_STR FILE_VERSION_FULL_STR "\0" #ifndef PRODUCT_COMMENTS #define PRODUCT_COMMENTS "@PRODUCT_COMMENTS@\0" #endif #ifndef PRODUCT_COMPANY_NAME #define PRODUCT_COMPANY_NAME "@PRODUCT_COMPANY_NAME@\0" #endif #ifndef PRODUCT_COMPANY_COPYRIGHT #define PRODUCT_COMPANY_COPYRIGHT "@PRODUCT_COMPANY_COPYRIGHT@\0" #endif #ifndef PRODUCT_FILE_DESCRIPTION #define PRODUCT_FILE_DESCRIPTION "@PRODUCT_FILE_DESCRIPTION@\0" #endif #ifndef PRODUCT_INTERNAL_NAME #define PRODUCT_INTERNAL_NAME "@PRODUCT_NAME@\0" #endif #ifndef PRODUCT_ORIGINAL_FILENAME #define PRODUCT_ORIGINAL_FILENAME "@PRODUCT_ORIGINAL_FILENAME@\0" #endif #ifndef PRODUCT_BUNDLE #define PRODUCT_BUNDLE "@PRODUCT_BUNDLE@\0" #endif fluidsynth-2.1.1/cmake_admin/VersionResource.rc000066400000000000000000000020231362231004000216030ustar00rootroot00000000000000#include "VersionInfo.h" #include "winver.h" VS_VERSION_INFO VERSIONINFO FILEVERSION FILE_VERSION_RESOURCE PRODUCTVERSION PRODUCT_VERSION_RESOURCE FILEFLAGSMASK VS_FFI_FILEFLAGSMASK #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_DLL FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "Comments", PRODUCT_COMMENTS VALUE "CompanyName", PRODUCT_COMPANY_NAME VALUE "FileDescription", PRODUCT_FILE_DESCRIPTION VALUE "FileVersion", FILE_VERSION_RESOURCE_STR VALUE "InternalName", PRODUCT_INTERNAL_NAME VALUE "LegalCopyright", PRODUCT_COMPANY_COPYRIGHT VALUE "OriginalFilename", PRODUCT_ORIGINAL_FILENAME VALUE "ProductName", PRODUCT_BUNDLE VALUE "ProductVersion", PRODUCT_VERSION_RESOURCE_STR END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END fluidsynth-2.1.1/cmake_admin/cmake_uninstall.cmake.in000066400000000000000000000015551362231004000227110ustar00rootroot00000000000000IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) STRING(REGEX REPLACE "\n" ";" files "${files}") FOREACH(file ${files}) MESSAGE(STATUS "Uninstalling \"${file}\"") IF(EXISTS "${file}") EXEC_PROGRAM( "@CMAKE_COMMAND@" ARGS "-E remove \"${file}\"" OUTPUT_VARIABLE rm_out RETURN_VALUE rm_retval ) IF("${rm_retval}" STREQUAL 0) ELSE("${rm_retval}" STREQUAL 0) MESSAGE(FATAL_ERROR "Problem when removing \"${file}\"") ENDIF("${rm_retval}" STREQUAL 0) ELSE(EXISTS "${file}") MESSAGE(STATUS "File \"${file}\" does not exist.") ENDIF(EXISTS "${file}") ENDFOREACH(file) fluidsynth-2.1.1/cmake_admin/generate_product_version.cmake000066400000000000000000000102031362231004000242200ustar00rootroot00000000000000include (CMakeParseArguments) set (GenerateProductVersionCurrentDir ${CMAKE_CURRENT_LIST_DIR}) # generate_product_version() function # # This function uses VersionInfo.in template file and VersionResource.rc file # to generate WIN32 resource with version information and general resource strings. # # Usage: # generate_product_version( # SomeOutputResourceVariable # NAME MyGreatProject # ICON ${PATH_TO_APP_ICON} # VERSION_MAJOR 2 # VERSION_MINOR 3 # VERSION_PATH ${BUILD_COUNTER} # VERSION_REVISION ${BUILD_REVISION} # ) # where BUILD_COUNTER and BUILD_REVISION could be values from your CI server. # # You can use generated resource for your executable targets: # add_executable(target-name ${target-files} ${SomeOutputResourceVariable}) # # You can specify resource strings in arguments: # NAME - name of executable (no defaults, ex: Microsoft Word) # BUNDLE - bundle (${NAME} is default, ex: Microsoft Office) # ICON - path to application icon (${CMAKE_SOURCE_DIR}/product.ico by default) # VERSION_MAJOR - 1 is default # VERSION_MINOR - 0 is default # VERSION_PATCH - 0 is default # VERSION_REVISION - 0 is default # COMPANY_NAME - your company name (no defaults) # COMPANY_COPYRIGHT - ${COMPANY_NAME} (C) Copyright ${CURRENT_YEAR} is default # COMMENTS - ${NAME} v${VERSION_MAJOR}.${VERSION_MINOR} is default # ORIGINAL_FILENAME - ${NAME} is default # INTERNAL_NAME - ${NAME} is default # FILE_DESCRIPTION - ${NAME} is default function(generate_product_version outfiles) set (options) set (oneValueArgs NAME BUNDLE VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_REVISION COMPANY_NAME COMPANY_COPYRIGHT COMMENTS ORIGINAL_FILENAME INTERNAL_NAME FILE_DESCRIPTION) set (multiValueArgs) cmake_parse_arguments(PRODUCT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if (NOT PRODUCT_BUNDLE OR "${PRODUCT_BUNDLE}" STREQUAL "") set(PRODUCT_BUNDLE "${PRODUCT_NAME}") endif() # if (NOT PRODUCT_ICON OR "${PRODUCT_ICON}" STREQUAL "") # set(PRODUCT_ICON "${CMAKE_SOURCE_DIR}/product.ico") # endif() if (NOT PRODUCT_VERSION_MAJOR OR "${PRODUCT_VERSION_MAJOR}" STREQUAL "") set(PRODUCT_VERSION_MAJOR 1) endif() if (NOT PRODUCT_VERSION_MINOR OR "${PRODUCT_VERSION_MINOR}" STREQUAL "") set(PRODUCT_VERSION_MINOR 0) endif() if (NOT PRODUCT_VERSION_PATCH OR "${PRODUCT_VERSION_PATCH}" STREQUAL "") set(PRODUCT_VERSION_PATCH 0) endif() if (NOT PRODUCT_VERSION_REVISION OR "${PRODUCT_VERSION_REVISION}" STREQUAL "") set(PRODUCT_VERSION_REVISION 0) endif() if (NOT PRODUCT_COMPANY_COPYRIGHT OR "${PRODUCT_COMPANY_COPYRIGHT}" STREQUAL "") string(TIMESTAMP PRODUCT_CURRENT_YEAR "%Y") set(PRODUCT_COMPANY_COPYRIGHT "${PRODUCT_COMPANY_NAME} (C) Copyright ${PRODUCT_CURRENT_YEAR}") endif() if (NOT PRODUCT_COMMENTS OR "${PRODUCT_COMMENTS}" STREQUAL "") set(PRODUCT_COMMENTS "${PRODUCT_NAME} v${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}") endif() if (NOT PRODUCT_ORIGINAL_FILENAME OR "${PRODUCT_ORIGINAL_FILENAME}" STREQUAL "") set(PRODUCT_ORIGINAL_FILENAME "${PRODUCT_NAME}") endif() if (NOT PRODUCT_INTERNAL_NAME OR "${PRODUCT_INTERNAL_NAME}" STREQUAL "") set(PRODUCT_INTERNAL_NAME "${PRODUCT_NAME}") endif() if (NOT PRODUCT_FILE_DESCRIPTION OR "${PRODUCT_FILE_DESCRIPTION}" STREQUAL "") set(PRODUCT_FILE_DESCRIPTION "${PRODUCT_NAME}") endif() set (_VersionInfoFile ${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.h) set (_VersionResourceFile ${CMAKE_CURRENT_BINARY_DIR}/VersionResource.rc) configure_file( ${GenerateProductVersionCurrentDir}/VersionInfo.in ${_VersionInfoFile} @ONLY) configure_file( ${GenerateProductVersionCurrentDir}/VersionResource.rc ${_VersionResourceFile} COPYONLY) list(APPEND ${outfiles} ${_VersionInfoFile} ${_VersionResourceFile}) set (${outfiles} ${${outfiles}} PARENT_SCOPE) endfunction() fluidsynth-2.1.1/cmake_admin/report.cmake000066400000000000000000000207171362231004000204470ustar00rootroot00000000000000 set ( AUDIO_MIDI_REPORT "\n" ) if ( ALSA_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} ALSA: yes\n" ) else ( ALSA_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} ALSA: no\n" ) endif ( ALSA_SUPPORT ) if ( COREAUDIO_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} CoreAudio: yes\n" ) else ( COREAUDIO_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} CoreAudio: no\n" ) endif ( COREAUDIO_SUPPORT ) if ( COREMIDI_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} CoreMIDI: yes\n" ) else ( COREMIDI_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} CoreMIDI: no\n" ) endif ( COREMIDI_SUPPORT ) if ( DSOUND_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} DSound: yes\n" ) else ( DSOUND_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} DSound: no\n" ) endif ( DSOUND_SUPPORT ) if ( JACK_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} JACK: yes\n" ) else ( JACK_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} JACK: no\n" ) endif ( JACK_SUPPORT ) if ( MIDISHARE_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} MidiShare: yes\n" ) else ( MIDISHARE_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} MidiShare: no\n" ) endif ( MIDISHARE_SUPPORT ) if ( OBOE_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} Oboe: yes\n" ) else ( OBOE_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} Oboe: no\n" ) endif ( OBOE_SUPPORT ) if ( OPENSLES_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} OpenSLES: yes\n" ) else ( OPENSLES_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} OpenSLES: no\n" ) endif ( OPENSLES_SUPPORT ) if ( DART_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} OS/2 DART: yes\n" ) else ( DART_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} OS/2 DART: no\n" ) endif ( DART_SUPPORT ) if ( OSS_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} OSS: yes\n" ) else ( OSS_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} OSS: no\n" ) endif ( OSS_SUPPORT ) if ( PORTAUDIO_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} PortAudio: yes\n" ) else ( PORTAUDIO_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} PortAudio: no\n" ) endif ( PORTAUDIO_SUPPORT ) if ( PULSE_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} PulseAudio: yes\n" ) else ( PULSE_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} PulseAudio: no\n" ) endif ( PULSE_SUPPORT ) if ( SDL2_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} SDL2: yes\n" ) else ( SDL2_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} SDL2: no\n" ) endif ( SDL2_SUPPORT ) if ( WAVEOUT_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WaveOut: yes\n" ) else ( WAVEOUT_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WaveOut: no\n" ) endif ( WAVEOUT_SUPPORT ) if ( WINMIDI_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WinMidi: yes\n" ) else ( WINMIDI_SUPPORT ) set ( AUDIO_MIDI_REPORT "${AUDIO_MIDI_REPORT} WinMidi: no\n" ) endif ( WINMIDI_SUPPORT ) set ( INPUTS_REPORT "\n" ) set ( INPUTS_REPORT "${INPUTS_REPORT}Support for SF3 files: " ) if ( LIBSNDFILE_HASVORBIS ) set ( INPUTS_REPORT "${INPUTS_REPORT}yes\n" ) elseif ( NOT LIBSNDFILE_SUPPORT ) set ( INPUTS_REPORT "${INPUTS_REPORT}no (libsndfile not found)\n" ) elseif ( NOT LIBSNDFILE_HASVORBIS ) set ( INPUTS_REPORT "${INPUTS_REPORT}no (libsndfile has no ogg vorbis support)\n" ) endif ( LIBSNDFILE_HASVORBIS ) set ( INPUTS_REPORT "${INPUTS_REPORT}Support for DLS files: " ) if ( LIBINSTPATCH_SUPPORT ) set ( INPUTS_REPORT "${INPUTS_REPORT}yes\n" ) else ( LIBINSTPATCH_SUPPORT ) set ( INPUTS_REPORT "${INPUTS_REPORT}no (libinstpatch not found)\n" ) endif ( LIBINSTPATCH_SUPPORT ) set ( RENDERING_REPORT "\n" ) if ( AUFILE_SUPPORT ) set ( RENDERING_REPORT "${RENDERING_REPORT}Audio to file rendering: yes\n" ) else ( AUFILE_SUPPORT ) set ( RENDERING_REPORT "${RENDERING_REPORT}Audio to file rendering: no\n" ) endif ( AUFILE_SUPPORT ) if ( LIBSNDFILE_SUPPORT ) set ( RENDERING_REPORT "${RENDERING_REPORT} libsndfile: yes\n" ) else ( LIBSNDFILE_SUPPORT ) set ( RENDERING_REPORT "${RENDERING_REPORT} libsndfile: no (RAW PCM rendering only)\n" ) endif ( LIBSNDFILE_SUPPORT ) set ( MISC_REPORT "\nMiscellaneous support:\n" ) if ( DBUS_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} D-Bus: yes\n" ) else ( DBUS_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} D-Bus: no\n" ) endif ( DBUS_SUPPORT ) if ( LADSPA_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} LADSPA support: yes\n" ) else ( LADSPA_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} LADSPA support: no\n" ) endif ( LADSPA_SUPPORT ) if ( LASH_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} LASH support: yes (NOTE: GPL library)\n" ) else ( LASH_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} LASH support: no\n" ) endif ( LASH_SUPPORT ) if ( NETWORK_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} NETWORK Support: yes\n" ) else ( NETWORK_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} NETWORK Support: no\n" ) endif ( NETWORK_SUPPORT ) if ( IPV6_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} IPV6 Support: yes\n" ) else ( IPV6_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} IPV6 Support: no\n" ) endif ( IPV6_SUPPORT ) if ( WITH_READLINE ) set ( MISC_REPORT "${MISC_REPORT} Readline: yes (NOTE: GPL library)\n" ) else ( WITH_READLINE ) set ( MISC_REPORT "${MISC_REPORT} Readline: no\n" ) endif ( WITH_READLINE ) if ( SYSTEMD_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} systemd: yes\n" ) else ( SYSTEMD_SUPPORT ) set ( MISC_REPORT "${MISC_REPORT} systemd: no\n" ) endif ( SYSTEMD_SUPPORT ) set ( DEVEL_REPORT "\nDeveloper nerds info:\n" ) if ( WITH_FLOAT ) set ( DEVEL_REPORT "${DEVEL_REPORT} Samples type: float\n" ) else ( WITH_FLOAT ) set ( DEVEL_REPORT "${DEVEL_REPORT} Samples type: double\n" ) endif ( WITH_FLOAT ) if ( ENABLE_MIXER_THREADS ) set ( DEVEL_REPORT "${DEVEL_REPORT} Multithread rendering: yes\n" ) else ( ENABLE_MIXER_THREADS ) set ( DEVEL_REPORT "${DEVEL_REPORT} Multithread rendering: no\n" ) endif ( ENABLE_MIXER_THREADS ) if ( HAVE_OPENMP ) set ( DEVEL_REPORT "${DEVEL_REPORT} OpenMP 4.0: yes\n" ) else ( HAVE_OPENMP ) set ( DEVEL_REPORT "${DEVEL_REPORT} OpenMP 4.0: no\n" ) endif ( HAVE_OPENMP ) if ( WITH_PROFILING ) set ( DEVEL_REPORT "${DEVEL_REPORT} Profiling: yes\n" ) else ( WITH_PROFILING ) set ( DEVEL_REPORT "${DEVEL_REPORT} Profiling: no\n" ) endif ( WITH_PROFILING ) if ( ENABLE_DEBUG ) set ( DEVEL_REPORT "${DEVEL_REPORT} Debug Build: yes\n" ) else ( ENABLE_DEBUG ) set ( DEVEL_REPORT "${DEVEL_REPORT} Debug Build: no\n" ) endif ( ENABLE_DEBUG ) if ( ENABLE_TRAPONFPE ) set ( DEVEL_REPORT "${DEVEL_REPORT} Trap on FPE (debug): yes\n" ) else ( ENABLE_TRAPONFPE ) set ( DEVEL_REPORT "${DEVEL_REPORT} Trap on FPE (debug): no\n" ) endif ( ENABLE_TRAPONFPE ) if ( ENABLE_FPECHECK ) set ( DEVEL_REPORT "${DEVEL_REPORT} Check FPE (debug): yes\n" ) else ( ENABLE_FPECHECK ) set ( DEVEL_REPORT "${DEVEL_REPORT} Check FPE (debug): no\n" ) endif ( ENABLE_FPECHECK ) if ( ENABLE_UBSAN ) set ( DEVEL_REPORT "${DEVEL_REPORT} UBSan (debug): yes\n" ) else ( ENABLE_UBSAN ) set ( DEVEL_REPORT "${DEVEL_REPORT} UBSan (debug): no\n" ) endif ( ENABLE_UBSAN ) message( STATUS "\n**************************************************************\n" "Build Summary:\n" "Build type: " ${CMAKE_BUILD_TYPE} "\n" "Install Prefix: " ${CMAKE_INSTALL_PREFIX} "\n" "\n" "Audio / MIDI driver support:" ${AUDIO_MIDI_REPORT} ${INPUTS_REPORT} ${RENDERING_REPORT} ${MISC_REPORT} ${DEVEL_REPORT} ) message ( "**************************************************************\n\n" ) fluidsynth-2.1.1/doc/000077500000000000000000000000001362231004000144405ustar00rootroot00000000000000fluidsynth-2.1.1/doc/CMakeLists.txt000066400000000000000000000026701362231004000172050ustar00rootroot00000000000000# FluidSynth - A Software Synthesize # # Copyright (C) 2003-2010 Peter Hanappe and others. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2.1 of # the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307, USA # CMake based build system. Pedro Lopez-Cabanillas find_package ( Doxygen ) if ( DOXYGEN_FOUND ) configure_file ( Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ) add_custom_target ( doxygen ${DOXYGEN} Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endif ( DOXYGEN_FOUND ) if ( UNIX ) install ( FILES fluidsynth.1 DESTINATION ${MAN_INSTALL_DIR} ) endif ( UNIX ) include ( FluidUnitTest ) add_custom_target ( demo ) ADD_FLUID_DEMO ( example ) ADD_FLUID_DEMO ( fluidsynth_arpeggio ) ADD_FLUID_DEMO ( fluidsynth_fx ) ADD_FLUID_DEMO ( fluidsynth_metronome ) ADD_FLUID_DEMO ( fluidsynth_simple ) fluidsynth-2.1.1/doc/Doxyfile000066400000000000000000000174341362231004000161570ustar00rootroot00000000000000# Doxyfile 1.6 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = libfluidsynth PROJECT_NUMBER = 2.1.1 OUTPUT_DIRECTORY = api CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO STRIP_FROM_PATH = ../ STRIP_FROM_INC_PATH = ../include/ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = NO TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = NO EXTRACT_PRIVATE = NO EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = NO EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = NO SORT_BRIEF_DOCS = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO GENERATE_BUGLIST = NO GENERATE_DEPRECATEDLIST = YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = ../doc/fluidsynth-v20-devdoc.txt ../include ../include/fluidsynth ../src INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c *.h RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = fluid_*.h EXCLUDE_SYMBOLS = EXAMPLE_PATH = ../doc EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_EXTRA_STYLESHEET = ../doc/doxy_formula.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = ../doc/fluidsettings.xml ../doc/fluidsettings.xsl GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project HTML_DYNAMIC_SECTIONS = YES CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = YES USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = __DOXYGEN__ EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO DOT_FONTNAME = FreeSans DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO TEMPLATE_RELATIONS = YES INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 1000 DOT_TRANSPARENT = YES DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = YES fluidsynth-2.1.1/doc/Doxyfile.cmake000066400000000000000000000177461362231004000172440ustar00rootroot00000000000000# Doxyfile 1.6 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = libfluidsynth PROJECT_NUMBER = @VERSION@ OUTPUT_DIRECTORY = api CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@/ STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@/include/ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = NO TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = NO EXTRACT_PRIVATE = NO EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = NO EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = YES HIDE_UNDOC_CLASSES = YES HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = NO SORT_BRIEF_DOCS = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO GENERATE_BUGLIST = NO GENERATE_DEPRECATEDLIST = YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- INPUT = @CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt @CMAKE_SOURCE_DIR@/include @CMAKE_SOURCE_DIR@/include/fluidsynth @CMAKE_SOURCE_DIR@/src @CMAKE_BINARY_DIR@/include/fluidsynth INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c *.h RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = fluid_*.h EXCLUDE_SYMBOLS = EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/doc EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxy_formula.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project HTML_DYNAMIC_SECTIONS = YES CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 GENERATE_TREEVIEW = NO TREEVIEW_WIDTH = 250 FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4wide EXTRA_PACKAGES = LATEX_HEADER = PDF_HYPERLINKS = YES USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = __DOXYGEN__ EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO DOT_FONTNAME = FreeSans DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO TEMPLATE_RELATIONS = YES INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png DOT_PATH = DOTFILE_DIRS = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 1000 DOT_TRANSPARENT = YES DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- SEARCHENGINE = YES fluidsynth-2.1.1/doc/FluidProfile_0004.pdf000066400000000000000000011454001362231004000201670ustar00rootroot00000000000000%PDF-1.4 %쏢 163 0 obj <> endobj xref 163 18 0000000015 00000 n 0000000677 00000 n 0000000796 00000 n 0000000964 00000 n 0000007137 00000 n 0000007159 00000 n 0000007202 00000 n 0000007285 00000 n 0000007373 00000 n 0000007440 00000 n 0000007508 00000 n 0000007594 00000 n 0000007667 00000 n 0000007700 00000 n 0000007792 00000 n 0000007867 00000 n 0000007999 00000 n 0000008071 00000 n trailer <]/Prev 310799>> startxref 0 %%EOF 164 0 obj <> endobj 165 0 obj <> /Contents 166 0 R >> endobj 166 0 obj <> stream x][6~h8͈wo^_b$c{`>0_'d,?$"YJ-uOόZ D*~[E"o^Nj;8&o~ySx-8gZ~S0*66̨i8|+S9 eRR-IeR2ɵ?W`A3m:60bp.AIZ+a.bmyٖ&vKUyMWDp-ZGAoAZ_, E^Qp[T)YH: g-8./ ]̔mx봵*ϭVa ?f] r?_P ͤ1Ž@ X3k䔠gܒzJNSDVKjQm5{B_W/%6&@ZQ;กϹA_įX>8Jr/y0kF`7^rsg(?fYɑX:tJA% gE~o#@O:3Ob|އ/<Ϣ/h0=Zɓig޴M}v ߐ56bj_vM\ 5:4 pٳ*Cc;ʏ,>>ns$CRC_Vº^;,fЎb+h|o4$|_7>+VwtƠS( !!"/El"|:7 E&eQ^ - YbDaWq~p3y@9sVrq71"ο礞BV ta5t B;bɨLmɭ8:2!ڡU@1lakaOP$m?Kc8"=/WiV6Dw#>;-SmE©ǞQhA[h,=(p J٠qSf9<-f56$um]׿t.鈳:0%|wC-{d~xwΘtVPgnG8>57\oVQrKD-y%_9RN7XZhlI4>I>)礪4_TLZjbl҅oF҃ I-2'R}&^y"&gQ}D|8=ܻt"hȅz796 &RhMC*Q-$sͣ[u8 }F+ѷ8-"r^׌l@T3i7ilAZ Cd=g>ە}>+7(5 k{AqQ^vPGXެҫ_2jP, #1z@jk/B젃EЗs:˦Q3 9ATL d@B0 VSQ2ؐ8aL&L~ب!eȖijyZA#{4Rޛ!%E\\iB[a$rġ\b "`YWMS&$ǰ#yZ+NƃTnWM\Cv?>9+9m_~2p>L[ ~b]a_)Z BX9%I:cm&3BNHU;6(,i-]HАHбVR"qu/ :hl ,C=D'޶taBR֌wIOx4H=6qV NerFrDj4BÍ:g-Qs NE;DH[X#B? C1[DNi%҉#hjޘIG!1[.}9ʲп!0"ȸ?!b[ۃN ˬQu+ PV e`#JZ6? 7sP \ѦОjM>3 #wY83zߣT"*7)p^ *}(5O ~Iq3Ze95HOfk_,9^̱WyhEm5:}M0HkXQɢV#k ҅ ^no$(=׾ϦHi6{(Kg|l-2vYq$7E y)z Z5`2^dh/Z3t€2Q_ݑgj֔8}`Lrod_,$DJ(zHn7C;ɱm:=[Ev|yh 55$#['o!)v@NI͈qɫuiÞhDY;G :(9 yFK[9Cʜ0%kZYOsa" T.as;[;)j->ܚv'FUB#\S/^Z_ m)͡nK'OC%#M-xw 's?{yu^8]Fhps [ia1erJ@3C6jΜ1kUh]r#οt?Ho : W$!7dtњommL9##f㶼U/$2!9E_vX4;`}һNl./AU#}'=½ 9,G,\qɛMK$%7o 1L]%p3NWJ*:CKfCأc|!CN9cϞ3rL9zmClĨ Bj -!vQHr95%&3gW?4j'+gNG.&]>@2y*l鼷8}IcM1&L_tp_{#'INIXϊm&2F72MZ 4Ig(%}sua9iE&׋Flɔƿ%űIm_x5`Oެbo7f^ +hrKvf ?`E'$ّmQ`+*qy0%OF1N<3MinB>N^8 ڹr{u^l]d.){:6f6rt]{NgJS.ږuq c<~G?:?D endstream endobj 167 0 obj 6099 endobj 168 0 obj <>endobj 169 0 obj <> endobj 170 0 obj <> endobj 171 0 obj <> endobj 172 0 obj <> endobj 173 0 obj <> endobj 174 0 obj <> endobj 175 0 obj <> endobj 176 0 obj <> endobj 177 0 obj <> endobj 178 0 obj <> endobj 179 0 obj <> endobj 180 0 obj <> stream N ÍaCb.M7mj!lȻ'ø"B"_)"aD!Blhe2M6YLS)2e2e2I$R)% 8 %Xl x<~`sGlj\8-E7zT%Їqq:./.I8 +?P\Gmye:@q bq endstream endobj 1 0 obj <> /Contents 2 0 R >> endobj 2 0 obj <> stream xZYE ~_/n}D%"$^-{d"~{Ƴ;Yf_=*)dٍG?jjg>nA4VJ iLU9/j~8{Zm6Ĩ3-){ܴAR% >ԋi!8Sf̿W!VhrJ qV\T>j*U5r;kjԽ[U11nI'd! '~rhR +[\`OF9dL>D}6yOe$JQ{ 0]r( K ^o ޮ$UcƼosNJg,R|(T4U ^;WRlW3Rj w1!I4:kF, $*O@X*1zF) eV!YyNYqѳpɶ:|JXjcJ2KAI]gN'щo`!g6P71L(zk(_(ѝ&qgꃩ\P{*z!!~āy 9!9; @/ E+b$eú@Pub;*n HIBجwjC|@BN8ʴ:IMuMwdA \uq~-kNImEz2gsژ6∑bUJxw4E(Ƹc[턵}(?|L"H0{?bƤ 0\' JK/u?d*:l| ubrYi9k YͩeSd%db8 F-u3ʗ%w Al4(ޒȒVޑASG0\>nfP_F50xthtݽڄ^6ؙ#ނ~-NBnr[̉Y+ Nvu1o%Θ$,-Ͻ.:ը{кfz~!"w]0س K}SWmVcX^n>@_7us17ˤIKzs#-A,o ,& "KOsan>(|t7endstream endobj 3 0 obj 1763 endobj 5 0 obj <> endobj 6 0 obj <> endobj 7 0 obj <> /Contents 8 0 R >> endobj 8 0 obj <> stream x]ݒܸuګ6M}8qEN m5hgwfE*~$s|$G#iSt!$~_i6Bn7^~?͛v3רgxƯ_mhZ>ku;#6i8al.ޟ'z!|%͍a_>Lӯc+Af9=ڶpH {8^eMNz:lGՉHȷlӴ}/}}? 6QʋVN+~9iNBMM}]8S75[۹> >QNr?N=uR*5QNzJk=_]_;ڌ{9z8W<ƽT1=idt7-uI~ſ]0.͍)o{4a4wGԜ1S/dL#HaS;pE%ق6.#W銞B|nncUhۈIQW)i~Z|}Djլ4g9sC0Q I@j XLf?_hóݛzo%0}jT>.^Ud(@7 .8 l?І"mUz{i[9࠶qNsd4<,<284 D{u{E"x9kFa0$WӦ'qZS"}R)*s1RG$Vp-7 gdzW#uS)MlIyTW_%t~LW~lʥB2mb2#`s2KI^N GHoГ]Z3@9Ϩ W~ie52Wg|#W#\+ ߏ*ld; ØL%)]W+ ƟFE;i 'pMF>lZ [!ν/fVh9#M nz4T B2D P V|[ϡ94{0!ڨACάhA^x4 "0:+'8h/FՀ. hܑ@q4 Ldz POĒzOvTTI:4E)sao,cUB(e6@M6dIxLWleX===¨5XD;YȣDS2umQD;1i4${7 q"75uOdLbnpғ>H#m1t)8q5Xo4j<4+h4"z,r6BYt뽽}?Dꥩj"m%PВ%g79 2GyBgbᐋ6ZLL&d Za%$>v:GulnQ?nk,O'3JHc:rU;(fJ^sN&(7I<*\eO#DT0oݢIؖÙSRJڠe a=P6۽N>!t2E-,TcGX 4pinY Iq@/GUIߴ-]cӝ75EID&5a&kS9sfkH"8N[N5Q!RtfLb9TЍTLV G>36E#/wh.~ܑ+ 0!WJqJ7orv֙c9NI㢍!)٨z1OT6Go~$jTeDI-z8ba-oEcs5 䖛 13|7[<®{ҢE]U9G ytx 2 L# ,Tيfc^ž'<q0hYYo|ʿ:5EOIښ=Cj4dž%N r$/zG jq9V@F}2)[bƸˊoh&j}&Z+8зU 4v2 prhq@ 1Cr6LZ56qNFÕ̟{JzԒjYrg2uzs%umi^ђ,^/vӍ+{k Kn8ǛϦmloPzRHNf\NWJXdN`N@V%҉fS,ۤmJ.k`KoB:QcI&r.(V2K$sCfF8$*d 2W G=Vg)'2O/3f:SN3C2jܣ/Qx6 >pPv48K7[4bLZfO%:#.\\A=dvb4 kDJEM-Kǵ4~FN EeIIÂ0(.>;7cKaP-Trꉧ.:”&,eZc:p%f,;*ߗܗO@్WKZFS]p*\\ߣS 's=Z$yFw[8ClK g Llvk ߪōL b3~q`Q+Md r?Sc<"u@,uΥɮ>ޕ0.hpncO˹ Қ93ֿX8 "'1 %pƫM1b$Wq+Ntr=$Zɯ@]S ktˏ_iA )P$|*bI,ik>l+38(=1^, EjNH*pv@ ?P3 k16ͧy0Y>Zlʴ(A)Šj.G'vf!E,(+Y¸rYT!OI-I J!!^J 5R nr!+ϡY;kSj?_L{##h6yv EPb3[>ywt!ȫvŁ!orr m@yy2STG)c„ 8SfZ+Ҥ>αjX-ZOXТy,zC:yBoiYpoP; a U#Ȕyc[de1 Ap.J沼k4*Rѫ<7131 _H͆y?C8 oSPq'Uh]S(܅wܵZutMS=#ћٛ&Yq!.,'/V p@ϩ{R^C%KĦ+JZ9|Wpz]QS?R0Vk]eV|B?5V A-Ϲˍ@i1:*S=UL|d3HS{[ēs}q## ZcP1(oUZGeϯqTo:>]zntC07l|7/_KCK?kkԎU0:_aZBW?6%čGՔ㭕} 7RާS?Sj9(CB#e}~ jcvm~R@,oJsWhj]t 幬xZA!Rd=}JrHi kx0=>^|$Vy54zLqlNgلƏ<0xR"U02ΓЗ1i]_z}_Œ3MĢĶ 'Y'TIzx鴃b ~\O6nF yJY0IZ/n 58B8 ug\|~a6€W:.}+Z(\/,!%ryҘߥTE䅑kʹQIS 'jbp!EX6Mjwz +3m6q/WTEx .1`rFnqMuyR0O~zą\Ċ&[S ްpHubcIIuIa)u%X glY>Ͱܴp0@fj2g]:GM4]PtWTij/_sY-F9/e~"pd*$E⣞tkХO1 2u^(X~AE؜T!Ζdsr^ sЩ+͌W5.+'OWӀ{v/Z?RfjOTUx&QΓ*#YcfOCIehTۅ;ߩ ?$껃v飱(dӉ $^{|ܶ~HQ&lh_I y7Kc7oՃs]x^7iqPzb{Etyn|i 6wh9)߇dCٶf#J7҅/? K٭q'9@I(x3lw۳r騊(-( P:OqMVUZX}vdv>O;e2]lJ1[C -2} UZ+KiFC癙3_m^Pu8NgBtmwȌLIT@}0kSqhdġǟ$WE{"uF{R㟶ZR5/u7vʺ (7yuxX_(IHuNBoPmx]+f`=x?.v®l\NB~%Sc6j“6-UQ{Q^<ڙTZmM&vXl> endobj 13 0 obj <> endobj 14 0 obj <> /Contents 15 0 R >> endobj 15 0 obj <> stream x]rFvSL*N 8 g*z KEDrMR^'7Ir@9̌(q@7>_릮xpW~I'mÿ?4j Mw]׵iOҋls:oxBxnґ8;/{=>0YGY5L[^< ͦ֎:z.omObܕִcշ]GBt'mins>/ԼZ&Sq!JBJ'bx7(ݱowfuh,Hc x3\͸Tk8pgmZX K6-y:atj'n÷ŏnXIpB@ @w o |Q0@Bre}N!)|6I7w(DxRB\seZKF4:h[c#:p? 9 3,_YVn$>A~1 4''C/{? "Gx~1ҼGpߒ:R[FቮUQ.po2k pk g־;A++ ` J(K$i4GPP`_%4#Hm"9ے/_xQm쫦cUnξ89vͫ]xI-}l6.d%wW;/gI zS׹:h PW4v4:)vK0 U;Mv@G1DX.فu[ty\jxeG\,+8)N(ݛN=M%=#PjЩ}lʊJwiϱ|aѸUe ]GYKjqn6oiɶ{ZQ>|}q]v&k}qemmXkykM\+# JLoN@tt52%<:E)Ҡ8_yk,+1w(U 6{rtrWfGRYuD"+.t]Fl~hdO/SΩLCB=BX+IM P2&#_>>7,sA"ݹ=+Eׁf0}h7d}_gJGmc( QVi~KIS$vY=`(K>I?3(F= (S>U8daWʏaW:4.#gm^>z'z\+ tSѧO[>41ME]uak;k` xh-c?/c1˰tG1uޅ%f% h7޶z>ERF8[$ƛ0сyOo0Vzm+C877볓?64/-=hlM~g]tYT"ФXkk-?¥뾡MiǾu>ZOIjm;EqwX;qp+wKj#Nb߈ ߨ1>";)gz_Z{υN7ƞ\]Xl3JC8C:T,TcY9xG%%f6qSQ66V[ꕣHrҸ &6C.{LHk5ktkI+UZn OwR&HXD\_IO@7hw \뢂 e!1V$p9<x4xˠe&/7WNM #I# l/c~f|Y'0E$=aZLPk{/$q.1q%n=yd'Gm[ 拴z&) tsrF5B>t]ޢ>',qqOCby~?]M+ 4]ųt攓81wqR`K21S3 <onqW[dyabA$dⒻD.qj}O`2*G x"tVQͬR.4}m8r!RG!jcɟ *$K-XuDg|d#W<}a-^k Bx.AG RA˩DD;EfH0,o0&ȺdmSX,Ivo ꐈi`]#Pg%uq 25hƈ.p'Sr8fzޥouVHо`,9 m0y2_%^s߄[3:>?w·{ar(|˸e`;-_!9++5M?5eFw]@X֙_tύʠB~pq7 ?n!P-6ŭx>W.w+ӳ4|mֺ6ݾn9l6Ő.|Ñة|}uMS"dݭE \Bk+Q`~3b w ˬ?fF4G>yg*=) 8gca(hL1hH1ȋ+f'hb5cƺvcR}\O!R,s5Rޫy'Zr1yNIuL''W&7qv O(3jF@&wZYoivKa?|ʁ(@sQ32-=(ڋ]$tm`{ep \hCN]qH"W4bd+r"6 &&L} Ѥ"7?(DUNXPLG/@I$ !N#-a DR9޺}I`)Wqi6#旭VXfatg @ 0 w='sn `$wp6IE:tGj\KRoGv7kuҺ#nbv 7ԵKS5l:obS(^q:$͘l_l6NDRhd˟)oG[ ?Wu@{WΞ~Vd~wϹ=$Fڹv^ AtTa6Tj>*6"\ {HBN^9{yW{qusa.jIx9]LC/e>ҽ9D5ԡȺhq/CRHQWeT(B*M@N;9z^rpJ`qh]ܢ;Ǔ/Q?yB|ԭM:Đٛ+{?4[lTNJ d咈!㶥Zz9 ?mS(ץ*҄/4յ#ﻩNOSg'4I \q9,CxqCp%r餑R!Tv7ȩ}ʅ8atd`Bj@$>Ș]F.(_Cx/" R\/Iv:IPD[`PVJ H"TORVTbEM0y*Ub7#j1+Ip=#Pd7s*zXH$*y$Oܘ;VV,gJ-Z'+VW^?kFkH 7C`׿ +$N5|DM},9D+[uRqaudqON Xas2d1i<-z>+s>l[_>ό펖Z9J59*f>X;8sfl\"h6%ɰp|.wAwBKJnD*sfL/]gA&+$T]F/$!%ظ3Gܕ0&kVʻLSj-Ywgκ4ϿLUQ䳴$DC. N.[؉ub$`hVXd6xTr#RY$0'mK Wqow;^s:b,Q^3S$ږv !!:[Aǁ)sasADT؄z6 O%(7{8Ϻq2FѧT6s SN(+C L v(BOǴ'ޛucиw'g5!aYܵMAR #Y=@y\ԤxYASuw.:PXWrbTãMYhNd\e_<{%@v .Q潔uѳID03HaVVfX$q" ՈeU Q7Gqu7HTt5q9|z$&'lOUY SM!LN,,zC8M"bơIŸ`._p0_X"yƅ{e< A->7+Ua)1`Sik`f?73w`&Ճ|;m)S}ғs1f ʙʵ"MyEKEZcszccrMW) QȾg^WJpiq|'n} s60vpR߬$ŕRdf&ٴ\6Sq.C@d%+«~t*5s__'w jWky^A<#ϴ,ʨ"t('M7sf YJ!SrCb]OYQ8c0?Ɯ:%3'Y}Da"^)/1v$]!.Rq OV )lw\ɵm x9L0${TTuW-2T>4Q7JZDk'mJȟ9Ak0l89صqokaA}kE eWJv`.ss?3ɔ]MRe&;Z|w6))1$}Vp?~ )sQ!ˤ),R4}~r[o4klx1*@ pc:z;egv4#=IrT]<.+hXbd,Zç?5/c06! 3 C3䂓8rU5Ȩyo)dž8(?gHYC"I/4rl,k25*rn%ǟyt3a.#X |L0 T}DP=x";-v%dVxRfQbW30Qm%Jq2?) }?4Cry^3n^!HvAR+3c. ވ'!oI-B"oJx(3vaET?UBS"!J]߼}lW; !+u1. ڮ.@ԑP@\iYPP\īa\qNktZ}HLp:wQ95dWdJۤ)խ̮eҊbj_?&=; d^ /:dJ:k1䲥˵0 ϗRGqT&.]>G #s$+܏3,tlHn# hodw+ʱNp+cg$h~ />kyBЖg(ukc-tj}0Q)]>h}u\KCt~ZgAY|1T_ݹ˜K`({\X#W)]_f9oa',H]p#(jf= A,C}uaڣ 4;͠Llʭ@)9-+Ë$&C?(4Ʃ zkrx~5T+Wrp7Bӷ߇;|2\AU]b@weM@H ypk`^mW~ 0ڬXV^.w^3.JϓZY("d+ d 1%=@1ְ4NsXU*u%GxIoeS#4IAx& 6M HQȗyT=GvmLCZsҋȞ!:WOQ% wd帉MTA wd G7ֹ92B1Z{endstream endobj 16 0 obj 6890 endobj 17 0 obj <> endobj 18 0 obj <> endobj 19 0 obj <> /Contents 20 0 R >> endobj 20 0 obj <> stream x]r}Wli(ؖŊдXP$cF~% ݘi/ ;🳦Vzğ8x^o5W[ o<}v. 3v: 󵷳|lqֶ A/!;ļoE[nRMZOEu۶_c23la]riQywyTvz}lFRV]GB;kXmj{aYaձbrMb[Zjhlpkݿo-օ:Ms|TZx}ڴ%YV4+mY`7nCDʪTwۮ|tmhc&u@$K癴da0Y4 0فX Sl;u(^a*u.ILK\1V ?c{v`y;)6t$~?`S;V@qneKygMhgzξ_nmMZkk4ɘS1/1ED:#8!%/iqad~֤*#cNp2!FȟhP4+rLlU ZO)'|Mw1ڏǜN RǕzDvU6H[vNOD}S9c#I!2 B.4~f:m%)El;u4(qLZ(^EID䣘DଐRС4=c7(h(_#Y2ڂ05]_M *'Fkän~|[-854HՌ}: =dV=9. J U.Aa,N[kqL& >[Hz15Et8̚jNPy ;C\LEyQ9>#{*2k/K$3"dR7i"d.fuȬe`i51*jRulɘ(w@r[jpjj4_gT H Pjp=NJpJMs]Põ|,yH՗dR7Xc}_5b ؇ [9/uGHbNA0-۬uL6$2ܥɆ&IHf4Efg?\'GX`3K<³mgؽIT7#-4R3)?xߔ/$fY6ġ U)L ,ѺyU(uVMYE _ Viapگx2~2V50폴Z66aZ\]yP^JKJ|qz'X,*{8b*΄v Nv.agn~ ,]o1cSd*R d/0/} kzMK!篱ctP02d_ Ui=!76ц2C y,O(" y 2VDR[!B^à`qT}u( {^g 0FKLț9##/GeCl;/޸ (<,z־ݛ1оCt:XJWʙ2>u]1^8wS5{)js+eS/)QSi~dbCo7!a@1lS-.S[у a&%ljz80;lJ.xKw׀?U>Ae:c@̔Y)bmLt%w3 3uX-78-Vנ#+ Pm䮯ŎRTK pF=`yj#~ CW6ΜK=9ؑMw)<ZiE߉w&/]wh2[pz_;7j4l[$kHN12|:M#FvT5aC7īm5;+CܛSr2 ?/ 030a^X &Em3׹O$s%>AI>>J&I!Q(.>Jdq"$27xoDP8{[+kKV_ʢ+az.$k0=5z|-ZL4CJݬ&^^{sL;!SD7 /H3iCEY'WtMn7D]Tڗ{-]DƋD +Vq~iO#h%wz Hͻ#8R3 ҭВiGRw7+0-F%!:*9thcњ.xMFϰ _Hf0<$ի5ۃL~qE?^ ,!k]uk技% R `+c w3abH-d!x2JjGt&{IJF#q$YoL$lTx:r|gl~?\˺RI̔ҔX&I߿GI4[#/}ߡi U!o҅(0bNK'n@0I u0S5J%@Fhwm+ZP׊]ټ΀/GՊĐ ɲs, 8ȅ6no{zI<{#=@4IV7陸,5~$Lj_H[̐KxаGң F b N4v(=,&5ž% Xt[Rx+lZ&̉7y|ܳH:흁WpI(9~4&\v~ ڃ:+/$}F1URHy&-\ZV~{˲l(鬛Mo "/EӼ+.=d)  IZi =G Vw!;j8UMHzxЃ<~*QtE*-}>CB$=7Gt[F?%M{{!YvD+@XKVL˃\]'`߄XNSnE^̓40?*5A1A{ob NcS{Bj0NxaϘx2{>j0%lE3]ƫV/MY_Ѭendstream endobj 21 0 obj 4812 endobj 22 0 obj <> endobj 23 0 obj <> endobj 24 0 obj <> /Contents 25 0 R >> endobj 25 0 obj <> stream x]r7rϧ8“wUa;ڊw]Y[TERQ%+\ɫi`=s/Zym"__;W}7G~V/ՋOY}^+ޮ??껔zUTs~ѓ߮U/wzm;bT7gto+;üoכR:~V}z]㗘i2CzaOʍP3KV(U-@0&@O[ BVo% =j2dxT虠)nP[vGuH9`8HXC'&/ LMP4ZZ/d&"*v(5kGa5ANAͺsZZͅPLN4)˾Ο/g :Ī(&j=H70rtmZ]4`SKu*_ABgb2L߫ 0,J(=`kdM&>N S20:h74F3 ER4u8yM$[hSmKev˭[iGU >;z1]4̈gwU5UR(CV^Yat/,o-bs۲ e], վ-i@QDMaZ FEEK[Q.; 6\G?@C~4ljVu[K*{|o2L)d[xCsA%PXl{Caa"j8Vbg4Yr;Gi L撡l1g7sɈt~`D&k3"> Ol4&6>ʏfC@eXpXD>ks5ڡՙCPtP^5Tfaq83K|Ib= ³e6fQj%r 1Ug'2 $@7-9\Aĵ0+0jʻ? Jz+v|tNy8wSGW告},o}[PNλ|*pɴchj_ܴB=\r ^Nm8,)/5XzG\y!>m$ʤsWQ֝"rNP]ҕ9IcU1d Yl7L ҙ%̞!ߧ۬6{W7H :G;wdy^IyV&g^\^ ?\ hM.⨢V2J6<& A;V6 Se?Z*?\ 3yq}Q[(H1+:@GW" .u$߶Dި: U1%;p>%pWb$cS4*PۨÈ͋~W 2<ƌ֊ 3?qKoњShӰM`t=dX;΄TZZjqC^trjkznVbބeFdrt%*Y;״{IHb6C,t҆$2Fsb@O#Q^6 d&{fȠyؠ0,Aģ#]x:btIFw59I6qmؑm*\913b 2*8HL OJaIȧ|q[Fka;h,gNv*Zv<<%{V!o<%im&kseG(pkkoD^6UX * #lSC<%@BTQޝ:eB ?<&v(tZ5Tk% IY ?F 2EAհ'{00If13hu?K:x8g+*pRΰ/]>Tu`/ 3ei3M2t4 f0NwFqs;7pb~&RϙH-,m̔K#=$É4l];nNmardN\29E+qg}C`vX$Vܛux&_2CJpoϲ .bmҏ ΅XIK"*9XWbGr[nzcزx3>,m&RbxO#  ԅYI_Jq=9 F&+maC/(cޯ$+O ]@mX~I+f8,~ՀlRi-ot1Smbl8nЁ){lzx쒪i4y 䜉:ka/}a|HpQ]IrؗK1۷Hp}7wMqk0M[)%Ϋl[P|R~8!x6&[=Uy\Tiϛz7o-|O&,R` ;,يʮF$#vo( 4ޙ o-p#aIhj7\ Nw6Y)Ƚӈn>M_lY]vu;^ϝ_"2\" ~'p2<\7 #!76G| .fw~3SW\x쪊uoY5AIJ@S :p|V%eG[G{#T-؋@mx*4NgKx֨"ˤѱf'O:>O:)lK{8'TvNŜKl:O6A ѣ"-,!*ҊEQӯO7f-0kzy&l8)#;MLbS9>;Zlex?9ٺVÃe_cQ:}q/2F2}=X]Wyte4[Ow$rjx*gv穙)d'X9rI|̚i^K StTTlW(`9ÂYfn=üL,,Qxz]t|4|CL^ē3oAhPOPЩtC;@v}{8uzQ"dka.\s[&jzs9Lc!6U}QMdn||nRր/X[GG3|`P-S}*|,9T(!>z0=~<0{,?ѐ O-$nAKLJN'Nef=yc#O`zo{sj˖R瓩@LB; 62 .t,|m51͠9_yIJX*z]'إ[7 ߳܀dG6J6HvqՍJvr,@AdTь&hN:! |8%òKn!#px],6뾳*)lqv{xǔ KȴKȿdɀ0<`Kw[@#Mhygaz4]4ǘY{D.628alÀzH(S(/0~v2ɾw ʕ59+AX5,Cմa8V8Ij6fCÆuJW*@o^o,Y FƗEH 8%RzJ3PB,c|zm>QGI{R9[}$]?&f*3vn):t8t[Xz!<6/&:K73.m8qY 8ҵKևrAf `},\$(jب<Dlh8DOӥ`DeHWK'YFFDӈF;~&x5u^bV)[cJ|z; ;folb{:;bUmxV#`bKе`bsj=b>BBr>p 1ۮMW"H+V/8%Ŏ!T%`@Z(pmn&:% b_7|īGɨ ֚P[ {qaK+j#$WmvBn!-HR88>q#x24aDqkFKȀ;8ZZ}رpRDŀG _ g`Ո;ge{WXCCFb@i5%(8ZWcoC&HFk͍endstream endobj 26 0 obj 6153 endobj 27 0 obj <> endobj 28 0 obj <> endobj 29 0 obj <> /Contents 30 0 R >> endobj 30 0 obj <> stream x\n}W,  {Ab˄M.K#%骚ݥ(IzТ]Sk^uoqy}Ӱzy{֭^|[u4j;:qֵ)% 㨕󭷫|jq7mmQk,²Mhr)7k]afc3WX_USYr*T* ˾kL\z6\TqonU j(zg Mcoq;vXvT-g.u:vA6!6:4>۔bks|hS |W oMLMIy}ۥ_4&]'=n WwrXq m5 ~AK@.vho M˹YX/i+}b0"lZ4]٘p7C'M>*Î*tG?؝_5Ъ.ұ1YWXxϸj ,c 7Xe}|X"9GTEm،;u9B|A{?Q8{é83zؗQ2ry``ȴ).LB/lJX;hNi")b%8% {D }x)L(xl8+է$M{')'=i6k՞x*RY4@dadMaaf-n*R)$$}S&/sgS6y*R$?zV6*ݞ26C>5ٰ\QH 窜ϻ^bU?1QskR@(a'Жma2k{P9[s! T9/^?ޢܑ-L6oU1>cRM>sB!HV({ԇJ4jBӓ!cd"C%FcBfk&}@]hv$%ͥ敠 IRX{FMh.mtԃ \FA\EB$(-*ǥAOA}rvg4 <Ԗ5}PaE..Pʦv~3^ %J% 3QƵ>+LArq; y=6 xf ) {|?CNJ1кN DڨXš>[U}@Y 9?̂+p+ AZًpizAN}¶2R@%Uѭa枘H3Qw⦹b]ukx=/vhDyͭh~^labqxor%LhnXINI&yIfRl XȇR?; 3Ue!yĸaPm8 9E>c!\ mF-=>{I=h fzѮ@AG>@j8&'KXRgcPQimBϰĪUq=&𪝸1Dx|HdH2 诪k{??ybn2ێ0$RI&12 ruMY+:ٻM f`Ij.=H됥$M(z%  # tQZvL'@8PT=\~P<效]vثz2GBXJB=a촕6~{41Udz'IGGiX ]ކHLS3_³@@Ϋ#wїI'hܓO'rhM1ŠT3ސ Ǚu"f+sŠNS1s,4Ɯ_N I+Ynu=;7l;ݎd9w; %q|p@BHέ9!SZ҉ۧA e_lA1a&i#l AVt8}w /؋EbEԯ.%1 iK7N0؅ ީrk-}+6ryz';C(mK‰q;pe{uo(Y*ȣ62u~yڢ,R빐֟H"\iHrH֗n#Iۈ~V }zIJP9;}K<\:ӕ {(}|l'U>/Length 5364>>stream xytEfw"(*syO@}YEPvs "(2p<+2q %7 ٸ7otn:}2S]_OW%?fv'$,ؙNݵ˯zku~c뛤!Tέw>_3:amzw5u)PueK\W)(7sܣ5G*_\DOI^w?<~>^?>-CHǫ[ވ#1c h´&iaUY }tfܙˮk%\̕(|j/Z0dC'fG2)ZQL1C)M jfMת(݈qa !E(lxRbډYGS񬭒ď`m+Vus82yWRQлK&ZW35D['z[uJf5N k>e'.H%*}{Ź9&^'aJ8`'=.4pgM.)8<1UfxɅo>KUM*T5:=G}WeeuqETo,E7I9׊R^gYJ#[b=y`b[<_^h2@J-|^9il#~^syuUwҽN߱ejU,Π|elɪp{mYmDl"h!Cw#L{V W򖏸Р/,uu:[+x]bHBUZzy\׽{_{q扢g>zSe{?"}]gywY^{z8&yFxQbDJcHᴏKV)>*A1Y+qEDi|qgük )KS"KۋG/rq09c_%ܘM2鳹ã^w"r-@jUs]&Yuk̗AfR/◾PPT5FJw ^}Ɲ2UgzG12^^m2Êw.W*nW}ݠOƐIj[Gt3i"#< ipzj2Г/sʕLMnB&h޽_7Hq\]deNRz/`aJİ)*[lr=c)wμܦ_q=}W%%NwNߜw=Ӏ#|(u낏hV9#‰ɯBE4 u]Zv'gkdyz_\;O2鸯Y_`ԞS&^'M}X GK.U !lKI==mȻfUߜՃt![D7I6*3:|r\qKb^!n4ubsY>TE2HV+Y|7\ϭp󗥑3.˜Ox]**RʫEkeRʫ"uO3d($Ą;$*JR⢯Lj6}N^uG^ޥE{ߧcK^#|d!ޅa9.^"rez=/7}Q>fъVeCeS>x.DjOL_.^&⑞J=beZ))\){[cTPb=byBTxVVIuh f,K;R ɤ^kE3ńיug*^ԕ1/&}.;kVAc/#?t? ufgQ1c1skv?9Ls?v||3f$7w.Ϛ9ǎIO=27BZwmVJe}ld{wm$EJR Y`Y*曇/2Ulﯪ8s>wi4kuOP&T낙ׅ:7qw\^^м٪ପпM,M{0^tN./u.;x Z+RU;yu{i5Ui]0v &*.}s,`X ԑoc΄Pww u ql?萭0xP7`Y+eK'a\+mՃ+:(ؕK(R IzS2ﳿ&&`K4㘴U##d6~g'XW5@_U(WztB%A` t\^6 @9.b| :IgV3ow^n| ZG8(Щ bYluv4=: סt{U -ǻ u~Qp_h][ò׵c/mhp),8%u32*֞X_ M+hU%{ QlŴu)^_{qwǾy=OZ_.xMbdf𼞳+u|6&{ڮau_u^{Ϸ,s[(FfwF?>オV AJơ؏;C4Fk8,;BfеK`yn_[/`离;Ty M/H {x7[7㏠lWע;\B- :qR㏐J&sx?gP;HT^OlSXpV^2C;^W]`:yxERruvϹA@@ϯo?#>>. mbz#W#6x(׃`x;&-}s>a endstream endobj 33 0 obj <>/Length 7740>>stream x{Ž{vYvv1QCԜ$|qoAh@ @#BD#<}(O(4ȊhbDHAA,̭z{U==UuuuzzTϴS=+?LWV.f)@l~z;%s~׫wTJIJIЗ/Nc5WE "˼7bMJDlK%/_qn$ry›3&fjx%`JIZz: ;DݕUā=>uz+x^WmhJ_8xgNo x=}8']OTOjbux#:! 7O9JP^'̅,ukV12+շ ^(H>Z+A=x3G^?$&^wz]飆FD mN^uz𺰠 ~c`o#UlӜ]Brm[Qt8a::x^i‚&_q/m?|oKꤒI4m[?e { 鲫h;.w\ґoΧpmں0OjU)lؑraitjڼuGny>{NO3%ט%W-2Vmon -;۾x^~sOO*N}<;{^3tWԸ=2cmNՀݍGV7מխLnǭg>6lh\}Mk+َt!ΏSԃI1c^$5vo1r'mǟv)F,+2Z٢0z>?n{A8m)Ϗg3mG|m{ߎ/ygf6t0₲^S^,05{ Gu&eʳ՛:WѠ~h._:Pӳ (_k̑Wo%{}֕=\RfV*{;=j]uRS{s5AVnr.'YwǷ~l{~\jcM~~P*ӏ6 p#juRzU`DC>'|wo7Ue ݔ~y[6_ߞ^}%| ^g?burҾ[֊ɗqiY>c@xx R5(2Od6ޔ-//zUJ-xN Z\^w 41feK.dժUaÆ^:{tq>^˝^ϾnkE<օtihJWtϜ.#>y||_޷^n:/s 叏(7o߰+;Z1oqo=K^2jgmlb^9 :`KL|7ѽ^D_j3 /:4_\6m{ͷk^ڊ"o#p=gL]}1ҟ]?ߔ7x9#RmZtm3Jjˋ:K޻ԾxL+e5+&v%lӋMkT\o-ً|zƌz+O gTza|m`Զ}GW2rH fmZ9MUӜUMw>)7Uy哶3fe߾J>_]T#u֯? xɧ^Wk%b mh:^?~__Όdvg.nښyݐ׹^:eP퐁/<|gzgXR`D;j>nލnFl9޽{ܓK^<|>ud⋍5=SOOfA.'lӳa&9\vyo{AY0bܹlQOY~s娳J#_}UgTͣ[qKk 7ϪX]TzÞә&=?J7mo=scj+q jԳvٻ[[/{^^h{{^76?{m!}ù,[Ro-+iزiGs6{e{~\ϑ_)1P8kPﵜ֯ vƣןVup7旊:t~#+׉IKuG/N;CzsE]-[m/}ꗚmY[ʼ߇j[|OuFFt[͗W}K{X^h)ĥvg_\oY\u'/mK^<gۛ0\m±\y)zvx^*ߔY/niMwPwx^U +9S0Sx>멛2ׇ7_wXw%]k~V$M-74쵹;??j|uVz:)&N][=:;?S_Oʙ7h]y/Nzc&{>o#>sޏ7'G`n6[y oSxsoL*b(gk7XczA;-$g_Z+xؑ^>u=u3PŝRjjiTJXk|b]}i%kr:כ5u3ñNuohv‘C??*jYÊì=uMxO_Zy%^?lm={6Kn*uzNkґ=V|YQ|uVz:)fN.ѭכWd<~y;}Š]& 5׷.ɾݛ^\%[y כ3:cעe){n3W~װLwqSLl]s ]Vkϸ|l̮ TUw8@=YY+lx$)ݾ?46mܟ^Guq9췻޵O769Ԓ=HS7loC Srv[ZYX[yGunƒ : ^'v$--,ܾrh{JywTpw~>[ê2`G)ǙO>{nfR՘sTw5A>< { r"/5gӽΦ3kߥapo4l˂qW V[a^87l}i53ZK3.ZϽ]3.%eV ̏G-?[^Ɗ֍`~&}E7{n.3;kI4}U8smך^hx>z~]٪ yfCukͧRo[xɧ^Wk%b>|~osO.MvdJ9FW͞<4 #JM&x ^U:wd7M׀a D_`Un-91R>f^~k&2 B+A=x^/O+?)u1J:xɧ^Wk%buR3x^p#juRLx^ʼn{}6V:ȍ_YDG,+շ ^(H>Z+A=x3rOB2j/ N3 B+A=x3rx^;p#juRLx|'^_|'#X[;C6}*y=‚Yө^_޼!SZMZEd/$ʯ\”^PZE 7%Q@`IRR}x 9hpY]~5ķ_Q}|+rb AS;WΆ۾Mzs/lC.z_BLpsMngRd "W۽UϠZI+jzIzR׵}xJe߾p@zݖSj^uGע˼mM]pD/ɓ5 u-l?7KTX(r<|P]bq (4x(upyݚU:_&ON Ĕt: ݻ^H-JRgt8:P8`X@:P8$c{7_HqN6 Xr*$`CLxf^K: ΙFuJUq&wt~垤o:rgX1>!uY =epWKm Q/>p2:^ ,:[_ Ap:]Eq|Q:|H ܁ӯ{\$,?rzJkE1D/P8jE'/vnQ:-;I|=;SĴvY4nOaE%(tS^صvP@씒3q^J+~{H?6J^wֻҘJޯ~._e-(^F>j>m=Q3x=& =:e+bta^J}VEq(3V^ %q~5y[: Q_LC"dTw!^x\T{AF QGQ|z9F&b}QĮC4Tg7!v!O۝+%QI|Q/JK}fz 8R)*5j/PU:y%r^ zgG'}i٦MTu{nmLႛmDf>#7" 9#N[$pG.;<+918PxfUH$` GQujuɣAOWJv>Dqx=x=k>'P"e_xR'؎&"n׍O($E(}C%k|EJ;0ȿ%/ N Rga׈%%B难|b'(7ߨq,ayyj(\CzNəx\Tg$>y$^. ]v=u}t .+ͫέq| ruIpk鍯Z_HiJ%^wosrq)0'EЃzJ==սwd|BN)U"ZAbyss/qs|=߂:3RiSO>5^+aŏd Vҋ뀾&:7|x='=Șz:Džҵs4zn(_RL%Lj^qĆIiɛP$ȓk踳s e|w۩!Vk>H:<4L'"/qSgK[_TNQdŒn*zyO Ahx=)Û<^Ks-z=!ZlûÔw$ވ~n#ӷ PC:9U8mqq NGQE+:.lC4}ߧ<\#߇twG:>z;r|>8|}#B$<}5ГR>έz2:u ׽%Y^wR6܃&zq∶uJy]#Op ~>j>zaA7ynޭz.#zX^SFau'ߩ-$ j^+ā5!fwzprh4>H2$א@Py")39ur}C5#<ѓ_4S^3c5z.|;|!D7Oc5j*?N~N.Hh`spODt߇|C^eJJ/пG5|J}ɺ$^; })O|_rևDQ\$_adQ.&~EՈSg}x<)q|#TݵHS5Sҋ<&|S?IoΰU$e5h /QLݑTV͇6qW7J8>o׍S) CBn!"oM4,Ioq$E_~8ƍ@|^uKbtAn,bz5؞mz'|5|T5x=kΟ7ףx_!l}-qWIl/M_Y)[6΁pp7IꋠtFi$G)%,7773lsL$e^C|!~i~[N 7V~盒;E521:4lzx'E X ~v@ E"V`| D^:uH}xxv:uX/tC@]jnh{VSFv >_gjwIz9 Rr |({s@n*z}x/'^^<,\2|5^wbЁ|}ڽuOGh endstream endobj 34 0 obj <>/Length 9283>>stream xyսǫfa@ B513.^yQhTGFG\Qb$戈 I41[;:03 nu5=5Uw[kW?@ͭ{wos>P̘>3t}Xxv߽FQ?%Y޻t}I֬ DUa.'>%)%=%I˟ER܆kU*] {y^4\9ȝy "%[ZR=W o΄ST9)Q)^&-ʉs-h=u8v>Suz):t]]ű]]WKAסt=ؾ+wO│Cסp-)ҡz$q涏ulL׵tݠt]t`kI]+%]'ٌI׷ww)%]wn)wXV/a?Dn, t[\ :: tNw.LȄoy7U[< aU5haunq-u: 0!>b[cНۣwOUﭵOֽ]7X۾c9W>;5'Z5POmP5xariG;fkz G۶\S"BYvʩGN;vR/kzx~.]Q:eKeY߱ʍSJNt !.z][RCJ AI6SuJr[iũvM?PõX[eݲ[[?heGyWUnj+7w'}9ȺPOnl]&<3V 8ڟUeM?eDי/8X6j4`ӛU]~6K{a/5bmd]}iߪ;CWխjk;Ӈ--,aԆZvUߪ۵ǭB\Ϸ JgRzvˠ|+\Kt^)A>:fueRw妷^x@Ɛݿt5LKBNuѩu=DAO/ 6=k9ui!{Tۥnx_03qbG{'DD]zЛ-;3F?vvjk\f|WUV© ~3( N2][0躵S=~oy{%K؟w /Pl^>s t}7;^좂uxsPL/԰j;C Op)_2%^>ӱj(';'sJ}EX3~}z|i=lvDXKA]^3?g7mhɩ?V=0hv~浰S|o#[X/\~;O}ǨWn'&ڡ?QySg~Qߧ]㝧?kZ:7,zs[{}a6嬟OQ/)H{~^Ui{;4ons2{M~Y,1_7npÍ;=Fb׭wuȺiuIU !i޽a]g/_sF8)o}uf :ߒ"WJNA]\Z^dDm3 gdδQt'$"]:W7oo9p\3ouxgxgcNtϚg:֍LYOu4]?ØrBn>yqaUP[A/F eSb]wY9S?ELQ³3m?ׯo}BΔG=p%ɟ̼:w}U='&ȮyW뎮?m۬ s=iwDuڶ3qV;BwW2fLٿؾ|MJ-ɷ\f0Q߶ڱ㼃uvqб-x }Kկ9bNtY NH ]wv@ėңuG{%]o[07z K=bmWvڞ~q 9>p,_m=y;Zn䜮νwpo }PSs[ă'+稸ۏ^}o;/~+f58~o.zNksYz닝ӞyB˦6w?X/O׋! M=An%^\H[Hl빋[ mǃ&nfF{M/b]]R݇gvs~TηH䃮lBU_ ٣ugKO:&+V{5FLusnY;e錢;:;Xγ.|b/}k^{1Z=(n{x5oͲw}KH=z'^uYCY>kjV\[x_rӉK[]??=2aS`Ң-v;^t] ג"WJNKJss]׻tgBvjp^L|}џl= Ecܺu|V#X׻0uG5+8暥?ndo?w @73Ԝbn>*|lڷol3jԥXkzDCuΓCv5}YGty\.ޯU-Wlڝ?f>=c~ޯNufyJ~zy?w7x$|u_],]'V$M͖݊{n_:%s%w67}{Ghm'6v{{~ٯyG4NpPKqX;ۮYGL+y/͔N!7?9>uzݏ֫u>3n~վJn]ӿ60پN}o{n]g0?l5'oZyY/v|Qo?#Rו(~]IW6%]@יb?P-)ҡz$)͍~<ɟsҔzԌݸw0S,k9l+[/}Hנ߲־{dRunA N{lVZkXa?au=h VGםc)u'|s:^1):#zt] tdiex+et]Ft]y:: ג"WJN ]ϜAס\+\Kt^)A>:&tNc8]Cu Z臣ZKnP ]NµH䃮lFSupZ8 tb N^:](%]'ٌ\ t]t`kI]+%]'لt=i`_חL"P~h%[јb3Fnnu=z$UM;C xn#^JU['LPs_U"/[_[-{GRMh>rtWeBY?/SR("jX17G+ qI.bP"]B_ L|nm]/u'17?% -=\}7JW~VJָH 1hW:|]thnוKVi~rm^^J ZcGw 4D:uX\ix@Sw}żB'\D,jE Һnk=7G5u$ e?2#%˭=Hܠl$1t]g:NK:fC Ww5Ot>Lu: Kף؏H(E~}PKC9ՒQf K׃K9꺖/an]tb?tGn/!Y;%QN(vo%~*툚dW6JtJ`pnKw\D^Ǡߌ+u$bA֗(=:f4OIt~)$x6~q)\-ס . Y::::::!_w׽$MO%!sBN;衠SrߥŸП ~3%;.0^ww/YMf\d9zYE+uz=Dխ'[}fE1W: ]63$CJ!ý82%?qON<,0DƣsD+$r1Us՘"xݽfȥcy/*/&qՏ(^e~n$v7;KHuXt]?!O}tD|ۑ3q2/hNuW)!E׵TXޟ)%+M_b^`HMTr jQQԲt/Ⱥ#]z{πvU a?\%2̌|hJb?+8)hV'*LN_=&]dKsLY;1:' XK_u ; ^u ; ^u ;$GFew=x>妳DIҕ>_١5d%Iϔ@e-Se6. H"-ҟt" %ҵcPK6Pꍁrr3$uI6=ѭ_ğ.uWuz{'t0^B$x]yH}J$nt6KOsiR74ʘ|=ewZxߔ~zr%N]z̷ 8]sb"*Dhixs&DCp.:Ռ q=b-RK.EDKܟ.M@W QҹJkwwYSqsDuAڔ u<3w=|Z@oasVIKn?0~+w]W[NntKkO}˾[T?$mzu}#q\)UJ)|=]k&3+wdfzr=5u8[f%4ԕdJHt]21EvX\DuRvt:с $E×Z]rI:w[! o;q#ڧ b_wNS.uhMq8CsVI݉t6ŭTb_.ēYO;(=QnKVEvc(-%w18"$ۨU|3tEX1Wb\>.zEݽ}eٰIEU',Hq '\HQ%^@p:!y~HG:ґtxxxi׍9_Lϯs*R,ȏ"QC1lyxOԔŸhkPolUV<%^Om:n~?auZz!סC=V.:*jR6+U:n3k&?ѓ_0\$EEWk챊w+eMPYz{އ瞠DN;&?%(^ks;?Ui{EoQA߈EUD.yȸ'8_Oz~(Z\|sIM\~QاiOYJ{S8.Z.}zpۮrTvQ?S:L ~(9 ։ nдҎ"WDʟ_d_i*q7up+UoX.$n=uNg>r.&wL<^DDt]w\kJC SȬ:y."2aMt]n')]7)iݝHGJEq A9W$S??~P6J2DNWuO-,~ vbQX+z(MKm=tK"J΍|m/&+qQwJ~bugS5g$QgxDzpWGZ_.2j%hDGD&i~yҎ~N\tl xj+wL?:to^9p5'UG?trJ)wQ)1x(!1n`\D ν4$^]z KRkRS2to]Z(q4uҖsz`湛u]׾qDu L9fL*/UY4Ax/H~Ÿ+NX<}9"lSE 0q%J8uJ7G%NpS<)o۸+ӉԺBts9Ei;3%]z\;Q{Ed:"dhuH;']ol˟uJ>< [ƟCxއ/I[ԭ A}Τ#u D=7:xi&}su 8L-]B]QQB]O1]9}҉O ?i} endstream endobj 35 0 obj <> endobj 36 0 obj <> endobj 37 0 obj <> endobj 38 0 obj <> /Contents 39 0 R >> endobj 39 0 obj <> stream x\ێܸ}hX9"% e6Ĺw/=3^G'^(*%{/ ⭪N*òR-zq.=,ŇU}G]/=MRJQE)u#S/4BBРҫZ9چg*zuڊJjW Y m]MVWp=Eg!u,e?־VtB*!b-0~j%i;tnY_Z(r 狕+Xovn-ǔg{rlPK\;]ڒup< '|R雞ks'_~mEat_ Rk,Agn0xwEUe 0.uh"kou%}ۢ՝Z ]`vjE?_ $ e]vb ^0GQfZ '.=@-T/y)Xg t A]X\8ѓp>DC`^C}靶U^zUWTEFTq"DhGO7 Ч3O.}OU=z 3 c[%Hث 4o?3>ߢn}ؖ*[J08f@TuVCFz AMDi]q`Ɓ_*u$y Ϯp(\ȴ6 -c( NZD?NKC<(9n)Ϥ#-1dlQ#zϲ[1YL{; 31+x/nc=^iw gFϛ=?ZX~FdȜ6#q"6~c8JB)ךb]Ug @ix 6QRX__(CYeF0v31:Ԟ}+'smzy'\)AQY{P{O[x0f5 &8bq E"<8=baP]6RUmc2۸a2=Ȕ@C?BFd0Lm ipZ1( ZKqds"$6Y2:ҐoP=rFh!98ˬ7ں^S_lqx6ZpZuhcϭAяFM o _~-:Ii)=|DO=:&=R!1$SӴ.gMHV:16+ #:د̶IJ ];N9|4[Bn M\0X\nr&HG5} ~m0;,XxX1Uʊ,uoR,gJ 9~@N.aGf^'BGNp3 Or/!SuZW?jLu  ^wE8p.PSxoߊuk$o7/[x9X1)o``8Џoޥe(*dnZcpMhZ](3I!_Mp-Dl%{?Aʷ ѫ$?)udUjܲ2'WEG@瑞' ^Q٧(H-bEiyZ̟S=&FuĚ1ݴŢM_1("F{OgOIC$L^/7n8FCB Y8#Կ,؁,HB\zd&S&sX$\\*b{cs>*㇖ J#T5yD"Twq6<0oyБ4&Q[$7&1z޵YT?tM}#dF2kV-EOaD "9 ;hE[qQjL3G46fg_2J,!٨Fr*Z?dG2դ)ĞXXAuޥ-ax:kb_Fs٘’'G1nt|"X45Ňe-l^RF-THرʶCX!x64~?m endstream endobj 40 0 obj 3497 endobj 41 0 obj <>/Length 17307>>stream xy'EUQ+TPUb#KQuFܘimDeQpAW@du9AQAcͥZV-sP@ j67{UƖ~W7n߸&6:Ê֒%.;ːβH޶\zu]΢XOubb&=]tt]c]]7&Bwq11 4&w93^㎵Ō'se,=]tt]c]]7&B\\^ܡ7(~ʂMӖVݳ__jMog?nx]>w>W#1٭Mg/gɌ{7\gf7zNR/?|Y%s~^ҙ'~}=fpNOguϘSe㤗f<)}OrxڭML|OιaPpÉX>1_?\1X8awAV,uY|uTS\t>k?k@ɼ_y3}P >H9n:PWN 름\?9us?n9u|0Qk,WsيU_/?u7io=&&[6njbComJ?:DY\uA:,BC\iy.ԍ_\s5?>kvzD=vRJԧhR>M}S%~\ypt99vC1oe⇿olew}eCq,̡ ߲fpգn{~{ /qަ|cc%SLJ{2.m~FSdek>u]Quexִ]Ok>r?칏r ٣T9Bm~cƇC}1z|iwCf]ho̜ ^xs5*功oyǥC?ُşm9ue z}Θޙ/۽k> Ҹҡ=ީ?\@ׇ?m~e~K8my6:k_VFg_wC7H]/I|Z?`V1n{s7 .X4NO ]΂3 "S둮׋!{x}hh/}stWz N1}h鋞2l{kCoã\Ғ>e ֚txAͲ/+־E/{a-_ko?}ҽ&G[]saW'sCz>+Њ֒%.;ːβT -n&uz^N?,yAõy WYW ya-W%k.ZhD_'?>p*%=1M=_u.Zwk˳&_<;jM-{+z> SzVES)׃ICvC]ww<eqm޶hR׿wƒ8>iO;w)}vF?m{iE? /ۻ?Q'U]/t%G=g;OCuWpiMxG6g-ԟz9uƲ4wi#Yppz߼C_uT':kg]^ k޾Kg~l@x3Hh]_Wlw]V޺u3O|o:Voj{n ]/)Uym䓃n9HaP,ɧru}UV*҃Jm&*Eff՗-[QJO "JKd$[v>$tڗ"Χ B] 4Q?vL%]N#b2_F4-Rz!v&-Ta.knfg x_1]#n#N$]W-Iח,?`5_gtfQ&:Z]/E!b]wx+\r"m UG^Qv,`P*⽞uw̐ kM$g͞, q~OwME@&b>ƮSז!u&><3w2IxQx?^}gz<||> ?ɜnA<ث{31}b˚z@x={x>ǞOeOǂXkX6m{ !'S0-0_5)gč׋fi]"YUv$xn#R+eE~d RiӺN8Uq w<#Y?F`Zc\Ӿ/I}ϧ\eB⟧D8T +lPj8\\ 2m¶OyDZ?š\ ]oԂNW'dHxU׫ l j}H8ߦi |?Gh +QCFwz(?+<2`3Bk\nt?]_7^GW[yktjk-^p5a z!;Kn8ׅ#uV;}OJ<Mv6ՋnI<&];h;D;3?y:_`gJŐ#u6j3s] PcBҾt\Q0Dg3Z@m*E{>$ZvBk#cg"Q:_8Cv:q'Ӭ5O*x{f:뼾RuNLt]g"jy:·Fe 8QOZ5NSi֫s^ʕu#o Ev8C_#1u~eֵeւ5aQ/,T4ٗiSfub/ekqA]TMu3 #.ZCA5颖F.lt_nЖ5QYm}[/wRW[4&JL[ӓuL( -8_ JwS]2UY[nF?TnLW [{jY442GjO&Cq2-o=ڎO#d_6808Pb翵 q`1)A@@@@_Q{HG:ґts @u? ^u? ^u? ^Cﯧi(2_iTJ9MME?}Ɠ"3m ]S2BjՇNဿw9Et$^'[׵uu!Z誖R3iNlWբ i!RLkv]u2mvJ)Ǟ* FF ܶhE9z/a7rSt:g#OC9^ k'O\'یcNS=:?ӎu\f}c[A{,oF8a.j?y^QBk̭uUƹ̫ucF4>RױN:G8ZoGZ_mM4Prlui&|)~u]sbj3nV٧ N6n`4U9Emh议k=l}bhL%#s@E%pVn]J3Pmݠnr!604YX'MUBuN3:j+l:Gyt]۞-S)s Kпj::#ik(dNHV>&뤔\~S&%s 7]Y|~n]u]C*u6}88RyԘjfS~bzs7sƫhNʥK+x:sqqCr_7e-W[M"I#is^DLoM9JM_0-F1)38}j{s`L:j\a|g}x0/':::Z~B*ґt#H7@@@@@@vu_+q@*WktۯCZZ4>[G%ȏ;ܬI[KPzwDK1aәle2rOyiz_dž0ͭ_ۏVL!D>H8/&|omO]^JF(]MVΈݤ-Lw;%z)ӫ:针Riq!ʭ_~tI#QQczպ!1ҵ~vmZ#st/GDJwX'" xntlǸtrno[}U]2mD-!=Twz[_۳꺚45]of*jv G~irP-b;԰|@+B՗)03MMFO׵ ]L-:ܢW9>+|t=ޔz1d"4Kr4:V,QqH?x&d;Ad?au=Y;gry>҅%kfkt}]< +8ӹ19SM4/کHuݧrBX:jbvumb=]j~k$׵Ji©܆}mtz])$"jt¥!6|T3LEuZ)Ptu2,E픂V_5]|o5:RW8F:&M`.s3{A;T ^cMuľ_u7h-X]h CA u0DZ8w^ sD3tp'v@v u#z= WOuwx(CO`;z,w :wէ7QAƚtIG/zOvhu)X] ts0zH5tv u0$uMtXe5n3%v>Ö&WOצ:F_i@a @@ƚ\슟B u0te]芟B u0te]芟B u0te]芟BKx]}i:/ґ ]MHGzjbEn~Xӕ~W %Zp5]Y')Ђx5]Y')Ђx5]Y')Ђxx}?k n+޶wD?u|AcuC"?M?.J;Ы+>!W˳KM*t;^6b-T"ĐD/nͱC I3)m}ہOTc\Ej~3Hh|dF vgg<]ϧ-[NILQ/\:碂zҎu4]^k1ӹZ;Zw)JuAvg4Qu=v'>vn>up4~D:%{^S&аüB)3#8:WHS=,_35nľ_c_d$gaWLSAp5X%Ek:==u]Uz :>C^o-_rk~)Rmũ&]s}c>Ci:sih]@Y3OZ4 gAKZl3'~κqius:juBECo0EYǍo c/C?㼡sj3tP!iOHHu=xPGڏz *D=q QJgDm1]Mpa2yg;h%WMg*Kp >뺨yfHٽvhk d\P7c#>uV+Fxy|A\~3~#OyOJIKݎ׵'"V Aji9s|'۳McjAU>=y>)_-:dSyZT[ީXuuι>MܦSH/7֯7> uv=k|uSNiڡsإO4sMz#%Ww"K+!z=ݔY{ŢsP_c.6ӍnⰮ2x߇w:u]k8Ccщq+(f0J@ }t>%e$z[]9.:QznY $Qu`^gn*/:'=f<]z%A};Ǘ C&.̍oE$cہ?qvtb(؋6: u!\mX3'4 @e]= zz5x&G) zeJ$GS~N)C~z{=R?"TAry=L.z(Rtl*|90_ɼ <1ʭV#Px t&zx_"_ԕͭs&|=S}s~9ggjpUwSzq"-C4["Ѣ@dԥIF:u߇g]y?Az}} q~ٟz,=#r ].jq "Nu̹C77^V׭"EH&O4|I"Ps鴫Po DVͫ)יY ] !h8R>IZp25 lCF'7_VB!TnMϪh=z-(R>*YMGM!]A]'d-ߡ_EhSKG|-]NMC;At__Z9ӟLRD9]gkS9x퉹u&v-kӺdJ|MDz8bi\?pad*m_O}95Nvi렩w;pG[oӢW_Y1A?$c_Wiatu8K  mAEnu&};`]_kEZ/ڜdzC>|~u#j-3QY<*:wejQ{ӕ֗|O5c_=]QnS~`t+V{?/ 靊xc^eq"]5߇ۗM@ifxu>Os4`~u}OgB7*ˬP*{OM-^2q?d>fJWɿVLdN4Kuga8y>}[}SND3~ċum:gPUzh89|^Xօpad*֎}UktEv:<>Z]$N7aMtGoJw_>-ٽiSNڊPŶzC>įO.cS ƁT~]grC6kŸ qOSz A'~-?hC9<8|S-Bflۡx.Gx=Z?}&ݎWTrcdX(OgDI;N0VcєYpޖIiQ}{ uOzv<=:g/vq߇g].-*n }D{Q*d>j|t>Yzs5[]TMy爎@C\QLÍn᠈jm>AZaXcw|E3u}3juy)t2QF'7#|dKM6q(v}֍W騛}>>|O(]'Āc5Yٰ.ozŦSuOQ/|$ףTB>!EA0d\PnVOKD~zp9GUfPHK -әb$nA]HFDQ]"u0rGK\fk8DSlT)MQ|>J'b@H$z#j8HHJk dA>#ǚ!HgJ﫮C^}Ept*bûDuuQYzV:چɟ8M  Y|j?v utaKbE+>nkBڰfOiry!N~ym;ĆnLPC>Yl ~T^8ñrYdyu"c'Mm 8E]Eṵ[X2-:R$Y|]m>`cMϤ身euK龫Z|u'u6HkR;&#&ˢҐhB;oBCt)L"릣 /y}FtAFzmg=5\:ۺ@YӉgE1[Ҷ hIgQ1"9ѥV6ś>IZp=G>qJ[KiOQp`?׭tRd*j;K[X'>|O(]'d㧴>O |=]e?ukQu]ZnDuYVh}xw]gvFp]חuP7uAm3:Mdo>RcxpW!BIZ#Vs WL ?M.ꦬvڥ1rXǵX'SiM%k$-qs`r8*j^)BW!דSEb8z['L.|/nѕXoig{iks! ׳ZwV@;}gA=j:x@Eg unu=Sq0"ץ{yτx tX}оXX?Ĝ؜.p EWMdl9c#5'0HaЍEVørn: ]C\EǬ8FzR-u]s5fGZe8l#5kA;|TʿmB76OZļhY4Oځ9Bs_%^/HMTLpogukzaTTM.5Fu/R| QyՎ7<"mr[uѺ_8[:kj6m;z+jIG+_.mStDUי]rik(dNHVN ~u<;lC'[?PucHFwB}ƥHz=EoK׵ g5]wzǴ9c_^뜾=_|PMsJ[غ>:~Fj羒DmxJط cr2I.:A@@v7@3ґt#H7@@@(YO=ԓ@Tͫ/)%dJȥHs2W ]2%]w!ؿϯsZRGF$hayҴǟt:lut=2i~ŸP]ϭ^iv[+<]_bm>˿hҩ[ܪ=KMl&-ƪ:A;bcEfv' ]OLj]o|>@ C{?NW#PG/OcUzT6~V&8 m7}S1>rW{㿭m^CӼǃ]6sc#Dd8ʩoj^uögW_Z{[?v}cSUD>~9E곮NNypGYm7ٷJ|]/t0/{<Qtݴn:ևd>AethKZ_ih+.x%P}[)|?u:qnJ2P9vujFT+ƾjX!?vV||bp۷Upt["ພmW$t=`?EKAZߌtn ԏrs LL1îVVFDRĬ)!N~V.[?T;~fvԗ-=vX׷Q٤~|s={n:Dz;[;@-|@}Ǔqk躅qx2n]0nOƭP_cZݗ빙v&V'tNXx¸sQd=na;[;ǔ|uC{;[;ǜx@ ;[; R7ktkM$g͞]r:7_nR٭el65#m?RNm՗S4g+gm#61#`>Z)4=EB^9Q+zgY1gH?C\vVn(Z7~d(?͐ +4m^[a旦7 >M$u֐]'ix}KFJǜus4ݡDbH1sc.g20KubԾ|:R9+N9^}G|Y1kϱ'~$*zo5u*~w|5g|Z35@n:j̸9LC*"|Kz/n[ucg ^}M V0Bɶ [X3U-$\5U7HuqSzfξߔ4 C4o/~u?@Q3:HICח,?@ͳ۪?\CdzCG^8_i^I{] @LKiozxȜAdyx?u?{s@(燮rob / mF]o1];t]dO O?kA endstream endobj 42 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <> endobj 45 0 obj <> /Contents 46 0 R >> endobj 46 0 obj <> stream xZ_o O1/vN[\w5Yױ7n6y(J"ڏVkt[7?J7VWIۻ045(횋V9g9A$wQ0)S2G賝n}}v5>hc[|l7N]Ά:y搫ƏZmw}n>n}_LZZ)Y_Uβ${r~Ɩ~尥Lvo\uS]4&r\\ֹpAݎ\NtYwcJjʢ}ZWk wp^lTt+i's Ig ܘ~Or?[08tr&fQܖz>YEm:|t~| s}t`%MoH1ArЃAY톽e 3 /#w R8r/U ND0vtYM`Z sγx=Tڢ2\KLx qQtKB[ꜸKi3NN)g r'M)R}`2+ : < w/@HS.z7Vȇ:@ء%0b3r^o#faӅ@Sןpr29t{/>N=m0nFAG9rZ`6 /^U?U=y,AZ | {X^ʈ3:H9>/Length 28479>>stream x{.YU߹OߤBw4(881sfQgD%jD#bbK/`pDh 2xC4O5q!8ϓ(y@lݴ}:v{ڷUUݿkk}֪}OW~^:#ў7^G;Go@o.{W ].oS!,iSV\i+aFU=E]f_5|yHI)@sCYY(V2CLgOч$OΚ[N :Eװ-Awi+a]ςC׵':t]Vt=9]]kO th_O~T_1?ehe: ]_t]`%i)S]םEԃl;osxn:AUn8 ]_t]`%i)S]םEԃlvn3G]ן,} ٓ}\ztu A:t]t,؄{ . g&L^r_|⿾w.a!ۇ'OW]]OCn@ɂM7?alx'\{OWɶy|7>=sExpƯ{;^qܯgw 7^}þ3n{W]~|>_ګ3>^oNzqo8Ϻ#ן4OW,w:ox?t]tx8]tW^8歷=俹=/KZp!i?1{G?#/}p =~Xҹsg|u^uϝxヌޟboe:t'}%W=Ꮊ{yuܳ|I3WW\|?= t]vt=_{LK,Ļ^7d.>-L>CB>Fu'Կeg@׏@E벳 ] HKt=<(Zt?|Oٹ_wZx+n|~٫.?\k?wx}-xg?}'g1?S_[9VI?eo~e:r?rtOkn<.7Ϸ=i$]7wvߧ|y;T{{rhGÿv׃-<7?[CN[xٙ_/?Hu?}É;9~ɯ'~W{?G?v~hAuu9Z'[}+o^{ާ{5?o)/A<~ĉ?ΥG=rF.unN=!~Ho~m'%|>(S+Nmf);IQu}Ǔ.?G~;%׿ w]s/oD޿g~8i?yu!|D/+w7<A];>ڻ>^vI{?}+"{]?]΂GTpMh$XN' uW? w~l趟9櫿=?GUo~pS_~.)sϾ6O-?OKOtw>=\POT N|C;:_{ 2]??}N}dg=DTOf?r!ɛ~/?t`o?Ƌ/]͜sÏ<[pz^r?>qy\o+I$OnUƹ{3]=q^t˿UwG-_>܇ع`^܇]5;]]{y.L;'N;IOWۻN?O[uoIC|H:y~@훯OdȞu\kO7]{ -ѻA:t]t,Hv$Τ7G~/s5'ӟ  ?o?}?3>ʯし{?G|ʹwwť?=w.t~8v~Cn9kv.9k{kϾ?"E.{>texJ]G{vw/xزj2u.Y p՟LVig᱇ Z`5>% TލO{S_]ϩ_PL*?|y~@g ]t_'RFnt=U3uA]]' $-eʡ뺳zuM:t]Vt=9]]kO }t {%LuYu:tB+IKr,t]d?|=@&ֶ]@%֠BuYD=fs] t]JR;]لt}c3}fcP}3>>"_^!D?xF_2AU_Ju̙On9 9:?L,׽ ي|}-o`r;s^X)"DZRyl}T!vJhAO$;\m!Sr3/dŅ ?;BǠvo^;:E&?LO^h|=t=\(%z̴+/?"LU Q`ko[oyl.j21[:ȒO,]U^k;BDz^0Q'Ү…6/IϾL'lj"Cծ% ]_QIhz9%:7/Y(SQ'-X̛~Cuy7./4WC}Vl^xn g~E,OVqN2ev1aS7R7>)v _8yTAX-Y!Y(P:F7;$y\z0.??~)Z~ WuDa094\ٳg G}wubsJvf$g\f//$RdB_N ~nӳv r?:8O6%Pq%u` v{eޛ\t=9ɪGf;ud%yCLau]B2]0cޯwn*J:fv֚'y7͗'a :-#fi7yoڐV9jI??j;UKKeܨ^Fsz5O|=Зyv%qH9/78U6LPC'hC .`,||||||;m?)69+۹ U ڟ~.VZXyC_o:-[6O;Ȯ~ʲa>ג1>4BY֖Wj;d7:Ef>B6*tuQm[[%o)Zk/tk)zu]pV Z{q2\To:4")4+ih+/ڲ\a3Nz;_5o>Sg%37Wcv=b7-;cvkcBZ"յur~V[I3WMuM' X u$H;^oxk_7ىzr%""C *hKTt$j3\<ݜ#kœq&;|3;x;%Ap8^]O_2#c gPv%q{|\/`hl#~x*y`3=Alv@lv@lz!0!Qr(Džv@lv@lv@l_F-Qejdr~'3~ nQSj7Y~(d~9K}r@Mċ92_ϹN t}{|}EVvm]Cмpخ y ߮Wˀ|4]W`ԚW t%%@]tu`;@0ϡ(G9Qr<.@l:vl:v_+RqEFq2G>{uBgt\xMu5OFoA}zZJSmŌBgۭ[B%זozkY ʬqY8fR?U,H vL|ߜNuƕ3*Zt`d;(-(׳Zv5-/L  q~v_$CHbbb;||;Oj7ꊐd~(O"!ͮG_6^UG4*7쓋 n;]/[Bv[^t_I+T]ֶ$nP^+k¸m5Aq 5: _qᔁI5UZc-\Ew%(o]r"Ϻu}NO]O\b\Zة>o+1tvd{v[}x(%n{y^k7&mRr]:]gb5dslMZ.~7]o=oz=~yyV[֥llt#k@/ IvV?CƅT"]VKo uYDžyy2ql5jY"锶݅dr~^ QI}I9?f=ƕ! X]ϥ ?qCMd[J<(o\tx|0VSO_$@`ア.vM'w_aA&$uk Am C:5u8L C:5TYԇ%y!X|⊰3`+CkGgwJO{dv0|RZ]in4OcƁ[\[:5tdt}$Gd/t] tzSPld-t8vMg]S{k?[9ټ*\s%J ǧ'K@7tܑtukrs-~$ukֲ/OzuvZ @O.@Y˾? E޺h5\q^Cy\8?Qrq7?G:5k_" t욵 k`]$]}a-~$ukֲ/Ozul:vؚ/~ֺuCxÏ[wg"%|~|SXד:~Ť3Ixc"秵aZ4)$&Z/Z֭" v@jyyw 3%:9ߣԒϔ՚ƁoN[!?񑴨Sa̫vhY nj'>^N ]On'UTⳲ'3$qS]S]W-!fH>bP&$7ś^.5B o\m%Mׅ%.aq(Ǭߵ{A~kOC]T2N{M\AvAEKpU3HB!Ls$u<T7BnղCF)U;Z 3~JHz Q_L. ^wRu]ճQuvxN8j/?d]~t]~ZkuD~c'k tL¦}zy|  o9EyD oHa@='6UL^`f`g=_q:um|%A0^6my˺R❬ ?_uw|ߺ)`j0j~CuZPC0UQC|o$]R!̩F*[:1jmEבxu5]g A1y>p|=X9S<29nN8! :.l9̇nǗ0g6z"aZW!L7gx?֡CBfߣ[Tyd  -0bT +uT tNfZtjz8evB2dyov|<Jܘ׎t]J͍/c?Oŀ2[ϵ<6Wk z:יq߲ 5ec\4u$¸߂i3?=Nę""à0Ѷ$ PԂO6xwk\I Ύ̢C#BiTSQzI<<)\%<>ir 8HK*ە Ϳ"ޛ[T()Bk?Յ5>'*`ɠ'Y|{}oujƇ⭿6Gk3F"w\Z$u=40n>OKލŊ(ao&/lQ}H!kK}`.)i,'oY/O"/ۏO2ys%ΨU_8 ̺50Muɪj] wo Qݲ3iZyͷns ) iuHrQSI˼*x TAIꗃ/Տ_Lu.SM az]딝ŢDuZk3[4_^WgK7ufk@Eef]zTZD7ҧ'3lyk]7lto׹ ۭKr-~R;W1N`3[TMtg:! |?Z'¸Fg\e\J)olo7TnPAXT'|ybSP|3MĿ}Uϯ7Pk/_ozw:xk|1Ɵ*(*wsE+힓y}^佹Aꊞ+K^95c:<6F);r80NI-rz2~(h+u˿F]w-)|5Ψ^Nj_b5^l~q*q($Q e&ZIx=:Y'VX|3Ds36)VmqB8Wy9߅+uIkHԲv:UMf;uĖqי:ZR K6>¸ijvVO5(ď{G?1 US̍<ENUVrx]OK|7uZ~$ku͘_ד]S. QC]]LBl0v:ǡ!CuюK-]I P?#G_-6EI̞•kŻ_+? &W.Tu֤jQTy*oBR֥wauŏ^OÜ yA:f*|gþDu|Z:?Յ5>'*ܫ]ʟ+ ECyNىA ]]3^ZPOFSR uoϕZ?8T: r6$ [4lN]O:.|4qnB7=I]wd>3sGq0Nrm>S.Q%6Mrt?tEUZSeisoc^ ]UMdIMoIKپ|?] W]צUt} !C~xEJ=A Z4F]u?Յ5>'*O!I}dZĉ^h>_2K=;O9*?SZh|)jb^] QP >hNj}@ϯ7lv|f-Zz]lσv?O8sWVM8|.)|IbqE (yE@`9[*2}Z ==_ݐ62oy0S".V9qgZVKz[| >\?A׳Ȟku7]7_'9u?[^t*]NZ;8G䄇'vvDb2p>.W&/ukPQr-\Rq׷ l9}W̃a^ u5;D~&㓵:8I Ql<bո~--_ד Hlq{oyODRם9J#]'z)z-UhT}먝Y;Ae9} ~x^Gm SjaFzTzC?NPT~ 7uU؎J7]׎vnH2k9DIى}3~R2.榲v:`K,Iv~ u{Tg=]¥4$Y/N'NAX7Q_B?ut?tn4Q ^>>*|fY<_ _b{/dNm8:{fg ߇0g}|Q S|wz-|?vَڜiw{Q^X(q فbQ]ܠ+GӐw4g~2t|< |Djsz꺼]ќ8g9b.ǡn(&T<_Ndzc :S-yS+_q/,fp]}+ٯc-n#E z /^k~HM3a`,cG ݕҿlzI?yWг%DrhόyQ/Jg;I;] mR5]H"y+hg95?TdYMmO}9π,~~$@u$3TA>.I y-3& 3@=k gd\"'H7A 7 ]sE6a%oa./{f}s.;;_]>N=g.0mnPe kyeU~Qa G~d[WC%:W#pg0d+qu&ɦwN<"L%%a׋|}L>uG-uZhLt1S/Pmtc137T9_o˫,ynɬ '%͵|} eN3\Nׅk6/~sjE9W|LI.BA\/+yqr6>JWb%IzWK0}/|>qX5:Alv@lv@l17 yP(G9QNDžv@lv@lv@l>ˍS.=EG0.k+;δҟ^Sb.Ya?خq\ٚ+ZVjW|JOwcNou9chedgiv;q\}sDϘ';vSrB%^uyu]q>*td^_QV> ;}ɡڃKƮ~ѡSk*ٵֺ~o5LQ]Hz*41/?6ʻ*oZ? /Jd i.c]d/_ϟU޴-W:`zkwn&ϊ %r}0]WYwj܃Zww> Dϭcg*j'j\b~Om܄&Z!~Րq8GfuO!.O2-\ha90G%u8.M+ Vۗ^7k}m2g>解KlJ3Fڜ#iX' WKtе(kFe`,U\5aܙRq3pgjSv\3|N1[יjcK_ma]׎K2(%S֊ON{c*TZvFAxAQ ٹLrqvrrZRخ&$~ڶFqY]ݎDߺco*B |=y&QI}I9є>_?9{%=_LвT3C|NngVx?Fدe2_ޟv<1OR#\[/_@9^>rzs zw-/03m=doq@Aao'{ 6̘ϯ/[_a|=Ol}8` _ϰ::v_{fY-K 7tm.3oxJΣ5 t6q0zI֟l|^Y<$ͼIKk@➺N\XyUӋ-:wl 0RW3/U-q JZV>5o}O^u>t]l_Syӱm^>}.Y\̍ko>){xש&Ruqm&Y+Rȗơ/֋[]kɎg] H6W'32.?%H&`~gfA Ȯo]bS@7q#aoኩsM;(k[ׄ =/ Hş =뺟^ G'|hv|&B_R=躤u}Nalǡ܎u-_<*]o:"{M/ZV[Zt]2.?~cCRCtN>S5co-a/z|g3.eunq\>?: `H%{!w|_gB%c|<7i-jwj)ot|(ҫmTZKJS67g>8_{̝̜׫zJb݋)JnGq' CYIu@k]o7^W[9lI/z5F>=gq}d}j s_Tn~GbrJ sބ!Igˡt|0q~4/>忖qS}ܮD7Uk>DכG%&4q!5.#*;Y!CYEŭ0jI.^6.ԮJ[Wq盠dWlR}zz|}NrM>.DדzmbqʼN}uU/cSZe_xlJ䔷[r !z%\3:ГNho.m_o%^pz:A!ԺHU/ʾ[ en6g?Muּߦ םG亮kU5Gnjt]O @]Jč_Oʎ W⽪ޮ͓~g'ɕ, +ʫ\.Q]NR%Ƕ'/<\+3񕰈LvH$ٹ) Bt=?o|qȰ$ek+7x"5B^Am#}si׿=nIU:::zs(G9Qr\RN _:::~OUDJc}z>}q.nP~yg\aKW}tI r|=uyzy*;[!յ$^489ViaJ='ۻ:忡_֎ms|R036< d|vG1D%zV2.u/Ƨ|EKxuɽ?I}rd>sq@,B;ً ۺ<yS]4A]ay*Uߐ?޷Q M6N\$1£Sz'Q|gg[aϝl=j7,ڕؤum|jClu*6rάӤ?s>lK'IU𞯇ټIV;^]Oz͟8>-4ŖB%]2nD׳! x K 5@gv>QVz>Iu k׳F$a*u|We|5Gu"tc׍!% `IAz TSԥV%Y#t}'/÷uG|-鯷.$!%Tɸ;`sU!6%%DLO]>iTt0܋Zv2^.\rH1ln2'gDg KR*A0o=O4]0`7vJuvKy _POPu`; _u`; _u`; Ds(G9Qr* '|||cuï~Ҹ"U8#:dU]V:.U~D܁'>=-l橶bB3u[vKI9-*VYoqB~.Y켙95t==F-]o+gTȬwPZPgl!k0[_z;_gܥnPTPͅOjw~E2$$&'LJʼY|vKg+"z5mex^XE@r>h(tlu:8_S7E-}?jK^ĭk-[3(u=4[a+.20IC}żu>dKNМl6S}@'jW8(cXα:Ut=i>@ͷ@]/7Q8?'Jtݒ|h}Mȥllu!j7u3#۵t]nz*zt>|# KvG(9^8$nlj -E޻L ] [דdFOkk :8E)m &n4"r%0z+SCTA~ ΟKBN13xP;.!߸< 9-at迀HÏ{c]|!$X~<3:5[[ݿ6 ! ukq@&$i/H>5?̊>.+.Wm>0[iZ|>Ӽ !NIiNﵓ[Ck]4_7zb~VQWާ1J--ݿi uk:J/DI8,_`@כ]0|.d-]TTCuHu]|:פ/?!l(H2ye9d?a%^v붰靯T3z3J7h1=ߣ\~tzCy+ܻ u]<6l XJs so]_Yg0KA!.:Ar&O(]OC!>*B%A2Oq!dz:1jmHjr0%_k?UnQxC\=_qwޜyv>yji$Ẏ3WtS˳M'M5}`a?iE~wU0ϏεouH$05U>O{˅'vg qqPE^[.izqbK|]h!.ouS;p2[}׉|z-]?D=\؟†fW{~x]殊[֫A'yOs9U+V8dWsQy붆 F+(vfOA7Z_?m|QG8aD%.3H.Su5d#S^.iQU5Quz-Z>' 3?u?%YZayk1τv-dz,ukE7ua[jNϪOum|*uyN6h1{~󵺮WJvB}扟}\m]׵S~ƋoTAx|lvq(ŴOQd_W( b2D/$?y uJ2[čf˅IwCy`xqp ]=Ww_6_'j5> כ^;]3^Z#TݾTk~p|=G~jΟl<_b^Gq`ϼ)};I#9KdֺRޢW?Wߏu>.{%z/-W<ǡI;~։*/y+ hYqο8|l[jdHu}Ngqq>ߵBԖau֡ɾ0.Q?)Wv[ uFxTE2KzaE6. vq \kSMbj5> כ^;]3^Zo4Gd}9Тv<_y3~UsyS1\>gu~˟+KZ\hzAY~v14!CH?_W|~(h+uKY및=,zyOa2c~ϙy䛀jZ̚B]F:(Ϟa`NO VWyޢszҾЙ uq^r ߅!J4¶\Og5Tmhoāىv &(AٙO-vkŻ_GvA+nuݼ$%zv]/!Upx o {{˗eyxg!-Ϻ^`>v٠#*j erqiU.ODr![NC_ĸ{T NG߫6`'Z늷R iIo%C0\ΐm96c]+YkMDz?%-|֔j@]w͏%^a>NQK)rWz6]S׳Im8QK7ƧP+Vnv;Jq!o`?C<Er;)|uV:V&aZ.&]|ʟyjڵ]ǜ#㐴 ]|ݶ),N;yC9z4Ȝ;/2cK'%SFE6:PޢaszҾЙpdMsS8h/-ݘH?'ꊐ|ݖ]5I^R.7|]%יHV;]gĀo),G8c7x eOqfQj]7Q~}&jKtݰn躼6]u>G ̋P6&pnV~m}2/%kZz3u?Յ5>'*O!I}dZS=lb> SP6c|Vv>3'Ϝx˃le7Q㥊C7"Zgg-}~WLZ$xzӋ`'`K^oy$G<_o{-Lh8?e|Q?O !ШGC\꯷UGIf ԾG-̆%>ΨGCuˇAoq/0t~!Oʎ]Dׅr.ÖtEOJijee NP$'C]0o u]h ]V8 ȫ&$qh6~jXYr-\r]IM._߶-L]1fe_\F&J;R,:>y:_dh!ꁍBԃr[|N]O78#ua;1AtnAHBAw󡑨A 7uF (?/3:HTN% =zYv\·v*oKЮ0Vn11I6Nny]j=2vC-zPz?çe\Յ5>'*_?nnn*kU,=&dymE:/DgC٥Gd<$ ~HէP9>m&j5> G%sp|=`uz?:W1vŠQ~<+3햔\8zb-&_w5~bER FGnL]T$fEaW oIkpyqX;׫5.$@Xǩ\B/ c*'רlzG}~u}30J0Fƻɘ&$nĆu+邼N֯7|?vB]pZ(%nw>HI\~NW"ؒ>fw qrC(*(%WSx/jlD-jMղgu)ɳK/愻QОBTKׁClA׳-JBut uy9t} EKt]j^;v|m;w'.?J׵K5gd]9cއgFh")Ƌ5I;]Ѝ\/Zt!7u !5{=3gRjBgVudm|~q'9K+|Ϡ0{Nnᰇuϛ?U% }Z">އ| ԫfSOkAe!޿B}\Mשǵ/g+Yg./X|\.-yyH@jvbJ/,6m:a._6HoNօ7}suV7?%}[.0mnP/ryakmΤdȎ#?^|a-a}+!^zPγvŹ/JB<_3iмp?KuX֒giKa.TS;uZhLt1S/PQbfnFשru|7=U.- '%͵|} eNSJu]]EZaI.BA\/+yqr6>JWbP!}=Tpߋ[/&K˘:Alv@lv@l17ിG9Qrsy\8|xlv@lv@lvMJ8S?npOSR>L[+e[ ;-ꂜ%]ǵzevͧTڏiaJy7֫~QSZ;VAvvkkǵ17yKو+z"m7>%.k.,t]O]dzKOF="~"%sp63 S}?vGFC44ٜ;jX]8 QK3Lb7ԡfV_ yeK[~z,q9Ϝ7咆$jGZ6 $[ɚw2&|KMdօ:\ϪujpՊVzŅi#f6+wg%::7S.-&}Qr MC~ou\%}H.鲝ZՒqسq|}ךBQyW4h1ul. ,)q/;"DҾPwgeeSׅGïBKJz mEs\pڗ*ݯ=dZW:]kS&!g8oG_& in*(c?[NyLm|iGJl,Ps+_ÔuBSlJ"Dv 2qڥϣT~6D0?Zk G7+ЎZ  |?<.g0?kQB(::|܂Qr(G9UN _:::Bn~~3b%?5C\ğ֬b+[ vkڭN-𒯻'?]vTܯal2CQ2UkYMrS V~gfpQ/*UUu8 خ=8+UzgzÛO@ċܺ$c_la_vfSOsT_ҴPhJۯ}u ٿk->jok~:̆O=?kԩ1z])_?upD ]+ߏvOi_FbX)YSƝ9]!7ú<y?vk:1n+:Χn줟|;:ݿfu/ъb\2l4봷yN1v_B]5k:ogtj|}DI%jq+/Gl-/:)/mBm kuOtk'9Y]?P/' zדg(ԗ3.QM nWR+#q -KUK:=$VkŁnZ_!?zoǣYci.5bu(:gP=>[_agz@ |=Ol}8`ÌsDžDmq7?q7?_K v3m=doq@Aao'{ 6 {[>[_a:aGwm[?:3Ae?I <q.0rā8T|8_UsWW4{#dBk;ybSx?%sRI+V|;y5;֙Oݠ%_,Owao_VָAq vZ%wodpa7%_A);fyoaLSzIL}.%};IuFO|SHn Cֹv[pGcGv >%yHUo''nx>x? endstream endobj 49 0 obj <> endobj 50 0 obj <> endobj 51 0 obj <> endobj 52 0 obj <> /Contents 53 0 R >> endobj 53 0 obj <> stream x[[o[~ׯKQ;iEQt}.Y4,93Cqt6BjmO?'7_: B!nᨐQnʉ.ˠkʮ -(y@i J!("lVZ at 0Lv3֚-o΂arC8— Lb-f,yydQ|,ZX gMiYivPzi3x" ۖanvn4Z=:+D!"p6ELKVL=x{iriHhAZgdZ(BUB1t&ף"fx(Nn?.$t狮SY.6dpuFVc/a֏>ѩB8iK'; P3)=hD>YLZ3TB2~ "Zsf< ֒;Fo'\ .$A+ei%U}1U:rEgD>zt?;& kF ð^$#e颬|3&/׹}A.DGwMN!UP>Vr%l:A;rXLDm qXU> >$`Ēh IKs8 ‰\N-(eZ'Q }$H | +0f|L!gc6Jv[ؔil Z)a/Cj:W5 ڞ8n&PrPBm6 C=N[9Vi T֞{ (sGfpR&}NGD:"p!޴֧V^7[/wmoU/'XR^Wc͘g L/yV\1}ɟ}~a XSmt,9)2{:,ck޴4%nt! UIC;aF'y&]f.PZf?:o}[) *BhWtω69BVϜ @uNcO4SYrJVO}p.:zUCk)1z\ETpG++ "=gs.%h>MZ%XD@n-LAB}lŏA/ꅍ!Kq,9}B dED8~p@0k}:-eixp~l N><P~" %.{k肗^`?r<sU(\asc0AUD3 &L}(8@Gk 6UǶgZIJDZ:/ׇe쬍>/>㿜6"&TI3Lޛf Ot1 W+J#WҹfC(hJD.H,Z~i]e%hJOĨzquǀ<Q1g"VG* O:7XȆàޖQs3mia^ǴWi0B[6tWm{x&"'>Mͱ-So@bӒL&&1 ^$6isx yo6%N:sW/)pv2EV?J͕t@.p-.F䃾:.r `}dabC.zYxsWYgEx|G8;4!n[m0RjM5bCg̐0ObrtA9.O> ֲՓE\ S\&ge+*KMWd_-Z-~ j{q_G{vQDei8=:kг|$z*{OL@ke=}ΏA!<4R9f|Mu ]uK@JUsJ5B_<OQҐQ={ʒMr/p޳3&8֡/Tt^:z颺 Z2"q|;/&&endstream endobj 54 0 obj 3189 endobj 55 0 obj <> endobj 56 0 obj <> endobj 57 0 obj <> /Contents 58 0 R >> endobj 58 0 obj <> stream xYKo7q|?|H hCR-W,mHo̬DIIl=8)Ggd+ıO7C~"uxW?׭'ukm8t@ho;|~D\Wί_iMu]9KwKdx-0aRiL Y9鸗߃|QTNY k>:,O!NsLE%.tBԼLQQeӡ@Nةp(crAjDfVnR2 n*}D[I9N0U>pzy?Vd~D v㜻?Xmw]Mh+%=@J-+ZOдJ\RddpJrO]&AF 3[鎕%$g{fO6q#XVsD 67cwzulJ# ! @4˾`܊=bP!:qkRJEsOC8+ۑLhYUEx(dýЬpn&ْgʗO0ln "y.- %|0>Ofmf^'04AQܦ͍g8@;@uNIٲ{+s![kC%4(l-aR@2v\njQ)Ig[κ6 5$dH1E2 \Xqf;SYµ&U_jZb/; ˑx F0[]VݥW.g45+jU1:Nvc x20v6O+opi9w7Hx'q-{'Ήz_wUiSdN<'>(+ B"mu,׸Lu=Mendstream endobj 59 0 obj 1295 endobj 60 0 obj <>/Length 22801>>stream x{oIY߹O+($22fz&^DUĖm :`@h4(AYjb!8k%YMxmw{>ή]ڟ[S[#'^kt~ڞ@~絿tB?I~eC/xZ 9$%^TeWryvQ=)~y~J`K H;?e^t^ߊR\<|ܘ)%e#KG/ů=OqH#in%n{Ǵ尃[K.t]q@vu t=p ]߅7 3-~9?OQ_oLhe ]GAVz]z~۱Oxﺝt= w:~2ŠR]S]W\H;͜߻~u}}ScRpb==8\'*UAםGk@ut]-BoQGN&O;PO=r_roN m]w]wDu躷 ]ԝe w0<+Ͼ#xGl#oxأ/:[)/{y}e+wyಧ~zv#;~]txer?صs[~{_s˷s>;785{3o\Fo{M1㯸suGt]S]WlT>B͛o𰤮?OO=iS{wa8ocO3|Uv?~奏}׼C w==zZ~Oԉ2t_|W># >𲋾摧|^sչ_cy~orza᧝K_ O;?v\{aU-y~N=,]wq]@Uu]E)]/ꌘ%)_!_y Ҿt_>? F} '(]˳λ~gO~_;|| N͂ѽ:~ Ksuw t]e]݅[tfHkt}x@gcn{韼{㮻ݯG?r루Hq-7u{wyD>}ѩyp剟z{Sv?nk._u8OWrُ-??ßg6z\|WpN~k(ӯ~%Gvx':!{j}],_}]?m/]rSn ;_x縴r]/W/:Co/}wʇ^3.>Η~\og}摱]pXڟ|/s]9߹ƚ` @׽`hd-E׿5wINw}壢t}N]?#%}]W]oNc}7=;ozM/=]늗~[k^#/;=g9y⇞SoGG~3enȕ ?w 뺻u>,,]?bx(I׏;Y}ooej/j?c;w~ǟo ?.G+̟|CƟp]?T):]M|]CKq~gx~(ջ+C?}~/\;w>|N0=ܯ3N{`Eq]?a-?߽yyހ; t[Ѓ^Oԧg g>}kݝCۿGwћ\^Gw*;1)qwj ]7ˏI]=7zo?;?7/ ]KOzm|?ۏ>[>_N>/|^?4tdWϻ{1NhxR?xաV'~C~)~}oK/p}(t^?)y^?D!ygv3 '٥#yO ~ @׽6awsd^zq>뱷>N;nKN}~uye}pS*owC7Ru}7ҝ-8ow|7?\B>o89xWq\PzE_q_q}}?3|c;-t\pq-}߼o}i;ߕ~~uwt/9/?w.@à)wu]q]߹w쏯{CnkG}i?+vx޵u}p{ p/7w|<ׯ:>==Ђt?sxF7f0~Iw?}I]<a8t}ߝӗ6smW=4[忽k| MiӇmtOr ;w7)wu]uvoS{i&NG|G=[i}}kzñArԺ}sK.t]q@u#{Ý7(րaw8xw<|Ç?:]|3>-{lT\o=b ]"O gЩf Pie\λ c6Z XVCH弒 M*. {O/oóº+r}e#uw8u}'3iҵu}Q77E="^]6Uy>~N:&iGp]4bok|gVB{۱O =A9@,7(Q3E`1_c7D·*K/c?Sw:DJZJ8y2u^WF}d8=9ߐ}\Zyv f& C:{s(={D; O9Y] "$'?o\7>E\{^J꺾p+TȲ3CzSAh&<_Nk=h'.9RK|ds0iʃycоjc_BsmȂ%f~? WlG$+4(r! &A]+(9+uq*Qz}ścG&~N8siAǡ&JR~*Wys{q"giܙr?.?_nb+-D qOg^_ەv}B^D(yWٮsC0pǮJ3 }wCJ9%;K8մuݷ[h=| ]%֝?L^[`lJ Ꜽ[/A~G]Qj5Εc,9{2X~Qlˬ))b'8mS< 񅖳] /+]<+_|~yU[rUG(ș_y$6'9} y [lF|?h u~:@?p^y8u~:@?p^臺_7ylm+z;gvHs$UrROJM %v_-Si={?ftӈrlyv}KloťٕJmTZ6f3W^'hdT|/pW XvseJTD_jW4dFXn(|:,{6sQMΈ;CK|$4GK'y= _&s:o!/v=/8󠙧~Ʈj.y˦Jg}z5i"5QY.ڽz@kW(;AbS'xv/oS\1_7 >شu\JQȫrzLp!mqX+:5rx|Qwy>jnISYju}uBQcu6s]vUuV\'25OCP}XIиnEa\p/%{U4Or*r%~9OFf QEhKbqTپ]> D*ʧL52?gpvr`%R<87</'zo+͹I]XF`1~x/EU,&$:@?p^y>7GH9SN9ky8u~azշ5e`6G_]>i<ÞZ%9e"MXO ~?y}(S*'Rz$HP2+_]o;k'>]}}qȫFτ';ڷrYkjS,g'M%(z4[")Ě,O8?e>wN`uMtٽʧDz$B>9ȣ%u][|[μK.> ~5 3.4; 5fr0W[cS3rt=V_U{㒥HIRu[reG 9!MD̿> 3ٙ9u9:m Bϖ+;yx+^i>] ,v=Hd`l5 >fbcku!m_&~oHط9]Qף֍G]9e9hUt-T^Wve}AMӳm]Wvq~+ 9(0V5A#M0t=a]j?ᴯ]`췩J6HC~/t=g\FIof\tݹ(}+ȱ+;}ͥu u7z(Y)Ͻ߯dr6*_y8u~ד_\3J" @ޚ˰:AOm Z .t:5zu&ʓ?/Z(^úۗ~_eՎWXjۗo:tjޟkSoK y]8+'LZl*ިz!6oJ&j!*_g׷7/cx˝V` zba#\`TWX˴l4~l\k˭-`0E9~%:y}]M̈́y2s5GB_v`TW@39Wv}]]+o b~opn)]ov.еlg<|^ɎE(׌]b- ^]Ec^Q!֊dnmXwҿ Et`.벝t=Afr^7ɣ&_5 " |y% n}?5If>m#?ӖJyPY\כxqu~Y|g34bm ^ܼ)\1Rڗ;VV9"fb~jt&xIS_NʃoQ53@gW'MW2&Tӷհ=#Ф_|Zuȇ:@?p^9\6Z:z&[Bp^@dky ^6wl- ߯0m[]y]?g֌vNZ4 x _R[?Ƥ/n)̬]yfwK~˶ MQgWYy֜]RUKUͫ?ȏwyg_2you^y8u~:@?p^y8u~OYK9^{2lwWeg6|w>3އ?+5a67.<ߓꯖlMu_sxig9;>˥]e7:fJ6tuVxVK uH\ÙMjQ{k42x*~^\+Z,92%lvLhkfm^zu}OAZH{#Q,}^7gs>]~Ov}]=UṨ`}gDse >GR#եtҼf TyOʎ7َgΞsyX?cv5RFty}ƴob,ۍڿ|^o=+b{)]}Ab;˗|)x.tN%抨<5$YJKۂVez>::i ת^vAtV+! kz$hX0.*n'9q?9ύ '#Kf"{4%_vp8BNGl.BtiYfKSp&ؿf x38d;9Ihtl|v)ai ƛV~Јώr$` . ,#|?"*Xy8u~K?#r)r5vu~u~:@?p^yۍzŲyK~{zᣯ]gPxYWaO_|YEWibN2&'mYἾ\rzo)o=E(~n쯮O˷JGb.8|{Ut]gB\v[rL5^|uͳv&ђdzp=-ObMdm'2;i}Unt&xvj^cY MM!R˒.-j-g% ?|S39Kywɩ9 *=eqR$|)-v䃲# ӐYm"y] _x˙SK̉:gv6yg˕sؕҾR]]yWvcSjtˈr=^iBgyx2M9}*a`a FLV>;QMJSqN#,gr&,GvkWRA%jq)-oVM I+uȇ:@?p^p3SN9S+ G8u~:@?p^V/Bv|XKcY oM[ ere6{o-ބvkYy|:Il}RI]o-z\a݌K2j+Ww7VS:5c뵩ޥ.`-6ob@Vئ}h,fwB{ y}}x<=]n;&`ϟl}GMugꗪ0i7}_5N\j~_qտ[zN]} dǢnkƮ_R/^F qJkEgN6};W__"^j0uZS3Z9IQX~?<޾ޟ$3^iiKX<(,bv 8:h?A׋3Vr^w/qVn^ה .L)D^+} ~3n 5Us:/'rPKAӷ(㚙 Y ^Uƿ+\ߦAj؞szh߯@>:y8wl- y=nxc8| m</t [ĻMⅎYgH]8ҚěVn'y([n'R) G8>x@1|`kx@1lm>6Z:z&[Bp^y6t^O{KM}ˬ kF5?6]F ;"Tqi Qw&ϔoEZ߭/ |[9)켱c>[7Q \`_c}k}y]gxZ<9\F$%[9V.Gͩ.o[So,OxzWJ`G5Z}̈matu:twj}fdoy}k7qh)*7Q`Z߭q^OGJԹ;j [J'fDw[}dE5:Hl=:@?p^y8u~:@?p^yjןpŕr2|?q.*lN~q~?+5a67.L"Lԋwk/)lU{,ftӈrlyET̖F̘]FΪjnik8Sma6U anL *(=8ښY<³׺@m]_fp2DyݜQ=tuY>y}w9 Wm碚v v䇖Ε1 )͑R :ieHW|\*%!>]/;d;B_^:UK?>cq.:yg캚ܮFlxۈW?OGߘ-RCLrQ!ԾvE'Nнؔ>ޠ?[f>}$YPY\Q'# kƚ0&T{+o#y9|.ud]TkDםzlbvYNDݮ4nf|}Nn驿?;2Q Bvsz+gǮB~,Bu]lںf.%(U@9u~yyN86n8] v_ua9BE)o) 6WD%OeR^j* GuMS_ؽV Zq< A]UXKc%AFm]O !*RWM$'R']_ם3addvIY߬U^df .?ZqHC(0M4Kr| W,3o|Ǟl'' W;.%#cx/ ?5M]ة[&҄`8+חY Z2Ur-EG%ӯrٍu qSvZ(]ؗ~?g[`kLX zۮ}].לՙƫ6u]RyvN~$1Z],}^G%2yIi\_>BSs{?>ʍVNWݫ|z,kI)[Q`>Boj\c&g~ p5V>.95#Gc5\% \Q0.Y/e].1|PvDZsҔ;M$ko9sjɟ9S笟ӮӦ0/ls>| yPRyVمbףdNV]QӠ]si&6(]UjBo~7;}u=j(/-]0}u3[>__EKBU}lWַ+4=kue'Ґm c%YS4Ҕ ?_@6٥NJ5 &*~t`4Bsef%_KםˊgrS\ʱ^뚾=_|lP}sZm]nQ+ Y,OO)g`ϓYe—<|,!H5sg'jTiJ6nI_#eTvU3b,R܄ȀNBq_|Y*h?`^-5N6EC7ԣԸb|^óɂa|y2ip^yο{r)r}vu~u~:@?p^y=Eȵߞ9y)r 䭩x+a [L4f/śbM7S3Z97XgϾ'_ϵm/TL~SR5^>MbkFω+y\+wV^{}KX}I?:/m ְ'v>;UM Kx5ժظLA~Ƶ/omNS^?SחuIL'3Wst=!(hGVKx=seZ߅>0/y?Qu&m/zk]vƃMuXԍr+6Ut=_+hA1BiLƹu*PDK R.YKtjF+u36Z:z&[Bp^@dky ^y8Æi]sYQo?ivu>16]vyy9x}SSZ Fqg(Nyc(;W[r'&;rͽ>nhi--#^ƃׇpF^&xlZy*źs >:%m欿<)5whD}Hzr^=W҃$Ib5{}^r5Bz-y}$v/qy34l>҈l:X_Uc6ޡrͽQ.gkk뻏]~Ov}]=UṨ`}gDse .mBJsT~Nwl"!>JIOˎ7َgNҏX ΃f&.*56Q7}GenaHkz]aщt/6z|7OlgO?v|'cxj 'uB+j|Ƣ隱fp=_4s`.^NJۈ/t^7qK]7YF5ub.Q+۾u=*ikӾ,c[z/θLBݜ=ޟkK&A]K2 yP|]/_y<[=NEt]XgFo_[!eQwy>jnISYju}uBQcu6s]vUuV\'25OCP}XIиQ[CJ{Uv<ɉynDחuL<]R7+D٣.Bv;mOjw?Revʧ3L0\*30Lphƛ&'IBNeK HX0޴OF|v<轭7&y [ vY`9Wp^y8u~Xn`!SN9S) GvE/E׼:@w^ltQFEމ(:@:@?UnTx+[۸ }:w{˂.jM{ vꖉ4a=)n/~6 \rzo)o=E(~n쯮O˷JGb.8Kz:*3a!.Nl-v\sVgu}>KI;ihv %2yIi\_>BSs{?>ʍVNWݫ|z,kI)[Q`>Boj\c&g~ p5Z3Jz&d# %K붜?&ʎH7rNCrgu-}-gN-3'rsu~-WvGQ4!V*ϽNV]QӠ]si&6(]UjBo~7;}u=j(/-]0}u3[>__EKBU}lWַ+4=kue'Ґm c%YS4Ҕ ?_@6٥NJ5 &*~t`4Bsef%_KםˊgrS\ʱ^뚾=_|lP}sZm]nQ+ Y,OO)g`ϓYe—<|,!H5sg'jTiJ6nI_#eTvU3b,R܄ȀNBq_|Y*h?`^-5N6EC7ԣԸb|^Њɂa|Њ@>:@?| =SN9SN9rp:@?:@?C+"olW9HT0_-Y&j^MhwթzIQ)}GHؾ*vzU۾|W~ m5S3^kSoK y]8+'LZl*ިzj;TTׇOؼ)`sJW^C߼p.ws[9m5뉝ώpUoR5^M*b*6.ӾyЬ_fqK5k[/)>TϺnW3ɟ? dj'ogr>AS p^W:'R[u]Xo͡k;xٖ IIQ~[Kx-(9C(:8_}z\Je;kzNhE#vaZjƊ0Q!0Ԙ'H۷hL[*Agq]oGMA ^d}ϰx!]32󪦾\pdJi_v&Z[Xueә%M}9 _*FT 2_ٟ6] ʘP-OV\6CV A]t=@p@dky ^t=nxc[ĻMⅎAlm>6Z:fH]8ҚěVn'y([n'R) G8>x@1z&[BǠ6wl- | m</t :@?Э_ox*1LwV8R&l{M4qMOeք5/޿Jd?]|ƛ(TyAӿox_dҧۃxV2j K^i=`&>|oJf4ҭFrǎظZwkĮo=t]4yuM\G-(u}z,sNOQWu]xZweXkzse[Հt=u#}\\vPo ٹ ^&N9YqE-X*I*KDK[mQOkՃ֟]Κ>λ/mߵI89Өf¾>ݨY-SIԇxKk:\>Py2޽}#ud;:WD׃o u?{?λǩ /uaz;+<<`c̉W?_y8u~׫ݨW,(Z/|, 6ʃ>/ ?5M]ة[&҄`8+חY Z2Ur-EG%ӯrٍu qSvZ(]ؗ~?g[`kLX zۮ}].לՙƫ6u]RyvN~$1Z],}^G%2yIi\_>BSs{?>ʍVNWݫ|z,kI)[Q`>Boj\c&g~ p5V>.95#Gc5\% \Q0.Y/e].1|PvDZsҔ;M$ko9sjɟ9S笟ӮӦ0/ls>| yPRyVمbףdNV]QӠ]si&6(]UjBo~7;}u=j(/-]0}u3[>__EKBU}lWַ+4=kue'Ґm c%YS4Ҕ ?_@6٥NJ5 &*~t`4Bsef%_KםˊgrS\ʱ^뚾=_|lP}sZm]nQ+ Y,OO)g`ϓYe—<|,!H5sg'jTiJ6nI_#eTvU3b,R܄ȀNBq_|Y*h?`^-5N6EC7ԣԸb|^óɂa|y2ip^yο{r)r}vu~u~:@?p^y=Eȵߞ9y)r 䭩x+a [L4f/śbM7S3Z97XgϾ'_ϵm/TL~SR5^>MbkFω+y\+wV^{}KX}I?:/m ְ'v>;UM Kx5ժظLA~Ƶ/omNS^?SחuIL'3Wst=!(hGVKx=seZ߅>0/y?Qu&m/zk]vƃMuXԍr+6Ut=_+hA1BiLƹu*PDK R.YKtjF+u36Z:z&[Bp^@dky ^y8C{m\jOMk;]ҙ y.ij ;Plbs~Sڬ_vaUJr}30{%g]*/}}.7Kw՞%ةى2Xo9vb4"q(\Ze5ݺzJ.itGߺ[]y=xs Grt}fԘu4t=z#^j^JrLH>wdJώh,g?&̶`>w>|̚B^C“T._pFڶ|^y-4gbV+浯D./AYW&SΗũC c쩮G=R桝.t]FԼ-BTpeQ_k^t :~UY+^9Np5-u]Nk^j^;o˗ 1QkwݴϻF籚zavI^ߝ KC&?< 4y00ߕyCxahߋf1(_iMQu-uOtyiX_pɗun)vŰ3iD}H|iXsIMQrw>:@T;7D`1߯ߗ;ž@O+7S:}}t]bo |k 5tlm ]t}mr __ endstream endobj 61 0 obj <> endobj 62 0 obj <> endobj 63 0 obj <> endobj 64 0 obj <> /Contents 65 0 R >> endobj 65 0 obj <> stream x[nW4IWfo  A0Id-uKvŲ iUVU5h6#mak2zl g&q Jd$A颱mٟgD!M/}":dtak]nݗ˒]yo]<֭uyiڐjVV em9C|&M:>oIg}ҧV"2TfA|֧7^$V*Itae+TLZ:=7~-Vh |׺|o*7%RtOݭ4mU.KJK ݴF$ne;kC[HdPlZ U<83^͝:M9W:IVnf߀BM 9k!zEʨJ6j'Xh6T}%bLqLv?n!vA4D20YR~kN(.qs(6}$O}>+f?^c2M}]UH0,~iNCb4<5q m[k}fį6(M+`-P!YxR5[km"s;2 Tҵ;D'.p$%nJΙ޷r薆XN-27dsfmqM\:x*5\a]80&CO$b/"J=_5˘4zlrp+*kQ>֍X|D!XԄ]OMt e2# 7'@Z_yD:MLG(] 5'XLE"6fAX(#a;0 BNG:v#@Ӣ`,-60QlVp9+Kt=N`➃^U8>r>%^M%R.QP~'J7\;ypx"톛==:jM?ugea};ձ>Lşc-7I^:~b34 gB+./gFLpK}2 mْGw`_9rZV]Yluaqr[ǠVb"A،(QP9Qx8wfwmP'FC""ɚ0k:&qi;Q/;k'S6O7PQ!`4rT9jnؘ,3w[)cr,]k_U¬ pm0k71 9*YA/K|+j`<_7LƸkDUe ?&ʖi"{2Ȕ((y(mceNNNc,&馧uP4ZJJIF[@"YeY2y(~˄A=i_q-nmU\!2'0p~`Ë9'7̨ﷸNc/5ɿ=zphEwD GR @*ƾ@SKgp.NDLzօg-"}e-9ɡ 6?I8@EG8Oõ[xpSs[OR0bo;bP 2v}>V2<d^;'؟yIw߅r118'33 P u)}:or8 Х^)bj”>-4V-#G}_20d42ljϸEd08~2 & di2Sݫ$m%2Sv (G./ NKRϡ2xT7 fύ p+=l ThB LLɻ#˸~qӪ%>}s:]:~)^Xx9Ϡ/hS^|CXL4׀-*q璓w)r*Mb6ߑw^l<},?_ք9U sp\nَ>{l|'Z uIb^cؗ3$䱝M?ēEuaM#U\;*5u.aCdm1aALS>h~Opl乷mM /#ѧAZ2>lJ{GAI%u0Uxl Pk㰡8/[endstream endobj 66 0 obj 3048 endobj 67 0 obj <>/Length 5015>>stream x{՝o遙 (]IdcͲfWTD$˜AB.FD` XEhDr$BpIs/f{^3LꮺUVWU?~hnWO[Յς }$;$ĮuA?&;瓏x8)Oa)~:ثLZbK_Ť/8}m&/YY\<2`6YgIb{4yXɛl/;a$[ron7'V+e4$6]އ.I4Sx]/8xb ^Ex^ ^XuцKu<2?A7Eiz,:GHuVz:WL/G?SR WnQ #Y,Z1\1=]!v7Q_:}E[q{]%|* _Yxݰ:A@x^O^g.O_.$9ٲг}_o&Y1 u4u悌$=$2¦.:9q~aG^[=ԑuՃnߝ(?|*nՑ?rGdO\Q5rxݭ 6z>}uץW"mǝJ.*ȥmrb6p`iUCs׼^\]/~1z\6f{O~mpN`JF=GJu3zwsW .Ai+{V ^kp륕uKx}d{[nʮ!C&WIWEu*.ȅyFko떧>^zyryi-߿ ,hҦ*߉ b<|ȕ񺴮L^YLYZiV*:W@x#Z1k:y[;^Ѷ䣽F]__Oߎ3fϞ=͓$/x}hGwQZGwG;e]-!;jJUEYt=*Z[iyU i7H}r{xEy9g{Vݱjg"h/+^?vgXѓl[uX<|晣++h<2'/RM_@m:2\\\!9rW=vtϰ,-UnkBetͤFurͽ+VƊ|"jWN)j KCߐy~/뾰<w#rq /u4uN4.WQy*v.W,T5:1z=#>S9rXOUm Wߺ.o~xi ]\O<:m2y6P3wV z-U KNK[|M8MEqt)MDtNn>^WC'Sٯ]3|?] o#}#]?YE#; (cf8^tW{ـn7VY>o魽!L>^]Fm?״xn/v9H_Sgk'޹4/|gnp[\uVNbծ }# y]Q2z=i07ڞҢv|QvsS@޵1[}DJH=/m?⪡91r>Ww+Mn)rc"+ղ #eBٓ07t^ _=jZ+l(ƜQoyŗ y .;O|x=R<["y;<5|n^Ӏי 29ew̗EiaB[)%q˓hs,)wyyux=fO׵u5Vxەy!.zڲvO-k]伇uux? x6H:uctmp<>c{˥35y'0I>X=oBU"rzxK]?*2@<ߵ׉}c%7}޾vwIc;pXCוz}]-=|AxWubt?fN^Nwɯڕyxʝ7 x&pؗ#]fx}_'G/Octi{z}koO=}^z}kmsiTHO{ސsd$f%O#hB>po:fZG:o9z9U:+x^mHuP9@"f.mL_؏2p7A)uen$֣3N=FP4ZZyu}3)#u3R}:9uV:.ڐuf:n0rx]^2-Vux]!vԱAP}6Z: 0rx]uۃ@1h/:  ^^WWu{=ux=x#a$X+F=x+&nd';{};ϻkΝaו擝!Xp>WNA,T^߿_5j;ts PYy׎:lzRԮv^nKу_z QL%SYQ< qǴd$ j}۸8,Ez<d0.Su:`X $5P@ux><xm4Dcp;WRpuO$Oʀ2zҵđS&#s''1%_e/*1Y+#`u"2?3/^I0ite #OdF $ *纳>94iji~(Cvm>]M,_.XíwxGpzuʆ%'xG1lEM7ș'LŲkG潛o/hG?}QKΛ>z]hW_׉tf-{ݑ]"C:noe+y>H6`2Jda85d :qpАR㏔J&`?^2y6cCyR}J<<x~vUuyxUZD`]_j:xHq\x2~9 sF޷>.gֈ6Mq#WK*.Ivzd';x=)>^uw 5WR endstream endobj 68 0 obj <>/Length 9359>>stream x{ENdd`âº+cW5jT4  ݨ$K@\AX+!J<*{"zN <$03IffnI}?__n4?K.XZ u}bwG{bX.g]]Sv &lG;OmwUa&%)%%I"cgwnC*Mخ=g) jzEk0WMfA:/A伤b+\Kt]nWwQ ֌JŻj랛 ǫ&avЕ֏St]Cu uN];ubt^ ]-3tgyMZ|MĥuH+\Kt^)A>:fEq{xhP_C]ʟA):t>: ג"WJNYO\0kY:=ZuKu>Q,unq-u: 0`Ԡ6&9&G}Tm=m{--ttc]]&TB]3Az;~ֽƽ>jcPеqxyn6NYٵF_.eOןvfWG۸7 ^5?;>vN<枕ힾz6rظlf36Uw~V`fN;u tt=](%]'T'M-ɹuӳA>g_ Ш\ۂ0h)®l^9,w-=?|Co/A]^.Cp0a!XzaO^:}\H˻-8ۣNnspHc_|>;N |FO^IVqצM>cl}(yfo{/lM+]O Bi tsIt'Qـo;]'BNu.^~Cȏr-]uєjdu~[W5ھW'\܋-O8?x~ ӢϿ"T7Tjt=N2]3dt=-iBz[3!.>UVs1y[Wo0cT[k1l]TCW';D ǟǺ_hUGwyc/DCn?q!cgL_ﲟŢ>!=9i\-h٣u(__|OM!c<Ϙ 1]݀  5]?{^Jx១Z+zȼHi۵W㠮QcG^Ok4t|kjdw 7@9uuaBt=ύ׃ƥ$dw{LIxiu(hޙSO|O->ep8.w.x1Oߙꎡ0jss=/ǤxcDg?=?o\~ڸƞuOǯVvY]_|lم &R;RuK#CP-i>l\&~zXH=ܸ}:WG> ?8yrԺ=J}s=It=G'}mc!;#z>Zz}M[:y1]݀ O% :ct]￳Ӎ?o./oBuwo߹OZ8Qoʋ<zm]NfO @NW>]wՏy'x4T@]azT:ȃ<;vՏ:xS/ .|{ݗuZzΨ?}n!o;޻PÔpU}]_wєuEONI:tDn80o` 쮌_) ;?r+7Ϭ8h(eu==;O|Oƞ=OႎŻor t3Byx-](FO&4\뷞1?Z~iNJ{<&sE 'w{3Joy}WTxGdpsЕ?߱`'iiځcp;\xqrXd찝Gv:I:TK7Ϯ/&.MB[0}WwN ,Twd/?;ح0ZZ7Y|oN N/] v?m$ gFOVGc# 8ppu{"I(ه˝nTjF)uA0nBt}Oh ot]](u+vA饠u݂tN*]sp-)ҡz$u:.:t] N/] {W.1X׵'nP ]NµH䃮l:sYqZ8 tb N쀮k䃮l:uCס#p-)ҡz$uLG֔4-xu ^/CXP~'`P/`)U׬.qn<W]V@YĊL|dkH{RiC Ve+r(Ҕ̱d]/^m@s]h– ?3v]:ʵNb3~J)BMim˵J١,=5(_@H9ϣǒz ĐD9$)j10zvm]גRtbR!\z"1ii<ϯ#3ͯ|X 6xD}zŐ$n,\z @(yxi8|uǭ7Q6|JO\?PÒo,uw%]wq~hR'rSnWؘ*)Cv {*CWzعqow?Wu?#WhҎO{;rX- bz0DZsZ[$S&_$>C1N̙{8vgYkWzPx3m/:{el~YnV4vWYY{t'=VcgD=wvuMkՊ䙛W%5\u'O{xdZ Wyxi_5^t=u]NH\M?.Yym ]q먺:*Ob%r/Ơ"$W%@l%]dG }J_(jII齺" la^vӜ`5 Zͩp?@S6^$ZSA? ^Cgv^\W/uoO5%J֜hӬDn0O$57t?\BEʿ䂥z":O lzRr {*ptr۫W~zAWR*]2xLS׉ŕv t?u#kE躁DbE&fwri:wYYHsrk7(.I,Q҉vb^;jo5t@1NB[Dh|`sabEE9litzdlGiA.s%eeKK3꺖/\.e:I5ϒRk@oi&R%뷱ruez >):6Ȧ\ʈ D?utOUAT?'{I۸Zq)ՌfC+yGKu[O7Y" R:q η̐7Jtj+ɔtt]2I }K);Ntyt.|^~l֠ ttCeuZǧȎd/RBWtەO/n(Wjq/knk҃~s <~3ux]gPr'WAGJ;ҋ8j/4UKu=qQ{8Ѫז7l~.YoSuҟRx]MVAyNKʽkJ-q)dV Ocm(w2 Vtdr;e麁Ne%^Ow")ArH~~ldrqqlfK~ i]׽k}]>ӟ^_un%IO䧤K\"ڗN4R[D?(w\e:E/#Ro~b2v hR?v+G%{2&ɦTo\:u: ^WuI * uvy.͟UV{UKWWny^I ɤ/xHWGMTVi{}kNy.j;ݚ;|Wi߷y^I#2h|#tMޘuzK_6`|fמG%W5 SE"8m|\e&k؂#uֵRkV)&e1~$dǽ9e\$=Ն~ux=[EkuM|ܛ}SETJ+pW ^M+WN{)E咧<|Oʙ]/x-}A.ڕg[I%z{%vjytǗk$lAzzo)5뒖3sL[@uhx0^wz@Ikߓh?KSWqs]]/gNqyծ桼TWDכz9+"Htx=sp\A$1`.$!)ziU䏣t+4QgkUCv):Le|Oߊu]ֿˑfiq^Wn5.yxⲉul4hvɫpuk|]ϣOGӵKJK,'&}?ZQtBW.H+21+MutȤ:L,E'*K:!7uƛvzul`Gv u'<}?A endstream endobj 69 0 obj <>/Length 9724>>stream xy՝5t MC74Aqa>h&jHp?Q A#jq!&&qKr!; ttUS]]u߽uky?{woU+m@3sƬC?'ijO-ko?>+{=,mÚ5p*,6 ubٙ[yvRŷ`/6ϫ=5 2V:/缠|+LKtCn,Q Jj4ǫϡRBUb؁KA饠uՂtRt=t]rN/]|!.z?y@){>Q[ ]N´$I䃮l&[>˲Kl}u])uz_+LKtZ)N>:fB_M_]:=_UKuò i' DYJ@)@uunB.t}ָV*&s_p뇮3+]]gCn@׹ q&kG7^o݆5S?vzo5~"U2~D;Fzѻ 8á[s~0zUJeO9]U'G ~dc Zk{/~hc5^şRtRN9v~iEj?}CrMէ_3= _'mǣ ޿,@rk>𖆱 UvSuЀ:ǹw?:MRև"v4]1p_oZ)4nȀ˘Bٖ$uR|u͌zݰVZI~ӽO;0-VY]vnYìNks޼cԁgmBG?p'9Ⱥ^|TOzJEoVOϜ [-#jzPÓv潎6qU㧮- O)u_S꺆]UlwoӛG Al^<{bi_`w̒u})CSd<;(ޗ ^W5˭ ߺ' ˦o+V߮yn?nzڝE~5dPP=+%\׋ww] ]g[aZCJqAI6s M{yy\):~z~ilƃgv^> 9Ey z񒐟#JڬCY3n zu[٣)5oSQNIk%m]E7't\mO3sc?m*V(ṇ*_:t~PO g䂮 Bi!f?HSt)?ut}7ec9fɒ%{ /ӼbA/nA]wEJZ-}~'TêyǏ^:\N AVىr=ϴ;smKN:ykwYkHuֻzO-%iwuϺ8ضyszv3oZ=9'_JG٥&>SOMw>ʷMC _nw8d]QS֯3b [D]_ڋ{*!](@ ' 9vΡS+=EJ^h.>:;W/wSݫgۉ⼆Wlڸu nܱΉ6q;ny}-FUMU-Kگ--[mErWDOyn u\޼qϼq͟yz:}҂{O{_'|.Fk< qġ{.As~kA _o}Cݸ<]_\:|kol<`lm]E[?_>}jޱ[ݐZA]-#{t |#ߪZ[W~?gL6;w=Pm-zxgċ)ԺoUmvV!hb|CJA׉ S1]|n CW6~,ޣnnX*}c/}k^{ѳ(۾xx7oͲwus3On [C~yiq8`SYxsg瀇'v L^n}+ZaZCJqAI6uZٿo}zNlY\Ndq^M.9ً߯7݇]x__v_rszǢюZnQ9Wwձ%X ;w]mK8`=ۥ6SR&}1êF]޽?п|M|Ou#θ޹Q?tƏ7u2{SqH9ϲ_|j4u_~yţ~omM^R١<5HL׭rE=!;t@tiu+~.)jѨ 1n! ʺ$i:1CMX=QwKGꫝf~tzyUx^W6MlAބP,m> 7|IyL9ϲ3ڕmJ&f~vO 4@O7?SuJ?CAYӼnV R{\3 ?eц1ciT!l*eMH7_m=QV`ʔFG㺮]/tqYuO3 vS{ߏQ>.@4[-_KE4$z3$Cw ҽi v.w%] b~$N ;)p~JTԨWՎհ)Ü6JK4oEpOީsF1^Oi}vj7VZ'^Փ`S9zcvӂ7B^UTZ^!b7SQ):P zߜ7ͦHC-7ԴD*R_fřH}9&jJi ﾿| ?ݯ"GR_AD7nr@eu=zS$SM;C 3^T['hLQ#UXץ\`sC בOXi<'~Jospzy;H8?@GSP#/P,kjh\\525Q:2?OJE4TV du?R]l83C8% %=}7o}.{[zL+zŧzyH_(%=kLl:Jx'LxkE5)_=]gN2#y.^fQiu]u%?.Uڛ]*b:^Ihjs}-IDZi;q|A.5s%ieJףK9꺒1/K\.i:A6IuTg^)J#v;l~Je ؗ6wH_cpnKu\xӞdߴ+u$bQ/=:f'tOF? b 73H JTA:::::::u?Q~K;_XZiބ`:AxK?'П4~3%;.1^w2,6γlTVQJݞ%QzU+%fW߬аwc"&ڕNʽo3>Cb~ݸ'P* h@ɔऊnGX=<95 X8SkIL*R ELUm2ފua#{G;W_pMjuQJ /*2w.%AFjS#u_*' -X?OEu}iC\t?U7^uMFpIu?ZNo޾_VFM0Q4XaSg_Oix2]J3G2/eiuF]z01)]75??#g\׽?=躮?gkZ2nvCdfI'm@ iҺ4TKikOcRbv̞kAI~kr$:s%X "gPJ7rvdfk01zM`p%Of3P@3l!8obV*o=(ubC☸R?5j/y^~{0x_@%-`s1W6$xxt~=_t#HG:)D@~@~@@~@@~@tXȬqX!޳i#^f(=i~^ԇ2Kmw)U]'K w]2-O ֛q,?3N z2[AJˬ]H -O^؍uNѨ%Hgib&@cqy\ȩ:EAO(vT  stU6uޟtr$;M'FI AץQ`ާx7NOj F=v+BC#7PxX~MAgK/I꺠ߔ֋`EYw/w(G Hb͙(K0SF:S3躪jUb H.ы/źp?ākQ*3yhkwsEYSI~f˴)XlqfzN58Ls^JnQ80C[;K-RQp:ϥ؄5꾠i_LU-ĪW|S]G6f)Ҿap\(tF1#C߯j"(=tGg+6H:PcX׉CqQSCUIKdJ?>kd:`'ꮿ4gS׉T ,t=ʼTވOKי pqۉ>Put2vq;뗾35lZܺq!?c缒t7ŬT`_$iO;H =Q~KVyvc(-%8"ۨU|SH:$, 0h+0.^^2h_E<(2J>aj^a~ʂ'Y `~ʅ HVuD::y\sHG:ґt#NtA|@~@@~@@~JCqzfv~SbQ~YՈ-S a$,ݠJkFUY x=ꌒ'r7> F!+qOe^~}qUkԕ) Pzķ5Y*H /#iQQUڬ{X{3{&T0I'/Î Oj~?RWZԸ^}~P7}^qK{^2)7=>Ly?xm`騮CiuJgGp KtVo^l?P/UU.q S@iES잆?Sz8뇒Sc\MIK/S{,쿸ywAq[~X {y?Ju&p;ߠ [ ;& 1✪j{UGSuq*5(Q^-t?u:kG?e0&)]IK5t*@Vu'R_q7ԏLn}?qwBSo ~̎Kb?jk} ]7?+ެ 1?JOI3E/Ϝv~{%oc~2Xq%&.KT?m7vLI$^fqʘ St}OV_@t:x%(}ʤ9J[hoeRix]B2~@{AA.[TZ? ǤzI'dOW/=5?fÉ.YE:%=x]B碽I r _PiL*^cKV&h/1%Tz@{+J@~@[Ϝ1ku LS lΧ/q J{))H~3PEwk ,=p,KZc] 'GtwLoǝw4SBM>ϙ+y<wxE:bu|$L:H74ӼnA:<HpvO-!@20{u+}sv x߯u :qb $CV__oDVeKC2m9E~+@G~!mWv :0t=mO+}lw&: endstream endobj 70 0 obj <> endobj 71 0 obj <> endobj 72 0 obj <> endobj 73 0 obj <> /Contents 74 0 R >> endobj 74 0 obj <> stream xXn7 S֙"' -Тƻv~8^UzH$ǫm"@}!(CHI{W3.o.&p=kT>bOd\6\4XHfwulj^v'm:ÍAIЦ}-~zϵ1z=,pkP2T~:RD% isREmAcѨF C;vRql/^Zd7ށ_A)v3tY!~o\z# Tn9B;kdCnCXˮ)guag,~MfE]6@CP؁0܀j24[߾%cWD,JkkbH%9.>%5S)%4zU ^YRZ`S02RTj|@qQ6}-Ę_'>M$tsVI!tʇ g2bKoZπyJ @ʒ0 1EԜ=,!$yPU%Y { ķ 3B`< 4sAK vcxw:%2.& >e쪯1 -JLUQ"X{#owMѶ❸|xlʑBh1o?^zxc7!`?s__K»5%mJ bBr WjM4&G?n0'{8$kGP&9q9`w]I'xȗ ]&nCYm8#? |M)S\PiRXάぉl\{Bծ䍄_b0BztIE^\@S:`jCq  Vzs-7s9C:yi5;d L(_{> p8v'ꐽj}@!eb9FƯWcEqj*j>)8QE]^Vbi)7)BM~34ϛkfdOHŌI%w~M%JlLϛ?Aendstream endobj 75 0 obj 1064 endobj 76 0 obj <>/Length 16274>>stream x{&GY7 f2$haQYaeq+eW5jT4 !Pt&Y .AXHVDC+U(F BIHLff{gz珙W]O^/W0|ʕZqu|ӟ=o m6p_mʊpxiSіYEhbpH;:{<_5y.Hg@vv=/:/r^mjS.{?n$v!a!ǜXE׊Fܢ{,vuts]ן'V:Җn{ut=Ī]_m_sw㷼3Y: b)GrCU6w~n񆙾t=R#Buthu%O9vّ㡽fۗ_гԺz(>rT]]Du躳`39T+Uc 1+|cׁ=zؙuAuAt]׻; F쭶^<`Zyh_m8/~_zOZ۾g>b1w:;n}37;'zww^;>;nzg~Yto=}ۦu=N5v^+?>'|v]7{׻n^uϸdG7~כΛ{=! ןz+=t]<娇l'm>[y[.uŁ;1_W%UzK;ϼUȬo<㉏XGv_]sfGju}kZ^qM=$w8Oqk>ɽ\6?nvUϝ?Xc˷uXtP?g|ɡsW9m^|iu//gs/+[wV]WDugt}YYP/'y{7_]V?4ktu(?{.|wo?c:>!_GW~&{[wV]WDug ] sHkt:,?cto|SnaӞ}c[]] GÛC]u]?^_wšnz#kזy΍gnV?}Ugv^_zIo9mkgq1;v嶯x'xˇ_V֤}_ځsN7r5`??pӬ#^t|7_/mwYӎt]vgݹ|]iBԗ+OOx!ٛUl]7e[9|Z=Б w,Yts|7o?y굧gM>;-v[} {_ws??񊯻 {' 4?U7}ݟ]kҾn9鸕ECW}joOի+w ?||X` @םzU%Kk1W'ILg} E_.eޥ땘[u+߾'7;_[L~욝o;C޷^t'Or ܾ^wLg胻|\/Y9[vv⺗= Rx}g\_W+9#oY\s^&3]?x΃Ҿ?ڳ_{X~pk i__ǯէn ő%YrSOX?qgҽ Q?]/~qT:u6d~፟~ʑo|3{/:7~}{|˖ V?{C??|n-gTt]e]ם$hij+H/ey>]_HRw{w-6oz?v}ZRxSkW'kN{Ïi}ܫoO;ațu}ˮkOzV㞻H笽~stzew4w;ïsݳR={1έs?_༣Mrߴ?]|}鏞g4cb[?~?z ң:w]wX?Ѷ̤ܜ3B>k|vӏOln}֫~߁'U|SuWn]ms7bu}6w=/]owϏ)?7yhiߺ/o#M/ǮCOy}mr΃3-_[YϿt{3ퟕ^ ]핛wa[u?zY,t} Anu=k~yo{G?k-?]v/ܱg]S+[sWuUgq~Ə xѦ}O] 7?{Ꟈ_=Hݏ.{:n皖~Lڿg={[<{O~{XUyI؅7=]t3snщ?VXuً)܇ǜU;kΚ˔{sg=f4KUl8OιYߑ}'쿜x՟?p;vz M~GքߛBgzٝޥVjjUΚ;"qؘ"qpx^Q5.RyaC=t=lZ躳`Nt?)躿-W䞓{ut= ]NCu+ŠՒ];Q]WDu}[;9Bntqq ]ut]?t]ajS娇lfϹHBC[AUu5t]<娇lfAuthu%O9vto oۏ_tݎ|}=W\yL=7k?a)rP+.R۷oȟ{y5蜵߮աrhS/ ?;JuӅWz-kUG(o4 A>z|zuc]}e}xo91QuoԢˠ`']t:ê3Q_hH{)S/t̫mތ`Rԥ=L|~:Z%)u5a}WAu Q_iȚK_p~$)E=yu}@YxIZHK Ï7ɒQ hu> A}9@!gwA;iq]0&( Nya@w 3E#uح+Wn| (^(Gt?ȇv"$~+de\ ˫{-LSZ7^BFɛP9s4ƕ5NZh;-UcuuuЧhJW^fַ2e]=k|C7N5~*q[D} _=7(?MɕB->k|Cio{]/ usܧǣwm u]:!߄gx5EㅮL\v5TG!S­hhB*2.Mˎx>.Q v?B"tCQCBD7q? ( ʟF P Ço*m/ʹ._w/}D/2JWZ+~70znWKfϏr 8Ҏղl29-n 1eGF|J#E,;$Eu~U?1E4as]7__PFwsG~өe?{M~Ԯ%.Ic)]gg]j\R͟I?:rIi㔈v"t\n/H|Wլ:8 Az vƎ7MW^\Rg$E?('"%={`joJ_NRzR^nvAZWkv M.R/NC(za.)Sו{(~QybϮ4H\u%ُ׹v u=tU\uV/)qzV)חձxC'ay";獚Usx sz(ڎx3zo]P%T*]o3.C ?3/K_\9Ǽ 'e<[U)IbǻMS~/ ۰ u[_hqqqM=^BEWu$h6UrɾUIyn]~ SlA.|`<u@0:x _|`<u@0Cit/96Gt΄vB\OH\ϥNˬ;zCO:9/[s3'S{rQ̘ǵ/ϵqY-O)jҾEԇR)*]mT֖j}[wJ!Wt6EL'jC-82%\TuVZeOKFszu}U EmYEh |ˣrmwe/\5jַF$ؑoZZwF&tiN?^'gDDZǥilGg@˺V~ЬP?Cv5ReS>Bt={ļӮM GenP2$WsݸAjWtx R=.'t\ֲ?# ź4sY^4㫹:xCW5hrc-D;Xo>J]ӍkV=68^إN4nVǫAY28eLe}mr]շBO1C|tIG  >ظ}(w$y tL_qW\y$t`p,YY뮙I6P( EBuBA0uK~U{SN9SN,\@0ut`<]:xG׳>(G,O֏~nc_|tk- ϓw{}/>?;5M ]MIvǡY8=z7[sBAU>% Hd\vc?".TVƁwvn eg{s\#=6℞h섶kb5uL]g*];){ae YO$n\_^B}{?.4ʍVxsn6U=N)9?$ҥ ^fE ox Z up%H͙6K̇QWJC5"S,EJKYM97e,}PD\ސ4)V[y_ fMu'rYijSXlu=|Zoy-2B{+R`hݠYr!6( "ԄBV_Z[:۬AFq\yլ0e9_RzZ<ݑve-8\))Jׅ~@#.S]OX+u~"엩J&HAA2.x_nV>ؕҾPu .կuݵ) g~+}^h-OuSFr'ʮ/zڹD[oU_v D4%ۉ_9"mTvU3BLRk#V; qq_籠]1GS͓IQM j^1?`\dA0?`(t`<oG%/(tPVWWg }i{ί%r[D} \)r)rWY|`:x _|`IYw=ؤƽ*,bu=tL??^Z/j\~Oj"G?xM\qq5[~ZV1}>V}xym+׿i0}=\tj-p\ƫP n_z~_Cʸ5sǛ۟k\z{ Q׻uZGS3b4\mߏv`dW@3.^Sp^+[Eǽu^OPtݵ n꺰֧d9dPMv,Dfohh/:.j4Bq湋~/몐DSMT.K#tA)z5 ޹"L(0X'-uwݶ@g~&b}Ag?wXk?Bד[S4B-%_/c~TS_S.biJi_ovuҵ/&*gjVg4N8v+Aӵ(j ^^濵+ߖG2&T uazG2:|S=|Cp#xxaĐ{z i2~ ^1wLF &SԿ[HY4?7,\PCrpAi~/rp]4Z?/_0@dj@0b=Lm=4Z?/uS[;M #|`<u@~7ʫnߗ zήFip.Oqّƥ~MKи7Nqo}mzz:[B>Gzs1]c`#V^;^wlV wW/XGe]OcK{;gs)lh_Mgܫu]s륍Ԑovs qO\׭{ǽ>eeBEM[/4z_K ʉA\*Zƽ.xVk O?-CZa"_SNݮ26_N&u nߏ+/|9svD 4 ]q)dG8&]꒮p]V]o*mԜʉw|!_S;yu^ֿ~\_)Ӧ6.y+9^ןz'1G}%S" +٬*LCk޸P?Z~g6ơ~Vz|d0j :xz0&:x _|`<u@0:x _|`<ןq*p4Du^_Qkk96βn E5z[#7-;wkT iYo"?xM#rYge+u?hijt^ﲩTYo!=__bCsi&U\n7(nܠv+l:Avv:X~kٟb]9܈K, ]/\fY+OnKu7zF5 uyhR/R'NW7}ek2񲾶UG.~ ms J{?^넎u]lܾVJQȻrT [ꄫL$ֿ$޾:^.撨5DJSۄf|p]_DAc}徦/\fZg:O)CU* 7=^K^%o7Γ6q? .n] Gʓ\.B[٦iOj~ן0hFT)8_ |wvtBVeCH7޸OF\vmAm&9;׿0B ~I |`<u@0C%=B)r)\Sn. _0:x _|`+']*0_&|~vUk\'{1H#?Cpz׻ٚZ2r)IpG%?]uq.BD2v;`(8? ߛ]虰'Dc']\٨uWe꺾?SI9S& .HHw?jlKb,ԏȟZ޷Nhe 7綺jo\cY DBqJ[/]oAEom]t}]q WZ.?+ۜiy|=CVhW)GK"%q릜?u>(".^omҔ[-׼|k|m۴k) u\ٺ]>ӈ~PηT9_Ɏ( ݿB -EC%_A&6S(:]6M mzM 4_5ܺ=N†u]kPt]scSC3#e!_Sun˱6=b]rDyi&^ycp['_Y{8/(jgh^׍DM%fouf|%7jy^f奛yn%AW3uUI>v7o_ UN&u b}m5FT^Aj22Ǎ~rs \mA\tc:m.3|кKcB?(u|}NiAV&^K5u)Cq7j !NlNˇ  L=_7pHعrT>WQzz;~H5 WVu)HgKu1A0:x _|`<u@0:x _!W.hGyݞ NȬ;zCzRMɒ/LԞydfZ,'ASjigCF6 qkK;%f&Uz2s2\;8  ȔpQq7'gD[ k! -jͭ|P(j*jGSH]5cF^]3o,k}ઉp_TӮ5"|SѺsyH6RN2'g:_K7َ/fL?XANC WH]zMx a 5kBQY. \n7nPD"Ȏ׽.]}^Bo-3"PK;qޚE3v&\aJR},yzGzGuuMBtݪg& ӕMxu=?>}Y/}]fSv\CiR9y/ׁ.:7A]շe.u=r:.";/%Q06 zy0ެ?u=hL&B7nt=h֙S}գJƍ_ӟ.;=ᵔUv% Hd\vc?".TVƁwvn eg{s\#=6℞h섶kb5uL]g*];){ae :_G-2zrIlݸ:ZSV\iVWm{,ka}SOs~6NIrK]-hͺ.2.4J\Kge3m֗0{S|uȪmt=T_#*=eqR$.uݔs\@ MCrk%`omT}"w~v6uΖ+[ףuJ)zFf&hI~G+w]WѺARClL P/Dzٗ xηu$\YQ׃㢹5D]7Yar{T/PyT_ٻ#5M7Z4u]9[qA. C%YS5R >ށG\dWn,oGE/SוL>?]o3/e\tݺ(}6+ȱ+;}͡6Ct]3v]_kMYC˭S(CLԏ'ߛ#ռb~ (Ȃa~P((_@{:x _\)r)rWY|`<:x _|`<uPJ O,Gy(r/[Qf0lN7'7oD5]N5(%_/B $ONo>Iʸr+Wgo:t´e6J/&ofA?cxwLzrsr1wdC x>h[;q>M?Ҿ6qEϫ^ƽ6sok]X`[嵭\CosAeG8q+B*4}iAk(on~q 6~E3D'_FjuگM͈upG~?՟)^AL2{Mu0z]l.:y?Au&mO~[1B5ٱ嚡WhޣzW фbf ŵ3ZK.B]O5Sl/]ЩNr\7x0oPcEuq[X~P\׋Fck]O?oO||ЏQM}M)}uՑJ*Gw\Yԗ; Ovakxzsw[ʘP-OץouhO)_uAÍ&SCajxxaĐ{z i2~ ^1wLFL?_oPzn!f$޸rpAi~iIkvKdj@0bxNˆ!_0@dj@0b=Lm=4Z?/u@0!l(}_*9vLÍQfw u^?w|ia\q41u:.G|}0;^Y2@mTp4O/m.4] zkx5kQer /=9ޤa]КDZ]7GӚA)t=V>1y(ԯ'E!D^L'_/MC֌5^:K7zD %_BQ֋fV Aƻ$H̖X/x]is=Fz'ɸi3x-CZ;z$_SNަʹkzoq]bj#s/ ]nؒFd4.s6$E=r]KI}p%cLh9EwB}6Z^ǫ9P]QSNi4ziz^׃[5nE׏rt)(^F5:͔0뗯u[^B8^ d]O |suz0&C|]~'7O=16ָB&W.βG4V+*zULWhȕ >ߛa("_#|_Ǝ\_u +C inZK.2'a@qiTl]tK>')J{]P;qqJ\yZqyx⚅YN7w (.n@nJy]o'cpqu|} ]9_yŕudKzþƟ tQeyqhUy:@Bdt]^'啱z+q5脵]YNxn5啣ˉJ uQ@r<iMdku=md88,d(]S%mM,`$t]=u7_G[NY,^܇G:Ҿ*u^vp>W{n:@gyxP_5R~o]hLo}}t\^BO> endobj 78 0 obj <> endobj 79 0 obj <> endobj 80 0 obj <> /Contents 81 0 R >> endobj 81 0 obj <> stream xWMo7K%echOM[R8,Ɏ#%˙V\/p8y u .U/]lv7UwߴGכ]*^J.L c4đuܙ>HǭW<9h;\3ÍAɦfKFϵ1L nh#{40XŚsRȚE]gѨs#ma80^(-whrco!gWVd$`aVV?uD.MG#_]r' ).Ѹ+~lcfu!SB=Ezg׸3E ?qOzP}(ڷ\ )5vzp1%ყ`¦Jaq#&gdqd{h!m>]r]CF&BJe&D\%mx 6pZi]8Op8P4>#:̈+6&,k+littQu[0VnHOT`0Tۛ|"X9?ʘ)H3Y#R%"T'wCOm1:=4A@r "A-79Lع 'ęV-֕ 3֪AZ \ho]aBܨ4GϕMuX@Ϩ}ыC!Kـؼ!Z=˿@h#OFx C{p J<rR t" &x9oy)J%aJpVp9zU rݫLdђ;_~PwcG$+tKGL?)Dq| =endstream endobj 82 0 obj 1053 endobj 83 0 obj <>/Length 16527>>stream x{&GY7IL$d V8,*+{,nvE*F$DnT$"H" "Ɋhae=??}t}ǎv b[t}6jpe}H8K<䴩hˬ"ePH{ nyk0!]Z+Wz^t^ۊՒ\(ݜICZ+C+QgՇǜXE׊Fܢ{~,vuts]ן'V:Җn{ut=Ī]_m_sw㷼3,t]G׃+X-yu>t]_qqn3]ut]_b)GrCU6{o+wlpow`U;Nܴq|Ήk5?{ꖟWl{Ewwiۼmny)O\9ywZt=,G=t]eP]|ҙB{Sz_j ՁY^U'Uiw}Q=\={?ٿ+?vj]_ڹzWt̖[\ۜ'65۞c}] ?nzϝ[x)_8{g3W}gq؁>!ˣo?e^ _=u;q]_ 뺳u7L5^=$?ctS뮻nӞ}c[]]i Gۺ^.Z~k9~q|mG޷z⦵royI~N0+|fu^{f/>g[;̭_}u^wI~~+_wN8OvnOuiz#u/>Ga;?3n1z7ugA^5|Z?nf>Sٟ3uiyuB]^U׽^ ۟x1q9"_3fcWz۹?/{zI>>wl{g>;Eܳ턕|њ]6߹i [+?sQ_ wLȏ=]59麜ע?=?lZH{]%w+CmҾٿێO4·#K^fڽWpӝ{KԢ]c%i__qlȞÛ>?=#_OgwמC/zHo@u>~6}K=f[Ψ ; ]7Hzio+HI<^{_w1W|xEC5-ϹmbvǮ_O%Z{>=_s~Lc_}_#o?s_tN_}YV}G }vfY7;z|M]_;gnI?q>?][^u[7g^}o/<}Kع;gv>t=t]snP}xj}s>7R%?sXL}g/*Q' f>G!y!hk|ێy@G-ut],~mI9GgC?kzvG]W6]~;n^~h^):V^-[w܍X]M]wM]lo}]cW>-7xCor;t/w&Ok?_}oS{`3sn=Z+lJ_|cًヨ)܇ǜ8kΚ˔{sg<[f4 6Tn<6WgȾ__N<MA&#kyut= ]UPUutf*u}g `CtN-6f|1^'wtϛS_W!;;|Fףzpot]k]wBҋ?xOD1oU5C,t]=BGut b)GrCU6ut]ߖn{ut=Ī]m ]8 ]G +VKrt=,G=t]e3_~@jm]wWA5uGp]K]G +VKrt=,G=t]e]Ў@ v]ɝC;޻z=]~L=7k?a)rP.\;woȟ}y<,Z檙dH,$q^oFԊ<iMZ/NZdJO$ZVu:Tv!`Gz*P]eW^]&Aԟ)%_|^.z,/_Lٹ=sϝ7 5ZtD@oXu}&z2 ;JǼ /D)akzmkX{?LlՄW\_ Df!Ӑ5=5?^p~$)E;yu}R3J6'B`E2FOF]/%L}e'z=0<>ɥ8缪bƵfJ K3jy=3SN_?Rכh~2k}G޻+镘')ߪN~>K&3PƕZK.o[%KȼEI5;0]L`rh\t韎vH5=U?D8߆P>3>GoWṯh,լuBw]o%8ԸokT'xkG=w$ٸ)uy [0ٔ\9+MוWC{?]㥼aDF@|ychw/Z5š?X4qC.\]}s{VE}@W:*n﬷:CsM(4:WSE?Knh]l<,;CBD>9(( ʓ&0 Çn*m}(ʹ./w/ 2JW:+7~~U>^uەc`;|]s-3% D7υrט2##o^RBH :*ϟI0F]_ț4&Ah RN,Go—e5i tw?]y묎+RKqs5'_G.)mѮPM~ɞ7ٶߩvUV0T,T^g:Fo;4]zsAG/ŸtT~*Wury"qk+a:JMJxAڱOk^_zە//4Jr; ]rL]Wnz^G զW?z0 }|^sId?#\*۵%o.&}VsBYzxǡZ\_V" ~jW}X-Bߟϡ9h;¾UviBchPt˸,i}H^9.d?|9)fML;mCh}OWh]ߖ}oPCmEӴ~+xkWj-z2#AcMtY_HJ4OsztG371bSJ7 dY߇pQ0,|`<u@0:x _|`<u@0>I{ 6)?Rls&l4zxBB%z.uZfC6x xaԞCi?sze|9]xqՈrhyM!U=J6tUmTKJSBm.xIoSTY;)Y]ŴA* 99#jY hVhn] /xHQ[VQs8B;_o(5ⵜy]gY 7WMv v䛖֝ѻ ]ESY4l6џVgqz&Y9}e+u?hijt^ﲩTYo!=__`Csi&U\n7(iܠv+l:Avv:X~؟b]9܊K, ]/\f䡩kzf'l7ڥXx &RכjUϼMNiA+5zP}VNxY_[j\z?`vzr=_}MBo6n_kn%(]@9zu]&EwD_]c/eosI]^STLmx2X^/\1>q_^z Z7ݧ!j@]ÎW dIRυz_#C AIL\![ۭlŴc'T 5[>SefyOkM4[@r񎯆Vxfi;d;]:u֡uqo\y'J^#.;.6ޜ_$I^f}B0:x _|_!SN9S)7 ku@0:x _Ynx#xG?q>ڵ I7ʽ>,/>?;5M,]'{1H#?g 5'd*_SڏiJe7W]G府 8eepu qBO4vB5O1嚭:-]xU3k?e0]{O$i\_^f~DvGF+ch9Us|[*Z$zӜSzSׅ~ Z/|˅7wDŽ M$pYL%A8_wjF]׈JvO>"o\)/e]7,iA9qzCҐZmA}'X[5kk]gߥ]Ma]诳9hFrRJ^Y ,t?ZE׽{k0ui,wS~ ^ejBo!~/^-t mv}#h.A-լ0e9_PzZ<ݑve[-8\))Jׅ}A#.S]OX+uy"엩J&HA_ ]2/c\tݺ(}6+ȱ+;}͡.Ct]3v]_kMYC˭Sjg~+}Yh-OMSFr'ʮ/zڹD[oUj_v D4%oډ_9"mTvU3BLR"V; qq쥄]1GS͓IQM j^1?`)(Ȃa~PP)%_u0S]ze_H^sL;%g)r)UnԊ<iW&d 0ut`<]zs?=sz)ȡFoEśð:Ɵ,Ÿ'?x#bur'Ruڲzi#~eAQx*}MGUNXz]M..LYfa\0IbRm=À=6q +[[ל\s 'Z_ǫAKEKx]4oi!ߔ]Mow+z^ 2ƷU{]J?柃0.m7 氯'tz Z.;QMKx5]Uq5OO~h\2C9g.^?˨ScuZGS3b\uߏv`dW@3.^S=p^+[Eǽu^OPtݵ n꺰6d9dZ.tJr+4BAt=t\+hB12ZӥsAU!̩t]3GTRtjtY_9awGޠɭ':q[X~P\׋x#~'\wh>[=RқQM}M)}u5J*Gv\Yԗ; Ovekxsw[ʘP-Oץo=70u{zn4Z?/tNˆA=Lm=4Z?/tNˆ/5ͯRn֔'ƕ5I?-7 kJx)ה53E\GMGNˆyɋ.=]3;M #fuuu'h >ZwLF=w$ٸ ]-;M #|`<u0|}gƋ֣ޗ2kerÅ~/<Wi5. k<.)T*ay+W|:TBD͇PU⭐۟!eӡn{Yׅ/xir]nvxE>U7\Zdy>JO|e*|kBhjj`yxǷd]>ު1"3}F/QaQ||?!%ӥq롭zиw3Ci:g\565˙#&"__1|F )hŊY:^rהuOM户惷z&t &2x&!_\&±P;V#w/K7s; wjY{\y|Kw]|}{h;zhcƱ߅iP&ͪۛ医w񞫹)UN$B#a)zV.4j+Xu]ke͡hmYڸk歫B. ak89DփYvd rz~6=q4Ubޗ^ѵ~KwaI2;)4"юL+_7S:x _|`<u@0:x _|`<\39R˺a[ٞ+\s;ȣ;6}{wq?gC6jO,xLdjO|1_ok1^k5ZaSH {u4R)*]mU[ƭ-ҷP8^eU_/d e|=pVzh1A)Jo'nNΈZ*Cx:4[#ȭK:^RԖU(כ+z 8x-gf}Y=Uᾨ]o}kDugnmBvTuy9!?MDz\vv~Vy0,xY7JugݮF׻l*|[g\sڵIU!, Jjvw7] N{]%x:gDXwⒽ5 C׋f|5W/yh~Y ۍv)>A1a.Bɵ4\~V9e} p05]EC5"S,EJKYM97e,|PD\ސ4)V[y_ eM'rYwijSXlu=|ZoyWidj ݏDwrwuoy^Lm4].B}[ȪW| ]Guu=hH8.K:oP˨f5k?LY j*+{wd]٠V+8z+9(ץa$kʽFuaw{TVJmeҁ RB׻ l|(]n+J rNi_s]׌]uvcZSr~(JA_oZ#SӔQIk㋞v.Qv{UW㭼]vc4*Mƛv"Wa]̷P?+/ºȀNDqy\\7{)}oĖTdR{sS|WOX J}x.d,@GJ;|`<a~o{ϔSN9SN,!_0:x _|` [O?hR{BS7ޚ.VZ_yZ_'U-o>Ia[qdWدrۗtD[ETso zY*1L냡TAu0`Mjܫ-Jh)tg[ל\s 'Z_ǫAKEKx]4oi!ߔ]Mow+z^ 2ƷU{]J?柃0/m7 氯'tz Z.;QMKx5]Uq5OO~h\2C9g.^?˨SYINZvкG^;BS2+ Wf}}]8]땭"E^:'T u]oSt`2|]&;t\3t 7{t]| 5PLV~FtiŰuUH&s*] :բ|ى~W\~w Jzzr񺈻neuqm7X{Gz~GCh~l7jk\,L)ӮW6V9"İRU g7n{?U?~e\-S^;=+0{|eWƄj~.5L}[~ؤ@wJ:t|`<{zn4Z?/uS[;M #|Nˆ!_0@dj@0bzs )7 kJxšҖ5IkuS%i2~ ^1ajxxaĐ{z i2~ ^1wLF :x _ar%?G/e״9=LEey+Ghq7k?^PuRJ1?4IwdZ9[% iR6-yq)WWiN9.#p1xG6ϭ%4Iwd*|_BhjjjͲwћ߅{Dn];xr>S#tέauWj{N㒵Bkq.hC딼OJוnc@> IinGvCʛ=[?qyrP57tN y;O87ڑ˅C_.fvs7s堼 C\w񞻌;5uWfܭwxy1pXB]nBCtKt|'H9NY3Ebuu3^x}ަD3[ڸ5j !4 e*y\T]/cVӎ2^{4V~woM.}BOX]Nqq$L+_7S:x _|`<u@0:x _|`<\39R u=^_;ȣ;6}|wq?gC6T|(LԞd%c.c8je9< &AVKFiRzCUڪ[[o%)67|(ަy!3'uCi/L Uz;AgwsrFղVѬܺAn]7^/ EmYEh |fȋrmwe/|\5jַF$ؑo*Zw. ]ESY4l6џVgqz&Y9}i\]b+u?hijt^ﲩTYo!=__ ,֜vmRUH?*v!ݝ jWHڥz\O`cFuixp+.[0thWsu{Ҏ܄~?_ixv]ʽ%])_ob(uI?V[DfatҸ[gg/X岯<]~ Snk?ݯW*<:'=$t&`VB󸻮^nyQ'\eR]$yO%={\BPv$fOV]O5oƛu}폩$Z֏z}JPIиckeGBP'Jn']Jt?}|ݺ*G)7+'F3]rlmMӎ՟Hv*7+_ZlZSw|5z;4Mӟ޹' Ye_v5B׭["֑1oq({(L{sv` n l#>$y :x _|`<uK~U{SN9SN,!_0:x _|`< g}QZAXn|sC=ծ|V(_[D:k.Zm B-WGA4-U?R=BM`тhV.-K]Mf˥ؘ3t_ /Sz YCjoHn; EsI juݬf)_DSͿBQ}elVַ+4juG\o!4 dMHQ.Lxqzj_ͳaL]W:0A RCCxߊ7͇au?YqOhjF[TRb+ԙ OB eG:I? c2 UnYݛh(jznwqaZ2 [%Ir}07hI{UX^ -Lzrsr1wdC x>h[;q>M?Ҿ.qEϫA}?sok]Xs`[嵭\CosAeG8q+B*4}iA5kY};4lE]݋guj|]75~45#I.ZkGVJx=3ʬ5ø u[ĺy ^J]Y? msN3LƜ dǂNk_z/^Fq)׊h.mv2 It=dN벝t=BZ7;Qӏq +A [o_Ou1^qm5?㶰L3;5Fck]O?O5||ЏQM}M)}u5J*Gv\Yԗ; Ovekxsw[ʘP-OץouN)_uAÍ&SCajxxaĐ{z i2~ ^1wLF0_ozn!faMi~o\YXSCr4?rMYXCaj׹;M #=Lm=4Z?/uS[;M #|Nˆ!_|`Je=vL%_/}-T.vE鿾d]w}[xݦ7IrĮf|ݕ&3l^Sox9]иw3,+/:^DK Ⱥ$ngxeB\͕?qh,ݠWbCzF= \A.g&_wwSzX*;++t~!"7s;橃C\w9zM>Kt_j'a|}ຮ?ggx]FZGU~>PJtK롷pJwDz|@Y3Eb Y*}xьkL~Y)5j 󡙟 ]^D_aGbZ?f3H2z]{K3/qw+1Kx_jVPh'.Uc%;)4"OhGS|`?w>&h}A2~iִR|];傟ʓDq%Qi6@}WB!kRDx,K\Zzq^5 WkuZ~Y&?(T?t !KWםuL_v]~i]WYu}.;R|Aqu4W2̷3%I^sLU.]O啸^Xךs*5c\ˉJ uQ@4"DZSlr g3OPJTYD^zA'sgH;@t}ǘuE}x7|% Q_Gz~!MQ|ni3iozEP8ٿ熨|`<{sa7\>P./yu@$]?G,>K]\#>}e>jQ endstream endobj 84 0 obj <>/Length 9818>>stream xy&U}f߆WP8Ide00QgD%JlЦEHH" E#4;D$y6io~[VwNZz??:w~g_mȶ_>梵#:M{u'u?5ӟ<4EWXдKORN‘.C).MA],vtީLuWӬ=5Rkdrr=otހyC{V,f͙n٥-58%18U*Vܧ`9^#~]Cl.u)u.t]^ ]G] F:R^]Butݵ`T/s?)/ѣSAV,[)M>t]d]>s}ƺ?uR:O]XQZn4u͚tW{v髙dw7.ϟuRb]폢}_'FNuAt]k:;~0z 3}_mhˢ]Ww]+ t] ]?jWbY^/:s7Y}}dct{u?raG/q>+[0m{qpw~ⴧrnɿ,N²>im cV/n7wۿ޺77*xͲgmG7~~Is7_xټ٧oFץtݡ&.R]C-}O鳔LA{-v>6v3O^GW.?kٶ}ߑw?y0X{_({ғJ.^Ϝڤn8Oi fx%:ruֺkV?oc雒]|y7*Q{Κ,[꙰ac)߲Rz .2JBe/M$dS|t

DuzB~;-g/Xo랷|z}Xo_?of/b{7/IN{'d~d^Q -{&N6&'jxG/uWorғWW.7-N7m\q'_Í;S{^:y3Fvߕy]QeB]}G-W~uv|qyѫE>5[WNzc5~t;D?do1_~zQKmkzqXM9uS>eUv];u ݴ?Ҟz\EӶ.YoE ~}?߼:{ׯe{$g{̻?3Ϧucמ4+|aۜl.KϺ/.9_ݶБ о5sx;-=t'zȦ:'GtHFV> t=߷yX?eBc]7뉨v?92J{缿ڱ䐑?9~N-=骦l"~KLp1Mn4|̶.:_vGo۱EfD}Ҷ ]OAEuY)t@@]J{T)])tħz"ɵTn><ՋSnM+R_x#ƾ흉w"(O7߻~M˖f|OI\?}OB7-7yb;N߽7M|pbK>2]}qBO{F!]O6[{Fu΅Ǐƒn˿5q=_E䛶<򼮟 2;uI)t@{GSC_cgOjp7?Io<+΋7||l~%qO?ps= Oɖz6^#{ΜWYg೺>m'sܷ={},=0r7um򉶔b$411ڿ~[_lٿ}HuG[spgu !>~늾O'-g.Ȧ9/|uٱw+f= Qǯ_Ǩ7/j[^>'3zѓc_wX婮__˄?tc^0oΈ?NX+_c_٭vuR躤>Anyُ_gp(sWGG|+W}x|/Ѣfv䩽/;u=R=.:}W7nHǼM|mձɭ?[>|gHn[Θ5NhgX%36[F:uNOϋ)t=77xF췴o[5W >m;\}աHJ?a)Ju}Æ;Ð8Sg-l-:Efud;$L (r,Ғ̉d]oX\#eT+toѦ<վO}C3u:t=rTw718N>i(C[HRt1iy1ξIy@t}Ѳnp.ʜ ): ǢlYם$ͪAazBVtCt/vI[vba_h$9]ωzB*@ <<* ϋ ֗><4Kz@io"}xh>!@Ċ|}\d|;S]8UE*>Dwߗ{N~}xh^/VK_"wϫ=7_H@ٲa'Q9k4.:ju}1ep]uP]ڨ0^w3spJVuUo+x3N긿Pu uТ& my> J\]=іF<uߛSoﴛ}Z%fupz{)U|?N( G6]޻!9=EP-y׀+^tjE7;BǬw E+oKU_R=ueHh* Rzj@hyo<-$WZSCTM=<<%wkox~yJ@Sꤪx'Z^4&ӊB6yjp@Tt={ʔߍ޻+8Ix»̻v<0wgnv{vL\235)o~F5ؐ99%e'Bo픜oMCh?u+d*WUi+Z(Y#uwW?-GAM3WBSUBWp՟2-%Q̉tzl=eF0OC[$rmtjJbB^?VH?^!_TK]uԜUpzk^GյPrt핤XcE'ic7u\ ]Xiq097tn0O 47t?,_ם'F!t=Kp zZ(t*k|z!WU*]{4Li [~_5GҦ]?%u7Ax^rId=c8֫L`m!1B&Jz]Q{Z%[6ʚ߯Qt̸ ;Ys#RXYRz2lܪ0^ώk1N*,!6F&=JV*t5-xVm?gYaVQE N^{m%3uReư'N 'Ur;NW:QWZBzt݃zNbTxV-Y޴ucFyM͑w12"뢒z-21_TT\H.-#աdTqBVڟ:Vt=|31pׁb/Ar麮z+:l*|%^yb8rsZHE.(L7 P@ Nvvkut?KڱV"9>]J;3^I!`?[qrү^o^ǂ%^SE9}Nzzt]%3k"th_X\\c#<)Bו2+ҥRn+ەEx\4'z˜C)iouR,7.oߺ]J0y\^C/ZgkZ=vATfH'm M麫$Hhv4x.e*x~iW(%^#!Hn06SzsK&+TӥhGO@j#[*E2WԟE%uˍR|u+78$YоՈΎL?Ƚ7eejk W@tu@:@w;^/e#$tI't%uu@:@w ^ݡx_7 k'EaS?W'~O:_na@2gjIV]WOK=֓ϖ@^в2{ n=2H!%Snvcpu]GtSv3.\%w]۠뒱 {ʏR6j]O$CsWiDe|ʚRVjoMyˬ[NTe^,xk>uvc$-2_8" ˨U|s3H$ (xk0n]^ hE!7<*Za>50dAa~ТxJҖxC:@wh{s9=N:餓N:bb:@w:@w ^݁x;tuЖx:'(+5[[6穧$T@{@^zk:-zk'O\ imkou\C=6(qTu{ UͥWQWt*USzLebb.x9?hjb]C?4cC5Q3;^}/;A#M?W^t@;f]*ӡVKՑ'~UQE?XLU#oj|sUoPOC1]I3߇7h r\CkuNǧΎa-J+ WrmW־x~_滶kPƽ_u{';N]TO{ N5ףlY?I2ѴՎ"׀Aϊ_gjq7z7qo:/ǩP[ nuzE'3t9^7d3;TМurmkuo#:.յ׀SŸENmhv72 At=df;M麇NhKDI?JֹbQ-/d{u%H[*uP(jC׃'jh>-~̥gJK .HM +]Z[X-L4;.QeS:c%o2~arvjhF濲u}KZe̐OݩFs?mz-@y:x;[php2l@{[': a;hp2l@{[':L3ȾےbbB~ĄI?M/&&OK$@nasip2l@{p°w8~a- @{a x°w8~a:@w ^CoqSRKz/m 81,z%G==1D$JSU!1l:u]h;HvwApCx]}o(RUƣm_:P5=gq7#? eymW ]+S"ãs^y(3X^aYmi)3$OIio^tK(m<4COtUC{<*e%e K^U(ti{<}_ endstream endobj 85 0 obj <>/Length 10350>>stream xyUuN:t'DPT (3gQ! "((KsdC("Q76gcsžNҝNwTTW~ֽU}?$շ]~W۫lg_''oO']nP?0g_^ӧO%f&5Uxt&.6. תl'Mbyޤ4yr\{z^p-)垫wٛ3ֻ]Kj"E1X1*_{nR38ASt]Cu zuN];ubt^ ]-es.wƍ߹W+Mנt]t`kI]+%]'B׷t)ceO/uCסÁp-)ҡz$q5,H㺮[7@XO:Q!]N1]݀ J>͜UFy*=zAo.Z@)@9uuaB)t}f71WvݻeC_e^}Zonݫe֔kwL?u2i윷v3}`iVY+{ۃuLom{a{Km{cFV_U}hz`5QRNR;?4Tj}_~0}嚁\_kLןl{|y?kql&2TN[Vׯkִnit^w/?%p7?|&^)EAwt|}cb.ߴR*]caߐ1p=-)ҡz$I3$9;׭?1kvϛm}koy{f{i?6/x0w|d]VطK__j=mPȫ?j|q`ciqw2Mn5oD}QĖRwuhmRӬ 6z#.|l˔ M2 ;^[i9cY_s܄Kh2>|gǯ\;ouU*/ϟ{F^',u_O7_7:wD/Y[Y)Ժ^ur|: ג"WJNYB]o+O1޽ŧQ6giߛqXH).eQ׫ԄA!,l?}mɷoJz(u_sTPM ϫN VQq=L_ #~_xj, &;cÿv=jkJ)g0gM^ڮ3zgPR=+%t=tdN+]gHz\"Muo'4}\>#.]yG>SͫV+ޚPq]Bmpc0Vê;Ϛ2:q\p9[ϚtGM'^-=yXKMC]?m^["}OGmz=Rsà԰b]ww^~#)ۦvLlGsu6wkj#?Q~gʦ^j!>5㝻R1hi[ҭ7>x}B>e™8w~>X&ջ۽E= V?7fWw_u(qX܊9el;7gѻOܻ~޳IS[.\|k\d+jgzk`kO_OS4uP}k߽7zηH䃮lP׽GDLt}ﮅ~v/ᾢkyab$"]:WׇmWno_=wx:]w3'oUSz_=溍Eh mܳ }t M@AFՇkeO]b]hO9w:-~@x:>vWB:<}f{د_+2\}>ƾFĂH{u߭?.I6 CnJx=G{w]AcBiE{kgn_k^Y=kKO\&с$ۯO%/=}/?ooqc"iu?9ؼGQ>w~7Cס뚥ĊX-qiJf >Jw롄#]zpW?g[x;@n[~ǞzeN-(mʶ|BC-jrN{Y~']syw{_cO[7qF<^Ǩ~sbQ޳yNkݷO#;^t}م|pO`ϮwWǜ׹~9;ko urNg-`{m mU1'{c'1Wέ09߈n@uR|uM::<7^j⣐ݝG}媣[|^@{S=nxo;u:|$Z/]#W}-|ɇ~h~0=c;0P J{}SwwZWKʵWOnI;gqqC뾖'=<N9]n^'~essI> ]땂+B]_끡?@N0R7/Rc@ןG?u&tiu=Ե\vޣk;EHsGN`_ì,3:"q]S୼P=>4=pH}5v[ud<6_'.%Џ|xn,uWj/+w4q~,Sb[uΓCvNZ׷iEt=|\.xmCUKuyVlW=C^|c뻆Wਗ ] Oу/VM <]mꆮC5KA׉IS˥b2^;%EL[?8ݕ[Ξt柽s׼ڎ?x^/?K3yk;յ;ux:ǧv-ϟtt]ߥ wi=wtL k{yۥ?u+JK3e~ !\tB7h橭;'>_]VgN:&ty;v N߸^!H,X8qt {Wd /ٳg\FXu^~%:'괐h(@ G"QjgE{{&"PiJPgkk[tHs]hB=x4"Jj'(Hr[׾r$zš(R B=Mimy@S$UZ !zH o锶ȞJ<^^wEqs.Qb]#:׭L$Zun)ut)KtJ-뾨 iuRNgv^F:V#QKy~Un~ezϣ%Xq~[yn\%@IzBC"i7nXճ[չPyxiF(8.,J@)q"Zϫ+MYq^;`_,;x@|E^RzF><}069׎%:].{n>|OﺋHvx޿;{_@T@:><P_'(^@Ju~T,~qy=2 @ip|gpw_yG^GeB!㩒E4׏>$`+fhT~֮E"`ہ*U8s+rﹱ͌#_c)ihҎJy|Ģ]ӽ$6r~Wd /$}^o~Z5X.l:Ųnqw=lh="3?buP&6?,Hu_N^5t1kS$'񖵟f~﯋)5T+ޒgv Ste[: Za^Aן4ME4q-^W4^uS4ͩY)~n+,'/w=uݓ.r'Dː'XucJ\]f %JȎ>D%"$ꎋ+DҜĭh5+򲧗7{@@\8t"@a)"8%u[nӹyM\1.(<}NO7fUKfϗ4VNm.|d?.9utN~K?ukmxT*w]ϸ)T$7R:+?`Z>U=>4M6^|JmE7^FJ4K))MVċx"OID ++lU[bk\D2 q>^빭y">1QV:.OsA68(u}6Ӣl lO:ۈZzF9 7omVJnk\$^ɝd&cЮnO8<lk \|гqG)pqÝr?t}WYRTdz QL]'W!7=2>,M9"]\AAyxs'؎|X/v]?DQrO)/bu.Hi.뵣C#{|q3h$5Oy"'Z 1Y?B]Gv$뎋mk+}R:'?[R6J߬Qt=͸ԣkaxk= _rx=>)ST*(NJ͚n~J;YR7\]=x鎋hڋLx9:$zF"%JOy},͓{<?W:7Wnu$DnzpC@ ^u< ^u< ^u< ^u< ^u< ^u< ^ʃ_׽$ڦ'uNf+ ?%'iA+I{O"k~P?e0^k1r,[A(ت7%}ać(]MdǥE]]8^$)J__)u]ϠT2Ɂ)IݎNǝu%y4KԵZt HBac Ҋ=Fț/*/'qEr]R2?E;ܕQI4TRdWaП\g&#gl:/rK:]n| <^`ht.W+gջ@-WhQۥtDKo[4h.CC9{&/fCT+1n^c\c[](^Ý{)u=N6NV]꙲Dxb:Uh-R׵lڗXHƋmWqASXzi!?_\w͗ҁ,zk@]7l]/%VWF@wY22߇b%,& K:P:P!x}ϋGt#HG:)lbu<:P:P:P!x5"ƿۘ>ߓ+}V~REuizbcYpr׳Y-Z2.q+#HR?Er7WEKt:kǠr՛2g{s#=,=ѭ-¦s&$tU1uޟtr8SL'FY EוQb4^D7NOk$A=v{z(cn&˵0)Ov~&XKMx̷4ǝqh^qIIthxs&%̇F#x]it]W_ *{Ć[l\ŗr]gM'8fU69MEtnuMNη4T>w7z6%<[{<|:N 8lsY)JHizaʝFוPh .r)!6] =sT݂Srq$9o֛kDžrJlT=:,ۚĽLWnRuFV׉ClpeQSC]I+J%?:d:'n S׉4 z.t=ͼԒޔK׹ g6h+}N;ǝ~<PEiu=*B\%~:Rt7ŭTb_.$YO;(Ӡ=/QqKV~ hPZJ4%7c?qD(HQ맕tIX1Wb\>.zEdѾ C|yP{s|zak^a~ꂢ|'Y`~HIQu:P:P\{HG:ґt#]& ^:P:P:P_,ίs*R,͏"QA\>&A괎O^J{ 8.NK ]mW>~_붫^=ﺽlPEigO=T>z6f48Nu_v$q^ =c_d_i*q7"q/W:OG^[.Z$n-dO]2lrǴ s_MTEu]{%hUjQL!Z~t湈|.+`EmMf[.TN#\7HPOLn}?.avBl sD?za=$ﺽpZi@׭i>趷(:~LRS%.Ls]Z)ZX-L;.qQwخ) +!ϭQo^I):`=#bq:S@y@@>\ @{F x@{F x@{F x@{F|_O= lbHD{Đ~&Ot61F;E{F_Whۘ4Z? ^Whۘ4Z? ^Whۘ4Z? ^u< ^Ci*R&~LMuw_3^#Ŝ,h/h2(o.^S4]/["ug3$"ᚢD .hѝ9yN_7Yۛ(HZȽ |\lݣXz4_b*?tѢk/<$/> endobj 87 0 obj <> endobj 88 0 obj <> endobj 89 0 obj <> /Contents 90 0 R >> endobj 90 0 obj <> stream x\[o\Ǒ~0j~ћ778[&$ (rH1Hߛ>]ԙhfX^]խVbj%oH>Ar>VR¬N.c4bGYV'~ǵ7k3<~VBFh?hic![~2plֺoS;z3v[Zo"z]);xxg_cj5"qG6׆߯7bP1xU-( F{: zhʛөG*]w:f\e|6:5? ]#n"]Pu7#Q/AUb ͍|a GLɨۆ%x֦BSA#DQϵA A AHAn\IZؾ4YWpAuSNL~@ɫU"/{a,m #Txd6*WKOG֪Ucnh:KOhqOdsH~ ?3 :cwr_3QwM־b@p!5MBDM[GQ}tH p0hJY!CSo%k`dUS>O#}< _N~<7Frch. "!#rjpsOmH-lJ W&7J~(+֢?yd b`H12 ygv]q<;G0+:uGN-njY/:ycD1S,AqK=S*%֎8B3KDNov/L:"}n=5YQg>7bn ш+[Yx\ e\}הX#]3 [+:{ navàa[vSQv(F/0FI@gct(J*1Y׌Էy-`GɬYNɂoYŪ{)c0Sb`pT&QD}~FYL)\{Eً }KUU~y۾nĎ!皿! Rug]-gMEp(}0 ~q@f0opMDK s8s>q4[; ;q2IB<ƕqI! 4/bw-,m_D~)BUydwTέZAx-Iw̐]q@%Cz/] @( q*LL뗉J E@+/ד4BΰȄ!X4"':h]6f1Qz{뉷y֟qԽ4t_ZBh3-,VYlQ7 HQW 1._K%'!格pFHkfXw$$QzK1o)q2)f2l/FE89Ȁ+mʯ>n._jLTTE>6+S#8\`)s9OiYVbBJ33ZqD4&^]gz\ J*6XaC_–:МN S Ns,/tq4L/ɘQ3ɜמ\0<:#q\a;N%} up>X4)`kG5z׭ .+$ӎsIsdi w :` F:щ~bJ$:}^L(o LֳpOؾ̓|VVq[ #m̐JfH_IbȈQ$2*BTtѴZ3g9PM?1PШjY3.qU[{60Lqf{ 4uҠ&nG`uZ/5ĦlݽeF@44Û{:{zkHhMʒ4[lhiOlhl2V ʦZGP6uED)!"i172u!Q[~4+kU'ŶGY=&Ҿ˭f  JՏ47lɻIT'at[1,Νlj9Y;+Lijjd^c1 LˡCjHbF ]ڣ9^3vOO#d k.=ҬHrs4)5t_-`bE::BOw]NںC#W2|Fdà2YO-&h0ه NG6wLle57!GA"|.3b&5VIa - #gVS@Ǿ\b!GzϽG -1=uӕStlu:xsS'}fwT _"cCjvǾjLzAZ%kW[loZ -Ia#Iqkm*_bnnP)b3dP5eN PԯRS$2z70(7%eדo<UǢ W%vh DA@#[hp̆:/Fȑ zR\~PMFZqxZ:\E&7% nZs;沭~SW|\3Ű ʮYKx]3 F,5hLlUh~yްy[H1K~Q8CK_9ԡ [f,6&#ˆz˨%w/\z&︇3ezL+1KD6*\2I)}X_^i024 2x45A|xll~ Kf ^ۛ~!}/ӇUÑá y*a( pظKS_.>7)N3'qu$QRF9#:X AO fC/YfTVp4U/ t=KWgX9] 4kE0Ԁ! fC{$<5IfmY nTT76"ʝq 9;nW! q"X5؟PI&J"FS|~K<әqL[ ?5בo▢˿LܸAGݛ>QE| % vdZ%Ey>z+]t+Urs5 >#X'Cʠ1_sQ`li2  "+j9P/ ǡc(g2v!B:y~/:ٖhY$: S)}*+p'? BXW&@WrBO1QՑƭ6ڵ6cK18pi*5V4e\AG2m^PaR6=/rD})鄔Id"=T*_ U̵t?a-eel XkU][]dW;^y'Kh1_y+ۆP$vMpEkӭRD?@Ow$ny> t!#x>| <+O<4x>K|+< χy>D!*`!G,<-4@l|V`SE_e(@Hjꅤι֚Sa87@m=xK 9QTa)p?mM*hDlGC+:\qK}S;o'`W/EYʵ'a0tVm 9tX5dr:5-YA^.3֩[ڵ3JdΕ|%j!jZkkS]/Ol׿$ڶ8) ͝f& tk0ݡQmrrل ~Q1B9$Iژ^oĴ*O޾<~N~ ;->G7A"K_*oN W ߖ\_6oחV d7ixE5{k}%LP[cRAH?& A"xq5ĝWz/ B^#}k/ڸHZ 18oC~ҍ_mc:~czA|c{=ϫ@|6|4tO:_soloA]D 7*VȊEumf,D qe.85SJL^j-UyѯɫJ_ xL)w0CShzXT#;Ƿ- w:֪ܽt`$AYM8B7*1"d󁫘EN[ zEm+)+uXWq>^+0Nܚ| 뚓cMߵba5J4 N1Ŋ8i0,*˘Rs%q]{v9^[=|=.G t@<=mvv葥P<]/-KVFt@O#O"}*\ӳtrt@zY:9zNn~yz9۲tr/#Keef6[f,3G7 t@,},W6%ލP4+fƤL”VߒWJ^endstream endobj 91 0 obj 5493 endobj 92 0 obj <>/Length 10658>>stream x{ENޓL&I&6$"geE+*䡼A ǂH"y!Hͪr$ΓIf2Lvߞt~U]ݷGS]߯]MPֿ̟'7ׇv &^mgsUa"%)%%I"cgGnC*Aخ=g7) jzE R"3y/]K r^ ג"]z1c+]R.ں&=st9yBu؁3@RunA]שc]W삮KAס tz>sϸkCסÁp-)ҡz$Ywu %?uRu:t}8u%E:t] td3#]#fe\:=\uKui5'*#dYZ@)@9uuaB)t×(Z9"ۺ$-ttc]]&Bgx;{UoZUv=0'7xfs^yNXӽ}V;}IMcg?iO[صZ,eO߼czkSV C_vX^;320|u7@WVGUT_K99H9iP}nի>~z~1]>ʈ#={Wnlu&2TN[Vkohִvivp7%pw?yF^)Auw\!K\oZd̅\|Kt^)A>:fAue Iε+,u}>?͵vϛmykuyf{i68w|d]VK__Wj=mPȫ?j|u`cmaw2yLn5wD}QĖRtjeRӬ ׿y딙#.zr M:^n9ScY_w܄ˎh2?|ǯX3owU*͛_qiϥ?OXzQ#v7w:D׮Q[Y)Ժ^}r: ג"WJNYB]o#O1}?gS3n/8U}2|$]kB~ koя;7&=뺯>*(uƄՇyNY8Ԟm+~_tkj,k'3ccz}jkJ)g0gM^ڮgPR=+%t=tdN+]gHz\"Muo'47_=#,YyG>쳃UXHMwMz(MTnv1^agMS8vgO:SMl;ڴi~ ~>ۏm .xgM .q-/l͡RP`_6zfmOouXע'=ui~aPjX{;]W)ߚ;vLl ˗&>mκMO D]_){{L-]שּظuZgtGKӦwl-ECW~):hsa`Wmjw2b7k zsmW[m/.3GaWf;zɁ{>{{0ijKesÒKwQ+m|ۭ{nz0#M5oͷ7?}Xk@uR|uȚյO/,%WtO_}-=Ld^ta:9Wzh}򢮻my}yYLo+)~tSs_"s~iʄʕi֊ u_Ej$N:ͷ~w+K^_skv%[lz={wCQﮞyPCQڑZW oonx ft 7u徽EgML$'V|b<];ڂ.>:έwϭ%]K`Cs;kctnmhcH Ag9378 ur^턆oktηH䃮lBuv)>Z=h/\st/W=w<h.|~ tɹux״rȭU_?la1r}pI롖'н%v盯:ٗkޛ{6O=! Z8vRtŹ-c(X6UjK:u^):"i*tuxuo=z`/;658\Gn1u]ٽF?v&tiu=5_v^wt=8q21L?ſ YCׇYf^zޣ(^߮zKn:z=r_18mjk|>+躦%E:t] td3S]ȭW1 .tϮG/ޣcs1ʻa+Wgr_oBuFi͕=k#kΟo<w/{'w9 _ΎRt]{_)k^}IM_>/A= W)׭:I!@t}3p8pESE>.~q܉||ԋ˷mQ3]C۪9w>}S7E}Sr}i;9 +}M雃 <]yǮ醮C5KA׉IS˥b2o\%EL틛?8ݕΙt?{}뗮o_)xu<涝r@:ϗ[_}k۾;L:yByxCS4Nkc:&O ݺCߥ{8nf?ΐ@.<[G4_oFlz]/.+u&c _oz{ Qu_}ړ4Z֚7Y|GV N/]v!UZfq;֯?c_L;Sײiy$4F)A>:&ttㅟuu<+\Kt^)A>:&ttN*]sp-)ҡz$u:.:t] N/]X$cktݠt]t`kI]+%]'t qZ8 tb N퀮k䃮l:"]Cׇ]'XZRCJ AI6<]1e\c_ruz||W-Q-|E?/[\?x.#: N8wP~b/\=]eAoѶ'nPiJPgkw܊mu\\=P%Jc<P_'h]*]y128KK\2{=4^MwBC0RpnHCg魼G*(V~D bbPYX/e~Fếٽ xG.LzA7H7i+^ z=B;@En{Û]ƣߕ>'2'Z>/37R^~18Ie+eY+y /׸ߛybD·h:ޢjj1'к<ꔼT'Td_)Cnt] +]9;gȷ(I}G]Ytn^C"Niqh=Iu=e4*7 VNn GҘ_Io\nq gu=zsP!˝!.\LuJj|x!j 8WxBLO_>_[׽E<*=Q:@v<{ZP^ѪxOD)Q:EaeMj_lVƿ?e8b=5N$9{<&Jg4dEsN-J]?XQ67Ǔ6yQc>ȷECmJ“^m+D H$_#J+V5ο/:lzQr {*pǽOn:]_*땟^P ⼠wt0=AubqyO+8ڔ#uy\t B߇:w0YHgrkL%"f,Q≔t1[]u ^AhkDNVҹ3% 1:8.#c;yŶO㹕>6E-)R7 ʖzu-?_"7l/ysKֿ Z)*CIcŎfMqA7OQhi߄}eP]ZG/~ { v3/GǑDψU9D)#oq~sGt9 8GBDR/~n 48y|z(Xu<`:P^@yz(Xu<`u/d)I~b`:[AD?m3hxEtZ?ioID}Wq44 ,\#"˺VL hI_i2$JnDWqju|kqWDf=W1l{d9n]3( j{reJrRE~gA] k#<)R*u:R)PcؘDz=~DqѶ|\ܓ".JWĎ%wfTNm&M#նGd; ?U'tDnwHڙ8N;zv~ΫRx5Dם#kh1-}%ܸ `9|s9_>J$qtuټJQg8NΗ Q;(g['IcVt=} DYQwH)b'1-]5n-k͏ٔ%թ۝2 yru]Ioial\Jz4>+*du<`:P^Cu%?/!ґt#H!X:P^@yz(X!_7k\'E1|M%'/JWP?|i^5kd%le|bcYprXg35[e]f"VGiDnԯ7Kt:kǠr՛ҏg{s#=L=ѭ-¦s&$tU1uޞtr8SL'FY E^+ia'eb*)ʏaJDSrq;{dJo~ZI'z%"W?J+`<' E7ȧW'.(q '^(z))zz(Xu<t#HG(M z(:P^@yz(EY_,ίs*R,͏"QA*^G ['v㚺w-^z3:(¶3ZcE׋;,V!~8rdrm_^*}uJz5;0D {X9?ȋWkΠrlw`L.t=c^'Dן]I`'_7k/T8WDrtۍh_TvPU.ϫ7Pt\p13scx]ا{*:>Ev${)_Ki ]ҍ+n^n;PxqK5]ڟx̜*(r Gg?~(9 it Ҏ$XzGJ뿼"JS8]=^{ǿy?Zu&qoQ%m|NgW&VyD|s(.J~y#~[ S4JR ^ nWr~*eLMOѩ9ys>S`h݇+1iv@`юۘ4Z; ^Pb^WhmL/(1X+h6&|_O= lbHDflbHD;MgC'E:%M z]A"Ƥ x@I%uv< ƤF;ochxAz(Xu> endobj 94 0 obj <> endobj 95 0 obj <> endobj 96 0 obj <> /Contents 97 0 R >> endobj 97 0 obj <> stream x]nGrϧ8ȟY~ $Z8v&K$H/WQR}C^YUuǍڈ{8pO~Hl^}< N:|aO/iH9 yHL1F#8rc&H7Yy4LƇ oL+^v˞ow~x_L{ ϢDz ]lB VegǭFJHd8J5NYIo?;Lʲd9tk8R&N8Eyq䖩..۵ކɔfO M;rZn1XvDiQzOzl!'=~!LLstj2<4$o{ VjBD5L؋\`IOd]dF%u\t݉I !L(8R'fUBH"di47 EK4 }Cǩc)Xm/}߇VsowK&\CJ\0#7#A{\2} w@ܰc<O8R>C]keMԆe^tDʒU:j"es.(tki,x(vHtE".[b Dжc4eEQ|̤Pl f_q!uB&9B mو9M@Pi+ы("!m.  vRf-'/B8Rt qt&K!Rfgf&[/+3)"J ]eL"E6ej x ooK}I۠ǥ6%Ң.m v&szl"WH4ܕ6qB٤{Էgx{XyVdZR䇐!|wxwpw'Ga8rQ>gV$\Env\ttY&y0i^RRv"Q0ݹޤ}Ee}Q##Ĝ`֎fQYeeK۟Yj9&W${{P`㭻2DYzfC|9JF$| JN.!%du6=qۓFoa~gYR6sz@w/v%qF| 8Da`bՈ粲(Ahp&񽹾_SK^qtyX,NGk\g#.sKܞ I??iʦk=-bA \q$ )1 X'.stcj)g yd--e ^gGx gz !Ñ!aVդVD|K;a{K[/-fȭ)Jo\тd'82C%;0OC3[Rs&ue[zk ; lQ҄K;_3|kFD$ũ dY+ fMg |W o`a͞n` |8l<NHroϻij2"NSڛݥC.}X[(|!{=3]!z00x>o)|'eO <8Sꬖ-qwXbpNʈDz.{"CO|1+|+amȧ4+d{1TlR>)ٗMsVyA9=Us0< H{> IBj%Tl* 7{Skt@A]nl^$\E\^lt>0 {]TCSDWk hw儨 G &.\dNcv à;nBݐ#-UĤsF1uދ`K025 )DH-| %pQ=b6xOa-kTCmpkc%-9ަt5@_V3m% Zi8չZG;Im e=gt%G@)hg -Ǡ#SYt9KGݔC;4w琇 0(9|0͂@4f%Q9qpUkw?SV _?fg/, 18ɖ˫(hl_ Lw@V@ gB9ߢ]b/|+aMDC Fu&tu1b3lM 42Y5=mg㳟%&"HA9|q? .R|̟}ۓ̓p=euE0wsw( *N3 4aff0[(wh3i4[nIځfwL\ƜS0a7aZ["|Y8"z!oRER\,K'ۧ\?@ #V)Aj*׮1vz@:"t,͐ԮO#! &9XFYվ wG7 JOAtȏ4U8*ʘr{=0?k,0\22]&O? Zr\7@"tܧF/aE+oŷ;F TVV$SZ kBã^]_ƙ_nVUc QX~wz /W#a}_5pߍU-l`wxX?@ 'PT{cQ+H{[I2n#K.Aǔ(* Q7v׃Fh68rW`E[SX3+⁞i:,DAORБ'^vb{VNygo2vb"SfnYa=rq.z 0ƨՈ1"^w$>[>FbY=6SG&cʹK#ZA*eyMo(b}sþ\\B0]c@kGV1X$ԏäޛ,6{Ӎz|˚'$U|L}zc )Ɍ6O?m.5o}OoO/Gj#զD?t'fh_:h:8@-Ptҡlm̟:Zg31J9~J?篒KpEGHTB;6F={i6O t)oqX2kecMvK 4}I"&~g3)}Sa; 1nNq;ĺﶻdc<9& ʦ6Fiu>?#I_:BZi6`s].7rrŗrRn_.r|])W-wqYy-)k!5^j{Ŷwr'ȗ&a'ѹ/R| \ܖ^rF47(Wfp5l[)sƖ d^-^Ƿm/g `zrSW]?jѧ MF%d-4zEc[Z5N9ÞRK@a BZ+pPĵKm1K}JgVY]h 6N,بCӡVirj 57ZgD_/grcaYi#6p󜖛8~ѷyRdC%'T*+;k>@gs@V SzV՘= U9eRJ48M\~m3&m[+,+ ~J&K&+!%,\W8vcg>Q-^g2?({V-#kug}!нKi ^ASUZ-U3t8u$=Q="臩N92z#4F |S:V? ,S>lo3p <%}S<r}%>OEq(-?8^{^S]kj󵎣VS_@ud~~w3gp#O}ms_'qos۳†}8X{[)d'Ƨn7n$seR3ZQ##WO܂lBY`֌ y%MX=ʖL[NtGIWs]3t(/E_L&B4-MUvchPQ2ɠoz_kxH:2M`XZաF3F٘H e ʨ^\/{cwQPI%5Q)YG49&=h~*:w՟Y8a4/x/35}2UZ5{Yhڟ|tHe7L1O{wĦӯ! F_ǣ #W3A; &zA$E,1官n %Ed} vgVsѱQ9biXb#M;Z0/p9)w g ~%!d Mqc7Q&D QX(Vo:5hS4z9LUJ#T˾|dd}!uׇSPkLuP^"C'Pkw9I-Wv i]j߸e!;*MS[`p,MPz_yErf%#lr$Y g֩'(vT&wKHW4GnjcbKަ@dIKb}- N=!TC!DOR0^!x`~F:p5.TS{&Muk,.o<&=*)4\ثay%#{X_҈i^z5}~s0r(d8t .|<'E_i15X(S2Sݷ^奌(cjSQZ+J^z5~~U~d~zaX1L zWEP40s_ꈍ0[$ey"}ίjՏN$ 3ҩMM:b^T2ߺ'k/j; 6t/팣݋suX_k.oqNcLO K\iKqN9b}R9uyY#jՏ,o6֋ai?PDBuʬ1*/VʨLʈRiuyY#ZcS gKS]&S(xꀎ ;EǛ/*U!U/MBk\Z]^GExZnM|u~$$:PAKHTZ1kP^ÕB21[s} ]Zʈu^tDzRgģY^ ,2RBL+eFSzIqj/F"_ժY<|ȟhCȆN8a}hA̛^{C.L+ï.tJ7GDgúڋZ#=UmՄ"su=7S?9G5xs S!K)> endobj 100 0 obj <> endobj 101 0 obj <> /Contents 102 0 R >> endobj 102 0 obj <> stream x]r}WL9uib=9e'qJbUbQIťMR*_@eŕMŖ4 t>}w88}}pLWMuy݁]ǧ[MX⠩aUj%+Tݪ7߯Ynj-ka{wNFx,zѵ`kԭVzu/߭7Zb"V١ ` zΝs[m}35̬kkf5[]7U+z>ǣfHƣaugNmO\ښI[mљncV^>c&lårJ6GJgunRF@f}ga|„]7 ekHՍ5,` Nכhjr#wГh:">OFԶN p!TuHrtF x98 CF  dr{&K3<]HE1Ed!t#K!`z;G VؙHK f%V:BN@n=V<'@"är)!)cBF4'dl iEs=n {@}f9GNuj5貛a!䑒a(FL.R>^tyN> -B5N^aɚxR?D|8Zqݽ(B z@<6A*ɴRVHx > cJ؁(f$,ΔX>F- V w:^&I dGH} 0ˆD ughE!zI$,0<}1 XҲ/A ΤT*yT=Ѓ+qԺm=e2]却nPGYF>' ) **~̏#L!j2j Qw!3Ϝ+@!" Rn6Fcq2xŷ1Ȟ17+XA'N׈^*H(7hf!kOaNj&ꙄZ\}Kgs%Tsi,,_eȹz#dHħ!ai=P >T3#N%>u[kGBH7gϮJ4n) `"nbQd=ncy98o/K+rcgDuˈ$;ZPA߬A3>J̨qs䜷^i  H^aC(F k/.νKb'9:"F6VT@ 06_Ӳ<4Ivϡ3L^ClmF B3iSWA)ɠH0y%Q7BT??ʜ)ג׃ǘBxx8C1f zgP ̈#S{vo[Șg3O&'/B{+D5>ENt!!bAKk|Io6 vH&уKU'ڝ2Eڒy1emkPx#srQ;VQ<[ -݈ں.s U3j;m Xc+JgFs>v4?8CTYΥ㔽6z*-UY59NHY)QY +âlseMLӴU&ᔩ+uI9{j~UǪNcPIZ#%ea6eckBfƷa˅M} Kt.]EĎ \⼷sN `y%O9հ=EZX~ч{$5N[")wQEs}Qq_Xrl9E+rb;d٣ZܢܳZR!ZGdIp'bRL'1$JWn)j,L$ h/f9虺1P0W+C8I:z$*Lv=.<΢Jȅum`,vfܽr~Z|OvpdQR0(,v;!a!M] xTn# 3Ggx)ZYl&݇1 ]sQ7ZsOBy 'w.D/.*+^8N*}pAB=KJx^9̓y3{FPݝ%}~CƸ]"TFvpuC\V'-]%0˂ zE Vs7;$|W7zxaJ@Z\/?:,X_h1uRݚr~TBA E;AV_:;юlCVvpE|ߖY:b1d%ջ잰4mO x\vxAhqK$V!Hȟ">yۼ] #P69nW|ʽ PƲR m4K}@*E~93'ܙte.٣ pvńK|dj#F".لeA| 5[A>]NZA'oYܟRNg{g|wJsCH0T>FmѭQJ-Y]|vtiitӡޒS-n%̩J7?|,lȔSҸWS](iTSa-&`dg >¯Ο&é9: X疇{ 8zgq}T̮!NCKґ".i_fZR:[g% *qD;I7(G=5ŎQ*t(^s*sG@0A[Q"')O{oD Uxar70듒6i+8% Gܠ%zpgzQ?AK oͨ-iѨS- u5NNX!BZ>Gԏ. "ݘZ䣓L]m_<XܓS(K_a?* :AWӪDiXӴ?XM Vz1# Xf|S|,Ɩ;XeăZ;;Mb#{ -T lIk&8R+t%rhezOz]KdzH?:leNk7dIJ 8n4eZi xN=Wh ȧir@I)~u?endstream endobj 103 0 obj 5237 endobj 104 0 obj <> endobj 105 0 obj <> endobj 106 0 obj <> /Contents 107 0 R >> endobj 107 0 obj <> stream x\[s~_Uy44gwkKR؄!e]_,#7tOC#tQYY ٽ&{}6+׳_g՟ne?cE)݃YY8d2 -3tt{4{:k 䲐Z6}Bc߫$_B0CBmy1FؙA{5N UUWQ. 4LYf'9sSVR02"іYe-$sUw>Lla+v_yrwi;R"([g4VoXߩY%rN=u[}i)'4ҡ\ ^l|㝜[c;G^Qn ?qS䞝*"*PJ/ʂ;pK-?^*wx[@Kov}Ln O b 7 6̟@OAڏ`Cc 0ُ2x{qPMlQhԳ5tֽf^e1YBdo  JLǫ~YXFb֒G?gy|hǐ@}dz(@<}=5-D]` nV{^&u}bV<t'g`_(`S M0QNU `7״\4~ߍ7t- i3?Q6Qk.v]Εoz=N{?0jrmkSV35e#h5S[WX~cj`km. |3!uBb9 lPB ⪥縷q^M/M^BT XhX4b2eUL-2fkb]ZSH{sAz* 1<ղX0MG & ic`y(Q(`/KD@yRY PKpR ֎&~95=iD$Cz5 BX$%gf#d!l`5I[@_ op, ]Q"4ȭ]0;`vJt2&;ߩ!ܾ߶@v+@<ٞׄQrK5`®51^!mOmsQ+`?:KlRz{,*Nف)L[GTm(W C)[u.- cX'6c!sKsT&52cy!rY%$^81+:VM "O:_Τfۼ|IEMrލN"J!D=)OA&9hqxdzD!%r:!sn_'\礨@[t=JWt"}˽FOUK6Փ{mks3PHXF,cQZ`a'|:o6Ԇ|v'^iJlJhFOWǜo`/Pw+P8ix,@*:)ׂǁ'OǗ]"IOCiMsUUHv(0QmǦj*HistuN;a_u%NѢzexB7i('AAB%x "%赍U*qWp-TvV| Ѧyᘉ?vNPl(Ci$$Ѡw1 )h`e2<.,Z/Ig=gUYTlBX0 U-g0;9Z_P9~E꘍n6yRQL*uE?Qs<`Q"$>lkXySmIx$}SP`^PyC꛽RE[ǥ6Ys1ؼjX7a?e} A$ʿh&ܭ)Yrcb\I]Sθkv[_yJthLk[p*_Z\}=*Lʗ^D[Ć*?f&C>sRJ(}ۧlimߊ%K[u#Vݏ{BtNjOU 1:H*Dʗ~^^UDе 8򖶸В)(J#*[m)"9dau ު\RuՎ6K,:L]+YkkWaK]|M5>^E@Bfk^ 5:IL[o!o:og/"F(bFZTl9!7~5-aJV” Vme["LXj tl=qi2%Fk>xEFנ?$c79ܼaAa^ /QGHx"4< g[}/endstream endobj 108 0 obj 3510 endobj 109 0 obj <> endobj 110 0 obj <> endobj 111 0 obj <> /Contents 112 0 R >> endobj 112 0 obj <> stream x]Ys~篘Jkk1QN*R.ǖY˥ZKE"HN1!%/ n z%)[)K|̠qu}bf\TG/>}uä=G/WM9UuxrP3k{:jZ֪5muלd:[+1|uIQK:Mӽo$o]]5իjQLkճt)njۏBQ&<;NZK4V+O7Ś ƭ櫓7mD.y+X?l-E:Z7u_+[m̊-7qۚ]7Mp6֕n SkZoq|w6ۿ/?wLXz`jhBG 5!RNrh4WƊtrS+MBp/(!сƠ B`F/<ҲVhe8vFPҮHKUN-BU6#du *`Vap>S3<2VAԙe$A#,޻`NKcq"2 ,YAs'HlP?˚AEh}fe+a=a(wJ6Yi`R?K7S7s $Sϯ0p UDqmo  tLP5dsOԛ䖡ݪ HAA@vKфq#FB)icHkۀB@0hḅx\Na%^j f&MQEn{EcmBY)]>K(Fa(1iI*;{Cfg]Ɉm5Dti(SMp1L^1ULVGt:"EIoxX3׸"|A1017P;G*ڻJ*riI(%|L2KpYB&"ߥMBέ G,H!-k9]68=] O VE;wg2*E 0>FכeGR9Zm08Kʕ2=yS+՛c7Y1{DOL 9"a?:FN7q6\NVurcmA 9фG%Ljpַ|Hw%tL9z'Vnv ]^SppMlüR{BP ?%NbH|Gѡ>On1"+WdqrQnfAn糉6x)+\C]-׭\]ee{ZZ!ZfL_@KVQd~TnEJաhپtXi*$AbTAQ򒓡@@*\s*K㋥2&=J2D&fB 7cAB/r3e8L9b"eHK@HD@`H l? Qq ~`+gso#3%;O}q>%Ia⍶%ED/(64cӲz<;25V3 [xv}?\qz_q N>;H8XXI2v#;o蝎rd,=3F;} 0.Ss\ u:"~X<J{6ٙgJJ IU"{?@7a6C91<((/ ;y<WYFR5(h#iFsa3im͒gS5mR=GxfXO%n%#@W0خe60ΖTjQ{[J]H>0U&=qֈ.ggE9 B}j<[<7UI:4Q!8:ƨm?{]5CIA'FG>Ң:F'oSC96L=4*u\ÍElc*W;cO ;o\evYD_aTE#(DIƲ?G`I)?oKBj<38PMrN- ; Q@p,ʂ;S轔i뿽gH ͐YT(}_?a]wOy^?c@6urN̫uq(i5Vsiy>)kzbĪdXͅAsBa9Hw| ki,_ḽkP[}+*i0Z`IL}(aFýg]C:qfM;gk:|# )p^|i2 7/+ASxI> > endobj 115 0 obj <> endobj 116 0 obj <> /Contents 117 0 R >> endobj 117 0 obj <> stream xrܸ]_1뉯t}勻ŏO_(UfqBᨅm,j+.NN^-R4vy2qޫtSk|uXUZXuq6s V.E ,thC,T,~ҕ4ˏ^fJoWkez+KΒRO۫_4&TʄڅPfqz v*@[vy5tX*;+ JǑm'+mR5e;lS5,-VMeU wM/JS=uX9V>u>v+Wy@+G,0ѭ0fnav/|KðInx fX h@=t,sjӰ26bL]l$~*W3g8_i6hyٝ֝J4vUuk/ {AEX-^cm+kUT!^w'5 haZ;`zW'o{: yZ{`\5m4BDyS1ŪmY# N{`UCPuߒ ml'k=]n,;cG>i_ŖycWXs ܠ_u0v}~ Y:Z$R ntx>4tͺkGg]gh^$‡> Ws X!yKtVмrQVg[nz!KzU]'V7$4Z/_!aNp'/'kcҴ5L |t<;K& 'c +}p AJ:SA"C8u ;)QCð9g6sl&@5D{9V&eP'bW O~󯭵 ;A&^fvs$h>_coVk H%=bJqJ2ĈGHONxȡ-3-WD~RNݪYӆБSa51BpR 0>UHu%oLWmM.:W-YfgL/\(afƆ[$#Y i5~ݟ ߈f![ @ tN蒆KduF|t'#BxIQ66df!  $.a):M3%I1t&gN@6@U,6_MH; JGQIkobX$*J4b}&[F XS/&s"k_.(Jz)cEj j@S-mYNmcbWԝZ>pH=ڲLΗeWHg8fSɈs9kJG&ڨ^%B^Ҧ"Mr>T\D?dCVxl,)'+ *nݢN/Sy1L& LPhjoyوXf䀘CLd9x>|xnG(;6Ll(uuz|!~=LiZ?E%<5E)_(ט&Qg&ؑrUQcs[fesںl9(}f Vb藖d')$ҳYf9tJO˜ؤ^wl1Z6p^팏LE7=Um 9M9klw9.1Q0>%yU#F*Ч9>%r,Guhaճ|oT/А >^ZLq FIL3&(ְD%gTMQ,#M!~WGDhjfPj?峍5m@<݃j4^&MaOY\vZԐ\aGt]ڡJ5+69 +\ڋ!K=~.̬XyO 9ɫKa+`1IəJox"(:xgfF" 6吙 UF; Ee7 rX,0b|Oo"k+ 6V>sA@נ )P%FZ31(q;:2,J9̈́x3)oт\tX .>z2R.$[QcHMWNiBYf]6ERi1SKLGü)i3 Yk%Zgќ8Fn0Ã{%YzJ3sWod"3ϻ]48!KTAiCwf :V ;UѢ)ݎ%_-E{R2`m4\ȩ,~t#a=tY{ 2cgCvb21E|ia$Ч3#b4CJ}O~/K[luݖc Pw,I:0$fz@|Q]g&L^$LhEZ@' Z2 w|Tv.T#П!ST"4qhJUCIԎil⣊[8(YUJ>H,ϖ~vGUֳ#Y5j[heZL=VCe d!MH%x:[73ߡ̑ݞϾe 8~/Wm|!7&|Ct3A*2Y–f(u@ wm!NQW B6+إ^$ ]+[xOńg{#Zw%Ų-ݚxE#O61];/ vendstream endobj 118 0 obj 4871 endobj 119 0 obj <> endobj 120 0 obj <> endobj 121 0 obj <> /Contents 122 0 R >> endobj 122 0 obj <> stream x\[q~_ȋvzy@dž z);f+{)6beCd D_.oϾ?쇳0oϗÿ\0H9 3\\1hD#Fg hpq{faAM+Cmֶo~Ƹi-h>78`v>UbY*eжmXhc"#mauj\YH{W鼭OOc^v,>]]L8Jspv4NFVrmK@ogz!qڎFD] '< <2Ng5/"k*"D剸BT=B+l#;ȓ$7 B = &#qO [Mk>GHg4{"31x&=SN4M$M"TR'{XF>ц׈3zǹY j=Eqvz U"x R!j@ Ge4.ތ-uWt% `WeaLL}?b%wȽG(WcdWbH&3A ]嬞UM;,n*:(#ě< h[H))ѲMR"Ĭw= J,d9 ~"{#`=wbb=b=v'$7iɟ1;" Lf[ck&"+B3R$DDo&;1PVu K[6­{q;$aNU:*FMj f*2{x>Zꘘ9Aƅ+G{6ڌO(ᠯHQ : .>)9p|=.) KLkvz'Ka,#kBșH ]RS}Tz^|eT=Rm0:>G r^%QI\ַ#7R:/Ճ/O ~;<5aG0xװXP2 Ҭ>}F,yfq1 fAJڂ cs,.<L#6L0ŲaI4]0ݱ4@quzrNJW5÷P8!G<0Ow>ˑ*/T{FZI(QvƬ(UyŤzӂ3opȉI*&HI? >-;'9 $KD>ۏ]&SH&'nzN{RyO+E=p`P`o얊AgB7N¼C6"CMTI@O#dA2eR*Tr~S 0$?{ Qg53*3`m*:oz[P:;+G W _d~,ӘQ8wjF⽻"O+e[|RdD{W>TjV[=^uFIK]}ϼM\|^ ߃.!G?c?hN({<ârorīh X )j2`D\&TݜXo󷠥T#O,c*_N`a؃(ǟeὔ6GA/Wx/Sua.bUt?cV,JCʞjb}UsjfTFSE)tucv1mE"b&VH/krm[n0:ZjspQrևZKJm-KΥϲJ`F 3jh7rdiq>_e8m RSc{׵CK籧\ⳡ.b32)lUptpFǧEכ9e$[m߳?O}qCc>h\ͣ87^W|.ЁpWLӀѾwN}%{MdH-NT8Ś9rcHFs)]_E櫸Mf}YtC~NwxEoZ?G#pC]6W lZ6{P8qg}c?D+2砀~QWarg, 9?#wW`SNFAҋ!d긅?8iDzu:[Co[mR+pz>=[7Ŋ&V$M)voHHLz79j| !Vؙv꿫O<9t l=$93>)T-7!Rg;ණG%-TPxҐvv=Jȷ'ڣ֩2x%Ɏ]pls|{4J|SJ~|`דIlcQyrx-}ζkoh#7sLmn 6nzϷGoAL`<Ϸ(D-:v=̖rf`]55ئ?_'Neh_063X?T\;#aPEp>"8+^Lok =7}KD N 3;Eridi*| c+# _'e +|+Z6 ٔWr2B5X+1`^E"KiiOE*KR@,Lpa*:_raF7^3+=3!砅sysSzҳ~IaJ0a~+=os^>B/^-/iml<1ZM5 OʎtVe~qF'6{$}$Z&NkIA깜QM WM5X Cؘ?SJ^p~aɒS XA(i}:I$E/t! ?aK9_b*O9|{wuJz@'7 gEh81:vS_;K9;aH:Uua!ӏSRvlA4^36|Qhcr1w: -;NH<f;19) *ʍֽ: :%R}& \76RY}Ul[{7QtQ0;YD%m@̎֋Ъ,ۈDͯ+ EW3=sUvIڍ]Ȏ9oзk9/яoCZi# %? wp2%-tk/gMz_G}k,}@p޾G0뵎+:'xհ<_]%Ws~oK'# /Y=|_:M{K;Η\OΗΗΗmˎmOIˎo<_|N˶|vY:|_cd Y: $> endobj 125 0 obj <> endobj 126 0 obj <> /Contents 127 0 R >> endobj 127 0 obj <> stream x\rܶSxi$H"MVO"쬵Xʖ5*y$pqUt8?Lw'O^4ۓ|vyJ׽f=UۙRY^^1~LY]ZUg;fBeBW*U{[VyYPن^.MV*mBnf~M*kFpƅI Ϣ4 PB*–m̴6U1QU|PELKZWh}_P?+zw>UnBxRT&S-c2S6v WOOZ׮2TltU}5mm,7vU4kEQv5],0l+{>Wtk:2BF(:ԿL[^].j1lz@ZA  M8H3AzЙ:Jy/[uj\wj6eXzax>#.IٻNP>B/lk՘D̑g&V#¡15qO*x#)m)Py=&R-ݺ7uØBi(mʺyyWR~^QyuE[HGLjZ}aUnW-fjE{J.6s(wC/&9  5Rd+-tI( o70@#襑u X"XTp͐kDuobB%1FxjRN)M)A>@!/=я%mQI[P"@ߤE'S$eDC^2,"SAi5ohlٻ[k(+k%:Ds(j]|YЙ˰A< , ٣o±֍ЂȺRE&lkUz&֕}MEf骕!rB@v%H0dnin)a Av;Uv9QPy+JD6?t^׏1Pݭ}{ Xbcx`MV;ފ; mJBfX@+ۀފ I$'/ /h:Hv#gϭ}h;)vgߟBߋz*U"OY=e,Zu. ;MKBg̬8|XYi%}+I[+'ݑ©䒪͸.(dgt1-uyYȚ~C0V4UQPmkj/N>Abs*fֻ>1%{ mWAĊo@ hDhu;uuC>4[궈,/:0h-$lPZ QpVy}t"nK ʴ;8R`9T[ȧgabURm-0{D7M`%8 Ҍ9I(2(,(F ;q$OhJ?<X85„!e|&;2QP#:WV⵲R>[0b²Tij|Ήi'tro1-ZsySjĐ!K]`,]hOM:ϒLODŽѢlk[* $_.]yYG&`4Lو9ˬ/[s<- @A:Q*^J>,N:_" c,È-m,$}!cP̄y1ӝeACyF ]1Q&+[ATv$RQ~!gRxrYEbN9 E-/m#_zi$N$Ƞ\\X]45 aȤ򖠐a@ID֞"fX|ۨo=Xew# i3IZN0+`7R3_JFjRa 9cUE(ԯBϾOmQ W9Ce3dGZgF'X#9'BkU銳  Nj Ոt 9Ų+A X~#Qΐ+1GØPUn%8'JhDFa腍?ӱEpp 7ɫ-aB7isSzl@ Ai+Cq ,= eUmuJ? x`G#IQ8Lp&g]պ8)a9~B)(2ީ$A 8Bdu l<`E`%4u؇%A!)C#`Q0+4((鍭~Cր78SDќ.[x<|y 7Ansm$[[-r NSC&=,8ήK)l@ Ut3fM$KvcǤk iz58 ;ڎZ@|ͩmaF5: ) UvEm`q%LF m$wsb8>NH+Ib vu4:] Ejz9-Fb3xiĶj f vX01, :5(ǫXRqjZwz6D~ 7rΧ= T\;<(]נ$g]&SsCV}No|Vx΅ѠnXJn{k꾓CvZ-HڻRծuCe[}7cO y0 jX'$Z`;H(xX| k`> endobj 130 0 obj <> endobj 131 0 obj <> /Contents 132 0 R >> endobj 132 0 obj <> stream x\{_BPD ,%h4uZF$WER;%;|~_,9%0cy^MBiW~zy?)W0O?]Ow]REi'hƖ5uUQiUӓ7窀ͮ涰l mFξǶslzQif?UY8_ ~/lwf6N߃Ft0MդY(gJfmbn gjUnJjv1_(WT6h)>[,>ݴkհܕO4Ѷ)m 4E':SMUܹ*[Յ}VWe e-GbReS;oR[)==!S}k5L6;$$ulpъ:#>g3 Ni Wħ آ. lqukm7n韡_6eY:wǩB-9 |e t{X,<4Ff־[]IQLOx;%a·)]VE0 k2 ؿ4 c$8o}}0BV]ZNêTPj=zN"Ȃ%8oJ9=='TZ s<\D<&VJp~ؚ7z^镥`uzN4;j= m#F#žbD/i<ۏ$umRjdbaBfן >hM ˪+?U36o8'/'i 25<}\uZ Ϣ+fxj=݌)>3Cփd>~3'\0{~%5M|% _bc:x0A%fa#'E ʕnG/[/:2Z?FEJ܉0F:@v-!ZM9KapSGڨ\ͻ=-ΑWkl'A{x4u9@''|3dUF={.%`CYTkt/ ʹ$"i(WfÙpO5bn)I.)> (QJ,o.Cd2JKH=}*IkwsNR ;1ʇnq*m-{.<;x\#"d\i}NϗM-/P󮬶YlZm5NׄWB-X4CEf! ||Y}5U3&;YD!rxEZ d;zL8%G{q.:u\  cr$CtVi6pVMC#/}c\ ݔPa _DuZtky 4[1C=#)Qw66`ŸO@ccL+FϡIG'5f"Q` spʑ'trch $ǷöKm/Υ$$ܤB2M 9$}2KFߏxFX S!yB&'H(JmT*-WrMaѤ1ô & Oӊ4G@딲ZՔ[GQ/yb6)9E1Bi5#Me\ qu4q=nP)Q]9AfftS*MɃ{drR3܂Z^;JG7(:+yR-*og4gvoKE[, l6'ZLXbQb}M7+I@×X!,E:Ki+C٫:fۤy9̠D5!TjGƈ7^8̄5`8OZ|";>1O7]UXeT:۔D-(ǃ$F&;2ePS6E ajWB@Z@RhR1L9 v:[nU c^}fkvl0b:g[$lEZujx]Y_Tl\*:ϛNLǢNvX!="*!$]+aieBme#Ljژ bCnb\X:wKpE[Ϙd-0k=b!}/*r+j&+]X-+:|?l< L22wƼ[}md;9l7ôΦxOad9 zG,<˸#;Go@(O=8nPفeUΤxYy | QXff[+]HU|EmM&sg&t,k)ݨhIʁ20d#$E2ҨiWDR}(|_=}5O]PEpW]9/lsv.`V^;8^]K5uim'Bv%kP6=._Jԩv~IO܃_sDr%nM&~ҽ!|'Y+A*x 5DP U)㸬*2\UGòH{ +"ںT`!Á4|ۻ6ma|SƖD4D4R;jd%QM? W~cD7l"{o̞e5pIًGo~dTL-3T3q$넕ul8&)] r2k ϚU S#Yc2$A'x=c[XFx0[ |ڱAx;ȴ9T7Tˣa&i(pUjd):O117TcX)NTq"@~müaⴶ̵HLJ/ -ױ74H`e|7$J" {jۢ~bf#gy6.O/҆02-,VZkE] ."+n踢)|U>*M> JA*ʃ$sbb`/O%{+|h-~\}*yB,z Gw#-Pg?diپp$+ę]c+ =^SO[sL(r36sedadD X1ɍ<)(6 .;nm!rbendstream endobj 133 0 obj 4066 endobj 134 0 obj <> endobj 135 0 obj <> endobj 136 0 obj <> /Contents 137 0 R >> endobj 137 0 obj <> stream x\n8hQl^$7n8hHQџ1Yۉ-ٺ86F_^f#8Q?DpYp *^T8|s[<ةOw^҄3>|fReU/NvmۺEcK[/ec{/w-ZfyWgtes;¾+jvPU8떧Xեs1gعdWaZێPͰ Uqw} S6M[y PTS˓b6a1VUcU˖sâ{u[]\ۖ^uZ`0C҆iqìmmv5<_`Ck?G~FaߏKb _Vk(l/ R۱/0ϿLu ^u)j!ѷ8u^`nb1>IUz0G01[%.`/Nv\<Ņ_`[}n0{AkoTڵ1wsM1jxlDB^jR~nu„}l]m g2n@仍; Abm0fPk~] T1!:`5ny@.V-MSY%y9HKyAI99<P5e[''LP-+졘v$4C:pIRp:q%l&"-ﭛ zzaWQo0I ":J9NT`,L\ z%5`6h `?2iFjWճ*jcO.=?@W4ϻ*bɦM˿S'49OT0AX~NRՕEx_{􆫢9*m>GԻqUrmxgIovߩqJ,xd K* *a8ZB鷈: *~2@0JRja;M0.w3Jav=a#}N3'ZMa85zεH+0+ Rt;iF+WތM <*m|E `f0c<'Hk {8MW>ϤI@,)"at $ ީ;a,㶅N )7LY9 qvY29)*6V5gw`D}GGIg|( 8P= )E|qkgy)mXF(ɘ=( =t:>2E}>jKD%E8+$wIfOmYW1;猆,%M>|}.Bla 9If,+lYl]Oz/gm=aEj=z w9@a$D4izj]MMIl'8 E?P QRʎdSFE+V?|\JH/rHr\! u!gқa:k ˫]%{&$;e sLUU'!o$z~'7jOf.LH[_|cLv^00"x g%E=zH`TlGCFxC-ZsIR!dbw yHץH;r5솽,,-O rN}H9ĩfrk5T~.YnPBw3:Йʀ:c%(gs佞D9y&jm%fp(Fٷ!iA,˾g;a%Ъ шŃ~ɏzεK+j|98)# ]UFx]& cZ.4 iTQd.1TNB)=BO,1xѓ9w(~XJݦQcP85~ v8 Ve;5YA顲$b߱$wR'[!>! CY%4U'3H~xJV!S[+ra!k 4AKshJtѭ cs~FӤy`]RBĠ,""Iq` 0CB+Bo1´^JewBoe;\[W/f߾uf^U]w`ż ~j)d.4FHLC< 7XcDV `_~QOtzQ"H=Ƭ tWN|CJW7n%,_^fu♁M *iݦJFJMOd)Hr)lJ s4NLщa5ҿu1']eEiv9ۜ+!B<YHs=nr{G{Z+\c {>lPCR˓._K,x~[8g4zs(8.$DZԾO{[H5(ڶljqpi[oBw endstream endobj 138 0 obj 4504 endobj 139 0 obj <> endobj 140 0 obj <> endobj 141 0 obj <> /Contents 142 0 R >> endobj 142 0 obj <> stream xi%E1P Q<Qlk5]]GC!*ANva!s1~oOYUY]ٝE"d7v#ʬ}RԕjwlqpU[lo=)݆Pb{qUhSpT'[wg]Yڦna?ʹZ~TuS|UλZ!6yh7}B逅Ue[iwOSnvVRUrtetײ4繏PF:g햺?Vbnb{ToLZo,ծn M6rвoz4BW4 5ޥF}5"q;vaiƃ5@cҍsY`7弅s][=p:nem.MdԾ#]GF 2t6csT UMc<1,ղH;+)B׈O QkQlJ'{^X7>ȑ6êfg ΄N'x1Gl%6>Dnږ$@D@3I$[ۿb L'$5L_NE)zd_i7}i~%i[jL2}%vZuzۥqBq=FLsyANr^u kH&FW;=y40e碪ا;M SYb.FJ hTuv26VLd2"w ƔkNJWխf ˆ#0Sux~=t˯en[+V>Uޢ'f+IK'HrеNcU܌'9@tN$ɖ XZIEcr! HfpI$_Իwg\"歮F5+1of^.7*of4cm T6m9\ٟa꺅Saƺn tSLW%mwewƕ؁!e3l2ZwMU @8hll7q ! ]kc}qjZڞOuLےPT1k~6zغ\h҆o.uMq{-klkgup b Hw.䢶 RMbN.kQ;#AK&& & }d}$xl4-q}kG#BdI YBuzMЃd˂U1FA/2IS*tlj+ȿH Q"jtk\B:" }p͡kɔT41tL?O:É0Ԯ&Dg3$jm$4J`nhn!Ӑgx!HJdְY)]5d ˸T{lStt#\fj6;e3^*Sۨ-(RJ/%A58Oق}0Y8v_dsJ֊0rĹtX}@.AvNbq&gFIG<%%34D9O껸Ֆu||mmoQ6i ߚu*)APD+ -8)0ZFԊͳ"z̺$t!ٙdSFHI=LM os+L.i5/C9fz/Y@4J&67piFhldp t;x>f[)cM bTWٔdA XxXLPW BQghԙm3Vy_iݢ$|Z셕{0:1In7-+X3@ISE/9~"nBbb'VBWfW1Z D 9RĮixHt\6K/S~d)u1zA%ዥh !8MR!qԨbˬ ы0aʋPnq nσʹ!FLd6hq3n?+S9x$w1j!Sk %p6(LX)rIpXr"Ucr#m%gŅ3 2侘ބp{ө&CR!ԑőY,1H1>KẎeV +>|SU7p(IB%'㎝ǰK&7k"5kTCBk<8xbÄ2OCWժ5m7,+d7Kup(d[euCy0~\xAa~O>j]|?Uֿ 'D3D9 Dޟ}^Ȉ3w]umŸvOqQ>kTDz&|f񦎣%R+woW[Zj$OG,u t7]t7(ւkV} ⠤qbl%1d< )0auܘ~8ʯ3Px h7xe"I/ }~i*} WJNM絘 $4mo==$O8.H]wTVAYZ@u'MD[0>%*V+נۇs^ Vx6WkɁ[c_ P,խ7}o h( [De8+w?@Ռa.a,٪lְnLnG!{J}V<(gxeVکގ`^2]nVNwEJū~ϊ}x]pnX@IZJx L#`zTvŠ#VgRY&@PH='i0}3**g1SAwX0ePR㌩T%$$.EwZQ @2BTOWRq~x증5N)'3k->Jɣ36%3j]e|}P-D rV@'@i)Tǎ&mЌj| )]6mEou`+'DGJv_akVSWtiKqKny55f.D$ՙpԙx+fD&:|$>Nd4I1!&V.y]5H97"yFm QGBU((>./U`ދ-MH2 25aE)w$L=+$iS?-5 pD6"Ë(~bɊdM ~Ԯt`3r jvEÍD_ƠsxYY0ESI‰ST ͔wr2=͢WhsU:Q"Lt ۥ5Ll$}>b8!2ͫU8ր"G3oR.[#S(r *␉x*LSk v(~hG?LwZƥ1o:W !$}Ɍ($ȝ20łzv,'eSiUCs,tJ=wa7xEK19od{x[Daendstream endobj 143 0 obj 4562 endobj 144 0 obj <> endobj 145 0 obj <> endobj 146 0 obj <> endobj 147 0 obj <> endobj 158 0 obj <>stream xYytTU}KڳPTiR KM` "G"FPn F6[xlԄQBa2cDP/RV)NB 2spYg0xЄP!|RRI熃=t~P P ;)\%$2&ɸ;g[{k1&ck-^245^x+n#DnY}NX=w0InFɇ/2R˗# dw I6qpxNיxg#7|!(nض)H$,ӂ(^!::.U3֥Ԑ~F s tiRz2~+aY? ~"687dPg'˯Y>K,|?RȔ%_8eYj3ʞ|/hˌی #F1[<mƛ%yx Pn*Iv9\;Cp!gPN:!rawѱ}ҶkbO; ߟN@rF)_6.ņ/3ʡQdḥdr;EV,+!z;s֒IA xzYQ巈 e xF

h~XԛeD$2C3Ύ5ιt`ZIbIl/yQTi$BRA^T*O)O*sb?ߤ }j 1@n5#,:Źu"E>b;bWz"JPh"Ul6R GǸt2̃,Z,-Ó\Wkkk)`%. [M.7$&$mGU)%:  PP^0lj~B .=Z}wMF|޺ɟ~էK[Uh( cYjYgٌf@FiƴĚ=of>> Nuv*vP!D$I+ZZΫb6V" mБdEfUX2#uY&?&B@$"/9; ֑O5p$B8%pQ%?R}ZG_'~G 匲$^e\˯dZ,Z@ժhZlǠ".F6 ЉFhbG;\֤B Z[PckޣG9&~)N B}w'4;3#h,g& GkM$=Y6x~dTrk8 ːn3n DGK dj?w#6?(7c$^$qr3Fj}Ƞ]\筓8–-W3utW>6 /| mGDt>"[ n }}sgdc:^_b/ϱaROa22q!0زC:Jq1-[O7]mݻvЇ7nK WSW"ɱ?%u'TFG Km#Yo!N+> !(qng,%TMG\hlו '5Wփ0HLdP>hbz?ԃ͑_yHĸ ͒l0`X$&w}%EU7OVZ٣!Q0L"To;}He(_~#V2)F+lcn]8wBQ14ֲwHD-c-TD|'Ӓg)̰xfd J>Gzk6Yp#^`hHrIϖ|GF9EҾ8OqϼO>]D|'c IS j=Ǭ΁rޤ":SYb NT/h+ݫۤcm "U(fez5XKÄ,Cl*Is`|Gi'Yo-;P]GXͅ l]R[cY2PB<|JRȤVStjD7[q6$`MԔvv47qj=P38%t"]Sӎ%a=z3gv5 Nilݻ|}2BE#e08tTf@aN*F̵[m$Sh:L gWcWrFL86H6P0LJng66j>22Ll#䑜PA PX/lX\]C|0"Pո Ck[#hͅ$0sL8^W^'E<>P2nY֘4``M6[z…~# /U^W20T099EC 0b6}^GZUI'B%J.DOM5ѐy\l.t*2o 1H %eL2D%ĜDB\"_"rҖ-xz#Y$QAR;{ٖgU];R}pI$LP5>“: zzՖ 'ByȵuZO*k)U35ё fM>X@@R_vtJMj ;;zJ)LJDr[v˗e,U(;Gp//탪lIͩ%9EUMǝxɧ^ =#{z>R xpi}9zDaS­o뜰%p#/~/ZwgVtbAɭc #'zJp֐wz WW3P ">` YVf}w`XgEsimGf!=i"PÂئ^W#Qr^mX$5~GͱoOG$ NAI$)lr:.2$2^OD݉W( `KD;XH+ Z=wk)ĦIim|jWh_M/K떞GQu2,2,d:Ɏs5 TϊSa3=_rD"\5pzS7Q@C4'ub\MQI@RpT{.fqhOT,2E7xP7nVybsqnqqyѨ7x̛tCL4NQ*ڳIw)0`n;Y-DzW?F-῔_/_4I{~cf'7g/;sޔ h NLS"O]}PYXp 0 a6x(0ʠ 0wlcsr 6D D|+U FWUSu"un F)y2\,$I{' )ǽ Z9Ip%U/cYe~z>Q_{ƞJ81> pkV =_mYؿo۷ ῂ)+T.a#Rƞ˰i?%_ endstream endobj 159 0 obj <>stream x]=n@SpfkK4N"QxY, /")Rt~9iuNyǩ kϏ5SaJot뗪9k![n>0/Ci}S^rU.ÿKm'.Vd8e֎=;;@q5iݳBz`g{=[=m=6 .4R*bE*VtP^%XѼcXѼ3XѼSXѼk+7TbE*Ŋ Qh^!ŊP^d(V2`O&[Glu7t˞KUǺ*ڪM%m2/zT?R endstream endobj 148 0 obj <> /Contents 149 0 R >> endobj 149 0 obj <> stream x\Yo~ؗ;wa |ȶ;A;q@KZ!#@mgjvj%`6w3j3S-YS{Rk˿_>هiullO9gẏ6YҡazB׺_C5ʺK;"V?7Z>8Wµ1FoX9O*c9JZJԝ`ݓƶggeNeMsf}l4}`IXrXj{Ό%?g\n˳E̹f6!C}(cu}#dPb*ntlMv{ifc ekuK0>* +Syyk˫ΕN c_PֽGyNk7Rl67H&:Xd-_cM?*V? (j`X4U~{aai|t} ,%'ePguUsH[ɥ:# kR.ىӽf7əU<jw@5r2'mץ|2m;ӢǭRեɢSBRA$MYީ'$E-abNV񄍾K#i+fƀOEs=T#GY|FҴ(-yUKIWg7fdDꔙ dU[5M#=rlD2iN, ~DYrfd<{ooA;lt冊W[c-XLo-3Y|(Z3dF{gdp{2h_7ZA|Sqb]O xWDX6:w%B@ T;h!:g?h_T)i0zOs6Z!S&痢 ~L< 9m@3i5hfA qfi]mFL/`msLL#\{%Hj^cmiҥdۘz+@D HXlj{k H1MF2a^haq!$ BR~2X #Vz2k1tND8ƈpJ+&5-y8ErҐ{4ω٘o|}8ET, 0#횏<.v2& F/SȱpjƓyEG UHP*O0v0Z`Ct7_5ȈQw9d͑YGHd]<+]#%ATu+^l#îWvd>/ =X0b OH6&<4A) LwU-Rd /ZO=To_2I8զ3!Zxd[) r-ǗhB4-7bld()ڨEM t!MΩS%Rdx= Qژ( 81Y0%)7x,,F~RpCU 4հkKxCi_P\Cq!}A1Kq[N, @/-4YZRK1+^DPMBYpOQ#̲"1 KZ xM`?hܡ L=?rEwO3K$b+:EdbN=2/s\D)Z1{?f D PM1 8d>HT1Oo,|9'Xz,D [P N6%E!\[u㻝Wra3h0K-jD Wrʁj8r,1^й!$(\%Fp'U'G>u%Ɗ *Y_bMu% ZҠ3Y~ G^;XA0oov3h@`cѰ+u Fځ(ʢ9-G5l1Q>J fyad@;_UPQ:5^ ڲ:Y%+>+彯pb r]i04nb W'`}ڎ}6ykKn`ZL ۼ\k7`4"vc&w\1ZdZ=]}XJ斬N==qh[wE[ hb6 ^p W¾l?8p$f_>G~9; rɒsKE`~C,Bk&)[4MaKf %:)?-G@uhH#Gq%5:V^zueHiu+E6ٴ(ٕTI`u W!^JrGBM(ؔ,ICXB$' ^O@`_+uRēg!"Лh[4W)ڀDr"zguϰ&X%=0hgCò{ ^ 啀ODciS;_<%+1;~&-_fd$IW|QnA#ۙUL

cV IPp',EF^^JwQ 4sQ%%zQ~=\FkC;tC;tC;tʗm#trktwho-$PT*@%fDȵJjou]He!V1̔LUMw+7Qg'k|R츶п/,v#}%䛣rA3t@a-ѣ}P'eWlkB~$3!tWRN~fAAkRJdCe:N!^x 1ծRc;TtEچ9͏W`7v `f0s3ɑt |Z(&~dWƔms\jw!~Dt&_z躎~Cm'4f/0 ] QI |f4-ͯ J; >ΰOOpWznMAh'Fgt[endstream endobj 150 0 obj 3992 endobj 151 0 obj <> endobj 152 0 obj <> endobj 153 0 obj <> /Contents 154 0 R >> endobj 154 0 obj <> stream x}N1 <ˤXc'qHPPB{܁r:~ ^%˲hfhN3 L0Z#Z)B5ia)Bᄒͭ=w>1)Dئe ,1K[.bY}l:tiۂmtE4z:Ű.v#kfu &arUl څuUg访0>*rT*~S'k*6;4zsU7glendstream endobj 155 0 obj 260 endobj 156 0 obj <> endobj 157 0 obj <> endobj 4 0 obj <> endobj 10 0 obj <> endobj 11 0 obj <> endobj 160 0 obj <>endobj 161 0 obj << /Type /Pages /Kids [ 165 0 R 1 0 R 7 0 R 14 0 R 19 0 R 24 0 R 29 0 R 38 0 R 45 0 R 52 0 R 57 0 R 64 0 R 73 0 R 80 0 R 89 0 R 96 0 R 101 0 R 106 0 R 111 0 R 116 0 R 121 0 R 126 0 R 131 0 R 136 0 R 141 0 R 148 0 R 153 0 R ] /Count 27 >> endobj 162 0 obj <>stream PDFCreator 2.3.2.6 2018-03-12T02:10:55+01:00 2018-03-12T02:10:55+01:00 PDFCreator 2.3.2.6 FluidProfile_0004Administrateur endstream endobj xref 0 163 0000000000 65535 f 0000008409 00000 n 0000008569 00000 n 0000010402 00000 n 0000308153 00000 n 0000010422 00000 n 0000010453 00000 n 0000010506 00000 n 0000010668 00000 n 0000016381 00000 n 0000308214 00000 n 0000308291 00000 n 0000016401 00000 n 0000016433 00000 n 0000016520 00000 n 0000016684 00000 n 0000023646 00000 n 0000023667 00000 n 0000023699 00000 n 0000023774 00000 n 0000023938 00000 n 0000028822 00000 n 0000028843 00000 n 0000028875 00000 n 0000028950 00000 n 0000029114 00000 n 0000035339 00000 n 0000035360 00000 n 0000035392 00000 n 0000035467 00000 n 0000035655 00000 n 0000039438 00000 n 0000039459 00000 n 0000045024 00000 n 0000052966 00000 n 0000062451 00000 n 0000062483 00000 n 0000062537 00000 n 0000062601 00000 n 0000062789 00000 n 0000066358 00000 n 0000066379 00000 n 0000083889 00000 n 0000083921 00000 n 0000083953 00000 n 0000084006 00000 n 0000084194 00000 n 0000086808 00000 n 0000086829 00000 n 0000115511 00000 n 0000115543 00000 n 0000115575 00000 n 0000115618 00000 n 0000115782 00000 n 0000119043 00000 n 0000119064 00000 n 0000119096 00000 n 0000119160 00000 n 0000119348 00000 n 0000120715 00000 n 0000120736 00000 n 0000143740 00000 n 0000143772 00000 n 0000143804 00000 n 0000143847 00000 n 0000144035 00000 n 0000147155 00000 n 0000147176 00000 n 0000152392 00000 n 0000161953 00000 n 0000171879 00000 n 0000171911 00000 n 0000171965 00000 n 0000172040 00000 n 0000172228 00000 n 0000173364 00000 n 0000173385 00000 n 0000189862 00000 n 0000189894 00000 n 0000189926 00000 n 0000189979 00000 n 0000190167 00000 n 0000191292 00000 n 0000191313 00000 n 0000208043 00000 n 0000218063 00000 n 0000228616 00000 n 0000228648 00000 n 0000228703 00000 n 0000228735 00000 n 0000228923 00000 n 0000234488 00000 n 0000234509 00000 n 0000245370 00000 n 0000245402 00000 n 0000245435 00000 n 0000245500 00000 n 0000245665 00000 n 0000252980 00000 n 0000253001 00000 n 0000253033 00000 n 0000253121 00000 n 0000253289 00000 n 0000258600 00000 n 0000258622 00000 n 0000258655 00000 n 0000258731 00000 n 0000258899 00000 n 0000262483 00000 n 0000262505 00000 n 0000262538 00000 n 0000262593 00000 n 0000262761 00000 n 0000267594 00000 n 0000267616 00000 n 0000267649 00000 n 0000267714 00000 n 0000267882 00000 n 0000272827 00000 n 0000272849 00000 n 0000272882 00000 n 0000272958 00000 n 0000273126 00000 n 0000278390 00000 n 0000278412 00000 n 0000278445 00000 n 0000278521 00000 n 0000278689 00000 n 0000283254 00000 n 0000283276 00000 n 0000283309 00000 n 0000283385 00000 n 0000283553 00000 n 0000287693 00000 n 0000287715 00000 n 0000287748 00000 n 0000287813 00000 n 0000287981 00000 n 0000292559 00000 n 0000292581 00000 n 0000292614 00000 n 0000292690 00000 n 0000292858 00000 n 0000297494 00000 n 0000297516 00000 n 0000297807 00000 n 0000298028 00000 n 0000298061 00000 n 0000303220 00000 n 0000303388 00000 n 0000307454 00000 n 0000307476 00000 n 0000307509 00000 n 0000307564 00000 n 0000307732 00000 n 0000308066 00000 n 0000308087 00000 n 0000308120 00000 n 0000298139 00000 n 0000302791 00000 n 0000308364 00000 n 0000308889 00000 n 0000309144 00000 n trailer <> startxref 148 %%EOF fluidsynth-2.1.1/doc/FluidSostenuto-005.pdf000066400000000000000000000517001362231004000204270ustar00rootroot00000000000000%PDF-1.4 %쏢 21 0 obj <> endobj xref 21 15 0000000015 00000 n 0000000612 00000 n 0000000728 00000 n 0000000891 00000 n 0000003903 00000 n 0000003924 00000 n 0000003966 00000 n 0000004047 00000 n 0000004117 00000 n 0000004179 00000 n 0000004246 00000 n 0000004314 00000 n 0000004345 00000 n 0000004419 00000 n 0000004507 00000 n trailer <<0FE90768DCE541E07A85FF8AC578C1C8>]/Prev 20969>> startxref 0 %%EOF 22 0 obj <> endobj 23 0 obj <> /Contents 24 0 R >> endobj 24 0 obj <> stream x\r}W[8U&e_RvlǛډMojk7%eܔ,ߔH3!AQ )d~pƥqЧу׊3!+vɗ]Wy'_+!g!͛vDe,2cɗGj2 )ɕ5.h5k3j$sGa PhI=,(iiYĮU3= 4 LXmAfT;)1ˑ4I' YMNFژI̤lk&+Ja1t&r üQX+nEm4{/b qiGo1q$_Jxt-5LVNruƒYo} K?&3zA%^!:'5'8%b] `OQrܧ<ԅ|CC.)st _b66N] Z¡!аi_{~O3tߔ}(jy_j~u9K\WB ؛j|JI`,oSj|]}Icؼ -MK>֌h4@"ccshEJĸ2{+3"ѴPQf1]ÛRnf\ \7-Ds 9YMK_r1~@r4dx.oPTOwz2,B䝰KK;ެZG"FZ`'x|PtEm"(r%.U!Lؔ\q nRw\h]<cHX{X9Ӧeڴ }ќB9Ŭ}_s8u cL+ x#[sSg(tO~E Tw nY/8S`T{{!ot4p&tK5nĥй!&R6wiJ`)RFxG0W*[m MK3ớ%Bgk᷵&!IAV!11(}-S5Ua|5Xim=AGYE-=j?NwR?:ca@׵ns )Av -{Fb.rٍw\\߾{pf8dh c[˥+h8v.x"^˱1>&s,p&V8C;v..p(Yzj_D f,,Vhҟ gE>RBߐOϱP"ygԝ:){I){\A \*:ثdw?[w2!O}ٽN:tn g%YWnV^.cU)o-4!c-frl.yc5{J%!xP;޳%ϰ4ww\ٻkkS@QS795rÉw8{$ tī6: N.Fx4{-@ Λ0ϼ[yMcmM}h֚D({¼ɩ\7&ښVXP{|ڏ:6{Ɉc:e 92 ~A~ E#4Uk~HLJ2Db;P_%wdǜ\殒,a.bK>wQȾC*s@Bgk\7{"{V|9+ ? qzi7AoxY}׹S\k>> =endstream endobj 25 0 obj 2940 endobj 26 0 obj <>endobj 27 0 obj <> endobj 28 0 obj <> endobj 29 0 obj <> endobj 30 0 obj <> endobj 31 0 obj <> endobj 32 0 obj <> endobj 33 0 obj <> endobj 34 0 obj <> endobj 35 0 obj <> stream  { gaˎxve(= M endstream endobj 1 0 obj <> /Contents 2 0 R >> endobj 2 0 obj <> stream x][~_!%R 9CL6R lч(diƖ6{q_ֿWr9~sV۸<;Wҿ-LEn?V|qsEO/o/|e !\\ȳiʼG,tΔ^\yJdBߚR!\}Ufjbj]fUUbUay~VY^FBj]gMrTM)߅htYE \ li$/J^B*J,Ud\\.ruoMLoSm*uMbeN0f`3C3\B6|ro]:ϔe)BQe< Pz m;R^E˷nXJ aWٞ_*]HX&k⟗NBb<)*ż.^?hih,7G3/ѧRU"Y%T&y e 0Bw1ʪa;_m >Īn#u}Pɍgs˒C׾*xa5Yث(~'/G4OB7;57=Z]xJfnR7Ɯkm?(;D=s0-B @iئwrʊ2 ':Ԉc9@ -pl+ـyYv ZeA j&w9Ҩ+ 82kDPְ?[ŔmQftR;q&Efi4YԪtgK7ucd2@  [("aG_?f O FhzyDBMd²}ΛV7G.iOS hCჇ Q N&"MM&źG\kBm6}mCV)u&hxJۅF+չ=C{jwt;j:x ;E|:٩F"]e%ʬ1Bvś3x,=3LJLT yJZ23'V>ϷZ{ {TۏMw" `G~U4)?Q}6|:z엸.q͏ \Ŋ7oʹ2hϳ/1 ?S_RM|Ta?g#3ŠKvdqrL8IH򎐸iqB.)` a|Ngr T(cgA"nrFw1.=e{d<&{8txI:PҭNag;tdɾjjP#E%&S|# B[v f~ob$%28@=*@`kF <1DQxz9BSDVܒlMZzE&=`<-x;S9 Gf⋮GRބLzendstream endobj 3 0 obj 4556 endobj 5 0 obj <> endobj 6 0 obj <> endobj 7 0 obj <> /Contents 8 0 R >> endobj 8 0 obj <> stream x][sܶ~ׯ>eI׼t4IXM't2kie)%Œ|e{H^biA\qEd"U٫?ԋw''M"ԟ>{TvmY$Mڶ-~lQVIU,JjqU\>W"OEתH-պH.Ji,[$6YʖuL嗪g[;&?UmyYd2*Ums06:s̓m6_,דSMRg˻:MM[iCõzW)O* j=Օ,7|n nݾ[eiRֵෟۼcMӿC.~q5noxcFvD?ث٘3bi,/'5E0Tnnlk{p#-%tB6UMm62ڮeЃ^-{ҜI[]e3ʲ\^ٻu+<;s?NeQQ~D#P5zjʦd'/:XX MUͰ!$r[oq6CM1A8S'M1̬>ghD?h+X¦apa3$sF)$eF0IRW795WmxMNF9δ[:"q:۵lḟ˂U73l~bO&$*|@"Xg!:*e;GD=w{#8f f#iLDbU!sϿړ% (àpAABz]ݷ6L7M\ 9[ EwՐX=IתNDфLh }@uw_9}eNW*Ӿ25iY)HnFP9l};L+-j*K:FX;o9["}-J&JJr3Kj]P&;yY =tyzmg94:n7t_"]/OO?)|QUT"͢^ޞ\ [l% g#u/Π>c< OLDTTGنچ=.4S]`y~lIx^mkݶ (` g~7NX#zzu7ۘԲQcZ YJX#$s1=E%I\wKHH ёQV\6,;)w5FN֞0R߲FJEM("yAb[#Éڤݙ ɐ%hEA!Ի′ל*$ mJ>u?Hz_Bi$")s{zM؜5N.<|l4Nc$99S;38:p$@AБňi:0y4o/E;`X9ױ1߄ILa۹N ȫj4cobX\s zilt 1\5Hk"A}=SW`>S= )FabdM <`xyC#A h19+\!v݁ߵ;\H/pq!_!tK_O]qsu45$҄݁9;OY #G1 4AJAfKÝnXqFÒZv1UG}9xLLL׍~jO,g3ۑ-۬BbüE;ZZKKP0qY:ES _t' 5ktHѓm gsL<2"i[1eq4.;6ǾC0.Ӕ8,I\od(@B6w 5ߠd rCILP-Gon.X( ϭq,+yF􁌗hnmCO JU"kq7eRx7ICԨ Ӹw:d5/t48#.+ *d3s'?D"09NSv:[51?w}+ Jo$GR6mIs'od+L\d7Ds|m0j8id.>FRZy)1 ,{2Gm~Sl!iNqߺȥ:Q[v4 RVngr HPqC ,c5> 6CyGv 0sxQA-U94>nOQx29J$8j23)SXM@VwR EP]|6dWx)qXű)P~̮ݵ]9@ ;k߰/z kuC{dX %hJDzh5\L,/:C%Եzl4qž&xl!@5ܘBT|c\^e¬ o&`XWif$ļJV$,͇=? b,YSvyf,=T%O: IŁSr"K`4rG%n1qZtt } .<6e${>͎iNxC6qW25LGN]qlVP*Eq4t$_"E{l]&m:5Sa4 EW=9C H"rD\Mje:ARp'`Ss!j9b-6gǙ'Du]O5u !&s꯮:0tP6B$zxߗVΐttfZ6*n>YB`Eɨʅ@[1nn y:}h 3 [c(3Yl ixE%idSԆ?`} Pȼq4],4\&r)c)20<#e grݟ0$ф[Jlb]ci!D}.ˆn*Pty$:2-&a䱘v>k&1dž6aHsfXg1%N@3CCGyj(Cmendstream endobj 9 0 obj 5047 endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> /Contents 13 0 R >> endobj 13 0 obj <> stream x\Yo~_@2xM6>`ͱ6@`44(MVk]9X/,Ū碮,7^Oz󤭚F3?-jU]M9ꞎ(*Za*mɻKQ[5TN5˙~ oOvr'|Pjr0R;%H,,8(YXQVӺېiihUj$!Mi&RJZQ̬sdʳNfmB;!}(Z| ~K!+L> kd|m+H9嬮k?{mZn a/n@?%Ρ#4f*+0lgmY`Z0̏Ψ;=5qgN >nխuwNo`GyN'dK<ߪIZ$rA'AﷹteNO8>۸|'4#3(Uك5(CLעm1ho~״*Ru"&|(gޅ՚HpK1j  +Ǟm~QiIc2@̠F*;yđWp| B5ݦA(PNJhW.֛6IT~DgT;Tx ,U)mcZ=ϋ~=4+F஼dB:P e%H{drm۷=Yœr $C*J7 \2IDWCV2̌7i JD䆍;X{4{RIyXfc9UEvό**(*VY@zkN%^+Ј ;?S;]+j=h Z!Iqngx ³ޑ}3΀!d^#1tDGj../4>HbkcHe6wYy{4%$Ю:u8ȴS}^(-\؁NtG!5,lS l,[d,Ҭ+Wy`<'4ˡ CDI"QD]н`z#y 'u.O'އ#L%tZ< c'p3s\F+Gc>ӝѸ򷐜ZhЖҌUtoGA1d? 8̳8 L\ C!/dgJI Y)B2&ʢG߭>cdɩ;4F'xgWÏ~vDNSx^rMmPtۗàO1Nk\j|p~ۡ]mlcYbr6V'3(72( { r gm$ėvܚ,J9,{kPh2ZiN[ArBЎ)I瑑`8{/)r ^0 "qMH%>I єhZشւi]rܾ_ߙB}!١ܿL$߉~.nL }`]A&h Ba&6z_]l{4cO DJ \,IL~ CK~!4WjK)@w9>VRUYߏ{[sa+5lso/&91~:PKqh?+ҊԙRzaU` )*J3 7_z[hm 44BuBŕ=M2oer$9H> τ8K*tYqwk;}4 XPx ֞bjM2L bbt7)pVi< biO *hJ $\^ s4`ŎFČ>fi]+$MP\ͱ& V}8=߇Iג./vy /K#CgdpC~p MzrG_N@po>+ +he ώoy|cv<4U}z)ơȊ^C % /YԕRi { i[sZ^rxdh>2 B^HKM?{0>S\p(HlS4%?2D`Lm1T =OhmT;v2^`]Pz?qߊendstream endobj 14 0 obj 3266 endobj 15 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <> endobj 4 0 obj <> endobj 18 0 obj <>endobj 19 0 obj << /Type /Pages /Kids [ 23 0 R 1 0 R 7 0 R 12 0 R ] /Count 4 >> endobj 20 0 obj <>stream PDFCreator 2.3.2.6 2019-09-14T15:17:54+02:00 2019-09-14T15:17:54+02:00 PDFCreator 2.3.2.6 FluidSostenuto-005Administrateur endstream endobj xref 0 21 0000000000 65535 f 0000004664 00000 n 0000004823 00000 n 0000009449 00000 n 0000018629 00000 n 0000009469 00000 n 0000009499 00000 n 0000009560 00000 n 0000009721 00000 n 0000014838 00000 n 0000014858 00000 n 0000014889 00000 n 0000014941 00000 n 0000015104 00000 n 0000018442 00000 n 0000018463 00000 n 0000018536 00000 n 0000018567 00000 n 0000018705 00000 n 0000019234 00000 n 0000019314 00000 n trailer <> startxref 147 %%EOF fluidsynth-2.1.1/doc/FluidSynth Thread safety paper for LAC 2011.odt000066400000000000000000002173061362231004000245010ustar00rootroot00000000000000PK)K>^2 ''mimetypeapplication/vnd.oasis.opendocument.textPK)K>dmEE-Pictures/100000000000000800000008DD0ADA29.pngPNG  IHDRt& IDATcπ? ȀIENDB`PK)K>J3__-Pictures/10000201000000F20000005D37AA1023.pngPNG  IHDR]";sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org<tEXtTitleFluidSynth Logo{h:tEXtAuthorJoshua Element Green f+tEXtCreation Time2010-11-29 !P IDATxyeYUU\*Ɩ1P 1@a9/a0.ҝ &qӄfh(`lnH*d`@i\RI5zÝ|sﻯ޽dɒ}KU7oKx^D!9Y52{^8UY +ȫk$ٳ^Iv8BIJ'%uEʪXYI]+Q6Ԭnlg;g_UW&ȯ0"gҋQĽX'<L@^@ ͨ/(1j!Vkc jmQZS 7*ϫ$/0K[jl}^UWȯ"3g08%V e{~Еa~R)-xB=B)\kZ"y>4EޯAQf:2%ѩϟ^B*˜&x#RnOzߎDax@^L9g>L`ͭ QUVe|XfyқE6QezeYo۪[0RUڟ^rEjvUH|"Va>2{a&`Ex^K/dBH…`r0B -` 1Zku],L<.fWY~LFlpL,MEHsczp*8}b!>kV8ꮭȠu4tO}~}܏[d, lq酌sr!PJ(SdkA5Zc1UkT*We1tPٍ|4\$dj8~.ln aR(.>>[9¤0#f՚_>7)rrYql^QD^a&` Q DЪ& ư&1+eumij={}VB = ruaӽ?tyQ&=gB J#RFwu<3juYE1d3hx0%vuuOUϟן/<;G^KV[|NP,VRQ=JjW6/xZ!T OkNV_dU Sj?RҏSc/YXcQJ*,p=l]'㛛ObjZԃ{X]N'x~%6hł;2R=yK\"2 £I™ 2B]$c֘:WUe"۪fniYp|}PUOr~wo`D+}-hw{q^hUAIS&ǹsA)Xi7kX1cQ2֥Q*WJtU<Tefe7l'զUIf.z g>[wTKAs4. W K ?jJ?2F]x;!xhR<+v6_.'[Ox#iZew\ &]ͳzmJ?|lxy\Q;-q ZZ~Ծku1/ 鵘1I)gQF@3N0-,!Lc)16JU]TUUmy^e"MJ%׫hPՙF/BF0dӊeNKKkV➐^@1YkԭReQY6*l}q>l|Ff?_TctVF':[~pHx^atXF5\&eą3!dJ\V`%ºcFZU,ʼ_e*]Γ3Y2x菟Ia-]=/y-w1D+[L s ea@*]GlâkŌF)S׵.qg:֋t|Li|8r_2> :V7Xi Kǝ^wҏ;d2@Ȅ_/5hmt]uE2*Fgօ|8x6/'.VU;vz_.:˧VF=&r.(Κ(/E%;a8>9gmfյRERei̓e2Y2"ˀF^' ȵ.E1Wv|*,?u_:dZ~x/O6*̒a '_Ͳԝgxܺ&Yo9eZѪGvwNq/jh^ ϧ\ #lRܥw Sy|i$UcR*ȆE2V Ne:Lԥ)8t0nGCaoYym:Am.=2F,5zZB)"O2*z"]*W\A)Q] Mw1hŭŭ%Y>wO)} ,ALyNCːFJiR&l4xm=ϨdxLƋ_#<-{vN ?aV >C %ŗS2iU]Uyf9^L'7?ZZN=)2!>{V{|xxi QoAw‹+"=EDU#k,\G]IU\/ƃe2TdK0׃46I:lkygqfOٳcql@[ݎ 5kvu{Qe2!8c%]Qc5V(2e:E2tl_*F[7q^pzAIսǏZe?ls.JdJ,iŤcVUx;Hr666ΟDoȓX•G۫W9ꭽ>.A=g\0B_d'GQt!wlF9]."_*ߴb ﶃLpZvʰ, Bz ]Ф?LkT]"K,Z1_۟3y\-lÔa10YEYLZϯZr"Up֍O7IO+cW|'=K7Z(v,?K'dQ2 KeFn g)<ƃhYCU=*YՆγA]SY%-kj'4/qOp0kK* ^: hUMRXB`HSx$ǤPABR܋{2j.^?K֫,2n2ʠtiJmaK[-IBpsq? t0 /8 k\z>J(,N_~W|B&ľ; W$0J9132j VL]JUUXcJue*155 r1*™O@d\Lx2`\zL3B i<Dpa˹y@`Sj-e;(0N zN'Ȥdfs4_A NlyOHi\{c) 4w P~$/J 2_ !d#;G}F+Ǻ<}^>dءL J`Qn7ZK|0Q{vQd^w|z4a3bM{kh"{pﻈ` aEC8wNᖩRf̽Gðya;ǙPӎ,Rscچ;_`_<]/_zQք2^{>eûy^|5a[#2~AP-B"۵6v1|wi2 3fM^sD>ah2θs}P^ermO^y"@*qhur/QZ}YOc`5ǻ52BSŴf2(Θw734րu>c!'÷n= k͂5q\"5mWfk)%TzLKo2_#uiݜ*rO⚅Z^05ƃZ2-/`[3Gn"uJi ZUu *XBw.{`QA`&K]7s\1fLLܹ27cs.ܜ>P 2}%(h w=kq2mjg>?΋MףNפVVJAYɚ玨0d"^e^0UEW' "3aDеvEƽaƳk:c ]0F<Ç Zg04q*hUCFU05LEE3Sr;yzx]FmBSţS%VU*̆Pe U{PekM (ϣ{╅DŽ^t@ze>pF)wb$^phq?Z _nlv]$N1T;rKyJA)D!x&ʴ]0F &|xQaBppaA_Xܼhȑ7nF0Vv*XK"@)QV xi:&"Q5r]A8'Bu.S*@)w^x`B1 A<˄xTk&21ۉv5Qf & "1 KȰXpHa1 8̏W<"Y?s|1 G}NgAPRf={caF](&=@7}W!*&<Ƞ ?^u>F6<@SW0V %lex&!AT1_ 1Zi@o#;!ßo=?sLT.S::&2NJ,1ġÑ(&5nnK  TU znLc4A>@6\GxTk@oH{F[X A(`[(Pc}[@kף>lއAEAE^<ƹD` %Al30/\4L,sD%x̨˸mv,fl4TU.RIđ{ncestP誄 {,FkmAf!nZ ,jZj,{V%TU( Aаf`Qucp)L]BWTY6G"پ-|wxCi \Z?>oG !6(:vܷ-&pw .Pٱt@U܅ƾ/a-FpFR.aƭkt^0]U jw 3 fG*`-!2a ٳ%f/uAo-eVcEMx]}y'dq jPp;ޱhQZiX̛j#vbD3C&5 ąv(=1v IDATНglР4vXU3LZ'e@" du$[W^cw=)~P%(ms;3 ~+_ysg m&XpcVU U+h)έdkwT5TUZvKIt\ 0N((;zKW䙍ӄB8V"$ !%iepj,04pgȎ(u^ c;NrغQ3e&r̤AdCdє j5Myh-%د,gk9}vהxbEBkjQC !,B[t U΃ͽ)C|(@)1|5bErak)!|ꇼQQb%^Di-Ƹg_jo9 lk-lVoq hHWbCAv<{z1M ":"6pX"ڔy1!BZKic#펩gB(DBI>5a\^L@Y.D]h( LQSߧE_s?>x.; ԃ5|b ^k~?] @!I];/?}N;Mވ ý'Bރ7c,~[PetAEmc#̹/[8(+ k=E]@9Gtl+eCk'YH¼X(qg6N J}"eGz xxjc鮩 ʅ+ށҀ/}GIRuut:Uk覄U.jиLk"Cn ݾ/>_#WS|WU7\%J w|xdU6\N)3jOlq~Mwon%,7Q}%V׻Jk)!DpR,Pf-=k//"k *C Q$Ȇ7:፦qxe6DUdu >bn7e!WZ\MfZ.Ԫ;CF{ՔÚ"5٫B6m`߆~g޺ߝ%6PB%.]c!އ [HWn_FuYMA AP’wG{9ElL(1,q}̌eo[ >z 7~x=嘥7<+ߢP$[ Ҥkz_R:0@r;J'Iu2 ;%0 0A`%)W~2Z(0k}wR%Gmr/5/MɄ{~|uaMiiNh\9 wc&/wߝ8磸Ƙ'glď}ognPnes C\溶=S-|Ce]jz ߸sĽrhE@0 ^@_monf6De~4(LxM` 5RAjIfŒ,nM`Zkv1SUC@Pu HxLQ;|k'8ſMo\K.Pe|N'.px}1 `^Y~n}Opxwktc U\Ћ`d‡?2;߱[ԥ>(@̇͟|d Vp p}msNc>םn9![e{!8 X?!>1d]U;2-E`%04 "3613b,!1 _"DVDgf<<)vwνw=cx.-Ur _;3۰l~qZGP%m3& 0‹Cۂ~v??Ũȓ Ek,( ..] [ʷu&Md.lH7EkfEQ6>kG"@E ,C^/ ڐ{EԽϭGt6bEBR-1>,6x ֵh}t#[B%0嬵mіtjݥ7xHhu3uӬxz ݹVv Gge_ /6> 13_KA!X- zg5ƻnnwZT6zMB;h1}c4GxeD;)rBek5XkuwD{P}`Y$+)v- u@mrgo̳sZWvI Ys*Onp:r<1q5CTF2,21w!e=R\vao PUmc9+}|7 VjLKm-c耉.~[seg/~边ϸō'K?HJ z͞o}Q|=lO}CMa&p{Rz%Xhx46rX'/=Tu VFo-V0Jc3i;@;4#rps ZbFkc)h9MSD@czIw0!P7d3WZ]Zo["j55*Aeև/ ,懛Otf GD܉.ΫV 6/Pf cQۨ1S`iv%u؆*ǰ0Se$tć>O?Q0 k'/di!坻ۿ0?_?e0a!@$`wҧJ1>jr9*8w=`5JCimXCҹ{$g%( !νq]tuk|. Dn1*냀C]7mq5 poڶ (%)z!>-**7fU Pe[ 팧^ͻӶ׬/ZO=065 PXR% ;#< 7>Ƿs5BP?{ -M)Z[G 耱( 0v~bN 7' }$El05^qb"Y*M!ԽqEvc$NFLvyMd7JnZ+ k+S1ֳ4VcW?$`M8o[`62)?k=R õe+z_aVWxCm,vJTwR&36%i񎽊_֓x•Tkj|WX&GvT¨o?O0GF}Bw^`t]`H$%B}x@$5.xFi/tG QcxME:6^w  wEp 0S(߻g>ïx}{E;& wLozC ozC ~xrO=bsKa8Օe|_{Ž'*=b"` .xo]ݣ/G9܉皍YD.,s=* )5Ʋ$)V0м޸V!K[Liq@a (/BGeYH92ZcVss粩wT|YTR%LOH)zl_=  YmS D57`DMn~ce@C@>M}Qh81?8sypʬ /O)( 1[9g~~"ش‹ D).00QlP 0Ljd7?U-BigQPMw^oDl$tPa^u3a@(J;{_LP ڮTo@˞Ln&RrCO5~K4OKbӄWoc{gXo܊an~Daj!q u{0*ZdܼS$ Ew>KpQ/y_`.W M $kWc[~o~cV3YفiTQWJIDAT`ŏb`(=:/ sxvnVm5U9(1s&9& #Dϻڞ3̵ CE[>W2\zB&a}.n 6n:TJ SouaUmEqgycīq-k?~ԋsgEoXf yZ#-m=}qCdYҠȶQvQ(^W 6swUpԨўEdSv>e!`KqmQ}3,W)1\C9CNEmlL U:dQm6Ihw_"ud5d RCleރ/86ڊx(]"d|zG'k1Cx7wA}F걶QOC'qwT[!OԆ?UJ|q{.-F7]ύ?o٣{?|iY$'Tuĝ ȿ/G(g(bƹMjl.$ \ޯ5);Xa`LT]5aǪamn( ANy'<~QVn@;_Kk =+/\VUGTԏ=sEn /I!H5h1 m P8.&VVXQbÞ״% 6ـ !ce[6m0ժ*)L]:H;q`}PjI$0$`[" Ixɀ9~YKLWwS [$YX`v_ԃ mĭ oFg?>yPrq8_-V:6`l#nj!K(=~w~^$`>gaRV\~bm!!qPի 0-㹮J Uq F XTʢWEqxh82]g7й TPlbbzEW ~b߸:@P]7$P1anj5ari6OX-"n@O}9dlMyXˁe7̩TP$BkxO(8QXԗYϹlOe_9hЭفd7&Mm9lĦ( qpT!8S.n Zq#J rzXj{>1#Ֆ'//&}A~f! @'BdsxN.#}Z(|U"'i67Q\a U(`!*Kjd}>9,Q"˙+gv3y.ZjyɱS]w~mlT1b>ϓj}qwN~2O;j߱^%G:.Ax֮h\O;<ޞ/9_fS=_N˲f/YK|Mw\^4o'LDjNfYNlA};}zߡ 0uZY*J|++ fWJm*||+m}G|Qr6pxGɎsnnJ?`I+skbvC&j)33$>M nu|gK۟A,DTFHkfWn@=sӉK9rVOPxG,Ki^͑3nhx}фZK ^_<  t`c):D9qi8\ гK<9k}rr2t~_F x^T?1E'i-̬½` C鱜rn~H dBRẏsRz_\>|ӻUGEzN5ݾ :bp{L(]Qrb}8 9 jjI)Ql 2(]S'UzIwHc)  9LD:Qerxɇ/w;^o:6>s{yn&ݵeU.u$e""sSJ*UU$@;,b+CUALD&T wg1l 09|0 8L;sV%"EU|(TDꜪsDdd cLsH6p|9xU]D_S0 tkE}Ӵ7"ClLMxG$Ꜩ+H\TC}r@fP_JϲM`ja31qT#NRF 8 ;TY:k ]- h>SɧL?SrJ9đ*& 5ldP)QE1D\坟*ԉl&n}^m s?DEV~ߟ }r.4nAX`N9-DTW |2̆*jv:{O~ @2ˠIW6ƦNw2%-2Uz(m:qG`C :8#N zR$dIOtT D Te0(%Ƥqm&NWj+q8mDId"%v-*PxU)rq/obﺟ}Cf[{s:" oވ(n&4JUMdY9 V+9P&'q^;]>ٸrfT A inĖą7$&.=p&DqJٶ6'ɶ6ddSZو GuDxxM@'}nj.#Ҝ]3G ڄM T6:v9$S?>G\o%5`>:@!)sq+\{uprq5P@OκWbRrR,+.rl"~Sn7xCd|Sp!g `g7&7eDpU6eȍ/I k]f#(!jmni^$jŸr#R[vY7>nnj]{MyMk(MA6x68VxŦuzq`(&`Wgiӱ \UEʤ2-^.E%wn?hmNpA-boρi\x6nV3ܼLUC˰MQCTG `<*VCȟh; V |*HjQB˙j>UGӞt[d|xK1]qLOݥODͲӊ9C+W9ZV6cQC݋0$"K6$R;Y&~^T][wQHu6^Y'ض./7.i#):Lq8`1\bZ"F/l,&;-'}]nݹp~`3GMkku9sy:Q SFǀ@8bIBr& UQx i#uAB(м ]um=wWe:bN%nrܹdyY6>ƼdWč&~Z HTʨ'rpSKw_~ҏvGѝmϢzI8Tm43:q}('P1!J0dͽ[=L#9U@dOu ?~tatxV {F_LLPhƞmmJUͨkM"SlL6J;^A-USj>Sh1-G$@YQYI9nuJ(q:ٔv44o#1!\ wu^޾8/RHe-~콨~h6eo? Yt/0\V[N-ZMvRU0'*#q uMAQB01>1+񅗈z2S)Ɗ!4Sw=WL=O^;;VCf&IYy$qs8m .[t*~҃n{n^_* `31n*+|? aVveeIh8&ɨuZĜ*pPFH$U\U&b@t 8˱E> >>O|ъ#cCiL&^bȨ*[9yWOL{ظ=Η[nV;32^mpk}b&u=׎yCOwfYUĭ.VW#W*ߡ< VnsjL9 Jj8Ґ{"'SAD"USl o^Sh&towlcl\2=@' [خ>dXkI6^ n? j]"Ӽ@cWL}ʁ;ucE~H  jRGNOResMT B!2c,2Y~_Tzȏ(/R(e״"5J ĤZQD*DZݩ&')cEн QO>VG?<@%:IENDB`PK)K> content.xml}ٖȕػ'N֨[.u[=SnR}|(,ɢ>~,}H ¦f-"ō_2R4 M(A%~?|uoG_~~Xq>8x;ޱ_i.q0{ {{Uo~Gc+YNo|l]w6|dzX|OЗY<,r,>Ga"W뵺6$}Ӧݭ&Uϭ4|.,T|vb9 qsa0E<=pA7kkK7_I𱦅t9t,|*/ WɞO*-<: >WA"~nWgO?y`8ݸLHعR. VIW-"_F>(LǸ=on|?ALb+PEaU=A|^iw#$2@A%wM9.?QϷP^ywFF@P X"S7 `6S #*}}d/lw0?=*Լk4hz(LV }7)A\-h#޽aΟ6Yu|FaQ!R?H:>_8ӿ "S~,Xm2U>vUCYvkǍI"r`pcC=<uF7_fwFA S9-FC'x /v"mXIԜ(`̃9)!Sdyo`)r,|c^EK7.ΒKR*qSk+7uRw(o я@EH AA ,>E`G֜Kwہ s[SMqe/\7X>ziR>|^6_ Sƾ nT]ә(C>AL`y WkYo;^޻d>MY6c|3k5nn/Y$sVzM 4n_-4<9ϳKnHtVWjc$.-O d`IM`=#gJԱN͂'^t:cۗZi,3DN-33k<Ƞ;M0t3ks r9'~u[|0ӥA:Z!e-|*%2Yz9{| i_/GaTQ?Jb 9*^$VQ|+eEO#beQ69c֚nE !k.]ⴺV~!]b+rMdtŦ4󭾧L r ((q-l=Tw ͠VCľ0yx ,A2TQ;В˴xjE5-gl?vKۍk"A4qn/ YbiѳCU_d3퐹ʙ*,+ Y[g,fwmQlm]d,sCv> `/ Y޴2 +盜2 |0Ü[buOlxQ.+`Ⱦ*gc˻Ⱦ*264ܷύxIZ.DxȎyt"'O?P22nܡ)g朩IƝ9cJo1G;? gd3B9.ĭkq"`v64`xu!0;cڇ` fhi1q]Rд}VĹtttop&:c]̞03]}py`fǁu)^_z0{&?<"`vj?J,VbD9#|gV9vgLGbD98ڽ%Gn-;"tH([@2RTeF.kX2#)Me8YvD˟ӱRJw\ːh~y,;dv`%ۚݷ;JHjRbģ.#L}ͩ숬Tbcei#Jow.K#+sdOo˓Qv;6!12G|vc2go7QzStܘv!7P+sdؼw9O9L26s̱4edXcMnȜɱ2DF#O9>q5[nṟuwcem&+slO[2`ȓ=~ṟKӵ۵A͝iK#Pӵ v5!Nᯊ-oƆK\r8Uy{ i 'hddC C"I?Hoz:^ca'(YآT$ }e>Hl)+O坎[ Tx.hC@C |W?Gf2а0qZzF,~ǞaE-4E-W'^")^ϭOX{>I<!u\! X= wt.tGa K$l<.1]\/7c`$waoh;VfDLX)%a΢j-?ygrxhD󑒫d?6' G__ST*Qև]jja}u/t(u@<<͊fp~+z YΒ((+3S/o ׿ o%YHe@Ay·;vx:>6]'QAπ( ap 0IV J\EU%MKԨ\4݇ lo[[W [i?d"[+φ-* 9 %y|ٰe]%!ǖj$o<$L%y|ٰ\%!ǖ$o<&WIȱec[W [ӫ$eO%y|ٰ]Ư娆!\+ǢK=$_ǥkmax;غ?񏷅k`xKغ?7W`_k>vӾzݕ{/@P8@<*,J ^"m4"|A%Le u{E4yIJ-,ڃ<=KeECA}y=}#Yb5Sg %Pw7ZiuZHDo>wn#?Y7Vd[CEb|bZ[M/e@ֶ}mݨzl^a(3mt,7Q,J\\>MIzNIBr&CMKsF%AHWT͜t׍_iuWZ>gԍúj;;6K\$+ѿi7~+'+ѿiՉ_~?ѷTƩ <ߧ@C{_ןevЯֽ~m{'[o[-~񮒨^oa_el[' j4O7!Y+@nm$ɕ_33z[y`q_}T͢\ԾF|'!psoE0ݡGxg|L&>Ű\孌5rqM\VT˹*?:5Q *7C˛̽[jչl]K_'K8֮Y7v&p1dY*!"FhҮj}T]՛ع>/ չƾz,U{|MQu՛zYg,JDY75MjJTM}Woqk]Uh_~.RzMkL]90Ⱦ1KM1ǺY2Gxh|,q7OWdjmF~ .iUz{rOӃg(OQ͊NL;uϫ$ Ȃ?΋H0[Er?(Gz3JXoIZ"ԕlbbblҴmČU{b UeLHi0>p>)w(nm⮆R̡3T F{z}+`} r%ٛe Eaz/am ? U+XԦ<YAfEb3xף)թfAU@W`|VWt!LA%q 3IhpphHi?4:14A$n9sQuZ$6\H65u35GU-՟*hf qY\`ӜSmd'/im 4Uۀ<8D1bZ㗁l`m.Bcڦ&B4:al5,a؊3]ƒE|tWhJt*u>G:R ,'7}:sWg6 1Ӓ1,TLlmXb^UӶ}W]ƺh}UǘdtxǍAv*M @ܞc Фv:a֡ѭ-]h|et>~kXs95y6>*KOC:/MdH5v߼Nr{wUg˳QKڞqnѵ &pj^*#g-tqR4~*u,JM"c }?A,z)sO)El"GT1SC&ګ D\U3/MB5cakÄ,O?ŸB'cvfN]O%Dgj^)"r[MzcMt;.p#Z߶okzoN۰V8 ; [LA4#pRe$y̦Rq}Q, @ ("ځ!]Su.܆]nX[lҵ(w;B3DZ0NJ 9.}(@ڎ)]FRm)C5̺HI_4]LP4{b(e'i7Rg^h1t D&XZk8D( MɀTc-64`cHdι#İ~P0FAg2X㞢_tSo0" 0V4US',dP42/ r]e/;&)X$?9}Nj-TG,?( u<Ii9Js"[!!T6DgH_HHFvͤX{gVio,y3i]B >83RvoyTp:יjkzПv3X|2l@{h'NB/PF2`{ 5WMTs2_Ҿ!gɑ ݱ©9yVQWXmYWMpWMp@tma-767dqC߶>O&U FP>qj4l>94ٳό !V޴A<[ XILp.~B0aUb,>M(P|[K,)i8q²$Ót#%.-N@-*A{$!h 3^oOh>`'9BE>zg07nJOɔsOV ^öS1%"apZo?U~*,xφӧ[]}G!C\Op"A'5!U`/pξ܇N1}&GJȈsPC>loK$6@(h#\,j(XjhԍYf&@?Ŋf)lFW35#7F[^#Gsb:fRvS dJN >NX.aE:|^F9v4˲'M{ D[G);jsg44#$E($z-4mPʹ(h{eJy"++Rrl !e`kpK[ tmnR~ XĊ><';A3+w 3B)%LmC}_8Hj/L64잩&*b ! NWň(d)[1TLH#e-o?y1@QyV.|MA(G7Ty*UA9uTn9H \Obrv>l:$(L}L#Jߦ켿c(! /`|[6g`23I\'bD:d@hI%t <:.\YZPV ]S\:@m?sGvp0 cFql䭮)v?WBSl4nb@ Qt2'kSڂxr/ØWpJ|,~4uiӹ?lc1G2?~Qb,/#όuga☂:x?֏@ s}l _i9Nq4hwznhK _w=$T[Q _Ja1BdlJ) {U0Plp0=y(C0YrafLv`,[M<$&Xɥføb_eH*8-90T% t{%R)^IXE|@#mS=enA:J(7U/6d5XLQ)rS|a 0 H\&%7KSz۽)2ƌ2X6wFFk_VcgR R@TQĬʲep3wX XKwck22]^ #Ae-ƁL0D&ydK=(` Ǿ O0afl4K#)lV\$C*FD4ǜS<- x)xO3UMco2!:']]Bx\HdXBx` ǥNG08)U'ΐ1*"4QjLʵNVGǡ&qJ8 z ,bAJI%bpna6.¡ !!>J 3& / :zKu(nʤ>.Ii#;bDXs!sEM\=4&CdO6. G2 KiS~)Wj _dSyEP$e0=Z͇^q( n^T 8kKQȬ2}R0!kmL$S!7EY4 er1X[ C',_[#">  X!*T.+ؙ$Ҽ%s.OH' @qff⣍{Edxֹ)2 7vY/Z:`V!ˆ ˉ Tf|_J? /nKmIy8әa.X.S6IZ)AsP*x)x֖-#.P<M]B~_tN5!L pm거\F%Lf2rL"7UBpLtHdL*dr^Iיs ^0N4D˿1 eݜFͼJDoUmf,n/( V/i[TlVglVcu:I*^ܬ>jMٌ^p15DpV XȤ[RҪrv.)4@^`50LlV}NJ3vGoK%5<\$C:וiG9Jw%*ErNJzXqz"䞔卋B23_'Gci`0l4)y܆[&k7{@z&^ lYHx4KSYFF_1}v~@*o4)pokĸ2r͸~i\%;8Mdb̟ytZ;3mH縍cs7?n j~3/4LHH(QPsڬ܂We _e~ȰVsWJT\ !NQPSg̖[T%= ?qU9%>h^1DPC9K〜=n IwВ̉F$+DPIѡ 7X0R U-Ɖ3Fs4"iȬgH1aI ^YgX_XtWFs<2j“bus&ޟ-j񍲼 (wd d )ӣ @Fd@|45Ie@ -kБPJL9-/ikvDn'IİSHQlw!BegXL_X~%QYu2) koKY;6T,j|*m[DC(K_u4a8BS|x>f1u) ௕Tg2D?,PKSx *l̫dK* O iecˢxMbh$]ϣrd$Zh/b[;!D!zT'7ˤw# bbBFvczv꽗"B L i؍vR'ݕMdr3CnefFJR""oXηVƼ$g.j2Uf8Њ~۰l-Y9ŻÏln,G<۬TN[k{%,+ 0,E!,2LR֛DƁYXH@HzeT7I"(ypΤNkN GՔBƀN8`zbDj#lb='d*A*i3O1ZRcYL%H#kLI*VXҠUb\ S̝bv6wb0' ԉXb6ŤO&~l 1|c,_K3?n)20O?qOTT1n2KA5hM DZ-R_)vՐԩX@D}b3 aMB?t*Y)PkEDrzUیu싶!7]֥J(.USI1IU1/1LaVRʋ*S4M霦l9M=pٸ5Y Bq9,uZ& Y7Lep U,(UjY Ic.7ƎsB&fx9p),#oU+^X"` G gP]_QhwP#Šaf`]hQIK+tx*W M1?_9<⊕\@Tsh/Le:jV2:R PABYhyV(+]Ft:|`lf %Dutքbm¬,B@̻+b+yV6(0-* QdV.:xı1Dq^V2dJ9A b8_"8Zfߜ]`#$+Q *Ip+ oHyK}9IfF*WXpxcnзq'<LohxA }VTU{&=(щ*U~"3<2X).vh U1b,vxŝb YGw c#htIfFl=@ aϑtLٱum*h7/p\e}0hF{"0ˍ, N_]ڳl%6'Cp@("TW)ncNHp+5`tUXem1 .|Z"ݖ55ul` ̖ v˸ hi#9)0A@ RЃZ.p o7B+nl @0nT!΂g(8=`)(guKT۶PC0v_7B,ˆCuEб7X/A(eT<Ɲ1vϾl(y'{q,q-??i4IyOUHmoq-u8ێC8FāFllW7ݢhf߫˯OX ջ;+TawX;ܳw7Ppn>wdV0?~3hjWٶhiPK$XZ;5PK)K> layout-cachecd`d(& $؁d C2Uw\   i7F:&x QJN(1f3XR6: E=<1 manifest.rdfn0EwrfYdhjCP}]ҢUչ:8<{Όl[7?QF=O,-p*Q,Tk!ɲ C"[2b,#V`_A":q4v!Zl2 Kc~|' *S "tr5M }j"'.#*u15teJ]a2zPK)K> styles.xml][s6~_QF$K8M$5oB`$H-ۭDppw.|ý0017^ooƵ{T郇4KQy5ޓ2D_%/iK%Jxgmsb5mcF[hGrkCƌd*7߄mǞ 5;#D^wFpS=$ۉZ&6c=8cO`IJcheƤhEwֈֈ!ީ^SGn#r*_or,X *Qi j}0Pήe-QĥHv#<;x諄t(4|`R6ڞЌM{ұ2Q߫7/Vn(IL ݹݸ9*)cM8Qfq<*~:E03K'uI b<02Vu.Qq;/&$t6q6:M& A6l{7d#j|]֞ ׎' ,~yסF<ҍ4`#7#ȥ68;D\#C|t{**zwx9Q0F7*Ea*(v|ǯg{ ` R2O5(,j-0q&a5rBg ZSZCR'|UZKQOrm{eJiCL O7Υ_qkyhS?UVW]g=Z16Kr~)/wڏb~D Kek˵`X?Jmڔ>Dc?I//wla!EF(&DNƜ-&2cׄu a2 )M QKC߄2de;c]T pE;o-"AꂫjA@9hSO3+JkqlУ Є*5QDM&q#8@,h49&jIohp:<@5,a.?@C˝%,9(@9[@ER}2*X)4*ˣǃ6COM2tpje<#7^a *B]xXؕ^ᜡ|lm0\<aqp()HE=dž kM?UbXe18 jހ" fxy!q;TyHGL~j"+ n"!w).D偄*7|Ai}=`VC\X1`^ߨ.Vk[ &wf+WZB~,{:T96յdMSDC~Rv!3-j=_0rwT^@%gִyΙgso ΚҽTHN`-XmXZSbd>igWc|E7 P~59kH L\1*\!cSd1]5ڬC)`_TSVDUwxf㱞xOn! lC3tbH^ L d2}.b/@@7Yb O^ժCRD-ݸ8wZQ>oFhnp2ԧ;y% D$iG?.m^"`|0.!x}l,RA[VpՌ+ _( +ir+(:k?Akݸ1탞#2urªs$Ù':섓4Mjj6x_ߊ3`>f},T5Kk^ 6S %Wَxc0\R#K8. E\7(-zհV;) iRj6:5UÆ`}loWU+ʙng:Va>)s{w.2\'d^B ^ϒdy~MA;U/5<%Q ( -c*I02È*0.[L~?"6HR\Ĥ }@OT6 KܥOdR%7Ut,_93wsS !j:nhYdB_L/Z%B_n>ѭ# Dk]c>^4APK~` Ψ$ԭ /g qz*㱓NB&xAz6,[Y&ݧ$1/3\.$gT@3?հY~MJzY<xzWMK[r1/7ϓJ/ OS1^ eO'N к'u2Tg/wR]=71>v8-hpC9'x>FeA'' a }J: ݨH=lCnCB+wݰw~9-=C2R˛Mrc7q}B5j(G%4!p(&!kKK#$(9"QaNz,͗w 8w{Y[>ҋM$^xwBp?^ާK4Ca&OA|*#hYoq(Fcn1ܓҊ'}!*Ey'5%$z(&RmM7i9=_Q؉j/_o>eg& qg ȭJiE0&ߺm ZC ,d[ }qn>׍ݒ+K͕I}.o+í0Np;-κ0;ɬX .aE/n-ݘdd..zrk-2en=UfW=]6mybK~PJ֗22c X ˚;pw68ONg,mhV|Xtff‚P^Ks#خX4G#URҒ5mB"ji""jeB"jiR϶TvtrQV%gQ+^eLqi6) >p=xaY,7mQcc2k/,$8J\vz ΧENrۉI؄EK((b8.pO IϭC"*@5-CgY.bE !@T 3I>>EHK+TbՋr M&%4|BZ}6'vR'#qz#_~f?iF/ a0U ]JPDronԚNj{?>1g*V;j+J)9Y|Rd] )yX?%ƒ]fˉ oPKE2عzPK)K>'mmmeta.xml OpenOffice.org/3.2$Unix OpenOffice.org_project/320m19$Build-9505FluidSynth real-time and thread safety challengesMarianne STARLANDER2004-09-23T23:50:002011-04-02T11:25:172004-11-17T02:17:0498PT30H36M32SDavid HenningssonPK)K>Thumbnails/thumbnail.pngS.<lζmO۶m۶ml۶mwj%$dwF"e`Vm) #!,}㦏 Gr ݏMXMlʹQ,Cfb Q, -QψndO?;]?{]J(ɸ:'|-ڑ _eYdp/=sR|a_A&!-?R*Z/R,f60 KxŌ(L1>$Y1iOngxJ-X+6 m~=*G7{{Y}s1IJ $Ϳ+:$~{p`6~ V.gYkkHk[~f]O/a 3,eJ'Ojuӹ^^eELp{? &مwSר ěķn~#r}[~.Fߓ́FKn>?P'l|ͳeؾ3tpGe]غŰX@cam9!ayA |7 ѽV̉ZWqNMU\N~ȟ6żmk0x$9{k4?ꭡLVOIm/p" Q jE{EsS6X~U/jYUc*~z vOξ-w)Y? 4VdQӕjg5bt$ x!A"`o+K_.çEM?i러DRwF eZC~_(܏`Z}ւP9#4Iy|G]mnw7DTFv֑DGŊ GirMNT$n_3Aڌ=?olWoQG?W+rd zno _pWP܉]4dSyźVL+`%9\k 8{ٶj[n%~,C'l H2@PˊY%b?-Y!_(ṘkyvhRDIqrOOhj6-Q VsTqjG'F8=_8Ϝ#q8 NM1j> =fKxW5ơJ9{vCn6 ^j|LG~nM"ޜ ^Ko{-JVhu>>xU}d δ7Eصu݂~mYÇWaC1W1vv~ꩈ foY,:uʯE |3M1a8t"L* 7KwWTqr|>+1s4H(w~2M~ |M$'Đw;Q^n>*Pߜ^@t wȼJwwa|1HUqy`=",*`&uQ*t],(d7mwx4k7ZXYtV%RP)oŏed|4e+I=c |lȲpGF!Y_g|C߼6>|:2W@ԟïM P{3񃫾_OnM~H9y#w|.2z{rv0n:ZΛn\mmG=c6ds||?[{(Eؕ սMcG l>!REs [)7ziǎ]$H X1hX[frxB#Ad0G#ZQeq `㓕{ "MKk'TӤ]1 ~0l\~X.˄wΫN_c<4҉*jUt~DZԳV tFuC_Tkc4XJ 5h4MGT=N&?dXxLgN&Dé1ը(ްL7!x^?\ed-c8=(~Zv%S r}Rv.ӤFo!qt#ى=" ~5f~)b!c[)+aKK|1lht*bm_ihA$H낊V;YȅU65oذV+/'u!$9Olc^2Td0Mx2l)}s^wtXJf~Z8z.jeVTqڒSV qCi?ȏH7Ãڣ_y.~`4n3#qwqiM{Jyg}ith<7L)>WQe*6 ڟ`$|Y֝H/K! +8N͕[ƃP>9 QN$^g1N(o%."(_` #zߡ.!ٗOy9 =nbz|@s8B`ݝU~פAߣ̩$ގsE8I< Io`@OR`7/~ޔ2Xisn H\"L6{vt2e]n6;Zuy q$AԧYXt9lE79.$~U h|]ur1Mp|:7X-,z%+l l=uܮ{7s7tW|Q? 6V RXɱAi--,KzJ&{BI"; Yι-8;S3^buE4;P2qxUJB_kTp},| ^&?Zk''+P&dmVIUZfeg AaeZX~"nmTMQj lաYhi׫:h0#AN2,a%Z7./`u7v NH쏎ul8 MDB!BS0f0/2!`=#%FGN&TZP3Ģ(Q@,Pe  7f}ao ς0t"WilE|zBt fx˓U 2[{:Ոؗݩ,V7z%01J/`{4CC{Itx.{_擆ߺ}UZhLe'0-f&>i78dpˀlyD:*<fPraq3N }OJ)փڏ e -wA (ׄ"{]8v /slo-d_>88⁺wஔ?Nh +j?W!t/0 J0$1Boj}o2rx!>U\D< 3(Y>Ť=nB诖]bU+5x ]c?9hȫ.nJĂ Ŕ)}e֕hfQ 7;{t-DFJMֵF-J?{!h,4Mc3xg7fhVSܬ8SbW ZEY.-e䠶L:c3$u"}xD\>h 2(ڶ/$al)%+4%ym_js14( 3ƃYl <ٿ`6|߭Wgwe{qLEKWm w~N*ֆ&iq׺];DJ!uN(*2‰(G/j O߷bl`2%!Un曬uSq883__Q6% Ұ[֛f2ךY_iv?\ k5,=)Csp`G0(WS$ pr#-Iԟ*ɭA5@ʪN.O䔹ᛓ""krщ\h " /Hz꫅*ӿ@H)Vq+~3A\jl^ňiUTP*&`utgӾHQsrQe+ uA0\ I ۧIZ}`idnUXDb)ep7\+4/3C Sz#R87po5za)׷UfN*6 0i~=]#$.d^;^y߹F' 3= t]}|}b)!9d63-!aH 0dZwY#\Z-X'beă^V^ϡ H~h  #lC$׸P G?{ގc 1?owj u:(@ cqq2$XN8~HN!'PTnk:ؚuAq祉P lθ5^1 :)IH:3h+% *ndWg~ұY+OqT-‚X~}jk< ?2|di9+n=sRι3=27V%sf*lxwIjo xs]4-X9ǷpcSɒ&nb1.Ry92QsOhjB?|)]4t#f ޫ^-g,)E?r$).v+$d/D"BZ ˏԒ"ErCH+ԛ%tUę;"\F-FXƼ;LxSIJ*7+>F(b)xT;ʃ+dd$MW͢Z]}d7cB G`Kƺ<j~76#$Rk}ˀG2tn1)v z O,KXZ02M u=₇8TXjwSױV~*K) K@|/:I- C>l5cpfÛ@<&㝨%Hv fQ1ze9==$+(, |x Xαٟͦ{4JKƷ`U("{y@詔}N :K s,!^^z7/+X'`Yq׶t#`l74v˷7 |F뱍ԚkL1ry➈Fe|SW>ϑ^L0뮻BP=0pNľƊ(?U/{̏*![~8ЏR<2 S_b[ybK&]]c#R2g-G*XA@O{*v}9$oC&Ԛ/ (t־)6|YQUMDi@FJV2w GnE24݋I{r#lxNytWL)UtΝDBVFAzl- X`޼j&JdB҂kB kOp-M۪Msˆ)i*+6 kEu93 z gg((¹PR&[ 0>i wo{G(鼻l% ޢf/ 4 ~=nPm_.DQ[8\;gYd$P볳Pٖ.SiWI!u@)@fQ5?F]U]vLz*8H nWp c5jǓR%W{>˽je}Ja<YWm1L;HPZo _K+bp[of1_cBZ 2Knc@cˁbX>ZGo[%)/ l7*MoNӉ-c̻, ,5:,?/`Q%bCLPc-9T-D ,ܛ T&H1$18Yyv#maX]~=GioRC7%)ih͔ '"\($r9V#a~  Ymn 7-&ِIѲ߷6aN^p8% 's-I&zr1 I a2@ hxsJ_Qm?꠬:f]fo?܁VRf}s EbbSlU4kS*gS?-U £ZԪbsQWv;2Y6^✯lm,oj^iF,4L vaỒG>1!r$s?)6g65׼`(0s$Z P'72#UnP=^aA.Y_tʒAS4FCt?3;; *#&U e^fT:+TXç^G/5 _y:,U=8:5 K1/z1!~gAW"w2#A14 a0Eb[`A&(EF|]YiNAz 3$\\$̀"@I(F'9~q[RԇA@L;Kny'̂9ć >wPSm&ǤT9Z<;Ǘ3\vڶ/}{sQngffE~/Ї蒊UM\  5eEV+WM@5U?烉\_Ю`sk |' y{ˇi贽7hG,Iӭc ^t'3зMݫTYM.p%oSMUygo5F wil$MX!Cv%4K@TrxuDf,*ʊy MnU9*hO{ ¹N Qrk ,~M= DKL[kqV?FeW ~:;+ȴ6g,PY .+tkL6*y D@$@k_=V'[Xj'b},RBonh1YFÂPy|@t12l9Sd'hu.zkFQ|ёP]rXi _&+gbn=5[}bPG9Uo#a b26jW7 < &O5c}XJb/W`ZŨI*|Ǡ4d;xt G7d -FDKLԸM6k]t0v ݍmS9+@%du4)ދ*!ES*[:D/35)I eu`t vNh&Iq5 2EbdA]jP ׵/Bv$%,$M;*I,fS|yO!YX퉁%OApO2BB9Dݨ9rZ4$+RtJҐ8B9J\Q (TYK$-eXZ88B/2]i3"LۤPY\c LxHř2Lk鼞T\(*u7 eyp<>fm8+t)"Y6ʠozqmB(%85zϟ)<=fw1fa>n>' f *]}ϰY5u*J0a??$:2;$"x(678j-W9-uC&Nkv@]dvqN\O(7LSv#ҬibFb}l|o#0l ŪzmM=nb{wAHtyI6EfJoз.PV&Ko<XQ*ݕ|kh**Iw2j,D^b(瘸M`уHŌ3ڇ!w}_ ȩ}e:rp AjL `j')g].[ Q4-.1dhSb h&wI`$mkFc/G(Dr!-]}c֮OQ=rgXm.uǶ#"J+\BD|%7HF(>)()Xf)ZnK6u7eW'j-0?µSOU[[mpZC. U- C;m"O *\2~aLۗ[`, 79_Zf2@L~)tdJRdm-gY=Ы6/xo#eN6#t?sUض:azJ HHfn\p(C`q~_`iem $h_Faނf"c'A[%d^Kpᭃ~BKAz *tuiI:H:K" e [T6X|xG!ϷNKͣG LcY1ns8!CT +9bc㖟SuIsg/d1>nDd vH=21ǘbo$aYhGz.oѮ<@,e@(#)nj,,AΙbN%8$[3Ud4&a&+ߡK 8fvB2$g,EV&bŝ[ނ1+ x,ݬʼnJQ`6y/d* J&@dkLdW}!5m<:F/—U6 My("k0nJD\P},1PznV)S#3rw0-DB܆^)NBjY+̘^bӞژÕ%-*pk-n'm7}?;{$1ϼPƟOCLdLxsddNl\ E$tH; Gt{ߨӇ,zݪRu|vd92)ua/t\yŎzb3eN4Xe,13D6caH)93lt}\;[)Xp)PZ~}p_3PRU'Ce4 l )׸K:KS/#Νnzf!%7ȈԀ+|8H_}K~>L[o ;!,fY&*wۺ%E'|0(eTGE"*BOGnPJ;EW~L?|gx:d5U_ r++9H|sfnQtdKƳv[-zrd@;jF*C0'>dA/3?# S!bhÝ 7L_tn!30%.K ha=갉}:Yܦ&AX;?C9 xww{Y[%Zsuܱ;!*Y7+%fq'"횏pG( krGA$$:Һun/& |MI;5:Eu`lH^vmlP(~j < ^I;|k|PR37YmaRX7m({ ú̈~`i#4/^^WmHҞ>KoWod2~Re4U C۸?9&0Wrq!t>RƹfbcYRXo,i6H"^7Ux q:_odYuJYSb(,To./W ˕k bҊYVLqEIǴ7cY$<d.;hYH@B@~Oh R(bڨf-hZ@ lJ)8RWπ֍?i .-%~h!U>Yz(n`< #LybhTyH%BnSԈG ߊ0kbټ|;#Loy@7]'VP_77^?e zl qݛ|A^NonЭ.[!9N;nxfj} <1y9+\scmIQ 3h#aS% ҼHW7ZuXθdw#L8̭yKqN|Wuf463o~^-;Q'l4ց_Su߫g)|G"˽|"U-e ],5Zb?/G7VsiMo/sw+WѤ끐];ο/f2B E}0-?ʈb$Y5t)ˊz{`E]4ꮡ%QOxVrb %@;wiVFRZquy}t %AY3cM2- lU=Csƒf0qm-(EM6.\a37 n w y3My&>XE>醢"]MĝCUh 1_atctgL ʀ&,N ʦ;D /$D)UNUWdc6ޟOw5I>ٶDKInuy>O 3`buy2jw?/>j)ܭgQ!JUAtWFmq\Rr1>ؔrH<ŜkS,H絬UgùR"GPqX􉐗t`$g[A!-ޫuFs)?̹ͦD>z[VT.ErrmJ1^Sy0z^(C;yi;{geE&[+l'nv"C>:d luBN@!COdi4,x;6^bOj4`K:&U;@"cE;eBO]yc%fz;UgG%Xf P̑'6;=* U/? E r!p˥C['~IbtE[,:-#OAqĂi6ض5[FwW69/{v ?4䡅ԔzBbҹ&Pz|USS9?SV:7iA@.UD q۟HZ=nV~Aӯ\.&8ldLyh69p! Q8W lxHy)Mk'-xr֩E\JO0n[z#6K;>C ւUcRA=6Pj#2;:抌%--N!$R7e "!8'e_˓9[= ήYITkΦ4 HclWfhdnNU eN\.q9xr%rA̩Nf޽ JCѶ {7H\Hq :%7AK?y֗XXb5OD"ˮs(nǏKW.z;#BPߐkck{2>[UK e7ěL>j"QhGH h8 K; Qka0~wt96yZ,'xzr(3ln}o&,:FI#[wH4#$]JKI uB(]ե5zn&B<$ŕeM9G5 1!xHL19"׃G^- c$֕M9AK Gj~HUT"y[6+'L?(F4u/N87 '#~/EKPe3G ?H#C;,MB2@6K֐ XOxx7b:Dpm9 H,P*lXi$}P*!>y ˤ7K$^ɾ=Ʈ|I ݛm۠/#u$~2fP.%EiLt I&1-aSv68ٿ@2%t_ S۶.0a n±y&uR@d`Y1X|$k2"K*_oN#PrTa WC5拖[m5pN#FM A_P=r"΍nBܛO-F}U JqZJ]g+ *^6;X<bR)oGD;Ndv}` * /I ЎY-I蔨/q>aj(hhdJ[g(3pWAv^2>*j֧ Z΄׬?rvseaW>O(խ]>kbZjei9iG7?c'f:-h~OeLv*_۱aoi!~ʉ%C"vq?d5cdb`nk R}?j\Z+D 1=eډD3P l(vWk/ml@ RJ>#/ֺXlz&7/r[|a E)2)ykpB+:~ ɿ *-]'J3Y V ]#3- i1{Mx}ٯG` `JCV&6{9YZ4?,?k&aA TB~zqO8x|k0>i9I8Jʄo @~(=U燜RT'L:f#!~8=Όy9 gIR~L'+ӄII|@3J?(Qv)QD'#=8 Wڑ.4X眬_eżr$B jөXHj#!W &5k)d0ʜO {4#},}̜`Zdx(@$zPbƌ#u<6/zDEH6׏B|i%B*#U!Ia ؉] !cb^,k+Д6g|{}1!}E֕NaX@_"Ms. ?pUc5k~=ٜſ-VHa0*A!,&7pSi3k;¦~LXwk Q Kfk2doLi`pp]nˆX"Ư/5C;%/-;J K1cq M\qW$PVeaԃᡕ&2ҒEtc_pT N"(Q99 (gtE K4kJ̇^D~^S+HJ/l#"3kh(K#ip͆rI[+!jbV/]|fxj \giQ2;ˣg C{ K:F΀x!0mS\xiVL"2kY3>N0u(k~~3VaOv:l)p+=R|'4ׅqocYٞ_T)S($F !%˪388sV_Q0^QR!ACyk4|=(z5bgIEe}:cdD-iGE*Ҽ#${i&R"g*hMXZ*~v#9{+9m:_*C0'iWoKL LblCg^]x 8clivx+'2܍ zKE"t"IA@WI_+ Y#c*Ns ߗ3r,iː|gkUc LE2[~Ok\>*A&fm[ Z:th >7P}-d'1ِNEU.ZO%sId䝦Ч3)0GQa0V<0 Bl^/[D Brp&Wj!f/H-vZk]}Bﯳ.AmذY\to=nY% B,?$TW.C6+A//]#EpP~åw+>0\`Hyx$⨷&^O /,J?h;vLJGR')"(fŬ֋p͜=mcZ%A e$% K!G})ĕޝ v :cÝG2*uHNrrlT1̛L"+r;L P鑒LL{rr,+@3Z ecd"x"ɡOyy.Vu!O?&w6H|W!U`-Y֍#K*kEW M%DYdWa'OzRV㪏'. Wk@2!֏]jw'?~v$˴1l$Ž䅨Ur6o{{dto]4Yx1S.~K  Tn{5[Lu/Jwvktنp}[qX)ʽ2 h0 ℨ*xX lcÁ w|}E Xo#H.X(8;gqNL`SWpk1JfMqZOt=Hwnsb^O[3x K>_[$Ştэ2GʵkjhA {WFjo0wv:IrW8f۷cHs6o]pG+=z-\ݦݗ!̹P6z~U}b\F]5ZƢIÐuZޤ8~Zf6APe=v%Zb|Ő=u8Y%EߍsiGQ=|/;hK~kFksFڝh K35#SB>+{:\υCAHWy.S"2#=8$h|ẎR֪-V(∓V !2.f22Cs^dT@&9Nce_O)P+1f;oB2Ô}bRC?r/ƣQJ:'ы ᤪlKބ>ߏWڄn­W8Ği(]x nѼ8G[|mrL,WV"<ݕ +͠[ry~by4XRgc$xG*,j͡ǚGn39ʊVh&.P~ht&.<;͐jLؔ1=3TI_lf#Sf:j~O!Uz_Gp#(88`p CqޔL+CG3ϣ)2^hhg*q(DbpwSiԖv$_x[kh{*5@d:1J'Y'qx[#c`ʽ\FP߷XG{?{!8jE4/>i.>|{lMz jOH6mE-4姼vgdX Q4>_+tiA]8{WauwBˏ1thEqEϔ򀃐L??](p6Qvk[~ayk|:Dgs,L|. Bp +xx䡅n,*e,o"5C کYOȧ'iWݗl~!WɱAÖ1n"![*h͛ K%D%"C}|fNdmOg%:|/ J_*_v4Q0[;xxd.۵M&ں2XeiEi$Bـ~] FQE-2,\SMKm 16?HHwc^xX1rQRP(+!(S?/5; µ"cF)!Iz]\cU%<`iNo9S=q(Wd!AovWcn9+3oFcARƱ yʒ.O$5^jb}̶燑,IVNJg!9%EˌeϚSB&ځc&${xmT,1s`י}>q^]m[y' 49s3@ Y+[bFt%ECPjFA5&4a!Q쏯|YL\唾p4c#l Clp4JS @ę"5SV /),T=p:I`ɌB,̗|!)^`Æ^Otq6u :xҝrQܚV isNqB%{V;-%kq%Mx:vHOQaMܽճ[[$=Nu %ZԚ>?+\>FC79aWaVMJ, A >,bU/`Lv.#~&Owqk`ǂeTbBox^C ¢pa#UQl-$GsGu\j M{BۋPTQ+(2R(>P-Q.gi'/A/>irlF#O2P"*̰;,zx E+J?woSeU2^ 6~5/WŻ_@ ;O''y7cMCʇ"ץw={jV#+%)%QxyƬy+av3z 6Em7eP?5!fNZ=ڎeԑ`,}:jnIq޵&Kwe"SsL'i!)12+<, gb 1$ɬ2~4}8O a Ts1!K.,Pu Do`0F7uX8or,QkB$~;"&Jf6@ku,^ϛ:85DU!YtK—=;ᜃњn# }L r<_.cOZ@uPyW1dݧܯ#--^4wcue>v4yTIB\Jf5r(e#7=L305;!H)]h7yjca`#eKsN&%W w7b{*#%yW׽9孴N !,F]?A Ur;kG2d ^-,̃43%=.`~ȥg[9V!vB dEIC|yrP-yv^縺|)t@vzN!Dz;-+@6zWSe!o3٫O5~4h"3&>}1ezW`d8cڄkT.ᮙA%R+UM˖+*k07rNWS#7[#7ہoba)mUqYӳ 't|f@Z|aqEɮhw6-/?ڍF1UWf ~Ԝ؄[tp8W ~p}Vŭ w_^ z?w MڮQ`tӽ Fݿ\ii/ѿp*suΝWWES^H+_`+РlV$^%|(1Ө+4EnDb,ͪa$$*$t?]EY"ק?h|@+ѭIȭ?RaBL-:[YWG4j:S5^]2\}l{ {P3KUyb!ݧш?m׉Rki1,(EraCstAھ8jwLM]MWg^h:X2ݳdMtxYg,oDqg~m7pr8z5!oZQt0#Mى@Uvgj &_KCrJgmvƟoR*X|BB*:`5|_>ǀ(>Sڡl/AbΒYd#gc9E ]Ϥw?  <^1v#&%ڊ)@j~>{| ɂ Ɲz#m7]blb=yV'61ȣc gÆgύ٧]Lwœw94ޗ/F;sGBA'S+Bb ^1;׏}99 t_9ͱGKdFǼڷ?DiMpu0I|V4ʓfM){Vq1M6օ6T_?K O? rT*Okh 6Px%X V6zkKK}o6du>-%MƶU.} |448@961%7p 4cy4 6B$54X 4iDn";cZ9j׭%UzMkGq]Sgz1r/u&.kC&[U;3Q@Qva#%7DwOlbES㭽mΛj+\~iWzSyjy:u^=`hɤ[ҩ 0\ON$&g=9:2t@/Q\Z @\п$I.e!^th ŧ]i7<&g^ɸOI1b-÷L@~qf +D9+ %r$!V8/&oak)x2b &d/iistAܧ?AןƥT㄄D LĹP#YrA"UIO`uAWG n*h@/XP $-'LE9UiPKC&<[\PK(K>'Configurations2/accelerator/current.xmlPK)K>Configurations2/progressbar/PK)K>Configurations2/floater/PK)K>Configurations2/popupmenu/PK)K>Configurations2/menubar/PK)K>Configurations2/toolbar/PK)K>Configurations2/images/Bitmaps/PK)K>Configurations2/statusbar/PK)K> settings.xmlZs8 ~M@wi iiB˛ITH!п.?v93ׇfHɖO\dB] _ 0]垺Ӌ܏_5s/S Jy3Y_bʜ@ AWkϣ˩Δ*7V**I|Iξp1JR>}qvUsT_&*+Z|;w2's˯\d@Ams2vCIjMNHG5 l/JŋE7Umm&jQYp;F.[wlJYiHӀ0X,}Cv3y_T"\+^X("w`Hђ5ߩdwk`ZɮrxQo=cyEQ.7BJ 2r8Cǒ^hn^<~M=gxZ`=f7bAf`mJyF0S?!z|OI&xF1{x}ElM]hHסMFW*RV׻A0[c8SqASEX]w ӷW5.VSo s|6ў|`#pj.(Ƙ%YҳΟU?^fִf'BJ_=zOQ٭'M1x Cݴm;NX ۚұH5x[{3k^+~ K8KqWx̝儭wNX{ Cixÿ-!{,5$i\۲iGot.\-1ҷAJZ}toZ!s)ܸcV#7 #53&5 L (=aHޟAg 0U}A0|ks3i|妩pvqw)aG=I(SLm"H 靈xڲGWPvx j a阪VAYү o- N|afm9qjP~* YlMye3J {M9z15JlƸJ5l+{BJ.OLkdZvh"n)`)¸^W?cיXԸIXLhUy?S!koRS]6mtϹipg=c3#КI31_vD;-- zܷur\{Ⱦ):f2xXP\FX+euլxTWKWqa>66 F x6]F:pӬF-S8IЩ[K֘o[n#}$xVc`!؞XLhr' R`вp#~TJ/a=`KZ't/-R:{ p۝Ua0Q{Kao?'2[ʇdItX{=nD3-l33}CEF)%ҍuEu?{~m ?ld'5=[;zƟ hj ,[wDM80O]zǍE̵ Izvtk !h-|kc1ւCuGqi#JK[/9K+:> aDGQl~*PKʄTP&PK)K>META-INF/manifest.xmln0 } Cbhg)=>#ӎY$~r4nnP?ERwϝJyitYR{|Oojف z*$mXtiK_jЗ$JcQF5AiuTFC'G1%[X~']g{l,>;M7A*?A4z}vכu-3JKus}^/z_Ϥ INS4r08; z( vn|\.OB?!:vH0z VTӡ{/at#I!PaEpnZ-5u'oa&F {cz?g fԁj'z8Yk(>ޮ%ŴPKDwfl PK)K>^2 ''mimetypePK)K>dmEE-MPictures/100000000000000800000008DD0ADA29.pngPK)K>J3__-Pictures/10000201000000F20000005D37AA1023.pngPK)K>$XZ;5 `content.xmlPK)K>#LĂ layout-cachePK(K>=<1 ҝmanifest.rdfPK)K>E2عz styles.xmlPK)K>'mmmeta.xmlPK)K>C&<[\sThumbnails/thumbnail.pngPK(K>'Configurations2/accelerator/current.xmlPK)K><Configurations2/progressbar/PK)K>vConfigurations2/floater/PK)K>Configurations2/popupmenu/PK)K>Configurations2/menubar/PK)K>Configurations2/toolbar/PK)K>PConfigurations2/images/Bitmaps/PK)K>Configurations2/statusbar/PK)K>ʄTP& settings.xmlPK)K>Dwfl META-INF/manifest.xmlPKfluidsynth-2.1.1/doc/README000066400000000000000000000007011362231004000153160ustar00rootroot00000000000000To build FluidSynth API reference documentation, make sure you have Doxygen installed. If you are using the cmake build system, change to the build directory and execute the following command in this doc/ directory: $ make doxygen The latest generated API HTML docs can also be found at: http://www.fluidsynth.org/api/ Even more documentation references are provided on our wiki page: https://github.com/FluidSynth/fluidsynth/wiki/Documentation fluidsynth-2.1.1/doc/android/000077500000000000000000000000001362231004000160605ustar00rootroot00000000000000fluidsynth-2.1.1/doc/android/.gitignore000066400000000000000000000000121362231004000200410ustar00rootroot00000000000000external fluidsynth-2.1.1/doc/android/Makefile.android000066400000000000000000000111061362231004000211360ustar00rootroot00000000000000# # The public targets in this Makefile are: build, clean, wipe # # What `build` target does: # # - build cerbero to build glib, libogg, libvorbis, libflac, and libsndfile. # - build glib-2.0.so and many other dependency shared libraries # - build Oboe shared library # - build libfluidsynth.so # - build libfluidsynth-assetloader.so # # Android app developers are supposed to copy all those shared # libraries into their apks (per ABI). # PWD=$(shell pwd) CERBERO=$(PWD)/external/cerbero OBOE=$(PWD)/external/oboe CMAKE=cmake ANDROID_NDK = $(PWD)/external/cerbero/build/android-ndk-18 ABIS_SIMPLE = x86 x86-64 armv7 arm64 DIST_PATH=$(CERBERO)/build/dist OBOE_BUILD_PATH=$(OBOE)/build all: build .PHONY: prepare prepare: checkout-oboe checkout-cerbero for abi in $(ABIS_SIMPLE) ; do \ cd $(CERBERO) && ./cerbero-uninstalled -c config/cross-android-$$abi.cbc bootstrap && cd $(PWD) ; \ done .PHONY: checkout-oboe checkout-oboe: $(OBOE) cd $(OBOE) && git checkout 9bf3943 $(OBOE): git clone https://github.com/Google/oboe.git $(OBOE) .PHONY: checkout-cerbero checkout-cerbero: $(CERBERO) cd $(CERBERO) && git checkout 0acd9b0 $(CERBERO): git clone https://github.com/atsushieno/cerbero.git $(CERBERO) .PHONY: build build: build-oboe dist-oboe build-deps-cerbero dist-deps-cerbero build-fluidsynth dist-fluidsynth build-fluidsynth-assetloader dist-fluidsynth-assetloader .PHONY: build-deps-cerbero build-deps-cerbero: for abi in $(ABIS_SIMPLE) ; do \ cd $(CERBERO) && ./cerbero-uninstalled -c config/cross-android-$$abi.cbc build glib libsndfile && cd $(PWD) ; \ done define run_make_abi_target make -f Makefile.android BUILD_ABI=$(1) A_ABI=$(2) $(3) endef define run_make_abi_target-unsafe if make -f Makefile.android BUILD_ABI=$(1) A_ABI=$(2) $(3) ; then \ echo "ignore failure for $(1)..." ; \ fi endef define run_make_for_all_abi $(call run_make_abi_target,x86,x86,$(1) ) $(call run_make_abi_target,x86_64,x86_64,$(1) ) $(call run_make_abi_target,armv7,armeabi-v7a,$(1) ) $(call run_make_abi_target-unsafe,arm64,arm64-v8a,$(1) ) endef .PHONY: dist-deps-cerbero dist-deps-cerbero: $(call run_make_for_all_abi, dist-deps-cerbero-one) .PHONY: dist-fluidsynth dist-fluidsynth: $(call run_make_for_all_abi, dist-fluidsynth-one) .PHONY: build-oboe build-oboe: $(call run_make_for_all_abi, build-oboe-one) .PHONY: dist-oboe dist-oboe: $(call run_make_for_all_abi, dist-oboe-one) .PHONY: build-fluidsynth build-fluidsynth: $(call run_make_for_all_abi, build-fluidsynth-one) build-fluidsynth-one: mkdir -p build/$(A_ABI) && cd build/$(A_ABI) && \ LD_RUN_PATH=$(DIST_PATH)/android-$(BUILD_ABI)/lib:$(OBOE_BUILD_PATH)/$(A_ABI) \ LD_LIBRARY_PATH=$(DIST_PATH)/android_$(BUILD_ABI)/lib \ PKG_CONFIG_PATH=$(DIST_PATH)/android_$(BUILD_ABI)/lib/pkgconfig/:$(OBOE_BUILD_PATH)/$(A_ABI) \ PKG_CONFIG_LIBDIR=$(DIST_PATH)/android_$(BUILD_ABI)/lib/pkgconfig/:$(OBOE_BUILD_PATH)/$(A_ABI) \ $(CMAKE) -DCMAKE_INSTALL_PREFIX=$(PWD)/dist/$(A_ABI) \ -Denable-floats=1 \ -DCMAKE_VERBOSE_MAKEFILE=1 \ -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake \ -Denable-opensles=on -Denable-oboe=on -Denable-oss=off -Denable-libsndfile=on \ -DANDROID_NATIVE_API_LEVEL=android-27 -DANDROID_PLATFORM=android-27 -DANDROID_ABI=$(A_ABI) ../../../.. && \ make build-oboe-one: mkdir -p $(OBOE)/build/$(A_ABI) && cd $(OBOE)/build/$(A_ABI) && \ $(CMAKE) -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK)/build/cmake/android.toolchain.cmake -DANDROID_ABI=$(A_ABI) -DANDROID_NATIVE_API_LEVEL=android-27 -DANDROID_PLATFORM=android-27 -DBUILD_SHARED_LIBS=on ../.. && make cp oboe-1.0.pc $(OBOE)/build/$(A_ABI) dist-oboe-one: mkdir -p dist/$(A_ABI) && cp $(OBOE)/build/$(A_ABI)/*.so dist/$(A_ABI)/ dist-deps-cerbero-one: mkdir -p dist/$(A_ABI) && cd dist/$(A_ABI) && cp ../../external/cerbero/build/dist/android_$(BUILD_ABI)/lib/*.so . && cd ../.. dist-fluidsynth-one: mkdir -p dist/$(A_ABI) && cd dist/$(A_ABI) && cp ../../build/$(A_ABI)/src/libfluidsynth.so . && cd ../.. cp -r ../../include/fluidsynth build/$(A_ABI)/include/ build-fluidsynth-assetloader: cd fluidsynth-assetloader && ./ext-build.sh dist-fluidsynth-assetloader: cp fluidsynth-assetloader/build/x86/*.so dist/x86/ cp fluidsynth-assetloader/build/x86_64/*.so dist/x86_64/ cp fluidsynth-assetloader/build/armeabi-v7a/*.so dist/armeabi-v7a/ cp fluidsynth-assetloader/build/arm64-v8a/*.so dist/arm64-v8a/ clean: rm -rf dist/* build/* external/oboe/build/* obj/local/* fluidsynth-asset-loader/build/* .PHONY: wipe wipe: $(CERBERO) for abi in $(ABIS_SIMPLE) ; do \ cd $(CERBERO) && ./cerbero-uninstalled -c config/cross-android-$$abi.cbc wipe && cd ../.. ; \ done fluidsynth-2.1.1/doc/android/README.Android.md000066400000000000000000000051601362231004000207200ustar00rootroot00000000000000# Android support in Fluidsynth Fluidsynth supports Android audio outputs by Oboe and OpenSLES audio drivers. Android also has Android MIDI API which is exposed only in Android Java API, but it is not exposed as a native API, therefore there is no `mdriver` support for Android. There is an example MidiDeviceService implementation for Fluidsynth at: https://github.com/atsushieno/fluidsynth-midi-service-j ## Usage `libfluidsynth.so` and `libfluidsynth-assetloader.so` are the library that should be packaged into apk. The latter is for asset-based "sfloader". By default, "oboe" is the default driver for Android. You can also explicitly specify "opensles" instead, with "audio.driver" setting: ``` fluid_settings_setstr (settings_handle, "audio.driver", "opensles"); ``` ## Custom SoundFont loader Since Android file access is quite limited and there is no common place to store soundfonts unlike Linux desktop (e.g. `/usr/share/sounds/sf2`), you will most likely have to provide custom soundfont loader. Since version 2.0.0 Fluidsynth comes with `fluid_sfloader_set_callbacks()` which brings [customizible file/stream reader](https://github.com/FluidSynth/fluidsynth/issues/241) (open/read/seek/tell/close). It is useful to implement simplified custom SF loader e.g. with Android assets or OBB streams. The Android implementation is in separate library called `libfluidsynth-assetloader.so`. It comes with native Asset sfloader. However, its usage is a bit tricky because AssetManager needs to be passed from Java code (even though we use AAssetManager API). Use `Java_fluidsynth_androidextensions_NativeHandler_setAssetManagerContext()` to initialize the this loader, then call `new_fluid_android_asset_sfloader()` to create a new sfloader. If you already have AAssetManager instance, then the first JNI function is ignorable and you only have to specify the manager to the second function. There is [an example source code](https://github.com/atsushieno/fluidsynth-midi-service-j/blob/a2a56b/fluidsynthjna/src/main/java/fluidsynth/androidextensions/AndroidNativeAssetSoundFontLoader.kt#L17) on how to do it. ## Building By default, you are supposed to provide `PKG_CONFIG_PATH` to glib etc. as well as oboe. There is nothing special. However, in reality, Oboe does not come up with an official package specification, so you will have to create it manually... unless you use `oboe-1.0.pc` in this directory as well as the build system set up here. There are "non-normative" build scripts i.e. `Makefile.android` and a couple of helper files in this directory. In case you don't have any dependencies such as glib for Android, then it would be helpful. fluidsynth-2.1.1/doc/android/fluidsynth-assetloader/000077500000000000000000000000001362231004000225555ustar00rootroot00000000000000fluidsynth-2.1.1/doc/android/fluidsynth-assetloader/CMakeLists.txt000066400000000000000000000012421362231004000253140ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6.0) project ( fluidsynth-assetloader C ) set ( fluidsynth-assetloader_sources fluid_androidasset.c ) add_library ( fluidsynth-assetloader SHARED ${fluidsynth-assetloader_sources} ) target_compile_options ( fluidsynth-assetloader PRIVATE -v PRIVATE -Wall PRIVATE "$<$:-Werror>") # Only include -Werror when building debug config include_directories ( ../../../include ) set ( CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L../../../dist/${ANDROID_ABI} -lfluidsynth" ) target_link_libraries ( fluidsynth-assetloader PRIVATE log android ) fluidsynth-2.1.1/doc/android/fluidsynth-assetloader/ext-build.sh000077500000000000000000000006601362231004000250130ustar00rootroot00000000000000PWD=`pwd` ABIS="x86 x86_64 armeabi-v7a arm64-v8a" HOST_OS=`uname | tr [:upper:] [:lower:]` ANDROID_NDK_PATH=~/android-sdk-$HOST_OS/ndk-bundle CMAKEFILE=$ANDROID_NDK_PATH/build/cmake/android.toolchain.cmake for A_ABI in $ABIS ; do mkdir -p build/$A_ABI && \ cd build/$A_ABI && \ cmake -DCMAKE_TOOLCHAIN_FILE=$CMAKEFILE -DANDROID_PLATFORM=android-27 -DANDROID_ABI=$A_ABI ../.. && \ make && cd ../.. ; done fluidsynth-2.1.1/doc/android/fluidsynth-assetloader/fluid_androidasset.c000066400000000000000000000061451362231004000265720ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #if defined(ANDROID) || defined(__DOXYGEN__) #define FLUIDSYNTH_API #include #include #include "fluid_androidasset.h" #include #include AAssetManager *fluid_android_asset_manager; fluid_sfloader_t* new_fluid_android_asset_sfloader(fluid_settings_t *settings, void *assetManager) { fluid_sfloader_t *loader; if (settings == NULL) return NULL; if (!fluid_android_asset_manager) fluid_android_asset_manager = (AAssetManager*) assetManager; if (fluid_android_asset_manager == NULL) return NULL; loader = new_fluid_defsfloader(settings); if (loader == NULL) return NULL; fluid_sfloader_set_callbacks(loader, asset_open, asset_read, asset_seek, asset_tell, asset_close); return loader; } /* This is a compromised solution for JNAerator for that 1) it cannot handle jobject with JNIEnv as parameters, and that 2) the returned pointer can be converted in the same manner that JNAerated methods. (Most likely my JNA usage issue but no one has answer for it.) */ void Java_fluidsynth_androidextensions_NativeHandler_setAssetManagerContext(JNIEnv *env, jobject _this, jobject assetManager) { if (assetManager == NULL) return; fluid_android_asset_manager = AAssetManager_fromJava (env, assetManager); } void *asset_open(const char *path) { if (fluid_android_asset_manager == NULL) return NULL; return AAssetManager_open (fluid_android_asset_manager, path, AASSET_MODE_RANDOM); } int asset_close(void *handle) { AAsset *asset; asset = (AAsset*) handle; AAsset_close (asset); return 0; } long asset_tell(void *handle) { AAsset *asset; asset = (AAsset*) handle; return AAsset_getLength(asset) - AAsset_getRemainingLength(asset); } int asset_seek(void *handle, long offset, int origin) { AAsset *asset; asset = (AAsset*) handle; return AAsset_seek (asset, (off_t) offset, origin); } int asset_read(void *buf, int count, void *handle) { AAsset *asset; asset = (AAsset*) handle; return AAsset_read (asset, buf, (size_t) count); } #endif /* if defined(ANDROID) || defined(__DOXYGEN__) */ fluidsynth-2.1.1/doc/android/fluidsynth-assetloader/fluid_androidasset.h000066400000000000000000000027771362231004000266060ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _PRIV_FLUID_ANDROIDASSET_H #define _PRIV_FLUID_ANDROIDASSET_H #ifdef __cplusplus extern "C" { #endif #include #include fluid_sfloader_t* new_fluid_android_asset_sfloader(fluid_settings_t *settings, void *assetManager); void Java_fluidsynth_androidextensions_NativeHandler_setAssetManagerContext(JNIEnv *env, jobject _this, jobject assetManager); void *asset_open(const char *path); int asset_close(void *handle); long asset_tell(void *handle); int asset_seek(void *handle, long offset, int origin); int asset_read(void *buf, int count, void *handle); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* _PRIV_FLUID_ANDROIDASSET_H */ fluidsynth-2.1.1/doc/android/jni/000077500000000000000000000000001362231004000166405ustar00rootroot00000000000000fluidsynth-2.1.1/doc/android/jni/Android.mk000066400000000000000000000020071362231004000205500ustar00rootroot00000000000000LOCAL_PATH := $(call my-dir) TARGET_PLATFORM := android-27 GLIB_LIB = ../dep/$(APP_ABI)/ include $(CLEAR_VARS) LOCAL_MODULE := glib-2.0 LOCAL_SRC_FILES := $(GLIB_LIB)/libglib-2.0.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := iconv LOCAL_SRC_FILES := $(GLIB_LIB)/libiconv.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := intl LOCAL_SRC_FILES := $(GLIB_LIB)/libintl.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := oboe LOCAL_SRC_FILES := $(GLIB_LIB)/liboboe.a include $(PREBUILT_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := fluidsynth_static LOCAL_SRC_FILES := ../dep/$(APP_ABI)/libfluidsynth.a include $(PREBUILT_STATIC_LIBRARY) LOCAL_MODULE := fluidsynth ifeq ($(NDK_DEBUG),1) cmd-strip := endif LOCAL_STATIC_LIBRARIES := glib-2.0 iconv intl oboe LOCAL_WHOLE_STATIC_LIBRARIES := fluidsynth_static LOCAL_LDLIBS := -lc -lOpenSLES -ldl -llog -landroid -L$(LOCAL_PATH)/../dist/$(APP_ABI) -loboe-c include $(BUILD_SHARED_LIBRARY) fluidsynth-2.1.1/doc/android/jni/Application.mk000066400000000000000000000001071362231004000214320ustar00rootroot00000000000000APP_PLATFORM := android-21 APP_ABI := x86 x86_64 armeabi-v7a arm64-v8a fluidsynth-2.1.1/doc/android/oboe-1.0.pc000066400000000000000000000003361362231004000176260ustar00rootroot00000000000000prefix=${pcfiledir} exec_prefix=${prefix} libdir=${prefix} includedir=${prefix}/../../include/ Name: Oboe Description: Oboe library Version: 1.0.0 Libs: -L${libdir} -loboe -landroid -llog -lstdc++ Cflags: -I${includedir} fluidsynth-2.1.1/doc/doxy_formula.css000066400000000000000000000003131362231004000176570ustar00rootroot00000000000000 code { background-color: #eeeeee; text-shadow: none; color: black; margin-left: 4px; margin-right: 4px; padding-left: 4px; padding-right: 4px; border-radius: 3px; white-space: nowrap; } fluidsynth-2.1.1/doc/example.c000066400000000000000000000033351362231004000162430ustar00rootroot00000000000000/* An example of how to use FluidSynth. To compile it on Linux: $ gcc -o example example.c `pkg-config fluidsynth --libs` To compile it on Windows: ... Author: Peter Hanappe. This code is in the public domain. Use it as you like. */ #include #if defined(WIN32) #include #define sleep(_t) Sleep(_t * 1000) #else #include #include #endif int main(int argc, char **argv) { fluid_settings_t *settings; fluid_synth_t *synth; fluid_audio_driver_t *adriver; int sfont_id; int i, key; /* Create the settings. */ settings = new_fluid_settings(); /* Change the settings if necessary*/ /* Create the synthesizer. */ synth = new_fluid_synth(settings); /* Create the audio driver. The synthesizer starts playing as soon as the driver is created. */ adriver = new_fluid_audio_driver(settings, synth); /* Load a SoundFont and reset presets (so that new instruments * get used from the SoundFont) */ sfont_id = fluid_synth_sfload(synth, "example.sf2", 1); if(sfont_id == FLUID_FAILED) { puts("Loading the SoundFont failed!"); goto err; } /* Initialize the random number generator */ srand(getpid()); for(i = 0; i < 12; i++) { /* Generate a random key */ key = 60 + (int)(12.0f * rand() / (float) RAND_MAX); /* Play a note */ fluid_synth_noteon(synth, 0, key, 80); /* Sleep for 1 second */ sleep(1); /* Stop the note */ fluid_synth_noteoff(synth, 0, key); } err: /* Clean up */ delete_fluid_audio_driver(adriver); delete_fluid_synth(synth); delete_fluid_settings(settings); return 0; } fluidsynth-2.1.1/doc/fluidsettings.xml000066400000000000000000000746341362231004000200640ustar00rootroot00000000000000 Synthesizer settings audio-channels int 1 1 128 By default, the synthesizer outputs a single stereo signal. Using this option, the synthesizer can output multi-channel audio. Sets the number of stereo channel pairs. So 1 is actually 2 channels (a stereo pair). audio-groups int 1 1 128 The output audio channel associated with a MIDI channel is wrapped around using the number of synth.audio-groups as modulo divider. This is typically the number of output channels on the sound card, as long as the LADSPA Fx unit is not used. In case of LADSPA unit, think of it as subgroups on a mixer. chorus.active bool 1 (TRUE) When set to 1 (TRUE) the chorus effects module is activated. Otherwise, no chorus will be added to the output signal. Note that the amount of signal sent to the chorus module depends on the "chorus send" generator defined in the SoundFont. chorus.depth num 8 0 256 Specifies the modulation depth of the chorus. chorus.level num 2 0 10 Specifies the output amplitude of the chorus signal. chorus.nr int 3 0 99 Sets the voice count of the chorus. chorus.speed num 0.3 0.1 5 Sets the modulation speed in Hz. cpu-cores int 1 1 256 Sets the number of synthesis CPU cores. If set to a value greater than 1, then additional synthesis threads will be created to take advantage of a multi CPU or CPU core system. This has the affect of utilizing more of the total CPU for voices or decreasing render times when synthesizing audio to a file. default-soundfont str C:\soundfonts\default.sf2 (Windows),
${CMAKE_INSTALL_PREFIX}/share/soundfonts/default.sf2 (all others)
The default soundfont file to use by the fluidsynth executable. The default value can be overridden during compilation time by setting the DEFAULT_SOUNDFONT cmake variable.
device-id int 0 0 126 Device identifier used for SYSEX commands, such as MIDI Tuning Standard commands. Only those SYSEX commands destined for this ID or to all devices will be acted upon. dynamic-sample-loading bool 0 (FALSE) When set to 1 (TRUE), samples are loaded to and unloaded from memory on demand. effects-channels int 2 2 2 Specifies the number of effects per group. Currently there only are two effects (i.e. reverb and chorus). effects-groups int 1 1 128 Specifies the number of effect units. By default, the sound of all voices is rendered by one reverb unit and one chorus unit respectively (even for multi-channel rendering). This setting gives the user control which effects of a voice to render to which independent audio channels. E.g. setting synth.effects-groups == synth.midi-channels allows to render the effects of each MIDI channel to separate audio buffers. If synth.effects-groups is smaller, it will wrap around. Note that any value >1 will significantly increase CPU usage. gain num 0.2 0.0 10.0 The gain is applied to the final or master output of the synthesizer. It is set to a low value by default to avoid the saturation of the output when many notes are played. ladspa.active bool 0 (FALSE) When set to "yes" the LADSPA subsystem will be enabled. This subsystem allows to load and interconnect LADSPA plug-ins. The output of the synthesizer is processed by the LADSPA subsystem. Note that the synthesizer has to be compiled with LADSPA support. More information about the LADSPA subsystem later. lock-memory bool 1 (TRUE) Page-lock memory that contains audio sample data, if true. midi-channels int 16 16 256 This setting defines the number of MIDI channels of the synthesizer. The MIDI standard defines 16 channels, so MIDI hardware is limited to this number. Internally FluidSynth can use more channels which can be mapped to different MIDI sources. midi-bank-select str gs gm, gs, xg, mma This setting defines how the synthesizer interprets Bank Select messages.

min-note-length int 10 0 65535 Sets the minimum note duration in milliseconds. This ensures that really short duration note events, such as percussion notes, have a better chance of sounding as intended. Set to 0 to disable this feature. overflow.age num 1000 -10000 10000 This score is divided by the number of seconds this voice has been active and is added to the overflow priority. It is usually a positive value and gives voices which have just been started a higher priority, making them less likely to be killed in an overflow situation. overflow.important num 5000 -50000 50000 This score is added to voices on channels marked with the synth.overflow.important-channels setting. overflow.important-channels str "" This setting is a comma-separated list of MIDI channel numbers that should be treated as "important" by the overflow calculation, adding the score set by synth.overflow.important to each voice on those channels. It can be used to make voices on particular MIDI channels less likely (synth.overflow.important > 0) or more likely (synth.overflow.important < 0) to be killed in an overflow situation. Channel numbers are 1-based, so the first MIDI channel is number 1. overflow.percussion num 4000 -10000 10000 Sets the overflow priority score added to voices on a percussion channel. This is usually a positive score, to give percussion voices a higher priority and less chance of being killed in an overflow situation. overflow.released num -2000 -10000 10000 Sets the overflow priority score added to voices that have already received a note-off event. This is usually a negative score, to give released voices a lower priority so that they are killed first in an overflow situation. overflow.sustained num -1000 -10000 10000 Sets the overflow priority score added to voices that are currently sustained. With the default value, sustained voices are considered less important and are more likely to be killed in an overflow situation. overflow.volume num 500 -10000 10000 Sets the overflow priority score added to voices based on their current volume. The voice volume is normalized to a value between 0 and 1 and multiplied with this setting. So voices with maximum volume get added the full score, voices with only half that volume get added half of this score. polyphony int 256 1 65535 The polyphony defines how many voices can be played in parallel. A note event produces one or more voices. Its good to set this to a value which the system can handle and will thus limit FluidSynth's CPU usage. When FluidSynth runs out of voices it will begin terminating lower priority voices for new note events. reverb.active bool 1 (TRUE) When set to 1 (TRUE) the reverb effects module is activated. Otherwise, no reverb will be added to the output signal. Note that the amount of signal sent to the reverb module depends on the "reverb send" generator defined in the SoundFont. reverb.damp num 0 0 1 Sets the amount of reverb damping. reverb.level num 0.9 0 1 Sets the reverb output amplitude. reverb.room-size num 0.2 0 1 Sets the room size (i.e. amount of wet) reverb. reverb.width num 0.5 0 100 Sets the stereo spread of the reverb signal. sample-rate num 44100.0 8000.0 96000.0 The sample rate of the audio generated by the synthesizer. threadsafe-api bool 1 (TRUE) Controls whether the synth's public API is protected by a mutex or not. Default is on, turn it off for slightly better performance if you know you're only accessing the synth from one thread only, this could be the case in many embedded use cases for example. Note that libfluidsynth can use many threads by itself (shell is one, midi driver is one, midi player is one etc) so you should usually leave it on. verbose bool 0 (FALSE) When set to 1 (TRUE) the synthesizer will print out information about the received MIDI events to the stdout. This can be helpful for debugging. This setting cannot be changed after the synthesizer has started. MIDI driver settings autoconnect bool 0 (FALSE) If 1 (TRUE), automatically connects FluidSynth to available MIDI input ports. alsa_seq, coremidi and jack are currently the only drivers making use of this. driver str alsa_seq (Linux),
winmidi (Windows),
jack (Mac OS X)
alsa_raw, alsa_seq, coremidi, jack, midishare, oss, winmidi The MIDI system to be used.
realtime-prio int 50 0 99 Sets the realtime scheduling priority of the MIDI thread (0 disables high priority scheduling). Linux is the only platform which currently makes use of different priority levels. Drivers which use this option: alsa_raw, alsa_seq, oss portname str Used by coremidi and alsa_seq drivers for the portnames registered with the MIDI subsystem. alsa.device str default ALSA MIDI device to use for RAW ALSA MIDI driver. alsa_seq.device str default ALSA sequencer device to use for ALSA sequencer driver. alsa_seq.id str pid ID to use when registering ports with the ALSA sequencer driver. If set to "pid" then the ID will be "FLUID Synth (PID)", where PID is the FluidSynth process ID of the audio thread otherwise the provided string will be used in place of PID. coremidi.id str pid Client ID to use for CoreMIDI driver. 'pid' will use process ID as port of the client name. jack.server str Jack server to connect to for Jack MIDI driver. If an empty string then the default server will be used. jack.id str fluidsynth-midi Client ID to use with the Jack MIDI driver. If jack is also used as audio driver and "midi.jack.server" and "audio.jack.server" are equal, this setting will be overridden by "audio.jack.id", because a client cannot have multiple names. oss.device str /dev/midi Device to use for OSS MIDI driver. winmidi.device str default Device for Windows MIDI driver.
MIDI player settings reset-synth bool 1 (TRUE) If true, reset the synth before starting a new MIDI song, so the state of a previous song can't affect the new song. Turn it off for seamless looping of a song. timing-source str sample sample, system Determines the timing source of the player sequencer. 'sample' uses the sample clock (how much audio has been output) to sequence events, in which case audio is synchronized with MIDI events. 'system' uses the system clock, audio and MIDI are not synchronized exactly. Shell (command line) settings prompt str "" In dump mode we set the prompt to "". The ui cannot easily handle lines, which don't end with cr. Changing the prompt cannot be done through a command, because the current shell does not handle empty arguments. port num 9800 1 65535 The shell can be used in a client/server mode. This setting controls what TCP/IP port the server uses. fluidsynth-2.1.1/doc/fluidsettings.xsl000066400000000000000000000172571362231004000200700ustar00rootroot00000000000000 FluidSettings

FluidSettings

deprecated deprecated deprecated deprecated
. Type int (bool)
Default
Values Values Min - Max 1, 0 -
Description

DEPRECATED

fluidsynth-2.1.1/doc/fluidsynth-v20-devdoc.txt000066400000000000000000001157101362231004000212460ustar00rootroot00000000000000/*! \mainpage FluidSynth 2.1 Developer Documentation \author Peter Hanappe \author Conrad Berhörster \author Antoine Schmitt \author Pedro López-Cabanillas \author Josh Green \author David Henningsson \author Tom Moebert \author Copyright © 2003-2020 Peter Hanappe, Conrad Berhörster, Antoine Schmitt, Pedro López-Cabanillas, Josh Green, David Henningsson, Tom Moebert \version Revision 2.1.1 \date 2020-02-16 All the source code examples in this document are in the public domain; you can use them as you please. This document is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ . The FluidSynth library is distributed under the GNU Lesser General Public License. A copy of the GNU Lesser General Public License is contained in the FluidSynth package; if not, visit http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt or write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. \section Abstract FluidSynth is a software synthesizer based on the SoundFont 2 specifications. The synthesizer is available as a shared object that can easily be reused in any application that wants to use wave-table synthesis. This document explains the basic usage of FluidSynth. Some of the more advanced features are not yet discussed but will be added in future versions. \section Contents Table of Contents - \ref Disclaimer - \ref Introduction - \ref NewIn2_1_1 - \ref NewIn2_1_0 - \ref NewIn2_0_8 - \ref NewIn2_0_7 - \ref NewIn2_0_6 - \ref NewIn2_0_5 - \ref NewIn2_0_3 - \ref NewIn2_0_2 - \ref NewIn2_0_0 - \ref CreatingSettings - \ref CreatingSynth - \ref CreatingAudioDriver - \ref UsingSynth - \ref LoadingSoundfonts - \ref SendingMIDI - \ref RealtimeMIDI - \ref MIDIPlayer - \ref FileRenderer - \ref MIDIPlayerMem - \ref MIDIRouter - \ref Sequencer - \ref Shell - \ref Multi-channel - \ref Advanced \section Disclaimer This documentation may be partly incomplete. As always, the source code is the final reference. SoundFont(R) is a registered trademark of E-mu Systems, Inc. \section Introduction What is FluidSynth? - FluidSynth is a software synthesizer based on the SoundFont 2 specifications. The synthesizer is available as a shared object (a concept also named Dynamic Linking Library, or DLL) that can be easily reused in any application for wave-table synthesis. This document explains the basic usage of FluidSynth. - FluidSynth provides a Command Line Interface program ready to be used from the console terminal, offering most of the library functionalities to end users, among them the ability of render and play Standard MIDI Files, receive real-time MIDI events from external hardware ports and other applications, perform advanced routing of such events, enabling at the same time a local shell as well as a remote server commands interface. - FluidSynth is an API (Application Programming Interface) relieving programmers from a lot of details of reading SoundFont and MIDI events and files, and sending the digital audio output to a Sound Card. These tasks can be accomplished using a small set of functions. This document explains most of the API functions and gives short examples about them. - FluidSynth uses instrument samples contained in standard SF2 (SoundFont 2) files, having a file structure based on the RIFF format. The specification is publicly available on the internet, but most users don't need to know any details of the format. - FluidSynth can easily be embedded in an application. It has a main header file, fluidsynth.h, and one dynamically linkable library. FluidSynth runs on Linux, Mac OS X, and the Windows platforms, and support for OS/2 and OpenSolaris is experimental. It has audio and midi drivers for all mentioned platforms but you can use it with your own drivers if your application already handles MIDI and audio input/output. This document explains the basic usage of FluidSynth and provides examples that you can reuse. - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org \section NewIn2_1_1 What's new in 2.1.1? - requirements for explicit sequencer client unregistering have been relaxed: delete_fluid_sequencer() now correctly frees any registered sequencer clients (clients can still be explicitly unregistered) - using the sequencer with the system timer as timing source has been deprecated \section NewIn2_1_0 What's new in 2.1.0? - refrain from using fluid_synth_set_sample_rate() - synth.sample-rate is no real-time setting anymore, see note about fluid_synth_set_sample_rate() - new reverb engine - chorus is now stereophonic - smallest allowed chorus speed is now 0.1 Hz (previously 0.29 Hz) - the following audio drivers were added: - opensles - oboe - sdl2 - waveout \section NewIn2_0_8 What's new in 2.0.8? - fluid_sample_set_sound_data() caused broken sound when copying sample data \section NewIn2_0_7 What's new in 2.0.7? - fluid_free() has been added to allow proper deallocation by programming languages other than C/C++ \section NewIn2_0_6 What's new in 2.0.6? - the MIDI player did not emit any audio when calling fluid_player_play() after fluid_player_stop() \section NewIn2_0_5 What's new in 2.0.5? - fluid_synth_process() omitted audio samples when called with arbitrary sample counts that were not a multiple of fluid_synth_get_internal_bufsize() - fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if "synth.dynamic-sample-loading" was set to FALSE \section NewIn2_0_3 What's new in 2.0.3? - fix incorrect behaviour of fluid_sample_set_sound_data() - add missing getters for midi events: - fluid_midi_event_get_text() - fluid_midi_event_get_lyrics() \section NewIn2_0_2 What's new in 2.0.2? - fluid_synth_error() has been deprecated, use fluid_set_log_function() to interfere log messages \section NewIn2_0_0 What's new in 2.0.0? FluidSynths major version was bumped. The API was reworked, deprecated functions were removed. Important changes that may not result in a compilation error but may cause your app to misbehave: - all public \c fluid_settings_* functions that return an integer which is not meant to be interpreted as bool consistently return either FLUID_OK or FLUID_FAILED - fluid_settings_setstr() cannot be used to set integer (toggle) settings with "yes" or "no" values anymore. Use fluid_settings_setint() instead, for example:
fluid_settings_setint(settings, "synth.reverb.active", 0) instead of fluid_settings_setstr(settings, "synth.reverb.active", "no") - explicit client unregistering is required for fluid_sequencer_register_client() and fluid_sequencer_register_fluidsynth() (since fluidsynth 2.1.1 not required anymore, but still recommend) - all public functions consistently receive signed integers for soundfont ids, bank and program numbers - use unique device names for the "audio.portaudio.device" setting - fluid_synth_process() received a new more flexible implementation, but now requires zeroed-out sample buffers Other changes in FluidSynth 2.0.0 concerning developers: - all public \c delete_* functions return void and are safe when called with NULL - the shell command handler was decoupled internally, as a consequence the param list of new_fluid_server() and new_fluid_cmd_handler() was adapted - \c fluid_settings_set* functions no longer silently register unknown settings but return an error instead - reverb: roomsize is now limited to an upper threshold of 1.0 to avoid exponential volume increase - rename \c fluid_mod_new() and \c fluid_mod_delete() to match naming conventions: new_fluid_mod() and delete_fluid_mod() - rename chorus getters to match naming conventions: fluid_synth_get_chorus_speed() and fluid_synth_get_chorus_depth() - fluid_synth_remove_sfont() returns FLUID_OK or FLUID_FAILED - introduce a separate data type for sequencer client IDs: #fluid_seq_id_t - fluid_get_userconf() has been implemented for Windows New Features and API additions: - add "midi.autoconnect" a setting for automatically connecting fluidsynth to available MIDI input ports - add "synth.overflow.important" and "synth.overflow.important-channels" settings to take midi channels during overflow calculation into account that are considered to be "important" - add "synth.dynamic-sample-loading" a setting for enabling on demand sample loading - add support for polyphonic key pressure events, see fluid_event_key_pressure() and fluid_synth_key_pressure() - add fluid_synth_add_default_mod() and fluid_synth_remove_default_mod() for manipulating default modulators - add individual reverb setters: fluid_synth_set_reverb_roomsize(), fluid_synth_set_reverb_damp(), fluid_synth_set_reverb_width(), fluid_synth_set_reverb_level() - add individual chorus setters: fluid_synth_set_chorus_nr(), fluid_synth_set_chorus_level(), fluid_synth_set_chorus_speed(), fluid_synth_set_chorus_depth(), fluid_synth_set_chorus_type() - add realtime settings for reverb and chorus parameters - add seek support to midi-player, see fluid_player_seek() - expose functions to manipulate the ladspa effects unit (see ladspa.h) - add support for text and lyrics midi events, see fluid_midi_event_set_lyrics() and fluid_midi_event_set_text() - complete rewrite of the soundfont loader API, see sfont.h - support for 24 bit audio samples, see fluid_sample_set_sound_data() - expose new_fluid_defsfloader() to support loading soundfonts from memory, see fluid_sfloader_set_callbacks() and fluidsynth_sfload_mem.c - remove these structs from the public API and provide proper getter and setter functions instead: - struct _fluid_sfloader_t - struct _fluid_sample_t - struct _fluid_sfont_t - struct _fluid_preset_t - add an additional general-purpose IIR filter, see fluid_synth_set_custom_filter() - add a custom sinusoidal modulator mapping function, see #FLUID_MOD_SIN - implement polymono support according to MIDI specs: - add basic channel support, see fluid_synth_reset_basic_channel(), fluid_synth_set_basic_channel(), fluid_synth_get_basic_channel() - implement MIDI modes Omni On, Omni Off, Poly, Mono, see #fluid_basic_channel_modes - implement portamento control, see fluid_synth_set_portamento_mode(), fluid_synth_get_portamento_mode() - implement legato control, see fluid_synth_set_legato_mode(), fluid_synth_get_legato_mode() - implement breath control, see fluid_synth_set_breath_mode(), fluid_synth_get_breath_mode() API cleanups: - the ramsfont has been removed, because it is unmaintained and believed to be unused; please get in touch with the mailing list if you still need it - remove deprecated fluid_synth_get_channel_info() in favour of fluid_synth_get_program() and fluid_synth_get_channel_preset() - remove deprecated fluid_settings_getstr() - remove deprecated fluid_synth_set_midi_router(), instead supply the midi-router instance when creating a command handler with new_fluid_cmd_handler() - remove deprecated fluid_get_hinstance() and fluid_set_hinstance() (dsound driver now uses the desktop window) - remove deprecated fluid_synth_create_key_tuning(), use fluid_synth_activate_key_tuning(synth, bank, prog, name, pitch, FALSE) instead - remove deprecated fluid_synth_create_octave_tuning(), use fluid_synth_activate_octave_tuning(synth, bank, prog, name, pitch, FALSE) instead - remove deprecated fluid_synth_select_tuning(), use fluid_synth_activate_tuning(synth, chan, bank, prog, FALSE) instead - remove deprecated fluid_synth_reset_tuning(), use fluid_synth_deactivate_tuning(synth, chan, FALSE) instead - remove deprecated FLUID_HINT_INTEGER - remove deprecated fluid_synth_set_gen2() as there doesn't seem to be a use case for absolute generator values - remove deprecated "synth.parallel-render" setting - remove obsolete "audio.[out|in]put-channels" settings - remove unimplemented "synth.dump" setting - remove fluid_cmd_handler_register() and fluid_cmd_handler_unregister() from public API, as they seem to be unused downstream - remove misspelled FLUID_SEQ_PITCHWHHELSENS macro - remove struct _fluid_mod_t from public API, use the getters and setters of mod.h instead - remove struct _fluid_gen_t, fluid_gen_set_default_values() and enum fluid_gen_flags from public API - remove macros fluid_sfont_get_id() and fluid_sample_refcount() from public API - remove FLUID_NUM_MOD macro from public API - remove the following deprecated enum values from public API: - GEN_LAST - LAST_LOG_LEVEL - FLUID_SEQ_LASTEVENT - FLUID_MIDI_ROUTER_RULE_COUNT \section CreatingSettings Creating and changing the settings Before you can use the synthesizer, you have to create a settings object. The settings objects is used by many components of the FluidSynth library. It gives a unified API to set the parameters of the audio drivers, the midi drivers, the synthesizer, and so forth. A number of default settings are defined by the current implementation. All settings have a name that follows the "dotted-name" notation. For example, "synth.polyphony" refers to the number of voices (polyphony) allocated by the synthesizer. The settings also have a type. There are currently three types: strings, numbers (double floats), and integers. You can change the values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), and fluid_settings_setint() functions. For example: \code #include int main(int argc, char** argv) { fluid_settings_t* settings = new_fluid_settings(); fluid_settings_setint(settings, "synth.polyphony", 128); /* ... */ delete_fluid_settings(settings); return 0; } \endcode The API contains the functions to query the type, the current value, the default value, the range and the "hints" of a setting. The range is the minimum and maximum value of the setting. The hints gives additional information about a setting. For example, whether a string represents a filename. Or whether a number should be interpreted on on a logarithmic scale. Check the settings.h API documentation for a description of all functions. \section CreatingSynth Creating the synthesizer To create the synthesizer, you pass it the settings object, as in the following example: \code #include int main(int argc, char** argv) { fluid_settings_t* settings; fluid_synth_t* synth; settings = new_fluid_settings(); synth = new_fluid_synth(settings); /* Do useful things here */ delete_fluid_synth(synth); delete_fluid_settings(settings); return 0; } \endcode For a full list of available synthesizer settings, please refer to FluidSettings Documentation. \section CreatingAudioDriver Creating the Audio Driver The synthesizer itself does not write any audio to the audio output. This allows application developers to manage the audio output themselves if they wish. The next section describes the use of the synthesizer without an audio driver in more detail. Creating the audio driver is straightforward: set the audio.driver settings and create the driver object. Because the FluidSynth has support for several audio systems, you may want to change which one you want to use. The list below shows the audio systems that are currently supported. It displays the name, as used by the fluidsynth library, and a description. - jack: JACK Audio Connection Kit (Linux, Mac OS X, Windows) - alsa: Advanced Linux Sound Architecture (Linux) - oss: Open Sound System (Linux, Unix) - pulseaudio: PulseAudio (Linux, Mac OS X, Windows) - coreaudio: Apple CoreAudio (Mac OS X) - dsound: Microsoft DirectSound (Windows) - portaudio: PortAudio Library (Mac OS 9 & X, Windows, Linux) - sndman: Apple SoundManager (Mac OS Classic) - dart: DART sound driver (OS/2) - opensles: OpenSL ES (Android) - oboe: Oboe (Android) - waveout: Microsoft WaveOut, alternative to DirectSound (Windows CE x86, Windows Mobile 2003 for ARMv5, Windows 98 SE, Windows NT 4.0, Windows XP and later) - file: Driver to output audio to a file - sdl2*: Simple DirectMedia Layer (Linux, Windows, Mac OS X, iOS, Android, FreeBSD, Haiku, etc.) The default audio driver depends on the settings with which FluidSynth was compiled. You can get the default driver with fluid_settings_getstr_default(). To get the list of available drivers use the fluid_settings_foreach_option() function. Finally, you can set the driver with fluid_settings_setstr(). In most cases, the default driver should work out of the box. Additional options that define the audio quality and latency are "audio.sample-format", "audio.period-size", and "audio.periods". The details are described later. You create the audio driver with the new_fluid_audio_driver() function. This function takes the settings and synthesizer object as arguments. For example: \code void init() { fluid_settings_t* settings; fluid_synth_t* synth; fluid_audio_driver_t* adriver; settings = new_fluid_settings(); /* Set the synthesizer settings, if necessary */ synth = new_fluid_synth(settings); fluid_settings_setstr(settings, "audio.driver", "jack"); adriver = new_fluid_audio_driver(settings, synth); } \endcode As soon as the audio driver is created, it will start playing. The audio driver creates a separate thread that uses the synthesizer object to generate the audio. There are a number of general audio driver settings. The audio.driver settings define the audio subsystem that will be used. The audio.periods and audio.period-size settings define the latency and robustness against scheduling delays. There are additional settings for the audio subsystems used. For a full list of available audio driver settings, please refer to FluidSettings Documentation. *Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done before the first call to new_fluid_settings()! Also make sure to call SDL_Quit() after all fluidsynth instances have been destroyed. \section UsingSynth Using the synthesizer without an audio driver It is possible to use the synthesizer object without creating an audio driver. This is desirable if the application using FluidSynth manages the audio output itself. The synthesizer has several API functions that can be used to obtain the audio output: fluid_synth_write_s16() fills two buffers (left and right channel) with samples coded as signed 16 bits (the endian-ness is machine dependent). fluid_synth_write_float() fills a left and right audio buffer with 32 bits floating point samples. The function fluid_synth_process() is the generic interface for synthesizing audio, which is also capable of multi channel audio output. \section LoadingSoundfonts Loading and managing SoundFonts Before any sound can be produced, the synthesizer needs a SoundFont. SoundFonts are loaded with the fluid_synth_sfload() function. The function takes the path to a SoundFont file and a boolean to indicate whether the presets of the MIDI channels should be updated after the SoundFont is loaded. When the boolean value is TRUE, all MIDI channel bank and program numbers will be refreshed, which may cause new instruments to be selected from the newly loaded SoundFont. The synthesizer can load any number of SoundFonts. The loaded SoundFonts are treated as a stack, where each new loaded SoundFont is placed at the top of the stack. When selecting presets by bank and program numbers, SoundFonts are searched beginning at the top of the stack. In the case where there are presets in different SoundFonts with identical bank and program numbers, the preset from the most recently loaded SoundFont is used. The fluid_synth_program_select() can be used for unambiguously selecting a preset or bank offsets could be applied to each SoundFont with fluid_synth_set_bank_offset(), to try and ensure that each preset has unique bank and program numbers. The fluid_synth_sfload() function returns the unique identifier of the loaded SoundFont, or -1 in case of an error. This identifier is used in subsequent management functions: fluid_synth_sfunload() removes the SoundFont, fluid_synth_sfreload() reloads the SoundFont. When a SoundFont is reloaded, it retains it's ID and position on the SoundFont stack. Additional API functions are provided to get the number of loaded SoundFonts and to get a pointer to the SoundFont. \section SendingMIDI Sending MIDI Events Once the synthesizer is up and running and a SoundFont is loaded, most people will want to do something useful with it. Make noise, for example. MIDI messages can be sent using the fluid_synth_noteon(), fluid_synth_noteoff(), fluid_synth_cc(), fluid_synth_pitch_bend(), fluid_synth_pitch_wheel_sens(), and fluid_synth_program_change() functions. For convenience, there's also a fluid_synth_bank_select() function (the bank select message is normally sent using a control change message). The following example show a generic graphical button that plays a note when clicked: \code class SoundButton : public SomeButton { public: SoundButton() : SomeButton() { if (!_synth) { initSynth(); } } static void initSynth() { _settings = new_fluid_settings(); _synth = new_fluid_synth(_settings); _adriver = new_fluid_audio_driver(_settings, _synth); } /* ... */ virtual int handleMouseDown(int x, int y) { /* Play a note on key 60 with velocity 100 on MIDI channel 0 */ fluid_synth_noteon(_synth, 0, 60, 100); } virtual int handleMouseUp(int x, int y) { /* Release the note on key 60 */ fluid_synth_noteoff(_synth, 0, 60); } protected: static fluid_settings_t* _settings; static fluid_synth_t* _synth; static fluid_audio_driver_t* _adriver; }; \endcode \section RealtimeMIDI Creating a Real-time MIDI Driver FluidSynth can process real-time MIDI events received from hardware MIDI ports or other applications. To do so, the client must create a MIDI input driver. It is a very similar process to the creation of the audio driver: you initialize some properties in a settings instance and call the new_fluid_midi_driver() function providing a callback function that will be invoked when a MIDI event is received. The following MIDI drivers are currently supported: - jack: JACK Audio Connection Kit MIDI driver (Linux, Mac OS X) - oss: Open Sound System raw MIDI (Linux, Unix) - alsa_raw: ALSA raw MIDI interface (Linux) - alsa_seq: ALSA sequencer MIDI interface (Linux) - winmidi: Microsoft Windows MM System (Windows) - midishare: MIDI Share (Linux, Mac OS X) - coremidi: Apple CoreMIDI (Mac OS X) \code #include int handle_midi_event(void* data, fluid_midi_event_t* event) { printf("event type: %d\n", fluid_midi_event_get_type(event)); } int main(int argc, char** argv) { fluid_settings_t* settings; fluid_midi_driver_t* mdriver; settings = new_fluid_settings(); mdriver = new_fluid_midi_driver(settings, handle_midi_event, NULL); /* ... */ delete_fluid_midi_driver(mdriver); return 0; } \endcode There are a number of general MIDI driver settings. The midi.driver setting defines the MIDI subsystem that will be used. There are additional settings for the MIDI subsystems used. For a full list of available midi driver settings, please refer to FluidSettings Documentation. \section MIDIPlayer Loading and Playing a MIDI file FluidSynth can be used to play MIDI files, using the MIDI File Player interface. It follows a high level implementation, though its implementation is currently incomplete. After initializing the synthesizer, create the player passing the synth instance to new_fluid_player(). Then, you can add some SMF file names to the player using fluid_player_add(), and finally call fluid_player_play() to start the playback. You can check if the player has finished by calling fluid_player_get_status(), or wait for the player to terminate using fluid_player_join(). \code #include int main(int argc, char** argv) { int i; fluid_settings_t* settings; fluid_synth_t* synth; fluid_player_t* player; fluid_audio_driver_t* adriver; settings = new_fluid_settings(); synth = new_fluid_synth(settings); player = new_fluid_player(synth); /* process command line arguments */ for (i = 1; i < argc; i++) { if (fluid_is_soundfont(argv[i])) { fluid_synth_sfload(synth, argv[1], 1); } if (fluid_is_midifile(argv[i])) { fluid_player_add(player, argv[i]); } } /* start the synthesizer thread */ adriver = new_fluid_audio_driver(settings, synth); /* play the midi files, if any */ fluid_player_play(player); /* wait for playback termination */ fluid_player_join(player); /* cleanup */ delete_fluid_audio_driver(adriver); delete_fluid_player(player); delete_fluid_synth(synth); delete_fluid_settings(settings); return 0; } \endcode A list of available MIDI player settings can be found in FluidSettings Documentation. \section FileRenderer Fast file renderer for non-realtime MIDI file rendering Instead of creating an audio driver as described in section \ref MIDIPlayer one may chose to use the file renderer, which is the fastest way to synthesize MIDI files. \code fluid_settings_t* settings; fluid_synth_t* synth; fluid_player_t* player; fluid_file_renderer_t* renderer; settings = new_fluid_settings(); // specify the file to store the audio to // make sure you compiled fluidsynth with libsndfile to get a real wave file // otherwise this file will only contain raw s16 stereo PCM fluid_settings_setstr(settings, "audio.file.name", "/path/to/output.wav"); // use number of samples processed as timing source, rather than the system timer fluid_settings_setstr(settings, "player.timing-source", "sample"); // since this is a non-realtime scenario, there is no need to pin the sample data fluid_settings_setint(settings, "synth.lock-memory", 0); synth = new_fluid_synth(settings); // *** loading of a soundfont omitted *** player = new_fluid_player(synth); fluid_player_add(player, "/path/to/midifile.mid"); fluid_player_play(player); renderer = new_fluid_file_renderer (synth); while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { if (fluid_file_renderer_process_block(renderer) != FLUID_OK) { break; } } // just for sure: stop the playback explicitly and wait until finished fluid_player_stop(player); fluid_player_join(player); delete_fluid_file_renderer(renderer); delete_fluid_player(player); delete_fluid_synth(synth); delete_fluid_settings(settings); \endcode Various output files types are supported, if compiled with libsndfile. Those can be specified via the \c settings object as well. Refer to the FluidSettings Documentation for more \c audio.file\.\* options. \section MIDIPlayerMem Playing a MIDI file from memory FluidSynth can be also play MIDI files directly from a buffer in memory. If you need to play a file from a stream (such as stdin, a network, or a high-level file interface), you can load the entire file into a buffer first, and then use this approach. Use the same technique as above, but rather than calling fluid_player_add(), load it into memory and call fluid_player_add_mem() instead. Once you have passed a buffer to fluid_player_add_mem(), it is copied, so you may use it again or free it immediately (it is your responsibility to free it if you allocated it). \code #include #include #include /* An example midi file */ const char MIDIFILE[] = { 0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x01, 0x01, 0xe0, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x90, 0x3c, 0x64, 0x87, 0x40, 0x80, 0x3c, 0x7f, 0x00, 0x90, 0x43, 0x64, 0x87, 0x40, 0x80, 0x43, 0x7f, 0x00, 0x90, 0x48, 0x64, 0x87, 0x40, 0x80, 0x48, 0x7f, 0x83, 0x60, 0xff, 0x2f, 0x00 }; int main(int argc, char** argv) { int i; void* buffer; size_t buffer_len; fluid_settings_t* settings; fluid_synth_t* synth; fluid_player_t* player; fluid_audio_driver_t* adriver; settings = new_fluid_settings(); synth = new_fluid_synth(settings); player = new_fluid_player(synth); adriver = new_fluid_audio_driver(settings, synth); /* process command line arguments */ for (i = 1; i < argc; i++) { if (fluid_is_soundfont(argv[i])) { fluid_synth_sfload(synth, argv[1], 1); } } /* queue up the in-memory midi file */ fluid_player_add_mem(player, MIDIFILE, sizeof(MIDIFILE)); /* play the midi file */ fluid_player_play(player); /* wait for playback termination */ fluid_player_join(player); /* cleanup */ delete_fluid_audio_driver(adriver); delete_fluid_player(player); delete_fluid_synth(synth); delete_fluid_settings(settings); return 0; } \endcode \section MIDIRouter Real-time MIDI router The MIDI router is one more processing layer directly behind the MIDI input. It processes incoming MIDI events and generates control events for the synth. It can be used to filter or modify events prior to sending them to the synthesizer. When created, the MIDI router is transparent and simply passes all MIDI events. Router "rules" must be added to actually make use of its capabilities. Some examples of MIDI router usage: - Filter messages (Example: Pass sustain pedal CCs only to selected channels) - Split the keyboard (Example: noteon with notenr < x: to ch 1, >x to ch 2) - Layer sounds (Example: for each noteon received on ch 1, create a noteon on ch1, ch2, ch3,...) - Velocity scaling (Example: for each noteon event, scale the velocity by 1.27) - Velocity switching (Example: v <= 100: "Angel Choir"; v > 100: "Hell's Bells") - Get rid of aftertouch The MIDI driver API has a clean separation between the midi thread and the synthesizer. That opens the door to add a midi router module. MIDI events coming from the MIDI player do not pass through the MIDI router. \code #include int main(int argc, char** argv) { fluid_settings_t* settings; fluid_synth_t* synth; fluid_midi_router_t* router; fluid_midi_router_rule_t* rule; settings = new_fluid_settings(); synth = new_fluid_synth(settings); /* Create the MIDI router and pass events to the synthesizer */ router = new_fluid_midi_router (settings, fluid_synth_handle_midi_event, synth); /* Clear default rules */ fluid_midi_router_clear_rules (router); /* Add rule to map all notes < MIDI note #60 on any channel to channel 4 */ rule = new_fluid_midi_router_rule (); fluid_midi_router_rule_set_chan (rule, 0, 15, 0.0, 4); /* Map all to channel 4 */ fluid_midi_router_rule_set_param1 (rule, 0, 59, 1.0, 0); /* Match notes < 60 */ fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_NOTE); /* Add rule to map all notes >= MIDI note #60 on any channel to channel 5 */ rule = new_fluid_midi_router_rule (); fluid_midi_router_rule_set_chan (rule, 0, 15, 0.0, 5); /* Map all to channel 5 */ fluid_midi_router_rule_set_param1 (rule, 60, 127, 1.0, 0); /* Match notes >= 60 */ fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_NOTE); /* Add rule to reverse direction of pitch bender on channel 7 */ rule = new_fluid_midi_router_rule (); fluid_midi_router_rule_set_chan (rule, 7, 7, 1.0, 0); /* Match channel 7 only */ fluid_midi_router_rule_set_param1 (rule, 0, 16383, -1.0, 16383); /* Reverse pitch bender */ fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_PITCH_BEND); /* ... Create audio driver, process events, etc ... */ /* cleanup */ delete_fluid_midi_router(router); delete_fluid_synth(synth); delete_fluid_settings(settings); return 0; } \endcode \section Sequencer FluidSynth's sequencer can be used to play MIDI events in a more flexible way than using the MIDI file player, which expects the events to be stored as Standard MIDI Files. Using the sequencer, you can provide the events one by one, with an optional timestamp for scheduling. The client program should first initialize the sequencer instance using the function new_fluid_sequencer2(). There is a complementary function delete_fluid_sequencer() to delete it. After creating the sequencer instance, the destinations can be registered using fluid_sequencer_register_fluidsynth() for the synthesizer destination, and optionally using fluid_sequencer_register_client() for the client destination providing a suitable callback function. It can be unregistered using fluid_sequencer_unregister_client(). After the initialization, events can be sent with fluid_sequencer_send_now() and scheduled to the future with fluid_sequencer_send_at(). The registration functions return identifiers, that can be used as destinations of an event using fluid_event_set_dest(). The function fluid_sequencer_get_tick() returns the current playing position. A program may choose a new timescale in milliseconds using fluid_sequencer_set_time_scale(). The following example uses the fluidsynth sequencer to implement a sort of music box. FluidSynth internal clock is used to schedule repetitive sequences of notes. The next sequence is scheduled on advance before the end of the current one, using a timer event that triggers a callback function. The scheduling times are always absolute values, to avoid slippage. \code #include "fluidsynth.h" fluid_synth_t* synth; fluid_audio_driver_t* adriver; fluid_sequencer_t* sequencer; short synthSeqID, mySeqID; unsigned int now; unsigned int seqduration; // prototype void seq_callback(unsigned int time, fluid_event_t* event, fluid_sequencer_t* seq, void* data); void createsynth() { fluid_settings_t* settings; settings = new_fluid_settings(); fluid_settings_setint(settings, "synth.reverb.active", 0); fluid_settings_setint(settings, "synth.chorus.active", 0); synth = new_fluid_synth(settings); adriver = new_fluid_audio_driver(settings, synth); sequencer = new_fluid_sequencer2(0); // register synth as first destination synthSeqID = fluid_sequencer_register_fluidsynth(sequencer, synth); // register myself as second destination mySeqID = fluid_sequencer_register_client(sequencer, "me", seq_callback, NULL); // the sequence duration, in ms seqduration = 1000; } void deletesynth() { delete_fluid_sequencer(sequencer); delete_fluid_audio_driver(adriver); delete_fluid_synth(synth); } void loadsoundfont() { int fluid_res; // put your own path here fluid_res = fluid_synth_sfload(synth, "Inside:VintageDreamsWaves-v2.sf2", 1); } void sendnoteon(int chan, short key, unsigned int date) { int fluid_res; fluid_event_t *evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, synthSeqID); fluid_event_noteon(evt, chan, key, 127); fluid_res = fluid_sequencer_send_at(sequencer, evt, date, 1); delete_fluid_event(evt); } void schedule_next_callback() { int fluid_res; // I want to be called back before the end of the next sequence unsigned int callbackdate = now + seqduration/2; fluid_event_t *evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, mySeqID); fluid_event_timer(evt, NULL); fluid_res = fluid_sequencer_send_at(sequencer, evt, callbackdate, 1); delete_fluid_event(evt); } void schedule_next_sequence() { // Called more or less before each sequence start // the next sequence start date now = now + seqduration; // the sequence to play // the beat : 2 beats per sequence sendnoteon(0, 60, now + seqduration/2); sendnoteon(0, 60, now + seqduration); // melody sendnoteon(1, 45, now + seqduration/10); sendnoteon(1, 50, now + 4*seqduration/10); sendnoteon(1, 55, now + 8*seqduration/10); // so that we are called back early enough to schedule the next sequence schedule_next_callback(); } /* sequencer callback */ void seq_callback(unsigned int time, fluid_event_t* event, fluid_sequencer_t* seq, void* data) { schedule_next_sequence(); } int main(void) { createsynth(); loadsoundfont(); // initialize our absolute date now = fluid_sequencer_get_tick(sequencer); schedule_next_sequence(); sleep(100000); deletesynth(); return 0; } \endcode \section Shell Shell interface The shell interface allows you to send simple textual commands to the synthesizer, to parse a command file, or to read commands from the stdin or other input streams. To find the list of currently supported commands, please check the fluid_cmd.c file or type "help" in the fluidsynth command line shell. For a full list of available command line settings, please refer to FluidSettings Documentation. \section Multi-channel Multi-Channel audio rendering FluidSynth is capable of rendering all audio and all effects from all MIDI channels to separate stero buffers. Refer to the documentation of fluid_synth_process() and review the different use-cases in the example file for information on how to do that: \ref fluidsynth_process.c \section Advanced Advanced features, not yet documented. API reference may contain more info. - Accessing low-level voice parameters - Reverb settings - Chorus settings - Interpolation settings (set_gen, get_gen, NRPN) - Voice overflow settings - LADSPA effects unit - MIDI tunings */ /*! \example example.c Example producing short random music with FluidSynth */ /*! \example fluidsynth_simple.c A basic example of using fluidsynth to play a single note */ /*! \example fluidsynth_fx.c Example of using effects with fluidsynth */ /*! \example fluidsynth_metronome.c Example of a simple metronome using the MIDI sequencer API */ /*! \example fluidsynth_arpeggio.c Example of an arpeggio generated using the MIDI sequencer API */ /*! \example fluidsynth_register_adriver.c Example of how to register audio drivers using fluid_audio_driver_register() (advanced users only) */ /*! \example fluidsynth_sfload_mem.c Example of how read a soundfont from memory (advanced users only) */ /*! \example fluidsynth_process.c Usage examples of how to render audio using fluid_synth_process() (advanced users only) */ fluidsynth-2.1.1/doc/fluidsynth.1000066400000000000000000000205671362231004000167250ustar00rootroot00000000000000.\" hey, Emacs: -*- nroff -*- .\" FluidSynth is free software; you can redistribute it and/or modify .\" it under the terms of the GNU Lesser General Public License as published by .\" the Free Software Foundation; either version 2.1 of the License, or .\" (at your option) any later version. .\" .\" This program is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU Lesser General Public License .\" along with this program; see the file LICENSE. If not, write to .\" the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. .\" .TH FluidSynth 1 "Feb 16, 2020" .\" Please update the above date whenever this man page is modified. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins (default) .\" .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 FluidSynth \- a SoundFont synthesizer .SH SYNOPSIS .B fluidsynth .RI [ options ] [ SoundFonts ] [ midifiles ] .SH DESCRIPTION \fBFluidSynth\fP is a real-time MIDI synthesizer based on the SoundFont(R) 2 specifications. It can be used to render MIDI input or MIDI files to audio. The MIDI events are read from a MIDI device. The sound is rendered in real-time to the sound output device. .PP The easiest way to start the synthesizer is to give it a SoundFont on the command line: 'fluidsynth soundfont.sf2'. fluidsynth will load the SoundFont and read MIDI events from the default MIDI device using the default MIDI driver. Once FluidSynth is running, it reads commands from the stdin. There are commands to send MIDI events manually, to load or unload SoundFonts, and so forth. All the available commands are discussed below. .PP FluidSynth can also be used to play a list of MIDI files. Simply run FluidSynth with the SoundFont and the list of MIDI files to play. In this case you might not want to open the MIDI device to read external events. Use the \-n option to deactivate MIDI input. If you also want to deactivate the use of the shell, start FluidSynth with the \-i option: 'fluidsynth \-ni soundfont.sf2 midifile1.mid midifile2.mid'. .PP Run fluidsynth with the \-\-help option to check for changes in the list of options. .SH OPTIONS \fBfluidsynth\fP accepts the following options: .TP .B \-a, \-\-audio\-driver=[label] The audio driver to use. "\-a help" to list valid options .TP .B \-c, \-\-audio\-bufcount=[count] Number of audio buffers .TP .B \-C, \-\-chorus Turn the chorus on or off [0|1|yes|no, default = on] .TP .B \-d, \-\-dump Dump incoming and outgoing MIDI events to stdout .TP .B \-E, \-\-audio\-file\-endian Audio file endian for fast rendering or aufile driver ("\-E help" for list) .TP .B \-f, \-\-load\-config Load command configuration file (shell commands) .TP .B \-F, \-\-fast\-render=[file] Render MIDI file to raw audio data and store in [file] .TP .B \-g, \-\-gain Set the master gain [0 < gain < 10, default = 0.2] .TP .B \-G, \-\-audio\-groups Defines the number of LADSPA audio nodes .TP .B \-h, \-\-help Print out this help summary .TP .B \-i, \-\-no\-shell Don't read commands from the shell [default = yes] .TP .B \-j, \-\-connect\-jack\-outputs Attempt to connect the jack outputs to the physical ports .TP .B \-K, \-\-midi\-channels=[num] The number of midi channels [default = 16] .TP .B \-l, \-\-disable\-lash Don't connect to LASH server .TP .B \-L, \-\-audio\-channels=[num] The number of stereo audio channels [default = 1] .TP .B \-m, \-\-midi\-driver=[label] The name of the midi driver to use. "\-m help" to list valid options. .TP .B \-n, \-\-no\-midi\-in Don't create a midi driver to read MIDI input events [default = yes] .TP .B \-o Define a setting, \-o name=value ("\-o help" to dump current values) .TP .B \-O, \-\-audio\-file\-format Audio file format for fast rendering or aufile driver ("\-O help" for list) .TP .B \-p, \-\-portname=[label] Set MIDI port name (alsa_seq, coremidi drivers) .TP .B \-q, \-\-quiet Do not print welcome message or other informational output .TP .B \-r, \-\-sample\-rate Set the sample rate .TP .B \-R, \-\-reverb Turn the reverb on or off [0|1|yes|no, default = on] .TP .B \-s, \-\-server Start FluidSynth as a server process .TP .B \-T, \-\-audio\-file\-type Audio file type for fast rendering or aufile driver ("\T help" for list) .TP .B \-v, \-\-verbose Print out verbose messages about midi events (synth.verbose=1) as well as other debug messages .TP .B \-V, \-\-version Show version of program .TP .B \-z, \-\-audio\-bufsize=[size] Size of each audio buffer .SH SETTINGS The settings to be specified with \-o are documented in the fluidsettings.xml hopefully shipped with this distribution or online at http://www.fluidsynth.org/api/fluidsettings.xml . We recommend viewing this file in a webbrowser, favourably Firefox. .SH SHELL COMMANDS .TP .B GENERAL .TP .B help Prints out list of help topics (type "help " to view details on available commands) .TP .B quit Quit the synthesizer .TP .B SOUNDFONTS .TP .B load filename Load a SoundFont .TP .B unload number Unload a SoundFont. The number is the index of the SoundFont on the stack. .TP .B fonts Lists the current SoundFonts on the stack .TP .B inst number Print out the available instruments for the SoundFont. .TP .B MIDI MESSAGES .TP .B noteon channel key velocity Send a note-on event .TP .B noteoff channel key Send a note-off event .TP .B cc channel ctrl value Send a control change event .TP .B prog chan num Send program-change message .TP .B select chan sfont bank prog Combination of bank-select and program-change .TP .B channels Print out the presets of all channels. .TP .B AUDIO SYNTHESIS .TP .B gain value Set the master gain (0 < gain < 5) .TP .B interp num Choose interpolation method for all channels .TP .B interpc chan num Choose interpolation method for one channel .TP .B REVERB .TP .B set synth.reverb.active [0|1] Turn the reverb on or off .TP .B set synth.reverb.room-size num Change reverb room size .TP .B set synth.reverb.damp num Change reverb damping .TP .B set synth.reverb.width num Change reverb width .TP .B set synth.reverb.level num Change reverb level .TP .B CHORUS .TP .B set synth.chorus.active [0|1] Turn the chorus on or off .TP .B set synth.chorus.nr n Use n delay lines (default 3) .TP .B set synth.chorus.level num Set output level of each chorus line to num .TP .B set synth.chorus.speed num Set mod speed of chorus to num (Hz) .TP .B set synth.chorus.depth num Set chorus modulation depth to num (ms) .TP .B MIDI ROUTER .TP .B router_default Reloads the default MIDI routing rules (input channels are mapped 1:1 to the synth) .TP .B router_clear Deletes all MIDI routing rules. .TP .B router_begin [note|cc|prog|pbend|cpress|kpress] Starts a new routing rule for events of the given type .TP .B router_chan min max mul add Limits the rule for events on min <= chan <= max. If the channel falls into the window, it is multiplied by 'mul', then 'add' is added. .TP .B router_par1 min max mul add Limits parameter 1 (for example note number in a note events). Similar to router_chan. .TP .B router_par2 min max mul add Limits parameter 2 (for example velocity in a note event). Similar to router_chan .TP .B router_end Finishes the current rule and adds it to the router. .TP .B Router examples .TP router_clear .TP router_begin note .TP router_chan 0 7 0 15 .TP router_end .TP Will accept only note events from the lower 8 MIDI channels. Regardless of the channel, the synthesizer plays the note on ch 15 (synthchannel=midichannel*0+15) .TP router_begin cc .TP router_chan 0 7 0 15 .TP router_par1 1 1 0 64 .TP router_add Configures the modulation wheel to act as sustain pedal (transforms CC 1 to CC 64 on the lower 8 MIDI channels, routes to ch 15) .SH AUTHORS Peter Hanappe .br Markus Nentwig .br Antoine Schmitt .br Josh Green .br Stephane Letz .br Tom Moebert Please check the AUTHORS and THANKS files for all credits .SH DISCLAIMER SoundFont(R) is a registered trademark of E-mu Systems, Inc. fluidsynth-2.1.1/doc/fluidsynth_arpeggio.c000066400000000000000000000112141362231004000206510ustar00rootroot00000000000000/* FluidSynth Arpeggio - Sequencer API example * * This code is in the public domain. * * To compile: * gcc -o fluidsynth_arpeggio -lfluidsynth fluidsynth_arpeggio.c * * To run: * fluidsynth_arpeggio soundfont [steps [duration]] * * [Pedro Lopez-Cabanillas ] */ #include #include #include fluid_synth_t *synth; fluid_audio_driver_t *audiodriver; fluid_sequencer_t *sequencer; short synth_destination, client_destination; unsigned int time_marker; /* duration of the pattern in ticks. */ unsigned int duration = 1440; /* notes of the arpeggio */ unsigned int notes[] = { 60, 64, 67, 72, 76, 79, 84, 79, 76, 72, 67, 64 }; /* number of notes in one pattern */ unsigned int pattern_size; /* prototype */ void sequencer_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data); /* schedule a note on message */ void schedule_noteon(int chan, short key, unsigned int ticks) { fluid_event_t *ev = new_fluid_event(); fluid_event_set_source(ev, -1); fluid_event_set_dest(ev, synth_destination); fluid_event_noteon(ev, chan, key, 127); fluid_sequencer_send_at(sequencer, ev, ticks, 1); delete_fluid_event(ev); } /* schedule a note off message */ void schedule_noteoff(int chan, short key, unsigned int ticks) { fluid_event_t *ev = new_fluid_event(); fluid_event_set_source(ev, -1); fluid_event_set_dest(ev, synth_destination); fluid_event_noteoff(ev, chan, key); fluid_sequencer_send_at(sequencer, ev, ticks, 1); delete_fluid_event(ev); } /* schedule a timer event (shall trigger the callback) */ void schedule_timer_event(void) { fluid_event_t *ev = new_fluid_event(); fluid_event_set_source(ev, -1); fluid_event_set_dest(ev, client_destination); fluid_event_timer(ev, NULL); fluid_sequencer_send_at(sequencer, ev, time_marker, 1); delete_fluid_event(ev); } /* schedule the arpeggio's notes */ void schedule_pattern(void) { int i, note_time, note_duration; note_time = time_marker; note_duration = duration / pattern_size; for(i = 0; i < pattern_size; ++i) { schedule_noteon(0, notes[i], note_time); note_time += note_duration; schedule_noteoff(0, notes[i], note_time); } time_marker += duration; } void sequencer_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { schedule_timer_event(); schedule_pattern(); } void usage(char *prog_name) { printf("Usage: %s soundfont.sf2 [steps [duration]]\n", prog_name); printf("\t(optional) steps: number of pattern notes, from 2 to %d\n", pattern_size); printf("\t(optional) duration: of the pattern in ticks, default %d\n", duration); } int main(int argc, char *argv[]) { int n; fluid_settings_t *settings; settings = new_fluid_settings(); pattern_size = sizeof(notes) / sizeof(int); if(argc < 2) { usage(argv[0]); } else { /* create the synth, driver and sequencer instances */ synth = new_fluid_synth(settings); /* load a SoundFont */ n = fluid_synth_sfload(synth, argv[1], 1); if(n != -1) { sequencer = new_fluid_sequencer2(0); /* register the synth with the sequencer */ synth_destination = fluid_sequencer_register_fluidsynth(sequencer, synth); /* register the client name and callback */ client_destination = fluid_sequencer_register_client(sequencer, "arpeggio", sequencer_callback, NULL); if(argc > 2) { n = atoi(argv[2]); if((n > 1) && (n <= pattern_size)) { pattern_size = n; } } if(argc > 3) { n = atoi(argv[3]); if(n > 0) { duration = n; } } audiodriver = new_fluid_audio_driver(settings, synth); /* get the current time in ticks */ time_marker = fluid_sequencer_get_tick(sequencer); /* schedule patterns */ schedule_pattern(); schedule_timer_event(); schedule_pattern(); /* wait for user input */ printf("press to stop\n"); n = getchar(); } /* clean and exit */ delete_fluid_audio_driver(audiodriver); delete_fluid_sequencer(sequencer); delete_fluid_synth(synth); } delete_fluid_settings(settings); return 0; } fluidsynth-2.1.1/doc/fluidsynth_fx.c000066400000000000000000000112331362231004000174720ustar00rootroot00000000000000/* FluidSynth FX - An example of using effects with fluidsynth * * This code is in the public domain. * * To compile: * gcc -g -O -o fluidsynth_fx fluidsynth_fx.c -lfluidsynth * * To run * fluidsynth_fx soundfont gain * * [Peter Hanappe] */ #include #include #include /* The structure with the effects data. This example simply applies a * linear gain the to synthesizer output. */ struct fx_data_t { fluid_synth_t *synth; float gain; } fx_data_t; /* This function implements the callback function of the audio driver * (see new_fluid_audio_driver2 below). The data argument is a pointer * to your private data structure. 'len' is the number of audio frames * in the buffers. 'nfx' and 'nout' are the number of input and output * audio buffers. 'fx' and 'out' are arrays of float buffers containing * the audio. The audio driver fills zero-initializes those buffers. * You are responsible for filling up those buffers, as the result will * be sent to the sound card. This is usually done by asking the synth * to fill those buffers appropriately using fluid_synth_process() * * NOTE: The API was designed to be generic. Audio driver may fill the * buffers with audio input from the soundcard, rather than zeros. */ int fx_function(void *data, int len, int nfx, float **fx, int nout, float **out) { struct fx_data_t *fx_data = (struct fx_data_t *) data; int i, k; if(fx == 0) { /* Note that some audio drivers may not provide buffers for effects like * reverb and chorus. In this case it's your decision what to do. If you * had called fluid_synth_process() like in the else branch below, no * effects would have been rendered. Instead, you may mix the effects * directly into the out buffers. */ if(fluid_synth_process(fx_data->synth, len, nout, out, nout, out) != FLUID_OK) { /* Some error occurred. Very unlikely to happen, though. */ return FLUID_FAILED; } } else { /* Call the synthesizer to fill the output buffers with its * audio output. */ if(fluid_synth_process(fx_data->synth, len, nfx, fx, nout, out) != FLUID_OK) { /* Some error occurred. Very unlikely to happen, though. */ return FLUID_FAILED; } } /* Apply your effects here. In this example, the gain is * applied to all the dry-audio output buffers. */ for(i = 0; i < nout; i++) { float *out_i = out[i]; for(k = 0; k < len; k++) { out_i[k] *= fx_data->gain; } } /* Apply the same effect to all available effect buffer. */ for(i = 0; i < nfx; i++) { float *fx_i = fx[i]; for(k = 0; k < len; k++) { fx_i[k] *= fx_data->gain; } } return FLUID_OK; } int main(int argc, char **argv) { fluid_settings_t *settings; fluid_synth_t *synth = NULL; fluid_audio_driver_t *adriver = NULL; int err = 0; struct fx_data_t fx_data; if(argc != 3) { fprintf(stderr, "Usage: fluidsynth_simple [soundfont] [gain]\n"); return 1; } /* Create the settings object. This example uses the default * values for the settings. */ settings = new_fluid_settings(); if(settings == NULL) { fprintf(stderr, "Failed to create the settings\n"); err = 2; goto cleanup; } /* Create the synthesizer */ synth = new_fluid_synth(settings); if(synth == NULL) { fprintf(stderr, "Failed to create the synthesizer\n"); err = 3; goto cleanup; } /* Load the soundfont */ if(fluid_synth_sfload(synth, argv[1], 1) == -1) { fprintf(stderr, "Failed to load the SoundFont\n"); err = 4; goto cleanup; } /* Fill in the data of the effects unit */ fx_data.synth = synth; fx_data.gain = atof(argv[2]); /* Create the audio driver. As soon as the audio driver is * created, the synthesizer can be played. */ adriver = new_fluid_audio_driver2(settings, fx_function, (void *) &fx_data); if(adriver == NULL) { fprintf(stderr, "Failed to create the audio driver\n"); err = 5; goto cleanup; } /* Play a note */ fluid_synth_noteon(synth, 0, 60, 100); printf("Press \"Enter\" to stop: "); fgetc(stdin); printf("done\n"); cleanup: if(adriver) { delete_fluid_audio_driver(adriver); } if(synth) { delete_fluid_synth(synth); } if(settings) { delete_fluid_settings(settings); } return err; } fluidsynth-2.1.1/doc/fluidsynth_metronome.c000066400000000000000000000102431362231004000210620ustar00rootroot00000000000000/* FluidSynth Metronome - Sequencer API example * * This code is in the public domain. * * To compile: * gcc -o fluidsynth_metronome -lfluidsynth fluidsynth_metronome.c * * To run: * fluidsynth_metronome soundfont [beats [tempo]] * * [Pedro Lopez-Cabanillas ] */ #include #include #include fluid_synth_t *synth; fluid_audio_driver_t *audiodriver; fluid_sequencer_t *sequencer; short synth_destination, client_destination; unsigned int time_marker; /* default tempo, beats per minute */ #define TEMPO 120 unsigned int note_duration = 60000 / TEMPO; /* metronome click/bell */ unsigned int weak_note = 33; unsigned int strong_note = 34; /* number of notes in one pattern */ unsigned int pattern_size = 4; /* prototype */ void sequencer_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data); /* schedule a note on message */ void schedule_noteon(int chan, short key, unsigned int ticks) { fluid_event_t *ev = new_fluid_event(); fluid_event_set_source(ev, -1); fluid_event_set_dest(ev, synth_destination); fluid_event_noteon(ev, chan, key, 127); fluid_sequencer_send_at(sequencer, ev, ticks, 1); delete_fluid_event(ev); } /* schedule a timer event (shall trigger the callback) */ void schedule_timer_event(void) { fluid_event_t *ev = new_fluid_event(); fluid_event_set_source(ev, -1); fluid_event_set_dest(ev, client_destination); fluid_event_timer(ev, NULL); fluid_sequencer_send_at(sequencer, ev, time_marker, 1); delete_fluid_event(ev); } /* schedule the metronome pattern */ void schedule_pattern(void) { int i, note_time; note_time = time_marker; for(i = 0; i < pattern_size; ++i) { schedule_noteon(9, i ? weak_note : strong_note, note_time); note_time += note_duration; } time_marker = note_time; } void sequencer_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { schedule_timer_event(); schedule_pattern(); } void usage(char *prog_name) { printf("Usage: %s soundfont.sf2 [beats [tempo]]\n", prog_name); printf("\t(optional) beats: number of pattern beats, default %d\n", pattern_size); printf("\t(optional) tempo: BPM (Beats Per Minute), default %d\n", TEMPO); } int main(int argc, char *argv[]) { int n; fluid_settings_t *settings; settings = new_fluid_settings(); if(argc < 2) { usage(argv[0]); } else { /* create the synth, driver and sequencer instances */ synth = new_fluid_synth(settings); /* load a SoundFont */ n = fluid_synth_sfload(synth, argv[1], 1); if(n != -1) { sequencer = new_fluid_sequencer2(0); /* register the synth with the sequencer */ synth_destination = fluid_sequencer_register_fluidsynth(sequencer, synth); /* register the client name and callback */ client_destination = fluid_sequencer_register_client(sequencer, "fluidsynth_metronome", sequencer_callback, NULL); audiodriver = new_fluid_audio_driver(settings, synth); if(argc > 2) { n = atoi(argv[2]); if(n > 0) { pattern_size = n; } } if(argc > 3) { n = atoi(argv[3]); if(n > 0) { note_duration = 60000 / n; } } /* get the current time in ticks */ time_marker = fluid_sequencer_get_tick(sequencer); /* schedule patterns */ schedule_pattern(); schedule_timer_event(); schedule_pattern(); /* wait for user input */ printf("press to stop\n"); n = getchar(); } /* clean and exit */ delete_fluid_audio_driver(audiodriver); delete_fluid_sequencer(sequencer); delete_fluid_synth(synth); } delete_fluid_settings(settings); return 0; } fluidsynth-2.1.1/doc/fluidsynth_process.c000066400000000000000000000100751362231004000205360ustar00rootroot00000000000000/* * This is a C99 program that outlines different usage examples for fluid_synth_process() */ #include #include #include int main() { // any arbitrary number of audio samples to render during on call of fluid_synth_process() enum { SAMPLES = 512 }; // ...creation of synth omitted... // USECASE1: render all dry audio channels + reverb and chorus to one stereo channel { // planar sample buffers that received synthesized (monophonic) audio float left[SAMPLES], right[SAMPLES]; // array of buffers used to setup channel mapping float *dry[1 * 2], *fx[1 * 2]; // first make sure to zero out the sample buffers everytime before calling fluid_synth_process() memset(left, 0, sizeof(left)); memset(right, 0, sizeof(right)); // setup channel mapping for a single stereo channel to which to render all dry audio to dry[0] = left; dry[1] = right; // Setup channel mapping for a single stereo channel to which to render effects to. // Just using the same sample buffers as for dry audio is fine here, as it will cause the effects to be mixed with dry output. // Note: reverb and chorus together make up two stereo channels. Setting up only one stereo channel is sufficient // as the channels wraps around (i.e. chorus will be mixed with reverb channel). fx[0] = left; fx[1] = right; int err = fluid_synth_process(synth, SAMPLES, 2, fx, 2, dry); if(err == FLUID_FAILED) { puts("oops"); } // USECASE2: only render dry audio and discard effects // same as above, but call fluid_synth_process() like: int err = fluid_synth_process(synth, SAMPLES, 0, NULL, 2, dry); if(err == FLUID_FAILED) { puts("oops"); } } // USECASE3: render audio and discard all samples { int err = fluid_synth_process(synth, SAMPLES, 0, NULL, 0, NULL); if(err == FLUID_FAILED) { puts("oops"); } } // USECASE4: multi-channel rendering, i.e. render all audio and effects channels to dedicated audio buffers // ofc it‘s not a good idea to allocate all the arrays on the stack { // lookup number of audio and effect (stereo-)channels of the synth // see "synth.audio-channels", "synth.effects-channels" and "synth.effects-groups" settings respectively int n_aud_chan = fluid_synth_count_audio_channels(synth); // by default there are two effects stereo channels (reverb and chorus) ... int n_fx_chan = fluid_synth_count_effects_channels(synth); // ... for each effects unit. Each unit takes care of the effects of one MIDI channel. // If there are less units than channels, it wraps around and one unit may render effects of multiple // MIDI channels. n_fx_chan *= fluid_synth_count_effects_groups(); // for simplicity, allocate one single sample pool float samp_buf[SAMPLES * (n_aud_chan + n_fx_chan) * 2]; // array of buffers used to setup channel mapping float *dry[n_aud_chan * 2], *fx[n_fx_chan * 2]; // setup buffers to mix dry stereo audio to // buffers are alternating left and right for each n_aud_chan, // please review documentation of fluid_synth_process() for(int i = 0; i < n_aud_chan * 2; i++) { dry[i] = &samp_buf[i * SAMPLES]; } // setup buffers to mix effects stereo audio to // similar channel layout as above, revie fluid_synth_process() for(int i = 0; i < n_fx_chan * 2; i++) { fx[i] = &samp_buf[n_aud_chan * 2 * SAMPLES + i * SAMPLES]; } // dont forget to zero sample buffer(s) before each rendering memset(samp_buf, 0, sizeof(samp_buf)); int err = fluid_synth_process(synth, SAMPLES, n_fx_chan * 2, fx, n_aud_chan * 2, dry); if(err == FLUID_FAILED) { puts("oops"); } } return 0; } fluidsynth-2.1.1/doc/fluidsynth_register_adriver.c000066400000000000000000000047171362231004000224260ustar00rootroot00000000000000/* * This is a simple C99 program that demonstrates the usage of fluid_audio_driver_register() * * There are 3 calls to fluid_audio_driver_register(), i.e. 3 iterations: * First the alsa driver is registered and created, followed by the jack and portaudio driver. * * The usual usecase would be to call fluid_audio_driver_register() only once providing the audio drivers needed during fluidsynth usage. * If necessary however fluid_audio_driver_register() can be called multiple times as demonstrated here. * Therefore the user must make sure to delete all fluid-instances of any kind before making the call to fluid_audio_driver_register(). * Else the behaviour is undefined and the application is likely to crash. */ #include #include int main() { const char *DRV[] = { "alsa", "jack", "portaudio" }; const char *adrivers[2]; /* three iterations, first register only alsa, then only jack, and last portaudio * ...just to demonstrate how and under which conditions fluid_audio_driver_register() * can be called */ for(int i = 0; i < sizeof(DRV) / sizeof(DRV[0]); i++) { adrivers[0] = DRV[i]; /* register any other driver you need * * adrivers[X] = "whatever"; */ adrivers[1] = NULL; /* NULL terminate the array */ /* register those audio drivers. Note that at this time no fluidsynth objects are alive! */ int res = fluid_audio_driver_register(adrivers); if(res != FLUID_OK) { puts("adriver reg err"); return -1; } fluid_settings_t *settings = new_fluid_settings(); res = fluid_settings_setstr(settings, "audio.driver", DRV[i]); /* As of fluidsynth 2, settings API has been refactored to return FLUID_OK|FAILED * rather than returning TRUE or FALSE */ #if FLUIDSYNTH_VERSION_MAJOR >= 2 if(res != FLUID_OK) #else if(res == 0) #endif { puts("audio.driver set err"); return -1; } fluid_synth_t *synth = new_fluid_synth(settings); fluid_audio_driver_t *ad = new_fluid_audio_driver(settings, synth); /* * ~~~ Do your daily business here ~~~ */ delete_fluid_audio_driver(ad); delete_fluid_synth(synth); delete_fluid_settings(settings); /* everything cleaned up, fluid_audio_driver_register() can be called again if needed */ } return 0; } fluidsynth-2.1.1/doc/fluidsynth_sfload_mem.c000066400000000000000000000037341362231004000211720ustar00rootroot00000000000000/* * This is a C99 program that demonstrates how to load a soundfont from memory. * * It only gives a brief overview on how to achieve this with fluidsynth's API. * Although it should compile, it's highly incomplete, as the details of it's * implementation depend on the users needs. */ #include #include #include void *my_open(const char *filename) { void *p; if(filename[0] != '&') { return NULL; } sscanf(filename, "&%p", &p); return p; } int my_read(void *buf, int count, void *handle) { // NYI return FLUID_OK; } int my_seek(void *handle, long offset, int origin) { // NYI return FLUID_OK; } int my_close(void *handle) { // NYI return FLUID_OK; } long my_tell(void *handle) { // NYI return 0; } int main() { int err = 0; fluid_settings_t *settings = new_fluid_settings(); fluid_synth_t *synth = new_fluid_synth(settings); fluid_sfloader_t *my_sfloader = new_fluid_defsfloader(settings); fluid_sfloader_set_callbacks(my_sfloader, my_open, my_read, my_seek, my_tell, my_close); fluid_synth_add_sfloader(synth, my_sfloader); char abused_filename[64]; const void *pointer_to_sf2_in_mem = 0x1234Beef; // some pointer to where the soundfont shall be loaded from sprintf(abused_filename, "&%p", pointer_to_sf2_in_mem); int id = fluid_synth_sfload(synth, abused_filename, 0); /* now my_open() will be called with abused_filename and should have opened the memory region */ if(id == FLUID_FAILED) { puts("oops"); err = -1; goto cleanup; } /* * ~~~ Do your daily business here ~~~ */ cleanup: /* deleting the synth also deletes my_sfloader */ delete_fluid_synth(synth); delete_fluid_settings(settings); return err; } fluidsynth-2.1.1/doc/fluidsynth_simple.c000066400000000000000000000036301362231004000203500ustar00rootroot00000000000000/* FluidSynth Simple - An example of using fluidsynth * * This code is in the public domain. * * To compile: * gcc -g -O -o fluidsynth_simple fluidsynth_simple.c -lfluidsynth * * To run * fluidsynth_simple soundfont * * [Peter Hanappe] */ #include #include int main(int argc, char **argv) { fluid_settings_t *settings; fluid_synth_t *synth = NULL; fluid_audio_driver_t *adriver = NULL; int err = 0; if(argc != 2) { fprintf(stderr, "Usage: fluidsynth_simple [soundfont]\n"); return 1; } /* Create the settings object. This example uses the default * values for the settings. */ settings = new_fluid_settings(); if(settings == NULL) { fprintf(stderr, "Failed to create the settings\n"); err = 2; goto cleanup; } /* Create the synthesizer */ synth = new_fluid_synth(settings); if(synth == NULL) { fprintf(stderr, "Failed to create the synthesizer\n"); err = 3; goto cleanup; } /* Load the soundfont */ if(fluid_synth_sfload(synth, argv[1], 1) == -1) { fprintf(stderr, "Failed to load the SoundFont\n"); err = 4; goto cleanup; } /* Create the audio driver. As soon as the audio driver is * created, the synthesizer can be played. */ adriver = new_fluid_audio_driver(settings, synth); if(adriver == NULL) { fprintf(stderr, "Failed to create the audio driver\n"); err = 5; goto cleanup; } /* Play a note */ fluid_synth_noteon(synth, 0, 60, 100); printf("Press \"Enter\" to stop: "); fgetc(stdin); printf("done\n"); cleanup: if(adriver) { delete_fluid_audio_driver(adriver); } if(synth) { delete_fluid_synth(synth); } if(settings) { delete_fluid_settings(settings); } return err; } fluidsynth-2.1.1/doc/ladspa.md000066400000000000000000000423631362231004000162360ustar00rootroot00000000000000# FluidSynth LADSPA Interface The [LADSPA](http://ladspa.org/) (Linux Audio Developer's Simple Plugin API) binding can be used to route the FluidSynth audio output through any number of LADSPA plugins. Please note that even though the "L" in LADSPA stands for "Linux", it can also be used on different platforms, for example Windows or MacOS. Check the "LADSPA on other Platforms" section at the end of this guide for more information. ## Configuration To configure and compile FluidSynth with LADSPA support, make sure you have the LADSPA SDK installed (or at least the ladspa.h header file available in an include path). Then compile FluidSynth in the usual way. You should see `LADSPA support: yes` in the cmake output. To enable the LADSPA engine, use the `synth.ladspa.active` setting when starting FluidSynth: fluidsynth -o synth.ladspa.active=1 ... # Quickstart Tutorial The following walks you through the process of adding a LADSPA plugin into your FluidSynth configuration. It assumes that you are running FluidSynth on Linux, that you have some experience with running Linux shell commands and that you know how to start FluidSynth from the command line and use it to play a MIDI file. ## Introduction to LADSPA You don't need to to have detailed knowledge of LADSPA to use effects with FluidSynth, but knowing some of it's concepts will help if you want to make the best use of it. If you have the LADSPA SDK installed you should be able to use the `listplugins` Linux command to list all plugins installed in your LADSPA path. And to show more details about a particular plugin library, you can use the `analyseplugin` Linux command. Here is an example showing the details of the `delay.so` plugin from the LADSPA SDK: ``` user@host:$ analyseplugin /usr/lib/ladspa/delay.so Plugin Name: "Simple Delay Line" Plugin Label: "delay_5s" Plugin Unique ID: 1043 Maker: "Richard Furse (LADSPA example plugins)" Copyright: "None" Must Run Real-Time: No Has activate() Function: Yes Has deactivate() Function: No Has run_adding() Function: No Environment: Normal or Hard Real-Time Ports: "Delay (Seconds)" input, control, 0 to 5, default 1 "Dry/Wet Balance" input, control, 0 to 1, default 0.5 "Input" input, audio "Output" output, audio ``` This output tells you that the `delay.so` library contains only a single plugin called "Simple Delay Line". Most importantly it lists the input and output ports, which can be used to set plugin parameters and connect the audio input and output to FluidSynth. "Delay (Seconds)" and "Dry/Wet Balance" are input controls. They are the parameters that a user can set to affect the way the plugin works. They control how long the delay should be and how the dry and wet signals should be mixed before writing them to the output. "Input" and "Output" are audio ports which carry samples into the plugin and out again after it has run. Mono plugins usually provide one set of input and output audio ports, stereo plugins usually provide two sets. But there are even plugins that only have a single output port and no input at all (think of noise generators...) Also note the line `Has run_adding() Function: No`. This specifies that this plugin can not mix it's audio output into an output buffer, but will always replace anything that is already there. This will become important again later on. ## FluidSynth Host Ports Just as LADSPA plugins have input and output ports, FluidSynth provides it's own audio ports that can be connected to plugins. On a standard stereo setup, the following four ports are automatically created: - Main:L - Main:R - Reverb:Send - Chorus:Send The "Main:L" and "Main:R" ports can be connected to effect input and output ports. They carry the main audio signals into the LADSPA effects and the modified signals back into FluidSynth. "Reverb:Send" and "Chorus:Send" can be used as effect inputs. They carry the mono effect send signals (as determined by the reverb and chorus send generators for each voice) into the LADSPA effects. Please note that if you run FluidSynth with the internal reverb and chorus effects active (which is the default), then those effects are already mixed into the Main:L and Main:R channels. Fore more details, please see the "Signal Flow" section below. For host port setups in multi-channel configurations, please see the "Multi-Channel Output" section below. ## Creating a Configuration File You can configure LADSPA effects using the FluidSynth shell, but writing the commands into a file and loading it at startup is much more comfortable. So let's create a file `effects.txt` with the following contents: effects.txt ``` ladspa_effect e1 /usr/lib/ladspa/delay.so ladspa_link e1 Input Main:L ladspa_link e1 Output Main:L ladspa_effect e2 /usr/lib/ladspa/delay.so delay_5s ladspa_link e2 Input Main:R ladspa_link e2 Output Main:R ladspa_start ``` As the "Simple Delay Line" plugin only works on a mono signal, the configuration above creates two effects: the one we named "e1" reads from and writes to the left FluidSynth audio channel "Main:L", the "e2" effect reads from and writes to the right channel "Main:R". Please note that we only specified the path to the library `/usr/lib/ladspa/delay.so` when creating the "e1" effect, but not which plugin from the library to use. This is possible because the delay.so library contains only a single plugin. If you want to use a library that contains more than one plugin, you would need to give the plugin name as well, as we've done when creating the "e2" effect. The string to use here is what is called "Plugin Label" in the `analyseplugin` output. ## Using the Configuration File Lets start FluidSynth with ALSA output and the standard SoundFont, enable LADSPA effects, load the effects.txt config file and give it a test MIDI file to play: (You will need to replace the `test.mid` with your own MIDI file and maybe change the paths to the effects.txt file and the SoundFont) ``` user@host:$ fluidsynth -a alsa -o synth.ladspa.active=1 -f effects.txt FluidR3_GM.sf2 test.mid ``` You should now hear the MIDI file played at a slightly lower volume with a one second delay effect added on both left and right channel. If not, please check the FluidSynth output for any error messages. ## Changing Parameters You probably noticed that we did not set any values for the "Delay (Seconds)" and "Dry/Wet Balance" control ports. The delay plugin specifies default values for these parameters: 1 second delay and a dry/wet balance of 0.5 (check the `analyseplugin` output above). So when you don't override them, the defaults are automatically used for rendering. Let's set different values now and set the delay time on the left channel to half a second and to 1.5 seconds on the right channel: ``` ladspa_effect e1 /usr/lib/ladspa/delay.so ladspa_link e1 Input Main:L ladspa_link e1 Output Main:L ladspa_set e1 Delay 0.5 ladspa_effect e2 /usr/lib/ladspa/delay.so ladspa_link e2 Input Main:R ladspa_link e2 Output Main:R ladspa_set e2 Delay 1.5 ladspa_start ``` Start FluidSynth again and you should hear that the delay is shorter on the left channel, longer on the right. You can even change control parameters while FluidSynth is running. Just type the `ladspa_set ...` commands into the FluidSynth shell. And to check the difference that the LADSPA effects have on the sound output, you can turn them off and on again during run-time. Just type in `ladspa_stop` and `ladspa_start` into the FluidSynth shell. ### Port Name Matching Plugin port names are sometimes very long, because the plugin writers want them to be self-documenting. But note that we didn't need to give the complete port name "Delay (Seconds)" in the `ladspa_set` commands, but chose to use a much shorter version: "Delay". When specifying a port name for the `ladspa_link` and `ladspa_set` commands, the system will look for any port that *starts with* the name you gave it. If there is only one match, then that port is chosen. If there are multiple matches (meaning your port name is ambiguous), you will see an error asking you to be more specific. So the configuration for the "e1" effect could also have been written with much shorter port names: ``` ladspa_effect e1 /usr/lib/ladspa/delay.so ladspa_link e1 In Main:L ladspa_link e1 Out Main:L ladspa_set e1 Del 0.5 ``` # Signal Flow The LADSPA effects unit runs immediately after the internal reverb and chorus effects have been processed. When no effects have been configured, the LADSPA engine is dormant and uses no additional system resources. When at least one effect is configured and the engine is activated, the rendered audio is passed into the LADSPA effects engine, the effects are run in the order that they were created and the resulting audio is passed back into FluidSynth (and from there to the sound card or other output). ## Effect Sends Please note that SoundFont designers can specify how much signal each instrument should add to the reverb and chorus effect sends. When FluidSynth renders a block of audio, all currently sounding instruments are mixed into the `Main` output channels. In addition, all instruments add their signal to the effect send ports (`Reverb:Send` and `Chorus:Send`) according to the effect send amount specified in the SoundFont. If you want to replace the internal reverb or chorus effects with a LADSPA plugin and you want to honour the decisions made by the SoundFont designer, you should use the `Reverb:Send` or `Chorus:Send` ports as effect input and `Main:L` and `Main:R` ports as effect outputs. (See the "Example Setups" section below for an example on how to replace the internal reverb with a LADSPA plugin.) Please note that FluidSynth uses a mono signal for both effects, that is why there is only a single send port for reverb and chorus. # LADSPA Command Reference The following is a description of all LADSPA-related commands that are available in the FluidSynth shell if it has been compiled with LADSPA support. - `ladspa_effect`: Create a new effect from a plugin library - `ladspa_buffer`: Create a new buffer - `ladspa_link`: Link an effect port to a host port or a buffer - `ladspa_set`: Set the value of an effect control - `ladspa_check`: Check the effect setup for any problems - `ladspa_start`: Start the effects unit - `ladspa_stop`: Stop the effects unit - `ladspa_reset`: Reset the effects unit ## ladspa_effect ``` ladspa_effect [plugin-name] [--mix [gain]] ``` Load the LADSPA plugin library given by `` and create a new effect (i.e. an instance of a plugin). `` can be chosen by the user and must unique. `` is optional if the library contains only one plugin. If the optional `--mix` parameter is given, then the LADSPA engine will call the `run_adding` interface of the plugin. This will make the effect add it's output to the output buffers instead of replacing them. The `--mix` parameter takes an optional float value `gain`, which will be multiplied with each sample before adding to the output buffers. Please note that there is no command to delete a single effect once created. To remove effects, please use `ladspa_reset` to clear everything start from scratch. Can only be called when the effect unit is not active. ## ladspa_buffer ``` ladspa_buffer ``` Create a new audio buffer called ``. The buffer is able to be used as mono output or mono input to an effect. Buffers can be used to connect plugins between each other without overwriting the host ports with temporary data. Please note that there is no command to delete a buffer. To remove buffers, please use `ladspa_reset` to clear everything and start from scratch. Can only be used when the effect unit is not active. ## ladspa_link ``` ladspa_link ``` Connects an effect input or output port with a buffer or a host port. This command can be called multiple times and will overwrite the previous connection made on that effect port. Please note that there is no command to unlink an effect port. Use `ladspa_reset` to clear everything and start from scratch. Can only be used when the effect unit is not active. ## ladspa_set ``` ladspa_set ``` Sets a control port of an effect to a float value. Can be used at any time, even when the effect unit is active. ## ladspa_check ``` ladspa_check ``` Checks the LADSPA effect configuration for errors. This command is also implicitly called when executing `ladspa_start`. ## ladspa_start ``` ladspa_start ``` Activates the effects unit and inserts the configured effects into FluidSynth's audio rendering pipeline. ## ladspa_stop ``` ladspa_stop ``` Deactivates the effects unit and removes the configured effects from FluidSynth's audio rendering pipeline. The configuration is left untouched, so it can be started again with `ladspa_start`. ## ladspa_reset ``` ladspa_reset ``` Deactivates the effects unit if active and clears all configuration and loaded plugins. # Example Setups All examples assume that your `LADSPA_PATH` environment variable points to the directory containing the plugin libraries (e.g. /usr/lib/ladspa). ## Single Plugin The following loads the delay.so plugin library from the LADSPA SDK and instantiates the delay effect under the name "e1". It connects the main left audio channel from FluidSynth with the plugin input and output and starts the effects engine. ``` ladspa_effect e1 delay.so ladspa_link e1 Input Main:L ladspa_link e1 Output Main:L ladspa_start ``` The audible effect should be an untouched right channel and a slightly lower volume on the left with a delay effect of 1 second on top. ## Replacing the FluidSynth Reverb Effect If you would like a different reverb implementation than the one built-in to FluidSynth, you can use a LADSPA reverb plugin like the "TAP Reverb" from [Tom's Audio Processing plugins](http://tap-plugins.sourceforge.net/ladspa.html). Here is the analyseplugin output for the `tap_reverb.so` plugin: ``` user@host:$ analyseplugin /usr/lib/ladspa/tap_reverb.so Plugin Name: "TAP Reverberator" Plugin Label: "tap_reverb" Plugin Unique ID: 2142 Maker: "Tom Szilagyi" Copyright: "GPL" Must Run Real-Time: No Has activate() Function: Yes Has deactivate() Function: No Has run_adding() Function: Yes Environment: Normal Ports: "Decay [ms]" input, control, 0 to 10000, default 2500 "Dry Level [dB]" input, control, -70 to 10, default 0 "Wet Level [dB]" input, control, -70 to 10, default 0 "Comb Filters" input, control, toggled, default 1 "Allpass Filters" input, control, toggled, default 1 "Bandpass Filter" input, control, toggled, default 1 "Enhanced Stereo" input, control, toggled, default 1 "Reverb Type" input, control, 0 to 42.1, default 0, integer "Input Left" input, audio "Output Left" output, audio "Input Right" input, audio "Output Right" output, audio ``` Using this information we can create a LADSPA configuration: effects.txt ``` ladspa_effect e1 /usr/lib/ladspa/tap_reverb.so ladspa_link e1 "Input Left" Reverb:Send ladspa_link e1 "Input Right" Reverb:Send ladspa_link e1 "Output Left" Main:L ladspa_link e1 "Output Right" Main:R ladspa_start ``` Start FluidSynth with the internal reverb disabled. (You will need to replace the `test.mid` with your own MIDI file and maybe change the paths to the effects.txt file and the SoundFont) ``` user@host:$ fluidsynth -a alsa -R0 -o synth.ladspa.active=1 -f effects.txt FluidR3_GM.sf2 test.mid ``` You will hear the output with a reverb effect from the plugin. And you can change the reverb control ports with the `ladspa_set` command while the MIDI file is playing. # Multi-Channel Output FluidSynth is capable of generating multi-channel output by specifying the `synth.audio-groups` and `synth.audio-channels` configuration settings. Explaining multi-channel output in detail is out of scope for this guide. But using multiple output channels has an effect on the host ports that are available to LADSPA plugins. As soon as you configure more than one audio-channel, the main audio ports will not be called "Main:L" and "Main:R" anymore, but will have indices added to their name. So if you start FluidSynth with `-o synth.audio-groups=2`, then the following ports will be created: - Main:L1 - Main:R1 - Main:L2 - Main:R2 - Reverb:Send - Chorus:Send If you want all main ports to act as outputs as well as inputs to the effects, then you also need to increase the `synth.audio-channels` setting. # LADSPA on other Platforms LADSPA is a very simple plugin architecture and only requires the ladspa.h header file as compile-time dependency. To build FluidSynth on non-Linux platform with LADSPA support, download the ladspa.h file from http://www.ladspa.org and place it somewhere in your compiler include path. Then configure and build LADSPA as you normally would. All information in the above documentation is valid for all other platforms as well. Just make sure you use the file path format specific to your platform in the `ladspa_effect` calls. For example, on Windows you should use ``` ladspa_effect c:\path\to\ladspa\plugin.dll ``` instead of ``` ladspa_effect /path/to/ladspa/plugin.so ``` Audacity provides a large number of precompiled LADSPA plugins for Windows and MacOS: http://www.audacityteam.org/download/plug-ins/ To get the `analyseplugin` and `listplugins` commands on Windows, you can either compile them yourself using the LADSPA-SDK source code from ladspa.org or install ladspa-sdk via Cygwin. fluidsynth-2.1.1/doc/polymono/000077500000000000000000000000001362231004000163145ustar00rootroot00000000000000fluidsynth-2.1.1/doc/polymono/FluidPolyMono-0004.pdf000066400000000000000000007570651362231004000221540ustar00rootroot00000000000000%PDF-1.4 %쏢 239 0 obj <> endobj xref 239 13 0000000015 00000 n 0000000577 00000 n 0000000696 00000 n 0000000864 00000 n 0000004351 00000 n 0000004373 00000 n 0000004416 00000 n 0000004499 00000 n 0000004570 00000 n 0000004633 00000 n 0000004666 00000 n 0000004722 00000 n 0000004840 00000 n trailer <<46EFD297739A7D0966097558F2DA68C8>]/Prev 248660>> startxref 0 %%EOF 240 0 obj <> endobj 241 0 obj <> /Contents 242 0 R >> endobj 242 0 obj <> stream x\r}߯ط즼fp˛ܜȱ-3)9"([")MX~/Lwc{#w% ;On~ZWJO?$&Gͤ7^>=~TUW !uߎ[vꕭu*(И3H5nٿm\hE[9L33f~=oz]Wjgb+W`4//le.b?xguאjnYͫT.efϝ=D]i ȯMfj~}NmNMduN_s؍F-Y0$:+x|!((pMU ڋb 1&r0U[$ښJO]*0Е1r߀ژQE 佱jv?0qI%nc Ƈ*ǵjC ]G<1hUcL07jmy)9׀@]ӢQ;@`y%UUeՖs]5m>[7Ԃ Vm`wy-={@iGEMڶ(j3avSZmh . Y7{ 3}S"N˨M]NH2ASPޓRk5&|vԕ-PL)>rfvy;f۪,+x)[4i#dka"}ONlM'8u>N=h3bڵIb.ɨIuhζq=g?GwiJnDrp=kA dɄMu04w'az<#oˁ>2#sYCa9>k~n&PT{Г]^$D=a!R֕($B95~~LV S9fh;QԜ,lRdld-: jXE+ILcihۆV?W$ʜa/f\c?'qMhxoзG8tGU׺^-5X/t/#IyIjR? @"%%8չ\Pe3a\cxL™k!Ext?' Dk`3)5CIZUv6 pEϤe-a )4{6Z^S= w|ٞm }FRr#iNIP;?`Y?lɥlmp=`زsPH)RQ2FH%*Lx|g@ Q$2bG$UqP wnJQOx/HɎ(D}7G˻ }`%)hL@Լ<bWA$Ht>PI2NVgɸ $>c#B-9SYmRIf228zSZ*jĢ1hĐD\{ !毴Qy+k] xtD,4 1>C*gQ~"墸8anԓBrn\> .S8p@@n$s]IbHOSdN٨mWQTrC?"IEOMr6W>j锻)uǘ1_)j9RLYl*g1HB*dFШ9Ml%qіe0lKDY7$O`FDԂ']f+XV T#{97uM 6÷͐N`*ݔ&_ @P1{)%䟰HϺDn*9XT)"!9P?wG+[qLjx2!&D]2d3Gm J{]]wƛ",>?ySaW֊0{P:7I4X^HAȸȮdRą/8dYKdb3և"1%Ҵ!1) %Zi cpRd\*s+e2ߊG2S k5oƦ,e%uEpfhyg>ൌ ~:B:L;Ȃw4s7 z"Ut5٢Z,2/i|6Ru픙 B!Te(]Q$<^W^߶vR1rD+ce-驐'ظƉ}4 M*؝#Yp(gS(fYR:z-!+Q!qDE>tY$b@}W~˂UWAs(łZMuˆB@y& zѳ9D]_u Uu2$Ttx UxKh1,2vZЇclCWR;ӗJϒRwV2kI o':pe X,}2SA]Dw~ɷ| 7t+Ii97DZ<1/)+F*4\65;Tit'W o/%vS==/~G}^{e]K 2ǸkO^dc78Xt$]4/ysIQc(1w>4Z_V EWx)Yxv?z@XAK&QQLJ _(]:keʭzX My Ghm+YNU"p2gW]k[n tG߾yoendstream endobj 243 0 obj 3413 endobj 244 0 obj <>endobj 245 0 obj <> endobj 246 0 obj <> endobj 247 0 obj <> endobj 248 0 obj <> endobj 249 0 obj <> endobj 250 0 obj <> endobj 251 0 obj <> stream `o M=VL8׉<"iĆriN,!YmSjH̐Mb5qB;#ą+jX[UcKQ#""33  "3 33 W%VeeeeeoVoVoVo%VVVVoVV]e]eVVRURI)$ Pc :G ]~,i&&*G*Hȱ,s_; (ܴ|:#U{ms#6t?-C&g)e}!'LQXП!'"p ܊{ FPNi!uo"Rb.22D hk T endstream endobj 1 0 obj <> /Contents 2 0 R >> endobj 2 0 obj <> stream x]_8OQq/[1%Ҽl003,4;K,D1 ;=A;ɶ..DeI鴔?e*"#-27{﯎7H=jL,eZ+(0TǿsÊǎ,br%VO Zkɗcwճ,֒dZY j?0+|Il}?TjY(Jb/It%僕f?IF+'-ώluG_%oa"u/ BARy?>ۑlD酐vqiuu߳2+XuvYN'bX~DJo'tG܈eZMR(.W^hFN²uYǂab"u!urj'/buP܆YC.y߇gY&L<ɩS;q2̬e~I[J2!4@GT@ 3ECmHOb X 2]e3)_ZXq.iWS ᱝ׫%Y1mdLgD+p:RW+([. Ќe xG8#C'JK>G (H4}Fci :#QINͤsW,ġcMoeZN2% &:ʲI]'AuPSl,R_"Uh53{O,BRt>I~yh4?Dx'8L/.8at$=Usj3~JZntա'-&nzo5S&)?vS, 7΍|E%:L1 4LD-2oUsM}c譙 |I^U*01)0_uE|mGgAvScunjajFẦ/ʭO0,HZ9QG\428ϩɴjQRss ewA5QCz$}頖JGCs@FuHQ]kQ3Xb$0 0NTDv)de_IXxG5jqx?x5)ᱜ_:@vfqK4GЮiRV}iV`xa5@؅%VE!7,OС0 îBq W d MNc$7י$z)Wuy͍KZ@PJxԤV=F \ITTUHOᙊ GxV*}Uk(DI|1Ro\ZJc@= i;Q I.ն.PoRP{ZM}IG"őkZGZuYى5Wޤuo|5١~c05B3H%aXn1F icW~9vI:ِ/T ]@Qr~J>Z<9r{/LtY'W Z-ЗIM[0mK@$S4Dӌ<3Jm SrL]4;D ty͖f&Cl`> @$b|0ɛ }P2O.Qx٩죍NVx)eD?5VTMpw@E(@A j ص0/RCa7af}a|·M@:XC8Z(blf56!M{{?@㞕eosp}.="k m>͒HfƉN_Fpvas)_7n CjaLq0Α3NmPmexD 5:5'YˍKIJBC'aJHkI0YV, )N\4p>nJ +;4zpk&ze,0Dm3DAͯw>.zɊrpAK* F##lmȝ>Ga3-C*2WDZc؝vex}v\jW$Ms9w>ojgV"p!y0t7)Ћ (58SL@DGX'H$4I3|+-rOR'} DdbOSRS+oyMG@>^tnjGKܣ^곉fG2'INI$=FZzdm+O?P$O*b.ͽ63.b<D6ꯉ F,m[;ycO'@qk>ئxyڽZ]}[5TWNl2ɁO߭\:O\5/n47iϖ-Pؒ$9/̬rC!4t.gw2ǥ-Sp}{c􎡢r"L9 !PsyM5af餝Q!R5[=5¨&:G5uz|= /J_6 %><;1Vah8"šL 7['|s&n,[B^#'.^c-ȝY!-lΞ^Ͱʚ5J{pbv -F࡯/Ki*yCRQ3|s$|{-m2` \\a&9^rJZrJ#y@t\ }68q@cb 7t~ 6_+qG!<ɬ4HOx“JHAvxK;`"D,FV߂Ǭ7]-a:mő~5ʸUɃBZr I onZJXwj"}Rs:ӂo*C]+ tr+Z㶾`D_"gfLi%lPٓG/kUӣٓ{ pLμ;;Ir Ĉ~7̒(Yʷ Qʥw0 %ZI_u~~T+@\iR]4(*2x1ryv4(!7(ZHItjt^ogB!gPQ0EJ:!n &=/@%>P3Y3K iD1c WEeO|>7ƈJ[x^g(Dn$+i_ =x/F:'3Dg* 5Fͺ>})8#zp6^ޟ$m XU·=LNj2y@ۉʃo:I]=A*8g-n{m\ܠk땶-`A?i~b+T5-96>G]bi Ʌ3Q>\ " [ùB0%=b\HZ;5E y HwX$%: el Aΰ0Kf[p'b1&#GkcPJ#0Ę-0ͭqQ1 &ΠddN B&-$=H !J6EUZ,Lah okv F8_洰xT"eIf5wQid,OeC%P~N@Gʪ}Xa˟A'쏔zc[xK"Aa+` L'@C;@4!g|F(j?LL:"sl1B`H~n\Kfp* dҮur3U{]{Yl2"^Э6Y;׃*YsA4?{Rٳt[XlˆIq-Y|.Py`apxRx()P,=I? HQf#<'X0oX*[LsR{OS'/ï}>o=V#YMzdzZ-53hZU=Bs Q?N=QgñG$M{1)}@nTu8mS|g^8gh=Y=1*6U$#NY*<2o0j,T}L +9 (tgtR8h G"<:pUɤ54S2g 3u>;n{bDGM n-^ Ǽ:bNQ*ĆZ7FBSx(_zk| a/Jx60ҙ3PD\7LW7rZhJ%lVibR7gVߵ;}h5i7!mI8n%R< 5p waPqSзom3i;뎉ΛNHٻ? BHgt}I'`c[''ɑw [f%~/U֦ovx w1vB i;o; w|g3i~u:#tqM qƝ kw+}طGPmhMc!瑼SJ/􇕆¥|ᗮ]E ǿV"R1DHxR7rEhe:^5up7ZXu<#O$^gW S2:n> endobj 8 0 obj <> endobj 9 0 obj <> /Contents 10 0 R >> endobj 10 0 obj <> stream x]ٲ6SJyg}T$8)WkI1J,ŋ/V.֊Pğ!)o|ס,KZT(C%YH\%t2|qݕ/Wpk8R.酿ե!.DzP„awq& {`Q_]gZ*U-Q1 _;#W|/"VkG|9"c/8UX.VL-[7ڟߓtͿf4,XqES~Zff;ܥJΊaCIw+TU=#^hP;;,7ʷ?1q9JSxlhy>$Ÿ Vc&3fx|p.SiBY R d+E&׵p<]୮:SkVw$~Ι\Ʌs5Ez PCZ_P0-ᔋ?<\x0~Ib6fıjzz3581{/>S֛sg˚~GaCHtD(Y]pApXgl. 7 g}ko,I=V-:jO D\(}1 Vgbl5;_Cm$u͇%|)2)>VP*bgv!00k# Iy-¦}l7~E } XmL`AxKhiGځ62Hc8˿0JaI'.{,/Yo%dЦH T~29$fwt]J\Ay'V( pCWl'jUPv">-/e{Y{ѱEmP1.T =8Q!MTوjr)NY R G'X`; vπmn+iUooQ.VɰV "Rlw!p kD‰J7@ Hi"U6f┬h 5QM(/L5Av0@i="ƾdMԒ⽗{ U_Sa_I/+j`TzTcRD} @Ȝxkfv@G2Ur)nsq) Y$ sf!M/H3q=} uT_G(`E׽] 7J\{l&송xQ ?^}AUԬy$o:[bQch <$`qӏyjŀ{UAr(g›'1d Hy{`S(musC@ťAZ_$$N8V@ʸ 4{`,Ά~sJQ\buJؕC/S]T3d cFk;f6??; cx]G>C wwÇT&Ä\Kx Ԋ }kdHeG^:K=0W'fƊTg/Z{L4lvr[{"ަ-rE}' vE5FʱgIM}z>!i)6+9ֹuTUk"lÈO*cEaRO3~4 , V-=$-R+u>ݹ?)ScrLI9_;9D>ޒF1I/ v!@v)

}?oc"e&)N&mB}Fɉ!Hj-K`% ܂KMȿdx `+` |D{ vKF,:vrm:i/a1lN4xHqdC?,nɇϑڠh[H'+8]0dq@ ~*߫;9FRͽ(oTZKf'y;;}He6Fȩ"V9[XԚX͟XM8cpb;`VCi[P '0U"+LEqH (e[HjH箇89FP;"ݞVlxWqH*#wp.ˠZ`l)Fm%Xpܷyq).eX\P5_{.N35t k?wegӝ;YkD%j+eRK-ش|:>sx[&ʍQB>4xh^b~މ5݅SpR]Y|Q '?˶ʆuDŨ3G̴FJQ}?``UoEA4wof9O58=~r Xh|;:on =lEKG;0eե-)ΰ ?ɉ5= "8+K9#8mhd .Q:݂]gϵ 5ޅ̜٤#~ ޳8f,g:@qŸF8vf\HcXfC 8Ԝ=rLsΚVmS)Sdef{~(4=9NnZoנWhE:w>xSح!mm¡& Uy?2[<c5/j-ۿ 9Pz1LŤJS=xsSI?e(ĔsCS*;&7aS, nE燇u'dfTam@j}ӝi=Ng2SE-5皚Keø6՚v l\V4]x j_3Cz~smNη"n)XUv .@N>yV`3yɓ,ytnbx9??%U<^*3cQh~!l^=o"\經-dv3|qȓ&f.nUxas2Dk!2,R8&WDŽ|Kk8lm]НA8Q;g eA9;ĉ.۬EW~2b}һH}zۧ2G0 8A Q!` #zg_1? 3{g9G0aCa=8{gLJʰ B%eTk{!KPa$:#O{:df{P7Ӊf;(5t8'S%x:xT*dX* ᧅȼ2*Nu[ZN h-Yt)yoU6ҎYi-m_>,T,4MZYgx#;7nNpEtE8%{= C)~m(2~niJΕ'2IqJoFs poХ#+E.ͮd)ʂ)*Us3D8EH^Xj) җ5]g)腈) }KGW}`%KA$;%[y|z5u\~L"S}__zybڮlob#1 oI!x/чz큃P mau'=@ʇuJmx*<%S],D \ a0UpS J:>ee2')Nv:}'*3yCEs&%e;(im:9Z&u4?$.u=Ox95V ZY`t_uq{X] |H(rC4ڻ;:eqJf~ӊm(2cV~<dXMU>m%<9O֜V<y,=,rk;V+d4OR,_|t◕D:ԁ%Tع8Kߥ=KB Wܔ)x8, ւE̮C%N@B"E` ~) >^PQ0?T D3W:_Y?xh┌l毳8O&]U#ODx*8$œnKJ't?Rx]o XXoVP>FÃA`=ܹļq0Mwr̊~ 0% I:>_j1ou#NXݓ4 @idS_T446Ƌu*Wfol3H8E$@x" y7mK`c호x80DcR ֎xÖafh{&'Nɘ^?jk:4(?QF&>)cCƆΪ'$Y0U^0G "1,~jzهyCv>%YSb(@@U޺c}"?t>"?"1k(=:.XD :sRx/ZRP^dZR k5ymΑ> endobj 13 0 obj <> endobj 14 0 obj <> /Contents 15 0 R >> endobj 15 0 obj <> stream x]ے+(V\~'_vS)vf(,@S4dY"@`>,BT˲./^`7ry ݣW_\GEoe4*~R¨e-Lr۵(RqW*Zi a%W_7j ZeY]otQZpka}qӾGZԫHRU%V ۵.lrm_T**+{TˋnPGVdS2}>Rk݂*B«..nAuAٺLU8 zݯEU8)A|߷ڮvFGTHK'Z8X[+Ëj7䏨_3%iH9v\l{l|M 馭Fet#-F[za 4sae#0ʍ2}Eb|GN$fP૯W*L!E} ?qDa/,֕ۏ݄ݸ3e߃NlVGlcZ)!(rH ~V AGn#j7Py%޾}Gz%_I[LD/upP+E`7s~ t WImjvwiw7X(Gl1=AzX'ĺ8&dנF7q܊!hhH^7-n;n^nq2 pLǃMXAaD iRchҍc1.zQhRfzyZ7LJ/J չD PL#D"D_r_a%~$"q *g sbN1,.Y\zNL*O"~i!{ﮍo:7P~KޯЌÛ:g_7+KӈxMuMSꬵqKgQ,W J>4p*hzק6 Y#(hewɉO0NTB Nã$"Vi]$Lp>Q:_N6hK&tcܱ?[Fz>2?u% J\g= 1"Ą;b9w_koEqw _^0/"ξ¶@‹IpnɃudքc|ˮe~\qp`nT>$4uE2v-MEYvƍeMw~XoC,oᰢ)yP+w1QuV\<dW0ğ PX w kI֒5Xʊڇeίޥs"]!/Vt]hظCFq~Nqsy17ݜ!` GVY_E:tQ^nRN7)A4.%v8|;<7 rĈ[η@GxKxt8CyzdT_'HsOϕI*oPPqr#ĴG<O{B $`5DSTJZp19fޯƿPwՎhFH0 [℠A B/'XF"8>Jtbb32`L>L~$0tRwBbArAJCTEeB B>1Ԍ#ޣ#,S@'fTI# /Bsl)w ]7E]'ob"!$zQT*6dIECtxH426!64$9A[̙]B09r-'ىP5`Haf<Լ1!y@ds+fe 2OJun| ^FT2 .rG,UL' 9#>Y="b<L4hy ]v38]MősR`Tx)U$%dPBٓ6'%Fb)C"9P!:C{njWI?>9gC1r< v ɣ`szHHt?Bg0=GzbYΘI$aYpVox uj5s;h%F EPEX `)XLw,J,Wg͎59WAא4kaS>ey5\!onT[SR$C571iighLK5dH}%υdO#iX(=5L|s8@Iv9FUzqt,TwBy}7TSz!P9,򹂗!"߆V#bSbJ}aܤ@eѡ0M[V׳B΍C#L lTםխQo=iLwshMZ/@,ܒiImu7\lnz|:>"l 2O\I!xe 9v "OWrxg .Lנ^pL>{2jU͗.e'دW@k8Ŏ]Va ל,u&&HA}'y&8)4<J|zlU˨eEGph*\2cqRVL+"YCpP˓jk8>zZ}9dž\;yF!Jcp㑍O}nrn؍# =?0ZpQ7d&/[xH.1_03Pƶ}b@ B]uYfj8?R*Hda\>7%?ٓiLRfA0H?\DZ:`:A9cH4`]d]X GU%:}kd"oqH3]:L!$kZDǺ"p6Ad%Zy,5vDZwlP3&8 %fi$tB8_yLjª$0*Ireq@0IFfZP+n[>0e09xSZiЃሽb"]1AhBt 8 $Lr)5a+"@hO:3/ ($ؿ,Vvg BCsec6 ,`{ {)v{LU-iKQͳi_,B>PNBj*E`r6)-̋-/8p=rʉ(|;,tpeלGh(c^u8ԋ v~>L0!7mY69ۍW֒c' X{K,ᘯ']y6{&RT&'M?@'͈uU+Gΰ|q>*%K#jRqYYR0v(L/ǛGidPasU; B ;;x{cTl#؎qbHPeQϙGy + fC5ɩ a'7S,ݪ;wy>S`Y;rDdC)3ہHFpT{HóѠCan66@UpCEgEଈiceS^_5N~jElD0N99OLDp\v%')`J30ԭD@χꢲD8Y4rG]h0RƧendstream endobj 16 0 obj 4484 endobj 17 0 obj <> endobj 18 0 obj <> endobj 20 0 obj <> endobj 21 0 obj <> endobj 232 0 obj <>stream xW}pT?܄Ҭ[HBHdwMcMH]ԲL$H!jU&mhU|KPt(Lv3BvXTcMRb[ۙ3ν~R! nl(.xE:E۹4wmG35{& {OsRq5mkԾӒHL+m,3͑OPE6w9);"Z 7vVزwnhիVq߶h)!i /G -G`bL‡0[? ~YMdo"! 5c#bIvҶ.6 ފx|d; oXwQ]plGYi%]4_;W qdcEg ;%7` a-vLv yXT͠ h߇ *wȰ >|RuK~RV~dXY>)[rA2 y]#9ELctT "4BX4QzCVoUm rq׈|9ՌŶf_}b*C! ZTXtӨ _n[9:>XcpK]F(,5f03HK^\i5ݪѨ'5ELY YEkĚȍҌcEByNC'S5q< V.[u3)j--n/(*2FS74ePBink{=D 3Zm\S\2YزL_aYm#`}M7_3{m`nv:>>=!y`~$zC4~:p,28m2tKN_x"=.jCTs sZUXHԘZr `]|l͟1aћIr1I\234O>Ȯ=\]J&< 9A zVS-FPU DP E$ Bύg  2E  g O' ϋ􌠽 eT&H$ b!~a*aapGR]r,bFQ;5^w5jUߢWWj4]BLrl-KԜZ%ih4zx*1`h@n~` bR*S6* @2,+0v8c ćpYã88B]WΔV8ąR9@jiHqK9xJooN[Tn<(vvV-vlq>限B/Te*ZV2I`Ҭϸ)gbΠSNQ甤IWDM|<ƏFS*Kpg3Q .CzԦkB&B' Vq[ aŸE?^gN p M_XV/9{po\KzO-f[u3k endstream endobj 22 0 obj <> /Contents 23 0 R >> endobj 23 0 obj <> stream x\Yr)oRa҇{!(&4ET#c9Q Hj?@cG./ԯrX8ys'7{}s^z7/i;n젩0[[=k9x954Pf lҵpVB9N?T 7M;Zq?Vղ36.C*~aZΛ~ Tj^KkEG}ejuT9)_@YK'f ;9;<=pN)쐜0pHϥTcVrj>BiYw%jL-^Kun'CQ7dB_U ,icK VXaؔY*ɶb,5ʻ~P!uK}Z _m ~RTZY~'$&mٶ[-ӾkBx: u3J4˃-ZJgn> hw}o:UbA8S눵o5P'XJ[dJYvN HCɄ<;R6a.~}.Ӓ̂}sSDpmdl;ȖdmR<N ZJz/x Au/tvC('qim /q_2HTZc@)pmX:0MZ<'Ue.{w΋"ku]3d2Y5MY+ųkrdp#NSiɝ7=Qr7G#8ehuMc)\3L9!S삧2~B.eZt3e,͐N=Gt;-S%9H;aXQcvZ/ar涕 עvʯ WeN_7 #!لTք=sp^q\WfkvXoX4uZ'NިB 9*_8 4LPU7M͐Pڬb!Fq5F* H70D/|@Hⰳ0,Q(ZFwA8EWq?) {L.RíJHx;p[[{22!]$ҶaXu8~ņh ¯ ۇEZ?u33&T6ύսsZ(8Ŗд6mlQa=BN}Nx\EI ו†YmgelTRq64 e=-ۡl+23'QC.:D';&v҇tZzR C"!^GJSF c6ǿ+a8@?ޒ9R% Sf 8``/`<8$np{,5d/P|%DЈ zKE 9'et\̀7iS4;`|=^0a 0 +rho@h52~xqHz y#3K/uY1 OHTgYŠ0"?*7((Rt5_q S`XYL_Fŷ0;Ӥ"t{v|1'vW '$X7߆N*jo"YzH+N nBg ]z7K4r6~#vLNؿnX[?]dh0P:Ǔ {ӴWkùh% "x2Z1OE6Ff>Y, 0IX'yE_jn[;VdAA؂}&0C0Ďy\LjgW =I8 b6C<(Nn{$QqMEFFz3Ph^qq.VzAj@.4 ctƺfSS75]~SţLX)JjOz9/YS\UK)#a05|Y{XԺ.ǐ(=!R:H27ˎ7X:q!cR@B:CVBEyLZ2ߗG7`a%5SOs  kԝ%ѓ?mm ޥ[ hEx4 s-Hz Km lt ௄3KZQ #"g 7ʲ#6,⑘T0Xs{?V!䑸)CmFGky6DNs+ʜL V[d;[g%~_-,µj`ǔ1dߥR,!KCD ^(֤:}CNz:4,,.'F&y0}feu<&J0xQ[ I녚脥.`VI_8ϼ-)O-s3yg\s^Hm2!ػ]KQjdKYaeZ_|w Xt{L^A穒1.ׅ%/yKC2}iI_L2߃w1Fu ).,~KThF4C^I"D}V,3QbiI< 眩[ Ήq*sae`ۺRУpɋ́ݛ S'UW|$t['7j3U^ d ΝOAKAg+qKk9ty Zt-IBֽ%s) L"kXHşRH7\ =RNۥ61"}yl?,?\#{C 4s :S^PRDb,1tbcigaRD#1@~p%v>eJj> !sŖz$\}(LtOWq$f;~2RtWFX4߶2#PLȋW)H0ݎ]FF֒JJr|Lp)q*4 NLJo#9>JXa_m[gđO\8.ޑv"Nb"r^p[Ϫeҕ;hXxnxg's6}̌h0L(en7o'Kb8D4F9d&cyJw9Iy [|*tM1GEѷ^Vuz]3G<웡5 q3Lv4n'Tv]4N&09ֶF?qU$ \?1rɗ7<#B 0F#);gӷ&5/ oKFeDb7c*T*$=lLFKC2 hA2LT,i %4К0ݟIE;N$pA=ӛvck!Mo+-lء IK)*X:g:RB!k(&Z9URfSjCm6LqX rmqLLeg|Qk F m% K+V#׎5;# =TZ1%[-لlWwIdcflJQ hwT[3|y,u:J)xz q'tRu~svUZ(ɮ+焔ٜd?u] m*y: /ӓO BSH_H0>RM|q/{|;#OI*Ϻ. }!Gh=S0..=qsNVܓSoKڔi "=QM9Ϟ$/]3u#Jmj7o*[ Ma> v'1sB3{Ӗ?m}+FfLhXN|usKo؍ƴbgc?Tpue3en隦א> endobj 27 0 obj <> endobj 28 0 obj <> /Contents 29 0 R >> endobj 29 0 obj <> stream x\Ys~ׯ78HsSv;JRTJ;ٕd7/ @7pF |jTw~iWw'~VBUR4fuxvЈM3ґY9ى[;x]*jQ!mot^amuXZCqzIZ3Y-P}hſF:pjB֕dOy ~W[u*\\juxrPuuRH2Y#k t4]iݴU ٵ$[R]5BX}lu`ih2TC6HSJ[J8 .S,\F'Xxi-,wG:p7؂й׍ֵHM8:B*^ydJ%~P,]ƺWtVi$K۟Q^k׮z_ea+>״{b(Ujok ic tVvu{zpYʌ|q{~^뜄iƲ{Q6Βz,(Cϲ\{5O-j,$ͧ~@+L|㬢gV6!&V<_$s NT u4e ={s?aúYk2 ØcnCxwbr8qQVi*uz}3n\X)kE״$58hO&L,/g`_Ų]9p:OʙqD+KVQA_t"ONFM!pXqR)ށDEq&*&Vz{3ډ? gpp';$56S\4u`@c5 IY hF,|`U)g<JC@<Ċ("&AM6: U`0+4S&"I()Y2N`Cr>Au!]-r^_enyI4l!`Lgͪ`158oc^ BGS fj+ۜPcbql%( WC>77ⲵ>E2([הg`JD'.k8^j{XT,WXHlNR^$AfbC-+sPqtopƘcQ^O樐7=e2kʪבϰpE5pʨmXgY:`b45.da-f[0Y`6iP/^c\?#XfE >1+ݮejxC2OJ7]N63bq8W[/frƁk:l~x.W=07$n do0@K#̦8ԁW}}37t{kفqm-t#+,ry8A#MrJ4}%DCc^tȃ5|78ePu<PlK I%G$/"NSE;6zBFvC;^ mQ^t܎A̾tbnTF&zhn/0r; .bZlE,C4Bp&)?'^]ZzVnXgsm~_e#$g*R*˳̸WyM%L%Wu4?͝# n8^{Vo,!Wm0 ~{wd{fp >%VSXΪ̢eCoC4O W=v-L;KدtfEli4p ^qfCACs\Sc$?aH1_vR,d,4wwg#ә:d^f7@WxVhϢq=&J C]7/^f/a']7$)N/kWnkpŤQd"l`יPlY)d_e̫< a`Aiڕ?5dc6XjFIf]lY܆)`y}Bj;; "džR(z>r1%f@|^eE(%9}b>2D:RVVjypiHmxl6Y8OՇzô.upAB&0*qxENS9{:O`Ϸ0#<ك )"o5:i4%( \%7wj&iǎ )DQ(MhdATDڲ7Kw͡Ǽo7eH=).3cG_&ϒ 9ӻ?#~‘N[>S8m>b ﰰc m=Koh6d(j%K>O-@h|ZjAd1G'@s],OodV`呍Gėь/3<ֶʉQYfCdO">@p=.y wR7x 'NӈܐBeuFeӑE h,q%NH ?;=oyOau>̤>ʀ؃ RB1 [\VLϘ,w6!:4[{T99|kg$dȬ\33b!}d;Ej:a Y:endstream endobj 30 0 obj 4638 endobj 31 0 obj <> endobj 32 0 obj <> endobj 33 0 obj <> /Contents 34 0 R >> endobj 34 0 obj <> stream x\Ys~ׯ[fR;q!8ljZ\Waid24HI63l%p4F׍Fs-JMQ\8:?|qqPe_'Wg5Zjƕ]T]t8:xR+`PnS7.\9彯Xe_.W*}h6ץ4tX*Σâl R%ז ---e|j7?90Qb1/ |%1jʫŸ ue&YUh0g#drU|zqj b/~$5JgXzב&Z>++Jj.UCUtUUKcn^S%+~y D4n8YGM԰_/+jg]1pE#XQ/26,&+ms?Y¢jN$uLHuEH`>VJnJREABz%HŜ1F9"1캓 <E2P+[3(6K&a]Kjڏґ;+-O(jhlE3]Lѵm`*F` oP-)Cѹ6ߑTDh$ՐN+?X, [ 귣$73%`h)K4TwNnPZGVҷQ b 5:dgQOe_/[UxaP(_ZxiXQys_cy8Vc=`\2jTiC~$ѕnh:`í%u4N=l=Gl?_=b$Z@b(@MuxsNSVib;غƉ]C=as "kܩRDFi;(_ۼ36ɉfBL@$T{ {ԭ83ݙ d;$TpTBepzJЏQ5Ȗߣ$u9f*_ a ދh*p|*`5uw_wpIp::k/Gv۵+7S`~ }h&ï Fwumpuʻ n_DS }ӻuu}+Le^ѻw|_m_""Nn*t<͞T8]o\]2tSHJa설a+z C=LNr|^Nx<`tW^xWtaKX<7x^bg|\IBmcs!;bg`:gZ6(A8|>U9CO=EoO882̑[Mu|욝xog@Q՛9WfN;k;Jm6)6ZJ߱Cj=򘡲*V3\>D 0gHݏ$͐"=B!4+>-etz Bkݧ)UΆD)5KJ5eg!X81H|tާ"]S@7 iI=!Iaiqم[{lv>W)/R1cv$U]g?/uę$^ Kņs{s&BtWebvZ}68';! I$,3Ql2][9sY]nnsJ u&GkOM/0RHkηHQ^"vbF֣x1se3ƣA> νΤ8b~ٺL Ґ#ce헖C8Gʊ`|4I;zU/^ $D 1c* (2xBQ ^jlHRm $ `j{ϡ<%r\å y#@wZR NH p'T'zMs$%ARVWc=w)!8e  `mwJb 3Z#r0o`)i7/ =%qDk8T'&]'ǧI`Suc[sI|&?g鉩K&2BM%%!ַ@J/#ڗ~~~of(ɚyT2a Iy|s/y?^|zVG=ݥo-*C{$ ,_b)6#Q60 QݘXa$D)I* 1;7mgC) y~IobNKgq:bNqc5xvte>lZ+43l灝MDQ[E?/38ȋʌOs<c"a_(2_dU&eZelONX 3,U ]x̝}T!>HavwLwpK4"k0 J3[J e]VЅ~8EO;A\h(xJO!ګ=?p[Ďqn8~kS"I S#9̆oF"zM|Uzw}JoLL|'|c^KU{?l*_kvjendstream endobj 35 0 obj 3242 endobj 36 0 obj <> endobj 37 0 obj <> endobj 38 0 obj <> /Contents 39 0 R >> endobj 39 0 obj <> stream xr}b߼ƒk!C)9R)"EJdbH&{i ЍAw\-#KK0Fc7Byo{GLFw^\O؈ʁ=FgI"f芰v62w`14?p`NOU?utk;,lTT5XL J(m;nޗhz`##!Э3ii*R d׻G>Pɮ"ʃ9mڍLE+𪼽M&42gaw7کO}T1"Fxo&|X' Oq = Ï3\k *NOқx,ocl1'%flC%SRvzZlD1X4哰tۄ gS]4yX$7%W)|?qΣ'mSQܒ0 ? folt?xq?ݻҰz1hhvp{)U6|kL >M'dhwc;APJU3#!4c%su1:t s> Z~X,\OA=F+1S2 uSˠ$ueN_9K]7Mɪ#VIkJL ' o\nI]kLUmKuSAb b[#&Cp4x2Vucȑ,&NYwHBU~mPYDb#rq01`Ge2$FloE*ȕKVrɗ&ۿtuzOROj|k[R>r\c"u.4Uϼ Y5K 9ad?m: } X<:Di{:/%G ҂ 1<(=U!yBVSׯ]Ɇri.AE'8'v~DaOh6kB҈>Š\_Yor"U$lטO؅;^D6UbPT\gKR_eˣQN/zErY>z5"L)HU)Kcpi76Vc pu֖6vYG4LvSc7vk[jZkMgp Ni0iJ #f}f£Xvߞ^=`q3g/U^ m.~q")oؚjГ=:Zk/ufE|{g/M:6ePp{xJxg6S4.ECS'-鷰 <~ ?@9[wEԁ޺f-6"KS7fJtM** {']-:8*0xEN2./8#?s?&n9=B OhQ2geULJ5熩;ًuk͆^4A-̀ƸVz^=B*bLBpCGЂfvkendstream endobj 40 0 obj 3624 endobj 41 0 obj <> endobj 42 0 obj <> endobj 43 0 obj <> /Contents 44 0 R >> endobj 44 0 obj <> stream x\[~?bsg 0EDh:zf\&!zR}TH|f!׋OWF'ͣ#)[a ƺ֙&HZ]._~-Tv,2ڴ{'+ ͅoVk $Y^Iߪh"dS?$dSK)m? #7+]~J˅U^6kojN˸:b=xZV2㒺bRg%umA.\Qҵ"kRQ.,?Y4|F P^̻27+)Z}ק|$>u_֡|[*JZՐbmuJf踼NÐ贤Ja[o ltZtm3s@?+}y ~tZV !L؉:.+&K1аBBta=l w캎T:w'3/IA>瘝KYI>cS/g?cDS^-_', OXNVљ1h2.SAi{sCZcemPxn]C_?,Q9j=PaPޠOأWze񚇓3A'|8|{V+:-QYoPjg)h| ԯ,5Qؚ?{i];[W0=L0"W?'rN. @vHs g.E$"fI=ʍ3!OF?49Ł=b!\7Y"|R>u}]DG[S&a`ιTU-OZ˥Sμ]#QTsCDQBR+[I?RfJ抑MlI(ak)]iӠrva@ZzFn9r8ׯEr4uidp~sXIٻfg<C-&{aRzՒ|IqcIrY 4-`L ffEqCavʷ$ϰIx :9ElJXxԨ܃nsYjװV0aVrH9i\"hMh{vSۊ#iq!6%$;WȄB=)5^c8 1OXajPmƨ @\/;9<>:dQEco+AyGoOh*1z(E `-_0ĉ8Ry{/z~p1fd >cvƮL;\a?tCi~B38ǿ,6ץ9"czHz0S((]|U쒵2b|I>yy@%[wR#|RjgἔH@:lc#oIZcVDjJ![HYsOՂw>GGMDfff|I2I#BgK+9Dc'qecݟN 4M4NJ|HARH^(C>DlLKH.QE*O _`^`wK$bST'S[v3:fN].3)lU}w'@rjZ3 ivtA )#Ą\MDСA}DɐO$͸"G ?%Rgl9K9̓iC;zdʅɟWb %)n,n#?i/]>3Z}ZF \툖cP9W{ɝ:q^s^ݬ}Q5=<&?N \sqbc92=XSȮ9zvI$<;Jh I~ y +\`pq'S7䉔}e0k{'v #Y,٣;Mv}26 tsMǿ9m-7X;)i3E7[8RDCm/ IG7 hCXYpEpݰ;EQL;= > O Tُ Á]lRs Š{3Cz/5%mAͨi=swR~Z)&q)?CW=ƼfN0O7ЫPm-z^6t6ln~br*VRK.c)-$ad'׈!x%wF:诣FJ>WUÔCew|S1F; s*7K'nQ<Tɬg!I;rü)W V8)2u:ݝeJĶ^.:(nYw/Cnu_1x6+}qb\؋+Fac>j$?Zf컙~!i_R*lL93L.J$hL|zAܰeg([a!^tֿV!KגɺefU $r&;@ yWB#>:qYdȖ48q 2jYċ0jѱQ;PV;gp3.ۭJNTPǎ>ۇR7HSw'й3^aԢu0n?cyD/7sgi@njc "^YׯjeЎ51+)xבDS&5>~3r8٥ŃdF)yGH7GUR_!B E*T i+Ayil6=Eb2 ŞgEKB0.bSS̽1bc q+qWSȥ c&9^x]T,eS|VD#t+:rq /&Ӈ여f{?@J)уQϢ&r7Dk㲼*$sIu]7^YT?XPBCJWA \}ZDbCq|T֎-&1;>PD^>-z &A:9 D@SP;6jaG)d_q˾7uYr@ܼ_b ayQ8#=ݮ:"c\ݧ'B4p;µJ旅j\s5#߱TLke_k۩*5"nR=k,,knsEO;4d'C=FȘ4i2$$g۽\_'XcN*d 7 e: \kRdtEC:Vf`6:j_ 3 m@ dȺ Y ĄBj0y}CyjT_Kq.샡/II9v7ߗYÊtn-btpϋnaJ\X όR2<0e_A{x*դ, VN DKٰ,:$iie1aqelL9ttY0<6߷o"endstream endobj 45 0 obj 4608 endobj 46 0 obj <> endobj 47 0 obj <> endobj 48 0 obj <> /Contents 49 0 R >> endobj 49 0 obj <> stream xn}bq߇<V|yN^&+yog*.AZ k=fڨ6:j"cuXb;6@B96/{l ^,Up_k[7t*ib=!p ƣnqԫ z}oq5=lucKx 踾8)kdxfny}\B`,@R^9C g=lE', xJAp GH_1֤9 A 5i7{ T /^H2j\Bb:4T7wtru|498zrWo'4$&16V٩m1cmsW HoC Zpa,~U1nP`H쳱VKe[dר.J'yU:a `;CK.3J6|Q>t:* $[V{Z%\kX4A^' 0 #]t F +I6KP&2o6@c+5ȉ݅JQĨ2 )c"aj zwXlEUiَP+Aʍ.ZO R ޑ~|}g}6aKL/UZ_?C޾<7 C"d? (b^!WƧO?t2AuWޖV':I7kYB^BV1=DdΪy"=@tFzlqZ-{l]MڪX$7Ht(C!2x\Rp{(}-<ɶGzvl!}<̔x#UMYUJnuuDD22O8l4T&eT<}3=afC͐glJN2:*s ᓜQűNzݲY/2ñO6CK?c74יʙU^aP$hc|mH9Xrj`އ&:K: QD[R%s.Vq+Lj#> : 0(5_2{xHtG<1fY^hS^ ]ke}N!|eJ` .j"jWXqa"k2}!4I4\%Aƻ{bĐ }=Q?+dRH F,ObzCh>/27Đ*BYTԣlݘ 9E+n]Νbo,'3G!"6kߦ?SF-|{&qBjC68ҥTLK FltÃ| eD'P[=>)*}ʽt&\w)Z< O8byƾgc ɦ{Զ"3z$8\ *6ה 蹒&4w)E=ZP{d1Dμc|+YI: ѯaQ9F/¬0$})/؎10\L<S1-8'v '˽A?}̡:X>OQ'y_x_s:ҫ~ y1. Ki֍MKe+RZuFt UzC-]wY ,Ř4Xןo*CUI'\ 襾+ d#iኄn4L@唛E_ k@R%WZ̥CevTـUz+r]@Hd' c=U9֣e^r}U"%h->ՃMpNu6SКPjv" Y_-ogJ'%eu(iq nu@ Eu Db]?t:] kw m/H418.HeINQBu`}'v L'{vp!ˊ9U?\pf -w‹Z&0T@X* r?(z8u>0JRC[1-q>tr8Ta?Kp'[9+2| _G{HwTp ţxU.{s/)ɗrwK[zHL@=$L;+9uZet qE< OR.:a2KMc%ЪO; ,i`X90[ַ(C꘹TYZ9*, Rvz}ѿ_ҋ63+F0_ 4.J;Th&?R`; o cvr{'L/p%Xçէj9!RjtmoX!‘C}R=Xg˃=wqe6kEpϤs:Y/8pk{L@n^!Qq*;fGTE8YU͒KGAb\`>X:S: EOޜeo-t+-)c f-׸םס66}[LN*~Y=95Z\0qMX>+`)wd0zEfVi*?qr[I(6c#A-V_}#3XׁdQ(g:]Fe6u-X\} h\db.h2%Oˇ+*mWr%N} غƕ)C+f:XJX3z':z:x`\H崽 dP(LLe[`.(T9Sr ?'}ջ[H?.3 Pѹ7'wu]4 $*a*.⢚Mdyf'jJ}3` ,`9kb> n˟7p:[.1IF ݟQJUMEu>єIχz>|MO0V4n0~62tK_F|%?JTw|%C=op|M&L?[}a*sV^mbgOC  4Qr]DJt"/cd5:eҐ-mM544Z&&e15g9" KcKeM@xGe]?> endobj 52 0 obj <> endobj 53 0 obj <> /Contents 54 0 R >> endobj 54 0 obj <> stream x]ݶO!9*I-i"IdۢHc='w"yN$%Cq(haX/3H%ٯP7g]ge/_6OmUݐ5ƨ?q} Jh"[!g_~?t-=Km?I+f ZQ-vO•-VtO%SKX3ߓ pX<˽"`SwZq8Ќ|t~'K?m$礕!F7gW0<~M@-phXD1/,<{M\' ʷOs--x~QƏce%Uщ]Jv<-pGvF?n)e2 |t]}.C4!׀PL}U&A#b|Y@dx,+YsI.3. h lf. [\@<$"P#Tt7}Axw?̨׻Jp iCK<] &: kgS132T8ϰ!6/mǜZ?rqG|G%D M+0h?f聩+DOAF6X\RL.'X'TE01kl=~Hk@^s_Sz1X"D78M~*.oVIK5* L̓O%t;##Z <!^ƥl&tv5mwqD5B[{[[=,@_:S֖ )  j,6`?@o\3ƾ186S'qlr:FE(ԜKXNkbF-} 7PnߵVqRtf~ \{ι8UzCN}6H/{'Z[y8$<~}y`35f8`A!}|Dl#'XY%Z|)>NgB4 7gRIا1 "}lD}.90 9iDc6 KȾg+`G5auD2A2!CXQ)O B3TC.bMSUBI q`(HDko< l6}$́rIk8}c O1ǭ'"/@ufŢ>7i9幟̈́'فҙ|@aĎje|Xy,8b> !ӍG]%=D4_΀YUsG Qo0Kb`DG)fxPva##ZM6Kr(up"u+ԏ$FC<|H f&_L唘W3Fxph~Ӿy0|P'v7<6"t>7;4 {0d1 J;y{@ܕ>EP BC[r[< w5?xrvUާ-'*`mjbFGoc6b*2>qM!s@6"#%2P }^,f3hdzi,)`4)`,9A*^W7L8)߁-  3ͬ&7ՂhYbef"zcgK(BX3froպ(}a~] Yf! Y5Am8%K}P}A{d$Dd†9^-P w`s: ٴMoZӅ߹9ȍr{ DRٽt3'oqK,d2\rx+g:GS*nt*q lv1\N-/ZF7gP&CD/ϼluF?&뵿<#3NݒŚ]uqY_-\$M f;Ǡѩs1E-RqogBh xVRJEj"X#\B(t"Q@ngmoHb]7g\m CNWxi\Id;(O>]^&oReG9^/2-5$<j;Dmz@e*,q`e (;L(7ڏwx@2wYVHsN`$cZA*\1ĥ/B*e>VXD_58SbѲYu*Mb٧RGb5jWB$W4e(rE3reg/{b boBC}A] SCʹ9ot:pL!ߜ ^6i"Hz $"iaw-߁ dEaiݵ\nT{:F;k!I 6$Yk_W̌(e4z\lHhJ0 6Y|=Ͷ6NsQ!\w Y2)c`C]N"(DL`E`Cl-7`C;4Ry\̘a`CqQ4{ (ؐfe_, 6Yx aX ,ؐf[A|SԏlA5|3'S{),s :QR!ZI7UJ68AkFJɆtGh%ݘ/T)ِ $*% tc>QdCSn/lH7pVҍF ZRAZI6#UJ6$8I+JɆtGi%ݘT)ِn,*% m\ !{nC࿯we}©hA:Hc^řf,t%0hIYꥯr} >_Vs;endstream endobj 55 0 obj 5097 endobj 56 0 obj <> endobj 57 0 obj <> endobj 58 0 obj <> /Contents 59 0 R >> endobj 59 0 obj <> stream x\Y~_ӌi7oҀb1lX> ^*Jvec{)d83+oHEvW'痦ko:7>_>yj7yee[>|~|~Cmú9g$ #XtecnnNW?v-׆)?koXnfZqMSwiLl2C +-pֿIwa)qƫϲYcpϓW\ lslF)GnPl+ޣ] fЈ | ΕcyCu_6eLuj~GRiBPO49y؊xoa`b7[;`NOш QMO87^}9Y~N s/L*ZAC܎h[Ck˒ZG8WڿAhʍYP#m/>ɫ̼i)ܦ==DI3J$fp??H K$k= ]{d ң{Vy=|tX`} &/reFᔒC)zΣT>y@SYu_F^9 B麮2Nj(Н;g Ѿ/f/KuP4G _Ob?%FR Ť5HCĨ3ߒ\UPP(HʰoBK>d{@g !J?TЈVc$2ל~a3Blb?ʍ:5@ji[cl@j;X_WnZuk##Yƾ:Oo0VC!C Jܜ}Y}yHnz8Y)ZcM6+# gNCcVz\S]Djk<v?*vwp'yִgXٟ,(7a^Mz "4a!AJܳV˿0  8̛mI[kof\V#G ^v{xj$ h/a`ÌHf6y`,R([4cj!.XЎOՙCEL;#M) YI9# *zW]_]p-t1=;s {Gr9!mvgW܆RXW8bP}(yB.iL{#`(ђd$سgr %& M̻?fLcx >+cZOD /(6I4usdL9K^?\-J )W5@ygt4$; d4JI,Js*CoEϪ&cW@h\@WhٴBK0$ݜqW e[J=Q6GcKp'Xƀ(qh|rTΥR^e-^`X 21Gp Ou](C/FR )6n"p"GIZ4m>^꣍Pț8u>o"$KA20F5VH}*]F QP9UM:dO`匋聎.T3;3w%wP hV8hhSDI%ǒ#95'[8cD?l;"a˒KF2UГ>ӌ!,JE[>"Ǣ$! nut$švf*9xZlkg&5u2cQ)`:*)-jN o4bobs4qxʦ8}7ŽQ R^P Ro+/X1fM{T3eAIّ| 7 93!<Ȝ‚" .&>ЕrBtKp_ RAFTV${)~8KcW0P=5=#0Ccr8 XkHsrFPlJM/ѡ%)J/񥃢,wH{jswU栂an&,I{3+X= >y72"f/@l$rBY+mTÍ/2a?^ ~FeuF޴R7%z|ΣE&PF*0ӓ'+ʵJI&߂K7LocYq]m<|N/kar]kb =!JY!,8 NwD*1C?q"3w v#ԝ'3yr>~(: t..=Syytkx(X ^SR2b9ɗ|"RÝ)Xv88iιlUu hڀ^^soNjKWC6:暅2erUQ)'p§ U#;'am7uVE>h:d T,x;{CGֵ 2a2^֬uYJ҅Nt-IǒnO$]wǒnO$]cIuҧ]K.:Ӯ%IG>Ztv:Ӯ%ISu-HG)Vk]KnӺ6%$]+M%IWNJ)kSkIbڔZt>_Lw{ܚi/C槢l2fo:.Fw6ϯ?_ѝ/?(tb_@(E 4&a㟤J?~GGkQ{: Vx7["6"? Qnendstream endobj 60 0 obj 4072 endobj 61 0 obj <> endobj 62 0 obj <> endobj 63 0 obj <> /Contents 64 0 R >> endobj 64 0 obj <> stream x=ko$q+[fU_l^>پ3bsFD;ImX^HvUd1! U,+W ۏ'_}WO'Ǔۏ߽M+c^:%)oVjYSf?lN7)~MklݰJ閛ͩښjM-1\TWImqäft#pI]3^,n89v=7x'\ :T_3ԛ(ո_}/}=lv5<9uKUf+(ծaX6LH^ݩí7v|Y׼YA^)V"]W`PcL~wQJgvVւ㖷]Qd25]Ъ4lvZnhgTYl}'RU=E* ˓`oewZo\n_nl'+ %Ʋ{n#c {h\}GVmnfnLj4{FZVFJr飧DjtOSi&Mi4ZjJ{-bw[mA-tUB1w#'0 ^W hLޱ8MDz⭟1^B3L?⺇9u+4}XV"`%| "cQp>"_e+)('"|P6VvbkXQqmqg-b$]w0[@РOy-Iu4jtCT!B2VYIPsY-p3o =ew@i]; #d<7jRҗec>˚hP}Q8SZϬb͗SNsìi eT$VW7踝9s2u'Cuaˏ0v4~7ɼa%{drDCG+ dU,#ޏ:FzGF~ﭴ`#7$YU67t-m|H+LdֈkLLq3>C8gӕjռWDc._YV3~*L3#+-2ՅWT .A2}ݠ-q<;YJKO`S3V>M'4 9dqڞ@[my,,{ *H?@"hYX٢BKl"$ <-΋dBI֔,!cr KZ֞һ&rDqq Z2PƠ LoIL.[[22țAA A&X9RX<36hPT-荺 K҄ӱR6+=q%O`eY$=el>ʜa~_Ъ=i]YManmG)f҆)k /祼u)h:Kqn=CP0YrBc5@>CF:%JeW󂓰rByTΥo( Ў1M 2Hlg5kj2Cg5[ԁfN[f:??o!Q{ױcYzDlϫoy:X!CnZ챦D3#h_>SCk: l;6]1`C@jÖeP,~m Z* ˥CJ hF]"W, ˳Y"o+秫=(y袒7 %xL:DBYNG yheOqs`B'!8=^ǟ]%S-k=*0`2ohPf6]BWqOE{+oC֬syD ATj4iP*yUNOA1CBR0ŸV`u9`"^P Lbؼ(+a(PT l;I0zNT+bױv? BLўa;/M@2/@%| <Uav9)ZMI)-2SI-c7@YeE]^rdD(|u~ů)R}N9&o<~e,ˣn1<x%uoaQ)Kؖ:=;^gٴfK3kai+O9WMjਹ/n=VO0sa~3)`pGwlr'cy`V'^k( @,XO<2'w]AѱdHAAe%)WaEql#7Cr̹hK(k0] ɢoyP4 NwDrdC8Q )G_<"i:=eF2ԕE@а`OC1EI(;i mL*ٛA]/D U8>nXe0ѽ0I"݅t)&pC A&9'(d/Bp5A\rKYwԴ]NM}Je x`%Sk孱tTkfj`Ev./yNn#%MKWR%"+D`҉0 *-띥4HYG%`1Ԟ^e:e@A\Ga(oɘi'h|BG_ t;| 7 SU'K lE~o9]BR 'eݺ8lCyKxXRH LAG]J8x׆7vg-ͩh+a@%\ܠΒ_'ha$D5pOD.H -Di TY=bHloSu،M^{b-NZ6VHhi]>V>OvrUe<~sdR=*)O;H$}rWQG84823-;0sXږh(!FTs*=ﯫhl !#^E^z+*Z9METUԳyq) k_K2FVa[T"[J7C_2&VP]: (}44ĸK\0v3'¶]kN?2ѱ(n4M(?-]pCi$8d{/yd4 ((/Ŕc[$KbI0ԗSQqi &eC.dETu,:l^K#dv9FBыc6MeG~.rMxtF SG%:狩Z0U9G m[Dj090g܎ 6IhN "S ^Z"iqm^B:2NN`Hsye{[M1;W<~ }1Cz.eIFxM a.,\0 wkbWՌ#Q|\wcɵՠtymDWj,J)s[|1H{b Ee]G_H?py18_^'!i9fb!.ĢI.H;[>L2eJ,Wy1rH {Sɢ lȐW \^iaGB2<5r[Rc8H%C.L.ߡxs5^i|wʇ~\#ᒋoTL(d: *_*ӟ$yzf9JBaJ47إ 3.IP1JrksZ[+顟@|B~ ųr}ihF~3P mk5'.KEF([&EnJLjLcdB}?pVƯ)Wٶ+>:^CH܊4˂b)~.(}{IY > 5(DibKE}X-'V wh8T 8ǯހweJ$,B]") ygi:IvOYpIxv|å$k*0Fˍč%o8m7R>J x 3"y| =*{JBz BB~#~'L; H߫j=e[E}Sw>l4Z|0]>.ѽIKz"ߚWkraAmDoxCv,z˛2 ,B>F6!BQzeg-w_|/rK,0iA]hC˜t_`Ϛ@_bFv5pAlo T~- 1xR<7$82!f 7&{$wC$0pHZ6X87*UBD%-4!ESoϊY_HM8eĬm~g <%_(pz|,wG :d,;rHa2(0zp$zd_ '0 %I=e0Pk½(}'y3t.3 F(v3*&x\C#gNmU=׉"ItBC,Exwxn<73!GKt=NGMEp( fa1Bޟ\ɂRli)]B7ɮ(JoN^^}$cle3 ayv[EVjB(r5@͋PG%@Z [4jM]^i"1YT7X&_'m;b@L A0WLE!&tߟ%1eMWqy:!Ъ+QFV𤯲7H Q0KAeqzi+E43t ÒXOg#adGTzW6E& MXP @LO'v\tME 4=Dyw܇> endobj 67 0 obj <> endobj 68 0 obj <> /Contents 69 0 R >> endobj 69 0 obj <> stream xvܶ]_xO6nIGGV[Yrd)McH%)Z` ܁qQo8}}᷶z^t#*^ilut~~)҆U9n6/֛ cqVf3mhVO\-^}Z_l!vz zBFƍI-\U7Y!dtmџjMMQyMk{~-RqVDZdV1)p&n8`<_UMSYq_TҨkʫ5)F0yVwijF[|麞K=oaG¡a_~;${EscEV̶]Ik$B6@īlRt_x!<0^';V6n>M1ޒ53uKqw?#yJ:^3'I HҐK+N_;^~y>{ û!`}Fe;P-$ EZxK]wF%o%H‡g4iщxN 1Z![帵%H֙wd ]o*}]9r@fIp|AŸmjkzqeY;L 2zB[@i'AhE'dل=؀4Ɩ>RoI[2\Ȩ{dsd!a Re++M &d,̭q,c (`<1]a4ύuV+;Zf_PC?{`#׆k/)Cq<:ҴD [=u~DGDBT@ rNAm"@ 'Eߗqo[0o\'ozf7 >t2 !,°pn:FhV $Ы 比g78#x<w } T̐mZ? `Q!4E Ğ!!8I0H227xBP}bAD .%qN|'Dڠ;xOrͦ%Csl$)+H/7#Xt6i  36en}Q-vaWѹo nqFCDI^d4)|5]S0iN$G1dVm|!}!̫?R}4Eȍq5s #o~R*=cxy;$^% ֈOL[]T !E< 3oPvLK]L܃}eZtekk%G%rÅrp7Iu81w>%0dbIj,TiXOؐ3[K _ y =  kS$Y m=şJN+9e$w'P- G -?˸"qs:E3Yq Q DdɗOcvO j2حY4/Kʾ`̜tSM qQpn<\N&ɟ>Z@E;>cYǩa!6iRu[>OSn'B|]0'b89gkj4QBtI8̐zÖoYr1_PscYlS^~S'NbJWɞDEpj%u[h([q/?*~jMZX _{Bf :ZJXSs0+4xO[XA@N7L֫6!qv.4_uAAM\ 05V]л1mkZ1axZ}ig{ɑ2ИZ+ y9vȻK7uκV0kAAc8٬ bvd^m*s3(ӊTdOJ$\#;_+ 8T޵>*F'@h,-L|~.E)7ӀdА8)fZfgx1(·,r;ac_ QpkJUz~l/~9-8t{=41^׎\ ٚYADIUaEe澦QpqFr)l*X00YR 9fT>DWyiY'ME9A#jMB:yA'"ht[$S%SB)>L5q'%X(09^fkf$׭ApB&:GZ7+dJ,˲T"ZH;_/Xtorå 4:n}w`9!oT.-}KGYwuÎn!!Vv(/X[֘(a%{sD{˲ڢiILJ*<+ p+/i%V՚5SaXھ(TБJ jŸxdYhR޴<1Hc1 JJ+7."> xŶ\Mh Db<# zw\r˽@h/a.M.fI,*3 H+ygթ.չp՜wOsS]u;ƟW)3oҥX|Hss(ӳqTġO K&J^Sz"ku 9g'%{bC`ߒp1{Þ]P0;X>|0-|WR0v/ M f޼"ǿ&a),R5wRDHDɳ Pyf|.L'zm8l ;[Z,J? h2JڏIByuBl 'ohgv|4֓ IU`=Fu]b]٠Wk/کA_`1:J_FBm&[{np0xm/t@9V'7A~=9t%.{6Ag0ӔJd!~DZٔz%AQ-ꋚeM[> f[j-?Fe9oyx#4^OXqTk n[- PvF=L袷/:,WA-QG"FX\'b5Lz ͈-h03nQuWAc8Ӿ#[~Yy<"|`+13@{`YmQ [<ڕl>Ϋ߶z)gAs9 CsdD7x>_W!2ƣ_f\l(BQ$dP88 n3:y=a2!`]D)-endstream endobj 70 0 obj 3945 endobj 71 0 obj <> endobj 72 0 obj <> endobj 73 0 obj <> /Contents 74 0 R >> endobj 74 0 obj <> stream x\ms_ɗrx?['St2NF#EzI.p`[4ԙ_`Xఋ}yvMI?ɫzIS ɗ0֚ߒRltaRzr~sbl^W\k*J\UM5};S_gsSY&|;chH?|/C#gg_t%sGDejay qE{f]Y>3 @jztiY~Z8ǙvWO>rig3:4tW>b+Eͺsvw{E%pb5nx/Ʀ)/D7 1(z5{b:[v/[ۚ{+` xBB$.)M A!v-LJ.޺wPne2ƒsBV0$z"=Wq0U{@A-k>*цhaA$4PQ)ƽ g]k#~Q]c@#hĜNg9WUE{vrzuU>#U>4t^#9tr' WLi3™=l;'w .hf;LPԷ"ęAjZo :1ϙH>m[ˈ \&b3A}04h8/q{ l>^0m>Zim2O dva,!%u:&`H Y<I@tJ낲B6&(8kҀv t#tS47 p>?D>Qp+m@6뉙.O^%w6 `uuiaNmVRc&$;DH:s+6<z7K_b ^TcEl Ch(:"36 G =C.5 tO QwxqαO"Ǎ݀!jóќ*ϲEz]>>^nuD=J:gi{RI|5֑5\g]F=Z1ZchD23Cq)D xE %yYf tK (F)ŀWjK0Q HgA.n\1nS~@J`%ۤ߶&ZKI=c+=Ht|PECh|hxc䵵ZD˘[Sul= ]bb3ͤK~ "*Ԟؽ"#|ٹRȌ\j`/! QϣAjE2͋%R(ȓϐʼp'Bjz tVNvp#x|zobO[&욶HM xxu3uE:R2M[} h R8)6Jղ)އԋֻ —UAE+Y;mGUSI~,uwIWo[!g54]ܽ[͞8 (oT^X(]h~gύmm{+ MCJNa1TN"n ,Gw,&e'gFj}r(^i %Aސ'R]~˯t2SNbzP84LC?NA}zIz3eGw1Awۿ)!L50@FTzcw./3f΁)a 4}pvwS{wz8k=(dPVi26BC[ߜplš@l<\l;[%LPl(CWi7LU\3iV]>,WE%g~Png-JVե/J#e4YyYGy??kEEw2ؿ*A`&E.n }p@Y(*0֙{3T7Qendstream endobj 75 0 obj 4094 endobj 76 0 obj <> endobj 77 0 obj <> endobj 78 0 obj <> /Contents 79 0 R >> endobj 79 0 obj <> stream x]Ys~ݔv48Sr\Rd<),/ `4=Ŗb@ 4;g/Ng٫L'?.\>9fZgg$-lP3T7w˾JAϿpee o p;XH._-Lj[|0Lp?{JZ \t= 'e{x,CwoM( ?t혰|v3DM-ATK. cI.՜v>8Y,yr~^-D' `ky6J`̈0 E;=ԅ[Bi^ۅbH Q \>E CAM_a`wy$Zxt7H'~EvigJE%$t< F*yi 2Q.k2v" $K S`). CV ]EL[2j` T%q:I8ӹ{M㱱wu|q%o 2K`3wx}?7`|:O}͋5|,i xT1bC?U]"[%՝V+F~E =O6̽=7Y&[S2˱ҺȞs)'rfB83*z + Ϩԯandkϑ6vdbǒ0V9+4Z2|?jngn%7ye@\aI!;^RT􉳫0A|3 DwּÐdXNHG=C7/glJ %VEmPG1ь+DMj~:r5}iP/sep{Ic 5Pt{8vPL%t`40Vťp.}FÃAh@ z 3;=8k7:c`*L6JߎeW%jQ]Gi5R1ӅAy==j;="޼͹ &at֐S^VY.dS9_f=z)&a] 09Þ$ L "ca/`*~nWcBOROs~m/kgh,#a~ mҼ:8-88wxc(\EVV'̜Hs╦ܞ;n&OCNaUڶUڶaӰM 8ocdo@=T&F^,v@4`8S0l뺱wɡQDF;D X83h}iGFX=V?]赥6n ^x,!*/Yb> ]&'VjO8m\&[ѼܲAack󤟱sH7W}xmI PENbG!!ײ.MAAǠ^m(#p`y'(t6C!F1yE+ݣ)sk,4*YbXjrhd *Mavz:U\H&:3^E1z;g) r0*օ.{M/CَU+>҃i|Ђ{cu*]=1p]Qxk\º~I֕xEO}>g75ʅz yxG҅ )#DABc* %#}HWj>KD4(}~̕68-hB>$8qrӐ5b5n-6@G18m׏!?_P?]nu^xd?Ho2 *+oi!F?8 Ӎĭ%y* 8j8q:;s gVd29P٢:~s~ fIAY0;yr1 㨀Vm'P .{p } ɬy(2.A4a Uʄc4;lJrtX8zz0p WTON96ZȥSrӨU8Tr竴?;- c| \,نǚ$ukW:`]6L;յP3\x i#nbOq6㉳^(T$} D`HM5`KSIԏc Z!\@;DמvRИBػ".~x?.3,j$a(@GaNWaFZy[!O>1w8(ÜGb^il^PekuZ[Fb Egc ǾAƹRĒ҆7J.[2U \TG&5鄦/&GQMeEbjei\ sz֞7|CyxkFZJF߶b\H|y=i]YߊM5Yq-w~`<AhH8pٗ^$ܧ-_JtG V}˝FRKb\kONP[6E! A<ӻτA?lIj2x|FǓovz0!U0i1ݏDx^MbuY'LHށ-uSs!܄;UUyv( 1&Bx-:WeJ[A]L }2tO$e aR<ՁNL=g1|ڀKegh 'Wy!~)?r-E 3+ymf)W/i.smI_X~R#Ro1aN!;l n|`=$m< unvz&u;ND}ƄPcub<}޿+r@0L`acشk99 GEPPf&XA-|~!ŠUf:GT X۫HQbJ3EaKI9]NUn|9b왑SdCy[Wj JܼuWi7YSk.-&ݺ7nZp ihy NB c)x~}tA$y GmmT'l~+/cRowaƂMj[\2QI>kR% $;[A@B؜'Pٸ.Puk3- KB˚4ei0wD7E Zk[|5ލ%JH.}P6j^ #'tEYåkTʸx6u7jI:|0 ^* oRz|)MP{Н+.FOfGA^QV\Qx$޷w=;,34bK 06ٙJZYc> endobj 82 0 obj <> endobj 83 0 obj <> /Contents 84 0 R >> endobj 84 0 obj <> stream x]nS&xl)4A]KErmK%%q_Wr8$?r8stALx7}) !7}9zuyqwTn^}g l>=](zMt)fs㲐M+vnV;Qm'q]t|_kE/TG;q'f`? 3Mf+r/췲EG;slJ?I7mܜ uczmv< }N6+8N_uB٨s4;kK=P pn\n-\ ϗΛROpIp ?-up߰ח gD)L~ VǾϷ055]VjR/kJ 㴢‘#/QHalHZuѥQeU2}WG"nq'~J}=o]0[unM NwEu]\MVvm;v~ѳ5}߯=tU_kݗ}5@TEYtXum 9֯ 0<.@z}5 /<4=vǕfTjW y=y!;| ƣ@I p{à1o-ʦr`29kn yXmJݞ t`|R2 0}lF{՟0A[H^Xj-k AHa_715@QTmgYoh-@n( 6LiL7J)%"B s4= 7)^iϑ*Z孩F?; Xv쎥wD+bB=/ps=3Q(!g329yRZatU+Q1((f3z3d;f*4& o5U]Jܚ*sٷ-(hAjX4ct:俅qmk0 d׶BfPs' 3uI(WK> ؎i~P`(s,=!(m &)m ߈kaFU8@NI"$O=3*t .2uUB Ptdy2Xou1ag\n_6v06p߅T&FcG&0 ,Fk&hoD^o}FGɪ&;H]"(xJ7۞׎'ĜHGoֻЄB(B< Y9/*A(N?ך\1WKHK&4;7axS68>' ISBVD'hbkw"<0/GB}lQP_asڝ =^D^|!B:MnfƲ:TF tuOXld׌b%dezWˤ00~Zi"GkXey3Ϗ~gbWtw*־pV4&ʁ;eg|o>86P곩 |qw.W*m93 acv>A7ޏڒtiJ{J^{ cM}Ȁ?9C9O1J,0IEx9׳s%iשؑO{ͼ0?J&Qw\'1R6rͧ~28`Y[aO@3q r/&g]/"@xۭE ʒ9~;Q]o>>Hts.?VΈҝ9L'v2 7=PnށM8E“F(%@;x.B8-zyQ8h+?R[ȇOJq.0.XY Fޫ$*g9ci?>)) \肦*˶skѾ"͕#y" $_hHZ-S N73 GWH\~㣨/XXfVB%s („4wHvE^Wǰ{Y5uݙ#D߁Q{B]S]ݪt3I}ڊ[K}^w|wn4l_((A0wS-qUTzkWVqÂWu\OUx$rMWV Gmvm)Q{ <ݞIH]Th=Ž`+Ԡwj$\hmzf0_trf ulhWifOvr{d#ʍ}MUin^]$]1?eK|W<"0+/ ͊DƖ̇*{6$72ΔxTʻE1k\턯 4!;7C/S~e)Qs)D98̭ 7Ąs_WJUwھp;SrTPtZzOɔn 6J07n91X;״`R ~ՕhpDf[| 镞8MZY_*y12z%Oe<#6Z͓h-ͲF46~:aMDy{`UvI ֟*UC+BkTT:h=.a `$A6@12I+01{GLL䇕1BYň~MH$]0l ]S ^ĿCˌ!4D_d`Ef,L:ԣ%pw踬`&BM.0S0uQ \/4GC,͉L"_>p_nDw/gJ6Vm#QQ rbFTU(RjMʂa:YK`Y?: 2u=[6E#4Ym FO`/K(3HVG2p}enhA=KI';5*֤֘L}HzIF6GYfn˸&!ITE& t7JUd)DaD3Ea4i ضHrhKLҖF,:ыI 9vcaDՒL\HT<|:6`Mb,dg'H |h݃riF zX  <0(g$:1s K ̆to@zwEK'wnЂvL.6-չٖ%%|akK3FaGХ]A%~,#lؗU)i^J}k]P7XWn=:BI&G&[e~8YDbq3OhJ1D杻2W^u<}S6|zM b^:^{{w L$G#YP84v}SET'}Lj,pc^#aעyjŮޞv6]xӪN9hQ1CtoJg&|_3Q3&\0S\#E^Ř/6o/q.#ՓF -(e;_x{۫@R!M=wQ~\24_UZ) Evo&di5hxRA̧w.ˈlɝCHuhS@fHZsX`MդlR+i_nk+A8F0ŒY\qӑ1"11Yv& Uk(K<9dN$s`$\'xɄ8q؟'/GΠCvm&L}ld#Qs# ipxe&J7ϕp?#!b̽ 0q^bTγ1XUГβFZ ^LqDHٍ k%{syMk=2SNב:4<_e?#2tXxt7 \2n]Dhf߇%yTD4@vQG9tE=As|C)(1T*aS2NXjid *YRJ>HS|҉Nd d-iי Y cM]#ņOrcK՗̨2o.IfMg(g֎z^@BCC_m gBTCF,N  W+' ~Y(QŮ0xnқ W?Pf`׺Mc#Oa HV5M~KË;)%n(R7+x^2+ȋ&б_-2v/. „/r&y8@N~F0MT{$ z9)`1)Nr7Qz!݁zO#8"4\BE_P-h?#1MCͤȭcf޸n0"뜼 'R^7d,{>Rsb}㦞)A c&|OF\){ שɂ"8ڡC|L%N, M3y)*\ٹp&"+/Gcݦ}ФydلB?ƭ8?}˕1ia;#Ցs$oF@ )R̥HHX L: {Eκ_c {S L~*Pw MG SXo> endobj 87 0 obj <> endobj 88 0 obj <> /Contents 89 0 R >> endobj 89 0 obj <> stream x][s嶑~ׯ8嗜q;dwgIG[8H֚Hc v wt}]1|x{wޝvgݱޞ')e(vJwZ,ӝһ'6LWYAI딱|ؿ>ܪo˯S4CQRtô}hh_]>45qYݹ}WqlY<r'\ C7٥<كRڗFCkAK tZE1GLh.L}DоeQ乂5 Z%cpny |7{ E-"x.@IT8䧤ߤrnYrwjqpkDa Rs!Bt@km82L [k+:o'CFAq\Pek)돎nP*HeV$VK|`bQb\#(]yRB17U8G!?4a_8èV}x6-k>mTݷe@FhQQ@DT8'B |n[TŸ('~!e-lN59imRv}GYAtDRVL*[J#SL3cK|-g#J0&"ScS8)/ -p3-\nCP˂8 ƃh\{.8HMV8>x9XGA8&U1$9jpC,n) =eKX0~GٴˌCeqt lu[b2b\:EP^ (9M,*a[i1߁cq1PnA?s+9D xh !\XZB ~y5̸0 ^}&eݤ'Wו2Uqd8B ٹU(fJ$6S+ݗ!ZYr^0H V%i6S#rjjYE]!rb)t%U=䨍 Dg)S< $lz|VQt=eP&{~r'!%`6VXc~Ma7NT`zZDJ ;L1MҬ2k6)uM%Gd^$Mc* W,Gfc4Uߗđ g/*?i]A~Cɹd\ ku5G}ZCk 7^VAԒԮ_s7N!}j/42>gǍcD lw7)ށ.Zc>{cxke2Js#XIt>*JQBe' t3:XIx -$)LCA3zfͧz'O}Vo!Hxb#}|J;Hvs6N FbLjN*l:E7?܉=s6 y0+'E,ZvC fx![Zv =CӌоWX;ӧG=tD`փ8|Qԩԡ{x`1(,bӗc3t 2,Z4dW.ÎɢCKFܻqOK&&sn"b)xI`@8!/$k aHM܏FW :genIH|HiSaJΎE-B9?ͳ8w:*ƒ+Nޚg%QC9Ybb͊NisGx0 t0L`ez^u!R3L4.P{$&+#0 4hF/M3kwG lP9\D(ɣ(A{)Er~^LOzC a\XJwEA=7.d9y ؿi͑^^aL[v6VRʋ4y3E)UZ"`9Aa4,Y[hI-c [*m+JHM80Lz\%HD16!zJ[f鬪ҶuV-(>8RBJ^} gWk`1 ubK:Vc)|,s,; fJK"5mw?+ևX hM:g\/ӔfH,wdڼh༛NNwL %…^I,u $z]^ȅu̚S Dh/n,TM})CpY *@yKuh҄8l]ZnQqQJ/XiVgQ_<ĊqoP"Vr&hzZ#/*V` qE&@rj,tQ&޷npf@6mnjrf6`uwT~ftө~i\?[(X0?Y U*~']=-nevu~u+U>>iŴ: >'SWNoB']2L 5"SyF UẌ́*B?lAM5*cbȟr` wjn0TV8KV}ݰ~_T2f,dt;KDΩy㮵ƍޅ)@G,ܾ덩MKksg2!5a$) OB9;[-8_Nz'MaJIԘ'RZ_VCaYGmk}ugפUt4dubEveAń8T?zjl2l!~$ H!uvbG7iN~\',=@GQEA:85Qs+PD+u- y-&6KYөvm4ڎZ۰$cf_|e+MVIE;SR)lJsޢ†D[xo~x/c&RLyOY- X~^2N4|t_攷zpc)NTr7;7G7$Lzo?kvGp2^h)9| *}C#Ri {e<i֗z(MV E Vnjxsb-Ѿɏ`Y|c.9m1[#`SSLjn}94Ӽ't,W}/jD 'z$aoaR'pS"HߓiN}f /Sky!\iD0,ݾ[O$Gt))J L' 1Z?Cj1bS- "4}^:E{?I7u M~J&EISr)B9 [{wAO0u>G_UQ7SNMDd?3qݶXZ8lCS3 UbaF牿K/Dkj!fU֔ĹirbS$TBH8 Zr)1/.| =#=Vl@bH)$LD.~6q|6zZ38}@\ۃmq*ֻJڛ)3:=@#~:̯^t{Ao-p"~S\6o^e^ñ|F[ mD9GdV>JRv'jK?3Avpv+h]4ԱH!nۻ2ez*`gZ;^F 0MĻ cfvp,<:^|[NҳHY!=)9{ǓM$mendstream endobj 90 0 obj 5631 endobj 91 0 obj <> endobj 92 0 obj <> endobj 93 0 obj <> /Contents 94 0 R >> endobj 94 0 obj <> stream x]mo_ߺ[UEtEhI(>5>suCˇZgy!_?q_Q<ޮ^Uuzu꨿R-تQQG_9+4k3{ڮj nqS5o5 ?nu1Bo4ln~$yl$t3^qߺJFO6ˮb[뮫::97/hݦimٯ cim^cfuxTӴkNe]^mWͱDg>dyV-3fc_NŦky 7]Y-r 廍, >9HjRRH3VѫȣnڊK rs ֻ^HtgxyMDV_4:iV Z\s~`sW \k44n!4/Pg31ƕ#s9E ZՑ;+ШO, ~0И5l6p8&=e7;¬=BޣH7SmB_ЏãR %9QNA-*^^2˺:D t lAXf\lS59npKv\ PßȄ o@s@Ph17ėa'W T~oQӷخkFDۛVO"2*a`=3y~+ktӈ&|6M+p}uM&xWz$s&jnb;!#|7FDe,Рވ1<(9|3# ;JqN #9vIʑe^mGYᑻ95YNcAPk,!cK7nZDUL )Ij3n&S4ш Vnt"D6y'7Evul_7Wtu]o,0MrM\Cޙin~3 |k'꺖>߯Ѽ8NJVV`no"Рxhn[Y`>Uӈ ¤"w18 #VR n8xv<ȋ~$C˂8(<#+0+߳d= XCUvr+j]z-yr'H *Ћ>&s zV#iseX7`Ÿ?*",FyQV'yF0ܿ@3&cw$m֢ ZL@@ς8wDw)m+F(]2k @P| fׯOZd)EcG= L-1G h :/B#^FcR눢sXT#)qϿ@4|o(MBF@C6/ 6jP" 8 Ĩ)CaoA SwNzr٣LE\%`، F,$'>m^pãc_|{2;5 v BPzSugjpD(]Cmу SwpUTSտE:d(r} AZAOQuFd}'rD:ơ-BFvZ"pGtc6f'|uj4; OB O`>X0a T XV.u&RAؙxRW)jpǾ׊җrTěp\瞨-r+W܁Hn` M!# bAQ!fJb8 PuLg~D5 ̕ 嚁JrQE36ׅJEx(,]wV G8;nJo\HH1L2^<ՑpDϏXE<.,kP$SKpǶ;Sq$l` $!yR{ 8bIԐ̏G~ԝ9~QJnlU|K7H:XFwE'6U":D*]LLfĤo4d N_ZfiNi9q|5\+4|(1GA9Hś"sti:(}@,";e_)y_u$fy홪2E/\FHIZ5dtjψ{^S>-?$zx;/rBP?wC6zD}cUg%<$^8y^sʖ4Ik]S+%Ws.v2COm>/s@sOQ$A5/\AIP+Q—"Gn9#7 eYC,dIfha Y)>hHH6jD7s[9=d h!DHiU]tdeSrGiGqi1gWd?6$UD;+6*֥;div:H <%%仔SJium ^&gm8!@V%i۶Av꼵`gAsۮbo# uٓngV OI(>`SZK%uKS >읷Ԭj,NOJ~\XGIendstream endobj 95 0 obj 4566 endobj 96 0 obj <> endobj 97 0 obj <> endobj 98 0 obj <> /Contents 99 0 R >> endobj 99 0 obj <> stream x]mܶ~buU*D@i Hƽ(좸W;}|..$rP/{^z·Z33 y?UesG_T7GяGm)7\>z~b+VZk::JRʮ[MU˚nV'^Pl7j-7e[Z\_l+1%V\\lTT:h_ۂI3̶UnD[Uו8ٲ_gx}bu8%zQZN.nl͍鳯] >E_{_lxrp*)6MUW0T\?A,&X;͇ w< RPAFU7.?Ԃ/Ӯ- em,j3MȆ\7Ԁ/C$C8GVw/-=s(:97furs Z_5iJT0~| N~z|_v8N -=~ A5ld/T{鷻@U_͵+NW~ .V%շtA pۢ:LwGRU#fbZ}< +d? ͵!DP ?@'AlA`Ȩʊ64c1U5CcV"x걲ԈJݭɿ'1Sբ6V`6 0cuQ_53J>\~_>Mb] ޔ vb#DLf0C, &;ՈflŻmZ֪hc͖]Ut=CQIE{WфD%U5?C%ki3:>Y } Ɯ&M R;؜ *lIbSi_G½AHb%wƭUMf{Ћ}KA[gǍ/\jBթbw Ipm{3 i.[~%2[;~ܥ75^f>#aS(XUeVO0@Ń O43P7Szt4fԕy$`Ua`/+.MNl.0(y1yNڀ DgYo^8d_;@E%IBxEn%E0{77 gH@H0pշfqsN\h/YYptj=ڽm_WB3\9 0) s!Ck6.XH/ oJ$-8T~ : ,0Ž`g RAZm"S{ %4 +vMGSp9EDl$Hf`n6fG3p-ÒDǹ*%ܡ9(=k{`0CQH>8-u+-Cs|5\*9GH""0l3b99A1B"LI (WG;Hy.tIjrw!42B7'f#3ZKKq71N_V38m$.`l 33eW6à;̯4/SkJ2t`-5=vܫCک؞9vO"53HfnE0Z%SC2C0yͶYQԺ ͓M&TL 3|}S`#R|DO)>SW˅ig1SςR+6Mx86l+L326j+ Ò 3%F%#3qaX0=lXT!Hi-3ȾKGUÜ!IWѫU^LMjqۧ?O%?,Can g"Hj:(P~ͫ0U[05! rٱp>,5O19yCf %҇ͥ HtBeH^sI&I4X19C0Yf+v`T<(1uՃAP+c.9Ȱp]rwº+6+6iHtCVN Y{wWLBOdi #TC%f0YZ[GHn Z ٓRK@2Er ށtr]_SG. p|aĴ3LQEtL)tP4[teB6g Є=/JP)Vʅle%p 8h#";e@KIh@Z ̗Vhkeix-|=$et]9s9!x'AAIfXo`Tc,gFKN`^C|h(;럨FPɈ; όKgNa$sof7 ]Z2B8vd(>/`0tOӖIȚ3*'U#.~g, ;B)mi2y,8{E.-;܏]֐KujZ0RSJrh.rpw xcA{djYJb%_BЌ}̿0XJ Wi⚮=} f#vl˦rj"<+Y.]zi\zK7FF8@/ ~}gE?Zmw!bX`#; OIwҳDaܰ=%Z!}QX: ̈OOʒ3MlWW6`&G#{/D{L~nwmNDOYp]i%G:6R\Sx=,:O#d| 5S$AzŸo'igcIZխ5Iqєb ӊJ0Sjcb۶ )5sFcR9?a^ȼ=j09%n+-f ]!=>r9,2n- @Y.cQnTzrPrph>Wy~L7hsPhFW|,s@Og$=">1>4F@&"v`E%[" Yɺ3y?cd6J:ch9}&+եUGNom3|0uOif;1Z-V v6$9)+ ":vut0#5E oa{?Oy<]FP>A`Aڂ=mweW..ϸ@5͌LEh{e:?]nGʼ8bA4m'21a͎)G,%2h9"|)%vQ0p(s9J5_NAr#㑜G74MYK5zf/uj^Dendstream endobj 100 0 obj 3992 endobj 101 0 obj <> endobj 102 0 obj <> endobj 103 0 obj <> /Contents 104 0 R >> endobj 104 0 obj <> stream x][o~k(RMm4Aql~HJ^II$?b7)CEQ~MYjS\㯛ͳ''m!7l_|rfQ]5'}K)ՏZZmZZo^|,*݈~nۺjۉnڪqwZmYKu]_wM t,8~FKZO$xWBTZuOvU8+>_+q01i~fF|i~guYmySKSw{[3RDmimSӪמǸgSCOX<02Mѩ]k撵]< Nn>Kػ'Ug-=~k[{^bů޺Iyb=rㅓ/˾tqYŎ> ǐ@7+t`hzRѥnNեVnk=m~-fم߿GWi=<US6†rE]NIvw*JS̿<ܖ:72:3Bz`͜Ard}@qJZHk=ֆTxMO̍CF;Z3SNؼ@A wU' ^=hns /V\MpF}' a/a/8fMʽV6 &UAW[v0JcU/ѸDe-l[AGyh'h$`fc| o][Tj6+YifU8F&[,ɬN9mp>4.*eͮԆ^1+|gͫ0!nbݼR>>4ؗɦnpZZ=;/=e׸|>;;Dz ڜbXGd2O?vywe^Cm9C[~?׽ک5.3gOӍjnuhuE#,)Sooquogԛfnf+ {d~qa(&e=J1j.%psϞ3c@i *x VN@w`0 ǐ\e=PKQB[9FX] "j~MBF u9:Lm]1CÌdA΃}V/DOZY`ё,3 #ulg@329)p>>|~xJΗP]kY)M I{',> U5<zlM3۶W%uY Q bϼk0pބn0`6  vLYގ1:ShAotfJB.2X?4z4ڊ{ 8FAOK\W;) ŇK C¬5cJ 1ď%nb#eѴ^di D*ߍ}̡Is$DJgquY) H DHMHvfi;7; i=>*.x1z jV8Bebu <.-6iQmq!}3 ጃTDZsV]KMęr8_@FǽtY'+EgTvi(>?OUň:52)/M6t5^]>ꈆKOB$ °j] 7΄ޑx{AX!Qވ%b bA)m[iTCNte?(2ϖQH)]& 4SV~2X.vo2^dt7#!0:2 ׼In{da`]s7iXN˜l#7>ē(jJ copC oh>`WJ'\}X9GG uguzFROsJ Y/SݿVF sjVO2c)1ˤT$}g.Og@/~Jo~ P}"… 85ޙlEGKȲhlrϢpr,&pSm&?C5~Q^]QS.|QrɃ:4!&ay7Y7$C`O&Leʹ &SsMedj'.wous>"LzvD$E]MTq|eǤrʀ墳wd,N(6Sbk%+q\7! ״ 42F=hXm3M}iC|6f43m:W._u4cdDLivNWc2IRf8 bįδcLWceD x^K*kʈ&H,`W.jpF2 [߂X+<(JXuGQL`Qk 8'q\1 CXo}d'F?vO&5WP3F)Ra4]5Zu/w<C! !uE\ҜSם8fTڇX0, @6ă1 ,La czi)CB,l[ɈRC1iTHF]S33J͌c1G;(MxO>!Fžec @:4Bb&+6tC07邴cH6#ݶSO-??-@C"gSTKԻS =p,SxKHar$4 T ج8Q*B;؁, +0HUՠ*"$[uMQ6\%v&u8:xT*"S2 ]޼L= Q0d=w.Vc߄})PB0ֲ֨=S >J[O'k0ir\ *Ȫwe!E&hCٕGTygXCq4grK"Π/C}Q,{쯉-`8VɔqG|4iaO3Xi֖2|*K> ?uHB0ehc~r_Lg }lvf{V`VIzv.#qP%!Lޅp.GgMP0bDt gߣU+&bo(u љ 9c :dZ3+`G ft4\;D>m‰u5N0ɽHWOZDzMqZ:ⓑlŊ/Rw2`42w`e~U`|٬a@˭i22>aӵuω? ;9," YNX,Q5ܭ5߯BskdBvNa\ /$QX"Ưfkr46L hC=f CǨdM w WҸGDDx`xM:J0 ɊF -W`c;Tf>;&+v&'Ŭ8w8NiF A;fo`BXS8JsE'& C?%M'{=LH|KTB/o w;m:SsyM;3Iԧ g'L`wbIxu(UDbbs.ԥKmHc ؚ*9Aak R>x9u+b"CSVLyꌼ#s1LędTV~z C}g:ikTE늶6D9wnB!Jq*#f !#%=ͷK?8sit]&='stЯN]gk ƒX+u;#'zsSDľ-x6ủ/5_s+o⟕H炛XͪC9EzNI6Sz+Iˆ55Ԓ\E$s~JQgs*NdM@̾VjUv9q1zw#&q%fK$۠X6aas,4 I<ĜSn*>m,C@t"#N)A 9_  hȜ̑#Lj*\FgZfhJU7f8]Xi :PR~s$3O클o)g4UVē~K&黔?zsa BdK{>m1\) l:C[ !s'1d͘"-&Yx6ZH)7L[񟵘K dB]`k-R>tFmendstream endobj 105 0 obj 5348 endobj 106 0 obj <> endobj 107 0 obj <> endobj 108 0 obj <> /Contents 109 0 R >> endobj 109 0 obj <> stream x]rݶ}Ws:MM]g}߷'UUUcbW7YSfwyguuv(Tκ\uUQתpf}Q?L?JkcOmSMe>Wu>8\shNTgEN۾z;7ufhy4m+pZT:VCjKݴڢ+϶izgz ލzN7T_a;q rPm7|̧g'_T5e׾5}kwN^&ReJRIjR${8WDB9~, y?w{Z|M~&(\1=Ej l'@Ś;}"whijiv/;9՗s7ó8}ܐZBf ^PEvp ξH(a3+ čl0$q]ч_\ɪY_ĪH/7rEٟєg YPTzGgYi`U|8batC#G6=[}؞fg[s2`N9ͱ$ Zi((SC-^%IÇ\Xx8I\kMC  $:u^NҔYљ#跀90Jqa"- m_1s!%\{z,5K&$Fғ;ΧE 5"DHɡں8(qN- }D/ T"IHqQW5JqA^\ y^>b^֐.D2=#)@  k֮y2 u=\'yhXv\.R˴@ ~{65A  :#jiDVHS.]Mݐȡ%N])=MD7= 35>$k2ȣ:C+|<Ϧ1<8.4jrlm,Kܷʴ`,TT]U ;;"3}kRprÉ~NJ=#UG4@{f&qqIa+y ):Yc>i`̀o=VBM/KS:c N2ZX(1^sG_Tg26aߤȦ\|~m,%df?@_/X1C'qUEäXyZKGI]^,BDhF׶Xd )!x,q%U) EiZ$X w<m5lmV.dRS` Īcfb7/ɮ tzjeVռQ!zGq]TLG$V r Ÿ`ZU )ѝ"wK¢-d8-VTU]B&g >I&u /zn+I[@6y&+r$4Wۣ X0fit5 @#-Ep (b 3E*@Q6^i-Pu=8©ҪJ7Ev^Y3? Z 3LiQdR5}IvL#1c/T $xraqF<~mVVU}7>?K od5Pun)2]UU[/󌃮6m+u7AiWYٗ?G,-CaMGRJyR*4ILh0}! \hR~δЅzWE#_]936&{Z-\CE76 Z рghN;NSxk OmɚaC]Q]s5'6y#{5OE(놀/r@ײmjqXGEB0TQ#pPkK61nHExƒ(3%N/x+LfdT>ᝋĵFye7D[i-51IKGuTuu'*&QTJ H86 U%V[HpD\vp94 Zn%6wG&?Ѭ@q+y<%$VJ9$S;r!]Da?a&>98۱RpWR,s丸/5mton5[|mD^EL"XS |nE]\KI^hq0^%b*1 @3{+C/DH5ӷ?[1-ӳ$JߙIъ̱{ϘPv=TG"8ɟr Y#aWF4X;qxHZ80DxXyX\ꁙv;K#Q*Ʉ3+"-6pQ2pD>,"jyJf}E5 hlۮi1(hzl$qz=="_v|(=0:[u͑4Bh7GNX5|)[sOBHΫYS3fI}'Phj*bl*~4@Sg/ . EhhtH*\ҽ?=lzd=6~Kʠ:(bӠi XLH3d61.!Nʼnqxi*Kf̕ UwT}W0 j;|CFRH0I]V6ƒIpJ cH"w8T=5X站%΂,ђ9ä́PMj#??s"P-A* $LtJЁ69;<RwL_$eHnޞ_0բ)!Z4VS<9ήX̯РP䇵 (oqqӫǠؿbt(켪) )>!B*IAB6h,&D~%Jgu"P2"MTc;)cK㝪1,WdyL=„=x=zSƶl8 Fko6%3OD]xj<] )Ɖ#w(9:/(b4:e]znm$XVmW7[̟p~1 P=_Pca?zړޗӄ? ~GV96a3!W(m7, 9wd|}KTfÑe|y~5 SUG7TeEO=~wl#"XO0QTm^G o@Zznc=ɦM5m ա*m{K3_#MSYendstream endobj 110 0 obj 4321 endobj 111 0 obj <> endobj 112 0 obj <> endobj 113 0 obj <> /Contents 114 0 R >> endobj 114 0 obj <> stream x]Ys~n7˱+N崙r=Hʔ-H*Q*+/ Ơ{qSu M0iG_gL#7|z56Z9q?I)lúFug~۷ 4Szsd5Jn_1-7j)Wn, bǤ`_-N{ߓ0Mٖ3N>ˡ;oی8,49an<R/ٯ ϋNJMk'v{?Vu~aRk[VF [-:-{%\@R, wDht- 6&:bmRzpS`~6v7PxɆ`71R7p&/\TOKrY_ @:7| |>_x^<0"L*)^߂\ WN ۸6%]ӶZ܇w=4{Z>9w,w<(@o:apʵf^en~vҀyѪ1Jaq &T__?Γ0PeD&PF#o(\q_}ax!9m-y}P1QGd1b`x5#6 5P/ FxMy!bX8M#$qnHGw",! yplHȐz1+}4ߍwXPUwT G(G#" OAn[h$5ixf#6kb삛B1H/s\7nQ@F 4zݹ *WX<0cLXk{/çWP|0TvWE#Q^HKeCi[S΃NBѢ3hIG)% CXȶ;7m(°$;: % )us16_i :I'?P4=*Dd ъhۡ5zETD32TAl:&*o P~37ϡq$AJ@,tpǺV8 V4-ץKBtF}ȔMGMlgkXudk^oZ35wv~u@@s )m ^Dy>RbF(g*CۧK2 \|$M@Zzf^%COHR@Z튽9K"zs6t>ҀQ RRGI7(-P^'~#S@x1p?bѲ:pkS5çJ7K51Hͳbq}N7>A-Bߠ 0L#"x9Xj58ɜL>SmI:5dTY''1Kd2-ʤ^jiQxD ]G] ~C~y!ɄuQD3%$ (IEtHZrf>Zwds%7}l逸M[B8`i([@UA?MתQH~4-7,qr +YMqIr"'^"|~Ux"Gm2/&,l~F S9^Dފ*|Fwp&IJVoiʔfpG-yPV39>)8ͨiOGHQqZfYq)g-8`3|V0|wU>]#؍ f ct'.O1ވF)+{9t+_Y"Ya|3hgldt&Ul=<;d`TQ0q8*9ld537Q$IX4Io1{GE1j=@@ ea5FcD4~kָv&fOA ,?~^S(q8ߝJyYYZx')=H 7󴵪sJيVWN7Z?#.h*x.#k.'u! L-y )\g9~jJB.T _ F<,V d!hF$b 6:*Eku 4nל{^ŽKOZ(s+m tO K[ah*.)=QB qO(] Y!)bl:i湸:~%'$"TVX:YDQ;չ͂grN>RTkO'15P3sQAvUM ,ݧ#W\]A?_ ێ9WJ3HO;2/{J8II\v\ܵ1` c>N 2]._~]?/u'8ӈ_MkS~yX& rJ`ךxZ5'LpҤ֟1yIgf^coSzL3]\DҤڶBxgBl_d3ŊG;{XKW,;58{]HtAĨJxp`Hwxtv hZ+q sԘW%LBs@:;u\BaPX Q`)S܉<$Ohs sv[ k\N"/ 2x0}^u_6U+ǻ;ߖZ}Y MYB};lin[Sz"m(0c^p~WG/ !b/nӂ[b E:Db(ťSgԀkrەToG>\)Z}њ|O?)#S37q{Hz &`\C#m(j'?{lK6GTg4y¾{d6$zX8њ'[*5رN9`>He&&'85;{kr0%Q؃GW6H-nr^F42~ Os$ƪt~ת Gk&'ʰRW;9zZz6C]+d?=tV yqgHY>}ӄRG0f=$ H^/!3Hф c&7yJ(EϜ#T@)vf|q)͖jS54 |ӟAo[3TDUʃ3A\$髝\&sV~rk]_SjY?çNٚn?t;4և.s84_ dG,Bp[%_lVՑ~Y-h?Y)dAY#Z33V ,oe1;tK9 mB>u ZhuM0~7#j49/QgpYzgImR9@Hj챺x{aAfhpԧ9{s[KaɸJH1JrC( 7c.1Ȟưj ?ɕr^p 1)v;2 <^czM 1X⚬/jdIp(OSj2!raPB|hSP^gܣv~Tob)*H=$8f8CIhAC.93i',endstream endobj 115 0 obj 5486 endobj 116 0 obj <> endobj 117 0 obj <> endobj 118 0 obj <> /Contents 119 0 R >> endobj 119 0 obj <> stream x][q~_q~w@o-YbJyXdLR]oK4Μˮ i4쏛yb|3ߙ͛y#; _x>lz]͓sl^]+)e6JOZn,ӓқW~8mդ9NǗ}ìDS5ޢ XHt s=؁~Egk ffAjѰͼ>o gxԀFL2Ӗ;3ᰠp&DBGn^:ak9}19enDHKWY;! VW/5DEE\XNI(۬V%u,nhVѥ&x&d@N>-I:y30 ˞vPvPFT.]UfكtA R^7:>N_wAiu eX\!,߽:4|#u1au]G"MDz>6$hh,L4b9[$ 9o>_UqƛSuC=.EG(_QqlԜ/Wy3 V;Պ)H[*4CEQ_Ծ{LV;xw%<WEUGp׾` +N+7؆ z%E=̕^Z9^MIoA*amq:MDd6ApENulc\3[!Bb t. wfo..l`xO'QJp2@d\ӛ8iE<ۆJI#׹FiZ>zJTPNx8x_^dq9}ׁmAB/u}_گ_ءcu.U@Dtr`'ߠAH^,-}Q"Ei$qnj|]fwj(*N-sΚaN5j":fv"Kju-t6)тiHU6|?W.գZPأ⩷Đ3c r1,w*NGDE{$»d 3ڌ(vpp76$+j9z-$9)4lhWY.:Y yҚy(1kt1Y>Cz<M*>F7-\{`~*@E+pF xb#_$ V幡lhpW ="!@2`#32p}o0k%o/H.)n^%@&A5A="j&a)^ftLpVhfXbIA_>lR6SHuD1`a3Hx'I&[us6prжӮ;eؠ^ GD@f S;U+bG'TFƻdqni@63%m ZS sVM܉*5Dt`¤q{a+pK|׊K8x1[pq%"ORz37֏q$(q^8a$lRt3]!E l΅4tG|V+Mo^-Ƣ6%a N7)t ^óԳ$ד+ݿ7GUu? \4"#u\Zݡ۲M O~3< `Cf$]Q.ly fM̱a3; 7B1CnPI#; Ri7BBLҞyja J`.mNr9'!6v;~tY-Ul1&{)/ N5UdPwPJ(h @_풣v/vݠ:b]Ug7FZZ!(up-1*Oh}85DRizT{ Q+,]ϲH DMaM|Y#W:)nU㢙( +HpsVi>ǕwR\^?+ĢySV 68k6T"4rd܌"uǦq" b`4-U#dtYbN?%)c~I_R1|ҼIĢݐ~_ 쓯ROw?q:#|e6hʥSa9wDzdgɡ] ?~KϱV4Һ7+0~F _RSr飽I]*HbC2 ?Br8 2ˉ)颾bq.}5O%Lqq0].b9/LI}>sSe>R2ŧd몇tƅ68 ~ :Ht ?lk'YA6"jf_t#XW>={8Dp}**vq2_5%Hȃ#{WkīiP(z>^j֥^Օ?]]PAjIϿG+,}_ZGW?Dt&Q6.OG "J`< ?ϲ8ȫekߢRHV[H@ ެ# `z gfgQIzHtZh>;iz=KxHC_13"Pn7C/ Ï[QՕu= +jsEz7?uųv;OF2$şӻ0TɭUj3Ar!&R̓맿`)ːB-l ͖9e LêJ=x V*HHxw=CJ7 ցj{ڄyȆ_?~lCB<%P:j {ͳ2+JE д ,f3:9,!rIzx4H /]2|__k8-fqVJImb] yP&<_Eh4Oڌ˸,fo4Ti(Xi Coʛ>1N'Y俫bUǠ%/ K{̰l8F5u}@Q(nʏ_d'>IBPzVXy]ߒ3U ̋sy~tVX8Ӡ6^` jPG@U 彞 O f(}/`~fTȒG hn^|K4_hTt6W=W5rus^3nMs E΀kKIϙ"(MM(!vaܫpdʝ dZLoҚWLܫpȝ xj/ &c#1HYD*QpI~GBt; Fzr2xM8x,ڤm»}٠ ,_՜ @Q_PϱȢVz3\`d{y:ӳm׬I5D&ZʍRN36J " 3<i=o~4om=xg}}Ρix+8ROW_ ~B;wp!śΨM`O{hfkɤѽ"}2b&z$VĮJI z@ quH,fpw$xB գЦťl pi _CyXgzKGaĜY,(?&2,lvp[<4}]!2M>!J.a?'^ C׳nZ+'lneC}dr' H?!ݫDOȄ`$bj8hiMax] w7S8ߋ{r{K9M49U)KeCk {!`EBe$=Z"ý_aYʈd6yT'kwP ֜+6vcÐ{ w-i=ǺR7^I= 6 'Q>8*ʆ{(!+fY +U% (L4ϩm`yMp7дLA3(q(Q4c:-? tQƳ"FxvxE1'bO{4Mbr)#Ff'ړ<:Q Z?d{RyQF8c'jOetIyȣȞU[[Zy).M~G5Jx,q0SV:(Dc1?-ڋUE@ NTMgLQ w5m,q0SV:(Dc["hUIhJ<%|.(TJ?Fퟪ^:G de?+Ƿ~?h<.ulʐ~[^|ii1#&0}K禘(E%9'Ђ8P녧m8">+ ]|_ u}CJ{krkQB7s5tf`բRS= >Y%5UOсV\{T&7L`IZ7Rp,Tp =r+E,zC%F dMURF^BhרRyZ(,`wU4_=à-}vF>\]MF_[d(7bn_<cHZfzgZE5J\5̀Ѫ y~,iHôVWЏG҈T18_Dc͟7WGpe<~?Ghendstream endobj 120 0 obj 6533 endobj 121 0 obj <> endobj 122 0 obj <> endobj 123 0 obj <> endobj 124 0 obj <> /Contents 125 0 R >> endobj 125 0 obj <> stream x=ےq]K*IU$"_ĕ@*E.t7f0X2h?ѺWO~߼0o.pQQ~~SZkBb:p(6c:<}u6GK|NƐ.c.^~quŸ//O6F.yuՆ\~qeY)'_^LnU5κЦ5 *;O~B=Z_׹cu/`NaW1&yݻ%O-,tZs^Z-tMl l74/?oi5&^wW /'OdﮮL]|/~>S+U=c5*7YQ70.g|=ϻ3B(K\tlW>#V6t YVG٬Ԩh^=}9osisY>gu+'DA5㯮Q(nEM۷Z;frhHoy->]O9䀻0iWKFc@G9;Mϯ*\q-W$lؗHV %DA/_85t;| CYfNv$;Մn*gÍM<ЦaEz`zM4 YpKאN~MNV[;gs5\:͌_$ƭl6Sozٌͅ{#eTRp(P̂S~e2]QJu9YV^=LV*'y^R(f_z+g^3 dw2xm\9 KyYd"aZE^W A銋)_^}mIw@:OZjux ցl,gW=z:澴\wgܮ^Qkr6nׇur4!8R I5[~%w^&ֳIWJ3Bk "<53\8S't4dq~M X2"$ t""ݛ9ӖTc<D'c}cIwQml٢sh+[BuN#lROTjj"F[ " Џ52Kz^'s\iK[UPnM 7! X0t3p:83QŮ] ֵ )_1ݟڕ_vCnO=ly.UmvtomsyP{<{e˚ϧQKj}βk:xDUO;Z]^{\kAՁi^Ljq+ )=PT98]R3۪뚾T^V%6KyV0qcOnA|Z>Qe!/xbCVO%a1<7k|Dx 5L/uG,am.r gn;߾LQyxlg~ͩh mU7Jwce)Vuܮpa\~o WGb.7jռ©Zg܁,"a]=զv._=erЌ !T6~˫|sj*Sd޾NوBia|Su^vŔ٤*@sLejw;\Rbq SCZ~=@c Tػoոit w 6ZgNR=O|s^k֠|{Lus!E[rrSe_Ο7im:*IgVe?U. W۞[}|LkRÛӰ?$'h&&}OtKEѬw dզdU _ӭn^ {$41Ǣkc˺>i4V]]G>Zlyy%?{DXJl5ڜbv^LB9#ByRb_ O\i={Tܮ#V+o͎ 57>ρ#왦gaP)iPp5M:9N+oG+?PuB12T/hkDhMQ%WjtNSyP3R3욊}qm`ه;X~T|6rVbǩM|7vg{݌j6&l;?O\:l;.'}wFy] >X u~qa?ǧp6h&8q<7pϸ'nۧIWV[΀/UzSQt$ K0wM{|67<ݣ4,RO8l>mFyv퓰/hjȌݘ.?̞~|:Ydc:}4R|+[+n2۪?Q+!HF~įx?0cү.ˋE/ZA5'/axzW?d8pw;l ܶ&}A00F(*30cD: dT Ȱ[01;z༾0 " *LLdA"V$#1sX ADU+8rg D>+,Xgy^Aϭ;Ǿ.9^Kda4c KV@\%x/ ,|x(>11'n^o5;5w7qowpЃ w@s`&!"c#ϝ N,Ucw}@T1q,sl 2bgрG"wƫb7TjTE< UĻBG>6 |qbB@czE1'ޘs?vC@?tF a"-35i0ъx'f+Cg57Ł*!&Z[h':;<+,)l>숪p(L Ka* 4g:/,\( r߫z3g:/ $!#{UH0(۽֪%R}Q!XHba;iE \d301Dq ؉,[#K/] $U|F%E-K&5DnXBz0I+*PDmeR3sAk i:jZ"JQk,# 2XUP= Yk:EQA2N*E[#5q4#RUW`UKUAbi[XeҾe]`IXq {{tk``0XY 4暢\YuM@V!`Ѩ"hYn+ |W6%yLV-k%=/BڀAcfYL67kMAumEhYo] lcym^ޜ5b 812Hƺqlހjf1<9 ۾5`pu#o_EQ*^| B ~E FY$=Ǣ!00PVPɊw:vreWsea 5ϠL?RcY* Xv{CBʞ2|mx7;c Jςεe$?j[$-l.BgzKPrtV18X]1 'owc8_Oۘv_ۊPC+/8&t_Sɸy^{Gg'OZog pQ \s#ӅC)d",Z7oC#YJHKk&l$nt,EIM۽c՘c2sdo}ՙFE^&tirWӛC"wLV%U.*ʄ*+Pu't(TmW^0y~o*QўT s f=b3IWDfB%+z$ ^$#ppǓ̠x'a7Z8# @|S Eۿiz7"9">UO/) à[[v1ȴ!{ 2i0WeGM碝6͙66/B|c]0hPE`0zήmZDE[PFm^5w@1'wBٌ`,0Wc[pBI{cL \Jy[b-U[:PܶmokeYY%y,3OAPrSu|$_*f{mɂd`#r稳z&%U$|J4[Qef@J1$(THcQ0s|5Fc`"JxOʋ=O* H1HY=iz$AIkT*{JNz<#0$ 5* lxlJ{O=Qa*2Ulz7'jRb,W KpHQvGsnCw*0=-q$$F@4Tk0V#c 3֝(_}b40;`ZtͫX!|]PT"c iנ\!0zם}-[10QD^ůX.icCz^\@Vڭ3{ DP(28Q;,Pq4#GPGl<ߠC G*!r>vj1xY[cյґφw_vsalΣ+1C LdEsT5b0ͦaOR]觘zuyD\ sਅ7Ž.uU=ne5^NYr*Q"R,EȲc&Sתg A pkQA;ΐ!c,x lMCLKk:WUI @mI0 y0 ӌj}ؔeEK$WɈFټYЈP y.>tn9v`ҨȬ 8ieFZ7<|V= A20ݞ= 2?&zv*+/E ,9uhlzKT,tb0ܙb!A8\> ]^/(,FhoNh `/,7@5rkP nt-϶6E$ļZyAFǭ!5桠UرM`vJԒ!Pbn\ *5)$g  2&U +HtT )Iy}+ }# s=R ky5w`_w8tJֶ%\@9]SΡayHmXFR@Jfa Z3> Uc4W;nm@4uըO&Zw6oce4VOqUQbDڍr2—~ojEJ\#hX3$wpLdEsT O*p(/_r<,cء4PIy JOuX2Vy4wR7{r=;ö9'8W/6z90O=q/RrBomj1>u nolsdҌJxcy5V`8gނ^'s3Ys& 4q;4f8fG:'`eЌը0&t6z 4VHce4/oNcUNLTcPtUW9 =6s6BJVDdӱ;>Uvb"A{jfNo?; Rendstream endobj 126 0 obj 7863 endobj 127 0 obj <> endobj 128 0 obj <> endobj 129 0 obj <> /Contents 130 0 R >> endobj 130 0 obj <> stream x]r8rSMIY\ wd)ofvT*5 y[Zwɫ&#;UoXG Hwu%䮶凓pR~>I_)o=33߉۝=Gnt;pu%NnvQ鮗TW}-{'Hh-ӮD  s}K8S*㩽ksz=N5RH.gC'ˉlJaw C5ٕyxIfkZvjgSa<,z?t%lh`::)ߒé4'!SyE?_xYwa_ܑn:r-959~PpSs=zMt::(tkb'?ޣۢW2ގGҫ< F[+s(fZd7(7;ygF[6&ZW0zE?BdƽV\us뫺6TȪwt»m^3WrDXv9u3Y1eO׫w\r;].:;/$l%/J$7| }DhǭJ۪% ~_1',PC"wx롧_i8Nv߼zfVjVwaѝ^]a cn)Tꤪzk pԚЇS~6uwZI HPK+Ť9WOelEf螑^/_ 'z0hᕷ"#Pcvg!.CmHy{۵'ijPogwg1.|Ŭӽ2ȍ> #R GXY[0ɫ<.zxZhC0aNg4θ>C?9̀Jy\?K<$ 6IV{vsoF<* 2݃P~ѫAf c Ȑ[.Øjrv&ѠB. 'aO__bljj@-)Щ8r!b=\0rg8wlWDՏA2hfMX'X;Pa ; Z蒑;zD>GơG"B( n31.wd=1&VsƑqCx`{PCj,A„sc"" dT/qP q_ y"VO=f*X lT3.H>K@G >&mT2>Ea-rfnUqJ^MDȬ:μfΉىnGW%KA<^@G1]s+C+y| b?PQa fQ 25CT}7mglЦ}=pjtU>4?\E-4f>epWnEVp]9u$x"1<f7Ns*d@r?=8^'g>fcLl$Q܏`x8 q[j}2 jg 6LE Nzgvlâ?LJ_=yz,:3}~fNE%7(lԎbTz9>;r~>G0+ uǎ,HE7h^`YQM%0K4Z{~W_1uu.ii=II`ڸGayw-j3u6>oj%ӭ4g5]G;!37i `VWXx2TSDu̢mgjrQ+10h_7>0V$z=q\,sI3}退 4, {(2q"}EC2k7'g&qI=QQ[1قjjҟ\?lU9W0"[g~k$yTOUW!شdY12Zk0DBaY@v^ `xVsK0A}>IH;#)œٰ61UM2BD&9 q(h*جu[Fٳe;΄`٩@KOM'B_e9!קlDŽ/mIl%.g WB;5}w?$֠~@WaԺK\~sC"0AI2m1…Va6NAI`Qh,*aɈ>ObzM2> +A'u0gEj\qVn8*s; ؄⣲@- 8:WbI6Ck`(B " x*_b^vMzRU>n73d= _x O`-ViTeN9Uqe6Xc9w4CF~:S,'>kKmXqL+J rtx-7)g0 7Kɩ4  "&bW`YA; a4^ o%?,V*xqD> ] g^Ueb!W%Vɴ#* Y.,lñaz'ꨨaet`,c0.G5v1vҡ\tfthB`Bh/BZ*Rv& @y['\2ѶLajy^18:]>?ٟltOĘuV &Gcp$[.έ. `g_.ÁBe3'_|g=n!8*א޸!cc•n\k^p5#kXď3xKzӞmMҺ5/vpnmퟪyT }Y%~=d A]Q'2ip8{Bn$:6;O &$6 Ђ2l/C+f^G;$㗿IcC欜ش(BP:h0%:z1+bF[3Lhqh3rqQ5m#W fL-1v:譕$ެG&rn7S IͰP |ڣW$6bw&Q¼BQHeZXŽh,B7~]IҮTg݇r#r"CYf犸:R,3@ZLX4xZa2N82d[ IhyӍ"F@Ҩ^Bǥ[^t~+2.Oyfr*o#ԵOJOK6z KSf/yHK'!!5r$s~]:e~?0^_۹FYd :fpx.@+FKC fZb({㝘Ү)Ι35F IUh,7 kK^ifwsO5y8v]HS{IJk1M9 ٫һCOЩʥ)'؞8%ENzjYӮc9R6d<ѥ*[*r;u, Y5mjPҔck2 5Rfm@{D_6.9UF"U 89rli@(yKr2U$rn'P㊳ RKH>zA C:#EÙ$DLy;Ŏ vyiq.=T(9pvQ*,so Ή%̶;f^0ۄ&dbM],RY'wnW[8ᔈ/@+B.ڄ9JַbڍFܡ gqiA@Ayyś&evv9O^L\6 c] cr`,).R?v'h Ā' O(u*PLJE@T16TBB E\>0ke=%3K`\y++!,,d¸,fU$PSs[?SЏY8y^P$>>u#-q2j'տN'%!lOZ/h"I" "ǔAO UuqVn/`mdcyS3FKi#I,*U5DDaAIHBGpJK Ƴ(ʻJe)wZlH犲c:'z{.AR0^xF*ǯDD`G -%9: \j|vlY1'NqCyh <^z@]aK+/'?ֆoṭ gxԓ,gW%'ox:K4sá(n_˺Q"Yn9'*˲Xa2KLdKRE=yVWPQ#h.+ B50)d|]f}45Ix xA="unL`+i, #Ao2OQeYJI܈~Ġak w; s7?֒3s|ݯT_u1\n t )ml[RJnD\3m& -_U 9]Y7!_\?+)4" n5yAk[UUC绡?ؗp6ѶAb?%| 4,%9/㉘դ\'Q[0wR )#:knn1}/0&nỨ a9+R {<*?^De7~.r4#ߧԓ?Tl'`^L|If>:x?UA-eC]d7gxo}]'>o XeLZ&l-}1|Mヾk!4.:J_\zFUjpLA LjB۹ e5 쒩k#RksSssFiL#)#' Y(+ZaR#!w(z%*f<~)YڃmI~'uE|c┽ca3ְw%LgEk-n^NL+OsI\x@İ{H}6i;V+q6ՁK͇w hQȢ& Bǂ;E!|Aj6=ڰUYArk{S?7~B}ٻba))</{~e |.>y ׏2^(Ą00-{O,1 3Af U%EM\2P!%el,o ѷ3>b0~)#@o(gܚ=`+%сS/7TÓɀ-K]/nȲ%\_(گ›Oa1_r"놪m ߈YŹ8M8~|ݽ|J!jqbDm6ͩI\B<34R$ s9f+0' % W:藮e9K4&(h Fvq5-֞㊝#F˩ La!3'OI9.˙;[ W86Zl5fAV揭YzJJ+C,'&cIf٤t>x:s1huIIendstream endobj 131 0 obj 6083 endobj 132 0 obj <> endobj 133 0 obj <> endobj 134 0 obj <> /Contents 135 0 R >> endobj 135 0 obj <> stream x\r O1KѻnI.%N$R);$[$%g'[=CRKe0^>,ӯ'1KWN/w?qӳ^_ϧӯOR!7_:rK;;_9 fNwFۣlp`f//?4.tV-gc\Y_h'Nq @Nΰyyƴ:0K a0Vs{c,q 3~։kI&<Ԏp8V}t^:v^/*Fw+<ڛYJ3d$mgWC4:ln1&sW{0>M^?Cg&狪p]4gyV8ϧ.P[ osՕUr%)l`r/'_ub(>Y0㻸AZo5ދMMgh'cCybhje3˗^饬-χN ;Mqۿfo`Fb]`BFDBV(t@kqm{^yezg#WXMw@xh`o߮56;x{߭lڱ^P9x-vZj{[# npu=5m'!B7tЀÀFfCc] *X~Wut@<'"h0{dgli৴ 9U!Cn'5nD#b` uʼtd64zgtu`-˺uVwV'`t V٥|׈wϒFHr=w3rjnouP2.9w]R^ y`u%KF(gqnLb"t0a~OOOU gO Bӆ׌"m^m q6/GB+oўpVqZ!nkYb8 6ٌSwK4~Vl^a_XvS[@^6ۣn1X¯1듑 `GfU-levŞnv w-};F*7Niڑ͗K,X%ؖksw-*-\'[iazy>R~{6Y ";:=yD}qmoGM,1ĉ+Ou:PUu. 45r"b"[qESl՝^Fנ{ʣ[1V-KEn|hȎ8jݬ:qwlxb6[nUp88m}aqlæGI]Om3t>U@‱'[ͻ)&;x;[3, O]{_V<ȿ%Xn|rڢn|'gx7LЗij9K=\ٍk̇դ0fvBxt3(=髝~3@˝RT).h5m2qQD^|irҠU%uAYH"e!}5y|Pf{MxfuG͎@(I4[:2qNeHPHGu-2[J"BBm6N٨&if,ZCٴƗ@tSW26Ѹ)'h 3S$U՚QŴfhu%lM@l Lke՚ٲj͢5Ha*I"َ!֢ 5ZsH[f'wQAF~ w^HQm`h+_$/zC UBv:19'ѯL:P9)cEJǖNs)O>v[OF #V9IH5ۥ-\8jlK( }OAY)t~JcRIڏ#%:,B(~`}, ʩB>ү 뻩[瀍է Q=p~ѿW3((Y Oņ_dbeU89q>b}Bm9063 X%vocǂ,dFH(k>uy˅)˵n36\ ̀u/ !AE;W" {)s%X&m }8n9[[C@ tA,{ʈh v)mrCzNU.֦HR$*#.ȋSi"\ 3 :DvȍɊ͠`P-f[ClJqwATLBYPɦ:Hh:&#q4aETϳPIJBBeDJ*"9@ZI$WXG| .7 -2Gy9JP~fub) վ,t n_ijv6jXega8Թaq!U!XhD<2,:\[32Kq~ u tQjsL&` .]dw&Y%Ҋ]neL1wΘdYׇUҤS_Oֵ VZN1AW $j} 3; 5endstream endobj 136 0 obj 4070 endobj 137 0 obj <> endobj 138 0 obj <> endobj 139 0 obj <> /Contents 140 0 R >> endobj 140 0 obj <> stream xݽݒ%9&vOeuu42kՎdk2.jZSU5WЅ^Nć_?#2#Z̋8p:H8 ?||~7͛K(?||>_·]}jx#=~a~io%xw{.]ژyo߽oWj-׷쩎 ӻTWjć贈2})?rs5\sy߿97Tc/{~96ߵM`K=}:/6ӧiye:)_Zڶ{Kx|Zӻv风0WeyV_U}j'9s~ 7F ~eWZ>o=0Rma<7_ Q.?.WGm'[iro <,}xs,+^>pޭəC_Cx{{'BKOTr"꼹rxEr۾_?bW\xJz(>~XCOh,tCmVzJ5-v"Vbz Oܫw &RpSr/ץ]kv9|8RsO-OU }{wB×YHeUxJ`b⒐Rl'dBAC?Uܱǵ\YVr|w"Z y_Z>+'C'JWo\*.T/j'~==ɧs{ÂFkց4H1_ Mmp1o>~hue,>:^oIT^#t:Z_Y(x={G^z @Kӻ5%^ңlព*.] YDOܧ.<9Yl4 ꏝFliVHOckZ(>TsQ'֙}Ԇe?.|Ri%=]O=tO?4fr_jF|ᬿd1$β0d'Fsuh;7 ᏟҲ?;C ljb*zi5 K@<>8Ѱ\cX'3iS$[!Gg#qO.~gx:?qrjgfGQ'O`tk-|t/xn]/߭?=连g!_nr{x-er]||ÊY| c"e\}vi߼ʾVDw TUw4̍JzgLzwf;f9 3ImfwIٴItmsRMۜfkI6msMf5LjLz*stImf1̑7D#v+M#"?p V [.DV^u%ys@ׇ7_͛zIy>7߽6b^:=*ݛJhN-.i7ivv)sk摂Ϟe% 1},\&ŧ˚Yʹc!+̬T`ڐR'ސqVde$e Q q]l k i---m֊OZ~Z'hV^&AʅG6|\^Pe1iyHv~p՟o߿뗴m;`QE[ߒB؎oUgt8ଁ V>˾y)DQN0 gy8.o5 ȯ5>C6=R/r3`8k__yw'PF䓖?S6vj*D/_K5'{俾p ?[KJm}is$},֫k,?-g|r?? Q{4fn`RQa~.⊫keFg۱_;)]Ҿ^-]ҶPy[MGJ^cX?$Yuǰ/aڦתr7m (\pMM;dR.}&P[i;b们2H_HknQ)&ːV=!PPӠ͝e|J/}@mw Nx˜H3[]{EhI.H/,nF߫޽ueh`PWҺЊ̀1c]P!T÷xI"YQ[v!s5nGy ;+,̖ԫ^ȐWAk P25Vo|H$UTZusAd̔o!J+S0Q:?a\:[tau-VgVwibv7v<]5m8*#ukm&ejI͎9m-[Xʮ)]&ah[tPpD01esUL50iR2&Qd8'MLMzG=ěG)ݤ7w:}*Dģ+&A.1~:Leah#R4?0en--݂ ÒL)cq@)u;Z"-#{]-@F^^oQ+ZFuoYv7BZ@&bo!nBΤ E+ZZ#-K(3{$H}l |TkkU\iٚ7 n>6 7Imy~xSi6^Ң2Q&$Wҡ >{v=%طqgQ] |&byIL%gRM0 x8Dte\>'v7 w y󐶆$oI C,40~g=2$+ad]Ma%1"]`bbG Syb|6ч&^7aܖմ@Зوbl^aشe5,w{aHF/ RelgƱ:}hRSTuǦnNeul.Bf`!CY6} n(^RjKؙhˎMݐuRcH HSlbSm (KɱdU] 5Y'h@z5:)œ&s֦b Xm 33c 4ɥ|-e K&4jiTH'ͨJ9 [5a6;d*^5鼦%tS β#օViIny71_cMzZ@hGNd;Z } bjDkZ6#l^HHЮ-A}KSƲև|,uԶU_V@ -h[nR47%Kʉ16__Tw鲍<&2NB,Zzʔp=A'2ڬ&QLzݒepCQp> 0gljgyɫ؄VmBj:5 OYF$&^j ZmSFGI)֩PjؕM&:la|ۘr 0ABѰ5+kz"N R#Xz_,j(CN5Z"&RryɫpI1.NbfnV.ADk`KPaoMH-1Pndd[WeYr-n] &,ݞx>օ֬E@#]N7sBI2ӥޮ7gJV1 qaoǸѣ5=(6d !`7 2d_uUm)`Ͱ!H4Ǧ Ʃ=`Wz0%Va86}6}plm86Qf*h#dʙ5u{(l:ݱCm.TTxuZH6X:96* $ N,-'T66a2e&ʲ iZXcm؃86U V%?*6y!Hmg`mꢺ@MM 96}f ؤ]amj\ Qv2U#6^W ^mqұ z m86}f'W3]4M}ZuS~ױov6B}N Zh- ხ*ZEBZ<)(/Sњ1Y9GE>5uXǾ|3iΞ6Ozt<{}Oٷ|ms;}ۘg6ٷ3EbPxapɎm8hD`$VKF nM˘ lR;vt1̬u#xނsGGG-ndf *7W9 S_Ф6t׮ͬΆM cCkЛ zql(uNn ){kUlk׉2W̃:z=[1l( C|GlWv\Od3%ap Q+m+ q|7 +1OMel?*/X=W9v6@ wd͙ᾕ; 9g=cJټ`Ř+kvz0oxd5;BM |iۃߔYDiz x}*)N{*|w䝯~9G؃Qaz]Cfp6r}%Ѱ`8١:\æ*%6Om&lv{{e1p3"cwXͰu;25b)pzf0󇮽`75}}@xYUp2v^cRfeLpzC#?#a(hy)£)7OvtN;/f % DR F X-Z" 73yЋc2XW8& ƽ~Yl$3KkUe#=)=\M,JT*:ډQIL n6硾c!FeOCO3F(ܰ"2VZc7 d)V񞵫®&7[Nn} jo-\yfcs-3`1`PzXϨYY XF{䜗.>[G+d PF:&ҹ]CS9;s sdSʵl,M#X5X܇֤H#}220˫+ 2K"ՈUg3Uc>hMFjqq}ςe3UNgi􃀉S82 .0yfZBYXGA^KL0\g5++(#us),Ti IR$ML;l}_ׇ#mn>RMRGmф 57EMps*|ڢ ^knBP &Tvbx ut<:WjךP3혆 ò.JΈj ΚŰa:;1G4.rfB]`&Ԍf& 83f U] 5#P3F 5#pd0fx 5Όs.˾:r8Tc̻Ig-aYWmg ,5F75+c3N|$29p?NQӝfq 3vtcG8_l(fo"iD\GǦC? [8d&2VF>`,e KwCx)m tcйGu!]t**%ߧNUT#&q$EVr'n>Ke1jX53VF3pkV+(vS\yscԩc{Spq+e#bfPg[5:hM˘,4 pvu<k7 65@րϺ{+kw Ky 5:ߡ5-c+x#gjoٻ%ð-bm=қ~fYYX_]r G3փg2֯G2N[UhA36A~gZ$?~Pfʍ۪"/쁭e#bayg,Qs"ִL {d2PϸBs=f5KgHkXd3K筅$.}^*tܐ0іW \4),N۹\\ DD~PaPn*l{(rb/j/f;r0Fa5RkV+ЈTj2ڜHeJ*ў%, 5:hzW|PзG(t 4Ͻ~~3KO1HAѱQ 9J>X,VKgP-uGk]AUJJ Y1(VQdYR@#}NҖi>ܚ8c)lW4>`5% L)jXhS0I0ԘFe%bX.LPwzSt&k7!qݰ-Ip6ܚU!Kaf,eUKeA`,&f2G >W,evWu36\RY&;{N{}KeA `\RTĽ6KeAPlTr%`# Jmʂgf\`>X* | Y* .sT7KeAqT7KRY,KeAsTJ-A ʂf,lA6b Y*KeA`\JT.pKe<;! aRKe8kr|OÆe-'Æ,9v {JT{ U:,frTRY`$7Ke =X* g&PKe,T.,TǮRY`$7Ke,`c3lN, Ke,FtTRY`B a#4l7փ =X* ' 0Y* LRY$ m{^,:lp`c= "_'lkqKe*ˆnqbL"(29{*]}9lA-hu_M*q$hJnʘ-/<OIkDC48s q+AiXJjtњ刕5d{K&,l, [ዏ$wvpp`^#vj7n yk\͖agYh:c ܛUcjDkZ:+(_s=|p]E9r5t;7Լ`b[+n &-6lϫ9G3^F-ǫeiXLz˧5TcA Yh\w/\e tSe-/5yXhIRj3nqF0jeQ+,ZgaJ? VYOC0OW+| Z-"#S%V FqGC)cr\b#>+enx% V/YDuIis)]f:wV?Uz4ˑjY d^QT1-u ,/gmǶzKюtT7M̢T91U6 =*{Lٝ/'i_>/o´DTm='xnK W!o*jOΫ/B=2]O?TJūaO~9j,ɧ)P0&֮)QKtxO9]385S-\pޫV55 ^ΘtdG0g o4OnXb]r d`?u9as5g?]Yk7||L9TS5_q;Z>2z\ qrɤeNHɤbdc/93뵳"F5d=^S_h_&sx3uYNūT5pɾy@%p!yɫA͞BH׶},eSeS, |bkE˗\VOkF;xFq!E䑳ǵhJex^* TJ&J"Z/D l/*J*}aUZ D s kC5+@(i|ԃ22jco`HL#Zђ0r8xDDJ n9Ѽ.N[p9ԕ."%3`X%&3ˤ[͢!D^oE auoЊdLjtq^ q8WJ+V0[+$CA]F(Zj]1] \ .TV )9 vM]B3Rzt=/ u< :8v5ǫc$euGL :d z/3~R3$FkٷAn>@ @gO$N7m7es}3 --դ.nEJ:cDkQO -&kV7u=\ȑe-bH@>jf55-kc { \eTYNhZmJ Y&Kj95M3Y6b`k"X5 @Y]5+K+Z^*~=VbdJT ?)Qe,3ֺi vY1CFT?\gD-yŌǣ/͈Z?V2֬^)#j>F5#*-fQAȌ![(K+y"hXݲ<1jST3(eS([}Tp (jeC"nj\|^QϹyQˡj@87i-zbXL5>=X؛UQ- o 0ՅRJ{J,PhjĪpunT #T#ʮF%#Hzw9neP:(YV4et0,֭][њh腃Dt~4EcDTOI.(uz12XAAǀCcd꼟82ZFdxEHMSpkZV#V5O''s]鼤a ޤXR| (WՈUg7Kk1ִlD1.99uCE噣[L8FNPNYSJ12VCrkV6"W]K3X(*ɀ&1|/y[\Ao~0D ?"{ " "d ZŠw+5v2Ce5bw2'Kw+;ZF;d9g^ y+Cw{#P-xÉB.ꋂs6~P9ILKZ4cXlJjj@zaX;^^%d.7r>37̈]As$hn/aFoZ8puza(h <:ua5+%`=hN| *v<1S͠O2 [^7~XFiPHU3u82As3#q[l:F0R)#PF̠K,\5-iϜ3қie[zڛ~F>hJ*j s^%hntM,l.<;˖k0#ln67,( p.lZ͡lX[)DM6֬Dr-%㿐ڼ_r5lrFyɫI%:Zn(/9LKVڢ1sO-4ꛡ!@nx\me[X[mFLQ֬U-} T3( "dLzYX%n jRPS]tV_%f. ` :/yN8[[xv.s݇ss]>EܨB0ڈ͠~@ݖ^E".j7MVc6P [EGsT`];diD"IQEtVEkq,/ʱl5ԱJnW%`y07 ҙsֆ!mڶ8 Jg8\ "9Vٮ.UB R=?BaaӧٗKc:װk K6qFe^Kc."a!cPN\O8fe=b>gyc@o JP(eK6PM^,.㰰2K)CN5Z=bK5sm!u][MBY@>"e=b!aP%bܚkҹH.lq8t6;yX|\ @L`i7Ffeq!Kyiv9:]|*4&n$):Q&qMp5^r]Rɝ{)v 5EXMWǭY{b>(ْ.窺}\(ZCHD*,ryЁWA`|E#z&sssWǨ1C6C,,?sa/Zȹ p^0|EA^[% ^ş~ y#U~>:p5#Ǡs:Gs\/&Z,}GK{ jHŶVb۪Ů}[]i=`һ]ۜD{6ٳbR}sLzY#fԦڬCyvXPHA٢2B*Nrgh|YTgƭH Z@#_"I$`W|xE")$åh*2:7a\֒gzE!G5x[Wi,.F ]"Y|8/y礼fq>:9֘L{I| BhAX$o ,'hI¤ν I.ӌ9 QAI&C8Crqi. %9`X @(,gVF8Y(u)cƺ,(/&L-D{+?*$54 YFH^]쥏 iBf{^]z>*tк Vr)uf1[HJ͎ZjQZJ (5If:X/}*(A(]f٭t57RPZo2YqG  Vv_hNJxq\QRԴ 锛4M%I-ARQ} RlԊzFNr1Q C~r2R߃0d%ÄLkd,œ-LSt(qaMHgUǁ˴!KVg{/EX'uj׹Jɘpu{B*ڛf5ܩl G\T9wNK&bG8`uwsEg̐;[[ 򞋺hEK:cD8L .a֫ܺ@ۆMT@Ч6RUW ]M[D 5FͱwPYas7ުFY *KS)vaԛL _zækT'@H;%`cArV԰1-`ɧ_Uji2*ձǦn(MrmJFJ x}Slk:DNG/;v:!B%RLz\S &sGVb X h8KfhKZ@bMڝSchSRs;k!x^3s*±j ¥ 懲 vf Iˁ)-A/JmtPaVNZj=}*5jKkZ6#W~/&3f+/ƀZuJdK- e$l 5CMZiYXka` ^FrkV"V5bgGz# ,ݧ+W)jf{ޱROb_Oە1{#kx^,v g(^(S" 6H%`-8Gk/>`QGSqO`j IĠ&Ud Ѷ,G5".5$ltC!(MP"V5".2gN5~'9Q)y,P/N5(CN5Z"rvMt+)j~^ 19y1a(`iLM*U, [FL~-ְD-e9bw<eV[КՎwa;mrS}Bؖh9,]V$t;ͨUj'Ͱ;96َc٩솫xɰ*jAn^Pu2%4Zώd>HhذRLǦϜ>ewlmwllW]mSIsW߱; "Bv3^w0;hf "z/-#6ȎM倝0l}Dbؤx'èkOYm&C9cSeg`ͰIe3XnϬxV*Aܰ'ކ wPH6" Yסrj6{Ql"%|sL8p$J;;U>W&`24> g [t,<àZ&hnhX=' вcM!t )% 3&Tz")󑒗[=%YcvO& DwGd jt5P9`}wDk%tю}N.G߼POdܚۈ{dVdr`yTjl*j׻aÉl8tkzx]5^6ݩaبl86]&m|Mzx}Z mvFe{=6g!!E_cʷz6YadnѨc%ez-R5-)`yFB'JSAfo|u;{i=S0}:qm}<+|q߄|,l5Q_@*)hǭI!XFI/$fe?~͌Ӊr,"DLHM3 `~8d!Iv Mql\r1lܜ*FWM257w6Ln dvwS6^]h=dL &Y$;pCbeI*Ivi&Stl3In$;ʌcH6Ο;G,AP2 |jp/2xZ2VijR:+xڅWHW|27 w7S'm&Y9.y&l^W @56ȋ`2)5fbnM@b=|0/o M)AƩS5ZAi2Ѩ֤HƉ¶qw8qa9lJ Lz^@eW*⽖5*.^Vt?X)g^V7.в-bm&қ%5pkZV"VWcp*GS~ƺXoj">z!Țt:ħqaC$AI*l4>`5μo~ $VE- d`XcњUbwh*x3s^ ACpmqqFnMJ*%Rp,5?![> bYgp]ٰGW8Z>:glpbظYuĆ۰qó:6CQALBq| 6!Z> q:Ѧ hY> ǃMY>CgF<0u:6 [vdž3;6펝s[|G|ݛ,])`,F Y6l\tgalvxg}&X> F欉;ztd͍1$,Y`7gr, ,gr,Fw|qY)Y`7{|RnY>--&`,H[aO'wg,e𥗀VGkL5n,^ðqi39v +z|;;g5z&Gl3@K|\¹DlUQ,ؘDQnjc{ ^( -߲1@)òXLXepJ5[2СXF [Sg'-|҄]Ԙ>IKiζQ|5.iƕ};\qUEB, ՝~j\UWٻ`Mr4d@D5ҋU>V5ڡ[Ӳz]#tcשfPڇ@)fTczT3(3V5ܚkoWTC$K,mJӇt}PWYPkKD5~RfƒmADFzGʣ`ٲs)[ IY^hq -b1c&QZ2v.9祻8EM<7ؑnkC'q&J~=%WW Z9"2ǔ2% K4\i5+QO!1l/VA}(nG|ㅷu&xpnʔq歌0oey+,[gaJ? VY{;{u"< _. K*{k|.To0eXLbh%~iAj ҚWra )> endobj 143 0 obj <> endobj 144 0 obj <> /Contents 145 0 R >> endobj 145 0 obj <> stream xr_1vbَűe!S<x8/13; .J(QC:.g{_k>N7<~{n{o~WFΨqi}Y'^q^+3K;m\w g/0Z 5s;b_ӂ?\?'Y#<@v:V87VqNoZ+'zy:ͷZ,3Np1M/:nv3Cki^ H/՝Zbήyimb>OZ87߶<,;o4eU@,@NVb&T<!pˢYh4ph_ ,T(1hra/k]VJzUr=g@J.i `ox)s  ɫ~Q!^º@o~SV4Fkls;ډ RzV'm ͹ {vCmv+ diйhD+ޣ1Bu0ƻ~4SN5wx_)2ϋŁ;Bp ]2_ !*@ R$qC,*)bhTՒ{ 8wt q֠E1 ț@ U7.o2ZuASa'x~H_Q.T9@VyڔKH/voPvNi'7]7d*ôަ,K\N]\؉W#K$<̉[mMMwrLTE 6ZSWzɗvWBUGc4d0g)kPmQ4I|Oh`"D y" D!YH>'TRTYRZiEX=$ !SˆD͢%mT7&;$ z#RT"1\.I4QAi "y;v]0JQf"+>X̦I'9xS1},rٿ=ͰG*J0o@9pPP׎S]E3@cw4qճNtdR+éhŋ.G [25YT/J0ZYyX@݌܅񹈶77Ix='j!8Z2E50\qaA\Wڍ uV.+tgPS %xh @dܖjl6i˛1Z 4X&m9&4ŋا@cm? Y7!e%^dJT`:~M؊: olNdia.M__U"t++Zbc^3׈:QʿχCש>[!1)˞b=7eJ`2TdI|8/1 `@$m4 n)`9&qZPa5q.W}5I 8W9m-}Xm+[D?&Y/؈8"uœ؝Zdo\3LO:k$]ڳk#A۰>C:o@f@vGGf4xܡ j#s 98$Y#; .ztVkaquW:Uqi1EJL`K+HN'ѝCRx_,5/W/[c٦w8]` YinJ̦ W/$W%\# kxYk%Jw)E;\m f_#4MJW_]F/8H"iRWg&f,SѸgE8]0^L +ݕ3~G$ޡ_X-rv2WSi ˧ Mfc1mФh6s}=|m=rS;W 45z{c{߶<)MwVzFE>ki^2;-e^{~}px%p_>/Mڪ T3Wި+` A 2ۮýMjG|y*? ~).'\ i:evҶZm}N,= >~3n[>-1U?Aاv|:!c\:@Ͷlգ=+DVsVSis DH̤tK#NDaA7Mh &~$mRE?/>Fy ?]/>w A6sn s;2SK{Sm>k:O/ K .O6 YP]HR?][w Z -GV}@ZY!q !=W"-G7M">(AA2O(endstream endobj 146 0 obj 3082 endobj 147 0 obj <> endobj 148 0 obj <> endobj 149 0 obj <> /Contents 150 0 R >> endobj 150 0 obj <> stream xr_1Ӯ ㍁nT8ЇRQ$E*%re>\Ҙ,~He F΄lxg׫Ou݊7W-S_ ϮN`iμ9yFZnheV7zb͖3i0nemm3~|55?ɯa$z1/ӂl2[~arVmIys)duߚi1''[IP:9^aeA' 9=<LXcڵ]:fdNYw^{׻z#+7pӍaiXyӯ­ok5W2"}뷅Y_$yZ8:X]&<=!htI}&F?\;  Zy8nZ w9R@x$x</|qflV oiۋ H7q~^.;p.!@Nw#IQKdN7cZ\tڋpݒV̗9rvT2_9FWH+ObbZ* pRQx4M$Amw@R^#PEl„,M -cp_xC[iZwM'`[+$4oفX/2BZCPH=p~9}h_ƂiHy=OԢ3oGgF&Nb^i2<)d Nt^$\vf9hw[i[G"ct~Ic \3vY|m1?ac!3^3k˳2VXv^\zo/09)7䤎Ri:ig?RX~L? ~ ;o'L΅X* wph\s ,V!o ۼ[67crzE9cDr&gě7y}?LgC,(dI 7$z6:,އ;hG/ or4Xt2bm pJiCIrrrO8o=)iE p$eDLz4ym/rrG%iFV ᶃ~܇X~ KxU(HҒs7?A[ N?!r 7ܒ|y‡Rw2`k!3 UQ+"G ׭1{VP`US0xPM=DafYg*  M qJsiIjؔ |VAgznC-J_tkP<~bwHef#VnR+z-IDR<6܊Z !)ňU|_Q[ߦ tE]Nѡc^vMuy1!]"NYԆ]ߟn) y|Ž +zp('"W X"rqD>..<ke!Ŋʹ)eҧW0!xMEFe=Kʶ)LYӥNq9xY-}^I$󕀧wQLxyugȂM8M﹠V9l+Td(u>"qBAƐ&\\7.X4fX6w]|"הxՒل2{92d\qf_z^£8J*+9|?x =^zsrD[qhGxܳGm&JYR1͘,#=oG7,d}qYxz6ߥ5tg L gbQx95HO!>t\ 09NiAYLӕ2U1a)Dm{XHhG.7"8\][HA9C'wen1}Eۮf`%T*8]nڲl ejV;BQx=2}xUrA(IZɷ3,,_};M 4B=^{T^`kEL!¤ӊjbWV1dD}-.J>St ܢΞ3-UbqOn8O?X (QwW qfPltIKR2OL.tGbq) OV~F5~?b}!XOB<np$&T`l1z @`IVGAMnJq!J=}B/A1D |}F9^0N𼫠KuĄ^sҥ\cXī'+e>6:6uEL@@CY`;"[SMy[-l\j'XR<'hQ-p~Fh2"R6ϝAq/]~_Ai?Pw`TFsMF~֠> endobj 153 0 obj <> endobj 154 0 obj <> /Contents 155 0 R >> endobj 155 0 obj <> stream xr_1줴#oTű%TT#"Jp3Diر2/Vgv[.Ver:I}WmOi@~&;d僚#'5(!yW߶5  ޷vWIt"e w$A`? sE`*.RGP=L:# ;tӌ0ɊOmkuR0LX, a {9H6IPM 4@f.͕;MoV穵Z` qU<7 !ۤzEɞ@fmj,w^,`b06\w ʔ( rS`V;tv)ueڬ)`/ЯS:]27g`+/A492+@-ڊ la@PHSYa^^oX+ [k܍`vYIf"sP]< 9?"}Z5y@5тAyYc l8ܺDG/x HX{kZuP?hJÞ&,#z5.,Hy<`=Ǜa;vi7G +RYsӫF0ިՂlc<ǻƆZC@$~gzb os 0A3vJBgv;NǠ5)XVT!vUF Uť(pE!x21D9R " ؀okO(оJנ,5y-Ip/i)9xg4wJbp)8!lO1'j65{` mk|#p_C%0}ϧc"I;Ϩ(OPk b/GI>?0Td% /-4gI9вSE쏁IkiP v1I^P4^E!v-k>._8S8vHhS %`F[3Lm%pT )4sn5Kt9X)jm^k)|Y .Dųҭ"c=tַwb`4(p˖u "iXFRlw Eoò@{»օ'ҟJt/ju6/ rOaI6A@ ži,v{vCs.K7ǺK=$)*`PEdCR`KꥷY&/w*A8<.0gͲ&yODzerϭ#1Ӷp͙z)T'-Ôh601X*! 6/9H5V8 Y0T}ggUduOb;텳$* 'Kp;F7t\00W"oS΄h9%".c)nruV g^49$ryT99%1xY=F)A/\L V}/jJ*DdT>p3f>aG?/,E+ ׯqL e@sʋL \N\ TW$fEd|X\k~RJVY!DzirhkT^gy򺷘ftY& 6ix^"zEY@[p;郊K4C2ٶA\ xRQlz-3w Jfr H X0t!s1 } ')(JWئP u8  ")XK^ I<+Ln6e^ނb$= ]ci7j='I#R_sJ4zTx'3IDr#r>G2`*ws\8a $y,0V3UK/(.I1u*Jx&`  ̗RwVUJv E3qHVSc@\yqsd{i+bҦ4K7X_3_L'9)u:,uZD.ɵL) a2v(f}*f2RC34)zJe8UsЮ*q4QLMF.ݿǫ,jPm ,]I ' !11ճ(1̕xOh]1N'_,yJLQeݣKk[ #dwe_ʂ':h46`"jnTSmfq* FIG?ŀP[{ia8~(:]+CYB$}{V*PG#sa;fߤI9xa A;$4}|' _ (i2ۢzʕ.;`92ToQ =ouJKO m2\/ ~%BWOnWug055pN֢,u ;rs"@ޝ߮H'x?0Bİ!KDWkmU߮*5y&usU@^ vC.7ێgn CKu7&%0e> endobj 158 0 obj <> endobj 159 0 obj <> /Contents 160 0 R >> endobj 160 0 obj <> stream x\Yo$~ׯ /3N@^vv `1:Vr,idi&YdgcKA,<:bϏ3Q6#?6NF.#q׿٫c(鬵zvo !t&UPI5;>%TVJ]NjRv0#O߹_KY*-=ux‘i~&8|+qCpOe a97j|z񟎘vv͎9-Tp:#"3Zgoz.Rtڽoq3?O7l:b,^n UŔwIfz^-D'1tX:Lj&k4(ރnzv#j+8hakTϮ!,֯MZ_Q7k?c9"|,s8E/gRwJ{}%l_$-wXX2=j ߰3#,˸)Lؓ#uN;x8T{y1ㅧ(1>ȭ@zg MnxV,S@;Y`[(JǶޭ7zo9!DOoѬʙMt4hSʦ".1aȾ,:Lof~^jU0&΋ԂCjvxl`e4=ް7iu`LӋJHnmE?A);8Q51}DmV0Nm6ؼs[>Gj.2߃'` }Vp"$ ^'dG) 6M #yM7`d. 9z :^MнzLIΞ{=W!Ehg{.sa8Cx²:g!-D*q^IifIdq6K뉥cǺ(%sJǜjG~>Q94 @LNG_ ~0#5!ݝ}8HN-H ɭuyr@7YV^=H՝LOsha45Nv=)~- nN[Tox {/`́uslXNH[wB%k2 ٸUI]wB:exu´ٸ{"G͙GNxhCL.r2>>UoEK,"FŬ2Qʡ-}^2]F$oPH@h-&7>bFYSs-wnKRP \$n2-I YF,7w0m#6[(F@YIv=Zy\Fg)_)!w1TrGfqzMvRj_Cu, kNdtԚBzWކADvZ SG *Qz7@MhBiRIcJp߃ALj> bdBwF2)v;|u;5Ը| aUw3~`3L Mf33 Q)1gi /g;3<-2D ٠l V=i:d>COBHq =m9ةG2U0_ M*ѭ_8 "%u]WcUB $A' a!~G\F9 OAG=l!"ޣŨЂMG1 QӠ>̆ܭ+{[88XnP%&YwAߣE9Yn9]ha yX5Dx$3'aFkmp~;xiJ&JB:pIczo |dBCG-!y,쭂{Ij ,ۖ"FKe/X$Zp_ڗL=Nŕ`Rv8s1D2 _p&%o=TmQrE j  D&¬Ք3xLbk^aq/;dIaQ'h<8w8j{e hxL;Z81!80^\yiBM bH_0xVcĜ??!?n85_ Q6WD3l :=@G:J9? yc7pX|:3 SIǻO0QnFq6f0Wy؈e LyU2gߣ~(090ozv :Bh26]#9VlԒ 1@r0 Ԩ n0J aoy ƒ~|'P}i%)kBIyjn&[ I9_w!P7kc+"~v( }Σ_PkriYf8Mh~DS[|]ab6L@ UX=̬"\V)}b)_Ag't漣hPX7ɷԑj (f\xTB"%Dk[e )!jliỴ0I,Jv^wcҝQgC2V@Zfd38YDe2"ɩ"r!}HЉ P-(æ4(D]*10w,V7;+fN!=HieFl\(qH %pl{iqEDt,ygH8f04 @V"aX* s ie03<LlE x1Fe^sF @ɴ^yLBkhq,鬔'nYLW,YJv~/_.%j6o+<@CMdHώuJfv7j4Ќu'M\ܨQt`ЉN=4yzzP=aI]IIQUECh[+3ѫO\{s@^pz-4{CJhLS`l4x,̑B0 g*-Dq{?jN(.&ob%_aPID;W{?W^>e[4qp2 mDCf2@۩T>4$cVWNQwےs%-55 A[IԕpGS]+pAӀ+]%h+_03ڻ;B?ڧl^e'YUtqW^x_sB)`@;7y&ε Ufq0kꧣ/eFr~2O 7<~~.w~5 yAr@qѨɍYՄ>NX.WM^4XEK6ېJM(`;':a4aiH6 i' Lj*{?vfYUO1.eq 3 |b` }Дg񅒳@O?y*L1 iM ǸȞI4V8oSpQVL#l= W4G9ؑǬTiͣҦ&GѼtSȺ\ Ʃ^Bk.ՏTGZ`շnO1U=V%Ԓcw n R3tD n!EvYUd.TUUΪ'gXCr818d F)Bi~s5I<ZYr[ OX;x$R CR8{ aZm[>]GppgV?tFL401?aʐ)CW:~JuJXcK6U^@ud}x?KTF` zq#NJ%C3wɦ<U!0ʂ"񉔾D6/X'{p mi3AC՟gn>.C Oˆ*S7d`,~fT@-LJ9kPAnIH_Sendstream endobj 161 0 obj 4199 endobj 162 0 obj <> endobj 163 0 obj <> endobj 164 0 obj <> /Contents 165 0 R >> endobj 165 0 obj <> stream x\Ys~篘Ӯ;܀+yp$%8>RQ\N\RKcf4f3R(?>n|˷US3^5P8G?Xlv>e]dLkM68ûM Q޵9 _k %X#@ =j{Fւv[FIlcBiM/]~7pP7 B/~zH& i|%mZGşgևk%iIvYpE&o$Q0tY1u(e[*=-Gpޮ؆>:lϣkxqt(689=8@lV72g[ދ-kp<i-k^[<`AzC_`_j|m25 #WB#y!h( e&E"^Ržoh0m"~6J&ӛi,SE/*(Q`dLQ}2Q|! aeHu &lÜNJhlm VWiu1b” uW[ kHb'[Q|'$f#=hQM.aDvW,Z+.Ahf e3`T&9s-*rJL#zɍjI9LC<؎2-M c|f17Á!>Sp6cGMT,~¶rmTl?t֔A= B-|oI(۴}C->Tگls}{ Zx`gFn îm5th2gƝ"*}ߓBۢa{*R*dnφG kC#dce cβѡ5>f)e+~Y`( [|j!¨2eϪEydW 'dh vhT3af1\S>&&+O4H7%[mB9;&Kv0]-zp- g{'1=̊HMwXe_eh|5D-@X|RР͍ZiZ B7 L,NDZe'ʶG:]Q3A;붿r ^tĩfm݈heKNM oNn%u/ı- NϷgI9j2h jzؤ"(ett:]ϊK!s'!@ ֩;Ihf 'MAx#Ȳ 㳒rSӒ  B'~c.,V-^#iy/pgtL&i,F^giR\P;c3POzK'4E-lO e~eJ@ RWy|jY B.Rhw+ {l,vQ  *\ ꞓLNbA}jЖ #=Iazu(ey%!Sy8 7]aL[v{#1{Ƙ]q6uZCX(?SJLR~UO##qjpF]|3۴_=GJ3߀R\i#Iepf4@k#g JlvX ,Kb3(V#l#IːLE6 srBμg.M'Ect/ʄEg#* 4J(p 3[JJ /$BG m$=t)F6b"p8l^NU -F3Y5bfX񧣄uwIa.wl>l$TBSܧS8brK7s[QV0DzkX";-,:,@6 VY6so|4su{;015TR| u"~}UF{k*2iӸ=`3&<,ُ;n}HZ /,c/j ]oGŐq=VVrN oï ]V#".la'PnCe$x& o ~r5םsiTq`_W'wnzʧe qY Shƿ#}=ˍYթyg^pNXr2K{ccW9W΢Yuhs E3ↅg9,#CrZ 2f1'fRKSTBq7D3Y%b_#NˍxHlEA@a,=ai:43s?,I*IT O4 ;?1OO=)+d8F핂d|r P0 =/ִendstream endobj 166 0 obj 3536 endobj 167 0 obj <> endobj 168 0 obj <> endobj 169 0 obj <> /Contents 170 0 R >> endobj 170 0 obj <> stream xYn6}߯$%om)4 6I:S4ouHpE12ùSE:*~={JW3R>Lݟ sj*J:kg3O !ZI)Q:zvZԴcJSgVJ 6OVv0# LԿ6,f~i1[{NZqغ!pxV0&i_u-cϙO3&lGZmmgY5_<&r;`* HXf@E64]Q9tFڊY|͎=Cj4L&eUU-zGmU:~ݴZQi ,r) $M-#,p@?hx),4;lX/pc2;w$gD70m] p7%8Ou@w]L+D,s"1N଎:8L%r&:*Ef]׮ .Ze-zcLǰJ CPn)V-ݐcoʉU}AiQTR/ϰNrS mÇɆE/XaO4@D/J&}!u83Y`BT07M߮,=LB7%8{ 9;+&J8s>`×-Jm62Rx3b-Yw^,y:Jl1F1WDbؓW d\5Y9ij3fBN˅ M=8 \e?nRJ %,eiRq0`=.sݏHDURwa>.t@wݰNRBuoSZhoNJ3s tM&tZ ] lo2q@̮@BRt"+@oa=![P+ A:)8t)_iY:rX[PUMW)" vz3X.rt 1E28 W2]j5sY/@DݤԹD~7gʁpN 0x tǽf+ZGUIh4ԳsLP_?D2]3ƍ+a-Fm0Cb(O"|~$<,V(+G*&AģaMuXxTQ@X:M G*n|=ߋ[x.r E.-3D6yC.txxttٟa2'˱_?GPO7'KhRgɾV_Xw+쬳n`$bW8 'ʈkOR4j<`RT& 43xj5Bv["Ժ'4{Y!1< ƭbv[d[z-KO2|QR2UGT+Ɇ Uwj~`N3C!+.գS9+P+DST1wŶ@ M> =aS=mnJӿ)0,̪] ge_p_{}/ָ/`(Ԁ|zŝ_)n͑$1`NJf?NJa{Cn\{+Z J7+0+<ͪ {QRK# qf~0eM7h'H'DvxVz3Y=  +?M,FYDpDIis^bWu)M:* Va D}y*sKyw$dlpO }Kw@ONxD F/ug#Bt ^` ܭH(w5'B ݷ;&OcV@Z3|îm=~>d/g]?endstream endobj 171 0 obj 1967 endobj 172 0 obj <> endobj 173 0 obj <> endobj 174 0 obj <> /Contents 175 0 R >> endobj 175 0 obj <> stream x=r]q;Rq(.r1?Kw=z%.<3=KLOvjf_{8z{ڽ~ݳX#|>%4M;sܽxFO9jw>,W'j1!j琲wߎc2'~I$^7'q<_4SMG#`e dhtwq,#ۣ1m$d{ oh-wr6 ]yc8([Vhw|46 P$)7~mr.}zwls4$-h뎮{Σͣ\Щ?~({;NE86SHu&^샘 O\I#cJvu*8 a3 ll&=|ӏHHx773in_J$ѽ}?#1<$/*ttc.#8qwW*+(GÏk)!Id+yXbGr%bp ٥{oҠ@qpÔw \3N;1`g $%c$8֎DW^Z$2jK1oD,K[Oss=H?}29!60 2趏1cAwi>ƍ]G+(-#7dq01M?`u:ͧZhq }Ӿ1 ܘo7#O|c:өfGRa|W;F<ӢS;R)Gv opg^H>q-X꒡ -еx~%-Befr=lb`?3Fu=r&-$CD?| o:!Ml[|RU}`>vM~m%2wUܙ v҃t#J˂԰`!N4޲8䠐4mBwR8)enc)M\#r)96f%)/y{ܲnJI&ΰH޴ 2v:oh ?PCK)T-`,WBf-65^&e8ߦvI)o]{>RJZ9vL{#Gz'he{,T6دX\ LTiuXw;\b1St~(y^X26Z"~fua,s¤Y}%/*Rϫ-VԆ78EWv;hk)Ѽ&FP~09k6o 穠#u薤}Ϛlȷ?~\Dx7@#odկ{[5%7Qiq|fn1~XGB${,P,qhp3 .@ܙNgI.0S1\z3KZ\ע p KQYVlX;y=?4~t`xo}*ټET@u|k_a_ 7ByjzHQR̉<y݇{Px5ȹ۽~D{ZzwحGw[n%OK׵z)"-wϦF=nn7KQ#t"ۊҞsMg:BgSPw$boECb?$؈~Fh%xj6]a; v4jib4#ڻ?GOtW *q8%r\)*D^edB8zngzvݏgKNO=Ys^`#,;FhJ?d,f cґ*> dw =i'TXGrcf|M,Ɛ_zA_/]04>ק$5KcsāpxbH+[,]ܚ&ž4r5`_z"4pR#49q),%}tH:2b2+ӨEJ,.x^J_a14t4#|&B]7lyT{Y6Gj&W-D 767RuYو4R4JN^yR}{ ʟhrӤ̦<H06&3HI$ !o;nqχ&oy20ka4LqmE~hss/Ȣ~`/tbZk. <`5&s2$ч$sVjIol@-;1YH܈Нlr\+tANZsyfhM}Ӄr&6RB93+͘4?A`tLW QėltTsR`% ;sbS/(p7L.6s\ۊd/VjϣFBO Ǡ=ovG$QԘ5qr\yFwr}=-n+ip+Qx>lp5|GgzQ/vW' ${`Y^(4_f0s|d73$9Lmp jPC.^n g۩ZA){?MXhplqj6̃yni"8P] e lYt~"3VG إ2̜؈od v|qwf@!<)a!6D9B|q\ڣ ̈́]%]\Ja*=q; r󝉩(yz1f0=;ecܦܜ(6ԴvҢ.XsWoyft7̪D-ʱXQ+_2OQ)CRπ7<3ֿnvgPt?]Ccwd8]Ec9+X5n n tf-61>OҀ;+oN\q ѸK-^[hy1: ;I7xA9[^o+^F0@BoF:$>lyQ>YȠ{VܭW1ciZ ~*Fj09 ׳l7roOt . ܎~|;@cS=-aVa :C`<ܯySD/[/ms1y3nyyǯDqն5aF{ORo>h*ڪc~MdA `}qZP$)f1N|Tݴd%\%i^Z%H=3=ل2؉oNJY9c?׳.^HQ&X{؄*yšBt03Xe"1W!⛢$ 8Ì~,0vةF?֬l5)0@&m:E$ . ]m1O} 8-ZO6-xco{K3s<~I?u)u4([ IEm]0$L@AOw*s//RIhDLEܴg&%݋oaL JKRD4B~R gJʈؐ¨VbR^KxF zJXb)WU."YؑG..`*rbL@5`c/DYBRї^^}ۅ2[n`J$놳p*fg lǽڪrg #B=[! j2ZѹKh53E2b$:?;#BŚaG%p0Q7b? rUx^UqC{/*۪򼪐ŪjK ZqxU޳VLKD&VBg Ľٖbprѷ](5z ق.aCY';&zQ6gQB6[# G>R)L7Yu.,SeÛmuP_r>/mRLb{=X9?U$0BoXj%D@Lb, 3E}ig3nʋlEDr b~NzREb3^A`(QѪ OV& /E]Yji҃ %jP`K%#@ I82B~Ung`KSo_t\W˼(T+Գ֨ jSJ [q $ux/,28o3,`_ ~~-b ZK;46;k$$`xWM:$o#hp9-,QOX[R\8NœL X?Gll$jgjYk{"JJu$j4sCĹՖ"-.c8!,Qql>SÎ~A)h'&qӈ]}8u {اԮ'W42 WncERNgŏmR+Gƃ%fMwM$~8va7cf];b/xA.ע8gcAt]jқ0%{VO2$$~@݄UDz6ͦ0g{[3wL` *E$ʹ@Oi.q~7ۺ mrP> ՞ă7\'0:nYSF9[6Il9ծ0' ŀ p~Z)͸DeTʁb] eZ _ylZ- vt<79 C#1[SlaP-_- iǵEVS5NL/Y&dZ4iĞN8EsKD/ ߵ3ED>Jq[ sxSH<~ WjLO̚4Ju{gIT˚6tY}wH;ER=DWJ=k_M~lF"S?-OŪ}mɏކިmpm 6BI >2:{XB=AҢƟovՂՈɸL?YOE}'3n uͿ')TR06m7M[ȜJpʧtVnMnĕB d7Up_[1+2{k; d]pȆw\q]P)xY-^э-7Fэ-Sjr6&)z73 {pmP֘Yg1}*Jg@нֻ9赎J@[Ç,ЖD3/OqRAfȲ^ryiZԼ̫S&ޑT},DP- ssb4'ƞ(: ա\iNQdu' B9kݬaÈȀ.T!ຑp`ů#5Z2<Ȓ-Hh|ߦ/OL|V-y4azߥug |5'?\В6KI9\轔Y٨w)EzRd q BLĈdVХa>i끥< )MO>#yYp<.h0VٶNr=IhVL+ʢb^pnO| O?;p e\ƵQ&nC' WVJJL+T,IuPP=pL,` CU :TTFh#-Ugk0CeQМkD}ę^bVIXۻk4;n~V&j>B'd{/N'뻋Í3PVoFlbo#M ކj֟i^7_?}0[_ko1+bTh|1*4/9FEƧQQ5y0jR‰&5h:PC45 sVjCsnĪ3YX<gll SS"m mǀ3,wɰiپelr ܾ)){5j@ qZ&CI)/~/! 5#jjI@Lp*E7Bg0U@ |{/_`{`jY`A-4Yfk0£8uSF?J5'*FūeUVayE䪢WĪjK(WXV& -}Ӂ"mvXPw$bۋI+XIMQjZNŚ1rӹ F<#)LXFʛeuDH`G%p,[ j8t}^i{15DYE+2J/gOhCg o4=.uqNNd oz^?Qɸ+ɸ*3hLƇaUҏD=1|qՔUv@ދ aXUMݬXwê:bU}D)U;9+[FejΖ;6L2u_ajdW\t2ݙ4*6))}UԔXoi5sPe:s5( 6Wz&tREB-󬽂#v+UX8ũ2>L#~֧Y%oD?R7=dm4MEarCAy|e#,]ﮀT-T8AFۨ OҶ51$jfuPpPޠ $Fڻ銬 3MG-쮙"|xx*@a Y#CIHzۼ^["xc m%MrԶ1i=޽Y2Tbi1{Jk>׽we͟L EnʖHjѪO̾m:-ZrTǶt ȁ00 N4spڃj@YGb}5Kad\ݢ!NxOb.oaQ6e(Icbfbp|\)/=}nZVkwrʖ_w6CElqHzxч.T=+t\^+}{?<+Jtendstream endobj 176 0 obj 8447 endobj 178 0 obj [/Pattern] endobj 179 0 obj <> endobj 180 0 obj <> endobj 181 0 obj <> endobj 182 0 obj <> endobj 183 0 obj <> /Contents 184 0 R >> endobj 184 0 obj <> stream x\Ys$q~ǯoZvHAK<`) YwVw`a{32z~^ Ż>\|&:{X}s M݊{oWI)[6Q+ͤٲI˵]Zn~VO X 'P?nv\YKm/Lgp?đpfAwLp1|Vi:4 O\z?yK4hmBmij.fA_FO ֗-q¬Pۛ 0Kן⭇}`WmFݠ%َGFz}F3jRP=7*g1nFKX0Oɞ[ش7o*9`hD74LsVH"&*إF F7N%| M}6/z9w!Av0H#*<v2/ƐdH:ɍ@ 3K-ڒ:Ey s}N(l ^ BO{RC),ZG ,og9fz<#ҡ)AL zH;ȓ ?ӉJm;bK|H~\"Er@g: dRM&Ln a*$4y m/Za:FdzR/$cow}rU,xe&^RX)[Z׾>B*AȱICa1 NfKPgo1>A+1ցEmb?Tt18lREMhaf&/?ƩHm<&p)ˇzO]O(S@$I9ۥ gDs:@g*9ył?C.l !bAPECN`-~%Rca!KUBU^%:ON>BMR^rK{3%ãޛ?Yk#  Zfr3xћpO,ޛSežwd#@^8;$ w/#rr@>|'BZ+`-1605>h Of!Ad($&,<[RȥI "Bn;r[Wm467Øc[qg~1/թ k(lYNG}Gr8&J @_J8.Sa|Kӄƞ[r'Ee.uoj>V鳘TT[wBlSuuw{# t ڢb[~̒rypvP+t듶NY4NmyI^qA ǵ3mBjwL!B'to r}p!♪|,9r1WTaf㰼aw&&*aZs z /$iw nQ 1A(?`^}iD*xN+W?\)B~[ܻDw{h 4Ȥ56JU4WXJ&tى{ k3?iH?3<~WmxB$C0tA rl'm]$$2 %+u 0ԯr旲D0^ GRO<b*PPL:VۦqgF/i0<ҕ4bՕ4[Kd1 xg*+eU񀰼FE`}\2X'#e5ndmJ}T>ri ůI \YHiDzH{ٔi,ޢ\ky`؄_csKC;wt'PۨzO@Ϝ/)SJT3n5DڸwuUÿjq59z`:hKe_q~;FؠE>w1[RzEHˆxt`c(C>|^W-N85AlV[xy@`.bDmvpU;)--Qշqޛ'ۄ, csمe IU v9Wбy sR(2ЈyI6O#b=!!)~څs OpbN`9إz_nR5Tȼa|CcC|ͺ$^/փu;hU]y^~1/m8 T<kmLErř-"H=7󑴟/8dlϫ}ѠkёH_jP%"7w M; j 2&Nt->楑_{DN?b,Gt:b>~ J\~e;STy ;Ϲp\tARTQ[M:TI1MD[HV}˔<+ydz Y=Ynrb^җqWlh ?鹢VZ0O]IXMH.]I7'ay"Jz0iv;0>*j;x Xuŧ7haXsNhZ!R#*:}®u_NZTXIP0V4 "[1^eӐjo+imWK@+plqH+A,/X A2oxCMp4lzdOZRo(qK:A.)^!;,,w|Hq)HKh^R~74R_(RO[CwQre/ @"endstream endobj 185 0 obj 5184 endobj 186 0 obj [/Pattern] endobj 187 0 obj <> endobj 188 0 obj <> endobj 189 0 obj <> endobj 190 0 obj <> endobj 191 0 obj <> /Contents 192 0 R >> endobj 192 0 obj <> stream x=r$q~(8u|(!eXK&..K{κf >lwYYyWVXWNn^}m#6]pfb~>{oGI)[I٨q3k39llrmymϛN~ O\kse}kn0M5dą0t 8gJ1,?X!_3~Zg/קxFMhki'}ga; i37Z0TOb!fw\1&m{Dl]5N[hXhyPn0JKV;yS^XJ㷭-jpJEvᡟxWF(P= *PvEjCѫ}(dG_)+&# EĀZ \md r>1ɀ6 [_"fCϷ9{~;vQՌk.~hYmY1h; miaE( -HhˁOw+*Mꥨr3C6n(6StQZDIAE)==DM`?m\XTh捰nf9eRxqV\f&fSv^*YGV0$1;iD,(Es<,鳳%&ycX **3 nTsw4y^4 3O!w4ΨTzpߡF ˳y=BU+>Eoz6@PN\]͟ff;l~7Ee[SUC!gXIZ_' ]2zAEHم¤Sv)B*QVz>Cϱs" ן@|1OR>6s)T ~L(&*gܴ`_(,YzXR/%쮫oM%RQBʍnЁuH"I`d=u(VĦ p WaGET "L\iYw..Tp La{gm,(ifDjp,RQEa҄zQhmR Ƕ rE; F:I$Ht$H~d!걀IZo=-'$jbQM7 Ӕ|l3j6P'Lq ̯   M =ىZ? ; 3Uh~_ -=$ 3-pI,h:X8k Ep[5 V.ڶ;U561E0Fb#>>ⳁrJ#si tZ_g`kpʑV^QZ,@cbZ B0X8ͱB.O~|ïHFb6Fidh]Z*9"xǃoy'|)ݗGȴNJSqo_1n@uho3r#Zʙ nk8RB5 Ң>PhEmOAjkL\6E1K^[0(`6Oxь+;b-:'8} *ۼ^7m=r?RXc :u`n91ysAVc5CQȎxߤЊ#lu2KD``9\5VG?TJ0biVl1+7,^( SYh.M'4mOhPT4 )#xȣy0ږ@A"$ҶF=X̽Y(q hqƃ$zȭE{oDu+;T=X&ȳX#if?he^m:QFd#Mܨ(XO<'i-;">s4 Mc] !m>0 ڮ廊8bY·{Q7eŽ5Z'zT~CtB`>"R)Jh*z؟J)7P&3[eL3|a{ͺ>YFlY\Mfx7CM65& ױ|t(Xއ-`@ kع |cƕa=㮤+|AO5;ؑ޲a5 y]9xSN)--I P,Z" YELēԀ`MS-aF~c^m*sPH%WZfCOR".trRbS 'e "gD8puBڢb_ۖ`V}RUJ Jn;%ws}DЏp^'KH&`^\_@}9o쀬ZHT2EqH/B[p`TO$nwm_D2')fL,>ի㠐/-sɂH[4GB&%u֙ƚ~bD*I3][9)W#E!ұZpA"JEޟ6= 0.G0];RMxċ p|؛bƊh'bcN,Mv3ApeTmXJrVWga2҈#${˂Gztʊ֖BQ=Z_At|lcm7HDF2KlM;rBʹj+sj:90cZ{8L+A Ogb79c[Np6^Ny,J=bzɴiխԁ0d |Ecg[ YS¢uEUI_">ʵޛ4#V5`7L[j꽡vRF ]~55ChU:xuh{?8_U<`b%4Æ2I 2ϻP^}p00~υG2A[ n-*2xqB\S1gϰU{pyܿWbf/ihb[NđׅH8 Fjk*v59m"ϋŀj5 O5C 3W79rC"\#owgLavȷ)8o4=A*2ͱJjrK I6ڶ7H-G}a @~'?pVEj\[W1"ilQgwT Wv]x9䨘#"B^$^ָ::[G@9uD}hs(=>֏!: $׻?>ڢ*u_bG;>9ѓ>9T~w|Pbrzf![4C%O(>32/O,01cuTy>pmeȼbDC?Bġbu ~%ŊhT&K=|?{oldYÃ,z~՟39dqs%g6ba_>ۇY~03kf.|R1G$F@Vi)#6}>ĺP*Ȱ$ .[d I,H6sA= pR TZ \߄vB &'KaN漰ˤZrZ&+E^޶ l`d;"<s%8ϛZ|>n ]03<pr "7uͥZj,[!&-e`gX,=˨ ЉW]LFyѼ2-}LRA~55& O_PDwfףqm[I:t92"[b -$ŇՌ`CM\HH5t}+T|dݼi_pܑ3g햳v@Fǯ:8VD ^x |54B--z]H/ijEQwW֨ !W&׹rexȕIȕ9ľ :| pL5:h $X .ie!#Xwx A(k.P<] ?.|,ou66 >VJBV)<}a0Eƿo#^xe&Lx6cE˵j+i3öVa#a3HY/P-Q|^!$]Om&,…dtypK"\H{^B=SbV].5]- r*c#EkOZÞcʱ`5c; HίB`׊^,tk96Xq:-QPnC\Zq W&16[7"p?w)T%B `&nk&83ag8,0' n_\ nwuP[lZj8D6ht芚r|{$byOFëMc0^%l%XK[&0Zn&h&lb]{T 2~ 0O)+:z!i?{/QD!kQk܁Ks-?KCg,b~]K-p";͇Ƭ"-(ET8o.z- A`{Gj\ڠPK@ nq\@}`Z `e;>Ֆn<<ޭCu=( ?v<2!F<%6\~q桯c(Jk㒬G')˓1I PbXLtw{&J%+7Vp9ˆA2v9nL"@*67^#DK]ǜ osvˍ݉nP&Ҧ72ڤZ^WUL<*Pv";^;r b`%KN+iu /iĜVii%$jME8C/eD؝>Zdendstream endobj 193 0 obj 6374 endobj 194 0 obj [/Pattern] endobj 195 0 obj <> endobj 196 0 obj <> endobj 197 0 obj <> endobj 198 0 obj <> endobj 199 0 obj <> /Contents 200 0 R >> endobj 200 0 obj <> stream x][q~_1{\:ز] S.RgE&+.I!+{i33sJleЗٟid|p4t7?v`3Ë$ 5ؠ`^w4rm2NI};6*cnF;q.~OL)./ۛ1i\/¿2U=-pޏ$ͧЕ8gسrg8ҍLao^ ԛ)}}jebg0+4s.JnGƘw"t//QWUP8xP<.znk=瓤7~qPS&.ّ$Z R7uQr)YZتSN8sʙif.p&,M_vW3DrJʌ/ϝ5M=z~WAq[+f%9ieIg8 4 Ri<;+aoQ@0Xղѹb'K-MUnȒ'DQ1긛N9Z46i q0@s=ؙz|楛;†xqSQ3 ow\h2e41w0u;/}8ɋH vCZ-#Pq'%t.<1f\C+P0aUKT:ì? pi^uy7%WqS+jNۂ02 dn,|F3Y{YK_lAlЇ)xjRX_T +(a|Tlʫi~i#* Ju$1 (a/X[mqi;t>5`,OAXr+4My_RR@ԑ^>d^HCK&X*xKAϝfѳ2^Q*APb!yabB( IC&$X`X·`'uTg fWC,ۋ|'ـFklqNGLH/Yg΄}(EδtkX~:! 1Su;t7ED޾d;u!5 ;nFu$qh*G¹d%&ȝ’7l&^Rdpؾ#e^v +ЎfRFP$#r 7^F}:?nH qY'͡vpE2?AKc=G)]rj/^f`<^p3{?JSYy%sֲr6{+fijM?:UOcurTЗ Y7 mשgp $!x'H$kk%oSqux,Y7PVnhr#8֥0( Қ'͚ ic*k3(ָ+i#*( OQ=Zsf)Pi e\rqӇSSB=th&M o>>Y\jX~ɉc$H>ۨ$$|Ppo'JqQ{Q'_PL>{z^QJ<#:bDs~gfpٹYDYM7zY29oBV,o%+Il+&"]<b8 mN5Kyj"nC{UYR8UfYjԓn, X&xqeXvԆ+:wWTL?2ApH+F:'V(6!J5n,xp(j'#iҶ5L6y$G)7ZW2wN%ˤ.e!?y)CpЅtg]6_>/Ѝ`m+K6卆YowºsZ 3w@`B͙qG{i֥ ?{}|wa-jɥ,3쀥H;!ͪ9p~W'n20#l$+M0p ΗCdgLÅdM6ͦvHnjn rQioXBeAE,UYkZh+SZ3qC?[ѽA [\7'}nKs:C\-$w#Ka%ew5c=ޚ  KBd,Ѭm6=!I#*-HӞǚcǸKܚ>X; I),iڪ'qFm6h ƾF_6^#8G櫑9xp=uZRNĶ̮v[FdZ'E'(];Rv3H@J/VQ艟'?P:4ţaږyxc&MNJNm)0qF8<.zXLluR3Ɨ-2,1y$1MwM}/crO %oxh G u5>6b@*ePnɹݢk@- $nA)#*R ՆoH*+nP4#*RZJBʏg{ܖB .Ș<$9p)쳗  wծv`?ɂ0Lm` ۣgRΩ\~B&|[Tk63N0 ~}N_Qk.e#Pq!Qh]j4hFfiai~쭐uKcg_:{{|]F =^Y-!nFHwX-.teS@$+OBqGP#+Sd:[{L?|E?Zyܣ_-CTsuLey+b⥷7>`5;3Ox(dCSk&}GKf̡&H?G=Wq\~+hw~s0GVF=^h]#4nP$L?Ua^;ѩZgMqQSl$j &d1`W ʃ3-V(fP"*fYh&ՙvyQ_*R=U㷴Jw;XT3noY~abcz.㟃$|N^a!iVH iPT)(-+Qeg פ畽974W1&op`,[ +F _fXUM\"Zh|^ڑzdn܂ *Y~)A'>.H )RnXٓ8Vk R(_m3QxWT{!ůS{k0j4_Rl$1Lb X1]P DNi'ߪIOJr:IA$w?Y$R,RN Z#}xYx`,i apDXQ̲w 9N%GNgzNg>R6VAgәl {i?UԉU7+gDq*_W {5Nv?o'3 d|?p JF-4nLf5WxSƂrLEH_*/yMAi\Ia֭>-"CIƾvVWv^rkP 3(NAr\&ŕjD२Ik> endobj 204 0 obj <> endobj 205 0 obj <> endobj 206 0 obj <> endobj 207 0 obj <> /Contents 208 0 R >> endobj 208 0 obj <> stream x]Ysܸ~_IY qcswsXTڒWDZ& @@1t}]ӵ7/wyvi~|ju0 9܍%)X5jl Smӛݟ?;t-Wzƕz?׆_NtƇ%\}8:0iiؓV>p_~7qnpUcmeDO/K. 8xn"-Ͽe: | p ÈX!KZ*ĞiD0`o/6tf)Y,dm@nчX PR ?[[Ī}A0Oq)ozc/nQrPez8/9c 9vbEҽtP|ܠ`0MxT{fH|5ʟb+8sjgKl֤ Qas9=LvIEkGL?aie;.TjV5>wکUczr9<$Ԁ q89@*zFF dN @ܞzy=8%^7!"hn (^pP9 40%Pd6Bd8/PvCa{$4 HZ4pSۗP|2YؚӎN4IXĠ9.P}_ӄPbKb31#I}FQ SQ8H>B<{¥킈e  \R 6bTZO yG+1F2Bn2g Ks|XR?x 5Æ/cSW/^FڬZOTu4̯jR3IX9}>+'Ƒ.GR;IiC cծ \һ8 5ˮT3~ A`=pvw2+:+z7[p(;OL6t]k4.bN7fDžx*[NIm, 6`Et&jFBc8ipm<}0FϼLT ,E_<: -DS%V45B2w]=Wʏ,3fcrT$3`He T!;ԄT$ I~.j(&BeocT"#ٵŽv:$5F&qUH-aIm>.pQ)d6JCYP!C$"z5)[Z])[3Ut K8*ȜJ`B![ &a,ZK N$% #2RL!ka6Ϻ 9 &vW-lY@p@g#DTĘbV3#S_Qr$i*JJHUY!Mcp {겶(0}a O`A'tFJ@[rC 7`-u!b΂!`[ײ~^TjhOqc)|EL>n" qj&2~PQk+QbH8*B6PZ-mt+d ;$c%D82H[;VJ"֎]3Աv}{j1mWؔ<+ˌYVd㬞Mib`! &9Iҡ2U(*,ptBlBq$)(\ȀY Sl>3!D*y5+] @JmC.7N% 6 \fpAwcECMx8Df6^QSϘZ咢;<1l/%q$JT"߉|p/ƷQ"^򓗉uC֏KoMՈH*D3qTHFD[ٙ@?EtB۪?:pQڙ/c6țbx lz;ɾDY*d=pOҘp_Kw \HwRn 6ؿ 67OQ) :Yobq:ن'6 HoGaa1=Ѳ3e22-:pK˜3,_[ A|(AOcN : ymbփѢsr6ԀLؐOA j>w%z pg4:S}u+Sqr%ᶓj62B-!SR(MuQ 8/OD nm)+x1SyuD78UǞH&Bx'7GlH:qj\b{Z Q ǯLEB5ŬuNd%x.5U?Ĭsl}u>ΘYb^Ar>`@Hq2`*ZG2Zd5#-Uo9?ł f贸:ɪ"6ŨG#c@`! P9nn˕OPi #%nB]D'/zeC_dɥ~t=kVB'֜@1+xp]J#iݳQ~c(sǤN%k> !D0jrCdՌ҄v)k2ɗήy:QU3S{ la@.%[ьS~lE\&@DkI\Kk*b5֛/9nJhn~%ɋo1d{1:w尐ߵ|̍4RK5O,D-J{?á&~-}m?j3r ueo7<7R8*a/Ax"1iy69 | MN]ֵ|q,e3/Ux^yo -- 3dlP#MO]%dGFą&/ePRL [^ ͜x͐rbxLa$/xe_})gP'Zd:ܮO+m`2Y:B|=΍:j$!N i+E )mQCeH5[jyDwJ;"G2|:a ?X zu@p\JgbpHvF(PQ& I+ a 3&wŽƚfLA  V\#}OA6MoFth?ة cǛ_dշ+q䪮$.Sb˷'NjBt㰪o$ zngegg,@eUNkk=Qm\$+<]ntCL4D]P.f^Ҹ|tGuu->RѲ}M4k>4H;UҺ~u+}jzVg5nkkS`tם]Qw# K7l0pXpxvg]4{w蛷{X: l4g vPSV\lFA\I.`t.W҉x,8xTiA:Q.H:i^y6TWz,M"߭ͥj dNeQ2o:*< 5)aHxendstream endobj 209 0 obj 5205 endobj 210 0 obj <> endobj 211 0 obj <> endobj 212 0 obj <> /Contents 213 0 R >> endobj 213 0 obj <> stream x][q~_qȴ\,L\NRvܥɥd ЍƙsV#JJ* @v n7g۽x6^*Iig8N9v0vv1:a`=207\_~ OpCڅk f*䬢//bKʏP|LUQ }ֹ9s>kVNz3 T] wϮas8y;`02#s˾˷|FNg8(k`0/f(s~vXVwn/aƊ5n'F%_\xw~i}Z5Zmj];McMM y4y0.M=̴G m{-b?o-4jBL^ϟL;Z)N#LX3@iϼ%{``:n` 4!%}{lTJ"z4M@d!WiuUnR6R{[M:@<1LUXl Z2ϡϰ]FIV#uh4^ңV+Ve3q$sniAJ̊F6m8R ViuЭ3IhaYqXF)KSnoahaڡ\&/U ۢs$PVapzeѮ X 5KN3 +Ŵ,xnNi~"Q5uHS|K#U$3个^%;ՒJ ,lbK|́Ve rӀȒ`w ^-ȩ&Q iIޒOQh ;Y{M5mBoɰ?A`aLV <#A0۩Zv< v(WTk>o04ojccjN~{Gqe0+1hm_4qh=-.")>^\K/L;Lc7B+Q~9DfNevIt xP^F ߧlޡN &L̿W6(oj݁+QrTrמu}Cz]N_S 3u-hF !ѹ+U pK.)_½c+t_8 dft[4(:hopsD{wۓ G,^& 'ވE SG'E6cjŬXJPd~~Ox`_!GP$Zyl$k;}h e>ҁHD2tO"Lngʎ>^23 9ۭsSUi85Z}CtԐ@xk2n3 Zt7넹$[[a:=MÉpZ'%Mhsû娛K\W{\͜, 54y|M Z<&^cܼ " wNbyѮƴ+d9U9U5¢Rb":J. Vy U32zPX .^$\삃̚$dyƗ3^7ʡt#?bϨN>9| F˿׀ ORJૌbx#"7H{hܞ*Ҥ"}~O+ Ϫ)&g'NT6BbLx0s[_xaV?ߟ'OFu؁@qፚ^JOS+0dӿQr;q`?h+d ח%-OM }駐2| NWRy;)[VqL0t%|{.вgN(ϝ}/ӳ``f9u0PvfX_+ԓy,ͣ4BpK菜'c3{'N^c:b@qD{ e~<:Il܅«%~;r5GNӲ[BI9V4B܁D4EIIHEtT#G dNva. A;x#Z!VlsG %RouR6ٹ*f1UOE^ `56Q#?V>aFi#J-RYA 9I)?t4@O˭F,6a ""K.!5LhѬ McɚL;A_g>~UsXeKq":(4}{eĬ-?ا}۩-)Je]8b2(ڻIlvI竖9ٹ!3bZ hȘxjΓʒwe eYgj/A^㱦+8X&VN:ÔdlW$V J5^T|ܢRGS+ Ef^gzx"R[XCҡ~[וΜni'CTI1Q)dz 1)Z<+xsKL*(|~[Qg5zJ`׶|ޅkKZ@Fgٚ>; a>Klѩ&70mv%fuVѱ&Xm: $-.uJuM~7/]uJRbB ĪH]ph%Pq -ǎ+&#pԉL N@G~ʊHPݾ̹l_uϚ~Fi~!*ƙp >_t-CbKLjtwuǮǮ#4bp>[b/"(';#Ubye!@͑ˆ@j} &Y aL:@it!Aj:H[RX>n-0TԅMU/ Rڍyi)Ey$ͣ4ERZ]SlNLY/ S: L?k(ʫ)Oq3*KeCHLZ30_K*Oي )e)h|tbAol["S_qkhAgOʼ\hI|BIZ}eL$]MR=F)*<^;,O۴PZAy4k')Lۏ[U=U<칤J[9O7RV45z9-NmOvʣלix2|'eG鄁QLt[VՋcH~YiQȽ, 6 Tp%m+ (^}^{U@D?SQttTV%:֢QiM1#Taы0h)|xPazG4vS"a TJ (2HCL pHnAɣ]B`RHT4&gY ;7Z< iz=ΐN7 ?,[ |,ēŨz(,RL.R--uYT׃xIDrR@4tK`]K ҉"58#W:4}+5aOWrӣ^#'\H|<2e4.l[P1\S 48~:牖)['TC㷠NfAWw'FLijGK^I iHO@+&v~;,[ ڦ58E$ͽ!H[ҼD5=pL ~:89r)U]TW%MDED Ww2wae[eM. tMp5sMϩYB]KDJ4'mYsH^[v:)[DޮImf#.fͲ)p'kQSfDe",1H$^vL$g< mH^A" H,Rtl1h XNDKTs*,A?g wa廨S6Q6SrƄDwe8Dp"JT|"ZKW%DJQ"7W<_C1#9z#'F;iHI:˔:& &%G:$FM$1ED0$a,XgP> endobj 216 0 obj <> endobj 217 0 obj <> /Contents 218 0 R >> endobj 218 0 obj <> stream x\Yo~_1;w6& &~Z^RkB$LW_u}U=_:6p1.n}o ֽXp?F/n/O8;Z'T;m:͠Mwza~a,v->2˿| _1oĵj~eϕ,stvKIWt 0gg?a{qf|[!uB(?p黕~;Z]v\qWb{6~n{ЅzD%b#ϛe=%'|_Q!}A|`vkwsdX";|e=0&}Vj|KlH~C|'B=ȉbs\;(i=+V2"3, !#]LG9x]azc[^#a<_׆FLyG!>i@H&n UCBGTJ(Dq*m^ ư m10,(6(#t`O \$`(nPuB^y] \`~@#/c>t\Hg \0 R&:#2wd!c{ CBw OJ@N0]|Ѐу AA+1!}ABuC/KB j0˝MX*{"t_w00=*!(,P(}B~꘏3/JmXU|t,ɮ0mhկ[f%{-+PiґFy (p[9cs&*}>i/}c &+q)q]ÙQ0]+ {8Mk+ʕǮOL`"n_Ԫ˨7=]1~Y[/$>?Q甍JEdY2-X1 {`wRDL=2S{ i%{!|SgiA+}/D!unE!N :׽^/r) -5r >Awg6Gra 6PSD V/gj$}/{^lr6*6!87y*A+:_fd2O6@*Ubz4xgP&{QE|7VvY|pP>OR ƥ#jTs!a0_U)g3+\nfV>Qs\Em|$ LF^9:meax5w Ӑ9 m3u,,O,`es:e볜uložӮ\!2Fp#uYAºmzz94wkp0q,42y0i͙vcST!o7(Ix100\(ɋ6P7P[ٙb*J?xcnnKOl {/i^uRP9xLshMfڴtWȷZAD W& ho"N5nI,&WJMWd#sט')Ϻy)c`fO~S[WmK U镜U)-]  Su"8`;]8փs^U-U~[u>Gw"Cy͖:֔V%Floµ2S#ln [i& |qS͢{scť7 !>^qE'Dh]v%qvU'  Bt,X|ɞ?_;jIɅMʍ(>$X}W>s!QJ פ3}ftwEf!-װ¿␈&KP=ʯD˟ɘd`䯷=XFw!B| ٙ*)Rx=S£Mܷ P ّÖ{v ( v]?$Mi6W&8۹ |"T3TwՁfڔcI^Kʆ׃qm wp/ lNÌid^oB_c\c,-Q D I4xW*Q;hΛ±bc $&`*%mņ"wx F!Dx{**J4Ev\ %1@ q#@ =KDYCϛJPIY|IYAq("T+_E mƱNN  ĺ&(=.}l~F2m1tѳfuBasQ.Qs*x ykqWզV;Nq$7m%$$$Ї<'c.'cLfQLNyo{K 5 > W穧`ۂM GɁ1b9#)ɡ=*(:duhD[TN^FcXsMl=HI)=!R,~r ݃p1VP[=7Mk/ mdy@T !P "HJ1c-'۷NLڪirvNkRz!sLHgZ"pfe~%+!:ݐC%rTj$9!RG6Q| \> endobj 221 0 obj <> endobj 222 0 obj <> /Contents 223 0 R >> endobj 223 0 obj <> stream x][w~篘ƽMG;f(ҐH9ۿ{(LP#K/(U_݀?7fvËwg_=1Wͫ-p|~~nϠad5g/ҷ`'-7 ӓқgcy|UJ/Kݖôf7~S-oKi1si&moJ| Կ^s`,L}rn$|(Ԇqq0x L+>B]RJ={ тzhXEU7E텘V)8nLLF*` qy?P 7W#~ZȦKO^DJ(&lrgg9o`0n16^ҌZ&.T>6YluH0qƬtk€+ s ^^zupS-㓴ᶗyPEψ6L< 0>LnP݊Z=X`U kL?{׹pS:{4l6𑟻QEoѳx7^8a'eN!#b̮Q>,`:pR&o(Ijd(ZЂ>hGpE_J۶Hm`BUaQ1Fu(I-P+Fv$I55O,Hz:(Cs)v\p!C\. k+"𧗨`5pIX+=Uж+k8S0@w4BvYSk>N.DŽf^]kxM}{瞗Wϔu9]#GϏ&rI4k=H!+Fbى@ & =&ROXNJbG.AwQjΑca"u;!d*=TُbUc+qtfZCyz{r!l!orwM{Zј|{ x[43ӈIg@D#b29tb%Ցmp \rk͑B }v#-4wh <>oͫ4h1+ 6+DeCJfq_1Z˨(چ!1!VD?8Kl6uLs= =.^bx8!V%!! v)CBdTN^ 6FfVa4YZ~L)A~ n"lt9 W"uwQkUG~́3  GH9,,; >(>.W#>nfYX1U;@&)<ܦ#)1P7%v|J ST"̓πFedzՂd89m{kziDok: ѩP\4H2_0 iSs>Z]C\9;"<q%9H $dzr_xla#UQȪ byM8IG$cNv 9q& ZtTE[MF>DipD86ӧؓ*OL'7[P"VYu Jl^Pw_{Eԉݫy` @9E=ˡ%7P9rhkա-s}^juvJkV|_KHב{KFD>Zy B 6yƵyG2uP*=%7K ) m4 m}QԴ q,́ }{a}?4kJ\IKRCLm H[s_ULnŽ =a;o”%@CNblȇ/wDh"Vv 5 " j4& vA;ܜp۷[:t65̎tF 搻ԣnd-̪1}%hӉ|<._e„ ۘ]eJ 18 .# :\ndG;_ZYޜ,RDW7i-Xe( c95[]Zgp>:n֛0I{smҐ^,JMdPh=w(k\iL@dwd'9C> %{@H-u*$H.}97 $ ԳK<4i kqE}5 ?ێ(Lj9k S_[&}}S)}JX8ߠ[d92}@1_{cCD "Ew+}Zn@踺mxnuJ|`yd@*FBhvC*LvHΣeou4/GL"D030TnAv |6ȐTɐǻ7js'#+`ŁO<鷺nd$GuXɍpM2H"Y?{lZNAk̈́ȕb#ryE;XQ/1XAb] CϖRĝ,xraرo&B1H:5PM>cq;XNCq7XN>w#v;·nCqP/w>w;a E1"úȩP`MVNkXQW' T%$֝b& #Y$eUJȣSlNP=QW{% t-AS`OmhǭC2Q  R-Kl%\:G}+/RRL1Z++ʰ2dLӫݷ9jkҹ6W[IVۿVlƫjKeOV:߬vdFk5ӭtBft*&iD H-Ωfv)q{bO;^ހ3ـ8D@6ةzXc1ޱGUy$pM 61Y5Z]mg1,wu&Y'\4L8C_D`tS; _Y!vBj#@sa )9` +vwF,\\O;F,2qx݁BG]@bpWԖKn >/>![;LeԿsM-X72IUWڦ?k|'|$ЖTwa\Aw+,sB%DF!8O ,7xB?QؗaݧvV|ܛ_|N _ . ^_˒qƝ\|(,mCRcYvCoL=)9A]li 7qcZz0QB=zε/D.k@yo3\ʴqiJ?f;(T%8 $Xzx!Ȍ' Z/eJ,9OM !rqw ;=Bv: ֩S}}J:cu4F c  gyfr %ND6|ѰkHjM9J3)tviH<%X +0NZ֡XfH_,m;P,babxnݯPŌ3$Ή:INw?"Nʨ)\Y:ǝF u 3uMb-| b`.{ ]͎Bɷ7ي@_Zzx]!XR_AG3UH1ˮ>>hma lW/O37iFaWvʾ]]Q90EImt1rY+9ȃ2EPN+]UWe9_lBt!??%_zQhPωq'/I-S7Q퓛(Y-q5'<4E,b|ɝteKDk7S8eNK&2}'~TT^(5'O1ٜB8O{sZ{/7ބ.{㊉M(7ބ.{썁I*qX78L-08Mvbendstream endobj 224 0 obj 5554 endobj 225 0 obj <> endobj 226 0 obj <> endobj 227 0 obj <> /Contents 228 0 R >> endobj 228 0 obj <> stream x[ےܶ}gRr(qXٸ*SjDYEIc9Z%7ݧO7ϊ\ȬWMvs)6/7|B7m3Q]5śZZe.>ln_nDl붫TNUnbm{$Jw&j]lklrK|g*Bw/B >+35wF.e훮;]\W0Zo^첦jseJow2N8# ە\j{\){dov{},h Gx()| Ʈ7{;+# ]=K˺#r ~KN]/+5Пل`~Jꍴ/\R`ˏ{wњV@|7Z_ԕָAx.UVjR8l>$M֩N ͭEyߋ[ G ?ۭbTsQڅuZF6* hWt_ǏNO.=&\ChE ;(/`'̀ n?wz6(LLAðrw¼@;-5U^U!:6 ,-Ca CSv4'^DBq-D/Q6*v(h&5`u.j̟1_ľ_9))vH 5B߸ݿpϬ2F|/P.}[5 u 6]r$3^~NȶiF;]ky<,0*'JWH%wd\%&;zOvm.d%D$hgc'#J[ç) z+пdT0Ԏƾ51Rf}hTFtq$ʬFi [@JFVxSsKoI!Qp!I:~T,7B^\I/" ta3D'=9wޜSkJgccb!Mt2nCZؓ,{ֈ]{Qy C]0I`⪇E-rgCkbpô<~^,@ GxтƆg0xu]Za2颎weM(ɞͅS.A՘oTLW6c%P8ȂM0K*H &X%zm -pX{.P-Ex)_%WΞB@ji܂{z00@ Ϗ<>=_8a?Zu΃ͣ6˦ Hqq?~WQJ0X؇MCbB!&3-/_m.~yz F\2fcWPVdk7-AҊ`8>RZkAk|at0ꚓ-a(l`s qNG]>ߔG!*{JQ&vx >ݼD P(JIqӼ-v6DYh>^O0ʗPz6<0K'ӟ\n܏d><#H 2F2,_׏K;u^y!7ӹ)fN/u%hXP)a}X _ H@ia,tǤ71k6'Y':#bDZ7N>fgQb6~yz4dWeMvwy$Uk/BxIP9#4A3MUp~pJhGB*k*2>߃La1MQB3;oo +8YҿDO9}-._a 뵕~{98o'-8rk(YLc4Sẉs<̺=SSxK;Jρå58S&32] f RdK'i'L@yb;1/L q؂zr~ -i@/O"]y7BRwiHb٦ĕ C!ȲޘƱKK":ޥ)l5^`U材TA%_.HBW,[Dud.{XsU\SeH'&XTnET٪範6,"/77 Mps&m}*(@ "Λ%t>)gS{*COp0;np{:c HATz:A}bSZ%5C,a 19dtc!f^s7"2X$|T4}y"udؤ4D1QyYqC)r /0G;4xv!57[?7 /1`={?ω405&VnsutXwttbMOxCGQ畿NXoDՖ粰kY y"J@iC$ %٬'ޭYPOG)EGhIC@lAZVH6rl`WbpAnpI R.%:y>3Yǐ5hBڤd!^\E_z7 ?n`%Jb*9H7endstream endobj 229 0 obj 3013 endobj 230 0 obj <> endobj 231 0 obj <> endobj 4 0 obj <> endobj 5 0 obj <> endobj 6 0 obj <> endobj 19 0 obj <> endobj 25 0 obj <> endobj 177 0 obj <>/Length 119>>stream x334R0A#9 İ36620E y\ XKM&"80N\ %E\ \ )YE ,x endstream endobj 233 0 obj <> endobj 234 0 obj <> endobj 235 0 obj <> endobj 236 0 obj <>endobj 237 0 obj << /Type /Pages /Kids [ 241 0 R 1 0 R 9 0 R 14 0 R 22 0 R 28 0 R 33 0 R 38 0 R 43 0 R 48 0 R 53 0 R 58 0 R 63 0 R 68 0 R 73 0 R 78 0 R 83 0 R 88 0 R 93 0 R 98 0 R 103 0 R 108 0 R 113 0 R 118 0 R 124 0 R 129 0 R 134 0 R 139 0 R 144 0 R 149 0 R 154 0 R 159 0 R 164 0 R 169 0 R 174 0 R 183 0 R 191 0 R 199 0 R 207 0 R 212 0 R 217 0 R 222 0 R 227 0 R ] /Count 43 >> endobj 238 0 obj <>stream PDFCreator 2.3.2.6 2018-10-09T02:17:59+02:00 2018-10-09T02:17:59+02:00 PDFCreator 2.3.2.6 FluidPolyMono-0004Administrateur endstream endobj xref 0 239 0000000000 65535 f 0000005316 00000 n 0000005476 00000 n 0000013016 00000 n 0000245129 00000 n 0000245212 00000 n 0000245296 00000 n 0000013036 00000 n 0000013067 00000 n 0000013151 00000 n 0000013314 00000 n 0000019647 00000 n 0000019668 00000 n 0000019700 00000 n 0000019773 00000 n 0000019937 00000 n 0000024493 00000 n 0000024514 00000 n 0000024648 00000 n 0000245367 00000 n 0000024847 00000 n 0000024879 00000 n 0000027624 00000 n 0000027788 00000 n 0000032432 00000 n 0000245440 00000 n 0000032453 00000 n 0000032485 00000 n 0000032551 00000 n 0000032715 00000 n 0000037425 00000 n 0000037446 00000 n 0000037478 00000 n 0000037555 00000 n 0000037719 00000 n 0000041033 00000 n 0000041054 00000 n 0000041086 00000 n 0000041152 00000 n 0000041316 00000 n 0000045012 00000 n 0000045033 00000 n 0000045065 00000 n 0000045131 00000 n 0000045295 00000 n 0000049975 00000 n 0000049996 00000 n 0000050028 00000 n 0000050094 00000 n 0000050258 00000 n 0000054893 00000 n 0000054914 00000 n 0000054946 00000 n 0000055012 00000 n 0000055176 00000 n 0000060345 00000 n 0000060366 00000 n 0000060398 00000 n 0000060453 00000 n 0000060617 00000 n 0000064761 00000 n 0000064782 00000 n 0000064814 00000 n 0000064880 00000 n 0000065044 00000 n 0000071524 00000 n 0000071545 00000 n 0000071577 00000 n 0000071654 00000 n 0000071818 00000 n 0000075835 00000 n 0000075856 00000 n 0000075888 00000 n 0000075965 00000 n 0000076129 00000 n 0000080295 00000 n 0000080316 00000 n 0000080348 00000 n 0000080425 00000 n 0000080589 00000 n 0000085243 00000 n 0000085264 00000 n 0000085296 00000 n 0000085373 00000 n 0000085537 00000 n 0000090704 00000 n 0000090725 00000 n 0000090757 00000 n 0000090812 00000 n 0000090976 00000 n 0000096679 00000 n 0000096700 00000 n 0000096732 00000 n 0000096809 00000 n 0000096973 00000 n 0000101611 00000 n 0000101632 00000 n 0000101664 00000 n 0000101719 00000 n 0000101885 00000 n 0000105950 00000 n 0000105972 00000 n 0000106005 00000 n 0000106050 00000 n 0000106218 00000 n 0000111640 00000 n 0000111662 00000 n 0000111695 00000 n 0000111751 00000 n 0000111919 00000 n 0000116314 00000 n 0000116336 00000 n 0000116369 00000 n 0000116414 00000 n 0000116582 00000 n 0000122142 00000 n 0000122164 00000 n 0000122197 00000 n 0000122264 00000 n 0000122432 00000 n 0000129039 00000 n 0000129061 00000 n 0000129128 00000 n 0000129161 00000 n 0000129260 00000 n 0000129428 00000 n 0000137365 00000 n 0000137387 00000 n 0000137420 00000 n 0000137475 00000 n 0000137643 00000 n 0000143800 00000 n 0000143822 00000 n 0000143855 00000 n 0000143911 00000 n 0000144079 00000 n 0000148223 00000 n 0000148245 00000 n 0000148278 00000 n 0000148345 00000 n 0000148513 00000 n 0000168895 00000 n 0000168918 00000 n 0000168951 00000 n 0000169007 00000 n 0000169175 00000 n 0000172331 00000 n 0000172353 00000 n 0000172386 00000 n 0000172453 00000 n 0000172621 00000 n 0000175983 00000 n 0000176005 00000 n 0000176038 00000 n 0000176105 00000 n 0000176273 00000 n 0000180205 00000 n 0000180227 00000 n 0000180260 00000 n 0000180338 00000 n 0000180506 00000 n 0000184779 00000 n 0000184801 00000 n 0000184834 00000 n 0000184912 00000 n 0000185080 00000 n 0000188690 00000 n 0000188712 00000 n 0000188745 00000 n 0000188812 00000 n 0000188980 00000 n 0000191021 00000 n 0000191043 00000 n 0000191076 00000 n 0000191132 00000 n 0000191337 00000 n 0000199858 00000 n 0000245534 00000 n 0000199880 00000 n 0000199908 00000 n 0000199943 00000 n 0000199976 00000 n 0000200011 00000 n 0000200078 00000 n 0000200283 00000 n 0000205541 00000 n 0000205563 00000 n 0000205591 00000 n 0000205626 00000 n 0000205659 00000 n 0000205694 00000 n 0000205749 00000 n 0000205954 00000 n 0000212402 00000 n 0000212424 00000 n 0000212452 00000 n 0000212487 00000 n 0000212520 00000 n 0000212555 00000 n 0000212622 00000 n 0000212827 00000 n 0000219155 00000 n 0000219177 00000 n 0000219205 00000 n 0000219240 00000 n 0000219273 00000 n 0000219308 00000 n 0000219375 00000 n 0000219543 00000 n 0000224822 00000 n 0000224844 00000 n 0000224877 00000 n 0000224944 00000 n 0000225112 00000 n 0000231665 00000 n 0000231687 00000 n 0000231720 00000 n 0000231776 00000 n 0000231944 00000 n 0000235735 00000 n 0000235757 00000 n 0000235790 00000 n 0000235846 00000 n 0000236014 00000 n 0000241642 00000 n 0000241664 00000 n 0000241697 00000 n 0000241774 00000 n 0000241942 00000 n 0000245029 00000 n 0000245051 00000 n 0000245084 00000 n 0000024956 00000 n 0000245882 00000 n 0000245943 00000 n 0000246019 00000 n 0000246095 00000 n 0000246625 00000 n 0000247004 00000 n trailer <> startxref 148 %%EOF fluidsynth-2.1.1/doc/polymono/leg_00.txt000066400000000000000000000054571362231004000201360ustar00rootroot00000000000000echo "legato mode 0 (retrigger), egal velocity on n1,n2,n3..." echo "---------------------------------------------------------" # Sounfont: GeneralUser GS 1.471 S. Christian Collins # Some presets # 0: Piano 0 ; short release # 16:organ 24:guitar # 52: Choir Aahs 53: Voice Oohs # # 56:trumpet 57:trombone 58:tuba 59:muted trumpet # 60:French horn 61:Brass section # 62:Synth brass 1 63:Synth Brass 2 # 64:Soprano sax 65:Alto sax 66:Tenor sax 67:Baritone sax. # 68:Oboe 69:English horn 70:Bassoon 71:Clarinet # 72:piccolo 73:Flute 74:Recorder 75:Pan flute # # 99:atmosphere; attack longer than organ, release longer than piano # 100:brillance , 101:gobelin echo "preset 73:flute" prog 0 73 echo "legato mode:0" setlegatomode 0 0 echo "legato On" cc 0 68 127 echo "noteon C 60 vel=127, during 1000 ms" noteon 0 60 127 sleep 1000 echo "noteon D,legato up C->D (60->62, vel=127), during 1000 ms" echo "noteoff C" noteon 0 62 127 noteoff 0 60 sleep 1000 echo "noteon E,legato up D->E (62->64, vel=127), during 1000 ms" echo "noteoff D" noteon 0 64 127 noteoff 0 62 sleep 1000 echo "noteon F,legato up E->F (64->65, vel=127), during 1000 ms" echo "noteoff E" noteon 0 65 127 noteoff 0 64 sleep 1000 echo "noteon G,legato up F->G (65->67, vel=127), during 1000 ms" echo "noteoff F" noteon 0 67 127 noteoff 0 65 sleep 1000 echo "noteon A,legato up G->A (67->69, vel=127), during 1000 ms" echo "noteoff G" noteon 0 69 127 noteoff 0 67 sleep 1000 echo "noteon B,legato up A->B (69->71, vel=127), during 1000 ms" echo "noteoff A" noteon 0 71 127 noteoff 0 69 sleep 1000 echo "noteon C,legato up B->C (71->72, vel=127), during 1000 ms" echo "noteoff B" noteon 0 72 127 noteoff 0 71 sleep 1000 echo "noteon B,legato down C->B (72->71, vel=127), during 1000 ms" echo "noteoff C" noteon 0 71 127 noteoff 0 72 sleep 1000 echo "noteon A,legato down B->A (71->69, vel=127), during 1000 ms" echo "noteoff B" noteon 0 69 127 noteoff 0 71 sleep 1000 echo "noteon G,legato down A->G (69->67, vel=127), during 1000 ms" echo "noteoff A" noteon 0 67 127 noteoff 0 69 sleep 1000 echo "noteon F,legato down G->F (67->65, vel=127), during 1000 ms" echo "noteoff G" noteon 0 65 127 noteoff 0 67 sleep 1000 echo "noteon E,legato down F->E (65->64, vel=127), during 1000 ms" echo "noteoff F" noteon 0 64 127 noteoff 0 65 sleep 1000 echo "noteon D,legato down E->D (64->62, vel=127), during 1000 ms" echo "noteoff E" noteon 0 62 127 noteoff 0 64 sleep 1000 echo "noteon C,legato down D->C (62->60, vel=127), during 1000 ms" echo "noteoff D" noteon 0 60 127 noteoff 0 62 sleep 1000 echo "noteoff C" noteoff 0 60 sleep 1000 echo "legato Off" cc 0 68 0 echo "End legato mode 1 (retrigger_1)" fluidsynth-2.1.1/doc/polymono/leg_01.txt000066400000000000000000000054751362231004000201370ustar00rootroot00000000000000echo "legato mode 1 (multi-retrigger), egal velocity on n1,n2,n3..." echo "-------------------------------------------------------------" # Sounfont: GeneralUser GS 1.471 S. Christian Collins # Some presets # 0: Piano 0 ; short release # 16:organ 24:guitar # 52: Choir Aahs 53: Voice Oohs # # 56:trumpet 57:trombone 58:tuba 59:muted trumpet # 60:French horn 61:Brass section # 62:Synth brass 1 63:Synth Brass 2 # 64:Soprano sax 65:Alto sax 66:Tenor sax 67:Baritone sax. # 68:Oboe 69:English horn 70:Bassoon 71:Clarinet # 72:piccolo 73:Flute 74:Recorder 75:Pan flute # # 99:atmosphere; attack longer than organ, release longer than piano # 100:brillance , 101:gobelin echo "preset 73:flute" prog 0 73 echo "legato mode:1" setlegatomode 0 1 echo "legato On" cc 0 68 127 echo "noteon C 60 vel=127, during 1000 ms" noteon 0 60 127 sleep 1000 echo "noteon D,legato up C->D (60->62, vel=127), during 1000 ms" echo "noteoff C" noteon 0 62 127 noteoff 0 60 sleep 1000 echo "noteon E,legato up D->E (62->64, vel=127), during 1000 ms" echo "noteoff D" noteon 0 64 127 noteoff 0 62 sleep 1000 echo "noteon F,legato up E->F (64->65, vel=127), during 1000 ms" echo "noteoff E" noteon 0 65 127 noteoff 0 64 sleep 1000 echo "noteon G,legato up F->G (65->67, vel=127), during 1000 ms" echo "noteoff F" noteon 0 67 127 noteoff 0 65 sleep 1000 echo "noteon A,legato up G->A (67->69, vel=127), during 1000 ms" echo "noteoff G" noteon 0 69 127 noteoff 0 67 sleep 1000 echo "noteon B,legato up A->B (69->71, vel=127), during 1000 ms" echo "noteoff A" noteon 0 71 127 noteoff 0 69 sleep 1000 echo "noteon C,legato up B->C (71->72, vel=127), during 1000 ms" echo "noteoff B" noteon 0 72 127 noteoff 0 71 sleep 1000 echo "noteon B,legato down C->B (72->71, vel=127), during 1000 ms" echo "noteoff C" noteon 0 71 127 noteoff 0 72 sleep 1000 echo "noteon A,legato down B->A (71->69, vel=127), during 1000 ms" echo "noteoff B" noteon 0 69 127 noteoff 0 71 sleep 1000 echo "noteon G,legato down A->G (69->67, vel=127), during 1000 ms" echo "noteoff A" noteon 0 67 127 noteoff 0 69 sleep 1000 echo "noteon F,legato down G->F (67->65, vel=127), during 1000 ms" echo "noteoff G" noteon 0 65 127 noteoff 0 67 sleep 1000 echo "noteon E,legato down F->E (65->64, vel=127), during 1000 ms" echo "noteoff F" noteon 0 64 127 noteoff 0 65 sleep 1000 echo "noteon D,legato down E->D (64->62, vel=127), during 1000 ms" echo "noteoff E" noteon 0 62 127 noteoff 0 64 sleep 1000 echo "noteon C,legato down D->C (62->60, vel=127), during 1000 ms" echo "noteoff D" noteon 0 60 127 noteoff 0 62 sleep 1000 echo "noteoff C" noteoff 0 60 sleep 1000 echo "legato Off" cc 0 68 0 echo "End legato mode 2 (multi-retrigger)" fluidsynth-2.1.1/doc/polymono/leg_por_00.txt000066400000000000000000000056311362231004000210100ustar00rootroot00000000000000echo "legato mode 0 (retrigger), egal velocity on n1,n2,n3..." echo "with portamento." echo "---------------------------------------------------------" # Sounfont: GeneralUser GS 1.471 S. Christian Collins # Some presets # 0: Piano 0 ; short release # 16:organ 24:guitar # 52: Choir Aahs 53: Voice Oohs # # 56:trumpet 57:trombone 58:tuba 59:muted trumpet # 60:French horn 61:Brass section # 62:Synth brass 1 63:Synth Brass 2 # 64:Soprano sax 65:Alto sax 66:Tenor sax 67:Baritone sax. # 68:Oboe 69:English horn 70:Bassoon 71:Clarinet # 72:piccolo 73:Flute 74:Recorder 75:Pan flute # # 99:atmosphere; attack longer than organ, release longer than piano # 100:brillance , 101:gobelin echo "preset 73:flute" prog 0 73 echo "legato mode:0" setlegatomode 0 0 echo "legato On, portamento On" cc 0 68 127 cc 0 5 2 cc 0 65 127 echo "noteon C 60 vel=127, during 1000 ms" noteon 0 60 127 sleep 1000 echo "noteon D,legato up C->D (60->62, vel=127), during 1000 ms" echo "noteoff C" noteon 0 62 127 noteoff 0 60 sleep 1000 echo "noteon E,legato up D->E (62->64, vel=127), during 1000 ms" echo "noteoff D" noteon 0 64 127 noteoff 0 62 sleep 1000 echo "noteon F,legato up E->F (64->65, vel=127), during 1000 ms" echo "noteoff E" noteon 0 65 127 noteoff 0 64 sleep 1000 echo "noteon G,legato up F->G (65->67, vel=127), during 1000 ms" echo "noteoff F" noteon 0 67 127 noteoff 0 65 sleep 1000 echo "noteon A,legato up G->A (67->69, vel=127), during 1000 ms" echo "noteoff G" noteon 0 69 127 noteoff 0 67 sleep 1000 echo "noteon B,legato up A->B (69->71, vel=127), during 1000 ms" echo "noteoff A" noteon 0 71 127 noteoff 0 69 sleep 1000 echo "noteon C,legato up B->C (71->72, vel=127), during 1000 ms" echo "noteoff B" noteon 0 72 127 noteoff 0 71 sleep 1000 echo "noteon B,legato down C->B (72->71, vel=127), during 1000 ms" echo "noteoff C" noteon 0 71 127 noteoff 0 72 sleep 1000 echo "noteon A,legato down B->A (71->69, vel=127), during 1000 ms" echo "noteoff B" noteon 0 69 127 noteoff 0 71 sleep 1000 echo "noteon G,legato down A->G (69->67, vel=127), during 1000 ms" echo "noteoff A" noteon 0 67 127 noteoff 0 69 sleep 1000 echo "noteon F,legato down G->F (67->65, vel=127), during 1000 ms" echo "noteoff G" noteon 0 65 127 noteoff 0 67 sleep 1000 echo "noteon E,legato down F->E (65->64, vel=127), during 1000 ms" echo "noteoff F" noteon 0 64 127 noteoff 0 65 sleep 1000 echo "noteon D,legato down E->D (64->62, vel=127), during 1000 ms" echo "noteoff E" noteon 0 62 127 noteoff 0 64 sleep 1000 echo "noteon C,legato down D->C (62->60, vel=127), during 1000 ms" echo "noteoff D" noteon 0 60 127 noteoff 0 62 sleep 1000 echo "noteoff C" noteoff 0 60 sleep 1000 echo "legato Off, portamento off" cc 0 68 0 cc 0 65 0 echo "End legato mode 1 (retrigger_1) with portamento" fluidsynth-2.1.1/doc/polymono/leg_por_01.txt000066400000000000000000000056471362231004000210200ustar00rootroot00000000000000echo "legato mode 1 (multi-retrigger), egal velocity on n1,n2,n3..." echo "with portamento." echo "-------------------------------------------------------------" # Sounfont: GeneralUser GS 1.471 S. Christian Collins # Some presets # 0: Piano 0 ; short release # 16:organ 24:guitar # 52: Choir Aahs 53: Voice Oohs # # 56:trumpet 57:trombone 58:tuba 59:muted trumpet # 60:French horn 61:Brass section # 62:Synth brass 1 63:Synth Brass 2 # 64:Soprano sax 65:Alto sax 66:Tenor sax 67:Baritone sax. # 68:Oboe 69:English horn 70:Bassoon 71:Clarinet # 72:piccolo 73:Flute 74:Recorder 75:Pan flute # # 99:atmosphere; attack longer than organ, release longer than piano # 100:brillance , 101:gobelin echo "preset 73:flute" prog 0 73 echo "legato mode:1" setlegatomode 0 1 echo "legato On, portamento On" cc 0 68 127 cc 0 5 2 cc 0 65 127 echo "noteon C 60 vel=127, during 1000 ms" noteon 0 60 127 sleep 1000 echo "noteon D,legato up C->D (60->62, vel=127), during 1000 ms" echo "noteoff C" noteon 0 62 127 noteoff 0 60 sleep 1000 echo "noteon E,legato up D->E (62->64, vel=127), during 1000 ms" echo "noteoff D" noteon 0 64 127 noteoff 0 62 sleep 1000 echo "noteon F,legato up E->F (64->65, vel=127), during 1000 ms" echo "noteoff E" noteon 0 65 127 noteoff 0 64 sleep 1000 echo "noteon G,legato up F->G (65->67, vel=127), during 1000 ms" echo "noteoff F" noteon 0 67 127 noteoff 0 65 sleep 1000 echo "noteon A,legato up G->A (67->69, vel=127), during 1000 ms" echo "noteoff G" noteon 0 69 127 noteoff 0 67 sleep 1000 echo "noteon B,legato up A->B (69->71, vel=127), during 1000 ms" echo "noteoff A" noteon 0 71 127 noteoff 0 69 sleep 1000 echo "noteon C,legato up B->C (71->72, vel=127), during 1000 ms" echo "noteoff B" noteon 0 72 127 noteoff 0 71 sleep 1000 echo "noteon B,legato down C->B (72->71, vel=127), during 1000 ms" echo "noteoff C" noteon 0 71 127 noteoff 0 72 sleep 1000 echo "noteon A,legato down B->A (71->69, vel=127), during 1000 ms" echo "noteoff B" noteon 0 69 127 noteoff 0 71 sleep 1000 echo "noteon G,legato down A->G (69->67, vel=127), during 1000 ms" echo "noteoff A" noteon 0 67 127 noteoff 0 69 sleep 1000 echo "noteon F,legato down G->F (67->65, vel=127), during 1000 ms" echo "noteoff G" noteon 0 65 127 noteoff 0 67 sleep 1000 echo "noteon E,legato down F->E (65->64, vel=127), during 1000 ms" echo "noteoff F" noteon 0 64 127 noteoff 0 65 sleep 1000 echo "noteon D,legato down E->D (64->62, vel=127), during 1000 ms" echo "noteoff E" noteon 0 62 127 noteoff 0 64 sleep 1000 echo "noteon C,legato down D->C (62->60, vel=127), during 1000 ms" echo "noteoff D" noteon 0 60 127 noteoff 0 62 sleep 1000 echo "noteoff C" noteoff 0 60 sleep 1000 echo "legato Off, portamento off" cc 0 68 0 cc 0 65 0 echo "End legato mode 2 (multi-retrigger) with portamento" fluidsynth-2.1.1/doc/polymono/poly_mono_0.txt000066400000000000000000000004261362231004000213110ustar00rootroot00000000000000# What are default 'basic channels' in FluidSynth ? #-------------------------------------------------- # At initialization the default basic channel is poly omnion (see pdf 2.2). # type the command basicchannels to display actual basic channels. basicchannels # end fluidsynth-2.1.1/doc/polymono/poly_mono_1.txt000066400000000000000000000015541362231004000213150ustar00rootroot00000000000000# How to change the whole set of actual basic channels ? # ------------------------------------------------------------- # Assuming you want to set 2 groups of channels: # Group 1: first channel 5 in poly mode, only one channel # Group 2: first channel 10 in mono mode , only one channel # Group 1 should have following settings: # basic channel 5, mode poly, omni off, (mode 2). # Group 2 should have the following settings: # basic channel 10, mode mono, omni off, (mode 3), composed of one channel. # First use the command resetbasicchannels to reset all basic channels resetbasicchannels # Then use command setbasicchannels using numbered mode 2 and 3: setbasicchannels 5 2 0 10 3 1 # or using named mode: # setbasicchannels 5 poly_omnioff 0 10 mono_omnioff 1 # Use basicchannels command to verify your settings basicchannels # end fluidsynth-2.1.1/doc/polymono/poly_mono_2.txt000066400000000000000000000014401362231004000213100ustar00rootroot00000000000000# How to add a new basic channel among others actual basic channels ? # ------------------------------------------------------------------- # Perhaps you have already set several groups of basics channels and # you want add a new one without modifying actual groups. # Assuming following actual groups: # Basic channel: 5, poly omni off(mode 2), nbr: 1 # Basic channel: 10, mono omni off(mode 3), nbr: 1 # Now we want to add a new group 3: # Group 3 should have the following settings: # basic channel 13, mode mono, omni off, (mode 3) composed of 2 channels. # Use command setbasicchannels using numbered mode 3: setbasicchannels 13 3 2 # or using named mode: # setbasicchannels 13 mono_omnioff 2 Use basicchannels command to verify your settings basicchannels # end fluidsynth-2.1.1/doc/polymono/poly_mono_3.txt000066400000000000000000000016471362231004000213220ustar00rootroot00000000000000# How to change an actual basic channel ? # --------------------------------------- # Perhaps you have already set several groups of basics channels # and you want change the settings of one. # Assuming following actual groups: # -Group 1:Basic channel: 5, poly omni off(mode 2), nbr: 1 # -Group 2:Basic channel: 10, mono omni off(mode 3), nbr: 1 # -Group 3:Basic channel: 13, mono omni off(mode 3), nbr: 2 # Now we want to change group 1: # Group 1 should have the following settings: # -basic channel 5, mode poly, omni on, (mode 0) composed of 4 channels in this group. #First use the command resetbasicchannels to clear the group intended to be changed. resetbasicchannels 5 # Then use command setbasicchannels using numbered mode 0: setbasicchannels 5 0 4 # or Using named mode: # setbasicchannels 5 poly_omnion 4 # Use basicchannels command to verify your settings basicchannels # end fluidsynth-2.1.1/doc/polymono/poly_mono_4.txt000066400000000000000000000017211362231004000213140ustar00rootroot00000000000000# How to change an actual basic channel and add a new one ? # --------------------------------------------------------- # Note that command setbasicchannels allows to add or change # groups of basics channels. # # Note also that the commands allows this for more than one # groups executing only one command: # The following command restores Group 1 to the state: # -Group 1:Basic channel: 5, poly omni off(mode 2), nbr: 1 # Then adds a new group: # -Group 0:Basic channel: 2, mono omni on(mode 1), composed of 3 possible channels in this group #First use the command resetbasicchannels to clear the group intended to be changed. resetbasicchannels 5 # Use command setbasicchannels to change a group and add a new one, using numbered mode 2 and 1: setbasicchannels 5 2 0 2 1 3 # or using named mode: # setbasicchannels 5 poly_omnioff 0 2 mono_omnion 3 # Use basicchannels command to verify your settings basicchannels # end fluidsynth-2.1.1/doc/polymono/poly_mono_5.txt000066400000000000000000000004161362231004000213150ustar00rootroot00000000000000# How to know the state of one or more MIDI channels ? # ----------------------------------------------------- # Use the command channelsmode [chan1 chan2 .] channelsmode # To display the state of MIDI channels 2,5, 10, 13 only channelsmode 2 5 10 13 # end fluidsynth-2.1.1/doc/polymono/readme.txt000066400000000000000000000010531362231004000203110ustar00rootroot00000000000000/fluidSynth/doc/polymono directory contains: 1) FluidPolyMono-0004.pdf, the documentation of poly/mono functionalities. 2) tutorials examples files: 2.1) tutorials chapter 2.1 poly_mono_0.txt poly_mono_1.txt poly_mono_2.txt poly_mono_3.txt poly_mono_4.txt poly_mono_5.txt 2.2) tutorials chapter 3.1 leg_00.txt leg_01.txt leg_por_00.txt leg_por_01.txt These tutorials files are usable directly on the FluidSynth console application. See the pdf file (chapters 2.1 and 3.1). jean-jacques ceresa fluidsynth-2.1.1/fluidsynth.conf.in000066400000000000000000000003201362231004000173330ustar00rootroot00000000000000# Mandatory parameters (uncomment and edit) #SOUND_FONT=@DEFAULT_SOUNDFONT@ # Additional optional parameters (may be useful, see 'man fluidsynth' for further info) #OTHER_OPTS='-a alsa -m alsa_seq -r 48000' fluidsynth-2.1.1/fluidsynth.pc.in000066400000000000000000000003241362231004000170140ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: FluidSynth Description: Software SoundFont synth Version: @VERSION@ Libs: -L${libdir} -lfluidsynth Cflags: -I${includedir} fluidsynth-2.1.1/fluidsynth.service.in000066400000000000000000000005211362231004000200510ustar00rootroot00000000000000[Unit] Description=FluidSynth Daemon Documentation=man:fluidsynth(1) After=sound.target [Service] Type=notify NotifyAccess=main EnvironmentFile=@FLUID_DAEMON_ENV_FILE@ EnvironmentFile=-%h/.config/fluidsynth ExecStart=@CMAKE_INSTALL_PREFIX@/@BIN_INSTALL_DIR@/fluidsynth -is $OTHER_OPTS $SOUND_FONT [Install] WantedBy=multi-user.target fluidsynth-2.1.1/fluidsynth.spec.in000066400000000000000000000045011362231004000173450ustar00rootroot00000000000000 %define name @PACKAGE@ %define version @VERSION@ %define release 1 %define prefix /usr Summary: A real-time software synthesizer based on SoundFont 2 specifications. Name: %{name} Version: %{version} Release: %{release} Prefix: %{prefix} Copyright: LGPL Group: Sound Source: http://savannah.nongnu.org/download/fluid/stable.pkg/%{version}/fluidsynth-%{version}.tar.gz URL: http://www.fluidsynth.org/ BuildRoot: /var/tmp/%{name}-%{version} %description FluidSynth is a real-time software synthesizer based on the SoundFont 2 specifications. FluidSynth can read MIDI events from MIDI input devices and render them to audio devices using SoundFont files to define the instrument sounds. It can also play MIDI files and supports real time effect control via SoundFont modulators and MIDI controls. FluidSynth can be interfaced to other programs in different ways, including linking as a shared library. %package devel Summary: Libraries and includes to build FluidSynth into other applications Group: Development/Libraries %description devel FluidSynth is a real-time software synthesizer based on the SoundFont 2 specifications. FluidSynth can read MIDI events from MIDI input devices and render them to audio devices using SoundFont files to define the instrument sounds. It can also play MIDI files and supports real time effect control via SoundFont modulators and MIDI controls. FluidSynth can be interfaced to other programs in different ways, including linking as a shared library. This package contains libraries and includes for building applications with FluidSynth support. %prep %setup %build ./configure --prefix=%{prefix} make %install if [ -d $RPM_BUILD_ROOT ]; then rm -rf $RPM_BUILD_ROOT; fi mkdir -p $RPM_BUILD_ROOT make prefix=$RPM_BUILD_ROOT%{prefix} install %clean if [ -d $RPM_BUILD_ROOT ]; then rm -rf $RPM_BUILD_ROOT; fi %files %defattr(-,root,root) %doc AUTHORS COPYING ChangeLog NEWS README TODO %{prefix}/bin/fluidsynth %{prefix}/lib/libfluidsynth.so* %{prefix}/man/man1/* %files devel %defattr(-,root,root) %doc doc/example.c doc/example.sf2 doc/api doc/html/* %{prefix}/lib/libfluidsynth.a %{prefix}/lib/libfluidsynth.la %{prefix}/lib/pkgconfig/fluidsynth.pc %{prefix}/include/fluidsynth.h %{prefix}/include/fluidsynth %changelog * Mon Aug 25 2003 Josh Green - Created initial fluidsynth.spec.in fluidsynth-2.1.1/include/000077500000000000000000000000001362231004000153165ustar00rootroot00000000000000fluidsynth-2.1.1/include/fluidsynth.cmake000066400000000000000000000066561362231004000205260ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_H #define _FLUIDSYNTH_H #include #ifdef __cplusplus extern "C" { #endif #cmakedefine01 BUILD_SHARED_LIBS #if (BUILD_SHARED_LIBS == 0) #define FLUIDSYNTH_API // building static lib? no visibility control then #elif defined(WIN32) #if defined(FLUIDSYNTH_NOT_A_DLL) #define FLUIDSYNTH_API #elif defined(FLUIDSYNTH_DLL_EXPORTS) #define FLUIDSYNTH_API __declspec(dllexport) #else #define FLUIDSYNTH_API __declspec(dllimport) #endif #elif defined(MACOS9) #define FLUIDSYNTH_API __declspec(export) #elif defined(__GNUC__) #define FLUIDSYNTH_API __attribute__ ((visibility ("default"))) #else #define FLUIDSYNTH_API #endif #if defined(__GNUC__) || defined(__clang__) # define FLUID_DEPRECATED __attribute__((deprecated)) #elif defined(_MSC_VER) && _MSC_VER > 1200 # define FLUID_DEPRECATED __declspec(deprecated) #else # define FLUID_DEPRECATED #endif /** * @file fluidsynth.h * @brief FluidSynth is a real-time synthesizer designed for SoundFont(R) files. * * This is the header of the fluidsynth library and contains the * synthesizer's public API. * * Depending on how you want to use or extend the synthesizer you * will need different API functions. You probably do not need all * of them. Here is what you might want to do: * * - Embedded synthesizer: create a new synthesizer and send MIDI * events to it. The sound goes directly to the audio output of * your system. * * - Plugin synthesizer: create a synthesizer and send MIDI events * but pull the audio back into your application. * * - SoundFont plugin: create a new type of "SoundFont" and allow * the synthesizer to load your type of SoundFonts. * * - MIDI input: Create a MIDI handler to read the MIDI input on your * machine and send the MIDI events directly to the synthesizer. * * - MIDI files: Open MIDI files and send the MIDI events to the * synthesizer. * * - Command lines: You can send textual commands to the synthesizer. * * SoundFont(R) is a registered trademark of E-mu Systems, Inc. */ #include "fluidsynth/types.h" #include "fluidsynth/settings.h" #include "fluidsynth/synth.h" #include "fluidsynth/shell.h" #include "fluidsynth/sfont.h" #include "fluidsynth/audio.h" #include "fluidsynth/event.h" #include "fluidsynth/midi.h" #include "fluidsynth/seq.h" #include "fluidsynth/seqbind.h" #include "fluidsynth/log.h" #include "fluidsynth/misc.h" #include "fluidsynth/mod.h" #include "fluidsynth/gen.h" #include "fluidsynth/voice.h" #include "fluidsynth/version.h" #include "fluidsynth/ladspa.h" #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_H */ fluidsynth-2.1.1/include/fluidsynth/000077500000000000000000000000001362231004000175075ustar00rootroot00000000000000fluidsynth-2.1.1/include/fluidsynth/audio.h000066400000000000000000000071161362231004000207660ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_AUDIO_H #define _FLUIDSYNTH_AUDIO_H #ifdef __cplusplus extern "C" { #endif /** * @file audio.h * @brief Functions for audio driver output. * @defgroup AudioFunctions Functions for audio output * * Defines functions for creating audio driver output. Use * new_fluid_audio_driver() to create a new audio driver for a given synth * and configuration settings. The function new_fluid_audio_driver2() can be * used if custom audio processing is desired before the audio is sent to the * audio driver (although it is not as efficient). * * @sa @ref CreatingAudioDriver */ /** * Callback function type used with new_fluid_audio_driver2() to allow for * custom user audio processing before the audio is sent to the driver. This * function is responsible for rendering audio to the buffers. * The buffers passed to this function are allocated and owned by the respective * audio driver and are only valid during that specific call (do not cache them). * For further details please refer to fluid_synth_process(). * @note Whereas fluid_synth_process() allows aliasing buffers, there is the guarentee that @p out * and @p fx buffers provided by fluidsynth's audio drivers never alias. This prevents downstream * applications from e.g. applying a custom effect accidentially to the same buffer multiple times. * @param data The user data parameter as passed to new_fluid_audio_driver2(). * @param len Count of audio frames to synthesize. * @param nfx Count of arrays in \c fx. * @param fx Array of buffers to store effects audio to. Buffers may alias with buffers of \c out. * @param nout Count of arrays in \c out. * @param out Array of buffers to store (dry) audio to. Buffers may alias with buffers of \c fx. * @return Should return #FLUID_OK on success, #FLUID_FAILED if an error occurred. */ typedef int (*fluid_audio_func_t)(void *data, int len, int nfx, float *fx[], int nout, float *out[]); FLUIDSYNTH_API fluid_audio_driver_t *new_fluid_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); FLUIDSYNTH_API fluid_audio_driver_t *new_fluid_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); FLUIDSYNTH_API void delete_fluid_audio_driver(fluid_audio_driver_t *driver); FLUIDSYNTH_API fluid_file_renderer_t *new_fluid_file_renderer(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_file_renderer_process_block(fluid_file_renderer_t *dev); FLUIDSYNTH_API void delete_fluid_file_renderer(fluid_file_renderer_t *dev); FLUIDSYNTH_API int fluid_file_set_encoding_quality(fluid_file_renderer_t *dev, double q); FLUIDSYNTH_API int fluid_audio_driver_register(const char **adrivers); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_AUDIO_H */ fluidsynth-2.1.1/include/fluidsynth/event.h000066400000000000000000000147771362231004000210210ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_EVENT_H #define _FLUIDSYNTH_EVENT_H #ifdef __cplusplus extern "C" { #endif /** * @file event.h * @brief Sequencer event functions and defines. * * Functions and constants for creating/processing sequencer events. */ /** * Sequencer event type enumeration. */ enum fluid_seq_event_type { FLUID_SEQ_NOTE = 0, /**< Note event with duration */ FLUID_SEQ_NOTEON, /**< Note on event */ FLUID_SEQ_NOTEOFF, /**< Note off event */ FLUID_SEQ_ALLSOUNDSOFF, /**< All sounds off event */ FLUID_SEQ_ALLNOTESOFF, /**< All notes off event */ FLUID_SEQ_BANKSELECT, /**< Bank select message */ FLUID_SEQ_PROGRAMCHANGE, /**< Program change message */ FLUID_SEQ_PROGRAMSELECT, /**< Program select message */ FLUID_SEQ_PITCHBEND, /**< Pitch bend message */ FLUID_SEQ_PITCHWHEELSENS, /**< Pitch wheel sensitivity set message @since 1.1.0 was misspelled previously */ FLUID_SEQ_MODULATION, /**< Modulation controller event */ FLUID_SEQ_SUSTAIN, /**< Sustain controller event */ FLUID_SEQ_CONTROLCHANGE, /**< MIDI control change event */ FLUID_SEQ_PAN, /**< Stereo pan set event */ FLUID_SEQ_VOLUME, /**< Volume set event */ FLUID_SEQ_REVERBSEND, /**< Reverb send set event */ FLUID_SEQ_CHORUSSEND, /**< Chorus send set event */ FLUID_SEQ_TIMER, /**< Timer event (useful for giving a callback at a certain time) */ FLUID_SEQ_ANYCONTROLCHANGE, /**< Any control change message (only internally used for remove_events) */ FLUID_SEQ_CHANNELPRESSURE, /**< Channel aftertouch event @since 1.1.0 */ FLUID_SEQ_KEYPRESSURE, /**< Polyphonic aftertouch event @since 2.0.0 */ FLUID_SEQ_SYSTEMRESET, /**< System reset event @since 1.1.0 */ FLUID_SEQ_UNREGISTERING, /**< Called when a sequencer client is being unregistered. @since 1.1.0 */ #ifndef __DOXYGEN__ FLUID_SEQ_LASTEVENT /**< @internal Defines the count of events enums @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ #endif }; /* Event alloc/free */ FLUIDSYNTH_API fluid_event_t *new_fluid_event(void); FLUIDSYNTH_API void delete_fluid_event(fluid_event_t *evt); /* Initializing events */ FLUIDSYNTH_API void fluid_event_set_source(fluid_event_t *evt, fluid_seq_id_t src); FLUIDSYNTH_API void fluid_event_set_dest(fluid_event_t *evt, fluid_seq_id_t dest); /* Timer events */ FLUIDSYNTH_API void fluid_event_timer(fluid_event_t *evt, void *data); /* Note events */ FLUIDSYNTH_API void fluid_event_note(fluid_event_t *evt, int channel, short key, short vel, unsigned int duration); FLUIDSYNTH_API void fluid_event_noteon(fluid_event_t *evt, int channel, short key, short vel); FLUIDSYNTH_API void fluid_event_noteoff(fluid_event_t *evt, int channel, short key); FLUIDSYNTH_API void fluid_event_all_sounds_off(fluid_event_t *evt, int channel); FLUIDSYNTH_API void fluid_event_all_notes_off(fluid_event_t *evt, int channel); /* Instrument selection */ FLUIDSYNTH_API void fluid_event_bank_select(fluid_event_t *evt, int channel, short bank_num); FLUIDSYNTH_API void fluid_event_program_change(fluid_event_t *evt, int channel, short preset_num); FLUIDSYNTH_API void fluid_event_program_select(fluid_event_t *evt, int channel, unsigned int sfont_id, short bank_num, short preset_num); /* Real-time generic instrument controllers */ FLUIDSYNTH_API void fluid_event_control_change(fluid_event_t *evt, int channel, short control, short val); /* Real-time instrument controllers shortcuts */ FLUIDSYNTH_API void fluid_event_pitch_bend(fluid_event_t *evt, int channel, int val); FLUIDSYNTH_API void fluid_event_pitch_wheelsens(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_modulation(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_sustain(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_pan(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_volume(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_reverb_send(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_chorus_send(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_key_pressure(fluid_event_t *evt, int channel, short key, short val); FLUIDSYNTH_API void fluid_event_channel_pressure(fluid_event_t *evt, int channel, short val); FLUIDSYNTH_API void fluid_event_system_reset(fluid_event_t *evt); /* Only for removing events */ FLUIDSYNTH_API void fluid_event_any_control_change(fluid_event_t *evt, int channel); /* Only when unregistering clients */ FLUIDSYNTH_API void fluid_event_unregistering(fluid_event_t *evt); /* Accessing event data */ FLUIDSYNTH_API int fluid_event_get_type(fluid_event_t *evt); FLUIDSYNTH_API fluid_seq_id_t fluid_event_get_source(fluid_event_t *evt); FLUIDSYNTH_API fluid_seq_id_t fluid_event_get_dest(fluid_event_t *evt); FLUIDSYNTH_API int fluid_event_get_channel(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_key(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_velocity(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_control(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_value(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_program(fluid_event_t *evt); FLUIDSYNTH_API void *fluid_event_get_data(fluid_event_t *evt); FLUIDSYNTH_API unsigned int fluid_event_get_duration(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_bank(fluid_event_t *evt); FLUIDSYNTH_API int fluid_event_get_pitch(fluid_event_t *evt); FLUIDSYNTH_API unsigned int fluid_event_get_sfont_id(fluid_event_t *evt); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_EVENT_H */ fluidsynth-2.1.1/include/fluidsynth/gen.h000066400000000000000000000134131362231004000204330ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_GEN_H #define _FLUIDSYNTH_GEN_H #ifdef __cplusplus extern "C" { #endif /** * @file gen.h * @brief Functions and defines for SoundFont generator effects. */ /** * Generator (effect) numbers (Soundfont 2.01 specifications section 8.1.3) */ enum fluid_gen_type { GEN_STARTADDROFS, /**< Sample start address offset (0-32767) */ GEN_ENDADDROFS, /**< Sample end address offset (-32767-0) */ GEN_STARTLOOPADDROFS, /**< Sample loop start address offset (-32767-32767) */ GEN_ENDLOOPADDROFS, /**< Sample loop end address offset (-32767-32767) */ GEN_STARTADDRCOARSEOFS, /**< Sample start address coarse offset (X 32768) */ GEN_MODLFOTOPITCH, /**< Modulation LFO to pitch */ GEN_VIBLFOTOPITCH, /**< Vibrato LFO to pitch */ GEN_MODENVTOPITCH, /**< Modulation envelope to pitch */ GEN_FILTERFC, /**< Filter cutoff */ GEN_FILTERQ, /**< Filter Q */ GEN_MODLFOTOFILTERFC, /**< Modulation LFO to filter cutoff */ GEN_MODENVTOFILTERFC, /**< Modulation envelope to filter cutoff */ GEN_ENDADDRCOARSEOFS, /**< Sample end address coarse offset (X 32768) */ GEN_MODLFOTOVOL, /**< Modulation LFO to volume */ GEN_UNUSED1, /**< Unused */ GEN_CHORUSSEND, /**< Chorus send amount */ GEN_REVERBSEND, /**< Reverb send amount */ GEN_PAN, /**< Stereo panning */ GEN_UNUSED2, /**< Unused */ GEN_UNUSED3, /**< Unused */ GEN_UNUSED4, /**< Unused */ GEN_MODLFODELAY, /**< Modulation LFO delay */ GEN_MODLFOFREQ, /**< Modulation LFO frequency */ GEN_VIBLFODELAY, /**< Vibrato LFO delay */ GEN_VIBLFOFREQ, /**< Vibrato LFO frequency */ GEN_MODENVDELAY, /**< Modulation envelope delay */ GEN_MODENVATTACK, /**< Modulation envelope attack */ GEN_MODENVHOLD, /**< Modulation envelope hold */ GEN_MODENVDECAY, /**< Modulation envelope decay */ GEN_MODENVSUSTAIN, /**< Modulation envelope sustain */ GEN_MODENVRELEASE, /**< Modulation envelope release */ GEN_KEYTOMODENVHOLD, /**< Key to modulation envelope hold */ GEN_KEYTOMODENVDECAY, /**< Key to modulation envelope decay */ GEN_VOLENVDELAY, /**< Volume envelope delay */ GEN_VOLENVATTACK, /**< Volume envelope attack */ GEN_VOLENVHOLD, /**< Volume envelope hold */ GEN_VOLENVDECAY, /**< Volume envelope decay */ GEN_VOLENVSUSTAIN, /**< Volume envelope sustain */ GEN_VOLENVRELEASE, /**< Volume envelope release */ GEN_KEYTOVOLENVHOLD, /**< Key to volume envelope hold */ GEN_KEYTOVOLENVDECAY, /**< Key to volume envelope decay */ GEN_INSTRUMENT, /**< Instrument ID (shouldn't be set by user) */ GEN_RESERVED1, /**< Reserved */ GEN_KEYRANGE, /**< MIDI note range */ GEN_VELRANGE, /**< MIDI velocity range */ GEN_STARTLOOPADDRCOARSEOFS, /**< Sample start loop address coarse offset (X 32768) */ GEN_KEYNUM, /**< Fixed MIDI note number */ GEN_VELOCITY, /**< Fixed MIDI velocity value */ GEN_ATTENUATION, /**< Initial volume attenuation */ GEN_RESERVED2, /**< Reserved */ GEN_ENDLOOPADDRCOARSEOFS, /**< Sample end loop address coarse offset (X 32768) */ GEN_COARSETUNE, /**< Coarse tuning */ GEN_FINETUNE, /**< Fine tuning */ GEN_SAMPLEID, /**< Sample ID (shouldn't be set by user) */ GEN_SAMPLEMODE, /**< Sample mode flags */ GEN_RESERVED3, /**< Reserved */ GEN_SCALETUNE, /**< Scale tuning */ GEN_EXCLUSIVECLASS, /**< Exclusive class number */ GEN_OVERRIDEROOTKEY, /**< Sample root note override */ /** * @brief Initial Pitch * * @note This is not "standard" SoundFont generator, because it is not * mentioned in the list of generators in the SF2 specifications. * It is used by FluidSynth internally to compute the nominal pitch of * a note on note-on event. By nature it shouldn't be allowed to be modulated, * however the specification defines a default modulator having "Initial Pitch" * as destination (cf. SF2.01 page 57 section 8.4.10 MIDI Pitch Wheel to Initial Pitch). * Thus it is impossible to cancel this default modulator, which would be required * to let the MIDI Pitch Wheel controller modulate a different generator. * In order to provide this flexibility, FluidSynth >= 2.1.0 uses a default modulator * "Pitch Wheel to Fine Tune", rather than Initial Pitch. The same "compromise" can * be found on the Audigy 2 ZS for instance. */ GEN_PITCH, GEN_CUSTOM_BALANCE, /**< Balance @note Not a real SoundFont generator */ /* non-standard generator for an additional custom high- or low-pass filter */ GEN_CUSTOM_FILTERFC, /**< Custom filter cutoff frequency */ GEN_CUSTOM_FILTERQ, /**< Custom filter Q */ #ifndef __DOXYGEN__ GEN_LAST /**< @internal Value defines the count of generators (#fluid_gen_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ #endif }; #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_GEN_H */ fluidsynth-2.1.1/include/fluidsynth/ladspa.h000066400000000000000000000053301362231004000211250ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_LADSPA_H #define _FLUIDSYNTH_LADSPA_H #ifdef __cplusplus extern "C" { #endif /** * @file ladspa.h * @brief Functions for manipulating the ladspa effects unit * * This header defines useful functions for programmatically manipulating the ladspa * effects unit of the synth that can be retrieved via fluid_synth_get_ladspa_fx(). * * Using any of those functions requires fluidsynth to be compiled with ladspa support. * Else all of those functions are useless dummies. */ FLUIDSYNTH_API int fluid_ladspa_is_active(fluid_ladspa_fx_t *fx); FLUIDSYNTH_API int fluid_ladspa_activate(fluid_ladspa_fx_t *fx); FLUIDSYNTH_API int fluid_ladspa_deactivate(fluid_ladspa_fx_t *fx); FLUIDSYNTH_API int fluid_ladspa_reset(fluid_ladspa_fx_t *fx); FLUIDSYNTH_API int fluid_ladspa_check(fluid_ladspa_fx_t *fx, char *err, int err_size); FLUIDSYNTH_API int fluid_ladspa_host_port_exists(fluid_ladspa_fx_t *fx, const char *name); FLUIDSYNTH_API int fluid_ladspa_add_buffer(fluid_ladspa_fx_t *fx, const char *name); FLUIDSYNTH_API int fluid_ladspa_buffer_exists(fluid_ladspa_fx_t *fx, const char *name); FLUIDSYNTH_API int fluid_ladspa_add_effect(fluid_ladspa_fx_t *fx, const char *effect_name, const char *lib_name, const char *plugin_name); FLUIDSYNTH_API int fluid_ladspa_effect_can_mix(fluid_ladspa_fx_t *fx, const char *name); FLUIDSYNTH_API int fluid_ladspa_effect_set_mix(fluid_ladspa_fx_t *fx, const char *name, int mix, float gain); FLUIDSYNTH_API int fluid_ladspa_effect_port_exists(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name); FLUIDSYNTH_API int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, float val); FLUIDSYNTH_API int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, const char *name); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_LADSPA_H */ fluidsynth-2.1.1/include/fluidsynth/log.h000066400000000000000000000056411362231004000204470ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_LOG_H #define _FLUIDSYNTH_LOG_H #ifdef __cplusplus extern "C" { #endif /** * @file log.h * @brief Logging interface * * The default logging function of the fluidsynth prints its messages * to the stderr. The synthesizer uses five level of messages: #FLUID_PANIC, * #FLUID_ERR, #FLUID_WARN, #FLUID_INFO, and #FLUID_DBG. * * A client application can install a new log function to handle the * messages differently. In the following example, the application * sets a callback function to display #FLUID_PANIC messages in a dialog, * and ignores all other messages by setting the log function to * NULL: * * @code * fluid_set_log_function(FLUID_PANIC, show_dialog, (void*) root_window); * fluid_set_log_function(FLUID_ERR, NULL, NULL); * fluid_set_log_function(FLUID_WARN, NULL, NULL); * fluid_set_log_function(FLUID_DBG, NULL, NULL); * @endcode */ /** * FluidSynth log levels. */ enum fluid_log_level { FLUID_PANIC, /**< The synth can't function correctly any more */ FLUID_ERR, /**< Serious error occurred */ FLUID_WARN, /**< Warning */ FLUID_INFO, /**< Verbose informational messages */ FLUID_DBG, /**< Debugging messages */ #ifndef __DOXYGEN__ LAST_LOG_LEVEL /**< @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ #endif }; /** * Log function handler callback type used by fluid_set_log_function(). * @param level Log level (#fluid_log_level) * @param message Log message text * @param data User data pointer supplied to fluid_set_log_function(). */ typedef void (*fluid_log_function_t)(int level, const char *message, void *data); FLUIDSYNTH_API fluid_log_function_t fluid_set_log_function(int level, fluid_log_function_t fun, void *data); FLUIDSYNTH_API void fluid_default_log_function(int level, const char *message, void *data); FLUIDSYNTH_API int fluid_log(int level, const char *fmt, ...) #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) __attribute__ ((format (printf, 2, 3))) #endif ; #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_LOG_H */ fluidsynth-2.1.1/include/fluidsynth/midi.h000066400000000000000000000163741362231004000206150ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_MIDI_H #define _FLUIDSYNTH_MIDI_H #ifdef __cplusplus extern "C" { #endif /** * @file midi.h * @brief Functions for MIDI events, drivers and MIDI file playback. */ FLUIDSYNTH_API fluid_midi_event_t *new_fluid_midi_event(void); FLUIDSYNTH_API void delete_fluid_midi_event(fluid_midi_event_t *event); FLUIDSYNTH_API int fluid_midi_event_set_type(fluid_midi_event_t *evt, int type); FLUIDSYNTH_API int fluid_midi_event_get_type(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_channel(fluid_midi_event_t *evt, int chan); FLUIDSYNTH_API int fluid_midi_event_get_channel(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_get_key(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_key(fluid_midi_event_t *evt, int key); FLUIDSYNTH_API int fluid_midi_event_get_velocity(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_velocity(fluid_midi_event_t *evt, int vel); FLUIDSYNTH_API int fluid_midi_event_get_control(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_control(fluid_midi_event_t *evt, int ctrl); FLUIDSYNTH_API int fluid_midi_event_get_value(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_value(fluid_midi_event_t *evt, int val); FLUIDSYNTH_API int fluid_midi_event_get_program(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_program(fluid_midi_event_t *evt, int val); FLUIDSYNTH_API int fluid_midi_event_get_pitch(fluid_midi_event_t *evt); FLUIDSYNTH_API int fluid_midi_event_set_pitch(fluid_midi_event_t *evt, int val); FLUIDSYNTH_API int fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, int size, int dynamic); FLUIDSYNTH_API int fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dynamic); FLUIDSYNTH_API int fluid_midi_event_get_text(fluid_midi_event_t *evt, void **data, int *size); FLUIDSYNTH_API int fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int dynamic); FLUIDSYNTH_API int fluid_midi_event_get_lyrics(fluid_midi_event_t *evt, void **data, int *size); /** * MIDI router rule type. * @since 1.1.0 */ typedef enum { FLUID_MIDI_ROUTER_RULE_NOTE, /**< MIDI note rule */ FLUID_MIDI_ROUTER_RULE_CC, /**< MIDI controller rule */ FLUID_MIDI_ROUTER_RULE_PROG_CHANGE, /**< MIDI program change rule */ FLUID_MIDI_ROUTER_RULE_PITCH_BEND, /**< MIDI pitch bend rule */ FLUID_MIDI_ROUTER_RULE_CHANNEL_PRESSURE, /**< MIDI channel pressure rule */ FLUID_MIDI_ROUTER_RULE_KEY_PRESSURE, /**< MIDI key pressure rule */ #ifndef __DOXYGEN__ FLUID_MIDI_ROUTER_RULE_COUNT /**< @internal Total count of rule types @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time!*/ #endif } fluid_midi_router_rule_type; /** * Generic callback function for MIDI events. * @param data User defined data pointer * @param event The MIDI event * @return Should return #FLUID_OK on success, #FLUID_FAILED otherwise * * Will be used between * - MIDI driver and MIDI router * - MIDI router and synth * to communicate events. * In the not-so-far future... */ typedef int (*handle_midi_event_func_t)(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API fluid_midi_router_t *new_fluid_midi_router(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); FLUIDSYNTH_API void delete_fluid_midi_router(fluid_midi_router_t *handler); FLUIDSYNTH_API int fluid_midi_router_set_default_rules(fluid_midi_router_t *router); FLUIDSYNTH_API int fluid_midi_router_clear_rules(fluid_midi_router_t *router); FLUIDSYNTH_API int fluid_midi_router_add_rule(fluid_midi_router_t *router, fluid_midi_router_rule_t *rule, int type); FLUIDSYNTH_API fluid_midi_router_rule_t *new_fluid_midi_router_rule(void); FLUIDSYNTH_API void delete_fluid_midi_router_rule(fluid_midi_router_rule_t *rule); FLUIDSYNTH_API void fluid_midi_router_rule_set_chan(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add); FLUIDSYNTH_API void fluid_midi_router_rule_set_param1(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add); FLUIDSYNTH_API void fluid_midi_router_rule_set_param2(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add); FLUIDSYNTH_API int fluid_midi_router_handle_midi_event(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API int fluid_midi_dump_prerouter(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API int fluid_midi_dump_postrouter(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API fluid_midi_driver_t *new_fluid_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); FLUIDSYNTH_API void delete_fluid_midi_driver(fluid_midi_driver_t *driver); /** * MIDI player status enum. * @since 1.1.0 */ enum fluid_player_status { FLUID_PLAYER_READY, /**< Player is ready */ FLUID_PLAYER_PLAYING, /**< Player is currently playing */ FLUID_PLAYER_DONE /**< Player is finished playing */ }; FLUIDSYNTH_API fluid_player_t *new_fluid_player(fluid_synth_t *synth); FLUIDSYNTH_API void delete_fluid_player(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_add(fluid_player_t *player, const char *midifile); FLUIDSYNTH_API int fluid_player_add_mem(fluid_player_t *player, const void *buffer, size_t len); FLUIDSYNTH_API int fluid_player_play(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_stop(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_join(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_set_loop(fluid_player_t *player, int loop); FLUIDSYNTH_API int fluid_player_set_midi_tempo(fluid_player_t *player, int tempo); FLUIDSYNTH_API int fluid_player_set_bpm(fluid_player_t *player, int bpm); FLUIDSYNTH_API int fluid_player_set_playback_callback(fluid_player_t *player, handle_midi_event_func_t handler, void *handler_data); FLUIDSYNTH_API int fluid_player_get_status(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_get_current_tick(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_get_total_ticks(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_get_bpm(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_get_midi_tempo(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_seek(fluid_player_t *player, int ticks); /// #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_MIDI_H */ fluidsynth-2.1.1/include/fluidsynth/misc.h000066400000000000000000000035341362231004000206200ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_MISC_H #define _FLUIDSYNTH_MISC_H #ifdef __cplusplus extern "C" { #endif /** * @file misc.h * @brief Miscellaneous utility functions and defines */ /** * Value that indicates success, used by most libfluidsynth functions. * @since 1.1.0 * * @note This was not publicly defined prior to libfluidsynth 1.1.0. When * writing code which should also be compatible with older versions, something * like the following can be used: * * @code * #include * * #ifndef FLUID_OK * #define FLUID_OK (0) * #define FLUID_FAILED (-1) * #endif * @endcode */ #define FLUID_OK (0) /** * Value that indicates failure, used by most libfluidsynth functions. * @since 1.1.0 * * @note See #FLUID_OK for more details. */ #define FLUID_FAILED (-1) FLUIDSYNTH_API int fluid_is_soundfont(const char *filename); FLUIDSYNTH_API int fluid_is_midifile(const char *filename); FLUIDSYNTH_API void fluid_free(void* ptr); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_MISC_H */ fluidsynth-2.1.1/include/fluidsynth/mod.h000066400000000000000000000077261362231004000204530ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_MOD_H #define _FLUIDSYNTH_MOD_H #ifdef __cplusplus extern "C" { #endif /** * @file mod.h * @brief SoundFont modulator functions and constants. */ /** * Flags defining the polarity, mapping function and type of a modulator source. * Compare with SoundFont 2.04 PDF section 8.2. * * Note: Bit values do not correspond to the SoundFont spec! Also note that * #FLUID_MOD_GC and #FLUID_MOD_CC are in the flags field instead of the source field. */ enum fluid_mod_flags { FLUID_MOD_POSITIVE = 0, /**< Mapping function is positive */ FLUID_MOD_NEGATIVE = 1, /**< Mapping function is negative */ FLUID_MOD_UNIPOLAR = 0, /**< Mapping function is unipolar */ FLUID_MOD_BIPOLAR = 2, /**< Mapping function is bipolar */ FLUID_MOD_LINEAR = 0, /**< Linear mapping function */ FLUID_MOD_CONCAVE = 4, /**< Concave mapping function */ FLUID_MOD_CONVEX = 8, /**< Convex mapping function */ FLUID_MOD_SWITCH = 12, /**< Switch (on/off) mapping function */ FLUID_MOD_GC = 0, /**< General controller source type (#fluid_mod_src) */ FLUID_MOD_CC = 16, /**< MIDI CC controller (source will be a MIDI CC number) */ FLUID_MOD_SIN = 0x80, /**< Custom non-standard sinus mapping function */ }; /** * General controller (if #FLUID_MOD_GC in flags). This * corresponds to SoundFont 2.04 PDF section 8.2.1 */ enum fluid_mod_src { FLUID_MOD_NONE = 0, /**< No source controller */ FLUID_MOD_VELOCITY = 2, /**< MIDI note-on velocity */ FLUID_MOD_KEY = 3, /**< MIDI note-on note number */ FLUID_MOD_KEYPRESSURE = 10, /**< MIDI key pressure */ FLUID_MOD_CHANNELPRESSURE = 13, /**< MIDI channel pressure */ FLUID_MOD_PITCHWHEEL = 14, /**< Pitch wheel */ FLUID_MOD_PITCHWHEELSENS = 16 /**< Pitch wheel sensitivity */ }; FLUIDSYNTH_API fluid_mod_t *new_fluid_mod(void); FLUIDSYNTH_API void delete_fluid_mod(fluid_mod_t *mod); FLUIDSYNTH_API size_t fluid_mod_sizeof(void); FLUIDSYNTH_API void fluid_mod_set_source1(fluid_mod_t *mod, int src, int flags); FLUIDSYNTH_API void fluid_mod_set_source2(fluid_mod_t *mod, int src, int flags); FLUIDSYNTH_API void fluid_mod_set_dest(fluid_mod_t *mod, int dst); FLUIDSYNTH_API void fluid_mod_set_amount(fluid_mod_t *mod, double amount); FLUIDSYNTH_API int fluid_mod_get_source1(const fluid_mod_t *mod); FLUIDSYNTH_API int fluid_mod_get_flags1(const fluid_mod_t *mod); FLUIDSYNTH_API int fluid_mod_get_source2(const fluid_mod_t *mod); FLUIDSYNTH_API int fluid_mod_get_flags2(const fluid_mod_t *mod); FLUIDSYNTH_API int fluid_mod_get_dest(const fluid_mod_t *mod); FLUIDSYNTH_API double fluid_mod_get_amount(const fluid_mod_t *mod); FLUIDSYNTH_API int fluid_mod_test_identity(const fluid_mod_t *mod1, const fluid_mod_t *mod2); FLUIDSYNTH_API int fluid_mod_has_source(const fluid_mod_t *mod, int cc, int ctrl); FLUIDSYNTH_API int fluid_mod_has_dest(const fluid_mod_t *mod, int gen); FLUIDSYNTH_API void fluid_mod_clone(fluid_mod_t *mod, const fluid_mod_t *src); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_MOD_H */ fluidsynth-2.1.1/include/fluidsynth/seq.h000066400000000000000000000060471362231004000204570ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_SEQ_H #define _FLUIDSYNTH_SEQ_H #ifdef __cplusplus extern "C" { #endif /** * @file seq.h * @brief MIDI event sequencer. */ /** * Event callback prototype for destination clients. * @param time Current sequencer tick value (see fluid_sequencer_get_tick()). * @param event The event being received * @param seq The sequencer instance * @param data User defined data registered with the client */ typedef void (*fluid_event_callback_t)(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data); FLUID_DEPRECATED FLUIDSYNTH_API fluid_sequencer_t *new_fluid_sequencer(void); FLUIDSYNTH_API fluid_sequencer_t *new_fluid_sequencer2(int use_system_timer); FLUIDSYNTH_API void delete_fluid_sequencer(fluid_sequencer_t *seq); FLUIDSYNTH_API int fluid_sequencer_get_use_system_timer(fluid_sequencer_t *seq); FLUIDSYNTH_API fluid_seq_id_t fluid_sequencer_register_client(fluid_sequencer_t *seq, const char *name, fluid_event_callback_t callback, void *data); FLUIDSYNTH_API void fluid_sequencer_unregister_client(fluid_sequencer_t *seq, fluid_seq_id_t id); FLUIDSYNTH_API int fluid_sequencer_count_clients(fluid_sequencer_t *seq); FLUIDSYNTH_API fluid_seq_id_t fluid_sequencer_get_client_id(fluid_sequencer_t *seq, int index); FLUIDSYNTH_API char *fluid_sequencer_get_client_name(fluid_sequencer_t *seq, fluid_seq_id_t id); FLUIDSYNTH_API int fluid_sequencer_client_is_dest(fluid_sequencer_t *seq, fluid_seq_id_t id); FLUIDSYNTH_API void fluid_sequencer_process(fluid_sequencer_t *seq, unsigned int msec); FLUIDSYNTH_API void fluid_sequencer_send_now(fluid_sequencer_t *seq, fluid_event_t *evt); FLUIDSYNTH_API int fluid_sequencer_send_at(fluid_sequencer_t *seq, fluid_event_t *evt, unsigned int time, int absolute); FLUIDSYNTH_API void fluid_sequencer_remove_events(fluid_sequencer_t *seq, fluid_seq_id_t source, fluid_seq_id_t dest, int type); FLUIDSYNTH_API unsigned int fluid_sequencer_get_tick(fluid_sequencer_t *seq); FLUIDSYNTH_API void fluid_sequencer_set_time_scale(fluid_sequencer_t *seq, double scale); FLUIDSYNTH_API double fluid_sequencer_get_time_scale(fluid_sequencer_t *seq); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_SEQ_H */ fluidsynth-2.1.1/include/fluidsynth/seqbind.h000066400000000000000000000024731362231004000213130ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_SEQBIND_H #define _FLUIDSYNTH_SEQBIND_H #include "seq.h" #ifdef __cplusplus extern "C" { #endif /** * @file seqbind.h * @brief Functions for binding sequencer objects to other subsystems. */ FLUIDSYNTH_API fluid_seq_id_t fluid_sequencer_register_fluidsynth(fluid_sequencer_t *seq, fluid_synth_t *synth); FLUIDSYNTH_API int fluid_sequencer_add_midi_event_to_buffer(void *data, fluid_midi_event_t *event); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_SEQBIND_H */ fluidsynth-2.1.1/include/fluidsynth/settings.h000066400000000000000000000145601362231004000215260ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_SETTINGS_H #define _FLUIDSYNTH_SETTINGS_H #ifdef __cplusplus extern "C" { #endif /** * @file settings.h * @brief Synthesizer settings * @defgroup SettingsFunctions Functions for settings management * * To create a synthesizer object you will have to specify its * settings. These settings are stored in a fluid_settings_t object. * @code * void * my_synthesizer () * { * fluid_settings_t *settings; * fluid_synth_t *synth; * fluid_audio_driver_t *adriver; * * settings = new_fluid_settings (); * fluid_settings_setstr(settings, "audio.driver", "alsa"); * // ... change settings ... * synth = new_fluid_synth (settings); * adriver = new_fluid_audio_driver (settings, synth); * // ... * } * @endcode * @sa @ref CreatingSettings */ /** * Hint FLUID_HINT_BOUNDED_BELOW indicates that the LowerBound field * of the FLUID_PortRangeHint should be considered meaningful. The * value in this field should be considered the (inclusive) lower * bound of the valid range. If FLUID_HINT_SAMPLE_RATE is also * specified then the value of LowerBound should be multiplied by the * sample rate. */ #define FLUID_HINT_BOUNDED_BELOW 0x1 /** Hint FLUID_HINT_BOUNDED_ABOVE indicates that the UpperBound field of the FLUID_PortRangeHint should be considered meaningful. The value in this field should be considered the (inclusive) upper bound of the valid range. If FLUID_HINT_SAMPLE_RATE is also specified then the value of UpperBound should be multiplied by the sample rate. */ #define FLUID_HINT_BOUNDED_ABOVE 0x2 /** * Hint FLUID_HINT_TOGGLED indicates that the data item should be * considered a Boolean toggle. Data less than or equal to zero should * be considered `off' or `false,' and data above zero should be * considered `on' or `true.' FLUID_HINT_TOGGLED may not be used in * conjunction with any other hint. */ #define FLUID_HINT_TOGGLED 0x4 #define FLUID_HINT_OPTIONLIST 0x02 /**< Setting is a list of string options */ /** * Settings type * * Each setting has a defined type: numeric (double), integer, string or a * set of values. The type of each setting can be retrieved using the * function fluid_settings_get_type() */ enum fluid_types_enum { FLUID_NO_TYPE = -1, /**< Undefined type */ FLUID_NUM_TYPE, /**< Numeric (double) */ FLUID_INT_TYPE, /**< Integer */ FLUID_STR_TYPE, /**< String */ FLUID_SET_TYPE /**< Set of values */ }; FLUIDSYNTH_API fluid_settings_t *new_fluid_settings(void); FLUIDSYNTH_API void delete_fluid_settings(fluid_settings_t *settings); FLUIDSYNTH_API int fluid_settings_get_type(fluid_settings_t *settings, const char *name); FLUIDSYNTH_API int fluid_settings_get_hints(fluid_settings_t *settings, const char *name, int *val); FLUIDSYNTH_API int fluid_settings_is_realtime(fluid_settings_t *settings, const char *name); FLUIDSYNTH_API int fluid_settings_setstr(fluid_settings_t *settings, const char *name, const char *str); FLUIDSYNTH_API int fluid_settings_copystr(fluid_settings_t *settings, const char *name, char *str, int len); FLUIDSYNTH_API int fluid_settings_dupstr(fluid_settings_t *settings, const char *name, char **str); FLUIDSYNTH_API int fluid_settings_getstr_default(fluid_settings_t *settings, const char *name, char **def); FLUIDSYNTH_API int fluid_settings_str_equal(fluid_settings_t *settings, const char *name, const char *value); FLUIDSYNTH_API int fluid_settings_setnum(fluid_settings_t *settings, const char *name, double val); FLUIDSYNTH_API int fluid_settings_getnum(fluid_settings_t *settings, const char *name, double *val); FLUIDSYNTH_API int fluid_settings_getnum_default(fluid_settings_t *settings, const char *name, double *val); FLUIDSYNTH_API int fluid_settings_getnum_range(fluid_settings_t *settings, const char *name, double *min, double *max); FLUIDSYNTH_API int fluid_settings_setint(fluid_settings_t *settings, const char *name, int val); FLUIDSYNTH_API int fluid_settings_getint(fluid_settings_t *settings, const char *name, int *val); FLUIDSYNTH_API int fluid_settings_getint_default(fluid_settings_t *settings, const char *name, int *val); FLUIDSYNTH_API int fluid_settings_getint_range(fluid_settings_t *settings, const char *name, int *min, int *max); /** * Callback function type used with fluid_settings_foreach_option() * @param data User defined data pointer * @param name Setting name * @param option A string option for this setting (iterates through the list) */ typedef void (*fluid_settings_foreach_option_t)(void *data, const char *name, const char *option); FLUIDSYNTH_API void fluid_settings_foreach_option(fluid_settings_t *settings, const char *name, void *data, fluid_settings_foreach_option_t func); FLUIDSYNTH_API int fluid_settings_option_count(fluid_settings_t *settings, const char *name); FLUIDSYNTH_API char *fluid_settings_option_concat(fluid_settings_t *settings, const char *name, const char *separator); /** * Callback function type used with fluid_settings_foreach() * @param data User defined data pointer * @param name Setting name * @param type Setting type (#fluid_types_enum) */ typedef void (*fluid_settings_foreach_t)(void *data, const char *name, int type); FLUIDSYNTH_API void fluid_settings_foreach(fluid_settings_t *settings, void *data, fluid_settings_foreach_t func); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_SETTINGS_H */ fluidsynth-2.1.1/include/fluidsynth/sfont.h000066400000000000000000000321751362231004000210210ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_SFONT_H #define _FLUIDSYNTH_SFONT_H #ifdef __cplusplus extern "C" { #endif /** * @file sfont.h * @brief SoundFont plugins * * It is possible to add new SoundFont loaders to the * synthesizer. This API allows for virtual SoundFont files to be loaded * and synthesized, which may not actually be SoundFont files, as long as they * can be represented by the SoundFont synthesis model. * * To add a new SoundFont loader to the synthesizer, call * fluid_synth_add_sfloader() and pass a pointer to an * #fluid_sfloader_t instance created by new_fluid_sfloader(). * On creation, you must specify a callback function \p load * that will be called for every file attempting to load it and * if successful returns a #fluid_sfont_t instance, or NULL if it fails. * * The #fluid_sfont_t structure contains a callback to obtain the * name of the SoundFont. It contains two functions to iterate * though the contained presets, and one function to obtain a * preset corresponding to a bank and preset number. This * function should return a #fluid_preset_t instance. * * The #fluid_preset_t instance contains some functions to obtain * information from the preset (name, bank, number). The most * important callback is the noteon function. The noteon function * is called by fluidsynth internally and * should call fluid_synth_alloc_voice() for every sample that has * to be played. fluid_synth_alloc_voice() expects a pointer to a * #fluid_sample_t instance and returns a pointer to the opaque * #fluid_voice_t structure. To set or increment the values of a * generator, use fluid_voice_gen_set() or fluid_voice_gen_incr(). When you are * finished initializing the voice call fluid_voice_start() to * start playing the synthesis voice. */ /** * Some notification enums for presets and samples. */ enum { FLUID_PRESET_SELECTED, /**< Preset selected notify */ FLUID_PRESET_UNSELECTED, /**< Preset unselected notify */ FLUID_SAMPLE_DONE /**< Sample no longer needed notify */ }; /** * Indicates the type of a sample used by the _fluid_sample_t::sampletype field. * This enum corresponds to the \c SFSampleLink enum in the SoundFont spec. * One \c flag may be bit-wise OR-ed with one \c value. */ enum fluid_sample_type { FLUID_SAMPLETYPE_MONO = 0x1, /**< Value used for mono samples */ FLUID_SAMPLETYPE_RIGHT = 0x2, /**< Value used for right samples of a stereo pair */ FLUID_SAMPLETYPE_LEFT = 0x4, /**< Value used for left samples of a stereo pair */ FLUID_SAMPLETYPE_LINKED = 0x8, /**< Value used for linked sample, which is currently not supported */ FLUID_SAMPLETYPE_OGG_VORBIS = 0x10, /**< Flag used for Ogg Vorbis compressed samples (non-standard compliant extension) as found in the program "sftools" developed by Werner Schweer from MuseScore @since 1.1.7 */ FLUID_SAMPLETYPE_ROM = 0x8000 /**< Flag that indicates ROM samples, causing the sample to be ignored */ }; /** * Method to load an instrument file (does not actually need to be a real file name, * could be another type of string identifier that the \a loader understands). * @param loader SoundFont loader * @param filename File name or other string identifier * @return The loaded instrument file (SoundFont) or NULL if an error occurred. */ typedef fluid_sfont_t *(*fluid_sfloader_load_t)(fluid_sfloader_t *loader, const char *filename); /** * The free method should free the memory allocated for a fluid_sfloader_t instance in * addition to any private data. Any custom user provided cleanup function must ultimately call * delete_fluid_sfloader() to ensure proper cleanup of the #fluid_sfloader_t struct. If no private data * needs to be freed, setting this to delete_fluid_sfloader() is sufficient. * @param loader SoundFont loader */ typedef void (*fluid_sfloader_free_t)(fluid_sfloader_t *loader); FLUIDSYNTH_API fluid_sfloader_t *new_fluid_sfloader(fluid_sfloader_load_t load, fluid_sfloader_free_t free); FLUIDSYNTH_API void delete_fluid_sfloader(fluid_sfloader_t *loader); FLUIDSYNTH_API fluid_sfloader_t *new_fluid_defsfloader(fluid_settings_t *settings); /** * Opens the file or memory indicated by \c filename in binary read mode. * \c filename matches the string provided during the fluid_synth_sfload() call. * * @return returns a file handle on success, NULL otherwise */ typedef void *(* fluid_sfloader_callback_open_t)(const char *filename); /** * Reads \c count bytes to the specified buffer \c buf. * * @return returns #FLUID_OK if exactly \c count bytes were successfully read, else returns #FLUID_FAILED and leaves \a buf unmodified. */ typedef int (* fluid_sfloader_callback_read_t)(void *buf, int count, void *handle); /** * Same purpose and behaviour as fseek. * * @param origin either \c SEEK_SET, \c SEEK_CUR or \c SEEK_END * * @return returns #FLUID_OK if the seek was successfully performed while not seeking beyond a buffer or file, #FLUID_FAILED otherwise */ typedef int (* fluid_sfloader_callback_seek_t)(void *handle, long offset, int origin); /** * Closes the handle returned by #fluid_sfloader_callback_open_t and frees used resources. * * @return returns #FLUID_OK on success, #FLUID_FAILED on error */ typedef int (* fluid_sfloader_callback_close_t)(void *handle); /** @return returns current file offset or #FLUID_FAILED on error */ typedef long (* fluid_sfloader_callback_tell_t)(void *handle); FLUIDSYNTH_API int fluid_sfloader_set_callbacks(fluid_sfloader_t *loader, fluid_sfloader_callback_open_t open, fluid_sfloader_callback_read_t read, fluid_sfloader_callback_seek_t seek, fluid_sfloader_callback_tell_t tell, fluid_sfloader_callback_close_t close); FLUIDSYNTH_API int fluid_sfloader_set_data(fluid_sfloader_t *loader, void *data); FLUIDSYNTH_API void *fluid_sfloader_get_data(fluid_sfloader_t *loader); /** * Method to return the name of a virtual SoundFont. * @param sfont Virtual SoundFont * @return The name of the virtual SoundFont. */ typedef const char *(*fluid_sfont_get_name_t)(fluid_sfont_t *sfont); /** * Get a virtual SoundFont preset by bank and program numbers. * @param sfont Virtual SoundFont * @param bank MIDI bank number (0-16383) * @param prenum MIDI preset number (0-127) * @return Should return an allocated virtual preset or NULL if it could not * be found. */ typedef fluid_preset_t *(*fluid_sfont_get_preset_t)(fluid_sfont_t *sfont, int bank, int prenum); /** * Start virtual SoundFont preset iteration method. * @param sfont Virtual SoundFont * * Starts/re-starts virtual preset iteration in a SoundFont. */ typedef void (*fluid_sfont_iteration_start_t)(fluid_sfont_t *sfont); /** * Virtual SoundFont preset iteration function. * @param sfont Virtual SoundFont * @return NULL when no more presets are available, otherwise the a pointer to the current preset * * Returns preset information to the caller. The returned buffer is only valid until a subsequent * call to this function. */ typedef fluid_preset_t *(*fluid_sfont_iteration_next_t)(fluid_sfont_t *sfont); /** * Method to free a virtual SoundFont bank. Any custom user provided cleanup function must ultimately call * delete_fluid_sfont() to ensure proper cleanup of the #fluid_sfont_t struct. If no private data * needs to be freed, setting this to delete_fluid_sfont() is sufficient. * @param sfont Virtual SoundFont to free. * @return Should return 0 when it was able to free all resources or non-zero * if some of the samples could not be freed because they are still in use, * in which case the free will be tried again later, until success. */ typedef int (*fluid_sfont_free_t)(fluid_sfont_t *sfont); FLUIDSYNTH_API fluid_sfont_t *new_fluid_sfont(fluid_sfont_get_name_t get_name, fluid_sfont_get_preset_t get_preset, fluid_sfont_iteration_start_t iter_start, fluid_sfont_iteration_next_t iter_next, fluid_sfont_free_t free); FLUIDSYNTH_API int delete_fluid_sfont(fluid_sfont_t *sfont); FLUIDSYNTH_API int fluid_sfont_set_data(fluid_sfont_t *sfont, void *data); FLUIDSYNTH_API void *fluid_sfont_get_data(fluid_sfont_t *sfont); FLUIDSYNTH_API int fluid_sfont_get_id(fluid_sfont_t *sfont); FLUIDSYNTH_API const char *fluid_sfont_get_name(fluid_sfont_t *sfont); FLUIDSYNTH_API fluid_preset_t *fluid_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum); FLUIDSYNTH_API void fluid_sfont_iteration_start(fluid_sfont_t *sfont); FLUIDSYNTH_API fluid_preset_t *fluid_sfont_iteration_next(fluid_sfont_t *sfont); /** * Method to get a virtual SoundFont preset name. * @param preset Virtual SoundFont preset * @return Should return the name of the preset. The returned string must be * valid for the duration of the virtual preset (or the duration of the * SoundFont, in the case of preset iteration). */ typedef const char *(*fluid_preset_get_name_t)(fluid_preset_t *preset); /** * Method to get a virtual SoundFont preset MIDI bank number. * @param preset Virtual SoundFont preset * @param return The bank number of the preset */ typedef int (*fluid_preset_get_banknum_t)(fluid_preset_t *preset); /** * Method to get a virtual SoundFont preset MIDI program number. * @param preset Virtual SoundFont preset * @param return The program number of the preset */ typedef int (*fluid_preset_get_num_t)(fluid_preset_t *preset); /** * Method to handle a noteon event (synthesize the instrument). * @param preset Virtual SoundFont preset * @param synth Synthesizer instance * @param chan MIDI channel number of the note on event * @param key MIDI note number (0-127) * @param vel MIDI velocity (0-127) * @return #FLUID_OK on success (0) or #FLUID_FAILED (-1) otherwise * * This method may be called from within synthesis context and therefore * should be as efficient as possible and not perform any operations considered * bad for realtime audio output (memory allocations and other OS calls). * * Call fluid_synth_alloc_voice() for every sample that has * to be played. fluid_synth_alloc_voice() expects a pointer to a * #fluid_sample_t structure and returns a pointer to the opaque * #fluid_voice_t structure. To set or increment the values of a * generator, use fluid_voice_gen_set() or fluid_voice_gen_incr(). When you are * finished initializing the voice call fluid_voice_start() to * start playing the synthesis voice. Starting with FluidSynth 1.1.0 all voices * created will be started at the same time. */ typedef int (*fluid_preset_noteon_t)(fluid_preset_t *preset, fluid_synth_t *synth, int chan, int key, int vel); /** * Method to free a virtual SoundFont preset. Any custom user provided cleanup function must ultimately call * delete_fluid_preset() to ensure proper cleanup of the #fluid_preset_t struct. If no private data * needs to be freed, setting this to delete_fluid_preset() is sufficient. * @param preset Virtual SoundFont preset * @return Should return 0 */ typedef void (*fluid_preset_free_t)(fluid_preset_t *preset); FLUIDSYNTH_API fluid_preset_t *new_fluid_preset(fluid_sfont_t *parent_sfont, fluid_preset_get_name_t get_name, fluid_preset_get_banknum_t get_bank, fluid_preset_get_num_t get_num, fluid_preset_noteon_t noteon, fluid_preset_free_t free); FLUIDSYNTH_API void delete_fluid_preset(fluid_preset_t *preset); FLUIDSYNTH_API int fluid_preset_set_data(fluid_preset_t *preset, void *data); FLUIDSYNTH_API void *fluid_preset_get_data(fluid_preset_t *preset); FLUIDSYNTH_API const char *fluid_preset_get_name(fluid_preset_t *preset); FLUIDSYNTH_API int fluid_preset_get_banknum(fluid_preset_t *preset); FLUIDSYNTH_API int fluid_preset_get_num(fluid_preset_t *preset); FLUIDSYNTH_API fluid_sfont_t *fluid_preset_get_sfont(fluid_preset_t *preset); FLUIDSYNTH_API fluid_sample_t *new_fluid_sample(void); FLUIDSYNTH_API void delete_fluid_sample(fluid_sample_t *sample); FLUIDSYNTH_API size_t fluid_sample_sizeof(void); FLUIDSYNTH_API int fluid_sample_set_name(fluid_sample_t *sample, const char *name); FLUIDSYNTH_API int fluid_sample_set_sound_data(fluid_sample_t *sample, short *data, char *data24, unsigned int nbframes, unsigned int sample_rate, short copy_data); FLUIDSYNTH_API int fluid_sample_set_loop(fluid_sample_t *sample, unsigned int loop_start, unsigned int loop_end); FLUIDSYNTH_API int fluid_sample_set_pitch(fluid_sample_t *sample, int root_key, int fine_tune); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_SFONT_H */ fluidsynth-2.1.1/include/fluidsynth/shell.h000066400000000000000000000051761362231004000210000ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_SHELL_H #define _FLUIDSYNTH_SHELL_H #ifdef __cplusplus extern "C" { #endif /** * @file shell.h * @brief Command shell interface * * The shell interface allows you to send simple textual commands to * the synthesizer, to parse a command file, or to read commands * from the stdin or other input streams. */ FLUIDSYNTH_API fluid_istream_t fluid_get_stdin(void); FLUIDSYNTH_API fluid_ostream_t fluid_get_stdout(void); FLUIDSYNTH_API char *fluid_get_userconf(char *buf, int len); FLUIDSYNTH_API char *fluid_get_sysconf(char *buf, int len); /* The command handler */ FLUIDSYNTH_API fluid_cmd_handler_t *new_fluid_cmd_handler(fluid_synth_t *synth, fluid_midi_router_t *router); FLUIDSYNTH_API void delete_fluid_cmd_handler(fluid_cmd_handler_t *handler); FLUIDSYNTH_API void fluid_cmd_handler_set_synth(fluid_cmd_handler_t *handler, fluid_synth_t *synth); /* Command function */ FLUIDSYNTH_API int fluid_command(fluid_cmd_handler_t *handler, const char *cmd, fluid_ostream_t out); FLUIDSYNTH_API int fluid_source(fluid_cmd_handler_t *handler, const char *filename); FLUIDSYNTH_API void fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler); /* Shell */ FLUIDSYNTH_API fluid_shell_t *new_fluid_shell(fluid_settings_t *settings, fluid_cmd_handler_t *handler, fluid_istream_t in, fluid_ostream_t out, int thread); FLUIDSYNTH_API void delete_fluid_shell(fluid_shell_t *shell); /* TCP/IP server */ FLUIDSYNTH_API fluid_server_t *new_fluid_server(fluid_settings_t *settings, fluid_synth_t *synth, fluid_midi_router_t *router); FLUIDSYNTH_API void delete_fluid_server(fluid_server_t *server); FLUIDSYNTH_API int fluid_server_join(fluid_server_t *server); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_SHELL_H */ fluidsynth-2.1.1/include/fluidsynth/synth.h000066400000000000000000000454101362231004000210310ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_SYNTH_H #define _FLUIDSYNTH_SYNTH_H #ifdef __cplusplus extern "C" { #endif /** * @file synth.h * @brief Embeddable SoundFont synthesizer * * You create a new synthesizer with new_fluid_synth() and you destroy * it with delete_fluid_synth(). Use the fluid_settings_t structure to specify * the synthesizer characteristics. * * You have to load a SoundFont in order to hear any sound. For that * you use the fluid_synth_sfload() function. * * You can use the audio driver functions to open * the audio device and create a background audio thread. * * The API for sending MIDI events is probably what you expect: * fluid_synth_noteon(), fluid_synth_noteoff(), ... */ FLUIDSYNTH_API fluid_synth_t *new_fluid_synth(fluid_settings_t *settings); FLUIDSYNTH_API void delete_fluid_synth(fluid_synth_t *synth); FLUIDSYNTH_API fluid_settings_t *fluid_synth_get_settings(fluid_synth_t *synth); /* MIDI channel messages */ FLUIDSYNTH_API int fluid_synth_noteon(fluid_synth_t *synth, int chan, int key, int vel); FLUIDSYNTH_API int fluid_synth_noteoff(fluid_synth_t *synth, int chan, int key); FLUIDSYNTH_API int fluid_synth_cc(fluid_synth_t *synth, int chan, int ctrl, int val); FLUIDSYNTH_API int fluid_synth_get_cc(fluid_synth_t *synth, int chan, int ctrl, int *pval); FLUIDSYNTH_API int fluid_synth_sysex(fluid_synth_t *synth, const char *data, int len, char *response, int *response_len, int *handled, int dryrun); FLUIDSYNTH_API int fluid_synth_pitch_bend(fluid_synth_t *synth, int chan, int val); FLUIDSYNTH_API int fluid_synth_get_pitch_bend(fluid_synth_t *synth, int chan, int *ppitch_bend); FLUIDSYNTH_API int fluid_synth_pitch_wheel_sens(fluid_synth_t *synth, int chan, int val); FLUIDSYNTH_API int fluid_synth_get_pitch_wheel_sens(fluid_synth_t *synth, int chan, int *pval); FLUIDSYNTH_API int fluid_synth_program_change(fluid_synth_t *synth, int chan, int program); FLUIDSYNTH_API int fluid_synth_channel_pressure(fluid_synth_t *synth, int chan, int val); FLUIDSYNTH_API int fluid_synth_key_pressure(fluid_synth_t *synth, int chan, int key, int val); FLUIDSYNTH_API int fluid_synth_bank_select(fluid_synth_t *synth, int chan, int bank); FLUIDSYNTH_API int fluid_synth_sfont_select(fluid_synth_t *synth, int chan, int sfont_id); FLUIDSYNTH_API int fluid_synth_program_select(fluid_synth_t *synth, int chan, int sfont_id, int bank_num, int preset_num); FLUIDSYNTH_API int fluid_synth_program_select_by_sfont_name(fluid_synth_t *synth, int chan, const char *sfont_name, int bank_num, int preset_num); FLUIDSYNTH_API int fluid_synth_get_program(fluid_synth_t *synth, int chan, int *sfont_id, int *bank_num, int *preset_num); FLUIDSYNTH_API int fluid_synth_unset_program(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_program_reset(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_system_reset(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_all_notes_off(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_all_sounds_off(fluid_synth_t *synth, int chan); /** * The midi channel type used by fluid_synth_set_channel_type() */ enum fluid_midi_channel_type { CHANNEL_TYPE_MELODIC = 0, /**< Melodic midi channel */ CHANNEL_TYPE_DRUM = 1 /**< Drum midi channel */ }; FLUIDSYNTH_API int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type); /* Low level access */ FLUIDSYNTH_API fluid_preset_t *fluid_synth_get_channel_preset(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_start(fluid_synth_t *synth, unsigned int id, fluid_preset_t *preset, int audio_chan, int midi_chan, int key, int vel); FLUIDSYNTH_API int fluid_synth_stop(fluid_synth_t *synth, unsigned int id); /* SoundFont management */ FLUIDSYNTH_API int fluid_synth_sfload(fluid_synth_t *synth, const char *filename, int reset_presets); FLUIDSYNTH_API int fluid_synth_sfreload(fluid_synth_t *synth, int id); FLUIDSYNTH_API int fluid_synth_sfunload(fluid_synth_t *synth, int id, int reset_presets); FLUIDSYNTH_API int fluid_synth_add_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont); FLUIDSYNTH_API int fluid_synth_remove_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont); FLUIDSYNTH_API int fluid_synth_sfcount(fluid_synth_t *synth); FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont(fluid_synth_t *synth, unsigned int num); FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont_by_id(fluid_synth_t *synth, int id); FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont_by_name(fluid_synth_t *synth, const char *name); FLUIDSYNTH_API int fluid_synth_set_bank_offset(fluid_synth_t *synth, int sfont_id, int offset); FLUIDSYNTH_API int fluid_synth_get_bank_offset(fluid_synth_t *synth, int sfont_id); /* Reverb */ FLUIDSYNTH_API int fluid_synth_set_reverb(fluid_synth_t *synth, double roomsize, double damping, double width, double level); FLUIDSYNTH_API int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize); FLUIDSYNTH_API int fluid_synth_set_reverb_damp(fluid_synth_t *synth, double damping); FLUIDSYNTH_API int fluid_synth_set_reverb_width(fluid_synth_t *synth, double width); FLUIDSYNTH_API int fluid_synth_set_reverb_level(fluid_synth_t *synth, double level); FLUIDSYNTH_API void fluid_synth_set_reverb_on(fluid_synth_t *synth, int on); FLUIDSYNTH_API double fluid_synth_get_reverb_roomsize(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_damp(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_level(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_width(fluid_synth_t *synth); /* Chorus */ /** * Chorus modulation waveform type. */ enum fluid_chorus_mod { FLUID_CHORUS_MOD_SINE = 0, /**< Sine wave chorus modulation */ FLUID_CHORUS_MOD_TRIANGLE = 1 /**< Triangle wave chorus modulation */ }; FLUIDSYNTH_API int fluid_synth_set_chorus(fluid_synth_t *synth, int nr, double level, double speed, double depth_ms, int type); FLUIDSYNTH_API int fluid_synth_set_chorus_nr(fluid_synth_t *synth, int nr); FLUIDSYNTH_API int fluid_synth_set_chorus_level(fluid_synth_t *synth, double level); FLUIDSYNTH_API int fluid_synth_set_chorus_speed(fluid_synth_t *synth, double speed); FLUIDSYNTH_API int fluid_synth_set_chorus_depth(fluid_synth_t *synth, double depth_ms); FLUIDSYNTH_API int fluid_synth_set_chorus_type(fluid_synth_t *synth, int type); FLUIDSYNTH_API void fluid_synth_set_chorus_on(fluid_synth_t *synth, int on); FLUIDSYNTH_API int fluid_synth_get_chorus_nr(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_level(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_speed(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_depth(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_get_chorus_type(fluid_synth_t *synth); /* see fluid_chorus_mod */ /* Audio and MIDI channels */ FLUIDSYNTH_API int fluid_synth_count_midi_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_audio_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_audio_groups(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_effects_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_effects_groups(fluid_synth_t *synth); /* Synthesis parameters */ FLUID_DEPRECATED FLUIDSYNTH_API void fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate); FLUIDSYNTH_API void fluid_synth_set_gain(fluid_synth_t *synth, float gain); FLUIDSYNTH_API float fluid_synth_get_gain(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_set_polyphony(fluid_synth_t *synth, int polyphony); FLUIDSYNTH_API int fluid_synth_get_polyphony(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_get_active_voice_count(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_get_internal_bufsize(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_set_interp_method(fluid_synth_t *synth, int chan, int interp_method); /** * Synthesis interpolation method. */ enum fluid_interp { FLUID_INTERP_NONE = 0, /**< No interpolation: Fastest, but questionable audio quality */ FLUID_INTERP_LINEAR = 1, /**< Straight-line interpolation: A bit slower, reasonable audio quality */ FLUID_INTERP_4THORDER = 4, /**< Fourth-order interpolation, good quality, the default */ FLUID_INTERP_7THORDER = 7, /**< Seventh-order interpolation */ FLUID_INTERP_DEFAULT = FLUID_INTERP_4THORDER, /**< Default interpolation method */ FLUID_INTERP_HIGHEST = FLUID_INTERP_7THORDER, /**< Highest interpolation method */ }; /* Generator interface */ FLUIDSYNTH_API int fluid_synth_set_gen(fluid_synth_t *synth, int chan, int param, float value); FLUIDSYNTH_API float fluid_synth_get_gen(fluid_synth_t *synth, int chan, int param); /* Tuning */ FLUIDSYNTH_API int fluid_synth_activate_key_tuning(fluid_synth_t *synth, int bank, int prog, const char *name, const double *pitch, int apply); FLUIDSYNTH_API int fluid_synth_activate_octave_tuning(fluid_synth_t *synth, int bank, int prog, const char *name, const double *pitch, int apply); FLUIDSYNTH_API int fluid_synth_tune_notes(fluid_synth_t *synth, int bank, int prog, int len, const int *keys, const double *pitch, int apply); FLUIDSYNTH_API int fluid_synth_activate_tuning(fluid_synth_t *synth, int chan, int bank, int prog, int apply); FLUIDSYNTH_API int fluid_synth_deactivate_tuning(fluid_synth_t *synth, int chan, int apply); FLUIDSYNTH_API void fluid_synth_tuning_iteration_start(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_tuning_iteration_next(fluid_synth_t *synth, int *bank, int *prog); FLUIDSYNTH_API int fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int prog, char *name, int len, double *pitch); /* Misc */ FLUIDSYNTH_API double fluid_synth_get_cpu_load(fluid_synth_t *synth); FLUID_DEPRECATED FLUIDSYNTH_API const char *fluid_synth_error(fluid_synth_t *synth); /* Default modulators */ /** * Enum used with fluid_synth_add_default_mod() to specify how to handle duplicate modulators. */ enum fluid_synth_add_mod { FLUID_SYNTH_OVERWRITE, /**< Overwrite any existing matching modulator */ FLUID_SYNTH_ADD, /**< Sum up modulator amounts */ }; FLUIDSYNTH_API int fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode); FLUIDSYNTH_API int fluid_synth_remove_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod); /* * Synthesizer plugin * * To create a synthesizer plugin, create the synthesizer as * explained above. Once the synthesizer is created you can call * any of the functions below to get the audio. */ FLUIDSYNTH_API int fluid_synth_write_s16(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr); FLUIDSYNTH_API int fluid_synth_write_float(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr); FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_nwrite_float(fluid_synth_t *synth, int len, float **left, float **right, float **fx_left, float **fx_right); FLUIDSYNTH_API int fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[]); /* Synthesizer's interface to handle SoundFont loaders */ FLUIDSYNTH_API void fluid_synth_add_sfloader(fluid_synth_t *synth, fluid_sfloader_t *loader); FLUIDSYNTH_API fluid_voice_t *fluid_synth_alloc_voice(fluid_synth_t *synth, fluid_sample_t *sample, int channum, int key, int vel); FLUIDSYNTH_API void fluid_synth_start_voice(fluid_synth_t *synth, fluid_voice_t *voice); FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t *synth, fluid_voice_t *buf[], int bufsize, int ID); FLUIDSYNTH_API int fluid_synth_handle_midi_event(void *data, fluid_midi_event_t *event); /** * Specifies the type of filter to use for the custom IIR filter */ enum fluid_iir_filter_type { FLUID_IIR_DISABLED = 0, /**< Custom IIR filter is not operating */ FLUID_IIR_LOWPASS, /**< Custom IIR filter is operating as low-pass filter */ FLUID_IIR_HIGHPASS, /**< Custom IIR filter is operating as high-pass filter */ FLUID_IIR_LAST /**< @internal Value defines the count of filter types (#fluid_iir_filter_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ }; /** * Specifies optional settings to use for the custom IIR filter. Can be bitwise ORed. */ enum fluid_iir_filter_flags { FLUID_IIR_Q_LINEAR = 1 << 0, /**< The Soundfont spec requires the filter Q to be interpreted in dB. If this flag is set the filter Q is instead assumed to be in a linear range */ FLUID_IIR_Q_ZERO_OFF = 1 << 1, /**< If this flag the filter is switched off if Q == 0 (prior to any transformation) */ FLUID_IIR_NO_GAIN_AMP = 1 << 2 /**< The Soundfont spec requires to correct the gain of the filter depending on the filter's Q. If this flag is set the filter gain will not be corrected. */ }; FLUIDSYNTH_API int fluid_synth_set_custom_filter(fluid_synth_t *, int type, int flags); /* LADSPA */ FLUIDSYNTH_API fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth); /* API: Poly mono mode */ /** Interface to poly/mono mode variables * * Channel mode bits OR-ed together so that it matches with the midi spec: poly omnion (0), mono omnion (1), poly omnioff (2), mono omnioff (3) */ enum fluid_channel_mode_flags { FLUID_CHANNEL_POLY_OFF = 0x01, /**< if flag is set, the basic channel is in mono on state, if not set poly is on */ FLUID_CHANNEL_OMNI_OFF = 0x02, /**< if flag is set, the basic channel is in omni off state, if not set omni is on */ }; /** Indicates the breath mode a channel is set to */ enum fluid_channel_breath_flags { FLUID_CHANNEL_BREATH_POLY = 0x10, /**< when channel is poly, this flag indicates that the default velocity to initial attenuation modulator is replaced by a breath to initial attenuation modulator */ FLUID_CHANNEL_BREATH_MONO = 0x20, /**< when channel is mono, this flag indicates that the default velocity to initial attenuation modulator is replaced by a breath modulator */ FLUID_CHANNEL_BREATH_SYNC = 0x40, /**< when channel is mono, this flag indicates that the breath controller(MSB)triggers noteon/noteoff on the running note */ }; /** Indicates the mode a basic channel is set to */ enum fluid_basic_channel_modes { FLUID_CHANNEL_MODE_MASK = (FLUID_CHANNEL_OMNI_OFF | FLUID_CHANNEL_POLY_OFF), /**< Mask Poly and Omni bits of #fluid_channel_mode_flags, usually only used internally */ FLUID_CHANNEL_MODE_OMNION_POLY = FLUID_CHANNEL_MODE_MASK & (~FLUID_CHANNEL_OMNI_OFF & ~FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 0 */ FLUID_CHANNEL_MODE_OMNION_MONO = FLUID_CHANNEL_MODE_MASK & (~FLUID_CHANNEL_OMNI_OFF & FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 1 */ FLUID_CHANNEL_MODE_OMNIOFF_POLY = FLUID_CHANNEL_MODE_MASK & (FLUID_CHANNEL_OMNI_OFF & ~FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 2 */ FLUID_CHANNEL_MODE_OMNIOFF_MONO = FLUID_CHANNEL_MODE_MASK & (FLUID_CHANNEL_OMNI_OFF | FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 3 */ FLUID_CHANNEL_MODE_LAST /**< @internal Value defines the count of basic channel modes (#fluid_basic_channel_modes) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ }; FLUIDSYNTH_API int fluid_synth_reset_basic_channel(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_get_basic_channel(fluid_synth_t *synth, int chan, int *basic_chan_out, int *mode_chan_out, int *basic_val_out); FLUIDSYNTH_API int fluid_synth_set_basic_channel(fluid_synth_t *synth, int chan, int mode, int val); /** Interface to mono legato mode * * Indicates the legato mode a channel is set to * n1,n2,n3,.. is a legato passage. n1 is the first note, and n2,n3,n4 are played legato with previous note. */ enum fluid_channel_legato_mode { FLUID_CHANNEL_LEGATO_MODE_RETRIGGER, /**< Mode 0 - Release previous note, start a new note */ FLUID_CHANNEL_LEGATO_MODE_MULTI_RETRIGGER, /**< Mode 1 - On contiguous notes retrigger in attack section using current value, shape attack using current dynamic and make use of previous voices if any */ FLUID_CHANNEL_LEGATO_MODE_LAST /**< @internal Value defines the count of legato modes (#fluid_channel_legato_mode) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ }; FLUIDSYNTH_API int fluid_synth_set_legato_mode(fluid_synth_t *synth, int chan, int legatomode); FLUIDSYNTH_API int fluid_synth_get_legato_mode(fluid_synth_t *synth, int chan, int *legatomode); /** Interface to portamento mode * * Indicates the portamento mode a channel is set to */ enum fluid_channel_portamento_mode { FLUID_CHANNEL_PORTAMENTO_MODE_EACH_NOTE, /**< Mode 0 - Portamento on each note (staccato or legato) */ FLUID_CHANNEL_PORTAMENTO_MODE_LEGATO_ONLY, /**< Mode 1 - Portamento only on legato note */ FLUID_CHANNEL_PORTAMENTO_MODE_STACCATO_ONLY, /**< Mode 2 - Portamento only on staccato note */ FLUID_CHANNEL_PORTAMENTO_MODE_LAST /**< @internal Value defines the count of portamento modes (#fluid_channel_portamento_mode) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ }; FLUIDSYNTH_API int fluid_synth_set_portamento_mode(fluid_synth_t *synth, int chan, int portamentomode); FLUIDSYNTH_API int fluid_synth_get_portamento_mode(fluid_synth_t *synth, int chan, int *portamentomode); /* Interface to breath mode */ FLUIDSYNTH_API int fluid_synth_set_breath_mode(fluid_synth_t *synth, int chan, int breathmode); FLUIDSYNTH_API int fluid_synth_get_breath_mode(fluid_synth_t *synth, int chan, int *breathmode); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_SYNTH_H */ fluidsynth-2.1.1/include/fluidsynth/types.h000066400000000000000000000072331362231004000210310ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_TYPES_H #define _FLUIDSYNTH_TYPES_H #ifdef __cplusplus extern "C" { #endif /** * @file types.h * @brief Type declarations */ typedef struct _fluid_hashtable_t fluid_settings_t; /**< Configuration settings instance */ typedef struct _fluid_synth_t fluid_synth_t; /**< Synthesizer instance */ typedef struct _fluid_voice_t fluid_voice_t; /**< Synthesis voice instance */ typedef struct _fluid_sfloader_t fluid_sfloader_t; /**< SoundFont loader plugin */ typedef struct _fluid_sfont_t fluid_sfont_t; /**< SoundFont */ typedef struct _fluid_preset_t fluid_preset_t; /**< SoundFont preset */ typedef struct _fluid_sample_t fluid_sample_t; /**< SoundFont sample */ typedef struct _fluid_mod_t fluid_mod_t; /**< SoundFont modulator */ typedef struct _fluid_audio_driver_t fluid_audio_driver_t; /**< Audio driver instance */ typedef struct _fluid_file_renderer_t fluid_file_renderer_t; /**< Audio file renderer instance */ typedef struct _fluid_player_t fluid_player_t; /**< MIDI player instance */ typedef struct _fluid_midi_event_t fluid_midi_event_t; /**< MIDI event */ typedef struct _fluid_midi_driver_t fluid_midi_driver_t; /**< MIDI driver instance */ typedef struct _fluid_midi_router_t fluid_midi_router_t; /**< MIDI router instance */ typedef struct _fluid_midi_router_rule_t fluid_midi_router_rule_t; /**< MIDI router rule */ typedef struct _fluid_hashtable_t fluid_cmd_hash_t; /**< Command handler hash table */ typedef struct _fluid_shell_t fluid_shell_t; /**< Command shell */ typedef struct _fluid_server_t fluid_server_t; /**< TCP/IP shell server instance */ typedef struct _fluid_event_t fluid_event_t; /**< Sequencer event */ typedef struct _fluid_sequencer_t fluid_sequencer_t; /**< Sequencer instance */ typedef struct _fluid_ramsfont_t fluid_ramsfont_t; /**< RAM SoundFont */ typedef struct _fluid_rampreset_t fluid_rampreset_t; /**< RAM SoundFont preset */ typedef struct _fluid_cmd_handler_t fluid_cmd_handler_t; /**< Shell Command Handler */ typedef struct _fluid_ladspa_fx_t fluid_ladspa_fx_t; /**< LADSPA effects instance */ typedef struct _fluid_file_callbacks_t fluid_file_callbacks_t; /**< Callback struct to perform custom file loading of soundfonts */ typedef int fluid_istream_t; /**< Input stream descriptor */ typedef int fluid_ostream_t; /**< Output stream descriptor */ typedef short fluid_seq_id_t; /**< Unique client IDs used by the sequencer and #fluid_event_t, obtained by fluid_sequencer_register_client() and fluid_sequencer_register_fluidsynth() */ #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_TYPES_H */ fluidsynth-2.1.1/include/fluidsynth/version.h.in000066400000000000000000000032121362231004000217500ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_VERSION_H #define _FLUIDSYNTH_VERSION_H #ifdef __cplusplus extern "C" { #endif /** * @file version.h * @brief Library version functions and defines */ #define FLUIDSYNTH_VERSION @FLUIDSYNTH_VERSION@ /**< String constant of libfluidsynth version. */ #define FLUIDSYNTH_VERSION_MAJOR @FLUIDSYNTH_VERSION_MAJOR@ /**< libfluidsynth major version integer constant. */ #define FLUIDSYNTH_VERSION_MINOR @FLUIDSYNTH_VERSION_MINOR@ /**< libfluidsynth minor version integer constant. */ #define FLUIDSYNTH_VERSION_MICRO @FLUIDSYNTH_VERSION_MICRO@ /**< libfluidsynth micro version integer constant. */ FLUIDSYNTH_API void fluid_version(int *major, int *minor, int *micro); FLUIDSYNTH_API char* fluid_version_str(void); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_VERSION_H */ fluidsynth-2.1.1/include/fluidsynth/voice.h000066400000000000000000000055301362231004000207700ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUIDSYNTH_VOICE_H #define _FLUIDSYNTH_VOICE_H #ifdef __cplusplus extern "C" { #endif /** * @file voice.h * @brief Synthesis voice manipulation functions. * * The interface to the synthesizer's voices. * Examples on using them can be found in fluid_defsfont.c. * Most of these functions should only be called from within synthesis context, * such as the SoundFont loader's noteon method. */ /** * Enum used with fluid_voice_add_mod() to specify how to handle duplicate modulators. */ enum fluid_voice_add_mod { FLUID_VOICE_OVERWRITE, /**< Overwrite any existing matching modulator */ FLUID_VOICE_ADD, /**< Add (sum) modulator amounts */ FLUID_VOICE_DEFAULT /**< For default modulators only, no need to check for duplicates */ }; FLUIDSYNTH_API void fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode); FLUIDSYNTH_API float fluid_voice_gen_get(fluid_voice_t *voice, int gen); FLUIDSYNTH_API void fluid_voice_gen_set(fluid_voice_t *voice, int gen, float val); FLUIDSYNTH_API void fluid_voice_gen_incr(fluid_voice_t *voice, int gen, float val); FLUIDSYNTH_API unsigned int fluid_voice_get_id(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_get_channel(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_get_key(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_get_actual_key(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_get_velocity(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_get_actual_velocity(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_is_playing(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_is_on(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_is_sustained(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_is_sostenuto(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_optimize_sample(fluid_sample_t *s); FLUIDSYNTH_API void fluid_voice_update_param(fluid_voice_t *voice, int gen); #ifdef __cplusplus } #endif #endif /* _FLUIDSYNTH_VOICE_H */ fluidsynth-2.1.1/sf2/000077500000000000000000000000001362231004000143655ustar00rootroot00000000000000fluidsynth-2.1.1/sf2/COPYRIGHT.txt000066400000000000000000000015031362231004000164750ustar00rootroot00000000000000 Vintage Dreams Waves v 2.0. for Creative Labs' AWE Soundcards (EMU Soundfont 2 Format) Copyright (c) Ian Wilson, 1996 (Updated January 1998) This soundfont is freeware. You may freely use and/or redistribute it subject to the following terms: 1. It is not altered, edited, modified, ripped, or converted to other formats, except for private use only. 2. It is distributed with this copyright notice. This soundfont is distributed WITHOUT WARRANTY, and without the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. No liability or damages can be inferred upon the said copyright owner, Ian Wilson. Any feedback, contact Ian Wilson. vintagedreamworks@hotmail.com http://www.geocities.com/SiliconValley/Campus/8645/index.html http://members.nbci.com/silicon39/ http://www.mp3.com/silicon39 fluidsynth-2.1.1/sf2/VintageDreamsWaves-v2.sf2000066400000000000000000011464201362231004000210750ustar00rootroot00000000000000RIFFsfbkLISTINFOifilINAMVintage Dreams Waves v 2.0isngEMU8000irom1MGMiverIPRDSBAWE32IENG Ian WilsonISFTSFEDT v1.00:SFEDT v1.10ICRDOct 24, 1996ICMTCreated by Ian Wilson Last Update - January 1998 eMail : aztec1@bellatlantic.net iwilson@butlerintl.com Downloaded from HammerSound http://www.pvv.org/~thammer/HammerSound/ICOP(Copyright (c) Ian Wilson - October 1996LISTsdtasmplv W\A.'@(Ѐƀ:~50*% w: - Q;c ǁJEYN"/}CHMLS|X]Kbfknruxz|X~[QA|-ٽlʇ>z94.a)L$mpcY i#< (3#=*H1R8\?fFoM{T[bg]SIąΌؓ WOHA:3,% ոήǤ{qipw~4* ك5>HT]gq{šɤЮ׸ )3#=*G1R8\?fFpMzT[bipw~ s$c*07s #&_-2.+-)&w$@" oխg΋F -kǿ-ƴW"Tm󁍂Յjͤ@PΖ{<ɿzT5 !,Y7-BLW\(3=)HR]zgq|}(~~u;::9:9%9v876543420N/-,>*]dUӮj=-=\v }vi\N:'!->O`/cba&aZB) P+:L ;ZzmXN.-ԣFג/͍}wun'kFeeh{@{DsehXI&5%Q_!X2SCNTcbSba`So;X# 4u !^] KDiK]UXppZHp9R9?hgpDߝ>ǎqjIA!~U4*:_sZV_qTjW7R99v*$j]$#ߘ?V!!)H[`VUhq`ye`98g9394LGfƔǺڎǤڢn>4 'BL] VZqo|YG9v9EB%[*`?>ڠJIe$tXErf3B;,#H==!M wnƶm ~m*((2DF'B=U@IKHj9)#5%_.u}UMѓз W+.:7EV95$ b06sPaPͿ˂ m%+.:Y.V(-A(I vZLgwKז4./:DA') ~ W% Q)(c@ #W8>.lϰ+5"l IZߩULO%3 (L8q;s3( %,G4 $8 "%ߺֵ {gJ?k*).x oaH(QC: c_ 01! ҈\-!!"$"$; I 4q$a6+Gε5/$ ! +3h6R1,[6ͭ)s:)P>0 0 #I9׽X3!G r29y߲p `C2nffc', D>@ ;9ca ABq C8| {գ +c *b+\2qN>U:3(uCsBL͡+|g rq.eF! $VrF"#!7?\30IV]>< L #$S t#uw;3) B C|^&56*Is8I  K t p$46,#"-@ =#  -e \Q3v-10h x%&tz &1AǐР+#@ 3!a");,1/%" r(/ J x{ NZV9 y|S  */^Q$:_ p+,QF ½׫VV4%`)iQg9\1C Td蒿 hYy \5THe,$Ci* FrIYm0/0< &@E BA֙ 0 1o{&s'2<`G-T n  ] i# "K6D>1jbqK.i u"B! f r ?ѹYU JZz1$=-W EY 0c r A#q&x63+gÔݘTP 53[ND 5|2+q\/61 *1$';O`1/jYʇ-Ōߕ  Ӷ=¥~&',xi*3 YάlӌN!U0-&'9G!-k?s?亚l]R -<JLG7 (Z9F>"s mEgJy6^m=8;)'y5'").  A'*/98$HL mѐ7,D7~c" 137WK|7ſ#l \!;G86{09 i [ ߊ,~(B3)q >1<$F (vZN /НV1Cb߲b,31) @9 FM%{ XR&_*VAb;ľ/ :BA5HR/w",$_):! A/N  S3 ظsH|ےƒe?ap*E$#NBvdX *D+z"4EB}>6!'<CbJa6=•5ϸ#= 2#,P! M&dAĝL=[>$T a) j Z,5!SV(HNVY$ OKA(kT <͓|x_Jz%1%][-)28,3wTըM[ӴGϭdzs &6c7 (&. 3*N  IҿNj4] C6w Pj?GYQ?4,#7KDzݨGrz &+/7<61g8:(\%"Y8Վ V0.N/ e%D{շU)n'M%zX25aS)ڷ)3ڢA(8 $"˱C 0r'~+0 % \&!$-$#l qٸ;)$MAkSDBT\X)N 2O_P Ml fk>Aޠ 'JWjw˞&{ uZ*}*j K U (՝)%d-J  >UA! T/Ds(g߅y[JJ;2hNRC5 '61L,4+zmޗjrQ ^< o3HJ6}+&%1v\ $"7t;0 yb/ Fj}~/_7B).AmHk2h  ?~x!1 }"P7, \  21MZ84bߋq@ -C~L1 :. x 9Q`0R=qFG*7+" sNK>{׹9dTdmZ1NPK:-k 4?U“ܞ#5d*SG) ].~.^id ޙE׉ݩ#a #ɤ֨n:"&&1@E<7 RV!B"K:#ܫτxuZ|\& (->hMP"< &U,l F.S4"Uߪ;C^CAI}N&.z"# ^8;+ "8~- )24#pu) # UD_=>U ([lwըDo5ܾ"M r T)V!'2.?#7y 0qݸ[/":H. ֙E P%9Վ'<ϢM%/>+) X[fhKϯԽil(G?"5&' :b'$JD^Iz8ҡT 6:#F-i3[H1L3,1DD*.'H~8xTVA "k LQ*2.$F;FWN7!n乨mcsε+0 'n &BW9] /R@83q "~<Ԯ ?fI&[1!A;N<2d|YԺn1*UжgՒi. z y5O9c& sC"-()$--A!)XP8 өWE-4-(+^>kT _]+ZPEIL/tЦɨD9EϻRѠ" a p  "n'IZ!E9@~U *F.δ % 1C!v?e9G #p$.C qkA"v@0C' ';=BAM[Jg3(  ɸПPϨp޸2"/P83#o =2D4E>/?K )_mWOIϿҖל;&:E,ֽ(,3G-,p5V]>, AbW{Ed{S[%wUNύ _j!i}K0^ ʑ,eCF؜Q4&O&/21,/D=7 hS } rj*T x#[. J  " H4 5~8 /51X5Y  \4:C"2(!:T;I7z S! >0=B  1l$J 5%T5My5?0 y eO(-# U`wп"ӝ6wJ)#4E/9 0++σ7L (f*(0aA6C=J9/&_a'5HJ tP v;<,ЀG O , i,G8=e:# 6Plʹ#%' _-@?UKV\7L 1_UT֞#!a~$|e>+_@s]3u%K02+KnPݼք–x xH]ѾQL:M};/Y"R$1 d7=t~f,fW ہ/?!n2Ė&8qZ6OGMM-=4!p45+JY:||!q9|3 q i) ]Q=$wڵ.?C7 Y/`$ 1 Q"C#<{]լ?!W,#Zv )ۓ:a5]NZa* ;u#(?۔y xL7?/ݑ, xY /*d uQ / Q TB]T}^F5%L`DW l(N!Nt.M0.)_b K..J F]-P#tLdԽK3"' 0*gi3' ,+ͩF-l0-K4 $)`!~!-;M;sEY#X1q 8Z ] E0F2 Pk;ݧM60%f {^M-kM6[S "OJE]WJ'˥tSU} IB踩! #y; =C'6=3"aA*ͭ"#P.]58s;FDiVC8Wf@5 L (G ('Z(s(=.^8~5xwl1E1 V #!*.,7  fo"`f &YqG];H_;g kTъ1=T6/B Li!S|!{?=l Ix^ 5F2N_.ZiH?!Ֆ:j%h 'g^cbo= h(+/iB?;JB^3O2?m˝_+Fh>*7v˫(/}ۥ"fy$8ׂ+!w4ԏv")v30%'/H {E)n8:\,̙PCDc */E +ʣ/9 NG36R  ':ȓ (ń26 I2J e&Nf";+9 (Z@(^"(`\$k8*|r?ئR3shӼ<t;1 q=-x$Y(kԣ!j_2"{'8n2 * @mR2Z7gi~&$WFP#S 4BX݋6˽-t_(k &00?_B  q (פ:UCyp2LU(;207Jv!~zo7ͯGe}x7S Ak2}r~q9&ƧiXW+ U ח" N%[h,4X3fY$" (*L8~$XG՟$L!.wg xP}#B27n" }ۀF%='h !,j% Ƀ7_4, z !| 3$;3&,7^Lk>&MU(7e86D=jF !KԀoB& 'x| )]$P)$p +c^L]7%›@5R)^g47.ϑ"o>L0zILd+:ח9c݉G W>$! * u&\& T*4&*)}(miA,]t $ 1TV'-aL. { .yT,Vߧl 1R!^) 68 N8%*-G3 hy߲a4 "U&)8=d-0%HdԡLR-': ! j qm1EZB%! _+' 0#L-)Mwe1GF{Q *o2 fD$}A8е$mׯi#V"@ZI:Vu&$z"1ſFwʬROR bu% /قB=$9҆׷672  9/ #F74Ĥ)>Ca<Juc@97G@ڬrBی e˽4e!F7#   E(q)ؘB81ޡ$@6T٢![9 d=E.Ft@!%1 i0C,X'NG-%T ߪ%YUp?r2#@?!(@ ٯ0)1F Z0{&,2e6D3bz' |c!\w>G$:&bX"[& eȎ]2t.0sJ)%_P2 6ל8.7w՛IAZy-EP,؞"yڽ@6QuT5?D+*F65 r&~} d& %d$ܕ4r՚$ܔ*@+/ԋ bu$N*hZ-U N'zX4]Z)K &CSiK;vBcC2S m*ѿBQ/8G&{=@.ԇµa+ .,:*MkJE'IYa"\f a/,EL BMɗ)߄N +xq! =Bf3Oۖ3fN*q/cڑq/Dy.9'9b]5Ln Ҵ,f: K P%S$`H/ Q-@"n,1d,9)ʴ[V:kϠ J߇2*Ԓ z z`( 1 T tO)#[O0G ݴ6+8x,M:ؒl ۉ?CȜYyf `cCF^`.4 F> Zڀ'֩&f x"G"Kt*3`'8h2Ȳ & 3 \XZܜ.%=+K \)7F&Ը-: i ,$ #]E (213 V6v_ڽP֌:B>NjRRG 7 og,+,ahVRYGqf!WrYM 4 :[6Cb6y49*+y 3#'X4NS,&9d*7~ȘY"e gXZ'P!?I Ɇ&B/R)o -,ac04b!G++W'!>)Ԛ b  .Ѷ7ȝC9!* `0ڟ` J6D+r=yj % fDř&c W6vΆ+Ά {6(YIƘbLGN2- 3-G[5ŝ/ը C"@$?~ Zb rp1̈́#QH($hաgC ' 36?7Z>W#}(R6VPQ'hMց@OvHν50\l",dw>| Bt7x ,|Rk"ز3BJ'y0  /1'n  we'q  P$8$%4\m!d,>9jO))9A }6T9EbY3%nke9,KVa Ys8Rϙ*T K&ݭ f߲+6 E'x!L~)%%HL1xR.V# D_'dRwh&U~w1=q,SE$;ۺ$)ASd o:^@ B.T X41^Ԝ$teZ}P\~@'w@@|# Z@Sh/η" `z"E :@N4+Uiik+̐w. SB(q zn.`!̨,>7Y0*Iڦ@ 92$j5Ah|)в2d%$ Ed` Oc24'u'V ,By)X{!>`t%h M0 S ;iGgĴ#6mmfdLJ߹ 'd@-;z'֥"suDEۿ2/rK%R09ZppD62P/^Y&.6PO(Bؿ*U>х[B> ԓ!Msc%?(lR15 U~t&а;ؿf?ZY!@;F@ *N|YE<&_`4'\ )mٰekX7%)H-98Đ,ד%~i8$CJ?9`?d_ Dvڽ940J( 5$ן,h |N I'@aù0U?`|np*ޙA Z2O%9:U,`F'Q,Gc*J>ھ:-I$YZw uKJ% %x00]*c(93_}#by=T=:/mn'b$c#<F:ЂP E$^| j X(F /0} kL)%Rty;Ý 'L.I&Hǻ5Cp K|q0/(FsWOh`b^xz̵%|l~R>y 5HtzE <C,ziu7〙UP ~mW("ϱكb \dziD؈`dgY6ӂ͏WӢ,0p/}KMQo;%wx?_#AI%|Rr0"bVj]!{ș҅{a_J1 >?M e F[  v #Сcq7TV!: :fwlIayrͦQ38@zUC'zm\3ƚ9W(6Iއ{) 0 O zAz7 _ݚ-$ˀ2 %f ".1LGF{'1qʶPAYDXP@G#/:5TڣJ#-I$sKLN;.T F7@2-ojtr2 |[tם˕I6@C>+9()N(-DnQ$x # jG 'GXBd/NA7mӞl t 1Mc/Y0q@/F$8+جPDe QXةa -3bDx>(&ug Nq.-!46> =f’Jt]D( -lIBGMt7(#F߷fƎ?b8\wr> 2γT16i)  [Q  31,$&7- 9=(? BUĎo.q` %GCy8eY %C" !p=s C֔<VG t@Gj/SZ-M.LuFr4D6j8$' L!1XԐۗSOLk2 :=,UILT*bi-a dơ"?yR*WkU+7WrCʷO 8@7?1<] 3QѴڣK/`pEc#j003v>,29ɋ}. /"CE[k2y̜YoB?jb^S7nHvq '(hIֺԒkP7vm܂ L& %qIެ+9u/!V) lj*3kr&GƼ!376@LROH8OۚIG߇ x n(~42a[a>4<iˈícNUo|Pt˼t!,RR7?3IEwP 9b<Ծo,NjLw T(4A>.;e}ةiP]g_[L)U%²l3XJ.. J}q22x1ƗԦ !# jK0BfBYpaZIP+ i[·Âs[3i%}dڗؾ̯(P0 ^jA *ISQI5"?x AL/7ۘD3y| ["0V>s; ;ZO8SЄ~1ME+[ @Z z+=;Nx'qנ2 #&2e2BrYW K7_@࡚*B6fr[*3Vйg=T 3X!R>}" ֘·2 /6?y v"Tљ"10 ?&7v-LJV3*#^vp%21&)ԃ4p f32$6f E<@F7+Qd:v3,uR:jOsp }w-`@#'*>B'iVli?!#L--?.? ExƩtӑ?n ]|U3 , .i bX$ &9%mQɷͺVGpx4(˸h54UfWY/WܤE:Oc_MN6c#ҋ Ѳ ~8D- oiWE>ئ%9t8B s+w1ՙ""l,3o8BK;<&*tʜ*r0}'01?$N_O-: uWn^ +"" f5HEHB7.?WﯽƴƧqlALq- DbfN# F۫yֽr0BK*6m5 v@pOF%ΐNf244m%g؈.-u j*'DӺZ>t D  x*D=-Y  Z9;')! w)WB3^5A/m32<2rZmP\Z}<hh+%t#D-iė0;>GT?p:KL$&K"  2C RhMҲO!W`R5[==ȀA|70(9e/gXߤ\)%W'Is1.H4Lt4MZ'x5}ȍ 0'(r%5}_$8@o>F)u_ZxH 3+h1QBHtJ&( - )}1(N} aFѶ 5q.`$ g7#7-fzxdi 'h {3_CAr8R;FL"YpAԭ : UvW וD^w!/1& I J.vDG/B GA3m0M)BgCPvr$1mJGf*~LQ2<ׅen%z4!!11HxŦa 5>&*Ց7= 3PSH-! *%oLExj Ӂ%'m4WGN#6Fe:V$ϔϮ*3oϔ 6 yۧR B~7k2n2  !!\򛘗W4 *APR;:B7ZR?ٰf+1C/"aJ@ '!iٱ%Ci.Vx.G*g?5 f& 7˭ZćRlAY3X!$?Mҕ NVcyiN+80dza>E?3B2/cU}7)r 9I>2L}/|)|@cY>ԅ]^ %fֹPH!5-0(& jnBFATT3̈́ƿˈTQTvzkID^U>4K7 V.B&"C^Co/QgL; bf, 5e\kw9Е F(0/4+ SJʸF2<9?CB%Qׅ?ߠ*QW D e2"O1bӛN4?^;z!r=l6$ư*$=qr=$٢a}+GfI,7HDFޠRU:$@-C"ѭ֯+C?A,X*=z;+.xS,Ӝ֢w ;fp,Ͼ{7 ^T$6:QZѸrG;$:#@=9>*k שղZ] =PS[F5E/d}jh+E:5gù]zV/ZH=;)-dD}j/:Af+ U7 Dz R A*f<eyhI @٪\o6C*1 .<8N9tJ_8d -5;`ȱ"DK:R/^̐ *OI[`VdEt^s 5>#$ኾǏ m06 VNA-m7!1.2q0d #>!33 swn8N.z-YCgl}<&ˏΈr+C#' F 3j*'ޖ|޲2_A^]JJ)}:17  $ 5$n$k ھ2rdVIZ7Д|s y BE5-x N. KFa.ƶŮҊ 2FLL1'P)< 7"^1K8(#כ'zb /C*8*؁ <.P;)/l %֠ 4QT`px]OÎBP1j46JM}F ;^6߃t["} <)x,=2ؚ*1ڒm~P.NIe4 餵^L)Jfuem;u OUѪΐ"HМd 'Rxdw_-,jj&07VReF4H1F'4J9(<ۧ);[r..6K7-I<%[6+*aE6; < Xըҝ1ٕ1HT F@ k 1dEr(.*/C<>&^cG[D 'NwOjӚ#KkZiD{k֥É~3:dM@ ^o)!$n(&^EK2Fi{ e7*!=FP2 ~aŒF)vabn1Jt"`/1,m c7TH` G#rӥ , E|ܕV/`rGP'  N⯀ݵH!r9 KKQK?F~7 \Y\2VpVX2uj`vQO! 7>.Iٳz0*6Ddvyx!'L4F@M tyo̸7 "* %Ӑ}"hi6@d'  䎾ݹhKK <$%!B&I!R /)19)w52 к}њ1zE9 EC-*yd{ ;48 ȖIW/v$&طFD@laWºZ2ZhG7 ݾ͵l ~/H>,յ   [".3N   3A#3P-"˹ :i^F  T zn T.&-#=3\S#?- 3Q w ) Y g 7!JDɑ+ijR:eP W!7 F"*1 0e67 x 0am ';9EI"฼Kmt߀K1S13@ǥ\ΖQZ"U1·kA΄{Uc =G@33'+ҏ"^ - >"qcӚ% +*\;a$6&(38-F47ƨ´ D88;5;<'ƽ@y3OJ!|qsKI M|T 3X 8^o)|  ! /9eͼ5V0kg?N خ+;GaK[;IU68~F=5KzӽXOfswN$XD-9E)4%GwLvp-2[f?%e($r%g R~~TH۵&TV9+}!/!+IɽB _CVGVAV% ށ'Rl\[rkƿ*. I ^(-@cމ_ k4D&4UřtQT )*'eIѫx! 1( {t"qV? ?R %V[ۊ| r!0/)$# ߎ5 BDL#.V ; #K<5> 칡ʿ?jBb9yօ~ '-% "% <6u$Ǜ~e<; 'eCmْ` CM@n;$|L:-y zh,M9Og=mϷp.X9+5: bp#*R(&Ni6( 牾lW\a'  - XemN'hUʱ:IFfF4 1U1.,/00a# fk/9F;'5 㹗+ bb- GN2KַB 104.۠^hn#!53t næ)#} z(+ic7u*-?YJ;.5& ĕF  q)+rѢ^ι)m+b/" ;ݭ vT,%>&x ކF[~8mke?ЮH3ӝ-j2Vc^B"Q:˰ <c_@;"tALb:LHo'|IW6E/c }şjےH0G'|f}у8%*} AObBAQO5VҚUq:3XjJH*#UcT -[2J"Y,%h{I³[G^xjΦEb&3t;T\Y'mw}>ۈ a&RyiX!G2_Z=~fk 3>tf2YwנrR }?:mVG/kF:23d+\VQV4괞ӓ L}pJ^ -d-rHvL,=1&#D-˜GkIJIq;P(:&ȶ } 3vH+),./ >_;QUU #32 ғɼ8F&OBG4DJnD? !%?0 6/=6/ O-<2:&(]%8$[?pmT pa -3+!V #x0{܂%+rATJrS:2:IEbю98 E> { &@U-nG:hyΟ˺ &+>Kfa4b ٻ#.1{mP'45_}iq:JiJF"剿 v[ -;W =,HO3 K 9ǺX U&2)/ yg;Z_6-[+u)'%#!    ,:K^s؋֥+TˁɱW–پ kgŴ'j[ܩbUaݚsH$ޒՑҐ׏0YEEF違HǀgB&&BgǀHFE膓EY0׏ҐՑޒ$HsݚaUbܩ[j'Ŵgk پWűǁT+ҥԋs^K:,    !#%'u)[+>-/0246O8:;=j?'ABDEFGI;KLsNPQ ST$VWYZ[N]^`Tabc#e[fghijl"m+n.o)pq rrstxu@vwwmxyyVzzu{{u||V}}~k~~~9mm9~~k~~}V}|u|{u{zVzyymxww@vxutsr rq)p.o+n"mljihg[f#ecbTa`^N][ZYW$VT SQPsNL;KIGEFDB'Aj?=;:O86420/>-[+u)'%#!   ~JG*.F078f55E1,$k}ӢŨ1`B$d7~4 Ic +3ƍ$c2! + L(˿7m-x^c27j\<0CW{>W&b4.U]%&..qk=һ@Ȕ?Ip)SoxscH'f5Ɖfd") .=JgT[T_D`_}[UO?G>6-$=L FD#<9.kT?b{48 a!$')*L,.0723a57,99:;<<<! A>'/ 2&jDQ$pkY ޸D%!я΀BIŨݿýPBK%6*,] xC5N֪K֭i'^::Ihη7i|Կc?ȍzͳϕԪG=2]TE#C*S  $(+0') /"($!&(*+-/135[709;<>t@4BCEfGIJiL NO>QRTTU\WXRZ[&]^_=abceUfghikl0m;nAo`<:8715T3~1/-+)(*&E$c"j z 8He~۔٬?_Ύ%aŨ/sԼ#nɷ3mܯP׬Lr2Ϣ.垡Z(ɘr_=8>Ki}.b=T˅:EȃZ󂒂=ꁬ_"΀zr܀ 9nPtރXӄWᅃeʉL،ōwe\fiyȘWM?tܪWƭSаAٳ\~_h!|4̅`.ӭՀe7 u_7}gG*f ] E(uQ/ "z$[&*(*+-L/124@679V;<>9@AwCEFBHIOKLiNOTQR5TUWdXY[W\]^)`Tabcef,g[haitj~kolrm^nFo/pqqrsGtuuv:wwgxyy&zz>{{/|||L}}}.~o~~~.F^l>(~~~6~}}O}|y|#|{.{z3zy#yxw3wvu-udtsrr8qDpWoznmlkjihxgFf2e0dca`_F^][ZBYWV@US|RQO8NLJKILHFDECAB@?~=;5:86M531U0.,J+)'!&c$"!0i'e'a :%;-_B:FݬC؎Hӗ@ΨPɧ ƍ_ֿMJùDֶX᳈_Ou7i=/ ښ˖ǔ-2]ӍX؊dوC+#ałZT'΀Āyv̀'dہ^Zu0Ȉ4щ{ԋh&юd< ʔ•ƛڜӝ =oϤDuWBfȵ%U5+ę7ǻHa΀҄պ`و'|OS::Ud^b t PP+S!"/$%p'(*9,-T/0123G5689:<=?@WBCEkFG9IJK9MNO.QcRSTVTWcXYZ["]1^._d`iabcRdgeRfbgWhTi)jkklmpn_oppq5rrsDtuu=vvw2xxyyzz{h{{+||| }h}}}~O~~~~~+OWf[N@)~~~R~ ~~}}T}|}|T|{{{z]zyoyxrxwWwv#vutatsr@rOqpp;oznmlk kDjoihgff+e)d=c5b!a(`!_#^][ZYXoWLV)UTRQAP OMLzK.=;@: 9764j31V0.-,*A)'q&$g#!l ro wPb I O4szqWr-*/L޿9نײHiϫ;ͻVȪ5ħG,ۿqA|E⵰* 󮑭haE Τ٣Ƣ̞Ɲ>u˖/b^PP#.A30փp3OсE.!׀nni~р|?@AB"DBEgFoGpHIJKLMNOPQRSTUVsWgXYYiZT[5\!]]^_`>abccdoe&ffghSijjGkklomm}n6oop7qqNrrjsst$uuvyvw~ww\xxxbyyUz}zzx{{{[||| }X}}}}9~u~~~~ 60a{rM/~~~~q~0~~}}R}|}||N|{{`{zzIzyy2yxOxwwvzv'vuuttsrgrqqpYpoHon&nxml:lk!kjJihhhgfeegdcbPb~a`__$^f]O\[[@ZBYeXWVUT T"SmR]QuPzONMLKJIHGFEE D CBA@>><;:98^76m5u4E3#21A0I/.,+*)('8&M%$#" +51#n^De W J  U8Y\0D+~S"kn\܌ۏmI"1OCϽδ͟uȕƱΚH?j*NxlԮ֭$QSK٣ӠSžН11wߖ1BΓ6&5p ߋa슌g}RLJ{ цlɅ]-h=փ~ołZ%AC뀭wxogGf]G@   5Uq-.>ׁ ?G!Q+R˅Ԇ'lׇe=Պ c'8ɎhMZSˑ;ڒmߓbqFۙl:ŞE埝Bע'wPQ!g򪲫fۮk5˳qb:˶H{\."ID•m94ƤǹȊɑl̮`9 ӓu;4 ب٣ڣ`ߙskP_5 bH eP4 s}iD&vT | ufNjK&q! z n!n"T##$%&''(})*)+ ,,-.>/ 0012[3%445O667o8;99:;i<==>Y??<@&AABgCCDEaF$GG6HIIJKKgLLMNNO^PQQdRRVSITTqU"VVFW;XXUYYZZ['\\e]]^ __``Gaa.bbSccVdee~e.fffJgg[hh2iiGjjkkk`llXmm n_nnoop1ppp8qqq?rrrTss2tbtt u^uuu$vivv,wWwwwUxLxxxMyvyyyyzzz<{~{{{|8|e||||||>}e}}}}~P~~~~~~~~~:_I[im9~~~~~~~~~~~m~d~4~}}}}}}}R} }|||9|2||{{{i{C{ {{zz#zyyyyyxxwwwkwvw!wvvTvuhu-utttQtssjs,srMr rqq&qpppopo oionnmnmjmmlllk^kjzjiihXh+hg,gf`feeleddcic cbbaa,ao`__1_^p^ ^c])]\4\[[Z%ZY_YX0XWWVVUUTSSRZRQtQQPOwON0NMMLKzKJPJJ@IH8HGFFEzEDXDCCBBA@@?o? ?P>=-=< <962.j*%!Hw" *[lN{H[1vL9[WRPRM4/( ώȍŒhۆZ΅Jń82!(0AÅOӆ_uՏې";At1՝w6٢:ݧ?⬡x ̵_ FÚ/-Ԥ6H {!%$*T.26\9;>EACF*IKNPxRTUzWYZ\!^_a#cdUf\grhzijklmnopqrsu v!wwjxx{yyz{{||.}}5~~F"~~}}|{o{zfzyVyxGxwvutsrqp|otn]mYlQk:j3ihgfWdbaU_]\PZX WLUSQJPM K|HE(C@=?;85d14-($_ #K [Otp0h!_~QlJFENP=AFsr(/=DG )(C%A8R^MeQ޿ =轖gf E+co(`U;i3[/ݳf@ )#8oV/ $WŎX0D14cuK'! : 7˗ݶ%~H>.f:7k\j7==(̝&/; q0f] $K^. l s!&Ol C| &y w,')yL,_M)tIiS?\$X]O  {z;R "nZ  C+Y*\GCSpE ufz]SFLaY~LW&5s;H.$f0E7-,b2kSnLH J6F :^vHq\ >00smx$?9F!-owQ&P8x+|cG(3ilG_,H7x9| WrR/>HoM(1 2S20kȍAKAl Q԰RpT#2!^o ݴi 3UՆס*-N+_.Z~,,%j\ Y< IhJ(2FSfTbLDT,A97gIh=2]n9 il34#|6 U,?C>.4+H1,dGw1̯p  2)GQ.'jom W S)( sM2kFh ( ( ]J8w {s KU\2e < N7 N.: >V<}O h _j(m%TO.`MOM'(= FUv]TO0^;6U(O%65% xc%NI{gc^~w!m1N6^QW!M<+ _LÎ|.+:#<>(mpù"Roஂ/}/@ %_}bPH7yUW` SyIh!g{nmOS8l49CL('3^&Hyj G6rQ⢰,P>'bD$J`TFv#P8r;(?L p,3j*S/zºɀ )S CkU/9# {Vȃ-T_rN0R>7Ȑ@NBG޿qb1m `sEY- H]O 7'kp(ȣSJ0k*׎<]Sn#zp*=(U%xGa2%зb(z߰'ixo>7C~p˽P@ : #X̶(r"b2d*˿k7Q6PTƆ  $ߩ4:ILjo>qܵE`߄][1۽ R ?D*vĵ)Q)ۭwhL( U#5pe:5QhF**h)`6/n^ ̆Whз?؂zb˵WJC<7> תNQg.T~ۭ,$ :3ߛ1M/F'^#Gm :4; !ˢ c~ ((^Z|ֳ,H Ir9ڏKß"8ķ.kV s7:1 7E?>HY/7#-@ 6ӀM]. C͎)>uo:oVn &yU M[R>B kµKȬ6GkB:<$8 +θ24!]o(Mg% x 7'veE-K-,TGGLw7ְ_odi4,_E_#ml4훸Ƥ͋\ 5RU&#T5M\M 僜+$Ϙ+&.šiF, ^t#p ߻Nɇ-٤#6&Mn'\y%bЛֺg+6rn`c.3*v)8] ;ĻǴn驒 p8FE../-^A5`ӻTs4 SSY.օA?mS?mHu#Q?!Dg /D.H c=#;X 6ώUb|\@ *jzB Nkf^9ݞQEY 8 7N$~/dH@ 4_8K,{  ږQ1#\ \}yli#?z CX:q^P=O"IS'.`</ 4{܄S2f xǯ9kz 2=[f%$7@RK ?.d3 3."+iH M$Dz*9|" yօJ Rf%@*6,wbd0X9 Q\7޾b!!*!}ŤL׃s)LP/i)jٱdQL%Cl$M%! _/Bbɇ7Yy H E9"1l $1 ) %/PD!ڭ!޼矒6h*|$<?2<DO."{ j`"sBU^(͉ p )]7d`G8J3~/.p=0-2q, P5̒ 3~sJr )Lu<" . *K$a'q;? .':uM;º#$[e MQ$VN n\  P /½GSWF- D4-+(d8 *Aa  D ;1_.9T]:7PY%%6d * SLqu pEռ {J{9uf%@U0{ 4+~Q5:Z c3!;AZa gQ ]>DD&in Sދ8!c[> 2:3B  8X #Kd ޺jAU3P+;gވ#e+M(&6Ld6H[-.E^ 2ٗ # ЁQ"" [)9 nKɔ \ѤO3$Q:3'DΣ:$K B 0I3`oYZ H ܮ6 QpY6 0q-%& u@nL"=q/o%@.@/Bwq$ a-DO? H uc n= "!l"{` , & >L`%f'zL=۾h"*.ζ *UO$ rf  I<`H _?>5M%6+B%!4#Ѳ,Qޕ lM ew܁sxp k.JE (I";G$}1 iZ+^/7*$F,"Glܛ 4e b  lQFG \'&$WҚD~+-hD& )WQ,<׻כrG AQJDI1~'Q5 #f&XP"J5 2'>! i7E#X^'R6[Wluْ[Q{ER#h GM*r4 k\v3)q6 .*X "nV?kLC`DF v " ? y7:F]H26/d RtU3 # $ _^#sk g v(!qcޠ_pLl dN# D#e Q20ID7\5"%OzF &R1>a'ae #".-l: !#2dk"='zF)mRF?r W*mCYyp t!C+<}aݧB f"@yd\)"%8 RK! 5 * PX i FmI`{(e9N~ j:q0 [a^] 1ؐXzG2F#yLPO(a  g 03;3Z /(' p7 *vu=|`U ?H\ (4 hc B 3aWEo H zKCw 01Xs5S5lT&r O :U uf?#!B #z|l Uy3D3fC'7a1+ =&v.[KdP o Es}3/ Z m H + ~IKS :, ~g = !/ I`s :ln  B. w@^ 0 _ 77*&^RE` }Ao {ITxi#OZJ0p  /w@{i" ,u+md= a P IVv   hA X JuZ\&a@ ]-klFP 2C :)MkZ&=i4m'[h  {}Y{U>A? D g" }- "sqE ]RFb MRs u& < ECp t JA\PvYcY[= Dqy gd6  \q'"M ['>7N]@b%*)kM=` L ?s^h Pw^ZL}iq_/_n uv p@f=x&3S?\ sSzFSCvVs&qfkK,lKRT[TG5Q<5\YXrD+m V^3r1SBw o$zB{aO?0u=2xsXV\,KzwX9C=4|,Vl2v17EQbGebPVi|WG&#'(0k S$*"r{V 0#z >a|*wB{p)  HoBS]WOd-b'|gr nfbP~-$S .w*)q;)@1(nJ6:0# (_ #߹,6.KGA#Yw Ew-&7) W1jA-ն9\N(ݝ dldؑW:jjdRr-FX7a (+/#ޢE]M5$ ޫ }HBC7^ Dр [o"9t\33+(8/sC;rӿ3OYP] <3F B9,!!LrCR(z '#v , ( +VLt%X } Xa l2.0Gʾ%ܓ0 ˇ s 0@ ba&36 ~/ Fy:X9B.!&\"x;&t y& ,.'@x'Qe*49f #/s |_%^%. '=TR+ mh ޻)'%k֥:51t*w}O{ _RZ6-N̮߬A=v/+)~JyA8F 7,|&q 'NbW!,qM$% )Vs\+>qp5ZrW.0۫ P.HMhB'զA.'%5Q0&)>M)΂<;siz; Ao !q$fqDl[Jܓn/Yv xU+5!9? w1hlx% Gg#=a7`!7($[F>G B 1\;υ> q%`< $2% 9ۍ އ;!ĸ'+&O^mY]YGaZ\ w0z}%<;q M (%%'!,X,t&N*!%` Q6,u"66{{=!(yhW&k2{%ׯ'I:Z٢)F"h nѬOx%_-[h *ц*3?  8 gu8(T4s+d|è* (~8m3IPH&K6O. [$^E/6:-Z2>n=]!W%{E:.Nv"v7(%z|OJ)jΗCg30-46 )(Ca  CL?=74 ^ h w32PcM {dW4)1)ϗ%fpUHU} t "p-7wAiƝ9w^,m,J)EoVr% v>07A$~F im!ՄKTHx|,-loJE* #;ޒ7 ְ2^7ߋ ').e ;V@!ڽ( a h+B7I!%c0 &~*0ͤ. [2QH)EAЭx- ]؅9GUCCۗkuw6@ ,xBV_:2$ [1<)JY1P B͜ '{ C7zͶ]qx Sq%V}e'8,::~GYq::L@JRcx<>Aڄ.֯kZy "o'n5` Aߞ:2r;M$$ ѼPD%j= R0 +D2x Cص"g 6/г >(&Ž F@%iO!߭4hz)9du3v [&&0 $NZ7# X 8ʊG;19om&޴>] S~- se  \94D579k  * /,95-|*-Y/w`;`ss1>!Zz/G&=ip=h2K*&+X'p"U ]Cľ(~ aڊ.>|r!! J ?p0 x: 5ܾ05%H*S0_%*2$"EY~CWY 2=.(,)8.(}pV3j8|F  +S21y <".T '${ ݣOk 9 X V9P܏<˒K# C"QU0a?72s)s+ 9-)#J-7 Kb\8Bg  Y 7:,! )5zH϶ 8؃ adOXHA ,p%E"(-纐.%^kܔgKN,|KB]-3;|AI<,  D_!!604CHmj,nz՗^Fh|6ic;X$>H:J ZvV(:+) 8%$+ݔ7'd,1}%YF~i7oN" 3Jo)ZXT n$y5m6I[ӽzq ;9 /,qLBl<ۃ^Fh|8Z;bW$ݦ" xw '/''*r qiCN!aA   ' 14 cIUퟯ K]r zi GtAj$3պ4=3۬+s> 1/!HH,E =7\ 6Y2Ll N5N!9/(LR l YC&Ѿ{&% ֪ES*#ݖ#? "/eB$  Z+=Ʒ d!3 /I,} 3$cG* 6Y ^yG C$ R |,r("5@1;4`!s+s"2Oo=M0o97 I%N=܎ #.(^7.k + +'/` SYvC":M-Q&jtGs0 j$"K  0 3p#U A/:M#(ִ;  $0P5u͜L\ٌ 3] !2, "hhL3N \kU84TrĚ (N-C #H NK /{%>&"]Cc:ą nY9 2o#ZόH65#8K17- `c%*(Jķ/'lU1t3єEA!%fO$Y\'P8`~,N ^dѺ|7#o I }L]($ְ< Aٳv@7 u" GՌ9 c)CGŢ 0,9 3wZݾ&L' -.ݱ} ]xTGQv+yon +xYDw`6UuH\š*l7&k#Z2T`84_E~-8"HZ(_/I&y[>r`e' * f΋ V&@ݗSrޕ)r52 3B  0X #ޮM.~w o C56iW'U*9#ߪ)~ 8 ?&'tBu2d+uD_M09R> /,+94  ^@ cf yThP #e&ف:c#15 4& ݗ -!zav'V AɪHZ*;rkCz#l8)!%$ p J$P c>Q+( RNBq>L7> ! UH7d=ݜ/ 1 ߿qBkN:$8*<cZ P%'i' eܢ%-sp2"6  0. j0*e+L "oh 0M ' 9$ !TJ_oޝ~,8N>fhL-D< jFf=:W%-7/~x= ƾ 7*? "Hhё243q .Q" |Ԡ>& '>,R.*,r-!-!!FyՖ_8{Xnm *א)bU)N$ ./;kE3 Z45Y:݃ ;9T0,s<= <@8^ " {'8 El'w S2KAڑYm#ʀ" "b/g.1g <90A,?B*衫&LJ 3[+gY0}{TH^؀  j0RFN wӸ4VoO&-/‘N~.>u)1\ !iT#.+*\ U "[ʫ2*r 1M "37L$ " Y%i߹Ij̮1\"}&!)/ Z5 [ELv.)[oYI' ׾Ng g1n8'oePʽ.!U.!٧'E2{FO&'4/͒S+'p?"НI9կ }PeЧMxs :U-{* ӄ7pN]2(t+$6تfr  G!# /@jv CHU Gߍ+K "}8ۇOuRк1b\04`aۻ6&fbR | # # &7c7 > %^n>cw;%Ӱ"<b| R k $d87K  H#*{7* 0O;H߲ "+nUJk?ܾU1?TV$io4g- yNMU;*ʰ4"_Bh F  '?[ռ.''mYMP{]Q۔CK >cE09@U% > ol4z++ߝ .!j(j t x'{Ml0 )U u JW]{12 hNٍ {WQ0`? ]f,؆߆ 2QQ aз+g # @]N)~ÆߐK*l \P5z%zl) !{ i$W2gғN9 \J 2b,pAJ2H"= qINE00I&ݵ&Cn֠=bAM'3YCɒ4[/&95x9 >, '5S )vta& z6otv0@ν2eIL" 83,˿,| K[%IXpTWUlXTڛ#pB#C7&ĪAߌ *%EUq8/t $Z;3"~q(߁; O!~_ ?V,u/ 9^Õ%p$ ty $ J@iI5[ C sUS7%81HCD0c [H0"%RO0K T"J 2gۮ"a͆!00b$[%$iS,o'%.*u5 ;N ]"+?&<)% =@56+u.q(ZN^j ; %^šKƩhHݽ'1j 1Bt0oqڡ#I rJs E F:8VGCDLVݏI:L!E f z1<68 يB" k a *сY*GN(zav  6Tb@-ޭ h^İUK;T 8ӉM)V"R'F8t 7>݂֟װ="C-\ OLʀ X#C[Ǻ1#L$Vt4[P-#}.I"@(߽N(f#s  -Du' {(EGZݵB)"XRkӳ`! o=Ӷ2*w%4N%->*|ý U3?qeDP*0"+3U)P4.݆#P #b2!+../< 9/ʝlSb %i̡ @ ''((l) :²<~a?CZݚ Pn+H 2g+F Ȳ Z)>U. .l5)"ydlm/[>[ڙO )!І#-:EsEJ(2ul-(=OO!-O2 U*ޣskЩ(hI g<zɱRaۘ |>8'#(+]Q2e>ы1P (o0#uJnsԎv2DЁcLF](Wc-'=յ*(F D )-* 'I,xH=9QYrpH 1@ߧ *I[ˀ$m x|X)L@+ * 3r8 "2TT;0F Y%8d4dՊ: 15%l3 1x ٟ,qzf pKAezPJvDE^ /0+}#%-?h@9<&p>3|2E(Ձ6բ<<ϥ1y㘶@8nWDB,}bE O(4΄ "BU͆˾*t>ɿtzMO6cgP]n1G0k#>=D 0}\p|:Ǫ6H\KGE>1Qšز}b%6,G4rзB,h5aVNnWVy >i6 +t5'Z^{ !)r#;.x8B;Tq} ${Wqg0KStW&o4HzC"6ZL6yxqE0WP JGGp;q;gV \j7=6V9{\MH!&sMGmR[TTxE0E5;#,;';԰@0DۋTh97Qh.Aܮ+y.56f {]E@f[dۺ?vpz U3L+BIL16`@>NaC7@M h6a($ 2z"64+ZDe+lS,`h 3 5F|J? P#G . p 9kV9,W us[[f S9S Csv5v%Xt>Bi_ HxC"a$Ueب7Ga,5.B` RLT~ E ަV;k>G x؜P )OEBm=cy`Fq<V֯[ &2"r[Tams6+F ?"[q*wh) ?c2}XD,%NW =cJ2f/:cեYj'&+Yd=];(w?j0aɡDX }u a?@ afRKl$\ F6Yٹw$O8/YwgER0[,R !fE${u!Y֎WF14gB ¾PE'wTN4̩Ռ"LH^U l2'*?^.l|C)G#Q^/SSClm: .!i;Gk$6U/e-guAXґ%b"8JHL :< km)RLO^ʧYj^AJb&7)LBED|5%d)k ˘ BTN޵IV 9/ f^Lc!<9иiol6$C[l5!!jE73ƶ!<&9!b=Ӄ¼ŘAN.S%IgB#h)+ՃC+F Aĥ|D1-#)'I#ɻՈ`/i$ :\G#Q< Kc.%.&0^CG/䬸\u.RG\2 "_ 0d Us٤4 V@LAs-":p# |^¶'<"aBɽ-o }g񻒏Nݖ,Y*Nť*R@a^: P0jXN :4: lԠ8#DTM8T5 ϛ x=<ߵ _}[^+ >^]w@ Z,0@k f8kiY?'6]dT7 `^"IǾe\޶ ]BsIEMEz4!pM5 : <®TjE )ERqx< ZN9ۣ(;maÂJ٫*rˋ5l/|Q w: CP`!41p f9k8Vƫ#B3<_K $~X!Vj1U{O,St@&چ^ R3>c:BuU$%nS᫏#v~.S5>Y9pܰ[8+ .joP H']DWCR <4E0TѦ|*OЛ4aE[,VrQ-JOQQ Q,:g}\k*Gբi+CM Ba['vi*/E8-.j%W1Z!:EA0 u@E)ko Zz{cXW 7V+3RA &K)}50G 0= f%P@eD'O(53)o%4"{=*H$. G#_JC(Uj58ԉDI"%x;ĖPӳ: $.$4i=D|핥*,5 f^(i~>4)&^Z54'{^ ) 9r!G(W p<+PZ#hdndž51Ϊ> _k !9Z=u9'* iGȂ͔Sm2',=*, N O" 0S]%4( +d0 31"ܙL1΋7I&(63Ј652 SqX*/APv('!N-BYW{W`?9U5*7 - $[, . 2b+M u =̇nـE0Vl[- >)bT&1`s HqUҖ 3 !Ԕ O ڴ$n\\#"&X z!*L &!wqp3_?'{$@XjP4> EB _ Aj M Q܁4#M "W U &!\=0ge9V&3 ++:F lKF&#qO+~BPc-  jGϋ,_" w (ش(]' k:Y!4TG+js b3I / ~]GP+" JSR3'.j 1 u\:s>%!{$b>}?TAQ&Q",%p9y~* &ci5 i^~CLd NeP nL B e  3Q h 3D8*yL- E I i55BFPdR Zc3 mL! fc^Crm s[ Q -G4MX#AAg >a ] i._~bTLG oy5 y? (0P },> DAne7I?*>- 5 %+ L c "(m +=-t? +G l9qdzz[}E*i Zk : p*zu)# D q;-Gm:z wMC7 Aw 5d k<'  DDJxI`! <[3 MIp(J*t`RZmnfcN KF" $T |W{0="1E MY_?*c U  4b B rK%"N 8/V^Y=) w  ^u N,>ykvS{ 2pW #HF HQ bi~w+ p h W5 Oq,E \w zcSm5=#t/ l9vRn (r k( . e49 . 6O zYu%% Q  V  ?Q {UD-e+  <&Xn(o(E 7z ZJ  /#J, \p!a" !gud&5 Ob??\;0i )w{C]8 " m#r+!k 8f Lx*cG|j{ +)! x`_UlC bN E[ >=@ |f0V kdWf IP6 8WO 5#}e6a[W^:b Y >"[$}I ;3e)Fj; XOI\!#.pF$]Ln3M!Wq2Y9:Jn\{XG]%ZYBGlM/dO{=GO Dx\7 >cDtGXk hz`qHg--:2 W|NQT]&%J6n#=DL$-dWK>GF:%C  9|:LBv259xT6Y SHx9@%~ KsJq GAG?f\7Or3 2B^w7,,-d@W}b>KJ<#+h;.ᑾAF5E߹j3ںǸ~.0C\حI]$6j~ & .-΋y~2a7h<_1US*?_ _#;G$BtѽQ 5PͮJݶ{9?tYa=&Cb_;jg fbN oG;vƇV g㖿=!p_W"jE-+H^l^DҭK$9G<04â n8+Ec] 19"<1] /2eԶ 澢y!C6) ɾ ` #e/Q k!ܲD,oŃV +6=[4 M~5u'̽^)uNB6|a .y /o 6Z p ?ܯ܇ d!^0<3``O D)g ~cj _DlSɣ)~#k.>N9 "B?V/so;3{/8,v@D\TWwMߣ]D6LVzP,=ύɶS} \bM/'e͂A|:*%큿?s$#4UE9x8E$94'<4gܜvxZ I>[5ZaJ$@LRKզ-a d%)%ƺ q 'vФºϳI eۖγ~&UKl<Z&|OE`abdx4.@ L!]MRߣ'b4=!(?s0!"Mk,Z2ڭ(SDD+,sn =f.$E(fAT-EHR_>2/+hPX:*I0 %E Fa oASB6O0& _F\U'թ)0;VL3D[[T͛ 8 ' &gO˱L*7N.^9Ϯ)o]03iJЉ"?OfNgS_5Mr[-F?%t8p c67a%y.EQ)l؜+U`M.ִm^ht%Kfadi]H&9/k  y R仹a뻶d5ݗ#-' kq T`(܈ ћ#, \(/٘P ]26nR@(E l+c̽ԉ9k^. F6OEz|-&P)ـ:o8(F(풻q;%E>a&: =);]ߎ̦0Ec7VL g-?zHR(!*]Hv<%9j.)ɟ ڟ,k+2 Y޶Xzڏ+n"Q1uם5-!O:VGZ1 >J&H} dEY$Gr{GA7Z;>MV(m[z,f2 X,=}P&`ms7lx 5BSOa\H]ɚ&/17BV2)[֘ #v/Vp!LGH2[(W g% 7XN0/RЧHڷ̍Q2;ӧIŏ| *9"uu0QExD3FT[8=59`6@NA'f*5;$(E,O*.x'&6J' ᤎL-c ԛD.dZfC\K\3 } 'BA\\Lr%TW42 ocЬM/T`g^A7!u`n8qX%WO-TI%#Q8)8 %sˇ.*k1)5!_;R*/+w$cřa*[Q -޹Є޽ZʘÓ++VE(S0-b׾̬¾6!zA=?b1*ıB[+= f/!{3!lI! & *[C.., ! аЇ'(#`p# Pw5^+5]ռԳ"Ӛb|(+`""~q>qPOCj) ߳Һ$ԄBJ[rnTxqp )5t/ >;4I<j񮺽o%U .T-7˶ͥkΒ*"ߑ%-6@Waq !q} Q7>Pûнzʹ(-,L  彼8jރ(vʢ񤶖˜VȺƹ՛d2h(-  li޸*"i69@Mқo I9I׋ t*ݯO&O%&0/'o ?xS;ŶZ 2?PH !B+<5@]a}#hM)mֲ˼#3\ (o%=Q2|#'#.w&8֥e F&D7%zwreB:7)1)x8WJQaadRT!d mVU +:SJ?'Ubǝ>(ɹ٥ 8(Ji0!0E% eB;\ P?zѬ!P෿lˌ&X! X}u08'8ѵJ2n253a,VbJ30:B3E:67E_tOy'0fZ.a:59'3:j7#^F6Y2+/@ %i'-x /'0Ye0<ݢk\^ a5VJO!Pa!|> 6j :<ԉv붑(HS]^2SDs)ň9w3c[F=   ςj2)[ (0w@ܪ|eީʐHk4-鬞 (sneԲ ; ֱ̡ӑZ˴'UƺI|o/*񗵱ӓjbTRB}|;]L 2uY,762C7; W Kջz"|C #@]G=DJ&v48XiavovvaA`p+ $6JY ~1#k 2bW9]LR&.) 4;.r܃4E*Z r?UKߨP^k9Fyn$6;+oD L<'5@H#in)9 pm+V;YG`6=MDR0c.6AWF`kp:G .֎MĔ^1Pd8aߒͪH,: aD؄޷#\X#.)$\$.:§ҧ'{ࢻ*CR`J". @ῙaѽOܭF Dƛ'jОG-z-T`Gc%1dm017 QZ"*7fK],"ۭedM%m<ӣ+ȪؔD. J"L;G1}!q!J% -6!ܨ ޹9 Q 0 .Y/XPZ*rԕ`\7ʛ/0d?b3f~5*.%" 4G Y 5#~(+P1*A ť+YY73ow *r{%ۼQ$|($,+y3,)>QmdgdeH2%v=s"DCi+* pQAV оjW ů H*# 9BP&f8f4Y x;)1-791E'g%Qý]L:! W l/ |]6 tWVͅ10$))OSiL*ZӚt0ݾY?٥9T>:`E|( "rچǃbA{KɁ \##-( 3Py2-JG,V]-" l.&)C'ٷ5$d=6$/|JHF"v ْ) b j֓𜓗GXjmɔT*&]Ӓϓ by;SD3m 8pmѸDL"60~O4n܃6Iu=' '>RN6HpP  ?%   %O^р lsI=5R@7Q*yy#B:gPN>1VPϋnÔ9-.?9;) 98HHQ2n'mt+yJCHI=K#2u@㿤2(RY#%Ԩ)D]Z>lE32z+ q̧HրTըo-(T?' "ZDACoԌp` ʭ/c TAŷ;=q\g]N$!G4" 6JJ:7WOWdZ d /X4"EDNo =nJ$C 6u(zR O+q2r8 *i9hˎ̴҇OC>vL3 850@EΓf/н\ZY  46TbX WP8zjhߵUڤ,Zī9:7ZNaI%`@\&4ZծC# k5ϋ3DK?Dꆺ]#W)9s-Sq^SCNPS&'k;دҺF- 0"c#Gq~/Ԟ?eR Zx$8VoѰHi,(ٹ(,҅f.dM/9|}"G}x 1G9V%/mqq.;J0gd$GA c3@q@!k% /a )0֬Ǜւ|:\9J&f#^Š8K:{,y]!ZѡA)+OZ/!錄4590+j{-Qӽ# 3"%ʊ|bL* uB6$O+IJVq̫˙=uAƭ?$K@6I閶DO#{Ni1r_TG,SN+%# _)UT\F.~UL:+hGN28 !HnpU Y9@E*J%e19&"H(7 i;1($ռʳ]jнpٵ 1-Zim`:1QkOB?Ç!/ú3:r/!Ko-oTxJT(=<(4Do3u[6HFT_.ݐQ̵!s l:SL8p&{e'/i4 E$ɸqqɑ̞dv?[GQ+ܐ' 뾗kk EԋS!z G1 -˥rL"MCeo.z7="a !+30O@xd^3@4G!$0Wr@()B,;޹a.5;h 14=ֿ'[ 8ЄٌOi>FMUCMW"9AH̱X5Iw ++XO.%&=OGلr/^\9 ,3dN!9J @ ޛ= ,F0k;I?I{' i-#+4UOۜ-t!4 0#ڎ@֥cОlֳ֑=ݔ| ]2u6U\mQG&&E6U:j0.u'U-D.9ZouQ 4p8O_hD|r9_ H-<(xѻF ܍E}@ a @GY8A.4-V2Jx ̻! ,$N@0FA0ZO#h}tF E1d,(?[u_N9$!Dg&7A2K G&5[܂ZG6x:>9>C=s13eո۹]6s"ܵo\ f`࿶ޠN"[73X2?F.)r(- !>P(=e-K = (s([d`#%;3Ҝ 0<;VT ,ӹXFڽ@ͺ~,[5bޟvzq02yBE;(#'<&܊m/<'12)P.T7ʓp1 Cʩ6X̿ #eKDz` 25g&jJzW|W5ɿѨ^ݼf[9 !-+%1)pܬ*C*Cp0, k%*!`WC={ 0GB3a .mXӧCR U2ܹ`wiГ˔љ.?H5.Aц[ 2 RӄR :GF0W+9*h4*K`V)&9X1oPtV%.96&<&8/5@bN]9WOP>NVG%#;>0*o0#*26y| ɂQ"!+{ya,MH%X'H(51 B !gьo4z?5Y puؔ8r4TӾ4'ݴ*5%4Be<7˾ԯRL:&:HK 607|ǰ#0 ~S*ͭ^A&m<)ڇuiSRXٵp<(z+$*rh!F . 3݋--1`[6"i*9g:Zp+d =)_%;-pI4(0U( dB{_ l,>E*2gIr/D%Y dEv=!Wլ e87;TYjz78 Q R4d*+05Uϯ3!ϼ9Y_i 㩼$^ $3]ܚ(9DT*f]r%`jŅ -3}Ծ ğYөzȃ`'eS^)@Hּaȝ& (_S%-PΑIo;7<6U"#{A) #냸 išq^} Q 11hT~|T]ӥp{ > ]&|"_{yPۧˉ Gpm8sMLG&eЧp|$y,7]˾G#/#0-r0lw}mH M M!G( %g9ߨFAQ<'%TI;NQU*kDu{tc%JE%HG=uhE$ RDZ!Wr%Jɿܺ2ٺ,r mO^:b-'6%hG<> Ǻ -N'7 ^ ?)W!$ qa}.Bhq2"Y:HP)uD>`RL %o ou##*5fB;a.+9Y)L&&mz`شլ(Ƚ=0;9䬛7JVJ 5J7}EŘJc6"h7ze>ZhQ^C::Blm @/1Pa :ұFwʛ بs"+"Է̻ 7/S97 =_"G8[;+/~ MEh7_b?~FO]L@LU `q6OHؑAXd\32 ƚ<~$ İӍ఼8 &H X!-Oa0A.87.5 PET0#*.5eC]7Ѝ ]8v";OXM`q.q}Ov D0oz25NV*U<9r-xДV7 cՆ;{[;3)8Oyte1-> Ш=G< (We"&x$/:-mI{g h &?)՝QjkǼ!2%2($QL. EMK#CiL:Z5?/[uC 0QAeŧ!Gwů@!B586G@r%AD=WQ/e = nL#7 s0U96.0; uC% D'W2+UE" V-saYy)R5q !;[DDWҗ˧f–HA;D^ol=39ؼ"ӻ.SB:; 'LEY>_2\%An/IRJg4;M1}VA~0~-kϵH![=ADR,[4(OZ{ʥ7PިZ"I S0om3$'i ")?A>9A+u_ Y܎6%+*AYIjf]I 'JcVkM J&?J,GRL+@}x/kRb@dt_N5D "ի ӽ:>ҭ*۳?rL 1"EARέ- _y ع9 %\K2(4O;Ew/L.<3kM9T?<]d~+çr *oP%U+4{> PkTO7>+%dz%([-,uNGJ''P,ts<.PB# "R.2CNG0_$?RS9[׮LlÅyƺ!cɇ˽/Ly=!J6(+< PQ+29<=M/B'|8ɷǫ, ӫr<$b; s!63v PoO×)2+0e:F}Ɗ°%(7 Bo94)-S\ :sA3$G&"{DR]A\:?;X -CRX]Zb4U @w'[,8F!Œ\jˍWY c;K&OFeBnp%P;14LtHU<819'7#$%( >+Qڠ6T Ãz).ǯR,3s: "UQy%.*籵ȜS=|]/=`;~Mļ% ;=&5p%1:>@ N}UA#V/ٞ -?C/(8u  νtI L-#[Ofy8vGd";qΐj@U;ٞv4*6ݫڎצ) T:5.wB$b)j2z6?^&%B30?^#8X>*HY$.RAN50װիI <מ[]'!S*W]Te\R7NZH* !!dUU| @ZUQ07*3Bh";۲ܵa6%DRIJ>u`{PQˌIG{ק^2ì΄ 01Ґۋh/*7:0 W ! `efUh#4gL3ot쪾?DZu;! $'^I`׺ d$Ԡ*9ږձˡB/+3N JpT)b#*1B@( 0aqG݂($fKDiD' -AjMK=2D$~ ' i: bͰ C&8PTA2 SI .'믷C]{!DiH R>]f$E(tZ62Q & g}l s#@,JxJ`ՕG%4+]+ X]*,U~Y3/4,l ̽ t%H76ɤ2ɼ!פ ߫ . +g~ h\UEAG Hn 8%lKU S+K61Z@=݌BNEH4!8:$d@< ؘ<-G6GBR8r%'sys[ɂ[:Z鶷9ޖ<5ԯ#ܥUȕw *|٤؃8GHH4+lB>;#>!Y )^AE C_FMM&x-(?LUA<8+dȤ5*~!r/K0*#C/[L58!LLbR[5qկ0[ r)Dz8q 9 D>+0= JzDs噱_*ҹw3pC7,lȝt/_n+9.H\_6.)+N]?gg?VEZDY5 GB9$(2Ooбģܭ8 ]:R;331WmfHn"aDD:)<1.יe^"Z !!-0/H4_Q{FίRp; ĉigȳ63̥F`A37U&*H:ؼ&H46k*z;֎ӳO{KKD%Қ l1:'D1 SP3kn I?<xљ`j֕D( 0&Tt9/Kg|xFdzƬ#jA2& P0c#TZSS7je@1 R4-NMBG;r1.ie ݀K|IFX!K;0 z @97 (Z2U٬;X޴75o,l,"+,>?SQ_QO0-c!`+덹aԧ/V%{,@)_emA676ZQ8X;ERHISV 7_ 94/U 73`L06\wG/&6̿Ơ:;'8|ϽĬF; Q9 )I ePB"B( 8?E3U,ۘIP?~ƲFs/557"z9!WӪ޸ nj(5 &V%Y50=6Fi ̪B,)%&3($:8'6N<< 2~B897Gϡd #H]b_\$ԉo >OӐ+v 9EITP;$o#j6x x*FLڪ;.w8nİ3  ?N_9@0'zO/YP MD02FBlz&צʸ&py$&2W u29HWR1N,=8ʢи= 1bB-t &7j ?IUpSyTUa9 5<,6ONZ%z!5)]C; '# &%8!9B%L/Eupү+4# 7d`qqvB38L"/&bj0FOGZB>H^'d۔h.7QL<ϕD,C%K42 Ȁ40&3Y0.2s/ABG9BG%8j0&&>Eg1c+4Ȫ Qȏ8 " "h⇼z"k.gll/*N 2y93Do4GJU! γS KNW*!BVG Q j4a c =$-9O]DIW: ^%3-%,24nϮ v/D 7C"$@> {Hx]9LG5 "TQ-^1 &=3- ,%J~?B5n!>H_?8$]#<+69Gji}-7_8 :MJEOE=S 9?=`C 99dÏĺK7xQ/A>!C'&$F<8>ݭ :;_`ZS>3<ՆNph+JRCfD]& !db:8Y-Q8M*]ȧl -֌hpWO@ԭYBuy  'ծ .62x80da fx!\34E0 ?0\< %,ݦA I#>5FJ[&i1~'2`;h$"d/%EYm]DQ0+Ra< E~բ*?ؑKH\;%GO-|u7Bz-!*T{L{PSl([0C99+25 U R>[XpR-(m<|PpMu\ 6b90j (WPaE,X2ʕש*0sx:I[Vj=3jGjV??2<f ٣ ԕznEH^.q;,@E $J`}K$տ*o?jEX= 3GM= FŠ׮˦a$A4x'9W$Ym}I?wP.gֶd ' 0+(f6UeUťPs(X׳ɵ{K7^! qں!JCiQ՝' <7@B`A¼ѭΊ]"f2T #5$LAP338lh0 ,5@2h{ͤ;0u  iȪ} 0@TÍ ̑0n(0)?2N]4g)GE!i/?5z--[970 +ѾOq #Mf4j0-[΀16 ;NVЬh1$/B(} S0g19rC{/&Q#|2G|V +:>&-z -4$"ȉ2p0`b1V=֯1+BI= +2~H(wV.94PQ4"NA5=7ۭ <~ "0gAK úv#C{E5+ʔ:F2_d.d٬~Vw1ۺߖY ZD(!Y}S4P8Y er3.TK#B'* L7Q4MTp7~yДV n:2,nU'{D aj-1v*`T2XQ&KI*_;GhQ:C6D+/!('L$ x}2X6N?7!)/ͮ lȠ-( 6w@AUA>I?(FU+!ZP%ShΧ&=P2,;^Sla&=2ڡ 42*wԞ+ywxʷ? 7/)9V չې):-܄!C%0A^=Fq5 .B..ډ;黶[t%B۱ʮ;6: () @- LǺ=ƌj^3.7&K6J>NoB41AhNTH,I"~ ɘ7$.ɂF >W?4k,hÇ 쐺pwڷ gi #ΏvqS%:, - P n _/7.'/]<ڦИ4!EEմ`!}&p)h ws%ѿ|-6j%4Xe2~?IW[ $rqI0L$-M L vA tb>v!zҧ ($RW 6@lj1i5f ȴ')@(;;I;(;c){ HGpcj] xA tD -UWbƎѦ$A=(R9/5KF9 '%ӛ@ϡkB)`/_[V[p)R 3\. AxYio?4Wŧ,X-^"'6ґ b&4HDy@'){q>E9, VL;G)t$:m&5dz0_PEo9Y5(&W2U= KY@L4rػ u笳r|t @I9Abl#cMUaQ&U,T6pH%)Ђ(3#xvb?4`*_~/51z[sCS_7|&a13:;[C0' $U/N͒4' w /_vϖ]b׼)C&o.WH)M2N&]V#Rٟ2Y)/4$Fn 4oRl ߻׫al-%%3 ^lV*&!E p ۶a#aE9/"D"|`!&X2^*" 0PaU/S >" {?UCc 9d - V2re ´VJ%2gK (#xٕ܎o`$w =~r9x_DI׭ӆ % &˘لo'C.Z>$&53&{j&%}%ôAB_U`:V.0<ӜYɋȷA&"Uui0Yld#+^s{廲2Ϙ F p7Z/IcX@4LUݳ2>^0\!Is[Jq$v Mexޭ+$VsD> !QJfHBL@5f`.AB@B2{06V+; R:Lz4۴KOl^H7A<+0-U\ UWF2@D. K7O7J JCn v+9itGe`"# \GOm- ޚ!] }*"#2]\A sň鱱kۺ+C4-v 7P>R8ڲ( ݟ 03 G ͟rw\Nݜ-`.6@W8['Q/aM#eXS¾iV߰.QG '+L,5B& A?&.bƜ/H/ޒ^ѫ+%pL//h ܆C 9;sd[Dh ؃Sؖ(HPd9\ RhH 4X9R2 n 6 c8 g'Ɯ$H ʟ#/P.*N Z)5;sOY=EZ#'6 -Je3+.'I>ХZs.#F?z 3QZfOG39ȈIH->#=.D}3/NIM-.Zëc $t0 rur>lINbPv1tYH <@FJѓ]f !')/'OgC'4U-ǧ-T3DE8nE(M 2n,} "14uk̀s6ֻ|-ݚڴ^ T<WMX96)!Ge  /924gc!' &,*ȱFX;2K)ф\֛&;}K@ RU5ʷ1.6́;96ni)x3ഷmʚȾ2:~Z#ӑ ǎɸ&.LUG/)n-Ap[KSb+E9?Oy?[XZDw&zxӭ@~OGpČ >;c d~0!0QX4 e¼y*˩%JU\AXĬunp9?)/\HC37PTHCɋ`F" I ]|))Pw7%٤// e޿?1_ P L:UcFv&?'F_5Y*Qa9ڃ++-NI1N ~-&t),i(5!u9A5:UUdmLi2W,*՞^* c0Wd a(Vߓ&#;y4$7P] }5~NZ2џ 7:Vt|L arm{mGK&]'$JaVE+ 6\#ol_*mӮ Wf:\ m1J"*,+n-S+57gީrLQRH Hݴ(&uT9 !)h4(7҄n!a.d0 $+`&9O*8_@v9 \1G 252ә'i4N[ t)yj;Q8L8Ɏ6OM z} Sݡ1%2P5*9II 'iN<H]cߪH1G sxfM** xW,' 4?&[#%<2<&J&+7SS~4JU 0rDX+:z1$^ &Mj !7ݔ֑7ݷ#\2#OA(bH^.OG'zΌmIH!wˇ1h05 C$Ż2gS]~=)ʬ2@4, qO<H @!w:d;/k'Hޭ*iC,!7F'Ζ]5.ʲ3SU=.{zB0ž5L!!D*+˨MÏ&6 3N:LϘ9"Z88 2 Z݆&~dP h˜+ i8t}D\Y=,W+/2!/U& 9HBр-+B(ojM,; =3ϖ\4"4\'ru׬dH(d! |QS5|$*L@9#H3)ilﵼn˯GM:0mCEݡ;3c'CjC6޵*$X-я+o}=2Ц5Կ;HټDS՛C+b;# C~,u, +Xh&8Z L(A+#}*f'ÇZmI>KrA@(6 {LL2b&"G~,5,#! oibP5GB1$vC}C?rŁV?oA7lU-1)Wĥ/w._%;H v/k3NثFtHbӄ8we$H\;iD25$C<)tOݼz"1ڥJQC-L̪"<%Ce _Aq-g(=eAGO6 e2ѡ@1օT>E̡FP8jۖ5O[erJGĚX?zνM M ?=Sl%_N -//Auƈ,JP PoC…cz 7^$9~u'( 43i\})̷ 2 D7RMbS 1dHmj(7 &e/R{PktzcmsA:x],! %0#l]wPI53^Rh k(hH#& x{ڎ N@X|[U oRe|+};R SE4 [^vq!M pBmzFK3ZQ~9tH{ӯE'5Eϑ'YQG՝M÷'N. &-"3 ˴"$)%Ǩ,x>^ * ez~" zVRE1׾%B(c9S"K< fjB0Ě#'ȉ!:6z Ѣ5E5I! 9aqhvE1GZ4gqMyv`=,f) 2$}V\+/e?ƶ7gw~o"98LW +`>ʔ8ԯgVC WiFP$Ca'4żWW %P j4ǨzXD NW**^3#i+`[?:]c-Ǩ^ &i >$Qjz0+08))A6l.M"DE$];GuDkRBjәRAޗut{-(4&N;- u/#!տD.-IMܘ)Y1V5f!ɛDCHvz 8͢Wx(@-/ I)l:+S0PxL| fmLjA2D ɾz1Q5^˭49$4)K JIcc > %uʺ e.7uI9Pr7&7l2}m%S Y]ڂ u1í(:"&'PCm,ʹ/r(17Eٔ%ɊD۲R e7~ϐ1$ *&l%'  aPzؤ 50  C< >O iX w?)(7hjnWfA30+f&E!7v kō#VQL(7kmE: 8%%WO.*UCc$dJ$"D(m j3r7#^D윃sS;V#&Ys,+8*H#dAu~ťx)ܼN T9ܜ= H 12 3`&3 o,kQ8~F(i.fxz:d Q%]4>p޵(^ِ1CV%WYErb ?'lE\A>|N1aŚ.WNNu s"090l*H`YK1?a6aB=.SM4T1 :i(;P/l,{_ On&rW7>'*$0qs3) ,{Y!<9*Y̕gNXQ40DB#a޻C@" ǴiH1:! U'8);l+ѹ H(vz-ܹ׍R‰&u-3d ޫ0 @ ?۱97@يn#PI(`)p*ft8t"WRd_q)!]KZǐ z[X9(0}RK4)O3)R }v8 E ! 3'<r3 S2K9Ա#C IT Q,q a2 # :Ф ?"9)V//Fn.Dm49JUOm4J"4h֋MjU*&)JZPS ?4Ļ1!|P0)%%&٦[bdF(ӷWv0#] V >85R5J"/XVxni.\'@t*` 'd+5L$A)4`$h g$ (GUp= H%h Qu76pF+ڻ7&6ޞ_N!QV֧]n7ʔR;Q+lNܣKdİ.k-X+݇ Oy8O0^Դ#XʏPX`T #Ine%"|#=k5Wh!'mO3V9*+1䴪BؽrZx4֯Eܕv&%4 @8,uNcw9kF|2Os&4χST9(x &'p(ɪH7=J/#JRϥIm@@-Wl@EF#?"u29j- PS*$%Aݸ(ݞv5d"]얆 ҔzUL* B@)^v\*310Qj3K> Zy!3&]2ѕi%l y Sܘ6X,ӄ ~_VS RGALp7@mՉ+I#݈(!T*^| T =$ Vn& 5_#p]((H:>'*6ߣ&8ÿݯNҪ'#>ݽ݉fTS5J $vc/-<' M>7;hIx l lW, 2!JۂR$ . J#<|-  &YT Q;u^ BQ aFb-mH2x}EF#4iqO99Y=|WQwJ$|Uo#rlw'-vv͉Ԥ%s y.6]oIX-zBe/y2mG=Ey?y=z1ܱ I%"-nsҸ(ˢF*نS*+FϾ6p1fR7g+NDaX=o-i7ws,yx%uvTjo[cHS2>2b'#uhܳd w2B{ۈZcŌޢ.Tj\ ?!P6+)yI=YOg_pj[vsx_wuwqotelXaHS7C~#+1d1' ̥ܿ*ϡK :B#룎Gmv*3&7*Fn:kT\I`V^iaVpjt3qv@uvvruHm=ruebl[SdOEZBsN-4 A$2##@yQԄ/P@{:Б*<P=Z]Eo\K V4j%¬eku^$ 1#=0[I@=SH\Sd>\ kdojTso)uspu u/t{upqdt@mqgm`phXaOYEQ;FG/<#Y1P% P0 U^mْ͝/ţ6}ş_J,Ő|pȊ/s{yO ѵQW1Ik-ٺBX t&0=#9+-B6(K?R/HY P_Er|,3_g]k ;Jk%2 hN[-ɬ Lc5g% xZ(")90$7+8>2C8I>OE%TJXPK](UGaYd^gajwelhn&k9pWm,qoqhpqHq^qqpqUoGqmkpk%o6iumdf`k6ch_f[bWO_*So[jN@WgIR'DN>I9C/3R>1-8'2 ,&  K> [K^5*f@E~Ϩ%+ TO0бAѥϭZϩ)BhQ~Kޙ&{7J$}L֒[n%3ԕӗ؟ݢOE1s֨ l54'FƌV)Є/*Lzݱ@U?uo 4N ]!`&RD+2 /$V4)8M.<2 A.7Dp;H?uLCOgGHSKqVNmYR;\:U^CXHa [c]eN`pgbidjfkihlimMkqnslngm0o)nDon'oonAoXn;omolnknj=miHlg$keic+g`e|^c$\aY_ Wn]HT [fQXfNUJK SHPDMRAI=F6:XC6?2_<.8+5/'S17#-4))%! x e^ KC6)(!".Fk *2Xݗϔՙ;9ũ)˹ǜgė%ڲ w$~_<6-N٤؝ۢKݚ8`O^_6ܔ+J@ؓuȔQ;<͓F~nM<Hs:"Ζ—FӘܖBЗ PS%줵ڦ\ݨ#ܦcݨZvUѹz\@ʞTջ؞ۊ} 'Jn +3NWrz / E U`g"e%](MW+4".%0'|3*6w-8-06;2=x5@ 8zB:D = Gx?:IA|J\CLENGoPIKRKTMUOzWQYSZDU \Vo]X^Z_[-a\GbL^Pc_Fd`*eaebfchgdhehrfi%gdigiVhih!j=iEwԷ4ώ׳5ҭڹ@@jVٚs5l, "H!sW :*ka80kb5'cO  s $J k / ;>#7.' 0! #)z$%O' (!*l#x+$,A&..'/ )0l*%2+q3#-4-P54/60718 3-:O4[;5<6=7>29?a:A;B<.C=EE@JF.AKGBBHHQC@I]D4JeE$KhFLgGLbHMYINKJO9KcP#L3QMQMRNSOEToPT>QURbVR WSWLTTXUXUYeVZWZW3[UX[X8\Y\Z*]Z]1[ ^[q^ [][][X^h\^\^:]F_]_]_R^ `^C`^w`@_`_`_``a>`5ar`Ma`aa`pa`{aaa1aaJaa^azanaoaza`aaLaa4aaa|a`ra`ca`Pa|`9aK`a``_`_`]_`_V`^"`^_0^_]n_]*_&]^\^a\F^[][][\Y[YO[XZXZWZWYV3Y VXUCXTW]TEWSV.S;VRUQ#URQTPSPhS[ORN0RMQLMPLHPKO$KNfJGNIMHL H-LYGuKFJEIDBW=Bu<;A;c@:?9>8=7<7 <6`:r4~938271605/4. 4-3,.2+=1*L0)X/(d.'n-&x,%+$*#)"(!' &%$#"! {unzgp`fW\ OR GH ?? 75 .+&"     *7ESct+:J[m ހ%ݕAܪ^|ߜ޽ (D'bMՂuԢٟJdqؓҘ)_DԕsͣCр<оrɩÄ́YǗYʣZ=ğȌ,0uƄ 2[ČEP¤iTͻ4 hȽs)ḍQ÷Y7º,$w]ӶIŶ1BHBֱŴfIгX"ⲹoS+̭Hp߯ydPj9Ϊଂ92߫h=&樣Yk1˩§D[Ũ*MΦݧzRt-C ǥ|W5lYH٦8+x dRB3& #/=L] p&3˦AQc!v@aҦΧ (IHujѨק3f(Rѩ:r@q#aت D$|g-8jʮ,aoL@ѮChDRPHfŴH%SK h{ƴ%JJbxк߷?GgWھ»N/¿7 {$뽜\ξAõ)ßzčt}rprft_y[ƁYNjZȗ^ɦ.dʷ@lTwjф ΂ғϛӤ).зԸEBv҉׉ԥ؞4)ٴR@rZגt#ٴݐFޭi<ێ[ ܳ{F m.ߔP(tQz,Q8xc 3$[OzB;kg*($J:p`(N@se+PAtd*M9oZz! B)cGd 0  M(  jB  Z  p,    \.  kA  xRb q~%*.$0+1004-7(8"7 5 !1!"+}""#o## ^$ $!K%!%"7&z"&" 'j#'#(#'B$Q($(*%1)%)&*&~*&*e'Y+'+F(1,(,%)-)r-*-q*F.*.J+/+~/",/,L0,0b-1-}14.1.E2/2l/ 3/m390300414h141O50252 62j6W363#747y47458758585A9P6969 7H:e7:7:@7d:7:7 ;H8\;8;8;J9O<9<9;q>;>1< ?@R>A>KA>A4?A~?B?^B@BW@B@&C@gC+ACqACA'DAfD>BDBDBEC\EGCECEC FDGFFDFDFDFD)G;E`GwEGDFDFEGKEMGEGEGEG(FH]FCHFsHFHFH.GH`G+IGWIGIGI%HIUHJH-JHVJHJ IJ:IJfIJIKI@KIeKJK:JKbJKJKJLJ5LJVL%KvLJKLnKLKLKLKMK+MLGM=LcM^L~M~LMLMLMLMLMKLKLLM2LMML3MgLGML[MLoMLMLMLMLM MM!MM6MMJMM^MNqMNMNM,NM8NMDNMONMZNMdNMnNNwNNN NN-NN9NNENNPNNZNNeNNnNNwNNNNNNNNNNNNNNNNNNNNNNNNNNNNMwMMtMMpMMkMMfMMaMM[MMTM}MNM{MFMxM>MtM6MpM-MlM$MgMMaMM\MMUMLOMLGML@ML7ML/ML&MLMLMLMLLsLLcLLSLLBLL1LLLL LLKLKLKwLKgLKWLKFLK5LmK$LWKL@KL*KKKKJKJKJKJQJdI@>j@>?@X>@(>?=?=?=_?i=2?8=?=><>tB<><=;=;=y;\=F;,=;<:<:85 85757S5k75674746t46;4_64)63535X353N52524s2492o41741313O131V30302c02(0q2/72/1u/19/1.O1.1.0G.0 .d0-)0-/S-/-w/,;/,/],.,-#+-*G-* -k*,.*,)X,),t)+6)+(f+(*+{(*=(*'s*'6*')B')'})&?)&)E&(&(%G(% (G%'%'$N'$'H$&$&#S&#&G#%#%"V%"%D"$"$!X$!$@!# # X#} #< ""W"w"5!!+  B h (Ce$B`@[>U{;O x7Gs2@nz,8hq'/bh &[` w6Ft4Aq~1=  oz .9   lv +5   ir (1 f o % . c k " +  a i (  ^ f &  \ d $[c#8N{<RAXF]Kd &Rl.Yu7a~#@j-K t7V~Ac&Mp4Y4g,F{ @YVn3n4IL`&g-x?IYf.t;MXm6v>X!^&|:h2a+](ST|GMr= }Jk6|Ie1}Jb/Nb/U$e2_.j8%S#g8a 1}Nr Cg9W (Un(?ߵt߇HY,ޤjv>I߻ߐݖdj9=޸ܹލ܍ca85 ݹ۳ݏ1۳ݟۉv_N5% ڹܭڐ܅g]>5ۿٛۘrqJJ"#تگ؂ڈZb3< ׽ז٥oHZ"4֮ֈؠb|ԝzֶW֔3sR0ԩӇdխBՌ kK+ӻ ӚyWԫ6ԋlL-ҳғrRӱ2ӒtU7ѳѓtTҿ5ҡ҃fHк+Л}^@ѷ"ћ~[к?Н#ЀcG*ѶћЀeϹKϝ0ρeJ.Эϓz`ΦF΋-qVk.]O@2$ ȴȥȖȇyɸjɫ\ɞNɑ@Ʉ2x$k_RF:."ȸ ȫǞǒDžxl_ȻSȰGȦ;ț/ȐSɹHɯ<ɥ1ɛ&ɑɇ}tjaXNE<Ȼ3ȱ*Ȧ"ȜȓȉvlcYPG>5,ȸ$ȱȪȢ țȔǍdžǀyrle_ǻYdzSǬMǥGǝAǖ;Ǐ5lȼfȶaȯ\ȩWȢRȜMȖHȐCȊ?Ȅ:5y1s-n(i$c ^YTOJ E A<73/*&" !   " $ ' )MkOmPpRsTuUxW{Y~[ʁ]ʄ_ʇbʊdʎfʑhʕkʘnʜpʟsʣvʧxʫ{ʮ~ʲʁʶʄʺʇʿʋʎʑʕʘʜʟʣʧʫʮʲʶʺ˿ %*06'v,{1́6̇;̍@̓E̙J̟O̥T̫Y̱_̷d̾jouz̗̝̀̆̌̒ͣͪͰͶͼ#+29AHPW_gnv ~͎͆!͖)͞0ͦ7ͮ>ͶFͿMU͑ ϘϠϧ"ϯ+Ϸ3ϾҨIҲTҼ^itъєџҪ ҵ"-7BLWalw&ӂ2Ӎ=ӗIӢTӭ`Ӹlw҃ҏқҧҳӿ)4?KWb n,y8Ԣ\խiչuԁԍԙԦԲԾ ".:F Q^"j/v;ւH֎U֚c֦pֳ|ֿՉՖգհս "/; HU&c4oA|N׉\זiףwװք׽ֺ֭֒֟%1>K X.e;rIWٌdٙr٧؀ٴ؍؛ةض+ 9F&T4aBoP}^ڊlژzڥوڳٖ٥ٳ" 0>&L4ZChQv`ۄnے}۠ڌۮښ۸۞۬ۺ '5C.Q<_KmZ|h݊wݘ܅ݦܔݴܣܱ  '6(D7SFaUpd~rލ݁ޛݐުݠ޹ݯݾ .(=7LF[Ujeytod~s߂ߑߠ߯߾ .'=6KFZUidxs ++::IJYYhiwx#.3=BLR.-==LL[[jjyz#.2>BMQ\akp{ #+2;BJQZ`jpy ' #)29BHQW`gov~!&06@EOT_dns~ *,9;IKYZhjxy#.3=CLR\akpz$'36BERTadqs **9:IIXXhhww$,3;BJQY`how~ "+1;@JOY^hmw{  +/;>JMY\hkwzy} ',6;DJSXagpu~%-3;BJQX_gnu|$+2:AH ..<!J-V9bDnPYdp{)5@K%W0b<mGyR]it "-8DO'Z2e=pI|T_jy1mKжÔюbm H;av9wic>~ܲ.*|Ren{ymrZ5G|yr\H;)]rypYZ8墾@uÿ58sXnxvg%N,ٽ;ޘ$oDM:Wlwxo]C$w+ȦцPoУ.]J`Vp}xx q7b#M{3tۇ\[ÐUʡ^)5 LI_mevy wnagPr;$+  鉅̗̇жU "U7IZ;gpwyHxfs(93.)$YE' &EX1LщɄԹжmlΧšy]b="*M|]6;ljec܇o@ن{rkhggjmrxɆֆ)5@EP]bouƇƇƇ̇̇̇ԇԇԇԇԇԇԇ܇܇܇܇܇܇܇ !)1:BJ[cu}ƈو ;[zމ ._6l@֌3Zю;=Ð_둏8䓬yJ"(W˟{g iI$+^ŸˆU+7LіGۭF}  @D!$&*e/38=AFJNRWZ^Tbehkn.qfsIuvxxxyyKyxpwusPqQnjfpb]1XYR LgE0>6.~&UH [sf}85XaHsGW5 * a(|zLqU>?us);}$OJ}j=+vMo$hxxke93⼉Ba#t%΁!36oꞷ[:QrY1|)Dm9fV/~{x3p8gSѦF5qbз: ̂"եqIJk=x~xC@3V9j{djS\օ[}4"<9/1NjhEz߅: .:~Ckj臃̱;|/녖ioy {XqX/~`LȀ1~aPԱyc=ey$K[82|6`#MOe 6N/TDO΅8{j/cq_iP#` 3f~N-DTGg37IJyf]"&kf Wm~p@5{V:`^~q<-QgaY6kGmK:E&ˀ: Js/l>7iGnwY+ z<4IDg{KqT. S c1`ORw3Sk{zlV";stxmlSa,CA8SQbZn9w||xCqh_eTI9=C1V%B_/?O_oo_O?//?O_oo_O?/f ~n~~}}||{3{zyxx4wSvTuIt2srpo|n,mkjigfdcna_ ^K\ZXVU(SYQ]OXMLK8IFG#EB@>@BDGI#K0M5O3QSTVXZ(\]_Mabkdeghljkmcnopqs4t@u@v"w xxy|z%{{p|}}}f~~aa ~n~~}}||{3{|zyxx4w@vTuIt2srpo|n,mkljigfdbna_ ^K\ZXVU(S3Q]OXMLK8IG#EB@>V(xR q,V50~%ͅJ ?o'X| [ٔau{( "/,(E b O? 7Y'۪b  S\MuI o /&)DCg:1& tx& p+8;56EvM Xm5BD=*LA@3MՆʙZa`7 L&(#D%[; !!NB ^ߓ K V ;5 1uٙf!(_% ;# .!Dq$,T5X/\F m ;]w#$?{ss[#  ",ah8·h"p5$( 'ޡj҇ #>*j:9KF1p~|o^3 {ߴSmet*N)L疶Ƕ <+G?/ عW۲$' zyZ NH1~ 3;%"|0<"#hd 0# !0`B7#mFtdC~Nٓq˲ܠ"L#fq %GA;X8>'D J۱ˋIvQ. )$O2 {&O,(j%7#d!q,]0%W&:'-bCB,,*G] :.tD/#\B4p" 4H;t4'z#  & E Vt>y>-5{05$q8qg3&%, ,<v{ - #VW  Ф\@ |{ )?AE*:!')%tCu"H*KϺ7aZ$  H R=eJ)sbV}ӫʭtG!f4|(|l>{x͖y0o.3T?(0_ ('WāۃޒڬD"#,9- qc .yN>YB <9"q!.TCG7ޝƢ \o &'WN n&?fI۟9̮,HO34Y! ,u 2)WU!9MARn׻C]c.A0c,T|r}@Y]*'ng{1$YWK.:qAVa .3!3չݹjs/bi t`ς&J0DZ"u~e()I|xê F3 =T+;&}^dӰoOrbf'?̈́h-|$>(PKCmHs&+ҥ2 !:3$Kҡ{ . ?S'dCGAɕֿ&  KA6ɞ"-)|3 10XE4&8; |۾'[$!j90.%s$]۶ڻOv o&=9#68`34-#YsB>!=_" _AݽҫԿNAQ$8;| EZ O  nNCV .3&(+>o!gLۃۜ+5V@ E#!!$Y-%x? |TD` !9T W[, }nFEωs%^ 3FEO]6)PR gr:$G}Q0 W.0 !s,344`U^nomQ, (5CyP@}j go%~cIW/U*\R ;9I:54Kiݭв¢١ x82DQ:15vT9J&ڀ2) 5;H:$UH3*3 ܴG#XF``V("ԡj/#!ԅֳˑ i-%&!1, B#y&#B ci")K"'4m' Ѧ] ߰sI,88 VUP ".f,3>4M&Lcr 09DK8 -ҳ2;-v s'2l$'N6وO1 3B8V%VڦX=0  g,6z$=[Hv4A> y<l!o*-(&v G /Iov%`ЩlϦ5ʩ^ O00u+n()z3(*IDaJ: \ӯ iAH2zګַ~sRM+ Jn{3gTC e%.3B?2eX,,V4  -V|AQ٥dWޠ u:!3W*hn6x &M b/!Gѡ'N,A'vJ:F"jEKTMGcݰ(\4 5)Y' R5K1,d&+4"B4-(x~VŊ@e{%~:}.B%7$$&aDd::;)=.3.j/. E|  LW4@-5"\֍Հ $}u 'MY(T&f쥼 ȥ7M/|fΖ.o >: ';W*bNG]A"thjvZ׃νK%h*5F9"`+A )395y"jmxE~vt`j5<3:l ')Iu.}@1.iՑe! e14p%V] ( k?5cĿh7py eAj r?kVD"]* :#?(wҞp( ]/7(""#[PG%,' `B߉(#.5ZOw>۾ y1(_D 3;?/ɠ?5VR_J<޸"O?6]0C @RM @!/T:fIt2*:EbLh*cU O Aؙ[=C=gL3V"F<\Qawo0:'DR/b$. b-6"f ]ii7kEօ&-J!# `nfX"&^ >1'8-<$WH kh{/LpXII*FڷВAL+bc$(<93-z V2X:a ' R L l*3*]u-1xu3}"g1Z'CFvݑkA38Ea[]= 辍XB) ##z\3Q3B<\4'.T!dfc!ѭ,nE7EڤĎ͡T v 17Dk!##.& gձb0"3`u7LS!G*l%iK Y }A~h'+2,PfL "8y7U)[ʢ*%EFWK"1@!@Eۗ**#Y'48K6b!poެ˟dƊIr=7d#!$&P  I$'9{"R"*Ik^aZˡe2L0 /x ps *61>+yRLA45QK*7wݨj*q &)3&  ho+ L/80PB; %} ׵k-6(w7c<ڶ*k#N,fjUaԽ F0lu +7=8&y͉VRl<"BSUM:u#F 9 d (i7Pړ8FDS8D &nٹ^ &J(Y. !u/?u|-;79-;Ĥs &RFԂ$=1-5)} bszx%!6 1'! ..](C fl͡/1 $(v"˕4!`'7!h٨y{&i+73-" @vpYmw?Nf=ڿ*Wy*77E*e  ,598\ y pȖj2MMK0>񪜿>E@UCWm !f a:H;HE9'?Cd>(3'] $īќݕu E2 9ZV=1^/+-.Z 4 17zJq+0)vb ([>"'3(ȶNI7ٵ&eBA:r;b &zsO@-5P`c*SD85 =~ u) _%F(_A5iͶò!ѣt" ESF@7+&PK 6m7 Im qvݹ_r-WEL:  ө-"&UY_xK(%;Z u, M`ܵ P5 Uz^ +T6*mm 4 #܁. J83(7 V>~+.292&s=ц_)D&GX0 7)dho+?/ _ n!"* *Zޛ =f/ 5 8xM.)wt j o} *K2lGU9ޛ?-(& g::;6h"BU}ħ 4;B$@4, ָB2]!!#xsO %" 1* j2F Y cw#>>0D+D &*&*ԪȂx(ɘ.,$e'-n(c<ZVu s )Y,]"+>VK(/W? cxˤ jI}  (P. CJ5p8( . j(!5 n 0$P22#} (B-?.3 `ږ4ʳ5 őݺ4l3+ 0 |(IbLz(5.WNW9/ľ^Ͱߪ6_+G; g9l*Mzaz_k0&e'kAyqli(I'LȚزZة [2J!WѾ2~K H P  >M X N,33jp!<وt޻b (0÷qj L\Cq$P%<++264|#Xyj̵%Pű_Y!X fئ t:[lJ+fT .,(3IeH$qPlhAc W]Il'/VWFP)f鵫 APMM= ;\/@Bx0+7Z-\|Ӷ'+ *Ɏ5JB =4R DJ6MZzk_4 2'42 &- Hh*& - UhfA0nc_{"56-*I+^I *D4 S$?P Km%%"7  ho~ ;>4ζٍ U k 5!Kɷłੂkׄp?E2%! 2ߎy΍فoDH}LoM,7K29+$+1da:zp~!S:!0Z_J f|#txw3y I XG_Xp J+s,GnT Q=" JΣݸ#68203L:5!-}㈵ᢛk}zq_i`X?PG?7._&? !aۡA!aaa!Aa!݁A_ (0?9AI_RZcks?|_{rj?bYQI@_8/'??~l+}B}||M|{{3{z^zyyyyx x}wvgvuTutttsr&rqp"pvonm,mslkjj5ikhgfed dc0b-aF`]_O^_]K\V[:Z?YBXWVTSRQP]OHNMKJIiHGEDzC#B@?g>3=;:2976M5 42X1/.%-+*)'G&$u##" Jm3Mdy - =L Yi{1Ha~&HoܙI{ղ9Ѩ4~=ɑ(ŵR¾3ۻb򶩵q0߯ͮVR,- 1OQv!?@#BzCDEG?HIJKMHN]OpPQRSTVWX?Y:ZV[K\_]O^;_F`-a0bc ddefgkh5ijjksl,mmnvo"ppq&rrtsttTuugvv}w xxyyyy^zz3{{{M|||B}}}~V~~~~&Lt}W1~~f~-~}}W}}|e||{N{z|zzyyx.xw"wvvhutItssTrqpRpon0namlkkPjnihgfe%eLdPcqbna`~_^]\[ZYfXfWdV:U2TSQPONXM?LJIHoGJFDCyBLA?>=)<:9Q865:421A0.-:,*s)&(&W%#"!Vx Uk 2 BQ_n/ltK(<#yF(3I0,ߖ'8.CT5s>{#TQ;3%fC*A/4B"(R5Ha , tc\Tص#SS ޵ 9A)_π5}ZzM3Ơ #275~6(e9P[ߌ\/g K4ݯ0~ QGĚ0H,9o;#*7m=w;(Ή T)FSf$T#lRƇ I _Cye0ُ*X;[VDj ' ^re@ , wb \S A t"G71 I\ޣNb E5Z7N0N0i&׸5 p *`a P'h /ͦn{T)ݼ(88(JX/$K7ސL)fҪ$I,?֔.<&31ɬan/?dIR۫$q9W!fStҿ&$B ي?3&0 Q*'#岫 _H)lߔ%`w~-\ѲL5 +*=&h z$8;!B"mR!HN!m/g*M?e '@?_`4 b o`28GMڰ +nb320.=,q!Sɖ46#<"-͡5[,2=%yǫM*`}-@;{E T;651̈@&jR'\ZFo l n"2 2MJ G_&l/к327  T!L&,?r 'z?SZ" WS?&4:4YN -Ï79*Ze*4 /\1CEz >}h= 7lq !hϚ!=I܊.  b)?3(i$Qeށ4"/"Զ<}֐ю=#4=*p~2!=d0u9"%+9J )^ ow RJ"ӯ$!,֘ yTV[,tD1ӝu>y>1;F(~5o3R\z-- 7' :!MEAlH/Dܼ3NGŜ`"  *KyAAV e'[%a w *۟-ϮMX&T t3SzQL/ 9XNpHXL}2"̞ (+:Z6QC @USV.z(v5SG? ;JY&96T5V/9 ZH)Cc%&Q jY]sm YC<0LɲH.׻ ;Ru7)HzGͳK9c$:x҂\vG|B'S# ?A\!5EY2~i0өXc a{ƀo1ϰl"R0#0r?)I+V$X &r387nV)fIw$8׸%'() C(6#a7 Io33|KߠK$nO5yb_T) h;T6+&~W2FY9^$d73 ?bk,Y,)&f " k CDQv&ѕ+$>~#! K11}!<Lљm,"O V8 $$nL宴Hź*| 4-饩-+7^!],| 0D &*:{F8 {=ۆ:+$8 o 0#0s#Կ6G !#g"ٮiv6k6ܬ{>F< $NC!7|v)ܺGj @ɼZCXGΥp IWw/4' tGr=FZ3  2@2.0 xq;eQ),!&'B5{=! ɝ7(F/* ӆ?#^ֶbf$ޣ:I&>W !3Ht2#R |1 @p7-Z\'L$ k%:2 سA3B ũ .b0`׀:-`1)c0t)7S,v/6fƿMSЉKB{|K:RE6׃H D1cH hn$.2,Z,/p -ۥ!nW.$Uq?  8G -ɻDy~ ]<*ΧCg^#o m+T<@h}a\蚚+k+ʝa y -k* 2:+!By9!9[9,58WG4EJ;9׆BzԽ( w,!9?t4:P"_9?D6+(#N BIe(y"Ah( ?ҧ48FiǼM-7;٬514QC$Fn_G&"=##e/v/CW6~Sx ̽7L$>{!4T w<< h S*Q"*!r36^V   Xc'4Ž YF2L6 ~Ly+R۴**=s"SVF18 C qO|H$3#z ]*] s }@is*~"!E"5GY*E3$'1 SښUl? 9͹N)M:S'<m! L43̿ ~9#t^ ܩ%4ZC ZF0'C F=[ +Q%/" ۓq0e n,4LFh\q=?.Yb 89No"O%*Ӹ>NJ%V< &= %N&&t܃3l.B_dF6r_;8'z>eK(aY"6 #r (F٪h*O 7'ZU "߾$ ߯oGR`՘4ߪ2T⟗SH:T]g 6 f$ -vt!$cH(N2N"3Y M-b) Q,!W/&ݑ8J;!MG!A!]׳3G)#1l)'5 3 &ТE+ބL+J0U&E&MIr-4 G+M& o[Tv%6="=\ ce.b^ ދ #rBČ-h:uG+(Ԡ ih'^,]!#c7lg {gLM*# %wK _u.cUu8Z M+"&yҧj k' r6c &',Z TؿތG4fدҭKO9,5q:ǭ۝$ e)x~4ńYār ϱJ )A#~9T8h.)<3ǢA*m#Ƴ,;QL:a.>  ׻3+0(^'j 9pY0o\-/z4&?&#aO&_}ZI c l?T(tE2‘Z vp~d !bFD(W1 >49RpQE>( "&r{/ ߌ#mAҾEZ:NjSh"3ש,-ݥ + -R%v26 8&Ѻ,'Sk)2jzܼRQ6|$K_JE0XO+jԡ-a\S9rLgغdu(- #ƍ [aDʩ{ W)ҽ4i5E D{3d""1r-Mj)H<0IBH$†7$!7QT,zڨ.ڸAB (0Az :6 . ,#/ &I!f.2u˧'R҅7p03mo"10K*F+R%-ɐzVn/UW!x>6jOH+5Vz"c|  S- +@t8 d>6b9:;pz1 5 ti͜ tYWJWWTE1h!.( R,#wׅ$o"&QKz<' )D!ƕ slL%IY,#+%̢ V'B5؏MR14,T)i%'Y,w/Vf7&|t P!Zٯ-E!j1&U&O?MFG"x;$)`B 0 pg0&<Aq"?  j1@+bI: qqoO.76X7N%6 hN2$ B=ēW/* ۦM]#iT,*iV#O"2 a)R P |Ue^<'egaQԙb b0; 2,td"F&;!Yyړ׋ u ٧F)/1- @(<@S p'w z!_6Fd& 9]ة/ߵ{3O ({ }#^1ʘ!d>0=?M:=a9̻*KaM>.H<##+R(!1d#M"I:<~.z__L"Ei0 In7^X ۱i^ (^;P "$e$$a;5.ѿ"sh$|~MKc%n\Z# 4QD | ,˹$ D7h oռ,t} N,r۸h/Ykv&Qo*cuS;a12t5#g#~ <: G~غ-%b (jKE.HɥFXAfe,3'V+^QߌؑDI -} /} ;n/47"~<t7kcGJ&}* U-Tv:(yfz!o\2j۰~.?Pˊy$\fԯpJ*1 h&Uܠ9$4f4(ԧAS'C{IHR },\+b2=(TG<\N.ѓFxB;R! &)~R  VN[ < ;C3ș)yW fF}C*j3%!˹=ҁALܳY^x/sBA /hIT_<{w8RG18gGss*oTZ.pb.gu+/X_*ݫ(TG+C˛( @yfoBʨ l7'٭ 5R8 2R11/_l60 F Q2'QAHN|J +tx- \2X BYXʷ$i*@>Gvn4 ׯS!S˴XsmU` '0 .-<^Amd$<.$T,B",.6DԄMv& Q$Oʶ>e)R*Ȉ'߹2p0 A ("U5J!<$SҪج%UQܿAAŘ`CA̳+l!{%-|99@5&zA |=l t <bw0 [ 8} pH  H9 N4 +q2X=+ P1N kuѭ8, qN·.+?A=#$7@Ҿ(v(Ҷ7S1!7=a@7_ ! <Mg˲K`M+ل{$et<  yF 2kBd y+F Fk ]G>}HU'c "d˺-0ŒL<(ޜ9H^ ց1#bji=eZ6M`" "PG̳9!&3W5`|7ڳ5 4C%3ޓ ~<82  4Y[Nۭt]:zv#ؔ-HU۹'-^,*XE$mȘ?9 t+(5< ([,ܾ1 Ձ=]7#!f8)P%ݰ@Il7C2+T.! o۴)f$5p։Ot?C (ph+JȖ% Of88In[Q_^HG1t=,Cb 90`_*NtV|_ԩLR;"M>gy"A_02(Emk"RL5soEɺ/DT<5!0PdJ)G= S/NATR+,8-E"γs e]K " zkc $Nr̻6!!'ȉB}QRP;P:8>נ!vLb 麰IX$":+7wp# :0"P".":%%?VQ4:Z#ڨg3}޸33ԇ"?>0MCD8ګ! ۰4ܛ'U٠*)I b0@?$# *R:I ,w t*8˾- V JUsьCR\ i^`8E#cں a77_!,4-"0&~~x;0[U{#"[FQ4&7ײ)\3+!(D&eԗ? -O'wKyJ-$ctásy)FO3CпO4\ɨC&FLDC,J}.h81RBl9#R DEWhM7T.Հ 8w @LR-mY"c &*ڶ&׺ZE@.(ՙ(#Juyک8_԰M&)X~E PU} Se(a"Ԍ3(/0TWb%?] @VA )<X 9oRnNaR=*Zė @0_ܞ#$s t@"U´0>O?c{W 7$m RHtO?]. t&2-:0L ="@7.Fd@k$Γ 8/+Gu$rђ,@2cB%y-J2}I( `5ͅXҲ73D,p4v O$ %$ RJh+c(@hȷJ('K?y?_B׏ $ aA_-9X)ӱi $<PX} 7)+k^<0B#-gLә= :1 EUoE?.b-!N,"$L/= &Y0&<=7" u-WEyh _ m:<^54xh D!Nl({* F W<37Mx'K/ X]{@z'JE/-f Cf- QSH#S<('Sl&94ug֝*e&5$O0v!xn,` i2'l,B+ҼS21Y8ɸ..'ssM~% N"v'!gYm| Խ(=9 L d ;$RG>X*y@#? B$߶/N <m; z LAHB=PѧCv%$ ]D`{ >ї=K5s1AN-7l 3݈8bIЃ rޭ(ԩ q ֲ8]-q@xє$PGst5a+ɩH++.W "T!.TЬ)E'A6"@܎,97m@H+#73.*jYUPb `R_$9 e.}Uڵd( "Զ- ק(O4$(f'#(+7%e Q*QU|ݭd< &g&;l7,06~=F'D'5#8jSi6wfon'XhƘWdĩSBU.;+ O 6v d<߉i@7C#PtTCD^#q+( cQ #A KR8ϑ3*w" J4? WF>.>$}&։!rA_(7 N,S$[.S_8512+ p!hS>0Ym,,(FW!{ Ւ+:\[ <~2'NR!-g&lc:| .&T!0ʂN!IpK:Q S 4H('mف ׾^b^pѲ.5e-+HWb(`&F*ٌ. }?W*/b5. 9C! b O OysM1_pi9b#Rv~+nG8G2b#Ҵ -* e:ؘ r+B>$-F $~! H+j&T,Y/2Gd%G9R9FD* Ě0Vob@:<1,lPt~+|+)F$kk  kn4>r.Q2=%+e 31C7;;S RBL~ZsA4͌*P #[ hT;\(fG.&="[`7XNS )9AW"Fl%m/_v"e2[־ n;'[v(*)5d:.[  ?%8,R aE)pٚ   `+!W %Ɇ "(͇#{ N3( 'G/Ѻ=L,o5 p&P$ѷGa ;ُb%t@'2V(_ǖK07\ -aqߌX Z*O5!kT2 >"v2%/89%K  L ,?S n) 'W,[Ӝ5aDP@R I#"g&* " ճ@|So4H"] &oh((&Aˮ0Bc:%iK6~'HAGo1&* 1!!uQ7H,(^Զ2%( 9?-+t:;=&9 * T  %wB2 (F#ѻSxIѶ 2M8"γ<*ޚ$F*B e0[ ֮,^Җ?$̽9L+H/-E0 yk #rOEE+n0V*!G4?YLF/$hDn0ܾl#*}M)"  a (d5 k=bml*= ڥ*-,?2+]XF3jC UJH*Nα  4D4'#.h1+OJǷc3ti1 IcE0=5"O ϟDI d TB]"m3}a * 0Z;9}M _C(}n; , v28[V"UHa&HLE p 8+w$Ղ !9'k('3L\$5"@H)IuS()I. " ? ,q 9|&ԡXk,6(Kr,C]G)-"j ) A' Nvۻ!C'80&O>-̾e 4%jK' \y2AbA-`wX l2@f$dC r (*RJ/,H$?\pt {ӬG'# >/)qEQR-7 alJ׏.*(%Jd(Lӆ K~+%] }0X&bIZT*/e`(b[·>3F@b4M_N'$3 _g|i+#33q:/9B3;XUDS (q T Ng *vu&OYN3&#O&|-8= H$ 7U4MP_%yڀ~9lV~~}}B}||M|{{3{z^zyyyyx x}wvgvuTutttsr&rqp"pvonm,mslkjj5ikhgfed dc0b-aF`]_O^_]K\V[:Z?YBXWVTSRQP]OHNMKJIiHGEDzC#B@?g>3=;:2976M5 42X1/.%-+*)'G&$u##" Jm3Mdy - =L Yi{1Ha~&HoܙI{ղ9Ѩ4~=ɑ(ŵR¾3ۻb򶩵q0߯ͮVR,- 1OQv!?@#BzCDEG?HIJKMHN]OpPQRSTVWX?Y:ZV[K\_]O^;_F`-a0bc ddefgkh5ijjksl,mmnvo"ppq&rrtsttTuugvv}w xxyyyy^zz3{{{M|||B}}}~V~~~~&Lt}W1~~f~-~}}W}}|e||{N{z|zzyyx.xw"wvvhutItssTrqpRpon0namlkkPjnihgfe%eLdPcqbna`~_^]\[ZYfXfWdV:U2TSQPONXM?LJIHoGJFDCyBLA?>=)<:9Q865:421A0.-:,*s)&(&W%#"!Vx Uk 2 BQ_}|~~d[~o~}}N|f{ezKyxvmusbrpn'maעOʐfI73/;'851.+s(8%!`_ B~\IYۘf<Au_ʬÇd ƠkWVh’ kݍboG58QĂd-  /g%˂Y@*d'$!  $1AVnݭ5nϰHǠhۼWܷl[ݩ|v|iĔ,,Îj 犽g‚/<܀P$  #M؀6&Z ф8_ߒn n4נѢפ1gD zwÓ.w&ҔUݱX0 pT8 Z3o"3%'*`-02W57:=? B}DFFIKM)P_RTVXZ\^`bOdfghiklnuop(rjstuvwxyze{||c}}g~~,v>~~ ~}|J|{zyy*x)wvtsrKqonmkjvhfe\ca_][YWUSQfO.MJHOFCA#?<4:7*520m-*((%" j6t & _?w)g2|o)֍fXe/غCұpb`i|ğ;E6Xq>3cI+H퀡c5*Uր.h#ņˋ+p~ LQ-&Fn׸_c2ɢΓә$زDsRCBCA 3tA!b$&r)+p.0X35)8:<1?|ACE.HZJ}LNPRTVXzZS\"^_aPcdfhiklm3ozpqrtu$v!wxxyzJ{{|+}}(~~~>h!~l~}}|a|{ {Nzyxwvutsrlq3pnmAljgigbfd2ca_^Z\ZXVTRPNLJHFvDFB@=;F964I2/-+(B&#V!_[O= % ~fO=1/7Lo֤DΕFǷyBy{Ө3q dǜ6/SYȇ'iwh󁊁.ހc8:f3n|k'Çǎ*lnחKȚOߝxƢz6ȩy]I;56>L`{SʍXӤEڜRuC[5{R $So"p$& )Q+-/ 2D4t68:<?A&C/E1G.I#KMNPRTKV XYv[]^V`akcd\fg(ijkmLn~opqrstuvwhx1yyzK{{{|}}}Y~~Ia ~~~}9}|+|{zDzyxx)wHv^uitkscrQq5pomlhkjhog fd(ca$`^]b[YX[VTRQEOmMKIGECA?=;9753a1=/-*(&R$"c!R  w , IgAr1y@ ۪~U0ʴȩƣġ¥к.ZůIEy)ʙr#ܕi<=k0J8L偈5쀭wK)2UHdՂQփeIP) &Lz3֚3uoU^׳e5 wg[SOORXbn~ؐڥܼ.Oq.V}Fm "Cb~ "$ ')+-/13568:<>c@9B DEG^IKLN-PQrS UV,XY3[\ ^_`PbcdAfghikAl^mrnopqwrdsIt'uuvwHxxyJzzv{|||h}}.~~~Or=~j~~}J}|`|{U{z)zyx*xowvuu8tWsmr~qponrm]lAkjhgfOe dbma`^T][{ZYWVTReQO4NLJEIGE-DrB@>'=[;975 4/2O0m.,*(&$"   !!!    %-8DScw݋ۣٽAjΖ/iŧ.xǼp˷*fٯQϬQ٩f.Ң{*ߞ[#Ę~fSGBCKYpً DʈmɆ, PX؀uO1  -Jǹ J<n_k2և6vDӎЗ9gӞU?U&};Ӽn SħUʺr+Хe(׳|FR%|S+iC~V -  Q"V "o$/&')f+-.04235278x:<=H?@kBCEGHJ}KLdNO9QRSZUVXQYZ[]Y^_`ac3dPeffyghijklpm\nAo!ppqrhs,ttuRvvwAxxnyyz{~{{a||+}}},~u~~~,\oB~~Q~~}]}}|3|{N{zSzy@yxxxwv.vuttUsrqp&pMoonmlkjihgfedcbnaN`)_^\[kZ2YWVmU%TRQ3PNM"LJ[IGFEC3B@C?=G<:A9716431/c.,6+)(f&$(#!BQXY  U OIEDHU j*S߹!܊cCԶ,ѣΘ˘Ȣ*ŵCf3ҹuĵpұBM觼nM/ۜכ֚ڙ0Orő*e-zˋ!|܉@:ԃsp#܁\#sT:$ .EbԀ:s>݂4S+ @׉r^ p(揧m6ԓ`A%ܛӜ͝˞͟Ѡ١2Nm٬0`Ȳ:w<̼dYî_Ǻw9Νh:ե~\>߱$ qaSF;1&}o _ L8tS,!k"#8%&'a)*,|-.00121456"8n9:? AGBCDE#GTHIJKL"NCObP~QRSTUVWXYZ[\]^_`abcddCeffghni:jkklDmmnhopplqrrOsstuu-vv8ww5xx"yyzkzz4{{{D|||2}y}}};~s~~~0VwvU/~~u~>~~}}<}||V||{O{zz*zyUyxrxwwwvv}utftsBsrrvqp4pon9nml%lmkji6ishgffNe~dcba#aF`f_^]\[ZYYX!W*V1U6T9S:R9Q6P0O*N!ML KIHGFED~CcBHA*@ ?=<;:Z928 7543[2,1/.-h,3+)('\&$%#"x!= LST Q  M IHJ Sc)~Eo; ߧwGؗmEѱЎmO0ǰƛņtcTG:0(!")2>JYj}ܪ8\Ҥ+[ 1l*oI9=W _Ώ@*':ljW~L솎2؅/݄Bm+산u> ցyN&ހlVC2# +;Mbỳ8`L5tBք$tƅqʆ%C t߉J)yuw !?Гd)Ėa@䙈/כ-ڝ:쟟T â}7r2}C ӫi5үtGƴuN(๽{\=—~fP9$̱͠ΏπpbUF:." ~xrmhb]XSMHC>94.(#  ynbUH;,p[ F!/"#$$%&'(g)J*,+,,-./m0J1&23345e6=7889:a;2<==>n?:@AABaC(DDEwF9GGH|I9JJKoL(MMNOOPPjQRR|S*TTU-VVW%XXnYZZT[[\.]]c^^_*``Raatbccdd5eeEffQggWhhWiiRjjGkk7ll!mmnvnnSoo*pppbqq+rrrMss tdttunuuvkvv w\www@xxxy]yyy"zbzzz{Q{{{{,|^||||}G}r}}}}~3~V~v~~~~~ 8McvxfQ<%~~~~~g~F~%~~}}}n}F}}|||l|>||{{w{C{{zzgz.zyyyy]\F\[M[ZQZYSYXSXWPWVLVUEUT=TS3SR'RQQP PONoNMZMLFLK.KJJIHnHGRGF4FEEDCeCBDBA!A@?k?>E>==<;b;:9:99z87O76$654`433322n10@0//x.-H-,,+*N*))('S'&!&%$U$###"!W! % Y'[)_-c2i9 qB  | N ! ^3qGa9?|nqJw oвʫRŰB%ʼnPq됗ɟ:BįН)f[\p7`h ] = z -;d> 20nciÌWgoYЗ#OHWm0 m!"#q$$$$8$s#Z" S\+o hBOTC ddBOI8tI9 M"(-15 96<?ACEGI&JJsKKKKKJIrHFEA:!5/[*m% eW aXh%0%D e!&+16~|zcs@kjc[TMF@n:4/)$Z 7 *6^$45)fE Ec"!'3,1*7=[CE|GHJJgKKKLKJIHGAE&C@> ;7H4l0,%L% ~ #MmwP| j!"#i$$$$7$k#[" NY&m o!o3݉hEnɣǮ߄D %60xAS%עg*  e 2 ] |za;ڔԦj5hߌȊˉьƤ(Imܕ:=r Q5"IiB"oϟɄ$xz:Иt(v7`=&> Zu?{pR8#C jJ'<WvzitT9!#h^ k}A ,@@'E^E@7)uHAFN' }@6JϏÁ$xyܛQ*Pɺ(cvQӀ܃51^hkg7Wc&~Vx MADW y$h4>DEB2b A[ 2LGkgy*|_C,! _PHM s2Jeg{wbHE, A~l Ya#~9CECx<0@f@`L R`J #$$"$&"8.!p| X@)29?^DGTJKKJ9HD9.$^ +JH $",7C[Q_o{ uYfWJ|>3)5!{ N *P'$/-\7BDO\k{}HziZkLm?3)O4F THf( (2@>#F*I KKAKIFB=?7/$\ 2+19n KdF "$$!$&"1!6ռ|Zn¬f^e.BH D ~ n #  9>;ًͶzSQrӾ1*a=hm@ݦ^͉}# FbP   S eN"*{{D:PǷˋуif?_  1#$$#!< 淾̃I,oc'Nf+)" >E5% 0Bh|E&r0d]xGpFY7!CC1  LSκ w35݃{,0Di 3,BD(i*0>up8]E9d4/*,&!dS G-_ /Hf|Gwisokgc`>\tXTQdMICFBU?;8E51.+`(9%" t`I+ s0ڎ,ԾDͿ*Ɔ־Gi|{jP3 AMȁ#%^}}xt9pYlthd`\YPUQM`JFTC?z<952E/,(%"~hVH=5- ' " _€+2? Pܨ$KjY{}"~|){yw v0tNrbpqn|ljhfdb`^\ZXVU0SPQrOMKI HSFDBAB?=;:b865Y31 0g.,%+)'U&$)#! tW@.  xqldYL<& ~\3h+܋@ח>ҍ"h˫(aė$Lpdzݱ   HΆ7ƒwcx݆c 9?mbwI~'}{zaxvtrqom&k.i5g;eCcKaV_a]p[YWUSQ P0NXLJHFEUCA?>Y<:8=753820.J-+ *r(&A%#" dI5#   ~ xqjd[PВDNa u:g QT{s|_|p_^4:li DgDŽ!@gat|`t:/%n)<3z 6%t7nŠFxpvz|{FtgR9%,{)6H+D[m0y}dvr|]HqZܢ/,w 9(ZƅʊXتܺͽxWTr1/*!ւ"*A쎢7T׺Va|rns=uxz|| {voeX7I7$$~&WoeEϐ h-%e6GbVcnv{|xDXM҅M|їٲ aY*Cb+N1j-C&:!= N*]|_?dԧ+G7>3*l'08@IPX^Hejo,tw=z|||yt_΋Ac^ ֊bJ|aȋ0´0"ƈOi0umh(-m0C10/U,(w$l {2 Ԁں_DbǍ솾n`)|zvtmsrsstu9wxz6{+||}|{z|xu^r@niic]VOF?>5t+!G _+T_|]"7ԓ @f\pij=' !"YORB2)^wE'o 98KVˍ˥  Iqrmqޟ$gAO=χu'049P>Mo8AAEMtylV`\Z!@9[+CF,-:Wo44/+B8# )'^ns᭤xD(=#& `x3!/' !"YORB2)^wE'o 98KVˍ˥  Iq>#01 H'3Q /.G-Vڐ!92.IK8Jev]#V66^&!S>J%8zR@+B(3t! b?ʨ$H)=)c^W!3mߩPNSӎj7<5#%"2,*Q[{B'Y E-MH2\vi3f^3T'8 )8l60*z!|mΏ֤`neg+`:FNXP:G1{#CB.һ•ί#ʦF@KފW<0  q37=?8>-+28;48.r(muRtԱ{TԳlCHFMRoV8ZVY3OA8< K^Wn"tt.trozh[C67%) d%i13//T+*&[$7"bnP ^JPW׫х\ Yoyj̥1 kM"'r' @&B8VA DXDXFBFG*GF~FGA3 *%&\3630l-*&"$7teտ&nΔsz>?/?8(Y \.89753`1/h,)%VPH L)ID;ΥJFC~EtŦѱŠo$R\f<%@2E8a=oBGaLQUY7ZQC{9:GL[?lst\tsQpj^H#7;8)r 3^#/30/+l*'$"Yl,H  ٷ֢fաU` `ր͡#([ˬ!\[e-.5Q|"y n  M9 C#-C599<>/ACEcHJ.MOQSV.X9Z8\)^__6[TRME9?;:z<&BlJQT^huosSu^uIuudt_sq=o;kcWB`6;:^9N;^856.4& Di\ |").1U0S/.,+m*M)'&Z%#"=! ) ATjh݊w2sq46$s V8ݭ#ХͰ9ӑB6  %  * <Fgn ! :S*9pp'  $X5QX *"B$%&$E o e *3:=/??\@@4AAAABB B BAAJA@<@%>80)'  bh j[&,0x01/-+6*(&%%\#!'4@Y 0x۟^.fq*մ汙lbTүdk")2S܉{ĜbXw[bǜ9ͺѻӹչٛڤd{'8?%tf6 Fn L"$%w%"' bNJ!+W3H8%:L:5:9X9n8641*& +'ZV?H@8ABBsCDD E~EEF@FFFEA:/1')#gP!)r29">?>=<;:o9E8 75j431d0.-,h*'  BRGfHLՏ>Յՠ8ւS֖uYG#ﬤ6ǮѿPixeJ G c9Wi*2J8B;=9@BEGIOLNP!SCU^WY[o]=__\zWIPHbAM<:);? GvPZdmruuguutsrrNpm0gs]zKo8J9Z<87;97c2F) R5!6$@'&-00/.Y-G,*)m('%x$2#!a U8u;WNU-G~ cV vc ipiòC+)vC;tvW F !gENmHq/%--/)=llgeT uPfZY1!3'6b>=YSSn7WKaT^`R 23g7{HR];0%M3v_曥h{kGhϳP+D&)`|d4a0r;Ծ3c8ޗǖ'1K櫑Ҝن*D _8ìLq(7fo ՖǼORi6_Lp2&9VONc>ضfvJWta@E?Y]oo:K{Sy: Ypl^ E Jef`[@̦ (TgWE?=dccT^dc5CT%#%"@Sk*cd;^eAMoVH\1KnLUO[OBؠ7fifd8a,TR"j`Hheb9ʔr۷6]W_eZC@Ʋk2 ,Ѷߤ&4.+णȵ޶(#tg"ފ׬e|t #V$SXPoUXjE$ُ,>xVZXWAƽ_ Ց(Hs+9X[NP& ܷړy͒Wyt!pFO[= $ŤXiѶ 32 ՜EݹhCڨ)B.K"F c٠Zh>%X 3΂5ѕ D-Һ՛ۨ&(15W62$ܳZÝ4NKV?ViT#TVZ\[XD ب1.L[`Ca2]QS6yҊoAνRkfon/m7jhfhjilblkhnS׮v!#IM]efld[Eؽ֩@:QSh0a\4afhxgf?gme^FS?NtV`aMu9lHƿ<@X/bdc`S8O׍>P+XA7PN3JkY__@^W#Gf+ X B1TKNx&+ 9 i$BV\[[[}YJN8 F0NHwGT]%BPQUTN>f5ϿV( QN=:EQrVEUeSS(URJBDNB 3־|/DK"MHv<!&A'M~TSSQLgH\GLJNQPLR4:scW %374)m|뉹͇  YQ1;z:3 -)Y+/2++[Sɡ.“߸j[7aG'ध3ɕ=  Ud̪uǭYvzPU9tws -Ze< 6%Cx ^ t#8:HaI?~0!{w|`!*28g< ?Z@@?=1:g5q/(O"  %0$?@@@@@:@??> = <;%<7==:1CY ܻВcȊ "\; |,49i<==>=J=r<:84>.# @=pאɝjR$J#18U<=@>=L=y<~;:%:99999873M-"T[i ^lgfv~ #$*-0000/!/- ,>)$8Sɼ4' ؞}˺.Pe ~j tnodJϐɰdCK5(Uϕ;UT<2lHh– w‡ت/vB1ԝ^_[d^VДԹDp[Ů3°ް(ɵƷb .cIZ\b5eeab[PAl-_}њxˈ˟͚طRh*?QG^dffdfebWA"jX{дͲdѽ>dQ1%7Ŧ߽6 .L=ahg`a\[!]Fa&efd1_%WN1E=Q85t58@=QDLU\*akaQ]UMiGE.IHQGZC^}WzC'&Y `:2 /r<FhLPQQiP>MG?V4C&(޳ݎA? 2JXYN=.$,!V#*3=GZNNSVoWWW]WVUWRMYF]^3,r+x1/;DIG >l.;, H7k {~dv!2Q@H_IE,@i<{HKOZOwNNUN/J>P+-ڰ=I|]L mէήo+:?JbP RQOJ #I/O5A1.+Xu N#4?Z.$)Z%o4{".'nAX]cD EFE&hW* 6'0O38pWe?c ;hπt0_2ilQ 擿>Ac6dE> F+ "Ziq[ݧ4]\@+I»/Y`b/.7 i/+K; r<;,1$-3?8 Y(U?IT>q,`>t!smXe%R&+ +CR-!>   H$1 ,)  i %XUs\[A=WMQ1uQx$x:Q LBE +* b|T*F"PR#"D`b+ /at{oL= !7Kׅ8@D{C$$"4823V ۿ'U\W+?+uٵ 260e7?,1( p-| e4o!Nye&q%K9!GgY-c9:9faUdFF('1I<3$WR%(в,,2U>b=Ү_;TzLmdQa-5vmO2<5G96sU}%vã㉵bݩgOHk3|{ +)>89 &X^vA1\+yOj:#_+#ҎÂ߄ `}VwTм =L|%!u. 2:(o2,\:Deؘf@4qÑ5uWݤEx^=+re,J- h0<0[$cx k rxt÷˘Շ:͒🸯bςdj  %Y:p ^1>`Qm`2py|o W?5) (&A)`|Ar_??,c/qu=4qʋے-P{C]uTP>wn,fXN 'P.^ 7MUi2Ku#{rV['Ic{)0PYhU4̀߃K*zrCޓ_{~|wlV+-@ϣ}/*6AHF;% 5>qϴ,Yz {p BEkz}~gh~+}zv}nauL* R;R(  $(*)$ 1K(q p Ҝ:fPy}~V|~"CO]mtx{o|p}+~~]w/~@~}|7{tywso4jbXK9!EV'ה싞FÃawP6"    /OĀ#ZJ;xusɔ򛓥Oگ9V iswmz|'}}~~HI~ 1BZR`kt:{~oy rh[M=2, %L3@L Wai5q2w{~}zzuoFg(^SHCFzNU\bhmr{vy`|I~~:}zw2toj]e@_XxQIA90'P K> ?2$(@-16Z:>BFJzN3RUJY\_behokmSprt{v6xy({`|k}I~~h~!~:}%|zxywv2troxmj6h]e`b@_[XUxQMIEA=9250],'H#P K!wsm5ʹF:7+(ui~m >0 .#CBȁȃQiZHϝ&%;1o6? GMcWMZj8*2(!OYarjc^%TO;DA2I8lԅ̈́; ]  C#. 2q<īԞؤFWNyjzze|tJtxii\[`tiัܣ ܮ OV1 ơRɱ3&Txoyzb :u@2SfQ8 5wL0`6OgM+*V(`-6/,W#RIؼ/4WY e>=jۣY!f?|<a{Zng""};60vXWN 3K&@a#QŋN5޽Rʨ뤳ޟ}ӓ K%óΕҨҹ0'+ɉ!K uIu(>=!QD* P" ؎2Q[lGԺ~$ʶҾj]і-U23 \7=3OE!d2f/yb*\Q U+Zh-TB(:,. T  +/,Î #%)*eFbG@DCDk<3 $/("6pmh+_4(% &(.+ %=89s~hgfgd_a)YOL H:&6\:X0\KDgU4ѽj'f̂룛H vq>["ݟzCͽ hR-O`8g.}F:k ~, Kd,UEH\'I\Kp$Va[\*yəጦ  ܧ[[@xͥ# a24H <I7V? PbxIZ8e@UPFE5\hb{FF5z-e35|Fsj^ k1JQI&U ' > WJ z.3=#A2r/8(T Y&0E,#F; k0.6e*172- IE9-.Y ^?@zL3# 25ۢ׹{.<598 M_:wE)k-4eqW!;\; ;,,:98,2v j&Hb_0 R-ݬd " u a5u+5&>9 ޡ 0O?1u`&.1 &*-7FZnG_)T9< !_y N9!)s l_" 0QLl(" !### 7  &*-x+R   "5r='J D/ < 2-9$O;#=b+"CBr#A4I 6$#IW@ ,># ڊ3-4#E1' .Oރ"D?lm"'6_4 06('U q}:;'!)a<+/<]30#5ux1TO+,0 Ja=I, 0sb$u  V!?-i8*/ < ~~ -"('>I' н{7GEEOFFX=wh@TK2YV,9;"y3L_iKQY2'7(4=)`1yT]}A @l:&5V,7ԧϕfPi1>T"114JcE6RarWa=8/4G={*1{ 9aOgF8Z)=NljkU+Z; 3>,8(W*,%_"7xFM-h k')*r), qKN #V. V |!(0I \7=u-F+&1$L{F!2v. _&F' 07y2csM 2r"5) !%C]kU C)0#/6,oR ?1-I!+hr* 9H.in+06((( 6-- K@"+8&_"7H^o!5 D? X:g9L~)#S6M#-0/ k o+u#-!f?N2Y!vlxr  4sD%'%!u GpFG1@ز | ],A9C }(8T44  Un9"\ p-I(WS`"c'|'479! tCro% 0ENX1/ S3ߤ*28\FC96/ 1FkJ($0B)!BJ ==7402wA EOMYH,+^#2Y t$LCEX"R  0!30=)+ ;]$41R3)/! 0 -B@w s$0+! wd&2=' >Zs0[24G.#<4v 1!&w'-%d '+/#* i"} 'C=i r#64"mX "@GHP9)' _)e<=e$;I6-| 421!]6AOC B4-J@s@ <4lLF#rO q+3M4>C?- NaM,&e*^h}*: O9Ebo0n)g9!\7*XC F)< B)XݲHV&7oBEwD-{ӡ},@/-[fk}LV&-WgEI'm?ؓ0%1CQAZ> #%N=K6 Tz+p>(=A02j- "z@+X_J( Au=F:6=S8}/ϳ t7ka7"xebg(_+QA_r8= )3A<3+@qJ T1 ޯC -6QiCr4r#N(SRJ&MobO(`Q,_?=;Y}U0*38U[DN΋/YQ?]@L9N;r[+qwl{T?+&  #&IYV =D- 9"U-&6-W   ~ &' 6C7/<#,w% k ?T VdOZ&ѽ ;]b\!C(H ڭ¿8!V/RE77ݯ4lY|68,ȧ̂ (@P[B*/NWt# "+&H2F} 4"54R^'$3F}=~QI-x#,1[D!?0 GM#1""1;'G +:7)"(0FU '.9,%~)T~4Cp.. QG$ F3;=G 8i=h6)?)!#iZ1,!!. O!*)nT 1p"'R m%<08K;-" 1:(EC7_&)#'6s%-*+/ uF{%8@#iF *Z >nl4) 4M)v B7 +4"  89%\l#2R+r-2 $p0oA51'R mJl0'M`C5s0EN$5D JO:-83/+)-/$T#rJf D&V{g585.#*!]QI֥ c!1LU1fe3,3$&@J   t-}1LXYP!/2$19b-:f2N"25l#.#*.q<=3xR,TABe<) |c B@ 7? F)&&J!R)/">#"v Uj57O2 J\%1B+B"!9m   #65(,+o/1_y %:-:q6{F:Y n38B&j l/^F#k \0)`<%09# w++" 4jQEt:P KQ+,":)q̎Z cMhOD-):*&G |o-A(d1az%0+ -7)9 /1'O&~x/2Y j5s;PYb-$'$<B `$G'y -r 4$S F*x! D B1(]&!,I% 5,{ ~*#m!12:Hx\,!n X2S>)172*kJWC-B,"7*T?'7Y<8t*Ns c%mK(*(o  k %,28>+%]0EKk; )/V:='|=^_H"F" .  $0Dj8~u>68a'c "7)xA;1_d*(  \J5|68D$ Z8 m["eX( o+~!3 `Pn!C"!. W"  |(/9l5uRy7$Q#rF"#3' D ;p9;G/{lH+ (->Q 8^6<X&G2 !,p k (Bd2j; ܅'z2`&.>%lЄ} 2MM `/My ?L=4&{O ;Og_2!3"%'a!e)45Jssn( 3@,PS."= 92 0TF#0B6IT(5I4 ML@#@Yqf*0Kb53Sf9,)%W1E-jm,A;)_f. C";4+a 6(7/)=6];)0#$2DI\kXQ9.{3 e* <%ME E!1<>K N3 |&"4K;@h69Z&6 RA*& uG-zHmC2B$ 9e*4LO( e0תZ-4J 6 a [ c/DB`FB$3<@i,C#!I E~4;3O)U) ;;6[4Uz=T33H!(1+];8ރq6) 3/+%   d d5D3c8,#4հ 4zSI_beҞ-A B&K 3L +~.Co%*|%Tt# ~ )-*F28' "=<T(.9%%0+J>{.Y| ;';%  L$*\!$3j6 3r)) z#P$4R &]b8@:}02Kv[%67+*~)l+$ & Ue!~ Af -5{&)2;+$!c2F$9l+ E%e-4/(W ?3B.)SvF"#1Fg36/޲(/N",,>IZ@;) c'bHەc$>B". 89dN;+q#$7%IC=:"3C^>$gp quIJ  (9 m\ 7L1r+ O 11' },T5$S {B b<D5&Yuh] /C?>!&a3)*&17- Gm  .nED~6f*&6$+ {m >Z#%+a$OkQ.-2'Q 6N #&%\A(  ''4 )&+5  $ v] F>"g9rw A (5a`B" y$4-1-n49a4g7\0MQra32,B(6zw 1)'%4 M4C.s w#57*YI x޴I3,@HEUBnA )S9S0(F 9[E}7M;!^1P\  (B*>-9x41,}gr0Jg-J}  / SG?K ל!9P6&l39/jW G=UyB* nqI'!,HX:mLp &W(8- m I: $a)'" 09/7l+ G2 Z51H) S4f=+s"P";GM 2I<8%)G^ #-b*33[U  4`*Wv4 W($P @ xETO:XGH6הwրTW3}^OL9WF<=GWkZB #q Y  %9O@0Xs3&HK;4"3NՈ@*?NN!? }7ia\E5!G1?5j(`"?#>3z8;:a-.  ,SC ߐ+ 8$UQa4b(xdjFI-7Nd0Q8 CCS0# /*zqY<+GLA$gW-K#<+)6@6*?3!SE]k='*Ё0$>WUN.I5@;oR/BxCO # D{Y23&K.= ,/ - R0s*c"Ni(*3XG7kQM9,m4069]n1 ) Med6yrAa6LF?pX"E~O`m|-p:UJJ=$p9p! AJ9,1/EZ$'WC$S:<![~v+/ZL[4 p ބ0'X?=M9EN6: |i%:PON:&c (',"<'7"1:.- +&! 6%4#z-Z)'m /@'sc D, 3n!!" Oj"><i /$9d "R(olee 7'.Ex$n -%o] e+y5)9 /=^Z+U>$]*I*0$cw) :.3DwR+4-1 $]2?"4+U' - )-g(-  tO[ +*.?.j(W0%9GK5^݇|"5HZ< 0 v*0/(C- _~*'gXH1<'_E߼L&E@epXE3B"{&xBS=EW9 E~22z&2C/jA#9*0 |&&2 %JK3KM(1G+څ'jFJF~G-/o%1t !(BE<1KDK! 1G6&+.vQt B 57=wG T<7+6F5+M-=;?2+'7*sQtE),,s 8+K<.+| {u(-&"$P {(!5NV@C С/f/ Ecf?NwqO > ?B_Z[$kݨB+W%D01?6 ɳQ^KoG 1 ^+H)__/:1$aH,e\'2[2OLqjRH @Mߦ^.>6GX3ݑE|&MC:SCWƀƞGpPEc\J?+~!/eت$&DU99z<62=44$5[@6gXAVsE7v!?_"l?CxjC/ni4J )a#k5Y 5p~U(i#m v,# $ | @%0@GL!r6lOd?gSv5'2--/GPP\'@ n,.%sC m$~g;6'"V! :Lr $J}L+9 k_CGLD'+ #;md!2[+^W'  +%td'*?.F 8 2E&N,E9w8$&8.3% w6'I0K9,2~%< (:t?y9&.`(i..@/_R&&44GGI00;ad0 .6GnJ&L0+#8=&%4C?l#D]4y ZB/"&GN$TYzBB%:^CYʄD4 KMCC$03H.-y ks.L12  g/( $ &$qs%8% +S1 =IzV G?8A(1&9dK &NVDwߣ'le AN12 !}+& "2 #s&|*7 ])mp=3E?b.>[X D'"a$BOF.{ DLb")9k2|po +ABF;Y.5)[.?OL68bIkbQr-3O?*$T0B 9'FJYF.PMw<(RjO:2n 8,se3GM>n ;m-41AJKWd`+dK F9 e);!EU-l(' `'$+9 gr>B [#*}a\F8Wk2@ 33 /;{ /Lw: & /$ K(%?'1?#q jiM  ` '-#;_&]"}&065p^]"#$e8%SQ^ }7a$~$ ^#4r :'S#S2t!u!%%%Mz[[c{_1!)5>,fO4szN:[" X!X/pWP[-;C!X K8*a.'.n5! M3c?5k((?;3dw=\O% !kIzA&=X  : $Z %09"#[9 0 / " #!G30G+04fܫjM` ]T)+2iv~QE31WY5NU-WN;F Q~(HD%  5@27ND1%;2J;r:H>0>)'1KRDY<<;D6$)2 #J1@  Bj Un y"3 NP# %&.0uH DD3(7$4q9C9'8,! 6S3(0-"  B!06'#*E):[9r !S_(ጰ7u {5Zj^i=9OiE?EDZx 19!jR*;T5 '!he(E? t#^o1;: W}` # 'Yj,'$ t ?:sM<DW.Sۻ='JJGz' }:v> m"SAI!Znmfw$%&) |` #)Qgc, kG)^/ |; 6%S2>1.W-4x@C) wq'*"5 4C) g |*VI#OCZA-/ %($ $'   <.&T ,15=-JAL!5+' \^!UV I V& &,H$"c3 `5*t8W255had3['`_k&@  rl P >")N#>-r@K'4!3K =+]P ! ;B> ?(|-/$53 B*> Z;f1 } ~%!$ o  0*o" l !!R86; <\%2]0-H3,-ߡP N4K5"B"=bd $:O>l K ؉718E!8b2i!J>rN; Z4H_%$!<M0j+Q*a'(( )W9M2 ,F(n" ( b m 9Z72* \ m#" Ye:-#+#w &w+ U*hoxOHSI $ "$ ws% R *^[&@&3Iam[D!&s0:0W`@u/ 0 G o AHK=r>#'+/9n > 7!%"v  {Bjq 3 Aa T +v$D\GU-1 I D %%Ze!52`&%4 '1$U`: k9~*]77*o>*, 6 r91=, $,$o& 4Bxb, "#x'($4rG6'' i!+iA*! $['*71+:dEC9y,&uA9L0@$1!+ &-5|$ o iM/6U>5! k};'U"` Sdy J$=2eM%L!P b(BT0 E2.%!Oc Q*,*#EwI Z)'l gE>pwN E2,2 "R] :26 B4oG&E-o2 $ !'t9<(3l|B=P,A~+D{+Ts%47"Jk(Q Au$3 ]x&%>+/Es  ",2+%;#8>+2JlD 8I "a3&F87!/u "/5"9R w6? "KkN Dm713$gO!b.L* <{ >=w*:*&!\mJ9b?0 '&;(A'. =mC2R*#Fgm7 7o 5#@G$.VC&7M16tqP0B2!Kee BM c##s9@ .TS7$]lҍ 46^l0bCz(82'4=Z4P3?1&& Y3K11Ir;'<)S%0o"61 b~ V /+*r)<%%=D%"u/!1HsV$"7,b C*}%m5$2, 4 %5wT)(q)8#dp }^V/c#$>"?*Z?9>qZO^1137b'F= 8 (> $~hd^B'R.9"  ip'kD2B ?ըv3IKG,[ u- hb16F? o[rN 6@ l+ %T N p-p'sE!U[jS;'Y5(-88?"]WSJ%9S "Ӑp75D19SO 5<U .E:i,1&m1?3'Z ` 4# 3~%r9=7& $GQO#!+[;72!d S<6+rRUݤM/"$6f1 1u b<<+f[sg +/ '.j =+ 40  #)1&),Wl?{J 6X.jo  v/EB6!RR4E6#n,3jj@c#$/!U 'GL%45%`B ##>&/V=U%@2 u d o3*t45=`H .t))0C4,Rr:rA ")0a)vu!QVH!X4984+$&u'1E=.0!SH K5,&G ;j-R!% z#7c.)1 +*^+k/ &! {^ F % j v uK#| /  U$ ,<6'?9o'7#C/ZQH"|.' $$u s$/ , ^ 6):&  3%= t 5uD(. &( i#z 40{ ` N00  :1 '6:#,/g#AG)&33 a"$/>$.h,E.3&!9$DH+! } AJ3oa *"H)4o`^  C?YE!Y&sK c]^ P>=,$SS { C y%7r(R%&%0> Yn9$"I;0 y;p3??%'*|<gV9E/SLB/+*z<m#/m (+ll"&VN@"0&0-)w?Fv?9*?-I'Nbݥ5G5u2\-nE2 #%HAlE- q%%+?!:>*vQ׷6m)'2%i78Z Qlpү̷̤4юR $+9DyP\f`t|<.yvUwtpMiZJ@2# VQr~,*jpF (- ]T+rϯqvIۣI%8c{0qV4kp݁edɵZ(ނo!0;CH7JG@4" 7rHٜǨ41`uzrX7%߶*Ƹٵ۶}%.)Rl|x$iS:h!I p?'Бc4Iy&09@EHIHE?6*yvٍ0v1Յa³uؙ*N^j{z&k V=$ :OȽ [_\yocTC2!tK1ܦҤ߾(yεWxxT{ʵН!'=  v"*06,<@RDGI J J9IJG?D@:3:+R!h.+E/3/GI7NKbZqM{zqDeVuF5$r9ŝzڵﻧ4čɩw2n"LCr;2u* " T>܍bĞ- 9ruGX׶ qvŻǎʏͼԜH9t{B =op"1&)C-036V9;:>^@NBDEFGHcIIIIII.HGED%B?R=l:,73/C+&j! -ҭ2ӸǨKMAesgʖ sTqs.)h6BMOXaipdvz}9K}HzDvXqk.e'^VNF(>5,-$gC[ dg/e$̑YyȻ&ϸf =M+Cֽ˿PƠɓ̳yݿ_v17!9%}44"V;qg.Գ{փ`- ͑^5º̔ 2_4}$m'' 6%0/;o>$bcA ۏo2۝W J ӵY+  a*,A1 2)].*;w E(L#%Cyu 2ԨaL^ TߺL3 f1$7!59'(%=D}44"V2>;6qgT.iٳ{䐃``x|- ͑R^5ºժ̬F ]2{_ 4{B}$I  m'RG' ~"6h9%"!0 /(51;{,o=>5$Xb7cA|  v_׏o2SŞWwo J ӵY׷+ ŵDl b a2ff)1-$7!*5;92'uR (q %83=DE}44""$Vt.2>D;(6qgT .G i+Uf س{iQ``x. |H7 - t7͑R^5ipºªժ#ܬvF% 3 ]f2{/_ 4B{B]6}$mI  Im%$T a'%@RGL<' ~"x-6;h990%"!)0q. _7i"/h0( %519;(6{,t! :o )=wG>$55$Xa b "&7 CcAQ2|8 _ (v'ؼ_/! Goݝ2ۓSxŞ W/wX 8o0_!Z).10-?)$ .!u%*059;;9u62,y'!m+ P &n (*3=)DUGEG?n4T&<n9\A P"$" U  D%g. 7 >BDA;(3{(0grtWg4R X * D j0ߔ+bΨ:ʱֻe(-h  ,ݫټڼ7ŀnurdmGx-@  zM#)H94 * rG=ޒ/چǀ<4wUe=ߊ8;w"Kg o#6ֺڪʨ7̍}I$  ,/ [ e2u{0`T |)4<BDgB=M6-s$gF vf @Cd"$~"P g'5@F=GC;<32'4  St"'k-x26:;;X9c5+0h*% ! %)-0 1c.( Z7=_")/1Z0@-(2$| !%l+'1669;;961n,&j!i  48 i)$4=DcG`Ex>A3$/W71#$a"Rh^ [aC zP&8/7>BCPA1;C2w'FN`|]81 } $ @,y,Ob;ѝm7;/`g>+BьIvҤ/9b#d?ա ?V+k0Y){-^ g ՟L6+\jlBVC %}Fr @{,/y,&Ob6;ѝƯSm7d';N/`qgWR>*+tHIBߌIhviҪؤH/&9Mbt#dPR?+աÐ Lx?1$V+n/k0e"C Y )r0{-(^ mg p_ۤȼ՟;♖L1"6I+\]mjlWB)/V V-C %s}PFr @{%,0/.y,)&"O\ bk&;6;G,FѝƯmSXm 7Yd'R1;DNHW/`hqVrg\WRH>V4*!>+t@HIJBsiGIhvipۤ ҋժ^ܤ{H /&/9pCMWb[mtl#d][PR I?5+! Isͪա'Ð& ƠL(mx? 1 $g(V+-n/0k0&)e"8C } Y ")0r0A/{-+($^ mg p7_+ҤѾd՟;{♚ԴL1g|[v.*ip;Xސ,ؤ b\0ԋ תt^mޤ{wH  /o0"&L+/49y>pCHMRW;]bg[mrtplmh#d_][VPRM ILD?:50+&! aI3Ds]ͪDաp'Ð& qƠLBҚ(ܚ=m|xS? '1! "$&g()V+,-.n/ 000k0,&)%e"<8XaC K } Y9KXU6 |"A&)W-00r0/A/t.{-_,+)(a&$"^ m g p~7_+uͤѾԹd'՟;m{72ԴL1Ig~ "',16;[@&EIN!SW+\`d2i]msqzuqjlgaX\WQLGB=83)/{*%i!zXV iVF-2U<UػC3ЪE ϜAUj[%HniysJx}Y=Q=!tBXԦ!gi% Sԫ)ؚڌܣ6T#iEt a p("'+d0+5:>CHN6StX]cmhmLsBӪIo"o=Χ^ (a^d]S3^pc3ȀR/"sΊtm=v6VhgúbWѾ?[߭Afbw9~)&TM[jwV% 4хWlcRP}|Mjd׏#2DmG} K=k BC?˔jN\Biv&̩#:ɿ{BnH·dZaӪوmIYꟿo0o`Ebg:v,"DkoKegt/ϟHz=*A"`Χ`O^G% 컟n,2(I6a^*x}6rdgbzrԀ]`qbbS#/|P=35Ŷ^p;(#<c3@{R$Д0Ȕ߾l)܍tR{{ُBюn_H#ṙd×ZaӪ2rو]|m\I3Yӟo0os`DEb3g:cFv, "yDߒhk yohKedgPltƁw/"Hz^=G$*(A"`rΧ`2OM^?G%5ތ \컺n6䀘,װ2M(I?"6Ka^m*x<}}x6r kdoagbizrF]`qe dV޹ۥWDө?Ѵяsf^^VK;Уj"mΆs$g˚ʾƫu)L=L68F ǟOlχ${thl"d?[QG=11& 1qT߭=}$1x숍}yDrwi^QA-ͤS?d}ӮCHI$fb ڱn;/@ƖǥJ֎P~wranj9hRfeddebgilpu{s*$d'P-bB_~99hSqexs|TӎD%w)* * ((%! &Ɠ@Ǖ<q\E.Q.ںp~l$[mK=/#( pcס\]ųaaR/RQ-J)/fb( {B_UA$o.8ZCTMV_gntx{b}}|{^x'uqm[jgbdsb\ahabejDr|&9`[ wVȆ)JcSPɵ'(Kf!|%К=g!ͪбDl>ھݖO[/5eX ۀٞaռҡz¬وm&B1rՇYVlxZMB:2,2'="ly Z [ P;t݂u٭/=ӥ5ѩцqkc]UE4ТdadR˅ʨDzƎVã.ôWʯ9lP<)zVmXA*.䶩zh(XHz:-- L ֱ̥ȯҽAϳp#D̬sO{Һ- T?8J%S0:E+OXa7ioguy9|}}|zwytpLmifc!b;aa9cfls~HݽSg⮿j`UIXʴДf$0>Qk TD5Դ؈" ޲!TUߴ;#+9=/˘JDZݦ>g]7H-U>_fShWK@81+M&m! k  ikg[u*r0i=Z`nQӚUb%\>0*()B*/d$:630.1-+*)R)(()(()B*E+,./H2 5c8d<>A3GNWbcr8(I2u((()Z))*u**+',,-z.m/r012845h7;95;i=?BrEH`LsPTZ_zfnv-`܏S,l8jUsd`?8Oaok|̗۩n˹ӼER0jCXP*ұU_7| ׆SZ*֨i֜#աQӖҿϯs̚6?ÿ!<"{U}uBhWCr*H beOMtk9B% Fͦ;=N{qib\DW~R3N^JFC A><<$:;8643,20/..1-|,+9+*J*))R))((()-((((;)))B**E++,B-../1H23 56c8G:d<>>AD3GJNRW3]bcjr-|((()-)L)n))))*O***+D+++0,,,L--..//00!11Q223_45567s8d9b:j;<=>@`AB7DE\GIJLNPSpUWZo]x`c%gjnsw|‡ Zyb@D1}Ois؟{B}zHq½t=vV"Z+˙-:FP!Zb?j#qwp}܂ԇwŐƔq1:ʧS Ķzvɿ ?dt~|nV3ʈ@̖8bk`Cҫ cӻ]ԭ4iբ?h֏ֱ*9GؚA3# ֯~W+՚_ԍ@ӡF~ҥ0Ѽ;д#ϏPͬ>zʫº6xe$0*;ۣҝwߖy}zt0nf^&VaLA5'XrľouSC|I?皯4˓\o"=<:99837\6544[322x10S0/K/.b.-- -,f,,+{+4+**o*8**)))^)>)!)))(((((G((((((()0)@)d))))*U***+K+++>,,,N--#..//00)11c2 33q4*55678}9{:;<=>/@AB\DE}G2IKLN QISU)XZ]`dg)k)ovsx}y 9Y4!D)Oo$4!,Ѕ.ބAZC9Aϱ @/ǝK]ծ8Vq-?ȠKlG6 %؈ ,7 H kϨ?-b-3μ.NL +Z% Yh. &]6c5ǘ')E+>b%F•Z*/F!gaڄGE͈5\;5#$u1$Uj 9g& [/.ק0b|A?{4{Iz'%@%ܞ! "k͂~8enu2j!|=F VS> Q7 B+{ / X," *$ئ1(!H`Bi ]>SH.'V!:ZwD̻ 4 (;M\m(߳A9481;r %[8nӦ3b>cJ۪h zI0PfK6$'r`iZH<pHX> {Kc.zO:Я3  yM1zlwwS%#Qt ;/!¡b,S;62IGeV  H/}"ܰZ+$qQ }Q%_ *\!"1v'eI]. IdsN/ȊO=@. T?., *2!4zֲA^Q"tM fƸ!uvԄ\Gگm j,By9GʘtM>`n*%0A6qps]: ##'):b\-} )u76vR/ B?DWD^'+ƅ,QX wB[M:ҩQ,8;':_Yd'\{R+3UM!xL%]%("[|X+"5=YDZ4G`ZIuB #+^$zТ#7 -C(V*umߤ Rhx=V.vq\;R@ʯ!A[)C =%ޕ(-0 Uj- / s޶'f/z 2 j5~;"bf)~ PL mx =؍#V c@& 7X>y!mF]Bs:] " c"%Ȟ=zL J7 m_>3&"6<'u\l6M˱&0K'r_3O $=!6=_ CI )Gb*lC@K*5c;*,\U8;Nw#Yrxމ{ ) 03Ï+ .H '*fN!<ߤ F  _JMW b".,3D IIB,D/" wj*:`r R 8@i4'ԑ3ʲ :e s|E빽bP)DTa<Oq sv`Ю'}+#ƪ<;U(g%Mr0rٖ'~w" JΐA%j=.X 09)1q&L7:Q3U%}8Xܱ: !敭W !S!9 4+E1~!: gͰ"/M30^Đ/ $^y>;y+ o%1|ۻ e#;lپH&LZ= ) eK%f_rw\UO1Ci#nN)I7.; uBܭ54~5%(v  (tg$ ,љ 9VO WzL[i - 1̞+ A)a"4 $#[4G6K_F$  ݺ5B2%/BNkjӭ#@&RP 2ܬ0FJ#,޺l>ށ } !"@% Z)(MHVjŔ #&h#$fO1&&( M1R-`&j2ݹ!['?>,d9}.Fj 5] @)l!eTbm F& 9"ڝ,^3& "S F$UM [ 70(5:K1/vّ(տE ,~ˍ7:;J*hʷZ+gT ]#qEɏ <-84ZȴX mq1}x& .b,&+f,B1%* PzB~'ǘ#z&?  !+!4512cbefx wYB(L{_PT'ׁ)"24C˶pL\O\"^B!R&?_ [%)c#gI 2c+JNpޭ/' tD.!ȱP5vg6#r$LĺUXe;!8k' Q6+m1`ά0TlC*%A\2&>Uj/$*)u76*F-m@ʲ2:"ϨLA36yɨwg~Tҵn~,$NED Ip{1f)(U7& '%L&87d#D UA3<_ Ul( s6߹| L/(`Bn%2$ 4C.yl* G ݪ"Jf_T6F8(`κ9<'W9W',%x$\l0#0`nZ&83)t V&5 .ثd?L .8d߱C8έ+s-{ #-KEk1R/D`F<#%\S 5h ŗF?[ )v/gbś#?D$D. &,SQ ś$q$.'-(N7N)'*/$]lG.%PKе*N!K9(;U :Z$]s# iTA*@Yڥ6!_^)+q韜A1×j!mDpp8E+2>FPC@ܭ + a}4)>rW ǭL ӯ)lԹ&@T(5eBq ..)mG=3 ށq%8I;5- 8By^.u&Jm&ʁ #M ]S1< r R!W%L֢=.& eh1/ T52(5ښ5X<&!1B]Z@ *5p8vbZ]/ 9lGe{ >ó  \9 @^'13үRF1"\I)F&*;+ش+o3Ġ=JGf/  d?hӟD_:yD v Ƕ!k;3caaw c3y6„8!2,1WhA{=qJc#1-A߬(\)lkRT/:3> 0 6=O1#edu h<= Ě!A!e=ӳE raE`u"7 I i?n B5,)>IOO3̶>i9A+lFO }ccd( ~d!*u 3=TA~ aP!, :m G"C{NޥI1,ѼSS,c,1o͞'4v8HB!! Q?hFd$k MFIb !轵2/ 7 k[Ͽeպ(!-!Uj [a NXTy Nx).ifn 2'?kOS3hD 1F(G "C.m#Qi 1I8K7 R(g6 ,<´!w?Ҏ"53@NРg+JuLooYan< 9(vEDgP,k&ּJ+rUk:T=}4 zH9MI/7&g L'7"˯/D9ڈ5h3NF#}]i! MZj $AT V ::K(c .JG"q &2"-.5PҸ7#8 EhL ЏV &'BP}6(s"-ҤP+0ټ|3r2YW^5a(;l2INK6ň1?B_&] m~?<LOB*C\ї-y 5>β $] 25"O@e#z( ^z &EJ^Φ> yt!̘$PL+JkA]?`PSEҠA 9]rfv"/tS14 la0pPb& w9&q9;p?ƭVK{97.8E0ȊIZ4}H CZCBvD 3MA.ͪ-{WdM<g 2dZ mϙTd/[-*#6 ~:8@H/'s CC)}+%Q -7V)e .cEBĻLUN;I&4I_zXGP*/o1>J)N&e Ҵ;m)8߀$B_&ge _3r ) +%+5J kFQ䤌'DV`8ͳ&* R,3MJ# 0:?&1&8yS?>L8 m Vİ<s#! ~G@X/ҩ-\Q0A̸ Q I .+h_W0v'ؤ1d%ͷ@Ρ+Η#2=Ii;r*:ܽ' @%5t9?PO͝Xx$5[IS%ΉKw_3%u" 61i^:5g͂FD۱6L=o^ |rE=Į=Aw4w^#y }A&?02ZjH O.!_f - @lNg]22?t"d),Tٓ aPZ a<7M69|)ޠ0' Yu0c9N/^3I,&=04$ <)[ڶ?цY4D *E _(2?=GQ[f_pz?{pf\_RH=3)? aAׁa!A!aA!a?_'1AC?FH_KMPSUXZ?]__bdgiloq?tv_y{~~_|y?wt?romjg_eb?`]?[XVSP_NK_IF?DA?<974_2/?-*(%" _?  _A?_ _ ?_??!_"#$?&'(*_+,-?/012?4568_9:;?=>?A_BCDFGHI?KLMO_PQR?TUVW_YZ[]^_`?bcdf_ghi?klmn_pqrt_uvw?yz{}_~}|?{yx_wvts?rpon?mkj_ihfe?dba_`_]\_[YXW?VTS_RQON?MKJIHFE_DBA@??=<_;:87?64321/._-,*)?(&%_$#! ?_?_  ??a!Aa!aAa!A!ܡaׁAԡa!΁AɁ!ša!Aa!ᷡA!ᮡa!Aa!ᠡAAᗡa!ᒁAa!ቡaAa!A!aAa!A!aAa!aA!aAAš!aA΁!aӡ!ׁAܡ!aA!a!A!aA_?  _?__?!"#%_&'(?*+,-?/013_456?89:<_=>?ABCD?FGHJ_KLM?OPQS_TUVXYZ[?]^_a_bcd?fghi_klmopqr?tuvx_yz{?}~~}_|{yx?wuts?rpo_nmkj?igf_edba?`^]\?[YX_WVTS?RPO_NMKJ_IGFE?DBA_@?=/ HRéR1ΌZ+΅Q-q4o %-;IV-ajryB}-~|ztamLdYXM?1"DWJ肃K86͋@FY }+?:HT`irax|jv~zucnbeZNAT36$g'.^6_=ODJYQfW]qbcgkosvy{}~}|yGwtpslgc]BXARKNEg>A7/V(  'HцR[iR12qΌb2Z+Ԁ΅qxQ-%q๾4ϼoC %%-4;BIOV[-a8fjor-vy\{B}~B-~||zwtWqamhLd_YSXMF?911*" 9ۗ"D˰ΤWBk4Jۄs ?K5u8`ꥨg6GƓՋW@=FQ u#g'+.2^69_=@ODGJHNYQXTfW:Z]_qbdcgikmoqsTuv?xyz{|}V~~a}~}}|{yxGwutTrp|nslPjgec`][BX:UAROKHNEAg>:A73/:,V($   'HLٟ݉LΆR[i@RUŠ12疛qΌvbmӁ2Z +rԀ]Ȃ΅q才<@xQA- %-q\\ǘ4Ҽyo=C (2 !%s)%-04Q8;n?B FILOSVX[^-ac8fhjmoprt-vwyAz\{e|B} ~~1B~-~k}|{|z9ywgvt2sWqvoamJkhfLda_K\YVSPXM.JFzC?<9M51-1*G&"  !u#%g'D)+,.02h4^6$89;_=B?@BOD FGaIJLHNOYQRXTUfWX:Z[]q^_ aqbcdfcghijklmnopqrsstTuvvw?xxy2zzN{{Y||8}}~V~~~,a}G~~-~}u}}||{{zyIyxwGwzvuttHsTrnqpo|n{mslekPjigfekdca`;_]m\[YBXV:USARPOMKWJHFNECA@g><:9A7z531/.:,a*V(w&$"    '1H1Liۉ٪ןLP̆ʿRÙ3[ni@ƪRU񤓣:Šx1`2 疰q~ΌH[vbm򂋂+Ӂ{2򀹀Z9 +LrԀ]ZȂ4"<΅hq(扬a<.@YxQ?Aƥ- %-q=๱\ u\Ǣɘd4Լ֙yڋoU=&C0$ ' ( 2 ' #!#%'s)N+%-)/024^6Q8:;=n? ABzD FGI#KLoNOQS}TVWX^Z[=]^_-abce8fcghijkmno ppqrstTu-vvwPxyyAzz\{{e||B}} ~^~~~1fxB~~~-~}k}}||{ {|zy9yxw4wgvut t2s=rWqjpvo|namXlJk4jhgfeLdbag`_]K\ZYXVUSRPNXMK.JiHF#EzCA?;><:97M531/- ,1*V(G&g$"  / O_#(-3/8?=_BGLQV[`f?k_pouzф!AQq1Qqс֡1Aa #?(_-27?A_BCD/FGHJOKLMOoPQR?TUVX_YZ[/]^_`Obcdfoghi?klmo_pqr/touvwOyz{}_~AфQ1qAђ!aAQ1qAѩ!a1Q!qAaá1ǁQ̑!qѱAՁaڡ1qQ!aAQ1qA!a?O / o ?_/O !#o$%&?()*,_-.//1234O678:o;<=??@AC_DEF/HoIJKOMNOQ_RST?VWXZO[\]/_o`ab?defh_ijk/mnoqOrst/vowxy?{|}a?/o_? / o  _ O/_O?oO? /!!o"##_$$%O&&'/(()**_++,O--.?//011o233O445?667/88o9::_;;/??o@AA_BBCODDE/FFGHH_IJJOKKL?MMNOOoPQQORRS?TTU/VVoWXX_YYZ?[[\/]]^___``aObbc?ddeffoghhOiij?kkl/mmonoo_ppq?rrs/ttouvv_wwxOyyz/{{|}}_~Q񀡁Aႁ!фqQAቑ1ыqaA񐑑1ђ!aQ񗑘Aᙁ!qQ񞡟A᠁1ѢqaA񧑨1ѩq!aQ񮑯1ᰁ!aQ񵑶A᷁!ѹqQAᾑ1qaġAő1ǁ!a˱Q̑A΁!qұQӡAՁ1qٱaۡAܑ1q!aQ1!aQA!qQA1qaA1!aO?oO ?  / o _?/o_O/_O ?!!"##o$%%O&&'?(()/**o+,,_--.?//0/11233_445O667?889::o;<???@/AAoBCC_DDE?FFG/HHoIJJ_KKLOMMN/OOPQQ_RSSOTTU?VVWXXoYZZO[[\?]]^/__o`aa_bbc?dde/ffghh_iijOkkl/mmnooopqqOrrs?ttu/vvowxx_yyz?{{|/}}o~Db~T!yE`Pr-A%a@OR..nSasgxh"/ =Ȥώ5Ii#ج، εN &Ya[%@_T0$r-#A%T0a@"NOR H. .wVnqSa>Bsgx_h"&/ 2=Ȥ2ώl5ǩ}>II?i#|֯8ض܌ ε:ݨN Ǖہ.&*Ywyta[ >%x+@LT__TB0?%$%-H5h-v'#4#9%)I08S@GNQ=ROG=. ]'%Ƚ.DdVdmnrqk=aS0B0 xkht`7 cWn,8&'+#1B^lܤ~J4+Bǃݬ: B1o>FIF;?3a#?ɋ 'Dz7ܽvڕ~pŪ-y,޵kFkU:ξgޕ d1 =)N*CY kzw`~P|_thM[L=0%k"n+5@ K9TX[_Ga_O[TLBU90)7%2##h'-q47N1t+J&!^4ZcJa"i&*/,5:@aEJdOSW[]_`;a`_][sXTPqLGC]>9B51c-#*r'Z%#6#/##4%.'),C0480DG@KN9PQbR3RQOKGB=O6.&| 7ۻU[ e/,rS v#.9CMZV^d/j`n?qrrqfokg2axZRJ(B+9/& M(zX'm5iGV r%]Od4eD }eZq/#+;)' * > 0Eyc0vXBߓX NA̛^4H9҇9 )<1j8h>.CFHzIHFC3?93+]#=8{؎e“ðȮŭe3 b.KJӅ;{[ؚN҄Y8x&ӮϪ?8ϡ ƞA7rLˇpIX>#ΰ%rtɊ/_ r4>Eʷ;*>L(*7C7OYbkqkw{Q~~A|xQtnhJbB[SL!E=70*%!,h~B"&i+_05;@EKO/T XM[]_`;a`_]D[#XTrP LjGB=O940-)9'3%#.#9##Z%d')-0k4u8<@D?HKFNdPQfR%RPNKGxB|<5.% S%M kIIm Gܕ5#[#=;\r*Ҝ8;3p }zX$2K8eq!Ѽ#SU.w#^{ h<1!p> V[L,D~)A$C=7Eg8~ h -6sb&Z!*.'0T,iow?Ū"<\@4,+T(wTԕAlt a,f> : A  l"+xR2_w#;E.#cv1b/66ZmS/r߅%;_֤ |*GUq pdiv 6  54s؇Q%lus/G 0 }+ *\8un޸,o$/ Ԡ$ &Z6׾'LO1_Tc7 6;N= -fC~ _'lf9E-!t0c/A) pn S<`|BO2[J >c!GõtL/3G:- [Ǵ8&H,cۍ "X$ lmU!P00. ӱIl(-(Ij v \$^,n1;@+Ϝz:x_Я1!2+b43DT$mJ=Zƞ ,j &ڣM/ϺډM8;-v6}* +oH[g@c;Ox8#>)5 /Fs";pwC`l?;_) |Qś/q E`Lo|<ԯ(Aq9v BpN +rN'8 "Y!D42x( lv/vH@&;_o@A t4r"]("'e!шH׏=ӺZ6=Dخ"=$ ^.6gP gr.ac&8]ݴ  ,]Nh֒*c1@|e5J4UUF }-''@.jjT v :* ,VNc' *ڀ(uAr ^&! :)!c@] %ф$K:]m,+wӈ$0}*!_U@?}-e 3d7hb5SH9Q#c>YݑY)$U\1-jP"iA+#s3} ZF86j׻C>&GG %-62H$nX 0 y:q%1 b5M3i$$_ .=8bGD .O9`{A _-&r#xX, ^.?D0Z(ܼ# j;$/S/(R00xе 17L1ӕGk4P ا'OOpm#8oW;d+ AK`]ɶ. j 3H~wz|a1C7Э6g&R!,@h:c dmZ([?a1;8$*i ]ЃK܅%П#ňH"5H'}ݔ`-u_zg O')Z2ׁt90m/a *W WԐ< R*i ϗ#|0  1g=!E9$L /'U֟E 43~"1K0gT% #\M 2%5ť-ԤL̯  5Ƨ;ά$;''7HST uUђ"y1c:NRqH *9  h$Jn>x8> 'Z?Q" q +T J.1:oqA XĠ@p"OQ'0!@4F^&}>? Z[G&|$ A "?]5^x*&0$FVZ*ly-r]Z=1PQ!JdU% Dȝ9x L$aABS#/X O6Cd we/ !#%pr`LK.{R6DUVA7"+ /  919?/$Jlz?$!6B}=*@8Xq%FQ|,?,9JGq >* 5Z 1Z*EsgX033 Teݏ$XW!>q"9" 2&a]>6~:2v=F[3vΑ&\ܼ&P԰HB N6!&m&\ v B:o=%:4(QO C &E.;&&{4F_0"'k 4 fmQ= % h{zu)󧷟G%P:.FˀDt J F-+eDb"(gx<5?}+R07 -* / 0qq?"EAa60NeKjNJze$ڭTB6b] * W&?rh@L !J"HL7/E'ϻI1 "6+)" _ҩ3w? Y1\LYP,#߬(?X<eC Tڀ P;2|.Aq5ض!4->;k @-,'[.*3 'LMY$Ԙ, sT+0C"-=(-a7I`@m.( }*D):u-%6Hj&w?4Įn2b14 52%]n49_W-Z6"J\li!D#ت  A7Kl bFκ* .k\j2I f;#* -(4 `* ;̇#jFIy18w~?~.E%E*3'Y 'TY8,ۄ$H$m//uQ)RcGW"! ' }9\ Q,wb.3`) | .W~@z Uͭ FujASN&e}AGN+<-y \Z6Ra7 ^*R |fR"_ j27J*mѸ<r-VпIX,I0,g:VNE)(AKcxYf"ˉU$ )o7NYft7'P6I,;;&4\?FկC;En!!Jw){+A ׎0Zݞ&tXͪ"< y-G1QR6ڞj]'e( z={ * 4Ņ/,B@ E iN'i"oo!K+<:%T ")SGϋ#-*W.5j޵qYR4šNnC+ ޛf:<٥4I|qRz+@a {>k95V*_)Ͱ$ ,Hv&iX."KgW&O8JϾ": 0I9۴Ag"CpL a ?LO9ėMg̩IPڷC)&2 ,ۃ+ y" ,ԛ/ O"{҈4`D4`5@p.-+D";-;`;+cV)%8W}uD5)ZI b[DproaƠ\ a L 0G;Xȩ1;z wk&1 n W ,Kt !< O+^*ֵC,1? C5c?qޜ %WqAQx4Y # Q:/#4Eomk ԿW@&+4Xg v.1!Kv"T1C#R8MP.u)zˆ`x([MC+5TV,(`,$9!]"7{'z/TV &eR"Ʋ2eމ) c/3G>ƥ~VH1[Oa.[>%Rԯ?0?UjB("9 -CN!WCv~5VR:(9Bҵ.e  sxP"C#a`! %Xbߩ*w)*6 ISֱ9 vE j4>^&VFuA)'2> W P| sN0G+~Ij(b(A0Iݓ G#[J !U< f y)Gq@ <C߽A Rj6Զ"cJcgH8/8 m=V΁Uih7T42=0f1  ]]~> ǁ-: 9;˕`8]?^AХlIVCr"*1)A i fBR:m{ )%1=v2EwUQ 7I[~2wJ:͸%@C//(>2 k j>e3ArNܻ*7)Oå5`c  ]-C8Ԍ@m#}9v" &L8T[;ߖ2 F*K@$M)R:⡑][0,/ V2$L?HAJ28#,& $125zq1d|5]ņ!Dӌ@Xx?˒O<0tG ,7#( MM%[ߡ)k!ER^x0m% IqIJUH4`> TѺ)1'6X@$!b6T7{- ݀݊.*Uoh{)e +N&+ "( r!| @M# /)>ū!X :Ry"@]n^;@\Dؿ%l1F;ڭ!t +t _a"w#;ʌ O 1 +?qn}L-n:w#_r>KӶ0I~lVN&Ên363U51 h@7П;XO !a!<%=0YEtDt(Zw#d+(M3c8'@W|\ +p$lBb !Ugtk-WHrX.$;A4<0i)z*ڛC9E<(Qݽ19G!8T^NJ`i]!R"JΣGQ5%ϳ32$7j!A9-w)nt'?֛Czq5,۝ZqO Jޱ6w H.~A5Ef`$yW05nd$/;W+E9lr$* 9(W Z֒* lR zC?C U#?DA1$ֶ.4̿"֠TyM mK#qn7W#nJ"1w 9#! oE:2E<%0 ϦQV^ [|{[)2Kѝ2̃]Ϊ}5#Vuym~ a(892^70-W<4X F}01<2耄GԵUz}`/h5޼Cz#@Qve|/D O*;6"\ u N.;5e[×̽Oxpe5 ׂęJܨF/L(u29;8/ 0YK5vy1 ?"[o|>{n~[Cj)@EѦy^֭PU ٢P4 #=%Vjx}1s`F'aóN߃h(-,7<:4*J CA7"%:0(8;*:2$ioaĀ( 9_Uky} rG`MI/`Qߝ'/ {"L(-u2d69^;;A;8F5/) G 0BYKы5v[yۡ.1 j0?CN"[8fov|~>{une~[PC6j)@6E،˦&Œy^ۭBP'UH {Ӣ΃xP74| s#g0=MJ%V`jrx;}}y1sj`eTF7' a򴳧NU܀߃Ј!^hUƛ(-Tt%,27w:<;: 84,0*q%J KC%'Apq 7d"%=+:04(8:;;*:72K,$*5߁iŇoA"aĀD(Ą -P[/; '{"%K(*-/u24c689:];;;;@;E:8O7E52/,)% F  0B)Z$Lšݗҋ̃܀7zԊx] .Mz÷˺R;.1 ~(i0K8?AGBNT [`6f%ko}svy|}~)}<{xuernZje`}[UPIC4=6)0j)"ba&@6FܱԌx˔ɧ^&ǓPz̨ш^ޭ_B P\'!UHu4H~ P|Тx̃ʗ@yHQ(7 5ݨ} sE{#)f07= DLJoP#V[`ejnruxL{9}~}{yv0s5oje`ZdTMF]?70'@6U "a%PAقVs3ހ҈#`:iſV̜P)0- S#!t%l),.02q57!9v:l;<1<;r;:98g64r2+0-*<(p%g"k J #KC %'A0p`q 7d%!"%(<+-90246'8}9:r;;;;,;):87 52/J,($t  *%54قiネ`qBC$}cƀ/ 3F)ێؗ 1|̅ԍw==> ?JovQu ;uw?" ;NoN W' ?'JAeohvQ3u n;w\uw`? " g'0;N0fojgN<, "$WK4' 1?L'9JYAelonhI^vQB3%u  .n+;hLw\ju}*~wm`IP?G/ " g, !'0O?;N[0f3momjg\N><,Y "$1W95<,+#Y o"VIp ${+1r7W6.&{ W y |9"u)0_8@GNU\af~jgm:oo`omjf b(\yUMEI=p4q+w"' 'roiWBk" Dmp% ,E27<@CEFFEB>3:4-e&T t3 '߬xʊżzSĒ^ϽՈ=' \%-56 D{3WBI5UőoC@Lz u§.n?}h,e"SnN^FMMɗzq샀7([S{^٤̬d͝ܗVee} ˀnP'ՓKLISTpdtaphdrVSpace FlutePX000 wWonderland Xylo0Y~DxGated Screamer-0[}xNew Age Organ |y STR-808 Drumset 2o F |{Electronic Drumset Kraftwerk DrumsetT)Analog Drumseta( S:v := TR-909 Drumset*RkN0TR-808 Drumset c~CR-78 Drumset* OU} 0TR-101 DrumsetebGpy0 TX81Z Sqncr Bass 2{Filtered StackaserzWoody Bassa000yDelicate MarimbaxDelicate BellsX awWind Down0- a000vWarbling Bird 2auU* UWarbling Bird 18atejiWjLaser Pops]000sYWind Blast]000rr2Warble a000000 q8uSinging Strings0 apuDistorted Lead-aouDHeavy Squarebbnu(Cheap Synth000mu$Polysynth 2tVclPing-PongOrgan ckVenus Violin Hitj Polysynth HitXiMono Analog BassilthCheesy Pad2TfNew Life 2a gMonster Strings beStab Bass StringsddKiller BassxPer Qc ThunderHcx **b#Zippy Bassa000a'Yazoo Bass Hit `+Yazoo Zips`_/Oingo-Boingo ( b^3Sawtooth Hit*** _]7Warehouse Percussion\;Breath Padingsa[>Dreamy Pad JZATechno Bells\ \YDRough StringssXIAwesome StringsWLSustained Harp VPBonky Organne ^USHouse Organ TWNoisy PopsPopsp oS\Ethnic Bowkm^R_Panned StacksX_Q`Monster Stack LeadfPcAcid Bass 2 c 4OhEcho Pop Bass0 plNkAcid Sub Bass* 8cMoTX81Z Sqncr Bass **LsYazoo Blipsa000-KvWarble PadlsTu _JySpudge BassPe000I|Undulating Pad dHBanshee Pader QGLong Bassc Bass FNew Lifeute ] ]EXylophoneZ000DClick PopsxexCBlistering Bells BWahodxATwips Ring ]@Melodic Vibratoh ?Screaming Wavepadi>Stacked Mega-Phaser=Pop Bass 2 hL<Pop Bass 1 ULWavepadavepad(b;Polysynth Warp i:Rubber BassTL9China Voicess d8FM Clang L7Phasing Choir* P X6Singing Bells FT5Harsh FM Bassyn d4Gated FM BasskHk3Dream Hit @ mL2FM Bass Hit mL1Faerie Chorale (k0Metallic ClinkexV/Oink Grind*hVL.Wailing HitWL-Casio VL-1 Popsj,Square Pop FluteX"Lead Synth 3ii+Lead Synth 2* j*Lead Synth 1 @j)Aluminium PlateHT(Space Warpi* t'Sine Bongos* X&Sine WhistleiXi%Vatican BelltteW$Phasing Strings`#Square FluteS iWater Triangleer!Cosmic Vibraphonei Bass Dragon ChoirVatican PipesTTFantasyPULlElectric Slap BassBingo BellsUL D50-ish Bellseq i Breezy Calliope hChoraleHSpULDistorted Sweep BassTX81Z Lately BassFFM Electric Bass** Smooth Strings 1tch Smooth Flute W#Triangle Simple V&Triangle Dream Flute)Thin Sawtootheh,Classic FM Bassh .Church Organ 1Meat Grinder X 4FM Christmas Bells 7Hard Grunge Bass j :Resonating Padg Pad=Sheet Bass XL@Dragon SweepXVCEl Cheapo OrganhkFDetuned Sawsum WIGrungy Ramp BassuatMSimple Square FlootPFM Carillionx iRFM Bells 1e me UEOPXpbagd !$&:;GKPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~     !')3=GQ]hjlz|~ %')+:<ACKMPS[\lnq$(,/ADHKWZ\_ruvy#'035ADGHRUWbglq| -03<DJPZ[_imt#&).89CP]gjlnq  !#./=>@FUYhios{}  %(*47<DFHPRT_aqtv}    & 0 5 8 : A D F P R U _ b d m p r w x      & ( * 2 3 @ C E R U W pmod W pgen`)<3&n$%% `,%#"r!)3 n%$ &  x#n"n!n8)) 3" L#!<P ,(&)"n3n$ &2 #n!n  ) 4))))))))))))) ) ) ) ) ))))))#)")!) )))))))4)3)2)1)0)/).)-),)+)*)))()')&)%)$)e)d)c)b)a)`)_)^)])\)[)Z)Y)X)W)V)U)T)S)R)Q)P)O)N)M)L)K)J)I)H)G)F)E)D)C)B)A)@)?)>)=)<);):)9)8)7)6)5)x)w)v)u)t)s)r)q)p)o)n)m)l)k)j)i)h)g)f)))))))))))))))))))))))~)})|){)z)y)))))))))))))))))))))))))))))))))))))))))))))))))))))))))!%$` # "& L#"!) )#  &` $` "!,d < d 4) (6n 4) 6  4) P 6-U4)3`  L#!"& $%,4)4)3$&%% `,%#"!))3,&$%#"!4)4)&$n%  #"!3<X%4)%4)  &$n%#"!3<X< P g%)&$%) L&$n% #"!3<X P )%)&$n%  #"!3<X))&$) #$%&% "!3<,)) n&2$n"!3, P )%) 6S&4 $ )=%")" #&` $` !, )4)4) n n&2$n"!3,4)S&3$G)4)#n"n !n&4)4&n)4&n) &#"!)  0&n%$` #n L "n!n4),4)& L x#"!( 4)!S4)  L&P$#n"!nd4)4)&%$` #n L9 0"n!n4)4)$#n L9 `"n!n34)4)#  &` "!n,)4)&$% L#"!&< )) L&n#n!n"n3 4 )4) L&n#n!n"n 3 %4 )4)&n#n!n"n 3 %  4 )4)3, L j#n!"& (4) 4) ()f L&$%#  "!(4)$~&~3)4) L&$%#  "!(4)3)4) d&$ x%#  "!(4))4)&$` %4 #"!(P %$P&PX!"#)3)4%$P&PX!"#) d%# "n!nd4 )4))$n L!#%"   4))$% (n &n"X4)$% ` &n"X4)% Ln &` $B!"#(X) $` 4#"!P &`  &  (4) nP( :4)$` %4 #"!(P &20,"4)0,4")%4$P&PX!"#)03)% L!# (,"P&n4)) L!#2 2(,"&n4)4))!#2 2(,"3&4)) L#"!<~ &0d"4)0d"4)(0d3")4 #"!( &" 04)" 04)%4$P&PX!"#)" 3)$&%#!"(P )d L3&$)P)  ) #!"n,  H ))  ) d#  &n$n"!, 4)4)3 L)$` &`  ) L X%$ #%!"34)4)$ L&. n#%!n" 4)4) $&%#n!n"n) ($& n#%!n" 4)4) $&%#n!n"n)!%$` # "& L#"!) 0)0<(&$!% #") &J$) L ~d"P# 2P!&n&X$ %)$) %"&n 4)&~$`n#%!"3  4)4) d "%# P!&n4)4) L~"P# P!&n4)4)%$` # !"&))$~&d% L#"!4))% L $&.% #n"n!n4)4)!$% L&#"),(&,$,3) ($2%&#"!: :4)4) dP #%$n&  "!)) #%$n&   n"n!n,4)4)X"&$) L&#"!&< ) ,!n"n  (& $ 4% T L#"!)4% T L#"!)#  &` $` "!, 4)4)4) 4)n L#$%&P!n"3  4)4) $&%#n!n"n) L#$~%&!n"3 ,n 4)4),!n"n  (&2$2&` $` #"` )  `  H#)% l L#"!& $ )~ %&$` n,n | n"n!n3,4)4)&!nn"n3( n,n 4)4)3!nnn$ &n#n"n)4)$ %  &#n"n!n)  & #n!` "n)4)%(&$` #n"n!n ( X) nnn&$n#n"n!n%  ,)4) L#"!)() % n,n&$` #n"n!n  X#"!)(% nN &$` #n"n!n ( X)<&~  ,%#n"n!n)  &$n"!n)4) (&$%#n"n!n4)) #n"n!n), n  & $nP#n"n!n4)) ($&%#n!n"n(P 4))n&P ( n T(#n"n!n)!n"n  &2$2,34)4)  &2$2 n"n!n,4)4)n&2$nnnn | n"n!n3,4)4) 3&"PP )) &<"n4)4)!&#$%" LP )4)< "&P #4))$ !#%&", P  4) $n&n4) "#n P!n&4)4)"#n  ~!n&4)4)#"!,P (P ~&4) n%$ &  x#n"n!n4))&< "n  4)) "&P 4)4) 2!#%$ P,"&` 4))% $&#n!n"n 3<    0P4)$&)#n$n% &"n  ,4"n)"n) &n"P  &n"n)") P"P  ))4)4 "y&2 P 4)4) n$#n!n"n&. 3   4)34)4 $.&.%#n!n"n)4 ,#n!n"n%&$3 ) ,#n!n"n%&$3 ) $.&.%#n!n"n) ,!n"n&P4))n  "&4)) n!n(P ,&2")4)#n !n"n(P &4)) !n(P ,& "4)) "&)$#n% 9 "n!n34)4) &d4)4)#n!n"n 3P    4)43) !n<P ,(&` "n4)) "n!n3(n nP 4)43)#r n, !n& "))#n!n"n 3    4 )4)#n!n"n x n3    4 )4) #"!( &4)4) "n!n3,4 )4))"n!n3n nP 4)4)#n"n !n&)$#% !n(P ,(&"n4))$` #% !n(P ,(&"n4))instInstrument8pInstrument15pInstrument16pInstrument9pInstrument3p808 Maracas  808 Clave1zX 808 Cymbal A 808 Conga Hi1nHC808 Conga Med( 808 Conga LoC808 Tom Hi 2 FC808 Tom Med 2P A808 Tom Lo 2* D808 CowbellXD808 Tom Med 1hX A808 Tom Hi 1CC808 Tom Lo 1 P A808 Hat Closed* C!808 RimD$808 Kick 1*AHz &808 Clap CG'808 Snare 2X C* )808 Hat Open** B,808 Snare 1 CG/808 Kick 2 BG2ELECTRONIC KICK 2ff3ELECTRONIC SD 2(i6ELECTRONIC SD 1j9ELECTRONIC KICK 1<ELECTRONIC TOM LO 1@ELECTRONIC TOM LO 2BELECTRONIC TOM MID 1DELECTRONIC TOM MID 2FELECTRONIC TOM HI 1HELECTRONIC TOM HI 2JKRAFTWERK BD 5lLKRAFTWERK SD 7OKRAFTWERK SD 6SKRAFTWERK KLAPScWKRAFTWERK SD 5hZKRAFTWERK BD 4g]KRAFTWERK SD 4_KRAFTWERK BD 3fbKRAFTWERK SD 3hdKRAFTWERK RIMSHOTncfKRAFTWERK CYMBAL** gKRAFTWERK HAT OPreqhKRAFTWERK HAT CLtchjKRAFTWERK SD 2alKRAFTWERK SD 1XgoKRAFTWERK BD 2sKRAFTWERK BD 1 auANALOG SNARE 9kwANALOG SNARE 8jyANALOG SNARE 7j|ANALOG SNAPPY BD kANALOG HAT OPEN 3s ANALOG HAT CLOSED 3ANALOG HAT CLOSED 2ANALOG HAT OPEN 2ANALOG SNAP SNAREhANALOG BOOM KICK ANALOG CLAPS 3aANALOG CLAPS 2^ANALOG ZAP OPENHfANALOG ZAP CLOSED**ANALOG CLAVEL0 fANALOG SNARE 6@ ANALOG SNARE 50fANALOG THROB KICKdFANALOG POPS OP 2astANALOG POPS OP 1se ANALOG POPS CL 2ANALOG POPS CL 1lteANALOG PHASERPANALOG TAMBOURINE ANALOG KICK 3o cANALOG RIM SHOTdANALOG CRASH CYMBAL1ANALOG COWBELL(bANALOG BONGO HIdANALOG BONGO MEDNALANALOG BONGO LOPdANALOG SNARE 4 YANALOG SNARE 3qbANALOG MARACASt_ANALOG CLAPS 1 [ANALOG HI TOM 2ANALOG HI TOM 1HfANALOG MID TOM 2* ANALOG MID TOM 1 *ANALOG LO TOM 2hANALOG LO TOM 1eANALOG CRASH CYMBAL2ANALOG HAT CLOSEDcANALOG HAT OPENdANALOG LASER ANALOG SNARE 2ANALOG KICK 20oANALOG KICK 10rANALOG SNARE 1Xp909 BASS BOOM1(j909 TOM HI 2 909 TOM HI 1 909 TOM MID 2 909 HAT OPEN 909 TOM MID 1 909 TOM LO 2 909 HAT CLOSED 909 TOM LO 1 909 SNARE HI 909 CLAP 909 SNARE LO 909 RIM 909 BASS DRUM HI 909 BASS DRUM LO 909 SNARE 2 909 SNARE 1 909 BASS DRUM 2 909 BASS DRUM 1 808 CLAVES 808 MARACAS 808 BONGO LO 808 BONGO MID 808 BONGO HI 808 COWBELL 808 HI TOM 2 808 CYMBAL 808 HI TOM 1 808 MID TOM 2 808 HAT OPEN 808 MID TOM 1 "808 LO TOM 2 #808 HAT CLOSED $808 LO TOM 1 &808 SNARE DRUM HI '808 CLAP *808 SNARE DRUM LO +808 RIM SHOT .808 BASS DRUM HI 0808 BASS DRUM LO 1808 SNARE HIGH 2808 SNARE SHARP 5808 SNARE FILTERED 8808 ULTRA SNAPPY ;808 BASS DRUM MUTE >808 BASS DRUM BOOM2?808 BASS DRUM SHORT@808 BASS DRUM BOOM1ACR78 HI TOM 21PBCR78 HI TOM 11SCCR78 MID TOM 2 RDCR78 MID TOM 1PECR78 LO TOM 21OFCR78 LO TOM 11GCR78 BONGO LO #03HCR78 BONGO MID #02ICR78 BONGO HI JCR78 COWBELL KCR78 TAMBOURINE MCR78 HAT OPEN OCR78 HAT CLOSED QCR78 SNARE 2 SCR78 METAL WCR78 SNARE 1 YCR78 RIM SHOT ]CR78 KICK 2 ^CR78 KICK 1 _CR78 CLAVE `CR78 MARACAS aCR78 GUIRO b101 LASER 3 c101 LASER 2 d101 LASER 1 e101 MARACAS f101 SHAKER g101 BONGO LO h101 BONGO MID i101 BONGO HI j101 COWBELL k101 TAMBOURINE m101 HI TOM 2 o101 CYMBAL p101 HI TOM 1 r101 MID TOM 2 s101 HAT OPEN t101 MID TOM 1 u101 LO TOM 2 v101 HAT CLOSED w101 LO TOM 1 x101 SNARE 2 y101 CLAPS }101 SNARE 1 101 RIM SHOT 101 KICK 2 101 KICK 1 101 SNARE EXTRA #02101 HAT OP EXTRA 101 HAT CL EXTRA 101 SNARE EXTRA 3 101 KICK EXTRA 3 101 SNARE EXTRA 2 101 SNARE EXTRA 1 101 KICK EXTRA 2 101 KICK EXTRA 1 ADDITIVE BELLch ]SMOOTH SAW S MELODIC VIBRATO FM CLANGt1 FM3rument1|FM BELL 21THARSH FM BASSs FAERIE CHORAL AAArument1pxOINKument1`oCLINKment1pSMOOTH SQUARErHALF RECTIFICATIONVIBRAPHONE]xGRUNGECHOIRe TXPIPESment1\xBELLSH BELLS BREEZECALLIOPE AAHSAAHS ELECTRIC BASS DM 3ument1x V DM 2ument1Z DM 1ument1V WHITENOISE 3 Xh XTRIANGLE WAVE1 USQUARE WAVE0UXSINE WAVE1VXSAWTOOTHt1XUXADDITIVE 3 T,WHITENOISE 0** XFM 2ument1V` FM 1ument1U`EOIibagP &29ELX_gs$+6=IMWamz!+;IZn{!)48JN[hx|"/:>LYau,29K^k{'';OP[ep!.<JXkx-=MRZ`emsx   " - 5 ; F N T _ g m x    ( 6 C W e x  # 6 K ` x   0 A V g w   & < N ` v ,CZq %:Og  1BTf|*<Og%7DR`v 2H^u,=Shy$;Qi 6J^u  4I[o !5Kav $,5>GS\enw&.;CPXckw'1>FR[dmv&/8AJV_hqz )2;DMXajs}imod }igenu" d.#&$:962-5" d.#&$:962-5" d.#&$:962-5" d.#&$:962-5" d.#&$:962-5+FF4:,"&$%5 +KK&$%4:W62-5 +11  $&%:=62-5+110&%$4:+62-5 +110$&%4:,62-5 +>>&$%:e62-5 +??:'&%$62-5 +@@4(:/&J%$J62-5 ,IIIII+22:&%$62-5 ,IIIII+//:!&%$62-5 ,IIIII+++:$&%$62-5 .4 +880@&$%62-:85+884&$%62-:85,IIIII+--:"&%$62-5 ,IIIII+00:&%$62-5 ,IIIII+))4:&&%$62-5 %$&+**:203 62-5 +**} 62-:55+%%3: &$%62-5+%%4&f$f:C%3 62-5 +##(&%$362-5 e _&$%+''062-:'5+(( W&f%$f3x62-5 +((3  &$%:&62-5 +((3 &$%:&62-5 & #%$+..$:603 62-5 +..} 62-:95+&&0d&%$3.62-5 +&&3 ( &$%:$62-5 +&& (3 &$%:$62-5 +$$(&<%$<362-5 "g3&P$P%+$$ 2-:.6K5+$$42-:.6K5+((("r4&P$P%:(62-5+(("r2-%:6w$&5+((("r 4&P$P%:(62-5+&&4 P '&$%:(62-5+&&2-%:6w$&5+&& ' P 4&$%:(62-5pe"g3&P$P%+## 2-:.6K5+##42-:/6K5+## ,"I&P$2-:6K5"r1%3%&$+))2-:(6K5"r1%3%&$+++2-:(6K5"r1%3%&$+--2-:(6K5"r1%3%&$+//2-:(6K5"r1%3%&$+002-:(6K5"r1%3%&$+222-:(6K5+$2-%:76L&5+:%5+0,:F%&~$~qq62-59,$J&J%+ 62- U":!5+ 62-"  :!5+ 2-%# :6K" $&0N5,$&%+//4 2-:#65+//2-:#65+//:?qq$&&&62-59(%&P$P (3+'' 62-:+5+''62-:*5+-- x4 ]&$%:+62-5+--2-%:6w$&5+-- x ] 4&$%:+62-5+!!@2-%:16L&$5+!!:%5+++62- 03:!$J&J%5+++62-0 3 :!$J&J%5+++&f$f62-3:0%5+""2-%:56L&$5+"":%5+))4 &q$q2-%:*65+))&q$q2-%:*65+%%&$(:>%5+11  @2-:46$` &` %5+..) d2-# +:46 " $&1%5+.. 32-# +6:! " $&1%5+**) d2-%# +:06 " $n&5+** 32-%# +6: " $n&5+((, # 3:!$&%5+((:#&$%5+((,#  3 :!$&%5+&&)2-%:$6L&,$,5+&&3:!$n&n%5+&&&&$&%:6 62-5+&&3&&$&%:662-5+$$2-%:76L&P$P5+$$:6&n$n%5+##1%:6&$%5+##III2-%:56L&$5+HH2-%# +4-:66L" $&Z 5+HH:M%# 6( " $&5+GG:B P 6 (2-&$%5+GG:D P 6(2-&$%5+GG32-%# +:.6z " $&5!+FF:B P'6 (2-&$%5+FF:D P'6(2-&$%5+FF32-%# +:.6z " $&5!+DDI:O2-%# 6LI" &$ (5+BB :D#d&$%62-5+AA :Cd&$%62-5+<<:B) d2-# +6 " $&%5+<< 32-# +6:+ " $&%5+==:C) d2-# +6 " $&1%5+== 32-# +6:, " $&1%53+;;:0%4-6$& 5+;;:2%4-6$&5+;;:2%4-6$&5+;;32-%4-:6L$&Z 5+:: d$&%62-:85 &$%( P3+77 62-:+5+7762-:)5 P3+55( %&$62-:+5+5562-:& $P%5+55(%&$62-:)5+44:= , & $%IIN 62-5+33 , &$%IIN 62-5+CC9&n$n%:*62-5`+32-%# +:6z " $&5!+( %&$62-:+5+62-:& $P%5+(%&$62-:)5+6 (2-:&P$P%5+6(2-:&P$P%5+32-%# +:60z " $&5!+ x&$%62-:5 +9d&$%:62-5`+9d&$%:62-5`+9d&&$&%:62-5`+9d&&$&%:62-5`+,,, &~$~%nn?! 62-5 x%(&$+66 n2-:465+660 n2-6:95+%6d$<&<:*5"+%2-:65+%%&f$f%# 460" dZ: 5"+%%2-%:605+99::%2-65+99d%&n$` 62-5 +88I n:F$%2-+4-6&%%5+88P$%:F2-6&5+88VPd$%:R2-6&53%&$+>>:2-6K5+>>0d$&%53%& $ +??:'2-6K5+??$&0d%53%&P$P+@@:32-6K5+@@$&0d%53+!!d32-%# +:60z " $P&P5!+!!62- n :(&P$P%5+!!62- n%&P$P:(5+""( &$%:062-5+""(&$%:/62-5+""d2-%:6w$&5+EE n2-%6"&$:Q5 )X(&P$P% Z3+''62-:+5+''362-:+53%I)&$+222-:(6K5+22&/$&0d%53%I)&$+002-:(6K5+00&/$&0d%53%I)&$+//2-:(6K5+//&/$/0d%53%I)&$+--2-:(6K5+--&&$/0d%53%I)&$+++2-:(6K5+++&/$/0d%53%)&$+))2-:(6K5+))$/&/0d%5+113 n$n2-04-:46 &` %5+113,$n062-6: &` %5+** n$2-0# +4-:06 &%5+**$062-6:&%5+.. n2-0+4-:46 &%5+..062-6:!&%5+  $& : 53+((d32-%# +:60z " $&5!+((62- :-(&$%5+((62-%&$:)(5+((62-$&%:*(5+$$%# 460" dZ$&:#Z 5"+##%6( dZ$&:,5"+&&32-%# +:60z " $&5!+&&( 2-%# +:6 " $&5+&&6 2-:!&$%5+&&62-: &$%5+""3d<%# +:0" Z $&5"+""4$"g32-%# :46K&5+222-%# +4-604;:"" Z$1&1 F5 +002-%# +4-604;:#" Z$& F5 +//2-%# +4-604;:%" Z$]&] F5 +..%# +4-:40 " $0&6 5+..%# +4-::6 ss" $o&o0 i5+..2-%# +4-:$60`  " $0& 5+..2-%# +4-:%6  " $0&0` 5+--2-%# +4-604;:&" Z$& F5 +++2-%# +4-604;:(" Z$`&`  5 +**%# +4-:00 " $s&s6 5+**2-%# +4-:!6  " $s&s0` 5+**2-%# +4-: 60`  " $s&s 5+**%# +4-:66 ss" $&0 i5+))2-%# +4-604;:)" Z$& F5 +((%# +4-:,6 " $1&1 0 K5+((2-%# +4-:60L" $&Z 5+((%# +4-:-6 " $1&10 K5+((%# +:,6 " $&4t K0Z5+''2-# +4-%:.60 " $&95+&&%# +4-:,60 " $& 5+&&0%# +:,6 " $&4t5+&&%# +4-:,60 " $]&]5+&&%# +4-:-60 " $&5+&&2-%# +4-:60L" $&Z 5+%%2-%# +4-:&6o0 " $&5+%%2-%# +4-:@6 " $&05+$$%# +4:0" Z N$&5"+$$32-%# +:46K "$&0N5+##%# +4: 0$W&W" Z '5"+%# +4-: 6 " $& 0 K5+2-%# +4-: 60L" $&Z 5+%# +4-:!6 " $&0 K5+%# +4-: 60 " $`&` i5+2-%# +4-: 60L" $&Z 5+%# +4-:!60 " $`&` i5+%# +4-: 60 " $& K5+2-%# +4:0$W&W" Z 5"+2-%# +4-:+6K0 "$0&05+2-%# +4:0$W&W" Z d5"+32-%#+:(6K0 "g$<&<,5+KK3 2-%# +4-:6 " $o&o5+GG2-%# 6" $&:E34d5+@@2-%# +:<6K " $&04_'5+??2-%# +:56K " $`&`04_'5+>>2-%# +:,6K " $&04_'5+882-%# +4:26$&$ " 0 U(5+882-%# 4-:860; ' " $& !5+222-%# +:6K" $&0(Z 4N5+112-%# +4-:+6 " $& 0s5+112-%# +:(60 " $&4b5+112-%# +4-:76 " 0$& 5+112-%# 4-60:# " $& =+5+002-%# +:6K" $&0(Z 4N5+//2-%# +4-:"6K" $1&10(Z N5+..2-0# +4-:46 " $&1% 5+..2-# +4-60;:! " $&1% 5+--2-%# +:$6K" $&0(Z 4N5+++2-%# +:&6K" $]&]0(Z N45+**2-0%# +4-:06  " $s&s5+**2-%# +4-60;: " $s&s 5+))2-%# +4-:(6K" $&(Z N5+((2-%# +4-:6L " &$m5+((%# +4-:,6/ " $&, y5+((%# +4-:06 " $&/ | 5+''0(32-# %:-6 " $&5+&&2-%# +4-:6L " &$m+5+&&%# +4-:%6 " $&6S5+&&%# +4-:&66 " $& <5+%%2-%$ # +4-:6  " 5+%%2-%$ # +4-:&6 " 5+$$2-%# +4-:(6L" &~$~Z '5+##2-%# +4-:46L " &$N5+2-%# +4-:6L " &s$s5+%# +4-:6 " $&6 5+%# +4-:66 " $&  5+2-%# +4-:6L " &$N5+%# +4-:!6' " $&  5+%# +4-: 6 " $&'  5+2-%# +4-:6L " &s$s5+2-%# +4-:6N " $&  s5+%# +4-:6 " $&N 5+2-%# +4-:6L " &$sm5+2-%# +4-: 6 " $&'  K5+2-%# +4-:!6' " $&  K5+2-%# +4-:(6L " &$ Q95+2-%# +4-:*6L" $ &5+2-%# +4-:(6L" &$ 5+2-%# +4-:/6LI" &$5+222-%# :6K" $&0'45+002-%# :6K" $&0'45+//2-%# 4-:"6K" $&0'5+--'2-%# :$6K" $&045+++'2-%# :&6K" $&045+))2-%# +4-:(6K" $&0'5+@@4)&$%:T62-5+??4(3)&a$a%:J62-5+>>43)&$%:=62-5+882-0%# :A6  " & $45+882-0%# :A6  " $&4_ r5+6662-%# 4 " &0r ]:A$5 +66 P2-%+4-60P:' " $& 5+..2-%# +4:/60 " &$Q 95 +..2-%# +4:460 " $o&o5 +**2-%# +4:+60 " $s&s 95 +**2-%$ # +4:/6 " 05 +((2-%# +4:60{ " $&5!+((2-%# +4:%6 " 0$&L5+((2-%# +4:06 " 04$&5 +((2-%# +:0604 " &$ 45 +''2-%4:'60T+  " $&#S5+''2-%#S+4-60: " $& 5+&&2-%# +4:60{ " &o$o5!+&&2-%# +:26 " &$ 4045 +&&2-%# +4:26 " $&045 +&&2-%# +4:%6 " 0Z&L$5+%%2-%$ # +4-:0  " 5+$$32-%# 60" &:+Z $5"+##Z2-%# 6" &:. $5"+""2-%# +: 6 " 4T0$o&o5+!!%# +4:604 " $&5+ 2-%4-: 60 " #$& x 5+55<2-# +4%604  " $J&J:45+44::$ # +4-%0&h " 95+332-:3%+4$h&O $5+GG2-%# 46 " &$ v :J5 +EE2-%# 46 "&$v:Q5 +@@2-%# +:<6K " $&04_N5+??2-%# +:56K " $&04_N5+>>%2-# +:,6K " &,04_N$,5+882-%# :?6  " &s V04$s4au5+882-%# +4a:?6  " $s&s04u5+662-%# 460 " &$v := o5 +662-%# 4-:"6  " $&0 <u5+222-%# +:6K" $&0(Z 4N5+112-0%# 4:76 " &$  !} p5 +112-%# +4:.6 " &0$5+002-%# +:6K" $&0(Z 4N5+//2-%# +4-:"6K" $&0(Z N5+..2-%# +4:/60 " &$Q5 +--2-%# +:$6K" $&0(Z 4N5+++2-%# +:&6K" $&0(Z N45+**2-%# +4:+60 " $s&s5 +))2-%# +4-:(6K" $&0(Z N5+((2-%# +4:60z " $o&o5!+((2-%# +4:$6 " 0$&5+((2-%# +4:16 " 0$& X'5 +((2-%# +4:060' " &$  Z5 +''2-0%# 4:'6' " $& ].5+''2-0%# 4:16' " $&r ]5+&&2-%# +4:60z " $&5!+&&2-%# +4:%6 " 0$s&sL5+&&2-%# +4:06 " '0$&5 +&&2-%# +4:/60' " &$ 5 +%%2-%$ # +4-:60 " 5+%%2-%# +4:%60 " $&5+$$2-%# +4:)60w " $&N5+##%# 460 " FZ$a&a:15"+ 2-%# +4:60z " $&5!+ 2-%# +4:+6 " '0$&5 + 2-%# +4:(60' " &$ 5 +2-%# +4:60 " &$ 5 +2-%# +4:60 " $s&s 5 +2-%# +4: 60z " $&5!+2-%# +4:&6 " 0$& K5 +2-%# +:&60 " &$  K4=5 +2-%# 460" Z$&:!Z '5"+2-%# +4:60z " $&5!+2-%# +4:&60' " &$ 5 +2-%# +4:)6 " '0$&`5 +2-%# +4:6 " 0$&L5+2-%# +4:60z " $&5!+2-%# +4:+60' " &$ 5 +2-%# +4:,6 " '0$&5 +2-%# +4:6 " 0$s&sL5+%# 460" FZ$&:Z 5"+%# 460 " FZ$&:"(5"&n$n <"g3 +R:T62-5&+:E62-5%+FQ:H62-5$+9:062-5#" d.#&$+E462-5*+FQ4:H62-5)+R]4:O62-5(+^4:T62-5'3 " d.#&$+E4:H62-5.+FQ4:T62-5-+R]4:[62-5,+^i4:_62-5+" d.#&$4:M62-5/3" d.#f&$+E4:862-5Q+FQ4:D62-5P+R]4 :P62-5O+^4:\62-5Nn" d.#f&$+E462-53+FQ4:H62-52+R]4:T62-51+^4:`62-50" d.#&$+E462-57+FQ4:G62-56+R]4A:T62-55+^4:`62-54" d.#&$64R2-:O583" d.#f&$4:P62-5:3" d.#&$4):M62-5;" d#&$4^:O62-59$4"r d#Y&+E462-5?+FQ4:H62-5>+R]4:O62-5=+^i4:T62-5<4" d.#&$+E462-5C+FQ4 :I62-5B+R]4:P62-5A+^i4:T62-5@" d.#&$4/:Q62-5D4" d.#&$4S:N62-5F" d.#&$4-:N62-5E" d.#&$4):N62-5G" d.#&$.493:R62-5H4" d.#&$4:L62-5I" d.#&$+E4:H62-5M+FQ4:T62-5L+R]4:[62-5K+^i4:`62-5J3" d.#&$+E4:-62-5U+FQ4:962-5T+R]4:E62-5S+^i4:Q62-5R" d.#&$+E4:-62-5Y+FQ4:962-5X+R]4:E62-5W+^i4:Q62-5V" d.#&$+E4:-62-5]+FQ4:962-5\+R]4:E62-5[+^i4:Q62-5Z" d.#&$:K62-5^" d.#&$+E4:-62-5b+FQ4:962-5a+R]4:E62-5`+^i4:Q62-5_"g d.#&$+E4:-62-5f+FQ4:962-5e+R]4:E62-5d+^i4:Q62-5c" d.#&$+E4:-62-5j+FQ4:962-5i+R]4:E62-5h+^i4:Q62-5g" d.#&$+E4:-62-5n+FQ4:962-5m+R]4:E62-5l+^i4:Q62-5k" d.#&$+E4:-62-5r+FQ4:962-5q+R]4:E62-5p+^i4:Q62-5o" d.#&$:K62-5s" d.#&$+E4:-62-5w+FQ4:962-5v+R]4:E62-5u+^i4:Q62-5t" d.#&$+E4:-62-5{+FQ4:962-5z+R]462-:E5y+^i4:Q62-5xshdrvSineWaveTriangleJFD<FM4-04400 lf}<Tri2q AltP}<Tri-Sq AFilter Q9}<FM5-04400 (}<Bell2-0440 WQ}<cowbell w}<claploopAAuenc.h6`D<white5  &&D<sinehi  &]'&V'D<white808-2B}'@'j@K}<bdloop @B@@B}<THROB DRUMb Drum* BsP L$PD<909BD11PQPQD<CLAP21QdRdD<SQUARELOdfdfD<NOISE6f{f{D<SQUARE2;{{<{{D<CLAP2{{D<NOISE3D<NOISE5D<SINEHID<1$7%7D<LASER1WXD<RIM''D<CLICKGHD<TRIHI >>D<SINE32^_D<WHITE4IID<TRIANGLEijD<808BD~ ~ D<WHITE2  D<WHITE32,2,D<BD1R,.S,.D<TR909BD/>/>D<wav1_1>A>A}<wav1_3ABAIB}<wav1_2BVD C-D}<wav1_4vD+EDE}<SAW4KEEeEED<SAW3EFEFD<SAW24FF[FFD<SAW1FG/GGD<W12_4H;HH;HD<W13_3[HH\HHD<W12_2H-IHID<W12_1MIJeIJD<W10000r>M 1JJ;JJD<W7_4JaKKZKD<W7_3KdLKPLD<W7_2L-NL#ND<W7_1MNQNQD<W6_4Q,RQRD<W6_3LR/SiRSD<W6_2OS/USUD<W6_1OU1YRUWD<MOD2_C3QYYoYYD<FM90000r>MY[yZ[D<191r>M[\\\D<WAV5r>M\']\\D<SQ4G]]Z]]D<SQ3]]]]D<SQ2^^;^^D<SQ1^_^_D<HR4____D<HR3`f`,`b`D<HR2````D<HR1aa+aaD<HOLLOWTH]ld 1 a\cybGcD<BELL4r>Mh|c ec eD<GRUNGE[Coarse T-e5g8f2gD<BELL10000Ug"ihhD<HISSBiɏBiɏD<AAHHAD<W8_4hwD<W8_3ȐސD<W8_2>RD<W8_1֑D<dm4_a4 Ytoff *ےgMD<dm4_a39D<dm4_a2YY!D<dm4_a19D<dm3_a4ΗΗD<dm3_a3;՘;D<dm3_a2D<dm3_a1,D<dm2_a4uD<dm2_a3D<dm2_a2?-KD<dm2_a1MdD<dm1_a4 D<dm1_a3-D<dm1_a2M;UD<dm1_a1[3`D<Noise38XYS[D<Tri_a4TUeqXX,D<Tri_a3TUeqXXLD<Tri_a2TUeqXX6dh1D<Tri_a1TUFrequenc޻zD<Sq_a4UHU U}HzD<Sq_a3UHU Ugм4D<Sq_a2UHU UD<Sq_a1UHU* Uվ/*D<Sine_a4 U* UOD<Sine_a3 U* U;D<Sine_a2 U* U D<Sine_a1 U* U&&D<Saw_a4cU* pYD<Saw_a3cU* pY? bD<Saw_a2cU* pY)W1D<Saw_a1cU*** **wD<add3_a4x Vyn TWD<add3_a3x Vyn TlWD<add3_a2x Vyn T{D<add3_a1x VKeynum T)D<Noise4Txcay **!^D<Fm2_a4VWAVD<Fm2_a3VWxLD<Fm2_a2VWpD<Fm2_a1VD<Fm3_a4  }7iD<Fm3_a3  aRD<Fm3_a2  #D<Fm3_a1 !eD<EOSfluidsynth-2.1.1/src/000077500000000000000000000000001362231004000144625ustar00rootroot00000000000000fluidsynth-2.1.1/src/CMakeLists.txt000066400000000000000000000274261362231004000172350ustar00rootroot00000000000000# FluidSynth - A Software Synthesizer # # Copyright (C) 2003-2010 Peter Hanappe and others. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2.1 of # the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA # 02111-1307, USA # CMake based build system. Pedro Lopez-Cabanillas include_directories ( ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/drivers ${CMAKE_SOURCE_DIR}/src/synth ${CMAKE_SOURCE_DIR}/src/rvoice ${CMAKE_SOURCE_DIR}/src/midi ${CMAKE_SOURCE_DIR}/src/utils ${CMAKE_SOURCE_DIR}/src/sfloader ${CMAKE_SOURCE_DIR}/src/bindings ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include ) include_directories ( SYSTEM ${GLIB_INCLUDE_DIRS} ${PTHREADS_INCLUDE_DIR} ${SDL2_INCLUDE_DIR} ${LIBINSTPATCH_INCLUDE_DIRS} ) # ************ library ************ if ( READLINE_SUPPORT ) include_directories ( ${READLINE_INCLUDE_DIR} ) endif ( READLINE_SUPPORT ) if ( PULSE_SUPPORT ) set ( fluid_pulse_SOURCES drivers/fluid_pulse.c ) include_directories ( ${PULSE_INCLUDE_DIRS} ) endif ( PULSE_SUPPORT ) if ( ALSA_SUPPORT ) set ( fluid_alsa_SOURCES drivers/fluid_alsa.c ) include_directories ( ${ALSA_INCLUDE_DIRS} ) endif ( ALSA_SUPPORT ) if ( COREAUDIO_SUPPORT ) set ( fluid_coreaudio_SOURCES drivers/fluid_coreaudio.c ) endif ( COREAUDIO_SUPPORT ) if ( COREMIDI_SUPPORT ) set ( fluid_coremidi_SOURCES drivers/fluid_coremidi.c ) endif ( COREMIDI_SUPPORT ) if ( DBUS_SUPPORT ) set ( fluid_dbus_SOURCES bindings/fluid_rtkit.c bindings/fluid_rtkit.h ) include_directories ( ${DBUS_INCLUDE_DIRS} ) endif ( DBUS_SUPPORT ) if ( JACK_SUPPORT ) set ( fluid_jack_SOURCES drivers/fluid_jack.c ) include_directories ( ${JACK_INCLUDE_DIRS} ) endif ( JACK_SUPPORT ) if ( PORTAUDIO_SUPPORT ) set ( fluid_portaudio_SOURCES drivers/fluid_portaudio.c ) include_directories ( ${PORTAUDIO_INCLUDE_DIRS} ) endif ( PORTAUDIO_SUPPORT ) if ( DSOUND_SUPPORT ) set ( fluid_dsound_SOURCES drivers/fluid_dsound.c ) endif ( DSOUND_SUPPORT ) if ( WAVEOUT_SUPPORT ) set ( fluid_waveout_SOURCES drivers/fluid_waveout.c ) endif ( WAVEOUT_SUPPORT ) if ( WINMIDI_SUPPORT ) set ( fluid_winmidi_SOURCES drivers/fluid_winmidi.c ) endif ( WINMIDI_SUPPORT ) if ( SDL2_SUPPORT ) set ( fluid_sdl2_SOURCES drivers/fluid_sdl2.c ) include_directories ( ${SDL2_INCLUDE_DIRS} ) endif ( SDL2_SUPPORT ) if ( OSS_SUPPORT ) set ( fluid_oss_SOURCES drivers/fluid_oss.c ) endif ( OSS_SUPPORT ) if ( LASH_SUPPORT ) set ( fluid_lash_SOURCES bindings/fluid_lash.c bindings/fluid_lash.h ) include_directories ( ${LASH_INCLUDE_DIRS}) endif ( LASH_SUPPORT ) if ( SYSTEMD_SUPPORT ) include_directories ( ${SYSTEMD_INCLUDE_DIRS}) endif ( SYSTEMD_SUPPORT ) if ( DART_SUPPORT ) set ( fluid_dart_SOURCES drivers/fluid_dart.c ) include_directories ( ${DART_INCLUDE_DIRS} ) endif ( DART_SUPPORT ) if ( LIBSNDFILE_SUPPORT ) include_directories ( ${LIBSNDFILE_INCLUDE_DIRS} ) endif ( LIBSNDFILE_SUPPORT ) if ( MIDISHARE_SUPPORT ) set ( fluid_midishare_SOURCES drivers/fluid_midishare.c ) include_directories ( ${MidiShare_INCLUDE_DIRS} ) endif ( MIDISHARE_SUPPORT ) if ( AUFILE_SUPPORT ) set ( fluid_aufile_SOURCES drivers/fluid_aufile.c ) endif ( AUFILE_SUPPORT ) if ( LIBINSTPATCH_SUPPORT ) set ( fluid_libinstpatch_SOURCES sfloader/fluid_instpatch.c sfloader/fluid_instpatch.h ) endif ( LIBINSTPATCH_SUPPORT ) if ( OPENSLES_SUPPORT ) set ( fluid_opensles_SOURCES drivers/fluid_opensles.c ) include_directories ( ${OpenSLES_INCLUDE_DIRS} ) endif ( OPENSLES_SUPPORT ) if ( OBOE_SUPPORT ) set ( fluid_oboe_SOURCES drivers/fluid_oboe.cpp ) include_directories ( ${OBOE_INCLUDE_DIRS} ) endif ( OBOE_SUPPORT ) set ( config_SOURCES ${CMAKE_BINARY_DIR}/config.h ) set ( libfluidsynth_SOURCES utils/fluid_conv.c utils/fluid_conv.h utils/fluid_hash.c utils/fluid_hash.h utils/fluid_list.c utils/fluid_list.h utils/fluid_ringbuffer.c utils/fluid_ringbuffer.h utils/fluid_settings.c utils/fluid_settings.h utils/fluidsynth_priv.h utils/fluid_sys.c utils/fluid_sys.h sfloader/fluid_defsfont.c sfloader/fluid_defsfont.h sfloader/fluid_sfont.h sfloader/fluid_sfont.c sfloader/fluid_sffile.c sfloader/fluid_sffile.h sfloader/fluid_samplecache.c sfloader/fluid_samplecache.h rvoice/fluid_adsr_env.c rvoice/fluid_adsr_env.h rvoice/fluid_chorus.c rvoice/fluid_chorus.h rvoice/fluid_iir_filter.c rvoice/fluid_iir_filter.h rvoice/fluid_lfo.c rvoice/fluid_lfo.h rvoice/fluid_rvoice.h rvoice/fluid_rvoice.c rvoice/fluid_rvoice_dsp.c rvoice/fluid_rvoice_event.h rvoice/fluid_rvoice_event.c rvoice/fluid_rvoice_mixer.h rvoice/fluid_rvoice_mixer.c rvoice/fluid_phase.h rvoice/fluid_rev.c rvoice/fluid_rev.h synth/fluid_chan.c synth/fluid_chan.h synth/fluid_event.c synth/fluid_event.h synth/fluid_gen.c synth/fluid_gen.h synth/fluid_mod.c synth/fluid_mod.h synth/fluid_synth.c synth/fluid_synth.h synth/fluid_synth_monopoly.c synth/fluid_tuning.c synth/fluid_tuning.h synth/fluid_voice.c synth/fluid_voice.h midi/fluid_midi.c midi/fluid_midi.h midi/fluid_midi_router.c midi/fluid_midi_router.h midi/fluid_seqbind.c midi/fluid_seq.c drivers/fluid_adriver.c drivers/fluid_adriver.h drivers/fluid_mdriver.c drivers/fluid_mdriver.h bindings/fluid_cmd.c bindings/fluid_cmd.h bindings/fluid_filerenderer.c bindings/fluid_ladspa.c bindings/fluid_ladspa.h ) set ( public_HEADERS ${CMAKE_SOURCE_DIR}/include/fluidsynth/audio.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/event.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/gen.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/ladspa.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/log.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/midi.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/misc.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/mod.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/seq.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/seqbind.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/settings.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/sfont.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/shell.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/synth.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/types.h ${CMAKE_SOURCE_DIR}/include/fluidsynth/voice.h ${CMAKE_BINARY_DIR}/include/fluidsynth/version.h ) set ( public_main_HEADER ${CMAKE_BINARY_DIR}/include/fluidsynth.h ) configure_file ( ${CMAKE_SOURCE_DIR}/include/fluidsynth/version.h.in ${CMAKE_BINARY_DIR}/include/fluidsynth/version.h ) configure_file ( ${CMAKE_SOURCE_DIR}/include/fluidsynth.cmake ${public_main_HEADER} ) if ( WIN32 ) include(generate_product_version) generate_product_version( VersionFilesOutputVariable NAME "Fluidsynth" BUNDLE "Fluidsynth" VERSION_MAJOR ${FLUIDSYNTH_VERSION_MAJOR} VERSION_MINOR ${FLUIDSYNTH_VERSION_MINOR} VERSION_PATCH ${FLUIDSYNTH_VERSION_MICRO} VERSION_REVISION 0 COMMENTS "Fluidsynth" COMPANY_NAME "Fluidsynth LGPL" ORIGINAL_FILENAME "libfluidsynth.dll" FILE_DESCRIPTION "Fluidsynth" ) endif ( WIN32 ) add_library ( libfluidsynth-OBJ OBJECT ${config_SOURCES} ${fluid_alsa_SOURCES} ${fluid_aufile_SOURCES} ${fluid_coreaudio_SOURCES} ${fluid_coremidi_SOURCES} ${fluid_dart_SOURCES} ${fluid_dbus_SOURCES} ${fluid_jack_SOURCES} ${fluid_lash_SOURCES} ${fluid_midishare_SOURCES} ${fluid_opensles_SOURCES} ${fluid_oboe_SOURCES} ${fluid_oss_SOURCES} ${fluid_portaudio_SOURCES} ${fluid_pulse_SOURCES} ${fluid_dsound_SOURCES} ${fluid_waveout_SOURCES} ${fluid_winmidi_SOURCES} ${fluid_sdl2_SOURCES} ${fluid_libinstpatch_SOURCES} ${libfluidsynth_SOURCES} ${public_HEADERS} ${public_main_HEADER} ${VersionFilesOutputVariable} ) if ( LIBFLUID_CPPFLAGS ) set_target_properties ( libfluidsynth-OBJ PROPERTIES COMPILE_FLAGS ${LIBFLUID_CPPFLAGS} ) endif ( LIBFLUID_CPPFLAGS ) # note: by default this target creates a shared object (or dll). To build a # static library instead, set the option BUILD_SHARED_LIBS to FALSE. add_library ( libfluidsynth $ ) if ( MACOSX_FRAMEWORK ) set_property ( SOURCE ${public_HEADERS} PROPERTY MACOSX_PACKAGE_LOCATION Headers/fluidsynth ) set_target_properties ( libfluidsynth PROPERTIES OUTPUT_NAME "FluidSynth" FRAMEWORK TRUE PUBLIC_HEADER "${public_main_HEADER}" FRAMEWORK_VERSION "${LIB_VERSION_CURRENT}" INSTALL_NAME_DIR "" VERSION ${LIB_VERSION_INFO} SOVERSION ${LIB_VERSION_CURRENT} ) elseif ( OS2 ) set_target_properties ( libfluidsynth PROPERTIES PUBLIC_HEADER "${public_HEADERS}" OUTPUT_NAME "fluidsynth" VERSION ${LIB_VERSION_INFO} SOVERSION ${LIB_VERSION_CURRENT} ) elseif ( WIN32 ) set_target_properties ( libfluidsynth PROPERTIES PUBLIC_HEADER "${public_HEADERS}" ARCHIVE_OUTPUT_NAME "fluidsynth" PREFIX "lib" OUTPUT_NAME "fluidsynth-${LIB_VERSION_CURRENT}" VERSION ${LIB_VERSION_INFO} SOVERSION ${LIB_VERSION_CURRENT} ) else ( MACOSX_FRAMEWORK ) set_target_properties ( libfluidsynth PROPERTIES PUBLIC_HEADER "${public_HEADERS}" PREFIX "lib" OUTPUT_NAME "fluidsynth" VERSION ${LIB_VERSION_INFO} SOVERSION ${LIB_VERSION_CURRENT} ) endif ( MACOSX_FRAMEWORK ) target_link_libraries ( libfluidsynth ${GLIB_LIBRARIES} ${GMODULE_LIBRARIES} ${LASH_LIBRARIES} ${JACK_LIBRARIES} ${ALSA_LIBRARIES} ${PULSE_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${LIBSNDFILE_LIBRARIES} ${SDL2_LIBRARIES} ${DBUS_LIBRARIES} ${READLINE_LIBS} ${DART_LIBS} ${COREAUDIO_LIBS} ${COREMIDI_LIBS} ${WINDOWS_LIBS} ${MidiShare_LIBS} ${OpenSLES_LIBS} ${OBOE_LIBS} ${LIBFLUID_LIBS} ${LIBINSTPATCH_LIBRARIES} ) # ************ CLI program ************ set ( fluidsynth_SOURCES fluidsynth.c ) add_executable ( fluidsynth ${fluidsynth_SOURCES} ) if ( FLUID_CPPFLAGS ) set_target_properties ( fluidsynth PROPERTIES COMPILE_FLAGS ${FLUID_CPPFLAGS} ) endif ( FLUID_CPPFLAGS ) target_link_libraries ( fluidsynth libfluidsynth ${SYSTEMD_LIBRARIES} ${FLUID_LIBS} ) if ( MACOSX_FRAMEWORK ) install ( TARGETS fluidsynth libfluidsynth RUNTIME DESTINATION ${BIN_INSTALL_DIR} FRAMEWORK DESTINATION ${FRAMEWORK_INSTALL_DIR} ARCHIVE DESTINATION ${FRAMEWORK_INSTALL_DIR} ) else ( MACOSX_FRAMEWORK ) install ( TARGETS fluidsynth libfluidsynth RUNTIME DESTINATION ${BIN_INSTALL_DIR} LIBRARY DESTINATION ${LIB_INSTALL_DIR} ARCHIVE DESTINATION ${LIB_INSTALL_DIR} PUBLIC_HEADER DESTINATION ${INCLUDE_INSTALL_DIR}/fluidsynth ) install ( FILES ${public_main_HEADER} DESTINATION ${INCLUDE_INSTALL_DIR} ) endif ( MACOSX_FRAMEWORK ) # ******* Auto Generated Lookup Tables ****** include(ExternalProject) ExternalProject_Add(gentables DOWNLOAD_COMMAND "" SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/gentables BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/gentables INSTALL_COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gentables/make_tables.exe "${CMAKE_BINARY_DIR}/" ) add_dependencies(libfluidsynth-OBJ gentables) fluidsynth-2.1.1/src/bindings/000077500000000000000000000000001362231004000162575ustar00rootroot00000000000000fluidsynth-2.1.1/src/bindings/fluid_cmd.c000066400000000000000000003564351362231004000203710ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_cmd.h" #include "fluid_synth.h" #include "fluid_settings.h" #include "fluid_hash.h" #include "fluid_midi_router.h" #include "fluid_sfont.h" #include "fluid_chan.h" /* FIXME: LADSPA used to need a lot of parameters on a single line. This is not * necessary anymore, so the limits below could probably be reduced */ #define MAX_TOKENS 100 #define MAX_COMMAND_LEN 1024 /* max command length accepted by fluid_command() */ #define FLUID_WORKLINELENGTH 1024 #define FLUID_ENTRY_COMMAND(data) fluid_cmd_handler_t* handler=(fluid_cmd_handler_t*)(data) /* the shell cmd handler struct */ struct _fluid_cmd_handler_t { fluid_synth_t *synth; fluid_midi_router_t *router; fluid_cmd_hash_t *commands; fluid_midi_router_rule_t *cmd_rule; /* Rule currently being processed by shell command handler */ int cmd_rule_type; /* Type of the rule (#fluid_midi_router_rule_type) */ }; struct _fluid_shell_t { fluid_settings_t *settings; fluid_cmd_handler_t *handler; fluid_thread_t *thread; fluid_istream_t in; fluid_ostream_t out; }; static fluid_thread_return_t fluid_shell_run(void *data); static void fluid_shell_init(fluid_shell_t *shell, fluid_settings_t *settings, fluid_cmd_handler_t *handler, fluid_istream_t in, fluid_ostream_t out); static int fluid_handle_voice_count(void *data, int ac, char **av, fluid_ostream_t out); void fluid_shell_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "shell.prompt", "", 0); fluid_settings_register_int(settings, "shell.port", 9800, 1, 65535, 0); } /** the table of all handled commands */ static const fluid_cmd_t fluid_commands[] = { /* general commands */ { "help", "general", fluid_handle_help, "help Shows help topics ('help TOPIC' for more info)" }, { "quit", "general", fluid_handle_quit, "quit Quit the synthesizer" }, { "source", "general", fluid_handle_source, "source filename Loads a file and parse every line as a command" }, /* event commands */ { "noteon", "event", fluid_handle_noteon, "noteon chan key vel Sends noteon" }, { "noteoff", "event", fluid_handle_noteoff, "noteoff chan key Sends noteoff" }, { "pitch_bend", "event", fluid_handle_pitch_bend, "pitch_bend chan offset Bends pitch" }, { "pitch_bend_range", "event", fluid_handle_pitch_bend_range, "pitch_bend_range chn range Sets pitch bend range for the given midi channel" }, { "cc", "event", fluid_handle_cc, "cc chan ctrl value Sends control-change message" }, { "prog", "event", fluid_handle_prog, "prog chan num Sends program-change message" }, { "select", "event", fluid_handle_select, "select chan sfont bank prog Combination of bank-select and program-change" }, { "load", "general", fluid_handle_load, "load file [reset] [bankofs] Loads SoundFont (reset=0|1, def 1; bankofs=n, def 0)" }, { "unload", "general", fluid_handle_unload, "unload id [reset] Unloads SoundFont by ID (reset=0|1, default 1)" }, { "reload", "general", fluid_handle_reload, "reload id Reload the SoundFont with the specified ID" }, { "fonts", "general", fluid_handle_fonts, "fonts Display the list of loaded SoundFonts" }, { "inst", "general", fluid_handle_inst, "inst font Print out the available instruments for the font" }, { "channels", "general", fluid_handle_channels, "channels [-verbose] Print out preset of all channels" }, { "interp", "general", fluid_handle_interp, "interp num Choose interpolation method for all channels" }, { "interpc", "general", fluid_handle_interpc, "interpc chan num Choose interpolation method for one channel" }, /* polymono commands */ { "basicchannels", "polymono", fluid_handle_basicchannels, "basicchannels Prints the list of basic channels" }, { "resetbasicchannels", "polymono", fluid_handle_resetbasicchannels, "resetbasicchannels [chan1 chan2..] Resets all or some basic channels" }, { "setbasicchannels", "polymono", fluid_handle_setbasicchannels, "setbasicchannels [chan mode val...] Sets default, adds basic channels" }, { "channelsmode", "polymono", fluid_handle_channelsmode, "channelsmode [chan1 chan2..] Prints channels mode" }, { "legatomode", "polymono", fluid_handle_legatomode, "legatomode [chan1 chan2..] Prints channels legato mode" }, { "setlegatomode", "polymono", fluid_handle_setlegatomode, "setlegatomode chan mode [chan mode..] Sets legato mode" }, { "portamentomode", "polymono", fluid_handle_portamentomode, "portamentomode [chan1 chan2..] Prints channels portamento mode" }, { "setportamentomode", "polymono", fluid_handle_setportamentomode, "setportamentomode chan mode [chan mode..] Sets portamento mode" }, { "breathmode", "polymono", fluid_handle_breathmode, "breathmode [chan1 chan2..] Prints channels breath mode" }, { "setbreathmode", "polymono", fluid_handle_setbreathmode, "setbreathmode chan poly(1/0) mono(1/0) breath_sync(1/0) [..] Sets breath mode" }, /* reverb commands */ { "rev_preset", "reverb", fluid_handle_reverbpreset, "rev_preset num Load preset num into the reverb unit" }, { "rev_setroomsize", "reverb", fluid_handle_reverbsetroomsize, "rev_setroomsize num Change reverb room size" }, { "rev_setdamp", "reverb", fluid_handle_reverbsetdamp, "rev_setdamp num Change reverb damping" }, { "rev_setwidth", "reverb", fluid_handle_reverbsetwidth, "rev_setwidth num Change reverb width" }, { "rev_setlevel", "reverb", fluid_handle_reverbsetlevel, "rev_setlevel num Change reverb level" }, { "reverb", "reverb", fluid_handle_reverb, "reverb [0|1|on|off] Turn the reverb on or off" }, /* chorus commands */ { "cho_set_nr", "chorus", fluid_handle_chorusnr, "cho_set_nr n Use n delay lines (default 3)" }, { "cho_set_level", "chorus", fluid_handle_choruslevel, "cho_set_level num Set output level of each chorus line to num" }, { "cho_set_speed", "chorus", fluid_handle_chorusspeed, "cho_set_speed num Set mod speed of chorus to num (Hz)" }, { "cho_set_depth", "chorus", fluid_handle_chorusdepth, "cho_set_depth num Set chorus modulation depth to num (ms)" }, { "chorus", "chorus", fluid_handle_chorus, "chorus [0|1|on|off] Turn the chorus on or off" }, { "gain", "general", fluid_handle_gain, "gain value Set the master gain (0 < gain < 5)" }, { "voice_count", "general", fluid_handle_voice_count, "voice_count Get number of active synthesis voices" }, /* tuning commands */ { "tuning", "tuning", fluid_handle_tuning, "tuning name bank prog Create a tuning with name, bank number, \n" " and program number (0 <= bank,prog <= 127)" }, { "tune", "tuning", fluid_handle_tune, "tune bank prog key pitch Tune a key" }, { "settuning", "tuning", fluid_handle_settuning, "settuning chan bank prog Set the tuning for a MIDI channel" }, { "resettuning", "tuning", fluid_handle_resettuning, "resettuning chan Restore the default tuning of a MIDI channel" }, { "tunings", "tuning", fluid_handle_tunings, "tunings Print the list of available tunings" }, { "dumptuning", "tuning", fluid_handle_dumptuning, "dumptuning bank prog Print the pitch details of the tuning" }, { "reset", "general", fluid_handle_reset, "reset System reset (all notes off, reset controllers)" }, /* settings commands */ { "set", "settings", fluid_handle_set, "set name value Set the value of a setting (must be a real-time setting to take effect immediately)" }, { "get", "settings", fluid_handle_get, "get name Get the value of a setting" }, { "info", "settings", fluid_handle_info, "info name Get information about a setting" }, { "settings", "settings", fluid_handle_settings, "settings Print out all settings" }, { "echo", "general", fluid_handle_echo, "echo arg Print arg" }, /* Sleep command, useful to insert a delay between commands */ { "sleep", "general", fluid_handle_sleep, "sleep duration sleep duration (in ms)" }, /* LADSPA-related commands */ #ifdef LADSPA { "ladspa_effect", "ladspa", fluid_handle_ladspa_effect, "ladspa_effect Create a new effect from a LADSPA plugin" }, { "ladspa_link", "ladspa", fluid_handle_ladspa_link, "ladspa_link Connect an effect port to a host port or buffer" }, { "ladspa_buffer", "ladspa", fluid_handle_ladspa_buffer, "ladspa_buffer Create a LADSPA buffer" }, { "ladspa_set", "ladspa", fluid_handle_ladspa_set, "ladspa_set Set the value of an effect control port" }, { "ladspa_check", "ladspa", fluid_handle_ladspa_check, "ladspa_check Check LADSPA configuration" }, { "ladspa_start", "ladspa", fluid_handle_ladspa_start, "ladspa_start Start LADSPA effects" }, { "ladspa_stop", "ladspa", fluid_handle_ladspa_stop, "ladspa_stop Stop LADSPA effect unit" }, { "ladspa_reset", "ladspa", fluid_handle_ladspa_reset, "ladspa_reset Stop and reset LADSPA effects" }, #endif /* router commands */ { "router_clear", "router", fluid_handle_router_clear, "router_clear Clears all routing rules from the midi router" }, { "router_default", "router", fluid_handle_router_default, "router_default Resets the midi router to default state" }, { "router_begin", "router", fluid_handle_router_begin, "router_begin [note|cc|prog|pbend|cpress|kpress]: Starts a new routing rule" }, { "router_chan", "router", fluid_handle_router_chan, "router_chan min max mul add filters and maps midi channels on current rule" }, { "router_par1", "router", fluid_handle_router_par1, "router_par1 min max mul add filters and maps parameter 1 (key/ctrl nr)" }, { "router_par2", "router", fluid_handle_router_par2, "router_par2 min max mul add filters and maps parameter 2 (vel/cc val)" }, { "router_end", "router", fluid_handle_router_end, "router_end closes and commits the current routing rule" }, #if WITH_PROFILING /* Profiling commands */ { "profile", "profile", fluid_handle_profile, "profile Prints default parameters used by prof_start" }, { "prof_set_notes", "profile", fluid_handle_prof_set_notes, "prof_set_notes nbr [bank prog] Sets notes number generated by prof_start" }, { "prof_set_print", "profile", fluid_handle_prof_set_print, "prof_set_print mode Sets print mode (0:simple, 1:full infos)" }, { "prof_start", "profile", fluid_handle_prof_start, "prof_start [n_prof [dur]] Starts n_prof measures of duration(ms) each" } #endif }; /** * Process a string command. * NOTE: FluidSynth 1.0.8 and above no longer modifies the 'cmd' string. * @param handler FluidSynth command handler * @param cmd Command string (NOTE: Gets modified by FluidSynth prior to 1.0.8) * @param out Output stream to display command response to * @return Integer value corresponding to: -1 on command error, 0 on success, * 1 if 'cmd' is a comment or is empty and -2 if quit was issued */ int fluid_command(fluid_cmd_handler_t *handler, const char *cmd, fluid_ostream_t out) { int result, num_tokens = 0; char **tokens = NULL; if(cmd[0] == '#' || cmd[0] == '\0') { return 1; } if(!g_shell_parse_argv(cmd, &num_tokens, &tokens, NULL)) { fluid_ostream_printf(out, "Error parsing command\n"); return FLUID_FAILED; } result = fluid_cmd_handler_handle(handler, num_tokens, &tokens[0], out); g_strfreev(tokens); return result; } /** * Create a new FluidSynth command shell. * @param settings Setting parameters to use with the shell * @param handler Command handler * @param in Input stream * @param out Output stream * @param thread TRUE if shell should be run in a separate thread, FALSE to run * it in the current thread (function blocks until "quit") * @return New shell instance or NULL on error */ fluid_shell_t * new_fluid_shell(fluid_settings_t *settings, fluid_cmd_handler_t *handler, fluid_istream_t in, fluid_ostream_t out, int thread) { fluid_shell_t *shell = FLUID_NEW(fluid_shell_t); if(shell == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } fluid_shell_init(shell, settings, handler, in, out); if(thread) { shell->thread = new_fluid_thread("shell", fluid_shell_run, shell, 0, TRUE); if(shell->thread == NULL) { delete_fluid_shell(shell); return NULL; } } else { shell->thread = NULL; fluid_shell_run(shell); } return shell; } static void fluid_shell_init(fluid_shell_t *shell, fluid_settings_t *settings, fluid_cmd_handler_t *handler, fluid_istream_t in, fluid_ostream_t out) { shell->settings = settings; shell->handler = handler; shell->in = in; shell->out = out; } /** * Delete a FluidSynth command shell. * @param shell Command shell instance */ void delete_fluid_shell(fluid_shell_t *shell) { fluid_return_if_fail(shell != NULL); if(shell->thread != NULL) { delete_fluid_thread(shell->thread); } FLUID_FREE(shell); } static fluid_thread_return_t fluid_shell_run(void *data) { fluid_shell_t *shell = (fluid_shell_t *)data; char workline[FLUID_WORKLINELENGTH]; char *prompt = NULL; int cont = 1; int errors = FALSE; int n; if(shell->settings) { fluid_settings_dupstr(shell->settings, "shell.prompt", &prompt); /* ++ alloc prompt */ } /* handle user input */ while(cont) { n = fluid_istream_readline(shell->in, shell->out, prompt ? prompt : "", workline, FLUID_WORKLINELENGTH); if(n < 0) { FLUID_LOG(FLUID_PANIC, "An error occurred while reading from stdin."); break; } /* handle the command */ switch(fluid_command(shell->handler, workline, shell->out)) { case 1: /* empty line or comment */ break; case FLUID_FAILED: /* erroneous command */ errors = TRUE; case FLUID_OK: /* valid command */ break; case -2: /* quit */ cont = 0; break; } if(n == 0) { FLUID_LOG(FLUID_INFO, "Received EOF while reading commands, exiting the shell."); break; } } FLUID_FREE(prompt); /* -- free prompt */ /* return FLUID_THREAD_RETURN_VALUE on success, something else on failure */ return errors ? (fluid_thread_return_t)(-1) : FLUID_THREAD_RETURN_VALUE; } /** * A convenience function to create a shell interfacing to standard input/output * console streams. * @param settings Settings instance for the shell * @param handler Command handler callback */ void fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler) { fluid_shell_t shell; fluid_shell_init(&shell, settings, handler, fluid_get_stdin(), fluid_get_stdout()); fluid_shell_run(&shell); } /** * Execute shell commands in a file. * @param handler Command handler callback * @param filename File name * @return 0 on success, a negative value on error */ int fluid_source(fluid_cmd_handler_t *handler, const char *filename) { int file; fluid_shell_t shell; int result; #ifdef WIN32 file = _open(filename, _O_RDONLY); #else file = open(filename, O_RDONLY); #endif if(file < 0) { return file; } fluid_shell_init(&shell, NULL, handler, file, fluid_get_stdout()); result = (fluid_shell_run(&shell) == FLUID_THREAD_RETURN_VALUE) ? 0 : -1; #ifdef WIN32 _close(file); #else close(file); #endif return result; } /** * Get the user specific FluidSynth command file name. * @param buf Caller supplied string buffer to store file name to. * @param len Length of \a buf * @return Returns \a buf pointer or NULL if no user command file for this system type. */ char * fluid_get_userconf(char *buf, int len) { const char *home = NULL; const char *config_file; #if defined(WIN32) home = getenv("USERPROFILE"); config_file = "\\fluidsynth.cfg"; #elif !defined(MACOS9) home = getenv("HOME"); config_file = "/.fluidsynth"; #endif if(home == NULL) { return NULL; } else { FLUID_SNPRINTF(buf, len, "%s%s", home, config_file); return buf; } } /** * Get the system FluidSynth command file name. * @param buf Caller supplied string buffer to store file name to. * @param len Length of \a buf * @return Returns \a buf pointer or NULL if no system command file for this system type. */ char * fluid_get_sysconf(char *buf, int len) { #if defined(WIN32) || defined(MACOS9) return NULL; #else FLUID_SNPRINTF(buf, len, "/etc/fluidsynth.conf"); return buf; #endif } /* * handlers */ int fluid_handle_noteon(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 3) { fluid_ostream_printf(out, "noteon: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1]) || !fluid_is_number(av[2])) { fluid_ostream_printf(out, "noteon: invalid argument\n"); return FLUID_FAILED; } return fluid_synth_noteon(handler->synth, atoi(av[0]), atoi(av[1]), atoi(av[2])); } int fluid_handle_noteoff(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 2) { fluid_ostream_printf(out, "noteoff: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1])) { fluid_ostream_printf(out, "noteon: invalid argument\n"); return FLUID_FAILED; } return fluid_synth_noteoff(handler->synth, atoi(av[0]), atoi(av[1])); } int fluid_handle_pitch_bend(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 2) { fluid_ostream_printf(out, "pitch_bend: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1])) { fluid_ostream_printf(out, "pitch_bend: invalid argument\n"); return FLUID_FAILED; } return fluid_synth_pitch_bend(handler->synth, atoi(av[0]), atoi(av[1])); } int fluid_handle_pitch_bend_range(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int channum; int value; if(ac < 2) { fluid_ostream_printf(out, "pitch_bend_range: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1])) { fluid_ostream_printf(out, "pitch_bend_range: invalid argument\n"); return FLUID_FAILED; } channum = atoi(av[0]); value = atoi(av[1]); fluid_channel_set_pitch_wheel_sensitivity(handler->synth->channel[channum], value); return FLUID_OK; } int fluid_handle_cc(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 3) { fluid_ostream_printf(out, "cc: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1]) || !fluid_is_number(av[2])) { fluid_ostream_printf(out, "cc: invalid argument\n"); return FLUID_FAILED; } return fluid_synth_cc(handler->synth, atoi(av[0]), atoi(av[1]), atoi(av[2])); } int fluid_handle_prog(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 2) { fluid_ostream_printf(out, "prog: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1])) { fluid_ostream_printf(out, "prog: invalid argument\n"); return FLUID_FAILED; } return fluid_synth_program_change(handler->synth, atoi(av[0]), atoi(av[1])); } int fluid_handle_select(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int sfont_id; int chan; int bank; int prog; if(ac < 4) { fluid_ostream_printf(out, "preset: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0]) || !fluid_is_number(av[1]) || !fluid_is_number(av[2]) || !fluid_is_number(av[3])) { fluid_ostream_printf(out, "preset: invalid argument\n"); return FLUID_FAILED; } chan = atoi(av[0]); sfont_id = atoi(av[1]); bank = atoi(av[2]); prog = atoi(av[3]); if(sfont_id != 0) { return fluid_synth_program_select(handler->synth, chan, sfont_id, bank, prog); } else { if(fluid_synth_bank_select(handler->synth, chan, bank) == FLUID_OK) { return fluid_synth_program_change(handler->synth, chan, prog); } return FLUID_FAILED; } } int fluid_handle_inst(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int font; fluid_sfont_t *sfont; fluid_preset_t *preset; int offset; if(ac < 1) { fluid_ostream_printf(out, "inst: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "inst: invalid argument\n"); return FLUID_FAILED; } font = atoi(av[0]); sfont = fluid_synth_get_sfont_by_id(handler->synth, font); offset = fluid_synth_get_bank_offset(handler->synth, font); if(sfont == NULL) { fluid_ostream_printf(out, "inst: invalid font number\n"); return FLUID_FAILED; } fluid_sfont_iteration_start(sfont); while((preset = fluid_sfont_iteration_next(sfont)) != NULL) { fluid_ostream_printf(out, "%03d-%03d %s\n", fluid_preset_get_banknum(preset) + offset, fluid_preset_get_num(preset), fluid_preset_get_name(preset)); } return FLUID_OK; } int fluid_handle_channels(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_preset_t *preset; int verbose = 0; int i; if(ac > 0 && FLUID_STRCMP(av[0], "-verbose") == 0) { verbose = 1; } for(i = 0; i < fluid_synth_count_midi_channels(handler->synth); i++) { preset = fluid_synth_get_channel_preset(handler->synth, i); if(preset == NULL) { fluid_ostream_printf(out, "chan %d, no preset\n", i); } else if(!verbose) { fluid_ostream_printf(out, "chan %d, %s\n", i, fluid_preset_get_name(preset)); } else { fluid_ostream_printf(out, "chan %d, sfont %d, bank %d, preset %d, %s\n", i, fluid_sfont_get_id(preset->sfont), fluid_preset_get_banknum(preset), fluid_preset_get_num(preset), fluid_preset_get_name(preset)); } } return FLUID_OK; } int fluid_handle_load(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); char buf[1024]; int id; int reset = 1; int offset = 0; if(ac < 1) { fluid_ostream_printf(out, "load: too few arguments\n"); return FLUID_FAILED; } if(ac == 2) { reset = atoi(av[1]); } if(ac == 3) { offset = atoi(av[2]); } /* Load the SoundFont without resetting the programs. The reset will * be done later (if requested). */ id = fluid_synth_sfload(handler->synth, fluid_expand_path(av[0], buf, 1024), 0); if(id == -1) { fluid_ostream_printf(out, "failed to load the SoundFont\n"); return FLUID_FAILED; } else { fluid_ostream_printf(out, "loaded SoundFont has ID %d\n", id); } if(offset) { fluid_synth_set_bank_offset(handler->synth, id, offset); } /* The reset should be done after the offset is set. */ if(reset) { fluid_synth_program_reset(handler->synth); } return FLUID_OK; } int fluid_handle_unload(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int reset = 1; if(ac < 1) { fluid_ostream_printf(out, "unload: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "unload: expected a number as argument\n"); return FLUID_FAILED; } if(ac == 2) { reset = atoi(av[1]); } if(fluid_synth_sfunload(handler->synth, atoi(av[0]), reset) != 0) { fluid_ostream_printf(out, "failed to unload the SoundFont\n"); return FLUID_FAILED; } return FLUID_OK; } int fluid_handle_reload(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 1) { fluid_ostream_printf(out, "reload: too few arguments\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "reload: expected a number as argument\n"); return FLUID_FAILED; } if(fluid_synth_sfreload(handler->synth, atoi(av[0])) == -1) { fluid_ostream_printf(out, "failed to reload the SoundFont\n"); return FLUID_FAILED; } return FLUID_OK; } int fluid_handle_fonts(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int i; fluid_sfont_t *sfont; int num; num = fluid_synth_sfcount(handler->synth); if(num == 0) { fluid_ostream_printf(out, "no SoundFont loaded (try load)\n"); return FLUID_OK; } fluid_ostream_printf(out, "ID Name\n"); for(i = 0; i < num; i++) { sfont = fluid_synth_get_sfont(handler->synth, i); if(sfont) { fluid_ostream_printf(out, "%2d %s\n", fluid_sfont_get_id(sfont), fluid_sfont_get_name(sfont)); } else { fluid_ostream_printf(out, "sfont is \"NULL\" for index %d\n", i); } } return FLUID_OK; } /* Purpose: * Response to 'rev_preset' command. * Load the values from a reverb preset into the reverb unit. */ int fluid_handle_reverbpreset(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int reverb_preset_number; fluid_ostream_printf(out, "rev_preset is deprecated and will be removed in a future release!\n"); if(ac < 1) { fluid_ostream_printf(out, "rev_preset: too few arguments\n"); return FLUID_FAILED; } reverb_preset_number = atoi(av[0]); if(fluid_synth_set_reverb_preset(handler->synth, reverb_preset_number) != FLUID_OK) { fluid_ostream_printf(out, "rev_preset: Failed. Parameter out of range?\n"); return FLUID_FAILED; }; return FLUID_OK; } /* Purpose: * Response to 'rev_setroomsize' command. * Load the new room size into the reverb unit. */ int fluid_handle_reverbsetroomsize(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t room_size; if(ac < 1) { fluid_ostream_printf(out, "rev_setroomsize: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "rev_setroomsize is deprecated! Use 'set synth.reverb.room-size %s' instead.\n", av[0]); room_size = atof(av[0]); if(room_size < 0) { fluid_ostream_printf(out, "rev_setroomsize: Room size must be positive!\n"); return FLUID_FAILED; } if(room_size > 1.0) { fluid_ostream_printf(out, "rev_setroomsize: Room size too big!\n"); return FLUID_FAILED; } fluid_synth_set_reverb_roomsize(handler->synth, room_size); return FLUID_OK; } /* Purpose: * Response to 'rev_setdamp' command. * Load the new damp factor into the reverb unit. */ int fluid_handle_reverbsetdamp(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t damp; if(ac < 1) { fluid_ostream_printf(out, "rev_setdamp: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "rev_setdamp is deprecated! Use 'set synth.reverb.damp %s' instead.\n", av[0]); damp = atof(av[0]); if((damp < 0.0f) || (damp > 1)) { fluid_ostream_printf(out, "rev_setdamp: damp must be between 0 and 1!\n"); return FLUID_FAILED; } fluid_synth_set_reverb_damp(handler->synth, damp); return FLUID_OK; } /* Purpose: * Response to 'rev_setwidth' command. * Load the new width into the reverb unit. */ int fluid_handle_reverbsetwidth(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t width; if(ac < 1) { fluid_ostream_printf(out, "rev_setwidth: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "rev_setroomsize is deprecated! Use 'set synth.reverb.width %s' instead.\n", av[0]); width = atof(av[0]); if((width < 0) || (width > 100)) { fluid_ostream_printf(out, "rev_setroomsize: Too wide! (0..100)\n"); return FLUID_FAILED; } fluid_synth_set_reverb_width(handler->synth, width); return FLUID_OK; } /* Purpose: * Response to 'rev_setlevel' command. * Load the new level into the reverb unit. */ int fluid_handle_reverbsetlevel(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t level; if(ac < 1) { fluid_ostream_printf(out, "rev_setlevel: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "rev_setlevel is deprecated! Use 'set synth.reverb.level %s' instead.\n", av[0]); level = atof(av[0]); if(fabs(level) > 30) { fluid_ostream_printf(out, "rev_setlevel: Value too high! (Value of 10 =+20 dB)\n"); return FLUID_FAILED; } fluid_synth_set_reverb_level(handler->synth, level); return FLUID_OK; } /* Purpose: * Response to 'reverb' command. * Change the FLUID_REVERB flag in the synth */ int fluid_handle_reverb(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 1) { fluid_ostream_printf(out, "reverb: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "reverb is deprecated! Use 'set synth.reverb.active %s' instead.\n", av[0]); if((FLUID_STRCMP(av[0], "0") == 0) || (FLUID_STRCMP(av[0], "off") == 0)) { fluid_synth_set_reverb_on(handler->synth, 0); } else if((FLUID_STRCMP(av[0], "1") == 0) || (FLUID_STRCMP(av[0], "on") == 0)) { fluid_synth_set_reverb_on(handler->synth, 1); } else { fluid_ostream_printf(out, "reverb: invalid arguments %s [0|1|on|off]", av[0]); return FLUID_FAILED; } return FLUID_OK; } /* Purpose: * Response to 'chorus_setnr' command */ int fluid_handle_chorusnr(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int nr; if(ac < 1) { fluid_ostream_printf(out, "cho_set_nr: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "cho_set_nr is deprecated! Use 'set synth.chorus.nr %s' instead.\n", av[0]); nr = atoi(av[0]); fluid_synth_set_chorus_nr(handler->synth, nr); return FLUID_OK; } /* Purpose: * Response to 'chorus_setlevel' command */ int fluid_handle_choruslevel(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t level; if(ac < 1) { fluid_ostream_printf(out, "cho_set_level: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "cho_set_level is deprecated! Use 'set synth.chorus.level %s' instead.\n", av[0]); level = atof(av[0]); fluid_synth_set_chorus_level(handler->synth, level); return FLUID_OK; } /* Purpose: * Response to 'chorus_setspeed' command */ int fluid_handle_chorusspeed(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t speed; if(ac < 1) { fluid_ostream_printf(out, "cho_set_speed: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "cho_set_speed is deprecated! Use 'set synth.chorus.speed %s' instead.\n", av[0]); speed = atof(av[0]); fluid_synth_set_chorus_speed(handler->synth, speed); return FLUID_OK; } /* Purpose: * Response to 'chorus_setdepth' command */ int fluid_handle_chorusdepth(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_real_t depth; if(ac < 1) { fluid_ostream_printf(out, "cho_set_depth: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "cho_set_depth is deprecated! Use 'set synth.chorus.depth %s' instead.\n", av[0]); depth = atof(av[0]); fluid_synth_set_chorus_depth(handler->synth, depth); return FLUID_OK; } int fluid_handle_chorus(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 1) { fluid_ostream_printf(out, "chorus: too few arguments\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "chorus is deprecated! Use 'set synth.chorus.active %s' instead.\n", av[0]); if((FLUID_STRCMP(av[0], "0") == 0) || (FLUID_STRCMP(av[0], "off") == 0)) { fluid_synth_set_chorus_on(handler->synth, 0); } else if((FLUID_STRCMP(av[0], "1") == 0) || (FLUID_STRCMP(av[0], "on") == 0)) { fluid_synth_set_chorus_on(handler->synth, 1); } else { fluid_ostream_printf(out, "chorus: invalid arguments %s [0|1|on|off]", av[0]); return FLUID_FAILED; } return FLUID_OK; } /* Purpose: * Response to the 'echo' command. * The command itself is useful, when the synth is used via TCP/IP. * It can signal for example, that a list of commands has been processed. */ int fluid_handle_echo(void *data, int ac, char **av, fluid_ostream_t out) { if(ac < 1) { fluid_ostream_printf(out, "echo: too few arguments.\n"); return FLUID_FAILED; } fluid_ostream_printf(out, "%s\n", av[0]); return FLUID_OK; } /* Purpose: * Sleep during a time in ms * The command itself is useful to insert a delay between commands. * It can help for example to build a small song using noteon/noteoff commands * in a command file. */ int fluid_handle_sleep(void *data, int ac, char **av, fluid_ostream_t out) { if(ac < 1) { fluid_ostream_printf(out, "sleep: too few arguments.\n"); return -1; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "sleep: argument should be a number in ms.\n"); return -1; } fluid_msleep(atoi(av[0])); /* delay in milliseconds */ return 0; } int fluid_handle_source(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 1) { fluid_ostream_printf(out, "source: too few arguments.\n"); return FLUID_FAILED; } fluid_source(handler, av[0]); return FLUID_OK; } /* Purpose: * Response to 'gain' command. */ int fluid_handle_gain(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); float gain; if(ac < 1) { fluid_ostream_printf(out, "gain: too few arguments.\n"); return FLUID_FAILED; } gain = atof(av[0]); if((gain < 0.0f) || (gain > 5.0f)) { fluid_ostream_printf(out, "gain: value should be between '0' and '5'.\n"); return FLUID_FAILED; }; fluid_synth_set_gain(handler->synth, gain); return FLUID_OK; } /* Response to voice_count command */ static int fluid_handle_voice_count(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ostream_printf(out, "voice_count: %d\n", fluid_synth_get_active_voice_count(handler->synth)); return FLUID_OK; } /* Purpose: * Response to 'interp' command. */ int fluid_handle_interp(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int interp; int chan = -1; /* -1: Set all channels */ if(ac < 1) { fluid_ostream_printf(out, "interp: too few arguments.\n"); return FLUID_FAILED; } interp = atoi(av[0]); if((interp < 0) || (interp > FLUID_INTERP_HIGHEST)) { fluid_ostream_printf(out, "interp: Bad value\n"); return FLUID_FAILED; }; fluid_synth_set_interp_method(handler->synth, chan, interp); return FLUID_OK; } /* Purpose: * Response to 'interp' command. */ int fluid_handle_interpc(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int interp; int chan; if(ac < 2) { fluid_ostream_printf(out, "interpc: too few arguments.\n"); return FLUID_FAILED; } chan = atoi(av[0]); interp = atoi(av[1]); if((chan < 0) || (chan >= fluid_synth_count_midi_channels(handler->synth))) { fluid_ostream_printf(out, "interp: Bad value for channel number.\n"); return FLUID_FAILED; }; if((interp < 0) || (interp > FLUID_INTERP_HIGHEST)) { fluid_ostream_printf(out, "interp: Bad value for interpolation method.\n"); return FLUID_FAILED; }; fluid_synth_set_interp_method(handler->synth, chan, interp); return FLUID_OK; } int fluid_handle_tuning(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); char *name; int bank, prog; if(ac < 3) { fluid_ostream_printf(out, "tuning: too few arguments.\n"); return FLUID_FAILED; } name = av[0]; if(!fluid_is_number(av[1])) { fluid_ostream_printf(out, "tuning: 2nd argument should be a number.\n"); return FLUID_FAILED; } bank = atoi(av[1]); if((bank < 0) || (bank >= 128)) { fluid_ostream_printf(out, "tuning: invalid bank number.\n"); return FLUID_FAILED; }; if(!fluid_is_number(av[2])) { fluid_ostream_printf(out, "tuning: 3rd argument should be a number.\n"); return FLUID_FAILED; } prog = atoi(av[2]); if((prog < 0) || (prog >= 128)) { fluid_ostream_printf(out, "tuning: invalid program number.\n"); return FLUID_FAILED; }; fluid_synth_activate_key_tuning(handler->synth, bank, prog, name, NULL, FALSE); return FLUID_OK; } int fluid_handle_tune(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int bank, prog, key; double pitch; if(ac < 4) { fluid_ostream_printf(out, "tune: too few arguments.\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "tune: 1st argument should be a number.\n"); return FLUID_FAILED; } bank = atoi(av[0]); if((bank < 0) || (bank >= 128)) { fluid_ostream_printf(out, "tune: invalid bank number.\n"); return FLUID_FAILED; }; if(!fluid_is_number(av[1])) { fluid_ostream_printf(out, "tune: 2nd argument should be a number.\n"); return FLUID_FAILED; } prog = atoi(av[1]); if((prog < 0) || (prog >= 128)) { fluid_ostream_printf(out, "tune: invalid program number.\n"); return FLUID_FAILED; }; if(!fluid_is_number(av[2])) { fluid_ostream_printf(out, "tune: 3rd argument should be a number.\n"); return FLUID_FAILED; } key = atoi(av[2]); if((key < 0) || (key >= 128)) { fluid_ostream_printf(out, "tune: invalid key number.\n"); return FLUID_FAILED; }; pitch = atof(av[3]); if(pitch < 0.0f) { fluid_ostream_printf(out, "tune: invalid pitch.\n"); return FLUID_FAILED; }; fluid_synth_tune_notes(handler->synth, bank, prog, 1, &key, &pitch, 0); return FLUID_OK; } int fluid_handle_settuning(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int chan, bank, prog; if(ac < 3) { fluid_ostream_printf(out, "settuning: too few arguments.\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "tune: 1st argument should be a number.\n"); return FLUID_FAILED; } chan = atoi(av[0]); if((chan < 0) || (chan >= fluid_synth_count_midi_channels(handler->synth))) { fluid_ostream_printf(out, "tune: invalid channel number.\n"); return FLUID_FAILED; }; if(!fluid_is_number(av[1])) { fluid_ostream_printf(out, "tuning: 2nd argument should be a number.\n"); return FLUID_FAILED; } bank = atoi(av[1]); if((bank < 0) || (bank >= 128)) { fluid_ostream_printf(out, "tuning: invalid bank number.\n"); return FLUID_FAILED; }; if(!fluid_is_number(av[2])) { fluid_ostream_printf(out, "tuning: 3rd argument should be a number.\n"); return FLUID_FAILED; } prog = atoi(av[2]); if((prog < 0) || (prog >= 128)) { fluid_ostream_printf(out, "tuning: invalid program number.\n"); return FLUID_FAILED; }; fluid_synth_activate_tuning(handler->synth, chan, bank, prog, FALSE); return FLUID_OK; } int fluid_handle_resettuning(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int chan; if(ac < 1) { fluid_ostream_printf(out, "resettuning: too few arguments.\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "tune: 1st argument should be a number.\n"); return FLUID_FAILED; } chan = atoi(av[0]); if((chan < 0) || (chan >= fluid_synth_count_midi_channels(handler->synth))) { fluid_ostream_printf(out, "tune: invalid channel number.\n"); return FLUID_FAILED; }; fluid_synth_deactivate_tuning(handler->synth, chan, FALSE); return FLUID_OK; } int fluid_handle_tunings(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int bank, prog; char name[256]; int count = 0; fluid_synth_tuning_iteration_start(handler->synth); while(fluid_synth_tuning_iteration_next(handler->synth, &bank, &prog)) { fluid_synth_tuning_dump(handler->synth, bank, prog, name, 256, NULL); fluid_ostream_printf(out, "%03d-%03d %s\n", bank, prog, name); count++; } if(count == 0) { fluid_ostream_printf(out, "No tunings available\n"); } return FLUID_OK; } int fluid_handle_dumptuning(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int bank, prog, i, res; double pitch[128]; char name[256]; if(ac < 2) { fluid_ostream_printf(out, "dumptuning: too few arguments.\n"); return FLUID_FAILED; } if(!fluid_is_number(av[0])) { fluid_ostream_printf(out, "dumptuning: 1st argument should be a number.\n"); return FLUID_FAILED; } bank = atoi(av[0]); if((bank < 0) || (bank >= 128)) { fluid_ostream_printf(out, "dumptuning: invalid bank number.\n"); return FLUID_FAILED; }; if(!fluid_is_number(av[1])) { fluid_ostream_printf(out, "dumptuning: 2nd argument should be a number.\n"); return FLUID_FAILED; } prog = atoi(av[1]); if((prog < 0) || (prog >= 128)) { fluid_ostream_printf(out, "dumptuning: invalid program number.\n"); return FLUID_FAILED; }; res = fluid_synth_tuning_dump(handler->synth, bank, prog, name, 256, pitch); if(FLUID_OK != res) { fluid_ostream_printf(out, "Tuning %03d-%03d does not exist.\n", bank, prog); return FLUID_FAILED; } fluid_ostream_printf(out, "%03d-%03d %s:\n", bank, prog, name); for(i = 0; i < 128; i++) { fluid_ostream_printf(out, "key %03d, pitch %5.2f\n", i, pitch[i]); } return FLUID_OK; } int fluid_handle_set(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); int hints; int ival; int ret = FLUID_FAILED; if(ac < 2) { fluid_ostream_printf(out, "set: Too few arguments.\n"); return ret; } switch(fluid_settings_get_type(handler->synth->settings, av[0])) { case FLUID_NO_TYPE: fluid_ostream_printf(out, "set: Parameter '%s' not found.\n", av[0]); return ret; case FLUID_INT_TYPE: if(fluid_settings_get_hints(handler->synth->settings, av[0], &hints) == FLUID_OK && hints & FLUID_HINT_TOGGLED) { if(FLUID_STRCASECMP(av[1], "yes") == 0 || FLUID_STRCASECMP(av[1], "true") == 0 || FLUID_STRCASECMP(av[1], "t") == 0) { ival = 1; } else { ival = atoi(av[1]); } } else { ival = atoi(av[1]); } ret = fluid_settings_setint(handler->synth->settings, av[0], ival); break; case FLUID_NUM_TYPE: ret = fluid_settings_setnum(handler->synth->settings, av[0], atof(av[1])); break; case FLUID_STR_TYPE: ret = fluid_settings_setstr(handler->synth->settings, av[0], av[1]); break; case FLUID_SET_TYPE: fluid_ostream_printf(out, "set: Parameter '%s' is a node.\n", av[0]); return FLUID_FAILED; } if(ret == FLUID_FAILED) { fluid_ostream_printf(out, "set: Value out of range. Try 'info %s' for valid ranges\n", av[0]); } if(!fluid_settings_is_realtime(handler->synth->settings, av[0])) { fluid_ostream_printf(out, "Warning: '%s' is not a realtime setting, changes won't take effect.\n", av[0]); } return ret; } int fluid_handle_get(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); if(ac < 1) { fluid_ostream_printf(out, "get: too few arguments.\n"); return FLUID_FAILED; } switch(fluid_settings_get_type(fluid_synth_get_settings(handler->synth), av[0])) { case FLUID_NO_TYPE: fluid_ostream_printf(out, "get: no such setting '%s'.\n", av[0]); return FLUID_FAILED; case FLUID_NUM_TYPE: { double value; fluid_settings_getnum(handler->synth->settings, av[0], &value); fluid_ostream_printf(out, "%.3f\n", value); break; } case FLUID_INT_TYPE: { int value; fluid_settings_getint(handler->synth->settings, av[0], &value); fluid_ostream_printf(out, "%d\n", value); break; } case FLUID_STR_TYPE: { char *s; fluid_settings_dupstr(handler->synth->settings, av[0], &s); /* ++ alloc string */ fluid_ostream_printf(out, "%s\n", s ? s : "NULL"); if(s) { FLUID_FREE(s); /* -- free string */ } break; } case FLUID_SET_TYPE: fluid_ostream_printf(out, "%s is a node\n", av[0]); break; } return FLUID_OK; } struct _fluid_handle_settings_data_t { size_t len; fluid_synth_t *synth; fluid_ostream_t out; }; static void fluid_handle_settings_iter1(void *data, const char *name, int type) { struct _fluid_handle_settings_data_t *d = (struct _fluid_handle_settings_data_t *) data; size_t len = FLUID_STRLEN(name); if(len > d->len) { d->len = len; } } static void fluid_handle_settings_iter2(void *data, const char *name, int type) { struct _fluid_handle_settings_data_t *d = (struct _fluid_handle_settings_data_t *) data; size_t len = FLUID_STRLEN(name); fluid_ostream_printf(d->out, "%s", name); while(len++ < d->len) { fluid_ostream_printf(d->out, " "); } fluid_ostream_printf(d->out, " "); switch(fluid_settings_get_type(fluid_synth_get_settings(d->synth), name)) { case FLUID_NUM_TYPE: { double value; fluid_settings_getnum(d->synth->settings, name, &value); fluid_ostream_printf(d->out, "%.3f\n", value); break; } case FLUID_INT_TYPE: { int value, hints; fluid_settings_getint(d->synth->settings, name, &value); if(fluid_settings_get_hints(d->synth->settings, name, &hints) == FLUID_OK) { if(!(hints & FLUID_HINT_TOGGLED)) { fluid_ostream_printf(d->out, "%d\n", value); } else { fluid_ostream_printf(d->out, "%s\n", value ? "True" : "False"); } } break; } case FLUID_STR_TYPE: { char *s; fluid_settings_dupstr(d->synth->settings, name, &s); /* ++ alloc string */ fluid_ostream_printf(d->out, "%s\n", s ? s : "NULL"); if(s) { FLUID_FREE(s); /* -- free string */ } break; } } } int fluid_handle_settings(void *d, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(d); struct _fluid_handle_settings_data_t data; data.len = 0; data.synth = handler->synth; data.out = out; fluid_settings_foreach(fluid_synth_get_settings(handler->synth), &data, fluid_handle_settings_iter1); fluid_settings_foreach(fluid_synth_get_settings(handler->synth), &data, fluid_handle_settings_iter2); return FLUID_OK; } struct _fluid_handle_option_data_t { int first; fluid_ostream_t out; }; void fluid_handle_print_option(void *data, const char *name, const char *option) { struct _fluid_handle_option_data_t *d = (struct _fluid_handle_option_data_t *) data; if(d->first) { fluid_ostream_printf(d->out, "%s", option); d->first = 0; } else { fluid_ostream_printf(d->out, ", %s", option); } } int fluid_handle_info(void *d, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(d); fluid_settings_t *settings = fluid_synth_get_settings(handler->synth); struct _fluid_handle_option_data_t data; if(ac < 1) { fluid_ostream_printf(out, "info: too few arguments.\n"); return FLUID_FAILED; } switch(fluid_settings_get_type(settings, av[0])) { case FLUID_NO_TYPE: fluid_ostream_printf(out, "info: no such setting '%s'.\n", av[0]); return FLUID_FAILED; case FLUID_NUM_TYPE: { double value, min, max, def; if(fluid_settings_getnum_range(settings, av[0], &min, &max) == FLUID_OK && fluid_settings_getnum(settings, av[0], &value) == FLUID_OK && fluid_settings_getnum_default(settings, av[0], &def) == FLUID_OK) { fluid_ostream_printf(out, "%s:\n", av[0]); fluid_ostream_printf(out, "Type: number\n"); fluid_ostream_printf(out, "Value: %.3f\n", value); fluid_ostream_printf(out, "Minimum value: %.3f\n", min); fluid_ostream_printf(out, "Maximum value: %.3f\n", max); fluid_ostream_printf(out, "Default value: %.3f\n", def); fluid_ostream_printf(out, "Real-time: %s\n", fluid_settings_is_realtime(settings, av[0]) ? "yes" : "no"); } else { fluid_ostream_printf(out, "An error occurred when processing %s\n", av[0]); } break; } case FLUID_INT_TYPE: { int value, min, max, def, hints; if(fluid_settings_getint_range(settings, av[0], &min, &max) == FLUID_OK && fluid_settings_getint(settings, av[0], &value) == FLUID_OK && fluid_settings_get_hints(settings, av[0], &hints) == FLUID_OK && fluid_settings_getint_default(settings, av[0], &def) == FLUID_OK) { fluid_ostream_printf(out, "%s:\n", av[0]); if(!(hints & FLUID_HINT_TOGGLED)) { fluid_ostream_printf(out, "Type: integer\n"); fluid_ostream_printf(out, "Value: %d\n", value); fluid_ostream_printf(out, "Minimum value: %d\n", min); fluid_ostream_printf(out, "Maximum value: %d\n", max); fluid_ostream_printf(out, "Default value: %d\n", def); } else { fluid_ostream_printf(out, "Type: boolean\n"); fluid_ostream_printf(out, "Value: %s\n", value ? "True" : "False"); fluid_ostream_printf(out, "Default value: %s\n", def ? "True" : "False"); } fluid_ostream_printf(out, "Real-time: %s\n", fluid_settings_is_realtime(settings, av[0]) ? "yes" : "no"); } else { fluid_ostream_printf(out, "An error occurred when processing %s\n", av[0]); } break; } case FLUID_STR_TYPE: { char *s; fluid_settings_dupstr(settings, av[0], &s); /* ++ alloc string */ fluid_ostream_printf(out, "%s:\n", av[0]); fluid_ostream_printf(out, "Type: string\n"); fluid_ostream_printf(out, "Value: %s\n", s ? s : "NULL"); fluid_settings_getstr_default(settings, av[0], &s); fluid_ostream_printf(out, "Default value: %s\n", s); if(s) { FLUID_FREE(s); } data.out = out; data.first = 1; fluid_ostream_printf(out, "Options: "); fluid_settings_foreach_option(settings, av[0], &data, fluid_handle_print_option); fluid_ostream_printf(out, "\n"); fluid_ostream_printf(out, "Real-time: %s\n", fluid_settings_is_realtime(settings, av[0]) ? "yes" : "no"); break; } case FLUID_SET_TYPE: fluid_ostream_printf(out, "%s:\n", av[0]); fluid_ostream_printf(out, "Type: node\n"); break; } return FLUID_OK; } int fluid_handle_reset(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_synth_system_reset(handler->synth); return FLUID_OK; } int fluid_handle_quit(void *data, int ac, char **av, fluid_ostream_t out) { fluid_ostream_printf(out, "cheers!\n"); return -2; } int fluid_handle_help(void *data, int ac, char **av, fluid_ostream_t out) { /* Purpose: * Prints the help text for the command line commands. * Can be used as follows: * - help * - help (topic), where (topic) is 'general', 'chorus', etc. * - help all */ char *topic = "help"; /* default, if no topic is given */ int count = 0; unsigned int i; fluid_ostream_printf(out, "\n"); /* 1st argument (optional): help topic */ if(ac >= 1) { topic = av[0]; } if(FLUID_STRCMP(topic, "help") == 0) { /* "help help": Print a list of all topics */ fluid_ostream_printf(out, "*** Help topics:***\n" "help all (prints all topics)\n"); for(i = 0; i < FLUID_N_ELEMENTS(fluid_commands); i++) { int listed_first_time = 1; unsigned int ii; for(ii = 0; ii < i; ii++) { if(FLUID_STRCMP(fluid_commands[i].topic, fluid_commands[ii].topic) == 0) { listed_first_time = 0; }; /* if topic has already been listed */ }; /* for all topics (inner loop) */ if(listed_first_time) { fluid_ostream_printf(out, "help %s\n", fluid_commands[i].topic); }; }; /* for all topics (outer loop) */ } else { /* help (arbitrary topic or "all") */ for(i = 0; i < FLUID_N_ELEMENTS(fluid_commands); i++) { if(fluid_commands[i].help != NULL) { if(FLUID_STRCMP(topic, "all") == 0 || FLUID_STRCMP(topic, fluid_commands[i].topic) == 0) { fluid_ostream_printf(out, "%s\n", fluid_commands[i].help); count++; }; /* if it matches the topic */ }; /* if help text exists */ }; /* foreach command */ if(count == 0) { fluid_ostream_printf(out, "Unknown help topic. Try 'help help'.\n"); }; }; return FLUID_OK; } #define CHECK_VALID_ROUTER(_router, _out) \ if (router == NULL) { \ fluid_ostream_printf(out, "cannot execute router command without a midi router.\n"); \ return FLUID_FAILED; \ } /* Command handler for "router_clear" command */ int fluid_handle_router_clear(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 0) { fluid_ostream_printf(out, "router_clear needs no arguments.\n"); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); fluid_midi_router_clear_rules(router); return FLUID_OK; } /* Command handler for "router_default" command */ int fluid_handle_router_default(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 0) { fluid_ostream_printf(out, "router_default needs no arguments.\n"); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); fluid_midi_router_set_default_rules(router); return FLUID_OK; } /* Command handler for "router_begin" command */ int fluid_handle_router_begin(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 1) { fluid_ostream_printf(out, "router_begin requires [note|cc|prog|pbend|cpress|kpress]\n"); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); if(FLUID_STRCMP(av[0], "note") == 0) { handler->cmd_rule_type = FLUID_MIDI_ROUTER_RULE_NOTE; } else if(FLUID_STRCMP(av[0], "cc") == 0) { handler->cmd_rule_type = FLUID_MIDI_ROUTER_RULE_CC; } else if(FLUID_STRCMP(av[0], "prog") == 0) { handler->cmd_rule_type = FLUID_MIDI_ROUTER_RULE_PROG_CHANGE; } else if(FLUID_STRCMP(av[0], "pbend") == 0) { handler->cmd_rule_type = FLUID_MIDI_ROUTER_RULE_PITCH_BEND; } else if(FLUID_STRCMP(av[0], "cpress") == 0) { handler->cmd_rule_type = FLUID_MIDI_ROUTER_RULE_CHANNEL_PRESSURE; } else if(FLUID_STRCMP(av[0], "kpress") == 0) { handler->cmd_rule_type = FLUID_MIDI_ROUTER_RULE_KEY_PRESSURE; } else { fluid_ostream_printf(out, "router_begin requires [note|cc|prog|pbend|cpress|kpress]\n"); return FLUID_FAILED; } if(handler->cmd_rule) { delete_fluid_midi_router_rule(handler->cmd_rule); } handler->cmd_rule = new_fluid_midi_router_rule(); if(!handler->cmd_rule) { return FLUID_FAILED; } return FLUID_OK; } /* Command handler for "router_end" command */ int fluid_handle_router_end(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 0) { fluid_ostream_printf(out, "router_end needs no arguments.\n"); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); if(!handler->cmd_rule) { fluid_ostream_printf(out, "No active router_begin command.\n"); return FLUID_FAILED; } /* Add the rule */ if(fluid_midi_router_add_rule(router, handler->cmd_rule, handler->cmd_rule_type) != FLUID_OK) { delete_fluid_midi_router_rule(handler->cmd_rule); /* Free on failure */ } handler->cmd_rule = NULL; return FLUID_OK; } /* Command handler for "router_chan" command */ int fluid_handle_router_chan(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 4) { fluid_ostream_printf(out, "router_chan needs four args: min, max, mul, add."); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); if(!handler->cmd_rule) { fluid_ostream_printf(out, "No active router_begin command.\n"); return FLUID_FAILED; } fluid_midi_router_rule_set_chan(handler->cmd_rule, atoi(av[0]), atoi(av[1]), atof(av[2]), atoi(av[3])); return FLUID_OK; } /* Command handler for "router_par1" command */ int fluid_handle_router_par1(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 4) { fluid_ostream_printf(out, "router_par1 needs four args: min, max, mul, add."); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); if(!handler->cmd_rule) { fluid_ostream_printf(out, "No active router_begin command.\n"); return FLUID_FAILED; } fluid_midi_router_rule_set_param1(handler->cmd_rule, atoi(av[0]), atoi(av[1]), atof(av[2]), atoi(av[3])); return FLUID_OK; } /* Command handler for "router_par2" command */ int fluid_handle_router_par2(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_midi_router_t *router = handler->router; if(ac != 4) { fluid_ostream_printf(out, "router_par2 needs four args: min, max, mul, add."); return FLUID_FAILED; } CHECK_VALID_ROUTER(router, out); if(!handler->cmd_rule) { fluid_ostream_printf(out, "No active router_begin command.\n"); return FLUID_FAILED; } fluid_midi_router_rule_set_param2(handler->cmd_rule, atoi(av[0]), atoi(av[1]), atof(av[2]), atoi(av[3])); return FLUID_OK; } /** commands Poly/mono mode *************************************************/ static const char *const mode_name[] = { "poly omni on (0)", "mono omni on (1)", "poly omni off(2)", "mono omni off(3)" }; /* Prints result message for commands: basicchannels, resetbasicchannels. Prints all basic channels and print a warning if there is no basic channel. @param synth the synth instance. @param out output stream. */ static int print_basic_channels(fluid_synth_t *synth, fluid_ostream_t out) { static const char *warning_msg = "Warning: no basic channels. All MIDI channels are disabled.\n" "Make use of setbasicchannels to set at least a default basic channel.\n"; int n_chan = synth->midi_channels; int i, n = 0; /* prints all basic channels */ for(i = 0; i < n_chan; i++) { int basic_chan, mode_chan, val; if(fluid_synth_get_basic_channel(synth, i, &basic_chan, &mode_chan, &val) == FLUID_OK) { if(basic_chan == i) { n++; fluid_ostream_printf(out, "Basic channel:%3d, %s, nbr:%3d\n", i, mode_name[mode_chan & FLUID_CHANNEL_MODE_MASK ], val); } } else { return FLUID_FAILED; /* error */ } } /* prints a warning if there is no basic channel */ if(n == 0) { fluid_ostream_printf(out, warning_msg); } return FLUID_OK; } /*----------------------------------------------------------------------------- basicchannels Prints the list of all MIDI basic channels information example: Basic channel: 0, poly omni on (0), nbr: 3 Basic channel: 3, poly omni off(2), nbr: 1 Basic channel: 8, mono omni off(3), nbr: 2 Basic channel: 13, mono omni on (1), nbr: 3 */ int fluid_handle_basicchannels(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; return print_basic_channels(synth, out); } /* Searches a mode name and returns the channel mode number. name must be: poly_omnion, mono_omnion, poly_omnioff, mono_omnioff. @param name to search. @return channel mode number (0 to 3) if name is valid, -1 otherwise. */ static int get_channel_mode_num(char *name) { /* argument names for channel mode parameter (see resetbasicchannels and setbasicchannels commands*/ static const char * const name_channel_mode [FLUID_CHANNEL_MODE_LAST] = {"poly_omnion", "mono_omnion", "poly_omnioff", "mono_omnioff"}; int i; for(i = 0 ; i < FLUID_CHANNEL_MODE_LAST; i++) { if(! FLUID_STRCMP(name, name_channel_mode[i])) { return i; } } return -1; } static const char invalid_arg_msg[] = "invalid argument\n"; /* checks basic channels arguments: chan1 mode1 val chan2 mode2 val2 ... All arguments can be numeric. mode parameter can be a name. Each group entry must have 3 parameters (chan,mode,val). @param ac argument count. @param av argument table. @param out output stream. @param name_cde command name prefix. @return 0 if arguments are valid, -1 otherwise. */ static int check_basicchannels_arguments(int ac, char **av, fluid_ostream_t out, char const *name_cde) { static const char too_few_arg_msg[] = "too few argument, chan mode val [chan mode val]...\n"; int i; for(i = 0; i < ac; i++) { /* checks parameters for list entries: chan1 mode1 val chan2 mode2 val2 ...*/ /* all parameters can be numeric. mode parameter can be a name. */ if(!fluid_is_number(av[i]) && ((i % 3 != 1) || get_channel_mode_num(av[i]) < 0)) { fluid_ostream_printf(out, "%s: %s", name_cde, invalid_arg_msg); return -1; } } if(ac % 3) { /* each group entry needs 3 parameters: basicchan,mode,val */ fluid_ostream_printf(out, "%s: channel %d, %s\n", name_cde, atoi(av[((ac / 3) * 3)]), too_few_arg_msg); return -1; } return 0; } /* checks channels arguments: chan1 chan2 ... all arguments must be numeric. @param ac argument count. @param av argument table. @param out output stream. @param name_cde command name prefix. @return 0 if arguments are valid, -1 otherwise. */ static int check_channels_arguments(int ac, char **av, fluid_ostream_t out, char const *name_cde) { int i; for(i = 0; i < ac; i++) { if(!fluid_is_number(av[i])) { fluid_ostream_printf(out, "%s: %s", name_cde, invalid_arg_msg); return -1; } } return 0; } /*----------------------------------------------------------------------------- resetbasicchannels With no parameters the command resets all basic channels. Note: Be aware than when a synth instance has no basic channels, all channels are disabled. In the intend to get some MIDI channels enabled, use the command setbasicchannels. resetbasicchannels chan1 [chan2 . . .] Resets basic channel group chan1, basic channel group chan2 . . . */ int fluid_handle_resetbasicchannels(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "resetbasicchannels"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; /* checks channels arguments: chan1 chan2 .... */ if(check_channels_arguments(ac, av, out, name_cde) < 0) { return -1; } if(ac) { /* resetbasicchannels chan1 [chan2 . . .] */ int i; for(i = 0; i < ac; i++) { int chan = atoi(av[i]); int result = fluid_synth_reset_basic_channel(synth, chan); if(result == FLUID_FAILED) { fluid_ostream_printf(out, "%s: channel %3d, %s", name_cde, chan, invalid_arg_msg); } } } else { /* resets all basic channels */ fluid_synth_reset_basic_channel(synth, -1); } /* prints result */ return print_basic_channels(synth, out); } /*----------------------------------------------------------------------------- setbasicchannels With no parameters the command sets one channel basic at basic channel 0 in Omni On Poly (i.e all the MIDI channels are polyphonic). setbasicchannels chan1 mode1 nbr1 [chan2 mode2 nbr2] ... ... Adds basic channel 1 and 2 The command fails if any channels overlaps any existing basic channel groups. To make room if necessary, existing basic channel groups can be cleared using resetbasicchannels command. Mode can be a numeric value or a name: numeric: 0 to 3 or name: poly_omnion , mono_omnion, poly_omnioff, mono_omnioff. */ int fluid_handle_setbasicchannels(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "setbasicchannels"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int result; int i, n ; if(!ac) { /* sets one default basic channel */ fluid_synth_reset_basic_channel(synth, -1); /* reset all basic channels */ /* sets one basic channel Omni On Poly (i.e all the MIDI channels are polyphonic) */ fluid_synth_set_basic_channel(synth, 0, FLUID_CHANNEL_MODE_OMNION_POLY, 0); return 0; } /* checks parameters: chan1 mode1 val1 chan2 mode2 val2 */ if(check_basicchannels_arguments(ac, av, out, name_cde) < 0) { return -1; } n = ac / 3; /* number of basic channel information */ for(i = 0; i < n; i++) { int basicchan, mode, val; basicchan = atoi(av[(i * 3)]); /* chan is numeric */ if(fluid_is_number(av[(i * 3) + 1])) { /* chan is numeric */ mode = atoi(av[(i * 3) + 1]); } else { /* mode is a name */ mode = get_channel_mode_num(av[(i * 3) + 1]); } val = atoi(av[(i * 3) + 2]); /* val is numeric */ /* changes or sets basic channels */ result = fluid_synth_set_basic_channel(synth, basicchan, mode, val); if(result == FLUID_FAILED) { fluid_ostream_printf(out, "%s: channel %3d, mode %3d, nbr %3d, %s", name_cde, basicchan, mode, val, invalid_arg_msg); } } return 0; } /* Print result message : "channel:x is outside MIDI channel count(y)" for commands: channelsmode, portamentomode, legatomode, breathmode,setbreathmode. @param out output stream. @param name_cde command name prefix. @param chan, MIDI channel number x. @param n_chan, number of MIDI channels y. */ static void print_channel_is_outside_count(fluid_ostream_t out, char const *name_cde, int chan, int n_chan) { fluid_ostream_printf(out, "%s: channel %3d is outside MIDI channel count(%d)\n", name_cde, chan, n_chan); } /*----------------------------------------------------------------------------- channelsmode Prints channel mode of all MIDI channels (Poly/mono, Enabled, Basic Channel) example Channel , Status , Type , Mode , Nbr of channels channel: 0, disabled channel: 1, disabled channel: 2, disabled channel: 3, disabled channel: 4, disabled channel: 5, enabled, basic channel, mono omni off(3), nbr: 2 channel: 6, enabled, -- , mono , -- channel: 7, disabled channel: 8, disabled channel: 9, disabled channel: 10, enabled, basic channel, mono omni off(3), nbr: 4 channel: 11, enabled, -- , mono , -- channel: 12, enabled, -- , mono , -- channel: 13, enabled, -- , mono , -- channel: 14, disabled channel: 15, disabled channelsmode chan1 chan2 Prints only channel mode of MIDI channels chan1, chan2 */ int fluid_handle_channelsmode(void *data, int ac, char **av, fluid_ostream_t out) { static const char header[] = "Channel , Status , Type , Mode , Nbr of channels\n"; static const char name_cde[] = "channelsmode"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int i, n, n_chan = synth->midi_channels; /* checks parameters: chan1 chan2 .... */ if(check_channels_arguments(ac, av, out, name_cde) < 0) { return -1; } if(ac) { n = ac; /* prints ac MIDI channels number */ } else { n = n_chan; /* prints all MIDI channels number */ } /* prints header */ fluid_ostream_printf(out, header); for(i = 0; i < n; i++) { int basic_chan, mode, val; int chan = ac ? atoi(av[i]) : i; int result = fluid_synth_get_basic_channel(synth, chan, &basic_chan, &mode, &val); if(result == FLUID_OK) { if(basic_chan != FLUID_FAILED) { /* This channel is enabled */ const char *p_basicchan; /* field basic channel */ const char *p_mode; /* field mode */ const char *p_nbr; /* field Nbr */ static const char blank[] = "--"; /* field empty */ if(chan == basic_chan) { /* This channel is a basic channel */ char nbr[10]; /* field Nbr */ FLUID_SNPRINTF(nbr, sizeof(nbr), "nbr:%3d", val); p_nbr = nbr; p_mode = mode_name[mode]; p_basicchan = "basic channel"; } else { /* This channel is member of a basic channel group */ p_basicchan = blank; if(mode & FLUID_CHANNEL_POLY_OFF) { p_mode = "mono"; } else { p_mode = "poly"; } p_nbr = blank; } fluid_ostream_printf(out, "channel:%3d, enabled, %-13s, %-16s, %s\n", chan, p_basicchan, p_mode, p_nbr); } else { fluid_ostream_printf(out, "channel:%3d, disabled\n", chan); } } else { print_channel_is_outside_count(out, name_cde, chan, n_chan); if(i < n - 1) { fluid_ostream_printf(out, header); } } } return 0; } /** commands mono legato mode ***********************************************/ /* Prints result message for commands: legatomode, portamentomode. @param result result from the command (FLUID_OK,FLUID_FAILED). @param out output stream. @param name_cde command name prefix. @param chan MIDI channel number to display. @param name_mode name of the mode to display. @param n_chan, number of MIDI channels. */ static void print_result_get_channel_mode(int result, fluid_ostream_t out, char const *name_cde, int chan, char const *name_mode, int n_chan) { if(result == FLUID_OK) { fluid_ostream_printf(out, "%s: channel %3d, %s\n", name_cde, chan, name_mode); } else { print_channel_is_outside_count(out, name_cde, chan, n_chan); } } /*----------------------------------------------------------------------------- legatomode Prints legato mode of all MIDI channels example channel: 0, (1)multi-retrigger channel: 1, (0)retrigger channel: 2, (1)multi-retrigger ..... legatomode chan1 chan2 Prints only legato mode of MIDI channels chan1, chan2 */ int fluid_handle_legatomode(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "legatomode"; static const char *const name_legato_mode[FLUID_CHANNEL_LEGATO_MODE_LAST] = { "(0)retrigger", "(1)multi-retrigger" }; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int mode = 0; int i, n, n_chan = synth->midi_channels; /* checks channels arguments: chan1 chan2 .... */ if(check_channels_arguments(ac, av, out, name_cde) < 0) { return -1; } if(ac) { n = ac; /* prints ac MIDI channels number */ } else { n = n_chan; /* prints all MIDI channels number */ } /* prints header */ fluid_ostream_printf(out, "Channel , legato mode\n"); for(i = 0; i < n; i++) { int chan = ac ? atoi(av[i]) : i; int result = fluid_synth_get_legato_mode(synth, chan, &mode); print_result_get_channel_mode(result, out, name_cde, chan, name_legato_mode[mode], n_chan); } return 0; } /* checks channels arguments by group: -example by group of 2 arguments: chan1 val1 chan2 val2 .. .. -example by group of 4 arguments: chan1 val1 val2 val3 chan2 val1 val2 val3 .... all arguments must be numeric. @param ac argument count. @param av argument table. @param nbr_arg_group number of arguments by group expected. @param out output stream. @param name_cde command name prefix. @param nbr_arg_group_msg message when the number of argument by group is invalid. @return 0 if arguments are valid, -1 otherwise. */ static int check_channels_group_arguments(int ac, char **av, int nbr_arg_group, fluid_ostream_t out, char const *name_cde, char const *nbr_arg_group_msg ) { if(ac) { /* checks channels numeric arguments */ if(check_channels_arguments(ac, av, out, name_cde) < 0) { return -1; } if(ac % nbr_arg_group) { /* each group entry needs nbr_arg_group parameters */ fluid_ostream_printf(out, "%s: channel %d, %s\n", name_cde, atoi(av[((ac / nbr_arg_group) * nbr_arg_group)]), nbr_arg_group_msg); return -1; } } else { fluid_ostream_printf(out, "%s: %s", name_cde, nbr_arg_group_msg); return -1; } return 0; } /* Prints result message for commands: setlegatomode, setportamentomode. @param result result from the command (FLUID_FAILED). @param out output stream. @param name_cde command name prefix. @param chan, MIDI channel number to display. @param mode, mode value to display. */ static void print_result_set_channel_mode(int result, fluid_ostream_t out, char const *name_cde, int chan, int mode) { if(result == FLUID_FAILED) { fluid_ostream_printf(out, "%s: channel %3d, mode %3d, %s", name_cde, chan, mode, invalid_arg_msg); } } static const char too_few_arg_chan_mode_msg[] = "too few argument, chan mode [chan mode]...\n"; /*----------------------------------------------------------------------------- setlegatomode chan0 mode1 [chan1 mode0] .. .. Changes legato mode for channels chan0 and [chan1] */ int fluid_handle_setlegatomode(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "setlegatomode"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int i, n ; /* checks channels arguments by group of 2: chan1 val1 chan2 val1 .. ..*/ if(check_channels_group_arguments(ac, av, 2, out, name_cde, too_few_arg_chan_mode_msg) < 0) { return -1; } n = ac / 2; /* number of legato groups information */ for(i = 0; i < n; i++) { int chan = atoi(av[(i * 2)]); int mode = atoi(av[(i * 2) + 1]); /* changes legato mode */ int result = fluid_synth_set_legato_mode(synth, chan, mode); print_result_set_channel_mode(result, out, name_cde, chan, mode); } return 0; } /** commands mono/poly portamento mode **************************************/ /*----------------------------------------------------------------------------- portamentomode Prints portamento mode of all MIDI channels example channel: 0, (2)staccato only channel: 1, (1)legato only channel: 2, (0)each note channel: 3, (1)legato only ..... portamentomode chan1 chan2 Prints only portamento mode of MIDI channels chan1, chan2 */ int fluid_handle_portamentomode(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "portamentomode"; static const char *const name_portamento_mode[FLUID_CHANNEL_PORTAMENTO_MODE_LAST] = { "(0)each note", "(1)legato only", "(2)staccato only" }; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int mode = 0; int i, n, n_chan = synth->midi_channels; /* checks channels arguments: chan1 chan2 . . . */ if(check_channels_arguments(ac, av, out, name_cde) < 0) { return -1; } if(ac) { n = ac; /* prints ac MIDI channels number */ } else { n = n_chan; /* prints all MIDI channels number */ } /* prints header */ fluid_ostream_printf(out, "Channel , portamento mode\n"); for(i = 0; i < n; i++) { int chan = ac ? atoi(av[i]) : i; int result = fluid_synth_get_portamento_mode(synth, chan, &mode); print_result_get_channel_mode(result, out, name_cde, chan, name_portamento_mode[mode], n_chan); } return 0; } /*----------------------------------------------------------------------------- setportamentomode chan1 mode1 [chan2 mode2] .. .. Changes portamento mode for channels chan1 and [chan2] */ int fluid_handle_setportamentomode(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "setportamentomode"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int i, n ; /* checks channels arguments by group of 2: chan1 val1 chan2 val1 .. .. */ if(check_channels_group_arguments(ac, av, 2, out, name_cde, too_few_arg_chan_mode_msg) < 0) { return -1; } n = ac / 2; /* number of portamento groups information */ for(i = 0; i < n; i++) { int chan = atoi(av[(i * 2)]); int mode = atoi(av[(i * 2) + 1]); /* changes portamento mode */ int result = fluid_synth_set_portamento_mode(synth, chan, mode); print_result_set_channel_mode(result, out, name_cde, chan, mode); } return 0; } /** commands mono/poly breath mode *******************************************/ /*----------------------------------------------------------------------------- breathmode Prints breath options of all MIDI channels. poly breath on/off, mono breath on/off, breath sync on/off example Channel , poly breath , mono breath , breath sync channel: 0, off , off , off channel: 1, off , off , off channel: 2, off , off , off ..... breathmode chan1 chan2 Prints only breath mode of MIDI channels chan1, chan2 */ int fluid_handle_breathmode(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "breathmode"; static const char *const header = "Channel , poly breath , mono breath , breath sync\n"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int breathmode; int i, n, n_chan = synth->midi_channels; /* checks channels arguments: chan1 chan2 . . . */ if(check_channels_arguments(ac, av, out, name_cde) < 0) { return -1; } if(ac) { n = ac; /* prints ac MIDI channels number */ } else { n = n_chan; /* prints all MIDI channels number */ } /* prints header */ fluid_ostream_printf(out, header); for(i = 0; i < n; i++) { int chan = ac ? atoi(av[i]) : i; int result = fluid_synth_get_breath_mode(synth, chan, &breathmode); if(result == FLUID_OK) { static const char on_msg[] = "on"; static const char off_msg[] = "off"; const char *msg_poly_breath, * msg_mono_breath, * msg_breath_sync; if(breathmode & FLUID_CHANNEL_BREATH_POLY) { msg_poly_breath = on_msg; } else { msg_poly_breath = off_msg; } if(breathmode & FLUID_CHANNEL_BREATH_MONO) { msg_mono_breath = on_msg; } else { msg_mono_breath = off_msg; } if(breathmode & FLUID_CHANNEL_BREATH_SYNC) { msg_breath_sync = on_msg; } else { msg_breath_sync = off_msg; } fluid_ostream_printf(out, "channel:%3d, %-12s, %-12s, %-11s\n", chan, msg_poly_breath, msg_mono_breath, msg_breath_sync); } else { print_channel_is_outside_count(out, name_cde, chan, n_chan); if(i < n - 1) { fluid_ostream_printf(out, header); } } } return 0; } /*----------------------------------------------------------------------------- setbreathmode chan1 poly_breath_mode(1/0) mono_breath_mode(1/0) mono_breath_sync(1/0) Changes breath options for channels chan1 [chan2] .. .. Example: setbreathmode 4 0 1 1 Parameter 1 is the channel number (i.e 4). Parameter 2 is the " Breath modulator " enable/disable for poly mode (i.e disabled). Parameter 3 is the " Breath modulator " enable/disable for mono mode (i.e enabled). Parameter 4 is "breath sync noteOn/Off" enable/disable for mono mode only (i.e enabled). */ int fluid_handle_setbreathmode(void *data, int ac, char **av, fluid_ostream_t out) { static const char name_cde[] = "setbreathmode"; static const char too_few_arg_breath_msg[] = "too few argument:\nchan 1/0(breath poly) 1/0(breath mono) 1/0(breath sync mono)[..]\n"; FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; int i, n, n_chan = synth->midi_channels; /* checks channels arguments by group of 4: chan1 val1 val2 val3 chan2 val1 val2 val3 .... ....*/ if(check_channels_group_arguments(ac, av, 4, out, name_cde, too_few_arg_breath_msg) < 0) { return -1; } n = ac / 4; /* number of breath groups information */ for(i = 0; i < n; i++) { int result; int chan = atoi(av[(i * 4)]); int poly_breath = atoi(av[(i * 4) + 1]); int mono_breath = atoi(av[(i * 4) + 2]); int breath_sync = atoi(av[(i * 4) + 3]); int breath_infos = 0; /* changes breath infos */ if(poly_breath) { breath_infos |= FLUID_CHANNEL_BREATH_POLY; } if(mono_breath) { breath_infos |= FLUID_CHANNEL_BREATH_MONO; } if(breath_sync) { breath_infos |= FLUID_CHANNEL_BREATH_SYNC; } result = fluid_synth_set_breath_mode(synth, chan, breath_infos); if(result == FLUID_FAILED) { print_channel_is_outside_count(out, name_cde, chan, n_chan); } } return 0; } #ifdef LADSPA #define CHECK_LADSPA_ENABLED(_fx, _out) \ if (_fx == NULL) \ { \ fluid_ostream_printf(_out, "LADSPA is not enabled.\n"); \ return FLUID_FAILED; \ } #define CHECK_LADSPA_INACTIVE(_fx, _out) \ if (fluid_ladspa_is_active(_fx)) \ { \ fluid_ostream_printf(_out, "LADSPA already started.\n"); \ return FLUID_FAILED; \ } #define LADSPA_ERR_LEN (1024) /** * ladspa_start */ int fluid_handle_ladspa_start(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; char error[LADSPA_ERR_LEN]; if(ac != 0) { fluid_ostream_printf(out, "ladspa_start does not accept any arguments\n"); return FLUID_FAILED; } CHECK_LADSPA_ENABLED(fx, out); CHECK_LADSPA_INACTIVE(fx, out); if(fluid_ladspa_check(fx, error, LADSPA_ERR_LEN) != FLUID_OK) { fluid_ostream_printf(out, "Unable to start LADSPA: %s", error); return FLUID_FAILED; } if(fluid_ladspa_activate(fx) != FLUID_OK) { fluid_ostream_printf(out, "Unable to start LADSPA.\n"); return FLUID_FAILED; } return FLUID_OK; } /** * ladspa_stop */ int fluid_handle_ladspa_stop(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; if(ac != 0) { fluid_ostream_printf(out, "ladspa_stop does not accept any arguments\n"); return FLUID_FAILED; } CHECK_LADSPA_ENABLED(fx, out); if(!fluid_ladspa_is_active(fx)) { fluid_ostream_printf(out, "LADSPA has not been started.\n"); } if(fluid_ladspa_deactivate(fx) != FLUID_OK) { fluid_ostream_printf(out, "Unable to stop LADSPA.\n"); return FLUID_FAILED; } return FLUID_OK; } /** * ladspa_reset */ int fluid_handle_ladspa_reset(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; if(ac != 0) { fluid_ostream_printf(out, "ladspa_reset does not accept any arguments\n"); return FLUID_FAILED; } CHECK_LADSPA_ENABLED(fx, out); fluid_ladspa_reset(fx); return FLUID_OK; } /** * ladspa_check */ int fluid_handle_ladspa_check(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; char error[LADSPA_ERR_LEN]; if(ac != 0) { fluid_ostream_printf(out, "ladspa_reset does not accept any arguments\n"); return FLUID_FAILED; } CHECK_LADSPA_ENABLED(fx, out); if(fluid_ladspa_check(fx, error, LADSPA_ERR_LEN) != FLUID_OK) { fluid_ostream_printf(out, "LADSPA check failed: %s", error); return FLUID_FAILED; } fluid_ostream_printf(out, "LADSPA check ok\n"); return FLUID_OK; } /** * ladspa_set */ int fluid_handle_ladspa_set(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; if(ac != 3) { fluid_ostream_printf(out, "ladspa_set needs three arguments: \n"); return FLUID_FAILED; }; CHECK_LADSPA_ENABLED(fx, out); /* Redundant check, just here to give a more detailed error message */ if(!fluid_ladspa_effect_port_exists(fx, av[0], av[1])) { fluid_ostream_printf(out, "Port '%s' not found on effect '%s'\n", av[1], av[0]); return FLUID_FAILED; } if(fluid_ladspa_effect_set_control(fx, av[0], av[1], atof(av[2])) != FLUID_OK) { fluid_ostream_printf(out, "Failed to set port '%s' on effect '%s', " "maybe it is not a control port?\n", av[1], av[0]); return FLUID_FAILED; } return FLUID_OK; }; /** * ladspa_buffer */ int fluid_handle_ladspa_buffer(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; if(ac != 1) { fluid_ostream_printf(out, "ladspa_buffer needs one argument: \n"); return FLUID_FAILED; }; CHECK_LADSPA_ENABLED(fx, out); CHECK_LADSPA_INACTIVE(fx, out); if(fluid_ladspa_add_buffer(fx, av[0]) != FLUID_OK) { fluid_ostream_printf(out, "Failed to add buffer\n"); return FLUID_FAILED; } return FLUID_OK; }; /** * ladspa_effect [plugin] [--mix [gain]] */ int fluid_handle_ladspa_effect(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; char *plugin_name = NULL; int pos; int mix = FALSE; float gain = 1.0f; if(ac < 2 || ac > 5) { fluid_ostream_printf(out, "ladspa_effect invalid arguments: " " [plugin] [--mix [gain]]\n"); return FLUID_FAILED; } pos = 2; /* If the first optional arg is not --mix, then it must be the plugin label */ if((pos < ac) && (FLUID_STRCMP(av[pos], "--mix") != 0)) { plugin_name = av[pos]; pos++; } /* If this optional arg is --mix and there's an argument after it, that that * must be the gain */ if((pos < ac) && (FLUID_STRCMP(av[pos], "--mix") == 0)) { mix = TRUE; if(pos + 1 < ac) { gain = atof(av[pos + 1]); } } CHECK_LADSPA_ENABLED(fx, out); CHECK_LADSPA_INACTIVE(fx, out); if(fluid_ladspa_add_effect(fx, av[0], av[1], plugin_name) != FLUID_OK) { fluid_ostream_printf(out, "Failed to create effect\n"); return FLUID_FAILED; } if(mix) { if(!fluid_ladspa_effect_can_mix(fx, av[0])) { fluid_ostream_printf(out, "Effect '%s' does not support --mix mode\n", av[0]); return FLUID_FAILED; } if(fluid_ladspa_effect_set_mix(fx, av[0], mix, gain) != FLUID_OK) { fluid_ostream_printf(out, "Failed to set --mix mode\n"); return FLUID_FAILED; } } return FLUID_OK; } /* * ladspa_link */ int fluid_handle_ladspa_link(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_ladspa_fx_t *fx = handler->synth->ladspa_fx; if(ac != 3) { fluid_ostream_printf(out, "ladspa_link needs 3 arguments: " " \n"); return FLUID_FAILED; } CHECK_LADSPA_ENABLED(fx, out); CHECK_LADSPA_INACTIVE(fx, out); if(!fluid_ladspa_effect_port_exists(fx, av[0], av[1])) { fluid_ostream_printf(out, "Port '%s' not found on effect '%s'\n", av[1], av[0]); return FLUID_FAILED; } if(!fluid_ladspa_host_port_exists(fx, av[2]) && !fluid_ladspa_buffer_exists(fx, av[2])) { fluid_ostream_printf(out, "Host port or buffer '%s' not found.\n", av[2]); return FLUID_FAILED; } if(fluid_ladspa_effect_link(fx, av[0], av[1], av[2]) != FLUID_OK) { fluid_ostream_printf(out, "Failed to link port\n"); return FLUID_FAILED; } return FLUID_OK; } #endif /* LADSPA */ #if WITH_PROFILING /* * Locks profile command to prevent simultaneous changes by an other shell * (may be a server shell (tcp)). * * @return FLUID_OK if success , otherwise FLUID_FAILED. */ static int fluid_profile_lock_command(fluid_ostream_t out) { if(! fluid_atomic_int_compare_and_exchange(&fluid_profile_lock, 0, 1)) { fluid_ostream_printf(out, "profile command in use in another shell. Try later!\n"); return FLUID_FAILED; } return FLUID_OK; } /* * Unlocks profile command. */ static void fluid_profile_unlock_command(void) { fluid_atomic_int_set(&fluid_profile_lock, 0); } /* * command: profile * * Prints default parameters used by prof_start command. * * Notes:0, bank:0, prog:16, print:0, n_prof:1, dur:500 ms. * * @return FLUID_OK if success , otherwise FLUID_FAILED. */ int fluid_handle_profile(void *data, int ac, char **av, fluid_ostream_t out) { /* locks to prevent simultaneous changes by an other shell */ /* (may be a server shell (tcp)) */ if(fluid_profile_lock_command(out) == FLUID_FAILED) { return FLUID_FAILED; } /* prints default parameters */ fluid_ostream_printf(out, " Notes:%d, bank:%d, prog:%d, print:%d, n_prof:%d, dur:%d ms\n", fluid_profile_notes, fluid_profile_bank, fluid_profile_prog, fluid_profile_print, fluid_profile_n_prof, fluid_profile_dur); /* unlocks */ fluid_profile_unlock_command(); return FLUID_OK; } /* * command: prof_set_notes nbr [bank prog] * * Sets notes number generated by prof_start command. * * nbr: notes numbers (generated during command "prof_start"). * bank, prog: preset bank and program number (default value if not specified). * * @return FLUID_OK if success , otherwise FLUID_FAILED. */ int fluid_handle_prof_set_notes(void *data, int ac, char **av, fluid_ostream_t out) { unsigned short nbr; /* previous parameters */ unsigned char bank, prog; /* previous parameters */ int r; /* return */ /* locks to prevent simultaneous changes by an other shell */ if(fluid_profile_lock_command(out) == FLUID_FAILED) { return FLUID_FAILED; } /* checks parameters */ if(ac < 1) { fluid_ostream_printf(out, "profile_notes: too few arguments\n"); fluid_profile_unlock_command(); return FLUID_FAILED; } /* gets default parameters */ nbr = fluid_profile_notes, bank = fluid_profile_bank; prog = fluid_profile_prog; r = fluid_is_number(av[0]); if(r) { /* checks nbr */ nbr = atoi(av[0]); /* get nbr parameter */ if(ac >= 2) { /* [bank prog] are optional */ if(ac >= 3) { r = fluid_is_number(av[1]) && fluid_is_number(av[2]); if(r) { bank = atoi(av[1]); /* gets bank parameter */ prog = atoi(av[2]); /* gets prog parameter */ } } else { /* prog is needed */ fluid_ostream_printf(out, "profile_set_notes: too few arguments\n"); fluid_profile_unlock_command(); return FLUID_FAILED; } } } if(!r) { fluid_ostream_printf(out, "profile_set_notes: invalid argument\n"); fluid_profile_unlock_command(); return FLUID_FAILED; } /* Saves new parameters */ fluid_profile_notes = nbr; fluid_profile_bank = bank; fluid_profile_prog = prog; /* unlocks */ fluid_profile_unlock_command(); return FLUID_OK; } /* * command: prof_set_print mode * * The command sets the print mode. * * mode: result print mode(used by prof_start"). * 0: simple printing, >0: full printing * * @return FLUID_OK if success , otherwise FLUID_FAILED. */ int fluid_handle_prof_set_print(void *data, int ac, char **av, fluid_ostream_t out) { int r; /* locks to prevent simultaneous changes by an other shell */ if(fluid_profile_lock_command(out) == FLUID_FAILED) { return FLUID_FAILED; } /* checks parameters */ if(ac < 1) { fluid_ostream_printf(out, "profile_set_print: too few arguments\n"); fluid_profile_unlock_command(); return FLUID_FAILED; } /* gets parameters */ if(fluid_is_number(av[0])) { /* checks and gets mode */ fluid_profile_print = atoi(av[0]); /* gets and saves mode parameter */ r = FLUID_OK; } else { fluid_ostream_printf(out, "profile_set_print: invalid argument\n"); r = FLUID_FAILED; } /* unlocks */ fluid_profile_unlock_command(); return r; } /* * Generates simultaneous notes for precise profiling. * * @param synth, synthesizer instance. * @param notes, the number of notes to generate. * @param bank, prog, soundfont bank preset number used. * @param out, stream output device. * @return the number of voices generated. It can be lower than notes number * when the preset have instrument only on few key range. */ static unsigned short fluid_profile_send_notes(fluid_synth_t *synth, int notes, int bank, int prog, fluid_ostream_t out) { int n; /* number of notes generated */ int n_voices, n_actives = 0; /* Maximum voices, voices generated */ int n_chan, chan, key ; /* MIDI channels count and maximum polyphony */ n_chan = fluid_synth_count_midi_channels(synth); /* channels count */ n_voices = fluid_synth_get_polyphony(synth); /* maximum voices */ /* */ fluid_ostream_printf(out, "Generating %d notes, ", notes); for(n = 0, key = FLUID_PROFILE_LAST_KEY + 1, chan = -1; n < notes; n++, key++) { if(key > FLUID_PROFILE_LAST_KEY) { /* next channel */ chan++; if(chan >= n_chan) { break; /* stops generation */ } /* select preset */ fluid_synth_bank_select(synth, chan, bank); fluid_synth_program_change(synth, chan, prog); key = FLUID_PROFILE_FIRST_KEY; } fluid_synth_noteon(synth, chan, key, FLUID_PROFILE_DEFAULT_VEL); n_actives = fluid_synth_get_active_voice_count(synth); /* running voices */ if(n_actives >= n_voices) { fluid_ostream_printf(out, "max polyphony reached:%d, ", n_voices); break; /* stops notes generation */ } } fluid_ostream_printf(out, "generated voices:%d\n", n_actives); return n_actives; } /* * command: prof_start [n_prof [dur] ] * * Starts n_prof measures of dur duration(ms) each. * * n_prof number of measures (default value if not specified). * dur: measure duration (ms) (default value if not specified). * * The result of each measure is displayed. * * Note: The command ends when the last measure ends or when the user * cancels the command using key (cancellation using * is implemented using FLUID_PROFILE_CANCEL macro in fluid_sys.h). * * @return FLUID_OK if success , otherwise FLUID_FAILED. */ int fluid_handle_prof_start(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_synth_t *synth = handler->synth; unsigned short n_prof, n, dur; /* previous parameters */ unsigned int total_dur, rem_dur; /* total and remainder duration (ms) */ unsigned short notes; /* notes number to generate */ int n_actives = 0; /* actives voices */ float gain; /* current gain */ int r = 1; /* checking parameter result */ /* Locks to prevent simultaneous command by an other shell */ if(fluid_profile_lock_command(out) == FLUID_FAILED) { return FLUID_FAILED; } /* Gets previous parameters values */ n_prof = fluid_profile_n_prof; /* number of measuses */ dur = fluid_profile_dur; /* duration of one measure (ms) */ /* check parameters */ if(ac >= 1) { r = fluid_is_number(av[0]); if(r) { n_prof = atoi(av[0]); /* gets n_prof parameter */ if(ac >= 2) { r = fluid_is_number(av[1]); if(r) { dur = atoi(av[1]);/* gets dur parameter */ } } } } if(!r || n_prof < 1 || dur < 1) { fluid_ostream_printf(out, "profile_start: invalid argument\n"); fluid_profile_unlock_command(); return FLUID_FAILED; } /* Saves new parameters */ fluid_profile_n_prof = n_prof; fluid_profile_dur = dur; /* Saves current gain */ gain = fluid_synth_get_gain(synth); /* Generates notes if any */ notes = fluid_profile_notes; if(notes) { /* checks if the synth is playing */ /* Warn the user */ if(fluid_synth_get_active_voice_count(synth)) { fluid_ostream_printf(out, "Warning: can't generate notes, please stop any playing\n"); } else { float send_gain; /* sets low gain before sending notes */ fluid_synth_set_gain(synth, 0.01); /* sends notes */ n_actives = fluid_profile_send_notes(synth, notes, fluid_profile_bank, fluid_profile_prog, out); /* compensates gain to avoid a loud sound */ send_gain = 1.0 * pow(10, (n_actives * FLUID_PROFILE_VOICE_ATTEN) / 20); fluid_synth_set_gain(synth, send_gain); /* Before starting profiling immediately we wait to ensures that voices are currently synthesized by audio rendering API. This ensure that macro probes will register the expected number of actives voices. */ fluid_msleep(200); /* wait 200 ms */ } } /* Starts - waits - prints n_prof measures */ fluid_ostream_printf(out, "Number of measures(n_prof):%d, duration of one measure(dur):%dms\n", n_prof, dur); /* Clears any previous pending key */ fluid_profile_is_cancel_req(); total_dur = rem_dur = n_prof * dur; for(n = 0 ; n < n_prof; rem_dur -= dur, n++) { unsigned int end_ticks;/* ending position (in ticks) */ unsigned int tm, ts, rm, rs; int status; ts = total_dur / 1000; tm = ts / 60; ts = ts % 60; /* total minutes and seconds */ rs = rem_dur / 1000; rm = rs / 60; rs = rs % 60; /* remainder minutes and seconds */ /* Prints total and remainder duration */ #ifdef FLUID_PROFILE_CANCEL fluid_ostream_printf(out, "\nProfiling time(mm:ss): Total=%d:%d Remainder=%d:%d, press to cancel\n", tm, ts, rm, rs); #else fluid_ostream_printf(out, "\nProfiling time(mm:ss): Total=%d:%d Remainder=%d:%d\n", tm, ts, rm, rs); #endif /* converts duration(ms) in end position in ticks. */ end_ticks = fluid_atomic_int_get(&synth->ticks_since_start) + dur * synth->sample_rate / 1000; /* requests to start the measurement in audio rendering API */ fluid_profile_start_stop(end_ticks, n); /* waits while running */ do { /* passive waiting */ fluid_msleep(500); /* wait 500 ms */ status = fluid_profile_get_status(); } while(status == PROFILE_RUNNING); /* checks if data are ready */ if(status == PROFILE_READY) { /* profiling data are ready, prints profile data */ fluid_profiling_print_data(synth->sample_rate, out); } /* checks if the measurement has been cancelled */ else if(status == PROFILE_CANCELED || status == PROFILE_STOP) { fluid_ostream_printf(out, "Profiling cancelled.\n"); break; /* cancel the command */ } } /* Stops voices if any had been generated */ if(n_actives) { fluid_ostream_printf(out, "Stopping %d voices...", n_actives); fluid_synth_system_reset(synth); /* waits until all voices become inactives */ do { fluid_msleep(10); /* wait 10 ms */ n_actives = fluid_synth_get_active_voice_count(synth); } while(n_actives); fluid_ostream_printf(out, "voices stopped.\n"); } /* Restores initial gain */ fluid_synth_set_gain(synth, gain); /* Unlocks */ fluid_profile_unlock_command(); return FLUID_OK; } #endif /* WITH_PROFILING */ int fluid_is_number(char *a) { while(*a != 0) { if(((*a < '0') || (*a > '9')) && (*a != '-') && (*a != '+') && (*a != '.')) { return FALSE; } a++; } return TRUE; } char * fluid_expand_path(char *path, char *new_path, int len) { #if defined(WIN32) || defined(MACOS9) FLUID_SNPRINTF(new_path, len - 1, "%s", path); #else if((path[0] == '~') && (path[1] == '/')) { char *home = getenv("HOME"); if(home == NULL) { FLUID_SNPRINTF(new_path, len - 1, "%s", path); } else { FLUID_SNPRINTF(new_path, len - 1, "%s%s", home, &path[1]); } } else { FLUID_SNPRINTF(new_path, len - 1, "%s", path); } #endif new_path[len - 1] = 0; return new_path; } /* * Command */ fluid_cmd_t *fluid_cmd_copy(const fluid_cmd_t *cmd) { fluid_cmd_t *copy = FLUID_NEW(fluid_cmd_t); if(copy == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } copy->name = FLUID_STRDUP(cmd->name); copy->topic = FLUID_STRDUP(cmd->topic); copy->help = FLUID_STRDUP(cmd->help); copy->handler = cmd->handler; return copy; } void delete_fluid_cmd(fluid_cmd_t *cmd) { fluid_return_if_fail(cmd != NULL); FLUID_FREE(cmd->name); FLUID_FREE(cmd->topic); FLUID_FREE(cmd->help); FLUID_FREE(cmd); } /* * Command handler */ static void fluid_cmd_handler_destroy_hash_value(void *value) { delete_fluid_cmd((fluid_cmd_t *)value); } /** * Create a new command handler. * @param synth If not NULL, all the default synthesizer commands will be added to the new handler. * @param router If not NULL, all the default midi_router commands will be added to the new handler. * @return New command handler, or NULL if alloc failed */ fluid_cmd_handler_t *new_fluid_cmd_handler(fluid_synth_t *synth, fluid_midi_router_t *router) { unsigned int i; fluid_cmd_handler_t *handler; handler = FLUID_NEW(fluid_cmd_handler_t); if(handler == NULL) { return NULL; } FLUID_MEMSET(handler, 0, sizeof(*handler)); handler->commands = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal, NULL, fluid_cmd_handler_destroy_hash_value); if(handler->commands == NULL) { FLUID_FREE(handler); return NULL; } handler->synth = synth; handler->router = router; for(i = 0; i < FLUID_N_ELEMENTS(fluid_commands); i++) { const fluid_cmd_t *cmd = &fluid_commands[i]; int is_router_cmd = FLUID_STRCMP(cmd->topic, "router") == 0; if((is_router_cmd && router == NULL) || (!is_router_cmd && synth == NULL)) { /* omit registering router and synth commands if they were not requested */ continue; } fluid_cmd_handler_register(handler, &fluid_commands[i]); } return handler; } /** * Delete a command handler. * @param handler Command handler to delete */ void delete_fluid_cmd_handler(fluid_cmd_handler_t *handler) { fluid_return_if_fail(handler != NULL); delete_fluid_hashtable(handler->commands); FLUID_FREE(handler); } /** * Register a new command to the handler. * @param handler Command handler instance * @param cmd Command info (gets copied) * @return #FLUID_OK if command was inserted, #FLUID_FAILED otherwise */ int fluid_cmd_handler_register(fluid_cmd_handler_t *handler, const fluid_cmd_t *cmd) { fluid_cmd_t *copy = fluid_cmd_copy(cmd); fluid_hashtable_insert(handler->commands, copy->name, copy); return FLUID_OK; } /** * Unregister a command from a command handler. * @param handler Command handler instance * @param cmd Name of the command * @return TRUE if command was found and unregistered, FALSE otherwise */ int fluid_cmd_handler_unregister(fluid_cmd_handler_t *handler, const char *cmd) { return fluid_hashtable_remove(handler->commands, cmd); } int fluid_cmd_handler_handle(void *data, int ac, char **av, fluid_ostream_t out) { FLUID_ENTRY_COMMAND(data); fluid_cmd_t *cmd; cmd = fluid_hashtable_lookup(handler->commands, av[0]); if(cmd && cmd->handler) { return (*cmd->handler)(handler, ac - 1, av + 1, out); } fluid_ostream_printf(out, "unknown command: %s (try help)\n", av[0]); return FLUID_FAILED; } #ifdef NETWORK_SUPPORT struct _fluid_server_t { fluid_server_socket_t *socket; fluid_settings_t *settings; fluid_synth_t *synth; fluid_midi_router_t *router; fluid_list_t *clients; fluid_mutex_t mutex; }; static void fluid_server_close(fluid_server_t *server) { fluid_list_t *list; fluid_list_t *clients; fluid_client_t *client; fluid_return_if_fail(server != NULL); fluid_mutex_lock(server->mutex); clients = server->clients; server->clients = NULL; fluid_mutex_unlock(server->mutex); list = clients; while(list) { client = fluid_list_get(list); fluid_client_quit(client); list = fluid_list_next(list); } delete_fluid_list(clients); if(server->socket) { delete_fluid_server_socket(server->socket); server->socket = NULL; } } static int fluid_server_handle_connection(fluid_server_t *server, fluid_socket_t client_socket, char *addr) { fluid_client_t *client; client = new_fluid_client(server, server->settings, client_socket); if(client == NULL) { return -1; } fluid_server_add_client(server, client); return 0; } void fluid_server_add_client(fluid_server_t *server, fluid_client_t *client) { fluid_mutex_lock(server->mutex); server->clients = fluid_list_append(server->clients, client); fluid_mutex_unlock(server->mutex); } void fluid_server_remove_client(fluid_server_t *server, fluid_client_t *client) { fluid_mutex_lock(server->mutex); server->clients = fluid_list_remove(server->clients, client); fluid_mutex_unlock(server->mutex); } struct _fluid_client_t { fluid_server_t *server; fluid_settings_t *settings; fluid_cmd_handler_t *handler; fluid_socket_t socket; fluid_thread_t *thread; }; static fluid_thread_return_t fluid_client_run(void *data) { fluid_shell_t shell; fluid_client_t *client = (fluid_client_t *)data; fluid_shell_init(&shell, client->settings, client->handler, fluid_socket_get_istream(client->socket), fluid_socket_get_ostream(client->socket)); fluid_shell_run(&shell); fluid_server_remove_client(client->server, client); delete_fluid_client(client); return FLUID_THREAD_RETURN_VALUE; } fluid_client_t * new_fluid_client(fluid_server_t *server, fluid_settings_t *settings, fluid_socket_t sock) { fluid_client_t *client; client = FLUID_NEW(fluid_client_t); if(client == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } client->server = server; client->socket = sock; client->settings = settings; client->handler = new_fluid_cmd_handler(server->synth, server->router); client->thread = new_fluid_thread("client", fluid_client_run, client, 0, FALSE); if(client->handler == NULL || client->thread == NULL) { goto error_recovery; } return client; error_recovery: FLUID_LOG(FLUID_ERR, "Out of memory"); delete_fluid_client(client); return NULL; } void fluid_client_quit(fluid_client_t *client) { fluid_socket_close(client->socket); FLUID_LOG(FLUID_DBG, "fluid_client_quit: joining"); fluid_thread_join(client->thread); FLUID_LOG(FLUID_DBG, "fluid_client_quit: done"); } void delete_fluid_client(fluid_client_t *client) { fluid_return_if_fail(client != NULL); delete_fluid_cmd_handler(client->handler); fluid_socket_close(client->socket); delete_fluid_thread(client->thread); FLUID_FREE(client); } #endif /* NETWORK_SUPPORT */ /** * Create a new TCP/IP command shell server. * @param settings Settings instance to use for the shell * @param synth If not NULL, the synth instance for the command handler to be used by the client * @param router If not NULL, the midi_router instance for the command handler to be used by the client * @return New shell server instance or NULL on error */ fluid_server_t * new_fluid_server(fluid_settings_t *settings, fluid_synth_t *synth, fluid_midi_router_t *router) { #ifdef NETWORK_SUPPORT fluid_server_t *server; int port; server = FLUID_NEW(fluid_server_t); if(server == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } server->settings = settings; server->clients = NULL; server->synth = synth; server->router = router; fluid_mutex_init(server->mutex); fluid_settings_getint(settings, "shell.port", &port); server->socket = new_fluid_server_socket(port, (fluid_server_func_t) fluid_server_handle_connection, server); if(server->socket == NULL) { FLUID_FREE(server); return NULL; } return server; #else FLUID_LOG(FLUID_WARN, "Network support disabled on this platform."); return NULL; #endif } /** * Delete a TCP/IP shell server. * @param server Shell server instance */ void delete_fluid_server(fluid_server_t *server) { #ifdef NETWORK_SUPPORT fluid_return_if_fail(server != NULL); fluid_server_close(server); FLUID_FREE(server); #endif } /** * Join a shell server thread (wait until it quits). * @param server Shell server instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_server_join(fluid_server_t *server) { #ifdef NETWORK_SUPPORT return fluid_server_socket_join(server->socket); #else return FLUID_FAILED; #endif } fluidsynth-2.1.1/src/bindings/fluid_cmd.h000066400000000000000000000206351362231004000203640ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_CMD_H #define _FLUID_CMD_H #include "fluid_sys.h" void fluid_shell_settings(fluid_settings_t *settings); /** some help functions */ int fluid_is_number(char *a); char *fluid_expand_path(char *path, char *new_path, int len); /** the handlers for the command lines */ int fluid_handle_help(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_quit(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_noteon(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_noteoff(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_pitch_bend(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_pitch_bend_range(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_cc(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_prog(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_select(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_inst(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_channels(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_load(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_unload(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reload(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_fonts(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reverbpreset(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reverbsetroomsize(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reverbsetdamp(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reverbsetwidth(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reverbsetlevel(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_chorusnr(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_choruslevel(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_chorusspeed(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_chorusdepth(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_chorus(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reverb(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_gain(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_interp(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_interpc(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_tuning(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_tune(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_settuning(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_resettuning(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_tunings(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_dumptuning(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_reset(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_source(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_echo(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_set(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_get(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_info(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_settings(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_clear(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_default(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_begin(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_end(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_chan(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_par1(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_router_par2(void *data, int ac, char **av, fluid_ostream_t out); #if WITH_PROFILING int fluid_handle_profile(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_prof_set_notes(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_prof_set_print(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_prof_start(void *data, int ac, char **av, fluid_ostream_t out); #endif int fluid_handle_basicchannels(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_resetbasicchannels(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_setbasicchannels(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_channelsmode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_legatomode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_setlegatomode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_portamentomode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_setportamentomode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_breathmode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_setbreathmode(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_sleep(void *data, int ac, char **av, fluid_ostream_t out); #ifdef LADSPA int fluid_handle_ladspa_effect(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_link(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_buffer(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_set(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_check(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_start(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_stop(void *data, int ac, char **av, fluid_ostream_t out); int fluid_handle_ladspa_reset(void *data, int ac, char **av, fluid_ostream_t out); #endif /** * Command handler function prototype. * @param data User defined data * @param ac Argument count * @param av Array of string arguments * @param out Output stream to send response to * @return Should return #FLUID_OK on success, #FLUID_FAILED otherwise */ typedef int (*fluid_cmd_func_t)(void *data, int ac, char **av, fluid_ostream_t out); /** * Shell command information structure. */ typedef struct { char *name; /**< The name of the command, as typed in the shell */ char *topic; /**< The help topic group of this command */ fluid_cmd_func_t handler; /**< Pointer to the handler for this command */ char *help; /**< A help string */ } fluid_cmd_t; fluid_cmd_t *fluid_cmd_copy(const fluid_cmd_t *cmd); void delete_fluid_cmd(fluid_cmd_t *cmd); int fluid_cmd_handler_handle(void *data, int ac, char **av, fluid_ostream_t out); int fluid_cmd_handler_register(fluid_cmd_handler_t *handler, const fluid_cmd_t *cmd); int fluid_cmd_handler_unregister(fluid_cmd_handler_t *handler, const char *cmd); void fluid_server_remove_client(fluid_server_t *server, fluid_client_t *client); void fluid_server_add_client(fluid_server_t *server, fluid_client_t *client); fluid_client_t *new_fluid_client(fluid_server_t *server, fluid_settings_t *settings, fluid_socket_t sock); void delete_fluid_client(fluid_client_t *client); void fluid_client_quit(fluid_client_t *client); #endif /* _FLUID_CMD_H */ fluidsynth-2.1.1/src/bindings/fluid_filerenderer.c000066400000000000000000000357201362231004000222630ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* * Low-level routines for file output. */ #include "fluid_sys.h" #include "fluid_synth.h" #include "fluid_settings.h" #if LIBSNDFILE_SUPPORT #include #endif struct _fluid_file_renderer_t { fluid_synth_t *synth; #if LIBSNDFILE_SUPPORT SNDFILE *sndfile; float *buf; #else FILE *file; short *buf; #endif int period_size; int buf_size; }; #if LIBSNDFILE_SUPPORT /* Default file type used, if none specified and auto extension search fails */ #define FLUID_FILE_RENDERER_DEFAULT_FILE_TYPE SF_FORMAT_WAV /* File audio format names. * !! Keep in sync with format_ids[] */ static const char *const format_names[] = { "s8", "s16", "s24", "s32", "u8", "float", "double" }; /* File audio format IDs. * !! Keep in sync with format_names[] */ static const int format_ids[] = { SF_FORMAT_PCM_S8, SF_FORMAT_PCM_16, SF_FORMAT_PCM_24, SF_FORMAT_PCM_32, SF_FORMAT_PCM_U8, SF_FORMAT_FLOAT, SF_FORMAT_DOUBLE }; /* File endian byte order names. * !! Keep in sync with endian_ids[] */ static const char *const endian_names[] = { "auto", "little", "big", "cpu" }; /* File endian byte order ids. * !! Keep in sync with endian_names[] */ static const int endian_ids[] = { SF_ENDIAN_FILE, SF_ENDIAN_LITTLE, SF_ENDIAN_BIG, SF_ENDIAN_CPU }; static int fluid_file_renderer_parse_options(char *filetype, char *format, char *endian, char *filename, SF_INFO *info); static int fluid_file_renderer_find_file_type(char *extension, int *type); static int fluid_file_renderer_find_valid_format(SF_INFO *info); #endif void fluid_file_renderer_settings(fluid_settings_t *settings) { #if LIBSNDFILE_SUPPORT SF_FORMAT_INFO finfo, cmpinfo; int major_count; int i, i2; unsigned int n; fluid_settings_register_str(settings, "audio.file.name", "fluidsynth.wav", 0); fluid_settings_register_str(settings, "audio.file.type", "auto", 0); fluid_settings_register_str(settings, "audio.file.format", "s16", 0); fluid_settings_register_str(settings, "audio.file.endian", "auto", 0); fluid_settings_add_option(settings, "audio.file.type", "auto"); sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &major_count, sizeof(int)); for(i = 0; i < major_count; i++) { finfo.format = i; sf_command(NULL, SFC_GET_FORMAT_MAJOR, &finfo, sizeof(finfo)); /* Check for duplicates */ for(i2 = 0; i2 < i; i2++) { cmpinfo.format = i2; sf_command(NULL, SFC_GET_FORMAT_MAJOR, &cmpinfo, sizeof(cmpinfo)); if(FLUID_STRCMP(cmpinfo.extension, finfo.extension) == 0) { break; } } if(i2 == i) { fluid_settings_add_option(settings, "audio.file.type", finfo.extension); } } for(n = 0; n < FLUID_N_ELEMENTS(format_names); n++) { fluid_settings_add_option(settings, "audio.file.format", format_names[n]); } for(n = 0; n < FLUID_N_ELEMENTS(endian_names); n++) { fluid_settings_add_option(settings, "audio.file.endian", endian_names[n]); } #else fluid_settings_register_str(settings, "audio.file.name", "fluidsynth.raw", 0); fluid_settings_register_str(settings, "audio.file.type", "raw", 0); fluid_settings_add_option(settings, "audio.file.type", "raw"); fluid_settings_register_str(settings, "audio.file.format", "s16", 0); fluid_settings_add_option(settings, "audio.file.format", "s16"); fluid_settings_register_str(settings, "audio.file.endian", "cpu", 0); fluid_settings_add_option(settings, "audio.file.endian", "cpu"); #endif } /** * Create a new file renderer and open the file. * @param synth The synth that creates audio data. * @return the new object, or NULL on failure * @since 1.1.0 * * NOTE: Available file types and formats depends on if libfluidsynth was * built with libsndfile support or not. If not then only RAW 16 bit output is * supported. * * Uses the following settings from the synth object: * - audio.file.name: Output filename * - audio.file.type: File type, "auto" tries to determine type from filename * extension with fallback to "wav". * - audio.file.format: Audio format * - audio.file.endian: Endian byte order, "auto" for file type's default byte order * - audio.period-size: Size of audio blocks to process * - synth.sample-rate: Sample rate to use */ fluid_file_renderer_t * new_fluid_file_renderer(fluid_synth_t *synth) { #if LIBSNDFILE_SUPPORT char *type, *format, *endian; SF_INFO info; double samplerate; int retval; #endif char *filename = NULL; fluid_file_renderer_t *dev; fluid_return_val_if_fail(synth != NULL, NULL); fluid_return_val_if_fail(synth->settings != NULL, NULL); dev = FLUID_NEW(fluid_file_renderer_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_file_renderer_t)); dev->synth = synth; fluid_settings_getint(synth->settings, "audio.period-size", &dev->period_size); #if LIBSNDFILE_SUPPORT dev->buf_size = 2 * dev->period_size * sizeof(float); dev->buf = FLUID_ARRAY(float, 2 * dev->period_size); #else dev->buf_size = 2 * dev->period_size * sizeof(short); dev->buf = FLUID_ARRAY(short, 2 * dev->period_size); #endif if(dev->buf == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } fluid_settings_dupstr(synth->settings, "audio.file.name", &filename); if(filename == NULL) { FLUID_LOG(FLUID_ERR, "No file name specified"); goto error_recovery; } #if LIBSNDFILE_SUPPORT memset(&info, 0, sizeof(info)); info.format = FLUID_FILE_RENDERER_DEFAULT_FILE_TYPE | SF_FORMAT_PCM_16; fluid_settings_dupstr(synth->settings, "audio.file.type", &type); fluid_settings_dupstr(synth->settings, "audio.file.format", &format); fluid_settings_dupstr(synth->settings, "audio.file.endian", &endian); retval = fluid_file_renderer_parse_options(type, format, endian, filename, &info); if(type) { FLUID_FREE(type); } if(format) { FLUID_FREE(format); } if(endian) { FLUID_FREE(endian); } if(!retval) { goto error_recovery; } fluid_settings_getnum(synth->settings, "synth.sample-rate", &samplerate); info.samplerate = samplerate + 0.5; info.channels = 2; /* Search for valid format for given file type, if invalid and no format was specified. * To handle Ogg/Vorbis and possibly future file types with new formats. * Checking if format is SF_FORMAT_PCM_16 isn't a fool proof way to check if * format was specified or not (if user specifies "s16" itself), but should suffice. */ if(!sf_format_check(&info) && ((info.format & SF_FORMAT_SUBMASK) != SF_FORMAT_PCM_16 || !fluid_file_renderer_find_valid_format(&info))) { FLUID_LOG(FLUID_ERR, "Invalid or unsupported audio file format settings"); goto error_recovery; } dev->sndfile = sf_open(filename, SFM_WRITE, &info); if(!dev->sndfile) { FLUID_LOG(FLUID_ERR, "Failed to open audio file '%s' for writing", filename); goto error_recovery; } /* Turn on clipping and normalization of floats (-1.0 - 1.0) */ sf_command(dev->sndfile, SFC_SET_CLIPPING, NULL, SF_TRUE); sf_command(dev->sndfile, SFC_SET_NORM_FLOAT, NULL, SF_TRUE); #else dev->file = FLUID_FOPEN(filename, "wb"); if(dev->file == NULL) { FLUID_LOG(FLUID_ERR, "Failed to open the file '%s'", filename); goto error_recovery; } #endif FLUID_FREE(filename); return dev; error_recovery: FLUID_FREE(filename); delete_fluid_file_renderer(dev); return NULL; } /** * Set vbr encoding quality (only available with libsndfile support) * @param dev File renderer object. * @param q The encoding quality, see libsndfile documentation of \c SFC_SET_VBR_ENCODING_QUALITY * @return #FLUID_OK if the quality has been successfully set, #FLUID_FAILED otherwise * @since 1.1.7 */ int fluid_file_set_encoding_quality(fluid_file_renderer_t *dev, double q) { #if LIBSNDFILE_SUPPORT if(sf_command(dev->sndfile, SFC_SET_VBR_ENCODING_QUALITY, &q, sizeof(double)) == SF_TRUE) { return FLUID_OK; } else #endif { return FLUID_FAILED; } } /** * Close file and destroy a file renderer object. * @param dev File renderer object. * @since 1.1.0 */ void delete_fluid_file_renderer(fluid_file_renderer_t *dev) { fluid_return_if_fail(dev != NULL); #if LIBSNDFILE_SUPPORT if(dev->sndfile != NULL) { int retval = sf_close(dev->sndfile); if(retval != 0) { FLUID_LOG(FLUID_WARN, "Error closing audio file: %s", sf_error_number(retval)); } } #else if(dev->file != NULL) { fclose(dev->file); } #endif FLUID_FREE(dev->buf); FLUID_FREE(dev); } /** * Write period_size samples to file. * @param dev File renderer instance * @return #FLUID_OK or #FLUID_FAILED if an error occurred * @since 1.1.0 */ int fluid_file_renderer_process_block(fluid_file_renderer_t *dev) { #if LIBSNDFILE_SUPPORT int n; fluid_synth_write_float(dev->synth, dev->period_size, dev->buf, 0, 2, dev->buf, 1, 2); n = sf_writef_float(dev->sndfile, dev->buf, dev->period_size); if(n != dev->period_size) { FLUID_LOG(FLUID_ERR, "Audio file write error: %s", sf_strerror(dev->sndfile)); return FLUID_FAILED; } return FLUID_OK; #else /* No libsndfile support */ size_t res, nmemb = dev->buf_size; fluid_synth_write_s16(dev->synth, dev->period_size, dev->buf, 0, 2, dev->buf, 1, 2); res = fwrite(dev->buf, 1, nmemb, dev->file); if(res < nmemb) { FLUID_LOG(FLUID_ERR, "Audio output file write error: %s", strerror(errno)); return FLUID_FAILED; } return FLUID_OK; #endif } #if LIBSNDFILE_SUPPORT /** * Parse a colon separated format string and configure an SF_INFO structure accordingly. * @param filetype File type string (NULL or "auto" to attempt to identify format * by filename extension, with fallback to "wav") * @param format File audio format string or NULL to use "s16" * @param endian File endian string or NULL to use "auto" which uses the file type's * default endian byte order. * @param filename File name (used by "auto" type to determine type, based on extension) * @param info Audio file info structure to configure * @return TRUE on success, FALSE otherwise */ static int fluid_file_renderer_parse_options(char *filetype, char *format, char *endian, char *filename, SF_INFO *info) { int type = -1; /* -1 indicates "auto" type */ char *s; unsigned int i; /* If "auto" type, then use extension to search for a match */ if(!filetype || FLUID_STRCMP(filetype, "auto") == 0) { type = FLUID_FILE_RENDERER_DEFAULT_FILE_TYPE; s = FLUID_STRRCHR(filename, '.'); if(s && s[1] != '\0') { if(!fluid_file_renderer_find_file_type(s + 1, &type)) { FLUID_LOG(FLUID_WARN, "Failed to determine audio file type from filename, defaulting to WAV"); } } } else if(!fluid_file_renderer_find_file_type(filetype, &type)) { FLUID_LOG(FLUID_ERR, "Invalid or unsupported audio file type '%s'", filetype); return FALSE; } info->format = (info->format & ~SF_FORMAT_TYPEMASK) | type; /* Look for subtype */ if(format) { for(i = 0; i < FLUID_N_ELEMENTS(format_names); i++) { if(FLUID_STRCMP(format, format_names[i]) == 0) { break; } } if(i >= FLUID_N_ELEMENTS(format_names)) { FLUID_LOG(FLUID_ERR, "Invalid or unsupported file audio format '%s'", format); return FALSE; } info->format = (info->format & ~SF_FORMAT_SUBMASK) | format_ids[i]; } #if LIBSNDFILE_HASVORBIS /* Force subformat to vorbis as nothing else would make sense currently */ if((info->format & SF_FORMAT_TYPEMASK) == SF_FORMAT_OGG) { info->format = (info->format & ~SF_FORMAT_SUBMASK) | SF_FORMAT_VORBIS; } #endif /* Look for endian */ if(endian) { for(i = 0; i < FLUID_N_ELEMENTS(endian_names); i++) { if(FLUID_STRCMP(endian, endian_names[i]) == 0) { break; } } if(i >= FLUID_N_ELEMENTS(endian_names)) { FLUID_LOG(FLUID_ERR, "Invalid or unsupported endian byte order '%s'", endian); return FALSE; } info->format = (info->format & ~SF_FORMAT_ENDMASK) | endian_ids[i]; } return TRUE; } /** * Searches for a supported libsndfile file type by extension. * @param extension The extension string * @param type Location to store the type (unmodified if not found) * @return TRUE if found, FALSE otherwise */ static int fluid_file_renderer_find_file_type(char *extension, int *type) { SF_FORMAT_INFO finfo; int major_count; int i; sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &major_count, sizeof(int)); for(i = 0; i < major_count; i++) { finfo.format = i; sf_command(NULL, SFC_GET_FORMAT_MAJOR, &finfo, sizeof(finfo)); if(FLUID_STRCMP(extension, finfo.extension) == 0) { break; } } if(i < major_count) { *type = finfo.format; return TRUE; } return FALSE; } /* Search for a valid audio format for a given file type */ static int fluid_file_renderer_find_valid_format(SF_INFO *info) { SF_FORMAT_INFO format_info; int count, i; sf_command(NULL, SFC_GET_FORMAT_SUBTYPE_COUNT, &count, sizeof(int)); for(i = 0; i < count; i++) { format_info.format = i; sf_command(NULL, SFC_GET_FORMAT_SUBTYPE, &format_info, sizeof(format_info)); info->format = (info->format & ~SF_FORMAT_SUBMASK) | format_info.format; if(sf_format_check(info)) { return TRUE; } } return FALSE; } #endif fluidsynth-2.1.1/src/bindings/fluid_ladspa.c000066400000000000000000001420541362231004000210600ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* This module: 3/2002 * Author: Markus Nentwig, nentwig@users.sourceforge.net * * Complete rewrite: 10/2017 * Author: Marcus Weseloh */ #include "fluid_ladspa.h" #if defined(LADSPA) || defined(__DOXYGEN__) #include #include #define FLUID_LADSPA_MAX_EFFECTS 100 #define FLUID_LADSPA_MAX_NODES 100 typedef enum _fluid_ladspa_state_t { FLUID_LADSPA_INACTIVE = 0, FLUID_LADSPA_ACTIVE, FLUID_LADSPA_RUNNING } fluid_ladspa_state_t; typedef enum _fluid_ladspa_dir_t { FLUID_LADSPA_INPUT, FLUID_LADSPA_OUTPUT, } fluid_ladspa_dir_t; typedef enum _fluid_ladspa_node_type_t { FLUID_LADSPA_NODE_AUDIO = 1, FLUID_LADSPA_NODE_CONTROL = 2, FLUID_LADSPA_NODE_EFFECT = 4, FLUID_LADSPA_NODE_HOST = 8, FLUID_LADSPA_NODE_USER = 16, } fluid_ladspa_node_type_t; typedef struct _fluid_ladspa_node_t { char *name; fluid_ladspa_node_type_t type; /* The buffer that LADSPA effects read and/or write to. * If FluidSynth has been compiled WITH_FLOAT, then this * points to the host buffer, otherwise it's a separate * buffer. */ LADSPA_Data *effect_buffer; /* Only set for host nodes, points to the host buffer */ fluid_real_t *host_buffer; int num_inputs; int num_outputs; } fluid_ladspa_node_t; typedef struct _fluid_ladspa_effect_t { char *name; /* Pointer to the library that this effect was loaded from */ fluid_module_t *lib; /* The descriptor defines the plugin implementation, the * handle points to an instance of that plugin */ const LADSPA_Descriptor *desc; LADSPA_Handle *handle; int active; /* Decides if we should call the run (mix = 0) or run_adding (mix = 1) * plugin interface */ int mix; /* Used to keep track of the port connection state */ fluid_ladspa_node_t **port_nodes; } fluid_ladspa_effect_t; struct _fluid_ladspa_fx_t { unsigned long sample_rate; /* The buffer size for all audio buffers (in samples) */ int buffer_size; fluid_ladspa_node_t *nodes[FLUID_LADSPA_MAX_NODES]; int num_nodes; /* Pointers to host nodes in nodes array */ fluid_ladspa_node_t *host_nodes[FLUID_LADSPA_MAX_NODES]; int num_host_nodes; /* Pointers to user audio nodes in nodes array */ fluid_ladspa_node_t *audio_nodes[FLUID_LADSPA_MAX_NODES]; int num_audio_nodes; fluid_ladspa_effect_t *effects[FLUID_LADSPA_MAX_EFFECTS]; int num_effects; fluid_rec_mutex_t api_mutex; fluid_atomic_int_t state; int pending_deactivation; fluid_cond_mutex_t *run_finished_mutex; fluid_cond_t *run_finished_cond; }; #define LADSPA_API_ENTER(_fx) (fluid_rec_mutex_lock((_fx)->api_mutex)) #define LADSPA_API_RETURN(_fx, _ret) \ fluid_rec_mutex_unlock((_fx)->api_mutex); \ return (_ret) static void clear_ladspa(fluid_ladspa_fx_t *fx); /* Node helpers */ static fluid_ladspa_node_t *new_fluid_ladspa_node(fluid_ladspa_fx_t *fx, const char *name, fluid_ladspa_node_type_t type, fluid_real_t *host_buffer); static void delete_fluid_ladspa_node(fluid_ladspa_node_t *node); static fluid_ladspa_node_t *get_node(fluid_ladspa_fx_t *fx, const char *name); /* Effect helpers */ static fluid_ladspa_effect_t * new_fluid_ladspa_effect(fluid_ladspa_fx_t *fx, const char *lib_name, const char *plugin_name); static void delete_fluid_ladspa_effect(fluid_ladspa_effect_t *effect); static void activate_effect(fluid_ladspa_effect_t *effect); static void deactivate_effect(fluid_ladspa_effect_t *effect); static fluid_ladspa_effect_t *get_effect(fluid_ladspa_fx_t *fx, const char *name); static int get_effect_port_idx(const fluid_ladspa_effect_t *effect, const char *name); static LADSPA_Data get_default_port_value(fluid_ladspa_effect_t *effect, unsigned int port_idx, int sample_rate); static void connect_node_to_port(fluid_ladspa_node_t *node, fluid_ladspa_dir_t dir, fluid_ladspa_effect_t *effect, int port_idx); static int create_control_port_nodes(fluid_ladspa_fx_t *fx, fluid_ladspa_effect_t *effect); /* Plugin helpers */ static const LADSPA_Descriptor *get_plugin_descriptor(fluid_module_t *lib, const char *name); /* Sanity checks */ static int check_all_ports_connected(fluid_ladspa_effect_t *effect, const char **name); static int check_no_inplace_broken(fluid_ladspa_effect_t *effect, const char **name1, const char **name2); static int check_host_output_used(fluid_ladspa_fx_t *fx); static int check_all_audio_nodes_connected(fluid_ladspa_fx_t *fx, const char **name); #ifndef WITH_FLOAT static FLUID_INLINE void copy_host_to_effect_buffers(fluid_ladspa_fx_t *fx, int num_samples); static FLUID_INLINE void copy_effect_to_host_buffers(fluid_ladspa_fx_t *fx, int num_samples); #endif /** * Creates a new LADSPA effects unit. * * @param sample_rate sample_rate for the LADSPA effects * @param buffer_size size of all audio buffers * @return pointer to the new LADSPA effects unit */ fluid_ladspa_fx_t *new_fluid_ladspa_fx(fluid_real_t sample_rate, int buffer_size) { fluid_ladspa_fx_t *fx; fx = FLUID_NEW(fluid_ladspa_fx_t); if(fx == NULL) { return NULL; } FLUID_MEMSET(fx, 0, sizeof(fluid_ladspa_fx_t)); /* Setup recursive mutex to protect access to public API */ fluid_rec_mutex_init(fx->api_mutex); fluid_atomic_int_set(&fx->state, FLUID_LADSPA_INACTIVE); fx->buffer_size = buffer_size; /* add 0.5 to minimize overall casting error */ fx->sample_rate = (unsigned long)(sample_rate + 0.5); /* Setup mutex and cond used to signal that fluid_ladspa_run has finished */ fx->run_finished_mutex = new_fluid_cond_mutex(); if(fx->run_finished_mutex == NULL) { goto error_recovery; } fx->run_finished_cond = new_fluid_cond(); if(fx->run_finished_cond == NULL) { goto error_recovery; } return fx; error_recovery: delete_fluid_ladspa_fx(fx); return NULL; } /** * Destroys and frees a LADSPA effects unit previously created * with new_fluid_ladspa_fx. * * @note This function does not check the engine state for * possible users, so make sure that you only call this * if you are sure nobody is using the engine anymore (especially * that nobody calls fluid_ladspa_run) * * @param fx LADSPA effects instance */ void delete_fluid_ladspa_fx(fluid_ladspa_fx_t *fx) { int i; fluid_return_if_fail(fx != NULL); clear_ladspa(fx); /* clear the remaining input or output nodes */ for(i = 0; i < fx->num_nodes; i++) { delete_fluid_ladspa_node(fx->nodes[i]); } if(fx->run_finished_cond != NULL) { delete_fluid_cond(fx->run_finished_cond); } if(fx->run_finished_mutex != NULL) { delete_fluid_cond_mutex(fx->run_finished_mutex); } fluid_rec_mutex_destroy(fx->api_mutex); FLUID_FREE(fx); } /** * Add host buffers to the LADSPA engine. * * @note The size of the buffers pointed to by the buffers array must be * at least as large as the buffer size given to new_fluid_ladspa_fx. * * @param fx LADSPA fx instance * @param prefix common name prefix for the created nodes * @param num_buffers number of of buffers buffer array * @param buffers array of sample buffers * @param buf_stride number of samples contained in one buffer * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_ladspa_add_host_ports(fluid_ladspa_fx_t *fx, const char *prefix, int num_buffers, fluid_real_t buffers[], int buf_stride) { int i; char name[99]; LADSPA_API_ENTER(fx); if(fluid_ladspa_is_active(fx)) { LADSPA_API_RETURN(fx, FLUID_FAILED); } /* Create nodes for all channels */ for(i = 0; i < num_buffers; i++) { /* If there is more than one buffer, then append a 1-based index to each node name */ if(num_buffers > 1) { FLUID_SNPRINTF(name, sizeof(name), "%s%d", prefix, (i + 1)); } else { FLUID_STRNCPY(name, prefix, sizeof(name)); } if(new_fluid_ladspa_node(fx, name, FLUID_LADSPA_NODE_AUDIO | FLUID_LADSPA_NODE_HOST, &buffers[i * buf_stride]) == NULL) { LADSPA_API_RETURN(fx, FLUID_FAILED); } } LADSPA_API_RETURN(fx, FLUID_OK); } /** * Set the sample rate of the LADSPA effects. * * Resets the LADSPA effects if the sample rate is different from the * previous sample rate. * * @param fx LADSPA fx instance * @param sample_rate new sample rate * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_ladspa_set_sample_rate(fluid_ladspa_fx_t *fx, fluid_real_t sample_rate) { unsigned long new_sample_rate; LADSPA_API_ENTER(fx); /* Add 0.5 to minimize rounding errors */ new_sample_rate = (unsigned long)(sample_rate + 0.5); if(fx->sample_rate == new_sample_rate) { LADSPA_API_RETURN(fx, FLUID_OK); } if(fluid_ladspa_is_active(fx)) { if(fluid_ladspa_reset(fx) != FLUID_OK) { FLUID_LOG(FLUID_ERR, "Failed to reset LADSPA, unable to change sample rate"); LADSPA_API_RETURN(fx, FLUID_FAILED); } } fx->sample_rate = new_sample_rate; LADSPA_API_RETURN(fx, FLUID_OK); } /** * Check if the LADSPA engine is currently used to render audio * * If an engine is active, the only allowed user actions are deactivation or * setting values of control ports on effects. Anything else, especially * adding or removing effects, buffers or ports, is only allowed in deactivated * state. * * @param fx LADSPA fx instance * @return TRUE if LADSPA effects engine is active, otherwise FALSE */ int fluid_ladspa_is_active(fluid_ladspa_fx_t *fx) { int is_active; fluid_return_val_if_fail(fx != NULL, FALSE); LADSPA_API_ENTER(fx); is_active = (fluid_atomic_int_get(&fx->state) != FLUID_LADSPA_INACTIVE); LADSPA_API_RETURN(fx, is_active); } /** * Activate the LADSPA fx instance and each configured effect. * * @param fx LADSPA fx instance * @return FLUID_OK if activation succeeded or already active, otherwise FLUID_FAILED */ int fluid_ladspa_activate(fluid_ladspa_fx_t *fx) { int i; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); if(fluid_ladspa_is_active(fx)) { LADSPA_API_RETURN(fx, FLUID_FAILED); } if(fluid_ladspa_check(fx, NULL, 0) != FLUID_OK) { FLUID_LOG(FLUID_ERR, "LADSPA check failed, unable to activate effects"); LADSPA_API_RETURN(fx, FLUID_FAILED); } for(i = 0; i < fx->num_effects; i++) { activate_effect(fx->effects[i]); } if(!fluid_atomic_int_compare_and_exchange(&fx->state, FLUID_LADSPA_INACTIVE, FLUID_LADSPA_ACTIVE)) { for(i = 0; i < fx->num_effects; i++) { deactivate_effect(fx->effects[i]); } LADSPA_API_RETURN(fx, FLUID_FAILED); } LADSPA_API_RETURN(fx, FLUID_OK); } /** * Deactivate a LADSPA fx instance and all configured effects. * * @note This function may sleep. * * @param fx LADSPA fx instance * @return FLUID_OK if deactivation succeeded, otherwise FLUID_FAILED */ int fluid_ladspa_deactivate(fluid_ladspa_fx_t *fx) { int i; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); /* If we are already inactive, then simply return success */ if(fluid_atomic_int_get(&fx->state) == FLUID_LADSPA_INACTIVE) { LADSPA_API_RETURN(fx, FLUID_OK); } /* Notify fluid_ladspa_run that we would like to deactivate and that it should * send us a signal when its done if it is currently running */ fx->pending_deactivation = 1; fluid_cond_mutex_lock(fx->run_finished_mutex); while(!fluid_atomic_int_compare_and_exchange(&fx->state, FLUID_LADSPA_ACTIVE, FLUID_LADSPA_INACTIVE)) { fluid_cond_wait(fx->run_finished_cond, fx->run_finished_mutex); } fluid_cond_mutex_unlock(fx->run_finished_mutex); /* Now that we're inactive, deactivate all effects and return success */ for(i = 0; i < fx->num_effects; i++) { deactivate_effect(fx->effects[i]); } fx->pending_deactivation = 0; LADSPA_API_RETURN(fx, FLUID_OK); } /** * Reset the LADSPA effects engine: Deactivate LADSPA if currently active, remove all * effects, remove all user nodes and unload all libraries. * * @param fx LADSPA fx instance * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_ladspa_reset(fluid_ladspa_fx_t *fx) { fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); if(fluid_ladspa_is_active(fx)) { if(fluid_ladspa_deactivate(fx) != FLUID_OK) { LADSPA_API_RETURN(fx, FLUID_FAILED); } } clear_ladspa(fx); LADSPA_API_RETURN(fx, FLUID_OK); } /** * Processes audio data via the LADSPA effects unit. * * FluidSynth calls this function during main output mixing, just after * the internal reverb and chorus effects have been processed. * * It copies audio data from the supplied buffers, runs all effects and copies the * resulting audio back into the same buffers. * * @param fx LADSPA effects instance * @param block_count number of blocks to render * @param block_size number of samples in a block */ void fluid_ladspa_run(fluid_ladspa_fx_t *fx, int block_count, int block_size) { int i; int num_samples; fluid_ladspa_effect_t *effect; /* Somebody wants to deactivate the engine, so let's give them a chance to do that. * And check that there is at least one effect, to avoid the overhead of the * atomic compare and exchange on an unconfigured LADSPA engine. */ if(fx->pending_deactivation || fx->num_effects == 0) { return; } /* Inform the engine that we are now running pluings, and bail out if it's not active */ if(!fluid_atomic_int_compare_and_exchange(&fx->state, FLUID_LADSPA_ACTIVE, FLUID_LADSPA_RUNNING)) { return; } num_samples = block_count * block_size; #ifndef WITH_FLOAT copy_host_to_effect_buffers(fx, num_samples); #endif for(i = 0; i < fx->num_audio_nodes; i++) { FLUID_MEMSET(fx->audio_nodes[i]->effect_buffer, 0, fx->buffer_size * sizeof(LADSPA_Data)); } /* Run each effect in the order that they were added */ for(i = 0; i < fx->num_effects; i++) { effect = fx->effects[i]; if(effect->mix) { effect->desc->run_adding(effect->handle, num_samples); } else { effect->desc->run(effect->handle, num_samples); } } #ifndef WITH_FLOAT copy_effect_to_host_buffers(fx, num_samples); #endif if(!fluid_atomic_int_compare_and_exchange(&fx->state, FLUID_LADSPA_RUNNING, FLUID_LADSPA_ACTIVE)) { FLUID_LOG(FLUID_ERR, "Unable to reset LADSPA running state!"); } /* If deactivation was requested while in running state, notify that we've finished now * and deactivation can proceed */ if(fx->pending_deactivation) { fluid_cond_mutex_lock(fx->run_finished_mutex); fluid_cond_broadcast(fx->run_finished_cond); fluid_cond_mutex_unlock(fx->run_finished_mutex); } } /** * Check if the effect plugin supports the run_adding and set_run_adding_gain * interfaces necessary for output mixing * * @param fx LADSPA fx * @param name the name of the effect * @return TRUE if mix mode is supported, otherwise FALSE */ int fluid_ladspa_effect_can_mix(fluid_ladspa_fx_t *fx, const char *name) { int can_mix; fluid_ladspa_effect_t *effect; fluid_return_val_if_fail(fx != NULL, FALSE); fluid_return_val_if_fail(name != NULL, FALSE); LADSPA_API_ENTER(fx); effect = get_effect(fx, name); if(effect == NULL) { LADSPA_API_RETURN(fx, FALSE); } can_mix = (effect->desc->run_adding != NULL && effect->desc->set_run_adding_gain != NULL); LADSPA_API_RETURN(fx, can_mix); } /** * Set if the effect should replace everything in the output buffers (mix = 0, default) * or add to the buffers with a fixed gain (mix = 1). * * @param fx LADSPA fx instance * @param name the name of the effect * @param mix (boolean) if to enable mix mode * @param gain the gain to apply to the effect output before adding to output. * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_ladspa_effect_set_mix(fluid_ladspa_fx_t *fx, const char *name, int mix, float gain) { fluid_ladspa_effect_t *effect; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); effect = get_effect(fx, name); if(effect == NULL) { LADSPA_API_RETURN(fx, FLUID_FAILED); } if(mix) { if(!fluid_ladspa_effect_can_mix(fx, name)) { FLUID_LOG(FLUID_ERR, "Effect '%s' does not support mix mode", name); LADSPA_API_RETURN(fx, FLUID_FAILED); } effect->desc->set_run_adding_gain(effect->handle, gain); } effect->mix = mix; LADSPA_API_RETURN(fx, FLUID_OK); } static void clear_ladspa(fluid_ladspa_fx_t *fx) { int i; /* Deactivate and free all effects */ for(i = 0; i < fx->num_effects; i++) { deactivate_effect(fx->effects[i]); delete_fluid_ladspa_effect(fx->effects[i]); } fx->num_effects = 0; /* Delete all nodes (but not the host audio nodes) */ for(i = 0; i < fx->num_nodes; i++) { if((fx->nodes[i]->type & FLUID_LADSPA_NODE_HOST) && (fx->nodes[i]->type & FLUID_LADSPA_NODE_AUDIO)) { continue; } delete_fluid_ladspa_node(fx->nodes[i]); } /* Fill the list with the host nodes and reset the connection counts */ for(i = 0; i < fx->num_host_nodes; i++) { fx->host_nodes[i]->num_inputs = 0; fx->host_nodes[i]->num_outputs = 0; fx->nodes[i] = fx->host_nodes[i]; } fx->num_nodes = fx->num_host_nodes; /* Reset list of user audio nodes */ fx->num_audio_nodes = 0; } /** * Check if a named host port exists * * @param fx LADSPA fx instance * @param name the port name * @return TRUE if the host port exists, otherwise FALSE */ int fluid_ladspa_host_port_exists(fluid_ladspa_fx_t *fx, const char *name) { fluid_ladspa_node_t *node; fluid_return_val_if_fail(fx != NULL, FALSE); fluid_return_val_if_fail(name != NULL, FALSE); LADSPA_API_ENTER(fx); node = get_node(fx, name); if(node == NULL) { LADSPA_API_RETURN(fx, FALSE); } if(node->type & FLUID_LADSPA_NODE_HOST) { LADSPA_API_RETURN(fx, TRUE); } LADSPA_API_RETURN(fx, FALSE); } /** * Check if a named user buffer exists * * @param fx LADSPA fx instance * @param name the buffer name * @return TRUE if the buffer exists, otherwise FALSE */ int fluid_ladspa_buffer_exists(fluid_ladspa_fx_t *fx, const char *name) { int exists; fluid_ladspa_node_t *node; fluid_return_val_if_fail(fx != NULL, FALSE); fluid_return_val_if_fail(name != NULL, FALSE); LADSPA_API_ENTER(fx); node = get_node(fx, name); if(node == NULL) { LADSPA_API_RETURN(fx, FALSE); } exists = ((node->type & FLUID_LADSPA_NODE_AUDIO) && (node->type & FLUID_LADSPA_NODE_USER)); LADSPA_API_RETURN(fx, exists); } /** * Check if the named port exists on an effect * * @param fx LADSPA fx instance * @param effect_name name of the effect * @param port_name the port name * @return TRUE if port was found, otherwise FALSE */ int fluid_ladspa_effect_port_exists(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name) { fluid_ladspa_effect_t *effect; int port_exists; fluid_return_val_if_fail(fx != NULL, FALSE); fluid_return_val_if_fail(effect_name != NULL, FALSE); fluid_return_val_if_fail(port_name != NULL, FALSE); LADSPA_API_ENTER(fx); effect = get_effect(fx, effect_name); if(effect == NULL) { LADSPA_API_RETURN(fx, FALSE); } port_exists = (get_effect_port_idx(effect, port_name) != -1); LADSPA_API_RETURN(fx, port_exists); } /** * Create and add a new audio buffer. * * @param fx LADSPA effects instance * @param name name of the new buffer * @return FLUID_OK on success, FLUID_FAILED on error */ int fluid_ladspa_add_buffer(fluid_ladspa_fx_t *fx, const char *name) { fluid_ladspa_node_t *node; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); if(fluid_ladspa_is_active(fx)) { LADSPA_API_RETURN(fx, FLUID_FAILED); } node = new_fluid_ladspa_node(fx, name, FLUID_LADSPA_NODE_AUDIO | FLUID_LADSPA_NODE_USER, NULL); if(node == NULL) { LADSPA_API_RETURN(fx, FLUID_FAILED); } LADSPA_API_RETURN(fx, FLUID_OK); } /** * Set the value of an effect control port * * @param fx LADSPA fx instance * @param effect_name name of the effect * @param port_name name of the port * @param val floating point value * @return FLUID_OK on success, FLUID_FAILED on error */ int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, float val) { fluid_ladspa_node_t *node; fluid_ladspa_effect_t *effect; int port_idx; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); fluid_return_val_if_fail(effect_name != NULL, FLUID_FAILED); fluid_return_val_if_fail(port_name != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); effect = get_effect(fx, effect_name); if(effect == NULL) { LADSPA_API_RETURN(fx, FLUID_FAILED); } port_idx = get_effect_port_idx(effect, port_name); if(port_idx < 0) { LADSPA_API_RETURN(fx, FLUID_FAILED); } if(!LADSPA_IS_PORT_CONTROL(effect->desc->PortDescriptors[port_idx])) { LADSPA_API_RETURN(fx, FLUID_FAILED); } node = effect->port_nodes[port_idx]; if(node == NULL) { LADSPA_API_RETURN(fx, FLUID_FAILED); } node->effect_buffer[0] = val; LADSPA_API_RETURN(fx, FLUID_OK); } /** * Create an effect, i.e. an instance of a LADSPA plugin * * @param fx LADSPA effects instance * @param effect_name name of the effect * @param lib_name filename of ladspa plugin library * @param plugin_name optional, plugin name if there is more than one plugin in the library * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_ladspa_add_effect(fluid_ladspa_fx_t *fx, const char *effect_name, const char *lib_name, const char *plugin_name) { fluid_ladspa_effect_t *effect; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); fluid_return_val_if_fail(effect_name != NULL, FLUID_FAILED); fluid_return_val_if_fail(lib_name != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); if(fluid_ladspa_is_active(fx)) { LADSPA_API_RETURN(fx, FLUID_FAILED); } if(fx->num_effects >= FLUID_LADSPA_MAX_EFFECTS) { FLUID_LOG(FLUID_ERR, "Maximum number of LADSPA effects reached"); LADSPA_API_RETURN(fx, FLUID_FAILED); } effect = new_fluid_ladspa_effect(fx, lib_name, plugin_name); if(effect == NULL) { LADSPA_API_RETURN(fx, FLUID_FAILED); } effect->name = FLUID_STRDUP(effect_name); if(effect->name == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); delete_fluid_ladspa_effect(effect); LADSPA_API_RETURN(fx, FLUID_FAILED); } if(create_control_port_nodes(fx, effect) != FLUID_OK) { delete_fluid_ladspa_effect(effect); LADSPA_API_RETURN(fx, FLUID_FAILED); } fx->effects[fx->num_effects++] = effect; LADSPA_API_RETURN(fx, FLUID_OK); } /** * Connect an effect port to a host port or buffer * * @note There is no corresponding disconnect function. If the connections need to be changed, * clear everything with fluid_ladspa_reset and start again from scratch. * * @param fx LADSPA effects instance * @param effect_name name of the effect * @param port_name the port name to connect to (case-insensitive prefix match) * @param name the host port or buffer to connect to (case-insensitive) * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, const char *name) { fluid_ladspa_effect_t *effect; fluid_ladspa_node_t *node; int port_idx; int port_flags; int dir; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); fluid_return_val_if_fail(effect_name != NULL, FLUID_FAILED); fluid_return_val_if_fail(port_name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); LADSPA_API_ENTER(fx); if(fluid_ladspa_is_active(fx)) { LADSPA_API_RETURN(fx, FLUID_FAILED); } effect = get_effect(fx, effect_name); if(effect == NULL) { FLUID_LOG(FLUID_ERR, "Effect '%s' not found", effect_name); LADSPA_API_RETURN(fx, FLUID_FAILED); } port_idx = get_effect_port_idx(effect, port_name); if(port_idx < 0) { FLUID_LOG(FLUID_ERR, "Port '%s' not found on effect '%s'", port_name, effect_name); LADSPA_API_RETURN(fx, FLUID_FAILED); } node = get_node(fx, name); if(node == NULL) { FLUID_LOG(FLUID_ERR, "Node '%s' not found", name); LADSPA_API_RETURN(fx, FLUID_FAILED); } port_flags = effect->desc->PortDescriptors[port_idx]; /* Check that requested port type matches the node type */ if(LADSPA_IS_PORT_CONTROL(port_flags) && !(node->type & FLUID_LADSPA_NODE_CONTROL)) { FLUID_LOG(FLUID_ERR, "Control port '%s' on effect '%s' can only connect to " "other control ports", port_name, effect_name); LADSPA_API_RETURN(fx, FLUID_FAILED); } else if(LADSPA_IS_PORT_AUDIO(port_flags) && !(node->type & FLUID_LADSPA_NODE_AUDIO)) { FLUID_LOG(FLUID_ERR, "Audio port '%s' on effect '%s' can only connect to" "other audio port or buffer", port_name, effect_name); LADSPA_API_RETURN(fx, FLUID_FAILED); } if(LADSPA_IS_PORT_INPUT(port_flags)) { dir = FLUID_LADSPA_INPUT; } else { dir = FLUID_LADSPA_OUTPUT; } connect_node_to_port(node, dir, effect, port_idx); LADSPA_API_RETURN(fx, FLUID_OK); } /** * Do a sanity check for problems in the LADSPA setup * * If the check detects problems and the err pointer is not NULL, a description of the first found * problem is written to that string (up to err_size - 1 characters). * * @param fx LADSPA fx instance * @param err externally provided buffer for the error message. Set to NULL if you don't need an error message. * @param err_size size of the err buffer * @return FLUID_OK if setup is valid, otherwise FLUID_FAILED (err will contain the error message) */ int fluid_ladspa_check(fluid_ladspa_fx_t *fx, char *err, int err_size) { int i; const char *str; const char *str2; fluid_ladspa_effect_t *effect; fluid_return_val_if_fail(fx != NULL, FLUID_FAILED); fluid_return_val_if_fail(err == NULL || err_size >= 0, FLUID_FAILED); LADSPA_API_ENTER(fx); /* Check that there is at least one effect */ if(fx->num_effects == 0) { FLUID_SNPRINTF(err, err_size, "No effects configured\n"); LADSPA_API_RETURN(fx, FLUID_FAILED); } for(i = 0; i < fx->num_effects; i++) { effect = fx->effects[i]; if(check_all_ports_connected(effect, &str) == FLUID_FAILED) { if(err != NULL) { FLUID_SNPRINTF(err, err_size, "Port '%s' on effect '%s' is not connected\n", str, effect->name); } LADSPA_API_RETURN(fx, FLUID_FAILED); } if(check_no_inplace_broken(effect, &str, &str2) == FLUID_FAILED) { if(err != NULL) { FLUID_SNPRINTF(err, err_size, "effect '%s' is in-place broken, '%s' and '%s' are not allowed " "to connect to the same node\n", effect->name, str, str2); } LADSPA_API_RETURN(fx, FLUID_FAILED); } } if(check_host_output_used(fx) == FLUID_FAILED) { if(err != NULL) { FLUID_SNPRINTF(err, err_size, "No effect outputs to one the host nodes\n"); } LADSPA_API_RETURN(fx, FLUID_FAILED); } if(check_all_audio_nodes_connected(fx, &str) == FLUID_FAILED) { if(err != NULL) { FLUID_SNPRINTF(err, err_size, "Audio node '%s' is not fully connected\n", str); } LADSPA_API_RETURN(fx, FLUID_FAILED); } LADSPA_API_RETURN(fx, FLUID_OK); } static void activate_effect(fluid_ladspa_effect_t *effect) { if(!effect->active) { effect->active = TRUE; if(effect->desc->activate != NULL) { effect->desc->activate(effect->handle); } } } static void deactivate_effect(fluid_ladspa_effect_t *effect) { if(effect->active) { effect->active = FALSE; if(effect->desc->deactivate != NULL) { effect->desc->deactivate(effect->handle); } } } /** * Return a LADSPA node by name. Nodes are searched case insensitive. * * @param fx LADSPA fx instance * @param name the node name string * @return a fluid_ladspa_node_t pointer or NULL if not found */ static fluid_ladspa_node_t *get_node(fluid_ladspa_fx_t *fx, const char *name) { int i; for(i = 0; i < fx->num_nodes; i++) { if(FLUID_STRCASECMP(fx->nodes[i]->name, name) == 0) { return fx->nodes[i]; } } return NULL; } /** * Return a LADSPA effect port index by name, using a 'fuzzy match'. * * Returns the first effect port which matches the name. If no exact match is * found, returns the port that starts with the specified name, but only if there is * only one such match. * * @param effect pointer to fluid_ladspa_effect_t * @param name the port name * @return index of the port in the effect or -1 on error */ static int get_effect_port_idx(const fluid_ladspa_effect_t *effect, const char *name) { unsigned int i; int port = -1; for(i = 0; i < effect->desc->PortCount; i++) { if(FLUID_STRNCASECMP(effect->desc->PortNames[i], name, FLUID_STRLEN(name)) == 0) { /* exact match, return immediately */ if(FLUID_STRLEN(effect->desc->PortNames[i]) == FLUID_STRLEN(name)) { return i; } /* more than one prefix match should be treated as not found */ if(port != -1) { return -1; } port = i; } } return port; } /** * Return a LADSPA descriptor structure for a plugin in a LADSPA library. * * If name is optional if the library contains only one plugin. * * @param lib pointer to the dynamically loaded library * @param name name (LADSPA Label) of the plugin * @return pointer to LADSPA_Descriptor, NULL on error or if not found */ static const LADSPA_Descriptor *get_plugin_descriptor(fluid_module_t *lib, const char *name) { const LADSPA_Descriptor *desc; const LADSPA_Descriptor *last_desc = NULL; LADSPA_Descriptor_Function ladspa_descriptor; int i; if(!fluid_module_symbol(lib, "ladspa_descriptor", (void *)&ladspa_descriptor)) { FLUID_LOG(FLUID_ERR, "Unable to find ladspa_descriptor in '%s'. " "Is this really a LADSPA plugin?", fluid_module_name(lib)); return NULL; } for(i = 0; /* endless */; i++) { desc = ladspa_descriptor(i); if(desc == NULL) { break; } if(name != NULL && FLUID_STRCMP(desc->Label, name) == 0) { return desc; } last_desc = desc; } if(name == NULL) { if(i == 1) { return last_desc; } FLUID_LOG(FLUID_ERR, "Library contains more than one plugin, please specify " "the plugin label"); } return NULL; } /** * Instantiate a LADSPA plugin from a library and set up the associated * control structures needed by the LADSPA fx engine. * * If the library contains only one plugin, then the name is optional. * Plugins are identified by their "Label" in the plugin descriptor structure. * * @param fx LADSPA fx instance * @param lib_name file path of the plugin library * @param plugin_name (optional) string name of the plugin (the LADSPA Label) * @return pointer to the new ladspa_plugin_t structure or NULL on error */ static fluid_ladspa_effect_t * new_fluid_ladspa_effect(fluid_ladspa_fx_t *fx, const char *lib_name, const char *plugin_name) { fluid_ladspa_effect_t *effect; effect = FLUID_NEW(fluid_ladspa_effect_t); if(effect == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(effect, 0, sizeof(fluid_ladspa_effect_t)); effect->lib = fluid_module_open(lib_name); if(effect->lib == NULL) { FLUID_LOG(FLUID_ERR, "Unable to load LADSPA library '%s': %s", lib_name, fluid_module_error()); delete_fluid_ladspa_effect(effect); return NULL; } effect->desc = get_plugin_descriptor(effect->lib, plugin_name); if(effect->desc == NULL) { delete_fluid_ladspa_effect(effect); return NULL; } effect->handle = effect->desc->instantiate(effect->desc, fx->sample_rate); if(effect->handle == NULL) { delete_fluid_ladspa_effect(effect); FLUID_LOG(FLUID_ERR, "Unable to instantiate plugin '%s' from '%s'", plugin_name, lib_name); return NULL; } effect->port_nodes = FLUID_ARRAY(fluid_ladspa_node_t *, effect->desc->PortCount); if(effect->port_nodes == NULL) { delete_fluid_ladspa_effect(effect); FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(effect->port_nodes, 0, effect->desc->PortCount * sizeof(fluid_ladspa_node_t *)); return effect; } static void delete_fluid_ladspa_effect(fluid_ladspa_effect_t *effect) { fluid_return_if_fail(effect != NULL); FLUID_FREE(effect->port_nodes); if(effect->handle != NULL && effect->desc != NULL && effect->desc->cleanup != NULL) { effect->desc->cleanup(effect->handle); } if(effect->lib != NULL) { fluid_module_close(effect->lib); } FLUID_FREE(effect->name); FLUID_FREE(effect); } static fluid_ladspa_node_t *new_fluid_ladspa_node(fluid_ladspa_fx_t *fx, const char *name, fluid_ladspa_node_type_t type, fluid_real_t *host_buffer) { int buffer_size; fluid_ladspa_node_t *node; /* For named nodes, make sure that the name is unique */ if(FLUID_STRLEN(name) > 0 && get_node(fx, name) != NULL) { return NULL; } if(fx->num_nodes >= FLUID_LADSPA_MAX_NODES) { FLUID_LOG(FLUID_ERR, "Maximum number of nodes reached"); return NULL; } node = FLUID_NEW(fluid_ladspa_node_t); if(node == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(node, 0, sizeof(fluid_ladspa_node_t)); node->name = FLUID_STRDUP(name); if(node->name == NULL) { delete_fluid_ladspa_node(node); FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } node->type = type; node->host_buffer = host_buffer; /* host audio nodes need a host buffer set */ if((type & FLUID_LADSPA_NODE_AUDIO) && (type & FLUID_LADSPA_NODE_HOST)) { if(node->host_buffer == NULL) { delete_fluid_ladspa_node(node); return NULL; } #ifdef WITH_FLOAT /* If the host uses the same floating-point width as LADSPA, then effects * can work in-place on the host buffer. Otherwise well need a separate * buffer for type conversion. */ node->effect_buffer = node->host_buffer; #endif } if(node->effect_buffer == NULL) { /* Control nodes only store a single floating-point value */ buffer_size = (type & FLUID_LADSPA_NODE_CONTROL) ? 1 : fx->buffer_size; node->effect_buffer = FLUID_ARRAY(LADSPA_Data, buffer_size); if(node->effect_buffer == NULL) { delete_fluid_ladspa_node(node); FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(node->effect_buffer, 0, buffer_size * sizeof(LADSPA_Data)); } fx->nodes[fx->num_nodes++] = node; /* Host and user audio nodes are also noted in separate lists to access them * quickly during fluid_ladspa_run */ if((type & FLUID_LADSPA_NODE_AUDIO) && (type & FLUID_LADSPA_NODE_HOST)) { fx->host_nodes[fx->num_host_nodes++] = node; } else if((type & FLUID_LADSPA_NODE_AUDIO) && (type & FLUID_LADSPA_NODE_USER)) { fx->audio_nodes[fx->num_audio_nodes++] = node; } return node; } static void delete_fluid_ladspa_node(fluid_ladspa_node_t *node) { fluid_return_if_fail(node != NULL); /* If effect_buffer the same as host_buffer, then the effect_buffer has been * provided externally, so don't free */ if((node->effect_buffer != NULL) && ((void *)node->effect_buffer != (void *)node->host_buffer)) { FLUID_FREE(node->effect_buffer); } FLUID_FREE(node->name); FLUID_FREE(node); } /** * Retrieve a ladspa_effect_t instance by it's name. * * @param fx LADSPA effects instance * @param name effect name * @return pointer to effect or NULL if not found */ static fluid_ladspa_effect_t *get_effect(fluid_ladspa_fx_t *fx, const char *name) { int i; LADSPA_API_ENTER(fx); for(i = 0; i < fx->num_effects; i++) { if(FLUID_STRNCASECMP(fx->effects[i]->name, name, FLUID_STRLEN(name)) == 0) { LADSPA_API_RETURN(fx, fx->effects[i]); } } LADSPA_API_RETURN(fx, NULL); } /** * Set the passed in float pointer to the default value of a effect port, as specified * by the LADSPA port hints. If no default hints are found or the port is not a control * node, it returns 0.0f; * * The sample rate is needed because some LADSPA port default hints are expressed as a * fraction of the current sample rate. * * @param effect pointer to effect instance * @param port_idx index of the port in the effect * @param sample_rate the current sample rate of the LADSPA fx * @return default port value or 0.0f */ static LADSPA_Data get_default_port_value(fluid_ladspa_effect_t *effect, unsigned int port_idx, int sample_rate) { const LADSPA_PortRangeHint *hint; LADSPA_PortRangeHintDescriptor flags; LADSPA_Data value = 0.0; float low_factor = 0.0; float high_factor = 0.0; if(port_idx >= effect->desc->PortCount) { return value; } hint = &effect->desc->PortRangeHints[port_idx]; flags = hint->HintDescriptor; if(!LADSPA_IS_HINT_HAS_DEFAULT(flags)) { return value; } if(LADSPA_IS_HINT_DEFAULT_0(flags)) { value = 0.0; } else if(LADSPA_IS_HINT_DEFAULT_1(flags)) { value = 1.0; } else if(LADSPA_IS_HINT_DEFAULT_100(flags)) { value = 100.0; } else if(LADSPA_IS_HINT_DEFAULT_440(flags)) { value = 440.0; } /* defaults based on lower or upper bounds must consider HINT_SAMPLE_RATE */ else { if(LADSPA_IS_HINT_DEFAULT_MINIMUM(flags)) { low_factor = 1.0; high_factor = 0.0; } else if(LADSPA_IS_HINT_DEFAULT_LOW(flags)) { low_factor = 0.75; high_factor = 0.25; } else if(LADSPA_IS_HINT_DEFAULT_MIDDLE(flags)) { low_factor = 0.5; high_factor = 0.5; } else if(LADSPA_IS_HINT_DEFAULT_HIGH(flags)) { low_factor = 0.25; high_factor = 0.75; } else if(LADSPA_IS_HINT_DEFAULT_MAXIMUM(flags)) { low_factor = 0.0; high_factor = 1.0; } if(LADSPA_IS_HINT_LOGARITHMIC(flags) && low_factor > 0 && high_factor > 0) { value = exp(log(hint->LowerBound) * low_factor + log(hint->UpperBound) * high_factor); } else { value = (hint->LowerBound * low_factor + hint->UpperBound * high_factor); } if(LADSPA_IS_HINT_SAMPLE_RATE(flags)) { value *= sample_rate; } } if(LADSPA_IS_HINT_INTEGER(flags)) { /* LADSPA doesn't specify which rounding method to use, so lets keep it simple... */ value = floor(value + 0.5); } return value; } /** * Create a control node for each control port on the passed in effect. The value of the * node is taken from port hint defaults, if available. This gets run automatically after * an effect has been added. * * @param fx LADSPA fx instance * @param effect effect instance * @return FLUID_OK on success, otherwise FLUID_FAILED */ static int create_control_port_nodes(fluid_ladspa_fx_t *fx, fluid_ladspa_effect_t *effect) { unsigned int i; fluid_ladspa_node_t *node; fluid_ladspa_dir_t dir; int port_flags; for(i = 0; i < effect->desc->PortCount; i++) { port_flags = effect->desc->PortDescriptors[i]; if(!LADSPA_IS_PORT_CONTROL(port_flags)) { continue; } node = new_fluid_ladspa_node(fx, "", FLUID_LADSPA_NODE_EFFECT | FLUID_LADSPA_NODE_CONTROL, NULL); if(node == NULL) { return FLUID_FAILED; } node->effect_buffer[0] = get_default_port_value(effect, i, fx->sample_rate); dir = (LADSPA_IS_PORT_INPUT(port_flags)) ? FLUID_LADSPA_INPUT : FLUID_LADSPA_OUTPUT; connect_node_to_port(node, dir, effect, i); } return FLUID_OK; } static void connect_node_to_port(fluid_ladspa_node_t *node, fluid_ladspa_dir_t dir, fluid_ladspa_effect_t *effect, int port_idx) { effect->desc->connect_port(effect->handle, port_idx, node->effect_buffer); effect->port_nodes[port_idx] = node; /* Mark node as connected in the respective direction */ if(dir == FLUID_LADSPA_INPUT) { node->num_outputs++; } else { node->num_inputs++; } } /** * Check that all ports on the effect are connected to a node. * * @param effect LADSPA effect instance * @param name if check fails, points to the name of first failed port * @return FLUID_OK on successful check, otherwise FLUID_FAILED */ static int check_all_ports_connected(fluid_ladspa_effect_t *effect, const char **name) { unsigned int i; for(i = 0; i < effect->desc->PortCount; i++) { if(effect->port_nodes[i] == NULL) { *name = effect->desc->PortNames[i]; return FLUID_FAILED; } } return FLUID_OK; } /** * In-place broken plugins can't cope with input and output audio ports connected * to the same buffer. Check for this condition in the effect. * * @param effect effect instance * @param name1 if check fails, points to the first port name * @param name2 if check fails, points to the second port name * @return FLUID_OK on successful check, otherwise FLUID_FAILED */ static int check_no_inplace_broken(fluid_ladspa_effect_t *effect, const char **name1, const char **name2) { unsigned int i, k; LADSPA_PortDescriptor flags1, flags2; if(!LADSPA_IS_INPLACE_BROKEN(effect->desc->Properties)) { return FLUID_OK; } for(i = 0; i < effect->desc->PortCount; i++) { flags1 = effect->desc->PortDescriptors[i]; for(k = 0; k < effect->desc->PortCount; k++) { flags2 = effect->desc->PortDescriptors[k]; if(i != k && effect->port_nodes[i]->effect_buffer == effect->port_nodes[k]->effect_buffer && (flags1 & 0x3) != (flags2 & 0x3) /* first two bits encode direction */ && LADSPA_IS_PORT_AUDIO(flags1) && LADSPA_IS_PORT_AUDIO(flags2)) { *name1 = effect->desc->PortNames[i]; *name2 = effect->desc->PortNames[k]; return FLUID_FAILED; } } } return FLUID_OK; } /** * Check that at least one host node is used by an effect * * @param fx LADSPA fx instance * @return FLUID_OK on successful check, otherwise FLUID_FAILED */ static int check_host_output_used(fluid_ladspa_fx_t *fx) { int i; for(i = 0; i < fx->num_host_nodes; i++) { if(fx->host_nodes[i]->num_inputs) { return FLUID_OK; } } return FLUID_FAILED; } /** * Check that all user audio nodes have an input and an output * * @param fx LADSPA fx instance * @param name if check fails, points to the name of first failed node * @return FLUID_OK on successful check, otherwise FLUID_FAILED */ static int check_all_audio_nodes_connected(fluid_ladspa_fx_t *fx, const char **name) { int i; for(i = 0; i < fx->num_audio_nodes; i++) { if(fx->audio_nodes[i]->num_inputs == 0 || fx->audio_nodes[i]->num_outputs == 0) { *name = fx->audio_nodes[i]->name; return FLUID_FAILED; } } return FLUID_OK; } #ifndef WITH_FLOAT /** * Copy and type convert host buffers to effect buffers. Used only if host and LADSPA * use different float types. */ static FLUID_INLINE void copy_host_to_effect_buffers(fluid_ladspa_fx_t *fx, int num_samples) { int i, n; fluid_ladspa_node_t *node; for(n = 0; n < fx->num_host_nodes; n++) { node = fx->host_nodes[n]; /* Only copy host nodes that have at least one output or output, i.e. * that are connected to at least one effect port. */ if(node->num_inputs > 0 || node->num_outputs > 0) { for(i = 0; i < num_samples; i++) { node->effect_buffer[i] = (LADSPA_Data)node->host_buffer[i]; } } } } /** * Copy and type convert effect buffers to host buffers. Used only if host and LADSPA * use different float types. */ static FLUID_INLINE void copy_effect_to_host_buffers(fluid_ladspa_fx_t *fx, int num_samples) { int i, n; fluid_ladspa_node_t *node; for(n = 0; n < fx->num_host_nodes; n++) { node = fx->host_nodes[n]; /* Only copy effect nodes that have at least one input, i.e. that are connected to * at least one effect output */ if(node->num_inputs > 0) { for(i = 0; i < num_samples; i++) { node->host_buffer[i] = (fluid_real_t)node->effect_buffer[i]; } } } } #endif /* WITH_FLOAT */ #else /* ifdef LADSPA */ /* Dummy functions to use if LADSPA is not compiled in, to keep the * FluidSynth library ABI stable */ int fluid_ladspa_is_active(fluid_ladspa_fx_t *fx) { return FALSE; } int fluid_ladspa_activate(fluid_ladspa_fx_t *fx) { return FLUID_FAILED; } int fluid_ladspa_deactivate(fluid_ladspa_fx_t *fx) { return FLUID_FAILED; } int fluid_ladspa_reset(fluid_ladspa_fx_t *fx) { return FLUID_FAILED; } int fluid_ladspa_check(fluid_ladspa_fx_t *fx, char *err, int err_size) { return FLUID_FAILED; } int fluid_ladspa_host_port_exists(fluid_ladspa_fx_t *fx, const char *name) { return FALSE; } int fluid_ladspa_add_buffer(fluid_ladspa_fx_t *fx, const char *name) { return FLUID_FAILED; } int fluid_ladspa_buffer_exists(fluid_ladspa_fx_t *fx, const char *name) { return FALSE; } int fluid_ladspa_add_effect(fluid_ladspa_fx_t *fx, const char *effect_name, const char *lib_name, const char *plugin_name) { return FLUID_FAILED; } int fluid_ladspa_effect_can_mix(fluid_ladspa_fx_t *fx, const char *name) { return FALSE; } int fluid_ladspa_effect_set_mix(fluid_ladspa_fx_t *fx, const char *name, int mix, float gain) { return FLUID_FAILED; } int fluid_ladspa_effect_port_exists(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name) { return FALSE; } int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, float val) { return FLUID_FAILED; } int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, const char *name) { return FLUID_FAILED; } #endif /* ifdef LADSPA */ fluidsynth-2.1.1/src/bindings/fluid_ladspa.h000066400000000000000000000026021362231004000210570ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_LADSPA_H #define _FLUID_LADSPA_H #include "fluid_sys.h" fluid_ladspa_fx_t *new_fluid_ladspa_fx(fluid_real_t sample_rate, int buffer_size); void delete_fluid_ladspa_fx(fluid_ladspa_fx_t *fx); int fluid_ladspa_set_sample_rate(fluid_ladspa_fx_t *fx, fluid_real_t sample_rate); void fluid_ladspa_run(fluid_ladspa_fx_t *fx, int block_count, int block_size); int fluid_ladspa_add_host_ports(fluid_ladspa_fx_t *fx, const char *prefix, int num_buffers, fluid_real_t buffers[], int buf_stride); #endif /* _FLUID_LADSPA_H */ fluidsynth-2.1.1/src/bindings/fluid_lash.c000066400000000000000000000110051362231004000205320ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_lash.h" #ifdef HAVE_LASH static void fluid_lash_save(fluid_synth_t *synth); static void fluid_lash_load(fluid_synth_t *synth, const char *filename); static void *fluid_lash_run(void *data); /* * lash client - this symbol needs to be in the library else * all clients would need a fluid_lash_client symbol. */ lash_client_t *fluid_lash_client; static pthread_t fluid_lash_thread; fluid_lash_args_t * fluid_lash_extract_args(int *pargc, char ***pargv) { return lash_extract_args(pargc, pargv); } int fluid_lash_connect(fluid_lash_args_t *args) { fluid_lash_client = lash_init(args, PACKAGE, LASH_Config_Data_Set | LASH_Terminal, LASH_PROTOCOL(2, 0)); return fluid_lash_client && lash_enabled(fluid_lash_client); } void fluid_lash_create_thread(fluid_synth_t *synth) { pthread_create(&fluid_lash_thread, NULL, fluid_lash_run, synth); } static void fluid_lash_save(fluid_synth_t *synth) { int i; int sfcount; fluid_sfont_t *sfont; lash_config_t *config; char num[32]; sfcount = fluid_synth_sfcount(synth); config = lash_config_new(); lash_config_set_key(config, "soundfont count"); lash_config_set_value_int(config, sfcount); lash_send_config(fluid_lash_client, config); for(i = sfcount - 1; i >= 0; i--) { sfont = fluid_synth_get_sfont(synth, i); config = lash_config_new(); sprintf(num, "%d", i); lash_config_set_key(config, num); lash_config_set_value_string(config, sfont->get_name(sfont)); lash_send_config(fluid_lash_client, config); } } static void fluid_lash_load(fluid_synth_t *synth, const char *filename) { fluid_synth_sfload(synth, filename, 1); } static void * fluid_lash_run(void *data) { lash_event_t *event; lash_config_t *config; fluid_synth_t *synth; int done = 0; int err; int pending_restores = 0; synth = (fluid_synth_t *) data; while(!done) { while((event = lash_get_event(fluid_lash_client))) { switch(lash_event_get_type(event)) { case LASH_Save_Data_Set: fluid_lash_save(synth); lash_send_event(fluid_lash_client, event); break; case LASH_Restore_Data_Set: lash_event_destroy(event); break; case LASH_Quit: err = kill(getpid(), SIGQUIT); if(err) { fprintf(stderr, "%s: error sending signal: %s", __FUNCTION__, strerror(errno)); } lash_event_destroy(event); done = 1; break; case LASH_Server_Lost: lash_event_destroy(event); done = 1; break; default: fprintf(stderr, "Received unknown LASH event of type %d\n", lash_event_get_type(event)); lash_event_destroy(event); break; } } while((config = lash_get_config(fluid_lash_client))) { if(FLUID_STRCMP(lash_config_get_key(config), "soundfont count") == 0) { pending_restores = lash_config_get_value_int(config); } else { fluid_lash_load(synth, lash_config_get_value_string(config)); pending_restores--; } lash_config_destroy(config); if(!pending_restores) { event = lash_event_new_with_type(LASH_Restore_Data_Set); lash_send_event(fluid_lash_client, event); } } usleep(10000); } return NULL; } #endif /* #if HAVE_LASH #else */ fluidsynth-2.1.1/src/bindings/fluid_lash.h000066400000000000000000000026511362231004000205460ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_LASH_H #define _FLUID_LASH_H #include "config.h" #ifdef HAVE_LASH #include "fluid_synth.h" #include extern lash_client_t *fluid_lash_client; #define fluid_lash_args_t lash_args_t #define fluid_lash_alsa_client_id lash_alsa_client_id #define fluid_lash_jack_client_name lash_jack_client_name FLUIDSYNTH_API fluid_lash_args_t *fluid_lash_extract_args(int *pargc, char ***pargv); FLUIDSYNTH_API int fluid_lash_connect(fluid_lash_args_t *args); FLUIDSYNTH_API void fluid_lash_create_thread(fluid_synth_t *synth); #endif /* defined(HAVE_LASH) */ #endif /* _FLUID_LASH_H */ fluidsynth-2.1.1/src/bindings/fluid_rtkit.c000066400000000000000000000237061362231004000207530ustar00rootroot00000000000000/*-*- Mode: C; c-basic-offset: 8 -*-*/ /*** Copyright 2009 Lennart Poettering Copyright 2010 David Henningsson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ***/ #include "fluid_sys.h" #ifdef DBUS_SUPPORT #include "fluid_rtkit.h" #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__DragonFly__) #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #if defined(__FreeBSD__) || defined(__DragonFly__) #include #endif static pid_t _gettid(void) { #if defined(__FreeBSD__) || defined(__DragonFly__) return pthread_getthreadid_np(); #else return (pid_t) syscall(SYS_gettid); #endif } static int translate_error(const char *name) { if(FLUID_STRCMP(name, DBUS_ERROR_NO_MEMORY) == 0) { return -ENOMEM; } if(FLUID_STRCMP(name, DBUS_ERROR_SERVICE_UNKNOWN) == 0 || FLUID_STRCMP(name, DBUS_ERROR_NAME_HAS_NO_OWNER) == 0) { return -ENOENT; } if(FLUID_STRCMP(name, DBUS_ERROR_ACCESS_DENIED) == 0 || FLUID_STRCMP(name, DBUS_ERROR_AUTH_FAILED) == 0) { return -EACCES; } return -EIO; } static long long rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval) { DBusMessage *m = NULL, *r = NULL; DBusMessageIter iter, subiter; dbus_int64_t i64; dbus_int32_t i32; DBusError error; int current_type; long long ret; const char *interfacestr = "org.freedesktop.RealtimeKit1"; dbus_error_init(&error); if(!(m = dbus_message_new_method_call( RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.DBus.Properties", "Get"))) { ret = -ENOMEM; goto finish; } if(!dbus_message_append_args( m, DBUS_TYPE_STRING, &interfacestr, DBUS_TYPE_STRING, &propname, DBUS_TYPE_INVALID)) { ret = -ENOMEM; goto finish; } if(!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { ret = translate_error(error.name); goto finish; } if(dbus_set_error_from_message(&error, r)) { ret = translate_error(error.name); goto finish; } ret = -EBADMSG; dbus_message_iter_init(r, &iter); while((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) { if(current_type == DBUS_TYPE_VARIANT) { dbus_message_iter_recurse(&iter, &subiter); while((current_type = dbus_message_iter_get_arg_type(&subiter)) != DBUS_TYPE_INVALID) { if(current_type == DBUS_TYPE_INT32) { dbus_message_iter_get_basic(&subiter, &i32); *propval = i32; ret = 0; } if(current_type == DBUS_TYPE_INT64) { dbus_message_iter_get_basic(&subiter, &i64); *propval = i64; ret = 0; } dbus_message_iter_next(&subiter); } } dbus_message_iter_next(&iter); } finish: if(m) { dbus_message_unref(m); } if(r) { dbus_message_unref(r); } dbus_error_free(&error); return ret; } int rtkit_get_max_realtime_priority(DBusConnection *connection) { long long retval = 0; int err; err = rtkit_get_int_property(connection, "MaxRealtimePriority", &retval); return err < 0 ? err : retval; } int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) { long long retval = 0; int err; err = rtkit_get_int_property(connection, "MinNiceLevel", &retval); if(err >= 0) { *min_nice_level = retval; } return err; } long long rtkit_get_rttime_nsec_max(DBusConnection *connection) { long long retval = 0; int err; err = rtkit_get_int_property(connection, "RTTimeNSecMax", &retval); return err < 0 ? err : retval; } int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) { DBusMessage *m = NULL, *r = NULL; dbus_uint64_t u64; dbus_uint32_t u32; DBusError error; int ret; dbus_error_init(&error); if(thread == 0) { thread = _gettid(); } if(!(m = dbus_message_new_method_call( RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.RealtimeKit1", "MakeThreadRealtime"))) { ret = -ENOMEM; goto finish; } u64 = (dbus_uint64_t) thread; u32 = (dbus_uint32_t) priority; if(!dbus_message_append_args( m, DBUS_TYPE_UINT64, &u64, DBUS_TYPE_UINT32, &u32, DBUS_TYPE_INVALID)) { ret = -ENOMEM; goto finish; } if(!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { ret = translate_error(error.name); goto finish; } if(dbus_set_error_from_message(&error, r)) { ret = translate_error(error.name); goto finish; } ret = 0; finish: if(m) { dbus_message_unref(m); } if(r) { dbus_message_unref(r); } dbus_error_free(&error); return ret; } int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) { DBusMessage *m = NULL, *r = NULL; dbus_uint64_t u64; dbus_int32_t s32; DBusError error; int ret; dbus_error_init(&error); if(thread == 0) { thread = _gettid(); } if(!(m = dbus_message_new_method_call( RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, "org.freedesktop.RealtimeKit1", "MakeThreadHighPriority"))) { ret = -ENOMEM; goto finish; } u64 = (dbus_uint64_t) thread; s32 = (dbus_int32_t) nice_level; if(!dbus_message_append_args( m, DBUS_TYPE_UINT64, &u64, DBUS_TYPE_INT32, &s32, DBUS_TYPE_INVALID)) { ret = -ENOMEM; goto finish; } if(!(r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error))) { ret = translate_error(error.name); goto finish; } if(dbus_set_error_from_message(&error, r)) { ret = translate_error(error.name); goto finish; } ret = 0; finish: if(m) { dbus_message_unref(m); } if(r) { dbus_message_unref(r); } dbus_error_free(&error); return ret; } #ifndef RLIMIT_RTTIME # define RLIMIT_RTTIME 15 #endif #define MAKE_REALTIME_RETURN(_value) \ do { \ dbus_connection_close(conn); \ dbus_connection_unref(conn); \ return _value; \ } while (0) int fluid_rtkit_make_realtime(pid_t thread, int priority) { DBusConnection *conn = NULL; DBusError error; int max_prio, res; long long max_rttime; struct rlimit old_limit, new_limit; if(!dbus_threads_init_default()) { return -ENOMEM; } /* Initialize system bus connection */ dbus_error_init(&error); conn = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); if(conn == NULL) { res = translate_error(error.name); dbus_error_free(&error); return res; } dbus_error_free(&error); /* Make sure we don't fail by wanting too much */ max_prio = rtkit_get_max_realtime_priority(conn); if(max_prio < 0) { MAKE_REALTIME_RETURN(max_prio); } if(priority >= max_prio) { priority = max_prio; } /* Enforce RLIMIT_RTTIME, also a must for obtaining rt prio through rtkit */ max_rttime = rtkit_get_rttime_nsec_max(conn); if(max_rttime < 0) { MAKE_REALTIME_RETURN(max_rttime); } new_limit.rlim_cur = new_limit.rlim_max = max_rttime; if(getrlimit(RLIMIT_RTTIME, &old_limit) < 0) { MAKE_REALTIME_RETURN(-1); } if(setrlimit(RLIMIT_RTTIME, &new_limit) < 0) { MAKE_REALTIME_RETURN(-1); } /* Finally, let's try */ res = rtkit_make_realtime(conn, thread, priority); if(res != 0) { setrlimit(RLIMIT_RTTIME, &old_limit); } MAKE_REALTIME_RETURN(res); } #else int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) { return -ENOTSUP; } int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) { return -ENOTSUP; } int rtkit_get_max_realtime_priority(DBusConnection *connection) { return -ENOTSUP; } int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) { return -ENOTSUP; } long long rtkit_get_rttime_nsec_max(DBusConnection *connection) { return -ENOTSUP; } int fluid_rtkit_make_realtime(pid_t thread, int priority) { return -ENOTSUP; } #endif /* defined(__linux__) || defined(__APPLE__) */ #endif /* DBUS_SUPPORT */ fluidsynth-2.1.1/src/bindings/fluid_rtkit.h000066400000000000000000000040011362231004000207430ustar00rootroot00000000000000/*-*- Mode: C; c-basic-offset: 8 -*-*/ #ifndef foortkithfoo #define foortkithfoo /*** Copyright 2009 Lennart Poettering Copyright 2010 David Henningsson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ***/ #ifdef DBUS_SUPPORT #include #include #ifdef __cplusplus extern "C" { #endif /* This is the reference implementation for a client for * RealtimeKit. You don't have to use this, but if do, just copy these * sources into your repository */ #define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" #define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" /* This is mostly equivalent to sched_setparam(thread, SCHED_RR, { * .sched_priority = priority }). 'thread' needs to be a kernel thread * id as returned by gettid(), not a pthread_t! If 'thread' is 0 the * current thread is used. The returned value is a negative errno * style error code, or 0 on success. */ int fluid_rtkit_make_realtime(pid_t thread, int priority); #ifdef __cplusplus } #endif #endif #endif fluidsynth-2.1.1/src/config.cmake000066400000000000000000000201571362231004000167360ustar00rootroot00000000000000#ifndef CONFIG_H #define CONFIG_H /* Define to enable ALSA driver */ #cmakedefine ALSA_SUPPORT @ALSA_SUPPORT@ /* Define to activate sound output to files */ #cmakedefine AUFILE_SUPPORT @AUFILE_SUPPORT@ /* whether or not we are supporting CoreAudio */ #cmakedefine COREAUDIO_SUPPORT @COREAUDIO_SUPPORT@ /* whether or not we are supporting CoreMIDI */ #cmakedefine COREMIDI_SUPPORT @COREMIDI_SUPPORT@ /* whether or not we are supporting DART */ #cmakedefine DART_SUPPORT @DART_SUPPORT@ /* Define if building for Mac OS X Darwin */ #cmakedefine DARWIN @DARWIN@ /* Define if D-Bus support is enabled */ #cmakedefine DBUS_SUPPORT @DBUS_SUPPORT@ /* Soundfont to load automatically in some use cases */ #cmakedefine DEFAULT_SOUNDFONT "@DEFAULT_SOUNDFONT@" /* Define to enable FPE checks */ #cmakedefine FPE_CHECK @FPE_CHECK@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ARPA_INET_H @HAVE_ARPA_INET_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ERRNO_H @HAVE_ERRNO_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_FCNTL_H @HAVE_FCNTL_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_INTTYPES_H @HAVE_INTTYPES_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IO_H @HAVE_IO_H@ /* whether or not we are supporting lash */ #cmakedefine HAVE_LASH @HAVE_LASH@ /* Define if systemd support is enabled */ #cmakedefine SYSTEMD_SUPPORT @SYSTEMD_SUPPORT@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LIMITS_H @HAVE_LIMITS_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LINUX_SOUNDCARD_H @HAVE_LINUX_SOUNDCARD_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_MACHINE_SOUNDCARD_H @HAVE_MACHINE_SOUNDCARD_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_MATH_H @HAVE_MATH_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_IN_H @HAVE_NETINET_IN_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_TCP_H @HAVE_NETINET_TCP_H@ /* Define if compiling the mixer with multi-thread support */ #cmakedefine ENABLE_MIXER_THREADS @ENABLE_MIXER_THREADS@ /* Define if compiling with openMP to enable parallel audio rendering */ #cmakedefine HAVE_OPENMP @HAVE_OPENMP@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PTHREAD_H @HAVE_PTHREAD_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SIGNAL_H @HAVE_SIGNAL_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDARG_H @HAVE_STDARG_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDINT_H @HAVE_STDINT_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDIO_H @HAVE_STDIO_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDLIB_H @HAVE_STDLIB_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STRINGS_H @HAVE_STRINGS_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STRING_H @HAVE_STRING_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_MMAN_H @HAVE_SYS_MMAN_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_SOCKET_H @HAVE_SYS_SOCKET_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_SOUNDCARD_H @HAVE_SYS_SOUNDCARD_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_STAT_H @HAVE_SYS_STAT_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TIME_H @HAVE_SYS_TIME_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TYPES_H @HAVE_SYS_TYPES_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_UNISTD_H @HAVE_UNISTD_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_WINDOWS_H @HAVE_WINDOWS_H@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_GETOPT_H @HAVE_GETOPT_H@ /* Define to 1 if you have the inet_ntop() function. */ #cmakedefine HAVE_INETNTOP @HAVE_INETNTOP@ /* Define to enable JACK driver */ #cmakedefine JACK_SUPPORT @JACK_SUPPORT@ /* Include the LADSPA Fx unit */ #cmakedefine LADSPA @LADSPA_SUPPORT@ /* Define to enable IPV6 support */ #cmakedefine IPV6_SUPPORT @IPV6_SUPPORT@ /* Define to enable network support */ #cmakedefine NETWORK_SUPPORT @NETWORK_SUPPORT@ /* Defined when fluidsynth is build in an automated environment, where no MSVC++ Runtime Debug Assertion dialogs should pop up */ #cmakedefine NO_GUI @NO_GUI@ /* libinstpatch for DLS and GIG */ #cmakedefine LIBINSTPATCH_SUPPORT @LIBINSTPATCH_SUPPORT@ /* libsndfile has ogg vorbis support */ #cmakedefine LIBSNDFILE_HASVORBIS @LIBSNDFILE_HASVORBIS@ /* Define to enable libsndfile support */ #cmakedefine LIBSNDFILE_SUPPORT @LIBSNDFILE_SUPPORT@ /* Define to enable MidiShare driver */ #cmakedefine MIDISHARE_SUPPORT @MIDISHARE_SUPPORT@ /* Define if using the MinGW32 environment */ #cmakedefine MINGW32 @MINGW32@ /* Define to enable OSS driver */ #cmakedefine OSS_SUPPORT @OSS_SUPPORT@ /* Define to enable OPENSLES driver */ #cmakedefine OPENSLES_SUPPORT @OPENSLES_SUPPORT@ /* Define to enable Oboe driver */ #cmakedefine OBOE_SUPPORT @OBOE_SUPPORT@ /* Name of package */ #cmakedefine PACKAGE "@PACKAGE@" /* Define to the address where bug reports for this package should be sent. */ #cmakedefine PACKAGE_BUGREPORT @PACKAGE_BUGREPORT@ /* Define to the full name of this package. */ #cmakedefine PACKAGE_NAME @PACKAGE_NAME@ /* Define to the full name and version of this package. */ #cmakedefine PACKAGE_STRING @PACKAGE_STRING@ /* Define to the one symbol short name of this package. */ #cmakedefine PACKAGE_TARNAME @PACKAGE_TARNAME@ /* Define to the version of this package. */ #cmakedefine PACKAGE_VERSION @PACKAGE_VERSION@ /* Define to enable PortAudio driver */ #cmakedefine PORTAUDIO_SUPPORT @PORTAUDIO_SUPPORT@ /* Define to enable PulseAudio driver */ #cmakedefine PULSE_SUPPORT @PULSE_SUPPORT@ /* Define to enable DirectSound driver */ #cmakedefine DSOUND_SUPPORT @DSOUND_SUPPORT@ /* Define to enable Windows WaveOut driver */ #cmakedefine WAVEOUT_SUPPORT @WAVEOUT_SUPPORT@ /* Define to enable Windows MIDI driver */ #cmakedefine WINMIDI_SUPPORT @WINMIDI_SUPPORT@ /* Define to enable SDL2 audio driver */ #cmakedefine SDL2_SUPPORT @SDL2_SUPPORT@ /* Define to 1 if you have the ANSI C header files. */ #cmakedefine STDC_HEADERS @STDC_HEADERS@ /* Soundfont to load for unit testing */ #cmakedefine TEST_SOUNDFONT "@TEST_SOUNDFONT@" /* SF3 Soundfont to load for unit testing */ #cmakedefine TEST_SOUNDFONT_SF3 "@TEST_SOUNDFONT_SF3@" /* Define to enable SIGFPE assertions */ #cmakedefine TRAP_ON_FPE @TRAP_ON_FPE@ /* Define to do all DSP in single floating point precision */ #cmakedefine WITH_FLOAT @WITH_FLOAT@ /* Define to profile the DSP code */ #cmakedefine WITH_PROFILING @WITH_PROFILING@ /* Define to use the readline library for line editing */ #cmakedefine WITH_READLINE @WITH_READLINE@ /* Define if the compiler supports VLA */ #cmakedefine SUPPORTS_VLA @SUPPORTS_VLA@ /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ #cmakedefine WORDS_BIGENDIAN @WORDS_BIGENDIAN@ /* Define to `__inline__' or `__inline' if that's what the C compiler calls it, or to nothing if 'inline' is not supported under any name. */ #ifndef __cplusplus #cmakedefine inline @INLINE_KEYWORD@ #endif /* Define to 1 if you have the sinf() function. */ #cmakedefine HAVE_SINF @HAVE_SINF@ /* Define to 1 if you have the cosf() function. */ #cmakedefine HAVE_COSF @HAVE_COSF@ /* Define to 1 if you have the fabsf() function. */ #cmakedefine HAVE_FABSF @HAVE_FABSF@ /* Define to 1 if you have the powf() function. */ #cmakedefine HAVE_POWF @HAVE_POWF@ /* Define to 1 if you have the sqrtf() function. */ #cmakedefine HAVE_SQRTF @HAVE_SQRTF@ /* Define to 1 if you have the logf() function. */ #cmakedefine HAVE_LOGF @HAVE_LOGF@ #endif /* CONFIG_H */ fluidsynth-2.1.1/src/drivers/000077500000000000000000000000001362231004000161405ustar00rootroot00000000000000fluidsynth-2.1.1/src/drivers/fluid_adriver.c000066400000000000000000000330661362231004000211330ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_adriver.h" #include "fluid_sys.h" #include "fluid_settings.h" /* * fluid_adriver_definition_t */ struct _fluid_audriver_definition_t { const char *name; fluid_audio_driver_t *(*new)(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *(*new2)(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void (*free)(fluid_audio_driver_t *driver); void (*settings)(fluid_settings_t *settings); }; /* Available audio drivers, listed in order of preference */ static const fluid_audriver_definition_t fluid_audio_drivers[] = { #if JACK_SUPPORT { "jack", new_fluid_jack_audio_driver, new_fluid_jack_audio_driver2, delete_fluid_jack_audio_driver, fluid_jack_audio_driver_settings }, #endif #if ALSA_SUPPORT { "alsa", new_fluid_alsa_audio_driver, new_fluid_alsa_audio_driver2, delete_fluid_alsa_audio_driver, fluid_alsa_audio_driver_settings }, #endif #if PULSE_SUPPORT { "pulseaudio", new_fluid_pulse_audio_driver, new_fluid_pulse_audio_driver2, delete_fluid_pulse_audio_driver, fluid_pulse_audio_driver_settings }, #endif #if OSS_SUPPORT { "oss", new_fluid_oss_audio_driver, new_fluid_oss_audio_driver2, delete_fluid_oss_audio_driver, fluid_oss_audio_driver_settings }, #endif #if OBOE_SUPPORT { "oboe", new_fluid_oboe_audio_driver, NULL, delete_fluid_oboe_audio_driver, fluid_oboe_audio_driver_settings }, #endif #if OPENSLES_SUPPORT { "opensles", new_fluid_opensles_audio_driver, NULL, delete_fluid_opensles_audio_driver, fluid_opensles_audio_driver_settings }, #endif #if COREAUDIO_SUPPORT { "coreaudio", new_fluid_core_audio_driver, new_fluid_core_audio_driver2, delete_fluid_core_audio_driver, fluid_core_audio_driver_settings }, #endif #if DSOUND_SUPPORT { "dsound", new_fluid_dsound_audio_driver, NULL, delete_fluid_dsound_audio_driver, fluid_dsound_audio_driver_settings }, #endif #if WAVEOUT_SUPPORT { "waveout", new_fluid_waveout_audio_driver, NULL, delete_fluid_waveout_audio_driver, fluid_waveout_audio_driver_settings }, #endif #if SNDMAN_SUPPORT { "sndman", new_fluid_sndmgr_audio_driver, new_fluid_sndmgr_audio_driver2, delete_fluid_sndmgr_audio_driver, NULL }, #endif #if PORTAUDIO_SUPPORT { "portaudio", new_fluid_portaudio_driver, NULL, delete_fluid_portaudio_driver, fluid_portaudio_driver_settings }, #endif #if DART_SUPPORT { "dart", new_fluid_dart_audio_driver, NULL, delete_fluid_dart_audio_driver, fluid_dart_audio_driver_settings }, #endif #if SDL2_SUPPORT { "sdl2", new_fluid_sdl2_audio_driver, NULL, delete_fluid_sdl2_audio_driver, fluid_sdl2_audio_driver_settings }, #endif #if AUFILE_SUPPORT { "file", new_fluid_file_audio_driver, NULL, delete_fluid_file_audio_driver, NULL }, #endif /* NULL terminator to avoid zero size array if no driver available */ { NULL, NULL, NULL, NULL, NULL } }; #define ENABLE_AUDIO_DRIVER(_drv, _idx) \ _drv[(_idx) / (sizeof(*(_drv))*8)] &= ~(1 << ((_idx) % (sizeof((*_drv))*8))) #define IS_AUDIO_DRIVER_ENABLED(_drv, _idx) \ (!(_drv[(_idx) / (sizeof(*(_drv))*8)] & (1 << ((_idx) % (sizeof((*_drv))*8))))) static uint8_t fluid_adriver_disable_mask[(FLUID_N_ELEMENTS(fluid_audio_drivers) + 7) / 8] = {0}; void fluid_audio_driver_settings(fluid_settings_t *settings) { unsigned int i; const char *def_name = NULL; fluid_settings_register_str(settings, "audio.sample-format", "16bits", 0); fluid_settings_add_option(settings, "audio.sample-format", "16bits"); fluid_settings_add_option(settings, "audio.sample-format", "float"); #if defined(WIN32) fluid_settings_register_int(settings, "audio.period-size", 512, 64, 8192, 0); fluid_settings_register_int(settings, "audio.periods", 8, 2, 64, 0); #elif defined(MACOS9) fluid_settings_register_int(settings, "audio.period-size", 64, 64, 8192, 0); fluid_settings_register_int(settings, "audio.periods", 8, 2, 64, 0); #else fluid_settings_register_int(settings, "audio.period-size", 64, 64, 8192, 0); fluid_settings_register_int(settings, "audio.periods", 16, 2, 64, 0); #endif fluid_settings_register_int(settings, "audio.realtime-prio", FLUID_DEFAULT_AUDIO_RT_PRIO, 0, 99, 0); fluid_settings_register_str(settings, "audio.driver", "", 0); for(i = 0; i < FLUID_N_ELEMENTS(fluid_audio_drivers) - 1; i++) { /* Select the default driver */ if (def_name == NULL) { def_name = fluid_audio_drivers[i].name; } /* Add the driver to the list of options */ fluid_settings_add_option(settings, "audio.driver", fluid_audio_drivers[i].name); if(fluid_audio_drivers[i].settings != NULL && IS_AUDIO_DRIVER_ENABLED(fluid_adriver_disable_mask, i)) { fluid_audio_drivers[i].settings(settings); } } /* Set the default driver, if any */ if(def_name != NULL) { fluid_settings_setstr(settings, "audio.driver", def_name); } } static const fluid_audriver_definition_t * find_fluid_audio_driver(fluid_settings_t *settings) { unsigned int i; char *name; char *allnames; for(i = 0; i < FLUID_N_ELEMENTS(fluid_audio_drivers) - 1; i++) { /* If this driver is de-activated, just ignore it */ if(!IS_AUDIO_DRIVER_ENABLED(fluid_adriver_disable_mask, i)) { continue; } if(fluid_settings_str_equal(settings, "audio.driver", fluid_audio_drivers[i].name)) { FLUID_LOG(FLUID_DBG, "Using '%s' audio driver", fluid_audio_drivers[i].name); return &fluid_audio_drivers[i]; } } fluid_settings_dupstr(settings, "audio.driver", &name); /* ++ alloc name */ FLUID_LOG(FLUID_ERR, "Couldn't find the requested audio driver '%s'.", name ? name : "NULL"); allnames = fluid_settings_option_concat(settings, "audio.driver", NULL); if(allnames != NULL) { if(allnames[0] != '\0') { FLUID_LOG(FLUID_INFO, "Valid drivers are: %s", allnames); } else { FLUID_LOG(FLUID_INFO, "No audio drivers available."); } FLUID_FREE(allnames); } FLUID_FREE(name); return NULL; } /** * Create a new audio driver. * @param settings Configuration settings used to select and create the audio * driver. * @param synth Synthesizer instance for which the audio driver is created for. * @return The new audio driver instance. * * Creates a new audio driver for a given \p synth instance with a defined set * of configuration \p settings. The \p settings instance must be the same that * you have passed to new_fluid_synth() when creating the \p synth instance. * Otherwise the behaviour is undefined. * * @note As soon as an audio driver is created, the \p synth starts rendering audio. * This means that all necessary sound-setup should be completed after this point, * thus of all object types in use (synth, midi player, sequencer, etc.) the audio * driver should always be the last one to be created and the first one to be deleted! * Also refer to the order of object creation in the code examples. */ fluid_audio_driver_t * new_fluid_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { const fluid_audriver_definition_t *def = find_fluid_audio_driver(settings); if(def) { fluid_audio_driver_t *driver = (*def->new)(settings, synth); if(driver) { driver->define = def; } return driver; } return NULL; } /** * Create a new audio driver. * @param settings Configuration settings used to select and create the audio * driver. * @param func Function called to fill audio buffers for audio playback * @param data User defined data pointer to pass to 'func' * @return The new audio driver instance. * * Like new_fluid_audio_driver() but allows for custom audio processing before * audio is sent to audio driver. It is the responsibility of the callback * \p func to render the audio into the buffers. If \p func uses a fluid_synth_t \p synth, * the \p settings instance must be the same that you have passed to new_fluid_synth() * when creating the \p synth instance. Otherwise the behaviour is undefined. * * @note Not as efficient as new_fluid_audio_driver(). * * @note As soon as an audio driver is created, the \p synth starts rendering audio. * This means that all necessary sound-setup should be completed after this point, * thus of all object types in use (synth, midi player, sequencer, etc.) the audio * driver should always be the last one to be created and the first one to be deleted! * Also refer to the order of object creation in the code examples. */ fluid_audio_driver_t * new_fluid_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { const fluid_audriver_definition_t *def = find_fluid_audio_driver(settings); if(def) { fluid_audio_driver_t *driver = NULL; if(def->new2 == NULL) { FLUID_LOG(FLUID_DBG, "Callback mode unsupported on '%s' audio driver", def->name); } else { driver = (*def->new2)(settings, func, data); if(driver) { driver->define = def; } } return driver; } return NULL; } /** * Deletes an audio driver instance. * @param driver Audio driver instance to delete * * Shuts down an audio driver and deletes its instance. */ void delete_fluid_audio_driver(fluid_audio_driver_t *driver) { fluid_return_if_fail(driver != NULL); driver->define->free(driver); } /** * @brief Registers audio drivers to use * * When creating a settings instance with new_fluid_settings(), all audio drivers are initialized once. * In the past this has caused segfaults and application crashes due to buggy soundcard drivers. * * This function enables the user to only initialize specific audio drivers when settings instances are created. * Therefore pass a NULL-terminated array of C-strings containing the \c names of audio drivers to register * for the usage with fluidsynth. * The \c names are the same as being used for the \c audio.driver setting. * * By default all audio drivers fluidsynth has been compiled with are registered, so calling this function is optional. * * @warning This function may only be called if no thread is residing in fluidsynth's API and no instances of any kind * are alive (e.g. as it would be the case right after fluidsynth's initial creation). Else the behaviour is undefined. * Furtermore any attempt of using audio drivers that have not been registered is undefined behaviour! * * @param adrivers NULL-terminated array of audio drivers to register. Pass NULL to register all available drivers. * @return #FLUID_OK if all the audio drivers requested by the user are supported by fluidsynth and have been * successfully registered. Otherwise #FLUID_FAILED is returned and this function has no effect. * * @note This function is not thread safe and will never be! * @since 1.1.9 */ int fluid_audio_driver_register(const char **adrivers) { unsigned int i; uint8_t disable_mask[FLUID_N_ELEMENTS(fluid_adriver_disable_mask)]; if(adrivers == NULL) { /* Pass NULL to register all available drivers. */ FLUID_MEMSET(fluid_adriver_disable_mask, 0, sizeof(fluid_adriver_disable_mask)); return FLUID_OK; } FLUID_MEMSET(disable_mask, 0xFF, sizeof(disable_mask)); for(i = 0; adrivers[i] != NULL; i++) { unsigned int j; /* search the requested audio driver in the template and enable it if found */ for(j = 0; j < FLUID_N_ELEMENTS(fluid_audio_drivers) - 1; j++) { if(FLUID_STRCMP(adrivers[i], fluid_audio_drivers[j].name) == 0) { ENABLE_AUDIO_DRIVER(disable_mask, j); break; } } if(j >= FLUID_N_ELEMENTS(fluid_audio_drivers) - 1) { /* requested driver not found, failure */ return FLUID_FAILED; } } /* Update list of activated drivers */ FLUID_MEMCPY(fluid_adriver_disable_mask, disable_mask, sizeof(disable_mask)); return FLUID_OK; } fluidsynth-2.1.1/src/drivers/fluid_adriver.h000066400000000000000000000131531362231004000211330ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_AUDRIVER_H #define _FLUID_AUDRIVER_H #include "fluidsynth_priv.h" /* * fluid_audio_driver_t */ typedef struct _fluid_audriver_definition_t fluid_audriver_definition_t; struct _fluid_audio_driver_t { const fluid_audriver_definition_t *define; }; void fluid_audio_driver_settings(fluid_settings_t *settings); /* Defined in fluid_filerenderer.c */ void fluid_file_renderer_settings(fluid_settings_t *settings); #if PULSE_SUPPORT fluid_audio_driver_t *new_fluid_pulse_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *new_fluid_pulse_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void delete_fluid_pulse_audio_driver(fluid_audio_driver_t *p); void fluid_pulse_audio_driver_settings(fluid_settings_t *settings); #endif #if ALSA_SUPPORT fluid_audio_driver_t *new_fluid_alsa_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *new_fluid_alsa_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void delete_fluid_alsa_audio_driver(fluid_audio_driver_t *p); void fluid_alsa_audio_driver_settings(fluid_settings_t *settings); #endif #if OSS_SUPPORT fluid_audio_driver_t *new_fluid_oss_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *new_fluid_oss_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void delete_fluid_oss_audio_driver(fluid_audio_driver_t *p); void fluid_oss_audio_driver_settings(fluid_settings_t *settings); #endif #if OPENSLES_SUPPORT fluid_audio_driver_t* new_fluid_opensles_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth); void delete_fluid_opensles_audio_driver(fluid_audio_driver_t* p); void fluid_opensles_audio_driver_settings(fluid_settings_t* settings); #endif #if OBOE_SUPPORT fluid_audio_driver_t* new_fluid_oboe_audio_driver(fluid_settings_t* settings, fluid_synth_t* synth); void delete_fluid_oboe_audio_driver(fluid_audio_driver_t* p); void fluid_oboe_audio_driver_settings(fluid_settings_t* settings); #endif #if COREAUDIO_SUPPORT fluid_audio_driver_t *new_fluid_core_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void delete_fluid_core_audio_driver(fluid_audio_driver_t *p); void fluid_core_audio_driver_settings(fluid_settings_t *settings); #endif #if DSOUND_SUPPORT fluid_audio_driver_t *new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); void delete_fluid_dsound_audio_driver(fluid_audio_driver_t *p); void fluid_dsound_audio_driver_settings(fluid_settings_t *settings); #endif #if WAVEOUT_SUPPORT fluid_audio_driver_t *new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); void delete_fluid_waveout_audio_driver(fluid_audio_driver_t *p); void fluid_waveout_audio_driver_settings(fluid_settings_t *settings); #endif #if PORTAUDIO_SUPPORT void fluid_portaudio_driver_settings(fluid_settings_t *settings); fluid_audio_driver_t *new_fluid_portaudio_driver(fluid_settings_t *settings, fluid_synth_t *synth); void delete_fluid_portaudio_driver(fluid_audio_driver_t *p); #endif #if JACK_SUPPORT fluid_audio_driver_t *new_fluid_jack_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *new_fluid_jack_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void delete_fluid_jack_audio_driver(fluid_audio_driver_t *p); void fluid_jack_audio_driver_settings(fluid_settings_t *settings); int fluid_jack_obtain_synth(fluid_settings_t *settings, fluid_synth_t **synth); #endif #if SNDMAN_SUPPORT fluid_audio_driver_t *new_fluid_sndmgr_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); fluid_audio_driver_t *new_fluid_sndmgr_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data); void delete_fluid_sndmgr_audio_driver(fluid_audio_driver_t *p); #endif #if DART_SUPPORT fluid_audio_driver_t *new_fluid_dart_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); void delete_fluid_dart_audio_driver(fluid_audio_driver_t *p); void fluid_dart_audio_driver_settings(fluid_settings_t *settings); #endif #if SDL2_SUPPORT fluid_audio_driver_t *new_fluid_sdl2_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); void delete_fluid_sdl2_audio_driver(fluid_audio_driver_t *p); void fluid_sdl2_audio_driver_settings(fluid_settings_t *settings); #endif #if AUFILE_SUPPORT fluid_audio_driver_t *new_fluid_file_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); void delete_fluid_file_audio_driver(fluid_audio_driver_t *p); #endif #endif /* _FLUID_AUDRIVER_H */ fluidsynth-2.1.1/src/drivers/fluid_alsa.c000066400000000000000000001116351362231004000204160ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_alsa.c * * Driver for the Advanced Linux Sound Architecture * */ #include "fluid_synth.h" #include "fluid_midi.h" #include "fluid_adriver.h" #include "fluid_mdriver.h" #include "fluid_settings.h" #if ALSA_SUPPORT #define ALSA_PCM_NEW_HW_PARAMS_API #include #include #include "fluid_lash.h" #define FLUID_ALSA_DEFAULT_MIDI_DEVICE "default" #define FLUID_ALSA_DEFAULT_SEQ_DEVICE "default" #define BUFFER_LENGTH 512 /** fluid_alsa_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; snd_pcm_t *pcm; fluid_audio_func_t callback; void *data; int buffer_size; fluid_thread_t *thread; int cont; } fluid_alsa_audio_driver_t; static fluid_thread_return_t fluid_alsa_audio_run_float(void *d); static fluid_thread_return_t fluid_alsa_audio_run_s16(void *d); typedef struct { char *name; snd_pcm_format_t format; snd_pcm_access_t access; fluid_thread_func_t run; } fluid_alsa_formats_t; static const fluid_alsa_formats_t fluid_alsa_formats[] = { { "s16, rw, interleaved", SND_PCM_FORMAT_S16, SND_PCM_ACCESS_RW_INTERLEAVED, fluid_alsa_audio_run_s16 }, { "float, rw, non interleaved", SND_PCM_FORMAT_FLOAT, SND_PCM_ACCESS_RW_NONINTERLEAVED, fluid_alsa_audio_run_float }, { NULL, 0, 0, NULL } }; /* * fluid_alsa_rawmidi_driver_t * */ typedef struct { fluid_midi_driver_t driver; snd_rawmidi_t *rawmidi_in; struct pollfd *pfd; int npfd; fluid_thread_t *thread; fluid_atomic_int_t should_quit; unsigned char buffer[BUFFER_LENGTH]; fluid_midi_parser_t *parser; } fluid_alsa_rawmidi_driver_t; static fluid_thread_return_t fluid_alsa_midi_run(void *d); /* * fluid_alsa_seq_driver_t * */ typedef struct { fluid_midi_driver_t driver; snd_seq_t *seq_handle; struct pollfd *pfd; int npfd; fluid_thread_t *thread; fluid_atomic_int_t should_quit; int port_count; int autoconn_inputs; snd_seq_addr_t autoconn_dest; } fluid_alsa_seq_driver_t; static fluid_thread_return_t fluid_alsa_seq_run(void *d); /************************************************************** * * Alsa audio driver * */ void fluid_alsa_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "audio.alsa.device", "default", 0); } fluid_audio_driver_t * new_fluid_alsa_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { return new_fluid_alsa_audio_driver2(settings, NULL, synth); } fluid_audio_driver_t * new_fluid_alsa_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { fluid_alsa_audio_driver_t *dev; double sample_rate; int periods, period_size; char *device = NULL; int realtime_prio = 0; int i, err, dir = 0; snd_pcm_hw_params_t *hwparams; snd_pcm_sw_params_t *swparams = NULL; snd_pcm_uframes_t uframes; unsigned int tmp; dev = FLUID_NEW(fluid_alsa_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_alsa_audio_driver_t)); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_dupstr(settings, "audio.alsa.device", &device); /* ++ dup device name */ fluid_settings_getint(settings, "audio.realtime-prio", &realtime_prio); dev->data = data; dev->callback = func; dev->cont = 1; dev->buffer_size = period_size; /* Open the PCM device */ if((err = snd_pcm_open(&dev->pcm, device ? device : "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) != 0) { if(err == -EBUSY) { FLUID_LOG(FLUID_ERR, "The \"%s\" audio device is used by another application", device ? device : "default"); goto error_recovery; } else { FLUID_LOG(FLUID_ERR, "Failed to open the \"%s\" audio device", device ? device : "default"); goto error_recovery; } } snd_pcm_hw_params_alloca(&hwparams); snd_pcm_sw_params_alloca(&swparams); /* Set hardware parameters. We continue trying access methods and sample formats until we have one that works. For example, if memory mapped access fails we try regular IO methods. (not finished, yet). */ for(i = 0; fluid_alsa_formats[i].name != NULL; i++) { snd_pcm_hw_params_any(dev->pcm, hwparams); if(snd_pcm_hw_params_set_access(dev->pcm, hwparams, fluid_alsa_formats[i].access) < 0) { FLUID_LOG(FLUID_DBG, "snd_pcm_hw_params_set_access() failed with audio format '%s'", fluid_alsa_formats[i].name); continue; } if(snd_pcm_hw_params_set_format(dev->pcm, hwparams, fluid_alsa_formats[i].format) < 0) { FLUID_LOG(FLUID_DBG, "snd_pcm_hw_params_set_format() failed with audio format '%s'", fluid_alsa_formats[i].name); continue; } if((err = snd_pcm_hw_params_set_channels(dev->pcm, hwparams, 2)) < 0) { FLUID_LOG(FLUID_ERR, "Failed to set the channels: %s", snd_strerror(err)); goto error_recovery; } tmp = (unsigned int) sample_rate; if((err = snd_pcm_hw_params_set_rate_near(dev->pcm, hwparams, &tmp, NULL)) < 0) { FLUID_LOG(FLUID_ERR, "Failed to set the sample rate: %s", snd_strerror(err)); goto error_recovery; } if(tmp != sample_rate) { /* There's currently no way to change the sampling rate of the synthesizer after it's been created. */ FLUID_LOG(FLUID_WARN, "Requested sample rate of %u, got %u instead, " "synthesizer likely out of tune!", (unsigned int) sample_rate, tmp); } uframes = period_size; if(snd_pcm_hw_params_set_period_size_near(dev->pcm, hwparams, &uframes, &dir) < 0) { FLUID_LOG(FLUID_ERR, "Failed to set the period size"); goto error_recovery; } if(uframes != (unsigned long) period_size) { FLUID_LOG(FLUID_WARN, "Requested a period size of %d, got %d instead", period_size, (int) uframes); dev->buffer_size = (int) uframes; period_size = uframes; /* period size is used below, so set it to the real value */ } tmp = periods; if(snd_pcm_hw_params_set_periods_near(dev->pcm, hwparams, &tmp, &dir) < 0) { FLUID_LOG(FLUID_ERR, "Failed to set the number of periods"); goto error_recovery; } if(tmp != (unsigned int) periods) { FLUID_LOG(FLUID_WARN, "Requested %d periods, got %d instead", periods, (int) tmp); } if(snd_pcm_hw_params(dev->pcm, hwparams) < 0) { FLUID_LOG(FLUID_WARN, "Audio device hardware configuration failed"); continue; } break; } if(fluid_alsa_formats[i].name == NULL) { FLUID_LOG(FLUID_ERR, "Failed to find an audio format supported by alsa"); goto error_recovery; } /* Set the software params */ snd_pcm_sw_params_current(dev->pcm, swparams); if(snd_pcm_sw_params_set_start_threshold(dev->pcm, swparams, period_size) != 0) { FLUID_LOG(FLUID_ERR, "Failed to set start threshold."); } if(snd_pcm_sw_params_set_avail_min(dev->pcm, swparams, period_size) != 0) { FLUID_LOG(FLUID_ERR, "Software setup for minimum available frames failed."); } if(snd_pcm_sw_params(dev->pcm, swparams) != 0) { FLUID_LOG(FLUID_ERR, "Software setup failed."); } if(snd_pcm_nonblock(dev->pcm, 0) != 0) { FLUID_LOG(FLUID_ERR, "Failed to set the audio device to blocking mode"); goto error_recovery; } /* Create the audio thread */ dev->thread = new_fluid_thread("alsa-audio", fluid_alsa_formats[i].run, dev, realtime_prio, FALSE); if(!dev->thread) { FLUID_LOG(FLUID_ERR, "Failed to start the alsa-audio thread."); goto error_recovery; } if(device) { FLUID_FREE(device); /* -- free device name */ } return (fluid_audio_driver_t *) dev; error_recovery: if(device) { FLUID_FREE(device); /* -- free device name */ } delete_fluid_alsa_audio_driver((fluid_audio_driver_t *) dev); return NULL; } void delete_fluid_alsa_audio_driver(fluid_audio_driver_t *p) { fluid_alsa_audio_driver_t *dev = (fluid_alsa_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); dev->cont = 0; if(dev->thread) { fluid_thread_join(dev->thread); delete_fluid_thread(dev->thread); } if(dev->pcm) { snd_pcm_close(dev->pcm); } FLUID_FREE(dev); } /* handle error after an ALSA write call */ static int fluid_alsa_handle_write_error(snd_pcm_t *pcm, int errval) { switch(errval) { case -EAGAIN: snd_pcm_wait(pcm, 1); break; // on some BSD variants ESTRPIPE is defined as EPIPE. // not sure why, maybe because this version of alsa doesn't support // suspending pcm streams. anyway, since EPIPE seems to be more // likely than ESTRPIPE, so ifdef it out in case. #if ESTRPIPE == EPIPE #warning "ESTRPIPE defined as EPIPE. This may cause trouble with ALSA playback." #else case -ESTRPIPE: if(snd_pcm_resume(pcm) != 0) { FLUID_LOG(FLUID_ERR, "Failed to resume the audio device"); return FLUID_FAILED; } #endif /* fall through ... */ /* ... since the stream got resumed, but still has to be prepared */ case -EPIPE: case -EBADFD: if(snd_pcm_prepare(pcm) != 0) { FLUID_LOG(FLUID_ERR, "Failed to prepare the audio device"); return FLUID_FAILED; } break; default: FLUID_LOG(FLUID_ERR, "The audio device error: %s", snd_strerror(errval)); return FLUID_FAILED; } return FLUID_OK; } static fluid_thread_return_t fluid_alsa_audio_run_float(void *d) { fluid_alsa_audio_driver_t *dev = (fluid_alsa_audio_driver_t *) d; fluid_synth_t *synth = (fluid_synth_t *)(dev->data); float *left; float *right; float *handle[2]; int n, buffer_size, offset; buffer_size = dev->buffer_size; left = FLUID_ARRAY(float, buffer_size); right = FLUID_ARRAY(float, buffer_size); if((left == NULL) || (right == NULL)) { FLUID_LOG(FLUID_ERR, "Out of memory."); goto error_recovery; } if(snd_pcm_prepare(dev->pcm) != 0) { FLUID_LOG(FLUID_ERR, "Failed to prepare the audio device"); goto error_recovery; } /* use separate loops depending on if callback supplied or not (overkill?) */ if(dev->callback) { while(dev->cont) { FLUID_MEMSET(left, 0, buffer_size * sizeof(*left)); FLUID_MEMSET(right, 0, buffer_size * sizeof(*right)); handle[0] = left; handle[1] = right; (*dev->callback)(synth, buffer_size, 0, NULL, 2, handle); offset = 0; while(offset < buffer_size) { handle[0] = left + offset; handle[1] = right + offset; n = snd_pcm_writen(dev->pcm, (void *)handle, buffer_size - offset); if(n < 0) /* error occurred? */ { if(fluid_alsa_handle_write_error(dev->pcm, n) != FLUID_OK) { goto error_recovery; } } else { offset += n; /* no error occurred */ } } /* while (offset < buffer_size) */ } /* while (dev->cont) */ } else /* no user audio callback (faster) */ { while(dev->cont) { fluid_synth_write_float(dev->data, buffer_size, left, 0, 1, right, 0, 1); offset = 0; while(offset < buffer_size) { handle[0] = left + offset; handle[1] = right + offset; n = snd_pcm_writen(dev->pcm, (void *)handle, buffer_size - offset); if(n < 0) /* error occurred? */ { if(fluid_alsa_handle_write_error(dev->pcm, n) != FLUID_OK) { goto error_recovery; } } else { offset += n; /* no error occurred */ } } /* while (offset < buffer_size) */ } /* while (dev->cont) */ } error_recovery: FLUID_FREE(left); FLUID_FREE(right); return FLUID_THREAD_RETURN_VALUE; } static fluid_thread_return_t fluid_alsa_audio_run_s16(void *d) { fluid_alsa_audio_driver_t *dev = (fluid_alsa_audio_driver_t *) d; float *left; float *right; short *buf; float *handle[2]; int n, buffer_size, offset; buffer_size = dev->buffer_size; left = FLUID_ARRAY(float, buffer_size); right = FLUID_ARRAY(float, buffer_size); buf = FLUID_ARRAY(short, 2 * buffer_size); if((left == NULL) || (right == NULL) || (buf == NULL)) { FLUID_LOG(FLUID_ERR, "Out of memory."); goto error_recovery; } handle[0] = left; handle[1] = right; if(snd_pcm_prepare(dev->pcm) != 0) { FLUID_LOG(FLUID_ERR, "Failed to prepare the audio device"); goto error_recovery; } /* use separate loops depending on if callback supplied or not */ if(dev->callback) { int dither_index = 0; while(dev->cont) { FLUID_MEMSET(left, 0, buffer_size * sizeof(*left)); FLUID_MEMSET(right, 0, buffer_size * sizeof(*right)); (*dev->callback)(dev->data, buffer_size, 0, NULL, 2, handle); /* convert floating point data to 16 bit (with dithering) */ fluid_synth_dither_s16(&dither_index, buffer_size, left, right, buf, 0, 2, buf, 1, 2); offset = 0; while(offset < buffer_size) { n = snd_pcm_writei(dev->pcm, (void *)(buf + 2 * offset), buffer_size - offset); if(n < 0) /* error occurred? */ { if(fluid_alsa_handle_write_error(dev->pcm, n) != FLUID_OK) { goto error_recovery; } } else { offset += n; /* no error occurred */ } } /* while (offset < buffer_size) */ } /* while (dev->cont) */ } else /* no user audio callback, dev->data is the synth instance */ { fluid_synth_t *synth = (fluid_synth_t *)(dev->data); while(dev->cont) { fluid_synth_write_s16(synth, buffer_size, buf, 0, 2, buf, 1, 2); offset = 0; while(offset < buffer_size) { n = snd_pcm_writei(dev->pcm, (void *)(buf + 2 * offset), buffer_size - offset); if(n < 0) /* error occurred? */ { if(fluid_alsa_handle_write_error(dev->pcm, n) != FLUID_OK) { goto error_recovery; } } else { offset += n; /* no error occurred */ } } /* while (offset < buffer_size) */ } /* while (dev->cont) */ } error_recovery: FLUID_FREE(left); FLUID_FREE(right); FLUID_FREE(buf); return FLUID_THREAD_RETURN_VALUE; } /************************************************************** * * Alsa MIDI driver * */ void fluid_alsa_rawmidi_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "midi.alsa.device", "default", 0); } /* * new_fluid_alsa_rawmidi_driver */ fluid_midi_driver_t * new_fluid_alsa_rawmidi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { int i, err; fluid_alsa_rawmidi_driver_t *dev; int realtime_prio = 0; int count; struct pollfd *pfd = NULL; char *device = NULL; /* not much use doing anything */ if(handler == NULL) { FLUID_LOG(FLUID_ERR, "Invalid argument"); return NULL; } /* allocate the device */ dev = FLUID_NEW(fluid_alsa_rawmidi_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_alsa_rawmidi_driver_t)); dev->driver.handler = handler; dev->driver.data = data; /* allocate one event to store the input data */ dev->parser = new_fluid_midi_parser(); if(dev->parser == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } fluid_settings_getint(settings, "midi.realtime-prio", &realtime_prio); /* get the device name. if none is specified, use the default device. */ fluid_settings_dupstr(settings, "midi.alsa.device", &device); /* ++ alloc device name */ /* open the hardware device. only use midi in. */ if((err = snd_rawmidi_open(&dev->rawmidi_in, NULL, device ? device : "default", SND_RAWMIDI_NONBLOCK)) < 0) { FLUID_LOG(FLUID_ERR, "Error opening ALSA raw MIDI port"); goto error_recovery; } snd_rawmidi_nonblock(dev->rawmidi_in, 1); /* get # of MIDI file descriptors */ count = snd_rawmidi_poll_descriptors_count(dev->rawmidi_in); if(count > 0) /* make sure there are some */ { pfd = FLUID_MALLOC(sizeof(struct pollfd) * count); dev->pfd = FLUID_MALLOC(sizeof(struct pollfd) * count); /* grab file descriptor POLL info structures */ count = snd_rawmidi_poll_descriptors(dev->rawmidi_in, pfd, count); } /* copy the input FDs */ for(i = 0; i < count; i++) /* loop over file descriptors */ { if(pfd[i].events & POLLIN) /* use only the input FDs */ { dev->pfd[dev->npfd].fd = pfd[i].fd; dev->pfd[dev->npfd].events = POLLIN; dev->pfd[dev->npfd].revents = 0; dev->npfd++; } } FLUID_FREE(pfd); fluid_atomic_int_set(&dev->should_quit, 0); /* create the MIDI thread */ dev->thread = new_fluid_thread("alsa-midi-raw", fluid_alsa_midi_run, dev, realtime_prio, FALSE); if(!dev->thread) { goto error_recovery; } if(device) { FLUID_FREE(device); /* -- free device name */ } return (fluid_midi_driver_t *) dev; error_recovery: if(device) { FLUID_FREE(device); /* -- free device name */ } delete_fluid_alsa_rawmidi_driver((fluid_midi_driver_t *) dev); return NULL; } /* * delete_fluid_alsa_rawmidi_driver */ void delete_fluid_alsa_rawmidi_driver(fluid_midi_driver_t *p) { fluid_alsa_rawmidi_driver_t *dev = (fluid_alsa_rawmidi_driver_t *) p; fluid_return_if_fail(dev != NULL); /* cancel the thread and wait for it before cleaning up */ fluid_atomic_int_set(&dev->should_quit, 1); if(dev->thread) { fluid_thread_join(dev->thread); delete_fluid_thread(dev->thread); } if(dev->rawmidi_in) { snd_rawmidi_close(dev->rawmidi_in); } if(dev->parser != NULL) { delete_fluid_midi_parser(dev->parser); } FLUID_FREE(dev); } /* * fluid_alsa_midi_run */ fluid_thread_return_t fluid_alsa_midi_run(void *d) { fluid_midi_event_t *evt; fluid_alsa_rawmidi_driver_t *dev = (fluid_alsa_rawmidi_driver_t *) d; int n, i; /* go into a loop until someone tells us to stop */ while(!fluid_atomic_int_get(&dev->should_quit)) { /* is there something to read? */ n = poll(dev->pfd, dev->npfd, 100); /* use a 100 milliseconds timeout */ if(n < 0) { perror("poll"); } else if(n > 0) { /* read new data */ n = snd_rawmidi_read(dev->rawmidi_in, dev->buffer, BUFFER_LENGTH); if((n < 0) && (n != -EAGAIN)) { FLUID_LOG(FLUID_ERR, "Failed to read the midi input"); fluid_atomic_int_set(&dev->should_quit, 1); } /* let the parser convert the data into events */ for(i = 0; i < n; i++) { evt = fluid_midi_parser_parse(dev->parser, dev->buffer[i]); if(evt != NULL) { (*dev->driver.handler)(dev->driver.data, evt); } } } } return FLUID_THREAD_RETURN_VALUE; } /************************************************************** * * Alsa sequencer * */ void fluid_alsa_seq_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "midi.alsa_seq.device", "default", 0); fluid_settings_register_str(settings, "midi.alsa_seq.id", "pid", 0); } static char *fluid_alsa_seq_full_id(char *id, char *buf, int len) { if(id != NULL) { if(FLUID_STRCMP(id, "pid") == 0) { FLUID_SNPRINTF(buf, len, "FLUID Synth (%d)", getpid()); } else { FLUID_SNPRINTF(buf, len, "FLUID Synth (%s)", id); } } else { FLUID_SNPRINTF(buf, len, "FLUID Synth"); } return buf; } static char *fluid_alsa_seq_full_name(char *id, int port, char *buf, int len) { if(id != NULL) { if(FLUID_STRCMP(id, "pid") == 0) { FLUID_SNPRINTF(buf, len, "Synth input port (%d:%d)", getpid(), port); } else { FLUID_SNPRINTF(buf, len, "Synth input port (%s:%d)", id, port); } } else { FLUID_SNPRINTF(buf, len, "Synth input port"); } return buf; } // Connect a single port_info to autoconnect_dest if it has right type/capabilities static void fluid_alsa_seq_autoconnect_port_info(fluid_alsa_seq_driver_t *dev, snd_seq_port_info_t *pinfo) { snd_seq_port_subscribe_t *subs; snd_seq_t *seq = dev->seq_handle; const unsigned int needed_type = SND_SEQ_PORT_TYPE_MIDI_GENERIC; const unsigned int needed_cap = SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ; const snd_seq_addr_t *sender = snd_seq_port_info_get_addr(pinfo); const char *pname = snd_seq_port_info_get_name(pinfo); if((snd_seq_port_info_get_type(pinfo) & needed_type) != needed_type) { return; } if((snd_seq_port_info_get_capability(pinfo) & needed_cap) != needed_cap) { return; } snd_seq_port_subscribe_alloca(&subs); snd_seq_port_subscribe_set_sender(subs, sender); snd_seq_port_subscribe_set_dest(subs, &dev->autoconn_dest); if(snd_seq_get_port_subscription(seq, subs) == 0) { FLUID_LOG(FLUID_WARN, "Connection %s is already subscribed", pname); return; } if(snd_seq_subscribe_port(seq, subs) < 0) { FLUID_LOG(FLUID_ERR, "Connection of %s failed (%s)", pname, snd_strerror(errno)); return; } FLUID_LOG(FLUID_INFO, "Connection of %s succeeded", pname); } // Autoconnect a single client port (by id) to autoconnect_dest if it has right type/capabilities static void fluid_alsa_seq_autoconnect_port(fluid_alsa_seq_driver_t *dev, int client_id, int port_id) { int err; snd_seq_t *seq = dev->seq_handle; snd_seq_port_info_t *pinfo; snd_seq_port_info_alloca(&pinfo); if((err = snd_seq_get_any_port_info(seq, client_id, port_id, pinfo)) < 0) { FLUID_LOG(FLUID_ERR, "snd_seq_get_any_port_info() failed: %s", snd_strerror(err)); return; } fluid_alsa_seq_autoconnect_port_info(dev, pinfo); } // Connect available ALSA MIDI inputs to the provided port_info static void fluid_alsa_seq_autoconnect(fluid_alsa_seq_driver_t *dev, const snd_seq_port_info_t *dest_pinfo) { int err; snd_seq_t *seq = dev->seq_handle; snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; // subscribe to future new clients/ports showing up if((err = snd_seq_connect_from(seq, snd_seq_port_info_get_port(dest_pinfo), SND_SEQ_CLIENT_SYSTEM, SND_SEQ_PORT_SYSTEM_ANNOUNCE)) < 0) { FLUID_LOG(FLUID_ERR, "snd_seq_connect_from() failed: %s", snd_strerror(err)); } snd_seq_client_info_alloca(&cinfo); snd_seq_port_info_alloca(&pinfo); dev->autoconn_dest = *snd_seq_port_info_get_addr(dest_pinfo); snd_seq_client_info_set_client(cinfo, -1); while(snd_seq_query_next_client(seq, cinfo) >= 0) { snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); snd_seq_port_info_set_port(pinfo, -1); while(snd_seq_query_next_port(seq, pinfo) >= 0) { fluid_alsa_seq_autoconnect_port_info(dev, pinfo); } } } /* * new_fluid_alsa_seq_driver */ fluid_midi_driver_t * new_fluid_alsa_seq_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { int i, err; fluid_alsa_seq_driver_t *dev; int realtime_prio = 0; int count; struct pollfd *pfd = NULL; char *device = NULL; char *id = NULL; char *portname = NULL; char full_id[64]; char full_name[64]; snd_seq_port_info_t *port_info = NULL; int midi_channels; /* not much use doing anything */ if(handler == NULL) { FLUID_LOG(FLUID_ERR, "Invalid argument"); return NULL; } /* allocate the device */ dev = FLUID_NEW(fluid_alsa_seq_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_alsa_seq_driver_t)); dev->driver.data = data; dev->driver.handler = handler; fluid_settings_getint(settings, "midi.realtime-prio", &realtime_prio); /* get the device name. if none is specified, use the default device. */ if(fluid_settings_dupstr(settings, "midi.alsa_seq.device", &device) != FLUID_OK) /* ++ alloc device name */ { goto error_recovery; } if(fluid_settings_dupstr(settings, "midi.alsa_seq.id", &id) != FLUID_OK) /* ++ alloc id string */ { goto error_recovery; } if(id == NULL) { id = FLUID_MALLOC(32); if(!id) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } sprintf(id, "%d", getpid()); } /* get the midi portname */ fluid_settings_dupstr(settings, "midi.portname", &portname); if(portname && FLUID_STRLEN(portname) == 0) { FLUID_FREE(portname); /* -- free port name */ portname = NULL; } /* open the sequencer INPUT only */ err = snd_seq_open(&dev->seq_handle, device ? device : "default", SND_SEQ_OPEN_INPUT, 0); if(err < 0) { FLUID_LOG(FLUID_ERR, "Error opening ALSA sequencer"); goto error_recovery; } snd_seq_nonblock(dev->seq_handle, 1); /* get # of MIDI file descriptors */ count = snd_seq_poll_descriptors_count(dev->seq_handle, POLLIN); if(count > 0) /* make sure there are some */ { pfd = FLUID_MALLOC(sizeof(struct pollfd) * count); dev->pfd = FLUID_MALLOC(sizeof(struct pollfd) * count); /* grab file descriptor POLL info structures */ count = snd_seq_poll_descriptors(dev->seq_handle, pfd, count, POLLIN); } /* copy the input FDs */ for(i = 0; i < count; i++) /* loop over file descriptors */ { if(pfd[i].events & POLLIN) /* use only the input FDs */ { dev->pfd[dev->npfd].fd = pfd[i].fd; dev->pfd[dev->npfd].events = POLLIN; dev->pfd[dev->npfd].revents = 0; dev->npfd++; } } FLUID_FREE(pfd); /* set the client name */ if(!portname) { snd_seq_set_client_name(dev->seq_handle, fluid_alsa_seq_full_id(id, full_id, 64)); } else { snd_seq_set_client_name(dev->seq_handle, portname); } /* create the ports */ snd_seq_port_info_alloca(&port_info); FLUID_MEMSET(port_info, 0, snd_seq_port_info_sizeof()); fluid_settings_getint(settings, "synth.midi-channels", &midi_channels); dev->port_count = midi_channels / 16; snd_seq_port_info_set_capability(port_info, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE); snd_seq_port_info_set_type(port_info, SND_SEQ_PORT_TYPE_MIDI_GM | SND_SEQ_PORT_TYPE_SYNTHESIZER | SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC); snd_seq_port_info_set_midi_channels(port_info, 16); snd_seq_port_info_set_port_specified(port_info, 1); for(i = 0; i < dev->port_count; i++) { if(!portname) { snd_seq_port_info_set_name(port_info, fluid_alsa_seq_full_name(id, i, full_name, 64)); } else { snd_seq_port_info_set_name(port_info, portname); } snd_seq_port_info_set_port(port_info, i); err = snd_seq_create_port(dev->seq_handle, port_info); if(err < 0) { FLUID_LOG(FLUID_ERR, "Error creating ALSA sequencer port"); goto error_recovery; } } fluid_settings_getint(settings, "midi.autoconnect", &dev->autoconn_inputs); if(dev->autoconn_inputs) { fluid_alsa_seq_autoconnect(dev, port_info); } /* tell the lash server our client id */ #ifdef HAVE_LASH { int enable_lash = 0; fluid_settings_getint(settings, "lash.enable", &enable_lash); if(enable_lash) { fluid_lash_alsa_client_id(fluid_lash_client, snd_seq_client_id(dev->seq_handle)); } } #endif /* HAVE_LASH */ fluid_atomic_int_set(&dev->should_quit, 0); /* create the MIDI thread */ dev->thread = new_fluid_thread("alsa-midi-seq", fluid_alsa_seq_run, dev, realtime_prio, FALSE); if(portname) { FLUID_FREE(portname); } if(id) { FLUID_FREE(id); } if(device) { FLUID_FREE(device); } return (fluid_midi_driver_t *) dev; error_recovery: if(portname) { FLUID_FREE(portname); } if(id) { FLUID_FREE(id); } if(device) { FLUID_FREE(device); } delete_fluid_alsa_seq_driver((fluid_midi_driver_t *) dev); return NULL; } /* * delete_fluid_alsa_seq_driver */ void delete_fluid_alsa_seq_driver(fluid_midi_driver_t *p) { fluid_alsa_seq_driver_t *dev = (fluid_alsa_seq_driver_t *) p; fluid_return_if_fail(dev != NULL); /* cancel the thread and wait for it before cleaning up */ fluid_atomic_int_set(&dev->should_quit, 1); if(dev->thread) { fluid_thread_join(dev->thread); delete_fluid_thread(dev->thread); } if(dev->seq_handle) { snd_seq_close(dev->seq_handle); } if(dev->pfd) { FLUID_FREE(dev->pfd); } FLUID_FREE(dev); } /* * fluid_alsa_seq_run */ fluid_thread_return_t fluid_alsa_seq_run(void *d) { int n, ev; snd_seq_event_t *seq_ev; fluid_midi_event_t evt; fluid_alsa_seq_driver_t *dev = (fluid_alsa_seq_driver_t *) d; /* go into a loop until someone tells us to stop */ while(!fluid_atomic_int_get(&dev->should_quit)) { /* is there something to read? */ n = poll(dev->pfd, dev->npfd, 100); /* use a 100 milliseconds timeout */ if(n < 0) { perror("poll"); } else if(n > 0) /* check for pending events */ { do { ev = snd_seq_event_input(dev->seq_handle, &seq_ev); /* read the events */ if(ev == -EAGAIN) { break; } /* Negative value indicates an error, ignore interrupted system call * (-EPERM) and input event buffer overrun (-ENOSPC) */ if(ev < 0) { /* FIXME - report buffer overrun? */ if(ev != -EPERM && ev != -ENOSPC) { FLUID_LOG(FLUID_ERR, "Error while reading ALSA sequencer (code=%d)", ev); fluid_atomic_int_set(&dev->should_quit, 1); } break; } switch(seq_ev->type) { case SND_SEQ_EVENT_NOTEON: evt.type = NOTE_ON; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.note.channel; evt.param1 = seq_ev->data.note.note; evt.param2 = seq_ev->data.note.velocity; break; case SND_SEQ_EVENT_NOTEOFF: evt.type = NOTE_OFF; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.note.channel; evt.param1 = seq_ev->data.note.note; evt.param2 = seq_ev->data.note.velocity; break; case SND_SEQ_EVENT_KEYPRESS: evt.type = KEY_PRESSURE; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.note.channel; evt.param1 = seq_ev->data.note.note; evt.param2 = seq_ev->data.note.velocity; break; case SND_SEQ_EVENT_CONTROLLER: evt.type = CONTROL_CHANGE; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel; evt.param1 = seq_ev->data.control.param; evt.param2 = seq_ev->data.control.value; break; case SND_SEQ_EVENT_PITCHBEND: evt.type = PITCH_BEND; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel; /* ALSA pitch bend is -8192 - 8191, we adjust it here */ evt.param1 = seq_ev->data.control.value + 8192; break; case SND_SEQ_EVENT_PGMCHANGE: evt.type = PROGRAM_CHANGE; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel; evt.param1 = seq_ev->data.control.value; break; case SND_SEQ_EVENT_CHANPRESS: evt.type = CHANNEL_PRESSURE; evt.channel = seq_ev->dest.port * 16 + seq_ev->data.control.channel; evt.param1 = seq_ev->data.control.value; break; case SND_SEQ_EVENT_SYSEX: if(seq_ev->data.ext.len < 2) { continue; } fluid_midi_event_set_sysex(&evt, (char *)(seq_ev->data.ext.ptr) + 1, seq_ev->data.ext.len - 2, FALSE); break; case SND_SEQ_EVENT_PORT_START: { if(dev->autoconn_inputs) { fluid_alsa_seq_autoconnect_port(dev, seq_ev->data.addr.client, seq_ev->data.addr.port); } } break; default: continue; /* unhandled event, next loop iteration */ } /* send the events to the next link in the chain */ (*dev->driver.handler)(dev->driver.data, &evt); } while(ev > 0); } /* if poll() > 0 */ } /* while (!dev->should_quit) */ return FLUID_THREAD_RETURN_VALUE; } #endif /* #if ALSA_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_aufile.c000066400000000000000000000067641362231004000207510ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_aufile.c * * Audio driver, outputs the audio to a file (non real-time) * */ #include "fluid_sys.h" #include "fluid_adriver.h" #include "fluid_settings.h" #if AUFILE_SUPPORT /** fluid_file_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; fluid_audio_func_t callback; void *data; fluid_file_renderer_t *renderer; int period_size; double sample_rate; fluid_timer_t *timer; unsigned int samples; } fluid_file_audio_driver_t; static int fluid_file_audio_run_s16(void *d, unsigned int msec); /************************************************************** * * 'file' audio driver * */ fluid_audio_driver_t * new_fluid_file_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_file_audio_driver_t *dev; int msec; dev = FLUID_NEW(fluid_file_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_file_audio_driver_t)); fluid_settings_getint(settings, "audio.period-size", &dev->period_size); fluid_settings_getnum(settings, "synth.sample-rate", &dev->sample_rate); dev->data = synth; dev->callback = (fluid_audio_func_t) fluid_synth_process; dev->samples = 0; dev->renderer = new_fluid_file_renderer(synth); if(dev->renderer == NULL) { goto error_recovery; } msec = (int)(0.5 + dev->period_size / dev->sample_rate * 1000.0); dev->timer = new_fluid_timer(msec, fluid_file_audio_run_s16, (void *) dev, TRUE, FALSE, TRUE); if(dev->timer == NULL) { FLUID_LOG(FLUID_PANIC, "Couldn't create the audio thread."); goto error_recovery; } return (fluid_audio_driver_t *) dev; error_recovery: delete_fluid_file_audio_driver((fluid_audio_driver_t *) dev); return NULL; } void delete_fluid_file_audio_driver(fluid_audio_driver_t *p) { fluid_file_audio_driver_t *dev = (fluid_file_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); delete_fluid_timer(dev->timer); delete_fluid_file_renderer(dev->renderer); FLUID_FREE(dev); } static int fluid_file_audio_run_s16(void *d, unsigned int clock_time) { fluid_file_audio_driver_t *dev = (fluid_file_audio_driver_t *) d; unsigned int sample_time; sample_time = (unsigned int)(dev->samples / dev->sample_rate * 1000.0); if(sample_time > clock_time) { return 1; } dev->samples += dev->period_size; return fluid_file_renderer_process_block(dev->renderer) == FLUID_OK ? 1 : 0; } #endif /* AUFILE_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_coreaudio.c000066400000000000000000000327431362231004000214520ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_coreaudio.c * * Driver for the Apple's CoreAudio on MacOS X * */ #include "fluid_adriver.h" #include "fluid_settings.h" /* * !!! Make sure that no include above includes !!! * It #defines some macros that collide with enum definitions of OpenTransportProviders.h, which is included from OSServices.h, included from CoreServices.h * * https://trac.macports.org/ticket/36962 */ #if COREAUDIO_SUPPORT #include #include #include #include /* * fluid_core_audio_driver_t * */ typedef struct { fluid_audio_driver_t driver; AudioUnit outputUnit; AudioStreamBasicDescription format; fluid_audio_func_t callback; void *data; unsigned int buffer_size; float *buffers[2]; double phase; } fluid_core_audio_driver_t; OSStatus fluid_core_audio_callback(void *data, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData); /************************************************************** * * CoreAudio audio driver * */ #define OK(x) (x == noErr) int get_num_outputs(AudioDeviceID deviceID) { int i, total = 0; UInt32 size; AudioObjectPropertyAddress pa; pa.mSelector = kAudioDevicePropertyStreamConfiguration; pa.mScope = kAudioDevicePropertyScopeOutput; pa.mElement = kAudioObjectPropertyElementMaster; if(OK(AudioObjectGetPropertyDataSize(deviceID, &pa, 0, 0, &size)) && size > 0) { AudioBufferList *bufList = FLUID_MALLOC(size); if(bufList == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return 0; } if(OK(AudioObjectGetPropertyData(deviceID, &pa, 0, 0, &size, bufList))) { int numStreams = bufList->mNumberBuffers; for(i = 0; i < numStreams; ++i) { AudioBuffer b = bufList->mBuffers[i]; total += b.mNumberChannels; } } FLUID_FREE(bufList); } return total; } void fluid_core_audio_driver_settings(fluid_settings_t *settings) { int i; UInt32 size; AudioObjectPropertyAddress pa; pa.mSelector = kAudioHardwarePropertyDevices; pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mElement = kAudioObjectPropertyElementMaster; fluid_settings_register_str(settings, "audio.coreaudio.device", "default", 0); fluid_settings_add_option(settings, "audio.coreaudio.device", "default"); if(OK(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa, 0, 0, &size))) { int num = size / (int) sizeof(AudioDeviceID); AudioDeviceID devs [num]; if(OK(AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, 0, &size, devs))) { for(i = 0; i < num; ++i) { char name [1024]; size = sizeof(name); pa.mSelector = kAudioDevicePropertyDeviceName; if(OK(AudioObjectGetPropertyData(devs[i], &pa, 0, 0, &size, name))) { if(get_num_outputs(devs[i]) > 0) { fluid_settings_add_option(settings, "audio.coreaudio.device", name); } } } } } } /* * new_fluid_core_audio_driver */ fluid_audio_driver_t * new_fluid_core_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { return new_fluid_core_audio_driver2(settings, NULL, synth); } /* * new_fluid_core_audio_driver2 */ fluid_audio_driver_t * new_fluid_core_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { char *devname = NULL; fluid_core_audio_driver_t *dev = NULL; int period_size, periods; double sample_rate; OSStatus status; UInt32 size; int i; dev = FLUID_NEW(fluid_core_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_core_audio_driver_t)); dev->callback = func; dev->data = data; // Open the default output unit ComponentDescription desc; desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_HALOutput; //kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; Component comp = FindNextComponent(NULL, &desc); if(comp == NULL) { FLUID_LOG(FLUID_ERR, "Failed to get the default audio device"); goto error_recovery; } status = OpenAComponent(comp, &dev->outputUnit); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Failed to open the default audio device. Status=%ld\n", (long int)status); goto error_recovery; } // Set up a callback function to generate output AURenderCallbackStruct render; render.inputProc = fluid_core_audio_callback; render.inputProcRefCon = (void *) dev; status = AudioUnitSetProperty(dev->outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &render, sizeof(render)); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Error setting the audio callback. Status=%ld\n", (long int)status); goto error_recovery; } fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); /* get the selected device name. if none is specified, use NULL for the default device. */ if(fluid_settings_dupstr(settings, "audio.coreaudio.device", &devname) == FLUID_OK /* alloc device name */ && devname && strlen(devname) > 0) { AudioObjectPropertyAddress pa; pa.mSelector = kAudioHardwarePropertyDevices; pa.mScope = kAudioObjectPropertyScopeWildcard; pa.mElement = kAudioObjectPropertyElementMaster; if(OK(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &pa, 0, 0, &size))) { int num = size / (int) sizeof(AudioDeviceID); AudioDeviceID devs [num]; if(OK(AudioObjectGetPropertyData(kAudioObjectSystemObject, &pa, 0, 0, &size, devs))) { for(i = 0; i < num; ++i) { char name [1024]; size = sizeof(name); pa.mSelector = kAudioDevicePropertyDeviceName; if(OK(AudioObjectGetPropertyData(devs[i], &pa, 0, 0, &size, name))) { if(get_num_outputs(devs[i]) > 0 && FLUID_STRCASECMP(devname, name) == 0) { AudioDeviceID selectedID = devs[i]; status = AudioUnitSetProperty(dev->outputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &selectedID, sizeof(AudioDeviceID)); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Error setting the selected output device. Status=%ld\n", (long int)status); goto error_recovery; } } } } } } } FLUID_FREE(devname); /* free device name */ dev->buffer_size = period_size * periods; // The DefaultOutputUnit should do any format conversions // necessary from our format to the device's format. dev->format.mSampleRate = sample_rate; // sample rate of the audio stream dev->format.mFormatID = kAudioFormatLinearPCM; // encoding type of the audio stream dev->format.mFormatFlags = kLinearPCMFormatFlagIsFloat; dev->format.mBytesPerPacket = 2 * sizeof(float); dev->format.mFramesPerPacket = 1; dev->format.mBytesPerFrame = 2 * sizeof(float); dev->format.mChannelsPerFrame = 2; dev->format.mBitsPerChannel = 8 * sizeof(float); FLUID_LOG(FLUID_DBG, "mSampleRate %g", dev->format.mSampleRate); FLUID_LOG(FLUID_DBG, "mFormatFlags %08X", dev->format.mFormatFlags); FLUID_LOG(FLUID_DBG, "mBytesPerPacket %d", dev->format.mBytesPerPacket); FLUID_LOG(FLUID_DBG, "mFramesPerPacket %d", dev->format.mFramesPerPacket); FLUID_LOG(FLUID_DBG, "mChannelsPerFrame %d", dev->format.mChannelsPerFrame); FLUID_LOG(FLUID_DBG, "mBytesPerFrame %d", dev->format.mBytesPerFrame); FLUID_LOG(FLUID_DBG, "mBitsPerChannel %d", dev->format.mBitsPerChannel); status = AudioUnitSetProperty(dev->outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &dev->format, sizeof(AudioStreamBasicDescription)); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Error setting the audio format. Status=%ld\n", (long int)status); goto error_recovery; } status = AudioUnitSetProperty(dev->outputUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Input, 0, &dev->buffer_size, sizeof(unsigned int)); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Failed to set the MaximumFramesPerSlice. Status=%ld\n", (long int)status); goto error_recovery; } FLUID_LOG(FLUID_DBG, "MaximumFramesPerSlice = %d", dev->buffer_size); dev->buffers[0] = FLUID_ARRAY(float, dev->buffer_size); dev->buffers[1] = FLUID_ARRAY(float, dev->buffer_size); if(dev->buffers[0] == NULL || dev->buffers[1] == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory."); goto error_recovery; } // Initialize the audio unit status = AudioUnitInitialize(dev->outputUnit); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Error calling AudioUnitInitialize(). Status=%ld\n", (long int)status); goto error_recovery; } // Start the rendering status = AudioOutputUnitStart(dev->outputUnit); if(status != noErr) { FLUID_LOG(FLUID_ERR, "Error calling AudioOutputUnitStart(). Status=%ld\n", (long int)status); goto error_recovery; } return (fluid_audio_driver_t *) dev; error_recovery: delete_fluid_core_audio_driver((fluid_audio_driver_t *) dev); return NULL; } /* * delete_fluid_core_audio_driver */ void delete_fluid_core_audio_driver(fluid_audio_driver_t *p) { fluid_core_audio_driver_t *dev = (fluid_core_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); CloseComponent(dev->outputUnit); if(dev->buffers[0]) { FLUID_FREE(dev->buffers[0]); } if(dev->buffers[1]) { FLUID_FREE(dev->buffers[1]); } FLUID_FREE(dev); } OSStatus fluid_core_audio_callback(void *data, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { int i, k; fluid_core_audio_driver_t *dev = (fluid_core_audio_driver_t *) data; int len = inNumberFrames; float *buffer = ioData->mBuffers[0].mData; if(dev->callback) { float *left = dev->buffers[0]; float *right = dev->buffers[1]; FLUID_MEMSET(left, 0, len * sizeof(float)); FLUID_MEMSET(right, 0, len * sizeof(float)); (*dev->callback)(dev->data, len, 0, NULL, 2, dev->buffers); for(i = 0, k = 0; i < len; i++) { buffer[k++] = left[i]; buffer[k++] = right[i]; } } else fluid_synth_write_float((fluid_synth_t *) dev->data, len, buffer, 0, 2, buffer, 1, 2); return noErr; } #endif /* #if COREAUDIO_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_coremidi.c000066400000000000000000000164001362231004000212630ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_coremidi.c * * Driver for Mac OSX native MIDI * Pedro Lopez-Cabanillas, Jan 2009 */ #include "fluidsynth_priv.h" #if COREMIDI_SUPPORT #include "fluid_midi.h" #include "fluid_mdriver.h" #include "fluid_settings.h" /* Work around for OSX 10.4 */ /* enum definition in OpenTransportProviders.h defines these tokens which are #defined from */ #ifdef TCP_NODELAY #undef TCP_NODELAY #endif #ifdef TCP_MAXSEG #undef TCP_MAXSEG #endif #ifdef TCP_KEEPALIVE #undef TCP_KEEPALIVE #endif /* End work around */ #include #include #include typedef struct { fluid_midi_driver_t driver; MIDIClientRef client; MIDIEndpointRef endpoint; MIDIPortRef input_port; fluid_midi_parser_t *parser; int autoconn_inputs; } fluid_coremidi_driver_t; void fluid_coremidi_callback(const MIDIPacketList *list, void *p, void *src); void fluid_coremidi_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "midi.coremidi.id", "pid", 0); } static void fluid_coremidi_autoconnect(fluid_coremidi_driver_t *dev, MIDIPortRef input_port) { int i; int source_count = MIDIGetNumberOfSources(); for(i = 0; i < source_count; ++i) { MIDIEndpointRef source = MIDIGetSource(i); CFStringRef externalName; OSStatus result = MIDIObjectGetStringProperty(source, kMIDIPropertyName, &externalName); const char *source_name = CFStringGetCStringPtr(externalName, kCFStringEncodingASCII); CFRelease(externalName); result = MIDIPortConnectSource(input_port, source, NULL); if(result != noErr) { FLUID_LOG(FLUID_ERR, "Failed to connect \"%s\" device to input port.", source_name); } else { FLUID_LOG(FLUID_DBG, "Connected input port to \"%s\".", source_name); } } } /* * new_fluid_coremidi_driver */ fluid_midi_driver_t * new_fluid_coremidi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { fluid_coremidi_driver_t *dev; MIDIClientRef client; MIDIEndpointRef endpoint; char clientid[32]; char *portname; char *id; CFStringRef str_portname; CFStringRef str_clientname; /* not much use doing anything */ if(handler == NULL) { FLUID_LOG(FLUID_ERR, "Invalid argument"); return NULL; } dev = FLUID_MALLOC(sizeof(fluid_coremidi_driver_t)); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } dev->client = 0; dev->endpoint = 0; dev->parser = 0; dev->driver.handler = handler; dev->driver.data = data; dev->parser = new_fluid_midi_parser(); if(dev->parser == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } fluid_settings_dupstr(settings, "midi.coremidi.id", &id); /* ++ alloc id string */ memset(clientid, 0, sizeof(clientid)); if(id != NULL) { if(FLUID_STRCMP(id, "pid") == 0) { FLUID_SNPRINTF(clientid, sizeof(clientid), " (%d)", getpid()); } else { FLUID_SNPRINTF(clientid, sizeof(clientid), " (%s)", id); } FLUID_FREE(id); /* -- free id string */ } str_clientname = CFStringCreateWithFormat(NULL, NULL, CFSTR("FluidSynth%s"), clientid); fluid_settings_dupstr(settings, "midi.portname", &portname); /* ++ alloc port name */ if(!portname || strlen(portname) == 0) str_portname = CFStringCreateWithFormat(NULL, NULL, CFSTR("FluidSynth virtual port%s"), clientid); else str_portname = CFStringCreateWithCString(NULL, portname, kCFStringEncodingASCII); if(portname) { FLUID_FREE(portname); /* -- free port name */ } OSStatus result = MIDIClientCreate(str_clientname, NULL, NULL, &client); CFRelease(str_clientname); if(result != noErr) { FLUID_LOG(FLUID_ERR, "Failed to create the MIDI input client"); goto error_recovery; } dev->client = client; result = MIDIDestinationCreate(client, str_portname, fluid_coremidi_callback, (void *)dev, &endpoint); if(result != noErr) { FLUID_LOG(FLUID_ERR, "Failed to create the MIDI input port. MIDI input not available."); goto error_recovery; } CFStringRef str_input_portname = CFSTR("input"); result = MIDIInputPortCreate(client, str_input_portname, fluid_coremidi_callback, (void *)dev, &dev->input_port); CFRelease(str_input_portname); if(result != noErr) { FLUID_LOG(FLUID_ERR, "Failed to create input port."); goto error_recovery; } fluid_settings_getint(settings, "midi.autoconnect", &dev->autoconn_inputs); if(dev->autoconn_inputs) { fluid_coremidi_autoconnect(dev, dev->input_port); } dev->endpoint = endpoint; return (fluid_midi_driver_t *) dev; error_recovery: delete_fluid_coremidi_driver((fluid_midi_driver_t *) dev); return NULL; } /* * delete_fluid_coremidi_driver */ void delete_fluid_coremidi_driver(fluid_midi_driver_t *p) { fluid_coremidi_driver_t *dev = (fluid_coremidi_driver_t *) p; fluid_return_if_fail(dev != NULL); if(dev->input_port != NULL) { MIDIPortDispose(dev->input_port); } if(dev->client != NULL) { MIDIClientDispose(dev->client); } if(dev->endpoint != NULL) { MIDIEndpointDispose(dev->endpoint); } if(dev->parser != NULL) { delete_fluid_midi_parser(dev->parser); } FLUID_FREE(dev); } void fluid_coremidi_callback(const MIDIPacketList *list, void *p, void *src) { unsigned int i, j; fluid_midi_event_t *event; fluid_coremidi_driver_t *dev = (fluid_coremidi_driver_t *)p; const MIDIPacket *packet = &list->packet[0]; for(i = 0; i < list->numPackets; ++i) { for(j = 0; j < packet->length; ++j) { event = fluid_midi_parser_parse(dev->parser, packet->data[j]); if(event != NULL) { (*dev->driver.handler)(dev->driver.data, event); } } packet = MIDIPacketNext(packet); } } #endif /* COREMIDI_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_dart.c000066400000000000000000000202161362231004000204220ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_dart.c * * Driver for OS/2 DART * */ #include "fluid_adriver.h" #include "fluid_settings.h" #include "fluid_sys.h" #if DART_SUPPORT #define INCL_DOS #include #define INCL_OS2MM #include #define NUM_MIX_BUFS 2 /** fluid_dart_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; fluid_synth_t *synth; int frame_size; USHORT usDeviceID; /* Amp Mixer device id */ MCI_MIX_BUFFER MixBuffers[NUM_MIX_BUFS]; /* Device buffers */ MCI_MIXSETUP_PARMS MixSetupParms; /* Mixer parameters */ MCI_BUFFER_PARMS BufferParms; /* Device buffer parms */ } fluid_dart_audio_driver_t; static HMODULE m_hmodMDM = NULLHANDLE; static ULONG(APIENTRY *m_pfnmciSendCommand)(USHORT, USHORT, ULONG, PVOID, USHORT) = NULL; static LONG APIENTRY fluid_dart_audio_run(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags); /************************************************************** * * DART audio driver * */ void fluid_dart_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "audio.dart.device", "default", 0); } fluid_audio_driver_t * new_fluid_dart_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_dart_audio_driver_t *dev; double sample_rate; int periods, period_size; UCHAR szFailedName[ 256 ]; MCI_AMP_OPEN_PARMS AmpOpenParms; int i; ULONG rc; dev = FLUID_NEW(fluid_dart_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_dart_audio_driver_t)); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); /* check the format */ if(!fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { FLUID_LOG(FLUID_ERR, "Unhandled sample format"); goto error_recovery; } dev->synth = synth; dev->frame_size = 2 * sizeof(short); /* Load only once */ if(m_hmodMDM == NULLHANDLE) { rc = DosLoadModule(szFailedName, sizeof(szFailedName), "MDM", &m_hmodMDM); if(rc != 0) { FLUID_LOG(FLUID_ERR, "Cannot load MDM.DLL for DART due to %s", szFailedName); goto error_recovery; } rc = DosQueryProcAddr(m_hmodMDM, 1, NULL, (PFN *)&m_pfnmciSendCommand); if(rc != 0) { FLUID_LOG(FLUID_ERR, "Cannot find mciSendCommand() in MDM.DLL"); DosFreeModule(m_hmodMDM); m_hmodMDM = NULLHANDLE; goto error_recovery; } } /* open the mixer device */ FLUID_MEMSET(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS)); AmpOpenParms.usDeviceID = (USHORT)0; AmpOpenParms.pszDeviceType = (PSZ)MCI_DEVTYPE_AUDIO_AMPMIX; rc = m_pfnmciSendCommand(0, MCI_OPEN, MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE, (PVOID)&AmpOpenParms, 0); if(rc != MCIERR_SUCCESS) { FLUID_LOG(FLUID_ERR, "Cannot open DART, rc = %lu", rc); goto error_recovery; } dev->usDeviceID = AmpOpenParms.usDeviceID; /* Set the MixSetupParms data structure to match the requirements. * This is a global that is used to setup the mixer. */ dev->MixSetupParms.ulBitsPerSample = BPS_16; dev->MixSetupParms.ulFormatTag = MCI_WAVE_FORMAT_PCM; dev->MixSetupParms.ulSamplesPerSec = sample_rate; dev->MixSetupParms.ulChannels = 2; /* Setup the mixer for playback of wave data */ dev->MixSetupParms.ulFormatMode = MCI_PLAY; dev->MixSetupParms.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; dev->MixSetupParms.pmixEvent = fluid_dart_audio_run; rc = m_pfnmciSendCommand(dev->usDeviceID, MCI_MIXSETUP, MCI_WAIT | MCI_MIXSETUP_INIT, (PVOID)&dev->MixSetupParms, 0); if(rc != MCIERR_SUCCESS) { FLUID_LOG(FLUID_ERR, "Cannot setup DART, rc = %lu", rc); goto error_recovery; } /* Set up the BufferParms data structure and allocate * device buffers from the Amp-Mixer */ dev->BufferParms.ulNumBuffers = NUM_MIX_BUFS; dev->BufferParms.ulBufferSize = periods * period_size * dev->frame_size; dev->BufferParms.pBufList = dev->MixBuffers; rc = m_pfnmciSendCommand(dev->usDeviceID, MCI_BUFFER, MCI_WAIT | MCI_ALLOCATE_MEMORY, (PVOID)&dev->BufferParms, 0); if((USHORT)rc != MCIERR_SUCCESS) { FLUID_LOG(FLUID_ERR, "Cannot allocate memory for DART, rc = %lu", rc); goto error_recovery; } /* Initialize all device buffers. */ for(i = 0; i < NUM_MIX_BUFS; i++) { FLUID_MEMSET(dev->MixBuffers[i].pBuffer, 0, dev->BufferParms.ulBufferSize); dev->MixBuffers[i].ulBufferLength = dev->BufferParms.ulBufferSize; dev->MixBuffers[i].ulFlags = 0; dev->MixBuffers[i].ulUserParm = (ULONG)dev; fluid_synth_write_s16(dev->synth, dev->MixBuffers[i].ulBufferLength / dev->frame_size, dev->MixBuffers[i].pBuffer, 0, 2, dev->MixBuffers[i].pBuffer, 1, 2); } /* Write buffers to kick off the amp mixer. */ dev->MixSetupParms.pmixWrite(dev->MixSetupParms.ulMixHandle, dev->MixBuffers, NUM_MIX_BUFS); return (fluid_audio_driver_t *) dev; error_recovery: delete_fluid_dart_audio_driver((fluid_audio_driver_t *) dev); return NULL; } void delete_fluid_dart_audio_driver(fluid_audio_driver_t *p) { fluid_dart_audio_driver_t *dev = (fluid_dart_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); if(dev->usDeviceID != 0) { MCI_GENERIC_PARMS GenericParms; /* Send message to stop the audio device */ m_pfnmciSendCommand(dev->usDeviceID, MCI_STOP, MCI_WAIT, (PVOID)&GenericParms, 0); /* Deallocate device buffers */ m_pfnmciSendCommand(dev->usDeviceID, MCI_BUFFER, MCI_WAIT | MCI_DEALLOCATE_MEMORY, (PVOID)&dev->BufferParms, 0); /* Close device the mixer device */ m_pfnmciSendCommand(dev->usDeviceID, MCI_CLOSE, MCI_WAIT, (PVOID)&GenericParms, 0); } FLUID_FREE(dev); } static LONG APIENTRY fluid_dart_audio_run(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, ULONG ulFlags) { fluid_dart_audio_driver_t *dev = (fluid_dart_audio_driver_t *)pBuffer->ulUserParm; switch(ulFlags) { case MIX_STREAM_ERROR | MIX_WRITE_COMPLETE: /* error occur in device */ case MIX_WRITE_COMPLETE: /* for playback */ FLUID_MEMSET(pBuffer->pBuffer, 0, pBuffer->ulBufferLength); fluid_synth_write_s16(dev->synth, pBuffer->ulBufferLength / dev->frame_size, pBuffer->pBuffer, 0, 2, pBuffer->pBuffer, 1, 2); dev->MixSetupParms.pmixWrite(dev->MixSetupParms.ulMixHandle, pBuffer, 1); break; } return TRUE; } #endif /* #if DART_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_dsound.c000066400000000000000000000322671362231004000207750ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_synth.h" #include "fluid_adriver.h" #include "fluid_settings.h" #if DSOUND_SUPPORT #define INITGUID #include #include #define NOBITMAP #include static DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter); static char *fluid_win32_error(HRESULT hr); typedef struct { fluid_audio_driver_t driver; LPDIRECTSOUND direct_sound; LPDIRECTSOUNDBUFFER prim_buffer; LPDIRECTSOUNDBUFFER sec_buffer; HANDLE thread; DWORD threadID; fluid_synth_t *synth; fluid_audio_callback_t write; HANDLE quit_ev; int bytes_per_second; DWORD buffer_byte_size; DWORD queue_byte_size; DWORD frame_size; } fluid_dsound_audio_driver_t; typedef struct { LPGUID devGUID; char *devname; } fluid_dsound_devsel_t; BOOL CALLBACK fluid_dsound_enum_callback(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context) { fluid_settings_t *settings = (fluid_settings_t *) context; fluid_settings_add_option(settings, "audio.dsound.device", (const char *)description); return TRUE; } BOOL CALLBACK fluid_dsound_enum_callback2(LPGUID guid, LPCTSTR description, LPCTSTR module, LPVOID context) { fluid_dsound_devsel_t *devsel = (fluid_dsound_devsel_t *) context; FLUID_LOG(FLUID_DBG, "Testing audio device: %s", description); if(FLUID_STRCASECMP(devsel->devname, description) == 0) { devsel->devGUID = FLUID_NEW(GUID); if(devsel->devGUID) { memcpy(devsel->devGUID, guid, sizeof(GUID)); FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %p", devsel->devGUID); } } return TRUE; } void fluid_dsound_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "audio.dsound.device", "default", 0); fluid_settings_add_option(settings, "audio.dsound.device", "default"); DirectSoundEnumerate((LPDSENUMCALLBACK) fluid_dsound_enum_callback, settings); } /* * new_fluid_dsound_audio_driver */ fluid_audio_driver_t * new_fluid_dsound_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { HRESULT hr; DSBUFFERDESC desc; fluid_dsound_audio_driver_t *dev = NULL; DSCAPS caps; char *buf1; DWORD bytes1; double sample_rate; int periods, period_size; fluid_dsound_devsel_t devsel; WAVEFORMATEX format; /* create and clear the driver data */ dev = FLUID_NEW(fluid_dsound_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_dsound_audio_driver_t)); dev->synth = synth; fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); /* Clear the buffer format */ ZeroMemory(&format, sizeof(WAVEFORMATEX)); /* check the format */ if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) { FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format"); dev->frame_size = 2 * sizeof(float); dev->write = fluid_synth_write_float; format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; } else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format"); dev->frame_size = 2 * sizeof(short); dev->write = fluid_synth_write_s16; format.wFormatTag = WAVE_FORMAT_PCM; } else { FLUID_LOG(FLUID_ERR, "Unhandled sample format"); goto error_recovery; } dev->buffer_byte_size = period_size * dev->frame_size; dev->queue_byte_size = periods * dev->buffer_byte_size; dev->bytes_per_second = sample_rate * dev->frame_size; /* Finish to initialize the buffer format */ format.nChannels = 2; format.wBitsPerSample = dev->frame_size * 4; format.nSamplesPerSec = (DWORD) sample_rate; format.nBlockAlign = (WORD) dev->frame_size; format.nAvgBytesPerSec = dev->bytes_per_second; devsel.devGUID = NULL; /* get the selected device name. if none is specified, use NULL for the default device. */ if(fluid_settings_dupstr(settings, "audio.dsound.device", &devsel.devname) == FLUID_OK /* ++ alloc device name */ && devsel.devname && strlen(devsel.devname) > 0) { /* look for the GUID of the selected device */ DirectSoundEnumerate((LPDSENUMCALLBACK) fluid_dsound_enum_callback2, (void *)&devsel); } if(devsel.devname) { FLUID_FREE(devsel.devname); /* -- free device name */ } /* open DirectSound */ hr = DirectSoundCreate(devsel.devGUID, &dev->direct_sound, NULL); if(hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to create the DirectSound object"); goto error_recovery; } hr = IDirectSound_SetCooperativeLevel(dev->direct_sound, GetDesktopWindow(), DSSCL_PRIORITY); if(hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to set the cooperative level"); goto error_recovery; } caps.dwSize = sizeof(caps); hr = IDirectSound_GetCaps(dev->direct_sound, &caps); if(hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to query the device capacities"); goto error_recovery; } /* create primary buffer */ ZeroMemory(&desc, sizeof(DSBUFFERDESC)); desc.dwSize = sizeof(DSBUFFERDESC); desc.dwFlags = DSBCAPS_PRIMARYBUFFER; if(caps.dwFreeHwMixingStreamingBuffers > 0) { desc.dwFlags |= DSBCAPS_LOCHARDWARE; } hr = IDirectSound_CreateSoundBuffer(dev->direct_sound, &desc, &dev->prim_buffer, NULL); if(hr != DS_OK) { FLUID_LOG(FLUID_ERR, "Failed to allocate the primary buffer"); goto error_recovery; } /* set the primary sound buffer to this format. if it fails, just print a warning. */ hr = IDirectSoundBuffer_SetFormat(dev->prim_buffer, &format); if(hr != DS_OK) { FLUID_LOG(FLUID_WARN, "Can't set format of primary sound buffer: %s", fluid_win32_error(hr)); } /* initialize the buffer description */ ZeroMemory(&desc, sizeof(DSBUFFERDESC)); desc.dwSize = sizeof(DSBUFFERDESC); desc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; desc.lpwfxFormat = &format; desc.dwBufferBytes = dev->queue_byte_size; if(caps.dwFreeHwMixingStreamingBuffers > 0) { desc.dwFlags |= DSBCAPS_LOCHARDWARE; } /* create the secondary sound buffer */ hr = IDirectSound_CreateSoundBuffer(dev->direct_sound, &desc, &dev->sec_buffer, NULL); if(hr != DS_OK) { FLUID_LOG(FLUID_ERR, "dsound: Can't create sound buffer: %s", fluid_win32_error(hr)); goto error_recovery; } /* Lock */ hr = IDirectSoundBuffer_Lock(dev->sec_buffer, 0, 0, (void *) &buf1, &bytes1, 0, 0, DSBLOCK_ENTIREBUFFER); if((hr != DS_OK) || (buf1 == NULL)) { FLUID_LOG(FLUID_PANIC, "Failed to lock the audio buffer. Exiting."); goto error_recovery; } /* fill the buffer with silence */ memset(buf1, 0, bytes1); /* Unlock */ IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, 0, 0); /* Create object to signal thread exit */ dev->quit_ev = CreateEvent(NULL, FALSE, FALSE, NULL); if(dev->quit_ev == NULL) { goto error_recovery; } /* start the audio thread */ dev->thread = CreateThread(NULL, 0, fluid_dsound_audio_run, (LPVOID) dev, 0, &dev->threadID); if(dev->thread == NULL) { goto error_recovery; } return (fluid_audio_driver_t *) dev; error_recovery: delete_fluid_dsound_audio_driver((fluid_audio_driver_t *) dev); return NULL; } void delete_fluid_dsound_audio_driver(fluid_audio_driver_t *d) { fluid_dsound_audio_driver_t *dev = (fluid_dsound_audio_driver_t *) d; fluid_return_if_fail(dev != NULL); /* wait till the audio thread exits */ if(dev->thread != NULL) { /* tell the audio thread to stop its loop */ SetEvent(dev->quit_ev); if(WaitForSingleObject(dev->thread, 2000) != WAIT_OBJECT_0) { /* on error kill the thread mercilessly */ FLUID_LOG(FLUID_DBG, "Couldn't join the audio thread. killing it."); TerminateThread(dev->thread, 0); } /* Release the thread object */ CloseHandle(dev->thread); } /* Release the event object */ if(dev->quit_ev != NULL) { CloseHandle(dev->quit_ev); } /* release all the allocated resources */ if(dev->sec_buffer != NULL) { IDirectSoundBuffer_Stop(dev->sec_buffer); IDirectSoundBuffer_Release(dev->sec_buffer); } if(dev->prim_buffer != NULL) { IDirectSoundBuffer_Release(dev->prim_buffer); } if(dev->direct_sound != NULL) { IDirectSound_Release(dev->direct_sound); } FLUID_FREE(dev); } static DWORD WINAPI fluid_dsound_audio_run(LPVOID lpParameter) { fluid_dsound_audio_driver_t *dev = (fluid_dsound_audio_driver_t *) lpParameter; short *buf1, *buf2; DWORD bytes1, bytes2; DWORD cur_position, frames, play_position, write_position, bytes; HRESULT res; int ms; cur_position = 0; /* boost the priority of the audio thread */ SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); IDirectSoundBuffer_Play(dev->sec_buffer, 0, 0, DSBPLAY_LOOPING); for(;;) { IDirectSoundBuffer_GetCurrentPosition(dev->sec_buffer, &play_position, &write_position); if(cur_position <= play_position) { bytes = play_position - cur_position; } else if((play_position < cur_position) && (write_position <= cur_position)) { bytes = dev->queue_byte_size + play_position - cur_position; } else { bytes = 0; } if(bytes >= dev->buffer_byte_size) { /* Lock */ res = IDirectSoundBuffer_Lock(dev->sec_buffer, cur_position, bytes, (void *) &buf1, &bytes1, (void *) &buf2, &bytes2, 0); if((res != DS_OK) || (buf1 == NULL)) { FLUID_LOG(FLUID_PANIC, "Failed to lock the audio buffer. System lockup might follow. Exiting."); ExitProcess(0); } /* fill the first part of the buffer */ if(bytes1 > 0) { frames = bytes1 / dev->frame_size; dev->write(dev->synth, frames, buf1, 0, 2, buf1, 1, 2); cur_position += frames * dev->frame_size; } /* fill the second part of the buffer */ if((buf2 != NULL) && (bytes2 > 0)) { frames = bytes2 / dev->frame_size; dev->write(dev->synth, frames, buf2, 0, 2, buf2, 1, 2); cur_position += frames * dev->frame_size; } /* Unlock */ IDirectSoundBuffer_Unlock(dev->sec_buffer, buf1, bytes1, buf2, bytes2); if(cur_position >= dev->queue_byte_size) { cur_position -= dev->queue_byte_size; } /* 1 ms of wait */ ms = 1; } else { /* Calculate how many milliseconds to sleep (minus 1 for safety) */ ms = (dev->buffer_byte_size - bytes) * 1000 / dev->bytes_per_second - 1; if(ms < 1) { ms = 1; } } /* Wait quit event or timeout */ if(WaitForSingleObject(dev->quit_ev, ms) == WAIT_OBJECT_0) { break; } } return 0; } static char *fluid_win32_error(HRESULT hr) { char *s = "Don't know why"; switch(hr) { case E_NOINTERFACE: s = "No such interface"; break; case DSERR_GENERIC: s = "Generic error"; break; case DSERR_ALLOCATED: s = "Required resources already allocated"; break; case DSERR_BADFORMAT: s = "The format is not supported"; break; case DSERR_INVALIDPARAM: s = "Invalid parameter"; break; case DSERR_NOAGGREGATION: s = "No aggregation"; break; case DSERR_OUTOFMEMORY: s = "Out of memory"; break; case DSERR_UNINITIALIZED: s = "Uninitialized"; break; case DSERR_UNSUPPORTED: s = "Function not supported"; break; } return s; } #endif /* DSOUND_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_jack.c000066400000000000000000000654651362231004000204170ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_jack.c * * Driver for the JACK * * This code is derived from the simple_client example in the JACK * source distribution. Many thanks to Paul Davis. * */ #include "fluid_synth.h" #include "fluid_adriver.h" #include "fluid_mdriver.h" #include "fluid_settings.h" #if JACK_SUPPORT #include #include #include "fluid_lash.h" typedef struct _fluid_jack_audio_driver_t fluid_jack_audio_driver_t; typedef struct _fluid_jack_midi_driver_t fluid_jack_midi_driver_t; /* Clients are shared for drivers using the same server. */ typedef struct { jack_client_t *client; char *server; /* Jack server name used */ fluid_jack_audio_driver_t *audio_driver; fluid_jack_midi_driver_t *midi_driver; } fluid_jack_client_t; /* Jack audio driver instance */ struct _fluid_jack_audio_driver_t { fluid_audio_driver_t driver; fluid_jack_client_t *client_ref; int audio_channels; jack_port_t **output_ports; int num_output_ports; float **output_bufs; jack_port_t **fx_ports; int num_fx_ports; float **fx_bufs; fluid_audio_func_t callback; void *data; }; /* Jack MIDI driver instance */ struct _fluid_jack_midi_driver_t { fluid_midi_driver_t driver; fluid_jack_client_t *client_ref; int midi_port_count; jack_port_t **midi_port; // array of midi port handles fluid_midi_parser_t *parser; int autoconnect_inputs; int autoconnect_is_outdated; }; static fluid_jack_client_t *new_fluid_jack_client(fluid_settings_t *settings, int isaudio, void *driver); static int fluid_jack_client_register_ports(void *driver, int isaudio, jack_client_t *client, fluid_settings_t *settings); void fluid_jack_driver_shutdown(void *arg); int fluid_jack_driver_srate(jack_nframes_t nframes, void *arg); int fluid_jack_driver_bufsize(jack_nframes_t nframes, void *arg); int fluid_jack_driver_process(jack_nframes_t nframes, void *arg); void fluid_jack_port_registration(jack_port_id_t port, int is_registering, void *arg); static fluid_mutex_t last_client_mutex = FLUID_MUTEX_INIT; /* Probably not necessary, but just in case drivers are created by multiple threads */ static fluid_jack_client_t *last_client = NULL; /* Last unpaired client. For audio/MIDI driver pairing. */ void fluid_jack_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "audio.jack.id", "fluidsynth", 0); fluid_settings_register_int(settings, "audio.jack.multi", 0, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_int(settings, "audio.jack.autoconnect", 0, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_str(settings, "audio.jack.server", "", 0); } /* * Connect all midi input ports to all terminal midi output ports */ void fluid_jack_midi_autoconnect(jack_client_t *client, fluid_jack_midi_driver_t *midi_driver) { int i, j; const char **midi_source_ports; midi_source_ports = jack_get_ports(client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsTerminal); if(midi_source_ports != NULL) { for(j = 0; midi_source_ports[j] != NULL; j++) { for(i = 0; i < midi_driver->midi_port_count; i++) { FLUID_LOG(FLUID_INFO, "jack midi autoconnect \"%s\" to \"%s\"", midi_source_ports[j], jack_port_name(midi_driver->midi_port[i])); jack_connect(client, midi_source_ports[j], jack_port_name(midi_driver->midi_port[i])); } } jack_free(midi_source_ports); } midi_driver->autoconnect_is_outdated = FALSE; } /* * Create Jack client as necessary, share clients of the same server. * @param settings Settings object * @param isaudio TRUE if audio driver, FALSE if MIDI * @param driver fluid_jack_audio_driver_t or fluid_jack_midi_driver_t * @param data The user data instance associated with the driver (fluid_synth_t for example) * @return New or paired Audio/MIDI Jack client */ static fluid_jack_client_t * new_fluid_jack_client(fluid_settings_t *settings, int isaudio, void *driver) { fluid_jack_client_t *client_ref = NULL; char *server = NULL; char *client_name; char name[64]; if(fluid_settings_dupstr(settings, isaudio ? "audio.jack.server" /* ++ alloc server name */ : "midi.jack.server", &server) != FLUID_OK) { return NULL; } fluid_mutex_lock(last_client_mutex); /* ++ lock last_client */ /* If the last client uses the same server and is not the same type (audio or MIDI), * then re-use the client. */ if(last_client && (last_client->server != NULL && server != NULL && FLUID_STRCMP(last_client->server, server) == 0) && ((!isaudio && last_client->midi_driver == NULL) || (isaudio && last_client->audio_driver == NULL))) { client_ref = last_client; /* Register ports */ if(fluid_jack_client_register_ports(driver, isaudio, client_ref->client, settings) == FLUID_OK) { last_client = NULL; /* No more pairing for this client */ if(isaudio) { fluid_atomic_pointer_set(&client_ref->audio_driver, driver); } else { fluid_atomic_pointer_set(&client_ref->midi_driver, driver); } } else { // do not free client_ref and do not goto error_recovery // client_ref is being used by another audio or midi driver. Freeing it here will create a double free. } fluid_mutex_unlock(last_client_mutex); /* -- unlock last_client */ if(server) { FLUID_FREE(server); } return client_ref; } /* No existing client for this Jack server */ client_ref = FLUID_NEW(fluid_jack_client_t); if(!client_ref) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } FLUID_MEMSET(client_ref, 0, sizeof(fluid_jack_client_t)); fluid_settings_dupstr(settings, isaudio ? "audio.jack.id" /* ++ alloc client name */ : "midi.jack.id", &client_name); if(client_name != NULL && client_name[0] != 0) { FLUID_SNPRINTF(name, sizeof(name), "%s", client_name); } else { FLUID_STRNCPY(name, "fluidsynth", sizeof(name)); } name[63] = '\0'; if(client_name) { FLUID_FREE(client_name); /* -- free client name */ } /* Open a connection to the Jack server and use the server name if specified */ if(server && server[0] != '\0') { client_ref->client = jack_client_open(name, JackServerName, NULL, server); } else { client_ref->client = jack_client_open(name, JackNullOption, NULL); } if(!client_ref->client) { FLUID_LOG(FLUID_ERR, "Failed to connect to Jack server."); goto error_recovery; } jack_set_port_registration_callback(client_ref->client, fluid_jack_port_registration, client_ref); jack_set_process_callback(client_ref->client, fluid_jack_driver_process, client_ref); jack_set_buffer_size_callback(client_ref->client, fluid_jack_driver_bufsize, client_ref); jack_set_sample_rate_callback(client_ref->client, fluid_jack_driver_srate, client_ref); jack_on_shutdown(client_ref->client, fluid_jack_driver_shutdown, client_ref); /* Register ports */ if(fluid_jack_client_register_ports(driver, isaudio, client_ref->client, settings) != FLUID_OK) { goto error_recovery; } /* tell the JACK server that we are ready to roll */ if(jack_activate(client_ref->client)) { FLUID_LOG(FLUID_ERR, "Failed to activate Jack client"); goto error_recovery; } /* tell the lash server our client name */ #ifdef HAVE_LASH { int enable_lash = 0; fluid_settings_getint(settings, "lash.enable", &enable_lash); if(enable_lash) { fluid_lash_jack_client_name(fluid_lash_client, name); } } #endif /* HAVE_LASH */ client_ref->server = server; /* !! takes over allocation */ server = NULL; /* Set to NULL so it doesn't get freed below */ last_client = client_ref; if(isaudio) { fluid_atomic_pointer_set(&client_ref->audio_driver, driver); } else { fluid_atomic_pointer_set(&client_ref->midi_driver, driver); } fluid_mutex_unlock(last_client_mutex); /* -- unlock last_client */ if(server) { FLUID_FREE(server); } return client_ref; error_recovery: fluid_mutex_unlock(last_client_mutex); /* -- unlock clients list */ if(server) { FLUID_FREE(server); /* -- free server name */ } if(client_ref) { if(client_ref->client) { jack_client_close(client_ref->client); } FLUID_FREE(client_ref); } return NULL; } static int fluid_jack_client_register_ports(void *driver, int isaudio, jack_client_t *client, fluid_settings_t *settings) { fluid_jack_audio_driver_t *dev; char name[64]; int multi; int i; unsigned long jack_srate; double sample_rate; if(!isaudio) { fluid_jack_midi_driver_t *dev = driver; int midi_channels, ports; fluid_settings_getint(settings, "synth.midi-channels", &midi_channels); ports = midi_channels / 16; if((dev->midi_port = FLUID_ARRAY(jack_port_t *, ports)) == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } for(i = 0; i < ports; i++) { FLUID_SNPRINTF(name, sizeof(name), "midi_%02d", i); dev->midi_port[i] = jack_port_register(client, name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput | JackPortIsTerminal, 0); if(dev->midi_port[i] == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create Jack MIDI port"); FLUID_FREE(dev->midi_port); dev->midi_port = NULL; return FLUID_FAILED; } } dev->midi_port_count = ports; return FLUID_OK; } dev = driver; fluid_settings_getint(settings, "audio.jack.multi", &multi); if(!multi) { /* create the two audio output ports */ dev->num_output_ports = 1; dev->num_fx_ports = 0; dev->output_ports = FLUID_ARRAY(jack_port_t *, 2 * dev->num_output_ports); if(dev->output_ports == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } dev->output_bufs = FLUID_ARRAY(float *, 2 * dev->num_output_ports); FLUID_MEMSET(dev->output_ports, 0, 2 * dev->num_output_ports * sizeof(jack_port_t *)); dev->output_ports[0] = jack_port_register(client, "left", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); dev->output_ports[1] = jack_port_register(client, "right", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if(dev->output_ports[0] == NULL || dev->output_ports[1] == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create Jack audio port"); goto error_recovery; } } else { fluid_settings_getint(settings, "synth.audio-channels", &dev->num_output_ports); dev->output_ports = FLUID_ARRAY(jack_port_t *, 2 * dev->num_output_ports); if(dev->output_ports == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } dev->output_bufs = FLUID_ARRAY(float *, 2 * dev->num_output_ports); if(dev->output_bufs == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } FLUID_MEMSET(dev->output_ports, 0, 2 * dev->num_output_ports * sizeof(jack_port_t *)); for(i = 0; i < dev->num_output_ports; i++) { sprintf(name, "l_%02d", i); if((dev->output_ports[2 * i] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create Jack audio port '%s'", name); goto error_recovery; } sprintf(name, "r_%02d", i); if((dev->output_ports[2 * i + 1] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create Jack audio port '%s'", name); goto error_recovery; } } fluid_settings_getint(settings, "synth.effects-channels", &dev->num_fx_ports); fluid_settings_getint(settings, "synth.effects-groups", &i); dev->num_fx_ports *= i; dev->fx_ports = FLUID_ARRAY(jack_port_t *, 2 * dev->num_fx_ports); if(dev->fx_ports == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } dev->fx_bufs = FLUID_ARRAY(float *, 2 * dev->num_fx_ports); if(dev->fx_bufs == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } FLUID_MEMSET(dev->fx_ports, 0, 2 * dev->num_fx_ports * sizeof(jack_port_t *)); for(i = 0; i < dev->num_fx_ports; i++) { sprintf(name, "fx_l_%02d", i); if((dev->fx_ports[2 * i] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create Jack fx audio port '%s'", name); goto error_recovery; } sprintf(name, "fx_r_%02d", i); if((dev->fx_ports[2 * i + 1] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create Jack fx audio port '%s'", name); goto error_recovery; } } } /* Adjust sample rate to match JACK's */ jack_srate = jack_get_sample_rate(client); FLUID_LOG(FLUID_DBG, "Jack engine sample rate: %lu", jack_srate); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); if((unsigned long)sample_rate != jack_srate) { fluid_synth_t* synth; if(fluid_jack_obtain_synth(settings, &synth) == FLUID_OK) { FLUID_LOG(FLUID_INFO, "Jack sample rate mismatch, adjusting." " (synth.sample-rate=%lu, jackd=%lu)", (unsigned long)sample_rate, jack_srate); fluid_synth_set_sample_rate(synth, jack_srate); /* Changing sample rate is non RT, so make sure we process it and/or other things now */ fluid_synth_process_event_queue(synth); } else { FLUID_LOG(FLUID_WARN, "Jack sample rate mismatch (synth.sample-rate=%lu, jackd=%lu)" " impossible to adjust, because the settings object provided to new_fluid_audio_driver2() was not used to create a synth." , (unsigned long)sample_rate, jack_srate); } } return FLUID_OK; error_recovery: FLUID_FREE(dev->output_ports); dev->output_ports = NULL; FLUID_FREE(dev->fx_ports); dev->fx_ports = NULL; FLUID_FREE(dev->output_bufs); dev->output_bufs = NULL; FLUID_FREE(dev->fx_bufs); dev->fx_bufs = NULL; return FLUID_FAILED; } static void fluid_jack_client_close(fluid_jack_client_t *client_ref, void *driver) { if(client_ref->audio_driver == driver) { fluid_atomic_pointer_set(&client_ref->audio_driver, NULL); } else if(client_ref->midi_driver == driver) { fluid_atomic_pointer_set(&client_ref->midi_driver, NULL); } if(client_ref->audio_driver || client_ref->midi_driver) { fluid_msleep(100); /* FIXME - Hack to make sure that resources don't get freed while Jack callback is active */ return; } fluid_mutex_lock(last_client_mutex); if(client_ref == last_client) { last_client = NULL; } fluid_mutex_unlock(last_client_mutex); if(client_ref->client) { jack_client_close(client_ref->client); } if(client_ref->server) { FLUID_FREE(client_ref->server); } FLUID_FREE(client_ref); } fluid_audio_driver_t * new_fluid_jack_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { return new_fluid_jack_audio_driver2(settings, NULL, synth); } fluid_audio_driver_t * new_fluid_jack_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { fluid_jack_audio_driver_t *dev = NULL; jack_client_t *client; const char **jack_ports; /* for looking up ports */ int autoconnect = 0; int i; dev = FLUID_NEW(fluid_jack_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_jack_audio_driver_t)); dev->callback = func; dev->data = data; dev->client_ref = new_fluid_jack_client(settings, TRUE, dev); if(!dev->client_ref) { FLUID_FREE(dev); return NULL; } client = dev->client_ref->client; /* connect the ports. */ /* FIXME: should be done by a patchbay application */ /* find some physical ports and connect to them */ fluid_settings_getint(settings, "audio.jack.autoconnect", &autoconnect); if(autoconnect) { jack_ports = jack_get_ports(client, NULL, NULL, JackPortIsInput | JackPortIsPhysical); if(jack_ports) { int err; int connected = 0; for(i = 0; jack_ports[i] && i < 2 * dev->num_output_ports; ++i) { err = jack_connect(client, jack_port_name(dev->output_ports[i]), jack_ports[i]); if(err) { FLUID_LOG(FLUID_ERR, "Error connecting jack port"); } else { connected++; } } for(i = 0; jack_ports[i] && i < 2 * dev->num_fx_ports; ++i) { err = jack_connect(client, jack_port_name(dev->fx_ports[i]), jack_ports[i]); if(err) { FLUID_LOG(FLUID_ERR, "Error connecting jack port"); } else { connected++; } } jack_free(jack_ports); /* free jack ports array (not the port values!) */ } else { FLUID_LOG(FLUID_WARN, "Could not connect to any physical jack ports; fluidsynth is unconnected"); } } return (fluid_audio_driver_t *) dev; } /* * delete_fluid_jack_audio_driver */ void delete_fluid_jack_audio_driver(fluid_audio_driver_t *p) { fluid_jack_audio_driver_t *dev = (fluid_jack_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); if(dev->client_ref != NULL) { fluid_jack_client_close(dev->client_ref, dev); } FLUID_FREE(dev->output_bufs); FLUID_FREE(dev->output_ports); FLUID_FREE(dev->fx_bufs); FLUID_FREE(dev->fx_ports); FLUID_FREE(dev); } /* Process function for audio and MIDI Jack drivers */ int fluid_jack_driver_process(jack_nframes_t nframes, void *arg) { fluid_jack_client_t *client = (fluid_jack_client_t *)arg; fluid_jack_audio_driver_t *audio_driver; fluid_jack_midi_driver_t *midi_driver; float *left, *right; int i; jack_midi_event_t midi_event; fluid_midi_event_t *evt; void *midi_buffer; jack_nframes_t event_count; jack_nframes_t event_index; unsigned int u; /* Process MIDI events first, so that they take effect before audio synthesis */ midi_driver = fluid_atomic_pointer_get(&client->midi_driver); if(midi_driver) { if(midi_driver->autoconnect_is_outdated) { fluid_jack_midi_autoconnect(client->client, midi_driver); } for(i = 0; i < midi_driver->midi_port_count; i++) { midi_buffer = jack_port_get_buffer(midi_driver->midi_port[i], 0); event_count = jack_midi_get_event_count(midi_buffer); for(event_index = 0; event_index < event_count; event_index++) { jack_midi_event_get(&midi_event, midi_buffer, event_index); /* let the parser convert the data into events */ for(u = 0; u < midi_event.size; u++) { evt = fluid_midi_parser_parse(midi_driver->parser, midi_event.buffer[u]); /* send the event to the next link in the chain */ if(evt != NULL) { fluid_midi_event_set_channel(evt, fluid_midi_event_get_channel(evt) + i * 16); midi_driver->driver.handler(midi_driver->driver.data, evt); } } } } } audio_driver = fluid_atomic_pointer_get(&client->audio_driver); if(audio_driver == NULL) { // shutting down return FLUID_OK; } if(audio_driver->callback == NULL && audio_driver->num_output_ports == 1 && audio_driver->num_fx_ports == 0) /* i.e. audio.jack.multi=no */ { left = (float *) jack_port_get_buffer(audio_driver->output_ports[0], nframes); right = (float *) jack_port_get_buffer(audio_driver->output_ports[1], nframes); return fluid_synth_write_float(audio_driver->data, nframes, left, 0, 1, right, 0, 1); } else { fluid_audio_func_t callback = (audio_driver->callback != NULL) ? audio_driver->callback : (fluid_audio_func_t) fluid_synth_process; for(i = 0; i < audio_driver->num_output_ports; i++) { int k = i * 2; audio_driver->output_bufs[k] = (float *)jack_port_get_buffer(audio_driver->output_ports[k], nframes); FLUID_MEMSET(audio_driver->output_bufs[k], 0, nframes * sizeof(float)); k = 2 * i + 1; audio_driver->output_bufs[k] = (float *)jack_port_get_buffer(audio_driver->output_ports[k], nframes); FLUID_MEMSET(audio_driver->output_bufs[k], 0, nframes * sizeof(float)); } for(i = 0; i < audio_driver->num_fx_ports; i++) { int k = i * 2; audio_driver->fx_bufs[k] = (float *) jack_port_get_buffer(audio_driver->fx_ports[k], nframes); FLUID_MEMSET(audio_driver->fx_bufs[k], 0, nframes * sizeof(float)); k = 2 * i + 1; audio_driver->fx_bufs[k] = (float *) jack_port_get_buffer(audio_driver->fx_ports[k], nframes); FLUID_MEMSET(audio_driver->fx_bufs[k], 0, nframes * sizeof(float)); } return callback(audio_driver->data, nframes, audio_driver->num_fx_ports * 2, audio_driver->fx_bufs, audio_driver->num_output_ports * 2, audio_driver->output_bufs); } } int fluid_jack_driver_bufsize(jack_nframes_t nframes, void *arg) { /* printf("the maximum buffer size is now %lu\n", nframes); */ return 0; } int fluid_jack_driver_srate(jack_nframes_t nframes, void *arg) { /* printf("the sample rate is now %lu/sec\n", nframes); */ /* FIXME: change the sample rate of the synthesizer! */ return 0; } void fluid_jack_driver_shutdown(void *arg) { // fluid_jack_audio_driver_t* dev = (fluid_jack_audio_driver_t*) arg; FLUID_LOG(FLUID_ERR, "Help! Lost the connection to the JACK server"); /* exit (1); */ } void fluid_jack_port_registration(jack_port_id_t port, int is_registering, void *arg) { fluid_jack_client_t *client_ref = (fluid_jack_client_t *)arg; if(client_ref->midi_driver != NULL) { client_ref->midi_driver->autoconnect_is_outdated = client_ref->midi_driver->autoconnect_inputs && is_registering != 0; } } void fluid_jack_midi_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "midi.jack.id", "fluidsynth-midi", 0); fluid_settings_register_str(settings, "midi.jack.server", "", 0); } /* * new_fluid_jack_midi_driver */ fluid_midi_driver_t * new_fluid_jack_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { fluid_jack_midi_driver_t *dev; fluid_return_val_if_fail(handler != NULL, NULL); /* allocate the device */ dev = FLUID_NEW(fluid_jack_midi_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_jack_midi_driver_t)); dev->driver.handler = handler; dev->driver.data = data; /* allocate one event to store the input data */ dev->parser = new_fluid_midi_parser(); if(dev->parser == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } fluid_settings_getint(settings, "midi.autoconnect", &dev->autoconnect_inputs); dev->autoconnect_is_outdated = dev->autoconnect_inputs; dev->client_ref = new_fluid_jack_client(settings, FALSE, dev); if(!dev->client_ref) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } return (fluid_midi_driver_t *)dev; error_recovery: delete_fluid_jack_midi_driver((fluid_midi_driver_t *)dev); return NULL; } void delete_fluid_jack_midi_driver(fluid_midi_driver_t *p) { fluid_jack_midi_driver_t *dev = (fluid_jack_midi_driver_t *)p; fluid_return_if_fail(dev != NULL); if(dev->client_ref != NULL) { fluid_jack_client_close(dev->client_ref, dev); } delete_fluid_midi_parser(dev->parser); FLUID_FREE(dev->midi_port); FLUID_FREE(dev); } int fluid_jack_obtain_synth(fluid_settings_t *settings, fluid_synth_t **synth) { void *data; if(!fluid_settings_is_realtime(settings, "synth.gain") || (data = fluid_settings_get_user_data(settings, "synth.gain")) == NULL) { return FLUID_FAILED; } *synth = data; return FLUID_OK; } #endif /* JACK_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_mdriver.c000066400000000000000000000123541362231004000211440ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_mdriver.h" #include "fluid_settings.h" /* * fluid_mdriver_definition */ struct _fluid_mdriver_definition_t { const char *name; fluid_midi_driver_t *(*new)(fluid_settings_t *settings, handle_midi_event_func_t event_handler, void *event_handler_data); void (*free)(fluid_midi_driver_t *p); void (*settings)(fluid_settings_t *settings); }; static const fluid_mdriver_definition_t fluid_midi_drivers[] = { #if ALSA_SUPPORT { "alsa_seq", new_fluid_alsa_seq_driver, delete_fluid_alsa_seq_driver, fluid_alsa_seq_driver_settings }, { "alsa_raw", new_fluid_alsa_rawmidi_driver, delete_fluid_alsa_rawmidi_driver, fluid_alsa_rawmidi_driver_settings }, #endif #if JACK_SUPPORT { "jack", new_fluid_jack_midi_driver, delete_fluid_jack_midi_driver, fluid_jack_midi_driver_settings }, #endif #if OSS_SUPPORT { "oss", new_fluid_oss_midi_driver, delete_fluid_oss_midi_driver, fluid_oss_midi_driver_settings }, #endif #if WINMIDI_SUPPORT { "winmidi", new_fluid_winmidi_driver, delete_fluid_winmidi_driver, fluid_winmidi_midi_driver_settings }, #endif #if MIDISHARE_SUPPORT { "midishare", new_fluid_midishare_midi_driver, delete_fluid_midishare_midi_driver, NULL }, #endif #if COREMIDI_SUPPORT { "coremidi", new_fluid_coremidi_driver, delete_fluid_coremidi_driver, fluid_coremidi_driver_settings }, #endif /* NULL terminator to avoid zero size array if no driver available */ { NULL, NULL, NULL, NULL } }; void fluid_midi_driver_settings(fluid_settings_t *settings) { unsigned int i; const char *def_name = NULL; fluid_settings_register_int(settings, "midi.autoconnect", 0, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_int(settings, "midi.realtime-prio", FLUID_DEFAULT_MIDI_RT_PRIO, 0, 99, 0); fluid_settings_register_str(settings, "midi.driver", "", 0); for(i = 0; i < FLUID_N_ELEMENTS(fluid_midi_drivers) - 1; i++) { /* Select the default driver */ if (def_name == NULL) { def_name = fluid_midi_drivers[i].name; } /* Add the driver to the list of options */ fluid_settings_add_option(settings, "midi.driver", fluid_midi_drivers[i].name); if(fluid_midi_drivers[i].settings != NULL) { fluid_midi_drivers[i].settings(settings); } } /* Set the default driver, if any */ if(def_name != NULL) { fluid_settings_setstr(settings, "midi.driver", def_name); } } /** * Create a new MIDI driver instance. * @param settings Settings used to configure new MIDI driver. * @param handler MIDI handler callback (for example: fluid_midi_router_handle_midi_event() * for MIDI router) * @param event_handler_data Caller defined data to pass to 'handler' * @return New MIDI driver instance or NULL on error */ fluid_midi_driver_t *new_fluid_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data) { fluid_midi_driver_t *driver = NULL; char *allnames; const fluid_mdriver_definition_t *def; for(def = fluid_midi_drivers; def->name != NULL; def++) { if(fluid_settings_str_equal(settings, "midi.driver", def->name)) { FLUID_LOG(FLUID_DBG, "Using '%s' midi driver", def->name); driver = def->new(settings, handler, event_handler_data); if(driver) { driver->define = def; } return driver; } } FLUID_LOG(FLUID_ERR, "Couldn't find the requested midi driver."); allnames = fluid_settings_option_concat(settings, "midi.driver", NULL); if(allnames != NULL) { if(allnames[0] != '\0') { FLUID_LOG(FLUID_INFO, "Valid drivers are: %s", allnames); } else { FLUID_LOG(FLUID_INFO, "No MIDI drivers available."); } FLUID_FREE(allnames); } return NULL; } /** * Delete a MIDI driver instance. * @param driver MIDI driver to delete */ void delete_fluid_midi_driver(fluid_midi_driver_t *driver) { fluid_return_if_fail(driver != NULL); driver->define->free(driver); } fluidsynth-2.1.1/src/drivers/fluid_mdriver.h000066400000000000000000000065541362231004000211560ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_MDRIVER_H #define _FLUID_MDRIVER_H #include "fluid_sys.h" /* * fluid_midi_driver_t */ typedef struct _fluid_mdriver_definition_t fluid_mdriver_definition_t; struct _fluid_midi_driver_t { const fluid_mdriver_definition_t *define; handle_midi_event_func_t handler; void *data; }; void fluid_midi_driver_settings(fluid_settings_t *settings); /* ALSA */ #if ALSA_SUPPORT fluid_midi_driver_t *new_fluid_alsa_rawmidi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); void delete_fluid_alsa_rawmidi_driver(fluid_midi_driver_t *p); void fluid_alsa_rawmidi_driver_settings(fluid_settings_t *settings); fluid_midi_driver_t *new_fluid_alsa_seq_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); void delete_fluid_alsa_seq_driver(fluid_midi_driver_t *p); void fluid_alsa_seq_driver_settings(fluid_settings_t *settings); #endif /* JACK */ #if JACK_SUPPORT void fluid_jack_midi_driver_settings(fluid_settings_t *settings); fluid_midi_driver_t *new_fluid_jack_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data); void delete_fluid_jack_midi_driver(fluid_midi_driver_t *p); #endif /* OSS */ #if OSS_SUPPORT fluid_midi_driver_t *new_fluid_oss_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); void delete_fluid_oss_midi_driver(fluid_midi_driver_t *p); void fluid_oss_midi_driver_settings(fluid_settings_t *settings); #endif /* Windows MIDI service */ #if WINMIDI_SUPPORT fluid_midi_driver_t *new_fluid_winmidi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); void delete_fluid_winmidi_driver(fluid_midi_driver_t *p); void fluid_winmidi_midi_driver_settings(fluid_settings_t *settings); #endif /* definitions for the MidiShare driver */ #if MIDISHARE_SUPPORT fluid_midi_driver_t *new_fluid_midishare_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); void delete_fluid_midishare_midi_driver(fluid_midi_driver_t *p); #endif /* definitions for the CoreMidi driver */ #if COREMIDI_SUPPORT fluid_midi_driver_t *new_fluid_coremidi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); void delete_fluid_coremidi_driver(fluid_midi_driver_t *p); void fluid_coremidi_driver_settings(fluid_settings_t *settings); #endif #endif /* _FLUID_AUDRIVER_H */ fluidsynth-2.1.1/src/drivers/fluid_midishare.c000066400000000000000000000311371362231004000214410ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_midishare.c * * Author: Stephane Letz (letz@grame.fr) Grame * * Interface to Grame's MidiShare drivers (www.grame.fr/MidiShare) * 21/12/01 : Add a compilation flag (MIDISHARE_DRIVER) for driver or application mode * 29/01/02 : Compilation on MacOSX, use a task for typeNote management * 03/06/03 : Adapdation for FluidSynth API * 18/03/04 : In application mode, connect MidiShare to the fluidsynth client (fluid_midishare_open_appl) */ #include "config.h" #if MIDISHARE_SUPPORT #include "fluid_midi.h" #include "fluid_mdriver.h" #include /* constants definitions */ #define MidiShareDrvRef 127 #if defined(MACINTOSH) && defined(MACOS9) #define MSHSlotName "\pfluidsynth" #define MSHDriverName "\pfluidsynth" #else #define MSHSlotName "fluidsynth" #define MSHDriverName "fluidsynth" #endif typedef struct { fluid_midi_driver_t driver; int status; short refnum; MidiFilterPtr filter; #if defined(MACINTOSH) && defined(MACOS9) UPPRcvAlarmPtr upp_alarm_ptr; UPPDriverPtr upp_wakeup_ptr; UPPDriverPtr upp_sleep_ptr; UPPTaskPtr upp_task_ptr; #endif SlotRefNum slotRef; unsigned char sysexbuf[FLUID_MIDI_PARSER_MAX_DATA_SIZE]; } fluid_midishare_midi_driver_t; static void fluid_midishare_midi_driver_receive(short ref); #if defined(MIDISHARE_DRIVER) static int fluid_midishare_open_driver(fluid_midishare_midi_driver_t *dev); static void fluid_midishare_close_driver(fluid_midishare_midi_driver_t *dev); #else static int fluid_midishare_open_appl(fluid_midishare_midi_driver_t *dev); static void fluid_midishare_close_appl(fluid_midishare_midi_driver_t *dev); #endif /* * new_fluid_midishare_midi_driver */ fluid_midi_driver_t * new_fluid_midishare_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { fluid_midishare_midi_driver_t *dev; int i; /* not much use doing anything */ if(handler == NULL) { FLUID_LOG(FLUID_ERR, "Invalid argument"); return NULL; } /* allocate the device */ dev = FLUID_NEW(fluid_midishare_midi_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_midishare_midi_driver_t)); dev->driver.handler = handler; dev->driver.data = data; /* register to MidiShare as Application or Driver */ #if defined(MIDISHARE_DRIVER) if(!fluid_midishare_open_driver(dev)) { goto error_recovery; } #else if(!fluid_midishare_open_appl(dev)) { goto error_recovery; } #endif /*MidiSetInfo(dev->refnum, dev->router->synth); */ MidiSetInfo(dev->refnum, dev); dev->filter = MidiNewFilter(); if(dev->filter == 0) { FLUID_LOG(FLUID_ERR, "Can not allocate MidiShare filter"); goto error_recovery; } for(i = 0 ; i < 256; i++) { MidiAcceptPort(dev->filter, i, 1); /* accept all ports */ MidiAcceptType(dev->filter, i, 0); /* reject all types */ } for(i = 0 ; i < 16; i++) { MidiAcceptChan(dev->filter, i, 1); /* accept all chan */ } /* accept only the following types */ MidiAcceptType(dev->filter, typeNote, 1); MidiAcceptType(dev->filter, typeKeyOn, 1); MidiAcceptType(dev->filter, typeKeyOff, 1); MidiAcceptType(dev->filter, typeCtrlChange, 1); MidiAcceptType(dev->filter, typeProgChange, 1); MidiAcceptType(dev->filter, typePitchWheel, 1); MidiAcceptType(dev->filter, typeSysEx, 1); /* set the filter */ MidiSetFilter(dev->refnum, dev->filter); dev->status = FLUID_MIDI_READY; return (fluid_midi_driver_t *) dev; error_recovery: delete_fluid_midishare_midi_driver((fluid_midi_driver_t *) dev); return NULL; } /* * delete_fluid_midishare_midi_driver */ void delete_fluid_midishare_midi_driver(fluid_midi_driver_t *p) { fluid_midishare_midi_driver_t *dev = (fluid_midishare_midi_driver_t *) p; fluid_return_if_fail(dev != NULL); if(dev->filter) { MidiFreeFilter(dev->filter); } #if defined(MIDISHARE_DRIVER) fluid_midishare_close_driver(dev); #else fluid_midishare_close_appl(dev); #endif #if defined(MACINTOSH) && defined(MACOS9) DisposeRoutineDescriptor(dev->upp_alarm_ptr); DisposeRoutineDescriptor(dev->upp_wakeup_ptr); DisposeRoutineDescriptor(dev->upp_sleep_ptr); DisposeRoutineDescriptor(dev->upp_task_ptr); #endif dev->status = FLUID_MIDI_DONE; FLUID_FREE(dev); } /* * fluid_midishare_keyoff_task */ static void fluid_midishare_keyoff_task(long date, short ref, long a1, long a2, long a3) { fluid_midishare_midi_driver_t *dev = (fluid_midishare_midi_driver_t *)MidiGetInfo(ref); fluid_midi_event_t new_event; MidiEvPtr e = (MidiEvPtr)a1; fluid_midi_event_set_type(&new_event, NOTE_OFF); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_pitch(&new_event, Pitch(e)); fluid_midi_event_set_velocity(&new_event, Vel(e)); /* release vel */ /* and send it on its way to the router */ (*dev->driver.handler)(dev->driver.data, &new_event); MidiFreeEv(e); } /* * fluid_midishare_midi_driver_receive */ static void fluid_midishare_midi_driver_receive(short ref) { fluid_midishare_midi_driver_t *dev = (fluid_midishare_midi_driver_t *)MidiGetInfo(ref); fluid_midi_event_t new_event; MidiEvPtr e; int count, i; while((e = MidiGetEv(ref))) { switch(EvType(e)) { case typeNote: /* Copy the data to fluid_midi_event_t */ fluid_midi_event_set_type(&new_event, NOTE_ON); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_pitch(&new_event, Pitch(e)); fluid_midi_event_set_velocity(&new_event, Vel(e)); /* and send it on its way to the router */ (*dev->driver.handler)(dev->driver.data, &new_event); #if defined(MACINTOSH) && defined(MACOS9) MidiTask(dev->upp_task_ptr, MidiGetTime() + Dur(e), ref, (long)e, 0, 0); #else MidiTask(fluid_midishare_keyoff_task, MidiGetTime() + Dur(e), ref, (long)e, 0, 0); #endif /* e gets freed in fluid_midishare_keyoff_task */ continue; case typeKeyOn: /* Copy the data to fluid_midi_event_t */ fluid_midi_event_set_type(&new_event, NOTE_ON); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_pitch(&new_event, Pitch(e)); fluid_midi_event_set_velocity(&new_event, Vel(e)); break; case typeKeyOff: /* Copy the data to fluid_midi_event_t */ fluid_midi_event_set_type(&new_event, NOTE_OFF); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_pitch(&new_event, Pitch(e)); fluid_midi_event_set_velocity(&new_event, Vel(e)); /* release vel */ break; case typeCtrlChange: /* Copy the data to fluid_midi_event_t */ fluid_midi_event_set_type(&new_event, CONTROL_CHANGE); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_control(&new_event, MidiGetField(e, 0)); fluid_midi_event_set_value(&new_event, MidiGetField(e, 1)); break; case typeProgChange: /* Copy the data to fluid_midi_event_t */ fluid_midi_event_set_type(&new_event, PROGRAM_CHANGE); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_program(&new_event, MidiGetField(e, 0)); break; case typePitchWheel: /* Copy the data to fluid_midi_event_t */ fluid_midi_event_set_type(&new_event, PITCH_BEND); fluid_midi_event_set_channel(&new_event, Chan(e)); fluid_midi_event_set_value(&new_event, ((MidiGetField(e, 0) + (MidiGetField(e, 1) << 7)) - 8192)); break; case typeSysEx: count = MidiCountFields(e); /* Discard empty or too large SYSEX messages */ if(count == 0 || count > FLUID_MIDI_PARSER_MAX_DATA_SIZE) { MidiFreeEv(e); continue; } /* Copy SYSEX data, one byte at a time */ for(i = 0; i < count; i++) { dev->sysexbuf[i] = MidiGetField(e, i); } fluid_midi_event_set_sysex(&new_event, dev->sysexbuf, count, FALSE); break; default: MidiFreeEv(e); continue; } MidiFreeEv(e); /* Send the MIDI event */ (*dev->driver.handler)(dev->driver.data, &new_event); } } #if defined(MIDISHARE_DRIVER) /* * fluid_midishare_wakeup */ static void fluid_midishare_wakeup(short r) { MidiConnect(MidiShareDrvRef, r, true); MidiConnect(r, MidiShareDrvRef, true); } /* * fluid_midishare_sleep */ static void fluid_midishare_sleep(short r) {} /* * fluid_midishare_open_driver */ static int fluid_midishare_open_driver(fluid_midishare_midi_driver_t *dev) { /* gcc wanted me to use {0,0} to initialize the reserved[2] fields */ TDriverInfos infos = { MSHDriverName, 100, 0, { 0, 0 } }; TDriverOperation op = { fluid_midishare_wakeup, fluid_midishare_sleep, { 0, 0, 0 } }; /* register to MidiShare */ #if defined(MACINTOSH) && defined(MACOS9) dev->upp_wakeup_ptr = NewDriverPtr(fluid_midishare_wakeup); dev->upp_sleep_ptr = NewDriverPtr(fluid_midishare_sleep); op.wakeup = (WakeupPtr)dev->upp_wakeup_ptr; op.sleep = (SleepPtr)dev->upp_sleep_ptr; dev->refnum = MidiRegisterDriver(&infos, &op); if(dev->refnum < 0) { FLUID_LOG(FLUID_ERR, "Can not open MidiShare Application client"); return 0; } dev->slotRef = MidiAddSlot(dev->refnum, MSHSlotName, MidiOutputSlot); dev->upp_alarm_ptr = NewRcvAlarmPtr(fluid_midishare_midi_driver_receive); dev->upp_task_ptr = NewTaskPtr(fluid_midishare_keyoff_task); MidiSetRcvAlarm(dev->refnum, dev->upp_alarm_ptr); #else dev->refnum = MidiRegisterDriver(&infos, &op); if(dev->refnum < 0) { FLUID_LOG(FLUID_ERR, "Can not open MidiShare Application client"); return 0; } dev->slotRef = MidiAddSlot(dev->refnum, MSHSlotName, MidiOutputSlot); MidiSetRcvAlarm(dev->refnum, fluid_midishare_midi_driver_receive); #endif return 1; } /* * fluid_midishare_close_driver */ static void fluid_midishare_close_driver(fluid_midishare_midi_driver_t *dev) { if(dev->refnum > 0) { MidiUnregisterDriver(dev->refnum); } } #else /* #if defined(MIDISHARE_DRIVER) */ /* * fluid_midishare_open_appl */ static int fluid_midishare_open_appl(fluid_midishare_midi_driver_t *dev) { /* register to MidiShare */ #if defined(MACINTOSH) && defined(MACOS9) dev->refnum = MidiOpen(MSHDriverName); if(dev->refnum < 0) { FLUID_LOG(FLUID_ERR, "Can not open MidiShare Driver client"); return 0; } dev->upp_alarm_ptr = NewRcvAlarmPtr(fluid_midishare_midi_driver_receive); dev->upp_task_ptr = NewTaskPtr(fluid_midishare_keyoff_task); MidiSetRcvAlarm(dev->refnum, dev->upp_alarm_ptr); #else dev->refnum = MidiOpen(MSHDriverName); if(dev->refnum < 0) { FLUID_LOG(FLUID_ERR, "Can not open MidiShare Driver client"); return 0; } MidiSetRcvAlarm(dev->refnum, fluid_midishare_midi_driver_receive); MidiConnect(0, dev->refnum, true); #endif return 1; } /* * fluid_midishare_close_appl */ static void fluid_midishare_close_appl(fluid_midishare_midi_driver_t *dev) { if(dev->refnum > 0) { MidiClose(dev->refnum); } } #endif /* #if defined(MIDISHARE_DRIVER) */ #endif /* MIDISHARE_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_oboe.cpp000066400000000000000000000145351362231004000207630ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_oboe.c * * Audio driver for Android Oboe. * */ extern "C" { #include "fluid_adriver.h" #include "fluid_settings.h" } // extern "C" #if OBOE_SUPPORT #include using namespace oboe; static const int NUM_CHANNELS = 2; class OboeAudioStreamCallback; /** fluid_oboe_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; fluid_synth_t *synth; int cont; OboeAudioStreamCallback *oboe_callback; AudioStream *stream; } fluid_oboe_audio_driver_t; class OboeAudioStreamCallback : public AudioStreamCallback { public: OboeAudioStreamCallback(void *userData) : user_data(userData) { } DataCallbackResult onAudioReady(AudioStream *stream, void *audioData, int32_t numFrames) { fluid_oboe_audio_driver_t *dev = static_cast(this->user_data); if(!dev->cont) { return DataCallbackResult::Stop; } if(stream->getFormat() == AudioFormat::Float) { fluid_synth_write_float(dev->synth, numFrames, static_cast(audioData), 0, 2, static_cast(audioData), 1, 2); } else { fluid_synth_write_s16(dev->synth, numFrames, static_cast(audioData), 0, 2, static_cast(audioData), 1, 2); } return DataCallbackResult::Continue; } private: void *user_data; }; void fluid_oboe_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_int(settings, "audio.oboe.id", 0, 0, 0x7FFFFFFF, 0); fluid_settings_register_str(settings, "audio.oboe.sharing-mode", "Shared", 0); fluid_settings_add_option(settings, "audio.oboe.sharing-mode", "Shared"); fluid_settings_add_option(settings, "audio.oboe.sharing-mode", "Exclusive"); fluid_settings_register_str(settings, "audio.oboe.performance-mode", "None", 0); fluid_settings_add_option(settings, "audio.oboe.performance-mode", "None"); fluid_settings_add_option(settings, "audio.oboe.performance-mode", "PowerSaving"); fluid_settings_add_option(settings, "audio.oboe.performance-mode", "LowLatency"); } /* * new_fluid_oboe_audio_driver */ fluid_audio_driver_t * new_fluid_oboe_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { Result result; fluid_oboe_audio_driver_t *dev; AudioStreamBuilder builder_obj; AudioStreamBuilder *builder = &builder_obj; AudioStream *stream; int period_frames; double sample_rate; int is_sample_format_float; int device_id; int sharing_mode; // 0: Shared, 1: Exclusive int performance_mode; // 0: None, 1: PowerSaving, 2: LowLatency try { dev = FLUID_NEW(fluid_oboe_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_oboe_audio_driver_t)); dev->synth = synth; dev->oboe_callback = new(std::nothrow) OboeAudioStreamCallback(dev); if(!dev->oboe_callback) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } fluid_settings_getint(settings, "audio.period-size", &period_frames); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float"); fluid_settings_getint(settings, "audio.oboe.id", &device_id); sharing_mode = fluid_settings_str_equal(settings, "audio.oboe.sharing-mode", "Exclusive") ? 1 : 0; performance_mode = fluid_settings_str_equal(settings, "audio.oboe.performance-mode", "PowerSaving") ? 1 : fluid_settings_str_equal(settings, "audio.oboe.performance-mode", "LowLatency") ? 2 : 0; builder->setDeviceId(device_id) ->setDirection(Direction::Output) ->setChannelCount(NUM_CHANNELS) ->setSampleRate(sample_rate) ->setFramesPerCallback(period_frames) ->setFormat(is_sample_format_float ? AudioFormat::Float : AudioFormat::I16) ->setSharingMode(sharing_mode == 1 ? SharingMode::Exclusive : SharingMode::Shared) ->setPerformanceMode( performance_mode == 1 ? PerformanceMode::PowerSaving : performance_mode == 2 ? PerformanceMode::LowLatency : PerformanceMode::None) ->setUsage(Usage::Media) ->setContentType(ContentType::Music) ->setCallback(dev->oboe_callback); result = builder->openStream(&stream); dev->stream = stream; if(result != Result::OK) { goto error_recovery; } dev->cont = 1; FLUID_LOG(FLUID_INFO, "Using Oboe driver"); stream->start(); return reinterpret_cast(dev); } catch(...) { FLUID_LOG(FLUID_ERR, "Unexpected Oboe driver initialization error"); } error_recovery: delete_fluid_oboe_audio_driver(reinterpret_cast(dev)); return NULL; } void delete_fluid_oboe_audio_driver(fluid_audio_driver_t *p) { fluid_oboe_audio_driver_t *dev = reinterpret_cast(p); fluid_return_if_fail(dev != NULL); try { dev->cont = 0; if(dev->stream != NULL) { dev->stream->stop(); dev->stream->close(); } } catch(...) {} delete dev->oboe_callback; FLUID_FREE(dev); } #endif // OBOE_SUPPORT fluidsynth-2.1.1/src/drivers/fluid_opensles.c000066400000000000000000000221321362231004000213170ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_opensles.c * * Audio driver for OpenSLES. * */ #include "fluid_adriver.h" #if OPENSLES_SUPPORT #include #include static const int NUM_CHANNELS = 2; /** fluid_opensles_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; SLObjectItf engine; SLObjectItf output_mix_object; SLObjectItf audio_player; SLPlayItf audio_player_interface; SLAndroidSimpleBufferQueueItf player_buffer_queue_interface; void *synth; int period_frames; int is_sample_format_float; /* used only by callback mode */ short *sles_buffer_short; float *sles_buffer_float; int cont; double sample_rate; } fluid_opensles_audio_driver_t; static void opensles_callback(SLAndroidSimpleBufferQueueItf caller, void *pContext); static void process_fluid_buffer(fluid_opensles_audio_driver_t *dev); void fluid_opensles_audio_driver_settings(fluid_settings_t *settings) { } /* * new_fluid_opensles_audio_driver */ fluid_audio_driver_t * new_fluid_opensles_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { SLresult result; fluid_opensles_audio_driver_t *dev; double sample_rate; int period_size; int realtime_prio = 0; int is_sample_format_float; SLEngineItf engine_interface; dev = FLUID_NEW(fluid_opensles_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(*dev)); fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.realtime-prio", &realtime_prio); is_sample_format_float = fluid_settings_str_equal(settings, "audio.sample-format", "float"); dev->synth = synth; dev->is_sample_format_float = is_sample_format_float; dev->period_frames = period_size; dev->sample_rate = sample_rate; dev->cont = 1; result = slCreateEngine(&(dev->engine), 0, NULL, 0, NULL, NULL); if(!dev->engine) { FLUID_LOG(FLUID_ERR, "Failed to create OpenSLES connection"); goto error_recovery; } result = (*dev->engine)->Realize(dev->engine, SL_BOOLEAN_FALSE); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } result = (*dev->engine)->GetInterface(dev->engine, SL_IID_ENGINE, &engine_interface); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } result = (*engine_interface)->CreateOutputMix(engine_interface, &dev->output_mix_object, 0, 0, 0); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } result = (*dev->output_mix_object)->Realize(dev->output_mix_object, SL_BOOLEAN_FALSE); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } { SLDataLocator_AndroidSimpleBufferQueue loc_buffer_queue = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2 /* number of buffers */ }; SLAndroidDataFormat_PCM_EX format_pcm = { SL_ANDROID_DATAFORMAT_PCM_EX, NUM_CHANNELS, ((SLuint32) sample_rate) * 1000, is_sample_format_float ? SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16, is_sample_format_float ? SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN, is_sample_format_float ? SL_ANDROID_PCM_REPRESENTATION_FLOAT : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT }; SLDataSource audio_src = { &loc_buffer_queue, &format_pcm }; SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, dev->output_mix_object }; SLDataSink audio_sink = {&loc_outmix, NULL}; const SLInterfaceID ids1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; const SLboolean req1[] = {SL_BOOLEAN_TRUE}; result = (*engine_interface)->CreateAudioPlayer(engine_interface, &(dev->audio_player), &audio_src, &audio_sink, 1, ids1, req1); } if(result != SL_RESULT_SUCCESS) { goto error_recovery; } result = (*dev->audio_player)->Realize(dev->audio_player, SL_BOOLEAN_FALSE); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } result = (*dev->audio_player)->GetInterface(dev->audio_player, SL_IID_PLAY, &(dev->audio_player_interface)); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } result = (*dev->audio_player)->GetInterface(dev->audio_player, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &(dev->player_buffer_queue_interface)); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } if(dev->is_sample_format_float) { dev->sles_buffer_float = FLUID_ARRAY(float, dev->period_frames * NUM_CHANNELS); } else { dev->sles_buffer_short = FLUID_ARRAY(short, dev->period_frames * NUM_CHANNELS); } if(dev->sles_buffer_float == NULL && dev->sles_buffer_short == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory."); goto error_recovery; } result = (*dev->player_buffer_queue_interface)->RegisterCallback(dev->player_buffer_queue_interface, opensles_callback, dev); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } if(dev->is_sample_format_float) { (*dev->player_buffer_queue_interface)->Enqueue(dev->player_buffer_queue_interface, dev->sles_buffer_float, dev->period_frames * NUM_CHANNELS * sizeof(float)); } else { (*dev->player_buffer_queue_interface)->Enqueue(dev->player_buffer_queue_interface, dev->sles_buffer_short, dev->period_frames * NUM_CHANNELS * sizeof(short)); } (*dev->audio_player_interface)->SetCallbackEventsMask(dev->audio_player_interface, SL_PLAYEVENT_HEADATEND); result = (*dev->audio_player_interface)->SetPlayState(dev->audio_player_interface, SL_PLAYSTATE_PLAYING); if(result != SL_RESULT_SUCCESS) { goto error_recovery; } FLUID_LOG(FLUID_INFO, "Using OpenSLES driver."); return (fluid_audio_driver_t *) dev; error_recovery: delete_fluid_opensles_audio_driver((fluid_audio_driver_t *) dev); return NULL; } void delete_fluid_opensles_audio_driver(fluid_audio_driver_t *p) { fluid_opensles_audio_driver_t *dev = (fluid_opensles_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); dev->cont = 0; if(dev->audio_player) { (*dev->audio_player)->Destroy(dev->audio_player); } if(dev->output_mix_object) { (*dev->output_mix_object)->Destroy(dev->output_mix_object); } if(dev->engine) { (*dev->engine)->Destroy(dev->engine); } if(dev->sles_buffer_float) { FLUID_FREE(dev->sles_buffer_float); } if(dev->sles_buffer_short) { FLUID_FREE(dev->sles_buffer_short); } FLUID_FREE(dev); } void opensles_callback(SLAndroidSimpleBufferQueueItf caller, void *pContext) { fluid_opensles_audio_driver_t *dev = (fluid_opensles_audio_driver_t *) pContext; SLresult result; process_fluid_buffer(dev); if(dev->is_sample_format_float) { result = (*caller)->Enqueue( dev->player_buffer_queue_interface, dev->sles_buffer_float, dev->period_frames * sizeof(float) * NUM_CHANNELS); } else { result = (*caller)->Enqueue( dev->player_buffer_queue_interface, dev->sles_buffer_short, dev->period_frames * sizeof(short) * NUM_CHANNELS); } /* if (result != SL_RESULT_SUCCESS) { // Do not simply break at just one single insufficient buffer. Go on. } */ } void process_fluid_buffer(fluid_opensles_audio_driver_t *dev) { short *out_short = dev->sles_buffer_short; float *out_float = dev->sles_buffer_float; int period_frames = dev->period_frames; if(dev->is_sample_format_float) { fluid_synth_write_float(dev->synth, period_frames, out_float, 0, 2, out_float, 1, 2); } else { fluid_synth_write_s16(dev->synth, period_frames, out_short, 0, 2, out_short, 1, 2); } } #endif fluidsynth-2.1.1/src/drivers/fluid_oss.c000066400000000000000000000453351362231004000203050ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_oss.c * * Drivers for the Open (?) Sound System */ #include "fluid_synth.h" #include "fluid_midi.h" #include "fluid_adriver.h" #include "fluid_mdriver.h" #include "fluid_settings.h" #if OSS_SUPPORT #if defined(HAVE_SYS_SOUNDCARD_H) #include #elif defined(HAVE_LINUX_SOUNDCARD_H) #include #else #include #endif #include #include #include #include #include #include #include #include #define BUFFER_LENGTH 512 // Build issue on some systems (OSS 4.0)? #if !defined(SOUND_PCM_WRITE_CHANNELS) && defined(SNDCTL_DSP_CHANNELS) #define SOUND_PCM_WRITE_CHANNELS SNDCTL_DSP_CHANNELS #endif /** fluid_oss_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; fluid_synth_t *synth; fluid_audio_callback_t read; void *buffer; fluid_thread_t *thread; int cont; int dspfd; int buffer_size; int buffer_byte_size; int bigendian; int formats; int format; int caps; fluid_audio_func_t callback; void *data; float *buffers[2]; } fluid_oss_audio_driver_t; /* local utilities */ static int fluid_oss_set_queue_size(fluid_oss_audio_driver_t *dev, int ss, int ch, int qs, int bs); static fluid_thread_return_t fluid_oss_audio_run(void *d); static fluid_thread_return_t fluid_oss_audio_run2(void *d); typedef struct { fluid_midi_driver_t driver; int fd; fluid_thread_t *thread; int status; unsigned char buffer[BUFFER_LENGTH]; fluid_midi_parser_t *parser; } fluid_oss_midi_driver_t; static fluid_thread_return_t fluid_oss_midi_run(void *d); void fluid_oss_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "audio.oss.device", "/dev/dsp", 0); } /* * new_fluid_oss_audio_driver */ fluid_audio_driver_t * new_fluid_oss_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_oss_audio_driver_t *dev = NULL; int channels, sr, sample_size = 0, oss_format; struct stat devstat; int queuesize; double sample_rate; int periods, period_size; int realtime_prio = 0; char *devname = NULL; int format; dev = FLUID_NEW(fluid_oss_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_oss_audio_driver_t)); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.realtime-prio", &realtime_prio); dev->dspfd = -1; dev->synth = synth; dev->callback = NULL; dev->data = NULL; dev->cont = 1; dev->buffer_size = (int) period_size; queuesize = (int)(periods * period_size); if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { sample_size = 16; oss_format = AFMT_S16_LE; dev->read = fluid_synth_write_s16; dev->buffer_byte_size = dev->buffer_size * 4; } else if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) { sample_size = 32; oss_format = -1; dev->read = fluid_synth_write_float; dev->buffer_byte_size = dev->buffer_size * 8; } else { FLUID_LOG(FLUID_ERR, "Unknown sample format"); goto error_recovery; } dev->buffer = FLUID_MALLOC(dev->buffer_byte_size); if(dev->buffer == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } if(fluid_settings_dupstr(settings, "audio.oss.device", &devname) != FLUID_OK || !devname) /* ++ alloc device name */ { devname = FLUID_STRDUP("/dev/dsp"); if(devname == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } if(stat(devname, &devstat) == -1) { FLUID_LOG(FLUID_ERR, "Device <%s> does not exists", devname); goto error_recovery; } if((devstat.st_mode & S_IFCHR) != S_IFCHR) { FLUID_LOG(FLUID_ERR, "Device <%s> is not a device file", devname); goto error_recovery; } dev->dspfd = open(devname, O_WRONLY, 0); if(dev->dspfd == -1) { FLUID_LOG(FLUID_ERR, "Device <%s> could not be opened for writing: %s", devname, strerror(errno)); goto error_recovery; } if(fluid_oss_set_queue_size(dev, sample_size, 2, queuesize, period_size) < 0) { FLUID_LOG(FLUID_ERR, "Can't set device buffer size"); goto error_recovery; } format = oss_format; if(ioctl(dev->dspfd, SNDCTL_DSP_SETFMT, &oss_format) < 0) { FLUID_LOG(FLUID_ERR, "Can't set the sample format"); goto error_recovery; } if(oss_format != format) { FLUID_LOG(FLUID_ERR, "Can't set the sample format"); goto error_recovery; } channels = 2; if(ioctl(dev->dspfd, SOUND_PCM_WRITE_CHANNELS, &channels) < 0) { FLUID_LOG(FLUID_ERR, "Can't set the number of channels"); goto error_recovery; } if(channels != 2) { FLUID_LOG(FLUID_ERR, "Can't set the number of channels"); goto error_recovery; } sr = sample_rate; if(ioctl(dev->dspfd, SNDCTL_DSP_SPEED, &sr) < 0) { FLUID_LOG(FLUID_ERR, "Can't set the sample rate"); goto error_recovery; } if((sr < 0.95 * sample_rate) || (sr > 1.05 * sample_rate)) { FLUID_LOG(FLUID_ERR, "Can't set the sample rate"); goto error_recovery; } /* Create the audio thread */ dev->thread = new_fluid_thread("oss-audio", fluid_oss_audio_run, dev, realtime_prio, FALSE); if(!dev->thread) { goto error_recovery; } if(devname) { FLUID_FREE(devname); /* -- free device name */ } return (fluid_audio_driver_t *) dev; error_recovery: if(devname) { FLUID_FREE(devname); /* -- free device name */ } delete_fluid_oss_audio_driver((fluid_audio_driver_t *) dev); return NULL; } fluid_audio_driver_t * new_fluid_oss_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { fluid_oss_audio_driver_t *dev = NULL; int channels, sr; struct stat devstat; int queuesize; double sample_rate; int periods, period_size; char *devname = NULL; int realtime_prio = 0; int format; dev = FLUID_NEW(fluid_oss_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_oss_audio_driver_t)); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.realtime-prio", &realtime_prio); dev->dspfd = -1; dev->synth = NULL; dev->read = NULL; dev->callback = func; dev->data = data; dev->cont = 1; dev->buffer_size = (int) period_size; queuesize = (int)(periods * period_size); dev->buffer_byte_size = dev->buffer_size * 2 * 2; /* 2 channels * 16 bits audio */ if(fluid_settings_dupstr(settings, "audio.oss.device", &devname) != FLUID_OK || !devname) { devname = FLUID_STRDUP("/dev/dsp"); if(!devname) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } if(stat(devname, &devstat) == -1) { FLUID_LOG(FLUID_ERR, "Device <%s> does not exists", devname); goto error_recovery; } if((devstat.st_mode & S_IFCHR) != S_IFCHR) { FLUID_LOG(FLUID_ERR, "Device <%s> is not a device file", devname); goto error_recovery; } dev->dspfd = open(devname, O_WRONLY, 0); if(dev->dspfd == -1) { FLUID_LOG(FLUID_ERR, "Device <%s> could not be opened for writing: %s", devname, strerror(errno)); goto error_recovery; } if(fluid_oss_set_queue_size(dev, 16, 2, queuesize, period_size) < 0) { FLUID_LOG(FLUID_ERR, "Can't set device buffer size"); goto error_recovery; } format = AFMT_S16_LE; if(ioctl(dev->dspfd, SNDCTL_DSP_SETFMT, &format) < 0) { FLUID_LOG(FLUID_ERR, "Can't set the sample format"); goto error_recovery; } if(format != AFMT_S16_LE) { FLUID_LOG(FLUID_ERR, "Can't set the sample format"); goto error_recovery; } channels = 2; if(ioctl(dev->dspfd, SOUND_PCM_WRITE_CHANNELS, &channels) < 0) { FLUID_LOG(FLUID_ERR, "Can't set the number of channels"); goto error_recovery; } if(channels != 2) { FLUID_LOG(FLUID_ERR, "Can't set the number of channels"); goto error_recovery; } sr = sample_rate; if(ioctl(dev->dspfd, SNDCTL_DSP_SPEED, &sr) < 0) { FLUID_LOG(FLUID_ERR, "Can't set the sample rate"); goto error_recovery; } if((sr < 0.95 * sample_rate) || (sr > 1.05 * sample_rate)) { FLUID_LOG(FLUID_ERR, "Can't set the sample rate"); goto error_recovery; } /* allocate the buffers. FIXME!!! don't use interleaved samples */ dev->buffer = FLUID_MALLOC(dev->buffer_byte_size); dev->buffers[0] = FLUID_ARRAY(float, dev->buffer_size); dev->buffers[1] = FLUID_ARRAY(float, dev->buffer_size); if((dev->buffer == NULL) || (dev->buffers[0] == NULL) || (dev->buffers[1] == NULL)) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } /* Create the audio thread */ dev->thread = new_fluid_thread("oss-audio", fluid_oss_audio_run2, dev, realtime_prio, FALSE); if(!dev->thread) { goto error_recovery; } if(devname) { FLUID_FREE(devname); /* -- free device name */ } return (fluid_audio_driver_t *) dev; error_recovery: if(devname) { FLUID_FREE(devname); /* -- free device name */ } delete_fluid_oss_audio_driver((fluid_audio_driver_t *) dev); return NULL; } /* * delete_fluid_oss_audio_driver */ void delete_fluid_oss_audio_driver(fluid_audio_driver_t *p) { fluid_oss_audio_driver_t *dev = (fluid_oss_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); dev->cont = 0; if(dev->thread) { fluid_thread_join(dev->thread); delete_fluid_thread(dev->thread); } if(dev->dspfd >= 0) { close(dev->dspfd); } FLUID_FREE(dev->buffer); FLUID_FREE(dev); } /** * fluid_oss_set_queue_size * * Set the internal buffersize of the output device. * * @param ss Sample size in bits * @param ch Number of channels * @param qs The queue size in frames * @param bs The synthesis buffer size in frames */ int fluid_oss_set_queue_size(fluid_oss_audio_driver_t *dev, int ss, int ch, int qs, int bs) { unsigned int fragmentSize; unsigned int fragSizePower; unsigned int fragments; unsigned int fragmentsPower; fragmentSize = (unsigned int)(bs * ch * ss / 8); fragSizePower = 0; while(0 < fragmentSize) { fragmentSize = (fragmentSize >> 1); fragSizePower++; } fragSizePower--; fragments = (unsigned int)(qs / bs); if(fragments < 2) { fragments = 2; } /* make sure fragments is a power of 2 */ fragmentsPower = 0; while(0 < fragments) { fragments = (fragments >> 1); fragmentsPower++; } fragmentsPower--; fragments = (1 << fragmentsPower); fragments = (fragments << 16) + fragSizePower; return ioctl(dev->dspfd, SNDCTL_DSP_SETFRAGMENT, &fragments); } /* * fluid_oss_audio_run */ fluid_thread_return_t fluid_oss_audio_run(void *d) { fluid_oss_audio_driver_t *dev = (fluid_oss_audio_driver_t *) d; fluid_synth_t *synth = dev->synth; void *buffer = dev->buffer; int len = dev->buffer_size; /* it's as simple as that: */ while(dev->cont) { dev->read(synth, len, buffer, 0, 2, buffer, 1, 2); if(write(dev->dspfd, buffer, dev->buffer_byte_size) < 0) { FLUID_LOG(FLUID_ERR, "Error writing to OSS sound device: %s", g_strerror(errno)); break; } } FLUID_LOG(FLUID_DBG, "Audio thread finished"); return FLUID_THREAD_RETURN_VALUE; } /* * fluid_oss_audio_run */ fluid_thread_return_t fluid_oss_audio_run2(void *d) { fluid_oss_audio_driver_t *dev = (fluid_oss_audio_driver_t *) d; short *buffer = (short *) dev->buffer; float *left = dev->buffers[0]; float *right = dev->buffers[1]; int buffer_size = dev->buffer_size; int dither_index = 0; FLUID_LOG(FLUID_DBG, "Audio thread running"); /* it's as simple as that: */ while(dev->cont) { FLUID_MEMSET(left, 0, buffer_size * sizeof(float)); FLUID_MEMSET(right, 0, buffer_size * sizeof(float)); (*dev->callback)(dev->data, buffer_size, 0, NULL, 2, dev->buffers); fluid_synth_dither_s16(&dither_index, buffer_size, left, right, buffer, 0, 2, buffer, 1, 2); if(write(dev->dspfd, buffer, dev->buffer_byte_size) < 0) { FLUID_LOG(FLUID_ERR, "Error writing to OSS sound device: %s", g_strerror(errno)); break; } } FLUID_LOG(FLUID_DBG, "Audio thread finished"); return FLUID_THREAD_RETURN_VALUE; } void fluid_oss_midi_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "midi.oss.device", "/dev/midi", 0); } /* * new_fluid_oss_midi_driver */ fluid_midi_driver_t * new_fluid_oss_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { fluid_oss_midi_driver_t *dev; int realtime_prio = 0; char *device = NULL; /* not much use doing anything */ if(handler == NULL) { FLUID_LOG(FLUID_ERR, "Invalid argument"); return NULL; } /* allocate the device */ dev = FLUID_NEW(fluid_oss_midi_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_oss_midi_driver_t)); dev->fd = -1; dev->driver.handler = handler; dev->driver.data = data; /* allocate one event to store the input data */ dev->parser = new_fluid_midi_parser(); if(dev->parser == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } /* get the device name. if none is specified, use the default device. */ fluid_settings_dupstr(settings, "midi.oss.device", &device); /* ++ alloc device name */ if(device == NULL) { device = FLUID_STRDUP("/dev/midi"); if(!device) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } fluid_settings_getint(settings, "midi.realtime-prio", &realtime_prio); /* open the default hardware device. only use midi in. */ dev->fd = open(device, O_RDONLY, 0); if(dev->fd < 0) { perror(device); goto error_recovery; } if(fcntl(dev->fd, F_SETFL, O_NONBLOCK) == -1) { FLUID_LOG(FLUID_ERR, "Failed to set OSS MIDI device to non-blocking: %s", strerror(errno)); goto error_recovery; } dev->status = FLUID_MIDI_READY; /* create MIDI thread */ dev->thread = new_fluid_thread("oss-midi", fluid_oss_midi_run, dev, realtime_prio, FALSE); if(!dev->thread) { goto error_recovery; } if(device) { FLUID_FREE(device); /* ++ free device */ } return (fluid_midi_driver_t *) dev; error_recovery: if(device) { FLUID_FREE(device); /* ++ free device */ } delete_fluid_oss_midi_driver((fluid_midi_driver_t *) dev); return NULL; } /* * delete_fluid_oss_midi_driver */ void delete_fluid_oss_midi_driver(fluid_midi_driver_t *p) { fluid_oss_midi_driver_t *dev = (fluid_oss_midi_driver_t *) p; fluid_return_if_fail(dev != NULL); /* cancel the thread and wait for it before cleaning up */ dev->status = FLUID_MIDI_DONE; if(dev->thread) { fluid_thread_join(dev->thread); delete_fluid_thread(dev->thread); } if(dev->fd >= 0) { close(dev->fd); } delete_fluid_midi_parser(dev->parser); FLUID_FREE(dev); } /* * fluid_oss_midi_run */ fluid_thread_return_t fluid_oss_midi_run(void *d) { fluid_oss_midi_driver_t *dev = (fluid_oss_midi_driver_t *) d; fluid_midi_event_t *evt; struct pollfd fds; int n, i; /* go into a loop until someone tells us to stop */ dev->status = FLUID_MIDI_LISTENING; fds.fd = dev->fd; fds.events = POLLIN; fds.revents = 0; while(dev->status == FLUID_MIDI_LISTENING) { n = poll(&fds, 1, 100); if(n == 0) { continue; } if(n < 0) { FLUID_LOG(FLUID_ERR, "Error waiting for MIDI input: %s", strerror(errno)); break; } /* read new data */ n = read(dev->fd, dev->buffer, BUFFER_LENGTH); if(n == -EAGAIN) { continue; } if(n < 0) { perror("read"); FLUID_LOG(FLUID_ERR, "Failed to read the midi input"); break; } /* let the parser convert the data into events */ for(i = 0; i < n; i++) { evt = fluid_midi_parser_parse(dev->parser, dev->buffer[i]); if(evt != NULL) { /* send the event to the next link in the chain */ (*dev->driver.handler)(dev->driver.data, evt); } } } return FLUID_THREAD_RETURN_VALUE; } #endif /*#if OSS_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_portaudio.c000066400000000000000000000270211362231004000214770ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_portaudio.c * * Drivers for the PortAudio API : www.portaudio.com * Implementation files for PortAudio on each platform have to be added * * Stephane Letz (letz@grame.fr) Grame * 12/20/01 Adapdation for new audio drivers * * Josh Green * 2009-01-28 Overhauled for PortAudio 19 API and current FluidSynth API (was broken) */ #include "fluid_synth.h" #include "fluid_settings.h" #include "fluid_adriver.h" #if PORTAUDIO_SUPPORT #include /** fluid_portaudio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; fluid_synth_t *synth; fluid_audio_callback_t read; PaStream *stream; } fluid_portaudio_driver_t; static int fluid_portaudio_run(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); #define PORTAUDIO_DEFAULT_DEVICE "PortAudio Default" /** * Checks if device_num is a valid device and returns the name of the portaudio device. * A device is valid if it is an output device with at least 2 channels. * * @param device_num index of the portaudio device to check. * @param name_ptr if device_num is valid, set to a unique device name, ignored otherwise * * The name returned is unique for each num_device index, so this * name is useful to identify any available host audio device. * This name is convenient for audio.portaudio.device setting. * * The format of the name is: device_index:host_api_name:host_device_name * * example: 5:MME:SB PCI * * 5: is the portaudio device index. * MME: is the host API name. * SB PCI: is the host device name. * * @return #FLUID_OK if device_num is a valid output device, #FLUID_FAILED otherwise. * When #FLUID_OK, the name is returned in allocated memory. The caller must check * the name pointer for a valid memory allocation and should free the memory. */ static int fluid_portaudio_get_device_name(int device_num, char **name_ptr) { const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(device_num); if(deviceInfo->maxOutputChannels >= 2) { const PaHostApiInfo *hostInfo = Pa_GetHostApiInfo(deviceInfo->hostApi); /* The size of the buffer name for the following format: device_index:host_api_name:host_device_name. */ int i = device_num; size_t size = 0; do { size++; i = i / 10 ; } while(i); /* index size */ /* host API size + host device size + 2 separators + zero termination */ size += FLUID_STRLEN(hostInfo->name) + FLUID_STRLEN(deviceInfo->name) + 3u; *name_ptr = FLUID_MALLOC(size); if(*name_ptr) { /* the name is filled if allocation is successful */ FLUID_SPRINTF(*name_ptr, "%d:%s:%s", device_num, hostInfo->name, deviceInfo->name); } return FLUID_OK; /* device_num is a valid device */ } else { return FLUID_FAILED; /* device_num is an invalid device */ } } /** * Initializes "audio.portaudio.device" setting with an options list of unique device names * of available sound card devices. * @param settings pointer to settings. */ void fluid_portaudio_driver_settings(fluid_settings_t *settings) { int numDevices; PaError err; int i; fluid_settings_register_str(settings, "audio.portaudio.device", PORTAUDIO_DEFAULT_DEVICE, 0); fluid_settings_add_option(settings, "audio.portaudio.device", PORTAUDIO_DEFAULT_DEVICE); err = Pa_Initialize(); if(err != paNoError) { FLUID_LOG(FLUID_ERR, "Error initializing PortAudio driver: %s", Pa_GetErrorText(err)); return; } numDevices = Pa_GetDeviceCount(); if(numDevices < 0) { FLUID_LOG(FLUID_ERR, "PortAudio returned unexpected device count %d", numDevices); } else { for(i = 0; i < numDevices; i++) { char *name; if(fluid_portaudio_get_device_name(i, &name) == FLUID_OK) { /* the device i is a valid output device */ if(name) { /* registers this name in the option list */ fluid_settings_add_option(settings, "audio.portaudio.device", name); FLUID_FREE(name); } else { FLUID_LOG(FLUID_ERR, "Out of memory"); break; } } } } /* done with PortAudio for now, may get reopened later */ err = Pa_Terminate(); if(err != paNoError) { printf("PortAudio termination error: %s\n", Pa_GetErrorText(err)); } } /** * Creates the portaudio driver and opens the portaudio device * indicated by audio.portaudio.device setting. * * @param settings pointer to settings * @param synth the synthesizer instance * @return pointer to the driver on success, NULL otherwise. */ fluid_audio_driver_t * new_fluid_portaudio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_portaudio_driver_t *dev = NULL; PaStreamParameters outputParams; char *device = NULL; /* the portaudio device name to work with */ double sample_rate; /* intended sample rate */ int period_size; /* intended buffer size */ PaError err; dev = FLUID_NEW(fluid_portaudio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } err = Pa_Initialize(); if(err != paNoError) { FLUID_LOG(FLUID_ERR, "Error initializing PortAudio driver: %s", Pa_GetErrorText(err)); FLUID_FREE(dev); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_portaudio_driver_t)); dev->synth = synth; /* gets audio parameters from the settings */ fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_dupstr(settings, "audio.portaudio.device", &device); /* ++ alloc device name */ memset(&outputParams, 0, sizeof(outputParams)); outputParams.channelCount = 2; /* For stereo output */ outputParams.suggestedLatency = (PaTime)period_size / sample_rate; /* Locate the device if specified */ if(FLUID_STRCMP(device, PORTAUDIO_DEFAULT_DEVICE) != 0) { /* The intended device is not the default device name, so we search a device among available devices */ int numDevices; int i; numDevices = Pa_GetDeviceCount(); if(numDevices < 0) { FLUID_LOG(FLUID_ERR, "PortAudio returned unexpected device count %d", numDevices); goto error_recovery; } for(i = 0; i < numDevices; i++) { char *name; if(fluid_portaudio_get_device_name(i, &name) == FLUID_OK) { /* the device i is a valid output device */ if(name) { /* We see if the name corresponds to audio.portaudio.device */ char found = (FLUID_STRCMP(device, name) == 0); FLUID_FREE(name); if(found) { /* the device index is found */ outputParams.device = i; /* The search is finished */ break; } } else { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } } if(i == numDevices) { FLUID_LOG(FLUID_ERR, "PortAudio device '%s' was not found", device); goto error_recovery; } } else { /* the default device will be used */ outputParams.device = Pa_GetDefaultOutputDevice(); } /* The device is found. We set the sample format and the audio rendering function suited to this format. */ if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { outputParams.sampleFormat = paInt16; dev->read = fluid_synth_write_s16; } else if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) { outputParams.sampleFormat = paFloat32; dev->read = fluid_synth_write_float; } else { FLUID_LOG(FLUID_ERR, "Unknown sample format"); goto error_recovery; } /* PortAudio section */ /* Open an audio I/O stream. */ err = Pa_OpenStream(&dev->stream, NULL, /* Input parameters */ &outputParams, /* Output parameters */ sample_rate, period_size, paNoFlag, fluid_portaudio_run, /* callback */ dev); if(err != paNoError) { FLUID_LOG(FLUID_ERR, "Error opening PortAudio stream: %s", Pa_GetErrorText(err)); goto error_recovery; } err = Pa_StartStream(dev->stream); /* starts the I/O stream */ if(err != paNoError) { FLUID_LOG(FLUID_ERR, "Error starting PortAudio stream: %s", Pa_GetErrorText(err)); goto error_recovery; } if(device) { FLUID_FREE(device); /* -- free device name */ } return (fluid_audio_driver_t *)dev; error_recovery: if(device) { FLUID_FREE(device); /* -- free device name */ } delete_fluid_portaudio_driver((fluid_audio_driver_t *)dev); return NULL; } /* PortAudio callback * fluid_portaudio_run */ static int fluid_portaudio_run(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { fluid_portaudio_driver_t *dev = (fluid_portaudio_driver_t *)userData; /* it's as simple as that: */ dev->read(dev->synth, frameCount, output, 0, 2, output, 1, 2); return 0; } /* * delete_fluid_portaudio_driver */ void delete_fluid_portaudio_driver(fluid_audio_driver_t *p) { fluid_portaudio_driver_t *dev = (fluid_portaudio_driver_t *)p; PaError err; fluid_return_if_fail(dev != NULL); /* PortAudio section */ if(dev->stream) { Pa_CloseStream(dev->stream); } err = Pa_Terminate(); if(err != paNoError) { printf("PortAudio termination error: %s\n", Pa_GetErrorText(err)); } FLUID_FREE(dev); } #endif /* PORTAUDIO_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_pulse.c000066400000000000000000000210221362231004000206140ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_pulse.c * * Audio driver for PulseAudio. * */ #include "fluid_synth.h" #include "fluid_adriver.h" #include "fluid_settings.h" #if PULSE_SUPPORT #include #include /** fluid_pulse_audio_driver_t * * This structure should not be accessed directly. Use audio port * functions instead. */ typedef struct { fluid_audio_driver_t driver; pa_simple *pa_handle; fluid_audio_func_t callback; void *data; int buffer_size; fluid_thread_t *thread; int cont; float *left; float *right; float *buf; } fluid_pulse_audio_driver_t; static fluid_thread_return_t fluid_pulse_audio_run(void *d); static fluid_thread_return_t fluid_pulse_audio_run2(void *d); void fluid_pulse_audio_driver_settings(fluid_settings_t *settings) { fluid_settings_register_str(settings, "audio.pulseaudio.server", "default", 0); fluid_settings_register_str(settings, "audio.pulseaudio.device", "default", 0); fluid_settings_register_str(settings, "audio.pulseaudio.media-role", "music", 0); fluid_settings_register_int(settings, "audio.pulseaudio.adjust-latency", 1, 0, 1, FLUID_HINT_TOGGLED); } fluid_audio_driver_t * new_fluid_pulse_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { return new_fluid_pulse_audio_driver2(settings, NULL, synth); } fluid_audio_driver_t * new_fluid_pulse_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { fluid_pulse_audio_driver_t *dev; pa_sample_spec samplespec; pa_buffer_attr bufattr; double sample_rate; int period_size, period_bytes, adjust_latency; char *server = NULL; char *device = NULL; char *media_role = NULL; int realtime_prio = 0; int err; float *left = NULL, *right = NULL, *buf = NULL; dev = FLUID_NEW(fluid_pulse_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_pulse_audio_driver_t)); fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_dupstr(settings, "audio.pulseaudio.server", &server); /* ++ alloc server string */ fluid_settings_dupstr(settings, "audio.pulseaudio.device", &device); /* ++ alloc device string */ fluid_settings_dupstr(settings, "audio.pulseaudio.media-role", &media_role); /* ++ alloc media-role string */ fluid_settings_getint(settings, "audio.realtime-prio", &realtime_prio); fluid_settings_getint(settings, "audio.pulseaudio.adjust-latency", &adjust_latency); if(media_role != NULL) { if(FLUID_STRCMP(media_role, "") != 0) { g_setenv("PULSE_PROP_media.role", media_role, TRUE); } FLUID_FREE(media_role); /* -- free media_role string */ } if(server && FLUID_STRCMP(server, "default") == 0) { FLUID_FREE(server); /* -- free server string */ server = NULL; } if(device && FLUID_STRCMP(device, "default") == 0) { FLUID_FREE(device); /* -- free device string */ device = NULL; } dev->data = data; dev->callback = func; dev->cont = 1; dev->buffer_size = period_size; samplespec.format = PA_SAMPLE_FLOAT32NE; samplespec.channels = 2; samplespec.rate = sample_rate; period_bytes = period_size * sizeof(float) * 2; bufattr.maxlength = adjust_latency ? -1 : period_bytes; bufattr.tlength = period_bytes; bufattr.minreq = -1; bufattr.prebuf = -1; /* Just initialize to same value as tlength */ bufattr.fragsize = -1; /* Not used */ dev->pa_handle = pa_simple_new(server, "FluidSynth", PA_STREAM_PLAYBACK, device, "FluidSynth output", &samplespec, NULL, /* pa_channel_map */ &bufattr, &err); if(!dev->pa_handle) { FLUID_LOG(FLUID_ERR, "Failed to create PulseAudio connection"); goto error_recovery; } FLUID_LOG(FLUID_INFO, "Using PulseAudio driver"); if(func != NULL) { left = FLUID_ARRAY(float, period_size); right = FLUID_ARRAY(float, period_size); if(left == NULL || right == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory."); goto error_recovery; } } buf = FLUID_ARRAY(float, period_size * 2); if(buf == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory."); goto error_recovery; } dev->left = left; dev->right = right; dev->buf = buf; /* Create the audio thread */ dev->thread = new_fluid_thread("pulse-audio", func ? fluid_pulse_audio_run2 : fluid_pulse_audio_run, dev, realtime_prio, FALSE); if(!dev->thread) { goto error_recovery; } FLUID_FREE(server); /* -- free server string */ FLUID_FREE(device); /* -- free device string */ return (fluid_audio_driver_t *) dev; error_recovery: FLUID_FREE(server); /* -- free server string */ FLUID_FREE(device); /* -- free device string */ FLUID_FREE(left); FLUID_FREE(right); FLUID_FREE(buf); delete_fluid_pulse_audio_driver((fluid_audio_driver_t *) dev); return NULL; } void delete_fluid_pulse_audio_driver(fluid_audio_driver_t *p) { fluid_pulse_audio_driver_t *dev = (fluid_pulse_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); dev->cont = 0; if(dev->thread) { fluid_thread_join(dev->thread); delete_fluid_thread(dev->thread); } if(dev->pa_handle) { pa_simple_free(dev->pa_handle); } FLUID_FREE(dev->left); FLUID_FREE(dev->right); FLUID_FREE(dev->buf); FLUID_FREE(dev); } /* Thread without audio callback, more efficient */ static fluid_thread_return_t fluid_pulse_audio_run(void *d) { fluid_pulse_audio_driver_t *dev = (fluid_pulse_audio_driver_t *) d; float *buf = dev->buf; int buffer_size; int err; buffer_size = dev->buffer_size; while(dev->cont) { fluid_synth_write_float(dev->data, buffer_size, buf, 0, 2, buf, 1, 2); if(pa_simple_write(dev->pa_handle, buf, buffer_size * sizeof(float) * 2, &err) < 0) { FLUID_LOG(FLUID_ERR, "Error writing to PulseAudio connection."); break; } } /* while (dev->cont) */ return FLUID_THREAD_RETURN_VALUE; } static fluid_thread_return_t fluid_pulse_audio_run2(void *d) { fluid_pulse_audio_driver_t *dev = (fluid_pulse_audio_driver_t *) d; fluid_synth_t *synth = (fluid_synth_t *)(dev->data); float *left = dev->left, *right = dev->right, *buf = dev->buf; float *handle[2]; int buffer_size; int err; int i; buffer_size = dev->buffer_size; handle[0] = left; handle[1] = right; while(dev->cont) { FLUID_MEMSET(left, 0, buffer_size * sizeof(float)); FLUID_MEMSET(right, 0, buffer_size * sizeof(float)); (*dev->callback)(synth, buffer_size, 0, NULL, 2, handle); /* Interleave the floating point data */ for(i = 0; i < buffer_size; i++) { buf[i * 2] = left[i]; buf[i * 2 + 1] = right[i]; } if(pa_simple_write(dev->pa_handle, buf, buffer_size * sizeof(float) * 2, &err) < 0) { FLUID_LOG(FLUID_ERR, "Error writing to PulseAudio connection."); break; } } /* while (dev->cont) */ return FLUID_THREAD_RETURN_VALUE; } #endif /* PULSE_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_sdl2.c000066400000000000000000000147421362231004000203430ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * Copyright (C) 2018 Carlo Bramini * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_synth.h" #include "fluid_adriver.h" #include "fluid_settings.h" #if SDL2_SUPPORT #include "SDL.h" typedef struct { fluid_audio_driver_t driver; fluid_synth_t *synth; fluid_audio_callback_t write_ptr; SDL_AudioDeviceID devid; int frame_size; } fluid_sdl2_audio_driver_t; static void SDLAudioCallback(void *data, void *stream, int len) { fluid_sdl2_audio_driver_t *dev = (fluid_sdl2_audio_driver_t *)data; len /= dev->frame_size; dev->write_ptr(dev->synth, len, stream, 0, 2, stream, 1, 2); } void fluid_sdl2_audio_driver_settings(fluid_settings_t *settings) { int n, nDevs; fluid_settings_register_str(settings, "audio.sdl2.device", "default", 0); fluid_settings_add_option(settings, "audio.sdl2.device", "default"); if(!SDL_WasInit(SDL_INIT_AUDIO)) { FLUID_LOG(FLUID_ERR, "SDL2 not initialized"); return; } nDevs = SDL_GetNumAudioDevices(0); for(n = 0; n < nDevs; n++) { const char *dev_name = SDL_GetAudioDeviceName(n, 0); if(dev_name != NULL) { FLUID_LOG(FLUID_DBG, "SDL2 driver testing audio device: %s", dev_name); fluid_settings_add_option(settings, "audio.sdl2.device", dev_name); } } } /* * new_fluid_sdl2_audio_driver */ fluid_audio_driver_t * new_fluid_sdl2_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_sdl2_audio_driver_t *dev = NULL; fluid_audio_callback_t write_ptr; double sample_rate; int period_size, sample_size; SDL_AudioSpec aspec, rspec; char *device; const char *dev_name; /* Check if SDL library has been started */ if(!SDL_WasInit(SDL_INIT_AUDIO)) { FLUID_LOG(FLUID_ERR, "Failed to create SDL2 audio driver, because the audio subsystem of SDL2 is not initialized."); return NULL; } /* Retrieve the settings */ fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.period-size", &period_size); /* Lower values do not seem to give good results */ if(period_size < 1024) { period_size = 1024; } else { /* According to documentation, it MUST be a power of two */ if((period_size & (period_size - 1)) != 0) { FLUID_LOG(FLUID_ERR, "\"audio.period-size\" must be a power of 2 for SDL2"); return NULL; } } /* Clear the format buffer */ FLUID_MEMSET(&aspec, 0, sizeof(aspec)); /* Setup mixing frequency */ aspec.freq = (int)sample_rate; /* Check the format */ if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) { FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format"); sample_size = sizeof(float); write_ptr = fluid_synth_write_float; aspec.format = AUDIO_F32SYS; } else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format"); sample_size = sizeof(short); write_ptr = fluid_synth_write_s16; aspec.format = AUDIO_S16SYS; } else { FLUID_LOG(FLUID_ERR, "Unhandled sample format"); return NULL; } /* Compile the format buffer */ aspec.channels = 2; aspec.samples = aspec.channels * ((period_size + 7) & ~7); aspec.callback = (SDL_AudioCallback)SDLAudioCallback; /* Set default device to use */ device = NULL; dev_name = NULL; /* get the selected device name. if none is specified, use default device. */ if(fluid_settings_dupstr(settings, "audio.sdl2.device", &device) == FLUID_OK && device != NULL && device[0] != '\0') { int n, nDevs = SDL_GetNumAudioDevices(0); for(n = 0; n < nDevs; n++) { dev_name = SDL_GetAudioDeviceName(n, 0); if(FLUID_STRCASECMP(dev_name, device) == 0) { FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %s", dev_name); break; } } if(n >= nDevs) { FLUID_LOG(FLUID_DBG, "Audio device %s, using \"default\"", device); dev_name = NULL; } } if(device != NULL) { FLUID_FREE(device); } do { /* create and clear the driver data */ dev = FLUID_NEW(fluid_sdl2_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); break; } FLUID_MEMSET(dev, 0, sizeof(fluid_sdl2_audio_driver_t)); /* set device pointer to userdata */ aspec.userdata = dev; /* Save copy of synth */ dev->synth = synth; /* Save copy of other variables */ dev->write_ptr = write_ptr; dev->frame_size = sample_size * aspec.channels; /* Open audio device */ dev->devid = SDL_OpenAudioDevice(dev_name, 0, &aspec, &rspec, 0); if(!dev->devid) { FLUID_LOG(FLUID_ERR, "Failed to open audio device"); break; } /* Start to play */ SDL_PauseAudioDevice(dev->devid, 0); return (fluid_audio_driver_t *) dev; } while(0); delete_fluid_sdl2_audio_driver(&dev->driver); return NULL; } void delete_fluid_sdl2_audio_driver(fluid_audio_driver_t *d) { fluid_sdl2_audio_driver_t *dev = (fluid_sdl2_audio_driver_t *) d; if(dev != NULL) { if(dev->devid) { /* Stop audio and close */ SDL_PauseAudioDevice(dev->devid, 1); SDL_CloseAudioDevice(dev->devid); } FLUID_FREE(dev); } } #endif /* SDL2_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_sndmgr.c000066400000000000000000000247671362231004000210010ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_sndmgr.c * * Driver for MacOS Classic */ #if SNDMAN_SUPPORT #include "fluid_synth.h" #include "fluid_adriver.h" #include "fluid_settings.h" #include typedef struct { fluid_audio_driver_t driver; SndDoubleBufferHeader2 *doubleHeader; SndDoubleBackUPP doubleCallbackProc; SndChannelPtr channel; int callback_is_audio_func; void *data; fluid_audio_func_t callback; float *convbuffers[2]; int bufferByteSize; int bufferFrameSize; } fluid_sndmgr_audio_driver_t; void pascal fluid_sndmgr_callback(SndChannelPtr chan, SndDoubleBufferPtr doubleBuffer); Fixed fluid_sndmgr_double_to_fix(long double theLD); /* * generic new : returns error */ int start_fluid_sndmgr_audio_driver(fluid_settings_t *settings, fluid_sndmgr_audio_driver_t *dev, int buffer_size) { int i; SndDoubleBufferHeader2 *doubleHeader = NULL; SndDoubleBufferPtr doubleBuffer = NULL; OSErr err; SndChannelPtr channel = NULL; double sample_rate; fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); dev->doubleCallbackProc = NewSndDoubleBackProc(fluid_sndmgr_callback); /* the channel */ FLUID_LOG(FLUID_DBG, "FLUID-SndManager@2"); err = SndNewChannel(&channel, sampledSynth, initStereo, NULL); if((err != noErr) || (channel == NULL)) { FLUID_LOG(FLUID_ERR, "Failed to allocate a sound channel (error %i)", err); return err; } /* the double buffer struct */ FLUID_LOG(FLUID_DBG, "FLUID-SndManager@3"); doubleHeader = FLUID_NEW(SndDoubleBufferHeader2); if(doubleHeader == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return -1; } doubleHeader->dbhBufferPtr[0] = NULL; doubleHeader->dbhBufferPtr[1] = NULL; doubleHeader->dbhNumChannels = 2; doubleHeader->dbhSampleSize = 16; doubleHeader->dbhCompressionID = 0; doubleHeader->dbhPacketSize = 0; doubleHeader->dbhSampleRate = fluid_sndmgr_double_to_fix((long double) sample_rate); doubleHeader->dbhDoubleBack = dev->doubleCallbackProc; doubleHeader->dbhFormat = 0; /* prepare dev */ FLUID_LOG(FLUID_DBG, "FLUID-SndManager@4"); dev->doubleHeader = doubleHeader; dev->channel = channel; dev->bufferFrameSize = buffer_size; dev->bufferByteSize = buffer_size * 2 * 2; /* the 2 doublebuffers */ FLUID_LOG(FLUID_DBG, "FLUID-SndManager@5"); for(i = 0; i < 2; i++) { doubleBuffer = (SndDoubleBufferPtr) FLUID_MALLOC(sizeof(SndDoubleBuffer) + dev->bufferByteSize); if(doubleBuffer == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return -1; } doubleBuffer->dbNumFrames = 0; doubleBuffer->dbFlags = 0; doubleBuffer->dbUserInfo[0] = (long) dev; doubleHeader->dbhBufferPtr[i] = doubleBuffer; CallSndDoubleBackProc(doubleHeader->dbhDoubleBack, channel, doubleBuffer); } /* start */ FLUID_LOG(FLUID_DBG, "FLUID-SndManager@6"); err = SndPlayDoubleBuffer(channel, (SndDoubleBufferHeader *)doubleHeader); if(err != noErr) { FLUID_LOG(FLUID_ERR, "Failed to start the sound driver (error %i)", err); return err; } FLUID_LOG(FLUID_DBG, "FLUID-SndManager@7"); return 0; } /* * new_fluid_sndmgr_audio_driver * This implementation used the 16bit format. */ fluid_audio_driver_t * new_fluid_sndmgr_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_sndmgr_audio_driver_t *dev = NULL; int period_size, periods, buffer_size; /* check the format */ if(!fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { FLUID_LOG(FLUID_ERR, "Unhandled sample format"); return NULL; } /* compute buffer size */ fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getint(settings, "audio.periods", &periods); buffer_size = period_size * periods; /* allocated dev */ dev = FLUID_NEW(fluid_sndmgr_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_sndmgr_audio_driver_t)); dev->callback_is_audio_func = false; dev->data = (void *)synth; dev->callback = NULL; if(start_fluid_sndmgr_audio_driver(settings, dev, buffer_size) != 0) { delete_fluid_sndmgr_audio_driver((fluid_audio_driver_t *)dev); return NULL; } return (fluid_audio_driver_t *)dev; } /* * new_fluid_sndmgr_audio_driver2 * * This implementation used the audio_func float format, with * conversion from float to 16bits in the driver. */ fluid_audio_driver_t * new_fluid_sndmgr_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, void *data) { fluid_sndmgr_audio_driver_t *dev = NULL; int period_size, periods, buffer_size; /* compute buffer size */ fluid_settings_getint(settings, "audio.period-size", &period_size); fluid_settings_getint(settings, "audio.periods", &periods); buffer_size = period_size * periods; /* allocated dev */ dev = FLUID_NEW(fluid_sndmgr_audio_driver_t); if(dev == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_sndmgr_audio_driver_t)); /* allocate the conversion buffers */ dev->convbuffers[0] = FLUID_ARRAY(float, buffer_size); dev->convbuffers[1] = FLUID_ARRAY(float, buffer_size); if((dev->convbuffers[0] == NULL) || (dev->convbuffers[1] == NULL)) { FLUID_LOG(FLUID_PANIC, "Out of memory"); goto error_recovery; } dev->callback_is_audio_func = true; dev->data = data; dev->callback = func; if(start_fluid_sndmgr_audio_driver(settings, dev, buffer_size) != 0) { goto error_recovery; } return (fluid_audio_driver_t *)dev; error_recovery: delete_fluid_sndmgr_audio_driver((fluid_audio_driver_t *)dev); return NULL; } /* * delete_fluid_sndmgr_audio_driver */ void delete_fluid_sndmgr_audio_driver(fluid_audio_driver_t *p) { fluid_sndmgr_audio_driver_t *dev = (fluid_sndmgr_audio_driver_t *) p; fluid_return_if_fail(dev != NULL); if(dev->channel != NULL) { SndDisposeChannel(dev->channel, 1); } if(dev->doubleCallbackProc != NULL) { DisposeRoutineDescriptor(dev->doubleCallbackProc); } if(dev->doubleHeader != NULL) { FLUID_FREE(dev->doubleHeader->dbhBufferPtr[0]); FLUID_FREE(dev->doubleHeader->dbhBufferPtr[1]); FLUID_FREE(dev->doubleHeader); } FLUID_FREE(dev->convbuffers[0]); FLUID_FREE(dev->convbuffers[1]); FLUID_FREE(dev); } /* * fluid_sndmgr_callback * */ void pascal fluid_sndmgr_callback(SndChannelPtr chan, SndDoubleBufferPtr doubleBuffer) { fluid_sndmgr_audio_driver_t *dev; signed short *buf; float *left; float *right; float v; int i, k, buffer_size; dev = (fluid_sndmgr_audio_driver_t *) doubleBuffer->dbUserInfo[0]; buf = (signed short *)doubleBuffer->dbSoundData; buffer_size = dev->bufferFrameSize; if(dev->callback_is_audio_func) { /* float API : conversion to signed short */ left = dev->convbuffers[0]; right = dev->convbuffers[1]; FLUID_MEMSET(left, 0, buffer_size * sizeof(float)); FLUID_MEMSET(right, 0, buffer_size * sizeof(float)); (*dev->callback)(dev->data, buffer_size, 0, NULL, 2, dev->convbuffers); for(i = 0, k = 0; i < buffer_size; i++) { v = 32767.0f * left[i]; fluid_clip(v, -32768.0f, 32767.0f); buf[k++] = (signed short) v; v = 32767.0f * right[i]; fluid_clip(v, -32768.0f, 32767.0f); buf[k++] = (signed short) v; } } else { /* let the synth do the conversion */ fluid_synth_write_s16((fluid_synth_t *)dev->data, buffer_size, buf, 0, 2, buf, 1, 2); } doubleBuffer->dbFlags = doubleBuffer->dbFlags | dbBufferReady; doubleBuffer->dbNumFrames = buffer_size; } /* * fluid_sndmgr_double_to_fix * * A Fixed number is of the type 12345.67890. It is 32 bits in size with the * high order bits representing the significant value (that before the point) * and the lower 16 bits representing the fractional part of the number. * The Sound Manager further complicates matters by using Fixed numbers, but * needing to represent numbers larger than what the Fixed is capable of. * To do this the Sound Manager treats the sign bit as having the value 32768 * which will cause any number greater or equal to 32768 to look like it is * negative. * This routine is designed to "do the right thing" and convert any long double * into the Fixed number it represents. * long double is the input type because AIFF files use extended80 numbers and * there are routines that will convert from an extended80 to a long double. * A long double has far greater precision than a Fixed, so any number whose * significant or fraction is larger than 65535 will not convert correctly. */ #define _MAX_VALUE 65535 #define _BITS_PER_BYTE 8 Fixed fluid_sndmgr_double_to_fix(long double theLD) { unsigned long theResult = 0; unsigned short theSignificant = 0, theFraction = 0; if(theLD < _MAX_VALUE) { theSignificant = theLD; theFraction = theLD - theSignificant; if(theFraction > _MAX_VALUE) { /* Won't be able to convert */ theSignificant = 0; theFraction = 0; } } theResult |= theSignificant; theResult = theResult << (sizeof(unsigned short) * _BITS_PER_BYTE); theResult |= theFraction; return theResult; } #endif fluidsynth-2.1.1/src/drivers/fluid_waveout.c000066400000000000000000000242641362231004000211710ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * Copyright (C) 2018 Carlo Bramini * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_synth.h" #include "fluid_adriver.h" #include "fluid_settings.h" #if WAVEOUT_SUPPORT #include #define NOBITMAP #include /* Number of buffers in the chain */ #define NB_SOUND_BUFFERS 4 /* Milliseconds of a single sound buffer */ #define MS_BUFFER_LENGTH 20 typedef struct { fluid_audio_driver_t driver; fluid_synth_t *synth; fluid_audio_callback_t write_ptr; HWAVEOUT hWaveOut; WAVEHDR waveHeader[NB_SOUND_BUFFERS]; int sample_size; int num_frames; HANDLE hThread; DWORD dwThread; int nQuit; HANDLE hQuit; } fluid_waveout_audio_driver_t; /* Thread for playing sample buffers */ static DWORD WINAPI fluid_waveout_synth_thread(void *data) { fluid_waveout_audio_driver_t *dev; WAVEHDR *pWave; MSG msg; int code; /* Forces creation of message queue */ PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); for(;;) { code = GetMessage(&msg, NULL, 0, 0); if(code < 0) { FLUID_LOG(FLUID_ERR, "fluid_waveout_synth_thread: GetMessage() failed."); break; } if(msg.message == WM_CLOSE) { break; } switch(msg.message) { case MM_WOM_DONE: pWave = (WAVEHDR *)msg.lParam; dev = (fluid_waveout_audio_driver_t *)pWave->dwUser; if(dev->nQuit > 0) { /* Release the sample buffer */ waveOutUnprepareHeader((HWAVEOUT)msg.wParam, pWave, sizeof(WAVEHDR)); if(--dev->nQuit == 0) { SetEvent(dev->hQuit); } } else { dev->write_ptr(dev->synth, dev->num_frames, pWave->lpData, 0, 2, pWave->lpData, 1, 2); waveOutWrite((HWAVEOUT)msg.wParam, pWave, sizeof(WAVEHDR)); } break; } } return 0; } void fluid_waveout_audio_driver_settings(fluid_settings_t *settings) { UINT n, nDevs = waveOutGetNumDevs(); #ifdef _UNICODE char dev_name[MAXPNAMELEN]; #endif fluid_settings_register_str(settings, "audio.waveout.device", "default", 0); fluid_settings_add_option(settings, "audio.waveout.device", "default"); for(n = 0; n < nDevs; n++) { WAVEOUTCAPS caps; MMRESULT res; res = waveOutGetDevCaps(n, &caps, sizeof(caps)); if(res == MMSYSERR_NOERROR) { #ifdef _UNICODE WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, dev_name, MAXPNAMELEN, 0, 0); FLUID_LOG(FLUID_DBG, "Testing audio device: %s", dev_name); fluid_settings_add_option(settings, "audio.waveout.device", dev_name); #else FLUID_LOG(FLUID_DBG, "Testing audio device: %s", caps.szPname); fluid_settings_add_option(settings, "audio.waveout.device", caps.szPname); #endif } } } /* * new_fluid_waveout_audio_driver */ fluid_audio_driver_t * new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) { fluid_waveout_audio_driver_t *dev = NULL; fluid_audio_callback_t write_ptr; double sample_rate; int periods, period_size, frequency, sample_size; LPSTR ptrBuffer; int lenBuffer; int device; int i; WAVEFORMATEX wfx; char dev_name[MAXPNAMELEN]; MMRESULT errCode; /* Retrieve the settings */ fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); fluid_settings_getint(settings, "audio.periods", &periods); fluid_settings_getint(settings, "audio.period-size", &period_size); /* Clear the format buffer */ ZeroMemory(&wfx, sizeof(WAVEFORMATEX)); /* check the format */ if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) { FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format"); sample_size = sizeof(float); write_ptr = fluid_synth_write_float; wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; } else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) { FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format"); sample_size = sizeof(short); write_ptr = fluid_synth_write_s16; wfx.wFormatTag = WAVE_FORMAT_PCM; } else { FLUID_LOG(FLUID_ERR, "Unhandled sample format"); return NULL; } /* Set frequency to integer */ frequency = (int)sample_rate; /* Compile the format buffer */ wfx.nChannels = 2; wfx.wBitsPerSample = sample_size * 8; wfx.nSamplesPerSec = frequency; wfx.nBlockAlign = sample_size * wfx.nChannels; wfx.nAvgBytesPerSec = frequency * wfx.nBlockAlign; /* Calculate the length of a single buffer */ lenBuffer = (MS_BUFFER_LENGTH * wfx.nAvgBytesPerSec + 999) / 1000; /* Round to 8-bytes size */ lenBuffer = (lenBuffer + 7) & ~7; /* create and clear the driver data */ dev = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(fluid_waveout_audio_driver_t) + lenBuffer * NB_SOUND_BUFFERS); if(dev == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } /* Save copy of synth */ dev->synth = synth; /* Save copy of other variables */ dev->write_ptr = write_ptr; dev->sample_size = sample_size; /* Calculate the number of frames in a block */ dev->num_frames = lenBuffer / wfx.nBlockAlign; /* Set default device to use */ device = WAVE_MAPPER; /* get the selected device name. if none is specified, use default device. */ if(fluid_settings_copystr(settings, "audio.waveout.device", dev_name, MAXPNAMELEN) == FLUID_OK && dev_name[0] != '\0') { UINT nDevs = waveOutGetNumDevs(); UINT n; #ifdef _UNICODE WCHAR lpwDevName[MAXPNAMELEN]; MultiByteToWideChar(CP_UTF8, 0, dev_name, -1, lpwDevName, MAXPNAMELEN); #endif for(n = 0; n < nDevs; n++) { WAVEOUTCAPS caps; MMRESULT res; res = waveOutGetDevCaps(n, &caps, sizeof(caps)); if(res == MMSYSERR_NOERROR) { #ifdef _UNICODE if(wcsicmp(lpwDevName, caps.szPname) == 0) #else if(FLUID_STRCASECMP(dev_name, caps.szPname) == 0) #endif { FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %s", dev_name); device = n; break; } } } } do { dev->hQuit = CreateEvent(NULL, FALSE, FALSE, NULL); if(dev->hQuit == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create quit event"); break; } /* Create thread which processes re-adding SYSEX buffers */ dev->hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) fluid_waveout_synth_thread, dev, 0, &dev->dwThread); if(dev->hThread == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create waveOut thread"); break; } errCode = waveOutOpen(&dev->hWaveOut, device, &wfx, (DWORD_PTR)dev->dwThread, 0, CALLBACK_THREAD); if(errCode != MMSYSERR_NOERROR) { FLUID_LOG(FLUID_ERR, "Failed to open waveOut device"); break; } /* Get pointer to sound buffer memory */ ptrBuffer = (LPSTR)(dev + 1); /* Setup the sample buffers */ for(i = 0; i < NB_SOUND_BUFFERS; i++) { /* Clear the sample buffer */ memset(ptrBuffer, 0, lenBuffer); /* Clear descriptor buffer */ memset(dev->waveHeader + i, 0, sizeof(WAVEHDR)); /* Compile descriptor buffer */ dev->waveHeader[i].lpData = ptrBuffer; dev->waveHeader[i].dwBufferLength = lenBuffer; dev->waveHeader[i].dwUser = (DWORD_PTR)dev; waveOutPrepareHeader(dev->hWaveOut, &dev->waveHeader[i], sizeof(WAVEHDR)); ptrBuffer += lenBuffer; } /* Play the sample buffers */ for(i = 0; i < NB_SOUND_BUFFERS; i++) { waveOutWrite(dev->hWaveOut, &dev->waveHeader[i], sizeof(WAVEHDR)); } return (fluid_audio_driver_t *) dev; } while(0); delete_fluid_waveout_audio_driver(&dev->driver); return NULL; } void delete_fluid_waveout_audio_driver(fluid_audio_driver_t *d) { fluid_waveout_audio_driver_t *dev = (fluid_waveout_audio_driver_t *) d; fluid_return_if_fail(dev != NULL); /* release all the allocated resources */ if(dev->hWaveOut != NULL) { dev->nQuit = NB_SOUND_BUFFERS; WaitForSingleObject(dev->hQuit, INFINITE); waveOutClose(dev->hWaveOut); } if(dev->hThread != NULL) { PostThreadMessage(dev->dwThread, WM_CLOSE, 0, 0); WaitForSingleObject(dev->hThread, INFINITE); CloseHandle(dev->hThread); } if(dev->hQuit != NULL) { CloseHandle(dev->hQuit); } HeapFree(GetProcessHeap(), 0, dev); } #endif /* WAVEOUT_SUPPORT */ fluidsynth-2.1.1/src/drivers/fluid_winmidi.c000066400000000000000000000247061362231004000211400ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* fluid_winmidi.c * * Driver for Windows MIDI * * NOTE: Unfortunately midiInAddBuffer(), for SYSEX data, should not be called * from within the MIDI input callback, despite many examples contrary to that * on the Internet. Some MIDI devices will deadlock. Therefore we add MIDIHDR * pointers to a queue and re-add them in a separate thread. Lame-o API! :( */ #include "fluidsynth_priv.h" #if WINMIDI_SUPPORT #include "fluid_midi.h" #include "fluid_mdriver.h" #include "fluid_settings.h" #define MIDI_SYSEX_MAX_SIZE 512 #define MIDI_SYSEX_BUF_COUNT 16 typedef struct { fluid_midi_driver_t driver; HMIDIIN hmidiin; /* MIDI HDR for SYSEX buffer */ MIDIHDR sysExHdrs[MIDI_SYSEX_BUF_COUNT]; /* Thread for SYSEX re-add thread */ HANDLE hThread; DWORD dwThread; /* Sysex data buffer */ unsigned char sysExBuf[MIDI_SYSEX_BUF_COUNT * MIDI_SYSEX_MAX_SIZE]; } fluid_winmidi_driver_t; #define msg_type(_m) ((unsigned char)(_m & 0xf0)) #define msg_chan(_m) ((unsigned char)(_m & 0x0f)) #define msg_p1(_m) ((_m >> 8) & 0x7f) #define msg_p2(_m) ((_m >> 16) & 0x7f) static char * fluid_winmidi_input_error(char *strError, MMRESULT no) { #ifdef _UNICODE WCHAR wStr[MAXERRORLENGTH]; midiInGetErrorText(no, wStr, MAXERRORLENGTH); WideCharToMultiByte(CP_UTF8, 0, wStr, -1, strError, MAXERRORLENGTH, 0, 0); #else midiInGetErrorText(no, strError, MAXERRORLENGTH); #endif return strError; } static void CALLBACK fluid_winmidi_callback(HMIDIIN hmi, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { fluid_winmidi_driver_t *dev = (fluid_winmidi_driver_t *) dwInstance; fluid_midi_event_t event; LPMIDIHDR pMidiHdr; unsigned char *data; unsigned int msg_param = (unsigned int) dwParam1; switch(wMsg) { case MIM_OPEN: break; case MIM_CLOSE: break; case MIM_DATA: event.type = msg_type(msg_param); event.channel = msg_chan(msg_param); if(event.type != PITCH_BEND) { event.param1 = msg_p1(msg_param); event.param2 = msg_p2(msg_param); } else /* Pitch bend is a 14 bit value */ { event.param1 = (msg_p2(msg_param) << 7) | msg_p1(msg_param); event.param2 = 0; } (*dev->driver.handler)(dev->driver.data, &event); break; case MIM_LONGDATA: /* SYSEX data */ if(dev->hThread == NULL) { break; } pMidiHdr = (LPMIDIHDR)dwParam1; data = (unsigned char *)(pMidiHdr->lpData); /* We only process complete SYSEX messages (discard those that are too small or too large) */ if(pMidiHdr->dwBytesRecorded > 2 && data[0] == 0xF0 && data[pMidiHdr->dwBytesRecorded - 1] == 0xF7) { fluid_midi_event_set_sysex(&event, pMidiHdr->lpData + 1, pMidiHdr->dwBytesRecorded - 2, FALSE); (*dev->driver.handler)(dev->driver.data, &event); } PostThreadMessage(dev->dwThread, MM_MIM_LONGDATA, 0, dwParam1); break; case MIM_ERROR: break; case MIM_LONGERROR: break; case MIM_MOREDATA: break; } } void fluid_winmidi_midi_driver_settings(fluid_settings_t *settings) { MMRESULT res; MIDIINCAPS in_caps; UINT i, num; fluid_settings_register_str(settings, "midi.winmidi.device", "default", 0); num = midiInGetNumDevs(); if(num > 0) { fluid_settings_add_option(settings, "midi.winmidi.device", "default"); for(i = 0; i < num; i++) { res = midiInGetDevCaps(i, &in_caps, sizeof(MIDIINCAPS)); if(res == MMSYSERR_NOERROR) { fluid_settings_add_option(settings, "midi.winmidi.device", in_caps.szPname); } } } } /* Thread for re-adding SYSEX buffers */ static DWORD WINAPI fluid_winmidi_add_sysex_thread(void *data) { fluid_winmidi_driver_t *dev = (fluid_winmidi_driver_t *)data; MSG msg; int code; for(;;) { code = GetMessage(&msg, NULL, 0, 0); if(code < 0) { FLUID_LOG(FLUID_ERR, "fluid_winmidi_add_sysex_thread: GetMessage() failed."); break; } if(msg.message == WM_CLOSE) { break; } switch(msg.message) { case MM_MIM_LONGDATA: midiInAddBuffer(dev->hmidiin, (LPMIDIHDR)msg.lParam, sizeof(MIDIHDR)); break; } } return 0; } /* * new_fluid_winmidi_driver */ fluid_midi_driver_t * new_fluid_winmidi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *data) { fluid_winmidi_driver_t *dev; MIDIHDR *hdr; MMRESULT res; UINT i, num, midi_num = 0; MIDIINCAPS in_caps; char strError[MAXERRORLENGTH]; char dev_name[MAXPNAMELEN]; /* not much use doing anything */ if(handler == NULL) { FLUID_LOG(FLUID_ERR, "Invalid argument"); return NULL; } /* get the device name. if none is specified, use the default device. */ if(fluid_settings_copystr(settings, "midi.winmidi.device", dev_name, MAXPNAMELEN) != FLUID_OK) { FLUID_LOG(FLUID_DBG, "No MIDI in device selected, using \"default\""); FLUID_STRCPY(dev_name, "default"); } /* check if there any midi devices installed */ num = midiInGetNumDevs(); if(num == 0) { FLUID_LOG(FLUID_ERR, "no MIDI in devices found"); return NULL; } /* find the device */ if(FLUID_STRCASECMP("default", dev_name) != 0) { for(i = 0; i < num; i++) { res = midiInGetDevCaps(i, &in_caps, sizeof(MIDIINCAPS)); if(res == MMSYSERR_NOERROR) { FLUID_LOG(FLUID_DBG, "Testing midi device: %s\n", in_caps.szPname); if(FLUID_STRCASECMP(dev_name, in_caps.szPname) == 0) { FLUID_LOG(FLUID_DBG, "Selected midi device number: %d\n", i); midi_num = i; break; } } } if(midi_num != i) { FLUID_LOG(FLUID_ERR, "Device <%s> does not exists", dev_name); return NULL; } } dev = FLUID_MALLOC(sizeof(fluid_winmidi_driver_t)); if(dev == NULL) { return NULL; } FLUID_MEMSET(dev, 0, sizeof(fluid_winmidi_driver_t)); dev->hmidiin = NULL; dev->driver.handler = handler; dev->driver.data = data; /* try opening the device */ res = midiInOpen(&dev->hmidiin, midi_num, (DWORD_PTR) fluid_winmidi_callback, (DWORD_PTR) dev, CALLBACK_FUNCTION); if(res != MMSYSERR_NOERROR) { FLUID_LOG(FLUID_ERR, "Couldn't open MIDI input: %s (error %d)", fluid_winmidi_input_error(strError, res), res); goto error_recovery; } /* Prepare and add SYSEX buffers */ for(i = 0; i < MIDI_SYSEX_BUF_COUNT; i++) { hdr = &dev->sysExHdrs[i]; hdr->lpData = (LPSTR)&dev->sysExBuf[i * MIDI_SYSEX_MAX_SIZE]; hdr->dwBufferLength = MIDI_SYSEX_MAX_SIZE; /* Prepare a buffer for SYSEX data and add it */ res = midiInPrepareHeader(dev->hmidiin, hdr, sizeof(MIDIHDR)); if(res == MMSYSERR_NOERROR) { res = midiInAddBuffer(dev->hmidiin, hdr, sizeof(MIDIHDR)); if(res != MMSYSERR_NOERROR) { FLUID_LOG(FLUID_WARN, "Failed to prepare MIDI SYSEX buffer: %s (error %d)", fluid_winmidi_input_error(strError, res), res); midiInUnprepareHeader(dev->hmidiin, hdr, sizeof(MIDIHDR)); } } else FLUID_LOG(FLUID_WARN, "Failed to prepare MIDI SYSEX buffer: %s (error %d)", fluid_winmidi_input_error(strError, res), res); } /* Create thread which processes re-adding SYSEX buffers */ dev->hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE) fluid_winmidi_add_sysex_thread, dev, 0, &dev->dwThread); if(dev->hThread == NULL) { FLUID_LOG(FLUID_ERR, "Failed to create SYSEX buffer processing thread"); goto error_recovery; } /* Start the MIDI input interface */ if(midiInStart(dev->hmidiin) != MMSYSERR_NOERROR) { FLUID_LOG(FLUID_ERR, "Failed to start the MIDI input. MIDI input not available."); goto error_recovery; } return (fluid_midi_driver_t *) dev; error_recovery: delete_fluid_winmidi_driver((fluid_midi_driver_t *) dev); return NULL; } /* * delete_fluid_winmidi_driver */ void delete_fluid_winmidi_driver(fluid_midi_driver_t *p) { int i; fluid_winmidi_driver_t *dev = (fluid_winmidi_driver_t *) p; fluid_return_if_fail(dev != NULL); if(dev->hThread != NULL) { PostThreadMessage(dev->dwThread, WM_CLOSE, 0, 0); WaitForSingleObject(dev->hThread, INFINITE); CloseHandle(dev->hThread); dev->hThread = NULL; } if(dev->hmidiin != NULL) { midiInStop(dev->hmidiin); midiInReset(dev->hmidiin); for(i = 0; i < MIDI_SYSEX_BUF_COUNT; i++) { MIDIHDR *hdr = &dev->sysExHdrs[i]; if ((hdr->dwFlags & MHDR_PREPARED)) { midiInUnprepareHeader(dev->hmidiin, hdr, sizeof(MIDIHDR)); } } midiInClose(dev->hmidiin); } FLUID_FREE(dev); } #endif /* WINMIDI_SUPPORT */ fluidsynth-2.1.1/src/fluidsynth.c000066400000000000000000000773601362231004000170340ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sys.h" #if !defined(WIN32) && !defined(MACINTOSH) #define _GNU_SOURCE #endif #if defined(HAVE_GETOPT_H) #include #define GETOPT_SUPPORT 1 #endif #ifdef LIBINSTPATCH_SUPPORT #include #endif #include "fluid_lash.h" #ifdef SYSTEMD_SUPPORT #include #endif #if SDL2_SUPPORT #include #endif void print_usage(void); void print_help(fluid_settings_t *settings); void print_welcome(void); void print_configure(void); /* * the globals */ fluid_cmd_handler_t *cmd_handler = NULL; int option_help = 0; /* set to 1 if "-o help" is specified */ /* Process a command line option -o setting=value, for example: -o synth.polyhony=16 */ int process_o_cmd_line_option(fluid_settings_t *settings, char *optarg) { char *val; int hints; int ival; for(val = optarg; *val != '\0'; val++) { if(*val == '=') { *val++ = 0; break; } } /* did user request list of settings */ if(FLUID_STRCMP(optarg, "help") == 0) { option_help = 1; return FLUID_OK; } if(FLUID_STRCMP(optarg, "") == 0) { fprintf(stderr, "Invalid -o option (name part is empty)\n"); return FLUID_FAILED; } switch(fluid_settings_get_type(settings, optarg)) { case FLUID_NUM_TYPE: if(fluid_settings_setnum(settings, optarg, atof(val)) != FLUID_OK) { fprintf(stderr, "Failed to set floating point parameter '%s'\n", optarg); return FLUID_FAILED; } break; case FLUID_INT_TYPE: if(fluid_settings_get_hints(settings, optarg, &hints) == FLUID_OK && hints & FLUID_HINT_TOGGLED) { if(FLUID_STRCASECMP(val, "yes") == 0 || FLUID_STRCASECMP(val, "true") == 0 || FLUID_STRCASECMP(val, "t") == 0) { ival = 1; } else { ival = atoi(val); } } else { ival = atoi(val); } if(fluid_settings_setint(settings, optarg, ival) != FLUID_OK) { fprintf(stderr, "Failed to set integer parameter '%s'\n", optarg); return FLUID_FAILED; } break; case FLUID_STR_TYPE: if(fluid_settings_setstr(settings, optarg, val) != FLUID_OK) { fprintf(stderr, "Failed to set string parameter '%s'\n", optarg); return FLUID_FAILED; } break; default: fprintf(stderr, "Setting parameter '%s' not found\n", optarg); return FLUID_FAILED; } return FLUID_OK; } static void print_pretty_int(int i) { if(i == INT_MAX) { printf("MAXINT"); } else if(i == INT_MIN) { printf("MININT"); } else { printf("%d", i); } } typedef struct { int count; /* Total count of options */ int curindex; /* Current index in options */ } OptionBag; /* Function to display each string option value */ static void settings_option_foreach_func(void *data, const char *name, const char *option) { OptionBag *bag = data; bag->curindex++; if(bag->curindex < bag->count) { printf("'%s',", option); } else { printf("'%s'", option); } } /* fluid_settings_foreach function for displaying option help "-o help" */ static void settings_foreach_func(void *data, const char *name, int type) { fluid_settings_t *settings = (fluid_settings_t *)data; double dmin, dmax, ddef; int imin, imax, idef, hints; char *defstr; int count; OptionBag bag; switch(type) { case FLUID_NUM_TYPE: fluid_settings_getnum_range(settings, name, &dmin, &dmax); fluid_settings_getnum_default(settings, name, &ddef); printf("%-24s FLOAT [min=%0.3f, max=%0.3f, def=%0.3f]\n", name, dmin, dmax, ddef); break; case FLUID_INT_TYPE: fluid_settings_getint_range(settings, name, &imin, &imax); fluid_settings_getint_default(settings, name, &idef); fluid_settings_get_hints(settings, name, &hints); if(!(hints & FLUID_HINT_TOGGLED)) { printf("%-24s INT [min=", name); print_pretty_int(imin); printf(", max="); print_pretty_int(imax); printf(", def="); print_pretty_int(idef); printf("]\n"); } else { printf("%-24s BOOL [def=%s]\n", name, idef ? "True" : "False"); } break; case FLUID_STR_TYPE: printf("%-24s STR", name); fluid_settings_getstr_default(settings, name, &defstr); count = fluid_settings_option_count(settings, name); if(defstr || count > 0) { if(defstr && count > 0) { printf(" [def='%s' vals:", defstr); } else if(defstr) { printf(" [def='%s'", defstr); } else { printf(" [vals:"); } if(count > 0) { bag.count = count; bag.curindex = 0; fluid_settings_foreach_option(settings, name, &bag, settings_option_foreach_func); } printf("]\n"); } else { printf("\n"); } break; case FLUID_SET_TYPE: printf("%-24s SET\n", name); break; } } /* Output options for a setting string to stdout */ static void show_settings_str_options(fluid_settings_t *settings, char *name) { OptionBag bag; bag.count = fluid_settings_option_count(settings, name); bag.curindex = 0; fluid_settings_foreach_option(settings, name, &bag, settings_option_foreach_func); printf("\n"); } static void fast_render_loop(fluid_settings_t *settings, fluid_synth_t *synth, fluid_player_t *player) { fluid_file_renderer_t *renderer; renderer = new_fluid_file_renderer(synth); if(!renderer) { return; } while(fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) { if(fluid_file_renderer_process_block(renderer) != FLUID_OK) { break; } } delete_fluid_file_renderer(renderer); } /* * main * Process initialization steps in the following order: 1)creating the settings. 2)reading/setting all options in command line. 3)creating the synth. 4)loading the soundfonts specified in command line (multiple soundfonts loading is possible). 5)create the audio driver (if not fast rendering). 6)create the router. 7)create the midi driver connected to the router. 8)create a player and add it any midifile specified in command line. (multiple midifiles loading is possible). 9)loading a default soundfont if needed before starting the player. 10)create a command handler. 11)reading the configuration file and submit it to the command handler. 12)create a tcp shell if any requested. 13)create a synchronous user shell if interactive. 14)entering fast rendering loop if requested. */ int main(int argc, char **argv) { fluid_settings_t *settings; int result = -1; int arg1 = 1; char buf[512]; int c, i; int interactive = 1; int quiet = 0; int midi_in = 1; fluid_player_t *player = NULL; fluid_midi_router_t *router = NULL; fluid_midi_driver_t *mdriver = NULL; fluid_audio_driver_t *adriver = NULL; fluid_synth_t *synth = NULL; #ifdef NETWORK_SUPPORT fluid_server_t *server = NULL; int with_server = 0; #endif char *config_file = NULL; int audio_groups = 0; int audio_channels = 0; int dump = 0; int fast_render = 0; static const char optchars[] = "a:C:c:dE:f:F:G:g:hijK:L:lm:nO:o:p:qR:r:sT:Vvz:"; #ifdef HAVE_LASH int connect_lash = 1; int enabled_lash = 0; /* set to TRUE if lash gets enabled */ fluid_lash_args_t *lash_args; lash_args = fluid_lash_extract_args(&argc, &argv); #endif #if SDL2_SUPPORT if(SDL_Init(SDL_INIT_AUDIO) != 0) { fprintf(stderr, "Warning: Unable to initialize SDL2 Audio: %s", SDL_GetError()); } else { atexit(SDL_Quit); } #endif /* create the settings */ settings = new_fluid_settings(); /* reading / setting options from the command line */ #ifdef GETOPT_SUPPORT /* pre section of GETOPT supported argument handling */ opterr = 0; while(1) { int option_index = 0; static struct option long_options[] = { {"audio-bufcount", 1, 0, 'c'}, {"audio-bufsize", 1, 0, 'z'}, {"audio-channels", 1, 0, 'L'}, {"audio-driver", 1, 0, 'a'}, {"audio-file-endian", 1, 0, 'E'}, {"audio-file-format", 1, 0, 'O'}, {"audio-file-type", 1, 0, 'T'}, {"audio-groups", 1, 0, 'G'}, {"chorus", 1, 0, 'C'}, {"connect-jack-outputs", 0, 0, 'j'}, {"disable-lash", 0, 0, 'l'}, {"dump", 0, 0, 'd'}, {"fast-render", 1, 0, 'F'}, {"gain", 1, 0, 'g'}, {"help", 0, 0, 'h'}, {"load-config", 1, 0, 'f'}, {"midi-channels", 1, 0, 'K'}, {"midi-driver", 1, 0, 'm'}, {"no-midi-in", 0, 0, 'n'}, {"no-shell", 0, 0, 'i'}, {"option", 1, 0, 'o'}, {"portname", 1, 0, 'p'}, {"quiet", 0, 0, 'q'}, {"reverb", 1, 0, 'R'}, {"sample-rate", 1, 0, 'r'}, {"server", 0, 0, 's'}, {"verbose", 0, 0, 'v'}, {"version", 0, 0, 'V'}, {0, 0, 0, 0} }; c = getopt_long(argc, argv, optchars, long_options, &option_index); if(c == -1) { break; } #else /* "pre" section to non getopt argument handling */ for(i = 1; i < argc; i++) { char *optarg; /* Skip non switch arguments (assume they are file names) */ if((argv[i][0] != '-') || (argv[i][1] == '\0')) { break; } c = argv[i][1]; optarg = strchr(optchars, c); /* find the option character in optchars */ if(optarg && optarg[1] == ':') /* colon follows if switch argument expected */ { if(++i >= argc) { printf("Option -%c requires an argument\n", c); print_usage(); goto cleanup; } else { optarg = argv[i]; if(optarg[0] == '-') { printf("Expected argument to option -%c found switch instead\n", c); print_usage(); goto cleanup; } } } else { optarg = ""; } #endif switch(c) { #ifdef GETOPT_SUPPORT case 0: /* shouldn't normally happen, a long option's flag is set to NULL */ printf("option %s", long_options[option_index].name); if(optarg) { printf(" with arg %s", optarg); } printf("\n"); break; #endif case 'a': if(FLUID_STRCMP(optarg, "help") == 0) { printf("-a options (audio driver):\n "); show_settings_str_options(settings, "audio.driver"); result = 0; goto cleanup; } else { fluid_settings_setstr(settings, "audio.driver", optarg); } break; case 'C': if((optarg != NULL) && ((FLUID_STRCMP(optarg, "0") == 0) || (FLUID_STRCMP(optarg, "no") == 0))) { fluid_settings_setint(settings, "synth.chorus.active", FALSE); } else { fluid_settings_setint(settings, "synth.chorus.active", TRUE); } break; case 'c': fluid_settings_setint(settings, "audio.periods", atoi(optarg)); break; case 'd': dump = 1; break; case 'E': if(FLUID_STRCMP(optarg, "help") == 0) { printf("-E options (audio file byte order):\n "); show_settings_str_options(settings, "audio.file.endian"); #if LIBSNDFILE_SUPPORT printf("\nauto: Use audio file format's default endian byte order\n" "cpu: Use CPU native byte order\n"); #else printf("\nNOTE: No libsndfile support!\n" "cpu: Use CPU native byte order\n"); #endif result = 0; goto cleanup; } else { fluid_settings_setstr(settings, "audio.file.endian", optarg); } break; case 'f': config_file = optarg; break; case 'F': fluid_settings_setstr(settings, "audio.file.name", optarg); fast_render = 1; break; case 'G': audio_groups = atoi(optarg); break; case 'g': fluid_settings_setnum(settings, "synth.gain", atof(optarg)); break; case 'h': print_welcome(); print_help(settings); result = 0; goto cleanup; break; case 'i': interactive = 0; break; case 'j': fluid_settings_setint(settings, "audio.jack.autoconnect", 1); fluid_settings_setint(settings, "midi.autoconnect", 1); break; case 'K': fluid_settings_setint(settings, "synth.midi-channels", atoi(optarg)); break; case 'L': audio_channels = atoi(optarg); fluid_settings_setint(settings, "synth.audio-channels", audio_channels); break; case 'l': /* disable LASH */ #ifdef HAVE_LASH connect_lash = 0; #endif break; case 'm': if(FLUID_STRCMP(optarg, "help") == 0) { printf("-m options (MIDI driver):\n "); show_settings_str_options(settings, "midi.driver"); result = 0; goto cleanup; } else { fluid_settings_setstr(settings, "midi.driver", optarg); } break; case 'n': midi_in = 0; break; case 'O': if(FLUID_STRCMP(optarg, "help") == 0) { printf("-O options (audio file format):\n "); show_settings_str_options(settings, "audio.file.format"); #if LIBSNDFILE_SUPPORT printf("\ns8, s16, s24, s32: Signed PCM audio of the given number of bits\n"); printf("float, double: 32 bit and 64 bit floating point audio\n"); printf("u8: Unsigned 8 bit audio\n"); #else printf("\nNOTE: No libsndfile support!\n"); #endif result = 0; goto cleanup; } else { fluid_settings_setstr(settings, "audio.file.format", optarg); } break; case 'o': if(process_o_cmd_line_option(settings, optarg) != FLUID_OK) { goto cleanup; } break; case 'p' : fluid_settings_setstr(settings, "midi.portname", optarg); break; case 'q': quiet = 1; #if defined(WIN32) /* Windows logs to stdout by default, so make sure anything * lower than PANIC is not printed either */ fluid_set_log_function(FLUID_ERR, NULL, NULL); fluid_set_log_function(FLUID_WARN, NULL, NULL); fluid_set_log_function(FLUID_INFO, NULL, NULL); fluid_set_log_function(FLUID_DBG, NULL, NULL); #endif break; case 'R': if((optarg != NULL) && ((FLUID_STRCMP(optarg, "0") == 0) || (FLUID_STRCMP(optarg, "no") == 0))) { fluid_settings_setint(settings, "synth.reverb.active", FALSE); } else { fluid_settings_setint(settings, "synth.reverb.active", TRUE); } break; case 'r': fluid_settings_setnum(settings, "synth.sample-rate", atof(optarg)); break; case 's': #ifdef NETWORK_SUPPORT with_server = 1; #else printf("\nNOTE: FluidSynth compiled without network support, unable to start server!\n"); #endif break; case 'T': if(FLUID_STRCMP(optarg, "help") == 0) { printf("-T options (audio file type):\n "); show_settings_str_options(settings, "audio.file.type"); #if LIBSNDFILE_SUPPORT printf("\nauto: Determine type from file name extension, defaults to \"wav\"\n"); #else printf("\nNOTE: No libsndfile support!\n"); #endif result = 0; goto cleanup; } else { fluid_settings_setstr(settings, "audio.file.type", optarg); } break; case 'V': print_welcome(); print_configure(); result = 0; goto cleanup; break; case 'v': fluid_settings_setint(settings, "synth.verbose", TRUE); fluid_set_log_function(FLUID_DBG, fluid_default_log_function, NULL); break; case 'z': fluid_settings_setint(settings, "audio.period-size", atoi(optarg)); break; #ifdef GETOPT_SUPPORT case '?': printf("Unknown option %c\n", optopt); print_usage(); goto cleanup; break; default: printf("?? getopt returned character code 0%o ??\n", c); break; #else /* Non getopt default case */ default: printf("Unknown switch '%c'\n", c); print_usage(); goto cleanup; break; #endif } /* end of switch statement */ } /* end of loop */ #ifdef GETOPT_SUPPORT arg1 = optind; #else arg1 = i; #endif if (!quiet) { print_welcome(); } /* option help requested? "-o help" */ if(option_help) { printf("FluidSynth settings:\n"); fluid_settings_foreach(settings, settings, settings_foreach_func); result = 0; goto cleanup; } #ifdef WIN32 SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); #endif #ifdef HAVE_LASH /* connect to the lash server */ if(connect_lash) { enabled_lash = fluid_lash_connect(lash_args); fluid_settings_setint(settings, "lash.enable", enabled_lash ? 1 : 0); } #endif /* The 'groups' setting is relevant for LADSPA operation and channel mapping * in rvoice_mixer. * If not given, set number groups to number of audio channels, because * they are the same (there is nothing between synth output and 'sound card') */ if((audio_groups == 0) && (audio_channels != 0)) { audio_groups = audio_channels; } if(audio_groups != 0) { fluid_settings_setint(settings, "synth.audio-groups", audio_groups); } if(fast_render) { midi_in = 0; /* disable MIDI driver creation */ interactive = 0; /* disable user shell creation */ #ifdef NETWORK_SUPPORT with_server = 0; /* disable tcp server shell creation */ #endif fluid_settings_setstr(settings, "player.timing-source", "sample"); fluid_settings_setint(settings, "synth.lock-memory", 0); } /* create the synthesizer */ synth = new_fluid_synth(settings); if(synth == NULL) { fprintf(stderr, "Failed to create the synthesizer\n"); goto cleanup; } /* load the soundfonts (check that all non options are SoundFont or MIDI files) */ for(i = arg1; i < argc; i++) { if(fluid_is_soundfont(argv[i])) { if(fluid_synth_sfload(synth, argv[i], 1) == -1) { fprintf(stderr, "Failed to load the SoundFont %s\n", argv[i]); } } else if(!fluid_is_midifile(argv[i])) { fprintf(stderr, "Parameter '%s' not a SoundFont or MIDI file or error occurred identifying it.\n", argv[i]); } } router = new_fluid_midi_router( settings, dump ? fluid_midi_dump_postrouter : fluid_synth_handle_midi_event, (void *)synth); if(router == NULL) { fprintf(stderr, "Failed to create the MIDI input router; no MIDI input\n" "will be available. You can access the synthesizer \n" "through the console.\n"); } /* start the midi router and link it to the synth */ if(midi_in && router != NULL) { /* In dump mode, text output is generated for events going into and out of the router. * The example dump functions are put into the chain before and after the router.. */ mdriver = new_fluid_midi_driver( settings, dump ? fluid_midi_dump_prerouter : fluid_midi_router_handle_midi_event, (void *) router); if(mdriver == NULL) { fprintf(stderr, "Failed to create the MIDI thread; no MIDI input\n" "will be available. You can access the synthesizer \n" "through the console.\n"); } } /* play the midi files, if any */ for(i = arg1; i < argc; i++) { if((argv[i][0] != '-') && fluid_is_midifile(argv[i])) { if(player == NULL) { player = new_fluid_player(synth); if(player == NULL) { fprintf(stderr, "Failed to create the midifile player.\n" "Continuing without a player.\n"); break; } if(router != NULL) { fluid_player_set_playback_callback(player, fluid_midi_router_handle_midi_event, router); } } fluid_player_add(player, argv[i]); } } /* start the player */ if(player != NULL) { /* Try to load the default soundfont, if no soundfont specified */ if(fluid_synth_get_sfont(synth, 0) == NULL) { char *s; if(fluid_settings_dupstr(settings, "synth.default-soundfont", &s) != FLUID_OK) { s = NULL; } if((s != NULL) && (s[0] != '\0')) { fluid_synth_sfload(synth, s, 1); } FLUID_FREE(s); } fluid_player_play(player); } /* try to load and execute the user or system configuration file */ cmd_handler = new_fluid_cmd_handler(synth, router); if(cmd_handler == NULL) { fprintf(stderr, "Failed to create the command handler\n"); goto cleanup; } if(config_file != NULL) { if(fluid_source(cmd_handler, config_file) < 0) { fprintf(stderr, "Failed to execute user provided command configuration file '%s'\n", config_file); } } else if(fluid_get_userconf(buf, sizeof(buf)) != NULL) { fluid_source(cmd_handler, buf); } else if(fluid_get_sysconf(buf, sizeof(buf)) != NULL) { fluid_source(cmd_handler, buf); } /* run the server, if requested */ #ifdef NETWORK_SUPPORT if(with_server) { server = new_fluid_server(settings, synth, router); if(server == NULL) { fprintf(stderr, "Failed to create the server.\n" "Continuing without it.\n"); } #ifdef SYSTEMD_SUPPORT else { sd_notify(0, "READY=1"); } #endif } #endif #ifdef HAVE_LASH if(enabled_lash) { fluid_lash_create_thread(synth); } #endif /* fast rendering audio file, if requested */ if(fast_render) { char *filename; if(player == NULL) { fprintf(stderr, "No midi file specified!\n"); goto cleanup; } fluid_settings_dupstr(settings, "audio.file.name", &filename); if (!quiet) { printf("Rendering audio to file '%s'..\n", filename); } if(filename) { FLUID_FREE(filename); } fast_render_loop(settings, synth, player); } else /* start the synthesis thread */ { adriver = new_fluid_audio_driver(settings, synth); if(adriver == NULL) { fprintf(stderr, "Failed to create the audio driver\n"); goto cleanup; } /* run the shell */ if(interactive) { printf("Type 'help' for help topics.\n\n"); /* In dump mode we set the prompt to "". The UI cannot easily * handle lines, which don't end with CR. Changing the prompt * cannot be done through a command, because the current shell * does not handle empty arguments. The ordinary case is dump == * 0. */ fluid_settings_setstr(settings, "shell.prompt", dump ? "" : "> "); fluid_usershell(settings, cmd_handler); /* this is a synchronous shell */ } } result = 0; cleanup: #ifdef NETWORK_SUPPORT if(server != NULL) { /* if the user typed 'quit' in the shell, kill the server */ if(!interactive) { fluid_server_join(server); } #ifdef SYSTEMD_SUPPORT sd_notify(0, "STOPPING=1"); #endif delete_fluid_server(server); } #endif /* NETWORK_SUPPORT */ if(cmd_handler != NULL) { delete_fluid_cmd_handler(cmd_handler); } if(player != NULL) { /* if the user typed 'quit' in the shell, stop the player */ if(interactive) { fluid_player_stop(player); } if(adriver != NULL || !fluid_settings_str_equal(settings, "player.timing-source", "sample")) { /* if no audio driver and sample timers are used, nothing makes the player advance */ fluid_player_join(player); } } delete_fluid_audio_driver(adriver); delete_fluid_player(player); delete_fluid_midi_driver(mdriver); delete_fluid_midi_router(router); delete_fluid_synth(synth); delete_fluid_settings(settings); return result; } /* * print_usage */ void print_usage() { fprintf(stderr, "Usage: fluidsynth [options] [soundfonts]\n"); fprintf(stderr, "Try -h for help.\n"); } void print_welcome() { printf("FluidSynth runtime version %s\n" "Copyright (C) 2000-2020 Peter Hanappe and others.\n" "Distributed under the LGPL license.\n" "SoundFont(R) is a registered trademark of E-mu Systems, Inc.\n\n", fluid_version_str()); } void print_configure() { puts("FluidSynth executable version " FLUIDSYNTH_VERSION); puts("Sample type=" #ifdef WITH_FLOAT "float" #else "double" #endif ); } /* * print_help */ void print_help(fluid_settings_t *settings) { char *audio_options; char *midi_options; audio_options = fluid_settings_option_concat(settings, "audio.driver", NULL); midi_options = fluid_settings_option_concat(settings, "midi.driver", NULL); printf("Usage: \n"); printf(" fluidsynth [options] [soundfonts] [midifiles]\n"); printf("Possible options:\n"); printf(" -a, --audio-driver=[label]\n" " The name of the audio driver to use.\n" " Valid values: %s\n", audio_options ? audio_options : "ERROR"); printf(" -c, --audio-bufcount=[count]\n" " Number of audio buffers\n"); printf(" -C, --chorus\n" " Turn the chorus on or off [0|1|yes|no, default = on]\n"); printf(" -d, --dump\n" " Dump incoming and outgoing MIDI events to stdout\n"); printf(" -E, --audio-file-endian\n" " Audio file endian for fast rendering or aufile driver (\"help\" for list)\n"); printf(" -f, --load-config\n" " Load command configuration file (shell commands)\n"); printf(" -F, --fast-render=[file]\n" " Render MIDI file to raw audio data and store in [file]\n"); printf(" -g, --gain\n" " Set the master gain [0 < gain < 10, default = 0.2]\n"); printf(" -G, --audio-groups\n" " Defines the number of LADSPA audio nodes\n"); printf(" -h, --help\n" " Print out this help summary\n"); printf(" -i, --no-shell\n" " Don't read commands from the shell [default = yes]\n"); printf(" -j, --connect-jack-outputs\n" " Attempt to connect the jack outputs to the physical ports\n"); printf(" -K, --midi-channels=[num]\n" " The number of midi channels [default = 16]\n"); #ifdef HAVE_LASH printf(" -l, --disable-lash\n" " Don't connect to LASH server\n"); #endif printf(" -L, --audio-channels=[num]\n" " The number of stereo audio channels [default = 1]\n"); printf(" -m, --midi-driver=[label]\n" " The name of the midi driver to use.\n" " Valid values: %s\n", midi_options ? midi_options : "ERROR"); printf(" -n, --no-midi-in\n" " Don't create a midi driver to read MIDI input events [default = yes]\n"); printf(" -o\n" " Define a setting, -o name=value (\"-o help\" to dump current values)\n"); printf(" -O, --audio-file-format\n" " Audio file format for fast rendering or aufile driver (\"help\" for list)\n"); printf(" -p, --portname=[label]\n" " Set MIDI port name (alsa_seq, coremidi drivers)\n"); printf(" -q, --quiet\n" " Do not print welcome message or other informational output\n" " (Windows only: also suppress all log messages lower than PANIC\n"); printf(" -r, --sample-rate\n" " Set the sample rate\n"); printf(" -R, --reverb\n" " Turn the reverb on or off [0|1|yes|no, default = on]\n"); printf(" -s, --server\n" " Start FluidSynth as a server process\n"); printf(" -T, --audio-file-type\n" " Audio file type for fast rendering or aufile driver (\"help\" for list)\n"); printf(" -v, --verbose\n" " Print out verbose messages about midi events (synth.verbose=1) as well as other debug messages\n"); printf(" -V, --version\n" " Show version of program\n"); printf(" -z, --audio-bufsize=[size]\n" " Size of each audio buffer\n"); if(audio_options) { FLUID_FREE(audio_options); } if(midi_options) { FLUID_FREE(midi_options); } } fluidsynth-2.1.1/src/gentables/000077500000000000000000000000001362231004000164265ustar00rootroot00000000000000fluidsynth-2.1.1/src/gentables/CMakeLists.txt000066400000000000000000000022221362231004000211640ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.1) # remove $CC from the current environment and by that force cmake to look for a (working) C compiler, # which hopefully will be the host compiler unset(ENV{CC}) # also unset $CFLAGS to avoid passing any cross compilation flags to the host compiler unset(ENV{CFLAGS}) # linker flags as well unset(ENV{LDFLAGS}) project (gentables C) set ( CMAKE_BUILD_TYPE Debug ) # hardcode ".exe" as suffix to the binary, else in case of cross-platform cross-compiling the calling cmake will not know the suffix used here and fail to find the binary set ( CMAKE_EXECUTABLE_SUFFIX ".exe" ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) # Add the executable that generates the table add_executable( make_tables make_tables.c gen_conv.c gen_rvoice_dsp.c) if ( WIN32 ) add_definitions ( -D_USE_MATH_DEFINES -D_CRT_SECURE_NO_WARNINGS ) else ( WIN32 ) target_link_libraries (make_tables "m") endif () fluidsynth-2.1.1/src/gentables/gen_conv.c000066400000000000000000000045631362231004000204000ustar00rootroot00000000000000 #include "utils/fluid_conv_tables.h" #include "make_tables.h" /* conversion tables */ static double fluid_ct2hz_tab[FLUID_CENTS_HZ_SIZE]; static double fluid_cb2amp_tab[FLUID_CB_AMP_SIZE]; static double fluid_concave_tab[FLUID_VEL_CB_SIZE]; static double fluid_convex_tab[FLUID_VEL_CB_SIZE]; static double fluid_pan_tab[FLUID_PAN_SIZE]; /* * void fluid_synth_init * * Does all the initialization for this module. */ static void fluid_conversion_config(void) { int i; double x; for(i = 0; i < FLUID_CENTS_HZ_SIZE; i++) { // 6,875 is just a factor that we already multiply into the lookup table to save // that multiplication in fluid_ct2hz_real() // 6.875 Hz because 440Hz / 2^6 fluid_ct2hz_tab[i] = 6.875 * powl(2.0, (double) i / 1200.0); } /* centibels to amplitude conversion * Note: SF2.01 section 8.1.3: Initial attenuation range is * between 0 and 144 dB. Therefore a negative attenuation is * not allowed. */ for(i = 0; i < FLUID_CB_AMP_SIZE; i++) { fluid_cb2amp_tab[i] = powl(10.0, (double) i / -200.0); } /* initialize the conversion tables (see fluid_mod.c fluid_mod_get_value cases 4 and 8) */ /* concave unipolar positive transform curve */ fluid_concave_tab[0] = 0.0; fluid_concave_tab[FLUID_VEL_CB_SIZE - 1] = 1.0; /* convex unipolar positive transform curve */ fluid_convex_tab[0] = 0; fluid_convex_tab[FLUID_VEL_CB_SIZE - 1] = 1.0; /* There seems to be an error in the specs. The equations are implemented according to the pictures on SF2.01 page 73. */ for(i = 1; i < FLUID_VEL_CB_SIZE - 1; i++) { x = (-200.0 / FLUID_PEAK_ATTENUATION) * log((double)(i * i) / ((FLUID_VEL_CB_SIZE - 1) * (FLUID_VEL_CB_SIZE - 1))) / M_LN10; fluid_convex_tab[i] = (1.0 - x); fluid_concave_tab[(FLUID_VEL_CB_SIZE - 1) - i] = x; } /* initialize the pan conversion table */ x = M_PI / 2.0 / (FLUID_PAN_SIZE - 1.0); for(i = 0; i < FLUID_PAN_SIZE; i++) { fluid_pan_tab[i] = sin(i * x); } } void gen_conv_table(FILE *fp) { /* Calculate the values */ fluid_conversion_config(); /* fluid_ct2hz_tab */ EMIT_ARRAY(fp, fluid_ct2hz_tab); EMIT_ARRAY(fp, fluid_cb2amp_tab); EMIT_ARRAY(fp, fluid_concave_tab); EMIT_ARRAY(fp, fluid_convex_tab); EMIT_ARRAY(fp, fluid_pan_tab); } fluidsynth-2.1.1/src/gentables/gen_rvoice_dsp.c000066400000000000000000000054211362231004000215620ustar00rootroot00000000000000 #include "rvoice/fluid_rvoice_dsp_tables.h" #include "make_tables.h" /* Linear interpolation table (2 coefficients centered on 1st) */ static double interp_coeff_linear[FLUID_INTERP_MAX][2]; /* 4th order (cubic) interpolation table (4 coefficients centered on 2nd) */ static double interp_coeff[FLUID_INTERP_MAX][4]; /* 7th order interpolation (7 coefficients centered on 3rd) */ static double sinc_table7[FLUID_INTERP_MAX][SINC_INTERP_ORDER]; static double cb_interp_coeff_linear(int y, int x) { return interp_coeff_linear[y][x]; } static double cb_interp_coeff (int y, int x) { return interp_coeff[y][x]; } static double cb_sinc_table7 (int y, int x) { return sinc_table7[y][x]; } /* Initializes interpolation tables */ void fluid_rvoice_dsp_config(void) { int i, i2; double x, v; double i_shifted; /* Initialize the coefficients for the interpolation. The math comes * from a mail, posted by Olli Niemitalo to the music-dsp mailing * list (I found it in the music-dsp archives * http://www.smartelectronix.com/musicdsp/). */ for(i = 0; i < FLUID_INTERP_MAX; i++) { x = (double) i / (double) FLUID_INTERP_MAX; interp_coeff[i][0] = (x * (-0.5 + x * (1 - 0.5 * x))); interp_coeff[i][1] = (1.0 + x * x * (1.5 * x - 2.5)); interp_coeff[i][2] = (x * (0.5 + x * (2.0 - 1.5 * x))); interp_coeff[i][3] = (0.5 * x * x * (x - 1.0)); interp_coeff_linear[i][0] = (1.0 - x); interp_coeff_linear[i][1] = x; } /* i: Offset in terms of whole samples */ for(i = 0; i < SINC_INTERP_ORDER; i++) { /* i2: Offset in terms of fractional samples ('subsamples') */ for(i2 = 0; i2 < FLUID_INTERP_MAX; i2++) { /* center on middle of table */ i_shifted = (double)i - ((double)SINC_INTERP_ORDER / 2.0) + (double)i2 / (double)FLUID_INTERP_MAX; /* sinc(0) cannot be calculated straightforward (limit needed for 0/0) */ if(fabs(i_shifted) > 0.000001) { double arg = M_PI * i_shifted; v = sin(arg) / (arg); /* Hanning window */ v *= 0.5 * (1.0 + cos(2.0 * arg / (double)SINC_INTERP_ORDER)); } else { v = 1.0; } sinc_table7[FLUID_INTERP_MAX - i2 - 1][i] = v; } } } void gen_rvoice_table_dsp (FILE *fp) { /* Calculate the values */ fluid_rvoice_dsp_config(); /* Emit the matrices */ emit_matrix(fp, "interp_coeff_linear", cb_interp_coeff_linear, FLUID_INTERP_MAX, 2); emit_matrix(fp, "interp_coeff", cb_interp_coeff, FLUID_INTERP_MAX, 4); emit_matrix(fp, "sinc_table7", cb_sinc_table7, FLUID_INTERP_MAX, 7); } fluidsynth-2.1.1/src/gentables/make_tables.c000066400000000000000000000033201362231004000210370ustar00rootroot00000000000000 #include "make_tables.h" static void write_value(FILE *fp, double val, int i) { fprintf(fp, " %.15e%c /* %d */\n", val, ',', i ); } /* Emit an array of real numbers */ void emit_array(FILE *fp, const char *tblname, const double *tbl, int size) { int i; fprintf(fp, "static const fluid_real_t %s[%d] = {\n", tblname, size); for (i = 0; i < size; i++) { write_value(fp, tbl[i], i); } fprintf(fp, "};\n\n"); } /* Emit a matrix of real numbers */ void emit_matrix(FILE *fp, const char *tblname, emit_matrix_cb tbl_cb, int sizeh, int sizel) { int i, j; fprintf(fp, "static const fluid_real_t %s[%d][%d] = {\n {\n", tblname, sizeh, sizel); for (i = 0; i < sizeh; i++) { for (j = 0; j < sizel; j++) { write_value(fp, tbl_cb(i, j), i*sizel+j); } if (i < (sizeh-1)) fprintf(fp, " }, {\n"); else fprintf(fp, " }\n};\n\n"); } } static void open_table(FILE**fp, const char* dir, const char* file) { char buf[2048] = {0}; strcat(buf, dir); strcat(buf, file); /* open the output file */ *fp = fopen(buf, "w"); if (*fp == NULL) { exit(-2); } /* Emit warning header */ fprintf(*fp, "/* THIS FILE HAS BEEN AUTOMATICALLY GENERATED. DO NOT EDIT. */\n\n"); } int main (int argc, char *argv[]) { FILE *fp; // make sure we have enough arguments if (argc < 2) return -1; open_table(&fp, argv[1], "fluid_conv_tables.c"); gen_conv_table(fp); fclose(fp); open_table(&fp, argv[1], "fluid_rvoice_dsp_tables.c"); gen_rvoice_table_dsp(fp); fclose(fp); return 0; } fluidsynth-2.1.1/src/gentables/make_tables.h000066400000000000000000000012511362231004000210450ustar00rootroot00000000000000 #ifndef _FLUID_MAKE_TABLES_H #define _FLUID_MAKE_TABLES_H #include #include #include #include #define EMIT_ARRAY(__fp__, __arr__) emit_array(__fp__, #__arr__, __arr__, sizeof(__arr__)/sizeof(*__arr__)) /* callback for general access to matrices */ typedef double (*emit_matrix_cb)(int y, int x); /* Generators */ void gen_rvoice_table_dsp(FILE *fp); void gen_conv_table(FILE *fp); /* Emit an array of real numbers */ void emit_array(FILE *fp, const char *tblname, const double *tbl, int size); /* Emit a matrix of real numbers */ void emit_matrix(FILE *fp, const char *tblname, emit_matrix_cb tbl_cb, int sizeh, int sizel); #endif fluidsynth-2.1.1/src/midi/000077500000000000000000000000001362231004000154045ustar00rootroot00000000000000fluidsynth-2.1.1/src/midi/fluid_midi.c000066400000000000000000001701111362231004000176560ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_midi.h" #include "fluid_sys.h" #include "fluid_synth.h" #include "fluid_settings.h" static int fluid_midi_event_length(unsigned char event); static int fluid_isasciistring(char *s); static long fluid_getlength(unsigned char *s); /* Read the entire contents of a file into memory, allocating enough memory * for the file, and returning the length and the buffer. * Note: This rewinds the file to the start before reading. * Returns NULL if there was an error reading or allocating memory. */ typedef FILE *fluid_file; static char *fluid_file_read_full(fluid_file fp, size_t *length); static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, void *data, int size, int dynamic); static void fluid_midi_event_get_sysex_LOCAL(fluid_midi_event_t *evt, void **data, int *size); #define READ_FULL_INITIAL_BUFLEN 1024 static fluid_track_t *new_fluid_track(int num); static void delete_fluid_track(fluid_track_t *track); static int fluid_track_set_name(fluid_track_t *track, char *name); static int fluid_track_add_event(fluid_track_t *track, fluid_midi_event_t *evt); static fluid_midi_event_t *fluid_track_next_event(fluid_track_t *track); static int fluid_track_get_duration(fluid_track_t *track); static int fluid_track_reset(fluid_track_t *track); static void fluid_track_send_events(fluid_track_t *track, fluid_synth_t *synth, fluid_player_t *player, unsigned int ticks); static int fluid_player_add_track(fluid_player_t *player, fluid_track_t *track); static int fluid_player_callback(void *data, unsigned int msec); static int fluid_player_reset(fluid_player_t *player); static int fluid_player_load(fluid_player_t *player, fluid_playlist_item *item); static void fluid_player_advancefile(fluid_player_t *player); static void fluid_player_playlist_load(fluid_player_t *player, unsigned int msec); static fluid_midi_file *new_fluid_midi_file(const char *buffer, size_t length); static void delete_fluid_midi_file(fluid_midi_file *mf); static int fluid_midi_file_read_mthd(fluid_midi_file *midifile); static int fluid_midi_file_load_tracks(fluid_midi_file *midifile, fluid_player_t *player); static int fluid_midi_file_read_track(fluid_midi_file *mf, fluid_player_t *player, int num); static int fluid_midi_file_read_event(fluid_midi_file *mf, fluid_track_t *track); static int fluid_midi_file_read_varlen(fluid_midi_file *mf); static int fluid_midi_file_getc(fluid_midi_file *mf); static int fluid_midi_file_push(fluid_midi_file *mf, int c); static int fluid_midi_file_read(fluid_midi_file *mf, void *buf, int len); static int fluid_midi_file_skip(fluid_midi_file *mf, int len); static int fluid_midi_file_eof(fluid_midi_file *mf); static int fluid_midi_file_read_tracklen(fluid_midi_file *mf); static int fluid_midi_file_eot(fluid_midi_file *mf); static int fluid_midi_file_get_division(fluid_midi_file *midifile); /*************************************************************** * * MIDIFILE */ /** * Check if a file is a MIDI file. * @param filename Path to the file to check * @return TRUE if it could be a MIDI file, FALSE otherwise * * The current implementation only checks for the "MThd" header in the file. * It is useful only to distinguish between SoundFont and MIDI files. */ int fluid_is_midifile(const char *filename) { FILE *fp; uint32_t id; int retcode = FALSE; do { if((fp = fluid_file_open(filename, NULL)) == NULL) { return retcode; } if(FLUID_FREAD(&id, sizeof(id), 1, fp) != 1) { break; } retcode = (id == FLUID_FOURCC('M', 'T', 'h', 'd')); } while(0); FLUID_FCLOSE(fp); return retcode; } /** * Return a new MIDI file handle for parsing an already-loaded MIDI file. * @internal * @param buffer Pointer to full contents of MIDI file (borrows the pointer). * The caller must not free buffer until after the fluid_midi_file is deleted. * @param length Size of the buffer in bytes. * @return New MIDI file handle or NULL on error. */ fluid_midi_file * new_fluid_midi_file(const char *buffer, size_t length) { fluid_midi_file *mf; mf = FLUID_NEW(fluid_midi_file); if(mf == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(mf, 0, sizeof(fluid_midi_file)); mf->c = -1; mf->running_status = -1; mf->buffer = buffer; mf->buf_len = length; mf->buf_pos = 0; mf->eof = FALSE; if(fluid_midi_file_read_mthd(mf) != FLUID_OK) { FLUID_FREE(mf); return NULL; } return mf; } static char * fluid_file_read_full(fluid_file fp, size_t *length) { size_t buflen; char *buffer; size_t n; /* Work out the length of the file in advance */ if(FLUID_FSEEK(fp, 0, SEEK_END) != 0) { FLUID_LOG(FLUID_ERR, "File load: Could not seek within file"); return NULL; } buflen = ftell(fp); if(FLUID_FSEEK(fp, 0, SEEK_SET) != 0) { FLUID_LOG(FLUID_ERR, "File load: Could not seek within file"); return NULL; } FLUID_LOG(FLUID_DBG, "File load: Allocating %lu bytes", buflen); buffer = FLUID_MALLOC(buflen); if(buffer == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } n = FLUID_FREAD(buffer, 1, buflen, fp); if(n != buflen) { FLUID_LOG(FLUID_ERR, "Only read %lu bytes; expected %lu", n, buflen); FLUID_FREE(buffer); return NULL; }; *length = n; return buffer; } /** * Delete a MIDI file handle. * @internal * @param mf MIDI file handle to close and free. */ void delete_fluid_midi_file(fluid_midi_file *mf) { fluid_return_if_fail(mf != NULL); FLUID_FREE(mf); } /* * Gets the next byte in a MIDI file, taking into account previous running status. * * returns -1 if EOF or read error */ int fluid_midi_file_getc(fluid_midi_file *mf) { unsigned char c; if(mf->c >= 0) { c = mf->c; mf->c = -1; } else { if(mf->buf_pos >= mf->buf_len) { mf->eof = TRUE; return -1; } c = mf->buffer[mf->buf_pos++]; mf->trackpos++; } return (int) c; } /* * Saves a byte to be returned the next time fluid_midi_file_getc() is called, * when it is necessary according to running status. */ int fluid_midi_file_push(fluid_midi_file *mf, int c) { mf->c = c; return FLUID_OK; } /* * fluid_midi_file_read */ int fluid_midi_file_read(fluid_midi_file *mf, void *buf, int len) { int num = len < mf->buf_len - mf->buf_pos ? len : mf->buf_len - mf->buf_pos; if(num != len) { mf->eof = TRUE; } if(num < 0) { num = 0; } /* Note: Read bytes, even if there aren't enough, but only increment * trackpos if successful (emulates old behaviour of fluid_midi_file_read) */ FLUID_MEMCPY(buf, mf->buffer + mf->buf_pos, num); mf->buf_pos += num; if(num == len) { mf->trackpos += num; } #if DEBUG else { FLUID_LOG(FLUID_DBG, "Could not read the requested number of bytes"); } #endif return (num != len) ? FLUID_FAILED : FLUID_OK; } /* * fluid_midi_file_skip */ int fluid_midi_file_skip(fluid_midi_file *mf, int skip) { int new_pos = mf->buf_pos + skip; /* Mimic the behaviour of fseek: Error to seek past the start of file, but * OK to seek past end (this just puts it into the EOF state). */ if(new_pos < 0) { FLUID_LOG(FLUID_ERR, "Failed to seek position in file"); return FLUID_FAILED; } /* Clear the EOF flag, even if moved past the end of the file (this is * consistent with the behaviour of fseek). */ mf->eof = FALSE; mf->buf_pos = new_pos; return FLUID_OK; } /* * fluid_midi_file_eof */ int fluid_midi_file_eof(fluid_midi_file *mf) { /* Note: This does not simply test whether the file read pointer is past * the end of the file. It mimics the behaviour of feof by actually * testing the stateful EOF condition, which is set to TRUE if getc or * fread have attempted to read past the end (but not if they have * precisely reached the end), but reset to FALSE upon a successful seek. */ return mf->eof; } /* * fluid_midi_file_read_mthd */ int fluid_midi_file_read_mthd(fluid_midi_file *mf) { char mthd[14]; if(fluid_midi_file_read(mf, mthd, sizeof(mthd)) != FLUID_OK) { return FLUID_FAILED; } if((FLUID_STRNCMP(mthd, "MThd", 4) != 0) || (mthd[7] != 6) || (mthd[9] > 2)) { FLUID_LOG(FLUID_ERR, "Doesn't look like a MIDI file: invalid MThd header"); return FLUID_FAILED; } mf->type = mthd[9]; mf->ntracks = (unsigned) mthd[11]; mf->ntracks += (unsigned int)(mthd[10]) << 16; if((signed char)mthd[12] < 0) { mf->uses_smpte = 1; mf->smpte_fps = -(signed char)mthd[12]; mf->smpte_res = (unsigned) mthd[13]; FLUID_LOG(FLUID_ERR, "File uses SMPTE timing -- Not implemented yet"); return FLUID_FAILED; } else { mf->uses_smpte = 0; mf->division = ((unsigned)mthd[12] << 8) | ((unsigned)mthd[13] & 0xff); FLUID_LOG(FLUID_DBG, "Division=%d", mf->division); } return FLUID_OK; } /* * fluid_midi_file_load_tracks */ int fluid_midi_file_load_tracks(fluid_midi_file *mf, fluid_player_t *player) { int i; for(i = 0; i < mf->ntracks; i++) { if(fluid_midi_file_read_track(mf, player, i) != FLUID_OK) { return FLUID_FAILED; } } return FLUID_OK; } /* * fluid_isasciistring */ int fluid_isasciistring(char *s) { /* From ctype.h */ #define fluid_isascii(c) (((c) & ~0x7f) == 0) size_t i, len = FLUID_STRLEN(s); for(i = 0; i < len; i++) { if(!fluid_isascii(s[i])) { return 0; } } return 1; #undef fluid_isascii } /* * fluid_getlength */ long fluid_getlength(unsigned char *s) { long i = 0; i = s[3] | (s[2] << 8) | (s[1] << 16) | (s[0] << 24); return i; } /* * fluid_midi_file_read_tracklen */ int fluid_midi_file_read_tracklen(fluid_midi_file *mf) { unsigned char length[5]; if(fluid_midi_file_read(mf, length, 4) != FLUID_OK) { return FLUID_FAILED; } mf->tracklen = fluid_getlength(length); mf->trackpos = 0; mf->eot = 0; return FLUID_OK; } /* * fluid_midi_file_eot */ int fluid_midi_file_eot(fluid_midi_file *mf) { #if DEBUG if(mf->trackpos > mf->tracklen) { printf("track overrun: %d > %d\n", mf->trackpos, mf->tracklen); } #endif return mf->eot || (mf->trackpos >= mf->tracklen); } /* * fluid_midi_file_read_track */ int fluid_midi_file_read_track(fluid_midi_file *mf, fluid_player_t *player, int num) { fluid_track_t *track; unsigned char id[5], length[5]; int found_track = 0; int skip; if(fluid_midi_file_read(mf, id, 4) != FLUID_OK) { return FLUID_FAILED; } id[4] = '\0'; mf->dtime = 0; while(!found_track) { if(fluid_isasciistring((char *) id) == 0) { FLUID_LOG(FLUID_ERR, "An non-ascii track header found, corrupt file"); return FLUID_FAILED; } else if(FLUID_STRCMP((char *) id, "MTrk") == 0) { found_track = 1; if(fluid_midi_file_read_tracklen(mf) != FLUID_OK) { return FLUID_FAILED; } track = new_fluid_track(num); if(track == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } while(!fluid_midi_file_eot(mf)) { if(fluid_midi_file_read_event(mf, track) != FLUID_OK) { delete_fluid_track(track); return FLUID_FAILED; } } /* Skip remaining track data, if any */ if(mf->trackpos < mf->tracklen) { if(fluid_midi_file_skip(mf, mf->tracklen - mf->trackpos) != FLUID_OK) { delete_fluid_track(track); return FLUID_FAILED; } } if(fluid_player_add_track(player, track) != FLUID_OK) { delete_fluid_track(track); return FLUID_FAILED; } } else { found_track = 0; if(fluid_midi_file_read(mf, length, 4) != FLUID_OK) { return FLUID_FAILED; } skip = fluid_getlength(length); /* fseek(mf->fp, skip, SEEK_CUR); */ if(fluid_midi_file_skip(mf, skip) != FLUID_OK) { return FLUID_FAILED; } } } if(fluid_midi_file_eof(mf)) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } return FLUID_OK; } /* * fluid_midi_file_read_varlen */ int fluid_midi_file_read_varlen(fluid_midi_file *mf) { int i; int c; mf->varlen = 0; for(i = 0;; i++) { if(i == 4) { FLUID_LOG(FLUID_ERR, "Invalid variable length number"); return FLUID_FAILED; } c = fluid_midi_file_getc(mf); if(c < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } if(c & 0x80) { mf->varlen |= (int)(c & 0x7F); mf->varlen <<= 7; } else { mf->varlen += c; break; } } return FLUID_OK; } /* * fluid_midi_file_read_event */ int fluid_midi_file_read_event(fluid_midi_file *mf, fluid_track_t *track) { int status; int type; int tempo; unsigned char *metadata = NULL; unsigned char *dyn_buf = NULL; unsigned char static_buf[256]; int nominator, denominator, clocks, notes; fluid_midi_event_t *evt; int channel = 0; int param1 = 0; int param2 = 0; int size; /* read the delta-time of the event */ if(fluid_midi_file_read_varlen(mf) != FLUID_OK) { return FLUID_FAILED; } mf->dtime += mf->varlen; /* read the status byte */ status = fluid_midi_file_getc(mf); if(status < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } /* not a valid status byte: use the running status instead */ if((status & 0x80) == 0) { if((mf->running_status & 0x80) == 0) { FLUID_LOG(FLUID_ERR, "Undefined status and invalid running status"); return FLUID_FAILED; } fluid_midi_file_push(mf, status); status = mf->running_status; } /* check what message we have */ mf->running_status = status; if(status == MIDI_SYSEX) /* system exclusif */ { /* read the length of the message */ if(fluid_midi_file_read_varlen(mf) != FLUID_OK) { return FLUID_FAILED; } if(mf->varlen) { FLUID_LOG(FLUID_DBG, "%s: %d: alloc metadata, len = %d", __FILE__, __LINE__, mf->varlen); metadata = FLUID_MALLOC(mf->varlen + 1); if(metadata == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } /* read the data of the message */ if(fluid_midi_file_read(mf, metadata, mf->varlen) != FLUID_OK) { FLUID_FREE(metadata); return FLUID_FAILED; } evt = new_fluid_midi_event(); if(evt == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); FLUID_FREE(metadata); return FLUID_FAILED; } evt->dtime = mf->dtime; size = mf->varlen; if(metadata[mf->varlen - 1] == MIDI_EOX) { size--; } /* Add SYSEX event and indicate that its dynamically allocated and should be freed with event */ fluid_midi_event_set_sysex(evt, metadata, size, TRUE); fluid_track_add_event(track, evt); mf->dtime = 0; } return FLUID_OK; } else if(status == MIDI_META_EVENT) /* meta events */ { int result = FLUID_OK; /* get the type of the meta message */ type = fluid_midi_file_getc(mf); if(type < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } /* get the length of the data part */ if(fluid_midi_file_read_varlen(mf) != FLUID_OK) { return FLUID_FAILED; } if(mf->varlen < 255) { metadata = &static_buf[0]; } else { FLUID_LOG(FLUID_DBG, "%s: %d: alloc metadata, len = %d", __FILE__, __LINE__, mf->varlen); dyn_buf = FLUID_MALLOC(mf->varlen + 1); if(dyn_buf == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } metadata = dyn_buf; } /* read the data */ if(mf->varlen) { if(fluid_midi_file_read(mf, metadata, mf->varlen) != FLUID_OK) { if(dyn_buf) { FLUID_FREE(dyn_buf); } return FLUID_FAILED; } } /* handle meta data */ switch(type) { case MIDI_COPYRIGHT: metadata[mf->varlen] = 0; break; case MIDI_TRACK_NAME: metadata[mf->varlen] = 0; fluid_track_set_name(track, (char *) metadata); break; case MIDI_INST_NAME: metadata[mf->varlen] = 0; break; case MIDI_LYRIC: case MIDI_TEXT: { void *tmp; int size = mf->varlen + 1; /* NULL terminate strings for safety */ metadata[size - 1] = '\0'; evt = new_fluid_midi_event(); if(evt == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); result = FLUID_FAILED; break; } evt->dtime = mf->dtime; tmp = FLUID_MALLOC(size); if(tmp == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); delete_fluid_midi_event(evt); evt = NULL; result = FLUID_FAILED; break; } FLUID_MEMCPY(tmp, metadata, size); fluid_midi_event_set_sysex_LOCAL(evt, type, tmp, size, TRUE); fluid_track_add_event(track, evt); mf->dtime = 0; } break; case MIDI_MARKER: break; case MIDI_CUE_POINT: break; /* don't care much for text events */ case MIDI_EOT: if(mf->varlen != 0) { FLUID_LOG(FLUID_ERR, "Invalid length for EndOfTrack event"); result = FLUID_FAILED; break; } mf->eot = 1; evt = new_fluid_midi_event(); if(evt == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); result = FLUID_FAILED; break; } evt->dtime = mf->dtime; evt->type = MIDI_EOT; fluid_track_add_event(track, evt); mf->dtime = 0; break; case MIDI_SET_TEMPO: if(mf->varlen != 3) { FLUID_LOG(FLUID_ERR, "Invalid length for SetTempo meta event"); result = FLUID_FAILED; break; } tempo = (metadata[0] << 16) + (metadata[1] << 8) + metadata[2]; evt = new_fluid_midi_event(); if(evt == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); result = FLUID_FAILED; break; } evt->dtime = mf->dtime; evt->type = MIDI_SET_TEMPO; evt->channel = 0; evt->param1 = tempo; evt->param2 = 0; fluid_track_add_event(track, evt); mf->dtime = 0; break; case MIDI_SMPTE_OFFSET: if(mf->varlen != 5) { FLUID_LOG(FLUID_ERR, "Invalid length for SMPTE Offset meta event"); result = FLUID_FAILED; break; } break; /* we don't use smtp */ case MIDI_TIME_SIGNATURE: if(mf->varlen != 4) { FLUID_LOG(FLUID_ERR, "Invalid length for TimeSignature meta event"); result = FLUID_FAILED; break; } nominator = metadata[0]; denominator = pow(2.0, (double) metadata[1]); clocks = metadata[2]; notes = metadata[3]; FLUID_LOG(FLUID_DBG, "signature=%d/%d, metronome=%d, 32nd-notes=%d", nominator, denominator, clocks, notes); break; case MIDI_KEY_SIGNATURE: if(mf->varlen != 2) { FLUID_LOG(FLUID_ERR, "Invalid length for KeySignature meta event"); result = FLUID_FAILED; break; } /* We don't care about key signatures anyway */ /* sf = metadata[0]; mi = metadata[1]; */ break; case MIDI_SEQUENCER_EVENT: break; default: break; } if(dyn_buf) { FLUID_LOG(FLUID_DBG, "%s: %d: free metadata", __FILE__, __LINE__); FLUID_FREE(dyn_buf); } return result; } else /* channel messages */ { type = status & 0xf0; channel = status & 0x0f; /* all channel message have at least 1 byte of associated data */ if((param1 = fluid_midi_file_getc(mf)) < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } switch(type) { case NOTE_ON: if((param2 = fluid_midi_file_getc(mf)) < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } break; case NOTE_OFF: if((param2 = fluid_midi_file_getc(mf)) < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } break; case KEY_PRESSURE: if((param2 = fluid_midi_file_getc(mf)) < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } break; case CONTROL_CHANGE: if((param2 = fluid_midi_file_getc(mf)) < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } break; case PROGRAM_CHANGE: break; case CHANNEL_PRESSURE: break; case PITCH_BEND: if((param2 = fluid_midi_file_getc(mf)) < 0) { FLUID_LOG(FLUID_ERR, "Unexpected end of file"); return FLUID_FAILED; } param1 = ((param2 & 0x7f) << 7) | (param1 & 0x7f); param2 = 0; break; default: /* Can't possibly happen !? */ FLUID_LOG(FLUID_ERR, "Unrecognized MIDI event"); return FLUID_FAILED; } evt = new_fluid_midi_event(); if(evt == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } evt->dtime = mf->dtime; evt->type = type; evt->channel = channel; evt->param1 = param1; evt->param2 = param2; fluid_track_add_event(track, evt); mf->dtime = 0; } return FLUID_OK; } /* * fluid_midi_file_get_division */ int fluid_midi_file_get_division(fluid_midi_file *midifile) { return midifile->division; } /****************************************************** * * fluid_track_t */ /** * Create a MIDI event structure. * @return New MIDI event structure or NULL when out of memory. */ fluid_midi_event_t * new_fluid_midi_event() { fluid_midi_event_t *evt; evt = FLUID_NEW(fluid_midi_event_t); if(evt == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } evt->dtime = 0; evt->type = 0; evt->channel = 0; evt->param1 = 0; evt->param2 = 0; evt->next = NULL; evt->paramptr = NULL; return evt; } /** * Delete MIDI event structure. * @param evt MIDI event structure */ void delete_fluid_midi_event(fluid_midi_event_t *evt) { fluid_midi_event_t *temp; fluid_return_if_fail(evt != NULL); while(evt) { temp = evt->next; /* Dynamic SYSEX event? - free (param2 indicates if dynamic) */ if((evt->type == MIDI_SYSEX || (evt-> type == MIDI_TEXT) || (evt->type == MIDI_LYRIC)) && evt->paramptr && evt->param2) { FLUID_FREE(evt->paramptr); } FLUID_FREE(evt); evt = temp; } } /** * Get the event type field of a MIDI event structure. * @param evt MIDI event structure * @return Event type field (MIDI status byte without channel) */ int fluid_midi_event_get_type(fluid_midi_event_t *evt) { return evt->type; } /** * Set the event type field of a MIDI event structure. * @param evt MIDI event structure * @param type Event type field (MIDI status byte without channel) * @return Always returns #FLUID_OK */ int fluid_midi_event_set_type(fluid_midi_event_t *evt, int type) { evt->type = type; return FLUID_OK; } /** * Get the channel field of a MIDI event structure. * @param evt MIDI event structure * @return Channel field */ int fluid_midi_event_get_channel(fluid_midi_event_t *evt) { return evt->channel; } /** * Set the channel field of a MIDI event structure. * @param evt MIDI event structure * @param chan MIDI channel field * @return Always returns #FLUID_OK */ int fluid_midi_event_set_channel(fluid_midi_event_t *evt, int chan) { evt->channel = chan; return FLUID_OK; } /** * Get the key field of a MIDI event structure. * @param evt MIDI event structure * @return MIDI note number (0-127) */ int fluid_midi_event_get_key(fluid_midi_event_t *evt) { return evt->param1; } /** * Set the key field of a MIDI event structure. * @param evt MIDI event structure * @param v MIDI note number (0-127) * @return Always returns #FLUID_OK */ int fluid_midi_event_set_key(fluid_midi_event_t *evt, int v) { evt->param1 = v; return FLUID_OK; } /** * Get the velocity field of a MIDI event structure. * @param evt MIDI event structure * @return MIDI velocity number (0-127) */ int fluid_midi_event_get_velocity(fluid_midi_event_t *evt) { return evt->param2; } /** * Set the velocity field of a MIDI event structure. * @param evt MIDI event structure * @param v MIDI velocity value * @return Always returns #FLUID_OK */ int fluid_midi_event_set_velocity(fluid_midi_event_t *evt, int v) { evt->param2 = v; return FLUID_OK; } /** * Get the control number of a MIDI event structure. * @param evt MIDI event structure * @return MIDI control number */ int fluid_midi_event_get_control(fluid_midi_event_t *evt) { return evt->param1; } /** * Set the control field of a MIDI event structure. * @param evt MIDI event structure * @param v MIDI control number * @return Always returns #FLUID_OK */ int fluid_midi_event_set_control(fluid_midi_event_t *evt, int v) { evt->param1 = v; return FLUID_OK; } /** * Get the value field from a MIDI event structure. * @param evt MIDI event structure * @return Value field */ int fluid_midi_event_get_value(fluid_midi_event_t *evt) { return evt->param2; } /** * Set the value field of a MIDI event structure. * @param evt MIDI event structure * @param v Value to assign * @return Always returns #FLUID_OK */ int fluid_midi_event_set_value(fluid_midi_event_t *evt, int v) { evt->param2 = v; return FLUID_OK; } /** * Get the program field of a MIDI event structure. * @param evt MIDI event structure * @return MIDI program number (0-127) */ int fluid_midi_event_get_program(fluid_midi_event_t *evt) { return evt->param1; } /** * Set the program field of a MIDI event structure. * @param evt MIDI event structure * @param val MIDI program number (0-127) * @return Always returns #FLUID_OK */ int fluid_midi_event_set_program(fluid_midi_event_t *evt, int val) { evt->param1 = val; return FLUID_OK; } /** * Get the pitch field of a MIDI event structure. * @param evt MIDI event structure * @return Pitch value (14 bit value, 0-16383, 8192 is center) */ int fluid_midi_event_get_pitch(fluid_midi_event_t *evt) { return evt->param1; } /** * Set the pitch field of a MIDI event structure. * @param evt MIDI event structure * @param val Pitch value (14 bit value, 0-16383, 8192 is center) * @return Always returns FLUID_OK */ int fluid_midi_event_set_pitch(fluid_midi_event_t *evt, int val) { evt->param1 = val; return FLUID_OK; } /** * Assign sysex data to a MIDI event structure. * @param evt MIDI event structure * @param data Pointer to SYSEX data * @param size Size of SYSEX data in bytes * @param dynamic TRUE if the SYSEX data has been dynamically allocated and * should be freed when the event is freed (only applies if event gets destroyed * with delete_fluid_midi_event()) * @return Always returns #FLUID_OK */ int fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, int size, int dynamic) { fluid_midi_event_set_sysex_LOCAL(evt, MIDI_SYSEX, data, size, dynamic); return FLUID_OK; } /** * Assign text data to a MIDI event structure. * @param evt MIDI event structure * @param data Pointer to text data * @param size Size of text data in bytes * @param dynamic TRUE if the data has been dynamically allocated and * should be freed when the event is freed via delete_fluid_midi_event() * @return Always returns #FLUID_OK * * @since 2.0.0 */ int fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dynamic) { fluid_midi_event_set_sysex_LOCAL(evt, MIDI_TEXT, data, size, dynamic); return FLUID_OK; } /** * Get the text of a MIDI event structure. * @param evt MIDI event structure * @param data Pointer to return text data on. * @param size Pointer to return text size on. * @return Returns #FLUID_OK if \p data and \p size previously set by * fluid_midi_event_set_text() have been successfully retrieved. * Else #FLUID_FAILED is returned and \p data and \p size are not changed. * @since 2.0.3 */ int fluid_midi_event_get_text(fluid_midi_event_t *evt, void **data, int *size) { fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); fluid_return_val_if_fail(evt->type == MIDI_TEXT, FLUID_FAILED); fluid_midi_event_get_sysex_LOCAL(evt, data, size); return FLUID_OK; } /** * Assign lyric data to a MIDI event structure. * @param evt MIDI event structure * @param data Pointer to lyric data * @param size Size of lyric data in bytes * @param dynamic TRUE if the data has been dynamically allocated and * should be freed when the event is freed via delete_fluid_midi_event() * @return Always returns #FLUID_OK * * @since 2.0.0 */ int fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int dynamic) { fluid_midi_event_set_sysex_LOCAL(evt, MIDI_LYRIC, data, size, dynamic); return FLUID_OK; } /** * Get the lyric of a MIDI event structure. * @param evt MIDI event structure * @param data Pointer to return lyric data on. * @param size Pointer to return lyric size on. * @return Returns #FLUID_OK if \p data and \p size previously set by * fluid_midi_event_set_lyrics() have been successfully retrieved. * Else #FLUID_FAILED is returned and \p data and \p size are not changed. * @since 2.0.3 */ int fluid_midi_event_get_lyrics(fluid_midi_event_t *evt, void **data, int *size) { fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); fluid_return_val_if_fail(evt->type == MIDI_LYRIC, FLUID_FAILED); fluid_midi_event_get_sysex_LOCAL(evt, data, size); return FLUID_OK; } static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, void *data, int size, int dynamic) { evt->type = type; evt->paramptr = data; evt->param1 = size; evt->param2 = dynamic; } static void fluid_midi_event_get_sysex_LOCAL(fluid_midi_event_t *evt, void **data, int *size) { if(data) { *data = evt->paramptr; } if(size) { *size = evt->param1; } } /****************************************************** * * fluid_track_t */ /* * new_fluid_track */ fluid_track_t * new_fluid_track(int num) { fluid_track_t *track; track = FLUID_NEW(fluid_track_t); if(track == NULL) { return NULL; } track->name = NULL; track->num = num; track->first = NULL; track->cur = NULL; track->last = NULL; track->ticks = 0; return track; } /* * delete_fluid_track */ void delete_fluid_track(fluid_track_t *track) { fluid_return_if_fail(track != NULL); FLUID_FREE(track->name); delete_fluid_midi_event(track->first); FLUID_FREE(track); } /* * fluid_track_set_name */ int fluid_track_set_name(fluid_track_t *track, char *name) { size_t len; if(track->name != NULL) { FLUID_FREE(track->name); } if(name == NULL) { track->name = NULL; return FLUID_OK; } len = FLUID_STRLEN(name); track->name = FLUID_MALLOC(len + 1); if(track->name == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } FLUID_STRCPY(track->name, name); return FLUID_OK; } /* * fluid_track_get_duration */ int fluid_track_get_duration(fluid_track_t *track) { int time = 0; fluid_midi_event_t *evt = track->first; while(evt != NULL) { time += evt->dtime; evt = evt->next; } return time; } /* * fluid_track_add_event */ int fluid_track_add_event(fluid_track_t *track, fluid_midi_event_t *evt) { evt->next = NULL; if(track->first == NULL) { track->first = evt; track->cur = evt; track->last = evt; } else { track->last->next = evt; track->last = evt; } return FLUID_OK; } /* * fluid_track_next_event */ fluid_midi_event_t * fluid_track_next_event(fluid_track_t *track) { if(track->cur != NULL) { track->cur = track->cur->next; } return track->cur; } /* * fluid_track_reset */ int fluid_track_reset(fluid_track_t *track) { track->ticks = 0; track->cur = track->first; return FLUID_OK; } /* * fluid_track_send_events */ void fluid_track_send_events(fluid_track_t *track, fluid_synth_t *synth, fluid_player_t *player, unsigned int ticks) { fluid_midi_event_t *event; int seeking = player->seek_ticks >= 0; if(seeking) { ticks = player->seek_ticks; /* update target ticks */ if(track->ticks > ticks) { fluid_track_reset(track); /* reset track if seeking backwards */ } } while(1) { event = track->cur; if(event == NULL) { return; } /* printf("track=%02d\tticks=%05u\ttrack=%05u\tdtime=%05u\tnext=%05u\n", */ /* track->num, */ /* ticks, */ /* track->ticks, */ /* event->dtime, */ /* track->ticks + event->dtime); */ if(track->ticks + event->dtime > ticks) { return; } track->ticks += event->dtime; if(!player || event->type == MIDI_EOT) { } else if(seeking && (event->type == NOTE_ON || event->type == NOTE_OFF)) { /* skip on/off messages */ } else { if(player->playback_callback) { player->playback_callback(player->playback_userdata, event); } } if(event->type == MIDI_SET_TEMPO) { fluid_player_set_midi_tempo(player, event->param1); } fluid_track_next_event(track); } } /****************************************************** * * fluid_player */ static void fluid_player_handle_reset_synth(void *data, const char *name, int value) { fluid_player_t *player = data; fluid_return_if_fail(player != NULL); player->reset_synth_between_songs = value; } /** * Create a new MIDI player. * @param synth Fluid synthesizer instance to create player for * @return New MIDI player instance or NULL on error (out of memory) */ fluid_player_t * new_fluid_player(fluid_synth_t *synth) { int i; fluid_player_t *player; player = FLUID_NEW(fluid_player_t); if(player == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } player->status = FLUID_PLAYER_READY; player->loop = 1; player->ntracks = 0; for(i = 0; i < MAX_NUMBER_OF_TRACKS; i++) { player->track[i] = NULL; } player->synth = synth; player->system_timer = NULL; player->sample_timer = NULL; player->playlist = NULL; player->currentfile = NULL; player->division = 0; player->send_program_change = 1; player->miditempo = 500000; player->deltatime = 4.0; player->cur_msec = 0; player->cur_ticks = 0; player->seek_ticks = -1; fluid_player_set_playback_callback(player, fluid_synth_handle_midi_event, synth); player->use_system_timer = fluid_settings_str_equal(synth->settings, "player.timing-source", "system"); if(player->use_system_timer) { player->system_timer = new_fluid_timer((int) player->deltatime, fluid_player_callback, player, TRUE, FALSE, TRUE); if(player->system_timer == NULL) { goto err; } } else { player->sample_timer = new_fluid_sample_timer(player->synth, fluid_player_callback, player); if(player->sample_timer == NULL) { goto err; } } fluid_settings_getint(synth->settings, "player.reset-synth", &i); fluid_player_handle_reset_synth(player, NULL, i); fluid_settings_callback_int(synth->settings, "player.reset-synth", fluid_player_handle_reset_synth, player); return player; err: delete_fluid_player(player); return NULL; } /** * Delete a MIDI player instance. * @param player MIDI player instance * @warning Do not call while the \p synth renders audio, i.e. an audio driver is running or any other synthesizer thread calls fluid_synth_process() or fluid_synth_nwrite_float() or fluid_synth_write_*() ! */ void delete_fluid_player(fluid_player_t *player) { fluid_list_t *q; fluid_playlist_item *pi; fluid_return_if_fail(player != NULL); fluid_player_stop(player); fluid_player_reset(player); delete_fluid_timer(player->system_timer); delete_fluid_sample_timer(player->synth, player->sample_timer); while(player->playlist != NULL) { q = player->playlist->next; pi = (fluid_playlist_item *) player->playlist->data; FLUID_FREE(pi->filename); FLUID_FREE(pi->buffer); FLUID_FREE(pi); delete1_fluid_list(player->playlist); player->playlist = q; } FLUID_FREE(player); } /** * Registers settings related to the MIDI player */ void fluid_player_settings(fluid_settings_t *settings) { /* player.timing-source can be either "system" (use system timer) or "sample" (use timer based on number of written samples) */ fluid_settings_register_str(settings, "player.timing-source", "sample", 0); fluid_settings_add_option(settings, "player.timing-source", "sample"); fluid_settings_add_option(settings, "player.timing-source", "system"); /* Selects whether the player should reset the synth between songs, or not. */ fluid_settings_register_int(settings, "player.reset-synth", 1, 0, 1, FLUID_HINT_TOGGLED); } int fluid_player_reset(fluid_player_t *player) { int i; for(i = 0; i < MAX_NUMBER_OF_TRACKS; i++) { if(player->track[i] != NULL) { delete_fluid_track(player->track[i]); player->track[i] = NULL; } } /* player->current_file = NULL; */ /* player->status = FLUID_PLAYER_READY; */ /* player->loop = 1; */ player->ntracks = 0; player->division = 0; player->send_program_change = 1; player->miditempo = 500000; player->deltatime = 4.0; return 0; } /* * fluid_player_add_track */ int fluid_player_add_track(fluid_player_t *player, fluid_track_t *track) { if(player->ntracks < MAX_NUMBER_OF_TRACKS) { player->track[player->ntracks++] = track; return FLUID_OK; } else { return FLUID_FAILED; } } /** * Change the MIDI callback function. This is usually set to * fluid_synth_handle_midi_event, but can optionally be changed * to a user-defined function instead, for intercepting all MIDI * messages sent to the synth. You can also use a midi router as * the callback function to modify the MIDI messages before sending * them to the synth. * @param player MIDI player instance * @param handler Pointer to callback function * @param handler_data Parameter sent to the callback function * @returns FLUID_OK * @since 1.1.4 */ int fluid_player_set_playback_callback(fluid_player_t *player, handle_midi_event_func_t handler, void *handler_data) { player->playback_callback = handler; player->playback_userdata = handler_data; return FLUID_OK; } /** * Add a MIDI file to a player queue. * @param player MIDI player instance * @param midifile File name of the MIDI file to add * @return #FLUID_OK or #FLUID_FAILED */ int fluid_player_add(fluid_player_t *player, const char *midifile) { fluid_playlist_item *pi = FLUID_MALLOC(sizeof(fluid_playlist_item)); char *f = FLUID_STRDUP(midifile); if(!pi || !f) { FLUID_FREE(pi); FLUID_FREE(f); FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } pi->filename = f; pi->buffer = NULL; pi->buffer_len = 0; player->playlist = fluid_list_append(player->playlist, pi); return FLUID_OK; } /** * Add a MIDI file to a player queue, from a buffer in memory. * @param player MIDI player instance * @param buffer Pointer to memory containing the bytes of a complete MIDI * file. The data is copied, so the caller may free or modify it immediately * without affecting the playlist. * @param len Length of the buffer, in bytes. * @return #FLUID_OK or #FLUID_FAILED */ int fluid_player_add_mem(fluid_player_t *player, const void *buffer, size_t len) { /* Take a copy of the buffer, so the caller can free immediately. */ fluid_playlist_item *pi = FLUID_MALLOC(sizeof(fluid_playlist_item)); void *buf_copy = FLUID_MALLOC(len); if(!pi || !buf_copy) { FLUID_FREE(pi); FLUID_FREE(buf_copy); FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } FLUID_MEMCPY(buf_copy, buffer, len); pi->filename = NULL; pi->buffer = buf_copy; pi->buffer_len = len; player->playlist = fluid_list_append(player->playlist, pi); return FLUID_OK; } /* * fluid_player_load */ int fluid_player_load(fluid_player_t *player, fluid_playlist_item *item) { fluid_midi_file *midifile; char *buffer; size_t buffer_length; int buffer_owned; if(item->filename != NULL) { fluid_file fp; /* This file is specified by filename; load the file from disk */ FLUID_LOG(FLUID_DBG, "%s: %d: Loading midifile %s", __FILE__, __LINE__, item->filename); /* Read the entire contents of the file into the buffer */ fp = FLUID_FOPEN(item->filename, "rb"); if(fp == NULL) { FLUID_LOG(FLUID_ERR, "Couldn't open the MIDI file"); return FLUID_FAILED; } buffer = fluid_file_read_full(fp, &buffer_length); FLUID_FCLOSE(fp); if(buffer == NULL) { return FLUID_FAILED; } buffer_owned = 1; } else { /* This file is specified by a pre-loaded buffer; load from memory */ FLUID_LOG(FLUID_DBG, "%s: %d: Loading midifile from memory (%p)", __FILE__, __LINE__, item->buffer); buffer = (char *) item->buffer; buffer_length = item->buffer_len; /* Do not free the buffer (it is owned by the playlist) */ buffer_owned = 0; } midifile = new_fluid_midi_file(buffer, buffer_length); if(midifile == NULL) { if(buffer_owned) { FLUID_FREE(buffer); } return FLUID_FAILED; } player->division = fluid_midi_file_get_division(midifile); fluid_player_set_midi_tempo(player, player->miditempo); // Update deltatime /*FLUID_LOG(FLUID_DBG, "quarter note division=%d\n", player->division); */ if(fluid_midi_file_load_tracks(midifile, player) != FLUID_OK) { if(buffer_owned) { FLUID_FREE(buffer); } delete_fluid_midi_file(midifile); return FLUID_FAILED; } delete_fluid_midi_file(midifile); if(buffer_owned) { FLUID_FREE(buffer); } return FLUID_OK; } void fluid_player_advancefile(fluid_player_t *player) { if(player->playlist == NULL) { return; /* No files to play */ } if(player->currentfile != NULL) { player->currentfile = fluid_list_next(player->currentfile); } if(player->currentfile == NULL) { if(player->loop == 0) { return; /* We're done playing */ } if(player->loop > 0) { player->loop--; } player->currentfile = player->playlist; } } void fluid_player_playlist_load(fluid_player_t *player, unsigned int msec) { fluid_playlist_item *current_playitem; int i; do { fluid_player_advancefile(player); if(player->currentfile == NULL) { /* Failed to find next song, probably since we're finished */ player->status = FLUID_PLAYER_DONE; return; } fluid_player_reset(player); current_playitem = (fluid_playlist_item *) player->currentfile->data; } while(fluid_player_load(player, current_playitem) != FLUID_OK); /* Successfully loaded midi file */ player->begin_msec = msec; player->start_msec = msec; player->start_ticks = 0; player->cur_ticks = 0; if(player->reset_synth_between_songs) { fluid_synth_system_reset(player->synth); } for(i = 0; i < player->ntracks; i++) { if(player->track[i] != NULL) { fluid_track_reset(player->track[i]); } } } /* * fluid_player_callback */ int fluid_player_callback(void *data, unsigned int msec) { int i; int loadnextfile; int status = FLUID_PLAYER_DONE; fluid_player_t *player; fluid_synth_t *synth; player = (fluid_player_t *) data; synth = player->synth; loadnextfile = player->currentfile == NULL ? 1 : 0; if(player->status == FLUID_PLAYER_DONE) { fluid_synth_all_notes_off(synth, -1); return 1; } do { if(loadnextfile) { loadnextfile = 0; fluid_player_playlist_load(player, msec); if(player->currentfile == NULL) { return 0; } } player->cur_msec = msec; player->cur_ticks = (player->start_ticks + (int)((double)(player->cur_msec - player->start_msec) / player->deltatime + 0.5)); /* 0.5 to average overall error when casting */ if(player->seek_ticks >= 0) { fluid_synth_all_sounds_off(synth, -1); /* avoid hanging notes */ } for(i = 0; i < player->ntracks; i++) { if(!fluid_track_eot(player->track[i])) { status = FLUID_PLAYER_PLAYING; fluid_track_send_events(player->track[i], synth, player, player->cur_ticks); } } if(player->seek_ticks >= 0) { player->start_ticks = player->seek_ticks; /* tick position of last tempo value (which is now) */ player->cur_ticks = player->seek_ticks; player->begin_msec = msec; /* only used to calculate the duration of playing */ player->start_msec = msec; /* should be the (synth)-time of the last tempo change */ player->seek_ticks = -1; /* clear seek_ticks */ } if(status == FLUID_PLAYER_DONE) { FLUID_LOG(FLUID_DBG, "%s: %d: Duration=%.3f sec", __FILE__, __LINE__, (msec - player->begin_msec) / 1000.0); loadnextfile = 1; } } while(loadnextfile); player->status = status; return 1; } /** * Activates play mode for a MIDI player if not already playing. * @param player MIDI player instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_player_play(fluid_player_t *player) { if(player->status == FLUID_PLAYER_PLAYING || player->playlist == NULL) { return FLUID_OK; } if(!player->use_system_timer) { fluid_sample_timer_reset(player->synth, player->sample_timer); } player->status = FLUID_PLAYER_PLAYING; return FLUID_OK; } /** * Pauses the MIDI playback. * * It will not rewind to the beginning of the file, use fluid_player_seek() for this purpose. * @param player MIDI player instance * @return Always returns #FLUID_OK */ int fluid_player_stop(fluid_player_t *player) { player->status = FLUID_PLAYER_DONE; fluid_player_seek(player, fluid_player_get_current_tick(player)); return FLUID_OK; } /** * Get MIDI player status. * @param player MIDI player instance * @return Player status (#fluid_player_status) * @since 1.1.0 */ int fluid_player_get_status(fluid_player_t *player) { return player->status; } /** * Seek in the currently playing file. * @param player MIDI player instance * @param ticks the position to seek to in the current file * @return #FLUID_FAILED if ticks is negative or after the latest tick of the file, * #FLUID_OK otherwise * @since 2.0.0 * * The actual seek is performed during the player_callback. */ int fluid_player_seek(fluid_player_t *player, int ticks) { if(ticks < 0 || ticks > fluid_player_get_total_ticks(player)) { return FLUID_FAILED; } player->seek_ticks = ticks; return FLUID_OK; } /** * Enable looping of a MIDI player * @param player MIDI player instance * @param loop Times left to loop the playlist. -1 means loop infinitely. * @return Always returns #FLUID_OK * @since 1.1.0 * * For example, if you want to loop the playlist twice, set loop to 2 * and call this function before you start the player. */ int fluid_player_set_loop(fluid_player_t *player, int loop) { player->loop = loop; return FLUID_OK; } /** * Set the tempo of a MIDI player. * @param player MIDI player instance * @param tempo Tempo to set playback speed to (in microseconds per quarter note, as per MIDI file spec) * @return Always returns #FLUID_OK */ int fluid_player_set_midi_tempo(fluid_player_t *player, int tempo) { player->miditempo = tempo; player->deltatime = (double) tempo / player->division / 1000.0; /* in milliseconds */ player->start_msec = player->cur_msec; player->start_ticks = player->cur_ticks; FLUID_LOG(FLUID_DBG, "tempo=%d, tick time=%f msec, cur time=%d msec, cur tick=%d", tempo, player->deltatime, player->cur_msec, player->cur_ticks); return FLUID_OK; } /** * Set the tempo of a MIDI player in beats per minute. * @param player MIDI player instance * @param bpm Tempo in beats per minute * @return Always returns #FLUID_OK */ int fluid_player_set_bpm(fluid_player_t *player, int bpm) { return fluid_player_set_midi_tempo(player, 60000000L / bpm); } /** * Wait for a MIDI player until the playback has been stopped. * @param player MIDI player instance * @return Always #FLUID_OK */ int fluid_player_join(fluid_player_t *player) { while(player->status != FLUID_PLAYER_DONE) { fluid_msleep(10); } return FLUID_OK; } /** * Get the number of tempo ticks passed. * @param player MIDI player instance * @return The number of tempo ticks passed * @since 1.1.7 */ int fluid_player_get_current_tick(fluid_player_t *player) { return player->cur_ticks; } /** * Looks through all available MIDI tracks and gets the absolute tick of the very last event to play. * @param player MIDI player instance * @return Total tick count of the sequence * @since 1.1.7 */ int fluid_player_get_total_ticks(fluid_player_t *player) { int i; int maxTicks = 0; for(i = 0; i < player->ntracks; i++) { if(player->track[i] != NULL) { int ticks = fluid_track_get_duration(player->track[i]); if(ticks > maxTicks) { maxTicks = ticks; } } } return maxTicks; } /** * Get the tempo of a MIDI player in beats per minute. * @param player MIDI player instance * @return MIDI player tempo in BPM * @since 1.1.7 */ int fluid_player_get_bpm(fluid_player_t *player) { return 60000000L / player->miditempo; } /** * Get the tempo of a MIDI player. * @param player MIDI player instance * @return Tempo of the MIDI player (in microseconds per quarter note, as per MIDI file spec) * @since 1.1.7 */ int fluid_player_get_midi_tempo(fluid_player_t *player) { return player->miditempo; } /************************************************************************ * MIDI PARSER * */ /* * new_fluid_midi_parser */ fluid_midi_parser_t * new_fluid_midi_parser() { fluid_midi_parser_t *parser; parser = FLUID_NEW(fluid_midi_parser_t); if(parser == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } parser->status = 0; /* As long as the status is 0, the parser won't do anything -> no need to initialize all the fields. */ return parser; } /* * delete_fluid_midi_parser */ void delete_fluid_midi_parser(fluid_midi_parser_t *parser) { fluid_return_if_fail(parser != NULL); FLUID_FREE(parser); } /** * Parse a MIDI stream one character at a time. * @param parser Parser instance * @param c Next character in MIDI stream * @return A parsed MIDI event or NULL if none. Event is internal and should * not be modified or freed and is only valid until next call to this function. * @internal Do not expose this function to the public API. It would allow downstream * apps to abuse fluidsynth as midi parser, e.g. feeding it with rawmidi and pull out * the needed midi information using the getter functions of fluid_midi_event_t. * This parser however is incomplete as it e.g. only provides a limited buffer to * store and process SYSEX data (i.e. doesn't allow arbitrary lengths) */ fluid_midi_event_t * fluid_midi_parser_parse(fluid_midi_parser_t *parser, unsigned char c) { fluid_midi_event_t *event; /* Real-time messages (0xF8-0xFF) can occur anywhere, even in the middle * of another message. */ if(c >= 0xF8) { if(c == MIDI_SYSTEM_RESET) { parser->event.type = c; parser->status = 0; /* clear the status */ return &parser->event; } return NULL; } /* Status byte? - If previous message not yet complete, it is discarded (re-sync). */ if(c & 0x80) { /* Any status byte terminates SYSEX messages (not just 0xF7) */ if(parser->status == MIDI_SYSEX && parser->nr_bytes > 0) { event = &parser->event; fluid_midi_event_set_sysex(event, parser->data, parser->nr_bytes, FALSE); } else { event = NULL; } if(c < 0xF0) /* Voice category message? */ { parser->channel = c & 0x0F; parser->status = c & 0xF0; /* The event consumes x bytes of data... (subtract 1 for the status byte) */ parser->nr_bytes_total = fluid_midi_event_length(parser->status) - 1; parser->nr_bytes = 0; /* 0 bytes read so far */ } else if(c == MIDI_SYSEX) { parser->status = MIDI_SYSEX; parser->nr_bytes = 0; } else { parser->status = 0; /* Discard other system messages (0xF1-0xF7) */ } return event; /* Return SYSEX event or NULL */ } /* Data/parameter byte */ /* Discard data bytes for events we don't care about */ if(parser->status == 0) { return NULL; } /* Max data size exceeded? (SYSEX messages only really) */ if(parser->nr_bytes == FLUID_MIDI_PARSER_MAX_DATA_SIZE) { parser->status = 0; /* Discard the rest of the message */ return NULL; } /* Store next byte */ parser->data[parser->nr_bytes++] = c; /* Do we still need more data to get this event complete? */ if(parser->status == MIDI_SYSEX || parser->nr_bytes < parser->nr_bytes_total) { return NULL; } /* Event is complete, return it. * Running status byte MIDI feature is also handled here. */ parser->event.type = parser->status; parser->event.channel = parser->channel; parser->nr_bytes = 0; /* Reset data size, in case there are additional running status messages */ switch(parser->status) { case NOTE_OFF: case NOTE_ON: case KEY_PRESSURE: case CONTROL_CHANGE: case PROGRAM_CHANGE: case CHANNEL_PRESSURE: parser->event.param1 = parser->data[0]; /* For example key number */ parser->event.param2 = parser->data[1]; /* For example velocity */ break; case PITCH_BEND: /* Pitch-bend is transmitted with 14-bit precision. */ parser->event.param1 = (parser->data[1] << 7) | parser->data[0]; break; default: /* Unlikely */ return NULL; } return &parser->event; } /* Purpose: * Returns the length of a MIDI message. */ static int fluid_midi_event_length(unsigned char event) { switch(event & 0xF0) { case NOTE_OFF: case NOTE_ON: case KEY_PRESSURE: case CONTROL_CHANGE: case PITCH_BEND: return 3; case PROGRAM_CHANGE: case CHANNEL_PRESSURE: return 2; } switch(event) { case MIDI_TIME_CODE: case MIDI_SONG_SELECT: case 0xF4: case 0xF5: return 2; case MIDI_TUNE_REQUEST: return 1; case MIDI_SONG_POSITION: return 3; } return 1; } fluidsynth-2.1.1/src/midi/fluid_midi.h000066400000000000000000000263571362231004000176770ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_MIDI_H #define _FLUID_MIDI_H #include "fluidsynth_priv.h" #include "fluid_sys.h" #include "fluid_list.h" typedef struct _fluid_midi_parser_t fluid_midi_parser_t; fluid_midi_parser_t *new_fluid_midi_parser(void); void delete_fluid_midi_parser(fluid_midi_parser_t *parser); fluid_midi_event_t *fluid_midi_parser_parse(fluid_midi_parser_t *parser, unsigned char c); /*************************************************************** * * CONSTANTS & ENUM */ #define MAX_NUMBER_OF_TRACKS 128 enum fluid_midi_event_type { /* channel messages */ NOTE_OFF = 0x80, NOTE_ON = 0x90, KEY_PRESSURE = 0xa0, CONTROL_CHANGE = 0xb0, PROGRAM_CHANGE = 0xc0, CHANNEL_PRESSURE = 0xd0, PITCH_BEND = 0xe0, /* system exclusive */ MIDI_SYSEX = 0xf0, /* system common - never in midi files */ MIDI_TIME_CODE = 0xf1, MIDI_SONG_POSITION = 0xf2, MIDI_SONG_SELECT = 0xf3, MIDI_TUNE_REQUEST = 0xf6, MIDI_EOX = 0xf7, /* system real-time - never in midi files */ MIDI_SYNC = 0xf8, MIDI_TICK = 0xf9, MIDI_START = 0xfa, MIDI_CONTINUE = 0xfb, MIDI_STOP = 0xfc, MIDI_ACTIVE_SENSING = 0xfe, MIDI_SYSTEM_RESET = 0xff, /* meta event - for midi files only */ MIDI_META_EVENT = 0xff }; enum fluid_midi_control_change { BANK_SELECT_MSB = 0x00, MODULATION_MSB = 0x01, BREATH_MSB = 0x02, FOOT_MSB = 0x04, PORTAMENTO_TIME_MSB = 0x05, DATA_ENTRY_MSB = 0x06, VOLUME_MSB = 0x07, BALANCE_MSB = 0x08, PAN_MSB = 0x0A, EXPRESSION_MSB = 0x0B, EFFECTS1_MSB = 0x0C, EFFECTS2_MSB = 0x0D, GPC1_MSB = 0x10, /* general purpose controller */ GPC2_MSB = 0x11, GPC3_MSB = 0x12, GPC4_MSB = 0x13, BANK_SELECT_LSB = 0x20, MODULATION_WHEEL_LSB = 0x21, BREATH_LSB = 0x22, FOOT_LSB = 0x24, PORTAMENTO_TIME_LSB = 0x25, DATA_ENTRY_LSB = 0x26, VOLUME_LSB = 0x27, BALANCE_LSB = 0x28, PAN_LSB = 0x2A, EXPRESSION_LSB = 0x2B, EFFECTS1_LSB = 0x2C, EFFECTS2_LSB = 0x2D, GPC1_LSB = 0x30, GPC2_LSB = 0x31, GPC3_LSB = 0x32, GPC4_LSB = 0x33, SUSTAIN_SWITCH = 0x40, PORTAMENTO_SWITCH = 0x41, SOSTENUTO_SWITCH = 0x42, SOFT_PEDAL_SWITCH = 0x43, LEGATO_SWITCH = 0x44, HOLD2_SWITCH = 0x45, SOUND_CTRL1 = 0x46, SOUND_CTRL2 = 0x47, SOUND_CTRL3 = 0x48, SOUND_CTRL4 = 0x49, SOUND_CTRL5 = 0x4A, SOUND_CTRL6 = 0x4B, SOUND_CTRL7 = 0x4C, SOUND_CTRL8 = 0x4D, SOUND_CTRL9 = 0x4E, SOUND_CTRL10 = 0x4F, GPC5 = 0x50, GPC6 = 0x51, GPC7 = 0x52, GPC8 = 0x53, PORTAMENTO_CTRL = 0x54, EFFECTS_DEPTH1 = 0x5B, EFFECTS_DEPTH2 = 0x5C, EFFECTS_DEPTH3 = 0x5D, EFFECTS_DEPTH4 = 0x5E, EFFECTS_DEPTH5 = 0x5F, DATA_ENTRY_INCR = 0x60, DATA_ENTRY_DECR = 0x61, NRPN_LSB = 0x62, NRPN_MSB = 0x63, RPN_LSB = 0x64, RPN_MSB = 0x65, ALL_SOUND_OFF = 0x78, ALL_CTRL_OFF = 0x79, LOCAL_CONTROL = 0x7A, ALL_NOTES_OFF = 0x7B, OMNI_OFF = 0x7C, OMNI_ON = 0x7D, POLY_OFF = 0x7E, POLY_ON = 0x7F }; /* General MIDI RPN event numbers (LSB, MSB = 0) */ enum midi_rpn_event { RPN_PITCH_BEND_RANGE = 0x00, RPN_CHANNEL_FINE_TUNE = 0x01, RPN_CHANNEL_COARSE_TUNE = 0x02, RPN_TUNING_PROGRAM_CHANGE = 0x03, RPN_TUNING_BANK_SELECT = 0x04, RPN_MODULATION_DEPTH_RANGE = 0x05 }; enum midi_meta_event { MIDI_TEXT = 0x01, MIDI_COPYRIGHT = 0x02, MIDI_TRACK_NAME = 0x03, MIDI_INST_NAME = 0x04, MIDI_LYRIC = 0x05, MIDI_MARKER = 0x06, MIDI_CUE_POINT = 0x07, MIDI_EOT = 0x2f, MIDI_SET_TEMPO = 0x51, MIDI_SMPTE_OFFSET = 0x54, MIDI_TIME_SIGNATURE = 0x58, MIDI_KEY_SIGNATURE = 0x59, MIDI_SEQUENCER_EVENT = 0x7f }; /* MIDI SYSEX useful manufacturer values */ enum midi_sysex_manuf { MIDI_SYSEX_MANUF_ROLAND = 0x41, /**< Roland manufacturer ID */ MIDI_SYSEX_UNIV_NON_REALTIME = 0x7E, /**< Universal non realtime message */ MIDI_SYSEX_UNIV_REALTIME = 0x7F /**< Universal realtime message */ }; #define MIDI_SYSEX_DEVICE_ID_ALL 0x7F /**< Device ID used in SYSEX messages to indicate all devices */ /* SYSEX sub-ID #1 which follows device ID */ #define MIDI_SYSEX_MIDI_TUNING_ID 0x08 /**< Sysex sub-ID #1 for MIDI tuning messages */ #define MIDI_SYSEX_GM_ID 0x09 /**< Sysex sub-ID #1 for General MIDI messages */ /** * SYSEX tuning message IDs. */ enum midi_sysex_tuning_msg_id { MIDI_SYSEX_TUNING_BULK_DUMP_REQ = 0x00, /**< Bulk tuning dump request (non-realtime) */ MIDI_SYSEX_TUNING_BULK_DUMP = 0x01, /**< Bulk tuning dump response (non-realtime) */ MIDI_SYSEX_TUNING_NOTE_TUNE = 0x02, /**< Tuning note change message (realtime) */ MIDI_SYSEX_TUNING_BULK_DUMP_REQ_BANK = 0x03, /**< Bulk tuning dump request (with bank, non-realtime) */ MIDI_SYSEX_TUNING_BULK_DUMP_BANK = 0x04, /**< Bulk tuning dump resonse (with bank, non-realtime) */ MIDI_SYSEX_TUNING_OCTAVE_DUMP_1BYTE = 0x05, /**< Octave tuning dump using 1 byte values (non-realtime) */ MIDI_SYSEX_TUNING_OCTAVE_DUMP_2BYTE = 0x06, /**< Octave tuning dump using 2 byte values (non-realtime) */ MIDI_SYSEX_TUNING_NOTE_TUNE_BANK = 0x07, /**< Tuning note change message (with bank, realtime/non-realtime) */ MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE = 0x08, /**< Octave tuning message using 1 byte values (realtime/non-realtime) */ MIDI_SYSEX_TUNING_OCTAVE_TUNE_2BYTE = 0x09 /**< Octave tuning message using 2 byte values (realtime/non-realtime) */ }; /* General MIDI sub-ID #2 */ #define MIDI_SYSEX_GM_ON 0x01 /**< Enable GM mode */ #define MIDI_SYSEX_GM_OFF 0x02 /**< Disable GM mode */ enum fluid_driver_status { FLUID_MIDI_READY, FLUID_MIDI_LISTENING, FLUID_MIDI_DONE }; /*************************************************************** * * TYPE DEFINITIONS & FUNCTION DECLARATIONS */ /* * fluid_midi_event_t */ struct _fluid_midi_event_t { fluid_midi_event_t *next; /* Link to next event */ void *paramptr; /* Pointer parameter (for SYSEX data), size is stored to param1, param2 indicates if pointer should be freed (dynamic if TRUE) */ unsigned int dtime; /* Delay (ticks) between this and previous event. midi tracks. */ unsigned int param1; /* First parameter */ unsigned int param2; /* Second parameter */ unsigned char type; /* MIDI event type */ unsigned char channel; /* MIDI channel */ }; /* * fluid_track_t */ struct _fluid_track_t { char *name; int num; fluid_midi_event_t *first; fluid_midi_event_t *cur; fluid_midi_event_t *last; unsigned int ticks; }; typedef struct _fluid_track_t fluid_track_t; #define fluid_track_eot(track) ((track)->cur == NULL) /* * fluid_playlist_item * Used as the `data' elements of the fluid_player.playlist. * Represents either a filename or a pre-loaded memory buffer. * Exactly one of `filename' and `buffer' is non-NULL. */ typedef struct { char *filename; /** Name of file (owned); NULL if data pre-loaded */ void *buffer; /** The MIDI file data (owned); NULL if filename */ size_t buffer_len; /** Number of bytes in buffer; 0 if filename */ } fluid_playlist_item; /* * fluid_player */ struct _fluid_player_t { int status; int ntracks; fluid_track_t *track[MAX_NUMBER_OF_TRACKS]; fluid_synth_t *synth; fluid_timer_t *system_timer; fluid_sample_timer_t *sample_timer; int loop; /* -1 = loop infinitely, otherwise times left to loop the playlist */ fluid_list_t *playlist; /* List of fluid_playlist_item* objects */ fluid_list_t *currentfile; /* points to an item in files, or NULL if not playing */ char send_program_change; /* should we ignore the program changes? */ char use_system_timer; /* if zero, use sample timers, otherwise use system clock timer */ char reset_synth_between_songs; /* 1 if system reset should be sent to the synth between songs. */ int seek_ticks; /* new position in tempo ticks (midi ticks) for seeking */ int start_ticks; /* the number of tempo ticks passed at the last tempo change */ int cur_ticks; /* the number of tempo ticks passed */ int begin_msec; /* the time (msec) of the beginning of the file */ int start_msec; /* the start time of the last tempo change */ int cur_msec; /* the current time */ int miditempo; /* as indicated by MIDI SetTempo: n 24th of a usec per midi-clock. bravo! */ double deltatime; /* milliseconds per midi tick. depends on set-tempo */ unsigned int division; handle_midi_event_func_t playback_callback; /* function fired on each midi event as it is played */ void *playback_userdata; /* pointer to user-defined data passed to playback_callback function */ }; void fluid_player_settings(fluid_settings_t *settings); /* * fluid_midi_file */ typedef struct { const char *buffer; /* Entire contents of MIDI file (borrowed) */ int buf_len; /* Length of buffer, in bytes */ int buf_pos; /* Current read position in contents buffer */ int eof; /* The "end of file" condition */ int running_status; int c; int type; int ntracks; int uses_smpte; unsigned int smpte_fps; unsigned int smpte_res; unsigned int division; /* If uses_SMPTE == 0 then division is ticks per beat (quarter-note) */ double tempo; /* Beats per second (SI rules =) */ int tracklen; int trackpos; int eot; int varlen; int dtime; } fluid_midi_file; #define FLUID_MIDI_PARSER_MAX_DATA_SIZE 1024 /**< Maximum size of MIDI parameters/data (largest is SYSEX data) */ /* * fluid_midi_parser_t */ struct _fluid_midi_parser_t { unsigned char status; /* Identifies the type of event, that is currently received ('Noteon', 'Pitch Bend' etc). */ unsigned char channel; /* The channel of the event that is received (in case of a channel event) */ unsigned int nr_bytes; /* How many bytes have been read for the current event? */ unsigned int nr_bytes_total; /* How many bytes does the current event type include? */ unsigned char data[FLUID_MIDI_PARSER_MAX_DATA_SIZE]; /* The parameters or SYSEX data */ fluid_midi_event_t event; /* The event, that is returned to the MIDI driver. */ }; #endif /* _FLUID_MIDI_H */ fluidsynth-2.1.1/src/midi/fluid_midi_router.c000066400000000000000000000726541362231004000212730ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* Original author: Markus Nentwig, nentwig@users.sourceforge.net * * Josh Green made it general purpose with a complete usable public API and * cleaned it up a bit. */ #include "fluid_midi_router.h" #include "fluid_midi.h" #include "fluid_synth.h" /* * fluid_midi_router */ struct _fluid_midi_router_t { fluid_mutex_t rules_mutex; fluid_midi_router_rule_t *rules[FLUID_MIDI_ROUTER_RULE_COUNT]; /* List of rules for each rule type */ fluid_midi_router_rule_t *free_rules; /* List of rules to free (was waiting for final events which were received) */ handle_midi_event_func_t event_handler; /* Callback function for generated events */ void *event_handler_data; /* One arg for the callback */ int nr_midi_channels; /* For clipping the midi channel */ }; struct _fluid_midi_router_rule_t { int chan_min; /* Channel window, for which this rule is valid */ int chan_max; fluid_real_t chan_mul; /* Channel multiplier (usually 0 or 1) */ int chan_add; /* Channel offset */ int par1_min; /* Parameter 1 window, for which this rule is valid */ int par1_max; fluid_real_t par1_mul; int par1_add; int par2_min; /* Parameter 2 window, for which this rule is valid */ int par2_max; fluid_real_t par2_mul; int par2_add; int pending_events; /* In case of noteon: How many keys are still down? */ char keys_cc[128]; /* Flags, whether a key is down / controller is set (sustain) */ fluid_midi_router_rule_t *next; /* next entry */ int waiting; /* Set to TRUE when rule has been deactivated but there are still pending_events */ }; /** * Create a new midi router. The default rules will pass all events unmodified. * @param settings Settings used to configure MIDI router * @param handler MIDI event callback. * @param event_handler_data Caller defined data pointer which gets passed to 'handler' * @return New MIDI router instance or NULL on error * * The MIDI handler callback should process the possibly filtered/modified MIDI * events from the MIDI router and forward them on to a synthesizer for example. * The function fluid_synth_handle_midi_event() can be used for \a handle and * a #fluid_synth_t passed as the \a event_handler_data parameter for this purpose. */ fluid_midi_router_t * new_fluid_midi_router(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data) { fluid_midi_router_t *router = NULL; int i; router = FLUID_NEW(fluid_midi_router_t); if(router == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(router, 0, sizeof(fluid_midi_router_t)); /* Retrieve the number of MIDI channels for range limiting */ fluid_settings_getint(settings, "synth.midi-channels", &router->nr_midi_channels); fluid_mutex_init(router->rules_mutex); router->event_handler = handler; router->event_handler_data = event_handler_data; /* Create default routing rules which pass all events unmodified */ for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { router->rules[i] = new_fluid_midi_router_rule(); if(!router->rules[i]) { goto error_recovery; } } return router; error_recovery: delete_fluid_midi_router(router); return NULL; } /** * Delete a MIDI router instance. * @param router MIDI router to delete * @return Returns #FLUID_OK on success, #FLUID_FAILED otherwise (only if NULL * \a router passed really) */ void delete_fluid_midi_router(fluid_midi_router_t *router) { fluid_midi_router_rule_t *rule; fluid_midi_router_rule_t *next_rule; int i; fluid_return_if_fail(router != NULL); for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { for(rule = router->rules[i]; rule; rule = next_rule) { next_rule = rule->next; FLUID_FREE(rule); } } fluid_mutex_destroy(router->rules_mutex); FLUID_FREE(router); } /** * Set a MIDI router to use default "unity" rules. Such a router will pass all * events unmodified. * @param router Router to set to default rules. * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ int fluid_midi_router_set_default_rules(fluid_midi_router_t *router) { fluid_midi_router_rule_t *new_rules[FLUID_MIDI_ROUTER_RULE_COUNT]; fluid_midi_router_rule_t *del_rules[FLUID_MIDI_ROUTER_RULE_COUNT]; fluid_midi_router_rule_t *rule, *next_rule, *prev_rule; int i, i2; fluid_return_val_if_fail(router != NULL, FLUID_FAILED); /* Allocate new default rules outside of lock */ for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { new_rules[i] = new_fluid_midi_router_rule(); if(!new_rules[i]) { /* Free already allocated rules */ for(i2 = 0; i2 < i; i2++) { delete_fluid_midi_router_rule(new_rules[i2]); } return FLUID_FAILED; } } fluid_mutex_lock(router->rules_mutex); /* ++ lock */ for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { del_rules[i] = NULL; prev_rule = NULL; /* Process existing rules */ for(rule = router->rules[i]; rule; rule = next_rule) { next_rule = rule->next; if(rule->pending_events == 0) /* Rule has no pending events? */ { /* Remove rule from rule list */ if(prev_rule) { prev_rule->next = next_rule; } else if(rule == router->rules[i]) { router->rules[i] = next_rule; } /* Prepend to delete list */ rule->next = del_rules[i]; del_rules[i] = rule; } else { rule->waiting = TRUE; /* Pending events, mark as waiting */ prev_rule = rule; } } /* Prepend new default rule */ new_rules[i]->next = router->rules[i]; router->rules[i] = new_rules[i]; } fluid_mutex_unlock(router->rules_mutex); /* -- unlock */ /* Free old rules outside of lock */ for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { for(rule = del_rules[i]; rule; rule = next_rule) { next_rule = rule->next; FLUID_FREE(rule); } } return FLUID_OK; } /** * Clear all rules in a MIDI router. Such a router will drop all events until * rules are added. * @param router Router to clear all rules from * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ int fluid_midi_router_clear_rules(fluid_midi_router_t *router) { fluid_midi_router_rule_t *del_rules[FLUID_MIDI_ROUTER_RULE_COUNT]; fluid_midi_router_rule_t *rule, *next_rule, *prev_rule; int i; fluid_return_val_if_fail(router != NULL, FLUID_FAILED); fluid_mutex_lock(router->rules_mutex); /* ++ lock */ for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { del_rules[i] = NULL; prev_rule = NULL; /* Process existing rules */ for(rule = router->rules[i]; rule; rule = next_rule) { next_rule = rule->next; if(rule->pending_events == 0) /* Rule has no pending events? */ { /* Remove rule from rule list */ if(prev_rule) { prev_rule->next = next_rule; } else if(rule == router->rules[i]) { router->rules[i] = next_rule; } /* Prepend to delete list */ rule->next = del_rules[i]; del_rules[i] = rule; } else { rule->waiting = TRUE; /* Pending events, mark as waiting */ prev_rule = rule; } } } fluid_mutex_unlock(router->rules_mutex); /* -- unlock */ /* Free old rules outside of lock */ for(i = 0; i < FLUID_MIDI_ROUTER_RULE_COUNT; i++) { for(rule = del_rules[i]; rule; rule = next_rule) { next_rule = rule->next; FLUID_FREE(rule); } } return FLUID_OK; } /** * Add a rule to a MIDI router. * @param router MIDI router * @param rule Rule to add (used directly and should not be accessed again following a * successful call to this function). * @param type The type of rule to add (#fluid_midi_router_rule_type) * @return #FLUID_OK on success, #FLUID_FAILED otherwise (invalid rule for example) * @since 1.1.0 */ int fluid_midi_router_add_rule(fluid_midi_router_t *router, fluid_midi_router_rule_t *rule, int type) { fluid_midi_router_rule_t *free_rules, *next_rule; fluid_return_val_if_fail(router != NULL, FLUID_FAILED); fluid_return_val_if_fail(rule != NULL, FLUID_FAILED); fluid_return_val_if_fail(type >= 0 && type < FLUID_MIDI_ROUTER_RULE_COUNT, FLUID_FAILED); fluid_mutex_lock(router->rules_mutex); /* ++ lock */ /* Take over free rules list, if any (to free outside of lock) */ free_rules = router->free_rules; router->free_rules = NULL; rule->next = router->rules[type]; router->rules[type] = rule; fluid_mutex_unlock(router->rules_mutex); /* -- unlock */ /* Free any deactivated rules which were waiting for events and are now done */ for(; free_rules; free_rules = next_rule) { next_rule = free_rules->next; FLUID_FREE(free_rules); } return FLUID_OK; } /** * Create a new MIDI router rule. * @return Newly allocated router rule or NULL if out of memory. * @since 1.1.0 * * The new rule is a "unity" rule which will accept any values and wont modify * them. */ fluid_midi_router_rule_t * new_fluid_midi_router_rule(void) { fluid_midi_router_rule_t *rule; rule = FLUID_NEW(fluid_midi_router_rule_t); if(rule == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(rule, 0, sizeof(fluid_midi_router_rule_t)); rule->chan_min = 0; rule->chan_max = 999999; rule->chan_mul = 1.0; rule->chan_add = 0; rule->par1_min = 0; rule->par1_max = 999999; rule->par1_mul = 1.0; rule->par1_add = 0; rule->par2_min = 0; rule->par2_max = 999999; rule->par2_mul = 1.0; rule->par2_add = 0; return rule; }; /** * Free a MIDI router rule. * @param rule Router rule to free * @since 1.1.0 * * Note that rules which have been added to a router are managed by the router, * so this function should seldom be needed. */ void delete_fluid_midi_router_rule(fluid_midi_router_rule_t *rule) { fluid_return_if_fail(rule != NULL); FLUID_FREE(rule); } /** * Set the channel portion of a rule. * @param rule MIDI router rule * @param min Minimum value for rule match * @param max Maximum value for rule match * @param mul Value which is multiplied by matching event's channel value (1.0 to not modify) * @param add Value which is added to matching event's channel value (0 to not modify) * @since 1.1.0 * * The \a min and \a max parameters define a channel range window to match * incoming events to. If \a min is less than or equal to \a max then an event * is matched if its channel is within the defined range (including \a min * and \a max). If \a min is greater than \a max then rule is inverted and matches * everything except in *between* the defined range (so \a min and \a max would match). * * The \a mul and \a add values are used to modify event channel values prior to * sending the event, if the rule matches. */ void fluid_midi_router_rule_set_chan(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add) { fluid_return_if_fail(rule != NULL); rule->chan_min = min; rule->chan_max = max; rule->chan_mul = mul; rule->chan_add = add; } /** * Set the first parameter portion of a rule. * @param rule MIDI router rule * @param min Minimum value for rule match * @param max Maximum value for rule match * @param mul Value which is multiplied by matching event's 1st parameter value (1.0 to not modify) * @param add Value which is added to matching event's 1st parameter value (0 to not modify) * @since 1.1.0 * * The 1st parameter of an event depends on the type of event. For note events * its the MIDI note #, for CC events its the MIDI control number, for program * change events its the MIDI program #, for pitch bend events its the bend value, * for channel pressure its the channel pressure value and for key pressure * its the MIDI note number. * * Pitch bend values have a maximum value of 16383 (8192 is pitch bend center) and all * other events have a max of 127. All events have a minimum value of 0. * * The \a min and \a max parameters define a parameter range window to match * incoming events to. If \a min is less than or equal to \a max then an event * is matched if its 1st parameter is within the defined range (including \a min * and \a max). If \a min is greater than \a max then rule is inverted and matches * everything except in *between* the defined range (so \a min and \a max would match). * * The \a mul and \a add values are used to modify event 1st parameter values prior to * sending the event, if the rule matches. */ void fluid_midi_router_rule_set_param1(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add) { fluid_return_if_fail(rule != NULL); rule->par1_min = min; rule->par1_max = max; rule->par1_mul = mul; rule->par1_add = add; } /** * Set the second parameter portion of a rule. * @param rule MIDI router rule * @param min Minimum value for rule match * @param max Maximum value for rule match * @param mul Value which is multiplied by matching event's 2nd parameter value (1.0 to not modify) * @param add Value which is added to matching event's 2nd parameter value (0 to not modify) * @since 1.1.0 * * The 2nd parameter of an event depends on the type of event. For note events * its the MIDI velocity, for CC events its the control value and for key pressure * events its the key pressure value. All other types lack a 2nd parameter. * * All applicable 2nd parameters have the range 0-127. * * The \a min and \a max parameters define a parameter range window to match * incoming events to. If \a min is less than or equal to \a max then an event * is matched if its 2nd parameter is within the defined range (including \a min * and \a max). If \a min is greater than \a max then rule is inverted and matches * everything except in *between* the defined range (so \a min and \a max would match). * * The \a mul and \a add values are used to modify event 2nd parameter values prior to * sending the event, if the rule matches. */ void fluid_midi_router_rule_set_param2(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add) { fluid_return_if_fail(rule != NULL); rule->par2_min = min; rule->par2_max = max; rule->par2_mul = mul; rule->par2_add = add; } /** * Handle a MIDI event through a MIDI router instance. * @param data MIDI router instance #fluid_midi_router_t, its a void * so that * this function can be used as a callback for other subsystems * (new_fluid_midi_driver() for example). * @param event MIDI event to handle * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * Purpose: The midi router is called for each event, that is received * via the 'physical' midi input. Each event can trigger an arbitrary number * of generated events (one for each rule that matches). * * In default mode, a noteon event is just forwarded to the synth's 'noteon' function, * a 'CC' event to the synth's 'CC' function and so on. * * The router can be used to: * - filter messages (for example: Pass sustain pedal CCs only to selected channels) * - split the keyboard (noteon with notenr < x: to ch 1, >x to ch 2) * - layer sounds (for each noteon received on ch 1, create a noteon on ch1, ch2, ch3,...) * - velocity scaling (for each noteon event, scale the velocity by 1.27 to give DX7 users * a chance) * - velocity switching ("v <=100: Angel Choir; V > 100: Hell's Bells") * - get rid of aftertouch * - ... */ int fluid_midi_router_handle_midi_event(void *data, fluid_midi_event_t *event) { fluid_midi_router_t *router = (fluid_midi_router_t *)data; fluid_midi_router_rule_t **rulep, *rule, *next_rule, *prev_rule = NULL; int event_has_par2 = 0; /* Flag, indicates that current event needs two parameters */ int par1_max = 127; /* Range limit for par1 */ int par2_max = 127; /* Range limit for par2 */ int ret_val = FLUID_OK; int chan; /* Channel of the generated event */ int par1; /* par1 of the generated event */ int par2; int event_par1; int event_par2; fluid_midi_event_t new_event; /* Some keyboards report noteoff through a noteon event with vel=0. * Convert those to noteoff to ease processing. */ if(event->type == NOTE_ON && event->param2 == 0) { event->type = NOTE_OFF; event->param2 = 127; /* Release velocity */ } fluid_mutex_lock(router->rules_mutex); /* ++ lock rules */ /* Depending on the event type, choose the correct list of rules. */ switch(event->type) { case NOTE_ON: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_NOTE]; event_has_par2 = 1; break; case NOTE_OFF: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_NOTE]; event_has_par2 = 1; break; case CONTROL_CHANGE: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_CC]; event_has_par2 = 1; break; case PROGRAM_CHANGE: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_PROG_CHANGE]; break; case PITCH_BEND: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_PITCH_BEND]; par1_max = 16383; break; case CHANNEL_PRESSURE: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_CHANNEL_PRESSURE]; break; case KEY_PRESSURE: rulep = &router->rules[FLUID_MIDI_ROUTER_RULE_KEY_PRESSURE]; event_has_par2 = 1; break; case MIDI_SYSTEM_RESET: case MIDI_SYSEX: ret_val = router->event_handler(router->event_handler_data, event); fluid_mutex_unlock(router->rules_mutex); /* -- unlock rules */ return ret_val; default: rulep = NULL; /* Event will not be passed on */ break; } /* Loop over rules in the list, looking for matches for this event. */ for(rule = rulep ? *rulep : NULL; rule; prev_rule = rule, rule = next_rule) { event_par1 = (int)event->param1; event_par2 = (int)event->param2; next_rule = rule->next; /* Rule may get removed from list, so get next here */ /* Channel window */ if(rule->chan_min > rule->chan_max) { /* Inverted rule: Exclude everything between max and min (but not min/max) */ if(event->channel > rule->chan_max && event->channel < rule->chan_min) { continue; } } else /* Normal rule: Exclude everything < max or > min (but not min/max) */ { if(event->channel > rule->chan_max || event->channel < rule->chan_min) { continue; } } /* Par 1 window */ if(rule->par1_min > rule->par1_max) { /* Inverted rule: Exclude everything between max and min (but not min/max) */ if(event_par1 > rule->par1_max && event_par1 < rule->par1_min) { continue; } } else /* Normal rule: Exclude everything < max or > min (but not min/max)*/ { if(event_par1 > rule->par1_max || event_par1 < rule->par1_min) { continue; } } /* Par 2 window (only applies to event types, which have 2 pars) * For noteoff events, velocity switching doesn't make any sense. * Velocity scaling might be useful, though. */ if(event_has_par2 && event->type != NOTE_OFF) { if(rule->par2_min > rule->par2_max) { /* Inverted rule: Exclude everything between max and min (but not min/max) */ if(event_par2 > rule->par2_max && event_par2 < rule->par2_min) { continue; } } else /* Normal rule: Exclude everything < max or > min (but not min/max)*/ { if(event_par2 > rule->par2_max || event_par2 < rule->par2_min) { continue; } } } /* Channel scaling / offset * Note: rule->chan_mul will probably be 0 or 1. If it's 0, input from all * input channels is mapped to the same synth channel. */ chan = rule->chan_add + (int)((fluid_real_t)event->channel * rule->chan_mul + (fluid_real_t)0.5); /* Par 1 scaling / offset */ par1 = rule->par1_add + (int)((fluid_real_t)event_par1 * rule->par1_mul + (fluid_real_t)0.5); /* Par 2 scaling / offset, if applicable */ if(event_has_par2) { par2 = rule->par2_add + (int)((fluid_real_t)event_par2 * rule->par2_mul + (fluid_real_t)0.5); } else { par2 = 0; } /* Channel range limiting */ if(chan < 0) { chan = 0; } else if(chan >= router->nr_midi_channels) { chan = router->nr_midi_channels - 1; } /* Par1 range limiting */ if(par1 < 0) { par1 = 0; } else if(par1 > par1_max) { par1 = par1_max; } /* Par2 range limiting */ if(event_has_par2) { if(par2 < 0) { par2 = 0; } else if(par2 > par2_max) { par2 = par2_max; } } /* At this point we have to create an event of event->type on 'chan' with par1 (maybe par2). * We keep track on the state of noteon and sustain pedal events. If the application tries * to delete a rule, it will only be fully removed, if pending noteoff / pedal off events have * arrived. In the meantime while waiting, it will only let through 'negative' events * (noteoff or pedal up). */ if(event->type == NOTE_ON || (event->type == CONTROL_CHANGE && par1 == SUSTAIN_SWITCH && par2 >= 64)) { /* Noteon or sustain pedal down event generated */ if(rule->keys_cc[par1] == 0) { rule->keys_cc[par1] = 1; rule->pending_events++; } } else if(event->type == NOTE_OFF || (event->type == CONTROL_CHANGE && par1 == SUSTAIN_SWITCH && par2 < 64)) { /* Noteoff or sustain pedal up event generated */ if(rule->keys_cc[par1] > 0) { rule->keys_cc[par1] = 0; rule->pending_events--; /* Rule is waiting for negative event to be destroyed? */ if(rule->waiting) { if(rule->pending_events == 0) { /* Remove rule from rule list */ if(prev_rule) { prev_rule->next = next_rule; } else { *rulep = next_rule; } /* Add to free list */ rule->next = router->free_rules; router->free_rules = rule; rule = prev_rule; /* Set rule to previous rule, which gets assigned to the next prev_rule value (in for() statement) */ } goto send_event; /* Pass the event to complete the cycle */ } } } /* Rule is still waiting for negative event? (note off or pedal up) */ if(rule->waiting) { continue; /* Skip (rule is inactive except for matching negative event) */ } send_event: /* At this point it is decided, what is sent to the synth. * Create a new event and make the appropriate call */ fluid_midi_event_set_type(&new_event, event->type); fluid_midi_event_set_channel(&new_event, chan); new_event.param1 = par1; new_event.param2 = par2; /* FIXME - What should be done on failure? For now continue to process events, but return failure to caller. */ if(router->event_handler(router->event_handler_data, &new_event) != FLUID_OK) { ret_val = FLUID_FAILED; } } fluid_mutex_unlock(router->rules_mutex); /* -- unlock rules */ return ret_val; } /** * MIDI event callback function to display event information to stdout * @param data MIDI router instance * @param event MIDI event data * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * An implementation of the #handle_midi_event_func_t function type, used for * displaying MIDI event information between the MIDI driver and router to * stdout. Useful for adding into a MIDI router chain for debugging MIDI events. */ int fluid_midi_dump_prerouter(void *data, fluid_midi_event_t *event) { switch(event->type) { case NOTE_ON: fprintf(stdout, "event_pre_noteon %i %i %i\n", event->channel, event->param1, event->param2); break; case NOTE_OFF: fprintf(stdout, "event_pre_noteoff %i %i %i\n", event->channel, event->param1, event->param2); break; case CONTROL_CHANGE: fprintf(stdout, "event_pre_cc %i %i %i\n", event->channel, event->param1, event->param2); break; case PROGRAM_CHANGE: fprintf(stdout, "event_pre_prog %i %i\n", event->channel, event->param1); break; case PITCH_BEND: fprintf(stdout, "event_pre_pitch %i %i\n", event->channel, event->param1); break; case CHANNEL_PRESSURE: fprintf(stdout, "event_pre_cpress %i %i\n", event->channel, event->param1); break; case KEY_PRESSURE: fprintf(stdout, "event_pre_kpress %i %i %i\n", event->channel, event->param1, event->param2); break; default: break; } return fluid_midi_router_handle_midi_event((fluid_midi_router_t *) data, event); } /** * MIDI event callback function to display event information to stdout * @param data MIDI router instance * @param event MIDI event data * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * An implementation of the #handle_midi_event_func_t function type, used for * displaying MIDI event information between the MIDI driver and router to * stdout. Useful for adding into a MIDI router chain for debugging MIDI events. */ int fluid_midi_dump_postrouter(void *data, fluid_midi_event_t *event) { switch(event->type) { case NOTE_ON: fprintf(stdout, "event_post_noteon %i %i %i\n", event->channel, event->param1, event->param2); break; case NOTE_OFF: fprintf(stdout, "event_post_noteoff %i %i %i\n", event->channel, event->param1, event->param2); break; case CONTROL_CHANGE: fprintf(stdout, "event_post_cc %i %i %i\n", event->channel, event->param1, event->param2); break; case PROGRAM_CHANGE: fprintf(stdout, "event_post_prog %i %i\n", event->channel, event->param1); break; case PITCH_BEND: fprintf(stdout, "event_post_pitch %i %i\n", event->channel, event->param1); break; case CHANNEL_PRESSURE: fprintf(stdout, "event_post_cpress %i %i\n", event->channel, event->param1); break; case KEY_PRESSURE: fprintf(stdout, "event_post_kpress %i %i %i\n", event->channel, event->param1, event->param2); break; default: break; } return fluid_synth_handle_midi_event((fluid_synth_t *) data, event); } fluidsynth-2.1.1/src/midi/fluid_midi_router.h000066400000000000000000000020261362231004000212620ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* Author: Markus Nentwig, nentwig@users.sourceforge.net */ #ifndef _FLUID_MIDIROUTER_H #define _FLUID_MIDIROUTER_H #include "fluidsynth_priv.h" #include "fluid_midi.h" #include "fluid_sys.h" #endif fluidsynth-2.1.1/src/midi/fluid_seq.c000066400000000000000000001041461362231004000175310ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* 2002 : API design by Peter Hanappe and Antoine Schmitt August 2002 : Implementation by Antoine Schmitt as@gratin.org as part of the infiniteCD author project http://www.infiniteCD.org/ */ #include "fluid_event.h" #include "fluid_sys.h" // timer, threads, etc... #include "fluid_list.h" /*************************************************************** * * SEQUENCER */ #define FLUID_SEQUENCER_EVENTS_MAX 1000 /* Private data for SEQUENCER */ struct _fluid_sequencer_t { unsigned int startMs; fluid_atomic_int_t currentMs; int useSystemTimer; double scale; // ticks per second fluid_list_t *clients; fluid_seq_id_t clientsID; /* for queue + heap */ fluid_evt_entry *preQueue; fluid_evt_entry *preQueueLast; fluid_timer_t *timer; int queue0StartTime; short prevCellNb; fluid_evt_entry *queue0[256][2]; fluid_evt_entry *queue1[255][2]; fluid_evt_entry *queueLater; fluid_evt_heap_t *heap; fluid_mutex_t mutex; }; /* Private data for clients */ typedef struct _fluid_sequencer_client_t { fluid_seq_id_t id; char *name; fluid_event_callback_t callback; void *data; } fluid_sequencer_client_t; /* prototypes */ static short _fluid_seq_queue_init(fluid_sequencer_t *seq, int nbEvents); static void _fluid_seq_queue_end(fluid_sequencer_t *seq); static short _fluid_seq_queue_pre_insert(fluid_sequencer_t *seq, fluid_event_t *evt); static void _fluid_seq_queue_pre_remove(fluid_sequencer_t *seq, fluid_seq_id_t src, fluid_seq_id_t dest, int type); static int _fluid_seq_queue_process(void *data, unsigned int msec); // callback from timer static void _fluid_seq_queue_insert_entry(fluid_sequencer_t *seq, fluid_evt_entry *evtentry); static void _fluid_seq_queue_remove_entries_matching(fluid_sequencer_t *seq, fluid_evt_entry *temp); static void _fluid_seq_queue_send_queued_events(fluid_sequencer_t *seq); static void _fluid_free_evt_queue(fluid_evt_entry **first, fluid_evt_entry **last); /* API implementation */ /** * Create a new sequencer object which uses the system timer. Use * new_fluid_sequencer2() to specify whether the system timer or * fluid_sequencer_process() is used to advance the sequencer. * @return New sequencer instance * @deprecated As of fluidsynth 2.1.1 the use of the system timer has been deprecated. */ fluid_sequencer_t * new_fluid_sequencer(void) { return new_fluid_sequencer2(TRUE); } /** * Create a new sequencer object. * @param use_system_timer If TRUE, sequencer will advance at the rate of the * system clock. If FALSE, call fluid_sequencer_process() to advance * the sequencer. * @return New sequencer instance * @since 1.1.0 * @note As of fluidsynth 2.1.1 the use of the system timer has been deprecated. */ fluid_sequencer_t * new_fluid_sequencer2(int use_system_timer) { fluid_sequencer_t *seq; if(use_system_timer) { FLUID_LOG(FLUID_WARN, "sequencer: Usage of the system timer has been deprecated!"); } seq = FLUID_NEW(fluid_sequencer_t); if(seq == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return NULL; } FLUID_MEMSET(seq, 0, sizeof(fluid_sequencer_t)); seq->scale = 1000; // default value seq->useSystemTimer = use_system_timer ? 1 : 0; seq->startMs = seq->useSystemTimer ? fluid_curtime() : 0; seq->clients = NULL; seq->clientsID = 0; if(-1 == _fluid_seq_queue_init(seq, FLUID_SEQUENCER_EVENTS_MAX)) { FLUID_FREE(seq); FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return NULL; } return(seq); } /** * Free a sequencer object. * @note Before fluidsynth 2.1.1 registered sequencer clients may not be fully freed by this function. * @param seq Sequencer to delete */ void delete_fluid_sequencer(fluid_sequencer_t *seq) { fluid_return_if_fail(seq != NULL); /* cleanup clients */ while(seq->clients) { fluid_sequencer_client_t *client = (fluid_sequencer_client_t *)seq->clients->data; fluid_sequencer_unregister_client(seq, client->id); } _fluid_seq_queue_end(seq); FLUID_FREE(seq); } /** * Check if a sequencer is using the system timer or not. * @param seq Sequencer object * @return TRUE if system timer is being used, FALSE otherwise. * @since 1.1.0 * @deprecated As of fluidsynth 2.1.1 the usage of the system timer has been deprecated. */ int fluid_sequencer_get_use_system_timer(fluid_sequencer_t *seq) { fluid_return_val_if_fail(seq != NULL, FALSE); return seq->useSystemTimer; } /* clients */ /** * Register a sequencer client. * @param seq Sequencer object * @param name Name of sequencer client * @param callback Sequencer client callback or NULL for a source client. * @param data User data to pass to the \a callback * @return Unique sequencer ID or #FLUID_FAILED on error * * Clients can be sources or destinations of events. Sources don't need to * register a callback. * * @note Implementations are encouraged to explicitly unregister any registered client with fluid_sequencer_unregister_client() before deleting the sequencer. */ fluid_seq_id_t fluid_sequencer_register_client(fluid_sequencer_t *seq, const char *name, fluid_event_callback_t callback, void *data) { fluid_sequencer_client_t *client; char *nameCopy; fluid_return_val_if_fail(seq != NULL, FLUID_FAILED); client = FLUID_NEW(fluid_sequencer_client_t); if(client == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return FLUID_FAILED; } nameCopy = FLUID_STRDUP(name); if(nameCopy == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); FLUID_FREE(client); return FLUID_FAILED; } seq->clientsID++; client->name = nameCopy; client->id = seq->clientsID; client->callback = callback; client->data = data; seq->clients = fluid_list_append(seq->clients, (void *)client); return (client->id); } /** * Unregister a previously registered client. * * The client's callback function will receive a FLUID_SEQ_UNREGISTERING event right before it is being unregistered. * @param seq Sequencer object * @param id Client ID as returned by fluid_sequencer_register_client(). */ void fluid_sequencer_unregister_client(fluid_sequencer_t *seq, fluid_seq_id_t id) { fluid_list_t *tmp; fluid_event_t evt; unsigned int now = fluid_sequencer_get_tick(seq); fluid_return_if_fail(seq != NULL); fluid_event_clear(&evt); fluid_event_unregistering(&evt); fluid_event_set_dest(&evt, id); fluid_event_set_time(&evt, now); tmp = seq->clients; while(tmp) { fluid_sequencer_client_t *client = (fluid_sequencer_client_t *)tmp->data; if(client->id == id) { // client found, remove it from the list to avoid recursive call when calling callback seq->clients = fluid_list_remove_link(seq->clients, tmp); // call the callback (if any), to free underlying memory (e.g. seqbind structure) if (client->callback != NULL) { (client->callback)(now, &evt, seq, client->data); } if(client->name) { FLUID_FREE(client->name); } delete1_fluid_list(tmp); FLUID_FREE(client); return; } tmp = tmp->next; } return; } /** * Count a sequencers registered clients. * @param seq Sequencer object * @return Count of sequencer clients. */ int fluid_sequencer_count_clients(fluid_sequencer_t *seq) { if(seq == NULL || seq->clients == NULL) { return 0; } return fluid_list_size(seq->clients); } /** * Get a client ID from its index (order in which it was registered). * @param seq Sequencer object * @param index Index of register client * @return Client ID or #FLUID_FAILED if not found */ fluid_seq_id_t fluid_sequencer_get_client_id(fluid_sequencer_t *seq, int index) { fluid_list_t *tmp; fluid_return_val_if_fail(seq != NULL, FLUID_FAILED); fluid_return_val_if_fail(index >= 0, FLUID_FAILED); tmp = fluid_list_nth(seq->clients, index); if(tmp == NULL) { return FLUID_FAILED; } else { fluid_sequencer_client_t *client = (fluid_sequencer_client_t *)tmp->data; return client->id; } } /** * Get the name of a registered client. * @param seq Sequencer object * @param id Client ID * @return Client name or NULL if not found. String is internal and should not * be modified or freed. */ char * fluid_sequencer_get_client_name(fluid_sequencer_t *seq, fluid_seq_id_t id) { fluid_list_t *tmp; fluid_return_val_if_fail(seq != NULL, NULL); tmp = seq->clients; while(tmp) { fluid_sequencer_client_t *client = (fluid_sequencer_client_t *)tmp->data; if(client->id == id) { return client->name; } tmp = tmp->next; } return NULL; } /** * Check if a client is a destination client. * @param seq Sequencer object * @param id Client ID * @return TRUE if client is a destination client, FALSE otherwise or if not found */ int fluid_sequencer_client_is_dest(fluid_sequencer_t *seq, fluid_seq_id_t id) { fluid_list_t *tmp; fluid_return_val_if_fail(seq != NULL, FALSE); tmp = seq->clients; while(tmp) { fluid_sequencer_client_t *client = (fluid_sequencer_client_t *)tmp->data; if(client->id == id) { return (client->callback != NULL); } tmp = tmp->next; } return FALSE; } /** * Send an event immediately. * @param seq Sequencer object * @param evt Event to send (not copied, used directly) */ void fluid_sequencer_send_now(fluid_sequencer_t *seq, fluid_event_t *evt) { fluid_seq_id_t destID; fluid_list_t *tmp; fluid_return_if_fail(seq != NULL); fluid_return_if_fail(evt != NULL); destID = fluid_event_get_dest(evt); /* find callback */ tmp = seq->clients; while(tmp) { fluid_sequencer_client_t *dest = (fluid_sequencer_client_t *)tmp->data; if(dest->id == destID) { if(fluid_event_get_type(evt) == FLUID_SEQ_UNREGISTERING) { fluid_sequencer_unregister_client(seq, destID); } else { if(dest->callback) { (dest->callback)(fluid_sequencer_get_tick(seq), evt, seq, dest->data); } } return; } tmp = tmp->next; } } /** * Schedule an event for sending at a later time. * @param seq Sequencer object * @param evt Event to send (will be copied into internal queue) * @param time Time value in ticks (in milliseconds with the default time scale of 1000). * @param absolute TRUE if \a time is absolute sequencer time (time since sequencer * creation), FALSE if relative to current time. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_sequencer_send_at(fluid_sequencer_t *seq, fluid_event_t *evt, unsigned int time, int absolute) { unsigned int now = fluid_sequencer_get_tick(seq); fluid_return_val_if_fail(seq != NULL, FLUID_FAILED); fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); /* set absolute */ if(!absolute) { time = now + time; } /* time stamp event */ fluid_event_set_time(evt, time); /* queue for processing later */ return _fluid_seq_queue_pre_insert(seq, evt); } /** * Remove events from the event queue. * @param seq Sequencer object * @param source Source client ID to match or -1 for wildcard * @param dest Destination client ID to match or -1 for wildcard * @param type Event type to match or -1 for wildcard (#fluid_seq_event_type) */ void fluid_sequencer_remove_events(fluid_sequencer_t *seq, fluid_seq_id_t source, fluid_seq_id_t dest, int type) { fluid_return_if_fail(seq != NULL); _fluid_seq_queue_pre_remove(seq, source, dest, type); } /************************************* time **************************************/ /** * Get the current tick of a sequencer. * @param seq Sequencer object * @return Current tick value */ unsigned int fluid_sequencer_get_tick(fluid_sequencer_t *seq) { unsigned int absMs; double nowFloat; unsigned int now; fluid_return_val_if_fail(seq != NULL, 0u); absMs = seq->useSystemTimer ? (int) fluid_curtime() : fluid_atomic_int_get(&seq->currentMs); nowFloat = ((double)(absMs - seq->startMs)) * seq->scale / 1000.0f; now = nowFloat; return now; } /** * Set the time scale of a sequencer. * @param seq Sequencer object * @param scale Sequencer scale value in ticks per second * (default is 1000 for 1 tick per millisecond, max is 1000.0) * * If there are already scheduled events in the sequencer and the scale is changed * the events are adjusted accordingly. */ void fluid_sequencer_set_time_scale(fluid_sequencer_t *seq, double scale) { fluid_return_if_fail(seq != NULL); if(scale <= 0) { FLUID_LOG(FLUID_WARN, "sequencer: scale <= 0 : %f\n", scale); return; } if(scale > 1000.0) // Otherwise : problems with the timer = 0ms... { scale = 1000.0; } if(seq->scale != scale) { double oldScale = seq->scale; // stop timer if(seq->timer) { delete_fluid_timer(seq->timer); seq->timer = NULL; } seq->scale = scale; // change start0 so that cellNb is preserved seq->queue0StartTime = (seq->queue0StartTime + seq->prevCellNb) * (seq->scale / oldScale) - seq->prevCellNb; // change all preQueue events for new scale { fluid_evt_entry *tmp; tmp = seq->preQueue; while(tmp) { if(tmp->entryType == FLUID_EVT_ENTRY_INSERT) { tmp->evt.time = tmp->evt.time * seq->scale / oldScale; } tmp = tmp->next; } } /* re-start timer */ if(seq->useSystemTimer) { seq->timer = new_fluid_timer((int)(1000 / seq->scale), _fluid_seq_queue_process, (void *)seq, TRUE, FALSE, TRUE); } } } /** * Get a sequencer's time scale. * @param seq Sequencer object. * @return Time scale value in ticks per second. */ double fluid_sequencer_get_time_scale(fluid_sequencer_t *seq) { fluid_return_val_if_fail(seq != NULL, 0); return seq->scale; } /********************** the queue **********************/ /* The queue stores all future events to be processed. Data structures There is a heap, allocated at init time, for managing a pool of event entries, that is description of an event, its time, and whether it is a normal event or a removal command. The queue is separated in two arrays, and a list. The first array 'queue0' corresponds to the events to be sent in the next 256 ticks (0 to 255), the second array 'queue1' contains the events to be send from now+256 to now+65535. The list called 'queueLater' contains the events to be sent later than that. In each array, one cell contains a list of events having the same time (in the queue0 array), or the same time/256 (in the queue1 array), and a pointer to the last event in the list of the cell so as to be able to insert fast at the end of the list (i.e. a cell = 2 pointers). The 'queueLater' list is ordered by time and by post time. This way, inserting 'soon' events is fast (below 65535 ticks, that is about 1 minute if 1 tick=1ms). Inserting later events is more slow, but this is a realtime engine, isn't it ? The queue0 starts at queue0StartTime. When 256 ticks have elapsed, the queue0 array is emptied, and the first cell of the queue1 array is expanded in the queue0 array, according to the time of each event. The queue1 array is shifted to the left, and the first events of the queueLater list are inserte in the last cell of the queue1 array. We remember the previously managed cell in queue0 in the prevCellNb variable. When processing the current cell, we process the events in between (late events). Functions The main thread functions first get an event entry from the heap, and copy the given event into it, then merely enqueue it in a preQueue. This is in order to protect the data structure: everything is managed in the callback (thread or interrupt, depending on the architecture). All queue data structure management is done in a timer callback: '_fluid_seq_queue_process'. The _fluid_seq_queue_process function first process the preQueue, inserting or removing event entries from the queue, then processes the queue, by sending events ready to be sent at the current time. Critical sections between the main thread (or app) and the sequencer thread (or interrupt) are: - the heap management (if two threads get a free event at the same time) - the preQueue access. These are really small and fast sections (merely a pointer or two changing value). They are not protected by a mutex for now (August 2002). Waiting for crossplatform mutex solutions. When changing this code, beware that the _fluid_seq_queue_pre_insert function may be called by the callback of the queue thread (ex : a note event inserts a noteoff event). */ /********************/ /* API */ /********************/ static short _fluid_seq_queue_init(fluid_sequencer_t *seq, int maxEvents) { seq->heap = _fluid_evt_heap_init(maxEvents); if(seq->heap == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return -1; } seq->preQueue = NULL; seq->preQueueLast = NULL; FLUID_MEMSET(seq->queue0, 0, 2 * 256 * sizeof(fluid_evt_entry *)); FLUID_MEMSET(seq->queue1, 0, 2 * 255 * sizeof(fluid_evt_entry *)); seq->queueLater = NULL; seq->queue0StartTime = fluid_sequencer_get_tick(seq); seq->prevCellNb = -1; fluid_mutex_init(seq->mutex); /* start timer */ if(seq->useSystemTimer) { seq->timer = new_fluid_timer((int)(1000 / seq->scale), _fluid_seq_queue_process, (void *)seq, TRUE, FALSE, TRUE); } return (0); } static void _fluid_seq_queue_end(fluid_sequencer_t *seq) { int i; /* free all remaining events */ _fluid_free_evt_queue(&seq->preQueue, &seq->preQueueLast); for(i = 0; i < 256; i++) { _fluid_free_evt_queue(&(seq->queue0[i][0]), &(seq->queue0[i][1])); } for(i = 0; i < 255; i++) { _fluid_free_evt_queue(&(seq->queue1[i][0]), &(seq->queue1[i][1])); } _fluid_free_evt_queue(&seq->queueLater, NULL); if(seq->timer) { delete_fluid_timer(seq->timer); seq->timer = NULL; } if(seq->heap) { _fluid_evt_heap_free(seq->heap); seq->heap = NULL; } fluid_mutex_destroy(seq->mutex); } /********************/ /* queue management */ /********************/ /* Create event_entry and append to the preQueue. * May be called from the main thread (usually) but also recursively * from the queue thread, when a callback itself does an insert... */ static short _fluid_seq_queue_pre_insert(fluid_sequencer_t *seq, fluid_event_t *evt) { fluid_evt_entry *evtentry = _fluid_seq_heap_get_free(seq->heap); if(evtentry == NULL) { /* should not happen */ FLUID_LOG(FLUID_PANIC, "sequencer: no more free events\n"); return -1; } evtentry->next = NULL; evtentry->entryType = FLUID_EVT_ENTRY_INSERT; FLUID_MEMCPY(&(evtentry->evt), evt, sizeof(fluid_event_t)); fluid_mutex_lock(seq->mutex); /* append to preQueue */ if(seq->preQueueLast) { seq->preQueueLast->next = evtentry; } else { seq->preQueue = evtentry; } seq->preQueueLast = evtentry; fluid_mutex_unlock(seq->mutex); return (0); } /* Create event_entry and append to the preQueue. * May be called from the main thread (usually) but also recursively * from the queue thread, when a callback itself does an insert... */ static void _fluid_seq_queue_pre_remove(fluid_sequencer_t *seq, fluid_seq_id_t src, fluid_seq_id_t dest, int type) { fluid_evt_entry *evtentry = _fluid_seq_heap_get_free(seq->heap); if(evtentry == NULL) { /* should not happen */ FLUID_LOG(FLUID_PANIC, "sequencer: no more free events\n"); return; } evtentry->next = NULL; evtentry->entryType = FLUID_EVT_ENTRY_REMOVE; { fluid_event_t *evt = &(evtentry->evt); fluid_event_set_source(evt, src); fluid_event_set_source(evt, src); fluid_event_set_dest(evt, dest); evt->type = type; } fluid_mutex_lock(seq->mutex); /* append to preQueue */ if(seq->preQueueLast) { seq->preQueueLast->next = evtentry; } else { seq->preQueue = evtentry; } seq->preQueueLast = evtentry; fluid_mutex_unlock(seq->mutex); return; } static void _fluid_free_evt_queue(fluid_evt_entry **first, fluid_evt_entry **last) { fluid_evt_entry *tmp2; fluid_evt_entry *tmp = *first; while(tmp != NULL) { tmp2 = tmp->next; FLUID_FREE(tmp); tmp = tmp2; } *first = NULL; if(last != NULL) { *last = NULL; } } /* Callback from timer (may be in a different thread, or in an interrupt) */ static int _fluid_seq_queue_process(void *data, unsigned int msec) { fluid_sequencer_t *seq = (fluid_sequencer_t *)data; fluid_sequencer_process(seq, msec); /* continue timer */ return 1; } /** * Advance a sequencer. * * If you have registered the synthesizer as client (fluid_sequencer_register_fluidsynth()), the synth * will take care of calling fluid_sequencer_process(). Otherwise it is up to the user to * advance the sequencer manually. * @param seq Sequencer object * @param msec Time to advance sequencer to (absolute time since sequencer start). * @since 1.1.0 */ void fluid_sequencer_process(fluid_sequencer_t *seq, unsigned int msec) { /* process prequeue */ fluid_evt_entry *tmp; fluid_evt_entry *next; fluid_mutex_lock(seq->mutex); /* get the preQueue */ tmp = seq->preQueue; seq->preQueue = NULL; seq->preQueueLast = NULL; fluid_mutex_unlock(seq->mutex); /* walk all the preQueue and process them in order : inserts and removes */ while(tmp) { next = tmp->next; if(tmp->entryType == FLUID_EVT_ENTRY_REMOVE) { _fluid_seq_queue_remove_entries_matching(seq, tmp); } else { _fluid_seq_queue_insert_entry(seq, tmp); } tmp = next; } /* send queued events */ fluid_atomic_int_set(&seq->currentMs, msec); _fluid_seq_queue_send_queued_events(seq); } #if 0 static void _fluid_seq_queue_print_later(fluid_sequencer_t *seq) { int count = 0; fluid_evt_entry *tmp = seq->queueLater; printf("queueLater:\n"); while(tmp) { unsigned int delay = tmp->evt.time - seq->queue0StartTime; printf("queueLater: Delay = %i\n", delay); tmp = tmp->next; count++; } printf("queueLater: Total of %i events\n", count); } #endif static void _fluid_seq_queue_insert_queue0(fluid_sequencer_t *seq, fluid_evt_entry *tmp, int cell) { if(seq->queue0[cell][1] == NULL) { seq->queue0[cell][1] = seq->queue0[cell][0] = tmp; } else { seq->queue0[cell][1]->next = tmp; seq->queue0[cell][1] = tmp; } tmp->next = NULL; } static void _fluid_seq_queue_insert_queue1(fluid_sequencer_t *seq, fluid_evt_entry *tmp, int cell) { if(seq->queue1[cell][1] == NULL) { seq->queue1[cell][1] = seq->queue1[cell][0] = tmp; } else { seq->queue1[cell][1]->next = tmp; seq->queue1[cell][1] = tmp; } tmp->next = NULL; } static void _fluid_seq_queue_insert_queue_later(fluid_sequencer_t *seq, fluid_evt_entry *evtentry) { fluid_evt_entry *prev; fluid_evt_entry *tmp; unsigned int time = evtentry->evt.time; /* insert in 'queueLater', after the ones that have the same * time */ /* first? */ if((seq->queueLater == NULL) || (seq->queueLater->evt.time > time)) { evtentry->next = seq->queueLater; seq->queueLater = evtentry; return; } /* walk queueLater */ /* this is the only slow thing : if the event is more than 65535 ticks after the current time */ prev = seq->queueLater; tmp = prev->next; while(tmp) { if(tmp->evt.time > time) { /* insert before tmp */ evtentry->next = tmp; prev->next = evtentry; return; } prev = tmp; tmp = prev->next; } /* last */ evtentry->next = NULL; prev->next = evtentry; } static void _fluid_seq_queue_insert_entry(fluid_sequencer_t *seq, fluid_evt_entry *evtentry) { /* time is relative to seq origin, in ticks */ fluid_event_t *evt = &(evtentry->evt); unsigned int time = evt->time; unsigned int delay; if(seq->queue0StartTime > 0) { /* queue0StartTime could be < 0 if the scale changed a lot early, breaking the following comparison */ if(time < (unsigned int)seq->queue0StartTime) { /* we are late, send now */ fluid_sequencer_send_now(seq, evt); _fluid_seq_heap_set_free(seq->heap, evtentry); return; } } if(seq->prevCellNb >= 0) { /* prevCellNb could be -1 is seq was just started - unlikely */ /* prevCellNb can also be -1 if cellNb was reset to 0 in _fluid_seq_queue_send_queued_events() */ if(time <= (unsigned int)(seq->queue0StartTime + seq->prevCellNb)) { /* we are late, send now */ fluid_sequencer_send_now(seq, evt); _fluid_seq_heap_set_free(seq->heap, evtentry); return; } } delay = time - seq->queue0StartTime; if(delay > 65535) { _fluid_seq_queue_insert_queue_later(seq, evtentry); } else if(delay > 255) { _fluid_seq_queue_insert_queue1(seq, evtentry, delay / 256 - 1); } else { _fluid_seq_queue_insert_queue0(seq, evtentry, delay); } } static int _fluid_seq_queue_matchevent(fluid_event_t *evt, int templType, fluid_seq_id_t templSrc, fluid_seq_id_t templDest) { int eventType; if(templSrc != -1 && templSrc != fluid_event_get_source(evt)) { return 0; } if(templDest != -1 && templDest != fluid_event_get_dest(evt)) { return 0; } if(templType == -1) { return 1; } eventType = fluid_event_get_type(evt); if(templType == eventType) { return 1; } if(templType == FLUID_SEQ_ANYCONTROLCHANGE) { if(eventType == FLUID_SEQ_PITCHBEND || eventType == FLUID_SEQ_MODULATION || eventType == FLUID_SEQ_SUSTAIN || eventType == FLUID_SEQ_PAN || eventType == FLUID_SEQ_VOLUME || eventType == FLUID_SEQ_REVERBSEND || eventType == FLUID_SEQ_CONTROLCHANGE || eventType == FLUID_SEQ_CHORUSSEND) { return 1; } } return 0; } static void _fluid_seq_queue_remove_entries_matching(fluid_sequencer_t *seq, fluid_evt_entry *templ) { /* we walk everything : this is slow, but that is life */ int i, type; fluid_seq_id_t src, dest; src = templ->evt.src; dest = templ->evt.dest; type = templ->evt.type; /* we can set it free now */ _fluid_seq_heap_set_free(seq->heap, templ); /* queue0 */ for(i = 0 ; i < 256 ; i++) { fluid_evt_entry *tmp = seq->queue0[i][0]; fluid_evt_entry *prev = NULL; while(tmp) { /* remove and/or walk */ if(_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) { /* remove */ if(prev) { prev->next = tmp->next; if(tmp == seq->queue0[i][1]) // last one in list { seq->queue0[i][1] = prev; } _fluid_seq_heap_set_free(seq->heap, tmp); tmp = prev->next; } else { /* first one in list */ seq->queue0[i][0] = tmp->next; if(tmp == seq->queue0[i][1]) // last one in list { seq->queue0[i][1] = NULL; } _fluid_seq_heap_set_free(seq->heap, tmp); tmp = seq->queue0[i][0]; } } else { prev = tmp; tmp = prev->next; } } } /* queue1 */ for(i = 0 ; i < 255 ; i++) { fluid_evt_entry *tmp = seq->queue1[i][0]; fluid_evt_entry *prev = NULL; while(tmp) { if(_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) { /* remove */ if(prev) { prev->next = tmp->next; if(tmp == seq->queue1[i][1]) // last one in list { seq->queue1[i][1] = prev; } _fluid_seq_heap_set_free(seq->heap, tmp); tmp = prev->next; } else { /* first one in list */ seq->queue1[i][0] = tmp->next; if(tmp == seq->queue1[i][1]) // last one in list { seq->queue1[i][1] = NULL; } _fluid_seq_heap_set_free(seq->heap, tmp); tmp = seq->queue1[i][0]; } } else { prev = tmp; tmp = prev->next; } } } /* queueLater */ { fluid_evt_entry *tmp = seq->queueLater; fluid_evt_entry *prev = NULL; while(tmp) { if(_fluid_seq_queue_matchevent((&tmp->evt), type, src, dest)) { /* remove */ if(prev) { prev->next = tmp->next; _fluid_seq_heap_set_free(seq->heap, tmp); tmp = prev->next; } else { seq->queueLater = tmp->next; _fluid_seq_heap_set_free(seq->heap, tmp); tmp = seq->queueLater; } } else { prev = tmp; tmp = prev->next; } } } } static void _fluid_seq_queue_send_cell_events(fluid_sequencer_t *seq, int cellNb) { fluid_evt_entry *next; fluid_evt_entry *tmp; tmp = seq->queue0[cellNb][0]; while(tmp) { fluid_sequencer_send_now(seq, &(tmp->evt)); next = tmp->next; _fluid_seq_heap_set_free(seq->heap, tmp); tmp = next; } seq->queue0[cellNb][0] = NULL; seq->queue0[cellNb][1] = NULL; } static void _fluid_seq_queue_slide(fluid_sequencer_t *seq) { short i; fluid_evt_entry *next; fluid_evt_entry *tmp; int count = 0; /* do the slide */ seq->queue0StartTime += 256; /* sort all queue1[0] into queue0 according to new queue0StartTime */ tmp = seq->queue1[0][0]; while(tmp) { unsigned int delay = tmp->evt.time - seq->queue0StartTime; next = tmp->next; if(delay > 255) { /* should not happen !! */ /* append it to queue1[1] */ _fluid_seq_queue_insert_queue1(seq, tmp, 1); } else { _fluid_seq_queue_insert_queue0(seq, tmp, delay); } tmp = next; count++; } /* slide all queue1[i] into queue1[i-1] */ for(i = 1 ; i < 255 ; i++) { seq->queue1[i - 1][0] = seq->queue1[i][0]; seq->queue1[i - 1][1] = seq->queue1[i][1]; } seq->queue1[254][0] = NULL; seq->queue1[254][1] = NULL; /* append queueLater to queue1[254] */ count = 0; tmp = seq->queueLater; while(tmp) { unsigned int delay = tmp->evt.time - seq->queue0StartTime; if(delay > 65535) { break; } next = tmp->next; /* append it */ _fluid_seq_queue_insert_queue1(seq, tmp, 254); tmp = next; count++; } seq->queueLater = tmp; } static void _fluid_seq_queue_send_queued_events(fluid_sequencer_t *seq) { unsigned int nowTicks = fluid_sequencer_get_tick(seq); short cellNb; cellNb = seq->prevCellNb + 1; while(cellNb <= (int)(nowTicks - seq->queue0StartTime)) { if(cellNb == 256) { cellNb = 0; _fluid_seq_queue_slide(seq); } /* slide */ /* process queue0[cellNb] */ _fluid_seq_queue_send_cell_events(seq, cellNb); /* the current scale may have changed through a callback event */ nowTicks = fluid_sequencer_get_tick(seq); /* next cell */ cellNb++; } seq->prevCellNb = cellNb - 1; } fluidsynth-2.1.1/src/midi/fluid_seqbind.c000066400000000000000000000265131362231004000203670ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* 2002 : API design by Peter Hanappe and Antoine Schmitt August 2002 : Implementation by Antoine Schmitt as@gratin.org as part of the infiniteCD author project http://www.infiniteCD.org/ */ #include "fluidsynth_priv.h" #include "fluid_synth.h" #include "fluid_midi.h" #include "fluid_event.h" /*************************************************************** * * SEQUENCER BINDING */ struct _fluid_seqbind_t { fluid_synth_t *synth; fluid_sequencer_t *seq; fluid_sample_timer_t *sample_timer; fluid_seq_id_t client_id; }; typedef struct _fluid_seqbind_t fluid_seqbind_t; int fluid_seqbind_timer_callback(void *data, unsigned int msec); void fluid_seq_fluidsynth_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data); /* Proper cleanup of the seqbind struct. */ void delete_fluid_seqbind(fluid_seqbind_t *seqbind) { fluid_return_if_fail(seqbind != NULL); if((seqbind->client_id != -1) && (seqbind->seq != NULL)) { fluid_sequencer_unregister_client(seqbind->seq, seqbind->client_id); seqbind->client_id = -1; } if((seqbind->sample_timer != NULL) && (seqbind->synth != NULL)) { delete_fluid_sample_timer(seqbind->synth, seqbind->sample_timer); seqbind->sample_timer = NULL; } FLUID_FREE(seqbind); } /** * Registers a synthesizer as a destination client of the given sequencer. * The \a synth is registered with the name "fluidsynth". * * @note Implementations are encouraged to explicitly unregister this client either by calling * fluid_sequencer_unregister_client() or by sending an unregistering event to the sequencer. Before * fluidsynth 2.1.1 this was mandatory to avoid memory leaks. @code{.cpp} fluid_seq_id_t seqid = fluid_sequencer_register_fluidsynth(seq, synth); // ... do work fluid_event_t* evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, seqid); fluid_event_unregistering(evt); // unregister the "fluidsynth" client immediately fluid_sequencer_send_now(seq, evt); delete_fluid_event(evt); delete_fluid_synth(synth); delete_fluid_sequencer(seq); @endcode * * @param seq Sequencer instance * @param synth Synthesizer instance * @returns Sequencer client ID, or #FLUID_FAILED on error. */ fluid_seq_id_t fluid_sequencer_register_fluidsynth(fluid_sequencer_t *seq, fluid_synth_t *synth) { fluid_seqbind_t *seqbind; fluid_return_val_if_fail(seq != NULL, FLUID_FAILED); fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); seqbind = FLUID_NEW(fluid_seqbind_t); if(seqbind == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return FLUID_FAILED; } FLUID_MEMSET(seqbind, 0, sizeof(*seqbind)); seqbind->client_id = -1; seqbind->synth = synth; seqbind->seq = seq; /* set up the sample timer */ if(!fluid_sequencer_get_use_system_timer(seq)) { seqbind->sample_timer = new_fluid_sample_timer(synth, fluid_seqbind_timer_callback, (void *) seqbind); if(seqbind->sample_timer == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); FLUID_FREE(seqbind); return FLUID_FAILED; } } /* register fluidsynth itself */ seqbind->client_id = fluid_sequencer_register_client(seq, "fluidsynth", fluid_seq_fluidsynth_callback, (void *)seqbind); if(seqbind->client_id == FLUID_FAILED) { delete_fluid_sample_timer(seqbind->synth, seqbind->sample_timer); FLUID_FREE(seqbind); return FLUID_FAILED; } return seqbind->client_id; } /* Callback for sample timer */ int fluid_seqbind_timer_callback(void *data, unsigned int msec) { fluid_seqbind_t *seqbind = (fluid_seqbind_t *) data; fluid_sequencer_process(seqbind->seq, msec); return 1; } /* Callback for midi events */ void fluid_seq_fluidsynth_callback(unsigned int time, fluid_event_t *evt, fluid_sequencer_t *seq, void *data) { fluid_synth_t *synth; fluid_seqbind_t *seqbind = (fluid_seqbind_t *) data; synth = seqbind->synth; switch(fluid_event_get_type(evt)) { case FLUID_SEQ_NOTEON: fluid_synth_noteon(synth, fluid_event_get_channel(evt), fluid_event_get_key(evt), fluid_event_get_velocity(evt)); break; case FLUID_SEQ_NOTEOFF: fluid_synth_noteoff(synth, fluid_event_get_channel(evt), fluid_event_get_key(evt)); break; case FLUID_SEQ_NOTE: { unsigned int dur; fluid_synth_noteon(synth, fluid_event_get_channel(evt), fluid_event_get_key(evt), fluid_event_get_velocity(evt)); dur = fluid_event_get_duration(evt); fluid_event_noteoff(evt, fluid_event_get_channel(evt), fluid_event_get_key(evt)); fluid_sequencer_send_at(seq, evt, dur, 0); } break; case FLUID_SEQ_ALLSOUNDSOFF: fluid_synth_all_sounds_off(synth, fluid_event_get_channel(evt)); break; case FLUID_SEQ_ALLNOTESOFF: fluid_synth_all_notes_off(synth, fluid_event_get_channel(evt)); break; case FLUID_SEQ_BANKSELECT: fluid_synth_bank_select(synth, fluid_event_get_channel(evt), fluid_event_get_bank(evt)); break; case FLUID_SEQ_PROGRAMCHANGE: fluid_synth_program_change(synth, fluid_event_get_channel(evt), fluid_event_get_program(evt)); break; case FLUID_SEQ_PROGRAMSELECT: fluid_synth_program_select(synth, fluid_event_get_channel(evt), fluid_event_get_sfont_id(evt), fluid_event_get_bank(evt), fluid_event_get_program(evt)); break; case FLUID_SEQ_ANYCONTROLCHANGE: /* nothing = only used by remove_events */ break; case FLUID_SEQ_PITCHBEND: fluid_synth_pitch_bend(synth, fluid_event_get_channel(evt), fluid_event_get_pitch(evt)); break; case FLUID_SEQ_PITCHWHEELSENS: fluid_synth_pitch_wheel_sens(synth, fluid_event_get_channel(evt), fluid_event_get_value(evt)); break; case FLUID_SEQ_CONTROLCHANGE: fluid_synth_cc(synth, fluid_event_get_channel(evt), fluid_event_get_control(evt), fluid_event_get_value(evt)); break; case FLUID_SEQ_MODULATION: fluid_synth_cc(synth, fluid_event_get_channel(evt), MODULATION_MSB, fluid_event_get_value(evt)); break; case FLUID_SEQ_SUSTAIN: fluid_synth_cc(synth, fluid_event_get_channel(evt), SUSTAIN_SWITCH, fluid_event_get_value(evt)); break; case FLUID_SEQ_PAN: fluid_synth_cc(synth, fluid_event_get_channel(evt), PAN_MSB, fluid_event_get_value(evt)); break; case FLUID_SEQ_VOLUME: fluid_synth_cc(synth, fluid_event_get_channel(evt), VOLUME_MSB, fluid_event_get_value(evt)); break; case FLUID_SEQ_REVERBSEND: fluid_synth_cc(synth, fluid_event_get_channel(evt), EFFECTS_DEPTH1, fluid_event_get_value(evt)); break; case FLUID_SEQ_CHORUSSEND: fluid_synth_cc(synth, fluid_event_get_channel(evt), EFFECTS_DEPTH3, fluid_event_get_value(evt)); break; case FLUID_SEQ_CHANNELPRESSURE: fluid_synth_channel_pressure(synth, fluid_event_get_channel(evt), fluid_event_get_value(evt)); break; case FLUID_SEQ_KEYPRESSURE: fluid_synth_key_pressure(synth, fluid_event_get_channel(evt), fluid_event_get_key(evt), fluid_event_get_value(evt)); break; case FLUID_SEQ_SYSTEMRESET: fluid_synth_system_reset(synth); break; case FLUID_SEQ_UNREGISTERING: /* free ourselves */ delete_fluid_seqbind(seqbind); break; case FLUID_SEQ_TIMER: /* nothing in fluidsynth */ break; default: break; } } static fluid_seq_id_t get_fluidsynth_dest(fluid_sequencer_t *seq) { int i; fluid_seq_id_t id; char *name; int j = fluid_sequencer_count_clients(seq); for(i = 0; i < j; i++) { id = fluid_sequencer_get_client_id(seq, i); name = fluid_sequencer_get_client_name(seq, id); if(name && (FLUID_STRCMP(name, "fluidsynth") == 0)) { return id; } } return -1; } /** * Transforms an incoming midi event (from a midi driver or midi router) to a * sequencer event and adds it to the sequencer queue for sending as soon as possible. * The signature of this function is of type #handle_midi_event_func_t. * @param data The sequencer, must be a valid #fluid_sequencer_t * @param event MIDI event * @return #FLUID_OK or #FLUID_FAILED * @since 1.1.0 */ int fluid_sequencer_add_midi_event_to_buffer(void *data, fluid_midi_event_t *event) { fluid_event_t evt; fluid_sequencer_t *seq; int chan; fluid_return_val_if_fail(data != NULL, FLUID_FAILED); fluid_return_val_if_fail(event != NULL, FLUID_FAILED); seq = (fluid_sequencer_t *) data; chan = fluid_midi_event_get_channel(event); fluid_event_clear(&evt); fluid_event_set_dest(&evt, get_fluidsynth_dest(seq)); switch(fluid_midi_event_get_type(event)) { case NOTE_OFF: fluid_event_noteoff(&evt, chan, (short)fluid_midi_event_get_key(event)); break; case NOTE_ON: fluid_event_noteon(&evt, fluid_midi_event_get_channel(event), (short)fluid_midi_event_get_key(event), (short)fluid_midi_event_get_velocity(event)); break; case CONTROL_CHANGE: fluid_event_control_change(&evt, chan, (short)fluid_midi_event_get_control(event), (short)fluid_midi_event_get_value(event)); break; case PROGRAM_CHANGE: fluid_event_program_change(&evt, chan, (short)fluid_midi_event_get_program(event)); break; case PITCH_BEND: fluid_event_pitch_bend(&evt, chan, fluid_midi_event_get_pitch(event)); break; case CHANNEL_PRESSURE: fluid_event_channel_pressure(&evt, chan, (short)fluid_midi_event_get_program(event)); break; case KEY_PRESSURE: fluid_event_key_pressure(&evt, chan, (short)fluid_midi_event_get_key(event), (short)fluid_midi_event_get_value(event)); break; case MIDI_SYSTEM_RESET: fluid_event_system_reset(&evt); break; default: /* Not yet implemented */ return FLUID_FAILED; } /* Schedule for sending at next call to fluid_sequencer_process */ return fluid_sequencer_send_at(seq, &evt, 0, 0); } fluidsynth-2.1.1/src/rvoice/000077500000000000000000000000001362231004000157515ustar00rootroot00000000000000fluidsynth-2.1.1/src/rvoice/fluid_adsr_env.c000066400000000000000000000025731362231004000211100ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_adsr_env.h" DECLARE_FLUID_RVOICE_FUNCTION(fluid_adsr_env_set_data) { fluid_adsr_env_t *env = obj; fluid_adsr_env_section_t section = param[0].i; unsigned int count = param[1].i; fluid_real_t coeff = param[2].real; fluid_real_t increment = param[3].real; fluid_real_t min = param[4].real; fluid_real_t max = param[5].real; env->data[section].count = count; env->data[section].coeff = coeff; env->data[section].increment = increment; env->data[section].min = min; env->data[section].max = max; } fluidsynth-2.1.1/src/rvoice/fluid_adsr_env.h000066400000000000000000000100171362231004000211050ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_ADSR_ENVELOPE_H #define _FLUID_ADSR_ENVELOPE_H #include "fluidsynth_priv.h" #include "fluid_sys.h" /* * envelope data */ struct _fluid_env_data_t { unsigned int count; fluid_real_t coeff; fluid_real_t increment; fluid_real_t min; fluid_real_t max; }; /* Indices for envelope tables */ enum fluid_voice_envelope_index_t { FLUID_VOICE_ENVDELAY, FLUID_VOICE_ENVATTACK, FLUID_VOICE_ENVHOLD, FLUID_VOICE_ENVDECAY, FLUID_VOICE_ENVSUSTAIN, FLUID_VOICE_ENVRELEASE, FLUID_VOICE_ENVFINISHED, FLUID_VOICE_ENVLAST }; typedef enum fluid_voice_envelope_index_t fluid_adsr_env_section_t; typedef struct _fluid_adsr_env_t fluid_adsr_env_t; struct _fluid_adsr_env_t { fluid_env_data_t data[FLUID_VOICE_ENVLAST]; unsigned int count; int section; fluid_real_t val; /* the current value of the envelope */ }; /* For performance, all functions are inlined */ static FLUID_INLINE void fluid_adsr_env_calc(fluid_adsr_env_t *env, int is_volenv) { fluid_env_data_t *env_data; fluid_real_t x; env_data = &env->data[env->section]; /* skip to the next section of the envelope if necessary */ while(env->count >= env_data->count) { // If we're switching envelope stages from decay to sustain, force the value to be the end value of the previous stage // Hmm, should this only apply to volenv? It was so before refactoring, so keep it for now. [DH] if(env->section == FLUID_VOICE_ENVDECAY && is_volenv) { env->val = env_data->min * env_data->coeff; } env_data = &env->data[++env->section]; env->count = 0; } /* calculate the envelope value and check for valid range */ x = env_data->coeff * env->val + env_data->increment; if(x < env_data->min) { x = env_data->min; env->section++; env->count = 0; } else if(x > env_data->max) { x = env_data->max; env->section++; env->count = 0; } else { env->count++; } env->val = x; } /* This one cannot be inlined since it is referenced in the event queue */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_adsr_env_set_data); static FLUID_INLINE void fluid_adsr_env_reset(fluid_adsr_env_t *env) { env->count = 0; env->section = 0; env->val = 0.0f; } static FLUID_INLINE fluid_real_t fluid_adsr_env_get_val(fluid_adsr_env_t *env) { return env->val; } static FLUID_INLINE void fluid_adsr_env_set_val(fluid_adsr_env_t *env, fluid_real_t val) { env->val = val; } static FLUID_INLINE fluid_adsr_env_section_t fluid_adsr_env_get_section(fluid_adsr_env_t *env) { return env->section; } static FLUID_INLINE void fluid_adsr_env_set_section(fluid_adsr_env_t *env, fluid_adsr_env_section_t section) { env->section = section; env->count = 0; } /* Used for determining which voice to kill. Returns max amplitude from now, and forward in time. */ static FLUID_INLINE fluid_real_t fluid_adsr_env_get_max_val(fluid_adsr_env_t *env) { if(env->section > FLUID_VOICE_ENVATTACK) { return env->val * 1000; } else { return env->data[FLUID_VOICE_ENVATTACK].max; } } #endif fluidsynth-2.1.1/src/rvoice/fluid_chorus.c000066400000000000000000001063471362231004000206160ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe, Markus Nentwig and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* based on a chorus implementation made by Juergen Mueller And Sundry Contributors in 1998 CHANGES - Adapted for fluidsynth, Peter Hanappe, March 2002 - Variable delay line implementation using bandlimited interpolation, code reorganization: Markus Nentwig May 2002 - Complete rewrite using lfo computed on the fly, first order all-pass interpolator and adding stereo unit: Jean-Jacques Ceresa, Jul 2019 */ /* * Chorus effect. * * Flow diagram scheme for n delays ( 1 <= n <= MAX_CHORUS ): * * ________ * direct signal (not implemented) >-->| | * _________ | | * mono | | | | * input ---+---->| delay 1 |-------------------------->| Stereo |---> right * | |_________| | | output * | /|\ | Unit | * : | | | * : +-----------------+ |(width) | * : | Delay control 1 |<-+ | | * : +-----------------+ | | |---> left * | _________ | | | output * | | | | | | * +---->| delay n |-------------------------->| | * |_________| | | | * /|\ | |________| * | | +--------------+ /|\ * +-----------------+ | |mod depth (ms)| | * | Delay control n |<-*--|lfo speed (Hz)| gain-out * +-----------------+ +--------------+ * * * The delay i is controlled by a sine or triangle modulation i ( 1 <= i <= n). * * The chorus unit process a monophonic input signal and produces stereo output * controlled by WIDTH macro. * Actually WIDTH is fixed to maximum value. But in the future, we could add a * setting (e.g "synth.chorus.width") allowing the user to get a gradually stereo * effect from minimum (monophonic) to maximum stereo effect. * * Delays lines are implemented using only one line for all chorus blocks. * Each chorus block has it own lfo (sinus/triangle). Each lfo are out of phase * to produce uncorrelated signal at the output of the delay line (this simulates * the presence of individual line for each block). Each lfo modulates the length * of the line using a depth modulation value and lfo frequency value common to * all lfos. * * LFO modulators are computed on the fly, instead of using lfo lookup table. * The advantages are: * - Avoiding a lost of 608272 memory bytes when lfo speed is low (0.3Hz). * - Allows to diminish the lfo speed lower limit to 0.1Hz instead of 0.3Hz. * A speed of 0.1 is interesting for chorus. Using a lookuptable for 0.1Hz * would require too much memory (1824816 bytes). * - Interpolation make use of first order all-pass interpolator instead of * bandlimited interpolation. * - Although lfo modulator is computed on the fly, cpu load is lower than * using lfo lookup table with bandlimited interpolator. */ #include "fluid_chorus.h" #include "fluid_sys.h" /*------------------------------------------------------------------------------------- Private --------------------------------------------------------------------------------------*/ // #define DEBUG_PRINT // allows message to be printed on the console. #define MAX_CHORUS 99 /* number maximum of block */ #define MAX_LEVEL 10 /* max output level */ #define MIN_SPEED_HZ 0.1 /* min lfo frequency (Hz) */ #define MAX_SPEED_HZ 5 /* max lfo frequency (Hz) */ /* WIDTH [0..10] value define a stereo separation between left and right. When 0, the output is monophonic. When > 0 , the output is stereophonic. Actually WIDTH is fixed to maximum value. But in the future we could add a setting to allow a gradually stereo effect from minimum (monophonic) to maximum stereo effect. */ #define WIDTH 10 /* SCALE_WET_WIDTH is a compensation weight factor to get an output amplitude (wet) rather independent of the width setting. 0: the output amplitude is fully dependant on the width setting. >0: the output amplitude is less dependant on the width setting. With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather independent of width setting (see fluid_chorus_set()). */ #define SCALE_WET_WIDTH 0.2f #define SCALE_WET 1.0f #define MAX_SAMPLES 2048 /* delay length in sample (46.4 ms at sample rate: 44100Hz).*/ #define LOW_MOD_DEPTH 176 /* low mod_depth/2 in samples */ #define HIGH_MOD_DEPTH MAX_SAMPLES/2 /* high mod_depth in sample */ #define RANGE_MOD_DEPTH (HIGH_MOD_DEPTH - LOW_MOD_DEPTH) /* Important min max values for MOD_RATE */ /* mod rate define the rate at which the modulator is updated. Examples 50: the modulator is updated every 50 samples (less cpu cycles expensive). 1: the modulator is updated every sample (more cpu cycles expensive). */ /* MOD_RATE acceptable for max lfo speed (5Hz) and max modulation depth (46.6 ms) */ #define LOW_MOD_RATE 5 /* MOD_RATE acceptable for low modulation depth (8 ms) */ #define HIGH_MOD_RATE 4 /* MOD_RATE acceptable for max modulation depth (46.6 ms) */ /* and max lfo speed (5 Hz) */ #define RANGE_MOD_RATE (HIGH_MOD_RATE - LOW_MOD_RATE) /* some chorus cpu_load measurement dependant of modulation rate: mod_rate (number of chorus blocks: 2) No stero unit: mod_rate | chorus cpu load(%) | one voice cpu load (%) ---------------------------------------------------- 50 | 0.204 | 5 | 0.256 | 0.169 1 | 0.417 | With stero unit: mod_rate | chorus cpu load(%) | one voice cpu load (%) ---------------------------------------------------- 50 | 0.220 | 5 | 0.274 | 0.169 1 | 0.465 | */ /* Number of samples to add to the desired length of the delay line. This allows to take account of rounding error interpolation when using large modulation depth. 1 is sufficient for max modulation depth (46.6 ms) and max lfo speed (5 Hz). */ //#define INTERP_SAMPLES_NBR 0 #define INTERP_SAMPLES_NBR 1 /*----------------------------------------------------------------------------- Sinusoidal modulator -----------------------------------------------------------------------------*/ /* modulator */ typedef struct { fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ fluid_real_t buffer1; /* buffer1 */ fluid_real_t buffer2; /* buffer2 */ fluid_real_t reset_buffer2;/* reset value of buffer2 */ } sinus_modulator; /*----------------------------------------------------------------------------- Triangle modulator -----------------------------------------------------------------------------*/ typedef struct { fluid_real_t freq; /* Osc. Frequency (in Hertz) */ fluid_real_t val; /* internal current value */ fluid_real_t inc; /* increment value */ } triang_modulator; /*----------------------------------------------------------------------------- modulator -----------------------------------------------------------------------------*/ typedef struct { /*-------------*/ int line_out; /* current line out position for this modulator */ /*-------------*/ sinus_modulator sinus; /* sinus lfo */ triang_modulator triang; /* triangle lfo */ /*-------------------------*/ /* first order All-Pass interpolator members */ fluid_real_t frac_pos_mod; /* fractional position part between samples */ /* previous value used when interpolating using fractional */ fluid_real_t buffer; } modulator; /* Private data for SKEL file */ struct _fluid_chorus_t { int type; fluid_real_t depth_ms; fluid_real_t level; fluid_real_t speed_Hz; int number_blocks; fluid_real_t sample_rate; /* width control: 0 monophonic, > 0 more stereophonic */ fluid_real_t width; fluid_real_t wet1, wet2; fluid_real_t *line; /* buffer line */ int size; /* effective internal size (in samples) */ int line_in; /* line in position */ /* center output position members */ fluid_real_t center_pos_mod; /* center output position modulated by modulator */ int mod_depth; /* modulation depth (in samples) */ /* variable rate control of center output position */ int index_rate; /* index rate to know when to update center_pos_mod */ int mod_rate; /* rate at which center_pos_mod is updated */ /* modulator member */ modulator mod[MAX_CHORUS]; /* sinus/triangle modulator */ }; /*----------------------------------------------------------------------------- Sets the frequency of sinus oscillator. @param mod pointer on modulator structure. @param freq frequency of the oscillator in Hz. @param sample_rate sample rate on audio output in Hz. @param phase initial phase of the oscillator in degree (0 to 360). -----------------------------------------------------------------------------*/ static void set_sinus_frequency(sinus_modulator *mod, float freq, float sample_rate, float phase) { fluid_real_t w = 2 * FLUID_M_PI * freq / sample_rate; /* initial angle */ fluid_real_t a; mod->a1 = 2 * FLUID_COS(w); a = (2 * FLUID_M_PI / 360) * phase; mod->buffer2 = FLUID_SIN(a - w); /* y(n-1) = sin(-intial angle) */ mod->buffer1 = FLUID_SIN(a); /* y(n) = sin(initial phase) */ mod->reset_buffer2 = FLUID_SIN(FLUID_M_PI / 2 - w); /* reset value for PI/2 */ } /*----------------------------------------------------------------------------- Gets current value of sinus modulator: y(n) = a1 . y(n-1) - y(n-2) out = a1 . buffer1 - buffer2 @param pointer on modulator structure. @return current value of the modulator sine wave. -----------------------------------------------------------------------------*/ static FLUID_INLINE fluid_real_t get_mod_sinus(sinus_modulator *mod) { fluid_real_t out; out = mod->a1 * mod->buffer1 - mod->buffer2; mod->buffer2 = mod->buffer1; if(out >= 1.0f) /* reset in case of instability near PI/2 */ { out = 1.0f; /* forces output to the right value */ mod->buffer2 = mod->reset_buffer2; } if(out <= -1.0f) /* reset in case of instability near -PI/2 */ { out = -1.0f; /* forces output to the right value */ mod->buffer2 = - mod->reset_buffer2; } mod->buffer1 = out; return out; } /*----------------------------------------------------------------------------- Set the frequency of triangular oscillator The frequency is converted in a slope value. The initial value is set according to frac_phase which is a position in the period relative to the beginning of the period. For example: 0 is the beginning of the period, 1/4 is at 1/4 of the period relative to the beginning. -----------------------------------------------------------------------------*/ static void set_triangle_frequency(triang_modulator *mod, float freq, float sample_rate, float frac_phase) { fluid_real_t ns_period; /* period in numbers of sample */ if(freq <= 0.0) { freq = 0.5f; } mod->freq = freq; ns_period = sample_rate / freq; /* the slope of a triangular osc (0 up to +1 down to -1 up to 0....) is equivalent to the slope of a saw osc (0 -> +4) */ mod->inc = 4 / ns_period; /* positive slope */ /* The initial value and the sign of the slope depend of initial phase: initial value = = (ns_period * frac_phase) * slope */ mod->val = ns_period * frac_phase * mod->inc; if(1.0 <= mod->val && mod->val < 3.0) { mod->val = 2.0 - mod->val; /* 1.0 down to -1.0 */ mod->inc = -mod->inc; /* negative slope */ } else if(3.0 <= mod->val) { mod->val = mod->val - 4.0; /* -1.0 up to +1.0. */ } /* else val < 1.0 */ } /*----------------------------------------------------------------------------- Get current value of triangular oscillator y(n) = y(n-1) + dy -----------------------------------------------------------------------------*/ static FLUID_INLINE fluid_real_t get_mod_triang(triang_modulator *mod) { mod->val = mod->val + mod->inc ; if(mod->val >= 1.0) { mod->inc = -mod->inc; return 1.0; } if(mod->val <= -1.0) { mod->inc = -mod->inc; return -1.0; } return mod->val; } /*----------------------------------------------------------------------------- Reads the sample value out of the modulated delay line. @param mdl, pointer on modulated delay line. @return the sample value. -----------------------------------------------------------------------------*/ static FLUID_INLINE fluid_real_t get_mod_delay(fluid_chorus_t *chorus, modulator *mod) { fluid_real_t out_index; /* new modulated index position */ int int_out_index; /* integer part of out_index */ fluid_real_t out; /* value to return */ /* Checks if the modulator must be updated (every mod_rate samples). */ /* Important: center_pos_mod must be used immediately for the first sample. So, mdl->index_rate must be initialized to mdl->mod_rate (new_mod_delay_line()) */ if(chorus->index_rate >= chorus->mod_rate) { /* out_index = center position (center_pos_mod) + sinus waweform */ if(chorus->type == FLUID_CHORUS_MOD_SINE) { out_index = chorus->center_pos_mod + get_mod_sinus(&mod->sinus) * chorus->mod_depth; } else { out_index = chorus->center_pos_mod + get_mod_triang(&mod->triang) * chorus->mod_depth; } /* extracts integer part in int_out_index */ if(out_index >= 0.0f) { int_out_index = (int)out_index; /* current integer part */ /* forces read index (line_out) with integer modulation value */ /* Boundary check and circular motion as needed */ if((mod->line_out = int_out_index) >= chorus->size) { mod->line_out -= chorus->size; } } else /* negative */ { int_out_index = (int)(out_index - 1); /* previous integer part */ /* forces read index (line_out) with integer modulation value */ /* circular motion as needed */ mod->line_out = int_out_index + chorus->size; } /* extracts fractionnal part. (it will be used when interpolating between line_out and line_out +1) and memorize it. Memorizing is necessary for modulation rate above 1 */ mod->frac_pos_mod = out_index - int_out_index; } /* First order all-pass interpolation ----------------------------------*/ /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ /* begins interpolation: read current sample */ out = chorus->line[mod->line_out]; /* updates line_out to the next sample. Boundary check and circular motion as needed */ if(++mod->line_out >= chorus->size) { mod->line_out -= chorus->size; } /* Fractional interpolation between next sample (at next position) and previous output added to current sample. */ out += mod->frac_pos_mod * (chorus->line[mod->line_out] - mod->buffer); mod->buffer = out; /* memorizes current output */ return out; } /*----------------------------------------------------------------------------- Push a sample val into the delay line -----------------------------------------------------------------------------*/ #define push_in_delay_line(dl, val) \ {\ dl->line[dl->line_in] = val;\ /* Incrementation and circular motion if necessary */\ if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ }\ /*----------------------------------------------------------------------------- Initialize : mod_rate, center_pos_mod, and index rate center_pos_mod is initialized so that the delay between center_pos_mod and line_in is: mod_depth + INTERP_SAMPLES_NBR. -----------------------------------------------------------------------------*/ static void set_center_position(fluid_chorus_t *chorus) { int center; /* Sets the modulation rate. This rate defines how often the center position (center_pos_mod ) is modulated . The value is expressed in samples. The default value is 1 that means that center_pos_mod is updated at every sample. For example with a value of 2, the center position position will be updated only one time every 2 samples only. */ chorus->mod_rate = LOW_MOD_RATE; /* default modulation rate */ /* compensate mod rate for high modulation depth */ if(chorus->mod_depth > LOW_MOD_DEPTH) { int delta_mod_depth = (chorus->mod_depth - LOW_MOD_DEPTH); chorus->mod_rate += (delta_mod_depth * RANGE_MOD_RATE) / RANGE_MOD_DEPTH; } /* Initializes the modulated center position (center_pos_mod) so that: - the delay between center_pos_mod and line_in is: mod_depth + INTERP_SAMPLES_NBR. */ center = chorus->line_in - (INTERP_SAMPLES_NBR + chorus->mod_depth); if(center < 0) { center += chorus->size; } chorus->center_pos_mod = (fluid_real_t)center; /* index rate to control when to update center_pos_mod */ /* Important: must be set to get center_pos_mod immediately used for the reading of first sample (see get_mod_delay()) */ chorus->index_rate = chorus->mod_rate; } /*----------------------------------------------------------------------------- Modulated delay line initialization. Sets the length line ( alloc delay samples). Remark: the function sets the internal size accordling to the length delay_length. The size is augmented by INTERP_SAMPLES_NBR to take account of interpolation. @param chorus, pointer chorus unit. @param delay_length the length of the delay line in samples. @return FLUID_OK if success , FLUID_FAILED if memory error. Return FLUID_OK if success, FLUID_FAILED if memory error. -----------------------------------------------------------------------------*/ static int new_mod_delay_line(fluid_chorus_t *chorus, int delay_length) { /*-----------------------------------------------------------------------*/ /* checks parameter */ if(delay_length < 1) { return FLUID_FAILED; } chorus->mod_depth = 0; /*----------------------------------------------------------------------- allocates delay_line and initialize members: - line, size, line_in... */ /* total size of the line: size = INTERP_SAMPLES_NBR + delay_length */ chorus->size = delay_length + INTERP_SAMPLES_NBR; chorus->line = FLUID_ARRAY(fluid_real_t, chorus->size); if(! chorus->line) { return FLUID_FAILED; } /* clears the buffer: - delay line - interpolator member: buffer, frac_pos_mod */ fluid_chorus_reset(chorus); /* Initializes line_in to the start of the buffer */ chorus->line_in = 0; /*------------------------------------------------------------------------ Initializes modulation members: - modulation rate (the speed at which center_pos_mod is modulated: mod_rate - modulated center position: center_pos_mod - index rate to know when to update center_pos_mod:index_rate -------------------------------------------------------------------------*/ /* Initializes the modulated center position: mod_rate, center_pos_mod, and index rate */ set_center_position(chorus); return FLUID_OK; } /*----------------------------------------------------------------------------- API ------------------------------------------------------------------------------*/ /** * Create the chorus unit. * @sample_rate audio sample rate in Hz. * @return pointer on chorus unit. */ fluid_chorus_t * new_fluid_chorus(fluid_real_t sample_rate) { fluid_chorus_t *chorus; chorus = FLUID_NEW(fluid_chorus_t); if(chorus == NULL) { FLUID_LOG(FLUID_PANIC, "chorus: Out of memory"); return NULL; } FLUID_MEMSET(chorus, 0, sizeof(fluid_chorus_t)); chorus->sample_rate = sample_rate; #ifdef DEBUG_PRINT printf("fluid_chorus_t:%d bytes\n", sizeof(fluid_chorus_t)); printf("fluid_real_t:%d bytes\n", sizeof(fluid_real_t)); #endif #ifdef DEBUG_PRINT printf("NEW_MOD\n"); #endif if(new_mod_delay_line(chorus, MAX_SAMPLES) == FLUID_FAILED) { goto error_recovery; } return chorus; error_recovery: delete_fluid_chorus(chorus); return NULL; } /** * Delete the chorus unit. * @param chorus pointer on chorus unit returned by new_fluid_chorus(). */ void delete_fluid_chorus(fluid_chorus_t *chorus) { fluid_return_if_fail(chorus != NULL); FLUID_FREE(chorus->line); FLUID_FREE(chorus); } /** * Clear the internal delay line and associate filter. * @param chorus pointer on chorus unit returned by new_fluid_chorus(). */ void fluid_chorus_reset(fluid_chorus_t *chorus) { int i; unsigned int u; /* reset delay line */ for(i = 0; i < chorus->size; i++) { chorus->line[i] = 0; } /* reset modulators's allpass filter */ for(u = 0; u < FLUID_N_ELEMENTS(chorus->mod); u++) { /* initializes 1st order All-Pass interpolator members */ chorus->mod[u].buffer = 0; /* previous delay sample value */ chorus->mod[u].frac_pos_mod = 0; /* fractional position (between consecutives sample) */ } } /** * Set one or more chorus parameters. * @param chorus Chorus instance * @param set Flags indicating which chorus parameters to set (#fluid_chorus_set_t) * @param nr Chorus voice count (0-99, CPU time consumption proportional to * this value) * @param level Chorus level (0.0-10.0) * @param speed Chorus speed in Hz (0.1-5.0) * @param depth_ms Chorus depth (max value depends on synth sample rate, * 0.0-21.0 is safe for sample rate values up to 96KHz) * @param type Chorus waveform type (#fluid_chorus_mod) */ void fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, fluid_real_t speed, fluid_real_t depth_ms, int type) { int i; if(set & FLUID_CHORUS_SET_NR) /* number of block */ { chorus->number_blocks = nr; } if(set & FLUID_CHORUS_SET_LEVEL) /* output level */ { chorus->level = level; } if(set & FLUID_CHORUS_SET_SPEED) /* lfo frequency (in Hz) */ { chorus->speed_Hz = speed; } if(set & FLUID_CHORUS_SET_DEPTH) /* modulation depth (in ms) */ { chorus->depth_ms = depth_ms; } if(set & FLUID_CHORUS_SET_TYPE) /* lfo shape (sinus, triangle) */ { chorus->type = type; } /* check min , max parameters */ if(chorus->number_blocks < 0) { FLUID_LOG(FLUID_WARN, "chorus: number blocks must be >=0! Setting value to 0."); chorus->number_blocks = 0; } else if(chorus->number_blocks > MAX_CHORUS) { FLUID_LOG(FLUID_WARN, "chorus: number blocks larger than max. allowed! Setting value to %d.", MAX_CHORUS); chorus->number_blocks = MAX_CHORUS; } if(chorus->speed_Hz < MIN_SPEED_HZ) { FLUID_LOG(FLUID_WARN, "chorus: speed is too low (min %f)! Setting value to min.", (double) MIN_SPEED_HZ); chorus->speed_Hz = MIN_SPEED_HZ; } else if(chorus->speed_Hz > MAX_SPEED_HZ) { FLUID_LOG(FLUID_WARN, "chorus: speed must be below %f Hz! Setting value to max.", (double) MAX_SPEED_HZ); chorus->speed_Hz = MAX_SPEED_HZ; } if(chorus->depth_ms < 0.0) { FLUID_LOG(FLUID_WARN, "chorus: depth must be positive! Setting value to 0."); chorus->depth_ms = 0.0; } if(chorus->level < 0.0) { FLUID_LOG(FLUID_WARN, "chorus: level must be positive! Setting value to 0."); chorus->level = 0.0; } else if(chorus->level > MAX_LEVEL) { FLUID_LOG(FLUID_WARN, "chorus: level must be < 10. A reasonable level is << 1! " "Setting it to 0.1."); chorus->level = 0.1; } /* initialize modulation depth (peak to peak) (in samples)*/ chorus->mod_depth = (int)(chorus->depth_ms / 1000.0 /* convert modulation depth in ms to s*/ * chorus->sample_rate); if(chorus->mod_depth > MAX_SAMPLES) { FLUID_LOG(FLUID_WARN, "chorus: Too high depth. Setting it to max (%d).", MAX_SAMPLES); chorus->mod_depth = MAX_SAMPLES; // set depth to maximum to avoid spamming console with above warning chorus->depth_ms = (chorus->mod_depth * 1000) / chorus->sample_rate; } chorus->mod_depth /= 2; /* amplitude is peak to peek / 2 */ #ifdef DEBUG_PRINT printf("depth_ms:%f, depth_samples/2:%d\n", chorus->depth_ms, chorus->mod_depth); #endif /* Initializes the modulated center position: mod_rate, center_pos_mod, and index rate. */ set_center_position(chorus); /* must be called before set_xxxx_frequency() */ #ifdef DEBUG_PRINT printf("mod_rate:%d\n", chorus->mod_rate); #endif /* initialize modulator frequency */ for(i = 0; i < chorus->number_blocks; i++) { set_sinus_frequency(&chorus->mod[i].sinus, chorus->speed_Hz * chorus->mod_rate, chorus->sample_rate, /* phase offset between modulators waveform */ (float)((360.0f / (float) chorus->number_blocks) * i)); set_triangle_frequency(&chorus->mod[i].triang, chorus->speed_Hz * chorus->mod_rate, chorus->sample_rate, /* phase offset between modulators waveform */ (float)i / chorus->number_blocks); } #ifdef DEBUG_PRINT printf("lfo type:%d\n", chorus->type); printf("speed_Hz:%f\n", chorus->speed_Hz); #endif /* Initialize the lfo waveform */ if((chorus->type != FLUID_CHORUS_MOD_SINE) && (chorus->type != FLUID_CHORUS_MOD_TRIANGLE)) { FLUID_LOG(FLUID_WARN, "chorus: Unknown modulation type. Using sinewave."); chorus->type = FLUID_CHORUS_MOD_SINE; } #ifdef DEBUG_PRINT if(chorus->type == FLUID_CHORUS_MOD_SINE) { printf("lfo: sinus\n"); } else { printf("lfo: triangle\n"); } printf("nr:%d\n", chorus->number_blocks); #endif /* Recalculate internal values after parameters change */ /* Note: Actually WIDTH is fixed to maximum value. But in the future we could add a setting "synth.chorus.width" to allow a gradually stereo effect from minimum (monophonic) to maximum stereo effect. If this setting will be added, remove the following instruction. */ chorus->width = WIDTH; { /* The stereo amplitude equation (wet1 and wet2 below) have a tendency to produce high amplitude with high width values ( 1 < width < 10). This results in an unwanted noisy output clipped by the audio card. To avoid this dependency, we divide by (1 + chorus->width * SCALE_WET_WIDTH) Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), the output amplitude (wet) seems rather independent of width setting */ fluid_real_t wet = chorus->level * SCALE_WET ; /* wet1 and wet2 are used by the stereo effect controlled by the width setting for producing a stereo ouptput from a monophonic chorus signal. Please see the note above about a side effect tendency */ if(chorus->number_blocks > 1) { wet = wet / (1.0f + chorus->width * SCALE_WET_WIDTH); chorus->wet1 = wet * (chorus->width / 2.0f + 0.5f); chorus->wet2 = wet * ((1.0f - chorus->width) / 2.0f); #ifdef DEBUG_PRINT printf("width:%f\n", chorus->width); if(chorus->width > 0) { printf("nr > 1, width > 0 => out stereo\n"); } else { printf("nr > 1, width:0 =>out mono\n"); } #endif } else { /* only one chorus block */ if(chorus->width == 0.0) { /* wet1 and wet2 should make stereo output monomophic */ chorus->wet1 = chorus->wet2 = wet; } else { /* for width > 0, wet1 and wet2 should make stereo output stereo with only one block. This will only possible by inverting the unique signal on each left and right output. Note however that with only one block, it isn't possible to have a graduate width effect */ chorus->wet1 = wet; chorus->wet2 = -wet; /* inversion */ } #ifdef DEBUG_PRINT printf("width:%f\n", chorus->width); if(chorus->width != 0) { printf("one block, width > 0 => out stereo\n"); } else { printf("one block, width:0 => out mono\n"); } #endif } } } /** * Process chorus by mixing the result in output buffer. * @param chorus pointer on chorus unit returned by new_fluid_chorus(). * @param in, pointer on monophonic input buffer of FLUID_BUFSIZE samples. * @param left_out, right_out, pointers on stereo output buffers of * FLUID_BUFSIZE samples. */ void fluid_chorus_processmix(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out) { int sample_index; int i; fluid_real_t d_out[2]; /* output stereo Left and Right */ /* foreach sample, process output sample then input sample */ for(sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) { fluid_real_t out; /* block output */ d_out[0] = d_out[1] = 0.0f; /* clear stereo unit input */ #if 0 /* Debug: Listen to the chorus signal only */ left_out[sample_index] = 0; right_out[sample_index] = 0; #endif ++chorus->index_rate; /* modulator rate */ /* foreach chorus block, process output sample */ for(i = 0; i < chorus->number_blocks; i++) { /* get sample from the output of modulated delay line */ out = get_mod_delay(chorus, &chorus->mod[i]); /* accumulate out into stereo unit input */ d_out[i & 1] += out; } /* update modulator index rate and output center position */ if(chorus->index_rate >= chorus->mod_rate) { chorus->index_rate = 0; /* clear modulator index rate */ /* updates center position (center_pos_mod) to the next position specified by modulation rate */ if((chorus->center_pos_mod += chorus->mod_rate) >= chorus->size) { chorus->center_pos_mod -= chorus->size; } } /* Adjust stereo input level in case of number_blocks odd: In those case, d_out[1] level is lower than d_out[0], so we need to add out value to d_out[1] to have d_out[0] and d_out[1] balanced. */ if((i & 1) && i > 2) // i = 3,5,7... { d_out[1] += out ; } /* process stereo unit */ /* Add the chorus stereo unit d_out to left and right output */ left_out[sample_index] += d_out[0] * chorus->wet1 + d_out[1] * chorus->wet2; right_out[sample_index] += d_out[1] * chorus->wet1 + d_out[0] * chorus->wet2; /* Write the current input sample into the circular buffer */ push_in_delay_line(chorus, in[sample_index]); } } /** * Process chorus by putting the result in output buffer (no mixing). * @param chorus pointer on chorus unit returned by new_fluid_chorus(). * @param in, pointer on monophonic input buffer of FLUID_BUFSIZE samples. * @param left_out, right_out, pointers on stereo output buffers of * FLUID_BUFSIZE samples. */ /* Duplication of code ... (replaces sample data instead of mixing) */ void fluid_chorus_processreplace(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out) { int sample_index; int i; fluid_real_t d_out[2]; /* output stereo Left and Right */ /* foreach sample, process output sample then input sample */ for(sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) { fluid_real_t out; /* block output */ d_out[0] = d_out[1] = 0.0f; /* clear stereo unit input */ #if 0 /* Debug: Listen to the chorus signal only */ left_out[sample_index] = 0; right_out[sample_index] = 0; #endif ++chorus->index_rate; /* modulator rate */ /* foreach chorus block, process output sample */ for(i = 0; i < chorus->number_blocks; i++) { /* get sample from the output of modulated delay line */ out = get_mod_delay(chorus, &chorus->mod[i]); /* accumulate out into stereo unit input */ d_out[i & 1] += out; } /* update modulator index rate and output center position */ if(chorus->index_rate >= chorus->mod_rate) { chorus->index_rate = 0; /* clear modulator index rate */ /* updates center position (center_pos_mod) to the next position specified by modulation rate */ if((chorus->center_pos_mod += chorus->mod_rate) >= chorus->size) { chorus->center_pos_mod -= chorus->size; } } /* Adjust stereo input level in case of number_blocks odd: In those case, d_out[1] level is lower than d_out[0], so we need to add out value to d_out[1] to have d_out[0] and d_out[1] balanced. */ if((i & 1) && i > 2) // i = 3,5,7... { d_out[1] += out ; } /* process stereo unit */ /* store the chorus stereo unit d_out to left and right output */ left_out[sample_index] = d_out[0] * chorus->wet1 + d_out[1] * chorus->wet2; right_out[sample_index] = d_out[1] * chorus->wet1 + d_out[0] * chorus->wet2; /* Write the current input sample into the circular buffer */ push_in_delay_line(chorus, in[sample_index]); } } fluidsynth-2.1.1/src/rvoice/fluid_chorus.h000066400000000000000000000043351362231004000206150ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_CHORUS_H #define _FLUID_CHORUS_H #include "fluidsynth_priv.h" typedef struct _fluid_chorus_t fluid_chorus_t; /** Flags for fluid_chorus_set() */ typedef enum { FLUID_CHORUS_SET_NR = 1 << 0, FLUID_CHORUS_SET_LEVEL = 1 << 1, FLUID_CHORUS_SET_SPEED = 1 << 2, FLUID_CHORUS_SET_DEPTH = 1 << 3, FLUID_CHORUS_SET_TYPE = 1 << 4, /** Value for fluid_chorus_set() which sets all chorus parameters. */ FLUID_CHORUS_SET_ALL = FLUID_CHORUS_SET_NR | FLUID_CHORUS_SET_LEVEL | FLUID_CHORUS_SET_SPEED | FLUID_CHORUS_SET_DEPTH | FLUID_CHORUS_SET_TYPE, } fluid_chorus_set_t; /* * chorus */ fluid_chorus_t *new_fluid_chorus(fluid_real_t sample_rate); void delete_fluid_chorus(fluid_chorus_t *chorus); void fluid_chorus_reset(fluid_chorus_t *chorus); void fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, fluid_real_t speed, fluid_real_t depth_ms, int type); void fluid_chorus_processmix(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); void fluid_chorus_processreplace(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); #endif /* _FLUID_CHORUS_H */ fluidsynth-2.1.1/src/rvoice/fluid_iir_filter.c000066400000000000000000000361131362231004000214340ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_iir_filter.h" #include "fluid_sys.h" #include "fluid_conv.h" /** * Applies a low- or high-pass filter with variable cutoff frequency and quality factor * for a given biquad transfer function: * b0 + b1*z^-1 + b2*z^-2 * H(z) = ------------------------ * a0 + a1*z^-1 + a2*z^-2 * * Also modifies filter state accordingly. * @param iir_filter Filter parameter * @param dsp_buf Pointer to the synthesized audio data * @param count Count of samples in dsp_buf */ /* * Variable description: * - dsp_a1, dsp_a2: Filter coefficients for the the previously filtered output signal * - dsp_b0, dsp_b1, dsp_b2: Filter coefficients for input signal * - coefficients normalized to a0 * * A couple of variables are used internally, their results are discarded: * - dsp_i: Index through the output buffer * - dsp_centernode: delay line for the IIR filter * - dsp_hist1: same * - dsp_hist2: same */ void fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, fluid_real_t *dsp_buf, int count) { if(iir_filter->type == FLUID_IIR_DISABLED || iir_filter->q_lin == 0) { return; } else { /* IIR filter sample history */ fluid_real_t dsp_hist1 = iir_filter->hist1; fluid_real_t dsp_hist2 = iir_filter->hist2; /* IIR filter coefficients */ fluid_real_t dsp_a1 = iir_filter->a1; fluid_real_t dsp_a2 = iir_filter->a2; fluid_real_t dsp_b02 = iir_filter->b02; fluid_real_t dsp_b1 = iir_filter->b1; int dsp_filter_coeff_incr_count = iir_filter->filter_coeff_incr_count; fluid_real_t dsp_centernode; int dsp_i; /* filter (implement the voice filter according to SoundFont standard) */ /* Check for denormal number (too close to zero). */ if(FLUID_FABS(dsp_hist1) < 1e-20f) { dsp_hist1 = 0.0f; /* FIXME JMG - Is this even needed? */ } /* Two versions of the filter loop. One, while the filter is * changing towards its new setting. The other, if the filter * doesn't change. */ if(dsp_filter_coeff_incr_count > 0) { fluid_real_t dsp_a1_incr = iir_filter->a1_incr; fluid_real_t dsp_a2_incr = iir_filter->a2_incr; fluid_real_t dsp_b02_incr = iir_filter->b02_incr; fluid_real_t dsp_b1_incr = iir_filter->b1_incr; /* Increment is added to each filter coefficient filter_coeff_incr_count times. */ for(dsp_i = 0; dsp_i < count; dsp_i++) { /* The filter is implemented in Direct-II form. */ dsp_centernode = dsp_buf[dsp_i] - dsp_a1 * dsp_hist1 - dsp_a2 * dsp_hist2; dsp_buf[dsp_i] = dsp_b02 * (dsp_centernode + dsp_hist2) + dsp_b1 * dsp_hist1; dsp_hist2 = dsp_hist1; dsp_hist1 = dsp_centernode; if(dsp_filter_coeff_incr_count-- > 0) { fluid_real_t old_b02 = dsp_b02; dsp_a1 += dsp_a1_incr; dsp_a2 += dsp_a2_incr; dsp_b02 += dsp_b02_incr; dsp_b1 += dsp_b1_incr; /* Compensate history to avoid the filter going havoc with large frequency changes */ if(iir_filter->compensate_incr && FLUID_FABS(dsp_b02) > 0.001f) { fluid_real_t compensate = old_b02 / dsp_b02; dsp_hist1 *= compensate; dsp_hist2 *= compensate; } } } /* for dsp_i */ } else /* The filter parameters are constant. This is duplicated to save time. */ { for(dsp_i = 0; dsp_i < count; dsp_i++) { /* The filter is implemented in Direct-II form. */ dsp_centernode = dsp_buf[dsp_i] - dsp_a1 * dsp_hist1 - dsp_a2 * dsp_hist2; dsp_buf[dsp_i] = dsp_b02 * (dsp_centernode + dsp_hist2) + dsp_b1 * dsp_hist1; dsp_hist2 = dsp_hist1; dsp_hist1 = dsp_centernode; } } iir_filter->hist1 = dsp_hist1; iir_filter->hist2 = dsp_hist2; iir_filter->a1 = dsp_a1; iir_filter->a2 = dsp_a2; iir_filter->b02 = dsp_b02; iir_filter->b1 = dsp_b1; iir_filter->filter_coeff_incr_count = dsp_filter_coeff_incr_count; fluid_check_fpe("voice_filter"); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_init) { fluid_iir_filter_t *iir_filter = obj; enum fluid_iir_filter_type type = param[0].i; enum fluid_iir_filter_flags flags = param[1].i; iir_filter->type = type; iir_filter->flags = flags; if(type != FLUID_IIR_DISABLED) { fluid_iir_filter_reset(iir_filter); } } void fluid_iir_filter_reset(fluid_iir_filter_t *iir_filter) { iir_filter->hist1 = 0; iir_filter->hist2 = 0; iir_filter->last_fres = -1.; iir_filter->q_lin = 0; iir_filter->filter_startup = 1; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_fres) { fluid_iir_filter_t *iir_filter = obj; fluid_real_t fres = param[0].real; iir_filter->fres = fres; iir_filter->last_fres = -1.; } static fluid_real_t fluid_iir_filter_q_from_dB(fluid_real_t q_dB) { /* The generator contains 'centibels' (1/10 dB) => divide by 10 to * obtain dB */ q_dB /= 10.0f; /* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */ fluid_clip(q_dB, 0.0f, 96.0f); /* Short version: Modify the Q definition in a way, that a Q of 0 * dB leads to no resonance hump in the freq. response. * * Long version: From SF2.01, page 39, item 9 (initialFilterQ): * "The gain at the cutoff frequency may be less than zero when * zero is specified". Assume q_dB=0 / q_lin=1: If we would leave * q as it is, then this results in a 3 dB hump slightly below * fc. At fc, the gain is exactly the DC gain (0 dB). What is * (probably) meant here is that the filter does not show a * resonance hump for q_dB=0. In this case, the corresponding * q_lin is 1/sqrt(2)=0.707. The filter should have 3 dB of * attenuation at fc now. In this case Q_dB is the height of the * resonance peak not over the DC gain, but over the frequency * response of a non-resonant filter. This idea is implemented as * follows: */ q_dB -= 3.01f; /* The 'sound font' Q is defined in dB. The filter needs a linear q. Convert. */ return FLUID_POW(10.0f, q_dB / 20.0f); } DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q) { fluid_iir_filter_t *iir_filter = obj; fluid_real_t q = param[0].real; int flags = iir_filter->flags; if(flags & FLUID_IIR_Q_ZERO_OFF && q <= 0.0) { q = 0; } else if(flags & FLUID_IIR_Q_LINEAR) { /* q is linear (only for user-defined filter) * increase to avoid Q being somewhere between zero and one, * which results in some strange amplified lowpass signal */ q++; } else { q = fluid_iir_filter_q_from_dB(q); } iir_filter->q_lin = q; iir_filter->filter_gain = 1.0; if(!(flags & FLUID_IIR_NO_GAIN_AMP)) { /* SF 2.01 page 59: * * The SoundFont specs ask for a gain reduction equal to half the * height of the resonance peak (Q). For example, for a 10 dB * resonance peak, the gain is reduced by 5 dB. This is done by * multiplying the total gain with sqrt(1/Q). `Sqrt' divides dB * by 2 (100 lin = 40 dB, 10 lin = 20 dB, 3.16 lin = 10 dB etc) * The gain is later factored into the 'b' coefficients * (numerator of the filter equation). This gain factor depends * only on Q, so this is the right place to calculate it. */ iir_filter->filter_gain /= FLUID_SQRT(q); } /* The synthesis loop will have to recalculate the filter coefficients. */ iir_filter->last_fres = -1.; } static FLUID_INLINE void fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, int transition_samples, fluid_real_t output_rate) { /* FLUID_IIR_Q_LINEAR may switch the filter off by setting Q==0 */ if(iir_filter->q_lin == 0) { return; } else { /* * Those equations from Robert Bristow-Johnson's `Cookbook * formulae for audio EQ biquad filter coefficients', obtained * from Harmony-central.com / Computer / Programming. They are * the result of the bilinear transform on an analogue filter * prototype. To quote, `BLT frequency warping has been taken * into account for both significant frequency relocation and for * bandwidth readjustment'. */ fluid_real_t omega = (fluid_real_t)(2.0 * M_PI) * (iir_filter->last_fres / output_rate); fluid_real_t sin_coeff = FLUID_SIN(omega); fluid_real_t cos_coeff = FLUID_COS(omega); fluid_real_t alpha_coeff = sin_coeff / (2.0f * iir_filter->q_lin); fluid_real_t a0_inv = 1.0f / (1.0f + alpha_coeff); /* Calculate the filter coefficients. All coefficients are * normalized by a0. Think of `a1' as `a1/a0'. * * Here a couple of multiplications are saved by reusing common expressions. * The original equations should be: * iir_filter->b0=(1.-cos_coeff)*a0_inv*0.5*iir_filter->filter_gain; * iir_filter->b1=(1.-cos_coeff)*a0_inv*iir_filter->filter_gain; * iir_filter->b2=(1.-cos_coeff)*a0_inv*0.5*iir_filter->filter_gain; */ /* "a" coeffs are same for all 3 available filter types */ fluid_real_t a1_temp = -2.0f * cos_coeff * a0_inv; fluid_real_t a2_temp = (1.0f - alpha_coeff) * a0_inv; fluid_real_t b02_temp, b1_temp; switch(iir_filter->type) { case FLUID_IIR_HIGHPASS: b1_temp = (1.0f + cos_coeff) * a0_inv * iir_filter->filter_gain; /* both b0 -and- b2 */ b02_temp = b1_temp * 0.5f; b1_temp *= -1.0f; break; case FLUID_IIR_LOWPASS: b1_temp = (1.0f - cos_coeff) * a0_inv * iir_filter->filter_gain; /* both b0 -and- b2 */ b02_temp = b1_temp * 0.5f; break; default: /* filter disabled, should never get here */ return; } iir_filter->compensate_incr = 0; if(iir_filter->filter_startup || (transition_samples == 0)) { /* The filter is calculated, because the voice was started up. * In this case set the filter coefficients without delay. */ iir_filter->a1 = a1_temp; iir_filter->a2 = a2_temp; iir_filter->b02 = b02_temp; iir_filter->b1 = b1_temp; iir_filter->filter_coeff_incr_count = 0; iir_filter->filter_startup = 0; // printf("Setting initial filter coefficients.\n"); } else { /* The filter frequency is changed. Calculate an increment * factor, so that the new setting is reached after one buffer * length. x_incr is added to the current value FLUID_BUFSIZE * times. The length is arbitrarily chosen. Longer than one * buffer will sacrifice some performance, though. Note: If * the filter is still too 'grainy', then increase this number * at will. */ iir_filter->a1_incr = (a1_temp - iir_filter->a1) / transition_samples; iir_filter->a2_incr = (a2_temp - iir_filter->a2) / transition_samples; iir_filter->b02_incr = (b02_temp - iir_filter->b02) / transition_samples; iir_filter->b1_incr = (b1_temp - iir_filter->b1) / transition_samples; if(FLUID_FABS(iir_filter->b02) > 0.0001f) { fluid_real_t quota = b02_temp / iir_filter->b02; iir_filter->compensate_incr = quota < 0.5f || quota > 2.f; } /* Have to add the increments filter_coeff_incr_count times. */ iir_filter->filter_coeff_incr_count = transition_samples; } fluid_check_fpe("voice_write filter calculation"); } } void fluid_iir_filter_calc(fluid_iir_filter_t *iir_filter, fluid_real_t output_rate, fluid_real_t fres_mod) { fluid_real_t fres; /* calculate the frequency of the resonant filter in Hz */ fres = fluid_ct2hz(iir_filter->fres + fres_mod); /* FIXME - Still potential for a click during turn on, can we interpolate between 20khz cutoff and 0 Q? */ /* I removed the optimization of turning the filter off when the * resonance frequence is above the maximum frequency. Instead, the * filter frequency is set to a maximum of 0.45 times the sampling * rate. For a 44100 kHz sampling rate, this amounts to 19845 * Hz. The reason is that there were problems with anti-aliasing when the * synthesizer was run at lower sampling rates. Thanks to Stephan * Tassart for pointing me to this bug. By turning the filter on and * clipping the maximum filter frequency at 0.45*srate, the filter * is used as an anti-aliasing filter. */ if(fres > 0.45f * output_rate) { fres = 0.45f * output_rate; } else if(fres < 5.f) { fres = 5.f; } /* if filter enabled and there is a significant frequency change.. */ if(iir_filter->type != FLUID_IIR_DISABLED && FLUID_FABS(fres - iir_filter->last_fres) > 0.01f) { /* The filter coefficients have to be recalculated (filter * parameters have changed). Recalculation for various reasons is * forced by setting last_fres to -1. The flag filter_startup * indicates, that the DSP loop runs for the first time, in this * case, the filter is set directly, instead of smoothly fading * between old and new settings. */ iir_filter->last_fres = fres; fluid_iir_filter_calculate_coefficients(iir_filter, FLUID_BUFSIZE, output_rate); } fluid_check_fpe("voice_write DSP coefficients"); } fluidsynth-2.1.1/src/rvoice/fluid_iir_filter.h000066400000000000000000000056761362231004000214530ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_IIR_FILTER_H #define _FLUID_IIR_FILTER_H #include "fluidsynth_priv.h" typedef struct _fluid_iir_filter_t fluid_iir_filter_t; DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_init); DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_fres); DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q); void fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, fluid_real_t *dsp_buf, int dsp_buf_count); void fluid_iir_filter_reset(fluid_iir_filter_t *iir_filter); void fluid_iir_filter_calc(fluid_iir_filter_t *iir_filter, fluid_real_t output_rate, fluid_real_t fres_mod); /* We can't do information hiding here, as fluid_voice_t includes the struct without a pointer. */ struct _fluid_iir_filter_t { enum fluid_iir_filter_type type; /* specifies the type of this filter */ enum fluid_iir_filter_flags flags; /* additional flags to customize this filter */ /* filter coefficients */ /* The coefficients are normalized to a0. */ /* b0 and b2 are identical => b02 */ fluid_real_t b02; /* b0 / a0 */ fluid_real_t b1; /* b1 / a0 */ fluid_real_t a1; /* a0 / a0 */ fluid_real_t a2; /* a1 / a0 */ fluid_real_t b02_incr; fluid_real_t b1_incr; fluid_real_t a1_incr; fluid_real_t a2_incr; int filter_coeff_incr_count; int compensate_incr; /* Flag: If set, must compensate history */ fluid_real_t hist1, hist2; /* Sample history for the IIR filter */ int filter_startup; /* Flag: If set, the filter will be set directly. Else it changes smoothly. */ fluid_real_t fres; /* the resonance frequency, in cents (not absolute cents) */ fluid_real_t last_fres; /* Current resonance frequency of the IIR filter */ /* Serves as a flag: A deviation between fres and last_fres */ /* indicates, that the filter has to be recalculated. */ fluid_real_t q_lin; /* the q-factor on a linear scale */ fluid_real_t filter_gain; /* Gain correction factor, depends on q */ }; #endif fluidsynth-2.1.1/src/rvoice/fluid_lfo.c000066400000000000000000000005111362231004000200550ustar00rootroot00000000000000#include "fluid_lfo.h" DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_incr) { fluid_lfo_t *lfo = obj; fluid_real_t increment = param[0].real; lfo->increment = increment; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_delay) { fluid_lfo_t *lfo = obj; unsigned int delay = param[0].i; lfo->delay = delay; } fluidsynth-2.1.1/src/rvoice/fluid_lfo.h000066400000000000000000000037721362231004000200760ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_LFO_H #define _FLUID_LFO_H #include "fluid_sys.h" typedef struct _fluid_lfo_t fluid_lfo_t; struct _fluid_lfo_t { fluid_real_t val; /* the current value of the LFO */ unsigned int delay; /* the delay of the lfo in samples */ fluid_real_t increment; /* the lfo frequency is converted to a per-buffer increment */ }; static FLUID_INLINE void fluid_lfo_reset(fluid_lfo_t *lfo) { lfo->val = 0.0f; } // These two cannot be inlined since they're used by event_dispatch DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_incr); DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_delay); static FLUID_INLINE fluid_real_t fluid_lfo_get_val(fluid_lfo_t *lfo) { return lfo->val; } static FLUID_INLINE void fluid_lfo_calc(fluid_lfo_t *lfo, unsigned int cur_delay) { if(cur_delay < lfo->delay) { return; } lfo->val += lfo->increment; if(lfo->val > (fluid_real_t) 1.0) { lfo->increment = -lfo->increment; lfo->val = (fluid_real_t) 2.0 - lfo->val; } else if(lfo->val < (fluid_real_t) -1.0) { lfo->increment = -lfo->increment; lfo->val = (fluid_real_t) -2.0 - lfo->val; } } #endif fluidsynth-2.1.1/src/rvoice/fluid_phase.h000066400000000000000000000070371362231004000204140ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_PHASE_H #define _FLUID_PHASE_H /* * phase */ #define FLUID_INTERP_BITS 8 #define FLUID_INTERP_BITS_MASK 0xff000000 #define FLUID_INTERP_BITS_SHIFT 24 #define FLUID_FRACT_MAX ((double)4294967296.0) /* fluid_phase_t * Purpose: * Playing pointer for voice playback * * When a sample is played back at a different pitch, the playing pointer in the * source sample will not advance exactly one sample per output sample. * This playing pointer is implemented using fluid_phase_t. * It is a 64 bit number. The higher 32 bits contain the 'index' (number of * the current sample), the lower 32 bits the fractional part. */ typedef uint64_t fluid_phase_t; /* Purpose: * Set a to b. * a: fluid_phase_t * b: fluid_phase_t */ #define fluid_phase_set(a,b) a=b; #define fluid_phase_set_int(a, b) ((a) = ((uint64_t)(b)) << 32) /* Purpose: * Sets the phase a to a phase increment given in b. * For example, assume b is 0.9. After setting a to it, adding a to * the playing pointer will advance it by 0.9 samples. */ #define fluid_phase_set_float(a, b) \ (a) = (((uint64_t)(b)) << 32) \ | (uint32_t) (((double)(b) - (int)(b)) * (double)FLUID_FRACT_MAX) /* create a fluid_phase_t from an index and a fraction value */ #define fluid_phase_from_index_fract(index, fract) \ ((((uint64_t)(index)) << 32) + (fract)) /* Purpose: * Return the index and the fractional part, respectively. */ #define fluid_phase_index(_x) \ ((unsigned int)((_x) >> 32)) #define fluid_phase_fract(_x) \ ((uint32_t)((_x) & 0xFFFFFFFF)) /* Get the phase index with fractional rounding */ #define fluid_phase_index_round(_x) \ ((unsigned int)(((_x) + 0x80000000) >> 32)) /* Purpose: * Takes the fractional part of the argument phase and * calculates the corresponding position in the interpolation table. * The fractional position of the playing pointer is calculated with a quite high * resolution (32 bits). It would be unpractical to keep a set of interpolation * coefficients for each possible fractional part... */ #define fluid_phase_fract_to_tablerow(_x) \ ((unsigned int)(fluid_phase_fract(_x) & FLUID_INTERP_BITS_MASK) >> FLUID_INTERP_BITS_SHIFT) #define fluid_phase_double(_x) \ ((double)(fluid_phase_index(_x)) + ((double)fluid_phase_fract(_x) / FLUID_FRACT_MAX)) /* Purpose: * Advance a by a step of b (both are fluid_phase_t). */ #define fluid_phase_incr(a, b) a += b /* Purpose: * Subtract b from a (both are fluid_phase_t). */ #define fluid_phase_decr(a, b) a -= b /* Purpose: * Subtract b samples from a. */ #define fluid_phase_sub_int(a, b) ((a) -= (uint64_t)(b) << 32) /* Purpose: * Creates the expression a.index++. */ #define fluid_phase_index_plusplus(a) (((a) += 0x100000000LL) #endif /* _FLUID_PHASE_H */ fluidsynth-2.1.1/src/rvoice/fluid_rev.c000066400000000000000000001610121362231004000200750ustar00rootroot00000000000000/****************************************************************************** * FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA * * * FDN REVERB * * Freeverb used by fluidsynth (v.1.1.10 and previous) is based on * Schroeder-Moorer reverberator: * https://ccrma.stanford.edu/~jos/pasp/Freeverb.html * * This FDN reverberation is based on jot FDN reverberator. * https://ccrma.stanford.edu/~jos/Reverb/FDN_Late_Reverberation.html * Like Freeverb it is a late reverb which is convenient for Fluidsynth. * * * .-------------------. * .-----------------| | * | - | Feedback | * | .--------------| Matrix | * | | |___________________| * | | /|\ /|\ * \|/ | .---------. .-------. | - | .------. * .->+ ---->| Delay 0 |-|L.P.F 0|--*-------->| |-> out * .---------. | | |_________| |_______| | | | left * |Tone | | | - - | |Stereo| * In ->|corrector|--* | - - | | unit | * mono |_________| | \|/ .---------. .-------. | | |-> out * ---->+ ->| Delay 7 |-|L.P.F 7|--------*-->| | right * |_________| |_______| |______| * /|\ /|\ /|\ /|\ * | | | | * roomsize --/ | width --/ | * damp ------/ level ------/ * * It takes a monophonic input and produces a stereo output. * * The parameters are the same than for Freeverb. * Also the default response of these parameters are the same than for Freeverb: * - roomsize (0 to 1): control the reverb time from 0.7 to 12.5 s. * This reverberation time is ofen called T60DC. * * - damp (0 to 1): controls the reverb time frequency dependency. * This controls the reverb time for the frequency sample rate/2 * * When 0, the reverb time for high frequencies is the same as * for DC frequency. * When > 0, high frequencies have less reverb time than lower frequencies. * * - width (0 to 100): controls the left/right output separation. * When 0, there are no separation and the signal on left and right. * output is the same. This sounds like a monophonic signal. * When 100, the separation between left and right is maximum. * * - level (0 to 1), controls the output level reverberation. * * This FDN reverb produces a better quality reverberation tail than Freeverb with * far less ringing by using modulated delay lines that help to cancel * the building of a lot of resonances in the reverberation tail even when * using only 8 delays lines (NBR_DELAYS = 8) (default). * * The frequency density (often called "modal density" is one property that * contributes to sound quality. Although 8 lines give good result, using 12 delays * lines brings the overall frequency density quality a bit higher. * This quality augmentation is noticeable particularly when using long reverb time * (roomsize = 1) on solo instrument with long release time. Of course the cpu load * augmentation is +50% relatively to 8 lines. * * As a general rule the reverberation tail quality is easier to perceive by ear * when using: * - percussive instruments (i.e piano and others). * - long reverb time (roomsize = 1). * - no damping (damp = 0). * - Using headphone. Avoid using loud speaker, you will be quickly misguided by the * natural reverberation of the room in which you are. * * The cpu load for 8 lines is a bit lower than for freeverb (- 3%), * but higher for 12 lines (+ 41%). * * * The memory consumption is less than for freeverb * (see the results table below). * * Two macros are usable at compiler time: * - NBR_DELAYS: number of delay lines. 8 (default) or 12. * - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response of * roomsize parameter. * When this macro is not defined (the default), roomsize has the same * response that Freeverb, that is: * - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). * * When this macro is defined, roomsize behaves linearly: * - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). * This linear response is convenient when using GUI controls. * * -------------------------------------------------------------------------- * Compare table: * Note: the cpu load in % are relative each to other. These values are * given by the fluidsynth profile commands. * -------------------------------------------------------------------------- * reverb | NBR_DELAYS | Performances | memory size | quality * | | (cpu_load: %) | (bytes)(see note) | * ========================================================================== * freeverb | 2 x 8 comb | 0.670 % | 204616 | ringing * | 2 x 4 all-pass | | | * ----------|--------------------------------------------------------------- * FDN | 8 | 0.650 % | 112160 | far less * modulated | |(feeverb - 3%) | (55% freeverb) | ringing * |--------------------------------------------------------------- * | 12 | 0.942 % | 168240 | best than * | |(freeverb + 41%) | (82 %freeverb) | 8 lines *--------------------------------------------------------------------------- * * Note: * Values in this column is the memory consumption for sample rate <= 44100Hz. * For sample rate > 44100Hz , multiply these values by (sample rate / 44100Hz). * * *---------------------------------------------------------------------------- * 'Denormalise' method to avoid loss of performance. * -------------------------------------------------- * According to music-dsp thread 'Denormalise', Pentium processors * have a hardware 'feature', that is of interest here, related to * numeric underflow. We have a recursive filter. The output decays * exponentially, if the input stops. So the numbers get smaller and * smaller... At some point, they reach 'denormal' level. This will * lead to drastic spikes in the CPU load. The effect was reproduced * with the reverb - sometimes the average load over 10 s doubles!!. * * The 'undenormalise' macro fixes the problem: As soon as the number * is close enough to denormal level, the macro forces the number to * 0.0f. The original macro is: * * #define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f * * This will zero out a number when it reaches the denormal level. * Advantage: Maximum dynamic range Disadvantage: We'll have to check * every sample, expensive. The alternative macro comes from a later * mail from Jon Watte. It will zap a number before it reaches * denormal level. Jon suggests to run it once per block instead of * every sample. */ /* Denormalising part II: * * Another method fixes the problem cheaper: Use a small DC-offset in * the filter calculations. Now the signals converge not against 0, * but against the offset. The constant offset is invisible from the * outside world (i.e. it does not appear at the output. There is a * very small turn-on transient response, which should not cause * problems. */ #include "fluid_rev.h" #include "fluid_sys.h" /*---------------------------------------------------------------------------- Configuration macros at compiler time. 3 macros are usable at compiler time: - NBR_DELAYs: number of delay lines. 8 (default) or 12. - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response for roomsize parameter. - DENORMALISING enable denormalising handling. -----------------------------------------------------------------------------*/ //#define INFOS_PRINT /* allows message to be printed on the console. */ /* Number of delay lines (must be only 8 or 12) 8 is the default. 12 produces a better quality but is +50% cpu expensive */ #define NBR_DELAYS 8 /* default*/ /* response curve of parameter roomsize */ /* The default response is the same as Freeverb: - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). when ROOMSIZE_RESPONSE_LINEAR is defined, the response is: - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). */ //#define ROOMSIZE_RESPONSE_LINEAR /* DENORMALISING enable denormalising handling */ #define DENORMALISING #ifdef DENORMALISING #define DC_OFFSET 1e-8f #else #define DC_OFFSET 0.0f #endif /*---------------------------------------------------------------------------- Initial internal reverb settings (at reverb creation time) -----------------------------------------------------------------------------*/ /* SCALE_WET_WIDTH is a compensation weight factor to get an output amplitude (wet) rather independent of the width setting. 0: the output amplitude is fully dependant on the width setting. >0: the output amplitude is less dependant on the width setting. With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather independent of width setting (see fluid_revmodel_update()). */ #define SCALE_WET_WIDTH 0.2f /* It is best to inject the input signal less ofen. This contributes to obtain a flatter response on comb filter. So the input gain is set to 0.1 rather 1.0. */ #define FIXED_GAIN 0.1f /* input gain */ /* SCALE_WET is adjusted to 5.0 to get internal output level equivalent to freeverb */ #define SCALE_WET 5.0f /* scale output gain */ /*---------------------------------------------------------------------------- Internal FDN late reverb settings -----------------------------------------------------------------------------*/ /*-- Reverberation time settings ---------------------------------- MIN_DC_REV_TIME est defined egal to the minimum value of freeverb: MAX_DC_REV_TIME est defined egal to the maximum value of freeverb: T60DC is computed from gi and the longuest delay line in freeverb: L8 = 1617 T60 = -3 * Li * T / log10(gi) T60 = -3 * Li * / (log10(gi) * sr) - Li: length of comb filter delay line. - sr: sample rate. - gi: the feedback gain. The minimum value for freeverb correspond to gi = 0.7. with Mi = 1617, sr at 44100 Hz, and gi = 0.7 => MIN_DC_REV_TIME = 0.7 s The maximum value for freeverb correspond to gi = 0.98. with Mi = 1617, sr at 44100 Hz, and gi = 0.98 => MAX_DC_REV_TIME = 12.5 s */ #define MIN_DC_REV_TIME 0.7f /* minimum T60DC reverb time: seconds */ #define MAX_DC_REV_TIME 12.5f /* maximumm T60DC time in seconds */ #define RANGE_REV_TIME (MAX_DC_REV_TIME - MIN_DC_REV_TIME) /* macro to compute internal reverberation time versus roomsize parameter */ #define GET_DC_REV_TIME(roomsize) (MIN_DC_REV_TIME + RANGE_REV_TIME * roomsize) /*-- Modulation related settings ----------------------------------*/ /* For many instruments, the range for MOD_FREQ and MOD_DEPTH should be: MOD_DEPTH: [3..6] (in samples). MOD_FREQ: [0.5 ..2.0] (in Hz). Values below the lower limits are often not sufficient to cancel unwanted "ringing"(resonant frequency). Values above upper limits augment the unwanted "chorus". With NBR_DELAYS to 8: MOD_DEPTH must be >= 4 to cancel the unwanted "ringing".[4..6]. With NBR_DELAYS to 12: MOD_DEPTH to 3 is sufficient to cancel the unwanted "ringing".[3..6] */ #define MOD_DEPTH 4 /* modulation depth (samples)*/ #define MOD_RATE 50 /* modulation rate (samples)*/ #define MOD_FREQ 1.0f /* modulation frequency (Hz) */ /* Number of samples to add to the desired length of a delay line. This allow to take account of modulation interpolation. 1 is sufficient with MOD_DEPTH equal to 6. */ #define INTERP_SAMPLES_NBR 1 /* phase offset between modulators waveform */ #define MOD_PHASE (360.0f/(float) NBR_DELAYS) #if (NBR_DELAYS == 8) #define DELAY_L0 601 #define DELAY_L1 691 #define DELAY_L2 773 #define DELAY_L3 839 #define DELAY_L4 919 #define DELAY_L5 997 #define DELAY_L6 1061 #define DELAY_L7 1129 #elif (NBR_DELAYS == 12) #define DELAY_L0 601 #define DELAY_L1 691 #define DELAY_L2 773 #define DELAY_L3 839 #define DELAY_L4 919 #define DELAY_L5 997 #define DELAY_L6 1061 #define DELAY_L7 1093 #define DELAY_L8 1129 #define DELAY_L9 1151 #define DELAY_L10 1171 #define DELAY_L11 1187 #endif /*---------------------------------------------------------------------------*/ /* The FDN late feed back matrix: A T A = P - 2 / N * u * u N N N N N: the matrix dimension (i.e NBR_DELAYS). P: permutation matrix. u: is a colomn vector of 1. */ #define FDN_MATRIX_FACTOR (fluid_real_t)(-2.0 / NBR_DELAYS) /*---------------------------------------------------------------------------- Internal FDN late structures and static functions -----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------- Delay absorbent low pass filter -----------------------------------------------------------------------------*/ typedef struct { fluid_real_t buffer; fluid_real_t b0, a1; /* filter coefficients */ } fdn_delay_lpf; /*----------------------------------------------------------------------------- Sets coefficients for delay absorbent low pass filter. @param lpf pointer on low pass filter structure. @param b0,a1 coefficients. -----------------------------------------------------------------------------*/ static void set_fdn_delay_lpf(fdn_delay_lpf *lpf, fluid_real_t b0, fluid_real_t a1) { lpf->b0 = b0; lpf->a1 = a1; } /*----------------------------------------------------------------------------- Process delay absorbent low pass filter. @param mod_delay modulated delay line. @param in, input sample. @param out output sample. -----------------------------------------------------------------------------*/ /* process low pass damping filter (input, output, delay) */ #define process_damping_filter(in,out,mod_delay) \ {\ out = in * mod_delay->dl.damping.b0 - mod_delay->dl.damping.buffer * \ mod_delay->dl.damping.a1;\ mod_delay->dl.damping.buffer = out;\ }\ /*----------------------------------------------------------------------------- Delay line : The delay line is composed of the line plus an absorbent low pass filter to get frequency dependant reverb time. -----------------------------------------------------------------------------*/ typedef struct { fluid_real_t *line; /* buffer line */ int size; /* effective internal size (in samples) */ /*-------------*/ int line_in; /* line in position */ int line_out; /* line out position */ /*-------------*/ fdn_delay_lpf damping; /* damping low pass filter */ } delay_line; /*----------------------------------------------------------------------------- Clears a delay line to DC_OFFSET float value. @param dl pointer on delay line structure -----------------------------------------------------------------------------*/ static void clear_delay_line(delay_line *dl) { int i; for(i = 0; i < dl->size; i++) { dl->line[i] = DC_OFFSET; } } /*----------------------------------------------------------------------------- Push a sample val into the delay line -----------------------------------------------------------------------------*/ #define push_in_delay_line(dl, val) \ {\ dl->line[dl->line_in] = val;\ /* Incrementation and circular motion if necessary */\ if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ }\ /*----------------------------------------------------------------------------- Modulator for modulated delay line -----------------------------------------------------------------------------*/ /*----------------------------------------------------------------------------- Sinusoidal modulator -----------------------------------------------------------------------------*/ /* modulator are integrated in modulated delay line */ typedef struct { fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ fluid_real_t buffer1; /* buffer1 */ fluid_real_t buffer2; /* buffer2 */ fluid_real_t reset_buffer2;/* reset value of buffer2 */ } sinus_modulator; /*----------------------------------------------------------------------------- Sets the frequency of sinus oscillator. @param mod pointer on modulator structure. @param freq frequency of the oscillator in Hz. @param sample_rate sample rate on audio output in Hz. @param phase initial phase of the oscillator in degree (0 to 360). -----------------------------------------------------------------------------*/ static void set_mod_frequency(sinus_modulator *mod, float freq, float sample_rate, float phase) { fluid_real_t w = 2 * FLUID_M_PI * freq / sample_rate; /* initial angle */ fluid_real_t a; mod->a1 = 2 * FLUID_COS(w); a = (2 * FLUID_M_PI / 360) * phase; mod->buffer2 = FLUID_SIN(a - w); /* y(n-1) = sin(-intial angle) */ mod->buffer1 = FLUID_SIN(a); /* y(n) = sin(initial phase) */ mod->reset_buffer2 = FLUID_SIN(FLUID_M_PI / 2 - w); /* reset value for PI/2 */ } /*----------------------------------------------------------------------------- Gets current value of sinus modulator: y(n) = a1 . y(n-1) - y(n-2) out = a1 . buffer1 - buffer2 @param pointer on modulator structure. @return current value of the modulator sine wave. -----------------------------------------------------------------------------*/ static FLUID_INLINE fluid_real_t get_mod_sinus(sinus_modulator *mod) { fluid_real_t out; out = mod->a1 * mod->buffer1 - mod->buffer2; mod->buffer2 = mod->buffer1; if(out >= 1.0f) /* reset in case of instability near PI/2 */ { out = 1.0f; /* forces output to the right value */ mod->buffer2 = mod->reset_buffer2; } if(out <= -1.0f) /* reset in case of instability near -PI/2 */ { out = -1.0f; /* forces output to the right value */ mod->buffer2 = - mod->reset_buffer2; } mod->buffer1 = out; return out; } /*----------------------------------------------------------------------------- Modulated delay line. The line is composed of: - the delay line with its damping low pass filter. - the sinusoidal modulator. - center output position modulated by the modulator. - variable rate control of center output position. - first order All-Pass interpolator. -----------------------------------------------------------------------------*/ typedef struct { /* delay line with damping low pass filter member */ delay_line dl; /* delayed line */ /*---------------------------*/ /* Sinusoidal modulator member */ sinus_modulator mod; /* sinus modulator */ /*-------------------------*/ /* center output position members */ fluid_real_t center_pos_mod; /* center output position modulated by modulator */ int mod_depth; /* modulation depth (in samples) */ /*-------------------------*/ /* variable rate control of center output position */ int index_rate; /* index rate to know when to update center_pos_mod */ int mod_rate; /* rate at which center_pos_mod is updated */ /*-------------------------*/ /* first order All-Pass interpolator members */ fluid_real_t frac_pos_mod; /* fractional position part between samples) */ /* previous value used when interpolating using fractional */ fluid_real_t buffer; } mod_delay_line; /*----------------------------------------------------------------------------- Modulated delay line initialization. Sets the length line ( alloc delay samples). Remark: the function sets the internal size accordling to the length delay_length. As the delay line is a modulated line, its internal size is augmented by mod_depth. The size is also augmented by INTERP_SAMPLES_NBR to take account of interpolation. @param mdl, pointer on modulated delay line. @param delay_length the length of the delay line in samples. @param mod_depth depth of the modulation in samples (amplitude of the sine wave). @param mod_rate the rate of the modulation in samples. @return FLUID_OK if success , FLUID_FAILED if memory error. Return FLUID_OK if success, FLUID_FAILED if memory error. -----------------------------------------------------------------------------*/ static int set_mod_delay_line(mod_delay_line *mdl, int delay_length, int mod_depth, int mod_rate ) { /*-----------------------------------------------------------------------*/ /* checks parameter */ if(delay_length < 1) { return FLUID_FAILED; } /* limits mod_depth to the requested delay length */ if(mod_depth >= delay_length) { FLUID_LOG(FLUID_INFO, "fdn reverb: modulation depth has been limited"); mod_depth = delay_length - 1; } mdl->mod_depth = mod_depth; /*----------------------------------------------------------------------- allocates delay_line and initialize members: - line, size, line_in, line_out... */ { /* total size of the line: size = INTERP_SAMPLES_NBR + mod_depth + delay_length */ mdl->dl.size = delay_length + mod_depth + INTERP_SAMPLES_NBR; mdl->dl.line = FLUID_ARRAY(fluid_real_t, mdl->dl.size); if(! mdl->dl.line) { return FLUID_FAILED; } clear_delay_line(&mdl->dl); /* clears the buffer */ /* Initializes line_in to the start of the buffer */ mdl->dl.line_in = 0; /* Initializes line_out index INTERP_SAMPLES_NBR samples after line_in */ /* so that the delay between line_out and line_in is: mod_depth + delay_length */ mdl->dl.line_out = mdl->dl.line_in + INTERP_SAMPLES_NBR; } /* Damping low pass filter -------------------*/ mdl->dl.damping.buffer = 0; /*------------------------------------------------------------------------ Initializes modulation members: - modulated center position: center_pos_mod - index rate to know when to update center_pos_mod:index_rate - modulation rate (the speed at which center_pos_mod is modulated: mod_rate - interpolator member: buffer, frac_pos_mod -------------------------------------------------------------------------*/ /* Sets the modulation rate. This rate defines how often the center position (center_pos_mod ) is modulated . The value is expressed in samples. The default value is 1 that means that center_pos_mod is updated at every sample. For example with a value of 2, the center position position will be updated only one time every 2 samples only. */ mdl->mod_rate = 1; /* default modulation rate: every one sample */ if(mod_rate > mdl->dl.size) { FLUID_LOG(FLUID_INFO, "fdn reverb: modulation rate is out of range"); } else { mdl->mod_rate = mod_rate; } /* Initializes the modulated center position (center_pos_mod) so that: - the delay between line_out and center_pos_mod is mod_depth. - the delay between center_pos_mod and line_in is delay_length. */ mdl->center_pos_mod = (fluid_real_t) INTERP_SAMPLES_NBR + mod_depth; /* index rate to control when to update center_pos_mod */ /* Important: must be set to get center_pos_mod immediately used for the reading of first sample (see get_mod_delay()) */ mdl->index_rate = mdl->mod_rate; /* initializes 1st order All-Pass interpolator members */ mdl->buffer = 0; /* previous delay sample value */ mdl->frac_pos_mod = 0; /* fractional position (between consecutives sample) */ return FLUID_OK; } /*----------------------------------------------------------------------------- Return norminal delay length @param mdl, pointer on modulated delay line. -----------------------------------------------------------------------------*/ static int get_mod_delay_line_length(mod_delay_line *mdl) { return (mdl->dl.size - mdl->mod_depth - INTERP_SAMPLES_NBR); } /*----------------------------------------------------------------------------- Reads the sample value out of the modulated delay line. @param mdl, pointer on modulated delay line. @return the sample value. -----------------------------------------------------------------------------*/ static FLUID_INLINE fluid_real_t get_mod_delay(mod_delay_line *mdl) { fluid_real_t out_index; /* new modulated index position */ int int_out_index; /* integer part of out_index */ fluid_real_t out; /* value to return */ /* Checks if the modulator must be updated (every mod_rate samples). */ /* Important: center_pos_mod must be used immediately for the first sample. So, mdl->index_rate must be initialized to mdl->mod_rate (set_mod_delay_line()) */ if(++mdl->index_rate >= mdl->mod_rate) { mdl->index_rate = 0; /* out_index = center position (center_pos_mod) + sinus waweform */ out_index = mdl->center_pos_mod + get_mod_sinus(&mdl->mod) * mdl->mod_depth; /* extracts integer part in int_out_index */ if(out_index >= 0.0f) { int_out_index = (int)out_index; /* current integer part */ /* forces read index (line_out) with integer modulation value */ /* Boundary check and circular motion as needed */ if((mdl->dl.line_out = int_out_index) >= mdl->dl.size) { mdl->dl.line_out -= mdl->dl.size; } } else /* negative */ { int_out_index = (int)(out_index - 1); /* previous integer part */ /* forces read index (line_out) with integer modulation value */ /* circular motion as needed */ mdl->dl.line_out = int_out_index + mdl->dl.size; } /* extracts fractionnal part. (it will be used when interpolating between line_out and line_out +1) and memorize it. Memorizing is necessary for modulation rate above 1 */ mdl->frac_pos_mod = out_index - int_out_index; /* updates center position (center_pos_mod) to the next position specified by modulation rate */ if((mdl->center_pos_mod += mdl->mod_rate) >= mdl->dl.size) { mdl->center_pos_mod -= mdl->dl.size; } } /* First order all-pass interpolation ----------------------------------*/ /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ /* begins interpolation: read current sample */ out = mdl->dl.line[mdl->dl.line_out]; /* updates line_out to the next sample. Boundary check and circular motion as needed */ if(++mdl->dl.line_out >= mdl->dl.size) { mdl->dl.line_out -= mdl->dl.size; } /* Fractional interpolation between next sample (at next position) and previous output added to current sample. */ out += mdl->frac_pos_mod * (mdl->dl.line[mdl->dl.line_out] - mdl->buffer); mdl->buffer = out; /* memorizes current output */ return out; } /*----------------------------------------------------------------------------- Late structure -----------------------------------------------------------------------------*/ struct _fluid_late { fluid_real_t samplerate; /* sample rate */ /*----- High pass tone corrector -------------------------------------*/ fluid_real_t tone_buffer; fluid_real_t b1, b2; /*----- Modulated delay lines lines ----------------------------------*/ mod_delay_line mod_delay_lines[NBR_DELAYS]; /*-----------------------------------------------------------------------*/ /* Output coefficients for separate Left and right stereo outputs */ fluid_real_t out_left_gain[NBR_DELAYS]; /* Left delay lines' output gains */ fluid_real_t out_right_gain[NBR_DELAYS];/* Right delay lines' output gains*/ }; typedef struct _fluid_late fluid_late; /*----------------------------------------------------------------------------- fluidsynth reverb structure -----------------------------------------------------------------------------*/ struct _fluid_revmodel_t { /* reverb parameters */ fluid_real_t roomsize; /* acting on reverb time */ fluid_real_t damp; /* acting on frequency dependent reverb time */ fluid_real_t level, wet1, wet2; /* output level */ fluid_real_t width; /* width stereo separation */ /* fdn reverberation structure */ fluid_late late; }; /*----------------------------------------------------------------------------- Updates Reverb time and absorbent filters coefficients from parameters: @param late pointer on late structure. @param roomsize (0 to 1): acting on reverb time. @param damping (0 to 1): acting on absorbent damping filter. Design formulas: https://ccrma.stanford.edu/~jos/Reverb/First_Order_Delay_Filter_Design.html https://ccrma.stanford.edu/~jos/Reverb/Tonal_Correction_Filter.html -----------------------------------------------------------------------------*/ static void update_rev_time_damping(fluid_late *late, fluid_real_t roomsize, fluid_real_t damp) { int i; fluid_real_t sample_period = 1 / late->samplerate; /* Sampling period */ int delay_length; /* delay length */ fluid_real_t dc_rev_time; /* Reverb time at 0 Hz (in seconds) */ fluid_real_t alpha, alpha2; /*-------------------------------------------- Computes dc_rev_time and alpha ----------------------------------------------*/ { fluid_real_t gi_tmp, ai_tmp; #ifdef ROOMSIZE_RESPONSE_LINEAR /* roomsize parameter behave linearly: * - roomsize (0 to 1) controls reverb time linearly (0.7 to 10 s). * This linear response is convenient when using GUI controls. */ /*----------------------------------------- Computes dc_rev_time ------------------------------------------*/ dc_rev_time = GET_DC_REV_TIME(roomsize); delay_length = get_mod_delay_line_length(&late->mod_delay_lines[NBR_DELAYS - 1]); /* computes gi_tmp from dc_rev_time using relation E2 */ gi_tmp = FLUID_POW(10, -3 * delay_length * sample_period / dc_rev_time); /* E2 */ #else /* roomsize parameters have the same response that Freeverb, that is: * - roomsize (0 to 1) controls concave reverb time (0.7 to 10 s). */ { /*----------------------------------------- Computes dc_rev_time ------------------------------------------*/ fluid_real_t gi_min, gi_max; /* values gi_min et gi_max are computed using E2 for the line with maximum delay */ delay_length = get_mod_delay_line_length(&late->mod_delay_lines[NBR_DELAYS - 1]); gi_max = FLUID_POW(10, (-3 * delay_length / MAX_DC_REV_TIME) * sample_period); /* E2 */ gi_min = FLUID_POW(10, (-3 * delay_length / MIN_DC_REV_TIME) * sample_period); /* E2 */ /* gi = f(roomsize, gi_max, gi_min) */ gi_tmp = gi_min + roomsize * (gi_max - gi_min); /* Computes T60DC from gi using inverse of relation E2.*/ dc_rev_time = -3 * FLUID_M_LN10 * delay_length * sample_period / FLUID_LOGF(gi_tmp); } #endif /* ROOMSIZE_RESPONSE_LINEAR */ /*-------------------------------------------- Computes alpha ----------------------------------------------*/ /* Computes alpha from damp,ai_tmp,gi_tmp using relation R */ /* - damp (0 to 1) controls concave reverb time for fs/2 frequency (T60DC to 0) */ ai_tmp = 1.0f * damp; /* Preserve the square of R */ alpha2 = 1.f / (1.f - ai_tmp / ((20.f / 80.f) * FLUID_LOGF(gi_tmp))); alpha = FLUID_SQRT(alpha2); /* R */ } /* updates tone corrector coefficients b1,b2 from alpha */ { /* Beta = (1 - alpha) / (1 + alpha) b1 = 1/(1-beta) b2 = beta * b1 */ fluid_real_t beta = (1 - alpha) / (1 + alpha); late->b1 = 1 / (1 - beta); late->b2 = beta * late->b1; late->tone_buffer = 0.0f; } /* updates damping coefficients of all lines (gi , ai) from dc_rev_time, alpha */ for(i = 0; i < NBR_DELAYS; i++) { fluid_real_t gi, ai; /* delay length */ delay_length = get_mod_delay_line_length(&late->mod_delay_lines[i]); /* iir low pass filter gain */ gi = FLUID_POW(10, -3 * delay_length * sample_period / dc_rev_time); /* iir low pass filter feedback gain */ ai = (20.f / 80.f) * FLUID_LOGF(gi) * (1.f - 1.f / alpha2); /* b0 = gi * (1 - ai), a1 = - ai */ set_fdn_delay_lpf(&late->mod_delay_lines[i].dl.damping, gi * (1.f - ai), -ai); } } /*----------------------------------------------------------------------------- Updates stereo coefficients @param late pointer on late structure @param wet level integrated in stereo coefficients. -----------------------------------------------------------------------------*/ static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) { int i; fluid_real_t wet; for(i = 0; i < NBR_DELAYS; i++) { /* delay lines output gains vectors Left and Right L R 0 | 1 1| 1 |-1 1| 2 | 1 -1| 3 |-1 -1| 4 | 1 1| 5 |-1 1| stereo gain = 6 | 1 -1| 7 |-1 -1| 8 | 1 1| 9 |-1 1| 10| 1 -1| 11|-1 -1| */ /* for left line: 00, ,02, ,04, ,06, ,08, ,10, ,12,... left_gain = +1 */ /* for left line: ,01, ,03, ,05, ,07, ,09, ,11,... left_gain = -1 */ wet = wet1; if(i & 1) { wet = -wet1; } late->out_left_gain[i] = wet; /* for right line: 00,01, ,04,05, ,08,09, ,12,13 right_gain = +1 */ /* for right line: ,02 ,03, ,06,07, ,10,11,... right_gain = -1 */ wet = wet1; if(i & 2) { wet = -wet1; } late->out_right_gain[i] = wet; } } /*----------------------------------------------------------------------------- fluid_late destructor. @param late pointer on late structure. -----------------------------------------------------------------------------*/ static void delete_fluid_rev_late(fluid_late *late) { int i; fluid_return_if_fail(late != NULL); /* free the delay lines */ for(i = 0; i < NBR_DELAYS; i++) { FLUID_FREE(late->mod_delay_lines[i].dl.line); } } /*----------------------------------------------------------------------------- Creates all modulated lines. @param late, pointer on the fnd late reverb to initialize. @param sample_rate, the audio sample rate. @return FLUID_OK if success, FLUID_FAILED otherwise. -----------------------------------------------------------------------------*/ static int create_mod_delay_lines(fluid_late *late, fluid_real_t sample_rate) { /* Delay lines length table (in samples) */ static const int delay_length[NBR_DELAYS] = { DELAY_L0, DELAY_L1, DELAY_L2, DELAY_L3, DELAY_L4, DELAY_L5, DELAY_L6, DELAY_L7, #if (NBR_DELAYS == 12) DELAY_L8, DELAY_L9, DELAY_L10, DELAY_L11 #endif }; int result; /* return value */ int i; /* 1)"modal density" is one property that contributes to the quality of the reverb tail. The more is the modal density, the less are unwanted resonant frequencies build during the decay time: modal density = total delay / sample rate. Delay line's length given by static table delay_length[] is nominal to get minimum modal density of 0.15 at sample rate 44100Hz. Here we set length_factor to 2 to multiply this nominal modal density by 2. This leads to a default modal density of 0.15 * 2 = 0.3 for sample rate <= 44100. For sample rate > 44100, length_factor is multiplied by sample_rate / 44100. This ensures that the default modal density keeps inchanged. (Without this compensation, the default modal density would be diminished for new sample rate change above 44100Hz). 2)Modulated delay line contributes to diminish resonnant frequencies (often called "ringing"). Modulation depth (mod_depth) is set to nominal value of MOD_DEPTH at sample rate 44100Hz. For sample rate > 44100, mod_depth is multiplied by sample_rate / 44100. This ensures that the effect of modulated delay line keeps inchanged. */ fluid_real_t length_factor = 2.0f; fluid_real_t mod_depth = MOD_DEPTH; if(sample_rate > 44100.0f) { fluid_real_t sample_rate_factor = sample_rate/44100.0f; length_factor *= sample_rate_factor; mod_depth *= sample_rate_factor; } #ifdef INFOS_PRINT // allows message to be printed on the console. printf("length_factor:%f, mod_depth:%f\n", length_factor, mod_depth); /* Print: modal density and total memory bytes */ { int i; int total_delay; /* total delay in samples */ for (i = 0, total_delay = 0; i < NBR_DELAYS; i++) { total_delay += length_factor * delay_length[i]; } /* modal density and total memory bytes */ printf("modal density:%f, total memory:%d bytes\n", total_delay / sample_rate , total_delay * sizeof(fluid_real_t)); } #endif for(i = 0; i < NBR_DELAYS; i++) /* for each delay line */ { /* allocate delay line and set local delay lines's parameters */ result = set_mod_delay_line(&late->mod_delay_lines[i], delay_length[i] * length_factor, mod_depth, MOD_RATE); if(result == FLUID_FAILED) { return FLUID_FAILED; } /* Sets local Modulators parameters: frequency and phase Each modulateur are shifted of MOD_PHASE degree */ set_mod_frequency(&late->mod_delay_lines[i].mod, MOD_FREQ * MOD_RATE, late->samplerate, (float)(MOD_PHASE * i)); } return FLUID_OK; } /*----------------------------------------------------------------------------- Creates the fdn reverb. @param late, pointer on the fnd late reverb to initialize. @param sample_rate the sample rate. @return FLUID_OK if success, FLUID_FAILED otherwise. -----------------------------------------------------------------------------*/ static int create_fluid_rev_late(fluid_late *late, fluid_real_t sample_rate) { FLUID_MEMSET(late, 0, sizeof(fluid_late)); late->samplerate = sample_rate; /*-------------------------------------------------------------------------- First initialize the modulated delay lines */ if(create_mod_delay_lines(late, sample_rate) == FLUID_FAILED) { return FLUID_FAILED; } return FLUID_OK; } /* Clears the delay lines. @param rev pointer on the reverb. */ static void fluid_revmodel_init(fluid_revmodel_t *rev) { int i; /* clears all the delay lines */ for(i = 0; i < NBR_DELAYS; i ++) { clear_delay_line(&rev->late.mod_delay_lines[i].dl); } } /* updates internal parameters. @param rev pointer on the reverb. */ static void fluid_revmodel_update(fluid_revmodel_t *rev) { /* Recalculate internal values after parameters change */ /* The stereo amplitude equation (wet1 and wet2 below) have a tendency to produce high amplitude with high width values ( 1 < width < 100). This results in an unwanted noisy output clipped by the audio card. To avoid this dependency, we divide by (1 + rev->width * SCALE_WET_WIDTH) Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), the output amplitude (wet) seems rather independent of width setting */ fluid_real_t wet = (rev->level * SCALE_WET) / (1.0f + rev->width * SCALE_WET_WIDTH); /* wet1 and wet2 are used by the stereo effect controlled by the width setting for producing a stereo ouptput from a monophonic reverb signal. Please see the note above about a side effect tendency */ rev->wet1 = wet * (rev->width / 2.0f + 0.5f); rev->wet2 = wet * ((1.0f - rev->width) / 2.0f); /* integrates wet1 in stereo coefficient (this will save one multiply) */ update_stereo_coefficient(&rev->late, rev->wet1); if(rev->wet1 > 0.0f) { rev->wet2 /= rev->wet1; } /* Reverberation time and damping */ update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); } /*---------------------------------------------------------------------------- Reverb API -----------------------------------------------------------------------------*/ /* * Creates a reverb. One created the reverb have no parameters set, so * fluid_revmodel_set() must be called at least one time after calling * new_fluid_revmodel(). * * @param sample_rate sample rate in Hz. * @return pointer on the new reverb or NULL if memory error. * Reverb API. */ fluid_revmodel_t * new_fluid_revmodel(fluid_real_t sample_rate) { fluid_revmodel_t *rev; rev = FLUID_NEW(fluid_revmodel_t); if(rev == NULL) { return NULL; } /* create fdn reverb */ if(create_fluid_rev_late(&rev->late, sample_rate) != FLUID_OK) { delete_fluid_revmodel(rev); return NULL; } return rev; } /* * free the reverb. * Note that while the reverb is used by calling any fluid_revmodel_processXXX() * function, calling delete_fluid_revmodel() isn't multi task safe because * delay line are freed. To deal properly with this issue follow the steps: * * 1) Stop reverb processing (i.e disable calling of any fluid_revmodel_processXXX(). * reverb functions. * 2) Delete the reverb by calling delete_fluid_revmodel(). * * @param rev pointer on reverb to free. * Reverb API. */ void delete_fluid_revmodel(fluid_revmodel_t *rev) { fluid_return_if_fail(rev != NULL); delete_fluid_rev_late(&rev->late); FLUID_FREE(rev); } /* * Sets one or more reverb parameters. Note this must be called at least one * time after calling new_fluid_revmodel(). * * Note that while the reverb is used by calling any fluid_revmodel_processXXX() * function, calling fluid_revmodel_set() could produce audible clics. * If this is a problem, optionally call fluid_revmodel_reset() before calling * fluid_revmodel_set(). * * @param rev Reverb instance. * @param set One or more flags from #fluid_revmodel_set_t indicating what * parameters to set (#FLUID_REVMODEL_SET_ALL to set all parameters). * @param roomsize Reverb room size. * @param damping Reverb damping. * @param width Reverb width. * @param level Reverb level. * * Reverb API. */ void fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, fluid_real_t damping, fluid_real_t width, fluid_real_t level) { /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_ROOMSIZE) { fluid_clip(roomsize, 0.0f, 1.0f); rev->roomsize = roomsize; } /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_DAMPING) { fluid_clip(damping, 0.0f, 1.0f); rev->damp = damping; } /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_WIDTH) { rev->width = width; } /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_LEVEL) { fluid_clip(level, 0.0f, 1.0f); rev->level = level; } /* updates internal parameters */ fluid_revmodel_update(rev); } /* * Applies a sample rate change on the reverb. * Note that while the reverb is used by calling any fluid_revmodel_processXXX() * function, calling fluid_revmodel_samplerate_change() isn't multi task safe because * delay line are memory reallocated. To deal properly with this issue follow * the steps: * 1) Stop reverb processing (i.e disable calling of any fluid_revmodel_processXXX(). * reverb functions. * 2) Change sample rate by calling fluid_revmodel_samplerate_change(). * 3) Restart reverb processing (i.e enabling calling of any fluid_revmodel_processXXX() * reverb functions. * * Another solution is to substitute step (2): * 2.1) delete the reverb by calling delete_fluid_revmodel(). * 2.2) create the reverb by calling new_fluid_revmodel(). * * @param rev the reverb. * @param sample_rate new sample rate value. * @return FLUID_OK if success, FLUID_FAILED otherwise (memory error). * Reverb API. */ int fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate) { rev->late.samplerate = sample_rate; /* new sample rate value */ /* free all delay lines */ delete_fluid_rev_late(&rev->late); /* create all delay lines */ if(create_mod_delay_lines(&rev->late, sample_rate) == FLUID_FAILED) { return FLUID_FAILED; /* memory error */ } /* updates damping filter coefficients according to sample rate change */ update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); return FLUID_OK; } /* * Damps the reverb by clearing the delay lines. * @param rev the reverb. * * Reverb API. */ void fluid_revmodel_reset(fluid_revmodel_t *rev) { fluid_revmodel_init(rev); } /*----------------------------------------------------------------------------- * fdn reverb process replace. * @param rev pointer on reverb. * @param in monophonic buffer input (FLUID_BUFSIZE sample). * @param left_out stereo left processed output (FLUID_BUFSIZE sample). * @param right_out stereo right processed output (FLUID_BUFSIZE sample). * * The processed reverb is replacing anything there in out. * Reverb API. -----------------------------------------------------------------------------*/ void fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out) { int i, k; fluid_real_t xn; /* mono input x(n) */ fluid_real_t out_tone_filter; /* tone corrector output */ fluid_real_t out_left, out_right; /* output stereo Left and Right */ fluid_real_t matrix_factor; /* partial matrix computation */ fluid_real_t delay_out_s; /* sample */ fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ for(k = 0; k < FLUID_BUFSIZE; k++) { /* stereo output */ out_left = out_right = 0; #ifdef DENORMALISING /* Input is adjusted by DC_OFFSET. */ xn = (in[k]) * FIXED_GAIN + DC_OFFSET; #else xn = (in[k]) * FIXED_GAIN; #endif /*-------------------------------------------------------------------- tone correction. */ out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; rev->late.tone_buffer = xn; xn = out_tone_filter; /*-------------------------------------------------------------------- process feedback delayed network: - xn is the input signal. - before inserting in the line input we first we get the delay lines output, filter them and compute output in delay_out[]. - also matrix_factor is computed (to simplify further matrix product) ---------------------------------------------------------------------*/ /* We begin with the modulated output delay line + damping filter */ matrix_factor = 0; for(i = 0; i < NBR_DELAYS; i++) { mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; /* get current modulated output */ delay_out_s = get_mod_delay(mdl); /* process low pass damping filter (input:delay_out_s, output:delay_out_s) */ process_damping_filter(delay_out_s, delay_out_s, mdl); /* Result in delay_out[], and matrix_factor. These will be of use later during input line process */ delay_out[i] = delay_out_s; /* result in delay_out[] */ matrix_factor += delay_out_s; /* result in matrix_factor */ /* Process stereo output */ /* stereo left = left + out_left_gain * delay_out */ out_left += rev->late.out_left_gain[i] * delay_out_s; /* stereo right= right+ out_right_gain * delay_out */ out_right += rev->late.out_right_gain[i] * delay_out_s; } /* now we process the input delay line.Each input is a combination of - xn: input signal - delay_out[] the output of a delay line given by a permutation matrix P - and matrix_factor. This computes: in_delay_line = xn + (delay_out[] * matrix A) with an algorithm equivalent but faster than using a product with matrix A. */ /* matrix_factor = output sum * (-2.0)/N */ matrix_factor *= FDN_MATRIX_FACTOR; matrix_factor += xn; /* adds reverb input signal */ for(i = 1; i < NBR_DELAYS; i++) { /* delay_in[i-1] = delay_out[i] + matrix_factor */ delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; push_in_delay_line(dl, delay_out[i] + matrix_factor); } /* last line input (NB_DELAY-1) */ /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ { delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; push_in_delay_line(dl, delay_out[0] + matrix_factor); } /*-------------------------------------------------------------------*/ #ifdef DENORMALISING /* Removes the DC offset */ out_left -= DC_OFFSET; out_right -= DC_OFFSET; #endif /* Calculates stereo output REPLACING anything already there: */ /* left_out[k] = out_left * rev->wet1 + out_right * rev->wet2; right_out[k] = out_right * rev->wet1 + out_left * rev->wet2; As wet1 is integrated in stereo coefficient wet 1 is now integrated in out_left and out_right we simplify previous relation by suppression of one multiply as this: left_out[k] = out_left + out_right * rev->wet2; right_out[k] = out_right + out_left * rev->wet2; */ left_out[k] = out_left + out_right * rev->wet2; right_out[k] = out_right + out_left * rev->wet2; } } /*----------------------------------------------------------------------------- * fdn reverb process mix. * @param rev pointer on reverb. * @param in monophonic buffer input (FLUID_BUFSIZE samples). * @param left_out stereo left processed output (FLUID_BUFSIZE samples). * @param right_out stereo right processed output (FLUID_BUFSIZE samples). * * The processed reverb is mixed in out with samples already there in out. * Reverb API. -----------------------------------------------------------------------------*/ void fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out) { int i, k; fluid_real_t xn; /* mono input x(n) */ fluid_real_t out_tone_filter; /* tone corrector output */ fluid_real_t out_left, out_right; /* output stereo Left and Right */ fluid_real_t matrix_factor; /* partial matrix term */ fluid_real_t delay_out_s; /* sample */ fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ for(k = 0; k < FLUID_BUFSIZE; k++) { /* stereo output */ out_left = out_right = 0; #ifdef DENORMALISING /* Input is adjusted by DC_OFFSET. */ xn = (in[k]) * FIXED_GAIN + DC_OFFSET; #else xn = (in[k]) * FIXED_GAIN; #endif /*-------------------------------------------------------------------- tone correction */ out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; rev->late.tone_buffer = xn; xn = out_tone_filter; /*-------------------------------------------------------------------- process feedback delayed network: - xn is the input signal. - before inserting in the line input we first we get the delay lines output, filter them and compute output in local delay_out[]. - also matrix_factor is computed (to simplify further matrix product). ---------------------------------------------------------------------*/ /* We begin with the modulated output delay line + damping filter */ matrix_factor = 0; for(i = 0; i < NBR_DELAYS; i++) { mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; /* get current modulated output */ delay_out_s = get_mod_delay(mdl); /* process low pass damping filter (input:delay_out_s, output:delay_out_s) */ process_damping_filter(delay_out_s, delay_out_s, mdl); /* Result in delay_out[], and matrix_factor. These will be of use later during input line process */ delay_out[i] = delay_out_s; /* result in delay_out[] */ matrix_factor += delay_out_s; /* result in matrix_factor */ /* Process stereo output */ /* stereo left = left + out_left_gain * delay_out */ out_left += rev->late.out_left_gain[i] * delay_out_s; /* stereo right= right+ out_right_gain * delay_out */ out_right += rev->late.out_right_gain[i] * delay_out_s; } /* now we process the input delay line. Each input is a combination of: - xn: input signal - delay_out[] the output of a delay line given by a permutation matrix P - and matrix_factor. This computes: in_delay_line = xn + (delay_out[] * matrix A) with an algorithm equivalent but faster than using a product with matrix A. */ /* matrix_factor = output sum * (-2.0)/N */ matrix_factor *= FDN_MATRIX_FACTOR; matrix_factor += xn; /* adds reverb input signal */ for(i = 1; i < NBR_DELAYS; i++) { /* delay_in[i-1] = delay_out[i] + matrix_factor */ delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; push_in_delay_line(dl, delay_out[i] + matrix_factor); } /* last line input (NB_DELAY-1) */ /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ { delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; push_in_delay_line(dl, delay_out[0] + matrix_factor); } /*-------------------------------------------------------------------*/ #ifdef DENORMALISING /* Removes the DC offset */ out_left -= DC_OFFSET; out_right -= DC_OFFSET; #endif /* Calculates stereo output MIXING anything already there: */ /* left_out[k] += out_left * rev->wet1 + out_right * rev->wet2; right_out[k] += out_right * rev->wet1 + out_left * rev->wet2; As wet1 is integrated in stereo coefficient wet 1 is now integrated in out_left and out_right we simplify previous relation by suppression of one multiply as this: left_out[k] += out_left + out_right * rev->wet2; right_out[k] += out_right + out_left * rev->wet2; */ left_out[k] += out_left + out_right * rev->wet2; right_out[k] += out_right + out_left * rev->wet2; } } fluidsynth-2.1.1/src/rvoice/fluid_rev.h000066400000000000000000000050561362231004000201070ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_REV_H #define _FLUID_REV_H #include "fluidsynth_priv.h" typedef struct _fluid_revmodel_t fluid_revmodel_t; /** Flags for fluid_revmodel_set() */ typedef enum { FLUID_REVMODEL_SET_ROOMSIZE = 1 << 0, FLUID_REVMODEL_SET_DAMPING = 1 << 1, FLUID_REVMODEL_SET_WIDTH = 1 << 2, FLUID_REVMODEL_SET_LEVEL = 1 << 3, /** Value for fluid_revmodel_set() which sets all reverb parameters. */ FLUID_REVMODEL_SET_ALL = FLUID_REVMODEL_SET_LEVEL | FLUID_REVMODEL_SET_WIDTH | FLUID_REVMODEL_SET_DAMPING | FLUID_REVMODEL_SET_ROOMSIZE, } fluid_revmodel_set_t; /* * reverb preset */ typedef struct _fluid_revmodel_presets_t { const char *name; fluid_real_t roomsize; fluid_real_t damp; fluid_real_t width; fluid_real_t level; } fluid_revmodel_presets_t; /* * reverb */ fluid_revmodel_t *new_fluid_revmodel(fluid_real_t sample_rate); void delete_fluid_revmodel(fluid_revmodel_t *rev); void fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); void fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); void fluid_revmodel_reset(fluid_revmodel_t *rev); void fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, fluid_real_t damping, fluid_real_t width, fluid_real_t level); int fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate); #endif /* _FLUID_REV_H */ fluidsynth-2.1.1/src/rvoice/fluid_rvoice.c000066400000000000000000000770351362231004000206030ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_rvoice.h" #include "fluid_conv.h" #include "fluid_sys.h" static void fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks); /** * @return -1 if voice is quiet, 0 if voice has finished, 1 otherwise */ static FLUID_INLINE int fluid_rvoice_calc_amp(fluid_rvoice_t *voice) { fluid_real_t target_amp; /* target amplitude */ if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVDELAY) { return -1; /* The volume amplitude is in hold phase. No sound is produced. */ } if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVATTACK) { /* the envelope is in the attack section: ramp linearly to max value. * A positive modlfo_to_vol should increase volume (negative attenuation). */ target_amp = fluid_cb2amp(voice->dsp.attenuation) * fluid_cb2amp(fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol) * fluid_adsr_env_get_val(&voice->envlfo.volenv); } else { fluid_real_t amplitude_that_reaches_noise_floor; fluid_real_t amp_max; target_amp = fluid_cb2amp(voice->dsp.attenuation) * fluid_cb2amp(FLUID_PEAK_ATTENUATION * (1.0f - fluid_adsr_env_get_val(&voice->envlfo.volenv)) + fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol); /* We turn off a voice, if the volume has dropped low enough. */ /* A voice can be turned off, when an estimate for the volume * (upper bound) falls below that volume, that will drop the * sample below the noise floor. */ /* If the loop amplitude is known, we can use it if the voice loop is within * the sample loop */ /* Is the playing pointer already in the loop? */ if(voice->dsp.has_looped) { amplitude_that_reaches_noise_floor = voice->dsp.amplitude_that_reaches_noise_floor_loop; } else { amplitude_that_reaches_noise_floor = voice->dsp.amplitude_that_reaches_noise_floor_nonloop; } /* voice->attenuation_min is a lower boundary for the attenuation * now and in the future (possibly 0 in the worst case). Now the * amplitude of sample and volenv cannot exceed amp_max (since * volenv_val can only drop): */ amp_max = fluid_cb2amp(voice->dsp.min_attenuation_cB) * fluid_adsr_env_get_val(&voice->envlfo.volenv); /* And if amp_max is already smaller than the known amplitude, * which will attenuate the sample below the noise floor, then we * can safely turn off the voice. Duh. */ if(amp_max < amplitude_that_reaches_noise_floor) { return 0; } } /* Volume increment to go from voice->amp to target_amp in FLUID_BUFSIZE steps */ voice->dsp.amp_incr = (target_amp - voice->dsp.amp) / FLUID_BUFSIZE; fluid_check_fpe("voice_write amplitude calculation"); /* no volume and not changing? - No need to process */ if((voice->dsp.amp == 0.0f) && (voice->dsp.amp_incr == 0.0f)) { return -1; } return 1; } /* these should be the absolute minimum that FluidSynth can deal with */ #define FLUID_MIN_LOOP_SIZE 2 #define FLUID_MIN_LOOP_PAD 0 #define FLUID_SAMPLESANITY_CHECK (1 << 0) #define FLUID_SAMPLESANITY_STARTUP (1 << 1) /* Purpose: * * Make sure, that sample start / end point and loop points are in * proper order. When starting up, calculate the initial phase. * TODO: Investigate whether this can be moved from rvoice to voice. */ static void fluid_rvoice_check_sample_sanity(fluid_rvoice_t *voice) { int min_index_nonloop = (int) voice->dsp.sample->start; int max_index_nonloop = (int) voice->dsp.sample->end; /* make sure we have enough samples surrounding the loop */ int min_index_loop = (int) voice->dsp.sample->start + FLUID_MIN_LOOP_PAD; int max_index_loop = (int) voice->dsp.sample->end - FLUID_MIN_LOOP_PAD + 1; /* 'end' is last valid sample, loopend can be + 1 */ fluid_check_fpe("voice_check_sample_sanity start"); #if 0 printf("Sample from %i to %i\n", voice->dsp.sample->start, voice->dsp.sample->end); printf("Sample loop from %i %i\n", voice->dsp.sample->loopstart, voice->dsp.sample->loopend); printf("Playback from %i to %i\n", voice->dsp.start, voice->dsp.end); printf("Playback loop from %i to %i\n", voice->dsp.loopstart, voice->dsp.loopend); #endif /* Keep the start point within the sample data */ if(voice->dsp.start < min_index_nonloop) { voice->dsp.start = min_index_nonloop; } else if(voice->dsp.start > max_index_nonloop) { voice->dsp.start = max_index_nonloop; } /* Keep the end point within the sample data */ if(voice->dsp.end < min_index_nonloop) { voice->dsp.end = min_index_nonloop; } else if(voice->dsp.end > max_index_nonloop) { voice->dsp.end = max_index_nonloop; } /* Keep start and end point in the right order */ if(voice->dsp.start > voice->dsp.end) { int temp = voice->dsp.start; voice->dsp.start = voice->dsp.end; voice->dsp.end = temp; /*FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Changing order of start / end points!"); */ } /* Zero length? */ if(voice->dsp.start == voice->dsp.end) { fluid_rvoice_voiceoff(voice, NULL); return; } if((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)) { /* Keep the loop start point within the sample data */ if(voice->dsp.loopstart < min_index_loop) { voice->dsp.loopstart = min_index_loop; } else if(voice->dsp.loopstart > max_index_loop) { voice->dsp.loopstart = max_index_loop; } /* Keep the loop end point within the sample data */ if(voice->dsp.loopend < min_index_loop) { voice->dsp.loopend = min_index_loop; } else if(voice->dsp.loopend > max_index_loop) { voice->dsp.loopend = max_index_loop; } /* Keep loop start and end point in the right order */ if(voice->dsp.loopstart > voice->dsp.loopend) { int temp = voice->dsp.loopstart; voice->dsp.loopstart = voice->dsp.loopend; voice->dsp.loopend = temp; /*FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Changing order of loop points!"); */ } /* Loop too short? Then don't loop. */ if(voice->dsp.loopend < voice->dsp.loopstart + FLUID_MIN_LOOP_SIZE) { voice->dsp.samplemode = FLUID_UNLOOPED; } /* The loop points may have changed. Obtain a new estimate for the loop volume. */ /* Is the voice loop within the sample loop? */ if((int)voice->dsp.loopstart >= (int)voice->dsp.sample->loopstart && (int)voice->dsp.loopend <= (int)voice->dsp.sample->loopend) { /* Is there a valid peak amplitude available for the loop, and can we use it? */ if(voice->dsp.sample->amplitude_that_reaches_noise_floor_is_valid && voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE) { voice->dsp.amplitude_that_reaches_noise_floor_loop = voice->dsp.sample->amplitude_that_reaches_noise_floor / voice->dsp.synth_gain; } else { /* Worst case */ voice->dsp.amplitude_that_reaches_noise_floor_loop = voice->dsp.amplitude_that_reaches_noise_floor_nonloop; }; }; } /* if sample mode is looped */ /* Run startup specific code (only once, when the voice is started) */ if(voice->dsp.check_sample_sanity_flag & FLUID_SAMPLESANITY_STARTUP) { if(max_index_loop - min_index_loop < FLUID_MIN_LOOP_SIZE) { if((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)) { voice->dsp.samplemode = FLUID_UNLOOPED; } } /* Set the initial phase of the voice (using the result from the start offset modulators). */ fluid_phase_set_int(voice->dsp.phase, voice->dsp.start); } /* if startup */ /* Is this voice run in loop mode, or does it run straight to the end of the waveform data? */ if(((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) && (fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE)) || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)) { /* Yes, it will loop as soon as it reaches the loop point. In * this case we must prevent, that the playback pointer (phase) * happens to end up beyond the 2nd loop point, because the * point has moved. The DSP algorithm is unable to cope with * that situation. So if the phase is beyond the 2nd loop * point, set it to the start of the loop. No way to avoid some * noise here. Note: If the sample pointer ends up -before the * first loop point- instead, then the DSP loop will just play * the sample, enter the loop and proceed as expected => no * actions required. */ int index_in_sample = fluid_phase_index(voice->dsp.phase); if(index_in_sample >= voice->dsp.loopend) { /* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Phase after 2nd loop point!"); */ fluid_phase_set_int(voice->dsp.phase, voice->dsp.loopstart); } } /* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Sample from %i to %i, loop from %i to %i", voice->dsp.start, voice->dsp.end, voice->dsp.loopstart, voice->dsp.loopend); */ /* Sample sanity has been assured. Don't check again, until some sample parameter is changed by modulation. */ voice->dsp.check_sample_sanity_flag = 0; #if 0 printf("Sane? playback loop from %i to %i\n", voice->dsp.loopstart, voice->dsp.loopend); #endif fluid_check_fpe("voice_check_sample_sanity"); } /** * Synthesize a voice to a buffer. * * @param voice rvoice to synthesize * @param dsp_buf Audio buffer to synthesize to (#FLUID_BUFSIZE in length) * @return Count of samples written to dsp_buf. (-1 means voice is currently * quiet, 0 .. #FLUID_BUFSIZE-1 means voice finished.) * * Panning, reverb and chorus are processed separately. The dsp interpolation * routine is in (fluid_rvoice_dsp.c). */ int fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) { int ticks = voice->envlfo.ticks; int count, is_looping; fluid_real_t modenv_val; /******************* sample sanity check **********/ if(!voice->dsp.sample) { return 0; } if(voice->dsp.check_sample_sanity_flag) { fluid_rvoice_check_sample_sanity(voice); } /******************* noteoff check ****************/ if(voice->envlfo.noteoff_ticks != 0 && voice->envlfo.ticks >= voice->envlfo.noteoff_ticks) { fluid_rvoice_noteoff_LOCAL(voice, 0); } voice->envlfo.ticks += FLUID_BUFSIZE; /******************* vol env **********************/ fluid_adsr_env_calc(&voice->envlfo.volenv, 1); fluid_check_fpe("voice_write vol env"); if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVFINISHED) { return 0; } /******************* mod env **********************/ fluid_adsr_env_calc(&voice->envlfo.modenv, 0); fluid_check_fpe("voice_write mod env"); /******************* lfo **********************/ fluid_lfo_calc(&voice->envlfo.modlfo, ticks); fluid_check_fpe("voice_write mod LFO"); fluid_lfo_calc(&voice->envlfo.viblfo, ticks); fluid_check_fpe("voice_write vib LFO"); /******************* amplitude **********************/ count = fluid_rvoice_calc_amp(voice); if(count <= 0) { return count; /* return -1 if voice is quiet, 0 if voice has finished */ } /******************* phase **********************/ /* SF2.04 section 8.1.2 #26: * attack of modEnv is convex ?!? */ modenv_val = (fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) ? fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)) : fluid_adsr_env_get_val(&voice->envlfo.modenv); /* Calculate the number of samples, that the DSP loop advances * through the original waveform with each step in the output * buffer. It is the ratio between the frequencies of original * waveform and output waveform.*/ voice->dsp.phase_incr = fluid_ct2hz_real(voice->dsp.pitch + voice->dsp.pitchoffset + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch + modenv_val * voice->envlfo.modenv_to_pitch) / voice->dsp.root_pitch_hz; /******************* portamento ****************/ /* pitchoffset is updated if enabled. Pitchoffset will be added to dsp pitch at next phase calculation time */ /* In most cases portamento will be disabled. Thus first verify that portamento is * enabled before updating pitchoffset and before disabling portamento when necessary, * in order to keep the performance loss at minimum. * If the algorithm would first update pitchoffset and then verify if portamento * needs to be disabled, there would be a significant performance drop on a x87 FPU */ if(voice->dsp.pitchinc > 0.0f) { /* portamento is enabled, so update pitchoffset */ voice->dsp.pitchoffset += voice->dsp.pitchinc; /* when pitchoffset reaches 0.0f, portamento is disabled */ if(voice->dsp.pitchoffset > 0.0f) { voice->dsp.pitchoffset = voice->dsp.pitchinc = 0.0f; } } else if(voice->dsp.pitchinc < 0.0f) { /* portamento is enabled, so update pitchoffset */ voice->dsp.pitchoffset += voice->dsp.pitchinc; /* when pitchoffset reaches 0.0f, portamento is disabled */ if(voice->dsp.pitchoffset < 0.0f) { voice->dsp.pitchoffset = voice->dsp.pitchinc = 0.0f; } } fluid_check_fpe("voice_write phase calculation"); /* if phase_incr is not advancing, set it to the minimum fraction value (prevent stuckage) */ if(voice->dsp.phase_incr == 0) { voice->dsp.phase_incr = 1; } /* voice is currently looping? */ is_looping = voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE || (voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE && fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE); /*********************** run the dsp chain ************************ * The sample is mixed with the output buffer. * The buffer has to be filled from 0 to FLUID_BUFSIZE-1. * Depending on the position in the loop and the loop size, this * may require several runs. */ switch(voice->dsp.interp_method) { case FLUID_INTERP_NONE: count = fluid_rvoice_dsp_interpolate_none(&voice->dsp, dsp_buf, is_looping); break; case FLUID_INTERP_LINEAR: count = fluid_rvoice_dsp_interpolate_linear(&voice->dsp, dsp_buf, is_looping); break; case FLUID_INTERP_4THORDER: default: count = fluid_rvoice_dsp_interpolate_4th_order(&voice->dsp, dsp_buf, is_looping); break; case FLUID_INTERP_7THORDER: count = fluid_rvoice_dsp_interpolate_7th_order(&voice->dsp, dsp_buf, is_looping); break; } fluid_check_fpe("voice_write interpolation"); if(count == 0) { return count; } /*************** resonant filter ******************/ fluid_iir_filter_calc(&voice->resonant_filter, voice->dsp.output_rate, fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + modenv_val * voice->envlfo.modenv_to_fc); fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); /* additional custom filter - only uses the fixed modulator, no lfos... */ fluid_iir_filter_calc(&voice->resonant_custom_filter, voice->dsp.output_rate, 0); fluid_iir_filter_apply(&voice->resonant_custom_filter, dsp_buf, count); return count; } /** * Initialize buffers up to (and including) bufnum */ static int fluid_rvoice_buffers_check_bufnum(fluid_rvoice_buffers_t *buffers, unsigned int bufnum) { unsigned int i; if(bufnum < buffers->count) { return FLUID_OK; } if(bufnum >= FLUID_RVOICE_MAX_BUFS) { return FLUID_FAILED; } for(i = buffers->count; i <= bufnum; i++) { buffers->bufs[i].amp = 0.0f; } buffers->count = bufnum + 1; return FLUID_OK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_amp) { fluid_rvoice_buffers_t *buffers = obj; unsigned int bufnum = param[0].i; fluid_real_t value = param[1].real; if(fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK) { return; } buffers->bufs[bufnum].amp = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_mapping) { fluid_rvoice_buffers_t *buffers = obj; unsigned int bufnum = param[0].i; int mapping = param[1].i; if(fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK) { return; } buffers->bufs[bufnum].mapping = mapping; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_reset) { fluid_rvoice_t *voice = obj; voice->dsp.has_looped = 0; voice->envlfo.ticks = 0; voice->envlfo.noteoff_ticks = 0; voice->dsp.amp = 0.0f; /* The last value of the volume envelope, used to calculate the volume increment during processing */ /* legato initialization */ voice->dsp.pitchoffset = 0.0; /* portamento initialization */ voice->dsp.pitchinc = 0.0; /* mod env initialization*/ fluid_adsr_env_reset(&voice->envlfo.modenv); /* vol env initialization */ fluid_adsr_env_reset(&voice->envlfo.volenv); /* Fixme: Retrieve from any other existing voice on this channel to keep LFOs in unison? */ fluid_lfo_reset(&voice->envlfo.viblfo); fluid_lfo_reset(&voice->envlfo.modlfo); /* Clear sample history in filter */ fluid_iir_filter_reset(&voice->resonant_filter); fluid_iir_filter_reset(&voice->resonant_custom_filter); /* Force setting of the phase at the first DSP loop run * This cannot be done earlier, because it depends on modulators. [DH] Is that comment really true? */ voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_noteoff) { fluid_rvoice_t *rvoice = obj; unsigned int min_ticks = param[0].i; fluid_rvoice_noteoff_LOCAL(rvoice, min_ticks); } static void fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks) { if(min_ticks > voice->envlfo.ticks) { /* Delay noteoff */ voice->envlfo.noteoff_ticks = min_ticks; return; } voice->envlfo.noteoff_ticks = 0; if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVATTACK) { /* A voice is turned off during the attack section of the volume * envelope. The attack section ramps up linearly with * amplitude. The other sections use logarithmic scaling. Calculate new * volenv_val to achieve equivalent amplitude during the release phase * for seamless volume transition. */ if(fluid_adsr_env_get_val(&voice->envlfo.volenv) > 0) { fluid_real_t lfo = fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol; fluid_real_t amp = fluid_adsr_env_get_val(&voice->envlfo.volenv) * fluid_cb2amp(lfo); fluid_real_t env_value = - (((-200.f / FLUID_M_LN10) * FLUID_LOGF(amp) - lfo) / FLUID_PEAK_ATTENUATION - 1); fluid_clip(env_value, 0.0f, 1.0f); fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value); } } if(fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) { /* A voice is turned off during the attack section of the modulation * envelope. The attack section use convex scaling with pitch and filter * frequency cutoff (see fluid_rvoice_write(): modenv_val = fluid_convex(127 * modenv.val) * The other sections use linear scaling: modenv_val = modenv.val * * Calculate new modenv.val to achieve equivalent modenv_val during the release phase * for seamless pitch and filter frequency cutoff transition. */ if(fluid_adsr_env_get_val(&voice->envlfo.modenv) > 0) { fluid_real_t env_value = fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)); fluid_clip(env_value, 0.0, 1.0); fluid_adsr_env_set_val(&voice->envlfo.modenv, env_value); } } fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVRELEASE); fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVRELEASE); } /** * skips to Attack section * * Updates vol and attack data * Correction on volume val to achieve equivalent amplitude at noteOn legato * * @param voice the synthesis voice to be updated */ static FLUID_INLINE void fluid_rvoice_local_retrigger_attack(fluid_rvoice_t *voice) { /* skips to Attack section */ /* Once in Attack section, current count must be reset, to be sure that the section will be not be prematurely finished. */ fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVATTACK); { /* Correction on volume val to achieve equivalent amplitude at noteOn legato */ fluid_env_data_t *env_data; fluid_real_t peak = fluid_cb2amp(voice->dsp.attenuation); fluid_real_t prev_peak = fluid_cb2amp(voice->dsp.prev_attenuation); voice->envlfo.volenv.val = (voice->envlfo.volenv.val * prev_peak) / peak; /* Correction on slope direction for Attack section */ env_data = &voice->envlfo.volenv.data[FLUID_VOICE_ENVATTACK]; if(voice->envlfo.volenv.val <= 1.0f) { /* slope attack for legato note needs to be positive from val up to 1 */ env_data->increment = 1.0f / env_data->count; env_data->min = -1.0f; env_data->max = 1.0f; } else { /* slope attack for legato note needs to be negative: from val down to 1 */ env_data->increment = -voice->envlfo.volenv.val / env_data->count; env_data->min = 1.0f; env_data->max = voice->envlfo.volenv.val; } } } /** * Used by legato Mode : multi_retrigger * see fluid_synth_noteon_mono_legato_multi_retrigger() * @param voice the synthesis voice to be updated */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack) { fluid_rvoice_t *voice = obj; int section; /* volume or modulation section */ /*------------------------------------------------------------------------- Section skip for volume envelope --------------------------------------------------------------------------*/ section = fluid_adsr_env_get_section(&voice->envlfo.volenv); if(section >= FLUID_VOICE_ENVHOLD) { /* DECAY, SUSTAIN,RELEASE section use logarithmic scaling. Calculates new volenv_val to achieve equivalent amplitude during the attack phase for seamless volume transition. */ fluid_real_t amp_cb, env_value; amp_cb = FLUID_PEAK_ATTENUATION * (1.0f - fluid_adsr_env_get_val(&voice->envlfo.volenv)); env_value = fluid_cb2amp(amp_cb); /* a bit of optimization */ fluid_clip(env_value, 0.0, 1.0); fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value); /* next, skips to Attack section */ } /* skips to Attack section from any section */ /* Update vol and attack data */ fluid_rvoice_local_retrigger_attack(voice); /*------------------------------------------------------------------------- Section skip for modulation envelope --------------------------------------------------------------------------*/ section = fluid_adsr_env_get_section(&voice->envlfo.modenv); if(section >= FLUID_VOICE_ENVHOLD) { /* DECAY, SUSTAIN,RELEASE section use linear scaling. Since v 2.1 , as recommended by soundfont 2.01/2.4 spec, ATTACK section uses convex shape (see fluid_rvoice_write() - fluid_convex()). Calculate new modenv value (new_value) for seamless attack transition. Here we need the inverse of fluid_convex() function defined as: new_value = pow(10, (1 - current_val) . FLUID_PEAK_ATTENUATION / -200 . 2.0) For performance reason we use fluid_cb2amp(Val) = pow(10, val/-200) with val = (1 - current_val) . FLUID_PEAK_ATTENUATION / 2.0 */ fluid_real_t new_value; /* new modenv value */ new_value = fluid_cb2amp((1.0f - fluid_adsr_env_get_val(&voice->envlfo.modenv)) * FLUID_PEAK_ATTENUATION / 2.0); fluid_clip(new_value, 0.0, 1.0); fluid_adsr_env_set_val(&voice->envlfo.modenv, new_value); } /* Skips from any section to ATTACK section */ fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVATTACK); } /** * sets the portamento dsp parameters: dsp.pitchoffset, dsp.pitchinc * @param voice rvoice to set portamento. * @param countinc increment count number. * @param pitchoffset pitch offset to apply to voice dsp.pitch. * * Notes: * 1) To get continuous portamento between consecutive noteOn (n1,n2,n3...), * pitchoffset is accumulated in current dsp pitchoffset. * 2) And to get constant portamento duration, dsp pitch increment is updated. */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_portamento) { fluid_rvoice_t *voice = obj; unsigned int countinc = param[0].i; fluid_real_t pitchoffset = param[1].real; if(countinc) { voice->dsp.pitchoffset += pitchoffset; voice->dsp.pitchinc = - voice->dsp.pitchoffset / countinc; } /* Then during the voice processing (in fluid_rvoice_write()), dsp.pitchoffset will be incremented by dsp pitchinc. */ } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_output_rate) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->dsp.output_rate = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_interp_method) { fluid_rvoice_t *voice = obj; int value = param[0].i; voice->dsp.interp_method = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_root_pitch_hz) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->dsp.root_pitch_hz = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_pitch) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->dsp.pitch = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_attenuation) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->dsp.prev_attenuation = voice->dsp.attenuation; voice->dsp.attenuation = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_min_attenuation_cB) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->dsp.min_attenuation_cB = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_viblfo_to_pitch) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->envlfo.viblfo_to_pitch = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_pitch) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->envlfo.modlfo_to_pitch = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_vol) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->envlfo.modlfo_to_vol = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_fc) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->envlfo.modlfo_to_fc = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_fc) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->envlfo.modenv_to_fc = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_pitch) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->envlfo.modenv_to_pitch = value; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_synth_gain) { fluid_rvoice_t *voice = obj; fluid_real_t value = param[0].real; voice->dsp.synth_gain = value; /* For a looped sample, this value will be overwritten as soon as the * loop parameters are initialized (they may depend on modulators). * This value can be kept, it is a worst-case estimate. */ voice->dsp.amplitude_that_reaches_noise_floor_nonloop = FLUID_NOISE_FLOOR / value; voice->dsp.amplitude_that_reaches_noise_floor_loop = FLUID_NOISE_FLOOR / value; voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_start) { fluid_rvoice_t *voice = obj; int value = param[0].i; voice->dsp.start = value; voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_end) { fluid_rvoice_t *voice = obj; int value = param[0].i; voice->dsp.end = value; voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopstart) { fluid_rvoice_t *voice = obj; int value = param[0].i; voice->dsp.loopstart = value; voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopend) { fluid_rvoice_t *voice = obj; int value = param[0].i; voice->dsp.loopend = value; voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_samplemode) { fluid_rvoice_t *voice = obj; enum fluid_loop value = param[0].i; voice->dsp.samplemode = value; voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_sample) { fluid_rvoice_t *voice = obj; fluid_sample_t *value = param[0].ptr; voice->dsp.sample = value; if(value) { voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP; } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_voiceoff) { fluid_rvoice_t *voice = obj; fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVFINISHED); fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVFINISHED); } fluidsynth-2.1.1/src/rvoice/fluid_rvoice.h000066400000000000000000000175671362231004000206140ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_RVOICE_H #define _FLUID_RVOICE_H #include "fluidsynth_priv.h" #include "fluid_iir_filter.h" #include "fluid_adsr_env.h" #include "fluid_lfo.h" #include "fluid_phase.h" #include "fluid_sfont.h" typedef struct _fluid_rvoice_envlfo_t fluid_rvoice_envlfo_t; typedef struct _fluid_rvoice_dsp_t fluid_rvoice_dsp_t; typedef struct _fluid_rvoice_buffers_t fluid_rvoice_buffers_t; typedef struct _fluid_rvoice_t fluid_rvoice_t; /* Smallest amplitude that can be perceived (full scale is +/- 0.5) * 16 bits => 96+4=100 dB dynamic range => 0.00001 * 24 bits => 144-4 = 140 dB dynamic range => 1.e-7 * 1.e-7 * 2 == 2.e-7 :) */ #define FLUID_NOISE_FLOOR ((fluid_real_t)2.e-7) enum fluid_loop { FLUID_UNLOOPED = 0, FLUID_LOOP_DURING_RELEASE = 1, FLUID_NOTUSED = 2, FLUID_LOOP_UNTIL_RELEASE = 3 }; /* * rvoice ticks-based parameters * These parameters must be updated even if the voice is currently quiet. */ struct _fluid_rvoice_envlfo_t { /* Note-off minimum length */ unsigned int ticks; unsigned int noteoff_ticks; /* vol env */ fluid_adsr_env_t volenv; /* mod env */ fluid_adsr_env_t modenv; fluid_real_t modenv_to_fc; fluid_real_t modenv_to_pitch; /* mod lfo */ fluid_lfo_t modlfo; fluid_real_t modlfo_to_fc; fluid_real_t modlfo_to_pitch; fluid_real_t modlfo_to_vol; /* vib lfo */ fluid_lfo_t viblfo; fluid_real_t viblfo_to_pitch; }; /* * rvoice parameters needed for dsp interpolation */ struct _fluid_rvoice_dsp_t { /* interpolation method, as in fluid_interp in fluidsynth.h */ enum fluid_interp interp_method; enum fluid_loop samplemode; /* Flag that is set as soon as the first loop is completed. */ char has_looped; /* Flag that initiates, that sample-related parameters have to be checked. */ char check_sample_sanity_flag; fluid_sample_t *sample; /* sample and loop start and end points (offset in sample memory). */ int start; int end; int loopstart; int loopend; /* Note: first point following the loop (superimposed on loopstart) */ /* Stuff needed for portamento calculations */ fluid_real_t pitchoffset; /* the portamento range in midicents */ fluid_real_t pitchinc; /* the portamento increment in midicents */ /* Stuff needed for phase calculations */ fluid_real_t pitch; /* the pitch in midicents */ fluid_real_t root_pitch_hz; fluid_real_t output_rate; /* Stuff needed for amplitude calculations */ fluid_real_t attenuation; /* the attenuation in centibels */ fluid_real_t prev_attenuation; /* the previous attenuation in centibels used by fluid_rvoice_multi_retrigger_attack() */ fluid_real_t min_attenuation_cB; /* Estimate on the smallest possible attenuation * during the lifetime of the voice */ fluid_real_t amplitude_that_reaches_noise_floor_nonloop; fluid_real_t amplitude_that_reaches_noise_floor_loop; fluid_real_t synth_gain; /* master gain */ /* Dynamic input to the interpolator below */ fluid_real_t amp; /* current linear amplitude */ fluid_real_t amp_incr; /* amplitude increment value for the next FLUID_BUFSIZE samples */ fluid_phase_t phase; /* the phase (current sample offset) of the sample wave */ fluid_real_t phase_incr; /* the phase increment for the next FLUID_BUFSIZE samples */ }; /* Currently left, right, reverb, chorus. To be changed if we ever add surround positioning, or stereo reverb/chorus */ #define FLUID_RVOICE_MAX_BUFS (4) /* * rvoice mixer-related parameters */ struct _fluid_rvoice_buffers_t { unsigned int count; /* Number of records in "bufs" */ struct { fluid_real_t amp; int mapping; /* Mapping to mixdown buffer index */ } bufs[FLUID_RVOICE_MAX_BUFS]; }; /* * Hard real-time parameters needed to synthesize a voice */ struct _fluid_rvoice_t { fluid_rvoice_envlfo_t envlfo; fluid_rvoice_dsp_t dsp; fluid_iir_filter_t resonant_filter; /* IIR resonant dsp filter */ fluid_iir_filter_t resonant_custom_filter; /* optional custom/general-purpose IIR resonant filter */ fluid_rvoice_buffers_t buffers; }; int fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_amp); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_mapping); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_noteoff); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_voiceoff); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_reset); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_portamento); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_output_rate); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_interp_method); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_root_pitch_hz); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_pitch); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_attenuation); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_min_attenuation_cB); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_viblfo_to_pitch); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_pitch); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_vol); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_fc); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_fc); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_pitch); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_synth_gain); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_start); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_end); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopstart); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopend); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_samplemode); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_sample); /* defined in fluid_rvoice_dsp.c */ void fluid_rvoice_dsp_config(void); int fluid_rvoice_dsp_interpolate_none(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); int fluid_rvoice_dsp_interpolate_linear(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); int fluid_rvoice_dsp_interpolate_4th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); int fluid_rvoice_dsp_interpolate_7th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); /* * Combines the most significant 16 bit part of a sample with a potentially present * least sig. 8 bit part in order to create a 24 bit sample. */ static FLUID_INLINE int32_t fluid_rvoice_get_sample(const short int *dsp_msb, const char *dsp_lsb, unsigned int idx) { /* cast sample to unsigned type, so we can safely shift and bitwise or * without relying on undefined behaviour (should never happen anyway ofc...) */ uint32_t msb = (uint32_t)dsp_msb[idx]; uint8_t lsb = 0U; /* most soundfonts have 16 bit samples, assume that it's unlikely we * experience 24 bit samples here */ if(FLUID_UNLIKELY(dsp_lsb != NULL)) { lsb = (uint8_t)dsp_lsb[idx]; } return (int32_t)((msb << 8) | lsb); } #endif fluidsynth-2.1.1/src/rvoice/fluid_rvoice_dsp.c000066400000000000000000000646761362231004000214600ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sys.h" #include "fluid_phase.h" #include "fluid_rvoice.h" #include "fluid_rvoice_dsp_tables.c" /* Purpose: * * Interpolates audio data (obtains values between the samples of the original * waveform data). * * Variables loaded from the voice structure (assigned in fluid_rvoice_write()): * - dsp_data: Pointer to the original waveform data * - dsp_phase: The position in the original waveform data. * This has an integer and a fractional part (between samples). * - dsp_phase_incr: For each output sample, the position in the original * waveform advances by dsp_phase_incr. This also has an integer * part and a fractional part. * If a sample is played at root pitch (no pitch change), * dsp_phase_incr is integer=1 and fractional=0. * - dsp_amp: The current amplitude envelope value. * - dsp_amp_incr: The changing rate of the amplitude envelope. * * A couple of variables are used internally, their results are discarded: * - dsp_i: Index through the output buffer * - dsp_buf: Output buffer of floating point values (FLUID_BUFSIZE in length) */ /* Interpolation (find a value between two samples of the original waveform) */ static FLUID_INLINE fluid_real_t fluid_rvoice_get_float_sample(const short int *dsp_msb, const char *dsp_lsb, unsigned int idx) { int32_t sample = fluid_rvoice_get_sample(dsp_msb, dsp_lsb, idx); return (fluid_real_t)sample; } /* No interpolation. Just take the sample, which is closest to * the playback pointer. Questionable quality, but very * efficient. */ int fluid_rvoice_dsp_interpolate_none(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) { fluid_phase_t dsp_phase = voice->phase; fluid_phase_t dsp_phase_incr; short int *dsp_data = voice->sample->data; char *dsp_data24 = voice->sample->data24; fluid_real_t dsp_amp = voice->amp; fluid_real_t dsp_amp_incr = voice->amp_incr; unsigned int dsp_i = 0; unsigned int dsp_phase_index; unsigned int end_index; /* Convert playback "speed" floating point value to phase index/fract */ fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); end_index = looping ? voice->loopend - 1 : voice->end; while(1) { dsp_phase_index = fluid_phase_index_round(dsp_phase); /* round to nearest point */ /* interpolate sequence of sample points */ for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) { dsp_buf[dsp_i] = dsp_amp * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index_round(dsp_phase); /* round to nearest point */ dsp_amp += dsp_amp_incr; } /* break out if not looping (buffer may not be full) */ if(!looping) { break; } /* go back to loop start */ if(dsp_phase_index > end_index) { fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); voice->has_looped = 1; } /* break out if filled buffer */ if(dsp_i >= FLUID_BUFSIZE) { break; } } voice->phase = dsp_phase; voice->amp = dsp_amp; return (dsp_i); } /* Straight line interpolation. * Returns number of samples processed (usually FLUID_BUFSIZE but could be * smaller if end of sample occurs). */ int fluid_rvoice_dsp_interpolate_linear(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) { fluid_phase_t dsp_phase = voice->phase; fluid_phase_t dsp_phase_incr; short int *dsp_data = voice->sample->data; char *dsp_data24 = voice->sample->data24; fluid_real_t dsp_amp = voice->amp; fluid_real_t dsp_amp_incr = voice->amp_incr; unsigned int dsp_i = 0; unsigned int dsp_phase_index; unsigned int end_index; fluid_real_t point; const fluid_real_t *FLUID_RESTRICT coeffs; /* Convert playback "speed" floating point value to phase index/fract */ fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); /* last index before 2nd interpolation point must be specially handled */ end_index = (looping ? voice->loopend - 1 : voice->end) - 1; /* 2nd interpolation point to use at end of loop or sample */ if(looping) { point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart); /* loop start */ } else { point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->end); /* duplicate end for samples no longer looping */ } while(1) { dsp_phase_index = fluid_phase_index(dsp_phase); /* interpolate the sequence of sample points */ for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) { coeffs = interp_coeff_linear[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } /* break out if buffer filled */ if(dsp_i >= FLUID_BUFSIZE) { break; } end_index++; /* we're now interpolating the last point */ /* interpolate within last point */ for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = interp_coeff_linear[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[1] * point); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; /* increment amplitude */ } if(!looping) { break; /* break out if not looping (end of sample) */ } /* go back to loop start (if past */ if(dsp_phase_index > end_index) { fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); voice->has_looped = 1; } /* break out if filled buffer */ if(dsp_i >= FLUID_BUFSIZE) { break; } end_index--; /* set end back to second to last sample point */ } voice->phase = dsp_phase; voice->amp = dsp_amp; return (dsp_i); } /* 4th order (cubic) interpolation. * Returns number of samples processed (usually FLUID_BUFSIZE but could be * smaller if end of sample occurs). */ int fluid_rvoice_dsp_interpolate_4th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) { fluid_phase_t dsp_phase = voice->phase; fluid_phase_t dsp_phase_incr; short int *dsp_data = voice->sample->data; char *dsp_data24 = voice->sample->data24; fluid_real_t dsp_amp = voice->amp; fluid_real_t dsp_amp_incr = voice->amp_incr; unsigned int dsp_i = 0; unsigned int dsp_phase_index; unsigned int start_index, end_index; fluid_real_t start_point, end_point1, end_point2; const fluid_real_t *FLUID_RESTRICT coeffs; /* Convert playback "speed" floating point value to phase index/fract */ fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); /* last index before 4th interpolation point must be specially handled */ end_index = (looping ? voice->loopend - 1 : voice->end) - 2; if(voice->has_looped) /* set start_index and start point if looped or not */ { start_index = voice->loopstart; start_point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); /* last point in loop (wrap around) */ } else { start_index = voice->start; start_point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->start); /* just duplicate the point */ } /* get points off the end (loop start if looping, duplicate point if end) */ if(looping) { end_point1 = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart); end_point2 = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart + 1); } else { end_point1 = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->end); end_point2 = end_point1; } while(1) { dsp_phase_index = fluid_phase_index(dsp_phase); /* interpolate first sample point (start or loop start) if needed */ for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * start_point + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } /* interpolate the sequence of sample points */ for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) { coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } /* break out if buffer filled */ if(dsp_i >= FLUID_BUFSIZE) { break; } end_index++; /* we're now interpolating the 2nd to last point */ /* interpolate within 2nd to last point */ for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[3] * end_point1); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } end_index++; /* we're now interpolating the last point */ /* interpolate within the last point */ for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[2] * end_point1 + coeffs[3] * end_point2); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } if(!looping) { break; /* break out if not looping (end of sample) */ } /* go back to loop start */ if(dsp_phase_index > end_index) { fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); if(!voice->has_looped) { voice->has_looped = 1; start_index = voice->loopstart; start_point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); } } /* break out if filled buffer */ if(dsp_i >= FLUID_BUFSIZE) { break; } end_index -= 2; /* set end back to third to last sample point */ } voice->phase = dsp_phase; voice->amp = dsp_amp; return (dsp_i); } /* 7th order interpolation. * Returns number of samples processed (usually FLUID_BUFSIZE but could be * smaller if end of sample occurs). */ int fluid_rvoice_dsp_interpolate_7th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) { fluid_phase_t dsp_phase = voice->phase; fluid_phase_t dsp_phase_incr; short int *dsp_data = voice->sample->data; char *dsp_data24 = voice->sample->data24; fluid_real_t dsp_amp = voice->amp; fluid_real_t dsp_amp_incr = voice->amp_incr; unsigned int dsp_i = 0; unsigned int dsp_phase_index; unsigned int start_index, end_index; fluid_real_t start_points[3], end_points[3]; const fluid_real_t *FLUID_RESTRICT coeffs; /* Convert playback "speed" floating point value to phase index/fract */ fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); /* add 1/2 sample to dsp_phase since 7th order interpolation is centered on * the 4th sample point */ fluid_phase_incr(dsp_phase, (fluid_phase_t)0x80000000); /* last index before 7th interpolation point must be specially handled */ end_index = (looping ? voice->loopend - 1 : voice->end) - 3; if(voice->has_looped) /* set start_index and start point if looped or not */ { start_index = voice->loopstart; start_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); start_points[1] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 2); start_points[2] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 3); } else { start_index = voice->start; start_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->start); /* just duplicate the start point */ start_points[1] = start_points[0]; start_points[2] = start_points[0]; } /* get the 3 points off the end (loop start if looping, duplicate point if end) */ if(looping) { end_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart); end_points[1] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart + 1); end_points[2] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart + 2); } else { end_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->end); end_points[1] = end_points[0]; end_points[2] = end_points[0]; } while(1) { dsp_phase_index = fluid_phase_index(dsp_phase); /* interpolate first sample point (start or loop start) if needed */ for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * start_points[2] + coeffs[1] * start_points[1] + coeffs[2] * start_points[0] + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } start_index++; /* interpolate 2nd to first sample point (start or loop start) if needed */ for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * start_points[1] + coeffs[1] * start_points[0] + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } start_index++; /* interpolate 3rd to first sample point (start or loop start) if needed */ for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * start_points[0] + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } start_index -= 2; /* set back to original start index */ /* interpolate the sequence of sample points */ for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } /* break out if buffer filled */ if(dsp_i >= FLUID_BUFSIZE) { break; } end_index++; /* we're now interpolating the 3rd to last point */ /* interpolate within 3rd to last point */ for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + coeffs[6] * end_points[0]); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } end_index++; /* we're now interpolating the 2nd to last point */ /* interpolate within 2nd to last point */ for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + coeffs[5] * end_points[0] + coeffs[6] * end_points[1]); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } end_index++; /* we're now interpolating the last point */ /* interpolate within last point */ for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) { coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + coeffs[4] * end_points[0] + coeffs[5] * end_points[1] + coeffs[6] * end_points[2]); /* increment phase and amplitude */ fluid_phase_incr(dsp_phase, dsp_phase_incr); dsp_phase_index = fluid_phase_index(dsp_phase); dsp_amp += dsp_amp_incr; } if(!looping) { break; /* break out if not looping (end of sample) */ } /* go back to loop start */ if(dsp_phase_index > end_index) { fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); if(!voice->has_looped) { voice->has_looped = 1; start_index = voice->loopstart; start_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); start_points[1] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 2); start_points[2] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 3); } } /* break out if filled buffer */ if(dsp_i >= FLUID_BUFSIZE) { break; } end_index -= 3; /* set end back to 4th to last sample point */ } /* sub 1/2 sample from dsp_phase since 7th order interpolation is centered on * the 4th sample point (correct back to real value) */ fluid_phase_decr(dsp_phase, (fluid_phase_t)0x80000000); voice->phase = dsp_phase; voice->amp = dsp_amp; return (dsp_i); } fluidsynth-2.1.1/src/rvoice/fluid_rvoice_dsp_tables.h000066400000000000000000000002521362231004000227730ustar00rootroot00000000000000 #ifndef _FLUID_RVOICE_DSP_TABLES_H #define _FLUID_RVOICE_DSP_TABLES_H #define FLUID_INTERP_MAX 256 #define SINC_INTERP_ORDER 7 /* 7th order constant */ #endif fluidsynth-2.1.1/src/rvoice/fluid_rvoice_event.c000066400000000000000000000135651362231004000220020ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_rvoice_event.h" #include "fluid_rvoice.h" #include "fluid_rvoice_mixer.h" #include "fluid_iir_filter.h" #include "fluid_lfo.h" #include "fluid_adsr_env.h" static int fluid_rvoice_eventhandler_push_LOCAL(fluid_rvoice_eventhandler_t *handler, const fluid_rvoice_event_t *src_event); static FLUID_INLINE void fluid_rvoice_event_dispatch(fluid_rvoice_event_t *event) { event->method(event->object, event->param); } /** * In order to be able to push more than one event atomically, * use push for all events, then use flush to commit them to the * queue. If threadsafe is false, all events are processed immediately. */ int fluid_rvoice_eventhandler_push_int_real(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, int intparam, fluid_real_t realparam) { fluid_rvoice_event_t local_event; local_event.method = method; local_event.object = object; local_event.param[0].i = intparam; local_event.param[1].real = realparam; return fluid_rvoice_eventhandler_push_LOCAL(handler, &local_event); } int fluid_rvoice_eventhandler_push(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, fluid_rvoice_param_t param[MAX_EVENT_PARAMS]) { fluid_rvoice_event_t local_event; local_event.method = method; local_event.object = object; FLUID_MEMCPY(&local_event.param, param, sizeof(*param) * MAX_EVENT_PARAMS); return fluid_rvoice_eventhandler_push_LOCAL(handler, &local_event); } int fluid_rvoice_eventhandler_push_ptr(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, void *ptr) { fluid_rvoice_event_t local_event; local_event.method = method; local_event.object = object; local_event.param[0].ptr = ptr; return fluid_rvoice_eventhandler_push_LOCAL(handler, &local_event); } static int fluid_rvoice_eventhandler_push_LOCAL(fluid_rvoice_eventhandler_t *handler, const fluid_rvoice_event_t *src_event) { fluid_rvoice_event_t *event; int old_queue_stored = fluid_atomic_int_add(&handler->queue_stored, 1); event = fluid_ringbuffer_get_inptr(handler->queue, old_queue_stored); if(event == NULL) { fluid_atomic_int_add(&handler->queue_stored, -1); FLUID_LOG(FLUID_WARN, "Ringbuffer full, try increasing polyphony!"); return FLUID_FAILED; // Buffer full... } FLUID_MEMCPY(event, src_event, sizeof(*event)); return FLUID_OK; } void fluid_rvoice_eventhandler_finished_voice_callback(fluid_rvoice_eventhandler_t *eventhandler, fluid_rvoice_t *rvoice) { fluid_rvoice_t **vptr = fluid_ringbuffer_get_inptr(eventhandler->finished_voices, 0); if(vptr == NULL) { return; // Buffer full } *vptr = rvoice; fluid_ringbuffer_next_inptr(eventhandler->finished_voices, 1); } fluid_rvoice_eventhandler_t * new_fluid_rvoice_eventhandler(int queuesize, int finished_voices_size, int bufs, int fx_bufs, int fx_units, fluid_real_t sample_rate, int extra_threads, int prio) { fluid_rvoice_eventhandler_t *eventhandler = FLUID_NEW(fluid_rvoice_eventhandler_t); if(eventhandler == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } eventhandler->mixer = NULL; eventhandler->queue = NULL; eventhandler->finished_voices = NULL; fluid_atomic_int_set(&eventhandler->queue_stored, 0); eventhandler->finished_voices = new_fluid_ringbuffer(finished_voices_size, sizeof(fluid_rvoice_t *)); if(eventhandler->finished_voices == NULL) { goto error_recovery; } eventhandler->queue = new_fluid_ringbuffer(queuesize, sizeof(fluid_rvoice_event_t)); if(eventhandler->queue == NULL) { goto error_recovery; } eventhandler->mixer = new_fluid_rvoice_mixer(bufs, fx_bufs, fx_units, sample_rate, eventhandler, extra_threads, prio); if(eventhandler->mixer == NULL) { goto error_recovery; } return eventhandler; error_recovery: delete_fluid_rvoice_eventhandler(eventhandler); return NULL; } int fluid_rvoice_eventhandler_dispatch_count(fluid_rvoice_eventhandler_t *handler) { return fluid_ringbuffer_get_count(handler->queue); } /** * Call fluid_rvoice_event_dispatch for all events in queue * @return number of events dispatched */ int fluid_rvoice_eventhandler_dispatch_all(fluid_rvoice_eventhandler_t *handler) { fluid_rvoice_event_t *event; int result = 0; while(NULL != (event = fluid_ringbuffer_get_outptr(handler->queue))) { fluid_rvoice_event_dispatch(event); result++; fluid_ringbuffer_next_outptr(handler->queue); } return result; } void delete_fluid_rvoice_eventhandler(fluid_rvoice_eventhandler_t *handler) { fluid_return_if_fail(handler != NULL); delete_fluid_rvoice_mixer(handler->mixer); delete_fluid_ringbuffer(handler->queue); delete_fluid_ringbuffer(handler->finished_voices); FLUID_FREE(handler); } fluidsynth-2.1.1/src/rvoice/fluid_rvoice_event.h000066400000000000000000000075321362231004000220040ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_RVOICE_EVENT_H #define _FLUID_RVOICE_EVENT_H #include "fluidsynth_priv.h" #include "fluid_rvoice_mixer.h" #include "fluid_ringbuffer.h" typedef struct _fluid_rvoice_event_t fluid_rvoice_event_t; struct _fluid_rvoice_event_t { fluid_rvoice_function_t method; void *object; fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; }; /* * Bridge between the renderer thread and the midi state thread. * fluid_rvoice_eventhandler_fetch_all() can be called in parallel * with fluid_rvoice_eventhandler_push/flush() */ struct _fluid_rvoice_eventhandler_t { fluid_ringbuffer_t *queue; /**< List of fluid_rvoice_event_t */ fluid_atomic_int_t queue_stored; /**< Extras pushed but not flushed */ fluid_ringbuffer_t *finished_voices; /**< return queue from handler, list of fluid_rvoice_t* */ fluid_rvoice_mixer_t *mixer; }; fluid_rvoice_eventhandler_t *new_fluid_rvoice_eventhandler( int queuesize, int finished_voices_size, int bufs, int fx_bufs, int fx_units, fluid_real_t sample_rate, int, int); void delete_fluid_rvoice_eventhandler(fluid_rvoice_eventhandler_t *); int fluid_rvoice_eventhandler_dispatch_all(fluid_rvoice_eventhandler_t *); int fluid_rvoice_eventhandler_dispatch_count(fluid_rvoice_eventhandler_t *); void fluid_rvoice_eventhandler_finished_voice_callback(fluid_rvoice_eventhandler_t *eventhandler, fluid_rvoice_t *rvoice); static FLUID_INLINE void fluid_rvoice_eventhandler_flush(fluid_rvoice_eventhandler_t *handler) { int queue_stored = fluid_atomic_int_get(&handler->queue_stored); if(queue_stored > 0) { fluid_atomic_int_set(&handler->queue_stored, 0); fluid_ringbuffer_next_inptr(handler->queue, queue_stored); } } /** * @return next finished voice, or NULL if nothing in queue */ static FLUID_INLINE fluid_rvoice_t * fluid_rvoice_eventhandler_get_finished_voice(fluid_rvoice_eventhandler_t *handler) { void *result = fluid_ringbuffer_get_outptr(handler->finished_voices); if(result == NULL) { return NULL; } result = * (fluid_rvoice_t **) result; fluid_ringbuffer_next_outptr(handler->finished_voices); return result; } int fluid_rvoice_eventhandler_push_int_real(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, int intparam, fluid_real_t realparam); int fluid_rvoice_eventhandler_push_ptr(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, void *ptr); int fluid_rvoice_eventhandler_push(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, fluid_rvoice_param_t param[MAX_EVENT_PARAMS]); static FLUID_INLINE void fluid_rvoice_eventhandler_add_rvoice(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_t *rvoice) { fluid_rvoice_eventhandler_push_ptr(handler, fluid_rvoice_mixer_add_voice, handler->mixer, rvoice); } #endif fluidsynth-2.1.1/src/rvoice/fluid_rvoice_mixer.c000066400000000000000000001255541362231004000220070ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_rvoice_mixer.h" #include "fluid_rvoice.h" #include "fluid_sys.h" #include "fluid_rev.h" #include "fluid_chorus.h" #include "fluid_ladspa.h" #include "fluid_synth.h" // If less than x voices, the thread overhead is larger than the gain, // so don't activate the thread(s). #define VOICES_PER_THREAD 8 typedef struct _fluid_mixer_buffers_t fluid_mixer_buffers_t; struct _fluid_mixer_buffers_t { fluid_rvoice_mixer_t *mixer; /**< Owner of object */ #if ENABLE_MIXER_THREADS fluid_thread_t *thread; /**< Thread object */ fluid_atomic_int_t ready; /**< Atomic: buffers are ready for mixing */ #endif fluid_rvoice_t **finished_voices; /* List of voices who have finished */ int finished_voice_count; fluid_real_t *local_buf; int buf_count; int fx_buf_count; /** buffer to store the left part of a stereo channel to. * Specifically a two dimensional array, containing \c buf_count sample buffers * (i.e. for each synth.audio-channels), of which each contains * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT audio items (=samples) * @note Each sample buffer is aligned to the FLUID_DEFAULT_ALIGNMENT * boundary provided that this pointer points to an aligned buffer. * So make sure to access the sample buffer by first aligning this * pointer using fluid_align_ptr() */ fluid_real_t *left_buf; /** dito, but for right part of a stereo channel */ fluid_real_t *right_buf; /** buffer to store the left part of a stereo effects channel to. * Specifically a two dimensional array, containing \c fx_buf_count buffers * (i.e. for each synth.effects-channels), of which each buffer contains * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT audio items (=samples) */ fluid_real_t *fx_left_buf; fluid_real_t *fx_right_buf; }; typedef struct _fluid_mixer_fx_t fluid_mixer_fx_t; struct _fluid_mixer_fx_t { fluid_revmodel_t *reverb; /**< Reverb unit */ fluid_chorus_t *chorus; /**< Chorus unit */ }; struct _fluid_rvoice_mixer_t { fluid_mixer_fx_t *fx; fluid_mixer_buffers_t buffers; /**< Used by mixer only: own buffers */ fluid_rvoice_eventhandler_t *eventhandler; fluid_rvoice_t **rvoices; /**< Read-only: Voices array, sorted so that all nulls are last */ int polyphony; /**< Read-only: Length of voices array */ int active_voices; /**< Read-only: Number of non-null voices */ int current_blockcount; /**< Read-only: how many blocks to process this time */ int fx_units; int with_reverb; /**< Should the synth use the built-in reverb unit? */ int with_chorus; /**< Should the synth use the built-in chorus unit? */ int mix_fx_to_out; /**< Should the effects be mixed in with the primary output? */ #ifdef LADSPA fluid_ladspa_fx_t *ladspa_fx; /**< Used by mixer only: Effects unit for LADSPA support. Never created or freed */ #endif #if ENABLE_MIXER_THREADS // int sleeping_threads; /**< Atomic: number of threads currently asleep */ // int active_threads; /**< Atomic: number of threads in the thread loop */ fluid_atomic_int_t threads_should_terminate; /**< Atomic: Set to TRUE when threads should terminate */ fluid_atomic_int_t current_rvoice; /**< Atomic: for the threads to know next voice to */ fluid_cond_t *wakeup_threads; /**< Signalled when the threads should wake up */ fluid_cond_mutex_t *wakeup_threads_m; /**< wakeup_threads mutex companion */ fluid_cond_t *thread_ready; /**< Signalled from thread, when the thread has a buffer ready for mixing */ fluid_cond_mutex_t *thread_ready_m; /**< thread_ready mutex companion */ int thread_count; /**< Number of extra mixer threads for multi-core rendering */ fluid_mixer_buffers_t *threads; /**< Array of mixer threads (thread_count in length) */ #endif }; #if ENABLE_MIXER_THREADS static void delete_rvoice_mixer_threads(fluid_rvoice_mixer_t *mixer); static int fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t *mixer, int thread_count, int prio_level); #endif static FLUID_INLINE void fluid_rvoice_mixer_process_fx(fluid_rvoice_mixer_t *mixer, int current_blockcount) { const int fx_channels_per_unit = mixer->buffers.fx_buf_count / mixer->fx_units; int i, f; void (*reverb_process_func)(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); void (*chorus_process_func)(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); fluid_real_t *out_rev_l, *out_rev_r, *out_ch_l, *out_ch_r; // all dry unprocessed mono input is stored in the left channel fluid_real_t *in_rev = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *in_ch = in_rev; fluid_profile_ref_var(prof_ref); if(mixer->mix_fx_to_out) { // mix effects to first stereo channel out_ch_l = out_rev_l = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); out_ch_r = out_rev_r = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); reverb_process_func = fluid_revmodel_processmix; chorus_process_func = fluid_chorus_processmix; } else { // replace effects into respective stereo effects channel out_ch_l = out_rev_l = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); out_ch_r = out_rev_r = fluid_align_ptr(mixer->buffers.fx_right_buf, FLUID_DEFAULT_ALIGNMENT); reverb_process_func = fluid_revmodel_processreplace; chorus_process_func = fluid_chorus_processreplace; } if(mixer->with_reverb) { for(f = 0; f < mixer->fx_units; f++) { int buf_idx = f * fx_channels_per_unit + SYNTH_REVERB_CHANNEL; for(i = 0; i < current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) { int samp_idx = buf_idx * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + i; reverb_process_func(mixer->fx[f].reverb, &in_rev[samp_idx], mixer->mix_fx_to_out ? &out_rev_l[i] : &out_rev_l[samp_idx], mixer->mix_fx_to_out ? &out_rev_r[i] : &out_rev_r[samp_idx]); } } fluid_profile(FLUID_PROF_ONE_BLOCK_REVERB, prof_ref, 0, current_blockcount * FLUID_BUFSIZE); } if(mixer->with_chorus) { for(f = 0; f < mixer->fx_units; f++) { int buf_idx = f * fx_channels_per_unit + SYNTH_CHORUS_CHANNEL; for(i = 0; i < current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) { int samp_idx = buf_idx * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + i; chorus_process_func(mixer->fx[f].chorus, &in_ch [samp_idx], mixer->mix_fx_to_out ? &out_ch_l[i] : &out_ch_l[samp_idx], mixer->mix_fx_to_out ? &out_ch_r[i] : &out_ch_r[samp_idx]); } } fluid_profile(FLUID_PROF_ONE_BLOCK_CHORUS, prof_ref, 0, current_blockcount * FLUID_BUFSIZE); } #ifdef LADSPA /* Run the signal through the LADSPA Fx unit. The buffers have already been * set up in fluid_rvoice_mixer_set_ladspa. */ if(mixer->ladspa_fx) { fluid_ladspa_run(mixer->ladspa_fx, current_blockcount, FLUID_BUFSIZE); fluid_check_fpe("LADSPA"); } #endif } /** * Glue to get fluid_rvoice_buffers_mix what it wants * Note: Make sure outbufs has 2 * (buf_count + fx_buf_count) elements before calling */ static FLUID_INLINE int fluid_mixer_buffers_prepare(fluid_mixer_buffers_t *buffers, fluid_real_t **outbufs) { fluid_real_t *base_ptr; int i; const int fx_channels_per_unit = buffers->fx_buf_count / buffers->mixer->fx_units; const int offset = buffers->buf_count * 2; int with_reverb = buffers->mixer->with_reverb; int with_chorus = buffers->mixer->with_chorus; /* Set up the reverb and chorus buffers only when the effect is enabled or * when LADSPA is active. Nonexisting buffers are detected in the DSP loop. * Not sending the effect signals saves some time in that case. */ #ifdef LADSPA int with_ladspa = (buffers->mixer->ladspa_fx != NULL); with_reverb = (with_reverb | with_ladspa); with_chorus = (with_chorus | with_ladspa); #endif // all the dry, non-processed mono audio for effects is to be stored in the left buffers base_ptr = fluid_align_ptr(buffers->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buffers->mixer->fx_units; i++) { int fx_idx = i * fx_channels_per_unit; outbufs[offset + fx_idx + SYNTH_REVERB_CHANNEL] = (with_reverb) ? &base_ptr[(fx_idx + SYNTH_REVERB_CHANNEL) * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT] : NULL; outbufs[offset + fx_idx + SYNTH_CHORUS_CHANNEL] = (with_chorus) ? &base_ptr[(fx_idx + SYNTH_CHORUS_CHANNEL) * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT] : NULL; } /* The output associated with a MIDI channel is wrapped around * using the number of audio groups as modulo divider. This is * typically the number of output channels on the 'sound card', * as long as the LADSPA Fx unit is not used. In case of LADSPA * unit, think of it as subgroups on a mixer. * * For example: Assume that the number of groups is set to 2. * Then MIDI channel 1, 3, 5, 7 etc. go to output 1, channels 2, * 4, 6, 8 etc to output 2. Or assume 3 groups: Then MIDI * channels 1, 4, 7, 10 etc go to output 1; 2, 5, 8, 11 etc to * output 2, 3, 6, 9, 12 etc to output 3. */ base_ptr = fluid_align_ptr(buffers->left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buffers->buf_count; i++) { outbufs[i * 2] = &base_ptr[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; } base_ptr = fluid_align_ptr(buffers->right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buffers->buf_count; i++) { outbufs[i * 2 + 1] = &base_ptr[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; } return offset + buffers->fx_buf_count; } static FLUID_INLINE void fluid_finish_rvoice(fluid_mixer_buffers_t *buffers, fluid_rvoice_t *rvoice) { if(buffers->finished_voice_count < buffers->mixer->polyphony) { buffers->finished_voices[buffers->finished_voice_count++] = rvoice; } else { FLUID_LOG(FLUID_ERR, "Exceeded finished voices array, try increasing polyphony"); } } static void fluid_mixer_buffer_process_finished_voices(fluid_mixer_buffers_t *buffers) { int i, j; for(i = 0; i < buffers->finished_voice_count; i++) { fluid_rvoice_t *v = buffers->finished_voices[i]; int av = buffers->mixer->active_voices; for(j = 0; j < av; j++) { if(v == buffers->mixer->rvoices[j]) { av--; /* Pack the array */ if(j < av) { buffers->mixer->rvoices[j] = buffers->mixer->rvoices[av]; } } } buffers->mixer->active_voices = av; fluid_rvoice_eventhandler_finished_voice_callback(buffers->mixer->eventhandler, v); } buffers->finished_voice_count = 0; } static FLUID_INLINE void fluid_rvoice_mixer_process_finished_voices(fluid_rvoice_mixer_t *mixer) { #if ENABLE_MIXER_THREADS int i; for(i = 0; i < mixer->thread_count; i++) { fluid_mixer_buffer_process_finished_voices(&mixer->threads[i]); } #endif fluid_mixer_buffer_process_finished_voices(&mixer->buffers); } static FLUID_INLINE fluid_real_t * get_dest_buf(fluid_rvoice_buffers_t *buffers, int index, fluid_real_t **dest_bufs, int dest_bufcount) { int j = buffers->bufs[index].mapping; if(j >= dest_bufcount || j < 0) { return NULL; } return dest_bufs[j]; } /** * Mix samples down from internal dsp_buf to output buffers * * @param buffers Destination buffer(s) * @param dsp_buf Mono sample source * @param start_block starting sample in dsp_buf * @param sample_count number of samples to mix following \c start_block * @param dest_bufs Array of buffers to mixdown to * @param dest_bufcount Length of dest_bufs (i.e count of buffers) */ static void fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, const fluid_real_t *FLUID_RESTRICT dsp_buf, int start_block, int sample_count, fluid_real_t **dest_bufs, int dest_bufcount) { /* buffers count to mixdown to */ int bufcount = buffers->count; int i, dsp_i; /* if there is nothing to mix, return immediately */ if(sample_count <= 0 || dest_bufcount <= 0) { return; } FLUID_ASSERT((uintptr_t)dsp_buf % FLUID_DEFAULT_ALIGNMENT == 0); FLUID_ASSERT((uintptr_t)(&dsp_buf[start_block * FLUID_BUFSIZE]) % FLUID_DEFAULT_ALIGNMENT == 0); /* mixdown for each buffer */ for(i = 0; i < bufcount; i++) { fluid_real_t *FLUID_RESTRICT buf = get_dest_buf(buffers, i, dest_bufs, dest_bufcount); fluid_real_t amp = buffers->bufs[i].amp; if(buf == NULL || amp == 0.0f) { continue; } FLUID_ASSERT((uintptr_t)buf % FLUID_DEFAULT_ALIGNMENT == 0); /* mixdown sample_count samples in the current buffer buf Note, that this loop could be unrolled by FLUID_BUFSIZE elements */ #pragma omp simd aligned(dsp_buf,buf:FLUID_DEFAULT_ALIGNMENT) for(dsp_i = 0; dsp_i < sample_count; dsp_i++) { // Index by blocks (not by samples) to let the compiler know that we always start accessing // buf and dsp_buf at the FLUID_BUFSIZE*sizeof(fluid_real_t) byte boundary and never somewhere // in between. // A good compiler should understand: Aha, so I don't need to add a peel loop when vectorizing // this loop. Great. buf[start_block * FLUID_BUFSIZE + dsp_i] += amp * dsp_buf[start_block * FLUID_BUFSIZE + dsp_i]; } } } /** * Synthesize one voice and add to buffer. * NOTE: If return value is less than blockcount*FLUID_BUFSIZE, that means * voice has been finished, removed and possibly replaced with another voice. */ static FLUID_INLINE void fluid_mixer_buffers_render_one(fluid_mixer_buffers_t *buffers, fluid_rvoice_t *rvoice, fluid_real_t **dest_bufs, unsigned int dest_bufcount, fluid_real_t *src_buf, int blockcount) { int i, total_samples = 0, last_block_mixed = 0; for(i = 0; i < blockcount; i++) { /* render one block in src_buf */ int s = fluid_rvoice_write(rvoice, &src_buf[FLUID_BUFSIZE * i]); if(s == -1) { /* the voice is silent, mix back all the previously rendered sound */ fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, total_samples - (last_block_mixed*FLUID_BUFSIZE), dest_bufs, dest_bufcount); last_block_mixed = i+1; /* future block start index to mix from */ total_samples += FLUID_BUFSIZE; /* accumulate samples count rendered */ } else { /* the voice wasn't quiet. Some samples have been rendered [0..FLUID_BUFSIZE] */ total_samples += s; if(s < FLUID_BUFSIZE) { /* voice has finished */ break; } } } /* Now mix the remaining blocks from last_block_mixed to total_sample */ fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, total_samples - (last_block_mixed*FLUID_BUFSIZE), dest_bufs, dest_bufcount); if(total_samples < blockcount * FLUID_BUFSIZE) { /* voice has finished */ fluid_finish_rvoice(buffers, rvoice); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_add_voice) { int i; fluid_rvoice_mixer_t *mixer = obj; fluid_rvoice_t *voice = param[0].ptr; if(mixer->active_voices < mixer->polyphony) { mixer->rvoices[mixer->active_voices++] = voice; return; // success } /* See if any voices just finished, if so, take its place. This can happen in voice overflow conditions. */ for(i = 0; i < mixer->active_voices; i++) { if(mixer->rvoices[i] == voice) { FLUID_LOG(FLUID_ERR, "Internal error: Trying to replace an existing rvoice in fluid_rvoice_mixer_add_voice?!"); return; } if(mixer->rvoices[i]->envlfo.volenv.section == FLUID_VOICE_ENVFINISHED) { fluid_finish_rvoice(&mixer->buffers, mixer->rvoices[i]); mixer->rvoices[i] = voice; return; // success } } /* This should never happen */ FLUID_LOG(FLUID_ERR, "Trying to exceed polyphony in fluid_rvoice_mixer_add_voice"); return; } static int fluid_mixer_buffers_update_polyphony(fluid_mixer_buffers_t *buffers, int value) { void *newptr; if(buffers->finished_voice_count > value) { return FLUID_FAILED; } newptr = FLUID_REALLOC(buffers->finished_voices, value * sizeof(fluid_rvoice_t *)); if(newptr == NULL && value > 0) { return FLUID_FAILED; } buffers->finished_voices = newptr; return FLUID_OK; } /** * Update polyphony - max number of voices (NOTE: not hard real-time capable) * @return FLUID_OK or FLUID_FAILED */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_polyphony) { void *newptr; fluid_rvoice_mixer_t *handler = obj; int value = param[0].i; if(handler->active_voices > value) { return /*FLUID_FAILED*/; } newptr = FLUID_REALLOC(handler->rvoices, value * sizeof(fluid_rvoice_t *)); if(newptr == NULL) { return /*FLUID_FAILED*/; } handler->rvoices = newptr; if(fluid_mixer_buffers_update_polyphony(&handler->buffers, value) == FLUID_FAILED) { return /*FLUID_FAILED*/; } #if ENABLE_MIXER_THREADS { int i; for(i = 0; i < handler->thread_count; i++) { if(fluid_mixer_buffers_update_polyphony(&handler->threads[i], value) == FLUID_FAILED) { return /*FLUID_FAILED*/; } } } #endif handler->polyphony = value; return /*FLUID_OK*/; } static void fluid_render_loop_singlethread(fluid_rvoice_mixer_t *mixer, int blockcount) { int i; FLUID_DECLARE_VLA(fluid_real_t *, bufs, mixer->buffers.buf_count * 2 + mixer->buffers.fx_buf_count * 2); int bufcount = fluid_mixer_buffers_prepare(&mixer->buffers, bufs); fluid_real_t *local_buf = fluid_align_ptr(mixer->buffers.local_buf, FLUID_DEFAULT_ALIGNMENT); fluid_profile_ref_var(prof_ref); for(i = 0; i < mixer->active_voices; i++) { fluid_mixer_buffers_render_one(&mixer->buffers, mixer->rvoices[i], bufs, bufcount, local_buf, blockcount); fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref, 1, blockcount * FLUID_BUFSIZE); } } static FLUID_INLINE void fluid_mixer_buffers_zero(fluid_mixer_buffers_t *buffers, int current_blockcount) { int i, size = current_blockcount * FLUID_BUFSIZE * sizeof(fluid_real_t); /* TODO: Optimize by only zero out the buffers we actually use later on. */ int buf_count = buffers->buf_count, fx_buf_count = buffers->fx_buf_count; fluid_real_t *FLUID_RESTRICT buf_l = fluid_align_ptr(buffers->left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *FLUID_RESTRICT buf_r = fluid_align_ptr(buffers->right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < buf_count; i++) { FLUID_MEMSET(&buf_l[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); FLUID_MEMSET(&buf_r[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); } buf_l = fluid_align_ptr(buffers->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); buf_r = fluid_align_ptr(buffers->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < fx_buf_count; i++) { FLUID_MEMSET(&buf_l[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); FLUID_MEMSET(&buf_r[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); } } static int fluid_mixer_buffers_init(fluid_mixer_buffers_t *buffers, fluid_rvoice_mixer_t *mixer) { static const int samplecount = FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT; buffers->mixer = mixer; buffers->buf_count = mixer->buffers.buf_count; buffers->fx_buf_count = mixer->buffers.fx_buf_count; /* Local mono voice buf */ buffers->local_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, samplecount, FLUID_DEFAULT_ALIGNMENT); /* Left and right audio buffers */ buffers->left_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); buffers->right_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); if((buffers->local_buf == NULL) || (buffers->left_buf == NULL) || (buffers->right_buf == NULL)) { FLUID_LOG(FLUID_ERR, "Out of memory"); return 0; } /* Effects audio buffers */ buffers->fx_left_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->fx_buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); buffers->fx_right_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->fx_buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); if((buffers->fx_left_buf == NULL) || (buffers->fx_right_buf == NULL)) { FLUID_LOG(FLUID_ERR, "Out of memory"); return 0; } buffers->finished_voices = NULL; if(fluid_mixer_buffers_update_polyphony(buffers, mixer->polyphony) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Out of memory"); return 0; } return 1; } /** * Note: Not hard real-time capable (calls malloc) */ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_samplerate) { fluid_rvoice_mixer_t *mixer = obj; fluid_real_t samplerate = param[1].real; // because fluid_synth_update_mixer() puts real into arg2 int i; for(i = 0; i < mixer->fx_units; i++) { if(mixer->fx[i].chorus) { delete_fluid_chorus(mixer->fx[i].chorus); } mixer->fx[i].chorus = new_fluid_chorus(samplerate); if(mixer->fx[i].reverb) { fluid_revmodel_samplerate_change(mixer->fx[i].reverb, samplerate); } } #if LADSPA if(mixer->ladspa_fx != NULL) { fluid_ladspa_set_sample_rate(mixer->ladspa_fx, samplerate); } #endif } /** * @param buf_count number of primary stereo buffers * @param fx_buf_count number of stereo effect buffers */ fluid_rvoice_mixer_t * new_fluid_rvoice_mixer(int buf_count, int fx_buf_count, int fx_units, fluid_real_t sample_rate, fluid_rvoice_eventhandler_t *evthandler, int extra_threads, int prio) { int i; fluid_rvoice_mixer_t *mixer = FLUID_NEW(fluid_rvoice_mixer_t); if(mixer == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(mixer, 0, sizeof(fluid_rvoice_mixer_t)); mixer->eventhandler = evthandler; mixer->fx_units = fx_units; mixer->buffers.buf_count = buf_count; mixer->buffers.fx_buf_count = fx_buf_count * fx_units; /* allocate the reverb module */ mixer->fx = FLUID_ARRAY(fluid_mixer_fx_t, fx_units); if(mixer->fx == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } FLUID_MEMSET(mixer->fx, 0, fx_units * sizeof(*mixer->fx)); for(i = 0; i < fx_units; i++) { mixer->fx[i].reverb = new_fluid_revmodel(sample_rate); mixer->fx[i].chorus = new_fluid_chorus(sample_rate); if(mixer->fx[i].reverb == NULL || mixer->fx[i].chorus == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } if(!fluid_mixer_buffers_init(&mixer->buffers, mixer)) { goto error_recovery; } #if ENABLE_MIXER_THREADS mixer->thread_ready = new_fluid_cond(); mixer->wakeup_threads = new_fluid_cond(); mixer->thread_ready_m = new_fluid_cond_mutex(); mixer->wakeup_threads_m = new_fluid_cond_mutex(); if(!mixer->thread_ready || !mixer->wakeup_threads || !mixer->thread_ready_m || !mixer->wakeup_threads_m) { goto error_recovery; } if(fluid_rvoice_mixer_set_threads(mixer, extra_threads, prio) != FLUID_OK) { goto error_recovery; } #endif return mixer; error_recovery: delete_fluid_rvoice_mixer(mixer); return NULL; } static void fluid_mixer_buffers_free(fluid_mixer_buffers_t *buffers) { FLUID_FREE(buffers->finished_voices); /* free all the sample buffers */ FLUID_FREE(buffers->local_buf); FLUID_FREE(buffers->left_buf); FLUID_FREE(buffers->right_buf); FLUID_FREE(buffers->fx_left_buf); FLUID_FREE(buffers->fx_right_buf); } void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t *mixer) { int i; fluid_return_if_fail(mixer != NULL); #if ENABLE_MIXER_THREADS delete_rvoice_mixer_threads(mixer); if(mixer->thread_ready) { delete_fluid_cond(mixer->thread_ready); } if(mixer->wakeup_threads) { delete_fluid_cond(mixer->wakeup_threads); } if(mixer->thread_ready_m) { delete_fluid_cond_mutex(mixer->thread_ready_m); } if(mixer->wakeup_threads_m) { delete_fluid_cond_mutex(mixer->wakeup_threads_m); } #endif fluid_mixer_buffers_free(&mixer->buffers); for(i = 0; i < mixer->fx_units; i++) { if(mixer->fx[i].reverb) { delete_fluid_revmodel(mixer->fx[i].reverb); } if(mixer->fx[i].chorus) { delete_fluid_chorus(mixer->fx[i].chorus); } } FLUID_FREE(mixer->fx); FLUID_FREE(mixer->rvoices); FLUID_FREE(mixer); } #ifdef LADSPA /** * Set a LADSPS fx instance to be used by the mixer and assign the mixer buffers * as LADSPA host buffers with sensible names */ void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t *mixer, fluid_ladspa_fx_t *ladspa_fx, int audio_groups) { mixer->ladspa_fx = ladspa_fx; if(ladspa_fx == NULL) { return; } else { fluid_real_t *main_l = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *main_r = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *rev = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); fluid_real_t *chor = rev; rev = &rev[SYNTH_REVERB_CHANNEL * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; chor = &chor[SYNTH_CHORUS_CHANNEL * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; fluid_ladspa_add_host_ports(ladspa_fx, "Main:L", audio_groups, main_l, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); fluid_ladspa_add_host_ports(ladspa_fx, "Main:R", audio_groups, main_r, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); fluid_ladspa_add_host_ports(ladspa_fx, "Reverb:Send", 1, rev, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); fluid_ladspa_add_host_ports(ladspa_fx, "Chorus:Send", 1, chor, FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); } } #endif DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_enabled) { fluid_rvoice_mixer_t *mixer = obj; int on = param[0].i; mixer->with_reverb = on; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_enabled) { fluid_rvoice_mixer_t *mixer = obj; int on = param[0].i; mixer->with_chorus = on; } void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t *mixer, int on) { mixer->mix_fx_to_out = on; } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_params) { fluid_rvoice_mixer_t *mixer = obj; int set = param[0].i; int nr = param[1].i; fluid_real_t level = param[2].real; fluid_real_t speed = param[3].real; fluid_real_t depth_ms = param[4].real; int type = param[5].i; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_chorus_set(mixer->fx[i].chorus, set, nr, level, speed, depth_ms, type); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_params) { fluid_rvoice_mixer_t *mixer = obj; int set = param[0].i; fluid_real_t roomsize = param[1].real; fluid_real_t damping = param[2].real; fluid_real_t width = param[3].real; fluid_real_t level = param[4].real; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_revmodel_set(mixer->fx[i].reverb, set, roomsize, damping, width, level); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_reverb) { fluid_rvoice_mixer_t *mixer = obj; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_revmodel_reset(mixer->fx[i].reverb); } } DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_chorus) { fluid_rvoice_mixer_t *mixer = obj; int i; for(i = 0; i < mixer->fx_units; i++) { fluid_chorus_reset(mixer->fx[i].chorus); } } int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t *mixer, fluid_real_t **left, fluid_real_t **right) { *left = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); *right = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); return mixer->buffers.buf_count; } int fluid_rvoice_mixer_get_fx_bufs(fluid_rvoice_mixer_t *mixer, fluid_real_t **fx_left, fluid_real_t **fx_right) { *fx_left = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); *fx_right = fluid_align_ptr(mixer->buffers.fx_right_buf, FLUID_DEFAULT_ALIGNMENT); return mixer->buffers.fx_buf_count; } int fluid_rvoice_mixer_get_bufcount(fluid_rvoice_mixer_t *mixer) { return FLUID_MIXER_MAX_BUFFERS_DEFAULT; } #if WITH_PROFILING int fluid_rvoice_mixer_get_active_voices(fluid_rvoice_mixer_t *mixer) { return mixer->active_voices; } #endif #if ENABLE_MIXER_THREADS static FLUID_INLINE fluid_rvoice_t * fluid_mixer_get_mt_rvoice(fluid_rvoice_mixer_t *mixer) { int i = fluid_atomic_int_exchange_and_add(&mixer->current_rvoice, 1); if(i >= mixer->active_voices) { return NULL; } return mixer->rvoices[i]; } #define THREAD_BUF_PROCESSING 0 #define THREAD_BUF_VALID 1 #define THREAD_BUF_NODATA 2 #define THREAD_BUF_TERMINATE 3 /* Core thread function (processes voices in parallel to primary synthesis thread) */ static fluid_thread_return_t fluid_mixer_thread_func(void *data) { fluid_mixer_buffers_t *buffers = data; fluid_rvoice_mixer_t *mixer = buffers->mixer; int hasValidData = 0; FLUID_DECLARE_VLA(fluid_real_t *, bufs, buffers->buf_count * 2 + buffers->fx_buf_count * 2); int bufcount = 0; int current_blockcount = 0; fluid_real_t *local_buf = fluid_align_ptr(buffers->local_buf, FLUID_DEFAULT_ALIGNMENT); while(!fluid_atomic_int_get(&mixer->threads_should_terminate)) { fluid_rvoice_t *rvoice = fluid_mixer_get_mt_rvoice(mixer); if(rvoice == NULL) { // if no voices: signal rendered buffers, sleep fluid_atomic_int_set(&buffers->ready, hasValidData ? THREAD_BUF_VALID : THREAD_BUF_NODATA); fluid_cond_mutex_lock(mixer->thread_ready_m); fluid_cond_signal(mixer->thread_ready); fluid_cond_mutex_unlock(mixer->thread_ready_m); fluid_cond_mutex_lock(mixer->wakeup_threads_m); while(1) { int j = fluid_atomic_int_get(&buffers->ready); if(j == THREAD_BUF_PROCESSING || j == THREAD_BUF_TERMINATE) { break; } fluid_cond_wait(mixer->wakeup_threads, mixer->wakeup_threads_m); } fluid_cond_mutex_unlock(mixer->wakeup_threads_m); hasValidData = 0; } else { // else: if buffer is not zeroed, zero buffers if(!hasValidData) { // blockcount may have changed, since thread was put to sleep current_blockcount = mixer->current_blockcount; fluid_mixer_buffers_zero(buffers, current_blockcount); bufcount = fluid_mixer_buffers_prepare(buffers, bufs); hasValidData = 1; } // then render voice to buffers fluid_mixer_buffers_render_one(buffers, rvoice, bufs, bufcount, local_buf, current_blockcount); } } return FLUID_THREAD_RETURN_VALUE; } static void fluid_mixer_buffers_mix(fluid_mixer_buffers_t *dst, fluid_mixer_buffers_t *src, int current_blockcount) { int i, j; int scount = current_blockcount * FLUID_BUFSIZE; int minbuf; fluid_real_t *FLUID_RESTRICT base_src; fluid_real_t *FLUID_RESTRICT base_dst; minbuf = dst->buf_count; if(minbuf > src->buf_count) { minbuf = src->buf_count; } base_src = fluid_align_ptr(src->left_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } base_src = fluid_align_ptr(src->right_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } minbuf = dst->fx_buf_count; if(minbuf > src->fx_buf_count) { minbuf = src->fx_buf_count; } base_src = fluid_align_ptr(src->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } base_src = fluid_align_ptr(src->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); base_dst = fluid_align_ptr(dst->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); for(i = 0; i < minbuf; i++) { #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) for(j = 0; j < scount; j++) { int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; base_dst[dsp_i] += base_src[dsp_i]; } } } /** * Go through all threads and see if someone is finished for mixing */ static int fluid_mixer_mix_in(fluid_rvoice_mixer_t *mixer, int extra_threads, int current_blockcount) { int i, result, hasmixed; do { hasmixed = 0; result = 0; for(i = 0; i < extra_threads; i++) { int j = fluid_atomic_int_get(&mixer->threads[i].ready); switch(j) { case THREAD_BUF_PROCESSING: result = 1; break; case THREAD_BUF_VALID: fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_NODATA); fluid_mixer_buffers_mix(&mixer->buffers, &mixer->threads[i], current_blockcount); hasmixed = 1; break; } } } while(hasmixed); return result; } static void fluid_render_loop_multithread(fluid_rvoice_mixer_t *mixer, int current_blockcount) { int i, bufcount; fluid_real_t *local_buf = fluid_align_ptr(mixer->buffers.local_buf, FLUID_DEFAULT_ALIGNMENT); FLUID_DECLARE_VLA(fluid_real_t *, bufs, mixer->buffers.buf_count * 2 + mixer->buffers.fx_buf_count * 2); // How many threads should we start this time? int extra_threads = mixer->active_voices / VOICES_PER_THREAD; if(extra_threads > mixer->thread_count) { extra_threads = mixer->thread_count; } if(extra_threads == 0) { // No extra threads? No thread overhead! fluid_render_loop_singlethread(mixer, current_blockcount); return; } bufcount = fluid_mixer_buffers_prepare(&mixer->buffers, bufs); // Prepare voice list fluid_cond_mutex_lock(mixer->wakeup_threads_m); fluid_atomic_int_set(&mixer->current_rvoice, 0); for(i = 0; i < extra_threads; i++) { fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_PROCESSING); } // Signal threads to wake up fluid_cond_broadcast(mixer->wakeup_threads); fluid_cond_mutex_unlock(mixer->wakeup_threads_m); // If thread is finished, mix it in while(fluid_mixer_mix_in(mixer, extra_threads, current_blockcount)) { // Otherwise get a voice and render it fluid_rvoice_t *rvoice = fluid_mixer_get_mt_rvoice(mixer); if(rvoice != NULL) { fluid_profile_ref_var(prof_ref); fluid_mixer_buffers_render_one(&mixer->buffers, rvoice, bufs, bufcount, local_buf, current_blockcount); fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref, 1, current_blockcount * FLUID_BUFSIZE); //test++; } else { // If no voices, wait for mixes. Make sure one is still processing to avoid deadlock int is_processing = 0; //waits++; fluid_cond_mutex_lock(mixer->thread_ready_m); for(i = 0; i < extra_threads; i++) { if(fluid_atomic_int_get(&mixer->threads[i].ready) == THREAD_BUF_PROCESSING) { is_processing = 1; } } if(is_processing) { fluid_cond_wait(mixer->thread_ready, mixer->thread_ready_m); } fluid_cond_mutex_unlock(mixer->thread_ready_m); } } //FLUID_LOG(FLUID_DBG, "Blockcount: %d, mixed %d of %d voices myself, waits = %d", // current_blockcount, test, mixer->active_voices, waits); } static void delete_rvoice_mixer_threads(fluid_rvoice_mixer_t *mixer) { int i; fluid_atomic_int_set(&mixer->threads_should_terminate, 1); // Signal threads to wake up fluid_cond_mutex_lock(mixer->wakeup_threads_m); for(i = 0; i < mixer->thread_count; i++) { fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_TERMINATE); } fluid_cond_broadcast(mixer->wakeup_threads); fluid_cond_mutex_unlock(mixer->wakeup_threads_m); for(i = 0; i < mixer->thread_count; i++) { if(mixer->threads[i].thread) { fluid_thread_join(mixer->threads[i].thread); delete_fluid_thread(mixer->threads[i].thread); } fluid_mixer_buffers_free(&mixer->threads[i]); } FLUID_FREE(mixer->threads); mixer->thread_count = 0; mixer->threads = NULL; } /** * Update amount of extra mixer threads. * @param thread_count Number of extra mixer threads for multi-core rendering * @param prio_level real-time prio level for the extra mixer threads */ static int fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t *mixer, int thread_count, int prio_level) { char name[16]; int i; // Kill all existing threads first if(mixer->thread_count) { delete_rvoice_mixer_threads(mixer); } if(thread_count == 0) { return FLUID_OK; } // Now prepare the new threads fluid_atomic_int_set(&mixer->threads_should_terminate, 0); mixer->threads = FLUID_ARRAY(fluid_mixer_buffers_t, thread_count); if(mixer->threads == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } FLUID_MEMSET(mixer->threads, 0, thread_count * sizeof(fluid_mixer_buffers_t)); mixer->thread_count = thread_count; for(i = 0; i < thread_count; i++) { fluid_mixer_buffers_t *b = &mixer->threads[i]; if(!fluid_mixer_buffers_init(b, mixer)) { return FLUID_FAILED; } fluid_atomic_int_set(&b->ready, THREAD_BUF_NODATA); FLUID_SNPRINTF(name, sizeof(name), "mixer%d", i); b->thread = new_fluid_thread(name, fluid_mixer_thread_func, b, prio_level, 0); if(!b->thread) { return FLUID_FAILED; } } return FLUID_OK; } #endif /** * Synthesize audio into buffers * @param blockcount number of blocks to render, each having FLUID_BUFSIZE samples * @return number of blocks rendered */ int fluid_rvoice_mixer_render(fluid_rvoice_mixer_t *mixer, int blockcount) { fluid_profile_ref_var(prof_ref); mixer->current_blockcount = blockcount; // Zero buffers fluid_mixer_buffers_zero(&mixer->buffers, blockcount); fluid_profile(FLUID_PROF_ONE_BLOCK_CLEAR, prof_ref, mixer->active_voices, blockcount * FLUID_BUFSIZE); #if ENABLE_MIXER_THREADS if(mixer->thread_count > 0) { fluid_render_loop_multithread(mixer, blockcount); } else #endif { fluid_render_loop_singlethread(mixer, blockcount); } fluid_profile(FLUID_PROF_ONE_BLOCK_VOICES, prof_ref, mixer->active_voices, blockcount * FLUID_BUFSIZE); // Process reverb & chorus fluid_rvoice_mixer_process_fx(mixer, blockcount); // Call the callback and pack active voice array fluid_rvoice_mixer_process_finished_voices(mixer); return blockcount; } fluidsynth-2.1.1/src/rvoice/fluid_rvoice_mixer.h000066400000000000000000000051331362231004000220020ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_RVOICE_MIXER_H #define _FLUID_RVOICE_MIXER_H #include "fluidsynth_priv.h" #include "fluid_rvoice.h" #include "fluid_ladspa.h" typedef struct _fluid_rvoice_mixer_t fluid_rvoice_mixer_t; int fluid_rvoice_mixer_render(fluid_rvoice_mixer_t *mixer, int blockcount); int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t *mixer, fluid_real_t **left, fluid_real_t **right); int fluid_rvoice_mixer_get_fx_bufs(fluid_rvoice_mixer_t *mixer, fluid_real_t **fx_left, fluid_real_t **fx_right); int fluid_rvoice_mixer_get_bufcount(fluid_rvoice_mixer_t *mixer); #if WITH_PROFILING int fluid_rvoice_mixer_get_active_voices(fluid_rvoice_mixer_t *mixer); #endif fluid_rvoice_mixer_t *new_fluid_rvoice_mixer(int buf_count, int fx_buf_count, int fx_units, fluid_real_t sample_rate, fluid_rvoice_eventhandler_t *, int, int); void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t *); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_add_voice); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_samplerate); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_polyphony); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_enabled); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_enabled); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_params); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_params); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_reverb); DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_chorus); void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t *mixer, int on); #ifdef LADSPA void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t *mixer, fluid_ladspa_fx_t *ladspa_fx, int audio_groups); #endif #endif fluidsynth-2.1.1/src/sfloader/000077500000000000000000000000001362231004000162615ustar00rootroot00000000000000fluidsynth-2.1.1/src/sfloader/fluid_defsfont.c000066400000000000000000001717261362231004000214360ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * SoundFont file loading code borrowed from Smurf SoundFont Editor * Copyright (C) 1999-2001 Josh Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_defsfont.h" #include "fluid_sfont.h" #include "fluid_sys.h" #include "fluid_synth.h" #include "fluid_samplecache.h" /* EMU8k/10k hardware applies this factor to initial attenuation generator values set at preset and * instrument level in a soundfont. We apply this factor when loading the generator values to stay * compatible as most existing soundfonts expect exactly this (strange, non-standard) behaviour. */ #define EMU_ATTENUATION_FACTOR (0.4f) /* Dynamic sample loading functions */ static int load_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset); static int unload_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset); static void unload_sample(fluid_sample_t *sample); static int dynamic_samples_preset_notify(fluid_preset_t *preset, int reason, int chan); static int dynamic_samples_sample_notify(fluid_sample_t *sample, int reason); static int fluid_preset_zone_create_voice_zones(fluid_preset_zone_t *preset_zone); static fluid_inst_t *find_inst_by_idx(fluid_defsfont_t *defsfont, int idx); /*************************************************************** * * SFONT LOADER */ /** * Creates a default soundfont2 loader that can be used with fluid_synth_add_sfloader(). * By default every synth instance has an initial default soundfont loader instance. * Calling this function is usually only necessary to load a soundfont from memory, by providing custom callback functions via fluid_sfloader_set_callbacks(). * * @param settings A settings instance obtained by new_fluid_settings() * @return A default soundfont2 loader struct */ fluid_sfloader_t *new_fluid_defsfloader(fluid_settings_t *settings) { fluid_sfloader_t *loader; fluid_return_val_if_fail(settings != NULL, NULL); loader = new_fluid_sfloader(fluid_defsfloader_load, delete_fluid_sfloader); if(loader == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } fluid_sfloader_set_data(loader, settings); return loader; } fluid_sfont_t *fluid_defsfloader_load(fluid_sfloader_t *loader, const char *filename) { fluid_defsfont_t *defsfont; fluid_sfont_t *sfont; defsfont = new_fluid_defsfont(fluid_sfloader_get_data(loader)); if(defsfont == NULL) { return NULL; } sfont = new_fluid_sfont(fluid_defsfont_sfont_get_name, fluid_defsfont_sfont_get_preset, fluid_defsfont_sfont_iteration_start, fluid_defsfont_sfont_iteration_next, fluid_defsfont_sfont_delete); if(sfont == NULL) { delete_fluid_defsfont(defsfont); return NULL; } fluid_sfont_set_data(sfont, defsfont); defsfont->sfont = sfont; if(fluid_defsfont_load(defsfont, &loader->file_callbacks, filename) == FLUID_FAILED) { fluid_defsfont_sfont_delete(sfont); return NULL; } return sfont; } /*************************************************************** * * PUBLIC INTERFACE */ int fluid_defsfont_sfont_delete(fluid_sfont_t *sfont) { if(delete_fluid_defsfont(fluid_sfont_get_data(sfont)) != FLUID_OK) { return -1; } delete_fluid_sfont(sfont); return 0; } const char *fluid_defsfont_sfont_get_name(fluid_sfont_t *sfont) { return fluid_defsfont_get_name(fluid_sfont_get_data(sfont)); } fluid_preset_t * fluid_defsfont_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum) { return fluid_defsfont_get_preset(fluid_sfont_get_data(sfont), bank, prenum); } void fluid_defsfont_sfont_iteration_start(fluid_sfont_t *sfont) { fluid_defsfont_iteration_start(fluid_sfont_get_data(sfont)); } fluid_preset_t *fluid_defsfont_sfont_iteration_next(fluid_sfont_t *sfont) { return fluid_defsfont_iteration_next(fluid_sfont_get_data(sfont)); } void fluid_defpreset_preset_delete(fluid_preset_t *preset) { fluid_defsfont_t *defsfont; fluid_defpreset_t *defpreset; defsfont = fluid_sfont_get_data(preset->sfont); defpreset = fluid_preset_get_data(preset); if(defsfont) { defsfont->preset = fluid_list_remove(defsfont->preset, defpreset); } delete_fluid_defpreset(defpreset); delete_fluid_preset(preset); } const char *fluid_defpreset_preset_get_name(fluid_preset_t *preset) { return fluid_defpreset_get_name(fluid_preset_get_data(preset)); } int fluid_defpreset_preset_get_banknum(fluid_preset_t *preset) { return fluid_defpreset_get_banknum(fluid_preset_get_data(preset)); } int fluid_defpreset_preset_get_num(fluid_preset_t *preset) { return fluid_defpreset_get_num(fluid_preset_get_data(preset)); } int fluid_defpreset_preset_noteon(fluid_preset_t *preset, fluid_synth_t *synth, int chan, int key, int vel) { return fluid_defpreset_noteon(fluid_preset_get_data(preset), synth, chan, key, vel); } /*************************************************************** * * SFONT */ /* * new_fluid_defsfont */ fluid_defsfont_t *new_fluid_defsfont(fluid_settings_t *settings) { fluid_defsfont_t *defsfont; defsfont = FLUID_NEW(fluid_defsfont_t); if(defsfont == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(defsfont, 0, sizeof(*defsfont)); fluid_settings_getint(settings, "synth.lock-memory", &defsfont->mlock); fluid_settings_getint(settings, "synth.dynamic-sample-loading", &defsfont->dynamic_samples); return defsfont; } /* * delete_fluid_defsfont */ int delete_fluid_defsfont(fluid_defsfont_t *defsfont) { fluid_list_t *list; fluid_preset_t *preset; fluid_sample_t *sample; fluid_return_val_if_fail(defsfont != NULL, FLUID_OK); /* Check that no samples are currently used */ for(list = defsfont->sample; list; list = fluid_list_next(list)) { sample = (fluid_sample_t *) fluid_list_get(list); if(sample->refcount != 0) { return FLUID_FAILED; } } if(defsfont->filename != NULL) { FLUID_FREE(defsfont->filename); } for(list = defsfont->sample; list; list = fluid_list_next(list)) { sample = (fluid_sample_t *) fluid_list_get(list); /* If the sample data pointer is different to the sampledata chunk of * the soundfont, then the sample has been loaded individually (SF3) * and needs to be unloaded explicitly. This is safe even if using * dynamic sample loading, as the sample_unload mechanism sets * sample->data to NULL after unload. */ if ((sample->data != NULL) && (sample->data != defsfont->sampledata)) { fluid_samplecache_unload(sample->data); } delete_fluid_sample(sample); } if(defsfont->sample) { delete_fluid_list(defsfont->sample); } if(defsfont->sampledata != NULL) { fluid_samplecache_unload(defsfont->sampledata); } for(list = defsfont->preset; list; list = fluid_list_next(list)) { preset = (fluid_preset_t *)fluid_list_get(list); fluid_defpreset_preset_delete(preset); } delete_fluid_list(defsfont->preset); for(list = defsfont->inst; list; list = fluid_list_next(list)) { delete_fluid_inst(fluid_list_get(list)); } delete_fluid_list(defsfont->inst); FLUID_FREE(defsfont); return FLUID_OK; } /* * fluid_defsfont_get_name */ const char *fluid_defsfont_get_name(fluid_defsfont_t *defsfont) { return defsfont->filename; } /* Load sample data for a single sample from the Soundfont file. * Returns FLUID_OK on error, otherwise FLUID_FAILED */ int fluid_defsfont_load_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata, fluid_sample_t *sample) { int num_samples; unsigned int source_end = sample->source_end; /* For uncompressed samples we want to include the 46 zero sample word area following each sample * in the Soundfont. Otherwise samples with loopend > end, which we have decided not to correct, would * be corrected after all in fluid_sample_sanitize_loop */ if(!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)) { source_end += 46; /* Length of zero sample word after each sample, according to SF specs */ /* Safeguard against Soundfonts that are not quite valid and don't include 46 sample words after the * last sample */ if(source_end >= (defsfont->samplesize / sizeof(short))) { source_end = defsfont->samplesize / sizeof(short); } } num_samples = fluid_samplecache_load( sfdata, sample->source_start, source_end, sample->sampletype, defsfont->mlock, &sample->data, &sample->data24); if(num_samples < 0) { return FLUID_FAILED; } if(num_samples == 0) { sample->start = sample->end = 0; sample->loopstart = sample->loopend = 0; return FLUID_OK; } /* Ogg Vorbis samples already have loop pointers relative to the individual decompressed sample, * but SF2 samples are relative to sample chunk start, so they need to be adjusted */ if(!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)) { sample->loopstart = sample->source_loopstart - sample->source_start; sample->loopend = sample->source_loopend - sample->source_start; } /* As we've just loaded an individual sample into it's own buffer, we need to adjust the start * and end pointers */ sample->start = 0; sample->end = num_samples - 1; return FLUID_OK; } /* Loads the sample data for all samples from the Soundfont file. For SF2 files, it loads the data in * one large block. For SF3 files, each compressed sample gets loaded individually. * Returns FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_defsfont_load_all_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata) { fluid_list_t *list; fluid_sample_t *sample; int sf3_file = (sfdata->version.major == 3); /* For SF2 files, we load the sample data in one large block */ if(!sf3_file) { int read_samples; int num_samples = sfdata->samplesize / sizeof(short); read_samples = fluid_samplecache_load(sfdata, 0, num_samples - 1, 0, defsfont->mlock, &defsfont->sampledata, &defsfont->sample24data); if(read_samples != num_samples) { FLUID_LOG(FLUID_ERR, "Attempted to read %d words of sample data, but got %d instead", num_samples, read_samples); return FLUID_FAILED; } } for(list = defsfont->sample; list; list = fluid_list_next(list)) { sample = fluid_list_get(list); if(sf3_file) { /* SF3 samples get loaded individually, as most (or all) of them are in Ogg Vorbis format * anyway */ if(fluid_defsfont_load_sampledata(defsfont, sfdata, sample) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to load sample '%s'", sample->name); return FLUID_FAILED; } fluid_sample_sanitize_loop(sample, (sample->end + 1) * sizeof(short)); } else { /* Data pointers of SF2 samples point to large sample data block loaded above */ sample->data = defsfont->sampledata; sample->data24 = defsfont->sample24data; fluid_sample_sanitize_loop(sample, defsfont->samplesize); } fluid_voice_optimize_sample(sample); } return FLUID_OK; } /* * fluid_defsfont_load */ int fluid_defsfont_load(fluid_defsfont_t *defsfont, const fluid_file_callbacks_t *fcbs, const char *file) { SFData *sfdata; fluid_list_t *p; SFPreset *sfpreset; SFSample *sfsample; fluid_sample_t *sample; fluid_defpreset_t *defpreset = NULL; defsfont->filename = FLUID_STRDUP(file); if(defsfont->filename == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } defsfont->fcbs = fcbs; /* The actual loading is done in the sfont and sffile files */ sfdata = fluid_sffile_open(file, fcbs); if(sfdata == NULL) { /* error message already printed */ return FLUID_FAILED; } if(fluid_sffile_parse_presets(sfdata) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Couldn't parse presets from soundfont file"); goto err_exit; } /* Keep track of the position and size of the sample data because it's loaded separately (and might be unoaded/reloaded in future) */ defsfont->samplepos = sfdata->samplepos; defsfont->samplesize = sfdata->samplesize; defsfont->sample24pos = sfdata->sample24pos; defsfont->sample24size = sfdata->sample24size; /* Create all samples from sample headers */ p = sfdata->sample; while(p != NULL) { sfsample = (SFSample *)fluid_list_get(p); sample = new_fluid_sample(); if(sample == NULL) { goto err_exit; } if(fluid_sample_import_sfont(sample, sfsample, defsfont) == FLUID_OK) { fluid_defsfont_add_sample(defsfont, sample); } else { delete_fluid_sample(sample); sample = NULL; } /* Store reference to FluidSynth sample in SFSample for later IZone fixups */ sfsample->fluid_sample = sample; p = fluid_list_next(p); } /* If dynamic sample loading is disabled, load all samples in the Soundfont */ if(!defsfont->dynamic_samples) { if(fluid_defsfont_load_all_sampledata(defsfont, sfdata) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Unable to load all sample data"); goto err_exit; } } /* Load all the presets */ p = sfdata->preset; while(p != NULL) { sfpreset = (SFPreset *)fluid_list_get(p); defpreset = new_fluid_defpreset(); if(defpreset == NULL) { goto err_exit; } if(fluid_defpreset_import_sfont(defpreset, sfpreset, defsfont) != FLUID_OK) { goto err_exit; } if(fluid_defsfont_add_preset(defsfont, defpreset) == FLUID_FAILED) { goto err_exit; } p = fluid_list_next(p); } fluid_sffile_close(sfdata); return FLUID_OK; err_exit: fluid_sffile_close(sfdata); delete_fluid_defpreset(defpreset); return FLUID_FAILED; } /* fluid_defsfont_add_sample * * Add a sample to the SoundFont */ int fluid_defsfont_add_sample(fluid_defsfont_t *defsfont, fluid_sample_t *sample) { defsfont->sample = fluid_list_append(defsfont->sample, sample); return FLUID_OK; } /* fluid_defsfont_add_preset * * Add a preset to the SoundFont */ int fluid_defsfont_add_preset(fluid_defsfont_t *defsfont, fluid_defpreset_t *defpreset) { fluid_preset_t *preset; preset = new_fluid_preset(defsfont->sfont, fluid_defpreset_preset_get_name, fluid_defpreset_preset_get_banknum, fluid_defpreset_preset_get_num, fluid_defpreset_preset_noteon, fluid_defpreset_preset_delete); if(defsfont->dynamic_samples) { preset->notify = dynamic_samples_preset_notify; } if(preset == NULL) { return FLUID_FAILED; } fluid_preset_set_data(preset, defpreset); defsfont->preset = fluid_list_append(defsfont->preset, preset); return FLUID_OK; } /* * fluid_defsfont_get_preset */ fluid_preset_t *fluid_defsfont_get_preset(fluid_defsfont_t *defsfont, int bank, int num) { fluid_preset_t *preset; fluid_list_t *list; for(list = defsfont->preset; list != NULL; list = fluid_list_next(list)) { preset = (fluid_preset_t *)fluid_list_get(list); if((fluid_preset_get_banknum(preset) == bank) && (fluid_preset_get_num(preset) == num)) { return preset; } } return NULL; } /* * fluid_defsfont_iteration_start */ void fluid_defsfont_iteration_start(fluid_defsfont_t *defsfont) { defsfont->preset_iter_cur = defsfont->preset; } /* * fluid_defsfont_iteration_next */ fluid_preset_t *fluid_defsfont_iteration_next(fluid_defsfont_t *defsfont) { fluid_preset_t *preset = (fluid_preset_t *)fluid_list_get(defsfont->preset_iter_cur); defsfont->preset_iter_cur = fluid_list_next(defsfont->preset_iter_cur); return preset; } /*************************************************************** * * PRESET */ /* * new_fluid_defpreset */ fluid_defpreset_t * new_fluid_defpreset(void) { fluid_defpreset_t *defpreset = FLUID_NEW(fluid_defpreset_t); if(defpreset == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } defpreset->next = NULL; defpreset->name[0] = 0; defpreset->bank = 0; defpreset->num = 0; defpreset->global_zone = NULL; defpreset->zone = NULL; return defpreset; } /* * delete_fluid_defpreset */ void delete_fluid_defpreset(fluid_defpreset_t *defpreset) { fluid_preset_zone_t *zone; fluid_return_if_fail(defpreset != NULL); delete_fluid_preset_zone(defpreset->global_zone); defpreset->global_zone = NULL; zone = defpreset->zone; while(zone != NULL) { defpreset->zone = zone->next; delete_fluid_preset_zone(zone); zone = defpreset->zone; } FLUID_FREE(defpreset); } int fluid_defpreset_get_banknum(fluid_defpreset_t *defpreset) { return defpreset->bank; } int fluid_defpreset_get_num(fluid_defpreset_t *defpreset) { return defpreset->num; } const char * fluid_defpreset_get_name(fluid_defpreset_t *defpreset) { return defpreset->name; } /* * fluid_defpreset_next */ fluid_defpreset_t * fluid_defpreset_next(fluid_defpreset_t *defpreset) { return defpreset->next; } /* * Adds global and local modulators list to the voice. This is done in 2 steps: * - Step 1: Local modulators replace identic global modulators. * - Step 2: global + local modulators are added to the voice using mode. * * Instrument zone list (local/global) must be added using FLUID_VOICE_OVERWRITE. * Preset zone list (local/global) must be added using FLUID_VOICE_ADD. * * @param voice voice instance. * @param global_mod global list of modulators. * @param local_mod local list of modulators. * @param mode Determines how to handle an existing identical modulator. * #FLUID_VOICE_ADD to add (offset) the modulator amounts, * #FLUID_VOICE_OVERWRITE to replace the modulator, */ static void fluid_defpreset_noteon_add_mod_to_voice(fluid_voice_t *voice, fluid_mod_t *global_mod, fluid_mod_t *local_mod, int mode) { fluid_mod_t *mod; /* list for 'sorting' global/local modulators */ fluid_mod_t *mod_list[FLUID_NUM_MOD]; int mod_list_count, i; /* identity_limit_count is the modulator upper limit number to handle with * existing identical modulators. * When identity_limit_count is below the actual number of modulators, this * will restrict identity check to this upper limit, * This is useful when we know by advance that there is no duplicate with * modulators at index above this limit. This avoid wasting cpu cycles at * noteon. */ int identity_limit_count; /* Step 1: Local modulators replace identic global modulators. */ /* local (instrument zone/preset zone), modulators: Put them all into a list. */ mod_list_count = 0; while(local_mod) { /* As modulators number in local_mod list was limited to FLUID_NUM_MOD at soundfont loading time (fluid_limit_mod_list()), here we don't need to check if mod_list is full. */ mod_list[mod_list_count++] = local_mod; local_mod = local_mod->next; } /* global (instrument zone/preset zone), modulators. * Replace modulators with the same definition in the global list: * (Instrument zone: SF 2.01 page 69, 'bullet' 8) * (Preset zone: SF 2.01 page 69, second-last bullet). * * mod_list contains local modulators. Now we know that there * is no global modulator identic to another global modulator (this has * been checked at soundfont loading time). So global modulators * are only checked against local modulators number. */ /* Restrict identity check to the number of local modulators */ identity_limit_count = mod_list_count; while(global_mod) { /* 'Identical' global modulators are ignored. * SF2.01 section 9.5.1 * page 69, 'bullet' 3 defines 'identical'. */ for(i = 0; i < identity_limit_count; i++) { if(fluid_mod_test_identity(global_mod, mod_list[i])) { break; } } /* Finally add the new modulator to the list. */ if(i >= identity_limit_count) { /* Although local_mod and global_mod lists was limited to FLUID_NUM_MOD at soundfont loading time, it is possible that local + global modulators exceeds FLUID_NUM_MOD. So, checks if mod_list_count reaches the limit. */ if(mod_list_count >= FLUID_NUM_MOD) { /* mod_list is full, we silently forget this modulator and next global modulators. When mod_list will be added to the voice, a warning will be displayed if the voice list is full. (see fluid_voice_add_mod_local()). */ break; } mod_list[mod_list_count++] = global_mod; } global_mod = global_mod->next; } /* Step 2: global + local modulators are added to the voice using mode. */ /* * mod_list contains local and global modulators, we know that: * - there is no global modulator identic to another global modulator, * - there is no local modulator identic to another local modulator, * So these local/global modulators are only checked against * actual number of voice modulators. */ /* Restrict identity check to the actual number of voice modulators */ /* Actual number of voice modulators : defaults + [instruments] */ identity_limit_count = voice->mod_count; for(i = 0; i < mod_list_count; i++) { mod = mod_list[i]; /* in mode FLUID_VOICE_OVERWRITE disabled instruments modulators CANNOT be skipped. */ /* in mode FLUID_VOICE_ADD disabled preset modulators can be skipped. */ if((mode == FLUID_VOICE_OVERWRITE) || (mod->amount != 0)) { /* Instrument modulators -supersede- existing (default) modulators. SF 2.01 page 69, 'bullet' 6 */ /* Preset modulators -add- to existing instrument modulators. SF2.01 page 70 first bullet on page */ fluid_voice_add_mod_local(voice, mod, mode, identity_limit_count); } } } /* * fluid_defpreset_noteon */ int fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int chan, int key, int vel) { fluid_preset_zone_t *preset_zone, *global_preset_zone; fluid_inst_t *inst; fluid_inst_zone_t *inst_zone, *global_inst_zone; fluid_voice_zone_t *voice_zone; fluid_list_t *list; fluid_voice_t *voice; int i; global_preset_zone = fluid_defpreset_get_global_zone(defpreset); /* run thru all the zones of this preset */ preset_zone = fluid_defpreset_get_zone(defpreset); while(preset_zone != NULL) { /* check if the note falls into the key and velocity range of this preset */ if(fluid_zone_inside_range(&preset_zone->range, key, vel)) { inst = fluid_preset_zone_get_inst(preset_zone); global_inst_zone = fluid_inst_get_global_zone(inst); /* run thru all the zones of this instrument that could start a voice */ for(list = preset_zone->voice_zone; list != NULL; list = fluid_list_next(list)) { voice_zone = fluid_list_get(list); /* check if the instrument zone is ignored and the note falls into the key and velocity range of this instrument zone. An instrument zone must be ignored when its voice is already running played by a legato passage (see fluid_synth_noteon_monopoly_legato()) */ if(fluid_zone_inside_range(&voice_zone->range, key, vel)) { inst_zone = voice_zone->inst_zone; /* this is a good zone. allocate a new synthesis process and initialize it */ voice = fluid_synth_alloc_voice_LOCAL(synth, inst_zone->sample, chan, key, vel, &voice_zone->range); if(voice == NULL) { return FLUID_FAILED; } /* Instrument level, generators */ for(i = 0; i < GEN_LAST; i++) { /* SF 2.01 section 9.4 'bullet' 4: * * A generator in a local instrument zone supersedes a * global instrument zone generator. Both cases supersede * the default generator -> voice_gen_set */ if(inst_zone->gen[i].flags) { fluid_voice_gen_set(voice, i, inst_zone->gen[i].val); } else if((global_inst_zone != NULL) && (global_inst_zone->gen[i].flags)) { fluid_voice_gen_set(voice, i, global_inst_zone->gen[i].val); } else { /* The generator has not been defined in this instrument. * Do nothing, leave it at the default. */ } } /* for all generators */ /* Adds instrument zone modulators (global and local) to the voice.*/ fluid_defpreset_noteon_add_mod_to_voice(voice, /* global instrument modulators */ global_inst_zone ? global_inst_zone->mod : NULL, inst_zone->mod, /* local instrument modulators */ FLUID_VOICE_OVERWRITE); /* mode */ /* Preset level, generators */ for(i = 0; i < GEN_LAST; i++) { /* SF 2.01 section 8.5 page 58: If some generators are encountered at preset level, they should be ignored. However this check is not necessary when the soundfont loader has ignored invalid preset generators. Actually load_pgen()has ignored these invalid preset generators: GEN_STARTADDROFS, GEN_ENDADDROFS, GEN_STARTLOOPADDROFS, GEN_ENDLOOPADDROFS, GEN_STARTADDRCOARSEOFS,GEN_ENDADDRCOARSEOFS, GEN_STARTLOOPADDRCOARSEOFS, GEN_KEYNUM, GEN_VELOCITY, GEN_ENDLOOPADDRCOARSEOFS, GEN_SAMPLEMODE, GEN_EXCLUSIVECLASS,GEN_OVERRIDEROOTKEY */ /* SF 2.01 section 9.4 'bullet' 9: A generator in a * local preset zone supersedes a global preset zone * generator. The effect is -added- to the destination * summing node -> voice_gen_incr */ if(preset_zone->gen[i].flags) { fluid_voice_gen_incr(voice, i, preset_zone->gen[i].val); } else if((global_preset_zone != NULL) && global_preset_zone->gen[i].flags) { fluid_voice_gen_incr(voice, i, global_preset_zone->gen[i].val); } else { /* The generator has not been defined in this preset * Do nothing, leave it unchanged. */ } } /* for all generators */ /* Adds preset zone modulators (global and local) to the voice.*/ fluid_defpreset_noteon_add_mod_to_voice(voice, /* global preset modulators */ global_preset_zone ? global_preset_zone->mod : NULL, preset_zone->mod, /* local preset modulators */ FLUID_VOICE_ADD); /* mode */ /* add the synthesis process to the synthesis loop. */ fluid_synth_start_voice(synth, voice); /* Store the ID of the first voice that was created by this noteon event. * Exclusive class may only terminate older voices. * That avoids killing voices, which have just been created. * (a noteon event can create several voice processes with the same exclusive * class - for example when using stereo samples) */ } } } preset_zone = fluid_preset_zone_next(preset_zone); } return FLUID_OK; } /* * fluid_defpreset_set_global_zone */ int fluid_defpreset_set_global_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone) { defpreset->global_zone = zone; return FLUID_OK; } /* * fluid_defpreset_import_sfont */ int fluid_defpreset_import_sfont(fluid_defpreset_t *defpreset, SFPreset *sfpreset, fluid_defsfont_t *defsfont) { fluid_list_t *p; SFZone *sfzone; fluid_preset_zone_t *zone; int count; char zone_name[256]; if(FLUID_STRLEN(sfpreset->name) > 0) { FLUID_STRCPY(defpreset->name, sfpreset->name); } else { FLUID_SNPRINTF(defpreset->name, sizeof(defpreset->name), "Bank%d,Pre%d", sfpreset->bank, sfpreset->prenum); } defpreset->bank = sfpreset->bank; defpreset->num = sfpreset->prenum; p = sfpreset->zone; count = 0; while(p != NULL) { sfzone = (SFZone *)fluid_list_get(p); FLUID_SNPRINTF(zone_name, sizeof(zone_name), "pz:%s/%d", defpreset->name, count); zone = new_fluid_preset_zone(zone_name); if(zone == NULL) { return FLUID_FAILED; } if(fluid_preset_zone_import_sfont(zone, sfzone, defsfont) != FLUID_OK) { delete_fluid_preset_zone(zone); return FLUID_FAILED; } if((count == 0) && (fluid_preset_zone_get_inst(zone) == NULL)) { fluid_defpreset_set_global_zone(defpreset, zone); } else if(fluid_defpreset_add_zone(defpreset, zone) != FLUID_OK) { return FLUID_FAILED; } p = fluid_list_next(p); count++; } return FLUID_OK; } /* * fluid_defpreset_add_zone */ int fluid_defpreset_add_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone) { if(defpreset->zone == NULL) { zone->next = NULL; defpreset->zone = zone; } else { zone->next = defpreset->zone; defpreset->zone = zone; } return FLUID_OK; } /* * fluid_defpreset_get_zone */ fluid_preset_zone_t * fluid_defpreset_get_zone(fluid_defpreset_t *defpreset) { return defpreset->zone; } /* * fluid_defpreset_get_global_zone */ fluid_preset_zone_t * fluid_defpreset_get_global_zone(fluid_defpreset_t *defpreset) { return defpreset->global_zone; } /*************************************************************** * * PRESET_ZONE */ /* * fluid_preset_zone_next */ fluid_preset_zone_t * fluid_preset_zone_next(fluid_preset_zone_t *zone) { return zone->next; } /* * new_fluid_preset_zone */ fluid_preset_zone_t * new_fluid_preset_zone(char *name) { fluid_preset_zone_t *zone = NULL; zone = FLUID_NEW(fluid_preset_zone_t); if(zone == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } zone->next = NULL; zone->voice_zone = NULL; zone->name = FLUID_STRDUP(name); if(zone->name == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); FLUID_FREE(zone); return NULL; } zone->inst = NULL; zone->range.keylo = 0; zone->range.keyhi = 128; zone->range.vello = 0; zone->range.velhi = 128; zone->range.ignore = FALSE; /* Flag all generators as unused (default, they will be set when they are found * in the sound font). * This also sets the generator values to default, but that is of no concern here.*/ fluid_gen_init(&zone->gen[0], NULL); zone->mod = NULL; /* list of modulators */ return zone; } /* * delete list of modulators. */ void delete_fluid_list_mod(fluid_mod_t *mod) { fluid_mod_t *tmp; while(mod) /* delete the modulators */ { tmp = mod; mod = mod->next; delete_fluid_mod(tmp); } } /* * delete_fluid_preset_zone */ void delete_fluid_preset_zone(fluid_preset_zone_t *zone) { fluid_list_t *list; fluid_return_if_fail(zone != NULL); delete_fluid_list_mod(zone->mod); for(list = zone->voice_zone; list != NULL; list = fluid_list_next(list)) { FLUID_FREE(fluid_list_get(list)); } delete_fluid_list(zone->voice_zone); FLUID_FREE(zone->name); FLUID_FREE(zone); } static int fluid_preset_zone_create_voice_zones(fluid_preset_zone_t *preset_zone) { fluid_inst_zone_t *inst_zone; fluid_sample_t *sample; fluid_voice_zone_t *voice_zone; fluid_zone_range_t *irange; fluid_zone_range_t *prange = &preset_zone->range; fluid_return_val_if_fail(preset_zone->inst != NULL, FLUID_FAILED); inst_zone = fluid_inst_get_zone(preset_zone->inst); while(inst_zone != NULL) { /* We only create voice ranges for zones that could actually start a voice, * i.e. that have a sample and don't point to ROM */ sample = fluid_inst_zone_get_sample(inst_zone); if((sample == NULL) || fluid_sample_in_rom(sample)) { inst_zone = fluid_inst_zone_next(inst_zone); continue; } voice_zone = FLUID_NEW(fluid_voice_zone_t); if(voice_zone == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } voice_zone->inst_zone = inst_zone; irange = &inst_zone->range; voice_zone->range.keylo = (prange->keylo > irange->keylo) ? prange->keylo : irange->keylo; voice_zone->range.keyhi = (prange->keyhi < irange->keyhi) ? prange->keyhi : irange->keyhi; voice_zone->range.vello = (prange->vello > irange->vello) ? prange->vello : irange->vello; voice_zone->range.velhi = (prange->velhi < irange->velhi) ? prange->velhi : irange->velhi; voice_zone->range.ignore = FALSE; preset_zone->voice_zone = fluid_list_append(preset_zone->voice_zone, voice_zone); inst_zone = fluid_inst_zone_next(inst_zone); } return FLUID_OK; } /** * Checks if modulator mod is identic to another modulator in the list * (specs SF 2.0X 7.4, 7.8). * @param mod, modulator list. * @param name, if not NULL, pointer on a string displayed as warning. * @return TRUE if mod is identic to another modulator, FALSE otherwise. */ static int fluid_zone_is_mod_identic(fluid_mod_t *mod, char *name) { fluid_mod_t *next = mod->next; while(next) { /* is mod identic to next ? */ if(fluid_mod_test_identity(mod, next)) { if(name) { FLUID_LOG(FLUID_WARN, "Ignoring identic modulator %s", name); } return TRUE; } next = next->next; } return FALSE; } /** * Limits the number of modulators in a modulator list. * This is appropriate to internal synthesizer modulators tables * which have a fixed size (FLUID_NUM_MOD). * * @param zone_name, zone name * @param list_mod, address of pointer on modulator list. */ static void fluid_limit_mod_list(char *zone_name, fluid_mod_t **list_mod) { int mod_idx = 0; /* modulator index */ fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ while(mod) { if((mod_idx + 1) > FLUID_NUM_MOD) { /* truncation of list_mod */ if(mod_idx) { prev_mod->next = NULL; } else { *list_mod = NULL; } delete_fluid_list_mod(mod); FLUID_LOG(FLUID_WARN, "%s, modulators count limited to %d", zone_name, FLUID_NUM_MOD); break; } mod_idx++; prev_mod = mod; mod = mod->next; } } /** * Checks and remove invalid modulators from a zone modulators list. * - checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1). * - checks identic modulators in the list (specs SF 2.01 7.4, 7.8). * @param zone_name, zone name. * @param list_mod, address of pointer on modulators list. */ static void fluid_zone_check_mod(char *zone_name, fluid_mod_t **list_mod) { fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ int mod_idx = 0; /* modulator index */ while(mod) { char zone_mod_name[256]; fluid_mod_t *next = mod->next; /* prepare modulator name: zonename/#modulator */ FLUID_SNPRINTF(zone_mod_name, sizeof(zone_mod_name), "%s/mod%d", zone_name, mod_idx); /* has mod invalid sources ? */ if(!fluid_mod_check_sources(mod, zone_mod_name) /* or is mod identic to any following modulator ? */ || fluid_zone_is_mod_identic(mod, zone_mod_name)) { /* the modulator is useless so we remove it */ if(prev_mod) { prev_mod->next = next; } else { *list_mod = next; } delete_fluid_mod(mod); /* freeing */ } else { prev_mod = mod; } mod = next; mod_idx++; } /* limits the size of modulators list */ fluid_limit_mod_list(zone_name, list_mod); } /* * fluid_zone_gen_import_sfont * Imports generators from sfzone to gen and range. * @param gen, pointer on destination generators table. * @param range, pointer on destination range generators. * @param sfzone, pointer on soundfont zone generators. */ static void fluid_zone_gen_import_sfont(fluid_gen_t *gen, fluid_zone_range_t *range, SFZone *sfzone) { fluid_list_t *r; SFGen *sfgen; for(r = sfzone->gen; r != NULL;) { sfgen = (SFGen *)fluid_list_get(r); switch(sfgen->id) { case GEN_KEYRANGE: range->keylo = sfgen->amount.range.lo; range->keyhi = sfgen->amount.range.hi; break; case GEN_VELRANGE: range->vello = sfgen->amount.range.lo; range->velhi = sfgen->amount.range.hi; break; case GEN_ATTENUATION: /* EMU8k/10k hardware applies a scale factor to initial attenuation generator values set at * preset and instrument level */ gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword * EMU_ATTENUATION_FACTOR; gen[sfgen->id].flags = GEN_SET; break; default: /* FIXME: some generators have an unsigne word amount value but i don't know which ones */ gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; gen[sfgen->id].flags = GEN_SET; break; } r = fluid_list_next(r); } } /* * fluid_zone_mod_source_import_sfont * Imports source information from sf_source to src and flags. * @param src, pointer on destination modulator source. * @param flags, pointer on destination modulator flags. * @param sf_source, soundfont modulator source. * @return return TRUE if success, FALSE if source type is unknown. */ static int fluid_zone_mod_source_import_sfont(unsigned char *src, unsigned char *flags, unsigned short sf_source) { int type; unsigned char flags_dest; /* destination flags */ /* sources */ *src = sf_source & 127; /* index of source, seven-bit value, SF2.01 section 8.2, page 50 */ /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ flags_dest = 0; if(sf_source & (1 << 7)) { flags_dest |= FLUID_MOD_CC; } else { flags_dest |= FLUID_MOD_GC; } /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ if(sf_source & (1 << 8)) { flags_dest |= FLUID_MOD_NEGATIVE; } else { flags_dest |= FLUID_MOD_POSITIVE; } /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ if(sf_source & (1 << 9)) { flags_dest |= FLUID_MOD_BIPOLAR; } else { flags_dest |= FLUID_MOD_UNIPOLAR; } /* modulator source types: SF2.01 section 8.2.1 page 52 */ type = sf_source >> 10; type &= 63; /* type is a 6-bit value */ if(type == 0) { flags_dest |= FLUID_MOD_LINEAR; } else if(type == 1) { flags_dest |= FLUID_MOD_CONCAVE; } else if(type == 2) { flags_dest |= FLUID_MOD_CONVEX; } else if(type == 3) { flags_dest |= FLUID_MOD_SWITCH; } else { *flags = flags_dest; /* This shouldn't happen - unknown type! */ return FALSE; } *flags = flags_dest; return TRUE; } /* * fluid_zone_mod_import_sfont * Imports modulators from sfzone to modulators list mod. * @param zone_name, zone name. * @param mod, address of pointer on modulators list to return. * @param sfzone, pointer on soundfont zone. * @return FLUID_OK if success, FLUID_FAILED otherwise. */ static int fluid_zone_mod_import_sfont(char *zone_name, fluid_mod_t **mod, SFZone *sfzone) { fluid_list_t *r; int count; /* Import the modulators (only SF2.1 and higher) */ for(count = 0, r = sfzone->mod; r != NULL; count++) { SFMod *mod_src = (SFMod *)fluid_list_get(r); fluid_mod_t *mod_dest = new_fluid_mod(); if(mod_dest == NULL) { return FLUID_FAILED; } mod_dest->next = NULL; /* pointer to next modulator, this is the end of the list now.*/ /* *** Amount *** */ mod_dest->amount = mod_src->amount; /* *** Source *** */ if(!fluid_zone_mod_source_import_sfont(&mod_dest->src1, &mod_dest->flags1, mod_src->src)) { /* This shouldn't happen - unknown type! * Deactivate the modulator by setting the amount to 0. */ mod_dest->amount = 0; } /* Note: When primary source input (src1) is set to General Controller 'No Controller', output will be forced to 0.0 at synthesis time (see fluid_mod_get_value()). That means that the minimum value of the modulator will be always 0.0. We need to force amount value to 0 to ensure a correct evaluation of the minimum value later (see fluid_voice_get_lower_boundary_for_attenuation()). */ if(((mod_dest->flags1 & FLUID_MOD_CC) == FLUID_MOD_GC) && (mod_dest->src1 == FLUID_MOD_NONE)) { mod_dest->amount = 0; } /* *** Dest *** */ mod_dest->dest = mod_src->dest; /* index of controlled generator */ /* *** Amount source *** */ if(!fluid_zone_mod_source_import_sfont(&mod_dest->src2, &mod_dest->flags2, mod_src->amtsrc)) { /* This shouldn't happen - unknown type! * Deactivate the modulator by setting the amount to 0. */ mod_dest->amount = 0; } /* Note: When secondary source input (src2) is set to General Controller 'No Controller', output will be forced to +1.0 at synthesis time (see fluid_mod_get_value()). That means that this source will behave unipolar only. We need to force the unipolar flag to ensure to ensure a correct evaluation of the minimum value later (see fluid_voice_get_lower_boundary_for_attenuation()). */ if(((mod_dest->flags2 & FLUID_MOD_CC) == FLUID_MOD_GC) && (mod_dest->src2 == FLUID_MOD_NONE)) { mod_dest->flags2 &= ~FLUID_MOD_BIPOLAR; } /* *** Transform *** */ /* SF2.01 only uses the 'linear' transform (0). * Deactivate the modulator by setting the amount to 0 in any other case. */ if(mod_src->trans != 0) { mod_dest->amount = 0; } /* Store the new modulator in the zone The order of modulators * will make a difference, at least in an instrument context: The * second modulator overwrites the first one, if they only differ * in amount. */ if(count == 0) { *mod = mod_dest; } else { fluid_mod_t *last_mod = *mod; /* Find the end of the list */ while(last_mod->next != NULL) { last_mod = last_mod->next; } last_mod->next = mod_dest; } r = fluid_list_next(r); } /* foreach modulator */ /* checks and removes invalid modulators in modulators list*/ fluid_zone_check_mod(zone_name, mod); return FLUID_OK; } /* * fluid_preset_zone_import_sfont */ int fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_defsfont_t *defsfont) { /* import the generators */ fluid_zone_gen_import_sfont(zone->gen, &zone->range, sfzone); if((sfzone->instsamp != NULL) && (sfzone->instsamp->data != NULL)) { SFInst *sfinst = sfzone->instsamp->data; zone->inst = find_inst_by_idx(defsfont, sfinst->idx); if(zone->inst == NULL) { zone->inst = fluid_inst_import_sfont(sfinst, defsfont); } if(zone->inst == NULL) { return FLUID_FAILED; } if(fluid_preset_zone_create_voice_zones(zone) == FLUID_FAILED) { return FLUID_FAILED; } } /* Import the modulators (only SF2.1 and higher) */ return fluid_zone_mod_import_sfont(zone->name, &zone->mod, sfzone); } /* * fluid_preset_zone_get_inst */ fluid_inst_t * fluid_preset_zone_get_inst(fluid_preset_zone_t *zone) { return zone->inst; } /*************************************************************** * * INST */ /* * new_fluid_inst */ fluid_inst_t * new_fluid_inst() { fluid_inst_t *inst = FLUID_NEW(fluid_inst_t); if(inst == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } inst->name[0] = 0; inst->global_zone = NULL; inst->zone = NULL; return inst; } /* * delete_fluid_inst */ void delete_fluid_inst(fluid_inst_t *inst) { fluid_inst_zone_t *zone; fluid_return_if_fail(inst != NULL); delete_fluid_inst_zone(inst->global_zone); inst->global_zone = NULL; zone = inst->zone; while(zone != NULL) { inst->zone = zone->next; delete_fluid_inst_zone(zone); zone = inst->zone; } FLUID_FREE(inst); } /* * fluid_inst_set_global_zone */ int fluid_inst_set_global_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone) { inst->global_zone = zone; return FLUID_OK; } /* * fluid_inst_import_sfont */ fluid_inst_t * fluid_inst_import_sfont(SFInst *sfinst, fluid_defsfont_t *defsfont) { fluid_list_t *p; fluid_inst_t *inst; SFZone *sfzone; fluid_inst_zone_t *inst_zone; char zone_name[256]; int count; inst = (fluid_inst_t *) new_fluid_inst(); if(inst == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } inst->source_idx = sfinst->idx; p = sfinst->zone; if(FLUID_STRLEN(sfinst->name) > 0) { FLUID_STRCPY(inst->name, sfinst->name); } else { FLUID_STRCPY(inst->name, ""); } count = 0; while(p != NULL) { sfzone = (SFZone *)fluid_list_get(p); /* instrument zone name */ FLUID_SNPRINTF(zone_name, sizeof(zone_name), "iz:%s/%d", inst->name, count); inst_zone = new_fluid_inst_zone(zone_name); if(inst_zone == NULL) { return NULL; } if(fluid_inst_zone_import_sfont(inst_zone, sfzone, defsfont) != FLUID_OK) { delete_fluid_inst_zone(inst_zone); return NULL; } if((count == 0) && (fluid_inst_zone_get_sample(inst_zone) == NULL)) { fluid_inst_set_global_zone(inst, inst_zone); } else if(fluid_inst_add_zone(inst, inst_zone) != FLUID_OK) { return NULL; } p = fluid_list_next(p); count++; } defsfont->inst = fluid_list_append(defsfont->inst, inst); return inst; } /* * fluid_inst_add_zone */ int fluid_inst_add_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone) { if(inst->zone == NULL) { zone->next = NULL; inst->zone = zone; } else { zone->next = inst->zone; inst->zone = zone; } return FLUID_OK; } /* * fluid_inst_get_zone */ fluid_inst_zone_t * fluid_inst_get_zone(fluid_inst_t *inst) { return inst->zone; } /* * fluid_inst_get_global_zone */ fluid_inst_zone_t * fluid_inst_get_global_zone(fluid_inst_t *inst) { return inst->global_zone; } /*************************************************************** * * INST_ZONE */ /* * new_fluid_inst_zone */ fluid_inst_zone_t * new_fluid_inst_zone(char *name) { fluid_inst_zone_t *zone = NULL; zone = FLUID_NEW(fluid_inst_zone_t); if(zone == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } zone->next = NULL; zone->name = FLUID_STRDUP(name); if(zone->name == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); FLUID_FREE(zone); return NULL; } zone->sample = NULL; zone->range.keylo = 0; zone->range.keyhi = 128; zone->range.vello = 0; zone->range.velhi = 128; zone->range.ignore = FALSE; /* Flag the generators as unused. * This also sets the generator values to default, but they will be overwritten anyway, if used.*/ fluid_gen_init(&zone->gen[0], NULL); zone->mod = NULL; /* list of modulators */ return zone; } /* * delete_fluid_inst_zone */ void delete_fluid_inst_zone(fluid_inst_zone_t *zone) { fluid_return_if_fail(zone != NULL); delete_fluid_list_mod(zone->mod); FLUID_FREE(zone->name); FLUID_FREE(zone); } /* * fluid_inst_zone_next */ fluid_inst_zone_t * fluid_inst_zone_next(fluid_inst_zone_t *zone) { return zone->next; } /* * fluid_inst_zone_import_sfont */ int fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, SFZone *sfzone, fluid_defsfont_t *defsfont) { /* import the generators */ fluid_zone_gen_import_sfont(inst_zone->gen, &inst_zone->range, sfzone); /* FIXME */ /* if (zone->gen[GEN_EXCLUSIVECLASS].flags == GEN_SET) { */ /* FLUID_LOG(FLUID_DBG, "ExclusiveClass=%d\n", (int) zone->gen[GEN_EXCLUSIVECLASS].val); */ /* } */ /* fixup sample pointer */ if((sfzone->instsamp != NULL) && (sfzone->instsamp->data != NULL)) { inst_zone->sample = ((SFSample *)(sfzone->instsamp->data))->fluid_sample; } /* Import the modulators (only SF2.1 and higher) */ return fluid_zone_mod_import_sfont(inst_zone->name, &inst_zone->mod, sfzone); } /* * fluid_inst_zone_get_sample */ fluid_sample_t * fluid_inst_zone_get_sample(fluid_inst_zone_t *zone) { return zone->sample; } int fluid_zone_inside_range(fluid_zone_range_t *range, int key, int vel) { /* ignoreInstrumentZone is set in mono legato playing */ int ignore_zone = range->ignore; /* Reset the 'ignore' request */ range->ignore = FALSE; return !ignore_zone && ((range->keylo <= key) && (range->keyhi >= key) && (range->vello <= vel) && (range->velhi >= vel)); } /*************************************************************** * * SAMPLE */ /* * fluid_sample_in_rom */ int fluid_sample_in_rom(fluid_sample_t *sample) { return (sample->sampletype & FLUID_SAMPLETYPE_ROM); } /* * fluid_sample_import_sfont */ int fluid_sample_import_sfont(fluid_sample_t *sample, SFSample *sfsample, fluid_defsfont_t *defsfont) { FLUID_STRCPY(sample->name, sfsample->name); sample->source_start = sfsample->start; sample->source_end = (sfsample->end > 0) ? sfsample->end - 1 : 0; /* marks last sample, contrary to SF spec. */ sample->source_loopstart = sfsample->loopstart; sample->source_loopend = sfsample->loopend; sample->start = sample->source_start; sample->end = sample->source_end; sample->loopstart = sample->source_loopstart; sample->loopend = sample->source_loopend; sample->samplerate = sfsample->samplerate; sample->origpitch = sfsample->origpitch; sample->pitchadj = sfsample->pitchadj; sample->sampletype = sfsample->sampletype; if(defsfont->dynamic_samples) { sample->notify = dynamic_samples_sample_notify; } if(fluid_sample_validate(sample, defsfont->samplesize) == FLUID_FAILED) { return FLUID_FAILED; } return FLUID_OK; } /* Called if a sample is no longer used by a voice. Used by dynamic sample loading * to unload a sample that is not used by any loaded presets anymore but couldn't * be unloaded straight away because it was still in use by a voice. */ static int dynamic_samples_sample_notify(fluid_sample_t *sample, int reason) { if(reason == FLUID_SAMPLE_DONE && sample->preset_count == 0) { unload_sample(sample); } return FLUID_OK; } /* Called if a preset has been selected for or unselected from a channel. Used by * dynamic sample loading to load and unload samples on demand. */ static int dynamic_samples_preset_notify(fluid_preset_t *preset, int reason, int chan) { fluid_defsfont_t *defsfont; if(reason == FLUID_PRESET_SELECTED) { FLUID_LOG(FLUID_DBG, "Selected preset '%s' on channel %d", fluid_preset_get_name(preset), chan); defsfont = fluid_sfont_get_data(preset->sfont); load_preset_samples(defsfont, preset); } else if(reason == FLUID_PRESET_UNSELECTED) { FLUID_LOG(FLUID_DBG, "Deselected preset '%s' from channel %d", fluid_preset_get_name(preset), chan); defsfont = fluid_sfont_get_data(preset->sfont); unload_preset_samples(defsfont, preset); } return FLUID_OK; } /* Walk through all samples used by the passed in preset and make sure that the * sample data is loaded for each sample. Used by dynamic sample loading. */ static int load_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset) { fluid_defpreset_t *defpreset; fluid_preset_zone_t *preset_zone; fluid_inst_t *inst; fluid_inst_zone_t *inst_zone; fluid_sample_t *sample; SFData *sffile = NULL; defpreset = fluid_preset_get_data(preset); preset_zone = fluid_defpreset_get_zone(defpreset); while(preset_zone != NULL) { inst = fluid_preset_zone_get_inst(preset_zone); inst_zone = fluid_inst_get_zone(inst); while(inst_zone != NULL) { sample = fluid_inst_zone_get_sample(inst_zone); if((sample != NULL) && (sample->start != sample->end)) { sample->preset_count++; /* If this is the first time this sample has been selected, * load the sampledata */ if(sample->preset_count == 1) { /* Make sure we have an open Soundfont file. Do this here * to avoid having to open the file if no loading is necessary * for a preset */ if(sffile == NULL) { sffile = fluid_sffile_open(defsfont->filename, defsfont->fcbs); if(sffile == NULL) { FLUID_LOG(FLUID_ERR, "Unable to open Soundfont file"); return FLUID_FAILED; } } if(fluid_defsfont_load_sampledata(defsfont, sffile, sample) == FLUID_OK) { fluid_sample_sanitize_loop(sample, (sample->end + 1) * sizeof(short)); fluid_voice_optimize_sample(sample); } else { FLUID_LOG(FLUID_ERR, "Unable to load sample '%s', disabling", sample->name); sample->start = sample->end = 0; } } } inst_zone = fluid_inst_zone_next(inst_zone); } preset_zone = fluid_preset_zone_next(preset_zone); } if(sffile != NULL) { fluid_sffile_close(sffile); } return FLUID_OK; } /* Walk through all samples used by the passed in preset and unload the sample data * of each sample that is not used by any selected preset anymore. Used by dynamic * sample loading. */ static int unload_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset) { fluid_defpreset_t *defpreset; fluid_preset_zone_t *preset_zone; fluid_inst_t *inst; fluid_inst_zone_t *inst_zone; fluid_sample_t *sample; defpreset = fluid_preset_get_data(preset); preset_zone = fluid_defpreset_get_zone(defpreset); while(preset_zone != NULL) { inst = fluid_preset_zone_get_inst(preset_zone); inst_zone = fluid_inst_get_zone(inst); while(inst_zone != NULL) { sample = fluid_inst_zone_get_sample(inst_zone); if((sample != NULL) && (sample->preset_count > 0)) { sample->preset_count--; /* If the sample is not used by any preset or used by a * sounding voice, unload it from the sample cache. If it's * still in use by a voice, dynamic_samples_sample_notify will * take care of unloading the sample as soon as the voice is * finished with it (but only on the next API call). */ if(sample->preset_count == 0 && sample->refcount == 0) { unload_sample(sample); } } inst_zone = fluid_inst_zone_next(inst_zone); } preset_zone = fluid_preset_zone_next(preset_zone); } return FLUID_OK; } /* Unload an unused sample from the samplecache */ static void unload_sample(fluid_sample_t *sample) { fluid_return_if_fail(sample != NULL); fluid_return_if_fail(sample->data != NULL); fluid_return_if_fail(sample->preset_count == 0); fluid_return_if_fail(sample->refcount == 0); FLUID_LOG(FLUID_DBG, "Unloading sample '%s'", sample->name); if(fluid_samplecache_unload(sample->data) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Unable to unload sample '%s'", sample->name); } else { sample->data = NULL; sample->data24 = NULL; } } static fluid_inst_t *find_inst_by_idx(fluid_defsfont_t *defsfont, int idx) { fluid_list_t *list; fluid_inst_t *inst; for(list = defsfont->inst; list != NULL; list = fluid_list_next(list)) { inst = fluid_list_get(list); if(inst->source_idx == idx) { return inst; } } return NULL; } fluidsynth-2.1.1/src/sfloader/fluid_defsfont.h000066400000000000000000000212241362231004000214260ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * SoundFont loading code borrowed from Smurf SoundFont Editor by Josh Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_DEFSFONT_H #define _FLUID_DEFSFONT_H #include "fluidsynth.h" #include "fluidsynth_priv.h" #include "fluid_sffile.h" #include "fluid_list.h" #include "fluid_mod.h" #include "fluid_gen.h" /*-----------------------------------sfont.h----------------------------*/ #define SF_SAMPMODES_LOOP 1 #define SF_SAMPMODES_UNROLL 2 #define SF_MIN_SAMPLERATE 400 #define SF_MAX_SAMPLERATE 50000 #define SF_MIN_SAMPLE_LENGTH 32 /*************************************************************** * * FORWARD DECLARATIONS */ typedef struct _fluid_defsfont_t fluid_defsfont_t; typedef struct _fluid_defpreset_t fluid_defpreset_t; typedef struct _fluid_preset_zone_t fluid_preset_zone_t; typedef struct _fluid_inst_t fluid_inst_t; typedef struct _fluid_inst_zone_t fluid_inst_zone_t; /**< Soundfont Instrument Zone */ typedef struct _fluid_voice_zone_t fluid_voice_zone_t; /* defines the velocity and key range for a zone */ struct _fluid_zone_range_t { int keylo; int keyhi; int vello; int velhi; unsigned char ignore; /* set to TRUE for legato playing to ignore this range zone */ }; /* Stored on a preset zone to keep track of the inst zones that could start a voice * and their combined preset zone/instument zone ranges */ struct _fluid_voice_zone_t { fluid_inst_zone_t *inst_zone; fluid_zone_range_t range; }; /* Public interface */ fluid_sfont_t *fluid_defsfloader_load(fluid_sfloader_t *loader, const char *filename); int fluid_defsfont_sfont_delete(fluid_sfont_t *sfont); const char *fluid_defsfont_sfont_get_name(fluid_sfont_t *sfont); fluid_preset_t *fluid_defsfont_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum); void fluid_defsfont_sfont_iteration_start(fluid_sfont_t *sfont); fluid_preset_t *fluid_defsfont_sfont_iteration_next(fluid_sfont_t *sfont); void fluid_defpreset_preset_delete(fluid_preset_t *preset); const char *fluid_defpreset_preset_get_name(fluid_preset_t *preset); int fluid_defpreset_preset_get_banknum(fluid_preset_t *preset); int fluid_defpreset_preset_get_num(fluid_preset_t *preset); int fluid_defpreset_preset_noteon(fluid_preset_t *preset, fluid_synth_t *synth, int chan, int key, int vel); int fluid_zone_inside_range(fluid_zone_range_t *zone_range, int key, int vel); /* * fluid_defsfont_t */ struct _fluid_defsfont_t { const fluid_file_callbacks_t *fcbs; /* the file callbacks used to load this Soundfont */ char *filename; /* the filename of this soundfont */ unsigned int samplepos; /* the position in the file at which the sample data starts */ unsigned int samplesize; /* the size of the sample data in bytes */ short *sampledata; /* the sample data, loaded in ram */ unsigned int sample24pos; /* position within sffd of the sm24 chunk, set to zero if no 24 bit sample support */ unsigned int sample24size; /* length within sffd of the sm24 chunk */ char *sample24data; /* if not NULL, the least significant byte of the 24bit sample data, loaded in ram */ fluid_sfont_t *sfont; /* pointer to parent sfont */ fluid_list_t *sample; /* the samples in this soundfont */ fluid_list_t *preset; /* the presets of this soundfont */ fluid_list_t *inst; /* the instruments of this soundfont */ int mlock; /* Should we try memlock (avoid swapping)? */ int dynamic_samples; /* Enables dynamic sample loading if set */ fluid_list_t *preset_iter_cur; /* the current preset in the iteration */ }; fluid_defsfont_t *new_fluid_defsfont(fluid_settings_t *settings); int delete_fluid_defsfont(fluid_defsfont_t *defsfont); int fluid_defsfont_load(fluid_defsfont_t *defsfont, const fluid_file_callbacks_t *file_callbacks, const char *file); const char *fluid_defsfont_get_name(fluid_defsfont_t *defsfont); fluid_preset_t *fluid_defsfont_get_preset(fluid_defsfont_t *defsfont, int bank, int prenum); void fluid_defsfont_iteration_start(fluid_defsfont_t *defsfont); fluid_preset_t *fluid_defsfont_iteration_next(fluid_defsfont_t *defsfont); int fluid_defsfont_load_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata, fluid_sample_t *sample); int fluid_defsfont_load_all_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata); int fluid_defsfont_add_sample(fluid_defsfont_t *defsfont, fluid_sample_t *sample); int fluid_defsfont_add_preset(fluid_defsfont_t *defsfont, fluid_defpreset_t *defpreset); /* * fluid_preset_t */ struct _fluid_defpreset_t { fluid_defpreset_t *next; char name[21]; /* the name of the preset */ unsigned int bank; /* the bank number */ unsigned int num; /* the preset number */ fluid_preset_zone_t *global_zone; /* the global zone of the preset */ fluid_preset_zone_t *zone; /* the chained list of preset zones */ }; fluid_defpreset_t *new_fluid_defpreset(void); void delete_fluid_defpreset(fluid_defpreset_t *defpreset); fluid_defpreset_t *fluid_defpreset_next(fluid_defpreset_t *defpreset); int fluid_defpreset_import_sfont(fluid_defpreset_t *defpreset, SFPreset *sfpreset, fluid_defsfont_t *defsfont); int fluid_defpreset_set_global_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone); int fluid_defpreset_add_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone); fluid_preset_zone_t *fluid_defpreset_get_zone(fluid_defpreset_t *defpreset); fluid_preset_zone_t *fluid_defpreset_get_global_zone(fluid_defpreset_t *defpreset); int fluid_defpreset_get_banknum(fluid_defpreset_t *defpreset); int fluid_defpreset_get_num(fluid_defpreset_t *defpreset); const char *fluid_defpreset_get_name(fluid_defpreset_t *defpreset); int fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int chan, int key, int vel); /* * fluid_preset_zone */ struct _fluid_preset_zone_t { fluid_preset_zone_t *next; char *name; fluid_inst_t *inst; fluid_list_t *voice_zone; fluid_zone_range_t range; fluid_gen_t gen[GEN_LAST]; fluid_mod_t *mod; /* List of modulators */ }; fluid_preset_zone_t *new_fluid_preset_zone(char *name); void delete_fluid_list_mod(fluid_mod_t *mod); void delete_fluid_preset_zone(fluid_preset_zone_t *zone); fluid_preset_zone_t *fluid_preset_zone_next(fluid_preset_zone_t *zone); int fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_defsfont_t *defssfont); fluid_inst_t *fluid_preset_zone_get_inst(fluid_preset_zone_t *zone); /* * fluid_inst_t */ struct _fluid_inst_t { char name[21]; int source_idx; /* Index of instrument in source Soundfont */ fluid_inst_zone_t *global_zone; fluid_inst_zone_t *zone; }; fluid_inst_t *new_fluid_inst(void); fluid_inst_t *fluid_inst_import_sfont(SFInst *sfinst, fluid_defsfont_t *defsfont); void delete_fluid_inst(fluid_inst_t *inst); int fluid_inst_set_global_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone); int fluid_inst_add_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone); fluid_inst_zone_t *fluid_inst_get_zone(fluid_inst_t *inst); fluid_inst_zone_t *fluid_inst_get_global_zone(fluid_inst_t *inst); /* * fluid_inst_zone_t */ struct _fluid_inst_zone_t { fluid_inst_zone_t *next; char *name; fluid_sample_t *sample; fluid_zone_range_t range; fluid_gen_t gen[GEN_LAST]; fluid_mod_t *mod; /* List of modulators */ }; fluid_inst_zone_t *new_fluid_inst_zone(char *name); void delete_fluid_inst_zone(fluid_inst_zone_t *zone); fluid_inst_zone_t *fluid_inst_zone_next(fluid_inst_zone_t *zone); int fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, SFZone *sfzone, fluid_defsfont_t *defsfont); fluid_sample_t *fluid_inst_zone_get_sample(fluid_inst_zone_t *zone); int fluid_sample_import_sfont(fluid_sample_t *sample, SFSample *sfsample, fluid_defsfont_t *defsfont); int fluid_sample_in_rom(fluid_sample_t *sample); #endif /* _FLUID_SFONT_H */ fluidsynth-2.1.1/src/sfloader/fluid_instpatch.c000066400000000000000000000476341362231004000216230ustar00rootroot00000000000000 #include "fluid_instpatch.h" #include "fluid_list.h" #include "fluid_sfont.h" #include "fluid_sys.h" #include typedef struct _fluid_instpatch_font_t { char name[256]; IpatchDLS2 *dls; fluid_list_t *preset_list; /* the presets of this soundfont */ fluid_list_t *preset_iter_cur; /* the current preset in the iteration */ } fluid_instpatch_font_t; typedef struct _fluid_instpatch_preset_t { fluid_instpatch_font_t *parent_sfont; IpatchSF2VoiceCache *cache; /* pointer to name of the preset, duplicated from item, allocated by glib */ char *name; int bank; int prog; } fluid_instpatch_preset_t; // private struct for storing additional data for each instpatch voice typedef struct _instpatch_voice_user_data { /* pointer to the sample store that holds the PCM */ IpatchSampleStoreCache *sample_store; /* pointer to a preallocated fluid_sample_t that we can use during noteon for this voice */ fluid_sample_t *sample; } fluid_instpatch_voice_user_data_t; /* max voices per instrument (voices exceeding this will not sound) */ enum { MAX_INST_VOICES = 128, }; void fluid_instpatch_init(void) { ipatch_init(); } static int delete_fluid_instpatch(fluid_instpatch_font_t *pfont); static const char *fluid_instpatch_sfont_get_name(fluid_sfont_t *sfont); static fluid_preset_t *fluid_instpatch_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum); /* sfloader callback to get the name of a preset */ static const char * fluid_instpatch_preset_get_name(fluid_preset_t *preset) { fluid_instpatch_preset_t *preset_data = fluid_preset_get_data(preset); return preset_data->name; } /* sfloader callback to get the bank number of a preset */ static int fluid_instpatch_preset_get_banknum(fluid_preset_t *preset) { fluid_instpatch_preset_t *preset_data = fluid_preset_get_data(preset); return preset_data->bank; } /* sfloader callback to get the preset number of a preset */ static int fluid_instpatch_preset_get_num(fluid_preset_t *preset) { fluid_instpatch_preset_t *preset_data = fluid_preset_get_data(preset); return preset_data->prog; } static void fluid_instpatch_iteration_start(fluid_sfont_t *sfont) { fluid_instpatch_font_t *pfont = fluid_sfont_get_data(sfont); pfont->preset_iter_cur = pfont->preset_list; } static fluid_preset_t *fluid_instpatch_iteration_next(fluid_sfont_t *sfont) { fluid_instpatch_font_t *pfont = fluid_sfont_get_data(sfont); fluid_preset_t *preset = fluid_list_get(pfont->preset_iter_cur); pfont->preset_iter_cur = fluid_list_next(pfont->preset_iter_cur); return preset; } /* sfloader callback for a noteon event */ static int fluid_instpatch_preset_noteon(fluid_preset_t *preset, fluid_synth_t *synth, int chan, int key, int vel) { /* voice index array */ guint16 voice_indices[MAX_INST_VOICES]; int sel_values[IPATCH_SF2_VOICE_CACHE_MAX_SEL_VALUES]; fluid_mod_t *fmod = g_alloca(fluid_mod_sizeof()); fluid_voice_t *flvoice; fluid_instpatch_preset_t *preset_data = fluid_preset_get_data(preset); int i, voice_count, voice_num, ret = FLUID_FAILED; GSList *p; /* lookup the voice cache that we've created on loading */ IpatchSF2VoiceCache *cache = preset_data->cache; /* loading and caching the instrument could have failed though */ if(FLUID_UNLIKELY(cache == NULL)) { return FLUID_FAILED; } g_object_ref(cache); for(i = 0; i < cache->sel_count; i++) { IpatchSF2VoiceSelInfo *sel_info = &cache->sel_info[i]; switch(sel_info->type) { case IPATCH_SF2_VOICE_SEL_NOTE: sel_values[i] = key; break; case IPATCH_SF2_VOICE_SEL_VELOCITY: sel_values[i] = vel; break; default: /* match any; NOTE: according to documentation this should be IPATCH_SF2_VOICE_SEL_WILDCARD */ sel_values[i] = -1; break; } } voice_count = ipatch_sf2_voice_cache_select(cache, sel_values, voice_indices, MAX_INST_VOICES); /* loop over matching voice indexes */ for(voice_num = 0; voice_num < voice_count; voice_num++) { IpatchSF2GenArray *gen_array; fluid_sample_t *fsample; IpatchSF2Voice *voice = IPATCH_SF2_VOICE_CACHE_GET_VOICE(cache, voice_indices[voice_num]); if(!voice->sample_store) { /* For ROM and other non-readable samples */ continue; } fsample = ((fluid_instpatch_voice_user_data_t *)voice->user_data)->sample; ret = fluid_sample_set_sound_data(fsample, ipatch_sample_store_cache_get_location(IPATCH_SAMPLE_STORE_CACHE(voice->sample_store)), NULL, voice->sample_size, voice->rate, FALSE ); if(FLUID_UNLIKELY(ret == FLUID_FAILED)) { FLUID_LOG(FLUID_ERR, "fluid_sample_set_sound_data() failed"); goto error_rec; } ret = fluid_sample_set_loop(fsample, voice->loop_start, voice->loop_end); if(FLUID_UNLIKELY(ret == FLUID_FAILED)) { FLUID_LOG(FLUID_ERR, "fluid_sample_set_loop() failed"); goto error_rec; } ret = fluid_sample_set_pitch(fsample, voice->root_note, voice->fine_tune); if(FLUID_UNLIKELY(ret == FLUID_FAILED)) { FLUID_LOG(FLUID_ERR, "fluid_sample_set_pitch() failed"); goto error_rec; } /* allocate the FluidSynth voice */ flvoice = fluid_synth_alloc_voice(synth, fsample, chan, key, vel); if(flvoice == NULL) { ret = FLUID_FAILED; goto error_rec; } /* set only those generator parameters that are set */ gen_array = &voice->gen_array; for(i = 0; i < IPATCH_SF2_GEN_COUNT; i++) { if(IPATCH_SF2_GEN_ARRAY_TEST_FLAG(gen_array, i)) { fluid_voice_gen_set(flvoice, i, (float)(gen_array->values[i].sword)); } } p = voice->mod_list; while(p) { static const unsigned int mod_mask = (IPATCH_SF2_MOD_MASK_DIRECTION | IPATCH_SF2_MOD_MASK_POLARITY | IPATCH_SF2_MOD_MASK_TYPE); IpatchSF2Mod *mod = p->data; fluid_mod_set_dest(fmod, mod->dest); fluid_mod_set_source1(fmod, mod->src & IPATCH_SF2_MOD_MASK_CONTROL, ((mod->src & mod_mask) >> IPATCH_SF2_MOD_SHIFT_DIRECTION) | ((mod->src & IPATCH_SF2_MOD_MASK_CC) ? FLUID_MOD_CC : 0)); fluid_mod_set_source2(fmod, mod->amtsrc & IPATCH_SF2_MOD_MASK_CONTROL, ((mod->amtsrc & mod_mask) >> IPATCH_SF2_MOD_SHIFT_DIRECTION) | ((mod->amtsrc & IPATCH_SF2_MOD_MASK_CC) ? FLUID_MOD_CC : 0)); fluid_mod_set_amount(fmod, mod->amount); fluid_voice_add_mod(flvoice, fmod, FLUID_VOICE_OVERWRITE); p = p->next; } fluid_synth_start_voice(synth, flvoice); /* sample store reference taken over by fsample structure */ } ret = FLUID_OK; error_rec: g_object_unref(cache); return ret; } /* sfloader callback to get a patch file name */ static const char * fluid_instpatch_sfont_get_name(fluid_sfont_t *sfont) { fluid_instpatch_font_t *sfont_data = fluid_sfont_get_data(sfont); return sfont_data->name; } static void delete_fluid_instpatch_preset(fluid_instpatch_preset_t *preset_data) { fluid_return_if_fail(preset_data != NULL); /* -- remove voice cache reference */ g_object_unref(preset_data->cache); g_free(preset_data->name); FLUID_FREE(preset_data); } static void fluid_instpatch_preset_free(fluid_preset_t *preset) { fluid_return_if_fail(preset != NULL); delete_fluid_instpatch_preset(fluid_preset_get_data(preset)); delete_fluid_preset(preset); } /* sfloader callback to get a preset (instrument) by bank and preset number */ static fluid_preset_t * fluid_instpatch_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum) { fluid_instpatch_font_t *sfont_data = fluid_sfont_get_data(sfont); fluid_preset_t *preset; fluid_list_t *list; for(list = sfont_data->preset_list; list != NULL; list = fluid_list_next(list)) { preset = (fluid_preset_t *)fluid_list_get(list); if((fluid_preset_get_banknum(preset) == bank) && (fluid_preset_get_num(preset) == prenum)) { return preset; } } return NULL; } static fluid_instpatch_voice_user_data_t *new_fluid_instpatch_voice_user_data(IpatchSampleStoreCache *sample_store) { fluid_instpatch_voice_user_data_t *data = FLUID_NEW(fluid_instpatch_voice_user_data_t); fluid_sample_t *sample = new_fluid_sample(); if(data == NULL || sample == NULL) { FLUID_FREE(data); delete_fluid_sample(sample); FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } data->sample = sample; /* Keep sample store cached by doing a dummy open */ ipatch_sample_store_cache_open(sample_store); data->sample_store = sample_store; return data; } static void fluid_instpatch_on_voice_user_data_destroy(gpointer user_data) { fluid_instpatch_voice_user_data_t *data = user_data; delete_fluid_sample(data->sample); ipatch_sample_store_cache_close(data->sample_store); FLUID_FREE(data); } static IpatchSF2VoiceCache *convert_dls_to_sf2_instrument(fluid_instpatch_font_t *patchfont, IpatchDLS2Inst *item, const char **err) { static const char no_conv[] = "Unable to find a voice cache converter for this type"; static const char conv_fail[] = "Failed to convert DLS inst to SF2 voices"; static const char cache_fail[] = "Failed to cache DLS inst to SF2 voices"; static const char oom[] = "Out of memory"; IpatchConverter *conv; IpatchSF2VoiceCache *cache; int i, count; /* create SF2 voice cache converter */ conv = ipatch_create_converter(G_OBJECT_TYPE(item), IPATCH_TYPE_SF2_VOICE_CACHE); /* no SF2 voice cache converter for this item type? */ if(conv == NULL) { *err = no_conv; return NULL; } cache = ipatch_sf2_voice_cache_new(NULL, 0); if(cache == NULL) { *err = oom; g_object_unref(conv); return NULL; } /* do not use the default modulator list of libinstpatch, we manage our own list of default modulators */ ipatch_sf2_voice_cache_set_default_mods(cache, NULL); ipatch_converter_add_input(conv, G_OBJECT(item)); ipatch_converter_add_output(conv, G_OBJECT(cache)); if(!ipatch_converter_convert(conv, NULL)) { *err = conv_fail; g_object_unref(cache); g_object_unref(conv); return NULL; } g_object_unref (conv); conv = NULL; /* Use voice->user_data to close open cached stores */ cache->voice_user_data_destroy = fluid_instpatch_on_voice_user_data_destroy; /* loop over voices and load sample data into RAM */ count = cache->voices->len; for(i = 0; i < count; i++) { IpatchSF2Voice *voice = &g_array_index(cache->voices, IpatchSF2Voice, i); if(!ipatch_sf2_voice_cache_sample_data(voice, NULL)) { *err = cache_fail; g_object_unref(cache); return NULL; } if((voice->user_data = new_fluid_instpatch_voice_user_data(IPATCH_SAMPLE_STORE_CACHE(voice->sample_store))) == NULL) { *err = oom; g_object_unref(cache); return NULL; } } /* !! caller takes over cache reference */ return cache; } fluid_instpatch_font_t *new_fluid_instpatch(fluid_sfont_t *sfont, const fluid_file_callbacks_t *fcbs, const char *filename) { fluid_instpatch_font_t *patchfont = NULL; GError *err = NULL; IpatchDLSReader *reader = NULL; IpatchDLSFile *file = NULL; IpatchFileHandle *handle = NULL; fluid_return_val_if_fail(filename != NULL, NULL); if((patchfont = FLUID_NEW(fluid_instpatch_font_t)) == NULL) { return NULL; } FLUID_MEMSET(patchfont, 0, sizeof(*patchfont)); FLUID_STRNCPY(&patchfont->name[0], filename, sizeof(patchfont->name)); /* open a file, we get a reference */ if((file = ipatch_dls_file_new()) == NULL) { FLUID_FREE(patchfont); return NULL; } /* ipatch_file_open() references the file again */ if((handle = ipatch_file_open(IPATCH_FILE(file), filename, "r", &err)) == NULL) { FLUID_LOG(FLUID_ERR, "ipatch_file_open() failed with error: '%s'", ipatch_gerror_message(err)); g_object_unref(file); FLUID_FREE(patchfont); return NULL; } /* get rid of the reference we own, we dont need it any longer */ g_object_unref(file); file = NULL; /* open a reader, this gives us a reference */ if((reader = ipatch_dls_reader_new(handle)) == NULL) { ipatch_file_close(handle); FLUID_FREE(patchfont); return NULL; } patchfont->dls = ipatch_dls_reader_load(reader, &err); /* unref the reader directly afterwards, not needed any longer */ g_object_unref(reader); reader = NULL; if(patchfont->dls == NULL) { FLUID_LOG(FLUID_ERR, "ipatch_dls_reader_new() failed with error: '%s'", ipatch_gerror_message(err)); // reader has already been unrefed, i.e. no need to call ipatch_file_close() FLUID_FREE(patchfont); return NULL; } else { /* at this point everything is owned by the IpatchDLS2*, no need for custom cleanups any longer */ IpatchIter iter; IpatchDLS2Inst *inst; gboolean success = ipatch_container_init_iter(IPATCH_CONTAINER(patchfont->dls), &iter, IPATCH_TYPE_DLS2_INST); if(success == FALSE) { goto bad_luck; } inst = ipatch_dls2_inst_first(&iter); if(inst == NULL) { FLUID_LOG(FLUID_ERR, "A soundfont file was accepted by libinstpatch, but it doesn't contain a single instrument. Dropping the whole file."); goto bad_luck; } /* loop over instruments, convert to sf2 voices, create fluid_samples, cache all presets in a list */ do { fluid_preset_t *preset; IpatchSF2VoiceCache *cache; int bank, prog; const char *err = NULL; ipatch_dls2_inst_get_midi_locale(inst, &bank, &prog); if((cache = convert_dls_to_sf2_instrument(patchfont, inst, &err)) == NULL) { FLUID_LOG(FLUID_WARN, "Unable to use DLS instrument bank %d , prog %d : %s.", bank, prog, err); } else { int is_percussion = (ipatch_item_get_flags(inst) & IPATCH_DLS2_INST_PERCUSSION) != 0; fluid_instpatch_preset_t *preset_data = FLUID_NEW(fluid_instpatch_preset_t); if(preset_data == NULL) { g_object_unref(inst); g_object_unref(cache); FLUID_LOG(FLUID_ERR, "Out of memory"); goto bad_luck; } FLUID_MEMSET(preset_data, 0, sizeof(*preset_data)); preset_data->parent_sfont = patchfont; preset_data->cache = cache; /* save name, bank and preset for quick lookup */ preset_data->bank = is_percussion * 128 + bank; preset_data->prog = prog; g_object_get(inst, "name", &preset_data->name, NULL); preset = new_fluid_preset(sfont, fluid_instpatch_preset_get_name, fluid_instpatch_preset_get_banknum, fluid_instpatch_preset_get_num, fluid_instpatch_preset_noteon, fluid_instpatch_preset_free); if(preset == NULL) { delete_fluid_instpatch_preset(preset_data); FLUID_LOG(FLUID_ERR, "Out of memory"); goto bad_luck; } else { fluid_preset_set_data(preset, preset_data); patchfont->preset_list = fluid_list_append(patchfont->preset_list, preset); } } inst = ipatch_dls2_inst_next(&iter); } while(inst); return patchfont; } bad_luck: delete_fluid_instpatch(patchfont); return NULL; } static int delete_fluid_instpatch(fluid_instpatch_font_t *pfont) { guint16 voice_indices[MAX_INST_VOICES]; int sel_values[IPATCH_SF2_VOICE_CACHE_MAX_SEL_VALUES]; fluid_list_t *list; fluid_return_val_if_fail(pfont != NULL, FLUID_OK); /* loop through all fluid samples and return error if any sample is currently in use for rendering */ for(list = pfont->preset_list; list; list = fluid_list_next(list)) { fluid_instpatch_preset_t *preset_data = fluid_preset_get_data((fluid_preset_t *)fluid_list_get(list)); int i, voice_count; /* lookup the voice cache that we've created on loading */ IpatchSF2VoiceCache *cache = preset_data->cache; if(cache == NULL) { continue; } g_object_ref(cache); for(i = 0; i < cache->sel_count; i++) { sel_values[i] = -1; } voice_count = ipatch_sf2_voice_cache_select(cache, sel_values, voice_indices, MAX_INST_VOICES); for(i = 0; i < voice_count; i++) { IpatchSF2Voice *voice = IPATCH_SF2_VOICE_CACHE_GET_VOICE(cache, voice_indices[i]); fluid_sample_t *fsample = ((fluid_instpatch_voice_user_data_t *)voice->user_data)->sample; if(fsample->refcount != 0) { g_object_unref(cache); return FLUID_FAILED; } } g_object_unref(cache); } for(list = pfont->preset_list; list; list = fluid_list_next(list)) { fluid_preset_delete_internal((fluid_preset_t *)fluid_list_get(list)); } delete_fluid_list(pfont->preset_list); // also unrefs IpatchDLSFile and IpatchFileHandle g_object_unref(pfont->dls); FLUID_FREE(pfont); return FLUID_OK; } static int fluid_instpatch_sfont_delete(fluid_sfont_t *sfont) { int ret; fluid_return_val_if_fail(sfont != NULL, -1); if((ret = delete_fluid_instpatch(fluid_sfont_get_data(sfont))) == FLUID_OK) { delete_fluid_sfont(sfont); } return ret; } static fluid_sfont_t *fluid_instpatch_loader_load(fluid_sfloader_t *loader, const char *filename) { fluid_instpatch_font_t *patchfont = NULL; fluid_sfont_t *sfont = NULL; sfont = new_fluid_sfont(fluid_instpatch_sfont_get_name, fluid_instpatch_sfont_get_preset, fluid_instpatch_iteration_start, fluid_instpatch_iteration_next, fluid_instpatch_sfont_delete); if(sfont == NULL) { return NULL; } if((patchfont = new_fluid_instpatch(sfont, &loader->file_callbacks, filename)) == NULL) { delete_fluid_sfont(sfont); return NULL; } fluid_sfont_set_data(sfont, patchfont); return sfont; } fluid_sfloader_t *new_fluid_instpatch_loader(fluid_settings_t *settings) { fluid_sfloader_t *loader; fluid_return_val_if_fail(settings != NULL, NULL); loader = new_fluid_sfloader(fluid_instpatch_loader_load, delete_fluid_sfloader); if(loader == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } fluid_sfloader_set_data(loader, settings); return loader; } fluidsynth-2.1.1/src/sfloader/fluid_instpatch.h000066400000000000000000000003671362231004000216200ustar00rootroot00000000000000 #ifndef _FLUID_INSTPATCH_H #define _FLUID_INSTPATCH_H #include "fluid_sfont.h" #include "fluid_settings.h" void fluid_instpatch_init(void); fluid_sfloader_t *new_fluid_instpatch_loader(fluid_settings_t *settings); #endif // _FLUID_INSTPATCH_H fluidsynth-2.1.1/src/sfloader/fluid_samplecache.c000066400000000000000000000202361362231004000220600ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * SoundFont file loading code borrowed from Smurf SoundFont Editor * Copyright (C) 1999-2001 Josh Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* CACHED SAMPLE DATA LOADER * * This is a wrapper around fluid_sffile_read_sample_data that attempts to cache the read * data across all FluidSynth instances in a global (process-wide) list. */ #include "fluid_samplecache.h" #include "fluid_sys.h" #include "fluid_list.h" typedef struct _fluid_samplecache_entry_t fluid_samplecache_entry_t; struct _fluid_samplecache_entry_t { /* The following members all form the cache key */ char *filename; time_t modification_time; unsigned int sf_samplepos; unsigned int sf_samplesize; unsigned int sf_sample24pos; unsigned int sf_sample24size; unsigned int sample_start; unsigned int sample_end; int sample_type; /* End of cache key members */ short *sample_data; char *sample_data24; int sample_count; int num_references; int mlocked; }; static fluid_list_t *samplecache_list = NULL; static fluid_mutex_t samplecache_mutex = FLUID_MUTEX_INIT; static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, time_t mtime); static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, time_t mtime); static void delete_samplecache_entry(fluid_samplecache_entry_t *entry); static int fluid_get_file_modification_time(char *filename, time_t *modification_time); /* PUBLIC INTERFACE */ int fluid_samplecache_load(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, int try_mlock, short **sample_data, char **sample_data24) { fluid_samplecache_entry_t *entry; int ret; time_t mtime; fluid_mutex_lock(samplecache_mutex); if(fluid_get_file_modification_time(sf->fname, &mtime) == FLUID_FAILED) { mtime = 0; } entry = get_samplecache_entry(sf, sample_start, sample_end, sample_type, mtime); if(entry == NULL) { entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type, mtime); if(entry == NULL) { ret = -1; goto unlock_exit; } samplecache_list = fluid_list_prepend(samplecache_list, entry); } if(try_mlock && !entry->mlocked) { /* Lock the memory to disable paging. It's okay if this fails. It * probably means that the user doesn't have the required permission. */ if(fluid_mlock(entry->sample_data, entry->sample_count * sizeof(short)) == 0) { if(entry->sample_data24 != NULL) { entry->mlocked = (fluid_mlock(entry->sample_data24, entry->sample_count) == 0); } else { entry->mlocked = TRUE; } if(!entry->mlocked) { fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short)); FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible."); } } } entry->num_references++; *sample_data = entry->sample_data; *sample_data24 = entry->sample_data24; ret = entry->sample_count; unlock_exit: fluid_mutex_unlock(samplecache_mutex); return ret; } int fluid_samplecache_unload(const short *sample_data) { fluid_list_t *entry_list; fluid_samplecache_entry_t *entry; int ret; fluid_mutex_lock(samplecache_mutex); entry_list = samplecache_list; while(entry_list) { entry = (fluid_samplecache_entry_t *)fluid_list_get(entry_list); if(sample_data == entry->sample_data) { entry->num_references--; if(entry->num_references == 0) { if(entry->mlocked) { fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short)); if(entry->sample_data24 != NULL) { fluid_munlock(entry->sample_data24, entry->sample_count); } } samplecache_list = fluid_list_remove(samplecache_list, entry); delete_samplecache_entry(entry); } ret = FLUID_OK; goto unlock_exit; } entry_list = fluid_list_next(entry_list); } FLUID_LOG(FLUID_ERR, "Trying to free sample data not found in cache."); ret = FLUID_FAILED; unlock_exit: fluid_mutex_unlock(samplecache_mutex); return ret; } /* Private functions */ static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, time_t mtime) { fluid_samplecache_entry_t *entry; entry = FLUID_NEW(fluid_samplecache_entry_t); if(entry == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(entry, 0, sizeof(*entry)); entry->filename = FLUID_STRDUP(sf->fname); if(entry->filename == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_exit; } entry->sf_samplepos = sf->samplepos; entry->sf_samplesize = sf->samplesize; entry->sf_sample24pos = sf->sample24pos; entry->sf_sample24size = sf->sample24size; entry->sample_start = sample_start; entry->sample_end = sample_end; entry->sample_type = sample_type; entry->modification_time = mtime; entry->sample_count = fluid_sffile_read_sample_data(sf, sample_start, sample_end, sample_type, &entry->sample_data, &entry->sample_data24); if(entry->sample_count < 0) { goto error_exit; } return entry; error_exit: delete_samplecache_entry(entry); return NULL; } static void delete_samplecache_entry(fluid_samplecache_entry_t *entry) { fluid_return_if_fail(entry != NULL); FLUID_FREE(entry->filename); FLUID_FREE(entry->sample_data); FLUID_FREE(entry->sample_data24); FLUID_FREE(entry); } static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, time_t mtime) { fluid_list_t *entry_list; fluid_samplecache_entry_t *entry; entry_list = samplecache_list; while(entry_list) { entry = (fluid_samplecache_entry_t *)fluid_list_get(entry_list); if((FLUID_STRCMP(sf->fname, entry->filename) == 0) && (mtime == entry->modification_time) && (sf->samplepos == entry->sf_samplepos) && (sf->samplesize == entry->sf_samplesize) && (sf->sample24pos == entry->sf_sample24pos) && (sf->sample24size == entry->sf_sample24size) && (sample_start == entry->sample_start) && (sample_end == entry->sample_end) && (sample_type == entry->sample_type)) { return entry; } entry_list = fluid_list_next(entry_list); } return NULL; } static int fluid_get_file_modification_time(char *filename, time_t *modification_time) { fluid_stat_buf_t buf; if(fluid_stat(filename, &buf)) { return FLUID_FAILED; } *modification_time = buf.st_mtime; return FLUID_OK; } fluidsynth-2.1.1/src/sfloader/fluid_samplecache.h000066400000000000000000000023451362231004000220660ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_SAMPLECACHE_H #define _FLUID_SAMPLECACHE_H #include "fluid_sfont.h" #include "fluid_sffile.h" int fluid_samplecache_load(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, int try_mlock, short **data, char **data24); int fluid_samplecache_unload(const short *sample_data); #endif /* _FLUID_SAMPLECACHE_H */ fluidsynth-2.1.1/src/sfloader/fluid_sffile.c000066400000000000000000002055711362231004000210720ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * SoundFont file loading code borrowed from Smurf SoundFont Editor * Copyright (C) 1999-2001 Josh Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sffile.h" #include "fluid_sfont.h" #include "fluid_sys.h" #if LIBSNDFILE_SUPPORT #include #endif #if LIBINSTPATCH_SUPPORT #include #endif /*=================================sfload.c======================== Borrowed from Smurf SoundFont Editor by Josh Green =================================================================*/ /* FOURCC definitions */ #define RIFF_FCC FLUID_FOURCC('R','I','F','F') #define LIST_FCC FLUID_FOURCC('L','I','S','T') #define SFBK_FCC FLUID_FOURCC('s','f','b','k') #define INFO_FCC FLUID_FOURCC('I','N','F','O') #define SDTA_FCC FLUID_FOURCC('s','d','t','a') #define PDTA_FCC FLUID_FOURCC('p','d','t','a') /* info/sample/preset */ #define IFIL_FCC FLUID_FOURCC('i','f','i','l') #define ISNG_FCC FLUID_FOURCC('i','s','n','g') #define INAM_FCC FLUID_FOURCC('I','N','A','M') #define IROM_FCC FLUID_FOURCC('i','r','o','m') /* info ids (1st byte of info strings) */ #define IVER_FCC FLUID_FOURCC('i','v','e','r') #define ICRD_FCC FLUID_FOURCC('I','C','R','D') #define IENG_FCC FLUID_FOURCC('I','E','N','G') #define IPRD_FCC FLUID_FOURCC('I','P','R','D') /* more info ids */ #define ICOP_FCC FLUID_FOURCC('I','C','O','P') #define ICMT_FCC FLUID_FOURCC('I','C','M','T') #define ISFT_FCC FLUID_FOURCC('I','S','F','T') /* and yet more info ids */ #define SNAM_FCC FLUID_FOURCC('s','n','a','m') #define SMPL_FCC FLUID_FOURCC('s','m','p','l') /* sample ids */ #define PHDR_FCC FLUID_FOURCC('p','h','d','r') #define PBAG_FCC FLUID_FOURCC('p','b','a','g') #define PMOD_FCC FLUID_FOURCC('p','m','o','d') #define PGEN_FCC FLUID_FOURCC('p','g','e','n') /* preset ids */ #define IHDR_FCC FLUID_FOURCC('i','n','s','t') #define IBAG_FCC FLUID_FOURCC('i','b','a','g') #define IMOD_FCC FLUID_FOURCC('i','m','o','d') #define IGEN_FCC FLUID_FOURCC('i','g','e','n') /* instrument ids */ #define SHDR_FCC FLUID_FOURCC('s','h','d','r') /* sample info */ #define SM24_FCC FLUID_FOURCC('s','m','2','4') /* Set when the FCC code is unknown */ #define UNKN_ID FLUID_N_ELEMENTS(idlist) /* * This declares a uint32_t array containing the SF2 chunk identifiers. */ static const uint32_t idlist[] = { RIFF_FCC, LIST_FCC, SFBK_FCC, INFO_FCC, SDTA_FCC, PDTA_FCC, IFIL_FCC, ISNG_FCC, INAM_FCC, IROM_FCC, IVER_FCC, ICRD_FCC, IENG_FCC, IPRD_FCC, ICOP_FCC, ICMT_FCC, ISFT_FCC, SNAM_FCC, SMPL_FCC, PHDR_FCC, PBAG_FCC, PMOD_FCC, PGEN_FCC, IHDR_FCC, IBAG_FCC, IMOD_FCC, IGEN_FCC, SHDR_FCC, SM24_FCC }; /* generator types */ typedef enum { Gen_StartAddrOfs, Gen_EndAddrOfs, Gen_StartLoopAddrOfs, Gen_EndLoopAddrOfs, Gen_StartAddrCoarseOfs, Gen_ModLFO2Pitch, Gen_VibLFO2Pitch, Gen_ModEnv2Pitch, Gen_FilterFc, Gen_FilterQ, Gen_ModLFO2FilterFc, Gen_ModEnv2FilterFc, Gen_EndAddrCoarseOfs, Gen_ModLFO2Vol, Gen_Unused1, Gen_ChorusSend, Gen_ReverbSend, Gen_Pan, Gen_Unused2, Gen_Unused3, Gen_Unused4, Gen_ModLFODelay, Gen_ModLFOFreq, Gen_VibLFODelay, Gen_VibLFOFreq, Gen_ModEnvDelay, Gen_ModEnvAttack, Gen_ModEnvHold, Gen_ModEnvDecay, Gen_ModEnvSustain, Gen_ModEnvRelease, Gen_Key2ModEnvHold, Gen_Key2ModEnvDecay, Gen_VolEnvDelay, Gen_VolEnvAttack, Gen_VolEnvHold, Gen_VolEnvDecay, Gen_VolEnvSustain, Gen_VolEnvRelease, Gen_Key2VolEnvHold, Gen_Key2VolEnvDecay, Gen_Instrument, Gen_Reserved1, Gen_KeyRange, Gen_VelRange, Gen_StartLoopAddrCoarseOfs, Gen_Keynum, Gen_Velocity, Gen_Attenuation, Gen_Reserved2, Gen_EndLoopAddrCoarseOfs, Gen_CoarseTune, Gen_FineTune, Gen_SampleId, Gen_SampleModes, Gen_Reserved3, Gen_ScaleTune, Gen_ExclusiveClass, Gen_OverrideRootKey, Gen_Dummy } Gen_Type; #define Gen_MaxValid Gen_Dummy - 1 /* maximum valid generator */ #define Gen_Count Gen_Dummy /* count of generators */ #define GenArrSize sizeof(SFGenAmount) * Gen_Count /* gen array size */ static const unsigned short invalid_inst_gen[] = { Gen_Unused1, Gen_Unused2, Gen_Unused3, Gen_Unused4, Gen_Reserved1, Gen_Reserved2, Gen_Reserved3, 0 }; static const unsigned short invalid_preset_gen[] = { Gen_StartAddrOfs, Gen_EndAddrOfs, Gen_StartLoopAddrOfs, Gen_EndLoopAddrOfs, Gen_StartAddrCoarseOfs, Gen_EndAddrCoarseOfs, Gen_StartLoopAddrCoarseOfs, Gen_Keynum, Gen_Velocity, Gen_EndLoopAddrCoarseOfs, Gen_SampleModes, Gen_ExclusiveClass, Gen_OverrideRootKey, 0 }; /* sfont file chunk sizes */ #define SF_PHDR_SIZE (38) #define SF_BAG_SIZE (4) #define SF_MOD_SIZE (10) #define SF_GEN_SIZE (4) #define SF_IHDR_SIZE (22) #define SF_SHDR_SIZE (46) #define READCHUNK(sf, var) \ do \ { \ if (sf->fcbs->fread(var, 8, sf->sffd) == FLUID_FAILED) \ return FALSE; \ ((SFChunk *)(var))->size = FLUID_LE32TOH(((SFChunk *)(var))->size); \ } while (0) #define READD(sf, var) \ do \ { \ uint32_t _temp; \ if (sf->fcbs->fread(&_temp, 4, sf->sffd) == FLUID_FAILED) \ return FALSE; \ var = FLUID_LE32TOH(_temp); \ } while (0) #define READW(sf, var) \ do \ { \ uint16_t _temp; \ if (sf->fcbs->fread(&_temp, 2, sf->sffd) == FLUID_FAILED) \ return FALSE; \ var = FLUID_LE16TOH(_temp); \ } while (0) #define READID(sf, var) \ do \ { \ if (sf->fcbs->fread(var, 4, sf->sffd) == FLUID_FAILED) \ return FALSE; \ } while (0) #define READSTR(sf, var) \ do \ { \ if (sf->fcbs->fread(var, 20, sf->sffd) == FLUID_FAILED) \ return FALSE; \ (*var)[20] = '\0'; \ } while (0) #define READB(sf, var) \ do \ { \ if (sf->fcbs->fread(&var, 1, sf->sffd) == FLUID_FAILED) \ return FALSE; \ } while (0) #define FSKIP(sf, size) \ do \ { \ if (sf->fcbs->fseek(sf->sffd, size, SEEK_CUR) == FLUID_FAILED) \ return FALSE; \ } while (0) #define FSKIPW(sf) \ do \ { \ if (sf->fcbs->fseek(sf->sffd, 2, SEEK_CUR) == FLUID_FAILED) \ return FALSE; \ } while (0) /* removes and advances a fluid_list_t pointer */ #define SLADVREM(list, item) \ do \ { \ fluid_list_t *_temp = item; \ item = fluid_list_next(item); \ list = fluid_list_remove_link(list, _temp); \ delete1_fluid_list(_temp); \ } while (0) static int load_header(SFData *sf); static int load_body(SFData *sf); static int process_info(SFData *sf, int size); static int process_sdta(SFData *sf, unsigned int size); static int process_pdta(SFData *sf, int size); static int load_phdr(SFData *sf, int size); static int load_pbag(SFData *sf, int size); static int load_pmod(SFData *sf, int size); static int load_pgen(SFData *sf, int size); static int load_ihdr(SFData *sf, int size); static int load_ibag(SFData *sf, int size); static int load_imod(SFData *sf, int size); static int load_igen(SFData *sf, int size); static int load_shdr(SFData *sf, unsigned int size); static int fixup_pgen(SFData *sf); static int fixup_igen(SFData *sf); static int chunkid(uint32_t id); static int read_listchunk(SFData *sf, SFChunk *chunk); static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size); static int preset_compare_func(void *a, void *b); static fluid_list_t *find_gen_by_id(int gen, fluid_list_t *genlist); static int valid_inst_genid(unsigned short genid); static int valid_preset_genid(unsigned short genid); static void delete_preset(SFPreset *preset); static void delete_inst(SFInst *inst); static void delete_zone(SFZone *zone); static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data); static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24); /** * Check if a file is a SoundFont file. * * If fluidsynth was built with DLS support, this function will also identify DLS files. * @param filename Path to the file to check * @return TRUE if it could be a SF2, SF3 or DLS file, FALSE otherwise * * @note This function only checks whether header(s) in the RIFF chunk are present. * A call to fluid_synth_sfload() might still fail. */ int fluid_is_soundfont(const char *filename) { FILE *fp; uint32_t fcc; int retcode = FALSE; do { if((fp = fluid_file_open(filename, NULL)) == NULL) { return retcode; } if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) { break; } if(fcc != RIFF_FCC) { break; } if(FLUID_FSEEK(fp, 4, SEEK_CUR)) { break; } if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) { break; } retcode = (fcc == SFBK_FCC); if(retcode) { break; // seems to be SF2, stop here } #ifdef LIBINSTPATCH_SUPPORT else { IpatchFileHandle *fhandle = ipatch_file_identify_open(filename, NULL); if(fhandle != NULL) { retcode = (ipatch_file_identify(fhandle->file, NULL) == IPATCH_TYPE_DLS_FILE); ipatch_file_close(fhandle); } } #endif } while(0); FLUID_FCLOSE(fp); return retcode; } /* * Open a SoundFont file and parse it's contents into a SFData structure. * * @param fname filename * @param fcbs file callback structure * @return the partially parsed SoundFont as SFData structure or NULL on error */ SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs) { SFData *sf; int fsize = 0; if(!(sf = FLUID_NEW(SFData))) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(sf, 0, sizeof(SFData)); sf->fcbs = fcbs; if((sf->sffd = fcbs->fopen(fname)) == NULL) { FLUID_LOG(FLUID_ERR, "Unable to open file '%s'", fname); goto error_exit; } sf->fname = FLUID_STRDUP(fname); if(sf->fname == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_exit; } /* get size of file by seeking to end */ if(fcbs->fseek(sf->sffd, 0L, SEEK_END) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Seek to end of file failed"); goto error_exit; } if((fsize = fcbs->ftell(sf->sffd)) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Get end of file position failed"); goto error_exit; } sf->filesize = fsize; if(fcbs->fseek(sf->sffd, 0, SEEK_SET) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Rewind to start of file failed"); goto error_exit; } if(!load_header(sf)) { goto error_exit; } return sf; error_exit: fluid_sffile_close(sf); return NULL; } /* * Parse all preset information from the soundfont * * @return FLUID_OK on success, otherwise FLUID_FAILED */ int fluid_sffile_parse_presets(SFData *sf) { if(!load_body(sf)) { return FLUID_FAILED; } return FLUID_OK; } /* Load sample data from the soundfont file * * This function will always return the sample data in WAV format. If the sample_type specifies an * Ogg Vorbis compressed sample, it will be decompressed automatically before returning. * * @param sf SFData instance * @param sample_start index of first sample point in Soundfont sample chunk * @param sample_end index of last sample point in Soundfont sample chunk * @param sample_type type of the sample in Soundfont * @param data pointer to sample data pointer, will point to loaded sample data on success * @param data24 pointer to 24-bit sample data pointer if 24-bit data present, will point to loaded * 24-bit sample data on success or NULL if no 24-bit data is present in file * * @return The number of sample words in returned buffers or -1 on failure */ int fluid_sffile_read_sample_data(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, short **data, char **data24) { int num_samples; if(sample_type & FLUID_SAMPLETYPE_OGG_VORBIS) { num_samples = fluid_sffile_read_vorbis(sf, sample_start, sample_end, data); } else { num_samples = fluid_sffile_read_wav(sf, sample_start, sample_end, data, data24); } return num_samples; } /* * Close a SoundFont file and free the SFData structure. * * @param sf pointer to SFData structure * @param fcbs file callback structure */ void fluid_sffile_close(SFData *sf) { fluid_list_t *entry; SFPreset *preset; SFInst *inst; if(sf->sffd) { sf->fcbs->fclose(sf->sffd); } FLUID_FREE(sf->fname); entry = sf->info; while(entry) { FLUID_FREE(fluid_list_get(entry)); entry = fluid_list_next(entry); } delete_fluid_list(sf->info); entry = sf->preset; while(entry) { preset = (SFPreset *)fluid_list_get(entry); delete_preset(preset); entry = fluid_list_next(entry); } delete_fluid_list(sf->preset); entry = sf->inst; while(entry) { inst = (SFInst *)fluid_list_get(entry); delete_inst(inst); entry = fluid_list_next(entry); } delete_fluid_list(sf->inst); entry = sf->sample; while(entry) { FLUID_FREE(fluid_list_get(entry)); entry = fluid_list_next(entry); } delete_fluid_list(sf->sample); FLUID_FREE(sf); } /* * Private functions */ /* sound font file load functions */ static int chunkid(uint32_t id) { unsigned int i; for(i = 0; i < FLUID_N_ELEMENTS(idlist); i++) { if(idlist[i] == id) { break; } } /* Return chunk id or UNKN_ID if not found */ return i; } static int load_header(SFData *sf) { SFChunk chunk; READCHUNK(sf, &chunk); /* load RIFF chunk */ if(chunk.id != RIFF_FCC) { /* error if not RIFF */ FLUID_LOG(FLUID_ERR, "Not a RIFF file"); return FALSE; } READID(sf, &chunk.id); /* load file ID */ if(chunk.id != SFBK_FCC) { /* error if not SFBK_ID */ FLUID_LOG(FLUID_ERR, "Not a SoundFont file"); return FALSE; } if(chunk.size != sf->filesize - 8) { FLUID_LOG(FLUID_ERR, "SoundFont file size mismatch"); return FALSE; } /* Process INFO block */ if(!read_listchunk(sf, &chunk)) { return FALSE; } if(chunk.id != INFO_FCC) { FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting INFO chunk"); return FALSE; } if(!process_info(sf, chunk.size)) { return FALSE; } /* Process sample chunk */ if(!read_listchunk(sf, &chunk)) { return FALSE; } if(chunk.id != SDTA_FCC) { FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting SAMPLE chunk"); return FALSE; } if(!process_sdta(sf, chunk.size)) { return FALSE; } /* process HYDRA chunk */ if(!read_listchunk(sf, &chunk)) { return FALSE; } if(chunk.id != PDTA_FCC) { FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting HYDRA chunk"); return FALSE; } sf->hydrapos = sf->fcbs->ftell(sf->sffd); sf->hydrasize = chunk.size; return TRUE; } static int load_body(SFData *sf) { if(sf->fcbs->fseek(sf->sffd, sf->hydrapos, SEEK_SET) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to seek to HYDRA position"); return FALSE; } if(!process_pdta(sf, sf->hydrasize)) { return FALSE; } if(!fixup_pgen(sf)) { return FALSE; } if(!fixup_igen(sf)) { return FALSE; } /* sort preset list by bank, preset # */ sf->preset = fluid_list_sort(sf->preset, (fluid_compare_func_t)preset_compare_func); return TRUE; } static int read_listchunk(SFData *sf, SFChunk *chunk) { READCHUNK(sf, chunk); /* read list chunk */ if(chunk->id != LIST_FCC) /* error if ! list chunk */ { FLUID_LOG(FLUID_ERR, "Invalid chunk id in level 0 parse"); return FALSE; } READID(sf, &chunk->id); /* read id string */ chunk->size -= 4; return TRUE; } static int process_info(SFData *sf, int size) { SFChunk chunk; union { char *chr; uint32_t *fcc; } item; unsigned short ver; while(size > 0) { READCHUNK(sf, &chunk); size -= 8; if(chunk.id == IFIL_FCC) { /* sound font version chunk? */ if(chunk.size != 4) { FLUID_LOG(FLUID_ERR, "Sound font version info chunk has invalid size"); return FALSE; } READW(sf, ver); sf->version.major = ver; READW(sf, ver); sf->version.minor = ver; if(sf->version.major < 2) { FLUID_LOG(FLUID_ERR, "Sound font version is %d.%d which is not" " supported, convert to version 2.0x", sf->version.major, sf->version.minor); return FALSE; } if(sf->version.major == 3) { #if !LIBSNDFILE_SUPPORT FLUID_LOG(FLUID_WARN, "Sound font version is %d.%d but fluidsynth was compiled without" " support for (v3.x)", sf->version.major, sf->version.minor); return FALSE; #endif } else if(sf->version.major > 2) { FLUID_LOG(FLUID_WARN, "Sound font version is %d.%d which is newer than" " what this version of fluidsynth was designed for (v2.0x)", sf->version.major, sf->version.minor); return FALSE; } } else if(chunk.id == IVER_FCC) { /* ROM version chunk? */ if(chunk.size != 4) { FLUID_LOG(FLUID_ERR, "ROM version info chunk has invalid size"); return FALSE; } READW(sf, ver); sf->romver.major = ver; READW(sf, ver); sf->romver.minor = ver; } else if(chunkid(chunk.id) != UNKN_ID) { if((chunk.id != ICMT_FCC && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2)) { FLUID_LOG(FLUID_ERR, "INFO sub chunk %.4s has invalid chunk size of %d bytes", (char*)&chunk.id, chunk.size); return FALSE; } /* alloc for chunk fcc and da chunk */ if(!(item.fcc = FLUID_MALLOC(chunk.size + sizeof(uint32_t) + 1))) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } /* attach to INFO list, fluid_sffile_close will cleanup if FAIL occurs */ sf->info = fluid_list_append(sf->info, item.fcc); /* save chunk fcc and update pointer to data value */ *item.fcc++ = chunk.id; if(sf->fcbs->fread(item.chr, chunk.size, sf->sffd) == FLUID_FAILED) { return FALSE; } /* force terminate info item */ item.chr[chunk.size] = '\0'; } else { FLUID_LOG(FLUID_ERR, "Invalid chunk id in INFO chunk"); return FALSE; } size -= chunk.size; } if(size < 0) { FLUID_LOG(FLUID_ERR, "INFO chunk size mismatch"); return FALSE; } return TRUE; } static int process_sdta(SFData *sf, unsigned int size) { SFChunk chunk; if(size == 0) { return TRUE; /* no sample data? */ } /* read sub chunk */ READCHUNK(sf, &chunk); size -= 8; if(chunk.id != SMPL_FCC) { FLUID_LOG(FLUID_ERR, "Expected SMPL chunk found invalid id instead"); return FALSE; } /* SDTA chunk may also contain sm24 chunk for 24 bit samples * (not yet supported), only an error if SMPL chunk size is * greater than SDTA. */ if(chunk.size > size) { FLUID_LOG(FLUID_ERR, "SDTA chunk size mismatch"); return FALSE; } /* sample data follows */ sf->samplepos = sf->fcbs->ftell(sf->sffd); /* used to check validity of sample headers */ sf->samplesize = chunk.size; FSKIP(sf, chunk.size); size -= chunk.size; if(sf->version.major >= 2 && sf->version.minor >= 4) { /* any chance to find another chunk here? */ if(size > 8) { /* read sub chunk */ READCHUNK(sf, &chunk); size -= 8; if(chunk.id == SM24_FCC) { int sm24size, sdtahalfsize; FLUID_LOG(FLUID_DBG, "Found SM24 chunk"); if(chunk.size > size) { FLUID_LOG(FLUID_WARN, "SM24 exceeds SDTA chunk, ignoring SM24"); goto ret; // no error } sdtahalfsize = sf->samplesize / 2; /* + 1 byte in the case that half the size of smpl chunk is an odd value */ sdtahalfsize += sdtahalfsize % 2; sm24size = chunk.size; if(sdtahalfsize != sm24size) { FLUID_LOG(FLUID_WARN, "SM24 not equal to half the size of SMPL chunk (0x%X != " "0x%X), ignoring SM24", sm24size, sdtahalfsize); goto ret; // no error } /* sample data24 follows */ sf->sample24pos = sf->fcbs->ftell(sf->sffd); sf->sample24size = sm24size; } } } ret: FSKIP(sf, size); return TRUE; } static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size) { READCHUNK(sf, chunk); *size -= 8; if(chunk->id != expid) { FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", (char*)&expid); return FALSE; } if(chunk->size % reclen) /* valid chunk size? */ { FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", (char*)&expid, reclen); return FALSE; } if((*size -= chunk->size) < 0) { FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", (char*)&expid); return FALSE; } return TRUE; } static int process_pdta(SFData *sf, int size) { SFChunk chunk; if(!pdtahelper(sf, PHDR_FCC, SF_PHDR_SIZE, &chunk, &size)) { return FALSE; } if(!load_phdr(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, PBAG_FCC, SF_BAG_SIZE, &chunk, &size)) { return FALSE; } if(!load_pbag(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, PMOD_FCC, SF_MOD_SIZE, &chunk, &size)) { return FALSE; } if(!load_pmod(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, PGEN_FCC, SF_GEN_SIZE, &chunk, &size)) { return FALSE; } if(!load_pgen(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, IHDR_FCC, SF_IHDR_SIZE, &chunk, &size)) { return FALSE; } if(!load_ihdr(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, IBAG_FCC, SF_BAG_SIZE, &chunk, &size)) { return FALSE; } if(!load_ibag(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, IMOD_FCC, SF_MOD_SIZE, &chunk, &size)) { return FALSE; } if(!load_imod(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, IGEN_FCC, SF_GEN_SIZE, &chunk, &size)) { return FALSE; } if(!load_igen(sf, chunk.size)) { return FALSE; } if(!pdtahelper(sf, SHDR_FCC, SF_SHDR_SIZE, &chunk, &size)) { return FALSE; } if(!load_shdr(sf, chunk.size)) { return FALSE; } return TRUE; } /* preset header loader */ static int load_phdr(SFData *sf, int size) { int i, i2; SFPreset *preset, *prev_preset = NULL; unsigned short pbag_idx, prev_pbag_idx = 0; if(size % SF_PHDR_SIZE || size == 0) { FLUID_LOG(FLUID_ERR, "Preset header chunk size is invalid"); return FALSE; } i = size / SF_PHDR_SIZE - 1; if(i == 0) { /* at least one preset + term record */ FLUID_LOG(FLUID_WARN, "File contains no presets"); FSKIP(sf, SF_PHDR_SIZE); return TRUE; } for(; i > 0; i--) { /* load all preset headers */ if((preset = FLUID_NEW(SFPreset)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } sf->preset = fluid_list_append(sf->preset, preset); preset->zone = NULL; /* In case of failure, fluid_sffile_close can cleanup */ READSTR(sf, &preset->name); /* possible read failure ^ */ READW(sf, preset->prenum); READW(sf, preset->bank); READW(sf, pbag_idx); READD(sf, preset->libr); READD(sf, preset->genre); READD(sf, preset->morph); if(prev_preset) { /* not first preset? */ if(pbag_idx < prev_pbag_idx) { FLUID_LOG(FLUID_ERR, "Preset header indices not monotonic"); return FALSE; } i2 = pbag_idx - prev_pbag_idx; while(i2--) { prev_preset->zone = fluid_list_prepend(prev_preset->zone, NULL); } } else if(pbag_idx > 0) /* 1st preset, warn if ofs >0 */ { FLUID_LOG(FLUID_WARN, "%d preset zones not referenced, discarding", pbag_idx); } prev_preset = preset; /* update preset ptr */ prev_pbag_idx = pbag_idx; } FSKIP(sf, 24); READW(sf, pbag_idx); /* Read terminal generator index */ FSKIP(sf, 12); if(pbag_idx < prev_pbag_idx) { FLUID_LOG(FLUID_ERR, "Preset header indices not monotonic"); return FALSE; } i2 = pbag_idx - prev_pbag_idx; while(i2--) { prev_preset->zone = fluid_list_prepend(prev_preset->zone, NULL); } return TRUE; } /* preset bag loader */ static int load_pbag(SFData *sf, int size) { fluid_list_t *p, *p2; SFZone *z, *pz = NULL; unsigned short genndx, modndx; unsigned short pgenndx = 0, pmodndx = 0; unsigned short i; if(size % SF_BAG_SIZE || size == 0) /* size is multiple of SF_BAG_SIZE? */ { FLUID_LOG(FLUID_ERR, "Preset bag chunk size is invalid"); return FALSE; } p = sf->preset; while(p) { /* traverse through presets */ p2 = ((SFPreset *)(p->data))->zone; while(p2) { /* traverse preset's zones */ if((size -= SF_BAG_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Preset bag chunk size mismatch"); return FALSE; } if((z = FLUID_NEW(SFZone)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } p2->data = z; z->gen = NULL; /* Init gen and mod before possible failure, */ z->mod = NULL; /* to ensure proper cleanup (fluid_sffile_close) */ READW(sf, genndx); /* possible read failure ^ */ READW(sf, modndx); z->instsamp = NULL; if(pz) { /* if not first zone */ if(genndx < pgenndx) { FLUID_LOG(FLUID_ERR, "Preset bag generator indices not monotonic"); return FALSE; } if(modndx < pmodndx) { FLUID_LOG(FLUID_ERR, "Preset bag modulator indices not monotonic"); return FALSE; } i = genndx - pgenndx; while(i--) { pz->gen = fluid_list_prepend(pz->gen, NULL); } i = modndx - pmodndx; while(i--) { pz->mod = fluid_list_prepend(pz->mod, NULL); } } pz = z; /* update previous zone ptr */ pgenndx = genndx; /* update previous zone gen index */ pmodndx = modndx; /* update previous zone mod index */ p2 = fluid_list_next(p2); } p = fluid_list_next(p); } size -= SF_BAG_SIZE; if(size != 0) { FLUID_LOG(FLUID_ERR, "Preset bag chunk size mismatch"); return FALSE; } READW(sf, genndx); READW(sf, modndx); if(!pz) { if(genndx > 0) { FLUID_LOG(FLUID_WARN, "No preset generators and terminal index not 0"); } if(modndx > 0) { FLUID_LOG(FLUID_WARN, "No preset modulators and terminal index not 0"); } return TRUE; } if(genndx < pgenndx) { FLUID_LOG(FLUID_ERR, "Preset bag generator indices not monotonic"); return FALSE; } if(modndx < pmodndx) { FLUID_LOG(FLUID_ERR, "Preset bag modulator indices not monotonic"); return FALSE; } i = genndx - pgenndx; while(i--) { pz->gen = fluid_list_prepend(pz->gen, NULL); } i = modndx - pmodndx; while(i--) { pz->mod = fluid_list_prepend(pz->mod, NULL); } return TRUE; } /* preset modulator loader */ static int load_pmod(SFData *sf, int size) { fluid_list_t *p, *p2, *p3; SFMod *m; p = sf->preset; while(p) { /* traverse through all presets */ p2 = ((SFPreset *)(p->data))->zone; while(p2) { /* traverse this preset's zones */ p3 = ((SFZone *)(p2->data))->mod; while(p3) { /* load zone's modulators */ if((size -= SF_MOD_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Preset modulator chunk size mismatch"); return FALSE; } if((m = FLUID_NEW(SFMod)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } p3->data = m; READW(sf, m->src); READW(sf, m->dest); READW(sf, m->amount); READW(sf, m->amtsrc); READW(sf, m->trans); p3 = fluid_list_next(p3); } p2 = fluid_list_next(p2); } p = fluid_list_next(p); } /* If there isn't even a terminal record Hmmm, the specs say there should be one, but.. */ if(size == 0) { return TRUE; } size -= SF_MOD_SIZE; if(size != 0) { FLUID_LOG(FLUID_ERR, "Preset modulator chunk size mismatch"); return FALSE; } FSKIP(sf, SF_MOD_SIZE); /* terminal mod */ return TRUE; } /* ------------------------------------------------------------------- * preset generator loader * generator (per preset) loading rules: * Zones with no generators or modulators shall be annihilated * Global zone must be 1st zone, discard additional ones (instrumentless zones) * * generator (per zone) loading rules (in order of decreasing precedence): * KeyRange is 1st in list (if exists), else discard * if a VelRange exists only preceded by a KeyRange, else discard * if a generator follows an instrument discard it * if a duplicate generator exists replace previous one * ------------------------------------------------------------------- */ static int load_pgen(SFData *sf, int size) { fluid_list_t *p, *p2, *p3, *dup, **hz = NULL; SFZone *z; SFGen *g; SFGenAmount genval; unsigned short genid; int level, skip, drop, gzone, discarded; p = sf->preset; while(p) { /* traverse through all presets */ gzone = FALSE; discarded = FALSE; p2 = ((SFPreset *)(p->data))->zone; if(p2) { hz = &p2; } while(p2) { /* traverse preset's zones */ level = 0; z = (SFZone *)(p2->data); p3 = z->gen; while(p3) { /* load zone's generators */ dup = NULL; skip = FALSE; drop = FALSE; if((size -= SF_GEN_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch"); return FALSE; } READW(sf, genid); if(genid == Gen_KeyRange) { /* nothing precedes */ if(level == 0) { level = 1; READB(sf, genval.range.lo); READB(sf, genval.range.hi); } else { skip = TRUE; } } else if(genid == Gen_VelRange) { /* only KeyRange precedes */ if(level <= 1) { level = 2; READB(sf, genval.range.lo); READB(sf, genval.range.hi); } else { skip = TRUE; } } else if(genid == Gen_Instrument) { /* inst is last gen */ level = 3; READW(sf, genval.uword); ((SFZone *)(p2->data))->instsamp = FLUID_INT_TO_POINTER(genval.uword + 1); break; /* break out of generator loop */ } else { level = 2; if(valid_preset_genid(genid)) { /* generator valid? */ READW(sf, genval.sword); dup = find_gen_by_id(genid, z->gen); } else { skip = TRUE; } } if(!skip) { if(!dup) { /* if gen ! dup alloc new */ if((g = FLUID_NEW(SFGen)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } p3->data = g; g->id = genid; } else { g = (SFGen *)(dup->data); /* ptr to orig gen */ drop = TRUE; } g->amount = genval; } else { /* Skip this generator */ discarded = TRUE; drop = TRUE; FSKIPW(sf); } if(!drop) { p3 = fluid_list_next(p3); /* next gen */ } else { SLADVREM(z->gen, p3); /* drop place holder */ } } /* generator loop */ if(level == 3) { SLADVREM(z->gen, p3); /* zone has inst? */ } else { /* congratulations its a global zone */ if(!gzone) { /* Prior global zones? */ gzone = TRUE; /* if global zone is not 1st zone, relocate */ if(*hz != p2) { void *save = p2->data; FLUID_LOG(FLUID_WARN, "Preset '%s': Global zone is not first zone", ((SFPreset *)(p->data))->name); SLADVREM(*hz, p2); *hz = fluid_list_prepend(*hz, save); continue; } } else { /* previous global zone exists, discard */ FLUID_LOG(FLUID_WARN, "Preset '%s': Discarding invalid global zone", ((SFPreset *)(p->data))->name); *hz = fluid_list_remove(*hz, p2->data); delete_zone((SFZone *)fluid_list_get(p2)); } } while(p3) { /* Kill any zones following an instrument */ discarded = TRUE; if((size -= SF_GEN_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch"); return FALSE; } FSKIP(sf, SF_GEN_SIZE); SLADVREM(z->gen, p3); } p2 = fluid_list_next(p2); /* next zone */ } if(discarded) { FLUID_LOG(FLUID_WARN, "Preset '%s': Some invalid generators were discarded", ((SFPreset *)(p->data))->name); } p = fluid_list_next(p); } /* in case there isn't a terminal record */ if(size == 0) { return TRUE; } size -= SF_GEN_SIZE; if(size != 0) { FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch"); return FALSE; } FSKIP(sf, SF_GEN_SIZE); /* terminal gen */ return TRUE; } /* instrument header loader */ static int load_ihdr(SFData *sf, int size) { int i, i2; SFInst *p, *pr = NULL; /* ptr to current & previous instrument */ unsigned short zndx, pzndx = 0; if(size % SF_IHDR_SIZE || size == 0) /* chunk size is valid? */ { FLUID_LOG(FLUID_ERR, "Instrument header has invalid size"); return FALSE; } size = size / SF_IHDR_SIZE - 1; if(size == 0) { /* at least one preset + term record */ FLUID_LOG(FLUID_WARN, "File contains no instruments"); FSKIP(sf, SF_IHDR_SIZE); return TRUE; } for(i = 0; i < size; i++) { /* load all instrument headers */ if((p = FLUID_NEW(SFInst)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } sf->inst = fluid_list_append(sf->inst, p); p->zone = NULL; /* For proper cleanup if fail (fluid_sffile_close) */ p->idx = i; READSTR(sf, &p->name); /* Possible read failure ^ */ READW(sf, zndx); if(pr) { /* not first instrument? */ if(zndx < pzndx) { FLUID_LOG(FLUID_ERR, "Instrument header indices not monotonic"); return FALSE; } i2 = zndx - pzndx; while(i2--) { pr->zone = fluid_list_prepend(pr->zone, NULL); } } else if(zndx > 0) /* 1st inst, warn if ofs >0 */ { FLUID_LOG(FLUID_WARN, "%d instrument zones not referenced, discarding", zndx); } pzndx = zndx; pr = p; /* update instrument ptr */ } FSKIP(sf, 20); READW(sf, zndx); if(zndx < pzndx) { FLUID_LOG(FLUID_ERR, "Instrument header indices not monotonic"); return FALSE; } i2 = zndx - pzndx; while(i2--) { pr->zone = fluid_list_prepend(pr->zone, NULL); } return TRUE; } /* instrument bag loader */ static int load_ibag(SFData *sf, int size) { fluid_list_t *p, *p2; SFZone *z, *pz = NULL; unsigned short genndx, modndx, pgenndx = 0, pmodndx = 0; int i; if(size % SF_BAG_SIZE || size == 0) /* size is multiple of SF_BAG_SIZE? */ { FLUID_LOG(FLUID_ERR, "Instrument bag chunk size is invalid"); return FALSE; } p = sf->inst; while(p) { /* traverse through inst */ p2 = ((SFInst *)(p->data))->zone; while(p2) { /* load this inst's zones */ if((size -= SF_BAG_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Instrument bag chunk size mismatch"); return FALSE; } if((z = FLUID_NEW(SFZone)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } p2->data = z; z->gen = NULL; /* In case of failure, */ z->mod = NULL; /* fluid_sffile_close can clean up */ READW(sf, genndx); /* READW = possible read failure */ READW(sf, modndx); z->instsamp = NULL; if(pz) { /* if not first zone */ if(genndx < pgenndx) { FLUID_LOG(FLUID_ERR, "Instrument generator indices not monotonic"); return FALSE; } if(modndx < pmodndx) { FLUID_LOG(FLUID_ERR, "Instrument modulator indices not monotonic"); return FALSE; } i = genndx - pgenndx; while(i--) { pz->gen = fluid_list_prepend(pz->gen, NULL); } i = modndx - pmodndx; while(i--) { pz->mod = fluid_list_prepend(pz->mod, NULL); } } pz = z; /* update previous zone ptr */ pgenndx = genndx; pmodndx = modndx; p2 = fluid_list_next(p2); } p = fluid_list_next(p); } size -= SF_BAG_SIZE; if(size != 0) { FLUID_LOG(FLUID_ERR, "Instrument chunk size mismatch"); return FALSE; } READW(sf, genndx); READW(sf, modndx); if(!pz) { /* in case that all are no zoners */ if(genndx > 0) { FLUID_LOG(FLUID_WARN, "No instrument generators and terminal index not 0"); } if(modndx > 0) { FLUID_LOG(FLUID_WARN, "No instrument modulators and terminal index not 0"); } return TRUE; } if(genndx < pgenndx) { FLUID_LOG(FLUID_ERR, "Instrument generator indices not monotonic"); return FALSE; } if(modndx < pmodndx) { FLUID_LOG(FLUID_ERR, "Instrument modulator indices not monotonic"); return FALSE; } i = genndx - pgenndx; while(i--) { pz->gen = fluid_list_prepend(pz->gen, NULL); } i = modndx - pmodndx; while(i--) { pz->mod = fluid_list_prepend(pz->mod, NULL); } return TRUE; } /* instrument modulator loader */ static int load_imod(SFData *sf, int size) { fluid_list_t *p, *p2, *p3; SFMod *m; p = sf->inst; while(p) { /* traverse through all inst */ p2 = ((SFInst *)(p->data))->zone; while(p2) { /* traverse this inst's zones */ p3 = ((SFZone *)(p2->data))->mod; while(p3) { /* load zone's modulators */ if((size -= SF_MOD_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Instrument modulator chunk size mismatch"); return FALSE; } if((m = FLUID_NEW(SFMod)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } p3->data = m; READW(sf, m->src); READW(sf, m->dest); READW(sf, m->amount); READW(sf, m->amtsrc); READW(sf, m->trans); p3 = fluid_list_next(p3); } p2 = fluid_list_next(p2); } p = fluid_list_next(p); } /* If there isn't even a terminal record Hmmm, the specs say there should be one, but.. */ if(size == 0) { return TRUE; } size -= SF_MOD_SIZE; if(size != 0) { FLUID_LOG(FLUID_ERR, "Instrument modulator chunk size mismatch"); return FALSE; } FSKIP(sf, SF_MOD_SIZE); /* terminal mod */ return TRUE; } /* load instrument generators (see load_pgen for loading rules) */ static int load_igen(SFData *sf, int size) { fluid_list_t *p, *p2, *p3, *dup, **hz = NULL; SFZone *z; SFGen *g; SFGenAmount genval; unsigned short genid; int level, skip, drop, gzone, discarded; p = sf->inst; while(p) { /* traverse through all instruments */ gzone = FALSE; discarded = FALSE; p2 = ((SFInst *)(p->data))->zone; if(p2) { hz = &p2; } while(p2) { /* traverse this instrument's zones */ level = 0; z = (SFZone *)(p2->data); p3 = z->gen; while(p3) { /* load zone's generators */ dup = NULL; skip = FALSE; drop = FALSE; if((size -= SF_GEN_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "IGEN chunk size mismatch"); return FALSE; } READW(sf, genid); if(genid == Gen_KeyRange) { /* nothing precedes */ if(level == 0) { level = 1; READB(sf, genval.range.lo); READB(sf, genval.range.hi); } else { skip = TRUE; } } else if(genid == Gen_VelRange) { /* only KeyRange precedes */ if(level <= 1) { level = 2; READB(sf, genval.range.lo); READB(sf, genval.range.hi); } else { skip = TRUE; } } else if(genid == Gen_SampleId) { /* sample is last gen */ level = 3; READW(sf, genval.uword); ((SFZone *)(p2->data))->instsamp = FLUID_INT_TO_POINTER(genval.uword + 1); break; /* break out of generator loop */ } else { level = 2; if(valid_inst_genid(genid)) { /* gen valid? */ READW(sf, genval.sword); dup = find_gen_by_id(genid, z->gen); } else { skip = TRUE; } } if(!skip) { if(!dup) { /* if gen ! dup alloc new */ if((g = FLUID_NEW(SFGen)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } p3->data = g; g->id = genid; } else { g = (SFGen *)(dup->data); drop = TRUE; } g->amount = genval; } else { /* skip this generator */ discarded = TRUE; drop = TRUE; FSKIPW(sf); } if(!drop) { p3 = fluid_list_next(p3); /* next gen */ } else { SLADVREM(z->gen, p3); } } /* generator loop */ if(level == 3) { SLADVREM(z->gen, p3); /* zone has sample? */ } else { /* its a global zone */ if(!gzone) { gzone = TRUE; /* if global zone is not 1st zone, relocate */ if(*hz != p2) { void *save = p2->data; FLUID_LOG(FLUID_WARN, "Instrument '%s': Global zone is not first zone", ((SFPreset *)(p->data))->name); SLADVREM(*hz, p2); *hz = fluid_list_prepend(*hz, save); continue; } } else { /* previous global zone exists, discard */ FLUID_LOG(FLUID_WARN, "Instrument '%s': Discarding invalid global zone", ((SFInst *)(p->data))->name); *hz = fluid_list_remove(*hz, p2->data); delete_zone((SFZone *)fluid_list_get(p2)); } } while(p3) { /* Kill any zones following a sample */ discarded = TRUE; if((size -= SF_GEN_SIZE) < 0) { FLUID_LOG(FLUID_ERR, "Instrument generator chunk size mismatch"); return FALSE; } FSKIP(sf, SF_GEN_SIZE); SLADVREM(z->gen, p3); } p2 = fluid_list_next(p2); /* next zone */ } if(discarded) { FLUID_LOG(FLUID_WARN, "Instrument '%s': Some invalid generators were discarded", ((SFInst *)(p->data))->name); } p = fluid_list_next(p); } /* for those non-terminal record cases, grr! */ if(size == 0) { return TRUE; } size -= SF_GEN_SIZE; if(size != 0) { FLUID_LOG(FLUID_ERR, "IGEN chunk size mismatch"); return FALSE; } FSKIP(sf, SF_GEN_SIZE); /* terminal gen */ return TRUE; } /* sample header loader */ static int load_shdr(SFData *sf, unsigned int size) { unsigned int i; SFSample *p; if(size % SF_SHDR_SIZE || size == 0) /* size is multiple of SHDR size? */ { FLUID_LOG(FLUID_ERR, "Sample header has invalid size"); return FALSE; } size = size / SF_SHDR_SIZE - 1; if(size == 0) { /* at least one sample + term record? */ FLUID_LOG(FLUID_WARN, "File contains no samples"); FSKIP(sf, SF_SHDR_SIZE); return TRUE; } /* load all sample headers */ for(i = 0; i < size; i++) { if((p = FLUID_NEW(SFSample)) == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FALSE; } sf->sample = fluid_list_append(sf->sample, p); READSTR(sf, &p->name); READD(sf, p->start); READD(sf, p->end); READD(sf, p->loopstart); READD(sf, p->loopend); READD(sf, p->samplerate); READB(sf, p->origpitch); READB(sf, p->pitchadj); FSKIPW(sf); /* skip sample link */ READW(sf, p->sampletype); } FSKIP(sf, SF_SHDR_SIZE); /* skip terminal shdr */ return TRUE; } /* "fixup" (inst # -> inst ptr) instrument references in preset list */ static int fixup_pgen(SFData *sf) { fluid_list_t *p, *p2, *p3; SFZone *z; int i; p = sf->preset; while(p) { p2 = ((SFPreset *)(p->data))->zone; while(p2) { /* traverse this preset's zones */ z = (SFZone *)(p2->data); if((i = FLUID_POINTER_TO_INT(z->instsamp))) { /* load instrument # */ p3 = fluid_list_nth(sf->inst, i - 1); if(!p3) { FLUID_LOG(FLUID_ERR, "Preset %03d %03d: Invalid instrument reference", ((SFPreset *)(p->data))->bank, ((SFPreset *)(p->data))->prenum); return FALSE; } z->instsamp = p3; } else { z->instsamp = NULL; } p2 = fluid_list_next(p2); } p = fluid_list_next(p); } return TRUE; } /* "fixup" (sample # -> sample ptr) sample references in instrument list */ static int fixup_igen(SFData *sf) { fluid_list_t *p, *p2, *p3; SFZone *z; int i; p = sf->inst; while(p) { p2 = ((SFInst *)(p->data))->zone; while(p2) { /* traverse instrument's zones */ z = (SFZone *)(p2->data); if((i = FLUID_POINTER_TO_INT(z->instsamp))) { /* load sample # */ p3 = fluid_list_nth(sf->sample, i - 1); if(!p3) { FLUID_LOG(FLUID_ERR, "Instrument '%s': Invalid sample reference", ((SFInst *)(p->data))->name); return FALSE; } z->instsamp = p3; } p2 = fluid_list_next(p2); } p = fluid_list_next(p); } return TRUE; } static void delete_preset(SFPreset *preset) { fluid_list_t *entry; SFZone *zone; if(!preset) { return; } entry = preset->zone; while(entry) { zone = (SFZone *)fluid_list_get(entry); delete_zone(zone); entry = fluid_list_next(entry); } delete_fluid_list(preset->zone); FLUID_FREE(preset); } static void delete_inst(SFInst *inst) { fluid_list_t *entry; SFZone *zone; if(!inst) { return; } entry = inst->zone; while(entry) { zone = (SFZone *)fluid_list_get(entry); delete_zone(zone); entry = fluid_list_next(entry); } delete_fluid_list(inst->zone); FLUID_FREE(inst); } /* Free all elements of a zone (Preset or Instrument) */ static void delete_zone(SFZone *zone) { fluid_list_t *entry; if(!zone) { return; } entry = zone->gen; while(entry) { FLUID_FREE(fluid_list_get(entry)); entry = fluid_list_next(entry); } delete_fluid_list(zone->gen); entry = zone->mod; while(entry) { FLUID_FREE(fluid_list_get(entry)); entry = fluid_list_next(entry); } delete_fluid_list(zone->mod); FLUID_FREE(zone); } /* preset sort function, first by bank, then by preset # */ static int preset_compare_func(void *a, void *b) { int aval, bval; aval = (int)(((SFPreset *)a)->bank) << 16 | ((SFPreset *)a)->prenum; bval = (int)(((SFPreset *)b)->bank) << 16 | ((SFPreset *)b)->prenum; return (aval - bval); } /* Find a generator by its id in the passed in list. * * @return pointer to SFGen if found, otherwise NULL */ static fluid_list_t *find_gen_by_id(int gen, fluid_list_t *genlist) { /* is generator in gen list? */ fluid_list_t *p; p = genlist; while(p) { if(p->data == NULL) { return NULL; } if(gen == ((SFGen *)p->data)->id) { break; } p = fluid_list_next(p); } return p; } /* check validity of instrument generator */ static int valid_inst_genid(unsigned short genid) { int i = 0; if(genid > Gen_MaxValid) { return FALSE; } while(invalid_inst_gen[i] && invalid_inst_gen[i] != genid) { i++; } return (invalid_inst_gen[i] == 0); } /* check validity of preset generator */ static int valid_preset_genid(unsigned short genid) { int i = 0; if(!valid_inst_genid(genid)) { return FALSE; } while(invalid_preset_gen[i] && invalid_preset_gen[i] != genid) { i++; } return (invalid_preset_gen[i] == 0); } static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24) { short *loaded_data = NULL; char *loaded_data24 = NULL; int num_samples = (end + 1) - start; fluid_return_val_if_fail(num_samples > 0, -1); if((start * sizeof(short) > sf->samplesize) || (end * sizeof(short) > sf->samplesize)) { FLUID_LOG(FLUID_ERR, "Sample offsets exceed sample data chunk"); goto error_exit; } /* Load 16-bit sample data */ if(sf->fcbs->fseek(sf->sffd, sf->samplepos + (start * sizeof(short)), SEEK_SET) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to seek to sample position"); goto error_exit; } loaded_data = FLUID_ARRAY(short, num_samples); if(loaded_data == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_exit; } if(sf->fcbs->fread(loaded_data, num_samples * sizeof(short), sf->sffd) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to read sample data"); goto error_exit; } /* If this machine is big endian, byte swap the 16 bit samples */ if(FLUID_IS_BIG_ENDIAN) { int i; for(i = 0; i < num_samples; i++) { loaded_data[i] = FLUID_LE16TOH(loaded_data[i]); } } *data = loaded_data; /* Optionally load additional 8 bit sample data for 24-bit support. Any failures while loading * the 24-bit sample data will be logged as errors but won't prevent the sample reading to * fail, as sound output is still possible with the 16-bit sample data. */ if(sf->sample24pos) { if((start > sf->sample24size) || (end > sf->sample24size)) { FLUID_LOG(FLUID_ERR, "Sample offsets exceed 24-bit sample data chunk"); goto error24_exit; } if(sf->fcbs->fseek(sf->sffd, sf->sample24pos + start, SEEK_SET) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to seek position for 24-bit sample data in data file"); goto error24_exit; } loaded_data24 = FLUID_ARRAY(char, num_samples); if(loaded_data24 == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory reading 24-bit sample data"); goto error24_exit; } if(sf->fcbs->fread(loaded_data24, num_samples, sf->sffd) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to read 24-bit sample data"); goto error24_exit; } } *data24 = loaded_data24; return num_samples; error24_exit: FLUID_LOG(FLUID_WARN, "Ignoring 24-bit sample data, sound quality might suffer"); FLUID_FREE(loaded_data24); *data24 = NULL; return num_samples; error_exit: FLUID_FREE(loaded_data); FLUID_FREE(loaded_data24); return -1; } /* Ogg Vorbis loading and decompression */ #if LIBSNDFILE_SUPPORT /* Virtual file access routines to allow loading individually compressed * samples from the Soundfont sample data chunk using the file callbacks * passing in during opening of the file */ typedef struct _sfvio_data_t { SFData *sffile; sf_count_t start; /* start byte offset of compressed data */ sf_count_t end; /* end byte offset of compressed data */ sf_count_t offset; /* current virtual file offset from start byte offset */ } sfvio_data_t; static sf_count_t sfvio_get_filelen(void *user_data) { sfvio_data_t *data = user_data; return (data->end + 1) - data->start; } static sf_count_t sfvio_seek(sf_count_t offset, int whence, void *user_data) { sfvio_data_t *data = user_data; SFData *sf = data->sffile; sf_count_t new_offset; switch(whence) { case SEEK_SET: new_offset = offset; break; case SEEK_CUR: new_offset = data->offset + offset; break; case SEEK_END: new_offset = sfvio_get_filelen(user_data) + offset; break; default: goto fail; /* proper error handling not possible?? */ } if(sf->fcbs->fseek(sf->sffd, sf->samplepos + data->start + new_offset, SEEK_SET) != FLUID_FAILED) { data->offset = new_offset; } fail: return data->offset; } static sf_count_t sfvio_read(void *ptr, sf_count_t count, void *user_data) { sfvio_data_t *data = user_data; SFData *sf = data->sffile; sf_count_t remain; remain = sfvio_get_filelen(user_data) - data->offset; if(count > remain) { count = remain; } if(count == 0) { return count; } if(sf->fcbs->fread(ptr, count, sf->sffd) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to read compressed sample data"); return 0; } data->offset += count; return count; } static sf_count_t sfvio_tell(void *user_data) { sfvio_data_t *data = user_data; return data->offset; } /** * Read Ogg Vorbis compressed data from the Soundfont and decompress it, returning the number of samples * in the decompressed WAV. Only 16-bit mono samples are supported. * * Note that this function takes byte indices for start and end source data. The sample headers in SF3 * files use byte indices, so those pointers can be passed directly to this function. * * This function uses a virtual file structure in order to read the Ogg Vorbis * data from arbitrary locations in the source file. */ static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data) { SNDFILE *sndfile; SF_INFO sfinfo; SF_VIRTUAL_IO sfvio = { sfvio_get_filelen, sfvio_seek, sfvio_read, NULL, sfvio_tell }; sfvio_data_t sfdata; short *wav_data = NULL; if((start_byte > sf->samplesize) || (end_byte > sf->samplesize)) { FLUID_LOG(FLUID_ERR, "Ogg Vorbis data offsets exceed sample data chunk"); return -1; } // Initialize file position indicator and SF_INFO structure sfdata.sffile = sf; sfdata.start = start_byte; sfdata.end = end_byte; sfdata.offset = 0; FLUID_MEMSET(&sfinfo, 0, sizeof(sfinfo)); /* Seek to beginning of Ogg Vorbis data in Soundfont */ if(sf->fcbs->fseek(sf->sffd, sf->samplepos + start_byte, SEEK_SET) == FLUID_FAILED) { FLUID_LOG(FLUID_ERR, "Failed to seek to compressd sample position"); return -1; } // Open sample as a virtual file sndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, &sfdata); if(!sndfile) { FLUID_LOG(FLUID_ERR, "%s", sf_strerror(sndfile)); return -1; } // Empty sample if(sfinfo.frames <= 0 || sfinfo.channels <= 0) { FLUID_LOG(FLUID_DBG, "Empty decompressed sample"); *data = NULL; sf_close(sndfile); return 0; } // Mono sample if(sfinfo.channels != 1) { FLUID_LOG(FLUID_DBG, "Unsupported channel count %d in ogg sample", sfinfo.channels); goto error_exit; } if((sfinfo.format & SF_FORMAT_OGG) == 0) { FLUID_LOG(FLUID_WARN, "OGG sample is not OGG compressed, this is not officially supported"); } wav_data = FLUID_ARRAY(short, sfinfo.frames * sfinfo.channels); if(!wav_data) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_exit; } /* Automatically decompresses the Ogg Vorbis data to 16-bit PCM */ if(sf_readf_short(sndfile, wav_data, sfinfo.frames) < sfinfo.frames) { FLUID_LOG(FLUID_DBG, "Decompression failed!"); FLUID_LOG(FLUID_ERR, "%s", sf_strerror(sndfile)); goto error_exit; } sf_close(sndfile); *data = wav_data; return sfinfo.frames; error_exit: FLUID_FREE(wav_data); sf_close(sndfile); return -1; } #else static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data) { return -1; } #endif fluidsynth-2.1.1/src/sfloader/fluid_sffile.h000066400000000000000000000163251362231004000210740ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * SoundFont loading code borrowed from Smurf SoundFont Editor by Josh Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_SFFILE_H #define _FLUID_SFFILE_H #include "fluid_gen.h" #include "fluid_list.h" #include "fluid_mod.h" #include "fluidsynth.h" #include "fluidsynth_priv.h" /* Sound Font structure defines */ /* Forward declarations */ typedef union _SFGenAmount SFGenAmount; typedef struct _SFVersion SFVersion; typedef struct _SFMod SFMod; typedef struct _SFGen SFGen; typedef struct _SFZone SFZone; typedef struct _SFSample SFSample; typedef struct _SFInst SFInst; typedef struct _SFPreset SFPreset; typedef struct _SFData SFData; typedef struct _SFChunk SFChunk; typedef struct _SFPhdr SFPhdr; typedef struct _SFBag SFBag; typedef struct _SFIhdr SFIhdr; typedef struct _SFShdr SFShdr; struct _SFVersion { /* version structure */ unsigned short major; unsigned short minor; }; struct _SFMod { /* Modulator structure */ unsigned short src; /* source modulator */ unsigned short dest; /* destination generator */ signed short amount; /* signed, degree of modulation */ unsigned short amtsrc; /* second source controls amnt of first */ unsigned short trans; /* transform applied to source */ }; union _SFGenAmount /* Generator amount structure */ { signed short sword; /* signed 16 bit value */ unsigned short uword; /* unsigned 16 bit value */ struct { unsigned char lo; /* low value for ranges */ unsigned char hi; /* high value for ranges */ } range; }; struct _SFGen { /* Generator structure */ unsigned short id; /* generator ID */ SFGenAmount amount; /* generator value */ }; struct _SFZone { /* Sample/instrument zone structure */ fluid_list_t *instsamp; /* instrument/sample pointer for zone */ fluid_list_t *gen; /* list of generators */ fluid_list_t *mod; /* list of modulators */ }; struct _SFSample { /* Sample structure */ char name[21]; /* Name of sample */ unsigned int start; /* Offset in sample area to start of sample */ unsigned int end; /* Offset from start to end of sample, this is the last point of the sample, the SF spec has this as the 1st point after, corrected on load/save */ unsigned int loopstart; /* Offset from start to start of loop */ unsigned int loopend; /* Offset from start to end of loop, marks the first point after loop, whose sample value is ideally equivalent to loopstart */ unsigned int samplerate; /* Sample rate recorded at */ unsigned char origpitch; /* root midi key number */ signed char pitchadj; /* pitch correction in cents */ unsigned short sampletype; /* 1 mono,2 right,4 left,linked 8,0x8000=ROM */ fluid_sample_t *fluid_sample; /* Imported sample (fixed up in fluid_defsfont_load) */ }; struct _SFInst { /* Instrument structure */ char name[21]; /* Name of instrument */ int idx; /* Index of this instrument in the Soundfont */ fluid_list_t *zone; /* list of instrument zones */ }; struct _SFPreset { /* Preset structure */ char name[21]; /* preset name */ unsigned short prenum; /* preset number */ unsigned short bank; /* bank number */ unsigned int libr; /* Not used (preserved) */ unsigned int genre; /* Not used (preserved) */ unsigned int morph; /* Not used (preserved) */ fluid_list_t *zone; /* list of preset zones */ }; /* NOTE: sffd is also used to determine if sound font is new (NULL) */ struct _SFData { /* Sound font data structure */ SFVersion version; /* sound font version */ SFVersion romver; /* ROM version */ unsigned int filesize; unsigned int samplepos; /* position within sffd of the sample chunk */ unsigned int samplesize; /* length within sffd of the sample chunk */ unsigned int sample24pos; /* position within sffd of the sm24 chunk, set to zero if no 24 bit sample support */ unsigned int sample24size; /* length within sffd of the sm24 chunk */ unsigned int hydrapos; unsigned int hydrasize; char *fname; /* file name */ FILE *sffd; /* loaded sfont file descriptor */ const fluid_file_callbacks_t *fcbs; /* file callbacks used to read this file */ fluid_list_t *info; /* linked list of info strings (1st byte is ID) */ fluid_list_t *preset; /* linked list of preset info */ fluid_list_t *inst; /* linked list of instrument info */ fluid_list_t *sample; /* linked list of sample info */ }; /* functions */ /*-----------------------------------sffile.h----------------------------*/ /* File structures and routines (used to be in sffile.h) */ /* sfont file data structures */ struct _SFChunk { /* RIFF file chunk structure */ unsigned int id; /* chunk id */ unsigned int size; /* size of the following chunk */ }; struct _SFPhdr { unsigned char name[20]; /* preset name */ unsigned short preset; /* preset number */ unsigned short bank; /* bank number */ unsigned short pbagndx; /* index into preset bag */ unsigned int library; /* just for preserving them */ unsigned int genre; /* Not used */ unsigned int morphology; /* Not used */ }; struct _SFBag { unsigned short genndx; /* index into generator list */ unsigned short modndx; /* index into modulator list */ }; struct _SFIhdr { char name[20]; /* Name of instrument */ unsigned short ibagndx; /* Instrument bag index */ }; struct _SFShdr { /* Sample header loading struct */ char name[20]; /* Sample name */ unsigned int start; /* Offset to start of sample */ unsigned int end; /* Offset to end of sample */ unsigned int loopstart; /* Offset to start of loop */ unsigned int loopend; /* Offset to end of loop */ unsigned int samplerate; /* Sample rate recorded at */ unsigned char origpitch; /* root midi key number */ signed char pitchadj; /* pitch correction in cents */ unsigned short samplelink; /* Not used */ unsigned short sampletype; /* 1 mono,2 right,4 left,linked 8,0x8000=ROM */ }; /* Public functions */ SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs); void fluid_sffile_close(SFData *sf); int fluid_sffile_parse_presets(SFData *sf); int fluid_sffile_read_sample_data(SFData *sf, unsigned int sample_start, unsigned int sample_end, int sample_type, short **data, char **data24); #endif /* _FLUID_SFFILE_H */ fluidsynth-2.1.1/src/sfloader/fluid_sfont.c000066400000000000000000000612261362231004000207500ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sfont.h" #include "fluid_sys.h" void *default_fopen(const char *path) { const char* msg; FILE* handle = fluid_file_open(path, &msg); if(handle == NULL) { FLUID_LOG(FLUID_ERR, "fluid_sfloader_load(): Failed to open '%s': %s", path, msg); } return handle; } int default_fclose(void *handle) { return FLUID_FCLOSE((FILE *)handle) == 0 ? FLUID_OK : FLUID_FAILED; } long default_ftell(void *handle) { return FLUID_FTELL((FILE *)handle); } int safe_fread(void *buf, int count, void *fd) { if(FLUID_FREAD(buf, count, 1, (FILE *)fd) != 1) { if(feof((FILE *)fd)) { FLUID_LOG(FLUID_ERR, "EOF while attempting to read %d bytes", count); } else { FLUID_LOG(FLUID_ERR, "File read failed"); } return FLUID_FAILED; } return FLUID_OK; } int safe_fseek(void *fd, long ofs, int whence) { if(FLUID_FSEEK((FILE *)fd, ofs, whence) != 0) { FLUID_LOG(FLUID_ERR, "File seek failed with offset = %ld and whence = %d", ofs, whence); return FLUID_FAILED; } return FLUID_OK; } /** * Creates a new SoundFont loader. * * @param load Pointer to a function that provides a #fluid_sfont_t (see #fluid_sfloader_load_t). * @param free Pointer to a function that destroys this instance (see #fluid_sfloader_free_t). * Unless any private data needs to be freed it is sufficient to set this to delete_fluid_sfloader(). * * @return the SoundFont loader instance on success, NULL otherwise. */ fluid_sfloader_t *new_fluid_sfloader(fluid_sfloader_load_t load, fluid_sfloader_free_t free) { fluid_sfloader_t *loader; fluid_return_val_if_fail(load != NULL, NULL); fluid_return_val_if_fail(free != NULL, NULL); loader = FLUID_NEW(fluid_sfloader_t); if(loader == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(loader, 0, sizeof(*loader)); loader->load = load; loader->free = free; fluid_sfloader_set_callbacks(loader, default_fopen, safe_fread, safe_fseek, default_ftell, default_fclose); return loader; } /** * Frees a SoundFont loader created with new_fluid_sfloader(). * * @param loader The SoundFont loader instance to free. */ void delete_fluid_sfloader(fluid_sfloader_t *loader) { fluid_return_if_fail(loader != NULL); FLUID_FREE(loader); } /** * Specify private data to be used by #fluid_sfloader_load_t. * * @param loader The SoundFont loader instance. * @param data The private data to store. * @return #FLUID_OK on success, #FLUID_FAILED otherwise. */ int fluid_sfloader_set_data(fluid_sfloader_t *loader, void *data) { fluid_return_val_if_fail(loader != NULL, FLUID_FAILED); loader->data = data; return FLUID_OK; } /** * Obtain private data previously set with fluid_sfloader_set_data(). * * @param loader The SoundFont loader instance. * @return The private data or NULL if none explicitly set before. */ void *fluid_sfloader_get_data(fluid_sfloader_t *loader) { fluid_return_val_if_fail(loader != NULL, NULL); return loader->data; } /** * Set custom callbacks to be used upon soundfont loading. * * Useful for loading a soundfont from memory, see \a doc/fluidsynth_sfload_mem.c as an example. * * @param loader The SoundFont loader instance. * @param open A function implementing #fluid_sfloader_callback_open_t. * @param read A function implementing #fluid_sfloader_callback_read_t. * @param seek A function implementing #fluid_sfloader_callback_seek_t. * @param tell A function implementing #fluid_sfloader_callback_tell_t. * @param close A function implementing #fluid_sfloader_callback_close_t. * @return #FLUID_OK if the callbacks have been successfully set, #FLUID_FAILED otherwise. */ int fluid_sfloader_set_callbacks(fluid_sfloader_t *loader, fluid_sfloader_callback_open_t open, fluid_sfloader_callback_read_t read, fluid_sfloader_callback_seek_t seek, fluid_sfloader_callback_tell_t tell, fluid_sfloader_callback_close_t close) { fluid_file_callbacks_t *cb; fluid_return_val_if_fail(loader != NULL, FLUID_FAILED); fluid_return_val_if_fail(open != NULL, FLUID_FAILED); fluid_return_val_if_fail(read != NULL, FLUID_FAILED); fluid_return_val_if_fail(seek != NULL, FLUID_FAILED); fluid_return_val_if_fail(tell != NULL, FLUID_FAILED); fluid_return_val_if_fail(close != NULL, FLUID_FAILED); cb = &loader->file_callbacks; cb->fopen = open; cb->fread = read; cb->fseek = seek; cb->ftell = tell; cb->fclose = close; // NOTE: if we ever make the instpatch loader public, this may return FLUID_FAILED return FLUID_OK; } /** * Creates a new virtual SoundFont instance structure. * @param get_name A function implementing #fluid_sfont_get_name_t. * @param get_preset A function implementing #fluid_sfont_get_preset_t. * @param iter_start A function implementing #fluid_sfont_iteration_start_t, or NULL if preset iteration not needed. * @param iter_next A function implementing #fluid_sfont_iteration_next_t, or NULL if preset iteration not needed. * @param free A function implementing #fluid_sfont_free_t. * @return The soundfont instance on success or NULL otherwise. */ fluid_sfont_t *new_fluid_sfont(fluid_sfont_get_name_t get_name, fluid_sfont_get_preset_t get_preset, fluid_sfont_iteration_start_t iter_start, fluid_sfont_iteration_next_t iter_next, fluid_sfont_free_t free) { fluid_sfont_t *sfont; fluid_return_val_if_fail(get_name != NULL, NULL); fluid_return_val_if_fail(get_preset != NULL, NULL); fluid_return_val_if_fail(free != NULL, NULL); sfont = FLUID_NEW(fluid_sfont_t); if(sfont == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(sfont, 0, sizeof(*sfont)); sfont->get_name = get_name; sfont->get_preset = get_preset; sfont->iteration_start = iter_start; sfont->iteration_next = iter_next; sfont->free = free; return sfont; } /** * Set private data to use with a SoundFont instance. * * @param sfont The SoundFont instance. * @param data The private data to store. * @return #FLUID_OK on success, #FLUID_FAILED otherwise. */ int fluid_sfont_set_data(fluid_sfont_t *sfont, void *data) { fluid_return_val_if_fail(sfont != NULL, FLUID_FAILED); sfont->data = data; return FLUID_OK; } /** * Retrieve the private data of a SoundFont instance. * * @param sfont The SoundFont instance. * @return The private data or NULL if none explicitly set before. */ void *fluid_sfont_get_data(fluid_sfont_t *sfont) { fluid_return_val_if_fail(sfont != NULL, NULL); return sfont->data; } /** * Retrieve the unique ID of a SoundFont instance. * * @param sfont The SoundFont instance. * @return The SoundFont ID. */ int fluid_sfont_get_id(fluid_sfont_t *sfont) { return sfont->id; } /** * Retrieve the name of a SoundFont instance. * * @param sfont The SoundFont instance. * @return The name of the SoundFont. */ const char *fluid_sfont_get_name(fluid_sfont_t *sfont) { return sfont->get_name(sfont); } /** * Retrieve the preset assigned the a SoundFont instance * for the given bank and preset number. * @param sfont The SoundFont instance. * @param bank bank number of the preset * @param prenum program number of the preset * @return The preset instance or NULL if none found. */ fluid_preset_t *fluid_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum) { return sfont->get_preset(sfont, bank, prenum); } /** * Starts / re-starts virtual preset iteration in a SoundFont. * @param sfont Virtual SoundFont instance */ void fluid_sfont_iteration_start(fluid_sfont_t *sfont) { fluid_return_if_fail(sfont != NULL); fluid_return_if_fail(sfont->iteration_start != NULL); sfont->iteration_start(sfont); } /** * Virtual SoundFont preset iteration function. * * Returns preset information to the caller and advances the * internal iteration state to the next preset for subsequent calls. * @param sfont The SoundFont instance. * @return NULL when no more presets are available, otherwise the a pointer to the current preset */ fluid_preset_t *fluid_sfont_iteration_next(fluid_sfont_t *sfont) { fluid_return_val_if_fail(sfont != NULL, NULL); fluid_return_val_if_fail(sfont->iteration_next != NULL, NULL); return sfont->iteration_next(sfont); } /** * Destroys a SoundFont instance created with new_fluid_sfont(). * * Implements #fluid_sfont_free_t. * * @param sfont The SoundFont instance to destroy. * @return Always returns 0. */ int delete_fluid_sfont(fluid_sfont_t *sfont) { fluid_return_val_if_fail(sfont != NULL, 0); FLUID_FREE(sfont); return 0; } /** * Create a virtual SoundFont preset instance. * * @param parent_sfont The SoundFont instance this preset shall belong to * @param get_name A function implementing #fluid_preset_get_name_t * @param get_bank A function implementing #fluid_preset_get_banknum_t * @param get_num A function implementing #fluid_preset_get_num_t * @param noteon A function implementing #fluid_preset_noteon_t * @param free A function implementing #fluid_preset_free_t * @return The preset instance on success, NULL otherwise. */ fluid_preset_t *new_fluid_preset(fluid_sfont_t *parent_sfont, fluid_preset_get_name_t get_name, fluid_preset_get_banknum_t get_bank, fluid_preset_get_num_t get_num, fluid_preset_noteon_t noteon, fluid_preset_free_t free) { fluid_preset_t *preset; fluid_return_val_if_fail(parent_sfont != NULL, NULL); fluid_return_val_if_fail(get_name != NULL, NULL); fluid_return_val_if_fail(get_bank != NULL, NULL); fluid_return_val_if_fail(get_num != NULL, NULL); fluid_return_val_if_fail(noteon != NULL, NULL); fluid_return_val_if_fail(free != NULL, NULL); preset = FLUID_NEW(fluid_preset_t); if(preset == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(preset, 0, sizeof(*preset)); preset->sfont = parent_sfont; preset->get_name = get_name; preset->get_banknum = get_bank; preset->get_num = get_num; preset->noteon = noteon; preset->free = free; return preset; } /** * Set private data to use with a SoundFont preset instance. * * @param preset The SoundFont preset instance. * @param data The private data to store. * @return #FLUID_OK on success, #FLUID_FAILED otherwise. */ int fluid_preset_set_data(fluid_preset_t *preset, void *data) { fluid_return_val_if_fail(preset != NULL, FLUID_FAILED); preset->data = data; return FLUID_OK; } /** * Retrieve the private data of a SoundFont preset instance. * * @param preset The SoundFont preset instance. * @return The private data or NULL if none explicitly set before. */ void *fluid_preset_get_data(fluid_preset_t *preset) { fluid_return_val_if_fail(preset != NULL, NULL); return preset->data; } /** * Retrieves the presets name by executing the \p get_name function * provided on its creation. * * @param preset The SoundFont preset instance. * @return Pointer to a NULL-terminated string containing the presets name. */ const char *fluid_preset_get_name(fluid_preset_t *preset) { return preset->get_name(preset); } /** * Retrieves the presets bank number by executing the \p get_bank function * provided on its creation. * * @param preset The SoundFont preset instance. * @return The bank number of \p preset. */ int fluid_preset_get_banknum(fluid_preset_t *preset) { return preset->get_banknum(preset); } /** * Retrieves the presets (instrument) number by executing the \p get_num function * provided on its creation. * * @param preset The SoundFont preset instance. * @return The number of \p preset. */ int fluid_preset_get_num(fluid_preset_t *preset) { return preset->get_num(preset); } /** * Retrieves the presets parent SoundFont instance. * * @param preset The SoundFont preset instance. * @return The parent SoundFont of \p preset. */ fluid_sfont_t *fluid_preset_get_sfont(fluid_preset_t *preset) { return preset->sfont; } /** * Destroys a SoundFont preset instance created with new_fluid_preset(). * * Implements #fluid_preset_free_t. * * @param preset The SoundFont preset instance to destroy. */ void delete_fluid_preset(fluid_preset_t *preset) { fluid_return_if_fail(preset != NULL); FLUID_FREE(preset); } /** * Create a new sample instance. * @return The sample on success, NULL otherwise. */ fluid_sample_t * new_fluid_sample() { fluid_sample_t *sample = NULL; sample = FLUID_NEW(fluid_sample_t); if(sample == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(sample, 0, sizeof(*sample)); return sample; } /** * Destroy a sample instance previously created with new_fluid_sample(). * @param sample The sample to destroy. */ void delete_fluid_sample(fluid_sample_t *sample) { fluid_return_if_fail(sample != NULL); if(sample->auto_free) { FLUID_FREE(sample->data); FLUID_FREE(sample->data24); } FLUID_FREE(sample); } /** * Returns the size of the fluid_sample_t structure. * * Useful in low latency scenarios e.g. to allocate a pool of samples. * * @return Size of fluid_sample_t in bytes * * @note It is recommend to zero initialize the memory before using the object. * * @warning Do NOT allocate samples on the stack and assign them to a voice! */ size_t fluid_sample_sizeof() { return sizeof(fluid_sample_t); } /** * Set the name of a SoundFont sample. * @param sample SoundFont sample * @param name Name to assign to sample (20 chars in length + zero terminator) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_sample_set_name(fluid_sample_t *sample, const char *name) { fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); FLUID_STRNCPY(sample->name, name, sizeof(sample->name)); return FLUID_OK; } /** * Assign sample data to a SoundFont sample. * @param sample SoundFont sample * @param data Buffer containing 16 bit (mono-)audio sample data * @param data24 If not NULL, pointer to the least significant byte counterparts of each sample data point in order to create 24 bit audio samples * @param nbframes Number of samples in \a data * @param sample_rate Sampling rate of the sample data * @param copy_data TRUE to copy the sample data (and automatically free it upon delete_fluid_sample()), FALSE to use it directly (and not free it) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note If \a copy_data is FALSE, data should have 8 unused frames at start * and 8 unused frames at the end and \a nbframes should be >=48 */ int fluid_sample_set_sound_data(fluid_sample_t *sample, short *data, char *data24, unsigned int nbframes, unsigned int sample_rate, short copy_data ) { /* the number of samples before the start and after the end */ #define SAMPLE_LOOP_MARGIN 8U fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); fluid_return_val_if_fail(data != NULL, FLUID_FAILED); fluid_return_val_if_fail(nbframes != 0, FLUID_FAILED); /* in case we already have some data */ if((sample->data != NULL || sample->data24 != NULL) && sample->auto_free) { FLUID_FREE(sample->data); FLUID_FREE(sample->data24); } sample->data = NULL; sample->data24 = NULL; if(copy_data) { unsigned int storedNbFrames; /* nbframes should be >= 48 (SoundFont specs) */ storedNbFrames = nbframes; if(storedNbFrames < 48) { storedNbFrames = 48; } storedNbFrames += 2 * SAMPLE_LOOP_MARGIN; sample->data = FLUID_ARRAY(short, storedNbFrames); if(sample->data == NULL) { goto error_rec; } FLUID_MEMSET(sample->data, 0, storedNbFrames * sizeof(short)); FLUID_MEMCPY(sample->data + SAMPLE_LOOP_MARGIN, data, nbframes * sizeof(short)); if(data24 != NULL) { sample->data24 = FLUID_ARRAY(char, storedNbFrames); if(sample->data24 == NULL) { goto error_rec; } FLUID_MEMSET(sample->data24, 0, storedNbFrames); FLUID_MEMCPY(sample->data24 + SAMPLE_LOOP_MARGIN, data24, nbframes * sizeof(char)); } /* pointers */ /* all from the start of data */ sample->start = SAMPLE_LOOP_MARGIN; sample->end = SAMPLE_LOOP_MARGIN + nbframes - 1; } else { /* we cannot assure the SAMPLE_LOOP_MARGIN */ sample->data = data; sample->data24 = data24; sample->start = 0; sample->end = nbframes - 1; } sample->samplerate = sample_rate; sample->sampletype = FLUID_SAMPLETYPE_MONO; sample->auto_free = copy_data; return FLUID_OK; error_rec: FLUID_LOG(FLUID_ERR, "Out of memory"); FLUID_FREE(sample->data); FLUID_FREE(sample->data24); sample->data = NULL; sample->data24 = NULL; return FLUID_FAILED; #undef SAMPLE_LOOP_MARGIN } /** * Set the loop of a sample. * * @param sample SoundFont sample * @param loop_start Start sample index of the loop. * @param loop_end End index of the loop (must be a valid sample as it marks the last sample to be played). * @return #FLUID_OK on success, #FLUID_FAILED otherwise. */ int fluid_sample_set_loop(fluid_sample_t *sample, unsigned int loop_start, unsigned int loop_end) { fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); sample->loopstart = loop_start; sample->loopend = loop_end; return FLUID_OK; } /** * Set the pitch of a sample. * * @param sample SoundFont sample * @param root_key Root MIDI note of sample (0-127) * @param fine_tune Fine tune in cents * @return #FLUID_OK on success, #FLUID_FAILED otherwise. */ int fluid_sample_set_pitch(fluid_sample_t *sample, int root_key, int fine_tune) { fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); fluid_return_val_if_fail(0 <= root_key && root_key <= 127, FLUID_FAILED); sample->origpitch = root_key; sample->pitchadj = fine_tune; return FLUID_OK; } /** * Validate parameters of a sample * */ int fluid_sample_validate(fluid_sample_t *sample, unsigned int buffer_size) { #define EXCLUSIVE_FLAGS (FLUID_SAMPLETYPE_MONO | FLUID_SAMPLETYPE_RIGHT | FLUID_SAMPLETYPE_LEFT) static const unsigned int supported_flags = EXCLUSIVE_FLAGS | FLUID_SAMPLETYPE_LINKED | FLUID_SAMPLETYPE_OGG_VORBIS | FLUID_SAMPLETYPE_ROM; /* ROM samples are unusable for us by definition */ if(sample->sampletype & FLUID_SAMPLETYPE_ROM) { FLUID_LOG(FLUID_WARN, "Sample '%s': ROM sample ignored", sample->name); return FLUID_FAILED; } if(sample->sampletype & ~supported_flags) { FLUID_LOG(FLUID_WARN, "Sample '%s' has unknown flags, possibly using an unsupported compression; sample ignored", sample->name); return FLUID_FAILED; } if((sample->sampletype & EXCLUSIVE_FLAGS) & ((sample->sampletype & EXCLUSIVE_FLAGS) - 1)) { FLUID_LOG(FLUID_INFO, "Sample '%s' should be either mono or left or right; using it anyway", sample->name); } if((sample->sampletype & FLUID_SAMPLETYPE_LINKED) && (sample->sampletype & EXCLUSIVE_FLAGS)) { FLUID_LOG(FLUID_INFO, "Linked sample '%s' should not be mono, left or right at the same time; using it anyway", sample->name); } if((sample->sampletype & EXCLUSIVE_FLAGS) == 0) { FLUID_LOG(FLUID_INFO, "Sample '%s' has no flags set, assuming mono", sample->name); sample->sampletype = FLUID_SAMPLETYPE_MONO; } /* Ogg vorbis compressed samples in the SF3 format use byte indices for * sample start and end pointers before decompression. Standard SF2 samples * use sample word indices for all pointers, so use half the buffer_size * for validation. */ if(!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)) { if(buffer_size % 2) { FLUID_LOG(FLUID_WARN, "Sample '%s': invalid buffer size", sample->name); return FLUID_FAILED; } buffer_size /= 2; } if((sample->end > buffer_size) || (sample->start >= sample->end)) { FLUID_LOG(FLUID_WARN, "Sample '%s': invalid start/end file positions", sample->name); return FLUID_FAILED; } return FLUID_OK; #undef EXCLUSIVE_FLAGS } /* Check the sample loop pointers and optionally convert them to something * usable in case they are broken. Return a boolean indicating if the pointers * have been modified, so the user can be notified of possible audio glitches. */ int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int buffer_size) { int modified = FALSE; unsigned int max_end = buffer_size / 2; /* In fluid_sample_t the sample end pointer points to the last sample, not * to the data word after the last sample. FIXME: why? */ unsigned int sample_end = sample->end + 1; if(sample->loopstart == sample->loopend) { /* Some SoundFonts disable loops by setting loopstart = loopend. While * technically invalid, we decided to accept those samples anyway. Just * ensure that those two pointers are within the sampledata by setting * them to 0. Don't report the modification, as this change has no audible * effect. */ sample->loopstart = sample->loopend = 0; return FALSE; } else if(sample->loopstart > sample->loopend) { unsigned int tmp; /* If loop start and end are reversed, try to swap them around and * continue validation */ FLUID_LOG(FLUID_DBG, "Sample '%s': reversed loop pointers '%d' - '%d', trying to fix", sample->name, sample->loopstart, sample->loopend); tmp = sample->loopstart; sample->loopstart = sample->loopend; sample->loopend = tmp; modified = TRUE; } /* The SoundFont 2.4 spec defines the loopstart index as the first sample * point of the loop while loopend is the first point AFTER the last sample * of the loop. However we cannot be sure whether any of loopend or end is * correct. Hours of thinking through this have concluded that it would be * best practice to mangle with loops as little as necessary by only making * sure the pointers are within sample->start to max_end. Incorrect * soundfont shall preferably fail loudly. */ if((sample->loopstart < sample->start) || (sample->loopstart > max_end)) { FLUID_LOG(FLUID_DBG, "Sample '%s': invalid loop start '%d', setting to sample start '%d'", sample->name, sample->loopstart, sample->start); sample->loopstart = sample->start; modified = TRUE; } if((sample->loopend < sample->start) || (sample->loopend > max_end)) { FLUID_LOG(FLUID_DBG, "Sample '%s': invalid loop end '%d', setting to sample end '%d'", sample->name, sample->loopend, sample_end); sample->loopend = sample_end; modified = TRUE; } if((sample->loopstart > sample_end) || (sample->loopend > sample_end)) { FLUID_LOG(FLUID_DBG, "Sample '%s': loop range '%d - %d' after sample end '%d', using it anyway", sample->name, sample->loopstart, sample->loopend, sample_end); } return modified; } fluidsynth-2.1.1/src/sfloader/fluid_sfont.h000066400000000000000000000156141362231004000207550ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _PRIV_FLUID_SFONT_H #define _PRIV_FLUID_SFONT_H #include "fluidsynth.h" int fluid_sample_validate(fluid_sample_t *sample, unsigned int max_end); int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int max_end); /* * Utility macros to access soundfonts, presets, and samples */ #define fluid_sfloader_delete(_loader) { if ((_loader) && (_loader)->free) (*(_loader)->free)(_loader); } #define fluid_sfloader_load(_loader, _filename) (*(_loader)->load)(_loader, _filename) #define fluid_sfont_delete_internal(_sf) ( ((_sf) && (_sf)->free)? (*(_sf)->free)(_sf) : 0) #define fluid_preset_delete_internal(_preset) \ { if ((_preset) && (_preset)->free) { (*(_preset)->free)(_preset); }} #define fluid_preset_noteon(_preset,_synth,_ch,_key,_vel) \ (*(_preset)->noteon)(_preset,_synth,_ch,_key,_vel) #define fluid_preset_notify(_preset,_reason,_chan) \ { if ((_preset) && (_preset)->notify) { (*(_preset)->notify)(_preset,_reason,_chan); }} #define fluid_sample_incr_ref(_sample) { (_sample)->refcount++; } #define fluid_sample_decr_ref(_sample) \ (_sample)->refcount--; \ if (((_sample)->refcount == 0) && ((_sample)->notify)) \ (*(_sample)->notify)(_sample, FLUID_SAMPLE_DONE); /** * File callback structure to enable custom soundfont loading (e.g. from memory). */ struct _fluid_file_callbacks_t { fluid_sfloader_callback_open_t fopen; fluid_sfloader_callback_read_t fread; fluid_sfloader_callback_seek_t fseek; fluid_sfloader_callback_close_t fclose; fluid_sfloader_callback_tell_t ftell; }; /** * SoundFont loader structure. */ struct _fluid_sfloader_t { void *data; /**< User defined data pointer used by _fluid_sfloader_t::load() */ /** Callback structure specifying file operations used during soundfont loading to allow custom loading, such as from memory */ fluid_file_callbacks_t file_callbacks; fluid_sfloader_free_t free; fluid_sfloader_load_t load; }; /** * Virtual SoundFont instance structure. */ struct _fluid_sfont_t { void *data; /**< User defined data */ int id; /**< SoundFont ID */ int refcount; /**< SoundFont reference count (1 if no presets referencing it) */ int bankofs; /**< Bank offset */ fluid_sfont_free_t free; fluid_sfont_get_name_t get_name; fluid_sfont_get_preset_t get_preset; fluid_sfont_iteration_start_t iteration_start; fluid_sfont_iteration_next_t iteration_next; }; /** * Virtual SoundFont preset. */ struct _fluid_preset_t { void *data; /**< User supplied data */ fluid_sfont_t *sfont; /**< Parent virtual SoundFont */ fluid_preset_free_t free; fluid_preset_get_name_t get_name; fluid_preset_get_banknum_t get_banknum; fluid_preset_get_num_t get_num; fluid_preset_noteon_t noteon; /** * Virtual SoundFont preset notify method. * @param preset Virtual SoundFont preset * @param reason #FLUID_PRESET_SELECTED or #FLUID_PRESET_UNSELECTED * @param chan MIDI channel number * @return Should return #FLUID_OK * * Implement this optional method if the preset needs to be notified about * preset select and unselect events. * * This method may be called from within synthesis context and therefore * should be as efficient as possible and not perform any operations considered * bad for realtime audio output (memory allocations and other OS calls). */ int (*notify)(fluid_preset_t *preset, int reason, int chan); }; /** * Virtual SoundFont sample. */ struct _fluid_sample_t { char name[21]; /**< Sample name */ /* The following for sample pointers store the original pointers from the Soundfont * file. They are never changed after loading and are used to re-create the * actual sample pointers after a sample has been unloaded and loaded again. The * actual sample pointers get modified during loading for SF3 (compressed) samples * and individually loaded SF2 samples. */ unsigned int source_start; unsigned int source_end; unsigned int source_loopstart; unsigned int source_loopend; unsigned int start; /**< Start index */ unsigned int end; /**< End index, index of last valid sample point (contrary to SF spec) */ unsigned int loopstart; /**< Loop start index */ unsigned int loopend; /**< Loop end index, first point following the loop (superimposed on loopstart) */ unsigned int samplerate; /**< Sample rate */ int origpitch; /**< Original pitch (MIDI note number, 0-127) */ int pitchadj; /**< Fine pitch adjustment (+/- 99 cents) */ int sampletype; /**< Specifies the type of this sample as indicated by the #fluid_sample_type enum */ int auto_free; /**< TRUE if _fluid_sample_t::data and _fluid_sample_t::data24 should be freed upon sample destruction */ short *data; /**< Pointer to the sample's 16 bit PCM data */ char *data24; /**< If not NULL, pointer to the least significant byte counterparts of each sample data point in order to create 24 bit audio samples */ int amplitude_that_reaches_noise_floor_is_valid; /**< Indicates if \a amplitude_that_reaches_noise_floor is valid (TRUE), set to FALSE initially to calculate. */ double amplitude_that_reaches_noise_floor; /**< The amplitude at which the sample's loop will be below the noise floor. For voice off optimization, calculated automatically. */ unsigned int refcount; /**< Count of voices using this sample */ int preset_count; /**< Count of selected presets using this sample (used for dynamic sample loading) */ /** * Implement this function to receive notification when sample is no longer used. * @param sample Virtual SoundFont sample * @param reason #FLUID_SAMPLE_DONE only currently * @return Should return #FLUID_OK */ int (*notify)(fluid_sample_t *sample, int reason); }; #endif /* _PRIV_FLUID_SFONT_H */ fluidsynth-2.1.1/src/synth/000077500000000000000000000000001362231004000156275ustar00rootroot00000000000000fluidsynth-2.1.1/src/synth/fluid_chan.c000066400000000000000000000543051362231004000200760ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_chan.h" #include "fluid_mod.h" #include "fluid_synth.h" #include "fluid_sfont.h" /* Field shift amounts for sfont_bank_prog bit field integer */ #define PROG_SHIFTVAL 0 #define BANK_SHIFTVAL 8 #define SFONT_SHIFTVAL 22 /* Field mask values for sfont_bank_prog bit field integer */ #define PROG_MASKVAL 0x000000FF /* Bit 7 is used to indicate unset state */ #define BANK_MASKVAL 0x003FFF00 #define BANKLSB_MASKVAL 0x00007F00 #define BANKMSB_MASKVAL 0x003F8000 #define SFONT_MASKVAL 0xFFC00000 static void fluid_channel_init(fluid_channel_t *chan); fluid_channel_t * new_fluid_channel(fluid_synth_t *synth, int num) { fluid_channel_t *chan; chan = FLUID_NEW(fluid_channel_t); if(chan == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } chan->synth = synth; chan->channum = num; chan->preset = NULL; chan->tuning = NULL; fluid_channel_init(chan); fluid_channel_init_ctrl(chan, 0); return chan; } static void fluid_channel_init(fluid_channel_t *chan) { fluid_preset_t *newpreset; int i, prognum, banknum; chan->sostenuto_orderid = 0; /*--- Init poly/mono modes variables --------------------------------------*/ chan->mode = 0; chan->mode_val = 0; /* monophonic list initialization */ for(i = 0; i < FLUID_CHANNEL_SIZE_MONOLIST; i++) { chan->monolist[i].next = i + 1; } chan->monolist[FLUID_CHANNEL_SIZE_MONOLIST - 1].next = 0; /* ending element chained to the 1st */ chan->i_last = chan->n_notes = 0; /* clears the list */ chan->i_first = chan->monolist[chan->i_last].next; /* first note index in the list */ fluid_channel_clear_prev_note(chan); /* Mark previous note invalid */ /*---*/ chan->key_mono_sustained = INVALID_NOTE; /* No previous mono note sustained */ chan->legatomode = FLUID_CHANNEL_LEGATO_MODE_MULTI_RETRIGGER; /* Default mode */ chan->portamentomode = FLUID_CHANNEL_PORTAMENTO_MODE_LEGATO_ONLY; /* Default mode */ /*--- End of poly/mono initialization --------------------------------------*/ chan->channel_type = (chan->channum == 9) ? CHANNEL_TYPE_DRUM : CHANNEL_TYPE_MELODIC; prognum = 0; banknum = (chan->channel_type == CHANNEL_TYPE_DRUM) ? DRUM_INST_BANK : 0; chan->sfont_bank_prog = 0 << SFONT_SHIFTVAL | banknum << BANK_SHIFTVAL | prognum << PROG_SHIFTVAL; newpreset = fluid_synth_find_preset(chan->synth, banknum, prognum); fluid_channel_set_preset(chan, newpreset); chan->interp_method = FLUID_INTERP_DEFAULT; chan->tuning_bank = 0; chan->tuning_prog = 0; chan->nrpn_select = 0; chan->nrpn_active = 0; if(chan->tuning) { fluid_tuning_unref(chan->tuning, 1); chan->tuning = NULL; } } /* @param is_all_ctrl_off if nonzero, only resets some controllers, according to http://www.midi.org/techspecs/rp15.php */ void fluid_channel_init_ctrl(fluid_channel_t *chan, int is_all_ctrl_off) { int i; chan->channel_pressure = 0; chan->pitch_bend = 0x2000; /* Range is 0x4000, pitch bend wheel starts in centered position */ for(i = 0; i < GEN_LAST; i++) { chan->gen[i] = 0.0f; } if(is_all_ctrl_off) { for(i = 0; i < ALL_SOUND_OFF; i++) { if(i >= EFFECTS_DEPTH1 && i <= EFFECTS_DEPTH5) { continue; } if(i >= SOUND_CTRL1 && i <= SOUND_CTRL10) { continue; } if(i == BANK_SELECT_MSB || i == BANK_SELECT_LSB || i == VOLUME_MSB || i == VOLUME_LSB || i == PAN_MSB || i == PAN_LSB || i == BALANCE_MSB || i == BALANCE_LSB ) { continue; } fluid_channel_set_cc(chan, i, 0); } } else { for(i = 0; i < 128; i++) { fluid_channel_set_cc(chan, i, 0); } fluid_channel_clear_portamento(chan); /* Clear PTC receive */ chan->previous_cc_breath = 0;/* Reset previous breath */ } /* Reset polyphonic key pressure on all voices */ for(i = 0; i < 128; i++) { fluid_channel_set_key_pressure(chan, i, 0); } /* Set RPN controllers to NULL state */ fluid_channel_set_cc(chan, RPN_LSB, 127); fluid_channel_set_cc(chan, RPN_MSB, 127); /* Set NRPN controllers to NULL state */ fluid_channel_set_cc(chan, NRPN_LSB, 127); fluid_channel_set_cc(chan, NRPN_MSB, 127); /* Expression (MSB & LSB) */ fluid_channel_set_cc(chan, EXPRESSION_MSB, 127); fluid_channel_set_cc(chan, EXPRESSION_LSB, 127); if(!is_all_ctrl_off) { chan->pitch_wheel_sensitivity = 2; /* two semi-tones */ /* Just like panning, a value of 64 indicates no change for sound ctrls */ for(i = SOUND_CTRL1; i <= SOUND_CTRL10; i++) { fluid_channel_set_cc(chan, i, 64); } /* Volume / initial attenuation (MSB & LSB) */ fluid_channel_set_cc(chan, VOLUME_MSB, 100); fluid_channel_set_cc(chan, VOLUME_LSB, 0); /* Pan (MSB & LSB) */ fluid_channel_set_cc(chan, PAN_MSB, 64); fluid_channel_set_cc(chan, PAN_LSB, 0); /* Balance (MSB & LSB) */ fluid_channel_set_cc(chan, BALANCE_MSB, 64); fluid_channel_set_cc(chan, BALANCE_LSB, 0); /* Reverb */ /* fluid_channel_set_cc (chan, EFFECTS_DEPTH1, 40); */ /* Note: although XG standard specifies the default amount of reverb to be 40, most people preferred having it at zero. See http://lists.gnu.org/archive/html/fluid-dev/2009-07/msg00016.html */ } } /* Only called by delete_fluid_synth(), so no need to queue a preset free event */ void delete_fluid_channel(fluid_channel_t *chan) { fluid_return_if_fail(chan != NULL); FLUID_FREE(chan); } /* FIXME - Calls fluid_channel_init() potentially in synthesis context */ void fluid_channel_reset(fluid_channel_t *chan) { fluid_channel_init(chan); fluid_channel_init_ctrl(chan, 0); } /* Should only be called from synthesis context */ int fluid_channel_set_preset(fluid_channel_t *chan, fluid_preset_t *preset) { fluid_sfont_t *sfont; if(chan->preset == preset) { return FLUID_OK; } if(chan->preset) { sfont = chan->preset->sfont; sfont->refcount--; } fluid_preset_notify(chan->preset, FLUID_PRESET_UNSELECTED, chan->channum); chan->preset = preset; if(preset) { sfont = preset->sfont; sfont->refcount++; } fluid_preset_notify(preset, FLUID_PRESET_SELECTED, chan->channum); return FLUID_OK; } /* Set SoundFont ID, MIDI bank and/or program. Use -1 to use current value. */ void fluid_channel_set_sfont_bank_prog(fluid_channel_t *chan, int sfontnum, int banknum, int prognum) { int oldval, newval, oldmask; newval = ((sfontnum != -1) ? sfontnum << SFONT_SHIFTVAL : 0) | ((banknum != -1) ? banknum << BANK_SHIFTVAL : 0) | ((prognum != -1) ? prognum << PROG_SHIFTVAL : 0); oldmask = ((sfontnum != -1) ? 0 : SFONT_MASKVAL) | ((banknum != -1) ? 0 : BANK_MASKVAL) | ((prognum != -1) ? 0 : PROG_MASKVAL); oldval = chan->sfont_bank_prog; newval = (newval & ~oldmask) | (oldval & oldmask); chan->sfont_bank_prog = newval; } /* Set bank LSB 7 bits */ void fluid_channel_set_bank_lsb(fluid_channel_t *chan, int banklsb) { int oldval, newval, style; style = chan->synth->bank_select; if(style == FLUID_BANK_STYLE_GM || style == FLUID_BANK_STYLE_GS) { return; /* ignored */ } oldval = chan->sfont_bank_prog; if(style == FLUID_BANK_STYLE_XG) { newval = (oldval & ~BANK_MASKVAL) | (banklsb << BANK_SHIFTVAL); } else /* style == FLUID_BANK_STYLE_MMA */ { newval = (oldval & ~BANKLSB_MASKVAL) | (banklsb << BANK_SHIFTVAL); } chan->sfont_bank_prog = newval; } /* Set bank MSB 7 bits */ void fluid_channel_set_bank_msb(fluid_channel_t *chan, int bankmsb) { int oldval, newval, style; style = chan->synth->bank_select; if(style == FLUID_BANK_STYLE_XG) { /* XG bank, do drum-channel auto-switch */ /* The number "120" was based on several keyboards having drums at 120 - 127, reference: http://lists.nongnu.org/archive/html/fluid-dev/2011-02/msg00003.html */ chan->channel_type = (120 <= bankmsb) ? CHANNEL_TYPE_DRUM : CHANNEL_TYPE_MELODIC; return; } if(style == FLUID_BANK_STYLE_GM || chan->channel_type == CHANNEL_TYPE_DRUM) { return; /* ignored */ } oldval = chan->sfont_bank_prog; if(style == FLUID_BANK_STYLE_GS) { newval = (oldval & ~BANK_MASKVAL) | (bankmsb << BANK_SHIFTVAL); } else /* style == FLUID_BANK_STYLE_MMA */ { newval = (oldval & ~BANKMSB_MASKVAL) | (bankmsb << (BANK_SHIFTVAL + 7)); } chan->sfont_bank_prog = newval; } /* Get SoundFont ID, MIDI bank and/or program. Use NULL to ignore a value. */ void fluid_channel_get_sfont_bank_prog(fluid_channel_t *chan, int *sfont, int *bank, int *prog) { int sfont_bank_prog; sfont_bank_prog = chan->sfont_bank_prog; if(sfont) { *sfont = (sfont_bank_prog & SFONT_MASKVAL) >> SFONT_SHIFTVAL; } if(bank) { *bank = (sfont_bank_prog & BANK_MASKVAL) >> BANK_SHIFTVAL; } if(prog) { *prog = (sfont_bank_prog & PROG_MASKVAL) >> PROG_SHIFTVAL; } } /** * Updates legato/ staccato playing state * The function is called: * - on noteon before adding a note into the monolist. * - on noteoff after removing a note out of the monolist. * @param chan fluid_channel_t. */ static void fluid_channel_update_legato_staccato_state(fluid_channel_t *chan) { /* Updates legato/ staccato playing state */ if(chan->n_notes) { chan->mode |= FLUID_CHANNEL_LEGATO_PLAYING; /* Legato state */ } else { chan->mode &= ~ FLUID_CHANNEL_LEGATO_PLAYING; /* Staccato state */ } } /** * Adds a note into the monophonic list. The function is part of the legato * detector. fluid_channel_add_monolist() is intended to be called by * fluid_synth_noteon_mono_LOCAL(). * * When a note is added at noteOn each element is use in the forward direction * and indexed by i_last variable. * * @param chan fluid_channel_t. * @param key MIDI note number (0-127). * @param vel MIDI velocity (0-127, 0=noteoff). * @param onenote. When 1 the function adds the note but the monophonic list * keeps only one note (used on noteOn poly). * Note: i_last index keeps a trace of the most recent note added. * prev_note keeps a trace of the note prior i_last note. * FLUID_CHANNEL_LEGATO_PLAYING bit keeps trace of legato/staccato playing state. * * More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). */ void fluid_channel_add_monolist(fluid_channel_t *chan, unsigned char key, unsigned char vel, unsigned char onenote) { unsigned char i_last = chan->i_last; /* Updates legato/ staccato playing state */ fluid_channel_update_legato_staccato_state(chan); if(chan->n_notes) { /* keeps trace of the note prior last note */ chan->prev_note = chan->monolist[i_last].note; } /* moves i_last forward before writing new note */ i_last = chan->monolist[i_last].next; chan->i_last = i_last; /* now ilast indexes the last note */ chan->monolist[i_last].note = key; /* we save note and velocity */ chan->monolist[i_last].vel = vel; if(onenote) { /* clears monolist before one note addition */ chan->i_first = i_last; chan->n_notes = 0; } if(chan->n_notes < FLUID_CHANNEL_SIZE_MONOLIST) { chan->n_notes++; /* updates n_notes */ } else { /* The end of buffer is reach. So circular motion for i_first */ /* i_first index is moved forward */ chan->i_first = chan->monolist[i_last].next; } } /** * Searching a note in the monophonic list. The function is part of the legato * detector. fluid_channel_search_monolist() is intended to be called by * fluid_synth_noteoff_mono_LOCAL(). * * The search starts from the first note in the list indexed by i_first * @param chan fluid_channel_t. * @param key MIDI note number (0-127) to search. * @param i_prev pointer on returned index of the note prior the note to search. * @return index of the note if find, FLUID_FAILED otherwise. * */ int fluid_channel_search_monolist(fluid_channel_t *chan, unsigned char key, int *i_prev) { short n = chan->n_notes; /* number of notes in monophonic list */ short j, i = chan->i_first; /* searching starts from i_first included */ for(j = 0 ; j < n ; j++) { if(chan->monolist[i].note == key) { if(i == chan->i_first) { /* tracking index of the previous note (i_prev) */ for(j = chan->i_last ; n < FLUID_CHANNEL_SIZE_MONOLIST; n++) { j = chan->monolist[j].next; } * i_prev = j; /* returns index of the previous note */ } return i; /* returns index of the note to search */ } * i_prev = i; /* tracking index of the previous note (i_prev) */ i = chan->monolist[i].next; /* next element */ } return FLUID_FAILED; /* not found */ } /** * removes a note from the monophonic list. The function is part of * the legato detector. * fluid_channel_remove_monolist() is intended to be called by * fluid_synth_noteoff_mono_LOCAL(). * * When a note is removed at noteOff the element concerned is fast unlinked * and relinked after the i_last element. * * @param chan fluid_channel_t. * @param * i, index of the note to remove. If i is invalid or the list is * empty, the function do nothing and returns FLUID_FAILED. * @param * On input, i_prev is a pointer on index of the note previous i. * On output i_prev is a pointer on index of the note previous i if i is the last note * in the list,FLUID_FAILED otherwise. When the returned index is valid it means * a legato detection on noteoff. * * Note: the following variables in Channel keeps trace of the situation. * - i_last index keeps a trace of the most recent note played even if * the list is empty. * - prev_note keeps a trace of the note removed if it is i_last. * - FLUID_CHANNEL_LEGATO_PLAYING bit keeps a trace of legato/staccato playing state. * * More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). */ void fluid_channel_remove_monolist(fluid_channel_t *chan, int i, int *i_prev) { unsigned char i_last = chan->i_last; /* checks if index is valid */ if(i < 0 || i >= FLUID_CHANNEL_SIZE_MONOLIST || !chan->n_notes) { * i_prev = FLUID_FAILED; } /* The element is about to be removed and inserted between i_last and next */ /* Note: when i is egal to i_last or egal to i_first, removing/inserting isn't necessary */ if(i == i_last) { /* Removing/Inserting isn't necessary */ /* keeps trace of the note prior last note */ chan->prev_note = chan->monolist[i_last].note; /* moves i_last backward to the previous */ chan->i_last = *i_prev; /* i_last index is moved backward */ } else { /* i is before i_last */ if(i == chan->i_first) { /* Removing/inserting isn't necessary */ /* i_first index is moved forward to the next element*/ chan->i_first = chan->monolist[i].next; } else { /* i is between i_first and i_last */ /* Unlinks element i and inserts after i_last */ chan->monolist[* i_prev].next = chan->monolist[i].next; /* unlinks i */ /*inserts i after i_last */ chan->monolist[i].next = chan->monolist[i_last].next; chan->monolist[i_last].next = i; } * i_prev = FLUID_FAILED; } chan->n_notes--; /* updates the number of note in the list */ /* Updates legato/ staccato playing state */ fluid_channel_update_legato_staccato_state(chan); } /** * On noteOff on a polyphonic channel,the monophonic list is fully flushed. * * @param chan fluid_channel_t. * Note: i_last index keeps a trace of the most recent note played even if * the list is empty. * prev_note keeps a trace of the note i_last . * FLUID_CHANNEL_LEGATO_PLAYING bit keeps a trace of legato/staccato playing. */ void fluid_channel_clear_monolist(fluid_channel_t *chan) { /* keeps trace off the most recent note played */ chan->prev_note = chan->monolist[chan->i_last].note; /* flushes the monolist */ chan->i_first = chan->monolist[chan->i_last].next; chan->n_notes = 0; /* Update legato/ sataccato playing state */ chan->mode &= ~ FLUID_CHANNEL_LEGATO_PLAYING; /* Staccato state */ } /** * On noteOn on a polyphonic channel,adds the note into the monophonic list * keeping only this note. * @param * chan fluid_channel_t. * key, vel, note and velocity added in the monolist * Note: i_last index keeps a trace of the most recent note inserted. * prev_note keeps a trace of the note prior i_last note. * FLUID_CHANNEL_LEGATO_PLAYING bit keeps trace of legato/staccato playing. */ void fluid_channel_set_onenote_monolist(fluid_channel_t *chan, unsigned char key, unsigned char vel) { fluid_channel_add_monolist(chan, key, vel, 1); } /** * The function changes the state (Valid/Invalid) of the previous note played in * a staccato manner (fluid_channel_prev_note()). * When potamento mode 'each note' or 'staccato only' is selected, on next * noteOn a portamento will be started from the most recent note played * staccato. * It will be possible that it isn't appropriate. To give the musician the * possibility to choose a portamento from this note , prev_note will be forced * to invalid state on noteOff if portamento pedal is Off. * * The function is intended to be called when the following event occurs: * - On noteOff (in poly or mono mode), to mark prev_note invalid. * - On Portamento Off(in poly or mono mode), to mark prev_note invalid. * @param chan fluid_channel_t. */ void fluid_channel_invalid_prev_note_staccato(fluid_channel_t *chan) { /* checks if the playing is staccato */ if(!(chan->mode & FLUID_CHANNEL_LEGATO_PLAYING)) { /* checks if portamento pedal is off */ if(! fluid_channel_portamento(chan)) { /* forces prev_note invalid */ fluid_channel_clear_prev_note(chan); } } /* else prev_note still remains valid for next fromkey portamento */ } /** * The function handles poly/mono commutation on legato pedal On/Off. * @param chan fluid_channel_t. * @param value, value of the CC legato. */ void fluid_channel_cc_legato(fluid_channel_t *chan, int value) { /* Special handling of the monophonic list */ if(!(chan->mode & FLUID_CHANNEL_POLY_OFF) && chan->n_notes) /* The monophonic list have notes */ { if(value < 64) /* legato is released */ { /* returns from monophonic to polyphonic with notes in the monophonic list */ /* The monophonic list is flushed keeping last note only Note: i_last index keeps a trace of the most recent note played. prev_note keeps a trace of the note i_last. FLUID_CHANNEL_LEGATO_PLAYING bit keeps trace of legato/staccato playing. */ chan->i_first = chan->i_last; chan->n_notes = 1; } else /* legato is depressed */ { /* Inters in monophonic from polyphonic with note in monophonic list */ /* Stops the running note to remain coherent with Breath Sync mode */ if((chan->mode & FLUID_CHANNEL_BREATH_SYNC) && !fluid_channel_breath_msb(chan)) { fluid_synth_noteoff_monopoly(chan->synth, chan->channum, fluid_channel_last_note(chan), 1); } } } } /** * The function handles CC Breath On/Off detection. When a channel is in * Breath Sync mode and in monophonic playing, the breath controller allows * to trigger noteon/noteoff note when the musician starts to breath (noteon) and * stops to breath (noteoff). * @param chan fluid_channel_t. * @param value, value of the CC Breath.. */ void fluid_channel_cc_breath_note_on_off(fluid_channel_t *chan, int value) { if((chan->mode & FLUID_CHANNEL_BREATH_SYNC) && fluid_channel_is_playing_mono(chan) && (chan->n_notes)) { /* The monophonic list isn't empty */ if((value > 0) && (chan->previous_cc_breath == 0)) { /* CC Breath On detection */ fluid_synth_noteon_mono_staccato(chan->synth, chan->channum, fluid_channel_last_note(chan), fluid_channel_last_vel(chan)); } else if((value == 0) && (chan->previous_cc_breath > 0)) { /* CC Breath Off detection */ fluid_synth_noteoff_monopoly(chan->synth, chan->channum, fluid_channel_last_note(chan), 1); } } chan->previous_cc_breath = value; } fluidsynth-2.1.1/src/synth/fluid_chan.h000066400000000000000000000321201362231004000200720ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_CHAN_H #define _FLUID_CHAN_H #include "fluidsynth_priv.h" #include "fluid_midi.h" #include "fluid_tuning.h" /* The mononophonic list is part of the legato detector for monophonic mode */ /* see fluid_synth_monopoly.c about a description of the legato detector device */ /* Size of the monophonic list - 1 is the minimum. it allows playing legato passage of any number of notes on noteon only. - Size above 1 allows playing legato on noteon but also on noteOff. This allows the musician to play fast trills. This feature is particularly usful when the MIDI input device is a keyboard. Choosing a size of 10 is sufficient (because most musicians have only 10 fingers when playing a monophonic instrument). */ #define FLUID_CHANNEL_SIZE_MONOLIST 10 /* The monophonic list +------------------------------------------------+ | +----+ +----+ +----+ +----+ | | |note| |note| |note| |note| | +--->|vel |-->|vel |-->....-->|vel |-->|vel |----+ +----+ +----+ +----+ +----+ /|\ /|\ | | i_first i_last The monophonic list is a circular buffer of FLUID_CHANNEL_SIZE_MONOLIST elements. Each element is linked forward at initialisation time. - when a note is added at noteOn (see fluid_channel_add_monolist()) each element is use in the forward direction and indexed by i_last variable. - when a note is removed at noteOff (see fluid_channel_remove_monolist()), the element concerned is fast unlinked and relinked after the i_last element. The most recent note added is indexed by i_last. The most ancient note added is the first note indexed by i_first. i_first is moving in the forward direction in a circular manner. */ struct mononote { unsigned char next; /* next note */ unsigned char note; /* note */ unsigned char vel; /* velocity */ }; /* * fluid_channel_t * * Mutual exclusion notes (as of 1.1.2): * None - everything should have been synchronized by the synth. */ struct _fluid_channel_t { fluid_synth_t *synth; /**< Parent synthesizer instance */ int channum; /**< MIDI channel number */ /* Poly Mono variables see macro access description */ int mode; /**< Poly Mono mode */ int mode_val; /**< number of channel in basic channel group */ /* monophonic list - legato detector */ unsigned char i_first; /**< First note index */ unsigned char i_last; /**< most recent note index since the most recent add */ unsigned char prev_note; /**< previous note of the most recent add/remove */ unsigned char n_notes; /**< actual number of notes in the list */ struct mononote monolist[FLUID_CHANNEL_SIZE_MONOLIST]; /**< monophonic list */ unsigned char key_mono_sustained; /**< previous sustained monophonic note */ unsigned char previous_cc_breath; /**< Previous Breath */ enum fluid_channel_legato_mode legatomode; /**< legato mode */ enum fluid_channel_portamento_mode portamentomode; /**< portamento mode */ /*- End of Poly/mono variables description */ unsigned char cc[128]; /**< MIDI controller values from [0;127] */ unsigned char key_pressure[128]; /**< MIDI polyphonic key pressure from [0;127] */ /* Drum channel flag, CHANNEL_TYPE_MELODIC, or CHANNEL_TYPE_DRUM. */ enum fluid_midi_channel_type channel_type; enum fluid_interp interp_method; /**< Interpolation method (enum fluid_interp) */ unsigned char channel_pressure; /**< MIDI channel pressure from [0;127] */ unsigned char pitch_wheel_sensitivity; /**< Current pitch wheel sensitivity */ short pitch_bend; /**< Current pitch bend value */ /* Sostenuto order id gives the order of SostenutoOn event. * This value is useful to known when the sostenuto pedal is depressed * (before or after a key note). We need to compare SostenutoOrderId with voice id. */ unsigned int sostenuto_orderid; int tuning_bank; /**< Current tuning bank number */ int tuning_prog; /**< Current tuning program number */ fluid_tuning_t *tuning; /**< Micro tuning */ fluid_preset_t *preset; /**< Selected preset */ int sfont_bank_prog; /**< SoundFont ID (bit 21-31), bank (bit 7-20), program (bit 0-6) */ /* NRPN system */ enum fluid_gen_type nrpn_select; /* Generator ID of SoundFont NRPN message */ char nrpn_active; /* 1 if data entry CCs are for NRPN, 0 if RPN */ /* The values of the generators, set by NRPN messages, or by * fluid_synth_set_gen(), are cached in the channel so they can be * applied to future notes. They are copied to a voice's generators * in fluid_voice_init(), which calls fluid_gen_init(). */ fluid_real_t gen[GEN_LAST]; }; fluid_channel_t *new_fluid_channel(fluid_synth_t *synth, int num); void fluid_channel_init_ctrl(fluid_channel_t *chan, int is_all_ctrl_off); void delete_fluid_channel(fluid_channel_t *chan); void fluid_channel_reset(fluid_channel_t *chan); int fluid_channel_set_preset(fluid_channel_t *chan, fluid_preset_t *preset); void fluid_channel_set_sfont_bank_prog(fluid_channel_t *chan, int sfont, int bank, int prog); void fluid_channel_set_bank_lsb(fluid_channel_t *chan, int banklsb); void fluid_channel_set_bank_msb(fluid_channel_t *chan, int bankmsb); void fluid_channel_get_sfont_bank_prog(fluid_channel_t *chan, int *sfont, int *bank, int *prog); #define fluid_channel_get_preset(chan) ((chan)->preset) #define fluid_channel_set_cc(chan, num, val) \ ((chan)->cc[num] = (val)) #define fluid_channel_get_cc(chan, num) \ ((chan)->cc[num]) #define fluid_channel_get_key_pressure(chan, key) \ ((chan)->key_pressure[key]) #define fluid_channel_set_key_pressure(chan, key, val) \ ((chan)->key_pressure[key] = (val)) #define fluid_channel_get_channel_pressure(chan) \ ((chan)->channel_pressure) #define fluid_channel_set_channel_pressure(chan, val) \ ((chan)->channel_pressure = (val)) #define fluid_channel_get_pitch_bend(chan) \ ((chan)->pitch_bend) #define fluid_channel_set_pitch_bend(chan, val) \ ((chan)->pitch_bend = (val)) #define fluid_channel_get_pitch_wheel_sensitivity(chan) \ ((chan)->pitch_wheel_sensitivity) #define fluid_channel_set_pitch_wheel_sensitivity(chan, val) \ ((chan)->pitch_wheel_sensitivity = (val)) #define fluid_channel_get_num(chan) ((chan)->channum) #define fluid_channel_set_interp_method(chan, new_method) \ ((chan)->interp_method = (new_method)) #define fluid_channel_get_interp_method(chan) \ ((chan)->interp_method); #define fluid_channel_set_tuning(_c, _t) { (_c)->tuning = _t; } #define fluid_channel_has_tuning(_c) ((_c)->tuning != NULL) #define fluid_channel_get_tuning(_c) ((_c)->tuning) #define fluid_channel_get_tuning_bank(chan) \ ((chan)->tuning_bank) #define fluid_channel_set_tuning_bank(chan, bank) \ ((chan)->tuning_bank = (bank)) #define fluid_channel_get_tuning_prog(chan) \ ((chan)->tuning_prog) #define fluid_channel_set_tuning_prog(chan, prog) \ ((chan)->tuning_prog = (prog)) #define fluid_channel_portamentotime(_c) \ ((_c)->cc[PORTAMENTO_TIME_MSB] * 128 + (_c)->cc[PORTAMENTO_TIME_LSB]) #define fluid_channel_portamento(_c) ((_c)->cc[PORTAMENTO_SWITCH] >= 64) #define fluid_channel_breath_msb(_c) ((_c)->cc[BREATH_MSB] > 0) #define fluid_channel_clear_portamento(_c) ((_c)->cc[PORTAMENTO_CTRL] = INVALID_NOTE) #define fluid_channel_legato(_c) ((_c)->cc[LEGATO_SWITCH] >= 64) #define fluid_channel_sustained(_c) ((_c)->cc[SUSTAIN_SWITCH] >= 64) #define fluid_channel_sostenuto(_c) ((_c)->cc[SOSTENUTO_SWITCH] >= 64) #define fluid_channel_set_gen(_c, _n, _v) { (_c)->gen[_n] = _v; } #define fluid_channel_get_gen(_c, _n) ((_c)->gen[_n]) #define fluid_channel_get_min_note_length_ticks(chan) \ ((chan)->synth->min_note_length_ticks) /* Macros interface to poly/mono mode variables */ #define MASK_BASICCHANINFOS (FLUID_CHANNEL_MODE_MASK|FLUID_CHANNEL_BASIC|FLUID_CHANNEL_ENABLED) /* Set the basic channel infos for a MIDI basic channel */ #define fluid_channel_set_basic_channel_info(chan,Infos) \ (chan->mode = (chan->mode & ~MASK_BASICCHANINFOS) | (Infos & MASK_BASICCHANINFOS)) /* Reset the basic channel infos for a MIDI basic channel */ #define fluid_channel_reset_basic_channel_info(chan) (chan->mode &= ~MASK_BASICCHANINFOS) /* Macros interface to breath variables */ #define FLUID_CHANNEL_BREATH_MASK (FLUID_CHANNEL_BREATH_POLY|FLUID_CHANNEL_BREATH_MONO|FLUID_CHANNEL_BREATH_SYNC) /* Set the breath infos for a MIDI channel */ #define fluid_channel_set_breath_info(chan,BreathInfos) \ (chan->mode = (chan->mode & ~FLUID_CHANNEL_BREATH_MASK) | (BreathInfos & FLUID_CHANNEL_BREATH_MASK)) /* Get the breath infos for a MIDI channel */ #define fluid_channel_get_breath_info(chan) (chan->mode & FLUID_CHANNEL_BREATH_MASK) /* Returns true when channel is mono or legato is on */ #define fluid_channel_is_playing_mono(chan) ((chan->mode & FLUID_CHANNEL_POLY_OFF) ||\ fluid_channel_legato(chan)) /* Macros interface to monophonic list variables */ #define INVALID_NOTE (255) /* Returns true when a note is a valid note */ #define fluid_channel_is_valid_note(n) (n != INVALID_NOTE) /* Marks prev_note as invalid. */ #define fluid_channel_clear_prev_note(chan) (chan->prev_note = INVALID_NOTE) /* Returns the most recent note from i_last entry of the monophonic list */ #define fluid_channel_last_note(chan) (chan->monolist[chan->i_last].note) /* Returns the most recent velocity from i_last entry of the monophonic list */ #define fluid_channel_last_vel(chan) (chan->monolist[chan->i_last].vel) /* prev_note is used to determine fromkey_portamento as well as fromkey_legato (see fluid_synth_get_fromkey_portamento_legato()). prev_note is updated on noteOn/noteOff mono by the legato detector as this: - On noteOn mono, before adding a new note into the monolist,the most recent note in the list (i.e at i_last position) is kept in prev_note. - Similarly, on noteOff mono , before removing a note out of the monolist, the most recent note (i.e those at i_last position) is kept in prev_note. */ #define fluid_channel_prev_note(chan) (chan->prev_note) /* Interface to poly/mono mode variables */ enum fluid_channel_mode_flags_internal { FLUID_CHANNEL_BASIC = 0x04, /**< if flag set the corresponding midi channel is a basic channel */ FLUID_CHANNEL_ENABLED = 0x08, /**< if flag set the corresponding midi channel is enabled, else disabled, i.e. channel ignores any MIDI messages */ /* FLUID_CHANNEL_LEGATO_PLAYING bit of channel mode keeps trace of the legato /staccato state playing. FLUID_CHANNEL_LEGATO_PLAYING bit is updated on noteOn/noteOff mono by the legato detector: - On noteOn, before inserting a new note into the monolist. - On noteOff, after removing a note out of the monolist. - On noteOn, this state is used by fluid_synth_noteon_mono_LOCAL() to play the current note legato or staccato. - On noteOff, this state is used by fluid_synth_noteoff_mono_LOCAL() to play the current noteOff legato with the most recent note. */ /* bit7, 1: means legato playing , 0: means staccato playing */ FLUID_CHANNEL_LEGATO_PLAYING = 0x80 }; /* End of interface to monophonic list variables */ void fluid_channel_add_monolist(fluid_channel_t *chan, unsigned char key, unsigned char vel, unsigned char onenote); int fluid_channel_search_monolist(fluid_channel_t *chan, unsigned char key, int *i_prev); void fluid_channel_remove_monolist(fluid_channel_t *chan, int i, int *i_prev); void fluid_channel_clear_monolist(fluid_channel_t *chan); void fluid_channel_set_onenote_monolist(fluid_channel_t *chan, unsigned char key, unsigned char vel); void fluid_channel_invalid_prev_note_staccato(fluid_channel_t *chan); void fluid_channel_cc_legato(fluid_channel_t *chan, int value); void fluid_channel_cc_breath_note_on_off(fluid_channel_t *chan, int value); #endif /* _FLUID_CHAN_H */ fluidsynth-2.1.1/src/synth/fluid_event.c000066400000000000000000000462571362231004000203150ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* 2002 : API design by Peter Hanappe and Antoine Schmitt August 2002 : Implementation by Antoine Schmitt as@gratin.org as part of the infiniteCD author project http://www.infiniteCD.org/ Oct4.2002 : AS : corrected bug in heap allocation, that caused a crash during sequencer free. */ #include "fluid_event.h" #include "fluidsynth_priv.h" /*************************************************************** * * SEQUENCER EVENTS */ /* Event alloc/free */ void fluid_event_clear(fluid_event_t *evt) { FLUID_MEMSET(evt, 0, sizeof(fluid_event_t)); // by default, no type evt->dest = -1; evt->src = -1; evt->type = -1; } /** * Create a new sequencer event structure. * @return New sequencer event structure or NULL if out of memory */ fluid_event_t * new_fluid_event() { fluid_event_t *evt; evt = FLUID_NEW(fluid_event_t); if(evt == NULL) { FLUID_LOG(FLUID_PANIC, "event: Out of memory\n"); return NULL; } fluid_event_clear(evt); return(evt); } /** * Delete a sequencer event structure. * @param evt Sequencer event structure created by new_fluid_event(). */ void delete_fluid_event(fluid_event_t *evt) { fluid_return_if_fail(evt != NULL); FLUID_FREE(evt); } /** * Set the time field of a sequencer event. * @internal * @param evt Sequencer event structure * @param time Time value to assign */ void fluid_event_set_time(fluid_event_t *evt, unsigned int time) { evt->time = time; } /** * Set source of a sequencer event. \c src must be a unique sequencer ID or -1 if not set. * @param evt Sequencer event structure * @param src Unique sequencer ID */ void fluid_event_set_source(fluid_event_t *evt, fluid_seq_id_t src) { evt->src = src; } /** * Set destination of this sequencer event, i.e. the sequencer client this event will be sent to. \c dest must be a unique sequencer ID. * @param evt Sequencer event structure * @param dest The destination unique sequencer ID */ void fluid_event_set_dest(fluid_event_t *evt, fluid_seq_id_t dest) { evt->dest = dest; } /** * Set a sequencer event to be a timer event. * @param evt Sequencer event structure * @param data User supplied data pointer */ void fluid_event_timer(fluid_event_t *evt, void *data) { evt->type = FLUID_SEQ_TIMER; evt->data = data; } /** * Set a sequencer event to be a note on event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param key MIDI note number (0-127) * @param vel MIDI velocity value (0-127) */ void fluid_event_noteon(fluid_event_t *evt, int channel, short key, short vel) { evt->type = FLUID_SEQ_NOTEON; evt->channel = channel; evt->key = key; evt->vel = vel; } /** * Set a sequencer event to be a note off event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param key MIDI note number (0-127) */ void fluid_event_noteoff(fluid_event_t *evt, int channel, short key) { evt->type = FLUID_SEQ_NOTEOFF; evt->channel = channel; evt->key = key; } /** * Set a sequencer event to be a note duration event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param key MIDI note number (0-127) * @param vel MIDI velocity value (0-127) * @param duration Duration of note in the time scale used by the sequencer (by default milliseconds) */ void fluid_event_note(fluid_event_t *evt, int channel, short key, short vel, unsigned int duration) { evt->type = FLUID_SEQ_NOTE; evt->channel = channel; evt->key = key; evt->vel = vel; evt->duration = duration; } /** * Set a sequencer event to be an all sounds off event. * @param evt Sequencer event structure * @param channel MIDI channel number */ void fluid_event_all_sounds_off(fluid_event_t *evt, int channel) { evt->type = FLUID_SEQ_ALLSOUNDSOFF; evt->channel = channel; } /** * Set a sequencer event to be a all notes off event. * @param evt Sequencer event structure * @param channel MIDI channel number */ void fluid_event_all_notes_off(fluid_event_t *evt, int channel) { evt->type = FLUID_SEQ_ALLNOTESOFF; evt->channel = channel; } /** * Set a sequencer event to be a bank select event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param bank_num MIDI bank number (0-16383) */ void fluid_event_bank_select(fluid_event_t *evt, int channel, short bank_num) { evt->type = FLUID_SEQ_BANKSELECT; evt->channel = channel; evt->control = bank_num; } /** * Set a sequencer event to be a program change event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val MIDI program number (0-127) */ void fluid_event_program_change(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_PROGRAMCHANGE; evt->channel = channel; evt->value = val; } /** * Set a sequencer event to be a program select event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param sfont_id SoundFont ID number * @param bank_num MIDI bank number (0-16383) * @param preset_num MIDI preset number (0-127) */ void fluid_event_program_select(fluid_event_t *evt, int channel, unsigned int sfont_id, short bank_num, short preset_num) { evt->type = FLUID_SEQ_PROGRAMSELECT; evt->channel = channel; evt->duration = sfont_id; evt->value = preset_num; evt->control = bank_num; } /** * Set a sequencer event to be an any control change event (for internal use). * @param evt Sequencer event structure * @param channel MIDI channel number */ void fluid_event_any_control_change(fluid_event_t *evt, int channel) { evt->type = FLUID_SEQ_ANYCONTROLCHANGE; evt->channel = channel; } /** * Set a sequencer event to be a pitch bend event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param pitch MIDI pitch bend value (0-16383, 8192 = no bend) */ void fluid_event_pitch_bend(fluid_event_t *evt, int channel, int pitch) { evt->type = FLUID_SEQ_PITCHBEND; evt->channel = channel; if(pitch < 0) { pitch = 0; } if(pitch > 16383) { pitch = 16383; } evt->pitch = pitch; } /** * Set a sequencer event to be a pitch wheel sensitivity event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param value MIDI pitch wheel sensitivity value in semitones */ void fluid_event_pitch_wheelsens(fluid_event_t *evt, int channel, short value) { evt->type = FLUID_SEQ_PITCHWHEELSENS; evt->channel = channel; evt->value = value; } /** * Set a sequencer event to be a modulation event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val MIDI modulation value (0-127) */ void fluid_event_modulation(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_MODULATION; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be a MIDI sustain event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val MIDI sustain value (0-127) */ void fluid_event_sustain(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_SUSTAIN; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be a MIDI control change event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param control MIDI control number (0-127) * @param val MIDI control value (0-127) */ void fluid_event_control_change(fluid_event_t *evt, int channel, short control, short val) { evt->type = FLUID_SEQ_CONTROLCHANGE; evt->channel = channel; evt->control = control; evt->value = val; } /** * Set a sequencer event to be a stereo pan event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val MIDI panning value (0-127, 0=left, 64 = middle, 127 = right) */ void fluid_event_pan(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_PAN; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be a volume event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val Volume value (0-127) */ void fluid_event_volume(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_VOLUME; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be a reverb send event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val Reverb amount (0-127) */ void fluid_event_reverb_send(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_REVERBSEND; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be a chorus send event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val Chorus amount (0-127) */ void fluid_event_chorus_send(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_CHORUSSEND; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be an unregistering event. * @param evt Sequencer event structure * @since 1.1.0 */ void fluid_event_unregistering(fluid_event_t *evt) { evt->type = FLUID_SEQ_UNREGISTERING; } /** * Set a sequencer event to be a channel-wide aftertouch event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param val Aftertouch amount (0-127) * @since 1.1.0 */ void fluid_event_channel_pressure(fluid_event_t *evt, int channel, short val) { evt->type = FLUID_SEQ_CHANNELPRESSURE; evt->channel = channel; if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->value = val; } /** * Set a sequencer event to be a polyphonic aftertouch event. * @param evt Sequencer event structure * @param channel MIDI channel number * @param key MIDI note number (0-127) * @param val Aftertouch amount (0-127) * @since 2.0.0 */ void fluid_event_key_pressure(fluid_event_t *evt, int channel, short key, short val) { evt->type = FLUID_SEQ_KEYPRESSURE; evt->channel = channel; if(key < 0) { key = 0; } if(key > 127) { key = 127; } if(val < 0) { val = 0; } if(val > 127) { val = 127; } evt->key = key; evt->value = val; } /** * Set a sequencer event to be a midi system reset event. * @param evt Sequencer event structure * @since 1.1.0 */ void fluid_event_system_reset(fluid_event_t *evt) { evt->type = FLUID_SEQ_SYSTEMRESET; } /* * Accessing event data */ /** * Get the event type (#fluid_seq_event_type) field from a sequencer event structure. * @param evt Sequencer event structure * @return Event type (#fluid_seq_event_type). */ int fluid_event_get_type(fluid_event_t *evt) { return evt->type; } /** * @internal * Get the time field from a sequencer event structure. * @param evt Sequencer event structure * @return Time value */ unsigned int fluid_event_get_time(fluid_event_t *evt) { return evt->time; } /** * Get the source sequencer client from a sequencer event structure. * @param evt Sequencer event structure * @return source field of the sequencer event */ fluid_seq_id_t fluid_event_get_source(fluid_event_t *evt) { return evt->src; } /** * Get the dest sequencer client from a sequencer event structure. * @param evt Sequencer event structure * @return dest field of the sequencer event */ fluid_seq_id_t fluid_event_get_dest(fluid_event_t *evt) { return evt->dest; } /** * Get the MIDI channel field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI zero-based channel number */ int fluid_event_get_channel(fluid_event_t *evt) { return evt->channel; } /** * Get the MIDI note field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI note number (0-127) */ short fluid_event_get_key(fluid_event_t *evt) { return evt->key; } /** * Get the MIDI velocity field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI velocity value (0-127) */ short fluid_event_get_velocity(fluid_event_t *evt) { return evt->vel; } /** * Get the MIDI control number field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI control number (0-127) */ short fluid_event_get_control(fluid_event_t *evt) { return evt->control; } /** * Get the value field from a sequencer event structure. * @param evt Sequencer event structure * @return Value field of event. * * The Value field is used by the following event types: * #FLUID_SEQ_PROGRAMCHANGE, #FLUID_SEQ_PROGRAMSELECT (preset_num), * #FLUID_SEQ_PITCHWHEELSENS, #FLUID_SEQ_MODULATION, #FLUID_SEQ_SUSTAIN, * #FLUID_SEQ_CONTROLCHANGE, #FLUID_SEQ_PAN, #FLUID_SEQ_VOLUME, * #FLUID_SEQ_REVERBSEND, #FLUID_SEQ_CHORUSSEND. */ short fluid_event_get_value(fluid_event_t *evt) { return evt->value; } /** * Get the data field from a sequencer event structure. * @param evt Sequencer event structure * @return Data field of event. * * Used by the #FLUID_SEQ_TIMER event type. */ void *fluid_event_get_data(fluid_event_t *evt) { return evt->data; } /** * Get the duration field from a sequencer event structure. * @param evt Sequencer event structure * @return Note duration value in the time scale used by the sequencer (by default milliseconds) * * Used by the #FLUID_SEQ_NOTE event type. */ unsigned int fluid_event_get_duration(fluid_event_t *evt) { return evt->duration; } /** * Get the MIDI bank field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI bank number (0-16383) * * Used by the #FLUID_SEQ_BANKSELECT and #FLUID_SEQ_PROGRAMSELECT * event types. */ short fluid_event_get_bank(fluid_event_t *evt) { return evt->control; } /** * Get the pitch field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI pitch bend pitch value (0-16383, 8192 = no bend) * * Used by the #FLUID_SEQ_PITCHBEND event type. */ int fluid_event_get_pitch(fluid_event_t *evt) { return evt->pitch; } /** * Get the MIDI program field from a sequencer event structure. * @param evt Sequencer event structure * @return MIDI program number (0-127) * * Used by the #FLUID_SEQ_PROGRAMCHANGE and #FLUID_SEQ_PROGRAMSELECT * event types. */ short fluid_event_get_program(fluid_event_t *evt) { return evt->value; } /** * Get the SoundFont ID field from a sequencer event structure. * @param evt Sequencer event structure * @return SoundFont identifier value. * * Used by the #FLUID_SEQ_PROGRAMSELECT event type. */ unsigned int fluid_event_get_sfont_id(fluid_event_t *evt) { return evt->duration; } /********************/ /* heap management */ /********************/ fluid_evt_heap_t * _fluid_evt_heap_init(int nbEvents) { #ifdef HEAP_WITH_DYNALLOC int i; fluid_evt_heap_t *heap; fluid_evt_entry *tmp; heap = FLUID_NEW(fluid_evt_heap_t); if(heap == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return NULL; } heap->freelist = NULL; fluid_mutex_init(heap->mutex); /* LOCK */ fluid_mutex_lock(heap->mutex); /* Allocate the event entries */ for(i = 0; i < nbEvents; i++) { tmp = FLUID_NEW(fluid_evt_entry); tmp->next = heap->freelist; heap->freelist = tmp; } /* UNLOCK */ fluid_mutex_unlock(heap->mutex); #else int i; fluid_evt_heap_t *heap; int siz = 2 * sizeof(fluid_evt_entry *) + sizeof(fluid_evt_entry) * nbEvents; heap = (fluid_evt_heap_t *)FLUID_MALLOC(siz); if(heap == NULL) { FLUID_LOG(FLUID_PANIC, "sequencer: Out of memory\n"); return NULL; } FLUID_MEMSET(heap, 0, siz); /* link all heap events */ { fluid_evt_entry *tmp = &(heap->pool); for(i = 0 ; i < nbEvents - 1 ; i++) { tmp[i].next = &(tmp[i + 1]); } tmp[nbEvents - 1].next = NULL; /* set head & tail */ heap->tail = &(tmp[nbEvents - 1]); heap->head = &(heap->pool); } #endif return (heap); } void _fluid_evt_heap_free(fluid_evt_heap_t *heap) { #ifdef HEAP_WITH_DYNALLOC fluid_evt_entry *tmp, *next; /* LOCK */ fluid_mutex_lock(heap->mutex); tmp = heap->freelist; while(tmp) { next = tmp->next; FLUID_FREE(tmp); tmp = next; } /* UNLOCK */ fluid_mutex_unlock(heap->mutex); fluid_mutex_destroy(heap->mutex); FLUID_FREE(heap); #else FLUID_FREE(heap); #endif } fluid_evt_entry * _fluid_seq_heap_get_free(fluid_evt_heap_t *heap) { #ifdef HEAP_WITH_DYNALLOC fluid_evt_entry *evt = NULL; /* LOCK */ fluid_mutex_lock(heap->mutex); #if !defined(MACOS9) if(heap->freelist == NULL) { heap->freelist = FLUID_NEW(fluid_evt_entry); if(heap->freelist != NULL) { heap->freelist->next = NULL; } } #endif evt = heap->freelist; if(evt != NULL) { heap->freelist = heap->freelist->next; evt->next = NULL; } /* UNLOCK */ fluid_mutex_unlock(heap->mutex); return evt; #else fluid_evt_entry *evt; if(heap->head == NULL) { return NULL; } /* take from head of the heap */ /* critical - should threadlock ? */ evt = heap->head; heap->head = heap->head->next; return evt; #endif } void _fluid_seq_heap_set_free(fluid_evt_heap_t *heap, fluid_evt_entry *evt) { #ifdef HEAP_WITH_DYNALLOC /* LOCK */ fluid_mutex_lock(heap->mutex); evt->next = heap->freelist; heap->freelist = evt; /* UNLOCK */ fluid_mutex_unlock(heap->mutex); #else /* append to the end of the heap */ /* critical - should threadlock ? */ heap->tail->next = evt; heap->tail = evt; evt->next = NULL; #endif } fluidsynth-2.1.1/src/synth/fluid_event.h000066400000000000000000000044341362231004000203110ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_EVENT_PRIV_H #define _FLUID_EVENT_PRIV_H #include "fluidsynth.h" #include "fluid_sys.h" /* Private data for event */ /* ?? should be optimized in size, using unions */ struct _fluid_event_t { unsigned int time; int type; fluid_seq_id_t src; fluid_seq_id_t dest; int channel; short key; short vel; short control; short value; short id; //?? unused ? int pitch; unsigned int duration; void *data; }; unsigned int fluid_event_get_time(fluid_event_t *evt); void fluid_event_set_time(fluid_event_t *evt, unsigned int time); void fluid_event_clear(fluid_event_t *evt); /* private data for sorter + heap */ enum fluid_evt_entry_type { FLUID_EVT_ENTRY_INSERT = 0, FLUID_EVT_ENTRY_REMOVE }; typedef struct _fluid_evt_entry fluid_evt_entry; struct _fluid_evt_entry { fluid_evt_entry *next; short entryType; fluid_event_t evt; }; #define HEAP_WITH_DYNALLOC 1 /* #undef HEAP_WITH_DYNALLOC */ typedef struct _fluid_evt_heap_t { #ifdef HEAP_WITH_DYNALLOC fluid_evt_entry *freelist; fluid_mutex_t mutex; #else fluid_evt_entry *head; fluid_evt_entry *tail; fluid_evt_entry pool; #endif } fluid_evt_heap_t; fluid_evt_heap_t *_fluid_evt_heap_init(int nbEvents); void _fluid_evt_heap_free(fluid_evt_heap_t *heap); fluid_evt_entry *_fluid_seq_heap_get_free(fluid_evt_heap_t *heap); void _fluid_seq_heap_set_free(fluid_evt_heap_t *heap, fluid_evt_entry *evt); #endif /* _FLUID_EVENT_PRIV_H */ fluidsynth-2.1.1/src/synth/fluid_gen.c000066400000000000000000000153631362231004000177370ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_gen.h" #include "fluid_chan.h" /* See SFSpec21 $8.1.3 */ static const fluid_gen_info_t fluid_gen_info[] = { /* number/name init nrpn-scale min max def */ { GEN_STARTADDROFS, 1, 1, 0.0f, 1e10f, 0.0f }, { GEN_ENDADDROFS, 1, 1, -1e10f, 0.0f, 0.0f }, { GEN_STARTLOOPADDROFS, 1, 1, -1e10f, 1e10f, 0.0f }, { GEN_ENDLOOPADDROFS, 1, 1, -1e10f, 1e10f, 0.0f }, { GEN_STARTADDRCOARSEOFS, 0, 1, 0.0f, 1e10f, 0.0f }, { GEN_MODLFOTOPITCH, 1, 2, -12000.0f, 12000.0f, 0.0f }, { GEN_VIBLFOTOPITCH, 1, 2, -12000.0f, 12000.0f, 0.0f }, { GEN_MODENVTOPITCH, 1, 2, -12000.0f, 12000.0f, 0.0f }, { GEN_FILTERFC, 1, 2, 1500.0f, 13500.0f, 13500.0f }, { GEN_FILTERQ, 1, 1, 0.0f, 960.0f, 0.0f }, { GEN_MODLFOTOFILTERFC, 1, 2, -12000.0f, 12000.0f, 0.0f }, { GEN_MODENVTOFILTERFC, 1, 2, -12000.0f, 12000.0f, 0.0f }, { GEN_ENDADDRCOARSEOFS, 0, 1, -1e10f, 0.0f, 0.0f }, { GEN_MODLFOTOVOL, 1, 1, -960.0f, 960.0f, 0.0f }, { GEN_UNUSED1, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_CHORUSSEND, 1, 1, 0.0f, 1000.0f, 0.0f }, { GEN_REVERBSEND, 1, 1, 0.0f, 1000.0f, 0.0f }, { GEN_PAN, 1, 1, -500.0f, 500.0f, 0.0f }, { GEN_UNUSED2, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_UNUSED3, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_UNUSED4, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_MODLFODELAY, 1, 2, -12000.0f, 5000.0f, -12000.0f }, { GEN_MODLFOFREQ, 1, 4, -16000.0f, 4500.0f, 0.0f }, { GEN_VIBLFODELAY, 1, 2, -12000.0f, 5000.0f, -12000.0f }, { GEN_VIBLFOFREQ, 1, 4, -16000.0f, 4500.0f, 0.0f }, { GEN_MODENVDELAY, 1, 2, -12000.0f, 5000.0f, -12000.0f }, { GEN_MODENVATTACK, 1, 2, -12000.0f, 8000.0f, -12000.0f }, { GEN_MODENVHOLD, 1, 2, -12000.0f, 5000.0f, -12000.0f }, { GEN_MODENVDECAY, 1, 2, -12000.0f, 8000.0f, -12000.0f }, { GEN_MODENVSUSTAIN, 0, 1, 0.0f, 1000.0f, 0.0f }, { GEN_MODENVRELEASE, 1, 2, -12000.0f, 8000.0f, -12000.0f }, { GEN_KEYTOMODENVHOLD, 0, 1, -1200.0f, 1200.0f, 0.0f }, { GEN_KEYTOMODENVDECAY, 0, 1, -1200.0f, 1200.0f, 0.0f }, { GEN_VOLENVDELAY, 1, 2, -12000.0f, 5000.0f, -12000.0f }, { GEN_VOLENVATTACK, 1, 2, -12000.0f, 8000.0f, -12000.0f }, { GEN_VOLENVHOLD, 1, 2, -12000.0f, 5000.0f, -12000.0f }, { GEN_VOLENVDECAY, 1, 2, -12000.0f, 8000.0f, -12000.0f }, { GEN_VOLENVSUSTAIN, 0, 1, 0.0f, 1440.0f, 0.0f }, { GEN_VOLENVRELEASE, 1, 2, -12000.0f, 8000.0f, -12000.0f }, { GEN_KEYTOVOLENVHOLD, 0, 1, -1200.0f, 1200.0f, 0.0f }, { GEN_KEYTOVOLENVDECAY, 0, 1, -1200.0f, 1200.0f, 0.0f }, { GEN_INSTRUMENT, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_RESERVED1, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_KEYRANGE, 0, 0, 0.0f, 127.0f, 0.0f }, { GEN_VELRANGE, 0, 0, 0.0f, 127.0f, 0.0f }, { GEN_STARTLOOPADDRCOARSEOFS, 0, 1, -1e10f, 1e10f, 0.0f }, { GEN_KEYNUM, 1, 0, 0.0f, 127.0f, -1.0f }, { GEN_VELOCITY, 1, 1, 0.0f, 127.0f, -1.0f }, { GEN_ATTENUATION, 1, 1, 0.0f, 1440.0f, 0.0f }, { GEN_RESERVED2, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_ENDLOOPADDRCOARSEOFS, 0, 1, -1e10f, 1e10f, 0.0f }, { GEN_COARSETUNE, 0, 1, -120.0f, 120.0f, 0.0f }, { GEN_FINETUNE, 0, 1, -99.0f, 99.0f, 0.0f }, { GEN_SAMPLEID, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_SAMPLEMODE, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_RESERVED3, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_SCALETUNE, 0, 1, 0.0f, 1200.0f, 100.0f }, { GEN_EXCLUSIVECLASS, 0, 0, 0.0f, 0.0f, 0.0f }, { GEN_OVERRIDEROOTKEY, 1, 0, 0.0f, 127.0f, -1.0f }, { GEN_PITCH, 1, 0, 0.0f, 127.0f, 0.0f }, { GEN_CUSTOM_BALANCE, 1, 0, -960.0f, 960.0f, 0.0f }, { GEN_CUSTOM_FILTERFC, 1, 2, 0.0f, 22050.0f, 0.0f }, { GEN_CUSTOM_FILTERQ, 1, 1, 0.0f, 960.0f, 0.0f } }; /* fluid_gen_init * * Set an array of generators to their initial value */ void fluid_gen_init(fluid_gen_t *gen, fluid_channel_t *channel) { int i; for(i = 0; i < GEN_LAST; i++) { gen[i].flags = GEN_UNUSED; gen[i].mod = 0.0; gen[i].nrpn = (channel == NULL) ? 0.0 : fluid_channel_get_gen(channel, i); gen[i].val = fluid_gen_info[i].def; } } fluid_real_t fluid_gen_scale(int gen, float value) { return (fluid_gen_info[gen].min + value * (fluid_gen_info[gen].max - fluid_gen_info[gen].min)); } fluid_real_t fluid_gen_scale_nrpn(int gen, int data) { data = data - 8192; fluid_clip(data, -8192, 8192); return (fluid_real_t)(data * fluid_gen_info[gen].nrpn_scale); } fluidsynth-2.1.1/src/synth/fluid_gen.h000066400000000000000000000042201362231004000177320ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_GEN_H #define _FLUID_GEN_H #include "fluidsynth_priv.h" typedef struct _fluid_gen_info_t { char num; /* Generator number */ char init; /* Does the generator need to be initialized (not used) */ char nrpn_scale; /* The scale to convert from NRPN (cfr. fluid_gen_map_nrpn()) */ float min; /* The minimum value */ float max; /* The maximum value */ float def; /* The default value (cfr. fluid_gen_init()) */ } fluid_gen_info_t; /* * SoundFont generator structure. */ typedef struct _fluid_gen_t { unsigned char flags; /**< Is the generator set or not (#fluid_gen_flags) */ double val; /**< The nominal value */ double mod; /**< Change by modulators */ double nrpn; /**< Change by NRPN messages */ } fluid_gen_t; /* * Enum value for 'flags' field of #fluid_gen_t (not really flags). */ enum fluid_gen_flags { GEN_UNUSED, /**< Generator value is not set */ GEN_SET, /**< Generator value is set */ }; #define fluid_gen_set_mod(_gen, _val) { (_gen)->mod = (double) (_val); } #define fluid_gen_set_nrpn(_gen, _val) { (_gen)->nrpn = (double) (_val); } fluid_real_t fluid_gen_scale(int gen, float value); fluid_real_t fluid_gen_scale_nrpn(int gen, int nrpn); void fluid_gen_init(fluid_gen_t *gen, fluid_channel_t *channel); #endif /* _FLUID_GEN_H */ fluidsynth-2.1.1/src/synth/fluid_mod.c000066400000000000000000000607071362231004000177470ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_mod.h" #include "fluid_chan.h" #include "fluid_voice.h" /** * Clone the modulators destination, sources, flags and amount. * @param mod the modulator to store the copy to * @param src the source modulator to retrieve the information from * @note The \c next member of \c mod will be left unchanged. */ void fluid_mod_clone(fluid_mod_t *mod, const fluid_mod_t *src) { mod->dest = src->dest; mod->src1 = src->src1; mod->flags1 = src->flags1; mod->src2 = src->src2; mod->flags2 = src->flags2; mod->amount = src->amount; } /** * Set a modulator's primary source controller and flags. * @param mod The modulator instance * @param src Modulator source (#fluid_mod_src or a MIDI controller number) * @param flags Flags determining mapping function and whether the source * controller is a general controller (#FLUID_MOD_GC) or a MIDI CC controller * (#FLUID_MOD_CC), see #fluid_mod_flags. */ void fluid_mod_set_source1(fluid_mod_t *mod, int src, int flags) { mod->src1 = src; mod->flags1 = flags; } /** * Set a modulator's secondary source controller and flags. * @param mod The modulator instance * @param src Modulator source (#fluid_mod_src or a MIDI controller number) * @param flags Flags determining mapping function and whether the source * controller is a general controller (#FLUID_MOD_GC) or a MIDI CC controller * (#FLUID_MOD_CC), see #fluid_mod_flags. */ void fluid_mod_set_source2(fluid_mod_t *mod, int src, int flags) { mod->src2 = src; mod->flags2 = flags; } /** * Set the destination effect of a modulator. * @param mod The modulator instance * @param dest Destination generator (#fluid_gen_type) */ void fluid_mod_set_dest(fluid_mod_t *mod, int dest) { mod->dest = dest; } /** * Set the scale amount of a modulator. * @param mod The modulator instance * @param amount Scale amount to assign */ void fluid_mod_set_amount(fluid_mod_t *mod, double amount) { mod->amount = (double) amount; } /** * Get the primary source value from a modulator. * @param mod The modulator instance * @return The primary source value (#fluid_mod_src or a MIDI CC controller value). */ int fluid_mod_get_source1(const fluid_mod_t *mod) { return mod->src1; } /** * Get primary source flags from a modulator. * @param mod The modulator instance * @return The primary source flags (#fluid_mod_flags). */ int fluid_mod_get_flags1(const fluid_mod_t *mod) { return mod->flags1; } /** * Get the secondary source value from a modulator. * @param mod The modulator instance * @return The secondary source value (#fluid_mod_src or a MIDI CC controller value). */ int fluid_mod_get_source2(const fluid_mod_t *mod) { return mod->src2; } /** * Get secondary source flags from a modulator. * @param mod The modulator instance * @return The secondary source flags (#fluid_mod_flags). */ int fluid_mod_get_flags2(const fluid_mod_t *mod) { return mod->flags2; } /** * Get destination effect from a modulator. * @param mod The modulator instance * @return Destination generator (#fluid_gen_type) */ int fluid_mod_get_dest(const fluid_mod_t *mod) { return mod->dest; } /** * Get the scale amount from a modulator. * @param mod The modulator instance * @return Scale amount */ double fluid_mod_get_amount(const fluid_mod_t *mod) { return (double) mod->amount; } /* * retrieves the initial value from the given source of the modulator */ static fluid_real_t fluid_mod_get_source_value(const unsigned char mod_src, const unsigned char mod_flags, fluid_real_t *range, const fluid_voice_t *voice ) { const fluid_channel_t *chan = voice->channel; fluid_real_t val; if(mod_flags & FLUID_MOD_CC) { /* From MIDI Recommended Practice (RP-036) Default Pan Formula: * "Since MIDI controller values range from 0 to 127, the exact center * of the range, 63.5, cannot be represented. Therefore, the effective * range for CC#10 is modified to be 1 to 127, and values 0 and 1 both * pan hard left. The recommended method is to subtract 1 from the * value of CC#10, and saturate the result to be non-negative." * * We treat the balance control in exactly the same way, as the same * problem applies here as well. */ if(mod_src == PAN_MSB || mod_src == BALANCE_MSB) { *range = 126; val = fluid_channel_get_cc(chan, mod_src) - 1; if(val < 0) { val = 0; } } else { val = fluid_channel_get_cc(chan, mod_src); } } else { switch(mod_src) { case FLUID_MOD_NONE: /* SF 2.01 8.2.1 item 0: src enum=0 => value is 1 */ val = *range; break; case FLUID_MOD_VELOCITY: val = fluid_voice_get_actual_velocity(voice); break; case FLUID_MOD_KEY: val = fluid_voice_get_actual_key(voice); break; case FLUID_MOD_KEYPRESSURE: val = fluid_channel_get_key_pressure(chan, voice->key); break; case FLUID_MOD_CHANNELPRESSURE: val = fluid_channel_get_channel_pressure(chan); break; case FLUID_MOD_PITCHWHEEL: val = fluid_channel_get_pitch_bend(chan); *range = 0x4000; break; case FLUID_MOD_PITCHWHEELSENS: val = fluid_channel_get_pitch_wheel_sensitivity(chan); break; default: FLUID_LOG(FLUID_ERR, "Unknown modulator source '%d', disabling modulator.", mod_src); val = 0.0; } } return val; } /** * transforms the initial value retrieved by \c fluid_mod_get_source_value into [0.0;1.0] */ static fluid_real_t fluid_mod_transform_source_value(fluid_real_t val, unsigned char mod_flags, const fluid_real_t range) { /* normalized value, i.e. usually in the range [0;1] */ const fluid_real_t val_norm = val / range; /* we could also only switch case the lower nibble of mod_flags, however * this would keep us from adding further mod types in the future * * instead just remove the flag(s) we already took care of */ mod_flags &= ~FLUID_MOD_CC; switch(mod_flags/* & 0x0f*/) { case FLUID_MOD_LINEAR | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =0 */ val = val_norm; break; case FLUID_MOD_LINEAR | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =1 */ val = 1.0f - val_norm; break; case FLUID_MOD_LINEAR | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =2 */ val = -1.0f + 2.0f * val_norm; break; case FLUID_MOD_LINEAR | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =3 */ val = 1.0f - 2.0f * val_norm; break; case FLUID_MOD_CONCAVE | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =4 */ val = fluid_concave(127 * (val_norm)); break; case FLUID_MOD_CONCAVE | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =5 */ val = fluid_concave(127 * (1.0f - val_norm)); break; case FLUID_MOD_CONCAVE | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =6 */ val = (val_norm > 0.5f) ? fluid_concave(127 * 2 * (val_norm - 0.5f)) : -fluid_concave(127 * 2 * (0.5f - val_norm)); break; case FLUID_MOD_CONCAVE | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =7 */ val = (val_norm > 0.5f) ? -fluid_concave(127 * 2 * (val_norm - 0.5f)) : fluid_concave(127 * 2 * (0.5f - val_norm)); break; case FLUID_MOD_CONVEX | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =8 */ val = fluid_convex(127 * (val_norm)); break; case FLUID_MOD_CONVEX | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =9 */ val = fluid_convex(127 * (1.0f - val_norm)); break; case FLUID_MOD_CONVEX | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =10 */ val = (val_norm > 0.5f) ? fluid_convex(127 * 2 * (val_norm - 0.5f)) : -fluid_convex(127 * 2 * (0.5f - val_norm)); break; case FLUID_MOD_CONVEX | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =11 */ val = (val_norm > 0.5f) ? -fluid_convex(127 * 2 * (val_norm - 0.5f)) : fluid_convex(127 * 2 * (0.5f - val_norm)); break; case FLUID_MOD_SWITCH | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =12 */ val = (val_norm >= 0.5f) ? 1.0f : 0.0f; break; case FLUID_MOD_SWITCH | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =13 */ val = (val_norm >= 0.5f) ? 0.0f : 1.0f; break; case FLUID_MOD_SWITCH | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =14 */ val = (val_norm >= 0.5f) ? 1.0f : -1.0f; break; case FLUID_MOD_SWITCH | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =15 */ val = (val_norm >= 0.5f) ? -1.0f : 1.0f; break; /* * MIDI CCs only have a resolution of 7 bits. The closer val_norm gets to 1, * the less will be the resulting change of the sinus. When using this sin() * for scaling the cutoff frequency, there will be no audible difference between * MIDI CCs 118 to 127. To avoid this waste of CCs multiply with 0.87 * (at least for unipolar) which makes sin() never get to 1.0 but to 0.98 which * is close enough. */ case FLUID_MOD_SIN | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* custom sin(x) */ val = FLUID_SIN((FLUID_M_PI / 2.0f * 0.87f) * val_norm); break; case FLUID_MOD_SIN | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* custom */ val = FLUID_SIN((FLUID_M_PI / 2.0f * 0.87f) * (1.0f - val_norm)); break; case FLUID_MOD_SIN | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* custom */ val = (val_norm > 0.5f) ? FLUID_SIN(FLUID_M_PI * (val_norm - 0.5f)) : -FLUID_SIN(FLUID_M_PI * (0.5f - val_norm)); break; case FLUID_MOD_SIN | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* custom */ val = (val_norm > 0.5f) ? -FLUID_SIN(FLUID_M_PI * (val_norm - 0.5f)) : FLUID_SIN(FLUID_M_PI * (0.5f - val_norm)); break; default: FLUID_LOG(FLUID_ERR, "Unknown modulator type '%d', disabling modulator.", mod_flags); val = 0.0f; break; } return val; } /* * fluid_mod_get_value. * Computes and return modulator output following SF2.01 * (See SoundFont Modulator Controller Model Chapter 9.5). * * Output = Transform(Amount * Map(primary source input) * Map(secondary source input)) * * Notes: * 1)fluid_mod_get_value, ignores the Transform operator. The result is: * * Output = Amount * Map(primary source input) * Map(secondary source input) * * 2)When primary source input (src1) is set to General Controller 'No Controller', * output is forced to 0. * * 3)When secondary source input (src2) is set to General Controller 'No Controller', * output is forced to +1.0 */ fluid_real_t fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice) { extern fluid_mod_t default_vel2filter_mod; fluid_real_t v1 = 0.0, v2 = 1.0; fluid_real_t range1 = 127.0, range2 = 127.0; /* 'special treatment' for default controller * * Reference: SF2.01 section 8.4.2 * * The GM default controller 'vel-to-filter cut off' is not clearly * defined: If implemented according to the specs, the filter * frequency jumps between vel=63 and vel=64. To maintain * compatibility with existing sound fonts, the implementation is * 'hardcoded', it is impossible to implement using only one * modulator otherwise. * * I assume here, that the 'intention' of the paragraph is one * octave (1200 cents) filter frequency shift between vel=127 and * vel=64. 'amount' is (-2400), at least as long as the controller * is set to default. * * Further, the 'appearance' of the modulator (source enumerator, * destination enumerator, flags etc) is different from that * described in section 8.4.2, but it matches the definition used in * several SF2.1 sound fonts (where it is used only to turn it off). * */ if(fluid_mod_test_identity(mod, &default_vel2filter_mod)) { // S. Christian Collins' mod, to stop forcing velocity based filtering /* if (voice->vel < 64){ return (fluid_real_t) mod->amount / 2.0; } else { return (fluid_real_t) mod->amount * (127 - voice->vel) / 127; } */ return 0; // (fluid_real_t) mod->amount / 2.0; } // end S. Christian Collins' mod /* get the initial value of the first source */ if(mod->src1 > 0) { v1 = fluid_mod_get_source_value(mod->src1, mod->flags1, &range1, voice); /* transform the input value */ v1 = fluid_mod_transform_source_value(v1, mod->flags1, range1); } /* When primary source input (src1) is set to General Controller 'No Controller', output is forced to 0.0 */ else { return 0.0; } /* no need to go further */ if(v1 == 0.0f) { return 0.0f; } /* get the second input source */ if(mod->src2 > 0) { v2 = fluid_mod_get_source_value(mod->src2, mod->flags2, &range2, voice); /* transform the second input value */ v2 = fluid_mod_transform_source_value(v2, mod->flags2, range2); } /* When secondary source input (src2) is set to General Controller 'No Controller', output is forced to +1.0 */ else { v2 = 1.0f; } /* it's as simple as that: */ return (fluid_real_t) mod->amount * v1 * v2; } /** * Create a new uninitialized modulator structure. * @return New allocated modulator or NULL if out of memory */ fluid_mod_t * new_fluid_mod() { fluid_mod_t *mod = FLUID_NEW(fluid_mod_t); if(mod == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } return mod; } /** * Free a modulator structure. * @param mod Modulator to free */ void delete_fluid_mod(fluid_mod_t *mod) { FLUID_FREE(mod); } /** * Returns the size of the fluid_mod_t structure. * * Useful in low latency scenarios e.g. to allocate a modulator on the stack. * * @return Size of fluid_mod_t in bytes */ size_t fluid_mod_sizeof() { return sizeof(fluid_mod_t); } /** * Checks if modulator with source 1 other than CC is FLUID_MOD_NONE. * * @param mod, modulator. * @return TRUE if modulator source 1 other than cc is FLUID_MOD_NONE, FALSE otherwise. */ static int fluid_mod_is_src1_none(const fluid_mod_t *mod) { return(((mod->flags1 & FLUID_MOD_CC) == 0) && (mod->src1 == FLUID_MOD_NONE)); } /** * Checks if modulators source other than CC source is invalid. * (specs SF 2.01 7.4, 7.8, 8.2.1) * * @param mod, modulator. * @param src1_select, source input selection to check. * 1 to check src1 source. * 0 to check src2 source. * @return FALSE if selected modulator source other than cc is invalid, TRUE otherwise. */ static int fluid_mod_check_non_cc_source(const fluid_mod_t *mod, unsigned char src1_select) { unsigned char flags, src; if(src1_select) { flags = mod->flags1; src = mod->src1; } else { flags = mod->flags2; src = mod->src2; } return(((flags & FLUID_MOD_CC) != 0) /* src is a CC */ /* SF2.01 section 8.2.1: Constant value */ || ((src == FLUID_MOD_NONE) || (src == FLUID_MOD_VELOCITY) /* Note-on velocity */ || (src == FLUID_MOD_KEY) /* Note-on key number */ || (src == FLUID_MOD_KEYPRESSURE) /* Poly pressure */ || (src == FLUID_MOD_CHANNELPRESSURE) /* Channel pressure */ || (src == FLUID_MOD_PITCHWHEEL) /* Pitch wheel */ || (src == FLUID_MOD_PITCHWHEELSENS) /* Pitch wheel sensitivity */ )); } /** * Checks if modulator CC source is invalid (specs SF 2.01 7.4, 7.8, 8.2.1). * @param mod, modulator. * @src1_select, source input selection: * 1 to check src1 source or * 0 to check src2 source. * @return FALSE if selected modulator's source CC is invalid, TRUE otherwise. */ static int fluid_mod_check_cc_source(const fluid_mod_t *mod, unsigned char src1_select) { unsigned char flags, src; if(src1_select) { flags = mod->flags1; src = mod->src1; } else { flags = mod->flags2; src = mod->src2; } return(((flags & FLUID_MOD_CC) == 0) /* src is non CC */ || ((src != BANK_SELECT_MSB) && (src != BANK_SELECT_LSB) && (src != DATA_ENTRY_MSB) && (src != DATA_ENTRY_LSB) /* is src not NRPN_LSB, NRPN_MSB, RPN_LSB, RPN_MSB */ && ((src < NRPN_LSB) || (RPN_MSB < src)) /* is src not ALL_SOUND_OFF, ALL_CTRL_OFF, LOCAL_CONTROL, ALL_NOTES_OFF ? */ /* is src not OMNI_OFF, OMNI_ON, POLY_OFF, POLY_ON ? */ && (src < ALL_SOUND_OFF) /* CC lsb shouldn't allowed to modulate (spec SF 2.01 - 8.2.1) However, as long fluidsynth will use only CC 7 bits resolution, it is safe to ignore these SF recommendations on CC receive. See explanations in fluid_synth_cc_LOCAL() */ /* uncomment next line to forbid CC lsb */ /* && ((src < 32) || (63 < src)) */ )); } /** * Checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1) * @param mod, modulator. * @param name,if not NULL, pointer on a string displayed as a warning. * @return TRUE if modulator sources src1, src2 are valid, FALSE otherwise. */ int fluid_mod_check_sources(const fluid_mod_t *mod, char *name) { static const char invalid_non_cc_src[] = "Invalid modulator, using non-CC source %s.src%d=%d"; static const char invalid_cc_src[] = "Invalid modulator, using CC source %s.src%d=%d"; static const char src1_is_none[] = "Modulator with source 1 none %s.src1=%d"; /* checks valid non cc sources */ if(!fluid_mod_check_non_cc_source(mod, 1)) /* check src1 */ { if(name) { FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 1, mod->src1); } return FALSE; } /* When src1 is non CC source FLUID_MOD_NONE, the modulator is valid but the output of this modulator will be forced to 0 at synthesis time. Also this modulator cannot be used to overwrite a default modulator (as there is no default modulator with src1 source equal to FLUID_MOD_NONE). Consequently it is useful to return FALSE to indicate this modulator being useless. It will be removed later with others invalid modulators. */ if(fluid_mod_is_src1_none(mod)) { if(name) { FLUID_LOG(FLUID_WARN, src1_is_none, name, mod->src1); } return FALSE; } if(!fluid_mod_check_non_cc_source(mod, 0)) /* check src2 */ { if(name) { FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 2, mod->src2); } return FALSE; } /* checks valid cc sources */ if(!fluid_mod_check_cc_source(mod, 1)) /* check src1 */ { if(name) { FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 1, mod->src1); } return FALSE; } if(!fluid_mod_check_cc_source(mod, 0)) /* check src2 */ { if(name) { FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 2, mod->src2); } return FALSE; } return TRUE; } /** * Checks if two modulators are identical in sources, flags and destination. * @param mod1 First modulator * @param mod2 Second modulator * @return TRUE if identical, FALSE otherwise * * SF2.01 section 9.5.1 page 69, 'bullet' 3 defines 'identical'. */ int fluid_mod_test_identity(const fluid_mod_t *mod1, const fluid_mod_t *mod2) { return mod1->dest == mod2->dest && mod1->src1 == mod2->src1 && mod1->src2 == mod2->src2 && mod1->flags1 == mod2->flags1 && mod1->flags2 == mod2->flags2; } /** * Check if the modulator has the given source. * * @param mod The modulator instance * @param cc Boolean value indicating if ctrl is a CC controller or not * @param ctrl The source to check for (if \c cc == FALSE : a value of type #fluid_mod_src, else the value of the MIDI CC to check for) * * @return TRUE if the modulator has the given source, FALSE otherwise. */ int fluid_mod_has_source(const fluid_mod_t *mod, int cc, int ctrl) { return ( ( ((mod->src1 == ctrl) && ((mod->flags1 & FLUID_MOD_CC) != 0) && (cc != 0)) || ((mod->src1 == ctrl) && ((mod->flags1 & FLUID_MOD_CC) == 0) && (cc == 0)) ) || ( ((mod->src2 == ctrl) && ((mod->flags2 & FLUID_MOD_CC) != 0) && (cc != 0)) || ((mod->src2 == ctrl) && ((mod->flags2 & FLUID_MOD_CC) == 0) && (cc == 0)) ) ); } /** * Check if the modulator has the given destination. * @param mod The modulator instance * @param gen The destination generator of type #fluid_gen_type to check for * @return TRUE if the modulator has the given destination, FALSE otherwise. */ int fluid_mod_has_dest(const fluid_mod_t *mod, int gen) { return mod->dest == gen; } /* debug function: Prints the contents of a modulator */ #ifdef DEBUG void fluid_dump_modulator(fluid_mod_t *mod) { int src1 = mod->src1; int dest = mod->dest; int src2 = mod->src2; int flags1 = mod->flags1; int flags2 = mod->flags2; fluid_real_t amount = (fluid_real_t)mod->amount; printf("Src: "); if(flags1 & FLUID_MOD_CC) { printf("MIDI CC=%i", src1); } else { switch(src1) { case FLUID_MOD_NONE: printf("None"); break; case FLUID_MOD_VELOCITY: printf("note-on velocity"); break; case FLUID_MOD_KEY: printf("Key nr"); break; case FLUID_MOD_KEYPRESSURE: printf("Poly pressure"); break; case FLUID_MOD_CHANNELPRESSURE: printf("Chan pressure"); break; case FLUID_MOD_PITCHWHEEL: printf("Pitch Wheel"); break; case FLUID_MOD_PITCHWHEELSENS: printf("Pitch Wheel sens"); break; default: printf("(unknown: %i)", src1); }; /* switch src1 */ }; /* if not CC */ if(flags1 & FLUID_MOD_NEGATIVE) { printf("- "); } else { printf("+ "); }; if(flags1 & FLUID_MOD_BIPOLAR) { printf("bip "); } else { printf("unip "); }; printf("-> "); switch(dest) { case GEN_FILTERQ: printf("Q"); break; case GEN_FILTERFC: printf("fc"); break; case GEN_CUSTOM_FILTERQ: printf("custom-Q"); break; case GEN_CUSTOM_FILTERFC: printf("custom-fc"); break; case GEN_VIBLFOTOPITCH: printf("VibLFO-to-pitch"); break; case GEN_MODENVTOPITCH: printf("ModEnv-to-pitch"); break; case GEN_MODLFOTOPITCH: printf("ModLFO-to-pitch"); break; case GEN_CHORUSSEND: printf("Chorus send"); break; case GEN_REVERBSEND: printf("Reverb send"); break; case GEN_PAN: printf("pan"); break; case GEN_CUSTOM_BALANCE: printf("balance"); break; case GEN_ATTENUATION: printf("att"); break; default: printf("dest %i", dest); }; /* switch dest */ printf(", amount %f flags %i src2 %i flags2 %i\n", amount, flags1, src2, flags2); }; #endif fluidsynth-2.1.1/src/synth/fluid_mod.h000066400000000000000000000036441362231004000177510ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_MOD_H #define _FLUID_MOD_H #include "fluidsynth_priv.h" #include "fluid_conv.h" /* * Modulator structure. See SoundFont 2.04 PDF section 8.2. */ struct _fluid_mod_t { unsigned char dest; /**< Destination generator to control */ unsigned char src1; /**< Source controller 1 */ unsigned char flags1; /**< Source controller 1 flags */ unsigned char src2; /**< Source controller 2 */ unsigned char flags2; /**< Source controller 2 flags */ double amount; /**< Multiplier amount */ /* The 'next' field allows to link modulators into a list. It is * not used in fluid_voice.c, there each voice allocates memory for a * fixed number of modulators. Since there may be a huge number of * different zones, this is more efficient. */ fluid_mod_t *next; }; fluid_real_t fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice); int fluid_mod_check_sources(const fluid_mod_t *mod, char *name); #ifdef DEBUG void fluid_dump_modulator(fluid_mod_t *mod); #endif #endif /* _FLUID_MOD_H */ fluidsynth-2.1.1/src/synth/fluid_synth.c000066400000000000000000007034171362231004000203370ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_synth.h" #include "fluid_sys.h" #include "fluid_chan.h" #include "fluid_tuning.h" #include "fluid_settings.h" #include "fluid_sfont.h" #include "fluid_defsfont.h" #include "fluid_instpatch.h" #ifdef TRAP_ON_FPE #define _GNU_SOURCE #include /* seems to not be declared in fenv.h */ extern int feenableexcept(int excepts); #endif #define FLUID_API_RETURN(return_value) \ do { fluid_synth_api_exit(synth); \ return return_value; } while (0) #define FLUID_API_RETURN_IF_CHAN_DISABLED(return_value) \ do { if (FLUID_LIKELY(synth->channel[chan]->mode & FLUID_CHANNEL_ENABLED)) \ {} \ else \ { FLUID_API_RETURN(return_value); } \ } while (0) #define FLUID_API_ENTRY_CHAN(fail_value) \ fluid_return_val_if_fail (synth != NULL, fail_value); \ fluid_return_val_if_fail (chan >= 0, fail_value); \ fluid_synth_api_enter(synth); \ if (chan >= synth->midi_channels) { \ FLUID_API_RETURN(fail_value); \ } \ static void fluid_synth_init(void); static void fluid_synth_api_enter(fluid_synth_t *synth); static void fluid_synth_api_exit(fluid_synth_t *synth); static int fluid_synth_noteon_LOCAL(fluid_synth_t *synth, int chan, int key, int vel); static int fluid_synth_noteoff_LOCAL(fluid_synth_t *synth, int chan, int key); static int fluid_synth_cc_LOCAL(fluid_synth_t *synth, int channum, int num); static int fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, int len, char *response, int *response_len, int avail_response, int *handled, int dryrun); int fluid_synth_all_notes_off_LOCAL(fluid_synth_t *synth, int chan); static int fluid_synth_all_sounds_off_LOCAL(fluid_synth_t *synth, int chan); static int fluid_synth_system_reset_LOCAL(fluid_synth_t *synth); static int fluid_synth_modulate_voices_LOCAL(fluid_synth_t *synth, int chan, int is_cc, int ctrl); static int fluid_synth_modulate_voices_all_LOCAL(fluid_synth_t *synth, int chan); static int fluid_synth_update_channel_pressure_LOCAL(fluid_synth_t *synth, int channum); static int fluid_synth_update_key_pressure_LOCAL(fluid_synth_t *synth, int chan, int key); static int fluid_synth_update_pitch_bend_LOCAL(fluid_synth_t *synth, int chan); static int fluid_synth_update_pitch_wheel_sens_LOCAL(fluid_synth_t *synth, int chan); static int fluid_synth_set_preset(fluid_synth_t *synth, int chan, fluid_preset_t *preset); static fluid_preset_t * fluid_synth_get_preset(fluid_synth_t *synth, int sfontnum, int banknum, int prognum); static fluid_preset_t * fluid_synth_get_preset_by_sfont_name(fluid_synth_t *synth, const char *sfontname, int banknum, int prognum); static void fluid_synth_update_presets(fluid_synth_t *synth); static void fluid_synth_update_gain_LOCAL(fluid_synth_t *synth); static int fluid_synth_update_polyphony_LOCAL(fluid_synth_t *synth, int new_polyphony); static void init_dither(void); static FLUID_INLINE int16_t round_clip_to_i16(float x); static int fluid_synth_render_blocks(fluid_synth_t *synth, int blockcount); static fluid_voice_t *fluid_synth_free_voice_by_kill_LOCAL(fluid_synth_t *synth); static void fluid_synth_kill_by_exclusive_class_LOCAL(fluid_synth_t *synth, fluid_voice_t *new_voice); static int fluid_synth_sfunload_callback(void *data, unsigned int msec); static fluid_tuning_t *fluid_synth_get_tuning(fluid_synth_t *synth, int bank, int prog); static int fluid_synth_replace_tuning_LOCK(fluid_synth_t *synth, fluid_tuning_t *tuning, int bank, int prog, int apply); static void fluid_synth_replace_tuning_LOCAL(fluid_synth_t *synth, fluid_tuning_t *old_tuning, fluid_tuning_t *new_tuning, int apply, int unref_new); static void fluid_synth_update_voice_tuning_LOCAL(fluid_synth_t *synth, fluid_channel_t *channel); static int fluid_synth_set_tuning_LOCAL(fluid_synth_t *synth, int chan, fluid_tuning_t *tuning, int apply); static void fluid_synth_set_gen_LOCAL(fluid_synth_t *synth, int chan, int param, float value); static void fluid_synth_stop_LOCAL(fluid_synth_t *synth, unsigned int id); static int fluid_synth_set_important_channels(fluid_synth_t *synth, const char *channels); /* Callback handlers for real-time settings */ static void fluid_synth_handle_gain(void *data, const char *name, double value); static void fluid_synth_handle_polyphony(void *data, const char *name, int value); static void fluid_synth_handle_device_id(void *data, const char *name, int value); static void fluid_synth_handle_overflow(void *data, const char *name, double value); static void fluid_synth_handle_important_channels(void *data, const char *name, const char *value); static void fluid_synth_handle_reverb_chorus_num(void *data, const char *name, double value); static void fluid_synth_handle_reverb_chorus_int(void *data, const char *name, int value); static void fluid_synth_reset_basic_channel_LOCAL(fluid_synth_t *synth, int chan, int nbr_chan); static int fluid_synth_check_next_basic_channel(fluid_synth_t *synth, int basicchan, int mode, int val); static void fluid_synth_set_basic_channel_LOCAL(fluid_synth_t *synth, int basicchan, int mode, int val); /*************************************************************** * * GLOBAL */ /* has the synth module been initialized? */ /* fluid_atomic_int_t may be anything, so init with {0} to catch most cases */ static fluid_atomic_int_t fluid_synth_initialized = {0}; /* default modulators * SF2.01 page 52 ff: * * There is a set of predefined default modulators. They have to be * explicitly overridden by the sound font in order to turn them off. */ static fluid_mod_t default_vel2att_mod; /* SF2.01 section 8.4.1 */ /*not static */ fluid_mod_t default_vel2filter_mod; /* SF2.01 section 8.4.2 */ static fluid_mod_t default_at2viblfo_mod; /* SF2.01 section 8.4.3 */ static fluid_mod_t default_mod2viblfo_mod; /* SF2.01 section 8.4.4 */ static fluid_mod_t default_att_mod; /* SF2.01 section 8.4.5 */ static fluid_mod_t default_pan_mod; /* SF2.01 section 8.4.6 */ static fluid_mod_t default_expr_mod; /* SF2.01 section 8.4.7 */ static fluid_mod_t default_reverb_mod; /* SF2.01 section 8.4.8 */ static fluid_mod_t default_chorus_mod; /* SF2.01 section 8.4.9 */ static fluid_mod_t default_pitch_bend_mod; /* SF2.01 section 8.4.10 */ static fluid_mod_t custom_balance_mod; /* Non-standard modulator */ /* custom_breath2att_modulator is not a default modulator specified in SF it is intended to replace default_vel2att_mod on demand using API fluid_set_breath_mode() or command shell setbreathmode. */ static fluid_mod_t custom_breath2att_mod; /* reverb presets */ static const fluid_revmodel_presets_t revmodel_preset[] = { /* name */ /* roomsize */ /* damp */ /* width */ /* level */ { "Test 1", 0.2f, 0.0f, 0.5f, 0.9f }, { "Test 2", 0.4f, 0.2f, 0.5f, 0.8f }, { "Test 3", 0.6f, 0.4f, 0.5f, 0.7f }, { "Test 4", 0.8f, 0.7f, 0.5f, 0.6f }, { "Test 5", 0.8f, 1.0f, 0.5f, 0.5f }, }; /*************************************************************** * * INITIALIZATION & UTILITIES */ void fluid_synth_settings(fluid_settings_t *settings) { fluid_settings_register_int(settings, "synth.verbose", 0, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_int(settings, "synth.reverb.active", 1, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_num(settings, "synth.reverb.room-size", FLUID_REVERB_DEFAULT_ROOMSIZE, 0.0f, 1.0f, 0); fluid_settings_register_num(settings, "synth.reverb.damp", FLUID_REVERB_DEFAULT_DAMP, 0.0f, 1.0f, 0); fluid_settings_register_num(settings, "synth.reverb.width", FLUID_REVERB_DEFAULT_WIDTH, 0.0f, 100.0f, 0); fluid_settings_register_num(settings, "synth.reverb.level", FLUID_REVERB_DEFAULT_LEVEL, 0.0f, 1.0f, 0); fluid_settings_register_int(settings, "synth.chorus.active", 1, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_int(settings, "synth.chorus.nr", FLUID_CHORUS_DEFAULT_N, 0, 99, 0); fluid_settings_register_num(settings, "synth.chorus.level", FLUID_CHORUS_DEFAULT_LEVEL, 0.0f, 10.0f, 0); fluid_settings_register_num(settings, "synth.chorus.speed", FLUID_CHORUS_DEFAULT_SPEED, 0.1f, 5.0f, 0); fluid_settings_register_num(settings, "synth.chorus.depth", FLUID_CHORUS_DEFAULT_DEPTH, 0.0f, 256.0f, 0); fluid_settings_register_int(settings, "synth.ladspa.active", 0, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_int(settings, "synth.lock-memory", 1, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_str(settings, "midi.portname", "", 0); #ifdef DEFAULT_SOUNDFONT fluid_settings_register_str(settings, "synth.default-soundfont", DEFAULT_SOUNDFONT, 0); #endif fluid_settings_register_int(settings, "synth.polyphony", 256, 1, 65535, 0); fluid_settings_register_int(settings, "synth.midi-channels", 16, 16, 256, 0); fluid_settings_register_num(settings, "synth.gain", 0.2f, 0.0f, 10.0f, 0); fluid_settings_register_int(settings, "synth.audio-channels", 1, 1, 128, 0); fluid_settings_register_int(settings, "synth.audio-groups", 1, 1, 128, 0); fluid_settings_register_int(settings, "synth.effects-channels", 2, 2, 2, 0); fluid_settings_register_int(settings, "synth.effects-groups", 1, 1, 128, 0); fluid_settings_register_num(settings, "synth.sample-rate", 44100.0f, 8000.0f, 96000.0f, 0); fluid_settings_register_int(settings, "synth.device-id", 0, 0, 126, 0); #ifdef ENABLE_MIXER_THREADS fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 256, 0); #else fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 1, 0); #endif fluid_settings_register_int(settings, "synth.min-note-length", 10, 0, 65535, 0); fluid_settings_register_int(settings, "synth.threadsafe-api", 1, 0, 1, FLUID_HINT_TOGGLED); fluid_settings_register_num(settings, "synth.overflow.percussion", 4000, -10000, 10000, 0); fluid_settings_register_num(settings, "synth.overflow.sustained", -1000, -10000, 10000, 0); fluid_settings_register_num(settings, "synth.overflow.released", -2000, -10000, 10000, 0); fluid_settings_register_num(settings, "synth.overflow.age", 1000, -10000, 10000, 0); fluid_settings_register_num(settings, "synth.overflow.volume", 500, -10000, 10000, 0); fluid_settings_register_num(settings, "synth.overflow.important", 5000, -50000, 50000, 0); fluid_settings_register_str(settings, "synth.overflow.important-channels", "", 0); fluid_settings_register_str(settings, "synth.midi-bank-select", "gs", 0); fluid_settings_add_option(settings, "synth.midi-bank-select", "gm"); fluid_settings_add_option(settings, "synth.midi-bank-select", "gs"); fluid_settings_add_option(settings, "synth.midi-bank-select", "xg"); fluid_settings_add_option(settings, "synth.midi-bank-select", "mma"); fluid_settings_register_int(settings, "synth.dynamic-sample-loading", 0, 0, 1, FLUID_HINT_TOGGLED); } /** * Get FluidSynth runtime version. * @param major Location to store major number * @param minor Location to store minor number * @param micro Location to store micro number */ void fluid_version(int *major, int *minor, int *micro) { *major = FLUIDSYNTH_VERSION_MAJOR; *minor = FLUIDSYNTH_VERSION_MINOR; *micro = FLUIDSYNTH_VERSION_MICRO; } /** * Get FluidSynth runtime version as a string. * @return FluidSynth version string, which is internal and should not be * modified or freed. */ char * fluid_version_str(void) { return FLUIDSYNTH_VERSION; } /* * void fluid_synth_init * * Does all the initialization for this module. */ static void fluid_synth_init(void) { #ifdef TRAP_ON_FPE /* Turn on floating point exception traps */ feenableexcept(FE_DIVBYZERO | FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID); #endif init_dither(); /* custom_breath2att_mod is not a default modulator specified in SF2.01. it is intended to replace default_vel2att_mod on demand using API fluid_set_breath_mode() or command shell setbreathmode. */ fluid_mod_set_source1(&custom_breath2att_mod, /* The modulator we are programming here */ BREATH_MSB, /* Source. breath MSB corresponds to 2. */ FLUID_MOD_CC /* MIDI continuous controller */ | FLUID_MOD_CONCAVE /* Curve shape. Corresponds to 'type=1' */ | FLUID_MOD_UNIPOLAR /* Polarity. Corresponds to 'P=0' */ | FLUID_MOD_NEGATIVE /* Direction. Corresponds to 'D=1' */ ); fluid_mod_set_source2(&custom_breath2att_mod, 0, 0); /* No 2nd source */ fluid_mod_set_dest(&custom_breath2att_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ fluid_mod_set_amount(&custom_breath2att_mod, FLUID_PEAK_ATTENUATION); /* Modulation amount: 960 */ /* SF2.01 page 53 section 8.4.1: MIDI Note-On Velocity to Initial Attenuation */ fluid_mod_set_source1(&default_vel2att_mod, /* The modulator we are programming here */ FLUID_MOD_VELOCITY, /* Source. VELOCITY corresponds to 'index=2'. */ FLUID_MOD_GC /* Not a MIDI continuous controller */ | FLUID_MOD_CONCAVE /* Curve shape. Corresponds to 'type=1' */ | FLUID_MOD_UNIPOLAR /* Polarity. Corresponds to 'P=0' */ | FLUID_MOD_NEGATIVE /* Direction. Corresponds to 'D=1' */ ); fluid_mod_set_source2(&default_vel2att_mod, 0, 0); /* No 2nd source */ fluid_mod_set_dest(&default_vel2att_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ fluid_mod_set_amount(&default_vel2att_mod, FLUID_PEAK_ATTENUATION); /* Modulation amount: 960 */ /* SF2.01 page 53 section 8.4.2: MIDI Note-On Velocity to Filter Cutoff * Have to make a design decision here. The specs don't make any sense this way or another. * One sound font, 'Kingston Piano', which has been praised for its quality, tries to * override this modulator with an amount of 0 and positive polarity (instead of what * the specs say, D=1) for the secondary source. * So if we change the polarity to 'positive', one of the best free sound fonts works... */ fluid_mod_set_source1(&default_vel2filter_mod, FLUID_MOD_VELOCITY, /* Index=2 */ FLUID_MOD_GC /* CC=0 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_NEGATIVE /* D=1 */ ); fluid_mod_set_source2(&default_vel2filter_mod, FLUID_MOD_VELOCITY, /* Index=2 */ FLUID_MOD_GC /* CC=0 */ | FLUID_MOD_SWITCH /* type=3 */ | FLUID_MOD_UNIPOLAR /* P=0 */ // do not remove | FLUID_MOD_NEGATIVE /* D=1 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_dest(&default_vel2filter_mod, GEN_FILTERFC); /* Target: Initial filter cutoff */ fluid_mod_set_amount(&default_vel2filter_mod, -2400); /* SF2.01 page 53 section 8.4.3: MIDI Channel pressure to Vibrato LFO pitch depth */ fluid_mod_set_source1(&default_at2viblfo_mod, FLUID_MOD_CHANNELPRESSURE, /* Index=13 */ FLUID_MOD_GC /* CC=0 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&default_at2viblfo_mod, 0, 0); /* no second source */ fluid_mod_set_dest(&default_at2viblfo_mod, GEN_VIBLFOTOPITCH); /* Target: Vib. LFO => pitch */ fluid_mod_set_amount(&default_at2viblfo_mod, 50); /* SF2.01 page 53 section 8.4.4: Mod wheel (Controller 1) to Vibrato LFO pitch depth */ fluid_mod_set_source1(&default_mod2viblfo_mod, MODULATION_MSB, /* Index=1 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&default_mod2viblfo_mod, 0, 0); /* no second source */ fluid_mod_set_dest(&default_mod2viblfo_mod, GEN_VIBLFOTOPITCH); /* Target: Vib. LFO => pitch */ fluid_mod_set_amount(&default_mod2viblfo_mod, 50); /* SF2.01 page 55 section 8.4.5: MIDI continuous controller 7 to initial attenuation*/ fluid_mod_set_source1(&default_att_mod, VOLUME_MSB, /* index=7 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_CONCAVE /* type=1 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_NEGATIVE /* D=1 */ ); fluid_mod_set_source2(&default_att_mod, 0, 0); /* No second source */ fluid_mod_set_dest(&default_att_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ fluid_mod_set_amount(&default_att_mod, FLUID_PEAK_ATTENUATION); /* Amount: 960 */ /* SF2.01 page 55 section 8.4.6 MIDI continuous controller 10 to Pan Position */ fluid_mod_set_source1(&default_pan_mod, PAN_MSB, /* index=10 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_BIPOLAR /* P=1 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&default_pan_mod, 0, 0); /* No second source */ fluid_mod_set_dest(&default_pan_mod, GEN_PAN); /* Target: pan */ /* Amount: 500. The SF specs $8.4.6, p. 55 says: "Amount = 1000 tenths of a percent". The center value (64) corresponds to 50%, so it follows that amount = 50% x 1000/% = 500. */ fluid_mod_set_amount(&default_pan_mod, 500.0); /* SF2.01 page 55 section 8.4.7: MIDI continuous controller 11 to initial attenuation*/ fluid_mod_set_source1(&default_expr_mod, EXPRESSION_MSB, /* index=11 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_CONCAVE /* type=1 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_NEGATIVE /* D=1 */ ); fluid_mod_set_source2(&default_expr_mod, 0, 0); /* No second source */ fluid_mod_set_dest(&default_expr_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ fluid_mod_set_amount(&default_expr_mod, FLUID_PEAK_ATTENUATION); /* Amount: 960 */ /* SF2.01 page 55 section 8.4.8: MIDI continuous controller 91 to Reverb send */ fluid_mod_set_source1(&default_reverb_mod, EFFECTS_DEPTH1, /* index=91 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&default_reverb_mod, 0, 0); /* No second source */ fluid_mod_set_dest(&default_reverb_mod, GEN_REVERBSEND); /* Target: Reverb send */ fluid_mod_set_amount(&default_reverb_mod, 200); /* Amount: 200 ('tenths of a percent') */ /* SF2.01 page 55 section 8.4.9: MIDI continuous controller 93 to Chorus send */ fluid_mod_set_source1(&default_chorus_mod, EFFECTS_DEPTH3, /* index=93 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&default_chorus_mod, 0, 0); /* No second source */ fluid_mod_set_dest(&default_chorus_mod, GEN_CHORUSSEND); /* Target: Chorus */ fluid_mod_set_amount(&default_chorus_mod, 200); /* Amount: 200 ('tenths of a percent') */ /* SF2.01 page 57 section 8.4.10 MIDI Pitch Wheel to Initial Pitch ... */ /* Initial Pitch is not a "standard" generator, because it isn't mentioned in the list of generators in the SF2 specifications. That's why destination Initial Pitch is replaced here by fine tune generator. */ fluid_mod_set_source1(&default_pitch_bend_mod, FLUID_MOD_PITCHWHEEL, /* Index=14 */ FLUID_MOD_GC /* CC =0 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_BIPOLAR /* P=1 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&default_pitch_bend_mod, FLUID_MOD_PITCHWHEELSENS, /* Index = 16 */ FLUID_MOD_GC /* CC=0 */ | FLUID_MOD_LINEAR /* type=0 */ | FLUID_MOD_UNIPOLAR /* P=0 */ | FLUID_MOD_POSITIVE /* D=0 */ ); /* Also see the comment in gen.h about GEN_PITCH */ fluid_mod_set_dest(&default_pitch_bend_mod, GEN_FINETUNE); /* Destination: Fine Tune */ fluid_mod_set_amount(&default_pitch_bend_mod, 12700.0); /* Amount: 12700 cents */ /* Non-standard MIDI continuous controller 8 to channel stereo balance */ fluid_mod_set_source1(&custom_balance_mod, BALANCE_MSB, /* Index=8 */ FLUID_MOD_CC /* CC=1 */ | FLUID_MOD_CONCAVE /* type=1 */ | FLUID_MOD_BIPOLAR /* P=1 */ | FLUID_MOD_POSITIVE /* D=0 */ ); fluid_mod_set_source2(&custom_balance_mod, 0, 0); fluid_mod_set_dest(&custom_balance_mod, GEN_CUSTOM_BALANCE); /* Destination: stereo balance */ /* Amount: 96 dB of attenuation (on the opposite channel) */ fluid_mod_set_amount(&custom_balance_mod, FLUID_PEAK_ATTENUATION); /* Amount: 960 */ #ifdef LIBINSTPATCH_SUPPORT /* defer libinstpatch init to fluid_instpatch.c to avoid #include "libinstpatch.h" */ fluid_instpatch_init(); #endif } static FLUID_INLINE unsigned int fluid_synth_get_ticks(fluid_synth_t *synth) { return fluid_atomic_int_get(&synth->ticks_since_start); } static FLUID_INLINE void fluid_synth_add_ticks(fluid_synth_t *synth, int val) { fluid_atomic_int_add(&synth->ticks_since_start, val); } /*************************************************************** * FLUID SAMPLE TIMERS * Timers that use written audio data as timing reference */ struct _fluid_sample_timer_t { fluid_sample_timer_t *next; /* Single linked list of timers */ unsigned long starttick; fluid_timer_callback_t callback; void *data; int isfinished; }; /* * fluid_sample_timer_process - called when synth->ticks is updated */ static void fluid_sample_timer_process(fluid_synth_t *synth) { fluid_sample_timer_t *st; long msec; int cont; unsigned int ticks = fluid_synth_get_ticks(synth); for(st = synth->sample_timers; st; st = st->next) { if(st->isfinished) { continue; } msec = (long)(1000.0 * ((double)(ticks - st->starttick)) / synth->sample_rate); cont = (*st->callback)(st->data, msec); if(cont == 0) { st->isfinished = 1; } } } fluid_sample_timer_t *new_fluid_sample_timer(fluid_synth_t *synth, fluid_timer_callback_t callback, void *data) { fluid_sample_timer_t *result = FLUID_NEW(fluid_sample_timer_t); if(result == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } fluid_sample_timer_reset(synth, result); result->isfinished = 0; result->data = data; result->callback = callback; result->next = synth->sample_timers; synth->sample_timers = result; return result; } void delete_fluid_sample_timer(fluid_synth_t *synth, fluid_sample_timer_t *timer) { fluid_sample_timer_t **ptr; fluid_return_if_fail(synth != NULL); fluid_return_if_fail(timer != NULL); ptr = &synth->sample_timers; while(*ptr) { if(*ptr == timer) { *ptr = timer->next; FLUID_FREE(timer); return; } ptr = &((*ptr)->next); } } void fluid_sample_timer_reset(fluid_synth_t *synth, fluid_sample_timer_t *timer) { timer->starttick = fluid_synth_get_ticks(synth); } /*************************************************************** * * FLUID SYNTH */ static FLUID_INLINE void fluid_synth_update_mixer(fluid_synth_t *synth, fluid_rvoice_function_t method, int intparam, fluid_real_t realparam) { fluid_return_if_fail(synth != NULL && synth->eventhandler != NULL); fluid_return_if_fail(synth->eventhandler->mixer != NULL); fluid_rvoice_eventhandler_push_int_real(synth->eventhandler, method, synth->eventhandler->mixer, intparam, realparam); } static FLUID_INLINE unsigned int fluid_synth_get_min_note_length_LOCAL(fluid_synth_t *synth) { int i; fluid_settings_getint(synth->settings, "synth.min-note-length", &i); return (unsigned int)(i * synth->sample_rate / 1000.0f); } /** * Create new FluidSynth instance. * @param settings Configuration parameters to use (used directly). * @return New FluidSynth instance or NULL on error * * @note The @p settings parameter is used directly and should freed after * the synth has been deleted. Further note that you may modify FluidSettings of the * @p settings instance. However, only those FluidSettings marked as 'realtime' will * affect the synth immediately. */ fluid_synth_t * new_fluid_synth(fluid_settings_t *settings) { fluid_synth_t *synth; fluid_sfloader_t *loader; char *important_channels; int i, nbuf, prio_level = 0; int with_ladspa = 0; /* initialize all the conversion tables and other stuff */ if(fluid_atomic_int_compare_and_exchange(&fluid_synth_initialized, 0, 1)) { fluid_synth_init(); } /* allocate a new synthesizer object */ synth = FLUID_NEW(fluid_synth_t); if(synth == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(synth, 0, sizeof(fluid_synth_t)); fluid_rec_mutex_init(synth->mutex); fluid_settings_getint(settings, "synth.threadsafe-api", &synth->use_mutex); synth->public_api_count = 0; synth->settings = settings; fluid_settings_getint(settings, "synth.reverb.active", &synth->with_reverb); fluid_settings_getint(settings, "synth.chorus.active", &synth->with_chorus); fluid_settings_getint(settings, "synth.verbose", &synth->verbose); fluid_settings_getint(settings, "synth.polyphony", &synth->polyphony); fluid_settings_getnum(settings, "synth.sample-rate", &synth->sample_rate); fluid_settings_getint(settings, "synth.midi-channels", &synth->midi_channels); fluid_settings_getint(settings, "synth.audio-channels", &synth->audio_channels); fluid_settings_getint(settings, "synth.audio-groups", &synth->audio_groups); fluid_settings_getint(settings, "synth.effects-channels", &synth->effects_channels); fluid_settings_getint(settings, "synth.effects-groups", &synth->effects_groups); fluid_settings_getnum_float(settings, "synth.gain", &synth->gain); fluid_settings_getint(settings, "synth.device-id", &synth->device_id); fluid_settings_getint(settings, "synth.cpu-cores", &synth->cores); fluid_settings_getnum_float(settings, "synth.overflow.percussion", &synth->overflow.percussion); fluid_settings_getnum_float(settings, "synth.overflow.released", &synth->overflow.released); fluid_settings_getnum_float(settings, "synth.overflow.sustained", &synth->overflow.sustained); fluid_settings_getnum_float(settings, "synth.overflow.volume", &synth->overflow.volume); fluid_settings_getnum_float(settings, "synth.overflow.age", &synth->overflow.age); fluid_settings_getnum_float(settings, "synth.overflow.important", &synth->overflow.important); /* register the callbacks */ fluid_settings_callback_num(settings, "synth.gain", fluid_synth_handle_gain, synth); fluid_settings_callback_int(settings, "synth.polyphony", fluid_synth_handle_polyphony, synth); fluid_settings_callback_int(settings, "synth.device-id", fluid_synth_handle_device_id, synth); fluid_settings_callback_num(settings, "synth.overflow.percussion", fluid_synth_handle_overflow, synth); fluid_settings_callback_num(settings, "synth.overflow.sustained", fluid_synth_handle_overflow, synth); fluid_settings_callback_num(settings, "synth.overflow.released", fluid_synth_handle_overflow, synth); fluid_settings_callback_num(settings, "synth.overflow.age", fluid_synth_handle_overflow, synth); fluid_settings_callback_num(settings, "synth.overflow.volume", fluid_synth_handle_overflow, synth); fluid_settings_callback_num(settings, "synth.overflow.important", fluid_synth_handle_overflow, synth); fluid_settings_callback_str(settings, "synth.overflow.important-channels", fluid_synth_handle_important_channels, synth); fluid_settings_callback_num(settings, "synth.reverb.room-size", fluid_synth_handle_reverb_chorus_num, synth); fluid_settings_callback_num(settings, "synth.reverb.damp", fluid_synth_handle_reverb_chorus_num, synth); fluid_settings_callback_num(settings, "synth.reverb.width", fluid_synth_handle_reverb_chorus_num, synth); fluid_settings_callback_num(settings, "synth.reverb.level", fluid_synth_handle_reverb_chorus_num, synth); fluid_settings_callback_int(settings, "synth.reverb.active", fluid_synth_handle_reverb_chorus_int, synth); fluid_settings_callback_int(settings, "synth.chorus.active", fluid_synth_handle_reverb_chorus_int, synth); fluid_settings_callback_int(settings, "synth.chorus.nr", fluid_synth_handle_reverb_chorus_int, synth); fluid_settings_callback_num(settings, "synth.chorus.level", fluid_synth_handle_reverb_chorus_num, synth); fluid_settings_callback_num(settings, "synth.chorus.depth", fluid_synth_handle_reverb_chorus_num, synth); fluid_settings_callback_num(settings, "synth.chorus.speed", fluid_synth_handle_reverb_chorus_num, synth); /* do some basic sanity checking on the settings */ if(synth->midi_channels % 16 != 0) { int n = synth->midi_channels / 16; synth->midi_channels = (n + 1) * 16; fluid_settings_setint(settings, "synth.midi-channels", synth->midi_channels); FLUID_LOG(FLUID_WARN, "Requested number of MIDI channels is not a multiple of 16. " "I'll increase the number of channels to the next multiple."); } if(synth->audio_channels < 1) { FLUID_LOG(FLUID_WARN, "Requested number of audio channels is smaller than 1. " "Changing this setting to 1."); synth->audio_channels = 1; } else if(synth->audio_channels > 128) { FLUID_LOG(FLUID_WARN, "Requested number of audio channels is too big (%d). " "Limiting this setting to 128.", synth->audio_channels); synth->audio_channels = 128; } if(synth->audio_groups < 1) { FLUID_LOG(FLUID_WARN, "Requested number of audio groups is smaller than 1. " "Changing this setting to 1."); synth->audio_groups = 1; } else if(synth->audio_groups > 128) { FLUID_LOG(FLUID_WARN, "Requested number of audio groups is too big (%d). " "Limiting this setting to 128.", synth->audio_groups); synth->audio_groups = 128; } if(synth->effects_channels < 2) { FLUID_LOG(FLUID_WARN, "Invalid number of effects channels (%d)." "Setting effects channels to 2.", synth->effects_channels); synth->effects_channels = 2; } /* The number of buffers is determined by the higher number of nr * groups / nr audio channels. If LADSPA is unused, they should be * the same. */ nbuf = synth->audio_channels; if(synth->audio_groups > nbuf) { nbuf = synth->audio_groups; } if(fluid_settings_dupstr(settings, "synth.overflow.important-channels", &important_channels) == FLUID_OK) { if(fluid_synth_set_important_channels(synth, important_channels) != FLUID_OK) { FLUID_LOG(FLUID_WARN, "Failed to set overflow important channels"); } FLUID_FREE(important_channels); } /* as soon as the synth is created it starts playing. */ synth->state = FLUID_SYNTH_PLAYING; synth->fromkey_portamento = INVALID_NOTE; /* disable portamento */ fluid_atomic_int_set(&synth->ticks_since_start, 0); synth->tuning = NULL; fluid_private_init(synth->tuning_iter); /* Initialize multi-core variables if multiple cores enabled */ if(synth->cores > 1) { fluid_settings_getint(synth->settings, "audio.realtime-prio", &prio_level); } /* Allocate event queue for rvoice mixer */ /* In an overflow situation, a new voice takes about 50 spaces in the queue! */ synth->eventhandler = new_fluid_rvoice_eventhandler(synth->polyphony * 64, synth->polyphony, nbuf, synth->effects_channels, synth->effects_groups, synth->sample_rate, synth->cores - 1, prio_level); if(synth->eventhandler == NULL) { goto error_recovery; } /* Setup the list of default modulators. * Needs to happen after eventhandler has been set up, as fluid_synth_enter_api is called in the process */ synth->default_mod = NULL; fluid_synth_add_default_mod(synth, &default_vel2att_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_vel2filter_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_at2viblfo_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_mod2viblfo_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_att_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_pan_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_expr_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_reverb_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_chorus_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &default_pitch_bend_mod, FLUID_SYNTH_ADD); fluid_synth_add_default_mod(synth, &custom_balance_mod, FLUID_SYNTH_ADD); /* Create and initialize the Fx unit.*/ fluid_settings_getint(settings, "synth.ladspa.active", &with_ladspa); if(with_ladspa) { #ifdef LADSPA synth->ladspa_fx = new_fluid_ladspa_fx(synth->sample_rate, FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE); if(synth->ladspa_fx == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } fluid_rvoice_mixer_set_ladspa(synth->eventhandler->mixer, synth->ladspa_fx, synth->audio_groups); #else /* LADSPA */ FLUID_LOG(FLUID_WARN, "FluidSynth has not been compiled with LADSPA support"); #endif /* LADSPA */ } /* allocate and add the dls sfont loader */ #ifdef LIBINSTPATCH_SUPPORT loader = new_fluid_instpatch_loader(settings); if(loader == NULL) { FLUID_LOG(FLUID_WARN, "Failed to create the instpatch SoundFont loader"); } else { fluid_synth_add_sfloader(synth, loader); } #endif /* allocate and add the default sfont loader */ loader = new_fluid_defsfloader(settings); if(loader == NULL) { FLUID_LOG(FLUID_WARN, "Failed to create the default SoundFont loader"); } else { fluid_synth_add_sfloader(synth, loader); } /* allocate all channel objects */ synth->channel = FLUID_ARRAY(fluid_channel_t *, synth->midi_channels); if(synth->channel == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } FLUID_MEMSET(synth->channel, 0, synth->midi_channels * sizeof(*synth->channel)); for(i = 0; i < synth->midi_channels; i++) { synth->channel[i] = new_fluid_channel(synth, i); if(synth->channel[i] == NULL) { goto error_recovery; } } /* allocate all synthesis processes */ synth->nvoice = synth->polyphony; synth->voice = FLUID_ARRAY(fluid_voice_t *, synth->nvoice); if(synth->voice == NULL) { goto error_recovery; } FLUID_MEMSET(synth->voice, 0, synth->nvoice * sizeof(*synth->voice)); for(i = 0; i < synth->nvoice; i++) { synth->voice[i] = new_fluid_voice(synth->eventhandler, synth->sample_rate); if(synth->voice[i] == NULL) { goto error_recovery; } } /* sets a default basic channel */ /* Sets one basic channel: basic channel 0, mode 0 (Omni On - Poly) */ /* (i.e all channels are polyphonic) */ /* Must be called after channel objects allocation */ fluid_synth_set_basic_channel_LOCAL(synth, 0, FLUID_CHANNEL_MODE_OMNION_POLY, synth->midi_channels); synth->min_note_length_ticks = fluid_synth_get_min_note_length_LOCAL(synth); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_polyphony, synth->polyphony, 0.0f); fluid_synth_set_reverb_on(synth, synth->with_reverb); fluid_synth_set_chorus_on(synth, synth->with_chorus); synth->cur = FLUID_BUFSIZE; synth->curmax = 0; synth->dither_index = 0; { double room, damp, width, level; fluid_settings_getnum(settings, "synth.reverb.room-size", &room); fluid_settings_getnum(settings, "synth.reverb.damp", &damp); fluid_settings_getnum(settings, "synth.reverb.width", &width); fluid_settings_getnum(settings, "synth.reverb.level", &level); fluid_synth_set_reverb_full(synth, FLUID_REVMODEL_SET_ALL, room, damp, width, level); } { double level, speed, depth; fluid_settings_getint(settings, "synth.chorus.nr", &i); fluid_settings_getnum(settings, "synth.chorus.level", &level); fluid_settings_getnum(settings, "synth.chorus.speed", &speed); fluid_settings_getnum(settings, "synth.chorus.depth", &depth); fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_ALL, i, level, speed, depth, FLUID_CHORUS_DEFAULT_TYPE); } synth->bank_select = FLUID_BANK_STYLE_GS; if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "gm")) { synth->bank_select = FLUID_BANK_STYLE_GM; } else if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "gs")) { synth->bank_select = FLUID_BANK_STYLE_GS; } else if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "xg")) { synth->bank_select = FLUID_BANK_STYLE_XG; } else if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "mma")) { synth->bank_select = FLUID_BANK_STYLE_MMA; } fluid_synth_process_event_queue(synth); /* FIXME */ synth->start = fluid_curtime(); return synth; error_recovery: delete_fluid_synth(synth); return NULL; } /** * Delete a FluidSynth instance. * @param synth FluidSynth instance to delete * * @note Other users of a synthesizer instance, such as audio and MIDI drivers, * should be deleted prior to freeing the FluidSynth instance. */ void delete_fluid_synth(fluid_synth_t *synth) { int i, k; fluid_list_t *list; fluid_sfont_t *sfont; fluid_sfloader_t *loader; fluid_return_if_fail(synth != NULL); fluid_profiling_print(); /* turn off all voices, needed to unload SoundFont data */ if(synth->voice != NULL) { for(i = 0; i < synth->nvoice; i++) { fluid_voice_t *voice = synth->voice[i]; if(!voice) { continue; } fluid_voice_unlock_rvoice(voice); fluid_voice_overflow_rvoice_finished(voice); if(fluid_voice_is_playing(voice)) { fluid_voice_off(voice); /* If we only use fluid_voice_off(voice) it will trigger a delayed * fluid_voice_stop(voice) via fluid_synth_check_finished_voices(). * But here, we are deleting the fluid_synth_t instance so * fluid_voice_stop() will be never triggered resulting in * SoundFont data never unloaded (i.e a serious memory leak). * So, fluid_voice_stop() must be explicitly called to insure * unloading SoundFont data */ fluid_voice_stop(voice); } } } /* also unset all presets for clean SoundFont unload */ if(synth->channel != NULL) { for(i = 0; i < synth->midi_channels; i++) { if(synth->channel[i] != NULL) { fluid_channel_set_preset(synth->channel[i], NULL); } } } delete_fluid_rvoice_eventhandler(synth->eventhandler); /* delete all the SoundFonts */ for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); fluid_sfont_delete_internal(sfont); } delete_fluid_list(synth->sfont); /* delete all the SoundFont loaders */ for(list = synth->loaders; list; list = fluid_list_next(list)) { loader = (fluid_sfloader_t *) fluid_list_get(list); fluid_sfloader_delete(loader); } delete_fluid_list(synth->loaders); if(synth->channel != NULL) { for(i = 0; i < synth->midi_channels; i++) { delete_fluid_channel(synth->channel[i]); } FLUID_FREE(synth->channel); } if(synth->voice != NULL) { for(i = 0; i < synth->nvoice; i++) { delete_fluid_voice(synth->voice[i]); } FLUID_FREE(synth->voice); } /* free the tunings, if any */ if(synth->tuning != NULL) { for(i = 0; i < 128; i++) { if(synth->tuning[i] != NULL) { for(k = 0; k < 128; k++) { delete_fluid_tuning(synth->tuning[i][k]); } FLUID_FREE(synth->tuning[i]); } } FLUID_FREE(synth->tuning); } fluid_private_free(synth->tuning_iter); #ifdef LADSPA /* Release the LADSPA effects unit */ delete_fluid_ladspa_fx(synth->ladspa_fx); #endif /* delete all default modulators */ delete_fluid_list_mod(synth->default_mod); FLUID_FREE(synth->overflow.important_channels); fluid_rec_mutex_destroy(synth->mutex); FLUID_FREE(synth); } /** * Get a textual representation of the last error * @param synth FluidSynth instance * @return Pointer to string of last error message. Valid until the same * calling thread calls another FluidSynth function which fails. String is * internal and should not be modified or freed. * @deprecated This function is not thread-safe and does not work with multiple synths. * It has been deprecated. It may return "" in a future release and will eventually be removed. */ const char * fluid_synth_error(fluid_synth_t *synth) { return ""; } /** * Send a note-on event to a FluidSynth object. * * This function will take care of proper legato playing. If a note on channel @p chan is * already playing at the given key @p key, it will be released (even if it is sustained). * In other words, overlapping notes are not allowed. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param key MIDI note number (0-127) * @param vel MIDI velocity (0-127, 0=noteoff) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_noteon(fluid_synth_t *synth, int chan, int key, int vel) { int result; fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); fluid_return_val_if_fail(vel >= 0 && vel <= 127, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); result = fluid_synth_noteon_LOCAL(synth, chan, key, vel); FLUID_API_RETURN(result); } /* Local synthesis thread variant of fluid_synth_noteon */ static int fluid_synth_noteon_LOCAL(fluid_synth_t *synth, int chan, int key, int vel) { fluid_channel_t *channel ; /* notes with velocity zero go to noteoff */ if(vel == 0) { return fluid_synth_noteoff_LOCAL(synth, chan, key); } channel = synth->channel[chan]; /* makes sure this channel has a preset */ if(channel->preset == NULL) { if(synth->verbose) { FLUID_LOG(FLUID_INFO, "noteon\t%d\t%d\t%d\t%05d\t%.3f\t%.3f\t%.3f\t%d\t%s", chan, key, vel, 0, fluid_synth_get_ticks(synth) / 44100.0f, (fluid_curtime() - synth->start) / 1000.0f, 0.0f, 0, "channel has no preset"); } return FLUID_FAILED; } if(fluid_channel_is_playing_mono(channel)) /* channel is mono or legato CC is On) */ { /* play the noteOn in monophonic */ return fluid_synth_noteon_mono_LOCAL(synth, chan, key, vel); } else { /* channel is poly and legato CC is Off) */ /* plays the noteOn in polyphonic */ /* Sets the note at first position in monophonic list */ /* In the case where the musician intends to inter the channel in monophonic (by depressing the CC legato on), the next noteOn mono could be played legato with the previous note poly (if the musician choose this). */ fluid_channel_set_onenote_monolist(channel, (unsigned char) key, (unsigned char) vel); /* If there is another voice process on the same channel and key, advance it to the release phase. */ fluid_synth_release_voice_on_same_note_LOCAL(synth, chan, key); /* a noteon poly is passed to fluid_synth_noteon_monopoly_legato(). This allows an opportunity to get this note played legato with a previous note if a CC PTC have been received before this noteon. This behavior is a MIDI specification (see FluidPolymono-0004.pdf chapter 4.3-a ,3.4.11 for details). */ return fluid_synth_noteon_monopoly_legato(synth, chan, INVALID_NOTE, key, vel); } } /** * Sends a note-off event to a FluidSynth object. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param key MIDI note number (0-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise (may just mean that no * voices matched the note off event) */ int fluid_synth_noteoff(fluid_synth_t *synth, int chan, int key) { int result; fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); result = fluid_synth_noteoff_LOCAL(synth, chan, key); FLUID_API_RETURN(result); } /* Local synthesis thread variant of fluid_synth_noteoff */ static int fluid_synth_noteoff_LOCAL(fluid_synth_t *synth, int chan, int key) { int status; fluid_channel_t *channel = synth->channel[chan]; if(fluid_channel_is_playing_mono(channel)) /* channel is mono or legato CC is On) */ { /* play the noteOff in monophonic */ status = fluid_synth_noteoff_mono_LOCAL(synth, chan, key); } else { /* channel is poly and legato CC is Off) */ /* removes the note from the monophonic list */ if(channel->n_notes && key == fluid_channel_last_note(channel)) { fluid_channel_clear_monolist(channel); } status = fluid_synth_noteoff_monopoly(synth, chan, key, 0); } /* Changes the state (Valid/Invalid) of the most recent note played in a staccato manner */ fluid_channel_invalid_prev_note_staccato(channel); return status; } /* Damps voices on a channel (turn notes off), if they're sustained by sustain pedal */ static int fluid_synth_damp_voices_by_sustain_LOCAL(fluid_synth_t *synth, int chan) { fluid_channel_t *channel = synth->channel[chan]; fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if((fluid_voice_get_channel(voice) == chan) && fluid_voice_is_sustained(voice)) { if(voice->key == channel->key_mono_sustained) { /* key_mono_sustained is a possible mono note sustainted (by sustain or sostenuto pedal). It must be marked released (INVALID_NOTE) here because it is released only by sustain pedal */ channel->key_mono_sustained = INVALID_NOTE; } fluid_voice_release(voice); } } return FLUID_OK; } /* Damps voices on a channel (turn notes off), if they're sustained by sostenuto pedal */ static int fluid_synth_damp_voices_by_sostenuto_LOCAL(fluid_synth_t *synth, int chan) { fluid_channel_t *channel = synth->channel[chan]; fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if((fluid_voice_get_channel(voice) == chan) && fluid_voice_is_sostenuto(voice)) { if(voice->key == channel->key_mono_sustained) { /* key_mono_sustained is a possible mono note sustainted (by sustain or sostenuto pedal). It must be marked released (INVALID_NOTE) here because it is released only by sostenuto pedal */ channel->key_mono_sustained = INVALID_NOTE; } fluid_voice_release(voice); } } return FLUID_OK; } /** * Adds the specified modulator \c mod as default modulator to the synth. \c mod will * take effect for any subsequently created voice. * @param synth FluidSynth instance * @param mod Modulator info (values copied, passed in object can be freed immediately afterwards) * @param mode Determines how to handle an existing identical modulator (#fluid_synth_add_mod) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Not realtime safe (due to internal memory allocation) and therefore should not be called * from synthesis context at the risk of stalling audio output. */ int fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode) { fluid_mod_t *default_mod; fluid_mod_t *last_mod = NULL; fluid_mod_t *new_mod; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(mod != NULL, FLUID_FAILED); fluid_return_val_if_fail((mode == FLUID_SYNTH_ADD) || (mode == FLUID_SYNTH_OVERWRITE) , FLUID_FAILED); /* Checks if modulators sources are valid */ if(!fluid_mod_check_sources(mod, "api fluid_synth_add_default_mod mod")) { return FLUID_FAILED; } fluid_synth_api_enter(synth); default_mod = synth->default_mod; while(default_mod != NULL) { if(fluid_mod_test_identity(default_mod, mod)) { if(mode == FLUID_SYNTH_ADD) { default_mod->amount += mod->amount; } else // mode == FLUID_SYNTH_OVERWRITE { default_mod->amount = mod->amount; } FLUID_API_RETURN(FLUID_OK); } last_mod = default_mod; default_mod = default_mod->next; } /* Add a new modulator (no existing modulator to add / overwrite). */ new_mod = new_fluid_mod(); if(new_mod == NULL) { FLUID_API_RETURN(FLUID_FAILED); } fluid_mod_clone(new_mod, mod); new_mod->next = NULL; if(last_mod == NULL) { synth->default_mod = new_mod; } else { last_mod->next = new_mod; } FLUID_API_RETURN(FLUID_OK); } /** * Removes the specified modulator \c mod from the synth's default modulator list. * fluid_mod_test_identity() will be used to test modulator matching. * @param synth synth instance * @param mod The modulator to remove * @return #FLUID_OK if a matching modulator was found and successfully removed, #FLUID_FAILED otherwise * * @note Not realtime safe (due to internal memory freeing) and therefore should not be called * from synthesis context at the risk of stalling audio output. */ int fluid_synth_remove_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod) { fluid_mod_t *default_mod; fluid_mod_t *last_mod; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(mod != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); last_mod = default_mod = synth->default_mod; while(default_mod != NULL) { if(fluid_mod_test_identity(default_mod, mod)) { if(synth->default_mod == default_mod) { synth->default_mod = default_mod->next; } else { last_mod->next = default_mod->next; } delete_fluid_mod(default_mod); FLUID_API_RETURN(FLUID_OK); } last_mod = default_mod; default_mod = default_mod->next; } FLUID_API_RETURN(FLUID_FAILED); } /** * Send a MIDI controller event on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param num MIDI controller number (0-127) * @param val MIDI controller value (0-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @note This function supports MIDI Global Controllers which will be sent to * all channels of the basic channel if this basic channel is in mode OmniOff/Mono. * This is accomplished by sending the CC one MIDI channel below the basic * channel of the receiver. * Examples: let a synthesizer with 16 MIDI channels: * - Let a basic channel 7 in mode 3 (Omni Off, Mono). If MIDI channel 6 is disabled it * could be used as CC global for all channels belonging to basic channel 7. * - Let a basic channel 0 in mode 3. If MIDI channel 15 is disabled it could be used * as CC global for all channels belonging to basic channel 0. */ int fluid_synth_cc(fluid_synth_t *synth, int chan, int num, int val) { int result = FLUID_FAILED; fluid_channel_t *channel; fluid_return_val_if_fail(num >= 0 && num <= 127, FLUID_FAILED); fluid_return_val_if_fail(val >= 0 && val <= 127, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); channel = synth->channel[chan]; if(channel->mode & FLUID_CHANNEL_ENABLED) { /* chan is enabled */ if(synth->verbose) { FLUID_LOG(FLUID_INFO, "cc\t%d\t%d\t%d", chan, num, val); } fluid_channel_set_cc(channel, num, val); result = fluid_synth_cc_LOCAL(synth, chan, num); } else /* chan is disabled so it is a candidate for global channel */ { /* looks for next basic channel */ int n_chan = synth->midi_channels; /* MIDI Channels number */ int basicchan ; if(chan < n_chan - 1) { basicchan = chan + 1; /* next channel */ } else { basicchan = 0; /* wrap to 0 */ } channel = synth->channel[basicchan]; /* Channel must be a basicchan in mode OMNIOFF_MONO */ if((channel->mode & FLUID_CHANNEL_BASIC) && ((channel->mode & FLUID_CHANNEL_MODE_MASK) == FLUID_CHANNEL_MODE_OMNIOFF_MONO)) { /* sends cc to all channels in this basic channel */ int i, nbr = channel->mode_val; for(i = basicchan; i < basicchan + nbr; i++) { if(synth->verbose) { FLUID_LOG(FLUID_INFO, "cc\t%d\t%d\t%d", i, num, val); } fluid_channel_set_cc(synth->channel[i], num, val); result = fluid_synth_cc_LOCAL(synth, i, num); } } /* The channel chan is not a valid 'global channel' */ else { result = FLUID_FAILED; } } FLUID_API_RETURN(result); } /* Local synthesis thread variant of MIDI CC set function. Most of CC are allowed to modulate but not all. A comment describes if CC num isn't allowed to modulate. Following explanations should help to understand both MIDI specifications and Soundfont specifications in regard to MIDI specs. MIDI specs: CC LSB (32 to 63) are LSB contributions to CC MSB (0 to 31). It's up to the synthesizer to decide to take LSB values into account or not. Actually Fluidsynth doesn't use CC LSB value inside fluid_voice_update_param() (once fluid_voice_modulate() has been triggered). This is because actually fluidsynth needs only 7 bits resolution (and not 14 bits) from these CCs. So fluidsynth is using only 7 bit MSB (except for portamento time). In regard to MIDI specs Fluidsynth behaves correctly. Soundfont specs 2.01 - 8.2.1: To deal correctly with MIDI CC (regardless if any synth will use CC MSB alone (7 bit) or both CCs MSB,LSB (14 bits) during synthesis), SF specs recommend not making use of CC LSB (i.e only CC MSB) in modulator sources to trigger modulation (i.e modulators with CC LSB connected to sources inputs should be ignored). These specifics are particularly suited for synths that use 14 bits CCs. In this case, the MIDI transmitter sends CC LSB first followed by CC MSB. The MIDI synth receives both CC LSB and CC MSB but only CC MSB will trigger the modulation. This will produce correct synthesis parameters update from a correct 14 bits CC. If in SF specs, modulator sources with CC LSB had been accepted, both CC LSB and CC MSB will triggers 2 modulations. This leads to incorrect synthesis parameters update followed by correct synthesis parameters update. However, as long as fluidsynth will use only CC 7 bits resolution, it is safe to ignore these SF recommendations on CC receive. */ static int fluid_synth_cc_LOCAL(fluid_synth_t *synth, int channum, int num) { fluid_channel_t *chan = synth->channel[channum]; int nrpn_select; int value; value = fluid_channel_get_cc(chan, num); switch(num) { case LOCAL_CONTROL: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ break; /* CC omnioff, omnion, mono, poly */ /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ case POLY_OFF: case POLY_ON: case OMNI_OFF: case OMNI_ON: /* allowed only if channum is a basic channel */ if(chan->mode & FLUID_CHANNEL_BASIC) { /* Construction of new_mode from current channel mode and this CC mode */ int new_mode = chan->mode & FLUID_CHANNEL_MODE_MASK; switch(num) { case POLY_OFF: new_mode |= FLUID_CHANNEL_POLY_OFF; break; case POLY_ON: new_mode &= ~FLUID_CHANNEL_POLY_OFF; break; case OMNI_OFF: new_mode |= FLUID_CHANNEL_OMNI_OFF; break; case OMNI_ON: new_mode &= ~FLUID_CHANNEL_OMNI_OFF; break; default: /* should never happen */ return FLUID_FAILED; } /* MIDI specs: if value is 0 it means all channels from channum to next basic channel minus 1 (if any) or to MIDI channel count minus 1. However, if value is > 0 (e.g. 4), the group of channels will be be limited to 4. value is ignored for #FLUID_CHANNEL_MODE_OMNIOFF_POLY as this mode implies a group of only one channel. */ /* Checks value range and changes this existing basic channel group */ value = fluid_synth_check_next_basic_channel(synth, channum, new_mode, value); if(value != FLUID_FAILED) { /* reset the current basic channel before changing it */ fluid_synth_reset_basic_channel_LOCAL(synth, channum, chan->mode_val); fluid_synth_set_basic_channel_LOCAL(synth, channum, new_mode, value); break; /* FLUID_OK */ } } return FLUID_FAILED; case LEGATO_SWITCH: /* not allowed to modulate */ /* handles Poly/mono commutation on Legato pedal On/Off.*/ fluid_channel_cc_legato(chan, value); break; case PORTAMENTO_SWITCH: /* not allowed to modulate */ /* Special handling of the monophonic list */ /* Invalids the most recent note played in a staccato manner */ fluid_channel_invalid_prev_note_staccato(chan); break; case SUSTAIN_SWITCH: /* not allowed to modulate */ /* Release voices if Sustain switch is released */ if(value < 64) /* Sustain is released */ { fluid_synth_damp_voices_by_sustain_LOCAL(synth, channum); } break; case SOSTENUTO_SWITCH: /* not allowed to modulate */ /* Release voices if Sostetuno switch is released */ if(value < 64) /* Sostenuto is released */ { fluid_synth_damp_voices_by_sostenuto_LOCAL(synth, channum); } else /* Sostenuto is depressed */ /* Update sostenuto order id when pedaling on Sostenuto */ { chan->sostenuto_orderid = synth->noteid; /* future voice id value */ } break; case BANK_SELECT_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_channel_set_bank_msb(chan, value & 0x7F); break; case BANK_SELECT_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_channel_set_bank_lsb(chan, value & 0x7F); break; case ALL_NOTES_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_synth_all_notes_off_LOCAL(synth, channum); break; case ALL_SOUND_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_synth_all_sounds_off_LOCAL(synth, channum); break; case ALL_CTRL_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_channel_init_ctrl(chan, 1); fluid_synth_modulate_voices_all_LOCAL(synth, channum); break; case DATA_ENTRY_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ break; case DATA_ENTRY_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ { int data = (value << 7) + fluid_channel_get_cc(chan, DATA_ENTRY_LSB); if(chan->nrpn_active) /* NRPN is active? */ { /* SontFont 2.01 NRPN Message (Sect. 9.6, p. 74) */ if((fluid_channel_get_cc(chan, NRPN_MSB) == 120) && (fluid_channel_get_cc(chan, NRPN_LSB) < 100)) { nrpn_select = chan->nrpn_select; if(nrpn_select < GEN_LAST) { float val = fluid_gen_scale_nrpn(nrpn_select, data); fluid_synth_set_gen_LOCAL(synth, channum, nrpn_select, val); } chan->nrpn_select = 0; /* Reset to 0 */ } } else if(fluid_channel_get_cc(chan, RPN_MSB) == 0) /* RPN is active: MSB = 0? */ { switch(fluid_channel_get_cc(chan, RPN_LSB)) { case RPN_PITCH_BEND_RANGE: /* Set bend range in semitones */ fluid_channel_set_pitch_wheel_sensitivity(synth->channel[channum], value); fluid_synth_update_pitch_wheel_sens_LOCAL(synth, channum); /* Update bend range */ /* FIXME - Handle LSB? (Fine bend range in cents) */ break; case RPN_CHANNEL_FINE_TUNE: /* Fine tune is 14 bit over +/-1 semitone (+/- 100 cents, 8192 = center) */ fluid_synth_set_gen_LOCAL(synth, channum, GEN_FINETUNE, (float)(data - 8192) * (100.0f / 8192.0f)); break; case RPN_CHANNEL_COARSE_TUNE: /* Coarse tune is 7 bit and in semitones (64 is center) */ fluid_synth_set_gen_LOCAL(synth, channum, GEN_COARSETUNE, value - 64); break; case RPN_TUNING_PROGRAM_CHANGE: fluid_channel_set_tuning_prog(chan, value); fluid_synth_activate_tuning(synth, channum, fluid_channel_get_tuning_bank(chan), value, TRUE); break; case RPN_TUNING_BANK_SELECT: fluid_channel_set_tuning_bank(chan, value); break; case RPN_MODULATION_DEPTH_RANGE: break; } } break; } case NRPN_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ fluid_channel_set_cc(chan, NRPN_LSB, 0); chan->nrpn_select = 0; chan->nrpn_active = 1; break; case NRPN_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ /* SontFont 2.01 NRPN Message (Sect. 9.6, p. 74) */ if(fluid_channel_get_cc(chan, NRPN_MSB) == 120) { if(value == 100) { chan->nrpn_select += 100; } else if(value == 101) { chan->nrpn_select += 1000; } else if(value == 102) { chan->nrpn_select += 10000; } else if(value < 100) { chan->nrpn_select += value; } } chan->nrpn_active = 1; break; case RPN_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ case RPN_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ chan->nrpn_active = 0; break; case BREATH_MSB: /* handles CC Breath On/Off noteOn/noteOff mode */ fluid_channel_cc_breath_note_on_off(chan, value); /* fall-through */ default: /* CC lsb shouldn't allowed to modulate (spec SF 2.01 - 8.2.1) */ /* However, as long fluidsynth will use only CC 7 bits resolution, it is safe to ignore these SF recommendations on CC receive. See explanations above */ /* if (! (32 <= num && num <= 63)) */ { return fluid_synth_modulate_voices_LOCAL(synth, channum, 1, num); } } return FLUID_OK; } /** * Get current MIDI controller value on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param num MIDI controller number (0-127) * @param pval Location to store MIDI controller value (0-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_get_cc(fluid_synth_t *synth, int chan, int num, int *pval) { fluid_return_val_if_fail(num >= 0 && num < 128, FLUID_FAILED); fluid_return_val_if_fail(pval != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); *pval = fluid_channel_get_cc(synth->channel[chan], num); FLUID_API_RETURN(FLUID_OK); } /* * Handler for synth.device-id setting. */ static void fluid_synth_handle_device_id(void *data, const char *name, int value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); synth->device_id = value; fluid_synth_api_exit(synth); } /** * Process a MIDI SYSEX (system exclusive) message. * @param synth FluidSynth instance * @param data Buffer containing SYSEX data (not including 0xF0 and 0xF7) * @param len Length of data in buffer * @param response Buffer to store response to or NULL to ignore * @param response_len IN/OUT parameter, in: size of response buffer, out: * amount of data written to response buffer (if FLUID_FAILED is returned and * this value is non-zero, it indicates the response buffer is too small) * @param handled Optional location to store boolean value if message was * recognized and handled or not (set to TRUE if it was handled) * @param dryrun TRUE to just do a dry run but not actually execute the SYSEX * command (useful for checking if a SYSEX message would be handled) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ /* SYSEX format (0xF0 and 0xF7 not passed to this function): * Non-realtime: 0xF0 0x7E [BODY] 0xF7 * Realtime: 0xF0 0x7F [BODY] 0xF7 * Tuning messages: 0xF0 0x7E/0x7F 0x08 [BODY] 0xF7 */ int fluid_synth_sysex(fluid_synth_t *synth, const char *data, int len, char *response, int *response_len, int *handled, int dryrun) { int avail_response = 0; if(handled) { *handled = FALSE; } if(response_len) { avail_response = *response_len; *response_len = 0; } fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(data != NULL, FLUID_FAILED); fluid_return_val_if_fail(len > 0, FLUID_FAILED); fluid_return_val_if_fail(!response || response_len, FLUID_FAILED); if(len < 4) { return FLUID_OK; } /* MIDI tuning SYSEX message? */ if((data[0] == MIDI_SYSEX_UNIV_NON_REALTIME || data[0] == MIDI_SYSEX_UNIV_REALTIME) && (data[1] == synth->device_id || data[1] == MIDI_SYSEX_DEVICE_ID_ALL) && data[2] == MIDI_SYSEX_MIDI_TUNING_ID) { int result; fluid_synth_api_enter(synth); result = fluid_synth_sysex_midi_tuning(synth, data, len, response, response_len, avail_response, handled, dryrun); FLUID_API_RETURN(result); } return FLUID_OK; } /* Handler for MIDI tuning SYSEX messages */ static int fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, int len, char *response, int *response_len, int avail_response, int *handled, int dryrun) { int realtime, msgid; int bank = 0, prog, channels; double tunedata[128]; int keys[128]; char name[17]={0}; int note, frac, frac2; uint8_t chksum; int i, count, index; const char *dataptr; char *resptr;; realtime = data[0] == MIDI_SYSEX_UNIV_REALTIME; msgid = data[3]; switch(msgid) { case MIDI_SYSEX_TUNING_BULK_DUMP_REQ: case MIDI_SYSEX_TUNING_BULK_DUMP_REQ_BANK: if(data[3] == MIDI_SYSEX_TUNING_BULK_DUMP_REQ) { if(len != 5 || data[4] & 0x80 || !response) { return FLUID_OK; } *response_len = 406; prog = data[4]; } else { if(len != 6 || data[4] & 0x80 || data[5] & 0x80 || !response) { return FLUID_OK; } *response_len = 407; bank = data[4]; prog = data[5]; } if(dryrun) { if(handled) { *handled = TRUE; } return FLUID_OK; } if(avail_response < *response_len) { return FLUID_FAILED; } /* Get tuning data, return if tuning not found */ if(fluid_synth_tuning_dump(synth, bank, prog, name, 17, tunedata) == FLUID_FAILED) { *response_len = 0; return FLUID_OK; } resptr = response; *resptr++ = MIDI_SYSEX_UNIV_NON_REALTIME; *resptr++ = synth->device_id; *resptr++ = MIDI_SYSEX_MIDI_TUNING_ID; *resptr++ = MIDI_SYSEX_TUNING_BULK_DUMP; if(msgid == MIDI_SYSEX_TUNING_BULK_DUMP_REQ_BANK) { *resptr++ = bank; } *resptr++ = prog; /* copy 16 ASCII characters (potentially not null terminated) to the sysex buffer */ FLUID_MEMCPY(resptr, name, 16); resptr += 16; for(i = 0; i < 128; i++) { note = tunedata[i] / 100.0; fluid_clip(note, 0, 127); frac = ((tunedata[i] - note * 100.0) * 16384.0 + 50.0) / 100.0; fluid_clip(frac, 0, 16383); *resptr++ = note; *resptr++ = frac >> 7; *resptr++ = frac & 0x7F; } if(msgid == MIDI_SYSEX_TUNING_BULK_DUMP_REQ) { /* NOTE: Checksum is not as straight forward as the bank based messages */ chksum = MIDI_SYSEX_UNIV_NON_REALTIME ^ MIDI_SYSEX_MIDI_TUNING_ID ^ MIDI_SYSEX_TUNING_BULK_DUMP ^ prog; for(i = 21; i < 128 * 3 + 21; i++) { chksum ^= response[i]; } } else { for(i = 1, chksum = 0; i < 406; i++) { chksum ^= response[i]; } } *resptr++ = chksum & 0x7F; if(handled) { *handled = TRUE; } break; case MIDI_SYSEX_TUNING_NOTE_TUNE: case MIDI_SYSEX_TUNING_NOTE_TUNE_BANK: dataptr = data + 4; if(msgid == MIDI_SYSEX_TUNING_NOTE_TUNE) { if(len < 10 || data[4] & 0x80 || data[5] & 0x80 || len != data[5] * 4 + 6) { return FLUID_OK; } } else { if(len < 11 || data[4] & 0x80 || data[5] & 0x80 || data[6] & 0x80 || len != data[6] * 4 + 7) { return FLUID_OK; } bank = *dataptr++; } if(dryrun) { if(handled) { *handled = TRUE; } return FLUID_OK; } prog = *dataptr++; count = *dataptr++; for(i = 0, index = 0; i < count; i++) { note = *dataptr++; if(note & 0x80) { return FLUID_OK; } keys[index] = note; note = *dataptr++; frac = *dataptr++; frac2 = *dataptr++; if(note & 0x80 || frac & 0x80 || frac2 & 0x80) { return FLUID_OK; } frac = frac << 7 | frac2; /* No change pitch value? Doesn't really make sense to send that, but.. */ if(note == 0x7F && frac == 16383) { continue; } tunedata[index] = note * 100.0 + (frac * 100.0 / 16384.0); index++; } if(index > 0) { if(fluid_synth_tune_notes(synth, bank, prog, index, keys, tunedata, realtime) == FLUID_FAILED) { return FLUID_FAILED; } } if(handled) { *handled = TRUE; } break; case MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE: case MIDI_SYSEX_TUNING_OCTAVE_TUNE_2BYTE: if((msgid == MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE && len != 19) || (msgid == MIDI_SYSEX_TUNING_OCTAVE_TUNE_2BYTE && len != 31)) { return FLUID_OK; } if(data[4] & 0x80 || data[5] & 0x80 || data[6] & 0x80) { return FLUID_OK; } if(dryrun) { if(handled) { *handled = TRUE; } return FLUID_OK; } channels = (data[4] & 0x03) << 14 | data[5] << 7 | data[6]; if(msgid == MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE) { for(i = 0; i < 12; i++) { frac = data[i + 7]; if(frac & 0x80) { return FLUID_OK; } tunedata[i] = (int)frac - 64; } } else { for(i = 0; i < 12; i++) { frac = data[i * 2 + 7]; frac2 = data[i * 2 + 8]; if(frac & 0x80 || frac2 & 0x80) { return FLUID_OK; } tunedata[i] = (((int)frac << 7 | (int)frac2) - 8192) * (200.0 / 16384.0); } } if(fluid_synth_activate_octave_tuning(synth, 0, 0, "SYSEX", tunedata, realtime) == FLUID_FAILED) { return FLUID_FAILED; } if(channels) { for(i = 0; i < 16; i++) { if(channels & (1 << i)) { fluid_synth_activate_tuning(synth, i, 0, 0, realtime); } } } if(handled) { *handled = TRUE; } break; } return FLUID_OK; } /** * Turn off all voices that are playing on the given MIDI channel, by putting them into release phase. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1), (chan=-1 selects all channels) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.4 */ int fluid_synth_all_notes_off(fluid_synth_t *synth, int chan) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(chan >= -1, FLUID_FAILED); fluid_synth_api_enter(synth); if(chan >= synth->midi_channels) { result = FLUID_FAILED; } else { /* Allowed (even for channel disabled) as chan = -1 selects all channels */ result = fluid_synth_all_notes_off_LOCAL(synth, chan); } FLUID_API_RETURN(result); } /* Local synthesis thread variant of all notes off, (chan=-1 selects all channels) */ //static int int fluid_synth_all_notes_off_LOCAL(fluid_synth_t *synth, int chan) { fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_playing(voice) && ((-1 == chan) || (chan == fluid_voice_get_channel(voice)))) { fluid_voice_noteoff(voice); } } return FLUID_OK; } /** * Immediately stop all voices on the given MIDI channel (skips release phase). * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1), (chan=-1 selects all channels) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.4 */ int fluid_synth_all_sounds_off(fluid_synth_t *synth, int chan) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(chan >= -1, FLUID_FAILED); fluid_synth_api_enter(synth); if(chan >= synth->midi_channels) { result = FLUID_FAILED; } else { /* Allowed (even for channel disabled) as chan = -1 selects all channels */ result = fluid_synth_all_sounds_off_LOCAL(synth, chan); } FLUID_API_RETURN(result); } /* Local synthesis thread variant of all sounds off, (chan=-1 selects all channels) */ static int fluid_synth_all_sounds_off_LOCAL(fluid_synth_t *synth, int chan) { fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_playing(voice) && ((-1 == chan) || (chan == fluid_voice_get_channel(voice)))) { fluid_voice_off(voice); } } return FLUID_OK; } /** * Reset reverb engine * @param synth FluidSynth instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_reset_reverb(fluid_synth_t *synth) { fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_reverb, 0, 0.0f); FLUID_API_RETURN(FLUID_OK); } /** * Reset chorus engine * @param synth FluidSynth instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_reset_chorus(fluid_synth_t *synth) { fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_chorus, 0, 0.0f); FLUID_API_RETURN(FLUID_OK); } /** * Send MIDI system reset command (big red 'panic' button), turns off notes, resets * controllers and restores initial basic channel configuration. * @param synth FluidSynth instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_system_reset(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); result = fluid_synth_system_reset_LOCAL(synth); FLUID_API_RETURN(result); } /* Local variant of the system reset command */ static int fluid_synth_system_reset_LOCAL(fluid_synth_t *synth) { int i; fluid_synth_all_sounds_off_LOCAL(synth, -1); for(i = 0; i < synth->midi_channels; i++) { fluid_channel_reset(synth->channel[i]); } /* Basic channel 0, Mode Omni On Poly */ fluid_synth_set_basic_channel(synth, 0, FLUID_CHANNEL_MODE_OMNION_POLY, synth->midi_channels); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_reverb, 0, 0.0f); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_chorus, 0, 0.0f); return FLUID_OK; } /** * Update voices on a MIDI channel after a MIDI control change. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param is_cc Boolean value indicating if ctrl is a CC controller or not * @param ctrl MIDI controller value * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ static int fluid_synth_modulate_voices_LOCAL(fluid_synth_t *synth, int chan, int is_cc, int ctrl) { fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_get_channel(voice) == chan) { fluid_voice_modulate(voice, is_cc, ctrl); } } return FLUID_OK; } /** * Update voices on a MIDI channel after all MIDI controllers have been changed. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ static int fluid_synth_modulate_voices_all_LOCAL(fluid_synth_t *synth, int chan) { fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_get_channel(voice) == chan) { fluid_voice_modulate_all(voice); } } return FLUID_OK; } /** * Set the MIDI channel pressure controller value. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param val MIDI channel pressure value (0-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_channel_pressure(fluid_synth_t *synth, int chan, int val) { int result; fluid_return_val_if_fail(val >= 0 && val <= 127, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); if(synth->verbose) { FLUID_LOG(FLUID_INFO, "channelpressure\t%d\t%d", chan, val); } fluid_channel_set_channel_pressure(synth->channel[chan], val); result = fluid_synth_update_channel_pressure_LOCAL(synth, chan); FLUID_API_RETURN(result); } /* Updates channel pressure from within synthesis thread */ static int fluid_synth_update_channel_pressure_LOCAL(fluid_synth_t *synth, int chan) { return fluid_synth_modulate_voices_LOCAL(synth, chan, 0, FLUID_MOD_CHANNELPRESSURE); } /** * Set the MIDI polyphonic key pressure controller value. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param key MIDI key number (0-127) * @param val MIDI key pressure value (0-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 2.0.0 */ int fluid_synth_key_pressure(fluid_synth_t *synth, int chan, int key, int val) { int result; fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); fluid_return_val_if_fail(val >= 0 && val <= 127, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); if(synth->verbose) { FLUID_LOG(FLUID_INFO, "keypressure\t%d\t%d\t%d", chan, key, val); } fluid_channel_set_key_pressure(synth->channel[chan], key, val); result = fluid_synth_update_key_pressure_LOCAL(synth, chan, key); FLUID_API_RETURN(result); } /* Updates key pressure from within synthesis thread */ static int fluid_synth_update_key_pressure_LOCAL(fluid_synth_t *synth, int chan, int key) { fluid_voice_t *voice; int i; int result = FLUID_OK; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(voice->chan == chan && voice->key == key) { result = fluid_voice_modulate(voice, 0, FLUID_MOD_KEYPRESSURE); if(result != FLUID_OK) { return result; } } } return result; } /** * Set the MIDI pitch bend controller value on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param val MIDI pitch bend value (0-16383 with 8192 being center) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_pitch_bend(fluid_synth_t *synth, int chan, int val) { int result; fluid_return_val_if_fail(val >= 0 && val <= 16383, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); if(synth->verbose) { FLUID_LOG(FLUID_INFO, "pitchb\t%d\t%d", chan, val); } fluid_channel_set_pitch_bend(synth->channel[chan], val); result = fluid_synth_update_pitch_bend_LOCAL(synth, chan); FLUID_API_RETURN(result); } /* Local synthesis thread variant of pitch bend */ static int fluid_synth_update_pitch_bend_LOCAL(fluid_synth_t *synth, int chan) { return fluid_synth_modulate_voices_LOCAL(synth, chan, 0, FLUID_MOD_PITCHWHEEL); } /** * Get the MIDI pitch bend controller value on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param ppitch_bend Location to store MIDI pitch bend value (0-16383 with * 8192 being center) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_get_pitch_bend(fluid_synth_t *synth, int chan, int *ppitch_bend) { int result; fluid_return_val_if_fail(ppitch_bend != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); *ppitch_bend = fluid_channel_get_pitch_bend(synth->channel[chan]); result = FLUID_OK; FLUID_API_RETURN(result); } /** * Set MIDI pitch wheel sensitivity on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param val Pitch wheel sensitivity value in semitones * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_pitch_wheel_sens(fluid_synth_t *synth, int chan, int val) { int result; fluid_return_val_if_fail(val >= 0 && val <= 72, FLUID_FAILED); /* 6 octaves!? Better than no limit.. */ FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); if(synth->verbose) { FLUID_LOG(FLUID_INFO, "pitchsens\t%d\t%d", chan, val); } fluid_channel_set_pitch_wheel_sensitivity(synth->channel[chan], val); result = fluid_synth_update_pitch_wheel_sens_LOCAL(synth, chan); FLUID_API_RETURN(result); } /* Local synthesis thread variant of set pitch wheel sensitivity */ static int fluid_synth_update_pitch_wheel_sens_LOCAL(fluid_synth_t *synth, int chan) { return fluid_synth_modulate_voices_LOCAL(synth, chan, 0, FLUID_MOD_PITCHWHEELSENS); } /** * Get MIDI pitch wheel sensitivity on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param pval Location to store pitch wheel sensitivity value in semitones * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since Sometime AFTER v1.0 API freeze. */ int fluid_synth_get_pitch_wheel_sens(fluid_synth_t *synth, int chan, int *pval) { int result; fluid_return_val_if_fail(pval != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); *pval = fluid_channel_get_pitch_wheel_sensitivity(synth->channel[chan]); result = FLUID_OK; FLUID_API_RETURN(result); } /** * Assign a preset to a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param preset Preset to assign to channel or NULL to clear (ownership is taken over) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ static int fluid_synth_set_preset(fluid_synth_t *synth, int chan, fluid_preset_t *preset) { fluid_channel_t *channel; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(chan >= 0 && chan < synth->midi_channels, FLUID_FAILED); channel = synth->channel[chan]; return fluid_channel_set_preset(channel, preset); } /* Get a preset by SoundFont, bank and program numbers. * Returns preset pointer or NULL. */ static fluid_preset_t * fluid_synth_get_preset(fluid_synth_t *synth, int sfontnum, int banknum, int prognum) { fluid_sfont_t *sfont; fluid_list_t *list; /* 128 indicates an "unset" operation" */ if(prognum == FLUID_UNSET_PROGRAM) { return NULL; } for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(fluid_sfont_get_id(sfont) == sfontnum) { return fluid_sfont_get_preset(sfont, banknum - sfont->bankofs, prognum); } } return NULL; } /* Get a preset by SoundFont name, bank and program. * Returns preset pointer or NULL. */ static fluid_preset_t * fluid_synth_get_preset_by_sfont_name(fluid_synth_t *synth, const char *sfontname, int banknum, int prognum) { fluid_sfont_t *sfont; fluid_list_t *list; for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(FLUID_STRCMP(fluid_sfont_get_name(sfont), sfontname) == 0) { return fluid_sfont_get_preset(sfont, banknum - sfont->bankofs, prognum); } } return NULL; } /* Find a preset by bank and program numbers. * Returns preset pointer or NULL. */ fluid_preset_t * fluid_synth_find_preset(fluid_synth_t *synth, int banknum, int prognum) { fluid_preset_t *preset; fluid_sfont_t *sfont; fluid_list_t *list; for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); preset = fluid_sfont_get_preset(sfont, banknum - sfont->bankofs, prognum); if(preset) { return preset; } } return NULL; } /** * Send a program change event on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param prognum MIDI program number (0-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ /* FIXME - Currently not real-time safe, due to preset allocation and mutex lock, * and may be called from within synthesis context. */ /* As of 1.1.1 prognum can be set to 128 to unset the preset. Not documented * since fluid_synth_unset_program() should be used instead. */ int fluid_synth_program_change(fluid_synth_t *synth, int chan, int prognum) { fluid_preset_t *preset = NULL; fluid_channel_t *channel; int subst_bank, subst_prog, banknum = 0, result = FLUID_FAILED; fluid_return_val_if_fail(prognum >= 0 && prognum <= 128, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); channel = synth->channel[chan]; if(channel->channel_type == CHANNEL_TYPE_DRUM) { banknum = DRUM_INST_BANK; } else { fluid_channel_get_sfont_bank_prog(channel, NULL, &banknum, NULL); } if(synth->verbose) { FLUID_LOG(FLUID_INFO, "prog\t%d\t%d\t%d", chan, banknum, prognum); } /* I think this is a hack for MIDI files that do bank changes in GM mode. * Proper way to handle this would probably be to ignore bank changes when in * GM mode. - JG * This is now possible by setting synth.midi-bank-select=gm, but let the hack * stay for the time being. - DH */ if(prognum != FLUID_UNSET_PROGRAM) { subst_bank = banknum; subst_prog = prognum; preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); /* Fallback to another preset if not found */ if(!preset) { /* Percussion: Fallback to preset 0 in percussion bank */ if(channel->channel_type == CHANNEL_TYPE_DRUM) { subst_prog = 0; subst_bank = DRUM_INST_BANK; preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); } /* Melodic instrument */ else { /* Fallback first to bank 0:prognum */ subst_bank = 0; preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); /* Fallback to first preset in bank 0 (usually piano...) */ if(!preset) { subst_prog = 0; preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); } } if(preset) { FLUID_LOG(FLUID_WARN, "Instrument not found on channel %d [bank=%d prog=%d], substituted [bank=%d prog=%d]", chan, banknum, prognum, subst_bank, subst_prog); } else { FLUID_LOG(FLUID_WARN, "No preset found on channel %d [bank=%d prog=%d]", chan, banknum, prognum); } } } /* Assign the SoundFont ID and program number to the channel */ fluid_channel_set_sfont_bank_prog(channel, preset ? fluid_sfont_get_id(preset->sfont) : 0, -1, prognum); result = fluid_synth_set_preset(synth, chan, preset); FLUID_API_RETURN(result); } /** * Set instrument bank number on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param bank MIDI bank number * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @note This function does not change the instrument currently assigned to \c chan, * as it is usually called prior to fluid_synth_program_change(). If you still want * instrument changes to take effect immediately, call fluid_synth_program_reset() * after having set up the bank configuration. * */ int fluid_synth_bank_select(fluid_synth_t *synth, int chan, int bank) { int result; fluid_return_val_if_fail(bank <= 16383, FLUID_FAILED); fluid_return_val_if_fail(bank >= 0, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); fluid_channel_set_sfont_bank_prog(synth->channel[chan], -1, bank, -1); result = FLUID_OK; FLUID_API_RETURN(result); } /** * Set SoundFont ID on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param sfont_id ID of a loaded SoundFont * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @note This function does not change the instrument currently assigned to \c chan, * as it is usually called prior to fluid_synth_bank_select() or fluid_synth_program_change(). * If you still want instrument changes to take effect immediately, call fluid_synth_program_reset() * after having selected the soundfont. */ int fluid_synth_sfont_select(fluid_synth_t *synth, int chan, int sfont_id) { int result; FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); fluid_channel_set_sfont_bank_prog(synth->channel[chan], sfont_id, -1, -1); result = FLUID_OK; FLUID_API_RETURN(result); } /** * Set the preset of a MIDI channel to an unassigned state. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.1 * * @note Channel retains its SoundFont ID and bank numbers, while the program * number is set to an "unset" state. MIDI program changes may re-assign a * preset if one matches. */ int fluid_synth_unset_program(fluid_synth_t *synth, int chan) { FLUID_API_ENTRY_CHAN(FLUID_FAILED); FLUID_API_RETURN(fluid_synth_program_change(synth, chan, FLUID_UNSET_PROGRAM)); } /** * Get current SoundFont ID, bank number and program number for a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param sfont_id Location to store SoundFont ID * @param bank_num Location to store MIDI bank number * @param preset_num Location to store MIDI program number * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_get_program(fluid_synth_t *synth, int chan, int *sfont_id, int *bank_num, int *preset_num) { int result; fluid_channel_t *channel; fluid_return_val_if_fail(sfont_id != NULL, FLUID_FAILED); fluid_return_val_if_fail(bank_num != NULL, FLUID_FAILED); fluid_return_val_if_fail(preset_num != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); channel = synth->channel[chan]; fluid_channel_get_sfont_bank_prog(channel, sfont_id, bank_num, preset_num); /* 128 indicates that the preset is unset. Set to 0 to be backwards compatible. */ if(*preset_num == FLUID_UNSET_PROGRAM) { *preset_num = 0; } result = FLUID_OK; FLUID_API_RETURN(result); } /** * Select an instrument on a MIDI channel by SoundFont ID, bank and program numbers. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param sfont_id ID of a loaded SoundFont * @param bank_num MIDI bank number * @param preset_num MIDI program number * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_program_select(fluid_synth_t *synth, int chan, int sfont_id, int bank_num, int preset_num) { fluid_preset_t *preset = NULL; fluid_channel_t *channel; int result; fluid_return_val_if_fail(bank_num >= 0, FLUID_FAILED); fluid_return_val_if_fail(preset_num >= 0, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); channel = synth->channel[chan]; preset = fluid_synth_get_preset(synth, sfont_id, bank_num, preset_num); if(preset == NULL) { FLUID_LOG(FLUID_ERR, "There is no preset with bank number %d and preset number %d in SoundFont %d", bank_num, preset_num, sfont_id); FLUID_API_RETURN(FLUID_FAILED); } /* Assign the new SoundFont ID, bank and program number to the channel */ fluid_channel_set_sfont_bank_prog(channel, sfont_id, bank_num, preset_num); result = fluid_synth_set_preset(synth, chan, preset); FLUID_API_RETURN(result); } /** * Select an instrument on a MIDI channel by SoundFont name, bank and program numbers. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param sfont_name Name of a loaded SoundFont * @param bank_num MIDI bank number * @param preset_num MIDI program number * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ int fluid_synth_program_select_by_sfont_name(fluid_synth_t *synth, int chan, const char *sfont_name, int bank_num, int preset_num) { fluid_preset_t *preset = NULL; fluid_channel_t *channel; int result; fluid_return_val_if_fail(sfont_name != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* Allowed only on MIDI channel enabled */ FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); channel = synth->channel[chan]; preset = fluid_synth_get_preset_by_sfont_name(synth, sfont_name, bank_num, preset_num); if(preset == NULL) { FLUID_LOG(FLUID_ERR, "There is no preset with bank number %d and preset number %d in SoundFont %s", bank_num, preset_num, sfont_name); FLUID_API_RETURN(FLUID_FAILED); } /* Assign the new SoundFont ID, bank and program number to the channel */ fluid_channel_set_sfont_bank_prog(channel, fluid_sfont_get_id(preset->sfont), bank_num, preset_num); result = fluid_synth_set_preset(synth, chan, preset); FLUID_API_RETURN(result); } /* * This function assures that every MIDI channel has a valid preset * (NULL is okay). This function is called after a SoundFont is * unloaded or reloaded. */ static void fluid_synth_update_presets(fluid_synth_t *synth) { fluid_channel_t *channel; fluid_preset_t *preset; int sfont, bank, prog; int chan; for(chan = 0; chan < synth->midi_channels; chan++) { channel = synth->channel[chan]; fluid_channel_get_sfont_bank_prog(channel, &sfont, &bank, &prog); preset = fluid_synth_get_preset(synth, sfont, bank, prog); fluid_synth_set_preset(synth, chan, preset); } } /** * Set up an event to change the sample-rate of the synth during the next rendering call. * @warning This function is broken-by-design! Don't use it! Instead, specify the sample-rate when creating the synth. * @deprecated As of fluidsynth 2.1.0 this function has been deprecated. * Changing the sample-rate is generally not considered to be a real-time use-case, as it always produces some audible artifact ("click", "pop") on the dry sound and effects (because LFOs for chorus and reverb need to be reinitialized). * The sample-rate change may also require memory allocation deep down in the effect units. * However, this memory allocation may fail and there is no way for the caller to know that, because the actual change of the sample-rate is executed during rendering. * This function cannot (must not) do the sample-rate change itself, otherwise the synth needs to be locked down, causing rendering to block. * Esp. do not use this function if this @p synth instance is used by an audio driver, because the audio driver cannot be notified by this sample-rate change. * Long story short: don't use it. * @code{.cpp} fluid_synth_t* synth; // assume initialized // [...] // sample-rate change needed? Delete the audio driver, if any. delete_fluid_audio_driver(adriver); // then delete the synth delete_fluid_synth(synth); // update the sample-rate fluid_settings_setnum(settings, "synth.sample-rate", 22050.0); // and re-create objects synth = new_fluid_synth(settings); adriver = new_fluid_audio_driver(settings, synth); * @endcode * @param synth FluidSynth instance * @param sample_rate New sample-rate (Hz) * @since 1.1.2 */ void fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate) { int i; fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); fluid_clip(sample_rate, 8000.0f, 96000.0f); synth->sample_rate = sample_rate; synth->min_note_length_ticks = fluid_synth_get_min_note_length_LOCAL(synth); for(i = 0; i < synth->polyphony; i++) { fluid_voice_set_output_rate(synth->voice[i], sample_rate); } fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_samplerate, 0, sample_rate); fluid_synth_api_exit(synth); } /* Handler for synth.gain setting. */ static void fluid_synth_handle_gain(void *data, const char *name, double value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_synth_set_gain(synth, (float) value); } /** * Set synth output gain value. * @param synth FluidSynth instance * @param gain Gain value (function clamps value to the range 0.0 to 10.0) */ void fluid_synth_set_gain(fluid_synth_t *synth, float gain) { fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); fluid_clip(gain, 0.0f, 10.0f); synth->gain = gain; fluid_synth_update_gain_LOCAL(synth); fluid_synth_api_exit(synth); } /* Called by synthesis thread to update the gain in all voices */ static void fluid_synth_update_gain_LOCAL(fluid_synth_t *synth) { fluid_voice_t *voice; float gain; int i; gain = synth->gain; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_playing(voice)) { fluid_voice_set_gain(voice, gain); } } } /** * Get synth output gain value. * @param synth FluidSynth instance * @return Synth gain value (0.0 to 10.0) */ float fluid_synth_get_gain(fluid_synth_t *synth) { float result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->gain; FLUID_API_RETURN(result); } /* * Handler for synth.polyphony setting. */ static void fluid_synth_handle_polyphony(void *data, const char *name, int value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_synth_set_polyphony(synth, value); } /** * Set synthesizer polyphony (max number of voices). * @param synth FluidSynth instance * @param polyphony Polyphony to assign * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.0.6 */ int fluid_synth_set_polyphony(fluid_synth_t *synth, int polyphony) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(polyphony >= 1 && polyphony <= 65535, FLUID_FAILED); fluid_synth_api_enter(synth); result = fluid_synth_update_polyphony_LOCAL(synth, polyphony); FLUID_API_RETURN(result); } /* Called by synthesis thread to update the polyphony value */ static int fluid_synth_update_polyphony_LOCAL(fluid_synth_t *synth, int new_polyphony) { fluid_voice_t *voice; int i; if(new_polyphony > synth->nvoice) { /* Create more voices */ fluid_voice_t **new_voices = FLUID_REALLOC(synth->voice, sizeof(fluid_voice_t *) * new_polyphony); if(new_voices == NULL) { return FLUID_FAILED; } synth->voice = new_voices; for(i = synth->nvoice; i < new_polyphony; i++) { synth->voice[i] = new_fluid_voice(synth->eventhandler, synth->sample_rate); if(synth->voice[i] == NULL) { return FLUID_FAILED; } fluid_voice_set_custom_filter(synth->voice[i], synth->custom_filter_type, synth->custom_filter_flags); } synth->nvoice = new_polyphony; } synth->polyphony = new_polyphony; /* turn off any voices above the new limit */ for(i = synth->polyphony; i < synth->nvoice; i++) { voice = synth->voice[i]; if(fluid_voice_is_playing(voice)) { fluid_voice_off(voice); } } fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_polyphony, synth->polyphony, 0.0f); return FLUID_OK; } /** * Get current synthesizer polyphony (max number of voices). * @param synth FluidSynth instance * @return Synth polyphony value. * @since 1.0.6 */ int fluid_synth_get_polyphony(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); result = synth->polyphony; FLUID_API_RETURN(result); } /** * @brief Get current number of active voices. * * I.e. the no. of voices that have been * started and have not yet finished. Unless called from synthesis context, * this number does not necessarily have to be equal to the number of voices * currently processed by the DSP loop, see below. * @param synth FluidSynth instance * @return Number of currently active voices. * @since 1.1.0 * * @note To generate accurate continuous statistics of the voice count, caller * should ensure this function is called synchronously with the audio synthesis * process. This can be done in the new_fluid_audio_driver2() audio callback * function for example. Otherwise every call to this function may return different * voice counts as it may change after any (concurrent) call to fluid_synth_write_*() made by * e.g. an audio driver or the applications audio rendering thread. */ int fluid_synth_get_active_voice_count(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); result = synth->active_voice_count; FLUID_API_RETURN(result); } /** * Get the internal synthesis buffer size value. * @param synth FluidSynth instance * @return Internal buffer size in audio frames. * * Audio is synthesized this number of frames at a time. Defaults to 64 frames. */ int fluid_synth_get_internal_bufsize(fluid_synth_t *synth) { return FLUID_BUFSIZE; } /** * Resend a bank select and a program change for every channel and assign corresponding instruments. * @param synth FluidSynth instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * This function is called mainly after a SoundFont has been loaded, * unloaded or reloaded. */ int fluid_synth_program_reset(fluid_synth_t *synth) { int i, prog; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); /* try to set the correct presets */ for(i = 0; i < synth->midi_channels; i++) { fluid_channel_get_sfont_bank_prog(synth->channel[i], NULL, NULL, &prog); fluid_synth_program_change(synth, i, prog); } FLUID_API_RETURN(FLUID_OK); } /** * Synthesize a block of floating point audio to separate audio buffers (multichannel rendering). First effect channel used by reverb, second for chorus. * @param synth FluidSynth instance * @param len Count of audio frames to synthesize * @param left Array of float buffers to store left channel of planar audio (as many as \c synth.audio-channels buffers, each of \c len in size) * @param right Array of float buffers to store right channel of planar audio (size: dito) * @param fx_left Since 1.1.7: If not \c NULL, array of float buffers to store left effect channels (as many as \c synth.effects-channels buffers, each of \c len in size) * @param fx_right Since 1.1.7: If not \c NULL, array of float buffers to store right effect channels (size: dito) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Should only be called from synthesis thread. * * @deprecated fluid_synth_nwrite_float() is deprecated and will be removed in a future release. It may continue to work or it may return #FLUID_FAILED in the future. Consider using the more powerful and flexible fluid_synth_process(). * * Usage example: * @code{.cpp} const int FramesToRender = 64; int channels; // retrieve number of stereo audio channels fluid_settings_getint(settings, "synth.audio-channels", &channels); // we need twice as many (mono-)buffers channels *= 2; // fluid_synth_nwrite_float renders planar audio, e.g. if synth.audio-channels==16: each midi channel gets rendered to its own stereo buffer, rather than having one buffer and interleaved PCM float** mix_buf = new float*[channels]; for(int i = 0; i < channels; i++) { mix_buf[i] = new float[FramesToRender]; } // retrieve number of (stereo) effect channels (internally hardcoded to reverb (first chan) and chrous (second chan)) fluid_settings_getint(settings, "synth.effects-channels", &channels); channels *= 2; float** fx_buf = new float*[channels]; for(int i = 0; i < channels; i++) { fx_buf[i] = new float[FramesToRender]; } float** mix_buf_l = mix_buf; float** mix_buf_r = &mix_buf[channels/2]; float** fx_buf_l = fx_buf; float** fx_buf_r = &fx_buf[channels/2]; fluid_synth_nwrite_float(synth, FramesToRender, mix_buf_l, mix_buf_r, fx_buf_l, fx_buf_r) * @endcode */ int fluid_synth_nwrite_float(fluid_synth_t *synth, int len, float **left, float **right, float **fx_left, float **fx_right) { fluid_real_t *left_in, *fx_left_in; fluid_real_t *right_in, *fx_right_in; double time = fluid_utime(); int i, num, available, count; #ifdef WITH_FLOAT int bytes; #endif float cpu_load; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(left != NULL, FLUID_FAILED); fluid_return_val_if_fail(right != NULL, FLUID_FAILED); fluid_return_val_if_fail(len >= 0, FLUID_FAILED); fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below /* First, take what's still available in the buffer */ count = 0; num = synth->cur; if(synth->cur < FLUID_BUFSIZE) { available = FLUID_BUFSIZE - synth->cur; fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); num = (available > len) ? len : available; #ifdef WITH_FLOAT bytes = num * sizeof(float); #endif for(i = 0; i < synth->audio_channels; i++) { #ifdef WITH_FLOAT FLUID_MEMCPY(left[i], &left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); FLUID_MEMCPY(right[i], &right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); #else //WITH_FLOAT int j; for(j = 0; j < num; j++) { left[i][j] = (float) left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; right[i][j] = (float) right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; } #endif //WITH_FLOAT } for(i = 0; i < synth->effects_channels; i++) { #ifdef WITH_FLOAT if(fx_left != NULL) { FLUID_MEMCPY(fx_left[i], &fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); } if(fx_right != NULL) { FLUID_MEMCPY(fx_right[i], &fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); } #else //WITH_FLOAT int j; if(fx_left != NULL) { for(j = 0; j < num; j++) { fx_left[i][j] = (float) fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; } } if(fx_right != NULL) { for(j = 0; j < num; j++) { fx_right[i][j] = (float) fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; } } #endif //WITH_FLOAT } count += num; num += synth->cur; /* if we're now done, num becomes the new synth->cur below */ } /* Then, run one_block() and copy till we have 'len' samples */ while(count < len) { fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 0); fluid_synth_render_blocks(synth, 1); // TODO: fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); num = (FLUID_BUFSIZE > len - count) ? len - count : FLUID_BUFSIZE; #ifdef WITH_FLOAT bytes = num * sizeof(float); #endif for(i = 0; i < synth->audio_channels; i++) { #ifdef WITH_FLOAT FLUID_MEMCPY(left[i] + count, &left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); FLUID_MEMCPY(right[i] + count, &right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); #else //WITH_FLOAT int j; for(j = 0; j < num; j++) { left[i][j + count] = (float) left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; right[i][j + count] = (float) right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; } #endif //WITH_FLOAT } for(i = 0; i < synth->effects_channels; i++) { #ifdef WITH_FLOAT if(fx_left != NULL) { FLUID_MEMCPY(fx_left[i] + count, &fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); } if(fx_right != NULL) { FLUID_MEMCPY(fx_right[i] + count, &fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); } #else //WITH_FLOAT int j; if(fx_left != NULL) { for(j = 0; j < num; j++) { fx_left[i][j + count] = (float) fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; } } if(fx_right != NULL) { for(j = 0; j < num; j++) { fx_right[i][j + count] = (float) fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; } } #endif //WITH_FLOAT } count += num; } synth->cur = num; time = fluid_utime() - time; cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); fluid_atomic_float_set(&synth->cpu_load, cpu_load); return FLUID_OK; } /** * mixes the samples of \p in to \p out * * @param out the output sample buffer to mix to * @param ooff sample offset in \p out * @param in the rvoice_mixer input sample buffer to mix from * @param ioff sample offset in \p in * @param buf_idx the sample buffer index of \p in to mix from * @param num number of samples to mix */ static FLUID_INLINE void fluid_synth_mix_single_buffer(float *FLUID_RESTRICT out, int ooff, const fluid_real_t *FLUID_RESTRICT in, int ioff, int buf_idx, int num) { if(out != NULL) { int j; for(j = 0; j < num; j++) { out[j + ooff] += (float) in[buf_idx * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + ioff]; } } } /** * @brief Synthesize floating point audio to stereo audio channels (implements the default interface #fluid_audio_func_t). * * Synthesize and mix audio to a given number of planar audio buffers. * Therefore pass nout = N*2 float buffers to \p out in order to render * the synthesized audio to \p N stereo channels. Each float buffer must be * able to hold \p len elements. * * \p out contains an array of planar buffers for normal, dry, stereo * audio (alternating left and right). Like: @code{.cpp} out[0] = left_buffer_audio_channel_0 out[1] = right_buffer_audio_channel_0 out[2] = left_buffer_audio_channel_1 out[3] = right_buffer_audio_channel_1 ... out[ (i * 2 + 0) % nout ] = left_buffer_audio_channel_i out[ (i * 2 + 1) % nout ] = right_buffer_audio_channel_i @endcode * * for zero-based channel index \p i. * The buffer layout of \p fx used for storing effects * like reverb and chorus looks similar: @code{.cpp} fx[0] = left_buffer_channel_of_reverb_unit_0 fx[1] = right_buffer_channel_of_reverb_unit_0 fx[2] = left_buffer_channel_of_chorus_unit_0 fx[3] = right_buffer_channel_of_chorus_unit_0 fx[4] = left_buffer_channel_of_reverb_unit_1 fx[5] = right_buffer_channel_of_reverb_unit_1 fx[6] = left_buffer_channel_of_chorus_unit_1 fx[7] = right_buffer_channel_of_chorus_unit_1 fx[8] = left_buffer_channel_of_reverb_unit_2 ... fx[ ((k * fluid_synth_count_effects_channels() + j) * 2 + 0) % nfx ] = left_buffer_for_effect_channel_j_of_unit_k fx[ ((k * fluid_synth_count_effects_channels() + j) * 2 + 1) % nfx ] = right_buffer_for_effect_channel_j_of_unit_k @endcode * where 0 <= k < fluid_synth_count_effects_groups() is a zero-based index denoting the effects unit and * 0 <= j < fluid_synth_count_effects_channels() is a zero-based index denoting the effect channel within * unit \p k. * * Any voice playing is assigned to audio channels based on the MIDI channel its playing on. Let \p chan be the * zero-based MIDI channel index an arbitrary voice is playing on. To determine the audio channel and effects unit it is * going to be rendered to use: * * i = chan % fluid_synth_count_audio_groups() * * k = chan % fluid_synth_count_effects_groups() * * @param synth FluidSynth instance * @param len Count of audio frames to synthesize and store in every single buffer provided by \p out and \p fx. * @param nfx Count of arrays in \c fx. Must be a multiple of 2 (because of stereo) * and in the range 0 <= nfx/2 <= (fluid_synth_count_effects_channels() * fluid_synth_count_effects_groups()). * @param fx Array of buffers to store effects audio to. Buffers may alias with buffers of \c out. NULL buffers are permitted and will cause to skip mixing any audio into that buffer. * @param nout Count of arrays in \c out. Must be a multiple of 2 (because of stereo) and in the range 0 <= nout/2 <= fluid_synth_count_audio_channels(). * @param out Array of buffers to store (dry) audio to. Buffers may alias with buffers of \c fx. NULL buffers are permitted and will cause to skip mixing any audio into that buffer. * @return #FLUID_OK on success, #FLUID_FAILED otherwise. * * @parblock * @note The owner of the sample buffers must zero them out before calling this * function, because any synthesized audio is mixed (i.e. added) to the buffers. * E.g. if fluid_synth_process() is called from a custom audio driver process function * (see new_fluid_audio_driver2()), the audio driver takes care of zeroing the buffers. * @endparblock * * @parblock * @note No matter how many buffers you pass in, fluid_synth_process() * will always render all audio channels to the * buffers in \c out and all effects channels to the * buffers in \c fx, provided that nout > 0 and nfx > 0 respectively. If * nout/2 < fluid_synth_count_audio_channels() it will wrap around. Same * is true for effects audio if nfx/2 < (fluid_synth_count_effects_channels() * fluid_synth_count_effects_groups()). * See usage examples below. * @endparblock * * @parblock * @note Should only be called from synthesis thread. * @endparblock */ int fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[]) { return fluid_synth_process_LOCAL(synth, len, nfx, fx, nout, out, fluid_synth_render_blocks); } int fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int)) { fluid_real_t *left_in, *fx_left_in; fluid_real_t *right_in, *fx_right_in; int nfxchan, nfxunits, naudchan; double time = fluid_utime(); int i, f, num, count, buffered_blocks; float cpu_load; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(nfx % 2 == 0, FLUID_FAILED); fluid_return_val_if_fail(nout % 2 == 0, FLUID_FAILED); fluid_return_val_if_fail(len >= 0, FLUID_FAILED); fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below nfxchan = synth->effects_channels; nfxunits = synth->effects_groups; naudchan = synth->audio_channels; fluid_return_val_if_fail(0 <= nfx / 2 && nfx / 2 <= nfxchan * nfxunits, FLUID_FAILED); fluid_return_val_if_fail(0 <= nout / 2 && nout / 2 <= naudchan, FLUID_FAILED); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, FALSE); /* First, take what's still available in the buffer */ count = 0; num = synth->cur; buffered_blocks = (synth->cur + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; if(synth->cur < buffered_blocks * FLUID_BUFSIZE) { int available = (buffered_blocks * FLUID_BUFSIZE) - synth->cur; num = (available > len) ? len : available; if(nout != 0) { for(i = 0; i < naudchan; i++) { float *out_buf = out[(i * 2) % nout]; fluid_synth_mix_single_buffer(out_buf, 0, left_in, synth->cur, i, num); out_buf = out[(i * 2 + 1) % nout]; fluid_synth_mix_single_buffer(out_buf, 0, right_in, synth->cur, i, num); } } if(nfx != 0) { // loop over all effects units for(f = 0; f < nfxunits; f++) { // write out all effects (i.e. reverb and chorus) for(i = 0; i < nfxchan; i++) { int buf_idx = f * nfxchan + i; float *out_buf = fx[(buf_idx * 2) % nfx]; fluid_synth_mix_single_buffer(out_buf, 0, fx_left_in, synth->cur, buf_idx, num); out_buf = fx[(buf_idx * 2 + 1) % nfx]; fluid_synth_mix_single_buffer(out_buf, 0, fx_right_in, synth->cur, buf_idx, num); } } } count += num; num += synth->cur; /* if we're now done, num becomes the new synth->cur below */ } /* Then, render blocks and copy till we have 'len' samples */ while(count < len) { int blocksleft = (len - count + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; int blockcount = block_render_func(synth, blocksleft); num = (blockcount * FLUID_BUFSIZE > len - count) ? len - count : blockcount * FLUID_BUFSIZE; if(nout != 0) { for(i = 0; i < naudchan; i++) { float *out_buf = out[(i * 2) % nout]; fluid_synth_mix_single_buffer(out_buf, count, left_in, 0, i, num); out_buf = out[(i * 2 + 1) % nout]; fluid_synth_mix_single_buffer(out_buf, count, right_in, 0, i, num); } } if(nfx != 0) { // loop over all effects units for(f = 0; f < nfxunits; f++) { // write out all effects (i.e. reverb and chorus) for(i = 0; i < nfxchan; i++) { int buf_idx = f * nfxchan + i; float *out_buf = fx[(buf_idx * 2) % nfx]; fluid_synth_mix_single_buffer(out_buf, count, fx_left_in, 0, buf_idx, num); out_buf = fx[(buf_idx * 2 + 1) % nfx]; fluid_synth_mix_single_buffer(out_buf, count, fx_right_in, 0, buf_idx, num); } } } count += num; } synth->cur = num; time = fluid_utime() - time; cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); fluid_atomic_float_set(&synth->cpu_load, cpu_load); return FLUID_OK; } /** * Synthesize a block of floating point audio samples to audio buffers. * @param synth FluidSynth instance * @param len Count of audio frames to synthesize * @param lout Array of floats to store left channel of audio * @param loff Offset index in 'lout' for first sample * @param lincr Increment between samples stored to 'lout' * @param rout Array of floats to store right channel of audio * @param roff Offset index in 'rout' for first sample * @param rincr Increment between samples stored to 'rout' * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * Useful for storing interleaved stereo (lout = rout, loff = 0, roff = 1, * lincr = 2, rincr = 2). * * @note Should only be called from synthesis thread. * @note Reverb and Chorus are mixed to \c lout resp. \c rout. */ int fluid_synth_write_float(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr) { return fluid_synth_write_float_LOCAL(synth, len, lout, loff, lincr, rout, roff, rincr, fluid_synth_render_blocks); } int fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr, int (*block_render_func)(fluid_synth_t *, int) ) { int n, cur, size; float *left_out = (float *) lout + loff; float *right_out = (float *) rout + roff; fluid_real_t *left_in; fluid_real_t *right_in; double time = fluid_utime(); float cpu_load; fluid_profile_ref_var(prof_ref); fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(lout != NULL, FLUID_FAILED); fluid_return_val_if_fail(rout != NULL, FLUID_FAILED); fluid_return_val_if_fail(len >= 0, FLUID_FAILED); fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 1); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); size = len; cur = synth->cur; do { /* fill up the buffers as needed */ if(cur >= synth->curmax) { int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; synth->curmax = FLUID_BUFSIZE * block_render_func(synth, blocksleft); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); cur = 0; } /* calculate amount of available samples */ n = synth->curmax - cur; /* keep track of emitted samples */ if(n > size) { n = size; } size -= n; /* update pointers to current position */ left_in += cur + n; right_in += cur + n; /* set final cursor position */ cur += n; /* reverse index */ n = 0 - n; do { *left_out = (float) left_in[n]; *right_out = (float) right_in[n]; left_out += lincr; right_out += rincr; } while(++n < 0); } while(size); synth->cur = cur; time = fluid_utime() - time; cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); fluid_atomic_float_set(&synth->cpu_load, cpu_load); fluid_profile_write(FLUID_PROF_WRITE, prof_ref, fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer), len); return FLUID_OK; } #define DITHER_SIZE 48000 #define DITHER_CHANNELS 2 static float rand_table[DITHER_CHANNELS][DITHER_SIZE]; /* Init dither table */ static void init_dither(void) { float d, dp; int c, i; for(c = 0; c < DITHER_CHANNELS; c++) { dp = 0; for(i = 0; i < DITHER_SIZE - 1; i++) { d = rand() / (float)RAND_MAX - 0.5f; rand_table[c][i] = d - dp; dp = d; } rand_table[c][DITHER_SIZE - 1] = 0 - dp; } } /* A portable replacement for roundf(), seems it may actually be faster too! */ static FLUID_INLINE int16_t round_clip_to_i16(float x) { long i; if(x >= 0.0f) { i = (long)(x + 0.5f); if(FLUID_UNLIKELY(i > 32767)) { i = 32767; } } else { i = (long)(x - 0.5f); if(FLUID_UNLIKELY(i < -32768)) { i = -32768; } } return (int16_t)i; } /** * Synthesize a block of 16 bit audio samples to audio buffers. * @param synth FluidSynth instance * @param len Count of audio frames to synthesize * @param lout Array of 16 bit words to store left channel of audio * @param loff Offset index in 'lout' for first sample * @param lincr Increment between samples stored to 'lout' * @param rout Array of 16 bit words to store right channel of audio * @param roff Offset index in 'rout' for first sample * @param rincr Increment between samples stored to 'rout' * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * Useful for storing interleaved stereo (lout = rout, loff = 0, roff = 1, * lincr = 2, rincr = 2). * * @note Should only be called from synthesis thread. * @note Reverb and Chorus are mixed to \c lout resp. \c rout. * @note Dithering is performed when converting from internal floating point to * 16 bit audio. */ int fluid_synth_write_s16(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr) { int di, n, cur, size; int16_t *left_out = (int16_t *)lout + loff; int16_t *right_out = (int16_t *)rout + roff; fluid_real_t *left_in; fluid_real_t *right_in; double time = fluid_utime(); float cpu_load; fluid_profile_ref_var(prof_ref); fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(lout != NULL, FLUID_FAILED); fluid_return_val_if_fail(rout != NULL, FLUID_FAILED); fluid_return_val_if_fail(len >= 0, FLUID_FAILED); fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 1); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); size = len; cur = synth->cur; di = synth->dither_index; do { /* fill up the buffers as needed */ if(cur >= synth->curmax) { int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); cur = 0; } /* calculate amount of available samples */ n = synth->curmax - cur; /* keep track of emitted samples */ if(n > size) { n = size; } size -= n; /* update pointers to current position */ left_in += cur + n; right_in += cur + n; /* set final cursor position */ cur += n; /* reverse index */ n = 0 - n; do { *left_out = round_clip_to_i16(left_in[n] * 32766.0f + rand_table[0][di]); *right_out = round_clip_to_i16(right_in[n] * 32766.0f + rand_table[1][di]); left_out += lincr; right_out += rincr; if(++di >= DITHER_SIZE) { di = 0; } } while(++n < 0); } while(size); synth->cur = cur; synth->dither_index = di; /* keep dither buffer continuous */ time = fluid_utime() - time; cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); fluid_atomic_float_set(&synth->cpu_load, cpu_load); fluid_profile_write(FLUID_PROF_WRITE, prof_ref, fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer), len); return 0; } /** * Converts stereo floating point sample data to signed 16 bit data with dithering. * @param dither_index Pointer to an integer which should be initialized to 0 * before the first call and passed unmodified to additional calls which are * part of the same synthesis output. * @param len Length in frames to convert * @param lin Buffer of left audio samples to convert from * @param rin Buffer of right audio samples to convert from * @param lout Array of 16 bit words to store left channel of audio * @param loff Offset index in 'lout' for first sample * @param lincr Increment between samples stored to 'lout' * @param rout Array of 16 bit words to store right channel of audio * @param roff Offset index in 'rout' for first sample * @param rincr Increment between samples stored to 'rout' * * @note Currently private to libfluidsynth. */ void fluid_synth_dither_s16(int *dither_index, int len, const float *lin, const float *rin, void *lout, int loff, int lincr, void *rout, int roff, int rincr) { int i, j, k; int16_t *left_out = lout; int16_t *right_out = rout; int di = *dither_index; fluid_profile_ref_var(prof_ref); for(i = 0, j = loff, k = roff; i < len; i++, j += lincr, k += rincr) { left_out[j] = round_clip_to_i16(lin[i] * 32766.0f + rand_table[0][di]); right_out[k] = round_clip_to_i16(rin[i] * 32766.0f + rand_table[1][di]); if(++di >= DITHER_SIZE) { di = 0; } } *dither_index = di; /* keep dither buffer continuous */ fluid_profile(FLUID_PROF_WRITE, prof_ref, 0, len); } static void fluid_synth_check_finished_voices(fluid_synth_t *synth) { int j; fluid_rvoice_t *fv; while(NULL != (fv = fluid_rvoice_eventhandler_get_finished_voice(synth->eventhandler))) { for(j = 0; j < synth->polyphony; j++) { if(synth->voice[j]->rvoice == fv) { fluid_voice_unlock_rvoice(synth->voice[j]); fluid_voice_stop(synth->voice[j]); break; } else if(synth->voice[j]->overflow_rvoice == fv) { fluid_voice_overflow_rvoice_finished(synth->voice[j]); break; } } } } /** * Process all waiting events in the rvoice queue. * Make sure no (other) rendering is running in parallel when * you call this function! */ void fluid_synth_process_event_queue(fluid_synth_t *synth) { fluid_rvoice_eventhandler_dispatch_all(synth->eventhandler); } /** * Process blocks (FLUID_BUFSIZE) of audio. * Must be called from renderer thread only! * @return number of blocks rendered. Might (often) return less than requested */ static int fluid_synth_render_blocks(fluid_synth_t *synth, int blockcount) { int i, maxblocks; fluid_profile_ref_var(prof_ref); /* Assign ID of synthesis thread */ // synth->synth_thread_id = fluid_thread_get_id (); fluid_check_fpe("??? Just starting up ???"); fluid_rvoice_eventhandler_dispatch_all(synth->eventhandler); /* do not render more blocks than we can store internally */ maxblocks = fluid_rvoice_mixer_get_bufcount(synth->eventhandler->mixer); if(blockcount > maxblocks) { blockcount = maxblocks; } for(i = 0; i < blockcount; i++) { fluid_sample_timer_process(synth); fluid_synth_add_ticks(synth, FLUID_BUFSIZE); /* If events have been queued waiting for fluid_rvoice_eventhandler_dispatch_all() * (should only happen with parallel render) stop processing and go for rendering */ if(fluid_rvoice_eventhandler_dispatch_count(synth->eventhandler)) { // Something has happened, we can't process more blockcount = i + 1; break; } } fluid_check_fpe("fluid_sample_timer_process"); blockcount = fluid_rvoice_mixer_render(synth->eventhandler->mixer, blockcount); /* Testcase, that provokes a denormal floating point error */ #if 0 { float num = 1; while(num != 0) { num *= 0.5; }; }; #endif fluid_check_fpe("??? Remainder of synth_one_block ???"); fluid_profile(FLUID_PROF_ONE_BLOCK, prof_ref, fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer), blockcount * FLUID_BUFSIZE); return blockcount; } /* * Handler for synth.reverb.* and synth.chorus.* double settings. */ static void fluid_synth_handle_reverb_chorus_num(void *data, const char *name, double value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_return_if_fail(synth != NULL); if(FLUID_STRCMP(name, "synth.reverb.room-size") == 0) { fluid_synth_set_reverb_roomsize(synth, value); } else if(FLUID_STRCMP(name, "synth.reverb.damp") == 0) { fluid_synth_set_reverb_damp(synth, value); } else if(FLUID_STRCMP(name, "synth.reverb.width") == 0) { fluid_synth_set_reverb_width(synth, value); } else if(FLUID_STRCMP(name, "synth.reverb.level") == 0) { fluid_synth_set_reverb_level(synth, value); } else if(FLUID_STRCMP(name, "synth.chorus.depth") == 0) { fluid_synth_set_chorus_depth(synth, value); } else if(FLUID_STRCMP(name, "synth.chorus.speed") == 0) { fluid_synth_set_chorus_speed(synth, value); } else if(FLUID_STRCMP(name, "synth.chorus.level") == 0) { fluid_synth_set_chorus_level(synth, value); } } /* * Handler for synth.reverb.* and synth.chorus.* integer settings. */ static void fluid_synth_handle_reverb_chorus_int(void *data, const char *name, int value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_return_if_fail(synth != NULL); if(FLUID_STRCMP(name, "synth.reverb.active") == 0) { fluid_synth_set_reverb_on(synth, value); } else if(FLUID_STRCMP(name, "synth.chorus.active") == 0) { fluid_synth_set_chorus_on(synth, value); } else if(FLUID_STRCMP(name, "synth.chorus.nr") == 0) { fluid_synth_set_chorus_nr(synth, value); } } /* * Handler for synth.overflow.* settings. */ static void fluid_synth_handle_overflow(void *data, const char *name, double value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); if(FLUID_STRCMP(name, "synth.overflow.percussion") == 0) { synth->overflow.percussion = value; } else if(FLUID_STRCMP(name, "synth.overflow.released") == 0) { synth->overflow.released = value; } else if(FLUID_STRCMP(name, "synth.overflow.sustained") == 0) { synth->overflow.sustained = value; } else if(FLUID_STRCMP(name, "synth.overflow.volume") == 0) { synth->overflow.volume = value; } else if(FLUID_STRCMP(name, "synth.overflow.age") == 0) { synth->overflow.age = value; } else if(FLUID_STRCMP(name, "synth.overflow.important") == 0) { synth->overflow.important = value; } fluid_synth_api_exit(synth); } /* Selects a voice for killing. */ static fluid_voice_t * fluid_synth_free_voice_by_kill_LOCAL(fluid_synth_t *synth) { int i; float best_prio = OVERFLOW_PRIO_CANNOT_KILL - 1; float this_voice_prio; fluid_voice_t *voice; int best_voice_index = -1; unsigned int ticks = fluid_synth_get_ticks(synth); for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; /* safeguard against an available voice. */ if(_AVAILABLE(voice)) { return voice; } this_voice_prio = fluid_voice_get_overflow_prio(voice, &synth->overflow, ticks); /* check if this voice has less priority than the previous candidate. */ if(this_voice_prio < best_prio) { best_voice_index = i; best_prio = this_voice_prio; } } if(best_voice_index < 0) { return NULL; } voice = synth->voice[best_voice_index]; FLUID_LOG(FLUID_DBG, "Killing voice %d, index %d, chan %d, key %d ", fluid_voice_get_id(voice), best_voice_index, fluid_voice_get_channel(voice), fluid_voice_get_key(voice)); fluid_voice_off(voice); return voice; } /** * Allocate a synthesis voice. * @param synth FluidSynth instance * @param sample Sample to assign to the voice * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param key MIDI note number for the voice (0-127) * @param vel MIDI velocity for the voice (0-127) * @return Allocated synthesis voice or NULL on error * * This function is called by a SoundFont's preset in response to a noteon event. * The returned voice comes with default modulators and generators. * A single noteon event may create any number of voices, when the preset is layered. * * @note Should only be called from within synthesis thread, which includes * SoundFont loader preset noteon method. */ fluid_voice_t * fluid_synth_alloc_voice(fluid_synth_t *synth, fluid_sample_t *sample, int chan, int key, int vel) { fluid_return_val_if_fail(sample != NULL, NULL); FLUID_API_ENTRY_CHAN(NULL); FLUID_API_RETURN(fluid_synth_alloc_voice_LOCAL(synth, sample, chan, key, vel, NULL)); } fluid_voice_t * fluid_synth_alloc_voice_LOCAL(fluid_synth_t *synth, fluid_sample_t *sample, int chan, int key, int vel, fluid_zone_range_t *zone_range) { int i, k; fluid_voice_t *voice = NULL; fluid_channel_t *channel = NULL; unsigned int ticks; /* check if there's an available synthesis process */ for(i = 0; i < synth->polyphony; i++) { if(_AVAILABLE(synth->voice[i])) { voice = synth->voice[i]; break; } } /* No success yet? Then stop a running voice. */ if(voice == NULL) { FLUID_LOG(FLUID_DBG, "Polyphony exceeded, trying to kill a voice"); voice = fluid_synth_free_voice_by_kill_LOCAL(synth); } if(voice == NULL) { FLUID_LOG(FLUID_WARN, "Failed to allocate a synthesis process. (chan=%d,key=%d)", chan, key); return NULL; } ticks = fluid_synth_get_ticks(synth); if(synth->verbose) { k = 0; for(i = 0; i < synth->polyphony; i++) { if(!_AVAILABLE(synth->voice[i])) { k++; } } FLUID_LOG(FLUID_INFO, "noteon\t%d\t%d\t%d\t%05d\t%.3f\t%.3f\t%.3f\t%d", chan, key, vel, synth->storeid, (float) ticks / 44100.0f, (fluid_curtime() - synth->start) / 1000.0f, 0.0f, k); } channel = synth->channel[chan]; if(fluid_voice_init(voice, sample, zone_range, channel, key, vel, synth->storeid, ticks, synth->gain) != FLUID_OK) { FLUID_LOG(FLUID_WARN, "Failed to initialize voice"); return NULL; } /* add the default modulators to the synthesis process. */ /* custom_breath2att_modulator is not a default modulator specified in SF it is intended to replace default_vel2att_mod for this channel on demand using API fluid_synth_set_breath_mode() or shell command setbreathmode for this channel. */ { int mono = fluid_channel_is_playing_mono(channel); fluid_mod_t *default_mod = synth->default_mod; while(default_mod != NULL) { if( /* See if default_mod is the velocity_to_attenuation modulator */ fluid_mod_test_identity(default_mod, &default_vel2att_mod) && // See if a replacement by custom_breath2att_modulator has been demanded // for this channel ((!mono && (channel->mode & FLUID_CHANNEL_BREATH_POLY)) || (mono && (channel->mode & FLUID_CHANNEL_BREATH_MONO))) ) { // Replacement of default_vel2att modulator by custom_breath2att_modulator fluid_voice_add_mod_local(voice, &custom_breath2att_mod, FLUID_VOICE_DEFAULT, 0); } else { fluid_voice_add_mod_local(voice, default_mod, FLUID_VOICE_DEFAULT, 0); } // Next default modulator to add to the voice default_mod = default_mod->next; } } return voice; } /* Kill all voices on a given channel, which have the same exclusive class * generator as new_voice. */ static void fluid_synth_kill_by_exclusive_class_LOCAL(fluid_synth_t *synth, fluid_voice_t *new_voice) { int excl_class = fluid_voice_gen_value(new_voice, GEN_EXCLUSIVECLASS); int i; /* Excl. class 0: No exclusive class */ if(excl_class == 0) { return; } /* Kill all notes on the same channel with the same exclusive class */ for(i = 0; i < synth->polyphony; i++) { fluid_voice_t *existing_voice = synth->voice[i]; int existing_excl_class = fluid_voice_gen_value(existing_voice, GEN_EXCLUSIVECLASS); /* If voice is playing, on the same channel, has same exclusive * class and is not part of the same noteon event (voice group), then kill it */ if(fluid_voice_is_playing(existing_voice) && fluid_voice_get_channel(existing_voice) == fluid_voice_get_channel(new_voice) && existing_excl_class == excl_class && fluid_voice_get_id(existing_voice) != fluid_voice_get_id(new_voice)) { fluid_voice_kill_excl(existing_voice); } } } /** * Activate a voice previously allocated with fluid_synth_alloc_voice(). * @param synth FluidSynth instance * @param voice Voice to activate * * This function is called by a SoundFont's preset in response to a noteon * event. Exclusive classes are processed here. * * @note Should only be called from within synthesis thread, which includes * SoundFont loader preset noteon method. */ void fluid_synth_start_voice(fluid_synth_t *synth, fluid_voice_t *voice) { fluid_return_if_fail(synth != NULL); fluid_return_if_fail(voice != NULL); // fluid_return_if_fail (fluid_synth_is_synth_thread (synth)); fluid_synth_api_enter(synth); /* Find the exclusive class of this voice. If set, kill all voices * that match the exclusive class and are younger than the first * voice process created by this noteon event. */ fluid_synth_kill_by_exclusive_class_LOCAL(synth, voice); fluid_voice_start(voice); /* Start the new voice */ fluid_voice_lock_rvoice(voice); fluid_rvoice_eventhandler_add_rvoice(synth->eventhandler, voice->rvoice); fluid_synth_api_exit(synth); } /** * Add a SoundFont loader to the synth. This function takes ownership of \c loader * and frees it automatically upon \c synth destruction. * @param synth FluidSynth instance * @param loader Loader API structure * * SoundFont loaders are used to add custom instrument loading to FluidSynth. * The caller supplied functions for loading files, allocating presets, * retrieving information on them and synthesizing note-on events. Using this * method even non SoundFont instruments can be synthesized, although limited * to the SoundFont synthesis model. * * @note Should only be called before any SoundFont files are loaded. */ void fluid_synth_add_sfloader(fluid_synth_t *synth, fluid_sfloader_t *loader) { fluid_return_if_fail(synth != NULL); fluid_return_if_fail(loader != NULL); fluid_synth_api_enter(synth); /* Test if sfont is already loaded */ if(synth->sfont == NULL) { synth->loaders = fluid_list_prepend(synth->loaders, loader); } fluid_synth_api_exit(synth); } /** * Load a SoundFont file (filename is interpreted by SoundFont loaders). * The newly loaded SoundFont will be put on top of the SoundFont * stack. Presets are searched starting from the SoundFont on the * top of the stack, working the way down the stack until a preset is found. * * @param synth FluidSynth instance * @param filename File to load * @param reset_presets TRUE to re-assign presets for all MIDI channels (equivalent to calling fluid_synth_program_reset()) * @return SoundFont ID on success, #FLUID_FAILED on error */ int fluid_synth_sfload(fluid_synth_t *synth, const char *filename, int reset_presets) { fluid_sfont_t *sfont; fluid_list_t *list; fluid_sfloader_t *loader; int sfont_id; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(filename != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); sfont_id = synth->sfont_id; if(++sfont_id != FLUID_FAILED) { /* MT NOTE: Loaders list should not change. */ for(list = synth->loaders; list; list = fluid_list_next(list)) { loader = (fluid_sfloader_t *) fluid_list_get(list); sfont = fluid_sfloader_load(loader, filename); if(sfont != NULL) { sfont->refcount++; synth->sfont_id = sfont->id = sfont_id; synth->sfont = fluid_list_prepend(synth->sfont, sfont); /* prepend to list */ /* reset the presets for all channels if requested */ if(reset_presets) { fluid_synth_program_reset(synth); } FLUID_API_RETURN(sfont_id); } } } FLUID_LOG(FLUID_ERR, "Failed to load SoundFont \"%s\"", filename); FLUID_API_RETURN(FLUID_FAILED); } /** * Unload a SoundFont. * @param synth FluidSynth instance * @param id ID of SoundFont to unload * @param reset_presets TRUE to re-assign presets for all MIDI channels * @return #FLUID_OK on success, #FLUID_FAILED on error */ int fluid_synth_sfunload(fluid_synth_t *synth, int id, int reset_presets) { fluid_sfont_t *sfont = NULL; fluid_list_t *list; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); /* remove the SoundFont from the list */ for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(fluid_sfont_get_id(sfont) == id) { synth->sfont = fluid_list_remove(synth->sfont, sfont); break; } } if(!list) { FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", id); FLUID_API_RETURN(FLUID_FAILED); } /* reset the presets for all channels (SoundFont will be freed when there are no more references) */ if(reset_presets) { fluid_synth_program_reset(synth); } else { fluid_synth_update_presets(synth); } /* -- Remove synth->sfont list's reference to SoundFont */ fluid_synth_sfont_unref(synth, sfont); FLUID_API_RETURN(FLUID_OK); } /* Unref a SoundFont and destroy if no more references */ void fluid_synth_sfont_unref(fluid_synth_t *synth, fluid_sfont_t *sfont) { fluid_return_if_fail(sfont != NULL); /* Shouldn't happen, programming error if so */ sfont->refcount--; /* -- Remove the sfont list's reference */ if(sfont->refcount == 0) /* No more references? - Attempt delete */ { if(fluid_sfont_delete_internal(sfont) == 0) /* SoundFont loader can block SoundFont unload */ { FLUID_LOG(FLUID_DBG, "Unloaded SoundFont"); } /* spin off a timer thread to unload the sfont later (SoundFont loader blocked unload) */ else { new_fluid_timer(100, fluid_synth_sfunload_callback, sfont, TRUE, TRUE, FALSE); } } } /* Callback to continually attempt to unload a SoundFont, * only if a SoundFont loader blocked the unload operation */ static int fluid_synth_sfunload_callback(void *data, unsigned int msec) { fluid_sfont_t *sfont = data; if(fluid_sfont_delete_internal(sfont) == 0) { FLUID_LOG(FLUID_DBG, "Unloaded SoundFont"); return FALSE; } else { return TRUE; } } /** * Reload a SoundFont. The SoundFont retains its ID and index on the SoundFont stack. * @param synth FluidSynth instance * @param id ID of SoundFont to reload * @return SoundFont ID on success, #FLUID_FAILED on error */ int fluid_synth_sfreload(fluid_synth_t *synth, int id) { char *filename = NULL; fluid_sfont_t *sfont; fluid_sfloader_t *loader; fluid_list_t *list; int index, ret = FLUID_FAILED; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); /* Search for SoundFont and get its index */ for(list = synth->sfont, index = 0; list; list = fluid_list_next(list), index++) { sfont = fluid_list_get(list); if(fluid_sfont_get_id(sfont) == id) { break; } } if(!list) { FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", id); goto exit; } /* keep a copy of the SoundFont's filename */ filename = FLUID_STRDUP(fluid_sfont_get_name(sfont)); if(filename == NULL || fluid_synth_sfunload(synth, id, FALSE) != FLUID_OK) { goto exit; } /* MT Note: SoundFont loader list will not change */ for(list = synth->loaders; list; list = fluid_list_next(list)) { loader = (fluid_sfloader_t *) fluid_list_get(list); sfont = fluid_sfloader_load(loader, filename); if(sfont != NULL) { sfont->id = id; sfont->refcount++; synth->sfont = fluid_list_insert_at(synth->sfont, index, sfont); /* insert the sfont at the same index */ /* reset the presets for all channels */ fluid_synth_update_presets(synth); ret = id; goto exit; } } FLUID_LOG(FLUID_ERR, "Failed to load SoundFont \"%s\"", filename); exit: FLUID_FREE(filename); FLUID_API_RETURN(ret); } /** * Add a SoundFont. The SoundFont will be added to the top of the SoundFont stack. * @param synth FluidSynth instance * @param sfont SoundFont to add * @return New assigned SoundFont ID or #FLUID_FAILED on error */ int fluid_synth_add_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont) { int sfont_id; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(sfont != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); sfont_id = synth->sfont_id; if(++sfont_id != FLUID_FAILED) { synth->sfont_id = sfont->id = sfont_id; synth->sfont = fluid_list_prepend(synth->sfont, sfont); /* prepend to list */ /* reset the presets for all channels */ fluid_synth_program_reset(synth); } FLUID_API_RETURN(sfont_id); } /** * Remove a SoundFont from the SoundFont stack without deleting it. * @param synth FluidSynth instance * @param sfont SoundFont to remove * @return #FLUID_OK if \c sfont successfully removed, #FLUID_FAILED otherwise * * SoundFont is not freed and is left as the responsibility of the caller. * * @note The SoundFont should only be freed after there are no presets * referencing it. This can only be ensured by the SoundFont loader and * therefore this function should not normally be used. */ int fluid_synth_remove_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont) { fluid_sfont_t *sfont_tmp; fluid_list_t *list; int ret = FLUID_FAILED; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(sfont != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); /* remove the SoundFont from the list */ for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont_tmp = fluid_list_get(list); if(sfont_tmp == sfont) { synth->sfont = fluid_list_remove(synth->sfont, sfont_tmp); ret = FLUID_OK; break; } } /* reset the presets for all channels */ fluid_synth_program_reset(synth); FLUID_API_RETURN(ret); } /** * Count number of loaded SoundFont files. * @param synth FluidSynth instance * @return Count of loaded SoundFont files. */ int fluid_synth_sfcount(fluid_synth_t *synth) { int count; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); count = fluid_list_size(synth->sfont); FLUID_API_RETURN(count); } /** * Get SoundFont by index. * @param synth FluidSynth instance * @param num SoundFont index on the stack (starting from 0 for top of stack). * @return SoundFont instance or NULL if invalid index * * @note Caller should be certain that SoundFont is not deleted (unloaded) for * the duration of use of the returned pointer. */ fluid_sfont_t * fluid_synth_get_sfont(fluid_synth_t *synth, unsigned int num) { fluid_sfont_t *sfont = NULL; fluid_list_t *list; fluid_return_val_if_fail(synth != NULL, NULL); fluid_synth_api_enter(synth); list = fluid_list_nth(synth->sfont, num); if(list) { sfont = fluid_list_get(list); } FLUID_API_RETURN(sfont); } /** * Get SoundFont by ID. * @param synth FluidSynth instance * @param id SoundFont ID * @return SoundFont instance or NULL if invalid ID * * @note Caller should be certain that SoundFont is not deleted (unloaded) for * the duration of use of the returned pointer. */ fluid_sfont_t * fluid_synth_get_sfont_by_id(fluid_synth_t *synth, int id) { fluid_sfont_t *sfont = NULL; fluid_list_t *list; fluid_return_val_if_fail(synth != NULL, NULL); fluid_synth_api_enter(synth); for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(fluid_sfont_get_id(sfont) == id) { break; } } FLUID_API_RETURN(list ? sfont : NULL); } /** * Get SoundFont by name. * @param synth FluidSynth instance * @param name Name of SoundFont * @return SoundFont instance or NULL if invalid name * @since 1.1.0 * * @note Caller should be certain that SoundFont is not deleted (unloaded) for * the duration of use of the returned pointer. */ fluid_sfont_t * fluid_synth_get_sfont_by_name(fluid_synth_t *synth, const char *name) { fluid_sfont_t *sfont = NULL; fluid_list_t *list; fluid_return_val_if_fail(synth != NULL, NULL); fluid_return_val_if_fail(name != NULL, NULL); fluid_synth_api_enter(synth); for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(FLUID_STRCMP(fluid_sfont_get_name(sfont), name) == 0) { break; } } FLUID_API_RETURN(list ? sfont : NULL); } /** * Get active preset on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @return Preset or NULL if no preset active on \c chan * * @note Should only be called from within synthesis thread, which includes * SoundFont loader preset noteon methods. Not thread safe otherwise. */ fluid_preset_t * fluid_synth_get_channel_preset(fluid_synth_t *synth, int chan) { fluid_preset_t *result; fluid_channel_t *channel; FLUID_API_ENTRY_CHAN(NULL); channel = synth->channel[chan]; result = channel->preset; fluid_synth_api_exit(synth); return result; } /** * Get list of currently playing voices. * @param synth FluidSynth instance * @param buf Array to store voices to (NULL terminated if not filled completely) * @param bufsize Count of indexes in buf * @param id Voice ID to search for or < 0 to return list of all playing voices * * @note Should only be called from within synthesis thread, which includes * SoundFont loader preset noteon methods. Voices are only guaranteed to remain * unchanged until next synthesis process iteration. */ void fluid_synth_get_voicelist(fluid_synth_t *synth, fluid_voice_t *buf[], int bufsize, int id) { int count = 0; int i; fluid_return_if_fail(synth != NULL); fluid_return_if_fail(buf != NULL); fluid_synth_api_enter(synth); for(i = 0; i < synth->polyphony && count < bufsize; i++) { fluid_voice_t *voice = synth->voice[i]; if(fluid_voice_is_playing(voice) && (id < 0 || (int)voice->id == id)) { buf[count++] = voice; } } if(count < bufsize) { buf[count] = NULL; } fluid_synth_api_exit(synth); } /** * Enable or disable reverb effect. * @param synth FluidSynth instance * @param on TRUE to enable reverb, FALSE to disable */ void fluid_synth_set_reverb_on(fluid_synth_t *synth, int on) { fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); synth->with_reverb = (on != 0); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_reverb_enabled, on != 0, 0.0f); fluid_synth_api_exit(synth); } /** * Activate a reverb preset. * @param synth FluidSynth instance * @param num Reverb preset number * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Currently private to libfluidsynth. */ int fluid_synth_set_reverb_preset(fluid_synth_t *synth, unsigned int num) { fluid_return_val_if_fail( num < FLUID_N_ELEMENTS(revmodel_preset), FLUID_FAILED ); fluid_synth_set_reverb(synth, revmodel_preset[num].roomsize, revmodel_preset[num].damp, revmodel_preset[num].width, revmodel_preset[num].level); return FLUID_OK; } /** * Set reverb parameters. * @param synth FluidSynth instance * @param roomsize Reverb room size value (0.0-1.0) * @param damping Reverb damping value (0.0-1.0) * @param width Reverb width value (0.0-100.0) * @param level Reverb level value (0.0-1.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Not realtime safe and therefore should not be called from synthesis * context at the risk of stalling audio output. */ int fluid_synth_set_reverb(fluid_synth_t *synth, double roomsize, double damping, double width, double level) { return fluid_synth_set_reverb_full(synth, FLUID_REVMODEL_SET_ALL, roomsize, damping, width, level); } /** * Set reverb roomsize. See fluid_synth_set_reverb() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize) { return fluid_synth_set_reverb_full(synth, FLUID_REVMODEL_SET_ROOMSIZE, roomsize, 0, 0, 0); } /** * Set reverb damping. See fluid_synth_set_reverb() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_damp(fluid_synth_t *synth, double damping) { return fluid_synth_set_reverb_full(synth, FLUID_REVMODEL_SET_DAMPING, 0, damping, 0, 0); } /** * Set reverb width. See fluid_synth_set_reverb() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_width(fluid_synth_t *synth, double width) { return fluid_synth_set_reverb_full(synth, FLUID_REVMODEL_SET_WIDTH, 0, 0, width, 0); } /** * Set reverb level. See fluid_synth_set_reverb() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_level(fluid_synth_t *synth, double level) { return fluid_synth_set_reverb_full(synth, FLUID_REVMODEL_SET_LEVEL, 0, 0, 0, level); } /** * Set one or more reverb parameters. * @param synth FluidSynth instance * @param set Flags indicating which parameters should be set (#fluid_revmodel_set_t) * @param roomsize Reverb room size value (0.0-1.2) * @param damping Reverb damping value (0.0-1.0) * @param width Reverb width value (0.0-100.0) * @param level Reverb level value (0.0-1.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Not realtime safe and therefore should not be called from synthesis * context at the risk of stalling audio output. */ int fluid_synth_set_reverb_full(fluid_synth_t *synth, int set, double roomsize, double damping, double width, double level) { int ret; fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); /* if non of the flags is set, fail */ fluid_return_val_if_fail(set & FLUID_REVMODEL_SET_ALL, FLUID_FAILED); /* Synth shadow values are set here so that they will be returned if querried */ fluid_synth_api_enter(synth); if(set & FLUID_REVMODEL_SET_ROOMSIZE) { synth->reverb_roomsize = roomsize; } if(set & FLUID_REVMODEL_SET_DAMPING) { synth->reverb_damping = damping; } if(set & FLUID_REVMODEL_SET_WIDTH) { synth->reverb_width = width; } if(set & FLUID_REVMODEL_SET_LEVEL) { synth->reverb_level = level; } param[0].i = set; param[1].real = roomsize; param[2].real = damping; param[3].real = width; param[4].real = level; /* finally enqueue an rvoice event to the mixer to actual update reverb */ ret = fluid_rvoice_eventhandler_push(synth->eventhandler, fluid_rvoice_mixer_set_reverb_params, synth->eventhandler->mixer, param); FLUID_API_RETURN(ret); } /** * Get reverb room size. * @param synth FluidSynth instance * @return Reverb room size (0.0-1.2) */ double fluid_synth_get_reverb_roomsize(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->reverb_roomsize; FLUID_API_RETURN(result); } /** * Get reverb damping. * @param synth FluidSynth instance * @return Reverb damping value (0.0-1.0) */ double fluid_synth_get_reverb_damp(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->reverb_damping; FLUID_API_RETURN(result); } /** * Get reverb level. * @param synth FluidSynth instance * @return Reverb level value (0.0-1.0) */ double fluid_synth_get_reverb_level(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->reverb_level; FLUID_API_RETURN(result); } /** * Get reverb width. * @param synth FluidSynth instance * @return Reverb width value (0.0-100.0) */ double fluid_synth_get_reverb_width(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->reverb_width; FLUID_API_RETURN(result); } /** * Enable or disable chorus effect. * @param synth FluidSynth instance * @param on TRUE to enable chorus, FALSE to disable */ void fluid_synth_set_chorus_on(fluid_synth_t *synth, int on) { fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); synth->with_chorus = (on != 0); fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_chorus_enabled, on != 0, 0.0f); fluid_synth_api_exit(synth); } /** * Set chorus parameters. It should be turned on with fluid_synth_set_chorus_on(). * Keep in mind, that the needed CPU time is proportional to 'nr'. * @param synth FluidSynth instance * @param nr Chorus voice count (0-99, CPU time consumption proportional to * this value) * @param level Chorus level (0.0-10.0) * @param speed Chorus speed in Hz (0.1-5.0) * @param depth_ms Chorus depth (max value depends on synth sample-rate, * 0.0-21.0 is safe for sample-rate values up to 96KHz) * @param type Chorus waveform type (#fluid_chorus_mod) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus(fluid_synth_t *synth, int nr, double level, double speed, double depth_ms, int type) { return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_ALL, nr, level, speed, depth_ms, type); } /** * Set the chorus voice count. See fluid_synth_set_chorus() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_nr(fluid_synth_t *synth, int nr) { return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_NR, nr, 0, 0, 0, 0); } /** * Set the chorus level. See fluid_synth_set_chorus() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_level(fluid_synth_t *synth, double level) { return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_LEVEL, 0, level, 0, 0, 0); } /** * Set the chorus speed. See fluid_synth_set_chorus() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_speed(fluid_synth_t *synth, double speed) { return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_SPEED, 0, 0, speed, 0, 0); } /** * Set the chorus depth. See fluid_synth_set_chorus() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_depth(fluid_synth_t *synth, double depth_ms) { return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_DEPTH, 0, 0, 0, depth_ms, 0); } /** * Set the chorus type. See fluid_synth_set_chorus() for further info. * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_type(fluid_synth_t *synth, int type) { return fluid_synth_set_chorus_full(synth, FLUID_CHORUS_SET_TYPE, 0, 0, 0, 0, type); } int fluid_synth_set_chorus_full(fluid_synth_t *synth, int set, int nr, double level, double speed, double depth_ms, int type) { int ret; fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); /* if non of the flags is set, fail */ fluid_return_val_if_fail(set & FLUID_CHORUS_SET_ALL, FLUID_FAILED); /* Synth shadow values are set here so that they will be returned if queried */ fluid_synth_api_enter(synth); if(set & FLUID_CHORUS_SET_NR) { synth->chorus_nr = nr; } if(set & FLUID_CHORUS_SET_LEVEL) { synth->chorus_level = level; } if(set & FLUID_CHORUS_SET_SPEED) { synth->chorus_speed = speed; } if(set & FLUID_CHORUS_SET_DEPTH) { synth->chorus_depth = depth_ms; } if(set & FLUID_CHORUS_SET_TYPE) { synth->chorus_type = type; } param[0].i = set; param[1].i = nr; param[2].real = level; param[3].real = speed; param[4].real = depth_ms; param[5].i = type; ret = fluid_rvoice_eventhandler_push(synth->eventhandler, fluid_rvoice_mixer_set_chorus_params, synth->eventhandler->mixer, param); FLUID_API_RETURN(ret); } /** * Get chorus voice number (delay line count) value. * @param synth FluidSynth instance * @return Chorus voice count */ int fluid_synth_get_chorus_nr(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->chorus_nr; FLUID_API_RETURN(result); } /** * Get chorus level. * @param synth FluidSynth instance * @return Chorus level value */ double fluid_synth_get_chorus_level(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->chorus_level; FLUID_API_RETURN(result); } /** * Get chorus speed in Hz. * @param synth FluidSynth instance * @return Chorus speed in Hz */ double fluid_synth_get_chorus_speed(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->chorus_speed; FLUID_API_RETURN(result); } /** * Get chorus depth. * @param synth FluidSynth instance * @return Chorus depth */ double fluid_synth_get_chorus_depth(fluid_synth_t *synth) { double result; fluid_return_val_if_fail(synth != NULL, 0.0); fluid_synth_api_enter(synth); result = synth->chorus_depth; FLUID_API_RETURN(result); } /** * Get chorus waveform type. * @param synth FluidSynth instance * @return Chorus waveform type (#fluid_chorus_mod) */ int fluid_synth_get_chorus_type(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->chorus_type; FLUID_API_RETURN(result); } /* * If the same note is hit twice on the same channel, then the older * voice process is advanced to the release stage. Using a mechanical * MIDI controller, the only way this can happen is when the sustain * pedal is held. In this case the behaviour implemented here is * natural for many instruments. Note: One noteon event can trigger * several voice processes, for example a stereo sample. Don't * release those... */ void fluid_synth_release_voice_on_same_note_LOCAL(fluid_synth_t *synth, int chan, int key) { int i; fluid_voice_t *voice; /* storeid is a parameter for fluid_voice_init() */ synth->storeid = synth->noteid++; /* for "monophonic playing" key is the previous sustained note if it exists (0 to 127) or INVALID_NOTE otherwise */ if(key == INVALID_NOTE) { return; } for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_playing(voice) && (fluid_voice_get_channel(voice) == chan) && (fluid_voice_get_key(voice) == key) && (fluid_voice_get_id(voice) != synth->noteid)) { /* Id of voices that was sustained by sostenuto */ if(fluid_voice_is_sostenuto(voice)) { synth->storeid = fluid_voice_get_id(voice); } /* Force the voice into release stage (pedaling is ignored) */ fluid_voice_release(voice); } } } /** * Set synthesis interpolation method on one or all MIDI channels. * @param synth FluidSynth instance * @param chan MIDI channel to set interpolation method on or -1 for all channels * @param interp_method Interpolation method (#fluid_interp) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_interp_method(fluid_synth_t *synth, int chan, int interp_method) { int i; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); if(chan < -1 || chan >= synth->midi_channels) { FLUID_API_RETURN(FLUID_FAILED); } if(synth->channel[0] == NULL) { FLUID_LOG(FLUID_ERR, "Channels don't exist (yet)!"); FLUID_API_RETURN(FLUID_FAILED); } for(i = 0; i < synth->midi_channels; i++) { if(chan < 0 || fluid_channel_get_num(synth->channel[i]) == chan) { fluid_channel_set_interp_method(synth->channel[i], interp_method); } } FLUID_API_RETURN(FLUID_OK); }; /** * Get the total count of MIDI channels. * @param synth FluidSynth instance * @return Count of MIDI channels */ int fluid_synth_count_midi_channels(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->midi_channels; FLUID_API_RETURN(result); } /** * Get the total count of audio channels. * @param synth FluidSynth instance * @return Count of audio channel stereo pairs (1 = 2 channels, 2 = 4, etc) */ int fluid_synth_count_audio_channels(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->audio_channels; FLUID_API_RETURN(result); } /** * Get the total number of allocated audio channels. Usually identical to the * number of audio channels. Can be employed by LADSPA effects subsystem. * * @param synth FluidSynth instance * @return Count of audio group stereo pairs (1 = 2 channels, 2 = 4, etc) */ int fluid_synth_count_audio_groups(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->audio_groups; FLUID_API_RETURN(result); } /** * Get the total number of allocated effects channels. * @param synth FluidSynth instance * @return Count of allocated effects channels */ int fluid_synth_count_effects_channels(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->effects_channels; FLUID_API_RETURN(result); } /** * Get the total number of allocated effects units. * @param synth FluidSynth instance * @return Count of allocated effects units */ int fluid_synth_count_effects_groups(fluid_synth_t *synth) { int result; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); result = synth->effects_groups; FLUID_API_RETURN(result); } /** * Get the synth CPU load value. * @param synth FluidSynth instance * @return Estimated CPU load value in percent (0-100) */ double fluid_synth_get_cpu_load(fluid_synth_t *synth) { fluid_return_val_if_fail(synth != NULL, 0); return fluid_atomic_float_get(&synth->cpu_load); } /* Get tuning for a given bank:program */ static fluid_tuning_t * fluid_synth_get_tuning(fluid_synth_t *synth, int bank, int prog) { if((synth->tuning == NULL) || (synth->tuning[bank] == NULL) || (synth->tuning[bank][prog] == NULL)) { return NULL; } return synth->tuning[bank][prog]; } /* Replace tuning on a given bank:program (need not already exist). * Synth mutex should already be locked by caller. */ static int fluid_synth_replace_tuning_LOCK(fluid_synth_t *synth, fluid_tuning_t *tuning, int bank, int prog, int apply) { fluid_tuning_t *old_tuning; if(synth->tuning == NULL) { synth->tuning = FLUID_ARRAY(fluid_tuning_t **, 128); if(synth->tuning == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } FLUID_MEMSET(synth->tuning, 0, 128 * sizeof(fluid_tuning_t **)); } if(synth->tuning[bank] == NULL) { synth->tuning[bank] = FLUID_ARRAY(fluid_tuning_t *, 128); if(synth->tuning[bank] == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return FLUID_FAILED; } FLUID_MEMSET(synth->tuning[bank], 0, 128 * sizeof(fluid_tuning_t *)); } old_tuning = synth->tuning[bank][prog]; synth->tuning[bank][prog] = tuning; if(old_tuning) { if(!fluid_tuning_unref(old_tuning, 1)) /* -- unref old tuning */ { /* Replace old tuning if present */ fluid_synth_replace_tuning_LOCAL(synth, old_tuning, tuning, apply, FALSE); } } return FLUID_OK; } /* Replace a tuning with a new one in all MIDI channels. new_tuning can be * NULL, in which case channels are reset to default equal tempered scale. */ static void fluid_synth_replace_tuning_LOCAL(fluid_synth_t *synth, fluid_tuning_t *old_tuning, fluid_tuning_t *new_tuning, int apply, int unref_new) { fluid_channel_t *channel; int old_tuning_unref = 0; int i; for(i = 0; i < synth->midi_channels; i++) { channel = synth->channel[i]; if(fluid_channel_get_tuning(channel) == old_tuning) { old_tuning_unref++; if(new_tuning) { fluid_tuning_ref(new_tuning); /* ++ ref new tuning for channel */ } fluid_channel_set_tuning(channel, new_tuning); if(apply) { fluid_synth_update_voice_tuning_LOCAL(synth, channel); } } } /* Send unref old tuning event if any unrefs */ if(old_tuning && old_tuning_unref) { fluid_tuning_unref(old_tuning, old_tuning_unref); } if(!unref_new || !new_tuning) { return; } fluid_tuning_unref(new_tuning, 1); } /* Update voice tunings in realtime */ static void fluid_synth_update_voice_tuning_LOCAL(fluid_synth_t *synth, fluid_channel_t *channel) { fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_on(voice) && (voice->channel == channel)) { fluid_voice_calculate_gen_pitch(voice); fluid_voice_update_param(voice, GEN_PITCH); } } } /** * Set the tuning of the entire MIDI note scale. * @param synth FluidSynth instance * @param bank Tuning bank number (0-127), not related to MIDI instrument bank * @param prog Tuning preset number (0-127), not related to MIDI instrument program * @param name Label name for this tuning * @param pitch Array of pitch values (length of 128, each value is number of * cents, for example normally note 0 is 0.0, 1 is 100.0, 60 is 6000.0, etc). * Pass NULL to create a equal tempered (normal) scale. * @param apply TRUE to apply new tuning in realtime to existing notes which * are using the replaced tuning (if any), FALSE otherwise * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ int fluid_synth_activate_key_tuning(fluid_synth_t *synth, int bank, int prog, const char *name, const double *pitch, int apply) { fluid_tuning_t *tuning; int retval = FLUID_OK; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); tuning = new_fluid_tuning(name, bank, prog); if(tuning) { if(pitch) { fluid_tuning_set_all(tuning, pitch); } retval = fluid_synth_replace_tuning_LOCK(synth, tuning, bank, prog, apply); if(retval == FLUID_FAILED) { fluid_tuning_unref(tuning, 1); } } else { retval = FLUID_FAILED; } FLUID_API_RETURN(retval); } /** * Activate an octave tuning on every octave in the MIDI note scale. * @param synth FluidSynth instance * @param bank Tuning bank number (0-127), not related to MIDI instrument bank * @param prog Tuning preset number (0-127), not related to MIDI instrument program * @param name Label name for this tuning * @param pitch Array of pitch values (length of 12 for each note of an octave * starting at note C, values are number of offset cents to add to the normal * tuning amount) * @param apply TRUE to apply new tuning in realtime to existing notes which * are using the replaced tuning (if any), FALSE otherwise * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ int fluid_synth_activate_octave_tuning(fluid_synth_t *synth, int bank, int prog, const char *name, const double *pitch, int apply) { fluid_tuning_t *tuning; int retval = FLUID_OK; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(pitch != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); tuning = new_fluid_tuning(name, bank, prog); if(tuning) { fluid_tuning_set_octave(tuning, pitch); retval = fluid_synth_replace_tuning_LOCK(synth, tuning, bank, prog, apply); if(retval == FLUID_FAILED) { fluid_tuning_unref(tuning, 1); } } else { retval = FLUID_FAILED; } FLUID_API_RETURN(retval); } /** * Set tuning values for one or more MIDI notes for an existing tuning. * @param synth FluidSynth instance * @param bank Tuning bank number (0-127), not related to MIDI instrument bank * @param prog Tuning preset number (0-127), not related to MIDI instrument program * @param len Number of MIDI notes to assign * @param key Array of MIDI key numbers (length of 'len', values 0-127) * @param pitch Array of pitch values (length of 'len', values are number of * cents from MIDI note 0) * @param apply TRUE to apply tuning change in realtime to existing notes using * the specified tuning, FALSE otherwise * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Prior to version 1.1.0 it was an error to specify a tuning that didn't * already exist. Starting with 1.1.0, the default equal tempered scale will be * used as a basis, if no tuning exists for the given bank and prog. */ int fluid_synth_tune_notes(fluid_synth_t *synth, int bank, int prog, int len, const int *key, const double *pitch, int apply) { fluid_tuning_t *old_tuning, *new_tuning; int retval = FLUID_OK; int i; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); fluid_return_val_if_fail(len > 0, FLUID_FAILED); fluid_return_val_if_fail(key != NULL, FLUID_FAILED); fluid_return_val_if_fail(pitch != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); old_tuning = fluid_synth_get_tuning(synth, bank, prog); if(old_tuning) { new_tuning = fluid_tuning_duplicate(old_tuning); } else { new_tuning = new_fluid_tuning("Unnamed", bank, prog); } if(new_tuning) { for(i = 0; i < len; i++) { fluid_tuning_set_pitch(new_tuning, key[i], pitch[i]); } retval = fluid_synth_replace_tuning_LOCK(synth, new_tuning, bank, prog, apply); if(retval == FLUID_FAILED) { fluid_tuning_unref(new_tuning, 1); } } else { retval = FLUID_FAILED; } FLUID_API_RETURN(retval); } /** * Activate a tuning scale on a MIDI channel. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param bank Tuning bank number (0-127), not related to MIDI instrument bank * @param prog Tuning preset number (0-127), not related to MIDI instrument program * @param apply TRUE to apply tuning change to active notes, FALSE otherwise * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 * * @note A default equal tempered scale will be created, if no tuning exists * on the given bank and prog. */ int fluid_synth_activate_tuning(fluid_synth_t *synth, int chan, int bank, int prog, int apply) { fluid_tuning_t *tuning; int retval = FLUID_OK; //fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); //fluid_return_val_if_fail (chan >= 0 && chan < synth->midi_channels, FLUID_FAILED); fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); tuning = fluid_synth_get_tuning(synth, bank, prog); /* If no tuning exists, create a new default tuning. We do this, so that * it can be replaced later, if any changes are made. */ if(!tuning) { tuning = new_fluid_tuning("Unnamed", bank, prog); if(tuning) { fluid_synth_replace_tuning_LOCK(synth, tuning, bank, prog, FALSE); } } if(tuning) { fluid_tuning_ref(tuning); /* ++ ref for outside of lock */ } if(!tuning) { FLUID_API_RETURN(FLUID_FAILED); } fluid_tuning_ref(tuning); /* ++ ref new tuning for following function */ retval = fluid_synth_set_tuning_LOCAL(synth, chan, tuning, apply); fluid_tuning_unref(tuning, 1); /* -- unref for outside of lock */ FLUID_API_RETURN(retval); } /* Local synthesis thread set tuning function (takes over tuning reference) */ static int fluid_synth_set_tuning_LOCAL(fluid_synth_t *synth, int chan, fluid_tuning_t *tuning, int apply) { fluid_tuning_t *old_tuning; fluid_channel_t *channel; channel = synth->channel[chan]; old_tuning = fluid_channel_get_tuning(channel); fluid_channel_set_tuning(channel, tuning); /* !! Takes over callers reference */ if(apply) { fluid_synth_update_voice_tuning_LOCAL(synth, channel); } /* Send unref old tuning event */ if(old_tuning) { fluid_tuning_unref(old_tuning, 1); } return FLUID_OK; } /** * Clear tuning scale on a MIDI channel (use default equal tempered scale). * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param apply TRUE to apply tuning change to active notes, FALSE otherwise * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.0 */ int fluid_synth_deactivate_tuning(fluid_synth_t *synth, int chan, int apply) { int retval = FLUID_OK; FLUID_API_ENTRY_CHAN(FLUID_FAILED); retval = fluid_synth_set_tuning_LOCAL(synth, chan, NULL, apply); FLUID_API_RETURN(retval); } /** * Start tuning iteration. * @param synth FluidSynth instance */ void fluid_synth_tuning_iteration_start(fluid_synth_t *synth) { fluid_return_if_fail(synth != NULL); fluid_synth_api_enter(synth); fluid_private_set(synth->tuning_iter, FLUID_INT_TO_POINTER(0)); fluid_synth_api_exit(synth); } /** * Advance to next tuning. * @param synth FluidSynth instance * @param bank Location to store MIDI bank number of next tuning scale * @param prog Location to store MIDI program number of next tuning scale * @return 1 if tuning iteration advanced, 0 if no more tunings */ int fluid_synth_tuning_iteration_next(fluid_synth_t *synth, int *bank, int *prog) { void *pval; int b = 0, p = 0; fluid_return_val_if_fail(synth != NULL, 0); fluid_return_val_if_fail(bank != NULL, 0); fluid_return_val_if_fail(prog != NULL, 0); fluid_synth_api_enter(synth); /* Current tuning iteration stored as: bank << 8 | program */ pval = fluid_private_get(synth->tuning_iter); p = FLUID_POINTER_TO_INT(pval); b = (p >> 8) & 0xFF; p &= 0xFF; if(!synth->tuning) { FLUID_API_RETURN(0); } for(; b < 128; b++, p = 0) { if(synth->tuning[b] == NULL) { continue; } for(; p < 128; p++) { if(synth->tuning[b][p] == NULL) { continue; } *bank = b; *prog = p; if(p < 127) { fluid_private_set(synth->tuning_iter, FLUID_INT_TO_POINTER(b << 8 | (p + 1))); } else { fluid_private_set(synth->tuning_iter, FLUID_INT_TO_POINTER((b + 1) << 8)); } FLUID_API_RETURN(1); } } FLUID_API_RETURN(0); } /** * Get the entire note tuning for a given MIDI bank and program. * @param synth FluidSynth instance * @param bank MIDI bank number of tuning * @param prog MIDI program number of tuning * @param name Location to store tuning name or NULL to ignore * @param len Maximum number of chars to store to 'name' (including NULL byte) * @param pitch Array to store tuning scale to or NULL to ignore (len of 128) * @return #FLUID_OK if matching tuning was found, #FLUID_FAILED otherwise */ int fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int prog, char *name, int len, double *pitch) { fluid_tuning_t *tuning; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); tuning = fluid_synth_get_tuning(synth, bank, prog); if(tuning) { if(name) { FLUID_SNPRINTF(name, len - 1, "%s", fluid_tuning_get_name(tuning)); name[len - 1] = 0; /* make sure the string is null terminated */ } if(pitch) { FLUID_MEMCPY(pitch, fluid_tuning_get_all(tuning), 128 * sizeof(double)); } } FLUID_API_RETURN(tuning ? FLUID_OK : FLUID_FAILED); } /** * Get settings assigned to a synth. * @param synth FluidSynth instance * @return FluidSynth settings which are assigned to the synth */ fluid_settings_t * fluid_synth_get_settings(fluid_synth_t *synth) { fluid_return_val_if_fail(synth != NULL, NULL); return synth->settings; } /** * Apply an offset to a SoundFont generator on a MIDI channel. * * This function allows to set an offset for the specified destination generator in real-time. * The offset will be applied immediately to all voices that are currently and subsequently playing * on the given MIDI channel. This functionality works equivalent to using NRPN MIDI messages to * manipulate synthesis parameters. See SoundFont spec, paragraph 8.1.3, for details on SoundFont * generator parameters and valid ranges, as well as paragraph 9.6 for details on NRPN messages. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param param SoundFont generator ID (#fluid_gen_type) * @param value Offset value (in native units of the generator) to assign to the MIDI channel * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_gen(fluid_synth_t *synth, int chan, int param, float value) { fluid_return_val_if_fail(param >= 0 && param < GEN_LAST, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); fluid_synth_set_gen_LOCAL(synth, chan, param, value); FLUID_API_RETURN(FLUID_OK); } /* Synthesis thread local set gen function */ static void fluid_synth_set_gen_LOCAL(fluid_synth_t *synth, int chan, int param, float value) { fluid_voice_t *voice; int i; fluid_channel_set_gen(synth->channel[chan], param, value); for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_get_channel(voice) == chan) { fluid_voice_set_param(voice, param, value); } } } /** * Retrieve the generator NRPN offset assigned to a MIDI channel. * * The value returned is in native units of the generator. By default, the offset is zero. * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param param SoundFont generator ID (#fluid_gen_type) * @return Current NRPN generator offset value assigned to the MIDI channel */ float fluid_synth_get_gen(fluid_synth_t *synth, int chan, int param) { float result; fluid_return_val_if_fail(param >= 0 && param < GEN_LAST, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); result = fluid_channel_get_gen(synth->channel[chan], param); FLUID_API_RETURN(result); } /** * Handle MIDI event from MIDI router, used as a callback function. * @param data FluidSynth instance * @param event MIDI event to handle * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_handle_midi_event(void *data, fluid_midi_event_t *event) { fluid_synth_t *synth = (fluid_synth_t *) data; int type = fluid_midi_event_get_type(event); int chan = fluid_midi_event_get_channel(event); switch(type) { case NOTE_ON: return fluid_synth_noteon(synth, chan, fluid_midi_event_get_key(event), fluid_midi_event_get_velocity(event)); case NOTE_OFF: return fluid_synth_noteoff(synth, chan, fluid_midi_event_get_key(event)); case CONTROL_CHANGE: return fluid_synth_cc(synth, chan, fluid_midi_event_get_control(event), fluid_midi_event_get_value(event)); case PROGRAM_CHANGE: return fluid_synth_program_change(synth, chan, fluid_midi_event_get_program(event)); case CHANNEL_PRESSURE: return fluid_synth_channel_pressure(synth, chan, fluid_midi_event_get_program(event)); case KEY_PRESSURE: return fluid_synth_key_pressure(synth, chan, fluid_midi_event_get_key(event), fluid_midi_event_get_value(event)); case PITCH_BEND: return fluid_synth_pitch_bend(synth, chan, fluid_midi_event_get_pitch(event)); case MIDI_SYSTEM_RESET: return fluid_synth_system_reset(synth); case MIDI_SYSEX: return fluid_synth_sysex(synth, event->paramptr, event->param1, NULL, NULL, NULL, FALSE); case MIDI_TEXT: case MIDI_LYRIC: case MIDI_SET_TEMPO: return FLUID_OK; } return FLUID_FAILED; } /** * Create and start voices using a preset and a MIDI note on event. * @param synth FluidSynth instance * @param id Voice group ID to use (can be used with fluid_synth_stop()). * @param preset Preset to synthesize * @param audio_chan Unused currently, set to 0 * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param key MIDI note number (0-127) * @param vel MIDI velocity number (1-127) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note Should only be called from within synthesis thread, which includes * SoundFont loader preset noteon method. */ int fluid_synth_start(fluid_synth_t *synth, unsigned int id, fluid_preset_t *preset, int audio_chan, int chan, int key, int vel) { int result; fluid_return_val_if_fail(preset != NULL, FLUID_FAILED); fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); fluid_return_val_if_fail(vel >= 1 && vel <= 127, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); synth->storeid = id; result = fluid_preset_noteon(preset, synth, chan, key, vel); FLUID_API_RETURN(result); } /** * Stop notes for a given note event voice ID. * @param synth FluidSynth instance * @param id Voice note event ID * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * @note In FluidSynth versions prior to 1.1.0 #FLUID_FAILED would be returned * if no matching voice note event ID was found. Versions after 1.1.0 only * return #FLUID_FAILED if an error occurs. */ int fluid_synth_stop(fluid_synth_t *synth, unsigned int id) { int result; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); fluid_synth_stop_LOCAL(synth, id); result = FLUID_OK; FLUID_API_RETURN(result); } /* Local synthesis thread variant of fluid_synth_stop */ static void fluid_synth_stop_LOCAL(fluid_synth_t *synth, unsigned int id) { fluid_voice_t *voice; int i; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_on(voice) && (fluid_voice_get_id(voice) == id)) { fluid_voice_noteoff(voice); } } } /** * Offset the bank numbers of a loaded SoundFont, i.e.\ subtract * \c offset from any bank number when assigning instruments. * * @param synth FluidSynth instance * @param sfont_id ID of a loaded SoundFont * @param offset Bank offset value to apply to all instruments * @return #FLUID_OK if the offset was set successfully, #FLUID_FAILED otherwise */ int fluid_synth_set_bank_offset(fluid_synth_t *synth, int sfont_id, int offset) { fluid_sfont_t *sfont; fluid_list_t *list; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(fluid_sfont_get_id(sfont) == sfont_id) { sfont->bankofs = offset; break; } } if(!list) { FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", sfont_id); FLUID_API_RETURN(FLUID_FAILED); } FLUID_API_RETURN(FLUID_OK); } /** * Get bank offset of a loaded SoundFont. * @param synth FluidSynth instance * @param sfont_id ID of a loaded SoundFont * @return SoundFont bank offset value */ int fluid_synth_get_bank_offset(fluid_synth_t *synth, int sfont_id) { fluid_sfont_t *sfont; fluid_list_t *list; int offset = 0; fluid_return_val_if_fail(synth != NULL, 0); fluid_synth_api_enter(synth); for(list = synth->sfont; list; list = fluid_list_next(list)) { sfont = fluid_list_get(list); if(fluid_sfont_get_id(sfont) == sfont_id) { offset = sfont->bankofs; break; } } if(!list) { FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", sfont_id); FLUID_API_RETURN(0); } FLUID_API_RETURN(offset); } void fluid_synth_api_enter(fluid_synth_t *synth) { if(synth->use_mutex) { fluid_rec_mutex_lock(synth->mutex); } if(!synth->public_api_count) { fluid_synth_check_finished_voices(synth); } synth->public_api_count++; } void fluid_synth_api_exit(fluid_synth_t *synth) { synth->public_api_count--; if(!synth->public_api_count) { fluid_rvoice_eventhandler_flush(synth->eventhandler); } if(synth->use_mutex) { fluid_rec_mutex_unlock(synth->mutex); } } /** * Set midi channel type * @param synth FluidSynth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param type MIDI channel type (#fluid_midi_channel_type) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * @since 1.1.4 */ int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type) { fluid_return_val_if_fail((type >= CHANNEL_TYPE_MELODIC) && (type <= CHANNEL_TYPE_DRUM), FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); synth->channel[chan]->channel_type = type; FLUID_API_RETURN(FLUID_OK); } /** * Return the LADSPA effects instance used by FluidSynth * * @param synth FluidSynth instance * @return pointer to LADSPA fx or NULL if compiled without LADSPA support or LADSPA is not active */ fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth) { fluid_return_val_if_fail(synth != NULL, NULL); return synth->ladspa_fx; } /** * Configure a general-purpose IIR biquad filter. * * This is an optional, additional filter that operates independently from the default low-pass filter required by the Soundfont2 standard. * By default this filter is off (#FLUID_IIR_DISABLED). * * @param synth FluidSynth instance * @param type Type of the IIR filter to use (see #fluid_iir_filter_type) * @param flags Additional flags to customize this filter or zero to stay with the default (see #fluid_iir_filter_flags) * * @return #FLUID_OK if the settings have been successfully applied, otherwise #FLUID_FAILED */ int fluid_synth_set_custom_filter(fluid_synth_t *synth, int type, int flags) { int i; fluid_voice_t *voice; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(type >= FLUID_IIR_DISABLED && type < FLUID_IIR_LAST, FLUID_FAILED); fluid_synth_api_enter(synth); synth->custom_filter_type = type; synth->custom_filter_flags = flags; for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; fluid_voice_set_custom_filter(voice, type, flags); } FLUID_API_RETURN(FLUID_OK); } /** * Set the important channels for voice overflow priority calculation. * * @param synth FluidSynth instance * @param channels comma-separated list of channel numbers * @return #FLUID_OK on success, otherwise #FLUID_FAILED */ static int fluid_synth_set_important_channels(fluid_synth_t *synth, const char *channels) { int i; int retval = FLUID_FAILED; int *values = NULL; int num_values; fluid_overflow_prio_t *scores; fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); scores = &synth->overflow; if(scores->num_important_channels < synth->midi_channels) { scores->important_channels = FLUID_REALLOC(scores->important_channels, sizeof(*scores->important_channels) * synth->midi_channels); if(scores->important_channels == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto exit; } scores->num_important_channels = synth->midi_channels; } FLUID_MEMSET(scores->important_channels, FALSE, sizeof(*scores->important_channels) * scores->num_important_channels); if(channels != NULL) { values = FLUID_ARRAY(int, synth->midi_channels); if(values == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto exit; } /* Every channel given in the comma-separated list of channel numbers * is set to TRUE, i.e. flagging it as "important". Channel numbers are * 1-based. */ num_values = fluid_settings_split_csv(channels, values, synth->midi_channels); for(i = 0; i < num_values; i++) { if(values[i] > 0 && values[i] <= synth->midi_channels) { scores->important_channels[values[i] - 1] = TRUE; } } } retval = FLUID_OK; exit: FLUID_FREE(values); return retval; } /* * Handler for synth.overflow.important-channels setting. */ static void fluid_synth_handle_important_channels(void *data, const char *name, const char *value) { fluid_synth_t *synth = (fluid_synth_t *)data; fluid_synth_api_enter(synth); fluid_synth_set_important_channels(synth, value); fluid_synth_api_exit(synth); } /** API legato mode *********************************************************/ /** * Sets the legato mode of a channel. * * @param synth the synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param legatomode The legato mode as indicated by #fluid_channel_legato_mode. * * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a legatomode is invalid. */ int fluid_synth_set_legato_mode(fluid_synth_t *synth, int chan, int legatomode) { /* checks parameters first */ fluid_return_val_if_fail(legatomode >= 0, FLUID_FAILED); fluid_return_val_if_fail(legatomode < FLUID_CHANNEL_LEGATO_MODE_LAST, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ synth->channel[chan]->legatomode = legatomode; /**/ FLUID_API_RETURN(FLUID_OK); } /** * Gets the legato mode of a channel. * * @param synth the synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param legatomode The legato mode as indicated by #fluid_channel_legato_mode. * * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a legatomode is NULL. */ int fluid_synth_get_legato_mode(fluid_synth_t *synth, int chan, int *legatomode) { /* checks parameters first */ fluid_return_val_if_fail(legatomode != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ * legatomode = synth->channel[chan]->legatomode; /**/ FLUID_API_RETURN(FLUID_OK); } /** API portamento mode *********************************************************/ /** * Sets the portamento mode of a channel. * * @param synth the synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param portamentomode The portamento mode as indicated by #fluid_channel_portamento_mode. * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a portamentomode is invalid. */ int fluid_synth_set_portamento_mode(fluid_synth_t *synth, int chan, int portamentomode) { /* checks parameters first */ fluid_return_val_if_fail(portamentomode >= 0, FLUID_FAILED); fluid_return_val_if_fail(portamentomode < FLUID_CHANNEL_PORTAMENTO_MODE_LAST, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ synth->channel[chan]->portamentomode = portamentomode; /**/ FLUID_API_RETURN(FLUID_OK); } /** * Gets the portamento mode of a channel. * * @param synth the synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param portamentomode Pointer to the portamento mode as indicated by #fluid_channel_portamento_mode. * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a portamentomode is NULL. */ int fluid_synth_get_portamento_mode(fluid_synth_t *synth, int chan, int *portamentomode) { /* checks parameters first */ fluid_return_val_if_fail(portamentomode != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ * portamentomode = synth->channel[chan]->portamentomode; /**/ FLUID_API_RETURN(FLUID_OK); } /** API breath mode *********************************************************/ /** * Sets the breath mode of a channel. * * @param synth the synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param breathmode The breath mode as indicated by #fluid_channel_breath_flags. * * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. */ int fluid_synth_set_breath_mode(fluid_synth_t *synth, int chan, int breathmode) { /* checks parameters first */ FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ fluid_channel_set_breath_info(synth->channel[chan], breathmode); /**/ FLUID_API_RETURN(FLUID_OK); } /** * Gets the breath mode of a channel. * * @param synth the synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param breathmode Pointer to the returned breath mode as indicated by #fluid_channel_breath_flags. * * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a breathmode is NULL. */ int fluid_synth_get_breath_mode(fluid_synth_t *synth, int chan, int *breathmode) { /* checks parameters first */ fluid_return_val_if_fail(breathmode != NULL, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ * breathmode = fluid_channel_get_breath_info(synth->channel[chan]); /**/ FLUID_API_RETURN(FLUID_OK); } /** API Poly/mono mode ******************************************************/ /* * Resets a basic channel group of MIDI channels. * @param synth the synth instance. * @param chan the beginning channel of the group. * @param nbr_chan the number of channel in the group. */ static void fluid_synth_reset_basic_channel_LOCAL(fluid_synth_t *synth, int chan, int nbr_chan) { int i; for(i = chan; i < chan + nbr_chan; i++) { fluid_channel_reset_basic_channel_info(synth->channel[i]); synth->channel[i]->mode_val = 0; } } /** * Disables and unassigns all channels from a basic channel group. * * @param synth The synth instance. * @param chan The basic channel of the group to reset or -1 to reset all channels. * @note By default (i.e. on creation after new_fluid_synth() and after fluid_synth_system_reset()) * a synth instance has one basic channel at channel 0 in mode #FLUID_CHANNEL_MODE_OMNION_POLY. * All other channels belong to this basic channel group. Make sure to call this function before * setting any custom basic channel setup. * * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a chan isn't a basic channel. */ int fluid_synth_reset_basic_channel(fluid_synth_t *synth, int chan) { int nbr_chan; /* checks parameters first */ if(chan < 0) { fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_synth_api_enter(synth); /* The range is all MIDI channels from 0 to MIDI channel count -1 */ chan = 0; /* beginning chan */ nbr_chan = synth->midi_channels; /* MIDI Channels number */ } else { FLUID_API_ENTRY_CHAN(FLUID_FAILED); /* checks if chan is a basic channel */ if(!(synth->channel[chan]->mode & FLUID_CHANNEL_BASIC)) { FLUID_API_RETURN(FLUID_FAILED); } /* The range is all MIDI channels in the group from chan */ nbr_chan = synth->channel[chan]->mode_val; /* nbr of channels in the group */ } /* resets the range of MIDI channels */ fluid_synth_reset_basic_channel_LOCAL(synth, chan, nbr_chan); FLUID_API_RETURN(FLUID_OK); } /** * Checks if a new basic channel group overlaps the next basic channel group. * * On success the function returns the possible number of channel for this * new basic channel group. * The function fails if the new group overlaps the next basic channel group. * * @param see fluid_synth_set_basic_channel. * @return * - On success, the effective number of channels for this new basic channel group, * #FLUID_FAILED otherwise. * - #FLUID_FAILED * - \a val has a number of channels overlapping next basic channel group or been * above MIDI channel count. */ static int fluid_synth_check_next_basic_channel(fluid_synth_t *synth, int basicchan, int mode, int val) { int i, n_chan = synth->midi_channels; /* MIDI Channels count */ int real_val = val; /* real number of channels in the group */ /* adjusts val range */ if(mode == FLUID_CHANNEL_MODE_OMNIOFF_POLY) { real_val = 1; /* mode poly omnioff implies a group of only one channel.*/ } else if(val == 0) { /* mode poly omnion (0), mono omnion (1), mono omni off (3) */ /* value 0 means all possible channels from basicchan to MIDI channel count -1.*/ real_val = n_chan - basicchan; } /* checks if val range is above MIDI channel count */ else if(basicchan + val > n_chan) { return FLUID_FAILED; } /* checks if this basic channel group overlaps next basic channel group */ for(i = basicchan + 1; i < basicchan + real_val; i++) { if(synth->channel[i]->mode & FLUID_CHANNEL_BASIC) { /* A value of 0 for val means all possible channels from basicchan to to the next basic channel -1 (if any). When i reaches the next basic channel group, real_val will be limited if it is possible */ if(val == 0) { /* limitation of real_val */ real_val = i - basicchan; break; } /* overlap with the next basic channel group */ return FLUID_FAILED; } } return real_val; } /** * Sets a new basic channel group only. The function doesn't allow to change an * existing basic channel. * * The function fails if any channel overlaps any existing basic channel group. * To make room if necessary, basic channel groups can be cleared using * fluid_synth_reset_basic_channel(). * * @param synth the synth instance. * @param chan the basic Channel number (0 to MIDI channel count-1). * @param mode the MIDI mode to use for chan (see #fluid_basic_channel_modes). * @param val number of channels in the group. * @note \a val is only relevant for mode #FLUID_CHANNEL_MODE_OMNION_POLY, * #FLUID_CHANNEL_MODE_OMNION_MONO and #FLUID_CHANNEL_MODE_OMNIOFF_MONO. A value * of 0 means all possible channels from \a chan to to next basic channel minus 1 (if any) * or to MIDI channel count minus 1. Val is ignored for #FLUID_CHANNEL_MODE_OMNIOFF_POLY * as this mode implies a group of only one channel. * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. * - \a mode is invalid. * - \a val has a number of channels overlapping another basic channel group or been * above MIDI channel count. * - When the function fails, any existing basic channels aren't modified. */ int fluid_synth_set_basic_channel(fluid_synth_t *synth, int chan, int mode, int val) { /* check parameters */ fluid_return_val_if_fail(mode >= 0, FLUID_FAILED); fluid_return_val_if_fail(mode < FLUID_CHANNEL_MODE_LAST, FLUID_FAILED); fluid_return_val_if_fail(val >= 0, FLUID_FAILED); FLUID_API_ENTRY_CHAN(FLUID_FAILED); /**/ if(val > 0 && chan + val > synth->midi_channels) { FLUID_API_RETURN(FLUID_FAILED); } /* Checks if there is an overlap with the next basic channel */ val = fluid_synth_check_next_basic_channel(synth, chan, mode, val); if(val == FLUID_FAILED || synth->channel[chan]->mode & FLUID_CHANNEL_ENABLED) { /* overlap with the next or previous channel group */ FLUID_LOG(FLUID_INFO, "basic channel %d overlaps another group", chan); FLUID_API_RETURN(FLUID_FAILED); } /* sets a new basic channel group */ fluid_synth_set_basic_channel_LOCAL(synth, chan, mode, val); /**/ FLUID_API_RETURN(FLUID_OK); } /* * Local version of fluid_synth_set_basic_channel(), called internally: * - by fluid_synth_set_basic_channel() to set a new basic channel group. * - during creation new_fluid_synth() or on CC reset to set a default basic channel group. * - on CC ominoff, CC omnion, CC poly , CC mono to change an existing basic channel group. * * @param see fluid_synth_set_basic_channel() */ static void fluid_synth_set_basic_channel_LOCAL(fluid_synth_t *synth, int basicchan, int mode, int val) { int i; /* sets the basic channel group */ for(i = basicchan; i < basicchan + val; i++) { int new_mode = mode; /* OMNI_OFF/ON, MONO/POLY ,others bits are zero */ int new_val; /* MIDI specs: when mode is changed, channel must receive ALL_NOTES_OFF */ fluid_synth_all_notes_off_LOCAL(synth, i); if(i == basicchan) { new_mode |= FLUID_CHANNEL_BASIC; /* First channel in the group */ new_val = val; /* number of channels in the group */ } else { new_val = 0; /* val is 0 for other channel than basic channel */ } /* Channel is enabled */ new_mode |= FLUID_CHANNEL_ENABLED; /* Now new_mode is OMNI OFF/ON,MONO/POLY, BASIC_CHANNEL or not and enabled */ fluid_channel_set_basic_channel_info(synth->channel[i], new_mode); synth->channel[i]->mode_val = new_val; } } /** * Searches a previous basic channel starting from chan. * * @param synth the synth instance. * @param chan starting index of the search (including chan). * @return index of the basic channel if found , FLUID_FAILED otherwise. */ static int fluid_synth_get_previous_basic_channel(fluid_synth_t *synth, int chan) { for(; chan >= 0; chan--) { /* searches previous basic channel */ if(synth->channel[chan]->mode & FLUID_CHANNEL_BASIC) { /* chan is the previous basic channel */ return chan; } } return FLUID_FAILED; } /** * Returns poly mono mode information of any MIDI channel. * * @param synth the synth instance * @param chan MIDI channel number (0 to MIDI channel count - 1) * @param basic_chan_out Buffer to store the basic channel \a chan belongs to or #FLUID_FAILED if \a chan is disabled. * @param mode_out Buffer to store the mode of \a chan (see #fluid_basic_channel_modes) or #FLUID_FAILED if \a chan is disabled. * @param val_out Buffer to store the total number of channels in this basic channel group or #FLUID_FAILED if \a chan is disabled. * @note If any of \a basic_chan_out, \a mode_out, \a val_out pointer is NULL * the corresponding information isn't returned. * * @return * - #FLUID_OK on success. * - #FLUID_FAILED * - \a synth is NULL. * - \a chan is outside MIDI channel count. */ int fluid_synth_get_basic_channel(fluid_synth_t *synth, int chan, int *basic_chan_out, int *mode_out, int *val_out) { int basic_chan = FLUID_FAILED; int mode = FLUID_FAILED; int val = FLUID_FAILED; /* checks parameters first */ FLUID_API_ENTRY_CHAN(FLUID_FAILED); if((synth->channel[chan]->mode & FLUID_CHANNEL_ENABLED) && /* chan is enabled , we search the basic channel chan belongs to */ (basic_chan = fluid_synth_get_previous_basic_channel(synth, chan)) != FLUID_FAILED) { mode = synth->channel[chan]->mode & FLUID_CHANNEL_MODE_MASK; val = synth->channel[basic_chan]->mode_val; } /* returns the information if they are requested */ if(basic_chan_out) { * basic_chan_out = basic_chan; } if(mode_out) { * mode_out = mode; } if(val_out) { * val_out = val; } FLUID_API_RETURN(FLUID_OK); } fluidsynth-2.1.1/src/synth/fluid_synth.h000066400000000000000000000263661362231004000203450ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_SYNTH_H #define _FLUID_SYNTH_H /*************************************************************** * * INCLUDES */ #include "fluid_sys.h" #include "fluid_list.h" #include "fluid_rev.h" #include "fluid_voice.h" #include "fluid_chorus.h" #include "fluid_ladspa.h" #include "fluid_midi_router.h" #include "fluid_rvoice_event.h" /*************************************************************** * * DEFINES */ #define FLUID_NUM_PROGRAMS 128 #define DRUM_INST_BANK 128 #define FLUID_UNSET_PROGRAM 128 /* Program number used to unset a preset */ #define FLUID_REVERB_DEFAULT_ROOMSIZE 0.2f /**< Default reverb room size */ #define FLUID_REVERB_DEFAULT_DAMP 0.0f /**< Default reverb damping */ #define FLUID_REVERB_DEFAULT_WIDTH 0.5f /**< Default reverb width */ #define FLUID_REVERB_DEFAULT_LEVEL 0.9f /**< Default reverb level */ #define FLUID_CHORUS_DEFAULT_N 3 /**< Default chorus voice count */ #define FLUID_CHORUS_DEFAULT_LEVEL 2.0f /**< Default chorus level */ #define FLUID_CHORUS_DEFAULT_SPEED 0.3f /**< Default chorus speed */ #define FLUID_CHORUS_DEFAULT_DEPTH 8.0f /**< Default chorus depth */ #define FLUID_CHORUS_DEFAULT_TYPE FLUID_CHORUS_MOD_SINE /**< Default chorus waveform type */ /*************************************************************** * * ENUM */ /** * Bank Select MIDI message styles. Default style is GS. */ enum fluid_midi_bank_select { FLUID_BANK_STYLE_GM, /**< GM style, bank = 0 always (CC0/MSB and CC32/LSB ignored) */ FLUID_BANK_STYLE_GS, /**< GS style, bank = CC0/MSB (CC32/LSB ignored) */ FLUID_BANK_STYLE_XG, /**< XG style, bank = CC32/LSB (CC0/MSB ignored) */ FLUID_BANK_STYLE_MMA /**< MMA style bank = 128*MSB+LSB */ }; enum fluid_synth_status { FLUID_SYNTH_CLEAN, FLUID_SYNTH_PLAYING, FLUID_SYNTH_QUIET, FLUID_SYNTH_STOPPED }; #define SYNTH_REVERB_CHANNEL 0 #define SYNTH_CHORUS_CHANNEL 1 /* * fluid_synth_t * * Mutual exclusion notes (as of 1.1.2): * * All variables are considered belongning to the "public API" thread, * which processes all MIDI, except for: * * ticks_since_start - atomic, set by rendering thread only * cpu_load - atomic, set by rendering thread only * cur, curmax, dither_index - used by rendering thread only * ladspa_fx - same instance copied in rendering thread. Synchronising handled internally. * */ struct _fluid_synth_t { fluid_rec_mutex_t mutex; /**< Lock for public API */ int use_mutex; /**< Use mutex for all public API functions? */ int public_api_count; /**< How many times the mutex is currently locked */ fluid_settings_t *settings; /**< the synthesizer settings */ int device_id; /**< Device ID used for SYSEX messages */ int polyphony; /**< Maximum polyphony */ int with_reverb; /**< Should the synth use the built-in reverb unit? */ int with_chorus; /**< Should the synth use the built-in chorus unit? */ int verbose; /**< Turn verbose mode on? */ double sample_rate; /**< The sample rate */ int midi_channels; /**< the number of MIDI channels (>= 16) */ int bank_select; /**< the style of Bank Select MIDI messages */ int audio_channels; /**< the number of audio channels (1 channel=left+right) */ int audio_groups; /**< the number of (stereo) 'sub'groups from the synth. Typically equal to audio_channels. */ int effects_channels; /**< the number of effects channels (>= 2) */ int effects_groups; /**< the number of effects units (>= 1) */ int state; /**< the synthesizer state */ fluid_atomic_uint_t ticks_since_start; /**< the number of audio samples since the start */ unsigned int start; /**< the start in msec, as returned by system clock */ fluid_overflow_prio_t overflow; /**< parameters for overflow priority (aka voice-stealing) */ fluid_list_t *loaders; /**< the SoundFont loaders */ fluid_list_t *sfont; /**< List of fluid_sfont_info_t for each loaded SoundFont (remains until SoundFont is unloaded) */ int sfont_id; /**< Incrementing ID assigned to each loaded SoundFont */ float gain; /**< master gain */ fluid_channel_t **channel; /**< the channels */ int nvoice; /**< the length of the synthesis process array (max polyphony allowed) */ fluid_voice_t **voice; /**< the synthesis voices */ int active_voice_count; /**< count of active voices */ unsigned int noteid; /**< the id is incremented for every new note. it's used for noteoff's */ unsigned int storeid; int fromkey_portamento; /**< fromkey portamento */ fluid_rvoice_eventhandler_t *eventhandler; double reverb_roomsize; /**< Shadow of reverb roomsize */ double reverb_damping; /**< Shadow of reverb damping */ double reverb_width; /**< Shadow of reverb width */ double reverb_level; /**< Shadow of reverb level */ int chorus_nr; /**< Shadow of chorus number */ double chorus_level; /**< Shadow of chorus level */ double chorus_speed; /**< Shadow of chorus speed */ double chorus_depth; /**< Shadow of chorus depth */ int chorus_type; /**< Shadow of chorus type */ int cur; /**< the current sample in the audio buffers to be output */ int curmax; /**< current amount of samples present in the audio buffers */ int dither_index; /**< current index in random dither value buffer: fluid_synth_(write_s16|dither_s16) */ fluid_atomic_float_t cpu_load; /**< CPU load in percent (CPU time required / audio synthesized time * 100) */ fluid_tuning_t ***tuning; /**< 128 banks of 128 programs for the tunings */ fluid_private_t tuning_iter; /**< Tuning iterators per each thread */ fluid_sample_timer_t *sample_timers; /**< List of timers triggered before a block is processed */ unsigned int min_note_length_ticks; /**< If note-offs are triggered just after a note-on, they will be delayed */ int cores; /**< Number of CPU cores (1 by default) */ fluid_mod_t *default_mod; /**< the (dynamic) list of default modulators */ fluid_ladspa_fx_t *ladspa_fx; /**< Effects unit for LADSPA support */ enum fluid_iir_filter_type custom_filter_type; /**< filter type of the user-defined filter currently used for all voices */ enum fluid_iir_filter_flags custom_filter_flags; /**< filter type of the user-defined filter currently used for all voices */ }; /** * Type definition of the synthesizer's audio callback function. * @param synth FluidSynth instance * @param len Count of audio frames to synthesize * @param out1 Array to store left channel of audio to * @param loff Offset index in 'out1' for first sample * @param lincr Increment between samples stored to 'out1' * @param out2 Array to store right channel of audio to * @param roff Offset index in 'out2' for first sample * @param rincr Increment between samples stored to 'out2' */ typedef int (*fluid_audio_callback_t)(fluid_synth_t *synth, int len, void *out1, int loff, int lincr, void *out2, int roff, int rincr); fluid_preset_t *fluid_synth_find_preset(fluid_synth_t *synth, int banknum, int prognum); void fluid_synth_sfont_unref(fluid_synth_t *synth, fluid_sfont_t *sfont); void fluid_synth_dither_s16(int *dither_index, int len, const float *lin, const float *rin, void *lout, int loff, int lincr, void *rout, int roff, int rincr); int fluid_synth_reset_reverb(fluid_synth_t *synth); int fluid_synth_set_reverb_preset(fluid_synth_t *synth, unsigned int num); int fluid_synth_set_reverb_full(fluid_synth_t *synth, int set, double roomsize, double damping, double width, double level); int fluid_synth_reset_chorus(fluid_synth_t *synth); int fluid_synth_set_chorus_full(fluid_synth_t *synth, int set, int nr, double level, double speed, double depth_ms, int type); fluid_sample_timer_t *new_fluid_sample_timer(fluid_synth_t *synth, fluid_timer_callback_t callback, void *data); void delete_fluid_sample_timer(fluid_synth_t *synth, fluid_sample_timer_t *timer); void fluid_sample_timer_reset(fluid_synth_t *synth, fluid_sample_timer_t *timer); void fluid_synth_process_event_queue(fluid_synth_t *synth); int fluid_synth_set_gen2(fluid_synth_t *synth, int chan, int param, float value, int absolute, int normalized); int fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int)); int fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr, int (*block_render_func)(fluid_synth_t *, int)); /* * misc */ void fluid_synth_settings(fluid_settings_t *settings); /* extern declared in fluid_synth_monopoly.c */ int fluid_synth_noteon_mono_staccato(fluid_synth_t *synth, int chan, int key, int vel); int fluid_synth_noteon_mono_LOCAL(fluid_synth_t *synth, int chan, int key, int vel); int fluid_synth_noteoff_mono_LOCAL(fluid_synth_t *synth, int chan, int key); int fluid_synth_noteon_monopoly_legato(fluid_synth_t *synth, int chan, int fromkey, int tokey, int vel); int fluid_synth_noteoff_monopoly(fluid_synth_t *synth, int chan, int key, char Mono); fluid_voice_t * fluid_synth_alloc_voice_LOCAL(fluid_synth_t *synth, fluid_sample_t *sample, int chan, int key, int vel, fluid_zone_range_t *zone_range); void fluid_synth_release_voice_on_same_note_LOCAL(fluid_synth_t *synth, int chan, int key); #endif /* _FLUID_SYNTH_H */ fluidsynth-2.1.1/src/synth/fluid_synth_monopoly.c000066400000000000000000000766101362231004000222710ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public License * as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_synth.h" #include "fluid_chan.h" #include "fluid_defsfont.h" /****************************************************************************** The legato detector is composed as this, variables: - monolist: monophonic list variable. - prev_note: to store the most recent note before adding on noteon or before removing on noteoff. - FLUID_CHANNEL_LEGATO_PLAYING: legato/staccato state bit that informs on legato or staccato playing. functions: - fluid_channel_add_monolist(), for inserting a new note. - fluid_channel_search_monolist(), for searching the position of a note into the list. - fluid_channel_remove_monolist(), for removing a note from the list. The monophonic list +------------------------------------------------+ | +----+ +----+ +----+ +----+ | | |note| |note| |note| |note| | +--->|vel |-->|vel |-->....-->|vel |-->|vel |----+ +----+ +----+ +----+ +----+ /|\ /|\ | | i_first i_last The list allows an easy automatic detection of a legato passage when it is played on a MIDI keyboard input device. It is useful also when the input device is an ewi (electronic wind instrument) or evi (electronic valve instrument) and these instruments are unable to send MIDI CC legato on/off. The list memorizes the notes in playing order. - (a) On noteOn n2, if a previous note n1 exists, there is a legato detection with n1 (with or without portamento from n1 to n2 See note below). - (b) On noteOff of the running note n2, if a previous note n1 exists, there is a legato detection from n2 to n1, allowing fast trills playing (with or without portamento from n2 to n1. See note below). Notes in the list are inserted to the end of the list that works like a circular buffer.The features are: 1) It is always possible to play an infinite legato passage in direct order (n1_On,n2_On,n3_On,....). 2) Playing legato in the reverse order (n10_Off, n9_Off,,...) helps in fast trills playing as the list memorizes 10 most recent notes. 3) Playing an infinite lagato passage in ascendant or descendant order, without playing trills is always possible using the usual way like this: First we begin with an ascendant passage, n1On, (n2On,n1Off), (n3On,n2Off) , (n4On,n3Off), then we continue with a descendant passage (n3On,n4off), (n2On,n3off), (n1On,n2off), n1Off...and so on Each MIDI channel have a legato detector. Note: Portamento is a feature independant of the legato detector. So portamento isn't part of the lagato detector. However portamento (when enabled) is triggered at noteOn (like legato). Like in legato situation it is usual to have a portamento from a note 'fromkey' to another note 'tokey'. Portamento fromkey note choice is determined at noteOn by fluid_synth_get_fromkey_portamento_legato() (see below). More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). ******************************************************************************/ /***************************************************************************** Portamento related functions in Poly or Mono mode ******************************************************************************/ /** * fluid_synth_get_fromkey_portamento_legato returns two information: * - fromkey note for portamento. * - fromkey note for legato. * +-----> fromkey_portamento * ______|________ * portamento modes >------->| | * | get_fromkey | * Porta.on/off >------------------------->|_______________| * (PTC) | * +-----> fromkey_legato * * The functions is intended to be call on noteOn mono * see fluid_synth_noteon_mono_staccato(), fluid_synth_noteon_monopoly_legato() * ------- * 1)The function determines if a portamento must occur on next noteOn. * The value returned is 'fromkey portamento' which is the pitchstart key * of a portamento, as function of PTC or (default_fromkey, prev_note) both * if Portamento On. By order of precedence the result is: * 1.1) PTC have precedence over Portamento On. * If CC PTC has been received, its value supersedes and any * portamento pedal On, default_fromkey,prev_note or portamento mode. * 1.2) Otherwise ,when Portamento On the function takes the following value: * - default_fromkey if valid * - otherwise prev_note(prev_note is the note prior the most recent * note played). * Then portamento mode is applied to validate the value chosen. * Where portamento mode is: * - each note, a portamento occurs on each note. * - legato only, portamento only on notes played legato. * - staccato only, portamento only on notes played staccato. * 1.3) Otherwise, portamento is off,INVALID_NOTE is returned (portamento is disabled). * ------ * 2)The function determines if a legato playing must occur on next noteOn. * 'fromkey legato note' is returned as a function of default_fromkey, PTC, * current mono/poly mode,actual 'staccato/legato' playing state and prev_note. * By order of precedence the result is: * 2.1) If valid, default_fromkey have precedence over any others values. * 2.2) Otherwise if CC PTC has been received its value is returned. * 2.3) Otherwise fromkey legato is determined from the mono/poly mode, * the actual 'staccato/legato' playing state (FLUID_CHANNEL_LEGATO_PLAYING) and prev_note * as this: * - in (poly/Mono) staccato , INVALID_NOTE is returned. * - in poly legato , actually we don't want playing legato. So * INVALID_NOTE is returned. * - in mono legato , prev_note is returned. * * On input * @param chan fluid_channel_t. * @param defaultFromkey, the default 'fromkey portamento' note or 'fromkey legato' * note (see description above). * * @return * 1)'fromkey portamento' is returned in fluid_synth_t.fromkey_portamento. * If valid,it means that portamento is enabled . * * 2)The 'fromkey legato' note is returned. * * Notes about usage: * The function is intended to be called when the following event occurs: * - On noteOn (Poly or Mono) after insertion in the monophonic list. * - On noteOff(mono legato playing). In this case, default_fromkey must be valid. * * Typical calling usage: * - In poly, default_fromkey must be INVALID_NOTE. * - In mono staccato playing,default_fromkey must be INVALID_NOTE. * - In mono legato playing,default_fromkey must be valid. */ static char fluid_synth_get_fromkey_portamento_legato(fluid_channel_t *chan, int default_fromkey) { unsigned char ptc = fluid_channel_get_cc(chan, PORTAMENTO_CTRL); if(fluid_channel_is_valid_note(ptc)) { /* CC PTC has been received */ fluid_channel_clear_portamento(chan); /* clears the CC PTC receive */ chan->synth->fromkey_portamento = ptc;/* returns fromkey portamento */ /* returns fromkey legato */ if(!fluid_channel_is_valid_note(default_fromkey)) { default_fromkey = ptc; } } else { /* determines and returns fromkey portamento */ unsigned char fromkey_portamento = INVALID_NOTE; if(fluid_channel_portamento(chan)) { /* Portamento when Portamento pedal is On */ /* 'fromkey portamento'is determined from the portamento mode and the most recent note played (prev_note)*/ enum fluid_channel_portamento_mode portamentomode = chan->portamentomode; if(fluid_channel_is_valid_note(default_fromkey)) { fromkey_portamento = default_fromkey; /* on each note */ } else { fromkey_portamento = fluid_channel_prev_note(chan); /* on each note */ } if(portamentomode == FLUID_CHANNEL_PORTAMENTO_MODE_LEGATO_ONLY) { /* Mode portamento:legato only */ if(!(chan->mode & FLUID_CHANNEL_LEGATO_PLAYING)) { fromkey_portamento = INVALID_NOTE; } } else if(portamentomode == FLUID_CHANNEL_PORTAMENTO_MODE_STACCATO_ONLY) { /* Mode portamento:staccato only */ if(chan->mode & FLUID_CHANNEL_LEGATO_PLAYING) { fromkey_portamento = INVALID_NOTE; } } /* else Mode portamento: on each note (staccato/legato) */ } /* Returns fromkey portamento */ chan->synth->fromkey_portamento = fromkey_portamento; /* Determines and returns fromkey legato */ if(!fluid_channel_is_valid_note(default_fromkey)) { /* in staccato (poly/Mono) returns INVALID_NOTE */ /* In mono mode legato playing returns the note prior most recent note played */ if(fluid_channel_is_playing_mono(chan) && (chan->mode & FLUID_CHANNEL_LEGATO_PLAYING)) { default_fromkey = fluid_channel_prev_note(chan); /* note prior last note */ } /* In poly mode legato playing, actually we don't want playing legato. So returns INVALID_NOTE */ } } return default_fromkey; /* Returns legato fromkey */ } /***************************************************************************** noteon - noteoff functions in Mono mode ******************************************************************************/ /* * noteon - noteoff on a channel in "monophonic playing". * * A channel needs to be played monophonic if this channel has been set in * monophonic mode by basic channel API.(see fluid_synth_polymono.c). * A channel needs also to be played monophonic if it has been set in * polyphonic mode and legato pedal is On during the playing. * When a channel is in "monophonic playing" state, only one note at a time can be * played in a staccato or legato manner (with or without portamento). * More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). * _______________ * ________________ | noteon | * | legato detector| O-->| mono_staccato |--*-> preset_noteon * noteon_mono ->| (add_monolist) |--O-- |_______________| | (with or without) * LOCAL |________________| O /|\ | (portamento) * /|\ set_onenote | | fromkey | * | | | portamento| * noteOn poly >---*------------------* | | * | | | * | _____ |________ | * portamento modes >--- | ->| | | * | | get_fromkey | | * Porta.on/off >--------------------- | ->|_______________| | * (PTC) | | | * | fromkey | fromkey | * | legato | portamento| * | _____\|/_______ | * *-->| noteon |--/ * | | monopoly | * | | legato |----> voices * legato modes >------- | ->|_______________| triggering * | (with or without) * | (portamento) * | * | * noteOff poly >---*----------------- | ---------+ * | clear | | * _\|/_____________ | | * | legato detector | O | * noteoff_mono->|(search_monolist)|-O-- _____\|/_______ * LOCAL |(remove_monolist)| O-->| noteoff | * |_________________| | monopoly |----> noteoff * Sust.on/off >------------------------->|_______________| * Sost.on/off ------------------------------------------------------------------------------*/ /** * Plays a noteon event for a Synth instance in "monophonic playing" state. * Please see the description above about "monophonic playing". * _______________ * ________________ | noteon | * | legato detector| O-->| mono_staccato |--->preset_noteon * noteon_mono ->| (add_monolist) |--O-- |_______________| * LOCAL |________________| O * | * | * | * | * | * | * | * | * | * | _______________ * | | noteon | * +-->| monopoly | * | legato |---> voices * |_______________| triggering * * The function uses the legato detector (see above) to determine if the note must * be played staccato or legato. * * @param synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param key MIDI note number (0-127). * @param vel MIDI velocity (0-127). * @return FLUID_OK on success, FLUID_FAILED otherwise. */ int fluid_synth_noteon_mono_LOCAL(fluid_synth_t *synth, int chan, int key, int vel) { fluid_channel_t *channel = synth->channel[chan]; /* Adds the note into the monophonic list */ fluid_channel_add_monolist(channel, key, vel, 0); /* in Breath Sync mode, the noteon triggering is postponed until the musician starts blowing in the breath controller */ if(!(channel->mode & FLUID_CHANNEL_BREATH_SYNC) || fluid_channel_breath_msb(channel)) { /* legato/staccato playing detection */ if(channel->mode & FLUID_CHANNEL_LEGATO_PLAYING) { /* legato playing */ /* legato from prev_note to key */ /* the voices from prev_note key number are to be used to play key number */ /* fromkey must be valid */ return fluid_synth_noteon_monopoly_legato(synth, chan, fluid_channel_prev_note(channel), key, vel); } else { /* staccato playing */ return fluid_synth_noteon_mono_staccato(synth, chan, key, vel); } } else { return FLUID_OK; } } /** * Plays a noteoff event for a Synth instance in "monophonic playing" state. * Please see the description above about "monophonic playing" * * _______________ * | noteon | * +-->| monopoly | * | | legato |----> voices * | |_______________| triggering * | (with or without) * | (portamento) * | * | * | * | * | * | * _________________ | * | legato detector | O * noteoff_mono->|(search_monolist)|-O-- _______________ * LOCAL |(remove_monolist)| O-->| noteoff | * |_________________| | monopoly |----> noteoff * |_______________| * * The function uses the legato detector (see above) to determine if the noteoff must * be played staccato or legato. * * @param synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param key MIDI note number (0-127). * @return FLUID_OK on success, FLUID_FAILED otherwise. */ int fluid_synth_noteoff_mono_LOCAL(fluid_synth_t *synth, int chan, int key) { int status; int i, i_prev; fluid_channel_t *channel = synth->channel[chan]; /* searching the note in the monophonic list */ i = fluid_channel_search_monolist(channel, key, &i_prev); if(i >= 0) { /* the note is in the monophonic list */ /* Removes the note from the monophonic list */ fluid_channel_remove_monolist(channel, i, &i_prev); /* in Breath Sync mode, the noteoff triggering is done if the musician is blowing in the breath controller */ if(!(channel->mode & FLUID_CHANNEL_BREATH_SYNC) || fluid_channel_breath_msb(channel)) { /* legato playing detection */ if(channel->mode & FLUID_CHANNEL_LEGATO_PLAYING) { /* the list contains others notes */ if(i_prev >= 0) { /* legato playing detection on noteoff */ /* legato from key to i_prev key */ /* the voices from key number are to be used to play i_prev key number. */ status = fluid_synth_noteon_monopoly_legato(synth, chan, key, channel->monolist[i_prev].note, channel->monolist[i_prev].vel); } /* else the note doesn't need to be played off */ else { status = FLUID_OK; } } else { /* the monophonic list is empty */ /* plays the monophonic note noteoff and eventually held by sustain/sostenuto */ status = fluid_synth_noteoff_monopoly(synth, chan, key, 1); } } else { status = FLUID_OK; } } else { /* the note is not found in the list so the note was played On when the channel was in polyphonic playing */ /* plays the noteoff as for polyphonic */ status = fluid_synth_noteoff_monopoly(synth, chan, key, 0); } return status; } /*---------------------------------------------------------------------------- staccato playing -----------------------------------------------------------------------------*/ /** * Plays noteon for a monophonic note in staccato manner. * Please see the description above about "monophonic playing". * _______________ * | noteon | * noteon_mono >------------------------>| mono_staccato |----> preset_noteon * |_______________| (with or without) * LOCAL /|\ (portamento) * | fromkey * | portamento * | * | * ______|________ * portamento modes >----->| | * | get_fromkey | * Porta.on/off >----------------------->|_______________| * Portamento * (PTC) * * We are in staccato situation (where no previous note have been depressed). * Before the note been passed to fluid_preset_noteon(), the function must determine * the from_key_portamento parameter used by fluid_preset_noteon(). * * from_key_portamento is returned by fluid_synth_get_fromkey_portamento_legato() function. * fromkey_portamento is set to valid/invalid key value depending of the portamento * modes (see portamento mode API) , CC portamento On/Off , and CC portamento control * (PTC). * * @param synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param key MIDI note number (0-127). * @param vel MIDI velocity (0-127). * @return FLUID_OK on success, FLUID_FAILED otherwise. */ int fluid_synth_noteon_mono_staccato(fluid_synth_t *synth, int chan, int key, int vel) { fluid_channel_t *channel = synth->channel[chan]; /* Before playing a new note, if a previous monophonic note is currently sustained it needs to be released */ fluid_synth_release_voice_on_same_note_LOCAL(synth, chan, channel->key_mono_sustained); /* Get possible 'fromkey portamento' */ fluid_synth_get_fromkey_portamento_legato(channel, INVALID_NOTE); /* The note needs to be played by voices allocation */ return fluid_preset_noteon(channel->preset, synth, chan, key, vel); } /** * Plays noteoff for a polyphonic or monophonic note * Please see the description above about "monophonic playing". * * * noteOff poly >---------------------------------+ * | * | * | * noteoff_mono _____\|/_______ * LOCAL >------------------------->| noteoff | * | monopoly |----> noteoff * Sust.on/off >------------------------->|_______________| * Sost.on/off * * The function has the same behaviour when the noteoff is poly of mono, except * that for mono noteoff, if any pedal (sustain or sostenuto ) is depressed, the * key is memorized. This is necessary when the next mono note will be played * staccato, as any current mono note currently sustained will need to be released * (see fluid_synth_noteon_mono_staccato()). * Note also that for a monophonic legato passage, the function is called only when * the last noteoff of the passage occurs. That means that if sustain or sostenuto * is depressed, only the last note of a legato passage will be sustained. * * @param synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param key MIDI note number (0-127). * @param Mono, 1 noteoff on monophonic note. * 0 noteoff on polyphonic note. * @return FLUID_OK on success, FLUID_FAILED otherwise. * * Note: On return, on monophonic, possible sustained note is memorized in * key_mono_sustained. Memorization is done here on noteOff. */ int fluid_synth_noteoff_monopoly(fluid_synth_t *synth, int chan, int key, char Mono) { int status = FLUID_FAILED; fluid_voice_t *voice; int i; fluid_channel_t *channel = synth->channel[chan]; /* Key_sustained is prepared to return no note sustained (INVALID_NOTE) */ if(Mono) { channel->key_mono_sustained = INVALID_NOTE; /* no mono note sustained */ } /* noteoff for all voices with same chan and same key */ for(i = 0; i < synth->polyphony; i++) { voice = synth->voice[i]; if(fluid_voice_is_on(voice) && fluid_voice_get_channel(voice) == chan && fluid_voice_get_key(voice) == key) { if(synth->verbose) { int used_voices = 0; int k; for(k = 0; k < synth->polyphony; k++) { if(!_AVAILABLE(synth->voice[k])) { used_voices++; } } FLUID_LOG(FLUID_INFO, "noteoff\t%d\t%d\t%d\t%05d\t%.3f\t%d", fluid_voice_get_channel(voice), fluid_voice_get_key(voice), 0, fluid_voice_get_id(voice), (fluid_curtime() - synth->start) / 1000.0f, used_voices); } /* if verbose */ fluid_voice_noteoff(voice); /* noteoff on monophonic note */ /* Key memorization if the note is sustained */ if(Mono && (fluid_voice_is_sustained(voice) || fluid_voice_is_sostenuto(voice))) { channel->key_mono_sustained = key; } status = FLUID_OK; } /* if voice on */ } /* for all voices */ return status; } /*---------------------------------------------------------------------------- legato playing -----------------------------------------------------------------------------*/ /** * Plays noteon for a monophonic note played legato. * Please see the description above about "monophonic playing". * * * _______________ * portamento modes >----->| | * | get_fromkey | * Porta.on/off >----------------------->|_______________| * Portamento | * (PTC) | +-->preset_noteon * fromkey | fromkey | (with or without) * legato | portamento| (portamento) * _____\|/_______ | * | noteon |--+ * noteon_mono >------------------------>| monopoly | * LOCAL | legato |----->voices * |_______________| triggering * /|\ (with or without) * | (portamento) * legato modes >-----------------+ * * We are in legato situation (where a previous note has been depressed). * The function must determine the from_key_portamento and from_key_legato parameters * used respectively by fluid_preset_noteon() function and voices triggering functions. * * from_key_portamento and from_key_legato are returned by * fluid_synth_get_fromkey_portamento_legato() function. * fromkey_portamento is set to valid/invalid key value depending of the portamento * modes (see portamento mode API), CC portamento On/Off, and CC portamento control * (PTC). * Then, depending of the legato modes (see legato mode API), the function will call * the appropriate triggering functions. * @param synth instance. * @param chan MIDI channel number (0 to MIDI channel count - 1). * @param fromkey MIDI note number (0-127). * @param tokey MIDI note number (0-127). * @param vel MIDI velocity (0-127). * @return FLUID_OK on success, FLUID_FAILED otherwise. * * Note: The voices with key 'fromkey' are to be used to play key 'tokey'. * The function is able to play legato through Preset Zone(s) (PZ) and * Instrument Zone(s) (IZ) as far as possible. * When key tokey is outside the current Instrument Zone, Preset Zone, * current 'fromkey' voices are released. If necessary new voices * are restarted when tokey enters inside new Instrument(s) Zones,Preset Zone(s). * More information in FluidPolyMono-0004.pdf chapter 4.7 (Appendices). */ int fluid_synth_noteon_monopoly_legato(fluid_synth_t *synth, int chan, int fromkey, int tokey, int vel) { fluid_channel_t *channel = synth->channel[chan]; enum fluid_channel_legato_mode legatomode = channel->legatomode; fluid_voice_t *voice; int i ; /* Gets possible 'fromkey portamento' and possible 'fromkey legato' note */ fromkey = fluid_synth_get_fromkey_portamento_legato(channel, fromkey); if(fluid_channel_is_valid_note(fromkey)) { for(i = 0; i < synth->polyphony; i++) { /* searching fromkey voices: only those who don't have 'note off' */ voice = synth->voice[i]; if(fluid_voice_is_on(voice) && fluid_voice_get_channel(voice) == chan && fluid_voice_get_key(voice) == fromkey) { fluid_zone_range_t *zone_range = voice->zone_range; /* Ignores voice when there is no instrument zone (i.e no zone_range). Otherwise checks if tokey is inside the range of the running voice */ if(zone_range && fluid_zone_inside_range(zone_range, tokey, vel)) { switch(legatomode) { case FLUID_CHANNEL_LEGATO_MODE_RETRIGGER: /* mode 0 */ fluid_voice_release(voice); /* normal release */ break; case FLUID_CHANNEL_LEGATO_MODE_MULTI_RETRIGGER: /* mode 1 */ /* Skip in attack section */ fluid_voice_update_multi_retrigger_attack(voice, tokey, vel); /* Starts portamento if enabled */ if(fluid_channel_is_valid_note(synth->fromkey_portamento)) { /* Sends portamento parameters to the voice dsp */ fluid_voice_update_portamento(voice, synth->fromkey_portamento, tokey); } /* The voice is now used to play tokey in legato manner */ /* Marks this Instrument Zone to be ignored during next fluid_preset_noteon() */ zone_range->ignore = TRUE; break; default: /* Invalid mode: this should never happen */ FLUID_LOG(FLUID_WARN, "Failed to execute legato mode: %d", legatomode); return FLUID_FAILED; } } else { /* tokey note is outside the voice range, so the voice is released */ fluid_voice_release(voice); } } } } /* May be,tokey will enter in new others Insrument Zone(s),Preset Zone(s), in this case it needs to be played by voices allocation */ return fluid_preset_noteon(channel->preset, synth, chan, tokey, vel); } fluidsynth-2.1.1/src/synth/fluid_tuning.c000066400000000000000000000104151362231004000204630ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_tuning.h" #include "fluid_sys.h" fluid_tuning_t *new_fluid_tuning(const char *name, int bank, int prog) { fluid_tuning_t *tuning; int i; tuning = FLUID_NEW(fluid_tuning_t); if(tuning == NULL) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } FLUID_MEMSET(tuning, 0, sizeof(fluid_tuning_t)); if(fluid_tuning_set_name(tuning, name) != FLUID_OK) { delete_fluid_tuning(tuning); return NULL; } tuning->bank = bank; tuning->prog = prog; for(i = 0; i < 128; i++) { tuning->pitch[i] = i * 100.0; } fluid_atomic_int_set(&tuning->refcount, 1); /* Start with a refcount of 1 */ return tuning; } /* Duplicate a tuning */ fluid_tuning_t * fluid_tuning_duplicate(fluid_tuning_t *tuning) { fluid_tuning_t *new_tuning; int i; new_tuning = FLUID_NEW(fluid_tuning_t); if(!new_tuning) { FLUID_LOG(FLUID_PANIC, "Out of memory"); return NULL; } FLUID_MEMSET(new_tuning, 0, sizeof(fluid_tuning_t)); if(fluid_tuning_set_name(new_tuning, tuning->name) != FLUID_OK) { delete_fluid_tuning(new_tuning); return NULL; } new_tuning->bank = tuning->bank; new_tuning->prog = tuning->prog; for(i = 0; i < 128; i++) { new_tuning->pitch[i] = tuning->pitch[i]; } fluid_atomic_int_set(&new_tuning->refcount, 1); /* Start with a refcount of 1 */ return new_tuning; } void delete_fluid_tuning(fluid_tuning_t *tuning) { fluid_return_if_fail(tuning != NULL); FLUID_FREE(tuning->name); FLUID_FREE(tuning); } /* Add a reference to a tuning object */ void fluid_tuning_ref(fluid_tuning_t *tuning) { fluid_return_if_fail(tuning != NULL); fluid_atomic_int_inc(&tuning->refcount); } /* Unref a tuning object, when it reaches 0 it is deleted, returns TRUE if deleted */ int fluid_tuning_unref(fluid_tuning_t *tuning, int count) { fluid_return_val_if_fail(tuning != NULL, FALSE); /* Add and compare are separate, but that is OK, since refcount will only * reach 0 when there are no references and therefore no possibility of * another thread adding a reference in between */ fluid_atomic_int_add(&tuning->refcount, -count); /* Delete when refcount reaches 0 */ if(!fluid_atomic_int_get(&tuning->refcount)) { delete_fluid_tuning(tuning); return TRUE; } else { return FALSE; } } int fluid_tuning_set_name(fluid_tuning_t *tuning, const char *name) { if(tuning->name != NULL) { FLUID_FREE(tuning->name); tuning->name = NULL; } if(name != NULL) { tuning->name = FLUID_STRDUP(name); if(tuning->name == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } } return FLUID_OK; } char *fluid_tuning_get_name(fluid_tuning_t *tuning) { return tuning->name; } void fluid_tuning_set_octave(fluid_tuning_t *tuning, const double *pitch_deriv) { int i; for(i = 0; i < 128; i++) { tuning->pitch[i] = i * 100.0 + pitch_deriv[i % 12]; } } void fluid_tuning_set_all(fluid_tuning_t *tuning, const double *pitch) { int i; for(i = 0; i < 128; i++) { tuning->pitch[i] = pitch[i]; } } void fluid_tuning_set_pitch(fluid_tuning_t *tuning, int key, double pitch) { if((key >= 0) && (key < 128)) { tuning->pitch[key] = pitch; } } fluidsynth-2.1.1/src/synth/fluid_tuning.h000066400000000000000000000042611362231004000204720ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* More information about micro tuning can be found at: http://www.midi.org/about-midi/tuning.htm http://www.midi.org/about-midi/tuning-scale.htm http://www.midi.org/about-midi/tuning_extens.htm */ #ifndef _FLUID_TUNING_H #define _FLUID_TUNING_H #include "fluidsynth_priv.h" struct _fluid_tuning_t { char *name; int bank; int prog; double pitch[128]; /* the pitch of every key, in cents */ fluid_atomic_int_t refcount; /* Tuning reference count */ }; fluid_tuning_t *new_fluid_tuning(const char *name, int bank, int prog); void delete_fluid_tuning(fluid_tuning_t *tuning); fluid_tuning_t *fluid_tuning_duplicate(fluid_tuning_t *tuning); void fluid_tuning_ref(fluid_tuning_t *tuning); int fluid_tuning_unref(fluid_tuning_t *tuning, int count); int fluid_tuning_set_name(fluid_tuning_t *tuning, const char *name); char *fluid_tuning_get_name(fluid_tuning_t *tuning); #define fluid_tuning_get_bank(_t) ((_t)->bank) #define fluid_tuning_get_prog(_t) ((_t)->prog) void fluid_tuning_set_pitch(fluid_tuning_t *tuning, int key, double pitch); #define fluid_tuning_get_pitch(_t, _key) ((_t)->pitch[_key]) void fluid_tuning_set_octave(fluid_tuning_t *tuning, const double *pitch_deriv); void fluid_tuning_set_all(fluid_tuning_t *tuning, const double *pitch); #define fluid_tuning_get_all(_t) (&(_t)->pitch[0]) #endif /* _FLUID_TUNING_H */ fluidsynth-2.1.1/src/synth/fluid_voice.c000066400000000000000000002115101362231004000202630ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sys.h" #include "fluid_voice.h" #include "fluid_mod.h" #include "fluid_chan.h" #include "fluid_conv.h" #include "fluid_synth.h" #include "fluid_sys.h" #include "fluid_sfont.h" #include "fluid_rvoice_event.h" #include "fluid_defsfont.h" /* used for filter turn off optimization - if filter cutoff is above the specified value and filter q is below the other value, turn filter off */ #define FLUID_MAX_AUDIBLE_FILTER_FC 19000.0f #define FLUID_MIN_AUDIBLE_FILTER_Q 1.2f /* min vol envelope release (to stop clicks) in SoundFont timecents */ #define FLUID_MIN_VOLENVRELEASE -7200.0f /* ~16ms */ static const int32_t INT24_MAX = (1 << (16 + 8 - 1)); static int fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t *voice); static int calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base, int gen_key2base, int is_decay); static fluid_real_t fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice); #define UPDATE_RVOICE0(proc) \ do { \ fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ fluid_rvoice_eventhandler_push(voice->eventhandler, proc, voice->rvoice, param); \ } while (0) #define UPDATE_RVOICE_GENERIC_R1(proc, obj, rarg) \ do { \ fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ param[0].real = rarg; \ fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ } while (0) #define UPDATE_RVOICE_GENERIC_I1(proc, obj, iarg) \ do { \ fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ param[0].i = iarg; \ fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ } while (0) #define UPDATE_RVOICE_GENERIC_I2(proc, obj, iarg1, iarg2) \ do { \ fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ param[0].i = iarg1; \ param[1].i = iarg2; \ fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ } while (0) #define UPDATE_RVOICE_GENERIC_IR(proc, obj, iarg, rarg) \ do { \ fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ param[0].i = iarg; \ param[1].real = rarg; \ fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ } while (0) #define UPDATE_RVOICE_R1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, voice->rvoice, arg1) #define UPDATE_RVOICE_I1(proc, arg1) UPDATE_RVOICE_GENERIC_I1(proc, voice->rvoice, arg1) #define UPDATE_RVOICE_BUFFERS_AMP(proc, iarg, rarg) UPDATE_RVOICE_GENERIC_IR(proc, &voice->rvoice->buffers, iarg, rarg) #define UPDATE_RVOICE_ENVLFO_R1(proc, envp, rarg) UPDATE_RVOICE_GENERIC_R1(proc, &voice->rvoice->envlfo.envp, rarg) #define UPDATE_RVOICE_ENVLFO_I1(proc, envp, iarg) UPDATE_RVOICE_GENERIC_I1(proc, &voice->rvoice->envlfo.envp, iarg) static FLUID_INLINE void fluid_voice_update_volenv(fluid_voice_t *voice, int enqueue, fluid_adsr_env_section_t section, unsigned int count, fluid_real_t coeff, fluid_real_t increment, fluid_real_t min, fluid_real_t max) { fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; param[0].i = section; param[1].i = count; param[2].real = coeff; param[3].real = increment; param[4].real = min; param[5].real = max; if(enqueue) { fluid_rvoice_eventhandler_push(voice->eventhandler, fluid_adsr_env_set_data, &voice->rvoice->envlfo.volenv, param); } else { fluid_adsr_env_set_data(&voice->rvoice->envlfo.volenv, param); } } static FLUID_INLINE void fluid_voice_update_modenv(fluid_voice_t *voice, int enqueue, fluid_adsr_env_section_t section, unsigned int count, fluid_real_t coeff, fluid_real_t increment, fluid_real_t min, fluid_real_t max) { fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; param[0].i = section; param[1].i = count; param[2].real = coeff; param[3].real = increment; param[4].real = min; param[5].real = max; if(enqueue) { fluid_rvoice_eventhandler_push(voice->eventhandler, fluid_adsr_env_set_data, &voice->rvoice->envlfo.modenv, param); } else { fluid_adsr_env_set_data(&voice->rvoice->envlfo.modenv, param); } } static FLUID_INLINE void fluid_voice_sample_unref(fluid_sample_t **sample) { if(*sample != NULL) { fluid_sample_decr_ref(*sample); *sample = NULL; } } /* * Swaps the current rvoice with the current overflow_rvoice */ static void fluid_voice_swap_rvoice(fluid_voice_t *voice) { fluid_rvoice_t *rtemp = voice->rvoice; int ctemp = voice->can_access_rvoice; voice->rvoice = voice->overflow_rvoice; voice->can_access_rvoice = voice->can_access_overflow_rvoice; voice->overflow_rvoice = rtemp; voice->can_access_overflow_rvoice = ctemp; } static void fluid_voice_initialize_rvoice(fluid_voice_t *voice, fluid_real_t output_rate) { fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; FLUID_MEMSET(voice->rvoice, 0, sizeof(fluid_rvoice_t)); /* The 'sustain' and 'finished' segments of the volume / modulation * envelope are constant. They are never affected by any modulator * or generator. Therefore it is enough to initialize them once * during the lifetime of the synth. */ fluid_voice_update_volenv(voice, FALSE, FLUID_VOICE_ENVSUSTAIN, 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); fluid_voice_update_volenv(voice, FALSE, FLUID_VOICE_ENVFINISHED, 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); fluid_voice_update_modenv(voice, FALSE, FLUID_VOICE_ENVSUSTAIN, 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); fluid_voice_update_modenv(voice, FALSE, FLUID_VOICE_ENVFINISHED, 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); param[0].i = FLUID_IIR_LOWPASS; param[1].i = 0; fluid_iir_filter_init(&voice->rvoice->resonant_filter, param); param[0].i = FLUID_IIR_DISABLED; fluid_iir_filter_init(&voice->rvoice->resonant_custom_filter, param); param[0].real = output_rate; fluid_rvoice_set_output_rate(voice->rvoice, param); } /* * new_fluid_voice */ fluid_voice_t * new_fluid_voice(fluid_rvoice_eventhandler_t *handler, fluid_real_t output_rate) { fluid_voice_t *voice; voice = FLUID_NEW(fluid_voice_t); if(voice == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } voice->can_access_rvoice = TRUE; voice->can_access_overflow_rvoice = TRUE; voice->rvoice = FLUID_NEW(fluid_rvoice_t); voice->overflow_rvoice = FLUID_NEW(fluid_rvoice_t); if(voice->rvoice == NULL || voice->overflow_rvoice == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); delete_fluid_voice(voice); return NULL; } voice->status = FLUID_VOICE_CLEAN; voice->chan = NO_CHANNEL; voice->key = 0; voice->vel = 0; voice->eventhandler = handler; voice->channel = NULL; voice->sample = NULL; voice->output_rate = output_rate; /* Initialize both the rvoice and overflow_rvoice */ fluid_voice_initialize_rvoice(voice, output_rate); fluid_voice_swap_rvoice(voice); fluid_voice_initialize_rvoice(voice, output_rate); return voice; } /* * delete_fluid_voice */ void delete_fluid_voice(fluid_voice_t *voice) { fluid_return_if_fail(voice != NULL); if(!voice->can_access_rvoice || !voice->can_access_overflow_rvoice) { FLUID_LOG(FLUID_WARN, "Deleting voice %u which has locked rvoices!", voice->id); } FLUID_FREE(voice->overflow_rvoice); FLUID_FREE(voice->rvoice); FLUID_FREE(voice); } /* fluid_voice_init * * Initialize the synthesis process * inst_zone, the Instrument Zone contains the sample, Keyrange,Velrange * of the voice. * When playing legato (n1,n2) in mono mode, n2 will use n1 voices * as far as n2 still enters in Keyrange,Velrange of n1. */ int fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample, fluid_zone_range_t *inst_zone_range, fluid_channel_t *channel, int key, int vel, unsigned int id, unsigned int start_time, fluid_real_t gain) { /* Note: The voice parameters will be initialized later, when the * generators have been retrieved from the sound font. Here, only * the 'working memory' of the voice (position in envelopes, history * of IIR filters, position in sample etc) is initialized. */ int i; if(!voice->can_access_rvoice) { if(voice->can_access_overflow_rvoice) { fluid_voice_swap_rvoice(voice); } else { FLUID_LOG(FLUID_ERR, "Internal error: Cannot access an rvoice in fluid_voice_init!"); return FLUID_FAILED; } } /* We are now guaranteed to have access to the rvoice */ if(voice->sample) { fluid_voice_off(voice); } voice->zone_range = inst_zone_range; /* Instrument zone range for legato */ voice->id = id; voice->chan = fluid_channel_get_num(channel); voice->key = (unsigned char) key; voice->vel = (unsigned char) vel; voice->channel = channel; voice->mod_count = 0; voice->start_time = start_time; voice->has_noteoff = 0; UPDATE_RVOICE0(fluid_rvoice_reset); /* Increment the reference count of the sample to prevent the unloading of the soundfont while this voice is playing, once for us and once for the rvoice. */ fluid_sample_incr_ref(sample); fluid_rvoice_eventhandler_push_ptr(voice->eventhandler, fluid_rvoice_set_sample, voice->rvoice, sample); fluid_sample_incr_ref(sample); voice->sample = sample; i = fluid_channel_get_interp_method(channel); UPDATE_RVOICE_I1(fluid_rvoice_set_interp_method, i); /* Set all the generators to their default value, according to SF * 2.01 section 8.1.3 (page 48). The value of NRPN messages are * copied from the channel to the voice's generators. The sound font * loader overwrites them. The generator values are later converted * into voice parameters in * fluid_voice_calculate_runtime_synthesis_parameters. */ fluid_gen_init(&voice->gen[0], channel); UPDATE_RVOICE_I1(fluid_rvoice_set_samplemode, _SAMPLEMODE(voice)); voice->synth_gain = gain; /* avoid division by zero later*/ if(voice->synth_gain < 0.0000001f) { voice->synth_gain = 0.0000001f; } UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, voice->synth_gain); /* Set up buffer mapping, should be done more flexible in the future. */ i = 2 * channel->synth->audio_groups; i += (voice->chan % channel->synth->effects_groups) * channel->synth->effects_channels; UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 2, i + SYNTH_REVERB_CHANNEL); UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 3, i + SYNTH_CHORUS_CHANNEL); i = 2 * (voice->chan % channel->synth->audio_groups); UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 0, i); UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 1, i + 1); return FLUID_OK; } /** * Update sample rate. * @note If the voice is active, it will be turned off. */ void fluid_voice_set_output_rate(fluid_voice_t *voice, fluid_real_t value) { if(fluid_voice_is_playing(voice)) { fluid_voice_off(voice); } voice->output_rate = value; UPDATE_RVOICE_GENERIC_R1(fluid_rvoice_set_output_rate, voice->rvoice, value); UPDATE_RVOICE_GENERIC_R1(fluid_rvoice_set_output_rate, voice->overflow_rvoice, value); } /** * Set the value of a generator. * @param voice Voice instance * @param i Generator ID (#fluid_gen_type) * @param val Generator value */ void fluid_voice_gen_set(fluid_voice_t *voice, int i, float val) { voice->gen[i].val = val; voice->gen[i].flags = GEN_SET; if(i == GEN_SAMPLEMODE) { UPDATE_RVOICE_I1(fluid_rvoice_set_samplemode, (int) val); } } /** * Offset the value of a generator. * @param voice Voice instance * @param i Generator ID (#fluid_gen_type) * @param val Value to add to the existing value */ void fluid_voice_gen_incr(fluid_voice_t *voice, int i, float val) { voice->gen[i].val += val; voice->gen[i].flags = GEN_SET; } /** * Get the value of a generator. * @param voice Voice instance * @param gen Generator ID (#fluid_gen_type) * @return Current generator value */ float fluid_voice_gen_get(fluid_voice_t *voice, int gen) { return voice->gen[gen].val; } fluid_real_t fluid_voice_gen_value(const fluid_voice_t *voice, int num) { return (fluid_real_t)(voice->gen[num].val + voice->gen[num].mod + voice->gen[num].nrpn); } /* * fluid_voice_start */ void fluid_voice_start(fluid_voice_t *voice) { /* The maximum volume of the loop is calculated and cached once for each * sample with its nominal loop settings. This happens, when the sample is used * for the first time.*/ fluid_voice_calculate_runtime_synthesis_parameters(voice); #ifdef WITH_PROFILING voice->ref = fluid_profile_ref(); #endif voice->status = FLUID_VOICE_ON; /* Increment voice count */ voice->channel->synth->active_voice_count++; } /** * Calculate the amplitude of a voice. * * @param gain The gain value in the range [0.0 ; 1.0] * @return An amplitude used by rvoice_mixer's buffers */ static FLUID_INLINE fluid_real_t fluid_voice_calculate_gain_amplitude(const fluid_voice_t *voice, fluid_real_t gain) { /* we use 24bit samples in fluid_rvoice_dsp. in order to normalize float * samples to [0.0;1.0] divide samples by the max. value of an int24 and * amplify them with the gain */ return gain * voice->synth_gain / (INT24_MAX * 1.0f); } /* Useful to return the nominal pitch of a key */ /* The nominal pitch is dependant of voice->root_pitch,tuning, and GEN_SCALETUNE generator. This is useful to set the value of GEN_PITCH generator on noteOn. This is useful to get the beginning/ending pitch for portamento. */ fluid_real_t fluid_voice_calculate_pitch(fluid_voice_t *voice, int key) { fluid_tuning_t *tuning; fluid_real_t x, pitch; /* Now the nominal pitch of the key is returned. * Note about SCALETUNE: SF2.01 8.1.3 says, that this generator is a * non-realtime parameter. So we don't allow modulation (as opposed * to fluid_voice_gen_value(voice, GEN_SCALETUNE) When the scale tuning is varied, * one key remains fixed. Here C3 (MIDI number 60) is used. */ if(fluid_channel_has_tuning(voice->channel)) { tuning = fluid_channel_get_tuning(voice->channel); x = fluid_tuning_get_pitch(tuning, (int)(voice->root_pitch / 100.0f)); pitch = voice->gen[GEN_SCALETUNE].val / 100.0f * (fluid_tuning_get_pitch(tuning, key) - x) + x; } else { pitch = voice->gen[GEN_SCALETUNE].val * (key - voice->root_pitch / 100.0f) + voice->root_pitch; } return pitch; } void fluid_voice_calculate_gen_pitch(fluid_voice_t *voice) { voice->gen[GEN_PITCH].val = fluid_voice_calculate_pitch(voice, fluid_voice_get_actual_key(voice)); } /* * fluid_voice_calculate_runtime_synthesis_parameters * * in this function we calculate the values of all the parameters. the * parameters are converted to their most useful unit for the DSP * algorithm, for example, number of samples instead of * timecents. Some parameters keep their "perceptual" unit and * conversion will be done in the DSP function. This is the case, for * example, for the pitch since it is modulated by the controllers in * cents. */ static int fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t *voice) { int i; unsigned int n; static int const list_of_generators_to_initialize[] = { GEN_STARTADDROFS, /* SF2.01 page 48 #0 */ GEN_ENDADDROFS, /* #1 */ GEN_STARTLOOPADDROFS, /* #2 */ GEN_ENDLOOPADDROFS, /* #3 */ /* GEN_STARTADDRCOARSEOFS see comment below [1] #4 */ GEN_MODLFOTOPITCH, /* #5 */ GEN_VIBLFOTOPITCH, /* #6 */ GEN_MODENVTOPITCH, /* #7 */ GEN_FILTERFC, /* #8 */ GEN_FILTERQ, /* #9 */ GEN_MODLFOTOFILTERFC, /* #10 */ GEN_MODENVTOFILTERFC, /* #11 */ /* GEN_ENDADDRCOARSEOFS [1] #12 */ GEN_MODLFOTOVOL, /* #13 */ /* not defined #14 */ GEN_CHORUSSEND, /* #15 */ GEN_REVERBSEND, /* #16 */ GEN_PAN, /* #17 */ /* not defined #18 */ /* not defined #19 */ /* not defined #20 */ GEN_MODLFODELAY, /* #21 */ GEN_MODLFOFREQ, /* #22 */ GEN_VIBLFODELAY, /* #23 */ GEN_VIBLFOFREQ, /* #24 */ GEN_MODENVDELAY, /* #25 */ GEN_MODENVATTACK, /* #26 */ GEN_MODENVHOLD, /* #27 */ GEN_MODENVDECAY, /* #28 */ /* GEN_MODENVSUSTAIN [1] #29 */ GEN_MODENVRELEASE, /* #30 */ /* GEN_KEYTOMODENVHOLD [1] #31 */ /* GEN_KEYTOMODENVDECAY [1] #32 */ GEN_VOLENVDELAY, /* #33 */ GEN_VOLENVATTACK, /* #34 */ GEN_VOLENVHOLD, /* #35 */ GEN_VOLENVDECAY, /* #36 */ /* GEN_VOLENVSUSTAIN [1] #37 */ GEN_VOLENVRELEASE, /* #38 */ /* GEN_KEYTOVOLENVHOLD [1] #39 */ /* GEN_KEYTOVOLENVDECAY [1] #40 */ /* GEN_STARTLOOPADDRCOARSEOFS [1] #45 */ GEN_KEYNUM, /* #46 */ GEN_VELOCITY, /* #47 */ GEN_ATTENUATION, /* #48 */ /* GEN_ENDLOOPADDRCOARSEOFS [1] #50 */ /* GEN_COARSETUNE [1] #51 */ /* GEN_FINETUNE [1] #52 */ GEN_OVERRIDEROOTKEY, /* #58 */ GEN_PITCH, /* --- */ GEN_CUSTOM_BALANCE, /* --- */ GEN_CUSTOM_FILTERFC, /* --- */ GEN_CUSTOM_FILTERQ /* --- */ }; /* When the voice is made ready for the synthesis process, a lot of * voice-internal parameters have to be calculated. * * At this point, the sound font has already set the -nominal- value * for all generators (excluding GEN_PITCH). Most generators can be * modulated - they include a nominal value and an offset (which * changes with velocity, note number, channel parameters like * aftertouch, mod wheel...) Now this offset will be calculated as * follows: * * - Process each modulator once. * - Calculate its output value. * - Find the target generator. * - Add the output value to the modulation value of the generator. * * Note: The generators have been initialized with * fluid_gen_init(). */ for(i = 0; i < voice->mod_count; i++) { fluid_mod_t *mod = &voice->mod[i]; fluid_real_t modval = fluid_mod_get_value(mod, voice); int dest_gen_index = mod->dest; fluid_gen_t *dest_gen = &voice->gen[dest_gen_index]; dest_gen->mod += modval; /* fluid_dump_modulator(mod); */ } /* Now the generators are initialized, nominal and modulation value. * The voice parameters (which depend on generators) are calculated * with fluid_voice_update_param. Processing the list of generator * changes will calculate each voice parameter once. * * Note [1]: Some voice parameters depend on several generators. For * example, the pitch depends on GEN_COARSETUNE, GEN_FINETUNE and * GEN_PITCH. voice->pitch. Unnecessary recalculation is avoided * by removing all but one generator from the list of voice * parameters. Same with GEN_XXX and GEN_XXXCOARSE: the * initialisation list contains only GEN_XXX. */ /* Calculate the voice parameter(s) dependent on each generator. */ for(n = 0; n < FLUID_N_ELEMENTS(list_of_generators_to_initialize); n++) { fluid_voice_update_param(voice, list_of_generators_to_initialize[n]); } /* Start portamento if enabled */ { /* fromkey note comes from "GetFromKeyPortamentoLegato()" detector. When fromkey is set to ValidNote , portamento is started */ /* Return fromkey portamento */ int fromkey = voice->channel->synth->fromkey_portamento; if(fluid_channel_is_valid_note(fromkey)) { /* Send portamento parameters to the voice dsp */ fluid_voice_update_portamento(voice, fromkey, fluid_voice_get_actual_key(voice)); } } /* Make an estimate on how loud this voice can get at any time (attenuation). */ UPDATE_RVOICE_R1(fluid_rvoice_set_min_attenuation_cB, fluid_voice_get_lower_boundary_for_attenuation(voice)); return FLUID_OK; } /* * calculate_hold_decay_buffers */ static int calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base, int gen_key2base, int is_decay) { /* Purpose: * * Returns the number of DSP loops, that correspond to the hold * (is_decay=0) or decay (is_decay=1) time. * gen_base=GEN_VOLENVHOLD, GEN_VOLENVDECAY, GEN_MODENVHOLD, * GEN_MODENVDECAY gen_key2base=GEN_KEYTOVOLENVHOLD, * GEN_KEYTOVOLENVDECAY, GEN_KEYTOMODENVHOLD, GEN_KEYTOMODENVDECAY */ fluid_real_t timecents; fluid_real_t seconds; int buffers; /* SF2.01 section 8.4.3 # 31, 32, 39, 40 * GEN_KEYTOxxxENVxxx uses key 60 as 'origin'. * The unit of the generator is timecents per key number. * If KEYTOxxxENVxxx is 100, a key one octave over key 60 (72) * will cause (60-72)*100=-1200 timecents of time variation. * The time is cut in half. */ timecents = (fluid_voice_gen_value(voice, gen_base) + fluid_voice_gen_value(voice, gen_key2base) * (fluid_real_t)(60 - fluid_voice_get_actual_key(voice))); /* Range checking */ if(is_decay) { /* SF 2.01 section 8.1.3 # 28, 36 */ if(timecents > 8000.f) { timecents = 8000.f; } } else { /* SF 2.01 section 8.1.3 # 27, 35 */ if(timecents > 5000.f) { timecents = 5000.f; } /* SF 2.01 section 8.1.2 # 27, 35: * The most negative number indicates no hold time */ if(timecents <= -32768.f) { return 0; } } /* SF 2.01 section 8.1.3 # 27, 28, 35, 36 */ if(timecents < -12000.f) { timecents = -12000.f; } seconds = fluid_tc2sec(timecents); /* Each DSP loop processes FLUID_BUFSIZE samples. */ /* round to next full number of buffers */ buffers = (int)(((fluid_real_t)voice->output_rate * seconds) / (fluid_real_t)FLUID_BUFSIZE + 0.5f); return buffers; } /* * The value of a generator (gen) has changed. (The different * generators are listed in fluidsynth.h, or in SF2.01 page 48-49) * Now the dependent 'voice' parameters are calculated. * * fluid_voice_update_param can be called during the setup of the * voice (to calculate the initial value for a voice parameter), or * during its operation (a generator has been changed due to * real-time parameter modifications like pitch-bend). * * Note: The generator holds three values: The base value .val, an * offset caused by modulators .mod, and an offset caused by the * NRPN system. fluid_voice_gen_value(voice, generator_enumerator) returns the sum * of all three. */ /** * Update all the synthesis parameters, which depend on generator \a gen. * @param voice Voice instance * @param gen Generator id (#fluid_gen_type) * * This is only necessary after changing a generator of an already operating voice. * Most applications will not need this function. */ void fluid_voice_update_param(fluid_voice_t *voice, int gen) { unsigned int count, z; fluid_real_t x = fluid_voice_gen_value(voice, gen); switch(gen) { case GEN_PAN: case GEN_CUSTOM_BALANCE: /* range checking is done in the fluid_pan and fluid_balance functions */ voice->pan = fluid_voice_gen_value(voice, GEN_PAN); voice->balance = fluid_voice_gen_value(voice, GEN_CUSTOM_BALANCE); /* left amp */ UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 0, fluid_voice_calculate_gain_amplitude(voice, fluid_pan(voice->pan, 1) * fluid_balance(voice->balance, 1))); /* right amp */ UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 1, fluid_voice_calculate_gain_amplitude(voice, fluid_pan(voice->pan, 0) * fluid_balance(voice->balance, 0))); break; case GEN_ATTENUATION: voice->attenuation = x; /* Range: SF2.01 section 8.1.3 # 48 * Motivation for range checking: * OHPiano.SF2 sets initial attenuation to a whooping -96 dB */ fluid_clip(voice->attenuation, 0.f, 1440.f); UPDATE_RVOICE_R1(fluid_rvoice_set_attenuation, voice->attenuation); break; /* The pitch is calculated from three different generators. * Read comment in fluidsynth.h about GEN_PITCH. */ case GEN_PITCH: case GEN_COARSETUNE: case GEN_FINETUNE: /* The testing for allowed range is done in 'fluid_ct2hz' */ voice->pitch = (fluid_voice_gen_value(voice, GEN_PITCH) + 100.0f * fluid_voice_gen_value(voice, GEN_COARSETUNE) + fluid_voice_gen_value(voice, GEN_FINETUNE)); UPDATE_RVOICE_R1(fluid_rvoice_set_pitch, voice->pitch); break; case GEN_REVERBSEND: /* The generator unit is 'tenths of a percent'. */ voice->reverb_send = x / 1000.0f; fluid_clip(voice->reverb_send, 0.f, 1.f); UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 2, fluid_voice_calculate_gain_amplitude(voice, voice->reverb_send)); break; case GEN_CHORUSSEND: /* The generator unit is 'tenths of a percent'. */ voice->chorus_send = x / 1000.0f; fluid_clip(voice->chorus_send, 0.f, 1.f); UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 3, fluid_voice_calculate_gain_amplitude(voice, voice->chorus_send)); break; case GEN_OVERRIDEROOTKEY: /* This is a non-realtime parameter. Therefore the .mod part of the generator * can be neglected. * NOTE: origpitch sets MIDI root note while pitchadj is a fine tuning amount * which offsets the original rate. This means that the fine tuning is * inverted with respect to the root note (so subtract it, not add). */ if(voice->sample != NULL) { if(voice->gen[GEN_OVERRIDEROOTKEY].val > -1) //FIXME: use flag instead of -1 { voice->root_pitch = voice->gen[GEN_OVERRIDEROOTKEY].val * 100.0f - voice->sample->pitchadj; } else { voice->root_pitch = voice->sample->origpitch * 100.0f - voice->sample->pitchadj; } x = (fluid_ct2hz_real(voice->root_pitch) * ((fluid_real_t) voice->output_rate / voice->sample->samplerate)); } else { if(voice->gen[GEN_OVERRIDEROOTKEY].val > -1) //FIXME: use flag instead of -1 { voice->root_pitch = voice->gen[GEN_OVERRIDEROOTKEY].val * 100.0f; } else { voice->root_pitch = 0; } x = fluid_ct2hz_real(voice->root_pitch); } /* voice->pitch depends on voice->root_pitch, so calculate voice->pitch now */ fluid_voice_calculate_gen_pitch(voice); UPDATE_RVOICE_R1(fluid_rvoice_set_root_pitch_hz, x); break; case GEN_FILTERFC: /* The resonance frequency is converted from absolute cents to * midicents .val and .mod are both used, this permits real-time * modulation. The allowed range is tested in the 'fluid_ct2hz' * function [PH,20021214] */ UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_filter, x); break; case GEN_FILTERQ: UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_q, &voice->rvoice->resonant_filter, x); break; /* same as the two above, only for the custom filter */ case GEN_CUSTOM_FILTERFC: UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_custom_filter, x); break; case GEN_CUSTOM_FILTERQ: UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_q, &voice->rvoice->resonant_custom_filter, x); break; case GEN_MODLFOTOPITCH: fluid_clip(x, -12000.f, 12000.f); UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_pitch, x); break; case GEN_MODLFOTOVOL: fluid_clip(x, -960.f, 960.f); UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_vol, x); break; case GEN_MODLFOTOFILTERFC: fluid_clip(x, -12000.f, 12000.f); UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_fc, x); break; case GEN_MODLFODELAY: fluid_clip(x, -12000.0f, 5000.0f); z = (unsigned int)(voice->output_rate * fluid_tc2sec_delay(x)); UPDATE_RVOICE_ENVLFO_I1(fluid_lfo_set_delay, modlfo, z); break; case GEN_MODLFOFREQ: /* - the frequency is converted into a delta value, per buffer of FLUID_BUFSIZE samples * - the delay into a sample delay */ fluid_clip(x, -16000.0f, 4500.0f); x = (4.0f * FLUID_BUFSIZE * fluid_act2hz(x) / voice->output_rate); UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, modlfo, x); break; case GEN_VIBLFOFREQ: /* vib lfo * * - the frequency is converted into a delta value, per buffer of FLUID_BUFSIZE samples * - the delay into a sample delay */ fluid_clip(x, -16000.0f, 4500.0f); x = 4.0f * FLUID_BUFSIZE * fluid_act2hz(x) / voice->output_rate; UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, viblfo, x); break; case GEN_VIBLFODELAY: fluid_clip(x, -12000.0f, 5000.0f); z = (unsigned int)(voice->output_rate * fluid_tc2sec_delay(x)); UPDATE_RVOICE_ENVLFO_I1(fluid_lfo_set_delay, viblfo, z); break; case GEN_VIBLFOTOPITCH: fluid_clip(x, -12000.f, 12000.f); UPDATE_RVOICE_R1(fluid_rvoice_set_viblfo_to_pitch, x); break; case GEN_KEYNUM: /* GEN_KEYNUM: SF2.01 page 46, item 46 * * If this generator is active, it forces the key number to its * value. Non-realtime controller. * * There is a flag, which should indicate, whether a generator is * enabled or not. But here we rely on the default value of -1. */ /* 2017-09-02: do not change the voice's key here, otherwise it will * never be released on a noteoff event */ #if 0 x = fluid_voice_gen_value(voice, GEN_KEYNUM); if(x >= 0) { voice->key = x; } #endif break; case GEN_VELOCITY: /* GEN_VELOCITY: SF2.01 page 46, item 47 * * If this generator is active, it forces the velocity to its * value. Non-realtime controller. * * There is a flag, which should indicate, whether a generator is * enabled or not. But here we rely on the default value of -1. */ /* 2017-09-02: do not change the voice's velocity here, use * fluid_voice_get_actual_velocity() to get the value of this generator * if active. */ #if 0 x = fluid_voice_gen_value(voice, GEN_VELOCITY); if(x > 0) { voice->vel = x; } #endif break; case GEN_MODENVTOPITCH: fluid_clip(x, -12000.f, 12000.f); UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_pitch, x); break; case GEN_MODENVTOFILTERFC: /* Range: SF2.01 section 8.1.3 # 1 * Motivation for range checking: * Filter is reported to make funny noises now and then */ fluid_clip(x, -12000.f, 12000.f); UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_fc, x); break; /* sample start and ends points * * Range checking is initiated via the * voice->check_sample_sanity flag, * because it is impossible to check here: * During the voice setup, all modulators are processed, while * the voice is inactive. Therefore, illegal settings may * occur during the setup (for example: First move the loop * end point ahead of the loop start point => invalid, then * move the loop start point forward => valid again. */ case GEN_STARTADDROFS: /* SF2.01 section 8.1.3 # 0 */ case GEN_STARTADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 4 */ if(voice->sample != NULL) { fluid_real_t start_fine = fluid_voice_gen_value(voice, GEN_STARTADDROFS); fluid_real_t start_coar = fluid_voice_gen_value(voice, GEN_STARTADDRCOARSEOFS); z = voice->sample->start + (int)start_fine + 32768 * (int)start_coar; UPDATE_RVOICE_I1(fluid_rvoice_set_start, z); } break; case GEN_ENDADDROFS: /* SF2.01 section 8.1.3 # 1 */ case GEN_ENDADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 12 */ if(voice->sample != NULL) { fluid_real_t end_fine = fluid_voice_gen_value(voice, GEN_ENDADDROFS); fluid_real_t end_coar = fluid_voice_gen_value(voice, GEN_ENDADDRCOARSEOFS); z = voice->sample->end + (int)end_fine + 32768 * (int)end_coar; UPDATE_RVOICE_I1(fluid_rvoice_set_end, z); } break; case GEN_STARTLOOPADDROFS: /* SF2.01 section 8.1.3 # 2 */ case GEN_STARTLOOPADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 45 */ if(voice->sample != NULL) { fluid_real_t lstart_fine = fluid_voice_gen_value(voice, GEN_STARTLOOPADDROFS); fluid_real_t lstart_coar = fluid_voice_gen_value(voice, GEN_STARTLOOPADDRCOARSEOFS); z = voice->sample->loopstart + (int)lstart_fine + 32768 * (int)lstart_coar; UPDATE_RVOICE_I1(fluid_rvoice_set_loopstart, z); } break; case GEN_ENDLOOPADDROFS: /* SF2.01 section 8.1.3 # 3 */ case GEN_ENDLOOPADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 50 */ if(voice->sample != NULL) { fluid_real_t lend_fine = fluid_voice_gen_value(voice, GEN_ENDLOOPADDROFS); fluid_real_t lend_coar = fluid_voice_gen_value(voice, GEN_ENDLOOPADDRCOARSEOFS); z = voice->sample->loopend + (int)lend_fine + 32768 * (int)lend_coar; UPDATE_RVOICE_I1(fluid_rvoice_set_loopend, z); } break; /* Conversion functions differ in range limit */ #define NUM_BUFFERS_DELAY(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_delay(_v) / FLUID_BUFSIZE) #define NUM_BUFFERS_ATTACK(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_attack(_v) / FLUID_BUFSIZE) #define NUM_BUFFERS_RELEASE(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_release(_v) / FLUID_BUFSIZE) /* volume envelope * * - delay and hold times are converted to absolute number of samples * - sustain is converted to its absolute value * - attack, decay and release are converted to their increment per sample */ case GEN_VOLENVDELAY: /* SF2.01 section 8.1.3 # 33 */ fluid_clip(x, -12000.0f, 5000.0f); count = NUM_BUFFERS_DELAY(x); fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVDELAY, count, 0.0f, 0.0f, -1.0f, 1.0f); break; case GEN_VOLENVATTACK: /* SF2.01 section 8.1.3 # 34 */ fluid_clip(x, -12000.0f, 8000.0f); count = 1 + NUM_BUFFERS_ATTACK(x); fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVATTACK, count, 1.0f, 1.0f / count, -1.0f, 1.0f); break; case GEN_VOLENVHOLD: /* SF2.01 section 8.1.3 # 35 */ case GEN_KEYTOVOLENVHOLD: /* SF2.01 section 8.1.3 # 39 */ count = calculate_hold_decay_buffers(voice, GEN_VOLENVHOLD, GEN_KEYTOVOLENVHOLD, 0); /* 0 means: hold */ fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVHOLD, count, 1.0f, 0.0f, -1.0f, 2.0f); break; case GEN_VOLENVDECAY: /* SF2.01 section 8.1.3 # 36 */ case GEN_VOLENVSUSTAIN: /* SF2.01 section 8.1.3 # 37 */ case GEN_KEYTOVOLENVDECAY: /* SF2.01 section 8.1.3 # 40 */ x = 1.0f - 0.001f * fluid_voice_gen_value(voice, GEN_VOLENVSUSTAIN); fluid_clip(x, 0.0f, 1.0f); count = calculate_hold_decay_buffers(voice, GEN_VOLENVDECAY, GEN_KEYTOVOLENVDECAY, 1); /* 1 for decay */ fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVDECAY, count, 1.0f, count ? -1.0f / count : 0.0f, x, 2.0f); break; case GEN_VOLENVRELEASE: /* SF2.01 section 8.1.3 # 38 */ fluid_clip(x, FLUID_MIN_VOLENVRELEASE, 8000.0f); count = 1 + NUM_BUFFERS_RELEASE(x); fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVRELEASE, count, 1.0f, -1.0f / count, 0.0f, 1.0f); break; /* Modulation envelope */ case GEN_MODENVDELAY: /* SF2.01 section 8.1.3 # 25 */ fluid_clip(x, -12000.0f, 5000.0f); fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVDELAY, NUM_BUFFERS_DELAY(x), 0.0f, 0.0f, -1.0f, 1.0f); break; case GEN_MODENVATTACK: /* SF2.01 section 8.1.3 # 26 */ fluid_clip(x, -12000.0f, 8000.0f); count = 1 + NUM_BUFFERS_ATTACK(x); fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVATTACK, count, 1.0f, 1.0f / count, -1.0f, 1.0f); break; case GEN_MODENVHOLD: /* SF2.01 section 8.1.3 # 27 */ case GEN_KEYTOMODENVHOLD: /* SF2.01 section 8.1.3 # 31 */ count = calculate_hold_decay_buffers(voice, GEN_MODENVHOLD, GEN_KEYTOMODENVHOLD, 0); /* 1 means: hold */ fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVHOLD, count, 1.0f, 0.0f, -1.0f, 2.0f); break; case GEN_MODENVDECAY: /* SF 2.01 section 8.1.3 # 28 */ case GEN_MODENVSUSTAIN: /* SF 2.01 section 8.1.3 # 29 */ case GEN_KEYTOMODENVDECAY: /* SF 2.01 section 8.1.3 # 32 */ count = calculate_hold_decay_buffers(voice, GEN_MODENVDECAY, GEN_KEYTOMODENVDECAY, 1); /* 1 for decay */ x = 1.0f - 0.001f * fluid_voice_gen_value(voice, GEN_MODENVSUSTAIN); fluid_clip(x, 0.0f, 1.0f); fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVDECAY, count, 1.0f, count ? -1.0f / count : 0.0f, x, 2.0f); break; case GEN_MODENVRELEASE: /* SF 2.01 section 8.1.3 # 30 */ fluid_clip(x, -12000.0f, 8000.0f); count = 1 + NUM_BUFFERS_RELEASE(x); fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVRELEASE, count, 1.0f, -1.0f / count, 0.0f, 2.0f); break; } /* switch gen */ } /** * Recalculate voice parameters for a given control. * @param voice the synthesis voice * @param cc flag to distinguish between a continuous control and a channel control (pitch bend, ...) * @param ctrl the control number: * when >=0, only modulators's destination having ctrl as source are updated. * when -1, all modulators's destination are updated (regardless of ctrl). * * In this implementation, I want to make sure that all controllers * are event based: the parameter values of the DSP algorithm should * only be updates when a controller event arrived and not at every * iteration of the audio cycle (which would probably be feasible if * the synth was made in silicon). * * The update is done in two steps: * * - step 1: first, we look for all the modulators that have the changed * controller as a source. This will yield a generator that will be changed * because of the controller event. * * - step 2: For this generator, calculate its new value. This is the * sum of its original value plus the values of all the attached modulators. * The generator flag is set to indicate the parameters must be updated. * This avoid the risk to call 'fluid_voice_update_param' several * times for the same generator if several modulators have that generator as * destination. So every changed generators are updated only once. */ /* bit table for each generator being updated. The bits are packed in variables Each variable have NBR_BIT_BY_VAR bits represented by NBR_BIT_BY_VAR_LN2. The size of the table is the number of variables: SIZE_UPDATED_GEN_BIT. Note: In this implementation NBR_BIT_BY_VAR_LN2 is set to 5 (convenient for 32 bits cpu) but this could be set to 6 for 64 bits cpu. */ #define NBR_BIT_BY_VAR_LN2 5 /* for 32 bits variables */ #define NBR_BIT_BY_VAR (1 << NBR_BIT_BY_VAR_LN2) #define NBR_BIT_BY_VAR_ANDMASK (NBR_BIT_BY_VAR - 1) #define SIZE_UPDATED_GEN_BIT ((GEN_LAST + NBR_BIT_BY_VAR_ANDMASK) / NBR_BIT_BY_VAR) #define is_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] & (1 << (gen & NBR_BIT_BY_VAR_ANDMASK))) #define set_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] |= (1 << (gen & NBR_BIT_BY_VAR_ANDMASK))) int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl) { int i, k; fluid_mod_t *mod; uint32_t gen; fluid_real_t modval; /* Clears registered bits table of updated generators */ uint32_t updated_gen_bit[SIZE_UPDATED_GEN_BIT] = {0}; /* printf("Chan=%d, CC=%d, Src=%d, Val=%d\n", voice->channel->channum, cc, ctrl, val); */ for(i = 0; i < voice->mod_count; i++) { mod = &voice->mod[i]; /* step 1: find all the modulators that have the changed controller as input source. When ctrl is -1 all modulators destination are updated */ if(ctrl < 0 || fluid_mod_has_source(mod, cc, ctrl)) { gen = fluid_mod_get_dest(mod); /* Skip if this generator has already been updated */ if(!is_gen_updated(updated_gen_bit, gen)) { modval = 0.0; /* step 2: for every attached modulator, calculate the modulation * value for the generator gen */ for(k = 0; k < voice->mod_count; k++) { if(fluid_mod_has_dest(&voice->mod[k], gen)) { modval += fluid_mod_get_value(&voice->mod[k], voice); } } fluid_gen_set_mod(&voice->gen[gen], modval); /* now recalculate the parameter values that are derived from the generator */ fluid_voice_update_param(voice, gen); /* set the bit that indicates this generator is updated */ set_gen_updated(updated_gen_bit, gen); } } } return FLUID_OK; } /** * Update all the modulators. This function is called after a * ALL_CTRL_OFF MIDI message has been received (CC 121). * * All destination of all modulators must be updated. */ int fluid_voice_modulate_all(fluid_voice_t *voice) { return fluid_voice_modulate(voice, 0, -1); } /** legato update functions --------------------------------------------------*/ /* Updates voice portamento parameters * * @voice voice the synthesis voice * @fromkey the beginning pitch of portamento. * @tokey the ending pitch of portamento. * * The function calculates pitch offset and increment, then these parameters * are send to the dsp. */ void fluid_voice_update_portamento(fluid_voice_t *voice, int fromkey, int tokey) { fluid_channel_t *channel = voice->channel; /* calculates pitch offset */ fluid_real_t PitchBeg = fluid_voice_calculate_pitch(voice, fromkey); fluid_real_t PitchEnd = fluid_voice_calculate_pitch(voice, tokey); fluid_real_t pitchoffset = PitchBeg - PitchEnd; /* Calculates increment countinc */ /* Increment is function of PortamentoTime (ms)*/ unsigned int countinc = (unsigned int)(((fluid_real_t)voice->output_rate * 0.001f * (fluid_real_t)fluid_channel_portamentotime(channel)) / (fluid_real_t)FLUID_BUFSIZE + 0.5f); /* Send portamento parameters to the voice dsp */ UPDATE_RVOICE_GENERIC_IR(fluid_rvoice_set_portamento, voice->rvoice, countinc, pitchoffset); } /*---------------------------------------------------------------*/ /*legato mode 1: multi_retrigger * * Modulates all generators dependent of key,vel. * Forces the voice envelopes in the attack section (legato mode 1). * * @voice voice the synthesis voice * @tokey the new key to be applied to this voice. * @vel the new velocity to be applied to this voice. */ void fluid_voice_update_multi_retrigger_attack(fluid_voice_t *voice, int tokey, int vel) { voice->key = tokey; /* new note */ voice->vel = vel; /* new velocity */ /* Updates generators dependent of velocity */ /* Modulates GEN_ATTENUATION (and others ) before calling fluid_rvoice_multi_retrigger_attack().*/ fluid_voice_modulate(voice, FALSE, FLUID_MOD_VELOCITY); /* Updates generator dependent of voice->key */ fluid_voice_update_param(voice, GEN_KEYTOMODENVHOLD); fluid_voice_update_param(voice, GEN_KEYTOMODENVDECAY); fluid_voice_update_param(voice, GEN_KEYTOVOLENVHOLD); fluid_voice_update_param(voice, GEN_KEYTOVOLENVDECAY); /* Updates pitch generator */ fluid_voice_calculate_gen_pitch(voice); fluid_voice_update_param(voice, GEN_PITCH); /* updates adsr generator */ UPDATE_RVOICE0(fluid_rvoice_multi_retrigger_attack); } /** end of legato update functions */ /* Force the voice into release stage. Useful anywhere a voice needs to be damped even if pedals (sustain sostenuto) are depressed. See fluid_synth_damp_voices_by_sustain_LOCAL(), fluid_synth_damp_voices_by_sostenuto_LOCAL, fluid_voice_noteoff(). */ void fluid_voice_release(fluid_voice_t *voice) { unsigned int at_tick = fluid_channel_get_min_note_length_ticks(voice->channel); UPDATE_RVOICE_I1(fluid_rvoice_noteoff, at_tick); voice->has_noteoff = 1; // voice is marked as noteoff occurred } /* * fluid_voice_noteoff * * Sending a noteoff event will advance the envelopes to section 5 (release). * The function is convenient for polyphonic or monophonic note */ void fluid_voice_noteoff(fluid_voice_t *voice) { fluid_channel_t *channel; fluid_profile(FLUID_PROF_VOICE_NOTE, voice->ref, 0, 0); channel = voice->channel; /* Sustain a note under Sostenuto pedal */ if(fluid_channel_sostenuto(channel) && channel->sostenuto_orderid > voice->id) { // Sostenuto depressed after note voice->status = FLUID_VOICE_HELD_BY_SOSTENUTO; } /* Or sustain a note under Sustain pedal */ else if(fluid_channel_sustained(channel)) { voice->status = FLUID_VOICE_SUSTAINED; } /* Or force the voice to release stage */ else { fluid_voice_release(voice); } } /* * fluid_voice_kill_excl * * Percussion sounds can be mutually exclusive: for example, a 'closed * hihat' sound will terminate an 'open hihat' sound ringing at the * same time. This behaviour is modeled using 'exclusive classes', * turning on a voice with an exclusive class other than 0 will kill * all other voices having that exclusive class within the same preset * or channel. fluid_voice_kill_excl gets called, when 'voice' is to * be killed for that reason. */ int fluid_voice_kill_excl(fluid_voice_t *voice) { unsigned int at_tick; if(!fluid_voice_is_playing(voice)) { return FLUID_OK; } /* Turn off the exclusive class information for this voice, so that it doesn't get killed twice */ fluid_voice_gen_set(voice, GEN_EXCLUSIVECLASS, 0); /* Speed up the volume envelope */ /* The value was found through listening tests with hi-hat samples. */ fluid_voice_gen_set(voice, GEN_VOLENVRELEASE, -200); fluid_voice_update_param(voice, GEN_VOLENVRELEASE); /* Speed up the modulation envelope */ fluid_voice_gen_set(voice, GEN_MODENVRELEASE, -200); fluid_voice_update_param(voice, GEN_MODENVRELEASE); at_tick = fluid_channel_get_min_note_length_ticks(voice->channel); UPDATE_RVOICE_I1(fluid_rvoice_noteoff, at_tick); return FLUID_OK; } /* * Called by fluid_synth when the overflow rvoice can be reclaimed. */ void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice) { voice->can_access_overflow_rvoice = 1; fluid_voice_sample_unref(&voice->overflow_rvoice->dsp.sample); } /* * fluid_voice_off * * Force the voice into finished stage. Useful anywhere a voice * needs to be cancelled from MIDI API. */ void fluid_voice_off(fluid_voice_t *voice) { UPDATE_RVOICE0(fluid_rvoice_voiceoff); /* request to finish the voice */ } /* * fluid_voice_stop * * Purpose: * Turns off a voice, meaning that it is not processed anymore by the * DSP loop, i.e. contrary part to fluid_voice_start(). */ void fluid_voice_stop(fluid_voice_t *voice) { fluid_profile(FLUID_PROF_VOICE_RELEASE, voice->ref, 0, 0); voice->chan = NO_CHANNEL; if(voice->can_access_rvoice) { fluid_voice_sample_unref(&voice->rvoice->dsp.sample); } voice->status = FLUID_VOICE_OFF; voice->has_noteoff = 1; /* Decrement the reference count of the sample. */ fluid_voice_sample_unref(&voice->sample); /* Decrement voice count */ voice->channel->synth->active_voice_count--; } /** * Adds a modulator to the voice if the modulator has valid sources. * @param voice Voice instance. * @param mod Modulator info (copied). * @param mode Determines how to handle an existing identical modulator. * #FLUID_VOICE_ADD to add (offset) the modulator amounts, * #FLUID_VOICE_OVERWRITE to replace the modulator, * #FLUID_VOICE_DEFAULT when adding a default modulator - no duplicate should * exist so don't check. */ void fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode) { /* Ignore the modulator if its sources inputs are invalid */ if(fluid_mod_check_sources(mod, "api fluid_voice_add_mod mod")) { fluid_voice_add_mod_local(voice, mod, mode, FLUID_NUM_MOD); } } /** * Adds a modulator to the voice. * local version of fluid_voice_add_mod function. Called at noteon time. * @param voice, mod, mode, same as for fluid_voice_add_mod() (see above). * @param check_limit_count is the modulator number limit to handle with existing * identical modulator(i.e mode FLUID_VOICE_OVERWRITE, FLUID_VOICE_ADD). * - When FLUID_NUM_MOD, all the voices modulators (since the previous call) * are checked for identity. * - When check_count_limit is below the actual number of voices modulators * (voice->mod_count), this will restrict identity check to this number, * This is useful when we know by advance that there is no duplicate with * modulators at index above this limit. This avoid wasting cpu cycles at noteon. */ void fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count) { int i; /* check_limit_count cannot be above voice->mod_count */ if(check_limit_count > voice->mod_count) { check_limit_count = voice->mod_count; } if(mode == FLUID_VOICE_ADD) { /* if identical modulator exists, add them */ for(i = 0; i < check_limit_count; i++) { if(fluid_mod_test_identity(&voice->mod[i], mod)) { // printf("Adding modulator...\n"); voice->mod[i].amount += mod->amount; return; } } } else if(mode == FLUID_VOICE_OVERWRITE) { /* if identical modulator exists, replace it (only the amount has to be changed) */ for(i = 0; i < check_limit_count; i++) { if(fluid_mod_test_identity(&voice->mod[i], mod)) { // printf("Replacing modulator...amount is %f\n",mod->amount); voice->mod[i].amount = mod->amount; return; } } } /* Add a new modulator (No existing modulator to add / overwrite). Also, default modulators (FLUID_VOICE_DEFAULT) are added without checking, if the same modulator already exists. */ if(voice->mod_count < FLUID_NUM_MOD) { fluid_mod_clone(&voice->mod[voice->mod_count++], mod); } else { FLUID_LOG(FLUID_WARN, "Voice %i has more modulators than supported, ignoring.", voice->id); } } /** * Get the unique ID of the noteon-event. * @param voice Voice instance * @return Note on unique ID * * A SoundFont loader may store the voice processes it has created for * real-time control during the operation of a voice (for example: parameter * changes in SoundFont editor). The synth uses a pool of voices, which are * 'recycled' and never deallocated. * * Before modifying an existing voice, check * - that its state is still 'playing' * - that the ID is still the same * * Otherwise the voice has finished playing. */ unsigned int fluid_voice_get_id(const fluid_voice_t *voice) { return voice->id; } /** * Check if a voice is producing sound. This is also true after a voice received a noteoff as it may be playing in release phase. * @param voice Voice instance * @return TRUE if playing, FALSE otherwise */ int fluid_voice_is_playing(const fluid_voice_t *voice) { return (voice->status == FLUID_VOICE_ON) || fluid_voice_is_sustained(voice) || fluid_voice_is_sostenuto(voice); } /** * Check if a voice is ON. A voice is ON, if it has not yet received a noteoff event. * @param voice Voice instance * @return TRUE if on, FALSE otherwise * @since 1.1.7 */ int fluid_voice_is_on(const fluid_voice_t *voice) { return (voice->status == FLUID_VOICE_ON && !voice->has_noteoff); } /** * Check if a voice keeps playing after it has received a noteoff due to being held by sustain. * @param voice Voice instance * @return TRUE if sustained, FALSE otherwise * @since 1.1.7 */ int fluid_voice_is_sustained(const fluid_voice_t *voice) { return (voice->status == FLUID_VOICE_SUSTAINED); } /** * Check if a voice keeps playing after it has received a noteoff due to being held by sostenuto. * @param voice Voice instance * @return TRUE if sostenuto, FALSE otherwise * @since 1.1.7 */ int fluid_voice_is_sostenuto(const fluid_voice_t *voice) { return (voice->status == FLUID_VOICE_HELD_BY_SOSTENUTO); } /** * If the voice is playing, gets the midi channel the voice is playing on. Else the result is undefined. * @param voice Voice instance * @return The channel assigned to this voice * @since 1.1.7 */ int fluid_voice_get_channel(const fluid_voice_t *voice) { return voice->chan; } /** * If the voice is playing, gets the midi key the voice is actually playing at. Else the result is undefined. * If the voice was started from an instrument which uses a fixed key generator, it returns that. * Else returns the same as \c fluid_voice_get_key. * @param voice Voice instance * @return The midi key this voice is playing at * @since 1.1.7 */ int fluid_voice_get_actual_key(const fluid_voice_t *voice) { fluid_real_t x = fluid_voice_gen_value(voice, GEN_KEYNUM); if(x >= 0) { return (int)x; } else { return fluid_voice_get_key(voice); } } /** * If the voice is playing, gets the midi key from the noteon event, by which the voice was initially turned on with. * Else the result is undefined. * @param voice Voice instance * @return The midi key of the noteon event that originally turned on this voice * @since 1.1.7 */ int fluid_voice_get_key(const fluid_voice_t *voice) { return voice->key; } /** * If the voice is playing, gets the midi velocity the voice is actually playing at. Else the result is undefined. * If the voice was started from an instrument which uses a fixed velocity generator, it returns that. * Else returns the same as \c fluid_voice_get_velocity. * @param voice Voice instance * @return The midi velocity this voice is playing at * @since 1.1.7 */ int fluid_voice_get_actual_velocity(const fluid_voice_t *voice) { fluid_real_t x = fluid_voice_gen_value(voice, GEN_VELOCITY); if(x > 0) { return (int)x; } else { return fluid_voice_get_velocity(voice); } } /** * If the voice is playing, gets the midi velocity from the noteon event, by which the voice was initially * turned on with. Else the result is undefined. * @param voice Voice instance * @return The midi velocity which originally turned on this voice * @since 1.1.7 */ int fluid_voice_get_velocity(const fluid_voice_t *voice) { return voice->vel; } /* * fluid_voice_get_lower_boundary_for_attenuation * * Purpose: * * A lower boundary for the attenuation (as in 'the minimum * attenuation of this voice, with volume pedals, modulators * etc. resulting in minimum attenuation, cannot fall below x cB) is * calculated. This has to be called during fluid_voice_start, after * all modulators have been run on the voice once. Also, * voice->attenuation has to be initialized. * (see fluid_voice_calculate_runtime_synthesis_parameters()) */ static fluid_real_t fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice) { int i; fluid_mod_t *mod; fluid_real_t possible_att_reduction_cB = 0; fluid_real_t lower_bound; for(i = 0; i < voice->mod_count; i++) { mod = &voice->mod[i]; /* Modulator has attenuation as target and can change over time? */ if((mod->dest == GEN_ATTENUATION) && ((mod->flags1 & FLUID_MOD_CC) || (mod->flags2 & FLUID_MOD_CC) || (mod->src1 == FLUID_MOD_CHANNELPRESSURE) || (mod->src1 == FLUID_MOD_KEYPRESSURE) || (mod->src1 == FLUID_MOD_PITCHWHEEL) || (mod->src2 == FLUID_MOD_CHANNELPRESSURE) || (mod->src2 == FLUID_MOD_KEYPRESSURE) || (mod->src2 == FLUID_MOD_PITCHWHEEL))) { fluid_real_t current_val = fluid_mod_get_value(mod, voice); /* min_val is the possible minimum value for this modulator. it depends of 3 things : 1)the minimum values of src1,src2 (i.e -1 if mapping is bipolar or 0 if mapping is unipolar). 2)the sign of amount. 3)absolute value of amount. When at least one source mapping is bipolar: min_val is -|amount| regardless the sign of amount. When both sources mapping are unipolar: min_val is -|amount|, if amount is negative. min_val is 0, if amount is positive */ fluid_real_t min_val = fabs(mod->amount); /* Can this modulator produce a negative contribution? */ if((mod->flags1 & FLUID_MOD_BIPOLAR) || (mod->flags2 & FLUID_MOD_BIPOLAR) || (mod->amount < 0)) { min_val = -min_val; /* min_val = - |amount|*/ } else { /* No negative value possible. But still, the minimum contribution is 0. */ min_val = 0; } /* For example: * - current_val=100 * - min_val=-4000 * - possible reduction contribution of this modulator = current_val - min_val = 4100 */ if(current_val > min_val) { possible_att_reduction_cB += (current_val - min_val); } } } lower_bound = voice->attenuation - possible_att_reduction_cB; /* SF2.01 specs do not allow negative attenuation */ if(lower_bound < 0) { lower_bound = 0; } return lower_bound; } int fluid_voice_set_param(fluid_voice_t *voice, int gen, fluid_real_t nrpn_value) { voice->gen[gen].nrpn = nrpn_value; voice->gen[gen].flags = GEN_SET; fluid_voice_update_param(voice, gen); return FLUID_OK; } int fluid_voice_set_gain(fluid_voice_t *voice, fluid_real_t gain) { fluid_real_t left, right, reverb, chorus; /* avoid division by zero*/ if(gain < 0.0000001f) { gain = 0.0000001f; } voice->synth_gain = gain; left = fluid_voice_calculate_gain_amplitude(voice, fluid_pan(voice->pan, 1) * fluid_balance(voice->balance, 1)); right = fluid_voice_calculate_gain_amplitude(voice, fluid_pan(voice->pan, 0) * fluid_balance(voice->balance, 0)); reverb = fluid_voice_calculate_gain_amplitude(voice, voice->reverb_send); chorus = fluid_voice_calculate_gain_amplitude(voice, voice->chorus_send); UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, gain); UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 0, left); UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 1, right); UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 2, reverb); UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 3, chorus); return FLUID_OK; } /* - Scan the loop * - determine the peak level * - Calculate, what factor will make the loop inaudible * - Store in sample */ /** * Calculate the peak volume of a sample for voice off optimization. * @param s Sample to optimize * @return #FLUID_OK on success, #FLUID_FAILED otherwise * * If the peak volume during the loop is known, then the voice can * be released earlier during the release phase. Otherwise, the * voice will operate (inaudibly), until the envelope is at the * nominal turnoff point. So it's a good idea to call * fluid_voice_optimize_sample() on each sample once. */ int fluid_voice_optimize_sample(fluid_sample_t *s) { int32_t peak_max = 0; int32_t peak_min = 0; int32_t peak; fluid_real_t normalized_amplitude_during_loop; double result; unsigned int i; /* ignore disabled samples */ if(s->start == s->end) { return (FLUID_OK); } if(!s->amplitude_that_reaches_noise_floor_is_valid) /* Only once */ { /* Scan the loop */ for(i = s->loopstart; i < s->loopend; i++) { int32_t val = fluid_rvoice_get_sample(s->data, s->data24, i); if(val > peak_max) { peak_max = val; } else if(val < peak_min) { peak_min = val; } } /* Determine the peak level */ if(peak_max > -peak_min) { peak = peak_max; } else { peak = -peak_min; } if(peak == 0) { /* Avoid division by zero */ peak = 1; } /* Calculate what factor will make the loop inaudible * For example: Take a peak of 3277 (10 % of 32768). The * normalized amplitude is 0.1 (10 % of 32768). An amplitude * factor of 0.0001 (as opposed to the default 0.00001) will * drop this sample to the noise floor. */ /* 16 bits => 96+4=100 dB dynamic range => 0.00001 */ normalized_amplitude_during_loop = ((fluid_real_t)peak) / (INT24_MAX * 1.0f); result = FLUID_NOISE_FLOOR / normalized_amplitude_during_loop; /* Store in sample */ s->amplitude_that_reaches_noise_floor = (double)result; s->amplitude_that_reaches_noise_floor_is_valid = 1; #if 0 printf("Sample peak detection: factor %f\n", (double)result); #endif } return FLUID_OK; } float fluid_voice_get_overflow_prio(fluid_voice_t *voice, fluid_overflow_prio_t *score, unsigned int cur_time) { float this_voice_prio = 0; int channel; /* Are we already overflowing? */ if(!voice->can_access_overflow_rvoice) { return OVERFLOW_PRIO_CANNOT_KILL; } /* Is this voice on the drum channel? * Then it is very important. * Also skip the released and sustained scores. */ if(voice->channel->channel_type == CHANNEL_TYPE_DRUM) { this_voice_prio += score->percussion; } else if(voice->has_noteoff) { /* Noteoff has */ this_voice_prio += score->released; } else if(fluid_voice_is_sustained(voice) || fluid_voice_is_sostenuto(voice)) { /* This voice is still active, since the sustain pedal is held down. * Consider it less important than non-sustained channels. * This decision is somehow subjective. But usually the sustain pedal * is used to play 'more-voices-than-fingers', so it shouldn't hurt * if we kill one voice. */ this_voice_prio += score->sustained; } /* We are not enthusiastic about releasing voices, which have just been started. * Otherwise hitting a chord may result in killing notes belonging to that very same * chord. So give newer voices a higher score. */ if(score->age) { cur_time -= voice->start_time; if(cur_time < 1) { cur_time = 1; // Avoid div by zero } this_voice_prio += (score->age * voice->output_rate) / cur_time; } /* take a rough estimate of loudness into account. Louder voices are more important. */ if(score->volume) { fluid_real_t a = voice->attenuation; if(voice->has_noteoff) { // FIXME: Should take into account where on the envelope we are...? } if(a < 0.1f) { a = 0.1f; // Avoid div by zero } this_voice_prio += score->volume / a; } /* Check if this voice is on an important channel. If so, then add the * score for important channels */ channel = fluid_voice_get_channel(voice); if(channel < score->num_important_channels && score->important_channels[channel]) { this_voice_prio += score->important; } return this_voice_prio; } void fluid_voice_set_custom_filter(fluid_voice_t *voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags) { UPDATE_RVOICE_GENERIC_I2(fluid_iir_filter_init, &voice->rvoice->resonant_custom_filter, type, flags); } fluidsynth-2.1.1/src/synth/fluid_voice.h000066400000000000000000000156341362231004000203010ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_VOICE_H #define _FLUID_VOICE_H #include "fluid_phase.h" #include "fluid_gen.h" #include "fluid_mod.h" #include "fluid_iir_filter.h" #include "fluid_adsr_env.h" #include "fluid_lfo.h" #include "fluid_rvoice.h" #include "fluid_rvoice_event.h" #define NO_CHANNEL 0xff typedef struct _fluid_overflow_prio_t fluid_overflow_prio_t; struct _fluid_overflow_prio_t { float percussion; /**< Is this voice on the drum channel? Then add this score */ float released; /**< Is this voice in release stage? Then add this score (usually negative) */ float sustained; /**< Is this voice sustained? Then add this score (usually negative) */ float volume; /**< Multiply current (or future) volume (a value between 0 and 1) */ float age; /**< This score will be divided by the number of seconds the voice has lasted */ float important; /**< This score will be added to all important channels */ char *important_channels; /**< "important" flags indexed by MIDI channel number */ int num_important_channels; /**< Number of elements in the important_channels array */ }; enum fluid_voice_status { FLUID_VOICE_CLEAN, FLUID_VOICE_ON, FLUID_VOICE_SUSTAINED, /* Sustained by Sustain pedal */ FLUID_VOICE_HELD_BY_SOSTENUTO, /* Sustained by Sostenuto pedal */ FLUID_VOICE_OFF }; /* * fluid_voice_t */ struct _fluid_voice_t { unsigned int id; /* the id is incremented for every new noteon. it's used for noteoff's */ unsigned char status; unsigned char chan; /* the channel number, quick access for channel messages */ unsigned char key; /* the key of the noteon event, quick access for noteoff */ unsigned char vel; /* the velocity of the noteon event */ fluid_channel_t *channel; fluid_rvoice_eventhandler_t *eventhandler; fluid_zone_range_t *zone_range; /* instrument zone range*/ fluid_sample_t *sample; /* Pointer to sample (dupe in rvoice) */ unsigned int start_time; int mod_count; fluid_mod_t mod[FLUID_NUM_MOD]; fluid_gen_t gen[GEN_LAST]; /* basic parameters */ fluid_real_t output_rate; /* the sample rate of the synthesizer (dupe in rvoice) */ /* basic parameters */ fluid_real_t pitch; /* the pitch in midicents (dupe in rvoice) */ fluid_real_t attenuation; /* the attenuation in centibels (dupe in rvoice) */ fluid_real_t root_pitch; /* master gain (dupe in rvoice) */ fluid_real_t synth_gain; /* pan */ fluid_real_t pan; /* balance */ fluid_real_t balance; /* reverb */ fluid_real_t reverb_send; /* chorus */ fluid_real_t chorus_send; /* rvoice control */ fluid_rvoice_t *rvoice; fluid_rvoice_t *overflow_rvoice; /* Used temporarily and only in overflow situations */ char can_access_rvoice; /* False if rvoice is being rendered in separate thread */ char can_access_overflow_rvoice; /* False if overflow_rvoice is being rendered in separate thread */ char has_noteoff; /* Flag set when noteoff has been sent */ #ifdef WITH_PROFILING /* for debugging */ double ref; #endif }; fluid_voice_t *new_fluid_voice(fluid_rvoice_eventhandler_t *handler, fluid_real_t output_rate); void delete_fluid_voice(fluid_voice_t *voice); void fluid_voice_start(fluid_voice_t *voice); void fluid_voice_calculate_gen_pitch(fluid_voice_t *voice); int fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample, fluid_zone_range_t *inst_zone_range, fluid_channel_t *channel, int key, int vel, unsigned int id, unsigned int time, fluid_real_t gain); int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl); int fluid_voice_modulate_all(fluid_voice_t *voice); /** Set the NRPN value of a generator. */ int fluid_voice_set_param(fluid_voice_t *voice, int gen, fluid_real_t value); /** Set the gain. */ int fluid_voice_set_gain(fluid_voice_t *voice, fluid_real_t gain); void fluid_voice_set_output_rate(fluid_voice_t *voice, fluid_real_t value); /** Update all the synthesis parameters, which depend on generator 'gen'. This is only necessary after changing a generator of an already operating voice. Most applications will not need this function.*/ void fluid_voice_update_param(fluid_voice_t *voice, int gen); /** legato modes */ /* force in the attack section for legato mode multi_retrigger: 1 */ void fluid_voice_update_multi_retrigger_attack(fluid_voice_t *voice, int tokey, int vel); /* Update portamento parameter */ void fluid_voice_update_portamento(fluid_voice_t *voice, int fromkey, int tokey); void fluid_voice_release(fluid_voice_t *voice); void fluid_voice_noteoff(fluid_voice_t *voice); void fluid_voice_off(fluid_voice_t *voice); void fluid_voice_stop(fluid_voice_t *voice); void fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count); void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice); int fluid_voice_kill_excl(fluid_voice_t *voice); float fluid_voice_get_overflow_prio(fluid_voice_t *voice, fluid_overflow_prio_t *score, unsigned int cur_time); #define OVERFLOW_PRIO_CANNOT_KILL 999999. /** * Locks the rvoice for rendering, so it can't be modified directly */ static FLUID_INLINE void fluid_voice_lock_rvoice(fluid_voice_t *voice) { voice->can_access_rvoice = 0; } /** * Unlocks the rvoice for rendering, so it can be modified directly */ static FLUID_INLINE void fluid_voice_unlock_rvoice(fluid_voice_t *voice) { voice->can_access_rvoice = 1; } #define _AVAILABLE(voice) ((voice)->can_access_rvoice && \ (((voice)->status == FLUID_VOICE_CLEAN) || ((voice)->status == FLUID_VOICE_OFF))) //#define _RELEASED(voice) ((voice)->chan == NO_CHANNEL) #define _SAMPLEMODE(voice) ((int)(voice)->gen[GEN_SAMPLEMODE].val) fluid_real_t fluid_voice_gen_value(const fluid_voice_t *voice, int num); void fluid_voice_set_custom_filter(fluid_voice_t *voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags); #endif /* _FLUID_VOICE_H */ fluidsynth-2.1.1/src/utils/000077500000000000000000000000001362231004000156225ustar00rootroot00000000000000fluidsynth-2.1.1/src/utils/fluid_conv.c000066400000000000000000000163061362231004000201240ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_conv.h" #include "fluid_sys.h" #include "fluid_conv_tables.c" /* * Converts absolute cents to Hertz * * As per sfspec section 9.3: * * ABSOLUTE CENTS - An absolute logarithmic measure of frequency based on a * reference of MIDI key number scaled by 100. * A cent is 1/1200 of an octave [which is the twelve hundredth root of two], * and value 6900 is 440 Hz (A-440). * * Implemented below basically is the following: * 440 * 2^((cents-6900)/1200) * = 440 * 2^((int)((cents-6900)/1200)) * 2^(((int)cents-6900)%1200)) * = 2^((int)((cents-6900)/1200)) * (440 * 2^(((int)cents-6900)%1200))) * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * This second factor is stored in the lookup table. * * The first factor can be implemented with a fast shift when the exponent * is always an int. This is the case when using 440/2^6 Hz rather than 440Hz * reference. */ fluid_real_t fluid_ct2hz_real(fluid_real_t cents) { if(FLUID_UNLIKELY(cents < 0)) { return (fluid_real_t) 1.0; } else { unsigned int mult, fac, rem; unsigned int icents = (unsigned int)cents; icents += 300u; // don't use stdlib div() here, it turned out have poor performance fac = icents / 1200u; rem = icents % 1200u; // Think of "mult" as the factor that we multiply (440/2^6)Hz with, // or in other words mult is the "first factor" of the above // functions comment. // // Assuming sizeof(uint)==4 this will give us a maximum range of // 32 * 1200cents - 300cents == 38100 cents == 29,527,900,160 Hz // which is much more than ever needed. For bigger values, just // safely wrap around (the & is just a replacement for the quick // modulo operation % 32). mult = 1u << (fac & (sizeof(mult)*8u - 1u)); // don't use ldexp() either (poor performance) return mult * fluid_ct2hz_tab[rem]; } } /* * fluid_ct2hz */ fluid_real_t fluid_ct2hz(fluid_real_t cents) { /* Filter fc limit: SF2.01 page 48 # 8 */ if(cents >= 13500) { cents = 13500; /* 20 kHz */ } else if(cents < 1500) { cents = 1500; /* 20 Hz */ } return fluid_ct2hz_real(cents); } /* * fluid_cb2amp * * in: a value between 0 and 1440, 0 is no attenuation * out: a value between 1 and 0 */ fluid_real_t fluid_cb2amp(fluid_real_t cb) { /* * cb: an attenuation in 'centibels' (1/10 dB) * SF2.01 page 49 # 48 limits it to 144 dB. * 96 dB is reasonable for 16 bit systems, 144 would make sense for 24 bit. */ /* minimum attenuation: 0 dB */ if(cb < 0) { return 1.0; } if(cb >= FLUID_CB_AMP_SIZE) { return 0.0; } return fluid_cb2amp_tab[(int) cb]; } /* * fluid_tc2sec_delay */ fluid_real_t fluid_tc2sec_delay(fluid_real_t tc) { /* SF2.01 section 8.1.2 items 21, 23, 25, 33 * SF2.01 section 8.1.3 items 21, 23, 25, 33 * * The most negative number indicates a delay of 0. Range is limited * from -12000 to 5000 */ if(tc <= -32768.0f) { return (fluid_real_t) 0.0f; }; if(tc < -12000.f) { tc = (fluid_real_t) -12000.0f; } if(tc > 5000.0f) { tc = (fluid_real_t) 5000.0f; } return FLUID_POW(2.f, tc / 1200.f); } /* * fluid_tc2sec_attack */ fluid_real_t fluid_tc2sec_attack(fluid_real_t tc) { /* SF2.01 section 8.1.2 items 26, 34 * SF2.01 section 8.1.3 items 26, 34 * The most negative number indicates a delay of 0 * Range is limited from -12000 to 8000 */ if(tc <= -32768.f) { return (fluid_real_t) 0.f; }; if(tc < -12000.f) { tc = (fluid_real_t) -12000.f; }; if(tc > 8000.f) { tc = (fluid_real_t) 8000.f; }; return FLUID_POW(2.f, tc / 1200.f); } /* * fluid_tc2sec */ fluid_real_t fluid_tc2sec(fluid_real_t tc) { /* No range checking here! */ return FLUID_POW(2.f, tc / 1200.f); } /* * fluid_tc2sec_release */ fluid_real_t fluid_tc2sec_release(fluid_real_t tc) { /* SF2.01 section 8.1.2 items 30, 38 * SF2.01 section 8.1.3 items 30, 38 * No 'most negative number' rule here! * Range is limited from -12000 to 8000 */ if(tc <= -32768.f) { return (fluid_real_t) 0.f; }; if(tc < -12000.f) { tc = (fluid_real_t) -12000.f; }; if(tc > 8000.f) { tc = (fluid_real_t) 8000.f; }; return FLUID_POW(2.f, tc / 1200.f); } /* * fluid_act2hz * * Convert from absolute cents to Hertz * * The inverse operation, converting from Hertz to cents, was unused and implemented as * fluid_hz2ct(fluid_real_t f) { return 6900.f + (1200.f / FLUID_M_LN2) * FLUID_LOGF(f / 440.0f)); } */ fluid_real_t fluid_act2hz(fluid_real_t c) { return 8.176f * FLUID_POW(2.f, c / 1200.f); } /* * fluid_pan */ fluid_real_t fluid_pan(fluid_real_t c, int left) { if(left) { c = -c; } if(c <= -500.f) { return (fluid_real_t) 0.f; } else if(c >= 500.f) { return (fluid_real_t) 1.f; } else { return fluid_pan_tab[(int)(c) + 500]; } } /* * Return the amount of attenuation based on the balance for the specified * channel. If balance is negative (turned toward left channel, only the right * channel is attenuated. If balance is positive, only the left channel is * attenuated. * * @params balance left/right balance, range [-960;960] in absolute centibels * @return amount of attenuation [0.0;1.0] */ fluid_real_t fluid_balance(fluid_real_t balance, int left) { /* This is the most common case */ if(balance == 0.f) { return 1.0f; } if((left && balance < 0.f) || (!left && balance > 0.f)) { return 1.0f; } if(balance < 0.f) { balance = -balance; } return fluid_cb2amp(balance); } /* * fluid_concave */ fluid_real_t fluid_concave(fluid_real_t val) { if(val < 0.f) { return 0.f; } else if(val >= (fluid_real_t)FLUID_VEL_CB_SIZE) { return 1.f; } return fluid_concave_tab[(int) val]; } /* * fluid_convex */ fluid_real_t fluid_convex(fluid_real_t val) { if(val < 0.f) { return 0.f; } else if(val >= (fluid_real_t)FLUID_VEL_CB_SIZE) { return 1.f; } return fluid_convex_tab[(int) val]; } fluidsynth-2.1.1/src/utils/fluid_conv.h000066400000000000000000000030341362231004000201230ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_CONV_H #define _FLUID_CONV_H #include "fluidsynth_priv.h" #include "utils/fluid_conv_tables.h" fluid_real_t fluid_ct2hz_real(fluid_real_t cents); fluid_real_t fluid_ct2hz(fluid_real_t cents); fluid_real_t fluid_cb2amp(fluid_real_t cb); fluid_real_t fluid_tc2sec(fluid_real_t tc); fluid_real_t fluid_tc2sec_delay(fluid_real_t tc); fluid_real_t fluid_tc2sec_attack(fluid_real_t tc); fluid_real_t fluid_tc2sec_release(fluid_real_t tc); fluid_real_t fluid_act2hz(fluid_real_t c); fluid_real_t fluid_pan(fluid_real_t c, int left); fluid_real_t fluid_balance(fluid_real_t balance, int left); fluid_real_t fluid_concave(fluid_real_t val); fluid_real_t fluid_convex(fluid_real_t val); #endif /* _FLUID_CONV_H */ fluidsynth-2.1.1/src/utils/fluid_conv_tables.h000066400000000000000000000032701362231004000214570ustar00rootroot00000000000000 #ifndef _FLUID_CONV_TABLES_H #define _FLUID_CONV_TABLES_H /* Attenuation range in centibels. Attenuation range is the dynamic range of the volume envelope generator from 0 to the end of attack segment. fluidsynth is a 24 bit synth, it could (should??) be 144 dB of attenuation. However the spec makes no distinction between 16 or 24 bit synths, so use 96 dB here. Note about usefulness of 24 bits: 1)Even fluidsynth is a 24 bit synth, this format is only relevant if the sample format coming from the soundfont is 24 bits and the audio sample format chosen by the application (audio.sample.format) is not 16 bits. 2)When the sample soundfont is 16 bits, the internal 24 bits number have 16 bits msb and lsb to 0. Consequently, at the DAC output, the dynamic range of this 24 bit sample is reduced to the the dynamic of a 16 bits sample (ie 90 db) even if this sample is produced by the audio driver using an audio sample format compatible for a 24 bit DAC. 3)When the audio sample format settings is 16 bits (audio.sample.format), the audio driver will make use of a 16 bit DAC, and the dynamic will be reduced to 96 dB even if the initial sample comes from a 24 bits soundfont. In both cases (2) or (3), the real dynamic range is only 96 dB. Other consideration for FLUID_NOISE_FLOOR related to case (1),(2,3): - for case (1), FLUID_NOISE_FLOOR should be the noise floor for 24 bits (i.e -138 dB). - for case (2) or (3), FLUID_NOISE_FLOOR should be the noise floor for 16 bits (i.e -90 dB). */ #define FLUID_PEAK_ATTENUATION 960.0f #define FLUID_CENTS_HZ_SIZE 1200 #define FLUID_VEL_CB_SIZE 128 #define FLUID_CB_AMP_SIZE 1441 #define FLUID_PAN_SIZE 1002 #endif fluidsynth-2.1.1/src/utils/fluid_hash.c000066400000000000000000001153001362231004000200740ustar00rootroot00000000000000/* GLIB - Library of useful routines for C programming * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02110-1301, USA. */ /* * Modified by the GLib Team and others 1997-2000. See the AUTHORS * file for a list of people on the GLib Team. See the ChangeLog * files for a list of changes. These files are distributed with * GLib at ftp://ftp.gtk.org/pub/gtk/. * * Adapted for FluidSynth use by Josh Green * September 8, 2009 from glib 2.18.4 */ /* * MT safe */ #include "fluid_sys.h" #include "fluid_hash.h" #include "fluid_list.h" #define HASH_TABLE_MIN_SIZE 11 #define HASH_TABLE_MAX_SIZE 13845163 typedef struct { fluid_hashtable_t *hashtable; fluid_hashnode_t *prev_node; fluid_hashnode_t *node; int position; int pre_advanced; // Boolean int version; } RealIter; /* Excerpt from glib gprimes.c */ static const unsigned int primes[] = { 11, 19, 37, 73, 109, 163, 251, 367, 557, 823, 1237, 1861, 2777, 4177, 6247, 9371, 14057, 21089, 31627, 47431, 71143, 106721, 160073, 240101, 360163, 540217, 810343, 1215497, 1823231, 2734867, 4102283, 6153409, 9230113, 13845163, }; static const unsigned int nprimes = FLUID_N_ELEMENTS(primes); static unsigned int spaced_primes_closest(unsigned int num) { unsigned int i; for(i = 0; i < nprimes; i++) { if(primes[i] > num) { return primes[i]; } } return primes[nprimes - 1]; } /* End excerpt from glib gprimes.c */ /* * @hashtable: our #fluid_hashtable_t * @key: the key to lookup against * @hash_return: optional key hash return location * Return value: a pointer to the described #fluid_hashnode_t pointer * * Performs a lookup in the hash table. Virtually all hash operations * will use this function internally. * * This function first computes the hash value of the key using the * user's hash function. * * If an entry in the table matching @key is found then this function * returns a pointer to the pointer to that entry in the table. In * the case that the entry is at the head of a chain, this pointer * will be an item in the nodes[] array. In the case that the entry * is not at the head of a chain, this pointer will be the ->next * pointer on the node that precedes it. * * In the case that no matching entry exists in the table, a pointer * to a %NULL pointer will be returned. To insert a item, this %NULL * pointer should be updated to point to the new #fluid_hashnode_t. * * If @hash_return is a pass-by-reference parameter. If it is * non-%NULL then the computed hash value is returned. This is to * save insertions from having to compute the hash record again for * the new record. */ static FLUID_INLINE fluid_hashnode_t ** fluid_hashtable_lookup_node(fluid_hashtable_t *hashtable, const void *key, unsigned int *hash_return) { fluid_hashnode_t **node_ptr, *node; unsigned int hash_value; hash_value = (* hashtable->hash_func)(key); node_ptr = &hashtable->nodes[hash_value % hashtable->size]; if(hash_return) { *hash_return = hash_value; } /* Hash table lookup needs to be fast. * We therefore remove the extra conditional of testing * whether to call the key_equal_func or not from * the inner loop. * * Additional optimisation: first check if our full hash * values are equal so we can avoid calling the full-blown * key equality function in most cases. */ if(hashtable->key_equal_func) { while((node = *node_ptr)) { if(node->key_hash == hash_value && hashtable->key_equal_func(node->key, key)) { break; } node_ptr = &(*node_ptr)->next; } } else { while((node = *node_ptr)) { if(node->key == key) { break; } node_ptr = &(*node_ptr)->next; } } return node_ptr; } /* * @hashtable: our #fluid_hashtable_t * @node_ptr_ptr: a pointer to the return value from * fluid_hashtable_lookup_node() * @notify: %TRUE if the destroy notify handlers are to be called * * Removes a node from the hash table and updates the node count. The * node is freed. No table resize is performed. * * If @notify is %TRUE then the destroy notify functions are called * for the key and value of the hash node. * * @node_ptr_ptr is a pass-by-reference in/out parameter. When the * function is called, it should point to the pointer to the node to * remove. This level of indirection is required so that the pointer * may be updated appropriately once the node has been removed. * * Before the function returns, the pointer at @node_ptr_ptr will be * updated to point to the position in the table that contains the * pointer to the "next" node in the chain. This makes this function * convenient to use from functions that iterate over the entire * table. If there is no further item in the chain then the * #fluid_hashnode_t pointer will be %NULL (ie: **node_ptr_ptr == %NULL). * * Since the pointer in the table to the removed node is replaced with * either a pointer to the next node or a %NULL pointer as * appropriate, the pointer at the end of @node_ptr_ptr will never be * modified at all. Stay tuned. :) */ static void fluid_hashtable_remove_node(fluid_hashtable_t *hashtable, fluid_hashnode_t ***node_ptr_ptr, int notify) { fluid_hashnode_t **node_ptr, *node; node_ptr = *node_ptr_ptr; node = *node_ptr; *node_ptr = node->next; if(notify && hashtable->key_destroy_func) { hashtable->key_destroy_func(node->key); } if(notify && hashtable->value_destroy_func) { hashtable->value_destroy_func(node->value); } FLUID_FREE(node); hashtable->nnodes--; } /* * fluid_hashtable_remove_all_nodes: * @hashtable: our #fluid_hashtable_t * @notify: %TRUE if the destroy notify handlers are to be called * * Removes all nodes from the table. Since this may be a precursor to * freeing the table entirely, no resize is performed. * * If @notify is %TRUE then the destroy notify functions are called * for the key and value of the hash node. */ static void fluid_hashtable_remove_all_nodes(fluid_hashtable_t *hashtable, int notify) { fluid_hashnode_t **node_ptr; int i; for(i = 0; i < hashtable->size; i++) { for(node_ptr = &hashtable->nodes[i]; *node_ptr != NULL;) { fluid_hashtable_remove_node(hashtable, &node_ptr, notify); } } hashtable->nnodes = 0; } /* * fluid_hashtable_resize: * @hashtable: our #fluid_hashtable_t * * Resizes the hash table to the optimal size based on the number of * nodes currently held. If you call this function then a resize will * occur, even if one does not need to occur. Use * fluid_hashtable_maybe_resize() instead. */ static void fluid_hashtable_resize(fluid_hashtable_t *hashtable) { fluid_hashnode_t **new_nodes; fluid_hashnode_t *node; fluid_hashnode_t *next; unsigned int hash_val; int new_size; int i; new_size = spaced_primes_closest(hashtable->nnodes); new_size = (new_size < HASH_TABLE_MIN_SIZE) ? HASH_TABLE_MIN_SIZE : ((new_size > HASH_TABLE_MAX_SIZE) ? HASH_TABLE_MAX_SIZE : new_size); new_nodes = FLUID_ARRAY(fluid_hashnode_t *, new_size); if(!new_nodes) { FLUID_LOG(FLUID_ERR, "Out of memory"); return; } FLUID_MEMSET(new_nodes, 0, new_size * sizeof(fluid_hashnode_t *)); for(i = 0; i < hashtable->size; i++) { for(node = hashtable->nodes[i]; node; node = next) { next = node->next; hash_val = node->key_hash % new_size; node->next = new_nodes[hash_val]; new_nodes[hash_val] = node; } } FLUID_FREE(hashtable->nodes); hashtable->nodes = new_nodes; hashtable->size = new_size; } /* * fluid_hashtable_maybe_resize: * @hashtable: our #fluid_hashtable_t * * Resizes the hash table, if needed. * * Essentially, calls fluid_hashtable_resize() if the table has strayed * too far from its ideal size for its number of nodes. */ static FLUID_INLINE void fluid_hashtable_maybe_resize(fluid_hashtable_t *hashtable) { int nnodes = hashtable->nnodes; int size = hashtable->size; if((size >= 3 * nnodes && size > HASH_TABLE_MIN_SIZE) || (3 * size <= nnodes && size < HASH_TABLE_MAX_SIZE)) { fluid_hashtable_resize(hashtable); } } /** * new_fluid_hashtable: * @hash_func: a function to create a hash value from a key. * Hash values are used to determine where keys are stored within the * #fluid_hashtable_t data structure. The fluid_direct_hash(), fluid_int_hash() and * fluid_str_hash() functions are provided for some common types of keys. * If hash_func is %NULL, fluid_direct_hash() is used. * @key_equal_func: a function to check two keys for equality. This is * used when looking up keys in the #fluid_hashtable_t. The fluid_direct_equal(), * fluid_int_equal() and fluid_str_equal() functions are provided for the most * common types of keys. If @key_equal_func is %NULL, keys are compared * directly in a similar fashion to fluid_direct_equal(), but without the * overhead of a function call. * * Creates a new #fluid_hashtable_t with a reference count of 1. * * Return value: a new #fluid_hashtable_t. **/ fluid_hashtable_t * new_fluid_hashtable(fluid_hash_func_t hash_func, fluid_equal_func_t key_equal_func) { return new_fluid_hashtable_full(hash_func, key_equal_func, NULL, NULL); } /** * new_fluid_hashtable_full: * @hash_func: a function to create a hash value from a key. * @key_equal_func: a function to check two keys for equality. * @key_destroy_func: a function to free the memory allocated for the key * used when removing the entry from the #fluid_hashtable_t or %NULL if you * don't want to supply such a function. * @value_destroy_func: a function to free the memory allocated for the * value used when removing the entry from the #fluid_hashtable_t or %NULL if * you don't want to supply such a function. * * Creates a new #fluid_hashtable_t like fluid_hashtable_new() with a reference count * of 1 and allows to specify functions to free the memory allocated for the * key and value that get called when removing the entry from the #fluid_hashtable_t. * * Return value: a new #fluid_hashtable_t. **/ fluid_hashtable_t * new_fluid_hashtable_full(fluid_hash_func_t hash_func, fluid_equal_func_t key_equal_func, fluid_destroy_notify_t key_destroy_func, fluid_destroy_notify_t value_destroy_func) { fluid_hashtable_t *hashtable; hashtable = FLUID_NEW(fluid_hashtable_t); if(!hashtable) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } hashtable->size = HASH_TABLE_MIN_SIZE; hashtable->nnodes = 0; hashtable->hash_func = hash_func ? hash_func : fluid_direct_hash; hashtable->key_equal_func = key_equal_func; fluid_atomic_int_set(&hashtable->ref_count, 1); hashtable->key_destroy_func = key_destroy_func; hashtable->value_destroy_func = value_destroy_func; hashtable->nodes = FLUID_ARRAY(fluid_hashnode_t *, hashtable->size); if(hashtable->nodes == NULL) { delete_fluid_hashtable(hashtable); FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } FLUID_MEMSET(hashtable->nodes, 0, hashtable->size * sizeof(*hashtable->nodes)); return hashtable; } /** * fluid_hashtable_iter_init: * @iter: an uninitialized #fluid_hashtable_iter_t. * @hashtable: a #fluid_hashtable_t. * * Initializes a key/value pair iterator and associates it with * @hashtable. Modifying the hash table after calling this function * invalidates the returned iterator. * |[ * fluid_hashtable_iter_t iter; * gpointer key, value; * * fluid_hashtable_iter_init (&iter, hashtable); * while (fluid_hashtable_iter_next (&iter, &key, &value)) * { * /* do something with key and value */ * } * ]| * * Since: 2.16 **/ void fluid_hashtable_iter_init(fluid_hashtable_iter_t *iter, fluid_hashtable_t *hashtable) { RealIter *ri = (RealIter *) iter; fluid_return_if_fail(iter != NULL); fluid_return_if_fail(hashtable != NULL); ri->hashtable = hashtable; ri->prev_node = NULL; ri->node = NULL; ri->position = -1; ri->pre_advanced = FALSE; } /** * fluid_hashtable_iter_next: * @iter: an initialized #fluid_hashtable_iter_t. * @key: a location to store the key, or %NULL. * @value: a location to store the value, or %NULL. * * Advances @iter and retrieves the key and/or value that are now * pointed to as a result of this advancement. If %FALSE is returned, * @key and @value are not set, and the iterator becomes invalid. * * Return value: %FALSE if the end of the #fluid_hashtable_t has been reached. * * Since: 2.16 **/ int fluid_hashtable_iter_next(fluid_hashtable_iter_t *iter, void **key, void **value) { RealIter *ri = (RealIter *) iter; fluid_return_val_if_fail(iter != NULL, FALSE); if(ri->pre_advanced) { ri->pre_advanced = FALSE; if(ri->node == NULL) { return FALSE; } } else { if(ri->node != NULL) { ri->prev_node = ri->node; ri->node = ri->node->next; } while(ri->node == NULL) { ri->position++; if(ri->position >= ri->hashtable->size) { return FALSE; } ri->prev_node = NULL; ri->node = ri->hashtable->nodes[ri->position]; } } if(key != NULL) { *key = ri->node->key; } if(value != NULL) { *value = ri->node->value; } return TRUE; } /** * fluid_hashtable_iter_get_hash_table: * @iter: an initialized #fluid_hashtable_iter_t. * * Returns the #fluid_hashtable_t associated with @iter. * * Return value: the #fluid_hashtable_t associated with @iter. * * Since: 2.16 **/ fluid_hashtable_t * fluid_hashtable_iter_get_hash_table(fluid_hashtable_iter_t *iter) { fluid_return_val_if_fail(iter != NULL, NULL); return ((RealIter *) iter)->hashtable; } static void iter_remove_or_steal(RealIter *ri, int notify) { fluid_hashnode_t *prev; fluid_hashnode_t *node; int position; fluid_return_if_fail(ri != NULL); fluid_return_if_fail(ri->node != NULL); prev = ri->prev_node; node = ri->node; position = ri->position; /* pre-advance the iterator since we will remove the node */ ri->node = ri->node->next; /* ri->prev_node is still the correct previous node */ while(ri->node == NULL) { ri->position++; if(ri->position >= ri->hashtable->size) { break; } ri->prev_node = NULL; ri->node = ri->hashtable->nodes[ri->position]; } ri->pre_advanced = TRUE; /* remove the node */ if(prev != NULL) { prev->next = node->next; } else { ri->hashtable->nodes[position] = node->next; } if(notify) { if(ri->hashtable->key_destroy_func) { ri->hashtable->key_destroy_func(node->key); } if(ri->hashtable->value_destroy_func) { ri->hashtable->value_destroy_func(node->value); } } FLUID_FREE(node); ri->hashtable->nnodes--; } /** * fluid_hashtable_iter_remove(): * @iter: an initialized #fluid_hashtable_iter_t. * * Removes the key/value pair currently pointed to by the iterator * from its associated #fluid_hashtable_t. Can only be called after * fluid_hashtable_iter_next() returned %TRUE, and cannot be called more * than once for the same key/value pair. * * If the #fluid_hashtable_t was created using fluid_hashtable_new_full(), the * key and value are freed using the supplied destroy functions, otherwise * you have to make sure that any dynamically allocated values are freed * yourself. * * Since: 2.16 **/ void fluid_hashtable_iter_remove(fluid_hashtable_iter_t *iter) { iter_remove_or_steal((RealIter *) iter, TRUE); } /** * fluid_hashtable_iter_steal(): * @iter: an initialized #fluid_hashtable_iter_t. * * Removes the key/value pair currently pointed to by the iterator * from its associated #fluid_hashtable_t, without calling the key and value * destroy functions. Can only be called after * fluid_hashtable_iter_next() returned %TRUE, and cannot be called more * than once for the same key/value pair. * * Since: 2.16 **/ void fluid_hashtable_iter_steal(fluid_hashtable_iter_t *iter) { iter_remove_or_steal((RealIter *) iter, FALSE); } /** * fluid_hashtable_ref: * @hashtable: a valid #fluid_hashtable_t. * * Atomically increments the reference count of @hashtable by one. * This function is MT-safe and may be called from any thread. * * Return value: the passed in #fluid_hashtable_t. * * Since: 2.10 **/ fluid_hashtable_t * fluid_hashtable_ref(fluid_hashtable_t *hashtable) { fluid_return_val_if_fail(hashtable != NULL, NULL); fluid_return_val_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0, hashtable); fluid_atomic_int_add(&hashtable->ref_count, 1); return hashtable; } /** * fluid_hashtable_unref: * @hashtable: a valid #fluid_hashtable_t. * * Atomically decrements the reference count of @hashtable by one. * If the reference count drops to 0, all keys and values will be * destroyed, and all memory allocated by the hash table is released. * This function is MT-safe and may be called from any thread. * * Since: 2.10 **/ void fluid_hashtable_unref(fluid_hashtable_t *hashtable) { fluid_return_if_fail(hashtable != NULL); fluid_return_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0); if(fluid_atomic_int_exchange_and_add(&hashtable->ref_count, -1) - 1 == 0) { fluid_hashtable_remove_all_nodes(hashtable, TRUE); FLUID_FREE(hashtable->nodes); FLUID_FREE(hashtable); } } /** * delete_fluid_hashtable: * @hashtable: a #fluid_hashtable_t. * * Destroys all keys and values in the #fluid_hashtable_t and decrements its * reference count by 1. If keys and/or values are dynamically allocated, * you should either free them first or create the #fluid_hashtable_t with destroy * notifiers using fluid_hashtable_new_full(). In the latter case the destroy * functions you supplied will be called on all keys and values during the * destruction phase. **/ void delete_fluid_hashtable(fluid_hashtable_t *hashtable) { fluid_return_if_fail(hashtable != NULL); fluid_return_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0); fluid_hashtable_remove_all(hashtable); fluid_hashtable_unref(hashtable); } /** * fluid_hashtable_lookup: * @hashtable: a #fluid_hashtable_t. * @key: the key to look up. * * Looks up a key in a #fluid_hashtable_t. Note that this function cannot * distinguish between a key that is not present and one which is present * and has the value %NULL. If you need this distinction, use * fluid_hashtable_lookup_extended(). * * Return value: the associated value, or %NULL if the key is not found. **/ void * fluid_hashtable_lookup(fluid_hashtable_t *hashtable, const void *key) { fluid_hashnode_t *node; fluid_return_val_if_fail(hashtable != NULL, NULL); node = *fluid_hashtable_lookup_node(hashtable, key, NULL); return node ? node->value : NULL; } /** * fluid_hashtable_lookup_extended: * @hashtable: a #fluid_hashtable_t. * @lookup_key: the key to look up. * @orig_key: returns the original key. * @value: returns the value associated with the key. * * Looks up a key in the #fluid_hashtable_t, returning the original key and the * associated value and a #gboolean which is %TRUE if the key was found. This * is useful if you need to free the memory allocated for the original key, * for example before calling fluid_hashtable_remove(). * * Return value: %TRUE if the key was found in the #fluid_hashtable_t. **/ int fluid_hashtable_lookup_extended(fluid_hashtable_t *hashtable, const void *lookup_key, void **orig_key, void **value) { fluid_hashnode_t *node; fluid_return_val_if_fail(hashtable != NULL, FALSE); node = *fluid_hashtable_lookup_node(hashtable, lookup_key, NULL); if(node == NULL) { return FALSE; } if(orig_key) { *orig_key = node->key; } if(value) { *value = node->value; } return TRUE; } /* * fluid_hashtable_insert_internal: * @hashtable: our #fluid_hashtable_t * @key: the key to insert * @value: the value to insert * @keep_new_key: if %TRUE and this key already exists in the table * then call the destroy notify function on the old key. If %FALSE * then call the destroy notify function on the new key. * * Implements the common logic for the fluid_hashtable_insert() and * fluid_hashtable_replace() functions. * * Do a lookup of @key. If it is found, replace it with the new * @value (and perhaps the new @key). If it is not found, create a * new node. */ static void fluid_hashtable_insert_internal(fluid_hashtable_t *hashtable, void *key, void *value, int keep_new_key) { fluid_hashnode_t **node_ptr, *node; unsigned int key_hash; fluid_return_if_fail(hashtable != NULL); fluid_return_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0); node_ptr = fluid_hashtable_lookup_node(hashtable, key, &key_hash); if((node = *node_ptr)) { if(keep_new_key) { if(hashtable->key_destroy_func) { hashtable->key_destroy_func(node->key); } node->key = key; } else { if(hashtable->key_destroy_func) { hashtable->key_destroy_func(key); } } if(hashtable->value_destroy_func) { hashtable->value_destroy_func(node->value); } node->value = value; } else { node = FLUID_NEW(fluid_hashnode_t); if(!node) { FLUID_LOG(FLUID_ERR, "Out of memory"); return; } node->key = key; node->value = value; node->key_hash = key_hash; node->next = NULL; *node_ptr = node; hashtable->nnodes++; fluid_hashtable_maybe_resize(hashtable); } } /** * fluid_hashtable_insert: * @hashtable: a #fluid_hashtable_t. * @key: a key to insert. * @value: the value to associate with the key. * * Inserts a new key and value into a #fluid_hashtable_t. * * If the key already exists in the #fluid_hashtable_t its current value is replaced * with the new value. If you supplied a @value_destroy_func when creating the * #fluid_hashtable_t, the old value is freed using that function. If you supplied * a @key_destroy_func when creating the #fluid_hashtable_t, the passed key is freed * using that function. **/ void fluid_hashtable_insert(fluid_hashtable_t *hashtable, void *key, void *value) { fluid_hashtable_insert_internal(hashtable, key, value, FALSE); } /** * fluid_hashtable_replace: * @hashtable: a #fluid_hashtable_t. * @key: a key to insert. * @value: the value to associate with the key. * * Inserts a new key and value into a #fluid_hashtable_t similar to * fluid_hashtable_insert(). The difference is that if the key already exists * in the #fluid_hashtable_t, it gets replaced by the new key. If you supplied a * @value_destroy_func when creating the #fluid_hashtable_t, the old value is freed * using that function. If you supplied a @key_destroy_func when creating the * #fluid_hashtable_t, the old key is freed using that function. **/ void fluid_hashtable_replace(fluid_hashtable_t *hashtable, void *key, void *value) { fluid_hashtable_insert_internal(hashtable, key, value, TRUE); } /* * fluid_hashtable_remove_internal: * @hashtable: our #fluid_hashtable_t * @key: the key to remove * @notify: %TRUE if the destroy notify handlers are to be called * Return value: %TRUE if a node was found and removed, else %FALSE * * Implements the common logic for the fluid_hashtable_remove() and * fluid_hashtable_steal() functions. * * Do a lookup of @key and remove it if it is found, calling the * destroy notify handlers only if @notify is %TRUE. */ static int fluid_hashtable_remove_internal(fluid_hashtable_t *hashtable, const void *key, int notify) { fluid_hashnode_t **node_ptr; fluid_return_val_if_fail(hashtable != NULL, FALSE); node_ptr = fluid_hashtable_lookup_node(hashtable, key, NULL); if(*node_ptr == NULL) { return FALSE; } fluid_hashtable_remove_node(hashtable, &node_ptr, notify); fluid_hashtable_maybe_resize(hashtable); return TRUE; } /** * fluid_hashtable_remove: * @hashtable: a #fluid_hashtable_t. * @key: the key to remove. * * Removes a key and its associated value from a #fluid_hashtable_t. * * If the #fluid_hashtable_t was created using fluid_hashtable_new_full(), the * key and value are freed using the supplied destroy functions, otherwise * you have to make sure that any dynamically allocated values are freed * yourself. * * Return value: %TRUE if the key was found and removed from the #fluid_hashtable_t. **/ int fluid_hashtable_remove(fluid_hashtable_t *hashtable, const void *key) { return fluid_hashtable_remove_internal(hashtable, key, TRUE); } /** * fluid_hashtable_steal: * @hashtable: a #fluid_hashtable_t. * @key: the key to remove. * * Removes a key and its associated value from a #fluid_hashtable_t without * calling the key and value destroy functions. * * Return value: %TRUE if the key was found and removed from the #fluid_hashtable_t. **/ int fluid_hashtable_steal(fluid_hashtable_t *hashtable, const void *key) { return fluid_hashtable_remove_internal(hashtable, key, FALSE); } /** * fluid_hashtable_remove_all: * @hashtable: a #fluid_hashtable_t * * Removes all keys and their associated values from a #fluid_hashtable_t. * * If the #fluid_hashtable_t was created using fluid_hashtable_new_full(), the keys * and values are freed using the supplied destroy functions, otherwise you * have to make sure that any dynamically allocated values are freed * yourself. * * Since: 2.12 **/ void fluid_hashtable_remove_all(fluid_hashtable_t *hashtable) { fluid_return_if_fail(hashtable != NULL); fluid_hashtable_remove_all_nodes(hashtable, TRUE); fluid_hashtable_maybe_resize(hashtable); } /** * fluid_hashtable_steal_all: * @hashtable: a #fluid_hashtable_t. * * Removes all keys and their associated values from a #fluid_hashtable_t * without calling the key and value destroy functions. * * Since: 2.12 **/ void fluid_hashtable_steal_all(fluid_hashtable_t *hashtable) { fluid_return_if_fail(hashtable != NULL); fluid_hashtable_remove_all_nodes(hashtable, FALSE); fluid_hashtable_maybe_resize(hashtable); } /* * fluid_hashtable_foreach_remove_or_steal: * @hashtable: our #fluid_hashtable_t * @func: the user's callback function * @user_data: data for @func * @notify: %TRUE if the destroy notify handlers are to be called * * Implements the common logic for fluid_hashtable_foreach_remove() and * fluid_hashtable_foreach_steal(). * * Iterates over every node in the table, calling @func with the key * and value of the node (and @user_data). If @func returns %TRUE the * node is removed from the table. * * If @notify is true then the destroy notify handlers will be called * for each removed node. */ static unsigned int fluid_hashtable_foreach_remove_or_steal(fluid_hashtable_t *hashtable, fluid_hr_func_t func, void *user_data, int notify) { fluid_hashnode_t *node, **node_ptr; unsigned int deleted = 0; int i; for(i = 0; i < hashtable->size; i++) { for(node_ptr = &hashtable->nodes[i]; (node = *node_ptr) != NULL;) { if((* func)(node->key, node->value, user_data)) { fluid_hashtable_remove_node(hashtable, &node_ptr, notify); deleted++; } else { node_ptr = &node->next; } } } fluid_hashtable_maybe_resize(hashtable); return deleted; } #if 0 /** * fluid_hashtable_foreach_remove: * @hashtable: a #fluid_hashtable_t. * @func: the function to call for each key/value pair. * @user_data: user data to pass to the function. * * Calls the given function for each key/value pair in the #fluid_hashtable_t. * If the function returns %TRUE, then the key/value pair is removed from the * #fluid_hashtable_t. If you supplied key or value destroy functions when creating * the #fluid_hashtable_t, they are used to free the memory allocated for the removed * keys and values. * * See #fluid_hashtable_iter_t for an alternative way to loop over the * key/value pairs in the hash table. * * Return value: the number of key/value pairs removed. **/ static unsigned int fluid_hashtable_foreach_remove(fluid_hashtable_t *hashtable, fluid_hr_func_t func, void *user_data) { fluid_return_val_if_fail(hashtable != NULL, 0); fluid_return_val_if_fail(func != NULL, 0); return fluid_hashtable_foreach_remove_or_steal(hashtable, func, user_data, TRUE); } #endif /** * fluid_hashtable_foreach_steal: * @hashtable: a #fluid_hashtable_t. * @func: the function to call for each key/value pair. * @user_data: user data to pass to the function. * * Calls the given function for each key/value pair in the #fluid_hashtable_t. * If the function returns %TRUE, then the key/value pair is removed from the * #fluid_hashtable_t, but no key or value destroy functions are called. * * See #fluid_hashtable_iter_t for an alternative way to loop over the * key/value pairs in the hash table. * * Return value: the number of key/value pairs removed. **/ unsigned int fluid_hashtable_foreach_steal(fluid_hashtable_t *hashtable, fluid_hr_func_t func, void *user_data) { fluid_return_val_if_fail(hashtable != NULL, 0); fluid_return_val_if_fail(func != NULL, 0); return fluid_hashtable_foreach_remove_or_steal(hashtable, func, user_data, FALSE); } /** * fluid_hashtable_foreach: * @hashtable: a #fluid_hashtable_t. * @func: the function to call for each key/value pair. * @user_data: user data to pass to the function. * * Calls the given function for each of the key/value pairs in the * #fluid_hashtable_t. The function is passed the key and value of each * pair, and the given @user_data parameter. The hash table may not * be modified while iterating over it (you can't add/remove * items). To remove all items matching a predicate, use * fluid_hashtable_foreach_remove(). * * See fluid_hashtable_find() for performance caveats for linear * order searches in contrast to fluid_hashtable_lookup(). **/ void fluid_hashtable_foreach(fluid_hashtable_t *hashtable, fluid_hr_func_t func, void *user_data) { fluid_hashnode_t *node; int i; fluid_return_if_fail(hashtable != NULL); fluid_return_if_fail(func != NULL); for(i = 0; i < hashtable->size; i++) { for(node = hashtable->nodes[i]; node; node = node->next) { (* func)(node->key, node->value, user_data); } } } /** * fluid_hashtable_find: * @hashtable: a #fluid_hashtable_t. * @predicate: function to test the key/value pairs for a certain property. * @user_data: user data to pass to the function. * * Calls the given function for key/value pairs in the #fluid_hashtable_t until * @predicate returns %TRUE. The function is passed the key and value of * each pair, and the given @user_data parameter. The hash table may not * be modified while iterating over it (you can't add/remove items). * * Note, that hash tables are really only optimized for forward lookups, * i.e. fluid_hashtable_lookup(). * So code that frequently issues fluid_hashtable_find() or * fluid_hashtable_foreach() (e.g. in the order of once per every entry in a * hash table) should probably be reworked to use additional or different * data structures for reverse lookups (keep in mind that an O(n) find/foreach * operation issued for all n values in a hash table ends up needing O(n*n) * operations). * * Return value: The value of the first key/value pair is returned, for which * func evaluates to %TRUE. If no pair with the requested property is found, * %NULL is returned. * * Since: 2.4 **/ void * fluid_hashtable_find(fluid_hashtable_t *hashtable, fluid_hr_func_t predicate, void *user_data) { fluid_hashnode_t *node; int i; fluid_return_val_if_fail(hashtable != NULL, NULL); fluid_return_val_if_fail(predicate != NULL, NULL); for(i = 0; i < hashtable->size; i++) { for(node = hashtable->nodes[i]; node; node = node->next) { if(predicate(node->key, node->value, user_data)) { return node->value; } } } return NULL; } /** * fluid_hashtable_size: * @hashtable: a #fluid_hashtable_t. * * Returns the number of elements contained in the #fluid_hashtable_t. * * Return value: the number of key/value pairs in the #fluid_hashtable_t. **/ unsigned int fluid_hashtable_size(fluid_hashtable_t *hashtable) { fluid_return_val_if_fail(hashtable != NULL, 0); return hashtable->nnodes; } /** * fluid_hashtable_get_keys: * @hashtable: a #fluid_hashtable_t * * Retrieves every key inside @hashtable. The returned data is valid * until @hashtable is modified. * * Return value: a #GList containing all the keys inside the hash * table. The content of the list is owned by the hash table and * should not be modified or freed. Use delete_fluid_list() when done * using the list. * * Since: 2.14 */ fluid_list_t * fluid_hashtable_get_keys(fluid_hashtable_t *hashtable) { fluid_hashnode_t *node; int i; fluid_list_t *retval; fluid_return_val_if_fail(hashtable != NULL, NULL); retval = NULL; for(i = 0; i < hashtable->size; i++) { for(node = hashtable->nodes[i]; node; node = node->next) { retval = fluid_list_prepend(retval, node->key); } } return retval; } /** * fluid_hashtable_get_values: * @hashtable: a #fluid_hashtable_t * * Retrieves every value inside @hashtable. The returned data is * valid until @hashtable is modified. * * Return value: a #GList containing all the values inside the hash * table. The content of the list is owned by the hash table and * should not be modified or freed. Use delete_fluid_list() when done * using the list. * * Since: 2.14 */ fluid_list_t * fluid_hashtable_get_values(fluid_hashtable_t *hashtable) { fluid_hashnode_t *node; int i; fluid_list_t *retval; fluid_return_val_if_fail(hashtable != NULL, NULL); retval = NULL; for(i = 0; i < hashtable->size; i++) { for(node = hashtable->nodes[i]; node; node = node->next) { retval = fluid_list_prepend(retval, node->value); } } return retval; } /* Extracted from glib/gstring.c */ /** * fluid_str_equal: * @v1: a key * @v2: a key to compare with @v1 * * Compares two strings for byte-by-byte equality and returns %TRUE * if they are equal. It can be passed to new_fluid_hashtable() as the * @key_equal_func parameter, when using strings as keys in a #Ghashtable. * * Returns: %TRUE if the two keys match */ int fluid_str_equal(const void *v1, const void *v2) { const char *string1 = v1; const char *string2 = v2; return FLUID_STRCMP(string1, string2) == 0; } /** * fluid_str_hash: * @v: a string key * * Converts a string to a hash value. * It can be passed to new_fluid_hashtable() as the @hash_func * parameter, when using strings as keys in a #fluid_hashtable_t. * * Returns: a hash value corresponding to the key */ unsigned int fluid_str_hash(const void *v) { /* 31 bit hash function */ const signed char *p = v; uint32_t h = *p; if(h) { for(p += 1; *p != '\0'; p++) { h = (h << 5) - h + *p; } } return h; } /* Extracted from glib/gutils.c */ /** * fluid_direct_equal: * @v1: a key. * @v2: a key to compare with @v1. * * Compares two #gpointer arguments and returns %TRUE if they are equal. * It can be passed to new_fluid_hashtable() as the @key_equal_func * parameter, when using pointers as keys in a #fluid_hashtable_t. * * Returns: %TRUE if the two keys match. */ int fluid_direct_equal(const void *v1, const void *v2) { return v1 == v2; } /** * fluid_direct_hash: * @v: a void * key * * Converts a gpointer to a hash value. * It can be passed to g_hashtable_new() as the @hash_func parameter, * when using pointers as keys in a #fluid_hashtable_t. * * Returns: a hash value corresponding to the key. */ unsigned int fluid_direct_hash(const void *v) { return FLUID_POINTER_TO_UINT(v); } /** * fluid_int_equal: * @v1: a pointer to a int key. * @v2: a pointer to a int key to compare with @v1. * * Compares the two #gint values being pointed to and returns * %TRUE if they are equal. * It can be passed to g_hashtable_new() as the @key_equal_func * parameter, when using pointers to integers as keys in a #fluid_hashtable_t. * * Returns: %TRUE if the two keys match. */ int fluid_int_equal(const void *v1, const void *v2) { return *((const int *) v1) == *((const int *) v2); } /** * fluid_int_hash: * @v: a pointer to a int key * * Converts a pointer to a #gint to a hash value. * It can be passed to g_hashtable_new() as the @hash_func parameter, * when using pointers to integers values as keys in a #fluid_hashtable_t. * * Returns: a hash value corresponding to the key. */ unsigned int fluid_int_hash(const void *v) { return *(const int *) v; } fluidsynth-2.1.1/src/utils/fluid_hash.h000066400000000000000000000117701362231004000201070ustar00rootroot00000000000000/* GLIB - Library of useful routines for C programming * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02110-1301, USA. */ /* * Modified by the GLib Team and others 1997-2000. See the AUTHORS * file for a list of people on the GLib Team. See the ChangeLog * files for a list of changes. These files are distributed with * GLib at ftp://ftp.gtk.org/pub/gtk/. */ /* * Adapted for FluidSynth use by Josh Green * September 8, 2009 from glib 2.18.4 * * - Self contained (no dependencies on glib) * - changed names to fluid_hashtable_... */ #ifndef _FLUID_HASH_H #define _FLUID_HASH_H #include "fluidsynth_priv.h" #include "fluid_list.h" #include "fluid_sys.h" /* Extracted from gtypes.h */ typedef void (*fluid_destroy_notify_t)(void *data); typedef unsigned int (*fluid_hash_func_t)(const void *key); typedef int (*fluid_equal_func_t)(const void *a, const void *b); /* End gtypes.h extraction */ typedef int (*fluid_hr_func_t)(void *key, void *value, void *user_data); typedef struct _fluid_hashtable_iter_t fluid_hashtable_iter_t; typedef struct _fluid_hashnode_t fluid_hashnode_t; struct _fluid_hashnode_t { void *key; void *value; fluid_hashnode_t *next; unsigned int key_hash; }; struct _fluid_hashtable_t { int size; int nnodes; fluid_hashnode_t **nodes; fluid_hash_func_t hash_func; fluid_equal_func_t key_equal_func; fluid_atomic_int_t ref_count; fluid_destroy_notify_t key_destroy_func; fluid_destroy_notify_t value_destroy_func; fluid_rec_mutex_t mutex; // Optionally used in other modules (fluid_settings.c for example) }; struct _fluid_hashtable_iter_t { /*< private >*/ void *dummy1; void *dummy2; void *dummy3; int dummy4; int dummy5; // Bool void *dummy6; }; fluid_hashtable_t *new_fluid_hashtable(fluid_hash_func_t hash_func, fluid_equal_func_t key_equal_func); fluid_hashtable_t *new_fluid_hashtable_full(fluid_hash_func_t hash_func, fluid_equal_func_t key_equal_func, fluid_destroy_notify_t key_destroy_func, fluid_destroy_notify_t value_destroy_func); void delete_fluid_hashtable(fluid_hashtable_t *hashtable); void fluid_hashtable_iter_init(fluid_hashtable_iter_t *iter, fluid_hashtable_t *hashtable); int fluid_hashtable_iter_next(fluid_hashtable_iter_t *iter, void **key, void **value); fluid_hashtable_t *fluid_hashtable_iter_get_hash_table(fluid_hashtable_iter_t *iter); void fluid_hashtable_iter_remove(fluid_hashtable_iter_t *iter); void fluid_hashtable_iter_steal(fluid_hashtable_iter_t *iter); fluid_hashtable_t *fluid_hashtable_ref(fluid_hashtable_t *hashtable); void fluid_hashtable_unref(fluid_hashtable_t *hashtable); void *fluid_hashtable_lookup(fluid_hashtable_t *hashtable, const void *key); int fluid_hashtable_lookup_extended(fluid_hashtable_t *hashtable, const void *lookup_key, void **orig_key, void **value); void fluid_hashtable_insert(fluid_hashtable_t *hashtable, void *key, void *value); void fluid_hashtable_replace(fluid_hashtable_t *hashtable, void *key, void *value); int fluid_hashtable_remove(fluid_hashtable_t *hashtable, const void *key); int fluid_hashtable_steal(fluid_hashtable_t *hashtable, const void *key); void fluid_hashtable_remove_all(fluid_hashtable_t *hashtable); void fluid_hashtable_steal_all(fluid_hashtable_t *hashtable); unsigned int fluid_hashtable_foreach_steal(fluid_hashtable_t *hashtable, fluid_hr_func_t func, void *user_data); void fluid_hashtable_foreach(fluid_hashtable_t *hashtable, fluid_hr_func_t func, void *user_data); void *fluid_hashtable_find(fluid_hashtable_t *hashtable, fluid_hr_func_t predicate, void *user_data); unsigned int fluid_hashtable_size(fluid_hashtable_t *hashtable); fluid_list_t *fluid_hashtable_get_keys(fluid_hashtable_t *hashtable); fluid_list_t *fluid_hashtable_get_values(fluid_hashtable_t *hashtable); int fluid_str_equal(const void *v1, const void *v2); unsigned int fluid_str_hash(const void *v); int fluid_direct_equal(const void *v1, const void *v2); unsigned int fluid_direct_hash(const void *v); int fluid_int_equal(const void *v1, const void *v2); unsigned int fluid_int_hash(const void *v); #endif /* _FLUID_HASH_H */ fluidsynth-2.1.1/src/utils/fluid_list.c000066400000000000000000000131151362231004000201250ustar00rootroot00000000000000/* GLIB - Library of useful routines for C programming * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02110-1301, USA. */ /* * Modified by the GLib Team and others 1997-1999. See the AUTHORS * file for a list of people on the GLib Team. See the ChangeLog * files for a list of changes. These files are distributed with * GLib at ftp://ftp.gtk.org/pub/gtk/. */ #include "fluid_sys.h" #include "fluid_list.h" fluid_list_t * new_fluid_list(void) { fluid_list_t *list; list = (fluid_list_t *) FLUID_MALLOC(sizeof(fluid_list_t)); list->data = NULL; list->next = NULL; return list; } void delete_fluid_list(fluid_list_t *list) { fluid_list_t *next; fluid_return_if_fail(list != NULL); while(list) { next = list->next; FLUID_FREE(list); list = next; } } void delete1_fluid_list(fluid_list_t *list) { FLUID_FREE(list); } fluid_list_t * fluid_list_append(fluid_list_t *list, void *data) { fluid_list_t *new_list; fluid_list_t *last; new_list = new_fluid_list(); new_list->data = data; if(list) { last = fluid_list_last(list); /* g_assert (last != NULL); */ last->next = new_list; return list; } else { return new_list; } } fluid_list_t * fluid_list_prepend(fluid_list_t *list, void *data) { fluid_list_t *new_list; new_list = new_fluid_list(); new_list->data = data; new_list->next = list; return new_list; } fluid_list_t * fluid_list_nth(fluid_list_t *list, int n) { while((n-- > 0) && list) { list = list->next; } return list; } fluid_list_t * fluid_list_remove(fluid_list_t *list, void *data) { fluid_list_t *tmp; fluid_list_t *prev; prev = NULL; tmp = list; while(tmp) { if(tmp->data == data) { if(prev) { prev->next = tmp->next; } if(list == tmp) { list = list->next; } tmp->next = NULL; delete_fluid_list(tmp); break; } prev = tmp; tmp = tmp->next; } return list; } fluid_list_t * fluid_list_remove_link(fluid_list_t *list, fluid_list_t *link) { fluid_list_t *tmp; fluid_list_t *prev; prev = NULL; tmp = list; while(tmp) { if(tmp == link) { if(prev) { prev->next = tmp->next; } if(list == tmp) { list = list->next; } tmp->next = NULL; break; } prev = tmp; tmp = tmp->next; } return list; } static fluid_list_t * fluid_list_sort_merge(fluid_list_t *l1, fluid_list_t *l2, fluid_compare_func_t compare_func) { fluid_list_t list, *l; l = &list; while(l1 && l2) { if(compare_func(l1->data, l2->data) < 0) { l = l->next = l1; l1 = l1->next; } else { l = l->next = l2; l2 = l2->next; } } l->next = l1 ? l1 : l2; return list.next; } fluid_list_t * fluid_list_sort(fluid_list_t *list, fluid_compare_func_t compare_func) { fluid_list_t *l1, *l2; if(!list) { return NULL; } if(!list->next) { return list; } l1 = list; l2 = list->next; while((l2 = l2->next) != NULL) { if((l2 = l2->next) == NULL) { break; } l1 = l1->next; } l2 = l1->next; l1->next = NULL; return fluid_list_sort_merge(fluid_list_sort(list, compare_func), fluid_list_sort(l2, compare_func), compare_func); } fluid_list_t * fluid_list_last(fluid_list_t *list) { if(list) { while(list->next) { list = list->next; } } return list; } int fluid_list_size(fluid_list_t *list) { int n = 0; while(list) { n++; list = list->next; } return n; } fluid_list_t *fluid_list_insert_at(fluid_list_t *list, int n, void *data) { fluid_list_t *new_list; fluid_list_t *cur; fluid_list_t *prev = NULL; new_list = new_fluid_list(); new_list->data = data; cur = list; while((n-- > 0) && cur) { prev = cur; cur = cur->next; } new_list->next = cur; if(prev) { prev->next = new_list; return list; } else { return new_list; } } /* Compare function to sort strings alphabetically, * for use with fluid_list_sort(). */ int fluid_list_str_compare_func(void *a, void *b) { if(a && b) { return FLUID_STRCMP((char *)a, (char *)b); } if(!a && !b) { return 0; } if(a) { return -1; } return 1; } fluidsynth-2.1.1/src/utils/fluid_list.h000066400000000000000000000042141362231004000201320ustar00rootroot00000000000000/* GLIB - Library of useful routines for C programming * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02110-1301, USA. */ #ifndef _FLUID_LIST_H #define _FLUID_LIST_H #include "fluidsynth_priv.h" /* * * Lists * * A sound font loader has to pack the data from the .SF2 file into * list structures of this type. * */ typedef struct _fluid_list_t fluid_list_t; typedef int (*fluid_compare_func_t)(void *a, void *b); struct _fluid_list_t { void *data; fluid_list_t *next; }; fluid_list_t *new_fluid_list(void); void delete_fluid_list(fluid_list_t *list); void delete1_fluid_list(fluid_list_t *list); fluid_list_t *fluid_list_sort(fluid_list_t *list, fluid_compare_func_t compare_func); fluid_list_t *fluid_list_append(fluid_list_t *list, void *data); fluid_list_t *fluid_list_prepend(fluid_list_t *list, void *data); fluid_list_t *fluid_list_remove(fluid_list_t *list, void *data); fluid_list_t *fluid_list_remove_link(fluid_list_t *list, fluid_list_t *llink); fluid_list_t *fluid_list_nth(fluid_list_t *list, int n); fluid_list_t *fluid_list_last(fluid_list_t *list); fluid_list_t *fluid_list_insert_at(fluid_list_t *list, int n, void *data); int fluid_list_size(fluid_list_t *list); #define fluid_list_next(slist) ((slist) ? (((fluid_list_t *)(slist))->next) : NULL) #define fluid_list_get(slist) ((slist) ? ((slist)->data) : NULL) int fluid_list_str_compare_func(void *a, void *b); #endif /* _FLUID_LIST_H */ fluidsynth-2.1.1/src/utils/fluid_ringbuffer.c000066400000000000000000000052301362231004000213020ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* * Josh Green * 2009-05-28 */ #include "fluid_ringbuffer.h" #include "fluid_sys.h" /** * Create a lock free queue with a fixed maximum count and size of elements. * @param count Count of elements in queue (fixed max number of queued elements) * @return New lock free queue or NULL if out of memory (error message logged) * * Lockless FIFO queues don't use any locking mechanisms and can therefore be * advantageous in certain situations, such as passing data between a lower * priority thread and a higher "real time" thread, without potential lock * contention which could stall the high priority thread. Note that there may * only be one producer thread and one consumer thread. */ fluid_ringbuffer_t * new_fluid_ringbuffer(int count, int elementsize) { fluid_ringbuffer_t *queue; fluid_return_val_if_fail(count > 0, NULL); queue = FLUID_NEW(fluid_ringbuffer_t); if(!queue) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } queue->array = FLUID_MALLOC(elementsize * count); if(!queue->array) { FLUID_LOG(FLUID_ERR, "Out of memory"); delete_fluid_ringbuffer(queue); return NULL; } /* Clear array, in case dynamic pointer reclaiming is being done */ FLUID_MEMSET(queue->array, 0, elementsize * count); queue->totalcount = count; queue->elementsize = elementsize; fluid_atomic_int_set(&queue->count, 0); queue->in = 0; queue->out = 0; return (queue); } /** * Free an event queue. * @param queue Lockless queue instance * * Care must be taken when freeing a queue, to ensure that the consumer and * producer threads will no longer access it. */ void delete_fluid_ringbuffer(fluid_ringbuffer_t *queue) { fluid_return_if_fail(queue != NULL); FLUID_FREE(queue->array); FLUID_FREE(queue); } fluidsynth-2.1.1/src/utils/fluid_ringbuffer.h000066400000000000000000000106041362231004000213100ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_RINGBUFFER_H #define _FLUID_RINGBUFFER_H #include "fluid_sys.h" /* * Lockless event queue instance. */ struct _fluid_ringbuffer_t { char *array; /**< Queue array of arbitrary size elements */ int totalcount; /**< Total count of elements in array */ fluid_atomic_int_t count; /**< Current count of elements */ int in; /**< Index in queue to store next pushed element */ int out; /**< Index in queue of next popped element */ int elementsize; /**< Size of each element */ void *userdata; }; typedef struct _fluid_ringbuffer_t fluid_ringbuffer_t; fluid_ringbuffer_t *new_fluid_ringbuffer(int count, int elementsize); void delete_fluid_ringbuffer(fluid_ringbuffer_t *queue); /** * Get pointer to next input array element in queue. * @param queue Lockless queue instance * @param offset Normally zero, or more if you need to push several items at once * @return Pointer to array element in queue to store data to or NULL if queue is full * * This function along with fluid_ringbuffer_next_inptr() form a queue "push" * operation and is split into 2 functions to avoid an element copy. Note that * the returned array element pointer may contain the data of a previous element * if the queue has wrapped around. This can be used to reclaim pointers to * allocated memory, etc. */ static FLUID_INLINE void * fluid_ringbuffer_get_inptr(fluid_ringbuffer_t *queue, int offset) { return fluid_atomic_int_get(&queue->count) + offset >= queue->totalcount ? NULL : queue->array + queue->elementsize * ((queue->in + offset) % queue->totalcount); } /** * Advance the input queue index to complete a "push" operation. * @param queue Lockless queue instance * @param count Normally one, or more if you need to push several items at once * * This function along with fluid_ringbuffer_get_inptr() form a queue "push" * operation and is split into 2 functions to avoid element copy. */ static FLUID_INLINE void fluid_ringbuffer_next_inptr(fluid_ringbuffer_t *queue, int count) { fluid_atomic_int_add(&queue->count, count); queue->in += count; if(queue->in >= queue->totalcount) { queue->in -= queue->totalcount; } } /** * Get amount of items currently in queue * @param queue Lockless queue instance * @return amount of items currently in queue */ static FLUID_INLINE int fluid_ringbuffer_get_count(fluid_ringbuffer_t *queue) { return fluid_atomic_int_get(&queue->count); } /** * Get pointer to next output array element in queue. * @param queue Lockless queue instance * @return Pointer to array element data in the queue or NULL if empty, can only * be used up until fluid_ringbuffer_next_outptr() is called. * * This function along with fluid_ringbuffer_next_outptr() form a queue "pop" * operation and is split into 2 functions to avoid an element copy. */ static FLUID_INLINE void * fluid_ringbuffer_get_outptr(fluid_ringbuffer_t *queue) { return fluid_ringbuffer_get_count(queue) == 0 ? NULL : queue->array + queue->elementsize * queue->out; } /** * Advance the output queue index to complete a "pop" operation. * @param queue Lockless queue instance * * This function along with fluid_ringbuffer_get_outptr() form a queue "pop" * operation and is split into 2 functions to avoid an element copy. */ static FLUID_INLINE void fluid_ringbuffer_next_outptr(fluid_ringbuffer_t *queue) { fluid_atomic_int_add(&queue->count, -1); if(++queue->out == queue->totalcount) { queue->out = 0; } } #endif /* _FLUID_ringbuffer_H */ fluidsynth-2.1.1/src/utils/fluid_settings.c000066400000000000000000001500751362231004000210210ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sys.h" #include "fluid_hash.h" #include "fluid_synth.h" #include "fluid_cmd.h" #include "fluid_adriver.h" #include "fluid_mdriver.h" #include "fluid_settings.h" #include "fluid_midi.h" /* maximum allowed components of a settings variable (separated by '.') */ #define MAX_SETTINGS_TOKENS 8 /* currently only a max of 3 are used */ #define MAX_SETTINGS_LABEL 256 /* max length of a settings variable label */ static void fluid_settings_init(fluid_settings_t *settings); static void fluid_settings_key_destroy_func(void *value); static void fluid_settings_value_destroy_func(void *value); static int fluid_settings_tokenize(const char *s, char *buf, char **ptr); /* Common structure to all settings nodes */ typedef struct { char *value; char *def; int hints; fluid_list_t *options; fluid_str_update_t update; void *data; } fluid_str_setting_t; typedef struct { double value; double def; double min; double max; int hints; fluid_num_update_t update; void *data; } fluid_num_setting_t; typedef struct { int value; int def; int min; int max; int hints; fluid_int_update_t update; void *data; } fluid_int_setting_t; typedef struct { fluid_hashtable_t *hashtable; } fluid_set_setting_t; typedef struct { int type; /**< fluid_types_enum */ union { fluid_str_setting_t str; fluid_num_setting_t num; fluid_int_setting_t i; fluid_set_setting_t set; }; } fluid_setting_node_t; static fluid_setting_node_t * new_fluid_str_setting(const char *value, const char *def, int hints) { fluid_setting_node_t *node; fluid_str_setting_t *str; node = FLUID_NEW(fluid_setting_node_t); if(!node) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } node->type = FLUID_STR_TYPE; str = &node->str; str->value = value ? FLUID_STRDUP(value) : NULL; str->def = def ? FLUID_STRDUP(def) : NULL; str->hints = hints; str->options = NULL; str->update = NULL; str->data = NULL; return node; } static void delete_fluid_str_setting(fluid_setting_node_t *node) { fluid_return_if_fail(node != NULL); FLUID_ASSERT(node->type == FLUID_STR_TYPE); FLUID_FREE(node->str.value); FLUID_FREE(node->str.def); if(node->str.options) { fluid_list_t *list = node->str.options; while(list) { FLUID_FREE(list->data); list = fluid_list_next(list); } delete_fluid_list(node->str.options); } FLUID_FREE(node); } static fluid_setting_node_t * new_fluid_num_setting(double min, double max, double def, int hints) { fluid_setting_node_t *node; fluid_num_setting_t *num; node = FLUID_NEW(fluid_setting_node_t); if(!node) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } node->type = FLUID_NUM_TYPE; num = &node->num; num->value = def; num->def = def; num->min = min; num->max = max; num->hints = hints; num->update = NULL; num->data = NULL; return node; } static void delete_fluid_num_setting(fluid_setting_node_t *node) { fluid_return_if_fail(node != NULL); FLUID_ASSERT(node->type == FLUID_NUM_TYPE); FLUID_FREE(node); } static fluid_setting_node_t * new_fluid_int_setting(int min, int max, int def, int hints) { fluid_setting_node_t *node; fluid_int_setting_t *i; node = FLUID_NEW(fluid_setting_node_t); if(!node) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } node->type = FLUID_INT_TYPE; i = &node->i; i->value = def; i->def = def; i->min = min; i->max = max; i->hints = hints; i->update = NULL; i->data = NULL; return node; } static void delete_fluid_int_setting(fluid_setting_node_t *node) { fluid_return_if_fail(node != NULL); FLUID_ASSERT(node->type == FLUID_INT_TYPE); FLUID_FREE(node); } static fluid_setting_node_t * new_fluid_set_setting(void) { fluid_setting_node_t *node; fluid_set_setting_t *set; node = FLUID_NEW(fluid_setting_node_t); if(!node) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } node->type = FLUID_SET_TYPE; set = &node->set; set->hashtable = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal, fluid_settings_key_destroy_func, fluid_settings_value_destroy_func); if(!set->hashtable) { FLUID_FREE(node); return NULL; } return node; } static void delete_fluid_set_setting(fluid_setting_node_t *node) { fluid_return_if_fail(node != NULL); FLUID_ASSERT(node->type == FLUID_SET_TYPE); delete_fluid_hashtable(node->set.hashtable); FLUID_FREE(node); } /** * Create a new settings object * @return the pointer to the settings object */ fluid_settings_t * new_fluid_settings(void) { fluid_settings_t *settings; settings = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal, fluid_settings_key_destroy_func, fluid_settings_value_destroy_func); if(settings == NULL) { return NULL; } fluid_rec_mutex_init(settings->mutex); fluid_settings_init(settings); return settings; } /** * Delete the provided settings object * @param settings a settings object */ void delete_fluid_settings(fluid_settings_t *settings) { fluid_return_if_fail(settings != NULL); fluid_rec_mutex_destroy(settings->mutex); delete_fluid_hashtable(settings); } /* Settings hash key destroy function */ static void fluid_settings_key_destroy_func(void *value) { FLUID_FREE(value); /* Free the string key value */ } /* Settings hash value destroy function */ static void fluid_settings_value_destroy_func(void *value) { fluid_setting_node_t *node = value; switch(node->type) { case FLUID_NUM_TYPE: delete_fluid_num_setting(node); break; case FLUID_INT_TYPE: delete_fluid_int_setting(node); break; case FLUID_STR_TYPE: delete_fluid_str_setting(node); break; case FLUID_SET_TYPE: delete_fluid_set_setting(node); break; } } void fluid_settings_init(fluid_settings_t *settings) { fluid_return_if_fail(settings != NULL); fluid_synth_settings(settings); fluid_shell_settings(settings); fluid_player_settings(settings); fluid_file_renderer_settings(settings); fluid_audio_driver_settings(settings); fluid_midi_driver_settings(settings); } static int fluid_settings_tokenize(const char *s, char *buf, char **ptr) { char *tokstr, *tok; int n = 0; if(FLUID_STRLEN(s) > MAX_SETTINGS_LABEL) { FLUID_LOG(FLUID_ERR, "Setting variable name exceeded max length of %d chars", MAX_SETTINGS_LABEL); return 0; } FLUID_STRCPY(buf, s); /* copy string to buffer, since it gets modified */ tokstr = buf; while((tok = fluid_strtok(&tokstr, "."))) { if(n >= MAX_SETTINGS_TOKENS) { FLUID_LOG(FLUID_ERR, "Setting variable name exceeded max token count of %d", MAX_SETTINGS_TOKENS); return 0; } else { ptr[n++] = tok; } } return n; } /** * Get a setting name, value and type * * @param settings a settings object * @param name Settings name * @param value Location to store setting node if found * @return #FLUID_OK if the node exists, #FLUID_FAILED otherwise */ static int fluid_settings_get(fluid_settings_t *settings, const char *name, fluid_setting_node_t **value) { fluid_hashtable_t *table = settings; fluid_setting_node_t *node = NULL; char *tokens[MAX_SETTINGS_TOKENS]; char buf[MAX_SETTINGS_LABEL + 1]; int ntokens; int n; ntokens = fluid_settings_tokenize(name, buf, tokens); if(table == NULL || ntokens <= 0) { return FLUID_FAILED; } for(n = 0; n < ntokens; n++) { node = fluid_hashtable_lookup(table, tokens[n]); if(!node) { return FLUID_FAILED; } table = (node->type == FLUID_SET_TYPE) ? node->set.hashtable : NULL; } if(value) { *value = node; } return FLUID_OK; } /** * Set a setting name, value and type, replacing it if already exists * * @param settings a settings object * @param name Settings name * @param value Node instance to assign (used directly) * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise */ static int fluid_settings_set(fluid_settings_t *settings, const char *name, fluid_setting_node_t *value) { fluid_hashtable_t *table = settings; fluid_setting_node_t *node; char *tokens[MAX_SETTINGS_TOKENS]; char buf[MAX_SETTINGS_LABEL + 1]; int n, num; char *dupname; num = fluid_settings_tokenize(name, buf, tokens); if(num == 0) { return FLUID_FAILED; } num--; for(n = 0; n < num; n++) { node = fluid_hashtable_lookup(table, tokens[n]); if(node) { if(node->type == FLUID_SET_TYPE) { table = node->set.hashtable; } else { /* path ends prematurely */ FLUID_LOG(FLUID_ERR, "'%s' is not a node. Name of the setting was '%s'", tokens[n], name); return FLUID_FAILED; } } else { /* create a new node */ fluid_setting_node_t *setnode; dupname = FLUID_STRDUP(tokens[n]); setnode = new_fluid_set_setting(); if(!dupname || !setnode) { if(dupname) { FLUID_FREE(dupname); } else { FLUID_LOG(FLUID_ERR, "Out of memory"); } if(setnode) { delete_fluid_set_setting(setnode); } return FLUID_FAILED; } fluid_hashtable_insert(table, dupname, setnode); table = setnode->set.hashtable; } } dupname = FLUID_STRDUP(tokens[num]); if(!dupname) { FLUID_LOG(FLUID_ERR, "Out of memory"); return FLUID_FAILED; } fluid_hashtable_insert(table, dupname, value); return FLUID_OK; } /** * Registers a new string value for the specified setting. * * @param settings a settings object * @param name the setting's name * @param def the default value for the setting * @param hints the hints for the setting * @return #FLUID_OK if the value has been register correctly, #FLUID_FAILED otherwise */ int fluid_settings_register_str(fluid_settings_t *settings, const char *name, const char *def, int hints) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) != FLUID_OK) { node = new_fluid_str_setting(def, def, hints); retval = fluid_settings_set(settings, name, node); if(retval != FLUID_OK) { delete_fluid_str_setting(node); } } else { /* if variable already exists, don't change its value. */ if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; setting->def = def ? FLUID_STRDUP(def) : NULL; setting->hints = hints; retval = FLUID_OK; } else { FLUID_LOG(FLUID_ERR, "Failed to register string setting '%s' as it already exists with a different type", name); } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Registers a new float value for the specified setting. * * @param settings a settings object * @param name the setting's name * @param def the default value for the setting * @param min the smallest allowed value for the setting * @param max the largest allowed value for the setting * @param hints the hints for the setting * @return #FLUID_OK if the value has been register correctly, #FLUID_FAILED otherwise */ int fluid_settings_register_num(fluid_settings_t *settings, const char *name, double def, double min, double max, int hints) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); /* For now, all floating point settings are bounded below and above */ hints |= FLUID_HINT_BOUNDED_BELOW | FLUID_HINT_BOUNDED_ABOVE; fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) != FLUID_OK) { /* insert a new setting */ node = new_fluid_num_setting(min, max, def, hints); retval = fluid_settings_set(settings, name, node); if(retval != FLUID_OK) { delete_fluid_num_setting(node); } } else { if(node->type == FLUID_NUM_TYPE) { /* update the existing setting but don't change its value */ fluid_num_setting_t *setting = &node->num; setting->min = min; setting->max = max; setting->def = def; setting->hints = hints; retval = FLUID_OK; } else { /* type mismatch */ FLUID_LOG(FLUID_ERR, "Failed to register numeric setting '%s' as it already exists with a different type", name); } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Registers a new integer value for the specified setting. * * @param settings a settings object * @param name the setting's name * @param def the default value for the setting * @param min the smallest allowed value for the setting * @param max the largest allowed value for the setting * @param hints the hints for the setting * @return #FLUID_OK if the value has been register correctly, #FLUID_FAILED otherwise */ int fluid_settings_register_int(fluid_settings_t *settings, const char *name, int def, int min, int max, int hints) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); /* For now, all integer settings are bounded below and above */ hints |= FLUID_HINT_BOUNDED_BELOW | FLUID_HINT_BOUNDED_ABOVE; fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) != FLUID_OK) { /* insert a new setting */ node = new_fluid_int_setting(min, max, def, hints); retval = fluid_settings_set(settings, name, node); if(retval != FLUID_OK) { delete_fluid_int_setting(node); } } else { if(node->type == FLUID_INT_TYPE) { /* update the existing setting but don't change its value */ fluid_int_setting_t *setting = &node->i; setting->min = min; setting->max = max; setting->def = def; setting->hints = hints; retval = FLUID_OK; } else { /* type mismatch */ FLUID_LOG(FLUID_ERR, "Failed to register int setting '%s' as it already exists with a different type", name); } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Registers a callback for the specified string setting. * * @param settings a settings object * @param name the setting's name * @param callback an update function for the setting * @param data user supplied data passed to the update function * @return #FLUID_OK if the callback has been set, #FLUID_FAILED otherwise */ int fluid_settings_callback_str(fluid_settings_t *settings, const char *name, fluid_str_update_t callback, void *data) { fluid_setting_node_t *node; fluid_str_setting_t *setting; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if((fluid_settings_get(settings, name, &node) != FLUID_OK) || node->type != FLUID_STR_TYPE) { fluid_rec_mutex_unlock(settings->mutex); return FLUID_FAILED; } setting = &node->str; setting->update = callback; setting->data = data; fluid_rec_mutex_unlock(settings->mutex); return FLUID_OK; } /** * Registers a callback for the specified numeric setting. * * @param settings a settings object * @param name the setting's name * @param callback an update function for the setting * @param data user supplied data passed to the update function * @return #FLUID_OK if the callback has been set, #FLUID_FAILED otherwise */ int fluid_settings_callback_num(fluid_settings_t *settings, const char *name, fluid_num_update_t callback, void *data) { fluid_setting_node_t *node; fluid_num_setting_t *setting; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if((fluid_settings_get(settings, name, &node) != FLUID_OK) || node->type != FLUID_NUM_TYPE) { fluid_rec_mutex_unlock(settings->mutex); return FLUID_FAILED; } setting = &node->num; setting->update = callback; setting->data = data; fluid_rec_mutex_unlock(settings->mutex); return FLUID_OK; } /** * Registers a callback for the specified int setting. * * @param settings a settings object * @param name the setting's name * @param callback an update function for the setting * @param data user supplied data passed to the update function * @return #FLUID_OK if the callback has been set, #FLUID_FAILED otherwise */ int fluid_settings_callback_int(fluid_settings_t *settings, const char *name, fluid_int_update_t callback, void *data) { fluid_setting_node_t *node; fluid_int_setting_t *setting; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if((fluid_settings_get(settings, name, &node) != FLUID_OK) || node->type != FLUID_INT_TYPE) { fluid_rec_mutex_unlock(settings->mutex); return FLUID_FAILED; } setting = &node->i; setting->update = callback; setting->data = data; fluid_rec_mutex_unlock(settings->mutex); return FLUID_OK; } void* fluid_settings_get_user_data(fluid_settings_t * settings, const char *name) { fluid_setting_node_t *node; void* retval = NULL; fluid_return_val_if_fail(settings != NULL, NULL); fluid_return_val_if_fail(name != NULL, NULL); fluid_return_val_if_fail(name[0] != '\0', NULL); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_NUM_TYPE) { fluid_num_setting_t *setting = &node->num; retval = setting->data; } else if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; retval = setting->data; } else if(node->type == FLUID_INT_TYPE) { fluid_int_setting_t *setting = &node->i; retval = setting->data; } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Get the type of the setting with the given name * * @param settings a settings object * @param name a setting's name * @return the type for the named setting (see #fluid_types_enum), or #FLUID_NO_TYPE when it does not exist */ int fluid_settings_get_type(fluid_settings_t *settings, const char *name) { fluid_setting_node_t *node; int type = FLUID_NO_TYPE; fluid_return_val_if_fail(settings != NULL, type); fluid_return_val_if_fail(name != NULL, type); fluid_return_val_if_fail(name[0] != '\0', type); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { type = node->type; } fluid_rec_mutex_unlock(settings->mutex); return type; } /** * Get the hints for the named setting as an integer bitmap * * @param settings a settings object * @param name a setting's name * @param hints set to the hints associated to the setting if it exists * @return #FLUID_OK if hints associated to the named setting exist, #FLUID_FAILED otherwise */ int fluid_settings_get_hints(fluid_settings_t *settings, const char *name, int *hints) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_NUM_TYPE) { fluid_num_setting_t *setting = &node->num; *hints = setting->hints; retval = FLUID_OK; } else if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; *hints = setting->hints; retval = FLUID_OK; } else if(node->type == FLUID_INT_TYPE) { fluid_int_setting_t *setting = &node->i; *hints = setting->hints; retval = FLUID_OK; } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Ask whether the setting is changeable in real-time. * * @param settings a settings object * @param name a setting's name * @return TRUE if the setting is changeable in real-time, FALSE otherwise */ int fluid_settings_is_realtime(fluid_settings_t *settings, const char *name) { fluid_setting_node_t *node; int isrealtime = FALSE; fluid_return_val_if_fail(settings != NULL, 0); fluid_return_val_if_fail(name != NULL, 0); fluid_return_val_if_fail(name[0] != '\0', 0); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_NUM_TYPE) { fluid_num_setting_t *setting = &node->num; isrealtime = setting->update != NULL; } else if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; isrealtime = setting->update != NULL; } else if(node->type == FLUID_INT_TYPE) { fluid_int_setting_t *setting = &node->i; isrealtime = setting->update != NULL; } } fluid_rec_mutex_unlock(settings->mutex); return isrealtime; } /** * Set a string value for a named setting * * @param settings a settings object * @param name a setting's name * @param str new string value * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise */ int fluid_settings_setstr(fluid_settings_t *settings, const char *name, const char *str) { fluid_setting_node_t *node; fluid_str_setting_t *setting; char *new_value = NULL; fluid_str_update_t callback = NULL; void *data = NULL; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if((fluid_settings_get(settings, name, &node) != FLUID_OK) || (node->type != FLUID_STR_TYPE)) { FLUID_LOG(FLUID_ERR, "Unknown string setting '%s'", name); goto error_recovery; } setting = &node->str; if(setting->value) { FLUID_FREE(setting->value); } if(str) { new_value = FLUID_STRDUP(str); if(new_value == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); goto error_recovery; } } setting->value = new_value; callback = setting->update; data = setting->data; /* Release the mutex before calling the update callback, to avoid * possible deadlocks with FluidSynths API lock */ fluid_rec_mutex_unlock(settings->mutex); if(callback) { (*callback)(data, name, new_value); } return FLUID_OK; error_recovery: fluid_rec_mutex_unlock(settings->mutex); return FLUID_FAILED; } /** * Copy the value of a string setting into the provided buffer (thread safe) * @param settings a settings object * @param name a setting's name * @param str Caller supplied buffer to copy string value to * @param len Size of 'str' buffer (no more than len bytes will be written, which * will always include a zero terminator) * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise * @since 1.1.0 * * @note A size of 256 should be more than sufficient for the string buffer. */ int fluid_settings_copystr(fluid_settings_t *settings, const char *name, char *str, int len) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(str != NULL, retval); fluid_return_val_if_fail(len > 0, retval); str[0] = 0; fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; if(setting->value) { FLUID_STRNCPY(str, setting->value, len); } retval = FLUID_OK; } else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ { fluid_int_setting_t *setting = &node->i; if(setting->hints & FLUID_HINT_TOGGLED) { FLUID_STRNCPY(str, setting->value ? "yes" : "no", len); retval = FLUID_OK; } } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Duplicate the value of a string setting * @param settings a settings object * @param name a setting's name * @param str Location to store pointer to allocated duplicate string * @return #FLUID_OK if the value exists and was successfully duplicated, #FLUID_FAILED otherwise * @since 1.1.0 * * Like fluid_settings_copystr() but allocates a new copy of the string. Caller * owns the string and should free it with fluid_free() when done using it. */ int fluid_settings_dupstr(fluid_settings_t *settings, const char *name, char **str) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(str != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; if(setting->value) { *str = FLUID_STRDUP(setting->value); if(!*str) { FLUID_LOG(FLUID_ERR, "Out of memory"); } } if(!setting->value || *str) { retval = FLUID_OK; /* Don't set to FLUID_OK if out of memory */ } } else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ { fluid_int_setting_t *setting = &node->i; if(setting->hints & FLUID_HINT_TOGGLED) { *str = FLUID_STRDUP(setting->value ? "yes" : "no"); if(!*str) { FLUID_LOG(FLUID_ERR, "Out of memory"); } if(!setting->value || *str) { retval = FLUID_OK; /* Don't set to FLUID_OK if out of memory */ } } } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Test a string setting for some value. * * @param settings a settings object * @param name a setting's name * @param s a string to be tested * @return TRUE if the value exists and is equal to \c s, FALSE otherwise */ int fluid_settings_str_equal(fluid_settings_t *settings, const char *name, const char *s) { fluid_setting_node_t *node; int retval = FALSE; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(s != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; if(setting->value) { retval = FLUID_STRCMP(setting->value, s) == 0; } } else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ { fluid_int_setting_t *setting = &node->i; if(setting->hints & FLUID_HINT_TOGGLED) { retval = FLUID_STRCMP(setting->value ? "yes" : "no", s) == 0; } } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Get the default value of a string setting. Note that the returned string is * not owned by the caller and should not be modified or freed. * * @param settings a settings object * @param name a setting's name * @param def the default string value of the setting if it exists * @return FLUID_OK on success, FLUID_FAILED otherwise */ int fluid_settings_getstr_default(fluid_settings_t *settings, const char *name, char **def) { fluid_setting_node_t *node; char *retval = NULL; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK) { if(node->type == FLUID_STR_TYPE) { fluid_str_setting_t *setting = &node->str; retval = setting->def; } else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ { fluid_int_setting_t *setting = &node->i; if(setting->hints & FLUID_HINT_TOGGLED) { retval = setting->def ? "yes" : "no"; } } } *def = retval; fluid_rec_mutex_unlock(settings->mutex); return retval != NULL ? FLUID_OK : FLUID_FAILED; } /** * Add an option to a string setting (like an enumeration value). * @param settings a settings object * @param name a setting's name * @param s option string to add * @return #FLUID_OK if the setting exists and option was added, #FLUID_FAILED otherwise * * Causes the setting's #FLUID_HINT_OPTIONLIST hint to be set. */ int fluid_settings_add_option(fluid_settings_t *settings, const char *name, const char *s) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(s != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_STR_TYPE)) { fluid_str_setting_t *setting = &node->str; char *copy = FLUID_STRDUP(s); setting->options = fluid_list_append(setting->options, copy); setting->hints |= FLUID_HINT_OPTIONLIST; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Remove an option previously assigned by fluid_settings_add_option(). * @param settings a settings object * @param name a setting's name * @param s option string to remove * @return #FLUID_OK if the setting exists and option was removed, #FLUID_FAILED otherwise */ int fluid_settings_remove_option(fluid_settings_t *settings, const char *name, const char *s) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(s != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_STR_TYPE)) { fluid_str_setting_t *setting = &node->str; fluid_list_t *list = setting->options; while(list) { char *option = (char *) fluid_list_get(list); if(FLUID_STRCMP(s, option) == 0) { FLUID_FREE(option); setting->options = fluid_list_remove_link(setting->options, list); retval = FLUID_OK; break; } list = fluid_list_next(list); } } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Set a numeric value for a named setting. * * @param settings a settings object * @param name a setting's name * @param val new setting's value * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise */ int fluid_settings_setnum(fluid_settings_t *settings, const char *name, double val) { fluid_setting_node_t *node; fluid_num_setting_t *setting; fluid_num_update_t callback = NULL; void *data = NULL; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if((fluid_settings_get(settings, name, &node) != FLUID_OK) || (node->type != FLUID_NUM_TYPE)) { FLUID_LOG(FLUID_ERR, "Unknown numeric setting '%s'", name); goto error_recovery; } setting = &node->num; if(val < setting->min || val > setting->max) { FLUID_LOG(FLUID_ERR, "requested set value for '%s' out of range", name); goto error_recovery; } setting->value = val; callback = setting->update; data = setting->data; /* Release the mutex before calling the update callback, to avoid * possible deadlocks with FluidSynths API lock */ fluid_rec_mutex_unlock(settings->mutex); if(callback) { (*callback)(data, name, val); } return FLUID_OK; error_recovery: fluid_rec_mutex_unlock(settings->mutex); return FLUID_FAILED; } /** * Get the numeric value of a named setting * * @param settings a settings object * @param name a setting's name * @param val variable pointer to receive the setting's numeric value * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise */ int fluid_settings_getnum(fluid_settings_t *settings, const char *name, double *val) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(val != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_NUM_TYPE)) { fluid_num_setting_t *setting = &node->num; *val = setting->value; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * float-typed wrapper for fluid_settings_getnum * * @param settings a settings object * @param name a setting's name * @param val variable pointer to receive the setting's float value * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise */ int fluid_settings_getnum_float(fluid_settings_t *settings, const char *name, float *val) { double tmp; if(fluid_settings_getnum(settings, name, &tmp) == FLUID_OK) { *val = tmp; return FLUID_OK; } return FLUID_FAILED; } /** * Get the range of values of a numeric setting * * @param settings a settings object * @param name a setting's name * @param min setting's range lower limit * @param max setting's range upper limit * @return #FLUID_OK if the setting's range exists, #FLUID_FAILED otherwise */ int fluid_settings_getnum_range(fluid_settings_t *settings, const char *name, double *min, double *max) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(min != NULL, retval); fluid_return_val_if_fail(max != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_NUM_TYPE)) { fluid_num_setting_t *setting = &node->num; *min = setting->min; *max = setting->max; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Get the default value of a named numeric (double) setting * * @param settings a settings object * @param name a setting's name * @param val set to the default value if the named setting exists * @return #FLUID_OK if the default value of the named setting exists, #FLUID_FAILED otherwise */ int fluid_settings_getnum_default(fluid_settings_t *settings, const char *name, double *val) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(val != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_NUM_TYPE)) { fluid_num_setting_t *setting = &node->num; *val = setting->def; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Set an integer value for a setting * * @param settings a settings object * @param name a setting's name * @param val new setting's integer value * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise */ int fluid_settings_setint(fluid_settings_t *settings, const char *name, int val) { fluid_setting_node_t *node; fluid_int_setting_t *setting; fluid_int_update_t callback = NULL; void *data = NULL; fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); fluid_return_val_if_fail(name != NULL, FLUID_FAILED); fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); fluid_rec_mutex_lock(settings->mutex); if((fluid_settings_get(settings, name, &node) != FLUID_OK) || (node->type != FLUID_INT_TYPE)) { FLUID_LOG(FLUID_ERR, "Unknown integer parameter '%s'", name); goto error_recovery; } setting = &node->i; if(val < setting->min || val > setting->max) { FLUID_LOG(FLUID_ERR, "requested set value for setting '%s' out of range", name); goto error_recovery; } setting->value = val; callback = setting->update; data = setting->data; /* Release the mutex before calling the update callback, to avoid * possible deadlocks with FluidSynths API lock */ fluid_rec_mutex_unlock(settings->mutex); if(callback) { (*callback)(data, name, val); } return FLUID_OK; error_recovery: fluid_rec_mutex_unlock(settings->mutex); return FLUID_FAILED; } /** * Get an integer value setting. * * @param settings a settings object * @param name a setting's name * @param val pointer to a variable to receive the setting's integer value * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise */ int fluid_settings_getint(fluid_settings_t *settings, const char *name, int *val) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(val != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_INT_TYPE)) { fluid_int_setting_t *setting = &node->i; *val = setting->value; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Get the range of values of an integer setting * @param settings a settings object * @param name a setting's name * @param min setting's range lower limit * @param max setting's range upper limit * @return #FLUID_OK if the setting's range exists, #FLUID_FAILED otherwise */ int fluid_settings_getint_range(fluid_settings_t *settings, const char *name, int *min, int *max) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(min != NULL, retval); fluid_return_val_if_fail(max != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_INT_TYPE)) { fluid_int_setting_t *setting = &node->i; *min = setting->min; *max = setting->max; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Get the default value of an integer setting. * * @param settings a settings object * @param name a setting's name * @param val set to the setting's default integer value if it exists * @return #FLUID_OK if the setting's default integer value exists, #FLUID_FAILED otherwise */ int fluid_settings_getint_default(fluid_settings_t *settings, const char *name, int *val) { fluid_setting_node_t *node; int retval = FLUID_FAILED; fluid_return_val_if_fail(settings != NULL, retval); fluid_return_val_if_fail(name != NULL, retval); fluid_return_val_if_fail(name[0] != '\0', retval); fluid_return_val_if_fail(val != NULL, retval); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && (node->type == FLUID_INT_TYPE)) { fluid_int_setting_t *setting = &node->i; *val = setting->def; retval = FLUID_OK; } fluid_rec_mutex_unlock(settings->mutex); return retval; } /** * Iterate the available options for a named string setting, calling the provided * callback function for each existing option. * * @param settings a settings object * @param name a setting's name * @param data any user provided pointer * @param func callback function to be called on each iteration * * @note Starting with FluidSynth 1.1.0 the \p func callback is called for each * option in alphabetical order. Sort order was undefined in previous versions. */ void fluid_settings_foreach_option(fluid_settings_t *settings, const char *name, void *data, fluid_settings_foreach_option_t func) { fluid_setting_node_t *node; fluid_str_setting_t *setting; fluid_list_t *p, *newlist = NULL; fluid_return_if_fail(settings != NULL); fluid_return_if_fail(name != NULL); fluid_return_if_fail(name[0] != '\0'); fluid_return_if_fail(func != NULL); fluid_rec_mutex_lock(settings->mutex); /* ++ lock */ if(fluid_settings_get(settings, name, &node) != FLUID_OK || node->type != FLUID_STR_TYPE) { fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ return; } setting = &node->str; /* Duplicate option list */ for(p = setting->options; p; p = p->next) { newlist = fluid_list_append(newlist, fluid_list_get(p)); } /* Sort by name */ newlist = fluid_list_sort(newlist, fluid_list_str_compare_func); for(p = newlist; p; p = p->next) { (*func)(data, name, (const char *)fluid_list_get(p)); } fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ delete_fluid_list(newlist); } /** * Count option string values for a string setting. * @param settings a settings object * @param name Name of setting * @return Count of options for this string setting (0 if none, -1 if not found * or not a string setting) * @since 1.1.0 */ int fluid_settings_option_count(fluid_settings_t *settings, const char *name) { fluid_setting_node_t *node; int count = -1; fluid_return_val_if_fail(settings != NULL, -1); fluid_return_val_if_fail(name != NULL, -1); fluid_return_val_if_fail(name[0] != '\0', -1); fluid_rec_mutex_lock(settings->mutex); if(fluid_settings_get(settings, name, &node) == FLUID_OK && node->type == FLUID_STR_TYPE) { count = fluid_list_size(node->str.options); } fluid_rec_mutex_unlock(settings->mutex); return (count); } /** * Concatenate options for a string setting together with a separator between. * @param settings Settings object * @param name Settings name * @param separator String to use between options (NULL to use ", ") * @return Newly allocated string or NULL on error (out of memory, not a valid * setting \p name or not a string setting). Free the string when finished with it by using fluid_free(). * @since 1.1.0 */ char * fluid_settings_option_concat(fluid_settings_t *settings, const char *name, const char *separator) { fluid_setting_node_t *node; fluid_list_t *p, *newlist = NULL; size_t count, len; char *str, *option; fluid_return_val_if_fail(settings != NULL, NULL); fluid_return_val_if_fail(name != NULL, NULL); fluid_return_val_if_fail(name[0] != '\0', NULL); if(!separator) { separator = ", "; } fluid_rec_mutex_lock(settings->mutex); /* ++ lock */ if(fluid_settings_get(settings, name, &node) != FLUID_OK || node->type != FLUID_STR_TYPE) { fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ return (NULL); } /* Duplicate option list, count options and get total string length */ for(p = node->str.options, count = 0, len = 0; p; p = p->next) { option = fluid_list_get(p); if(option) { newlist = fluid_list_append(newlist, option); len += FLUID_STRLEN(option); count++; } } if(count > 1) { len += (count - 1) * FLUID_STRLEN(separator); } len++; /* For terminator */ /* Sort by name */ newlist = fluid_list_sort(newlist, fluid_list_str_compare_func); str = FLUID_MALLOC(len); if(str) { str[0] = 0; for(p = newlist; p; p = p->next) { option = fluid_list_get(p); strcat(str, option); if(p->next) { strcat(str, separator); } } } fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ delete_fluid_list(newlist); if(!str) { FLUID_LOG(FLUID_ERR, "Out of memory"); } return (str); } /* Structure passed to fluid_settings_foreach_iter recursive function */ typedef struct { char path[MAX_SETTINGS_LABEL + 1]; /* Maximum settings label length */ fluid_list_t *names; /* For fluid_settings_foreach() */ } fluid_settings_foreach_bag_t; static int fluid_settings_foreach_iter(void *key, void *value, void *data) { fluid_settings_foreach_bag_t *bag = data; char *keystr = key; fluid_setting_node_t *node = value; size_t pathlen; char *s; pathlen = FLUID_STRLEN(bag->path); if(pathlen > 0) { bag->path[pathlen] = '.'; bag->path[pathlen + 1] = 0; } strcat(bag->path, keystr); switch(node->type) { case FLUID_NUM_TYPE: case FLUID_INT_TYPE: case FLUID_STR_TYPE: s = FLUID_STRDUP(bag->path); if(s) { bag->names = fluid_list_append(bag->names, s); } break; case FLUID_SET_TYPE: fluid_hashtable_foreach(node->set.hashtable, fluid_settings_foreach_iter, bag); break; } bag->path[pathlen] = 0; return 0; } /** * Iterate the existing settings defined in a settings object, calling the * provided callback function for each setting. * * @param settings a settings object * @param data any user provided pointer * @param func callback function to be called on each iteration * * @note Starting with FluidSynth 1.1.0 the \p func callback is called for each * setting in alphabetical order. Sort order was undefined in previous versions. */ void fluid_settings_foreach(fluid_settings_t *settings, void *data, fluid_settings_foreach_t func) { fluid_settings_foreach_bag_t bag; fluid_setting_node_t *node; fluid_list_t *p; fluid_return_if_fail(settings != NULL); fluid_return_if_fail(func != NULL); bag.path[0] = 0; bag.names = NULL; fluid_rec_mutex_lock(settings->mutex); /* Add all node names to the bag.names list */ fluid_hashtable_foreach(settings, fluid_settings_foreach_iter, &bag); /* Sort names */ bag.names = fluid_list_sort(bag.names, fluid_list_str_compare_func); /* Loop over names and call the callback */ for(p = bag.names; p; p = p->next) { if(fluid_settings_get(settings, (const char *)(p->data), &node) == FLUID_OK && node) { (*func)(data, (const char *)(p->data), node->type); } FLUID_FREE(p->data); /* -- Free name */ } fluid_rec_mutex_unlock(settings->mutex); delete_fluid_list(bag.names); /* -- Free names list */ } /** * Split a comma-separated list of integers and fill the passed * in buffer with the parsed values. * * @param str the comma-separated string to split * @param buf user-supplied buffer to hold the parsed numbers * @param buf_len length of user-supplied buffer * @return number of parsed values or -1 on failure */ int fluid_settings_split_csv(const char *str, int *buf, int buf_len) { char *s; char *tok; char *tokstr; int n = 0; s = tokstr = FLUID_STRDUP(str); if(s == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return -1; } while((tok = fluid_strtok(&tokstr, ",")) && n < buf_len) { buf[n++] = atoi(tok); } FLUID_FREE(s); return n; } fluidsynth-2.1.1/src/utils/fluid_settings.h000066400000000000000000000047311362231004000210230ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #ifndef _FLUID_SETTINGS_H #define _FLUID_SETTINGS_H int fluid_settings_add_option(fluid_settings_t *settings, const char *name, const char *s); int fluid_settings_remove_option(fluid_settings_t *settings, const char *name, const char *s); typedef void (*fluid_str_update_t)(void *data, const char *name, const char *value); int fluid_settings_register_str(fluid_settings_t *settings, const char *name, const char *def, int hints); int fluid_settings_callback_str(fluid_settings_t *settings, const char *name, fluid_str_update_t fun, void *data); typedef void (*fluid_num_update_t)(void *data, const char *name, double value); int fluid_settings_register_num(fluid_settings_t *settings, const char *name, double def, double min, double max, int hints); int fluid_settings_callback_num(fluid_settings_t *settings, const char *name, fluid_num_update_t fun, void *data); /* Type specific wrapper for fluid_settings_getnum */ int fluid_settings_getnum_float(fluid_settings_t *settings, const char *name, float *val); typedef void (*fluid_int_update_t)(void *data, const char *name, int value); int fluid_settings_register_int(fluid_settings_t *settings, const char *name, int def, int min, int max, int hints); int fluid_settings_callback_int(fluid_settings_t *settings, const char *name, fluid_int_update_t fun, void *data); int fluid_settings_split_csv(const char *str, int *buf, int buf_len); void* fluid_settings_get_user_data(fluid_settings_t * settings, const char *name); #endif /* _FLUID_SETTINGS_H */ fluidsynth-2.1.1/src/utils/fluid_sys.c000066400000000000000000001353271362231004000200020ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ #include "fluid_sys.h" #if WITH_READLINE #include #include #endif #ifdef DBUS_SUPPORT #include "fluid_rtkit.h" #endif #if HAVE_PTHREAD_H && !defined(WIN32) // Do not include pthread on windows. It includes winsock.h, which collides with ws2tcpip.h from fluid_sys.h // It isn't need on Windows anyway. #include #endif /* WIN32 HACK - Flag used to differentiate between a file descriptor and a socket. * Should work, so long as no SOCKET or file descriptor ends up with this bit set. - JG */ #ifdef _WIN32 #define FLUID_SOCKET_FLAG 0x40000000 #else #define FLUID_SOCKET_FLAG 0x00000000 #define SOCKET_ERROR -1 #define INVALID_SOCKET -1 #endif /* SCHED_FIFO priority for high priority timer threads */ #define FLUID_SYS_TIMER_HIGH_PRIO_LEVEL 10 typedef struct { fluid_thread_func_t func; void *data; int prio_level; } fluid_thread_info_t; struct _fluid_timer_t { long msec; fluid_timer_callback_t callback; void *data; fluid_thread_t *thread; int cont; int auto_destroy; }; struct _fluid_server_socket_t { fluid_socket_t socket; fluid_thread_t *thread; int cont; fluid_server_func_t func; void *data; }; static int fluid_istream_gets(fluid_istream_t in, char *buf, int len); static fluid_log_function_t fluid_log_function[LAST_LOG_LEVEL] = { fluid_default_log_function, fluid_default_log_function, fluid_default_log_function, fluid_default_log_function, #ifdef DEBUG fluid_default_log_function #else NULL #endif }; static void *fluid_log_user_data[LAST_LOG_LEVEL] = { NULL }; static const char fluid_libname[] = "fluidsynth"; /** * Installs a new log function for a specified log level. * @param level Log level to install handler for. * @param fun Callback function handler to call for logged messages * @param data User supplied data pointer to pass to log function * @return The previously installed function. */ fluid_log_function_t fluid_set_log_function(int level, fluid_log_function_t fun, void *data) { fluid_log_function_t old = NULL; if((level >= 0) && (level < LAST_LOG_LEVEL)) { old = fluid_log_function[level]; fluid_log_function[level] = fun; fluid_log_user_data[level] = data; } return old; } /** * Default log function which prints to the stderr. * @param level Log level * @param message Log message * @param data User supplied data (not used) */ void fluid_default_log_function(int level, const char *message, void *data) { FILE *out; #if defined(WIN32) out = stdout; #else out = stderr; #endif switch(level) { case FLUID_PANIC: FLUID_FPRINTF(out, "%s: panic: %s\n", fluid_libname, message); break; case FLUID_ERR: FLUID_FPRINTF(out, "%s: error: %s\n", fluid_libname, message); break; case FLUID_WARN: FLUID_FPRINTF(out, "%s: warning: %s\n", fluid_libname, message); break; case FLUID_INFO: FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message); break; case FLUID_DBG: FLUID_FPRINTF(out, "%s: debug: %s\n", fluid_libname, message); break; default: FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message); break; } fflush(out); } /** * Print a message to the log. * @param level Log level (#fluid_log_level). * @param fmt Printf style format string for log message * @param ... Arguments for printf 'fmt' message string * @return Always returns #FLUID_FAILED */ int fluid_log(int level, const char *fmt, ...) { if((level >= 0) && (level < LAST_LOG_LEVEL)) { fluid_log_function_t fun = fluid_log_function[level]; if(fun != NULL) { char errbuf[1024]; va_list args; va_start(args, fmt); FLUID_VSNPRINTF(errbuf, sizeof(errbuf), fmt, args); va_end(args); (*fun)(level, errbuf, fluid_log_user_data[level]); } } return FLUID_FAILED; } void* fluid_alloc(size_t len) { void* ptr = malloc(len); #if defined(DEBUG) && !defined(_MSC_VER) // garbage initialize allocated memory for debug builds to ease reproducing // bugs like 44453ff23281b3318abbe432fda90888c373022b . // // MSVC++ already garbage initializes allocated memory by itself (debug-heap). // // 0xCC because // * it makes pointers reliably crash when dereferencing them, // * floating points are still some valid but insanely huge negative number, and // * if for whatever reason this allocated memory is executed, it'll trigger // INT3 (...at least on x86) if(ptr != NULL) { memset(ptr, 0xCC, len); } #endif return ptr; } /** * Convenience wrapper for free() that satisfies at least C90 requirements. * Especially useful when using fluidsynth with programming languages that do not provide malloc() and free(). * @note Only use this function when the API documentation explicitly says so. Otherwise use adequate \c delete_fluid_* functions. * @since 2.0.7 */ void fluid_free(void* ptr) { free(ptr); } /** * An improved strtok, still trashes the input string, but is portable and * thread safe. Also skips token chars at beginning of token string and never * returns an empty token (will return NULL if source ends in token chars though). * NOTE: NOT part of public API * @internal * @param str Pointer to a string pointer of source to tokenize. Pointer gets * updated on each invocation to point to beginning of next token. Note that * token char gets overwritten with a 0 byte. String pointer is set to NULL * when final token is returned. * @param delim String of delimiter chars. * @return Pointer to the next token or NULL if no more tokens. */ char *fluid_strtok(char **str, char *delim) { char *s, *d, *token; char c; if(str == NULL || delim == NULL || !*delim) { FLUID_LOG(FLUID_ERR, "Null pointer"); return NULL; } s = *str; if(!s) { return NULL; /* str points to a NULL pointer? (tokenize already ended) */ } /* skip delimiter chars at beginning of token */ do { c = *s; if(!c) /* end of source string? */ { *str = NULL; return NULL; } for(d = delim; *d; d++) /* is source char a token char? */ { if(c == *d) /* token char match? */ { s++; /* advance to next source char */ break; } } } while(*d); /* while token char match */ token = s; /* start of token found */ /* search for next token char or end of source string */ for(s = s + 1; *s; s++) { c = *s; for(d = delim; *d; d++) /* is source char a token char? */ { if(c == *d) /* token char match? */ { *s = '\0'; /* overwrite token char with zero byte to terminate token */ *str = s + 1; /* update str to point to beginning of next token */ return token; } } } /* we get here only if source string ended */ *str = NULL; return token; } /** * Suspend the execution of the current thread for the specified amount of time. * @param milliseconds to wait. */ void fluid_msleep(unsigned int msecs) { g_usleep(msecs * 1000); } /** * Get time in milliseconds to be used in relative timing operations. * @return Monotonic time in milliseconds. */ unsigned int fluid_curtime(void) { float now; static float initial_time = 0; if(initial_time == 0) { initial_time = (float)fluid_utime(); } now = (float)fluid_utime(); return (unsigned int)((now - initial_time) / 1000.0f); } /** * Get time in microseconds to be used in relative timing operations. * @return time in microseconds. * Note: When used for profiling we need high precision clock given * by g_get_monotonic_time()if available (glib version >= 2.53.3). * If glib version is too old and in the case of Windows the function * uses high precision performance counter instead of g_getmonotic_time(). */ double fluid_utime(void) { double utime; #if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 28 /* use high precision monotonic clock if available (g_monotonic_time(). * For Winfdows, if this clock is actually implemented as low prec. clock * (i.e. in case glib is too old), high precision performance counter are * used instead. * see: https://bugzilla.gnome.org/show_bug.cgi?id=783340 */ #if defined(WITH_PROFILING) && defined(WIN32) &&\ /* glib < 2.53.3 */\ (GLIB_MINOR_VERSION <= 53 && (GLIB_MINOR_VERSION < 53 || GLIB_MICRO_VERSION < 3)) /* use high precision performance counter. */ static LARGE_INTEGER freq_cache = {0, 0}; /* Performance Frequency */ LARGE_INTEGER perf_cpt; if(! freq_cache.QuadPart) { QueryPerformanceFrequency(&freq_cache); /* Frequency value */ } QueryPerformanceCounter(&perf_cpt); /* Counter value */ utime = perf_cpt.QuadPart * 1000000.0 / freq_cache.QuadPart; /* time in micros */ #else utime = g_get_monotonic_time(); #endif #else /* fallback to less precise clock */ GTimeVal timeval; g_get_current_time(&timeval); utime = (timeval.tv_sec * 1000000.0 + timeval.tv_usec); #endif return utime; } #if defined(WIN32) /* Windoze specific stuff */ void fluid_thread_self_set_prio(int prio_level) { if(prio_level > 0) { SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); } } #elif defined(__OS2__) /* OS/2 specific stuff */ void fluid_thread_self_set_prio(int prio_level) { if(prio_level > 0) { DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, PRTYD_MAXIMUM, 0); } } #else /* POSIX stuff.. Nice POSIX.. Good POSIX. */ void fluid_thread_self_set_prio(int prio_level) { struct sched_param priority; if(prio_level > 0) { memset(&priority, 0, sizeof(priority)); priority.sched_priority = prio_level; if(pthread_setschedparam(pthread_self(), SCHED_FIFO, &priority) == 0) { return; } #ifdef DBUS_SUPPORT /* Try to gain high priority via rtkit */ if(fluid_rtkit_make_realtime(0, prio_level) == 0) { return; } #endif FLUID_LOG(FLUID_WARN, "Failed to set thread to high priority"); } } #ifdef FPE_CHECK /*************************************************************** * * Floating point exceptions * * The floating point exception functions were taken from Ircam's * jMax source code. http://www.ircam.fr/jmax * * FIXME: check in config for i386 machine * * Currently not used. I leave the code here in case we want to pick * this up again some time later. */ /* Exception flags */ #define _FPU_STATUS_IE 0x001 /* Invalid Operation */ #define _FPU_STATUS_DE 0x002 /* Denormalized Operand */ #define _FPU_STATUS_ZE 0x004 /* Zero Divide */ #define _FPU_STATUS_OE 0x008 /* Overflow */ #define _FPU_STATUS_UE 0x010 /* Underflow */ #define _FPU_STATUS_PE 0x020 /* Precision */ #define _FPU_STATUS_SF 0x040 /* Stack Fault */ #define _FPU_STATUS_ES 0x080 /* Error Summary Status */ /* Macros for accessing the FPU status word. */ /* get the FPU status */ #define _FPU_GET_SW(sw) __asm__ ("fnstsw %0" : "=m" (*&sw)) /* clear the FPU status */ #define _FPU_CLR_SW() __asm__ ("fnclex" : : ) /* Purpose: * Checks, if the floating point unit has produced an exception, print a message * if so and clear the exception. */ unsigned int fluid_check_fpe_i386(char *explanation) { unsigned int s; _FPU_GET_SW(s); _FPU_CLR_SW(); s &= _FPU_STATUS_IE | _FPU_STATUS_DE | _FPU_STATUS_ZE | _FPU_STATUS_OE | _FPU_STATUS_UE; if(s) { FLUID_LOG(FLUID_WARN, "FPE exception (before or in %s): %s%s%s%s%s", explanation, (s & _FPU_STATUS_IE) ? "Invalid operation " : "", (s & _FPU_STATUS_DE) ? "Denormal number " : "", (s & _FPU_STATUS_ZE) ? "Zero divide " : "", (s & _FPU_STATUS_OE) ? "Overflow " : "", (s & _FPU_STATUS_UE) ? "Underflow " : ""); } return s; } /* Purpose: * Clear floating point exception. */ void fluid_clear_fpe_i386(void) { _FPU_CLR_SW(); } #endif // ifdef FPE_CHECK #endif // #else (its POSIX) /*************************************************************** * * Profiling (Linux, i586 only) * */ #if WITH_PROFILING /* Profiling interface between profiling command shell and audio rendering API (FluidProfile_0004.pdf- 3.2.2). Macros are in defined in fluid_sys.h. */ /* ----------------------------------------------------------------------------- Shell task side | Profiling interface | Audio task side ----------------------------------------------------------------------------- profiling | Internal | | | Audio command <---> |<-- profling -->| Data |<--macros -->| <--> rendering shell | API | | | API */ /* default parameters for shell command "prof_start" in fluid_sys.c */ unsigned short fluid_profile_notes = 0; /* number of generated notes */ /* preset bank:0 prog:16 (organ) */ unsigned char fluid_profile_bank = FLUID_PROFILE_DEFAULT_BANK; unsigned char fluid_profile_prog = FLUID_PROFILE_DEFAULT_PROG; /* print mode */ unsigned char fluid_profile_print = FLUID_PROFILE_DEFAULT_PRINT; /* number of measures */ unsigned short fluid_profile_n_prof = FLUID_PROFILE_DEFAULT_N_PROF; /* measure duration in ms */ unsigned short fluid_profile_dur = FLUID_PROFILE_DEFAULT_DURATION; /* lock between multiple-shell */ fluid_atomic_int_t fluid_profile_lock = 0; /**/ /*---------------------------------------------- Profiling Data -----------------------------------------------*/ unsigned char fluid_profile_status = PROFILE_STOP; /* command and status */ unsigned int fluid_profile_end_ticks = 0; /* ending position (in ticks) */ fluid_profile_data_t fluid_profile_data[] = /* Data duration */ { {"synth_write_* ------------>", 1e10, 0.0, 0.0, 0, 0, 0}, {"synth_one_block ---------->", 1e10, 0.0, 0.0, 0, 0, 0}, {"synth_one_block:clear ---->", 1e10, 0.0, 0.0, 0, 0, 0}, {"synth_one_block:one voice->", 1e10, 0.0, 0.0, 0, 0, 0}, {"synth_one_block:all voices>", 1e10, 0.0, 0.0, 0, 0, 0}, {"synth_one_block:reverb --->", 1e10, 0.0, 0.0, 0, 0, 0}, {"synth_one_block:chorus --->", 1e10, 0.0, 0.0, 0, 0, 0}, {"voice:note --------------->", 1e10, 0.0, 0.0, 0, 0, 0}, {"voice:release ------------>", 1e10, 0.0, 0.0, 0, 0, 0} }; /*---------------------------------------------- Internal profiling API -----------------------------------------------*/ /* logging profiling data (used on synthesizer instance deletion) */ void fluid_profiling_print(void) { int i; printf("fluid_profiling_print\n"); FLUID_LOG(FLUID_INFO, "Estimated times: min/avg/max (micro seconds)"); for(i = 0; i < FLUID_PROFILE_NBR; i++) { if(fluid_profile_data[i].count > 0) { FLUID_LOG(FLUID_INFO, "%s: %.3f/%.3f/%.3f", fluid_profile_data[i].description, fluid_profile_data[i].min, fluid_profile_data[i].total / fluid_profile_data[i].count, fluid_profile_data[i].max); } else { FLUID_LOG(FLUID_DBG, "%s: no profiling available", fluid_profile_data[i].description); } } } /* Macro that returns cpu load in percent (%) * @dur: duration (micro second). * @sample_rate: sample_rate used in audio driver (Hz). * @n_amples: number of samples collected during 'dur' duration. */ #define fluid_profile_load(dur,sample_rate,n_samples) \ (dur * sample_rate / n_samples / 10000.0) /* prints cpu loads only * * @param sample_rate the sample rate of audio output. * @param out output stream device. * * ------------------------------------------------------------------------------ * Cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) and maximum voices * ------------------------------------------------------------------------------ * nVoices| total(%)|voices(%)| reverb(%)|chorus(%)| voice(%)|estimated maxVoices * -------|---------|---------|----------|---------|---------|------------------- * 250| 41.544| 41.544| 0.000| 0.000| 0.163| 612 */ static void fluid_profiling_print_load(double sample_rate, fluid_ostream_t out) { unsigned int n_voices; /* voices number */ static const char max_voices_not_available[] = " not available"; const char *pmax_voices; char max_voices_available[20]; /* First computes data to be printed */ double total, voices, reverb, chorus, all_voices, voice; /* voices number */ n_voices = fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count ? fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].n_voices / fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count : 0; /* total load (%) */ total = fluid_profile_data[FLUID_PROF_WRITE].count ? fluid_profile_load(fluid_profile_data[FLUID_PROF_WRITE].total, sample_rate, fluid_profile_data[FLUID_PROF_WRITE].n_samples) : 0; /* reverb load (%) */ reverb = fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].count ? fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].total, sample_rate, fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].n_samples) : 0; /* chorus load (%) */ chorus = fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].count ? fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].total, sample_rate, fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].n_samples) : 0; /* total voices load: total - reverb - chorus (%) */ voices = total - reverb - chorus; /* One voice load (%): all_voices / n_voices. */ all_voices = fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count ? fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].total, sample_rate, fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].n_samples) : 0; voice = n_voices ? all_voices / n_voices : 0; /* estimated maximum voices number */ if(voice > 0) { FLUID_SNPRINTF(max_voices_available, sizeof(max_voices_available), "%17d", (unsigned int)((100.0 - reverb - chorus) / voice)); pmax_voices = max_voices_available; } else { pmax_voices = max_voices_not_available; } /* Now prints data */ fluid_ostream_printf(out, " ------------------------------------------------------------------------------\n"); fluid_ostream_printf(out, " Cpu loads(%%) (sr:%6.0f Hz, sp:%6.2f microsecond) and maximum voices\n", sample_rate, 1000000.0 / sample_rate); fluid_ostream_printf(out, " ------------------------------------------------------------------------------\n"); fluid_ostream_printf(out, " nVoices| total(%%)|voices(%%)| reverb(%%)|chorus(%%)| voice(%%)|estimated maxVoices\n"); fluid_ostream_printf(out, " -------|---------|---------|----------|---------|---------|-------------------\n"); fluid_ostream_printf(out, "%8d|%9.3f|%9.3f|%10.3f|%9.3f|%9.3f|%s\n", n_voices, total, voices, reverb, chorus, voice, pmax_voices); } /* * prints profiling data (used by profile shell command: prof_start). * The function is an internal profiling API between the "profile" command * prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2). * * @param sample_rate the sample rate of audio output. * @param out output stream device. * * When print mode is 1, the function prints all the information (see below). * When print mode is 0, the function prints only the cpu loads. * * ------------------------------------------------------------------------------ * Duration(microsecond) and cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) * ------------------------------------------------------------------------------ * Code under profiling |Voices| Duration (microsecond) | Load(%) * | nbr| min| avg| max| * ---------------------------|------|--------------------------------|---------- * synth_write_* ------------>| 250| 3.91| 2188.82| 3275.00| 41.544 * synth_one_block ---------->| 250| 1150.70| 2273.56| 3241.47| 41.100 * synth_one_block:clear ---->| 250| 3.07| 4.62| 61.18| 0.084 * synth_one_block:one voice->| 1| 4.19| 9.02| 1044.27| 0.163 * synth_one_block:all voices>| 250| 1138.41| 2259.11| 3217.73| 40.839 * synth_one_block:reverb --->| no profiling available * synth_one_block:chorus --->| no profiling available * voice:note --------------->| no profiling available * voice:release ------------>| no profiling available * ------------------------------------------------------------------------------ * Cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) and maximum voices * ------------------------------------------------------------------------------ * nVoices| total(%)|voices(%)| reverb(%)|chorus(%)| voice(%)|estimated maxVoices * -------|---------|---------|----------|---------|---------|------------------- * 250| 41.544| 41.544| 0.000| 0.000| 0.163| 612 */ void fluid_profiling_print_data(double sample_rate, fluid_ostream_t out) { int i; if(fluid_profile_print) { /* print all details: Duration(microsecond) and cpu loads(%) */ fluid_ostream_printf(out, " ------------------------------------------------------------------------------\n"); fluid_ostream_printf(out, " Duration(microsecond) and cpu loads(%%) (sr:%6.0f Hz, sp:%6.2f microsecond)\n", sample_rate, 1000000.0 / sample_rate); fluid_ostream_printf(out, " ------------------------------------------------------------------------------\n"); fluid_ostream_printf(out, " Code under profiling |Voices| Duration (microsecond) | Load(%%)\n"); fluid_ostream_printf(out, " | nbr| min| avg| max|\n"); fluid_ostream_printf(out, " ---------------------------|------|--------------------------------|----------\n"); for(i = 0; i < FLUID_PROFILE_NBR; i++) { unsigned int count = fluid_profile_data[i].count; if(count > 0) { /* data are available */ if(FLUID_PROF_WRITE <= i && i <= FLUID_PROF_ONE_BLOCK_CHORUS) { double load = fluid_profile_load(fluid_profile_data[i].total, sample_rate, fluid_profile_data[i].n_samples); fluid_ostream_printf(out, " %s|%6d|%10.2f|%10.2f|%10.2f|%8.3f\n", fluid_profile_data[i].description, /* code under profiling */ fluid_profile_data[i].n_voices / count, /* voices number */ fluid_profile_data[i].min, /* minimum duration */ fluid_profile_data[i].total / count, /* average duration */ fluid_profile_data[i].max, /* maximum duration */ load); /* cpu load */ } else { /* note and release duration */ fluid_ostream_printf(out, " %s|%6d|%10.0f|%10.0f|%10.0f|\n", fluid_profile_data[i].description, /* code under profiling */ fluid_profile_data[i].n_voices / count, fluid_profile_data[i].min, /* minimum duration */ fluid_profile_data[i].total / count, /* average duration */ fluid_profile_data[i].max); /* maximum duration */ } } else { /* data aren't available */ fluid_ostream_printf(out, " %s| no profiling available\n", fluid_profile_data[i].description); } } } /* prints cpu loads only */ fluid_profiling_print_load(sample_rate, out);/* prints cpu loads */ } /* Returns true if the user cancels the current profiling measurement. Actually this is implemented using the key. To add this functionality: 1) Adds #define FLUID_PROFILE_CANCEL in fluid_sys.h. 2) Adds the necessary code inside fluid_profile_is_cancel(). When FLUID_PROFILE_CANCEL is not defined, the function return FALSE. */ int fluid_profile_is_cancel_req(void) { #ifdef FLUID_PROFILE_CANCEL #if defined(WIN32) /* Windows specific stuff */ /* Profile cancellation is supported for Windows */ /* returns TRUE if key is depressed */ return(GetAsyncKeyState(VK_RETURN) & 0x1); #elif defined(__OS2__) /* OS/2 specific stuff */ /* Profile cancellation isn't yet supported for OS2 */ /* For OS2, replaces the following line with the function that returns true when the keyboard key is depressed */ return FALSE; /* default value */ #else /* POSIX stuff */ /* Profile cancellation is supported for Linux */ /* returns true is is depressed */ { /* Here select() is used to poll the standard input to see if an input is ready. As the standard input is usually buffered, the user needs to depress to set the input to a "ready" state. */ struct timeval tv; fd_set fds; /* just one fds need to be polled */ tv.tv_sec = 0; /* Setting both values to 0, means a 0 timeout */ tv.tv_usec = 0; FD_ZERO(&fds); /* reset fds */ FD_SET(STDIN_FILENO, &fds); /* sets fds to poll standard input only */ select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); /* polling */ return (FD_ISSET(0, &fds)); /* returns true if standard input is ready */ } #endif /* OS stuff */ #else /* FLUID_PROFILE_CANCEL not defined */ return FALSE; /* default value */ #endif /* FLUID_PROFILE_CANCEL */ } /** * Returns status used in shell command "prof_start". * The function is an internal profiling API between the "profile" command * prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2). * * @return status * - PROFILE_READY profiling data are ready. * - PROFILE_RUNNING, profiling data are still under acquisition. * - PROFILE_CANCELED, acquisition has been cancelled by the user. * - PROFILE_STOP, no acquisition in progress. * * When status is PROFILE_RUNNING, the caller can do passive waiting, or other * work before recalling the function later. */ int fluid_profile_get_status(void) { /* Checks if user has requested to cancel the current measurement */ /* Cancellation must have precedence over other status */ if(fluid_profile_is_cancel_req()) { fluid_profile_start_stop(0, 0); /* stops the measurement */ return PROFILE_CANCELED; } switch(fluid_profile_status) { case PROFILE_READY: return PROFILE_READY; /* profiling data are ready */ case PROFILE_START: return PROFILE_RUNNING;/* profiling data are under acquisition */ default: return PROFILE_STOP; } } /** * Starts or stops profiling measurement. * The function is an internal profiling API between the "profile" command * prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2). * * @param end_tick end position of the measure (in ticks). * - If end_tick is greater then 0, the function starts a measure if a measure * isn't running. If a measure is already running, the function does nothing * and returns. * - If end_tick is 0, the function stops a measure. * @param clear_data, * - If clear_data is 0, the function clears fluid_profile_data before starting * a measure, otherwise, the data from the started measure will be accumulated * within fluid_profile_data. */ void fluid_profile_start_stop(unsigned int end_ticks, short clear_data) { if(end_ticks) /* This is a "start" request */ { /* Checks if a measure is already running */ if(fluid_profile_status != PROFILE_START) { short i; fluid_profile_end_ticks = end_ticks; /* Clears profile data */ if(clear_data == 0) for(i = 0; i < FLUID_PROFILE_NBR; i++) { fluid_profile_data[i].min = 1e10;/* min sets to max value */ fluid_profile_data[i].max = 0; /* maximum sets to min value */ fluid_profile_data[i].total = 0; /* total duration microsecond */ fluid_profile_data[i].count = 0; /* data count */ fluid_profile_data[i].n_voices = 0; /* voices number */ fluid_profile_data[i].n_samples = 0;/* audio samples number */ } fluid_profile_status = PROFILE_START; /* starts profiling */ } /* else do nothing when profiling is already started */ } else /* This is a "stop" request */ { /* forces the current running profile (if any) to stop */ fluid_profile_status = PROFILE_STOP; /* stops profiling */ } } #endif /* WITH_PROFILING */ /*************************************************************** * * Threads * */ #if OLD_GLIB_THREAD_API /* Rather than inline this one, we just declare it as a function, to prevent * GCC warning about inline failure. */ fluid_cond_t * new_fluid_cond(void) { if(!g_thread_supported()) { g_thread_init(NULL); } return g_cond_new(); } #endif static gpointer fluid_thread_high_prio(gpointer data) { fluid_thread_info_t *info = data; fluid_thread_self_set_prio(info->prio_level); info->func(info->data); FLUID_FREE(info); return NULL; } /** * Create a new thread. * @param func Function to execute in new thread context * @param data User defined data to pass to func * @param prio_level Priority level. If greater than 0 then high priority scheduling will * be used, with the given priority level (used by pthreads only). 0 uses normal scheduling. * @param detach If TRUE, 'join' does not work and the thread destroys itself when finished. * @return New thread pointer or NULL on error */ fluid_thread_t * new_fluid_thread(const char *name, fluid_thread_func_t func, void *data, int prio_level, int detach) { GThread *thread; fluid_thread_info_t *info = NULL; GError *err = NULL; g_return_val_if_fail(func != NULL, NULL); #if OLD_GLIB_THREAD_API /* Make sure g_thread_init has been called. * FIXME - Probably not a good idea in a shared library, * but what can we do *and* remain backwards compatible? */ if(!g_thread_supported()) { g_thread_init(NULL); } #endif if(prio_level > 0) { info = FLUID_NEW(fluid_thread_info_t); if(!info) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } info->func = func; info->data = data; info->prio_level = prio_level; #if NEW_GLIB_THREAD_API thread = g_thread_try_new(name, fluid_thread_high_prio, info, &err); #else thread = g_thread_create(fluid_thread_high_prio, info, detach == FALSE, &err); #endif } else { #if NEW_GLIB_THREAD_API thread = g_thread_try_new(name, (GThreadFunc)func, data, &err); #else thread = g_thread_create((GThreadFunc)func, data, detach == FALSE, &err); #endif } if(!thread) { FLUID_LOG(FLUID_ERR, "Failed to create the thread: %s", fluid_gerror_message(err)); g_clear_error(&err); FLUID_FREE(info); return NULL; } #if NEW_GLIB_THREAD_API if(detach) { g_thread_unref(thread); // Release thread reference, if caller wants to detach } #endif return thread; } /** * Frees data associated with a thread (does not actually stop thread). * @param thread Thread to free */ void delete_fluid_thread(fluid_thread_t *thread) { /* Threads free themselves when they quit, nothing to do */ } /** * Join a thread (wait for it to terminate). * @param thread Thread to join * @return FLUID_OK */ int fluid_thread_join(fluid_thread_t *thread) { g_thread_join(thread); return FLUID_OK; } static fluid_thread_return_t fluid_timer_run(void *data) { fluid_timer_t *timer; int count = 0; int cont; long start; long delay; timer = (fluid_timer_t *)data; /* keep track of the start time for absolute positioning */ start = fluid_curtime(); while(timer->cont) { cont = (*timer->callback)(timer->data, fluid_curtime() - start); count++; if(!cont) { break; } /* to avoid incremental time errors, calculate the delay between two callbacks bringing in the "absolute" time (count * timer->msec) */ delay = (count * timer->msec) - (fluid_curtime() - start); if(delay > 0) { fluid_msleep(delay); } } FLUID_LOG(FLUID_DBG, "Timer thread finished"); if(timer->auto_destroy) { FLUID_FREE(timer); } return FLUID_THREAD_RETURN_VALUE; } fluid_timer_t * new_fluid_timer(int msec, fluid_timer_callback_t callback, void *data, int new_thread, int auto_destroy, int high_priority) { fluid_timer_t *timer; timer = FLUID_NEW(fluid_timer_t); if(timer == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); return NULL; } timer->msec = msec; timer->callback = callback; timer->data = data; timer->cont = TRUE ; timer->thread = NULL; timer->auto_destroy = auto_destroy; if(new_thread) { timer->thread = new_fluid_thread("timer", fluid_timer_run, timer, high_priority ? FLUID_SYS_TIMER_HIGH_PRIO_LEVEL : 0, FALSE); if(!timer->thread) { FLUID_FREE(timer); return NULL; } } else { fluid_timer_run(timer); /* Run directly, instead of as a separate thread */ if(auto_destroy) { /* do NOT return freed memory */ return NULL; } } return timer; } void delete_fluid_timer(fluid_timer_t *timer) { int auto_destroy; fluid_return_if_fail(timer != NULL); auto_destroy = timer->auto_destroy; timer->cont = 0; fluid_timer_join(timer); /* Shouldn't access timer now if auto_destroy enabled, since it has been destroyed */ if(!auto_destroy) { FLUID_FREE(timer); } } int fluid_timer_join(fluid_timer_t *timer) { int auto_destroy; if(timer->thread) { auto_destroy = timer->auto_destroy; fluid_thread_join(timer->thread); if(!auto_destroy) { timer->thread = NULL; } } return FLUID_OK; } /*************************************************************** * * Sockets and I/O * */ /** * Get standard in stream handle. * @return Standard in stream. */ fluid_istream_t fluid_get_stdin(void) { return STDIN_FILENO; } /** * Get standard output stream handle. * @return Standard out stream. */ fluid_ostream_t fluid_get_stdout(void) { return STDOUT_FILENO; } /** * Read a line from an input stream. * @return 0 if end-of-stream, -1 if error, non zero otherwise */ int fluid_istream_readline(fluid_istream_t in, fluid_ostream_t out, char *prompt, char *buf, int len) { #if WITH_READLINE if(in == fluid_get_stdin()) { char *line; line = readline(prompt); if(line == NULL) { return -1; } FLUID_SNPRINTF(buf, len, "%s", line); buf[len - 1] = 0; if(buf[0] != '\0') { add_history(buf); } free(line); return 1; } else #endif { fluid_ostream_printf(out, "%s", prompt); return fluid_istream_gets(in, buf, len); } } /** * Reads a line from an input stream (socket). * @param in The input socket * @param buf Buffer to store data to * @param len Maximum length to store to buf * @return 1 if a line was read, 0 on end of stream, -1 on error */ static int fluid_istream_gets(fluid_istream_t in, char *buf, int len) { char c; int n; buf[len - 1] = 0; while(--len > 0) { #ifndef WIN32 n = read(in, &c, 1); if(n == -1) { return -1; } #else /* Handle read differently depending on if its a socket or file descriptor */ if(!(in & FLUID_SOCKET_FLAG)) { // usually read() is supposed to return '\n' as last valid character of the user input // when compiled with compatibility for WinXP however, read() may return 0 (EOF) rather than '\n' // this would cause the shell to exit early n = read(in, &c, 1); if(n == -1) { return -1; } } else { #ifdef NETWORK_SUPPORT n = recv(in & ~FLUID_SOCKET_FLAG, &c, 1, 0); if(n == SOCKET_ERROR) #endif { return -1; } } #endif if(n == 0) { *buf = 0; // return 1 if read from stdin, else 0, to fix early exit of shell return (in == STDIN_FILENO); } if(c == '\n') { *buf = 0; return 1; } /* Store all characters excluding CR */ if(c != '\r') { *buf++ = c; } } return -1; } /** * Send a printf style string with arguments to an output stream (socket). * @param out Output stream * @param format printf style format string * @param ... Arguments for the printf format string * @return Number of bytes written or -1 on error */ int fluid_ostream_printf(fluid_ostream_t out, const char *format, ...) { char buf[4096]; va_list args; int len; va_start(args, format); len = FLUID_VSNPRINTF(buf, 4095, format, args); va_end(args); if(len == 0) { return 0; } if(len < 0) { printf("fluid_ostream_printf: buffer overflow"); return -1; } buf[4095] = 0; #ifndef WIN32 return write(out, buf, FLUID_STRLEN(buf)); #else { int retval; /* Handle write differently depending on if its a socket or file descriptor */ if(!(out & FLUID_SOCKET_FLAG)) { return write(out, buf, (unsigned int)FLUID_STRLEN(buf)); } #ifdef NETWORK_SUPPORT /* Socket */ retval = send(out & ~FLUID_SOCKET_FLAG, buf, (int)FLUID_STRLEN(buf), 0); return retval != SOCKET_ERROR ? retval : -1; #else return -1; #endif } #endif } #ifdef NETWORK_SUPPORT int fluid_server_socket_join(fluid_server_socket_t *server_socket) { return fluid_thread_join(server_socket->thread); } static int fluid_socket_init(void) { #ifdef _WIN32 WSADATA wsaData; int res = WSAStartup(MAKEWORD(2, 2), &wsaData); if(res != 0) { FLUID_LOG(FLUID_ERR, "Server socket creation error: WSAStartup failed: %d", res); return FLUID_FAILED; } #endif return FLUID_OK; } static void fluid_socket_cleanup(void) { #ifdef _WIN32 WSACleanup(); #endif } static int fluid_socket_get_error(void) { #ifdef _WIN32 return (int)WSAGetLastError(); #else return errno; #endif } fluid_istream_t fluid_socket_get_istream(fluid_socket_t sock) { return sock | FLUID_SOCKET_FLAG; } fluid_ostream_t fluid_socket_get_ostream(fluid_socket_t sock) { return sock | FLUID_SOCKET_FLAG; } void fluid_socket_close(fluid_socket_t sock) { if(sock != INVALID_SOCKET) { #ifdef _WIN32 closesocket(sock); #else close(sock); #endif } } static fluid_thread_return_t fluid_server_socket_run(void *data) { fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data; fluid_socket_t client_socket; #ifdef IPV6_SUPPORT struct sockaddr_in6 addr; #else struct sockaddr_in addr; #endif #ifdef HAVE_INETNTOP #ifdef IPV6_SUPPORT char straddr[INET6_ADDRSTRLEN]; #else char straddr[INET_ADDRSTRLEN]; #endif /* IPV6_SUPPORT */ #endif /* HAVE_INETNTOP */ socklen_t addrlen = sizeof(addr); int r; FLUID_MEMSET((char *)&addr, 0, sizeof(addr)); FLUID_LOG(FLUID_DBG, "Server listening for connections"); while(server_socket->cont) { client_socket = accept(server_socket->socket, (struct sockaddr *)&addr, &addrlen); FLUID_LOG(FLUID_DBG, "New client connection"); if(client_socket == INVALID_SOCKET) { if(server_socket->cont) { FLUID_LOG(FLUID_ERR, "Failed to accept connection: %d", fluid_socket_get_error()); } server_socket->cont = 0; return FLUID_THREAD_RETURN_VALUE; } else { #ifdef HAVE_INETNTOP #ifdef IPV6_SUPPORT inet_ntop(AF_INET6, &addr.sin6_addr, straddr, sizeof(straddr)); #else inet_ntop(AF_INET, &addr.sin_addr, straddr, sizeof(straddr)); #endif r = server_socket->func(server_socket->data, client_socket, straddr); #else r = server_socket->func(server_socket->data, client_socket, inet_ntoa(addr.sin_addr)); #endif if(r != 0) { fluid_socket_close(client_socket); } } } FLUID_LOG(FLUID_DBG, "Server closing"); return FLUID_THREAD_RETURN_VALUE; } fluid_server_socket_t * new_fluid_server_socket(int port, fluid_server_func_t func, void *data) { fluid_server_socket_t *server_socket; #ifdef IPV6_SUPPORT struct sockaddr_in6 addr; #else struct sockaddr_in addr; #endif fluid_socket_t sock; fluid_return_val_if_fail(func != NULL, NULL); if(fluid_socket_init() != FLUID_OK) { return NULL; } #ifdef IPV6_SUPPORT sock = socket(AF_INET6, SOCK_STREAM, 0); if(sock == INVALID_SOCKET) { FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error()); fluid_socket_cleanup(); return NULL; } FLUID_MEMSET(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_port = htons((uint16_t)port); addr.sin6_addr = in6addr_any; #else sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == INVALID_SOCKET) { FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error()); fluid_socket_cleanup(); return NULL; } FLUID_MEMSET(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port); addr.sin_addr.s_addr = htonl(INADDR_ANY); #endif if(bind(sock, (const struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) { FLUID_LOG(FLUID_ERR, "Failed to bind server socket: %d", fluid_socket_get_error()); fluid_socket_close(sock); fluid_socket_cleanup(); return NULL; } if(listen(sock, SOMAXCONN) == SOCKET_ERROR) { FLUID_LOG(FLUID_ERR, "Failed to listen on server socket: %d", fluid_socket_get_error()); fluid_socket_close(sock); fluid_socket_cleanup(); return NULL; } server_socket = FLUID_NEW(fluid_server_socket_t); if(server_socket == NULL) { FLUID_LOG(FLUID_ERR, "Out of memory"); fluid_socket_close(sock); fluid_socket_cleanup(); return NULL; } server_socket->socket = sock; server_socket->func = func; server_socket->data = data; server_socket->cont = 1; server_socket->thread = new_fluid_thread("server", fluid_server_socket_run, server_socket, 0, FALSE); if(server_socket->thread == NULL) { FLUID_FREE(server_socket); fluid_socket_close(sock); fluid_socket_cleanup(); return NULL; } return server_socket; } void delete_fluid_server_socket(fluid_server_socket_t *server_socket) { fluid_return_if_fail(server_socket != NULL); server_socket->cont = 0; if(server_socket->socket != INVALID_SOCKET) { fluid_socket_close(server_socket->socket); } if(server_socket->thread) { fluid_thread_join(server_socket->thread); delete_fluid_thread(server_socket->thread); } FLUID_FREE(server_socket); // Should be called the same number of times as fluid_socket_init() fluid_socket_cleanup(); } #endif // NETWORK_SUPPORT FILE* fluid_file_open(const char* path, const char** errMsg) { static const char ErrExist[] = "File does not exist."; static const char ErrRegular[] = "File is not regular, refusing to open it."; static const char ErrNull[] = "File does not exists or insufficient permissions to open it."; FILE* handle = NULL; if(!g_file_test(path, G_FILE_TEST_EXISTS)) { if(errMsg != NULL) { *errMsg = ErrExist; } } else if(!g_file_test(path, G_FILE_TEST_IS_REGULAR)) { if(errMsg != NULL) { *errMsg = ErrRegular; } } else if((handle = FLUID_FOPEN(path, "rb")) == NULL) { if(errMsg != NULL) { *errMsg = ErrNull; } } return handle; } fluidsynth-2.1.1/src/utils/fluid_sys.h000066400000000000000000000563001362231004000200000ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* * @file fluid_sys.h * * This header contains a bunch of (mostly) system and machine * dependent functions: * * - timers * - current time in milliseconds and microseconds * - debug logging * - profiling * - memory locking * - checking for floating point exceptions * * fluidsynth's wrapper OSAL so to say; include it in .c files, be careful to include * it in fluidsynth's private header files (see comment in fluid_coreaudio.c) */ #ifndef _FLUID_SYS_H #define _FLUID_SYS_H #include "fluidsynth_priv.h" #if HAVE_MATH_H #include #endif #if HAVE_ERRNO_H #include #endif #if HAVE_STDARG_H #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_FCNTL_H #include #endif #if HAVE_SYS_MMAN_H #include #endif #if HAVE_SYS_TYPES_H #include #endif #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_TIME_H #include #endif #if HAVE_SYS_SOCKET_H #include #endif #if HAVE_NETINET_IN_H #include #endif #if HAVE_NETINET_TCP_H #include #endif #if HAVE_ARPA_INET_H #include #endif #if HAVE_LIMITS_H #include #endif #if HAVE_OPENMP #include #endif #if HAVE_IO_H #include // _open(), _close(), read(), write() on windows #endif #if HAVE_SIGNAL_H #include #endif /** Integer types */ #if HAVE_STDINT_H #include #else /* Assume GLIB types */ typedef gint8 int8_t; typedef guint8 uint8_t; typedef gint16 int16_t; typedef guint16 uint16_t; typedef gint32 int32_t; typedef guint32 uint32_t; typedef gint64 int64_t; typedef guint64 uint64_t; typedef guintptr uintptr_t; typedef gintptr intptr_t; #endif #if defined(WIN32) && HAVE_WINDOWS_H #include #include /* Provides also socklen_t */ /* WIN32 special defines */ #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #ifdef _MSC_VER #pragma warning(disable : 4244) #pragma warning(disable : 4101) #pragma warning(disable : 4305) #pragma warning(disable : 4996) #endif #endif /* Darwin special defines (taken from config_macosx.h) */ #ifdef DARWIN # define MACINTOSH # define __Types__ #endif #ifdef LADSPA #include #endif #include /** * Macro used for safely accessing a message from a GError and using a default * message if it is NULL. * @param err Pointer to a GError to access the message field of. * @return Message string */ #define fluid_gerror_message(err) ((err) ? err->message : "No error details") #define FLUID_INLINE inline /* Integer<->pointer conversion */ #define FLUID_POINTER_TO_UINT(x) ((unsigned int)(uintptr_t)(x)) #define FLUID_UINT_TO_POINTER(x) ((void *)(uintptr_t)(x)) #define FLUID_POINTER_TO_INT(x) ((signed int)(intptr_t)(x)) #define FLUID_INT_TO_POINTER(x) ((void *)(intptr_t)(x)) /* Endian detection */ #define FLUID_IS_BIG_ENDIAN (G_BYTE_ORDER == G_BIG_ENDIAN) #define FLUID_LE32TOH(x) GINT32_FROM_LE(x) #define FLUID_LE16TOH(x) GINT16_FROM_LE(x) #if FLUID_IS_BIG_ENDIAN #define FLUID_FOURCC(_a, _b, _c, _d) \ (uint32_t)(((uint32_t)(_a) << 24) | ((uint32_t)(_b) << 16) | ((uint32_t)(_c) << 8) | (uint32_t)(_d)) #else #define FLUID_FOURCC(_a, _b, _c, _d) \ (uint32_t)(((uint32_t)(_d) << 24) | ((uint32_t)(_c) << 16) | ((uint32_t)(_b) << 8) | (uint32_t)(_a)) #endif /* * Utility functions */ char *fluid_strtok(char **str, char *delim); #if defined(__OS2__) #define INCL_DOS #include typedef int socklen_t; #endif /** Time functions */ unsigned int fluid_curtime(void); double fluid_utime(void); /** Timers */ /* if the callback function returns 1 the timer will continue; if it returns 0 it will stop */ typedef int (*fluid_timer_callback_t)(void *data, unsigned int msec); typedef struct _fluid_timer_t fluid_timer_t; fluid_timer_t *new_fluid_timer(int msec, fluid_timer_callback_t callback, void *data, int new_thread, int auto_destroy, int high_priority); void delete_fluid_timer(fluid_timer_t *timer); int fluid_timer_join(fluid_timer_t *timer); int fluid_timer_stop(fluid_timer_t *timer); // Macros to use for pre-processor if statements to test which Glib thread API we have (pre or post 2.32) #define NEW_GLIB_THREAD_API (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 32)) #define OLD_GLIB_THREAD_API (GLIB_MAJOR_VERSION < 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32)) /* Muteces */ #if NEW_GLIB_THREAD_API /* glib 2.32 and newer */ /* Regular mutex */ typedef GMutex fluid_mutex_t; #define FLUID_MUTEX_INIT { 0 } #define fluid_mutex_init(_m) g_mutex_init (&(_m)) #define fluid_mutex_destroy(_m) g_mutex_clear (&(_m)) #define fluid_mutex_lock(_m) g_mutex_lock(&(_m)) #define fluid_mutex_unlock(_m) g_mutex_unlock(&(_m)) /* Recursive lock capable mutex */ typedef GRecMutex fluid_rec_mutex_t; #define fluid_rec_mutex_init(_m) g_rec_mutex_init(&(_m)) #define fluid_rec_mutex_destroy(_m) g_rec_mutex_clear(&(_m)) #define fluid_rec_mutex_lock(_m) g_rec_mutex_lock(&(_m)) #define fluid_rec_mutex_unlock(_m) g_rec_mutex_unlock(&(_m)) /* Dynamically allocated mutex suitable for fluid_cond_t use */ typedef GMutex fluid_cond_mutex_t; #define fluid_cond_mutex_lock(m) g_mutex_lock(m) #define fluid_cond_mutex_unlock(m) g_mutex_unlock(m) static FLUID_INLINE fluid_cond_mutex_t * new_fluid_cond_mutex(void) { GMutex *mutex; mutex = g_new(GMutex, 1); g_mutex_init(mutex); return (mutex); } static FLUID_INLINE void delete_fluid_cond_mutex(fluid_cond_mutex_t *m) { fluid_return_if_fail(m != NULL); g_mutex_clear(m); g_free(m); } /* Thread condition signaling */ typedef GCond fluid_cond_t; #define fluid_cond_signal(cond) g_cond_signal(cond) #define fluid_cond_broadcast(cond) g_cond_broadcast(cond) #define fluid_cond_wait(cond, mutex) g_cond_wait(cond, mutex) static FLUID_INLINE fluid_cond_t * new_fluid_cond(void) { GCond *cond; cond = g_new(GCond, 1); g_cond_init(cond); return (cond); } static FLUID_INLINE void delete_fluid_cond(fluid_cond_t *cond) { fluid_return_if_fail(cond != NULL); g_cond_clear(cond); g_free(cond); } /* Thread private data */ typedef GPrivate fluid_private_t; #define fluid_private_init(_priv) memset (&_priv, 0, sizeof (_priv)) #define fluid_private_free(_priv) #define fluid_private_get(_priv) g_private_get(&(_priv)) #define fluid_private_set(_priv, _data) g_private_set(&(_priv), _data) #else /* glib prior to 2.32 */ /* Regular mutex */ typedef GStaticMutex fluid_mutex_t; #define FLUID_MUTEX_INIT G_STATIC_MUTEX_INIT #define fluid_mutex_destroy(_m) g_static_mutex_free(&(_m)) #define fluid_mutex_lock(_m) g_static_mutex_lock(&(_m)) #define fluid_mutex_unlock(_m) g_static_mutex_unlock(&(_m)) #define fluid_mutex_init(_m) do { \ if (!g_thread_supported ()) g_thread_init (NULL); \ g_static_mutex_init (&(_m)); \ } while(0) /* Recursive lock capable mutex */ typedef GStaticRecMutex fluid_rec_mutex_t; #define fluid_rec_mutex_destroy(_m) g_static_rec_mutex_free(&(_m)) #define fluid_rec_mutex_lock(_m) g_static_rec_mutex_lock(&(_m)) #define fluid_rec_mutex_unlock(_m) g_static_rec_mutex_unlock(&(_m)) #define fluid_rec_mutex_init(_m) do { \ if (!g_thread_supported ()) g_thread_init (NULL); \ g_static_rec_mutex_init (&(_m)); \ } while(0) /* Dynamically allocated mutex suitable for fluid_cond_t use */ typedef GMutex fluid_cond_mutex_t; #define delete_fluid_cond_mutex(m) g_mutex_free(m) #define fluid_cond_mutex_lock(m) g_mutex_lock(m) #define fluid_cond_mutex_unlock(m) g_mutex_unlock(m) static FLUID_INLINE fluid_cond_mutex_t * new_fluid_cond_mutex(void) { if(!g_thread_supported()) { g_thread_init(NULL); } return g_mutex_new(); } /* Thread condition signaling */ typedef GCond fluid_cond_t; fluid_cond_t *new_fluid_cond(void); #define delete_fluid_cond(cond) g_cond_free(cond) #define fluid_cond_signal(cond) g_cond_signal(cond) #define fluid_cond_broadcast(cond) g_cond_broadcast(cond) #define fluid_cond_wait(cond, mutex) g_cond_wait(cond, mutex) /* Thread private data */ typedef GStaticPrivate fluid_private_t; #define fluid_private_get(_priv) g_static_private_get(&(_priv)) #define fluid_private_set(_priv, _data) g_static_private_set(&(_priv), _data, NULL) #define fluid_private_free(_priv) g_static_private_free(&(_priv)) #define fluid_private_init(_priv) do { \ if (!g_thread_supported ()) g_thread_init (NULL); \ g_static_private_init (&(_priv)); \ } while(0) #endif /* Atomic operations */ #define fluid_atomic_int_inc(_pi) g_atomic_int_inc(_pi) #define fluid_atomic_int_get(_pi) g_atomic_int_get(_pi) #define fluid_atomic_int_set(_pi, _val) g_atomic_int_set(_pi, _val) #define fluid_atomic_int_dec_and_test(_pi) g_atomic_int_dec_and_test(_pi) #define fluid_atomic_int_compare_and_exchange(_pi, _old, _new) \ g_atomic_int_compare_and_exchange(_pi, _old, _new) #if GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 30) #define fluid_atomic_int_exchange_and_add(_pi, _add) \ g_atomic_int_add(_pi, _add) #define fluid_atomic_int_add(_pi, _add) \ g_atomic_int_add(_pi, _add) #else #define fluid_atomic_int_exchange_and_add(_pi, _add) \ g_atomic_int_exchange_and_add(_pi, _add) #define fluid_atomic_int_add(_pi, _add) \ g_atomic_int_exchange_and_add(_pi, _add) #endif #define fluid_atomic_pointer_get(_pp) g_atomic_pointer_get(_pp) #define fluid_atomic_pointer_set(_pp, val) g_atomic_pointer_set(_pp, val) #define fluid_atomic_pointer_compare_and_exchange(_pp, _old, _new) \ g_atomic_pointer_compare_and_exchange(_pp, _old, _new) static FLUID_INLINE void fluid_atomic_float_set(volatile float *fptr, float val) { int32_t ival; memcpy(&ival, &val, 4); fluid_atomic_int_set((volatile int *)fptr, ival); } static FLUID_INLINE float fluid_atomic_float_get(volatile float *fptr) { int32_t ival; float fval; ival = fluid_atomic_int_get((volatile int *)fptr); memcpy(&fval, &ival, 4); return fval; } /* Threads */ /* other thread implementations might change this for their needs */ typedef void *fluid_thread_return_t; /* static return value for thread functions which requires a return value */ #define FLUID_THREAD_RETURN_VALUE (NULL) typedef GThread fluid_thread_t; typedef fluid_thread_return_t (*fluid_thread_func_t)(void *data); #define FLUID_THREAD_ID_NULL NULL /* A NULL "ID" value */ #define fluid_thread_id_t GThread * /* Data type for a thread ID */ #define fluid_thread_get_id() g_thread_self() /* Get unique "ID" for current thread */ fluid_thread_t *new_fluid_thread(const char *name, fluid_thread_func_t func, void *data, int prio_level, int detach); void delete_fluid_thread(fluid_thread_t *thread); void fluid_thread_self_set_prio(int prio_level); int fluid_thread_join(fluid_thread_t *thread); /* Dynamic Module Loading, currently only used by LADSPA subsystem */ #ifdef LADSPA typedef GModule fluid_module_t; #define fluid_module_open(_name) g_module_open((_name), G_MODULE_BIND_LOCAL) #define fluid_module_close(_mod) g_module_close(_mod) #define fluid_module_error() g_module_error() #define fluid_module_name(_mod) g_module_name(_mod) #define fluid_module_symbol(_mod, _name, _ptr) g_module_symbol((_mod), (_name), (_ptr)) #endif /* LADSPA */ /* Sockets and I/O */ int fluid_istream_readline(fluid_istream_t in, fluid_ostream_t out, char *prompt, char *buf, int len); int fluid_ostream_printf(fluid_ostream_t out, const char *format, ...); #if defined(WIN32) typedef SOCKET fluid_socket_t; #else typedef int fluid_socket_t; #endif /* The function should return 0 if no error occurred, non-zero otherwise. If the function return non-zero, the socket will be closed by the server. */ typedef int (*fluid_server_func_t)(void *data, fluid_socket_t client_socket, char *addr); fluid_server_socket_t *new_fluid_server_socket(int port, fluid_server_func_t func, void *data); void delete_fluid_server_socket(fluid_server_socket_t *sock); int fluid_server_socket_join(fluid_server_socket_t *sock); void fluid_socket_close(fluid_socket_t sock); fluid_istream_t fluid_socket_get_istream(fluid_socket_t sock); fluid_ostream_t fluid_socket_get_ostream(fluid_socket_t sock); /* File access */ #define fluid_stat(_filename, _statbuf) g_stat((_filename), (_statbuf)) #if !GLIB_CHECK_VERSION(2, 26, 0) /* GStatBuf has not been introduced yet, manually typedef to what they had at that time: * https://github.com/GNOME/glib/blob/e7763678b56e3be073cc55d707a6e92fc2055ee0/glib/gstdio.h#L98-L115 */ #if defined(WIN32) || HAVE_WINDOWS_H // somehow reliably mock G_OS_WIN32?? // Any effort from our side to reliably mock GStatBuf on Windows is in vain. E.g. glib-2.16 is broken as it uses struct stat rather than struct _stat32 on Win x86. // Disable it (the user has been warned by cmake). #undef fluid_stat #define fluid_stat(_filename, _statbuf) (-1) typedef struct _fluid_stat_buf_t{int st_mtime;} fluid_stat_buf_t; #else /* posix, OS/2, etc. */ typedef struct stat fluid_stat_buf_t; #endif #else typedef GStatBuf fluid_stat_buf_t; #endif FILE* fluid_file_open(const char* filename, const char** errMsg); /* Profiling */ #if WITH_PROFILING /** profiling interface between Profiling command shell and Audio rendering API (FluidProfile_0004.pdf- 3.2.2) */ /* ----------------------------------------------------------------------------- Shell task side | Profiling interface | Audio task side ----------------------------------------------------------------------------- profiling | Internal | | | Audio command <---> |<-- profling -->| Data |<--macros -->| <--> rendering shell | API | | | API */ /* default parameters for shell command "prof_start" in fluid_sys.c */ #define FLUID_PROFILE_DEFAULT_BANK 0 /* default bank */ #define FLUID_PROFILE_DEFAULT_PROG 16 /* default prog (organ) */ #define FLUID_PROFILE_FIRST_KEY 12 /* first key generated */ #define FLUID_PROFILE_LAST_KEY 108 /* last key generated */ #define FLUID_PROFILE_DEFAULT_VEL 64 /* default note velocity */ #define FLUID_PROFILE_VOICE_ATTEN -0.04f /* gain attenuation per voice (dB) */ #define FLUID_PROFILE_DEFAULT_PRINT 0 /* default print mode */ #define FLUID_PROFILE_DEFAULT_N_PROF 1 /* default number of measures */ #define FLUID_PROFILE_DEFAULT_DURATION 500 /* default duration (ms) */ extern unsigned short fluid_profile_notes; /* number of generated notes */ extern unsigned char fluid_profile_bank; /* bank,prog preset used by */ extern unsigned char fluid_profile_prog; /* generated notes */ extern unsigned char fluid_profile_print; /* print mode */ extern unsigned short fluid_profile_n_prof;/* number of measures */ extern unsigned short fluid_profile_dur; /* measure duration in ms */ extern fluid_atomic_int_t fluid_profile_lock ; /* lock between multiple shell */ /**/ /*---------------------------------------------- Internal profiling API (in fluid_sys.c) -----------------------------------------------*/ /* Starts a profiling measure used in shell command "prof_start" */ void fluid_profile_start_stop(unsigned int end_ticks, short clear_data); /* Returns status used in shell command "prof_start" */ int fluid_profile_get_status(void); /* Prints profiling data used in shell command "prof_start" */ void fluid_profiling_print_data(double sample_rate, fluid_ostream_t out); /* Returns True if profiling cancellation has been requested */ int fluid_profile_is_cancel_req(void); /* For OS that implement key for profile cancellation: 1) Adds #define FLUID_PROFILE_CANCEL 2) Adds the necessary code inside fluid_profile_is_cancel() see fluid_sys.c */ #if defined(WIN32) /* Profile cancellation is supported for Windows */ #define FLUID_PROFILE_CANCEL #elif defined(__OS2__) /* OS/2 specific stuff */ /* Profile cancellation isn't yet supported for OS2 */ #else /* POSIX stuff */ #define FLUID_PROFILE_CANCEL /* Profile cancellation is supported for linux */ #include /* STDIN_FILENO */ #include /* select() */ #endif /* posix */ /* logging profiling data (used on synthesizer instance deletion) */ void fluid_profiling_print(void); /*---------------------------------------------- Profiling Data (in fluid_sys.c) -----------------------------------------------*/ /** Profiling data. Keep track of min/avg/max values to profile a piece of code. */ typedef struct _fluid_profile_data_t { const char *description; /* name of the piece of code under profiling */ double min, max, total; /* duration (microsecond) */ unsigned int count; /* total count */ unsigned int n_voices; /* voices number */ unsigned int n_samples; /* audio samples number */ } fluid_profile_data_t; enum { /* commands/status (profiling interface) */ PROFILE_STOP, /* command to stop a profiling measure */ PROFILE_START, /* command to start a profile measure */ PROFILE_READY, /* status to signal that a profiling measure has finished and ready to be printed */ /*- State returned by fluid_profile_get_status() -*/ /* between profiling commands and internal profiling API */ PROFILE_RUNNING, /* a profiling measure is running */ PROFILE_CANCELED,/* a profiling measure has been canceled */ }; /* Data interface */ extern unsigned char fluid_profile_status ; /* command and status */ extern unsigned int fluid_profile_end_ticks; /* ending position (in ticks) */ extern fluid_profile_data_t fluid_profile_data[]; /* Profiling data */ /*---------------------------------------------- Probes macros -----------------------------------------------*/ /** Macro to obtain a time reference used for the profiling */ #define fluid_profile_ref() fluid_utime() /** Macro to create a variable and assign the current reference time for profiling. * So we don't get unused variable warnings when profiling is disabled. */ #define fluid_profile_ref_var(name) double name = fluid_utime() /** * Profile identifier numbers. List all the pieces of code you want to profile * here. Be sure to add an entry in the fluid_profile_data table in * fluid_sys.c */ enum { FLUID_PROF_WRITE, FLUID_PROF_ONE_BLOCK, FLUID_PROF_ONE_BLOCK_CLEAR, FLUID_PROF_ONE_BLOCK_VOICE, FLUID_PROF_ONE_BLOCK_VOICES, FLUID_PROF_ONE_BLOCK_REVERB, FLUID_PROF_ONE_BLOCK_CHORUS, FLUID_PROF_VOICE_NOTE, FLUID_PROF_VOICE_RELEASE, FLUID_PROFILE_NBR /* number of profile probes */ }; /** Those macros are used to calculate the min/avg/max. Needs a profile number, a time reference, the voices and samples number. */ /* local macro : acquiere data */ #define fluid_profile_data(_num, _ref, voices, samples)\ {\ double _now = fluid_utime();\ double _delta = _now - _ref;\ fluid_profile_data[_num].min = _delta < fluid_profile_data[_num].min ?\ _delta : fluid_profile_data[_num].min; \ fluid_profile_data[_num].max = _delta > fluid_profile_data[_num].max ?\ _delta : fluid_profile_data[_num].max;\ fluid_profile_data[_num].total += _delta;\ fluid_profile_data[_num].count++;\ fluid_profile_data[_num].n_voices += voices;\ fluid_profile_data[_num].n_samples += samples;\ _ref = _now;\ } /** Macro to collect data, called from inner functions inside audio rendering API */ #define fluid_profile(_num, _ref, voices, samples)\ {\ if ( fluid_profile_status == PROFILE_START)\ { /* acquires data */\ fluid_profile_data(_num, _ref, voices, samples)\ }\ } /** Macro to collect data, called from audio rendering API (fluid_write_xxxx()). This macro control profiling ending position (in ticks). */ #define fluid_profile_write(_num, _ref, voices, samples)\ {\ if (fluid_profile_status == PROFILE_START)\ {\ /* acquires data first: must be done before checking that profile is finished to ensure at least one valid data sample. */\ fluid_profile_data(_num, _ref, voices, samples)\ if (fluid_synth_get_ticks(synth) >= fluid_profile_end_ticks)\ {\ /* profiling is finished */\ fluid_profile_status = PROFILE_READY;\ }\ }\ } #else /* No profiling */ #define fluid_profiling_print() #define fluid_profile_ref() 0 #define fluid_profile_ref_var(name) #define fluid_profile(_num,_ref,voices, samples) #define fluid_profile_write(_num,_ref, voices, samples) #endif /* WITH_PROFILING */ /** Memory locking Memory locking is used to avoid swapping of the large block of sample data. */ #if defined(HAVE_SYS_MMAN_H) && !defined(__OS2__) #define fluid_mlock(_p,_n) mlock(_p, _n) #define fluid_munlock(_p,_n) munlock(_p,_n) #else #define fluid_mlock(_p,_n) 0 #define fluid_munlock(_p,_n) #endif /** Floating point exceptions fluid_check_fpe() checks for "unnormalized numbers" and other exceptions of the floating point processsor. */ #ifdef FPE_CHECK #define fluid_check_fpe(expl) fluid_check_fpe_i386(expl) #define fluid_clear_fpe() fluid_clear_fpe_i386() unsigned int fluid_check_fpe_i386(char *explanation_in_case_of_fpe); void fluid_clear_fpe_i386(void); #else #define fluid_check_fpe(expl) #define fluid_clear_fpe() #endif /* System control */ void fluid_msleep(unsigned int msecs); /** * Advances the given \c ptr to the next \c alignment byte boundary. * Make sure you've allocated an extra of \c alignment bytes to avoid a buffer overflow. * * @note \c alignment must be a power of two * @return Returned pointer is guaranteed to be aligned to \c alignment boundary and in range \f[ ptr <= returned_ptr < ptr + alignment \f]. */ static FLUID_INLINE void *fluid_align_ptr(const void *ptr, unsigned int alignment) { uintptr_t ptr_int = (uintptr_t)ptr; unsigned int offset = ptr_int & (alignment - 1); unsigned int add = (alignment - offset) & (alignment - 1); // advance the pointer to the next alignment boundary ptr_int += add; /* assert alignment is power of two */ FLUID_ASSERT(!(alignment == 0) && !(alignment & (alignment - 1))); return (void *)ptr_int; } #define FLUID_DEFAULT_ALIGNMENT (64U) #endif /* _FLUID_SYS_H */ fluidsynth-2.1.1/src/utils/fluidsynth_priv.h000066400000000000000000000222331362231004000212260ustar00rootroot00000000000000/* FluidSynth - A Software Synthesizer * * Copyright (C) 2003 Peter Hanappe and others. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA */ /* * @file fluidsynth_priv.h * * lightweight part of fluid_sys.h, containing forward declarations of fluidsynth's private types and private macros * * include this one file in fluidsynth's private header files */ #ifndef _FLUIDSYNTH_PRIV_H #define _FLUIDSYNTH_PRIV_H #include #include "config.h" #if HAVE_STDLIB_H #include // malloc, free #endif #if HAVE_STDIO_H #include // printf #endif #if HAVE_STRING_H #include #endif #include "fluidsynth.h" /*************************************************************** * * BASIC TYPES */ #if defined(WITH_FLOAT) typedef float fluid_real_t; #else typedef double fluid_real_t; #endif #if defined(SUPPORTS_VLA) # define FLUID_DECLARE_VLA(_type, _name, _len) \ _type _name[_len] #else # define FLUID_DECLARE_VLA(_type, _name, _len) \ _type* _name = g_newa(_type, (_len)) #endif /** Atomic types */ typedef int fluid_atomic_int_t; typedef unsigned int fluid_atomic_uint_t; typedef float fluid_atomic_float_t; /*************************************************************** * * FORWARD DECLARATIONS */ typedef struct _fluid_env_data_t fluid_env_data_t; typedef struct _fluid_adriver_definition_t fluid_adriver_definition_t; typedef struct _fluid_channel_t fluid_channel_t; typedef struct _fluid_tuning_t fluid_tuning_t; typedef struct _fluid_hashtable_t fluid_hashtable_t; typedef struct _fluid_client_t fluid_client_t; typedef struct _fluid_server_socket_t fluid_server_socket_t; typedef struct _fluid_sample_timer_t fluid_sample_timer_t; typedef struct _fluid_zone_range_t fluid_zone_range_t; typedef struct _fluid_rvoice_eventhandler_t fluid_rvoice_eventhandler_t; /* Declare rvoice related typedefs here instead of fluid_rvoice.h, as it's needed * in fluid_lfo.c and fluid_adsr.c as well */ typedef union _fluid_rvoice_param_t { void *ptr; int i; fluid_real_t real; } fluid_rvoice_param_t; enum { MAX_EVENT_PARAMS = 6 }; /**< Maximum number of #fluid_rvoice_param_t to be passed to an #fluid_rvoice_function_t */ typedef void (*fluid_rvoice_function_t)(void *obj, const fluid_rvoice_param_t param[MAX_EVENT_PARAMS]); /* Macro for declaring an rvoice event function (#fluid_rvoice_function_t). The functions may only access * those params that were previously set in fluid_voice.c */ #define DECLARE_FLUID_RVOICE_FUNCTION(name) void name(void* obj, const fluid_rvoice_param_t param[MAX_EVENT_PARAMS]) /*************************************************************** * * CONSTANTS */ #define FLUID_BUFSIZE 64 /**< FluidSynth internal buffer size (in samples) */ #define FLUID_MIXER_MAX_BUFFERS_DEFAULT (8192/FLUID_BUFSIZE) /**< Number of buffers that can be processed in one rendering run */ #define FLUID_MAX_EVENTS_PER_BUFSIZE 1024 /**< Maximum queued MIDI events per #FLUID_BUFSIZE */ #define FLUID_MAX_RETURN_EVENTS 1024 /**< Maximum queued synthesis thread return events */ #define FLUID_MAX_EVENT_QUEUES 16 /**< Maximum number of unique threads queuing events */ #define FLUID_DEFAULT_AUDIO_RT_PRIO 60 /**< Default setting for audio.realtime-prio */ #define FLUID_DEFAULT_MIDI_RT_PRIO 50 /**< Default setting for midi.realtime-prio */ #define FLUID_NUM_MOD 64 /**< Maximum number of modulators in a voice */ /*************************************************************** * * SYSTEM INTERFACE */ /* Math constants */ #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 #endif #ifndef M_LN2 #define M_LN2 0.69314718055994530941723212145818 #endif #ifndef M_LN10 #define M_LN10 2.3025850929940456840179914546844 #endif #define FLUID_M_PI ((fluid_real_t)M_PI) #define FLUID_M_LN2 ((fluid_real_t)M_LN2) #define FLUID_M_LN10 ((fluid_real_t)M_LN10) /* Math functions */ #if defined WITH_FLOAT && defined HAVE_SINF #define FLUID_SIN sinf #else #define FLUID_SIN (fluid_real_t)sin #endif #if defined WITH_FLOAT && defined HAVE_COSF #define FLUID_COS cosf #else #define FLUID_COS (fluid_real_t)cos #endif #if defined WITH_FLOAT && defined HAVE_FABSF #define FLUID_FABS fabsf #else #define FLUID_FABS (fluid_real_t)fabs #endif #if defined WITH_FLOAT && defined HAVE_POWF #define FLUID_POW powf #else #define FLUID_POW (fluid_real_t)pow #endif #if defined WITH_FLOAT && defined HAVE_SQRTF #define FLUID_SQRT sqrtf #else #define FLUID_SQRT (fluid_real_t)sqrt #endif #if defined WITH_FLOAT && defined HAVE_LOGF #define FLUID_LOGF logf #else #define FLUID_LOGF (fluid_real_t)log #endif /* Memory allocation */ #define FLUID_MALLOC(_n) fluid_alloc(_n) #define FLUID_REALLOC(_p,_n) realloc(_p,_n) #define FLUID_FREE(_p) fluid_free(_p) #define FLUID_NEW(_t) (_t*)FLUID_MALLOC(sizeof(_t)) #define FLUID_ARRAY_ALIGNED(_t,_n,_a) (_t*)FLUID_MALLOC((_n)*sizeof(_t) + ((unsigned int)_a - 1u)) #define FLUID_ARRAY(_t,_n) FLUID_ARRAY_ALIGNED(_t,_n,1u) void* fluid_alloc(size_t len); /* File access */ #define FLUID_FOPEN(_f,_m) fopen(_f,_m) #define FLUID_FCLOSE(_f) fclose(_f) #define FLUID_FREAD(_p,_s,_n,_f) fread(_p,_s,_n,_f) #define FLUID_FSEEK(_f,_n,_set) fseek(_f,_n,_set) #define FLUID_FTELL(_f) ftell(_f) /* Memory functions */ #define FLUID_MEMCPY(_dst,_src,_n) memcpy(_dst,_src,_n) #define FLUID_MEMSET(_s,_c,_n) memset(_s,_c,_n) /* String functions */ #define FLUID_STRLEN(_s) strlen(_s) #define FLUID_STRCMP(_s,_t) strcmp(_s,_t) #define FLUID_STRNCMP(_s,_t,_n) strncmp(_s,_t,_n) #define FLUID_STRCPY(_dst,_src) strcpy(_dst,_src) #define FLUID_STRNCPY(_dst,_src,_n) \ do { strncpy(_dst,_src,_n); \ (_dst)[(_n)-1]='\0'; \ }while(0) #define FLUID_STRCHR(_s,_c) strchr(_s,_c) #define FLUID_STRRCHR(_s,_c) strrchr(_s,_c) #ifdef strdup #define FLUID_STRDUP(s) strdup(s) #else #define FLUID_STRDUP(s) FLUID_STRCPY(FLUID_MALLOC(FLUID_STRLEN(s) + 1), s) #endif #define FLUID_SPRINTF sprintf #define FLUID_FPRINTF fprintf #if (defined(WIN32) && _MSC_VER < 1900) || defined(MINGW32) /* need to make sure we use a C99 compliant implementation of (v)snprintf(), * i.e. not microsofts non compliant extension _snprintf() as it doesn't * reliably null-terminate the buffer */ #define FLUID_SNPRINTF g_snprintf #else #define FLUID_SNPRINTF snprintf #endif #if (defined(WIN32) && _MSC_VER < 1500) || defined(MINGW32) #define FLUID_VSNPRINTF g_vsnprintf #else #define FLUID_VSNPRINTF vsnprintf #endif #if defined(WIN32) && !defined(MINGW32) #define FLUID_STRCASECMP _stricmp #else #define FLUID_STRCASECMP strcasecmp #endif #if defined(WIN32) && !defined(MINGW32) #define FLUID_STRNCASECMP _strnicmp #else #define FLUID_STRNCASECMP strncasecmp #endif #define fluid_clip(_val, _min, _max) \ { (_val) = ((_val) < (_min))? (_min) : (((_val) > (_max))? (_max) : (_val)); } #if WITH_FTS #define FLUID_PRINTF post #define FLUID_FLUSH() #else #define FLUID_PRINTF printf #define FLUID_FLUSH() fflush(stdout) #endif /* People who want to reduce the size of the may do this by entirely * removing the logging system. This will cause all log messages to * be discarded at compile time, allowing to save about 80 KiB for * the compiled binary. */ #if 0 #define FLUID_LOG (void)sizeof #else #define FLUID_LOG fluid_log #endif #if defined(DEBUG) && !defined(NDEBUG) #define FLUID_ASSERT(a) g_assert(a) #else #define FLUID_ASSERT(a) #endif #define FLUID_LIKELY G_LIKELY #define FLUID_UNLIKELY G_UNLIKELY /* Misc */ #if defined(__INTEL_COMPILER) #define FLUID_RESTRICT restrict #elif defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #define FLUID_RESTRICT __restrict__ #elif defined(_MSC_VER) && _MSC_VER >= 1400 #define FLUID_RESTRICT __restrict #else #warning "Dont know how this compiler handles restrict pointers, refuse to use them." #define FLUID_RESTRICT #endif #define FLUID_N_ELEMENTS(struct) (sizeof (struct) / sizeof (struct[0])) #define FLUID_MEMBER_SIZE(struct, member) ( sizeof (((struct *)0)->member) ) #define fluid_return_if_fail(cond) \ if(cond) \ ; \ else \ return #define fluid_return_val_if_fail(cond, val) \ fluid_return_if_fail(cond) (val) #endif /* _FLUIDSYNTH_PRIV_H */ fluidsynth-2.1.1/test/000077500000000000000000000000001362231004000146525ustar00rootroot00000000000000fluidsynth-2.1.1/test/CMakeLists.txt000066400000000000000000000015171362231004000174160ustar00rootroot00000000000000 ENABLE_TESTING() include ( FluidUnitTest ) # first define the test target, used by the macros below add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) ## add unit tests here ## ADD_FLUID_TEST(test_sample_cache) ADD_FLUID_TEST(test_sfont_loading) ADD_FLUID_TEST(test_sample_rate_change) # ADD_FLUID_TEST(test_preset_sample_loading) ADD_FLUID_TEST(test_pointer_alignment) ADD_FLUID_TEST(test_seqbind_unregister) ADD_FLUID_TEST(test_synth_chorus_reverb) ADD_FLUID_TEST(test_snprintf) ADD_FLUID_TEST(test_synth_process) ADD_FLUID_TEST(test_ct2hz) ADD_FLUID_TEST(test_sample_validate) ADD_FLUID_TEST(test_seq_event_queue_sort) ADD_FLUID_TEST(test_seq_scale) ADD_FLUID_TEST(test_jack_obtaining_synth) # if ( LIBSNDFILE_HASVORBIS ) # ADD_FLUID_TEST(test_sf3_sfont_loading) # endif ( LIBSNDFILE_HASVORBIS ) fluidsynth-2.1.1/test/README.md000066400000000000000000000020551362231004000161330ustar00rootroot00000000000000 This directory contains small executables to verify fluidsynths correct behaviour, i.e. unit tests. #### Do *not* blindly use the tests as template for your application! Although some tests might serve as educational demonstration of how to use certain parts of fluidsynth, they are **not** intended to do so! It is most likely that those tests will consist of many hacky parts that are necessary to test fluidsynth (e.g. including fluidsynth's private headers to access internal data types and functions). For user applications this programming style is strongly discouraged! Keep referring to the documentation and code examples listed in the [API documentation](http://www.fluidsynth.org/api/). #### Developers To add a unit test just duplicate an existing one, give it a unique name and update the CMakeLists.txt by * adding a call to `ADD_FLUID_TEST()` and * a dependency to the custom `check` target. Execute the tests via `make check`. Unit tests should use the `VintageDreamsWaves-v2.sf2` as test soundfont. Use the `TEST_SOUNDFONT` macro to access it. fluidsynth-2.1.1/test/test.h000066400000000000000000000021711362231004000160030ustar00rootroot00000000000000 #pragma once #include "config.h" #include #include /* * We call abort() because on Linux this sends a signal to the process and causes the debugger to break. * * MSVC++ runtime opens a dialog when abort() is called, saying that abort() has been called and you can * click "Retry" to make the debugger break. * When executed by CI build however, the dialog causes the unit tests to be stuck forever. * Thus suppress the dialog on windows. * MinGW however requires explicit linking against MSVCRT >= 8.0 for _set_abort_behavior(). * It's not worth the hassle to implement this with cmake... */ #if defined(NO_GUI) && defined(MINGW32) #define TEST_ABORT exit(EXIT_FAILURE); #elif defined(NO_GUI) && defined(WIN32) #define TEST_ABORT _set_abort_behavior(0, _WRITE_ABORT_MSG); abort() #else #define TEST_ABORT abort() #endif #define TEST_ASSERT(COND) do { if (!(COND)) { fprintf(stderr, __FILE__ ":%d assertion (%s) failed\n", __LINE__, #COND); TEST_ABORT; } } while (0) /* macro to test whether a fluidsynth function succeeded or not */ #define TEST_SUCCESS(FLUID_FUNCT) TEST_ASSERT((FLUID_FUNCT) != FLUID_FAILED) fluidsynth-2.1.1/test/test_ct2hz.c000066400000000000000000000024451362231004000171140ustar00rootroot00000000000000 #include "test.h" #include "utils/fluid_conv.h" #include "utils/fluid_sys.h" // this test makes sure FLUID_SNPRINTF uses a proper C99 compliant implementation int float_eq(fluid_real_t x, fluid_real_t y) { static const float EPS = 1e-5; FLUID_LOG(FLUID_INFO, "Comparing %.9f and %.9f", x, y); return fabs(x-y) < EPS; } int main(void) { // 440 * 2^((x-6900)/1200) where x is the cent value given to ct2hz() TEST_ASSERT(float_eq(fluid_ct2hz_real(38099), 2.9510849101059895e10)); TEST_ASSERT(float_eq(fluid_ct2hz_real(13500), 19912.12696)); TEST_ASSERT(float_eq(fluid_ct2hz_real(12900), 14080)); TEST_ASSERT(float_eq(fluid_ct2hz_real(12899), 14071.86942)); TEST_ASSERT(float_eq(fluid_ct2hz_real(12700), 12543.85395)); TEST_ASSERT(float_eq(fluid_ct2hz_real(6900), 440)); TEST_ASSERT(float_eq(fluid_ct2hz_real(5700), 220)); TEST_ASSERT(float_eq(fluid_ct2hz_real(4500), 110)); TEST_ASSERT(float_eq(fluid_ct2hz_real(901), 13.75794461)); TEST_ASSERT(float_eq(fluid_ct2hz_real(900), 13.75)); TEST_ASSERT(float_eq(fluid_ct2hz_real(899), 13.74205998)); TEST_ASSERT(float_eq(fluid_ct2hz_real(1), 8.180522806)); TEST_ASSERT(float_eq(fluid_ct2hz_real(0), 8.175798916)); // often referred to as Absolute zero in the SF2 spec return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_jack_obtaining_synth.c000066400000000000000000000021311362231004000222410ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" #include "fluid_adriver.h" // The jack driver may need the synth instance to adjust the sample-rate in case it mismatches with // the sample-rate of the jack driver. However, new_fluid_audio_driver2() does not receive a synth pointer. // Thus looking up the synth instance must be done via the settings object. int main(void) { #if JACK_SUPPORT fluid_synth_t *obtained_synth; fluid_synth_t *expected_synth; fluid_settings_t *settings = new_fluid_settings(); TEST_ASSERT(settings != NULL); expected_synth = new_fluid_synth(settings); TEST_ASSERT(expected_synth != NULL); TEST_SUCCESS(fluid_jack_obtain_synth(settings, &obtained_synth)); TEST_ASSERT(obtained_synth == expected_synth); delete_fluid_synth(obtained_synth); delete_fluid_settings(settings); obtained_synth = expected_synth = NULL; settings = new_fluid_settings(); TEST_ASSERT(settings != NULL); TEST_ASSERT(fluid_jack_obtain_synth(settings, &obtained_synth) == FLUID_FAILED); delete_fluid_settings(settings); #endif return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_pointer_alignment.c000066400000000000000000000013501362231004000215720ustar00rootroot00000000000000 #include "test.h" #include "utils/fluid_sys.h" // test for fluid_align_ptr() int main(void) { unsigned int align; uintptr_t ptr, aligned_ptr; for(align = 32; align <= 4 * 1024u; align <<= 1) { for(ptr = 0; ptr <= (align << 10); ptr++) { char *tmp = fluid_align_ptr((char *)ptr, align); aligned_ptr = (uintptr_t)tmp; // pointer must be aligned properly TEST_ASSERT(aligned_ptr % align == 0); // aligned pointer must not be smaller than ptr TEST_ASSERT(aligned_ptr >= ptr); // aligned pointer must not be bigger than alignment TEST_ASSERT(aligned_ptr < ptr + align); } } return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_preset_sample_loading.c000066400000000000000000000055401362231004000224210ustar00rootroot00000000000000#include "test.h" #include "fluidsynth.h" #include "sfloader/fluid_sfont.h" #include "sfloader/fluid_defsfont.h" #include "utils/fluid_sys.h" #include "utils/fluid_list.h" // load our sf2 and sf3 test soundfonts, with and without dynamic sample loading int main(void) { int id[2], sfcount, dyn_sample, i; /* setup */ fluid_settings_t *settings = new_fluid_settings(); for(dyn_sample = 0; dyn_sample <= 1; dyn_sample++) { fluid_synth_t *synth; fluid_settings_setint(settings, "synth.dynamic-sample-loading", dyn_sample); synth = new_fluid_synth(settings); id[0] = fluid_synth_sfload(synth, TEST_SOUNDFONT, 0); id[1] = fluid_synth_sfload(synth, TEST_SOUNDFONT_SF3, 0); sfcount = fluid_synth_sfcount(synth); TEST_ASSERT(id[0] != FLUID_FAILED); #if LIBSNDFILE_SUPPORT TEST_ASSERT(id[1] != FLUID_FAILED); TEST_ASSERT(sfcount == 2); #else TEST_ASSERT(id[1] == FLUID_FAILED); TEST_ASSERT(sfcount == 1); #endif for(i = 0; i < sfcount; i++) { fluid_preset_t *preset; fluid_list_t *list; fluid_preset_t *prev_preset = NULL; fluid_sample_t *prev_sample = NULL; int count = 0; fluid_sfont_t *sfont = fluid_synth_get_sfont_by_id(synth, id[i]); fluid_defsfont_t *defsfont = fluid_sfont_get_data(sfont); /* Make sure we have the right number of presets */ fluid_sfont_iteration_start(sfont); while((preset = fluid_sfont_iteration_next(sfont)) != NULL) { count++; if(preset->notify != NULL) { preset->notify(preset, FLUID_PRESET_SELECTED, 0); } /* make sure we actually got a different preset */ TEST_ASSERT(preset != prev_preset); prev_preset = preset; } /* VintageDreams has 136 presets */ TEST_ASSERT(count == 136); /* Make sure we have the right number of samples */ count = 0; for(list = defsfont->sample; list; list = fluid_list_next(list)) { fluid_sample_t *sample = fluid_list_get(list); if(sample->data != NULL) { count++; } TEST_ASSERT(sample->amplitude_that_reaches_noise_floor_is_valid); /* Make sure we actually got a different sample */ TEST_ASSERT(sample != prev_sample); prev_sample = sample; } /* VintageDreams has 123 valid samples (one is a ROM sample and ignored) */ TEST_ASSERT(count == 123); } /* teardown */ delete_fluid_synth(synth); } delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_sample_cache.c000066400000000000000000000024021362231004000204570ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" // use local fluidsynth header #include "utils/fluid_sys.h" // this test aims to make sure that sample data used by multiple synths is not freed // once unloaded by its parent synth int main(void) { enum { FRAMES = 1024 }; float buf[FRAMES * 2]; fluid_settings_t *settings = new_fluid_settings(); fluid_synth_t *synth1 = new_fluid_synth(settings), *synth2 = new_fluid_synth(settings); TEST_ASSERT(settings != NULL); TEST_ASSERT(synth1 != NULL); TEST_ASSERT(synth2 != NULL); // load a sfont to synth1 TEST_SUCCESS(fluid_synth_sfload(synth1, TEST_SOUNDFONT, 1)); // load the same font to synth2 and start a note TEST_SUCCESS(fluid_synth_sfload(synth2, TEST_SOUNDFONT, 1)); TEST_SUCCESS(fluid_synth_noteon(synth2, 0, 60, 127)); // render some audio TEST_SUCCESS(fluid_synth_write_float(synth2, FRAMES, buf, 0, 2, buf, 1, 2)); // delete the synth that owns the soundfont delete_fluid_synth(synth1); // render again with the unloaded sfont and hope no segfault happens TEST_SUCCESS(fluid_synth_write_float(synth2, FRAMES, buf, 0, 2, buf, 1, 2)); delete_fluid_synth(synth2); delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_sample_rate_change.c000066400000000000000000000036511362231004000216630ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" #include "synth/fluid_synth.h" #include "synth/fluid_voice.h" #include "rvoice/fluid_rvoice.h" #include "utils/fluid_sys.h" static void verify_sample_rate(fluid_synth_t *synth, int expected_srate) { int i; TEST_ASSERT(synth->sample_rate == expected_srate); for(i = 0; i < synth->polyphony; i++) { TEST_ASSERT(synth->voice[i]->output_rate == expected_srate); TEST_ASSERT(synth->voice[i]->rvoice->dsp.output_rate == expected_srate); TEST_ASSERT(synth->voice[i]->overflow_rvoice->dsp.output_rate == expected_srate); } // TODO check fx, rvoice_mixer et. al.? } // this test should make sure that sample rate changed are handled correctly int main(void) { int polyphony; double sample_rate; fluid_settings_t *settings = new_fluid_settings(); fluid_synth_t *synth = new_fluid_synth(settings); TEST_ASSERT(settings != NULL); TEST_ASSERT(synth != NULL); TEST_SUCCESS(fluid_settings_getint(settings, "synth.polyphony", &polyphony)); TEST_ASSERT(polyphony == synth->polyphony); TEST_SUCCESS(fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate)); verify_sample_rate(synth, sample_rate); TEST_SUCCESS(fluid_synth_sfload(synth, TEST_SOUNDFONT, 1)); // play some notes to start some voices TEST_SUCCESS(fluid_synth_noteon(synth, 0, 60, 127)); TEST_SUCCESS(fluid_synth_noteon(synth, 2, 71, 127)); TEST_SUCCESS(fluid_synth_noteon(synth, 3, 82, 127)); verify_sample_rate(synth, sample_rate); // set srate to minimum allowed sample_rate = 8000; fluid_synth_set_sample_rate(synth, sample_rate); // manually dispatch rvoice events to get samples rates updated (this simulates the render thread) fluid_synth_process_event_queue(synth); verify_sample_rate(synth, sample_rate); delete_fluid_synth(synth); delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_sample_validate.c000066400000000000000000000071461362231004000212170ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" #include "sfloader/fluid_sfont.h" #include "utils/fluid_sys.h" // this tests ensures that samples with invalid SfSampleType flag combinations are rejected int main(void) { fluid_sample_t* sample = new_fluid_sample(); sample->start = 0; sample->end = 1; /// valid flags { sample->sampletype = FLUID_SAMPLETYPE_OGG_VORBIS; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_LINKED; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_MONO; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_RIGHT; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_LEFT; TEST_SUCCESS(fluid_sample_validate(sample, 2)); } /// valid, but unsupported linked sample flags { sample->sampletype = FLUID_SAMPLETYPE_LINKED; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_LINKED | FLUID_SAMPLETYPE_OGG_VORBIS; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_LINKED | FLUID_SAMPLETYPE_MONO; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_LINKED | FLUID_SAMPLETYPE_RIGHT; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_LINKED | FLUID_SAMPLETYPE_LEFT; TEST_SUCCESS(fluid_sample_validate(sample, 2)); } /// valid, but rejected ROM sample flags { sample->sampletype = FLUID_SAMPLETYPE_ROM; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype = FLUID_SAMPLETYPE_ROM | FLUID_SAMPLETYPE_OGG_VORBIS; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype = FLUID_SAMPLETYPE_ROM | FLUID_SAMPLETYPE_LINKED; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype = FLUID_SAMPLETYPE_ROM | FLUID_SAMPLETYPE_MONO; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype = FLUID_SAMPLETYPE_ROM | FLUID_SAMPLETYPE_RIGHT; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype = FLUID_SAMPLETYPE_ROM | FLUID_SAMPLETYPE_LEFT; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); } /// invalid flag combinations { // no flags set? technically illegal, but handle it as mono sample->sampletype = 0; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype = FLUID_SAMPLETYPE_MONO | FLUID_SAMPLETYPE_RIGHT; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype |= FLUID_SAMPLETYPE_LEFT; TEST_SUCCESS(fluid_sample_validate(sample, 2)); sample->sampletype |= FLUID_SAMPLETYPE_LINKED; TEST_SUCCESS(fluid_sample_validate(sample, 2)); } // unknown flags must be rejected (this seems to be implicitly required by SF3) { sample->sampletype = 0x20; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype |= 0x40; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype = 0x80; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); sample->sampletype <<= 1; TEST_ASSERT(fluid_sample_validate(sample, 2) == FLUID_FAILED); } delete_fluid_sample(sample); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_seq_event_queue_sort.c000066400000000000000000000073321362231004000223260ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" // use local fluidsynth header #include "fluid_event.h" #include static short order = 0; void callback_stable_sort(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { static const int expected_type_order[] = { FLUID_SEQ_NOTEOFF, FLUID_SEQ_NOTEON, FLUID_SEQ_NOTEOFF, FLUID_SEQ_NOTEON, FLUID_SEQ_SYSTEMRESET, FLUID_SEQ_UNREGISTERING }; TEST_ASSERT(fluid_event_get_type(event) == expected_type_order[order++]); } void test_order_same_tick(fluid_sequencer_t *seq, fluid_event_t *evt) { // silently creates a fluid_seqbind_t int i, seqid = fluid_sequencer_register_client(seq, "test order at same tick", callback_stable_sort, NULL); TEST_SUCCESS(seqid); TEST_ASSERT(fluid_sequencer_count_clients(seq) == 1); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, seqid); for(i = 1; i <= 2; i++) { fluid_event_noteoff(evt, 0, 64); TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 1)); fluid_event_noteon(evt, 0, 64, 127); TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 1)); } fluid_event_system_reset(evt); TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, 2, 1)); fluid_event_unregistering(evt); TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, 2, 1)); fluid_sequencer_process(seq, 1); TEST_ASSERT(order == 2); fluid_sequencer_process(seq, 2); TEST_ASSERT(order == 6); fluid_sequencer_unregister_client(seq, seqid); TEST_ASSERT(fluid_sequencer_count_clients(seq) == 0); } static unsigned int prev_time, done = FALSE; void callback_correct_order(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { if(done) { TEST_ASSERT(fluid_event_get_type(event) == FLUID_SEQ_UNREGISTERING); } else { TEST_ASSERT(fluid_event_get_type(event) == FLUID_SEQ_CONTROLCHANGE); } TEST_ASSERT(prev_time <= fluid_event_get_time(event) && fluid_event_get_time(event) <= prev_time + 1); prev_time = fluid_event_get_time(event); } void test_correct_order(fluid_sequencer_t *seq, fluid_event_t *evt) { // silently creates a fluid_seqbind_t unsigned int i, offset; int seqid = fluid_sequencer_register_client(seq, "correct order test", callback_correct_order, NULL); TEST_SUCCESS(seqid); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, seqid); fluid_event_control_change(evt, 0, 1, 127); for(i = 0; i < 10000; i++) { TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, 10000 - i, 0)); } for(; i <= 10000 + 20000; i++) { TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); } for(; i < 80000; i++) { TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, 80000 - (i - 10000 - 20000), 0)); } for(; i < 200000; i++) { TEST_SUCCESS(fluid_sequencer_send_at(seq, evt, i, 0)); } offset = prev_time = fluid_sequencer_get_tick(seq); fluid_sequencer_process(seq, i + offset); TEST_ASSERT(prev_time == (i - 1) + offset); done = TRUE; fluid_sequencer_unregister_client(seq, seqid); } // simple test to ensure that manually unregistering and deleting the internal fluid_seqbind_t works without crashing int main(void) { fluid_event_t *evt; fluid_sequencer_t *seq = new_fluid_sequencer2(0 /*i.e. use sample timer*/); TEST_ASSERT(seq != NULL); TEST_ASSERT(fluid_sequencer_get_use_system_timer(seq) == 0); evt = new_fluid_event(); TEST_ASSERT(evt != NULL); test_order_same_tick(seq, evt); test_correct_order(seq, evt); // client should be removed, deleting the seq should not free the struct again delete_fluid_event(evt); delete_fluid_sequencer(seq); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_seq_scale.c000066400000000000000000000125001362231004000200120ustar00rootroot00000000000000#include "test.h" #include "fluidsynth.h" #include "utils/fluid_sys.h" #include "synth/fluid_event.h" static fluid_sequencer_t *sequencer; static short synthSeqID, mySeqID; static void sendnoteon(int chan, short key, short velocity, unsigned int time) { fluid_event_t *evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, synthSeqID); fluid_event_noteon(evt, chan, key, velocity); TEST_SUCCESS(fluid_sequencer_send_at(sequencer, evt, time, 0)); delete_fluid_event(evt); } static void sendpc(int chan, short val, unsigned int time) { fluid_event_t *evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, synthSeqID); fluid_event_program_change(evt, chan, val); TEST_SUCCESS(fluid_sequencer_send_at(sequencer, evt, time, 0)); delete_fluid_event(evt); } static void schedule_next_callback(unsigned int time) { fluid_event_t *evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, mySeqID); fluid_event_timer(evt, NULL); TEST_SUCCESS(fluid_sequencer_send_at(sequencer, evt, time, 0)); delete_fluid_event(evt); } static const unsigned int expected_callback_times[] = { 0, 0, 120, 120, 240, 240, 360, 360, 480, 1440, 1560, 1560, 1680, 1680, 1800, 1800, 1920, 2700, 2820, 2820, 2940, 2940, 3060, 3060, 3180, 4275, 4395, 4395, 4515, 4515, 4635, 4635, 4755, 799, 919, 919, 1039, 1039, 1159, 1159, 1279, 15999 }; static void fake_synth_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { static int callback_idx = 0; TEST_ASSERT(time == expected_callback_times[callback_idx++]); FLUID_LOG(FLUID_INFO, "synth callback time: %u %u", time, fluid_event_get_time(event)); } static void seq_callback(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data) { static int phase = 0; switch(phase) { case 0: sendpc(0, 47, 0); fluid_sequencer_set_time_scale(seq, 320); sendnoteon(0, 47, 60, 0); sendnoteon(0, 47, 0, 120); sendnoteon(0, 47, 90, 120); sendnoteon(0, 47, 0, 240); sendnoteon(0, 47, 110, 240); sendnoteon(0, 47, 0, 360); sendnoteon(0, 47, 127, 360); sendnoteon(0, 47, 0, 480); schedule_next_callback(480 + 240); break; case 1: fluid_sequencer_set_time_scale(seq, 1280 / 2); sendnoteon(0, 47, 60, 0); sendnoteon(0, 47, 0, 120); sendnoteon(0, 47, 80, 120); sendnoteon(0, 47, 0, 240); sendnoteon(0, 47, 90, 240); sendnoteon(0, 47, 0, 360); sendnoteon(0, 47, 100, 360); sendnoteon(0, 47, 0, 480); schedule_next_callback(480 + 240); break; case 2: fluid_sequencer_set_time_scale(seq, 800); sendnoteon(0, 47, 60, 0); sendnoteon(0, 47, 0, 120); sendnoteon(0, 47, 80, 120); sendnoteon(0, 47, 0, 240); sendnoteon(0, 47, 90, 240); sendnoteon(0, 47, 0, 360); sendnoteon(0, 47, 100, 360); sendnoteon(0, 47, 0, 480); schedule_next_callback(480 + 240); break; case 3: fluid_sequencer_set_time_scale(seq, 1000); sendnoteon(0, 47, 60, 0); sendnoteon(0, 47, 0, 120); sendnoteon(0, 47, 80, 120); sendnoteon(0, 47, 0, 240); sendnoteon(0, 47, 90, 240); sendnoteon(0, 47, 0, 360); sendnoteon(0, 47, 100, 360); sendnoteon(0, 47, 0, 480); schedule_next_callback(480 + 240); break; case 4: fluid_sequencer_set_time_scale(seq, 320 / 2); sendnoteon(0, 47, 60, 0); sendnoteon(0, 47, 0, 120); sendnoteon(0, 47, 80, 120); sendnoteon(0, 47, 0, 240); sendnoteon(0, 47, 90, 240); sendnoteon(0, 47, 0, 360); sendnoteon(0, 47, 100, 360); sendnoteon(0, 47, 0, 480); break; case 5: // this is the unregistering event, ignore break; default: TEST_ASSERT(0); } phase++; } // for debug, uncomment below to hear the notes // #define SOUND int main(void) { int i; #ifdef SOUND fluid_settings_t *settings = new_fluid_settings(); fluid_settings_setstr(settings, "audio.driver", "alsa"); fluid_synth_t *synth = new_fluid_synth(settings); TEST_SUCCESS(fluid_synth_sfload(synth, TEST_SOUNDFONT, 1)); #endif sequencer = new_fluid_sequencer2(0); TEST_ASSERT(sequencer != NULL); #ifdef SOUND synthSeqID = fluid_sequencer_register_fluidsynth(sequencer, synth); #else // register fake synth as first destination synthSeqID = fluid_sequencer_register_client(sequencer, "fake synth", fake_synth_callback, NULL); #endif TEST_SUCCESS(synthSeqID); // register myself as scheduling destination mySeqID = fluid_sequencer_register_client(sequencer, "me", seq_callback, NULL); TEST_SUCCESS(mySeqID); // initialize our absolute date schedule_next_callback(0); #ifdef SOUND fluid_audio_driver_t *adriver = new_fluid_audio_driver(settings, synth); fluid_msleep(100000); delete_fluid_audio_driver(adriver); delete_fluid_synth(synth); delete_fluid_settings(settings); #else for(i = 0; i < 100000; i++) { fluid_sequencer_process(sequencer, i); } #endif delete_fluid_sequencer(sequencer); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_seqbind_unregister.c000066400000000000000000000037201362231004000217530ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" // use local fluidsynth header static void test_unregister_with_event(fluid_synth_t *synth) { fluid_sequencer_t *seq = new_fluid_sequencer2(0 /*i.e. use sample timer*/); // silently creates a fluid_seqbind_t int seqid = fluid_sequencer_register_fluidsynth(seq, synth); fluid_event_t *evt = new_fluid_event(); fluid_event_set_source(evt, -1); fluid_event_set_dest(evt, seqid); // manually free the fluid_seqbind_t fluid_event_unregistering(evt); fluid_sequencer_send_now(seq, evt); // client should be removed, deleting the seq should not free the struct again delete_fluid_event(evt); delete_fluid_sequencer(seq); } static void test_unregister_with_client_unregister(fluid_synth_t *synth) { fluid_sequencer_t *seq = new_fluid_sequencer2(0 /*i.e. use sample timer*/); // silently creates a fluid_seqbind_t int seqid = fluid_sequencer_register_fluidsynth(seq, synth); fluid_sequencer_unregister_client(seq, seqid); // client should be removed, deleting the seq should not free the struct again delete_fluid_sequencer(seq); } static void test_unregister_without_unregister(fluid_synth_t *synth) { fluid_sequencer_t *seq = new_fluid_sequencer2(0 /*i.e. use sample timer*/); // silently creates a fluid_seqbind_t fluid_sequencer_register_fluidsynth(seq, synth); // client should be removed delete_fluid_sequencer(seq); } // test to ensure that unregistering and deleting the internal fluid_seqbind_t works without double-free // // valgrind should be used to ensure that there are no memory leaks int main(void) { fluid_settings_t *settings = new_fluid_settings(); fluid_synth_t *synth = new_fluid_synth(settings); test_unregister_with_event(synth); test_unregister_with_client_unregister(synth); test_unregister_without_unregister(synth); delete_fluid_synth(synth); delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_sf3_sfont_loading.c000066400000000000000000000036671362231004000214720ustar00rootroot00000000000000#include "test.h" #include "fluidsynth.h" #include "sfloader/fluid_sfont.h" #include "utils/fluid_sys.h" int main(void) { int id; fluid_sfont_t *sfont; fluid_settings_t *settings = new_fluid_settings(); fluid_synth_t *synth = new_fluid_synth(settings); TEST_ASSERT(settings != NULL); TEST_ASSERT(synth != NULL); // no sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 0); FLUID_LOG(FLUID_INFO, "Attempt to open %s", TEST_SOUNDFONT_SF3); // load a sfont to synth TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT_SF3, 1)); // one sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 1); sfont = fluid_synth_get_sfont_by_id(synth, id); TEST_ASSERT(sfont != NULL); // this is still the same filename as we've put in TEST_ASSERT(FLUID_STRCMP(TEST_SOUNDFONT_SF3, fluid_sfont_get_name(sfont)) == 0); TEST_ASSERT(fluid_sfont_get_id(sfont) == id); // still the same id? TEST_ASSERT(fluid_synth_sfreload(synth, id) == id); // one sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 1); sfont = fluid_synth_get_sfont_by_id(synth, id); TEST_ASSERT(sfont != NULL); // still the same filename? TEST_ASSERT(FLUID_STRCMP(TEST_SOUNDFONT_SF3, fluid_sfont_get_name(sfont)) == 0); // correct id stored? TEST_ASSERT(fluid_sfont_get_id(sfont) == id); // remove the sfont without deleting TEST_SUCCESS(fluid_synth_remove_sfont(synth, sfont)); // no sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 0); // re-add the sfont without deleting TEST_SUCCESS(id = fluid_synth_add_sfont(synth, sfont)); // one sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 1); // destroy the sfont TEST_SUCCESS(fluid_synth_sfunload(synth, id, 0)); // no sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 0); delete_fluid_synth(synth); delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_sfont_loading.c000066400000000000000000000045731362231004000207140ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" #include "sfloader/fluid_sfont.h" #include "sfloader/fluid_defsfont.h" #include "utils/fluid_sys.h" // this tests the soundfont loading API of the synth. // might be expanded to test the soundfont loader as well... int main(void) { int id; fluid_sfont_t *sfont; fluid_defsfont_t *defsfont; fluid_settings_t *settings = new_fluid_settings(); fluid_synth_t *synth = new_fluid_synth(settings); TEST_ASSERT(settings != NULL); TEST_ASSERT(synth != NULL); // no sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 0); TEST_ASSERT(fluid_is_soundfont(TEST_SOUNDFONT) == TRUE); // load a sfont to synth TEST_SUCCESS(id = fluid_synth_sfload(synth, TEST_SOUNDFONT, 1)); // one sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 1); TEST_ASSERT((sfont = fluid_synth_get_sfont_by_id(synth, id)) != NULL); // this is still the same filename as we've put in TEST_ASSERT(FLUID_STRCMP(TEST_SOUNDFONT, fluid_sfont_get_name(sfont)) == 0); TEST_ASSERT(fluid_sfont_get_id(sfont) == id); // still the same id? TEST_ASSERT(fluid_synth_sfreload(synth, id) == id); // one sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 1); TEST_ASSERT((sfont = fluid_synth_get_sfont_by_id(synth, id)) != NULL); // still the same filename? TEST_ASSERT(FLUID_STRCMP(TEST_SOUNDFONT, fluid_sfont_get_name(sfont)) == 0); // correct id stored? TEST_ASSERT(fluid_sfont_get_id(sfont) == id); // remove the sfont without deleting TEST_SUCCESS(fluid_synth_remove_sfont(synth, sfont)); // no sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 0); // re-add the sfont without deleting TEST_SUCCESS(id = fluid_synth_add_sfont(synth, sfont)); // one sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 1); // count the number of presets, instruments, samples defsfont = fluid_sfont_get_data(sfont); TEST_ASSERT(fluid_list_size(defsfont->preset) == 136); TEST_ASSERT(fluid_list_size(defsfont->sample) == 124-1); // SineWave ROM sample ignored TEST_ASSERT(fluid_list_size(defsfont->inst) == 238); // destroy the sfont TEST_SUCCESS(fluid_synth_sfunload(synth, id, 0)); // no sfont loaded TEST_ASSERT(fluid_synth_sfcount(synth) == 0); delete_fluid_synth(synth); delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_snprintf.c000066400000000000000000000007441362231004000177250ustar00rootroot00000000000000 #include "test.h" #include "utils/fluid_sys.h" // this test makes sure FLUID_SNPRINTF uses a proper C99 compliant implementation int main(void) { char buf[2 + 1]; int ret = FLUID_SNPRINTF(buf, sizeof(buf), "99"); TEST_ASSERT(ret == 2); TEST_ASSERT(buf[2] == '\0'); ret = FLUID_SNPRINTF(buf, sizeof(buf), "999"); TEST_ASSERT(ret == 3); // output truncated, buffer must be NULL terminated! TEST_ASSERT(buf[2] == '\0'); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_synth_chorus_reverb.c000066400000000000000000000054511362231004000221570ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" // this test should make sure that sample rate changed are handled correctly int main(void) { fluid_synth_t *synth; fluid_settings_t *settings = new_fluid_settings(); TEST_ASSERT(settings != NULL); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.room-size", 0.1)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.damp", 0.2)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.width", 0.3)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.level", 0.4)); TEST_SUCCESS(fluid_settings_setint(settings, "synth.chorus.nr", 99)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.chorus.level", 0.5)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.chorus.speed", 0.6)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.chorus.depth", 0.7)); synth = new_fluid_synth(settings); TEST_ASSERT(synth != NULL); // check that the synth is initialized with the correct values TEST_ASSERT(fluid_synth_get_reverb_roomsize(synth) == 0.1); TEST_ASSERT(fluid_synth_get_reverb_damp(synth) == 0.2); TEST_ASSERT(fluid_synth_get_reverb_width(synth) == 0.3); TEST_ASSERT(fluid_synth_get_reverb_level(synth) == 0.4); TEST_ASSERT(fluid_synth_get_chorus_nr(synth) == 99); TEST_ASSERT(fluid_synth_get_chorus_level(synth) == 0.5); TEST_ASSERT(fluid_synth_get_chorus_speed(synth) == 0.6); TEST_ASSERT(fluid_synth_get_chorus_depth(synth) == 0.7); // update the realtime settings afterward TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.room-size", 0.11)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.damp", 0.22)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.width", 0.33)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.reverb.level", 0.44)); TEST_SUCCESS(fluid_settings_setint(settings, "synth.chorus.nr", 11)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.chorus.level", 0.55)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.chorus.speed", 0.66)); TEST_SUCCESS(fluid_settings_setnum(settings, "synth.chorus.depth", 0.77)); // check that the realtime settings correctly update the values in the synth TEST_ASSERT(fluid_synth_get_reverb_roomsize(synth) == 0.11); TEST_ASSERT(fluid_synth_get_reverb_damp(synth) == 0.22); TEST_ASSERT(fluid_synth_get_reverb_width(synth) == 0.33); TEST_ASSERT(fluid_synth_get_reverb_level(synth) == 0.44); TEST_ASSERT(fluid_synth_get_chorus_nr(synth) == 11); TEST_ASSERT(fluid_synth_get_chorus_level(synth) == 0.55); TEST_ASSERT(fluid_synth_get_chorus_speed(synth) == 0.66); TEST_ASSERT(fluid_synth_get_chorus_depth(synth) == 0.77); delete_fluid_synth(synth); delete_fluid_settings(settings); return EXIT_SUCCESS; } fluidsynth-2.1.1/test/test_synth_process.c000066400000000000000000000072101362231004000207600ustar00rootroot00000000000000 #include "test.h" #include "fluidsynth.h" #include "fluidsynth_priv.h" #include "fluid_synth.h" #include // static const int CHANNELS=16; enum { SAMPLES=1024 }; static int smpl; int render_one_mock(fluid_synth_t *synth, int blocks) { fluid_real_t *left_in, *fx_left_in; fluid_real_t *right_in, *fx_right_in; int i, j; int naudchan = fluid_synth_count_audio_channels(synth); fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); for(i = 0; i < naudchan; i++) { for(j = 0; j < blocks * FLUID_BUFSIZE; j++) { int idx = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; right_in[idx] = left_in[idx] = (float)smpl++; } } return blocks; } int process_and_check(fluid_synth_t* synth, int number_of_samples, int offset) { int i; float left[SAMPLES], right[SAMPLES]; float *dry[1 * 2]; dry[0] = left; dry[1] = right; FLUID_MEMSET(left, 0, sizeof(left)); FLUID_MEMSET(right, 0, sizeof(right)); TEST_SUCCESS(fluid_synth_process_LOCAL(synth, number_of_samples, 0, NULL, 2, dry, render_one_mock)); for(i=0; i