// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0

#pragma once

#include <memory>
#include <functional>
#include <vector>
#include <string>

typedef unsigned int uint;

/** \file liquidsfz.hh
 * \brief This header contains the public API for liquidsfz
 */

namespace LiquidSFZ
{

/**
 * \brief Log levels for @ref LiquidSFZ::Synth
 */
enum class Log {
  DEBUG,
  INFO,
  WARNING,
  ERROR,
  DISABLE_ALL // special log level which can be used to disable all logging
};

/**
 * \brief Information for one continuous controller
 */
class CCInfo
{
  friend class Synth;

  struct Impl;
  std::unique_ptr<Impl> impl;
public:
  CCInfo();
  CCInfo (CCInfo&&) = default;
  ~CCInfo();

  /**
   * @returns CC controller number (0-127)
   */
  int cc() const;

  /**
   * Some .sfz files contain labels (label_cc opcode) for the CCs they provide.
   * If a label is available, this function will return it.  If no label was
   * defined in the .sfz file, a label will be generated automatically (like
   * "CC080"). To check if a label is available, call @ref has_label().
   *
   * @returns label for the CC controller
   */
  std::string label() const;

  /**
   * This function returns true if the .sfz file contains a label for
   * this CC (that is, if a label_cc opcode was found for this CC).
   *
   * @returns true if a label is available.
   */
  bool has_label() const;

  /**
   * This contains the initial value for the CC.
   *
   * @returns the a CC value (0-127)
   */
  int default_value() const;
};

/**
 * \brief Information for one key
 */
class KeyInfo
{
  friend class Synth;

  struct Impl;
  std::unique_ptr<Impl> impl;
public:
  KeyInfo();
  KeyInfo (KeyInfo&&) = default;
  ~KeyInfo();

  /**
   * @returns midi key number (1-127)
   */
  int key() const;

  /**
   * Some .sfz files assign labels to some of the keys; if no label is
   * available this function will return "".
   *
   * @returns label for the key
   */
  std::string label() const;

  /**
   * Get flag that indicates whether this key acts is a key switch (as
   * opposed to regular midi notes).
   *
   * @returns true if this is a key switch
   */
  bool is_switch() const;
};

/**
 * \brief Information for one AriaBank program
 */
class ProgramInfo
{
  friend class Synth;

  struct Impl;
  std::unique_ptr<Impl> impl;
public:
  ProgramInfo();
  ProgramInfo (ProgramInfo&&) = default;
  ~ProgramInfo();

  /**
   * This function is just provided for convenience, the program index returned by this
   * is guaranteed to be identical to the index of the ProgramInfo in the vector.
   *
   * @returns program index (starting at 0)
   */
  uint index() const;

  /**
   * In AriaBank .bank.xml files a label (for the end user) is provided for each program.
   *
   * @returns a label for the program
   */
  std::string label() const;
};

/**
 * \brief SFZ Synthesizer (main API)
 */
class Synth
{
  struct Impl;
  std::unique_ptr<Impl> impl;

public:
  Synth();
  ~Synth();

  /**
   * \brief Set sample rate of the synthesizer
   *
   * @param sample_rate   new sample rate
   */
  void set_sample_rate (uint sample_rate);

  /**
   * \brief Get sample rate
   *
   * @returns the sample rate
   */
  uint sample_rate() const;

  /**
   * \brief Set maximum number of voices
   *
   * @param n_voices   maximum number of voices
   */
  void set_max_voices (uint n_voices);

  /**
   * \brief Get maximum number of voices
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   *
   * @returns maximum number of voices (maximum polyphony)
   */
  uint max_voices() const;

  /**
   * \brief Set live mode
   *
   * @param live_mode  new value for live mode
   *
   * Usually, liquidsfz loads samples on demand without blocking which is the
   * correct thing to do when used live in a real time audio thread. This
   * function can be used to make liquidsfz process() function wait for the
   * samples to be loaded.
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void set_live_mode (bool live_mode);

  /**
   * \brief Get live mode
   *
   * See @ref set_live_mode().
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   *
   * @returns whether synth is in live mode
   */
  bool live_mode() const;

  /**
   * \brief Set preload time
   *
   * @param time_ms  new value for the preload time in milliseconds
   *
   * Configure preload time for samples in milliseconds. This function must be
   * called before @ref load(). In some cases (for instance if the offset opcode
   * is used), the actual preload time will be higher than this value. The
   * default value for the preload time is 500ms.
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void set_preload_time (uint time_ms);

  /**
   * \brief Get preload time
   *
   * See @ref set_preload_time().
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   *
   * @returns preload time in milliseconds
   */
  uint preload_time() const;

