// QuantumTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>
#include "graal_isolate.h"
#include "Proton.h"

const int NB_CHANS = 2; // [1 - 8] the demo audio file is stereo. If you change this value you may need to change the demo audio file too.
const float SAMPLE_RATE = 44100.0f; // [32000 - 192000] the demo audio file is at 44.1 kHz
const double TIME_STRETCH_RATIO = 1.2; // [0.25 - 4.0] time-stretching ratio.
const int QUALITY = 1; // 0 = Fast, 1 = Quality
const int MODE = 1; // [0..2]: 0 = Solo; 1 = Poly; 2 = Smooth (slow)
const float TRANSIENT_SENSITIVITY = 0.5f; // [0.0 - 1.0] transient sensitivity.

FILE* inputFile;

/*
* This is the callback function invoked by proton_process(). Its purpose is to supply input audio samples (the audio to transform).
*
* The audio samples are represented by 32-bit floating point values (float) between -1.0 and 1.0.
* It is ok to have occasional samples over the [-1.0, 1.0] range, and in fact the algorithm might output such samples, even if
* the input is normalized. It is your responsability to clip or limit the result if desired.
* However, please do NOT supply values in the [-32768.0, 32767.0] range or any other range!
* 16-bit/24-bit/32-bit signed integer audio for instance must be properly converted to floats between -1.0 and 1.0.
*
* The channels are interleaved. On a stereo signal, the data consists of sample 0 of left channel, sample 0 of right channel,
* sample 1 of left channel, sample 1 of right channel, etc. 6 channels can be used for 5.1 audio. The order of the channels
* does not matter (there is no special treatment for the bass channel for instance) but must be consistent.
*
* The length argument is a number of "audio sample frames". 1 audio sample frame = one audio sample in each channel.
* Hence, a length of 1 on stereo audio stream means two 32-bit float values (on per channel), and hence '2 * sizeof(float)' bytes.
* The returned value obeys the same convention.
*
* The function must put the requested number of audio sample frames into the "target" pointer argument, and return the number
* of audio sample frames that have actually been provided.
*
* The function must ALWAYS provide exactly the requested number of audio sample frames, UNLESS the end of the audio stream has
* been reached, in which case the number of remaining audio sample frames must be returned.
* The library will assume end of stream (EOS) as soon as the returned value is less than the requested length.
*
* If the function is invoked again after EOS, it must return 0.
* If the function is invoked with a 0 length, it must do nothing and return 0 (this should not happen though...).
*
* Do not expect any regular pattern about the number of audio sample frames that are requested. Due to the complexity of the
* algorithm, each call might potentially request a completely different length. Furthermore, for a given call to proton_process(),
* this callback function can be invoked any number of time (including not at all).
*
* In this example, the implementation reads from a demo file that has exactly the required raw format (32-bit float samples
* between -1.0 and 1.0, interleaved).
*
* In practice, the callback function will read from the corresponding tracks of your DAW or application.
*/
int readSamples(float* target, int length) {
    printf("requested input audio frames %i\n", length);
    int result = (int)fread(target, sizeof(float), length * NB_CHANS, inputFile);
    /*
    * fread() already returns a smaller value than the requested one when EOF is reached, so we can just
    * use this value as the return value. It will also return 0 if invoked again after EOF, which is what
    * is expected as well by Proton.
    */
    result /= NB_CHANS;
    printf("supplied input audio frames %i\n", result);
    return result;
}

