diff foosdk/sdk/foobar2000/SDK/audio_chunk.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/SDK/audio_chunk.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,740 @@
+#include "foobar2000-sdk-pch.h"
+#include "mem_block_container.h"
+#include "audio_chunk.h"
+
+void audio_chunk::allocate(size_t size, bool bQuicker) {
+	if (bQuicker) {
+		const size_t before = this->get_data_size();
+		const size_t allow_waste = pfc::max_t<size_t>(size, 4096);
+		const size_t upper = (size + allow_waste > size) ? size + allow_waste : SIZE_MAX;
+		if (before >= size && before <= upper) return;
+	}
+	this->set_data_size(size);
+}
+
+void audio_chunk::set_data(const audio_sample* src, size_t samples, spec_t const & spec, bool bQuicker) {
+	t_size size = samples * spec.chanCount;
+	allocate(size, bQuicker);
+	if (src)
+		pfc::memcpy_t(get_data(), src, size);
+	else
+		pfc::memset_t(get_data(), (audio_sample)0, size);
+	set_sample_count(samples);
+	set_spec(spec);
+}
+
+void audio_chunk::set_data(const audio_sample* src, size_t samples, unsigned nch, unsigned srate, unsigned channel_config)
+{
+	set_data(src, samples, makeSpec(srate, nch, channel_config));
+}
+
+void audio_chunk::set_data(const audio_sample* src, size_t samples, unsigned nch, unsigned srate) {
+
+	set_data(src, samples, makeSpec(srate, nch));
+}
+
+inline bool check_exclusive(unsigned val, unsigned mask)
+{
+	return (val&mask)!=0 && (val&mask)!=mask;
+}
+
+static void _import8u(uint8_t const * in, audio_sample * out, size_t count) {
+	for(size_t walk = 0; walk < count; ++walk) {
+		uint32_t i = *(in++);
+		i -= 0x80; // to signed
+		*(out++) = (audio_sample) (int32_t) i / (float) 0x80;
+	}
+}
+
+static void _import8s(uint8_t const * in, audio_sample * out, size_t count) {
+	for(size_t walk = 0; walk < count; ++walk) {
+		int32_t i = (int8_t) *(in++);
+		*(out++) = (audio_sample) i / (float) 0x80;
+	}
+}
+
+static audio_sample _import24s(uint32_t i) {
+	i ^= 0x800000; // to unsigned
+	i -= 0x800000; // and back to signed / fill MSBs proper
+	return (audio_sample) (int32_t) i / (audio_sample) 0x800000;
+}
+
+static void _import24(const void * in_, audio_sample * out, size_t count) {
+	const uint8_t * in = (const uint8_t*) in_;
+#if 1
+	while(count > 0 && !pfc::is_ptr_aligned_t<4>(in)) {
+		uint32_t i = *(in++);
+		i |= (uint32_t) *(in++) << 8;
+		i |= (uint32_t) *(in++) << 16;
+		*(out++) = _import24s(i);
+		--count;
+	}
+	{
+		for(size_t loop = count >> 2; loop; --loop) {
+			uint32_t i1 = * (uint32_t*) in; in += 4;
+			uint32_t i2 = * (uint32_t*) in; in += 4;
+			uint32_t i3 = * (uint32_t*) in; in += 4;
+			*out++ = _import24s( i1 & 0xFFFFFF );
+			*out++ = _import24s( (i1 >> 24) | ((i2 & 0xFFFF) << 8) );
+			*out++ = _import24s( (i2 >> 16) | ((i3 & 0xFF) << 16) );
+			*out++ = _import24s( i3 >> 8 );
+		}
+		count &= 3;
+	}
+	for( ; count ; --count) {
+		uint32_t i = *(in++);
+		i |= (uint32_t) *(in++) << 8;
+		i |= (uint32_t) *(in++) << 16;
+		*(out++) = _import24s(i);
+	}
+#else
+	if (count > 0) {
+		int32_t i = *(in++);
+		i |= (int32_t) *(in++) << 8;
+		i |= (int32_t) (int8_t) *in << 16;
+		*out++ = (audio_sample) i / (audio_sample) 0x800000;
+		--count;
+
+		// Now we have in ptr at offset_of_next - 1 and we can read as int32 then discard the LSBs
+		for(;count;--count) {
+			int32_t i = *(  int32_t*) in; in += 3;
+			*out++ = (audio_sample) (i >> 8) / (audio_sample) 0x800000;
+		}
+	}
+#endif
+}
+
+template<bool byteSwap, bool isSigned> static void _import16any(const void * in, audio_sample * out, size_t count) {
+	uint16_t const * inPtr = (uint16_t const*) in;
+	const audio_sample factor = 1.0f / (audio_sample) 0x8000;
+	for(size_t walk = 0; walk < count; ++walk) {
+		uint16_t v = *inPtr++;
+		if (byteSwap) v = pfc::byteswap_t(v);
+		if (!isSigned) v ^= 0x8000; // to signed
+		*out++ = (audio_sample) (int16_t) v * factor;
+	}
+}
+
+template<bool byteSwap, bool isSigned> static void _import32any(const void * in, audio_sample * out, size_t count) {
+	uint32_t const * inPtr = (uint32_t const*) in;
+	const audio_sample factor = 1.0f / (audio_sample) 0x80000000ul;
+	for(size_t walk = 0; walk < count; ++walk) {
+		uint32_t v = *inPtr++;
+		if (byteSwap) v = pfc::byteswap_t(v);
+		if (!isSigned) v ^= 0x80000000u; // to signed
+		*out++ = (audio_sample) (int32_t) v * factor;
+	}
+}
+
+template<bool byteSwap, bool isSigned> static void _import24any(const void * in, audio_sample * out, size_t count) {
+	uint8_t const * inPtr = (uint8_t const*) in;
+	const audio_sample factor = 1.0f / (audio_sample) 0x800000;
+	for(size_t walk = 0; walk < count; ++walk) {
+		uint32_t v;
+		if (byteSwap) v = (uint32_t) inPtr[2] | ( (uint32_t) inPtr[1] << 8 ) | ( (uint32_t) inPtr[0] << 16 );
+		else v = (uint32_t) inPtr[0] | ( (uint32_t) inPtr[1] << 8 ) | ( (uint32_t) inPtr[2] << 16 );
+		inPtr += 3;
+		if (isSigned) v ^= 0x800000; // to unsigned
+		v -= 0x800000; // then subtract to get proper MSBs
+		*out++ = (audio_sample) (int32_t) v * factor;
+	}
+}
+
+void audio_chunk::set_data_fixedpoint_ex(const void * source,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
+{
+	PFC_ASSERT( check_exclusive(flags,FLAG_SIGNED|FLAG_UNSIGNED) );
+	PFC_ASSERT( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
+
+	bool byteSwap = !!(flags & FLAG_BIG_ENDIAN);
+	if (pfc::byte_order_is_big_endian) byteSwap = !byteSwap;
+
+	t_size count = size / (bps/8);
+	set_data_size(count);
+	audio_sample * buffer = get_data();
+	bool isSigned = !!(flags & FLAG_SIGNED);
+
+	switch(bps)
+	{
+	case 8:
+		// byte order irrelevant
+		if (isSigned) _import8s( (const uint8_t*) source , buffer, count);
+		else _import8u( (const uint8_t*) source , buffer, count);
+		break;
+	case 16:
+		if (byteSwap) {
+			if (isSigned) {
+				_import16any<true, true>( source, buffer, count );
+			} else {
+				_import16any<true, false>( source, buffer, count );
+			}
+		} else {
+			if (isSigned) {
+				//_import16any<false, true>( source, buffer, count );
+				audio_math::convert_from_int16((const int16_t*)source,count,buffer,1.0);
+			} else {
+				_import16any<false, false>( source, buffer, count);
+			}
+		}
+		break;
+	case 24:
+		if (byteSwap) {
+			if (isSigned) {
+				_import24any<true, true>( source, buffer, count );
+			} else {
+				_import24any<true, false>( source, buffer, count );
+			}
+		} else {
+			if (isSigned) {
+				//_import24any<false, true>( source, buffer, count);
+				_import24( source, buffer, count);
+			} else {
+				_import24any<false, false>( source, buffer, count);
+			}
+		}
+		break;
+	case 32:
+		if (byteSwap) {
+			if (isSigned) {
+				_import32any<true, true>( source, buffer, count );
+			} else {
+				_import32any<true, false>( source, buffer, count );
+			}
+		} else {
+			if (isSigned) {
+				audio_math::convert_from_int32((const int32_t*)source,count,buffer,1.0);
+			} else {
+				_import32any<false, false>( source, buffer, count);
+			}
+		}
+		break;
+	default:
+		//unknown size, cant convert
+		pfc::memset_t(buffer,(audio_sample)0,count);
+		break;
+	}
+	set_sample_count(count/nch);
+	set_srate(srate);
+	set_channels(nch,p_channel_config);
+}
+
+void audio_chunk::set_data_fixedpoint_ms(const void * ptr, size_t bytes, unsigned sampleRate, unsigned channels, unsigned bps, unsigned channelConfig) {
+	//set_data_fixedpoint_ex(ptr,bytes,sampleRate,channels,bps,(bps==8 ? FLAG_UNSIGNED : FLAG_SIGNED) | flags_autoendian(), channelConfig);
+	PFC_ASSERT( bps != 0 );
+	size_t count = bytes / (bps/8);
+	this->set_data_size( count );
+	audio_sample * buffer = this->get_data();
+	switch(bps) {
+	case 8:
+		_import8u((const uint8_t*)ptr, buffer, count);
+		break;
+	case 16:
+		audio_math::convert_from_int16((const int16_t*) ptr, count, buffer, 1.0);
+		break;
+	case 24:
+		_import24( ptr, buffer, count);
+		break;
+	case 32:
+		audio_math::convert_from_int32((const int32_t*) ptr, count, buffer, 1.0);
+		break;
+	default:
+		PFC_ASSERT(!"Unknown bit depth!");
+		memset(buffer, 0, sizeof(audio_sample) * count);
+		break;
+	}
+	set_sample_count(count/channels);
+	set_srate(sampleRate);
+	set_channels(channels,channelConfig);
+}
+
+void audio_chunk::set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned sampleRate,unsigned channels,unsigned bps,unsigned channelConfig) {
+	PFC_ASSERT( bps != 0 );
+	size_t count = bytes / (bps/8);
+	this->set_data_size( count );
+	audio_sample * buffer = this->get_data();
+	switch(bps) {
+	case 8:
+		_import8s((const uint8_t*)ptr, buffer, count);
+		break;
+	case 16:
+		audio_math::convert_from_int16((const int16_t*) ptr, count, buffer, 1.0);
+		break;
+	case 24:
+		_import24( ptr, buffer, count);
+		break;
+	case 32:
+		audio_math::convert_from_int32((const int32_t*) ptr, count, buffer, 1.0);
+		break;
+	default:
+		PFC_ASSERT(!"Unknown bit depth!");
+		memset(buffer, 0, sizeof(audio_sample) * count);
+		break;
+	}
+	set_sample_count(count/channels);
+	set_srate(sampleRate);
+	set_channels(channels,channelConfig);
+}
+
+void audio_chunk::set_data_int16(const int16_t * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config) {
+	const size_t count = samples * nch;
+	this->set_data_size( count );
+	audio_sample * buffer = this->get_data();
+	audio_math::convert_from_int16(src, count, buffer, 1.0);
+	set_sample_count(samples);
+	set_srate(srate);
+	set_channels(nch,channel_config);
+}
+
+template<class t_float>
+static void process_float_multi(audio_sample * p_out,const t_float * p_in,const t_size p_count)
+{
+	audio_math::convert(p_in, p_out, p_count);
+}
+
+template<class t_float>
+static void process_float_multi_swap(audio_sample * p_out,const t_float * p_in,const t_size p_count)
+{
+	for(size_t n=0;n<p_count;n++) {
+		p_out[n] = (audio_sample) pfc::byteswap_t(p_in[n]);
+	}
+}
+
+void audio_chunk::set_data_32(const float* src, size_t samples, spec_t const& spec) {
+#if audio_sample_size == 32
+	set_data(src, samples, spec);
+#else
+	t_size size = samples * spec.chanCount;
+	set_data_size(size);
+	if (src)
+		audio_math::convert(src, get_data(), size);
+	else
+		pfc::memset_t(get_data(), (audio_sample)0, size);
+	set_sample_count(samples);
+	set_spec(spec);
+#endif
+}
+void audio_chunk::set_data_32(const float* src, size_t samples, unsigned nch, unsigned srate) { 
+	set_data_32(src, samples, makeSpec(srate, nch) );
+}
+
+void audio_chunk::set_data_floatingpoint_ex(const void * ptr,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
+{
+	PFC_ASSERT(is_supported_floatingpoint(bps));
+	PFC_ASSERT( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
+	PFC_ASSERT( ! (flags & (FLAG_SIGNED|FLAG_UNSIGNED) ) );
+
+	bool use_swap = pfc::byte_order_is_big_endian ? !!(flags & FLAG_LITTLE_ENDIAN) : !!(flags & FLAG_BIG_ENDIAN);
+
+	const t_size count = size / (bps/8);
+	set_data_size(count);
+	audio_sample * out = get_data();
+
+	if (bps == 32)
+	{
+		if (use_swap)
+			process_float_multi_swap(out,reinterpret_cast<const float*>(ptr),count);
+		else
+			process_float_multi(out,reinterpret_cast<const float*>(ptr),count);
+	}
+	else if (bps == 64)
+	{
+		if (use_swap)
+			process_float_multi_swap(out,reinterpret_cast<const double*>(ptr),count);
+		else
+			process_float_multi(out,reinterpret_cast<const double*>(ptr),count);
+	} else if (bps == 16) {
+		const uint16_t * in = reinterpret_cast<const uint16_t*>(ptr);
+		if (use_swap) {
+			for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat16(pfc::byteswap_t(in[walk]));
+		} else {
+			for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat16(in[walk]);
+		}
+	} else if (bps == 24) {
+		const uint8_t * in = reinterpret_cast<const uint8_t*>(ptr);
+		if (use_swap) {
+			for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat24ptrbs(&in[walk*3]);
+		} else {
+			for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat24ptr(&in[walk*3]);
+		}
+	} else pfc::throw_exception_with_message< exception_io_data >("invalid bit depth");
+
+	set_sample_count(count/nch);
+	set_srate(srate);
+	set_channels(nch,p_channel_config);
+}
+
+pfc::string8 audio_chunk::formatChunkSpec() const {
+	pfc::string8 msg;
+	msg << get_sample_rate() << " Hz, " << get_channels() << ":0x" << pfc::format_hex(get_channel_config(), 2) << " channels, " << get_sample_count() << " samples";
+	return msg;
+}
+
+void audio_chunk::debugChunkSpec() const {
+	FB2K_DebugLog() << "Chunk: " << this->formatChunkSpec();
+}
+
+#if PFC_DEBUG
+void audio_chunk::assert_valid(const char * ctx) const {
+	if (!is_valid()) {
+		FB2K_DebugLog() << "audio_chunk::assert_valid failure in " << ctx;
+		debugChunkSpec();
+		uBugCheck();
+	}
+}
+#endif
+bool audio_chunk::is_valid() const
+{
+	unsigned nch = get_channels();
+	if (nch == 0 || nch > 32) return false;
+	if (!g_is_valid_sample_rate(get_srate())) return false;
+	t_size samples = get_sample_count();
+	if (samples==0 || samples >= 0x80000000ul / (sizeof(audio_sample) * nch) ) return false;
+	t_size size = get_data_size();
+	if (samples * nch > size) return false;
+	if (!get_data()) return false;
+	return true;
+}
+
+bool audio_chunk::is_spec_valid() const {
+	return this->get_spec().is_valid();
+}
+
+void audio_chunk::pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate) {
+	if (is_empty())
+	{
+		if (hint_srate && hint_nch) {
+			return set_data(0,samples,hint_nch,hint_srate);
+		} else throw exception_io_data();
+	}
+	else
+	{
+		if (hint_srate && hint_srate != get_srate()) samples = MulDiv_Size(samples,get_srate(),hint_srate);
+		if (samples > get_sample_count())
+		{
+			t_size old_size = get_sample_count() * get_channels();
+			t_size new_size = samples * get_channels();
+			set_data_size(new_size);
+			pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
+			set_sample_count(samples);
+		}
+	}
+}
+
+void audio_chunk::pad_with_silence(t_size samples) {
+	if (samples > get_sample_count())
+	{
+		t_size old_size = get_sample_count() * get_channels();
+		t_size new_size = pfc::multiply_guarded(samples,(size_t)get_channels());
+		set_data_size(new_size);
+		pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
+		set_sample_count(samples);
+	}
+}
+
+void audio_chunk::set_silence(t_size samples) {
+	t_size items = samples * get_channels();
+	set_data_size(items);
+	pfc::memset_null_t(get_data(), items);
+	set_sample_count(samples);
+}
+
+void audio_chunk::set_silence_seconds( double seconds ) {
+	set_silence( (size_t) audio_math::time_to_samples( seconds, this->get_sample_rate() ) ); 
+}
+
+void audio_chunk::insert_silence_fromstart(t_size samples) {
+	t_size old_size = get_sample_count() * get_channels();
+	t_size delta = samples * get_channels();
+	t_size new_size = old_size + delta;
+	set_data_size(new_size);
+	audio_sample * ptr = get_data();
+	pfc::memmove_t(ptr+delta,ptr,old_size);
+	pfc::memset_t(ptr,(audio_sample)0,delta);
+	set_sample_count(get_sample_count() + samples);
+}
+
+bool audio_chunk::process_skip(double & skipDuration) {
+	t_uint64 skipSamples = audio_math::time_to_samples(skipDuration, get_sample_rate());
+	if (skipSamples == 0) {skipDuration = 0; return true;}
+	const t_size mySamples = get_sample_count();
+	if (skipSamples < mySamples) {
+		skip_first_samples((t_size)skipSamples); 
+		skipDuration = 0;
+		return true;
+	}
+	if (skipSamples == mySamples) {
+		skipDuration = 0;
+		return false;
+	}
+	skipDuration -= audio_math::samples_to_time(mySamples, get_sample_rate());
+	return false;
+}
+
+t_size audio_chunk::skip_first_samples(t_size samples_delta)
+{
+	t_size samples_old = get_sample_count();
+	if (samples_delta >= samples_old)
+	{
+		set_sample_count(0);
+		set_data_size(0);
+		return samples_old;
+	}
+	else
+	{
+		t_size samples_new = samples_old - samples_delta;
+		unsigned nch = get_channels();
+		audio_sample * ptr = get_data();
+		pfc::memmove_t(ptr,ptr+nch*samples_delta,nch*samples_new);
+		set_sample_count(samples_new);
+		set_data_size(nch*samples_new);
+		return samples_delta;
+	}
+}
+
+audio_sample audio_chunk::get_peak(audio_sample p_peak) const {
+	return pfc::max_t(p_peak, get_peak());
+}
+
+audio_sample audio_chunk::get_peak() const {
+	return audio_math::calculate_peak(get_data(),get_sample_count() * get_channels());
+}
+
+void audio_chunk::scale(audio_sample p_value)
+{
+	audio_sample * ptr = get_data();
+	audio_math::scale(ptr,get_sample_count() * get_channels(),ptr,p_value);
+}
+
+
+namespace {
+
+struct sampleToIntDesc {
+	unsigned bps, bpsValid;
+	bool useUpperBits;
+	audio_sample scale;
+};
+template<typename int_t> class sampleToInt {
+public:
+	sampleToInt(sampleToIntDesc const & d) {
+		clipLo = - ( (int_t) 1 << (d.bpsValid-1));
+		clipHi = ( (int_t) 1 << (d.bpsValid-1)) - 1;
+		scale = (float) ( (int64_t) 1 << (d.bpsValid - 1) ) * d.scale;
+		if (d.useUpperBits) {
+			shift = (int8_t)( d.bps - d.bpsValid );
+		} else {
+			shift = 0;
+		}
+	}
+	inline int_t operator() (audio_sample s) const {
+		int_t v;
+		if constexpr (sizeof(int_t) > 4) v = (int_t) audio_math::rint64( s * scale );
+		else v = (int_t)audio_math::rint32( s * scale );
+		return pfc::clip_t<int_t>( v, clipLo, clipHi) << shift;
+	}
+private:
+	int_t clipLo, clipHi;
+	int8_t shift;
+	audio_sample scale;
+};
+}
+static void render_24bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
+	t_uint8 * outWalk = reinterpret_cast<t_uint8*>(out);
+	sampleToInt<int32_t> gen(d);
+	for(t_size walk = 0; walk < inLen; ++walk) {
+		int32_t v = gen(in[walk]);
+		*(outWalk ++) = (t_uint8) (v & 0xFF);
+		*(outWalk ++) = (t_uint8) ((v >> 8) & 0xFF);
+		*(outWalk ++) = (t_uint8) ((v >> 16) & 0xFF);
+	}
+}
+static void render_8bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
+	sampleToInt<int32_t> gen(d);
+	t_int8 * outWalk = reinterpret_cast<t_int8*>(out);
+	for(t_size walk = 0; walk < inLen; ++walk) {
+		*outWalk++ = (t_int8)gen(in[walk]);
+	}
+}
+static void render_16bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
+	sampleToInt<int32_t> gen(d);
+	int16_t * outWalk = reinterpret_cast<int16_t*>(out);
+	for(t_size walk = 0; walk < inLen; ++walk) {
+		*outWalk++ = (int16_t)gen(in[walk]);
+	}
+}
+
+template<typename internal_t>
+static void render_32bit_(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
+	sampleToInt<internal_t> gen(d); // must use int64 for clipping
+	int32_t * outWalk = reinterpret_cast<int32_t*>(out);
+	for(t_size walk = 0; walk < inLen; ++walk) {
+		*outWalk++ = (int32_t)gen(in[walk]);
+	}
+}
+
+bool audio_chunk::g_toFixedPoint(const audio_sample * in, void * out, size_t count, uint32_t bps, uint32_t bpsValid, bool useUpperBits, audio_sample scale) {
+	const sampleToIntDesc d = {bps, bpsValid, useUpperBits, scale};
+	if (bps == 0) {
+		PFC_ASSERT(!"How did we get here?");
+		return false;
+	} else if (bps <= 8) {
+		render_8bit(in, count, out, d);
+	} else if (bps <= 16) {
+		render_16bit(in, count, out, d);
+	} else if (bps <= 24) {
+		render_24bit(in, count, out, d);
+	} else if (bps <= 32) {
+		if (bpsValid <= 28) { // for speed
+			render_32bit_<int32_t>(in, count, out, d);
+		} else {
+			render_32bit_<int64_t>(in, count, out, d);
+		}
+	} else {
+		PFC_ASSERT(!"How did we get here?");
+		return false;
+	}
+
+	return true;
+}
+
+bool audio_chunk::toFixedPoint(class mem_block_container & out, uint32_t bps, uint32_t bpsValid, bool useUpperBits, audio_sample scale) const {
+	bps = (bps + 7) & ~7;
+	if (bps < bpsValid) return false;
+	const size_t count = get_sample_count() * get_channel_count();
+	out.set_size( count * (bps/8) );
+	return g_toFixedPoint(get_data(), out.get_ptr(), count, bps, bpsValid, useUpperBits, scale);
+}
+
+bool audio_chunk::to_raw_data(mem_block_container & out, t_uint32 bps, bool useUpperBits, audio_sample scale) const {
+	uint32_t bpsValid = bps;
+	bps = (bps + 7) & ~7;
+	const size_t count = get_sample_count() * get_channel_count();
+	out.set_size( count * (bps/8) );
+	void * outPtr = out.get_ptr();
+	audio_sample const * inPtr = get_data();
+	if (bps == 32) {
+		float * f = (float*) outPtr;
+		audio_math::convert(inPtr, f, count, scale);
+		return true;
+	} else {
+		return g_toFixedPoint(inPtr, outPtr, count, bps, bpsValid, useUpperBits, scale);
+	}
+}
+
+audio_chunk::spec_t audio_chunk::makeSpec(uint32_t rate, uint32_t channels) {
+	return makeSpec( rate, channels, g_guess_channel_config(channels) );
+}
+
+audio_chunk::spec_t audio_chunk::makeSpec(uint32_t rate, uint32_t channels, uint32_t mask) {
+	PFC_ASSERT(mask == 0 || pfc::countBits32(mask) == channels);
+	spec_t spec = {};
+	spec.sampleRate = rate; spec.chanCount = channels; spec.chanMask = mask;
+	return spec;
+}
+
+bool audio_chunk::spec_t::equals( const spec_t & v1, const spec_t & v2 ) {
+	return v1.sampleRate == v2.sampleRate && v1.chanCount == v2.chanCount && v1.chanMask == v2.chanMask;
+}
+
+pfc::string8 audio_chunk::spec_t::toString(const char * delim) const {
+	pfc::string_formatter temp;
+	if ( sampleRate > 0 ) temp << sampleRate << "Hz";
+	if (chanCount > 0) {
+		if ( temp.length() > 0 ) temp << delim;
+		temp << chanCount << "ch";
+	}
+
+	if ( chanMask != audio_chunk::channel_config_mono && chanMask != audio_chunk::channel_config_stereo ) {
+		pfc::string8 strMask;
+		audio_chunk::g_formatChannelMaskDesc( chanMask, strMask );
+		if ( temp.length() > 0) temp << delim;
+		temp << strMask;
+	}		
+	return temp;
+}
+
+audio_chunk::spec_t audio_chunk::get_spec() const {
+	spec_t spec = {};
+	spec.sampleRate = this->get_sample_rate();
+	spec.chanCount = this->get_channel_count();
+	spec.chanMask = this->get_channel_config();
+	return spec;
+}
+void audio_chunk::set_spec(const spec_t & spec) {
+	set_sample_rate(spec.sampleRate);
+	set_channels( spec.chanCount, spec.chanMask );
+}
+
+bool audio_chunk::spec_t::is_valid() const {
+    if (this->chanCount==0 || this->chanCount>256) return false;
+    if (!audio_chunk::g_is_valid_sample_rate(this->sampleRate)) return false;
+    return true;
+}
+
+#ifdef _WIN32
+
+WAVEFORMATEX audio_chunk::spec_t::toWFX() const {
+	const uint32_t sampleWidth = sizeof(audio_sample);
+
+	WAVEFORMATEX wfx = {};
+	wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
+	wfx.nChannels = (WORD) chanCount;
+	wfx.nSamplesPerSec = sampleRate;
+	wfx.nAvgBytesPerSec = sampleRate * chanCount * sampleWidth;
+	wfx.nBlockAlign = (WORD)( chanCount * sampleWidth );
+	wfx.wBitsPerSample = sampleWidth * 8;
+	return wfx;
+}
+
+WAVEFORMATEXTENSIBLE audio_chunk::spec_t::toWFXEX() const {
+	const uint32_t sampleWidth = sizeof(audio_sample);
+	const bool isFloat = true;
+
+	WAVEFORMATEXTENSIBLE wfxe;
+	wfxe.Format = toWFX();
+	wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+	wfxe.Format.cbSize = sizeof(wfxe) - sizeof(wfxe.Format);
+	wfxe.Samples.wValidBitsPerSample = sampleWidth * 8;
+	wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(this->chanMask);
+	wfxe.SubFormat = isFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
+	
+	return wfxe;
+}
+
+WAVEFORMATEX audio_chunk::spec_t::toWFXWithBPS(uint32_t bps) const {
+	const uint32_t sampleWidth = (bps+7)/8;
+
+	WAVEFORMATEX wfx = {};
+	wfx.wFormatTag = WAVE_FORMAT_PCM;
+	wfx.nChannels = (WORD)chanCount;
+	wfx.nSamplesPerSec = sampleRate;
+	wfx.nAvgBytesPerSec = sampleRate * chanCount * sampleWidth;
+	wfx.nBlockAlign = (WORD)( chanCount * sampleWidth );
+	wfx.wBitsPerSample = (WORD)( sampleWidth * 8 );
+	return wfx;
+}
+
+WAVEFORMATEXTENSIBLE audio_chunk::spec_t::toWFXEXWithBPS(uint32_t bps) const {
+	const uint32_t sampleWidth = (bps + 7) / 8;
+	const bool isFloat = false;
+
+	WAVEFORMATEXTENSIBLE wfxe;
+	wfxe.Format = toWFXWithBPS(bps);
+	wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+	wfxe.Format.cbSize = sizeof(wfxe) - sizeof(wfxe.Format);
+	wfxe.Samples.wValidBitsPerSample = (WORD)( sampleWidth * 8 );
+	wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(this->chanMask);
+	wfxe.SubFormat = isFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
+
+	return wfxe;
+}
+#endif // _WIN32
+
+void audio_chunk::append(const audio_chunk& other) {
+	if (other.get_spec() != this->get_spec()) {
+		throw pfc::exception_invalid_params();
+	}
+
+	this->grow_data_size(get_used_size() + other.get_used_size());
+	audio_sample* p = this->get_data() + get_used_size();
+	memcpy(p, other.get_data(), other.get_used_size() * sizeof(audio_sample));
+	set_sample_count(get_sample_count() + other.get_sample_count());
+}