  /**
   * \brief Set sample quality
   *
   * @param sample_quality new value for sample quality
   *
   * Select interpolation quality (this is called sample_quality, because there
   * is an SFZ2 opcode called sample_quality that does just this). Currently
   * liquidsfz implements 3 different levels for the interpolation quality.
   *
   * 1. linear interpolation
   * 2. polynomial interpolation (hermite 6 point, 3rd order)
   * 3. high quality interpolation (hybrid 2x upsampling + 4 point polynomial interpolation)
   *
   * The default for this value is 3, which provides the best quality at a
   * reasonable performance on modern systems.
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void set_sample_quality (int sample_quality);

  /**
   * \brief Get sample quality
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   *
   * @returns current sample quality
   */
  int sample_quality();

  /**
   * \brief Get active voice count
   *
   * The maximum number of voices can be set using \ref set_max_voices.
   * Note that the number of voices that are active is not the same as
   * the number of midi notes, as synthesizing one note can require more
   * than one voice, for instance if multiple samples are cross-faded.
   *
   * @returns the number of currently active voices
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  uint active_voice_count() const;

  /**
   * \brief Set global gain
   *
   * @param gain  gain (as a factor)
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void set_gain (float gain);

  /**
   * \brief Load .sfz file including all samples
   *
   * @param filename   name of the .sfz file
   *
   * @returns true if the .sfz could be loaded successfully
   *
   * Typically, this function is called with an .sfz filename. It can also be
   * used with a hydrogen \c drumkit.xml file. In this case the drumkit is loaded
   * by mapping hydrogen to sfz features. The format will be auto-detected,
   * it will be treated as hydrogen if it contains typical hydrogen tags.
   */
  bool load (const std::string& filename);

  /**
   * \brief Check if the file is in AriaBank format
   *
   * @param filename name of the .bank.xml file
   *
   * @returns true if the file is in AriaBank format and can be loaded by
   * load_bank().
   */
  bool is_bank (const std::string& filename) const;

  /**
   * \brief Load an AriaBank file
   *
   * @param filename name of the .bank.xml file
   *
   * @returns true if the .bank.xml file could be loaded sucessfully
   *
   * This only loads the program index, after loading it the programs can
   * be listed by list_programs() and programs can be loaded by select_program().
   */
  bool load_bank (const std::string& filename);

  /**
   * \brief List available programs defined in AriaBank file
   *
   * @returns a vector of ProgramInfo entries
   *
   * This is only valid if a bank has been loaded using load_bank(), if no bank
   * has been loaded the vector will be empty.
   */
  std::vector<ProgramInfo> list_programs() const;

  /**
   * \brief Select program from previously loaded AriaBank file
   *
   * @returns true if the corresponding .sfz file could be loaded successfully
   */
  bool select_program (uint program);

  /**
   * \brief List CCs supported by this .sfz file
   *
   * @returns a vector that contains information for each supported CC
   */
  std::vector<CCInfo> list_ccs() const;

  /**
   * \brief List active keys used by this .sfz file
   *
   * A key is active if either a sample will be played if the key pressed or
   * the key acts as key switch. Inactive keys (which do nothing) will not be
   * part of the returned vector.
   *
   * @returns a vector that contains information for the supported keys
   */
  std::vector<KeyInfo> list_keys() const;

