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:
parent
1021c482af
commit
ef329e633a
24 changed files with 4317 additions and 112 deletions
|
@ -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"/>
|
||||
|
|
|
@ -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
14
tools/audio/Makefile
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
29
tools/audio/sampleconv/.clang-format
Normal file
29
tools/audio/sampleconv/.clang-format
Normal 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
2
tools/audio/sampleconv/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
sampleconv
|
36
tools/audio/sampleconv/Makefile
Normal file
36
tools/audio/sampleconv/Makefile
Normal 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)
|
38
tools/audio/sampleconv/src/codec/codec.h
Normal file
38
tools/audio/sampleconv/src/codec/codec.h
Normal 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
|
56
tools/audio/sampleconv/src/codec/uncompressed.c
Normal file
56
tools/audio/sampleconv/src/codec/uncompressed.c
Normal 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;
|
||||
}
|
21
tools/audio/sampleconv/src/codec/uncompressed.h
Normal file
21
tools/audio/sampleconv/src/codec/uncompressed.h
Normal 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
|
1319
tools/audio/sampleconv/src/codec/vadpcm.c
Normal file
1319
tools/audio/sampleconv/src/codec/vadpcm.c
Normal file
File diff suppressed because it is too large
Load diff
50
tools/audio/sampleconv/src/codec/vadpcm.h
Normal file
50
tools/audio/sampleconv/src/codec/vadpcm.h
Normal 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
|
655
tools/audio/sampleconv/src/codec/vadpcm_tabledesign.c
Normal file
655
tools/audio/sampleconv/src/codec/vadpcm_tabledesign.c
Normal 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;
|
||||
}
|
837
tools/audio/sampleconv/src/container/aiff.c
Normal file
837
tools/audio/sampleconv/src/container/aiff.c
Normal 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);
|
||||
}
|
28
tools/audio/sampleconv/src/container/aiff.h
Normal file
28
tools/audio/sampleconv/src/container/aiff.h
Normal 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
|
25
tools/audio/sampleconv/src/container/container.c
Normal file
25
tools/audio/sampleconv/src/container/container.c
Normal 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;
|
||||
}
|
99
tools/audio/sampleconv/src/container/container.h
Normal file
99
tools/audio/sampleconv/src/container/container.h
Normal 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
|
552
tools/audio/sampleconv/src/container/wav.c
Normal file
552
tools/audio/sampleconv/src/container/wav.c
Normal 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;
|
||||
}
|
20
tools/audio/sampleconv/src/container/wav.h
Normal file
20
tools/audio/sampleconv/src/container/wav.h
Normal 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
|
209
tools/audio/sampleconv/src/main.c
Normal file
209
tools/audio/sampleconv/src/main.c
Normal 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;
|
||||
}
|
43
tools/audio/sampleconv/src/util.c
Normal file
43
tools/audio/sampleconv/src/util.c
Normal 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);
|
||||
}
|
116
tools/audio/sampleconv/src/util.h
Normal file
116
tools/audio/sampleconv/src/util.h
Normal 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
|
Loading…
Reference in a new issue