#import "AudioStretcher.h"
#import "Constraints.h"
#import "time_scale.h"
#import "Util.h"

#define MIN_STRETCH_FACTOR 0.1
#define MAX_STRETCH_FACTOR 10

@implementation AudioStretcher

+(AudioStretcher*) audioStretcher {
    AudioStretcher* s = [AudioStretcher new];
    checkOperation(time_scale_init(&s->timeScaleState, SAMPLE_RATE, 1.0) != NULL);
    return s;
}

-(NSData*) stretchAudioData:(NSData*)audioData stretchFactor:(double)stretchFactor {
    require(stretchFactor > MIN_STRETCH_FACTOR);
    require(stretchFactor < MAX_STRETCH_FACTOR);
    
    if (audioData == nil) return nil;
    
    checkOperationDescribe(time_scale_rate(&timeScaleState, (float)stretchFactor) == 0, @"Changing time scaling");
    
    int inputSampleCount = [audioData length]/sizeof(int16_t);
    int maxOutputSampleCount = [self getSafeMaxOutputSampleCountFromInputSampleCount:inputSampleCount];
    
    int16_t* input = (int16_t*)[audioData bytes];
    
    NSMutableData* d = [NSMutableData dataWithLength:(NSUInteger)maxOutputSampleCount*sizeof(int16_t)];
    int outputSampleCount = time_scale(&timeScaleState, [d mutableBytes], input, inputSampleCount);
    checkOperationDescribe(outputSampleCount >= 0 && outputSampleCount <= maxOutputSampleCount, @"Scaling audio");
    
    return [d take:(NSUInteger)outputSampleCount*sizeof(int16_t)];
}

-(int) getSafeMaxOutputSampleCountFromInputSampleCount:(int)inputSampleCount{
    // WARNING: In some cases SpanDSP (time_scale.h v 1.20) underestimates how much buffer it will need, so we must pad the result to be safe.
    // Issues has been notified upstream and once it is patched the padding can be removed
    int unsafe_maxOutputSampleCount = time_scale_max_output_len(&timeScaleState, inputSampleCount);
    const int BUFFER_OVERFLOW_PROTECTION_PAD = 2048;
    const int BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER = 2;
    int expandedMaxCountToAvoidBufferOverflows = BUFFER_OVERFLOW_PROTECTION_PAD + (unsafe_maxOutputSampleCount * BUFFER_OVERFLOW_PROPORTIONAL_MULTIPLIER);
    
    checkOperation(expandedMaxCountToAvoidBufferOverflows >= 0);
    return expandedMaxCountToAvoidBufferOverflows;
}

-(void) dealloc {
    checkOperation(time_scale_release(&timeScaleState) == 0);
}

@end
