"""
	SoundFont Reader by Yui Kinomoto @arlez80
"""

"""
	SampleLink
"""
const sample_link_mono_sample = 1
const sample_link_right_sample = 2
const sample_link_left_sample = 4
const sample_link_linked_sample = 8
const sample_link_rom_mono_sample = 0x8001
const sample_link_rom_right_sample = 0x8002
const sample_link_rom_left_sample = 0x8004
const sample_link_rom_linked_sample = 0x8008

"""
	GenerateOperator
"""
const gen_oper_start_addrs_offset = 0
const gen_oper_end_addrs_offset = 1
const gen_oper_startloop_addrs_offset = 2
const gen_oper_endloop_addrs_offset = 3
const gen_oper_start_addrs_coarse_offset = 4
const gen_oper_mod_lfo_to_pitch = 5
const gen_oper_vib_lfo_to_pitch = 6
const gen_oper_mod_env_to_pitch = 7
const gen_oper_initial_filter_fc = 8
const gen_oper_initial_filter_q = 9
const gen_oper_mod_lfo_to_filter_fc = 10
const gen_oper_mod_env_to_filter_fc = 11
const gen_oper_end_addrs_coarse_offset = 12
const gen_oper_mod_lfo_to_volume = 13
const gen_oper_unused1 = 14
const gen_oper_chorus_effects_send = 15
const gen_oper_reverb_effects_send = 16
const gen_oper_pan = 17
const gen_oper_unused2 = 18
const gen_oper_unused3 = 19
const gen_oper_unused4 = 20
const gen_oper_delay_mod_lfo = 21
const gen_oper_freq_mod_lfo = 22
const gen_oper_delay_vib_lfo = 23
const gen_oper_freq_vib_lfo = 24
const gen_oper_delay_mod_env = 25
const gen_oper_attack_mod_env = 26
const gen_oper_hold_mod_env = 27
const gen_oper_decay_mod_env = 28
const gen_oper_sustain_mod_env = 29
const gen_oper_release_mod_env = 30
const gen_oper_keynum_to_mod_env_hold = 31
const gen_oper_keynum_to_mod_env_decay = 32
const gen_oper_delay_vol_env = 33
const gen_oper_attack_vol_env = 34
const gen_oper_hold_vol_env = 35
const gen_oper_decay_vol_env = 36
const gen_oper_sustain_vol_env = 37
const gen_oper_release_vol_env = 38
const gen_oper_keynum_to_vol_env_hold = 39
const gen_oper_keynum_to_vol_env_decay = 40
const gen_oper_instrument = 41
const gen_oper_reserved1 = 42
const gen_oper_key_range = 43
const gen_oper_vel_range = 44
const gen_oper_startloop_addrs_coarse_offset = 45
const gen_oper_keynum = 46
const gen_oper_velocity = 47
const gen_oper_initial_attenuation = 48
const gen_oper_reserved2 = 49
const gen_oper_endloop_addrs_coarse_offset = 50
const gen_oper_coarse_tune = 51
const gen_oper_fine_tune = 52
const gen_oper_sample_id = 53
const gen_oper_sample_modes = 54
const gen_oper_reserved3 = 55
const gen_oper_scale_tuning = 56
const gen_oper_exclusive_class = 57
const gen_oper_overriding_root_key = 58
const gen_oper_unused5 = 59
const gen_oper_end_oper = 60

"""
	SampleMode
"""
const sample_mode_no_loop = 0
const sample_mode_loop_continuously = 1
const sample_mode_unused_no_loop = 2
const sample_mode_loop_ends_by_key_depression = 3

"""
	ファイルから読み込み
	@param	path	File path
	@return	smf
"""
func read_file( path ):
	var f = File.new( )

	if not f.file_exists( path ):
		print( "file %s is not found" % path )
		breakpoint

	f.open( path, f.READ )
	var stream = StreamPeerBuffer.new( )
	stream.set_data_array( f.get_buffer( f.get_len( ) ) )
	stream.big_endian = false
	f.close( )

	return self._read( stream )

"""
	配列から読み込み
	@param	data	PoolByteArray
	@return	smf
"""
func read_data( data ):
	var stream = StreamPeerBuffer.new( )
	stream.set_data_array( data )
	stream.big_endian = false
	return self._read( stream )

"""
	読み込み
	@param	input
	@return	SoundFont
"""
func _read( input ):
	self._check_chunk( input, "RIFF" )
	self._check_header( input, "sfbk" )

	var info = self._read_info( input )
	var sdta = self._read_sdta( input )
	var pdta = self._read_pdta( input )

	return {
		"info": info,
		"sdta": sdta,
		"pdta": pdta,
	}

"""
	チャンクチェック
	@param	input
	@param	hdr
"""
func _check_chunk( input, hdr ):
	self._check_header( input, hdr )
	input.get_32( )

