//
//  ChunkList.m
//  CogAudio Framework
//
//  Created by Christopher Snowhill on 2/5/22.
//

#import <Accelerate/Accelerate.h>

#import "ChunkList.h"

#import "hdcd_decode2.h"

#if !DSD_DECIMATE
#import "dsd2float.h"
#endif

#ifdef _DEBUG
#import "BadSampleCleaner.h"
#endif

#if DSD_DECIMATE
/**
 * DSD 2 PCM: Stage 1:
 * Decimate by factor 8
 * (one byte (8 samples) -> one float sample)
 * The bits are processed from least signicifant to most signicicant.
 * @author Sebastian Gesemann
 */

#define dsd2pcm_FILTER_COEFFS_COUNT 64
static const float dsd2pcm_FILTER_COEFFS[64] = {
	0.09712411121659f, 0.09613438994044f, 0.09417884216316f, 0.09130441727307f,
	0.08757947648990f, 0.08309142055179f, 0.07794369263673f, 0.07225228745463f,
	0.06614191680338f, 0.05974199351302f, 0.05318259916599f, 0.04659059631228f,
	0.04008603356890f, 0.03377897290478f, 0.02776684382775f, 0.02213240062966f,
	0.01694232798846f, 0.01224650881275f, 0.00807793792573f, 0.00445323755944f,
	0.00137370697215f, -0.00117318019994f, -0.00321193033831f, -0.00477694265140f,
	-0.00591028841335f, -0.00665946056286f, -0.00707518873201f, -0.00720940203988f,
	-0.00711340642819f, -0.00683632603227f, -0.00642384017266f, -0.00591723006715f,
	-0.00535273320457f, -0.00476118922548f, -0.00416794965654f, -0.00359301524813f,
	-0.00305135909510f, -0.00255339111833f, -0.00210551956895f, -0.00171076760278f,
	-0.00136940723130f, -0.00107957856005f, -0.00083786862365f, -0.00063983084245f,
	-0.00048043272086f, -0.00035442550015f, -0.00025663481039f, -0.00018217573430f,
	-0.00012659899635f, -0.00008597726991f, -0.00005694188820f, -0.00003668060332f,
	-0.00002290670286f, -0.00001380895679f, -0.00000799057558f, -0.00000440385083f,
	-0.00000228567089f, -0.00000109760778f, -0.00000047286430f, -0.00000017129652f,
	-0.00000004282776f, 0.00000000119422f, 0.00000000949179f, 0.00000000747450f
};

struct dsd2pcm_state {
	/*
	 * This is the 2nd half of an even order symmetric FIR
	 * lowpass filter (to be used on a signal sampled at 44100*64 Hz)
	 * Passband is 0-24 kHz (ripples +/- 0.025 dB)
	 * Stopband starts at 176.4 kHz (rejection: 170 dB)
	 * The overall gain is 2.0
	 */

	/* These remain constant for the duration */
	int FILT_LOOKUP_PARTS;
	float *FILT_LOOKUP_TABLE;
	uint8_t *REVERSE_BITS;
	int FIFO_LENGTH;
	int FIFO_OFS_MASK;

	/* These are altered */
	int *fifo;
	int fpos;
};

static void dsd2pcm_free(void *);
static void dsd2pcm_reset(void *);

