Midi play
This commit is contained in:
parent
600555a80b
commit
6993e4bd92
Binary file not shown.
@ -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
|
Binary file not shown.
@ -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
|
Binary file not shown.
Binary file not shown.
@ -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
|
BIN
Audio/tetris.wav
BIN
Audio/tetris.wav
Binary file not shown.
@ -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
|
@ -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)
|
||||
func _on_LineLcearDelay_timeout():
|
||||
mute_midi_channel(MIDI_LINE_CLEAR_CHANNELS, true)
|
||||
|
@ -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"]
|
||||
|
||||
|
||||
|
3
MusicMidiPlayer.gd
Normal file
3
MusicMidiPlayer.gd
Normal file
@ -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]
|
@ -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 )
|
||||
|
||||
|
@ -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
|
||||
|
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.
@ -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)
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user