RetroPlatformer/addons/modplayer/XM.gd

288 lines
7.7 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
XM reader by あるる(きのもと 結衣) @arlez80
This script required Mod.gd for declaring the data structures.
MIT License
"""
class_name XM
enum XMNoteFlags {
NOTE = 0x01,
INSTRUMENT = 0x02,
VOLUME = 0x04,
EFFECT_COMMAND = 0x08,
EFFECT_PARAM = 0x10,
FIRST_BYTE_AS_FLAGS = 0x80,
}
"""
ファイルから読み込む
@param path ファイルパス
@return xm
"""
func read_file( path:String ) -> Mod.ModParseResult:
var f:File = File.new( )
var result: = Mod.ModParseResult.new( )
var err:int = f.open( path, f.READ )
if err != OK:
result.error = err
return result
var stream:StreamPeerBuffer = StreamPeerBuffer.new( )
stream.set_data_array( f.get_buffer( f.get_len( ) ) )
f.close( )
result.data = self._read( stream )
if result.data == null:
result.error = ERR_PARSE_ERROR
return result
"""
配列から読み込む
@param data データ
@return xm
"""
func read_data( data:PoolByteArray ) -> Mod.ModParseResult:
var stream:StreamPeerBuffer = StreamPeerBuffer.new( )
stream.set_data_array( data )
var result: = Mod.ModParseResult.new( )
result.data = self._read( stream )
if result.data == null:
result.error = ERR_PARSE_ERROR
return result
"""
ストリームから読み込み
@param stream ストリーム
@return xm
"""
func _read( stream:StreamPeerBuffer ) -> Mod.ModData:
var xm: = Mod.ModData.new( )
if self._read_string( stream, 17 ) != "Extended Module: ":
return null
xm.module_name = self._read_string( stream, 20 )
if stream.get_u8( ) != 0x1A:
return null
xm.tracker_name = self._read_string( stream, 20 )
xm.version = stream.get_u16( )
var header_size:int = stream.get_u32( )
xm.song_length = stream.get_u16( )
xm.restart_position = stream.get_u16( )
xm.channel_count = stream.get_u16( )
var pattern_count:int = stream.get_u16( )
var instrument_count:int = stream.get_u16( )
xm.flags = stream.get_u16( )
xm.init_tick = stream.get_u16( )
xm.init_bpm = stream.get_u16( )
xm.song_positions = stream.get_partial_data( header_size - 20 )[1]
xm.patterns = self._read_patterns( stream, xm.flags, pattern_count, xm.channel_count )
if len( xm.patterns ) == 0:
return null
xm.instruments = self._read_instruments( stream, instrument_count )
if len( xm.instruments ) == 0:
return null
return xm
"""
パターン読み込み
@param stream ストリーム
@param xm_file_flags XMデータに関するフラグ
@param pattern_count パターン数
@param channles 最大チャンネル数
@return パターンデータ
"""
func _read_patterns( stream:StreamPeerBuffer, xm_file_flags:int, pattern_count:int, channels:int ) -> Array:
var patterns:Array = []
for i in range( pattern_count ):
var pattern:Array = []
if stream.get_u32( ) != 9:
return []
if stream.get_u8( ) != 0:
return []
var row_count:int = stream.get_u16( )
var size:int = stream.get_u16( )
for k in range( row_count ):
var line:Array = []
for ch in range( channels ):
var patr: = Mod.ModPatternNote.new( )
var first:int = stream.get_u8( )
if first & XMNoteFlags.FIRST_BYTE_AS_FLAGS != 0:
if first & XMNoteFlags.NOTE != 0:
patr.note = stream.get_u8( )
else:
patr.note = first
first = 0xFF
if 0 < patr.note:
patr.note -= 1
if first & XMNoteFlags.INSTRUMENT != 0:
patr.instrument = stream.get_u8( )
if first & XMNoteFlags.VOLUME != 0:
patr.volume = stream.get_u8( )
if first & XMNoteFlags.EFFECT_COMMAND != 0:
patr.effect_command = stream.get_u8( )
if first & XMNoteFlags.EFFECT_PARAM != 0:
patr.effect_param = stream.get_u8( )
if 0 < patr.note:
if xm_file_flags & Mod.ModFlags.LINEAR_FREQUENCY_TABLE != 0:
patr.key_number = self._conv_linear_freq( patr.note )
else:
patr.key_number = self._conv_amiga_freq( patr.note )
line.append( patr )
pattern.append( line )
patterns.append( pattern )
return patterns
"""
ート番号からAMIGA式で周波数計算する
@param note ノート番号
@return 周波数
"""
func _conv_amiga_freq( note:int ) -> int:
return int( 6848.0 / pow( 2.0, note / 12.0 ) )
"""
ノート番号から線形で周波数計算する
@param note ノート番号
@return 周波数
"""
func _conv_linear_freq( note:int ) -> int:
return 7680 - note * 64
"""
楽器データを読み込む
@param stream ストリーム
@param instrument_count 楽器数
@return 楽器データ
"""
func _read_instruments( stream:StreamPeerBuffer, instrument_count:int ) -> Array:
var instruments:Array = []
for i in range( instrument_count ):
var inst: = Mod.ModInstrument.new( )
var size:int = stream.get_u32( )
var remain_size:int = size
inst.name = self._read_string( stream, 22 )
#print( "%d/%d %s" % [ i, instrument_count, inst.name] )
if stream.get_u8( ) != 0:
return []
var sample_count:int = stream.get_u16( )
var sample_numbers:Array = []
remain_size -= 4 + 22 + 1 + 2
if 0 < sample_count:
var sample_header_size:int = stream.get_u32( )
sample_numbers = stream.get_partial_data( 96 )[1]
inst.volume_envelope = Mod.ModEnvelope.new( )
for k in range( 12 ):
var ve: = Mod.ModEnvelopePoint.new( )
ve.frame = stream.get_u16( )
ve.value = stream.get_u16( )
inst.volume_envelope.points.append( ve )
inst.panning_envelope = Mod.ModEnvelope.new( )
for k in range( 12 ):
var pe: = Mod.ModEnvelopePoint.new( )
pe.frame = stream.get_u16( )
pe.value = stream.get_u16( )
inst.panning_envelope.points.append( pe )
remain_size -= 4 + 96 + 48 + 48
inst.volume_envelope.point_count = stream.get_u8( )
inst.panning_envelope.point_count = stream.get_u8( )
inst.volume_envelope.sustain_point = stream.get_u8( )
inst.volume_envelope.loop_start_point = stream.get_u8( )
inst.volume_envelope.loop_end_point = stream.get_u8( )
inst.panning_envelope.sustain_point = stream.get_u8( )
inst.panning_envelope.loop_start_point = stream.get_u8( )
inst.panning_envelope.loop_end_point = stream.get_u8( )
inst.volume_envelope.set_flag( stream.get_u8( ) )
inst.panning_envelope.set_flag( stream.get_u8( ) )
inst.vibrato_type = stream.get_u8( )
inst.vibrato_speed = stream.get_u8( )
inst.vibrato_depth = stream.get_u8( )
inst.vibrato_depth_shift = stream.get_u8( )
inst.volume_fadeout = stream.get_u16( )
remain_size -= 16
if 0 < remain_size:
stream.get_partial_data( remain_size ) # reserved
if 0 < sample_count:
var sounds:Array = []
# Sound Header
for k in range( sample_count ):
var xms: = Mod.ModSample.new( )
xms.length = stream.get_u32( )
xms.loop_start = stream.get_u32( )
xms.loop_length = stream.get_u32( )
xms.volume = stream.get_u8( )
xms.finetune = stream.get_8( )
xms.loop_type = stream.get_u8( )
if xms.loop_type & 16 != 0:
xms.bit = 16
xms.loop_type &= 3
xms.panning = stream.get_u8( )
xms.relative_note = stream.get_8( )
stream.get_u8( ) # Reserved
xms.name = self._read_string( stream, 22 )
sounds.append( xms )
# Sound Data
for xms in sounds:
var d:Array = []
var p:int = 0
if xms.bit == 16:
for k in range( xms.length >> 1 ):
p += stream.get_16( )
d.append( p & 0xFF )
d.append( ( p >> 8 ) & 0xFF )
else:
for k in range( xms.length ):
p += stream.get_8( )
d.append( p & 0xFF )
xms.data = PoolByteArray( d )
inst.samples = []
for k in sample_numbers:
if len( sounds ) <= k:
inst.samples.append( sounds[0] )
else:
inst.samples.append( sounds[k] )
instruments.append( inst )
return instruments
"""
文字列の読み込み
@param stream ストリーム
@param size 文字列サイズ
@return 読み込んだ文字列を返す
"""
func _read_string( stream:StreamPeerBuffer, size:int ) -> String:
return stream.get_partial_data( size )[1].get_string_from_ascii( )