Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .project
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>vgmparse</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.python.pydev.PyDevBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.python.pydev.pythonNature</nature>
</natures>
</projectDescription>
8 changes: 8 additions & 0 deletions .pydevproject
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/${PROJECT_DIR_NAME}</path>
</pydev_pathproperty>
</pydev_project>
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
A Python module for parsing [VGM (Video Game Music)](https://en.wikipedia.org/wiki/VGM_(file_format))
files. `.vgm` and `.vgz` (Gzip compressed) files are supported.

Currently, only version 1.50 of the VGM specification is supported.
Currently, only versions 1.01 and 1.50 of the VGM specification are supported.

## Installation
The `vgmparse` module can be installed directly from GitHub using `pip`:
Expand Down
Binary file not shown.
20 changes: 20 additions & 0 deletions test/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import unittest
import vgmparse

class TestParsing(unittest.TestCase):

def test_vgm_1_01(self):
with open('Alex Kidd in Miracle World - 01 - Title Screen.vgm', 'rb') as f:
file_data = f.read()
parser = vgmparse.Parser(file_data)

# The first command is a GG Stereo command
self.assertEqual(0x4F, ord(parser.command_list[0]['command']))
self.assertEqual(0xFF, ord(parser.command_list[0]['data']))

# The second command is a SN76496 Latch/Data command
self.assertEqual(0x50, ord(parser.command_list[1]['command']))
self.assertEqual(0x80, ord(parser.command_list[1]['data']))

if __name__ == '__main__':
unittest.main()
97 changes: 49 additions & 48 deletions vgmparse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,39 @@ class Parser:

# Supported VGM versions
supported_ver_list = [
0x00000101,
0x00000150,
]

# VGM metadata offsets
metadata_offsets = {
# Version 1.50
0x00000150: {
'vgm_ident': {'offset': 0x00, 'size': 4, 'type_format': None},
'eof_offset': {'offset': 0x04, 'size': 4, 'type_format': '<I'},
'version': {'offset': 0x08, 'size': 4, 'type_format': '<I'},
'sn76489_clock': {'offset': 0x0c, 'size': 4, 'type_format': '<I'},
'ym2413_clock': {'offset': 0x10, 'size': 4, 'type_format': '<I'},
'gd3_offset': {'offset': 0x14, 'size': 4, 'type_format': '<I'},
'total_samples': {'offset': 0x18, 'size': 4, 'type_format': '<I'},
'loop_offset': {'offset': 0x1c, 'size': 4, 'type_format': '<I'},
'loop_samples': {'offset': 0x20, 'size': 4, 'type_format': '<I'},
'rate': {'offset': 0x24, 'size': 4, 'type_format': '<I'},
'sn76489_feedback': {
'offset': 0x28,
'size': 2,
'type_format': '<H',
},
'sn76489_shift_register_width': {
'offset': 0x2a,
'size': 1,
'type_format': 'B',
},
'ym2612_clock': {'offset': 0x2c, 'size': 4, 'type_format': '<I'},
'ym2151_clock': {'offset': 0x30, 'size': 4, 'type_format': '<I'},
'vgm_data_offset': {
'offset': 0x34,
'size': 4,
'type_format': '<I',
},
}
'vgm_ident': {'offset': 0x00, 'size': 4, 'type_format': None},
'eof_offset': {'offset': 0x04, 'size': 4, 'type_format': '<I'},
'version': {'offset': 0x08, 'size': 4, 'type_format': '<I'},
'sn76489_clock': {'offset': 0x0c, 'size': 4, 'type_format': '<I'},
'ym2413_clock': {'offset': 0x10, 'size': 4, 'type_format': '<I'},
'gd3_offset': {'offset': 0x14, 'size': 4, 'type_format': '<I'},
'total_samples': {'offset': 0x18, 'size': 4, 'type_format': '<I'},
'loop_offset': {'offset': 0x1c, 'size': 4, 'type_format': '<I'},
'loop_samples': {'offset': 0x20, 'size': 4, 'type_format': '<I'},
'rate': {'offset': 0x24, 'size': 4, 'type_format': '<I'},
'sn76489_feedback': {
'offset': 0x28,
'size': 2,
'type_format': '<H',
},
'sn76489_shift_register_width': {
'offset': 0x2a,
'size': 1,
'type_format': 'B',
},
'ym2612_clock': {'offset': 0x2c, 'size': 4, 'type_format': '<I'},
'ym2151_clock': {'offset': 0x30, 'size': 4, 'type_format': '<I'},
'vgm_data_offset': {
'offset': 0x34,
'size': 4,
'type_format': '<I',
},
}

def __init__(self, vgm_data):
Expand Down Expand Up @@ -81,7 +79,7 @@ def parse_commands(self):
# Seek to the start of the VGM data
self.data.seek(
self.metadata['vgm_data_offset'] +
self.metadata_offsets[self.metadata['version']]['vgm_data_offset']['offset']
self.metadata_offsets['vgm_data_offset']['offset']
)

while True:
Expand Down Expand Up @@ -164,7 +162,7 @@ def parse_gd3(self):
# Seek to the start of the GD3 data
self.data.seek(
self.metadata['gd3_offset'] +
self.metadata_offsets[self.metadata['version']]['gd3_offset']['offset']
self.metadata_offsets['gd3_offset']['offset']
)

# Skip 8 bytes ('Gd3 ' string and 4 byte version identifier)
Expand Down Expand Up @@ -220,21 +218,20 @@ def parse_metadata(self):
self.metadata = {}

# Iterate over the offsets and parse the metadata
for version, offsets in self.metadata_offsets.items():
for value, offset_data in offsets.items():

# Seek to the data location and read the data
self.data.seek(offset_data['offset'])
data = self.data.read(offset_data['size'])

# Unpack the data if required
if offset_data['type_format'] is not None:
self.metadata[value] = struct.unpack(
offset_data['type_format'],
data,
)[0]
else:
self.metadata[value] = data
for value, offset_data in self.metadata_offsets.items():

# Seek to the data location and read the data
self.data.seek(offset_data['offset'])
data = self.data.read(offset_data['size'])

# Unpack the data if required
if offset_data['type_format'] is not None:
self.metadata[value] = struct.unpack(
offset_data['type_format'],
data,
)[0]
else:
self.metadata[value] = data

# Seek back to the original position in the VGM data
self.data.seek(original_pos)
Expand Down Expand Up @@ -266,4 +263,8 @@ def validate_vgm_data(self):

def validate_vgm_version(self):
if self.metadata['version'] not in self.supported_ver_list:
raise VersionError('VGM version is not supported')
raise VersionError('VGM version %s is not supported' % self.version_str())

def version_str(self):
version = self.metadata['version']
return '%01x.%02x' % (version >> 8, version & 0xFF)
Binary file added vgmparse/__pycache__/__init__.cpython-36.pyc
Binary file not shown.