Midi play
This commit is contained in:
		
							
								
								
									
										96
									
								
								midi/ADSR.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								midi/ADSR.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| extends AudioStreamPlayer | ||||
|  | ||||
| """ | ||||
| 	AudioStreamPlayer with ADSR | ||||
| """ | ||||
|  | ||||
| var releasing = false | ||||
| var instrument = null | ||||
| var velocity = 0 | ||||
| var pitch_bend = 0 | ||||
| var mix_rate = 0 | ||||
| var using_timer = 0.0 | ||||
| var timer = 0.0 | ||||
| var current_volume = 0 | ||||
| var maximum_volume_db = -8.0 | ||||
| var minimum_volume_db = -108.0 | ||||
| var pan = 0.5 | ||||
| var ads_state = [ | ||||
| 	{ "time": 0, "volume": 1.0 }, | ||||
| 	{ "time": 0.2, "volume": 0.95 }, | ||||
| 	# { "time": 0.2, "jump_to": 0.0 },	# not implemented | ||||
| ] | ||||
| var release_state = [ | ||||
| 	{ "time": 0, "volume": 0.8 }, | ||||
| 	{ "time": 0.01, "volume": 0.0 }, | ||||
| 	# { "time": 0.2, "jump_to": 0.0 },	# not implemented | ||||
| ] | ||||
|  | ||||
| func _ready( ): | ||||
| 	self.stop( ) | ||||
|  | ||||
| func set_instrument( instrument ): | ||||
| 	self.instrument = instrument | ||||
| 	self.mix_rate = instrument.mix_rate | ||||
| 	self.stream = instrument.stream.duplicate( ) | ||||
| 	self.ads_state = instrument.ads_state | ||||
| 	self.release_state = instrument.release_state | ||||
|  | ||||
| func play( ): | ||||
| 	self.releasing = false | ||||
| 	self.timer = 0.0 | ||||
| 	self.using_timer = 0.0 | ||||
| 	self.current_volume = self.ads_state[0].volume | ||||
| 	self.stream.mix_rate = round( self.mix_rate * ( 1.0 + self.pitch_bend * 0.5 ) ) | ||||
| 	.play( 0.0 ) | ||||
| 	self._update_volume( ) | ||||
|  | ||||
| func start_release( ): | ||||
| 	self.releasing = true | ||||
| 	self.current_volume = self.release_state[0].volume | ||||
| 	self.timer = 0.0 | ||||
| 	self._update_volume( ) | ||||
|  | ||||
| func set_pitch_bend( pb ): | ||||
| 	self.pitch_bend = pb | ||||
| 	var pos = self.get_playback_position( ) | ||||
| 	self.stream.mix_rate = round( self.mix_rate * ( 1.0 + self.pitch_bend * 0.5 ) ) | ||||
| 	.play( pos ) | ||||
|  | ||||
| func _process( delta ): | ||||
| 	if not self.playing: | ||||
| 		return | ||||
|  | ||||
| 	self.timer += delta | ||||
| 	self.using_timer += delta | ||||
| 	# self.transform.origin.x = self.pan * self.get_viewport( ).size.x | ||||
|  | ||||
| 	# ADSR | ||||
| 	var use_state = null | ||||
| 	if self.releasing: | ||||
| 		use_state = self.release_state | ||||
| 	else: | ||||
| 		use_state = self.ads_state | ||||
|  | ||||
| 	var all_states = use_state.size( ) | ||||
| 	var last_state = all_states - 1 | ||||
| 	if use_state[last_state].time <= self.timer: | ||||
| 		self.current_volume = use_state[last_state].volume | ||||
| 		if self.releasing: | ||||
| 			self.stop( ) | ||||
| 	else: | ||||
| 		for state_number in range( 1, all_states ): | ||||
| 			var state = use_state[state_number] | ||||
| 			if self.timer < state.time: | ||||
| 				var pre_state = use_state[state_number-1] | ||||
| 				var s = ( state.time - self.timer ) / ( state.time - pre_state.time ) | ||||
| 				var t = 1.0 - s | ||||
| 				self.current_volume = pre_state.volume * s + state.volume * t | ||||
| 				break | ||||
|  | ||||
| 	self._update_volume( ) | ||||
|  | ||||
| func _update_volume( ): | ||||
| 	var s = self.current_volume | ||||
| 	var t = 1.0 - s | ||||
| 	self.volume_db = s * self.maximum_volume_db + t * self.minimum_volume_db | ||||
							
								
								
									
										15
									
								
								midi/ADSR.tscn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								midi/ADSR.tscn
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| [gd_scene load_steps=2 format=2] | ||||
|  | ||||
| [ext_resource path="res://midi/ADSR.gd" type="Script" id=1] | ||||
|  | ||||
| [node name="ADSR" type="AudioStreamPlayer"] | ||||
|  | ||||
| stream = null | ||||
| volume_db = 0.0 | ||||
| autoplay = false | ||||
| mix_target = 0 | ||||
| bus = "Master" | ||||
| script = ExtResource( 1 ) | ||||
| _sections_unfolded = [ "Pause", "Transform", "Z Index" ] | ||||
|  | ||||
|  | ||||
							
								
								
									
										326
									
								
								midi/Bank.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										326
									
								
								midi/Bank.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,326 @@ | ||||
