mirror of
https://github.com/jwetzell/JakAudioTools.git
synced 2026-07-26 10:08:39 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3155973dec |
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jwetzell/jakaudiotools"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := &cli.Command{
|
||||
Name: "vagparser",
|
||||
Usage: "parse VAG files",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "mus",
|
||||
Value: "",
|
||||
Usage: "path to JAK MUS file",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Usage: "directory to output parsed MUS files to (if not specified, will just print names and counts)",
|
||||
Value: "",
|
||||
Required: false,
|
||||
},
|
||||
},
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
musPath := cmd.String("mus")
|
||||
musData, err := os.ReadFile(musPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read WAD file: %w", err)
|
||||
}
|
||||
|
||||
musFile, err := jakaudiotools.ParseMUS(musData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse MUS file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("found MUS %+v\n", musFile)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
if err := cmd.Run(context.Background(), os.Args); err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
package jakaudiotools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type MUSFile struct {
|
||||
Version uint32
|
||||
SubVersion uint32
|
||||
MinorVersion uint32
|
||||
a uint32
|
||||
SoundBankStart uint32
|
||||
SoundBankSize uint32
|
||||
SequenceDataStart uint32
|
||||
b uint32
|
||||
}
|
||||
|
||||
func readUint32(data []byte, offset int) uint32 {
|
||||
return uint32(data[offset]) | uint32(data[offset+1])<<8 | uint32(data[offset+2])<<16 | uint32(data[offset+3])<<24
|
||||
}
|
||||
|
||||
// MUS = namedtuple('MUS','version subversion minorversion something soundBankStart soundBankSize sequenceDataStart s3')
|
||||
// MUSStructString= "8I"
|
||||
func (mfe *MUSFile) Unmarshal(data []byte) error {
|
||||
if len(data) < 32 {
|
||||
return fmt.Errorf("data too short to unmarshal MUSFileEntry")
|
||||
}
|
||||
mfe.Version = readUint32(data, 0)
|
||||
mfe.SubVersion = readUint32(data, 4)
|
||||
mfe.MinorVersion = readUint32(data, 8)
|
||||
mfe.a = readUint32(data, 12)
|
||||
mfe.SoundBankStart = readUint32(data, 16)
|
||||
mfe.SoundBankSize = readUint32(data, 20)
|
||||
mfe.SequenceDataStart = readUint32(data, 24)
|
||||
mfe.b = readUint32(data, 28)
|
||||
return nil
|
||||
}
|
||||
|
||||
type SBV2 struct {
|
||||
Signature string
|
||||
Version uint32
|
||||
HVersion uint32
|
||||
Name string
|
||||
a uint32
|
||||
b uint16
|
||||
InstrumentCount uint16
|
||||
RegionCount uint16
|
||||
NumSample uint16
|
||||
f uint32
|
||||
InstrumentOffset uint32
|
||||
RegionOffset uint32
|
||||
i uint32
|
||||
SoundBankSize uint32
|
||||
k uint32
|
||||
l uint32
|
||||
ExtName string
|
||||
m uint16
|
||||
n uint16
|
||||
o uint16
|
||||
p uint16
|
||||
q uint32
|
||||
}
|
||||
|
||||
// #struct type 4s I I 4s I H H H H I I I I I I I 12s H H H H I
|
||||
// SBV2 = namedtuple('SBV2', 'signature version hversion name a b nMap nSMap nSample f instrumentOffset regionOffset i soundBankSize k l extname m n o p q')
|
||||
// SBV2StructString = "4s2I4sI4H7I12s4HI"
|
||||
func (sbv2 *SBV2) Unmarshal(data []byte) error {
|
||||
if len(data) < 158 {
|
||||
return fmt.Errorf("data too short to unmarshal SBV2")
|
||||
}
|
||||
sbv2.Signature = string(data[0:4])
|
||||
sbv2.Version = readUint32(data, 4)
|
||||
sbv2.HVersion = readUint32(data, 8)
|
||||
sbv2.Name = string(data[12:16])
|
||||
sbv2.a = readUint32(data, 16)
|
||||
sbv2.b = uint16(data[20]) | uint16(data[21])<<8
|
||||
sbv2.InstrumentCount = uint16(data[22]) | uint16(data[23])<<8
|
||||
sbv2.RegionCount = uint16(data[24]) | uint16(data[25])<<8
|
||||
sbv2.NumSample = uint16(data[26]) | uint16(data[27])<<8
|
||||
sbv2.f = readUint32(data, 28)
|
||||
sbv2.InstrumentOffset = readUint32(data, 32)
|
||||
sbv2.RegionOffset = readUint32(data, 36)
|
||||
sbv2.i = readUint32(data, 40)
|
||||
sbv2.SoundBankSize = readUint32(data, 44)
|
||||
sbv2.k = readUint32(data, 48)
|
||||
sbv2.l = readUint32(data, 52)
|
||||
sbv2.ExtName = string(data[56:68])
|
||||
sbv2.m = uint16(data[68]) | uint16(data[69])<<8
|
||||
sbv2.n = uint16(data[70]) | uint16(data[71])<<8
|
||||
sbv2.o = uint16(data[72]) | uint16(data[73])<<8
|
||||
sbv2.p = uint16(data[74]) | uint16(data[75])<<8
|
||||
sbv2.q = readUint32(data, 76)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Instrument struct {
|
||||
RegionCount uint8
|
||||
Volume uint8
|
||||
zero uint16
|
||||
RegionOffset uint32
|
||||
}
|
||||
|
||||
// #struct type B B H I
|
||||
// Instrument = namedtuple('Instrument','regionCount volume zero regionOffset')
|
||||
func (inst *Instrument) Unmarshal(data []byte) error {
|
||||
if len(data) < 8 {
|
||||
return fmt.Errorf("data too short to unmarshal Instrument")
|
||||
}
|
||||
inst.RegionCount = data[0]
|
||||
inst.Volume = data[1]
|
||||
inst.zero = uint16(data[2]) | uint16(data[3])<<8
|
||||
inst.RegionOffset = readUint32(data, 4)
|
||||
return nil
|
||||
}
|
||||
|
||||
// #struct type B B B B h B B H B B B B H I I
|
||||
// Region = namedtuple('Region','type volume1 a b pan c volume2 zero d e f g loopFlag sampleOffset sampleId')
|
||||
// RegionStructString = "=BBBBhBBHBBBBHII"
|
||||
type Region struct {
|
||||
Type uint8
|
||||
Volume1 uint8
|
||||
a uint8
|
||||
b uint8
|
||||
Pan int16
|
||||
c uint8
|
||||
Volume2 uint8
|
||||
zero uint16
|
||||
d uint8
|
||||
e uint8
|
||||
f uint8
|
||||
g uint8
|
||||
LoopFlag uint16
|
||||
SampleOffset uint32
|
||||
SampleId uint32
|
||||
}
|
||||
|
||||
func (r *Region) Unmarshal(data []byte) error {
|
||||
if len(data) < 24 {
|
||||
return fmt.Errorf("data too short to unmarshal Region")
|
||||
}
|
||||
r.Type = data[0]
|
||||
r.Volume1 = data[1]
|
||||
r.a = data[2]
|
||||
r.b = data[3]
|
||||
r.Pan = int16(data[4]) | int16(data[5])<<8
|
||||
r.c = data[6]
|
||||
r.Volume2 = data[7]
|
||||
r.zero = uint16(data[8]) | uint16(data[9])<<8
|
||||
r.d = data[10]
|
||||
r.e = data[11]
|
||||
r.f = data[12]
|
||||
r.g = data[13]
|
||||
r.LoopFlag = uint16(data[14]) | uint16(data[15])<<8
|
||||
r.SampleOffset = readUint32(data, 16)
|
||||
r.SampleId = readUint32(data, 20)
|
||||
return nil
|
||||
}
|
||||
|
||||
// #struct type I I I I
|
||||
// SEQ = namedtuple('SEQ','version subversion minorversion sequenceDataSize')
|
||||
// SEQStructString = "4I"
|
||||
|
||||
// #struct type 4s H H I I 4s I I I I I H B B
|
||||
// MID = namedtuple('MID','signature a b c d name e midiDataOffset g midiTempo division zero trackIndex trackCount')
|
||||
// MIDStructString = "4s2H2I4s5IH2B"
|
||||
|
||||
// #struct type 4s H B B 4s I
|
||||
// MMID = namedtuple('MMID','signature type a trackCount name zero')
|
||||
// MMIDStructString = "4sH2B4sI"
|
||||
|
||||
func ParseMUS(data []byte) (*MUSFile, error) {
|
||||
|
||||
// nameLength := 20
|
||||
// soundsStart := 24
|
||||
// soundsLength := 20
|
||||
|
||||
offset := 0
|
||||
musFile := &MUSFile{}
|
||||
err := musFile.Unmarshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal MUS file: %w", err)
|
||||
}
|
||||
offset += 32
|
||||
sbv2Start := offset
|
||||
sbv2Info := &SBV2{}
|
||||
err = sbv2Info.Unmarshal(data[offset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal MUS file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("SBV2 info: %+v\n", sbv2Info)
|
||||
|
||||
instruments := []Instrument{}
|
||||
offset = sbv2Start + int(sbv2Info.InstrumentOffset)
|
||||
for i := 0; i < int(sbv2Info.InstrumentCount); i++ {
|
||||
instrument := &Instrument{}
|
||||
err := instrument.Unmarshal(data[offset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal instrument at offset %d: %w", offset, err)
|
||||
}
|
||||
fmt.Printf("instrument %d: %+v\n", i, instrument)
|
||||
instruments = append(instruments, *instrument)
|
||||
offset += 8
|
||||
}
|
||||
fmt.Printf("found %d instruments\n", len(instruments))
|
||||
|
||||
regions := []Region{}
|
||||
|
||||
offset = sbv2Start + int(sbv2Info.RegionOffset)
|
||||
for i := 0; i < int(sbv2Info.RegionCount); i++ {
|
||||
region := &Region{}
|
||||
err := region.Unmarshal(data[offset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal region at offset %d: %w", offset, err)
|
||||
}
|
||||
fmt.Printf("region %d: %+v\n", i, region)
|
||||
regions = append(regions, *region)
|
||||
offset += 24
|
||||
}
|
||||
|
||||
// TODO(jwetzell): parse samples in regions
|
||||
|
||||
sequenceOffset := int(musFile.SequenceDataStart)
|
||||
|
||||
sequence := &Sequence{}
|
||||
err = sequence.Unmarshal(data[sequenceOffset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal sequence at offset %d: %w", sequenceOffset, err)
|
||||
}
|
||||
fmt.Printf("sequence: %+v\n", sequence)
|
||||
|
||||
offset = sequenceOffset + 16
|
||||
|
||||
midBlockType := string(data[offset : offset+4])
|
||||
fmt.Printf("mid block type: %s\n", midBlockType)
|
||||
|
||||
if midBlockType == "MID " {
|
||||
midBlock := &MID{}
|
||||
err = midBlock.Unmarshal(data[offset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal MID block at offset %d: %w", offset, err)
|
||||
}
|
||||
fmt.Printf("MID block: %+v\n", midBlock)
|
||||
// midBlocks = append(midBlocks, *midBlock)
|
||||
} else if midBlockType == "MMID" {
|
||||
mmidBlockStart := offset
|
||||
mmidBlock := &MMID{
|
||||
MIDBlocks: []MID{},
|
||||
}
|
||||
err = mmidBlock.Unmarshal(data[offset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal MMID block at offset %d: %w", offset, err)
|
||||
}
|
||||
fmt.Printf("MMID block: %+v\n", mmidBlock)
|
||||
|
||||
offset += 16
|
||||
|
||||
for i := 0; i < int(mmidBlock.TrackCount); i++ {
|
||||
blockOffset := int(readUint32(data[offset:], 0)) + mmidBlockStart
|
||||
fmt.Printf("Found MID block at offset %d\n", blockOffset)
|
||||
|
||||
midBlock := &MID{
|
||||
BlockOffset: blockOffset,
|
||||
}
|
||||
err = midBlock.Unmarshal(data[blockOffset:])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal MID block at offset %d: %w", blockOffset, err)
|
||||
}
|
||||
mmidBlock.MIDBlocks = append(mmidBlock.MIDBlocks, *midBlock)
|
||||
offset += 4
|
||||
}
|
||||
for i, midBlock := range mmidBlock.MIDBlocks {
|
||||
blockOffset := midBlock.BlockOffset
|
||||
if i != len(mmidBlock.MIDBlocks)-1 {
|
||||
nextBlockOffset := mmidBlock.MIDBlocks[i+1].BlockOffset
|
||||
midBlock.MidiData = data[blockOffset+int(midBlock.MidiDataOffset) : nextBlockOffset]
|
||||
} else {
|
||||
midBlock.MidiData = data[blockOffset+int(midBlock.MidiDataOffset):]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("unknown mid block type: %s", midBlockType)
|
||||
}
|
||||
|
||||
return musFile, nil
|
||||
}
|
||||
|
||||
// #struct type I I I I
|
||||
// SEQ = namedtuple('SEQ','version subversion minorversion sequenceDataSize')
|
||||
// SEQStructString = "4I"
|
||||
type Sequence struct {
|
||||
Version uint32
|
||||
SubVersion uint32
|
||||
MinorVersion uint32
|
||||
SequenceDataSize uint32
|
||||
}
|
||||
|
||||
func (s *Sequence) Unmarshal(data []byte) error {
|
||||
if len(data) < 16 {
|
||||
return fmt.Errorf("data too short to unmarshal Sequence")
|
||||
}
|
||||
s.Version = readUint32(data, 0)
|
||||
s.SubVersion = readUint32(data, 4)
|
||||
s.MinorVersion = readUint32(data, 8)
|
||||
s.SequenceDataSize = readUint32(data, 12)
|
||||
return nil
|
||||
}
|
||||
|
||||
// #struct type 4s H H I I 4s I I I I I H B B
|
||||
// MID = namedtuple('MID','signature a b c d name e midiDataOffset g midiTempo division zero trackIndex trackCount')
|
||||
// MIDStructString = "4s2H2I4s5IH2B"
|
||||
type MID struct {
|
||||
Signature string
|
||||
a uint16
|
||||
b uint16
|
||||
c uint32
|
||||
d uint32
|
||||
Name string
|
||||
e uint32
|
||||
MidiDataOffset uint32
|
||||
g uint32
|
||||
MidiTempo uint32
|
||||
Division uint32
|
||||
zero uint16
|
||||
TrackIndex uint8
|
||||
TrackCount uint8
|
||||
|
||||
BlockOffset int
|
||||
MidiData []byte
|
||||
}
|
||||
|
||||
func (m *MID) Unmarshal(data []byte) error {
|
||||
if len(data) < 44 {
|
||||
return fmt.Errorf("data too short to unmarshal MID")
|
||||
}
|
||||
m.Signature = string(data[0:4])
|
||||
m.a = uint16(data[4]) | uint16(data[5])<<8
|
||||
m.b = uint16(data[6]) | uint16(data[7])<<8
|
||||
m.c = readUint32(data, 8)
|
||||
m.d = readUint32(data, 12)
|
||||
m.Name = string(data[16:20])
|
||||
m.e = readUint32(data, 20)
|
||||
m.MidiDataOffset = readUint32(data, 24)
|
||||
m.g = readUint32(data, 28)
|
||||
m.MidiTempo = readUint32(data, 32)
|
||||
m.Division = readUint32(data, 36)
|
||||
m.zero = uint16(data[40]) | uint16(data[41])<<8
|
||||
m.TrackIndex = data[42]
|
||||
m.TrackCount = data[43]
|
||||
return nil
|
||||
}
|
||||
|
||||
// #struct type 4s H B B 4s I
|
||||
// MMID = namedtuple('MMID','signature type a trackCount name zero')
|
||||
// MMIDStructString = "4sH2B4sI"
|
||||
type MMID struct {
|
||||
Signature string
|
||||
Type uint16
|
||||
a uint8
|
||||
TrackCount uint8
|
||||
Name string
|
||||
zero uint32
|
||||
MIDBlocks []MID
|
||||
}
|
||||
|
||||
func (mmid *MMID) Unmarshal(data []byte) error {
|
||||
if len(data) < 16 {
|
||||
return fmt.Errorf("data too short to unmarshal MMID")
|
||||
}
|
||||
mmid.Signature = string(data[0:4])
|
||||
mmid.Type = uint16(data[4]) | uint16(data[5])<<8
|
||||
mmid.a = data[6]
|
||||
mmid.TrackCount = data[7]
|
||||
mmid.Name = string(data[8:12])
|
||||
mmid.zero = readUint32(data, 12)
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user