/* 
 *  Squeezelite - lightweight headless squeezebox emulator
 *
 *  (c) Adrian Smith 2012-2015, triode1@btinternet.com
 *      Ralph Irving 2015-2023, ralph_irving@hotmail.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

// dsd support

#include "squeezelite.h"

#if DSD

// use dsd2pcm from Sebastian Gesemann for conversion to pcm:
#include "./dsd2pcm/dsd2pcm.h"

extern log_level loglevel;

extern struct buffer *streambuf;
extern struct buffer *outputbuf;
extern struct streamstate stream;
extern struct outputstate output;
extern struct decodestate decode;
extern struct processstate process;

#define LOCK_S   mutex_lock(streambuf->mutex)
#define UNLOCK_S mutex_unlock(streambuf->mutex)
#define LOCK_O   mutex_lock(outputbuf->mutex)
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
#if PROCESS
#define LOCK_O_direct   if (decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct if (decode.direct) mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct   if (!decode.direct) mutex_lock(outputbuf->mutex)
#define UNLOCK_O_not_direct if (!decode.direct) mutex_unlock(outputbuf->mutex)
#define IF_DIRECT(x)    if (decode.direct) { x }
#define IF_PROCESS(x)   if (!decode.direct) { x }
#else
#define LOCK_O_direct   mutex_lock(outputbuf->mutex)
#define UNLOCK_O_direct mutex_unlock(outputbuf->mutex)
#define LOCK_O_not_direct
#define UNLOCK_O_not_direct
#define IF_DIRECT(x)    { x }
#define IF_PROCESS(x)
#endif

#define BLOCK 4096 // expected size of dsd block
#define BLOCK_FRAMES BLOCK * BYTES_PER_FRAME
#define WRAP_BUF_SIZE 32 // max 4 bytes per frame and 8 channels

typedef enum { UNKNOWN=0, DSF, DSDIFF } dsd_type;

static dsd_format outfmt = PCM; // local copy of output.dsdfmt to avoid holding output lock

struct dsd {
	dsd_type type;
	u32_t consume;
	u32_t sample_rate;
	u32_t channels;
	u64_t sample_bytes;
	u32_t block_size;
	bool  lsb_first;
	dsd2pcm_ctx *dsd2pcm_ctx[2];
	float *transfer[2];
};

static struct dsd *d;

static u64_t unpack64be(const u8_t *p) {
	return 
		(u64_t)p[0] << 56 | (u64_t)p[1] << 48 | (u64_t)p[2] << 40 | (u64_t)p[3] << 32 |
		(u64_t)p[4] << 24 | (u64_t)p[5] << 16 |	(u64_t)p[6] << 8 | (u64_t)p[7];
}

static u64_t unpack64le(const u8_t *p) {
	return
		(u64_t)p[7] << 56 | (u64_t)p[6] << 48 | (u64_t)p[5] << 40 | (u64_t)p[4] << 32 |
		(u64_t)p[3] << 24 | (u64_t)p[2] << 16 |	(u64_t)p[1] << 8 | (u64_t)p[0];
}

static u32_t unpack32le(const u8_t *p) {
	return
		(u32_t)p[3] << 24 | (u32_t)p[2] << 16 |	(u32_t)p[1] << 8 | (u32_t)p[0];
}

static int _read_header(void) {
	unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
	s32_t consume;

	if (!d->type && bytes >= 4) {
		if (!memcmp(streambuf->readp, "FRM8", 4)) {
			d->type = DSDIFF;
		} else if (!memcmp(streambuf->readp, "DSD ", 4)) {
			d->type = DSF;
		} else {
			LOG_WARN("bad type");
			return -1;
		}
	}

	while (bytes >= 16) {
		char id[5];
		u64_t len = d->type == DSDIFF ? unpack64be(streambuf->readp + 4) : unpack64le(streambuf->readp + 4);
		memcpy(id, streambuf->readp, 4);
		id[4] = '\0';
		consume = 0;

		if (d->type == DSDIFF) {
			if (!strcmp(id, "FRM8")) {
				if (!memcmp(streambuf->readp + 12, "DSD ", 4)) {
					consume = 16; // read into
				} else {
					LOG_WARN("bad dsdiff FRM8");
					return -1;
				}
			}
			if (!strcmp(id, "PROP") && !memcmp(streambuf->readp + 12, "SND ", 4)) {
				consume = 16; // read into
			}
			if (!strcmp(id, "FVER")) {
				LOG_INFO("DSDIFF version: %u.%u.%u.%u", *(streambuf->readp + 12), *(streambuf->readp + 13),
					 *(streambuf->readp + 14), *(streambuf->readp + 15));
			}
			if (!strcmp(id, "FS  ")) {
				d->sample_rate = unpackN((void *)(streambuf->readp + 12));
				LOG_INFO("sample rate: %u", d->sample_rate);
			}
			if (!strcmp(id, "CHNL")) {
				d->channels = unpackn((void *)(streambuf->readp + 12));
				LOG_INFO("channels: %u", d->channels);
			}
			if (!strcmp(id, "DSD ")) {
				LOG_INFO("found dsd len: " FMT_u64, len);
				d->sample_bytes = len;
				_buf_inc_readp(streambuf, 12);
				bytes  -= 12;
				return 1; // got to the audio
			}
		}

		if (d->type == DSF) {
			if (!strcmp(id, "fmt ")) {
				if (bytes >= len && bytes >= 52) {
					u32_t version = unpack32le((void *)(streambuf->readp + 12));
					u32_t format  = unpack32le((void *)(streambuf->readp + 16));
					LOG_INFO("DSF version: %u format: %u", version, format);
					if (format != 0) {
						LOG_WARN("only support DSD raw format");
						return -1;
					}
					d->channels = unpack32le((void *)(streambuf->readp + 24));
					d->sample_rate = unpack32le((void *)(streambuf->readp + 28));
					d->lsb_first = (unpack32le((void *)(streambuf->readp + 32)) == 1);
					d->sample_bytes = unpack64le((void *)(streambuf->readp + 36)) / 8;
					d->block_size = unpack32le((void *)(streambuf->readp + 44));
					LOG_INFO("channels: %u", d->channels);
					LOG_INFO("sample rate: %u", d->sample_rate);
					LOG_INFO("lsb first: %u", d->lsb_first);
					LOG_INFO("sample bytes: " FMT_u64, d->sample_bytes);
					LOG_INFO("block size: %u", d->block_size);
				} else {
					consume = -1; // come back later
				}
			}
			if (!strcmp(id, "data")) {
				LOG_INFO("found dsd len: " FMT_u64, len);
				_buf_inc_readp(streambuf, 12);
				bytes  -= 12;
				return 1; // got to the audio
			}
		}

		// default to consuming whole chunk
		if (!consume) {
			consume = (s32_t)((d->type == DSDIFF) ? len + 12 : len);
		}

		if (bytes >= consume) {
			LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d", id, len, consume);
			_buf_inc_readp(streambuf, consume);
			bytes  -= consume;
		} else if (consume > 0) {
			LOG_DEBUG("id: %s len: " FMT_u64 " consume: %d - partial consume: %u", id, len, consume, bytes);
			_buf_inc_readp(streambuf, bytes);
			d->consume = consume - bytes;
			break;
		} else {
			break;
		}
	}

	return 0;
}

static decode_state _decode_dsf(void) {

	// samples in streambuf are interleaved on block basis
	// we transfer whole blocks for all channels in one call and so itterate the while loop below to handle wraps
	
	unsigned bytes = _buf_used(streambuf);
	unsigned block_left = d->block_size;
	unsigned padding = 0;
	
	unsigned bytes_per_frame;
	switch (outfmt) {
	case DSD_U32_LE:
	case DSD_U32_BE:
		bytes_per_frame = 4;
		break;
	case DSD_U16_LE:
	case DSD_U16_BE:
	case DOP:
	case DOP_S24_LE:
	case DOP_S24_3LE:
		bytes_per_frame = 2;
		break;
	default:
		bytes_per_frame = 1;
	}
	
	if (bytes < d->block_size * d->channels) {
		LOG_INFO("stream too short"); // this can occur when scanning the track
		return DECODE_COMPLETE;
	}
	
	IF_PROCESS(
		process.in_frames = 0;
	);

	while (block_left) {
		
		frames_t frames, out, count;
		unsigned bytes_read;
		
		u8_t *iptrl = (u8_t *)streambuf->readp;
		u8_t *iptrr = (u8_t *)streambuf->readp + d->block_size;
		u32_t *optr;
		
		if (iptrr >= streambuf->wrap) {
			iptrr -= streambuf->size;
		}
		
		// Remove zero padding from last block in case of inaccurate sample count 
		if ((_buf_used(streambuf) == d->block_size * d->channels)
			&& (d->sample_bytes > _buf_used(streambuf))) {
			int i;
			u8_t *ipl, *ipr;
			for (i = d->block_size - 1; i > 0; i--) {
				ipl = iptrl + i;
				if (ipl >= streambuf->wrap) ipl -= streambuf->size;
				ipr = iptrr + i;	
				if (ipr >= streambuf->wrap) ipr -= streambuf->size;
				if (*ipl || *ipr) break;
				padding++;
			}
			block_left -= padding;
		}

		bytes = min(block_left, min(streambuf->wrap - iptrl, streambuf->wrap - iptrr));

		IF_DIRECT(
			out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
			optr = (u32_t *)outputbuf->writep;
		);
		IF_PROCESS(
			out = process.max_in_frames - process.in_frames;
			optr = (u32_t *)(process.inbuf + process.in_frames * BYTES_PER_FRAME);
		);

		frames = min(bytes, d->sample_bytes) / bytes_per_frame;
		if (frames == 0) {
			if (d->sample_bytes && bytes >= (2 * d->sample_bytes)) {
				// byte(s) left fill frame with silence byte(s) and play
				int i;
				for (i = d->sample_bytes; i < bytes_per_frame; i++)
					*(iptrl + i) = *(iptrr + i) = 0x69;
				frames = 1;
			} else {
				// should not get here due to wrapping m/2 for dsd should never result in 0 as header len is always even
				LOG_INFO("frames got to zero");
				return DECODE_COMPLETE;
			}
		}

		frames = min(frames, out);
		frames = min(frames, BLOCK);
		bytes_read = frames * bytes_per_frame;
		
		count = frames;
		
		switch (outfmt) {
			
		case DSD_U32_LE:
		case DSD_U32_BE:
			
			if (d->channels == 1) {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16
							| dsd2pcm_bitreverse[*(iptrl+2)] << 8 | dsd2pcm_bitreverse[*(iptrl+3)];
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16
							| dsd2pcm_bitreverse[*(iptrl+2)] << 8 | dsd2pcm_bitreverse[*(iptrl+3)];
						iptrl += 4;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16 | *(iptrl+2) << 8 | *(iptrl+3);
						*(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16 | *(iptrl+2) << 8 | *(iptrl+3);
						iptrl += 4;
					}
				}
			} else {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16
							| dsd2pcm_bitreverse[*(iptrl+2)] << 8 | dsd2pcm_bitreverse[*(iptrl+3)];
						*(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 24 | dsd2pcm_bitreverse[*(iptrr+1)] << 16
							| dsd2pcm_bitreverse[*(iptrr+2)] << 8 | dsd2pcm_bitreverse[*(iptrr+3)];
						iptrl += 4;
						iptrr += 4;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16 | *(iptrl+2) << 8 | *(iptrl+3);
						*(optr++) = *(iptrr) << 24 | *(iptrr+1) << 16 | *(iptrr+2) << 8 | *(iptrr+3);
						iptrl += 4;
						iptrr += 4;
					}
				}
			}
			
			break;

		case DSD_U16_LE:
		case DSD_U16_BE:
			
			if (d->channels == 1) {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16;
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16;
						iptrl += 2;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16;
						*(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16;
						iptrl += 2;
					}
				}
			} else {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24 | dsd2pcm_bitreverse[*(iptrl+1)] << 16;
						*(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 24 | dsd2pcm_bitreverse[*(iptrr+1)] << 16;
						iptrl += 2;
						iptrr += 2;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 24 | *(iptrl+1) << 16;
						*(optr++) = *(iptrr) << 24 | *(iptrr+1) << 16;
						iptrl += 2;
						iptrr += 2;
					}
				}
			}
			
			break;

		case DSD_U8:
			
			if (d->channels == 1) {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24;
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24;
						iptrl += 1;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 24;
						*(optr++) = *(iptrl) << 24;
						iptrl += 1;
					}
				}
			} else {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 24;
						*(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 24;
						iptrl += 1;
						iptrr += 1;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 24;
						*(optr++) = *(iptrr) << 24;
						iptrl += 1;
						iptrr += 1;
					}
				}
			}
			
			break;

		case DOP:
		case DOP_S24_LE:
		case DOP_S24_3LE:
			
			if (d->channels == 1) {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8;
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8;
						iptrl += 2;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8;
						*(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8;
						iptrl += 2;
					}
				}
			} else {
				if (d->lsb_first) {
					while (count--) {
						*(optr++) = dsd2pcm_bitreverse[*(iptrl)] << 16 | dsd2pcm_bitreverse[*(iptrl+1)] << 8;
						*(optr++) = dsd2pcm_bitreverse[*(iptrr)] << 16 | dsd2pcm_bitreverse[*(iptrr+1)] << 8;
						iptrl += 2;
						iptrr += 2;
					}
				} else {
					while (count--) {
						*(optr++) = *(iptrl) << 16 | *(iptrl+1) << 8;
						*(optr++) = *(iptrr) << 16 | *(iptrr+1) << 8;
						iptrl += 2;
						iptrr += 2;
					}
				}
			}
			
			break;

		case PCM:
			
			if (d->channels == 1) {
				float *iptrf = d->transfer[0];
				dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptrl, 1, d->lsb_first, iptrf, 1);
				while (count--) {
					double scaled = *iptrf++ * 0x7fffffff;
					if (scaled >  2147483647.0) scaled =  2147483647.0;
					if (scaled < -2147483648.0) scaled = -2147483648.0;
					*optr++ = (s32_t)scaled;
					*optr++ = (s32_t)scaled;
				}
			} else {
				float *iptrfl = d->transfer[0];
				float *iptrfr = d->transfer[1];
				dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptrl, 1, d->lsb_first, iptrfl, 1);
				dsd2pcm_translate(d->dsd2pcm_ctx[1], frames, iptrr, 1, d->lsb_first, iptrfr, 1);
				while (count--) {
					double scaledl = *iptrfl++ * 0x7fffffff;
					double scaledr = *iptrfr++ * 0x7fffffff;
					if (scaledl >  2147483647.0) scaledl =  2147483647.0;
					if (scaledl < -2147483648.0) scaledl = -2147483648.0;
					if (scaledr >  2147483647.0) scaledr =  2147483647.0;
					if (scaledr < -2147483648.0) scaledr = -2147483648.0;
					*optr++ = (s32_t)scaledl;
					*optr++ = (s32_t)scaledr;
				}
			}
			
			break;
			
		}
		
		_buf_inc_readp(streambuf, bytes_read);
		
		block_left -= bytes_read;
		
		if (d->sample_bytes > bytes_read) {
			d->sample_bytes -= bytes_read;
		} else {
			LOG_INFO("end of track samples");
			block_left = 0;
			d->sample_bytes = 0;
		}
		
		IF_DIRECT(
			_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
		);
		IF_PROCESS(
			process.in_frames += frames;
		);

		LOG_SDEBUG("write %u frames", frames);
	}
	
	if (padding) {
		_buf_inc_readp(streambuf, padding);
		LOG_INFO("Zero padding removed: %u bytes", padding);
	}
	
	// skip the other channel blocks
	// the right channel has already been read and is guarenteed to be in streambuf so can be skipped immediately
	if (d->channels > 1) {
		_buf_inc_readp(streambuf, d->block_size);
	}
	if (d->channels > 2) {
		d->consume = d->block_size * (d->channels - 2);
	}

	return DECODE_RUNNING;
}

static decode_state _decode_dsdiff(void) {

	// samples in streambuf are interleaved on byte per channel
	// we process as little as necessary per call and only need to handle frames wrapping round streambuf

	unsigned bytes_per_frame, bytes_read;
	frames_t out, frames, count;
	u8_t *iptr;
	u32_t *optr;
	u8_t tmp[WRAP_BUF_SIZE];
	
	unsigned bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
	
	IF_DIRECT(
		out = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / BYTES_PER_FRAME;
	);
	IF_PROCESS(
		out = process.max_in_frames;
	);
	
	switch (outfmt) {
	case DSD_U32_LE:
	case DSD_U32_BE:
		bytes_per_frame = d->channels * 4;
		break;
	case DSD_U16_LE:
	case DSD_U16_BE:
	case DOP:
	case DOP_S24_LE:
	case DOP_S24_3LE:
		bytes_per_frame = d->channels * 2;
		break;
	default:
		bytes_per_frame = d->channels;
		out = min(out, BLOCK);
	}
	
	frames = min(min(bytes, d->sample_bytes) / bytes_per_frame, out);
	bytes_read = frames * bytes_per_frame;
	
	iptr = (u8_t *)streambuf->readp;
	
	IF_DIRECT(
		optr = (u32_t *)outputbuf->writep;
	);
	IF_PROCESS(
		optr = (u32_t *)process.inbuf;
	);
	
	// handle wrap around end of streambuf and partial dsd frame at end of stream
	if (!frames && bytes < bytes_per_frame) {
		memset(tmp, 0x69, WRAP_BUF_SIZE); // 0x69 = dsd silence
		memcpy(tmp, streambuf->readp, bytes);
		if (_buf_used(streambuf) > bytes_per_frame) {
			memcpy(tmp + bytes, streambuf->buf, bytes_per_frame - bytes);
			bytes_read = bytes_per_frame;
		} else {
			bytes_read = bytes;
		}
		iptr = tmp;
		frames = 1;
	}
	
	count = frames;
	
	switch (outfmt) {

	case DSD_U32_LE:
	case DSD_U32_BE:
		
		if (d->channels == 1) {
			while (count--) {
				*(optr++) = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
				*(optr++) = *(iptr) << 24 | *(iptr+1) << 16 | *(iptr+2) << 8 | *(iptr+3);
				iptr += bytes_per_frame;
			}
		} else {
			while (count--) {
				*(optr++) = *(iptr  ) << 24 | *(iptr + d->channels)     << 16
					| *(iptr + 2 * d->channels)     << 8 | *(iptr + 3 * d->channels);
				*(optr++) = *(iptr+1) << 24 | *(iptr + d->channels + 1) << 16
					| *(iptr + 2 * d->channels + 1) << 8 | *(iptr + 3 * d->channels + 1);
				iptr += bytes_per_frame;
			}
		}
		
		break;
		
	case DSD_U16_LE:
	case DSD_U16_BE:
		
		if (d->channels == 1) {
			while (count--) {
				*(optr++) = *(iptr) << 24 | *(iptr+1) << 16;
				*(optr++) = *(iptr) << 24 | *(iptr+1) << 16;
				iptr += bytes_per_frame;
			}
		} else {
			while (count--) {
				*(optr++) = *(iptr  ) << 24 | *(iptr + d->channels)     << 16;
				*(optr++) = *(iptr+1) << 24 | *(iptr + d->channels + 1) << 16;
				iptr += bytes_per_frame;
			}
		}
		
		break;
		
	case DSD_U8:
		
		if (d->channels == 1) {
			while (count--) {
				*(optr++) = *(iptr) << 24;
				*(optr++) = *(iptr) << 24;
				iptr += bytes_per_frame;
			}
		} else {
			while (count--) {
				*(optr++) = *(iptr  ) << 24;
				*(optr++) = *(iptr+1) << 24;
				iptr += bytes_per_frame;
			}
		}
		
		break;
		
	case DOP:
	case DOP_S24_LE:
	case DOP_S24_3LE:
		
		if (d->channels == 1) {
			while (count--) {
				*(optr++) = *(iptr) << 16 | *(iptr+1) << 8;
				*(optr++) = *(iptr) << 16 | *(iptr+1) << 8;
				iptr += bytes_per_frame;
			}
		} else {
			while (count--) {
				*(optr++) = *(iptr  ) << 16 | *(iptr + d->channels)     << 8;
				*(optr++) = *(iptr+1) << 16 | *(iptr + d->channels + 1) << 8;
				iptr += bytes_per_frame;
			}
		}
		
		break;
		
	case PCM:
		
		if (d->channels == 1) {
			float *iptrf = d->transfer[0];
			dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptr, 1, 0, iptrf, 1);
			while (count--) {
				double scaled = *iptrf++ * 0x7fffffff;
				if (scaled >  2147483647.0) scaled =  2147483647.0;
				if (scaled < -2147483648.0) scaled = -2147483648.0;
				*optr++ = (s32_t)scaled;
				*optr++ = (s32_t)scaled;
			}
		} else {
			float *iptrfl = d->transfer[0];
			float *iptrfr = d->transfer[1];
			dsd2pcm_translate(d->dsd2pcm_ctx[0], frames, iptr,     d->channels, 0, iptrfl, 1);
			dsd2pcm_translate(d->dsd2pcm_ctx[1], frames, iptr + 1, d->channels, 0, iptrfr, 1);
			while (count--) {
				double scaledl = *iptrfl++ * 0x7fffffff;
				double scaledr = *iptrfr++ * 0x7fffffff;
				if (scaledl >  2147483647.0) scaledl =  2147483647.0;
				if (scaledl < -2147483648.0) scaledl = -2147483648.0;
				if (scaledr >  2147483647.0) scaledr =  2147483647.0;
				if (scaledr < -2147483648.0) scaledr = -2147483648.0;
				*optr++ = (s32_t)scaledl;
				*optr++ = (s32_t)scaledr;
			}
		}

		break;
		
	}
	
	_buf_inc_readp(streambuf, bytes_read);
	
	if (d->sample_bytes > bytes_read) {
		d->sample_bytes -= bytes_read;
	} else {
		LOG_INFO("end of track samples");
		d->sample_bytes = 0;
	}
	
	IF_DIRECT(
		_buf_inc_writep(outputbuf, frames * BYTES_PER_FRAME);
			  );
	IF_PROCESS(
		process.in_frames = frames;
	);
	
	LOG_SDEBUG("write %u frames", frames);

	return DECODE_RUNNING;
}


static decode_state dsd_decode(void) {
	decode_state ret;
	char *fmtstr;

	fmtstr = "None";

	LOCK_S;

	if ((stream.state <= DISCONNECT && !_buf_used(streambuf)) || (!decode.new_stream && d->sample_bytes == 0)) {
		UNLOCK_S;
		return DECODE_COMPLETE;
	}

	if (d->consume) {
		unsigned consume = min(d->consume, min(_buf_used(streambuf), _buf_cont_read(streambuf)));
		LOG_DEBUG("consume: %u of %u", consume, d->consume);
		_buf_inc_readp(streambuf, consume);
		d->consume -= consume;
		if (d->consume) {
			UNLOCK_S;
			return DECODE_RUNNING;
		}
	}

	if (decode.new_stream) {
		int r = _read_header();
		if (r < 1) {
			UNLOCK_S;
			return DECODE_ERROR;
		}
		if (r == 0) {
			UNLOCK_S;
			return DECODE_RUNNING;
		}
		// otherwise got to start of audio

		LOCK_O;

		LOG_INFO("setting track_start");
		output.track_start = outputbuf->writep;

		outfmt = output.dsdfmt;

		switch (outfmt) {
		case DSD_U32_LE:
			fmtstr = "DSD_U32_LE";
			output.next_sample_rate = d->sample_rate / 32;
			break;
		case DSD_U32_BE:
			fmtstr = "DSD_U32_BE";
			output.next_sample_rate = d->sample_rate / 32;
			break;
		case DSD_U16_LE:
			fmtstr = "DSD_U16_LE";
			output.next_sample_rate = d->sample_rate / 16;
			break;
		case DSD_U16_BE:
			fmtstr = "DSD_U16_BE";
			output.next_sample_rate = d->sample_rate / 16;
			break;
		case DSD_U8:
			fmtstr = "DSD_U8";
			output.next_sample_rate = d->sample_rate / 8;
			break;
		case DOP:
			fmtstr = "DOP";
			output.next_sample_rate = d->sample_rate / 16;
			break;
		case DOP_S24_LE:
			fmtstr = "DOP_S24_LE";
			output.next_sample_rate = d->sample_rate / 16;
			break;
		case DOP_S24_3LE:
			fmtstr = "DOP_S24_3LE";
			output.next_sample_rate = d->sample_rate / 16;
			break;
		case PCM:
			// PCM case after DSD rate check and possible fallback to PCM conversion 
			break;
		}

		if (outfmt != PCM && output.next_sample_rate > output.supported_rates[0]) {
			LOG_INFO("DSD sample rate too high for device - converting to PCM");
			outfmt = PCM;
		}
		
		if (outfmt == PCM) {
			LOG_INFO("DSD to PCM output");
			output.next_sample_rate = decode_newstream(d->sample_rate / 8, output.supported_rates);
			if (output.fade_mode) _checkfade(true);
		} else {
			LOG_INFO("DSD%u stream, format: %s, rate: %uHz\n", d->sample_rate / 44100, fmtstr, output.next_sample_rate);
			output.fade = FADE_INACTIVE;
		}

		output.next_fmt = outfmt;
		decode.new_stream = false;

		UNLOCK_O;
	}

	LOCK_O_direct;

	switch (d->type) {
	case DSF:
		ret = _decode_dsf();
		break;
	case DSDIFF:
		ret = _decode_dsdiff();
		break;
	default:
		ret = DECODE_ERROR;
	}

	UNLOCK_O_direct;
	UNLOCK_S;

	return ret;
}

void dsd_init(dsd_format format, unsigned delay) {
	LOCK_O;
	output.dsdfmt = format;
	output.dsd_delay = delay;
	UNLOCK_O;
}

static void dsd_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
	d->type = UNKNOWN;

	if (!d->dsd2pcm_ctx[0]) {
		d->dsd2pcm_ctx[0] = dsd2pcm_init();
		d->dsd2pcm_ctx[1] = dsd2pcm_init();
	} else {
		dsd2pcm_reset(d->dsd2pcm_ctx[0]);
		dsd2pcm_reset(d->dsd2pcm_ctx[1]);
	}
	if (!d->transfer[1]) {
		d->transfer[0] = malloc(sizeof(float) * BLOCK);
		d->transfer[1] = malloc(sizeof(float) * BLOCK);
	}
}

static void dsd_close(void) {
	if (d->dsd2pcm_ctx[0]) {
		dsd2pcm_destroy(d->dsd2pcm_ctx[0]);
		dsd2pcm_destroy(d->dsd2pcm_ctx[1]);
		d->dsd2pcm_ctx[0] = NULL;
		d->dsd2pcm_ctx[1] = NULL;
	}
	if (d->transfer[0]) {
		free(d->transfer[0]);
		free(d->transfer[1]);
		d->transfer[0] = NULL;
		d->transfer[1] = NULL;
	}
}

struct codec *register_dsd(void) {
	static struct codec ret = { 
		'd',         // id
		"dsf,dff",   // types
		BLOCK * 2,   // min read
		BLOCK_FRAMES,// min space
		dsd_open,    // open
		dsd_close,   // close
		dsd_decode,  // decode
	};

	d = malloc(sizeof(struct dsd));
	if (!d) {
		return NULL;
	}

	memset(d, 0, sizeof(struct dsd));

	dsd2pcm_precalc();

	LOG_INFO("using dsd to decode dsf,dff");
	return &ret;
}

// invert polarity for frames in the output buffer
void dsd_invert(u32_t *ptr, frames_t frames) {
	while (frames--) {
		*ptr = ~(*ptr);
		++ptr;
		*ptr = ~(*ptr);
		++ptr;
	}
}

// fill silence buffer with 10101100 which represents dsd silence
void dsd_silence_frames(u32_t *ptr, frames_t frames) {
	while (frames--) {
		*ptr++ = 0x69696969;
		*ptr++ = 0x69696969;
	}
}

#endif // DSD
