"""
	SMF reader/writer by Yui Kinomoto @arlez80
"""

# -----------------------------------------------------------------------------
# 定数

# Control Numbers
const control_number_bank_select_msb = 0x00
const control_number_modulation = 0x01
const control_number_breath_controller = 0x02
const control_number_foot_controller = 0x04
const control_number_portamento_time = 0x05
const control_number_data_entry = 0x06
const control_number_volume = 0x07
const control_number_balance = 0x08
const control_number_pan = 0x0A
const control_number_expression = 0x0B

const control_number_bank_select_lsb = 0x20
const control_number_modulation_lsb = 0x21
const control_number_breath_controller_lsb = 0x22
const control_number_foot_controller_lsb = 0x24
const control_number_portamento_time_lsb = 0x25
const control_number_data_entry_lsb = 0x26
const control_number_channel_volume_lsb = 0x27
const control_number_calance_lsb = 0x28
const control_number_pan_lsb = 0x2A
const control_number_expression_lsb = 0x2B

const control_number_hold = 0x40
const control_number_portament = 0x41
const control_number_sostenuto = 0x42
const control_number_soft_pedal = 0x43
const control_number_legato_foot_switch = 0x44
const control_number_freeze = 0x45
const control_number_sound_variation = 0x46
const control_number_timbre = 0x47
const control_number_release_time = 0x48
const control_number_attack_time = 0x49
const control_number_brightness = 0x4A
const control_number_vibrato_rate = 0x4B
const control_number_vibrato_depth = 0x4C
const control_number_vibrato_delay = 0x4D

const control_number_nrpn_lsb = 0x62
const control_number_nrpn_msb = 0x63
const control_number_rpn_lsb = 0x64
const control_number_rpn_msb = 0x65
const control_number_tkool_loop_point = 0x6F	# CC111

# Manufacture ID
const manufacture_id_universal_nopn_realtime_sys_ex = 0x7E
const manufacture_id_universal_realtime_sys_ex = 0x7F
const manufacture_id_kawai_musical_instruments_mfg_co_ltd = 0x40
const manufacture_id_roland_corporation = 0x41
const manufacture_id_korg_inc = 0x42
const manufacture_id_yamaha_corporation = 0x43
const manufacture_id_casio_computer_co_ltd = 0x44
const manufacture_id_kamiya_studio_co_ltd = 0x46
const manufacture_id_akai_electric_co_ltd = 0x47

# Enums
enum MIDIEventType {
	note_off,					# 8*
	note_on,					# 9*
	polyphonic_key_pressure,	# A*
	control_change,				# B*
	program_change,				# C*
	channel_pressure,			# D*
	pitch_bend,					# E*
	system_event,				# F*
}

enum MIDISystemEventType {
	sys_ex,					
	divided_sys_ex,			
	text_event,				# 01
	copyright,				# 02
	track_name,				# 03
	instrument_name,		# 04
	lyric,					# 05
	marker,					# 06
	cue_point,				# 07
	midi_channel_prefix,	# 20
	end_of_track,			# 2F

	set_tempo,				# 51

	smpte_offset,			# 54
	beat,					# 58
	key,					# 59

	unknown,
}

# -----------------------------------------------------------------------------
# 読み込み : Reader

var last_event_type

"""
	ファイルから読み込み
	@param	path	File path
	@return	smf or null(read error)
"""
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 = true
	f.close( )

	return self._read( stream )

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

"""
	読み込み
	@param	input
	@return	smf
"""
func _read( input ):
	var header = self._read_chunk_data( input )
	if header.id != "MThd" and header.size != 6:
		print( "expected MThd header" )
		return null

	var format_type = header.stream.get_u16( )
	var track_count = header.stream.get_u16( )
	var timebase = header.stream.get_u16( )

	var tracks = []
	for i in range( 0, track_count ):
		var track = self._read_track( input, i )
		if track == null:
			return null
		tracks.append( track )

	return {
		"format_type": format_type,
		"track_count": track_count,
		"timebase": timebase,
		"tracks": tracks,
	}