  /**
   * \brief Add a note on event
   *
   * This function adds a note on event to the event list that will be rendered
   * by the next process() call. A note on with velocity 0 will be treated as
   * note off.
   *
   * Events must always be added sorted by event time (time_frames).
   *
   * @param time_frames the start time of the event (in frames)
   * @param channel     midi channel
   * @param key         note that was pressed
   * @param velocity    velocity for note on
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void add_event_note_on (uint time_frames, int channel, int key, int velocity);

  /**
   * \brief Add a note off event
   *
   * This function adds a note off event to the event list that will be rendered
   * by the next process() call.
   *
   * Events must always be added sorted by event time (time_frames).
   *
   * @param time_frames the start time of the event (in frames)
   * @param channel     midi channel
   * @param key         note that was pressed
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void add_event_note_off (uint time_frames, int channel, int key);

  /**
   * \brief Add a cc event
   *
   * This function adds a cc event to the event list that will be rendered
   * by the next process() call.
   *
   * Events must always be added sorted by event time (time_frames).
   *
   * @param time_frames the start time of the event (in frames)
   * @param channel     midi channel
   * @param cc          the controller
   * @param value       new value for the controller
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void add_event_cc (uint time_frames, int channel, int cc, int value);

  /**
   * \brief Add a pitch bend event
   *
   * This function adds a pitch bend event to the event list that will be
   * rendered by the next process() call.
   *
   * Events must always be added sorted by event time (time_frames). The
   * pitch bend value must in range [0..16383], center value is 8192.
   *
   * @param time_frames the start time of the event (in frames)
   * @param channel     midi channel
   * @param value       new value for the controller [0..16383]
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void add_event_pitch_bend (uint time_frames, int channel, int value);

  /**
   * \brief Synthesize audio
   *
   * This will render one stereo output stream, left output to outputs[0] and
   * right output to outputs[1]. The contents of the output buffers will be overwritten.
   *
   * Events that should be processed by this function can be added by calling some
   * event functions before process():
   *  - add_event_note_on()
   *  - add_event_note_off()
   *  - add_event_cc()
   *  - add_event_pitch_bend()
   *
   * These events will processed at the appropriate time positions (which means
   * we provide sample accurate timing for all events). Events must always be
   * added sorted by event time. After process() is done, the contents of event
   * buffer will be discarded.
   *
   * @param outputs    buffers for the output of the synthesizer
   * @param n_frames   number of stereo frames to be written
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void process (float **outputs, uint n_frames);

  /**
   * \brief All sound off
   *
   * Stop all active voices immediately. This may click because the voices are
   * not faded out.
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void all_sound_off();

  /**
   * \brief System reset
   *
   * Reset Synth state:
   *  - reset all CC values to their defaults
   *  - stop all active voices immediately
   *
   * This may click because the voices are not faded out.
   *
   * <em>This function is real-time safe and can be used from the audio thread.</em>
   */
  void system_reset();

  /**
   * \brief Set minimum log level
   *
   * This function can be used to set the minimum level of log messages that you
   * are interested in. By default this is set to Log::INFO, which means that you
   * will get errors, warnings and info messages, but not debug messages. Note
   * that if you want to ignore some messages, it is better (more efficient) to
   * do so by setting the log level than by setting a logging function with
   * set_log_function() and ignoring some messages.
   *
   * If you want to disable all messages, log_level can be set to Log::DISABLE_ALL.
   *
   * @param log_level minimum log level
   */
  void set_log_level (Log log_level);

  /**
   * \brief Set custom logging function
   *
   * @param log_function function to be called for log entries
   */
  void set_log_function (std::function<void (Log, const char *)> log_function);

  /**
   * \brief Set progress function
   *
   * Loading .sfz files can take a long time. In order to give feedback to the
   * user, a progress function can be registered before calling load().  This
   * progress function will be called during load() periodically with a
   * percentage from 0%..100%. This can be used to display a progress bar at
   * the ui.
   *
   * @param progress_function function to be called
   */
  void set_progress_function (std::function<void (double)> progress_function);

  /**
   * \brief Get memory used by sample cache
   *
   * The sample cache is shared between all liquidsfz Synth instances, so the
   * value returned by this function is the total memory used for all samples
   * used by all instance together.
   *
   * <em>This function is real-time safe and can be safely called from any
   * thread at any time without synchronization.</em>
   *
   * @returns the size of the sample cache in bytes
   */
  size_t cache_size() const;

  /**
   * \brief Get number of cached samples
   *
   * The sample cache is shared between all liquidsfz Synth instances, so the
   * number of cached sample files returned by this function is the total
   * number of samples used by all instance together.
   *
   * <em>This function is real-time safe and can be safely called from any
   * thread at any time without synchronization.</em>
   *
   * @returns number of cached samples
   */
  uint cache_file_count() const;

  /**
   * \brief Get number of sample cache lookup misses
   *
   * Returns the number of times a sample data lookup in the shared sample
   * cache failed. In this case the requested sample data was not available in
   * the cache and zero values were used instead, which may result in an
   * audible glitch.
   *
   * The sample cache is shared between all liquidsfz Synth instances, so the
   * number returned by this function reflects the total number of cache
   * misses across all instances.
   *
   * <em>This function is real-time safe and can be safely called from any
   * thread at any time without synchronization.</em>
   *
   * @returns number of sample cache lookup misses
   */
  uint cache_miss_count() const;

  /**
   * \brief Set maximum memory used by sample cache
   *
   * @param max_cache_size maximum number of bytes to use for the cache
   *
   * Set maximum memory used by sample cache. Note that in some cases, the
   * preloading requirements will make it impossible to free memory, so this
   * limit can't always be enforced by the sample cache.
   *
   * The sample cache is shared between all liquidsfz Synth instances, this
   * limit affects all instances.
   *
   * <em>This function is real-time safe and can be safely called from any
   * thread at any time without synchronization.</em>
   */
  void set_max_cache_size (size_t max_cache_size);

