#include <stdio.h>
#include <string.h>

#include "SoundBuffer.h"
#include "Constants.h"
#include "Timing.h"
#include "Log.h"

#define _DEBUG_BUFFER_SIZE 256
static char _debugBuffer[_DEBUG_BUFFER_SIZE];

// aside from implementing a regular circular sound sample buffer, this module also 
// uses edge detection to categorize frames (defined according to consumer - but normally 
// video frames) into categories:
// 
//     - frames playing one sustained tone (i.e. equally-spaced edges)
//     - frames playing more complex tones (i.e. edges spaced relatively differently)
//     - silent frames (i.e. no edges)
//
// depending on the type of frame, a synchronization template is chosen:
// 
// templates for when synchronization occurs (provides default values for synchronization)
// 
//   - EAGER will synchronize frequently, enforcing a narrow interval
//      Used when: frame is playing more complex tones
//                 frame is silent
//           Pros: resyncs are almost never noticeable during normal gameplay, when mixed tones play
//                 forces aggressive "catching up" during silence, with likely zero audio impact
//           Cons: sustained tones sound choppy (e.g. long BEEP commands, or the tape lead tone)
// 
//   - LAZY will synchronize infrequently, enforcing a wide interval
//      Used when: frame is playing one sustained tone
//           Pros: clear tones (sustained or mixed)
//           Cons: high latency, resyncs are noticeable
//
// NOTE: during the development of version 31, timing (tstate/scanline) improved, which lowered
//       resyncs/s greatly - from ~6/s to ~0.2/s. However, even so, sustained tones crackled (e.g.
//       during tape lead-in tones - because tstate/scanline overflow adjustment fluctuated.
//       Thus, it was decided to keep the lazy/eager strategy in place, despite the extra complexity
enum soundSyncTemplate { Eager = 0, Lazy = 1 };
#define SOUNDBUFFER_DEFAULT_SOUND_SYNC_TEMPLATE Eager

// thresholds used to find edges among normalized samples
#define EDGE_LOW_THRESHOLD (0.4f)
#define EDGE_HIGH_THRESHOLD (0.6f)

// EXPERIMENTALLY DETERMINED
// 6.0f causes stutters in lead-in tone when loading, and misses "clicks" when typing continuously
// 8.0f causes too many stutters in nipper2 menu
#define BUFFER_SIZE_FROM_FRAME_DURATION_FACTOR (10.0f)

static Uint8 _inInitialSyncPeriod = 1;

// holds our samples, one byte per sample
static float* _buffer;
static Uint32 _bufferSize;
// holds sync templates per sample, so that different syncs can be used for various
// sound shapes (constant tone, etc.)
static enum soundSyncTemplate* _syncTemplateBuffer;

static Sint64 _risingEdgesThisFrame;
static Sint64 _fallingEdgesThisFrame;
static Sint64 _samplesThisFrame;
static Sint64 _risingEdgePeriodThisFrame;
static Sint64 _fallingEdgePeriodThisFrame;
static Sint64 _lastRisingEdgeSampleNumber;
static Sint64 _lastFallingEdgeSampleNumber;
static Uint8 _risingEdgesAreUniformThisFrame;
static Uint8 _fallingEdgesAreUniformThisFrame;

static Uint64 _framesWithLazySync = 0;
static Uint64 _framesWithEagerSync = 0;

static Uint8 _userHasOverriddenASetting = 0;

// tends to take longer to synchronize on faster computers/newer OSes
// so we wait for this long before we record any resyncs, for the statistics
// EXPERIMENTALLY DETERMINED
static double _initialSyncPeriodSeconds = 5.0f;

static Uint32 _sampleRatePerSecond;
static Uint64 _totalSamplesRead = 0;

// these represent the read and write "heads"
//     SDL's audio module reads
//     CPU activity writes
static Uint32 _read_index;
static Uint32 _write_index;