"""
	トラックの読み込み
	@param	input
	@param	track_number	トラックナンバー
	@return	track data or null(read error)
"""
func _read_track( input, track_number ):
	var track_chunk = self._read_chunk_data( input )
	if track_chunk.id != "MTrk":
		print( "Unknown chunk: " + track_chunk.id )
		return null

	var stream = track_chunk.stream
	var time = 0
	var events = []

	while 0 < stream.get_available_bytes( ):
		var delta_time = self._read_variable_int( stream )
		time += delta_time
		var event_type_byte = stream.get_u8( )

		var event
		if self._is_system_event( event_type_byte ):
			var args = self._read_system_event( stream, event_type_byte )
			if args == null: return null
			event = {
				"type": MIDIEventType.system_event,
				"args": args
			}
		else:
			event = self._read_event( stream, event_type_byte )
			if event == null: return null

			if ( event_type_byte & 0x80 ) == 0:
				event_type_byte = self.last_event_type

		events.append({
			"time": time,
			"channel_number": event_type_byte & 0x0f,
			"event": event,
		})

	return {
		"track_number": track_number,
		"events": events,
	}

"""
	システムイベントか否かを返す
	@param	b	event type
	@return	システムイベントならtrueを返す
"""
func _is_system_event( b ):
	return ( b & 0xf0 ) == 0xf0

"""
	システムイベントの読み込み
"""
func _read_system_event( stream, event_type_byte ):
	if event_type_byte == 0xff:
		var meta_type = stream.get_u8( )
		var size = self._read_variable_int( stream )

		match meta_type:
			0x01:
				return { "type": MIDISystemEventType.text_event, "text": self._read_string( stream, size ) }
			0x02:
				return { "type": MIDISystemEventType.copyright, "text": self._read_string( stream, size ) }
			0x03:
				return { "type": MIDISystemEventType.track_name, "text": self._read_string( stream, size ) }
			0x04:
				return { "type": MIDISystemEventType.instrument_name, "text": self._read_string( stream, size ) }
			0x05:
				return { "type": MIDISystemEventType.lyric, "text": self._read_string( stream, size ) }
			0x06:
				return { "type": MIDISystemEventType.marker, "text": self._read_string( stream, size ) }
			0x07:
				return { "type": MIDISystemEventType.cue_point, "text": self._read_string( stream, size ) }
			0x20:
				if size != 1:
					print( "MIDI Channel Prefix length is not 1" )
					return null
				return { "type": MIDISystemEventType.midi_channel_prefix, "prefix": stream.get_u8( ) }
			0x2F:
				if size != 0:
					print( "End of track with unknown data" )
					return null
				return { "type": MIDISystemEventType.end_of_track }
			0x51:
				if size != 3:
					print( "Tempo length is not 3" )
					return null
				# beat per microseconds
				var bpm = stream.get_u8( ) << 16
				bpm |= stream.get_u8( ) << 8
				bpm |= stream.get_u8( )
				return { "type": MIDISystemEventType.set_tempo, "bpm": bpm }
			0x54:
				if size != 5:
					print( "SMPTE length is not 5" )
					return null
				var hr = stream.get_u8( )
				var mm = stream.get_u8( )
				var se = stream.get_u8( )
				var fr = stream.get_u8( )
				var ff = stream.get_u8( )
				return {
					"type": MIDISystemEventType.smpte_offset,
					"hr": hr,
					"mm": mm,
					"se": se,
					"fr": fr,
					"ff": ff,
				}
			0x58:
				if size != 4:
					print( "Beat length is not 4" )
					return null
				var numerator = stream.get_u8( )
				var denominator = stream.get_u8( )
				var clock = stream.get_u8( )
				var beat32 = stream.get_u8( )
				return {
					"type": MIDISystemEventType.beat,
					"numerator": numerator,
					"denominator": denominator,
					"clock": clock,
					"beat32": beat32,
				}
			0x59:
				if size != 2:
					print( "Key length is not 2" )
					return null
				var sf = stream.get_u8( )
				var minor = stream.get_u8( ) == 1
				return {
					"type": MIDISystemEventType.key,
					"sf": sf,
					"minor": minor,
				}
			_:
				return {
					"type": MIDISystemEventType.unknown,
					"meta_type": meta_type,
					"data": stream.get_partial_data( size )[1],
				}
	elif event_type_byte == 0xf0:
		var size = self._read_variable_int( stream )
		return {
			"type": MIDISystemEventType.sys_ex,
			"data": stream.get_partial_data( size )[1],
		}
	elif event_type_byte == 0xf7:
		var size = self._read_variable_int( stream )
		return {
			"type": MIDISystemEventType.divided_sys_ex,
			"data": stream.get_partial_data( size )[1],
		}

	print( "Unknown system event type: %x" % event_type_byte )
	return null