static void *dsd2pcm_alloc() {
	struct dsd2pcm_state *state = (struct dsd2pcm_state *)calloc(1, sizeof(struct dsd2pcm_state));

	float *FILT_LOOKUP_TABLE;
	double *temp;
	uint8_t *REVERSE_BITS;

	if(!state)
		return NULL;

	state->FILT_LOOKUP_PARTS = (dsd2pcm_FILTER_COEFFS_COUNT + 7) / 8;
	const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
	// The current 128 tap FIR leads to an 8 KB lookup table
	state->FILT_LOOKUP_TABLE = (float *)calloc(sizeof(float), FILT_LOOKUP_PARTS << 8);
	if(!state->FILT_LOOKUP_TABLE)
		goto fail;
	FILT_LOOKUP_TABLE = state->FILT_LOOKUP_TABLE;
	temp = (double *)calloc(sizeof(double), 0x100);
	if(!temp)
		goto fail;
	for(int part = 0, sofs = 0, dofs = 0; part < FILT_LOOKUP_PARTS;) {
		memset(temp, 0, 0x100 * sizeof(double));
		for(int bit = 0, bitmask = 0x80; bit < 8 && sofs + bit < dsd2pcm_FILTER_COEFFS_COUNT;) {
			double coeff = dsd2pcm_FILTER_COEFFS[sofs + bit];
			for(int bite = 0; bite < 0x100; bite++) {
				if((bite & bitmask) == 0) {
					temp[bite] -= coeff;
				} else {
					temp[bite] += coeff;
				}
			}
			bit++;
			bitmask >>= 1;
		}
		for(int s = 0; s < 0x100;) {
			FILT_LOOKUP_TABLE[dofs++] = (float)temp[s++];
		}
		part++;
		sofs += 8;
	}
	free(temp);
	{ // calculate FIFO stuff
		int k = 1;
		while(k < FILT_LOOKUP_PARTS * 2) k <<= 1;
		state->FIFO_LENGTH = k;
		state->FIFO_OFS_MASK = k - 1;
	}
	state->REVERSE_BITS = (uint8_t *)calloc(1, 0x100);
	if(!state->REVERSE_BITS)
		goto fail;
	REVERSE_BITS = state->REVERSE_BITS;
	for(int i = 0, j = 0; i < 0x100; i++) {
		REVERSE_BITS[i] = (uint8_t)j;
		// "reverse-increment" of j
		for(int bitmask = 0x80;;) {
			if(((j ^= bitmask) & bitmask) != 0) break;
			if(bitmask == 1) break;
			bitmask >>= 1;
		}
	}

	state->fifo = (int *)calloc(sizeof(int), state->FIFO_LENGTH);
	if(!state->fifo)
		goto fail;

	dsd2pcm_reset(state);

	return (void *)state;

fail:
	dsd2pcm_free(state);
	return NULL;
}

static void *dsd2pcm_dup(void *_state) {
	struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
	if(state) {
		struct dsd2pcm_state *newstate = (struct dsd2pcm_state *)calloc(1, sizeof(struct dsd2pcm_state));
		if(newstate) {
			newstate->FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
			newstate->FIFO_LENGTH = state->FIFO_LENGTH;
			newstate->FIFO_OFS_MASK = state->FIFO_OFS_MASK;
			newstate->fpos = state->fpos;

			newstate->FILT_LOOKUP_TABLE = (float *)calloc(sizeof(float), state->FILT_LOOKUP_PARTS << 8);
			if(!newstate->FILT_LOOKUP_TABLE)
				goto fail;

			memcpy(newstate->FILT_LOOKUP_TABLE, state->FILT_LOOKUP_TABLE, sizeof(float) * (state->FILT_LOOKUP_PARTS << 8));

			newstate->REVERSE_BITS = (uint8_t *)calloc(1, 0x100);
			if(!newstate->REVERSE_BITS)
				goto fail;

			memcpy(newstate->REVERSE_BITS, state->REVERSE_BITS, 0x100);

			newstate->fifo = (int *)calloc(sizeof(int), state->FIFO_LENGTH);
			if(!newstate->fifo)
				goto fail;

			memcpy(newstate->fifo, state->fifo, sizeof(int) * state->FIFO_LENGTH);

			return (void *)newstate;
		}

	fail:
		dsd2pcm_free(newstate);
		return NULL;
	}

	return NULL;
}

static void dsd2pcm_free(void *_state) {
	struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
	if(state) {
		free(state->fifo);
		free(state->REVERSE_BITS);
		free(state->FILT_LOOKUP_TABLE);
		free(state);
	}
}

static void dsd2pcm_reset(void *_state) {
	struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
	const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
	int *fifo = state->fifo;
	for(int i = 0; i < FILT_LOOKUP_PARTS; i++) {
		fifo[i] = 0x55;
		fifo[i + FILT_LOOKUP_PARTS] = 0xAA;
	}
	state->fpos = FILT_LOOKUP_PARTS;
}

static int dsd2pcm_latency(void *_state) {
	struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
	if(state)
		return state->FIFO_LENGTH;
	else
		return 0;
}