// where the write index was at the start of this frame
static Uint32 _write_index_atFrameStart;

// tracks total distance travelled by read and write "heads"
static Sint64 _read_index_distance_traveled;
static Sint64 _write_index_distance_traveled;

// counts all occurrences when resynchronization is needed, so it can be reported in the log
static Uint32 _resync_count__read_is_too_close_behind_write;
static Uint32 _resync_count__read_is_too_far_behind_write;

static Uint32 _resync_count__read_is_too_close_behind_write__EAGER;
static Uint32 _resync_count__read_is_too_far_behind_write__EAGER;
static Uint32 _resync_count__read_is_too_close_behind_write__LAZY;
static Uint32 _resync_count__read_is_too_far_behind_write__LAZY;

// there is drift sometimes between samples which are meant to represent a 
// perfect square wave
// EXPERIMENTALLY DETERMINED AT 44100Hz
//     - at 0, tape lead tone sounds choppy
//     - at >2, too many frames are considered for lazy
#define DRIFT_SAMPLES_AT_44100_HZ (1.0f)
static Uint16 _sustainedTonePeriodDriftSamples;

static Uint8 _resyncLoggingEnabled = 0;

static double _bufferSizeMs = 0.0f;

// resynchronization parameters in samples
Uint32 _sync__min_trailing_distance_SAMPLES;
Uint32 _sync__max_trailing_distance_SAMPLES;
Uint32 _sync__rewind_amount_SAMPLES;
Uint32 _sync__fastforward_amount_SAMPLES;

// resynchronization parameters in milliseconds
static double _sync__min_trailing_distance_MS;
static double _sync__max_trailing_distance_MS;
static double _sync__rewind_amount_MS;
static double _sync__fastforward_amount_MS;

// Lazy sync template
#define LAZY_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR (0.02f)
#define LAZY_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR (0.98f)
#define LAZY_SYNC_DEFAULT__REWIND__FACTOR (0.75f)
#define LAZY_SYNC_DEFAULT__FAST_FORWARD__FACTOR (0.75f)
// Eager sync template
#define EAGER_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR (0.02f)
#define EAGER_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR (0.3f)
#define EAGER_SYNC_DEFAULT__REWIND__FACTOR (0.0083f)
#define EAGER_SYNC_DEFAULT__FAST_FORWARD__FACTOR (0.0083f)

static enum soundSyncTemplate _soundSyncTemplate = SOUNDBUFFER_DEFAULT_SOUND_SYNC_TEMPLATE;

// uses values in milliseconds to compute values in samples
//
void _compute_sync_values_in_samples() {
    _sync__min_trailing_distance_SAMPLES = (Uint32)((double)_sampleRatePerSecond * _sync__min_trailing_distance_MS / 1000.0f);
    _sync__max_trailing_distance_SAMPLES = (Uint32)((double)_sampleRatePerSecond * _sync__max_trailing_distance_MS / 1000.0f);
    _sync__rewind_amount_SAMPLES = (Uint32)((double)_sampleRatePerSecond * _sync__rewind_amount_MS / 1000.0f);
    _sync__fastforward_amount_SAMPLES = (Uint32)((double)_sampleRatePerSecond * _sync__fastforward_amount_MS / 1000.0f);
}

void _soundbuffer_sync__set_min_trailing_distance_MS(double value) {
    _sync__min_trailing_distance_MS = value;
    _compute_sync_values_in_samples();
}

void soundbuffer_override_min_trailing_distance_MS(double value) {
    _userHasOverriddenASetting = 1;
    _soundbuffer_sync__set_min_trailing_distance_MS(value);
}

void _soundbuffer_sync__set_max_trailing_distance_MS(double value) {
    _sync__max_trailing_distance_MS = value;
    _compute_sync_values_in_samples();
}

void soundbuffer_override_max_trailing_distance_MS(double value) {
    _userHasOverriddenASetting = 1;
    _soundbuffer_sync__set_max_trailing_distance_MS(value);
}

