/** * 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); } }
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(¤tTime, 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); } }
static int _testInitAudioClock(void) { AudioClock audioClock = getAudioClock(); assertUnsignedLongEquals(ZERO_UNSIGNED_LONG, audioClock->currentFrame); assertFalse(audioClock->isPlaying); assertFalse(audioClock->transportChanged); return 0; }
static int _testStopAudioClock(void) { AudioClock audioClock = getAudioClock(); advanceAudioClock(audioClock, kAudioClockTestBlocksize); audioClockStop(audioClock); assertFalse(audioClock->isPlaying); assert(audioClock->transportChanged) return 0; }
static int _testAdvanceAudioClock(void) { AudioClock audioClock = getAudioClock(); advanceAudioClock(audioClock, kAudioClockTestBlocksize); assertUnsignedLongEquals(kAudioClockTestBlocksize, audioClock->currentFrame); assert(audioClock->isPlaying); assert(audioClock->transportChanged); return 0; }
static int _testAdvanceClockMulitpleTimes(void) { AudioClock audioClock = getAudioClock(); int i; for (i = 0; i < 100; i++) { advanceAudioClock(audioClock, kAudioClockTestBlocksize); } assert(audioClock->isPlaying); assertFalse(audioClock->transportChanged); assertUnsignedLongEquals(kAudioClockTestBlocksize * 100, audioClock->currentFrame); return 0; }
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; }
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(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: { 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 = 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; }
static void _audioClockTestTeardown(void) { freeAudioClock(getAudioClock()); }