static void dsd2pcm_process(void *_state, const uint8_t *src, size_t sofs, size_t sinc, float *dest, size_t dofs, size_t dinc, size_t len) {
	struct dsd2pcm_state *state = (struct dsd2pcm_state *)_state;
	int bite1, bite2, temp;
	float sample;
	int *fifo = state->fifo;
	const uint8_t *REVERSE_BITS = state->REVERSE_BITS;
	const float *FILT_LOOKUP_TABLE = state->FILT_LOOKUP_TABLE;
	const int FILT_LOOKUP_PARTS = state->FILT_LOOKUP_PARTS;
	const int FIFO_OFS_MASK = state->FIFO_OFS_MASK;
	int fpos = state->fpos;
	while(len > 0) {
		fifo[fpos] = REVERSE_BITS[fifo[fpos]] & 0xFF;
		fifo[(fpos + FILT_LOOKUP_PARTS) & FIFO_OFS_MASK] = src[sofs] & 0xFF;
		sofs += sinc;
		temp = (fpos + 1) & FIFO_OFS_MASK;
		sample = 0;
		for(int k = 0, lofs = 0; k < FILT_LOOKUP_PARTS;) {
			bite1 = fifo[(fpos - k) & FIFO_OFS_MASK];
			bite2 = fifo[(temp + k) & FIFO_OFS_MASK];
			sample += FILT_LOOKUP_TABLE[lofs + bite1] + FILT_LOOKUP_TABLE[lofs + bite2];
			k++;
			lofs += 0x100;
		}
		fpos = temp;
		dest[dofs] = sample;
		dofs += dinc;
		len--;
	}
	state->fpos = fpos;
}

static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels, void **dsd2pcm) {
	for(size_t channel = 0; channel < channels; ++channel) {
		dsd2pcm_process(dsd2pcm[channel], input, channel, channels, output, channel, channels, count);
	}
}
#else
static void convert_dsd_to_f32(float *output, const uint8_t *input, size_t count, size_t channels) {
	const uint8_t *iptr = input;
	float *optr = output;
	for(size_t index = 0; index < count; ++index) {
		for(size_t channel = 0; channel < channels; ++channel) {
			uint8_t sample = *iptr++;
			cblas_scopy(8, &dsd2float[sample][0], 1, optr++, (int)channels);
		}
		optr += channels * 7;
	}
}
#endif

static void convert_u8_to_s16(int16_t *output, const uint8_t *input, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		uint16_t sample = (input[i] << 8) | input[i];
		sample ^= 0x8080;
		output[i] = (int16_t)(sample);
	}
}

static void convert_s8_to_s16(int16_t *output, const uint8_t *input, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		uint16_t sample = (input[i] << 8) | input[i];
		output[i] = (int16_t)(sample);
	}
}

static void convert_u16_to_s16(int16_t *buffer, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		buffer[i] ^= 0x8000;
	}
}

static void convert_s16_to_hdcd_input(int32_t *output, const int16_t *input, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		output[i] = input[i];
	}
}

static void convert_s24_to_s32(int32_t *output, const uint8_t *input, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		int32_t sample = (input[i * 3] << 8) | (input[i * 3 + 1] << 16) | (input[i * 3 + 2] << 24);
		output[i] = sample;
	}
}

static void convert_u24_to_s32(int32_t *output, const uint8_t *input, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		int32_t sample = (input[i * 3] << 8) | (input[i * 3 + 1] << 16) | (input[i * 3 + 2] << 24);
		output[i] = sample ^ 0x80000000;
	}
}

static void convert_u32_to_s32(int32_t *buffer, size_t count) {
	for(size_t i = 0; i < count; ++i) {
		buffer[i] ^= 0x80000000;
	}
}

static void convert_f64_to_f32(float *output, const double *input, size_t count) {
	vDSP_vdpsp(input, 1, output, 1, count);
}