void _soundbuffer_sync__set_rewind_amount_MS(double value) {
    _sync__rewind_amount_MS = value;
    _compute_sync_values_in_samples();
}

void soundbuffer_override_rewind_amount_MS(double value) {
    _userHasOverriddenASetting = 1;
    _soundbuffer_sync__set_rewind_amount_MS(value);
}

void _soundbuffer_sync__set_fastforward_amount_MS(double value) {
    _sync__fastforward_amount_MS = value;
    _compute_sync_values_in_samples();
}

void soundbuffer_override_fastforward_amount_MS(double value) {
    _userHasOverriddenASetting = 1;
    _soundbuffer_sync__set_fastforward_amount_MS(value);
}

void _set_default_sync_values(double bufferSizeMs) {
    // assume eager
    double min_trailing_factor;
    double max_trailing_factor;
    double rewind_factor;
    double fast_forward_factor;

    switch (_soundSyncTemplate) {
    case Lazy:
        min_trailing_factor = LAZY_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR;
        max_trailing_factor = LAZY_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR;
        rewind_factor = LAZY_SYNC_DEFAULT__REWIND__FACTOR;
        fast_forward_factor = LAZY_SYNC_DEFAULT__FAST_FORWARD__FACTOR;
        break;
    case Eager:
    default:
        min_trailing_factor = EAGER_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR;
        max_trailing_factor = EAGER_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR;
        rewind_factor = EAGER_SYNC_DEFAULT__REWIND__FACTOR;
        fast_forward_factor = EAGER_SYNC_DEFAULT__FAST_FORWARD__FACTOR;
        break;
    }

    _soundbuffer_sync__set_min_trailing_distance_MS(_bufferSizeMs * min_trailing_factor);
    _soundbuffer_sync__set_max_trailing_distance_MS(_bufferSizeMs * max_trailing_factor);
    _soundbuffer_sync__set_rewind_amount_MS(_bufferSizeMs * rewind_factor);
    _soundbuffer_sync__set_fastforward_amount_MS(_bufferSizeMs * fast_forward_factor);
}

void _soundbuffer_set_buffer_size_MS(double bufferSizeMs) {
    _bufferSizeMs = bufferSizeMs;
    _set_default_sync_values(bufferSizeMs);
}

void soundbuffer_override_buffer_size_MS(double bufferSizeMs) {
    _userHasOverriddenASetting = 1;
    _soundbuffer_set_buffer_size_MS(bufferSizeMs);
}

void soundbuffer_set_buffer_default(Uint16 framems) {
    Uint16 frameMsBounded = framems < DEFAULT_FRAME_MS ? DEFAULT_FRAME_MS : framems;
    double soundBufferSizeMs = (double)frameMsBounded * BUFFER_SIZE_FROM_FRAME_DURATION_FACTOR;
    _soundbuffer_set_buffer_size_MS(soundBufferSizeMs);
}

void soundbuffer_preinit(Uint32 samplesPerSecond) {
    _sampleRatePerSecond = samplesPerSecond;
    _set_default_sync_values(_bufferSizeMs);

    _sustainedTonePeriodDriftSamples = (Uint16)((double)samplesPerSecond / 44100.0f * DRIFT_SAMPLES_AT_44100_HZ);
    if (_sustainedTonePeriodDriftSamples == 0) {
        _sustainedTonePeriodDriftSamples = 1;
    }
}

void soundbuffer_notify_start_of_frame() {
    _write_index_atFrameStart = _write_index;

    _risingEdgesThisFrame = 0;
    _fallingEdgesThisFrame = 0;
    _samplesThisFrame = 0;

    // assume uniformity to begin the next frame
    _risingEdgesAreUniformThisFrame = 1;
    _fallingEdgesAreUniformThisFrame = 1;
}

