bool 
AuditLogger::open()
{
    if (-1 != mAuditFd)
        return true;
    
    // @@@  use audit_get_cond() when it's available
    int acond = au_get_state();
    switch (acond)
    {
        case AUC_NOAUDIT:
            return false;
        case AUC_AUDITING:
            break;
        default:
            logInternalError("error checking auditing status (%d)", acond);
            UnixError::throwMe(acond);  // assume it's a Unix error
    }
    if ((mAuditFd = au_open()) < 0)
    {
        logInternalError("au_open() failed (%s)", strerror(errno));
        UnixError::throwMe(errno);
    }
    return true;
}
Esempio n. 2
0
/**
 *  Reads from inputSource.
 *
 * @param inputSource The SampleSource to read from.
 * @param silenceSource The source from where to read silentTailFrames silent tail frames.
 * @param buffer The SampleBuffer to which the samples will be written.
 * @param silentTailFrames Number of silent samples that will be provided at the end of inputSource.
 * @return True if there is more input to read.
 */
boolByte readInput(SampleSource inputSource, SampleSource silenceSource, SampleBuffer buffer, unsigned long silentTailFrames) {
  unsigned long framesRead;
  unsigned long bufferSize = buffer->blocksize;

  inputSource->readSampleBlock(inputSource, buffer);
  framesRead = buffer->blocksize; //buffer->blocksize tells how man frames have been read from inputSource, during tail period it will be 0.

  //We are not done until framesRead < bufferSize and silenceSource->numSamplesProcessed/buffer->numChannels == silentTailFrames
  if(framesRead == bufferSize) {
    return true; // more input
  } else if(framesRead < bufferSize) {
    unsigned long remainingSilence = silentTailFrames - silenceSource->numSamplesProcessed/buffer->numChannels; // 0 < remainingSilence <= silentTailFrames
    unsigned long numberOfFrames = remainingSilence < (bufferSize - framesRead) ? remainingSilence : (bufferSize - framesRead); // 0 < numberOfFrames <= bufferSize
    SampleBuffer silenceBuffer = newSampleBuffer(buffer->numChannels, numberOfFrames);
    if(!silenceSource->readSampleBlock(silenceSource, silenceBuffer)) {
      logInternalError("SilentSource does not behave correct.");
    }
    buffer->blocksize = framesRead + numberOfFrames; // 0 < buffer->blocksize <= bufferSize.
    sampleBufferCopyAndMapChannelsWithOffset(buffer, framesRead, silenceBuffer, 0, numberOfFrames);
    freeSampleBuffer(silenceBuffer);
    return silenceSource->numSamplesProcessed/buffer->numChannels != silentTailFrames;
  } else {
    logInternalError("framesRead > bufferSize");
    return false; //stop here.
  }
}
Esempio n. 3
0
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 = 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;
}
Esempio n. 4
0
/**
 *  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);//blocksize < skipHeadFrames
    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 { //                  skipHeadFrames <=  framesProcessed
    // Normal case: Nothing more to cut. The whole block shall be written.
    outputSource->writeSampleBlock(outputSource, buffer);
  }
}
void
AuditLogger::writeToken(token_t *token, const char *name)
{
    const char *tokenName = name ? name : "<unidentified>";
    if (NULL == token)
    {
        logInternalError("Invalid '%s' token", tokenName);
        close();
        UnixError::throwMe(EPERM);      // per audit_submit()
    }
    if (au_write(mAuditFd, token) < 0)
    {
        logInternalError("Error writing '%s' token (%s)", tokenName, strerror(errno));
        close();
        UnixError::throwMe(errno);
    }
}
static boolByte _openSampleSourceAiff(void *sampleSourcePtr, const SampleSourceOpenAs openAs) {
  SampleSource sampleSource = (SampleSource)sampleSourcePtr;
#if HAVE_LIBAUDIOFILE
  SampleSourceAudiofileData extraData = (SampleSourceAudiofileData)(sampleSource->extraData);
#else
  SampleSourcePcmData extraData = (SampleSourcePcmData)(sampleSource->extraData);
#endif

  if(openAs == SAMPLE_SOURCE_OPEN_READ) {
#if HAVE_LIBAUDIOFILE
    extraData->fileHandle = afOpenFile(sampleSource->sourceName->data, "r", NULL);
    if(extraData->fileHandle != NULL) {
      setNumChannels(afGetVirtualChannels(extraData->fileHandle, AF_DEFAULT_TRACK));
      setSampleRate((float)afGetRate(extraData->fileHandle, AF_DEFAULT_TRACK));
    }
#else
    logInternalError("Executable was not built with a library to read AIFF files");
#endif
  }
  else if(openAs == SAMPLE_SOURCE_OPEN_WRITE) {
#if HAVE_LIBAUDIOFILE
    AFfilesetup outfileSetup = afNewFileSetup();
    afInitFileFormat(outfileSetup, AF_FILE_AIFF);
    afInitByteOrder(outfileSetup, AF_DEFAULT_TRACK, AF_BYTEORDER_BIGENDIAN);
    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("Executable was not built with a library to write AIFF files");
#endif
  }
  else {
    logInternalError("Invalid type for openAs in AIFF file");
    return false;
  }

  if(extraData->fileHandle == NULL) {
    logError("AIFF 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;
}
Esempio n. 7
0
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);
}
Esempio n. 8
0
boolByte sampleBufferCopyAndMapChannelsWithOffset(SampleBuffer destinationBuffer, unsigned long destinationOffset, const SampleBuffer sourceBuffer, unsigned long sourceOffset, unsigned long numberOfFrames) {
  unsigned int i;

  // 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(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(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;
}
Esempio n. 9
0
static const char* _logTimeColor(const LogColorScheme colorScheme) {
  if(colorScheme == COLOR_SCHEME_DARK) {
    return ANSI_COLOR_CYAN;
  }
  else if(colorScheme == COLOR_SCHEME_LIGHT) {
    return ANSI_COLOR_GREEN;
  }
  else {
    logInternalError("Invalid color scheme for status char");
    return ANSI_COLOR_WHITE;
  }
}
Esempio n. 10
0
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;
}
Esempio n. 11
0
static const char* _logTimeZebraStripeColor(const long elapsedTime, const LogColorScheme colorScheme, const int zebraSizeInMs) {
  boolByte zebraState = (boolByte)((elapsedTime / zebraSizeInMs) % 2);
  if(colorScheme == COLOR_SCHEME_DARK) {
    return zebraState ? ANSI_COLOR_WHITE : ANSI_COLOR_YELLOW;
  }
  else if(colorScheme == COLOR_SCHEME_LIGHT) {
    return zebraState ? ANSI_COLOR_BLACK : ANSI_COLOR_BLUE;
  }
  else {
    logInternalError("Invalid color scheme for stripe color");
    return ANSI_COLOR_WHITE;
  }
}
Esempio n. 12
0
const char *_getPlatformVstDirName(const PlatformInfo platform) {
  switch (platform->type) {
  case PLATFORM_LINUX:
    return platform->is64BitRuntime ? "Linux-x86_64" : "Linux-i686";
  case PLATFORM_MACOSX:
    return "Mac OS X";
  case PLATFORM_WINDOWS:
    return platform->is64BitRuntime ? "Windows 64-bit" : "Windows 32-bit";
  default:
    logInternalError("No VST resource directory for platform");
    return NULL;
  }
}
void 
AuditLogger::close(bool writeLog/* = true*/)
{
    if (-1 != mAuditFd)
    {
        int keep = writeLog == true ?  AU_TO_WRITE : AU_TO_NO_WRITE;
        int error = au_close(mAuditFd, keep, mEvent);
        mAuditFd = -1;
        if (writeLog == true && error < 0)
        {
            logInternalError("au_close() failed; record not committed");
            UnixError::throwMe(error);
        }
    }
}
Esempio n. 14
0
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;
  }
}
Esempio n. 15
0
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;
}
Esempio n. 16
0
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;
  }
}
Esempio n. 17
0
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->bitDepth = getBitDepth();

            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;
}
Esempio n. 18
0
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;
  }
}
Esempio n. 19
0
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;
}
Esempio n. 20
0
static const char* _logLevelStatusColor(const LogLevel logLevel, const LogColorScheme colorScheme) {
  if(colorScheme == COLOR_SCHEME_DARK) {
    switch(logLevel) {
      case LOG_DEBUG: return ANSI_COLOR_WHITE;
      case LOG_INFO:  return ANSI_COLOR_GREEN;
      case LOG_WARN:  return ANSI_COLOR_MAGENTA;
      case LOG_ERROR: return ANSI_COLOR_RED;
      default:        return ANSI_COLOR_WHITE;
    }
  }
  else if(colorScheme == COLOR_SCHEME_LIGHT) {
    switch(logLevel) {
      case LOG_DEBUG: return ANSI_COLOR_BLACK;
      case LOG_INFO:  return ANSI_COLOR_GREEN;
      case LOG_WARN:  return ANSI_COLOR_MAGENTA;
      case LOG_ERROR: return ANSI_COLOR_RED;
      default:        return ANSI_COLOR_WHITE;
    }
  }
  else {
    logInternalError("Invalid color scheme for status char");
    return ANSI_COLOR_WHITE;
  }
}
Esempio n. 21
0
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 = NULL;
    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++;
        freeMidiEvent(midiEvent);
        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);
            freeMidiEvent(midiEvent);
            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: {
            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");
            free(trackData);
            freeMidiEvent(midiEvent);
            return false;

        case TIME_DIVISION_TYPE_INVALID:
        default:
            logInternalError("Invalid time division type");
            free(trackData);
            freeMidiEvent(midiEvent);
            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);
                midiEvent = NULL;
                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);
            midiEvent = NULL;
        }
    }

    free(trackData);
    freeMidiEvent(midiEvent);
    return true;
}
Esempio n. 22
0
static boolByte _openSampleSourceWave(void *sampleSourcePtr, const SampleSourceOpenAs openAs) {
  SampleSource sampleSource = (SampleSource)sampleSourcePtr;
#if HAVE_LIBAUDIOFILE
  SampleSourceAudiofileData extraData = sampleSource->extraData;
#else
  SampleSourcePcmData extraData = (SampleSourcePcmData)sampleSource->extraData;
#endif

  if(openAs == SAMPLE_SOURCE_OPEN_READ) {
#if HAVE_LIBAUDIOFILE
    extraData->fileHandle = afOpenFile(sampleSource->sourceName->data, "r", NULL);
    if(extraData->fileHandle != NULL) {
      setNumChannels(afGetVirtualChannels(extraData->fileHandle, AF_DEFAULT_TRACK));
      setSampleRate((float)afGetRate(extraData->fileHandle, AF_DEFAULT_TRACK));
    }
#else
    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;
      }
    }