"""
	ヘッダーチェック
	@param	input
	@param	hdr
"""
func _check_header( input, hdr ):
	var chk = input.get_string( 4 )
	if hdr != chk:
		print( "Doesn't exist " + hdr + " header" )
		breakpoint

"""
	チャンク読み込み
	@param	input
	@param	needs_header
	@param	chunk
"""
func _read_chunk( stream, needs_header = null ):
	var header = stream.get_string( 4 )
	if needs_header != null:
		if needs_header != header:
			print( "Doesn't exist " + needs_header + " header" )
			breakpoint
	var size = stream.get_u32( )
	var new_stream = StreamPeerBuffer.new( )
	new_stream.set_data_array( stream.get_partial_data( size )[1] )
	new_stream.big_endian = false

	return {
		"header": header,
		"size": size,
		"stream": new_stream,
	}

"""
	INFOチャンクを読み込む
	@param	stream
	@param	chunk
"""
func _read_info( stream ):
	var chunk = self._read_chunk( stream, "LIST" )
	self._check_header( chunk.stream, "INFO" )

	var info = {
		"ifil":null,
		"isng":null,
		"inam":null,

		"irom":null,
		"iver":null,
		"icrd":null,
		"ieng":null,
		"iprd":null,
		"icop":null,
		"icmt":null,
		"isft":null,
	}

	while 0 < chunk.stream.get_available_bytes( ):
		var sub_chunk = self._read_chunk( chunk.stream )
		match sub_chunk.header.to_lower( ):
			"ifil":
				info.ifil = self._read_version_tag( sub_chunk.stream )
			"isng":
				info.isng = sub_chunk.stream.get_string( sub_chunk.size )
			"inam":
				info.inam = sub_chunk.stream.get_string( sub_chunk.size )
			"irom":
				info.irom = sub_chunk.stream.get_string( sub_chunk.size )
			"iver":
				info.iver = self._read_version_tag( sub_chunk.stream )
			"icrd":
				info.icrd = sub_chunk.stream.get_string( sub_chunk.size )
			"ieng":
				info.ieng = sub_chunk.stream.get_string( sub_chunk.size )
			"iprd":
				info.iprd = sub_chunk.stream.get_string( sub_chunk.size )
			"icop":
				info.icop = sub_chunk.stream.get_string( sub_chunk.size )
			"icmt":
				info.icmt = sub_chunk.stream.get_string( sub_chunk.size )
			"isft":
				info.isft = sub_chunk.stream.get_string( sub_chunk.size )
			_:
				print( "unknown header" )
				breakpoint

	return info

"""
	バージョンタグを読み込む
	@param	stream
	@param	chunk
"""
func _read_version_tag( stream ):
	var major = stream.get_u16( )
	var minor = stream.get_u16( )

	return {
		"major": major,
		"minor": minor,
	}

"""
	SDTAを読み込む
	@param	stream
	@param	chunk
"""
func _read_sdta( stream ):
	var chunk = self._read_chunk( stream, "LIST" )
	self._check_header( chunk.stream, "sdta" )

	var smpl = self._read_chunk( chunk.stream, "smpl" )
	var smpl_bytes = smpl.stream.get_partial_data( smpl.size )[1]

	var sm24_bytes = null
	if 0 < chunk.stream.get_available_bytes( ):
		var sm24_chunk = self._read_chunk( chunk.stream, "sm24" )
		sm24_bytes = sm24_chunk.stream.get_partial_data( sm24_chunk.size )[1]

	return {
		"smpl": smpl_bytes,
		"sm24": sm24_bytes,
	}

"""
	PDTAを読み込む
	@param	stream
	@param	chunk
"""
func _read_pdta( stream ):
	var chunk = self._read_chunk( stream, "LIST" )
	self._check_header( chunk.stream, "pdta" )

	var phdr = self._read_pdta_phdr( chunk.stream )
	var pbag = self._read_pdta_bag( chunk.stream )
	var pmod = self._read_pdta_mod( chunk.stream )
	var pgen = self._read_pdta_gen( chunk.stream )
	var inst = self._read_pdta_inst( chunk.stream )
	var ibag = self._read_pdta_bag( chunk.stream )
	var imod = self._read_pdta_mod( chunk.stream )
	var igen = self._read_pdta_gen( chunk.stream )
	var shdr = self._read_pdta_shdr( chunk.stream )

	return {
		"phdr": phdr,
		"pbag": pbag,
		"pmod": pmod,
		"pgen": pgen,
		"inst": inst,
		"ibag": ibag,
		"imod": imod,
		"igen": igen,
		"shdr": shdr,
	}