void soundbuffer_notify_end_of_frame() {
    enum soundSyncTemplate syncTemplateForThisFrame = SOUNDBUFFER_DEFAULT_SOUND_SYNC_TEMPLATE;

    if (!_userHasOverriddenASetting) {
        // we vary sync template only when user has not overridden our sync settings
        if (_risingEdgesThisFrame == 0 && _fallingEdgesThisFrame == 0) {
            // we are Eager during silence, to sync back from a potentially long desync during Lazy
            syncTemplateForThisFrame = Eager;
            _framesWithEagerSync++;
        }
        // NOTE: changing this to a && (to be more strict, really) fixes the "click" at the start of
        //           each of Shredder's speech lines in TMNT, at 384kHz
        //           ... but it still changes pitch of song (after Shredder finishes speaking) some seconds into it
        //       it does NOT fix Ping Pong's cluck... cluck... cluck drums at 384kHz
        else if (_risingEdgesAreUniformThisFrame || _fallingEdgesAreUniformThisFrame) {
            // while duty cycle can vary, if at least one of either rising or falling edges
            // happen uniformly (same duration between them), then it's a sustained tone
            // sustained tones sound choppy if using Eager sync, so use Lazy instead
            syncTemplateForThisFrame = Lazy;
            _framesWithLazySync++;
        }
        else {
            // default
            syncTemplateForThisFrame = Eager;
            _framesWithEagerSync++;
        }

        // fill in sync template types for all samples written this frame
        Uint32 cursor = _write_index_atFrameStart;
        for (int i = 0; i < _samplesThisFrame; i++) {
            _syncTemplateBuffer[cursor++] = syncTemplateForThisFrame;
            if (cursor == _bufferSize) {
                cursor = 0;
            }
        }
    }
}

// rewinds (moves "backward") a circular buffer index by AT MOST one full buffer length
void _soundbuffer_rewind_index(Uint32* index, Uint64* indexDistanceTravelled, Uint32 rewindAmount) {
    if (*index >= rewindAmount) {
        *index -= rewindAmount;
    }
    else {
        *index = *index + _bufferSize - rewindAmount;
    }

    *indexDistanceTravelled -= rewindAmount;
}

// fast-forwards (moves "forward") a circular buffer index by AT MOST one full buffer length
void _soundbuffer_fastforward_index(Uint32* index, Uint64* indexDistanceTravelled, Uint32 fastForwardAmount) {
    *index += fastForwardAmount;
    if (*index >= _bufferSize) {
        *index -= _bufferSize;
    }

    *indexDistanceTravelled += fastForwardAmount;
}

void _soundbuffer_rewind_read() {
    _soundbuffer_rewind_index(&_read_index, &_read_index_distance_traveled, _sync__rewind_amount_SAMPLES);
}

void _soundbuffer_fastforward_read() {
    _soundbuffer_fastforward_index(&_read_index, &_read_index_distance_traveled, _sync__fastforward_amount_SAMPLES);
}

// depending on computer and OS, sound is poorly synchronized for a short while at application start
// since this can report much higher resync numbers, we ignore a long time interval at the start
inline Uint8 _soundbuffer_is_in_initial_sync_period() {
    if (!_inInitialSyncPeriod) {
        return 0;
    }

    Uint32 syncPeriodLengthInSamples = (Uint32)((double)_sampleRatePerSecond * _initialSyncPeriodSeconds);
    if (_read_index_distance_traveled > syncPeriodLengthInSamples) {
        // we've read past the initial sync period
        _inInitialSyncPeriod = 0;
        return 0;
    }

    return 1;
}

