//
//  SFPlayer.cpp
//  MIDI
//
//  Created by Christopher Snowhill on 5/3/21.
//  Copyright © 2021-2022 Christopher Snowhill. All rights reserved.
//

#include "SFPlayer.h"

#include <string.h>

#define _countof(x) (sizeof((x)) / sizeof(((x)[0])))

SFPlayer::SFPlayer()
: MIDIPlayer() {
	_synth[0] = 0;
	_synth[1] = 0;
	_synth[2] = 0;
	uInterpolationMethod = FLUID_INTERP_DEFAULT;
	bDynamicLoading = true;

	for(unsigned int i = 0; i < 3; ++i) {
		_settings[i] = new_fluid_settings();

		fluid_settings_setnum(_settings[i], "synth.gain", 0.2);
		fluid_settings_setnum(_settings[i], "synth.sample-rate", 44100);
		fluid_settings_setint(_settings[i], "synth.midi-channels", 16);
		fluid_settings_setint(_settings[i], "synth.dynamic-sample-loading", bDynamicLoading ? 1 : 0);
		fluid_settings_setint(_settings[i], "synth.device-id", 0x10 + i);
	}
}

SFPlayer::~SFPlayer() {
	for(unsigned int i = 0; i < 3; ++i) {
		if(_synth[i]) delete_fluid_synth(_synth[i]);
		if(_settings[i]) delete_fluid_settings(_settings[i]);
	}
}

void SFPlayer::setInterpolationMethod(unsigned method) {
	uInterpolationMethod = method;
	for(unsigned int i = 0; i < 3; ++i)
		if(_synth[i]) fluid_synth_set_interp_method(_synth[i], -1, method);
}

void SFPlayer::setDynamicLoading(bool enabled) {
	shutdown();
	bDynamicLoading = enabled;
}

void SFPlayer::send_event(uint32_t b) {
	int param2 = (b >> 16) & 0xFF;
	int param1 = (b >> 8) & 0xFF;
	int cmd = b & 0xF0;
	int chan = b & 0x0F;
	int port = (b >> 24) & 0x7F;
	fluid_synth_t *_synth = this->_synth[0];

	if(port && port < 3)
		_synth = this->_synth[port];

	switch(cmd) {
		case 0x80:
			fluid_synth_noteoff(_synth, chan, param1);
			break;
		case 0x90:
			fluid_synth_noteon(_synth, chan, param1, param2);
			break;
		case 0xA0:
			break;
		case 0xB0:
			fluid_synth_cc(_synth, chan, param1, param2);
			break;
		case 0xC0:
			fluid_synth_program_change(_synth, chan, param1);
			break;
		case 0xD0:
			fluid_synth_channel_pressure(_synth, chan, param1);
			break;
		case 0xE0:
			fluid_synth_pitch_bend(_synth, chan, (param2 << 7) | param1);
			break;
	}
}

void SFPlayer::send_sysex(const uint8_t *data, size_t size, size_t port) {
	if(port >= 3)
		port = 0;
	if(data && size > 2 && data[0] == 0xF0 && data[size - 1] == 0xF7) {
		++data;
		size -= 2;
		fluid_synth_sysex(_synth[0], (const char *)data, size, NULL, NULL, NULL, 0);
		fluid_synth_sysex(_synth[1], (const char *)data, size, NULL, NULL, NULL, 0);
		fluid_synth_sysex(_synth[2], (const char *)data, size, NULL, NULL, NULL, 0);
	}
}

void SFPlayer::render(float *out, unsigned long count) {
	unsigned long done = 0;
	memset(out, 0, sizeof(float) * 2 * count);
	while(done < count) {
		float buffer[512 * 2];
		unsigned long todo = count - done;
		unsigned long i;
		if(todo > 512) todo = 512;
		for(unsigned long j = 0; j < 3; ++j) {
			memset(buffer, 0, sizeof(buffer));
			fluid_synth_write_float(_synth[j], todo, buffer, 0, 2, buffer, 1, 2);
			for(i = 0; i < todo; ++i) {
				out[i * 2 + 0] += buffer[i * 2 + 0];
				out[i * 2 + 1] += buffer[i * 2 + 1];
			}
		}
		out += todo * 2;
		done += todo;
	}
}

