/*
* SACD Decoder plugin
* Copyright (c) 2011-2024 Maxim V.Anisiutkin <maxim.anisiutkin@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "sacd_core.h"
#include "dop_converter.h"
#include "dsd_stream_service.h"
#include "dsdpcm_decoder.h"
#include "dst_decoder.h"
#include <psapi.h>
#include <helpers/input_helpers.h>
#include <deque>

constexpr int UPDATE_STATS_MS = 500;
constexpr int BITRATE_AVGS = 16;
constexpr audio_sample PCM_OVERLOAD_THRESHOLD = 1.0f;
constexpr unsigned char DSD_SILENCE_BYTE = 0x69;

enum {
	input_flag_dsd_extract             = 1 << 16,
	input_flag_dsd_edited_master_track = 1 << 17,
};

static int get_sacd_channel_map_from_loudspeaker_config(int loudspeaker_config) {
	int sacd_channel_map;
	switch (loudspeaker_config) {
	case 0:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right;
		break;
	case 1:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right;
		break;
	case 2:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center | audio_chunk::channel_lfe;
		break;
	case 3:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center | audio_chunk::channel_back_left | audio_chunk::channel_back_right;
		break;
	case 4:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center | audio_chunk::channel_lfe | audio_chunk::channel_back_left | audio_chunk::channel_back_right;
		break;
	case 5:
		sacd_channel_map = audio_chunk::channel_front_center;
		break;
	case 6:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center;
		break;
	default:
		sacd_channel_map = 0;
		break;
	}
	return sacd_channel_map;
}

static int get_sacd_channel_map_from_channels(int channels) {
	int sacd_channel_map;
	switch (channels) {
	case 1:
		sacd_channel_map = audio_chunk::channel_front_center;
		break;
	case 2:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right;
		break;
	case 3:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center;
		break;
	case 4:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right;
		break;
	case 5:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center | audio_chunk::channel_back_left | audio_chunk::channel_back_right;
		break;
	case 6:
		sacd_channel_map = audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_front_center | audio_chunk::channel_lfe | audio_chunk::channel_back_left | audio_chunk::channel_back_right;
		break;
	default:
		sacd_channel_map = audio_chunk::g_guess_channel_config(channels);
		break;
	}
	return sacd_channel_map;
}

static conv_type_e get_converter_type() {
	auto conv_type = conv_type_e::MULTISTAGE;
	switch (CSACDPreferences::get_converter_mode()) {
	case 0:
	case 1:
		conv_type = conv_type_e::MULTISTAGE;
		break;
	case 2:
	case 3:
		conv_type = conv_type_e::DIRECT;
		break;
	case 4:
	case 5:
		conv_type = conv_type_e::USER;
		break;
	}
	return conv_type;
}

static bool get_converter_fp64() {
	auto conv_fp64 = false;
	switch (CSACDPreferences::get_converter_mode()) {
	case 1:
	case 3:
	case 5:
		conv_fp64 = true;
		break;
	}
	return conv_fp64;
}

static bool call_from_foo_module(LPCTSTR p_module_name, abort_callback& p_abort) {
	MODULEINFO module_info;
	memset(&module_info, 0, sizeof(module_info));
	if (GetModuleInformation(GetCurrentProcess(), GetModuleHandle(p_module_name), &module_info, sizeof(module_info))) {
		BYTE* module_vtbl = reinterpret_cast<BYTE**>(&p_abort)[0];
		DWORD module_size = (DWORD)module_info.SizeOfImage;
		BYTE* module_base = (BYTE*)module_info.lpBaseOfDll;
		if (module_vtbl >= module_base && module_vtbl <= module_base + module_size) {
			return true;
		}
	}
	return false;
}

class dsdpcm_wrapper_t final {
	static dsdpcm_decoder_t s_playback;
	static size_t s_channels;
	static size_t s_framerate;
	static size_t s_dsd_samplerate;
	static size_t s_pcm_samplerate;
	static bool s_req_init;
	static bool s_use_ramp;
	static bool s_use_correction;
	static double s_ramp_duration;
	static double s_ramp_delay;
	static double s_ramp_timedown;

	dsdpcm_decoder_t m_convert;
	bool m_use_playback{ false };
public:
	static void req_init() {
		s_req_init = true;
	}
	static bool use_ramp() {
		return s_use_ramp;
	}
	static void set_ramp(bool p_use) {
		s_use_ramp = p_use;
	}
	static void set_ramp(double p_duration) {
		s_ramp_duration = p_duration;
	}
	void set_payback(bool p_use) {
		m_use_playback = p_use;
	}
	double get_delay() {
		return m_use_playback ? s_playback.get_delay() : m_convert.get_delay();
	}
	int init(size_t p_channels, size_t p_framerate, size_t p_dsd_samplerate, size_t p_pcm_samplerate, conv_type_e p_conv_type, bool p_conv_fp64, double* p_fir_data = nullptr, size_t p_fir_size = 0, size_t p_fir_decimation = 0) {
		int rc{ 0 };
		if (m_use_playback) {
			if (s_req_init || !(s_channels == p_channels && s_framerate == p_framerate && s_dsd_samplerate == p_dsd_samplerate && s_pcm_samplerate == p_pcm_samplerate)) {
				s_req_init = false;
				rc = s_playback.init(p_channels, p_framerate, p_dsd_samplerate, p_pcm_samplerate, p_conv_type, p_conv_fp64, p_fir_data, p_fir_size, p_fir_decimation);
				s_channels       = p_channels;
				s_framerate      = p_framerate;
				s_dsd_samplerate = p_dsd_samplerate;
				s_pcm_samplerate = p_pcm_samplerate;
				s_ramp_delay = s_playback.get_delay() / s_pcm_samplerate;
				set_ramp(true);
				s_use_correction = true;
			}
			if (use_ramp()) {
				s_ramp_timedown = s_ramp_duration;
			}
			set_ramp(true);
		}
		else {
			rc = m_convert.init(p_channels, p_framerate, p_dsd_samplerate, p_pcm_samplerate, p_conv_type, p_conv_fp64, p_fir_data, p_fir_size, p_fir_decimation);
		}
		return rc;
	}
	void free() {
		if (m_use_playback) {
			s_playback.free();
		}
		else {
			m_convert.free();
		}
	}
	size_t convert(const unsigned char* p_dsd_data, size_t p_dsd_size, audio_sample* p_pcm_data) {
		size_t pcm_size;
		if (m_use_playback) {
			pcm_size = s_playback.convert(p_dsd_data, p_dsd_size, p_pcm_data);
			if (s_use_correction) {
				correct(p_pcm_data, pcm_size, s_channels, s_playback.get_delay());
				s_use_correction = false;
			}
			if (s_ramp_timedown > 0.0) {
				for (auto sample = 0u; sample < pcm_size / s_channels; sample++) {
					auto volume =	static_cast<audio_sample>((s_ramp_timedown > s_ramp_duration - s_ramp_delay) ? 0.0 : ((s_ramp_timedown > 0.0) ? (s_ramp_duration - s_ramp_delay - s_ramp_timedown) / (s_ramp_duration - s_ramp_delay) : 1.0));
					s_ramp_timedown -= 1.0 / s_pcm_samplerate;
					for (auto ch = 0u; ch < s_channels; ch++) {
						p_pcm_data[sample * s_channels + ch] *= volume;
					}
				}
			}
		}
		else {
			pcm_size = m_convert.convert(p_dsd_data, p_dsd_size, p_pcm_data);
		}
		return pcm_size;
	}
	void correct(audio_sample* p_pcm_data, size_t p_pcm_size, size_t p_channels, double p_delay) {
		auto samples = min(p_channels ? p_pcm_size / p_channels : 0u, size_t(2.0 * p_delay));
		auto zeroes = 2 * samples / 3;
		for (auto sample = 0u; sample < samples; sample++) {
			auto scale = (sample < zeroes) ? audio_sample(0) : audio_sample(sample - zeroes) / audio_sample(samples - zeroes);
			for (auto ch = 0u; ch < p_channels; ch++) {
				p_pcm_data[sample * p_channels + ch] *= scale;
			}
		}
	}
};

size_t dsdpcm_wrapper_t::s_channels{ 0 };
size_t dsdpcm_wrapper_t::s_framerate{ 0 };
size_t dsdpcm_wrapper_t::s_dsd_samplerate{ 0 };
size_t dsdpcm_wrapper_t::s_pcm_samplerate{ 0 };
bool dsdpcm_wrapper_t::s_req_init{ false };
bool dsdpcm_wrapper_t::s_use_ramp{ false };
bool dsdpcm_wrapper_t::s_use_correction{ false };
double dsdpcm_wrapper_t::s_ramp_duration{ 0.0 };
double dsdpcm_wrapper_t::s_ramp_delay{ 0.0 };
double dsdpcm_wrapper_t::s_ramp_timedown{ 0.0 };
dsdpcm_decoder_t dsdpcm_wrapper_t::s_playback;

class playback_handler_t : public play_callback_static {
public:
	playback_handler_t() {}
	void on_playback_starting(play_control::t_track_command p_command, bool p_paused) {
		dsdpcm_wrapper_t::req_init();
	}
	void on_playback_new_track(metadb_handle_ptr p_track) {}
	void on_playback_stop(play_control::t_stop_reason p_reason) {
		dsdpcm_wrapper_t::req_init();
	}
	void on_playback_seek(double p_time) {
		dsdpcm_wrapper_t::req_init();
	}
	void on_playback_pause(bool p_state) {}
	void on_playback_edited(metadb_handle_ptr p_track) {}
	void on_playback_dynamic_info(const file_info& p_info) {}
	void on_playback_dynamic_info_track(const file_info& p_info) {}
	void on_playback_time(double p_time) {}
	void on_volume_change(float p_new_val) {}
	unsigned get_flags() {
		return flag_on_playback_starting | flag_on_playback_seek | flag_on_playback_stop;
	}
};

static play_callback_static_factory_t<playback_handler_t> g_playback_sacd_factory;


class input_sacd_t : public sacd_core_t, public input_stubs {
	string8              input_path;
	uint32_t             input_subsong;
	unsigned             input_flags;
	t_input_open_reason  input_reason;

	area_id_e            playable_area;
	int64_t              sacd_bitrate[BITRATE_AVGS];
	int                  sacd_bitrate_idx;
	int64_t              sacd_bitrate_sum;
	
	vector<uint8_t>      dsx_data;
	vector<audio_sample> pcm_data;

	dst_decoder_t        dst_decoder;
	bool                 dst_decoder_initialized;

	dsdpcm_wrapper_t     dsdpcm_decoder;
	double*              dsdpcm_decoder_fir_data;
	t_size               dsdpcm_decoder_fir_size;
	int                  dsdpcm_decoder_fir_decimation;

	audio_sample         vol_adjust_coef;
	audio_sample         lfe_adjust_coef;
	bool                 log_overloads;
	string8              log_track_name;
	uint32_t             info_update_time_ms;
	int                  pcm_out_channels;
	unsigned int         pcm_out_channel_map;
	int                  pcm_out_samplerate;
	double               pcm_out_delay;
	int                  pcm_out_append_samples;
	int                  pcm_out_remove_samples;
	uint64_t             pcm_out_offset;
	int                  pcm_min_samplerate;

	bool                 use_dsd_path;
	bool                 use_pcm_path;
	bool                 use_dop_for_pcm;
	dop_converter_t      dop_converter;

	bool                 call_from_foo_converter;

	int                                  dsd_samplerate;
	static_api_ptr_t<dsd_stream_service> dsd_stream;
	
	int                      framerate;
	bool                     read_frame;
	std::deque<frame_span_e> span_queue;
public:
	input_sacd_t() {
		CSACDPreferences::load();
		input_reason = input_open_info_read;
		access_mode = ACCESS_MODE_NULL;
		vol_adjust_coef = 1.0f;
		lfe_adjust_coef = 1.0f;
		log_overloads = false;
		info_update_time_ms = 0;
		dsdpcm_decoder_fir_data = nullptr;
		dsdpcm_decoder_fir_size = 0;
		dsdpcm_decoder_fir_decimation = 0;
		use_dsd_path = false;
		use_pcm_path = true;
		read_frame = false;
	}

	virtual ~input_sacd_t() {
		if (use_dsd_path) {
			dsd_stream->set_streaming(false);
		}
	}

	void open(file_ptr p_filehint, const char* p_path, t_input_open_reason p_reason, abort_callback& p_abort) {
		call_from_foo_converter = call_from_foo_module(_T("foo_converter.dll"), p_abort);
		input_path = p_path;
		input_reason = p_reason;
		sacd_core_t::open(p_filehint, p_path, p_reason, p_abort);
	}

	t_uint32 get_subsong_count() {
		t_uint32 track_count = 0;
		switch (CSACDPreferences::get_area()) {
		case AREA_TWOCH:
			track_count = sacd_reader->get_track_count(AREA_TWOCH);
			if (track_count == 0) {
				sacd_reader->set_mode(access_mode | AREA_MULCH, true);
				track_count = sacd_reader->get_track_count(AREA_MULCH);
			}
			break;
		case AREA_MULCH:
			track_count = sacd_reader->get_track_count(AREA_MULCH);
			if (track_count == 0) {
				sacd_reader->set_mode(access_mode | AREA_TWOCH);
				track_count = sacd_reader->get_track_count(AREA_TWOCH);
			}
			break;
		default:
			track_count = sacd_reader->get_track_count(AREA_TWOCH) + sacd_reader->get_track_count(AREA_MULCH);
			break;
		}
		return track_count;
	}

	t_uint32 get_subsong(t_uint32 p_index) {
		auto subsong = sacd_reader->get_track_number(p_index);
		return subsong;
	}

	void get_info(t_uint32 p_subsong, file_info& p_info, abort_callback& p_abort) {
		sacd_reader->get_info(p_subsong, p_info);
		if (sacd_metabase) {
			sacd_metabase->get_track_info(p_subsong, p_info);
		}
		switch (input_reason) {
		case input_open_decode:
			if (!use_dsd_path) {
				p_info.info_set_int("samplerate", pcm_out_samplerate);
			}
			if (!use_dsd_path || call_from_foo_converter) {
				p_info.info_set_int("bitspersample", 24);
			}
			break;
		case input_open_info_read:
			if (call_from_foo_converter) {
				p_info.info_set_int("bitspersample", 24);
			}
			break;
		}
		if (log_overloads) {
			auto track_title = p_info.meta_get("title", 0);
			log_track_name = track_title ? track_title : "Untitled";
		}
	}

	t_filestats2 get_stats2(uint32_t p_f, abort_callback& p_abort) {
		return t_filestats2::from_legacy(sacd_media->get_stats());
	}

	const char* get_time_stamp(double t) {
		static char ts[13];
		int fraction = (int)((t - floor(t)) * 1000);
		int second = (int)floor(t);
		int minute = second / 60;
		int hour = minute / 60;
		ts[0] = '0' + (hour / 10) % 6;
		ts[1] = '0' + hour % 10;
		ts[2] = ':';
		ts[3] = '0' + (minute / 10) % 6;
		ts[4] = '0' + minute % 10;
		ts[5] = '.';
		ts[6] = '0' + (second / 10) % 6;
		ts[7] = '0' + second % 10;
		ts[8] = '.';
		ts[9] = '0' + (fraction / 100) % 10;
		ts[10] = '0' + (fraction / 10) % 10;
		ts[11] = '0' + fraction % 10;
		ts[12] = '\0';
		return ts;
	}

	void adjust_lfe(audio_sample* pcm_data, t_size pcm_size, unsigned channels, unsigned channel_config) {
		if ((channels >= 4) && (channel_config & audio_chunk::channel_lfe) && (lfe_adjust_coef != 1.0f)) {
			for (t_size sample = 0; sample < pcm_size; sample++) {
				pcm_data[sample * channels + 3] *= lfe_adjust_coef;
			}
		}
	}

	void check_overloads(const audio_sample* pcm_data, t_size pcm_size) {
		for (t_size sample = 0; sample < pcm_size; sample++) {
			for (int ch = 0; ch < pcm_out_channels; ch++) {
				audio_sample v = vol_adjust_coef * pcm_data[sample * pcm_out_channels + ch];
				if ((v > +PCM_OVERLOAD_THRESHOLD) || (v < -PCM_OVERLOAD_THRESHOLD)) {
					double overload_time = (double)(pcm_out_offset + sample) / (double)pcm_out_samplerate;
					const char* track_name = log_track_name;
					const char* time_stamp = get_time_stamp(overload_time);
					console::printf("Overload at '%s' ch:%d [%s]", track_name, ch, time_stamp);
					break;
				}
			}
		}
	}

	void decode_initialize(t_uint32 p_subsong, unsigned p_flags, abort_callback& p_abort) {
		input_subsong = p_subsong;
		input_flags = p_flags;
		if (p_flags & input_flag_dsd_edited_master_track) {
			sacd_reader->set_mode(ACCESS_MODE_EDITED_MASTER_TRACK);
		}
		if (!sacd_reader->select_track(p_subsong)) {
			throw exception_io();
		}
		if (CSACDPreferences::get_trace()) {
			if (!sacd_reader->is_gapless(p_subsong)) {
				console::printf("Warning: track is not gapless");
			}
		}
		auto use_vol_adjust = (input_flags & input_flag_playback) || (call_from_foo_converter && !use_dop_for_pcm);
		auto track = metadb::get()->handle_create(input_path, p_subsong);
		metadb_info_container::ptr info;
		if (track->get_info_ref(info)) {
			if (info->info().get_replaygain().is_track_gain_present()) {
				use_vol_adjust = false;
			}
		}
		vol_adjust_coef = use_vol_adjust ? (audio_sample)audio_math::gain_to_scale(CSACDPreferences::get_vol_adjust()) : 1.0f;
		lfe_adjust_coef = (audio_sample)audio_math::gain_to_scale(CSACDPreferences::get_lfe_adjust());
		log_overloads = CSACDPreferences::get_log_overloads();
		dsd_samplerate = sacd_reader->get_samplerate(p_subsong);
		framerate = sacd_reader->get_framerate(p_subsong);
		pcm_out_channels = sacd_reader->get_channels(p_subsong);
		pcm_out_channel_map = get_sacd_channel_map_from_loudspeaker_config(sacd_reader->get_loudspeaker_config(p_subsong));
		if (pcm_out_channel_map == 0) {
			pcm_out_channel_map = get_sacd_channel_map_from_channels(pcm_out_channels);
		}
		pcm_min_samplerate = 44100;
		while ((pcm_min_samplerate / framerate) * framerate != pcm_min_samplerate) {
			pcm_min_samplerate *= 2;
		}
		pcm_out_samplerate = CSACDPreferences::get_samplerate();
		pcm_out_samplerate = max(pcm_min_samplerate, pcm_out_samplerate);
		pcm_out_samplerate = min(((pcm_out_samplerate % 44100) ? (dsd_samplerate / 44100) * 48000 : dsd_samplerate) / 8, pcm_out_samplerate);
		pcm_data.resize(pcm_out_channels * pcm_out_samplerate / framerate);
		memset(sacd_bitrate, 0, sizeof(sacd_bitrate));
		sacd_bitrate_idx = 0;
		sacd_bitrate_sum = 0;
		dst_decoder_initialized = false;
		if (input_flags & input_flag_playback) {
			dsdpcm_decoder.set_payback(true);
			dsdpcm_decoder.set_ramp(CSACDPreferences::get_ramp());
			use_dsd_path = CSACDPreferences::use_dsd_path();
			use_pcm_path = CSACDPreferences::use_pcm_path();
			use_dop_for_pcm = false;
		}
		else {
			use_dsd_path = false;
			use_pcm_path = true;
			use_dop_for_pcm = call_from_foo_converter && CSACDPreferences::get_dop_for_converter();
			use_dop_for_pcm = use_dop_for_pcm || (input_flags & input_flag_dsd_extract);
		}
		if (use_dsd_path) {
			dsd_stream->set_streaming(true);
		}
		if (use_pcm_path) {
			if (use_dop_for_pcm) {
				audio_chunk::spec_t spec{ (uint32_t)dsd_samplerate, (uint32_t)pcm_out_channels, pcm_out_channel_map };
				dop_converter.set_inp_spec(spec);
				spec.sampleRate = (uint32_t)dsd_samplerate / 16;
				dop_converter.set_out_spec(spec);
			}
		}
		if (use_pcm_path && !use_dop_for_pcm) {
			if (get_converter_type() == conv_type_e::USER) {
				dsdpcm_decoder_fir_data = CSACDPreferences::get_user_fir().get_ptr();
				dsdpcm_decoder_fir_size = CSACDPreferences::get_user_fir().get_size();
				dsdpcm_decoder_fir_decimation = CSACDPreferences::get_decimation();
				if (!dsdpcm_decoder_fir_decimation) {
					dsdpcm_decoder_fir_decimation = (dsdpcm_decoder_fir_size < 80) ? 8 : 1 << (int)log2(dsdpcm_decoder_fir_size / 10.0);
				}
			}
			auto rv = dsdpcm_decoder.init(pcm_out_channels, framerate, dsd_samplerate, pcm_out_samplerate, get_converter_type(), get_converter_fp64(), dsdpcm_decoder_fir_data, dsdpcm_decoder_fir_size, dsdpcm_decoder_fir_decimation);
			if (rv < 0) {
				if (rv == -2) {
					popup_message::g_show("No installed FIR, continue with the default", "DSD2PCM", popup_message::icon_error);
				}
				int rv = dsdpcm_decoder.init(pcm_out_channels, framerate, dsd_samplerate, pcm_out_samplerate, conv_type_e::DIRECT, get_converter_fp64());
				if (rv < 0) {
					throw exception_io();
				}
			}
			pcm_out_delay = dsdpcm_decoder.get_delay();
			if (CSACDPreferences::get_trace()) {
				console::printf("FIR delay: %s samples", format_float(pcm_out_delay, 0, 3).toString());
			}
			pcm_out_append_samples = 0;
			pcm_out_remove_samples = 0;
			if (!(input_flags & (input_flag_playback | input_flag_dsd_edited_master_track))) {
				auto pcm_out_delay_in_samples = (pcm_out_delay > 0.0) ? int(pcm_out_delay + 0.5) : 0;
				pcm_out_append_samples = pcm_out_delay_in_samples;
				pcm_out_remove_samples = pcm_out_delay_in_samples;
				auto dsdpcm_fir_length = 2 * pcm_out_delay * dsd_samplerate / 8 / pcm_out_samplerate;
				auto dsd_frame_size = dsd_samplerate / 8 / framerate;
				auto dsd_span_frames = (dsdpcm_fir_length > 0) ? int(dsdpcm_fir_length / dsd_frame_size + 1) : 0;
				sacd_reader->set_track_span(dsd_span_frames);
			}
		}
		read_frame = true;
		span_queue.clear();
	}

	frame_span_e decode_read_frame() {
		auto dsx_span{ frame_span_e::NO_TRACK };
		for (;;) {
			if (read_frame) {
				dsx_data.resize(dsd_samplerate / 8 / framerate * pcm_out_channels);
				auto [read_ok, frame_size, frame_type, frame_span] = sacd_reader->read_frame(dsx_data.data(), dsx_data.size());
				if (read_ok) {
					span_queue.push_front(frame_span);
					dsx_data.resize(frame_size);
					sacd_bitrate_idx = (++sacd_bitrate_idx) % BITRATE_AVGS;
					sacd_bitrate_sum -= sacd_bitrate[sacd_bitrate_idx];
					sacd_bitrate[sacd_bitrate_idx] = int64_t(8) * frame_size * framerate;
					sacd_bitrate_sum += sacd_bitrate[sacd_bitrate_idx];
					if (frame_type == frame_type_e::DST) {
						if (!dst_decoder_initialized) {
							if (dst_decoder.init(sacd_reader->get_channels(), sacd_reader->get_samplerate() / 8 / sacd_reader->get_framerate()) != 0) {
								return dsx_span;
							}
							dst_decoder_initialized = true;
						}
					}
				}
				else {
					read_frame = false;
				}
			}
			if (!read_frame) {
				dsx_data.clear();
			}
			if (dst_decoder_initialized) {
				dst_decoder.run(dsx_data);
			}
			if (!dsx_data.empty()) {
				if (!span_queue.empty()) {
					dsx_span = span_queue.back();
					span_queue.pop_back();
				}
				break;
			}
			if (span_queue.empty()) {
				break;
			}
		}
		return dsx_span;
	}

	bool decode_run_internal(audio_chunk& p_chunk, mem_block_container* p_raw, abort_callback& p_abort) {
		bool has_data{ false };
		for (;;) {
			auto dsx_span{ decode_read_frame() };
			if (dsx_span == frame_span_e::IN_TRACK) {
				if (p_raw) {
					p_raw->set_size(dsx_data.size());
					memcpy(p_raw->get_ptr(), dsx_data.data(), dsx_data.size());
				}
				if (use_dsd_path) {
					dsd_stream->write(dsx_data.data(), dsx_data.size() / pcm_out_channels, pcm_out_channels, dsd_samplerate, pcm_out_channel_map);
					if (!use_pcm_path) {
						p_chunk.set_sample_rate(pcm_min_samplerate);
						p_chunk.set_channels(pcm_out_channels, pcm_out_channel_map);
						p_chunk.set_silence(pcm_min_samplerate * (dsx_data.size() / pcm_out_channels) / (dsd_samplerate / 8));
						has_data = true;
						break;
					}
				}
				if (use_pcm_path) {
					if (use_dop_for_pcm) {
						dop_converter.dsd_to_dop(dsx_data.data(), dsx_data.size() / pcm_out_channels, p_chunk);
						has_data = true;
						break;
					}
				}
			}
			if (dsx_span == frame_span_e::IN_TRACK) {
				auto pcm_out_samples = (int)dsdpcm_decoder.convert(dsx_data.data(), dsx_data.size(), pcm_data.data()) / pcm_out_channels;
				if (pcm_out_samples > pcm_out_remove_samples) {
					adjust_lfe(pcm_data.data() + pcm_out_channels * pcm_out_remove_samples, pcm_out_samples - pcm_out_remove_samples, pcm_out_channels, pcm_out_channel_map);
					if (log_overloads) {
						check_overloads(pcm_data.data() + pcm_out_channels * pcm_out_remove_samples, pcm_out_samples - pcm_out_remove_samples);
					}
					p_chunk.set_data(pcm_data.data() + pcm_out_channels * pcm_out_remove_samples, pcm_out_samples - pcm_out_remove_samples, pcm_out_channels, pcm_out_samplerate, pcm_out_channel_map);
					p_chunk.scale(vol_adjust_coef);
					pcm_out_offset += pcm_out_samples - pcm_out_remove_samples;
					pcm_out_remove_samples = 0;
					has_data = true;
					break;
				}
				else {
					pcm_out_remove_samples -= pcm_out_samples;
				}
			}
			if (dsx_span == frame_span_e::POST_TRACK || dsx_span == frame_span_e::NO_TRACK) {
				if (pcm_out_append_samples > 0) {
					if (dsx_span == frame_span_e::NO_TRACK) {
						dsx_data.resize(dsd_samplerate / 8 / framerate * pcm_out_channels, DSD_SILENCE_BYTE);
					}
					auto pcm_out_samples = (int)dsdpcm_decoder.convert(dsx_data.data(), dsx_data.size(), pcm_data.data()) / pcm_out_channels;
					auto samples_to_append = min(pcm_out_samples, pcm_out_append_samples);
					adjust_lfe(pcm_data.data(), samples_to_append, pcm_out_channels, pcm_out_channel_map);
					if (log_overloads) {
						check_overloads(pcm_data.data(), samples_to_append);
					}
					p_chunk.set_data(pcm_data.data(), samples_to_append, pcm_out_channels, pcm_out_samplerate, pcm_out_channel_map);
					p_chunk.scale(vol_adjust_coef);
					pcm_out_offset += samples_to_append;
					pcm_out_append_samples -= samples_to_append;
					has_data = true;
					break;
				}
			}
			if (dsx_span == frame_span_e::NO_TRACK) {
				break;
			}
		}
		if (!has_data) {
			dsdpcm_decoder.set_ramp(CSACDPreferences::get_transition() > 0.0);
		}
		return has_data;
	}

	bool decode_run(audio_chunk& p_chunk, abort_callback& p_abort) {
		return decode_run_internal(p_chunk, nullptr, p_abort);
	}

	bool decode_run_raw(audio_chunk& p_chunk, mem_block_container& p_raw, abort_callback& p_abort) {
		return decode_run_internal(p_chunk, &p_raw, p_abort);
	}

	void decode_seek(double p_seconds, abort_callback& p_abort) {
		if (!sacd_reader->seek(p_seconds)) {
			throw exception_io();
		}
		dst_decoder.flush();
		span_queue.clear();
	}

	bool decode_can_seek() {
		return sacd_media->can_seek();
	}

	bool decode_get_dynamic_info(file_info& p_info, double& p_timestamp_delta) {
		DWORD curr_time_ms = GetTickCount();
		if (info_update_time_ms + (DWORD)UPDATE_STATS_MS >= curr_time_ms) {
			return false;
		}
		info_update_time_ms = curr_time_ms;
		t_int64 bitrate_vbr{ 0 };
		string8 codec;
		switch (media_type) {
		case media_type_e::ISO:
		case media_type_e::DSDIFF:
		case media_type_e::DSF:
			bitrate_vbr = sacd_bitrate_sum / BITRATE_AVGS;
			break;
		case media_type_e::WAVPACK:
			bitrate_vbr = static_cast<t_int64>(sacd_reader->get_instant_bitrate() + 0.5);
			break;
		}
		p_info.info_set_bitrate_vbr((bitrate_vbr + 500) / 1000);
		return true;
	}
	
	bool decode_get_dynamic_info_track(file_info& p_info, double& p_timestamp_delta) {
		return false;
	}
	
	void decode_on_idle(abort_callback & p_abort) {
		sacd_media->on_idle();
	}

	void retag_set_info(t_uint32 p_subsong, const file_info& p_info, abort_callback& p_abort) {
		file_info_impl info = p_info;
		if (CSACDPreferences::get_editable_tags()) {
			sacd_reader->set_info(p_subsong, info);
			if (sacd_metabase) {
				sacd_metabase->set_track_info(p_subsong, info, false);
				if (CSACDPreferences::get_linked_tags()) {
					t_uint32 twoch_count = sacd_reader->get_track_count(ACCESS_MODE_TWOCH);
					t_uint32 mulch_count = sacd_reader->get_track_count(ACCESS_MODE_MULCH);
					if (twoch_count == mulch_count) {
						sacd_metabase->set_track_info((p_subsong <= twoch_count) ? p_subsong + twoch_count : p_subsong - twoch_count, info, true);
					}
				}
			}
		}
	}
	
	void retag_commit(abort_callback& p_abort) {
		if (CSACDPreferences::get_editable_tags()) {
			sacd_reader->commit();
			if (sacd_metabase) {
				sacd_metabase->commit();
			}
		}
	}

	void remove_tags(abort_callback& abort) {
		file_info_impl blank;
		for (auto index = 0u; index < get_subsong_count(); index++) {
			retag_set_info(get_subsong(index), blank, abort);
		}
		retag_commit(abort);
	}

	void set_logger(event_logger::ptr p_ptr) {
	}

	size_t extended_param(const GUID& p_type, size_t p_arg1, void* p_arg2, size_t p_arg2size) {
		return 0;
	}

	static const char* g_get_name() {
		return SACD_NAME;
	}

	static const GUID g_get_guid() {
		return SACD_GUID;
	}

	static const GUID g_get_preferences_guid() {
		return CSACDPreferences::class_guid;
	}
};

static input_factory_t<input_sacd_t> g_input_sacd_factory;

class initquit_sacd_t : public initquit {
public:
	virtual void on_init() {
#ifdef _DEBUG
		_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF | _CRTDBG_CHECK_CRT_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
	}
	
	virtual void on_quit() {
	}
};

static initquit_factory_t<initquit_sacd_t> g_initquit_sacd_factory;

DECLARE_COMPONENT_VERSION(SACD_NAME, SACD_VERSION, SACD_COPYRIGHT);
DECLARE_FILE_TYPE("SACD files", "*.DAT;*.DFF;*.DSF;*.ISO;MASTER1.TOC");
VALIDATE_COMPONENT_FILENAME(SACD_FILE);
