diff --git a/.gitignore b/.gitignore index 7feb15e6..ba713f78 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,7 @@ compile_commands.json *.a *.txt.user +.DS_Store + clients/sfizz_jack clients/sfzprint diff --git a/.travis.yml b/.travis.yml index 66159c94..a3ee62cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,8 @@ matrix: env: - MATRIX_EVAL="CC=gcc-8 && CXX=g++-8" - - os: osx - osx_image: xcode10.1 +# - os: osx +# osx_image: xcode10.1 before_install: - sh ${TRAVIS_BUILD_DIR}/.travis/before_install.sh diff --git a/.travis/script.sh b/.travis/script.sh index fa98dd09..66692662 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -7,10 +7,10 @@ if [ "$TRAVIS_OS_NAME" = "linux" ]; then gcc -v && g++ -v && cmake --version && /usr/local/bin/cmake --version && $SHELL --version mkdir build && cd build - /usr/local/bin/cmake -D CMAKE_BUILD_TYPE=Release -D SFIZZ_CLIENTS=ON .. + /usr/local/bin/cmake -D CMAKE_BUILD_TYPE=Debug -D SFIZZ_CLIENTS=ON .. make -j$(nproc) elif [ "$TRAVIS_OS_NAME" = "osx" ]; then mkdir build && cd build - cmake -D CMAKE_BUILD_TYPE=Release -D SFIZZ_CLIENTS=ON -G Xcode .. - xcodebuild -project sfizz.xcodeproj -alltargets -configuration Release build + cmake -D SFIZZ_CLIENTS=OFF -G Xcode .. # FIXME: client build + xcodebuild -project sfizz.xcodeproj -alltargets -configuration Debug build fi diff --git a/CMakeLists.txt b/CMakeLists.txt index c42efe4f..047c2b82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,10 @@ cmake_minimum_required(VERSION 3.13) project(sfizz VERSION 1.0.0 LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 14) +# Do not override if set to more than 14 +if (NOT CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 14) + set(CMAKE_CXX_STANDARD 14) +endif() # Enable LTO set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # To override the policy in abseil and benchmark @@ -67,10 +70,13 @@ if (WIN32) set_target_properties(sndfile PROPERTIES LINKER_LANGUAGE CXX) file(COPY "${WIN_SNDFILE_PATH}/bin/libsndfile-1.dll" DESTINATION ${CMAKE_BINARY_DIR}) target_include_directories(sndfile INTERFACE "${WIN_SNDFILE_PATH}/include") -elseif (APPLE) - add_library(sndfile STATIC IMPORTED) - set_target_properties(sndfile PROPERTIES LINKER_LANGUAGE CXX) - target_include_directories(sndfile INTERFACE "usr/local/include") +elseif(APPLE) + find_path(SNDFILE_INCLUDE_DIR NAMES sndfile.hh PATHS "/usr/local/include") + find_library(SNDFILE_LIBRARY NAMES sndfile PATHS "/usr/local/lib") + message(STATUS "include dir: " ${SNDFILE_INCLUDE_DIR} " lib: " ${SNDFILE_LIBRARY}) + add_library(sndfile SHARED IMPORTED GLOBAL) + target_include_directories(sndfile INTERFACE ${SNDFILE_INCLUDE_DIR}) + target_link_libraries(sndfile INTERFACE ${SNDFILE_LIBRARY}) endif() add_subdirectory(sfizz) diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt index 56fe7532..0e7f525d 100644 --- a/clients/CMakeLists.txt +++ b/clients/CMakeLists.txt @@ -6,5 +6,14 @@ target_link_libraries(sfzprint sfizz::parser absl::flags_parse) ############################### # Basic command line program +if(APPLE) + find_path(JACK_INCLUDE_DIR NAMES jack.h PATHS "/usr/local/include/jack") + find_library(JACK_LIBRARY NAMES jack PATHS "/usr/local/lib") + message(STATUS "include dir: " ${JACK_INCLUDE_DIR} " lib: " ${JACK_LIBRARY}) + add_library(jack SHARED IMPORTED GLOBAL) + target_include_directories(jack INTERFACE ${JACK_INCLUDE_DIR}) + target_link_libraries(jack INTERFACE ${JACK_LIBRARY}) +endif() + add_executable(sfizz_jack jack_client.cpp) target_link_libraries(sfizz_jack sfizz::sfizz jack absl::flags_parse) diff --git a/sfizz/AtomicGuard.h b/sfizz/AtomicGuard.h new file mode 100644 index 00000000..4577083d --- /dev/null +++ b/sfizz/AtomicGuard.h @@ -0,0 +1,60 @@ +// Copyright (c) 2019, Paul Ferrand +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +namespace sfz +{ +class AtomicGuard +{ +public: + AtomicGuard() = delete; + AtomicGuard(std::atomic& guard) + : guard(guard) + { + guard = true; + } + ~AtomicGuard() + { + guard = false; + } +private: + std::atomic& guard; +}; +class AtomicDisabler +{ +public: + AtomicDisabler() = delete; + AtomicDisabler(std::atomic& allowed) + : allowed(allowed) + { + allowed = false; + } + ~AtomicDisabler() + { + allowed = true; + } +private: + std::atomic& allowed; +}; +} \ No newline at end of file diff --git a/sfizz/Buffer.h b/sfizz/Buffer.h index 7f7b11ea..7f982bd4 100644 --- a/sfizz/Buffer.h +++ b/sfizz/Buffer.h @@ -160,4 +160,5 @@ private: pointer _alignedEnd { nullptr }; LEAK_DETECTOR(Buffer); }; + } \ No newline at end of file diff --git a/sfizz/CMakeLists.txt b/sfizz/CMakeLists.txt index 8ac10141..81b792b3 100644 --- a/sfizz/CMakeLists.txt +++ b/sfizz/CMakeLists.txt @@ -14,7 +14,9 @@ set(SFIZZ_SOURCES include(CheckIncludeFiles) CHECK_INCLUDE_FILES(x86intrin.h HAVE_X86INTRIN_H) CHECK_INCLUDE_FILES(intrin.h HAVE_INTRIN_H) -CHECK_INCLUDE_FILES(arm_neon.h HAVE_ARM_NEON_H) +if (!APPLE) + CHECK_INCLUDE_FILES(arm_neon.h HAVE_ARM_NEON_H) +endif() # SIMD checks if (HAVE_X86INTRIN_H AND UNIX) @@ -71,4 +73,4 @@ target_link_libraries(sfizz PUBLIC absl::strings) target_link_libraries(sfizz PRIVATE sndfile absl::flat_hash_map) add_library(sfizz::parser ALIAS sfizz_parser) -add_library(sfizz::sfizz ALIAS sfizz) \ No newline at end of file +add_library(sfizz::sfizz ALIAS sfizz) diff --git a/sfizz/Synth.cpp b/sfizz/Synth.cpp index 3cc03336..a2e61aba 100644 --- a/sfizz/Synth.cpp +++ b/sfizz/Synth.cpp @@ -22,9 +22,10 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Synth.h" -#include "MidiState.h" +#include "AtomicGuard.h" #include "Config.h" #include "Debug.h" +#include "MidiState.h" #include "ScopedFTZ.h" #include "StringViewHelpers.h" #include "absl/algorithm/container.h" @@ -139,17 +140,20 @@ void sfz::Synth::handleControlOpcodes(const std::vector& members) { for (auto& member : members) { switch (hash(member.opcode)) { - case hash("Set_cc"): [[fallthrough]]; + case hash("Set_cc"): + [[fallthrough]]; case hash("set_cc"): if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter)) setValueFromOpcode(member, midiState.cc[*member.parameter], Default::ccRange); break; - case hash("Label_cc"): [[fallthrough]]; + case hash("Label_cc"): + [[fallthrough]]; case hash("label_cc"): if (member.parameter && Default::ccRange.containsWithEnd(*member.parameter)) ccNames.emplace_back(*member.parameter, member.value); break; - case hash("Default_path"): [[fallthrough]]; + case hash("Default_path"): + [[fallthrough]]; case hash("default_path"): { auto stringPath = std::string(member.value.begin(), member.value.end()); auto newPath = fs::path(stringPath); @@ -184,7 +188,7 @@ void addEndpointsToVelocityCurve(sfz::Region& region) bool sfz::Synth::loadSfzFile(const fs::path& filename) { - canEnterCallback = false; + AtomicDisabler callbackDisabler { canEnterCallback }; while (inCallback) { std::this_thread::sleep_for(1ms); } @@ -247,8 +251,7 @@ bool sfz::Synth::loadSfzFile(const fs::path& filename) DBG("Removed " << regions.size() - std::distance(regions.begin(), lastRegion) - 1 << " out of " << regions.size() << " regions."); regions.resize(std::distance(regions.begin(), lastRegion) + 1); - - canEnterCallback = true; + return parserReturned; } @@ -261,12 +264,12 @@ sfz::Voice* sfz::Synth::findFreeVoice() noexcept // Find voices that can be stolen DBG("No free voice, trying to steal"); voiceViewArray.clear(); - for (auto& voice: voices) + for (auto& voice : voices) if (voice->canBeStolen()) voiceViewArray.push_back(voice.get()); absl::c_sort(voices, [](const auto& lhs, const auto& rhs) { return lhs->getSourcePosition() > rhs->getSourcePosition(); }); - for (auto* voice: voiceViewArray) { + for (auto* voice : voiceViewArray) { DBG("Average voice power: " << voice->getMeanSquaredAverage()); if (voice->getMeanSquaredAverage() < config::voiceStealingThreshold) { DBG("Stealing voice..."); @@ -297,6 +300,11 @@ void sfz::Synth::garbageCollect() noexcept void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept { + AtomicDisabler callbackDisabler { canEnterCallback }; + while (inCallback) { + std::this_thread::sleep_for(1ms); + } + this->samplesPerBlock = samplesPerBlock; this->tempBuffer.resize(samplesPerBlock); for (auto& voice : voices) @@ -305,6 +313,11 @@ void sfz::Synth::setSamplesPerBlock(int samplesPerBlock) noexcept void sfz::Synth::setSampleRate(float sampleRate) noexcept { + AtomicDisabler callbackDisabler { canEnterCallback }; + while (inCallback) { + std::this_thread::sleep_for(1ms); + } + this->sampleRate = sampleRate; for (auto& voice : voices) voice->setSampleRate(sampleRate); @@ -314,18 +327,17 @@ void sfz::Synth::renderBlock(AudioSpan buffer) noexcept { ScopedFTZ ftz; buffer.fill(0.0f); + if (!canEnterCallback) return; - inCallback = true; + AtomicGuard callbackGuard { inCallback }; auto tempSpan = AudioSpan(tempBuffer).first(buffer.getNumFrames()); for (auto& voice : voices) { voice->renderBlock(tempSpan); buffer.add(tempSpan); } - - inCallback = false; } void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity) noexcept @@ -334,6 +346,12 @@ void sfz::Synth::noteOn(int delay, int channel, int noteNumber, uint8_t velocity ASSERT(noteNumber >= 0); midiState.noteOn(noteNumber, velocity); + + if (!canEnterCallback) + return; + + AtomicGuard callbackGuard { inCallback }; + auto randValue = randNoteDistribution(Random::randomGenerator); for (auto& region : noteActivationLists[noteNumber]) { @@ -361,7 +379,12 @@ void sfz::Synth::noteOff(int delay, int channel, int noteNumber, uint8_t velocit ASSERT(noteNumber < 128); ASSERT(noteNumber >= 0); - // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a + if (!canEnterCallback) + return; + + AtomicGuard callbackGuard { inCallback }; + + // FIXME: Some keyboards (e.g. Casio PX5S) can send a real note-off velocity. In this case, do we have a // way in sfz to specify that a release trigger should NOT use the note-on velocity? // auto replacedVelocity = (velocity == 0 ? sfz::getNoteVelocity(noteNumber) : velocity); auto replacedVelocity = midiState.getNoteVelocity(noteNumber); @@ -389,6 +412,11 @@ void sfz::Synth::cc(int delay, int channel, int ccNumber, uint8_t ccValue) noexc ASSERT(ccNumber < 128); ASSERT(ccNumber >= 0); + if (!canEnterCallback) + return; + + AtomicGuard callbackGuard { inCallback }; + for (auto& voice : voices) voice->registerCC(delay, channel, ccNumber, ccValue);