  /**
   * \brief Get maximum memory used by sample cache
   *
   * This function returns the limit for the number of bytes used by the
   * sample cache, see @ref set_max_cache_size().
   *
   * <em>This function is real-time safe and can be safely called from any
   * thread at any time without synchronization.</em>.
   */
  size_t max_cache_size() const;
};


/**
 *
\mainpage LiquidSFZ

\section intro_sec Introduction

LiquidSFZ is a library to load and replay sample based instruments in .sfz format. If you want
to use libquidsfz in your own project, use the LiquidSFZ::Synth class.

\section simple_example A Simple Example

\code
// Compile this example with:
// $ g++ -o example example.cc `pkg-config --libs --cflags liquidsfz`

#include <liquidsfz.hh>

int
main (int argc, char **argv)
{
  if (argc != 2)
    {
      printf ("usage: %s <sfz_filename>\n", argv[0]);
      return 1;
    }

  LiquidSFZ::Synth synth;
  synth.set_sample_rate (48000);

  if (!synth.load (argv[1])) // load sfz
    {
      printf ("%s: failed to load sfz file '%s'\n", argv[0], argv[1]);
      return 1;
    }

  // start a note
  synth.add_event_note_on (0, 0, 60, 100);

  // render one block of audio data
  float left_output[1024];
  float right_output[1024];
  float *output[2] = { left_output, right_output };
  synth.process (output, 1024);

  // at this point we would typically play back the audio data
  for (int i = 0; i < 1024; i++)
    printf ("%d %f %f\n", i, left_output[i], right_output[i]);
}
\endcode

# Using multiple threads

The LiquidSFZ API, unless otherwise documented, doesn't use locks to make the
API thread safe. This means that you are responsible that <b>only one API
function is executed at a time</b>.

So you must not call a function (i.e. \ref Synth::process) in thread A and
another function (i.e. \ref Synth::load) in thread B so that both are running
at the same time. Instead you need to either use a mutex to ensure that only
one function is running at a time, or use other synchronization between the
threads A and B to ensure that only one thread uses the \ref Synth instance at
any point in time.

Note that if you have more than one \ref Synth instance (object), these can be
treated as isolated instances, i.e. different instances can be use concurrently
from different threads.

## The audio thread

Most applications will have one single real-time thread that generates the
audio output. In this real-time thread, only some of the API functions provided
by liquidsfz should be used, to avoid stalling the audio thread (for instance
by waiting for a lock, allocating memory or doing file I/O). Each of the
functions on this list is implemented wait-free (which means there is an
upper bound on the number of steps required).

- \ref Synth::active_voice_count
- \ref Synth::add_event_note_on
- \ref Synth::add_event_note_off
- \ref Synth::add_event_cc
- \ref Synth::add_event_pitch_bend
- \ref Synth::all_sound_off
- \ref Synth::process
- \ref Synth::set_gain
- \ref Synth::system_reset

Typically these restrictions mean that at the beginning of your application,
you setup the \ref Synth object by loading an .sfz file, setting the sample
rate, number of voices and so forth. Eventually this is done, and you start a
real-time thread that does audio I/O. Since you must ensure that only one
thread at a time is using the \ref Synth object, you would then only use
the functions of this list in the audio thread and no longer use any other
function from any other thread.

If you need to access one of the other functions (for instance for loading a
new .sfz file using \ref Synth::load), you would ensure that the audio thread
is no longer using the \ref Synth object. Then the setup phase would start
again, loading, setting sample rate and so forth, until you can restart the
audio thread.

## Callbacks from load

It is possible to request progress information for \ref Synth::load by using
\ref Synth::set_progress_function. If you enable this, the progress function
will be invoked in the same thread that you called \ref Synth::load.

## Callbacks from the logging framework

If you setup a custom log function using \ref Synth::set_log_function,
callbacks will occur in the threads that you call functions in. For example,
if you \ref Synth::load during initialization, you will get callbacks in the
thread you started load in.

Messages that are <b>relevant to end users</b> are errors, warnings and info
messages.  For these log levels, we guarantee that such log messages are never
produced from functions that can be run in the audio thread (real-time thread).
These log messages are enabled by default.

This means that the callback set by \ref Synth::set_log_function doesn't need
to be realtime safe, as these callbacks only occur in non-rt-safe functions
such as \ref Synth::load.

Messages that are <b>relevant to developers</b> use the log level debug. These
can occur from any thread, including real-time safe functions like \ref
Synth::process. Debug messages are only relevant for developers, so in almost
every case you should keep them disabled, which is the default. If you enable
debug messages using \ref Synth::set_log_level, you either need to provide an
realtime safe logging callback or accept that during development, debug
messages will break realtime capabilty.

*/

}
