summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpepper <peppersclothescult@gmail.com>2015-01-10 21:32:32 -0800
committerpepper <peppersclothescult@gmail.com>2015-01-10 21:32:32 -0800
commitd53fa8a169832563c62262078b8d2ffe5cab8473 (patch)
treeb911d06d357d009c976709780f10e92ce915228a
first
-rw-r--r--.gitignore58
-rw-r--r--.gitmodules9
-rw-r--r--BUILDING.md121
-rw-r--r--CMakeLists.txt189
-rw-r--r--CONTRIBUTING.md133
-rw-r--r--ChangeLog.txt73
-rw-r--r--LICENSE.txt46
-rw-r--r--README.md196
-rw-r--r--TESTING.md43
-rwxr-xr-xbin/Linux/mrswatsonbin0 -> 140798 bytes
-rwxr-xr-xbin/Linux/mrswatson64bin0 -> 145647 bytes
-rwxr-xr-xbin/Linux/mrswatsontestbin0 -> 280665 bytes
-rwxr-xr-xbin/Linux/mrswatsontest64bin0 -> 276471 bytes
-rw-r--r--bin/Windows/mrswatson.exebin0 -> 214528 bytes
-rw-r--r--bin/Windows/mrswatson64.exebin0 -> 218112 bytes
-rw-r--r--bin/Windows/mrswatsontest.exebin0 -> 283136 bytes
-rw-r--r--bin/Windows/mrswatsontest64.exebin0 -> 329216 bytes
-rw-r--r--main/CMakeLists.txt32
-rw-r--r--main/MrsWatsonMain.c70
-rw-r--r--main/NOTES101
-rw-r--r--main/cmd1
-rwxr-xr-xmain/endian_testbin0 -> 8496 bytes
-rw-r--r--main/endian_test.c11
-rwxr-xr-xmain/example.fxpbin0 -> 187581 bytes
-rw-r--r--main/example.midbin0 -> 261 bytes
-rwxr-xr-xmain/example.mid.oldbin0 -> 21504 bytes
-rwxr-xr-xmain/example_1.midbin0 -> 2162 bytes
-rwxr-xr-xmain/mrswatsonbin0 -> 158220 bytes
-rwxr-xr-xmain/mrswatson64bin0 -> 151764 bytes
-rw-r--r--main/mytest.bash1
-rw-r--r--main/new_example.bash2
-rw-r--r--main/rebuild.bash3
-rwxr-xr-xscripts/format-code.sh3
-rwxr-xr-xscripts/make-release.sh31
-rw-r--r--scripts/mrswatson.astyle8
-rwxr-xr-xscripts/new-file.sh85
-rw-r--r--source/CMakeLists.txt138
-rw-r--r--source/MrsWatson.c792
-rw-r--r--source/MrsWatson.h37
-rw-r--r--source/MrsWatsonOptions.c324
-rw-r--r--source/MrsWatsonOptions.h68
-rw-r--r--source/app/BuildInfo.c101
-rw-r--r--source/app/BuildInfo.h78
-rw-r--r--source/app/ProgramOption.c534
-rw-r--r--source/app/ProgramOption.h246
-rw-r--r--source/app/ReturnCodes.h55
-rw-r--r--source/audio/AudioSettings.c242
-rw-r--r--source/audio/AudioSettings.h183
-rw-r--r--source/audio/SampleBuffer.c175
-rw-r--r--source/audio/SampleBuffer.h107
-rw-r--r--source/base/CharString.c298
-rw-r--r--source/base/CharString.h182
-rw-r--r--source/base/Endian.c93
-rw-r--r--source/base/Endian.h89
-rw-r--r--source/base/File.c922
-rw-r--r--source/base/File.h262
-rw-r--r--source/base/LinkedList.c157
-rw-r--r--source/base/LinkedList.h98
-rw-r--r--source/base/PlatformInfo.c272
-rw-r--r--source/base/PlatformInfo.h68
-rw-r--r--source/base/PlatformInfoMac.m41
-rw-r--r--source/base/Types.h89
-rw-r--r--source/io/RiffFile.c93
-rw-r--r--source/io/RiffFile.h75
-rw-r--r--source/io/SampleSource.c165
-rw-r--r--source/io/SampleSource.h94
-rw-r--r--source/io/SampleSourceAudiofile.c204
-rw-r--r--source/io/SampleSourceAudiofile.h47
-rw-r--r--source/io/SampleSourcePcm.c221
-rw-r--r--source/io/SampleSourcePcm.h92
-rw-r--r--source/io/SampleSourceSilence.c84
-rw-r--r--source/io/SampleSourceSilence.h33
-rw-r--r--source/io/SampleSourceWave.c499
-rw-r--r--source/io/SampleSourceWave.h33
-rw-r--r--source/logging/ErrorReporter.c286
-rw-r--r--source/logging/ErrorReporter.h111
-rw-r--r--source/logging/EventLogger.c398
-rw-r--r--source/logging/EventLogger.h219
-rw-r--r--source/logging/LogPrinter.c144
-rw-r--r--source/logging/LogPrinter.h133
-rw-r--r--source/midi/MidiEvent.c55
-rw-r--r--source/midi/MidiEvent.h80
-rw-r--r--source/midi/MidiSequence.c100
-rw-r--r--source/midi/MidiSequence.h86
-rw-r--r--source/midi/MidiSource.c75
-rw-r--r--source/midi/MidiSource.h83
-rw-r--r--source/midi/MidiSourceFile.c343
-rw-r--r--source/midi/MidiSourceFile.h50
-rw-r--r--source/plugin/Plugin.c205
-rw-r--r--source/plugin/Plugin.h195
-rw-r--r--source/plugin/PluginChain.c420
-rw-r--r--source/plugin/PluginChain.h165
-rw-r--r--source/plugin/PluginGain.c133
-rw-r--r--source/plugin/PluginGain.h47
-rw-r--r--source/plugin/PluginLimiter.c121
-rw-r--r--source/plugin/PluginLimiter.h37
-rw-r--r--source/plugin/PluginPassthru.c105
-rw-r--r--source/plugin/PluginPassthru.h37
-rw-r--r--source/plugin/PluginPreset.c104
-rw-r--r--source/plugin/PluginPreset.h111
-rw-r--r--source/plugin/PluginPresetFxp.c280
-rw-r--r--source/plugin/PluginPresetFxp.h74
-rw-r--r--source/plugin/PluginPresetInternalProgram.c74
-rw-r--r--source/plugin/PluginPresetInternalProgram.h40
-rw-r--r--source/plugin/PluginSilence.c105
-rw-r--r--source/plugin/PluginSilence.h37
-rw-r--r--source/plugin/PluginVst2x.cpp897
-rw-r--r--source/plugin/PluginVst2x.h91
-rw-r--r--source/plugin/PluginVst2xHostCallback.cpp433
-rw-r--r--source/plugin/PluginVst2xHostCallback.h45
-rw-r--r--source/plugin/PluginVst2xId.c101
-rw-r--r--source/plugin/PluginVst2xId.h45
-rw-r--r--source/plugin/PluginVst2xLinux.cpp113
-rw-r--r--source/plugin/PluginVst2xMac.cpp136
-rw-r--r--source/plugin/PluginVst2xWindows.cpp114
-rw-r--r--source/time/AudioClock.c72
-rw-r--r--source/time/AudioClock.h82
-rw-r--r--source/time/TaskTimer.c164
-rw-r--r--source/time/TaskTimer.h107
-rw-r--r--test/CMakeLists.txt90
-rw-r--r--test/MrsWatsonTestMain.c348
-rw-r--r--test/MrsWatsonTestMain.h20
-rw-r--r--test/analysis/AnalysisClipping.c25
-rw-r--r--test/analysis/AnalysisClipping.h3
-rw-r--r--test/analysis/AnalysisClippingTest.c37
-rw-r--r--test/analysis/AnalysisDistortion.c33
-rw-r--r--test/analysis/AnalysisDistortion.h3
-rw-r--r--test/analysis/AnalysisDistortionTest.c37
-rw-r--r--test/analysis/AnalysisSilence.c25
-rw-r--r--test/analysis/AnalysisSilence.h3
-rw-r--r--test/analysis/AnalysisSilenceTest.c57
-rw-r--r--test/analysis/AnalyzeFile.c123
-rw-r--r--test/analysis/AnalyzeFile.h37
-rw-r--r--test/app/ProgramOptionTest.c505
-rw-r--r--test/audio/AudioSettingsTest.c213
-rw-r--r--test/audio/SampleBufferTest.c134
-rw-r--r--test/base/CharStringTest.c422
-rw-r--r--test/base/EndianTest.c155
-rw-r--r--test/base/FileTest.c1526
-rw-r--r--test/base/LinkedListTest.c245
-rw-r--r--test/base/PlatformInfoTest.c86
-rw-r--r--test/io/SampleSourceTest.c55
-rw-r--r--test/midi/MidiSequenceTest.c138
-rw-r--r--test/midi/MidiSourceTest.c41
-rwxr-xr-xtest/mrswatsontestbin0 -> 334168 bytes
-rwxr-xr-xtest/mrswatsontest64bin0 -> 292172 bytes
-rw-r--r--test/plugin/PluginChainTest.c311
-rw-r--r--test/plugin/PluginMock.c95
-rw-r--r--test/plugin/PluginMock.h19
-rw-r--r--test/plugin/PluginPresetMock.c42
-rw-r--r--test/plugin/PluginPresetMock.h15
-rw-r--r--test/plugin/PluginPresetTest.c78
-rw-r--r--test/plugin/PluginTest.c91
-rw-r--r--test/plugin/PluginVst2xIdTest.c87
-rw-r--r--test/time/AudioClockTest.c82
-rw-r--r--test/time/TaskTimerTest.c279
-rw-r--r--test/unit/ApplicationRunner.c297
-rw-r--r--test/unit/ApplicationRunner.h39
-rw-r--r--test/unit/IntegrationTests.c175
-rw-r--r--test/unit/TestRunner.c152
-rw-r--r--test/unit/TestRunner.h173
-rw-r--r--test/unit/UnitTests.c160
-rw-r--r--vendor/CMakeLists.txt298
-rw-r--r--vendor/audiofile-config/linux/config.h104
-rw-r--r--vendor/audiofile-config/mac/config.h104
-rw-r--r--vendor/flac-config/linux/config.h235
-rw-r--r--vendor/flac-config/mac/config.h235
167 files changed, 22792 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c0b46c8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,58 @@
+# Build products
+build/*
+MrsWatson*.zip
+**/Debug/*
+**/Release/*
+
+# Temporary files
+
+*.swp
+*~
+*.o
+*.obj
+*.so
+*.a
+
+# Test output
+out/*
+out.wav
+
+# Stuff from various IDE's
+.idea/*
+*.xcodeproj
+nbproject/*
+*.perspectivev3
+*.pbxuser
+*.user
+*.sdf
+*.opensdf
+*.suo
+*.log
+*.tlog
+*.idb
+*.pdb
+*.sln
+*.vcxproj
+*.vcxproj.filters
+*.lastbuildstate
+*.cbp
+
+# Non-redistributable third-party
+vendor/vstsdk2.4/*
+
+# Vim helpers
+tags
+cscope.out
+
+# CMake
+CMakeCache.txt
+CMakeScripts/*
+**/CMakeScripts/*
+CMakeFiles
+Makefile
+Makefile.in
+*.cmake
+**/*.dir
+#particular to this one
+*.wav
+the_walshing*
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5c89afb
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,9 @@
+[submodule "vendor/AudioTestData"]
+ path = vendor/AudioTestData
+ url = git://github.com/teragonaudio/AudioTestData.git
+[submodule "vendor/audiofile"]
+ path = vendor/audiofile
+ url = git://github.com/teragonaudio/audiofile.git
+[submodule "vendor/flac"]
+ path = vendor/flac
+ url = git://github.com/teragonaudio/flac.git
diff --git a/BUILDING.md b/BUILDING.md
new file mode 100644
index 0000000..eeddf74
--- /dev/null
+++ b/BUILDING.md
@@ -0,0 +1,121 @@
+Building MrsWatson from Source
+==============================
+
+To build MrsWatson on any platform, you will need to acquire a copy of the
+VST SDK source code. Due to licensing restrictions of the VST SDK, this code
+is not distributed with MrsWatson itself. It can be downloaded from
+[Steinberg's Developer Page][1], and then extracted to the
+`vendor/vstsdk2.4` subdirectory of the project before compiling.
+
+The MrsWatson source code also contains a few submodules, it is recommended
+//do you know what this means? syn c th git has submodules to include other repositories into current repository ahh so is there a command to sync them
+from the current repo or do I have to find them? we can try without it
+that you sync the submodules before building the code.
+
+//I should have the cmake package
+Mac OSX
+-------
+
+To build MrsWatson on Mac OS X, you will need to have Apple's Developer
+Tools installed. You can download Xcode either from Apple's developer
+website or from the Mac App Store. You also need to install the command line
+tools provided by Xcode, which are not installed by default. To do this, go
+to Xcode's preferences, then to the downloads tab, and in "Components" you
+find a button to install the command line tools.
+
+[Homebrew][2] is also recommended for building MrsWatson on Mac OS X. from
+homebrew, you will need the CMake package, but possibly automake and
+autoconf as well. See the homebrew webpage for details on installation of
+packages.
+
+If you want to use Xcode to develop MrsWatson you will need to generate a
+project file like so:
+
+ cmake -G Xcode .
+
+This command will generate an Xcode project in the current directory which
+you can use to compile the software as you would any other project. For
+other IDE's, such as CLion, QT Creator, NetBeans, emacs, or vim, you can
+generate a regular makefile to build the software like any other Unix
+package. To do that you must generate the makefiles like so:
+
+ cmake -G "Unix Makefiles" .
+ make
+
+Ninja and CCache are also supported, both tools should work "out of the box".
+
+
+Linux
+-----
+
+Building MrsWatson on Linux may require a few additional packages on your
+computer, including those necessary to make a 32-bit build on 64-bit
+machines. By default, the software is built with your native architecture,
+so if you would like to make a 32-bit build on a 64-bit machine, you will
+also need the following packages:
+
+ * libstdc++
+ * g++-multilib
+ * ia32-libs
+
+This is in addition to the standard build tools (gcc, g++, etc). Otherwise,
+building on Linux should be a simple matter of:
+
+ cmake .
+ make
+ make install
+
+
+General Unix Tips
+-----------------
+
+There are several other generators supported by CMake, you can see the list
+of supported ones by running cmake -h on the command line. By default, cmake
+will generate a release build configuration, which will place binaries in
+the "bin" directory. If you want debuggable binaries, then you must run the
+following command when you generate your build environment:
+
+ cmake -DCMAKE_BUILD_TYPE=Debug -G "Your generator of choice" .
+ make clean && make
+
+On Unix (including both Mac OS X and Linux), the project generated by CMake
+will produce both 32 and 64-bit binaries. These binaries will have the
+number "64" appended to the end. Even though Mac OS X has the Universal
+Binary format that could contain both architectures, two separate binaries
+are shipped on this platform to avoid confusion when loading plugins.
+
+
+Windows
+-------
+
+On Windows, Visual Studio is used to compile the software. The MrsWatson
+source code does not include a Visual Studio project, instead CMake is used
+to generate Visual Studio project which can then be used to build the
+software. After you've installed cmkae for Windows, you must generate the
+build directory where the Visual Studio product will be placed. Unlike on
+Unix, you cannot build the software from the same directory as the source
+code. So generating the Visual Studio project will look something like this:
+
+ cd c:\wherever
+ mkdir MrsWatson-build
+ cd MrsWatson-build
+ cmake -G "Visual Studio 11" C:\path\to\MrsWatson
+
+This command will generate a Visual Studio project in
+`C:\wherever\MrsWatson-build` which can use to build the software. Just like
+on Unix, CMake supports several different generators which can be listed by
+running CMake -h. Namely, several different versions of Visual Studio are
+supported, and you should pick the one which matches your installation.
+
+Unlike on Unix, the generated Visual Studio project will not generate both
+32- and 64-bit builds of the software. Instead you must create another build
+directory for the 64-bit project. That is done like so:
+
+ cd c:\wherever
+ mkdir MrsWatson-build64
+ cd MrsWatson-build64
+ cmake -G "Visual Studio 11 Win64" C:\path\to\MrsWatson
+
+
+[1]: http://www.steinberg.net/en/company/developer.html
+[2]: http://mxcl.github.io/homebrew/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..1631a1a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,189 @@
+cmake_minimum_required(VERSION 2.8.11)
+project(MrsWatson)
+
+set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
+set(cmake_SCRIPTS_DIR ${CMAKE_SOURCE_DIR}/cmake)
+include_directories(${CMAKE_SOURCE_DIR}/source)
+include_directories(${CMAKE_SOURCE_DIR}/vendor/vstsdk2.4/pluginterfaces/vst2.x)
+
+# Build options ################################################
+
+option(WITH_AUDIOFILE "Use libaudiofile for reading/writing files" OFF)
+option(WITH_FLAC "Support for FLAC files (requires libaudiofile)" OFF)
+
+# Platform properties ##########################################
+
+if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(PLATFORM_NAME "Linux")
+ set(PLATFORM_CONFIG_DIR "linux")
+elseif(APPLE)
+ set(PLATFORM_NAME "Mac OS X")
+ set(PLATFORM_CONFIG_DIR "mac")
+elseif(WIN32)
+ set(PLATFORM_NAME "Windows")
+ set(PLATFORM_CONFIG_DIR "windows")
+else()
+ set(PLATFORM_NAME "Unknown")
+endif()
+
+if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(PLATFORM_BITS 64)
+else()
+ set(PLATFORM_BITS 32)
+endif()
+
+# Build Flags ##################################################
+
+if(WITH_AUDIOFILE)
+ add_definitions(-DUSE_AUDIOFILE=1)
+endif()
+
+if(WITH_FLAC)
+ if(NOT WITH_AUDIOFILE)
+ message(FATAL_ERROR "FLAC support requires audiofile to be built")
+ endif()
+ add_definitions(-DUSE_FLAC=1)
+endif()
+
+if(MSVC)
+ # We don't care about intdir, binary output path is set above
+ set(CMAKE_CFG_INTDIR ".")
+
+ set(CMAKE_C_FLAGS_DEBUG "/D DEBUG=1 /D _DEBUG /MTd /Ob0 /Od /RTC1")
+ set(CMAKE_C_FLAGS_MINSIZEREL "/MT /O1 /Ob1 /Oi /D NDEBUG")
+ set(CMAKE_C_FLAGS_RELEASE "/MT /O2 /Ob2 /Oi /D NDEBUG")
+ set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MT /Zi /O2 /Ob1 /D NDEBUG")
+
+ set(CMAKE_CXX_FLAGS_DEBUG "/D DEBUG=1 /D _DEBUG /MTd /Zi /Ob0 /Od /RTC1")
+ set(CMAKE_CXX_FLAGS_MINSIZEREL "/MT /O1 /Ob1 /Oi /D NDEBUG")
+ set(CMAKE_CXX_FLAGS_RELEASE "/MT /O2 /Ob2 /Oi /D NDEBUG")
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MT /Zi /O2 /Ob1 /D NDEBUG")
+
+ add_definitions("/W3 /D _CRT_SECURE_NO_WARNINGS=1 /D WINDOWS=1")
+endif()
+
+if(UNIX)
+ add_definitions("-DUNIX=1")
+
+ # GCC flags common to all Unix platforms
+ set(SHARED_GCC_FLAGS_LIST
+ "-fmessage-length=0"
+ "-pipe"
+
+ "-Wno-trigraphs"
+ "-Wmissing-field-initializers"
+ "-Wreturn-type"
+ "-Wunused-variable"
+ "-Wshadow"
+ "-Wsign-compare"
+ "-Wswitch"
+ "-Wswitch-default"
+
+ "-Waddress"
+ "-Wchar-subscripts"
+ "-Wcomment"
+ "-Wformat"
+ "-Wmaybe-uninitialized"
+ "-Wnonnull"
+ "-Wparentheses"
+ "-Wreturn-type"
+ "-Wsequence-point"
+ "-Wstrict-aliasing"
+ "-Wstrict-overflow=1"
+ "-Wswitch"
+ "-Wtrigraphs"
+ "-Wuninitialized"
+ "-Wunused-function"
+ "-Wunused-label"
+ "-Wunused-value"
+ "-Wunused-variable"
+ "-Wvolatile-register-var"
+ )
+
+ set(SHARED_GCC_CFLAGS_LIST
+ "-std=gnu99"
+ "-Wmain"
+ "-Wenum-compare"
+ "-Wmissing-braces"
+ "-Wimplicit-int"
+ "-Wimplicit-function-declaration"
+ "-Wpointer-sign"
+ )
+
+ set(SHARED_GCC_CPPFLAGS_LIST
+ "-Wsign-compare"
+ "-Weffc++"
+ "-Wc++11-compat"
+ "-Wreorder"
+ )
+
+ # Linux-specific GCC stuff
+ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(PLATFORM_GCC_FLAGS_LIST
+ "-Wuninitialized"
+ )
+
+ add_definitions("-DLINUX=1")
+ add_definitions("-D_POSIX_C_SOURCE=200809L")
+ add_definitions("-D__cdecl=")
+ endif()
+
+ # Mac OSX GCC stuff
+ if(APPLE)
+ set(PLATFORM_GCC_FLAGS_LIST
+ "-fpascal-strings"
+ "-Wnewline-eof"
+ "-Wshorten-64-to-32"
+ "-fasm-blocks"
+ "-mmacosx-version-min=10.5"
+ )
+
+ set(PLATFORM_LINKER_FLAGS_LIST
+ "-framework Carbon"
+ "-framework CoreFoundation"
+ "-framework Foundation"
+ )
+
+ add_definitions("-DMACOSX=1")
+ # Homebrew places installed library files in /usr/local, but no major
+ # Linux distro does that anymore (last time I checked...)
+ include_directories("/usr/local/include")
+ endif()
+
+ # CMake doesn't really support multi-line strings, so the
+ # compiler flags are in lists above to make them easier to
+ # manage. However, we must build strings from the lists in
+ # order to set the respective CMake variables which they
+ # correspond to.
+ string(REPLACE ";" " " SHARED_GCC_FLAGS "${SHARED_GCC_FLAGS_LIST}")
+ string(REPLACE ";" " " SHARED_GCC_CFLAGS "${SHARED_GCC_CFLAGS_LIST}")
+ string(REPLACE ";" " " SHARED_GCC_CPPFLAGS "${SHARED_GCC_CPPFLAGS_LIST}")
+ string(REPLACE ";" " " PLATFORM_GCC_FLAGS "${PLATFORM_GCC_FLAGS_LIST}")
+ string(REPLACE ";" " " PLATFORM_LINKER_FLAGS "${PLATFORM_LINKER_FLAGS_LIST}")
+
+ # Set compiler flags from shared & platform-specific lists
+ set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${SHARED_GCC_CFLAGS} ${PLATFORM_GCC_FLAGS}")
+ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${SHARED_GCC_CFLAGS} ${PLATFORM_GCC_FLAGS}")
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${SHARED_GCC_CPPFLAGS} ${PLATFORM_GCC_FLAGS}")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${SHARED_GCC_CPPFLAGS} ${PLATFORM_GCC_FLAGS}")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PLATFORM_LINKER_FLAGS}")
+endif()
+
+# Subdirectories ###############################################
+
+add_subdirectory(source)
+add_subdirectory(main)
+add_subdirectory(test)
+add_subdirectory(vendor)
+
+# Build summary ################################################
+
+message("-- Build configuration")
+message(" C Compiler: ${CMAKE_C_COMPILER}")
+message(" C++ Compiler: ${CMAKE_CXX_COMPILER}")
+message(" Build type: ${CMAKE_BUILD_TYPE}")
+message(" Platform name: ${PLATFORM_NAME}")
+message(" Platform bitness: ${PLATFORM_BITS}-bit")
+message("-- Build options")
+message(" WITH_AUDIOFILE: ${WITH_AUDIOFILE}")
+message(" WITH_FLAC: ${WITH_FLAC}")
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..371f849
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,133 @@
+Contributing to MrsWatson
+=========================
+
+Submitting a Patch
+------------------
+
+So perhaps you've found a bug in MrsWatson, or maybe even added a new
+feature which you would like to see merged into the codebase. Great!
+Patches are happily accepted into the project, provided they meet the
+following conditions:
+
+* The [test suite][1] still passes with 100% success rate
+ - Also, you get big bonus points for adding new tests, both in the case
+ of bugfixes or new features.
+ - The test suite is also run through [valgrind][5] and must not leak any
+ resources or overwrite memory boundaries. As valgrind only runs well on
+ Linux, you may ask for valgrind verification in your pull request if
+ you do not have access to a Linux machine/VM.
+* The code is properly formatted, and meets the project's style and
+ readability conventions.
+* In the case of a new feature, it provides something which is useful to
+ the broader community.
+
+Regarding the last point, niche features which are only likely to be used by
+yourself should not be submitted to the codebase. Since the code is
+open-source, you are free to maintain a fork with whatever crazy features
+you like and pull from upstream.
+
+Lastly, it should be noted that patches are *only* accepted via GitHub on
+the [project's official homepage][4]. Patches sent as diffs over email,
+with carrier pigeons, etc., will be asked to resubmit as pull requests over
+GitHub. This is because GitHub offers code review and public visibility of
+the merge process.
+
+
+Coding Style Conventions
+------------------------
+
+MrsWatson is indented in [K&R style][2], with the addition of the 1TBS
+convention for function declarations. This style has been chosen for its
+widespread familiarity and good readability. Indentation width is 4 spaces
+(no tabs). Camel-casing is used for class and variable names, with
+uppercase used for types and lowercase for instances.
+
+You can use the [astyle][3] program to format your additions to MrsWatson.
+Please run the script `scripts/format-code.sh` from the project's top-level
+directory before submitting any pull requests.
+
+
+Coding Conventions
+------------------
+
+MrsWatson is written in C, although there is a small amount of C++ code
+used to communicate with VST plug-ins. Please do not expand the amount of
+C++ code in the project unless strictly necessary (and the same goes for
+other languages such as Obj-C).
+
+That said, MrsWatson uses a python-like meta-class structure which you
+should adhere to. Also, long and readable names are preferred (which is in
+contrast to much C code on the internet).
+
+
+Meta-Classes
+------------
+
+Classes in MrsWatson are simple structs with some associated functions that
+are able to operate on them. A naming convention is used to associate the
+class with the associated functions. Each of these functions takes in a
+"self" argument as its first parameter, just like in Python. Also like
+Python, there are no private members in the structs, so a leading underscore
+is used to indicate that a member should not be directly accessed.
+
+Instead of passing raw pointers throughout the source code, typedefs are
+used to make the code easier to understand. Thus, a sample class might look
+like this:
+
+ typedef struct {
+ int foo;
+ char _bar;
+ char* baz;
+ } MyClassMembers;
+ typedef MyClassMembers* MyClass;
+
+Each class should provide a constructor function and a destructor function
+so that the caller does not need to manually allocate and free the
+associated memory. By convention, these functions should be prefixed with
+"new" and "free". Continuing our example:
+
+ MyClass newMyClass(void)
+ {
+ MyClass result = (MyClass)malloc(sizeof(MyClassMembers));
+ result->foo = 0;
+ result->_bar = 'a';
+ result->baz = NULL;
+ return result;
+ }
+
+ void freeMyClass(MyClass self)
+ {
+ if (self != NULL) {
+ free(self->baz);
+ free(self);
+ }
+ }
+
+Any member functions for this class should begin with the class name. This
+begins to get a little verbose, but at least it makes the code easy to
+understand. Continuing our example:
+
+ char myClassGetBar(MyClass self)
+ {
+ return self->_bar;
+ }
+
+ size_t myClassBazLength(MyClass self)
+ {
+ return self->baz != NULL ? strlen(self->baz) : 0;
+ }
+
+The result is a convention that allows one to write code like this:
+
+ MyClass whatever = newMyClass();
+ if(myClassBazLength(whatever)) {
+ char ch = myClassGetBar(whatever);
+ }
+ freeMyClass(whatever);
+
+
+[1]: TESTING.md
+[2]: http://en.wikipedia.org/wiki/Indent_style#K.26R_style
+[3]: http://astyle.sourceforge.net/astyle.html
+[4]: https://github.com/teragonaudio/MrsWatson
+[5]: http://valgrind.org/
diff --git a/ChangeLog.txt b/ChangeLog.txt
new file mode 100644
index 0000000..72dc504
--- /dev/null
+++ b/ChangeLog.txt
@@ -0,0 +1,73 @@
+Version 0.9.7:
+- New command-line option for setting parameters
+- Ensure that executable runs on 10.5 and above
+- Now have a single option for setting time signature
+- Handle key signature & proprietary MIDI meta events
+- Fix several small memory leaks
+- Fix several crashes
+- Log CPU usage overloads
+- Bugfixes, tests with 32/64 bit detection
+- Bugfixes, tests with big/little endian detection
+- New and more robust file API
+- Fix CMake project generation on Mac/Windows
+- Allow test suite to show log output from app
+- Improve internal documentation
+
+Version 0.9.6:
+- Compatibility with VST shell plugins (including Waves plugins)
+- Compatibility fixes with plugins that declare more than 2 outputs
+ (including Independence)
+- Compatibility fixes for Chainer and other plugins which rely on VstEvents*
+ struct to remain in memory during processReplacing()
+- MrsWatson now sends correct musical time in PPQ/Bars when asked by
+ plugin
+- Add 64-bit binaries to distribution zipfile (experimental)
+- Fix tons of memory leaks; test suite now runs with 0 bytes leaked
+- Fix several memory corruption bugs, valgrind now runs test suite
+ with no errors
+- Windows build migrated to cmake
+- Several other bugfixes and small improvements
+
+Version 0.9.5:
+- Better support for MIDI meta events. Tempo and time signature are now
+ correctly read from MIDI files.
+- Fix a bug where MrsWatson would not know when to stop processing an
+ instrument plugin chain.
+- Fix a bug where some monophonic VSTi's would not receive notes data in
+ an expected format.
+- Fix some memory corruption bugs.
+- Improved application test suite.
+
+Version 0.9.4:
+- Fix several heap corruption bugs
+- Fix build on linux
+- Organizing code into packages
+- Various small bugfixes
+- Improve test coverage
+
+Version 0.9.3:
+- Introduce CMake as build system for unix platforms (Linux & Mac OSX)
+- Several crash fixes reported by users
+- Ability to generate diagnostic error reports
+- Improved logging
+- Improved test coverage
+- More accurate time recording on all platforms
+- Other small improvements
+- FXP presets with chunks now supported
+
+Version 0.9.2:
+- Using Visual Studio to build on Windows
+- Introduced test suite, though test coverage is current small
+- Experimental native support for writing WAVE data (only active
+ for the Windows build right now).
+- Update to audiofile 0.3.4
+- Fix a number of Windows bugs
+- Improved log formatting
+- Can send time in PPQ
+- Command line option for manually setting MIDI time division
+
+Version 0.9.1:
+- Switching from libaiff to audiofile (provides WAV support)
+
+Version 0.9.0:
+- Initial public release
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..6e6ba96
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,46 @@
+MrsWatson, Copyright (c) 2012, Teragon Audio. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+* 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 HOLDER 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.
+
+Third-party libraries
+---------------------
+VST is a trademark of Steinberg Media Technologies GmbH. The VST SDK is
+licensed unde a separate agreement which can be found here:
+
+http://www.steinberg.net/en/company/developer.html
+
+
+Audiofile is licensed under the Library GNU Public Licence:
+
+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., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307 USA.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ff90069
--- /dev/null
+++ b/README.md
@@ -0,0 +1,196 @@
+MrsWatson
+=========
+
+MrsWatson is a command-line audio plugin host. It takes an audio and/or MIDI
+file as input, and processes it through one or more audio plugins. Currently
+MrsWatson only supports VST 2.x plugins, but more formats are planned in the
+future. MrsWatson was designed for primarily three purposes:
+
+* Audio plugin development and testing
+* Automated audio processing for servers or other applications
+* Unit testing audio plugins
+
+
+Examples
+--------
+
+Say you have an audio file which you would like processed through a group of
+mastering plugins.
+
+ mrswatson --input mysong.pcm --output out.pcm --plugin plugin1;plugin2
+
+This will print the following output:
+
+ - 00000000 000000 Plugin 'plugin1' is of type VST2.x
+ - 00000000 000000 Plugin 'plugin2' is of type VST2.x
+ - 00000000 000000 MrsWatson version 0.1.0 initialized
+ - 00000000 000000 Opening VST2.x plugin 'plugin1'
+ - 00000000 000029 Opening VST2.x plugin 'plugin2'
+ - 00000000 000144 Processing with samplerate 44100, blocksize 512, 2 channels
+ - 03532800 000204 Total processing time 60ms, approximate breakdown by component:
+ - 03532800 000204 plugin1: 38ms
+ - 03532800 000204 plugin2: 11ms
+ - 03532800 000204 MrsWatson: 11ms
+ - 03532800 000204 Read 3528000 frames from mysong.pcm, wrote 3532800 frames to out.pcm
+ - 03532800 000204 Shutting down
+ - 03532800 000210 Closing plugin 'plugin1'
+ - 03532800 000212 Closing plugin 'plugin2'
+ - 03532800 000212 Goodbye!
+
+To see more or less logging output, use the `--verbose` or `--quiet` options,
+respectively. MrsWatson generates colored output (if your terminal supports
+it) with two times per line, the first for the current sample and the second
+for the time in milliseconds since processing began. The sample time also
+changes colors after every 44100 samples to help visually break up processing
+times. This value can be changed with the `--zebra-size` option.
+
+To process a MIDI file through an instrument, you'd do something like this:
+
+ mrswatson --midi-file mysong.mid --output out.pcm --plugin piano,soft.fxp
+
+Like the first example, a list of plugins separated with semi-colons can be
+given here so that the audio generated by the instrument can be processed
+through any number of effects. Each plugin name can be followed by a comma and
+the location to a preset file to be loaded before processing.
+
+Complete help for MrsWatson can be found by running the program with no
+arguments, or with `--help`. The `--help` switch prints quite a lot of output,
+so you can also use `--options` to see all supported options and their default
+values.
+
+
+Loading Plugins
+---------------
+
+Currently, MrsWatson loads plugins by their short name by searching in the
+standard installation locations for your platform, as well as the current
+working directory and by absolute path. Use the `--list-plugins` option to see
+the order of locations searched and the plugins found there.
+
+While MrsWatson has some understanding of getting and setting plugin
+parameters, it does not extend this functionality to the end user. Rather than
+passing a huge list of parameters on the command line, one should instead
+create a preset for the plugin and load it with the comma-separated syntax
+as shown above.
+
+
+Limitations
+-----------
+
+As MrsWatson is a new codebase, there are lots of missing features, some more
+important than others. To encourage a quick initial release, the following
+features are not yet present in MrsWatson, but may be added at some point in
+the future:
+
+* AudioUnit plugins on Mac OSX
+* File support for compressed audio
+* Resampling of input source if desired
+* True realtime mode
+
+I have also tried to identify incomplete areas of the code and log them to the
+console, which means that we are aware that this feature is missing and will
+be added soon. If you see some other missing functionality or experience a
+crash or other bug, please report an issue on the [project page][3].
+
+
+Bug Reporting
+-------------
+
+If you believe you have found a bug in MrsWatson, please try first running it
+with the `--verbose` argument. This will generate extra logging output which
+may help to solve the problem.
+
+The easiest way to report a bug is to send an email to Teragon Audio's support
+address: support (at) teragonaudio (dot) com. MrsWatson has a special
+command-line switch to aid in diagnosing runtime problems, `--error-report`.
+When enabled it will create a zipfile on the desktop containing the input,
+output, logs, and optionally the plugins themselves. Please include these
+reports for bugs resulting in incorrect behavior or crashes.
+
+A test suite program, named `mrswatsontest`, can be found in the Mrswatson
+zipfile. If tests fail on your platform, please report this along with your
+bug.
+
+MrsWatson uses [GitHub issues][4] for bug reporting, if you would like to
+submit an issue yourself.
+
+
+Building
+--------
+
+Instructions for building MrsWatson can be found in the file
+[Building.txt][9].
+
+
+History and Name
+----------------
+
+MrsWatson takes its name from a prior product from [Teragon Audio][1] called
+*MissWatson* (and the original name there, in case you were wondering, is a
+bit of a joke on the [Dr. Watson][2] utility).
+
+In 2009 I sold the exclusive rights to MissWatson to a company interested in
+using it for server-side audio processing. As per the terms of our agreement,
+I made one last public release, and removed the code from my website. Since
+then, I have received numerous emails and inquiries regarding the project,
+mostly from plugin developers or people interested in VST technology. Though I
+have no regrets about discontinuing the original MissWatson (particularly
+since I didn't have time to properly maintain it), I felt that it was a useful
+utility which should be made available and open-sourced.
+
+In 2011, my NDA with the aforementioned company expired, and I started working
+on a new MissWatson. MrsWatson is the result of this labor.
+
+As I no longer have the source code for MissWatson, MrsWatson is a black-box
+implementation. They share only the name and concept in common. The two
+programs are **incompatible** and have **fundamentally different behavior**.
+They are also written in different languages (the original in C++, MrsWatson
+in C), and have completely different internal architecture.
+
+
+Donate
+------
+
+If you are using MrsWatson to do something cool, please send me a link to your
+project! If you appreciate MrsWatson and would like to donate money, please
+instead make a donation to a charity on our behalf, and let us know about it.
+The organizations which have helped us the most are:
+
+* [EFF][5]: Without the EFF, programs like MrsWatson would be significantly
+ harder to create and distribute.
+* [Wikipedia][6]: Writing MrsWatson involves a lot of research as well as
+ coding, and Wikipedia is an essential part of this.
+
+
+Special Thanks
+--------------
+
+Big additional thanks to:
+
+* Andrew McCrea, (@thrusong)
+* Michael Pruett, (@mpruett)
+* Jarl Friis (@jarl-dk)
+
+
+Licensing
+---------
+
+MrsWatson is made available under the BSD license. For more details, see the
+`LICENSE.txt` file distributed with the source code. MrsWatson also uses the
+following third-party libraries, which are licensed under the respective
+agreements:
+
+* [VST][7]: Licensed under Steinberg's VST SDK license agreement, version 2.4.
+ For more information, see Steinberg's developer portal.
+* [libaudiofile][8]: Written by Michael Pruett, licensed under GNU Library
+ General Public License.
+
+[1]: http://www.teragonaudio.com
+[2]: http://en.wikipedia.org/wiki/Dr._Watson_(debugger)
+[3]: http://github.com/teragonaudio/MrsWatson
+[4]: https://github.com/teragonaudio/MrsWatson/issues
+[5]: https://supporters.eff.org/donate
+[6]: http://wikimediafoundation.org/wiki/WMFJA085/en
+[7]: http://www.steinberg.net/en/company/developer.html
+[8]: http://audiofile.68k.org/
+[9]: https://github.com/teragonaudio/MrsWatson/blob/master/doc/Building.md
diff --git a/TESTING.md b/TESTING.md
new file mode 100644
index 0000000..b5398fa
--- /dev/null
+++ b/TESTING.md
@@ -0,0 +1,43 @@
+Testing MrsWatson
+=================
+
+MrsWatson has an extensive test suit that is used to verify the program is
+running correctly. All releases of MrsWatson must pass the entire test
+suite with no failed tests, so if you modify any of the source code, it is a
+good idea to run the suite to make sure that all tasks continue to pass.
+Additionally, each release must pass the test suite run through
+[valgrind][1], in order to ensure that no memory leaks or corruption is
+present.
+
+The test suite is built by the generated project file, and is named
+`mrswatsontest`, or `mrswatsontest64` if you are building for 64-bit. This
+application contains all of the internal unit tests, but is also capable of
+launching the MrsWatson binary for integration tests.
+
+
+Integration Tests
+-----------------
+
+The test suite also has a number of integration tests which execute the
+built binary MrsWatson application, running it with a variety of
+command-line arguments and input files. The output generated by these tests
+is checked for audio quality and the expected output code. Currently, the
+audio quality verification includes the following checks:
+
+* Silence (ie, output was produced)
+* Clipping
+* Distortion
+
+The integration tests (called "application test suite" in `mrswatsontest`)
+rely on the [AudioTestData repository][2], which is a submodule of the
+MrsWatson project. This repository contains several pre-compiled VST
+plugins for various platforms, audio data files, and MIDI data files.
+
+To run the application test suite, you will need to provide `mrswatsontest`
+with the location of the AudioTestData repository (which is included as a
+submodule of MrsWatson), and the location of MrsWatson itself. For more
+information, run `mrswatsontest --help full` from the command line.
+
+
+[1]: http://valgrind.org/
+[2]: https://github.com/teragonaudio/AudioTestData
diff --git a/bin/Linux/mrswatson b/bin/Linux/mrswatson
new file mode 100755
index 0000000..2b892a1
--- /dev/null
+++ b/bin/Linux/mrswatson
Binary files differ
diff --git a/bin/Linux/mrswatson64 b/bin/Linux/mrswatson64
new file mode 100755
index 0000000..c2c2cd1
--- /dev/null
+++ b/bin/Linux/mrswatson64
Binary files differ
diff --git a/bin/Linux/mrswatsontest b/bin/Linux/mrswatsontest
new file mode 100755
index 0000000..88a9980
--- /dev/null
+++ b/bin/Linux/mrswatsontest
Binary files differ
diff --git a/bin/Linux/mrswatsontest64 b/bin/Linux/mrswatsontest64
new file mode 100755
index 0000000..b2dd7b4
--- /dev/null
+++ b/bin/Linux/mrswatsontest64
Binary files differ
diff --git a/bin/Windows/mrswatson.exe b/bin/Windows/mrswatson.exe
new file mode 100644
index 0000000..3a7eb4f
--- /dev/null
+++ b/bin/Windows/mrswatson.exe
Binary files differ
diff --git a/bin/Windows/mrswatson64.exe b/bin/Windows/mrswatson64.exe
new file mode 100644
index 0000000..ac70b80
--- /dev/null
+++ b/bin/Windows/mrswatson64.exe
Binary files differ
diff --git a/bin/Windows/mrswatsontest.exe b/bin/Windows/mrswatsontest.exe
new file mode 100644
index 0000000..7a77a85
--- /dev/null
+++ b/bin/Windows/mrswatsontest.exe
Binary files differ
diff --git a/bin/Windows/mrswatsontest64.exe b/bin/Windows/mrswatsontest64.exe
new file mode 100644
index 0000000..50afc9c
--- /dev/null
+++ b/bin/Windows/mrswatsontest64.exe
Binary files differ
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
new file mode 100644
index 0000000..bbcfa35
--- /dev/null
+++ b/main/CMakeLists.txt
@@ -0,0 +1,32 @@
+cmake_minimum_required(VERSION 2.6)
+project(MrsWatsonMain)
+
+include(${cmake_SCRIPTS_DIR}/ConfigureTarget.cmake)
+
+set(mrswatsonmain_SOURCES MrsWatsonMain.c)
+set(mrswatsonmain_LIBS mrswatsoncore)
+set(mrswatsonmain_64_LIBS mrswatsoncore64)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(mrswatsonmain_LIBS ${mrswatsonmain_LIBS} dl)
+ set(mrswatsonmain_64_LIBS ${mrswatsonmain_64_LIBS} dl)
+endif()
+
+if(WITH_AUDIOFILE)
+ set(mrswatsonmain_LIBS ${mrswatsonmain_LIBS} audiofile)
+ set(mrswatsonmain_64_LIBS ${mrswatsonmain_64_LIBS} audiofile64)
+
+ if(WITH_FLAC)
+ set(mrswatsonmain_LIBS ${mrswatsonmain_LIBS} flac)
+ set(mrswatsonmain_64_LIBS ${mrswatsonmain_64_LIBS} flac64)
+ endif()
+endif()
+
+# Main executable target and associated libraries
+add_executable(mrswatson ${mrswatsonmain_SOURCES})
+target_link_libraries(mrswatson ${mrswatsonmain_LIBS})
+add_executable(mrswatson64 ${mrswatsonmain_SOURCES})
+target_link_libraries(mrswatson64 ${mrswatsonmain_64_LIBS})
+
+configure_target(mrswatson 32)
+configure_target(mrswatson64 64)
diff --git a/main/MrsWatsonMain.c b/main/MrsWatsonMain.c
new file mode 100644
index 0000000..71063ee
--- /dev/null
+++ b/main/MrsWatsonMain.c
@@ -0,0 +1,70 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include "MrsWatson.h"
+#include "logging/ErrorReporter.h"
+#include "logging/EventLogger.h"
+
+// This must be global so that in case of a crash or signal, we can still generate
+// a complete error report with a reference
+static ErrorReporter gErrorReporter = NULL;
+
+static void handleSignal(int signum)
+{
+ logCritical("Sent signal %d, exiting", signum);
+
+ if (gErrorReporter != NULL && gErrorReporter->started) {
+ errorReporterClose(gErrorReporter);
+ } else {
+ logPossibleBug("MrsWatson (or one of its hosted plugins) has encountered a serious error and crashed.");
+ }
+
+ exit(RETURN_CODE_SIGNAL + signum);
+}
+
+int main(int argc, char *argv[])
+{
+ gErrorReporter = newErrorReporter();
+
+ // Set up signal handling only after logging is initialized. If we crash before
+ // here, something is seriously wrong.
+#ifdef SIGHUP
+ signal(SIGHUP, handleSignal);
+#endif
+#ifdef SIGINT
+ signal(SIGINT, handleSignal);
+#endif
+#ifdef SIGQUIT
+ signal(SIGQUIT, handleSignal);
+#endif
+#ifdef SIGILL
+ signal(SIGILL, handleSignal);
+#endif
+#ifdef SIGABRT
+ signal(SIGABRT, handleSignal);
+#endif
+#ifdef SIGFPE
+ signal(SIGFPE, handleSignal);
+#endif
+#ifdef SIGKILL
+ signal(SIGKILL, handleSignal);
+#endif
+#ifdef SIGBUS
+ signal(SIGBUS, handleSignal);
+#endif
+#ifdef SIGSEGV
+ signal(SIGSEGV, handleSignal);
+#endif
+#ifdef SIGSYS
+ signal(SIGSYS, handleSignal);
+#endif
+#ifdef SIGPIPE
+ signal(SIGPIPE, handleSignal);
+#endif
+#ifdef SIGTERM
+ signal(SIGTERM, handleSignal);
+#endif
+
+ return mrsWatsonMain(gErrorReporter, argc, argv);
+}
diff --git a/main/NOTES b/main/NOTES
new file mode 100644
index 0000000..7a62491
--- /dev/null
+++ b/main/NOTES
@@ -0,0 +1,101 @@
+
+0000000: 5249 4646 2848 4a02 5741 5645 666d 7420 RIFF(HJ.WAVEfmt
+0000010: 1400 0000 0100 0200 44ac 0000 10b1 0200 ........D.......
+0000020: 0400 1000 0000 0000 6461 7461 0048 4a02 ........data.HJ.
+0000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+
+0000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+
+
+
+
+//ok good
+0000000: 5249 4646 2848 4a02 5741 5645 666d 7420 RIFF(HJ.WAVEfmt
+0000010: 1400 0000 0100 0200 44ac 0000 10b1 0200 ........D.......
+0000020: 0400 1000 0000 0000 6461 7461 0048 4a02 ........data.HJ.
+0000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+
+
+
+
+
+$ cat /tmp/test2.wav | xxd | head
+0000000: 5249 4646 2470 db01 5741 5645 666d 7420 RIFF$p..WAVEfmt
+0000010: 1000 0000 0100 0200 44ac 0000 10b1 0200 ........D....│··
+0000020: 0400 1000 6461 7461 0070 db01 0000 0000 ....data.p...│··
+0000030: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000040: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000050: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000060: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000070: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000080: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+ok well the last thing that needs to be done with mrswatson, I need some sort of way to pass tempo on the commandline. for some reason, there is
+a commandline argument for it, but it doesn't seem to work? we had to hardcode tempo, if you remember
+seems like it should be easy to fix yeah just change hardcoded value with value from cli variable it already have let's take a look at it? sure
+
+https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+5249 4646 2470 db01 5741 5645 # riff , size, wav
+666d 7420 # fmt
+1000 0000 # chunck1 size
+0100 0200 # PCM, 2 ch
+44ac 0000 # Sample rate. so wee that in original wav it's 00 00 0000. yeah also
+
+it's 1000 0000 vs 1400 0000
+4 is tbytes longer htoo, yeah it is...
+
+kww
+ok
+5249 4646 === "RIFF"
+2870 db01 === size of whole file
+
+but in the second one
+2470 db01 is the size of the whole file...is that an error do you think? could be but it's looks like 4 bytes short only well
+docs say:
+This is the size of the
+entire file in bytes minus 8 bytes for the
+two fields not included in this count:
+ChunkID and ChunkSize.
+almost like he forgot to subtract one of them? y or subtracted too many...not sureep, can fiix that too. ok
+should it all be in this function in the code we were looking at? yep. ok I'll try to fix it I guess hoping I'm not going
+to get confused about subtracting binary or something, are the lengths we're dealing with here all integers/decimals? yes
+so it should be easy? yep
+
+
+yeah thanks, slow connection can't find anything
+
+
+
+bash-3.2$ cat test1419968848.wav | xxd | head
+0000000: 5249 4646 2870 db01 5741 5645 666d 7420 RIFF(p..WAVEfmt
+0000010: 1400 0000 0100 0200 0000 0000 10b1 0200 .............
+0000020: 0400 1000 0000 0000 6461 7461 0070 db01 ........data.p..
+0000030: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000040: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000050: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000060: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000070: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000080: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+0000090: 0000 0000 0000 0000 0000 0000 0000 0000 .............│··
+bash-3.2$ cat test142*.wav | xxd | head
+0000000: 5249 4646 2870 db01 5741 5645 666d 7420 RIFF(p..WAVEfmt
+0000010: 1400 0000 0100 0200 44ac 0000 10b1 0200 ........D.......
+0000020: 0400 1000 0000 0000 6461 7461 0070 db01 ........data.p..
+0000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000040: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
+0000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
diff --git a/main/cmd b/main/cmd
new file mode 100644
index 0000000..2471520
--- /dev/null
+++ b/main/cmd
@@ -0,0 +1 @@
+./mrswatson --plugin 'Omnisphere,example.fxp' -m example.mid -o /tmp/test.wav
diff --git a/main/endian_test b/main/endian_test
new file mode 100755
index 0000000..3791421
--- /dev/null
+++ b/main/endian_test
Binary files differ
diff --git a/main/endian_test.c b/main/endian_test.c
new file mode 100644
index 0000000..3e831dd
--- /dev/null
+++ b/main/endian_test.c
@@ -0,0 +1,11 @@
+#include <stdio.h>
+int main(){
+ int n = 1;
+
+// little endian if true
+ if(*(char *)&n == 1){
+ printf("little endian\n");
+ }else{
+ printf("big endian\n");
+ }
+}
diff --git a/main/example.fxp b/main/example.fxp
new file mode 100755
index 0000000..b75c25f
--- /dev/null
+++ b/main/example.fxp
Binary files differ
diff --git a/main/example.mid b/main/example.mid
new file mode 100644
index 0000000..c1873a4
--- /dev/null
+++ b/main/example.mid
Binary files differ
diff --git a/main/example.mid.old b/main/example.mid.old
new file mode 100755
index 0000000..bf3bfc0
--- /dev/null
+++ b/main/example.mid.old
Binary files differ
diff --git a/main/example_1.mid b/main/example_1.mid
new file mode 100755
index 0000000..113e354
--- /dev/null
+++ b/main/example_1.mid
Binary files differ
diff --git a/main/mrswatson b/main/mrswatson
new file mode 100755
index 0000000..6eb0709
--- /dev/null
+++ b/main/mrswatson
Binary files differ
diff --git a/main/mrswatson64 b/main/mrswatson64
new file mode 100755
index 0000000..8fd7980
--- /dev/null
+++ b/main/mrswatson64
Binary files differ
diff --git a/main/mytest.bash b/main/mytest.bash
new file mode 100644
index 0000000..772bc11
--- /dev/null
+++ b/main/mytest.bash
@@ -0,0 +1 @@
+/usr/local/lib/MrsWatson/main/mrswatson --tempo 100 -p 'Omnisphere,example.fxp' -m example.mid -o test`date +%s`.wav
diff --git a/main/new_example.bash b/main/new_example.bash
new file mode 100644
index 0000000..2a5e816
--- /dev/null
+++ b/main/new_example.bash
@@ -0,0 +1,2 @@
+new_example() { ex=$(cat ~/MIDI_FILES/type0 | sort -R | sed -n 1p); cp ~/MIDI_FILES/files/$ex /tmp/example.mid ; cp /tmp/example.mid ~/example.mid; }
+new_example
diff --git a/main/rebuild.bash b/main/rebuild.bash
new file mode 100644
index 0000000..da93704
--- /dev/null
+++ b/main/rebuild.bash
@@ -0,0 +1,3 @@
+#!/bin/bash
+cd ../; cmake -G "Unix Makefiles"; make clean && make; cd main;
+
diff --git a/scripts/format-code.sh b/scripts/format-code.sh
new file mode 100755
index 0000000..606379a
--- /dev/null
+++ b/scripts/format-code.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+astyle --options=./scripts/mrswatson.astyle --recursive "main/*.c" "source/*.c" "source/*.cpp" "source/*.h" "test/*.c" "test/*.h"
diff --git a/scripts/make-release.sh b/scripts/make-release.sh
new file mode 100755
index 0000000..b91b5c5
--- /dev/null
+++ b/scripts/make-release.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# Get latest version number
+VERSION=$(git tag | sort | tail -1)
+
+# Copy files to temporary working directory
+OUTDIR=MrsWatson-$VERSION
+cp -r bin $OUTDIR
+cp README.md $OUTDIR/README.txt
+cp LICENSE.txt $OUTDIR/LICENSE.txt
+
+# Copy documentation
+mkdir $OUTDIR/Docs
+for x in doc/* ; do
+ FILENAME=$(echo $(basename $x) | cut -d '.' -f 1)
+ cp $x $OUTDIR/Docs/$FILENAME.txt
+done
+
+# Cleanup crap which should not be shipped with distribution zipfile
+find $OUTDIR -name .DS_Store -exec rm {} \;
+rm -rf $OUTDIR/*/Debug
+rm -rf $OUTDIR/*/Release
+
+zip -r MrsWatson.zip $OUTDIR
+cp MrsWatson.zip MrsWatson-$VERSION.zip
+
+# Print out distribution zipfile size
+du -hs MrsWatson.zip
+
+# Cleanup scratch directory
+rm -rf $OUTDIR
diff --git a/scripts/mrswatson.astyle b/scripts/mrswatson.astyle
new file mode 100644
index 0000000..749f1d9
--- /dev/null
+++ b/scripts/mrswatson.astyle
@@ -0,0 +1,8 @@
+style=1tbs
+indent=spaces
+indent-preproc-define
+break-blocks
+pad-header
+pad-oper
+align-pointer=name
+align-reference=name
diff --git a/scripts/new-file.sh b/scripts/new-file.sh
new file mode 100755
index 0000000..9cb65bb
--- /dev/null
+++ b/scripts/new-file.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+if [ -z "$1" ] ; then
+ printf "Usage: new-file [file basename] (package)\n\n"
+ exit 1
+fi
+
+fileBasename=$1
+if ! [ -z "$2" ] ; then
+ filePackage=$2
+else
+ filePackage=""
+fi
+
+fullDate=$(date +"%d %b %y")
+year=$(date +"%Y")
+
+printf "//\n\
+// %s.h - MrsWatson\n\
+// Created by Nik Reiman on %s.\n\
+// Copyright (c) %s Teragon Audio. All rights reserved.\n\
+//\n\
+// Redistribution and use in source and binary forms, with or without\n\
+// modification, are permitted provided that the following conditions are met:\n\
+//\n\
+// * Redistributions of source code must retain the above copyright notice,\n\
+// this list of conditions and the following disclaimer.\n\
+// * Redistributions in binary form must reproduce the above copyright notice,\n\
+// this list of conditions and the following disclaimer in the documentation\n\
+// and/or other materials provided with the distribution.\n\
+//\n\
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n\
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n\
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n\
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n\
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n\
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n\
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n\
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n\
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n\
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n\
+// POSSIBILITY OF SUCH DAMAGE.\n\
+//\n\
+\n\
+#ifndef MrsWatson_${fileBasename}_h\n\
+#define MrsWatson_${fileBasename}_h\n\
+\n\
+#endif\n" "$fileBasename" "$fullDate" "$year" > source/$filePackage/$fileBasename.h
+printf "Created source/$filePackage/$fileBasename.h\n"
+
+printf "//\n\
+// %s.c - MrsWatson\n\
+// Created by Nik Reiman on %s.\n\
+// Copyright (c) %s Teragon Audio. All rights reserved.\n\
+//\n\
+// Redistribution and use in source and binary forms, with or without\n\
+// modification, are permitted provided that the following conditions are met:\n\
+//\n\
+// * Redistributions of source code must retain the above copyright notice,\n\
+// this list of conditions and the following disclaimer.\n\
+// * Redistributions in binary form must reproduce the above copyright notice,\n\
+// this list of conditions and the following disclaimer in the documentation\n\
+// and/or other materials provided with the distribution.\n\
+//\n\
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n\
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n\
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n\
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\n\
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n\
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\n\
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n\
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\n\
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n\
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n\
+// POSSIBILITY OF SUCH DAMAGE.\n\
+//\n\
+\n\
+#include \"${fileBasename}.h\"\n\
+\n" "$fileBasename" "$fullDate" "$year" > source/$filePackage/$fileBasename.c
+printf "Created source/$filePackage/$fileBasename.c\n"
+
+printf "#include \"unit/TestRunner.h\"\n\
+#include \"$filePackage/$fileBasename.h\"\n\
+\n\
+\n" > test/$filePackage/${fileBasename}Test.c
+printf "Created test/$filePackage/${fileBasename}Test.c\n"
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
new file mode 100644
index 0000000..f430ba8
--- /dev/null
+++ b/source/CMakeLists.txt
@@ -0,0 +1,138 @@
+cmake_minimum_required(VERSION 2.8.11)
+project(mrswatsoncore)
+
+include(${cmake_SCRIPTS_DIR}/ConfigureTarget.cmake)
+
+set(mrswatsoncore_SOURCES
+ app/BuildInfo.c
+ app/ProgramOption.c
+ audio/AudioSettings.c
+ audio/SampleBuffer.c
+ base/CharString.c
+ base/Endian.c
+ base/File.c
+ base/LinkedList.c
+ base/PlatformInfo.c
+ io/RiffFile.c
+ io/SampleSource.c
+ io/SampleSourcePcm.c
+ io/SampleSourceSilence.c
+ io/SampleSourceWave.c
+ logging/ErrorReporter.c
+ logging/EventLogger.c
+ logging/LogPrinter.c
+ midi/MidiEvent.c
+ midi/MidiSequence.c
+ midi/MidiSource.c
+ midi/MidiSourceFile.c
+ MrsWatson.c
+ MrsWatsonOptions.c
+ plugin/Plugin.c
+ plugin/PluginChain.c
+ plugin/PluginGain.c
+ plugin/PluginLimiter.c
+ plugin/PluginPassthru.c
+ plugin/PluginPreset.c
+ plugin/PluginPresetFxp.c
+ plugin/PluginPresetInternalProgram.c
+ plugin/PluginSilence.c
+ plugin/PluginVst2x.cpp
+ plugin/PluginVst2xHostCallback.cpp
+ plugin/PluginVst2xId.c
+ time/AudioClock.c
+ time/TaskTimer.c
+)
+
+set(mrswatsoncore_HEADERS
+ app/BuildInfo.h
+ app/ProgramOption.h
+ app/ReturnCodes.h
+ audio/AudioSettings.h
+ audio/SampleBuffer.h
+ base/CharString.h
+ base/Endian.h
+ base/File.h
+ base/LinkedList.h
+ base/PlatformInfo.h
+ base/Types.h
+ io/RiffFile.h
+ io/SampleSource.h
+ io/SampleSourcePcm.h
+ io/SampleSourceSilence.h
+ io/SampleSourceWave.h
+ logging/ErrorReporter.h
+ logging/EventLogger.h
+ logging/LogPrinter.h
+ midi/MidiEvent.h
+ midi/MidiSequence.h
+ midi/MidiSource.h
+ midi/MidiSourceFile.h
+ MrsWatson.h
+ MrsWatsonOptions.h
+ plugin/Plugin.h
+ plugin/PluginChain.h
+ plugin/PluginGain.h
+ plugin/PluginLimiter.h
+ plugin/PluginPassthru.h
+ plugin/PluginPreset.h
+ plugin/PluginPresetFxp.h
+ plugin/PluginPresetInternalProgram.h
+ plugin/PluginSilence.h
+ plugin/PluginVst2x.h
+ plugin/PluginVst2xHostCallback.h
+ plugin/PluginVst2xId.h
+ time/AudioClock.h
+ time/TaskTimer.h
+)
+
+if(WITH_AUDIOFILE)
+ set(mrswatsoncore_SOURCES
+ ${mrswatsoncore_SOURCES}
+ io/SampleSourceAudiofile.c
+ )
+ set(mrswatsoncore_HEADERS
+ ${mrswatsoncore_HEADERS}
+ io/SampleSourceAudiofile.h
+ )
+ include_directories(${CMAKE_SOURCE_DIR}/vendor/audiofile/libaudiofile)
+endif()
+
+# Add some extra platform-specific sources
+if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(mrswatsoncore_PLATFORM_SOURCES
+ plugin/PluginVst2xLinux.cpp
+ )
+elseif(APPLE)
+ set(mrswatsoncore_PLATFORM_SOURCES
+ base/PlatformInfoMac.m
+ plugin/PluginVst2xMac.cpp
+ )
+elseif(MSVC)
+ set(mrswatsoncore_PLATFORM_SOURCES
+ plugin/PluginVst2xWindows.cpp
+ )
+endif()
+
+source_group(app ".*/app/.*")
+source_group(audio ".*/audio/.*")
+source_group(base ".*/base/.*")
+source_group(io ".*/io/.*")
+source_group(logging ".*/logging/.*")
+source_group(midi ".*/midi/.*")
+source_group(plugin ".*/plugin/.*")
+source_group(time ".*/time/.*")
+
+add_library(mrswatsoncore STATIC
+ ${mrswatsoncore_SOURCES}
+ ${mrswatsoncore_PLATFORM_SOURCES}
+ ${mrswatsoncore_HEADERS}
+)
+
+add_library(mrswatsoncore64 STATIC
+ ${mrswatsoncore_SOURCES}
+ ${mrswatsoncore_PLATFORM_SOURCES}
+ ${mrswatsoncore_HEADERS}
+)
+
+configure_target(mrswatsoncore 32)
+configure_target(mrswatsoncore64 64)
diff --git a/source/MrsWatson.c b/source/MrsWatson.c
new file mode 100644
index 0000000..ae01976
--- /dev/null
+++ b/source/MrsWatson.c
@@ -0,0 +1,792 @@
+//
+// MrsWatson.c - MrsWatson
+// Created by Nik Reiman on 12/5/11.
+// Copyright (c) 2011 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <string.h>
+
+#include "app/BuildInfo.h"
+#include "audio/AudioSettings.h"
+#include "base/PlatformInfo.h"
+#include "io/SampleSource.h"
+#include "io/SampleSourcePcm.h"
+#include "logging/EventLogger.h"
+#include "logging/LogPrinter.h"
+#include "midi/MidiSequence.h"
+#include "midi/MidiSource.h"
+#include "plugin/PluginChain.h"
+#include "time/AudioClock.h"
+
+#include "MrsWatsonOptions.h"
+#include "MrsWatson.h"
+
+static void _printTaskTime(void *item, void *userData)
+{
+ TaskTimer taskTimer = (TaskTimer)item;
+ TaskTimer totalTimer = (TaskTimer)userData;
+ CharString prettyTimeString = taskTimerHumanReadbleString(taskTimer);
+ double timePercentage = 100.0f * taskTimer->totalTaskTime / totalTimer->totalTaskTime;
+ logInfo(" %s %s: %s (%2.1f%%)", taskTimer->component->data, taskTimer->subcomponent->data, prettyTimeString->data, timePercentage);
+ freeCharString(prettyTimeString);
+}
+
+static void _remapFileToErrorReport(ErrorReporter errorReporter, ProgramOptions options, unsigned int index, boolByte copyFile)
+{
+ if (options->options[index]->enabled) {
+ CharString optionString = programOptionsGetString(options, index);
+
+ if (copyFile) {
+ if (!errorReportCopyFileToReport(errorReporter, optionString)) {
+ logWarn("Failed copying '%s' to error report directory, please include this file manually", optionString);
+ }
+ }
+
+ errorReporterRemapPath(errorReporter, optionString);
+ }
+}
+
+static void printWelcomeMessage(int argc, char **argv)
+{
+ CharString stringBuffer = newCharString();
+ CharString versionString = buildInfoGetVersionString();
+ char *space;
+ int i;
+
+ logInfo("%s initialized, build %ld", versionString->data, buildInfoGetDatestamp());
+ // Recycle to use for the platform name
+ freeCharString(stringBuffer);
+ freeCharString(versionString);
+
+ // Don't bother doing a bunch of silly work in case the log level isn't debug
+ if (isLogLevelAtLeast(LOG_DEBUG)) {
+ PlatformInfo platform = newPlatformInfo();
+ logDebug("Host platform is %s (%s)", platform->shortName->data, platform->name->data);
+ logDebug("Application is %d-bit", platform->is64Bit ? 64 : 32);
+ freePlatformInfo(platform);
+
+ stringBuffer = newCharStringWithCapacity(kCharStringLengthLong);
+
+ for (i = 1; i < argc; i++) {
+ space = strchr(argv[i], ' ');
+
+ if (space != NULL) {
+ charStringAppendCString(stringBuffer, "\"");
+ }
+
+ charStringAppendCString(stringBuffer, argv[i]);
+
+ if (space != NULL) {
+ charStringAppendCString(stringBuffer, "\"");
+ }
+
+ charStringAppendCString(stringBuffer, " ");
+ }
+
+ logDebug("Launched with options: %s", stringBuffer->data);
+ freeCharString(stringBuffer);
+ }
+}
+
+static void printVersion(void)
+{
+ CharString versionString = buildInfoGetVersionString();
+ CharString wrappedLicenseInfo;
+ CharString licenseString = newCharStringWithCString(LICENSE_STRING);
+
+ printf("%s, build %ld\nCopyright (c) %ld, %s. All rights reserved.\n\n",
+ versionString->data, buildInfoGetDatestamp(), buildInfoGetYear(), VENDOR_NAME);
+ wrappedLicenseInfo = charStringWrap(licenseString, 0);
+ printf("%s\n\n", wrappedLicenseInfo->data);
+
+ freeCharString(licenseString);
+ freeCharString(wrappedLicenseInfo);
+ freeCharString(versionString);
+}
+
+static ReturnCodes buildPluginChain(PluginChain pluginChain, const CharString argument, const CharString pluginSearchRoot)
+{
+ // Construct plugin chain
+ if (!pluginChainAddFromArgumentString(pluginChain, argument, pluginSearchRoot)) {
+ return RETURN_CODE_INVALID_PLUGIN_CHAIN;
+ }
+
+ // No longer needed
+ freeCharString(pluginSearchRoot);
+
+ if (pluginChain->numPlugins == 0) {
+ logError("No plugins loaded");
+ return RETURN_CODE_INVALID_PLUGIN_CHAIN;
+ }
+
+ return RETURN_CODE_SUCCESS;
+}
+
+static ReturnCodes setupInputSource(SampleSource inputSource)
+{
+ if (inputSource == NULL) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+
+ if (inputSource->sampleSourceType == SAMPLE_SOURCE_TYPE_PCM) {
+ sampleSourcePcmSetSampleRate(inputSource, getSampleRate());
+ sampleSourcePcmSetNumChannels(inputSource, getNumChannels());
+ }
+
+ if (!inputSource->openSampleSource(inputSource, SAMPLE_SOURCE_OPEN_READ)) {
+ logError("Input source '%s' could not be opened", inputSource->sourceName->data);
+ return RETURN_CODE_IO_ERROR;
+ }
+
+ return RETURN_CODE_SUCCESS;
+}
+
+static ReturnCodes setupMidiSource(MidiSource midiSource, MidiSequence *outSequence)
+{
+ if (midiSource != NULL) {
+ if (!midiSource->openMidiSource(midiSource)) {
+ logError("MIDI source '%s' could not be opened", midiSource->sourceName->data);
+ return RETURN_CODE_IO_ERROR;
+ }
+
+ // Read in all events from the MIDI source
+ // TODO: This will not work if we want to support streaming MIDI events (ie, from a pipe)
+ *outSequence = newMidiSequence();
+
+ if (!midiSource->readMidiEvents(midiSource, *outSequence)) {
+ logWarn("Failed reading MIDI events from source '%s'", midiSource->sourceName->data);
+ return RETURN_CODE_IO_ERROR;
+ }
+ }
+
+ return RETURN_CODE_SUCCESS;
+}
+
+static ReturnCodes setupOutputSource(SampleSource outputSource)
+{
+ if (outputSource == NULL) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+
+ if (!outputSource->openSampleSource(outputSource, SAMPLE_SOURCE_OPEN_WRITE)) {
+ logError("Output source '%s' could not be opened", outputSource->sourceName->data);
+ return RETURN_CODE_IO_ERROR;
+ }
+
+ return RETURN_CODE_SUCCESS;
+}
+
+static void _processMidiMetaEvent(void *item, void *userData)
+{
+ MidiEvent midiEvent = (MidiEvent)item;
+ boolByte *finishedReading = (boolByte *)userData;
+
+ if (midiEvent->eventType == MIDI_TYPE_META) {
+ switch (midiEvent->status) {
+ case MIDI_META_TYPE_TEMPO:
+ setTempoFromMidiBytes(midiEvent->extraData);
+ break;
+
+ case MIDI_META_TYPE_TIME_SIGNATURE:
+ if (!setTimeSignatureFromMidiBytes(midiEvent->extraData)) {
+ logWarn("Could not set time signature from MIDI file");
+ }
+
+ break;
+
+ case MIDI_META_TYPE_TRACK_END:
+ logInfo("Reached end of MIDI track");
+ *finishedReading = true;
+ break;
+
+ default:
+ logWarn("Don't know how to process MIDI meta event of type 0x%x", midiEvent->status);
+ break;
+ }
+ }
+}
+
+/**
+ * Reads from inputSource.
+ *
+ * @param inputSource The SampleSource to read from.
+ * @param buffer The SampleBuffer to which the samples will be written.
+ * @return True if there is more input to read.
+ */
+boolByte readInput(SampleSource inputSource, SampleBuffer buffer)
+{
+ unsigned long framesRead;
+ unsigned long bufferSize = buffer->blocksize;
+
+ inputSource->readSampleBlock(inputSource, buffer);
+ // buffer->blocksize tells how many frames have been read from inputSource
+ framesRead = buffer->blocksize;
+
+ if (framesRead == bufferSize) {
+ // We have filled up the buffer, so return true to ask for more input
+ return true;
+ } else if (framesRead < bufferSize) {
+ // Partial read, meaning that we have reached the end of file
+ unsigned long numberOfFrames = (bufferSize - framesRead);
+ SampleBuffer silenceBuffer = newSampleBuffer(buffer->numChannels, numberOfFrames);
+
+ buffer->blocksize = framesRead + numberOfFrames;
+ sampleBufferCopyAndMapChannelsWithOffset(buffer, framesRead, silenceBuffer, 0, numberOfFrames);
+ freeSampleBuffer(silenceBuffer);
+
+ // Finished reading
+ return false;
+ } else {
+ // Return false so that this callback is not reached again. With such a weird error, we should
+ // not continue execution.
+ logInternalError("Read more frames than expected, this should not happen");
+ return false;
+ }
+}
+
+/**
+ * Writes to outputSource.
+ *
+ * @param outputSource The SampleSource to write to.
+ * @param silenceSource The source from where to write skipHeadFrames frames.
+ * @param buffer The SampleBuffer with the samples to be written.
+ * @param skipHeadFrames Number of frames to ignore before writing to outputSource.
+ */
+void writeOutput(SampleSource outputSource, SampleSource silenceSource, SampleBuffer buffer, unsigned long skipHeadFrames)
+{
+ unsigned long framesSkiped = silenceSource->numSamplesProcessed / buffer->numChannels;
+ unsigned long framesProcessed = framesSkiped + outputSource->numSamplesProcessed / buffer->numChannels;
+ unsigned long nextBlockStart = framesProcessed + buffer->blocksize;
+
+ if (framesProcessed != getAudioClock()->currentFrame) {
+ logInternalError("framesProcessed (%lu) != getAudioClock()->currentFrame (%lu)",
+ framesProcessed, getAudioClock()->currentFrame);
+ }
+
+ // Cut the delay at the start
+ if (nextBlockStart <= skipHeadFrames ) {
+ // Cutting away the whole block. nothing is written to the outputSource
+ silenceSource->writeSampleBlock(silenceSource, buffer);
+ } else if (framesProcessed < skipHeadFrames && skipHeadFrames < nextBlockStart) {
+ SampleBuffer sourceBuffer = newSampleBuffer(buffer->numChannels, buffer->blocksize);
+ unsigned long skippedFrames = skipHeadFrames - framesProcessed;
+ unsigned long soundFrames = nextBlockStart - skipHeadFrames;
+
+ // Cutting away start part of the block.
+ sourceBuffer->blocksize = skippedFrames;
+ sampleBufferCopyAndMapChannelsWithOffset(sourceBuffer, 0, buffer, 0, sourceBuffer->blocksize);
+ silenceSource->writeSampleBlock(silenceSource, sourceBuffer);
+
+ // Writing remaining end part of the block.
+ sourceBuffer->blocksize = soundFrames;
+ sampleBufferCopyAndMapChannelsWithOffset(sourceBuffer, 0, buffer, skippedFrames, sourceBuffer->blocksize);
+ outputSource->writeSampleBlock(outputSource, sourceBuffer);
+
+ freeSampleBuffer(sourceBuffer);
+ } else {
+ // Normal case: Nothing more to cut. The whole block shall be written.
+ outputSource->writeSampleBlock(outputSource, buffer);
+ }
+}
+
+int mrsWatsonMain(ErrorReporter errorReporter, int argc, char **argv)
+{
+ ReturnCodes result;
+ // Input/Output sources, plugin chain, and other required objects
+ SampleSource inputSource = NULL;
+ SampleSource outputSource = NULL;
+ AudioClock audioClock;
+ PluginChain pluginChain;
+ CharString pluginSearchRoot = newCharString();
+ boolByte shouldDisplayPluginInfo = false;
+ MidiSequence midiSequence = NULL;
+ MidiSource midiSource = NULL;
+ unsigned long maxTimeInMs = 0;
+ unsigned long maxTimeInFrames = 0;
+ unsigned long processingDelayInFrames;
+ ProgramOptions programOptions;
+ ProgramOption option;
+ Plugin headPlugin;
+ SampleBuffer inputSampleBuffer = NULL;
+ SampleBuffer outputSampleBuffer = NULL;
+ TaskTimer initTimer, totalTimer, inputTimer, outputTimer = NULL;
+ LinkedList taskTimerList = NULL;
+ CharString totalTimeString = NULL;
+ boolByte finishedReading = false;
+ SampleSource silentSampleOutput;
+ unsigned int i;
+
+ initTimer = newTaskTimerWithCString(PROGRAM_NAME, "Initialization");
+ totalTimer = newTaskTimerWithCString(PROGRAM_NAME, "Total Time");
+ taskTimerStart(initTimer);
+ taskTimerStart(totalTimer);
+
+ initEventLogger();
+ initAudioSettings();
+ initAudioClock();
+ audioClock = getAudioClock();
+ initPluginChain();
+ pluginChain = getPluginChain();
+ programOptions = newMrsWatsonOptions();
+ inputSource = sampleSourceFactory(NULL);
+
+ if (!programOptionsParseArgs(programOptions, argc, argv)) {
+ printf("Run with '--help' to see possible options\n");
+ printf("Or run with '--help full' to see extended help for all options\n");
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+
+ // These options conflict with standard processing (more or less), so check to see if the user wanted one
+ // of these and then exit right away.
+ if (argc == 1) {
+ printf("%s needs at least a plugin, input source, and output source to run.\n\n", PROGRAM_NAME);
+ printMrsWatsonQuickstart(argv[0]);
+ return RETURN_CODE_NOT_RUN;
+ } else if (programOptions->options[OPTION_HELP]->enabled) {
+ printMrsWatsonQuickstart(argv[0]);
+
+ if (charStringIsEmpty(programOptionsGetString(programOptions, OPTION_HELP))) {
+ printf("All options, where <argument> is required and [argument] is optional:\n");
+ programOptionsPrintHelp(programOptions, false, DEFAULT_INDENT_SIZE);
+ } else {
+ if (charStringIsEqualToCString(programOptionsGetString(programOptions, OPTION_HELP), "full", true)) {
+ programOptionsPrintHelp(programOptions, true, DEFAULT_INDENT_SIZE);
+ }
+ // Yeah this is a bit silly, but the performance obviously doesn't matter
+ // here and I don't feel like cluttering up this already huge function
+ // with more variables.
+ else if (programOptionsFind(programOptions, programOptionsGetString(programOptions, OPTION_HELP))) {
+ programOptionPrintHelp(programOptionsFind(programOptions, programOptionsGetString(programOptions, OPTION_HELP)),
+ true, DEFAULT_INDENT_SIZE, 0);
+ } else {
+ printf("Invalid option '%s', try running --help full to see help for all options\n",
+ programOptionsGetString(programOptions, OPTION_HELP)->data);
+ }
+ }
+
+ return RETURN_CODE_NOT_RUN;
+ } else if (programOptions->options[OPTION_VERSION]->enabled) {
+ printVersion();
+ return RETURN_CODE_NOT_RUN;
+ } else if (programOptions->options[OPTION_COLOR_TEST]->enabled) {
+ printTestPattern();
+ return RETURN_CODE_NOT_RUN;
+ }
+ // See if we are to make an error report and make necessary changes to the
+ // options for good diagnostics. Note that error reports cannot be generated
+ // for any of the above options which return with RETURN_CODE_NOT_RUN.
+ else if (programOptions->options[OPTION_ERROR_REPORT]->enabled) {
+ errorReporterInitialize(errorReporter);
+ programOptions->options[OPTION_VERBOSE]->enabled = true;
+ programOptions->options[OPTION_LOG_FILE]->enabled = true;
+ programOptions->options[OPTION_DISPLAY_INFO]->enabled = true;
+ // Shell script with original command line arguments
+ errorReporterCreateLauncher(errorReporter, argc, argv);
+ // Rewrite some paths before any input or output sources have been opened.
+ _remapFileToErrorReport(errorReporter, programOptions, OPTION_INPUT_SOURCE, true);
+ _remapFileToErrorReport(errorReporter, programOptions, OPTION_OUTPUT_SOURCE, false);
+ _remapFileToErrorReport(errorReporter, programOptions, OPTION_MIDI_SOURCE, true);
+ _remapFileToErrorReport(errorReporter, programOptions, OPTION_LOG_FILE, false);
+ }
+
+ // Read in options from a configuration file, if given
+ if (programOptions->options[OPTION_CONFIG_FILE]->enabled) {
+ if (!programOptionsParseConfigFile(programOptions, programOptionsGetString(programOptions, OPTION_CONFIG_FILE))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ }
+
+ // Parse these options first so that log messages displayed in the below
+ // loop are properly displayed
+ if (programOptions->options[OPTION_VERBOSE]->enabled) {
+ setLogLevel(LOG_DEBUG);
+ } else if (programOptions->options[OPTION_QUIET]->enabled) {
+ setLogLevel(LOG_ERROR);
+ } else if (programOptions->options[OPTION_LOG_LEVEL]->enabled) {
+ setLogLevelFromString(programOptionsGetString(programOptions, OPTION_LOG_LEVEL));
+ }
+
+ if (programOptions->options[OPTION_COLOR_LOGGING]->enabled) {
+ // If --color was given but with no string argument, then force color. Otherwise
+ // colors will be provided automatically anyways.
+ if (charStringIsEmpty(programOptionsGetString(programOptions, OPTION_COLOR_LOGGING))) {
+ programOptionsSetCString(programOptions, OPTION_COLOR_LOGGING, "force");
+ }
+
+ setLoggingColorEnabledWithString(programOptionsGetString(programOptions, OPTION_COLOR_LOGGING));
+ }
+
+ if (programOptions->options[OPTION_LOG_FILE]->enabled) {
+ setLogFile(programOptionsGetString(programOptions, OPTION_LOG_FILE));
+ }
+
+ // Parse other options and set up necessary objects
+ for (i = 0; i < programOptions->numOptions; i++) {
+ option = programOptions->options[i];
+
+ if (option->enabled) {
+ switch (option->index) {
+ case OPTION_BLOCKSIZE:
+ if (!setBlocksize((const SampleCount)programOptionsGetNumber(programOptions, OPTION_BLOCKSIZE))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ break;
+
+ case OPTION_CHANNELS:
+ if (!setNumChannels((const ChannelCount)programOptionsGetNumber(programOptions, OPTION_CHANNELS))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ break;
+
+ case OPTION_DISPLAY_INFO:
+ shouldDisplayPluginInfo = true;
+ break;
+
+ case OPTION_INPUT_SOURCE:
+ freeSampleSource(inputSource);
+ inputSource = sampleSourceFactory(programOptionsGetString(programOptions, OPTION_INPUT_SOURCE));
+ break;
+
+ case OPTION_MAX_TIME:
+ maxTimeInMs = (const unsigned long)programOptionsGetNumber(programOptions, OPTION_MAX_TIME);
+ break;
+
+ case OPTION_MIDI_SOURCE:
+ midiSource = newMidiSource(guessMidiSourceType(programOptionsGetString(
+ programOptions, OPTION_MIDI_SOURCE)),
+ programOptionsGetString(programOptions, OPTION_MIDI_SOURCE));
+ break;
+
+ case OPTION_OUTPUT_SOURCE:
+ outputSource = sampleSourceFactory(programOptionsGetString(programOptions, OPTION_OUTPUT_SOURCE));
+ break;
+
+ case OPTION_PLUGIN_ROOT:
+ charStringCopy(pluginSearchRoot, programOptionsGetString(programOptions, OPTION_PLUGIN_ROOT));
+ break;
+
+ case OPTION_REALTIME:
+ pluginChainSetRealtime(pluginChain, true);
+ break;
+
+ case OPTION_SAMPLE_RATE:
+ if (!setSampleRate(programOptionsGetNumber(programOptions, OPTION_SAMPLE_RATE))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ break;
+
+ case OPTION_TEMPO:
+ if (!setTempo(programOptionsGetNumber(programOptions, OPTION_TEMPO))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ break;
+
+ case OPTION_TIME_SIGNATURE:
+ if (!setTimeSignatureFromString(programOptionsGetString(programOptions, OPTION_TIME_SIGNATURE))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+
+ break;
+
+ case OPTION_ZEBRA_SIZE:
+ setLoggingZebraSize((const unsigned long)programOptionsGetNumber(programOptions, OPTION_ZEBRA_SIZE));
+ break;
+
+ default:
+ // Ignore -- no special handling needs to be performed here
+ break;
+ }
+ }
+ }
+
+ if (programOptions->options[OPTION_LIST_PLUGINS]->enabled) {
+ listAvailablePlugins(pluginSearchRoot);
+ return RETURN_CODE_NOT_RUN;
+ }
+
+ if (programOptions->options[OPTION_LIST_FILE_TYPES]->enabled) {
+ sampleSourcePrintSupportedTypes();
+ return RETURN_CODE_NOT_RUN;
+ }
+
+ printWelcomeMessage(argc, argv);
+
+ if ((result = setupInputSource(inputSource)) != RETURN_CODE_SUCCESS) {
+ logError("Input source could not be opened, exiting");
+ return result;
+ }
+
+ if ((result = buildPluginChain(pluginChain, programOptionsGetString(programOptions, OPTION_PLUGIN),
+ pluginSearchRoot)) != RETURN_CODE_SUCCESS) {
+ logError("Plugin chain could not be constructed, exiting");
+ return result;
+ }
+
+ if (midiSource != NULL) {
+ result = setupMidiSource(midiSource, &midiSequence);
+
+ if (result != RETURN_CODE_SUCCESS) {
+ logError("MIDI source could not be opened, exiting");
+ return result;
+ }
+ }
+
+ // Copy plugins before they have been opened
+ if (programOptions->options[OPTION_ERROR_REPORT]->enabled) {
+ if (errorReporterShouldCopyPlugins()) {
+ if (!errorReporterCopyPlugins(errorReporter, pluginChain)) {
+ logWarn("Failed copying plugins to error report directory");
+ }
+ }
+ }
+
+ // Initialize the plugin chain after the global sample rate has been set
+ result = pluginChainInitialize(pluginChain);
+
+ if (result != RETURN_CODE_SUCCESS) {
+ logError("Could not initialize plugin chain");
+ return result;
+ }
+
+ // Display info for plugins in the chain before checking for valid input/output sources
+ if (shouldDisplayPluginInfo) {
+ pluginChainInspect(pluginChain);
+ }
+
+ // Execute any parameter changes
+ if (programOptions->options[OPTION_PARAMETER]->enabled) {
+ if (!pluginChainSetParameters(pluginChain, programOptionsGetList(programOptions, OPTION_PARAMETER))) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ }
+
+ // Setup output source here. Having an invalid output source should not cause the program
+ // to exit if the user only wants to list plugins or query info about a chain.
+ if ((result = setupOutputSource(outputSource)) != RETURN_CODE_SUCCESS) {
+ logError("Output source could not be opened, exiting");
+ return result;
+ }
+
+ // Verify input/output sources. This must be done after the plugin chain is initialized
+ // otherwise the head plugin type is not known, which influences whether we must abort
+ // processing.
+ if (programOptions->options[OPTION_ERROR_REPORT]->enabled) {
+ if (charStringIsEqualToCString(inputSource->sourceName, "-", false) ||
+ charStringIsEqualToCString(outputSource->sourceName, "-", false)) {
+ printf("ERROR: Using stdin/stdout is incompatible with --error-report\n");
+ return RETURN_CODE_NOT_RUN;
+ }
+
+ if (midiSource != NULL && charStringIsEqualToCString(midiSource->sourceName, "-", false)) {
+ printf("ERROR: MIDI source from stdin is incompatible with --error-report\n");
+ return RETURN_CODE_NOT_RUN;
+ }
+ }
+
+ if (outputSource == NULL) {
+ logInternalError("Default output sample source was null");
+ return RETURN_CODE_INTERNAL_ERROR;
+ }
+
+ if (inputSource == NULL || inputSource->sampleSourceType == SAMPLE_SOURCE_TYPE_SILENCE) {
+ // If the first plugin in the chain is an instrument, use the silent source as our input and
+ // make sure that there is a corresponding MIDI file
+ headPlugin = pluginChain->plugins[0];
+
+ if (headPlugin->pluginType == PLUGIN_TYPE_INSTRUMENT) {
+ if (midiSource == NULL) {
+ // I guess some instruments (like white noise generators etc.) don't necessarily
+ // need MIDI, actually this is most useful for our internal plugins and generators.
+ // Anyways, this should only be a soft warning for those who know what they're doing.
+ logWarn("Plugin chain contains an instrument, but no MIDI source was supplied");
+
+ if (maxTimeInMs == 0) {
+ // However, if --max-time wasn't given, then there is effectively no input source
+ // and thus processing would continue forever. That won't work.
+ logError("No valid input source or maximum time, don't know when to stop processing");
+ return RETURN_CODE_MISSING_REQUIRED_OPTION;
+ } else {
+ // If maximum time was given and there is no other input source, then use silence
+ inputSource = sampleSourceFactory(NULL);
+ }
+ }
+ } else {
+ logError("Plugin chain contains only effects, but no input source was supplied");
+ return RETURN_CODE_MISSING_REQUIRED_OPTION;
+ }
+ }
+
+ inputSampleBuffer = newSampleBuffer(getNumChannels(), getBlocksize());
+ inputTimer = newTaskTimerWithCString(PROGRAM_NAME, "Input Source");
+ outputSampleBuffer = newSampleBuffer(getNumChannels(), getBlocksize());
+ outputTimer = newTaskTimerWithCString(PROGRAM_NAME, "Output Source");
+
+ // Initialization is finished, we should be able to free this memory now
+ freeProgramOptions(programOptions);
+
+ // If a maximum time was given, figure it out here
+ if (maxTimeInMs > 0) {
+ maxTimeInFrames = (unsigned long)(maxTimeInMs * getSampleRate()) / 1000l;
+ }
+
+ processingDelayInFrames = pluginChainGetProcessingDelay(pluginChain);
+ pluginChainPrepareForProcessing(pluginChain);
+
+ // Update sample rate on the event logger
+ setLoggingZebraSize((const unsigned long)getSampleRate());
+ logInfo("Starting processing input source");
+ logDebug("Sample rate: %.0f", getSampleRate());
+ logDebug("Blocksize: %d", getBlocksize());
+ logDebug("Channels: %d", getNumChannels());
+ logDebug("Tempo: %.2f", getTempo());
+ logDebug("Processing delay frames: %lu", processingDelayInFrames);
+ logDebug("Time signature: %d/%d", getTimeSignatureBeatsPerMeasure(), getTimeSignatureNoteValue());
+ taskTimerStop(initTimer);
+
+ silentSampleOutput = sampleSourceFactory(NULL);
+
+ // Main processing loop
+ while (!finishedReading) {
+ taskTimerStart(inputTimer);
+ finishedReading = (boolByte)!readInput(inputSource, inputSampleBuffer);
+
+ // TODO: For streaming MIDI, we would need to read in events from source here
+ if (midiSequence != NULL) {
+ LinkedList midiEventsForBlock = newLinkedList();
+ // MIDI source overrides the value set to finishedReading by the input source
+ finishedReading = (boolByte)!fillMidiEventsFromRange(midiSequence, audioClock->currentFrame, getBlocksize(), midiEventsForBlock);
+ linkedListForeach(midiEventsForBlock, _processMidiMetaEvent, &finishedReading);
+ // here is midi call
+ pluginChainProcessMidi(pluginChain, midiEventsForBlock);
+ freeLinkedList(midiEventsForBlock);
+ }
+
+ taskTimerStop(inputTimer);
+
+ if (maxTimeInFrames > 0 && audioClock->currentFrame >= maxTimeInFrames) {
+ logInfo("Maximum time reached, stopping processing after this block");
+ finishedReading = true;
+ }
+// and here is audio call, no difference on input so I should uncomment, right? yes
+ pluginChainProcessAudio(pluginChain, inputSampleBuffer, outputSampleBuffer);
+
+ taskTimerStart(outputTimer);
+
+ if (finishedReading) {
+ outputSampleBuffer->blocksize = inputSampleBuffer->blocksize;//The input buffer size has been adjusted.
+ logDebug("Using buffer size of %d for final block", outputSampleBuffer->blocksize);
+ }
+// here it writes audio, but not midi, and looks like vst can only output midi after processing midi hmm? the omnisphere is a synth though? it doesn't output midi,
+// does it? not sure, but it's not implemented in mrswatson...well the weird thing was that I was able to process other synths, just this one came up with a
+// negative tempo. I can put that line back in and load a preset for omnisphere, or is there something else here you think might be messing things up?
+// well it might be waiting for midi events first and then wait for audio processing call, so it might work actually, but not sure...without this line or with it? with it
+
+ //so here it write down all samples, but it's generic code, there should be wav specific code for it:
+ writeOutput(outputSource, silentSampleOutput, outputSampleBuffer, processingDelayInFrames);
+ taskTimerStop(outputTimer);
+ advanceAudioClock(audioClock, outputSampleBuffer->blocksize);
+ }
+
+ // Close file handles for input/output sources
+ silentSampleOutput->closeSampleSource(silentSampleOutput);
+ inputSource->closeSampleSource(inputSource);
+ outputSource->closeSampleSource(outputSource);
+
+ // Print out statistics about each plugin's time usage
+ // TODO: On windows, the total processing time is stored in clocks and not milliseconds
+ // These values must be converted using the QueryPerformanceFrequency() function
+ audioClockStop(audioClock);
+ taskTimerStop(totalTimer);
+
+ if (totalTimer->totalTaskTime > 0) {
+ taskTimerList = newLinkedList();
+ linkedListAppend(taskTimerList, initTimer);
+ linkedListAppend(taskTimerList, inputTimer);
+ linkedListAppend(taskTimerList, outputTimer);
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ linkedListAppend(taskTimerList, pluginChain->audioTimers[i]);
+ linkedListAppend(taskTimerList, pluginChain->midiTimers[i]);
+ }
+
+ totalTimeString = taskTimerHumanReadbleString(totalTimer);
+ logInfo("Total processing time %s, approximate breakdown:", totalTimeString->data);
+ linkedListForeach(taskTimerList, _printTaskTime, totalTimer);
+ } else {
+ // Woo-hoo!
+ logInfo("Total processing time <1ms. Either something went wrong, or your computer is smokin' fast!");
+ }
+
+ freeTaskTimer(initTimer);
+ freeTaskTimer(inputTimer);
+ freeTaskTimer(outputTimer);
+ freeTaskTimer(totalTimer);
+ freeLinkedList(taskTimerList);
+ freeCharString(totalTimeString);
+
+ if (midiSequence != NULL) {
+ logInfo("Read %ld MIDI events from %s",
+ midiSequence->numMidiEventsProcessed,
+ midiSource->sourceName->data);
+ } else {
+ logInfo("Read %ld frames from %s",
+ inputSource->numSamplesProcessed / getNumChannels(),
+ inputSource->sourceName->data);
+ }
+
+ logInfo("Wrote %ld frames to %s",
+ outputSource->numSamplesProcessed / getNumChannels(),
+ outputSource->sourceName->data);
+
+ // Shut down and free data (will also close open files, plugins, etc)
+ logInfo("Shutting down");
+ freeSampleSource(inputSource);
+ freeSampleSource(outputSource);
+ freeSampleBuffer(inputSampleBuffer);
+ freeSampleBuffer(outputSampleBuffer);
+ pluginChainShutdown(pluginChain);
+ freePluginChain(pluginChain);
+
+ if (midiSource != NULL) {
+ freeMidiSource(midiSource);
+ }
+
+ if (midiSequence != NULL) {
+ freeMidiSequence(midiSequence);
+ }
+
+ freeAudioSettings();
+ logInfo("Goodbye!");
+ freeEventLogger();
+ freeAudioClock(getAudioClock());
+
+ if (errorReporter->started) {
+ errorReporterClose(errorReporter);
+ }
+
+ freeErrorReporter(errorReporter);
+
+ return RETURN_CODE_SUCCESS;
+}
diff --git a/source/MrsWatson.h b/source/MrsWatson.h
new file mode 100644
index 0000000..c52dac5
--- /dev/null
+++ b/source/MrsWatson.h
@@ -0,0 +1,37 @@
+//
+// MrsWatson.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_MrsWatson_h
+#define MrsWatson_MrsWatson_h
+
+#include "app/ReturnCodes.h"
+#include "base/CharString.h"
+#include "logging/ErrorReporter.h"
+
+int mrsWatsonMain(ErrorReporter errorReporter, int argc, char *argv[]);
+
+#endif
diff --git a/source/MrsWatsonOptions.c b/source/MrsWatsonOptions.c
new file mode 100644
index 0000000..0bfb697
--- /dev/null
+++ b/source/MrsWatsonOptions.c
@@ -0,0 +1,324 @@
+//
+// MrsWatsonOptions.c - MrsWatson
+// Created by Nik Reiman on 10/23/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+
+#include "audio/AudioSettings.h"
+#include "base/File.h"
+
+#include "MrsWatsonOptions.h"
+
+ProgramOptions newMrsWatsonOptions(void)
+{
+ ProgramOptions options = newProgramOptions(NUM_OPTIONS);
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_BLOCKSIZE,
+ "blocksize",
+ "Blocksize in frames to use for processing. If input source is not an even \
+multiple of the blocksize, then empty frames will be added to the last block.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeNumber,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetNumber(options, OPTION_BLOCKSIZE, (const float)getBlocksize());
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_CHANNELS,
+ "channels",
+ "Number of channels for output source. If the input source specifies a channel \
+count, then that value will override the one set by this option.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeNumber,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetNumber(options, OPTION_CHANNELS, (const float)getNumChannels());
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_COLOR_LOGGING,
+ "color",
+ "Colored logging output. Argument can be 'auto', 'force', or 'none'. If no \
+argument given, 'force' is assumed. If attached to a terminal device, color is \
+used automatically unless 'none' is given to this option.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeOptional));
+ programOptionsSetCString(options, OPTION_COLOR_LOGGING, "auto");
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_COLOR_TEST,
+ "color-test",
+ "Run a test of all color output combinations.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+ options->options[OPTION_COLOR_TEST]->hideInHelp = true;
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_CONFIG_FILE,
+ "config-file",
+ "Load options from a configuration file. The file will be read *after* other \
+options have been parsed, so any options given on the command line will be overriden \
+by those from the file. The file should be plain text, and one argument per line, \
+like so:\n\n\
+\t--plugin-root\n\
+\t/path/to/my/plugins\n\
+\t--verbose",
+ NO_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_DISPLAY_INFO,
+ "display-info",
+ "Print information about each plugin in the chain.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_ERROR_REPORT,
+ "error-report",
+ "Generate an error report zipfile on the desktop.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_LIST_FILE_TYPES,
+ "list-file-types",
+ "Print a list of supported file types for input/output sources.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_HELP,
+ "help",
+ "With no arguments, prints a summary of options and their default settings. \
+Otherwise, extended help can be printed for an individual option given by \
+[argument], or use 'full' to print extended help for all options.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeOptional));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_INPUT_SOURCE,
+ "input",
+ "Input source to use for processing, where the file type is determined from \
+the extension. Run with --list-file-types to see a list of supported types. Use \
+'-' to read from stdin.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_LIST_PLUGINS,
+ "list-plugins",
+ "List available plugins. Useful for determining if a plugin can be 'seen'.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_LOG_FILE,
+ "log-file",
+ "Save logging output to the given file instead of the terminal's standard error.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetCString(options, OPTION_LOG_FILE, "log.txt");
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_LOG_LEVEL,
+ "log-level",
+ "Logging level to use. Options include: debug, info, warn, error. Critical \
+errors are always logged to console regardless of this setting.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetCString(options, OPTION_LOG_LEVEL, "info");
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_MAX_TIME,
+ "max-time",
+ "Force processing to stop after <argument> milliseconds, regardless of the \
+input source length. Mostly useful when using internal plugins as sources.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeNumber,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_MIDI_SOURCE,
+ "midi-file",
+ "MIDI file to read events from. Required if processing an instrument plugin.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_OUTPUT_SOURCE,
+ "output",
+ "Output source to write processed data to, where the file type is determined \
+from the extension. Run with --list-file-types to see a list of supported types. \
+Use '-' to write to stdout..",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeOptional));
+ programOptionsSetCString(options, OPTION_OUTPUT_SOURCE, "out.wav");
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_PARAMETER,
+ "parameter",
+ "Set a parameter in a plugin. May be specified multiple times, but can only \
+set parameters for the first plugin in a chain. Parameter indexes for plugins \
+can be found with the --display-info option. Use comma-separated arguments for \
+index/value, for example:\n\n\
+\t--parameter 1,0.3 --parameter 0,0.75",
+ NO_SHORT_FORM,
+ kProgramOptionTypeList,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_PLUGIN,
+ "plugin",
+ "Plugin(s) to process. Multiple plugins can given in a semicolon-separated \
+list, in which case they will be placed into a chain in the order specified. \
+Note that if you specify multiple plugins, you will have to put the argument \
+in quotes or else the shell may mis-interpret it as multiple commands. \
+Instrument plugins must appear first in any chains. Plugins are searched for in \
+the --plugin-root directory, the current directory, and the standard locations \
+for the OS. File extensions are added automatically to plugin names. Each plugin \
+may be followed by a comma with a program to be loaded, which should be of the \
+corresponding file format for the respective plugin. For shell plugins (like \
+Waves), use --display-info to get a list of sub-plugin ID's and then use a colon \
+to indicate which plugin to load. Examples:\n\n\
+\t--plugin LFX-1310\n\
+\t--plugin 'AutoTune,KayneWest.fxp;Compressor,SoftKnee.fxp;Limiter'\n\
+\t--plugin 'WavesShell-VST' --display-info (list shell sub-plugins)\n\
+\t--plugin 'WavesShell-VST:IDFX' (load a shell plugins)",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_PLUGIN_ROOT,
+ "plugin-root",
+ "Custom non-system directory to use when searching for plugins. Will be searched \
+ before system directories if given.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_QUIET,
+ "quiet",
+ "Only log critical errors.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_REALTIME,
+ "realtime",
+ "Simulate running in realtime by sleeping for any remaining time needed to process \
+the given block. Some plugins which are unable to do offline rendering may require this \
+option in order to function properly.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_SAMPLE_RATE,
+ "sample-rate",
+ "Sample rate to use when processing. If the input source specifies its own \
+sample rate, that value will override the one set by this option. No error checking \
+is done for sample rates (other than requiring it to be greater than 0), however \
+using unusual sample rates will probably result in weird behavior from plugins.",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeNumber,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetNumber(options, OPTION_SAMPLE_RATE, (const float)getSampleRate());
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_TEMPO,
+ "tempo",
+ "Tempo to use when processing.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeNumber,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetNumber(options, OPTION_TEMPO, getTempo());
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_TIME_SIGNATURE,
+ "time-signature",
+ "Set the global time signature. Should be a string formatted like \"3/4\".",
+ NO_SHORT_FORM,
+ kProgramOptionTypeString,
+ kProgramOptionArgumentTypeRequired));
+ // This is kind of cheating, because the default time signature could be
+ // anything. Realistically we know it will always be hardcoded to 4/4, so this
+ // hardcoded string is also relatively safe.
+ programOptionsSetCString(options, OPTION_TIME_SIGNATURE, "4/4");
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_VERBOSE,
+ "verbose",
+ "Verbose logging. Logging output is printed in the following form:\n\
+(Level) (Frames processed) (Elapsed time in ms) (Logging message)",
+ HAS_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_VERSION,
+ "version",
+ "Print full program version and copyright information.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeEmpty,
+ kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(options, newProgramOptionWithName(
+ OPTION_ZEBRA_SIZE,
+ "zebra-size",
+ "Alternate logging output colors every <argument> frames.",
+ NO_SHORT_FORM,
+ kProgramOptionTypeNumber,
+ kProgramOptionArgumentTypeRequired));
+ programOptionsSetNumber(options, OPTION_ZEBRA_SIZE, (const float)getSampleRate());
+
+ return options;
+}
+
+void printMrsWatsonQuickstart(const char *argvName)
+{
+ File argvFile = newFileWithPathCString(argvName);
+ const char *programBasename = argvFile->absolutePath->data;
+ printf("Run with '--help full' to see extended help for all options.\n");
+ printf("Quickstart for effects: %s -p <plugin> -i <input file> -o <output>\n", programBasename);
+ printf("Quickstart for instruments: %s -p <name> -m <midi file> -o <output>\n", programBasename);
+ printf("\n");
+ freeFile(argvFile);
+}
diff --git a/source/MrsWatsonOptions.h b/source/MrsWatsonOptions.h
new file mode 100644
index 0000000..5930b31
--- /dev/null
+++ b/source/MrsWatsonOptions.h
@@ -0,0 +1,68 @@
+//
+// MrsWatsonOptions.h - MrsWatson
+// Created by Nik Reiman on 10/23/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_MrsWatsonOptions_h
+#define MrsWatson_MrsWatsonOptions_h
+
+#include "app/ProgramOption.h"
+
+// Runtime options
+typedef enum {
+ OPTION_BLOCKSIZE,
+ OPTION_CHANNELS,
+ OPTION_COLOR_LOGGING,
+ OPTION_COLOR_TEST,
+ OPTION_CONFIG_FILE,
+ OPTION_DISPLAY_INFO,
+ OPTION_ERROR_REPORT,
+ OPTION_HELP,
+ OPTION_INPUT_SOURCE,
+ OPTION_LIST_FILE_TYPES,
+ OPTION_LIST_PLUGINS,
+ OPTION_LOG_FILE,
+ OPTION_LOG_LEVEL,
+ OPTION_MAX_TIME,
+ OPTION_MIDI_SOURCE,
+ OPTION_OUTPUT_SOURCE,
+ OPTION_PARAMETER,
+ OPTION_PLUGIN,
+ OPTION_PLUGIN_ROOT,
+ OPTION_QUIET,
+ OPTION_REALTIME,
+ OPTION_SAMPLE_RATE,
+ OPTION_TEMPO,
+ OPTION_TIME_SIGNATURE,
+ OPTION_VERBOSE,
+ OPTION_VERSION,
+ OPTION_ZEBRA_SIZE,
+ NUM_OPTIONS
+} ProgramOptionIndex;
+
+ProgramOptions newMrsWatsonOptions(void);
+void printMrsWatsonQuickstart(const char *argvName);
+
+#endif
diff --git a/source/app/BuildInfo.c b/source/app/BuildInfo.c
new file mode 100644
index 0000000..d79a31d
--- /dev/null
+++ b/source/app/BuildInfo.c
@@ -0,0 +1,101 @@
+//
+// BuildInfo.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "app/BuildInfo.h"
+#include "logging/EventLogger.h"
+
+unsigned long buildInfoGetYear(void)
+{
+ unsigned long result = 0;
+ CharString buildDate = newCharStringWithCapacity(kCharStringLengthShort);
+ const char *compilerDate = __DATE__;
+ size_t startingIndex = strlen(compilerDate) - 4;
+ strncpy(buildDate->data, compilerDate + startingIndex, 4);
+ result = (unsigned long)strtol(buildDate->data, NULL, 10);
+ freeCharString(buildDate);
+ return result;
+}
+
+static short _getMonthNumber(const char *abbreviatedMonthName)
+{
+ if (!strncmp(abbreviatedMonthName, "Jan", 3)) {
+ return 1;
+ } else if (!strncmp(abbreviatedMonthName, "Feb", 3)) {
+ return 2;
+ } else if (!strncmp(abbreviatedMonthName, "Mar", 3)) {
+ return 3;
+ } else if (!strncmp(abbreviatedMonthName, "Apr", 3)) {
+ return 4;
+ } else if (!strncmp(abbreviatedMonthName, "May", 3)) {
+ return 5;
+ } else if (!strncmp(abbreviatedMonthName, "Jun", 3)) {
+ return 6;
+ } else if (!strncmp(abbreviatedMonthName, "Jul", 3)) {
+ return 7;
+ } else if (!strncmp(abbreviatedMonthName, "Aug", 3)) {
+ return 8;
+ } else if (!strncmp(abbreviatedMonthName, "Sep", 3)) {
+ return 9;
+ } else if (!strncmp(abbreviatedMonthName, "Oct", 3)) {
+ return 10;
+ } else if (!strncmp(abbreviatedMonthName, "Nov", 3)) {
+ return 11;
+ } else if (!strncmp(abbreviatedMonthName, "Dec", 3)) {
+ return 12;
+ } else {
+ logInternalError("Invalid build month '%s'", abbreviatedMonthName);
+ return 0;
+ }
+}
+
+unsigned long buildInfoGetDatestamp(void)
+{
+ unsigned long result = buildInfoGetYear() * 10000;
+
+ CharString buffer = newCharStringWithCapacity(kCharStringLengthShort);
+ strncpy(buffer->data, __DATE__, 3);
+ result += _getMonthNumber(buffer->data) * 100;
+
+ charStringClear(buffer);
+ strncpy(buffer->data, __DATE__ + 4, 2);
+ result += strtol(buffer->data, NULL, 10);
+
+ freeCharString(buffer);
+ return result;
+}
+
+CharString buildInfoGetVersionString(void)
+{
+ CharString result = newCharStringWithCapacity(kCharStringLengthShort);
+ snprintf(result->data, result->capacity, "%s version %d.%d.%d",
+ PROGRAM_NAME, VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
+ return result;
+}
diff --git a/source/app/BuildInfo.h b/source/app/BuildInfo.h
new file mode 100644
index 0000000..c2977ef
--- /dev/null
+++ b/source/app/BuildInfo.h
@@ -0,0 +1,78 @@
+//
+// BuildInfo.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_BuildInfo_h
+#define MrsWatson_BuildInfo_h
+
+#include "base/CharString.h"
+
+#define PROGRAM_NAME "MrsWatson"
+#define VENDOR_NAME "Teragon Audio"
+
+#define VERSION_MAJOR 0
+#define VERSION_MINOR 9
+#define VERSION_PATCH 7
+
+#define PROJECT_WEBSITE "https://github.com/teragonaudio/mrswatson"
+#define SUPPORT_WEBSITE "https://github.com/teragonaudio/mrswatson/issues"
+#define SUPPORT_EMAIL "support@teragonaudio.com"
+
+#define LICENSE_STRING "Redistribution and use in source and binary forms, with or without " \
+ "modification, are permitted provided that the following conditions are met:\n\n" \
+ "* Redistributions of source code must retain the above copyright notice, this list of " \
+ "conditions and the following disclaimer.\n" \
+ "* 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.\n\n" \
+ "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 HOLDER 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."
+
+/**
+ * @return Year the application was built in
+ */
+unsigned long buildInfoGetYear(void);
+
+/**
+ * @return Build timestamp in the form YYYYMMDD
+ */
+unsigned long buildInfoGetDatestamp(void);
+
+/**
+ * Get the full application name and version
+ * @return String with the application's name and version. The caller must free
+ * this memory when finished with it.
+ */
+CharString buildInfoGetVersionString(void);
+
+#endif
diff --git a/source/app/ProgramOption.c b/source/app/ProgramOption.c
new file mode 100644
index 0000000..075b033
--- /dev/null
+++ b/source/app/ProgramOption.c
@@ -0,0 +1,534 @@
+//
+// ProgramOption.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "app/ProgramOption.h"
+#include "base/File.h"
+#include "logging/EventLogger.h"
+
+CharString _programOptionGetString(const ProgramOption self);
+float _programOptionGetNumber(const ProgramOption self);
+
+ProgramOption newProgramOption(void)
+{
+ return newProgramOptionWithName(-1, EMPTY_STRING, EMPTY_STRING, false,
+ kProgramOptionTypeNumber, kProgramOptionArgumentTypeInvalid);
+}
+
+ProgramOption newProgramOptionWithName(const int optionIndex, const char *name,
+ const char *help, boolByte hasShortForm, ProgramOptionType type,
+ ProgramOptionArgumentType argumentType)
+{
+ ProgramOption option = (ProgramOption)malloc(sizeof(ProgramOptionMembers));
+
+ option->index = (unsigned int)optionIndex;
+ option->name = newCharStringWithCString(name);
+ option->help = newCharStringWithCString(help);
+ option->hasShortForm = hasShortForm;
+ option->hideInHelp = false;
+
+ option->type = type;
+
+ switch (type) {
+ case kProgramOptionTypeEmpty:
+ // Nothing needed here
+ break;
+
+ case kProgramOptionTypeString:
+ option->_data.string = newCharString();
+ break;
+
+ case kProgramOptionTypeNumber:
+ option->_data.number = 0.0f;
+ break;
+
+ case kProgramOptionTypeList:
+ option->_data.list = newLinkedList();
+ break;
+
+ default:
+ logInternalError("ProgramOption with invalid type");
+ break;
+ }
+
+ option->argumentType = argumentType;
+ option->enabled = false;
+
+ return option;
+}
+
+void _programOptionPrintDefaultValue(const ProgramOption self)
+{
+ CharString stringValue;
+
+ switch (self->type) {
+ case kProgramOptionTypeString:
+ stringValue = _programOptionGetString(self);
+
+ if (stringValue != NULL && !charStringIsEmpty(stringValue)) {
+ printf(", default value '%s'", stringValue->data);
+ }
+
+ break;
+
+ case kProgramOptionTypeNumber:
+ printf(", default value: %.0f", _programOptionGetNumber(self));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void programOptionPrintHelp(const ProgramOption self, boolByte withFullHelp, int indentSize, int initialIndent)
+{
+ CharString wrappedHelpString;
+ int i;
+
+ if (self == NULL) {
+ logError("Can't find help for that option. Try running with --help to see all options\n");
+ return;
+ }
+
+ // Initial argument indent
+ for (i = 0; i < initialIndent; i ++) {
+ printf(" ");
+ }
+
+ // All arguments have a long form, so that will always be printed
+ printf("--%s", self->name->data);
+
+ if (self->hasShortForm) {
+ printf(" (or -%c)", self->name->data[0]);
+ }
+
+ switch (self->argumentType) {
+ case kProgramOptionArgumentTypeRequired:
+ printf(" <argument>");
+ break;
+
+ case kProgramOptionArgumentTypeOptional:
+ printf(" [argument]");
+ break;
+
+ case kProgramOptionArgumentTypeNone:
+ default:
+ break;
+ }
+
+ _programOptionPrintDefaultValue(self);
+
+ if (withFullHelp) {
+ // Newline and indentation before help
+ wrappedHelpString = charStringWrap(self->help, (unsigned int)(initialIndent + indentSize));
+ printf("\n%s\n\n", wrappedHelpString->data);
+ freeCharString(wrappedHelpString);
+ } else {
+ printf("\n");
+ }
+}
+
+CharString _programOptionGetString(const ProgramOption self)
+{
+ return self->type == kProgramOptionTypeString ? self->_data.string : NULL;
+}
+
+float _programOptionGetNumber(const ProgramOption self)
+{
+ return self->type == kProgramOptionTypeNumber ? self->_data.number : -1.0f;
+}
+
+static LinkedList _programOptionGetList(const ProgramOption self)
+{
+ return self->type == kProgramOptionTypeList ? self->_data.list : NULL;
+}
+
+static void _programOptionSetString(ProgramOption self, const CharString value)
+{
+ if (self->type == kProgramOptionTypeString) {
+ charStringCopy(self->_data.string, value);
+ }
+}
+
+static void _programOptionSetCString(ProgramOption self, const char *value)
+{
+ CharString valueString = newCharStringWithCString(value);
+ _programOptionSetString(self, valueString);
+ freeCharString(valueString);
+}
+
+static void _programOptionSetNumber(ProgramOption self, const float value)
+{
+ if (self->type == kProgramOptionTypeNumber) {
+ self->_data.number = value;
+ }
+}
+
+static void _programOptionSetListItem(ProgramOption self, void *value)
+{
+ if (self->type == kProgramOptionTypeList) {
+ linkedListAppend(self->_data.list, value);
+ }
+}
+
+static void _programOptionSetData(ProgramOption self, const char *data)
+{
+ if (data == NULL) {
+ return;
+ }
+
+ switch (self->type) {
+ case kProgramOptionTypeString:
+ _programOptionSetCString(self, data);
+ break;
+
+ case kProgramOptionTypeNumber:
+ // Windows doesn't do strtof :(
+ _programOptionSetNumber(self, (float)strtod(data, NULL));
+ break;
+
+ case kProgramOptionTypeList:
+ _programOptionSetListItem(self, (void *)data);
+ break;
+
+ default:
+ logInternalError("Set ProgramOption with invalid type");
+ break;
+ }
+}
+
+void freeProgramOption(ProgramOption self)
+{
+ if (self != NULL) {
+ freeCharString(self->name);
+ freeCharString(self->help);
+
+ switch (self->type) {
+ case kProgramOptionTypeString:
+ freeCharString(self->_data.string);
+ break;
+
+ case kProgramOptionTypeList:
+ // Note: This will not actually free the associated strings for this
+ // option. This is ok if the list items are parsed from argv/argc, but
+ // otherwise memory could be leaked here.
+ freeLinkedList(self->_data.list);
+ break;
+
+ default:
+ break;
+ }
+
+ free(self);
+ }
+}
+
+
+ProgramOptions newProgramOptions(int numOptions)
+{
+ ProgramOptions options = (ProgramOptions)malloc(sizeof(ProgramOptionsMembers));
+ options->numOptions = (unsigned int)numOptions;
+ options->options = (ProgramOption *)malloc(sizeof(ProgramOption) * numOptions);
+ memset(options->options, 0, sizeof(ProgramOption) * numOptions);
+ return options;
+}
+
+boolByte programOptionsAdd(const ProgramOptions self, const ProgramOption option)
+{
+ if (option != NULL && option->index < self->numOptions) {
+ self->options[option->index] = option;
+ return true;
+ }
+
+ return false;
+}
+
+static boolByte _isStringShortOption(const char *testString)
+{
+ return (boolByte)(testString != NULL && strlen(testString) == 2 && testString[0] == '-');
+}
+
+static boolByte _isStringLongOption(const char *testString)
+{
+ return (boolByte)(testString != NULL && strlen(testString) > 2 && testString[0] == '-' && testString[1] == '-');
+}
+
+static ProgramOption _findProgramOption(ProgramOptions self, const char *name)
+{
+ ProgramOption potentialMatchOption, optionMatch;
+ CharString optionStringWithoutDashes;
+ unsigned int i;
+
+ if (_isStringShortOption(name)) {
+ for (i = 0; i < self->numOptions; i++) {
+ potentialMatchOption = self->options[i];
+
+ if (potentialMatchOption->hasShortForm && potentialMatchOption->name->data[0] == name[1]) {
+ return potentialMatchOption;
+ }
+ }
+ }
+
+ if (_isStringLongOption(name)) {
+ optionMatch = NULL;
+ optionStringWithoutDashes = newCharStringWithCapacity(kCharStringLengthShort);
+ strncpy(optionStringWithoutDashes->data, name + 2, strlen(name) - 2);
+
+ for (i = 0; i < self->numOptions; i++) {
+ potentialMatchOption = self->options[i];
+
+ if (charStringIsEqualTo(potentialMatchOption->name, optionStringWithoutDashes, false)) {
+ optionMatch = potentialMatchOption;
+ break;
+ }
+ }
+
+ freeCharString(optionStringWithoutDashes);
+ return optionMatch;
+ }
+
+ // If no option was found, then return null
+ return NULL;
+}
+
+static boolByte _fillOptionArgument(ProgramOption self, int *currentArgc, int argc, char **argv)
+{
+ if (self->argumentType == kProgramOptionArgumentTypeNone) {
+ return true;
+ } else if (self->argumentType == kProgramOptionArgumentTypeOptional) {
+ int potentialNextArgc = *currentArgc + 1;
+
+ if (potentialNextArgc >= argc) {
+ return true;
+ } else {
+ char *potentialNextArg = argv[potentialNextArgc];
+
+ // If the next string in the sequence is NOT an argument, we assume it is the optional argument
+ if (!_isStringShortOption(potentialNextArg) && !_isStringLongOption(potentialNextArg)) {
+ _programOptionSetData(self, potentialNextArg);
+ (*currentArgc)++;
+ return true;
+ } else {
+ // Otherwise, it is another option, but that's ok
+ return true;
+ }
+ }
+ } else if (self->argumentType == kProgramOptionArgumentTypeRequired) {
+ int nextArgc = *currentArgc + 1;
+
+ if (nextArgc >= argc) {
+ logCritical("Option '%s' requires an argument, but none was given", self->name->data);
+ return false;
+ } else {
+ char *nextArg = argv[nextArgc];
+
+ if (_isStringShortOption(nextArg) || _isStringLongOption(nextArg)) {
+ logCritical("Option '%s' requires an argument, but '%s' is not valid", self->name->data, nextArg);
+ return false;
+ } else {
+ _programOptionSetData(self, nextArg);
+ (*currentArgc)++;
+ return true;
+ }
+ }
+ } else {
+ logInternalError("Unknown argument type '%d'", self->argumentType);
+ return false;
+ }
+}
+
+boolByte programOptionsParseArgs(ProgramOptions self, int argc, char **argv)
+{
+ int argumentIndex;
+
+ for (argumentIndex = 1; argumentIndex < argc; argumentIndex++) {
+ const ProgramOption option = _findProgramOption(self, argv[argumentIndex]);
+
+ if (option == NULL) {
+ logCritical("Invalid option '%s'", argv[argumentIndex]);
+ return false;
+ } else {
+ if (_fillOptionArgument(option, &argumentIndex, argc, argv)) {
+ option->enabled = true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ // If we make it to here, return true
+ return true;
+}
+
+boolByte programOptionsParseConfigFile(ProgramOptions self, const CharString filename)
+{
+ boolByte result = false;
+ File configFile = NULL;
+ LinkedList configFileLines = NULL;
+ CharString *argvCharStrings;
+ int argc;
+ char **argv;
+ int i;
+
+ if (filename == NULL || charStringIsEmpty(filename)) {
+ logCritical("Cannot read options from empty filename");
+ return false;
+ }
+
+ configFile = newFileWithPath(filename);
+
+ if (configFile == NULL || configFile->fileType != kFileTypeFile) {
+ logCritical("Cannot read options from non-existent file '%s'", filename->data);
+ freeFile(configFile);
+ return false;
+ }
+
+ configFileLines = fileReadLines(configFile);
+
+ if (configFileLines == NULL) {
+ logInternalError("Could not split config file lines");
+ return false;
+ } else if (linkedListLength(configFileLines) == 0) {
+ logInfo("Config file '%s' is empty", filename->data);
+ freeLinkedList(configFileLines);
+ freeFile(configFile);
+ return true;
+ } else {
+ // Don't need the file anymore, it can be freed here
+ freeFile(configFile);
+ }
+
+ argvCharStrings = (CharString *)linkedListToArray(configFileLines);
+ argc = linkedListLength(configFileLines);
+ argv = (char **)malloc(sizeof(char *) * (argc + 1));
+ // Normally this would be the application name, don't care about it here
+ argv[0] = NULL;
+
+ for (i = 0; i < argc; i++) {
+ argv[i + 1] = argvCharStrings[i]->data;
+ }
+
+ argc++;
+ result = programOptionsParseArgs(self, argc, argv);
+
+ freeLinkedListAndItems(configFileLines, (LinkedListFreeItemFunc)freeCharString);
+ free(argvCharStrings);
+ free(argv);
+ return result;
+}
+
+void programOptionsPrintHelp(const ProgramOptions self, boolByte withFullHelp, int indentSize)
+{
+ unsigned int i;
+
+ for (i = 0; i < self->numOptions; i++) {
+ if (!self->options[i]->hideInHelp) {
+ programOptionPrintHelp(self->options[i], withFullHelp, indentSize, indentSize);
+ }
+ }
+}
+
+ProgramOption programOptionsFind(const ProgramOptions self, const CharString string)
+{
+ unsigned int i;
+
+ for (i = 0; i < self->numOptions; i++) {
+ if (charStringIsEqualTo(string, self->options[i]->name, true)) {
+ return self->options[i];
+ }
+ }
+
+ return NULL;
+}
+
+void programOptionsPrintHelpForOption(const ProgramOptions self, const CharString string,
+ boolByte withFullHelp, int indentSize)
+{
+ programOptionPrintHelp(programOptionsFind(self, string), withFullHelp, indentSize, 0);
+}
+
+const CharString programOptionsGetString(const ProgramOptions self, const unsigned int index)
+{
+ return index < self->numOptions ? _programOptionGetString(self->options[index]) : NULL;
+}
+
+float programOptionsGetNumber(const ProgramOptions self, const unsigned int index)
+{
+ return index < self->numOptions ? _programOptionGetNumber(self->options[index]) : -1.0f;
+}
+
+const LinkedList programOptionsGetList(const ProgramOptions self, const unsigned int index)
+{
+ return index < self->numOptions ? _programOptionGetList(self->options[index]) : NULL;
+}
+
+void programOptionsSetCString(ProgramOptions self, const unsigned int index, const char *value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetCString(self->options[index], value);
+ }
+}
+
+void programOptionsSetString(ProgramOptions self, const unsigned int index, const CharString value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetString(self->options[index], value);
+ }
+}
+
+void programOptionsSetNumber(ProgramOptions self, const unsigned int index, const float value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetNumber(self->options[index], value);
+ }
+}
+
+void programOptionsSetListItem(ProgramOptions self, const unsigned int index, void *value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetListItem(self->options[index], value);
+ }
+}
+
+void freeProgramOptions(ProgramOptions self)
+{
+ unsigned int i;
+
+ if (self == NULL) {
+ return;
+ }
+
+ for (i = 0; i < self->numOptions; i++) {
+ freeProgramOption(self->options[i]);
+ }
+
+ free(self->options);
+ free(self);
+}
diff --git a/source/app/ProgramOption.h b/source/app/ProgramOption.h
new file mode 100644
index 0000000..c331b53
--- /dev/null
+++ b/source/app/ProgramOption.h
@@ -0,0 +1,246 @@
+//
+// ProgramOption.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_ProgramOption_h
+#define MrsWatson_ProgramOption_h
+
+#include "base/CharString.h"
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+#define NO_SHORT_FORM false
+#define HAS_SHORT_FORM true
+
+typedef enum {
+ kProgramOptionTypeEmpty,
+ kProgramOptionTypeString,
+ kProgramOptionTypeNumber,
+ kProgramOptionTypeList,
+ kProgramOptionTypeNumTypes
+} ProgramOptionType;
+
+typedef enum {
+ kProgramOptionArgumentTypeNone,
+ kProgramOptionArgumentTypeOptional,
+ kProgramOptionArgumentTypeRequired,
+ kProgramOptionArgumentTypeInvalid
+} ProgramOptionArgumentType;
+
+typedef union {
+ CharString string;
+ float number;
+ LinkedList list;
+} ProgramOptionData;
+
+typedef struct {
+ unsigned int index;
+ CharString name;
+ CharString help;
+ boolByte hasShortForm;
+ // For "hidden" options which should not be printed out in the help output
+ boolByte hideInHelp;
+
+ ProgramOptionType type;
+ ProgramOptionData _data;
+ ProgramOptionArgumentType argumentType;
+ boolByte enabled;
+} ProgramOptionMembers;
+typedef ProgramOptionMembers *ProgramOption;
+
+typedef struct {
+ ProgramOption *options;
+ unsigned int numOptions;
+} ProgramOptionsMembers;
+typedef ProgramOptionsMembers *ProgramOptions;
+
+
+/**
+ * Create a new ProgramOption instance
+ * @return An initialized ProgramOption
+ */
+ProgramOption newProgramOption(void);
+
+/**
+ * Create a new ProgramOption instance with some default values
+ * @param opnionIndex Reference index for option (ie, from an enum)
+ * @param name Full option name, hyphenated in the case of multiple words
+ * @param help Full help string
+ * @param hasShortForm True if the option should also be matched with the first letter
+ * @param argumentType Expected argument type which can be passed to this option
+ * @return
+ */
+ProgramOption newProgramOptionWithName(const int optionIndex, const char *name,
+ const char *help, boolByte hasShortForm, ProgramOptionType type,
+ ProgramOptionArgumentType argumentType);
+
+/**
+ * Print out help for the option
+ * @param self
+ * @param withFullHelp Print the entire help or just the argument name and summary
+ * @param indentSize Number of spaces to indent output
+ * @param initialIndent Initial number of spaces to offset output
+ */
+void programOptionPrintHelp(const ProgramOption self, boolByte withFullHelp,
+ int indentSize, int initialIndent);
+
+/**
+ * Free memory used by a ProgramOption instance
+ * @param self
+ */
+void freeProgramOption(ProgramOption self);
+
+
+/**
+ * Create a new ProgramOptions container
+ * @param numOptions Number of options to hold
+ * @return An initialized ProgramOptions
+ */
+ProgramOptions newProgramOptions(int numOptions);
+
+/**
+ * Add a ProgramOption instance to the collection
+ * @param self
+ * @param option Option to add to the collection. Note that this option must have
+ * its index set correctly, as the ProgramOptions options array is statically
+ * allocated to a set size when the object is initialized.
+ * @return True on success, false if option is null or has an invalid index
+ */
+boolByte programOptionsAdd(const ProgramOptions self, const ProgramOption option);
+
+/**
+ * Find a ProgramOption by name
+ * @param self
+ * @param name Name to search for (case insensitive)
+ * @return Matching ProgramOption, NULL otherwise
+ */
+ProgramOption programOptionsFind(const ProgramOptions self, const CharString name);
+
+/**
+ * Parse a command line argument array.
+ * @param self
+ * @param argc Number of arguments (ie, from main(int argc, char** argv)
+ * @param argv Argument array (ie, from main(int argc, char** argv)
+ * @return False if an error occurred during parsing, such as a missing or invalid argument
+ */
+boolByte programOptionsParseArgs(ProgramOptions self, int argc, char **argv);
+
+/**
+ * Parse a configuration file to options. File should be plain text with one
+ * argument per line
+ * @param self
+ * @param filename Filename to parse
+ * @return True if all options were correctly parsed, false if there was an error
+ * either opening the file or with the arguments themselves.
+ */
+boolByte programOptionsParseConfigFile(ProgramOptions self, const CharString filename);
+
+/**
+ * Print out help for all options
+ * @param self
+ * @param withFullHelp Include full help text, or just option summaries
+ * @param indentSize Indent size to use for output
+ */
+void programOptionsPrintHelp(const ProgramOptions self, boolByte withFullHelp, int indentSize);
+
+/**
+ * Find an option and print out its help
+ * @param self
+ * @param string Option name to find
+ * @param withFullHelp True if full help should be shown, otherwise just the
+ * summary string.
+ * @param indentSize Indent size to use for output
+ */
+void programOptionsPrintHelpForOption(const ProgramOptions self, const CharString string,
+ boolByte withFullHelp, int indentSize);
+
+/**
+ * Get string value for an option
+ * @param self
+ * @param index Option index
+ * @return Option value string, or NULL if this option is of a different type
+ */
+const CharString programOptionsGetString(const ProgramOptions self, const unsigned int index);
+
+/**
+ * Get numeric value for an option
+ * @param self
+ * @param index Option index
+ * @return Option value string, or -1 if this option is of a different type
+ */
+float programOptionsGetNumber(const ProgramOptions self, const unsigned int index);
+
+/**
+ * Get linked list values for an option
+ * @param self
+ * @param index Option index
+ * @return Option value string, or NULL if this option is of a different type
+ */
+const LinkedList programOptionsGetList(const ProgramOptions self, const unsigned int index);
+
+/**
+ * Set an option's string value. If setting the wrong type to the option, this
+ * call does nothing.
+ * @param self
+ * @param index Option index
+ * @param value Value to set
+ */
+void programOptionsSetCString(ProgramOptions self, const unsigned int index, const char *value);
+
+/**
+ * Set an option's string value. If setting the wrong type to the option, this
+ * call does nothing.
+ * @param self
+ * @param index Option index
+ * @param value Value to set
+ */
+void programOptionsSetString(ProgramOptions self, const unsigned int index, const CharString value);
+
+/**
+ * Set an option's numeric value. If setting the wrong type to the option, this
+ * call does nothing.
+ * @param self
+ * @param index Option index
+ * @param value Value to set
+ */
+void programOptionsSetNumber(ProgramOptions self, const unsigned int index, const float value);
+
+/**
+ * Add an item to an option's linked list. If this option has the wrong type,
+ * then this call does nothing.
+ * @param self
+ * @param index Option index
+ * @param value Value to add
+ */
+void programOptionsSetListItem(ProgramOptions self, const unsigned int index, void *value);
+
+/**
+ * Free memory used by a ProgramOptions array and all options in the collection
+ * @param self
+ */
+void freeProgramOptions(ProgramOptions self);
+
+#endif
diff --git a/source/app/ReturnCodes.h b/source/app/ReturnCodes.h
new file mode 100644
index 0000000..2ff8708
--- /dev/null
+++ b/source/app/ReturnCodes.h
@@ -0,0 +1,55 @@
+//
+// ReturnCodes.h - MrsWatson
+// Created by Nik Reiman on 10 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_ReturnCodes_h
+#define MrsWatson_ReturnCodes_h
+
+// Exit result codes
+typedef enum {
+ RETURN_CODE_SUCCESS,
+ RETURN_CODE_NOT_RUN,
+ RETURN_CODE_INVALID_ARGUMENT,
+ RETURN_CODE_MISSING_REQUIRED_OPTION,
+ RETURN_CODE_IO_ERROR,
+ RETURN_CODE_PLUGIN_ERROR,
+ RETURN_CODE_INVALID_PLUGIN_CHAIN,
+ RETURN_CODE_UNSUPPORTED_FEATURE,
+ RETURN_CODE_INTERNAL_ERROR,
+ // This return code should always be last in this enum list. It is not
+ // actually used, but instead we add the signal number to it and exit with
+ // that code instead.
+ RETURN_CODE_SIGNAL,
+
+ // Failure codes when forking processes (at present only used by test suite)
+ RETURN_CODE_FORK_FAILED = -1,
+ RETURN_CODE_SHELL_FAILED = 127,
+ RETURN_CODE_LAUNCH_FAILED_OTHER = 255,
+
+ NUM_RETURN_CODES
+} ReturnCodes;
+
+#endif
diff --git a/source/audio/AudioSettings.c b/source/audio/AudioSettings.c
new file mode 100644
index 0000000..7d47ec4
--- /dev/null
+++ b/source/audio/AudioSettings.c
@@ -0,0 +1,242 @@
+//
+// AudioSettings.c - MrsWatson
+// Created by Nik Reiman on 1/4/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "audio/AudioSettings.h"
+#include "logging/EventLogger.h"
+
+AudioSettings audioSettingsInstance = NULL;
+
+void initAudioSettings(void)
+{
+ if (audioSettingsInstance != NULL) {
+ freeAudioSettings();
+ }
+
+ audioSettingsInstance = malloc(sizeof(AudioSettingsMembers));
+ audioSettingsInstance->sampleRate = DEFAULT_SAMPLE_RATE;
+ audioSettingsInstance->numChannels = DEFAULT_NUM_CHANNELS;
+ audioSettingsInstance->blocksize = DEFAULT_BLOCKSIZE;
+ audioSettingsInstance->tempo = DEFAULT_TEMPO;
+ audioSettingsInstance->timeSignatureBeatsPerMeasure = DEFAULT_TIMESIG_BEATS_PER_MEASURE;
+ audioSettingsInstance->timeSignatureNoteValue = DEFAULT_TIMESIG_NOTE_VALUE;
+}
+
+static AudioSettings _getAudioSettings(void)
+{
+ if (audioSettingsInstance == NULL) {
+ initAudioSettings();
+ }
+
+ return audioSettingsInstance;
+}
+
+SampleRate getSampleRate(void)
+{
+ return _getAudioSettings()->sampleRate;
+}
+
+ChannelCount getNumChannels(void)
+{
+ return _getAudioSettings()->numChannels;
+}
+
+SampleCount getBlocksize(void)
+{
+ return _getAudioSettings()->blocksize;
+}
+
+Tempo getTempo(void)
+{
+ return _getAudioSettings()->tempo;
+}
+
+unsigned short getTimeSignatureBeatsPerMeasure(void)
+{
+ return _getAudioSettings()->timeSignatureBeatsPerMeasure;
+}
+
+unsigned short getTimeSignatureNoteValue(void)
+{
+ return _getAudioSettings()->timeSignatureNoteValue;
+}
+
+
+boolByte setSampleRate(const SampleRate sampleRate)
+{
+ if (sampleRate <= 0.0f) {
+ logError("Can't set sample rate to %f", sampleRate);
+ return false;
+ }
+
+ logInfo("Setting sample rate to %gHz", sampleRate);
+ _getAudioSettings()->sampleRate = sampleRate;
+ return true;
+}
+
+boolByte setNumChannels(const ChannelCount numChannels)
+{
+ if (numChannels <= 0) {
+ logError("Can't set channel count to %d", numChannels);
+ return false;
+ }
+
+ logInfo("Setting %d channels", numChannels);
+ _getAudioSettings()->numChannels = numChannels;
+ return true;
+}
+
+boolByte setBlocksize(const SampleCount blocksize)
+{
+ if (blocksize <= 0) {
+ logError("Can't set invalid blocksize %d", blocksize);
+ return false;
+ }
+
+ logInfo("Setting blocksize to %ld", blocksize);
+ _getAudioSettings()->blocksize = blocksize;
+ return true;
+}
+
+
+boolByte setTempo(const Tempo tempo)
+{
+ if (tempo <= 0.0f) {
+ logError("Cannot set tempo to %f", tempo);
+ return false;
+ }
+
+ //here
+ logInfo("NOT more Setting tempo to %f", tempo);
+ _getAudioSettings()->tempo = tempo;
+ return true;
+}
+
+void setTempoFromMidiBytes(const byte *bytes)
+{
+ double tempo;
+ unsigned long beatLengthInMicroseconds = 0;
+
+ printf("inside midi bytes function");
+ if (bytes != NULL) {
+ float tempotest = getTempo();
+ //seem like a good way to test it? yep
+ printf ("THIS WAS THE TEMPO: %f", tempotest);
+ if (!getTempo()){ //something like this? not sure how to tell if a struct is empty it's integer
+ logInfo("tempo was empty so setting to default value");
+ beatLengthInMicroseconds = (unsigned long)(0x00000000 | (bytes[0] << 16) | (bytes[1] << 8) | (bytes[2]));
+ // Convert beats / microseconds -> beats / minutes
+
+ tempo = (1000000.0 / (double)beatLengthInMicroseconds) * 60.0;
+ tempo = 120; //(1000000.0 / (double)2000) * 60.0;
+ //i guess this function just not called at all yeah but where is tempo Set then? default of some kind
+ //almost like this file isn't getting compiled. above I set tempo to 120, not 100 it might be not calling this function because bytes == NULL
+ //something like this? yes
+ setTempo((float)tempo);
+ }else{
+ logInfo("Using tempo from command line args");
+ }// so midi doesn't have tempo setting i guess t's setting to 100 now, it is supplied from cli? no
+ }else{
+ logInfo("bytes == NULL");
+ }// so midi doesn't have tempo setting i guess t's setting to 100 now, it is supplied from cli? no
+}
+
+boolByte setTimeSignatureBeatsPerMeasure(const unsigned short beatsPerMeasure)
+{
+ // Bit of an easter egg :)
+ if (beatsPerMeasure < 2 || beatsPerMeasure > 12) {
+ logInfo("Freaky time signature, but whatever you say...");
+ }
+
+ if (beatsPerMeasure <= 0) {
+ logError("Ignoring attempt to set time signature numerator to %d", beatsPerMeasure);
+ return false;
+ }
+
+ _getAudioSettings()->timeSignatureBeatsPerMeasure = beatsPerMeasure;
+ return true;
+}
+
+boolByte setTimeSignatureNoteValue(const unsigned short noteValue)
+{
+ // Bit of an easter egg :)
+ if (!(noteValue == 2 || noteValue == 4 || noteValue == 8 || noteValue == 16) || noteValue < 2 || noteValue > 16) {
+ logInfo("Interesting time signature you've chosen. I'm sure this piece is going to sound great...");
+ }
+
+ if (noteValue <= 0) {
+ logError("Ignoring attempt to set time signature denominator to %d", noteValue);
+ return false;
+ }
+
+ _getAudioSettings()->timeSignatureNoteValue = noteValue;
+ return true;
+}
+
+boolByte setTimeSignatureFromString(const CharString signature)
+{
+ char *slash = NULL;
+ unsigned short numerator = 0;
+ unsigned short denominator = 0;
+
+ if (!charStringIsEmpty(signature)) {
+ slash = strchr(signature->data, '/');
+
+ if (slash != NULL) {
+ *slash = '\0';
+ numerator = (unsigned short)strtod(signature->data, NULL);
+ denominator = (unsigned short)strtod(slash + 1, NULL);
+
+ if (numerator > 0 && denominator > 0) {
+ return (boolByte)(setTimeSignatureBeatsPerMeasure(numerator) &&
+ setTimeSignatureNoteValue(denominator));
+ }
+ }
+ }
+
+ return false;
+}
+
+boolByte setTimeSignatureFromMidiBytes(const byte *bytes)
+{
+ if (bytes != NULL) {
+ return (boolByte)(setTimeSignatureBeatsPerMeasure(bytes[0]) &&
+ setTimeSignatureNoteValue((unsigned const short)powl(2, bytes[1])));
+ }
+
+ return false;
+}
+
+void freeAudioSettings(void)
+{
+ free(audioSettingsInstance);
+ audioSettingsInstance = NULL;
+}
diff --git a/source/audio/AudioSettings.h b/source/audio/AudioSettings.h
new file mode 100644
index 0000000..5d050f2
--- /dev/null
+++ b/source/audio/AudioSettings.h
@@ -0,0 +1,183 @@
+//
+// AudioSettings.h - MrsWatson
+// Created by Nik Reiman on 1/4/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_AudioSettings_h
+#define MrsWatson_AudioSettings_h
+
+#include "base/Types.h"
+#include "base/CharString.h"
+
+#define DEFAULT_SAMPLE_RATE 44100.0f
+#define DEFAULT_NUM_CHANNELS 2
+#define DEFAULT_BLOCKSIZE 512l
+#define DEFAULT_TIME_DIVISION 96
+#define DEFAULT_BITRATE 16
+#define DEFAULT_TEMPO 120.0f
+#define DEFAULT_TIMESIG_BEATS_PER_MEASURE 4
+#define DEFAULT_TIMESIG_NOTE_VALUE 4
+
+typedef struct {
+ SampleRate sampleRate;
+ ChannelCount numChannels;
+ SampleCount blocksize;
+ Tempo tempo;
+ unsigned short timeSignatureBeatsPerMeasure;
+ unsigned short timeSignatureNoteValue;
+} AudioSettingsMembers;
+
+typedef AudioSettingsMembers *AudioSettings;
+extern AudioSettings audioSettingsInstance;
+
+/**
+ * Initialize the global audio settings instance. Since many different classes
+ * require quick access to the audio settings, this is one of the few classes
+ * that has a global singleton instance rather than a "new" allocator.
+ */
+void initAudioSettings(void);
+
+/**
+ * Get the current sample rate.
+ * @return Sample rate in Hertz
+ */
+SampleRate getSampleRate(void);
+
+/**
+ * Get the number of output channels.
+ * @return Number of channels
+ */
+ChannelCount getNumChannels(void);
+
+/**
+ * Give the current block size, which is the number of sample frames sent to the
+ * plugin each time process is called. Note that the blocksize is the number of
+ * *frames* sent to the plugin, so if the channel count is 2 and the blocksize
+ * is 512, 1024 samples will be sent to the plugin. However in that case this
+ * function would still return 512.
+ * @return Blocksize, in sample frames
+ */
+SampleCount getBlocksize(void);
+
+/**
+ * Get the current tempo, in beats per minute
+ * @return Temo in BPM
+ */
+Tempo getTempo(void);
+
+/**
+ * Get the current time signature's numerator, the number of beats per measure.
+ * @return Time signature numerator
+ */
+unsigned short getTimeSignatureBeatsPerMeasure(void);
+
+/**
+ * Get the current time signatures denominator, the value of one beat unit.
+ * @return Time signature denominator
+ */
+unsigned short getTimeSignatureNoteValue(void);
+
+/**
+ * Set the sample rate to be used during processing. This must be set before the
+ * plugin chain is initialized. This function only requires a nonzero value,
+ * however some plugins may behave strangely when sent unusual sample rates.
+ * @param sampleRate Sample rate, in Hertz
+ * @return True if successfully set, false otherwise
+ */
+boolByte setSampleRate(const SampleRate sampleRate);
+
+/**
+ * Set the number of channels to be used during processing. Note that if the
+ * input source defines a channel called, it may override this value.
+ * @param numChannels Number of channels
+ * @return True if successfully set, false otherwise
+ */
+boolByte setNumChannels(const ChannelCount numChannels);
+
+/**
+ * Set the blocksize to be used during processing. Again this should be called
+ * before initializing the plugin chain.
+ * @param blocksize Blocksize in sample frames
+ * @return True if successfully set, false otherwise
+ */
+boolByte setBlocksize(const SampleCount blocksize);
+
+/**
+ * Set tempo to be used during processing.
+ * @param tempo Tempo in beats per minute
+ * @return True if successfully set, false otherwise
+ */
+boolByte setTempo(const Tempo tempo);
+
+/**
+ * MIDI files represent tempo in meta events with a three-byte payload. This
+ * method transforms the three byte sequence from such file into an actual tempo
+ * in beats per minute, and then sets the global tempo to this value.
+ * @param bytes Three byte sequence as read from a MIDI file
+ */
+void setTempoFromMidiBytes(const byte *bytes);
+
+/**
+ * Set the time signature's numerator. This function does very little error
+ * checking, but it does require a non-zero value. However, many plugins may act
+ * strangely with unusual time signatures.
+ * @param beatsPerMeasure Time signature numerator
+ * @return True if successfully set, false otherwise
+ */
+boolByte setTimeSignatureBeatsPerMeasure(const unsigned short beatsPerMeasure);
+
+/**
+ * Set the time signature's denominator. This function does very little error
+ * checking, but it does require a non-zero value. However, many plugins may act
+ * strangely with unusual time signatures.
+ * @param noteValue Time signature denominator
+ * @return True if successfully set, false otherwise
+ */
+boolByte setTimeSignatureNoteValue(const unsigned short noteValue);
+
+/**
+ * MIDI files represent musical time signature with a two-byte sequence. This
+ * function takes two bytes, derives the corresponding time signature, and sets
+ * it in the global instance.
+ * @param bytes Two byte sequence as read from a MIDI file
+ * @return True if successfully set, false otherwise
+ */
+boolByte setTimeSignatureFromMidiBytes(const byte *bytes);
+
+/**
+ * Set the time signature from a string, should look like "3/4".
+ * @param signature Time signature to set
+ * @return True if successfully set, false otherwise
+ */
+boolByte setTimeSignatureFromString(const CharString signature);
+
+/**
+ * Release memory of the global audio settings instance. Any attempt to use the
+ * audio settings functions after this has been called will result in undefined
+ * behavior.
+ */
+void freeAudioSettings(void);
+
+#endif
diff --git a/source/audio/SampleBuffer.c b/source/audio/SampleBuffer.c
new file mode 100644
index 0000000..610cd95
--- /dev/null
+++ b/source/audio/SampleBuffer.c
@@ -0,0 +1,175 @@
+//
+// SampleBuffer.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include "audio/AudioSettings.h"
+#include "audio/SampleBuffer.h"
+#include "base/Endian.h"
+#include "logging/EventLogger.h"
+#include "SampleBuffer.h"
+
+SampleBuffer newSampleBuffer(ChannelCount numChannels, SampleCount blocksize)
+{
+ SampleBuffer sampleBuffer = (SampleBuffer)malloc(sizeof(SampleBufferMembers));
+ sampleBuffer->numChannels = numChannels;
+ sampleBuffer->blocksize = blocksize;
+ sampleBuffer->samples = (Samples *)malloc(sizeof(Samples) * numChannels);
+
+ for (ChannelCount i = 0; i < numChannels; i++) {
+ sampleBuffer->samples[i] = (Samples)malloc(sizeof(Sample) * blocksize);
+ }
+
+ sampleBufferClear(sampleBuffer);
+ return sampleBuffer;
+}
+
+void sampleBufferClear(SampleBuffer self)
+{
+ for (ChannelCount i = 0; i < self->numChannels; i++) {
+ memset(self->samples[i], 0, sizeof(Sample) * self->blocksize);
+ }
+}
+
+boolByte sampleBufferCopyAndMapChannelsWithOffset(SampleBuffer destinationBuffer,
+ SampleCount destinationOffset,
+ const SampleBuffer sourceBuffer,
+ SampleCount sourceOffset,
+ SampleCount numberOfFrames)
+{
+ // Definitely not supported.
+ if (destinationBuffer->blocksize < destinationOffset + numberOfFrames) {
+ logInternalError("Destination buffer size %d < %d", destinationBuffer->blocksize, destinationOffset + numberOfFrames);
+ return false;
+ }
+
+ // Definitely not supported.
+ if (sourceBuffer->blocksize < sourceOffset + numberOfFrames) {
+ logInternalError("Source buffer size %d < %d", sourceBuffer->blocksize, sourceOffset + numberOfFrames);
+ return false;
+ }
+
+ if (sourceBuffer->numChannels != destinationBuffer->numChannels) {
+ logDebug("Mapping channels from %d -> %d", sourceBuffer->numChannels, destinationBuffer->numChannels);
+ }
+
+ // If the other buffer is bigger (or the same size) as this buffer, then only
+ // copy up to the channel count of this buffer. Any other data will be lost,
+ // sorry about that!
+ if (sourceBuffer->numChannels >= destinationBuffer->numChannels) {
+ for (ChannelCount i = 0; i < destinationBuffer->numChannels; ++i) {
+ memcpy(destinationBuffer->samples[i] + destinationOffset, sourceBuffer->samples[i] + sourceOffset, sizeof(Sample) * numberOfFrames);
+ }
+ }
+ // But if this buffer is bigger than the other buffer, then copy all channels
+ // to this one. For example, if this buffer is 4 channels and the other buffer
+ // is 2 channels, then we copy the stereo pair to this channel (L R L R).
+ else {
+ for (ChannelCount i = 0; i < destinationBuffer->numChannels; ++i) {
+ if (sourceBuffer->numChannels > 0) {
+ memcpy(destinationBuffer->samples[i] + destinationOffset,
+ sourceBuffer->samples[i % sourceBuffer->numChannels] + sourceOffset,
+ sizeof(Sample) * numberOfFrames);
+ } else {
+ // If the other buffer has zero channels just clear this buffer.
+ memset(destinationBuffer->samples[i] + destinationOffset, 0, sizeof(Sample) * numberOfFrames);
+ }
+ }
+ }
+
+ return true;
+}
+
+boolByte sampleBufferCopyAndMapChannels(SampleBuffer self, const SampleBuffer buffer)
+{
+ // Definitely not supported, otherwise it would be hard to deal with partial
+ // copies and so forth.
+ if (self->blocksize != buffer->blocksize) {
+ logInternalError("Source and destination buffer are not the same size");
+ return false;
+ }
+
+ return sampleBufferCopyAndMapChannelsWithOffset(self, 0, buffer, 0, self->blocksize);
+}
+
+void sampleBufferCopyPcmSamples(SampleBuffer self, const short *inPcmSamples)
+{
+ const unsigned int numChannels = self->numChannels;
+ const unsigned long numInterlacedSamples = numChannels * self->blocksize;
+ unsigned int currentInterlacedSample = 0;
+ unsigned int currentDeinterlacedSample = 0;
+ unsigned int currentChannel;
+
+ while (currentInterlacedSample < numInterlacedSamples) {
+ for (currentChannel = 0; currentChannel < numChannels; ++currentChannel) {
+ Sample convertedSample = (Sample)inPcmSamples[currentInterlacedSample++] / 32767.0f;
+ self->samples[currentChannel][currentDeinterlacedSample] = convertedSample;
+ }
+
+ ++currentDeinterlacedSample;
+ }
+}
+
+void sampleBufferGetPcmSamples(const SampleBuffer self, short *outPcmSamples, boolByte flipEndian)
+{
+ const unsigned long blocksize = self->blocksize;
+ const unsigned int numChannels = self->numChannels;
+ unsigned int currentInterlacedSample = 0;
+ unsigned int currentSample = 0;
+ unsigned int currentChannel = 0;
+ short shortValue;
+ Sample sample;
+
+ for (currentSample = 0; currentSample < blocksize; ++currentSample) {
+ for (currentChannel = 0; currentChannel < numChannels; ++currentChannel) {
+ sample = self->samples[currentChannel][currentSample];
+ shortValue = (short)(sample * 32767.0f);
+
+ if (flipEndian) {
+ outPcmSamples[currentInterlacedSample++] = flipShortEndian(shortValue);
+ } else {
+ outPcmSamples[currentInterlacedSample++] = shortValue;
+ }
+ }
+ }
+}
+
+void freeSampleBuffer(SampleBuffer self)
+{
+ if (self == NULL) {
+ return;
+ }
+
+ for (ChannelCount channel = 0; channel < self->numChannels; ++channel) {
+ free(self->samples[channel]);
+ }
+ free(self->samples);
+ free(self);
+}
diff --git a/source/audio/SampleBuffer.h b/source/audio/SampleBuffer.h
new file mode 100644
index 0000000..7c19dc9
--- /dev/null
+++ b/source/audio/SampleBuffer.h
@@ -0,0 +1,107 @@
+//
+// SampleBuffer.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_SampleBuffer_h
+#define MrsWatson_SampleBuffer_h
+
+#include "base/Types.h"
+
+typedef struct {
+ ChannelCount numChannels;
+ SampleCount blocksize;
+ Samples *samples;
+} SampleBufferMembers;
+typedef SampleBufferMembers *SampleBuffer;
+
+/**
+ * Create a new SampleBuffer instance
+ * @param numChannels Number of channels
+ * @param blocksize Processing blocksize to use
+ * @return An initialized SampleBuffer instance
+ */
+SampleBuffer newSampleBuffer(ChannelCount numChannels, SampleCount blocksize);
+
+/**
+ * Set all samples to zero
+ * @param self
+ */
+void sampleBufferClear(SampleBuffer self);
+
+/**
+ * Copy some samples from another buffer to this one
+ * @param destinationBuffer
+ * @param destinationOffset zero-based index of where to start in destinationBuffer.
+ * @param sourceBuffer Other buffer to copy from
+ * @param sourceOffset zero-based index of where to start in buffer.
+ * @param numberOfFrames number of frames to copy.
+ * @return True on success, false on failure
+ */
+boolByte sampleBufferCopyAndMapChannelsWithOffset(SampleBuffer destinationBuffer,
+ SampleCount destinationOffset,
+ const SampleBuffer sourceBuffer,
+ SampleCount sourceOffset,
+ SampleCount numberOfFrames);
+
+/**
+* Copy all samples from another buffer to this one
+* @param self
+* @param buffer Other buffer to copy from
+* @return True on success, false on failure
+*/
+boolByte sampleBufferCopyAndMapChannels(SampleBuffer self, const SampleBuffer buffer);
+
+/**
+ * Copy a buffer of interlaced short integer samples to a sample buffer. This
+ * function also converts the samples from integers to floating-point numbers.
+ * Mostly useful for reading raw PCM data into a format usable by plugins.
+ * @param self
+ * @param inPcmSamples Array of interlaced samples. Note that the size of the
+ * length of this array must match the SampleBuffer's blocksize * channel count,
+ * or else undefined behavior will occur.
+ */
+void sampleBufferCopyPcmSamples(SampleBuffer self, const short *inPcmSamples);
+
+/**
+ * Get an array of interlaced short integer samples from the SampleBuffer. This
+ * function will also convert the samples from floating-point numbers to short
+ * integers. Mostly useful for writing raw PCM data.
+ * @param self
+ * @param outPcmSamples A pre-allocated array large enough to hold the result of
+ * the conversion. This means that at least blocksize * channel count samples
+ * must be allocated
+ * @param flipEndian True if the output data should have the samples flipped
+ * from the native endianness.
+ */
+void sampleBufferGetPcmSamples(const SampleBuffer self, short *outPcmSamples, boolByte flipEndian);
+
+/**
+ * Free all memory used by a SampleBuffer instance
+ * @param sampleBuffer
+ */
+void freeSampleBuffer(SampleBuffer self);
+
+#endif
diff --git a/source/base/CharString.c b/source/base/CharString.c
new file mode 100644
index 0000000..e44e4dd
--- /dev/null
+++ b/source/base/CharString.c
@@ -0,0 +1,298 @@
+//
+// CharString.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "logging/EventLogger.h"
+
+CharString newCharString(void)
+{
+ return newCharStringWithCapacity(kCharStringLengthDefault);
+}
+
+CharString newCharStringWithCapacity(size_t length)
+{
+ CharString charString = (CharString)malloc(sizeof(CharStringMembers));
+ charString->capacity = length;
+ charString->data = (char *)malloc(sizeof(char) * length);
+ charStringClear(charString);
+ return charString;
+}
+
+CharString newCharStringWithCString(const char *string)
+{
+ size_t length;
+ CharString result = NULL;
+
+ length = string != NULL ? strlen(string) : 0;
+
+ if (length > kCharStringLengthLong) {
+ logError("Can't create string with length %d", length);
+ } else if (length == 0) {
+ result = newCharString();
+ } else {
+ // Add 1 to compensate for trailing null character
+ result = newCharStringWithCapacity(length + 1);
+ strncpy(result->data, string, length);
+ }
+
+ return result;
+}
+
+void charStringAppend(CharString self, const CharString string)
+{
+ charStringAppendCString(self, string->data);
+}
+
+void charStringAppendCString(CharString self, const char *string)
+{
+ size_t stringLength = strlen(string);
+ size_t selfLength = strlen(self->data);
+
+ if (stringLength + selfLength >= self->capacity) {
+ self->capacity = stringLength + selfLength + 1; // don't forget the null!
+ self->data = (char *)realloc(self->data, self->capacity);
+ strcat(self->data, string);
+ } else {
+ strcat(self->data, string);
+ }
+}
+
+void charStringClear(CharString self)
+{
+ memset(self->data, 0, self->capacity);
+}
+
+void charStringCopyCString(CharString self, const char *string)
+{
+ strncpy(self->data, string, self->capacity);
+}
+
+void charStringCopy(CharString self, const CharString string)
+{
+ strncpy(self->data, string->data, self->capacity);
+}
+
+boolByte charStringIsEmpty(const CharString self)
+{
+ return (boolByte)(self == NULL || self->data == NULL || self->data[0] == '\0');
+}
+
+boolByte charStringIsEqualTo(const CharString self, const CharString string, boolByte caseInsensitive)
+{
+ size_t comparisonSize;
+
+ if (self == NULL || string == NULL) {
+ return false;
+ }
+
+ // Only compare to the length of the smaller of the two strings
+ comparisonSize = self->capacity < string->capacity ? self->capacity : string->capacity;
+
+ if (caseInsensitive) {
+ return (boolByte)(strncasecmp(self->data, string->data, comparisonSize) == 0);
+ } else {
+ return (boolByte)(strncmp(self->data, string->data, comparisonSize) == 0);
+ }
+}
+
+boolByte charStringIsEqualToCString(const CharString self, const char *string, boolByte caseInsensitive)
+{
+ if (self == NULL || string == NULL) {
+ return false;
+ } else if (caseInsensitive) {
+ return (boolByte)(strncasecmp(self->data, string, self->capacity) == 0);
+ } else {
+ return (boolByte)(strncmp(self->data, string, self->capacity) == 0);
+ }
+}
+
+boolByte charStringIsLetter(const CharString self, const size_t index)
+{
+ const char ch = self->data[index];
+ return (boolByte)((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
+}
+
+boolByte charStringIsNumber(const CharString self, const size_t index)
+{
+ const char ch = self->data[index];
+ return (boolByte)(ch >= '0' && ch <= '9');
+}
+
+LinkedList charStringSplit(const CharString self, const char delimiter)
+{
+ LinkedList result = NULL;
+ char *delimiterPtr = NULL;
+ char *selfIndex = self->data;
+ CharString item = NULL;
+ size_t charsToCopy = 0;
+ boolByte done = false;
+
+ if (delimiter == '\0') {
+ logError("Cannot split string with NULL delimiter");
+ return NULL;
+ }
+
+ result = newLinkedList();
+
+ while (!done) {
+ delimiterPtr = strchr(selfIndex, delimiter);
+
+ if (delimiterPtr == NULL) {
+ done = true;
+ charsToCopy = self->data + strlen(self->data) - selfIndex;
+ } else {
+ charsToCopy = delimiterPtr - selfIndex;
+ }
+
+ if (charsToCopy > 0) {
+ item = newCharStringWithCapacity(charsToCopy + 1);
+ strncpy(item->data, selfIndex, charsToCopy);
+ linkedListAppend(result, item);
+ }
+
+ selfIndex = delimiterPtr + 1;
+ }
+
+ return result;
+}
+
+void _charStringWrap(const char *srcString, char *destString, size_t destStringSize, int indentSize, int lineLength);
+void _charStringWrap(const char *srcString, char *destString, size_t destStringSize, int indentSize, int lineLength)
+{
+ char *lineBuffer = NULL;
+ unsigned long destStringIndex = 0;
+ unsigned long srcStringIndex = 0;
+ size_t lineIndex = 0;
+ int indentIndex = 0;
+ size_t bufferLength;
+ char *newlinePosition;
+ char *lastSpacePosition;
+
+ // Sanity checks
+ if (srcString == NULL) {
+ return;
+ } else if (indentSize < 0 || indentSize > lineLength) {
+ return;
+ } else if (lineLength <= 0) {
+ return;
+ }
+
+ lineBuffer = (char *)malloc(sizeof(char) * lineLength);
+
+ while (srcStringIndex < strlen(srcString)) {
+ if (lineIndex == 0) {
+ for (indentIndex = 0; indentIndex < indentSize; indentIndex++) {
+ destString[destStringIndex++] = ' ';
+ lineIndex++;
+ }
+ }
+
+ // Clear out the line buffer, and copy a full line into it
+ memset(lineBuffer, 0, lineLength);
+ bufferLength = lineLength - lineIndex - 1; // don't forget the null!
+
+ if (bufferLength <= 0) {
+ break;
+ }
+
+ strncpy(lineBuffer, srcString + srcStringIndex, bufferLength);
+
+ // Check to see if we have copied the last line of the source string. If so, append that to
+ // the destination string and break.
+ if (bufferLength + srcStringIndex >= strlen(srcString)) {
+ strncpy(destString + destStringIndex, lineBuffer, destStringSize - destStringIndex - 1);
+ break;
+ }
+
+ // Look for any newlines in the buffer, and stop there if we find any
+ newlinePosition = strchr(lineBuffer, '\n');
+
+ if (newlinePosition != NULL) {
+ bufferLength = newlinePosition - lineBuffer + 1;
+ strncpy(destString + destStringIndex, lineBuffer, destStringSize - destStringIndex - 1);
+ destStringIndex += bufferLength;
+ srcStringIndex += bufferLength;
+ lineIndex = 0;
+ continue;
+ }
+
+ // If no newlines were found, then find the last space in this line and copy to that point
+ lastSpacePosition = strrchr(lineBuffer, ' ');
+
+ if (lastSpacePosition == NULL) {
+ // If NULL is returned here, then there are no spaces in this line. In this case, insert
+ // a hyphen at the end of the line and start a new line. Also, we need to leave room
+ // for the newline, so subtract 2 from the total buffer length.
+ bufferLength = lineLength - lineIndex - 1;
+ strncpy(destString + destStringIndex, lineBuffer, bufferLength);
+ destString[lineLength - 1] = '-';
+ // Move the destination string index ahead 1 to account for the hyphen, and the source
+ // string index back one to copy the last character from the previous line.
+ destStringIndex++;
+ srcStringIndex--;
+ } else {
+ bufferLength = lastSpacePosition - lineBuffer;
+ strncpy(destString + destStringIndex, lineBuffer, bufferLength);
+ }
+
+ // Increase string indexes and continue looping
+ destStringIndex += bufferLength;
+ destString[destStringIndex++] = '\n';
+ srcStringIndex += bufferLength + 1;
+ lineIndex = 0;
+ }
+
+ free(lineBuffer);
+}
+
+CharString charStringWrap(const CharString srcString, unsigned int indentSize)
+{
+ CharString destString;
+
+ if (srcString == NULL) {
+ return NULL;
+ }
+
+ // Allocate 2x as many characters as needed to avoid buffer overflows.
+ // Since this method is only used in "user-friendly" cases, it's ok to be
+ // a bit wasteful in the name of avoiding memory corruption. Therefore this
+ // function should *not* used for regular logging or text output.
+ destString = newCharStringWithCapacity(srcString->capacity * 2);
+ _charStringWrap(srcString->data, destString->data, destString->capacity, indentSize, TERMINAL_LINE_LENGTH);
+ return destString;
+}
+
+void freeCharString(CharString self)
+{
+ if (self != NULL) {
+ free(self->data);
+ free(self);
+ }
+}
diff --git a/source/base/CharString.h b/source/base/CharString.h
new file mode 100644
index 0000000..ff0e282
--- /dev/null
+++ b/source/base/CharString.h
@@ -0,0 +1,182 @@
+//
+// CharString.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_CharString_h
+#define MrsWatson_CharString_h
+
+#include <stdlib.h>
+
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+static const size_t kCharStringLengthDefault = 256;
+static const size_t kCharStringLengthShort = 32;
+static const size_t kCharStringLengthLong = 8192;
+
+#define EMPTY_STRING ""
+#define DEFAULT_INDENT_SIZE 2
+#ifndef TERMINAL_LINE_LENGTH
+#define TERMINAL_LINE_LENGTH 80
+#endif
+
+#if WINDOWS
+#define strncasecmp _strnicmp
+#elif LINUX
+#include <strings.h>
+#endif
+
+typedef struct {
+ size_t capacity;
+ char *data;
+} CharStringMembers;
+typedef CharStringMembers *CharString;
+
+/**
+ * @return Create a new CharString instance
+ */
+CharString newCharString(void);
+
+/**
+ * @param length Number of characters
+ * @return Create a new CharString instance
+ */
+CharString newCharStringWithCapacity(size_t length);
+
+/**
+ * Create a new CharString from a C-String
+ * @param string C-String to use (copied to contents)
+ * @return New CharString instance
+ */
+CharString newCharStringWithCString(const char *string);
+
+/**
+ * Append another CharString to this instance, truncating if necessary. Appending
+ * a string to itself will result in undefined behavior.
+ * @param self
+ * @param string String to append
+ */
+void charStringAppend(CharString self, const CharString string);
+
+/**
+ * Append a C-String to this CharString. Appending a string to itself will result
+ * in undefined behavior.
+ * @param self
+ * @param string NULL-terminated string to append
+ */
+void charStringAppendCString(CharString self, const char *string);
+
+/**
+ * Copy the contents of another CharString to this one
+ * @param self
+ * @param string String to copy
+ */
+void charStringCopy(CharString self, const CharString string);
+
+/**
+ * Copy the contents of a C-String to this one
+ * @param self
+ * @param string NULL-terminated string to copy
+ */
+void charStringCopyCString(CharString self, const char *string);
+
+/**
+ * Clear a string's contents
+ * @param self
+ */
+void charStringClear(CharString self);
+
+/**
+ * @param self
+ * @return True if string is NULL or empty (ie, ""), false otherwise
+ */
+boolByte charStringIsEmpty(const CharString self);
+
+/**
+ * Test for string equality
+ * @param self
+ * @param string String to compare to
+ * @param caseInsensitive True for a case-insensitive comparison
+ * @return True if the strings are equal, false otherwise
+ */
+boolByte charStringIsEqualTo(const CharString self, const CharString string, boolByte caseInsensitive);
+
+/**
+ * Test for string equality
+ * @param self
+ * @param string NULL-terminated C-String to compare to
+ * @param caseInsensitive True for a case-insensitive comparison
+ * @return True if the strings are equal, false otherwise
+ */
+boolByte charStringIsEqualToCString(const CharString self, const char *string, boolByte caseInsensitive);
+
+/**
+ * Test if a given character in this string is a letter. This function does not
+ * do any bounds checking, and "letter" means an ASCII character.
+ * @param self
+ * @param index String index. If beyond the bounds of the string, undefined
+ * behavior will result.
+ * @return True if an ASCII character
+ */
+boolByte charStringIsLetter(const CharString self, const size_t index);
+
+/**
+ * Test if a given character in this string is a number. This function does not
+ * do any bounds checking.
+ * @param self
+ * @param index String index. If beyond the bounds of the string, undefined
+ * behavior will result.
+ * @return True is character is a number
+ */
+boolByte charStringIsNumber(const CharString self, const size_t index);
+
+/**
+ * Break a string up into a list of strings based on a delimeter character. The
+ * list of strings do not include the delimiter character itself.
+ * @param self
+ * @param delimiter Delimiter character (cannot be NULL)
+ * @return List of strings (may be an empty list if the delimiter was not found),
+ * or NULL if invalid input given.
+ */
+LinkedList charStringSplit(const CharString self, const char delimiter);
+
+/**
+ * Wrap a string to fix nicely within the width of a terminal window. This
+ * function is a bit inefficient, and should not be called in performance
+ * crucial code areas.
+ * @param self
+ * @param indentSize Initial indenting size to use
+ * @return The copy of the string line wrapped to fit in a terminal window
+ */
+CharString charStringWrap(const CharString self, unsigned int indentSize);
+
+/**
+ * Free a CharStar and its contents
+ * @param self
+ */
+void freeCharString(CharString self);
+
+#endif
diff --git a/source/base/Endian.c b/source/base/Endian.c
new file mode 100644
index 0000000..5eb3c81
--- /dev/null
+++ b/source/base/Endian.c
@@ -0,0 +1,93 @@
+//
+// Endian.c - MrsWatson
+// Created by Nik Reiman on 10 Dec 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 "base/Endian.h"
+#include "base/PlatformInfo.h"
+
+unsigned short flipShortEndian(const unsigned short value)
+{
+ return (value << 8) | (value >> 8);
+}
+
+unsigned short convertBigEndianShortToPlatform(const unsigned short value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return (value << 8) | (value >> 8);
+ } else {
+ return value;
+ }
+}
+
+unsigned int convertBigEndianIntToPlatform(const unsigned int value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24);
+ } else {
+ return value;
+ }
+}
+
+unsigned int convertLittleEndianIntToPlatform(const unsigned int value)
+{
+ if (!platformInfoIsLittleEndian()) {
+ return (value << 24) | ((value << 8) & 0x00ff0000) | ((value >> 8) & 0x0000ff00) | (value >> 24);
+ } else {
+ return value;
+ }
+}
+
+unsigned short convertByteArrayToUnsignedShort(const byte *value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return ((value[1] << 8) & 0x0000ff00) | value[0];
+ } else {
+ return ((value[0] << 8) & 0x0000ff00) | value[1];
+ }
+}
+
+unsigned int convertByteArrayToUnsignedInt(const byte *value)
+{
+ if (platformInfoIsLittleEndian()) {
+ return ((value[3] << 24) | ((value[2] << 16) & 0x00ff0000) |
+ ((value[1] << 8) & 0x0000ff00) | value[0]);
+ } else {
+ return ((value[0] << 24) | ((value[1] << 16) & 0x00ff0000) |
+ ((value[2] << 8) & 0x0000ff00) | value[0]);
+ }
+}
+
+float convertBigEndianFloatToPlatform(const float value)
+{
+ float result = 0.0f;
+ byte *floatToConvert = (byte *)&value;
+ byte *floatResult = (byte *)&result;
+ floatResult[0] = floatToConvert[3];
+ floatResult[1] = floatToConvert[2];
+ floatResult[2] = floatToConvert[1];
+ floatResult[3] = floatToConvert[0];
+ return result;
+}
diff --git a/source/base/Endian.h b/source/base/Endian.h
new file mode 100644
index 0000000..27be1c7
--- /dev/null
+++ b/source/base/Endian.h
@@ -0,0 +1,89 @@
+//
+// Endian.h - MrsWatson
+// Created by Nik Reiman on 10 Dec 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_Endian_h
+#define MrsWatson_Endian_h
+
+#include "base/Types.h"
+
+/**
+ * Flip bytes for a short value. This does not take into account the host's
+ * endian-ness.
+ * @param value Short integer
+ * @return Flipped version of short integer
+ */
+unsigned short flipShortEndian(const unsigned short value);
+
+/**
+ * Convert a big endian short integer to the platform's native endian-ness.
+ * @param value Short integer
+ * @return Flipped version of short integer. If the host is big endian, the same
+ * value will be returned.
+ */
+unsigned short convertBigEndianShortToPlatform(const unsigned short value);
+
+/**
+ * Convert a big endian integer to the platform's native endian-ness.
+ * @param value Integer
+ * @return Flipped version of integer. If the host is big endian, the same value
+ * will be returned.
+ */
+unsigned int convertBigEndianIntToPlatform(const unsigned int value);
+
+/**
+ * Convert a little endian short integer to the platform's native endian-ness.
+ * @param value Short integer
+ * @return Flipped version of short integer. If the host is little endian, the
+ * same value will be returned.
+ */
+unsigned int convertLittleEndianIntToPlatform(const unsigned int value);
+
+/**
+ * Convert a big endian floating-point value to the platform's native endian-ness.
+ * @param value Floating-point number
+ * @return Flipped version of float. If the host is big endian, the same value
+ * will be returned.
+ */
+float convertBigEndianFloatToPlatform(const float value);
+
+/**
+ * Convert raw bytes to an unsigned short value, taking into account the host's
+ * endian-ness.
+ * @param value A buffer which holds at least two bytes
+ * @return Unsigned short integer
+ */
+unsigned short convertByteArrayToUnsignedShort(const byte *value);
+
+/**
+ * Convert raw bytes to an unsigned int value, taking into account the host's
+ * endian-ness.
+ * @param value A buffer which holds at least four bytes
+ * @return Unsigned short integer
+ */
+unsigned int convertByteArrayToUnsignedInt(const byte *value);
+
+#endif
diff --git a/source/base/File.c b/source/base/File.c
new file mode 100644
index 0000000..f420eb6
--- /dev/null
+++ b/source/base/File.c
@@ -0,0 +1,922 @@
+//
+// File.c - MrsWatson
+// Created by Nik Reiman on 09 Dec 12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+// Must be declared before stdlib, shouldn't have any effect on Windows builds
+#define _XOPEN_SOURCE 700
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "base/File.h"
+#include "logging/EventLogger.h"
+
+#if WINDOWS
+#include <Windows.h>
+#include <Shellapi.h>
+#elif UNIX
+#if LINUX
+#include <errno.h>
+#elif MACOSX
+#include <mach-o/dyld.h>
+#endif
+
+#include <dirent.h>
+#include <ftw.h>
+#include <unistd.h>
+#endif
+
+static const int kFileNameStringLength = 1024;
+static const int kFileMaxRecursionDepth = 32;
+static const char *kFileNameInvalidCharacters = "*:?<>|";
+
+static boolByte _isAbsolutePath(const CharString path)
+{
+#if WINDOWS
+
+ if (path->capacity > 3) {
+ // Check for strings which start with a drive letter, ie C:\file
+ if (path->data[1] == ':' && path->data[2] == PATH_DELIMITER) {
+ return true;
+ }
+ // Check for network paths, ie \\SERVER\file
+ else if (path->data[0] == PATH_DELIMITER && path->data[1] == PATH_DELIMITER) {
+ return true;
+ }
+ }
+
+#else
+
+ if (path->capacity > 1 && path->data[0] == PATH_DELIMITER) {
+ return true;
+ }
+
+#endif
+ return false;
+}
+
+static CharString _convertRelativePathToAbsolute(const CharString path)
+{
+ CharString result = newCharStringWithCapacity(kFileNameStringLength);
+ CharString currentDirectory = fileGetCurrentDirectory();
+ snprintf(result->data, result->capacity, "%s%c%s", currentDirectory->data, PATH_DELIMITER, path->data);
+ freeCharString(currentDirectory);
+ return result;
+}
+
+static CharString _buildAbsolutePath(const CharString parentDir, const CharString filename)
+{
+ CharString result = NULL;
+ CharString parentDirAbsPath = NULL;
+
+ if (parentDir == NULL || charStringIsEmpty(parentDir)) {
+ logWarn("Attempt to build absolute path with empty directory");
+ return result;
+ } else if (filename == NULL || charStringIsEmpty(filename)) {
+ logWarn("Attempt to build absolute path with empty file");
+ return result;
+ } else if (_isAbsolutePath(filename)) {
+ result = newCharStringWithCapacity(kFileNameStringLength);
+ charStringCopy(result, filename);
+ return result;
+ }
+
+ if (_isAbsolutePath(parentDir)) {
+ parentDirAbsPath = newCharStringWithCapacity(kFileNameStringLength);
+ charStringCopy(parentDirAbsPath, parentDir);
+ } else {
+ parentDirAbsPath = _convertRelativePathToAbsolute(parentDir);
+ }
+
+ result = newCharStringWithCapacity(kFileNameStringLength);
+ snprintf(result->data, result->capacity, "%s%c%s", parentDirAbsPath->data, PATH_DELIMITER, filename->data);
+ freeCharString(parentDirAbsPath);
+ return result;
+}
+
+static boolByte _isDirectory(const CharString path)
+{
+ boolByte result = false;
+
+#if UNIX
+ struct stat buffer;
+
+ if (stat(path->data, &buffer) == 0) {
+ result = (boolByte)S_ISDIR(buffer.st_mode);
+ }
+
+#elif WINDOWS
+ DWORD fileAttributes = GetFileAttributesA((LPCSTR)path->data);
+
+ if (fileAttributes != INVALID_FILE_ATTRIBUTES) {
+ result = (boolByte)(fileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+ }
+
+#endif
+
+ return result;
+}
+
+static boolByte _pathContainsInvalidChars(const CharString path)
+{
+ size_t i = 0;
+#if WINDOWS
+
+ // The colon is not allowed in pathnames (even on Windows), however it is
+ // present in absolute pathnames for the drive letter. Thus we must skip
+ // the first 3 characters of the path when dealing with absolute paths on
+ // this platform.
+ if (_isAbsolutePath(path)) {
+ i = 3;
+ }
+
+#endif
+
+ if (path != NULL) {
+ for (i = 0; i < strlen(kFileNameInvalidCharacters); ++i) {
+ if (strchr(path->data, kFileNameInvalidCharacters[i]) != NULL) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+File newFile(void)
+{
+ File result = (File)malloc(sizeof(FileMembers));
+ result->absolutePath = newCharStringWithCapacity(kFileNameStringLength);
+ result->fileType = kFileTypeInvalid;
+ result->_fileHandle = NULL;
+ result->_openMode = kFileOpenModeClosed;
+ return result;
+}
+
+File newFileWithPath(const CharString path)
+{
+ File result = newFile();
+ CharString currentDirectory = NULL;
+ CharString absolutePath = NULL;
+
+ if (path != NULL && !charStringIsEmpty(path)) {
+ if (_isAbsolutePath(path)) {
+ charStringCopy(result->absolutePath, path);
+ } else if (_pathContainsInvalidChars(path)) {
+ logWarn("Could not create file/directory with name '%s'", path->data);
+ freeFile(result);
+ return NULL;
+ } else {
+ currentDirectory = fileGetCurrentDirectory();
+
+ if (currentDirectory == NULL) {
+ logWarn("Could not create file relative to current directory");
+ freeFile(result);
+ return NULL;
+ }
+
+ absolutePath = _buildAbsolutePath(currentDirectory, path);
+ charStringCopy(result->absolutePath, absolutePath);
+ freeCharString(currentDirectory);
+ freeCharString(absolutePath);
+ }
+
+ if (fileExists(result)) {
+ result->fileType = _isDirectory(result->absolutePath) ? kFileTypeDirectory : kFileTypeFile;
+ }
+ }
+
+ return result;
+}
+
+File newFileWithPathCString(const char *path)
+{
+ File result = NULL;
+
+ if (path != NULL) {
+ CharString pathString = newCharStringWithCString(path);
+ result = newFileWithPath(pathString);
+ freeCharString(pathString);
+ }
+
+ return result;
+}
+
+File newFileWithParent(const File parent, const CharString path)
+{
+ File result = NULL;
+ CharString absolutePath = NULL;
+
+ if (parent == NULL) {
+ logWarn("Cannot create file/directory with null parent");
+ return NULL;
+ } else if (!fileExists(parent)) {
+ logWarn("Cannot create file/directory under non-existent parent");
+ return NULL;
+ } else if (!_isDirectory(parent->absolutePath)) {
+ logWarn("Cannot create file/directory under non-directory parent '%s'", parent->absolutePath->data);
+ return NULL;
+ } else if (path == NULL || charStringIsEmpty(path)) {
+ logWarn("Cannot create empty file/directory name with parent");
+ return NULL;
+ } else if (_pathContainsInvalidChars(path)) {
+ logWarn("Could not create file/directory with name '%s'", path->data);
+ freeFile(result);
+ return NULL;
+ } else if (_isAbsolutePath(path)) {
+ logWarn("Cannot create file '%s' with absolute directory under a parent", path->data);
+ freeFile(result);
+ return NULL;
+ }
+
+ result = newFile();
+ absolutePath = _buildAbsolutePath(parent->absolutePath, path);
+ charStringCopy(result->absolutePath, absolutePath);
+ freeCharString(absolutePath);
+
+ if (fileExists(result)) {
+ result->fileType = _isDirectory(result->absolutePath) ? kFileTypeDirectory : kFileTypeFile;
+ }
+
+ return result;
+}
+
+boolByte fileExists(File self)
+{
+#if WINDOWS
+ // Visual Studio's compiler is not C99 compliant, so variable declarations
+ // need to be at the top.
+ unsigned long fileAttributes;
+#endif
+
+ // Normally, we don't do paranoid sanity checks for self != NULL, but in this
+ // case it's useful since the old file API had a different calling convention
+ // which supported calling fileExists() with NULL.
+ if (self == NULL || self->absolutePath == NULL) {
+ return false;
+ }
+
+#if UNIX
+ struct stat fileStat;
+ boolByte result = (boolByte)(stat(self->absolutePath->data, &fileStat) == 0);
+ return result;
+
+#elif WINDOWS
+ fileAttributes = GetFileAttributesA((LPCSTR)self->absolutePath->data);
+
+ if (fileAttributes == INVALID_FILE_ATTRIBUTES) {
+ return false;
+ }
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+boolByte fileCreate(File self, const FileType fileType)
+{
+ if (fileExists(self)) {
+ return false;
+ } else if (charStringIsEmpty(self->absolutePath)) {
+ logWarn("Attempt to create file/directory without a path");
+ return false;
+ }
+
+ switch (fileType) {
+ case kFileTypeFile:
+ self->_fileHandle = fopen(self->absolutePath->data, "wb");
+
+ if (self->_fileHandle != NULL) {
+ self->fileType = kFileTypeFile;
+ self->_openMode = kFileOpenModeWrite;
+ return true;
+ }
+
+ break;
+
+ case kFileTypeDirectory:
+#if UNIX
+ if (mkdir(self->absolutePath->data, 0755) == 0) {
+#elif WINDOWS
+
+ if (CreateDirectoryA(self->absolutePath->data, NULL)) {
+#endif
+ self->fileType = kFileTypeDirectory;
+ return true;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static File _copyFileToDirectory(File self, const File destination) {
+ File result = NULL;
+ CharString basename = NULL;
+ void *selfContents = NULL;
+ size_t selfSize = 0;
+
+ // Close and re-open file to make sure that we start reading at the beginning
+ fileClose(self);
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not re-open file for reading during copy");
+ return NULL;
+ }
+
+ basename = fileGetBasename(self);
+
+ if (basename == NULL) {
+ logError("Could not get basename during copy");
+ } else {
+ result = newFileWithParent(destination, basename);
+ freeCharString(basename);
+
+ if (result == NULL) {
+ logError("Could not create destination file object");
+ return NULL;
+ }
+
+ if (!fileCreate(result, kFileTypeFile)) {
+ logError("Could not create destination file");
+ freeFile(result);
+ return NULL;
+ }
+
+ selfSize = fileGetSize(self);
+
+ if (selfSize == 0) {
+ // If the source file is empty, then creating the result file is good
+ // enough and we can return here.
+ logDebug("Source file during copy is 0 bytes");
+ return result;
+ }
+
+ selfContents = fileReadBytes(self, selfSize);
+
+ if (selfContents == NULL) {
+ logError("Could not read source file during copy");
+ freeFile(result);
+ return NULL;
+ }
+
+ if (!fileWriteBytes(result, selfContents, selfSize)) {
+ logError("Could not copy source file");
+ freeFile(result);
+ return NULL;
+ }
+ }
+
+ return result;
+}
+
+static File _copyDirectoryToDirectory(File self, const File destination) {
+ File result = NULL;
+ // Get the basename first, because if it fails then there's no point in doing
+ // the actual copy.
+ CharString basename = fileGetBasename(self);
+#if WINDOWS
+ SHFILEOPSTRUCT fileOperation;
+#endif
+
+ if (basename == NULL) {
+ logError("Could not get basename from directory during copy");
+ return NULL;
+ }
+
+#if UNIX
+ // Using nftw() is a real pain here, because the recursive callback will start
+ // at the top of the tree and work back up, meaning that for each file we'd
+ // need to recursively mkdir the basename, etc.
+ // So this is a bit lazy but actually more foolproof
+ CharString copyCommand = newCharString();
+ snprintf(copyCommand->data, copyCommand->capacity, "/bin/cp -r \"%s\" \"%s\"",
+ self->absolutePath->data, destination->absolutePath->data);
+ int systemResult = system(copyCommand->data);
+ freeCharString(copyCommand);
+
+ if (WEXITSTATUS(systemResult) == 0) {
+ result = newFileWithParent(destination, basename);
+
+ if (result == NULL) {
+ logError("Copied '%s' to '%s', but could not create a File object for the result",
+ self->absolutePath->data, destination->absolutePath->data);
+ freeCharString(basename);
+ return NULL;
+ }
+ } else {
+ logError("Could not copy directory '%s' to '%s'",
+ self->absolutePath->data, destination->absolutePath->data);
+ }
+
+#elif WINDOWS
+ memset(&fileOperation, 0, sizeof(fileOperation));
+ fileOperation.wFunc = FO_COPY;
+ fileOperation.pFrom = self->absolutePath->data;
+ fileOperation.pTo = destination->absolutePath->data;
+ fileOperation.fFlags = FOF_NO_UI;
+
+ if (SHFileOperationA(&fileOperation) == 0) {
+ result = newFileWithParent(destination, basename);
+ }
+
+#endif
+
+ freeCharString(basename);
+ return result;
+}
+
+File fileCopyTo(File self, const File destination) {
+ File result = NULL;
+
+ if (destination == NULL || !fileExists(destination)) {
+ logError("Attempt to copy file/directory to invalid destination");
+ return NULL;
+ } else if (!_isDirectory(destination->absolutePath)) {
+ logError("Attempt to copy file/directory to non-directory destination");
+ return NULL;
+ } else if (!fileExists(self)) {
+ logError("Attempt to copy non-existent file");
+ return NULL;
+ }
+
+ switch (self->fileType) {
+ case kFileTypeFile:
+ result = _copyFileToDirectory(self, destination);
+ break;
+
+ case kFileTypeDirectory:
+ result = _copyDirectoryToDirectory(self, destination);
+ break;
+
+ default:
+ logError("Attempt to copy invalid file type");
+ break;
+ }
+
+ return result;
+}
+
+#if UNIX
+static int _removeCallback(const char *path, const struct stat * fileState, int typeflag, struct FTW * ftwBuffer) {
+ int result = remove(path);
+
+ if (result != 0) {
+ logWarn("Could not remove '%s'", path);
+ }
+
+ return result;
+}
+#endif
+
+boolByte fileRemove(File self) {
+ boolByte result = false;
+#if WINDOWS
+ SHFILEOPSTRUCTA fileOperation = {0};
+#endif
+
+ if (fileExists(self)) {
+ switch (self->fileType) {
+ case kFileTypeFile:
+ // Yes, this seems a bit silly, but otherwise we threaten to leak resources
+ fileClose(self);
+ result = (boolByte)(remove(self->absolutePath->data) == 0);
+ break;
+
+ case kFileTypeDirectory:
+#if UNIX
+ result = (boolByte)(nftw(self->absolutePath->data, _removeCallback, kFileMaxRecursionDepth, FTW_DEPTH | FTW_PHYS) == 0);
+#elif WINDOWS
+ memset(&fileOperation, 0, sizeof(fileOperation));
+ fileOperation.wFunc = FO_DELETE;
+ fileOperation.pFrom = self->absolutePath->data;
+ fileOperation.pTo = NULL;
+ fileOperation.fFlags = FOF_NO_UI | FOF_NOCONFIRMATION | FOF_SILENT;
+ result = (SHFileOperationA(&fileOperation) == 0);
+#endif
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (result) {
+ self->fileType = kFileTypeInvalid;
+ }
+
+ return result;
+}
+
+LinkedList fileListDirectory(File self) {
+ LinkedList items = newLinkedList();
+ CharString path;
+ File file;
+
+#if UNIX
+ DIR *directoryPtr = opendir(self->absolutePath->data);
+
+ if (directoryPtr == NULL) {
+ freeLinkedList(items);
+ return 0;
+ }
+
+ struct dirent *entry;
+
+ while ((entry = readdir(directoryPtr)) != NULL) {
+ if (entry->d_name[0] != '.') {
+ path = newCharStringWithCString(entry->d_name);
+ file = newFileWithParent(self, path);
+ linkedListAppend(items, file);
+ freeCharString(path);
+ }
+ }
+
+ closedir(directoryPtr);
+
+#elif WINDOWS
+ WIN32_FIND_DATAA findData;
+ HANDLE findHandle;
+ CharString searchString = newCharStringWithCapacity(kFileNameStringLength);
+
+ snprintf(searchString->data, searchString->capacity, "%s\\*", self->absolutePath->data);
+ findHandle = FindFirstFileA((LPCSTR)(searchString->data), &findData);
+ freeCharString(searchString);
+
+ if (findHandle == INVALID_HANDLE_VALUE) {
+ freeLinkedList(items);
+ return 0;
+ }
+
+ do {
+ if (findData.cFileName[0] != '.') {
+ path = newCharStringWithCString(findData.cFileName);
+ file = newFileWithParent(self, path);
+ linkedListAppend(items, file);
+ freeCharString(path);
+ }
+ } while (FindNextFileA(findHandle, &findData) != 0);
+
+ FindClose(findHandle);
+
+#else
+ logUnsupportedFeature("List directory contents");
+#endif
+
+ return items;
+}
+
+size_t fileGetSize(File self) {
+ size_t result = 0;
+
+#if UNIX
+ struct stat fileStat;
+
+ if (self->absolutePath == NULL) {
+ return 0;
+ }
+
+ if (stat(self->absolutePath->data, &fileStat) == 0) {
+ if (S_ISREG(fileStat.st_mode)) {
+ // Yes, this will result in a loss of precision, but both freah and fwrite
+ // take a size_t argument, which is what this function is mostly used for.
+ result = (size_t)fileStat.st_size;
+ }
+ }
+
+#elif WINDOWS
+ HANDLE handle = CreateFileA(self->absolutePath->data, GENERIC_READ, 0, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (handle != INVALID_HANDLE_VALUE) {
+ result = GetFileSize(handle, NULL);
+ CloseHandle(handle);
+ }
+
+#else
+ logUnsupportedFeature("Get file size");
+#endif
+
+ return result;
+}
+
+CharString fileReadContents(File self) {
+ CharString result = NULL;
+ size_t fileSize = 0;
+ size_t itemsRead = 0;
+
+ if (self->fileType != kFileTypeFile) {
+ logError("Attempt to read contents from non-file object '%s'", self->absolutePath->data);
+ return NULL;
+ }
+
+ // Windows has problems opening files multiple times (which is done internally in
+ // fileGetSize() via CreateFile), so we must close the file first, calculate size,
+ // and then reopen it for reading.
+ fileClose(self);
+ fileSize = (size_t)fileGetSize(self);
+ fileClose(self);
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for reading", self->absolutePath->data);
+ return NULL;
+ } else {
+ self->_openMode = kFileOpenModeRead;
+ }
+ }
+
+ if (fileSize > 0) {
+ result = newCharStringWithCapacity(fileSize + 1);
+ itemsRead = fread(result->data, 1, fileSize, self->_fileHandle);
+
+ if (itemsRead != fileSize) {
+ logError("Expected to read %d items, read %d items instead", fileSize, itemsRead);
+ }
+ }
+
+ return result;
+}
+
+LinkedList fileReadLines(File self) {
+ LinkedList result = NULL;
+ CharString line = NULL;
+ boolByte done = false;
+ char *newline = NULL;
+
+ if (self->fileType != kFileTypeFile) {
+ logError("Attempt to read contents from non-file object '%s'", self->absolutePath->data);
+ return NULL;
+ }
+
+ if (self->_openMode != kFileOpenModeRead && self->_fileHandle != NULL) {
+ fileClose(self);
+ }
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for reading", self->absolutePath->data);
+ return NULL;
+ } else {
+ self->_openMode = kFileOpenModeRead;
+ }
+ }
+
+ result = newLinkedList();
+
+ while (!done) {
+ line = newCharString();
+
+ if (fgets(line->data, (int)line->capacity, self->_fileHandle) == NULL) {
+ freeCharString(line);
+ done = true;
+ } else {
+ newline = strrchr(line->data, '\n');
+
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ // Also trim these characters, in case the file has Windows-style newlines
+ newline = strrchr(line->data, '\r');
+
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ linkedListAppend(result, line);
+ }
+ }
+
+ return result;
+}
+
+void *fileReadBytes(File self, size_t numBytes) {
+ void *result = NULL;
+ size_t itemsRead = 0;
+
+ if (numBytes == 0) {
+ // Here we log an error (instead of an info message, as is done with the
+ // same type of error during writes) because the caller is probably
+ // expecting a non-NULL object back.
+ logError("Attempt to read 0 bytes from file");
+ return NULL;
+ }
+
+ if (self->fileType != kFileTypeFile) {
+ logError("Attempt to read bytes from non-file object '%s'", self->absolutePath->data);
+ return NULL;
+ }
+
+ if (self->_openMode != kFileOpenModeRead && self->_fileHandle != NULL) {
+ fileClose(self);
+ }
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "rb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for reading", self->absolutePath->data);
+ return NULL;
+ } else {
+ self->_openMode = kFileOpenModeRead;
+ }
+ }
+
+ if (numBytes > 0) {
+ result = malloc(numBytes + 1);
+ memset(result, 0, numBytes + 1);
+ itemsRead = fread(result, 1, numBytes, self->_fileHandle);
+
+ if (itemsRead != numBytes) {
+ logError("Expected to read %d items, read %d items instead", numBytes, itemsRead);
+ }
+ }
+
+ return result;
+}
+
+boolByte fileWrite(File self, const CharString data) {
+ return fileWriteBytes(self, data->data, strlen(data->data));
+}
+
+boolByte fileWriteBytes(File self, const void *data, size_t numBytes) {
+ size_t itemsWritten = 0;
+
+ if (numBytes == 0) {
+ // Here we only log an info message, as this isn't such a critical error,
+ // but it is rather weird.
+ logInfo("Attempt to write 0 bytes to file");
+ return false;
+ }
+
+ if (!fileExists(self)) {
+ logError("Attempt to write to non-existent file");
+ return false;
+ }
+
+ if (self->_openMode != kFileOpenModeWrite && self->_fileHandle != NULL) {
+ fileClose(self);
+ }
+
+ if (self->_fileHandle == NULL) {
+ self->_fileHandle = fopen(self->absolutePath->data, "wb");
+
+ if (self->_fileHandle == NULL) {
+ logError("Could not open '%s' for writing", self->absolutePath->data);
+ return false;
+ } else {
+ self->_openMode = kFileOpenModeWrite;
+ }
+ }
+
+ itemsWritten = fwrite(data, 1, numBytes, self->_fileHandle);
+ return (boolByte)(itemsWritten == numBytes);
+}
+
+CharString fileGetBasename(File self) {
+ CharString result = NULL;
+ char *lastDelimiter = NULL;
+
+ if (self->absolutePath == NULL || charStringIsEmpty(self->absolutePath)) {
+ return NULL;
+ }
+
+ lastDelimiter = strrchr(self->absolutePath->data, PATH_DELIMITER);
+
+ if (lastDelimiter == NULL) {
+ result = newCharStringWithCString(self->absolutePath->data);
+ } else {
+ result = newCharStringWithCString(lastDelimiter + 1);
+ }
+
+ return result;
+}
+
+File fileGetParent(File self) {
+ File result = NULL;
+ CharString path = NULL;
+ char *lastDelimiter = NULL;
+
+ if (self->absolutePath == NULL || charStringIsEmpty(self->absolutePath)) {
+ return NULL;
+ }
+
+ lastDelimiter = strrchr(self->absolutePath->data, PATH_DELIMITER);
+
+ if (lastDelimiter == NULL) {
+ result = newFileWithPath(self->absolutePath);
+ } else {
+ path = newCharStringWithCapacity(kFileNameStringLength);
+ strncpy(path->data, self->absolutePath->data, lastDelimiter - self->absolutePath->data);
+ result = newFileWithPath(path);
+ freeCharString(path);
+ }
+
+ return result;
+}
+
+CharString fileGetExtension(File self) {
+ CharString basename = fileGetBasename(self);
+ CharString result = NULL;
+ char *dot = NULL;
+
+ if (basename == NULL || charStringIsEmpty(basename)) {
+ freeCharString(basename);
+ return NULL;
+ }
+
+ dot = strrchr(basename->data, '.');
+
+ if (dot != NULL) {
+ result = newCharStringWithCString(dot + 1);
+ }
+
+ freeCharString(basename);
+ return result;
+}
+
+CharString fileGetExecutablePath(void) {
+ CharString executablePath = newCharString();
+#if LINUX
+ ssize_t result = readlink("/proc/self/exe", executablePath->data, executablePath->capacity);
+
+ if (result < 0) {
+ logWarn("Could not find executable path, %s", stringForLastError(errno));
+ return NULL;
+ }
+
+#elif MACOSX
+ _NSGetExecutablePath(executablePath->data, (uint32_t *)&executablePath->capacity);
+#elif WINDOWS
+ GetModuleFileNameA(NULL, executablePath->data, (DWORD)executablePath->capacity);
+#endif
+ return executablePath;
+}
+
+CharString fileGetCurrentDirectory(void) {
+ CharString currentDirectory = newCharString();
+#if UNIX
+
+ if (getcwd(currentDirectory->data, currentDirectory->capacity) == NULL) {
+ logError("Could not get current working directory");
+ freeCharString(currentDirectory);
+ return NULL;
+ }
+
+#elif WINDOWS
+ GetCurrentDirectoryA((DWORD)currentDirectory->capacity, currentDirectory->data);
+#endif
+ return currentDirectory;
+}
+
+void fileClose(File self) {
+ if (self->_fileHandle != NULL && self->fileType == kFileTypeFile) {
+ fflush(self->_fileHandle);
+ fclose(self->_fileHandle);
+ self->_fileHandle = NULL;
+ self->_openMode = kFileOpenModeClosed;
+ }
+}
+
+void freeFile(File self) {
+ if (self) {
+ fileClose(self);
+ freeCharString(self->absolutePath);
+ free(self);
+ }
+}
diff --git a/source/base/File.h b/source/base/File.h
new file mode 100644
index 0000000..9c30924
--- /dev/null
+++ b/source/base/File.h
@@ -0,0 +1,262 @@
+//
+// File.h - MrsWatson
+// Created by Nik Reiman on 09 Dec 12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_File_h
+#define MrsWatson_File_h
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "base/CharString.h"
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+#if UNIX
+#define PATH_DELIMITER '/'
+#define ROOT_DIRECTORY "/"
+#elif WINDOWS
+#define PATH_DELIMITER '\\'
+#define ROOT_DIRECTORY "C:\\"
+#endif
+
+typedef enum {
+ kFileTypeFile,
+ kFileTypeDirectory,
+ kFileTypeInvalid
+} FileType;
+
+typedef enum {
+ kFileOpenModeClosed,
+ kFileOpenModeRead,
+ kFileOpenModeWrite,
+ kFileOpenModeInvalid
+} FileOpenMode;
+
+typedef struct {
+ CharString absolutePath;
+ FileType fileType;
+
+ /** Private */
+ FILE *_fileHandle;
+ /** Private */
+ FileOpenMode _openMode;
+} FileMembers;
+typedef FileMembers *File;
+
+/**
+ * @return New empty file object
+ */
+File newFile(void);
+
+/**
+ * Create a new file object which points to a given path. If something exists at
+ * the path, then this object will be initialized with the correct type.
+ * @param path Object path. If this is not an absolute path, it is assumed to
+ * be relative to the current directory.
+ * @return New file object
+ */
+File newFileWithPath(const CharString path);
+
+/**
+ * Create a new file object which points to a given path. If something exists at
+ * the path, then this object will be initialized with the correct type.
+ * @param path Object path. If this is not an absolute path, it is assumed to
+ * be relative to the current directory.
+ * @return New file object
+ */
+File newFileWithPathCString(const char *path);
+
+/**
+ * Create a new file object which points to a path under another directory. If
+ * something exists at the path, then this object will be initialized with the
+ * correct type.
+ * @param parent Directory which the object is located under. If parent does not
+ * exist or is not a directory, this will return NULL.
+ * @param path Object path, relative to the parent. This may not be an absolute
+ * path.
+ * @return New file object, or NULL if this object could not be created.
+ */
+File newFileWithParent(const File parent, const CharString path);
+
+/**
+ * Check to see if the path referenced by this object exists on disk.
+ * @param self
+ * @return True if the object exists on disk
+ */
+boolByte fileExists(File self);
+
+/**
+ * Create a file object on disk of the given type. This call will fail if an
+ * object already exists on the disk at the path pointed to by this file.
+ * @param self
+ * @param fileType Type of object to create
+ * @return True if the object was created
+ */
+boolByte fileCreate(File self, const FileType fileType);
+
+/**
+ * Copy a file object to a new location. If this file is a directory, it will
+ * be copied recursively. This call will fail if the destination does not exist.
+ * @param self
+ * @param destination Target destination to copy objects to.
+ * @return File object for copied object, or NULL if it could not be copied. The
+ * caller must free this object when finished with it.
+ */
+File fileCopyTo(File self, const File destination);
+
+/**
+ * Remove the file from disk. If this object is a directory, then it will be
+ * removed recursively.
+ * Note: On Windows, you must make sure that all files inside of this directory
+ * are closed, otherwise the operation will fail.
+ * @param self
+ * @return True if the object could be removed
+ */
+boolByte fileRemove(File self);
+
+/**
+ * List the contents of a directory, non-recursively. Special entries such as
+ * "." and ".." are not included in the listing, nor are hidden dotfiles.
+ * @param self
+ * @return A linked list of File objects, or NULL on error. The caller must
+ * release this memory using freeLinkedListAndItems.
+ */
+LinkedList fileListDirectory(File self);
+
+/**
+ * Return the size of a file in bytes.
+ * @param self
+ * @return Number of bytes in the file, or 0 if this object does not exist or
+ * is not a file.
+ */
+size_t fileGetSize(File self);
+
+/**
+ * Read the contents of an entire file into a string. If the file had previously
+ * been opened for writing, then it will be flushed, closed, and reopened for
+ * reading.
+ * @param self
+ * @return CharString containing file contents, or NULL if an error occurred.
+ */
+CharString fileReadContents(File self);
+
+/**
+ * Read the contents of an entire file line-by-line. The lines are returned in
+ * a linked list, which the caller must free when finished (and should use the
+ * freeLinkedListAndItems() method with freeCharString as the second argument).
+ * @param self
+ * @return LinkedList containing a CharString for each line, or NULL if an
+ * error occurred. Note that the newline character is removed from the lines.
+ */
+LinkedList fileReadLines(File self);
+
+/**
+ * Read part of a binary file into a raw byte array. If end of file is reached,
+ * then an array of numBytes is still delivered with the extra bytes initialized
+ * to 0. This is most useful when reading raw PCM data from disk. If the file
+ * had previously been opened for writing, then it will be flushed, closed, and
+ * reopened for reading.
+ * @param self
+ * @param numBytes Number of bytes to deliver, at most. If this is greater than
+ * the size of the actual file, then the entire file will be read. If this is
+ * zero, then NULL will be returned.
+ * @return An initialized array of numBytes bytes with the data, or NULL if an
+ * error occurred.
+ */
+void *fileReadBytes(File self, size_t numBytes);
+
+/**
+ * Write a string to file. The first time this function is called, the file will
+ * be opened for write mode, truncating any data present there if the file
+ * already exists. File buffers are not flushed until fileClose() is called.
+ * @param self
+ * @param data String to write
+ * @return True if the data was written
+ */
+boolByte fileWrite(File self, const CharString data);
+
+/**
+ * Write a binary byte array to disk. The first time this function is called,
+ * the file will be opened for write mode, truncating any data present there if
+ * the file already exists. File buffers are not flushed until fileClose() is
+ * called.
+ * @param self
+ * @param data Binary data to write
+ * @param numBytes Number of bytes to write
+ * @return True if the data could be written
+ */
+boolByte fileWriteBytes(File self, const void *data, size_t numBytes);
+
+/**
+ * Get the file basename, for example "/foo/bar" would return "bar" (regardless
+ * of whether "bar" is a file or directory).
+ * @param self
+ * @return CharString containing basename. The caller must free this memory when
+ * finished with it.
+ */
+CharString fileGetBasename(File self);
+
+/**
+ * Get a file's parent directory.
+ * @param self
+ * @return File representing the parent directory. The caller must free this
+ * object when finished with it.
+ */
+File fileGetParent(File self);
+
+/**
+ * Return a pointer to the file's extension.
+ * @param self
+ * @return Pointer to file extension, or NULL if the file has no extension or
+ * an error occurred.
+ */
+CharString fileGetExtension(File self);
+
+/**
+ * Return the path to the current running executable.
+ * @return Absolute path of the current executable
+ */
+CharString fileGetExecutablePath(void);
+
+/**
+ * Get the current working directory.
+ * @return Current working directory
+ */
+CharString fileGetCurrentDirectory(void);
+
+/**
+ * Close a file and flush its buffers to disk.
+ * @param self
+ */
+void fileClose(File self);
+
+/**
+ * Free a file object and any associated resources
+ * @param self
+ */
+void freeFile(File self);
+
+#endif
diff --git a/source/base/LinkedList.c b/source/base/LinkedList.c
new file mode 100644
index 0000000..4eec9c3
--- /dev/null
+++ b/source/base/LinkedList.c
@@ -0,0 +1,157 @@
+//
+// LinkedList.c - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#include "base/LinkedList.h"
+#include "base/Types.h"
+
+LinkedList newLinkedList(void)
+{
+ LinkedList list = malloc(sizeof(LinkedListMembers));
+
+ list->item = NULL;
+ list->nextItem = NULL;
+ list->_numItems = 0;
+
+ return list;
+}
+
+void linkedListAppend(LinkedList self, void *item)
+{
+ LinkedListIterator iterator = self;
+ LinkedListIterator headNode;
+ LinkedList nextItem;
+
+ if (self == NULL || item == NULL) {
+ return;
+ }
+
+ // First item in the list
+ if (iterator->item == NULL) {
+ iterator->item = item;
+ iterator->_numItems = 1;
+ return;
+ }
+
+ headNode = self;
+
+ while (true) {
+ if (iterator->nextItem == NULL) {
+ nextItem = newLinkedList();
+ nextItem->item = item;
+ iterator->nextItem = nextItem;
+ headNode->_numItems++;
+ break;
+ } else {
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ }
+ }
+}
+
+int linkedListLength(LinkedList self)
+{
+ return self != NULL ? self->_numItems : 0;
+}
+
+void **linkedListToArray(LinkedList self)
+{
+ LinkedListIterator iterator = self;
+ void **array;
+ int i = 0;
+
+ if (self == NULL || linkedListLength(self) == 0) {
+ return NULL;
+ }
+
+ array = (void **)malloc(sizeof(void *) * (linkedListLength(self) + 1));
+
+ while (iterator != NULL) {
+ if (iterator->item != NULL) {
+ array[i++] = iterator->item;
+ }
+
+ iterator = iterator->nextItem;
+ }
+
+ array[i] = NULL;
+ return array;
+}
+
+void linkedListForeach(LinkedList self, LinkedListForeachFunc foreachFunc, void *userData)
+{
+ LinkedListIterator iterator = self;
+
+ while (iterator != NULL) {
+ if (iterator->item != NULL) {
+ foreachFunc(iterator->item, userData);
+ }
+
+ iterator = iterator->nextItem;
+ }
+}
+
+void freeLinkedList(LinkedList self)
+{
+ LinkedListIterator iterator = self;
+
+ while (iterator != NULL) {
+ if (iterator->nextItem == NULL) {
+ free(iterator);
+ break;
+ } else {
+ LinkedList current = iterator;
+ iterator = iterator->nextItem;
+ free(current);
+ }
+ }
+}
+
+void freeLinkedListAndItems(LinkedList self, LinkedListFreeItemFunc freeItem)
+{
+ LinkedListIterator iterator = self;
+ LinkedList current;
+
+ if (iterator->item == NULL) {
+ free(iterator);
+ return;
+ }
+
+ while (true) {
+ if (iterator->nextItem == NULL) {
+ freeItem(iterator->item);
+ free(iterator);
+ break;
+ } else {
+ freeItem(iterator->item);
+ current = iterator;
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ free(current);
+ }
+ }
+}
diff --git a/source/base/LinkedList.h b/source/base/LinkedList.h
new file mode 100644
index 0000000..1b5b851
--- /dev/null
+++ b/source/base/LinkedList.h
@@ -0,0 +1,98 @@
+//
+// LinkedList.h - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_LinkedList_h
+#define MrsWatson_LinkedList_h
+
+typedef struct {
+ void *item;
+ void *nextItem;
+
+ // This field should be considered private, and is only valid for the head node
+ int _numItems;
+} LinkedListMembers;
+
+typedef LinkedListMembers *LinkedList;
+typedef LinkedListMembers *LinkedListIterator;
+
+typedef void (*LinkedListForeachFunc)(void *item, void *userData);
+typedef void (*LinkedListFreeItemFunc)(void *item);
+
+/**
+ * Create a new linked list
+ * @return Linked list with no items
+ */
+LinkedList newLinkedList(void);
+
+/**
+ * Add an item to the end of a list
+ * @param self
+ * @param item Item to append. Items should (but do not necessarily have to be)
+ * of the same type. However, if items in the list are not of the same type,
+ * using functions such as foreachItemInList() will be much more difficult.
+ */
+void linkedListAppend(LinkedList self, void *item);
+
+/**
+ * Get the number of items in a list. Use this function instead of accessing
+ * the fields of the list, as the field names or use may change in the future.
+ * @param self
+ * @return Number of items in the list
+ */
+int linkedListLength(LinkedList self);
+
+/**
+ * Flatten a LinkedList to an array. The resulting array will be size N + 1,
+ * with a NULL object at the end of the array.
+ * @param self
+ * @return Array of void* objects with terminating NULL member
+ */
+void **linkedListToArray(LinkedList self);
+
+/**
+ * Iterate over each item in a linked list, calling the given function on each item.
+ * @param self
+ * @param foreachFunc Function to call
+ * @param userData User data to pass to the function
+ */
+void linkedListForeach(LinkedList self, LinkedListForeachFunc foreachFunc, void *userData);
+
+/**
+ * Free each item in a linked list. The contents of the items themselves are *not*
+ * freed. To do that, call freeLinkedListAndItems() instead.
+ * @param self
+ */
+void freeLinkedList(LinkedList self);
+
+/**
+ * Free a linked list and each of its items.
+ * @param self
+ * @param freeItem Free function to be called for each item
+ */
+void freeLinkedListAndItems(LinkedList self, LinkedListFreeItemFunc freeItem);
+
+#endif
diff --git a/source/base/PlatformInfo.c b/source/base/PlatformInfo.c
new file mode 100644
index 0000000..44c45cc
--- /dev/null
+++ b/source/base/PlatformInfo.c
@@ -0,0 +1,272 @@
+//
+// PlatformInfo.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <string.h>
+
+#include "base/PlatformInfo.h"
+#include "logging/EventLogger.h"
+
+#if LINUX
+#include <sys/utsname.h>
+#include "base/File.h"
+
+#define LSB_FILE_PATH "/etc/lsb-release"
+#define LSB_DISTRIBUTION "DISTRIB_DESCRIPTION"
+#endif
+
+static PlatformType _getPlatformType()
+{
+#if MACOSX
+ return PLATFORM_MACOSX;
+#elif WINDOWS
+ return PLATFORM_WINDOWS;
+#elif LINUX
+ return PLATFORM_LINUX;
+#else
+ return PLATFORM_UNSUPPORTED;
+#endif
+}
+
+static const char *_getShortPlatformName(void)
+{
+#if MACOSX
+ return "Mac OS X";
+#elif WINDOWS
+
+ if (platformInfoIsRuntime64Bit()) {
+ return "Windows 64-bit";
+ } else {
+ return "Windows 32-bit";
+ }
+
+#elif LINUX
+
+ if (platformInfoIsRuntime64Bit()) {
+ return "Linux-x86_64";
+ } else {
+ return "Linux-i686";
+ }
+
+#else
+ return "Unsupported";
+#endif
+}
+
+#if LINUX
+static void _findLsbDistribution(void *item, void *userData)
+{
+ CharString line = (CharString)item;
+ CharString distributionName = (CharString)userData;
+ LinkedList tokens = charStringSplit(line, '=');
+
+ if (tokens != NULL && linkedListLength(tokens) == 2) {
+ CharString *tokensArray = (CharString *)linkedListToArray(tokens);
+ CharString key = tokensArray[0];
+ CharString value = tokensArray[1];
+
+ if (!strcmp(key->data, LSB_DISTRIBUTION)) {
+ charStringCopy(distributionName, value);
+ }
+
+ free(tokensArray);
+ }
+
+ freeLinkedListAndItems(tokens, (LinkedListFreeItemFunc)freeCharString);
+}
+#endif
+
+#if MACOSX
+extern void _getMacVersionString(CharString outString);
+#endif
+
+static CharString _getPlatformName(void)
+{
+ CharString result = newCharString();
+#if MACOSX
+ charStringCopyCString(result, _getShortPlatformName());
+ _getMacVersionString(result);
+#elif LINUX
+ CharString distributionName = newCharString();
+ struct utsname systemInfo;
+ File lsbRelease = NULL;
+ LinkedList lsbReleaseLines = NULL;
+
+ if (uname(&systemInfo) != 0) {
+ logWarn("Could not get system information from uname");
+ charStringCopyCString(result, "Linux (Unknown platform)");
+ freeCharString(distributionName);
+ return result;
+ }
+
+ charStringCopyCString(distributionName, "(Unknown distribution)");
+
+ lsbRelease = newFileWithPathCString(LSB_FILE_PATH);
+
+ if (fileExists(lsbRelease)) {
+ lsbReleaseLines = fileReadLines(lsbRelease);
+
+ if (lsbReleaseLines != NULL && linkedListLength(lsbReleaseLines) > 0) {
+ linkedListForeach(lsbReleaseLines, _findLsbDistribution, distributionName);
+ }
+ }
+
+ if (charStringIsEmpty(result)) {
+ snprintf(result->data, result->capacity, "Linux %s, kernel %s %s",
+ distributionName->data, systemInfo.release, systemInfo.machine);
+ }
+
+ freeCharString(distributionName);
+ freeLinkedListAndItems(lsbReleaseLines, (LinkedListFreeItemFunc)freeCharString);
+ freeFile(lsbRelease);
+#elif WINDOWS
+ OSVERSIONINFOEX versionInformation;
+ memset(&versionInformation, 0, sizeof(OSVERSIONINFOEX));
+ versionInformation.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ GetVersionEx((OSVERSIONINFO *)&versionInformation);
+ // Generic string which will also work with newer versions of windows
+ snprintf(result->data, result->capacity, "Windows %d.%d",
+ versionInformation.dwMajorVersion, versionInformation.dwMinorVersion);
+
+ // This is a bit lame, but it seems that this is the standard way of getting
+ // the platform name on Windows.
+ switch (versionInformation.dwMajorVersion) {
+ case 6:
+ switch (versionInformation.dwMinorVersion) {
+ case 2:
+ charStringCopyCString(result, "Windows 8");
+ break;
+
+ case 1:
+ charStringCopyCString(result, "Windows 7");
+ break;
+
+ case 0:
+ charStringCopyCString(result, "Windows Vista");
+ break;
+ }
+
+ break;
+
+ case 5:
+ switch (versionInformation.dwMinorVersion) {
+ case 2:
+ charStringCopyCString(result, "Windows Server 2003");
+ break;
+
+ case 1:
+ charStringCopyCString(result, "Windows XP");
+ break;
+
+ case 0:
+ charStringCopyCString(result, "Windows 2000");
+ break;
+ }
+
+ break;
+ }
+
+#else
+ charStringCopyCString(result, "Unsupported platform");
+#endif
+
+ return result;
+}
+
+boolByte platformInfoIsRuntime64Bit(void)
+{
+ return (boolByte)(sizeof(void *) == 8);
+}
+
+boolByte platformInfoIsHost64Bit(void)
+{
+ boolByte result = false;
+
+#if LINUX
+ struct utsname systemInfo;
+
+ if (uname(&systemInfo) != 0) {
+ logError("Could not get system bitness from uname");
+ } else {
+ result = (boolByte)(strcmp(systemInfo.machine, "x86_64") == 0);
+ }
+
+#elif MACOSX
+#elif WINDOWS
+ typedef BOOL (WINAPI * IsWow64ProcessFuncPtr)(HANDLE, PBOOL);
+ BOOL isProcessRunningInWow64 = false;
+ IsWow64ProcessFuncPtr isWow64ProcessFunc = NULL;
+
+ // The IsWow64Process() function is not available on all versions of Windows,
+ // so it must be looked up first and called only if it exists.
+ isWow64ProcessFunc = (IsWow64ProcessFuncPtr)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");
+
+ if (isWow64ProcessFunc != NULL) {
+ if (isWow64ProcessFunc(GetCurrentProcess(), &isProcessRunningInWow64)) {
+ // IsWow64Process will only return true if the current process is a 32-bit
+ // application running on 64-bit Windows.
+ if (isProcessRunningInWow64) {
+ result = true;
+ } else {
+ // If false, then we can assume that the host has the same bitness as
+ // the executable.
+ result = platformInfoIsRuntime64Bit();
+ }
+ }
+ }
+
+#else
+ logUnsupportedFeature("Get host 64-bitness");
+#endif
+
+ return result;
+}
+
+boolByte platformInfoIsLittleEndian(void)
+{
+ int num = 1;
+ return (boolByte)(*(char *)&num == 1);
+}
+
+PlatformInfo newPlatformInfo(void)
+{
+ PlatformInfo platformInfo = (PlatformInfo)malloc(sizeof(PlatformInfoMembers));
+ platformInfo->type = _getPlatformType();
+ platformInfo->name = _getPlatformName();
+ platformInfo->shortName = newCharStringWithCString(_getShortPlatformName());
+ platformInfo->is64Bit = platformInfoIsHost64Bit();
+ return platformInfo;
+}
+
+void freePlatformInfo(PlatformInfo self)
+{
+ if (self != NULL) {
+ freeCharString(self->name);
+ freeCharString(self->shortName);
+ free(self);
+ }
+}
diff --git a/source/base/PlatformInfo.h b/source/base/PlatformInfo.h
new file mode 100644
index 0000000..cd706d6
--- /dev/null
+++ b/source/base/PlatformInfo.h
@@ -0,0 +1,68 @@
+//
+// PlatformInfo.h - MrsWatson
+// Created by Nik Reiman on 14/12/14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PlatformInfo_h
+#define MrsWatson_PlatformInfo_h
+
+#include "base/CharString.h"
+
+typedef enum {
+ PLATFORM_UNSUPPORTED,
+ PLATFORM_MACOSX,
+ PLATFORM_WINDOWS,
+ PLATFORM_LINUX,
+ NUM_PLATFORMS
+} PlatformType;
+
+typedef struct {
+ PlatformType type;
+ CharString name;
+ CharString shortName;
+ boolByte is64Bit;
+} PlatformInfoMembers;
+typedef PlatformInfoMembers *PlatformInfo;
+
+PlatformInfo newPlatformInfo(void);
+
+/**
+ * @brief Static method which returns true if the host CPU is little endian
+ */
+boolByte platformInfoIsLittleEndian(void);
+
+/**
+ * @brief True if the platform is a native 64-bit OS
+ */
+boolByte platformInfoIsHost64Bit(void);
+
+/**
+ * @brief True if the executable is running as a 64-bit binary
+ */
+boolByte platformInfoIsRuntime64Bit(void);
+
+void freePlatformInfo(PlatformInfo self);
+
+#endif
diff --git a/source/base/PlatformInfoMac.m b/source/base/PlatformInfoMac.m
new file mode 100644
index 0000000..2bf0fd9
--- /dev/null
+++ b/source/base/PlatformInfoMac.m
@@ -0,0 +1,41 @@
+//
+// PlatformInfoMac.mm - MrsWatson
+// Created by Nik Reiman on 5/12/14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#if MACOSX
+
+#include <Foundation/Foundation.h>
+
+#include "base/CharString.h"
+
+void _getMacVersionString(CharString outString);
+void _getMacVersionString(CharString outString) {
+ NSString *osVersion = [[NSProcessInfo processInfo] operatingSystemVersionString];
+ const char *osVersionCString = [osVersion UTF8String];
+ charStringAppendCString(outString, osVersionCString);
+}
+
+#endif
diff --git a/source/base/Types.h b/source/base/Types.h
new file mode 100644
index 0000000..c1772ef
--- /dev/null
+++ b/source/base/Types.h
@@ -0,0 +1,89 @@
+//
+// Types.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_Types_h
+#define MrsWatson_Types_h
+
+// Custom types used across the application
+typedef int PcmSample; // TODO: int32_t?
+typedef float Sample;
+typedef Sample *Samples;
+
+typedef double SampleRate;
+typedef double Tempo;
+typedef unsigned long SampleCount;
+typedef unsigned short ChannelCount;
+
+// Using "bool" or "boolByte" (or their uppercase equivalents) is a bit dangerous
+// since compilers on some platforms define this for us. This gets tricky when
+// mixing C89/C99 syntax, so to be safe, we will use a new made-up type instead.
+typedef unsigned char boolByte;
+
+#ifndef byte
+typedef unsigned char byte;
+#endif
+
+#ifndef false
+#define false 0
+#endif
+
+#ifndef true
+#define true 1
+#endif
+
+// Platform-specific hooks or compiler overrides
+#if WINDOWS
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+
+// Even redefining most of the functions below doesn't stop the compiler
+// from nagging about them.
+#pragma warning(disable: 4996)
+
+// Substitutes for POSIX functions not found on Windows
+#define strcasecmp _stricmp
+#define strdup _strdup
+#define unlink _unlink
+#define snprintf _snprintf
+#define isatty _isatty
+#define chdir _chdir
+#define unlink _unlink
+#endif
+
+// LibraryHandle definition
+#if MACOSX
+#include <CoreFoundation/CFBundle.h>
+typedef CFBundleRef LibraryHandle;
+#elif LINUX
+typedef void *LibraryHandle;
+#elif WINDOWS
+typedef HMODULE LibraryHandle;
+#else
+typedef void *LibraryHandle;
+#endif
+
+#endif
diff --git a/source/io/RiffFile.c b/source/io/RiffFile.c
new file mode 100644
index 0000000..269780c
--- /dev/null
+++ b/source/io/RiffFile.c
@@ -0,0 +1,93 @@
+//
+// RiffFile.c - MrsWatson
+// Created by Nik Reiman on 8/13/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdlib.h>
+#include <string.h>
+
+#include "base/Endian.h"
+#include "io/RiffFile.h"
+
+RiffChunk newRiffChunk(void)
+{
+ //anything weird about this? nope, lets get back to sourcewave
+ RiffChunk chunk = (RiffChunk)malloc(sizeof(RiffChunkMembers));
+ memset(chunk->id, 0, 5);
+ chunk->size = 0;
+ chunk->data = NULL;
+ return chunk;
+}
+
+boolByte riffChunkReadNext(RiffChunk self, FILE *fileHandle, boolByte readData)
+{
+ size_t itemsRead = 0;
+ byte *chunkSize;
+
+ if (fileHandle != NULL) {
+ itemsRead = fread(self->id, 1, 4, fileHandle);
+
+ if (itemsRead != 4) {
+ return false;
+ }
+
+ chunkSize = (byte *)malloc(sizeof(byte) * 4);
+ memset(chunkSize, 0, 4);
+ itemsRead = fread(chunkSize, 1, 4, fileHandle);
+
+ if (itemsRead != 4) {
+ free(chunkSize);
+ return false;
+ }
+
+ self->size = convertByteArrayToUnsignedInt(chunkSize);
+ free(chunkSize);
+
+ if (self->size > 0 && readData) {
+ self->data = (byte *)malloc(self->size);
+ itemsRead = fread(self->data, 1, self->size, fileHandle);
+
+ if (itemsRead != self->size) {
+ return false;
+ }
+ }
+ }
+
+ return (boolByte)!feof(fileHandle);
+}
+
+boolByte riffChunkIsIdEqualTo(const RiffChunk self, const char *id)
+{
+ return (boolByte)(strncmp(self->id, id, 4) == 0);
+}
+
+void freeRiffChunk(RiffChunk self)
+{
+ if (self->data) {
+ free(self->data);
+ }
+
+ free(self);
+}
diff --git a/source/io/RiffFile.h b/source/io/RiffFile.h
new file mode 100644
index 0000000..5945d58
--- /dev/null
+++ b/source/io/RiffFile.h
@@ -0,0 +1,75 @@
+//
+// RiffFile.h - MrsWatson
+// Created by Nik Reiman on 8/13/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_RiffFile_h
+#define MrsWatson_RiffFile_h
+
+#include <stdio.h>
+
+#include "base/CharString.h"
+#include "base/Types.h"
+
+typedef struct {
+ char id[5];
+ unsigned int size;
+ byte *data;
+} RiffChunkMembers;
+typedef RiffChunkMembers *RiffChunk;
+
+/**
+ * Create a new RIFF chunk object
+ * @return RiffChunk object
+ */
+RiffChunk newRiffChunk(void);
+
+/**
+ * Read the contents of the next chunk of a RIFF file into this object
+ * @param self
+ * @param fileHandle RIFF file, which should be opened for reading
+ * @param readData If true, save the contents of the chunk in the RiffChunk's
+ * data field. This is not always appropriate, for instance in the case of a
+ * PCM file body. In this case, one usually wants to know the size of the data
+ * chunk, but then to read bites from it in smaller blocks.
+ * @return True if the chunk was successfully read
+ */
+boolByte riffChunkReadNext(RiffChunk self, FILE *fileHandle, boolByte readData);
+
+/**
+ * Test to see if this chunk's ID is equal to the given four character sequence
+ * @param self
+ * @param id String to compare to, should be exactly 4 characters
+ * @return True if the ID's are equal, false otherwise
+ */
+boolByte riffChunkIsIdEqualTo(const RiffChunk self, const char *id);
+
+/**
+ * Free a RiffChunk object and its associated memory.
+ * @param self
+ */
+void freeRiffChunk(RiffChunk self);
+
+#endif
diff --git a/source/io/SampleSource.c b/source/io/SampleSource.c
new file mode 100644
index 0000000..06ba016
--- /dev/null
+++ b/source/io/SampleSource.c
@@ -0,0 +1,165 @@
+//
+// SampleSource.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/File.h"
+#include "io/SampleSource.h"
+#include "logging/EventLogger.h"
+
+void sampleSourcePrintSupportedTypes(void)
+{
+ logInfo("Supported audio file types:");
+ // We can theoretically support more formats, pretty much anything audiofile supports
+ // would work here. However, most of those file types are rather uncommon, and require
+ // special setup when writing, so we only choose the most common ones.
+#if USE_AUDIOFILE
+ logInfo("- AIFF (via libaudiofile)");
+#endif
+#if USE_FLAC
+ logInfo("- FLAC (via libaudiofile)");
+#endif
+
+ // Always supported
+ logInfo("- PCM");
+
+#if USE_AUDIOFILE
+ logInfo("- WAV (via libaudiofile)");
+#else
+ logInfo("- WAV (internal)");
+#endif
+}
+
+static SampleSourceType _sampleSourceGuess(const CharString sampleSourceName)
+{
+ File sourceFile = NULL;
+ CharString sourceFileExtension = NULL;
+ SampleSourceType result = SAMPLE_SOURCE_TYPE_PCM;
+
+ if (sampleSourceName == NULL || charStringIsEmpty(sampleSourceName)) {
+ result = SAMPLE_SOURCE_TYPE_SILENCE;
+ } else {
+ // Look for stdin/stdout
+ if (strlen(sampleSourceName->data) == 1 && sampleSourceName->data[0] == '-') {
+ result = SAMPLE_SOURCE_TYPE_PCM;
+ } else {
+ sourceFile = newFileWithPath(sampleSourceName);
+ sourceFileExtension = fileGetExtension(sourceFile);
+ freeFile(sourceFile);
+
+ // If there is no file extension, then automatically assume raw PCM data. Deal with it!
+ if (charStringIsEmpty(sourceFileExtension)) {
+ result = SAMPLE_SOURCE_TYPE_PCM;
+ }
+ // Possible file extensions for raw PCM data
+ else if (charStringIsEqualToCString(sourceFileExtension, "pcm", true) ||
+ charStringIsEqualToCString(sourceFileExtension, "raw", true) ||
+ charStringIsEqualToCString(sourceFileExtension, "dat", true)) {
+ result = SAMPLE_SOURCE_TYPE_PCM;
+ }
+
+#if USE_AUDIOFILE
+ else if (charStringIsEqualToCString(sourceFileExtension, "aif", true) ||
+ charStringIsEqualToCString(sourceFileExtension, "aiff", true)) {
+ result = SAMPLE_SOURCE_TYPE_AIFF;
+ }
+
+#endif
+
+#if USE_FLAC
+ else if (charStringIsEqualToCString(sourceFileExtension, "flac", true)) {
+ result = SAMPLE_SOURCE_TYPE_FLAC;
+ }
+
+#endif
+
+ else if (charStringIsEqualToCString(sourceFileExtension, "wav", true) ||
+ charStringIsEqualToCString(sourceFileExtension, "wave", true)) {
+ result = SAMPLE_SOURCE_TYPE_WAVE;
+ } else {
+ logCritical("Sample source '%s' does not match any supported type", sampleSourceName->data);
+ result = SAMPLE_SOURCE_TYPE_INVALID;
+ }
+ }
+ }
+
+ freeCharString(sourceFileExtension);
+ return result;
+}
+
+extern SampleSource _newSampleSourceAudiofile(const CharString sampleSourceName,
+ const SampleSourceType sampleSourceType);
+extern SampleSource _newSampleSourcePcm(const CharString sampleSourceName);
+extern SampleSource _newSampleSourceSilence();
+extern SampleSource _newSampleSourceWave(const CharString sampleSourceName);
+
+SampleSource sampleSourceFactory(const CharString sampleSourceName)
+{
+ SampleSourceType sampleSourceType = _sampleSourceGuess(sampleSourceName);
+
+ switch (sampleSourceType) {
+ case SAMPLE_SOURCE_TYPE_SILENCE:
+ return _newSampleSourceSilence();
+
+ case SAMPLE_SOURCE_TYPE_PCM:
+ return _newSampleSourcePcm(sampleSourceName);
+
+#if USE_AUDIOFILE
+
+ case SAMPLE_SOURCE_TYPE_AIFF:
+ return _newSampleSourceAudiofile(sampleSourceName, sampleSourceType);
+#endif
+
+#if USE_FLAC
+
+ case SAMPLE_SOURCE_TYPE_FLAC:
+ return _newSampleSourceAudiofile(sampleSourceName, sampleSourceType);
+#endif
+
+#if USE_AUDIOFILE
+
+ case SAMPLE_SOURCE_TYPE_WAVE:
+ return _newSampleSourceAudiofile(sampleSourceName, sampleSourceType);
+#else
+
+ case SAMPLE_SOURCE_TYPE_WAVE:
+ return _newSampleSourceWave(sampleSourceName);
+#endif
+
+ default:
+ return NULL;
+ }
+}
+
+void freeSampleSource(SampleSource self)
+{
+ self->freeSampleSourceData(self->extraData);
+ freeCharString(self->sourceName);
+ free(self);
+}
diff --git a/source/io/SampleSource.h b/source/io/SampleSource.h
new file mode 100644
index 0000000..f4ef0f4
--- /dev/null
+++ b/source/io/SampleSource.h
@@ -0,0 +1,94 @@
+//
+// SampleSource.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_SampleSource_h
+#define MrsWatson_SampleSource_h
+
+#include "audio/SampleBuffer.h"
+#include "base/CharString.h"
+#include "base/Types.h"
+
+typedef enum {
+ SAMPLE_SOURCE_TYPE_INVALID,
+ SAMPLE_SOURCE_TYPE_SILENCE,
+ SAMPLE_SOURCE_TYPE_PCM,
+ SAMPLE_SOURCE_TYPE_AIFF,
+ SAMPLE_SOURCE_TYPE_FLAC,
+ SAMPLE_SOURCE_TYPE_MP3,
+ SAMPLE_SOURCE_TYPE_OGG,
+ SAMPLE_SOURCE_TYPE_WAVE,
+ NUM_SAMPLE_SOURCES
+} SampleSourceType;
+
+typedef enum {
+ SAMPLE_SOURCE_OPEN_NOT_OPENED,
+ SAMPLE_SOURCE_OPEN_READ,
+ SAMPLE_SOURCE_OPEN_WRITE,
+ NUM_SAMPLE_SOURCE_OPEN_AS
+} SampleSourceOpenAs;
+
+typedef boolByte (*OpenSampleSourceFunc)(void *, const SampleSourceOpenAs);
+typedef boolByte (*ReadSampleBlockFunc)(void *, SampleBuffer);
+typedef boolByte (*WriteSampleBlockFunc)(void *, const SampleBuffer);
+typedef void (*CloseSampleSourceFunc)(void *);
+typedef void (*FreeSampleSourceDataFunc)(void *);
+
+typedef struct {
+ SampleSourceType sampleSourceType;
+ SampleSourceOpenAs openedAs;
+ CharString sourceName;
+ unsigned long numSamplesProcessed;
+
+ OpenSampleSourceFunc openSampleSource;
+ ReadSampleBlockFunc readSampleBlock;
+ WriteSampleBlockFunc writeSampleBlock;
+ CloseSampleSourceFunc closeSampleSource;
+ FreeSampleSourceDataFunc freeSampleSourceData;
+
+ void *extraData;
+} SampleSourceMembers;
+typedef SampleSourceMembers *SampleSource;
+
+/**
+ * Factory method to create a new sample source
+ * @param sampleSourceName Source name. If NULL, then a silent sample source is created.
+ * @return Initialized sample source, or NULL if none could be created
+ */
+SampleSource sampleSourceFactory(const CharString sampleSourceName);
+
+/**
+ * Print a list of all supported sample source pipes to the log
+ */
+void sampleSourcePrintSupportedTypes(void);
+
+/**
+ * Release a sample source and associated resources
+ * @param self
+ */
+void freeSampleSource(SampleSource self);
+
+#endif
diff --git a/source/io/SampleSourceAudiofile.c b/source/io/SampleSourceAudiofile.c
new file mode 100644
index 0000000..17fd1b9
--- /dev/null
+++ b/source/io/SampleSourceAudiofile.c
@@ -0,0 +1,204 @@
+//
+// SampleSourceAudiofile.c - MrsWatson
+// Created by Nik Reiman on 1/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#if USE_AUDIOFILE
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "audio/AudioSettings.h"
+#include "io/SampleSourceAudiofile.h"
+#include "io/SampleSourcePcm.h"
+#include "logging/EventLogger.h"
+
+static boolByte _openSampleSourceAudiofile(void *sampleSourcePtr, const SampleSourceOpenAs openAs)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourceAudiofileData extraData = sampleSource->extraData;
+
+ if (openAs == SAMPLE_SOURCE_OPEN_READ) {
+ extraData->fileHandle = afOpenFile(sampleSource->sourceName->data, "r", NULL);
+
+ if (extraData->fileHandle != NULL) {
+ setNumChannels((const unsigned int)afGetVirtualChannels(extraData->fileHandle, AF_DEFAULT_TRACK));
+ setSampleRate((float)afGetRate(extraData->fileHandle, AF_DEFAULT_TRACK));
+ }
+ } else if (openAs == SAMPLE_SOURCE_OPEN_WRITE) {
+ int byteOrder = AF_BYTEORDER_LITTLEENDIAN;
+ int outfileFormat;
+ switch (sampleSource->sampleSourceType) {
+ case SAMPLE_SOURCE_TYPE_AIFF:
+ // AIFF is the only file format we support which is big-endian. That is,
+ // even on big-endian platforms (which are untested), raw PCM should still
+ // write little-endian data.
+ byteOrder = AF_BYTEORDER_BIGENDIAN;
+ outfileFormat = AF_FILE_AIFF;
+ break;
+ case SAMPLE_SOURCE_TYPE_WAVE:
+ outfileFormat = AF_FILE_WAVE;
+ break;
+ case SAMPLE_SOURCE_TYPE_FLAC:
+ outfileFormat = AF_FILE_FLAC;
+ break;
+ default:
+ logInternalError("Unsupported audiofile type %d", sampleSource->sampleSourceType);
+ return false;
+ }
+
+ AFfilesetup outfileSetup = afNewFileSetup();
+ afInitFileFormat(outfileSetup, outfileFormat);
+ afInitByteOrder(outfileSetup, AF_DEFAULT_TRACK, byteOrder);
+ afInitChannels(outfileSetup, AF_DEFAULT_TRACK, getNumChannels());
+ afInitRate(outfileSetup, AF_DEFAULT_TRACK, getSampleRate());
+ afInitSampleFormat(outfileSetup, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, DEFAULT_BITRATE);
+ extraData->fileHandle = afOpenFile(sampleSource->sourceName->data, "w", outfileSetup);
+ } else {
+ logInternalError("Invalid type for openAs in audiofile source");
+ return false;
+ }
+
+ if (extraData->fileHandle == NULL) {
+ logError("File '%s' could not be opened for %s",
+ sampleSource->sourceName->data,
+ openAs == SAMPLE_SOURCE_OPEN_READ ? "reading" : "writing");
+ return false;
+ }
+
+ sampleSource->openedAs = openAs;
+ return true;
+}
+
+boolByte _readBlockFromAudiofile(void *sampleSourcePtr, SampleBuffer sampleBuffer)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourceAudiofileData extraData = (SampleSourceAudiofileData)(sampleSource->extraData);
+ const size_t bufferByteSize = sizeof(short) * getNumChannels() * getBlocksize();
+ AFframecount numFramesRead = 0;
+
+ if (extraData->pcmBuffer == NULL) {
+ extraData->pcmBuffer = (short *)malloc(bufferByteSize);
+ }
+
+ memset(extraData->pcmBuffer, 0, bufferByteSize);
+ numFramesRead = afReadFrames(extraData->fileHandle, AF_DEFAULT_TRACK,
+ extraData->pcmBuffer, (int)getBlocksize());
+ sampleBufferCopyPcmSamples(sampleBuffer, extraData->pcmBuffer);
+
+ // Set the blocksize of the sample buffer to be the number of frames read
+ sampleBuffer->blocksize = (unsigned long)numFramesRead;
+ sampleSource->numSamplesProcessed += numFramesRead;
+
+ if (numFramesRead == 0) {
+ logDebug("End of audio file reached");
+ return false;
+ } else if (numFramesRead < 0) {
+ logError("Error reading audio file");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+boolByte _writeBlockToAudiofile(void *sampleSourcePtr, const SampleBuffer sampleBuffer)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourceAudiofileData extraData = (SampleSourceAudiofileData)(sampleSource->extraData);
+ const AFframecount numSamplesToWrite = sampleBuffer->blocksize;
+ const size_t bufferByteSize = sizeof(short) * getNumChannels() * getBlocksize();
+ AFframecount numFramesWritten = 0;
+
+ if (extraData->pcmBuffer == NULL) {
+ extraData->pcmBuffer = (short *)malloc(bufferByteSize);
+ }
+
+ memset(extraData->pcmBuffer, 0, bufferByteSize);
+ // TODO: flip endian argument is probably wrong for some file formats (namely AIFF)!!
+ sampleBufferGetPcmSamples(sampleBuffer, extraData->pcmBuffer, false);
+
+ numFramesWritten = afWriteFrames(extraData->fileHandle, AF_DEFAULT_TRACK,
+ extraData->pcmBuffer, (int)getBlocksize());
+ sampleSource->numSamplesProcessed += getBlocksize() * getNumChannels();
+
+ if (numFramesWritten == -1) {
+ logWarn("audiofile encountered an error when writing to file");
+ return false;
+ } else if (numFramesWritten == numSamplesToWrite) {
+ return true;
+ } else {
+ logWarn("Short write occurred while writing samples");
+ return false;
+ }
+}
+
+void _closeSampleSourceAudiofile(void *sampleSourcePtr)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourceAudiofileData extraData = (SampleSourceAudiofileData)sampleSource->extraData;
+
+ if (extraData->fileHandle != NULL) {
+ afCloseFile(extraData->fileHandle);
+ }
+}
+
+void _freeSampleSourceDataAudiofile(void *sampleSourceDataPtr)
+{
+ SampleSourceAudiofileData extraData = (SampleSourceAudiofileData)sampleSourceDataPtr;
+
+ if (extraData->pcmBuffer != NULL) {
+ free(extraData->pcmBuffer);
+ }
+
+ free(extraData);
+}
+
+SampleSource _newSampleSourceAudiofile(const CharString sampleSourceName,
+ const SampleSourceType sampleSourceType)
+{
+ SampleSource sampleSource = (SampleSource)malloc(sizeof(SampleSourceMembers));
+ SampleSourceAudiofileData extraData = (SampleSourceAudiofileData)malloc(sizeof(SampleSourceAudiofileDataMembers));
+
+ sampleSource->sampleSourceType = sampleSourceType;
+ sampleSource->openedAs = SAMPLE_SOURCE_OPEN_NOT_OPENED;
+ sampleSource->sourceName = newCharString();
+ charStringCopy(sampleSource->sourceName, sampleSourceName);
+ sampleSource->numSamplesProcessed = 0;
+
+ sampleSource->openSampleSource = _openSampleSourceAudiofile;
+ sampleSource->readSampleBlock = _readBlockFromAudiofile;
+ sampleSource->writeSampleBlock = _writeBlockToAudiofile;
+ sampleSource->closeSampleSource = _closeSampleSourceAudiofile;
+ sampleSource->freeSampleSourceData = _freeSampleSourceDataAudiofile;
+
+ extraData->fileHandle = NULL;
+ extraData->pcmBuffer = NULL;
+
+ sampleSource->extraData = extraData;
+ return sampleSource;
+}
+
+#endif
diff --git a/source/io/SampleSourceAudiofile.h b/source/io/SampleSourceAudiofile.h
new file mode 100644
index 0000000..3dcf146
--- /dev/null
+++ b/source/io/SampleSourceAudiofile.h
@@ -0,0 +1,47 @@
+//
+// SampleSourceAudiofile.h - MrsWatson
+// Created by Nik Reiman on 1/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_SampleSourceAudiofile_h
+#define MrsWatson_SampleSourceAudiofile_h
+
+#if USE_AUDIOFILE
+#include <audiofile.h>
+
+// This isn't a real SampleSource class, but rather a base class to facilitate
+// in reading and writing any file supported via the audiofile library. As each
+// format has slightly different methods for opening and configuring them, those
+// are defined in the individual subclasses.
+
+typedef struct {
+ AFfilehandle fileHandle;
+ short *pcmBuffer;
+} SampleSourceAudiofileDataMembers;
+
+typedef SampleSourceAudiofileDataMembers *SampleSourceAudiofileData;
+
+#endif
+#endif
diff --git a/source/io/SampleSourcePcm.c b/source/io/SampleSourcePcm.c
new file mode 100644
index 0000000..f0be1e8
--- /dev/null
+++ b/source/io/SampleSourcePcm.c
@@ -0,0 +1,221 @@
+//
+// SampleSourcePcm.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "audio/AudioSettings.h"
+#include "base/PlatformInfo.h"
+#include "io/SampleSourcePcm.h"
+#include "logging/EventLogger.h"
+
+static boolByte openSampleSourcePcm(void *sampleSourcePtr, const SampleSourceOpenAs openAs)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)(sampleSource->extraData);
+
+ extraData->dataBufferNumItems = 0;
+
+ if (openAs == SAMPLE_SOURCE_OPEN_READ) {
+ if (charStringIsEqualToCString(sampleSource->sourceName, "-", false)) {
+ extraData->fileHandle = stdin;
+ charStringCopyCString(sampleSource->sourceName, "stdin");
+ extraData->isStream = true;
+ } else {
+ extraData->fileHandle = fopen(sampleSource->sourceName->data, "rb");
+ }
+ } else if (openAs == SAMPLE_SOURCE_OPEN_WRITE) {
+ if (charStringIsEqualToCString(sampleSource->sourceName, "-", false)) {
+ extraData->fileHandle = stdout;
+ charStringCopyCString(sampleSource->sourceName, "stdout");
+ extraData->isStream = true;
+ } else {
+ extraData->fileHandle = fopen(sampleSource->sourceName->data, "wb");
+ }
+ } else {
+ logInternalError("Invalid type for openAs in PCM file");
+ return false;
+ }
+
+ if (extraData->fileHandle == NULL) {
+ logError("PCM File '%s' could not be opened for %s",
+ sampleSource->sourceName->data, openAs == SAMPLE_SOURCE_OPEN_READ ? "reading" : "writing");
+ return false;
+ }
+
+ sampleSource->openedAs = openAs;
+ return true;
+}
+
+size_t sampleSourcePcmRead(SampleSourcePcmData self, SampleBuffer sampleBuffer)
+{
+ size_t pcmSamplesRead = 0;
+
+ if (self == NULL || self->fileHandle == NULL) {
+ logCritical("Corrupt PCM data structure");
+ return 0;
+ }
+
+ if (self->dataBufferNumItems < (size_t)(sampleBuffer->numChannels * sampleBuffer->blocksize)) {
+ self->dataBufferNumItems = (size_t)(sampleBuffer->numChannels * sampleBuffer->blocksize);
+ self->interlacedPcmDataBuffer = (short *)realloc(self->interlacedPcmDataBuffer, sizeof(short) * self->dataBufferNumItems);
+ }
+
+ // Clear the PCM data buffer, or else the last block will have dirty samples in the end
+ memset(self->interlacedPcmDataBuffer, 0, sizeof(short) * self->dataBufferNumItems);
+
+ pcmSamplesRead = fread(self->interlacedPcmDataBuffer, sizeof(short), self->dataBufferNumItems, self->fileHandle);
+
+ if (pcmSamplesRead < self->dataBufferNumItems) {
+ logDebug("End of PCM file reached");
+ // Set the blocksize of the sample buffer to be the number of frames read
+ sampleBuffer->blocksize = pcmSamplesRead / sampleBuffer->numChannels;
+ }
+
+ logDebug("Read %d samples from PCM file", pcmSamplesRead);
+
+ sampleBufferCopyPcmSamples(sampleBuffer, self->interlacedPcmDataBuffer);
+ return pcmSamplesRead;
+}
+
+static boolByte readBlockFromPcmFile(void *sampleSourcePtr, SampleBuffer sampleBuffer)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)(sampleSource->extraData);
+ SampleCount originalBlocksize = sampleBuffer->blocksize;
+ size_t samplesRead = sampleSourcePcmRead(extraData, sampleBuffer);
+ sampleSource->numSamplesProcessed += samplesRead;
+ return (boolByte)(originalBlocksize == sampleBuffer->blocksize);
+}
+
+size_t sampleSourcePcmWrite(SampleSourcePcmData self, const SampleBuffer sampleBuffer)
+{
+ size_t pcmSamplesWritten = 0;
+ size_t numSamplesToWrite = (size_t)(sampleBuffer->numChannels * sampleBuffer->blocksize);
+
+ if (self == NULL || self->fileHandle == NULL) {
+ logCritical("Corrupt PCM data structure");
+ return false;
+ }
+
+ if (self->dataBufferNumItems < (size_t)(sampleBuffer->numChannels * sampleBuffer->blocksize)) {
+ self->dataBufferNumItems = (size_t)(sampleBuffer->numChannels * sampleBuffer->blocksize);
+ self->interlacedPcmDataBuffer = (short *)realloc(self->interlacedPcmDataBuffer, sizeof(short) * self->dataBufferNumItems);
+ }
+
+ // Clear the PCM data buffer just to be safe
+ memset(self->interlacedPcmDataBuffer, 0, sizeof(short) * self->dataBufferNumItems);
+
+ boolByte isLittleEndian = (boolByte)(self->isLittleEndian != platformInfoIsLittleEndian());
+ sampleBufferGetPcmSamples(sampleBuffer, self->interlacedPcmDataBuffer, isLittleEndian);
+ pcmSamplesWritten = fwrite(self->interlacedPcmDataBuffer, sizeof(short), numSamplesToWrite, self->fileHandle);
+
+ if (pcmSamplesWritten < numSamplesToWrite) {
+ logWarn("Short write to PCM file");
+ return pcmSamplesWritten;
+ }
+
+ logDebug("Wrote %d samples to PCM file", pcmSamplesWritten);
+ return pcmSamplesWritten;
+}
+
+static boolByte writeBlockToPcmFile(void *sampleSourcePtr, const SampleBuffer sampleBuffer)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)(sampleSource->extraData);
+ unsigned int samplesWritten = (int)sampleSourcePcmWrite(extraData, sampleBuffer);
+ sampleSource->numSamplesProcessed += samplesWritten;
+ return (boolByte)(samplesWritten == sampleBuffer->blocksize);
+}
+
+static void _closeSampleSourcePcm(void *sampleSourcePtr)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+
+ if (extraData->fileHandle != NULL) {
+ fclose(extraData->fileHandle);
+ }
+}
+
+void sampleSourcePcmSetSampleRate(void *sampleSourcePtr, SampleRate sampleRate)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+ extraData->sampleRate = (unsigned int)sampleRate;
+}
+
+void sampleSourcePcmSetNumChannels(void *sampleSourcePtr, int numChannels)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+ extraData->numChannels = (unsigned short)numChannels;
+}
+
+void freeSampleSourceDataPcm(void *sampleSourceDataPtr)
+{
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSourceDataPtr;
+
+ if (extraData->interlacedPcmDataBuffer != NULL) {
+ free(extraData->interlacedPcmDataBuffer);
+ extraData->interlacedPcmDataBuffer = NULL;
+ }
+
+ free(extraData);
+}
+
+SampleSource _newSampleSourcePcm(const CharString sampleSourceName)
+{
+ SampleSource sampleSource = (SampleSource)malloc(sizeof(SampleSourceMembers));
+ SampleSourcePcmData extraData = (SampleSourcePcmData)malloc(sizeof(SampleSourcePcmDataMembers));
+
+ sampleSource->sampleSourceType = SAMPLE_SOURCE_TYPE_PCM;
+ sampleSource->openedAs = SAMPLE_SOURCE_OPEN_NOT_OPENED;
+ sampleSource->sourceName = newCharString();
+ charStringCopy(sampleSource->sourceName, sampleSourceName);
+ sampleSource->numSamplesProcessed = 0;
+
+ sampleSource->openSampleSource = openSampleSourcePcm;
+ sampleSource->readSampleBlock = readBlockFromPcmFile;
+ sampleSource->writeSampleBlock = writeBlockToPcmFile;
+ sampleSource->closeSampleSource = _closeSampleSourcePcm;
+ sampleSource->freeSampleSourceData = freeSampleSourceDataPcm;
+
+ extraData->isStream = false;
+ extraData->isLittleEndian = true;
+ extraData->fileHandle = NULL;
+ extraData->dataBufferNumItems = 0;
+ extraData->interlacedPcmDataBuffer = NULL;
+
+ extraData->numChannels = (unsigned short)getNumChannels();
+ extraData->sampleRate = (unsigned int)getSampleRate();
+ extraData->bitsPerSample = 16;
+ sampleSource->extraData = extraData;
+
+ return sampleSource;
+}
diff --git a/source/io/SampleSourcePcm.h b/source/io/SampleSourcePcm.h
new file mode 100644
index 0000000..0dfe789
--- /dev/null
+++ b/source/io/SampleSourcePcm.h
@@ -0,0 +1,92 @@
+//
+// SampleSourcePcm.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_InputSourcePcm_h
+#define MrsWatson_InputSourcePcm_h
+
+#include <stdio.h>
+
+#include "io/SampleSource.h"
+
+typedef struct {
+ boolByte isStream;
+ boolByte isLittleEndian;
+ FILE *fileHandle;
+ size_t dataBufferNumItems;
+ short *interlacedPcmDataBuffer;
+
+ ChannelCount numChannels;
+ SampleRate sampleRate;
+ unsigned short bitsPerSample;
+} SampleSourcePcmDataMembers;
+typedef SampleSourcePcmDataMembers *SampleSourcePcmData;
+// this struct is actually the structure we need, looks like name just redefined later. sampleRate, numChannels, need to check those.
+// ok awesome...so basically in order to check classes/structs, sometimes you have to look in the header file? yeah all structs and enums will be in header file
+// and what is an enum (compared to a struct?) enumeration of possible values for one variable. like TYPE_MP3 TYPE_WAV etc. weird ok no worries
+
+/**
+ * Read raw PCM data to a floating-point sample buffer
+ * @param self
+ * @param sampleBuffer
+ * @return Number of samples read
+ * http://ghghgh.us/VST/
+ * this documentation is still a bit tough for me to read
+ */
+size_t sampleSourcePcmRead(SampleSourcePcmData self, SampleBuffer sampleBuffer);
+
+/**
+ * Writes data from a sample buffer to a PCM output
+ * @param self
+ * @param sampleBuffer
+ * @return Number of samples written
+ */
+size_t sampleSourcePcmWrite(SampleSourcePcmData self, const SampleBuffer sampleBuffer);
+
+/**
+ * Set the sample rate to be used for raw PCM file operations. This is most
+ * relevant when writing a WAVE or a AIFF file, as the sample rate must be given
+ * in the file header.
+ * @param sampleSourcePtr
+ * @param sampleRate Sample rate, in Hertz
+ */
+void sampleSourcePcmSetSampleRate(void *sampleSourcePtr, double sampleRate);
+
+/**
+ * Set the number of channels to be used for raw PCM file operations. Like the
+ * sample, this is most relevant when writing a WAVE or a AIFF file.
+ * @param sampleSourcePtr
+ * @param numChannels Number of channels
+ */
+void sampleSourcePcmSetNumChannels(void *sampleSourcePtr, int numChannels);
+
+/**
+ * Free a PCM sample source and all associated data
+ * @param sampleSourceDataPtr Pointer to sample source data
+ */
+void freeSampleSourceDataPcm(void *sampleSourceDataPtr);
+
+#endif
diff --git a/source/io/SampleSourceSilence.c b/source/io/SampleSourceSilence.c
new file mode 100644
index 0000000..37136e2
--- /dev/null
+++ b/source/io/SampleSourceSilence.c
@@ -0,0 +1,84 @@
+//
+// SampleSourceSilence.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#include "audio/AudioSettings.h"
+#include "io/SampleSource.h"
+#include "logging/EventLogger.h"
+
+static boolByte _openSampleSourceSilence(void *sampleSourcePtr, const SampleSourceOpenAs openAs)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ sampleSource->openedAs = openAs;
+ return true;
+}
+
+static void _closeSampleSourceSilence(void *sampleSourcePtr)
+{
+}
+
+static boolByte _readBlockFromSilence(void *sampleSourcePtr, SampleBuffer sampleBuffer)
+{
+ unsigned long samplesRead = sampleBuffer->blocksize * sampleBuffer->numChannels;
+ sampleBufferClear(sampleBuffer);
+ ((SampleSource)sampleSourcePtr)->numSamplesProcessed += sampleBuffer->blocksize * sampleBuffer->numChannels;
+ logDebug("Read %d samples from silence source", samplesRead);
+ return true;
+}
+
+static boolByte _writeBlockToSilence(void *sampleSourcePtr, const SampleBuffer sampleBuffer)
+{
+ unsigned long samplesWritten = sampleBuffer->blocksize * sampleBuffer->numChannels;
+ ((SampleSource)sampleSourcePtr)->numSamplesProcessed += samplesWritten;
+ logDebug("Wrote %d samples to silence source", samplesWritten);
+ return true;
+}
+
+static void _freeInputSourceDataSilence(void *sampleSourceDataPtr)
+{
+}
+
+SampleSource _newSampleSourceSilence(void)
+{
+ SampleSource sampleSource = (SampleSource)malloc(sizeof(SampleSourceMembers));
+
+ sampleSource->sampleSourceType = SAMPLE_SOURCE_TYPE_SILENCE;
+ sampleSource->openedAs = SAMPLE_SOURCE_OPEN_NOT_OPENED;
+ sampleSource->sourceName = newCharString();
+ charStringCopyCString(sampleSource->sourceName, "(silence)");
+ sampleSource->numSamplesProcessed = 0;
+
+ sampleSource->openSampleSource = _openSampleSourceSilence;
+ sampleSource->closeSampleSource = _closeSampleSourceSilence;
+ sampleSource->readSampleBlock = _readBlockFromSilence;
+ sampleSource->writeSampleBlock = _writeBlockToSilence;
+ sampleSource->freeSampleSourceData = _freeInputSourceDataSilence;
+
+ return sampleSource;
+}
diff --git a/source/io/SampleSourceSilence.h b/source/io/SampleSourceSilence.h
new file mode 100644
index 0000000..d4b793f
--- /dev/null
+++ b/source/io/SampleSourceSilence.h
@@ -0,0 +1,33 @@
+//
+// SampleSourceSilence.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_SampleSourceSilence_h
+#define MrsWatson_SampleSourceSilence_h
+
+// SampleSourceSilent contains only private functions
+
+#endif
diff --git a/source/io/SampleSourceWave.c b/source/io/SampleSourceWave.c
new file mode 100644
index 0000000..6e1442d
--- /dev/null
+++ b/source/io/SampleSourceWave.c
@@ -0,0 +1,499 @@
+//
+// SampleSourceWave.c - MrsWatson
+// Created by Nik Reiman on 1/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "audio/AudioSettings.h"
+#include "base/Endian.h"
+#include "io/RiffFile.h"
+#include "io/SampleSource.h"
+#include "io/SampleSourcePcm.h"
+#include "logging/EventLogger.h"
+
+static boolByte _readWaveFileInfo(const char *filename, SampleSourcePcmData extraData)
+{
+ int chunkOffset = 0;
+ RiffChunk chunk = newRiffChunk();
+ char format[4];
+ size_t itemsRead;
+ unsigned int audioFormat;
+ unsigned int byteRate;
+ unsigned int expectedByteRate;
+ unsigned int blockAlign;
+ unsigned int expectedBlockAlign;
+
+ if (riffChunkReadNext(chunk, extraData->fileHandle, false)) {
+ if (!riffChunkIsIdEqualTo(chunk, "RIFF")) {
+ logFileError(filename, "Invalid RIFF chunk descriptor");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ // The WAVE file format has two sub-chunks, with the size of both calculated in the size field. Before
+ // either of the subchunks, there are an extra 4 bytes which indicate the format type. We need to read
+ // that before either of the subchunks can be parsed.
+ itemsRead = fread(format, sizeof(byte), 4, extraData->fileHandle);
+
+ if (itemsRead != 4 || strncmp(format, "WAVE", 4)) {
+ logFileError(filename, "Invalid format description");
+ freeRiffChunk(chunk);
+ return false;
+ }
+ } else {
+ logFileError(filename, "No chunks following descriptor");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ if (riffChunkReadNext(chunk, extraData->fileHandle, true)) {
+ if (!riffChunkIsIdEqualTo(chunk, "fmt ")) {
+ logError(filename, "Invalid format chunk header");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ audioFormat = convertByteArrayToUnsignedShort(chunk->data + chunkOffset);
+ chunkOffset += 2;
+ if (audioFormat != 1) {
+ logError("WAVE file with audio format %d is not supported", audioFormat);
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ extraData->numChannels = convertByteArrayToUnsignedShort(chunk->data + chunkOffset);
+ chunkOffset += 2;
+ setNumChannels(extraData->numChannels);
+
+ extraData->sampleRate = convertByteArrayToUnsignedInt(chunk->data + chunkOffset);
+ chunkOffset += 4;
+ setSampleRate(extraData->sampleRate);
+
+ byteRate = convertByteArrayToUnsignedInt(chunk->data + chunkOffset);
+ chunkOffset += 4;
+
+ blockAlign = convertByteArrayToUnsignedShort(chunk->data + chunkOffset);
+ chunkOffset += 2;
+
+ extraData->bitsPerSample = convertByteArrayToUnsignedShort(chunk->data + chunkOffset);
+
+ if (extraData->bitsPerSample > 16) {
+ logUnsupportedFeature("Bitrates greater than 16");
+ freeRiffChunk(chunk);
+ return false;
+ } else if (extraData->bitsPerSample < 16) {
+ logUnsupportedFeature("Bitrates lower than 16");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ expectedByteRate = extraData->sampleRate * extraData->numChannels * extraData->bitsPerSample / 8;
+
+ if (expectedByteRate != byteRate) {
+ logWarn("Possibly invalid bitrate %d, expected %d", byteRate, expectedByteRate);
+ }
+
+ expectedBlockAlign = (unsigned int)(extraData->numChannels * extraData->bitsPerSample / 8);
+
+ if (expectedBlockAlign != blockAlign) {
+ logWarn("Possibly invalid block align %d, expected %d", blockAlign, expectedBlockAlign);
+ }
+ } else {
+ logFileError(filename, "WAVE file has no chunks following format");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ // We don't need the format data anymore, so free and re-alloc the chunk to avoid a small memory leak
+ freeRiffChunk(chunk);
+ chunk = newRiffChunk();
+
+ if (riffChunkReadNext(chunk, extraData->fileHandle, false)) {
+ if (!riffChunkIsIdEqualTo(chunk, "data")) {
+ logFileError(filename, "WAVE file has invalid data chunk header");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ logDebug("WAVE file has %d bytes", chunk->size);
+ }
+
+ freeRiffChunk(chunk);
+ return true;
+}
+
+// here it writes wav, sample data is rom SampleSourcePcmData
+//any idea what that is? should be filled somewhere in read or init sections. basically one class to hold any audio data, regardless of format wav\mp3 etc.
+//ok it's basically a struct, right? yes
+//ok how would you debug something like that in C? we can add couple of printf with data from this struct, to see what' going into wav file well we
+//can try it, but better to ask you, how did you find this section of the script? I'm having trouble reading this code because I don't
+//entirely understand how C stuff is organized vs other scripting languages
+//well it's same everywhere, generally each section of program is located in separate file, so for example input output function will be in one file,
+//audio specific will be in another etc. i just checked whole folder structure to get overall picture of program and then can go fro one place to another to
+//see how it works.and mrswatson.c? is that sort of going to be the file that ties all modules together? yep usally called as main.c, but here it's name of program. .
+//and that's common too, right? to name it after the program? yes especially then there is several binaries. ahh like the 64bit version, vs 32bit? yep
+//ok and should I expect some of the modules to pull in modules of their own? yeors ok so first look in main.c, then how did you find this particular function/method?
+//so in main file there was very generic function like writeoutput, but i knew that it should write wav structure somehow, so there should be another module to do that,
+//checked "io" folder and found SOurceSampleWav.c, and then this function in particular. did you see a reference to the io folder in main.c? no just a guess
+//ok so the one thing that makes C a little trickier than python/perl or something is the header files, do you ever look in those? nope, they just help compile,
+//not have much useful ssto uff so the headers can actually be stored in this file as well, it's just an option to keep them separate and make the code shorter?
+//not really, they used in other files, so we don't have to write everytime prototpe of used function in every file which use them....so you're saying the compiler
+//won't compile things if it sees a keyword it doesn't recognize, and that's why it needs to load all headers first? right, either prototype or whole function
+//so the header files are necessary because the script is broken up into different sections, if it was all in one file, it would compile fine? yeah, bt still some issues
+//with order of functions, because functon should be before call to the function. so there would still need to be headers for each keyword at the top of the file? yeah
+//ok this is not as complicated as I thought well lets look briefly at that, and then I think I've learned enough to fiddle with this on my own. Not sure omnisphere itself
+//is worth my time, but mrswatson seems to be good
+//so how do we find SampleSourcePcmData's struct definition? just with grep looks like its all in this file?
+//so we need to find where SampleSourcePcmData is loaded no need, print oh yeah, we know that f write correct data to it terminal so it's onlt in ths function right yes, I remember now.
+//
+//
+//
+//It seems that part of the problem with mrswatson is that the midi files I was feeding it were missing parameters, and mrswatson
+//doesn't have defaults for those ( like tempo, and sampleRate)? yeah ok c couoold bel
+//
+//there is a standard for midi called General Midi, it's a bit weird, I guess I'll read about it some more
+//
+//
+//
+static boolByte _writeWaveFileInfo(SampleSourcePcmData extraData)
+{
+ //so how do we know whether samplerate is something we can print with printf? couldn't that be a struct too? need to check definition too
+ ////how would we access SampleRate here from extraData?
+ // extraData->sampleRate * extraData->numChannels * extraData->bitsPerSample
+ // ok so similar syntax to perl? yep and because you cannot multiply structs, we should just go straight to printf? right
+ printf("SampleRate: %f", extraData->sampleRate); //like that? why can't I cast the value as a string? there is no casts in C for strings and any other types,
+ printf("NumChannels: %d", extraData->numChannels); //like that? why can't I cast the value as a string? there is no casts in C for strings and any other types,
+ //so we should build this now? yep
+
+ RiffChunk chunk = newRiffChunk();
+ unsigned short audioFormat = 1;
+ unsigned int byteRate = extraData->sampleRate * extraData->numChannels * extraData->bitsPerSample / 8;
+ printf("byteRate: %d\n", byteRate); //I should test this next? i guess we need to compare original file from mrswatson and sox fixed file
+ unsigned short blockAlign = (unsigned short)(extraData->numChannels * extraData->bitsPerSample / 8);
+ unsigned int extraParams = 0;
+
+ memcpy(chunk->id, "RIFF", 4); //ok so the 4 here stands for 4 bytes, right? yep what is memcpy doing here exactly? copy from one memory region to another
+ //so it is copying "RIFF" to chunk->id ? yes but it doesn't take the memory address or pointer as an argument? chunk->id is pointer. ok is it a pointer
+ //to the location of the chunk? most likely it's allocated inside newRiffChunk function. code here is a bit junky, could be written better
+ //what if it were memcpy(chunk->id, "RIFF", sizeof("RIFF")); would that be the same? should be, but size of string is larger by one byte for \0, not sure
+ //oh in this case, would it copy only the first 4 bytes of "RIFF"? yes ok
+ //writes the letters "RIFF"
+ if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) {
+ logError("Could not write RIFF header");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ // Write the size, but this will need to be set again when the file is finished writing
+ //chunk->size here is wrong!! FIXME
+ if (fwrite(&(chunk->size), sizeof(unsigned int), 1, extraData->fileHandle) != 1) {
+ logError("Could not write RIFF chunk size");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ memcpy(chunk->id, "WAVE", 4);
+ //writes the letters "WAVE"
+ if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) {
+ logError("Could not WAVE format");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ // Write the format header
+ memcpy(chunk->id, "fmt ", 4);
+ chunk->size = 20;
+
+ //writes the letters "fmt "
+ //0
+ if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) {
+ logError("Could not write format header");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ //This is wrong!! FIXME this didn't line up with that document, but I was a bit confused because there are 6 of these one byte statements, should be four I thought
+ //it's not one byte, siz efof(unsigned int) should be 4 anyway structure of wav is correct, we only need to check sampleRate output well this doesn't line up with
+ //what we got from sox
+ //0
+ if (fwrite(&(chunk->size), sizeof(unsigned int), 1, extraData->fileHandle) != 1) {
+ logError("Could not write format chunk size");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ // NOTE: These calls will not work on big-endian platforms
+ //4
+ if (fwrite(&audioFormat, sizeof(unsigned short), 1, extraData->fileHandle) != 1) {
+ logError("Could not write audio format");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ //6
+ if (fwrite(&(extraData->numChannels), sizeof(unsigned short), 1, extraData->fileHandle) != 1) {
+ logError("Could not write channel count");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ //8
+ int sampleRateInt = (int)extraData->sampleRate; //should we compile it again? not yet
+ if (fwrite(&sampleRateInt, sizeof(unsigned int), 1, extraData->fileHandle) != 1) {
+ logError("Could not write sample rate");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ //12
+ if (fwrite(&(byteRate), sizeof(unsigned int), 1, extraData->fileHandle) != 1) {
+ logError("Could not write byte rate");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ //16
+ if (fwrite(&(blockAlign), sizeof(unsigned short), 1, extraData->fileHandle) != 1) {
+ logError("Could not write block align");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ //18
+ if (fwrite(&(extraData->bitsPerSample), sizeof(unsigned short), 1, extraData->fileHandle) != 1) {
+ logError("Could not write bits per sample");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ if (fwrite(&(extraParams), sizeof(byte), 4, extraData->fileHandle) != 4) {
+ logError("Could not write extra PCM parameters");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ memcpy(chunk->id, "data", 4);// don't see how can data not be written into fe, weird zeroes.
+
+ if (fwrite(chunk->id, sizeof(byte), 4, extraData->fileHandle) != 4) {
+ logError("Could not write format header");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ if (fwrite(&(chunk->size), sizeof(unsigned int), 1, extraData->fileHandle) != 1) {
+ logError("Could not write data chunk size");
+ freeRiffChunk(chunk);
+ return false;
+ }
+
+ freeRiffChunk(chunk);
+ return true;
+}
+
+static boolByte _openSampleSourceWave(void *sampleSourcePtr, const SampleSourceOpenAs openAs)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+
+ if (openAs == SAMPLE_SOURCE_OPEN_READ) {
+ extraData->fileHandle = fopen(sampleSource->sourceName->data, "rb");
+
+ if (extraData->fileHandle != NULL) {
+ if (_readWaveFileInfo(sampleSource->sourceName->data, extraData)) {
+ setNumChannels(extraData->numChannels);
+ setSampleRate(extraData->sampleRate);
+ } else {
+ fclose(extraData->fileHandle);
+ extraData->fileHandle = NULL;
+ }
+ }
+ } else if (openAs == SAMPLE_SOURCE_OPEN_WRITE) {
+ extraData->fileHandle = fopen(sampleSource->sourceName->data, "wb");
+
+ if (extraData->fileHandle != NULL) {
+ extraData->numChannels = (unsigned short)getNumChannels();
+ extraData->sampleRate = (unsigned int)getSampleRate();
+ extraData->bitsPerSample = 16;
+
+ if (!_writeWaveFileInfo(extraData)) {
+ fclose(extraData->fileHandle);
+ extraData->fileHandle = NULL;
+ }
+ }
+ } else {
+ logInternalError("Invalid type for openAs in WAVE file");
+ return false;
+ }
+
+ if (extraData->fileHandle == NULL) {
+ logError("WAVE file '%s' could not be opened for %s",
+ sampleSource->sourceName->data, openAs == SAMPLE_SOURCE_OPEN_READ ? "reading" : "writing");
+ return false;
+ }
+
+ sampleSource->openedAs = openAs;
+ return true;
+}
+
+static boolByte _readBlockFromWaveFile(void *sampleSourcePtr, SampleBuffer sampleBuffer)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+ unsigned long originalBlocksize = sampleBuffer->blocksize;
+ size_t samplesRead = sampleSourcePcmRead(extraData, sampleBuffer);
+ sampleSource->numSamplesProcessed += samplesRead;
+ return (boolByte)(originalBlocksize == sampleBuffer->blocksize);
+}
+
+static boolByte _writeBlockToWaveFile(void *sampleSourcePtr, const SampleBuffer sampleBuffer)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourcePtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+ unsigned int samplesWritten = (int)sampleSourcePcmWrite(extraData, sampleBuffer);
+ sampleSource->numSamplesProcessed += samplesWritten;
+ return (boolByte)(samplesWritten == sampleBuffer->blocksize);
+}
+
+void _closeSampleSourceWave(void *sampleSourceDataPtr)
+{
+ SampleSource sampleSource = (SampleSource)sampleSourceDataPtr;
+ SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
+ size_t numBytesWritten;
+ RiffChunk chunk;
+
+ if (sampleSource->openedAs == SAMPLE_SOURCE_OPEN_WRITE) {
+ // Re-open the file for editing
+ fflush(extraData->fileHandle);
+
+ if (fclose(extraData->fileHandle) != 0) {
+ logError("Could not close WAVE file for finalization");
+ return;
+ }
+
+ extraData->fileHandle = fopen(sampleSource->sourceName->data, "rb+");
+
+ if (extraData->fileHandle == NULL) {
+ logError("Could not reopen WAVE file for finalization");
+ return;
+ }
+
+ // First go to the second chunk in the file and re-read the chunk length //is it possible that it is altered again at the end? yep
+ if (fseek(extraData->fileHandle, 12, SEEK_SET) != 0) {
+ logError("Could not seek to second chunk during WAVE file finalization");
+ fclose(extraData->fileHandle);
+ return;
+ }
+
+ chunk = newRiffChunk();
+
+ if (!riffChunkReadNext(chunk, extraData->fileHandle, false)) {
+ logError("Could not read RIFF chunk during WAVE file finalization");
+ fclose(extraData->fileHandle);
+ freeRiffChunk(chunk);
+ return;
+ }
+
+ // Go to the next chunk, and then skip the type and write the new length //should I test it again? not yet, still need to fix this function code
+ // should we go back and undo some of the things from before? no need, it looks closer to sox output each time
+ // need to find out which line of code writes 000000 instead of data, try commenting out fwrites here and compile
+ // so this one fix 1400 0000 part anything else we need?
+ if (fseek(extraData->fileHandle, (long)(chunk->size + 4), SEEK_CUR) != 0) {
+ logError("Could not seek to next chunk during WAVE file finalization");
+ fclose(extraData->fileHandle);
+ freeRiffChunk(chunk);
+ return;
+ }
+
+ numBytesWritten = sampleSource->numSamplesProcessed * extraData->bitsPerSample / 8;
+
+ if (fwrite(&numBytesWritten, sizeof(unsigned int), 1, extraData->fileHandle) != 1) { //somwhere here it get overwritten
+ logError("Could not write WAVE file size during finalization");
+ fclose(extraData->fileHandle);
+ freeRiffChunk(chunk);
+ return;
+ }
+//ok should be fine now
+ // ok, I should also write a shorter midi file for us to use for testing. I should be able to modifiy this one and take out a lot of the note events.ok
+// should I do that now? (in a different screen?) yeah
+ // if I write this comment, should it work? might not, depends on how cmake is smart
+
+ // Add 40 bytes for fmt chunk size and write the RIFF chunk size
+ numBytesWritten += ftell(extraData->fileHandle) - 8;
+
+ if (fseek(extraData->fileHandle, 4, SEEK_SET) != 0) {
+ logError("Could not seek to fmt chunk during WAVE file finalization");
+ fclose(extraData->fileHandle);
+ freeRiffChunk(chunk);
+ return;
+ }
+
+ if (fwrite(&numBytesWritten, sizeof(unsigned int), 1, extraData->fileHandle) != 1) {
+ logError("Could not write WAVE file size in fmt chunk during finalization");
+ fclose(extraData->fileHandle);
+ freeRiffChunk(chunk);
+ return;
+ }
+
+ fflush(extraData->fileHandle);
+ fclose(extraData->fileHandle);
+ freeRiffChunk(chunk);
+ } else if (sampleSource->openedAs == SAMPLE_SOURCE_OPEN_READ && extraData->fileHandle != NULL) {
+ fclose(extraData->fileHandle);
+ }
+}
+
+SampleSource _newSampleSourceWave(const CharString sampleSourceName)
+{
+ SampleSource sampleSource = (SampleSource)malloc(sizeof(SampleSourceMembers));
+ SampleSourcePcmData extraData = (SampleSourcePcmData)malloc(sizeof(SampleSourcePcmDataMembers));
+
+ sampleSource->sampleSourceType = SAMPLE_SOURCE_TYPE_WAVE;
+ sampleSource->openedAs = SAMPLE_SOURCE_OPEN_NOT_OPENED;
+ sampleSource->sourceName = newCharString();
+ charStringCopy(sampleSource->sourceName, sampleSourceName);
+ sampleSource->numSamplesProcessed = 0;
+
+ sampleSource->openSampleSource = _openSampleSourceWave;
+ sampleSource->readSampleBlock = _readBlockFromWaveFile;
+ sampleSource->writeSampleBlock = _writeBlockToWaveFile;
+ sampleSource->closeSampleSource = _closeSampleSourceWave;
+ sampleSource->freeSampleSourceData = freeSampleSourceDataPcm;
+
+ extraData->isStream = false;
+ extraData->isLittleEndian = true;
+ extraData->fileHandle = NULL;
+ extraData->dataBufferNumItems = 0;
+ extraData->interlacedPcmDataBuffer = NULL;
+
+ extraData->numChannels = (unsigned short)getNumChannels();
+ extraData->sampleRate = (unsigned int)getSampleRate();
+ extraData->bitsPerSample = 16;
+
+ sampleSource->extraData = extraData;
+ return sampleSource;
+}
diff --git a/source/io/SampleSourceWave.h b/source/io/SampleSourceWave.h
new file mode 100644
index 0000000..102fd5e
--- /dev/null
+++ b/source/io/SampleSourceWave.h
@@ -0,0 +1,33 @@
+//
+// SampleSourceWave.h - MrsWatson
+// Created by Nik Reiman on 1/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_SampleSourceWave_h
+#define MrsWatson_SampleSourceWave_h
+
+// SampleSourceWave has only private functions
+
+#endif
diff --git a/source/logging/ErrorReporter.c b/source/logging/ErrorReporter.c
new file mode 100644
index 0000000..29f6ac7
--- /dev/null
+++ b/source/logging/ErrorReporter.c
@@ -0,0 +1,286 @@
+//
+// ErrorReport.c - MrsWatson
+// Created by Nik Reiman on 9/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "app/BuildInfo.h"
+#include "base/File.h"
+#include "base/PlatformInfo.h"
+#include "logging/ErrorReporter.h"
+#include "logging/EventLogger.h"
+
+#if WINDOWS
+#include <Shlobj.h>
+#endif
+
+static const char *kErrorReportInfoText = "MrsWatson is now running \
+in error report mode, which will generate a report on your desktop with \
+any input/output sources and error logs. This also enables some extra \
+arguments and will disable console logging.\n";
+static const char *kErrorReportCopyPluginsPromptText = "Would you like to copy the \
+plugin to the report? All data sent to the official support address is kept \
+strictly confidential. If the plugin in question has copy protection (or is \
+cracked), or depends on external resources, this probably won't work. But if \
+the plugin can be copied, it greatly helps in fixing bugs.\n\
+Copy the plugin? (y/n) ";
+
+
+ErrorReporter newErrorReporter(void)
+{
+ ErrorReporter errorReporter = (ErrorReporter)malloc(sizeof(ErrorReporterMembers));
+
+ errorReporter->started = false;
+ errorReporter->completed = false;
+ errorReporter->reportName = newCharString();
+
+ errorReporter->desktopPath = newCharString();
+ errorReporter->reportDirPath = newCharString();
+
+ return errorReporter;
+}
+
+void errorReporterInitialize(ErrorReporter self)
+{
+ CharString infoText = newCharStringWithCString(kErrorReportInfoText);
+ CharString wrappedInfoText;
+ time_t now;
+ size_t length;
+ size_t i;
+
+ printf("=== Starting error report ===\n");
+ wrappedInfoText = charStringWrap(infoText, 0);
+ // The second newline here is intentional
+ printf("%s\n", wrappedInfoText->data);
+
+ time(&now);
+ self->started = true;
+
+ snprintf(self->reportName->data, self->reportName->capacity,
+ "MrsWatson Report %s", ctime(&now));
+ // Trim the final newline character from this string if it exists
+ length = strlen(self->reportName->data);
+
+ if (self->reportName->data[length - 1] == '\n') {
+ self->reportName->data[length - 1] = '\0';
+ length--;
+ }
+
+ for (i = 0; i < length; i++) {
+ if (!(charStringIsLetter(self->reportName, i) ||
+ charStringIsNumber(self->reportName, i))) {
+ self->reportName->data[i] = '-';
+ }
+ }
+
+#if UNIX
+ snprintf(self->desktopPath->data, self->desktopPath->capacity,
+ "%s/Desktop", getenv("HOME"));
+#elif WINDOWS
+ SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, self->desktopPath->data);
+#endif
+
+ // Try to place the report on the user's desktop. However, if we cannot find
+ // the desktop (which may occur with localized Linux installations, for instance),
+ // then just dump it in the current directory instead.
+ File desktopPath = newFileWithPath(self->desktopPath);
+ File reportPath;
+
+ if (!fileExists(desktopPath)) {
+ logWarn("Could not find desktop location, placing error report in current directory instead");
+ CharString currentDirString = fileGetCurrentDirectory();
+ File currentDir = newFileWithPath(currentDirString);
+ reportPath = newFileWithParent(currentDir, self->reportName);
+ freeFile(currentDir);
+ freeCharString(currentDirString);
+ } else {
+ reportPath = newFileWithParent(desktopPath, self->reportName);
+ freeFile(desktopPath);
+ }
+
+ if (fileExists(reportPath)) {
+ logCritical("The path '%s' already contains a previous error report. Please remove the report data and try again.");
+ } else {
+ fileCreate(reportPath, kFileTypeDirectory);
+ }
+
+ // Now we should have a real error report path
+ charStringCopy(self->reportDirPath, reportPath->absolutePath);
+
+ freeFile(reportPath);
+ freeCharString(wrappedInfoText);
+ freeCharString(infoText);
+}
+
+void errorReporterCreateLauncher(ErrorReporter self, int argc, char *argv[])
+{
+ CharString outScriptName = newCharString();
+ FILE *scriptFilePointer;
+ int i;
+
+ charStringCopyCString(outScriptName, "run.sh");
+ errorReporterRemapPath(self, outScriptName);
+ scriptFilePointer = fopen(outScriptName->data, "w");
+ fprintf(scriptFilePointer, "#!/bin/sh\n");
+ fprintf(scriptFilePointer, "mrswatson");
+
+ for (i = 1; i < argc; i++) {
+ // Don't run with the error report option again
+ if (strcmp(argv[i], "--error-report")) {
+ fprintf(scriptFilePointer, " %s", argv[i]);
+ }
+ }
+
+ fprintf(scriptFilePointer, "\n");
+ fclose(scriptFilePointer);
+}
+
+void errorReporterRemapPath(ErrorReporter self, CharString path)
+{
+ File pathAsFile = newFileWithPath(path);
+ CharString basename = fileGetBasename(pathAsFile);
+ File parent = newFileWithPath(self->reportDirPath);
+ File remappedPath = newFileWithParent(parent, basename);
+
+ charStringCopy(path, remappedPath->absolutePath);
+
+ freeCharString(basename);
+ freeFile(parent);
+ freeFile(pathAsFile);
+ freeFile(remappedPath);
+}
+
+boolByte errorReportCopyFileToReport(ErrorReporter self, CharString path)
+{
+ boolByte success;
+
+ // Copy the destination path so that the original is not modified
+ CharString destination = newCharString();
+ charStringCopy(destination, path);
+ errorReporterRemapPath(self, destination);
+
+ File reportDirPath = newFileWithPath(self->reportDirPath);
+ File distinationPath = newFileWithPath(path);
+ File result = fileCopyTo(distinationPath, reportDirPath);
+ success = fileExists(result);
+
+ freeCharString(destination);
+ freeFile(reportDirPath);
+ freeFile(distinationPath);
+ freeFile(result);
+ return success;
+}
+
+// This could live in File, however this is currently the only place it is being used
+// and also it's a rather cheap hack, so I would prefer to keep it as a static function
+// here until another use-case presents itself. If that should happen, then we should
+// refactor this code properly and move it to File.
+static boolByte _copyDirectoryToErrorReportDir(ErrorReporter self, CharString path)
+{
+ boolByte success;
+
+#if UNIX
+ int result;
+ CharString copyCommand = newCharString();
+ // TODO: This is the lazy way of doing this...
+ snprintf(copyCommand->data, copyCommand->capacity, "/bin/cp -r \"%s\" \"%s\"",
+ path->data, self->reportDirPath->data);
+ result = system(copyCommand->data);
+ success = (boolByte)(WEXITSTATUS(result) == 0);
+
+ if (!success) {
+ logError("Could not copy '%s' to '%s'\n", path->data, self->reportDirPath->data);
+ }
+
+#else
+ logUnsupportedFeature("Copy directory recursively");
+ success = false;
+#endif
+
+ return success;
+}
+
+boolByte errorReporterShouldCopyPlugins(void)
+{
+ CharString promptText = newCharStringWithCString(kErrorReportCopyPluginsPromptText);
+ CharString wrappedPromptText;
+ char response;
+
+ wrappedPromptText = charStringWrap(promptText, 0);
+ printf("%s", wrappedPromptText->data);
+ freeCharString(wrappedPromptText);
+ freeCharString(promptText);
+
+ response = (char)getchar();
+ return (boolByte)(response == 'y' || response == 'Y');
+}
+
+boolByte errorReporterCopyPlugins(ErrorReporter self, PluginChain pluginChain)
+{
+ CharString pluginAbsolutePath = NULL;
+ Plugin currentPlugin = NULL;
+ boolByte failed = false;
+ unsigned int i;
+ PlatformInfo platform = newPlatformInfo();
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ currentPlugin = pluginChain->plugins[i];
+ pluginAbsolutePath = currentPlugin->pluginAbsolutePath;
+
+ if (charStringIsEmpty(pluginAbsolutePath)) {
+ logInfo("Plugin '%s' does not have an absolute path and could not be copied", currentPlugin->pluginName->data);
+ } else if (platform->type == PLATFORM_MACOSX) {
+ failed |= !_copyDirectoryToErrorReportDir(self, pluginAbsolutePath);
+ } else {
+ failed |= !errorReportCopyFileToReport(self, pluginAbsolutePath);
+ }
+ }
+
+ freePlatformInfo(platform);
+ return (boolByte)!failed;
+}
+
+void errorReporterClose(ErrorReporter self)
+{
+ // Always do this, just in case
+ flushErrorLog();
+
+ printf("\n=== Error report complete ===\n");
+ printf("Created error report at %s\n", self->reportDirPath->data);
+ printf("Please compress and email the report to: %s\n", SUPPORT_EMAIL);
+ printf("Thanks!\n");
+}
+
+void freeErrorReporter(ErrorReporter errorReporter)
+{
+ freeCharString(errorReporter->reportName);
+ freeCharString(errorReporter->reportDirPath);
+ freeCharString(errorReporter->desktopPath);
+ free(errorReporter);
+}
diff --git a/source/logging/ErrorReporter.h b/source/logging/ErrorReporter.h
new file mode 100644
index 0000000..74b4012
--- /dev/null
+++ b/source/logging/ErrorReporter.h
@@ -0,0 +1,111 @@
+//
+// ErrorReporter.h - MrsWatson
+// Created by Nik Reiman on 9/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_ErrorReporter_h
+#define MrsWatson_ErrorReporter_h
+
+#include "base/CharString.h"
+#include "plugin/PluginChain.h"
+
+typedef struct {
+ CharString reportName;
+ CharString reportDirPath;
+ CharString desktopPath;
+ boolByte started;
+ boolByte completed;
+} ErrorReporterMembers;
+typedef ErrorReporterMembers *ErrorReporter;
+
+/**
+ * Create a new ErrorReporter
+ * @return ErrorReporter object
+ */
+ErrorReporter newErrorReporter(void);
+
+/**
+ * Create the necessary resources used by the ErrorReporter. This is not done
+ * during object construction because large files can be copied and interactive
+ * input requested from the user.
+ * @param self
+ */
+void errorReporterInitialize(ErrorReporter self);
+
+/**
+ * Create a shell script that can be used to launch MrsWatson with the same
+ * arguments which caused the error to happen
+ * @param self
+ * @param argc Number of arguments, as taken from main()
+ * @param argv Argument array, as taken from main()
+ */
+void errorReporterCreateLauncher(ErrorReporter self, int argc, char *argv[]);
+
+/**
+ * Remap a resource to point to the ErrorReporter's directory. This ensures all
+ * resources are contained within the same folder, and can be easily compressed
+ * for sending after the program finishes executing.
+ * @param self
+ * @param path Input path to be remapped
+ */
+void errorReporterRemapPath(ErrorReporter self, CharString path);
+
+/**
+ * Copy a file to the ErrorReporter's directory
+ * @param self
+ * @param path File to copy
+ * @return True if the file successfully copied
+ */
+boolByte errorReportCopyFileToReport(ErrorReporter self, CharString path);
+
+/**
+ * Ask the user if plugins should be copied to the report directory in addition
+ * to other files. Because there can be copy protection issues, not to mention
+ * file sizes, it is better to ask the user before copying the plugins.
+ * @return True if the user wishes to copy all plugins
+ */
+boolByte errorReporterShouldCopyPlugins(void);
+
+/**
+ * Copy all plug-ins to the ErrorReporter directory
+ * @param self
+ * @param pluginChain Initialized plugin chain
+ * @return True if all plugins were copied
+ */
+boolByte errorReporterCopyPlugins(ErrorReporter self, PluginChain pluginChain);
+
+/**
+ * Close any resources associated with the ErrorReporter
+ * @param self
+ */
+void errorReporterClose(ErrorReporter self);
+
+/**
+ * Free all memory associated with an ErrorReporter instance
+ * @param self
+ */
+void freeErrorReporter(ErrorReporter self);
+
+#endif
diff --git a/source/logging/EventLogger.c b/source/logging/EventLogger.c
new file mode 100644
index 0000000..dc9b6a9
--- /dev/null
+++ b/source/logging/EventLogger.c
@@ -0,0 +1,398 @@
+//
+// EventLogger.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "app/BuildInfo.h"
+#include "audio/AudioSettings.h"
+#include "logging/EventLogger.h"
+#include "logging/LogPrinter.h"
+#include "time/AudioClock.h"
+
+#include "MrsWatson.h"
+
+#if WINDOWS
+#include <Windows.h>
+#include <io.h>
+#elif UNIX
+#include <unistd.h>
+#endif
+
+EventLogger eventLoggerInstance = NULL;
+
+void initEventLogger(void)
+{
+#if WINDOWS
+ ULONGLONG currentTime;
+#else
+ struct timeval currentTime;
+#endif
+
+ eventLoggerInstance = (EventLogger)malloc(sizeof(EventLoggerMembers));
+ eventLoggerInstance->logLevel = LOG_INFO;
+ eventLoggerInstance->logFile = NULL;
+ eventLoggerInstance->useColor = false;
+ eventLoggerInstance->zebraStripeSize = (unsigned long)DEFAULT_SAMPLE_RATE;
+ eventLoggerInstance->systemErrorMessage = NULL;
+
+#if WINDOWS
+ currentTime = GetTickCount();
+ eventLoggerInstance->startTimeInSec = (unsigned long)(currentTime / 1000);
+ eventLoggerInstance->startTimeInMs = (unsigned long)currentTime;
+#else
+ gettimeofday(&currentTime, NULL);
+ eventLoggerInstance->startTimeInSec = currentTime.tv_sec;
+ eventLoggerInstance->startTimeInMs = currentTime.tv_usec / 1000;
+#endif
+
+ if (isatty(1)) {
+ eventLoggerInstance->useColor = true;
+ }
+}
+
+static EventLogger _getEventLoggerInstance(void)
+{
+ return eventLoggerInstance;
+}
+
+char *stringForLastError(int errorNumber)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+
+ if (eventLogger->systemErrorMessage == NULL) {
+ eventLogger->systemErrorMessage = newCharString();
+ }
+
+#if UNIX
+ return strerror(errorNumber);
+#elif WINDOWS
+ FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, errorNumber, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ eventLogger->systemErrorMessage->data, eventLogger->systemErrorMessage->capacity - 1, NULL);
+ return eventLogger->systemErrorMessage->data;
+#endif
+
+}
+
+boolByte isLogLevelAtLeast(LogLevel logLevel)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ return (boolByte)(logLevel >= eventLogger->logLevel);
+}
+
+void setLogLevel(LogLevel logLevel)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->logLevel = logLevel;
+}
+
+void setLogLevelFromString(const CharString logLevelString)
+{
+ if (charStringIsEqualToCString(logLevelString, "debug", true)) {
+ setLogLevel(LOG_DEBUG);
+ } else if (charStringIsEqualToCString(logLevelString, "info", true)) {
+ setLogLevel(LOG_INFO);
+ } else if (charStringIsEqualToCString(logLevelString, "warn", true)) {
+ setLogLevel(LOG_WARN);
+ } else if (charStringIsEqualToCString(logLevelString, "error", true)) {
+ setLogLevel(LOG_ERROR);
+ } else {
+ logCritical("Invalid log level '%s', see '--help full' for valid arguments", logLevelString->data);
+ }
+}
+
+void setLogFile(const CharString logFileName)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->logFile = fopen(logFileName->data, "a");
+
+ if (eventLogger->logFile == NULL) {
+ logCritical("Could not open file '%s' for logging", logFileName->data);
+ } else {
+ eventLogger->useColor = false;
+ }
+}
+
+void setLoggingColorEnabled(boolByte useColor)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->useColor = useColor;
+}
+
+void setLoggingColorEnabledWithString(const CharString colorSchemeName)
+{
+ if (charStringIsEmpty(colorSchemeName)) {
+ setLoggingColorEnabled(false);
+ } else if (charStringIsEqualToCString(colorSchemeName, "none", false)) {
+ setLoggingColorEnabled(false);
+ } else if (charStringIsEqualToCString(colorSchemeName, "auto", false)) {
+ setLoggingColorEnabled((boolByte)isatty(1));
+ } else if (charStringIsEqualToCString(colorSchemeName, "force", false)) {
+ setLoggingColorEnabled(true);
+ } else {
+ // Use critical log level to avoid colors
+ logCritical("Unknown color scheme '%s'", colorSchemeName->data);
+ }
+}
+
+void setLoggingZebraSize(const unsigned long zebraStripeSize)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->zebraStripeSize = zebraStripeSize;
+}
+
+static char _logLevelStatusChar(const LogLevel logLevel)
+{
+ switch (logLevel) {
+ case LOG_DEBUG:
+ return 'D';
+
+ case LOG_INFO:
+ return '-';
+
+ case LOG_WARN:
+ return 'W';
+
+ case LOG_ERROR:
+ return 'E';
+
+ default:
+ return '!';
+ }
+}
+
+static LogColor _logLevelStatusColor(const LogLevel logLevel)
+{
+ switch (logLevel) {
+ case LOG_DEBUG:
+ return COLOR_FG_DKGRAY;
+
+ case LOG_INFO:
+ return COLOR_RESET;
+
+ case LOG_WARN:
+ return COLOR_FG_YELLOW;
+
+ case LOG_ERROR:
+ return COLOR_FG_RED;
+
+ default:
+ return COLOR_RESET;
+ }
+}
+
+static LogColor _logTimeColor(void)
+{
+ return COLOR_FG_CYAN;
+}
+
+static LogColor _logTimeZebraStripeColor(const long elapsedTime, const unsigned long zebraSizeInMs)
+{
+ boolByte zebraState = (boolByte)((elapsedTime / zebraSizeInMs) % 2);
+ return zebraState ? COLOR_FG_OLIVE : COLOR_FG_GREEN;
+}
+
+static void _printMessage(const LogLevel logLevel, const long elapsedTimeInMs, const long numFramesProcessed, const char *message, const EventLogger eventLogger)
+{
+ char *logString = (char *)malloc(sizeof(char) * kCharStringLengthLong);
+
+ if (eventLogger->useColor) {
+ snprintf(logString, kCharStringLengthLong, "%c ", _logLevelStatusChar(logLevel));
+ printToLog(_logLevelStatusColor(logLevel), eventLogger->logFile, logString);
+ snprintf(logString, kCharStringLengthLong, "%08ld ", numFramesProcessed);
+ printToLog(_logTimeZebraStripeColor(numFramesProcessed, eventLogger->zebraStripeSize),
+ eventLogger->logFile, logString);
+ snprintf(logString, kCharStringLengthLong, "%06ld ", elapsedTimeInMs);
+ printToLog(_logTimeColor(), eventLogger->logFile, logString);
+ printToLog(_logLevelStatusColor(logLevel), eventLogger->logFile, message);
+ } else {
+ snprintf(logString, kCharStringLengthLong, "%c %08ld %06ld %s", _logLevelStatusChar(logLevel), numFramesProcessed, elapsedTimeInMs, message);
+ printToLog(COLOR_NONE, eventLogger->logFile, logString);
+ }
+
+ flushLog(eventLogger->logFile);
+ free(logString);
+}
+
+static void _logMessage(const LogLevel logLevel, const char *message, va_list arguments)
+{
+ long elapsedTimeInMs;
+ EventLogger eventLogger = _getEventLoggerInstance();
+#if WINDOWS
+ ULONGLONG currentTime;
+#else
+ struct timeval currentTime;
+#endif
+
+ if (eventLogger != NULL && logLevel >= eventLogger->logLevel) {
+ CharString formattedMessage = newCharStringWithCapacity(kCharStringLengthDefault * 2);
+ vsnprintf(formattedMessage->data, formattedMessage->capacity, message, arguments);
+#if WINDOWS
+ currentTime = GetTickCount();
+ elapsedTimeInMs = (unsigned long)(currentTime - eventLogger->startTimeInMs);
+#else
+ gettimeofday(&currentTime, NULL);
+ elapsedTimeInMs = ((currentTime.tv_sec - (eventLogger->startTimeInSec + 1)) * 1000) +
+ (currentTime.tv_usec / 1000) + (1000 - eventLogger->startTimeInMs);
+#endif
+ _printMessage(logLevel, elapsedTimeInMs, getAudioClock()->currentFrame, formattedMessage->data, eventLogger);
+ freeCharString(formattedMessage);
+ }
+}
+
+void logDebug(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_DEBUG, message, arguments);
+ va_end(arguments);
+}
+
+void logInfo(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_INFO, message, arguments);
+ va_end(arguments);
+}
+
+void logWarn(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_WARN, message, arguments);
+ va_end(arguments);
+}
+
+void logError(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_ERROR, message, arguments);
+ va_end(arguments);
+}
+
+void logCritical(const char *message, ...)
+{
+ va_list arguments;
+ CharString formattedMessage = newCharString();
+ CharString wrappedMessage;
+
+ va_start(arguments, message);
+ // Instead of going through the common logging method, we always dump critical
+ // messages to stderr
+ vsnprintf(formattedMessage->data, formattedMessage->capacity, message, arguments);
+ wrappedMessage = charStringWrap(formattedMessage, 0);
+ fprintf(stderr, "ERROR: %s\n", wrappedMessage->data);
+
+ if (eventLoggerInstance != NULL && eventLoggerInstance->logFile != NULL) {
+ fprintf(eventLoggerInstance->logFile, "ERROR: %s\n", wrappedMessage->data);
+ }
+
+ freeCharString(formattedMessage);
+ freeCharString(wrappedMessage);
+ va_end(arguments);
+}
+
+void logInternalError(const char *message, ...)
+{
+ va_list arguments;
+ CharString formattedMessage = newCharString();
+
+ va_start(arguments, message);
+ // Instead of going through the common logging method, we always dump critical messages to stderr
+ vsnprintf(formattedMessage->data, formattedMessage->capacity, message, arguments);
+ fprintf(stderr, "INTERNAL ERROR: %s\n", formattedMessage->data);
+
+ if (eventLoggerInstance != NULL && eventLoggerInstance->logFile != NULL) {
+ fprintf(eventLoggerInstance->logFile, "INTERNAL ERROR: %s\n", formattedMessage->data);
+ }
+
+ freeCharString(formattedMessage);
+ va_end(arguments);
+
+ fprintf(stderr, " This should not have happened. Please take a minute to report a bug.\n");
+ fprintf(stderr, " Support website: %s\n", SUPPORT_WEBSITE);
+ fprintf(stderr, " Support email: %s\n", SUPPORT_EMAIL);
+}
+
+void logUnsupportedFeature(const char *featureName)
+{
+ fprintf(stderr, "UNSUPPORTED FEATURE: %s\n", featureName);
+ fprintf(stderr, " This feature is not yet supported. Please help us out and submit a patch! :)\n");
+ fprintf(stderr, " Project website: %s\n", PROJECT_WEBSITE);
+ fprintf(stderr, " Support email: %s\n", SUPPORT_EMAIL);
+}
+
+void logDeprecated(const char *functionName, const char *plugin)
+{
+ logWarn("Call to deprecated function '%s' made by plugin '%s'", functionName, plugin);
+}
+
+void logFileError(const char *filename, const char *message)
+{
+ logCritical("Could not parse file '%s'", filename);
+ logCritical("Got error message message: %s", message);
+ logPossibleBug("This file is either corrupt or was parsed incorrectly");
+}
+
+void logPossibleBug(const char *cause)
+{
+ CharString extraText = newCharStringWithCString("If you believe this to be a \
+bug in MrsWatson, please re-run the program with the --error-report option to \
+generate a diagnostic report to send to support.");
+ CharString wrappedCause;
+ CharString wrappedExtraText;
+
+ wrappedCause = charStringWrap(newCharStringWithCString(cause), 0);
+ wrappedExtraText = charStringWrap(extraText, 0);
+ fprintf(stderr, "%s\n", wrappedCause->data);
+ fprintf(stderr, "%s\n", wrappedExtraText->data);
+
+ freeCharString(wrappedCause);
+ freeCharString(wrappedExtraText);
+}
+
+void flushErrorLog(void)
+{
+ if (eventLoggerInstance != NULL && eventLoggerInstance->logFile != NULL) {
+ fflush(eventLoggerInstance->logFile);
+ }
+}
+
+void freeEventLogger(void)
+{
+ if (eventLoggerInstance->logFile != NULL) {
+ fclose(eventLoggerInstance->logFile);
+ }
+
+ freeCharString(eventLoggerInstance->systemErrorMessage);
+ free(eventLoggerInstance);
+ eventLoggerInstance = NULL;
+}
diff --git a/source/logging/EventLogger.h b/source/logging/EventLogger.h
new file mode 100644
index 0000000..ebc294a
--- /dev/null
+++ b/source/logging/EventLogger.h
@@ -0,0 +1,219 @@
+//
+// EventLogger.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_EventLogger_h
+#define MrsWatson_EventLogger_h
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "base/CharString.h"
+#include "base/Types.h"
+
+typedef enum {
+ LOG_DEBUG,
+ LOG_INFO,
+ LOG_WARN,
+ LOG_ERROR,
+ NUM_LOG_LEVELS
+} LogLevel;
+
+typedef struct {
+ LogLevel logLevel;
+ long startTimeInSec;
+ long startTimeInMs;
+ boolByte useColor;
+ unsigned long zebraStripeSize;
+ FILE *logFile;
+ CharString systemErrorMessage;
+} EventLoggerMembers;
+typedef EventLoggerMembers *EventLogger;
+extern EventLogger eventLoggerInstance;
+
+/**
+ * Initialize the global event logger instance. Unlike other classes, the event
+ * logger exists as a global singleton, since it is called from numerous places
+ * throughout the code base.
+ */
+void initEventLogger(void);
+
+/**
+ * Get a descriptive error message from the operating system.
+ * @param errorNumber Error code from the operating system
+ * @return String description of the error code
+ */
+char *stringForLastError(int errorNumber);
+
+/**
+ * Check if the current log level is at a given level or higher. This is useful
+ * to avoid doing work to generate logging messages which would not actually
+ * be seen by the user.
+ * @param logLevel Target log level
+ * @return True if the logging level is at this level or higher
+ */
+boolByte isLogLevelAtLeast(LogLevel logLevel);
+
+/**
+ * Setup the logging level to be used
+ * @param logLevel Log level
+ */
+void setLogLevel(LogLevel logLevel);
+
+/**
+ * Set the logging level from a human readable string
+ * @param logLevelString Log level as a string. If they given string does not
+ * match any log level, no change is made.
+ */
+void setLogLevelFromString(const CharString logLevelString);
+
+/**
+ * Send all logging outputs tree files instead of standard error
+ * @param logFileName File name to log to. The file will be opened for appending
+ * text, it will not be overwritten.
+ */
+void setLogFile(const CharString logFileName);
+
+/**
+ * Enable or disable the use of color for console logging. By default, the
+ * EventLogger will not use colors if outputting to a non-interactive terminal.
+ * @param useColor True if color should be used
+ */
+void setLoggingColorEnabled(boolByte useColor);
+
+/**
+ * Set the color scheme to be used with a string name. If no valid name is
+ * passed to this method, no action will be taken.
+ * @param colorSchemeName Color scheme string
+ */
+void setLoggingColorEnabledWithString(const CharString colorSchemeName);
+
+/**
+ * When colored logging is enabled, MrsWatson uses different color shades for
+ * the current sample number in the log output. By default, the colors alternate
+ * every one second of processed audio. This makes it much easier to follow the
+ * logging output, and to find a given event in a very large stream of text.
+ * This method allows one to set how often the colors should alternate, instead
+ * of the default rate of one second.
+ * @param zebraStripeSize How long each color should be displayed, in sample
+ * frames
+ */
+void setLoggingZebraSize(const unsigned long zebraStripeSize);
+
+/**
+ * Log a debug message.
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logDebug(const char *message, ...);
+
+/**
+ * Log a message with regular priority
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logInfo(const char *message, ...);
+
+/**
+ * Log a warning message
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logWarn(const char *message, ...);
+
+/**
+ * Log an error message
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logError(const char *message, ...);
+
+/**
+ * Log a severe error message. Unlike logError, this method does not use colors
+ * or check the log level. It is reserved for messages which must be shown to
+ * the user at all costs.
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logCritical(const char *message, ...);
+
+/**
+ * Display a message to the user indicating that something terribly wrong has
+ * occurred within the program. This call should be reserved for errors which
+ * are not expected under normal circumstances.
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logInternalError(const char *message, ...);
+
+/**
+ * Display a message to the user indicating that they have tried to use part of
+ * the program which is incomplete. For example, plugin to host callbacks which
+ * are known to exist but not yet not fully supported. In these cases, we should
+ * let the user know that they are not encountering a bug, and also encourage
+ * contribution to the project by indicating that this is a planned future.
+ * @param featureName Missing feature name
+ */
+void logUnsupportedFeature(const char *featureName);
+
+/**
+ * Used when a plug-in has attempted to utilize a deprecated host feature. In
+ * such cases, undefined behavior in the plugin may result, so it is useful for
+ * the user to know about this, either to tell the plugin's developer or simply
+ * to know that an incompatibility may exist.
+ * @param functionName Deprecated feature name
+ * @param plugin
+ */
+void logDeprecated(const char *functionName, const char *plugin);
+
+/**
+ * Used to indicate that a file may be corrupt or incorrectly parsed. This is
+ * similar to a critical log message, except that extra information about the
+ * file and situation is shown.
+ * @param filename File name
+ * @param message Description of what went wrong
+ */
+void logFileError(const char *filename, const char *message);
+
+/**
+ * Used when a serious error has occurred, and its source is not entirely known.
+ * This occurs usually with segmentation faults or other errors which result in
+ * the program crashing.
+ * @param cause Description of error cause
+ */
+void logPossibleBug(const char *cause);
+
+/**
+ * Flush the contents of the ErrorLogger
+ */
+void flushErrorLog(void);
+
+/**
+ * Free all memory and associated resources from the global EventLogger instance
+ */
+void freeEventLogger(void);
+
+#endif
diff --git a/source/logging/LogPrinter.c b/source/logging/LogPrinter.c
new file mode 100644
index 0000000..87d4bff
--- /dev/null
+++ b/source/logging/LogPrinter.c
@@ -0,0 +1,144 @@
+//
+// LogPrinter.c - MrsWatson
+// Created by Nik Reiman on 10/23/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdlib.h>
+
+#include "logging/LogPrinter.h"
+
+#if UNIX
+void printToLog(const char *color, FILE *logFile, const char *message)
+{
+ if (logFile == NULL) {
+ if (color == NULL) {
+ fprintf(stderr, "%s", message);
+ } else {
+ fprintf(stderr, "%s%s%s", color, message, COLOR_RESET);
+ }
+ } else {
+ fprintf(logFile, "%s", message);
+ }
+}
+#elif WINDOWS
+void printToLog(const LogColor color, FILE *logFile, const char *message)
+{
+ static HANDLE consoleHandle = NULL;
+ CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
+
+ if (logFile == NULL) {
+ if (color != 0) {
+ if (consoleHandle == NULL) {
+ consoleHandle = GetStdHandle(STD_ERROR_HANDLE);
+ }
+
+ GetConsoleScreenBufferInfo(consoleHandle, &screenBufferInfo);
+ SetConsoleTextAttribute(consoleHandle, color);
+ fprintf(stderr, "%s", message);
+ SetConsoleTextAttribute(consoleHandle, screenBufferInfo.wAttributes);
+ } else {
+ fprintf(stderr, "%s", message);
+ }
+ } else {
+ fprintf(logFile, "%s", message);
+ }
+}
+#endif
+
+void flushLog(FILE *logFile)
+{
+ if (logFile == NULL) {
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ } else {
+ fprintf(logFile, "\n");
+ fflush(logFile);
+ }
+}
+
+void printTestPattern(void)
+{
+ printToLog(COLOR_FG_BLACK, NULL, "COLOR_FG_BLACK");
+ flushLog(NULL);
+ printToLog(COLOR_FG_MAROON, NULL, "COLOR_FG_MAROON");
+ flushLog(NULL);
+ printToLog(COLOR_FG_GREEN, NULL, "COLOR_FG_GREEN");
+ flushLog(NULL);
+ printToLog(COLOR_FG_OLIVE, NULL, "COLOR_FG_OLIVE");
+ flushLog(NULL);
+ printToLog(COLOR_FG_NAVY, NULL, "COLOR_FG_NAVY");
+ flushLog(NULL);
+ printToLog(COLOR_FG_PURPLE, NULL, "COLOR_FG_PURPLE");
+ flushLog(NULL);
+ printToLog(COLOR_FG_TEAL, NULL, "COLOR_FG_TEAL");
+ flushLog(NULL);
+ printToLog(COLOR_FG_GRAY, NULL, "COLOR_FG_GRAY");
+ flushLog(NULL);
+ printToLog(COLOR_FG_DKGRAY, NULL, "COLOR_FG_DKGRAY");
+ flushLog(NULL);
+ printToLog(COLOR_FG_RED, NULL, "COLOR_FG_RED");
+ flushLog(NULL);
+ printToLog(COLOR_FG_YELLOW, NULL, "COLOR_FG_YELLOW");
+ flushLog(NULL);
+ printToLog(COLOR_FG_BLUE, NULL, "COLOR_FG_BLUE");
+ flushLog(NULL);
+ printToLog(COLOR_FG_FUCHSIA, NULL, "COLOR_FG_FUCHSIA");
+ flushLog(NULL);
+ printToLog(COLOR_FG_CYAN, NULL, "COLOR_FG_CYAN");
+ flushLog(NULL);
+
+ printToLog(COLOR_FG_WHITE, NULL, "COLOR_FG_WHITE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_BLACK, NULL, "COLOR_BG_BLACK");
+ flushLog(NULL);
+ printToLog(COLOR_BG_MAROON, NULL, "COLOR_BG_MAROON");
+ flushLog(NULL);
+ printToLog(COLOR_BG_GREEN, NULL, "COLOR_BG_GREEN");
+ flushLog(NULL);
+ printToLog(COLOR_BG_OLIVE, NULL, "COLOR_BG_OLIVE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_NAVY, NULL, "COLOR_BG_NAVY");
+ flushLog(NULL);
+ printToLog(COLOR_BG_PURPLE, NULL, "COLOR_BG_PURPLE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_TEAL, NULL, "COLOR_BG_TEAL");
+ flushLog(NULL);
+ printToLog(COLOR_BG_GRAY, NULL, "COLOR_BG_GRAY");
+ flushLog(NULL);
+ printToLog(COLOR_BG_DKGRAY, NULL, "COLOR_BG_DKGRAY");
+ flushLog(NULL);
+ printToLog(COLOR_BG_RED, NULL, "COLOR_BG_RED");
+ flushLog(NULL);
+ printToLog(COLOR_BG_YELLOW, NULL, "COLOR_BG_YELLOW");
+ flushLog(NULL);
+ printToLog(COLOR_BG_BLUE, NULL, "COLOR_BG_BLUE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_FUCHSIA, NULL, "COLOR_BG_FUCHSIA");
+ flushLog(NULL);
+ printToLog(COLOR_BG_CYAN, NULL, "COLOR_BG_CYAN");
+ flushLog(NULL);
+ printToLog(COLOR_BG_WHITE, NULL, "COLOR_BG_WHITE");
+ flushLog(NULL);
+}
diff --git a/source/logging/LogPrinter.h b/source/logging/LogPrinter.h
new file mode 100644
index 0000000..bed457c
--- /dev/null
+++ b/source/logging/LogPrinter.h
@@ -0,0 +1,133 @@
+//
+// LogPrinter.h - MrsWatson
+// Created by Nik Reiman on 10/23/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_LogPrinter_h
+#define MrsWatson_LogPrinter_h
+
+#include <stdio.h>
+
+#if WINDOWS
+#include <Windows.h>
+#endif
+
+#if UNIX
+typedef const char *LogColor;
+
+#define COLOR_RESET "\x1b[0m"
+#define COLOR_NONE NULL
+
+#define COLOR_FG_BLACK "\x1b[30m"
+#define COLOR_FG_MAROON "\x1b[31m"
+#define COLOR_FG_GREEN "\x1b[92m"
+#define COLOR_FG_OLIVE "\x1b[32m"
+#define COLOR_FG_NAVY "\x1b[34m"
+#define COLOR_FG_PURPLE "\x1b[35m"
+#define COLOR_FG_TEAL "\x1b[36m"
+#define COLOR_FG_GRAY "\x1b[37m"
+#define COLOR_FG_DKGRAY "\x1b[90m"
+#define COLOR_FG_RED "\x1b[31m"
+#define COLOR_FG_YELLOW "\x1b[33m"
+#define COLOR_FG_BLUE "\x1b[94m"
+#define COLOR_FG_FUCHSIA "\x1b[95m"
+#define COLOR_FG_CYAN "\x1b[96m"
+#define COLOR_FG_WHITE "\x1b[37m"
+
+#define COLOR_BG_BLACK "\x1b[40m\x1b[37m"
+#define COLOR_BG_MAROON "\x1b[41m\x1b[37m"
+#define COLOR_BG_GREEN "\x1b[102m\x1b[30m"
+#define COLOR_BG_OLIVE "\x1b[42m\x1b[30m"
+#define COLOR_BG_NAVY "\x1b[44m\x1b[37m"
+#define COLOR_BG_PURPLE "\x1b[45m\x1b[37m"
+#define COLOR_BG_TEAL "\x1b[46m\x1b[30m"
+#define COLOR_BG_GRAY "\x1b[47m\x1b[30m"
+#define COLOR_BG_DKGRAY "\x1b[100m\x1b[37m"
+#define COLOR_BG_RED "\x1b[101m\x1b[37m"
+#define COLOR_BG_YELLOW "\x1b[43m\x1b[30m"
+#define COLOR_BG_BLUE "\x1b[104m\x1b[37m"
+#define COLOR_BG_FUCHSIA "\x1b[105m\x1b[30m"
+#define COLOR_BG_CYAN "\x1b[46m\x1b[30m"
+#define COLOR_BG_WHITE "\x1b[47m\x1b[30m"
+
+#elif WINDOWS
+typedef WORD LogColor;
+
+#define COLOR_RESET 0
+#define COLOR_NONE 0
+
+#define COLOR_FG_BLACK 0
+#define COLOR_FG_MAROON 0x05
+#define COLOR_FG_GREEN FOREGROUND_GREEN | FOREGROUND_INTENSITY
+#define COLOR_FG_OLIVE FOREGROUND_GREEN
+#define COLOR_FG_NAVY FOREGROUND_BLUE
+#define COLOR_FG_PURPLE 0x0c
+#define COLOR_FG_TEAL 0x03
+#define COLOR_FG_GRAY 0x07
+#define COLOR_FG_DKGRAY 0x08
+#define COLOR_FG_RED FOREGROUND_RED | FOREGROUND_INTENSITY
+#define COLOR_FG_YELLOW 0x0e
+#define COLOR_FG_BLUE FOREGROUND_BLUE | FOREGROUND_INTENSITY
+#define COLOR_FG_FUCHSIA 0x0b
+#define COLOR_FG_CYAN 0x03 | FOREGROUND_INTENSITY
+#define COLOR_FG_WHITE 0x0f
+
+#define COLOR_BG_BLACK 0
+#define COLOR_BG_MAROON BACKGROUND_RED
+#define COLOR_BG_GREEN BACKGROUND_GREEN | BACKGROUND_INTENSITY
+#define COLOR_BG_OLIVE BACKGROUND_GREEN
+#define COLOR_BG_NAVY BACKGROUND_BLUE
+#define COLOR_BG_PURPLE 0xc0
+#define COLOR_BG_TEAL 0x30
+#define COLOR_BG_GRAY 0x70
+#define COLOR_BG_DKGRAY 0x80
+#define COLOR_BG_RED BACKGROUND_RED | BACKGROUND_INTENSITY
+#define COLOR_BG_YELLOW 0xe0
+#define COLOR_BG_BLUE BACKGROUND_BLUE | BACKGROUND_INTENSITY
+#define COLOR_BG_FUCHSIA 0xb0
+#define COLOR_BG_CYAN 0x30
+#define COLOR_BG_WHITE 0xf0
+#endif
+
+/**
+ * Print a message with colors to the log
+ * @param color Color to use
+ * @param logFile Log file to write to
+ * @param message Message contents
+ */
+void printToLog(const LogColor color, FILE *logFile, const char *message);
+
+/**
+ * Flush the log file to disk
+ * @param logFile Log file
+ */
+void flushLog(FILE *logFile);
+
+/**
+ * Print a test pattern of all known color combinations.
+ */
+void printTestPattern(void);
+
+#endif
diff --git a/source/midi/MidiEvent.c b/source/midi/MidiEvent.c
new file mode 100644
index 0000000..29519df
--- /dev/null
+++ b/source/midi/MidiEvent.c
@@ -0,0 +1,55 @@
+//
+// MidiEvent.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#include "midi/MidiEvent.h"
+
+MidiEvent newMidiEvent(void)
+{
+ MidiEvent midiEvent = malloc(sizeof(MidiEventMembers));
+
+ midiEvent->eventType = MIDI_TYPE_INVALID;
+ midiEvent->deltaFrames = 0;
+ midiEvent->timestamp = 0;
+ midiEvent->status = 0;
+ midiEvent->data1 = 0;
+ midiEvent->data2 = 0;
+ midiEvent->extraData = NULL;
+
+ return midiEvent;
+}
+
+void freeMidiEvent(MidiEvent self)
+{
+ if (self->eventType == MIDI_TYPE_SYSEX || self->eventType == MIDI_TYPE_META) {
+ free(self->extraData);
+ }
+
+ free(self);
+}
diff --git a/source/midi/MidiEvent.h b/source/midi/MidiEvent.h
new file mode 100644
index 0000000..5ff134b
--- /dev/null
+++ b/source/midi/MidiEvent.h
@@ -0,0 +1,80 @@
+//
+// MidiEvent.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_MidiEvent_h
+#define MrsWatson_MidiEvent_h
+
+#include "base/Types.h"
+
+typedef enum {
+ MIDI_TYPE_INVALID,
+ MIDI_TYPE_REGULAR,
+ MIDI_TYPE_SYSEX,
+ MIDI_TYPE_META,
+ NUM_MIDI_TYPES
+} MidiEventType;
+
+typedef struct {
+ MidiEventType eventType;
+ unsigned long deltaFrames;
+ unsigned long timestamp;
+ byte status;
+ byte data1;
+ byte data2;
+ byte *extraData;
+} MidiEventMembers;
+typedef MidiEventMembers *MidiEvent;
+
+// MIDI Meta Event types
+#define MIDI_META_TYPE_TEXT 0x01
+#define MIDI_META_TYPE_COPYRIGHT 0x02
+#define MIDI_META_TYPE_SEQUENCE_NAME 0x03
+#define MIDI_META_TYPE_INSTRUMENT 0x04
+#define MIDI_META_TYPE_LYRIC 0x05
+#define MIDI_META_TYPE_MARKER 0x06
+#define MIDI_META_TYPE_CUE_POINT 0x07
+#define MIDI_META_TYPE_PROGRAM_NAME 0x08
+#define MIDI_META_TYPE_DEVICE_NAME 0x09
+#define MIDI_META_TYPE_TEMPO 0x51
+#define MIDI_META_TYPE_TIME_SIGNATURE 0x58
+#define MIDI_META_TYPE_KEY_SIGNATURE 0x59
+#define MIDI_META_TYPE_PROPRIETARY 0x7f
+#define MIDI_META_TYPE_TRACK_END 0x2f
+
+/**
+ * Create a new MIDI event
+ * @return MidiEvent object
+ */
+MidiEvent newMidiEvent(void);
+
+/**
+ * Free a MIDI event object and its associated resources
+ * @param self
+ */
+void freeMidiEvent(MidiEvent self);
+
+#endif
diff --git a/source/midi/MidiSequence.c b/source/midi/MidiSequence.c
new file mode 100644
index 0000000..f5ebb8b
--- /dev/null
+++ b/source/midi/MidiSequence.c
@@ -0,0 +1,100 @@
+//
+// MidiSequence.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#include "logging/EventLogger.h"
+#include "midi/MidiSequence.h"
+
+MidiSequence newMidiSequence(void)
+{
+ MidiSequence midiSequence = malloc(sizeof(MidiSequenceMembers));
+
+ midiSequence->midiEvents = newLinkedList();
+ midiSequence->_lastEvent = midiSequence->midiEvents;
+ midiSequence->_lastTimestamp = 0;
+ midiSequence->numMidiEventsProcessed = 0;
+
+ return midiSequence;
+}
+
+void appendMidiEventToSequence(MidiSequence self, MidiEvent midiEvent)
+{
+ if (self != NULL && midiEvent != NULL) {
+ linkedListAppend(self->midiEvents, midiEvent);
+ }
+}
+
+boolByte fillMidiEventsFromRange(MidiSequence self, const unsigned long startTimestamp,
+ const unsigned long blocksize, LinkedList outMidiEvents)
+{
+ MidiEvent midiEvent;
+ LinkedListIterator iterator = self->_lastEvent;
+ const unsigned long stopTimestamp = startTimestamp + blocksize;
+
+ while (true) {
+ if ((iterator == NULL) || (iterator->item == NULL)) {
+ return false;
+ }
+
+ midiEvent = iterator->item;
+
+ if (stopTimestamp < midiEvent->timestamp) {
+ // We have not yet reached this event, stop iterating
+ break;
+ } else if (startTimestamp <= midiEvent->timestamp && stopTimestamp > midiEvent->timestamp) {
+ midiEvent->deltaFrames = midiEvent->timestamp - startTimestamp;
+ logDebug("Scheduling MIDI event 0x%x (%x, %x) in %ld frames",
+ midiEvent->status, midiEvent->data1, midiEvent->data2, midiEvent->deltaFrames);
+ linkedListAppend(outMidiEvents, midiEvent);
+ self->_lastEvent = iterator->nextItem;
+ self->numMidiEventsProcessed++;
+ } else if (startTimestamp > midiEvent->timestamp) {
+ logInternalError("Inconsistent MIDI sequence ordering");
+ }
+
+ // Last item in the list
+ if (iterator->nextItem == NULL) {
+ if (startTimestamp <= midiEvent->timestamp && stopTimestamp > midiEvent->timestamp) {
+ return false;
+ }
+
+ break;
+ }
+
+ iterator = iterator->nextItem;
+ }
+
+ return true;
+}
+
+void freeMidiSequence(MidiSequence self)
+{
+ freeLinkedListAndItems(self->midiEvents, (LinkedListFreeItemFunc)freeMidiEvent);
+ free(self);
+}
diff --git a/source/midi/MidiSequence.h b/source/midi/MidiSequence.h
new file mode 100644
index 0000000..8e7dbb0
--- /dev/null
+++ b/source/midi/MidiSequence.h
@@ -0,0 +1,86 @@
+//
+// MidiSequence.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_MidiSequence_h
+#define MrsWatson_MidiSequence_h
+
+#include "base/LinkedList.h"
+#include "midi/MidiEvent.h"
+
+typedef struct {
+ LinkedList midiEvents;
+ LinkedListIterator _lastEvent;
+ int _lastTimestamp;
+ int numMidiEventsProcessed;
+} MidiSequenceMembers;
+
+/**
+ * The purpose of this class is to hold a series of MIDI events in sequential
+ * order. After being read from a MidiSource, such as a file or perhaps an
+ * actual device, the events are stored here where they can easily be read block
+ * by block.
+ */
+typedef MidiSequenceMembers *MidiSequence;
+
+/**
+ * Creating a new MidiSequence object
+ * @return MidiSequence instance
+ */
+MidiSequence newMidiSequence(void);
+
+/**
+ * Add an event to the end of the sequence. The event's timestamp must be
+ * properly set before making this call. Events added into the sequence in this
+ * call are not sorted, it is the responsibility of the caller to add the events
+ * sequentially in the order which they should be played back.
+ * @param self
+ * @param midiEvent MidiEvent to add
+ */
+void appendMidiEventToSequence(MidiSequence self, MidiEvent midiEvent);
+
+/**
+ * Populate a linked list with MIDI events for a given block. This method does
+ * not return a linked list in order to optimize for memory usage.
+ * @param self
+ * @param startTimestamp Sample frame that marks the starting point of the block
+ * @param blocksize Blocksize, which determines the range of events that will be
+ * added to the list
+ * @param outMidiEvents Lists to upend events to
+ * @return True if more events remain in the list after this call is complete,
+ * false otherwise. This is so that the caller can tell when the end of the MIDI
+ * sequence has been reached.
+ */
+boolByte fillMidiEventsFromRange(MidiSequence self, const unsigned long startTimestamp,
+ const unsigned long blocksize, LinkedList outMidiEvents);
+
+/**
+ * Free a MIDI sequence and its associated resources
+ * @param self
+ */
+void freeMidiSequence(MidiSequence midiSequence);
+
+#endif
diff --git a/source/midi/MidiSource.c b/source/midi/MidiSource.c
new file mode 100644
index 0000000..d339f74
--- /dev/null
+++ b/source/midi/MidiSource.c
@@ -0,0 +1,75 @@
+//
+// MidiSource.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#include "base/File.h"
+#include "logging/EventLogger.h"
+#include "midi/MidiSourceFile.h"
+
+MidiSourceType guessMidiSourceType(const CharString midiSourceTypeString)
+{
+ if (!charStringIsEmpty(midiSourceTypeString)) {
+ File midiSourceFile = newFileWithPath(midiSourceTypeString);
+ CharString fileExtension = fileGetExtension(midiSourceFile);
+ freeFile(midiSourceFile);
+
+ if (fileExtension == NULL) {
+ return MIDI_SOURCE_TYPE_INVALID;
+ } else if (charStringIsEqualToCString(fileExtension, "mid", true) ||
+ charStringIsEqualToCString(fileExtension, "midi", true)) {
+ freeCharString(fileExtension);
+ return MIDI_SOURCE_TYPE_FILE;
+ } else {
+ logCritical("MIDI source '%s' does not match any supported type");
+ freeCharString(fileExtension);
+ return MIDI_SOURCE_TYPE_INVALID;
+ }
+ } else {
+ logInternalError("MIDI source type was null");
+ return MIDI_SOURCE_TYPE_INVALID;
+ }
+}
+
+MidiSource newMidiSource(MidiSourceType midiSourceType, const CharString midiSourceName)
+{
+ switch (midiSourceType) {
+ case MIDI_SOURCE_TYPE_FILE:
+ return newMidiSourceFile(midiSourceName);
+
+ default:
+ return NULL;
+ }
+}
+
+void freeMidiSource(MidiSource self)
+{
+ self->freeMidiSourceData(self->extraData);
+ freeCharString(self->sourceName);
+ free(self);
+}
diff --git a/source/midi/MidiSource.h b/source/midi/MidiSource.h
new file mode 100644
index 0000000..9f1468f
--- /dev/null
+++ b/source/midi/MidiSource.h
@@ -0,0 +1,83 @@
+//
+// MidiSource.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_MidiSource_h
+#define MrsWatson_MidiSource_h
+
+#include "base/CharString.h"
+#include "base/Types.h"
+#include "midi/MidiSequence.h"
+
+typedef enum {
+ MIDI_SOURCE_TYPE_INVALID,
+ MIDI_SOURCE_TYPE_FILE,
+ NUM_MIDI_SOURCE_TYPES
+} MidiSourceType;
+
+typedef boolByte (*OpenMidiSourceFunc)(void *);
+typedef boolByte (*ReadMidiEventsFunc)(void *, MidiSequence);
+typedef void (*FreeMidiSourceDataFunc)(void *);
+
+typedef struct {
+ MidiSourceType midiSourceType;
+ CharString sourceName;
+
+ OpenMidiSourceFunc openMidiSource;
+ ReadMidiEventsFunc readMidiEvents;
+ FreeMidiSourceDataFunc freeMidiSourceData;
+
+ void *extraData;
+} MidiSourceMembers;
+
+/**
+ * A class which acts as a source for MIDI data.
+ */
+typedef MidiSourceMembers *MidiSource;
+
+/**
+ * Factory method to create a new MIDI source
+ * @param midiSourceType Source type, which should be the result of a call to
+ * guessMidiSourceType()
+ * @param midiSourceName MIDI source name
+ * @return MidiSource object, or NULL if no object could be created
+ */
+MidiSource newMidiSource(MidiSourceType midiSourceType, const CharString midiSourceName);
+
+/**
+ * Determine an appropriate source type based on a file name.
+ * @param midiSourceTypeString Source name
+ * @return Source type
+ */
+MidiSourceType guessMidiSourceType(const CharString midiSourceTypeString);
+
+/**
+ * Free a MidiSource and its associated resources
+ * @param self
+ */
+void freeMidiSource(MidiSource self);
+
+#endif
diff --git a/source/midi/MidiSourceFile.c b/source/midi/MidiSourceFile.c
new file mode 100644
index 0000000..33ceff2
--- /dev/null
+++ b/source/midi/MidiSourceFile.c
@@ -0,0 +1,343 @@
+//
+// MidiSourceFile.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "audio/AudioSettings.h"
+#include "base/Endian.h"
+#include "logging/EventLogger.h"
+#include "midi/MidiSourceFile.h"
+
+static boolByte _openMidiSourceFile(void *midiSourcePtr)
+{
+ MidiSource midiSource = midiSourcePtr;
+ MidiSourceFileData extraData = midiSource->extraData;
+
+ extraData->fileHandle = fopen(midiSource->sourceName->data, "rb");
+
+ if (extraData->fileHandle == NULL) {
+ logError("MIDI file '%s' could not be opened for reading", midiSource->sourceName->data);
+ return false;
+ }
+
+ return true;
+}
+
+static boolByte _readMidiFileChunkHeader(FILE *midiFile, const char *expectedChunkId)
+{
+ byte chunkId[5];
+ size_t itemsRead;
+
+ memset(chunkId, 0, 5);
+ itemsRead = fread(chunkId, sizeof(byte), 4, midiFile);
+
+ if (itemsRead < 4) {
+ logError("Short read of MIDI file (at chunk ID)");
+ return false;
+ } else if (strncmp((char *)chunkId, expectedChunkId, 4)) {
+ logError("MIDI file does not have valid chunk ID");
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static boolByte _readMidiFileHeader(FILE *midiFile, unsigned short *formatType, unsigned short *numTracks, unsigned short *timeDivision)
+{
+ unsigned int numBytesBuffer;
+ size_t itemsRead;
+ unsigned int numBytes;
+ unsigned short wordBuffer;
+
+ if (!_readMidiFileChunkHeader(midiFile, "MThd")) {
+ return false;
+ }
+
+ itemsRead = fread(&numBytesBuffer, sizeof(unsigned int), 1, midiFile);
+
+ if (itemsRead < 1) {
+ logError("Short read of MIDI file (at header, num items)");
+ return false;
+ }
+
+ numBytes = convertBigEndianIntToPlatform(numBytesBuffer);
+
+ if (numBytes != 6) {
+ logError("MIDI file has %d bytes in header chunk, expected 6", numBytes);
+ return false;
+ }
+
+ itemsRead = fread(&wordBuffer, sizeof(unsigned short), 1, midiFile);
+
+ if (itemsRead != 1) {
+ logError("Short read of MIDI file (at header, format type)");
+ return false;
+ }
+
+ *formatType = convertBigEndianShortToPlatform(wordBuffer);
+
+ itemsRead = fread(&wordBuffer, sizeof(unsigned short), 1, midiFile);
+
+ if (itemsRead != 1) {
+ logError("Short read of MIDI file (at header, num tracks)");
+ return false;
+ }
+
+ *numTracks = convertBigEndianShortToPlatform(wordBuffer);
+
+ itemsRead = fread(&wordBuffer, sizeof(unsigned short), 1, midiFile);
+
+ if (itemsRead != 1) {
+ logError("Short read of MIDI file (at header, time division)");
+ return false;
+ }
+
+ *timeDivision = convertBigEndianShortToPlatform(wordBuffer);
+ logDebug("Time division is %d", *timeDivision);
+
+ return true;
+}
+
+static boolByte _readMidiFileTrack(FILE *midiFile, const int trackNumber,
+ const int timeDivision, const MidiFileTimeDivisionType divisionType,
+ MidiSequence midiSequence)
+{
+ unsigned int numBytesBuffer;
+ byte *trackData, *currentByte, *endByte;
+ size_t itemsRead, numBytes;
+ unsigned long currentTimeInSampleFrames = 0;
+ unsigned long unpackedVariableLength;
+ MidiEvent midiEvent;
+ unsigned int i;
+
+ if (!_readMidiFileChunkHeader(midiFile, "MTrk")) {
+ return false;
+ }
+
+ itemsRead = fread(&numBytesBuffer, sizeof(unsigned int), 1, midiFile);
+
+ if (itemsRead < 1) {
+ logError("Short read of MIDI file (at track %d header, num items)", trackNumber);
+ return false;
+ }
+
+ // Read in the entire track in one pass and parse the events from the buffer data. Much easier
+ // than having to call fread() for each event.
+ numBytes = (size_t)convertBigEndianIntToPlatform(numBytesBuffer);
+ trackData = (byte *)malloc(numBytes);
+ itemsRead = fread(trackData, 1, numBytes, midiFile);
+
+ if (itemsRead != numBytes) {
+ logError("Short read of MIDI file (at track %d)", trackNumber);
+ free(trackData);
+ return false;
+ }
+
+ currentByte = trackData;
+ endByte = trackData + numBytes;
+
+ while (currentByte < endByte) {
+ // Unpack variable length timestamp
+ unpackedVariableLength = *currentByte;
+
+ if (unpackedVariableLength & 0x80) {
+ unpackedVariableLength &= 0x7f;
+
+ do {
+ unpackedVariableLength = (unpackedVariableLength << 7) + (*(++currentByte) & 0x7f);
+ } while (*currentByte & 0x80);
+ }
+
+ currentByte++;
+ midiEvent = newMidiEvent();
+
+ switch (*currentByte) {
+ case 0xff:
+ midiEvent->eventType = MIDI_TYPE_META;
+ currentByte++;
+ midiEvent->status = *(currentByte++);
+ numBytes = *(currentByte++);
+ midiEvent->extraData = (byte *)malloc(numBytes);
+
+ for (i = 0; i < numBytes; i++) {
+ midiEvent->extraData[i] = *(currentByte++);
+ }
+
+ break;
+
+ case 0x7f:
+ logUnsupportedFeature("MIDI files containing sysex events");
+ free(trackData);
+ return false;
+
+ default:
+ midiEvent->eventType = MIDI_TYPE_REGULAR;
+ midiEvent->status = *currentByte++;
+ midiEvent->data1 = *currentByte++;
+
+ // All regular MIDI events have 3 bytes except for program change and channel aftertouch
+ if (!((midiEvent->status & 0xf0) == 0xc0 || (midiEvent->status & 0xf0) == 0xd0)) {
+ midiEvent->data2 = *currentByte++;
+ }
+
+ break;
+ }
+
+ switch (divisionType) {
+ case TIME_DIVISION_TYPE_TICKS_PER_BEAT: {
+ // TODO: If the time signature is not 4/4, this calculation will be wrong
+ double ticksPerSecond = (double)timeDivision * getTempo() / 60.0;
+ double sampleFramesPerTick = getSampleRate() / ticksPerSecond;
+ currentTimeInSampleFrames += (long)(unpackedVariableLength * sampleFramesPerTick);
+ }
+ break;
+
+ case TIME_DIVISION_TYPE_FRAMES_PER_SECOND:
+ // Actually, this should be caught when parsing the file type
+ logUnsupportedFeature("Time division frames/sec");
+ return false;
+
+ case TIME_DIVISION_TYPE_INVALID:
+ default:
+ logInternalError("Invalid time division type");
+ return false;
+ }
+
+ midiEvent->timestamp = currentTimeInSampleFrames;
+
+ if (midiEvent->eventType == MIDI_TYPE_META) {
+ switch (midiEvent->status) {
+ case MIDI_META_TYPE_TEXT:
+ case MIDI_META_TYPE_COPYRIGHT:
+ case MIDI_META_TYPE_SEQUENCE_NAME:
+ case MIDI_META_TYPE_INSTRUMENT:
+ case MIDI_META_TYPE_LYRIC:
+ case MIDI_META_TYPE_MARKER:
+ case MIDI_META_TYPE_CUE_POINT:
+
+ // This event type could theoretically be supported, as long as the
+ // plugin supports it
+ case MIDI_META_TYPE_PROGRAM_NAME:
+ case MIDI_META_TYPE_DEVICE_NAME:
+ case MIDI_META_TYPE_KEY_SIGNATURE:
+ case MIDI_META_TYPE_PROPRIETARY:
+ logDebug("Ignoring MIDI meta event of type 0x%x at %ld", midiEvent->status, midiEvent->timestamp);
+ break;
+
+ case MIDI_META_TYPE_TEMPO:
+ case MIDI_META_TYPE_TIME_SIGNATURE:
+ case MIDI_META_TYPE_TRACK_END:
+ logDebug("Parsed MIDI meta event of type 0x%02x at %ld", midiEvent->status, midiEvent->timestamp);
+ appendMidiEventToSequence(midiSequence, midiEvent);
+ break;
+
+ default:
+ logWarn("Ignoring MIDI meta event of type 0x%x at %ld", midiEvent->status, midiEvent->timestamp);
+ break;
+ }
+ } else {
+ logDebug("MIDI event of type 0x%02x parsed at %ld", midiEvent->status, midiEvent->timestamp);
+ appendMidiEventToSequence(midiSequence, midiEvent);
+ }
+ }
+
+ free(trackData);
+ return true;
+}
+
+static boolByte _readMidiEventsFile(void *midiSourcePtr, MidiSequence midiSequence)
+{
+ MidiSource midiSource = (MidiSource)midiSourcePtr;
+ MidiSourceFileData extraData = (MidiSourceFileData)(midiSource->extraData);
+ unsigned short formatType, numTracks, timeDivision = 0;
+ int track;
+
+ if (!_readMidiFileHeader(extraData->fileHandle, &formatType, &numTracks, &timeDivision)) {
+ return false;
+ }
+
+ if (formatType != 0) {
+ logUnsupportedFeature("MIDI file types other than 0");
+ return false;
+ } else if (formatType == 0 && numTracks != 1) {
+ logError("MIDI file '%s' is of type 0, but contains %d tracks", midiSource->sourceName->data, numTracks);
+ return false;
+ }
+
+ // Determine time division type
+ if (timeDivision & 0x7fff) {
+ extraData->divisionType = TIME_DIVISION_TYPE_TICKS_PER_BEAT;
+ } else {
+ extraData->divisionType = TIME_DIVISION_TYPE_FRAMES_PER_SECOND;
+ logUnsupportedFeature("MIDI file with time division in frames/second");
+ return false;
+ }
+
+ logDebug("MIDI file is type %d, has %d tracks, and time division %d (type %d)",
+ formatType, numTracks, timeDivision, extraData->divisionType);
+
+ for (track = 0; track < numTracks; track++) {
+ if (!_readMidiFileTrack(extraData->fileHandle, track, timeDivision, extraData->divisionType, midiSequence)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void _freeMidiEventsFile(void *midiSourceDataPtr)
+{
+ MidiSourceFileData extraData = midiSourceDataPtr;
+
+ if (extraData->fileHandle != NULL) {
+ fclose(extraData->fileHandle);
+ }
+
+ free(extraData);
+}
+
+MidiSource newMidiSourceFile(const CharString midiSourceName)
+{
+ MidiSource midiSource = (MidiSource)malloc(sizeof(MidiSourceMembers));
+ MidiSourceFileData extraData = (MidiSourceFileData)malloc(sizeof(MidiSourceFileDataMembers));
+
+ midiSource->midiSourceType = MIDI_SOURCE_TYPE_FILE;
+ midiSource->sourceName = newCharString();
+ charStringCopy(midiSource->sourceName, midiSourceName);
+
+ midiSource->openMidiSource = _openMidiSourceFile;
+ midiSource->readMidiEvents = _readMidiEventsFile;
+ midiSource->freeMidiSourceData = _freeMidiEventsFile;
+
+ extraData->divisionType = TIME_DIVISION_TYPE_INVALID;
+ extraData->fileHandle = NULL;
+ midiSource->extraData = extraData;
+
+ return midiSource;
+}
diff --git a/source/midi/MidiSourceFile.h b/source/midi/MidiSourceFile.h
new file mode 100644
index 0000000..a0a3b63
--- /dev/null
+++ b/source/midi/MidiSourceFile.h
@@ -0,0 +1,50 @@
+//
+// MidiSourceFile.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_MidiSourceFile_h
+#define MrsWatson_MidiSourceFile_h
+
+#include <stdio.h>
+
+#include "midi/MidiSource.h"
+
+typedef enum {
+ TIME_DIVISION_TYPE_INVALID,
+ TIME_DIVISION_TYPE_TICKS_PER_BEAT,
+ TIME_DIVISION_TYPE_FRAMES_PER_SECOND,
+ NUM_TIME_DIVISION_TYPES
+} MidiFileTimeDivisionType;
+
+typedef struct {
+ FILE *fileHandle;
+ MidiFileTimeDivisionType divisionType;
+} MidiSourceFileDataMembers;
+typedef MidiSourceFileDataMembers *MidiSourceFileData;
+
+MidiSource newMidiSourceFile(const CharString midiSourceName);
+
+#endif
diff --git a/source/plugin/Plugin.c b/source/plugin/Plugin.c
new file mode 100644
index 0000000..a307e8e
--- /dev/null
+++ b/source/plugin/Plugin.c
@@ -0,0 +1,205 @@
+//
+// Plugin.c - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "audio/AudioSettings.h"
+#include "logging/EventLogger.h"
+#include "plugin/Plugin.h"
+#include "plugin/PluginGain.h"
+#include "plugin/PluginLimiter.h"
+#include "plugin/PluginPassthru.h"
+#include "plugin/PluginVst2x.h"
+#include "plugin/PluginSilence.h"
+
+static PluginInterfaceType _guessPluginInterfaceType(const CharString pluginName, const CharString pluginSearchRoot)
+{
+ PluginInterfaceType pluginType = PLUGIN_TYPE_INVALID;
+
+ if (pluginName == NULL || charStringIsEmpty(pluginName)) {
+ logError("Attempt to guess plugin with empty name");
+ return pluginType;
+ }
+
+ logDebug("Trying to find plugin '%s'", pluginName->data);
+
+ if (pluginVst2xExists(pluginName, pluginSearchRoot)) {
+ logInfo("Plugin '%s' is of type VST2.x", pluginName->data);
+ pluginType = PLUGIN_TYPE_VST_2X;
+ } else if (!strncmp(INTERNAL_PLUGIN_PREFIX, pluginName->data, strlen(INTERNAL_PLUGIN_PREFIX))) {
+ logInfo("Plugin '%s' is an internal plugin", pluginName->data);
+ pluginType = PLUGIN_TYPE_INTERNAL;
+ } else {
+ logError("Plugin '%s' could not be found", pluginName->data);
+ }
+
+ return pluginType;
+}
+
+static void _logPluginLocation(const CharString location)
+{
+ logInfo("Location (internal):", location->data);
+}
+
+static void _listAvailablePluginsInternal(void)
+{
+ CharString internalLocation = newCharStringWithCString("(Internal)");
+ _logPluginLocation(internalLocation);
+ logInfo(" %s", kInternalPluginPassthruName);
+ logInfo(" %s", kInternalPluginSilenceName);
+ freeCharString(internalLocation);
+}
+
+void listAvailablePlugins(const CharString pluginRoot)
+{
+ listAvailablePluginsVst2x(pluginRoot);
+ _listAvailablePluginsInternal();
+}
+
+/**
+ * Used to check if an internal plugin (ie, starting with "mrs_" matches an
+ * internal plugin name. This function only compares to the length of the
+ * internal name, so that extra parameters can be appended to the end of the
+ * plugin name argument.
+ * @param pluginName Plugin name to check
+ * @param internalName Internal name to compare against
+ * @return True if the plugin is a match
+ */
+static boolByte _internalPluginNameMatches(const CharString pluginName, const char *internalName)
+{
+ return (boolByte)(strncmp(pluginName->data, internalName, strlen(internalName)) == 0);
+}
+
+// Plugin newPlugin(PluginInterfaceType interfaceType, const CharString pluginName, const CharString pluginLocation) {
+Plugin pluginFactory(const CharString pluginName, const CharString pluginRoot)
+{
+ PluginInterfaceType interfaceType = _guessPluginInterfaceType(pluginName, pluginRoot);
+
+ if (interfaceType == PLUGIN_TYPE_INVALID) {
+ return NULL;
+ }
+
+ switch (interfaceType) {
+ case PLUGIN_TYPE_VST_2X:
+ return newPluginVst2x(pluginName, pluginRoot);
+
+ case PLUGIN_TYPE_INTERNAL:
+ if (_internalPluginNameMatches(pluginName, kInternalPluginGainName)) {
+ return newPluginGain(pluginName);
+ } else if (_internalPluginNameMatches(pluginName, kInternalPluginLimiterName)) {
+ return newPluginLimiter(pluginName);
+ } else if (_internalPluginNameMatches(pluginName, kInternalPluginPassthruName)) {
+ return newPluginPassthru(pluginName);
+ } else if (_internalPluginNameMatches(pluginName, kInternalPluginSilenceName)) {
+ return newPluginSilence(pluginName);
+ } else {
+ logError("'%s' is not a recognized internal plugin", pluginName->data);
+ return NULL;
+ }
+
+ default:
+ logError("Could not find plugin type for '%s'", pluginName->data);
+ return NULL;
+ }
+}
+
+boolByte openPlugin(Plugin self)
+{
+ if (self == NULL) {
+ logError("There is no plugin to open");
+ return false;
+ } else if (self->isOpen) {
+ return true;
+ }
+
+ if (!self->openPlugin(self)) {
+ logError("Plugin '%s' could not be opened", self->pluginName->data);
+ return false;
+ } else {
+ self->inputBuffer = newSampleBuffer((ChannelCount)self->getSetting(self, PLUGIN_NUM_INPUTS), getBlocksize());
+ self->outputBuffer = newSampleBuffer((ChannelCount)self->getSetting(self, PLUGIN_NUM_OUTPUTS), getBlocksize());
+ self->isOpen = true;
+ }
+
+ return true;
+}
+
+boolByte closePlugin(Plugin self)
+{
+ if (self == NULL) {
+ logError("There is no plugin to open");
+ return false;
+ }
+
+ self->closePlugin(self);
+ freeSampleBuffer(self->inputBuffer);
+ self->inputBuffer = NULL;
+ freeSampleBuffer(self->outputBuffer);
+ self->outputBuffer = NULL;
+ self->isOpen = false;
+ return true;
+}
+
+Plugin _newPlugin(PluginInterfaceType interfaceType, PluginType pluginType)
+{
+ Plugin plugin = (Plugin)malloc(sizeof(PluginMembers));
+
+ plugin->interfaceType = interfaceType;
+ plugin->pluginType = pluginType;
+ plugin->pluginName = newCharString();
+ plugin->pluginLocation = newCharString();
+ plugin->pluginAbsolutePath = newCharString();
+
+ plugin->inputBuffer = NULL;
+ plugin->outputBuffer = NULL;
+ plugin->isOpen = false;
+
+ return plugin;
+}
+
+void freePlugin(Plugin self)
+{
+ if (self != NULL) {
+ if (self->extraData != NULL) {
+ self->freePluginData(self->extraData);
+ free(self->extraData);
+ }
+
+ if (self->inputBuffer != NULL) {
+ freeSampleBuffer(self->inputBuffer);
+ }
+ if (self->outputBuffer != NULL) {
+ freeSampleBuffer(self->outputBuffer);
+ }
+ freeCharString(self->pluginName);
+ freeCharString(self->pluginLocation);
+ freeCharString(self->pluginAbsolutePath);
+ free(self);
+ }
+}
diff --git a/source/plugin/Plugin.h b/source/plugin/Plugin.h
new file mode 100644
index 0000000..1fb9519
--- /dev/null
+++ b/source/plugin/Plugin.h
@@ -0,0 +1,195 @@
+//
+// Plugin.h - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_Plugin_h
+#define MrsWatson_Plugin_h
+
+#include "audio/SampleBuffer.h"
+#include "base/CharString.h"
+#include "base/LinkedList.h"
+
+// All internal plugins should start with this string
+#define INTERNAL_PLUGIN_PREFIX "mrs_"
+
+typedef enum {
+ PLUGIN_TYPE_INVALID,
+ PLUGIN_TYPE_VST_2X,
+ PLUGIN_TYPE_INTERNAL,
+ NUM_PLUGIN_INTERFACE_TYPES
+} PluginInterfaceType;
+
+typedef enum {
+ PLUGIN_TYPE_UNKNOWN,
+ PLUGIN_TYPE_UNSUPPORTED,
+ PLUGIN_TYPE_EFFECT,
+ PLUGIN_TYPE_INSTRUMENT,
+ NUM_PLUGIN_TYPES
+} PluginType;
+
+typedef enum {
+ PLUGIN_SETTING_TAIL_TIME_IN_MS,
+ PLUGIN_NUM_INPUTS,
+ PLUGIN_NUM_OUTPUTS,
+ PLUGIN_INITIAL_DELAY,
+ NUM_PLUGIN_SETTINGS
+} PluginSetting;
+
+/**
+ * Called when a plugin is to be opened. This includes loading any dynamic
+ * libraries into memory initializing the plugin.
+ * @param pluginPtr self
+ */
+typedef boolByte (*PluginOpenFunc)(void *pluginPtr);
+/**
+ * Called when the plugin should display some generic info about itself. This
+ * may be a list of supported parameters or programs, or any other information
+ * relevant to the user. See the implementation in the PluginVST2x class for an
+ * example.
+ * @param pluginPtr self
+ */
+typedef void (*PluginDisplayInfoFunc)(void *pluginPtr);
+/**
+ * Used to gather information about the plugin, such as the number of inputs and
+ * outputs. See the PluginSetting enum for examples of information which may be
+ * requested.
+ * @param pluginPtr self
+ * @param pluginSetting Setting to query
+ */
+typedef int (*PluginGetSettingFunc)(void *pluginPtr, PluginSetting pluginSetting);
+/**
+ * Called with the host wants to process a block of audio samples.
+ * @param pluginPtr self
+ * @param inputs Block of input samples to process
+ * @param outputs Block where output samples shall be written
+ */
+typedef void (*PluginProcessAudioFunc)(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs);
+/**
+ * Called the host wants to process MIDI events. This will be called directly
+ * before the call to process audio.
+ * @param pluginPtr self
+ * @param midiEvents List of events to process. This should be non-empty, as
+ * this function is not called when there are no events to process.
+ */
+typedef void (*PluginProcessMidiEventsFunc)(void *pluginPtr, LinkedList midiEvents);
+/**
+ * Set a parameter within a plugin
+ * @param pluginPtr self
+ * @param index Parameter index
+ * @param value New value
+ */
+typedef boolByte (*PluginSetParameterFunc)(void *pluginPtr, unsigned int index, float value);
+/**
+ * Called once before audio processing begins. Some interfaces provide hooks for
+ * a plugin to prepare itself before audio blocks are sent to it.
+ * @param pluginPtr self
+ */
+typedef void (*PluginPrepareForProcessingFunc)(void *pluginPtr);
+/**
+ * Called when the plugin is to be uninitialized and closed.
+ * @param pluginPtr self
+ */
+typedef void (*PluginCloseFunc)(void *pluginPtr);
+/**
+ * Pointer to the free routine for the plugin interface
+ * @param pluginPtr self
+ */
+typedef void (*FreePluginDataFunc)(void *pluginDataPtr);
+
+typedef struct {
+ PluginInterfaceType interfaceType;
+ PluginType pluginType;
+ CharString pluginName;
+ CharString pluginLocation;
+ CharString pluginAbsolutePath;
+
+ PluginOpenFunc openPlugin;
+ PluginDisplayInfoFunc displayInfo;
+ PluginGetSettingFunc getSetting;
+ PluginProcessAudioFunc processAudio;
+ PluginProcessMidiEventsFunc processMidiEvents;
+ PluginSetParameterFunc setParameter;
+ PluginPrepareForProcessingFunc prepareForProcessing;
+ PluginCloseFunc closePlugin;
+ FreePluginDataFunc freePluginData;
+ SampleBuffer inputBuffer;
+ SampleBuffer outputBuffer;
+ boolByte isOpen;
+
+ void *extraData;
+} PluginMembers;
+
+/**
+ * One of the base classes of the API, this represents a plugin. Currently only
+ * instrument and effect plugins are supported.
+ */
+typedef PluginMembers *Plugin;
+
+/**
+ * Create a plugin instance. The plugin interface type will be automatically
+ * determined.
+ * @param pluginName Plugin name. For plugins which supports loading directly
+ * from the filesystem, this argument may also be an absolute path.
+ * @param pluginRoot User-provided search root path. May be NULL or empty.
+ * @return Initialized object, or NULL if no matching plugin was found
+ */
+Plugin pluginFactory(const CharString pluginName, const CharString pluginRoot);
+
+/**
+ * List all known plugins of all known types on the system.
+ * @param pluginRoot User-provided search root path
+ */
+void listAvailablePlugins(const CharString pluginRoot);
+
+/**
+ * Open a plugin.
+ * @param self
+ */
+boolByte openPlugin(Plugin self);
+
+/**
+ * Close a plugin.
+ * @param self
+ */
+boolByte closePlugin(Plugin self);
+
+/**
+* Create a new plugin. Considered "protected", only subclasses of Plugin should
+* directly call this.
+* @param interfaceType Plugin interface type
+* @param pluginType Plugin type
+* @return Plugin initialized base Plugin struct
+*/
+Plugin _newPlugin(PluginInterfaceType interfaceType, PluginType pluginType);
+
+/**
+ * Release a plugin and all of its associated resources. Note that the plugin
+ * must be closed before this is called, or else resources will be leaked.
+ * @param self
+ */
+void freePlugin(Plugin self);
+
+#endif
diff --git a/source/plugin/PluginChain.c b/source/plugin/PluginChain.c
new file mode 100644
index 0000000..bbf2f9e
--- /dev/null
+++ b/source/plugin/PluginChain.c
@@ -0,0 +1,420 @@
+//
+// PluginChain.c - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "logging/EventLogger.h"
+#include "plugin/PluginChain.h"
+#include "audio/AudioSettings.h"
+
+PluginChain pluginChainInstance = NULL;
+
+PluginChain getPluginChain(void)
+{
+ return pluginChainInstance;
+}
+
+void initPluginChain(void)
+{
+ pluginChainInstance = (PluginChain)malloc(sizeof(PluginChainMembers));
+
+ pluginChainInstance->numPlugins = 0;
+ pluginChainInstance->plugins = (Plugin *)malloc(sizeof(Plugin) * MAX_PLUGINS);
+ pluginChainInstance->presets = (PluginPreset *)malloc(sizeof(PluginPreset) * MAX_PLUGINS);
+ pluginChainInstance->audioTimers = (TaskTimer *)malloc(sizeof(TaskTimer) * MAX_PLUGINS);
+ pluginChainInstance->midiTimers = (TaskTimer *)malloc(sizeof(TaskTimer) * MAX_PLUGINS);
+
+ pluginChainInstance->_realtime = false;
+ pluginChainInstance->_realtimeTimer = NULL;
+}
+
+boolByte pluginChainAppend(PluginChain self, Plugin plugin, PluginPreset preset)
+{
+ if (plugin == NULL) {
+ return false;
+ } else if (self->numPlugins + 1 >= MAX_PLUGINS) {
+ logError("Could not add plugin '%s', maximum number reached", plugin->pluginName->data);
+ return false;
+ } else if (!openPlugin(plugin)) {
+ return false;
+ } else {
+ self->plugins[self->numPlugins] = plugin;
+ self->presets[self->numPlugins] = preset;
+ self->audioTimers[self->numPlugins] = newTaskTimer(plugin->pluginName, "Audio Processing");
+ self->midiTimers[self->numPlugins] = newTaskTimer(plugin->pluginName, "MIDI Processing");
+ self->numPlugins++;
+ return true;
+ }
+}
+
+boolByte pluginChainAddFromArgumentString(PluginChain pluginChain, const CharString argumentString, const CharString userSearchPath)
+{
+ // Expect a semicolon-separated string of plugins with comma separators for preset names
+ // Example: plugin1,preset1name;plugin2,preset2name
+ char *substringStart;
+ char *pluginSeparator;
+ char *endChar;
+ CharString pluginNameBuffer = NULL;
+ CharString presetNameBuffer = NULL;
+ char *presetSeparator;
+ PluginPreset preset;
+ Plugin plugin;
+ size_t substringLength;
+
+ if (charStringIsEmpty(argumentString)) {
+ logWarn("Plugin chain string is empty");
+ return false;
+ }
+
+ substringStart = argumentString->data;
+ pluginSeparator = strchr(argumentString->data, CHAIN_STRING_PLUGIN_SEPARATOR);
+ endChar = argumentString->data + strlen(argumentString->data);
+
+ do {
+ if (pluginSeparator == NULL) {
+ substringLength = strlen(argumentString->data);
+ } else {
+ substringLength = pluginSeparator - substringStart;
+ }
+
+ pluginNameBuffer = newCharString();
+ strncpy(pluginNameBuffer->data, substringStart, substringLength);
+
+ // Look for the separator for presets to load into these plugins
+ presetNameBuffer = newCharString();
+ presetSeparator = strchr(pluginNameBuffer->data, CHAIN_STRING_PROGRAM_SEPARATOR);
+
+ if (presetSeparator != NULL) {
+ // Null-terminate this string to force it to end, then extract preset name from next char
+ *presetSeparator = '\0';
+ strncpy(presetNameBuffer->data, presetSeparator + 1, strlen(presetSeparator + 1));
+ }
+
+ // Find preset for this plugin (if given)
+ preset = NULL;
+
+ if (strlen(presetNameBuffer->data) > 0) {
+ logInfo("Opening preset '%s' for plugin", presetNameBuffer->data);
+ preset = pluginPresetFactory(presetNameBuffer);
+ }
+
+ // Guess the plugin type from the file extension, search root, etc.
+ plugin = pluginFactory(pluginNameBuffer, userSearchPath);
+
+ if (plugin != NULL) {
+ if (!pluginChainAppend(pluginChain, plugin, preset)) {
+ logError("Plugin '%s' could not be added to the chain", pluginNameBuffer->data);
+ free(pluginNameBuffer);
+ free(presetNameBuffer);
+ return false;
+ }
+ }
+
+ if (pluginSeparator == NULL) {
+ break;
+ } else {
+ substringStart = pluginSeparator + 1;
+ pluginSeparator = strchr(pluginSeparator + 1, CHAIN_STRING_PLUGIN_SEPARATOR);
+ }
+ } while (substringStart < endChar);
+
+ freeCharString(pluginNameBuffer);
+ freeCharString(presetNameBuffer);
+ return true;
+}
+
+static boolByte _loadPresetForPlugin(Plugin plugin, PluginPreset preset)
+{
+ if (pluginPresetIsCompatibleWith(preset, plugin)) {
+ if (!preset->openPreset(preset)) {
+ logError("Could not open preset '%s'", preset->presetName->data);
+ return false;
+ }
+
+ if (!preset->loadPreset(preset, plugin)) {
+ logError("Could not load preset '%s' in plugin '%s'", preset->presetName->data, plugin->pluginName->data);
+ return false;
+ }
+
+ logInfo("Loaded preset '%s' in plugin '%s'", preset->presetName->data, plugin->pluginName->data);
+ return true;
+ } else {
+ logError("Preset '%s' is not a compatible format for plugin", preset->presetName->data);
+ return false;
+ }
+}
+
+ReturnCodes pluginChainInitialize(PluginChain pluginChain)
+{
+ Plugin plugin;
+ PluginPreset preset;
+ unsigned int i;
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ plugin = pluginChain->plugins[i];
+
+ if (!openPlugin(plugin)) {
+ return RETURN_CODE_PLUGIN_ERROR;
+ } else {
+ if (i > 0 && plugin->pluginType == PLUGIN_TYPE_INSTRUMENT) {
+ logError("Instrument plugin '%s' must be first in the chain", plugin->pluginName->data);
+ return RETURN_CODE_INVALID_PLUGIN_CHAIN;
+ } else if (plugin->pluginType == PLUGIN_TYPE_UNKNOWN) {
+ logError("Plugin '%s' has unknown type; It was probably not loaded correctly", plugin->pluginName->data);
+ return RETURN_CODE_PLUGIN_ERROR;
+ } else if (plugin->pluginType == PLUGIN_TYPE_UNSUPPORTED) {
+ logError("Plugin '%s' is of unsupported type", plugin->pluginName->data);
+ return RETURN_CODE_PLUGIN_ERROR;
+ }
+
+ preset = pluginChain->presets[i];
+
+ if (preset != NULL) {
+ if (!_loadPresetForPlugin(plugin, preset)) {
+ return RETURN_CODE_INVALID_ARGUMENT;
+ }
+ }
+ }
+ }
+
+ return RETURN_CODE_SUCCESS;
+}
+
+void pluginChainInspect(PluginChain pluginChain)
+{
+ Plugin plugin;
+ unsigned int i;
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ plugin = pluginChain->plugins[i];
+ plugin->displayInfo(plugin);
+ }
+}
+
+void pluginChainPrepareForProcessing(PluginChain self)
+{
+ Plugin plugin;
+ unsigned int i;
+
+ for (i = 0; i < self->numPlugins; i++) {
+ plugin = self->plugins[i];
+ plugin->prepareForProcessing(plugin);
+ }
+}
+
+int pluginChainGetMaximumTailTimeInMs(PluginChain pluginChain)
+{
+ Plugin plugin;
+ int tailTime;
+ int maxTailTime = 0;
+ unsigned int i;
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ plugin = pluginChain->plugins[i];
+ tailTime = plugin->getSetting(plugin, PLUGIN_SETTING_TAIL_TIME_IN_MS);
+
+ if (tailTime > maxTailTime) {
+ maxTailTime = tailTime;
+ }
+ }
+
+ return maxTailTime;
+}
+
+unsigned long pluginChainGetProcessingDelay(PluginChain self)
+{
+ unsigned long processingDelay = 0;
+ unsigned int i;
+
+ for (i = 0; i < self->numPlugins; i++) {
+ Plugin plugin = self->plugins[i];
+ processingDelay += plugin->getSetting(plugin, PLUGIN_INITIAL_DELAY);
+ }
+
+ return processingDelay;
+}
+
+typedef struct {
+ Plugin plugin;
+ boolByte success;
+} _PluginChainSetParameterPassData;
+
+void _pluginChainSetParameter(void *item, void *userData)
+{
+ // Expect that the linked list contains CharStrings, single that is what is
+ // being given from the command line.
+ char *parameterValue = (char *)item;
+ _PluginChainSetParameterPassData *passData = (_PluginChainSetParameterPassData *)userData;
+ Plugin plugin = passData->plugin;
+ char *comma = NULL;
+ int index;
+ float value;
+
+ // If a previous attempt to set a parameter failed, then return right away
+ // since this method will return false anyways.
+ if (!passData->success) {
+ return;
+ }
+
+ // TODO: Need a "pair" type, this string parsing is done several times in the codebase
+ comma = strchr(parameterValue, ',');
+
+ if (comma == NULL) {
+ logError("Malformed parameter string, see --help parameter for usage");
+ return;
+ }
+
+ *comma = '\0';
+ index = (int)strtod(parameterValue, NULL);
+ value = (float)strtod(comma + 1, NULL);
+ logDebug("Set parameter %d to %f", index, value);
+ passData->success = plugin->setParameter(plugin, (unsigned int)index, value);
+}
+
+boolByte pluginChainSetParameters(PluginChain self, const LinkedList parameters)
+{
+ _PluginChainSetParameterPassData passData;
+ passData.plugin = self->plugins[0];
+ passData.success = true;
+ logDebug("Setting parameters on head plugin in chain");
+ linkedListForeach(parameters, _pluginChainSetParameter, &passData);
+ return passData.success;
+}
+
+void pluginChainSetRealtime(PluginChain self, boolByte realtime)
+{
+ self->_realtime = realtime;
+
+ if (realtime) {
+ self->_realtimeTimer = newTaskTimerWithCString("PluginChain", "Realtime");
+ } else if (self->_realtimeTimer) {
+ freeTaskTimer(self->_realtimeTimer);
+ }
+}
+
+void pluginChainProcessAudio(PluginChain pluginChain, SampleBuffer inBuffer, SampleBuffer outBuffer)
+{
+ Plugin plugin;
+ unsigned int i;
+ double processingTimeInMs;
+ double totalProcessingTimeInMs;
+ const double maxProcessingTimeInMs = inBuffer->blocksize * 1000.0 / getSampleRate();
+
+ if (pluginChain->_realtime) {
+ taskTimerStart(pluginChain->_realtimeTimer);
+ }
+
+ SampleBuffer formerOutputBuffer = inBuffer;
+ SampleBuffer nextInputBuffer = NULL;
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ plugin = pluginChain->plugins[i];
+ logDebug("Processing audio with plugin '%s'", plugin->pluginName->data);
+ nextInputBuffer = plugin->inputBuffer;
+ nextInputBuffer->blocksize = formerOutputBuffer->blocksize;
+ sampleBufferCopyAndMapChannels(nextInputBuffer, formerOutputBuffer);
+ plugin->outputBuffer->blocksize = plugin->inputBuffer->blocksize;
+ taskTimerStart(pluginChain->audioTimers[i]);
+ plugin->processAudio(plugin, plugin->inputBuffer, plugin->outputBuffer);
+ processingTimeInMs = taskTimerStop(pluginChain->audioTimers[i]);
+
+ if (processingTimeInMs > maxProcessingTimeInMs && pluginChain->_realtime) {
+ logWarn("Possible dropout! Plugin '%s' spent %dms processing time (%dms max)",
+ plugin->pluginName->data, (int)processingTimeInMs, (int)maxProcessingTimeInMs);
+ } else {
+ logDebug("Plugin '%s' spent %dms processing (%d%% effective CPU usage)",
+ plugin->pluginName->data, (int)processingTimeInMs,
+ (int)(processingTimeInMs / maxProcessingTimeInMs));
+ }
+
+ formerOutputBuffer = plugin->outputBuffer;
+ }
+
+ nextInputBuffer = outBuffer;
+ nextInputBuffer->blocksize = formerOutputBuffer->blocksize;
+ sampleBufferCopyAndMapChannels(nextInputBuffer, formerOutputBuffer);
+
+ if (pluginChain->_realtime) {
+ totalProcessingTimeInMs = taskTimerStop(pluginChain->_realtimeTimer);
+
+ if (totalProcessingTimeInMs < maxProcessingTimeInMs) {
+ taskTimerSleep(maxProcessingTimeInMs - totalProcessingTimeInMs);
+ }
+ }
+}
+
+void pluginChainProcessMidi(PluginChain pluginChain, LinkedList midiEvents)
+{
+ Plugin plugin;
+
+ if (midiEvents->item != NULL) {
+ logDebug("Processing plugin chain MIDI events");
+ // Right now, we only process MIDI in the first plugin in the chain
+ // TODO: Is this really the correct behavior? How do other sequencers do it?
+ plugin = pluginChain->plugins[0];
+ taskTimerStart(pluginChain->midiTimers[0]);
+ plugin->processMidiEvents(plugin, midiEvents);
+ taskTimerStop(pluginChain->midiTimers[0]);
+ }
+}
+
+void pluginChainShutdown(PluginChain pluginChain)
+{
+ Plugin plugin;
+ unsigned int i;
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ plugin = pluginChain->plugins[i];
+ logInfo("Closing plugin '%s'", plugin->pluginName->data);
+ closePlugin(plugin);
+ }
+}
+
+void freePluginChain(PluginChain pluginChain)
+{
+ unsigned int i;
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ freePluginPreset(pluginChain->presets[i]);
+ freePlugin(pluginChain->plugins[i]);
+ freeTaskTimer(pluginChain->audioTimers[i]);
+ freeTaskTimer(pluginChain->midiTimers[i]);
+ }
+
+ free(pluginChain->presets);
+ free(pluginChain->plugins);
+ free(pluginChain->audioTimers);
+ free(pluginChain->midiTimers);
+
+ if (pluginChain->_realtime) {
+ freeTaskTimer(pluginChain->_realtimeTimer);
+ }
+
+ free(pluginChain);
+}
diff --git a/source/plugin/PluginChain.h b/source/plugin/PluginChain.h
new file mode 100644
index 0000000..62c58a1
--- /dev/null
+++ b/source/plugin/PluginChain.h
@@ -0,0 +1,165 @@
+//
+// PluginChain.h - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginChain_h
+#define MrsWatson_PluginChain_h
+
+#include "app/ReturnCodes.h"
+#include "base/LinkedList.h"
+#include "plugin/Plugin.h"
+#include "plugin/PluginPreset.h"
+#include "time/TaskTimer.h"
+
+#define MAX_PLUGINS 8
+#define CHAIN_STRING_PLUGIN_SEPARATOR ';'
+#define CHAIN_STRING_PROGRAM_SEPARATOR ','
+
+typedef struct {
+ unsigned int numPlugins;
+ Plugin *plugins;
+ PluginPreset *presets;
+ TaskTimer *audioTimers;
+ TaskTimer *midiTimers;
+
+ // Private fields
+ boolByte _realtime;
+ TaskTimer _realtimeTimer;
+} PluginChainMembers;
+
+/**
+ * Class which holds multiple plugins which process audio in serial. Only one
+ * instrument may be present in a plugin chain.
+ */
+typedef PluginChainMembers *PluginChain;
+
+/**
+ * Get a reference to the global plugin chain instance.
+ * @return Reference to global plugin chain, or NULL if the global instance has
+ * not yet been initialized.
+ */
+PluginChain getPluginChain(void);
+
+/**
+ * Initialize the global plugin chain instance. Should be called fairly
+ * early in the program initialization.
+ */
+void initPluginChain(void);
+
+/**
+ * Append a plugin to the end of the chain
+ * @param self
+ * @param plugin Plugin to add
+ * @param preset Preset to be loaded into the plugin. If no preset is desired,
+ * passed NULL here.
+ * @return True if the plugin could be added to the end of the chain
+ */
+boolByte pluginChainAppend(PluginChain self, Plugin plugin, PluginPreset preset);
+
+// TODO: Deprecate and remove this function
+boolByte pluginChainAddFromArgumentString(PluginChain self, const CharString argumentString, const CharString userSearchPath);
+
+/**
+ * Open and initialize all plugins in the chain.
+ * @param self
+ * @return RETURN_CODE_SUCCESS on success, other code on failure
+ */
+ReturnCodes pluginChainInitialize(PluginChain self);
+
+/**
+ * Inspect each plugin in the chain
+ * @param self
+ */
+void pluginChainInspect(PluginChain self);
+
+/**
+ * Get the maximum amount of tail time (post*processing time with empty input)
+ * needed for the chain. This is essentially the largest tail time value for any
+ * plug-in in the chain.
+ * @param self
+ * @return Maximum tail time, in milliseconds
+ */
+int pluginChainGetMaximumTailTimeInMs(PluginChain self);
+
+/**
+ * Get the total processing delay in frames.
+ * @param self
+ * @return Total processing delay, in frames.
+ */
+unsigned long pluginChainGetProcessingDelay(PluginChain self);
+
+/**
+ * Set parameters on the first plugin in a chain.
+ * @param self
+ * @param parameters List of parameters to be applied
+ * @return True if all parameters were set, false otherwise
+ */
+boolByte pluginChainSetParameters(PluginChain self, const LinkedList parameters);
+
+/**
+ * Set realtime mode for the plugin chain. When set, calls to pluginChainProcessAudio()
+ * will sleep for the additional time required to process the block in realtime.
+ * @param realtime True to enable realtime mode, false to disable (default)
+ * @param self
+ */
+void pluginChainSetRealtime(PluginChain self, boolByte realtime);
+
+/**
+ * Prepare each plugin in the chain for processing. This should be called before
+ * the first block of audio is sent to the chain.
+ * @param self
+ */
+void pluginChainPrepareForProcessing(PluginChain self);
+
+/**
+ * Process a single block of samples through each plugin in the chain.
+ * @param self
+ * @param inBuffer Input sample block
+ * @param outBuffer Output sample block
+ */
+void pluginChainProcessAudio(PluginChain self, SampleBuffer inBuffer, SampleBuffer outBuffer);
+
+/**
+ * Send a list of MIDI events to be processed by the chain. Currently, only the
+ * first plugin in the chain will receive these events.
+ * @param self
+ * @param midiEvents List of events to process
+ */
+void pluginChainProcessMidi(PluginChain self, LinkedList midiEvents);
+
+/**
+ * Close all plugins in the chain
+ * @param self
+ */
+void pluginChainShutdown(PluginChain self);
+
+/**
+ * Free the plugin chain and all associated resources (including all plugins)
+ * @param self
+ */
+void freePluginChain(PluginChain self);
+
+#endif
diff --git a/source/plugin/PluginGain.c b/source/plugin/PluginGain.c
new file mode 100644
index 0000000..d2f21b9
--- /dev/null
+++ b/source/plugin/PluginGain.c
@@ -0,0 +1,133 @@
+//
+// PluginGain.c - MrsWatson
+// Created by Nik Reiman on 26 May 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 "audio/SampleBuffer.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginGain.h"
+
+const char *kInternalPluginGainName = INTERNAL_PLUGIN_PREFIX "gain";
+
+static void _pluginGainEmpty(void *pluginPtr)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginGainOpen(void *pluginPtr)
+{
+ return true;
+}
+
+static void _pluginGainGetAbsolutePath(void *pluginPtr, CharString outPath)
+{
+ // Internal plugins don't have a path, and thus can't be copied. So just copy
+ // an empty string here and let any callers needing the absolute path to check
+ // for this value before doing anything important.
+ charStringClear(outPath);
+}
+
+static void _pluginGainDisplayInfo(void *pluginPtr)
+{
+ logInfo("Information for Internal plugin '%s'", kInternalPluginGainName);
+ logInfo("Type: effect, parameters: none");
+ logInfo("Description: a basic gain effect");
+}
+
+static int _pluginGainGetSetting(void *pluginPtr, PluginSetting pluginSetting)
+{
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS:
+ return 0;
+
+ case PLUGIN_NUM_INPUTS:
+ return 2;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return 2;
+
+ default:
+ return 0;
+ }
+}
+
+static void _pluginGainProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+{
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginGainSettings settings = (PluginGainSettings)plugin->extraData;
+ unsigned long channel, sample;
+
+ sampleBufferCopyAndMapChannels(outputs, inputs);
+
+ for (channel = 0; channel < outputs->numChannels; ++channel) {
+ for (sample = 0; sample < outputs->blocksize; ++sample) {
+ outputs->samples[channel][sample] *= settings->gain;
+ }
+ }
+}
+
+static void _pluginGainProcessMidiEvents(void *pluginPtr, LinkedList midiEvents)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginGainSetParameter(void *pluginPtr, unsigned int i, float value)
+{
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginGainSettings settings = (PluginGainSettings)plugin->extraData;
+
+ switch (i) {
+ case PLUGIN_GAIN_SETTINGS_GAIN:
+ settings->gain = value;
+ return true;
+
+ default:
+ logError("Attempt to set invalid parameter %d on internal gain plugin", i);
+ return false;
+ }
+}
+
+Plugin newPluginGain(const CharString pluginName)
+{
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_EFFECT);
+ PluginGainSettings settings = (PluginGainSettings)malloc(sizeof(PluginGainSettingsMembers));
+
+ charStringCopy(plugin->pluginName, pluginName);
+ charStringCopyCString(plugin->pluginLocation, "Internal");
+
+ plugin->openPlugin = _pluginGainOpen;
+ plugin->displayInfo = _pluginGainDisplayInfo;
+ plugin->getSetting = _pluginGainGetSetting;
+ plugin->prepareForProcessing = _pluginGainEmpty;
+ plugin->processAudio = _pluginGainProcessAudio;
+ plugin->processMidiEvents = _pluginGainProcessMidiEvents;
+ plugin->setParameter = _pluginGainSetParameter;
+ plugin->closePlugin = _pluginGainEmpty;
+ plugin->freePluginData = _pluginGainEmpty;
+
+ settings->gain = 1.0f;
+ plugin->extraData = settings;
+ return plugin;
+}
diff --git a/source/plugin/PluginGain.h b/source/plugin/PluginGain.h
new file mode 100644
index 0000000..b1d6b65
--- /dev/null
+++ b/source/plugin/PluginGain.h
@@ -0,0 +1,47 @@
+//
+// PluginGain.h - MrsWatson
+// Created by Nik Reiman on 26 May 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginGain_h
+#define MrsWatson_PluginGain_h
+
+#include "plugin/Plugin.h"
+
+extern const char *kInternalPluginGainName;
+
+typedef enum {
+ PLUGIN_GAIN_SETTINGS_GAIN,
+ PLUGIN_GAIN_NUM_SETTINGS
+} PluginGainSettingsIndex;
+
+typedef struct {
+ float gain;
+} PluginGainSettingsMembers;
+typedef PluginGainSettingsMembers *PluginGainSettings;
+
+Plugin newPluginGain(const CharString pluginName);
+
+#endif
diff --git a/source/plugin/PluginLimiter.c b/source/plugin/PluginLimiter.c
new file mode 100644
index 0000000..4ae20d2
--- /dev/null
+++ b/source/plugin/PluginLimiter.c
@@ -0,0 +1,121 @@
+//
+// PluginLimiter.c - MrsWatson
+// Created by Nik Reiman on 26 May 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 "audio/SampleBuffer.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginLimiter.h"
+
+const char *kInternalPluginLimiterName = INTERNAL_PLUGIN_PREFIX "limiter";
+
+static void _pluginLimiterEmpty(void *pluginPtr)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginLimiterOpen(void *pluginPtr)
+{
+ return true;
+}
+
+static void _pluginLimiterGetAbsolutePath(void *pluginPtr, CharString outPath)
+{
+ // Internal plugins don't have a path, and thus can't be copied. So just copy
+ // an empty string here and let any callers needing the absolute path to check
+ // for this value before doing anything important.
+ charStringClear(outPath);
+}
+
+static void _pluginLimiterDisplayInfo(void *pluginPtr)
+{
+ logInfo("Information for Internal plugin '%s'", kInternalPluginLimiterName);
+ logInfo("Type: effect, parameters: none");
+ logInfo("Description: a brickwall limiter effect");
+}
+
+static int _pluginLimiterGetSetting(void *pluginPtr, PluginSetting pluginSetting)
+{
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS:
+ return 0;
+
+ case PLUGIN_NUM_INPUTS:
+ return 2;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return 2;
+
+ default:
+ return 0;
+ }
+}
+
+static void _pluginLimiterProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+{
+ unsigned long channel, sample;
+
+ sampleBufferCopyAndMapChannels(outputs, inputs);
+
+ for (channel = 0; channel < outputs->numChannels; ++channel) {
+ for (sample = 0; sample < outputs->blocksize; ++sample) {
+ if (outputs->samples[channel][sample] > 1.0f) {
+ outputs->samples[channel][sample] = 1.0f;
+ } else if (outputs->samples[channel][sample] < -1.0f) {
+ outputs->samples[channel][sample] = -1.0f;
+ }
+ }
+ }
+}
+
+static void _pluginLimiterProcessMidiEvents(void *pluginPtr, LinkedList midiEvents)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginLimiterSetParameter(void *pluginPtr, unsigned int i, float value)
+{
+ return false;
+}
+
+Plugin newPluginLimiter(const CharString pluginName)
+{
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_EFFECT);
+ charStringCopy(plugin->pluginName, pluginName);
+ charStringCopyCString(plugin->pluginLocation, "Internal");
+
+ plugin->openPlugin = _pluginLimiterOpen;
+ plugin->displayInfo = _pluginLimiterDisplayInfo;
+ plugin->getSetting = _pluginLimiterGetSetting;
+ plugin->prepareForProcessing = _pluginLimiterEmpty;
+ plugin->processAudio = _pluginLimiterProcessAudio;
+ plugin->processMidiEvents = _pluginLimiterProcessMidiEvents;
+ plugin->setParameter = _pluginLimiterSetParameter;
+ plugin->closePlugin = _pluginLimiterEmpty;
+ plugin->freePluginData = _pluginLimiterEmpty;
+
+ plugin->extraData = NULL;
+ return plugin;
+}
diff --git a/source/plugin/PluginLimiter.h b/source/plugin/PluginLimiter.h
new file mode 100644
index 0000000..af6ddd7
--- /dev/null
+++ b/source/plugin/PluginLimiter.h
@@ -0,0 +1,37 @@
+//
+// PluginLimiter.h - MrsWatson
+// Created by Nik Reiman on 26 May 14.
+// Copyright (c) 2014 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginLimiter_h
+#define MrsWatson_PluginLimiter_h
+
+#include "plugin/Plugin.h"
+
+extern const char *kInternalPluginLimiterName;
+
+Plugin newPluginLimiter(const CharString pluginName);
+
+#endif
diff --git a/source/plugin/PluginPassthru.c b/source/plugin/PluginPassthru.c
new file mode 100644
index 0000000..353eeed
--- /dev/null
+++ b/source/plugin/PluginPassthru.c
@@ -0,0 +1,105 @@
+//
+// PluginPassthru.c - MrsWatson
+// Created by Nik Reiman on 8/17/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdlib.h>
+
+#include "logging/EventLogger.h"
+#include "plugin/PluginPassthru.h"
+
+const char *kInternalPluginPassthruName = INTERNAL_PLUGIN_PREFIX "passthru";
+
+static void _pluginPassthruEmpty(void *pluginPtr)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginPassthruOpen(void *pluginPtr)
+{
+ return true;
+}
+
+static void _pluginPassthruDisplayInfo(void *pluginPtr)
+{
+ logInfo("Information for Internal plugin '%s'", kInternalPluginPassthruName);
+ logInfo("Type: effect, parameters: none");
+ logInfo("Description: a passthru effect which copies input data to the output");
+}
+
+static int _pluginPassthruGetSetting(void *pluginPtr, PluginSetting pluginSetting)
+{
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS:
+ return 0;
+
+ case PLUGIN_NUM_INPUTS:
+ return 2;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return 2;
+
+ case PLUGIN_INITIAL_DELAY:
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+static void _pluginPassthruProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+{
+ sampleBufferCopyAndMapChannels(outputs, inputs);
+}
+
+static void _pluginPassthruProcessMidiEvents(void *pluginPtr, LinkedList midiEvents)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginPassthruSetParameter(void *pluginPtr, unsigned int i, float value)
+{
+ return false;
+}
+
+Plugin newPluginPassthru(const CharString pluginName)
+{
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_EFFECT);
+ charStringCopy(plugin->pluginName, pluginName);
+ charStringCopyCString(plugin->pluginLocation, "Internal");
+
+ plugin->openPlugin = _pluginPassthruOpen;
+ plugin->displayInfo = _pluginPassthruDisplayInfo;
+ plugin->getSetting = _pluginPassthruGetSetting;
+ plugin->prepareForProcessing = _pluginPassthruEmpty;
+ plugin->processAudio = _pluginPassthruProcessAudio;
+ plugin->processMidiEvents = _pluginPassthruProcessMidiEvents;
+ plugin->setParameter = _pluginPassthruSetParameter;
+ plugin->closePlugin = _pluginPassthruEmpty;
+ plugin->freePluginData = _pluginPassthruEmpty;
+
+ plugin->extraData = NULL;
+ return plugin;
+}
diff --git a/source/plugin/PluginPassthru.h b/source/plugin/PluginPassthru.h
new file mode 100644
index 0000000..61848ad
--- /dev/null
+++ b/source/plugin/PluginPassthru.h
@@ -0,0 +1,37 @@
+//
+// PluginPassthru.h - MrsWatson
+// Created by Nik Reiman on 8/17/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginPassthru_h
+#define MrsWatson_PluginPassthru_h
+
+#include "plugin/Plugin.h"
+
+extern const char *kInternalPluginPassthruName;
+
+Plugin newPluginPassthru(const CharString pluginName);
+
+#endif
diff --git a/source/plugin/PluginPreset.c b/source/plugin/PluginPreset.c
new file mode 100644
index 0000000..4c22211
--- /dev/null
+++ b/source/plugin/PluginPreset.c
@@ -0,0 +1,104 @@
+//
+// PluginPreset.c - MrsWatson
+// Created by Nik Reiman on 1/13/12.
+// Copyright (c) 2011 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/File.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginPreset.h"
+#include "plugin/PluginPresetFxp.h"
+#include "plugin/PluginPresetInternalProgram.h"
+
+static PluginPresetType _pluginPresetGuessType(const CharString presetName)
+{
+ if (presetName == NULL || charStringIsEmpty(presetName)) {
+ return PRESET_TYPE_INVALID;
+ }
+
+ File presetFile = newFileWithPath(presetName);
+ CharString fileExtension = fileGetExtension(presetFile);
+ freeFile(presetFile);
+
+ if (fileExtension == NULL) {
+ for (size_t i = 0; i < strlen(presetName->data); i++) {
+ if (!charStringIsNumber(presetName, i)) {
+ return PRESET_TYPE_INVALID;
+ }
+ }
+
+ // If the preset name is all numeric, then it's an internal program number
+ return PRESET_TYPE_INTERNAL_PROGRAM;
+ } else if (charStringIsEqualToCString(fileExtension, "fxp", true)) {
+ freeCharString(fileExtension);
+ return PRESET_TYPE_FXP;
+ } else {
+ logCritical("Preset '%s' does not match any supported type", presetName->data);
+ freeCharString(fileExtension);
+ return PRESET_TYPE_INVALID;
+ }
+}
+
+PluginPreset pluginPresetFactory(const CharString presetName)
+{
+ PluginPresetType presetType = _pluginPresetGuessType(presetName);
+
+ switch (presetType) {
+ case PRESET_TYPE_FXP:
+ return newPluginPresetFxp(presetName);
+
+ case PRESET_TYPE_INTERNAL_PROGRAM:
+ return newPluginPresetInternalProgram(presetName);
+
+ default:
+ return NULL;
+ }
+}
+
+void pluginPresetSetCompatibleWith(PluginPreset pluginPreset, PluginInterfaceType interfaceType)
+{
+ pluginPreset->compatiblePluginTypes |= (1 << interfaceType);
+}
+
+boolByte pluginPresetIsCompatibleWith(const PluginPreset pluginPreset, const Plugin plugin)
+{
+ return (pluginPreset->compatiblePluginTypes & (1 << plugin->interfaceType));
+}
+
+void freePluginPreset(PluginPreset pluginPreset)
+{
+ if (pluginPreset != NULL) {
+ if (pluginPreset->extraData != NULL) {
+ pluginPreset->freePresetData(pluginPreset->extraData);
+ free(pluginPreset->extraData);
+ }
+
+ freeCharString(pluginPreset->presetName);
+ free(pluginPreset);
+ }
+}
diff --git a/source/plugin/PluginPreset.h b/source/plugin/PluginPreset.h
new file mode 100644
index 0000000..76e2b79
--- /dev/null
+++ b/source/plugin/PluginPreset.h
@@ -0,0 +1,111 @@
+//
+// PluginPreset.h - MrsWatson
+// Created by Nik Reiman on 1/13/12.
+// Copyright (c) 2011 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginPreset_h
+#define MrsWatson_PluginPreset_h
+
+#include "base/CharString.h"
+#include "plugin/Plugin.h"
+
+typedef enum {
+ PRESET_TYPE_INVALID,
+ PRESET_TYPE_FXP,
+ PRESET_TYPE_INTERNAL_PROGRAM,
+ NUM_PRESET_TYPES
+} PluginPresetType;
+
+/**
+ * Called when the preset is to be loaded from the filesystem
+ * @param pluginPresetPtr self
+ */
+typedef boolByte (*OpenPresetFunc)(void *pluginPresetPtr);
+/**
+ * Called when the preset is to be loaded into a plugin
+ * @param plugin Plugin which will receive the preset. This should check that
+ * the preset is compatible with the given plugging before loading it.
+ * @param pluginPresetPtr self
+ * @return True on success, false on failure
+ */
+typedef boolByte (*LoadPresetFunc)(void *pluginPresetPtr, Plugin plugin);
+/**
+ * Free a preset and it's related data
+ * @param pluginPresetPtr self
+ */
+typedef void (*FreePresetDataFunc)(void *pluginPresetPtr);
+
+typedef struct {
+ PluginPresetType presetType;
+ CharString presetName;
+ unsigned int compatiblePluginTypes;
+
+ OpenPresetFunc openPreset;
+ LoadPresetFunc loadPreset;
+ FreePresetDataFunc freePresetData;
+
+ void *extraData;
+} PluginPresetMembers;
+
+/**
+ * Class which is used to hold preset data which will be loaded into a plugin
+ * before audio processing.
+ */
+typedef PluginPresetMembers *PluginPreset;
+
+/**
+ * Create a new plugin preset from a given name. Usually this function inspects
+ * the name and guesses an appropriate handler based on the file extension.
+ * @param presetName Preset name
+ * @return Initialized PluginPreset or NULL if no preset type found
+ */
+PluginPreset pluginPresetFactory(const CharString presetName);
+
+/**
+ * Check if a preset will be compatible with a plugin. Some plugin interface
+ * types have extra safety checks to make sure that a preset must match the
+ * plugin's ID, this call essentially wraps this functionality.
+ * @param self
+ * @param plugin Plugin to check against
+ * @return True if the preset can be loaded into the plugin
+ */
+boolByte pluginPresetIsCompatibleWith(const PluginPreset self, const Plugin plugin);
+
+/**
+ * Set interface compatibility for a preset type. This function should only be
+ * called from a PluginPreset subclass, you should not have to manually call
+ * this in normal host operations.
+ * @param self
+ * @param interfaceType Interface type to set
+ */
+void pluginPresetSetCompatibleWith(PluginPreset self, PluginInterfaceType interfaceType);
+
+/**
+ * Free a PluginPreset and all associated resources
+ * @param self
+ */
+void freePluginPreset(PluginPreset self);
+
+#endif
diff --git a/source/plugin/PluginPresetFxp.c b/source/plugin/PluginPresetFxp.c
new file mode 100644
index 0000000..200d310
--- /dev/null
+++ b/source/plugin/PluginPresetFxp.c
@@ -0,0 +1,280 @@
+//
+// PluginPresetFxp.c - MrsWatson
+// Created by Nik Reiman on 1/13/12.
+// Copyright (c) 2011 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/Endian.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginPresetFxp.h"
+#include "plugin/PluginVst2x.h"
+
+static boolByte _openPluginPresetFxp(void *pluginPresetPtr)
+{
+ PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr;
+ PluginPresetFxpData extraData = (PluginPresetFxpData)(pluginPreset->extraData);
+ extraData->fileHandle = fopen(pluginPreset->presetName->data, "rb");
+
+ if (extraData->fileHandle == NULL) {
+ logError("Preset '%s' could not be opened for reading", pluginPreset->presetName->data);
+ return false;
+ }
+
+ return true;
+}
+
+static boolByte _loadPluginPresetFxp(void *pluginPresetPtr, Plugin plugin)
+{
+ PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr;
+ PluginPresetFxpData extraData = (PluginPresetFxpData)(pluginPreset->extraData);
+ FxpProgram inProgram = NULL;
+ PluginPresetFxpProgramType programType;
+
+ char *chunk;
+ size_t chunkSize;
+ unsigned int valueBuffer;
+ size_t numObjectsRead;
+ float parameterValue;
+ unsigned int i;
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at chunkMagic");
+ return false;
+ }
+
+ inProgram = (FxpProgram)malloc(sizeof(FxpProgramMembers));
+ inProgram->chunkMagic = convertBigEndianIntToPlatform(valueBuffer);
+
+ if (inProgram->chunkMagic != 0x43636E4B) { // 'CcnK'
+ logError("FXP preset file has bad chunk magic");
+ free(inProgram);
+ return false;
+ }
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at byteSize");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->byteSize = convertBigEndianIntToPlatform(valueBuffer);
+ logDebug("FXP program has %d bytes in main chunk", inProgram->byteSize);
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at fxMagic");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->fxMagic = convertBigEndianIntToPlatform(valueBuffer);
+
+ if (inProgram->fxMagic == 0x4678436b) { // 'FxCk'
+ programType = FXP_TYPE_REGULAR;
+ } else if (inProgram->fxMagic == 0x46504368) { // 'FPCh'
+ programType = FXP_TYPE_OPAQUE_CHUNK;
+ } else {
+ logError("FXP preset has invalid fxMagic type");
+ free(inProgram);
+ return false;
+ }
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at version");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->version = convertBigEndianIntToPlatform(valueBuffer);
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at fxID");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->fxID = convertBigEndianIntToPlatform(valueBuffer);
+ logDebug("Preset's fxID is %d", inProgram->fxID);
+
+ if (inProgram->fxID != pluginVst2xGetUniqueId(plugin)) {
+ logError("Preset '%s' is not compatible with plugin '%s'", pluginPreset->presetName->data, plugin->pluginName->data);
+ free(inProgram);
+ return false;
+ }
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at fxVersion");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->fxVersion = convertBigEndianIntToPlatform(valueBuffer);
+
+ if (inProgram->fxVersion != pluginVst2xGetVersion(plugin)) {
+ logWarn("Plugin has version %ld, but preset has version %d. Loading this preset may result in unexpected behavior!",
+ pluginVst2xGetVersion(plugin), inProgram->fxVersion);
+ } else {
+ logDebug("Preset's version is %d", inProgram->fxVersion);
+ }
+
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at numParams");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->numParams = convertBigEndianIntToPlatform(valueBuffer);
+ logDebug("Preset has %d params", inProgram->numParams);
+
+ memset(inProgram->prgName, 0, sizeof(char) * 28);
+ numObjectsRead = fread(inProgram->prgName, sizeof(char), 28, extraData->fileHandle);
+
+ if (numObjectsRead != 28) {
+ logError("Short read of FXP preset file at prgName");
+ free(inProgram);
+ return false;
+ }
+
+ charStringCopyCString(pluginPreset->presetName, inProgram->prgName);
+ logDebug("Preset's name is %s", pluginPreset->presetName->data);
+
+ if (programType == FXP_TYPE_REGULAR) {
+ for (i = 0; i < inProgram->numParams; i++) {
+ float parameterBuffer = 0.0f;
+ numObjectsRead = fread(&parameterBuffer, sizeof(float), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset at parameter data");
+ free(inProgram);
+ return false;
+ }
+
+ parameterValue = convertBigEndianFloatToPlatform(parameterBuffer);
+ plugin->setParameter(plugin, i, parameterValue);
+ }
+ } else if (programType == FXP_TYPE_OPAQUE_CHUNK) {
+ numObjectsRead = fread(&valueBuffer, sizeof(unsigned int), 1, extraData->fileHandle);
+
+ if (numObjectsRead != 1) {
+ logError("Short read of FXP preset file at chunk size");
+ free(inProgram);
+ return false;
+ }
+
+ inProgram->content.data.size = convertBigEndianIntToPlatform(valueBuffer);
+ chunkSize = inProgram->content.data.size;
+
+ if (chunkSize == 0) {
+ logError("FXP preset has chunk of 0 bytes");
+ free(inProgram);
+ return false;
+ } else {
+ logDebug("Plugin has chunk size of %d bytes", chunkSize);
+ }
+
+ chunk = (char *)malloc(sizeof(char) * chunkSize);
+ memset(chunk, 0, sizeof(char) * chunkSize);
+ numObjectsRead = fread(chunk, sizeof(char), chunkSize, extraData->fileHandle);
+
+ if (numObjectsRead != chunkSize) {
+ logError("Short read of FXP preset file at chunk");
+ free(inProgram);
+ free(chunk);
+ return false;
+ }
+
+ // The chunk has been read, set it to the actual plugin
+ if (plugin->interfaceType == PLUGIN_TYPE_VST_2X) {
+ pluginVst2xSetProgramChunk(plugin, chunk, chunkSize);
+ free(inProgram);
+ free(chunk);
+ return true;
+ } else {
+ logInternalError("Load FXP preset to wrong plugin type");
+ free(inProgram);
+ free(chunk);
+ return false;
+ }
+ } else {
+ logInternalError("Invalid FXP program type");
+ free(inProgram);
+ return false;
+ }
+
+ free(inProgram);
+ return true;
+}
+
+static void _freePluginPresetDataFxp(void *extraDataPtr)
+{
+ PluginPresetFxpData extraData = extraDataPtr;
+
+ if (extraData->fileHandle != NULL) {
+ fclose(extraData->fileHandle);
+ }
+
+ if (extraData->chunk != NULL) {
+ free(extraData->chunk);
+ }
+}
+
+PluginPreset newPluginPresetFxp(const CharString presetName)
+{
+ PluginPreset pluginPreset = (PluginPreset)malloc(sizeof(PluginPresetMembers));
+ PluginPresetFxpData extraData = (PluginPresetFxpData)malloc(sizeof(PluginPresetFxpDataMembers));
+
+ pluginPreset->presetType = PRESET_TYPE_FXP;
+ pluginPreset->presetName = newCharString();
+ charStringCopy(pluginPreset->presetName, presetName);
+ pluginPreset->compatiblePluginTypes = 0;
+ pluginPresetSetCompatibleWith(pluginPreset, PLUGIN_TYPE_VST_2X);
+
+ pluginPreset->openPreset = _openPluginPresetFxp;
+ pluginPreset->loadPreset = _loadPluginPresetFxp;
+ pluginPreset->freePresetData = _freePluginPresetDataFxp;
+
+ extraData->fileHandle = NULL;
+ extraData->chunk = NULL;
+ pluginPreset->extraData = extraData;
+
+ return pluginPreset;
+}
+
diff --git a/source/plugin/PluginPresetFxp.h b/source/plugin/PluginPresetFxp.h
new file mode 100644
index 0000000..c951dbb
--- /dev/null
+++ b/source/plugin/PluginPresetFxp.h
@@ -0,0 +1,74 @@
+//
+// PluginPresetFxp.h - MrsWatson
+// Created by Nik Reiman on 1/13/12.
+// Copyright (c) 2011 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginPresetFxp_h
+#define MrsWatson_PluginPresetFxp_h
+
+#include <stdio.h>
+
+#include "plugin/PluginPreset.h"
+
+typedef enum {
+ FXP_TYPE_INVALID,
+ FXP_TYPE_REGULAR,
+ FXP_TYPE_OPAQUE_CHUNK,
+} PluginPresetFxpProgramType;
+
+// Copied from the VST SDK. Yes, this is a bit lame, but otherwise the C++ "virus"
+// starts to leak into the code again, and as vstfxstore.h is pure C, I don't see
+// any reason why all these files must be compiled as C++.
+typedef struct {
+ unsigned int chunkMagic; ///< 'CcnK'
+ unsigned int byteSize; ///< size of this chunk, excl. magic + byteSize
+
+ unsigned int fxMagic; ///< 'FxCk' (regular) or 'FPCh' (opaque chunk)
+ unsigned int version; ///< format version (currently 1)
+ unsigned int fxID; ///< fx unique ID
+ unsigned int fxVersion; ///< fx version
+
+ unsigned int numParams; ///< number of parameters
+ char prgName[28]; ///< program name (null-terminated ASCII string)
+
+ union {
+ float *params; ///< variable sized array with parameter values
+ struct {
+ unsigned int size; ///< size of program data
+ char *chunk; ///< variable sized array with opaque program data
+ } data; ///< program chunk data
+ } content; ///< program content depending on fxMagic
+} FxpProgramMembers;
+typedef FxpProgramMembers *FxpProgram;
+
+typedef struct {
+ FILE *fileHandle;
+ byte *chunk;
+} PluginPresetFxpDataMembers;
+typedef PluginPresetFxpDataMembers *PluginPresetFxpData;
+
+PluginPreset newPluginPresetFxp(const CharString presetName);
+
+#endif
diff --git a/source/plugin/PluginPresetInternalProgram.c b/source/plugin/PluginPresetInternalProgram.c
new file mode 100644
index 0000000..27e5a89
--- /dev/null
+++ b/source/plugin/PluginPresetInternalProgram.c
@@ -0,0 +1,74 @@
+//
+// PluginPresetInternalProgram.c - MrsWatson
+// Created by Nik Reiman on 19 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdlib.h>
+
+#include "base/CharString.h"
+#include "plugin/PluginPresetInternalProgram.h"
+#include "plugin/PluginVst2x.h"
+
+static boolByte _openPluginPresetInternalProgram(void *pluginPresetPtr)
+{
+ PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr;
+ PluginPresetInternalProgramData extraData = (PluginPresetInternalProgramData)pluginPreset->extraData;
+ extraData->programNumber = (unsigned int)strtoul(pluginPreset->presetName->data, NULL, 10);
+ return true;
+}
+
+static boolByte _loadPluginPresetInternalProgram(void *pluginPresetPtr, Plugin plugin)
+{
+ PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr;
+ PluginPresetInternalProgramData extraData = (PluginPresetInternalProgramData)pluginPreset->extraData;
+ return pluginVst2xSetProgram(plugin, extraData->programNumber);
+}
+
+static void _freePluginPresetInternalProgram(void *extraDataPtr)
+{
+ // Nothing needed here
+}
+
+PluginPreset newPluginPresetInternalProgram(const CharString presetName)
+{
+ PluginPreset pluginPreset = (PluginPreset)malloc(sizeof(PluginPresetMembers));
+ PluginPresetInternalProgramData extraData; // Yay for long type names!
+ extraData = (PluginPresetInternalProgramData)malloc(sizeof(PluginPresetInternalProgramDataMembers));
+
+ pluginPreset->presetType = PRESET_TYPE_INTERNAL_PROGRAM;
+ pluginPreset->presetName = newCharString();
+ charStringCopy(pluginPreset->presetName, presetName);
+ pluginPreset->compatiblePluginTypes = 0;
+ pluginPresetSetCompatibleWith(pluginPreset, PLUGIN_TYPE_VST_2X);
+
+ pluginPreset->openPreset = _openPluginPresetInternalProgram;
+ pluginPreset->loadPreset = _loadPluginPresetInternalProgram;
+ pluginPreset->freePresetData = _freePluginPresetInternalProgram;
+
+ extraData->programNumber = 0;
+ pluginPreset->extraData = extraData;
+
+ return pluginPreset;
+}
diff --git a/source/plugin/PluginPresetInternalProgram.h b/source/plugin/PluginPresetInternalProgram.h
new file mode 100644
index 0000000..d93795e
--- /dev/null
+++ b/source/plugin/PluginPresetInternalProgram.h
@@ -0,0 +1,40 @@
+//
+// PluginPresetInternalProgram.h - MrsWatson
+// Created by Nik Reiman on 19 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginPresetInternalProgram_h
+#define MrsWatson_PluginPresetInternalProgram_h
+
+#include "plugin/PluginPreset.h"
+
+typedef struct {
+ unsigned int programNumber;
+} PluginPresetInternalProgramDataMembers;
+typedef PluginPresetInternalProgramDataMembers *PluginPresetInternalProgramData;
+
+PluginPreset newPluginPresetInternalProgram(const CharString presetName);
+
+#endif
diff --git a/source/plugin/PluginSilence.c b/source/plugin/PluginSilence.c
new file mode 100644
index 0000000..f3f79a3
--- /dev/null
+++ b/source/plugin/PluginSilence.c
@@ -0,0 +1,105 @@
+//
+// PluginSilence.c - MrsWatson
+// Created by Nik Reiman on 19 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdlib.h>
+
+#include "logging/EventLogger.h"
+#include "plugin/PluginSilence.h"
+
+const char *kInternalPluginSilenceName = INTERNAL_PLUGIN_PREFIX "silence";
+
+static void _pluginSilenceEmpty(void *pluginPtr)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginSilenceOpen(void *pluginPtr)
+{
+ return true;
+}
+
+static void _pluginSilenceDisplayInfo(void *pluginPtr)
+{
+ logInfo("Information for Internal plugin '%s'", kInternalPluginSilenceName);
+ logInfo("Type: instrument, parameters: none");
+ logInfo("Description: an instrument which generates silence");
+}
+
+static int _pluginSilenceGetSetting(void *pluginPtr, PluginSetting pluginSetting)
+{
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS:
+ return 0;
+
+ case PLUGIN_NUM_INPUTS:
+ return 0;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return 2;
+
+ case PLUGIN_INITIAL_DELAY:
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+static void _pluginSilenceProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+{
+ sampleBufferClear(outputs);
+}
+
+static void _pluginSilenceProcessMidiEvents(void *pluginPtr, LinkedList midiEvents)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginSilenceSetParameter(void *pluginPtr, unsigned int i, float value)
+{
+ return false;
+}
+
+Plugin newPluginSilence(const CharString pluginName)
+{
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_INSTRUMENT);
+ charStringCopy(plugin->pluginName, pluginName);
+ charStringCopyCString(plugin->pluginLocation, "Internal");
+
+ plugin->openPlugin = _pluginSilenceOpen;
+ plugin->displayInfo = _pluginSilenceDisplayInfo;
+ plugin->getSetting = _pluginSilenceGetSetting;
+ plugin->prepareForProcessing = _pluginSilenceEmpty;
+ plugin->processAudio = _pluginSilenceProcessAudio;
+ plugin->processMidiEvents = _pluginSilenceProcessMidiEvents;
+ plugin->setParameter = _pluginSilenceSetParameter;
+ plugin->closePlugin = _pluginSilenceEmpty;
+ plugin->freePluginData = _pluginSilenceEmpty;
+
+ plugin->extraData = NULL;
+ return plugin;
+}
diff --git a/source/plugin/PluginSilence.h b/source/plugin/PluginSilence.h
new file mode 100644
index 0000000..bb5891a
--- /dev/null
+++ b/source/plugin/PluginSilence.h
@@ -0,0 +1,37 @@
+//
+// PluginSilence.h - MrsWatson
+// Created by Nik Reiman on 19 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginSilence_h
+#define MrsWatson_PluginSilence_h
+
+#include "plugin/Plugin.h"
+
+extern const char *kInternalPluginSilenceName;
+
+Plugin newPluginSilence(const CharString pluginName);
+
+#endif
diff --git a/source/plugin/PluginVst2x.cpp b/source/plugin/PluginVst2x.cpp
new file mode 100644
index 0000000..e39f3ec
--- /dev/null
+++ b/source/plugin/PluginVst2x.cpp
@@ -0,0 +1,897 @@
+//
+// PluginVst2x.cpp - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+// C++ includes
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+#include "plugin/PluginVst2xHostCallback.h"
+
+// C includes
+extern "C" {
+#include <stdlib.h>
+
+#include "audio/AudioSettings.h"
+#include "base/File.h"
+#include "base/PlatformInfo.h"
+#include "base/Types.h"
+#include "logging/EventLogger.h"
+#include "midi/MidiEvent.h"
+#include "plugin/PluginVst2x.h"
+#include "plugin/PluginVst2xId.h"
+
+ extern LinkedList getVst2xPluginLocations(CharString currentDirectory);
+ extern LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath);
+ extern AEffect *loadVst2xPlugin(LibraryHandle libraryHandle);
+ extern void closeLibraryHandle(LibraryHandle libraryHandle);
+}
+
+// Opaque struct must be declared here rather than in the header, otherwise many
+// other files in this project must be compiled as C++ code. =/
+typedef struct {
+ AEffect *pluginHandle;
+ PluginVst2xId pluginId;
+ Vst2xPluginDispatcherFunc dispatcher;
+ LibraryHandle libraryHandle;
+ boolByte isPluginShell;
+ VstInt32 shellPluginId;
+ // Must be retained until processReplacing() is called, so best to keep a
+ // reference in the plugin's data storage.
+ struct VstEvents *vstEvents;
+} PluginVst2xDataMembers;
+typedef PluginVst2xDataMembers *PluginVst2xData;
+
+// Implementation body starts here
+extern "C" {
+// Current plugin ID, which is mostly used by shell plugins during initialization.
+// To support VST shell plugins, we must provide them with a unique ID of a sub-plugin
+// which they will ask for from the host (opcode audioMasterCurrentId). The problem
+// is that this host callback is made when the plugin's main() function is called for
+// the first time, in loadVst2xPlugin(). While the AEffect struct provides a void* user
+// member for storing a pointer to an arbitrary object which could be used to store this
+// value, that struct is not fully constructed when the callback is made to the host
+// (in fact, calling the plugin's main() *returns* the AEffect* which we save in our
+// extraData struct). Therefore it is not possible to have the plugin reach our host
+// callback with some custom data, and we must keep a global variable to the current
+// effect ID.
+// That said, this prevents initializing plugins in multiple threads in the future, as
+// as we must set this to the correct ID before calling the plugin's main() function
+// when setting up the effect chain.
+ VstInt32 currentPluginUniqueId;
+
+ static const char *_getVst2xPlatformExtension(void)
+ {
+ PlatformInfo platform = newPlatformInfo();
+ PlatformType platformType = platform->type;
+ freePlatformInfo(platform);
+
+ switch (platformType) {
+ case PLATFORM_MACOSX:
+ return ".vst";
+
+ case PLATFORM_WINDOWS:
+ return ".dll";
+
+ case PLATFORM_LINUX:
+ return ".so";
+
+ default:
+ return EMPTY_STRING;
+ }
+ }
+
+ static void _logPluginVst2xInLocation(void *item, void *userData)
+ {
+ File itemFile = (File)item;
+ CharString itemPath = newCharStringWithCString(itemFile->absolutePath->data);
+ boolByte *pluginsFound = (boolByte *)userData;
+ char *dot;
+
+ logDebug("Checking item '%s'", itemPath->data);
+ dot = strrchr(itemPath->data, '.');
+
+ if (dot != NULL) {
+ if (!strncmp(dot, _getVst2xPlatformExtension(), 3)) {
+ *dot = '\0';
+ logInfo(" %s", itemPath->data);
+ *pluginsFound = true;
+ }
+ }
+
+ freeCharString(itemPath);
+ }
+
+ static void _logPluginLocation(const CharString location)
+ {
+ logInfo("Location '%s', type VST 2.x:", location->data);
+ }
+
+ static void _listPluginsVst2xInLocation(void *item, void *userData)
+ {
+ CharString locationString;
+ File location = NULL;
+ LinkedList locationItems;
+ boolByte pluginsFound = false;
+
+ locationString = (CharString)item;
+ _logPluginLocation(locationString);
+ location = newFileWithPath(locationString);
+ locationItems = fileListDirectory(location);
+
+ if (linkedListLength(locationItems) == 0) {
+ // Empty or does not exist, return
+ logInfo(" (Empty or non-existent directory)");
+ freeLinkedList(locationItems);
+ freeFile(location);
+ return;
+ }
+
+ linkedListForeach(locationItems, _logPluginVst2xInLocation, &pluginsFound);
+
+ if (!pluginsFound) {
+ logInfo(" (No plugins found)");
+ }
+
+ freeFile(location);
+ freeLinkedListAndItems(locationItems, (LinkedListFreeItemFunc)freeFile);
+ }
+
+ void listAvailablePluginsVst2x(const CharString pluginRoot)
+ {
+ if (!charStringIsEmpty(pluginRoot)) {
+ _listPluginsVst2xInLocation(pluginRoot, NULL);
+ }
+
+ LinkedList pluginLocations = getVst2xPluginLocations(fileGetCurrentDirectory());
+ linkedListForeach(pluginLocations, _listPluginsVst2xInLocation, NULL);
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ }
+
+ static boolByte _doesVst2xPluginExistAtLocation(const CharString pluginName, const CharString locationName)
+ {
+ boolByte result = false;
+ const char *subpluginSeparator = NULL;
+ CharString pluginSearchName = NULL;
+ CharString pluginSearchExtension = NULL;
+ File location = NULL;
+ File pluginSearchPath = NULL;
+
+ subpluginSeparator = strrchr(pluginName->data, kPluginVst2xSubpluginSeparator);
+
+ if (subpluginSeparator != NULL) {
+ pluginSearchName = newCharString();
+ strncpy(pluginSearchName->data, pluginName->data, subpluginSeparator - pluginName->data);
+ result = _doesVst2xPluginExistAtLocation(pluginSearchName, locationName);
+ freeCharString(pluginSearchName);
+ return result;
+ }
+
+ logDebug("Looking for plugin '%s' in '%s'", pluginName->data, locationName->data);
+
+ location = newFileWithPath(locationName);
+
+ if (location == NULL || !fileExists(location) || location->fileType != kFileTypeDirectory) {
+ logWarn("Location '%s' is not a valid directory", locationName->data);
+ freeFile(location);
+ return result;
+ }
+
+ pluginSearchName = newCharStringWithCString(pluginName->data);
+ pluginSearchPath = newFileWithParent(location, pluginSearchName);
+ pluginSearchExtension = fileGetExtension(pluginSearchPath);
+
+ if (pluginSearchExtension == NULL) {
+ freeFile(pluginSearchPath);
+ charStringAppendCString(pluginSearchName, _getVst2xPlatformExtension());
+ pluginSearchPath = newFileWithParent(location, pluginSearchName);
+ }
+
+ if (fileExists(pluginSearchPath)) {
+ result = true;
+ }
+
+ freeCharString(pluginSearchExtension);
+ freeCharString(pluginSearchName);
+ freeFile(pluginSearchPath);
+ freeFile(location);
+ return result;
+ }
+
+ static CharString _getVst2xPluginLocation(const CharString pluginName, const CharString pluginRoot)
+ {
+ File pluginAbsolutePath = newFileWithPath(pluginName);
+
+ if (fileExists(pluginAbsolutePath)) {
+ File pluginParentDir = fileGetParent(pluginAbsolutePath);
+ CharString result = newCharStringWithCString(pluginParentDir->absolutePath->data);
+ freeFile(pluginParentDir);
+ freeFile(pluginAbsolutePath);
+ return result;
+ } else {
+ freeFile(pluginAbsolutePath);
+ }
+
+ // Then search the path given to --plugin-root, if given
+ if (!charStringIsEmpty(pluginRoot)) {
+ if (_doesVst2xPluginExistAtLocation(pluginName, pluginRoot)) {
+ return newCharStringWithCString(pluginRoot->data);
+ }
+ }
+
+ // If the plugin wasn't found in the user's plugin root, then try searching
+ // the default locations for the platform, starting with the current directory.
+ LinkedList pluginLocations = getVst2xPluginLocations(fileGetCurrentDirectory());
+
+ if (pluginLocations->item == NULL) {
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ return NULL;
+ }
+
+ LinkedListIterator iterator = pluginLocations;
+
+ while (iterator != NULL) {
+ CharString searchLocation = (CharString)(iterator->item);
+
+ if (_doesVst2xPluginExistAtLocation(pluginName, searchLocation)) {
+ CharString result = newCharStringWithCString(searchLocation->data);
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ return result;
+ }
+
+ iterator = (LinkedListIterator)iterator->nextItem;
+ }
+
+ freeLinkedListAndItems(pluginLocations, (LinkedListFreeItemFunc)freeCharString);
+ return NULL;
+ }
+
+ boolByte pluginVst2xExists(const CharString pluginName, const CharString pluginRoot)
+ {
+ CharString pluginLocation = _getVst2xPluginLocation(pluginName, pluginRoot);
+ boolByte result = (boolByte)((pluginLocation != NULL) && !charStringIsEmpty(pluginLocation));
+ freeCharString(pluginLocation);
+ return result;
+ }
+
+ static short _canPluginDo(Plugin plugin, const char *canDoString)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ VstIntPtr result = data->dispatcher(data->pluginHandle, effCanDo, 0, 0, (void *)canDoString, 0.0f);
+ return (short)result;
+ }
+
+ static void _resumePlugin(Plugin plugin)
+ {
+ logDebug("Resuming plugin '%s'", plugin->pluginName->data);
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+
+ if (data->isPluginShell && data->shellPluginId == 0) {
+ logError("'%s' is a shell plugin, but no sub-plugin ID was given, run with --help plugin", plugin->pluginName->data);
+ }
+
+ data->dispatcher(data->pluginHandle, effMainsChanged, 0, 1, NULL, 0.0f);
+ data->dispatcher(data->pluginHandle, effStartProcess, 0, 0, NULL, 0.0f);
+ }
+
+ static void _suspendPlugin(Plugin plugin)
+ {
+ logDebug("Suspending plugin '%s'", plugin->pluginName->data);
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ data->dispatcher(data->pluginHandle, effMainsChanged, 0, 0, NULL, 0.0f);
+ data->dispatcher(data->pluginHandle, effStopProcess, 0, 0, NULL, 0.0f);
+ }
+
+ static void _setSpeakers(struct VstSpeakerArrangement *speakerArrangement, int channels)
+ {
+ memset(speakerArrangement, 0, sizeof(struct VstSpeakerArrangement));
+ speakerArrangement->numChannels = channels;
+
+ if (channels <= 8) {
+ speakerArrangement->numChannels = channels;
+ } else {
+ logInfo("Number of channels = %d. Will only arrange 8 speakers.", channels);
+ speakerArrangement->numChannels = 8;
+ }
+
+ switch (speakerArrangement->numChannels) {
+ case 0:
+ speakerArrangement->type = kSpeakerArrEmpty;
+ break;
+
+ case 1:
+ speakerArrangement->type = kSpeakerArrMono;
+ break;
+
+ case 2:
+ speakerArrangement->type = kSpeakerArrStereo;
+ break;
+
+ case 3:
+ speakerArrangement->type = kSpeakerArr30Music;
+ break;
+
+ case 4:
+ speakerArrangement->type = kSpeakerArr40Music;
+ break;
+
+ case 5:
+ speakerArrangement->type = kSpeakerArr50;
+ break;
+
+ case 6:
+ speakerArrangement->type = kSpeakerArr60Music;
+ break;
+
+ case 7:
+ speakerArrangement->type = kSpeakerArr70Music;
+ break;
+
+ case 8:
+ speakerArrangement->type = kSpeakerArr80Music;
+ break;
+
+ default:
+ logInternalError("Cannot arrange more than 8 speakers.");//The datastructure does not allow.
+ break;
+ }
+
+ for (int i = 0; i < speakerArrangement->numChannels; i++) {
+ speakerArrangement->speakers[i].azimuth = 0.0f;
+ speakerArrangement->speakers[i].elevation = 0.0f;
+ speakerArrangement->speakers[i].radius = 0.0f;
+ speakerArrangement->speakers[i].reserved = 0.0f;
+ speakerArrangement->speakers[i].name[0] = '\0';
+ speakerArrangement->speakers[i].type = kSpeakerUndefined;
+ }
+ }
+
+ static boolByte _initVst2xPlugin(Plugin plugin)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ PluginVst2xId subpluginId;
+
+ logDebug("Initializing VST2.x plugin '%s'", plugin->pluginName->data);
+
+ if (data->pluginHandle->flags & effFlagsIsSynth) {
+ plugin->pluginType = PLUGIN_TYPE_INSTRUMENT;
+ } else {
+ plugin->pluginType = PLUGIN_TYPE_EFFECT;
+ }
+
+ if (data->pluginHandle->dispatcher(data->pluginHandle, effGetPlugCategory, 0, 0, NULL, 0.0f) == kPlugCategShell) {
+ subpluginId = newPluginVst2xIdWithId((unsigned long)data->shellPluginId);
+ logDebug("VST is a shell plugin, sub-plugin ID '%s'", subpluginId->idString->data);
+ freePluginVst2xId(subpluginId);
+ data->isPluginShell = true;
+ }
+
+ data->dispatcher(data->pluginHandle, effOpen, 0, 0, NULL, 0.0f);
+ data->dispatcher(data->pluginHandle, effSetSampleRate, 0, 0, NULL, (float)getSampleRate());
+ data->dispatcher(data->pluginHandle, effSetBlockSize, 0, (VstIntPtr)getBlocksize(), NULL, 0.0f);
+ struct VstSpeakerArrangement inSpeakers;
+ _setSpeakers(&inSpeakers, data->pluginHandle->numInputs);
+ struct VstSpeakerArrangement outSpeakers;
+ _setSpeakers(&outSpeakers, data->pluginHandle->numOutputs);
+ data->dispatcher(data->pluginHandle, effSetSpeakerArrangement, 0, (VstIntPtr)&inSpeakers, &outSpeakers, 0.0f);
+
+ return true;
+ }
+
+ unsigned long pluginVst2xGetUniqueId(const Plugin self)
+ {
+ if (self->interfaceType == PLUGIN_TYPE_VST_2X) {
+ PluginVst2xData data = (PluginVst2xData)self->extraData;
+ return (unsigned long)data->pluginHandle->uniqueID;
+ }
+
+ return 0;
+ }
+
+ unsigned long pluginVst2xGetVersion(const Plugin self)
+ {
+ if (self->interfaceType == PLUGIN_TYPE_VST_2X) {
+ PluginVst2xData data = (PluginVst2xData)self->extraData;
+ return (unsigned long)data->pluginHandle->version;
+ }
+
+ return 0;
+ }
+
+ void pluginVst2xAudioMasterIOChanged(const Plugin self, AEffect const *const newValues)
+ {
+ PluginVst2xData data = (PluginVst2xData)(self->extraData);
+ data->pluginHandle->initialDelay = newValues->initialDelay;
+
+ if (newValues->numInputs != data->pluginHandle->numInputs || newValues->numOutputs != data->pluginHandle->numOutputs) {
+ data->pluginHandle->numInputs = newValues->numInputs;
+ struct VstSpeakerArrangement inSpeakers;
+ _setSpeakers(&inSpeakers, data->pluginHandle->numInputs);
+ data->pluginHandle->numOutputs = newValues->numOutputs;
+ struct VstSpeakerArrangement outSpeakers;
+ _setSpeakers(&outSpeakers, data->pluginHandle->numOutputs);
+ data->dispatcher(data->pluginHandle, effSetSpeakerArrangement, 0, (VstIntPtr)&inSpeakers, &outSpeakers, 0.0f);
+ }
+ }
+
+ static boolByte _openVst2xPlugin(void *pluginPtr)
+ {
+ boolByte result = false;
+ AEffect *pluginHandle;
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ File pluginPath = newFileWithPath(plugin->pluginName);
+ CharString pluginBasename = fileGetBasename(pluginPath);
+ char *subpluginSeparator = strrchr((char *)pluginBasename, kPluginVst2xSubpluginSeparator);
+ CharString subpluginIdString = NULL;
+
+ if (subpluginSeparator != NULL) {
+ *subpluginSeparator = '\0';
+ subpluginIdString = newCharStringWithCapacity(kCharStringLengthShort);
+ strncpy(subpluginIdString->data, subpluginSeparator + 1, 4);
+ PluginVst2xId subpluginId = newPluginVst2xIdWithStringId(subpluginIdString);
+ data->shellPluginId = (VstInt32)subpluginId->id;
+ currentPluginUniqueId = data->shellPluginId;
+ freePluginVst2xId(subpluginId);
+ }
+
+ logInfo("Opening VST2.x plugin '%s'", plugin->pluginName->data);
+
+ if (fileExists(pluginPath)) {
+ charStringCopy(plugin->pluginAbsolutePath, pluginPath->absolutePath);
+ } else {
+ File pluginLocationPath = newFileWithPath(plugin->pluginLocation);
+ CharString pluginNameWithExtension = newCharString();
+ charStringCopy(pluginNameWithExtension, plugin->pluginName);
+ charStringAppendCString(pluginNameWithExtension, _getVst2xPlatformExtension());
+ File pluginAbsolutePath = newFileWithParent(pluginLocationPath, pluginNameWithExtension);
+ charStringCopy(plugin->pluginAbsolutePath, pluginAbsolutePath->absolutePath);
+ freeFile(pluginAbsolutePath);
+ freeFile(pluginLocationPath);
+ freeCharString(pluginNameWithExtension);
+ }
+
+ freeFile(pluginPath);
+ logDebug("Plugin location is '%s'", plugin->pluginAbsolutePath->data);
+
+ data->libraryHandle = getLibraryHandleForPlugin(plugin->pluginAbsolutePath);
+
+ if (data->libraryHandle == NULL) {
+ return false;
+ }
+
+ pluginHandle = loadVst2xPlugin(data->libraryHandle);
+
+ if (pluginHandle == NULL) {
+ logError("Could not load VST2.x plugin '%s'", plugin->pluginAbsolutePath->data);
+ return false;
+ }
+
+ // The plugin name which is passed into this function is basically just used to find the
+ // actual location. Now that the plugin has been loaded, we can set a friendlier name.
+ CharString temp = plugin->pluginName;
+ plugin->pluginName = newCharStringWithCString(pluginBasename->data);
+ freeCharString(pluginBasename);
+ freeCharString(temp);
+
+ if (data->shellPluginId && subpluginIdString != NULL) {
+ charStringAppendCString(plugin->pluginName, " (");
+ charStringAppend(plugin->pluginName, subpluginIdString);
+ charStringAppendCString(plugin->pluginName, ")");
+ }
+
+ // Check plugin's magic number. If incorrect, then the file either was not loaded
+ // properly, is not a real VST plugin, or is otherwise corrupt.
+ if (pluginHandle->magic != kEffectMagic) {
+ logError("Plugin '%s' has bad magic number, possibly corrupt", plugin->pluginName->data);
+ } else {
+ data->dispatcher = (Vst2xPluginDispatcherFunc)(pluginHandle->dispatcher);
+ data->pluginHandle = pluginHandle;
+ result = _initVst2xPlugin(plugin);
+
+ if (result) {
+ data->pluginId = newPluginVst2xIdWithId((unsigned long)data->pluginHandle->uniqueID);
+ }
+ }
+
+ freeCharString(subpluginIdString);
+ return result;
+ }
+
+ static LinkedList _getCommonCanDos(void)
+ {
+ LinkedList result = newLinkedList();
+ linkedListAppend(result, (char *)"sendVstEvents");
+ linkedListAppend(result, (char *)"sendVstMidiEvent");
+ linkedListAppend(result, (char *)"receiveVstEvents");
+ linkedListAppend(result, (char *)"receiveVstMidiEvent");
+ linkedListAppend(result, (char *)"receiveVstTimeInfo");
+ linkedListAppend(result, (char *)"offline");
+ linkedListAppend(result, (char *)"midiProgramNames");
+ linkedListAppend(result, (char *)"bypass");
+ return result;
+ }
+
+ static const char *_prettyTextForCanDoResult(int result)
+ {
+ if (result == -1) {
+ return "No";
+ } else if (result == 0) {
+ return "Don't know";
+ } else if (result == 1) {
+ return "Yes";
+ } else {
+ return "Undefined response";
+ }
+ }
+
+ static void _displayVst2xPluginCanDo(void *item, void *userData)
+ {
+ char *canDoString = (char *)item;
+ short result = _canPluginDo((Plugin)userData, canDoString);
+ logInfo(" %s: %s", canDoString, _prettyTextForCanDoResult(result));
+ }
+
+ static void _displayVst2xPluginInfo(void *pluginPtr)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ CharString nameBuffer = newCharString();
+
+ logInfo("Information for VST2.x plugin '%s'", plugin->pluginName->data);
+ data->dispatcher(data->pluginHandle, effGetVendorString, 0, 0, nameBuffer->data, 0.0f);
+ logInfo("Vendor: %s", nameBuffer->data);
+ VstInt32 vendorVersion = (VstInt32)data->dispatcher(data->pluginHandle, effGetVendorVersion, 0, 0, NULL, 0.0f);
+ logInfo("Version: %d", vendorVersion);
+ charStringClear(nameBuffer);
+
+ logInfo("Unique ID: %s", data->pluginId->idString->data);
+ freeCharString(nameBuffer);
+
+ VstInt32 pluginCategory = (VstInt32)data->dispatcher(data->pluginHandle, effGetPlugCategory, 0, 0, NULL, 0.0f);
+
+ switch (plugin->pluginType) {
+ case PLUGIN_TYPE_EFFECT:
+ logInfo("Plugin type: effect, category %d", pluginCategory);
+ break;
+
+ case PLUGIN_TYPE_INSTRUMENT:
+ logInfo("Plugin type: instrument, category %d", pluginCategory);
+ break;
+
+ default:
+ logInfo("Plugin type: other, category %d", pluginCategory);
+ break;
+ }
+
+ logInfo("Version: %d", data->pluginHandle->version);
+ logInfo("I/O: %d/%d", data->pluginHandle->numInputs, data->pluginHandle->numOutputs);
+ logInfo("InitialDelay: %d frames", data->pluginHandle->initialDelay);
+
+ if (data->isPluginShell && data->shellPluginId == 0) {
+ logInfo("Sub-plugins:");
+ nameBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+
+ while (true) {
+ charStringClear(nameBuffer);
+ VstInt32 shellPluginId = (VstInt32)data->dispatcher(data->pluginHandle, effShellGetNextPlugin, 0, 0, nameBuffer->data, 0.0f);
+
+ if (shellPluginId == 0 || charStringIsEmpty(nameBuffer)) {
+ break;
+ } else {
+ PluginVst2xId subpluginId = newPluginVst2xIdWithId((unsigned long)shellPluginId);
+ logInfo(" '%s' (%s)", subpluginId->idString->data, nameBuffer->data);
+ freePluginVst2xId(subpluginId);
+ }
+ }
+
+ freeCharString(nameBuffer);
+ } else {
+ nameBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+ logInfo("Parameters (%d total):", data->pluginHandle->numParams);
+
+ for (VstInt32 i = 0; i < data->pluginHandle->numParams; i++) {
+ float value = data->pluginHandle->getParameter(data->pluginHandle, i);
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetParamName, i, 0, nameBuffer->data, 0.0f);
+ logInfo(" %d: '%s' (%f)", i, nameBuffer->data, value);
+
+ if (isLogLevelAtLeast(LOG_DEBUG)) {
+ logDebug(" Displaying common values for parameter:");
+
+ for (unsigned int j = 0; j < 128; j++) {
+ const float midiValue = (float)j / 127.0f;
+ // Don't use the other setParameter function, or else that will log like crazy to
+ // a different log level.
+ data->pluginHandle->setParameter(data->pluginHandle, i, midiValue);
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetParamDisplay, i, 0, nameBuffer->data, 0.0f);
+ logDebug(" %0.3f/MIDI value %d (0x%02x): %s", midiValue, j, j, nameBuffer->data);
+ }
+ }
+ }
+
+ logInfo("Programs (%d total):", data->pluginHandle->numPrograms);
+
+ for (int i = 0; i < data->pluginHandle->numPrograms; i++) {
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetProgramNameIndexed, i, 0, nameBuffer->data, 0.0f);
+ logInfo(" %d: '%s'", i, nameBuffer->data);
+ }
+
+ charStringClear(nameBuffer);
+ data->dispatcher(data->pluginHandle, effGetProgramName, 0, 0, nameBuffer->data, 0.0f);
+ logInfo("Current program: '%s'", nameBuffer->data);
+ freeCharString(nameBuffer);
+
+ logInfo("Common canDo's:");
+ LinkedList commonCanDos = _getCommonCanDos();
+ linkedListForeach(commonCanDos, _displayVst2xPluginCanDo, plugin);
+ freeLinkedList(commonCanDos);
+ }
+ }
+
+ static int _getVst2xPluginSetting(void *pluginPtr, PluginSetting pluginSetting)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS: {
+ VstInt32 tailSize = (VstInt32)data->dispatcher(data->pluginHandle, effGetTailSize, 0, 0, NULL, 0.0f);
+
+ // For some reason, the VST SDK says that plugins return a 1 here for no tail.
+ if (tailSize == 1 || tailSize == 0) {
+ return 0;
+ } else {
+ // If tailSize is not 0 or 1, then it is assumed to be in samples
+ return (int)((double)tailSize * getSampleRate() / 1000.0f);
+ }
+ }
+
+ case PLUGIN_NUM_INPUTS:
+ return data->pluginHandle->numInputs;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return data->pluginHandle->numOutputs;
+
+ case PLUGIN_INITIAL_DELAY:
+ return data->pluginHandle->initialDelay;
+
+ default:
+ logUnsupportedFeature("Plugin setting for VST2.x");
+ return 0;
+ }
+ }
+
+ void pluginVst2xSetProgramChunk(Plugin plugin, char *chunk, size_t chunkSize)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ data->dispatcher(data->pluginHandle, effSetChunk, 1, (VstIntPtr)chunkSize, chunk, 0.0f);
+ }
+
+ static void _processAudioVst2xPlugin(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ data->pluginHandle->processReplacing(data->pluginHandle, inputs->samples, outputs->samples, (VstInt32)outputs->blocksize);
+ }
+
+ static void _fillVstMidiEvent(const MidiEvent midiEvent, VstMidiEvent *vstMidiEvent)
+ {
+ switch (midiEvent->eventType) {
+ case MIDI_TYPE_REGULAR:
+ vstMidiEvent->type = kVstMidiType;
+ vstMidiEvent->byteSize = sizeof(VstMidiEvent);
+ vstMidiEvent->deltaFrames = (VstInt32)midiEvent->deltaFrames;
+ vstMidiEvent->midiData[0] = midiEvent->status;
+ vstMidiEvent->midiData[1] = midiEvent->data1;
+ vstMidiEvent->midiData[2] = midiEvent->data2;
+ vstMidiEvent->flags = 0;
+ vstMidiEvent->reserved1 = 0;
+ vstMidiEvent->reserved2 = 0;
+ break;
+
+ case MIDI_TYPE_SYSEX:
+ logUnsupportedFeature("VST2.x plugin sysex messages");
+ break;
+
+ case MIDI_TYPE_META:
+ // Ignore, don't care
+ break;
+
+ default:
+ logInternalError("Cannot convert MIDI event type '%d' to VstMidiEvent", midiEvent->eventType);
+ break;
+ }
+ }
+
+ static void _processMidiEventsVst2xPlugin(void *pluginPtr, LinkedList midiEvents)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)(plugin->extraData);
+ int numEvents = linkedListLength(midiEvents);
+
+ // Free events from the previous call
+ if (data->vstEvents != NULL) {
+ for (int i = 0; i < data->vstEvents->numEvents; i++) {
+ free(data->vstEvents->events[i]);
+ }
+
+ free(data->vstEvents);
+ }
+
+ data->vstEvents = (struct VstEvents *)malloc(sizeof(struct VstEvent) + (numEvents * sizeof(struct VstEvent *)));
+ data->vstEvents->numEvents = numEvents;
+
+ // Some monophonic instruments have problems dealing with the order of MIDI events,
+ // so send them all note off events *first* followed by any other event types.
+ LinkedListIterator iterator = midiEvents;
+ int outIndex = 0;
+
+ while (iterator != NULL && outIndex < numEvents) {
+ MidiEvent midiEvent = (MidiEvent)(iterator->item);
+
+ if (midiEvent != NULL && (midiEvent->status >> 4) == 0x08) {
+ VstMidiEvent *vstMidiEvent = (VstMidiEvent *)malloc(sizeof(VstMidiEvent));
+ _fillVstMidiEvent(midiEvent, vstMidiEvent);
+ data->vstEvents->events[outIndex] = (VstEvent *)vstMidiEvent;
+ outIndex++;
+ }
+
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ }
+
+ iterator = midiEvents;
+
+ while (iterator != NULL && outIndex < numEvents) {
+ MidiEvent midiEvent = (MidiEvent)(iterator->item);
+
+ if (midiEvent != NULL && (midiEvent->status >> 4) != 0x08) {
+ VstMidiEvent *vstMidiEvent = (VstMidiEvent *)malloc(sizeof(VstMidiEvent));
+ _fillVstMidiEvent(midiEvent, vstMidiEvent);
+ data->vstEvents->events[outIndex] = (VstEvent *)vstMidiEvent;
+ outIndex++;
+ }
+
+ iterator = (LinkedListIterator)(iterator->nextItem);
+ }
+
+ data->dispatcher(data->pluginHandle, effProcessEvents, 0, 0, data->vstEvents, 0.0f);
+ }
+
+ boolByte pluginVst2xSetProgram(Plugin plugin, const int programNumber)
+ {
+ PluginVst2xData data = (PluginVst2xData)plugin->extraData;
+ CharString currentProgram;
+ VstInt32 result;
+
+ if (programNumber < data->pluginHandle->numPrograms) {
+ result = (VstInt32)data->pluginHandle->dispatcher(data->pluginHandle, effSetProgram, 0, programNumber, NULL, 0.0f);
+
+ if (result != 0) {
+ logError("Plugin '%s' failed to load program number %d", plugin->pluginName->data, programNumber);
+ return false;
+ } else {
+ result = (VstInt32)data->pluginHandle->dispatcher(data->pluginHandle, effGetProgram, 0, 0, NULL, 0.0f);
+
+ if (result != programNumber) {
+ logError("Plugin '%s' claimed to load program %d successfully, but current program is %d",
+ plugin->pluginName->data, programNumber, result);
+ return false;
+ } else {
+ currentProgram = newCharStringWithCapacity(kVstMaxProgNameLen + 1);
+ data->dispatcher(data->pluginHandle, effGetProgramName, 0, 0, currentProgram->data, 0.0f);
+ logDebug("Current program is now '%s'", currentProgram->data);
+ freeCharString(currentProgram);
+ return true;
+ }
+ }
+ } else {
+ logError("Cannot load program, plugin '%s' only has %d programs",
+ plugin->pluginName->data, data->pluginHandle->numPrograms - 1);
+ return false;
+ }
+ }
+
+ static boolByte _setParameterVst2xPlugin(void *pluginPtr, unsigned int index, float value)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ PluginVst2xData data = (PluginVst2xData)(plugin->extraData);
+
+ if (index < (unsigned int)data->pluginHandle->numParams) {
+ CharString valueBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+ data->pluginHandle->setParameter(data->pluginHandle, index, value);
+ data->dispatcher(data->pluginHandle, effGetParamDisplay, index, 0, valueBuffer->data, 0.0f);
+ logInfo("Set parameter %d on plugin '%s' to %f (%s)",
+ index, plugin->pluginName->data, value, valueBuffer->data);
+ freeCharString(valueBuffer);
+ return true;
+ } else {
+ logError("Cannot set parameter %d on plugin '%s', invalid index", index, plugin->pluginName->data);
+ return false;
+ }
+ }
+
+ static void _prepareForProcessingVst2xPlugin(void *pluginPtr)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ _resumePlugin(plugin);
+ }
+
+ static void _closeVst2xPlugin(void *pluginPtr)
+ {
+ Plugin plugin = (Plugin)pluginPtr;
+ _suspendPlugin(plugin);
+ }
+
+ static void _freeVst2xPluginData(void *pluginDataPtr)
+ {
+ PluginVst2xData data = (PluginVst2xData)(pluginDataPtr);
+
+ data->dispatcher(data->pluginHandle, effClose, 0, 0, NULL, 0.0f);
+ data->dispatcher = NULL;
+ data->pluginHandle = NULL;
+ freePluginVst2xId(data->pluginId);
+ closeLibraryHandle(data->libraryHandle);
+
+ if (data->vstEvents != NULL) {
+ for (int i = 0; i < data->vstEvents->numEvents; i++) {
+ free(data->vstEvents->events[i]);
+ }
+
+ free(data->vstEvents);
+ }
+ }
+
+ Plugin newPluginVst2x(const CharString pluginName, const CharString pluginRoot)
+ {
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_VST_2X, PLUGIN_TYPE_UNKNOWN);
+ charStringCopy(plugin->pluginName, pluginName);
+ plugin->pluginLocation = _getVst2xPluginLocation(pluginName, pluginRoot);
+
+ plugin->openPlugin = _openVst2xPlugin;
+ plugin->displayInfo = _displayVst2xPluginInfo;
+ plugin->getSetting = _getVst2xPluginSetting;
+ plugin->processAudio = _processAudioVst2xPlugin;
+ plugin->processMidiEvents = _processMidiEventsVst2xPlugin;
+ plugin->setParameter = _setParameterVst2xPlugin;
+ plugin->prepareForProcessing = _prepareForProcessingVst2xPlugin;
+ plugin->closePlugin = _closeVst2xPlugin;
+ plugin->freePluginData = _freeVst2xPluginData;
+
+ PluginVst2xData extraData = (PluginVst2xData)malloc(sizeof(PluginVst2xDataMembers));
+ extraData->pluginHandle = NULL;
+ extraData->pluginId = NULL;
+ extraData->dispatcher = NULL;
+ extraData->libraryHandle = NULL;
+ extraData->isPluginShell = false;
+ extraData->shellPluginId = 0;
+ extraData->vstEvents = NULL;
+ plugin->extraData = extraData;
+
+ return plugin;
+ }
+}
diff --git a/source/plugin/PluginVst2x.h b/source/plugin/PluginVst2x.h
new file mode 100644
index 0000000..8c52d4e
--- /dev/null
+++ b/source/plugin/PluginVst2x.h
@@ -0,0 +1,91 @@
+//
+// PluginVst2x.h - MrsWatson
+// Created by Nik Reiman on 1/3/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginVst2x_h
+#define MrsWatson_PluginVst2x_h
+
+#include "base/CharString.h"
+#include "plugin/Plugin.h"
+
+static const char kPluginVst2xSubpluginSeparator = ':';
+
+/**
+ * List all available VST2.x plugins in common system locations. Note that this
+ * function does not do recursive searches (yet).
+ * @param pluginRoot User-provided plugin root path to search
+ */
+void listAvailablePluginsVst2x(const CharString pluginRoot);
+
+/**
+ * Create a new instance of a VST 2.x plugin
+ * @param pluginName Plugin name
+ * @param pluginRoot User-defined plugin root path
+ * @return Initialized Plugin object, or NULL if no such plugin was found
+ */
+Plugin newPluginVst2x(const CharString pluginName, const CharString pluginRoot);
+
+/**
+ * Get the VST2.x unique ID
+ * @param self
+ * @return Unique ID, or 0 if not known (yes, this can happen)
+ */
+unsigned long pluginVst2xGetUniqueId(const Plugin self);
+
+/**
+ * Get the plugin's version number. Used to determine compatible FXB/FXP patches.
+ * @param self
+ * @return Plugin version, or 0 if an error occurred. Note that some (buggy) plugins
+ * could potentially have a declared version of 0 as well.
+ */
+unsigned long pluginVst2xGetVersion(const Plugin self);
+
+/**
+ * See if a VST2.x plugin exists with the given name. Absolute paths will also
+ * be respected if passed.
+ * @param pluginName Plugin name (short name or absolute path)
+ * @param pluginRoot User-provided plugin root path
+ * @return True if such a plugin exists in any location, false otherwise
+ */
+boolByte pluginVst2xExists(const CharString pluginName, const CharString pluginRoot);
+
+/**
+ * Set an internal program number for a VST2.x plugin.
+ * @param self
+ * @param programNumber Program to set
+ * @return True if the program could be set and verified
+ */
+boolByte pluginVst2xSetProgram(Plugin self, const int programNumber);
+
+/**
+ * Set chuck preset data from an FXP preset to a VST2.x plugin.
+ * @param self
+ * @param chunk Chunk data to set
+ * @param chunkSize Chunk size
+ */
+void pluginVst2xSetProgramChunk(Plugin self, char *chunk, size_t chunkSize);
+
+#endif
diff --git a/source/plugin/PluginVst2xHostCallback.cpp b/source/plugin/PluginVst2xHostCallback.cpp
new file mode 100644
index 0000000..12cc409
--- /dev/null
+++ b/source/plugin/PluginVst2xHostCallback.cpp
@@ -0,0 +1,433 @@
+//
+// Vst2xHostCallback.cpp - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+// C++ includes
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+#include "plugin/PluginVst2xHostCallback.h"
+
+// C includes
+extern "C" {
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "app/BuildInfo.h"
+#include "audio/AudioSettings.h"
+#include "base/CharString.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginChain.h"
+#include "plugin/PluginVst2x.h"
+#include "plugin/PluginVst2xId.h"
+#include "time/AudioClock.h"
+
+ void pluginVst2xAudioMasterIOChanged(const Plugin self, AEffect const *const newValues);
+}
+
+// Global variables (sigh, unfortunately yes). When plugins ask for the time, they
+// expect that the host will pass them a pointer to a VstTimeInfo struct, a pointer
+// to which they are not the owner of and are not expected to free afterwards...
+// which is actually the correct thing to do, given that if this were the case, a
+// huge number of plugins would probably fail to do this and leak memory all over
+// the place.
+// Anyways, since we cannot scope this variable intelligently, we instead keep one
+// instance of it as a static variable, so it is always avaible to plugins when they
+// ask for the time.
+static VstTimeInfo vstTimeInfo;
+
+extern "C" {
+// Current plugin ID, which is mostly used by shell plugins during initialization.
+// Instance declared in PluginVst2x.cpp, see explanation for the global-ness and
+// need of this variable there.
+ extern VstInt32 currentPluginUniqueId;
+
+ static int _canHostDo(const char *pluginName, const char *canDoString)
+ {
+ boolByte supported = false;
+
+ logDebug("Plugin '%s' asked if we can do '%s'", pluginName, canDoString);
+
+ if (!strcmp(canDoString, EMPTY_STRING)) {
+ logWarn("Plugin '%s' asked if we can do an empty string. This is probably a bug.", pluginName);
+ } else if (!strcmp(canDoString, "sendVstEvents")) {
+ supported = true;
+ } else if (!strcmp(canDoString, "sendVstMidiEvent")) {
+ supported = true;
+ } else if (!strcmp(canDoString, "sendVstTimeInfo")) {
+ supported = true;
+ } else if (!strcmp(canDoString, "receiveVstEvents")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "receiveVstMidiEvent")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "reportConnectionChanges")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "acceptIOChanges")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "sizeWindow")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "offline")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "openFileSelector")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "closeFileSelector")) {
+ supported = false;
+ } else if (!strcmp(canDoString, "startStopProcess")) {
+ supported = true;
+ } else if (!strcmp(canDoString, "shellCategory")) {
+ supported = true;
+ } else if (!strcmp(canDoString, "sendVstMidiEventFlagIsRealtime")) {
+ supported = false;
+ } else {
+ logInfo("Plugin '%s' asked if host canDo '%s' (unimplemented)", pluginName, canDoString);
+ }
+
+ return supported;
+ }
+
+ VstIntPtr VSTCALLBACK pluginVst2xHostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *dataPtr, float opt)
+ {
+ // This string is used in a bunch of logging calls below
+ PluginVst2xId pluginId;
+
+ if (effect != NULL) {
+ pluginId = newPluginVst2xIdWithId((unsigned long)effect->uniqueID);
+ } else {
+ // During plugin initialization, the dispatcher can be called without a
+ // valid plugin instance, as the AEffect* struct is still not fully constructed
+ // at that point.
+ pluginId = newPluginVst2xId();
+ }
+
+ const char *pluginIdString = pluginId->idString->data;
+ VstIntPtr result = 0;
+
+ logDebug("Plugin '%s' called host dispatcher with %d, %d, %d", pluginIdString, opcode, index, value);
+
+ switch (opcode) {
+ case audioMasterAutomate:
+ // The plugin will call this if a parameter has changed via MIDI or the GUI, so the host can update
+ // itself accordingly. We don't care about this (for the time being), and as we don't support either
+ // GUI's or live MIDI, this opcode can be ignored.
+ break;
+
+ case audioMasterVersion:
+ // We are a VST 2.4 compatible host
+ result = 2400;
+ break;
+
+ case audioMasterCurrentId:
+ // Use the current plugin ID, needed by VST shell plugins to determine which sub-plugin to load
+ result = currentPluginUniqueId;
+ break;
+
+ case audioMasterIdle:
+ // Ignore
+ result = 1;
+ break;
+
+ case audioMasterPinConnected:
+ logDeprecated("audioMasterPinConnected", pluginIdString);
+ break;
+
+ case audioMasterWantMidi:
+ // This (deprecated) call is sometimes made by VST2.3 instruments to tell
+ // the host that it is an instrument. We can safely ignore it.
+ result = 1;
+ break;
+
+ case audioMasterGetTime: {
+ AudioClock audioClock = getAudioClock();
+
+ // These values are always valid
+ vstTimeInfo.samplePos = audioClock->currentFrame;
+ vstTimeInfo.sampleRate = getSampleRate();
+
+ // Set flags for transport state
+ vstTimeInfo.flags = 0;
+ vstTimeInfo.flags |= audioClock->transportChanged ? kVstTransportChanged : 0;
+ vstTimeInfo.flags |= audioClock->isPlaying ? kVstTransportPlaying : 0;
+
+ // Fill values based on other flags which may have been requested
+ if (value & kVstNanosValid) {
+ // It doesn't make sense to return this value, as the plugin may try to calculate
+ // something based on the current system time. As we are running offline, anything
+ // the plugin calculates here will probably be wrong given the way we are running.
+ // However, for realtime mode, this flag should be implemented in that case.
+ logWarn("Plugin '%s' asked for time in nanoseconds (unsupported)", pluginIdString);
+ }
+
+ if (value & kVstPpqPosValid) {
+ // TODO: Move calculations to AudioClock
+ double samplesPerBeat = (60.0 / getTempo()) * getSampleRate();
+ // Musical time starts with 1, not 0
+ vstTimeInfo.ppqPos = (vstTimeInfo.samplePos / samplesPerBeat) + 1.0;
+ logDebug("Current PPQ position is %g", vstTimeInfo.ppqPos);
+ vstTimeInfo.flags |= kVstPpqPosValid;
+ }
+
+ if (value & kVstTempoValid) {
+ vstTimeInfo.tempo = getTempo();
+ vstTimeInfo.flags |= kVstTempoValid;
+ }
+
+ if (value & kVstBarsValid) {
+ if (!(value & kVstPpqPosValid)) {
+ logError("Plugin requested position in bars, but not PPQ");
+ }
+
+ // TODO: Move calculations to AudioClock
+ double currentBarPos = floor(vstTimeInfo.ppqPos / (double)getTimeSignatureBeatsPerMeasure());
+ vstTimeInfo.barStartPos = currentBarPos * (double)getTimeSignatureBeatsPerMeasure() + 1.0;
+ logDebug("Current bar is %g", vstTimeInfo.barStartPos);
+ vstTimeInfo.flags |= kVstBarsValid;
+ }
+
+ if (value & kVstCyclePosValid) {
+ // We don't support cycling, so this is always 0
+ }
+
+ if (value & kVstTimeSigValid) {
+ vstTimeInfo.timeSigNumerator = getTimeSignatureBeatsPerMeasure();
+ vstTimeInfo.timeSigDenominator = getTimeSignatureNoteValue();
+ vstTimeInfo.flags |= kVstTimeSigValid;
+ }
+
+ if (value & kVstSmpteValid) {
+ logUnsupportedFeature("Current time in SMPTE format");
+ }
+
+ if (value & kVstClockValid) {
+ logUnsupportedFeature("Sample frames until next clock");
+ }
+
+ result = (VstIntPtr)&vstTimeInfo;
+ break;
+ }
+
+ case audioMasterProcessEvents:
+ logUnsupportedFeature("VST master opcode audioMasterProcessEvents");
+ break;
+
+ case audioMasterSetTime:
+ logDeprecated("audioMasterSetTime", pluginIdString);
+ break;
+
+ case audioMasterTempoAt:
+ logDeprecated("audioMasterTempoAt", pluginIdString);
+ break;
+
+ case audioMasterGetNumAutomatableParameters:
+ logDeprecated("audioMasterGetNumAutomatableParameters", pluginIdString);
+ break;
+
+ case audioMasterGetParameterQuantization:
+ logDeprecated("audioMasterGetParameterQuantization", pluginIdString);
+ break;
+
+ case audioMasterIOChanged: {
+ if (effect != NULL) {
+ PluginChain pluginChain = getPluginChain();
+ logDebug("Number of inputs: %d", effect->numInputs);
+ logDebug("Number of outputs: %d", effect->numOutputs);
+ logDebug("Number of parameters: %d", effect->numParams);
+ logDebug("Initial Delay: %d", effect->initialDelay);
+ result = -1;
+
+ for (unsigned int i = 0; i < pluginChain->numPlugins; ++i) {
+ if ((unsigned long)effect->uniqueID == pluginVst2xGetUniqueId(pluginChain->plugins[i])) {
+ logDebug("Updating plugin");
+ pluginVst2xAudioMasterIOChanged(pluginChain->plugins[i], effect);
+ result = 0;
+ break;//Only one plugin will match anyway.
+ }
+ }
+
+ break;
+ }
+ }
+
+ case audioMasterNeedIdle:
+ logDeprecated("audioMasterNeedIdle", pluginIdString);
+ break;
+
+ case audioMasterSizeWindow:
+ logWarn("Plugin '%s' asked us to resize window (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterGetSampleRate:
+ result = (int)getSampleRate();
+ break;
+
+ case audioMasterGetBlockSize:
+ result = (VstIntPtr)getBlocksize();
+ break;
+
+ case audioMasterGetInputLatency:
+ // Input latency is not used, and is always 0
+ result = 0;
+ break;
+
+ case audioMasterGetOutputLatency:
+ // Output latency is not used, and is always 0
+ result = 0;
+ break;
+
+ case audioMasterGetPreviousPlug:
+ logDeprecated("audioMasterGetPreviousPlug", pluginIdString);
+ break;
+
+ case audioMasterGetNextPlug:
+ logDeprecated("audioMasterGetNextPlug", pluginIdString);
+ break;
+
+ case audioMasterWillReplaceOrAccumulate:
+ logDeprecated("audioMasterWillReplaceOrAccumulate", pluginIdString);
+ break;
+
+ case audioMasterGetCurrentProcessLevel:
+ // We are not a multithreaded app and have no GUI, so this is unsupported.
+ result = kVstProcessLevelUnknown;
+ break;
+
+ case audioMasterGetAutomationState:
+ // Automation is also not supported (for now)
+ result = kVstAutomationUnsupported;
+ break;
+
+ case audioMasterOfflineStart:
+ logWarn("Plugin '%s' asked us to start offline processing (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterOfflineRead:
+ logWarn("Plugin '%s' asked to read offline data (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterOfflineWrite:
+ logWarn("Plugin '%s' asked to write offline data (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterOfflineGetCurrentPass:
+ logWarn("Plugin '%s' asked for current offline pass (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterOfflineGetCurrentMetaPass:
+ logWarn("Plugin '%s' asked for current offline meta pass (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterSetOutputSampleRate:
+ logDeprecated("audioMasterSetOutputSampleRate", pluginIdString);
+ break;
+
+ case audioMasterGetOutputSpeakerArrangement:
+ logDeprecated("audioMasterGetOutputSpeakerArrangement", pluginIdString);
+ break;
+
+ case audioMasterGetVendorString:
+ strncpy((char *)dataPtr, VENDOR_NAME, kVstMaxVendorStrLen);
+ result = 1;
+ break;
+
+ case audioMasterGetProductString:
+ strncpy((char *)dataPtr, PROGRAM_NAME, kVstMaxProductStrLen);
+ result = 1;
+ break;
+
+ case audioMasterGetVendorVersion:
+ // Return our version as a single string, in the form ABCC, which corresponds to version A.B.C
+ // Often times the patch can reach double-digits, so it gets two decimal places.
+ result = VERSION_MAJOR * 1000 + VERSION_MINOR * 100 + VERSION_PATCH;
+ break;
+
+ case audioMasterVendorSpecific:
+ logWarn("Plugin '%s' made a vendor specific call (unsupported). Arguments: %d, %d, %f", pluginIdString, index, value, opt);
+ break;
+
+ case audioMasterCanDo:
+ result = _canHostDo(pluginIdString, (char *)dataPtr);
+ break;
+
+ case audioMasterSetIcon:
+ logDeprecated("audioMasterSetIcon", pluginIdString);
+ break;
+
+ case audioMasterGetLanguage:
+ result = kVstLangEnglish;
+ break;
+
+ case audioMasterOpenWindow:
+ logDeprecated("audioMasterOpenWindow", pluginIdString);
+ break;
+
+ case audioMasterCloseWindow:
+ logDeprecated("audioMasterCloseWindow", pluginIdString);
+ break;
+
+ case audioMasterGetDirectory:
+ logWarn("Plugin '%s' asked for directory pointer (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterUpdateDisplay:
+ // Ignore
+ break;
+
+ case audioMasterBeginEdit:
+ logWarn("Plugin '%s' asked to begin parameter automation (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterEndEdit:
+ logWarn("Plugin '%s' asked to end parameter automation (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterOpenFileSelector:
+ logWarn("Plugin '%s' asked us to open file selector (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterCloseFileSelector:
+ logWarn("Plugin '%s' asked us to close file selector (unsupported)", pluginIdString);
+ break;
+
+ case audioMasterEditFile:
+ logDeprecated("audioMasterEditFile", pluginIdString);
+ break;
+
+ case audioMasterGetChunkFile:
+ logDeprecated("audioMasterGetChunkFile", pluginIdString);
+ break;
+
+ case audioMasterGetInputSpeakerArrangement:
+ logDeprecated("audioMasterGetInputSpeakerArrangement", pluginIdString);
+ break;
+
+ default:
+ logWarn("Plugin '%s' asked if host can do unknown opcode %d", pluginIdString, opcode);
+ break;
+ }
+
+ freePluginVst2xId(pluginId);
+ return result;
+ }
+} // extern "C"
diff --git a/source/plugin/PluginVst2xHostCallback.h b/source/plugin/PluginVst2xHostCallback.h
new file mode 100644
index 0000000..4f4ff46
--- /dev/null
+++ b/source/plugin/PluginVst2xHostCallback.h
@@ -0,0 +1,45 @@
+//
+// PluginVst2xCallbacks.h - MrsWatson
+// Created by Nik Reiman on 13 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginVst2xCallbacks_h
+#define MrsWatson_PluginVst2xCallbacks_h
+
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+
+// Callbacks used by VST2.x plugins
+typedef AEffect *(*Vst2xPluginEntryFunc)(audioMasterCallback host);
+typedef VstIntPtr (*Vst2xPluginDispatcherFunc)(AEffect *effect, VstInt32 opCode, VstInt32 index, VstIntPtr value, void *ptr, float opt);
+typedef float (*Vst2xPluginGetParameterFunc)(AEffect *effect, VstInt32 index);
+typedef void (*Vst2xPluginSetParameterFunc)(AEffect *effect, VstInt32 index, float value);
+typedef void (*Vst2xPluginProcessFunc)(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames);
+
+extern "C" {
+ VstIntPtr VSTCALLBACK pluginVst2xHostCallback(AEffect *effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void *dataPtr, float opt);
+}
+
+#endif
diff --git a/source/plugin/PluginVst2xId.c b/source/plugin/PluginVst2xId.c
new file mode 100644
index 0000000..3fd8038
--- /dev/null
+++ b/source/plugin/PluginVst2xId.c
@@ -0,0 +1,101 @@
+//
+// PluginVst2xId.c - MrsWatson
+// Created by Nik Reiman on 07 Jun 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "base/CharString.h"
+#include "plugin/PluginVst2xId.h"
+
+static CharString _convertIntIdToString(const unsigned long id)
+{
+ CharString result = newCharStringWithCapacity(5);
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ result->data[i] = (char)(id >> ((3 - i) * 8) & 0xff);
+ }
+
+ return result;
+}
+
+static unsigned long _convertStringIdToInt(const CharString idString)
+{
+ unsigned long result = 0;
+ int i;
+
+ if (idString != NULL && strlen(idString->data) == 4) {
+ for (i = 0; i < 4; i++) {
+ result |= (unsigned long)(idString->data[i]) << ((3 - i) * 8);
+ }
+ }
+
+ return result;
+}
+
+PluginVst2xId newPluginVst2xId(void)
+{
+ PluginVst2xId pluginVst2xId = (PluginVst2xId)malloc(sizeof(PluginVst2xIdMembers));
+
+ pluginVst2xId->id = 0;
+ pluginVst2xId->idString = newCharStringWithCString(PLUGIN_VST2X_ID_UNKNOWN);
+
+ return pluginVst2xId;
+}
+
+PluginVst2xId newPluginVst2xIdWithId(unsigned long id)
+{
+ PluginVst2xId pluginVst2xId = newPluginVst2xId();
+
+ pluginVst2xId->id = id;
+ freeCharString(pluginVst2xId->idString);
+ pluginVst2xId->idString = _convertIntIdToString(id);
+
+ return pluginVst2xId;
+}
+
+PluginVst2xId newPluginVst2xIdWithStringId(const CharString idString)
+{
+ PluginVst2xId pluginVst2xId = newPluginVst2xId();
+
+ pluginVst2xId->id = _convertStringIdToInt(idString);
+
+ if (idString != NULL && pluginVst2xId->id > 0) {
+ charStringCopy(pluginVst2xId->idString, idString);
+ }
+
+ return pluginVst2xId;
+}
+
+void freePluginVst2xId(PluginVst2xId self)
+{
+ if (self) {
+ freeCharString(self->idString);
+ free(self);
+ }
+}
diff --git a/source/plugin/PluginVst2xId.h b/source/plugin/PluginVst2xId.h
new file mode 100644
index 0000000..185a9c1
--- /dev/null
+++ b/source/plugin/PluginVst2xId.h
@@ -0,0 +1,45 @@
+//
+// PluginVst2xId.h - MrsWatson
+// Created by Nik Reiman on 07 Jun 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_PluginVst2xId_h
+#define MrsWatson_PluginVst2xId_h
+
+#define PLUGIN_VST2X_ID_UNKNOWN "????"
+
+typedef struct {
+ unsigned long id;
+ CharString idString;
+} PluginVst2xIdMembers;
+typedef PluginVst2xIdMembers *PluginVst2xId;
+
+PluginVst2xId newPluginVst2xId(void);
+PluginVst2xId newPluginVst2xIdWithId(unsigned long id);
+PluginVst2xId newPluginVst2xIdWithStringId(const CharString stringId);
+
+void freePluginVst2xId(PluginVst2xId self);
+
+#endif
diff --git a/source/plugin/PluginVst2xLinux.cpp b/source/plugin/PluginVst2xLinux.cpp
new file mode 100644
index 0000000..5543344
--- /dev/null
+++ b/source/plugin/PluginVst2xLinux.cpp
@@ -0,0 +1,113 @@
+//
+// PluginVst2xLinux.c - MrsWatson
+// Created by Nik Reiman on 13 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#if LINUX
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+
+extern "C" {
+#include <dlfcn.h>
+#include <stdlib.h>
+#include "base/CharString.h"
+#include "base/LinkedList.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginVst2xHostCallback.h"
+
+ LinkedList getVst2xPluginLocations(CharString currentDirectory)
+ {
+ LinkedList locations = newLinkedList();
+ CharString locationBuffer;
+ char *vstPathEnv;
+
+ linkedListAppend(locations, currentDirectory);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s/.vst", getenv("HOME"));
+ linkedListAppend(locations, locationBuffer);
+
+ locationBuffer = newCharString();
+ vstPathEnv = getenv("VST_PATH");
+
+ if (vstPathEnv != NULL) {
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s", vstPathEnv);
+ linkedListAppend(locations, locationBuffer);
+ } else {
+ freeCharString(locationBuffer);
+ }
+
+ return locations;
+ }
+
+ LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath)
+ {
+ void *libraryHandle = dlopen(pluginAbsolutePath->data, RTLD_NOW | RTLD_LOCAL);
+
+ if (libraryHandle == NULL) {
+ logError("Could not open library, %s", dlerror());
+ return NULL;
+ }
+
+ return libraryHandle;
+ }
+
+ AEffect *loadVst2xPlugin(LibraryHandle libraryHandle)
+ {
+ // Somewhat cheap hack to avoid a tricky compiler warning. Casting from void* to a proper function
+ // pointer will cause GCC to warn that "ISO C++ forbids casting between pointer-to-function and
+ // pointer-to-object". Here, we represent both types in a union and use the correct one in the given
+ // context, thus avoiding the need to cast anything.
+ // See also: http://stackoverflow.com/a/2742234/14302
+ union {
+ Vst2xPluginEntryFunc entryPointFuncPtr;
+ void *entryPointVoidPtr;
+ } entryPoint;
+
+ entryPoint.entryPointVoidPtr = dlsym(libraryHandle, "VSTPluginMain");
+
+ if (entryPoint.entryPointVoidPtr == NULL) {
+ entryPoint.entryPointVoidPtr = dlsym(libraryHandle, "main");
+
+ if (entryPoint.entryPointVoidPtr == NULL) {
+ logError("Couldn't get a pointer to plugin's main()");
+ return NULL;
+ }
+ }
+
+ Vst2xPluginEntryFunc mainEntryPoint = entryPoint.entryPointFuncPtr;
+ AEffect *plugin = mainEntryPoint(pluginVst2xHostCallback);
+ return plugin;
+ }
+
+ void closeLibraryHandle(LibraryHandle libraryHandle)
+ {
+ if (dlclose(libraryHandle) != 0) {
+ logWarn("Could not safely close plugin, possible resource leak");
+ }
+ }
+
+} // extern "C"
+#endif
diff --git a/source/plugin/PluginVst2xMac.cpp b/source/plugin/PluginVst2xMac.cpp
new file mode 100644
index 0000000..75369d4
--- /dev/null
+++ b/source/plugin/PluginVst2xMac.cpp
@@ -0,0 +1,136 @@
+//
+// PluginVst2xMac.c - MrsWatson
+// Created by Nik Reiman on 13 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#if MACOSX
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+
+extern "C" {
+#include <stdlib.h>
+#include <CoreFoundation/CFBundle.h>
+#include "base/CharString.h"
+#include "logging/EventLogger.h"
+#include "plugin/PluginVst2xHostCallback.h"
+
+ LinkedList getVst2xPluginLocations(CharString currentDirectory);
+ LinkedList getVst2xPluginLocations(CharString currentDirectory)
+ {
+ LinkedList locations = newLinkedList();
+ CharString locationBuffer;
+
+ linkedListAppend(locations, currentDirectory);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "/Library/Audio/Plug-Ins/VST");
+ linkedListAppend(locations, locationBuffer);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s/Library/Audio/Plug-Ins/VST", getenv("HOME"));
+ linkedListAppend(locations, locationBuffer);
+
+ return locations;
+ }
+
+ LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath);
+ LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath)
+ {
+ // Create a path to the bundle
+ CFStringRef pluginPathStringRef = CFStringCreateWithCString(NULL, pluginAbsolutePath->data, kCFStringEncodingASCII);
+ CFURLRef bundleUrl = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, pluginPathStringRef, kCFURLPOSIXPathStyle, true);
+
+ if (bundleUrl == NULL) {
+ logError("Couldn't make URL reference for plugin");
+ return NULL;
+ }
+
+ // Open the bundle
+ CFBundleRef bundleRef = CFBundleCreate(kCFAllocatorDefault, bundleUrl);
+
+ if (bundleRef == NULL) {
+ logError("Couldn't create bundle reference");
+ CFRelease(pluginPathStringRef);
+ CFRelease(bundleUrl);
+ return NULL;
+ }
+
+ // Clean up
+ CFRelease(pluginPathStringRef);
+ CFRelease(bundleUrl);
+
+ return bundleRef;
+ }
+
+ AEffect *loadVst2xPlugin(LibraryHandle libraryHandle);
+ AEffect *loadVst2xPlugin(LibraryHandle libraryHandle)
+ {
+ // Somewhat cheap hack to avoid a tricky compiler warning. Casting from void* to a proper function
+ // pointer will cause GCC to warn that "ISO C++ forbids casting between pointer-to-function and
+ // pointer-to-object". Here, we represent both types in a union and use the correct one in the given
+ // context, thus avoiding the need to cast anything.
+ // See also: http://stackoverflow.com/a/2742234/14302
+ union {
+ Vst2xPluginEntryFunc entryPointFuncPtr;
+ void *entryPointVoidPtr;
+ } entryPoint;
+
+ entryPoint.entryPointVoidPtr = CFBundleGetFunctionPointerForName(libraryHandle, CFSTR("VSTPluginMain"));
+ Vst2xPluginEntryFunc mainEntryPoint = entryPoint.entryPointFuncPtr;
+
+ // VST plugins previous to the 2.4 SDK used main_macho for the entry point name
+ if (mainEntryPoint == NULL) {
+ entryPoint.entryPointVoidPtr = CFBundleGetFunctionPointerForName(libraryHandle, CFSTR("main_macho"));
+ mainEntryPoint = entryPoint.entryPointFuncPtr;
+ }
+
+ if (mainEntryPoint == NULL) {
+ logError("Couldn't get a pointer to plugin's main()");
+ CFBundleUnloadExecutable(libraryHandle);
+ CFRelease(libraryHandle);
+ return NULL;
+ }
+
+ AEffect *plugin = mainEntryPoint(pluginVst2xHostCallback);
+
+ if (plugin == NULL) {
+ logError("Plugin's main() returns null");
+ CFBundleUnloadExecutable(libraryHandle);
+ CFRelease(libraryHandle);
+ return NULL;
+ }
+
+ return plugin;
+ }
+
+ void closeLibraryHandle(LibraryHandle libraryHandle);
+ void closeLibraryHandle(LibraryHandle libraryHandle)
+ {
+ CFBundleUnloadExecutable(libraryHandle);
+ CFRelease(libraryHandle);
+ }
+
+} // extern "C"
+#endif
diff --git a/source/plugin/PluginVst2xWindows.cpp b/source/plugin/PluginVst2xWindows.cpp
new file mode 100644
index 0000000..bbc2c8c
--- /dev/null
+++ b/source/plugin/PluginVst2xWindows.cpp
@@ -0,0 +1,114 @@
+//
+// PluginVst2xWindows.c - MrsWatson
+// Created by Nik Reiman on 13 May 13.
+// Copyright (c) 2013 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#if WINDOWS
+#define VST_FORCE_DEPRECATED 0
+#include "aeffectx.h"
+#include "plugin/PluginVst2xHostCallback.h"
+
+extern "C" {
+#include <stdio.h>
+#include "base/PlatformInfo.h"
+#include "logging/EventLogger.h"
+
+ static const char *kPlatformWindowsProgramFolder = "C:\\Program Files";
+ static const char *kPlatformWindows32BitProgramFolder = "C:\\Program Files (x86)";
+
+ LinkedList getVst2xPluginLocations(CharString currentDirectory)
+ {
+ LinkedList locations = newLinkedList();
+ CharString locationBuffer;
+ const char *programFiles = (!platformInfoIsRuntime64Bit() && platformInfoIsHost64Bit()) ?
+ kPlatformWindows32BitProgramFolder : kPlatformWindowsProgramFolder;
+
+ linkedListAppend(locations, currentDirectory);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "C:\\VstPlugins");
+ linkedListAppend(locations, locationBuffer);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s\\VstPlugIns", programFiles);
+ linkedListAppend(locations, locationBuffer);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s\\Common Files\\VstPlugIns", programFiles);
+ linkedListAppend(locations, locationBuffer);
+
+ locationBuffer = newCharString();
+ snprintf(locationBuffer->data, (size_t)(locationBuffer->capacity), "%s\\Steinberg\\VstPlugIns", programFiles);
+ linkedListAppend(locations, locationBuffer);
+
+ return locations;
+ }
+
+ LibraryHandle getLibraryHandleForPlugin(const CharString pluginAbsolutePath)
+ {
+ HMODULE libraryHandle = LoadLibraryExA((LPCSTR)pluginAbsolutePath->data, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
+ DWORD errorCode = GetLastError();
+
+ if (libraryHandle == NULL) {
+ if (errorCode == ERROR_BAD_EXE_FORMAT) {
+ logError("Could not open library, wrong architecture");
+ } else {
+ logError("Could not open library, error code '%s'", stringForLastError(errorCode));
+ }
+
+ return NULL;
+ }
+
+ return libraryHandle;
+ }
+
+ AEffect *loadVst2xPlugin(LibraryHandle libraryHandle)
+ {
+ Vst2xPluginEntryFunc entryPoint = (Vst2xPluginEntryFunc)GetProcAddress(libraryHandle, "VSTPluginMain");
+
+ if (entryPoint == NULL) {
+ entryPoint = (Vst2xPluginEntryFunc)GetProcAddress(libraryHandle, "VstPluginMain()");
+ }
+
+ if (entryPoint == NULL) {
+ entryPoint = (Vst2xPluginEntryFunc)GetProcAddress(libraryHandle, "main");
+ }
+
+ if (entryPoint == NULL) {
+ logError("Couldn't get a pointer to plugin's main()");
+ return NULL;
+ }
+
+ AEffect *plugin = entryPoint(pluginVst2xHostCallback);
+ return plugin;
+ }
+
+ void closeLibraryHandle(LibraryHandle libraryHandle)
+ {
+ FreeLibrary(libraryHandle);
+ }
+
+} // extern "C"
+#endif
diff --git a/source/time/AudioClock.c b/source/time/AudioClock.c
new file mode 100644
index 0000000..022abb2
--- /dev/null
+++ b/source/time/AudioClock.c
@@ -0,0 +1,72 @@
+//
+// AudioClock.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#include "time/AudioClock.h"
+
+AudioClock audioClockInstance = NULL;
+
+void initAudioClock(void)
+{
+ audioClockInstance = (AudioClock)malloc(sizeof(AudioClockMembers));
+ audioClockInstance->currentFrame = 0;
+ audioClockInstance->transportChanged = false;
+ audioClockInstance->isPlaying = false;
+}
+
+AudioClock getAudioClock(void)
+{
+ return audioClockInstance;
+}
+
+void advanceAudioClock(AudioClock self, const unsigned long blocksize)
+{
+ if (self->currentFrame == 0 || !self->isPlaying) {
+ self->transportChanged = true;
+ self->isPlaying = true;
+ } else {
+ self->transportChanged = false;
+ }
+
+ self->currentFrame += blocksize;
+}
+
+void audioClockStop(AudioClock self)
+{
+ self->isPlaying = false;
+ self->transportChanged = true;
+}
+
+void freeAudioClock(AudioClock self)
+{
+ if (self != NULL) {
+ free(self);
+ self = NULL;
+ }
+}
diff --git a/source/time/AudioClock.h b/source/time/AudioClock.h
new file mode 100644
index 0000000..a0e5f2d
--- /dev/null
+++ b/source/time/AudioClock.h
@@ -0,0 +1,82 @@
+//
+// AudioClock.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_AudioClock_h
+#define MrsWatson_AudioClock_h
+
+#include "base/Types.h"
+
+/**
+ * The AudioClock class keeps track of the sequence time and delivers the
+ * position in a variety of formats. Unlike most other classes, this one
+ * maintains a singleton instance because it must be accessed from C++
+ * callbacks where it is difficult to pass a void* pointer.
+ */
+
+typedef struct {
+ boolByte transportChanged;
+ boolByte isPlaying;
+ unsigned long currentFrame;
+} AudioClockMembers;
+typedef AudioClockMembers *AudioClock;
+extern AudioClock audioClockInstance;
+
+/**
+ * Initialize the global audio clock instance. Should be called fairly
+ * early in the program initialization, as other components may depend
+ * on knowing the current position.
+ */
+void initAudioClock(void);
+
+/**
+ * Get a reference to the global audio clock instance.
+ * @return Reference to global audio clock, or NULL if the global instance has
+ * not yet been initialized.
+ */
+AudioClock getAudioClock(void);
+
+/**
+ * Advanced the global audio clock by a given number of samples. This should be
+ * called after processing each block.
+ * @param self
+ * @param blocksize Block size in sample frames.
+ */
+void advanceAudioClock(AudioClock self, const unsigned long blocksize);
+
+/**
+ * Indicate that playback is stopped.
+ * @param self
+ */
+void audioClockStop(AudioClock self);
+
+/**
+ * Free an audio clock instance and its associated resources.
+ * @param self
+ */
+void freeAudioClock(AudioClock self);
+
+#endif
diff --git a/source/time/TaskTimer.c b/source/time/TaskTimer.c
new file mode 100644
index 0000000..9add3b1
--- /dev/null
+++ b/source/time/TaskTimer.c
@@ -0,0 +1,164 @@
+//
+// TaskTimer.c - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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 <stdio.h>
+#include <stdlib.h>
+
+#if UNIX
+#include <time.h>
+#endif
+
+#include "time/TaskTimer.h"
+
+TaskTimer newTaskTimer(const CharString component, const char *subcomponent)
+{
+ const char *componentCString = component != NULL ? component->data : NULL;
+ return newTaskTimerWithCString(componentCString, subcomponent);
+}
+
+TaskTimer newTaskTimerWithCString(const char *component, const char *subcomponent)
+{
+ TaskTimer taskTimer = (TaskTimer)malloc(sizeof(TaskTimerMembers));
+#if WINDOWS
+ LARGE_INTEGER queryFrequency;
+#endif
+
+ taskTimer->component = newCharStringWithCString(component);
+ taskTimer->subcomponent = newCharStringWithCString(subcomponent);
+ taskTimer->enabled = true;
+ taskTimer->_running = false;
+ taskTimer->totalTaskTime = 0.0;
+
+#if WINDOWS
+ QueryPerformanceFrequency(&queryFrequency);
+ taskTimer->counterFrequency = (double)(queryFrequency.QuadPart) / 1000.0;
+#endif
+
+ return taskTimer;
+}
+
+void taskTimerStart(TaskTimer self)
+{
+ if (self->_running) {
+ taskTimerStop(self);
+ }
+
+#if WINDOWS
+ QueryPerformanceCounter(&(self->startTime));
+#elif UNIX
+ gettimeofday(&self->startTime, NULL);
+#endif
+ self->_running = true;
+}
+
+double taskTimerStop(TaskTimer self)
+{
+ double elapsedTimeInMs = 0.0;
+#if UNIX
+ double elapsedFullSeconds;
+ double elapsedMicroseconds;
+ struct timeval currentTime;
+#elif WINDOWS
+ LONGLONG elapsedTimeInClocks;
+ LARGE_INTEGER stopTime;
+#endif
+
+ if (!self->_running) {
+ return 0.0;
+ }
+
+#if UNIX
+
+ if (gettimeofday(&currentTime, NULL) == 0) {
+ if (currentTime.tv_sec == self->startTime.tv_sec) {
+ elapsedTimeInMs = (double)(currentTime.tv_usec - self->startTime.tv_usec) / 1000.0;
+ } else {
+ elapsedFullSeconds = (double)(currentTime.tv_sec - self->startTime.tv_sec - 1);
+ elapsedMicroseconds = (double)(currentTime.tv_usec + (1000000l - self->startTime.tv_usec));
+ elapsedTimeInMs = (elapsedFullSeconds * 1000.0) + (elapsedMicroseconds / 1000.0);
+ }
+
+ self->totalTaskTime += elapsedTimeInMs;
+ }
+
+#elif WINDOWS
+ QueryPerformanceCounter(&stopTime);
+ elapsedTimeInClocks = stopTime.QuadPart - self->startTime.QuadPart;
+ elapsedTimeInMs = (double)(elapsedTimeInClocks) / self->counterFrequency;
+ self->totalTaskTime += elapsedTimeInMs;
+#endif
+
+ self->_running = false;
+ return elapsedTimeInMs;
+}
+
+CharString taskTimerHumanReadbleString(TaskTimer self)
+{
+ int hours, minutes, seconds;
+ CharString outString = newCharStringWithCapacity(kCharStringLengthShort);
+
+ if (self->totalTaskTime < 1000) {
+ snprintf(outString->data, outString->capacity, "%dms", (int)self->totalTaskTime);
+ } else if (self->totalTaskTime < 60 * 1000) {
+ seconds = (int)(self->totalTaskTime / 1000.0);
+ snprintf(outString->data, outString->capacity, "%dsec", seconds);
+ } else {
+ seconds = (int)(self->totalTaskTime / 1000.0) % 60;
+ minutes = (int)(self->totalTaskTime / (1000.0 * 60.0));
+
+ if (minutes > 60) {
+ hours = minutes / 60;
+ minutes = (minutes % 60);
+ snprintf(outString->data, outString->capacity, "%d:%d:%dsec", hours, minutes, seconds);
+ } else {
+ snprintf(outString->data, outString->capacity, "%d:%dsec", minutes, seconds);
+ }
+ }
+
+ return outString;
+}
+
+void taskTimerSleep(const double milliseconds)
+{
+#if UNIX
+ struct timespec sleepTime;
+ sleepTime.tv_sec = 0;
+ sleepTime.tv_nsec = (long)(1000000.0 * milliseconds);
+ nanosleep(&sleepTime, NULL);
+#elif WINDOWS
+ Sleep((DWORD)milliseconds);
+#endif
+}
+
+void freeTaskTimer(TaskTimer self)
+{
+ if (self != NULL) {
+ freeCharString(self->component);
+ freeCharString(self->subcomponent);
+ free(self);
+ }
+}
diff --git a/source/time/TaskTimer.h b/source/time/TaskTimer.h
new file mode 100644
index 0000000..43dac8c
--- /dev/null
+++ b/source/time/TaskTimer.h
@@ -0,0 +1,107 @@
+//
+// TaskTimer.h - MrsWatson
+// Created by Nik Reiman on 1/5/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * 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 HOLDER 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.
+//
+
+#ifndef MrsWatson_TaskTimer_h
+#define MrsWatson_TaskTimer_h
+
+#include "base/CharString.h"
+
+#if UNIX
+#include <sys/time.h>
+#endif
+
+typedef struct {
+ CharString component;
+ CharString subcomponent;
+ boolByte enabled;
+ boolByte _running;
+ double totalTaskTime;
+
+#if WINDOWS
+ LARGE_INTEGER startTime;
+ double counterFrequency;
+#elif UNIX
+ struct timeval startTime;
+#endif
+} TaskTimerMembers;
+typedef TaskTimerMembers *TaskTimer;
+
+/**
+ * Create a new task timer.
+ * @param component Component name which this timer belongs to. NULL or empty
+ * string may be passed for this argument.
+ * @param subcomponent Subcomponent which this timer is measured. NULL or empty
+ * string may be passed for this argument.
+ * @return Initialized instance
+ */
+TaskTimer newTaskTimer(const CharString component, const char *subcomponent);
+
+/**
+ * Create a new task timer.
+ * @param component Component name which this timer belongs to. NULL or empty
+ * string may be passed for this argument.
+ * @param subcomponent Subcomponent which this timer is measured. NULL or empty
+ * string may be passed for this argument.
+ * @return Initialized instance
+ */
+TaskTimer newTaskTimerWithCString(const char *component, const char *subcomponent);
+
+/**
+ * Start the timer. Timers may be stopped and started multiple times.
+ * @param self
+ */
+void taskTimerStart(TaskTimer self);
+
+/**
+ * Stop the timer. Timers may be stopped and started multiple times.
+ * @param self
+ * @return Time used since last call to taskTimerStart()
+ */
+double taskTimerStop(TaskTimer self);
+
+/**
+ * Get the string representation of the total accumulated time for this timer.
+ * @param self
+ * @return Formatted string, which the caller must free themselves when finished
+ */
+CharString taskTimerHumanReadbleString(TaskTimer self);
+
+/**
+ * Suspend execution for a given amount of milliseconds. Depending on the host
+ * operating system, the amount of time actually slept may differ slightly from
+ * the requested amount.
+ * @param milliseconds Number of milliseconds to sleep
+ */
+void taskTimerSleep(const double milliseconds);
+
+/**
+ * Free a task timer and its associated resources
+ * @param self
+ */
+void freeTaskTimer(TaskTimer self);
+
+#endif
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..6489fdb
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,90 @@
+cmake_minimum_required(VERSION 2.8.11)
+project(MrsWatsonTest)
+
+include(${cmake_SCRIPTS_DIR}/ConfigureTarget.cmake)
+
+set(mrswatsontest_SOURCES
+ MrsWatsonTestMain.c
+ analysis/AnalysisClipping.c
+ analysis/AnalysisClippingTest.c
+ analysis/AnalysisDistortion.c
+ analysis/AnalysisDistortionTest.c
+ analysis/AnalysisSilence.c
+ analysis/AnalysisSilenceTest.c
+ analysis/AnalyzeFile.c
+ app/ProgramOptionTest.c
+ audio/AudioSettingsTest.c
+ audio/SampleBufferTest.c
+ base/CharStringTest.c
+ base/EndianTest.c
+ base/FileTest.c
+ base/LinkedListTest.c
+ base/PlatformInfoTest.c
+ io/SampleSourceTest.c
+ midi/MidiSequenceTest.c
+ midi/MidiSourceTest.c
+ plugin/PluginChainTest.c
+ plugin/PluginMock.c
+ plugin/PluginPresetMock.c
+ plugin/PluginPresetTest.c
+ plugin/PluginTest.c
+ plugin/PluginVst2xIdTest.c
+ time/AudioClockTest.c
+ time/TaskTimerTest.c
+ unit/ApplicationRunner.c
+ unit/IntegrationTests.c
+ unit/TestRunner.c
+ unit/UnitTests.c
+)
+
+set(mrswatsontest_HEADERS
+ MrsWatsonTestMain.h
+ analysis/AnalysisClipping.h
+ analysis/AnalysisDistortion.h
+ analysis/AnalysisSilence.h
+ analysis/AnalyzeFile.h
+ plugin/PluginMock.h
+ plugin/PluginPresetMock.h
+ unit/ApplicationRunner.h
+ unit/TestRunner.h
+)
+
+source_group(analysis ".*/analysis/.*")
+source_group(app ".*/app/.*")
+source_group(audio ".*/audio/.*")
+source_group(base ".*/base/.*")
+source_group(io ".*/io/.*")
+source_group(midi ".*/midi/.*")
+source_group(plugin ".*/plugin/.*")
+source_group(time ".*/time/.*")
+source_group(unit ".*/unit/.*")
+
+set(mrswatsontest_LIBS mrswatsoncore)
+set(mrswatsontest_64_LIBS mrswatsoncore64)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ set(mrswatsontest_LIBS ${mrswatsontest_LIBS} dl)
+ set(mrswatsontest_64_LIBS ${mrswatsontest_64_LIBS} dl)
+endif()
+
+if(WITH_AUDIOFILE)
+ set(mrswatsontest_LIBS ${mrswatsontest_LIBS} audiofile)
+ set(mrswatsontest_64_LIBS ${mrswatsontest_64_LIBS} audiofile64)
+
+ if(WITH_FLAC)
+ set(mrswatsontest_LIBS ${mrswatsontest_LIBS} flac)
+ set(mrswatsontest_64_LIBS ${mrswatsontest_64_LIBS} flac64)
+ endif()
+endif()
+
+add_executable(mrswatsontest ${mrswatsontest_SOURCES} ${mrswatsontest_HEADERS})
+target_link_libraries(mrswatsontest ${mrswatsontest_LIBS})
+add_executable(mrswatsontest64 ${mrswatsontest_SOURCES} ${mrswatsontest_HEADERS})
+target_link_libraries(mrswatsontest64 ${mrswatsontest_64_LIBS})
+
+configure_target(mrswatsontest 32)
+configure_target(mrswatsontest64 64)
+
+# The main executable must be built to correctly run integration tests
+add_dependencies(mrswatsontest mrswatson)
+add_dependencies(mrswatsontest64 mrswatson64)
diff --git a/test/MrsWatsonTestMain.c b/test/MrsWatsonTestMain.c
new file mode 100644
index 0000000..980cbfd
--- /dev/null
+++ b/test/MrsWatsonTestMain.c
@@ -0,0 +1,348 @@
+//
+// MrsWatsonTestMain.c
+// MrsWatson
+//
+// Created by Nik Reiman on 8/9/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "app/ProgramOption.h"
+#include "base/File.h"
+#include "base/PlatformInfo.h"
+#include "unit/ApplicationRunner.h"
+
+#include "MrsWatsonTestMain.h"
+
+extern LinkedList getTestSuites(void);
+extern TestSuite findTestSuite(LinkedList testSuites, const CharString testSuiteName);
+extern TestCase findTestCase(TestSuite testSuite, char *testName);
+extern void printUnitTestSuites(void);
+extern TestSuite runUnitTests(LinkedList testSuites, boolByte onlyPrintFailing);
+extern TestSuite runIntegrationTests(TestEnvironment testEnvironment);
+
+static const char *DEFAULT_TEST_SUITE_NAME = "all";
+
+#if UNIX
+static const char *MRSWATSON_EXE_NAME = "mrswatson";
+#elif WINDOWS
+static const char *MRSWATSON_EXE_NAME = "mrswatson.exe";
+#else
+static const char *MRSWATSON_EXE_NAME = "mrswatson";
+#endif
+
+static ProgramOptions _newTestProgramOptions(void)
+{
+ ProgramOptions programOptions = newProgramOptions(NUM_TEST_OPTIONS);
+ srand((unsigned int)time(NULL));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_SUITE, "suite",
+ "Choose a test suite to run. Current suites include:\n\
+\t- Integration: run audio quality tests against actual executable\n\
+\t- Unit: run all internal unit tests\n\
+\t- All: run all tests (default)\n\
+\t- A suite name (use '--list' to see all suite names)",
+ true, kProgramOptionTypeString, kProgramOptionArgumentTypeRequired));
+ programOptionsSetCString(programOptions, OPTION_TEST_SUITE, DEFAULT_TEST_SUITE_NAME);
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_NAME, "test",
+ "Run a single test. Tests are named 'Suite:Name', for example:\n\
+\t-t 'LinkedList:AppendItem'",
+ true, kProgramOptionTypeString, kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_PRINT_TESTS, "list-tests",
+ "List all unit tests in the same format required by --test",
+ true, kProgramOptionTypeEmpty, kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_MRSWATSON_PATH, "mrswatson-path",
+ "Path to mrswatson executable. By default, mrswatson is assumed to be in the same \
+directory as mrswatsontest. Only required for running integration tests.",
+ true, kProgramOptionTypeString, kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_RESOURCES_PATH, "resources",
+ "Path to resources directory. Only required for running integration tests.",
+ true, kProgramOptionTypeString, kProgramOptionArgumentTypeRequired));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_PRINT_ONLY_FAILING, "quiet",
+ "Print only failing tests. Note that if a test causes the suite to crash, the \
+bad test's name will not be printed. In this case, re-run without this option, as \
+the test names will be printed before the tests are executed.",
+ true, kProgramOptionTypeEmpty, kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_KEEP_FILES, "keep-files",
+ "Keep files generated by integration tests (such as log files, audio output, \
+etc.). Normally these files are automatically removed if a test succeeds.",
+ true, kProgramOptionTypeEmpty, kProgramOptionArgumentTypeNone));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_HELP, "help",
+ "Print full program help (this screen), or just the help for a single argument.",
+ true, kProgramOptionTypeString, kProgramOptionArgumentTypeOptional));
+
+ programOptionsAdd(programOptions, newProgramOptionWithName(OPTION_TEST_VERBOSE, "verbose",
+ "Show logging output from tests",
+ true, kProgramOptionTypeEmpty, kProgramOptionArgumentTypeNone));
+
+ return programOptions;
+}
+
+void _printTestSummary(int testsRun, int testsPassed, int testsFailed, int testsSkipped)
+{
+ CharString numberBuffer = newCharStringWithCapacity(kCharStringLengthShort);
+
+ printToLog(getLogColor(kTestLogEventReset), NULL, "Ran ");
+ sprintf(numberBuffer->data, "%d", testsRun);
+ printToLog(getLogColor(kTestLogEventSection), NULL, numberBuffer->data);
+ printToLog(getLogColor(kTestLogEventReset), NULL, " tests: ");
+ sprintf(numberBuffer->data, "%d", testsPassed);
+ printToLog(getLogColor(kTestLogEventPass), NULL, numberBuffer->data);
+ printToLog(getLogColor(kTestLogEventReset), NULL, " passed, ");
+
+ sprintf(numberBuffer->data, "%d", testsFailed);
+
+ if (testsFailed > 0) {
+ printToLog(getLogColor(kTestLogEventFail), NULL, numberBuffer->data);
+ } else {
+ printToLog(getLogColor(kTestLogEventReset), NULL, numberBuffer->data);
+ }
+
+ printToLog(getLogColor(kTestLogEventReset), NULL, " failed, ");
+
+ sprintf(numberBuffer->data, "%d", testsSkipped);
+
+ if (testsSkipped > 0) {
+ printToLog(getLogColor(kTestLogEventSkip), NULL, numberBuffer->data);
+ } else {
+ printToLog(getLogColor(kTestLogEventReset), NULL, numberBuffer->data);
+ }
+
+ printToLog(getLogColor(kTestLogEventReset), NULL, " skipped");
+ flushLog(NULL);
+
+ freeCharString(numberBuffer);
+}
+
+File _findMrsWatsonExe(CharString mrsWatsonExeArg)
+{
+ CharString currentExecutableFilename = NULL;
+ CharString mrsWatsonExeName = NULL;
+ File currentExecutablePath = NULL;
+ File currentExecutableDir = NULL;
+ File mrsWatsonExe = NULL;
+
+ if (mrsWatsonExeArg != NULL && !charStringIsEmpty(mrsWatsonExeArg)) {
+ mrsWatsonExe = newFileWithPath(mrsWatsonExeArg);
+ } else {
+ currentExecutableFilename = fileGetExecutablePath();
+ currentExecutablePath = newFileWithPath(currentExecutableFilename);
+
+ if (currentExecutablePath != NULL) {
+ currentExecutableDir = fileGetParent(currentExecutablePath);
+
+ if (currentExecutableDir != NULL) {
+ mrsWatsonExeName = newCharStringWithCString(MRSWATSON_EXE_NAME);
+
+ if (platformInfoIsRuntime64Bit()) {
+ charStringAppendCString(mrsWatsonExeName, "64");
+ }
+
+ mrsWatsonExe = newFileWithParent(currentExecutableDir, mrsWatsonExeName);
+ }
+ }
+ }
+
+ freeCharString(currentExecutableFilename);
+ freeCharString(mrsWatsonExeName);
+ freeFile(currentExecutablePath);
+ freeFile(currentExecutableDir);
+ return mrsWatsonExe;
+}
+
+int main(int argc, char *argv[])
+{
+ ProgramOptions programOptions;
+ int totalTestsRun = 0;
+ int totalTestsPassed = 0;
+ int totalTestsFailed = 0;
+ int totalTestsSkipped = 0;
+ CharString testSuiteToRun = NULL;
+ CharString testSuiteName = NULL;
+ CharString mrsWatsonExeName = NULL;
+ CharString totalTimeString = NULL;
+ CharString executablePath = NULL;
+ File mrsWatsonExe = NULL;
+ File resourcesPath = NULL;
+ boolByte shouldRunUnitTests = false;
+ boolByte shouldRunIntegrationTests = false;
+ TestCase testCase = NULL;
+ TestSuite testSuite = NULL;
+ LinkedList testSuites = NULL;
+ TestSuite unitTestResults = NULL;
+ TestEnvironment testEnvironment = NULL;
+ TaskTimer timer;
+ char *testArgument;
+ char *colon;
+ char *testCaseName;
+
+ timer = newTaskTimer(NULL, NULL);
+ taskTimerStart(timer);
+
+ programOptions = _newTestProgramOptions();
+
+ if (!programOptionsParseArgs(programOptions, argc, argv)) {
+ printf("Or run with --help (option) to see help for a single option\n");
+ return -1;
+ }
+
+ if (programOptions->options[OPTION_TEST_HELP]->enabled) {
+ printf("Run with '--help full' to see extended help for all options.\n");
+
+ if (charStringIsEmpty(programOptionsGetString(programOptions, OPTION_TEST_HELP))) {
+ printf("All options, where <argument> is required and [argument] is optional\n");
+ programOptionsPrintHelp(programOptions, false, DEFAULT_INDENT_SIZE);
+ } else {
+ programOptionsPrintHelp(programOptions, true, DEFAULT_INDENT_SIZE);
+ }
+
+ return -1;
+ } else if (programOptions->options[OPTION_TEST_PRINT_TESTS]->enabled) {
+ printUnitTestSuites();
+ return -1;
+ }
+
+ if (programOptions->options[OPTION_TEST_VERBOSE]->enabled) {
+ initEventLogger();
+ setLogLevel(LOG_DEBUG);
+ }
+
+ testSuiteToRun = programOptionsGetString(programOptions, OPTION_TEST_SUITE);
+
+ if (programOptions->options[OPTION_TEST_NAME]->enabled) {
+ shouldRunUnitTests = false;
+ shouldRunIntegrationTests = false;
+
+ testArgument = programOptionsGetString(programOptions, OPTION_TEST_NAME)->data;
+ colon = strchr(testArgument, ':');
+
+ if (colon == NULL) {
+ printf("ERROR: Invalid test name");
+ programOptionPrintHelp(programOptions->options[OPTION_TEST_NAME], true, DEFAULT_INDENT_SIZE, 0);
+ return -1;
+ }
+
+ testCaseName = strdup(colon + 1);
+ *colon = '\0';
+
+ testSuiteName = programOptionsGetString(programOptions, OPTION_TEST_NAME);
+ testSuites = getTestSuites();
+ testSuite = findTestSuite(testSuites, testSuiteName);
+
+ if (testSuite == NULL) {
+ printf("ERROR: Could not find test suite '%s'\n", testSuiteName->data);
+ freeLinkedListAndItems(testSuites, (LinkedListFreeItemFunc)freeTestSuite);
+ return -1;
+ }
+
+ testCase = findTestCase(testSuite, testCaseName);
+
+ if (testCase == NULL) {
+ printf("ERROR: Could not find test case '%s'\n", testCaseName);
+ freeLinkedListAndItems(testSuites, (LinkedListFreeItemFunc)freeTestSuite);
+ return -1;
+ } else {
+ printf("Running test in %s:\n", testSuite->name);
+ runTestCase(testCase, testSuite);
+ freeLinkedListAndItems(testSuites, (LinkedListFreeItemFunc)freeTestSuite);
+ }
+ } else if (charStringIsEqualToCString(testSuiteToRun, "all", true)) {
+ shouldRunUnitTests = true;
+ shouldRunIntegrationTests = true;
+ } else if (charStringIsEqualToCString(testSuiteToRun, "unit", true)) {
+ shouldRunUnitTests = true;
+ } else if (charStringIsEqualToCString(testSuiteToRun, "integration", true)) {
+ shouldRunIntegrationTests = true;
+ } else {
+ testSuites = getTestSuites();
+ testSuite = findTestSuite(testSuites, testSuiteToRun);
+
+ if (testSuite == NULL) {
+ printf("ERROR: Invalid test suite '%s'\n", testSuiteToRun->data);
+ printf("Run with '--list' suite to show possible test suites\n");
+ freeLinkedListAndItems(testSuites, (LinkedListFreeItemFunc)freeTestSuite);
+ return -1;
+ } else {
+ testSuite->onlyPrintFailing = programOptions->options[OPTION_TEST_PRINT_ONLY_FAILING]->enabled;
+ runTestSuite(testSuite, NULL);
+ totalTestsRun = testSuite->numSuccess + testSuite->numFail;
+ totalTestsPassed = testSuite->numSuccess;
+ totalTestsFailed = testSuite->numFail;
+ totalTestsSkipped = testSuite->numSkips;
+ freeLinkedListAndItems(testSuites, (LinkedListFreeItemFunc)freeTestSuite);
+ }
+ }
+
+ if (shouldRunUnitTests) {
+ printf("=== Unit tests ===\n");
+ testSuites = getTestSuites();
+ unitTestResults = runUnitTests(testSuites,
+ programOptions->options[OPTION_TEST_PRINT_ONLY_FAILING]->enabled);
+
+ totalTestsRun += unitTestResults->numSuccess + unitTestResults->numFail;
+ totalTestsPassed += unitTestResults->numSuccess;
+ totalTestsFailed += unitTestResults->numFail;
+ totalTestsSkipped += unitTestResults->numSkips;
+
+ freeLinkedListAndItems(testSuites, (LinkedListFreeItemFunc)freeTestSuite);
+ freeTestSuite(unitTestResults);
+ }
+
+ mrsWatsonExe = _findMrsWatsonExe(programOptionsGetString(programOptions, OPTION_TEST_MRSWATSON_PATH));
+
+ if (shouldRunIntegrationTests && mrsWatsonExe == NULL) {
+ printf("Could not find mrswatson, skipping integration tests\n");
+ shouldRunIntegrationTests = false;
+ }
+
+ if (programOptions->options[OPTION_TEST_RESOURCES_PATH]->enabled) {
+ resourcesPath = newFileWithPath(programOptionsGetString(programOptions, OPTION_TEST_RESOURCES_PATH));
+ }
+
+ if (shouldRunIntegrationTests && !fileExists(resourcesPath)) {
+ printf("Could not find test resources, skipping integration tests\n");
+ shouldRunIntegrationTests = false;
+ }
+
+ if (shouldRunIntegrationTests) {
+ printf("\n=== Integration tests ===\n");
+ testEnvironment = newTestEnvironment(mrsWatsonExe->absolutePath->data, resourcesPath->absolutePath->data);
+ testEnvironment->results->onlyPrintFailing = programOptions->options[OPTION_TEST_PRINT_ONLY_FAILING]->enabled;
+ testEnvironment->results->keepFiles = programOptions->options[OPTION_TEST_KEEP_FILES]->enabled;
+ runIntegrationTests(testEnvironment);
+ totalTestsRun += testEnvironment->results->numSuccess + testEnvironment->results->numFail;
+ totalTestsPassed += testEnvironment->results->numSuccess;
+ totalTestsFailed += testEnvironment->results->numFail;
+ totalTestsSkipped += testEnvironment->results->numSkips;
+ }
+
+ taskTimerStop(timer);
+
+ if (totalTestsRun > 0) {
+ printf("\n=== Finished ===\n");
+ _printTestSummary(totalTestsRun, totalTestsPassed, totalTestsFailed, totalTestsSkipped);
+ totalTimeString = taskTimerHumanReadbleString(timer);
+ printf("Total time: %s\n", totalTimeString->data);
+ }
+
+ freeTestEnvironment(testEnvironment);
+ freeProgramOptions(programOptions);
+ freeCharString(executablePath);
+ freeCharString(mrsWatsonExeName);
+ freeCharString(totalTimeString);
+ freeFile(mrsWatsonExe);
+ freeFile(resourcesPath);
+ freeTaskTimer(timer);
+ return totalTestsFailed;
+}
diff --git a/test/MrsWatsonTestMain.h b/test/MrsWatsonTestMain.h
new file mode 100644
index 0000000..8d8e94e
--- /dev/null
+++ b/test/MrsWatsonTestMain.h
@@ -0,0 +1,20 @@
+//
+// TestSuite.h
+// MrsWatson
+//
+// Created by Nik Reiman on 8/9/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+
+typedef enum {
+ OPTION_TEST_SUITE,
+ OPTION_TEST_NAME,
+ OPTION_TEST_PRINT_TESTS,
+ OPTION_TEST_MRSWATSON_PATH,
+ OPTION_TEST_RESOURCES_PATH,
+ OPTION_TEST_PRINT_ONLY_FAILING,
+ OPTION_TEST_KEEP_FILES,
+ OPTION_TEST_HELP,
+ OPTION_TEST_VERBOSE,
+ NUM_TEST_OPTIONS
+} TestProgramOptionIndex;
diff --git a/test/analysis/AnalysisClipping.c b/test/analysis/AnalysisClipping.c
new file mode 100644
index 0000000..55229ae
--- /dev/null
+++ b/test/analysis/AnalysisClipping.c
@@ -0,0 +1,25 @@
+#include <math.h>
+#include "AnalysisClipping.h"
+
+boolByte analysisClipping(const SampleBuffer sampleBuffer, AnalysisFunctionData data)
+{
+ for (ChannelCount i = 0; i < sampleBuffer->numChannels; i++) {
+ for (SampleCount j = 0; j < sampleBuffer->blocksize; j++) {
+ if (fabs(sampleBuffer->samples[i][j]) >= 1.0f) {
+ if (data->consecutiveFailCounter > data->failTolerance) {
+ data->failedChannel = i;
+ data->failedSample = j;
+ return false;
+ } else {
+ data->consecutiveFailCounter++;
+ }
+ } else {
+ if (data->consecutiveFailCounter > 0) {
+ data->consecutiveFailCounter--;
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/test/analysis/AnalysisClipping.h b/test/analysis/AnalysisClipping.h
new file mode 100644
index 0000000..6dc56ef
--- /dev/null
+++ b/test/analysis/AnalysisClipping.h
@@ -0,0 +1,3 @@
+#include "AnalyzeFile.h"
+
+boolByte analysisClipping(const SampleBuffer sampleBuffer, AnalysisFunctionData data);
diff --git a/test/analysis/AnalysisClippingTest.c b/test/analysis/AnalysisClippingTest.c
new file mode 100644
index 0000000..bd062a3
--- /dev/null
+++ b/test/analysis/AnalysisClippingTest.c
@@ -0,0 +1,37 @@
+#include "unit/TestRunner.h"
+#include "AnalysisClipping.h"
+
+static int _testAnalysisClipping(void)
+{
+ SampleBuffer s = newSampleBuffer(1, 128);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ unsigned long i;
+
+ for (i = 0; i < s->blocksize; i++) {
+ s->samples[0][i] = 1.0f;
+ }
+
+ assertFalse(analysisClipping(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testAnalysisNotClipping(void)
+{
+ SampleBuffer s = newSampleBuffer(1, 8);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ assert(analysisClipping(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+TestSuite addAnalysisClippingTests(void);
+TestSuite addAnalysisClippingTests(void)
+{
+ TestSuite testSuite = newTestSuite("AnalysisClipping", NULL, NULL);
+ addTest(testSuite, "AnalysisClipping", _testAnalysisClipping);
+ addTest(testSuite, "AnalysisNotClipping", _testAnalysisNotClipping);
+ return testSuite;
+}
diff --git a/test/analysis/AnalysisDistortion.c b/test/analysis/AnalysisDistortion.c
new file mode 100644
index 0000000..27b8492
--- /dev/null
+++ b/test/analysis/AnalysisDistortion.c
@@ -0,0 +1,33 @@
+#include <stdlib.h>
+#include "AnalysisDistortion.h"
+
+// If two samples differ by more than this amount, then we call it distortion
+static const Sample kAnalysisDistortionTolerance = 0.5f;
+
+boolByte analysisDistortion(const SampleBuffer sampleBuffer, AnalysisFunctionData data)
+{
+ Sample difference;
+
+ for (ChannelCount channelIndex = 0; channelIndex < sampleBuffer->numChannels; channelIndex++) {
+ for (SampleCount sampleIndex = 0; sampleIndex < sampleBuffer->blocksize; sampleIndex++) {
+ if (sampleBuffer->samples[channelIndex][sampleIndex] > data->lastSample[channelIndex]) {
+ difference = sampleBuffer->samples[channelIndex][sampleIndex] - data->lastSample[channelIndex];
+ } else {
+ difference = data->lastSample[channelIndex] - sampleBuffer->samples[channelIndex][sampleIndex];
+ }
+
+ if (difference >= kAnalysisDistortionTolerance) {
+ // In this test, we don't care about the consecutive sample count. That is because
+ // we also want to detect harsh clicks which occur by a jump in the amplitude, which
+ // is a common error in many plugins.
+ data->failedChannel = channelIndex;
+ data->failedSample = sampleIndex;
+ return false;
+ }
+
+ data->lastSample[channelIndex] = sampleBuffer->samples[channelIndex][sampleIndex];
+ }
+ }
+
+ return true;
+}
diff --git a/test/analysis/AnalysisDistortion.h b/test/analysis/AnalysisDistortion.h
new file mode 100644
index 0000000..a517777
--- /dev/null
+++ b/test/analysis/AnalysisDistortion.h
@@ -0,0 +1,3 @@
+#include "AnalyzeFile.h"
+
+boolByte analysisDistortion(const SampleBuffer sampleBuffer, AnalysisFunctionData data);
diff --git a/test/analysis/AnalysisDistortionTest.c b/test/analysis/AnalysisDistortionTest.c
new file mode 100644
index 0000000..cb9441c
--- /dev/null
+++ b/test/analysis/AnalysisDistortionTest.c
@@ -0,0 +1,37 @@
+#include "AnalysisDistortion.h"
+#include "unit/TestRunner.h"
+
+static int _testAnalysisDistortion(void)
+{
+ SampleBuffer s = newSampleBuffer(1, 8);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ unsigned int i;
+
+ for (i = 0; i < s->blocksize; i++) {
+ s->samples[0][i] = 0.9f * (i % 2 ? 1.0f : -1.0f);
+ }
+
+ assertFalse(analysisDistortion(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testAnalysisNotDistortion(void)
+{
+ SampleBuffer s = newSampleBuffer(1, 8);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ assert(analysisDistortion(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+TestSuite addAnalysisDistortionTests(void);
+TestSuite addAnalysisDistortionTests(void)
+{
+ TestSuite testSuite = newTestSuite("AnalysisDistortion", NULL, NULL);
+ addTest(testSuite, "AnalysisDistortion", _testAnalysisDistortion);
+ addTest(testSuite, "AnalysisNotDistortion", _testAnalysisNotDistortion);
+ return testSuite;
+}
diff --git a/test/analysis/AnalysisSilence.c b/test/analysis/AnalysisSilence.c
new file mode 100644
index 0000000..0876245
--- /dev/null
+++ b/test/analysis/AnalysisSilence.c
@@ -0,0 +1,25 @@
+#include "AnalysisSilence.h"
+#include "AnalyzeFile.h"
+
+boolByte analysisSilence(const SampleBuffer sampleBuffer, AnalysisFunctionData data)
+{
+ for (ChannelCount i = 0; i < sampleBuffer->numChannels; ++i) {
+ for (SampleCount j = 0; j < sampleBuffer->blocksize; ++j) {
+ if (sampleBuffer->samples[i][j] == 0.0f) {
+ data->consecutiveFailCounter++;
+
+ if (data->consecutiveFailCounter > data->failTolerance) {
+ data->failedChannel = i;
+ data->failedSample = j;
+ return false;
+ }
+ } else {
+ if (data->consecutiveFailCounter > 0) {
+ data->consecutiveFailCounter = 0;
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/test/analysis/AnalysisSilence.h b/test/analysis/AnalysisSilence.h
new file mode 100644
index 0000000..4e56c6c
--- /dev/null
+++ b/test/analysis/AnalysisSilence.h
@@ -0,0 +1,3 @@
+#include "AnalyzeFile.h"
+
+boolByte analysisSilence(const SampleBuffer sampleBuffer, AnalysisFunctionData data);
diff --git a/test/analysis/AnalysisSilenceTest.c b/test/analysis/AnalysisSilenceTest.c
new file mode 100644
index 0000000..968bc37
--- /dev/null
+++ b/test/analysis/AnalysisSilenceTest.c
@@ -0,0 +1,57 @@
+#include "AnalysisSilence.h"
+#include "unit/TestRunner.h"
+
+static int _testAnalysisSilence(void)
+{
+ SampleBuffer s = newSampleBuffer(1, 64);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ assertFalse(analysisSilence(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testAnalysisNotSilence(void)
+{
+ SampleBuffer s = newSampleBuffer(2, 64);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ unsigned long i;
+ unsigned int j;
+
+ for (i = 0; i < s->blocksize; i++) {
+ for (j = 0; j < s->numChannels; j++) {
+ s->samples[j][i] = 32767.0;
+ }
+ }
+
+ assert(analysisSilence(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testAnalysisNotSilenceInOneChannel(void)
+{
+ SampleBuffer s = newSampleBuffer(1, 64);
+ AnalysisFunctionData d = newAnalysisFunctionData();
+ unsigned long i;
+
+ for (i = 0; i < s->blocksize; i++) {
+ s->samples[0][i] = 32767.0;
+ }
+
+ assert(analysisSilence(s, d));
+ freeAnalysisFunctionData(d);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+TestSuite addAnalysisSilenceTests(void);
+TestSuite addAnalysisSilenceTests(void)
+{
+ TestSuite testSuite = newTestSuite("AnalysisSilence", NULL, NULL);
+ addTest(testSuite, "AnalysisSilence", _testAnalysisSilence);
+ addTest(testSuite, "AnalysisNotSilence", _testAnalysisNotSilence);
+ addTest(testSuite, "AnalysisNotSilenceInOneChannel", _testAnalysisNotSilenceInOneChannel);
+ return testSuite;
+}
diff --git a/test/analysis/AnalyzeFile.c b/test/analysis/AnalyzeFile.c
new file mode 100644
index 0000000..11e020f
--- /dev/null
+++ b/test/analysis/AnalyzeFile.c
@@ -0,0 +1,123 @@
+#include <stdlib.h>
+#include "audio/AudioSettings.h"
+#include "io/SampleSource.h"
+#include "AnalysisClipping.h"
+#include "AnalysisDistortion.h"
+#include "AnalysisSilence.h"
+
+// Number of consecutive samples which need to fail in order for the test to fail
+static const int kAnalysisDefaultFailTolerance = 16;
+// Use a blocksize of the default * 2 in order to avoid false positives of the
+// silence detection algorithm, since the last block is likely to be silent.
+static const int kAnalysisBlocksize = DEFAULT_BLOCKSIZE * 2;
+
+static LinkedList _getAnalysisFunctions(void)
+{
+ AnalysisFunctionData data;
+ LinkedList functionsList = newLinkedList();
+
+ data = newAnalysisFunctionData();
+ data->analysisName = "clipping";
+ data->functionPtr = (void *)analysisClipping;
+ linkedListAppend(functionsList, data);
+
+ data = newAnalysisFunctionData();
+ data->analysisName = "distortion";
+ data->functionPtr = (void *)analysisDistortion;
+ linkedListAppend(functionsList, data);
+
+ data = newAnalysisFunctionData();
+ data->analysisName = "silence";
+ data->functionPtr = (void *)analysisSilence;
+ data->failTolerance = kAnalysisBlocksize;
+ linkedListAppend(functionsList, data);
+
+ return functionsList;
+}
+
+static void _runAnalysisFunction(void *item, void *userData)
+{
+ AnalysisFunctionData functionData = (AnalysisFunctionData)item;
+ AnalysisFuncPtr analysisFuncPtr = (AnalysisFuncPtr)(functionData->functionPtr);
+ AnalysisData analysisData = (AnalysisData)userData;
+
+ if (!analysisFuncPtr(analysisData->sampleBuffer, functionData)) {
+ charStringCopyCString(analysisData->failedAnalysisFunctionName, functionData->analysisName);
+ *(analysisData->failedAnalysisFrame) = *(analysisData->currentFrame) + functionData->failedSample;
+ *(analysisData->failedAnalysisChannel) = functionData->failedChannel;
+ *(analysisData->result) = false;
+ }
+}
+
+boolByte analyzeFile(const char *filename, CharString failedAnalysisFunctionName,
+ ChannelCount *failedAnalysisChannel, SampleCount *failedAnalysisFrame)
+{
+ boolByte result;
+ CharString analysisFilename;
+ SampleSource sampleSource;
+ LinkedList analysisFunctions;
+ AnalysisData analysisData = (AnalysisData)malloc(sizeof(AnalysisDataMembers));
+ SampleCount currentFrame = 0;
+
+ // Needed to initialize new sample sources
+ initAudioSettings();
+ analysisFunctions = _getAnalysisFunctions();
+ analysisFilename = newCharStringWithCString(filename);
+ sampleSource = sampleSourceFactory(analysisFilename);
+
+ if (sampleSource == NULL) {
+ freeCharString(analysisFilename);
+ free(analysisData);
+ freeAudioSettings();
+ return false;
+ }
+
+ result = sampleSource->openSampleSource(sampleSource, SAMPLE_SOURCE_OPEN_READ);
+
+ if (!result) {
+ free(analysisData);
+ return result;
+ }
+
+ analysisData->failedAnalysisFunctionName = failedAnalysisFunctionName;
+ analysisData->failedAnalysisChannel = failedAnalysisChannel;
+ analysisData->failedAnalysisFrame = failedAnalysisFrame;
+ analysisData->sampleBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, kAnalysisBlocksize);
+ analysisData->currentFrame = &currentFrame;
+ analysisData->result = &result;
+
+ while (sampleSource->readSampleBlock(sampleSource, analysisData->sampleBuffer) && result) {
+ linkedListForeach(analysisFunctions, _runAnalysisFunction, analysisData);
+ currentFrame += kAnalysisBlocksize;
+ }
+
+ freeSampleSource(sampleSource);
+ freeCharString(analysisFilename);
+ freeAudioSettings();
+ freeSampleBuffer(analysisData->sampleBuffer);
+ freeLinkedListAndItems(analysisFunctions, (LinkedListFreeItemFunc)freeAnalysisFunctionData);
+ free(analysisData);
+ return result;
+}
+
+void freeAnalysisFunctionData(AnalysisFunctionData self)
+{
+ free(self->lastSample);
+ free(self);
+}
+
+AnalysisFunctionData newAnalysisFunctionData(void)
+{
+ AnalysisFunctionData result = (AnalysisFunctionData)malloc(sizeof(AnalysisFunctionDataMembers));
+ result->analysisName = NULL;
+ result->consecutiveFailCounter = 0;
+ result->failedSample = 0;
+ result->functionPtr = NULL;
+ // TODO: Should use max channels, when we get that
+ result->lastSample = (Sample*)malloc(sizeof(Sample) * 2);
+ result->lastSample[0] = 0.0f;
+ result->lastSample[1] = 0.0f;
+ result->failTolerance = kAnalysisDefaultFailTolerance;
+ return result;
+}
+
diff --git a/test/analysis/AnalyzeFile.h b/test/analysis/AnalyzeFile.h
new file mode 100644
index 0000000..d47b3ba
--- /dev/null
+++ b/test/analysis/AnalyzeFile.h
@@ -0,0 +1,37 @@
+#include <stdio.h>
+#include "base/Types.h"
+#include "audio/SampleBuffer.h"
+#include "base/CharString.h"
+
+#ifndef MrsWatson_AnalyzeFile_h
+#define MrsWatson_AnalyzeFile_h
+
+typedef struct {
+ const char *analysisName;
+ void *functionPtr;
+ int consecutiveFailCounter;
+ Sample *lastSample;
+ SampleCount failedSample;
+ ChannelCount failedChannel;
+ int failTolerance;
+} AnalysisFunctionDataMembers;
+typedef AnalysisFunctionDataMembers *AnalysisFunctionData;
+typedef boolByte (*AnalysisFuncPtr)(const SampleBuffer sampleBuffer, AnalysisFunctionData data);
+
+typedef struct {
+ CharString failedAnalysisFunctionName;
+ SampleCount *failedAnalysisFrame;
+ ChannelCount *failedAnalysisChannel;
+ SampleCount *currentFrame;
+ boolByte *result;
+ SampleBuffer sampleBuffer;
+ AnalysisFunctionData functionData;
+} AnalysisDataMembers;
+typedef AnalysisDataMembers *AnalysisData;
+
+AnalysisFunctionData newAnalysisFunctionData(void);
+boolByte analyzeFile(const char *filename, CharString failedAnalysisFunctionName,
+ ChannelCount *failedAnalysisChannel, SampleCount *failedAnalysisFrame);
+void freeAnalysisFunctionData(AnalysisFunctionData self);
+
+#endif
diff --git a/test/app/ProgramOptionTest.c b/test/app/ProgramOptionTest.c
new file mode 100644
index 0000000..1361349
--- /dev/null
+++ b/test/app/ProgramOptionTest.c
@@ -0,0 +1,505 @@
+#include "unit/TestRunner.h"
+#include "app/ProgramOption.h"
+#include "base/File.h"
+
+#if UNIX
+#define TEST_CONFIG_FILE "/tmp/mrswatsontest-config.txt"
+#elif WINDOWS
+#define TEST_CONFIG_FILE "C:\\Temp\\mrswatsontest-config.txt"
+#else
+#define TEST_CONFIG_FILE "mrswatsontest-config.txt"
+#endif
+
+static void _programOptionTeardown(void)
+{
+ CharString configFilePath = newCharStringWithCString(TEST_CONFIG_FILE);
+ File configFile = newFileWithPath(configFilePath);
+
+ if (fileExists(configFile)) {
+ fileRemove(configFile);
+ }
+
+ freeCharString(configFilePath);
+ freeFile(configFile);
+}
+
+static ProgramOption _getTestOption(void)
+{
+ return newProgramOptionWithName(0, "test", "test help", true,
+ kProgramOptionTypeNumber, kProgramOptionArgumentTypeOptional);
+}
+
+static int _testNewProgramOptions(void)
+{
+ ProgramOptions p = newProgramOptions(4);
+ assertIntEquals(4, p->numOptions);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testAddNewProgramOption(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ ProgramOption o;
+ assert(programOptionsAdd(p, _getTestOption()));
+ assertIntEquals(1, p->numOptions);
+ o = p->options[0];
+ assertNotNull(o);
+ assertCharStringEquals("test", o->name);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testAddNullProgramOption(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ assertFalse(programOptionsAdd(p, NULL));
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testAddNewProgramOptionOutsideRange(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ ProgramOption o = _getTestOption();
+ o->index++;
+ assertFalse(programOptionsAdd(p, o));
+ assertIntEquals(1, p->numOptions);
+ freeProgramOption(o);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testParseCommandLineShortOption(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ char *argv[2];
+ argv[0] = "exe";
+ argv[1] = "-t";
+ assert(programOptionsAdd(p, _getTestOption()));
+ assertFalse(p->options[0]->enabled);
+ assert(programOptionsParseArgs(p, 2, argv));
+ assert(p->options[0]->enabled);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testParseCommandLineLongOption(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ char *argv[2];
+ argv[0] = "exe";
+ argv[1] = "--test";
+ assert(programOptionsAdd(p, _getTestOption()));
+ assertFalse(p->options[0]->enabled);
+ assert(programOptionsParseArgs(p, 2, argv));
+ assert(p->options[0]->enabled);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testParseCommandLineInvalidOption(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ char *argv[2];
+ argv[0] = "exe";
+ argv[1] = "invalid";
+ assert(programOptionsAdd(p, _getTestOption()));
+ assertFalse(p->options[0]->enabled);
+ assertFalse(programOptionsParseArgs(p, 2, argv));
+ assertFalse(p->options[0]->enabled);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testParseCommandLineRequiredOption(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ ProgramOption o = _getTestOption();
+ char *argv[3];
+ argv[0] = "exe";
+ argv[1] = "--test";
+ argv[2] = "1.23";
+ o->argumentType = kProgramOptionArgumentTypeRequired;
+ assert(programOptionsAdd(p, o));
+
+ assertFalse(p->options[0]->enabled);
+ assertDoubleEquals(0.0, programOptionsGetNumber(p, 0), 0.0f);
+ assert(programOptionsParseArgs(p, 3, argv));
+ assertDoubleEquals(1.23, programOptionsGetNumber(p, 0), TEST_DEFAULT_TOLERANCE);
+ assert(p->options[0]->enabled);
+
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testParseCommandLineRequiredOptionMissing(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ ProgramOption o = _getTestOption();
+ char *argv[2];
+ argv[0] = "exe";
+ argv[1] = "--test";
+ o->argumentType = kProgramOptionArgumentTypeRequired;
+ assert(programOptionsAdd(p, o));
+
+ assertFalse(p->options[0]->enabled);
+ assertDoubleEquals(0.0, programOptionsGetNumber(p, 0), TEST_EXACT_TOLERANCE);
+ assertFalse(programOptionsParseArgs(p, 2, argv));
+ assertFalse(p->options[0]->enabled);
+
+ freeProgramOptions(p);
+ return 0;
+}
+
+static ProgramOptions _getTestProgramOptionsForConfigFile(void)
+{
+ ProgramOptions p = newProgramOptions(2);
+ ProgramOption o1, o2;
+ o1 = newProgramOptionWithName(0, "test", "test help", true, kProgramOptionTypeString, kProgramOptionArgumentTypeNone);
+ programOptionsAdd(p, o1);
+ o2 = newProgramOptionWithName(1, "other", "test help", true, kProgramOptionTypeString, kProgramOptionArgumentTypeRequired);
+ programOptionsAdd(p, o2);
+ return p;
+}
+
+static FILE *_openTestProgramConfigFile(void)
+{
+ FILE *fp = fopen(TEST_CONFIG_FILE, "w");
+ return fp;
+}
+
+static int _testParseConfigFile(void)
+{
+ ProgramOptions p = _getTestProgramOptionsForConfigFile();
+ CharString filename = newCharStringWithCString(TEST_CONFIG_FILE);
+ FILE *fp = _openTestProgramConfigFile();
+ fprintf(fp, "--test\n-o\nfoo\n");
+ fclose(fp);
+ assert(programOptionsParseConfigFile(p, filename));
+ assert(p->options[0]->enabled);
+ assert(p->options[1]->enabled);
+ assertCharStringEquals("foo", programOptionsGetString(p, 1));
+
+ unlink(TEST_CONFIG_FILE);
+ freeProgramOptions(p);
+ freeCharString(filename);
+ return 0;
+}
+
+static int _testParseInvalidConfigFile(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ CharString filename = newCharStringWithCString("invalid");
+ assertFalse(programOptionsParseConfigFile(p, filename));
+ freeProgramOptions(p);
+ freeCharString(filename);
+ return 0;
+}
+
+static int _testParseNullConfigFile(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ assertFalse(programOptionsParseConfigFile(p, NULL));
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testParseConfigFileWithInvalidOptions(void)
+{
+ ProgramOptions p = _getTestProgramOptionsForConfigFile();
+ CharString filename = newCharStringWithCString(TEST_CONFIG_FILE);
+ FILE *fp = _openTestProgramConfigFile();
+
+ fprintf(fp, "--test\n-s\n");
+ fclose(fp);
+ assertFalse(programOptionsParseConfigFile(p, filename));
+ assert(p->options[0]->enabled);
+ assertFalse(p->options[1]->enabled);
+
+ unlink(TEST_CONFIG_FILE);
+ freeProgramOptions(p);
+ freeCharString(filename);
+ return 0;
+}
+
+static int _testFindProgramOptionFromString(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ CharString c = newCharStringWithCString("test");
+ ProgramOption o;
+
+ assert(programOptionsAdd(p, _getTestOption()));
+ assertIntEquals(p->numOptions, 1);
+ o = programOptionsFind(p, c);
+ assertNotNull(o);
+ assertCharStringEquals("test", o->name);
+
+ freeProgramOptions(p);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testFindProgramOptionFromStringInvalid(void)
+{
+ ProgramOptions p = newProgramOptions(1);
+ CharString c = newCharStringWithCString("invalid");
+ ProgramOption o;
+
+ assert(programOptionsAdd(p, _getTestOption()));
+ assertIntEquals(1, p->numOptions);
+ o = programOptionsFind(p, c);
+ assertIsNull(o);
+
+ freeProgramOptions(p);
+ freeCharString(c);
+ return 0;
+}
+
+static ProgramOptions _getTestOptionMultipleTypes(void)
+{
+ ProgramOptions p = newProgramOptions(3);
+ programOptionsAdd(p, newProgramOptionWithName(0, "string", "help", true,
+ kProgramOptionTypeString, kProgramOptionArgumentTypeRequired));
+ programOptionsAdd(p, newProgramOptionWithName(1, "numeric", "help", true,
+ kProgramOptionTypeNumber, kProgramOptionArgumentTypeRequired));
+ programOptionsAdd(p, newProgramOptionWithName(2, "list", "help", true,
+ kProgramOptionTypeList, kProgramOptionArgumentTypeRequired));
+ return p;
+}
+
+static int _testGetString(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s = programOptionsGetString(p, 0);
+ assertCharStringEquals("", s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetStringForWrongType(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s = programOptionsGetString(p, 1);
+ assertIsNull(s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetStringForInvalidOption(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s = programOptionsGetString(p, 4);
+ assertIsNull(s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetNumeric(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ float f = programOptionsGetNumber(p, 1);
+ assertDoubleEquals(0.0, f, TEST_DEFAULT_TOLERANCE);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetNumericForWrongType(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ float f = programOptionsGetNumber(p, 0);
+ assertDoubleEquals(-1.0, f, TEST_DEFAULT_TOLERANCE);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetNumericForInvalidOption(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ float f = programOptionsGetNumber(p, 4);
+ assertDoubleEquals(-1.0, f, TEST_DEFAULT_TOLERANCE);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetList(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ LinkedList l = programOptionsGetList(p, 2);
+ assertIntEquals(0, l->_numItems);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetListForWrongType(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ LinkedList l = programOptionsGetList(p, 0);
+ assertIsNull(l);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testGetListForInvalidOption(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ LinkedList l = programOptionsGetList(p, 4);
+ assertIsNull(l);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetString(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s;
+ programOptionsSetCString(p, 0, "test");
+ s = programOptionsGetString(p, 0);
+ assertNotNull(s);
+ assertCharStringEquals("test", s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetStringForWrongType(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ programOptionsSetCString(p, 1, "test");
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetStringForInvalidOption(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s;
+ programOptionsSetCString(p, 4, "test");
+ s = programOptionsGetString(p, 0);
+ assertNotNull(s);
+ assertCharStringEquals("", s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetStringWithNull(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s;
+ programOptionsSetCString(p, 0, NULL);
+ s = programOptionsGetString(p, 0);
+ assertNotNull(s);
+ assertCharStringEquals("", s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetNumeric(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ float f;
+ programOptionsSetNumber(p, 1, 1.23f);
+ f = programOptionsGetNumber(p, 1);
+ assertDoubleEquals(1.23, f, TEST_DEFAULT_TOLERANCE);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetNumericForWrongType(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ programOptionsSetNumber(p, 0, 1.23f);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetNumericForInvalidOption(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ programOptionsSetNumber(p, 4, 1.23f);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetListItem(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s;
+ LinkedList l;
+ CharString r;
+
+ s = newCharStringWithCString("test");
+ programOptionsSetListItem(p, 2, s);
+ l = programOptionsGetList(p, 2);
+ assertIntEquals(1, linkedListLength(l));
+ r = l->item;
+ assertCharStringEquals("test", r);
+ freeCharString(s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetListItemForWrongType(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s = newCharStringWithCString("test");
+ programOptionsSetListItem(p, 1, s);
+ freeCharString(s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+static int _testSetListItemForInvalidOption(void)
+{
+ ProgramOptions p = _getTestOptionMultipleTypes();
+ CharString s = newCharStringWithCString("test");
+ programOptionsSetListItem(p, 4, s);
+ freeCharString(s);
+ freeProgramOptions(p);
+ return 0;
+}
+
+TestSuite addProgramOptionTests(void);
+TestSuite addProgramOptionTests(void)
+{
+ TestSuite testSuite = newTestSuite("ProgramOption", NULL, _programOptionTeardown);
+ addTest(testSuite, "NewObject", _testNewProgramOptions);
+ addTest(testSuite, "AddNewProgramOption", _testAddNewProgramOption);
+ addTest(testSuite, "AddNullProgramOption", _testAddNullProgramOption);
+ addTest(testSuite, "AddNewProgramOptionOutsideRange", _testAddNewProgramOptionOutsideRange);
+
+ addTest(testSuite, "ParseCommandLineShortOption", _testParseCommandLineShortOption);
+ addTest(testSuite, "ParseCommandLineLongOption", _testParseCommandLineLongOption);
+ addTest(testSuite, "ParseCommandLineInvalidOption", _testParseCommandLineInvalidOption);
+ addTest(testSuite, "ParseCommandLineRequiredOption", _testParseCommandLineRequiredOption);
+ addTest(testSuite, "ParseCommandLineRequiredOptionMissing", _testParseCommandLineRequiredOptionMissing);
+
+ addTest(testSuite, "ParseConfigFile", _testParseConfigFile);
+ addTest(testSuite, "ParseInvalidConfigFile", _testParseInvalidConfigFile);
+ addTest(testSuite, "ParseNullConfigFile", _testParseNullConfigFile);
+ addTest(testSuite, "ParseConfigFileWithInvalidOptions", _testParseConfigFileWithInvalidOptions);
+
+ addTest(testSuite, "FindProgramOptionFromString", _testFindProgramOptionFromString);
+ addTest(testSuite, "FindProgramOptionFromStringInvalid", _testFindProgramOptionFromStringInvalid);
+
+ addTest(testSuite, "GetString", _testGetString);
+ addTest(testSuite, "GetStringForWrongType", _testGetStringForWrongType);
+ addTest(testSuite, "GetStringForInvalidOption", _testGetStringForInvalidOption);
+ addTest(testSuite, "GetNumeric", _testGetNumeric);
+ addTest(testSuite, "GetNumericForWrongType", _testGetNumericForWrongType);
+ addTest(testSuite, "GetNumericForInvalidOption", _testGetNumericForInvalidOption);
+ addTest(testSuite, "GetList", _testGetList);
+ addTest(testSuite, "GetListForWrongType", _testGetListForWrongType);
+ addTest(testSuite, "GetListForInvalidOption", _testGetListForInvalidOption);
+ addTest(testSuite, "SetString", _testSetString);
+ addTest(testSuite, "SetStringForWrongType", _testSetStringForWrongType);
+ addTest(testSuite, "SetStringForInvalidOption", _testSetStringForInvalidOption);
+ addTest(testSuite, "SetStringWithNull", _testSetStringWithNull);
+ addTest(testSuite, "SetNumeric", _testSetNumeric);
+ addTest(testSuite, "SetNumericForWrongType", _testSetNumericForWrongType);
+ addTest(testSuite, "SetNumericForInvalidOption", _testSetNumericForInvalidOption);
+ addTest(testSuite, "SetListItem", _testSetListItem);
+ addTest(testSuite, "SetListItemForWrongType", _testSetListItemForWrongType);
+ addTest(testSuite, "SetListItemForInvalidOption", _testSetListItemForInvalidOption);
+
+ return testSuite;
+}
diff --git a/test/audio/AudioSettingsTest.c b/test/audio/AudioSettingsTest.c
new file mode 100644
index 0000000..bbdbe62
--- /dev/null
+++ b/test/audio/AudioSettingsTest.c
@@ -0,0 +1,213 @@
+#include "audio/AudioSettings.h"
+#include "unit/TestRunner.h"
+
+static void _audioSettingsSetup(void)
+{
+ initAudioSettings();
+}
+
+static void _audioSettingsTeardown(void)
+{
+ freeAudioSettings();
+}
+
+static int _testInitAudioSettings(void)
+{
+ assertDoubleEquals(DEFAULT_SAMPLE_RATE, getSampleRate(), TEST_DEFAULT_TOLERANCE);
+ assertIntEquals(DEFAULT_NUM_CHANNELS, getNumChannels());
+ assertUnsignedLongEquals(DEFAULT_BLOCKSIZE, getBlocksize());
+ assertDoubleEquals(DEFAULT_TEMPO, getTempo(), TEST_DEFAULT_TOLERANCE);
+ assertIntEquals(DEFAULT_TIMESIG_BEATS_PER_MEASURE, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(DEFAULT_TIMESIG_NOTE_VALUE, getTimeSignatureNoteValue());
+ return 0;
+}
+
+static int _testSetSampleRate(void)
+{
+ setSampleRate(22050.0);
+ assertDoubleEquals(22050.0, getSampleRate(), TEST_DEFAULT_TOLERANCE);
+ return 0;
+}
+
+static int _testSetInvalidSampleRate(void)
+{
+ setSampleRate(22050.0);
+ assertDoubleEquals(22050.0, getSampleRate(), TEST_DEFAULT_TOLERANCE);
+ assertFalse(setSampleRate(0.0));
+ assertDoubleEquals(22050.0, getSampleRate(), TEST_DEFAULT_TOLERANCE);
+ return 0;
+}
+
+static int _testSetNumChannels(void)
+{
+ setNumChannels(4);
+ assertIntEquals(4, getNumChannels());
+ return 0;
+}
+
+static int _testSetInvalidNumChannels(void)
+{
+ setNumChannels(2);
+ assertIntEquals(2, getNumChannels());
+ assertFalse(setNumChannels(0));
+ assertIntEquals(2, getNumChannels());
+ return 0;
+}
+
+static int _testSetBlocksize(void)
+{
+ setBlocksize(123);
+ assertUnsignedLongEquals(123l, getBlocksize());
+ return 0;
+}
+
+static int _testSetInvalidBlocksize(void)
+{
+ setBlocksize(123);
+ assertUnsignedLongEquals(123l, getBlocksize());
+ assertFalse(setBlocksize(0));
+ assertUnsignedLongEquals(123l, getBlocksize());
+ return 0;
+}
+
+static int _testSetTempo(void)
+{
+ setTempo(123.45f);
+ assertDoubleEquals(123.45, getTempo(), 0.1);
+ return 0;
+}
+
+static int _testSetInvalidTempo(void)
+{
+ setTempo(100.0);
+ assertDoubleEquals(100.0, getTempo(), TEST_DEFAULT_TOLERANCE);
+ assertFalse(setTempo(-666.0));
+ assertDoubleEquals(100.0, getTempo(), TEST_DEFAULT_TOLERANCE);
+ assertFalse(setTempo(0.0));
+ assertDoubleEquals(100.0, getTempo(), TEST_DEFAULT_TOLERANCE);
+ return 0;
+}
+
+static int _testSetTempoWithMidiBytes(void)
+{
+ byte bytes[3];
+ bytes[0] = 0x13;
+ bytes[1] = 0xe7;
+ bytes[2] = 0x1b;
+ setTempoFromMidiBytes(bytes);
+ assertDoubleEquals(46.0, getTempo(), TEST_DEFAULT_TOLERANCE);
+ return 0;
+}
+
+static int _testSetTempoWithMidiBytesNull(void)
+{
+ setTempo(100.0);
+ assertDoubleEquals(100.0, getTempo(), TEST_DEFAULT_TOLERANCE);
+ setTempoFromMidiBytes(NULL);
+ assertDoubleEquals(100.0, getTempo(), TEST_DEFAULT_TOLERANCE);
+ return 0;
+}
+
+static int _testSetTimeSigBeatsPerMeasure(void)
+{
+ assert(setTimeSignatureBeatsPerMeasure(8));
+ assertIntEquals(8, getTimeSignatureBeatsPerMeasure());
+ return 0;
+}
+
+static int _testSetTimeSigNoteValue(void)
+{
+ assert(setTimeSignatureNoteValue(2));
+ assertIntEquals(2, getTimeSignatureNoteValue());
+ return 0;
+}
+
+static int _testSetTimeSignatureWithMidiBytes(void)
+{
+ byte bytes[2];
+ // Corresponds to a time signature of 3/8
+ bytes[0] = 3;
+ bytes[1] = 3;
+ assert(setTimeSignatureFromMidiBytes(bytes));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ return 0;
+}
+
+static int _testSetTimeSignatureWithMidiBytesNull(void)
+{
+ assert(setTimeSignatureBeatsPerMeasure(3));
+ assert(setTimeSignatureNoteValue(8));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ assertFalse(setTimeSignatureFromMidiBytes(NULL));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ return 0;
+}
+
+static int _testSetTimeSignatureFromString(void)
+{
+ CharString s = newCharStringWithCString("2/16");
+ assert(setTimeSignatureBeatsPerMeasure(3));
+ assert(setTimeSignatureNoteValue(8));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ assert(setTimeSignatureFromString(s));
+ assertIntEquals(2, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(16, getTimeSignatureNoteValue());
+ freeCharString(s);
+ return 0;
+}
+
+static int _testSetTimeSignatureFromInvalidString(void)
+{
+ CharString s = newCharStringWithCString("invalid/none");
+ assert(setTimeSignatureBeatsPerMeasure(3));
+ assert(setTimeSignatureNoteValue(8));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ assertFalse(setTimeSignatureFromString(s));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ freeCharString(s);
+ return 0;
+}
+
+static int _testSetTimeSignatureFromNullString(void)
+{
+ assert(setTimeSignatureBeatsPerMeasure(3));
+ assert(setTimeSignatureNoteValue(8));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ assertFalse(setTimeSignatureFromString(NULL));
+ assertIntEquals(3, getTimeSignatureBeatsPerMeasure());
+ assertIntEquals(8, getTimeSignatureNoteValue());
+ return 0;
+}
+
+TestSuite addAudioSettingsTests(void);
+TestSuite addAudioSettingsTests(void)
+{
+ TestSuite testSuite = newTestSuite("AudioSettings", _audioSettingsSetup, _audioSettingsTeardown);
+ addTest(testSuite, "Initialization", _testInitAudioSettings);
+ addTest(testSuite, "SetSampleRate", _testSetSampleRate);
+ addTest(testSuite, "SetInvalidSampleRate", _testSetInvalidSampleRate);
+ addTest(testSuite, "SetNumChannels", _testSetNumChannels);
+ addTest(testSuite, "SetInvalidNumChannels", _testSetInvalidNumChannels);
+ addTest(testSuite, "SetBlocksize", _testSetBlocksize);
+ addTest(testSuite, "SetInvalidBlocksize", _testSetInvalidBlocksize);
+ addTest(testSuite, "SetTempo", _testSetTempo);
+ addTest(testSuite, "SetInvalidTempo", _testSetInvalidTempo);
+ addTest(testSuite, "SetTempoWithMidiBytes", _testSetTempoWithMidiBytes);
+ addTest(testSuite, "SetTempoWithMidiBytesNull", _testSetTempoWithMidiBytesNull);
+
+ addTest(testSuite, "SetTimeSignatureBeatsPerMeasure", _testSetTimeSigBeatsPerMeasure);
+ addTest(testSuite, "SetTimeSignatureNoteValue", _testSetTimeSigNoteValue);
+ addTest(testSuite, "SetTimeSignatureWithMidiBytes", _testSetTimeSignatureWithMidiBytes);
+ addTest(testSuite, "SetTimeSignatureWithMidiBytesNull", _testSetTimeSignatureWithMidiBytesNull);
+ addTest(testSuite, "SetTimeSignatureFromString", _testSetTimeSignatureFromString);
+ addTest(testSuite, "SetTimeSignatureFromInvalidString", _testSetTimeSignatureFromInvalidString);
+ addTest(testSuite, "SetTimeSignatureFromNullString", _testSetTimeSignatureFromNullString);
+ return testSuite;
+}
diff --git a/test/audio/SampleBufferTest.c b/test/audio/SampleBufferTest.c
new file mode 100644
index 0000000..9909f0f
--- /dev/null
+++ b/test/audio/SampleBufferTest.c
@@ -0,0 +1,134 @@
+#include "audio/AudioSettings.h"
+#include "audio/SampleBuffer.h"
+#include "unit/TestRunner.h"
+
+static SampleBuffer _newMockSampleBuffer(void)
+{
+ return newSampleBuffer(1, 1);
+}
+
+static int _testNewSampleBuffer(void)
+{
+ SampleBuffer s = _newMockSampleBuffer();
+ assertIntEquals(1, s->numChannels);
+ assertUnsignedLongEquals(1l, s->blocksize);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testNewSampleBufferMultichannel(void)
+{
+ SampleBuffer s = newSampleBuffer(8, 128);
+ unsigned int i, j;
+ assertNotNull(s);
+ assertIntEquals(8, s->numChannels);
+
+ // Actually write a bunch of samples to expose memory corruption
+ for (i = 0; i < s->blocksize; ++i) {
+ for (j = 0; j < s->numChannels; ++j) {
+ s->samples[j][i] = 0.5f;
+ }
+ }
+
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testClearSampleBuffer(void)
+{
+ SampleBuffer s = _newMockSampleBuffer();
+ s->samples[0][0] = 123;
+ sampleBufferClear(s);
+ assertDoubleEquals(0.0, s->samples[0][0], TEST_DEFAULT_TOLERANCE);
+ freeSampleBuffer(s);
+ return 0;
+}
+
+static int _testCopyAndMapChannelsSampleBuffers(void)
+{
+ SampleBuffer s1 = _newMockSampleBuffer();
+ SampleBuffer s2 = _newMockSampleBuffer();
+ s1->samples[0][0] = 123.0;
+ assert(sampleBufferCopyAndMapChannels(s2, s1));
+ assertDoubleEquals(123.0, s2->samples[0][0], TEST_DEFAULT_TOLERANCE);
+ freeSampleBuffer(s1);
+ freeSampleBuffer(s2);
+ return 0;
+}
+
+static int _testCopyAndMapChannelsSampleBuffersDifferentBlocksizes(void)
+{
+ SampleBuffer s1 = newSampleBuffer(1, DEFAULT_BLOCKSIZE);
+ SampleBuffer s2 = _newMockSampleBuffer();
+
+ s1->samples[0][0] = 123.0;
+ assertFalse(sampleBufferCopyAndMapChannels(s2, s1));
+ // Contents should not change; copying with different sizes is invalid
+ assertDoubleEquals(123.0, s1->samples[0][0], TEST_DEFAULT_TOLERANCE);
+
+ freeSampleBuffer(s1);
+ freeSampleBuffer(s2);
+ return 0;
+}
+
+static int _testCopyAndMapChannelsSampleBuffersDifferentChannelsBigger(void)
+{
+ SampleBuffer s1 = newSampleBuffer(4, 1);
+ SampleBuffer s2 = newSampleBuffer(2, 1);
+
+ s2->samples[0][0] = 1.0;
+ s2->samples[1][0] = 2.0;
+
+ assert(sampleBufferCopyAndMapChannels(s1, s2));
+ assertDoubleEquals(1.0, s1->samples[0][0], TEST_DEFAULT_TOLERANCE);
+ assertDoubleEquals(2.0, s1->samples[1][0], TEST_DEFAULT_TOLERANCE);
+ assertDoubleEquals(1.0, s1->samples[2][0], TEST_DEFAULT_TOLERANCE);
+ assertDoubleEquals(2.0, s1->samples[3][0], TEST_DEFAULT_TOLERANCE);
+
+ freeSampleBuffer(s1);
+ freeSampleBuffer(s2);
+ return 0;
+}
+
+static int _testCopyAndMapChannelsSampleBuffersDifferentChannelsSmaller(void)
+{
+ SampleBuffer s1 = newSampleBuffer(1, 1);
+ SampleBuffer s2 = newSampleBuffer(4, 1);
+ unsigned int i;
+
+ for (i = 0; i < s1->numChannels; i++) {
+ s1->samples[i][0] = 1.0;
+ }
+
+ for (i = 0; i < s2->numChannels; i++) {
+ s2->samples[i][0] = 2.0;
+ }
+
+ assert(sampleBufferCopyAndMapChannels(s1, s2));
+ assertDoubleEquals(2.0, s1->samples[0][0], TEST_DEFAULT_TOLERANCE);
+
+ freeSampleBuffer(s1);
+ freeSampleBuffer(s2);
+ return 0;
+}
+
+static int _testFreeNullSampleBuffer(void)
+{
+ freeSampleBuffer(NULL);
+ return 0;
+}
+
+TestSuite addSampleBufferTests(void);
+TestSuite addSampleBufferTests(void)
+{
+ TestSuite testSuite = newTestSuite("SampleBuffer", NULL, NULL);
+ addTest(testSuite, "NewObject", _testNewSampleBuffer);
+ addTest(testSuite, "NewSampleBufferMultichannel", _testNewSampleBufferMultichannel);
+ addTest(testSuite, "ClearSampleBuffer", _testClearSampleBuffer);
+ addTest(testSuite, "CopyAndMapChannelsSampleBuffers", _testCopyAndMapChannelsSampleBuffers);
+ addTest(testSuite, "CopyAndMapChannelsSampleBuffersDifferentSizes", _testCopyAndMapChannelsSampleBuffersDifferentBlocksizes);
+ addTest(testSuite, "CopyAndMapChannelsSampleBuffersDifferentChannelsBigger", _testCopyAndMapChannelsSampleBuffersDifferentChannelsBigger);
+ addTest(testSuite, "CopyAndMapChannelsSampleBuffersDifferentChannelsSmaller", _testCopyAndMapChannelsSampleBuffersDifferentChannelsSmaller);
+ addTest(testSuite, "FreeNullSampleBuffer", _testFreeNullSampleBuffer);
+ return testSuite;
+}
diff --git a/test/base/CharStringTest.c b/test/base/CharStringTest.c
new file mode 100644
index 0000000..aabf206
--- /dev/null
+++ b/test/base/CharStringTest.c
@@ -0,0 +1,422 @@
+#include "unit/TestRunner.h"
+
+static char *const TEST_STRING = "test string";
+static char *const TEST_STRING_CAPS = "TEST STRING";
+static char *const OTHER_TEST_STRING = "other test string";
+
+static int _testNewCharString(void)
+{
+ CharString c = newCharString();
+ assertSizeEquals(kCharStringLengthDefault, c->capacity);
+ assertCharStringEquals(EMPTY_STRING, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNewCharStringWithCapacity(void)
+{
+ size_t testSize = 123;
+ CharString c = newCharStringWithCapacity(testSize);
+ assertSizeEquals(testSize, c->capacity);
+ assertCharStringEquals(EMPTY_STRING, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNewObjectWithNullCString(void)
+{
+ CharString c = newCharStringWithCString(NULL);
+ assertSizeEquals(kCharStringLengthDefault, c->capacity);
+ assertCharStringEquals(EMPTY_STRING, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNewObjectWithEmptyCString(void)
+{
+ CharString c = newCharStringWithCString(EMPTY_STRING);
+ assertSizeEquals(kCharStringLengthDefault, c->capacity);
+ assertCharStringEquals(EMPTY_STRING, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testClearCharString(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ charStringClear(c);
+ assertSizeEquals(kCharStringLengthDefault, c->capacity);
+ assertCharStringEquals(EMPTY_STRING, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCopyToCharString(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ assertSizeEquals(kCharStringLengthDefault, c->capacity);
+ assertCharStringEquals(TEST_STRING, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCopyCharStrings(void)
+{
+ CharString c, c2;
+ c = newCharString();
+ c2 = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ charStringCopy(c2, c);
+ assertCharStringEquals(c2->data, c);
+ freeCharString(c);
+ freeCharString(c2);
+ return 0;
+}
+
+static int _testIsEmptyStringEmpty(void)
+{
+ CharString c = newCharString();
+ assert(charStringIsEmpty(c));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testIsNullEmptyString(void)
+{
+ assert(charStringIsEmpty(NULL));
+ return 0;
+}
+
+static int _testIsRegularStringNotEmpty(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ assertFalse(charStringIsEmpty(c));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testAppendCharStrings(void)
+{
+ CharString a = newCharStringWithCString("a");
+ CharString b = newCharStringWithCString("b");
+ charStringAppend(a, b);
+ assertCharStringEquals("ab", a);
+ freeCharString(a);
+ freeCharString(b);
+ return 0;
+}
+
+static int _testAppendCharStringsOverCapacity(void)
+{
+ CharString a = newCharStringWithCString("a");
+ CharString b = newCharStringWithCString("1234567890");
+ assertSizeEquals((size_t)2, a->capacity);
+ charStringAppend(a, b);
+ assertCharStringEquals("a1234567890", a);
+ assertSizeEquals((size_t)12, a->capacity);
+ freeCharString(a);
+ freeCharString(b);
+ return 0;
+}
+
+static int _testCharStringEqualsSameString(void)
+{
+ CharString c, c2;
+ c = newCharString();
+ c2 = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ charStringCopyCString(c2, TEST_STRING);
+ assert(charStringIsEqualTo(c, c2, false));
+ freeCharString(c);
+ freeCharString(c2);
+ return 0;
+}
+
+static int _testCharStringDoesEqualDifferentString(void)
+{
+ CharString c, c2;
+ c = newCharString();
+ c2 = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ charStringCopyCString(c2, OTHER_TEST_STRING);
+ assertFalse(charStringIsEqualTo(c, c2, false));
+ freeCharString(c);
+ freeCharString(c2);
+ return 0;
+}
+
+static int _testCharStringEqualsSameStringInsensitive(void)
+{
+ CharString c, c2;
+ c = newCharString();
+ c2 = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ charStringCopyCString(c2, TEST_STRING_CAPS);
+ assert(charStringIsEqualTo(c, c2, true));
+ freeCharString(c);
+ freeCharString(c2);
+ return 0;
+}
+
+static int _testCharStringDoesNotEqualSameStringInsensitive(void)
+{
+ CharString c, c2;
+ c = newCharString();
+ c2 = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ charStringCopyCString(c2, TEST_STRING_CAPS);
+ assertFalse(charStringIsEqualTo(c, c2, false));
+ freeCharString(c);
+ freeCharString(c2);
+ return 0;
+}
+
+static int _testCharStringEqualsNull(void)
+{
+ CharString c = newCharString();
+ assertFalse(charStringIsEqualTo(c, NULL, false));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCharStringEqualsSameCString(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ assert(charStringIsEqualToCString(c, TEST_STRING, false));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCharStringNotEqualToDifferentCString(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ assertFalse(charStringIsEqualToCString(c, OTHER_TEST_STRING, false));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCharStringEqualsSameCStringInsensitive(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ assert(charStringIsEqualToCString(c, TEST_STRING_CAPS, true));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCharStringNotEqualsCStringInsensitive(void)
+{
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_STRING);
+ assertFalse(charStringIsEqualToCString(c, TEST_STRING_CAPS, false));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testCharStringEqualsCStringNull(void)
+{
+ CharString c = newCharString();
+ assertFalse(charStringIsEqualToCString(c, NULL, false));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testIsLetter(void)
+{
+ CharString c = newCharStringWithCString("a");
+ assert(charStringIsLetter(c, 0));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testIsNotLetter(void)
+{
+ CharString c = newCharStringWithCString("0");
+ assertFalse(charStringIsLetter(c, 0));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testIsNumber(void)
+{
+ CharString c = newCharStringWithCString("0");
+ assert(charStringIsNumber(c, 0));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testIsNotNumber(void)
+{
+ CharString c = newCharStringWithCString("a");
+ assertFalse(charStringIsNumber(c, 0));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testSplitString(void)
+{
+ CharString c = newCharStringWithCString("abc,def,ghi,");
+ LinkedList l = charStringSplit(c, ',');
+ CharString *items = NULL;
+
+ assertNotNull(l);
+ assertIntEquals(3, linkedListLength(l));
+ items = (CharString *)linkedListToArray(l);
+ assertNotNull(items);
+ assertCharStringEquals("abc", items[0]);
+ assertCharStringEquals("def", items[1]);
+ assertCharStringEquals("ghi", items[2]);
+
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ freeCharString(c);
+ free(items);
+ return 0;
+}
+
+static int _testSplitStringWithoutDelimiter(void)
+{
+ const char *expected = "abcdefg";
+ CharString c = newCharStringWithCString(expected);
+ CharString c2 = NULL;
+ LinkedList l = charStringSplit(c, ',');
+
+ assertNotNull(l);
+ assertIntEquals(1, linkedListLength(l));
+ c2 = l->item;
+ assertCharStringEquals(expected, c2);
+
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testSplitStringNULLDelimiter(void)
+{
+ CharString c = newCharStringWithCString("abcdefh");
+ LinkedList l = charStringSplit(c, 0);
+ assertIsNull(l);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testSplitStringEmptyString(void)
+{
+ CharString c = newCharString();
+ LinkedList l = charStringSplit(c, ',');
+ assertNotNull(l);
+ assertIntEquals(0, linkedListLength(l));
+ freeCharString(c);
+ freeLinkedList(l);
+ return 0;
+}
+
+// This function is not technically public, but we test against it instead of
+// the public version in order to set a shorter line length. This makes test
+// cases much easier to construct.
+extern boolByte _charStringWrap(const char *srcString, char *destString,
+ size_t destStringSize, int indentSize, int lineLength);
+
+static int _testWrapNullSourceString(void)
+{
+ assertFalse(charStringWrap(NULL, 0));
+ return 0;
+}
+
+static int _testWrapString(void)
+{
+ CharString src = newCharStringWithCString("1234 6789 bcde 01");
+ // Create dest string the same way as in wrapString(), cheap I know...
+ CharString dest = newCharStringWithCapacity(src->capacity * 2);
+ _charStringWrap(src->data, dest->data, dest->capacity, 0, 0x10);
+ assertCharStringEquals("1234 6789 bcde\n01", dest);
+ freeCharString(src);
+ freeCharString(dest);
+ return 0;
+}
+
+static int _testWrapStringWithIndent(void)
+{
+ CharString src = newCharStringWithCString("1234 6789 bcde 01");
+ // Create dest string the same way as in wrapString(), cheap I know...
+ CharString dest = newCharStringWithCapacity(src->capacity * 2);
+ _charStringWrap(src->data, dest->data, dest->capacity, 1, 0xe);
+ assertCharStringEquals(" 1234 6789\n bcde 01", dest);
+ freeCharString(src);
+ freeCharString(dest);
+ return 0;
+}
+
+static int _testWrapStringLongerThanLine(void)
+{
+ CharString src = newCharStringWithCString("123456789abcdef12");
+ // Create dest string the same way as in wrapString(), cheap I know...
+ CharString dest = newCharStringWithCapacity(src->capacity * 2);
+ _charStringWrap(src->data, dest->data, dest->capacity, 0, 0xf);
+ assertCharStringEquals("123456789abcde-\nf12", dest);
+ freeCharString(src);
+ freeCharString(dest);
+ return 0;
+}
+
+static int _testFreeNullCharString(void)
+{
+ freeCharString(NULL);
+ return 0;
+}
+
+TestSuite addCharStringTests(void);
+TestSuite addCharStringTests(void)
+{
+ TestSuite testSuite = newTestSuite("CharString", NULL, NULL);
+
+ addTest(testSuite, "NewObject", _testNewCharString);
+ addTest(testSuite, "NewObjectWithCapacity", _testNewCharStringWithCapacity);
+ addTest(testSuite, "NewObjectWithNullCString", _testNewObjectWithNullCString);
+ addTest(testSuite, "NewObjectWithEmptyCString", _testNewObjectWithEmptyCString);
+
+ addTest(testSuite, "ClearString", _testClearCharString);
+ addTest(testSuite, "CopyToCharString", _testCopyToCharString);
+ addTest(testSuite, "CopyCharStrings", _testCopyCharStrings);
+ addTest(testSuite, "EmptyStringIsEmpty", _testIsEmptyStringEmpty);
+ addTest(testSuite, "NullIsEmpty", _testIsNullEmptyString);
+ addTest(testSuite, "RegularStringIsNotEmpty", _testIsRegularStringNotEmpty);
+
+ addTest(testSuite, "AppendCharStrings", _testAppendCharStrings);
+ addTest(testSuite, "AppendCharStringsOverCapacity", _testAppendCharStringsOverCapacity);
+
+ addTest(testSuite, "EqualsSameString", _testCharStringEqualsSameString);
+ addTest(testSuite, "DoesNotEqualDifferentString", _testCharStringDoesEqualDifferentString);
+ addTest(testSuite, "EqualsSameStringWithCaseInsensitive", _testCharStringEqualsSameStringInsensitive);
+ addTest(testSuite, "DoesNotEqualSameStringWithDifferentCase", _testCharStringDoesNotEqualSameStringInsensitive);
+ addTest(testSuite, "EqualsNull", _testCharStringEqualsNull);
+
+ addTest(testSuite, "EqualsSameCString", _testCharStringEqualsSameCString);
+ addTest(testSuite, "DoesNotEqualDifferentCString", _testCharStringNotEqualToDifferentCString);
+ addTest(testSuite, "EqualsSameCStringWithCaseInsensitive", _testCharStringEqualsSameCStringInsensitive);
+ addTest(testSuite, "DoesNotEqualSameCStringWithDifferentCase", _testCharStringNotEqualsCStringInsensitive);
+ addTest(testSuite, "EqualsCStringNull", _testCharStringEqualsCStringNull);
+
+ addTest(testSuite, "IsLetter", _testIsLetter);
+ addTest(testSuite, "IsNotLetter", _testIsNotLetter);
+ addTest(testSuite, "IsNumber", _testIsNumber);
+ addTest(testSuite, "IsNotNumber", _testIsNotNumber);
+
+ addTest(testSuite, "SplitString", _testSplitString);
+ addTest(testSuite, "SplitStringWithoutDelimiter", _testSplitStringWithoutDelimiter);
+ addTest(testSuite, "SplitStringNULLDelimiter", _testSplitStringNULLDelimiter);
+ addTest(testSuite, "SplitStringEmptyString", _testSplitStringEmptyString);
+
+ addTest(testSuite, "WrapNullSourceString", _testWrapNullSourceString);
+ addTest(testSuite, "WrapString", _testWrapString);
+ addTest(testSuite, "WrapStringWithIndent", _testWrapStringWithIndent);
+ addTest(testSuite, "WrapStringLongerThanLine", _testWrapStringLongerThanLine);
+
+ addTest(testSuite, "FreeNullCharString", _testFreeNullCharString);
+
+ return testSuite;
+}
diff --git a/test/base/EndianTest.c b/test/base/EndianTest.c
new file mode 100644
index 0000000..5b64fb2
--- /dev/null
+++ b/test/base/EndianTest.c
@@ -0,0 +1,155 @@
+#include "unit/TestRunner.h"
+#include "base/Endian.h"
+
+#include "unit/TestRunner.h"
+
+#if LINUX
+#if defined(__BYTE_ORDER__)
+#define HOST_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define HOST_LITTLE_ENDIAN (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#endif
+#elif MACOSX
+#include <machine/endian.h>
+#define HOST_BIG_ENDIAN (__DARWIN_BYTE_ORDER == __DARWIN_BIG_ENDIAN)
+#define HOST_LITTLE_ENDIAN (__DARWIN_BYTE_ORDER == __DARWIN_LITTLE_ENDIAN)
+#elif WINDOWS
+// Windows is always little-endian, see: http://support.microsoft.com/kb/102025
+#define HOST_BIG_ENDIAN 0
+#define HOST_LITTLE_ENDIAN 1
+#endif
+
+#if !defined(HOST_BIG_ENDIAN) || !defined(HOST_LITTLE_ENDIAN)
+#error Host platform endian-ness not known
+#error Please define either HOST_BIG_ENDIAN or HOST_LITTLE_ENDIAN
+#elif HOST_BIG_ENDIAN && HOST_LITTLE_ENDIAN
+#error Both HOST_BIG_ENDIAN and HOST_LITTLE_ENDIAN cannot be defined to 1!
+#endif
+
+static int _testFlipShortEndian(void)
+{
+ unsigned short s = 0xabcd;
+ unsigned short r = flipShortEndian(s);
+ assertUnsignedLongEquals(0xcdabul, r);
+ assertIntEquals(s, flipShortEndian(r));
+ return 0;
+}
+
+static int _testConvertBigEndianShortToPlatform(void)
+{
+ unsigned short s = 0xabcd;
+ unsigned short r = convertBigEndianShortToPlatform(s);
+#if HOST_BIG_ENDIAN
+ assertUnsignedLongEquals(s, (unsigned long)r);
+#elif HOST_LITTLE_ENDIAN
+ assertUnsignedLongEquals((unsigned long)s, (unsigned long)flipShortEndian(r));
+#endif
+ return 0;
+}
+
+static int _testConvertBigEndianIntToPlatform(void)
+{
+ unsigned int i = 0xdeadbeef;
+ unsigned int r = convertBigEndianIntToPlatform(i);
+#if HOST_BIG_ENDIAN
+ assertUnsignedLongEquals((unsigned long)i, r);
+#elif HOST_LITTLE_ENDIAN
+ assertUnsignedLongEquals(0xefbeaddeul, r);
+#endif
+ return 0;
+}
+
+static int _testConvertLittleEndianIntToPlatform(void)
+{
+ unsigned int i = 0xdeadbeef;
+ unsigned int r = convertLittleEndianIntToPlatform(i);
+#if HOST_BIG_ENDIAN
+ assertUnsignedLongEquals(0xefbeaddeul, r);
+#elif HOST_LITTLE_ENDIAN
+ assertUnsignedLongEquals((unsigned long)i, r);
+#endif
+ return 0;
+}
+
+static int _testConvertBigEndianFloatToPlatform(void)
+{
+ // Generate an integer with a known value and convert it to a float using pointer trickery
+ int i = 0xdeadbeef;
+ int i2 = 0xefbeadbe;
+ float *f = (float *)&i;
+ float *f2 = (float *)&i2;
+ float r = convertBigEndianFloatToPlatform(*f);
+ // Unfortunately, the above pointer trickery will result in a really huge number, so the
+ // standard comparison tolerance is *way* too low. This number is the lowest possible
+ // result that we should accept for this test.
+ const double bigFloatTolerance = 3.02231e+24;
+
+#if HOST_BIG_ENDIAN
+ assertDoubleEquals(*f, r);
+#elif HOST_LITTLE_ENDIAN
+ // Sanity check to make sure that the actual result is the really huge number which we
+ // are expecting.
+ assert(fabs(r) > bigFloatTolerance);
+ assertDoubleEquals(*f2, r, bigFloatTolerance);
+#endif
+ return 0;
+}
+
+static int _testConvertByteArrayToUnsignedShort(void)
+{
+ byte *b = (byte *)malloc(sizeof(byte) * 2);
+ int i;
+ unsigned short s;
+
+ for (i = 0; i < 2; i++) {
+ b[i] = (byte)(0xaa + i);
+ }
+
+ s = convertByteArrayToUnsignedShort(b);
+
+#if HOST_BIG_ENDIAN
+ assertUnsignedLongEquals(0xaaabul, s);
+#elif HOST_LITTLE_ENDIAN
+ assertUnsignedLongEquals(0xabaaul, s);
+#endif
+
+ free(b);
+ return 0;
+}
+
+static int _testConvertByteArrayToUnsignedInt(void)
+{
+ byte *b = (byte *)malloc(sizeof(byte) * 4);
+ unsigned int s;
+
+ for (size_t i = 0; i < 4; i++) {
+ b[i] = (byte)(0xaa + i);
+ }
+ s = convertByteArrayToUnsignedInt(b);
+
+
+#if HOST_BIG_ENDIAN
+ assertUnsignedLongEquals(0xaaabacadul, s);
+#elif HOST_LITTLE_ENDIAN
+ assertUnsignedLongEquals(0xadacabaaul, s);
+#endif
+
+ free(b);
+ return 0;
+}
+
+TestSuite addEndianTests(void);
+TestSuite addEndianTests(void)
+{
+ TestSuite testSuite = newTestSuite("Endian", NULL, NULL);
+
+ addTest(testSuite, "FlipShortEndian", _testFlipShortEndian);
+ addTest(testSuite, "ConvertBigEndianShortToPlatform", _testConvertBigEndianShortToPlatform);
+ addTest(testSuite, "ConvertBigEndianIntToPlatform", _testConvertBigEndianIntToPlatform);
+ addTest(testSuite, "ConvertLittleEndianIntToPlatform", _testConvertLittleEndianIntToPlatform);
+ addTest(testSuite, "ConvertBigEndianFloatToPlatform", _testConvertBigEndianFloatToPlatform);
+
+ addTest(testSuite, "ConvertByteArrayToUnsignedShort", _testConvertByteArrayToUnsignedShort);
+ addTest(testSuite, "ConvertByteArrayToUnsignedInt", _testConvertByteArrayToUnsignedInt);
+
+ return testSuite;
+}
diff --git a/test/base/FileTest.c b/test/base/FileTest.c
new file mode 100644
index 0000000..45977c1
--- /dev/null
+++ b/test/base/FileTest.c
@@ -0,0 +1,1526 @@
+#include "unit/TestRunner.h"
+#include "base/File.h"
+
+#define TEST_DIRNAME "test_dir"
+#define TEST_DIRNAME_WITH_DOT "test.dir"
+#define TEST_DIRNAME_COPY_DEST "test_dir_dest"
+#define TEST_FILENAME "test_file.txt"
+
+static void _fileTestTeardown(void)
+{
+ CharString testFilePath = newCharStringWithCString(TEST_FILENAME);
+ File testFile = newFileWithPath(testFilePath);
+ CharString testDirPath = newCharStringWithCString(TEST_DIRNAME);
+ File testDir = newFileWithPath(testDirPath);
+ CharString testDirWithDotPath = newCharStringWithCString(TEST_DIRNAME_WITH_DOT);
+ File testDirWithDot = newFileWithPath(testDirWithDotPath);
+ CharString testDirCopyPath = newCharStringWithCString(TEST_DIRNAME_COPY_DEST);
+ File testDirCopy = newFileWithPath(testDirCopyPath);
+
+ if (fileExists(testFile)) {
+ fileRemove(testFile);
+ }
+
+ if (fileExists(testDir)) {
+ fileRemove(testDir);
+ }
+
+ if (fileExists(testDirWithDot)) {
+ fileRemove(testDirWithDot);
+ }
+
+ if (fileExists(testDirCopy)) {
+ fileRemove(testDirCopy);
+ }
+
+ freeFile(testFile);
+ freeFile(testDir);
+ freeFile(testDirWithDot);
+ freeFile(testDirCopy);
+ freeCharString(testFilePath);
+ freeCharString(testDirPath);
+ freeCharString(testDirWithDotPath);
+ freeCharString(testDirCopyPath);
+}
+
+static int _testNewFileDefault(void)
+{
+ File f = newFile();
+
+ assertNotNull(f);
+ assertCharStringEquals(EMPTY_STRING, f->absolutePath);
+ assertIntEquals(kFileTypeInvalid, f->fileType);
+ assertIntEquals(kFileOpenModeClosed, f->_openMode);
+ assertIsNull(f->_fileHandle);
+ assertFalse(fileExists(f));
+
+ freeFile(f);
+ return 0;
+}
+
+static int _testNewFileAlreadyExists(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ File ftest = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+
+ ftest = newFileWithPath(p);
+ assertIntEquals(kFileTypeFile, ftest->fileType);
+ assertIntEquals(kFileOpenModeClosed, ftest->_openMode);
+ assertIsNull(ftest->_fileHandle);
+
+ freeCharString(p);
+ freeFile(f);
+ freeFile(ftest);
+ return 0;
+}
+
+static int _testNewFileWithRelativePath(void)
+{
+ CharString p = newCharString();
+ CharString pAbs = newCharString();
+ CharString pwd = fileGetCurrentDirectory();
+ File f = NULL;
+
+ sprintf(p->data, "%s%c%s", TEST_DIRNAME, PATH_DELIMITER, TEST_FILENAME);
+ f = newFileWithPath(p);
+ assertNotNull(f);
+ sprintf(pAbs->data, "%s%c%s", pwd->data, PATH_DELIMITER, p->data);
+ assertCharStringEquals(pAbs->data, f->absolutePath);
+ assertFalse(fileExists(f));
+ assertIntEquals(kFileTypeInvalid, f->fileType);
+ assertIntEquals(kFileOpenModeClosed, f->_openMode);
+ assertIsNull(f->_fileHandle);
+
+ freeFile(f);
+ freeCharString(p);
+ freeCharString(pAbs);
+ freeCharString(pwd);
+ return 0;
+}
+
+static int _testNewFileWithAbsolutePath(void)
+{
+ CharString p = newCharString();
+ CharString pAbs = newCharString();
+ File f = NULL;
+
+ sprintf(p->data, "%s%c%s%c%s", ROOT_DIRECTORY, PATH_DELIMITER, TEST_DIRNAME, PATH_DELIMITER, TEST_FILENAME);
+ f = newFileWithPath(p);
+ assertNotNull(f);
+ // Mostly just testing to make sure that absolute paths are not incorrectly
+ // translated from relative ones
+ sprintf(pAbs->data, "%s%c%s%c%s", ROOT_DIRECTORY, PATH_DELIMITER, TEST_DIRNAME, PATH_DELIMITER, TEST_FILENAME);
+ assert(charStringIsEqualTo(pAbs, f->absolutePath, false));
+ assertFalse(fileExists(f));
+ assertIntEquals(kFileTypeInvalid, f->fileType);
+ assertIntEquals(kFileOpenModeClosed, f->_openMode);
+ assertIsNull(f->_fileHandle);
+
+ freeFile(f);
+ freeCharString(p);
+ freeCharString(pAbs);
+ return 0;
+}
+
+static int _testNewFileWithNetworkPath(void)
+{
+#if WINDOWS
+ CharString p = newCharString();
+ CharString pAbs = newCharString();
+ CharString pwd = fileGetCurrentDirectory();
+ File f;
+
+ sprintf(p->data, "\\\\%s%c%s", TEST_DIRNAME, PATH_DELIMITER, TEST_FILENAME);
+ f = newFileWithPath(p);
+ assertNotNull(f);
+ pAbs = newCharString();
+ // Mostly just testing to make sure that network paths are not incorrectly
+ // translated from relative ones
+ sprintf(pAbs->data, "\\\\%s%c%s", TEST_DIRNAME, PATH_DELIMITER, TEST_FILENAME);
+ assert(charStringIsEqualTo(pAbs, f->absolutePath, false));
+ assertFalse(fileExists(f));
+ assertIntEquals(kFileTypeInvalid, f->fileType);
+ assertIntEquals(kFileOpenModeClosed, f->_openMode);
+ assertIsNull(f->_fileHandle);
+
+ freeFile(f);
+ freeCharString(p);
+ freeCharString(pAbs);
+ freeCharString(pwd);
+#elif UNIX
+ // Unix systems mount network drives as regular filesystem paths, this test
+ // isn't necessary on that platform
+#else
+ assert(false);
+#endif
+ return 0;
+}
+
+static int _testNewFileWithInvalidPath(void)
+{
+ CharString p;
+ File f;
+
+ // These characters are not permitted in filenames on Windows or Unix. The
+ // file interface should refuse to create an object with such a path,
+ // regardless of type.
+
+ p = newCharStringWithCString("*");
+ f = newFileWithPath(p);
+ assertIsNull(f);
+ freeCharString(p);
+
+ p = newCharStringWithCString(":");
+ f = newFileWithPath(p);
+ assertIsNull(f);
+ freeCharString(p);
+
+ p = newCharStringWithCString("?");
+ f = newFileWithPath(p);
+ assertIsNull(f);
+ freeCharString(p);
+
+ p = newCharStringWithCString("<");
+ f = newFileWithPath(p);
+ assertIsNull(f);
+ freeCharString(p);
+
+ p = newCharStringWithCString(">");
+ f = newFileWithPath(p);
+ assertIsNull(f);
+ freeCharString(p);
+
+ p = newCharStringWithCString("|");
+ f = newFileWithPath(p);
+ assertIsNull(f);
+ freeCharString(p);
+
+ freeFile(f);
+ return 0;
+}
+
+static int _testNewFileWithNullPath(void)
+{
+ File f;
+
+ // Should yield the same result as newFile()
+ f = newFileWithPath(NULL);
+ assertNotNull(f);
+ assertCharStringEquals(EMPTY_STRING, f->absolutePath);
+ assertIntEquals(kFileTypeInvalid, f->fileType);
+ assertFalse(fileExists(f));
+
+ freeFile(f);
+ return 0;
+}
+
+static int _testNewFileWithCStringPath(void)
+{
+ File f = newFileWithPathCString(TEST_FILENAME);
+ assertNotNull(f);
+ assertFalse(fileExists(f));
+ freeFile(f);
+ return 0;
+}
+
+static int _testNewFileWithCStringPathNull(void)
+{
+ File f = newFileWithPathCString(NULL);
+ assertIsNull(f);
+ return 0;
+}
+
+static int _testNewFileWithParent(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File dir = newFileWithPath(pdir);
+ File f = NULL;
+ CharString pwd = fileGetCurrentDirectory();
+ CharString pAbs = newCharString();
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ f = newFileWithParent(dir, pfile);
+ assertNotNull(f);
+ assertFalse(fileExists(f));
+ sprintf(pAbs->data, "%s%c%s%c%s", pwd->data, PATH_DELIMITER, pdir->data, PATH_DELIMITER, pfile->data);
+ assertCharStringEquals(pAbs->data, f->absolutePath);
+ assertIntEquals(kFileOpenModeClosed, f->_openMode);
+ assertIsNull(f->_fileHandle);
+
+ freeCharString(pAbs);
+ freeCharString(pdir);
+ freeCharString(pfile);
+ freeCharString(pwd);
+ freeFile(f);
+ freeFile(dir);
+ return 0;
+}
+
+static int _testNewFileWithParentNullParent(void)
+{
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithParent(NULL, pfile);
+ assertIsNull(f);
+ freeCharString(pfile);
+ return 0;
+}
+
+static int _testNewFileWithParentNullPath(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(pdir);
+ File f = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ newFileWithParent(d, NULL);
+ assertIsNull(f);
+
+ freeCharString(pdir);
+ freeFile(d);
+ return 0;
+}
+
+static int _testNewFileWithParentEmptyPath(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ CharString empty = newCharString();
+ File d = newFileWithPath(pdir);
+ File f = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ newFileWithParent(d, empty);
+ assertIsNull(f);
+
+ freeCharString(pdir);
+ freeCharString(empty);
+ freeFile(d);
+ return 0;
+}
+
+static int _testNewFileWithParentAlreadyExists(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File dir = newFileWithPath(pdir);
+ File f = NULL;
+ File ftest = NULL;
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ f = newFileWithParent(dir, pfile);
+ assertNotNull(f);
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ ftest = newFileWithParent(dir, pfile);
+ assert(fileExists(ftest));
+ assertIntEquals(kFileTypeFile, ftest->fileType);
+
+ freeCharString(pdir);
+ freeCharString(pfile);
+ freeFile(f);
+ freeFile(ftest);
+ freeFile(dir);
+ return 0;
+}
+
+static int _testNewFileWithParentNotDirectory(void)
+{
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(pfile);
+ File ftest = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ ftest = newFileWithParent(f, pfile);
+ assertIsNull(ftest);
+
+ freeCharString(pfile);
+ freeFile(f);
+ freeFile(ftest);
+ return 0;
+}
+
+static int _testNewFileWithParentInvalid(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File dir = newFileWithPath(pdir);
+ File f = NULL;
+
+ assertFalse(fileExists(dir));
+ f = newFileWithParent(dir, pfile);
+ assertIsNull(f);
+
+ freeCharString(pdir);
+ freeCharString(pfile);
+ freeFile(f);
+ freeFile(dir);
+ return 0;
+}
+
+static int _testNewFileWithParentAbsolutePath(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ File dir = newFileWithPath(pdir);
+ File f = NULL;
+ CharString p = newCharString();
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ assert(fileExists(dir));
+ sprintf(p->data, "%s%c%s", ROOT_DIRECTORY, PATH_DELIMITER, TEST_FILENAME);
+ f = newFileWithParent(dir, p);
+ assertIsNull(f);
+
+ freeCharString(p);
+ freeCharString(pdir);
+ freeFile(f);
+ freeFile(dir);
+ return 0;
+}
+
+static int _testFileExists(void)
+{
+ File f;
+ FILE *fp = fopen(TEST_FILENAME, "w");
+ CharString c = newCharStringWithCString(TEST_FILENAME);
+ assert(fp != NULL);
+ fclose(fp);
+
+ f = newFileWithPath(c);
+ assert(fileExists(f));
+
+ freeFile(f);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testFileExistsInvalid(void)
+{
+ CharString c = newCharStringWithCString("invalid");
+ File f = newFileWithPath(c);
+
+ assertFalse(fileExists(f));
+
+ freeFile(f);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testFileCreateFile(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f, f2;
+
+ f = newFileWithPath(p);
+ assertNotNull(f);
+ // Might fail because a previous test didn't clean up properly
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assertIntEquals(kFileOpenModeWrite, f->_openMode);
+ assertNotNull(f->_fileHandle);
+
+ // Just to make sure...
+ f2 = newFileWithPath(p);
+ assert(fileExists(f2));
+ assertIntEquals(kFileTypeFile, f2->fileType);
+ assertIntEquals(kFileOpenModeClosed, f2->_openMode);
+ assertIsNull(f2->_fileHandle);
+
+ freeFile(f);
+ freeFile(f2);
+ freeCharString(p);
+ return 0;
+}
+
+static int _testFileCreateDir(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File f = newFileWithPath(p);
+ File f2;
+
+ assertNotNull(f);
+ // Might fail because a previous test didn't clean up properly
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeDirectory));
+ assert(fileExists(f));
+ // Just to make sure...
+ f2 = newFileWithPath(p);
+ assert(fileExists(f2));
+ assertIntEquals(kFileTypeDirectory, f2->fileType);
+ assertIntEquals(kFileOpenModeClosed, f->_openMode);
+ assertIsNull(f->_fileHandle);
+
+ freeFile(f);
+ freeFile(f2);
+ freeCharString(p);
+ return 0;
+}
+
+static int _testFileCreateInvalidType(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+
+ assertNotNull(f);
+ // Might fail because a previous test didn't clean up properly
+ assertFalse(fileExists(f));
+ assertFalse(fileCreate(f, kFileTypeInvalid));
+ assertFalse(fileExists(f));
+
+ freeFile(f);
+ freeCharString(p);
+ return 0;
+}
+
+static int _testFileCreateAlreadyExists(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+
+ assertNotNull(f);
+ // Might fail because a previous test didn't clean up properly
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assertFalse(fileCreate(f, kFileTypeFile));
+
+ freeFile(f);
+ freeCharString(p);
+ return 0;
+}
+
+static int _testFileCopyToWithFile(void)
+{
+ CharString psrc = newCharStringWithCString(TEST_FILENAME);
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ File src = newFileWithPath(psrc);
+ File dest = newFileWithPath(pdest);
+ File copy = NULL;
+
+ assertNotNull(src);
+ assertNotNull(dest);
+ assertFalse(fileExists(src));
+ assertFalse(fileExists(dest));
+
+ assert(fileCreate(dest, kFileTypeDirectory));
+ assert(fileExists(dest));
+ assert(fileCreate(src, kFileTypeFile));
+ assert(fileExists(src));
+ copy = fileCopyTo(src, dest);
+ assertNotNull(copy);
+ assert(fileExists(copy));
+
+ freeFile(src);
+ freeFile(dest);
+ freeFile(copy);
+ freeCharString(psrc);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileCopyToWithDirectory(void)
+{
+ CharString psrc = newCharStringWithCString(TEST_DIRNAME);
+ CharString psrcFile = newCharStringWithCString(TEST_FILENAME);
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME_COPY_DEST);
+ File src = newFileWithPath(psrc);
+ File srcFile = NULL;
+ File dest = newFileWithPath(pdest);
+ File copy = NULL;
+
+ assertNotNull(src);
+ assertNotNull(dest);
+ assertFalse(fileExists(src));
+ assertFalse(fileExists(dest));
+
+ assert(fileCreate(dest, kFileTypeDirectory));
+ assert(fileExists(dest));
+
+ assert(fileCreate(src, kFileTypeDirectory));
+ assert(fileExists(src));
+ srcFile = newFileWithParent(src, psrcFile);
+ assertNotNull(srcFile);
+ assertFalse(fileExists(srcFile));
+ assert(fileCreate(srcFile, kFileTypeFile));
+ assert(fileExists(srcFile));
+
+ copy = fileCopyTo(src, dest);
+ assertNotNull(copy);
+ assert(fileExists(copy));
+
+ freeFile(src);
+ freeFile(srcFile);
+ freeFile(dest);
+ freeFile(copy);
+ freeCharString(psrc);
+ freeCharString(psrcFile);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileCopyToInvalidDestination(void)
+{
+ CharString psrc = newCharStringWithCString(TEST_FILENAME);
+ CharString pdest = newCharStringWithCString("invalid");
+ File src = newFileWithPath(psrc);
+ File dest = newFileWithPath(pdest);
+ File copy = NULL;
+
+ assertNotNull(src);
+ assertNotNull(dest);
+ assertFalse(fileExists(src));
+ assertFalse(fileExists(dest));
+
+ assert(fileCreate(src, kFileTypeFile));
+ assert(fileExists(src));
+ copy = fileCopyTo(src, dest);
+ assertIsNull(copy);
+
+ freeFile(src);
+ freeFile(dest);
+ freeFile(copy);
+ freeCharString(psrc);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileCopyInvalidTo(void)
+{
+ CharString psrc = newCharStringWithCString("invalid");
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ File src = newFileWithPath(psrc);
+ File dest = newFileWithPath(pdest);
+ File copy = NULL;
+
+ assertNotNull(src);
+ assertNotNull(dest);
+ assertFalse(fileExists(src));
+ assertFalse(fileExists(dest));
+
+ assert(fileCreate(dest, kFileTypeDirectory));
+ assert(fileExists(dest));
+ copy = fileCopyTo(src, dest);
+ assertIsNull(copy);
+
+ freeFile(src);
+ freeFile(dest);
+ freeFile(copy);
+ freeCharString(psrc);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileCopyToNull(void)
+{
+ CharString psrc = newCharStringWithCString(TEST_FILENAME);
+ File src = newFileWithPath(psrc);
+ File copy = NULL;
+
+ assertNotNull(src);
+ assertFalse(fileExists(src));
+
+ assert(fileCreate(src, kFileTypeFile));
+ assert(fileExists(src));
+ copy = fileCopyTo(src, NULL);
+ assertIsNull(copy);
+
+ freeFile(src);
+ freeFile(copy);
+ freeCharString(psrc);
+ return 0;
+}
+
+static int _testFileRemoveDir(void)
+{
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ File dir = newFileWithPath(pdest);
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ assert(fileExists(dir));
+ assert(fileRemove(dir));
+ assertFalse(fileExists(dir));
+
+ freeFile(dir);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileRemoveDirWithContents(void)
+{
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ CharString psubdir = newCharStringWithCString(TEST_DIRNAME);
+ CharString psubfile = newCharStringWithCString(TEST_FILENAME);
+ File dir = newFileWithPath(pdest);
+ File subdir = NULL;
+ File subfile = NULL;
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ assert(fileExists(dir));
+
+ // Must be created after parent directory is created
+ subdir = newFileWithParent(dir, psubdir);
+ assertNotNull(subdir);
+ assertFalse(fileExists(subdir));
+ assert(fileCreate(subdir, kFileTypeDirectory));
+ assert(fileExists(subdir));
+
+ // Must be created after parent directory is created
+ subfile = newFileWithParent(dir, psubfile);
+ assertNotNull(subfile);
+ assertFalse(fileExists(subfile));
+ assert(fileCreate(subfile, kFileTypeFile));
+ assert(fileExists(subfile));
+ // Windows will not be able to delete this directory unless all files in it are
+ // closed and unlocked.
+ fileClose(subfile);
+
+ // Remove the parent directory and assert that all children are gone
+ assert(fileRemove(dir));
+ assertFalse(fileExists(dir));
+ assertFalse(fileExists(subdir));
+ assertFalse(fileExists(subfile));
+
+ freeFile(dir);
+ freeFile(subdir);
+ freeFile(subfile);
+ freeCharString(pdest);
+ freeCharString(psubdir);
+ freeCharString(psubfile);
+ return 0;
+}
+
+static int _testFileRemoveFile(void)
+{
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(pfile);
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileRemove(f));
+ assertFalse(fileExists(f));
+
+ freeCharString(pfile);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileRemoveInvalid(void)
+{
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(pfile);
+
+ assertFalse(fileExists(f));
+ assertFalse(fileRemove(f));
+
+ freeCharString(pfile);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileListDirectory(void)
+{
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ CharString psubdir = newCharStringWithCString(TEST_DIRNAME);
+ CharString psubfile = newCharStringWithCString(TEST_FILENAME);
+ File dir = newFileWithPath(pdest);
+ File subdir = NULL;
+ File subfile = NULL;
+ LinkedList list;
+ File item;
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ assert(fileExists(dir));
+
+ subdir = newFileWithParent(dir, psubdir);
+ assertNotNull(subdir);
+ assertFalse(fileExists(subdir));
+ assert(fileCreate(subdir, kFileTypeDirectory));
+ assertNotNull(subdir);
+
+ subfile = newFileWithParent(dir, psubfile);
+ assertNotNull(subfile);
+ assertFalse(fileExists(subfile));
+ assert(fileCreate(subfile, kFileTypeFile));
+ assert(fileExists(subfile));
+
+ list = fileListDirectory(dir);
+ assertNotNull(list);
+ item = (File)list->item;
+ assertNotNull(item);
+
+ if (item->fileType == kFileTypeFile) {
+ assertCharStringContains(TEST_FILENAME, item->absolutePath);
+ } else if (item->fileType == kFileTypeDirectory) {
+ assertCharStringContains(TEST_DIRNAME, item->absolutePath);
+ } else {
+ assert(false);
+ }
+
+ item = (File)(((LinkedList)list->nextItem)->item);
+ assertNotNull(item);
+
+ if (item->fileType == kFileTypeFile) {
+ assertCharStringContains(TEST_FILENAME, item->absolutePath);
+ } else if (item->fileType == kFileTypeDirectory) {
+ assertCharStringContains(TEST_DIRNAME, item->absolutePath);
+ } else {
+ assert(false);
+ }
+
+ freeLinkedListAndItems(list, (LinkedListFreeItemFunc)freeFile);
+ freeFile(dir);
+ freeFile(subfile);
+ freeFile(subdir);
+ freeCharString(pdest);
+ freeCharString(psubdir);
+ freeCharString(psubfile);
+ return 0;
+}
+
+static int _testFileListDirectoryInvalid(void)
+{
+ CharString pdest = newCharStringWithCString(TEST_FILENAME);
+ File file = newFileWithPath(pdest);
+ LinkedList list;
+
+ assertFalse(fileExists(file));
+ assert(fileCreate(file, kFileTypeFile));
+ assert(fileExists(file));
+ list = fileListDirectory(file);
+ assertIsNull(list);
+
+ freeFile(file);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileListDirectoryNotExists(void)
+{
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ File dir = newFileWithPath(pdest);
+ LinkedList list;
+
+ assertFalse(fileExists(dir));
+ list = fileListDirectory(dir);
+ assertIsNull(list);
+
+ freeFile(dir);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileListDirectoryEmpty(void)
+{
+ CharString pdest = newCharStringWithCString(TEST_DIRNAME);
+ File dir = newFileWithPath(pdest);
+ LinkedList list;
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ assert(fileExists(dir));
+ list = fileListDirectory(dir);
+ assertNotNull(list);
+ assertIntEquals(0, linkedListLength(list));
+
+ freeLinkedList(list);
+ freeFile(dir);
+ freeCharString(pdest);
+ return 0;
+}
+
+static int _testFileGetSize(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileWrite(f, p));
+ fileClose(f); // force flush and close to be called
+ assertSizeEquals(strlen(p->data), fileGetSize(f));
+
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetSizeNotExists(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+
+ assertFalse(fileExists(f));
+ assertSizeEquals((size_t)0, fileGetSize(f));
+
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetSizeDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(p);
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ assertSizeEquals((size_t)0, fileGetSize(d));
+
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileReadContents(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ result = fileReadContents(f);
+ assertNotNull(result);
+ assertCharStringEquals(p->data, result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileReadContentsNotExists(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ result = fileReadContents(f);
+ assertIsNull(result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileReadContentsDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(p);
+ CharString result = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ result = fileReadContents(d);
+ assertIsNull(result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileReadLines(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ LinkedList lines = NULL;
+ CharString *items = NULL;
+ int i;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ assert(fileWriteBytes(f, "\n", 1));
+ assert(fileWrite(f, p));
+ lines = fileReadLines(f);
+ assertNotNull(lines);
+ assertIntEquals(2, linkedListLength(lines));
+ items = (CharString *)linkedListToArray(lines);
+ assertNotNull(items);
+
+ for (i = 0; i < linkedListLength(lines); i++) {
+ assertCharStringEquals(p->data, items[i]);
+ }
+
+ freeLinkedListAndItems(lines, (LinkedListFreeItemFunc)freeCharString);
+ freeCharString(p);
+ freeFile(f);
+ free(items);
+ return 0;
+}
+
+static int _testFileReadLinesEmpty(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ LinkedList lines = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ lines = fileReadLines(f);
+ assertNotNull(lines);
+ assertIntEquals(0, linkedListLength(lines));
+
+ freeLinkedList(lines);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileReadLinesNotExists(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ LinkedList result = NULL;
+
+ assertFalse(fileExists(f));
+ result = fileReadLines(f);
+ assertIsNull(result);
+
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileReadLinesDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(p);
+ LinkedList result = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ result = fileReadLines(d);
+ assertIsNull(result);
+
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileReadBytes(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ size_t s = 0;
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ fileClose(f);
+ s = fileGetSize(f);
+ assert(s > 0);
+ char *fileData = fileReadBytes(f, s);
+ result = newCharStringWithCString(fileData);
+ assertNotNull(result);
+ assertCharStringEquals(TEST_FILENAME, result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ free(fileData);
+ return 0;
+}
+
+static int _testFileReadBytesNotExists(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ void *result = NULL;
+
+ assertFalse(fileExists(f));
+ // Note the fake size here
+ result = fileReadBytes(f, (size_t)100);
+ assertIsNull(result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileReadBytesDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File d = newFileWithPath(p);
+ void *result = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ result = fileReadBytes(d, (size_t)100);
+ assertIsNull(result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileReadBytesZeroSize(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ size_t s = 0;
+ char *result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ fileClose(f);
+ s = fileGetSize(f);
+ assert(s > 0);
+ result = (char *)fileReadBytes(f, 0);
+ assertIsNull(result);
+
+ free(result);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileReadBytesGreaterSize(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ size_t s = 0;
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ fileClose(f);
+ s = fileGetSize(f);
+ assert(s > 0);
+ char *fileData = (char*)fileReadBytes(f, s * 2);
+ result = newCharStringWithCString(fileData);
+ assertNotNull(result);
+ assertCharStringEquals(TEST_FILENAME, result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ free(fileData);
+ return 0;
+}
+
+static int _testFileWrite(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ result = fileReadContents(f);
+ assertNotNull(result);
+ assertCharStringEquals(TEST_FILENAME, result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileWriteMultiple(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ CharString p2 = newCharStringWithCString(p->data);
+ File f = newFileWithPath(p);
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWrite(f, p));
+ assert(fileWrite(f, p));
+ result = fileReadContents(f);
+ assertNotNull(result);
+ charStringAppend(p, p2);
+ assertCharStringEquals(p->data, result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeCharString(p2);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileWriteInvalid(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+
+ assertFalse(fileExists(f));
+ assertFalse(fileWrite(f, p));
+
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileWriteDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File d = newFileWithPath(p);
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assertFalse(fileWrite(d, p));
+
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileWriteBytes(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ size_t s = 0;
+ CharString result = NULL;
+
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+ assert(fileWriteBytes(f, p->data, strlen(p->data)));
+ fileClose(f);
+ s = fileGetSize(f);
+ assert(s > 0);
+ char *fileData = (char *)fileReadBytes(f, s);
+ result = newCharStringWithCString(fileData);
+ assertNotNull(result);
+ assertCharStringEquals(TEST_FILENAME, result);
+
+ freeCharString(result);
+ freeCharString(p);
+ freeFile(f);
+ free(fileData);
+ return 0;
+}
+
+static int _testFileWriteBytesInvalid(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+
+ assertFalse(fileExists(f));
+ assertFalse(fileWriteBytes(f, p->data, strlen(p->data)));
+
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileWriteBytesDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File d = newFileWithPath(p);
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assertFalse(fileWriteBytes(d, p->data, strlen(p->data)));
+
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileGetBasename(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ CharString b = fileGetBasename(f);
+
+ assertNotNull(b);
+ assertCharStringEquals(TEST_FILENAME, b);
+
+ freeCharString(b);
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetBasenameInvalid(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFile();
+ CharString b = fileGetBasename(f);
+
+ assertIsNull(b);
+
+ freeCharString(p);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetBasenameDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(p);
+ CharString b = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ b = fileGetBasename(d);
+ assertNotNull(b);
+ assertCharStringEquals(TEST_DIRNAME, b);
+
+ freeCharString(b);
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileGetParent(void)
+{
+ CharString pdir = newCharStringWithCString(TEST_DIRNAME);
+ File dir = newFileWithPath(pdir);
+ CharString pfile = newCharStringWithCString(TEST_FILENAME);
+ File f = NULL;
+ File result = NULL;
+
+ assertFalse(fileExists(dir));
+ assert(fileCreate(dir, kFileTypeDirectory));
+ assert(fileExists(dir));
+
+ f = newFileWithParent(dir, pfile);
+ assertNotNull(f);
+ assertFalse(fileExists(f));
+ assert(fileCreate(f, kFileTypeFile));
+ assert(fileExists(f));
+
+ result = fileGetParent(f);
+ assertNotNull(result);
+ assertCharStringEquals(dir->absolutePath->data, result->absolutePath);
+
+ freeCharString(pdir);
+ freeCharString(pfile);
+ freeFile(f);
+ freeFile(dir);
+ freeFile(result);
+ return 0;
+}
+
+static int _testFileGetParentInvalid(void)
+{
+ File f = newFile();
+ File result = fileGetParent(f);
+ assertIsNull(result);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetExtension(void)
+{
+ CharString p = newCharStringWithCString(TEST_FILENAME);
+ File f = newFileWithPath(p);
+ CharString extension = fileGetExtension(f);
+
+ assertNotNull(extension);
+ assertCharStringEquals("txt", extension);
+
+ freeCharString(p);
+ freeCharString(extension);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetExtensionDirectory(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(p);
+ CharString result = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+ assert(fileExists(d));
+ result = fileGetExtension(d);
+ assertIsNull(result);
+
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileGetExtensionInvalid(void)
+{
+ File f = newFile();
+ CharString result = fileGetExtension(f);
+ assertIsNull(result);
+ freeFile(f);
+ return 0;
+}
+
+static int _testFileGetExtensionNone(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME);
+ File d = newFileWithPath(p);
+ CharString result = NULL;
+
+ result = fileGetExtension(d);
+ assertIsNull(result);
+
+ freeCharString(p);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileGetExtensionWithDotInPath(void)
+{
+ CharString p = newCharStringWithCString(TEST_DIRNAME_WITH_DOT);
+ File d = newFileWithPath(p);
+ CharString pfile = newCharStringWithCString(TEST_DIRNAME_COPY_DEST);
+ File f = NULL;
+ CharString result = NULL;
+
+ assertFalse(fileExists(d));
+ assert(fileCreate(d, kFileTypeDirectory));
+
+ f = newFileWithParent(d, pfile);
+ assertNotNull(f);
+ result = fileGetExtension(f);
+ // The parent directory has a dot, but the file doesn't so this call
+ // should return null.
+ assertIsNull(result);
+
+ freeCharString(p);
+ freeCharString(pfile);
+ freeFile(f);
+ freeFile(d);
+ return 0;
+}
+
+static int _testFileFreeNull(void)
+{
+ freeFile(NULL);
+ return 0;
+}
+
+TestSuite addFileTests(void);
+TestSuite addFileTests(void)
+{
+ TestSuite testSuite = newTestSuite("File", NULL, _fileTestTeardown);
+ addTest(testSuite, "NewFileDefault", _testNewFileDefault);
+
+ addTest(testSuite, "NewFileAlreadyExists", _testNewFileAlreadyExists);
+ addTest(testSuite, "NewFileWithRelativePath", _testNewFileWithRelativePath);
+ addTest(testSuite, "NewFileWithAbsolutePath", _testNewFileWithAbsolutePath);
+ addTest(testSuite, "NewFileWithNetworkPath", _testNewFileWithNetworkPath);
+ addTest(testSuite, "NewFileWithInvalidPath", _testNewFileWithInvalidPath);
+ addTest(testSuite, "NewFileWithNullPath", _testNewFileWithNullPath);
+
+ addTest(testSuite, "NewFileWithCStringPath", _testNewFileWithCStringPath);
+ addTest(testSuite, "NewFileWithCStringPathNull", _testNewFileWithCStringPathNull);
+
+ addTest(testSuite, "NewFileWithParent", _testNewFileWithParent);
+ addTest(testSuite, "NewFileWithParentNullParent", _testNewFileWithParentNullParent);
+ addTest(testSuite, "NewFileWithParentNullPath", _testNewFileWithParentNullPath);
+ addTest(testSuite, "NewFileWithParentEmptyPath", _testNewFileWithParentEmptyPath);
+ addTest(testSuite, "NewFileWithParentAlreadyExists", _testNewFileWithParentAlreadyExists);
+ addTest(testSuite, "NewFileWithParentNotDirectory", _testNewFileWithParentNotDirectory);
+ addTest(testSuite, "NewFileWithParentInvalid", _testNewFileWithParentInvalid);
+ addTest(testSuite, "NewFileWithParentAbsolutePath", _testNewFileWithParentAbsolutePath);
+
+ addTest(testSuite, "FileExists", _testFileExists);
+ addTest(testSuite, "FileExistsInvalid", _testFileExistsInvalid);
+
+ addTest(testSuite, "FileCreateFile", _testFileCreateFile);
+ addTest(testSuite, "FileCreateDir", _testFileCreateDir);
+ addTest(testSuite, "FileCreateInvalidType", _testFileCreateInvalidType);
+ addTest(testSuite, "FileCreateAlreadyExists", _testFileCreateAlreadyExists);
+
+ addTest(testSuite, "FileCopyToWithFile", _testFileCopyToWithFile);
+ addTest(testSuite, "FileCopyToWithDirectory", _testFileCopyToWithDirectory);
+ addTest(testSuite, "FileCopyToInvalidDestination", _testFileCopyToInvalidDestination);
+ addTest(testSuite, "FileCopyNullTo", _testFileCopyInvalidTo);
+ addTest(testSuite, "FileCopyToNull", _testFileCopyToNull);
+
+ addTest(testSuite, "FileRemoveDir", _testFileRemoveDir);
+ addTest(testSuite, "FileRemoveDirWithContents", _testFileRemoveDirWithContents);
+ addTest(testSuite, "FileRemoveFile", _testFileRemoveFile);
+ addTest(testSuite, "FileRemoveInvalid", _testFileRemoveInvalid);
+
+ addTest(testSuite, "FileListDirectory", _testFileListDirectory);
+ addTest(testSuite, "FileListDirectoryInvalid", _testFileListDirectoryInvalid);
+ addTest(testSuite, "FileListDirectoryNotExists", _testFileListDirectoryNotExists);
+ addTest(testSuite, "FileListDirectoryEmpty", _testFileListDirectoryEmpty);
+
+ addTest(testSuite, "FileGetSize", _testFileGetSize);
+ addTest(testSuite, "FileGetSizeNotExists", _testFileGetSizeNotExists);
+ addTest(testSuite, "FileGetSizeDirectory", _testFileGetSizeDirectory);
+
+ addTest(testSuite, "FileReadContents", _testFileReadContents);
+ addTest(testSuite, "FileReadContentsNotExists", _testFileReadContentsNotExists);
+ addTest(testSuite, "FileReadContentsDirectory", _testFileReadContentsDirectory);
+
+ addTest(testSuite, "FileReadLines", _testFileReadLines);
+ addTest(testSuite, "FileReadLinesEmpty", _testFileReadLinesEmpty);
+ addTest(testSuite, "FileReadLinesNotExists", _testFileReadLinesNotExists);
+ addTest(testSuite, "FileReadLinesDirectory", _testFileReadLinesDirectory);
+
+ addTest(testSuite, "FileReadBytes", _testFileReadBytes);
+ addTest(testSuite, "FileReadBytesNotExists", _testFileReadBytesNotExists);
+ addTest(testSuite, "FileReadBytesDirectory", _testFileReadBytesDirectory);
+ addTest(testSuite, "FileReadBytesZeroSize", _testFileReadBytesZeroSize);
+ addTest(testSuite, "FileReadBytesGreaterSize", _testFileReadBytesGreaterSize);
+
+ addTest(testSuite, "FileWrite", _testFileWrite);
+ addTest(testSuite, "FileWriteMulitple", _testFileWriteMultiple);
+ addTest(testSuite, "FileWriteInvalid", _testFileWriteInvalid);
+ addTest(testSuite, "FileWriteDirectory", _testFileWriteDirectory);
+
+ addTest(testSuite, "FileWriteBytes", _testFileWriteBytes);
+ addTest(testSuite, "FileWriteBytesInvalid", _testFileWriteBytesInvalid);
+ addTest(testSuite, "FileWriteBytesDirectory", _testFileWriteBytesDirectory);
+
+ addTest(testSuite, "FileGetBasename", _testFileGetBasename);
+ addTest(testSuite, "FileGetBasenameInvalid", _testFileGetBasenameInvalid);
+ addTest(testSuite, "FileGetBasenameDirectory", _testFileGetBasenameDirectory);
+
+ addTest(testSuite, "FileGetParent", _testFileGetParent);
+ addTest(testSuite, "FileGetParentInvalid", _testFileGetParentInvalid);
+
+ addTest(testSuite, "FileGetExtension", _testFileGetExtension);
+ addTest(testSuite, "FileGetExtensionDirectory", _testFileGetExtensionDirectory);
+ addTest(testSuite, "FileGetExtensionInvalid", _testFileGetExtensionInvalid);
+ addTest(testSuite, "FileGetExtensionNone", _testFileGetExtensionNone);
+ addTest(testSuite, "FileGetExtensionWithDotInPath", _testFileGetExtensionWithDotInPath);
+
+ addTest(testSuite, "FileFreeNull", _testFileFreeNull);
+
+ return testSuite;
+}
diff --git a/test/base/LinkedListTest.c b/test/base/LinkedListTest.c
new file mode 100644
index 0000000..15b4290
--- /dev/null
+++ b/test/base/LinkedListTest.c
@@ -0,0 +1,245 @@
+#include <stdlib.h>
+#include "unit/TestRunner.h"
+
+static char *const TEST_ITEM_STRING = "test string";
+static char *const OTHER_TEST_ITEM_STRING = "other test string";
+
+static boolByte _gNumForeachCallbacksMade;
+static boolByte _gForeachCallbackOk;
+
+static void _linkedListTestSetup(void)
+{
+ _gNumForeachCallbacksMade = 0;
+ _gForeachCallbackOk = false;
+}
+
+static int _testNewLinkedList(void)
+{
+ LinkedList l = newLinkedList();
+ assertNotNull(l);
+ assertIsNull(l->nextItem);
+ assertIntEquals(0, linkedListLength(l));
+ assert(l->item == NULL);
+ freeLinkedList(l);
+ return 0;
+}
+
+static int _testAppendItemToList(void)
+{
+ LinkedList l = newLinkedList();
+ CharString c = newCharString();
+ charStringCopyCString(c, TEST_ITEM_STRING);
+ linkedListAppend(l, c);
+ assertNotNull(l->item);
+ assertCharStringEquals(TEST_ITEM_STRING, ((CharString)l->item));
+ assertIsNull(l->nextItem);
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ return 0;
+}
+
+static int _testAppendMultipleItemsToList(void)
+{
+ LinkedListIterator i;
+ LinkedList l = newLinkedList();
+ CharString c = newCharString();
+ CharString c2 = newCharString();
+
+ charStringCopyCString(c, TEST_ITEM_STRING);
+ charStringCopyCString(c2, OTHER_TEST_ITEM_STRING);
+ linkedListAppend(l, c);
+ linkedListAppend(l, c2);
+ assertNotNull(l->item);
+ assertCharStringEquals(TEST_ITEM_STRING, ((CharString)l->item));
+ assertNotNull(l->nextItem);
+ i = (LinkedListIterator)(l->nextItem);
+ assertNotNull(i->item);
+ assertCharStringEquals(OTHER_TEST_ITEM_STRING, ((CharString)i->item));
+ assertIsNull(i->nextItem);
+ assertIntEquals(2, linkedListLength(l));
+
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ return 0;
+}
+
+static int _testAppendNullItemToList(void)
+{
+ LinkedList l = newLinkedList();
+ linkedListAppend(l, NULL);
+ assertIsNull(l->item);
+ assertIsNull(l->nextItem);
+ assertIntEquals(0, linkedListLength(l));
+ freeLinkedList(l);
+ return 0;
+}
+
+static int _testAppendItemToNullList(void)
+{
+ CharString c = newCharString();
+ // The test here is not to crash
+ linkedListAppend(NULL, c);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNumItemsInList(void)
+{
+ CharString c;
+ int i;
+ LinkedList l = newLinkedList();
+
+ for (i = 0; i < 100; i++) {
+ c = newCharString();
+ charStringCopyCString(c, TEST_ITEM_STRING);
+ linkedListAppend(l, c);
+ }
+
+ assertIntEquals(100, linkedListLength(l));
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ return 0;
+}
+
+static int _testNumItemsInNullList(void)
+{
+ assertIntEquals(0, linkedListLength(NULL));
+ return 0;
+}
+
+static int _testLinkedListToArray(void)
+{
+ LinkedList l = newLinkedList();
+ CharString *arr;
+ CharString c;
+
+ linkedListAppend(l, newCharStringWithCString("one"));
+ linkedListAppend(l, newCharStringWithCString("two"));
+
+ arr = (CharString *)linkedListToArray(l);
+ assertNotNull(arr);
+ c = (CharString)arr[0];
+ assertCharStringEquals("one", c);
+ c = (CharString)arr[1];
+ assertCharStringEquals("two", c);
+ assertIsNull(arr[2]);
+
+ free(arr);
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ return 0;
+}
+
+static int _testLinkedListToArrayWithNull(void)
+{
+ CharString **arr;
+ arr = (CharString **)linkedListToArray(NULL);
+ assertIsNull(arr);
+ return 0;
+}
+
+static int _testLinkedListWithEmptyList(void)
+{
+ CharString **arr;
+ LinkedList l = newLinkedList();
+ arr = (CharString **)linkedListToArray(l);
+ assertIsNull(arr);
+ freeLinkedList(l);
+ return 0;
+}
+
+
+static void _linkedListEmptyCallback(void *item, void *userData)
+{
+ _gNumForeachCallbacksMade++;
+}
+
+static int _testForeachOverNullList(void)
+{
+ linkedListForeach(NULL, _linkedListEmptyCallback, NULL);
+ assertIntEquals(0, _gNumForeachCallbacksMade);
+ return 0;
+}
+
+static int _testForeachOverEmptyList(void)
+{
+ LinkedList l = newLinkedList();
+ linkedListForeach(l, _linkedListEmptyCallback, NULL);
+ assertIntEquals(0, _gNumForeachCallbacksMade);
+ freeLinkedList(l);
+ return 0;
+}
+
+static void _linkedListTestStringCallback(void *item, void *userData)
+{
+ CharString charString = (CharString)item;
+ _gForeachCallbackOk = charStringIsEqualToCString(charString, TEST_ITEM_STRING, false);
+ _gNumForeachCallbacksMade++;
+}
+
+static int _testForeachOverList(void)
+{
+ LinkedList l = newLinkedList();
+ CharString c = newCharString();
+
+ charStringCopyCString(c, TEST_ITEM_STRING);
+ linkedListAppend(l, c);
+ linkedListForeach(l, _linkedListTestStringCallback, NULL);
+ assertIntEquals(1, _gNumForeachCallbacksMade);
+ assert(_gForeachCallbackOk);
+
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ return 0;
+}
+
+static void _linkedListUserDataCallback(void *item, void *userData)
+{
+ CharString charString = (CharString)userData;
+ _gForeachCallbackOk = charStringIsEqualToCString(charString, TEST_ITEM_STRING, false);
+ _gNumForeachCallbacksMade++;
+}
+
+static int _testForeachOverUserData(void)
+{
+ LinkedList l = newLinkedList();
+ CharString c = newCharString();
+
+ charStringCopyCString(c, TEST_ITEM_STRING);
+ linkedListAppend(l, c);
+ linkedListForeach(l, _linkedListUserDataCallback, c);
+ assertIntEquals(1, _gNumForeachCallbacksMade);
+ assert(_gForeachCallbackOk);
+
+ freeLinkedListAndItems(l, (LinkedListFreeItemFunc)freeCharString);
+ return 0;
+}
+
+static int _testFreeNullLinkedList(void)
+{
+ freeLinkedList(NULL);
+ return 0;
+}
+
+TestSuite addLinkedListTests(void);
+TestSuite addLinkedListTests(void)
+{
+ TestSuite testSuite = newTestSuite("LinkedList", _linkedListTestSetup, NULL);
+ addTest(testSuite, "NewObject", _testNewLinkedList);
+
+ addTest(testSuite, "AppendItem", _testAppendItemToList);
+ addTest(testSuite, "AppendMultipleItems", _testAppendMultipleItemsToList);
+ addTest(testSuite, "AppendNullItem", _testAppendNullItemToList);
+ addTest(testSuite, "AppendItemToNullList", _testAppendItemToNullList);
+
+ addTest(testSuite, "NumItemsInList", _testNumItemsInList);
+ addTest(testSuite, "NumItemsInNullList", _testNumItemsInNullList);
+
+ addTest(testSuite, "LinkedListToArray", _testLinkedListToArray);
+ addTest(testSuite, "LinkedListToArrayWithNull", _testLinkedListToArrayWithNull);
+ addTest(testSuite, "LinkedListWithEmptyList", _testLinkedListWithEmptyList);
+
+ addTest(testSuite, "ForeachOverNullList", _testForeachOverNullList);
+ addTest(testSuite, "ForeachOverEmptyList", _testForeachOverEmptyList);
+ addTest(testSuite, "ForeachOverList", _testForeachOverList);
+ addTest(testSuite, "ForeachWithUserData", _testForeachOverUserData);
+
+ addTest(testSuite, "FreeNullLinkedList", _testFreeNullLinkedList);
+
+ return testSuite;
+}
diff --git a/test/base/PlatformInfoTest.c b/test/base/PlatformInfoTest.c
new file mode 100644
index 0000000..4744602
--- /dev/null
+++ b/test/base/PlatformInfoTest.c
@@ -0,0 +1,86 @@
+#include "unit/TestRunner.h"
+#include "base/PlatformInfo.h"
+
+static int _testGetPlatformType(void)
+{
+ PlatformInfo platform = newPlatformInfo();
+#if LINUX
+ assertIntEquals(PLATFORM_LINUX, platform->type);
+#elif MACOSX
+ assertIntEquals(PLATFORM_MACOSX, platform->type);
+#elif WINDOWS
+ assertIntEquals(PLATFORM_WINDOWS, platform->type);
+#else
+ assertIntEquals(PLATFORM_UNSUPPORTED, platform->type);
+#endif
+ freePlatformInfo(platform);
+ return 0;
+}
+
+static int _testGetPlatformName(void)
+{
+ PlatformInfo platform = newPlatformInfo();
+#if LINUX
+ assertCharStringContains("Linux", platform->name);
+#elif MACOSX
+ assertCharStringContains("Mac OS X", platform->name);
+#elif WINDOWS
+ assertCharStringContains("Windows", platform->name);
+#else
+ assertCharStringEquals("Unsupported platform", platform->name);
+#endif
+ freePlatformInfo(platform);
+ return 0;
+}
+
+static int _testGetShortPlatformName(void)
+{
+ PlatformInfo platform = newPlatformInfo();
+#if LINUX
+
+ if (platformInfoIsHost64Bit() && platformInfoIsRuntime64Bit()) {
+ assertCharStringEquals("Linux-x86_64", platform->shortName);
+ } else {
+ assertCharStringEquals("Linux-i686", platform->shortName);
+ }
+
+#elif MACOSX
+ assertCharStringEquals("Mac OS X", platform->shortName);
+#elif WINDOWS
+
+ if (platformInfoIsHost64Bit() && platformInfoIsRuntime64Bit()) {
+ assertCharStringEquals("Windows 64-bit", platform->shortName);
+ } else {
+ assertCharStringEquals("Windows 32-bit", platform->shortName);
+ }
+
+#else
+ assertCharStringEquals("Unsupported", platform->shortName);
+#endif
+ freePlatformInfo(platform);
+ return 0;
+}
+
+static int _testIsHostLittleEndian(void)
+{
+#if HOST_BIG_ENDIAN
+ assertFalse(isHostLittleEndian());
+#elif HOST_LITTLE_ENDIAN
+ assert(isHostLittleEndian());
+#endif
+ return 0;
+}
+
+TestSuite addPlatformInfoTests(void);
+TestSuite addPlatformInfoTests(void)
+{
+ TestSuite testSuite = newTestSuite("PlatformInfo", NULL, NULL);
+
+ addTest(testSuite, "GetPlatformType", _testGetPlatformType);
+ addTest(testSuite, "GetPlatformName", _testGetPlatformName);
+ addTest(testSuite, "GetShortPlatformName", _testGetShortPlatformName);
+
+ addTest(testSuite, "IsHostLittleEndian", _testIsHostLittleEndian);
+
+ return testSuite;
+}
diff --git a/test/io/SampleSourceTest.c b/test/io/SampleSourceTest.c
new file mode 100644
index 0000000..999c90b
--- /dev/null
+++ b/test/io/SampleSourceTest.c
@@ -0,0 +1,55 @@
+#include "unit/TestRunner.h"
+#include "io/SampleSource.h"
+#include "audio/AudioSettings.h"
+
+const char *TEST_SAMPLESOURCE_FILENAME = "test.pcm";
+
+static void _sampleSourceSetup(void)
+{
+ initAudioSettings();
+}
+
+static void _sampleSourceTeardown(void)
+{
+ freeAudioSettings();
+}
+
+static int _testGuessSampleSourceTypePcm(void)
+{
+ CharString c = newCharStringWithCString(TEST_SAMPLESOURCE_FILENAME);
+ SampleSource s = sampleSourceFactory(c);
+ assertIntEquals(SAMPLE_SOURCE_TYPE_PCM, s->sampleSourceType);
+ freeSampleSource(s);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testGuessSampleSourceTypeEmpty(void)
+{
+ CharString empty = newCharString();
+ SampleSource s = sampleSourceFactory(empty);
+ assertIntEquals(SAMPLE_SOURCE_TYPE_SILENCE, s->sampleSourceType);
+ freeSampleSource(s);
+ freeCharString(empty);
+ return 0;
+}
+
+static int _testGuessSampleSourceTypeWrongCase(void)
+{
+ CharString c = newCharStringWithCString("TEST.PCM");
+ SampleSource s = sampleSourceFactory(c);
+ assertIntEquals(SAMPLE_SOURCE_TYPE_PCM, s->sampleSourceType);
+ freeSampleSource(s);
+ freeCharString(c);
+ return 0;
+}
+
+TestSuite addSampleSourceTests(void);
+TestSuite addSampleSourceTests(void)
+{
+ TestSuite testSuite = newTestSuite("SampleSource", _sampleSourceSetup, _sampleSourceTeardown);
+ addTest(testSuite, "GuessSampleSourceTypePcm", _testGuessSampleSourceTypePcm);
+ addTest(testSuite, "GuessSampleSourceTypeEmpty", _testGuessSampleSourceTypeEmpty);
+ addTest(testSuite, "GuessSampleSourceTypeWrongCase", _testGuessSampleSourceTypeWrongCase);
+ return testSuite;
+}
diff --git a/test/midi/MidiSequenceTest.c b/test/midi/MidiSequenceTest.c
new file mode 100644
index 0000000..6b306ba
--- /dev/null
+++ b/test/midi/MidiSequenceTest.c
@@ -0,0 +1,138 @@
+#include "unit/TestRunner.h"
+#include "midi/MidiSequence.h"
+
+static int _testNewMidiSequence(void)
+{
+ MidiSequence m = newMidiSequence();
+ assertNotNull(m);
+ assertIntEquals(0, linkedListLength(m->midiEvents));
+ freeMidiSequence(m);
+ return 0;
+}
+
+static int _testAppendMidiEventToSequence(void)
+{
+ MidiSequence m = newMidiSequence();
+ MidiEvent e = newMidiEvent();
+ appendMidiEventToSequence(m, e);
+ assertIntEquals(1, linkedListLength(m->midiEvents));
+ freeMidiSequence(m);
+ return 0;
+}
+
+static int _testAppendNullMidiEventToSequence(void)
+{
+ MidiSequence m = newMidiSequence();
+ appendMidiEventToSequence(m, NULL);
+ assertIntEquals(0, linkedListLength(m->midiEvents));
+ freeMidiSequence(m);
+ return 0;
+}
+
+static int _testAppendEventToNullSequence(void)
+{
+ // Test is not crashing
+ MidiEvent e = newMidiEvent();
+ appendMidiEventToSequence(NULL, e);
+ freeMidiEvent(e);
+ return 0;
+}
+
+static int _testFillMidiEventsFromRangeStart(void)
+{
+ MidiSequence m = newMidiSequence();
+ MidiEvent e = newMidiEvent();
+ LinkedList l = newLinkedList();
+
+ e->status = 0xf7;
+ e->timestamp = 100;
+ appendMidiEventToSequence(m, e);
+ assertFalse(fillMidiEventsFromRange(m, 0, 256, l));
+ assertIntEquals(1, linkedListLength(l));
+ assertIntEquals(0xf7, ((MidiEvent)l->item)->status);
+
+ freeMidiSequence(m);
+ freeLinkedList(l);
+ return 0;
+}
+
+static int _testFillEventsFromEmptyRange(void)
+{
+ MidiSequence m = newMidiSequence();
+ MidiEvent e = newMidiEvent();
+ LinkedList l = newLinkedList();
+
+ e->status = 0xf7;
+ e->timestamp = 100;
+ appendMidiEventToSequence(m, e);
+ assert(fillMidiEventsFromRange(m, 0, 0, l));
+ assertIntEquals(0, linkedListLength(l));
+
+ freeMidiSequence(m);
+ freeLinkedList(l);
+ return 0;
+}
+
+static int _testFillEventsSequentially(void)
+{
+ MidiSequence m = newMidiSequence();
+ MidiEvent e = newMidiEvent();
+ MidiEvent e2 = newMidiEvent();
+ LinkedList l = newLinkedList();
+
+ e->status = 0xf7;
+ e->timestamp = 100;
+ e2->status = 0xf7;
+ e2->timestamp = 300;
+ appendMidiEventToSequence(m, e);
+ appendMidiEventToSequence(m, e2);
+ assert(fillMidiEventsFromRange(m, 0, 256, l));
+ assertIntEquals(1, linkedListLength(l));
+ freeLinkedList(l);
+ l = newLinkedList();
+ assertFalse(fillMidiEventsFromRange(m, 256, 256, l));
+ assertIntEquals(1, linkedListLength(l));
+
+ freeMidiSequence(m);
+ freeLinkedList(l);
+ return 0;
+}
+
+static int _testFillEventsFromRangePastSequence(void)
+{
+ MidiSequence m = newMidiSequence();
+ MidiEvent e = newMidiEvent();
+ LinkedList l = newLinkedList();
+
+ e->status = 0xf7;
+ e->timestamp = 100;
+ appendMidiEventToSequence(m, e);
+ // Should return false since this is the last event in the sequence
+ assertFalse(fillMidiEventsFromRange(m, 0, 200, l));
+ assertIntEquals(1, linkedListLength(l));
+ freeLinkedList(l);
+ l = newLinkedList();
+ assertFalse(fillMidiEventsFromRange(m, 200, 256, l));
+ assertIntEquals(0, linkedListLength(l));
+
+ freeMidiSequence(m);
+ freeLinkedList(l);
+ return 0;
+}
+
+TestSuite addMidiSequenceTests(void);
+TestSuite addMidiSequenceTests(void)
+{
+ TestSuite testSuite = newTestSuite("MidiSequence", NULL, NULL);
+
+ addTest(testSuite, "Initialization", _testNewMidiSequence);
+ addTest(testSuite, "AppendEvent", _testAppendMidiEventToSequence);
+ addTest(testSuite, "AppendNullEvent", _testAppendNullMidiEventToSequence);
+ addTest(testSuite, "AppendEventToNullSequence", _testAppendEventToNullSequence);
+ addTest(testSuite, "FillEventsFromRangeStart", _testFillMidiEventsFromRangeStart);
+ addTest(testSuite, "FillEventsFromEmptyRange", _testFillEventsFromEmptyRange);
+ addTest(testSuite, "FillEventsSequentially", _testFillEventsSequentially);
+ addTest(testSuite, "FillEventsFromRangePastSequenceEnd", _testFillEventsFromRangePastSequence);
+
+ return testSuite;
+}
diff --git a/test/midi/MidiSourceTest.c b/test/midi/MidiSourceTest.c
new file mode 100644
index 0000000..c64eb65
--- /dev/null
+++ b/test/midi/MidiSourceTest.c
@@ -0,0 +1,41 @@
+#include "unit/TestRunner.h"
+#include "midi/MidiSource.h"
+
+const char *TEST_MIDI_FILENAME = "test.mid";
+
+static int _testGuessMidiSourceType(void)
+{
+ CharString c = newCharStringWithCString(TEST_MIDI_FILENAME);
+ assertIntEquals(MIDI_SOURCE_TYPE_FILE, guessMidiSourceType(c));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testGuessMidiSourceTypeInvalid(void)
+{
+ CharString c = newCharStringWithCString("invalid");
+ assertIntEquals(MIDI_SOURCE_TYPE_INVALID, guessMidiSourceType(c));
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNewMidiSource(void)
+{
+ CharString c = newCharStringWithCString(TEST_MIDI_FILENAME);
+ MidiSource m = newMidiSource(MIDI_SOURCE_TYPE_FILE, c);
+ assertCharStringEquals(TEST_MIDI_FILENAME, m->sourceName);
+ assertIntEquals(MIDI_SOURCE_TYPE_FILE, m->midiSourceType);
+ freeMidiSource(m);
+ freeCharString(c);
+ return 0;
+}
+
+TestSuite addMidiSourceTests(void);
+TestSuite addMidiSourceTests(void)
+{
+ TestSuite testSuite = newTestSuite("MidiSource", NULL, NULL);
+ addTest(testSuite, "GuessMidiSourceType", _testGuessMidiSourceType);
+ addTest(testSuite, "GuessMidiSourceTypeInvalid", _testGuessMidiSourceTypeInvalid);
+ addTest(testSuite, "NewObject", _testNewMidiSource);
+ return testSuite;
+}
diff --git a/test/mrswatsontest b/test/mrswatsontest
new file mode 100755
index 0000000..8a90eb0
--- /dev/null
+++ b/test/mrswatsontest
Binary files differ
diff --git a/test/mrswatsontest64 b/test/mrswatsontest64
new file mode 100755
index 0000000..74b31af
--- /dev/null
+++ b/test/mrswatsontest64
Binary files differ
diff --git a/test/plugin/PluginChainTest.c b/test/plugin/PluginChainTest.c
new file mode 100644
index 0000000..cb07f71
--- /dev/null
+++ b/test/plugin/PluginChainTest.c
@@ -0,0 +1,311 @@
+#include "unit/TestRunner.h"
+#include "audio/AudioSettings.h"
+#include "midi/MidiEvent.h"
+#include "plugin/PluginChain.h"
+#include "plugin/PluginPassthru.h"
+
+#include "PluginMock.h"
+#include "PluginPresetMock.h"
+
+static void _pluginChainTestSetup(void)
+{
+ initPluginChain();
+}
+
+static void _pluginChainTestTeardown(void)
+{
+ freePluginChain(getPluginChain());
+}
+
+static int _testInitPluginChain(void)
+{
+ PluginChain p = getPluginChain();
+ assertIntEquals(0, p->numPlugins);
+ assertNotNull(p->plugins);
+ assertNotNull(p->presets);
+ return 0;
+}
+
+static int _testAddFromArgumentStringNull(void)
+{
+ PluginChain p = getPluginChain();
+ CharString c = newCharStringWithCString("/");
+
+ assertFalse(pluginChainAddFromArgumentString(p, NULL, c));
+ assertIntEquals(0, p->numPlugins);
+
+ freeCharString(c);
+ return 0;
+}
+
+static int _testAddFromArgumentStringEmpty(void)
+{
+ PluginChain p = getPluginChain();
+ CharString c = newCharStringWithCString("/");
+ CharString empty = newCharString();
+
+ assertFalse(pluginChainAddFromArgumentString(p, empty, c));
+ assertIntEquals(0, p->numPlugins);
+
+ freeCharString(c);
+ freeCharString(empty);
+ return 0;
+}
+
+static int _testAddFromArgumentStringEmptyLocation(void)
+{
+ PluginChain p = getPluginChain();
+ CharString c = newCharStringWithCString(kInternalPluginPassthruName);
+ CharString empty = newCharString();
+
+ assert(pluginChainAddFromArgumentString(p, c, empty));
+ assertIntEquals(1, p->numPlugins);
+
+ freeCharString(c);
+ freeCharString(empty);
+ return 0;
+}
+
+static int _testAddFromArgumentStringNullLocation(void)
+{
+ PluginChain p = getPluginChain();
+ CharString c = newCharStringWithCString(kInternalPluginPassthruName);
+
+ assert(pluginChainAddFromArgumentString(p, c, NULL));
+ assertIntEquals(1, p->numPlugins);
+
+ freeCharString(c);
+ return 0;
+}
+
+static int _testAddFromArgumentString(void)
+{
+ PluginChain p = getPluginChain();
+ CharString testArgs = newCharStringWithCString(kInternalPluginPassthruName);
+
+ assert(pluginChainAddFromArgumentString(p, testArgs, NULL));
+ assertIntEquals(1, p->numPlugins);
+ assertNotNull(p->plugins[0]);
+ assertIntEquals(PLUGIN_TYPE_INTERNAL, p->plugins[0]->pluginType);
+ assertCharStringEquals(kInternalPluginPassthruName, p->plugins[0]->pluginName);
+
+ freeCharString(testArgs);
+ return 0;
+}
+
+static int _testAddFromArgumentStringMultiple(void)
+{
+ PluginChain p = getPluginChain();
+ CharString testArgs = newCharStringWithCString(kInternalPluginPassthruName);
+ unsigned int i;
+
+ assert(pluginChainAddFromArgumentString(p, testArgs, NULL));
+ assert(pluginChainAddFromArgumentString(p, testArgs, NULL));
+ assertIntEquals(2, p->numPlugins);
+
+ for (i = 0; i < p->numPlugins; i++) {
+ assertNotNull(p->plugins[i]);
+ assertIntEquals(PLUGIN_TYPE_INTERNAL, p->plugins[i]->pluginType);
+ assertCharStringEquals(kInternalPluginPassthruName, p->plugins[i]->pluginName);
+ }
+
+ freeCharString(testArgs);
+ return 0;
+}
+
+static int _testAddPluginWithPresetFromArgumentString(void)
+{
+ PluginChain p = getPluginChain();
+ CharString testArgs = newCharStringWithCString("mrs_passthru,testPreset.fxp");
+
+ assert(pluginChainAddFromArgumentString(p, testArgs, NULL));
+ assertIntEquals(1, p->numPlugins);
+ assertIntEquals(PLUGIN_TYPE_INTERNAL, p->plugins[0]->pluginType);
+ assertCharStringEquals(kInternalPluginPassthruName, p->plugins[0]->pluginName);
+ assertNotNull(p->presets[0]);
+ assertCharStringEquals("testPreset.fxp", p->presets[0]->presetName);
+
+ freeCharString(testArgs);
+ return 0;
+}
+
+static int _testAddFromArgumentStringWithPresetSpaces(void)
+{
+ PluginChain p = getPluginChain();
+ CharString testArgs = newCharStringWithCString("mrs_passthru,test preset.fxp");
+
+ assert(pluginChainAddFromArgumentString(p, testArgs, NULL));
+ assertIntEquals(1, p->numPlugins);
+ assertIntEquals(PLUGIN_TYPE_INTERNAL, p->plugins[0]->pluginType);
+ assertCharStringEquals(kInternalPluginPassthruName, p->plugins[0]->pluginName);
+ assertNotNull(p->presets[0]);
+ assertCharStringEquals("test preset.fxp", p->presets[0]->presetName);
+
+ freeCharString(testArgs);
+ return 0;
+}
+
+static int _testAppendPlugin(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ assert(pluginChainAppend(p, mock, NULL));
+ return 0;
+}
+
+static int _testAppendWithNullPlugin(void)
+{
+ PluginChain p = getPluginChain();
+ assertFalse(pluginChainAppend(p, NULL, NULL));
+ return 0;
+}
+
+static int _testAppendWithPreset(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ PluginPreset mockPreset = newPluginPresetMock();
+ assert(pluginChainAppend(p, mock, mockPreset));
+ return 0;
+}
+
+static int _testInitializePluginChain(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ PluginPreset mockPreset = newPluginPresetMock();
+
+ assert(pluginChainAppend(p, mock, mockPreset));
+ pluginChainInitialize(p);
+ assert(((PluginMockData)mock->extraData)->isOpen);
+ assert(((PluginPresetMockData)mockPreset->extraData)->isOpen);
+ assert(((PluginPresetMockData)mockPreset->extraData)->isLoaded);
+
+ return 0;
+}
+
+static int _testGetMaximumTailTime(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ int maxTailTime = 0;
+
+ assert(pluginChainAppend(p, mock, NULL));
+ maxTailTime = pluginChainGetMaximumTailTimeInMs(p);
+ assertIntEquals(kPluginMockTailTime, maxTailTime);
+
+ return 0;
+}
+
+static int _testPrepareForProcessing(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+
+ assert(pluginChainAppend(p, mock, NULL));
+ assertIntEquals(RETURN_CODE_SUCCESS, pluginChainInitialize(p));
+ pluginChainPrepareForProcessing(p);
+ assert(((PluginMockData)mock->extraData)->isPrepared);
+
+ return 0;
+}
+
+static int _testProcessPluginChainAudio(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ SampleBuffer inBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, DEFAULT_BLOCKSIZE);
+ SampleBuffer outBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, DEFAULT_BLOCKSIZE);
+
+ assert(pluginChainAppend(p, mock, NULL));
+ pluginChainProcessAudio(p, inBuffer, outBuffer);
+ assert(((PluginMockData)mock->extraData)->processAudioCalled);
+
+ freeSampleBuffer(inBuffer);
+ freeSampleBuffer(outBuffer);
+ return 0;
+}
+
+static int _testProcessPluginChainAudioRealtime(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ SampleBuffer inBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, DEFAULT_BLOCKSIZE);
+ SampleBuffer outBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, DEFAULT_BLOCKSIZE);
+ TaskTimer t = newTaskTimerWithCString("test", "test");
+
+ assert(pluginChainAppend(p, mock, NULL));
+ pluginChainSetRealtime(p, true);
+ taskTimerStart(t);
+ pluginChainProcessAudio(p, inBuffer, outBuffer);
+ assertTimeEquals(1000 * DEFAULT_BLOCKSIZE / getSampleRate(), taskTimerStop(t), 0.1);
+ assert(((PluginMockData)mock->extraData)->processAudioCalled);
+
+ freeTaskTimer(t);
+ freeSampleBuffer(inBuffer);
+ freeSampleBuffer(outBuffer);
+ return 0;
+}
+
+static int _testProcessPluginChainMidiEvents(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+ SampleBuffer inBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, DEFAULT_BLOCKSIZE);
+ SampleBuffer outBuffer = newSampleBuffer(DEFAULT_NUM_CHANNELS, DEFAULT_BLOCKSIZE);
+ LinkedList list = newLinkedList();
+ MidiEvent midi = newMidiEvent();
+
+ linkedListAppend(list, midi);
+ assert(pluginChainAppend(p, mock, NULL));
+ pluginChainProcessMidi(p, list);
+ assert(((PluginMockData)mock->extraData)->processMidiCalled);
+
+ freeMidiEvent(midi);
+ freeLinkedList(list);
+ freeSampleBuffer(inBuffer);
+ freeSampleBuffer(outBuffer);
+ return 0;
+}
+
+static int _testShutdown(void)
+{
+ Plugin mock = newPluginMock();
+ PluginChain p = getPluginChain();
+
+ assert(pluginChainAppend(p, mock, NULL));
+ pluginChainShutdown(p);
+ assertFalse(((PluginMockData)mock->extraData)->isOpen);
+
+ return 0;
+}
+
+TestSuite addPluginChainTests(void);
+TestSuite addPluginChainTests(void)
+{
+ TestSuite testSuite = newTestSuite("PluginChain", _pluginChainTestSetup, _pluginChainTestTeardown);
+ addTest(testSuite, "Initialization", _testInitPluginChain);
+ addTest(testSuite, "AddFromArgumentStringNull", _testAddFromArgumentStringNull);
+ addTest(testSuite, "AddFromArgumentStringEmpty", _testAddFromArgumentStringEmpty);
+ addTest(testSuite, "AddFromArgumentStringEmptyLocation", _testAddFromArgumentStringEmptyLocation);
+ addTest(testSuite, "AddFromArgumentStringNullLocation", _testAddFromArgumentStringNullLocation);
+ addTest(testSuite, "AddFromArgumentString", _testAddFromArgumentString);
+ addTest(testSuite, "AddFromArgumentStringMultiple", _testAddFromArgumentStringMultiple);
+ addTest(testSuite, "AddPluginWithPresetFromArgumentString", _testAddPluginWithPresetFromArgumentString);
+ addTest(testSuite, "AddFromArgumentStringWithPresetSpaces", _testAddFromArgumentStringWithPresetSpaces);
+ addTest(testSuite, "AppendPlugin", _testAppendPlugin);
+ addTest(testSuite, "AppendWithNullPlugin", _testAppendWithNullPlugin);
+ addTest(testSuite, "AppendWithPreset", _testAppendWithPreset);
+ addTest(testSuite, "InitializePluginChain", _testInitializePluginChain);
+
+ addTest(testSuite, "GetMaximumTailTime", _testGetMaximumTailTime);
+
+ addTest(testSuite, "PrepareForProcessing", _testPrepareForProcessing);
+ addTest(testSuite, "ProcessPluginChainAudio", _testProcessPluginChainAudio);
+ addTest(testSuite, "ProcessPluginChainAudioRealtime", _testProcessPluginChainAudioRealtime);
+ addTest(testSuite, "ProcessPluginChainMidiEvents", _testProcessPluginChainMidiEvents);
+
+ addTest(testSuite, "Shutdown", _testShutdown);
+
+ return testSuite;
+}
diff --git a/test/plugin/PluginMock.c b/test/plugin/PluginMock.c
new file mode 100644
index 0000000..249a722
--- /dev/null
+++ b/test/plugin/PluginMock.c
@@ -0,0 +1,95 @@
+#include "PluginMock.h"
+
+
+static void _pluginMockEmpty(void *pluginPtr)
+{
+ // Nothing to do here
+}
+
+static boolByte _pluginMockOpen(void *pluginPtr)
+{
+ Plugin self = (Plugin)pluginPtr;
+ PluginMockData extraData = (PluginMockData)self->extraData;
+ extraData->isOpen = true;
+ return true;
+}
+
+static int _pluginMockGetSetting(void *pluginPtr, PluginSetting pluginSetting)
+{
+ switch (pluginSetting) {
+ case PLUGIN_SETTING_TAIL_TIME_IN_MS:
+ return kPluginMockTailTime;
+
+ case PLUGIN_NUM_INPUTS:
+ return 2;
+
+ case PLUGIN_NUM_OUTPUTS:
+ return 2;
+
+ case PLUGIN_INITIAL_DELAY:
+ return 0;
+
+ default:
+ return 0;
+ }
+}
+
+static void _pluginMockPrepareForProcessing(void *pluginPtr)
+{
+ Plugin self = (Plugin)pluginPtr;
+ PluginMockData extraData = (PluginMockData)self->extraData;
+ extraData->isPrepared = true;
+}
+
+static void _pluginMockProcessAudio(void *pluginPtr, SampleBuffer inputs, SampleBuffer outputs)
+{
+ Plugin self = (Plugin)pluginPtr;
+ PluginMockData extraData = (PluginMockData)self->extraData;
+ extraData->processAudioCalled = true;
+ sampleBufferClear(outputs);
+}
+
+static void _pluginMockProcessMidiEvents(void *pluginPtr, LinkedList midiEvents)
+{
+ Plugin self = (Plugin)pluginPtr;
+ PluginMockData extraData = (PluginMockData)self->extraData;
+ extraData->processMidiCalled = true;
+}
+
+static boolByte _pluginMockSetParameter(void *pluginPtr, unsigned int i, float value)
+{
+ return false;
+}
+
+static void _pluginMockClose(void *pluginPtr)
+{
+ Plugin self = (Plugin)pluginPtr;
+ PluginMockData extraData = (PluginMockData)self->extraData;
+ extraData->isOpen = false;
+}
+
+Plugin newPluginMock(void)
+{
+ Plugin plugin = _newPlugin(PLUGIN_TYPE_INTERNAL, PLUGIN_TYPE_INSTRUMENT);
+ charStringCopyCString(plugin->pluginName, "Mock");
+ charStringCopyCString(plugin->pluginLocation, "Internal");
+
+ plugin->openPlugin = _pluginMockOpen;
+ plugin->displayInfo = _pluginMockEmpty;
+ plugin->getSetting = _pluginMockGetSetting;
+ plugin->prepareForProcessing = _pluginMockPrepareForProcessing;
+ plugin->processAudio = _pluginMockProcessAudio;
+ plugin->processMidiEvents = _pluginMockProcessMidiEvents;
+ plugin->setParameter = _pluginMockSetParameter;
+ plugin->closePlugin = _pluginMockClose;
+ plugin->freePluginData = _pluginMockEmpty;
+
+ PluginMockData extraData = (PluginMockData)malloc(sizeof(PluginMockDataMembers));
+ extraData->isOpen = false;
+ extraData->isPrepared = false;
+ extraData->processAudioCalled = false;
+ extraData->processMidiCalled = false;
+ plugin->extraData = extraData;
+
+ return plugin;
+}
diff --git a/test/plugin/PluginMock.h b/test/plugin/PluginMock.h
new file mode 100644
index 0000000..e5eaa15
--- /dev/null
+++ b/test/plugin/PluginMock.h
@@ -0,0 +1,19 @@
+#ifndef MrsWatson_PluginMock_h
+#define MrsWatson_PluginMock_h
+
+#include "base/Types.h"
+#include "plugin/Plugin.h"
+
+static const int kPluginMockTailTime = 123;
+
+typedef struct {
+ boolByte isOpen;
+ boolByte isPrepared;
+ boolByte processAudioCalled;
+ boolByte processMidiCalled;
+} PluginMockDataMembers;
+typedef PluginMockDataMembers *PluginMockData;
+
+Plugin newPluginMock(void);
+
+#endif
diff --git a/test/plugin/PluginPresetMock.c b/test/plugin/PluginPresetMock.c
new file mode 100644
index 0000000..8911c75
--- /dev/null
+++ b/test/plugin/PluginPresetMock.c
@@ -0,0 +1,42 @@
+#include "PluginPresetMock.h"
+
+static boolByte _openPluginPresetMock(void *pluginPresetPtr)
+{
+ PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr;
+ PluginPresetMockData extraData = (PluginPresetMockData)pluginPreset->extraData;
+ extraData->isOpen = true;
+ return true;
+}
+
+static boolByte _loadPluginPresetMock(void *pluginPresetPtr, Plugin plugin)
+{
+ PluginPreset pluginPreset = (PluginPreset)pluginPresetPtr;
+ PluginPresetMockData extraData = (PluginPresetMockData)pluginPreset->extraData;
+ extraData->isLoaded = true;
+ return true;
+}
+
+static void _freePluginPresetMock(void *extraDataPtr)
+{
+}
+
+PluginPreset newPluginPresetMock(void)
+{
+ PluginPreset pluginPreset = (PluginPreset)malloc(sizeof(PluginPresetMembers));
+ PluginPresetMockData extraData = (PluginPresetMockData)malloc(sizeof(PluginPresetMockDataMembers));
+
+ pluginPreset->presetType = PRESET_TYPE_INTERNAL_PROGRAM;
+ pluginPreset->presetName = newCharString();
+ pluginPreset->compatiblePluginTypes = 0;
+ pluginPresetSetCompatibleWith(pluginPreset, PLUGIN_TYPE_INTERNAL);
+
+ pluginPreset->openPreset = _openPluginPresetMock;
+ pluginPreset->loadPreset = _loadPluginPresetMock;
+ pluginPreset->freePresetData = _freePluginPresetMock;
+
+ extraData->isOpen = false;
+ extraData->isLoaded = false;
+ pluginPreset->extraData = extraData;
+
+ return pluginPreset;
+}
diff --git a/test/plugin/PluginPresetMock.h b/test/plugin/PluginPresetMock.h
new file mode 100644
index 0000000..70c9089
--- /dev/null
+++ b/test/plugin/PluginPresetMock.h
@@ -0,0 +1,15 @@
+#ifndef MrsWaston_PluginPresetMock_h
+#define MrsWaston_PluginPresetMock_h
+
+#include "base/Types.h"
+#include "plugin/PluginPreset.h"
+
+typedef struct {
+ boolByte isOpen;
+ boolByte isLoaded;
+} PluginPresetMockDataMembers;
+typedef PluginPresetMockDataMembers *PluginPresetMockData;
+
+PluginPreset newPluginPresetMock(void);
+
+#endif
diff --git a/test/plugin/PluginPresetTest.c b/test/plugin/PluginPresetTest.c
new file mode 100644
index 0000000..e16024f
--- /dev/null
+++ b/test/plugin/PluginPresetTest.c
@@ -0,0 +1,78 @@
+#include "unit/TestRunner.h"
+#include "plugin/PluginPreset.h"
+#include "PluginMock.h"
+
+const char *TEST_PRESET_FILENAME = "test.fxp";
+
+static int _testGuessPluginPresetType(void)
+{
+ CharString c = newCharStringWithCString(TEST_PRESET_FILENAME);
+ PluginPreset p = pluginPresetFactory(c);
+ assertIntEquals(PRESET_TYPE_FXP, p->presetType);
+ freePluginPreset(p);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testGuessPluginPresetTypeInvalid(void)
+{
+ CharString c = newCharStringWithCString("invalid");
+ PluginPreset p = pluginPresetFactory(c);
+ assertIsNull(p);
+ freePluginPreset(p);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNewObject(void)
+{
+ CharString c = newCharStringWithCString(TEST_PRESET_FILENAME);
+ PluginPreset p = pluginPresetFactory(c);
+ assertIntEquals(p->presetType, PRESET_TYPE_FXP);
+ assertCharStringEquals(TEST_PRESET_FILENAME, p->presetName);
+ freePluginPreset(p);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testIsPresetCompatibleWithPlugin(void)
+{
+ CharString c = newCharStringWithCString(TEST_PRESET_FILENAME);
+ PluginPreset p = pluginPresetFactory(c);
+ Plugin mockPlugin = newPluginMock();
+
+ pluginPresetSetCompatibleWith(p, PLUGIN_TYPE_INTERNAL);
+ assert(pluginPresetIsCompatibleWith(p, mockPlugin));
+
+ freePlugin(mockPlugin);
+ freeCharString(c);
+ freePluginPreset(p);
+ return 0;
+}
+
+static int _testIsPresetNotCompatibleWithPlugin(void)
+{
+ CharString c = newCharStringWithCString(TEST_PRESET_FILENAME);
+ PluginPreset p = pluginPresetFactory(c);
+ Plugin mockPlugin = newPluginMock();
+
+ pluginPresetSetCompatibleWith(p, PLUGIN_TYPE_VST_2X);
+ assertFalse(pluginPresetIsCompatibleWith(p, mockPlugin));
+
+ freePlugin(mockPlugin);
+ freeCharString(c);
+ freePluginPreset(p);
+ return 0;
+}
+
+TestSuite addPluginPresetTests(void);
+TestSuite addPluginPresetTests(void)
+{
+ TestSuite testSuite = newTestSuite("PluginPreset", NULL, NULL);
+ addTest(testSuite, "GuessPluginPresetType", _testGuessPluginPresetType);
+ addTest(testSuite, "GuessPluginPresetTypeInvalid", _testGuessPluginPresetTypeInvalid);
+ addTest(testSuite, "NewObject", _testNewObject);
+ addTest(testSuite, "IsPresetCompatibleWithPlugin", _testIsPresetCompatibleWithPlugin);
+ addTest(testSuite, "IsPresetNotCompatibleWithPlugin", _testIsPresetNotCompatibleWithPlugin);
+ return testSuite;
+}
diff --git a/test/plugin/PluginTest.c b/test/plugin/PluginTest.c
new file mode 100644
index 0000000..7c2a575
--- /dev/null
+++ b/test/plugin/PluginTest.c
@@ -0,0 +1,91 @@
+#include "unit/TestRunner.h"
+#include "plugin/Plugin.h"
+
+static int _testPluginFactory(void)
+{
+ CharString silence = newCharStringWithCString("mrs_silence");
+ CharString pluginRoot = newCharString();
+ Plugin p = pluginFactory(silence, pluginRoot);
+
+ assertNotNull(p);
+ assertIntEquals(PLUGIN_TYPE_INTERNAL, p->interfaceType);
+ assertCharStringEquals(silence->data, p->pluginName);
+
+ freeCharString(silence);
+ freeCharString(pluginRoot);
+ freePlugin(p);
+ return 0;
+}
+
+static int _testPluginFactoryInvalidPlugin(void)
+{
+ CharString invalid = newCharStringWithCString("invalid");
+ CharString pluginRoot = newCharString();
+ Plugin p = pluginFactory(invalid, pluginRoot);
+
+ assertIsNull(p);
+
+ freeCharString(invalid);
+ freeCharString(pluginRoot);
+ freePlugin(p);
+ return 0;
+}
+
+static int _testPluginFactoryNullPluginName(void)
+{
+ CharString pluginRoot = newCharString();
+ Plugin p = pluginFactory(NULL, pluginRoot);
+
+ assertIsNull(p);
+
+ freeCharString(pluginRoot);
+ freePlugin(p);
+ return 0;
+}
+
+static int _testPluginFactoryEmptyPluginName(void)
+{
+ CharString invalid = newCharString();
+ CharString pluginRoot = newCharString();
+ Plugin p = pluginFactory(invalid, pluginRoot);
+
+ assertIsNull(p);
+
+ freeCharString(invalid);
+ freeCharString(pluginRoot);
+ freePlugin(p);
+ return 0;
+}
+
+static int _testPluginFactoryNullRoot(void)
+{
+ CharString silence = newCharStringWithCString("mrs_silence");
+ Plugin p = pluginFactory(silence, NULL);
+
+ assertNotNull(p);
+ assertIntEquals(PLUGIN_TYPE_INTERNAL, p->interfaceType);
+ assertCharStringEquals(silence->data, p->pluginName);
+
+ freeCharString(silence);
+ freePlugin(p);
+ return 0;
+}
+
+static int _testFreeNullPlugin(void)
+{
+ freePlugin(NULL);
+ return 0;
+}
+
+TestSuite addPluginTests(void);
+TestSuite addPluginTests(void)
+{
+ TestSuite testSuite = newTestSuite("Plugin", NULL, NULL);
+ addTest(testSuite, "PluginFactory", _testPluginFactory);
+ addTest(testSuite, "PluginFactoryInvalidPlugin", _testPluginFactoryInvalidPlugin);
+ addTest(testSuite, "PluginFactoryNullPluginName", _testPluginFactoryNullPluginName);
+ addTest(testSuite, "PluginFactoryEmptyPluginName", _testPluginFactoryEmptyPluginName);
+ addTest(testSuite, "PluginFactoryNullRoot", _testPluginFactoryNullRoot);
+ addTest(testSuite, "FreeNullPlugin", _testFreeNullPlugin);
+ return testSuite;
+}
diff --git a/test/plugin/PluginVst2xIdTest.c b/test/plugin/PluginVst2xIdTest.c
new file mode 100644
index 0000000..a065a55
--- /dev/null
+++ b/test/plugin/PluginVst2xIdTest.c
@@ -0,0 +1,87 @@
+#include "unit/TestRunner.h"
+#include "plugin/PluginVst2xId.h"
+
+static int _testNewPluginVst2xId(void)
+{
+ PluginVst2xId id = newPluginVst2xId();
+ assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, id->id);
+ assertCharStringEquals(PLUGIN_VST2X_ID_UNKNOWN, id->idString);
+ freePluginVst2xId(id);
+ return 0;
+}
+
+static int _testNewPluginVst2xIdWithIntId(void)
+{
+ PluginVst2xId id = newPluginVst2xIdWithId(0x61626364);
+ assertUnsignedLongEquals(0x61626364l, id->id);
+ assertCharStringEquals("abcd", id->idString);
+ freePluginVst2xId(id);
+ return 0;
+}
+
+static int _testNewPluginVst2xIdWithZeroIntId(void)
+{
+ PluginVst2xId id = newPluginVst2xIdWithId(0);
+ assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, id->id);
+ assertCharStringEquals(EMPTY_STRING, id->idString);
+ freePluginVst2xId(id);
+ return 0;
+}
+
+static int _testNewPluginVst2xIdWithStringId(void)
+{
+ CharString c = newCharStringWithCString("abcd");
+ PluginVst2xId id = newPluginVst2xIdWithStringId(c);
+ assertUnsignedLongEquals(0x61626364l, id->id);
+ assertCharStringEquals(c->data, id->idString);
+ freePluginVst2xId(id);
+ freeCharString(c);
+ return 0;
+}
+
+static int _testNewPluginVst2xIdWithEmptyStringId(void)
+{
+ CharString empty = newCharStringWithCString(EMPTY_STRING);
+ PluginVst2xId id = newPluginVst2xIdWithStringId(empty);
+ assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, id->id);
+ assertCharStringEquals(PLUGIN_VST2X_ID_UNKNOWN, id->idString);
+ freePluginVst2xId(id);
+ freeCharString(empty);
+ return 0;
+}
+
+static int _testNewPluginVst2xIdWithNullStringId(void)
+{
+ PluginVst2xId id = newPluginVst2xIdWithStringId(NULL);
+ assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, id->id);
+ assertCharStringEquals(PLUGIN_VST2X_ID_UNKNOWN, id->idString);
+ freePluginVst2xId(id);
+ return 0;
+}
+
+static int _testNewPluginVst2xIdWithInvalidStringId(void)
+{
+ CharString c = newCharStringWithCString("a");
+ PluginVst2xId id = newPluginVst2xIdWithStringId(c);
+ assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, id->id);
+ assertCharStringEquals(PLUGIN_VST2X_ID_UNKNOWN, id->idString);
+ freePluginVst2xId(id);
+ freeCharString(c);
+ return 0;
+}
+
+TestSuite addPluginVst2xIdTests(void);
+TestSuite addPluginVst2xIdTests(void)
+{
+ TestSuite testSuite = newTestSuite("PluginVst2xId", NULL, NULL);
+
+ addTest(testSuite, "NewPluginVst2xId", _testNewPluginVst2xId);
+ addTest(testSuite, "NewPluginVst2xIdWithIntId", _testNewPluginVst2xIdWithIntId);
+ addTest(testSuite, "NewPluginVst2xIdWithZeroIntId", _testNewPluginVst2xIdWithZeroIntId);
+ addTest(testSuite, "NewPluginVst2xIdWithStringId", _testNewPluginVst2xIdWithStringId);
+ addTest(testSuite, "NewPluginVst2xIdWithNullStringId", _testNewPluginVst2xIdWithNullStringId);
+ addTest(testSuite, "NewPluginVst2xIdWithEmptyStringId", _testNewPluginVst2xIdWithEmptyStringId);
+ addTest(testSuite, "NewPluginVst2xIdWithInvalidStringId", _testNewPluginVst2xIdWithInvalidStringId);
+
+ return testSuite;
+}
diff --git a/test/time/AudioClockTest.c b/test/time/AudioClockTest.c
new file mode 100644
index 0000000..8f29a8c
--- /dev/null
+++ b/test/time/AudioClockTest.c
@@ -0,0 +1,82 @@
+#include "unit/TestRunner.h"
+#include "time/AudioClock.h"
+
+static const unsigned long kAudioClockTestBlocksize = 256;
+
+static void _audioClockTestSetup(void)
+{
+ initAudioClock();
+}
+
+static void _audioClockTestTeardown(void)
+{
+ freeAudioClock(getAudioClock());
+}
+
+static int _testInitAudioClock(void)
+{
+ AudioClock audioClock = getAudioClock();
+ assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, audioClock->currentFrame);
+ assertFalse(audioClock->isPlaying);
+ assertFalse(audioClock->transportChanged);
+ return 0;
+}
+
+static int _testAdvanceAudioClock(void)
+{
+ AudioClock audioClock = getAudioClock();
+ advanceAudioClock(audioClock, kAudioClockTestBlocksize);
+ assertUnsignedLongEquals(kAudioClockTestBlocksize, audioClock->currentFrame);
+ assert(audioClock->isPlaying);
+ assert(audioClock->transportChanged);
+ return 0;
+}
+
+static int _testStopAudioClock(void)
+{
+ AudioClock audioClock = getAudioClock();
+ advanceAudioClock(audioClock, kAudioClockTestBlocksize);
+ audioClockStop(audioClock);
+ assertFalse(audioClock->isPlaying);
+ assert(audioClock->transportChanged)
+ return 0;
+}
+
+static int _testRestartAudioClock(void)
+{
+ AudioClock audioClock = getAudioClock();
+ advanceAudioClock(audioClock, kAudioClockTestBlocksize);
+ audioClockStop(audioClock);
+ advanceAudioClock(audioClock, kAudioClockTestBlocksize);
+ assert(audioClock->isPlaying);
+ assert(audioClock->transportChanged);
+ assertUnsignedLongEquals(kAudioClockTestBlocksize * 2, audioClock->currentFrame);
+ return 0;
+}
+
+static int _testAdvanceClockMulitpleTimes(void)
+{
+ AudioClock audioClock = getAudioClock();
+ int i;
+
+ for (i = 0; i < 100; i++) {
+ advanceAudioClock(audioClock, kAudioClockTestBlocksize);
+ }
+
+ assert(audioClock->isPlaying);
+ assertFalse(audioClock->transportChanged);
+ assertUnsignedLongEquals(kAudioClockTestBlocksize * 100, audioClock->currentFrame);
+ return 0;
+}
+
+TestSuite addAudioClockTests(void);
+TestSuite addAudioClockTests(void)
+{
+ TestSuite testSuite = newTestSuite("AudioClock", _audioClockTestSetup, _audioClockTestTeardown);
+ addTest(testSuite, "Initialization", _testInitAudioClock);
+ addTest(testSuite, "AdvanceClock", _testAdvanceAudioClock);
+ addTest(testSuite, "StopClock", _testStopAudioClock);
+ addTest(testSuite, "RestartClock", _testRestartAudioClock);
+ addTest(testSuite, "MultipleAdvance", _testAdvanceClockMulitpleTimes);
+ return testSuite;
+}
diff --git a/test/time/TaskTimerTest.c b/test/time/TaskTimerTest.c
new file mode 100644
index 0000000..10ae41e
--- /dev/null
+++ b/test/time/TaskTimerTest.c
@@ -0,0 +1,279 @@
+#include <math.h>
+
+#include "unit/TestRunner.h"
+#include "time/TaskTimer.h"
+
+#define SLEEP_DURATION_MS 10.0
+// Timer testing is a bit unreliable, so we just check to see that each sleep
+// call (see below) is recorded off no more than this amount of milliseconds.
+#define MAX_TIMER_TOLERANCE_MS 1.5f
+#define TEST_COMPONENT_NAME "component"
+#define TEST_SUBCOMPONENT_NAME "subcomponent"
+
+// Keep a static task timer which can easily be destroyed in the teardown. The
+// reason for this is that when running the test suite in valgrind, the timing
+// tests often fail, which will cuase the suite to leak. Having failed timing
+// tests is probably ok when running in valgrind, but leaking memory isn't, as
+// that's the entire point of running it there. :)
+static TaskTimer _testTaskTimer;
+
+static void _testTaskTimerSetup(void)
+{
+ _testTaskTimer = newTaskTimerWithCString(TEST_COMPONENT_NAME, TEST_SUBCOMPONENT_NAME);
+}
+
+static void _testTaskTimerTeardown(void)
+{
+ freeTaskTimer(_testTaskTimer);
+}
+
+static int _testNewTaskTimer(void)
+{
+ CharString c = newCharStringWithCString(TEST_COMPONENT_NAME);
+ TaskTimer t = newTaskTimer(c, TEST_SUBCOMPONENT_NAME);
+
+ assert(t->enabled);
+ assertCharStringEquals(TEST_COMPONENT_NAME, t->component);
+ assertCharStringEquals(TEST_SUBCOMPONENT_NAME, t->subcomponent);
+ assertDoubleEquals(0.0, t->totalTaskTime, TEST_DEFAULT_TOLERANCE);
+
+ freeCharString(c);
+ freeTaskTimer(t);
+ return 0;
+}
+
+static int _testNewObjectWithEmptyComponent(void)
+{
+ CharString c = newCharStringWithCString(EMPTY_STRING);
+ TaskTimer t = newTaskTimer(c, TEST_SUBCOMPONENT_NAME);
+
+ assertNotNull(t);
+ assertCharStringEquals(EMPTY_STRING, t->component);
+ assertCharStringEquals(TEST_SUBCOMPONENT_NAME, t->subcomponent);
+
+ freeCharString(c);
+ freeTaskTimer(t);
+ return 0;
+}
+
+static int _testNewObjectWithEmptySubcomponent(void)
+{
+ CharString c = newCharStringWithCString(TEST_COMPONENT_NAME);
+ TaskTimer t = newTaskTimer(c, EMPTY_STRING);
+
+ assertNotNull(t);
+ assertCharStringEquals(TEST_COMPONENT_NAME, t->component);
+ assertCharStringEquals(EMPTY_STRING, t->subcomponent);
+
+ freeCharString(c);
+ freeTaskTimer(t);
+ return 0;
+}
+
+static int _testNewObjectWithNullComponent(void)
+{
+ TaskTimer t = newTaskTimer(NULL, NULL);
+
+ assertNotNull(t);
+ assertCharStringEquals(EMPTY_STRING, t->component);
+ assertCharStringEquals(EMPTY_STRING, t->subcomponent);
+
+ freeTaskTimer(t);
+ return 0;
+}
+
+static int _testNewObjectWithNullComponentCString(void)
+{
+ TaskTimer t = newTaskTimerWithCString(NULL, NULL);
+
+ assertNotNull(t);
+ assertCharStringEquals(EMPTY_STRING, t->component);
+ assertCharStringEquals(EMPTY_STRING, t->subcomponent);
+
+ freeTaskTimer(t);
+ return 0;
+}
+
+static int _testNewObjectWithNullSubcomponent(void)
+{
+ CharString c = newCharStringWithCString(TEST_COMPONENT_NAME);
+ TaskTimer t = newTaskTimer(c, NULL);
+
+ assertNotNull(t);
+ assertCharStringEquals(TEST_COMPONENT_NAME, t->component);
+ assertCharStringEquals(EMPTY_STRING, t->subcomponent);
+
+ freeCharString(c);
+ freeTaskTimer(t);
+ return 0;
+}
+
+static int _testNewObjectWithCStrings(void)
+{
+ TaskTimer t = newTaskTimerWithCString(TEST_COMPONENT_NAME, TEST_SUBCOMPONENT_NAME);
+
+ assert(t->enabled);
+ assertCharStringEquals(TEST_COMPONENT_NAME, t->component);
+ assertCharStringEquals(TEST_SUBCOMPONENT_NAME, t->subcomponent);
+ assertDoubleEquals(0.0, t->totalTaskTime, TEST_DEFAULT_TOLERANCE);
+
+ freeTaskTimer(t);
+ return 0;
+}
+
+static void _testSleep(void)
+{
+ taskTimerSleep(SLEEP_DURATION_MS);
+}
+
+static int _testTaskTimerDuration(void)
+{
+ double elapsedTime = 0.0;
+ taskTimerStart(_testTaskTimer);
+ _testSleep();
+ elapsedTime = taskTimerStop(_testTaskTimer);
+ assertTimeEquals(SLEEP_DURATION_MS, _testTaskTimer->totalTaskTime, MAX_TIMER_TOLERANCE_MS);
+ assertTimeEquals(SLEEP_DURATION_MS, elapsedTime, MAX_TIMER_TOLERANCE_MS);
+ return 0;
+}
+
+static int _testTaskTimerDurationMultipleTimes(void)
+{
+ double elapsedTime = 0.0;
+ int i;
+
+ for (i = 0; i < 5; i++) {
+ taskTimerStart(_testTaskTimer);
+ _testSleep();
+ elapsedTime = taskTimerStop(_testTaskTimer);
+ assertTimeEquals(SLEEP_DURATION_MS, elapsedTime, MAX_TIMER_TOLERANCE_MS);
+ elapsedTime = 0.0;
+ }
+
+ assertTimeEquals(5.0 * SLEEP_DURATION_MS, _testTaskTimer->totalTaskTime, MAX_TIMER_TOLERANCE_MS * 5.0);
+
+ return 0;
+}
+
+static int _testTaskTimerCallStartTwice(void)
+{
+ taskTimerStart(_testTaskTimer);
+ taskTimerStart(_testTaskTimer);
+ _testSleep();
+ taskTimerStop(_testTaskTimer);
+ assertTimeEquals(SLEEP_DURATION_MS, _testTaskTimer->totalTaskTime, MAX_TIMER_TOLERANCE_MS);
+ return 0;
+}
+
+static int _testTaskTimerCallStopTwice(void)
+{
+ taskTimerStart(_testTaskTimer);
+ _testSleep();
+ taskTimerStop(_testTaskTimer);
+ taskTimerStop(_testTaskTimer);
+ assertTimeEquals(SLEEP_DURATION_MS, _testTaskTimer->totalTaskTime, MAX_TIMER_TOLERANCE_MS);
+ return 0;
+}
+
+static int _testCallStopBeforeStart(void)
+{
+ taskTimerStop(_testTaskTimer);
+ taskTimerStart(_testTaskTimer);
+ _testSleep();
+ taskTimerStop(_testTaskTimer);
+ assertTimeEquals(SLEEP_DURATION_MS, _testTaskTimer->totalTaskTime, MAX_TIMER_TOLERANCE_MS);
+ return 0;
+}
+
+static int _testHumanReadableTimeMs(void)
+{
+ CharString s;
+ _testTaskTimer->totalTaskTime = 230;
+ s = taskTimerHumanReadbleString(_testTaskTimer);
+ assertCharStringEquals("230ms", s);
+ freeCharString(s);
+ return 0;
+}
+
+static int _testHumanReadableTimeSec(void)
+{
+ CharString s;
+ // 23 seconds
+ _testTaskTimer->totalTaskTime = 23000;
+ s = taskTimerHumanReadbleString(_testTaskTimer);
+ assertCharStringEquals("23sec", s);
+ freeCharString(s);
+ return 0;
+}
+
+static int _testHumanReadableTimeMinSec(void)
+{
+ CharString s;
+ // 10 minutes, 23 seconds
+ _testTaskTimer->totalTaskTime = 600000 + 23000;
+ s = taskTimerHumanReadbleString(_testTaskTimer);
+ assertCharStringEquals("10:23sec", s);
+ freeCharString(s);
+ return 0;
+}
+
+static int _testHumanReadableTimeHoursMinSec(void)
+{
+ CharString s;
+ // 2 hours, 10 minutes, 23 seconds
+ _testTaskTimer->totalTaskTime = (1000 * 60 * 60 * 2) + 600000 + 23000;
+ s = taskTimerHumanReadbleString(_testTaskTimer);
+ assertCharStringEquals("2:10:23sec", s);
+ freeCharString(s);
+ return 0;
+}
+
+static int _testHumanReadableTimeNotStarted(void)
+{
+ CharString s = taskTimerHumanReadbleString(_testTaskTimer);
+ assertCharStringEquals("0ms", s);
+ freeCharString(s);
+ return 0;
+}
+
+static int _testSleepMilliseconds(void)
+{
+ double elapsedTime;
+ TaskTimer t = newTaskTimerWithCString("test", "test");
+ taskTimerStart(t);
+ taskTimerSleep(12);
+ elapsedTime = taskTimerStop(t);
+ assertTimeEquals(12, elapsedTime, 0.1);
+ freeTaskTimer(t);
+ return 0;
+}
+
+TestSuite addTaskTimerTests(void);
+TestSuite addTaskTimerTests(void)
+{
+ TestSuite testSuite = newTestSuite("TaskTimer", _testTaskTimerSetup, _testTaskTimerTeardown);
+ addTest(testSuite, "NewObject", _testNewTaskTimer);
+ addTest(testSuite, "NewObjectWithEmptyComponent", _testNewObjectWithEmptyComponent);
+ addTest(testSuite, "NewObjectWithEmptySubcomponent", _testNewObjectWithEmptySubcomponent);
+ addTest(testSuite, "NewObjectWithNullComponent", _testNewObjectWithNullComponent);
+ addTest(testSuite, "NewObjectWithNullComponentCString", _testNewObjectWithNullComponentCString);
+ addTest(testSuite, "NewObjectWithNullSubcomponent", _testNewObjectWithNullSubcomponent);
+ addTest(testSuite, "NewObjectWithCStrings", _testNewObjectWithCStrings);
+
+ addTest(testSuite, "TaskDuration", _testTaskTimerDuration);
+ addTest(testSuite, "TaskDurationMultipleTimes", _testTaskTimerDurationMultipleTimes);
+
+ addTest(testSuite, "CallStartTwice", _testTaskTimerCallStartTwice);
+ addTest(testSuite, "CallStopTwice", _testTaskTimerCallStopTwice);
+ addTest(testSuite, "CallStartTwice", _testTaskTimerCallStartTwice);
+ addTest(testSuite, "CallStopBeforeStart", _testCallStopBeforeStart);
+
+ addTest(testSuite, "HumanReadableTimeMs", _testHumanReadableTimeMs);
+ addTest(testSuite, "HumanReadableTimeSec", _testHumanReadableTimeSec);
+ addTest(testSuite, "HumanReadableTimeMinSec", _testHumanReadableTimeMinSec);
+ addTest(testSuite, "HumanReadableTimeHoursMinSec", _testHumanReadableTimeHoursMinSec);
+ addTest(testSuite, "HumanReadableTimeNotStarted", _testHumanReadableTimeNotStarted);
+
+ addTest(testSuite, "SleepMilliseconds", _testSleepMilliseconds);
+ return testSuite;
+}
diff --git a/test/unit/ApplicationRunner.c b/test/unit/ApplicationRunner.c
new file mode 100644
index 0000000..54b4812
--- /dev/null
+++ b/test/unit/ApplicationRunner.c
@@ -0,0 +1,297 @@
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "ApplicationRunner.h"
+#include "base/File.h"
+#include "base/PlatformInfo.h"
+#include "analysis/AnalyzeFile.h"
+
+const char *kDefaultTestOutputFileType = "pcm";
+static const char *kApplicationRunnerOutputFolder = "out";
+static const int kApplicationRunnerWaitTimeoutInMs = 1000;
+
+CharString buildTestArgumentString(const char *arguments, ...)
+{
+ CharString formattedArguments;
+ va_list argumentList;
+ va_start(argumentList, arguments);
+ formattedArguments = newCharStringWithCapacity(kCharStringLengthLong);
+ vsnprintf(formattedArguments->data, formattedArguments->capacity, arguments, argumentList);
+ va_end(argumentList);
+ return formattedArguments;
+}
+
+CharString getTestResourceFilename(const char *resourcesPath, const char *resourceType, const char *resourceName)
+{
+ CharString filename = newCharString();
+ snprintf(filename->data, filename->capacity, "%s%c%s%c%s",
+ resourcesPath, PATH_DELIMITER, resourceType, PATH_DELIMITER, resourceName);
+ return filename;
+}
+
+CharString getTestOutputFilename(const char *testName, const char *fileExtension)
+{
+ CharString filename = newCharString();
+ char *space;
+ char *spacePtr;
+
+ snprintf(filename->data, filename->capacity, "%s%c%s.%s",
+ kApplicationRunnerOutputFolder, PATH_DELIMITER, testName, fileExtension);
+ spacePtr = filename->data;
+
+ do {
+ space = strchr(spacePtr + 1, ' ');
+
+ if (space == NULL || (unsigned int)(space - filename->data) > strlen(filename->data)) {
+ break;
+ } else {
+ *space = '-';
+ }
+ } while (true);
+
+ return filename;
+}
+
+static CharString _getTestPluginResourcesPath(const char *resourcesPath)
+{
+ CharString pluginRoot = newCharString();
+ PlatformInfo platform = newPlatformInfo();
+ snprintf(pluginRoot->data, pluginRoot->capacity, "%s%cvst%c%s",
+ resourcesPath, PATH_DELIMITER, PATH_DELIMITER, platform->shortName->data);
+ freePlatformInfo(platform);
+ return pluginRoot;
+}
+
+static CharString _getDefaultArguments(TestEnvironment testEnvironment, const char *testName, const char *outputFilename)
+{
+ CharString outString = newCharStringWithCapacity(kCharStringLengthLong);
+ CharString logfileName = getTestOutputFilename(testName, "txt");
+ CharString resourcesPath = _getTestPluginResourcesPath(testEnvironment->resourcesPath);
+ snprintf(outString->data, outString->capacity,
+ "--log-file \"%s\" --verbose --output \"%s\" --plugin-root \"%s\"",
+ logfileName->data, outputFilename, resourcesPath->data);
+ freeCharString(logfileName);
+ freeCharString(resourcesPath);
+ return outString;
+}
+
+static void _removeOutputFile(const char *argument)
+{
+ CharString outputFilename = newCharStringWithCString(argument);
+ File outputFile = newFileWithPath(outputFilename);
+
+ if (fileExists(outputFile)) {
+ fileRemove(outputFile);
+ }
+
+ freeCharString(outputFilename);
+ freeFile(outputFile);
+}
+
+static void _removeOutputFiles(const char *testName)
+{
+ // Remove all possible output files generated during testing
+ CharString outputFilename;
+
+ outputFilename = getTestOutputFilename(testName, "aif");
+ _removeOutputFile(outputFilename->data);
+ freeCharString(outputFilename);
+ outputFilename = getTestOutputFilename(testName, "flac");
+ _removeOutputFile(outputFilename->data);
+ freeCharString(outputFilename);
+ outputFilename = getTestOutputFilename(testName, "pcm");
+ _removeOutputFile(outputFilename->data);
+ freeCharString(outputFilename);
+ outputFilename = getTestOutputFilename(testName, "wav");
+ _removeOutputFile(outputFilename->data);
+ freeCharString(outputFilename);
+ outputFilename = getTestOutputFilename(testName, "txt");
+ _removeOutputFile(outputFilename->data);
+ freeCharString(outputFilename);
+}
+
+static const char *_getResultCodeString(const int resultCode)
+{
+ switch (resultCode) {
+ case RETURN_CODE_SUCCESS:
+ return "Success";
+
+ case RETURN_CODE_NOT_RUN:
+ return "Not run";
+
+ case RETURN_CODE_INVALID_ARGUMENT:
+ return "Invalid argument";
+
+ case RETURN_CODE_MISSING_REQUIRED_OPTION:
+ return "Missing required option";
+
+ case RETURN_CODE_IO_ERROR:
+ return "I/O error";
+
+ case RETURN_CODE_PLUGIN_ERROR:
+ return "Plugin error";
+
+ case RETURN_CODE_INVALID_PLUGIN_CHAIN:
+ return "Invalid plugin chain";
+
+ case RETURN_CODE_UNSUPPORTED_FEATURE:
+ return "Unsupported feature";
+
+ case RETURN_CODE_INTERNAL_ERROR:
+ return "Internal error";
+
+ case RETURN_CODE_SIGNAL:
+ return "Caught signal";
+
+ default:
+ return "Unknown";
+ }
+}
+
+void runIntegrationTest(const TestEnvironment testEnvironment,
+ const char *testName, CharString testArguments,
+ ReturnCodes expectedResultCode, const char *outputFileType)
+{
+ int result = -1;
+ ReturnCodes resultCode = (ReturnCodes)result;
+ CharString arguments = newCharStringWithCapacity(kCharStringLengthLong);
+ CharString defaultArguments;
+ CharString failedAnalysisFunctionName = newCharString();
+ ChannelCount failedAnalysisChannel;
+ SampleCount failedAnalysisFrame;
+ File outputFolder = NULL;
+ CharString outputFilename = getTestOutputFilename(testName,
+ outputFileType == NULL ? kDefaultTestOutputFileType : outputFileType);
+
+#if WINDOWS
+ STARTUPINFOA startupInfo;
+ PROCESS_INFORMATION processInfo;
+#endif
+
+ // Remove files from a previous test run
+ outputFolder = newFileWithPathCString(kApplicationRunnerOutputFolder);
+
+ if (fileExists(outputFolder)) {
+ _removeOutputFiles(testName);
+ } else {
+ fileCreate(outputFolder, kFileTypeDirectory);
+ }
+
+ // Create the command line argument
+ charStringAppendCString(arguments, "\"");
+ charStringAppendCString(arguments, testEnvironment->applicationPath);
+ charStringAppendCString(arguments, "\"");
+ charStringAppendCString(arguments, " ");
+ defaultArguments = _getDefaultArguments(testEnvironment, testName, outputFilename->data);
+ charStringAppend(arguments, defaultArguments);
+ charStringAppendCString(arguments, " ");
+ charStringAppend(arguments, testArguments);
+
+ if (!testEnvironment->results->onlyPrintFailing) {
+ printTestName(testName);
+ }
+
+#if WINDOWS
+ memset(&startupInfo, 0, sizeof(startupInfo));
+ memset(&processInfo, 0, sizeof(processInfo));
+ startupInfo.cb = sizeof(startupInfo);
+ result = CreateProcessA((LPCSTR)(testEnvironment->applicationPath), (LPSTR)(arguments->data),
+ 0, 0, false, CREATE_DEFAULT_ERROR_MODE, 0, 0, &startupInfo, &processInfo);
+
+ if (result) {
+ // TODO: Check return codes for these calls
+ WaitForSingleObject(processInfo.hProcess, kApplicationRunnerWaitTimeoutInMs);
+ GetExitCodeProcess(processInfo.hProcess, (LPDWORD)&resultCode);
+ CloseHandle(processInfo.hProcess);
+ CloseHandle(processInfo.hThread);
+ } else {
+ logCritical("Could not launch process, got error %s", stringForLastError(GetLastError()));
+ return;
+ }
+
+#else
+ result = system(arguments->data);
+ resultCode = (ReturnCodes)WEXITSTATUS(result);
+#endif
+
+ if (resultCode == RETURN_CODE_FORK_FAILED ||
+ resultCode == RETURN_CODE_SHELL_FAILED ||
+ resultCode == RETURN_CODE_LAUNCH_FAILED_OTHER) {
+ if (testEnvironment->results->onlyPrintFailing) {
+ printTestName(testName);
+ }
+
+ printTestFail();
+ logCritical("Could not launch shell, got return code %d\n\
+Please check the executable path specified in the --mrswatson-path argument.",
+ resultCode);
+ testEnvironment->results->numFail++;
+ } else if (resultCode == expectedResultCode) {
+ if (outputFileType != NULL) {
+ if (analyzeFile(outputFilename->data, failedAnalysisFunctionName,
+ &failedAnalysisChannel, &failedAnalysisFrame)) {
+ testEnvironment->results->numSuccess++;
+
+ if (!testEnvironment->results->keepFiles) {
+ _removeOutputFiles(testName);
+ }
+
+ if (!testEnvironment->results->onlyPrintFailing) {
+ printTestSuccess();
+ }
+ } else {
+ if (testEnvironment->results->onlyPrintFailing) {
+ printTestName(testName);
+ }
+
+ fprintf(stderr, "Audio analysis check for %s failed at channel %d, frame %lu. ",
+ failedAnalysisFunctionName->data, failedAnalysisChannel, failedAnalysisFrame);
+ printTestFail();
+ testEnvironment->results->numFail++;
+ }
+ } else {
+ testEnvironment->results->numSuccess++;
+
+ if (!testEnvironment->results->keepFiles) {
+ _removeOutputFiles(testName);
+ }
+
+ if (!testEnvironment->results->onlyPrintFailing) {
+ printTestSuccess();
+ }
+ }
+ } else {
+ if (testEnvironment->results->onlyPrintFailing) {
+ printTestName(testName);
+ }
+
+ fprintf(stderr, "Expected result code %d (%s), got %d (%s). ",
+ expectedResultCode, _getResultCodeString(expectedResultCode),
+ resultCode, _getResultCodeString(resultCode));
+ printTestFail();
+ testEnvironment->results->numFail++;
+ }
+
+ freeCharString(outputFilename);
+ freeCharString(arguments);
+ freeCharString(defaultArguments);
+ freeCharString(testArguments);
+ freeCharString(failedAnalysisFunctionName);
+}
+
+void freeTestEnvironment(TestEnvironment self)
+{
+ if (self != NULL) {
+ freeTestSuite(self->results);
+ free(self);
+ }
+}
+
+TestEnvironment newTestEnvironment(char *applicationPath, char *resourcesPath)
+{
+ TestEnvironment testEnvironment = (TestEnvironment)malloc(sizeof(TestEnvironmentMembers));
+ testEnvironment->applicationPath = applicationPath;
+ testEnvironment->resourcesPath = resourcesPath;
+ testEnvironment->results = newTestSuite("Results", NULL, NULL);
+ return testEnvironment;
+}
diff --git a/test/unit/ApplicationRunner.h b/test/unit/ApplicationRunner.h
new file mode 100644
index 0000000..7682f01
--- /dev/null
+++ b/test/unit/ApplicationRunner.h
@@ -0,0 +1,39 @@
+#ifndef MrsWatson_ApplicationRunner_h
+#define MrsWatson_ApplicationRunner_h
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include "unit/TestRunner.h"
+#include "base/LinkedList.h"
+#include "logging/EventLogger.h"
+#include "base/CharString.h"
+#include "MrsWatson.h"
+
+extern const char *kDefaultTestOutputFileType;
+
+typedef struct {
+ int currentIndex;
+ char **outArray;
+} ArgumentsCopyData;
+
+typedef struct {
+ char *applicationPath;
+ char *resourcesPath;
+ TestSuite results;
+} TestEnvironmentMembers;
+typedef TestEnvironmentMembers *TestEnvironment;
+TestEnvironment newTestEnvironment(char *applicationPath, char *resourcesPath);
+
+void runIntegrationTest(const TestEnvironment testEnvironment,
+ const char *testName, CharString testArguments,
+ ReturnCodes expectedResultCode, const char *outputFileType);
+
+CharString buildTestArgumentString(const char *arguments, ...);
+CharString getTestResourceFilename(const char *resourcesPath, const char *resourceType, const char *resourceName);
+CharString getTestOutputFilename(const char *testName, const char *fileExtension);
+
+void freeTestEnvironment(TestEnvironment testEnvironment);
+
+#endif
diff --git a/test/unit/IntegrationTests.c b/test/unit/IntegrationTests.c
new file mode 100644
index 0000000..b2180ba
--- /dev/null
+++ b/test/unit/IntegrationTests.c
@@ -0,0 +1,175 @@
+#include "ApplicationRunner.h"
+
+extern void _printTestSummary(int testsRun, int testsPassed, int testsFailed, int testsSkipped);
+
+void runIntegrationTests(TestEnvironment environment);
+void runIntegrationTests(TestEnvironment environment)
+{
+ // Test resource paths
+ const char *resourcesPath = environment->resourcesPath;
+ CharString _a440_mono_pcm = getTestResourceFilename(resourcesPath, "audio", "a440-mono.pcm");
+ CharString _a440_stereo_aiff = getTestResourceFilename(resourcesPath, "audio", "a440-stereo.aif");
+ CharString _a440_stereo_flac = getTestResourceFilename(resourcesPath, "audio", "a440-stereo.flac");
+ CharString _a440_stereo_pcm = getTestResourceFilename(resourcesPath, "audio", "a440-stereo.pcm");
+ CharString _a440_stereo_wav = getTestResourceFilename(resourcesPath, "audio", "a440-stereo.wav");
+ CharString _c_scale_mid = getTestResourceFilename(resourcesPath, "midi", "c-scale.mid");
+ CharString _again_test_fxp = getTestResourceFilename(resourcesPath, "presets", "again-test.fxp");
+ const char *a440_stereo_aiff = _a440_stereo_aiff->data;
+ const char *a440_stereo_flac = _a440_stereo_flac->data;
+ const char *a440_mono_pcm = _a440_mono_pcm->data;
+ const char *a440_stereo_pcm = _a440_stereo_pcm->data;
+ const char *a440_stereo_wav = _a440_stereo_wav->data;
+ const char *c_scale_mid = _c_scale_mid->data;
+ const char *again_test_fxp = _again_test_fxp->data;
+
+ // Basic non-processing operations
+ runIntegrationTest(environment, "List plugins",
+ newCharStringWithCString("--list-plugins"),
+ RETURN_CODE_NOT_RUN, NULL);
+ runIntegrationTest(environment, "List file types",
+ newCharStringWithCString("--list-file-types"),
+ RETURN_CODE_NOT_RUN, NULL);
+ runIntegrationTest(environment, "Invalid argument",
+ newCharStringWithCString("--invalid"),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+
+ // Invalid configurations
+ runIntegrationTest(environment, "Run with no plugins",
+ newCharString(),
+ RETURN_CODE_INVALID_PLUGIN_CHAIN, NULL);
+ runIntegrationTest(environment, "Effect with no input source",
+ newCharStringWithCString("--plugin again"),
+ RETURN_CODE_MISSING_REQUIRED_OPTION, NULL);
+ runIntegrationTest(environment, "Instrument with no MIDI source",
+ newCharStringWithCString("--plugin vstxsynth"),
+ RETURN_CODE_MISSING_REQUIRED_OPTION, NULL);
+ runIntegrationTest(environment, "Plugin chain with instrument not at head",
+ buildTestArgumentString("--plugin \"again;vstxsynth\" --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_INVALID_PLUGIN_CHAIN, NULL);
+ runIntegrationTest(environment, "Plugin with invalid preset",
+ buildTestArgumentString("--plugin \"again,invalid.fxp\" --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Preset for wrong plugin",
+ buildTestArgumentString("--plugin \"vstxsynth,%s\" --midi-file \"%s\"", again_test_fxp, c_scale_mid),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid parameter",
+ buildTestArgumentString("--plugin again --input \"%s\" --parameter 1,0.5", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid time signature",
+ buildTestArgumentString("--plugin again --input \"%s\" --time-signature invalid", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid tempo",
+ buildTestArgumentString("--plugin again --input \"%s\" --tempo 0", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid blocksize",
+ buildTestArgumentString("--plugin again --input \"%s\" --blocksize 0", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid bit depth",
+ buildTestArgumentString("--plugin again --input \"%s\" --bit-depth 5", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid channel count",
+ buildTestArgumentString("--plugin again --input \"%s\" --channels 0", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Set invalid sample rate",
+ buildTestArgumentString("--plugin again --input \"%s\" --sample-rate 0", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+
+ // Audio file types
+ runIntegrationTest(environment, "Read PCM file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Write PCM file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, "pcm");
+ runIntegrationTest(environment, "Read WAV file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_wav),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Write WAV file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, "wav");
+
+#if USE_AUDIOFILE
+ runIntegrationTest(environment, "Read AIFF file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_aiff),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Write AIFF file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, "aif");
+#endif
+
+#if USE_FLAC
+ runIntegrationTest(environment, "Read FLAC file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_flac),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Write FLAC file",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, "flac");
+#endif
+
+ // Configuration tests
+ runIntegrationTest(environment, "Read mono input source",
+ buildTestArgumentString("--plugin again --input \"%s\" --channels 1", a440_mono_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Read with user-defined sample rate",
+ buildTestArgumentString("--plugin again --input \"%s\" --sample-rate 48000", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Read with user-defined blocksize",
+ buildTestArgumentString("--plugin again --input \"%s\" --blocksize 128", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Set parameter",
+ buildTestArgumentString("--plugin again --input \"%s\" --parameter 0,0.5", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Set time signature",
+ buildTestArgumentString("--plugin again --input \"%s\" --time-signature 3/4", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+
+ // Internal plugins
+ runIntegrationTest(environment, "Process with internal limiter",
+ buildTestArgumentString("--plugin mrs_limiter --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Process with internal gain plugin",
+ buildTestArgumentString("--plugin mrs_gain --parameter 0,0.5 --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Process with internal gain plugin and invalid parameter",
+ buildTestArgumentString("--plugin mrs_gain --parameter 1,0.5 --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_INVALID_ARGUMENT, NULL);
+ runIntegrationTest(environment, "Process with internal passthru plugin",
+ buildTestArgumentString("--plugin mrs_passthru --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+#if 0
+ // This test case works, but fails the analysis check for silence (obviously).
+ // It will remain disabled until we have a smarter way to specify which analysis
+ // functions should be run for each integration test.
+ runIntegrationTest(environment, "Process with silence generator",
+ newCharStringWithCString("--plugin mrs_silence --max-time 1000"),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+#endif
+
+ // Plugin processing tests
+ runIntegrationTest(environment, "Process audio with again plugin",
+ buildTestArgumentString("--plugin again --input \"%s\"", a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Process MIDI with vstxsynth plugin",
+ buildTestArgumentString("--plugin vstxsynth --midi-file \"%s\"", c_scale_mid),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Process effect chain",
+ buildTestArgumentString("--plugin vstxsynth,again --midi-file \"%s\"", c_scale_mid),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Load FXP preset to VST",
+ buildTestArgumentString("--plugin \"again,%s\" --input \"%s\"", again_test_fxp, a440_stereo_pcm),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+ runIntegrationTest(environment, "Load internal program to VST",
+ buildTestArgumentString("--plugin vstxsynth,2 --midi-file \"%s\"", c_scale_mid),
+ RETURN_CODE_SUCCESS, kDefaultTestOutputFileType);
+
+ _printTestSummary(environment->results->numSuccess + environment->results->numFail + environment->results->numSkips,
+ environment->results->numSuccess, environment->results->numFail, environment->results->numSkips);
+
+ freeCharString(_a440_stereo_aiff);
+ freeCharString(_a440_stereo_flac);
+ freeCharString(_a440_mono_pcm);
+ freeCharString(_a440_stereo_pcm);
+ freeCharString(_a440_stereo_wav);
+ freeCharString(_c_scale_mid);
+ freeCharString(_again_test_fxp);
+}
diff --git a/test/unit/TestRunner.c b/test/unit/TestRunner.c
new file mode 100644
index 0000000..4c90cb3
--- /dev/null
+++ b/test/unit/TestRunner.c
@@ -0,0 +1,152 @@
+#include <stdlib.h>
+#if WINDOWS
+#include <io.h>
+#endif
+
+#include "unit/TestRunner.h"
+
+void addTestToTestSuite(TestSuite testSuite, TestCase testCase)
+{
+ linkedListAppend(testSuite->testCases, testCase);
+}
+
+const LogColor getLogColor(TestLogEventType eventType)
+{
+ switch (eventType) {
+ case kTestLogEventSection:
+ return isatty(1) ? COLOR_FG_CYAN : COLOR_NONE;
+
+ case kTestLogEventPass:
+ return isatty(1) ? COLOR_FG_GREEN : COLOR_NONE;
+
+ case kTestLogEventFail:
+ return isatty(1) ? COLOR_BG_MAROON : COLOR_NONE;
+
+ case kTestLogEventSkip:
+ return isatty(1) ? COLOR_FG_YELLOW : COLOR_NONE;
+
+ case kTestLogEventReset:
+ return isatty(1) ? COLOR_RESET : COLOR_NONE;
+
+ default:
+ return COLOR_NONE;
+ }
+}
+
+void printTestSuccess(void)
+{
+ printToLog(getLogColor(kTestLogEventPass), NULL, "OK");
+ flushLog(NULL);
+}
+
+void printTestFail(void)
+{
+ printToLog(getLogColor(kTestLogEventFail), NULL, "FAIL");
+ flushLog(NULL);
+}
+
+static void _printTestSkipped(void)
+{
+ printToLog(getLogColor(kTestLogEventSkip), NULL, "Skipped");
+ flushLog(NULL);
+}
+
+void printTestName(const char *testName)
+{
+ fprintf(stderr, " %s: ", testName);
+ // Flush standard output in case the test crashes. That way at least the
+ // crashing test name is seen.
+ fflush(stderr);
+}
+
+void runTestCase(void *item, void *extraData)
+{
+ TestCase testCase = (TestCase)item;
+ TestSuite testSuite = (TestSuite)extraData;
+ int result;
+
+ if (!testSuite->onlyPrintFailing) {
+ printTestName(testCase->name);
+ }
+
+ if (testCase->testCaseFunc != NULL) {
+ if (testSuite->setup != NULL) {
+ testSuite->setup();
+ }
+
+ result = testCase->testCaseFunc();
+
+ if (result == 0) {
+ if (!testSuite->onlyPrintFailing) {
+ printTestSuccess();
+ }
+
+ testSuite->numSuccess++;
+ } else {
+ printTestFail();
+ testSuite->numFail++;
+ }
+
+ if (testSuite->teardown != NULL) {
+ testSuite->teardown();
+ }
+ } else {
+ if (!testSuite->onlyPrintFailing) {
+ _printTestSkipped();
+ }
+
+ testSuite->numSkips++;
+ }
+}
+
+void runTestSuite(void *testSuitePtr, void *extraData)
+{
+ TestSuite testSuite = (TestSuite)testSuitePtr;
+
+ printToLog(getLogColor(kTestLogEventReset), NULL, "Running tests in ");
+ printToLog(getLogColor(kTestLogEventSection), NULL, testSuite->name);
+ flushLog(NULL);
+
+ linkedListForeach(testSuite->testCases, runTestCase, testSuite);
+}
+
+// In both the TestSuite and TestCase objects we assume that we do not need ownership of
+// the strings passed in, since they should be allocated on the heap and live for the
+// lifetime of the program.
+TestSuite newTestSuite(char *name, TestCaseSetupFunc setup, TestCaseTeardownFunc teardown)
+{
+ TestSuite testSuite = (TestSuite)malloc(sizeof(TestSuiteMembers));
+ testSuite->name = name;
+ testSuite->numSuccess = 0;
+ testSuite->numFail = 0;
+ testSuite->numSkips = 0;
+ testSuite->testCases = newLinkedList();
+ testSuite->setup = setup;
+ testSuite->teardown = teardown;
+ testSuite->onlyPrintFailing = false;
+ testSuite->keepFiles = false;
+ return testSuite;
+}
+
+TestCase newTestCase(char *name, char *filename, int lineNumber, TestCaseExecFunc testCaseFunc)
+{
+ TestCase testCase = (TestCase)malloc(sizeof(TestCaseMembers));
+ testCase->name = name;
+ testCase->filename = filename;
+ testCase->lineNumber = lineNumber;
+ testCase->testCaseFunc = testCaseFunc;
+ return testCase;
+}
+
+void freeTestCase(TestCase self)
+{
+ free(self);
+}
+
+void freeTestSuite(TestSuite self)
+{
+ if (self != NULL) {
+ freeLinkedListAndItems(self->testCases, (LinkedListFreeItemFunc)freeTestCase);
+ free(self);
+ }
+}
diff --git a/test/unit/TestRunner.h b/test/unit/TestRunner.h
new file mode 100644
index 0000000..597a80a
--- /dev/null
+++ b/test/unit/TestRunner.h
@@ -0,0 +1,173 @@
+#ifndef MrsWatsonTest_TestRunner_h
+#define MrsWatsonTest_TestRunner_h
+
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+#include "base/CharString.h"
+#include "base/File.h"
+#include "logging/LogPrinter.h"
+
+#if UNIX
+#include <unistd.h>
+#endif
+
+#ifndef __func__
+#define __func__ __FUNCTION__
+#endif
+
+typedef enum {
+ kTestLogEventSection,
+ kTestLogEventPass,
+ kTestLogEventFail,
+ kTestLogEventSkip,
+ kTestLogEventReset,
+ kTestLogEventInvalid
+} TestLogEventType;
+const LogColor getLogColor(TestLogEventType eventType);
+
+typedef int (*TestCaseExecFunc)(void);
+typedef void (*TestCaseSetupFunc)(void);
+typedef void (*TestCaseTeardownFunc)(void);
+
+typedef struct {
+ char *name;
+ char *filename;
+ int lineNumber;
+ TestCaseExecFunc testCaseFunc;
+} TestCaseMembers;
+typedef TestCaseMembers *TestCase;
+
+typedef struct {
+ char *name;
+ int numSuccess;
+ int numFail;
+ int numSkips;
+ LinkedList testCases;
+ TestCaseSetupFunc setup;
+ TestCaseTeardownFunc teardown;
+ boolByte onlyPrintFailing;
+ boolByte keepFiles;
+} TestSuiteMembers;
+typedef TestSuiteMembers *TestSuite;
+
+void addTestToTestSuite(TestSuite testSuite, TestCase testCase);
+void runTestSuite(void *testSuitePtr, void *extraData);
+void runTestCase(void *item, void *extraData);
+void printTestName(const char *testName);
+void printTestSuccess(void);
+void printTestFail(void);
+
+TestSuite newTestSuite(char *name, TestCaseSetupFunc setup, TestCaseTeardownFunc teardown);
+TestCase newTestCase(char *name, char *filename, int lineNumber, TestCaseExecFunc testCaseFunc);
+
+void freeTestCase(TestCase self);
+void freeTestSuite(TestSuite self);
+
+static const char *_getFileBasename(const char *filename)
+{
+ const char *lastDelimiter;
+
+ if (filename == NULL) {
+ return NULL;
+ }
+
+ lastDelimiter = strrchr(filename, PATH_DELIMITER);
+
+ if (lastDelimiter == NULL) {
+ return (char *)filename;
+ } else {
+ return lastDelimiter + 1;
+ }
+}
+
+#define addTest(testSuite, name, testCaseFunc) { \
+ addTestToTestSuite(testSuite, newTestCase(name, __FILE__, __LINE__, testCaseFunc)); \
+ }
+
+#define assert(_result) { \
+ if(!(_result)) { \
+ fprintf(stderr, "\nAssertion failed at %s:%d. ", _getFileBasename(__FILE__), __LINE__); \
+ return 1; \
+ } \
+ }
+
+#define assertFalse(_result) assert((_result) == false)
+#define assertIsNull(_result) assert((_result) == NULL)
+#define assertNotNull(_result) assert((_result) != NULL)
+
+#define assertIntEquals(expected, _result) { \
+ int _resultInt = _result; \
+ if(_resultInt != expected) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected %d, got %d. ", _getFileBasename(__FILE__), __LINE__, expected, _resultInt); \
+ return 1; \
+ } \
+ }
+
+#define assertLongEquals(expected, _result) { \
+ long _resultLong = _result; \
+ if(_resultLong != expected) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected %lu, got %lu. ", _getFileBasename(__FILE__), __LINE__, expected, _resultLong); \
+ return 1; \
+ } \
+ }
+
+#define ZERO_UNSIGNED_LONG (unsigned long)0
+#define assertUnsignedLongEquals(expected, _result) { \
+ unsigned long _resultULong = _result; \
+ if(_resultULong != expected) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected %ld, got %ld. ", _getFileBasename(__FILE__), __LINE__, expected, _resultULong); \
+ return 1; \
+ } \
+ }
+
+#define assertSizeEquals(expected, _result) { \
+ size_t _resultSize = _result; \
+ if(_result != expected) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected %zu, got %zu. ", _getFileBasename(__FILE__), __LINE__, expected, _resultSize); \
+ return 1; \
+ } \
+ }
+
+#define TEST_DEFAULT_TOLERANCE 0.01
+#define TEST_EXACT_TOLERANCE 0.0
+
+#define assertDoubleEquals(expected, _result, tolerance) { \
+ double resultRounded = floor(_result * 100.0) / 100.0; \
+ double expectedRounded = floor(expected * 100.0) / 100.0; \
+ double _resultDiff = fabs(resultRounded - expectedRounded); \
+ if(_resultDiff > tolerance) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected %g, got %g. ", _getFileBasename(__FILE__), __LINE__, expectedRounded, resultRounded); \
+ return 1; \
+ } \
+ }
+
+ // Timing assertions fail all the time in debug mode, because the binary is
+ // running in the debugger, is not optimized, is being profiled, etc. So for
+ // debug builds, we should not return early here, or else that will cause
+ // valgrind to go crazy and report a bunch of leaks.
+#define assertTimeEquals(expected, _result, tolerance) { \
+ double resultRounded = floor(_result * 100.0) / 100.0; \
+ double expectedRounded = floor(expected * 100.0) / 100.0; \
+ double _resultDiff = fabs(resultRounded - expectedRounded); \
+ if(_resultDiff > tolerance) { \
+ fprintf(stderr, "Warning: timing assertion failed at %s:%d. Expected %gms, got %gms. ", _getFileBasename(__FILE__), __LINE__, expectedRounded, resultRounded); \
+ } \
+ }
+
+#define assertCharStringEquals(expected, _result) { \
+ if(!charStringIsEqualToCString(_result, expected, false)) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected '%s', got '%s'. ", _getFileBasename(__FILE__), __LINE__, expected, _result->data); \
+ return 1; \
+ } \
+ }
+
+#define assertCharStringContains(expected, _result) { \
+ if(strstr(_result->data, expected) == NULL) { \
+ fprintf(stderr, "Assertion failed at %s:%d. Expected '%s' to contain '%s'. ", _getFileBasename(__FILE__), __LINE__, _result->data, expected); \
+ return 1; \
+ } \
+ }
+
+#endif
diff --git a/test/unit/UnitTests.c b/test/unit/UnitTests.c
new file mode 100644
index 0000000..b2bae69
--- /dev/null
+++ b/test/unit/UnitTests.c
@@ -0,0 +1,160 @@
+#include <stdlib.h>
+#include "base/LinkedList.h"
+#include "unit/TestRunner.h"
+
+extern TestSuite addAudioClockTests(void);
+extern TestSuite addAudioSettingsTests(void);
+extern TestSuite addCharStringTests(void);
+extern TestSuite addEndianTests(void);
+extern TestSuite addFileTests(void);
+extern TestSuite addLinkedListTests(void);
+extern TestSuite addMidiSequenceTests(void);
+extern TestSuite addMidiSourceTests(void);
+extern TestSuite addPlatformInfoTests(void);
+extern TestSuite addPluginTests(void);
+extern TestSuite addPluginChainTests(void);
+extern TestSuite addPluginPresetTests(void);
+extern TestSuite addPluginVst2xIdTests(void);
+extern TestSuite addProgramOptionTests(void);
+extern TestSuite addSampleBufferTests(void);
+extern TestSuite addSampleSourceTests(void);
+extern TestSuite addTaskTimerTests(void);
+
+extern TestSuite addAnalysisClippingTests(void);
+extern TestSuite addAnalysisDistortionTests(void);
+extern TestSuite addAnalysisSilenceTests(void);
+
+extern void _printTestSummary(int testsRun, int testsPassed, int testsFailed, int testsSkipped);
+
+static void _sumTestSuiteResults(void *item, void *extraData)
+{
+ TestSuite testSuite = (TestSuite)item;
+ TestSuite result = (TestSuite)extraData;
+ result->numSuccess += testSuite->numSuccess;
+ result->numFail += testSuite->numFail;
+ result->numSkips += testSuite->numSkips;
+}
+
+LinkedList getTestSuites(void);
+LinkedList getTestSuites(void)
+{
+ LinkedList unitTestSuites = newLinkedList();
+ linkedListAppend(unitTestSuites, addAudioClockTests());
+ linkedListAppend(unitTestSuites, addAudioSettingsTests());
+ linkedListAppend(unitTestSuites, addCharStringTests());
+ linkedListAppend(unitTestSuites, addEndianTests());
+ linkedListAppend(unitTestSuites, addFileTests());
+ linkedListAppend(unitTestSuites, addLinkedListTests());
+ linkedListAppend(unitTestSuites, addMidiSequenceTests());
+ linkedListAppend(unitTestSuites, addMidiSourceTests());
+ linkedListAppend(unitTestSuites, addPlatformInfoTests());
+ linkedListAppend(unitTestSuites, addPluginTests());
+ linkedListAppend(unitTestSuites, addPluginChainTests());
+ linkedListAppend(unitTestSuites, addPluginPresetTests());
+ linkedListAppend(unitTestSuites, addPluginVst2xIdTests());
+ linkedListAppend(unitTestSuites, addProgramOptionTests());
+ linkedListAppend(unitTestSuites, addSampleBufferTests());
+ linkedListAppend(unitTestSuites, addSampleSourceTests());
+ linkedListAppend(unitTestSuites, addTaskTimerTests());
+
+ linkedListAppend(unitTestSuites, addAnalysisClippingTests());
+ linkedListAppend(unitTestSuites, addAnalysisDistortionTests());
+ linkedListAppend(unitTestSuites, addAnalysisSilenceTests());
+
+ return unitTestSuites;
+}
+
+static void _setTestSuiteOnlyPrintFailing(void *item, void *userData)
+{
+ TestSuite testSuite = (TestSuite)item;
+ testSuite->onlyPrintFailing = true;
+}
+
+TestSuite runUnitTests(LinkedList testSuites, boolByte onlyPrintFailing);
+TestSuite runUnitTests(LinkedList testSuites, boolByte onlyPrintFailing)
+{
+ TestSuite suiteResults;
+
+ if (onlyPrintFailing) {
+ linkedListForeach(testSuites, _setTestSuiteOnlyPrintFailing, NULL);
+ }
+
+ linkedListForeach(testSuites, runTestSuite, NULL);
+ // Create a new test suite to be used as the userData passed to the foreach loop
+ suiteResults = newTestSuite("Suite results", NULL, NULL);
+ linkedListForeach(testSuites, _sumTestSuiteResults, suiteResults);
+
+ _printTestSummary(suiteResults->numSuccess + suiteResults->numFail + suiteResults->numSkips,
+ suiteResults->numSuccess, suiteResults->numFail, suiteResults->numSkips);
+
+ return suiteResults;
+}
+
+TestCase findTestCase(TestSuite testSuite, char *testName);
+TestCase findTestCase(TestSuite testSuite, char *testName)
+{
+ LinkedList iterator = testSuite->testCases;
+ TestCase currentTestCase = NULL;
+
+ while (iterator != NULL) {
+ if (iterator->item != NULL) {
+ currentTestCase = (TestCase)iterator->item;
+
+ if (!strncasecmp(currentTestCase->name, testName, strlen(currentTestCase->name))) {
+ return currentTestCase;
+ }
+ }
+
+ iterator = iterator->nextItem;
+ }
+
+ return NULL;
+}
+
+TestSuite findTestSuite(LinkedList testSuites, const CharString testSuiteName);
+TestSuite findTestSuite(LinkedList testSuites, const CharString testSuiteName)
+{
+ LinkedList iterator = testSuites;
+ TestSuite testSuite = NULL;
+
+ if (testSuiteName == NULL || charStringIsEmpty(testSuiteName)) {
+ return NULL;
+ }
+
+ while (iterator != NULL) {
+ if (iterator->item != NULL) {
+ testSuite = (TestSuite)iterator->item;
+
+ if (charStringIsEqualToCString(testSuiteName, testSuite->name, true)) {
+ break;
+ } else {
+ testSuite = NULL;
+ }
+ }
+
+ iterator = iterator->nextItem;
+ }
+
+ return testSuite;
+}
+
+static void _printTestCases(void *item, void *userData)
+{
+ TestCase testCase = (TestCase)item;
+ char *testSuiteName = (char *)userData;
+ printf("%s:%s\n", testSuiteName, testCase->name);
+}
+
+static void _printTestsInSuite(void *item, void *userData)
+{
+ TestSuite testSuite = (TestSuite)item;
+ linkedListForeach(testSuite->testCases, _printTestCases, testSuite->name);
+}
+
+void printUnitTestSuites(void);
+void printUnitTestSuites(void)
+{
+ LinkedList unitTestSuites = getTestSuites();
+ linkedListForeach(unitTestSuites, _printTestsInSuite, NULL);
+ freeLinkedListAndItems(unitTestSuites, (LinkedListFreeItemFunc)freeTestSuite);
+}
diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt
new file mode 100644
index 0000000..458d0dd
--- /dev/null
+++ b/vendor/CMakeLists.txt
@@ -0,0 +1,298 @@
+cmake_minimum_required(VERSION 2.8.11)
+project(MrsWatsonVendor)
+
+include(${cmake_SCRIPTS_DIR}/ConfigureTarget.cmake)
+
+# The some of third-party libraries under the vendor directory are built with
+# autoconf (ie, ./configure scripts), which can be a bit tricky to integrate
+# with CMake, even with the ExternalProject module (and not to mention on
+# Windows, where mixing autoconf and VS builds is a nightmare in the making).
+#
+# So instead, we build these libraries via CMake, which adds a bit of
+# maintainence to update but makes the build procedure much easier. The process
+# of updating a new library is as such:
+#
+# 1. Update from upstream in teragonaudio's git fork/mirror of the repo
+# 2. Bump submodule in project
+# 3. Refresh file list below
+# 4. Comment out any ignored files
+# 5. Re-run ./configure
+# 6. Copy the generated config.h file to the platform-specific directory
+#
+
+# audiofile ####################################################
+
+# Configured with: ./configure --enable-flac --enable-static --disable-docs
+# Also, the generated config.h file should be edited such that the definition
+# of ENABLE_FLAC is not hardcoded to 0/1, but rather to USE_FLAC (the value
+# set by our CMakeLists).
+
+if(WITH_AUDIOFILE)
+ set(audiofile_SOURCES
+ audiofile/libaudiofile/aes.cpp
+ audiofile/libaudiofile/af_vfs.cpp
+ audiofile/libaudiofile/AIFF.cpp
+ audiofile/libaudiofile/alac/ag_dec.c
+ audiofile/libaudiofile/alac/ag_enc.c
+ audiofile/libaudiofile/alac/ALACBitUtilities.c
+ audiofile/libaudiofile/alac/ALACDecoder.cpp
+ audiofile/libaudiofile/alac/ALACEncoder.cpp
+ audiofile/libaudiofile/alac/dp_dec.c
+ audiofile/libaudiofile/alac/dp_enc.c
+ audiofile/libaudiofile/alac/EndianPortable.c
+ audiofile/libaudiofile/alac/matrix_dec.c
+ audiofile/libaudiofile/alac/matrix_enc.c
+ audiofile/libaudiofile/AudioFormat.cpp
+ audiofile/libaudiofile/aupv.c
+ audiofile/libaudiofile/AVR.cpp
+ audiofile/libaudiofile/Buffer.cpp
+ audiofile/libaudiofile/CAF.cpp
+ audiofile/libaudiofile/compression.cpp
+ audiofile/libaudiofile/data.cpp
+ audiofile/libaudiofile/debug.cpp
+ audiofile/libaudiofile/error.c
+ audiofile/libaudiofile/extended.c
+ audiofile/libaudiofile/File.cpp
+ audiofile/libaudiofile/FileHandle.cpp
+ audiofile/libaudiofile/FLACFile.cpp
+ audiofile/libaudiofile/format.cpp
+ audiofile/libaudiofile/g711.c
+ audiofile/libaudiofile/IFF.cpp
+ audiofile/libaudiofile/Instrument.cpp
+ audiofile/libaudiofile/IRCAM.cpp
+ audiofile/libaudiofile/Loop.cpp
+ audiofile/libaudiofile/Marker.cpp
+ audiofile/libaudiofile/Miscellaneous.cpp
+ audiofile/libaudiofile/modules/ALAC.cpp
+ audiofile/libaudiofile/modules/BlockCodec.cpp
+ audiofile/libaudiofile/modules/FileModule.cpp
+ audiofile/libaudiofile/modules/FLAC.cpp
+ audiofile/libaudiofile/modules/G711.cpp
+ audiofile/libaudiofile/modules/IMA.cpp
+ audiofile/libaudiofile/modules/Module.cpp
+ audiofile/libaudiofile/modules/ModuleState.cpp
+ audiofile/libaudiofile/modules/MSADPCM.cpp
+ audiofile/libaudiofile/modules/PCM.cpp
+ audiofile/libaudiofile/modules/RebufferModule.cpp
+ audiofile/libaudiofile/modules/SimpleModule.cpp
+ # audiofile/libaudiofile/modules/UT_RebufferModule.cpp (Ignored)
+ audiofile/libaudiofile/NeXT.cpp
+ audiofile/libaudiofile/NIST.cpp
+ audiofile/libaudiofile/openclose.cpp
+ audiofile/libaudiofile/PacketTable.cpp
+ audiofile/libaudiofile/pcm.cpp
+ audiofile/libaudiofile/query.cpp
+ audiofile/libaudiofile/Raw.cpp
+ audiofile/libaudiofile/SampleVision.cpp
+ audiofile/libaudiofile/Setup.cpp
+ audiofile/libaudiofile/Track.cpp
+ audiofile/libaudiofile/units.cpp
+ audiofile/libaudiofile/util.cpp
+ audiofile/libaudiofile/UUID.cpp
+ audiofile/libaudiofile/VOC.cpp
+ audiofile/libaudiofile/WAVE.cpp
+ )
+
+ set(audiofile_HEADERS
+ audiofile/libaudiofile/af_vfs.h
+ audiofile/libaudiofile/afinternal.h
+ audiofile/libaudiofile/AIFF.h
+ audiofile/libaudiofile/alac/aglib.h
+ audiofile/libaudiofile/alac/ALACAudioTypes.h
+ audiofile/libaudiofile/alac/ALACBitUtilities.h
+ audiofile/libaudiofile/alac/ALACDecoder.h
+ audiofile/libaudiofile/alac/ALACEncoder.h
+ audiofile/libaudiofile/alac/dplib.h
+ audiofile/libaudiofile/alac/EndianPortable.h
+ audiofile/libaudiofile/alac/matrixlib.h
+ audiofile/libaudiofile/audiofile.h
+ audiofile/libaudiofile/AudioFormat.h
+ audiofile/libaudiofile/aupvinternal.h
+ audiofile/libaudiofile/aupvlist.h
+ audiofile/libaudiofile/AVR.h
+ audiofile/libaudiofile/Buffer.h
+ audiofile/libaudiofile/byteorder.h
+ audiofile/libaudiofile/CAF.h
+ audiofile/libaudiofile/Compiler.h
+ audiofile/libaudiofile/compression.h
+ audiofile/libaudiofile/debug.h
+ audiofile/libaudiofile/error.h
+ audiofile/libaudiofile/extended.h
+ audiofile/libaudiofile/Features.h
+ audiofile/libaudiofile/File.h
+ audiofile/libaudiofile/FileHandle.h
+ audiofile/libaudiofile/FLACFile.h
+ audiofile/libaudiofile/g711.h
+ audiofile/libaudiofile/IFF.h
+ audiofile/libaudiofile/Instrument.h
+ audiofile/libaudiofile/IRCAM.h
+ audiofile/libaudiofile/Marker.h
+ audiofile/libaudiofile/modules/ALAC.h
+ audiofile/libaudiofile/modules/BlockCodec.h
+ audiofile/libaudiofile/modules/FileModule.h
+ audiofile/libaudiofile/modules/FLAC.h
+ audiofile/libaudiofile/modules/G711.h
+ audiofile/libaudiofile/modules/IMA.h
+ audiofile/libaudiofile/modules/Module.h
+ audiofile/libaudiofile/modules/ModuleState.h
+ audiofile/libaudiofile/modules/MSADPCM.h
+ audiofile/libaudiofile/modules/PCM.h
+ audiofile/libaudiofile/modules/RebufferModule.h
+ audiofile/libaudiofile/modules/SimpleModule.h
+ audiofile/libaudiofile/NeXT.h
+ audiofile/libaudiofile/NIST.h
+ audiofile/libaudiofile/PacketTable.h
+ audiofile/libaudiofile/pcm.h
+ audiofile/libaudiofile/Raw.h
+ audiofile/libaudiofile/SampleVision.h
+ audiofile/libaudiofile/Setup.h
+ audiofile/libaudiofile/Shared.h
+ audiofile/libaudiofile/Tag.h
+ audiofile/libaudiofile/Track.h
+ audiofile/libaudiofile/units.h
+ audiofile/libaudiofile/util.h
+ audiofile/libaudiofile/UUID.h
+ audiofile/libaudiofile/VOC.h
+ audiofile/libaudiofile/WAVE.h
+ )
+
+ add_library(audiofile STATIC
+ ${audiofile_SOURCES}
+ ${audiofile_HEADERS}
+ audiofile-config/${PLATFORM_CONFIG_DIR}/config.h
+ )
+
+ add_library(audiofile64 STATIC
+ ${audiofile_SOURCES}
+ ${audiofile_HEADERS}
+ audiofile-config/${PLATFORM_CONFIG_DIR}/config.h
+ )
+
+ include_directories(audiofile/libaudiofile)
+ source_group(config audiofile-config/${PLATFORM_CONFIG_DIR}/config.h)
+
+ target_include_directories(audiofile PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/audiofile-config/${PLATFORM_CONFIG_DIR}
+ )
+ target_include_directories(audiofile64 PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/audiofile-config/${PLATFORM_CONFIG_DIR}
+ )
+
+ if(WITH_FLAC)
+ target_include_directories(audiofile PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include
+ )
+ target_include_directories(audiofile64 PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include
+ )
+ endif()
+
+ configure_target(audiofile 32)
+ configure_target(audiofile64 64)
+endif()
+
+# flac #########################################################
+
+# Configured with: ./configure --disable-ogg --enable-static
+# But on Mac: ./configure --disable-ogg --enable-static --disable-asm-optimizations
+# (See https://github.com/Homebrew/homebrew/issues/34589)
+
+if(WITH_FLAC)
+ set(flac_SOURCES
+ flac/src/libFLAC/bitmath.c
+ flac/src/libFLAC/bitreader.c
+ flac/src/libFLAC/bitwriter.c
+ flac/src/libFLAC/cpu.c
+ flac/src/libFLAC/crc.c
+ flac/src/libFLAC/fixed.c
+ flac/src/libFLAC/fixed_intrin_sse2.c
+ flac/src/libFLAC/fixed_intrin_ssse3.c
+ flac/src/libFLAC/float.c
+ flac/src/libFLAC/format.c
+ flac/src/libFLAC/lpc.c
+ flac/src/libFLAC/lpc_intrin_avx2.c
+ flac/src/libFLAC/lpc_intrin_sse.c
+ flac/src/libFLAC/lpc_intrin_sse2.c
+ flac/src/libFLAC/lpc_intrin_sse41.c
+ flac/src/libFLAC/md5.c
+ flac/src/libFLAC/memory.c
+ flac/src/libFLAC/metadata_iterators.c
+ flac/src/libFLAC/metadata_object.c
+ # flac/src/libFLAC/ogg_decoder_aspect.c (Ignore)
+ # flac/src/libFLAC/ogg_encoder_aspect.c (Ignore)
+ # flac/src/libFLAC/ogg_helper.c (Ignore)
+ # flac/src/libFLAC/ogg_mapping.c (Ignore)
+ flac/src/libFLAC/stream_decoder.c
+ flac/src/libFLAC/stream_encoder.c
+ flac/src/libFLAC/stream_encoder_framing.c
+ flac/src/libFLAC/stream_encoder_intrin_avx2.c
+ flac/src/libFLAC/stream_encoder_intrin_sse2.c
+ flac/src/libFLAC/stream_encoder_intrin_ssse3.c
+ flac/src/libFLAC/window.c
+ )
+
+ set(flac++_SOURCES
+ flac/src/libFLAC++/metadata.cpp
+ flac/src/libFLAC++/stream_decoder.cpp
+ flac/src/libFLAC++/stream_encoder.cpp
+ )
+
+ set(flac_HEADERS
+ flac/include/FLAC/all.h
+ flac/include/FLAC/assert.h
+ flac/include/FLAC/callback.h
+ flac/include/FLAC/export.h
+ flac/include/FLAC/format.h
+ flac/include/FLAC/metadata.h
+ flac/include/FLAC/ordinals.h
+ flac/include/FLAC/stream_decoder.h
+ flac/include/FLAC/stream_encoder.h
+ )
+
+ set(flac++_HEADERS
+ flac/include/FLAC++/all.h
+ flac/include/FLAC++/decoder.h
+ flac/include/FLAC++/encoder.h
+ flac/include/FLAC++/export.h
+ flac/include/FLAC++/metadata.h
+ )
+
+ add_library(flac STATIC
+ ${flac_SOURCES}
+ ${flac++_SOURCES}
+ ${flac_HEADERS}
+ ${flac++_HEADERS}
+ flac-config/${PLATFORM_CONFIG_DIR}/config.h
+ )
+
+ add_library(flac64 STATIC
+ ${flac_SOURCES}
+ ${flac++_SOURCES}
+ ${flac_HEADERS}
+ ${flac++_HEADERS}
+ flac-config/${PLATFORM_CONFIG_DIR}/config.h
+ )
+
+ target_include_directories(flac PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include/FLAC
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include/FLAC++
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/src/libFLAC/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac-config/${PLATFORM_CONFIG_DIR}
+ )
+ target_include_directories(flac64 PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include/FLAC
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/include/FLAC++
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac/src/libFLAC/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/flac-config/${PLATFORM_CONFIG_DIR}
+ )
+
+ source_group(config flac-config/${PLATFORM_CONFIG_DIR}/config.h)
+
+ set_target_properties(flac PROPERTIES COMPILE_DEFINITIONS "HAVE_CONFIG_H=1")
+ set_target_properties(flac64 PROPERTIES COMPILE_DEFINITIONS "HAVE_CONFIG_H=1")
+
+ configure_target(flac 32)
+ configure_target(flac64 64)
+endif()
diff --git a/vendor/audiofile-config/linux/config.h b/vendor/audiofile-config/linux/config.h
new file mode 100644
index 0000000..20c1808
--- /dev/null
+++ b/vendor/audiofile-config/linux/config.h
@@ -0,0 +1,104 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Whether FLAC is enabled. */
+#define ENABLE_FLAC USE_FLAC
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "audiofile"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "audiofile"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "audiofile 0.3.6"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "audiofile"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "0.3.6"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "0.3.6"
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+# define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+/* # undef WORDS_BIGENDIAN */
+# endif
+#endif
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
diff --git a/vendor/audiofile-config/mac/config.h b/vendor/audiofile-config/mac/config.h
new file mode 100644
index 0000000..20c1808
--- /dev/null
+++ b/vendor/audiofile-config/mac/config.h
@@ -0,0 +1,104 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Whether FLAC is enabled. */
+#define ENABLE_FLAC USE_FLAC
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "audiofile"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "audiofile"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "audiofile 0.3.6"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "audiofile"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "0.3.6"
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Version number of package */
+#define VERSION "0.3.6"
+
+/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most
+ significant byte first (like Motorola and SPARC, unlike Intel). */
+#if defined AC_APPLE_UNIVERSAL_BUILD
+# if defined __BIG_ENDIAN__
+# define WORDS_BIGENDIAN 1
+# endif
+#else
+# ifndef WORDS_BIGENDIAN
+/* # undef WORDS_BIGENDIAN */
+# endif
+#endif
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
diff --git a/vendor/flac-config/linux/config.h b/vendor/flac-config/linux/config.h
new file mode 100644
index 0000000..69e92db
--- /dev/null
+++ b/vendor/flac-config/linux/config.h
@@ -0,0 +1,235 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Target processor is big endian. */
+#define CPU_IS_BIG_ENDIAN 0
+
+/* Target processor is little endian. */
+#define CPU_IS_LITTLE_ENDIAN 1
+
+/* define to align allocated memory on 32-byte boundaries */
+#define FLAC__ALIGN_MALLOC_DATA 1
+
+/* define if building for ia32/i386 */
+/* #undef FLAC__CPU_IA32 */
+
+/* define if building for PowerPC */
+/* #undef FLAC__CPU_PPC */
+
+/* define if building for PowerPC with SPE ABI */
+/* #undef FLAC__CPU_PPC_SPE */
+
+/* define if building for SPARC */
+/* #undef FLAC__CPU_SPARC */
+
+/* define if building for x86_64 */
+#define FLAC__CPU_X86_64 1
+
+/* define if you have docbook-to-man or docbook2man */
+/* #undef FLAC__HAS_DOCBOOK_TO_MAN */
+
+/* define if you are compiling for x86 and have the NASM assembler */
+/* #undef FLAC__HAS_NASM */
+
+/* define if you have the ogg library */
+#define FLAC__HAS_OGG 0
+
+/* Set to 1 if <x86intrin.h> is available. */
+#define FLAC__HAS_X86INTRIN
+
+/* define to disable use of assembly code */
+/* #undef FLAC__NO_ASM */
+
+/* define if your operating system supports SSE instructions */
+#define FLAC__SSE_OS 1
+
+/* define if building for Darwin / MacOS X */
+/* #undef FLAC__SYS_DARWIN */
+
+/* define if building for Linux */
+/* #undef FLAC__SYS_LINUX */
+
+/* define to enable use of Altivec instructions */
+#define FLAC__USE_ALTIVEC 1
+
+/* Define to 1 if `TIOCGWINSZ' requires <sys/ioctl.h>. */
+#define GWINSZ_IN_SYS_IOCTL 1
+
+/* Compiler has the __builtin_bswap16 intrinsic */
+#define HAVE_BSWAP16 1
+
+/* Compiler has the __builtin_bswap32 intrinsic */
+#define HAVE_BSWAP32 1
+
+/* Define to 1 if you have the <byteswap.h> header file. */
+#define HAVE_BYTESWAP_H 1
+
+/* Define to 1 if you have the <cpuid.h> header file. */
+#define HAVE_CPUID_H 1
+
+/* Define to 1 if C++ supports variable-length arrays. */
+#define HAVE_CXX_VARARRAYS 1
+
+/* Define to 1 if C supports variable-length arrays. */
+#define HAVE_C_VARARRAYS 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
+#define HAVE_FSEEKO 1
+
+/* Define to 1 if you have the `getopt_long' function. */
+#define HAVE_GETOPT_LONG 1
+
+/* Define if you have the iconv() function and it works. */
+#define HAVE_ICONV 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
+#define HAVE_LANGINFO_CODESET 1
+
+/* lround support */
+#define HAVE_LROUND 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if the system has the type `socklen_t'. */
+#define HAVE_SOCKLEN_T 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define to 1 if typeof works with your compiler. */
+#define HAVE_TYPEOF 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <x86intrin.h> header file. */
+#define HAVE_X86INTRIN_H 1
+
+/* Define as const if the declaration of iconv() needs const. */
+#define ICONV_CONST
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "flac"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "flac-dev@xiph.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "flac"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "flac 1.3.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "flac"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "https://www.xiph.org/flac/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.3.1"
+
+/* The size of `off_t', as computed by sizeof. */
+#define SIZEOF_OFF_T 8
+
+/* The size of `void*', as computed by sizeof. */
+#define SIZEOF_VOIDP 8
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define VERSION "1.3.1"
+
+/* Target processor is big endian. */
+#define WORDS_BIGENDIAN 0
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
+/* #undef _LARGEFILE_SOURCE */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* 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
+/* #undef inline */
+#endif
+
+/* Define to __typeof__ if your compiler spells it that way. */
+/* #undef typeof */
diff --git a/vendor/flac-config/mac/config.h b/vendor/flac-config/mac/config.h
new file mode 100644
index 0000000..7a569cf
--- /dev/null
+++ b/vendor/flac-config/mac/config.h
@@ -0,0 +1,235 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define if building universal (internal helper macro) */
+/* #undef AC_APPLE_UNIVERSAL_BUILD */
+
+/* Target processor is big endian. */
+#define CPU_IS_BIG_ENDIAN 0
+
+/* Target processor is little endian. */
+#define CPU_IS_LITTLE_ENDIAN 1
+
+/* define to align allocated memory on 32-byte boundaries */
+#define FLAC__ALIGN_MALLOC_DATA 1
+
+/* define if building for ia32/i386 */
+/* #undef FLAC__CPU_IA32 */
+
+/* define if building for PowerPC */
+/* #undef FLAC__CPU_PPC */
+
+/* define if building for PowerPC with SPE ABI */
+/* #undef FLAC__CPU_PPC_SPE */
+
+/* define if building for SPARC */
+/* #undef FLAC__CPU_SPARC */
+
+/* define if building for x86_64 */
+#define FLAC__CPU_X86_64 1
+
+/* define if you have docbook-to-man or docbook2man */
+/* #undef FLAC__HAS_DOCBOOK_TO_MAN */
+
+/* define if you are compiling for x86 and have the NASM assembler */
+#define FLAC__HAS_NASM 1
+
+/* define if you have the ogg library */
+#define FLAC__HAS_OGG 0
+
+/* Set to 1 if <x86intrin.h> is available. */
+#define FLAC__HAS_X86INTRIN
+
+/* define to disable use of assembly code */
+#define FLAC__NO_ASM 1
+
+/* define if your operating system supports SSE instructions */
+#define FLAC__SSE_OS 1
+
+/* define if building for Darwin / MacOS X */
+#define FLAC__SYS_DARWIN 1
+
+/* define if building for Linux */
+/* #undef FLAC__SYS_LINUX */
+
+/* define to enable use of Altivec instructions */
+#define FLAC__USE_ALTIVEC 1
+
+/* Define to 1 if `TIOCGWINSZ' requires <sys/ioctl.h>. */
+/* #undef GWINSZ_IN_SYS_IOCTL */
+
+/* Compiler has the __builtin_bswap16 intrinsic */
+#define HAVE_BSWAP16 1
+
+/* Compiler has the __builtin_bswap32 intrinsic */
+#define HAVE_BSWAP32 1
+
+/* Define to 1 if you have the <byteswap.h> header file. */
+/* #undef HAVE_BYTESWAP_H */
+
+/* Define to 1 if you have the <cpuid.h> header file. */
+#define HAVE_CPUID_H 1
+
+/* Define to 1 if C++ supports variable-length arrays. */
+#define HAVE_CXX_VARARRAYS 1
+
+/* Define to 1 if C supports variable-length arrays. */
+#define HAVE_C_VARARRAYS 1
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#define HAVE_DLFCN_H 1
+
+/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */
+#define HAVE_FSEEKO 1
+
+/* Define to 1 if you have the `getopt_long' function. */
+#define HAVE_GETOPT_LONG 1
+
+/* Define if you have the iconv() function and it works. */
+#define HAVE_ICONV 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
+#define HAVE_LANGINFO_CODESET 1
+
+/* lround support */
+#define HAVE_LROUND 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if the system has the type `socklen_t'. */
+/* #undef HAVE_SOCKLEN_T */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <termios.h> header file. */
+#define HAVE_TERMIOS_H 1
+
+/* Define to 1 if typeof works with your compiler. */
+#define HAVE_TYPEOF 1
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <x86intrin.h> header file. */
+#define HAVE_X86INTRIN_H 1
+
+/* Define as const if the declaration of iconv() needs const. */
+#define ICONV_CONST
+
+/* Define to the sub-directory in which libtool stores uninstalled libraries.
+ */
+#define LT_OBJDIR ".libs/"
+
+/* Name of package */
+#define PACKAGE "flac"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "flac-dev@xiph.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "flac"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "flac 1.3.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "flac"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "https://www.xiph.org/flac/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "1.3.1"
+
+/* The size of `off_t', as computed by sizeof. */
+#define SIZEOF_OFF_T 8
+
+/* The size of `void*', as computed by sizeof. */
+#define SIZEOF_VOIDP 8
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define VERSION "1.3.1"
+
+/* Target processor is big endian. */
+#define WORDS_BIGENDIAN 0
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
+/* #undef _LARGEFILE_SOURCE */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* 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
+/* #undef inline */
+#endif
+
+/* Define to __typeof__ if your compiler spells it that way. */
+/* #undef typeof */