void SFPlayer::setSoundFont(const char *in) {
	sSoundFontName = in;
	shutdown();
}

void SFPlayer::setFileSoundFont(const char *in) {
	sFileSoundFontName = in;
	shutdown();
}

void SFPlayer::shutdown() {
	for(unsigned int i = 0; i < 3; ++i) {
		if(_synth[i]) delete_fluid_synth(_synth[i]);
		_synth[i] = 0;
	}
	initialized = false;
}

bool SFPlayer::startup() {
	if(_synth[0] && _synth[1] && _synth[2]) return true;

	for(unsigned int i = 0; i < 3; ++i) {
		fluid_settings_setnum(_settings[i], "synth.sample-rate", uSampleRate);
		fluid_settings_setint(_settings[i], "synth.dynamic-sample-loading", bDynamicLoading ? 1 : 0);
		_synth[i] = new_fluid_synth(_settings[i]);
		if(!_synth[i]) {
			_last_error = "Out of memory";
			return false;
		}
		fluid_synth_set_interp_method(_synth[i], -1, uInterpolationMethod);
	}
	if(sSoundFontName.length()) {
		std::string ext;
		size_t dot = sSoundFontName.find_last_of('.');
		if(dot != std::string::npos)
			ext.assign(sSoundFontName.begin() + dot + 1, sSoundFontName.end());
		if(!strcasecmp(ext.c_str(), "sf2") || !strcasecmp(ext.c_str(), "sf3")) {
			for(unsigned i = 0; i < 3; ++i) {
				if(FLUID_FAILED == fluid_synth_sfload(_synth[i], sSoundFontName.c_str(), 1)) {
					shutdown();
					_last_error = "Failed to load SoundFont bank: ";
					_last_error += sSoundFontName;
					return false;
				}
			}
		} else if(!strcasecmp(ext.c_str(), "sflist")) {
			FILE *fl = fopen(sSoundFontName.c_str(), "r");
			if(fl) {
				std::string path, temp;
				char name[32768];
				size_t slash = sSoundFontName.find_last_of('\\');
				if(slash != std::string::npos)
					path.assign(sSoundFontName.begin(), sSoundFontName.begin() + slash + 1);
				while(!feof(fl)) {
					if(!fgets(name, 32767, fl)) break;
					name[32767] = 0;
					char *cr = strchr(name, '\n');
					if(cr) *cr = 0;
					cr = strchr(name, '\r');
					if(cr) *cr = 0;
					if(name[0] == '/') {
						temp = name;
					} else {
						temp = path;
						temp += name;
					}
					for(unsigned i = 0; i < 3; ++i) {
						if(FLUID_FAILED == fluid_synth_sfload(_synth[i], temp.c_str(), 1)) {
							fclose(fl);
							shutdown();
							_last_error = "Failed to load SoundFont bank: ";
							_last_error += temp;
							return false;
						}
					}
				}
				fclose(fl);
			} else {
				_last_error = "Failed to open SoundFont list: ";
				_last_error += sSoundFontName;
				return false;
			}
		}
	}

	if(sFileSoundFontName.length()) {
		for(unsigned i = 0; i < 3; ++i) {
			if(FLUID_FAILED == fluid_synth_sfload(_synth[i], sFileSoundFontName.c_str(), 1)) {
				shutdown();
				_last_error = "Failed to load SoundFont bank: ";
				_last_error += sFileSoundFontName;
				return false;
			}
		}
	}

	_last_error = "";

	initialized = true;

	setFilterMode(mode, reverb_chorus_disabled);

	return true;
}

const char *SFPlayer::GetLastError() const {
	if(_last_error.length()) return _last_error.c_str();
	return NULL;
}