static void convert_be_to_le(uint8_t *buffer, size_t bitsPerSample, size_t bytes) {
	size_t i;
	bitsPerSample = (bitsPerSample + 7) / 8;
	switch(bitsPerSample) {
		case 2:
			for(i = 0; i < bytes; i += 2) {
				*(int16_t *)buffer = __builtin_bswap16(*(int16_t *)buffer);
				buffer += 2;
			}
			break;

		case 3: {
			union {
				vDSP_int24 int24;
				uint32_t int32;
			} intval;
			intval.int32 = 0;
			for(i = 0; i < bytes; i += 3) {
				intval.int24 = *(vDSP_int24 *)buffer;
				intval.int32 = __builtin_bswap32(intval.int32 << 8);
				*(vDSP_int24 *)buffer = intval.int24;
				buffer += 3;
			}
		} break;

		case 4:
			for(i = 0; i < bytes; i += 4) {
				*(uint32_t *)buffer = __builtin_bswap32(*(uint32_t *)buffer);
				buffer += 4;
			}
			break;

		case 8:
			for(i = 0; i < bytes; i += 8) {
				*(uint64_t *)buffer = __builtin_bswap64(*(uint64_t *)buffer);
				buffer += 8;
			}
			break;
	}
}

@implementation ChunkList

@synthesize listDuration;
@synthesize maxDuration;

- (id)initWithMaximumDuration:(double)duration {
	self = [super init];

	if(self) {
		chunkList = [[NSMutableArray alloc] init];
		listDuration = 0.0;
		maxDuration = duration;

		inAdder = NO;
		inRemover = NO;
		inPeeker = NO;
		stopping = NO;
		
		formatRead = NO;

		inputBuffer = NULL;
		inputBufferSize = 0;

#if DSD_DECIMATE
		dsd2pcm = NULL;
		dsd2pcmCount = 0;
		dsd2pcmLatency = 0;
#endif
	}

	return self;
}

- (void)dealloc {
	stopping = YES;
	while(inAdder || inRemover || inPeeker) {
		usleep(500);
	}
	if(hdcd_decoder) {
		free(hdcd_decoder);
		hdcd_decoder = NULL;
	}
#if DSD_DECIMATE
	if(dsd2pcm && dsd2pcmCount) {
		for(size_t i = 0; i < dsd2pcmCount; ++i) {
			dsd2pcm_free(dsd2pcm[i]);
			dsd2pcm[i] = NULL;
		}
		free(dsd2pcm);
		dsd2pcm = NULL;
	}
#endif
	if(tempData) {
		free(tempData);
	}
}

- (void)reset {
	@synchronized(chunkList) {
		[chunkList removeAllObjects];
		listDuration = 0.0;
	}
}

- (BOOL)isEmpty {
	@synchronized(chunkList) {
		return [chunkList count] == 0;
	}
}

- (BOOL)isFull {
	return (maxDuration - listDuration) < 0.05;
}

- (void)addChunk:(AudioChunk *)chunk {
	if(stopping) return;

	inAdder = YES;

	const double chunkDuration = [chunk duration];

	@synchronized(chunkList) {
		[chunkList addObject:chunk];
		listDuration += chunkDuration;
	}

	inAdder = NO;
}

- (AudioChunk *)removeSamples:(size_t)maxFrameCount {
	if(stopping) {
		return [[AudioChunk alloc] init];
	}

	@synchronized(chunkList) {
		inRemover = YES;
		if(![chunkList count]) {
			inRemover = NO;
			return [[AudioChunk alloc] init];
		}
		AudioChunk *chunk = [chunkList objectAtIndex:0];
		if([chunk frameCount] <= maxFrameCount) {
			[chunkList removeObjectAtIndex:0];
			listDuration -= [chunk duration];
			inRemover = NO;
			return chunk;
		}
		NSData *removedData = [chunk removeSamples:maxFrameCount];
		AudioChunk *ret = [[AudioChunk alloc] init];
		[ret setFormat:[chunk format]];
		[ret setChannelConfig:[chunk channelConfig]];
		[ret assignData:removedData];
		listDuration -= [ret duration];
		inRemover = NO;
		return ret;
	}
}

