Files
2026-04-15 23:15:43 -05:00

378 lines
11 KiB
Go

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
}