summaryrefslogtreecommitdiff
path: root/source
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 /source
first
Diffstat (limited to 'source')
-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
83 files changed, 14144 insertions, 0 deletions
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