- (AudioChunk *)removeSamplesAsFloat32:(size_t)maxFrameCount {
	if(stopping) {
		return [[AudioChunk alloc] init];
	}
	
	@synchronized (chunkList) {
		inRemover = YES;
		if(![chunkList count]) {
			inRemover = NO;
			return [[AudioChunk alloc] init];
		}
		AudioChunk *chunk = [chunkList objectAtIndex:0];
#if !DSD_DECIMATE
		AudioStreamBasicDescription asbd = [chunk format];
		if(asbd.mBitsPerChannel == 1) {
			maxFrameCount /= 8;
		}
#endif
		if([chunk frameCount] <= maxFrameCount) {
			[chunkList removeObjectAtIndex:0];
			listDuration -= [chunk duration];
			inRemover = NO;
			return [self convertChunk:chunk];
		}
		NSData *removedData = [chunk removeSamples:maxFrameCount];
		AudioChunk *ret = [[AudioChunk alloc] init];
		[ret setFormat:[chunk format]];
		[ret setChannelConfig:[chunk channelConfig]];
		[ret assignData:removedData];
		listDuration -= [ret duration];
		inRemover = NO;
		return [self convertChunk:ret];
	}
}

- (AudioChunk *)convertChunk:(AudioChunk *)inChunk {
	AudioStreamBasicDescription chunkFormat = [inChunk format];
	uint32_t chunkConfig = [inChunk channelConfig];
	BOOL chunkLossless = [inChunk lossless];
	if(!formatRead || memcmp(&chunkFormat, &inputFormat, sizeof(chunkFormat)) != 0 ||
	   chunkConfig != inputChannelConfig || chunkLossless != inputLossless) {
		formatRead = YES;
		inputFormat = chunkFormat;
		inputChannelConfig = chunkConfig;
		inputLossless = chunkLossless;

		BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
		if((!isFloat && !(inputFormat.mBitsPerChannel >= 1 && inputFormat.mBitsPerChannel <= 32)) || (isFloat && !(inputFormat.mBitsPerChannel == 32 || inputFormat.mBitsPerChannel == 64)))
			return [[AudioChunk alloc] init];

		// These are really placeholders, as we're doing everything internally now
		if(inputLossless &&
		   inputFormat.mBitsPerChannel == 16 &&
		   inputFormat.mChannelsPerFrame == 2 &&
		   inputFormat.mSampleRate == 44100) {
			// possibly HDCD, run through decoder
			if(hdcd_decoder) {
				free(hdcd_decoder);
				hdcd_decoder = NULL;
			}
			hdcd_decoder = calloc(1, sizeof(hdcd_state_stereo_t));
			hdcd_reset_stereo((hdcd_state_stereo_t *)hdcd_decoder, 44100);
		}

		floatFormat = inputFormat;
		floatFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked;
		floatFormat.mBitsPerChannel = 32;
		floatFormat.mBytesPerFrame = (32 / 8) * floatFormat.mChannelsPerFrame;
		floatFormat.mBytesPerPacket = floatFormat.mBytesPerFrame * floatFormat.mFramesPerPacket;

#if DSD_DECIMATE
		if(inputFormat.mBitsPerChannel == 1) {
			// Decimate this for speed
			floatFormat.mSampleRate *= 1.0 / 8.0;
			if(dsd2pcm && dsd2pcmCount) {
				for(size_t i = 0; i < dsd2pcmCount; ++i) {
					dsd2pcm_free(dsd2pcm[i]);
					dsd2pcm[i] = NULL;
				}
				free(dsd2pcm);
				dsd2pcm = NULL;
			}
			dsd2pcmCount = floatFormat.mChannelsPerFrame;
			dsd2pcm = (void **)calloc(dsd2pcmCount, sizeof(void *));
			dsd2pcm[0] = dsd2pcm_alloc();
			dsd2pcmLatency = dsd2pcm_latency(dsd2pcm[0]);
			for(size_t i = 1; i < dsd2pcmCount; ++i) {
				dsd2pcm[i] = dsd2pcm_dup(dsd2pcm[0]);
			}
		}
#endif
	}
	
	NSUInteger samplesRead = [inChunk frameCount];
	
	if(!samplesRead) {
		return [[AudioChunk alloc] init];
	}
	
	BOOL isFloat = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsFloat);
	BOOL isUnsigned = !isFloat && !(inputFormat.mFormatFlags & kAudioFormatFlagIsSignedInteger);
	size_t bitsPerSample = inputFormat.mBitsPerChannel;
	BOOL isBigEndian = !!(inputFormat.mFormatFlags & kAudioFormatFlagIsBigEndian);

	NSData *inputData = [inChunk removeSamples:samplesRead];

