Source code for mrcrowbar.lib.audio.vgm

from enum import IntEnum
from mrcrowbar import models as mrc


[docs] class DataBlock( mrc.Block ): compat_stop = mrc.Const( mrc.UInt8( 0x00 ), 0x66 ) data_type = mrc.UInt8( 0x01 ) length = mrc.UInt32_LE( 0x02 ) data = mrc.Bytes( 0x06, length=mrc.Ref( 'length' ) )
[docs] class MemoryWrite( mrc.Block ): compat_stop = mrc.Const( mrc.UInt8( 0x00 ), 0x66 ) chip_type = mrc.UInt8( 0x01 ) read_offset = mrc.UInt24_LE( 0x02 ) write_offset = mrc.UInt24_LE( 0x05 ) length = mrc.UInt24_LE( 0x08 )
[docs] class Blank( mrc.Block ): @property def repr( self ): return ''
[docs] class Write8( mrc.Block ): value = mrc.UInt8( 0x00 ) @property def repr( self ): return 'value=0x{:02x}'.format( self.value )
[docs] class Write16( mrc.Block ): value = mrc.UInt16_LE( 0x00 ) @property def repr( self ): return 'value=0x{:04x}'.format( self.value )
[docs] class RegisterWrite8( mrc.Block ): register = mrc.UInt8( 0x00 ) value = mrc.UInt8( 0x00 ) @property def repr( self ): return 'register=0x{:02x}, value=0x{:02x}'.format( self.register, self.value )
[docs] class Reserved8( mrc.Block ): unk1 = mrc.UInt8( 0x00 )
[docs] class Reserved16( mrc.Block ): unk1 = mrc.UInt8( 0x00 ) unk2 = mrc.UInt8( 0x01 )
[docs] class Reserved24( mrc.Block ): unk1 = mrc.UInt8( 0x00 ) unk2 = mrc.UInt8( 0x01 ) unk3 = mrc.UInt8( 0x02 )
[docs] class Reserved32( mrc.Block ): unk1 = mrc.UInt8( 0x00 ) unk2 = mrc.UInt8( 0x01 ) unk3 = mrc.UInt8( 0x02 ) unk4 = mrc.UInt8( 0x03 )
[docs] class PSGData( mrc.Block ): type = mrc.Bits( 0x00, 0b10000000 ) data_raw = mrc.Bits( 0x00, 0b01111111 ) @property def channel( self ): return None if not self.type else ((self.data_raw >> 5) & 0x3) @channel.setter def channel( self, value ): assert self.type self.data_raw &= 0b0011111 self.data_raw |= (value & 0x3) << 5 @property def control( self ): return None if not self.type else ('VOLUME' if ((self.data_raw >> 4) & 1) else 'TONE') @control.setter def control( self, value ): assert self.type self.data_raw &= 0b1101111 if value == 'VOLUME': self.data_raw |= 0b0010000 @property def data( self ): return self.data_raw & 0xf if self.type else self.data_raw & 0x3f @data.setter def data( self, value ): if self.type: self.data_raw &= 0b1110000 self.data_raw |= value & 0xf else: self.data_raw &= 0b1000000 self.data_raw |= value & 0x3f @property def repr( self ): result = 'type={}'.format( 'LATCH' if self.type else 'DATA' ) if self.type: result += ', channel={}'.format( self.channel ) result += ', control={}'.format( self.control ) result += ', data={:04b}'.format( self.data ) else: result += ', data={:06b}'.format( self.data ) return result
# source: http://www.smspower.org/uploads/Music/vgmspec170.txt COMMAND_LIST = [ ('GG_STEREO', 0x4f, Write8), ('SN76489', 0x50, PSGData), ('YM2413', 0x51, RegisterWrite8), ('YM2612_0', 0x52, RegisterWrite8), ('YM2612_1', 0x53, RegisterWrite8), ('YM2151', 0x54, RegisterWrite8), ('YM2203', 0x55, RegisterWrite8), ('YM2608_0', 0x56, RegisterWrite8), ('YM2608_1', 0x57, RegisterWrite8), ('YM2610_0', 0x58, RegisterWrite8), ('YM2610_1', 0x59, RegisterWrite8), ('YM3812', 0x5a, RegisterWrite8), ('YM3526', 0x5b, RegisterWrite8), ('Y8950', 0x5c, RegisterWrite8), ('YMZ280B', 0x5d, RegisterWrite8), ('YMF262_0', 0x5e, RegisterWrite8), ('YMF262_1', 0x5f, RegisterWrite8), ('WAIT', 0x61, Write16), ('WAIT_735_60HZ', 0x62, Blank), ('WAIT_882_50HZ', 0x63, Blank), ('END_OF_DATA', 0x66, Blank), ('DATA_BLOCK', 0x67, DataBlock), ('MEMORY_WRITE', 0x68, MemoryWrite), ] for i in range( 16 ): COMMAND_LIST.append( ( 'WAIT_{}'.format( i+1 ), 0x70+i, Blank ) ) for i in range( 16 ): COMMAND_LIST.append( ( 'YM2612_0_2A_WAIT_{}'.format( i ), 0x80+i, Blank ) ) for i in range( 0x30, 0x40 ): COMMAND_LIST.append( ( 'RESERVED_{:02X}'.format( i ), i, Reserved8 ) ) for i in range( 0x40, 0x4f ): COMMAND_LIST.append( ( 'RESERVED_{:02X}'.format( i ), i, Reserved16 ) ) for i in range( 0xa1, 0xb0 ): COMMAND_LIST.append( ( 'RESERVED_{:02X}'.format( i ), i, Reserved16 ) ) for i in range( 0xc5, 0xd0 ): COMMAND_LIST.append( ( 'RESERVED_{:02X}'.format( i ), i, Reserved24 ) ) for i in range( 0xd5, 0xe0 ): COMMAND_LIST.append( ( 'RESERVED_{:02X}'.format( i ), i, Reserved24 ) ) for i in range( 0xe1, 0x100 ): COMMAND_LIST.append( ( 'RESERVED_{:02X}'.format( i ), i, Reserved32 ) ) Command = IntEnum( 'Command', [(x[0], x[1]) for x in COMMAND_LIST] ) COMMAND_MAP = {Command(x[1]): x[2] for x in COMMAND_LIST}
[docs] class VGM150( mrc.Block ): magic = mrc.Const( mrc.Bytes( 0x00, length=0x04, default=b'Vgm ' ), b'Vgm ' ) eof_offset = mrc.UInt32_LE( 0x04 ) version = mrc.UInt32_LE( 0x08 ) sn76489_clock = mrc.UInt32_LE( 0x0c ) ym2413_clock = mrc.UInt32_LE( 0x10 ) gd3_offset = mrc.UInt32_LE( 0x14 ) total_sample_count = mrc.UInt32_LE( 0x18 ) loop_offset = mrc.UInt32_LE( 0x1c ) loop_sample_count = mrc.UInt32_LE( 0x20 ) rate = mrc.UInt32_LE( 0x24 ) sn76489_feedback = mrc.UInt16_LE( 0x28 ) sn76489_shiftwidth = mrc.UInt8( 0x2a ) sn76489_flags = mrc.UInt8( 0x2b ) ym2612_clock = mrc.UInt32_LE( 0x2c ) ym2151_clock = mrc.UInt32_LE( 0x30 ) vgm_data_offset_raw = mrc.UInt32_LE( 0x34 ) header_extra = mrc.Bytes( 0x38, length=0x08, default=b'\x00'*8 ) @property def vgm_data_offset( self ): if self.version >= 0x150: return self.vgm_data_offset_raw + 0x34 return 0x40 vgm_data = mrc.ChunkField( COMMAND_MAP, mrc.Ref( 'vgm_data_offset' ), id_field=mrc.UInt8, id_enum=Command, default_klass=mrc.Unknown, stream_end=b'\x66' ) extra = mrc.Bytes( mrc.EndOffset( 'vgm_data' ), default=b'' )