int main() {

    graal_isolate_t* isolate = NULL;
    graal_isolatethread_t* thread = NULL;

    /*
    * Create an isolate and an isolate_thread.
    * This code is specific to the GraalVM Java implementation used by this library.
    * 
    * An isolate is a structure that manages the Java Heap and garbage collector.
    * An isolate_thread manages the parts of the stack (of a given thread) that belongs to Java code.
    * These structures are necessary to apply garbage collection to objects created by Java code, without
    * touching those created by C/C++ code.
    * 
    * BTW any memory allocated by C/C++ code must be freed by C/C++ code, and any memory allocated by Java
    * code must NOT be freed by C/C++ code.
    * 
    * This code is assuming all the processing is done by a single thread. To use multiple threads (in general in
    * that case each thread will create its own engine to process a different audio track, because multi-thread processing of
    * a single audio stream is not supported), a 'graal_isolatethread_t' must be created for each additional thread
    * using graal_attach_thread(). See graal_isolate.h for more.
    * 
    * It is also possible to create two different isolates (and not only different isolate threads), in which case
    * two separate memory heaps will be used. Note that a given engine must always be used with the same isolate.
    * See later for engine creation.
    */
    if (graal_create_isolate(NULL, &isolate, &thread) != 0) {
        fprintf(stderr, "initialization error\n");
        return 1;
    }

    /*
    * Open our demo audio file, that will be read by the callback function readSamples().
    * The result of time-stretching this file will be written in another file (see later).
    */
    inputFile = fopen("Gab.raw32", "rb");
    if (!inputFile) {
        fprintf(stderr, "could not open input file\n");
        return 1;
    }

    /*
    * Create a file into which the result is written. In practice you will instead write to your DAW or application's
    * audio track.
    * 
    * In this demo code, data is written directly in raw format. You can open it for listening using Audacity, menu "File",
    * "Import...", "Raw data...", then choose encoding "32-bit float", "little endian" and number of channels "2".
    */
    FILE* outputFile = fopen("Result.raw32", "wb");
    if (!outputFile) {
        fprintf(stderr, "could not create output file\n");
        return 1;
    }

    /*
    * Create an engine. An engine can process an audio stream of 1 to 8 channels. Multiple engines can be created, for
    * instance to process multiple audio streams. An engine cannot be reused when it has finished processing a stream.
    * 
    * An audio stream generally corresponds to an audio track in your DAW or application, but can also correspond to
    * a selection. It can be any contiguous audio data. Do not use the same engine to process multiple non contiguous
    * selections or different tracks.
    * 
    * The result is an opaque value that must be passed to other methods and must not be interpreted. Only the null value
    * can be checked for, and means the engine could not be created; in general because one of the values is out of range
    * (check for roundoff errors, such as 0.2499999 instead of 0.25 for example).
    * 
    * The sample rate must be between 32000 (32 kHz) and 192000 (192 kHz).
    * The number of channels must be between 1 and 8 (for instance for 7.1 surround); 2 is stereo.
    *   Note: only use channels for spatial/stereo audio because the engine will preserve the phase relations
    *   between channels in order to preserve the stereo/spatial field. If you have different tracks corresponding to different
    *   "sources" (such as vocal, bass, etc), it is better to process each of these tracks using a separate engine, grouping
    *   only the left and right channels belonging to the same "source".
    * The time stretch ratio is between 0.25 (4 times faster) and 4.0 (4 times slower).
    * The quality is between 1 and 5. The value 3 is recommanded as a starting point.
    * The transient sensitivity is between 0.0 and 1.0. The value 0.5 is recommanded as a starting point.
    *   This value is ignored if the quality is < 3, because no transient detection/processing is done at quality 2 or 1.
    */
    void* engine = proton_createEngine(thread, SAMPLE_RATE, NB_CHANS, TIME_STRETCH_RATIO, QUALITY, MODE, TRANSIENT_SENSITIVITY);
    if (engine == NULL) {
        fprintf(stderr, "could not create engine\n");
        return 1;
    }

    /*
    * The engine uses an iterative "pull" approach: in each iteration, it produces a fixed amount of output audio frames,
    * and uses a callback to request the required input audio frames. This method queries the fixed amount of output
    * audio frames that will be produced in each iteration. It is used to preallocate a buffer for output audio frames.
    * 
    * The result is in audio sample frames (one audio sample in each channel)
    */
    int outputChunkLength = proton_getOutputChunkLength(thread, engine);
    printf("output audio chunk length: %i\n", outputChunkLength);

    /*
    * Allocate a buffer for output audio samples. Note that the previously requested length must be multiplied by the number
    * of channels, because it is given in audio sample frames (one audio sample in each channel).
    * 
    * If allocating bytes directly using malloc(), also multiply by 'sizeof(float)'.
    */
    float* outputChunk = new float[outputChunkLength * NB_CHANS];
    int outputOffset = 0; // This is just to track the amount of output audio frames produced

    /*
    * The following call is optional.
    * 
    * If not present, off-line mode is assumed. The latency will automatically be compensated for.
    * In off-line mode, the first call to proton_process() might be significantly slower than the
    * subsequent ones, and may also allocate objects and invoke the callback function several times.
    * 
    * If present, real-time mode is assumed. This call will prepare the engine so that the
    * calls to proton_process() are all approximately the same speed, and are all reading approximately
    * the same amout of audio input from the callback function.
    * 
    * On the other hand, in real-time mode, the latency is *not* compensated for, and is hence
    * returned by this method. It is given in output sample frames. It can be fractional if the time-stretch
    * ratio is fractional. In practice you can safely round it to the nearest integer (or ignore it
    * if you do not want to compensate for latency)
    */
    // double latency = proton_prepareRealtime(thread, engine, TIME_STRETCH_RATIO);
    // printf("latency: %f\n", latency);

    /*
    * Processing is always done in an iteration
    */
    while (true) {
        /*
        * This is the core "processing" function.
        * The isolate thread, and the engine must be supplied as 1st and 2nd arguments.
        * The 3rd argument is the callback function to supply input samples,
        *   readSamples() in this example. See before the main() function.
        * The 4th argument is the pointer into which the next chunk of output audio will be written.
        * The result is the number of audio sample frames actually written.
        *   This can be any amount, the only guarantee is that it is not greater than the value returned by
        *   proton_getOutputChunkLength(). In practice, this is exactly that value, except for the last
        *   chunk, but this might change in future versions.
        *   A result of -1 means that the end of the output audio stream has been reached.
        */
        int produced = proton_process(thread, engine, &readSamples, outputChunk);
        printf("produced: %i\n", produced);
        if (produced < 0) {
            /*
            * The loop terminates when -1 is returned.
            * This will only happens if the callback function (function readSamples() above in this example)
            * properly reports the end of the input audio stream by returning less than the requested number
            * of audio sample frames. Note however that once readSamples() reports the end of the input
            * audio stream, an arbitrary number of additional iterations can be necessary until the end
            * of the output audio stream is reached.
            * 
            * One -1 has been returned, the engine can no longer be used. The only next valid call is to destroy it
            * with proton_destroyEngine(). To process another audio stream, a new engine must be created.
            */
            printf("eof mark received; leaving loop\n");
            break;
        }

        /*
        * In this example, we append the output audio chunk in a file created before the loop. In practice you will write it
        * to the appropriate track of your DAW or application. Be sure to write the amount of data as returned by the
        * proton_process(), and not the fixed amount returned by proton_getOutputChunkLength(), because they will
        * not match in the last iteration.
        * 
        * See the comment below on the readSamples() function for more about the actual audio format.
        * 
        * Notes:
        * - do not expect the total output length to be exactly the total input length times the time stretching ratio:
        *   - in practice the rounding performed by proton might be different than yours
        *   - with quality 3 and greater, the output length is ususally not more than one audio sample frame apart from the exact
        *     theoretical length. With quality 1 or 2 on the other hand, a subsequent drift can be expected.
        * - even if all input audio samples are between -1.0 and 1.0, the output audio samples may occasionally be outside
        *   of this range (especially on compressed tracks with no headroom). It is your responsability to clip or
        *   compress the result, if desired.
        */
        fwrite(outputChunk, sizeof(float), produced * NB_CHANS, outputFile);
        outputOffset += produced; // This is just to track the total number of produced output audio sample frames...
    }

    // Processing is complete. Here we close the input and output audio files
    printf("total output audio frames: %i\n", outputOffset);
    fclose(outputFile);
    fclose(inputFile);

    /*
    * Delete our output audio chunk array.
    */
    delete[] outputChunk;

    /*
    * Destroy the engine to release the associated memory and resources.
    */
    proton_destroyEngine(thread, engine);

    /*
    * If all engines created with this isolate have been destroyed, destroying the isolate itself will further release
    * resources. It is still possible to create a new isolate, isolate thread and engine afterwards to process
    * audio streams again.
    * 
    * Any engine that was created with this isolate and has not be destroyed can no longer be used after this call.
    */
    graal_tear_down_isolate(thread);
}