#endif
  }
  else if(openAs == SAMPLE_SOURCE_OPEN_WRITE) {
#if HAVE_LIBAUDIOFILE
    AFfilesetup outfileSetup = afNewFileSetup();
    afInitFileFormat(outfileSetup, AF_FILE_WAVE);
    afInitByteOrder(outfileSetup, AF_DEFAULT_TRACK, AF_BYTEORDER_LITTLEENDIAN);
    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
    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;
      }
    }
#endif
  }
  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;
}
Esempio n. 23
0
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 tailTimeInMs = 0;
  unsigned long tailTimeInFrames = 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 silentSampleInput;
  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->options[OPTION_INPUT_SOURCE], true);
    _remapFileToErrorReport(errorReporter, programOptions->options[OPTION_OUTPUT_SOURCE], false);
    _remapFileToErrorReport(errorReporter, programOptions->options[OPTION_MIDI_SOURCE], true);
    _remapFileToErrorReport(errorReporter, programOptions->options[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:
          setBlocksize((const unsigned long)programOptionsGetNumber(programOptions, OPTION_BLOCKSIZE));
          break;
        case OPTION_CHANNELS:
          setNumChannels((const unsigned long)programOptionsGetNumber(programOptions, OPTION_CHANNELS));
          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_SAMPLE_RATE:
          setSampleRate(programOptionsGetNumber(programOptions, OPTION_SAMPLE_RATE));
          break;
        case OPTION_TAIL_TIME:
          tailTimeInMs = (long)programOptionsGetNumber(programOptions, OPTION_TAIL_TIME);
          break;
        case OPTION_TEMPO:
          setTempo(programOptionsGetNumber(programOptions, OPTION_TEMPO));
          break;
        case OPTION_TIME_SIGNATURE:
          if(!setTimeSignatureFromString(programOptionsGetString(programOptions, OPTION_TIME_SIGNATURE))) {
            return RETURN_CODE_INVALID_ARGUMENT;
          }
          break;
        case OPTION_ZEBRA_SIZE:
          setLoggingZebraSize((int)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 = newSampleSourceSilence();
        }
      }
    }
    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);
  // Get largest tail time requested by any plugin in the chain
  tailTimeInMs += pluginChainGetMaximumTailTimeInMs(pluginChain);
  tailTimeInFrames = (unsigned long)(tailTimeInMs * getSampleRate()) / 1000l + processingDelayInFrames;
  pluginChainPrepareForProcessing(pluginChain);

  // Update sample rate on the event logger
  setLoggingZebraSize((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);

  silentSampleInput = sampleSourceFactory(NULL);
  silentSampleOutput = sampleSourceFactory(NULL);
  // Main processing loop
  while(!finishedReading) {
    taskTimerStart(inputTimer);
    finishedReading = !readInput(inputSource, silentSampleInput, inputSampleBuffer, tailTimeInFrames);

    // 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 = !fillMidiEventsFromRange(midiSequence, audioClock->currentFrame, getBlocksize(), midiEventsForBlock);
      linkedListForeach(midiEventsForBlock, _processMidiMetaEvent, &finishedReading);
      pluginChainProcessMidi(pluginChain, midiEventsForBlock);
      freeLinkedList(midiEventsForBlock);
    }
    taskTimerStop(inputTimer);

    if(maxTimeInFrames > 0 && audioClock->currentFrame >= maxTimeInFrames) {
      logInfo("Maximum time reached, stopping processing after this block");
      finishedReading = true;
    }

    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);
    }
    writeOutput(outputSource, silentSampleOutput, outputSampleBuffer, processingDelayInFrames);
    taskTimerStop(outputTimer);
    advanceAudioClock(audioClock, outputSampleBuffer->blocksize);
  }

  // Close file handles for input/output sources
  silentSampleInput->closeSampleSource(silentSampleInput);
  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;
}