635 lines
16 KiB
GDScript
635 lines
16 KiB
GDScript
"""
|
|
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 )
|