"""
	通常のイベント読み込み
	@param	stream
	@param	event_type_byte
	@return	MIDIEvent
"""
func _read_event( stream, event_type_byte ):
	var param = 0

	if ( event_type_byte & 0x80 ) == 0:
		# running status
		param = event_type_byte
		event_type_byte = self.last_event_type
	else:
		param = stream.get_u8( )
		self.last_event_type = event_type_byte

	var event_type = event_type_byte & 0xf0

	match event_type:
		0x80:
			return {
				"type": MIDIEventType.note_off,
				"note": param,
				"velocity": stream.get_u8( ),
			}
		0x90:
			var velocity = stream.get_u8( )
			if velocity == 0:
				# velocity0のnote_onはnote_off扱いにする
				return {
					"type": MIDIEventType.note_off,
					"note": param,
					"velocity": velocity,
				}
			else:
				return {
					"type": MIDIEventType.note_on,
					"note": param,
					"velocity": velocity,
				}
		0xA0:
			return {
				"type": MIDIEventType.polyphonic_key_pressure,
				"note": param,
				"value": stream.get_u8( ),
			}
		0xB0:
			return {
				"type": MIDIEventType.control_change,
				"number": param,
				"value": stream.get_u8( ),
			}
		0xC0:
			return {
				"type": MIDIEventType.program_change,
				"number": param,
			}
		0xD0:
			return {
				"type": MIDIEventType.channel_pressure,
				"value": param,
			}
		0xE0:
			return {
				"type": MIDIEventType.pitch_bend,
				"value": param | ( stream.get_u8( ) << 7 ),
			}

	print( "unknown event type: %d" % event_type_byte )
	return null

"""
	可変長数値の読み込み
	@param	stream
	@return	数値
"""
func _read_variable_int( stream ):
	var result = 0

	while true:
		var c = stream.get_u8( )
		if ( c & 0x80 ) != 0:
			result |= c & 0x7f
			result <<= 7
		else:
			result |= c
			break

	return result

"""
	チャンクデータの読み込み
	@param	stream	Stream
	@return	chunk data
"""
func _read_chunk_data( stream ):
	var id = self._read_string( stream, 4 )
	var size = stream.get_32( )
	var new_stream = StreamPeerBuffer.new( )
	new_stream.set_data_array( stream.get_partial_data( size )[1] )
	new_stream.big_endian = true

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

"""
	文字列の読み込み
	@param	stream	Stream
	@param	size	string size
	@return string
"""
func _read_string( stream, size ):
	return stream.get_partial_data( size )[1].get_string_from_ascii( )

# -----------------------------------------------------------------------------
# 書き込み: Writer

"""
	書き込む
	@param	smf	SMF structure
	@return	PoolByteArray
"""
func write( smf ):
	var stream = StreamPeerBuffer.new( )
	stream.big_endian = true
	
	stream.put_utf8_string( "MThd".to_ascii( ) )
	stream.put_u32( 6 )
	stream.put_u16( smf.format_type )
	stream.put_u16( len( smf.tracks ) )
	stream.put_u16( smf.timebase )

	for t in smf.tracks:
		self._write_track( stream, t )

	return stream.get_partial_data( stream.get_available_bytes( ) )[1]

"""
	トラックデータソート用
"""
class TrackEventSorter:
	static func sort( a, b ):
		if a.time < b.time:
			return true
		return false

"""
	可変長数字を書き込む
	@param	stream
	@param	i
"""
func _write_variable_int( stream, i ):
	while true:
		var v = i & 0x7f
		i >>= 7
		if i != 0:
			stream.put_u8( v | 0x80 )
		else:
			stream.put_u8( v )
			break