/*
* Note about aborting in the middle of the process (for example if the user cancels the operation):
* The best way is to:
* - ensure the callback function (readSamples() in this example) repeteadly returns 0 (when the operation is cancelled)
* - your code should continue to iterate the loop until proton_process() return -1 to properly drain the output.
* Aborting the loop without waiting for the output to drain may not properly release ressources, altough
* in general destroying the engine AND the isolate should then free everything.
*/

/*
* Note about multi-threading and multi-core:
* - a single engine cannot use multiple cores/threads to process an audio stream faster.
* - in general, a given engine must be used by a single thread. It is possible to use it by multiple threads only if the calls
*   are strictly sequentials, and proper memory fences are issued. Furthermore, all threads must be attached to the isolate.
* - multiple engines can be created and used by a single thread, for instance to process multiple tracks.
* - multiple engines can be created by multiple threads (one engine per thread) to process multiple *different* audio streams
*   concurrently. Again all threads must be attached to the isolate.
*   - it is possible to create multiple engines using the same isolate, or to create one isolate per engine.
*     - with one isolate per engine, more memory is used, but the performance can be better (in terms of scalability)
*       because each isolate has its own memory management and garbage collector, that does not block the other engines.
* 
*
* The library is designed in such a way to avoid garbage collections during processing. However, this only works if
* exactly one isolate is created per engine. Creating and destroying multiple engines in the same isolate may trigger
* garbage collections (but will use a little less memory).
*/
