diff --git a/Audio/Song A pizzicato solo.ogg b/Audio/Song A pizzicato solo.ogg deleted file mode 100644 index cab2dfe..0000000 Binary files a/Audio/Song A pizzicato solo.ogg and /dev/null differ diff --git a/Audio/Song A pizzicato solo.ogg.import b/Audio/Song A pizzicato solo.ogg.import deleted file mode 100644 index a3d26c9..0000000 --- a/Audio/Song A pizzicato solo.ogg.import +++ /dev/null @@ -1,15 +0,0 @@ -[remap] - -importer="ogg_vorbis" -type="AudioStreamOGGVorbis" -path="res://.import/Song A pizzicato solo.ogg-bb0592fa96a1944827b2c377836a7301.oggstr" - -[deps] - -source_file="res://Audio/Song A pizzicato solo.ogg" -dest_files=[ "res://.import/Song A pizzicato solo.ogg-bb0592fa96a1944827b2c377836a7301.oggstr" ] - -[params] - -loop=false -loop_offset=0 diff --git a/Audio/Song A without pizzicato.ogg b/Audio/Song A without pizzicato.ogg deleted file mode 100644 index 77fedcc..0000000 Binary files a/Audio/Song A without pizzicato.ogg and /dev/null differ diff --git a/Audio/Song A without pizzicato.ogg.import b/Audio/Song A without pizzicato.ogg.import deleted file mode 100644 index 00edff4..0000000 --- a/Audio/Song A without pizzicato.ogg.import +++ /dev/null @@ -1,15 +0,0 @@ -[remap] - -importer="ogg_vorbis" -type="AudioStreamOGGVorbis" -path="res://.import/Song A without pizzicato.ogg-0762944017b6daf2dd37510d2a86a592.oggstr" - -[deps] - -source_file="res://Audio/Song A without pizzicato.ogg" -dest_files=[ "res://.import/Song A without pizzicato.ogg-0762944017b6daf2dd37510d2a86a592.oggstr" ] - -[params] - -loop=false -loop_offset=0 diff --git a/Audio/Tetris - Song A.mid b/Audio/Tetris - Song A.mid deleted file mode 100644 index 3034c1a..0000000 Binary files a/Audio/Tetris - Song A.mid and /dev/null differ diff --git a/Audio/line_clear.wav b/Audio/line_clear.wav deleted file mode 100644 index e6507cd..0000000 Binary files a/Audio/line_clear.wav and /dev/null differ diff --git a/Audio/line_clear.wav.import b/Audio/line_clear.wav.import deleted file mode 100644 index cf8e030..0000000 --- a/Audio/line_clear.wav.import +++ /dev/null @@ -1,21 +0,0 @@ -[remap] - -importer="wav" -type="AudioStreamSample" -path="res://.import/line_clear.wav-7797a050b9c9acfadee761ccba5d496d.sample" - -[deps] - -source_file="res://Audio/line_clear.wav" -dest_files=[ "res://.import/line_clear.wav-7797a050b9c9acfadee761ccba5d496d.sample" ] - -[params] - -force/8_bit=false -force/mono=false -force/max_rate=false -force/max_rate_hz=44100 -edit/trim=true -edit/normalize=true -edit/loop=false -compress/mode=0 diff --git a/Audio/tetris.wav b/Audio/tetris.wav deleted file mode 100644 index f6cc51d..0000000 Binary files a/Audio/tetris.wav and /dev/null differ diff --git a/Audio/tetris.wav.import b/Audio/tetris.wav.import deleted file mode 100644 index 1da9ad9..0000000 --- a/Audio/tetris.wav.import +++ /dev/null @@ -1,21 +0,0 @@ -[remap] - -importer="wav" -type="AudioStreamSample" -path="res://.import/tetris.wav-72e7c256c9dbf193e3a65319af7e85d5.sample" - -[deps] - -source_file="res://Audio/tetris.wav" -dest_files=[ "res://.import/tetris.wav-72e7c256c9dbf193e3a65319af7e85d5.sample" ] - -[params] - -force/8_bit=false -force/mono=false -force/max_rate=false -force/max_rate_hz=44100 -edit/trim=true -edit/normalize=true -edit/loop=false -compress/mode=0 diff --git a/GridMap/GridMap.gd b/GridMap/GridMap.gd index 3ce1b6e..bee35fd 100644 --- a/GridMap/GridMap.gd +++ b/GridMap/GridMap.gd @@ -10,12 +10,21 @@ const TetroS = preload("res://Tetrominos/TetroS.tscn") const TetroT = preload("res://Tetrominos/TetroT.tscn") const TetroZ = preload("res://Tetrominos/TetroZ.tscn") +const NB_LINES = 20 +const NB_COLLUMNS = 10 + const EMPTY_CELL = -1 -const NB_MINOES = 4 + const NEXT_POSITION = Vector3(13, 16, 0) const START_POSITION = Vector3(5, 20, 0) const HOLD_POSITION = Vector3(-5, 16, 0) -const SOUND_POSITION = Vector3(5, 10, 6) + +const movements = { + "move_right": Vector3(1, 0, 0), + "move_left": Vector3(-1, 0, 0), + "soft_drop": Vector3(0, -1, 0) +} + const SCORES = [ [0, 4, 1], [1, 8, 2], @@ -24,25 +33,23 @@ const SCORES = [ [8] ] const LINES_CLEARED_NAMES = ["", "SINGLE", "DOUBLE", "TRIPLE", "TETRIS"] -const T_SPIN_NAMES = ["", "MINI T-SPIN", "T-SPIN"] -const NB_LINES = 20 -const NB_COLLUMNS = 10 +const T_SPIN_NAMES = ["", "T-SPIN", "MINI T-SPIN"] + +const MIDI_MOVE_CHANNELS = [7, 8, 9, 11] +const MIDI_LINE_CLEAR_CHANNELS = [2, 6, 10] var next_piece = random_piece() var current_piece var held_piece var current_piece_held = false -var locked = false + var autoshift_action = "" -var movements = { - "move_right": Vector3(1, 0, 0), - "move_left": Vector3(-1, 0, 0), - "soft_drop": Vector3(0, -1, 0) -} + var exploding_lines = [] var lines_to_clear = [] var random_bag = [] var playing = true + var level = 0 var goal = 0 var score = 0 @@ -84,7 +91,6 @@ func new_piece(): current_piece.emit_trail(true) autoshift_action = "" update_ghost_piece() - $Music2.translation = SOUND_POSITION next_piece = random_piece() next_piece.translation = NEXT_POSITION if move(movements["soft_drop"]): @@ -113,21 +119,21 @@ func process_actions(): if action != autoshift_action: if Input.is_action_pressed(action): if move(movements[action]): - move_music() + move_midi() autoshift_action = action $AutoShiftTimer.stop() $AutoShiftDelay.start() if Input.is_action_just_pressed("hard_drop"): - move_music() + move_midi() while move(movements["soft_drop"]): pass lock_piece() if Input.is_action_just_pressed("rotate_clockwise"): rotate(Tetromino.CLOCKWISE) - move_music() + move_midi() if Input.is_action_just_pressed("rotate_counterclockwise"): rotate(Tetromino.COUNTERCLOCKWISE) - move_music() + move_midi() if Input.is_action_just_pressed("hold"): hold() @@ -154,7 +160,7 @@ func possible_positions(initial_positions, movement): position = initial_positions[i] + movement if is_free_cell(position): test_positions.append(position) - if test_positions.size() == NB_MINOES: + if test_positions.size() == Tetromino.NB_MINOES: return test_positions else: return [] @@ -164,7 +170,6 @@ func move(movement): $LockDelay.start() if movement.x: update_ghost_piece() - $Music2.translate(movement) return true else: return false @@ -177,9 +182,11 @@ func rotate(direction): else: return false -func move_music(): - AudioServer.set_bus_mute(AudioServer.get_bus_index("Music2"), false) - $MusicDelay.start() +func move_midi(): + for channel_id in MIDI_MOVE_CHANNELS: + $MidiPlayer.channel_status[channel_id].pan = current_piece.translation.x / 10.0 + mute_midi_channel(MIDI_MOVE_CHANNELS, false) + $MidiPlayer/MoveDelay.start() func update_ghost_piece(): var new_positions = current_piece.positions() @@ -225,10 +232,13 @@ func update_score(): score += 100 * s goal -= s print(T_SPIN_NAMES[current_piece.t_spin], ' ', LINES_CLEARED_NAMES[lines_to_clear.size()], " Score ", score) + mute_midi_channel(MIDI_LINE_CLEAR_CHANNELS, false) + $MidiPlayer.play_now() if lines_to_clear.size() == Tetromino.NB_MINOES: - $TetrisSFX.play() + $MidiPlayer/LineLcearDelay.wait_time = 1.71 else: - $LineCLearSFX.play() + $MidiPlayer/LineLcearDelay.wait_time = 0.86 + $MidiPlayer/LineLcearDelay.start() if goal <= 0: new_level() else: @@ -250,7 +260,6 @@ func hold(): current_piece.translation = START_POSITION current_piece.emit_trail(true) update_ghost_piece() - $Music2.translation = SOUND_POSITION else: held_piece = current_piece new_piece() @@ -262,15 +271,16 @@ func resume(): playing = true $DropTimer.start() $LockDelay.start() - start_musics() + $MidiPlayer.play() + mute_midi_channel(MIDI_MOVE_CHANNELS, true) + mute_midi_channel(MIDI_LINE_CLEAR_CHANNELS, true) print("RESUME") func pause(): playing = false $DropTimer.stop() $LockDelay.stop() - $Music.stop() - $Music2.stop() + $MidiPlayer.stop() print("PAUSE") func game_over(): @@ -284,13 +294,12 @@ func _notification(what): if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT: pause() -func _on_MusicDelay_timeout(): - AudioServer.set_bus_mute(AudioServer.get_bus_index("Music2"), true) +func mute_midi_channel(channels, muted): + for channel_id in channels: + $MidiPlayer.channel_mute[channel_id] = muted -func _on_Music_finished(): - start_musics() +func _on_MoveDelay_timeout(): + mute_midi_channel(MIDI_MOVE_CHANNELS, true) -func start_musics(): - $Music.play() - $Music2.play() - AudioServer.set_bus_mute(AudioServer.get_bus_index("Music2"), true) \ No newline at end of file +func _on_LineLcearDelay_timeout(): + mute_midi_channel(MIDI_LINE_CLEAR_CHANNELS, true) diff --git a/GridMap/GridMap.tscn b/GridMap/GridMap.tscn index f9a01de..6f85784 100644 --- a/GridMap/GridMap.tscn +++ b/GridMap/GridMap.tscn @@ -1,14 +1,11 @@ -[gd_scene load_steps=11 format=2] +[gd_scene load_steps=8 format=2] [ext_resource path="res://Mino/MinoLibrary.tres" type="MeshLibrary" id=1] [ext_resource path="res://GridMap/GridMap.gd" type="Script" id=2] [ext_resource path="res://GridMap/GridBack.tscn" type="PackedScene" id=3] [ext_resource path="res://GridMap/BackMaterial.tres" type="Material" id=4] [ext_resource path="res://Tetrominos/GhostPiece.tscn" type="PackedScene" id=5] -[ext_resource path="res://Audio/Song A without pizzicato.ogg" type="AudioStream" id=6] -[ext_resource path="res://Audio/Song A pizzicato solo.ogg" type="AudioStream" id=7] -[ext_resource path="res://Audio/line_clear.wav" type="AudioStream" id=8] -[ext_resource path="res://Audio/tetris.wav" type="AudioStream" id=9] +[ext_resource path="res://midi/MidiPlayer.tscn" type="PackedScene" id=6] [sub_resource type="CubeMesh" id=1] @@ -95,62 +92,26 @@ mesh = SubResource( 1 ) transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 0 ) _sections_unfolded = [ "Transform" ] -[node name="Music" type="AudioStreamPlayer" parent="." index="9"] +[node name="MidiPlayer" parent="." index="9" instance=ExtResource( 6 )] -stream = ExtResource( 6 ) -volume_db = 0.0 -pitch_scale = 1.0 -autoplay = false -mix_target = 0 -bus = "Music" -_sections_unfolded = [ "Attenuation Filter", "Doppler", "Pause" ] +file = "res://midi/Tetris - Song A.mid" +volume_db = -3 +loop = true +soundfont = "res://midi/TimGM6mb.sf2" -[node name="Music2" type="AudioStreamPlayer3D" parent="." index="10"] - -transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 0 ) -stream = ExtResource( 7 ) -attenuation_model = 0 -unit_db = 0.0 -unit_size = 1.0 -max_db = 6.0 -pitch_scale = 1.0 -autoplay = false -max_distance = 0.0 -out_of_range_mode = 0 -bus = "Music2" -area_mask = 1 -emission_angle_enabled = false -emission_angle_degrees = 45.0 -emission_angle_filter_attenuation_db = -12.0 -attenuation_filter_cutoff_hz = 5000.0 -attenuation_filter_db = -24.0 -doppler_tracking = 0 -_sections_unfolded = [ "Attenuation Filter", "Doppler" ] - -[node name="MusicDelay" type="Timer" parent="." index="11"] +[node name="MoveDelay" type="Timer" parent="MidiPlayer" index="1"] process_mode = 1 wait_time = 0.1 one_shot = true autostart = false -[node name="LineCLearSFX" type="AudioStreamPlayer" parent="." index="12"] +[node name="LineLcearDelay" type="Timer" parent="MidiPlayer" index="2"] -stream = ExtResource( 8 ) -volume_db = 0.0 -pitch_scale = 1.0 -autoplay = false -mix_target = 0 -bus = "SFX" - -[node name="TetrisSFX" type="AudioStreamPlayer" parent="." index="13"] - -stream = ExtResource( 9 ) -volume_db = 0.0 -pitch_scale = 1.0 -autoplay = false -mix_target = 0 -bus = "SFX" +process_mode = 1 +wait_time = 0.86 +one_shot = false +autostart = false [connection signal="timeout" from="DropTimer" to="." method="_on_DropTimer_timeout"] @@ -162,8 +123,8 @@ bus = "SFX" [connection signal="timeout" from="ExplosionDelay" to="." method="_on_ExplosionDelay_timeout"] -[connection signal="finished" from="Music" to="." method="_on_Music_finished"] +[connection signal="timeout" from="MidiPlayer/MoveDelay" to="." method="_on_MoveDelay_timeout"] -[connection signal="timeout" from="MusicDelay" to="." method="_on_MusicDelay_timeout"] +[connection signal="timeout" from="MidiPlayer/LineLcearDelay" to="." method="_on_LineLcearDelay_timeout"] diff --git a/MusicMidiPlayer.gd b/MusicMidiPlayer.gd new file mode 100644 index 0000000..b6e7e38 --- /dev/null +++ b/MusicMidiPlayer.gd @@ -0,0 +1,3 @@ +extends "MidiPlayer.gd" + +export var channel_mute = [false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false] diff --git a/WorldEnvironment.tscn b/WorldEnvironment.tscn index 5977bf4..12fe401 100644 --- a/WorldEnvironment.tscn +++ b/WorldEnvironment.tscn @@ -89,7 +89,7 @@ adjustment_contrast = 1.0 adjustment_saturation = 0.34 _sections_unfolded = [ "Adjustments", "Ambient Light", "Background", "Fog", "Resource" ] -[node name="WorldEnvironment" type="WorldEnvironment"] +[node name="WorldEnvironment" type="WorldEnvironment" index="0"] environment = SubResource( 2 ) diff --git a/default_bus_layout.tres b/default_bus_layout.tres index 0ba00c1..8edf417 100644 --- a/default_bus_layout.tres +++ b/default_bus_layout.tres @@ -1,4 +1,21 @@ -[gd_resource type="AudioBusLayout" format=2] +[gd_resource type="AudioBusLayout" load_steps=2 format=2] + +[sub_resource type="AudioEffectDelay" id=1] + +resource_name = "Delay" +dry = 1.0 +tap1/active = true +tap1/delay_ms = 250.0 +tap1/level_db = -6.0 +tap1/pan = 0.2 +tap2/active = true +tap2/delay_ms = 500.0 +tap2/level_db = -12.0 +tap2/pan = -0.4 +feedback/active = false +feedback/delay_ms = 340.0 +feedback/level_db = -6.0 +feedback/lowpass = 16000.0 [resource] @@ -14,12 +31,14 @@ bus/1/mute = false bus/1/bypass_fx = false bus/1/volume_db = -16.8 bus/1/send = "Master" -bus/2/name = "Music2" +bus/2/name = "LineClear" bus/2/solo = false bus/2/mute = false bus/2/bypass_fx = false bus/2/volume_db = 5.5 bus/2/send = "Master" +bus/2/effect/0/effect = SubResource( 1 ) +bus/2/effect/0/enabled = true bus/3/name = "SFX" bus/3/solo = false bus/3/mute = false diff --git a/midi/ADSR.gd b/midi/ADSR.gd new file mode 100644 index 0000000..a0ec35c --- /dev/null +++ b/midi/ADSR.gd @@ -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 diff --git a/midi/ADSR.tscn b/midi/ADSR.tscn new file mode 100644 index 0000000..78e0fce --- /dev/null +++ b/midi/ADSR.tscn @@ -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" ] + + diff --git a/midi/Bank.gd b/midi/Bank.gd new file mode 100644 index 0000000..09c2484 --- /dev/null +++ b/midi/Bank.gd @@ -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 + diff --git a/midi/MOD.gd b/midi/MOD.gd new file mode 100644 index 0000000..49dcebc --- /dev/null +++ b/midi/MOD.gd @@ -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( ) diff --git a/midi/MidiPlayer.gd b/midi/MidiPlayer.gd new file mode 100644 index 0000000..98b9f55 --- /dev/null +++ b/midi/MidiPlayer.gd @@ -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 diff --git a/midi/MidiPlayer.tscn b/midi/MidiPlayer.tscn new file mode 100644 index 0000000..ac52dac --- /dev/null +++ b/midi/MidiPlayer.tscn @@ -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" + + diff --git a/midi/SMF.gd b/midi/SMF.gd new file mode 100644 index 0000000..85e28ce --- /dev/null +++ b/midi/SMF.gd @@ -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 ) diff --git a/midi/SoundFont.gd b/midi/SoundFont.gd new file mode 100644 index 0000000..58a6c4a --- /dev/null +++ b/midi/SoundFont.gd @@ -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 diff --git a/midi/Tetris - Song A.mid b/midi/Tetris - Song A.mid new file mode 100644 index 0000000..5c1ba9c Binary files /dev/null and b/midi/Tetris - Song A.mid differ diff --git a/midi/TimGM6mb.sf2 b/midi/TimGM6mb.sf2 new file mode 100644 index 0000000..4316d4c Binary files /dev/null and b/midi/TimGM6mb.sf2 differ diff --git a/project.godot b/project.godot index 0c1c97c..c008403 100644 --- a/project.godot +++ b/project.godot @@ -52,6 +52,6 @@ rotate_clockwise=[ Object(InputEventJoypadButton,"resource_local_to_scene":false , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":88,"unicode":0,"echo":false,"script":null) ] rotate_counterclockwise=[ Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null) -, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777221,"unicode":0,"echo":false,"script":null) +, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777224,"unicode":0,"echo":false,"script":null) , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":90,"unicode":0,"echo":false,"script":null) ]