"""
	トラックデータを書き込む
	@param	stream
	@param	track
"""
func _write_track( stream, track ):
	var events = track.events.duplicate( )
	events.sort_custom( TrackEventSorter, "sort" )

	var buf = StreamPeerBuffer.new( )
	buf.big_endian = true
	var time = 0

	for e in events:
		self._write_variable_int( buf, e.time - time )
		time = e.time
		match e.type:
			MIDIEventType.note_off:
				buf.put_u8( 0x80 | e.channel_number )
				buf.put_u8( e.note )
				buf.put_u8( e.velocity )
			MIDIEventType.note_on:
				buf.put_u8( 0x90 | e.channel_number )
				buf.put_u8( e.note )
				buf.put_u8( e.velocity )
			MIDIEventType.polyphonic_key_pressure:
				buf.put_u8( 0xA0 | e.channel_number )
				buf.put_u8( e.note )
				buf.put_u8( e.value )
			MIDIEventType.control_change:
				buf.put_u8( 0xB0 | e.channel_number )
				buf.put_u8( e.number )
				buf.put_u8( e.value )
			MIDIEventType.program_change:
				buf.put_u8( 0xC0 | e.channel_number )
				buf.put_u8( e.number )
			MIDIEventType.channel_pressure:
				buf.put_u8( 0xD0 | e.channel_number )
				buf.put_u8( e.value )
			MIDIEventType.pitch_bend:
				buf.put_u8( 0xE0 | e.channel_number )
				buf.put_u8( e.value & 0x7f )
				buf.put_u8( ( e.value >> 7 ) & 0x7f )
			MIDIEventType.system_event:
				self._write_system_event( buf, e )

	var track_size = buf.get_available_bytes( )
	stream.put_utf8_string( "MTrk".to_ascii( ) )
	stream.put_u32( track_size )
	stream.put_data( buf.get_partial_data( track_size )[1] )

"""
	システムイベント書き込み
	@param	stream
	@param	event
"""
func _write_system_event( stream, event ):
	match event.type:
		MIDISystemEventType.sys_ex:
			stream.put_u8( 0xF0 )
			self._write_variable_int( stream, len( event.data ) )
			stream.put_data( event.data )
		MIDISystemEventType.divided_sys_ex:
			stream.put_u8( 0xF7 )
			self._write_variable_int( stream, len( event.data ) )
			stream.put_data( event.data )
		MIDISystemEventType.text_event:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x01 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )
		MIDISystemEventType.copyright:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x02 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )
		MIDISystemEventType.track_name:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x03 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )
		MIDISystemEventType.instrument_name:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x04 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )
		MIDISystemEventType.lyric:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x05 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )
		MIDISystemEventType.marker:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x06 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )
		MIDISystemEventType.cue_point:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x07 )
			self._write_variable_int( stream, len( event.text ) )
			stream.put_data( event.text.to_ascii( ) )

		MIDISystemEventType.midi_channel_prefix:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x20 )
			stream.put_u8( 0x01 )
			stream.put_u8( event.prefix )
		MIDISystemEventType.end_of_track:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x2F )
			stream.put_u8( 0x00 )
		MIDISystemEventType.set_tempo:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x51 )
			stream.put_u8( 0x03 )
			stream.put_u8( ( event.bpm >> 16 ) & 0xFF )
			stream.put_u8( ( event.bpm >> 8 ) & 0xFF )
			stream.put_u8( event.bpm & 0xFF )
		MIDISystemEventType.smpte_offset:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x54 )
			stream.put_u8( 0x05 )
			stream.put_u8( event.hr )
			stream.put_u8( event.mm )
			stream.put_u8( event.se )
			stream.put_u8( event.fr )
			stream.put_u8( event.ff )
		MIDISystemEventType.beat:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x58 )
			stream.put_u8( 0x04 )
			stream.put_u8( event.numerator )
			stream.put_u8( event.denominator )
			stream.put_u8( event.clock )
			stream.put_u8( event.beat32 )
		MIDISystemEventType.key:
			stream.put_u8( 0xFF )
			stream.put_u8( 0x59 )
			stream.put_u8( 0x02 )
			stream.put_u8( event.sf )
			stream.put_u8( 1 if event.minor else 0 )
		MIDISystemEventType.unknown:
			stream.put_u8( 0xFF )
			stream.put_u8( event.meta_type )
			stream.put_u8( len( event.data ) )
			stream.put_data( event.data )