| """ | ||||
| 	Sound Bank by Yui Kinomoto @arlez80 | ||||
| """ | ||||
|  | ||||
| const drum_track_bank = 128 | ||||
| const SoundFont = preload( "SoundFont.gd" ) | ||||
|  | ||||
| # デフォルト | ||||
| var default_mix_rate_table = [819,868,920,974,1032,1094,1159,1228,1301,1378,1460,1547,1639,1736,1840,1949,2065,2188,2318,2456,2602,2756,2920,3094,3278,3473,3679,3898,4130,4375,4635,4911,5203,5513,5840,6188,6556,6945,7358,7796,8259,8751,9271,9822,10406,11025,11681,12375,13111,13891,14717,15592,16519,17501,18542,19644,20812,22050,23361,24750,26222,27781,29433,31183,33038,35002,37084,39289,41625,44100,46722,49501,52444,55563,58866,62367,66075,70004,74167,78577,83250,88200,93445,99001,104888,111125,117733,124734,132151,140009,148334,157155,166499,176400,186889,198002,209776,222250,235466,249467,264301,280018,296668,314309,332999,352800,373779,396005,419552,444500,470932,498935,528603,560035,593337,628618,665998,705600,747557,792009,839105,889000,941863,997869,1057205,1120070,1186673,1257236] | ||||
| var default_ads_state = [ | ||||
| 	{ "time": 0, "volume": 1.0 }, | ||||
| 	{ "time": 0.2, "volume": 0.95 }, | ||||
| ] | ||||
| var default_release_state = [ | ||||
| 	{ "time": 0, "volume": 0.95 }, | ||||
| 	{ "time": 0.01, "volume": 0.0 }, | ||||
| ] | ||||
|  | ||||
| # 音色テーブル | ||||
| var presets = {} | ||||
|  | ||||
| """ | ||||
| 	楽器 | ||||
| """ | ||||
| func create_preset( ): | ||||
| 	var instruments = [] | ||||
| 	for i in range( 0, 128 ): | ||||
| 		instruments.append( null ) | ||||
| 	return { | ||||
| 		"name": "", | ||||
| 		"number": 0, | ||||
| 		"instruments": instruments, | ||||
| 		"bags": [], | ||||
| 	} | ||||
|  | ||||
| """ | ||||
| 	ノート | ||||
| """ | ||||
| func create_instrument( ): | ||||
| 	return { | ||||
| 		"mix_rate": 44100, | ||||
| 		"stream": null, | ||||
| 		"ads_state": default_ads_state, | ||||
| 		"release_state": default_release_state, | ||||
| 		"preset": null, | ||||
| 		# "assine_group": 0,	# reserved | ||||
| 	} | ||||
|  | ||||
| """ | ||||
| 	再生周波数計算 | ||||
| """ | ||||
| func calc_mix_rate( rate, center_key, target_key ): | ||||
| 	return round( rate * pow( 2.0, ( target_key - center_key ) / 12.0 ) ) | ||||
|  | ||||
| """ | ||||
| 	追加 | ||||
| """ | ||||
| func set_preset_sample( program_number, base_sample, base_mix_rate ): | ||||
| 	var mix_rate_table = default_mix_rate_table | ||||
|  | ||||
| 	if base_mix_rate != 44100: | ||||
| 		print( "not implemented" ) | ||||
| 		breakpoint | ||||
|  | ||||
| 	var preset = self.create_preset( ) | ||||
| 	preset.name = "#%03d" % program_number | ||||
| 	preset.number = program_number | ||||
| 	for i in range(0,128): | ||||
| 		var inst = self.create_instrument( ) | ||||
| 		inst.mix_rate = mix_rate_table[i] | ||||
| 		inst.stream = base_sample | ||||
| 		inst.preset = preset | ||||
| 		preset.instruments[i] = inst | ||||
|  | ||||
| 	self.set_preset( program_number, preset ) | ||||
|  | ||||
| """ | ||||
| 	追加 | ||||
| """ | ||||
| func set_preset( program_number, preset ): | ||||
| 	self.presets[program_number] = preset | ||||
|  | ||||
| """ | ||||
| 	指定した楽器を取得 | ||||
| """ | ||||
| func get_preset( program_number, bank = 0 ): | ||||
| 	var pc = program_number | ( bank << 7 ) | ||||
|  | ||||
| 	if not self.presets.has( pc ): | ||||
| 		if drum_track_bank == bank: | ||||
| 			pc = drum_track_bank | ||||
| 		else: | ||||
| 			pc = program_number | ||||
| 		if not self.presets.has( pc ): | ||||
| 			pc = self.presets.keys( )[0] | ||||
|  | ||||
| 	var preset = self.presets[pc] | ||||
| 	return preset | ||||
|  | ||||
| """ | ||||
| 	サウンドフォント読み込み | ||||
| """ | ||||
| func read_soundfont( sf, need_program_numbers = null ): | ||||
| 	var sf_insts = self._read_soundfont_pdta_inst( sf ) | ||||
|  | ||||
| 	var bag_index = 0 | ||||
| 	var gen_index = 0 | ||||
| 	for phdr_index in range( 0, len( sf.pdta.phdr )-1 ): | ||||
| 		var phdr = sf.pdta.phdr[phdr_index] | ||||
|  | ||||
| 		var preset = self.create_preset( ) | ||||
| 		var program_number = phdr.preset | ( phdr.bank << 7 ) | ||||
|  | ||||
| 		preset.name = phdr.name | ||||
| 		preset.number = program_number | ||||
|  | ||||
| 		var bag_next = sf.pdta.phdr[phdr_index+1].preset_bag_index | ||||
| 		var bag_count = bag_index | ||||
| 		while bag_count < bag_next: | ||||
| 			var gen_next = sf.pdta.pbag[bag_count+1].gen_ndx | ||||
| 			var gen_count = gen_index | ||||
| 			var bag = { | ||||
| 				"preset": preset, | ||||
| 				"coarse_tune": 0, | ||||
| 				"fine_tune": 0, | ||||
| 				"key_range": null, | ||||
| 				"instrument": null, | ||||
| 				"pan": 0.5, | ||||
| 			} | ||||
| 			while gen_count < gen_next: | ||||
| 				var gen = sf.pdta.pgen[gen_count] | ||||
| 				match gen.gen_oper: | ||||
| 					SoundFont.gen_oper_coarse_tune: | ||||
| 						bag.coarse_tune = gen.amount | ||||
| 					SoundFont.gen_oper_fine_tune: | ||||
| 						bag.fine_tune = gen.amount | ||||
| 					SoundFont.gen_oper_key_range: | ||||
| 						bag.key_range = { | ||||
| 							"high": gen.uamount >> 8, | ||||
| 							"low": gen.uamount & 0xFF, | ||||
| 						} | ||||
| 					SoundFont.gen_oper_pan: | ||||
| 						bag.pan = ( gen.amount + 500 ) / 1000.0 | ||||
| 					SoundFont.gen_oper_instrument: | ||||
| 						bag.instrument = sf_insts[gen.uamount] | ||||
| 				gen_count += 1 | ||||
| 			if bag.instrument != null: | ||||
| 				preset.bags.append( bag ) | ||||
| 			gen_index = gen_next | ||||
| 			bag_count += 1 | ||||
| 		bag_index = bag_next | ||||
|  | ||||
| 		# 追加するか? | ||||
| 		if need_program_numbers != null: | ||||
| 			if not( program_number in need_program_numbers ) and not( phdr.preset in need_program_numbers ): | ||||
| 				continue | ||||
| 		# 追加 | ||||
| 		self._read_soundfont_preset_compose_sample( sf, preset ) | ||||
| 		self.set_preset( program_number, preset ) | ||||
|  | ||||
| func _read_soundfont_preset_compose_sample( sf, preset ): | ||||
| 	""" | ||||
| 	var samples = [] | ||||
| 	var zero_4bytes = PoolIntArray( [ 0, 0 ] ) | ||||
| 	for bag in preset.bags[0].instrument.bags: | ||||
| 		var sample = PoolIntArray( ) | ||||
| 		var start = bag.sample.start + bag.sample_start_offset | ||||
| 		var end = bag.sample.end + bag.sample_end_offset | ||||
| 		for i in range( 0, ( end - start ) / 2 ): | ||||
| 			sample.append_array( zero_4bytes ) | ||||
| 		samples.append( sample ) | ||||
| 	""" | ||||
|  | ||||
| 	for pbag_index in range( 0, preset.bags.size( ) ): | ||||
| 		var pbag = preset.bags[pbag_index] | ||||
| 		for ibag_index in range( 0, pbag.instrument.bags.size( ) ): | ||||
| 			var ibag = pbag.instrument.bags[ibag_index] | ||||
| 			var start = ibag.sample.start + ibag.sample_start_offset | ||||
| 			var end = ibag.sample.end + ibag.sample_end_offset | ||||
| 			var start_loop = ibag.sample.start_loop + ibag.sample_start_loop_offset | ||||
| 			var end_loop = ibag.sample.end_loop + ibag.sample_end_loop_offset | ||||
| 			var mix_rate = ibag.sample.sample_rate * pow( 2.0, ( pbag.coarse_tune + ibag.coarse_tune ) / 12.0 ) * pow( 2.0, ( pbag.fine_tune + ibag.sample.pitch_correction + ibag.fine_tune ) / 1200.0 ) | ||||
|  | ||||
| 			var ass = AudioStreamSample.new( ) | ||||
| 			ass.data = sf.sdta.smpl.subarray( start * 2, end * 2 ) | ||||
| 			ass.format = AudioStreamSample.FORMAT_16_BITS | ||||
| 			ass.mix_rate = mix_rate | ||||
| 			ass.stereo = false #bag.sample.sample_type != SoundFont.sample_link_mono_sample | ||||
| 			ass.loop_begin = start_loop - start | ||||
| 			ass.loop_end = end_loop - start | ||||
| 			if ibag.sample_modes == SoundFont.sample_mode_no_loop or ibag.sample_modes == SoundFont.sample_mode_unused_no_loop: | ||||
| 				ass.loop_mode = AudioStreamSample.LOOP_DISABLED | ||||
| 			else: | ||||
| 				ass.loop_mode = AudioStreamSample.LOOP_FORWARD | ||||
| 			var key_range = ibag.key_range | ||||
| 			if pbag.key_range != null: | ||||
| 				key_range = pbag.key_range | ||||
| 			for key_number in range( key_range.low, key_range.high + 1 ): | ||||
| 				#if preset.number == drum_track_bank << 7: | ||||
| 				#	if 36 <= key_number and key_number <= 40: | ||||
| 				#		print( key_number, " # ", ibag.sample.name ); | ||||
| 				if preset.instruments[key_number] != null: | ||||
| 					continue | ||||
| 				var instrument = self.create_instrument( ) | ||||
| 				instrument.preset = preset | ||||
| 				if ibag.original_key == 255: | ||||
| 					instrument.mix_rate = ibag.sample.sample_rate | ||||
| 				else: | ||||
| 					instrument.mix_rate = self.calc_mix_rate( mix_rate, ibag.original_key, key_number ) | ||||
| 				instrument.stream = ass | ||||
| 	 | ||||
| 				var a = ibag.adsr.attack_vol_env_time | ||||
| 				var d = ibag.adsr.decay_vol_env_time | ||||
| 				var s = ibag.adsr.sustain_vol_env_level | ||||
| 				var r = ibag.adsr.release_vol_env_time | ||||
| 				instrument.ads_state = [ | ||||
| 					{ "time": 0, "volume": 0.0 }, | ||||
| 					{ "time": a, "volume": 1.0 }, | ||||
| 					{ "time": a+d, "volume": s }, | ||||
| 				] | ||||
| 				instrument.release_state = [ | ||||
| 					{ "time": 0, "volume": s }, | ||||
| 					{ "time": r, "volume": 0.0 }, | ||||
| 				] | ||||
| 				preset.instruments[key_number] = instrument | ||||
|  | ||||
| func _read_soundfont_pdta_inst( sf ): | ||||
| 	var sf_insts = [] | ||||
| 	var bag_index = 0 | ||||
| 	var gen_index = 0 | ||||
|  | ||||
| 	for inst_index in range(0, len( sf.pdta.inst ) - 1 ): | ||||
| 		var inst = sf.pdta.inst[inst_index] | ||||
| 		var sf_inst = {"name": inst.name, "bags": [] } | ||||
|  | ||||
| 		var bag_next = sf.pdta.inst[inst_index+1].inst_bag_ndx | ||||
| 		var bag_count = bag_index | ||||
| 		var global_bag = {} | ||||
| 		while bag_count < bag_next: | ||||
| 			var bag = { | ||||
| 				"sample": null, | ||||
| 				"sample_id": -1, | ||||
| 				"sample_start_offset": 0, | ||||
| 				"sample_end_offset": 0, | ||||
| 				"sample_start_loop_offset": 0, | ||||
| 				"sample_end_loop_offset": 0, | ||||
| 				"coarse_tune": 0, | ||||
| 				"fine_tune": 0, | ||||
| 				"original_key": 255, | ||||
| 				"keynum": 0, | ||||
| 				"sample_modes": 0, | ||||
| 				"key_range": { "high": 127, "low": 0 }, | ||||
| 				"vel_range": { "high": 127, "low": 0 }, | ||||
| 				"adsr": { | ||||
| 					"attack_vol_env_time": 0.001, | ||||
| 					"decay_vol_env_time": 0.001, | ||||
| 					"sustain_vol_env_level": 1.0, | ||||
| 					"release_vol_env_time": 0.001, | ||||
| 				}, | ||||
| 			} | ||||
| 			var gen_next = sf.pdta.ibag[bag_count+1].gen_ndx | ||||
| 			var gen_count = gen_index | ||||
| 			while gen_count < gen_next: | ||||
| 				var gen = sf.pdta.igen[gen_count] | ||||
| 				match gen.gen_oper: | ||||
| 					SoundFont.gen_oper_key_range: | ||||
| 						bag.key_range.high = gen.uamount >> 8 | ||||
| 						bag.key_range.low = gen.uamount & 0xFF | ||||
| 					SoundFont.gen_oper_vel_range: | ||||
| 						bag.vel_range.high = gen.uamount >> 8 | ||||
| 						bag.vel_range.low = gen.uamount & 0xFF | ||||
| 					SoundFont.gen_oper_overriding_root_key: | ||||
| 						bag.original_key = gen.amount | ||||
| 					SoundFont.gen_oper_start_addrs_offset: | ||||
| 						bag.sample_start_offset += gen.amount | ||||
| 					SoundFont.gen_oper_end_addrs_offset: | ||||
| 						bag.sample_end_offset += gen.amount | ||||
| 					SoundFont.gen_oper_start_addrs_coarse_offset: | ||||
| 						bag.sample_start_offset += gen.amount * 32768 | ||||
| 					SoundFont.gen_oper_end_addrs_coarse_offset: | ||||
| 						bag.sample_end_offset += gen.amount * 32768 | ||||
| 					SoundFont.gen_oper_startloop_addrs_offset: | ||||
| 						bag.sample_start_loop_offset += gen.amount | ||||
| 					SoundFont.gen_oper_endloop_addrs_offset: | ||||
| 						bag.sample_end_loop_offset += gen.amount | ||||
| 					SoundFont.gen_oper_startloop_addrs_coarse_offset: | ||||
| 						bag.sample_start_loop_offset += gen.amount * 32768 | ||||
| 					SoundFont.gen_oper_endloop_addrs_coarse_offset: | ||||
| 						bag.sample_end_loop_offset += gen.amount * 32768 | ||||
| 					SoundFont.gen_oper_coarse_tune: | ||||
| 						bag.coarse_tune = gen.amount | ||||
| 					SoundFont.gen_oper_fine_tune: | ||||
| 						bag.fine_tune = gen.amount | ||||
| 					SoundFont.gen_oper_keynum: | ||||
| 						bag.keynum = gen.amount | ||||
| 					SoundFont.gen_oper_attack_vol_env: | ||||
| 						bag.adsr.attack_vol_env_time = pow( 2.0, gen.amount / 1200.0 ) | ||||
| 					SoundFont.gen_oper_decay_vol_env: | ||||
| 						bag.adsr.decay_vol_env_time = pow( 2.0, gen.amount / 1200.0 ) | ||||
| 					SoundFont.gen_oper_release_vol_env: | ||||
| 						bag.adsr.release_vol_env_time = pow( 2.0, gen.amount / 1200.0 ) | ||||
| 					SoundFont.gen_oper_sustain_vol_env: | ||||
| 						var s = min( max( 0, gen.amount ), 1440 ) | ||||
| 						bag.adsr.sustain_vol_env_level = ( 1440.0 - s ) / 1440.0 | ||||
| 					SoundFont.gen_oper_sample_modes: | ||||
| 						bag.sample_modes = gen.uamount | ||||
| 					SoundFont.gen_oper_sample_id: | ||||
| 						bag.sample_id = gen.uamount | ||||
| 						bag.sample = sf.pdta.shdr[gen.amount] | ||||
| 						if bag.original_key == 255: | ||||
| 							bag.original_key = bag.sample.original_key | ||||
| 					#_: | ||||
| 					#	print( gen.gen_oper ) | ||||
| 				gen_count += 1 | ||||
| 			# global zoneでない場合 | ||||
| 			if bag.sample != null: | ||||
| 				sf_inst.bags.append( bag ) | ||||
| 			else: | ||||
| 				global_bag = bag | ||||
| 			gen_index = gen_next | ||||
| 			bag_count += 1 | ||||
| 		sf_insts.append( sf_inst ) | ||||
| 		bag_index = bag_next | ||||
|  | ||||
| 	return sf_insts | ||||
|  | ||||
							
								
								
									
										140
									
								
								midi/MOD.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								midi/MOD.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| """ | ||||
| 	MOD reader by Yui Kinomoto @arlez80 | ||||
| """ | ||||
|  | ||||
| """ | ||||
| 	ファイルから読み込み | ||||
| 	@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 = true | ||||
| 	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 = true | ||||
| 	return self._read( stream ) | ||||
|  | ||||
| """ | ||||
| 	読み込み | ||||
| 	@param	stream | ||||
| 	@return	smf | ||||
| """ | ||||
| func _read( stream ): | ||||
| 	var name = self._read_string( stream, 20 ) | ||||
| 	var samples = self._read_sample_informations( stream ) | ||||
| 	var song_length = stream.get_u8( ) | ||||
| 	var unknown_number = stream.get_u8( ) | ||||
| 	var song_positions = stream.get_partial_data( 128 )[1] | ||||
| 	var max_song_position = 0 | ||||
| 	for sp in song_positions: | ||||
| 		if max_song_position < sp: | ||||
| 			max_song_position = sp | ||||
|  | ||||
| 	var magic = self._read_string( stream, 4 ) | ||||
| 	var channel_count = 4 | ||||
| 	match magic: | ||||
| 		"6CHN": | ||||
| 			channel_count = 6 | ||||
| 		"FLT8", "8CHN", "CD81", "OKTA": | ||||
| 			channel_count = 8 | ||||
| 		"16CN": | ||||
| 			channel_count = 16 | ||||
| 		"32CN": | ||||
| 			channel_count = 32 | ||||
| 		_: | ||||
| 			# print( "Unknown magic" ) | ||||
| 			# breakpoint | ||||
| 			pass | ||||
|  | ||||
| 	var patterns = self._read_patterns( stream, max_song_position, channel_count ) | ||||
| 	self._read_sample_data( stream, samples ) | ||||
|  | ||||
| 	return { | ||||
| 		"name": name, | ||||
| 		"song_length": song_length, | ||||
| 		"unknown_number": unknown_number, | ||||
| 		"song_positions": song_positions, | ||||
| 		"magic": magic, | ||||
|  | ||||
| 		"patterns": patterns, | ||||
| 		"samples": samples, | ||||
| 	} | ||||
|  | ||||
| """ | ||||
| 	サンプルのデータを読み込む | ||||
| """ | ||||
| func _read_sample_informations( stream ): | ||||
| 	var samples = [] | ||||
|  | ||||
| 	for i in range( 0, 31 ): | ||||
| 		var sample = {} | ||||
| 		sample.name = self._read_string( stream, 22 ) | ||||
| 		sample.length = stream.get_u16( ) * 2 | ||||
| 		sample.fine_tune = stream.get_u8( ) & 0x0F | ||||
| 		if 0x08 < sample.fine_tune: | ||||
| 			sample.fine_tune = 0x10 - sample.fine_tune | ||||
| 		sample.volume = stream.get_u8( ) | ||||
| 		sample.loop_start = stream.get_u16( ) * 2 | ||||
| 		sample.loop_length = stream.get_u16( ) * 2 | ||||
|  | ||||
| 		samples.append( sample ) | ||||
|  | ||||
| 	return samples | ||||
|  | ||||
| """ | ||||
| 	パターンを読み込む | ||||
| """ | ||||
| func _read_patterns( stream, max_position, channels ): | ||||
| 	var patterns = [] | ||||
| 	for position in range( 0, max_position ): | ||||
| 		var pattern = [] | ||||
| 		for i in range( 0, 64 ): | ||||
| 			var line = [] | ||||
| 			for ch in range( 0, channels ): | ||||
| 				var v1 = stream.get_u16( ) | ||||
| 				var v2 = stream.get_u16( ) | ||||
| 				var sample_number = ( ( v1 >> 8 ) & 0xF0 ) | ( ( v2 >> 12 ) & 0x0F )   | ||||
| 				var key_number = v1 & 0x0FFF | ||||
| 				var effect_command = v2 & 0x0FFF | ||||
| 				line.append({ | ||||
| 					"sample_number": sample_number, | ||||
| 					"key_number": key_number, | ||||
| 					"effect_command": effect_command, | ||||
| 				}) | ||||
| 			pattern.append( line ) | ||||
| 		patterns.append( pattern ) | ||||
|  | ||||
| """ | ||||
| 	波形データ読み込む | ||||
| """ | ||||
| func _read_sample_data( stream, samples ): | ||||
| 	for sample in samples: | ||||
| 		sample.data = stream.get_partial_data( sample.length )[1] | ||||
|  | ||||
| """ | ||||
| 	文字列の読み込み | ||||
| 	@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( ) | ||||
							
								
								
									
										411
									
								
								midi/MidiPlayer.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								midi/MidiPlayer.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,411 @@ | ||||
