2019-01-06 15:48:00 +01:00

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 )