float soundbuffer_read(Uint8 preferLongResyncs) {
    Sint64 distance = _write_index_distance_traveled - _read_index_distance_traveled;
    // synchronize when either:
    //     - read has almost caught up to or is ahead of write, or
    //     - read is too far behind write
    if (distance < (Sint64)_sync__min_trailing_distance_SAMPLES) {
        // read has almost caught up to write (read is too fast)
        if (!timing_is_faster()) {
            // only gather statistics for when CPU is running at normal speed
            if (!_soundbuffer_is_in_initial_sync_period()) {
                // and not in the initial sync period
                _resync_count__read_is_too_close_behind_write++;

                switch (_syncTemplateBuffer[_read_index]) {
                case Lazy:
                    _resync_count__read_is_too_close_behind_write__LAZY++;
                    break;
                case Eager:
                default:
                    _resync_count__read_is_too_close_behind_write__EAGER++;
                    break;
                }

                if (_resyncLoggingEnabled) {
                    sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "RESYNC (read fast):  read: %I64d  write: %I64d  min:%I64d  overflow:%I64d",
                        _read_index_distance_traveled,
                        _write_index_distance_traveled,
                        (Sint64)_sync__min_trailing_distance_SAMPLES,
                        (Sint64)_sync__min_trailing_distance_SAMPLES - distance);
                    log_write(_debugBuffer);
                }
            }
        }
        _soundbuffer_rewind_read();
    }
    else if (distance > (Sint64)_sync__max_trailing_distance_SAMPLES) {
        // read is too far behind write (read is too slow)
        if (!timing_is_faster()) {
            // only gather statistics for when CPU is running at normal speed
            if (!_soundbuffer_is_in_initial_sync_period()) {
                // and not in the initial sync period
                _resync_count__read_is_too_far_behind_write++;

                switch (_syncTemplateBuffer[_read_index]) {
                case Lazy:
                    _resync_count__read_is_too_far_behind_write__LAZY++;
                    break;
                case Eager:
                default:
                    _resync_count__read_is_too_far_behind_write__EAGER++;
                    break;
                }

                if (_resyncLoggingEnabled) {
                    sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "RESYNC (read slow):  read: %I64d  write: %I64d  max:%I64d  underflow:%I64d",
                        _read_index_distance_traveled,
                        _write_index_distance_traveled,
                        (Sint64)_sync__max_trailing_distance_SAMPLES,
                        distance - (Sint64)_sync__max_trailing_distance_SAMPLES);
                    log_write(_debugBuffer);
                }
            }
        }

        _soundbuffer_fastforward_read();
    }

    // perform the actual read
    float value = _buffer[_read_index];
    enum soundSyncTemplate templateTypeAtSample;
    if (preferLongResyncs) {
        templateTypeAtSample = Lazy;
    }
    else {
        templateTypeAtSample = _syncTemplateBuffer[_read_index];
    }

    // vary the sync template if needed
    if (_soundSyncTemplate != templateTypeAtSample) {
        // sync template for this sample is different than what was current, so make it current
        _soundSyncTemplate = templateTypeAtSample;
        _set_default_sync_values(_bufferSizeMs);
    }

    _read_index++;
    _read_index_distance_traveled++;
    if (_read_index == _bufferSize) {
        _read_index = 0;
    }

    if (!_soundbuffer_is_in_initial_sync_period()) {
        _totalSamplesRead++;
    }
    return value;
}

