mirror of
https://github.com/jwetzell/JakAudioTools.git
synced 2026-07-26 10:08:39 +00:00
378 lines
11 KiB
Go
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
|
|
}
|