#if DSD_DECIMATE
	const size_t sizeFactor = 3;
#else
	const size_t sizeFactor = (bitsPerSample == 1) ? 9 : 3;
#endif
	size_t newSize = samplesRead * floatFormat.mBytesPerPacket * sizeFactor + 64;
	if(!tempData || tempDataSize < newSize)
		tempData = realloc(tempData, tempDataSize = newSize); // Either two buffers plus padding, and/or double precision in case of endian flip

	// double buffer system, with alignment
	const size_t buffer_adder_base = (samplesRead * floatFormat.mBytesPerPacket + 31) & ~31;

	NSUInteger bytesReadFromInput = samplesRead * inputFormat.mBytesPerPacket;

	uint8_t *inputBuffer = (uint8_t *)[inputData bytes];
	BOOL inputChanged = NO;

	BOOL hdcdSustained = NO;

	if(bytesReadFromInput && isBigEndian) {
		// Time for endian swap!
		memcpy(&tempData[0], [inputData bytes], bytesReadFromInput);
		convert_be_to_le((uint8_t *)(&tempData[0]), inputFormat.mBitsPerChannel, bytesReadFromInput);
		inputBuffer = &tempData[0];
		inputChanged = YES;
	}

	if(bytesReadFromInput && isFloat && bitsPerSample == 64) {
		// Time for precision loss from weird inputs
		const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base * 2 : 0;
		samplesRead = bytesReadFromInput / sizeof(double);
		convert_f64_to_f32((float *)(&tempData[buffer_adder]), (const double *)inputBuffer, samplesRead);
		bytesReadFromInput = samplesRead * sizeof(float);
		inputBuffer = &tempData[buffer_adder];
		inputChanged = YES;
		bitsPerSample = 32;
	}

	if(bytesReadFromInput && !isFloat) {
		float gain = 1.0;
		if(bitsPerSample == 1) {
			const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
			samplesRead = bytesReadFromInput / inputFormat.mBytesPerPacket;
			convert_dsd_to_f32((float *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead, inputFormat.mChannelsPerFrame
#if DSD_DECIMATE
							   ,
							   dsd2pcm
#endif
			);
#if !DSD_DECIMATE
			samplesRead *= 8;
#endif
			bitsPerSample = 32;
			bytesReadFromInput = samplesRead * floatFormat.mBytesPerPacket;
			isFloat = YES;
			inputBuffer = &tempData[buffer_adder];
			inputChanged = YES;
#if DSD_DECIMATE
			float scaleFactor = 2.0f;
			vDSP_vsdiv((float *)inputBuffer, 1, &scaleFactor, (float *)inputBuffer, 1, bytesReadFromInput / sizeof(float));
#endif
		} else if(bitsPerSample <= 8) {
			samplesRead = bytesReadFromInput;
			const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
			if(!isUnsigned)
				convert_s8_to_s16((int16_t *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead);
			else
				convert_u8_to_s16((int16_t *)(&tempData[buffer_adder]), (const uint8_t *)inputBuffer, samplesRead);
			bitsPerSample = 16;
			bytesReadFromInput = samplesRead * 2;
			isUnsigned = NO;
			inputBuffer = &tempData[buffer_adder];
			inputChanged = YES;
		}
		if(hdcd_decoder) { // implied bits per sample is 16, produces 32 bit int scale
			samplesRead = bytesReadFromInput / 2;
			const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
			if(isUnsigned)
				convert_u16_to_s16((int16_t *)inputBuffer, samplesRead);
			convert_s16_to_hdcd_input((int32_t *)(&tempData[buffer_adder]), (int16_t *)inputBuffer, samplesRead);
			hdcd_process_stereo((hdcd_state_stereo_t *)hdcd_decoder, (int32_t *)(&tempData[buffer_adder]), (int)(samplesRead / 2));
			if(((hdcd_state_stereo_t *)hdcd_decoder)->channel[0].sustain &&
			   ((hdcd_state_stereo_t *)hdcd_decoder)->channel[1].sustain) {
				hdcdSustained = YES;
			}
			gain = 2.0;
			bitsPerSample = 32;
			bytesReadFromInput = samplesRead * 4;
			isUnsigned = NO;
			inputBuffer = &tempData[buffer_adder];
			inputChanged = YES;
		} else if(bitsPerSample <= 16) {
			samplesRead = bytesReadFromInput / 2;
			const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
			if(isUnsigned) {
				if(!inputChanged) {
					memcpy(&tempData[buffer_adder], inputBuffer, samplesRead * 2);
					inputBuffer = &tempData[buffer_adder];
					inputChanged = YES;
				}
				convert_u16_to_s16((int16_t *)inputBuffer, samplesRead);
			}
			const size_t buffer_adder2 = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
			vDSP_vflt16((const short *)inputBuffer, 1, (float *)(&tempData[buffer_adder2]), 1, samplesRead);
			float scale = 1ULL << 15;
			vDSP_vsdiv((const float *)(&tempData[buffer_adder2]), 1, &scale, (float *)(&tempData[buffer_adder2]), 1, samplesRead);
			bitsPerSample = 32;
			bytesReadFromInput = samplesRead * sizeof(float);
			isUnsigned = NO;
			isFloat = YES;
			inputBuffer = &tempData[buffer_adder2];
			inputChanged = YES;
		} else if(bitsPerSample <= 24) {
			const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0;
			samplesRead = bytesReadFromInput / 3;
			if(isUnsigned)
				convert_u24_to_s32((int32_t *)(&tempData[buffer_adder]), (uint8_t *)inputBuffer, samplesRead);
			else
				convert_s24_to_s32((int32_t *)(&tempData[buffer_adder]), (uint8_t *)inputBuffer, samplesRead);
			bitsPerSample = 32;
			bytesReadFromInput = samplesRead * 4;
			isUnsigned = NO;
			inputBuffer = &tempData[buffer_adder];
			inputChanged = YES;
		}
		if(!isFloat && bitsPerSample <= 32) {
			samplesRead = bytesReadFromInput / 4;
			if(isUnsigned) {
				if(!inputChanged) {
					memcpy(&tempData[0], inputBuffer, bytesReadFromInput);
					inputBuffer = &tempData[0];
				}
				convert_u32_to_s32((int32_t *)inputBuffer, samplesRead);
			}
			const size_t buffer_adder = (inputBuffer == &tempData[0]) ? buffer_adder_base : 0; // vDSP functions expect aligned to four elements
			vDSP_vflt32((const int *)inputBuffer, 1, (float *)(&tempData[buffer_adder]), 1, samplesRead);
			float scale = (1ULL << 31) / gain;
			vDSP_vsdiv((const float *)(&tempData[buffer_adder]), 1, &scale, (float *)(&tempData[buffer_adder]), 1, samplesRead);
			bitsPerSample = 32;
			bytesReadFromInput = samplesRead * sizeof(float);
			isUnsigned = NO;
			isFloat = YES;
			inputBuffer = &tempData[buffer_adder];
		}

#ifdef _DEBUG
		[BadSampleCleaner cleanSamples:(float *)inputBuffer
								amount:bytesReadFromInput / sizeof(float)
							  location:@"post int to float conversion"];
#endif
	}

	AudioChunk *outChunk = [[AudioChunk alloc] init];
	[outChunk setFormat:floatFormat];
	[outChunk setChannelConfig:inputChannelConfig];
	[outChunk setLossless:inputLossless];
	if(hdcdSustained) [outChunk setHDCD];
	
	[outChunk assignSamples:inputBuffer frameCount:bytesReadFromInput / floatFormat.mBytesPerPacket];
	
	return outChunk;
}

- (BOOL)peekFormat:(AudioStreamBasicDescription *)format channelConfig:(uint32_t *)config {
	if(stopping) return NO;
	@synchronized(chunkList) {
		if([chunkList count]) {
			AudioChunk *chunk = [chunkList objectAtIndex:0];
			*format = [chunk format];
			*config = [chunk channelConfig];
			return YES;
		}
	}
	return NO;
}

@end