void soundbuffer_write(float sample) {
    _samplesThisFrame++;
    
    // ==== EDGE DETECTION ====
    float previousSample;
    if (_write_index == 0) {
        previousSample = _buffer[_bufferSize - 1];
    }
    else {
        previousSample = _buffer[_write_index - 1];
    }
    
    if (previousSample <= EDGE_LOW_THRESHOLD && sample >= EDGE_HIGH_THRESHOLD) {
        // this sample makes a rising edge
        if (_risingEdgesThisFrame == 1) {
            // this is the second edge of this frame
            // so we can compare to the first, to establish a period
            _risingEdgePeriodThisFrame = _samplesThisFrame - _lastRisingEdgeSampleNumber;
        }
        else if (_risingEdgesThisFrame > 1) {
            // this is third or more edge of this frame
            // by now, we've establishd a period, so we can compare to it
            Sint64 drift = _risingEdgePeriodThisFrame - (_samplesThisFrame - _lastRisingEdgeSampleNumber);
            if (llabs(drift) > _sustainedTonePeriodDriftSamples) {
                // this frame is not a sustained tone
                _risingEdgesAreUniformThisFrame = 0;
            }
        }
    
        _risingEdgesThisFrame++;
        _lastRisingEdgeSampleNumber = _samplesThisFrame;
    }
    else if (previousSample >= EDGE_HIGH_THRESHOLD && sample <= EDGE_LOW_THRESHOLD) {
        // this sample makes a falling edge
        if (_fallingEdgesThisFrame == 1) {
            // this is the second edge of this frame
            // so we can compare to the first, to establish a period
            _fallingEdgePeriodThisFrame = _samplesThisFrame - _lastFallingEdgeSampleNumber;
        }
        else if (_fallingEdgesThisFrame > 1) {
            // this is third or more edge of this frame
            // by now, we've establishd a period, so we can compare to it
            Sint64 drift = _fallingEdgePeriodThisFrame - (_samplesThisFrame - _lastFallingEdgeSampleNumber);
            if (llabs(drift) > _sustainedTonePeriodDriftSamples) {
                // this frame is not a sustained tone
                _fallingEdgesAreUniformThisFrame = 0;
            }
        }
    
        _fallingEdgesThisFrame++;
        _lastFallingEdgeSampleNumber = _samplesThisFrame;
    }

    // ==== STORE SAMPLE IN BUFFER ====
    _buffer[_write_index++] = sample;
    _write_index_distance_traveled++;
    if (_write_index == _bufferSize) {
        _write_index = 0;
    }
}

void soundbuffer_enable_resync_logging() {
    log_write("Logging sound resyncs");
    _resyncLoggingEnabled = 1;
}

void soundbuffer_start() {
    sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync log ignore initial interval: %.2f sec",
        _initialSyncPeriodSeconds);
    log_write(_debugBuffer);

    _bufferSize = (Uint32) ((double)_sampleRatePerSecond * _bufferSizeMs / 1000.0f);
    _buffer = (float*)malloc(_bufferSize * sizeof(float));
    if (_buffer == NULL) {
        log_write("Error: could not allocate memory for the audio circular buffer");
        return;
    }
    memset(_buffer, 0, _bufferSize);
    _read_index = 0;
    _write_index = 0;

    _syncTemplateBuffer = (enum soundSyncTemplate*)malloc(_bufferSize * sizeof(enum soundSyncTemplate));
    if (_syncTemplateBuffer == NULL) {
        log_write("Error: could not allocate memory for the sync template buffer");
        return;
    }
    memset(_syncTemplateBuffer, Eager, _bufferSize*sizeof(enum soundSyncTemplate));

    _read_index_distance_traveled = 0;
    _write_index_distance_traveled = 0;

    _resync_count__read_is_too_close_behind_write = 0;
    _resync_count__read_is_too_far_behind_write = 0;

    _resync_count__read_is_too_close_behind_write__EAGER = 0;
    _resync_count__read_is_too_far_behind_write__EAGER = 0;
    _resync_count__read_is_too_close_behind_write__LAZY = 0;
    _resync_count__read_is_too_far_behind_write__LAZY = 0;

    _compute_sync_values_in_samples();
}