"""
	phdr 読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_phdr( stream ):
	var chunk = self._read_chunk( stream, "phdr" )
	var phdrs = []

	while 0 < chunk.stream.get_available_bytes( ):
		var phdr = {
			"name": "",
			"preset": 0,
			"bank": 0,
			"preset_bag_index": 0,
			"library": 0,
			"genre": 0,
			"morphology": 0,
		}

		phdr.name = chunk.stream.get_string( 20 )
		phdr.preset = chunk.stream.get_u16( )
		phdr.bank = chunk.stream.get_u16( )
		phdr.preset_bag_index = chunk.stream.get_u16( )
		phdr.library = chunk.stream.get_32( )
		phdr.genre = chunk.stream.get_32( )
		phdr.morphology = chunk.stream.get_32( )

		phdrs.append( phdr )

	return phdrs

"""
	*bag読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_bag( stream ):
	var chunk = self._read_chunk( stream )
	var bags = []

	if chunk.header.substr( 1, 3 ) != "bag":
		print( "Doesn't exist *bag header." )
		breakpoint

	while 0 < chunk.stream.get_available_bytes( ):
		var bag = {
			"gen_ndx": 0,
			"mod_ndx": 0,
		}
	
		bag.gen_ndx = chunk.stream.get_u16( )
		bag.mod_ndx = chunk.stream.get_u16( )
		bags.append( bag )

	return bags

"""
	*mod読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_mod( stream ):
	var chunk = self._read_chunk( stream )
	var mods = []

	if chunk.header.substr( 1, 3 ) != "mod":
		print( "Doesn't exist *mod header." )
		breakpoint

	while 0 < chunk.stream.get_available_bytes( ):
		var mod = {
			"src_oper": null,
			"dest_oper": 0,
			"amount": 0,
			"amt_src_oper": null,
			"trans_oper": 0,
		}
	
		mod.src_oper = self._read_pdta_modulator( chunk.stream.get_u16( ) )
		mod.dest_oper = chunk.stream.get_u16( )
		mod.amount = chunk.stream.get_u16( )
		mod.amt_src_oper = self._read_pdta_modulator( chunk.stream.get_u16( ) )
		mod.trans_oper = chunk.stream.get_u16( )
		mods.append( mod )

	return mods

"""
	PDTA-Modulator 読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_modulator( u ):
	return {
		"type": ( u >> 10 ) & 0x3f,
		"direction": ( u >> 8 ) & 0x01,
		"polarity": ( u >> 9 ) & 0x01,
		"controller": u & 0x7f,
		"controllerPallete": ( u >> 7 ) & 0x01,
	}

"""
	gen 読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_gen( stream ):
	var chunk = self._read_chunk( stream )
	var gens = []

	if chunk.header.substr( 1, 3 ) != "gen":
		print( "Doesn't exist *gen header." )
		breakpoint

	while 0 < chunk.stream.get_available_bytes( ):
		var gen = {
			"gen_oper": 0,
			"amount": 0,
			"uamount": 0,
		}
	
		gen.gen_oper = chunk.stream.get_u16( )
		var u = chunk.stream.get_u16( )
		gen.uamount = u
		gen.amount = u
		if 32767 < u:
			gen.amount = - ( 65536 - u )
		gens.append( gen )

	return gens

"""
	inst読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_inst( stream ):
	var chunk = self._read_chunk( stream, "inst" )
	var insts = []

	while 0 < chunk.stream.get_available_bytes( ):
		var inst = {
			"name": "",
			"inst_bag_ndx": 0,
		}
	
		inst.name = chunk.stream.get_string( 20 )
		inst.inst_bag_ndx = chunk.stream.get_u16( )
		insts.append( inst )

	return insts

"""
	shdr 読み込み
	@param	stream
	@param	chunk
"""
func _read_pdta_shdr( stream ):
	var chunk = self._read_chunk( stream, "shdr" )
	var shdrs = []

	while 0 < chunk.stream.get_available_bytes( ):
		var shdr = {
			"name": "",
			"start": 0,
			"end": 0,
			"start_loop": 0,
			"end_loop": 0,
			"sample_rate": 0,
			"original_key": 0,
			"pitch_correction": 0,
			"sample_link": 0,
			"sample_type": 0,
		}
	
		shdr.name = chunk.stream.get_string( 20 )
		shdr.start = chunk.stream.get_u32( )
		shdr.end = chunk.stream.get_u32( )
		shdr.start_loop = chunk.stream.get_u32( )
		shdr.end_loop = chunk.stream.get_u32( )
		shdr.sample_rate = chunk.stream.get_u32( )
		shdr.original_key = chunk.stream.get_u8( )
		shdr.pitch_correction = chunk.stream.get_8( )
		shdr.sample_link = chunk.stream.get_u16( )
		shdr.sample_type = chunk.stream.get_u16( )
		shdrs.append( shdr )

	return shdrs