Source code for mrcrowbar.lib.audio.vgm

from __future__ import annotations

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 f"value=0x{self.value:02x}"
[docs]class Write16( mrc.Block ): value = mrc.UInt16_LE( 0x00 ) @property def repr( self ): return f"value=0x{self.value:04x}"
[docs]class RegisterWrite8( mrc.Block ): register = mrc.UInt8( 0x00 ) value = mrc.UInt8( 0x00 ) @property def repr( self ): return f"register=0x{self.register:02x}, value=0x{self.value:02x}"
[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 ): type_str = "LATCH" if self.type else "DATA" result = f"type={type_str}" if self.type: result += f", channel={self.channel}" result += f", control={self.control}" result += f", data={self.data:04b}" else: result += ", data={self.data:06b}" 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( (f"WAIT_{i + 1}", 0x70 + i, Blank) ) for i in range( 16 ): COMMAND_LIST.append( (f"YM2612_0_2A_WAIT_{i}", 0x80 + i, Blank) ) for i in range( 0x30, 0x40 ): COMMAND_LIST.append( (f"RESERVED_{i:02X}", i, Reserved8) ) for i in range( 0x40, 0x4f ): COMMAND_LIST.append( (f"RESERVED_{i:02X}", i, Reserved16) ) for i in range( 0xa1, 0xb0 ): COMMAND_LIST.append( (f"RESERVED_{i:02X}", i, Reserved16) ) for i in range( 0xc5, 0xd0 ): COMMAND_LIST.append( (f"RESERVED_{i:02X}", i, Reserved24) ) for i in range( 0xd5, 0xe0 ): COMMAND_LIST.append( (f"RESERVED_{i:02X}", i, Reserved24) ) for i in range( 0xe1, 0x100 ): COMMAND_LIST.append( (f"RESERVED_{i:02X}", 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"" )