| extends Node | ||||
|  | ||||
| const max_track = 16 | ||||
| const max_channel = 16 | ||||
| const max_note_number = 128 | ||||
| const max_program_number = 128 | ||||
| const drum_track_channel = 0x09 | ||||
| const drum_track_bank = 128 | ||||
| const ADSR = preload( "ADSR.tscn" ) | ||||
| const SMF = preload( "SMF.gd" ) | ||||
| const SoundFont = preload( "SoundFont.gd" ) | ||||
| const Bank = preload( "Bank.gd" ) | ||||
|  | ||||
| export var max_polyphony = 64 | ||||
| export var file = "" | ||||
| export var playing = false | ||||
| export var channel_mute = [false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false] | ||||
| export var play_speed = 1.0 | ||||
| export var volume_db = -30 | ||||
| export var key_shift = 0 | ||||
| export var loop = false | ||||
| export var loop_start = 0 | ||||
| export var soundfont = "" | ||||
| export var mix_target = AudioStreamPlayer.MIX_TARGET_STEREO | ||||
| export var bus = "Master" | ||||
|  | ||||
| var smf_data = null | ||||
| var tempo = 120 setget set_tempo | ||||
| var seconds_to_timebase = 2.3 | ||||
| var position = 0 | ||||
| var last_position = 0 | ||||
| var track_status = null | ||||
| var channel_status = [] | ||||
| var channel_volume_db = 20 | ||||
|  | ||||
| var bank = null | ||||
| var audio_stream_players = [] | ||||
|  | ||||
| var _used_program_numbers = [] | ||||
|  | ||||
| var play_later_events = {2: {}, 6:{}, 10:{}} | ||||
|  | ||||
| signal changed_tempo( tempo ) | ||||
| signal appeared_lyric( lyric ) | ||||
| signal appeared_marker( marker ) | ||||
| signal appeared_cue_point( cue_point ) | ||||
| signal looped | ||||
|  | ||||
| func _ready( ): | ||||
| 	if self.playing: | ||||
| 		self.play( ) | ||||
|  | ||||
| """ | ||||
| 	初期化 | ||||
| """ | ||||
| func _prepare_to_play( ): | ||||
| 	# ファイル読み込み | ||||
| 	if self.smf_data == null: | ||||
| 		var smf_reader = SMF.new( ) | ||||
| 		self.smf_data = smf_reader.read_file( self.file ) | ||||
|  | ||||
| 	self._init_track( ) | ||||
| 	self._analyse_smf( ) | ||||
| 	self._init_channel( ) | ||||
|  | ||||
| 	# 楽器 | ||||
| 	if self.bank == null: | ||||
| 		self.bank = Bank.new( ) | ||||
| 		if self.soundfont != "": | ||||
| 			var sf_reader = SoundFont.new( ) | ||||
| 			var sf2 = sf_reader.read_file( self.soundfont ) | ||||
| 			self.bank.read_soundfont( sf2, self._used_program_numbers ) | ||||
|  | ||||
| 	# 発音機 | ||||
| 	if self.audio_stream_players.size( ) == 0: | ||||
| 		for i in range( self.max_polyphony ): | ||||
| 			var audio_stream_player = ADSR.instance( ) | ||||
| 			audio_stream_player.mix_target = self.mix_target | ||||
| 			audio_stream_player.bus = self.bus | ||||
| 			self.add_child( audio_stream_player ) | ||||
| 			self.audio_stream_players.append( audio_stream_player ) | ||||
| 	else: | ||||
| 		for audio_stream_player in self.audio_stream_players: | ||||
| 			audio_stream_player.mix_target = self.mix_target | ||||
| 			audio_stream_player.bus = self.bus | ||||
|  | ||||
| """ | ||||
| 	トラック初期化 | ||||
| """ | ||||
| class TrackSorter: | ||||
| 	static func sort(a, b): | ||||
| 		if a.time < b.time: | ||||
| 			return true | ||||
| 		elif b.time < a.time: | ||||
| 			return false | ||||
| 		else: | ||||
| 			if a.event.type == SMF.MIDIEventType.note_off: | ||||
| 				return true | ||||
| 			elif b.event.type == SMF.MIDIEventType.note_off: | ||||
| 				return false | ||||
|  | ||||
| func _init_track( ): | ||||
| 	self.track_status = { | ||||
| 		"events": [], | ||||
| 		"event_pointer": 0, | ||||
| 	} | ||||
|  | ||||
| 	for track in self.smf_data.tracks: | ||||
| 		self.track_status.events += track.events | ||||
| 	if 1 < self.smf_data.tracks.size( ): | ||||
| 		self.track_status.events.sort_custom( TrackSorter, "sort" ) | ||||
| 	self.last_position = self.track_status.events[len(self.track_status.events)-1].time | ||||
|  | ||||
| """ | ||||
| 	SMF解析 | ||||
| """ | ||||
| func _analyse_smf( ): | ||||
| 	var channels = [] | ||||
| 	for i in range( max_channel ): | ||||
| 		channels.append({ | ||||
| 			#"note_on": {}, | ||||
| 			#"program_number": 0, | ||||
| 			"number": i, | ||||
| 			"bank": 0, | ||||
| 		}) | ||||
| 	self._used_program_numbers = [] | ||||
|  | ||||
| 	for event_chunk in self.track_status.events: | ||||
| 		var channel_number = event_chunk.channel_number | ||||
| 		var channel = channels[channel_number] | ||||
| 		var event = event_chunk.event | ||||
|  | ||||
| 		match event.type: | ||||
| 			#SMF.MIDIEventType.note_off: | ||||
| 			#	channel.note_on.erase( event.note ) | ||||
| 			#SMF.MIDIEventType.note_on: | ||||
| 			#	channel.note_on[event.note] = true | ||||
| 			SMF.MIDIEventType.program_change: | ||||
| 				var program_number = event.number | ( channel.bank << 7 ) | ||||
| 				# channel.program_number = program_number | ||||
| 				if not( event.number in self._used_program_numbers ): | ||||
| 					self._used_program_numbers.append( event.number ) | ||||
| 				if not( program_number in self._used_program_numbers ): | ||||
| 					self._used_program_numbers.append( program_number ) | ||||
| 			SMF.MIDIEventType.control_change: | ||||
| 				match event.number: | ||||
| 					SMF.control_number_bank_select_msb: | ||||
| 						if channel.number == drum_track_channel: | ||||
| 							channel.bank = drum_track_bank | ||||
| 						else: | ||||
| 							channel.bank = ( channel.bank & 0x7F ) | ( event.value << 7 ) | ||||
| 					SMF.control_number_bank_select_lsb: | ||||
| 						if channel.number == drum_track_channel: | ||||
| 							channel.bank = drum_track_bank | ||||
| 						else: | ||||
| 							channel.bank = ( channel.bank & 0x3F80 ) | ( event.value & 0x7F ) | ||||
| 					SMF.control_number_tkool_loop_point: | ||||
| 						self.loop_start = event_chunk.time | ||||
|  | ||||
| """ | ||||
| 	チャンネル初期化 | ||||
| """ | ||||
| func _init_channel( ): | ||||
| 	self.channel_status = [] | ||||
| 	for i in range( max_channel ): | ||||
| 		var drum_track = ( i == drum_track_channel ) | ||||
| 		var bank = 0 | ||||
| 		if drum_track: | ||||
| 			bank = self.drum_track_bank | ||||
| 		self.channel_status.append({ | ||||
| 			"number": i, | ||||
| 			"note_on": {}, | ||||
| 			"bank": bank, | ||||
| 			"program": 0, | ||||
| 			"volume": 1.0, | ||||
| 			"expression": 1.0, | ||||
| 			"pitch_bend": 0.0, | ||||
| 			"drum_track": drum_track, | ||||
| 			"pan": 0.5, | ||||
| 		}) | ||||
|  | ||||
| """ | ||||
| 	再生 | ||||
| 	@param	from_position | ||||
| """ | ||||
| func play( from_position = 0 ): | ||||
| 	self._prepare_to_play( ) | ||||
| 	self.playing = true | ||||
| 	self.seek( from_position ) | ||||
|  | ||||
| """ | ||||
| 	シーク | ||||
| """ | ||||
| func seek( to_position ): | ||||
| 	self._stop_all_notes( ) | ||||
| 	self.position = to_position | ||||
|  | ||||
| 	var pointer = 0 | ||||
| 	var length = len(self.track_status.events) | ||||
| 	while pointer < length: | ||||
| 		var event_chunk = self.track_status.events[pointer] | ||||
| 		if self.position < event_chunk.time: | ||||
| 			break | ||||
| 		pointer += 1 | ||||
| 	self.track_status.event_pointer = pointer | ||||
|  | ||||
| """ | ||||
| 	停止 | ||||
| """ | ||||
| func stop( ): | ||||
| 	self._stop_all_notes( ) | ||||
| 	self.playing = false | ||||
|  | ||||
| """ | ||||
| 	テンポ設定 | ||||
| """ | ||||
| func set_tempo( bpm ): | ||||
| 	tempo = bpm | ||||
| 	self.seconds_to_timebase = tempo / 60.0 | ||||
| 	self.emit_signal( "changed_tempo", bpm ) | ||||
|  | ||||
| """ | ||||
| 	全音を止める | ||||
| """ | ||||
| func _stop_all_notes( ): | ||||
| 	for audio_stream_player in self.audio_stream_players: | ||||
| 		audio_stream_player.stop( ) | ||||
|  | ||||
| 	for channel in self.channel_status: | ||||
| 		channel.note_on = {} | ||||
|  | ||||
| """ | ||||
| 	毎フレーム処理 | ||||
| """ | ||||
| func _process( delta ): | ||||
| 	if self.smf_data == null: | ||||
| 		return | ||||
| 	if not self.playing: | ||||
| 		return | ||||
|  | ||||
| 	self.position += self.smf_data.timebase * delta * self.seconds_to_timebase * self.play_speed | ||||
| 	self._process_track( ) | ||||
|  | ||||
| """ | ||||
| 	トラック処理 | ||||
| """ | ||||
| func _process_track( ): | ||||
| 	var track = self.track_status | ||||
| 	if track.events == null: | ||||
| 		return | ||||
|  | ||||
| 	var length = len(track.events) | ||||
|  | ||||
| 	if length <= track.event_pointer: | ||||
| 		if self.loop: | ||||
| 			self.seek( self.loop_start ) | ||||
| 			self.emit_signal( "looped" ) | ||||
| 		else: | ||||
| 			self.playing = false | ||||
| 		return | ||||
|  | ||||
| 	for channel in self.channel_status: | ||||
| 		for key_number in channel.note_on.keys( ): | ||||
| 			var note_on = channel.note_on[key_number] | ||||
| 			if not note_on.playing: | ||||
| 				channel.note_on.erase( key_number ) | ||||
|  | ||||
| 	while track.event_pointer < length: | ||||
| 		var event_chunk = track.events[track.event_pointer] | ||||
| 		if self.position < event_chunk.time: | ||||
| 			break | ||||
| 		track.event_pointer += 1 | ||||
|  | ||||
| 		var channel = self.channel_status[event_chunk.channel_number] | ||||
| 		var event = event_chunk.event | ||||
|  | ||||
| 		match event.type: | ||||
| 			SMF.MIDIEventType.note_off: | ||||
| 				self._process_track_event_note_off( channel, event ) | ||||
| 				if event_chunk.channel_number in play_later_events: | ||||
| 					play_later_events[event_chunk.channel_number].erase(event.note) | ||||
| 			SMF.MIDIEventType.note_on: | ||||
| 				self._process_track_event_note_on( channel, event ) | ||||
| 				if event_chunk.channel_number in play_later_events: | ||||
| 					play_later_events[event_chunk.channel_number][event.note] = event | ||||
| 			SMF.MIDIEventType.program_change: | ||||
| 				channel.program = event.number | ||||
| 			SMF.MIDIEventType.control_change: | ||||
| 				self._process_track_event_control_change( channel, event ) | ||||
| 			SMF.MIDIEventType.pitch_bend: | ||||
| 				channel.pitch_bend = event.value / 8192.0 - 1.0 | ||||
| 				self._update_pitch_bend_note( channel ) | ||||
| 			SMF.MIDIEventType.system_event: | ||||
| 				self._process_track_system_event( channel, event ) | ||||
| 			_: | ||||
| 				# 無視 | ||||
| 				pass | ||||
| 				 | ||||
| func play_now(): | ||||
| 	for channel in play_later_events: | ||||
| 		for note in play_later_events[channel]: | ||||
| 			_process_track_event_note_on( channel_status[channel], play_later_events[channel][note] )  | ||||
|  | ||||
| func _process_track_event_note_off( channel, event ): | ||||
| 	var key_number = event.note + self.key_shift | ||||
| 	if channel.note_on.has( key_number ): | ||||
| 		var note_player = channel.note_on[key_number] | ||||
| 		if note_player != null: | ||||
| 			note_player.start_release( ) | ||||
| 			channel.note_on.erase( key_number ) | ||||
|  | ||||
| func _process_track_event_note_on( channel, event ): | ||||
| 	if not self.channel_mute[channel.number]: | ||||
| 		var key_number = event.note + self.key_shift | ||||
| 		var note_volume = channel.volume * channel.expression * ( event.velocity / 127.0 ) | ||||
| 		var volume_db = note_volume * self.channel_volume_db - self.channel_volume_db + self.volume_db | ||||
| 		var preset = self.bank.get_preset( channel.program, channel.bank ) | ||||
| 		var instrument = preset.instruments[key_number] | ||||
|  | ||||
| 		if instrument != null: | ||||
| 			if channel.note_on.has( key_number ): | ||||
| 				channel.note_on[key_number].start_release( ) | ||||
|  | ||||
| 			var note_player = self._get_idle_player( channel.program ) | ||||
| 			if note_player != null: | ||||
| 				note_player.velocity = event.velocity | ||||
| 				note_player.maximum_volume_db = volume_db | ||||
| 				note_player.pitch_bend = channel.pitch_bend | ||||
| 				note_player.set_instrument( instrument ) | ||||
| 				note_player.play( ) | ||||
| 				if not channel.drum_track: | ||||
| 					channel.note_on[key_number] = note_player | ||||
|  | ||||
| func _process_track_event_control_change( channel, event ): | ||||
| 	match event.number: | ||||
| 		SMF.control_number_volume: | ||||
| 			channel.volume = event.value / 127.0 | ||||
| 			self._update_volume_note( channel ) | ||||
| 		SMF.control_number_expression: | ||||
| 			channel.expression = event.value / 127.0 | ||||
| 			self._update_volume_note( channel ) | ||||
| 		SMF.control_number_pan: | ||||
| 			channel.pan = event.value / 127.0 | ||||
| 		SMF.control_number_bank_select_msb: | ||||
| 			if channel.drum_track: | ||||
| 				channel.bank = self.drum_track_bank | ||||
| 			else: | ||||
| 				channel.bank = ( channel.bank & 0x7F ) | ( event.value << 7 ) | ||||
| 		SMF.control_number_bank_select_lsb: | ||||
| 			if channel.drum_track: | ||||
| 				channel.bank = self.drum_track_bank | ||||
| 			else: | ||||
| 				channel.bank = ( channel.bank & 0x3F80 ) | ( event.value & 0x7F ) | ||||
| 		_: | ||||
| 			# 無視 | ||||
| 			pass | ||||
|  | ||||
| func _process_track_system_event( channel, event ): | ||||
| 	match event.args.type: | ||||
| 		SMF.MIDISystemEventType.set_tempo: | ||||
| 			self.tempo = 60000000.0 / event.args.bpm | ||||
| 		SMF.MIDISystemEventType.lyric: | ||||
| 			self.emit_signal( "appeared_lyric", event.args.text ) | ||||
| 		SMF.MIDISystemEventType.marker: | ||||
| 			self.emit_signal( "appeared_marker", event.args.text ) | ||||
| 		SMF.MIDISystemEventType.cue_point: | ||||
| 			self.emit_signal( "appeared_cue_point", event.args.text ) | ||||
| 		_: | ||||
| 			# 無視 | ||||
| 			pass | ||||
|  | ||||
| func _get_idle_player( program ): | ||||
| 	var stopped_audio_stream_player = null | ||||
| 	var minimum_volume = 100.0 | ||||
| 	var oldest_audio_stream_player = null | ||||
| 	var oldest = 0.0 | ||||
|  | ||||
| 	for audio_stream_player in self.audio_stream_players: | ||||
| 		if not audio_stream_player.playing: | ||||
| 			return audio_stream_player | ||||
| 		if audio_stream_player.releasing and audio_stream_player.current_volume < minimum_volume: | ||||
| 			stopped_audio_stream_player = audio_stream_player | ||||
| 			minimum_volume = audio_stream_player.current_volume | ||||
| 		if oldest < audio_stream_player.using_timer: | ||||
| 			oldest_audio_stream_player = audio_stream_player | ||||
| 			oldest = audio_stream_player.using_timer | ||||
|  | ||||
| 	if stopped_audio_stream_player != null: | ||||
| 		return stopped_audio_stream_player | ||||
|  | ||||
| 	return oldest_audio_stream_player | ||||
|  | ||||
| func _update_volume_note( channel ): | ||||
| 	for note in channel.note_on.values( ): | ||||
| 		var note_volume = channel.volume * channel.expression * ( note.velocity / 127.0 ) | ||||
| 		var volume_db = note_volume * self.channel_volume_db - self.channel_volume_db + self.volume_db | ||||
| 		note.maximum_volume_db = volume_db | ||||
|  | ||||
| func _update_pitch_bend_note( channel ): | ||||
| 	for note in channel.note_on.values( ): | ||||
| 		note.set_pitch_bend( channel.pitch_bend ) | ||||
|  | ||||
| """ | ||||
| 	現在発音中の音色数を返す | ||||
| """ | ||||
| func get_now_playing_polyphony( ): | ||||
| 	var polyphony = 0 | ||||
| 	for audio_stream_player in self.audio_stream_players: | ||||
| 		if audio_stream_player.playing: | ||||
| 			polyphony += 1 | ||||
| 	return polyphony | ||||
							
								
								
									
										30
									
								
								midi/MidiPlayer.tscn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								midi/MidiPlayer.tscn
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| [gd_scene load_steps=2 format=2] | ||||
|  | ||||
| [ext_resource path="res://midi/MidiPlayer.gd" type="Script" id=1] | ||||
|  | ||||
| [node name="MidiPlayer" type="Node" index="0"] | ||||
|  | ||||
| script = ExtResource( 1 ) | ||||
| max_polyphony = 64 | ||||
| file = "" | ||||
| playing = false | ||||
| channel_mute = [ false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false ] | ||||
| play_speed = 1.0 | ||||
| volume_db = -30 | ||||
| key_shift = 0 | ||||
| loop = false | ||||
| loop_start = 0 | ||||
| soundfont = "" | ||||
| mix_target = 0 | ||||
| bus = "Master" | ||||
|  | ||||
| [node name="Default" type="AudioStreamPlayer" parent="." index="0"] | ||||
|  | ||||
| stream = null | ||||
| volume_db = 0.0 | ||||
| pitch_scale = 1.0 | ||||
| autoplay = false | ||||
| mix_target = 0 | ||||
| bus = "Master" | ||||
|  | ||||
|  | ||||
							
								
								
									
										634
									
								
								midi/SMF.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										634
									
								
								midi/SMF.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,634 @@ | ||||
| """ | ||||
| 	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 ) | ||||
							
								
								
									
										493
									
								
								midi/SoundFont.gd
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								midi/SoundFont.gd
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,493 @@ | ||||
| """ | ||||
| 	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 | ||||
							
								
								
									
										
											BIN
										
									
								
								midi/Tetris - Song A.mid
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								midi/Tetris - Song A.mid
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								midi/TimGM6mb.sf2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								midi/TimGM6mb.sf2
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user