1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2024-11-25 01:34:18 +00:00

[Audio 2/?] Extract audio samples to wav (#2020)

* [Audio 2/?] Extract audio samples to wav

Co-authored-by: zelda2774 <69368340+zelda2774@users.noreply.github.com>

* How

* Hopefully fix warning I don't get locally

* Pad default sample filenames, comment on the vadpcm frame encoder functions, other suggested changes

* Small tweaks to above

* Remove some obsolete code

---------

Co-authored-by: zelda2774 <69368340+zelda2774@users.noreply.github.com>
This commit is contained in:
Tharo 2024-08-09 03:39:18 +01:00 committed by GitHub
parent 1021c482af
commit ef329e633a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 4317 additions and 112 deletions

View file

@ -1,105 +1,105 @@
<!-- This file is only for extraction of vanilla data. For other purposes see assets/audio/samplebanks/ -->
<SampleBank Name="SampleBank_0" Index="0">
<Sample Name="SAMPLE_0_0" FileName="Sample0" Offset="0x000000" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_1" FileName="Sample1" Offset="0x0005B0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_2" FileName="Sample2" Offset="0x000780" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_3" FileName="Sample3" Offset="0x000C70" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_4" FileName="Sample4" Offset="0x001730" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_5" FileName="Sample5" Offset="0x001ED0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_6" FileName="Sample6" Offset="0x002D10" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_7" FileName="Sample7" Offset="0x004250" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_8" FileName="Sample8" Offset="0x005480" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_9" FileName="Sample9" Offset="0x0077D0" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_10" FileName="Sample10" Offset="0x0097E0" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_11" FileName="Sample11" Offset="0x00B660" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_12" FileName="Sample12" Offset="0x00BC80" SampleRate="32000" BaseNote="F3"/>
<Sample Name="SAMPLE_0_13" FileName="Sample13" Offset="0x00E4B0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_14" FileName="Sample14" Offset="0x011D80" SampleRate="24000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_15" FileName="Sample15" Offset="0x01C4A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_16" FileName="Sample16" Offset="0x027DA0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_17" FileName="Sample17" Offset="0x033800" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_18" FileName="Sample18" Offset="0x0393F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_19" FileName="Sample19" Offset="0x03D090" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_20" FileName="Sample20" Offset="0x03E360" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_21" FileName="Sample21" Offset="0x03ECE0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_22" FileName="Sample22" Offset="0x03F770" SampleRate="16000" BaseNote="G4"/>
<Sample Name="SAMPLE_0_23" FileName="Sample23" Offset="0x040000" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_24" FileName="Sample24" Offset="0x040AA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_25" FileName="Sample25" Offset="0x0414F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_26" FileName="Sample26" Offset="0x041A20" SampleRate="8000" BaseNote="G3"/>
<Sample Name="SAMPLE_0_27" FileName="Sample27" Offset="0x041CD0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_28" FileName="Sample28" Offset="0x0439A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_29" FileName="Sample29" Offset="0x046810" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_30" FileName="Sample30" Offset="0x048840" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_31" FileName="Sample31" Offset="0x04A510" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_32" FileName="Sample32" Offset="0x04A6A0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_33" FileName="Sample33" Offset="0x04F790" SampleRate="23220" BaseNote="C4"/>
<Sample Name="SAMPLE_0_34" FileName="Sample34" Offset="0x053210" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_35" FileName="Sample35" Offset="0x055BA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_36" FileName="Sample36" Offset="0x057D10" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_37" FileName="Sample37" Offset="0x05B590" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_38" FileName="Sample38" Offset="0x05CBA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_39" FileName="Sample39" Offset="0x060240" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_40" FileName="Sample40" Offset="0x061B90" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_41" FileName="Sample41" Offset="0x063350" SampleRate="16000" BaseNote="AF4"/>
<Sample Name="SAMPLE_0_42" FileName="Sample42" Offset="0x063B70" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_43" FileName="Sample43" Offset="0x064600" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_44" FileName="Sample44" Offset="0x064F70" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_45" FileName="Sample45" Offset="0x065580" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_46" FileName="Sample46" Offset="0x067FA0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_47" FileName="Sample47" Offset="0x068CC0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_48" FileName="Sample48" Offset="0x069130" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_49" FileName="Sample49" Offset="0x06A040" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_50" FileName="Sample50" Offset="0x06BA00" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_51" FileName="Sample51" Offset="0x06C3C0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_52" FileName="Sample52" Offset="0x06D5B0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_53" FileName="Sample53" Offset="0x071C70" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_54" FileName="Sample54" Offset="0x0732A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_55" FileName="Sample55" Offset="0x074790" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_56" FileName="Sample56" Offset="0x077010" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_57" FileName="Sample57" Offset="0x077BA0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_58" FileName="Sample58" Offset="0x07AFD0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_59" FileName="Sample59" Offset="0x07D290" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_60" FileName="Sample60" Offset="0x07F3F0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_61" FileName="Sample61" Offset="0x07FC90" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_62" FileName="Sample62" Offset="0x080E00" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_63" FileName="Sample63" Offset="0x082000" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_64" FileName="Sample64" Offset="0x082ED0" SampleRate="22050" BaseNote="C0"/>
<Sample Name="SAMPLE_0_65" FileName="Sample65" Offset="0x084380" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_66" FileName="Sample66" Offset="0x087030" SampleRate="22050" BaseNote="C0"/>
<Sample Name="SAMPLE_0_67" FileName="Sample67" Offset="0x087440" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_68" FileName="Sample68" Offset="0x088620" SampleRate="22050" BaseNote="C0"/>
<Sample Name="SAMPLE_0_69" FileName="Sample69" Offset="0x088A50" SampleRate="45530" BaseNote="F1"/>
<Sample Name="SAMPLE_0_70" FileName="Sample70" Offset="0x08A4B0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_71" FileName="Sample71" Offset="0x08E160" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_72" FileName="Sample72" Offset="0x08F6F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_73" FileName="Sample73" Offset="0x093DB0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_74" FileName="Sample74" Offset="0x094B10" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_75" FileName="Sample75" Offset="0x098B00" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_76" FileName="Sample76" Offset="0x09D5F0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_77" FileName="Sample77" Offset="0x0A0260" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_78" FileName="Sample78" Offset="0x0A14A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_79" FileName="Sample79" Offset="0x0A2590" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_80" FileName="Sample80" Offset="0x0A9EF0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_81" FileName="Sample81" Offset="0x0AB9E0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_82" FileName="Sample82" Offset="0x0ADBA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_83" FileName="Sample83" Offset="0x0AF0A0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_84" FileName="Sample84" Offset="0x0B0960" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_85" FileName="Sample85" Offset="0x0B3600" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_86" FileName="Sample86" Offset="0x0B3B10" SampleRate="24000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_87" FileName="Sample87" Offset="0x0B4B90" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_88" FileName="Sample88" Offset="0x0B5A80" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_89" FileName="Sample89" Offset="0x0B8690" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_90" FileName="Sample90" Offset="0x0BA0D0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_91" FileName="Sample91" Offset="0x0BBB00" SampleRate="32000" BaseNote="B1"/>
<Sample Name="SAMPLE_0_92" FileName="Sample92" Offset="0x0C42B0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_93" FileName="Sample93" Offset="0x0C5140" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_94" FileName="Sample94" Offset="0x0C88C0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_95" FileName="Sample95" Offset="0x0CAF60" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_96" FileName="Sample96" Offset="0x0D16F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_97" FileName="Sample97" Offset="0x0D2110" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_98" FileName="Sample98" Offset="0x0D3DC0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_99" FileName="Sample99" Offset="0x0D57A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_0" FileName="Sample000" Offset="0x000000" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_1" FileName="Sample001" Offset="0x0005B0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_2" FileName="Sample002" Offset="0x000780" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_3" FileName="Sample003" Offset="0x000C70" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_4" FileName="Sample004" Offset="0x001730" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_5" FileName="Sample005" Offset="0x001ED0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_6" FileName="Sample006" Offset="0x002D10" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_7" FileName="Sample007" Offset="0x004250" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_8" FileName="Sample008" Offset="0x005480" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_9" FileName="Sample009" Offset="0x0077D0" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_10" FileName="Sample010" Offset="0x0097E0" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_11" FileName="Sample011" Offset="0x00B660" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_12" FileName="Sample012" Offset="0x00BC80" SampleRate="32000" BaseNote="F3"/>
<Sample Name="SAMPLE_0_13" FileName="Sample013" Offset="0x00E4B0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_14" FileName="Sample014" Offset="0x011D80" SampleRate="24000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_15" FileName="Sample015" Offset="0x01C4A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_16" FileName="Sample016" Offset="0x027DA0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_17" FileName="Sample017" Offset="0x033800" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_18" FileName="Sample018" Offset="0x0393F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_19" FileName="Sample019" Offset="0x03D090" SampleRate="32000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_20" FileName="Sample020" Offset="0x03E360" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_21" FileName="Sample021" Offset="0x03ECE0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_22" FileName="Sample022" Offset="0x03F770" SampleRate="16000" BaseNote="G4"/>
<Sample Name="SAMPLE_0_23" FileName="Sample023" Offset="0x040000" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_24" FileName="Sample024" Offset="0x040AA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_25" FileName="Sample025" Offset="0x0414F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_26" FileName="Sample026" Offset="0x041A20" SampleRate="8000" BaseNote="G3"/>
<Sample Name="SAMPLE_0_27" FileName="Sample027" Offset="0x041CD0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_28" FileName="Sample028" Offset="0x0439A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_29" FileName="Sample029" Offset="0x046810" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_30" FileName="Sample030" Offset="0x048840" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_31" FileName="Sample031" Offset="0x04A510" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_32" FileName="Sample032" Offset="0x04A6A0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_33" FileName="Sample033" Offset="0x04F790" SampleRate="23220" BaseNote="C4"/>
<Sample Name="SAMPLE_0_34" FileName="Sample034" Offset="0x053210" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_35" FileName="Sample035" Offset="0x055BA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_36" FileName="Sample036" Offset="0x057D10" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_37" FileName="Sample037" Offset="0x05B590" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_38" FileName="Sample038" Offset="0x05CBA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_39" FileName="Sample039" Offset="0x060240" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_40" FileName="Sample040" Offset="0x061B90" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_41" FileName="Sample041" Offset="0x063350" SampleRate="16000" BaseNote="AF4"/>
<Sample Name="SAMPLE_0_42" FileName="Sample042" Offset="0x063B70" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_43" FileName="Sample043" Offset="0x064600" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_44" FileName="Sample044" Offset="0x064F70" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_45" FileName="Sample045" Offset="0x065580" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_46" FileName="Sample046" Offset="0x067FA0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_47" FileName="Sample047" Offset="0x068CC0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_48" FileName="Sample048" Offset="0x069130" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_49" FileName="Sample049" Offset="0x06A040" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_50" FileName="Sample050" Offset="0x06BA00" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_51" FileName="Sample051" Offset="0x06C3C0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_52" FileName="Sample052" Offset="0x06D5B0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_53" FileName="Sample053" Offset="0x071C70" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_54" FileName="Sample054" Offset="0x0732A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_55" FileName="Sample055" Offset="0x074790" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_56" FileName="Sample056" Offset="0x077010" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_57" FileName="Sample057" Offset="0x077BA0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_58" FileName="Sample058" Offset="0x07AFD0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_59" FileName="Sample059" Offset="0x07D290" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_60" FileName="Sample060" Offset="0x07F3F0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_61" FileName="Sample061" Offset="0x07FC90" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_62" FileName="Sample062" Offset="0x080E00" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_63" FileName="Sample063" Offset="0x082000" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_64" FileName="Sample064" Offset="0x082ED0" SampleRate="22050" BaseNote="C0"/>
<Sample Name="SAMPLE_0_65" FileName="Sample065" Offset="0x084380" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_66" FileName="Sample066" Offset="0x087030" SampleRate="22050" BaseNote="C0"/>
<Sample Name="SAMPLE_0_67" FileName="Sample067" Offset="0x087440" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_68" FileName="Sample068" Offset="0x088620" SampleRate="22050" BaseNote="C0"/>
<Sample Name="SAMPLE_0_69" FileName="Sample069" Offset="0x088A50" SampleRate="45530" BaseNote="F1"/>
<Sample Name="SAMPLE_0_70" FileName="Sample070" Offset="0x08A4B0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_71" FileName="Sample071" Offset="0x08E160" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_72" FileName="Sample072" Offset="0x08F6F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_73" FileName="Sample073" Offset="0x093DB0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_74" FileName="Sample074" Offset="0x094B10" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_75" FileName="Sample075" Offset="0x098B00" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_76" FileName="Sample076" Offset="0x09D5F0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_77" FileName="Sample077" Offset="0x0A0260" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_78" FileName="Sample078" Offset="0x0A14A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_79" FileName="Sample079" Offset="0x0A2590" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_80" FileName="Sample080" Offset="0x0A9EF0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_81" FileName="Sample081" Offset="0x0AB9E0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_82" FileName="Sample082" Offset="0x0ADBA0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_83" FileName="Sample083" Offset="0x0AF0A0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_84" FileName="Sample084" Offset="0x0B0960" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_85" FileName="Sample085" Offset="0x0B3600" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_86" FileName="Sample086" Offset="0x0B3B10" SampleRate="24000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_87" FileName="Sample087" Offset="0x0B4B90" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_88" FileName="Sample088" Offset="0x0B5A80" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_89" FileName="Sample089" Offset="0x0B8690" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_90" FileName="Sample090" Offset="0x0BA0D0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_91" FileName="Sample091" Offset="0x0BBB00" SampleRate="32000" BaseNote="B1"/>
<Sample Name="SAMPLE_0_92" FileName="Sample092" Offset="0x0C42B0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_93" FileName="Sample093" Offset="0x0C5140" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_94" FileName="Sample094" Offset="0x0C88C0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_95" FileName="Sample095" Offset="0x0CAF60" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_96" FileName="Sample096" Offset="0x0D16F0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_97" FileName="Sample097" Offset="0x0D2110" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_98" FileName="Sample098" Offset="0x0D3DC0" SampleRate="16000" BaseNote="C4"/>
<Sample Name="SAMPLE_0_99" FileName="Sample099" Offset="0x0D57A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_100" FileName="Sample100" Offset="0x0DE0A0" SampleRate="22050" BaseNote="C4"/>
<Sample Name="SAMPLE_0_101" FileName="Sample101" Offset="0x0E01F0" SampleRate="16000" BaseNote="C0"/>
<Sample Name="SAMPLE_0_102" FileName="Sample102" Offset="0x0E2510" SampleRate="22050" BaseNote="C4"/>

View file

@ -20,13 +20,16 @@ endif
all: $(PROGRAMS)
$(MAKE) -C ZAPD
$(MAKE) -C fado
$(MAKE) -C audio
clean:
$(RM) $(PROGRAMS) $(addsuffix .exe,$(PROGRAMS))
$(MAKE) -C ZAPD clean
$(MAKE) -C fado clean
$(MAKE) -C audio clean
distclean: clean
$(MAKE) -C audio distclean
.PHONY: all clean distclean

14
tools/audio/Makefile Normal file
View file

@ -0,0 +1,14 @@
.PHONY: all clean distclean format
all:
$(MAKE) -C sampleconv
clean:
$(MAKE) -C sampleconv clean
distclean: clean
$(MAKE) -C sampleconv distclean
format:
$(MAKE) -C sampleconv format

View file

@ -4,17 +4,18 @@
# Extract audio files
#
import os
import os, shutil, time
from dataclasses import dataclass
from enum import auto, Enum
from multiprocessing.pool import ThreadPool
from typing import Dict, List, Tuple, Union
from xml.etree import ElementTree
from xml.etree.ElementTree import Element
from .audio_tables import AudioCodeTable, AudioCodeTableEntry, AudioStorageMedium
from .audiotable import AudioTableFile
from .audiotable import AudioTableData, AudioTableFile, AudioTableSample
from .audiobank_file import AudiobankFile
from .util import align, debugm, error, incbin
from .util import align, debugm, error, incbin, program_get
class MMLVersion(Enum):
OOT = auto()
@ -114,13 +115,34 @@ def collect_soundfonts(audiobank_seg : memoryview, extracted_dir : str, version_
return soundfonts
def extract_samplebank(extracted_dir : str, sample_banks : List[Union[AudioTableFile, int]], bank : AudioTableFile,
write_xml : bool):
def aifc_extract_one_sample(base_path : str, sample : AudioTableSample):
aifc_path = f"{base_path}/aifc/{sample.filename}"
ext_compressed = sample.codec_file_extension_compressed()
ext_decompressed = sample.codec_file_extension_decompressed()
wav_path = f"{base_path}/{sample.filename.replace(ext_compressed, ext_decompressed)}"
# export to AIFC
sample.to_file(aifc_path)
# decode to AIFF/WAV
program_get(f"{SAMPLECONV_PATH} --matching pcm16 {aifc_path} {wav_path}")
def aifc_extract_one_bin(base_path : str, sample : AudioTableData):
# export to BIN
if BASEROM_DEBUG:
sample.to_file(f"{base_path}/aifc/{sample.filename}")
# copy to correct location
shutil.copyfile(f"{base_path}/aifc/{sample.filename}", f"{base_path}/{sample.filename}")
else:
sample.to_file(f"{base_path}/{sample.filename}")
def extract_samplebank(pool : ThreadPool, extracted_dir : str, sample_banks : List[Union[AudioTableFile, int]],
bank : AudioTableFile, write_xml : bool):
# deal with remaining gaps, have to blob them unless we can find an exact match in another bank
bank.finalize_coverage(sample_banks)
# assign names
bank.assign_names()
base_path = f"{extracted_dir}/assets/audio/samples/{bank.name}"
# write xml
with open(f"{extracted_dir}/assets/audio/samplebanks/{bank.file_name}.xml", "w") as outfile:
outfile.write(bank.to_xml(f"assets/audio/samples/{bank.name}"))
@ -129,6 +151,31 @@ def extract_samplebank(extracted_dir : str, sample_banks : List[Union[AudioTable
if write_xml:
bank.write_extraction_xml(f"assets/xml/audio/samplebanks/{bank.file_name}.xml")
# write sample sand blobs
os.makedirs(f"{base_path}/aifc", exist_ok=True)
aifc_samples = [sample for sample in bank.samples_final if isinstance(sample, AudioTableSample)]
bin_samples = [sample for sample in bank.samples_final if not isinstance(sample, AudioTableSample)]
t_start = time.time()
# we assume the number of bin samples are very small and don't multiprocess it
for sample in bin_samples:
aifc_extract_one_bin(base_path, sample)
# multiprocess aifc extraction + decompression
async_results = [pool.apply_async(aifc_extract_one_sample, args=(base_path, sample)) for sample in aifc_samples]
# block until done
[res.get() for res in async_results]
dt = time.time() - t_start
print(f"Samplebank {bank.name} extraction took {dt:.3f}s")
# drop aifc dir if not in debug mode
if not BASEROM_DEBUG:
shutil.rmtree(f"{base_path}/aifc")
def extract_audio_for_version(version_info : GameVersionInfo, extracted_dir : str, read_xml : bool, write_xml : bool):
print("Setting up...")
@ -234,13 +281,17 @@ def extract_audio_for_version(version_info : GameVersionInfo, extracted_dir : st
print("Extracting samplebanks...")
# Check that the sampleconv binary is available
assert os.path.isfile(SAMPLECONV_PATH) , "Compile sampleconv"
os.makedirs(f"{extracted_dir}/assets/audio/samplebanks", exist_ok=True)
if write_xml:
os.makedirs(f"assets/xml/audio/samplebanks", exist_ok=True)
for bank in sample_banks:
if isinstance(bank, AudioTableFile):
extract_samplebank(extracted_dir, sample_banks, bank, write_xml)
with ThreadPool(processes=os.cpu_count()) as pool:
for bank in sample_banks:
if isinstance(bank, AudioTableFile):
extract_samplebank(pool, extracted_dir, sample_banks, bank, write_xml)
# ==================================================================================================================
# Extract soundfonts

View file

@ -438,8 +438,8 @@ class AudiobankFile:
self.coverage.append([[unref_start_offset, Padding], [unref_end_offset, Padding]])
continue
coverage_log(f"Unaccounted: 0x{unref_start_offset:X}({unref_start_type.__name__}) " + \
f"to 0x{unref_end_offset:X}({unref_end_type.__name__})")
coverage_log(f"Unaccounted: 0x{unref_start_offset:04X}({unref_start_type.__name__}) " + \
f"to 0x{unref_end_offset:04X}({unref_end_type.__name__})")
coverage_log([f"0x{b:02X}" for b in unaccounted_data])
try:

View file

@ -4,7 +4,7 @@
#
#
import struct
import math, struct
from typing import Dict, Tuple
from xml.etree.ElementTree import Element
@ -474,7 +474,9 @@ class AudioTableFile:
if sample.start in self.extraction_sample_info:
return self.extraction_sample_info[sample.start]["FileName"] + ext
print(f"WARNING: Missing extraction xml entry for sample at offset=0x{sample.start:X}")
return f"Sample{index}{ext}"
npad = int(math.floor(1 + math.log10(len(self.samples)))) if len(self.samples) != 0 else 0
return f"Sample{index:0{npad}}{ext}"
def blob_filename(self, start, end):
if self.extraction_blob_info is not None:

View file

@ -0,0 +1,29 @@
IndentWidth: 4
Language: Cpp
UseTab: Never
ColumnLimit: 120
PointerAlignment: Right
BreakBeforeBraces: Linux
AlwaysBreakAfterReturnType: TopLevel
AlignArrayOfStructures: Left
SpaceAfterCStyleCast: false
SpaceBeforeParens: ControlStatementsExceptControlMacros
Cpp11BracedListStyle: false
IndentCaseLabels: true
BinPackArguments: true
BinPackParameters: true
AlignAfterOpenBracket: Align
AlignOperands: true
BreakBeforeTernaryOperators: true
BreakBeforeBinaryOperators: None
AllowShortBlocksOnASingleLine: true
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AlignEscapedNewlines: Left
AlignTrailingComments: true
SortIncludes: false
AlignConsecutiveMacros: Consecutive
ForEachMacros: ['LL_FOREACH']

2
tools/audio/sampleconv/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build/
sampleconv

View file

@ -0,0 +1,36 @@
CC := gcc
CFLAGS := -Wall -Wextra -MMD
OPTFLAGS := -Og -g3
LDFLAGS :=
CLANG_FORMAT := clang-format-14
FORMAT_ARGS := -i -style=file
SRC_DIRS := $(shell find src -type d)
C_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c))
O_FILES := $(foreach f,$(C_FILES:.c=.o),build/$f)
DEP_FILES := $(foreach f,$(C_FILES:.c=.d),build/$f)
$(shell mkdir -p build $(foreach dir,$(SRC_DIRS),build/$(dir)))
.PHONY: all clean distclean format
all: sampleconv
clean:
$(RM) -rf build sampleconv
distclean: clean
format:
$(CLANG_FORMAT) $(FORMAT_ARGS) $(C_FILES) $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.h))
sampleconv: $(O_FILES)
$(CC) $(LDFLAGS) $(O_FILES) -lm -o $@
build/src/%.o: src/%.c
$(CC) -c $(CFLAGS) $(OPTFLAGS) $< -o $@
-include $(DEP_FILES)

View file

@ -0,0 +1,38 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef CODEC_H_
#define CODEC_H_
#include <stdint.h>
#include "../container/container.h"
#include "vadpcm.h"
#include "uncompressed.h"
typedef struct enc_dec_opts {
// Matching
bool matching;
// VADPCM options
bool truncate;
uint32_t min_loop_length;
table_design_spec design;
} enc_dec_opts;
typedef struct codec_spec {
const char *name;
sample_data_type type;
int frame_size;
bool compressed;
int (*decode)(container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int (*encode)(container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
} codec_spec;
#endif

View file

@ -0,0 +1,56 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include "../util.h"
#include "codec.h"
#include "../container/container.h"
int
pcm16_enc_dec(UNUSED container_data *ctnr, UNUSED const codec_spec *codec, UNUSED const enc_dec_opts *opts)
{
// Since we decode to and encode from pcm16, there's nothing to do.
return 0;
}
// TODO
int
pcm8_dec(UNUSED container_data *ctnr, UNUSED const codec_spec *codec, UNUSED const enc_dec_opts *opts)
{
#if 0
for (size_t i = 0; i < num_samples; i++) {
uint8_t insamp = ((uint8_t *)in)[i];
int16_t outsamp = insamp << 8; // - 0x80 before shift ?
((int16_t *)out)[i] = outsamp;
}
#endif
return 0;
}
// TODO
int
pcm8_enc(UNUSED container_data *ctnr, UNUSED const codec_spec *codec, UNUSED const enc_dec_opts *opts)
{
#if 0
for (size_t i = 0; i < num_samples; i++) {
uint16_t insamp = ((uint16_t *)in)[i];
uint8_t outsamp = insamp >> 8;
((uint8_t *)out)[i] = outsamp;
}
#endif
return 0;
}

View file

@ -0,0 +1,21 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef CODEC_UNCOMPRESSED_H
#define CODEC_UNCOMPRESSED_H
int
pcm16_dec(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int
pcm16_enc(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int
pcm16_enc_dec(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: CC0-1.0
*/
#ifndef CODEC_VADPCM_H
#define CODEC_VADPCM_H
#include <stdbool.h>
#include <stdint.h>
#define VADPCM_BOOK_SIZE(order, npredictors) (8 * (order) * (npredictors))
#define VADPCM_BOOK_SIZE_BYTES(order, npredictors) (sizeof(int16_t) * VADPCM_BOOK_SIZE(order, npredictors))
typedef struct {
int16_t order;
int16_t npredictors;
} ALADPCMbookhead;
typedef int16_t ALADPCMbookstate[];
typedef struct {
uint32_t start;
uint32_t end;
uint32_t count;
int16_t state[16];
} ALADPCMloop;
typedef struct {
unsigned int order;
unsigned int bits;
unsigned int refine_iters;
double thresh;
unsigned int frame_size;
} table_design_spec;
int
tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_data_out, void *sample_data,
size_t num_samples, const table_design_spec *design);
struct container_data;
struct codec_spec;
struct enc_dec_opts;
int
vadpcm_enc(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
int
vadpcm_dec(struct container_data *ctnr, const struct codec_spec *codec, const struct enc_dec_opts *opts);
#endif

View file

@ -0,0 +1,655 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "../util.h"
#include "vadpcm.h"
// Levinson-Durbin algorithm for iteratively solving for prediction coefficients
// https://en.wikipedia.org/wiki/Levinson_recursion
static int
durbin(double *acvec, int order, double *reflection_coeffs, double *prediction_coeffs, double *error)
{
int i, j;
double sum, E;
int ret;
prediction_coeffs[0] = 1.0;
E = acvec[0]; // E[0] = r{xx}[0]
ret = 0;
for (i = 1; i <= order; i++) {
// SUM(j, a[i-1][j] * r{xx}[i - j] )
sum = 0.0;
for (j = 1; j <= i - 1; j++) {
sum += prediction_coeffs[j] * acvec[i - j];
}
// a[i][i] = -Delta[i-1] / E[i-1]
prediction_coeffs[i] = (E > 0.0) ? (-(acvec[i] + sum) / E) : 0.0;
// k[i] = a[i][i]
reflection_coeffs[i] = prediction_coeffs[i];
if (fabs(reflection_coeffs[i]) > 1.0) {
// incr when a predictor coefficient is > 1 (indicates numerical instability)
ret++;
}
for (j = 1; j < i; j++) {
// a[i][j] = a[i-1][j] + a[i-1][i - j] * a[i][i]
prediction_coeffs[j] += prediction_coeffs[i - j] * prediction_coeffs[i];
}
// E[i] = E[i-1] * (1.0 - k[i] ** 2)
// = E[i-1] * (1.0 - a[i][i] ** 2)
E *= 1.0 - prediction_coeffs[i] * prediction_coeffs[i];
}
*error = E;
return ret;
}
// Reflection coefficients (k) -> Predictor coefficients (a)
// A subset of Levinson-Durbin that only computes predictors from known reflection coefficients and previous predictors
static void
afromk(double *k, double *ai, int order)
{
int i, j;
ai[0] = 1.0;
for (i = 1; i <= order; i++) {
// a[i][i] = k[i]
ai[i] = k[i];
for (j = 1; j <= i - 1; j++) {
// a[i][j] = a[i-1][j] + a[i-1][i - j] * k[i] = a[i-1][j] + a[i-1][i - j] * a[i][i]
ai[j] += ai[i - j] * ai[i];
}
}
}
// Prediction coefficients (a) -> Reflection coefficients (k)
// Performs afromk in reverse?
// Returns 0 if numerically stable, otherwise returns non-zero
static int
kfroma(double *in, double *out, int order)
{
int i, j;
double div;
double temp;
double next[(order + 1)];
int ret = 0;
out[order] = in[order];
for (i = order - 1; i >= 1; i--) {
for (j = 0; j <= i; j++) {
temp = out[i + 1];
div = 1.0 - temp * temp;
if (div == 0.0) {
return 1;
}
next[j] = (in[j] - in[i - j + 1] * temp) / div;
}
for (j = 0; j <= i; j++) {
in[j] = next[j];
}
out[i] = next[i];
if (fabs(out[i]) > 1.0) {
// Not numerically stable
ret++;
}
}
return ret;
}
// autocorrelation (r{xx}) from predictors (a) ?
static void
rfroma(double *in, int n, double *out)
{
int i, j;
double mat[n + 1][n + 1];
double div;
mat[n][0] = 1.0;
for (i = 1; i <= n; i++) {
mat[n][i] = -in[i];
}
for (i = n; i >= 1; i--) {
div = 1.0 - mat[i][i] * mat[i][i];
for (j = 1; j <= i - 1; j++) {
mat[i - 1][j] = (mat[i][i - j] * mat[i][i] + mat[i][j]) / div;
}
}
out[0] = 1.0;
for (i = 1; i <= n; i++) {
out[i] = 0.0;
for (j = 1; j <= i; j++) {
out[i] += mat[i][j] * out[i - j];
}
}
}
static double
model_dist(double *predictors, double *data, int order)
{
double autocorrelation_data[order + 1];
double autocorrelation_predictors[order + 1];
double ret;
int i, j;
// autocorrelation from data
rfroma(data, order, autocorrelation_data);
// autocorrelation from predictors
for (i = 0; i <= order; i++) {
autocorrelation_predictors[i] = 0.0;
for (j = 0; j <= order - i; j++) {
autocorrelation_predictors[i] += predictors[j] * predictors[i + j];
}
}
// compute "model distance" (scaled L2 norm: 2 * inner(ac1, ac2) )
ret = autocorrelation_data[0] * autocorrelation_predictors[0];
for (i = 1; i <= order; i++) {
ret += 2 * autocorrelation_data[i] * autocorrelation_predictors[i];
}
return ret;
}
// Calculate the autocorrelation matrix of two vectors at x and x - xlen
// https://en.wikipedia.org/wiki/Autocorrelation
static void
acmat(int16_t *x, int order, int xlen, double **ac)
{
int i, j, k;
for (i = 1; i <= order; i++) {
for (j = 1; j <= order; j++) {
// R{xx}[i,j] = E[X[i] * X[j]]
ac[i][j] = 0.0;
for (k = 0; k < xlen; k++) {
ac[i][j] += x[k - i] * x[k - j];
}
}
}
}
// Computes the autocorrelation vector of two vectors at x and x - xlen
static void
acvect(int16_t *x, int order, int xlen, double *ac)
{
int i, j;
for (i = 0; i <= order; i++) {
ac[i] = 0.0;
// r{xx} = E(x(m)x) = SUM(j, x[j - i] * x[j])
for (j = 0; j < xlen; j++) {
ac[i] -= x[j - i] * x[j];
}
}
}
/**
* Lower-Upper (with Permutation vector) (LUP) Decomposition
*
* Replaces a real n-by-n matrix "a" with the LU decomposition of a row-wise
* permutation of itself.
*
* Input parameters:
* a: The matrix which is operated on. 1-indexed; it should be of size
* (n+1) x (n+1), and row/column index 0 is not used.
* n: The size of the matrix.
*
* Output parameters:
* indx: The row permutation performed. 1-indexed; it should be of size n+1,
* and index 0 is not used.
* d: the determinant of the permutation matrix.
*
* Returns 1 to indicate failure if the matrix is singular or has zeroes on the
* diagonal, 0 on success.
*
* Derived from ludcmp in "Numerical Recipes in C: The Art of Scientific Computing",
* with modified error handling.
*/
static int
lud(double **a, int n, int *perm, int *d)
{
int i, imax = 0, j, k;
double big, dum, sum, temp;
double min, max;
double vv[n + 1];
*d = 1;
for (i = 1; i <= n; i++) {
big = 0.0;
for (j = 1; j <= n; j++)
if ((temp = fabs(a[i][j])) > big)
big = temp;
if (big == 0.0)
return 1;
vv[i] = 1.0 / big;
}
for (j = 1; j <= n; j++) {
for (i = 1; i < j; i++) {
sum = a[i][j];
for (k = 1; k < i; k++)
sum -= a[i][k] * a[k][j];
a[i][j] = sum;
}
big = 0.0;
for (i = j; i <= n; i++) {
sum = a[i][j];
for (k = 1; k < j; k++)
sum -= a[i][k] * a[k][j];
a[i][j] = sum;
if ((dum = vv[i] * fabs(sum)) >= big) {
big = dum;
imax = i;
}
}
if (j != imax) {
for (k = 1; k <= n; k++) {
dum = a[imax][k];
a[imax][k] = a[j][k];
a[j][k] = dum;
}
*d = -(*d);
vv[imax] = vv[j];
}
perm[j] = imax;
if (a[j][j] == 0.0)
return 1;
if (j != n) {
dum = 1.0 / (a[j][j]);
for (i = j + 1; i <= n; i++)
a[i][j] *= dum;
}
}
min = 1e10;
max = 0.0;
for (i = 1; i <= n; i++) {
temp = fabs(a[i][i]);
if (temp < min)
min = temp;
if (temp > max)
max = temp;
}
return (min / max < 1e-10) ? 1 : 0;
}
/**
* Solves the set of n linear equations Ax = b, using LU decomposition back-substitution.
*
* Input parameters:
* a: The LU decomposition of a matrix, created by "lud".
* n: The size of the matrix.
* indx: Row permutation vector, created by "lud".
* b: The vector b in the equation. 1-indexed; is should be of size n+1, and index 0 is not used.
*
* Output parameters:
* b: The output vector x. 1-indexed.
*
* From "Numerical Recipes in C: The Art of Scientific Computing".
*/
static void
lubksb(double **a, int n, int *perm, double *b)
{
int i, ii = 0, ip, j;
double sum;
for (i = 1; i <= n; i++) {
ip = perm[i];
sum = b[ip];
b[ip] = b[i];
if (ii) {
for (j = ii; j <= i - 1; j++)
sum -= a[i][j] * b[j];
} else if (sum) {
ii = i;
}
b[i] = sum;
}
for (i = n; i >= 1; i--) {
sum = b[i];
for (j = i + 1; j <= n; j++)
sum -= a[i][j] * b[j];
b[i] = sum / a[i][i];
}
}
static void
split(double **predictors, double *delta, int order, int npredictors, double scale)
{
int i, j;
for (i = 0; i < npredictors; i++) {
for (j = 0; j <= order; j++) {
predictors[i + npredictors][j] = predictors[i][j] + delta[j] * scale;
}
}
}
static void
refine(double **predictors, int order, int npredictors, double *data, int data_size, int refine_iters)
{
int iter;
double dist;
double dummy;
double best_value;
int best_index;
int i, j;
double rsums[npredictors][order + 1];
int counts[npredictors];
double vec[order + 1];
for (iter = 0; iter < refine_iters; iter++) {
// For some number of refinement iterations
// Initialize averages
memset(counts, 0, npredictors * sizeof(int));
memset(rsums, 0, npredictors * (order + 1) * sizeof(double));
// Sum autocorrelations
for (i = 0; i < data_size; i++) {
best_value = 1e30;
best_index = 0;
// Find index that minimizes the "model distance"
for (j = 0; j < npredictors; j++) {
dist = model_dist(predictors[j], &data[(order + 1) * i], order);
if (dist < best_value) {
best_value = dist;
best_index = j;
}
}
counts[best_index]++;
rfroma(&data[(order + 1) * i], order, vec); // compute autocorrelation from predictors
for (j = 0; j <= order; j++)
rsums[best_index][j] += vec[j]; // add to average autocorrelation
}
// finalize average autocorrelations
for (i = 0; i < npredictors; i++) {
if (counts[i] > 0) {
for (j = 0; j <= order; j++) {
rsums[i][j] /= counts[i];
}
}
}
for (i = 0; i < npredictors; i++) {
// compute predictors from average autocorrelation
durbin(rsums[i], order, vec, predictors[i], &dummy);
// vec is reflection coeffs
// clamp reflection coeffs
for (j = 1; j <= order; j++) {
if (vec[j] >= 1.0)
vec[j] = 0.9999999999;
if (vec[j] <= -1.0)
vec[j] = -0.9999999999;
}
// clamped reflection coeffs -> predictors
afromk(vec, predictors[i], order);
}
}
}
static int
read_row(int16_t *p, double *row, int order)
{
double fval;
int ival;
int i, j, k;
int overflows;
double table[8][order];
for (i = 0; i < order; i++) {
for (j = 0; j < i; j++)
table[i][j] = 0.0;
for (j = i; j < order; j++)
table[i][j] = -row[order - j + i];
}
for (i = order; i < 8; i++)
for (j = 0; j < order; j++)
table[i][j] = 0.0;
for (i = 1; i < 8; i++)
for (j = 1; j <= order; j++)
if (i - j >= 0)
for (k = 0; k < order; k++)
table[i][k] -= row[j] * table[i - j][k];
overflows = 0;
for (i = 0; i < order; i++) {
for (j = 0; j < 8; j++) {
fval = table[j][i] * (double)(1 << 11);
if (fval < 0.0) {
ival = (int)(fval - 0.5);
if (ival < -0x8000)
overflows++;
} else {
ival = (int)(fval + 0.5);
if (ival >= 0x8000)
overflows++;
}
*(p++) = ival;
}
}
return overflows;
}
int
tabledesign_run(int16_t *order_out, int16_t *npredictors_out, int16_t **book_data_out, void *sample_data,
size_t num_samples, const table_design_spec *design)
{
static const table_design_spec default_design = {
.order = 2,
.bits = 2,
.refine_iters = 2,
.thresh = 10.0,
.frame_size = 16,
};
if (design == NULL)
design = &default_design;
int16_t order = design->order;
int16_t npredictors = 1 << design->bits;
unsigned int frame_size = design->frame_size;
int num_order = order + 1;
double vec[num_order];
int perm[num_order];
double reflection_coeffs[num_order];
int16_t *buffer = MALLOC_CHECKED_INFO(2 * frame_size * sizeof(int16_t), "frame_size=%u", frame_size);
double **predictors = MALLOC_CHECKED_INFO(npredictors * sizeof(double *), "npredictors=%d", npredictors);
for (int i = 0; i < npredictors; i++)
predictors[i] = MALLOC_CHECKED_INFO(num_order * sizeof(double), "npredictors=%d", npredictors);
double **autocorrelation_matrix = MALLOC_CHECKED_INFO(num_order * sizeof(double *), "num_order=%d", num_order);
for (int i = 0; i < num_order; i++)
autocorrelation_matrix[i] = MALLOC_CHECKED_INFO(num_order * sizeof(double), "num_order=%d", num_order);
size_t nframes = num_samples - (num_samples % frame_size);
double *data =
MALLOC_CHECKED_INFO(nframes * num_order * sizeof(double), "nframes=%lu, num_order=%d", nframes, num_order);
uint32_t data_size = 0;
int16_t *sample = sample_data;
// (back-)align to a multiple of the frame size
int16_t *sample_end = sample + nframes;
memset(buffer, 0, frame_size * sizeof(int16_t));
for (; sample < sample_end; sample += frame_size) {
// Copy sample data into second half of buffer, during the first iteration the first half is 0 while in
// later iterations the second half of the previous iteration is shifted into the first half.
memcpy(&buffer[frame_size], sample, frame_size * sizeof(int16_t));
// Compute autocorrelation vector of the two vectors in the buffer
acvect(&buffer[frame_size], order, frame_size, vec);
// First element is the largest(?)
if (fabs(vec[0]) > design->thresh) {
// Over threshold
// Computes the autocorrelation matrix of the two vectors in the buffer
acmat(&buffer[frame_size], order, frame_size, autocorrelation_matrix);
// Compute the LUP decomposition of the autocorrelation matrix
int perm_det;
if (lud(autocorrelation_matrix, order, perm, &perm_det) == 0) { // Continue only if numerically stable
// Back-substitution to solve the linear equation Ra = r
// where
// R = autocorrelation matrix
// r = autocorrelation vector
// a = linear prediction coefficients
// After this vec contains the prediction coefficients
lubksb(autocorrelation_matrix, order, perm, vec);
vec[0] = 1.0;
// Compute reflection coefficients from prediction coefficients
if (kfroma(vec, reflection_coeffs, order) == 0) { // Continue only if numerically stable
data[data_size * num_order + 0] = 1.0;
// clamp the reflection coefficients
for (int i = 1; i < num_order; i++) {
if (reflection_coeffs[i] >= 1.0)
reflection_coeffs[i] = 0.9999999999;
if (reflection_coeffs[i] <= -1.0)
reflection_coeffs[i] = -0.9999999999;
}
// Compute prediction coefficients from reflection coefficients
afromk(reflection_coeffs, &data[data_size * num_order], order);
data_size++;
}
}
}
// Move second vector to first vector
memcpy(&buffer[0], &buffer[frame_size], frame_size * sizeof(int16_t));
}
// Create a vector [1.0, 0.0, ..., 0.0]
vec[0] = 1.0;
for (int i = 1; i < num_order; i++)
vec[i] = 0.0;
for (uint32_t i = 0; i < data_size; i++) {
// Compute autocorrelation from predictors
rfroma(&data[i * num_order], order, predictors[0]);
for (int k = 1; k < num_order; k++)
vec[k] += predictors[0][k];
}
for (int i = 1; i < num_order; i++)
vec[i] /= data_size;
// vec is the average autocorrelation
// Compute predictors for average autocorrelation using Levinson-Durbin algorithm
double dummy;
durbin(vec, order, reflection_coeffs, predictors[0], &dummy);
// clamp results
for (int i = 1; i < num_order; i++) {
if (reflection_coeffs[i] >= 1.0)
reflection_coeffs[i] = 0.9999999999;
if (reflection_coeffs[i] <= -1.0)
reflection_coeffs[i] = -0.9999999999;
}
// Convert clamped reflection coefficients to predictors
afromk(reflection_coeffs, predictors[0], order);
// Split and refine predictors
for (unsigned cur_bits = 0; cur_bits < design->bits; cur_bits++) {
double split_delta[num_order];
for (int i = 0; i < num_order; i++)
split_delta[i] = 0.0;
split_delta[order - 1] = -1.0;
split(predictors, split_delta, order, 1 << cur_bits, 0.01);
refine(predictors, order, 1 << (1 + cur_bits), data, data_size, design->refine_iters);
}
int16_t *book_data = MALLOC_CHECKED_INFO((8 * order * npredictors + 2) * sizeof(int16_t),
"order=%d, npredictors=%d", order, npredictors);
*order_out = order;
*npredictors_out = npredictors;
int num_oflow = 0;
for (int i = 0; i < npredictors; i++)
num_oflow += read_row(&book_data[8 * order * i], predictors[i], order);
if (num_oflow) {
error("overflow detected in tabledesign");
}
*book_data_out = book_data;
free(buffer);
free(data);
for (int i = 0; i < num_order; i++)
free(autocorrelation_matrix[i]);
free(autocorrelation_matrix);
for (int i = 0; i < npredictors; i++)
free(predictors[i]);
free(predictors);
return 0;
}

View file

@ -0,0 +1,837 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "../util.h"
#include "../codec/vadpcm.h"
#include "aiff.h"
typedef struct {
int16_t numChannels;
uint16_t numFramesH;
uint16_t numFramesL;
int16_t sampleSize;
uint8_t sampleRate[10]; // 80-bit float
// Followed by compression type + compression name pstring
} aiff_COMM;
typedef struct {
uint16_t nMarkers;
} aiff_MARK;
typedef struct {
uint16_t MarkerID;
uint16_t positionH;
uint16_t positionL;
} Marker;
typedef enum {
LOOP_PLAYMODE_NONE = 0,
LOOP_PLAYMODE_FWD = 1,
LOOP_PLAYMODE_FWD_BWD = 2
} aiff_loop_playmode;
typedef struct {
int16_t playMode; // aiff_loop_playmode
// Marker IDs
int16_t beginLoop;
int16_t endLoop;
} Loop;
typedef struct {
int8_t baseNote;
int8_t detune;
int8_t lowNote;
int8_t highNote;
int8_t lowVelocity;
int8_t highVelocity;
int16_t gain;
Loop sustainLoop;
Loop releaseLoop;
} aiff_INST;
typedef struct {
int32_t offset;
int32_t blockSize;
} aiff_SSND;
static void
read_pstring(FILE *f, char *out)
{
unsigned char len;
// read string length
FREAD(f, &len, sizeof(len));
// read string and null-terminate it
FREAD(f, out, len);
out[len] = '\0';
// pad to 2-byte boundary
if (!(len & 1))
FREAD(f, &len, 1);
}
static char *
read_pstring_alloc(FILE *f)
{
unsigned char len;
// read string length
FREAD(f, &len, sizeof(len));
// alloc
char *out = MALLOC_CHECKED_INFO(len + 1, "len=%u", len);
// read string and null-terminate it
FREAD(f, out, len);
out[len] = '\0';
// pad to 2-byte boundary
if (!(len & 1))
FREAD(f, &len, 1);
return out;
}
static void
write_pstring(FILE *f, const char *in)
{
unsigned char zr = 0;
size_t inlen = strlen(in);
if (inlen > 255)
error("Invalid pstring length.");
unsigned char len = inlen;
CHUNK_WRITE(f, &len);
CHUNK_WRITE_RAW(f, in, len);
if (!(len & 1))
CHUNK_WRITE(f, &zr);
}
static_assert(sizeof(double) == sizeof(uint64_t), "Double is assumed to be 64-bit");
#define F64_GET_SGN(bits) (((bits) >> 63) & 1) // 1-bit
#define F64_GET_EXP(bits) ((((bits) >> 52) & 0x7FF) - 0x3FF) // 15-bit
#define F64_GET_MANT_H(bits) (((bits) >> 32) & 0xFFFFF) // 20-bit
#define F64_GET_MANT_L(bits) ((bits)&0xFFFFFFFF) // 32-bit
static void
f64_to_f80(double f64, uint8_t *f80)
{
union {
uint32_t w[3];
uint8_t b[12];
} f80tmp;
// get f64 bits
uint64_t f64_bits = *(uint64_t *)&f64;
int f64_sgn = F64_GET_SGN(f64_bits);
int f64_exponent = F64_GET_EXP(f64_bits);
uint32_t f64_mantissa_hi = F64_GET_MANT_H(f64_bits);
uint32_t f64_mantissa_lo = F64_GET_MANT_L(f64_bits);
// build f80 words
f80tmp.w[0] = (f64_sgn << 15) | (f64_exponent + 0x3FFF);
f80tmp.w[1] = (1 << 31) | (f64_mantissa_hi << 11) | (f64_mantissa_lo >> 21);
f80tmp.w[2] = f64_mantissa_lo << 11;
// byteswap to BE
f80tmp.w[0] = htobe32(f80tmp.w[0]);
f80tmp.w[1] = htobe32(f80tmp.w[1]);
f80tmp.w[2] = htobe32(f80tmp.w[2]);
// write bytes
for (size_t i = 0; i < 10; i++)
f80[i] = f80tmp.b[i + 2];
}
static void
f80_to_f64(double *f64, uint8_t *f80)
{
union {
uint32_t w[3];
uint8_t b[12];
} f80tmp;
// read bytes
f80tmp.b[0] = f80tmp.b[1] = 0;
for (size_t i = 0; i < 10; i++)
f80tmp.b[i + 2] = f80[i];
// byteswap from BE
f80tmp.w[0] = be32toh(f80tmp.w[0]);
f80tmp.w[1] = be32toh(f80tmp.w[1]);
f80tmp.w[2] = be32toh(f80tmp.w[2]);
// get f64 parts
int f64_sgn = (f80tmp.w[0] >> 15) & 1;
int f64_exponent = (f80tmp.w[0] & 0x7FFF) - 0x3FFF;
uint32_t f64_mantissa_hi = (f80tmp.w[1] >> 11) & 0xFFFFF;
uint32_t f64_mantissa_lo = ((f80tmp.w[1] & 0x7FF) << 21) | (f80tmp.w[2] >> 11);
// build bitwise f64
uint64_t f64_bits = ((uint64_t)f64_sgn << 63) | ((((uint64_t)f64_exponent + 0x3FF) & 0x7FF) << 52) |
((uint64_t)f64_mantissa_hi << 32) | ((uint64_t)f64_mantissa_lo);
// write double
*f64 = *(double *)&f64_bits;
}
int
aiff_aifc_common_read(container_data *out, FILE *in, UNUSED bool matching, uint32_t size)
{
bool has_comm = false;
bool has_inst = false;
bool has_ssnd = false;
size_t num_markers = 0;
container_marker *markers = NULL;
memset(out, 0, sizeof(*out));
while (true) {
long start = ftell(in);
if (start > 8 + size) {
error("Overran file");
}
if (start == 8 + size) {
break;
}
char cc4[4];
uint32_t chunk_size;
FREAD(in, cc4, 4);
FREAD(in, &chunk_size, 4);
chunk_size = be32toh(chunk_size);
chunk_size++;
chunk_size &= ~1;
switch (CC4(cc4[0], cc4[1], cc4[2], cc4[3])) {
case CC4('C', 'O', 'M', 'M'): {
aiff_COMM comm;
FREAD(in, &comm, sizeof(comm));
comm.numChannels = be16toh(comm.numChannels);
comm.numFramesH = be16toh(comm.numFramesH);
comm.numFramesL = be16toh(comm.numFramesL);
comm.sampleSize = be16toh(comm.sampleSize);
uint32_t num_samples = (comm.numFramesH << 16) | comm.numFramesL;
double sample_rate;
f80_to_f64(&sample_rate, comm.sampleRate);
uint32_t comp_type = CC4('N', 'O', 'N', 'E');
if (chunk_size > sizeof(aiff_COMM)) {
uint32_t compressionType;
FREAD(in, &compressionType, sizeof(compressionType));
comp_type = be32toh(compressionType);
}
out->num_channels = comm.numChannels;
out->sample_rate = sample_rate;
out->bit_depth = comm.sampleSize;
out->num_samples = num_samples;
switch (comp_type) {
case CC4('N', 'O', 'N', 'E'):
out->data_type = SAMPLE_TYPE_PCM16;
break;
case CC4('H', 'P', 'C', 'M'):
out->data_type = SAMPLE_TYPE_PCM8;
break;
case CC4('A', 'D', 'P', '9'):
out->data_type = SAMPLE_TYPE_VADPCM;
break;
case CC4('A', 'D', 'P', '5'):
out->data_type = SAMPLE_TYPE_VADPCM_HALF;
break;
default:
error("Unrecognized aiff/aifc compression type");
break;
}
if (chunk_size > sizeof(aiff_COMM) + 4) {
char compression_name[257];
read_pstring(in, compression_name);
}
has_comm = true;
} break;
case CC4('I', 'N', 'S', 'T'): {
aiff_INST inst;
FREAD(in, &inst, sizeof(inst));
inst.gain = be16toh(inst.gain);
inst.sustainLoop.playMode = be16toh(inst.sustainLoop.playMode);
inst.sustainLoop.beginLoop = be16toh(inst.sustainLoop.beginLoop);
inst.sustainLoop.endLoop = be16toh(inst.sustainLoop.endLoop);
inst.releaseLoop.playMode = be16toh(inst.releaseLoop.playMode);
inst.releaseLoop.beginLoop = be16toh(inst.releaseLoop.beginLoop);
inst.releaseLoop.endLoop = be16toh(inst.releaseLoop.endLoop);
out->base_note = inst.baseNote;
out->fine_tune = inst.detune;
out->key_low = inst.lowNote;
out->key_hi = inst.highNote;
out->vel_low = inst.lowVelocity;
out->vel_hi = inst.highVelocity;
out->gain = inst.gain;
size_t nloops = 0;
nloops += inst.sustainLoop.playMode != LOOP_PLAYMODE_NONE;
nloops += inst.releaseLoop.playMode != LOOP_PLAYMODE_NONE;
if (nloops != 0) {
out->loops = MALLOC_CHECKED_INFO(nloops * sizeof(container_loop), "nloops=%lu", nloops);
size_t i = 0;
if (inst.sustainLoop.playMode != LOOP_PLAYMODE_NONE) {
out->loops[i].id = 0;
switch (inst.sustainLoop.playMode) {
case LOOP_PLAYMODE_FWD:
out->loops[i].type = LOOP_FORWARD;
break;
case LOOP_PLAYMODE_FWD_BWD:
out->loops[i].type = LOOP_FORWARD_BACKWARD;
break;
default:
error("Unrecognized PlayMode in sustainLoop");
break;
}
out->loops[i].start = inst.sustainLoop.beginLoop;
out->loops[i].end = inst.sustainLoop.endLoop;
out->loops[i].num = 0xFFFFFFFF;
out->loops[i].fraction = 0;
i++;
}
if (inst.releaseLoop.playMode != LOOP_PLAYMODE_NONE) {
out->loops[i].id = 1;
switch (inst.sustainLoop.playMode) {
case LOOP_PLAYMODE_FWD:
out->loops[i].type = LOOP_FORWARD;
break;
case LOOP_PLAYMODE_FWD_BWD:
out->loops[i].type = LOOP_FORWARD_BACKWARD;
break;
default:
error("Unrecognized PlayMode in releaseLoop");
break;
}
out->loops[i].start = inst.releaseLoop.beginLoop;
out->loops[i].end = inst.releaseLoop.endLoop;
out->loops[i].num = 0xFFFFFFFF;
out->loops[i].fraction = 0;
i++;
}
}
has_inst = true;
} break;
case CC4('M', 'A', 'R', 'K'): {
aiff_MARK mark;
FREAD(in, &mark, sizeof(mark));
mark.nMarkers = be16toh(mark.nMarkers);
num_markers = mark.nMarkers;
markers = MALLOC_CHECKED_INFO(num_markers * sizeof(container_marker), "num_markers=%lu", num_markers);
for (size_t i = 0; i < num_markers; i++) {
Marker marker;
char *name;
FREAD(in, &marker, sizeof(marker));
name = read_pstring_alloc(in);
markers[i].id = be16toh(marker.MarkerID);
markers[i].frame_number = (be16toh(marker.positionH) << 16) | be16toh(marker.positionL);
markers[i].name = name;
}
} break;
case CC4('A', 'P', 'P', 'L'): {
char subcc4[4];
FREAD(in, subcc4, 4);
switch (CC4(subcc4[0], subcc4[1], subcc4[2], subcc4[3])) {
case CC4('s', 't', 'o', 'c'): {
char chunk_name[257];
read_pstring(in, chunk_name);
if (strequ(chunk_name, "VADPCMCODES")) {
int16_t version;
uint16_t order;
uint16_t npredictors;
FREAD(in, &version, sizeof(version));
version = be16toh(version);
FREAD(in, &order, sizeof(order));
order = be16toh(order);
FREAD(in, &npredictors, sizeof(npredictors));
npredictors = be16toh(npredictors);
size_t book_size_bytes = VADPCM_BOOK_SIZE_BYTES(order, npredictors);
int16_t *book_state =
MALLOC_CHECKED_INFO(book_size_bytes, "order=%u, npredictors=%u", order, npredictors);
FREAD(in, book_state, book_size_bytes);
out->vadpcm.book_version = version;
out->vadpcm.book_header.order = order;
out->vadpcm.book_header.npredictors = npredictors;
out->vadpcm.book_data = book_state;
for (size_t i = 0; i < VADPCM_BOOK_SIZE(order, npredictors); i++)
out->vadpcm.book_data[i] = be16toh(out->vadpcm.book_data[i]);
out->vadpcm.has_book = true;
} else if (strequ(chunk_name, "VADPCMLOOPS")) {
int16_t version;
int16_t nloops;
FREAD(in, &version, sizeof(version));
version = be16toh(version);
FREAD(in, &nloops, sizeof(nloops));
nloops = be16toh(nloops);
out->vadpcm.loop_version = version;
out->vadpcm.num_loops = nloops;
if (out->vadpcm.num_loops)
out->vadpcm.loops = MALLOC_CHECKED_INFO(out->vadpcm.num_loops * sizeof(ALADPCMloop),
"num_loops=%u", out->vadpcm.num_loops);
for (size_t i = 0; i < out->vadpcm.num_loops; i++) {
FREAD(in, &out->vadpcm.loops[i], sizeof(ALADPCMloop));
out->vadpcm.loops[i].start = be32toh(out->vadpcm.loops[i].start);
out->vadpcm.loops[i].end = be32toh(out->vadpcm.loops[i].end);
out->vadpcm.loops[i].count = be32toh(out->vadpcm.loops[i].count);
for (size_t j = 0; j < ARRAY_COUNT(out->vadpcm.loops[i].state); j++)
out->vadpcm.loops[i].state[j] = be16toh(out->vadpcm.loops[i].state[j]);
}
} else {
warning("Skipping unknown APPL::stoc subchunk: \"%s\"", chunk_name);
}
} break;
default:
warning("Skipping unknown APPL subchunk: \"%c%c%c%c\"", subcc4[0], subcc4[1], subcc4[2],
subcc4[3]);
break;
}
} break;
case CC4('S', 'S', 'N', 'D'): {
aiff_SSND ssnd;
FREAD(in, &ssnd, sizeof(ssnd));
ssnd.offset = be32toh(ssnd.offset);
ssnd.blockSize = be32toh(ssnd.blockSize);
size_t data_size = chunk_size - sizeof(ssnd);
void *data = MALLOC_CHECKED_INFO(data_size, "SSND chunk size = %lu", data_size);
FREAD(in, data, data_size);
if (ssnd.offset != 0 || ssnd.blockSize != 0)
error("Bad SSND chunk in aiff/aifc, offset/blockSize != 0");
out->data = data;
out->data_size = data_size;
has_ssnd = true;
} break;
default:
warning("Skipping unknown aiff chunk: \"%c%c%c%c\"", cc4[0], cc4[1], cc4[2], cc4[3]);
break;
}
long read_size = ftell(in) - start - 8;
if (read_size > chunk_size)
error("overran chunk: %lu vs %u\n", read_size, chunk_size);
else if (read_size < chunk_size)
warning("did not read entire %.*s chunk: %lu vs %u", 4, cc4, read_size, chunk_size);
fseek(in, start + 8 + chunk_size, SEEK_SET);
}
if (!has_comm)
error("aiff/aifc has no COMM chunk");
if (!has_inst)
error("aiff/aifc has no INST chunk");
if (!has_ssnd)
error("aiff/aifc has no SSND chunk");
if (out->data_type == SAMPLE_TYPE_PCM16) {
assert(out->data_size % 2 == 0);
assert(out->bit_depth == 16);
for (size_t i = 0; i < out->data_size / 2; i++)
((uint16_t *)(out->data))[i] = be16toh(((uint16_t *)(out->data))[i]);
}
for (size_t i = 0; i < out->num_loops; i++) {
container_marker *marker;
marker = NULL;
for (size_t j = 0; j < num_markers; j++) {
if (markers[j].id == out->loops[i].start) {
marker = &markers[j];
break;
}
}
if (marker == NULL)
error("AIFF/C loop references non-existent marker");
out->loops[i].start = marker->frame_number;
marker = NULL;
for (size_t j = 0; j < num_markers; j++) {
if (markers[j].id == out->loops[i].end) {
marker = &markers[j];
break;
}
}
if (marker == NULL)
error("AIFF/C loop references non-existent marker");
out->loops[i].end = marker->frame_number;
}
fclose(in);
return 0;
}
int
aifc_read(container_data *out, const char *path, bool matching)
{
FILE *in = fopen(path, "rb");
if (in == NULL)
error("Failed to open \"%s\" for reading", path);
char form[4];
uint32_t size;
char aifc[4];
FREAD(in, form, 4);
FREAD(in, &size, 4);
size = be32toh(size);
FREAD(in, aifc, 4);
if (!CC4_CHECK(form, "FORM") || !CC4_CHECK(aifc, "AIFC"))
error("Not an aifc file?");
return aiff_aifc_common_read(out, in, matching, size);
}
int
aiff_read(container_data *out, const char *path, bool matching)
{
FILE *in = fopen(path, "rb");
if (in == NULL)
error("Failed to open \"%s\" for reading", path);
char form[4];
uint32_t size;
char aiff[4];
FREAD(in, form, 4);
FREAD(in, &size, 4);
size = be32toh(size);
FREAD(in, aiff, 4);
if (!CC4_CHECK(form, "FORM") || !CC4_CHECK(aiff, "AIFF"))
error("Not an aiff file?");
return aiff_aifc_common_read(out, in, matching, size);
}
static int
aiff_aifc_common_write(container_data *in, const char *path, bool aifc, bool matching)
{
FILE *out = fopen(path, "wb");
if (out == NULL)
error("Failed to open %s for writing\n", path);
const char *aifc_head = "FORM\0\0\0\0AIFC";
const char *aiff_head = "FORM\0\0\0\0AIFF";
FWRITE(out, (aifc) ? aifc_head : aiff_head, 12);
long chunk_start;
uint32_t compression_type;
const char *compression_name;
switch (in->data_type) {
case SAMPLE_TYPE_PCM16:
compression_type = CC4('N', 'O', 'N', 'E');
compression_name = NULL;
break;
case SAMPLE_TYPE_PCM8:
compression_type = CC4('H', 'P', 'C', 'M');
compression_name = "Half-frame PCM";
break;
case SAMPLE_TYPE_VADPCM:
compression_type = CC4('A', 'D', 'P', '9');
compression_name = "Nintendo/SGI VADPCM 9-bytes/frame";
break;
case SAMPLE_TYPE_VADPCM_HALF:
compression_type = CC4('A', 'D', 'P', '5');
compression_name = "Nintendo/SGI VADPCM 5-bytes/frame";
break;
default:
error("Unhandled data type for aiff/aifc container");
break;
}
if (compression_type != CC4('N', 'O', 'N', 'E') && !aifc) {
error("Compressed data types must use aifc output");
}
aiff_COMM comm = {
.numChannels = htobe16(in->num_channels),
.numFramesH = htobe16(in->num_samples >> 16),
.numFramesL = htobe16(in->num_samples),
.sampleSize = htobe16(in->bit_depth),
.sampleRate = { 0 },
};
f64_to_f80(in->sample_rate, comm.sampleRate);
CHUNK_BEGIN(out, "COMM", &chunk_start);
CHUNK_WRITE(out, &comm);
if (compression_name != NULL) {
uint32_t compressionType = htobe32(compression_type);
CHUNK_WRITE(out, &compressionType);
write_pstring(out, compression_name);
}
CHUNK_END(out, chunk_start, htobe32);
if (in->num_loops > 2)
error("Only up to two loops are supported for AIFF/C");
size_t num_markers = 0;
container_marker markers[4];
static char *marker_names[4] = {
"start",
"end",
"start",
"end",
};
Loop sustainLoop = { 0 };
Loop releaseLoop = { 0 };
for (size_t i = 0; i < in->num_loops; i++) {
Loop *target;
switch (in->loops[i].id) {
case 0:
target = &sustainLoop;
break;
case 1:
target = &releaseLoop;
break;
default:
continue;
}
unsigned type;
switch (in->loops[i].type) {
case LOOP_FORWARD:
type = LOOP_PLAYMODE_FWD;
break;
case LOOP_FORWARD_BACKWARD:
type = LOOP_PLAYMODE_FWD_BWD;
break;
default:
error("Unsupported loop type in aiff/aifc");
break;
}
target->playMode = htobe16(type);
markers[num_markers].id = num_markers + 1;
markers[num_markers].name = marker_names[num_markers];
markers[num_markers].frame_number = in->loops[i].start;
target->beginLoop = htobe16(1 + num_markers++);
markers[num_markers].id = num_markers + 1;
markers[num_markers].name = marker_names[num_markers];
markers[num_markers].frame_number = in->loops[i].end;
target->endLoop = htobe16(1 + num_markers++);
}
if (num_markers != 0) {
CHUNK_BEGIN(out, "MARK", &chunk_start);
aiff_MARK mark = {
.nMarkers = htobe16(num_markers),
};
CHUNK_WRITE(out, &mark);
for (size_t i = 0; i < num_markers; i++) {
Marker marker = {
.MarkerID = htobe16(markers[i].id),
.positionH = htobe16(markers[i].frame_number >> 16),
.positionL = htobe16(markers[i].frame_number),
};
CHUNK_WRITE(out, &marker);
write_pstring(out, markers[i].name);
}
CHUNK_END(out, chunk_start, htobe32);
}
aiff_INST inst = {
.baseNote = in->base_note,
.detune = in->fine_tune,
.lowNote = in->key_low,
.highNote = in->key_hi,
.lowVelocity = in->vel_low,
.highVelocity = in->vel_hi,
.gain = htobe16(in->gain),
.sustainLoop = sustainLoop,
.releaseLoop = releaseLoop,
};
CHUNK_BEGIN(out, "INST", &chunk_start);
CHUNK_WRITE(out, &inst);
CHUNK_END(out, chunk_start, htobe32);
if (aifc || matching) {
// If we're writing an aifc, or we want to match on round-trip, emit an application-specific chunk for the
// vadpcm codebook.
if (in->vadpcm.has_book) {
// APPL::stoc::VADPCMCODES
CHUNK_BEGIN(out, "APPL", &chunk_start);
CHUNK_WRITE_RAW(out, "stoc", 4);
write_pstring(out, "VADPCMCODES");
int16_t version = htobe16(in->vadpcm.book_version);
int16_t order = htobe16(in->vadpcm.book_header.order);
int16_t npredictors = htobe16(in->vadpcm.book_header.npredictors);
CHUNK_WRITE(out, &version);
CHUNK_WRITE(out, &order);
CHUNK_WRITE(out, &npredictors);
size_t book_size = VADPCM_BOOK_SIZE(in->vadpcm.book_header.order, in->vadpcm.book_header.npredictors);
int16_t book_data_out[book_size];
for (size_t i = 0; i < book_size; i++)
book_data_out[i] = htobe16(in->vadpcm.book_data[i]);
CHUNK_WRITE_RAW(out, book_data_out, sizeof(int16_t) * book_size);
CHUNK_END(out, chunk_start, htobe32);
}
// Only write a vadpcm loop structure for compressed output. Loop states match on round-trip so we need not
// save them in uncompressed output.
if (aifc && in->vadpcm.num_loops != 0) {
// APPL::stoc::VADPCMLOOPS
CHUNK_BEGIN(out, "APPL", &chunk_start);
CHUNK_WRITE_RAW(out, "stoc", 4);
write_pstring(out, "VADPCMLOOPS");
int16_t version = htobe16(in->vadpcm.loop_version);
int16_t nloops = htobe16(in->vadpcm.num_loops);
CHUNK_WRITE(out, &version);
CHUNK_WRITE(out, &nloops);
for (size_t i = 0; i < in->vadpcm.num_loops; i++) {
ALADPCMloop outloop = in->vadpcm.loops[i];
outloop.start = htobe32(outloop.start);
outloop.end = htobe32(outloop.end);
outloop.count = htobe32(outloop.count);
for (size_t i = 0; i < ARRAY_COUNT(outloop.state); i++)
outloop.state[i] = htobe16(outloop.state[i]);
CHUNK_WRITE(out, &outloop);
}
CHUNK_END(out, chunk_start, htobe32);
}
}
if (in->data_type == SAMPLE_TYPE_PCM16) {
assert(in->data_size % 2 == 0);
assert(in->bit_depth == 16);
for (size_t i = 0; i < in->data_size / 2; i++) {
((uint16_t *)in->data)[i] = htobe16(((uint16_t *)in->data)[i]);
}
}
aiff_SSND ssnd = {
.offset = 0,
.blockSize = 0,
};
CHUNK_BEGIN(out, "SSND", &chunk_start);
CHUNK_WRITE(out, &ssnd);
CHUNK_WRITE_RAW(out, in->data, in->data_size);
CHUNK_END(out, chunk_start, htobe32);
uint32_t size = htobe32(ftell(out) - 8);
fseek(out, 4, SEEK_SET);
fwrite(&size, 4, 1, out);
fclose(out);
return 0;
}
int
aifc_write(container_data *data, const char *path, bool matching)
{
return aiff_aifc_common_write(data, path, true, matching);
}
int
aiff_write(container_data *data, const char *path, bool matching)
{
return aiff_aifc_common_write(data, path, false, matching);
}

View file

@ -0,0 +1,28 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef AIFF_H
#define AIFF_H
#include <stddef.h>
#include <stdint.h>
#include "../codec/vadpcm.h"
#include "container.h"
int
aifc_read(container_data *out, const char *path, bool matching);
int
aiff_read(container_data *out, const char *path, bool matching);
int
aifc_write(container_data *in, const char *path, bool matching);
int
aiff_write(container_data *in, const char *path, bool matching);
#endif

View file

@ -0,0 +1,25 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <stdlib.h>
#include "container.h"
int
container_destroy(container_data *data)
{
if (data->data != NULL)
free(data->data);
if (data->loops != NULL)
free(data->loops);
if (data->vadpcm.book_data != NULL)
free(data->vadpcm.book_data);
if (data->vadpcm.loops != NULL)
free(data->vadpcm.loops);
return 0;
}

View file

@ -0,0 +1,99 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef CONTAINER_H
#define CONTAINER_H
#include <stdbool.h>
#include <stdint.h>
#include "../codec/vadpcm.h"
typedef enum {
// Uncompressed
SAMPLE_TYPE_PCM16,
SAMPLE_TYPE_PCM8,
// Compressed
SAMPLE_TYPE_VADPCM,
SAMPLE_TYPE_VADPCM_HALF
} sample_data_type;
typedef struct {
const char *fext;
int (*read)(struct container_data *out, const char *path, bool matching);
int (*write)(struct container_data *out, const char *path, bool matching);
} container_spec;
typedef enum {
LOOP_NONE = 0,
LOOP_FORWARD = 1,
LOOP_FORWARD_BACKWARD = 2,
LOOP_BACKWARD = 3
} loop_type;
typedef struct {
uint16_t id;
loop_type type;
uint32_t start;
uint32_t end;
uint32_t fraction;
uint32_t num;
} container_loop;
typedef struct {
uint16_t id;
uint32_t frame_number;
char *name;
} container_marker;
typedef struct container_data {
// Sample details
double sample_rate;
unsigned int num_channels;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bit_depth; // also called sample_size
uint32_t num_samples; // also apparently called num_frames? but that's wrong
// Instrument details
int8_t base_note;
int8_t fine_tune;
int8_t gain;
int8_t key_low;
int8_t key_hi;
int8_t vel_low;
int8_t vel_hi;
// Sample data, possibly compressed
sample_data_type data_type;
void *data;
size_t data_size;
unsigned num_loops;
container_loop *loops;
// VADPCM-specific data
struct {
// VADPCM codebook
int16_t book_version;
ALADPCMbookhead book_header;
int16_t *book_data;
bool has_book;
// VADPCM loop data
int16_t loop_version;
unsigned num_loops;
ALADPCMloop *loops;
} vadpcm;
} container_data;
int
container_destroy(container_data *data);
#endif

View file

@ -0,0 +1,552 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../util.h"
#include "../codec/vadpcm.h"
#include "wav.h"
typedef enum {
WAVE_TYPE_PCM = 1,
WAVE_TYPE_FLOAT = 3,
WAVE_TYPE_ALAW = 6,
WAVE_TYPE_MULAW = 7,
WAVE_TYPE_EXTENSIBLE = 0xFFFE,
} wav_type;
typedef struct {
uint16_t type;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bit_depth;
} wav_fmt;
typedef struct {
uint32_t num_samples;
} wav_fact;
typedef struct {
int8_t base_note;
int8_t fine_tune;
int8_t gain;
int8_t key_low;
int8_t key_hi;
int8_t vel_low;
int8_t vel_hi;
char _pad[1];
} wav_inst;
typedef struct {
uint32_t manufacturer;
uint32_t product;
uint32_t sample_period;
uint32_t unity_note;
char _pad[3];
uint8_t fine_tune;
uint32_t format;
uint32_t offset;
uint32_t num_sample_loops;
uint32_t sampler_data;
} wav_smpl;
typedef struct {
uint32_t cue_point_index;
uint32_t type;
uint32_t start;
uint32_t end;
uint32_t fraction;
uint32_t num;
} wav_loop;
static const char *
wav_type_name(int type)
{
switch (type) {
case WAVE_TYPE_PCM:
return "PCM";
case WAVE_TYPE_FLOAT:
return "Float";
case WAVE_TYPE_ALAW:
return "ALAW";
case WAVE_TYPE_MULAW:
return "MULAW";
case WAVE_TYPE_EXTENSIBLE:
return "Extensible";
default:
return "Unknown (should never be here)";
}
}
int
wav_read(container_data *out, const char *path, UNUSED bool matching)
{
bool has_fmt = false;
bool has_fact = false;
bool has_data = false;
bool has_inst = false;
bool has_smpl = false;
memset(out, 0, sizeof(*out));
FILE *in = fopen(path, "rb");
if (in == NULL)
error("Failed to open \"%s\" for reading", path);
char riff[4];
uint32_t size;
char wave[4];
FREAD(in, riff, 4);
FREAD(in, &size, 4);
size = le32toh(size);
FREAD(in, wave, 4);
if (!CC4_CHECK(riff, "RIFF") || !CC4_CHECK(wave, "WAVE"))
error("Not a wav file?");
while (true) {
bool skipped = false;
long start = ftell(in);
if (start > 8 + size) {
error("Overran file");
}
if (start == 8 + size) {
break;
}
char cc4[4];
uint32_t chunk_size;
FREAD(in, cc4, 4);
FREAD(in, &chunk_size, 4);
chunk_size = le32toh(chunk_size);
switch (CC4(cc4[0], cc4[1], cc4[2], cc4[3])) {
case CC4('f', 'm', 't', ' '): {
wav_fmt fmt;
FREAD(in, &fmt, sizeof(fmt));
fmt.type = le16toh(fmt.type);
fmt.num_channels = le16toh(fmt.num_channels);
fmt.sample_rate = le32toh(fmt.sample_rate);
fmt.byte_rate = le32toh(fmt.byte_rate);
fmt.block_align = le16toh(fmt.block_align);
fmt.bit_depth = le16toh(fmt.bit_depth);
if (fmt.bit_depth != 16)
error("Wav input format should be 16-bit PCM, was %u-bit", fmt.bit_depth);
switch (fmt.type) {
case WAVE_TYPE_PCM:
out->data_type = SAMPLE_TYPE_PCM16;
break;
case WAVE_TYPE_FLOAT:
case WAVE_TYPE_MULAW:
case WAVE_TYPE_ALAW:
case WAVE_TYPE_EXTENSIBLE:
error("Unhandled sample type: %s, should be PCM", wav_type_name(fmt.type));
break;
default:
error("Unrecognized sample type: %d, should be PCM", fmt.type);
break;
}
out->num_channels = fmt.num_channels;
out->sample_rate = fmt.sample_rate;
out->byte_rate = fmt.byte_rate;
out->block_align = fmt.block_align;
out->bit_depth = fmt.bit_depth;
has_fmt = true;
} break;
case CC4('f', 'a', 'c', 't'): {
wav_fact fact;
FREAD(in, &fact, sizeof(fact));
fact.num_samples = le32toh(fact.num_samples);
out->num_samples = fact.num_samples;
has_fact = true;
} break;
case CC4('d', 'a', 't', 'a'): {
void *data = MALLOC_CHECKED_INFO(chunk_size, "data size = %u", chunk_size);
FREAD(in, data, chunk_size);
out->data = data;
out->data_size = chunk_size;
has_data = true;
} break;
case CC4('i', 'n', 's', 't'): {
wav_inst inst;
FREAD(in, &inst, sizeof(inst));
out->base_note = inst.base_note;
out->fine_tune = inst.fine_tune;
out->gain = inst.gain;
out->key_low = inst.key_low;
out->key_hi = inst.key_hi;
out->vel_low = inst.vel_low;
out->vel_hi = inst.vel_hi;
has_inst = true;
} break;
case CC4('s', 'm', 'p', 'l'): {
wav_smpl smpl;
FREAD(in, &smpl, sizeof(smpl));
smpl.manufacturer = le32toh(smpl.manufacturer);
smpl.product = le32toh(smpl.product);
smpl.sample_period = le32toh(smpl.sample_period);
smpl.unity_note = le32toh(smpl.unity_note);
smpl.format = le32toh(smpl.format);
smpl.offset = le32toh(smpl.offset);
smpl.num_sample_loops = le32toh(smpl.num_sample_loops);
smpl.sampler_data = le32toh(smpl.sampler_data);
out->num_loops = smpl.num_sample_loops;
if (out->num_loops != 0) {
out->loops =
MALLOC_CHECKED_INFO(out->num_loops * sizeof(container_loop), "num_loops=%u", out->num_loops);
for (size_t i = 0; i < out->num_loops; i++) {
wav_loop loop;
FREAD(in, &loop, sizeof(loop));
loop.cue_point_index = le32toh(loop.cue_point_index);
loop.type = le32toh(loop.type);
loop.start = le32toh(loop.start);
loop.end = le32toh(loop.end);
loop.fraction = le32toh(loop.fraction);
loop.num = le32toh(loop.num);
loop_type type;
switch (loop.type) {
case 0:
type = LOOP_FORWARD;
break;
case 1:
type = LOOP_FORWARD_BACKWARD;
break;
case 2:
type = LOOP_BACKWARD;
break;
default:
error("Unrecognized loop type in wav");
}
out->loops[i].id = i;
out->loops[i].type = type;
out->loops[i].start = loop.start;
out->loops[i].end = loop.end;
out->loops[i].fraction = loop.fraction;
out->loops[i].num = loop.num;
}
}
has_smpl = true;
} break;
case CC4('z', 'z', 'b', 'k'): {
char vadpcmcodes[12];
uint16_t version;
uint16_t order;
uint16_t npredictors;
FREAD(in, vadpcmcodes, sizeof(vadpcmcodes));
FREAD(in, &version, sizeof(version));
version = le16toh(version);
FREAD(in, &order, sizeof(order));
order = le16toh(order);
FREAD(in, &npredictors, sizeof(npredictors));
npredictors = le16toh(npredictors);
size_t book_size = VADPCM_BOOK_SIZE(order, npredictors);
size_t book_data_size = sizeof(int16_t) * book_size;
int16_t *book_data =
MALLOC_CHECKED_INFO(book_data_size, "order=%u, npredictors=%u", order, npredictors);
FREAD(in, book_data, book_data_size);
out->vadpcm.book_header.order = order;
out->vadpcm.book_header.npredictors = npredictors;
out->vadpcm.book_data = book_data;
for (size_t i = 0; i < book_size; i++) {
out->vadpcm.book_data[i] = le16toh(out->vadpcm.book_data[i]);
}
out->vadpcm.has_book = true;
} break;
case CC4('z', 'z', 'l', 'p'): {
uint16_t version;
uint16_t nloops;
FREAD(in, &version, sizeof(version));
version = le16toh(version);
FREAD(in, &nloops, sizeof(nloops));
nloops = le16toh(nloops);
if (nloops != 0)
out->vadpcm.loops = MALLOC_CHECKED_INFO(nloops * sizeof(ALADPCMloop), "nloops=%u", nloops);
for (size_t i = 0; i < nloops; i++) {
uint32_t loop_start;
uint32_t loop_end;
uint32_t loop_num;
FREAD(in, &loop_start, sizeof(loop_start));
loop_start = le32toh(loop_start);
FREAD(in, &loop_end, sizeof(loop_end));
loop_end = le32toh(loop_end);
FREAD(in, &loop_num, sizeof(loop_num));
loop_num = le32toh(loop_num);
out->vadpcm.loops[i].start = loop_start;
out->vadpcm.loops[i].end = loop_end;
out->vadpcm.loops[i].count = loop_num;
if (out->vadpcm.loops[i].count != 0) {
FREAD(in, out->vadpcm.loops[i].state, sizeof(out->vadpcm.loops[i].state));
for (size_t j = 0; j < ARRAY_COUNT(out->vadpcm.loops[i].state); j++) {
out->vadpcm.loops[i].state[j] = le16toh(out->vadpcm.loops[i].state[j]);
}
}
}
} break;
default:
warning("Skipping unknown wav chunk: \"%c%c%c%c\"", cc4[0], cc4[1], cc4[2], cc4[3]);
skipped = true;
break;
}
long read_size = ftell(in) - start - 8;
if (read_size > chunk_size)
error("overran chunk");
else if (!skipped && read_size < chunk_size)
warning("did not read entire %*s chunk: %lu vs %u", 4, cc4, read_size, chunk_size);
fseek(in, start + 8 + chunk_size, SEEK_SET);
}
if (!has_fmt)
error("wav has no fmt chunk");
if (!has_data)
error("wav has no data chunk");
if (!has_fact) {
out->num_samples = out->data_size / (out->bit_depth / 8);
}
if (!has_inst) {
out->base_note = 60; // C4
out->fine_tune = 0;
out->gain = 0;
out->key_low = 0;
out->key_hi = 0;
out->vel_low = 0;
out->vel_hi = 0;
}
if (!has_smpl) {
out->num_loops = 0;
out->loops = NULL;
}
if (out->data_type == SAMPLE_TYPE_PCM16) {
if (out->data_size % 2 != 0)
error("wav data size is not a multiple of 2 despite being pcm16-formatted?");
for (size_t i = 0; i < out->data_size / 2; i++)
((uint16_t *)(out->data))[i] = le16toh(((uint16_t *)(out->data))[i]);
}
fclose(in);
return 0;
}
int
wav_write(container_data *in, const char *path, bool matching)
{
long chunk_start;
FILE *out = fopen(path, "wb");
FWRITE(out, "RIFF\0\0\0\0WAVE", 12);
uint16_t fmt_type;
switch (in->data_type) {
case SAMPLE_TYPE_PCM16:
fmt_type = WAVE_TYPE_PCM;
break;
default:
error("Unrecognized sample type for wav output");
break;
}
wav_fmt fmt = {
.type = htole16(fmt_type),
.num_channels = htole16(in->num_channels),
.sample_rate = htole32(in->sample_rate),
.byte_rate = htole32(in->sample_rate * (in->bit_depth / 8)),
.block_align = htole16(in->num_channels * (in->bit_depth / 8)),
.bit_depth = htole16(in->bit_depth),
};
CHUNK_BEGIN(out, "fmt ", &chunk_start);
CHUNK_WRITE(out, &fmt);
CHUNK_END(out, chunk_start, htole32);
wav_fact fact = {
.num_samples = htole32(in->num_samples),
};
CHUNK_BEGIN(out, "fact", &chunk_start);
CHUNK_WRITE(out, &fact);
CHUNK_END(out, chunk_start, htole32);
if (in->data_type == SAMPLE_TYPE_PCM16) {
assert(in->bit_depth == 16);
assert(in->data_size % 2 == 0);
for (size_t i = 0; i < in->data_size / 2; i++) {
((uint16_t *)in->data)[i] = htole16(((uint16_t *)in->data)[i]);
}
}
CHUNK_BEGIN(out, "data", &chunk_start);
CHUNK_WRITE_RAW(out, in->data, in->data_size);
CHUNK_END(out, chunk_start, htole32);
wav_inst inst = {
.base_note = in->base_note,
.fine_tune = in->fine_tune,
.gain = in->gain,
.key_low = in->key_low,
.key_hi = in->key_hi,
.vel_low = in->vel_low,
.vel_hi = in->vel_hi,
._pad = { 0 },
};
CHUNK_BEGIN(out, "inst", &chunk_start);
CHUNK_WRITE(out, &inst);
CHUNK_END(out, chunk_start, htole32);
wav_smpl smpl = {
.manufacturer = 0,
.product = 0,
.sample_period = 0,
.unity_note = 0,
._pad = {0, 0, 0},
.fine_tune = 0,
.format = 0,
.offset = 0,
.num_sample_loops = htole32(in->num_loops),
.sampler_data = 0,
};
CHUNK_BEGIN(out, "smpl", &chunk_start);
CHUNK_WRITE(out, &smpl);
for (size_t i = 0; i < in->num_loops; i++) {
uint32_t wav_loop_type;
switch (in->loops[i].type) {
case LOOP_FORWARD:
wav_loop_type = 0;
break;
case LOOP_FORWARD_BACKWARD:
wav_loop_type = 1;
break;
case LOOP_BACKWARD:
wav_loop_type = 2;
break;
default:
error("Unrecognized loop type for wav output");
}
wav_loop loop = {
.cue_point_index = 0,
.type = htole32(wav_loop_type),
.start = htole32(in->loops[i].start),
.end = htole32(in->loops[i].end),
.fraction = htole32(in->loops[i].fraction),
.num = htole32(in->loops[i].num),
};
CHUNK_WRITE(out, &loop);
}
CHUNK_END(out, chunk_start, htole32);
// Books generally don't match on round-trip, if matching we should write a vadpcm book chunk to the uncompressed
// output so it may be read back when re-encoding.
if (in->vadpcm.has_book && matching) {
uint32_t book_size = VADPCM_BOOK_SIZE(in->vadpcm.book_header.order, in->vadpcm.book_header.npredictors);
uint16_t version = htole16(in->vadpcm.book_version);
uint16_t order = htole16(in->vadpcm.book_header.order);
uint16_t npredictors = htole16(in->vadpcm.book_header.npredictors);
int16_t book_state[book_size];
for (size_t i = 0; i < book_size; i++) {
book_state[i] = htole16(in->vadpcm.book_data[i]);
}
CHUNK_BEGIN(out, "zzbk", &chunk_start);
CHUNK_WRITE_RAW(out, "VADPCMCODES", sizeof("VADPCMCODES"));
CHUNK_WRITE(out, &version);
CHUNK_WRITE(out, &order);
CHUNK_WRITE(out, &npredictors);
CHUNK_WRITE_RAW(out, book_state, sizeof(int16_t) * book_size);
CHUNK_END(out, chunk_start, htole32);
}
// Loop states match on round-trip while books don't, so don't write a vadpcm loop chunk if the output format
// is uncompressed.
if (in->vadpcm.num_loops != 0 && in->data_type != SAMPLE_TYPE_PCM16) {
CHUNK_BEGIN(out, "zzlp", &chunk_start);
for (size_t i = 0; i < in->vadpcm.num_loops; i++) {
uint32_t loop_start = htole32(in->vadpcm.loops[i].start);
uint32_t loop_end = htole32(in->vadpcm.loops[i].end);
uint32_t loop_count = htole32(in->vadpcm.loops[i].count);
int16_t loop_state[16];
for (size_t j = 0; j < ARRAY_COUNT(in->vadpcm.loops[i].state); j++) {
loop_state[j] = htole16(in->vadpcm.loops[i].state[j]);
}
CHUNK_WRITE(out, &loop_start);
CHUNK_WRITE(out, &loop_end);
CHUNK_WRITE(out, &loop_count);
CHUNK_WRITE_RAW(out, loop_state, sizeof(loop_state));
}
CHUNK_END(out, chunk_start, htole32);
}
uint32_t size = htole32(ftell(out) - 8);
fseek(out, 4, SEEK_SET);
fwrite(&size, 4, 1, out);
fclose(out);
return 0;
}

View file

@ -0,0 +1,20 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef WAV_H
#define WAV_H
#include "../codec/vadpcm.h"
#include "container.h"
int
wav_write(container_data *in, const char *path, bool matching);
int
wav_read(container_data *out, const char *path, bool matching);
#endif

View file

@ -0,0 +1,209 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <assert.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "util.h"
#include "codec/codec.h"
#include "codec/uncompressed.h"
#include "codec/vadpcm.h"
#include "container/container.h"
#include "container/wav.h"
#include "container/aiff.h"
static const codec_spec codecs[] = {
{"pcm16", SAMPLE_TYPE_PCM16, 16, false, pcm16_enc_dec, pcm16_enc_dec},
{ "vadpcm", SAMPLE_TYPE_VADPCM, 9, true, vadpcm_dec, vadpcm_enc },
{ "vadpcm-half", SAMPLE_TYPE_VADPCM_HALF, 5, true, vadpcm_dec, vadpcm_enc },
// { "pcm8", SAMPLE_TYPE_PCM8, TODO, TODO, pcm8_dec, pcm8_enc },
};
static const container_spec containers[] = {
{".wav", wav_read, wav_write },
{ ".aiff", aiff_read, aiff_write},
{ ".aifc", aifc_read, aifc_write},
};
static bool
str_endswith(const char *str1, const char *str2)
{
size_t len1 = strlen(str1);
size_t len2 = strlen(str2);
if (len2 > len1)
return false;
return strequ(str1 + len1 - len2, str2);
}
NORETURN static void
help(const char *progname)
{
fprintf(stderr, "%s [--matching] out_codec_name in_path out_path\n", progname);
fprintf(stderr, "Supported codecs:\n");
fprintf(stderr, " pcm16\n");
fprintf(stderr, " vadpcm\n");
fprintf(stderr, " vadpcm-half\n");
fprintf(stderr, "Possible input/output formats with supported codecs, automatically selected by file extension:\n");
fprintf(stderr, " wav [pcm16]\n");
fprintf(stderr, " aiff [pcm16]\n");
fprintf(stderr, " aifc [vadpcm, vadpcm-half]\n");
exit(EXIT_FAILURE);
}
NORETURN static void
usage(const char *progname)
{
fprintf(stderr, "%s [--matching] out_codec_name in_path out_path\n", progname);
exit(EXIT_FAILURE);
}
static const codec_spec *
codec_from_name(const char *name)
{
for (size_t i = 0; i < ARRAY_COUNT(codecs); i++) {
if (strequ(name, codecs[i].name))
return &codecs[i];
}
return NULL;
}
static const codec_spec *
codec_from_type(sample_data_type type)
{
for (size_t i = 0; i < ARRAY_COUNT(codecs); i++) {
if (type == codecs[i].type)
return &codecs[i];
}
return NULL;
}
static const container_spec *
container_from_name(const char *name)
{
for (size_t i = 0; i < ARRAY_COUNT(containers); i++) {
if (str_endswith(name, containers[i].fext))
return &containers[i];
}
return NULL;
}
int
main(int argc, char **argv)
{
const char *progname = argv[0];
// Required arguments
const char *in_path = NULL;
const char *out_path = NULL;
const char *out_codec_name = NULL;
// Optional arguments
enc_dec_opts opts = {
.matching = false,
// VADPCM
.truncate = false,
.min_loop_length = 800,
.design.order = 2,
.design.bits = 2,
.design.refine_iters = 2,
.design.thresh = 10.0,
.design.frame_size = 16,
};
// parse args
#define arg_error(fmt, ...) \
do { \
fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
usage(progname); \
} while (0)
int argn = 0;
for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-') {
// Optional args
if (strequ(argv[i], "--help")) {
help(progname);
} else if (strequ(argv[i], "--matching")) {
if (opts.matching)
arg_error("Received --matching option twice");
opts.matching = true;
continue;
}
arg_error("Unknown option \"%s\"", argv[i]);
} else {
// Required args
switch (argn) {
case 0:
out_codec_name = argv[i];
break;
case 1:
in_path = argv[i];
break;
case 2:
out_path = argv[i];
break;
default:
arg_error("Unknown positional argument \"%s\"", argv[i]);
break;
}
argn++;
}
}
if (argn != 3)
arg_error("Not enough positional arguments");
#undef arg_error
const container_spec *in_container = container_from_name(in_path);
if (in_container == NULL)
error("Unsupported input format");
const container_spec *out_container = container_from_name(out_path);
if (out_container == NULL)
error("Unsupported output format");
const codec_spec *out_codec = codec_from_name(out_codec_name);
if (out_codec == NULL)
error("Unrecognized output codec: \"%s\"", out_codec_name);
container_data ctnr;
// Read input from container
if (in_container->read(&ctnr, in_path, opts.matching))
error("Error reading input file");
// Determine input codec from input container automatically
const codec_spec *in_codec = codec_from_type(ctnr.data_type);
if (in_codec == NULL)
error("Unrecognized input codec: type=%d", ctnr.data_type);
// Decode to PCM16 (this does nothing if in_codec is PCM16)
if (in_codec->decode(&ctnr, in_codec, &opts))
error("Error in decoding");
// Encode to output (this does nothing if out_codec is PCM16)
if (out_codec->encode(&ctnr, out_codec, &opts))
error("Error in encoding");
// Write output to container
if (out_container->write(&ctnr, out_path, opts.matching))
error("Error reading output file");
container_destroy(&ctnr);
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,43 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "util.h"
NORETURN void
error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "\x1b[91m"
"Error: "
"\x1b[97m");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\x1b[0m"
"\n");
va_end(ap);
exit(EXIT_FAILURE);
}
void
warning(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "\x1b[95m"
"Warning: "
"\x1b[97m");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\x1b[0m"
"\n");
va_end(ap);
}

View file

@ -0,0 +1,116 @@
/**
* SPDX-FileCopyrightText: Copyright (C) 2024 ZeldaRET
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#ifndef UTIL_H
#define UTIL_H
#include <stdint.h>
// Endian
#if defined(__linux__) || defined(__CYGWIN__)
#include <endian.h>
#elif defined(__APPLE__)
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define htole16(x) OSSwapHostToLittleInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define le16toh(x) OSSwapLittleToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#define htole32(x) OSSwapHostToLittleInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#define le32toh(x) OSSwapLittleToHostInt32(x)
#define htobe64(x) OSSwapHostToBigInt64(x)
#define htole64(x) OSSwapHostToLittleInt64(x)
#define be64toh(x) OSSwapBigToHostInt64(x)
#define le64toh(x) OSSwapLittleToHostInt64(x)
#else
#error "Endian conversion unsupported, add it"
#endif
#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0]))
#define NORETURN __attribute__((noreturn))
#define UNUSED __attribute__((unused))
#define strequ(s1, s2) ((__builtin_constant_p(s2) ? strncmp(s1, s2, sizeof(s2) - 1) : strcmp(s1, s2)) == 0)
#define MALLOC_CHECKED(length) \
({ \
size_t malloc_len_ = (size_t)(length); \
void *result_ = malloc(malloc_len_); \
if (result_ == NULL) \
error("[malloc] Failed to allocate %lu bytes @ [%s:%u]", malloc_len_, __FILE__, __LINE__); \
result_; \
})
#define MALLOC_CHECKED_INFO(length, fmt, ...) \
({ \
size_t malloc_len_ = (size_t)(length); \
void *result_ = malloc(malloc_len_); \
if (result_ == NULL) \
error("[malloc] Failed to allocate %lu bytes @ [%s:%u] (" fmt ")", malloc_len_, __FILE__, __LINE__, \
##__VA_ARGS__); \
result_; \
})
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define ABS(x) (((x) < 0) ? (-(x)) : (x))
#define FWRITE(file, data, size) \
do { \
if (fwrite((data), (size), 1, (file)) != 1) { \
error("[%s:%d] Could not write %lu bytes to file", __FILE__, __LINE__, (size_t)(size)); \
} \
} while (0)
#define FREAD(file, data, size) \
do { \
if (fread((data), (size), 1, (file)) != 1) { \
error("[%s:%d] Could not read %lu bytes from file", __FILE__, __LINE__, (size_t)(size)); \
} \
} while (0)
#define CC4_CHECK(buf, str) \
((buf)[0] == (str)[0] && (buf)[1] == (str)[1] && (buf)[2] == (str)[2] && (buf)[3] == (str)[3])
#define CC4(c1, c2, c3, c4) (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4))
#define CHUNK_BEGIN(file, name, start) \
do { \
*(start) = ftell(out); \
FWRITE(file, name "\0\0\0\0", 8); \
} while (0)
#define CHUNK_WRITE(file, structure) \
do { \
FWRITE(file, structure, sizeof(*(structure))); \
} while (0)
#define CHUNK_WRITE_RAW(file, data, length) FWRITE(file, data, length)
#define CHUNK_END(file, start, endian_func) \
do { \
long end = ftell(out); \
uint32_t size = endian_func(end - (start)-8); \
fseek(out, (start) + 4, SEEK_SET); \
FWRITE(out, &size, 4); \
fseek(out, end, SEEK_SET); \
} while (0)
__attribute__((format(printf, 1, 2))) NORETURN void
error(const char *fmt, ...);
__attribute__((format(printf, 1, 2))) void
warning(const char *fmt, ...);
#endif