void soundbuffer_destroy() {
    if (_buffer != NULL) {
        free(_buffer);
        _buffer = NULL;
    }

    if (_syncTemplateBuffer != NULL) {
        free(_syncTemplateBuffer);
        _syncTemplateBuffer = NULL;
    }

    if (_userHasOverriddenASetting) {
        log_write("Warning: variable sync disabled because user overrode a sync setting");
    }
    
    // time spent reading
    double seconds = (double)_read_index_distance_traveled / (double)_sampleRatePerSecond;
    
    sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Sample rate: %u/sec",
        _sampleRatePerSecond);
    log_write(_debugBuffer);
    
    double bufferSizeMs = (double)_bufferSize / (double)_sampleRatePerSecond * 1000.0f;
    sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Buffer size: %.2fms (%u samples)",
        bufferSizeMs,
        _bufferSize);
    log_write(_debugBuffer);

    // we don't report resyncs during the sync period
    seconds -= _initialSyncPeriodSeconds;

    if (_userHasOverriddenASetting) {
        // resync numbers - when user overrode settings, so we can report them
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - minimum trailing distance: %.2fms (%u samples)",
            _sync__min_trailing_distance_MS,
            _sync__min_trailing_distance_SAMPLES);
        log_write(_debugBuffer);

        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - maximum trailing distance: %.2fms (%u samples)",
            _sync__max_trailing_distance_MS,
            _sync__max_trailing_distance_SAMPLES);
        log_write(_debugBuffer);

        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - rewind amount: %.2fms (%u samples)",
            _sync__rewind_amount_MS,
            _sync__rewind_amount_SAMPLES);
        log_write(_debugBuffer);

        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - fast forward amount: %.2fms (%u samples)",
            _sync__fastforward_amount_MS,
            _sync__fastforward_amount_SAMPLES);
        log_write(_debugBuffer);

        double readTooClosePerSecond = 0.0f;
        double readTooFarPerSecond = 0.0f;

        if (seconds > 0.0f) {
            readTooClosePerSecond = (double)_resync_count__read_is_too_close_behind_write / seconds;
            readTooFarPerSecond = (double)_resync_count__read_is_too_far_behind_write / seconds;
        }

        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Sound resync (read too fast): %u (%.1f/sec)",
            _resync_count__read_is_too_close_behind_write,
            readTooClosePerSecond);
        log_write(_debugBuffer);

        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Sound resync (read too slow): %u (%.1f/sec)",
            _resync_count__read_is_too_far_behind_write,
            readTooFarPerSecond);
        log_write(_debugBuffer);
    }
    else {
        // user did not override our settings
        double total = (double)(_framesWithEagerSync + _framesWithLazySync);
        if (total <= 0.0f) {
            total = 0.0001f;
        }
        double eagerPercentage = (double)_framesWithEagerSync / total * 100.0f;
        double lazyPercentage = (double)_framesWithLazySync / total * 100.0f;
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Breakdown of resync template by frame: %I64d Eager (%.1f%%), %I64d Lazy (%.1f%%)",
            _framesWithEagerSync,
            eagerPercentage,
            _framesWithLazySync,
            lazyPercentage);
        log_write(_debugBuffer);

        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Allowed sustained tone period drift: %u samples",
            _sustainedTonePeriodDriftSamples);
        log_write(_debugBuffer);


        // eager
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Eager) - minimum trailing distance: %.2fms (%u samples)",
            _bufferSizeMs * EAGER_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * EAGER_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR / 1000.0f));
        log_write(_debugBuffer);
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Eager) - maximum trailing distance: %.2fms (%u samples)",
            _bufferSizeMs * EAGER_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * EAGER_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR / 1000.0f));
        log_write(_debugBuffer);
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Eager) - rewind amount: %.2fms (%u samples)",
            _bufferSizeMs * EAGER_SYNC_DEFAULT__REWIND__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * EAGER_SYNC_DEFAULT__REWIND__FACTOR / 1000.0f));
        log_write(_debugBuffer);
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Eager) - fast forward: %.2fms (%u samples)",
            _bufferSizeMs * EAGER_SYNC_DEFAULT__FAST_FORWARD__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * EAGER_SYNC_DEFAULT__FAST_FORWARD__FACTOR / 1000.0f));
        log_write(_debugBuffer);

        if (seconds > 0.0f) {
            double readTooClosePerSecond = (double)_resync_count__read_is_too_close_behind_write__EAGER / seconds;
            double readTooFarPerSecond = (double)_resync_count__read_is_too_far_behind_write__EAGER / seconds;

            sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Eager) - (read too fast): %u (%.1f/sec)",
                _resync_count__read_is_too_close_behind_write__EAGER,
                readTooClosePerSecond);
            log_write(_debugBuffer);

            sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Eager) - (read too slow): %u (%.1f/sec)",
                _resync_count__read_is_too_far_behind_write__EAGER,
                readTooFarPerSecond);
            log_write(_debugBuffer);
        }
        else {
            log_write("Resync - (Eager) - (read too fast): 0");
            log_write("Resync - (Eager) - (read too slow): 0");
        }


        // lazy
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Lazy) - minimum trailing distance: %.2fms (%u samples)",
            _bufferSizeMs * LAZY_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * LAZY_SYNC_DEFAULT__MIN_TRAILING_DISTANCE__FACTOR / 1000.0f));
        log_write(_debugBuffer);
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Lazy) - maximum trailing distance: %.2fms (%u samples)",
            _bufferSizeMs * LAZY_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * LAZY_SYNC_DEFAULT__MAX_TRAILING_DISTANCE__FACTOR / 1000.0f));
        log_write(_debugBuffer);
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Lazy) - rewind amount: %.2fms (%u samples)",
            _bufferSizeMs * LAZY_SYNC_DEFAULT__REWIND__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * LAZY_SYNC_DEFAULT__REWIND__FACTOR / 1000.0f));
        log_write(_debugBuffer);
        sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Lazy) - fast forward: %.2fms (%u samples)",
            _bufferSizeMs * LAZY_SYNC_DEFAULT__FAST_FORWARD__FACTOR,
            (Uint32)((double)_sampleRatePerSecond * _bufferSizeMs * LAZY_SYNC_DEFAULT__FAST_FORWARD__FACTOR / 1000.0f));
        log_write(_debugBuffer);

        if (seconds > 0.0f) {
            double readTooClosePerSecond = (double)_resync_count__read_is_too_close_behind_write__LAZY / seconds;
            double readTooFarPerSecond = (double)_resync_count__read_is_too_far_behind_write__LAZY / seconds;

            sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Lazy) - (read too fast): %u (%.1f/sec)",
                _resync_count__read_is_too_close_behind_write__LAZY,
                readTooClosePerSecond);
            log_write(_debugBuffer);

            sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Lazy) - (read too slow): %u (%.1f/sec)",
                _resync_count__read_is_too_far_behind_write__LAZY,
                readTooFarPerSecond);
            log_write(_debugBuffer);
        }
        else {
            log_write("Resync - (Lazy) - (read too fast): 0");
            log_write("Resync - (Lazy) - (read too slow): 0");
        }

        if (seconds > 0.0f) {
            double actualSamplesPerSecond = (double)_totalSamplesRead / seconds;
            sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Actual samples/s %.1f  (target %d)",
                actualSamplesPerSecond,
                _sampleRatePerSecond);
            log_write(_debugBuffer);
        }


        //// totals
        //double readTooClosePerSecond = 0.0f;
        //double readTooFarPerSecond = 0.0f;

        //if (seconds > 0.0f) {
        //    readTooClosePerSecond = (double)_resync_count__read_is_too_close_behind_write / seconds;
        //    readTooFarPerSecond = (double)_resync_count__read_is_too_far_behind_write / seconds;
        //}

        //sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Total) - (read too fast): %u (%.1f/sec)",
        //    _resync_count__read_is_too_close_behind_write,
        //    readTooClosePerSecond);
        //log_write(_debugBuffer);

        //sprintf_s(_debugBuffer, _DEBUG_BUFFER_SIZE - 10, "Resync - (Total) - (read too slow): %u (%.1f/sec)",
        //    _resync_count__read_is_too_far_behind_write,
        //    readTooFarPerSecond);
        //log_write(_debugBuffer);
    }
}
