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 TetroT = preload("res://Tetrominos/TetroT.tscn")
|
||||||
const TetroZ = preload("res://Tetrominos/TetroZ.tscn")
|
const TetroZ = preload("res://Tetrominos/TetroZ.tscn")
|
||||||
|
|
||||||
|
const NB_LINES = 20
|
||||||
|
const NB_COLLUMNS = 10
|
||||||
|
|
||||||
const EMPTY_CELL = -1
|
const EMPTY_CELL = -1
|
||||||
const NB_MINOES = 4
|
|
||||||
const NEXT_POSITION = Vector3(13, 16, 0)
|
const NEXT_POSITION = Vector3(13, 16, 0)
|
||||||
const START_POSITION = Vector3(5, 20, 0)
|
const START_POSITION = Vector3(5, 20, 0)
|
||||||
const HOLD_POSITION = Vector3(-5, 16, 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 = [
|
const SCORES = [
|
||||||
[0, 4, 1],
|
[0, 4, 1],
|
||||||
[1, 8, 2],
|
[1, 8, 2],
|
||||||
@ -24,25 +33,23 @@ const SCORES = [
|
|||||||
[8]
|
[8]
|
||||||
]
|
]
|
||||||
const LINES_CLEARED_NAMES = ["", "SINGLE", "DOUBLE", "TRIPLE", "TETRIS"]
|
const LINES_CLEARED_NAMES = ["", "SINGLE", "DOUBLE", "TRIPLE", "TETRIS"]
|
||||||
const T_SPIN_NAMES = ["", "MINI T-SPIN", "T-SPIN"]
|
const T_SPIN_NAMES = ["", "T-SPIN", "MINI T-SPIN"]
|
||||||
const NB_LINES = 20
|
|
||||||
const NB_COLLUMNS = 10
|
const MIDI_MOVE_CHANNELS = [7, 8, 9, 11]
|
||||||
|
const MIDI_LINE_CLEAR_CHANNELS = [2, 6, 10]
|
||||||
|
|
||||||
var next_piece = random_piece()
|
var next_piece = random_piece()
|
||||||
var current_piece
|
var current_piece
|
||||||
var held_piece
|
var held_piece
|
||||||
var current_piece_held = false
|
var current_piece_held = false
|
||||||
var locked = false
|
|
||||||
var autoshift_action = ""
|
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 exploding_lines = []
|
||||||
var lines_to_clear = []
|
var lines_to_clear = []
|
||||||
var random_bag = []
|
var random_bag = []
|
||||||
var playing = true
|
var playing = true
|
||||||
|
|
||||||
var level = 0
|
var level = 0
|
||||||
var goal = 0
|
var goal = 0
|
||||||
var score = 0
|
var score = 0
|
||||||
@ -84,7 +91,6 @@ func new_piece():
|
|||||||
current_piece.emit_trail(true)
|
current_piece.emit_trail(true)
|
||||||
autoshift_action = ""
|
autoshift_action = ""
|
||||||
update_ghost_piece()
|
update_ghost_piece()
|
||||||
$Music2.translation = SOUND_POSITION
|
|
||||||
next_piece = random_piece()
|
next_piece = random_piece()
|
||||||
next_piece.translation = NEXT_POSITION
|
next_piece.translation = NEXT_POSITION
|
||||||
if move(movements["soft_drop"]):
|
if move(movements["soft_drop"]):
|
||||||
@ -113,21 +119,21 @@ func process_actions():
|
|||||||
if action != autoshift_action:
|
if action != autoshift_action:
|
||||||
if Input.is_action_pressed(action):
|
if Input.is_action_pressed(action):
|
||||||
if move(movements[action]):
|
if move(movements[action]):
|
||||||
move_music()
|
move_midi()
|
||||||
autoshift_action = action
|
autoshift_action = action
|
||||||
$AutoShiftTimer.stop()
|
$AutoShiftTimer.stop()
|
||||||
$AutoShiftDelay.start()
|
$AutoShiftDelay.start()
|
||||||
if Input.is_action_just_pressed("hard_drop"):
|
if Input.is_action_just_pressed("hard_drop"):
|
||||||
move_music()
|
move_midi()
|
||||||
while move(movements["soft_drop"]):
|
while move(movements["soft_drop"]):
|
||||||
pass
|
pass
|
||||||
lock_piece()
|
lock_piece()
|
||||||
if Input.is_action_just_pressed("rotate_clockwise"):
|
if Input.is_action_just_pressed("rotate_clockwise"):
|
||||||
rotate(Tetromino.CLOCKWISE)
|
rotate(Tetromino.CLOCKWISE)
|
||||||
move_music()
|
move_midi()
|
||||||
if Input.is_action_just_pressed("rotate_counterclockwise"):
|
if Input.is_action_just_pressed("rotate_counterclockwise"):
|
||||||
rotate(Tetromino.COUNTERCLOCKWISE)
|
rotate(Tetromino.COUNTERCLOCKWISE)
|
||||||
move_music()
|
move_midi()
|
||||||
if Input.is_action_just_pressed("hold"):
|
if Input.is_action_just_pressed("hold"):
|
||||||
hold()
|
hold()
|
||||||
|
|
||||||
@ -154,7 +160,7 @@ func possible_positions(initial_positions, movement):
|
|||||||
position = initial_positions[i] + movement
|
position = initial_positions[i] + movement
|
||||||
if is_free_cell(position):
|
if is_free_cell(position):
|
||||||
test_positions.append(position)
|
test_positions.append(position)
|
||||||
if test_positions.size() == NB_MINOES:
|
if test_positions.size() == Tetromino.NB_MINOES:
|
||||||
return test_positions
|
return test_positions
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
@ -164,7 +170,6 @@ func move(movement):
|
|||||||
$LockDelay.start()
|
$LockDelay.start()
|
||||||
if movement.x:
|
if movement.x:
|
||||||
update_ghost_piece()
|
update_ghost_piece()
|
||||||
$Music2.translate(movement)
|
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
return false
|
return false
|
||||||
@ -177,9 +182,11 @@ func rotate(direction):
|
|||||||
else:
|
else:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
func move_music():
|
func move_midi():
|
||||||
AudioServer.set_bus_mute(AudioServer.get_bus_index("Music2"), false)
|
for channel_id in MIDI_MOVE_CHANNELS:
|
||||||
$MusicDelay.start()
|
$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():
|
func update_ghost_piece():
|
||||||
var new_positions = current_piece.positions()
|
var new_positions = current_piece.positions()
|
||||||
@ -225,10 +232,13 @@ func update_score():
|
|||||||
score += 100 * s
|
score += 100 * s
|
||||||
goal -= s
|
goal -= s
|
||||||
print(T_SPIN_NAMES[current_piece.t_spin], ' ', LINES_CLEARED_NAMES[lines_to_clear.size()], " Score ", score)
|
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:
|
if lines_to_clear.size() == Tetromino.NB_MINOES:
|
||||||
$TetrisSFX.play()
|
$MidiPlayer/LineLcearDelay.wait_time = 1.71
|
||||||
else:
|
else:
|
||||||
$LineCLearSFX.play()
|
$MidiPlayer/LineLcearDelay.wait_time = 0.86
|
||||||
|
$MidiPlayer/LineLcearDelay.start()
|
||||||
if goal <= 0:
|
if goal <= 0:
|
||||||
new_level()
|
new_level()
|
||||||
else:
|
else:
|
||||||
@ -250,7 +260,6 @@ func hold():
|
|||||||
current_piece.translation = START_POSITION
|
current_piece.translation = START_POSITION
|
||||||
current_piece.emit_trail(true)
|
current_piece.emit_trail(true)
|
||||||
update_ghost_piece()
|
update_ghost_piece()
|
||||||
$Music2.translation = SOUND_POSITION
|
|
||||||
else:
|
else:
|
||||||
held_piece = current_piece
|
held_piece = current_piece
|
||||||
new_piece()
|
new_piece()
|
||||||
@ -262,15 +271,16 @@ func resume():
|
|||||||
playing = true
|
playing = true
|
||||||
$DropTimer.start()
|
$DropTimer.start()
|
||||||
$LockDelay.start()
|
$LockDelay.start()
|
||||||
start_musics()
|
$MidiPlayer.play()
|
||||||
|
mute_midi_channel(MIDI_MOVE_CHANNELS, true)
|
||||||
|
mute_midi_channel(MIDI_LINE_CLEAR_CHANNELS, true)
|
||||||
print("RESUME")
|
print("RESUME")
|
||||||
|
|
||||||
func pause():
|
func pause():
|
||||||
playing = false
|
playing = false
|
||||||
$DropTimer.stop()
|
$DropTimer.stop()
|
||||||
$LockDelay.stop()
|
$LockDelay.stop()
|
||||||
$Music.stop()
|
$MidiPlayer.stop()
|
||||||
$Music2.stop()
|
|
||||||
print("PAUSE")
|
print("PAUSE")
|
||||||
|
|
||||||
func game_over():
|
func game_over():
|
||||||
@ -284,13 +294,12 @@ func _notification(what):
|
|||||||
if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT:
|
if what == MainLoop.NOTIFICATION_WM_FOCUS_OUT:
|
||||||
pause()
|
pause()
|
||||||
|
|
||||||
func _on_MusicDelay_timeout():
|
func mute_midi_channel(channels, muted):
|
||||||
AudioServer.set_bus_mute(AudioServer.get_bus_index("Music2"), true)
|
for channel_id in channels:
|
||||||
|
$MidiPlayer.channel_mute[channel_id] = muted
|
||||||
|
|
||||||
func _on_Music_finished():
|
func _on_MoveDelay_timeout():
|
||||||
start_musics()
|
mute_midi_channel(MIDI_MOVE_CHANNELS, true)
|
||||||
|
|
||||||
func start_musics():
|
func _on_LineLcearDelay_timeout():
|
||||||
$Music.play()
|
mute_midi_channel(MIDI_LINE_CLEAR_CHANNELS, true)
|
||||||
$Music2.play()
|
|
||||||
AudioServer.set_bus_mute(AudioServer.get_bus_index("Music2"), 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://Mino/MinoLibrary.tres" type="MeshLibrary" id=1]
|
||||||
[ext_resource path="res://GridMap/GridMap.gd" type="Script" id=2]
|
[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/GridBack.tscn" type="PackedScene" id=3]
|
||||||
[ext_resource path="res://GridMap/BackMaterial.tres" type="Material" id=4]
|
[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://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://midi/MidiPlayer.tscn" type="PackedScene" 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]
|
|
||||||
|
|
||||||
[sub_resource type="CubeMesh" id=1]
|
[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 )
|
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 0 )
|
||||||
_sections_unfolded = [ "Transform" ]
|
_sections_unfolded = [ "Transform" ]
|
||||||
|
|
||||||
[node name="Music" type="AudioStreamPlayer" parent="." index="9"]
|
[node name="MidiPlayer" parent="." index="9" instance=ExtResource( 6 )]
|
||||||
|
|
||||||
stream = ExtResource( 6 )
|
file = "res://midi/Tetris - Song A.mid"
|
||||||
volume_db = 0.0
|
volume_db = -3
|
||||||
pitch_scale = 1.0
|
loop = true
|
||||||
autoplay = false
|
soundfont = "res://midi/TimGM6mb.sf2"
|
||||||
mix_target = 0
|
|
||||||
bus = "Music"
|
|
||||||
_sections_unfolded = [ "Attenuation Filter", "Doppler", "Pause" ]
|
|
||||||
|
|
||||||
[node name="Music2" type="AudioStreamPlayer3D" parent="." index="10"]
|
[node name="MoveDelay" type="Timer" parent="MidiPlayer" index="1"]
|
||||||
|
|
||||||
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"]
|
|
||||||
|
|
||||||
process_mode = 1
|
process_mode = 1
|
||||||
wait_time = 0.1
|
wait_time = 0.1
|
||||||
one_shot = true
|
one_shot = true
|
||||||
autostart = false
|
autostart = false
|
||||||
|
|
||||||
[node name="LineCLearSFX" type="AudioStreamPlayer" parent="." index="12"]
|
[node name="LineLcearDelay" type="Timer" parent="MidiPlayer" index="2"]
|
||||||
|
|
||||||
stream = ExtResource( 8 )
|
process_mode = 1
|
||||||
volume_db = 0.0
|
wait_time = 0.86
|
||||||
pitch_scale = 1.0
|
one_shot = false
|
||||||
autoplay = false
|
autostart = 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"
|
|
||||||
|
|
||||||
[connection signal="timeout" from="DropTimer" to="." method="_on_DropTimer_timeout"]
|
[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="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
|
adjustment_saturation = 0.34
|
||||||
_sections_unfolded = [ "Adjustments", "Ambient Light", "Background", "Fog", "Resource" ]
|
_sections_unfolded = [ "Adjustments", "Ambient Light", "Background", "Fog", "Resource" ]
|
||||||
|
|
||||||
[node name="WorldEnvironment" type="WorldEnvironment"]
|
[node name="WorldEnvironment" type="WorldEnvironment" index="0"]
|
||||||
|
|
||||||
environment = SubResource( 2 )
|
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]
|
[resource]
|
||||||
|
|
||||||
@ -14,12 +31,14 @@ bus/1/mute = false
|
|||||||
bus/1/bypass_fx = false
|
bus/1/bypass_fx = false
|
||||||
bus/1/volume_db = -16.8
|
bus/1/volume_db = -16.8
|
||||||
bus/1/send = "Master"
|
bus/1/send = "Master"
|
||||||
bus/2/name = "Music2"
|
bus/2/name = "LineClear"
|
||||||
bus/2/solo = false
|
bus/2/solo = false
|
||||||
bus/2/mute = false
|
bus/2/mute = false
|
||||||
bus/2/bypass_fx = false
|
bus/2/bypass_fx = false
|
||||||
bus/2/volume_db = 5.5
|
bus/2/volume_db = 5.5
|
||||||
bus/2/send = "Master"
|
bus/2/send = "Master"
|
||||||
|
bus/2/effect/0/effect = SubResource( 1 )
|
||||||
|
bus/2/effect/0/enabled = true
|
||||||
bus/3/name = "SFX"
|
bus/3/name = "SFX"
|
||||||
bus/3/solo = false
|
bus/3/solo = false
|
||||||
bus/3/mute = 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)
|
, 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)
|
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)
|
, 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