summaryrefslogtreecommitdiff
path: root/source/MrsWatson.c
blob: ae01976ade75dfdb58c9b11f715388d36f30dc26 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
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;
}