From 4d12799e908f509be89b636df2a92ef3ceb76557 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 28 Feb 2024 13:23:31 +0000 Subject: [PATCH] Dataset building and inference improvements (#10) * update spectrogram params * fix inference and dataset building --- amt/assets/mel_filters.npz | Bin 4271 -> 27910 bytes amt/audio.py | 4 +- amt/data.py | 135 ++++++++++++++++++------------------- amt/inference.py | 30 ++++----- amt/run.py | 15 +++-- amt/tokenizer.py | 4 +- amt/train.py | 9 ++- config/config.json | 7 +- config/models/medium.json | 2 +- config/models/small.json | 2 +- config/models/test.json | 2 +- requirements.txt | 2 +- tests/test_data.py | 11 ++- 13 files changed, 119 insertions(+), 104 deletions(-) diff --git a/amt/assets/mel_filters.npz b/amt/assets/mel_filters.npz index 28ea26909dbdfd608aef67afc4d74d7961ae4bb6..c57535f0a2466d2b612d5fcd5259a2b77aac3bd8 100644 GIT binary patch literal 27910 zcmZ6zbyQT{`vbaxCxHv`hl&@gmJ$I#u}_lo%Y z-h0+!@yFgz?9Y>XpMB<&EXrf12M-=R`|pq9!HwO3(e2;o#e>%mY)x(Sd0Cn596TOA zc=@2^;gkPnJ$SJ5=Fh(e+q37|Vb+)2d#bs;nI_$uoNuD)!>+}8Ub36~3=z-hC5(Fg ziaIs}JpmSh{)*bz#3BJM=+Dk%l4|ZVet7d__i{FMtxSFQ=6nUZgXG_}6@XlJ`3Qt9 zg=`N&yja4q`A1y75_w(9kuA(?ZgQzpYI9YWjJq#CzNNd-TMCfhJ3ToczgEyQroFrZ z`W;w60)_p2>=zA;bs<0GWd+cXp1+Q7G|*jc*qso*Av!DRPBfB*%_XsJNZC!BoKUNF zQ+746&;bb*WzniSPvx*CPiDVOwz;Si-| zHx1uY;lMkJ_4!>red{v}7VubZ*rzN%>UX*oIzZ1$ivL(wmh;c3H^BxZ&OY00yX{8* z6>@VOHT@&=2tFl4^=Vt6O#Ya@vB2!APIh)6G0O-5$oDZ{niPF6_UBQRn?)vl#dydR zJHO|U)BHv>Wy@QQ3*4-g;Fp{}M@wAzwwa>$$zR4n8J~Y!8ZEn_*aCG&Z#rewcF80W zM||e0H|YqgjQRJm^dhD0Wt`3r-~M?rjoz(=P()3~eyYjx&mU$P&lhWRhHdV>sxmoW z^h^1W{-~Fk!uvgHKMBPk^fp09fBLq@Kzl4{|{W!aThKxs54h}9HbWfae8xvEz;f$?&@aoT_#5M{8CyMd# z2ef$8BIYe3J;9hojT473%Nc$I$CL`fbyS16A7RRTj|%#e$9_>Fm5QtfNC@{zM^SZs zo|?-J+p#d8dJfW6McmhIVMG{I@lO{DS$&@GR2u3HA|YzK*gOUKDsgJl<(I0%-=3KY z&MKtr#B~Dxp+dhV$0!F#ELwKWD4gw9ts2~|_CzLUDx2meo|l&Z776-wKL2$W0)9MX z=EfTN2ML-{)a!S+#=uVn*=SbA-JYhk*4Z#}yY%51(s;C&cgw^T-O0LndAvY4!{1PF zVjfo{U|)_Xy0Ss+wm9J$tmeLT6%zf=Rr>MX+~aLE=iKsz73FwwSgOi>$kVj!^eaIB zFrEI|Fr&WW1GX8nLthod4@MRD}hf}LY|rH3DC)Y5zlHs4o# zTG5=;N$3qX#d>&;I_~Q<|C)VQtBuQNrn=5)fSS{C`a1XMdJm7{DNaZ^3_2hDzb0Z* z7LY#S!*@@|2X3tcOpJ7}mY(4Bwvn<~`n;sgZ@~fDI(>q=y5>*Lnfp69!683D$tXgs2FxolCqn1``TR1xLp6lQvDeekR z=l{4D8Bh+b{S^VX+b7q+SB2@){xZ5A65_nH`8aOgh)NJbZ5NjDKk+YsI?}OX$*iZ~ z^2x)iEhcLg6G3rWo9OOSRW7nsl<90$P!XWc@qfrbw|+*lO=G=T0T@^9CpN?Abn;wK z&5C^b12->PHeh85p_^d0SB~1gjyrGeo#wDe!vT55juv^{@xH*ZX>^+>5@vCUX*y+` zRDbg3BVqGS#wy|+gse|DsLR-?a=uCb&YRy}oCXbmzx$&p8m|UPSdDVnR{^(`t9DEh z6AxMS|l|W^LDWloPRNc5W39T&q1}=Ljbk`tN;YKRF%*|Aa-t%deb=! zLXTdqgnH5Vm7DQ_|0RBf-9Pu!s@kk=t4C=wOiBztSiXRKWf42;!m5V#TV6H zi7)g9m2f@ORW70xGRw2jp7xQi>@}Rq{iVGXo{X~(w2w8-Z$1?2Ow`Kfh(^sXJYA#j z`ia>5*eZzYXz(-9!a7UPb-l1osM=6Ao^B2Eeytn{^<#U|6JMH;E{I=_eWQk4pKxQ6 zi>fih)Ohd)RKH1{s$)%YDd8txzn_~mN#?NZ_5Qcs0^3G$LWd$}s6{bs zY%3?#CauJ?M!y){{Ba>4j%ShEoI1=o9V#Qm`xAl?ba0$D=_kJr6dDl8gr$f;4?&(V zlcYi)>YgmC@)F=3*Z%i7{#x4-`a?6&fv}JisdS|V%@_a80gThq`T?h678PPiZ0*CV zM|XD4G-`e8Riu;uJ5XfI#6T3jYq6%Q$Ys4#Y~^>Tbm$*YkSowx$iP%z@VN=E5EGVP zSK$rvq#q})%^yL=}3^jy3 zx7B3_S)u5GCeR@+-17z@j&Q727s{kB(4Qu2tB`-ON_}Ra)o%QMx`=frNl7!6cNncv zuao!^@vjB3@-~8o8MoA`oSW3P7namcCWWm+z`2DbY-*^-$B6yuI3;RRrgG?UD@Bi? z!hQ6k3x>~FXhitwb*4FYR8F{0kP3u{LxNaYJH-MM!0bIPYLZz^YMXF7`iurJpfW4> zML~Me&I~l9R}DGD--a{b?bltH-H(MRpp*9s7Jt7SSEe{i6e8#y%>4lzrziHKf^OMM{?90(UGRsTe%k9J| z+tf*^dUDU#VNKqcdR2nUWc0Wld1N|B?C?xN^bS`J{9pNo&ppzEQH649sY z@H4MU)xpCW2%nc=qASkR@z+q|uaV6UBqpcu+aOI6JI7z!0tx@&uE4fWS)VWRKY7J%x(FQ`F9|#`K$9QZV8}pkOFb^VE#Mo3oju@n`B(5n-7HwmOt!!Dv|R)IqBp|msQ~fj^*+` zakJqzpZa0M8<+T*2A{C$@kk$X&wRJ)Y2DE-tFzE2qM#SH`A8jrCI=jl~D@=U?bF$c)Eo zbi~Hy!*Q9BWriQAo^p}?kp4h~mrNJn+Q+dn=0xl0YrnnMFdzH3hq;+&^l*}OCL*Rp z`rpt|$Zr}+UfXpS5q^$}lr1&$We4;Lg;Z3O7@YN*pK&)m%flkdw?Zvsm}gswJrBjE z_nKqKr>Kb-q9^WyEH5l-Nxv~ z9tl(%5A>U`wj@x+TG0mn%EOP|%XQ=QRvuq+t!rB6yI?G@*!@s+OU4tnUO>;n@NRd- zR82zX)!40jnD`veD{o<9DA+aYHnD0wo&5Q3lJStMtmJ6CT@Y=sUQgm`V7Z~LsFnxg zz>Qlo$6d$p>Uy37*gry4nLznV+j-f;#3P=!N~~+(n$1I!m3c?+E3Hz}p7W?M0e{+$ zoDE%GA0G^cW^Ai@43sK%1r47eukd28C-Hu7@Jk#MSd&XJY8r~$FC-2f7slVAPcDU+ zLxKB++q7&G)uQ#9uCZyX0y z1P$!sH#gwkc;LNb-$7HO=I+9X%{({aP%r9N8_)M!RT@Vu&5uQCh)~&h+r7-{@+aY= zzZ(IO_O1LXfV&MXp=tD+r=1>7l-VSVS4=OLM+nzY&(arHKW)isqC?Ie6SVJ$d5#*Q zC;E|HR2-I4mcEqcPh)fKP^q+WCDyMPSXfVQd*)YzBZNnl324fxt#D)JFF9#j8JS%; z?|BL#Qg2(sfMjn%gxGm)6B$Xtv?Xa~hk{T4*578n=trUfF^2Y)w#Smv>XX~$w*4r4 zhZTZTq&8oLM)+G=$8eB(s2N+wKh{fW$tplJOD*l_-s_H!`V#fN+Shs8c%M9)^%rVi zQ666?JLKFs75F|R;`X~QSL|_ZXXg5~plWH__o>5KLl2BWB7|gIPfi4pt7t#Z#G7$Z zq3ng;hN+f`m;D-lcd!>p(=IM?t9DZgiBJGN_pD-&Elfj;Yv5VcmQCr25`KJV==)4( zRy+i{w59tm;kf zj9itA(KH5Q2YVzOw4D7os#SkDJ)V%!yUx95IfT^(4HaKmaCXR6LGJ{BKZ0RNj;n2U zk7xMN4=K$ZfNfj^v5ikgCP?DPMZ$6mybQ<91V-x~yX^fGt)YD-MW6g7M0u{;Zje-C zs=D*r7vR1tfbeyK(Cd*5!eKLyT9e|k*Z`V_c-s365emja49S@{4zUY*N^`XLBr{8{ zSl!|)_f-o{$)JR|I<;WOk@1d>5Qop0r@I_JE4Cy=OBzEuC{G7{u|4@6YK+&)pWK1$ z(Rk%{iQZ0!_N2#)wRw@7GbQmegNNfY8}h*FCUc7N67rJf_28$d(}v!))R@QMuwe|B z2$$W&O0*ef9(2)_x!<`)Qx-KKH$!YMvU{-n)%oiuZW9~TE{CRdpu$)Y=}Sq=+Hzr! z(J!dxjTt^bxx~`*P%x8SI?SzECb|?lGRO_Pt)b861G=(S4?+j~w?21;`#iJpC1D6i z3s|YdAxLt0K&PRy|IwZ!ajN9k*@A3jJ=LSrkKY#5eg|LL&Mq`n3z7NR`zxVXFKpZl z*A8zL=y>_6+Svuh`K&drI7m`HPpbM^_2O?Q;-|9hgJ-?1KmcnJZp#MWvIl}hog`18 zjHg3w3(OMkGoz1Iis&mZMp3@mdl~LT(m6D9;zRp~p7m>nb%f$XzHY|7gy$@IJJ7Vw zRJ@wpc?sf_ZiCTlm+fdjiXvl{gq>Stn|}Y*EdRhv?cl@c_1sTIxAs+!zKFSI?l1s> z!SUL7x!W;c-2}7N3-bQR^4RI_UR}j1=ZwC1rhEJL3EKJg-l%WHPPOQ(Esw_ClwVkf z=-gCl!@O(hx95EE9;>z-hv&v~c09$M7cfL-3$O2W5>aKFk=%Nk8=}8j?PE`{QIV7{!>mF~$eD?~p zvM88-iF*vl{50^K_SnnNxX17r9T(X*8PqJ$jklP;_)_o?WkC*4uO6JNh z&?~9DhqVF%^5T|S7sb0tUVdRZ{%GiE$?nqR9dX)8>SfmOroP<3BolXPa`Z!J7Vds^ z9_C^>nM~eQ^ufxw`lJ~V)1{fG%(k2}-Gz&+_R&l%A{TuEn70I|VV{S#^Hv9^;HdJ=1#06n&$S~q*)POh|2Me&>b z`Z-kYZPb=yLyfZ^RlmGBh;0NXA4p27b)~SI?m-76%k+G;CFXjgz9&jMSvzENS_#Oh zlhj0PvME+!$$#T)%Cy>wyYJxo@YYEx{j4^#aKFzC_iiDj=lX>gu>~}UPIMTW(NB87 z(fFnV$sad#H1J(T7%~m@m{j=(AB)6IXE?}s&1}u$(AqwwY|3OAYmN`<7tXNo;vxYj z8ym#p1w1|0$m(1?cCR*J3KG;vukXXw#1+rfsgeJH;j8Xi*4VMk$Wyw z@^WZDJ`FpV!#-2#7xSa0M{6yD?_LTVcV?-LI*=5Pk#CQ?wpnsW9?eX?7zbz@NU4(w zZt39q;>#}F>?j^wxI?dModD=$yLd~AMeW5d@$S>;F`alQLX*ZAl8%gci>*qE?&2LVDp}XYvj>ZsV2}dETpA z0I4L#L@kPO1;;D>Ribc+wp@P9L2umSxkx7Sx+pe=T`K(ugN~#ZEY9d&IC6mAT6MD@ z71R=NRlHSVH6)8d_I0LUs4NtTJWcZveC1k5Ua09J`Z1fbSbt*Sy&Hpz0yv;3Hk5Uy znM6;2=$A$_zR|>%Kc|X~xtKHAI?6Rv;MZ08+#8W!hGI25BDt69by|Bbo+4}3< z8^6CN|2KY>I|&7COdGkjWaWxpq**L0w4AXtLbExdhg#(gF3BcOY$B6s6Qag%qXY$x zHHD}BaGIbwhA-8gsFho1bYgLg+M1#qmVGr#;&0hez{C&gx?D7GK!fc{;{*xT9$2qSuKL~6&xwTPAZwz7N60rHHN<<*|4aWK`EhL(}i@E zfAxF(B|luQ^i2_V;cJi>LomxQ+j8VlgOid3Hy`83w;I@2IGhT)PvUsXulb03s;6)V z*YpBOV(aPtQ@!<&_mbgn!SzK6koKI)bR3$Rd0JE1%cj;*68&wfkVh4CjUEgC;i$ZC zR_=1SlrEON(oO#SNha~N-e&cGrS9l}z{v9ZZXmD>Ht`6Pjmc40-_YulBmorpWi|^n zT?2}*cTD^({!qnWo8)Vk+iE5P_KZZN?t#RV;F)b&=o>l7HcuP}2|p?tA!79C8DU<~ z7%r3{`IAR3%83_ufVre|Oj+?aUEHFKTQ8K?v+us_e6csRz4Z1_VwN1O`i5O)7rYz_ zUi=chh3F4@*L^n$4ba zQIx@3F1L4 z5e7$l2|>bXovlJM#NVvYJ4?YntRL$QN9#@q1TDhB9MsS19jR|#l`}-GudZVy` z@G<|7*w7`y&(TvII#|BNBgWU(=DwA8B)zJadWN>qBqncQ9hQeLpDg4_G)t znia60?#ARXg#GM`)k)A}(+>lBFg-mCYR^pPbn;dI>IWub9o}4s@WMe)GD4Zn!IV+} ziq(+dH_~nWsAVS`>}_%wpTw_dqWNW`{>IzMoYLXbODjqF&d6T2Jx{(z7rEcc>Lm4D z5>|rn3rJt8qz`5Wk(Tgsb#VG|#M=~q6V)g*&i56MDP{Pfu;wcjb1Yl;+D9WqZ>>9B zPtxRiC*{B#@whCpTx!QYiQ0EbaK+e}HsQDgN?@`c)Pal!Ow+vr3=4?bcBWm%i~cjV zH@yd(;^cdc8K-&PYE{N3&cReCvmV}aZ^zV_23*?QQ=Xu{3Qk8~FS(g! zz1Q>DPuMHAthH2E;&g)Oe@w?REGp>$8;g}t)5bZ!D)?9vP))WDKFY7i66)1<`62I5 z%-M(1A0V`_$w{>1C)ZNS{BRF;(uMfRwmv*doS^34vg#>f13;i~`2!_XMI~PgIkqgv z;JLF8VSn2qT5KH$I0!3aT&}8qR$Ej4)Y;t5xL*}hQ_Di`st*!~Vw$97r|RI+S}Xy_ zB#2V3nAm-0WFUAaygT(X#n)$uXE<&-wGOb><2Y59>k5c%<=cYaS4b>kXU?{T!p%Zb z!Da~t92Yg&0(F@ZVF=57cuk~#kzn?8c0s0XP(-pgyo19c+M6VS2F;T^cFNY-G_Hem z<5SZoyLIo#u{0N@qejO5IY>w?aVJ5PaS#n{@1AQj7v^?lN2C1L)S_gA@`)!{Ysc+j zZ`|aPI3FuyH5#&-pl^DHi?`U3>zr}DUM)d+iM{J@?dSRfScLfu&GuX4%$SAN^wUTv zUTt&DL$PLVP_0_m2oQJSER$2+g4&^`RWfkuK`zk%P&YOPpmKKu@4oNAhuJJu7xvk??XWl-7qHVGMSNG}r`B#N=_ai4rgQORUeWf&TcV~ObpK$< z`(6FBY&wM?O+w2+4YQehf?-Agu6jdN%L(&Iw~N&`l0HH=ZI@pR!Kv>G=dog~Fk8zM zP!>pr@coiEzVxru|E?_JsPC-&b&htju9rj2d{iv~;dR!5s$=!a3@krjEZSHr0=BPwfd!X)5&;Zv+(vU@Y=6D@b=-S|IbZ{wMSSX_ctZb{@#>e zW9PlUD)ITz|9he0!2<%y`>PVBnuy8ib;q&}%$FVS#Y>4zA(wM+zfQb0M!qw03+^Bw z`1#{)rcp-N3)jVWWA8eD&hp~9)=YQ`uFZO!3l7}Ynazx!9{TsKqK*R^uk=m&S^i#6@K4E50?>)EIo~+^j!jQ>b3Bs#N_IZqIy6da~i`zBD5_&nq&vi^`zI5$c_?w}dqjv1q|m>tEkrj1i^mSUK3xdfuVSy>-}~NpsZqNxt{sV=yP!psK$% zYeC;P2lY<-Hig|{)Bj%7m1ryL<-=zW#x5G{k(t$`>kt2oPn4=zKFrMu8RSChS#7Yb zfcwaEH#@ht{|zgVUCU)9Q35CJA(b}JgRzsa0?#*!+V|3X`{X!NaU_v~x8ndhXmVBX zE|btagiGj7Ib!a-SsRrpAu;n9_bdc5rs9OyNoAMZS{m!mc#FReSzY_G;*GrMeB%%x z4i5NcY`=~=j(2oNZlihn#JcbF;gZYpLQNqG4A7NQU&2T8c<>&XJhvUrUkgjX!7I1T zl!9WeD>xUE>ulP+VWQ#Exqbo9rz+eMFPNB(Q&e^m)vGwO?veS=u3Wkd{#KG6kkG=o zGG?pGnu~txkN3K*xHekydt)K+_(bw=9o>+-DkWA(jnMx=Mp3?HGH_~r71j3DvfXU0 zkhm9*t<*jFtmXO8s6L-`Q;qj%HTg`j-wZUToxFdeA zyKVm_y~o>DuIVUye#TM6#oO&hVIk{(!s}Vy(I$M$-8g+X4^*YlK%$NxUNmL?^Y?z! zLfE*~2rxvJA8DT?t2I)?o9B^7c<(Y>NBw{Unk94$Oht1Hus$r&xK#_-wENE|dN5>~lvu487Mlb$rYj(u~||Ae!Ld-e9*V^k>dQ{=+)>PxD{gQ{4;c{%32YnEAI-YpEf_zpRJZDF00PjB&_dl{Y5md<={C zx}Xp&LWpdBz5!HMxQ3${O5EijV#IpvH%1*b4OF3t>?a?g8!@&6Rf{z%^{xth@`bql z<`{qG8Dy#FD!!c0{!rh&7NIZ@*Fhw3n>j}$Aljs8vO06eirdC>T_XX+D;~g`I!q-? zgPiY?o~r((>2tYBw)XVToKmGMBUs|+^K%!Qtb_~MwhAJ*%_0eOsZmLBe~UC-4a@tr z^@+O@&@N@mllKoMOe<`*;_m`4az&1ip%k9(yP}DI1s1yWhvt6VZk&QHz1Ce!&SrZe zybf0hF`rMuxxxcwtVRBN@eK>O20sM%mzK!kW7lp$9i0%%k{5ew#nI8 zcw(DTICF_kR#@_XMFYneRF!9BHk;O;PnGSdV+eZaDq_s#XVHfs#@d3d2O^%k+@(01 z!RhXIvSCE1rkdZ?Q1j9XOu>N{9%IiD58+47ay-}IlqO%SVp04*d|RG^`+P@cuJ_(Q z#k0xeLKEL~t?`-Sx>5{HB?3p*O|N$)yYg56cRF~d1^2sc^;ger(`b%mE|7&A&Z9mu znbrLblJ>m)h>=t#E(C2;>TCOdli@3iAP3EQ-9m+568~g6AUv{Bz8z5Zv&QGlk1zlLP|!rg3d}2y+QfP;uD6HPcnXc86RKHLU8RmZ~fTg2x(tHPn|&+I#;l zVO_Q81{)_*;q+Ebab7OII3c*pvePVyYam+-V67=&>kI#&$a1DT_x;$^?2d%^a7U@! zJx@SpN1z^H=z~BrdN1C-3GZdLGmJwHf!s(r~27FYX)Y2o%zG|*riOsOilZ# z9d(mudh?DI9(W7pwL%Aci2eSIibm|Qd%!u?0c=TMJ0ozX=Rr|MfbSA%25)eqC#`jg5g1Q*TCfMGy62QKx?16K$x|q@7#0P$e|Jk(YUl7~E~U zM1J;kuF~k>?IUCr^%C4H)-H@egtOQM!^g>-j@$&`B29P8+Dki}UZe%lCP@CY9tjxs z^5g>_mRIy^3h2)3YlCHd%q{Ca2q6mOx%$Wsq{%pwnNGf-)ds)(qb>2syE^D{Y6@e0 zb_6EbuqsP22?WKgW4&#n?!tVAbvCh~qy)Vmyc5_!We+V+X81$Y(s?zsR20Zq_YpfF zZ~TQu5i)9O7HHMtB6MjPnV5uwU?~pe=W-i&R!f8LiE`wqz)5>C|4yJ)X zHXs=7aK7DPn}GdE?i$Xc-X%u~{M{lYzs;-GHt#a>`2Y{NnW%E=MH1B!I@NuMU_SaA zOx+#eG&!=a|iD#9KXpn5(|{hPMM)oHac zc~ZH1$;nJ-li55K)Tf=QJzRMa{G264;_Sx=KAyI!g<|Q8dY_1D9gCswaE~hGA7}^k zc2UWp(D(PmoQxjr#kTzF$(U&o7;4U&1uk4iD;p>L$4Bnhg3NN}ueZU}?UHVlOod8{ zX>j4wSf>jI>Ym7Y_<B63uqf&hYlM1pm?11?mtpTdW zxT`VUa$lIk#^y=TJr4rHgbwe<9majdT_+ou z3S|`#aDfe|a(y~`wOZ?q#Gzo5-idO`WKQ{`%L8JA{pZId&!LD(=jAkgQc@=dVpPwM)>o(Dy{2FWuMNc1&x_Cjdq8_XSa?$hiP+n zUE5h2{EBx}wNOobEI_RL7gBQBY1=-^wZ2q=KwqUo<61mR<BkY+fp2s&8Kc^bThS5|W zac&zZGFgqw`7~Nt?9RVHZne8i-S$aO3b9H!=~>eEfzq;Z{xC8?`S>EQf|D`H2-JZ3 z^Y)m4Z^czP5&eSsw1jVizQJm$4_vlWFf2a}wkqCzp*CJu@xpoqeFL}-k-;LqC~Yg7KKVvb3`JMRs_UvPr#~CHve?eavn8sY?+;euy4T z&a78A%!SWXhQ$wX$4{irgY+AdnzBMXwju4iV%Y63u7)W_RplH9l6NRI2e$(2pl)*RB;mL^p+ z6OcXINHD2Vkwk^)V_}n}8MXO)4^N?=EZk9PTJwNg^ z=h@aCwt#tv2(f|dcTQc0z~oLAZ7n>?8_UlfBM=AN2FWD(n(`!vJe>di;Lbc=FVELjhg=2HSJDEQ-%!S z(T&=IvZ<;;j!6enIL#hrS~66By(>-gwiqYmwkc$I8j^A4;Ty!+N5G=Zx(hPJE3W?W8PEXwh>{m*0zg9*qh5Xm48`8H2g%rGEI7J zT^oIPm%y&tADLlsLC}2Icmx}%@(3ChQM0Jn}tD;pD6jGiAe>&cId z7v<*&;3A$^IIvT@I}*5<@_6d9QG#`_a@jths;?;KD%6hr>tNT0~+>%1@PO_7!K zIEN_JzIEkU|15JJEIHcsQm?z{tT&Oh_d}O^ot0G1!G)9ktL-t)IJ?PXT|AvSCY(8P z&|KI)_HQ=Pr(Hno-OCc?@?X>(Y{g`Ke?)vv8y%MxW%PL=7N4jAC2lZFb+R9MQ^&(4 zqwH!6TJEAXNjs#pY~XK7osN?v)@RNwJB8)T(wT>DC#y6aH@NRMQqCd50#E&q7j@0T zo{E2nnlxB;+V|i049l=;g(%1v=RRYp z41d!UOSFP+Y!z}&nq|KD!lUS0KfRKS`58Xq5^-Qb+J(NkzZxhw2UG`z&C@Y$vP2fw z(ZIUT9!P_=R5No^xoEE*#dG};^qW>qxbgEjLGz>yI-EJhbsQu(@Kn}HEIYm&Y1;p* z?Vz?ze&6HtLn2}zH|;=RbDx8{>VPGT2q>Hc4ZNkg9LtkA%QYT$LekJxQv&)iNhpQ< zd16?y&}5_=Oe(qy5_-Is9Ke2_X?N0^bJd5n!@p~ia}*#cM$F`dJr}>^Yh)Og$Y&KHQt_r_3hSkJyRucU{;AR~xp6?$=^CO}3b8um8w9dk)^qH?-s3;{2txy#hB;Oo-3OlM`l@ zC-T;3p0wVL8lfE;R{&@K3+eF=(5-(cCf{UT7w|O@YH;X;i}Gil7joSf%73dk!Hapm%=`T6uRm=F7ey-O)SD z!O(X(MJ}gf%~Qu)Q8z(K#`L610_Ok2y4|gdtj=?_7~R)XpWXcWZQhy*B7X6_>b;7W z?ag&V$mWTd(QQ4IpBn!=$g17Tyl}Mkh7l@05$aDePe)@i`v0pN2TCJb@5xoRu3Y*V z{~)^;T}F;H;8xQ&nd%T?%cu+LS$XQKY`FN~ykf+~gWW8FmwAQ;h#e-=eCQ{81jlz` zaQ4J-?aolp-ag(0yKHJ*)onemqo30Vmr@4X4jas|^X+@qGlr8=Ig%2=%(|l3-B$!~ z02ZN+OnQjgs&LbjKVmih1;)$ z9ur3!PH@R~u&MdIxVP*hfM=drD=SQDafN}stK`_1&&|7axNNxGKL8pd0>Xh$7c5KZM`@c`$VkR2Yx$Adc!9${*misU)hZb_s zW#sebE6fMuTX);D)^+(bt{RV6JU*3!=H50pbOmYDJ)0b27E1+Mv6RdT=J?o4(gCkC z0=@Sd&Rx5vkDw#b{(n$!QCvJc@dv$M1DM^;Qm*y%GN2kp!OxlGGbF@?*TaKHxbv$U&@LuAZvOua4!1?(nks zsv^77f^k{VUX9#z9OF64Nq3Y0vV+Mz&<4gY`xJ2{PXk=18d_?i@{J9qBCClO=fO%s zG0jU{Mt$;ML)0@9@E3^|AtVdpVVga9={YY6qABtiV{lF2JZyet zFxX=eYn}%d~V0Xh+@g0$}0| z&2$pTOZmPgV3%kX%t<|hU}d_&T++EqKq6n_e3KYcwvY+9sc4~XBO^3DBHuznS!k-* z2|SLi8MKmEr3y_`-qyMH#?pR(uV|LYm(x3je?Q@8P9Z7zNzzE`5hfBN{_O_D{zS3o zqzN@!p~lu}*te>3y8Qi4O#-tUG$e8W<6Cx%w@86|P2-tbAA=WEkZT~*Ezw9#v~PI* zBaP^j4C=eg#DcHVg&5B9(5o;xc{j4VV1|==oND?8$!`vOw90t1<3>?nbmf&l$sXG(= z8I=;7D>I$cdsmgw!8wzkF*$IUAy z%w7s7o%ueY+BW9fe|`x1`N(!<>fj+Zmx$_er<^oU_npe+4RdU@T@)I*E8GG?scZz( zk`5JO*#MD7W6Pe%%!+GMTPzSgPifXv?@yu(SXGFm%#o17i+f|KnO*i>z@vNrQ3tix zoK1Q9o{!4ATf2&PmyE_eo7NIgsLeS+X~=-fAK-rL!`z${WCubaOkwiZ^4$kg90vP( zBOW)RDytBi@)=0&YlT7z=O$w5lj|?+N&)a1eOlSfLmNUzE{Q@G9cq?o8_;oB;4pI0 z&@q&s@}XO}?0zch@D7)%TRxig-IY2A>?n#Xd3pV&U+;(UVOpF`1lBAlwB%ZnnAm z2tzLlPx}M^f1k)Mm-Y_RZbJ)vu0Qvry*5^;+O(Ab(OY@jvD7@y>4h9C-rn`BEw?P~ z85&>)LOkt{4g7?8ZMt$vbwjzT%9$QFC!jhI%ePV8wKtB!rc`TOl%fG#u?&3t!9c`I zz8X$dcHso(DyzO|VVpQv)QI(R{I2_Q86pjIKjx5Th3pR}H zyc#QF&Xm&5qY3ULw2 zlJGdHjK|Zk&?Y1$dCOPW&oeeKHd&(miKQ#}GK73hYse=!$Fc0~s+Uf>v=(odD%Omd zS)Cpy7FkZhimj6rhl;8&egT!^ytOQR`X*|@!F;mPtW>p-=K~G(cli8Hq_VAHh+LIE z9ZzR5PPqBB$t+aY$?yvAT$x8thl43DR}#N_Z*Q_Zdta3=*|||ioBI=e{Zkn!9S%ON zjOy3Z%1Tc?U&mfII7#hCV42-!!)YwczDkn|4RDs8aE~O`C7oY}YYRp@4d1;(IKwAA ztopvzhouB6raLi9kTh0VhVb=qdN1#s z;6-hVX%sk_=f~Xge>gEsp?Ub^?TXz#0ilxbkBM;rgY2eVY>j<2Z15v;8MqzGUOWBP zgHTxGsa+!T0E=8q^L$vuYlxncDL}icV#y`1xhJWiyocNyZq!^$=5yCG_(gW3)(GXf%$g-6)unHYW#rI#n-)ID&^1m~ z~baM#GNIg*8RzXT9-P4N`7}=hnSDlnbljp|-s>BmHK>+t;Ah(1SsO zzh#ubuLQc5j^tp%;Ga%lFz+}zc+O3+jZZLNCme9&Rpif)Ge$BkG z%M5Dyc4(rw#Yo4=EcaC)EDk~GFJ%yFr+lWyz}K7s?cFp!-}gQzAHX}`*C`<%$iCb^ zY*@YW&sy!8XA|)qzjD9nLLP`f=c=yUuWyOk{{NS1E)9}v|Nob2*txj>U8;F}_s${c zu1^2=`y(sbAT8ouNAXW=FFPKiu$4-_8od-AFg47Y(9AM+a|_M?4 z)=rRxyaGWKygw3YF$k8qha62d8&7 zz`8z7vL2pp+dK|?Eto!jNS;#XxZ9g1^? z>9QCgmy`kAzx=m$b%8h2Xjhmbbr$Jb)9h{5=@B~Q80t*Tdbs zibcM?lsT7YB{M!H?2VcB^IhPPIVA0!e@-vp;`6vW3q?dS8u}@k_Z{|o_WfDSH&16@ zkWQupTXm0J6~&6eMDP)iYzi`Vccyyh-5IU8_MXU5+JyrGUeHvxCk^#Ao@?H|=?Dhg zWp=<63z4lhm?%aB;L(Nxi!E_FiwbF^1>=aAb8~;h7(pXo8FPtz*SWNMZ?2EfQz>BM zsAGSQA;J;+Wxpw%W3X15e3zC^zqkhk0r-SNtI+Q`&b%CW=jW@*Q4aXr(+XtQALPC~ z#p(yzhkdY-ob@u{a|~&Yji5i%n|ngr=FWXL9(FA$zukTa2Y*D?@gy))UwO!-^Z#tE zj#yFTt$nMvB)EX%H@s(FkwAEU{pZ5q_u&u*kg-kAx$CaCu@2ns(TA>3xp`3g44afvdFrdfz;H-5=dB}fH(KHS2CSp*nw zSCQo+e-5(=m=VUE|Boux!}r`BSJu7oQMkJ{Dv2uR@afYD3v%ZC32WGiAUoaMC02dC zKT3*Qg?I~kZr@a-vz@0ktL^_w>4;)Xj+-r?*hp^eIRUG8sD#0pZP0tM#pG+|F8S>a z19#QBRA&|p&++R-fXp9$4i}4_LQ?E|Mq8;U7H(uS=;Zc4JT<6ofL3|eq2I0uQ0O9- z(Je3i%O~I4?;$q@m*;7l7Z$xz%y&J#&tY~{u6@zx3A9%;?USdt_bKAJ^w8e;pKh29 zDvG?l!$qTd??;`50So8t{(_Yi9~5bZcn>^7oZu=*qzur9G7B8mf#RSuUxLnwy5Ek; z85sFX$i0S^B5}-Z(f&B;FVNvquOdbBub-y8+C^@82fLk6Qr|y9fs>BeHiZnJq_|G< zEPQ@FYSCDAZtLN&N+Cg(LYT>-a@k2Fzd(bo+c~lBgq6IxelyL^*v2}O5<51ROmkY5 zc?HB_3uN-%znph_&beIj1N1V|l^pNPU5d=)?6dFQTGSTvIwwrk7Mw8`^oHY2t3F1(woG>$|ZAe&y)YlD{eN=_5D%n56_K{#@@+r?h*UFK-=N zYF(Z`k$Z>Ij^4K-1W)x zQ9t`p<}?citIc!tP85^UyGrK7`Kq$`JzUB z7*S=0aullBy&;B_QL!N{icPbqDSUd0i4SbF#{SE9(}H=0w5w;5k7|?kjRzT>hPq2H zh`%FM8U)@k*PCRr={We2)3)?#jnvTG#dO5dN&!?W9$$bwcxNdJBw0qMkJ40d^e>FR z_RM%p%7g109chZRj(CL)r59a+{a{vxUbg)HPK=a?gesdHMqpe&Bn9$1)iIu(#y!8@ z4~dBNcJ(W-+23Z|BSBMIJ&Fy+CLS&d^EO6^1QkzNQHHXJ)4EsQ)pdh$YFa8nrj4S{ zBXplnADAc{ujV0ov#zRwv>chc>@>b3{FJxuX|8W%n9kdAAfaq9@v*UMxvFBJn=vwh zWBwHnWjJD1HL9^f2_k)BSD_p>mnfH)%Fzm!xZ{NDVU@zQiN8!qgVz(=G`zqol@oQ~ zL(s1X=+vwtUFTV|khmJ|)nh5xxN4>3q+&s_T^8jSjrJCV5|t0Xs9~pYSe}7hRtSkF zzt+Wv1sZv-9~43|4xzJhLon*qsvnU667l!FnAcnl_e32muh%RHgmcp`rBWP_(Jy~| zQvWah1d+ADAf3QVx*ocv#F!DpA1A1~rz?_p*iuFTglMUZJFrD}M_=dd-b+P#G7@ns`^=%r4Z`QT5l_4Gy0GCk%N=PH#8$!WEykS*vPl-#@lGAL%>0fMa%B zK*iy|jV>F2bdZvI)hSMhyg^XMM-d-R0&u%sMcq*^BXU&R{3thk0$LZEdGH zH|BAHg0xtztmULccWYRe=Gd@DbAb51<`=W5QY5EW2dzS2X=ho_IJQs(5FdOjY^K8Fa82>U0XO`lR2f z7{?{!ZSe23riZ!SxGQkXZ8;=xyi%<^#ko-&(K2Pvz@rG!Hnpyh3KkcmmcrdwEtB^SBXaSo1VAf6FPw2TS7DXSw?@$1wtpZ;+gCpx!+CsdFNB@BLVGE9Kzrep|d9W@)UlmsEw8CuFe`t zvhkCqc%-o7BxaKsbBn=rzoF{hthHWpV?N8vD>y|YQ{96z5rJ|Cdg7J^TBvBffW6!X zNHW&_*`|E$0xTaGD_Tm9s&7mHK7?(2N_GkWV)Q}K6M6VsIOuc#{f)th@3 z(cvxK}{(584aVGX~0-zTQG(NZ3btk}nxq8?n(~Q5r#?5(oC@f#axQjU{-4Dn-Vflcp zOm74k;?QF`K5SOn%+9fkWT$BP6<^sZ}YaI5%zr>GU_kDaZ}gVDK9avN^j{gGp2x9M{*l&T2`m_*=n0L-f|#qoDFnfyrBytZAM9)J%aJo%Mz#Pn>UY8 z#wKoK`nmoB=dGZo(k;tYhT&#{`n4ufnFmACgI(#%Bh57xi`{;)sw6Lk9!gCbj-JN8 zOiR12+5QWH65bB)U8zwHaQ?ecani)S{YcW!m(-Msfb`FGlxZtZAu zAK^%4;en^peS+ESyM4(%jkssbM|QOmty0?SdCW#u-`^E^_nI_%l&Fz|yxiT8Q{|~+ zv_Tp&e0VqUWbmI_b?_>2Cg}^ZVYJToAIhKzz_OI#qZoBy9BYZTufx!G5GyVW3=^ph zC_NO}(`ZNYqDhN(=~o+sJvJgy;t^x{pEz99N29mK|MV978m&u(^+(`g1t5? zh8Jf83%tDg_bMmvbd6od{0Zivav?xnX)tok)4n@N5ruza$h+xruZRQz-Hxa|^iJAA z=zC@|B#x#@Zk^x#Me8JFWhTLFg9qMVuQY0@VJ5k=jVKVeeB)jg6Qb0B6B62 z+m_RFh%(G0_4ju zVZB(0b$e`zF+*f|PDtWNDcPUv?+kug$o42=acr{v-__>snqnW=*B=jUML>z1@<@z&t+CqJ-rSoqMjp9eZ zb0eG``dALu_|ci6b5IKDa}*QZ#77z}#5|-K(=C}J?4Wo?_t9(($bEQEbF5X%5a$Yc zMra@}zRWIp$)S4e3zinKPhA{GD99vFWW539+M}Z+P@B!Z;;^6XUf0hIFa4@)I%0(x z=ySDpH(^e8fuBsd#wU~4X8qbLH$ap z673b1{x#Z%!rEm3Bc#)V9zRgU_-wFSBANd9J5ggNRx4Z^lXK}oRbHc6Ete%8+C@^s z8jRTA)#do)5l~MJ;jA0!)D=#+@an4V!Vc&!t_3np^@pi(3z6J z%OdK3DSXUlf@N!!#B=V zGDdol-5FhO!u?GN>4&z_@hJNjl{vh0aNDb*{pc+d58YFd-*>SkvE=zP)Np*&cLm{i zJDxq!lXpyqzU^N;Zn9w5@?Bi2BvT>u`taZztPpM>G>nEIewi~L1iugy((QP1E@0k4iE?LZ^Kbj z;4f3Cs^@7V2JlOlUT&k?I1XntOphDZD({%$`$!2+VCxM>i9VJZi=0YsqMWh+)<9wb z3WQ7-+6LywGx$4}?{=TS_*gY$=N_ghO(o1_qw6FlNU^v{RyKmCO4n&f5zxl|$txo_ zRox+n=o@So0no$<4LLZXDoRd9WGgq?-5GQx^tjl>2gybA0t%wl$|YdfLKi=@aue3c zQ0@`vnIL|+Z&%%bVJdWn3+r6n^CKMZ$8;ADD~@K>Tm9zzl`}|BC8z6h(8#-A9xrpT zb(zV9+8_7uZky_a@U1iD2VYemiDyOOz6tyL42@{YblT%90n)2vx4X?pAzuYM$Bzlz z6Nbgw9Z=T`?=HGSgh1&|C1QHb9KyqT8<&M~KX?x&I1H}neQBbs-5n9#DXZFiH=?P+ zW9LUaXpV6VU#G!g!`C*p+-F3^v_(#!^G>? z5>8a!DDlQ)rGy;8?^R~9|6272K9}iPoHR{~yg8yeZ9`c3^1!DQ6xeXGn19lfs=SSn ziy3|IPwLApUZ0(H9C=+eYxey+=xnU1jO3wRB)8)>x^=_IZ&YQ{&nCo%Sl>X+WB57~ zSr@NX+?72#Qny0p{p;{3on1xg;|1RW-a*g8pf!-O*47=ToMu4Ss0zIeKC11}_R*NU z5l7A!HOC~SzLN^g@-{uXk|Onee=^zwc!)g^<1#g9^Sm-{&xbo*`Z{6R-UE?B{9{$n zB8daSTp*WFHT-_06My2{o5Ve&32LlEz7vZqKgCs?nGtxJk|flCR@h19OF1h3v^Tzj z%8Jbcr$iV*UW8mpyE$M3ND<7k<{CN^a}niB=#W(8o70bm2RQ8H;~}LjeRDx zQIjf@USiRHW*;eiJrR>Xy$hBRFA3e=op@9wov+JEHvl(W&Trfu!EMGOLSM>A>v2C1 zRoxkVVmoA)v3H@&lEKKQSJ7ihZdf1%2az6FpsBXgqDlkUzBOgXo5hJzfMpMpp zy-5=<_F8mC722gMe?i9txrTfU1sWS(-DT;MKO0D(T$riGps8bI$&JDa9e83R|2-R< zu@@0{cQ7G(gj3hJ-FiXU`csE=B+WiP!_kRhawT;>uTIC9K`-j%oA_~3HCbs0%*_Tv zC3-V16*BhuG+}90Py!jz={(kc6=S!h)(GCJxFqt<)Y1eCFEs5;lhaPA=6Ip{J-fAd zFs4T9fhZ|G;K>-k-1MYb9~Be;sI_@|vnTOIuAywS8)b}^7o#*Ko(0JCGze%Qb)RIu zSQ1>`3ewNsu76t-Kc&EtP~1it4_k{v`PFBYF%zMq%5C9&^S^nmFI1sxbUMUYc}8?}!O4y#^c&j|!Bb)lbhYc@^=Rh!MH432cv#IU*FVVAkQ& zLkYU4P5(-G`RPmXkJm=C|&tdsSXd z-iscep~~9MqKf%D%JdQ6oslX>d;9mkDZ+UZ8sREqTI-Xgpp7CHA8z9G;POwvLBDNX z)=n22{~>?bG47N^*rQYB4$r2lHvZ7C?Uehbw!4b5Nd9u6_+T|@6U6`^Qx;(IN`a%E zFRw|HS^ey1JHA*`H(nH5K9WD73k-ifH|GW-ZLCT|_8V!?Ff$OJiEGL2z9i&)Yp;sY zse?;6L_>K9vYLf%_5uC;@jT@VT47J5Go8QQOqp2CTwdO8ca4$2b5pu%kW_?aenB68 z2Prwd@mHb=t@TXxu~z96=e49{;G*(wE>KuRdvE-BJzQB;s!v z9oXMG-Ytc%1z_cmlO>j;15p?UwR`n2T+SX>zsxz-BZFGnY~54WUmual*x4Kaerht8S;Ke05NAN9F~)FNhc#K+?E+el{VS|Wjy0HKgz&iPj6)|4-Bn9)kqEMK zKr07@V&=~=W3V1*#-?_zd`Q{EhHHS_kRf$%scgvk)I}EK5{Ce^RBfS;nDOlG*%t9W z<_$EV5Lx1l-xh}N#`JhJr;AF=x_A^VXvtO_*o@QzO%=D+iSu&x_?UcmxqAT6xnUaf zT6)AG50LfY0rght5(Jx>0@x_d_F{<}ZNV`yc<+a* z6u4^1uUtzMg1}zK4tcH0O@}YlhMWbIqj|!SL{q})%uU~AxA(39yYw3c-o~4U{&Bk% zlkUq|F-fB1Wis9vwu(MR>E(k9O(i8{l5R2M}EueE#bWlBKoZ+WaNWw7cu zLIzcQA z4Aou2(z9~2ai)bQ2=p#noy2gvvL^<45~6@+MZB4rQzQLcUP<;{%ko6CjnN-V74U!- zg?oa;xI$_m7vt#~7n-HEqo&R8(OKvSOq+}zopk73SHoZr3ny#p(}Dap(Rx0E254J+^zdH!&b5 ze@JzQVIR2MIKSSkSPaS^y!+=5d?QoVvDO<>cKMT&{`V9x^_&FOwoe`2e&?TKm5)&F zT}5$U6CWaA+}0$lJNYpM@;6uCHY8j@Y!4iYZGjNIy_hA7^2%z+bGS6{F|R2iUJEbu z>E&CmD$+vW?+T?sjl=Yms%q8XPs|*(7Z%!hU6CCg-h)jq*3~=n#b^@yTb$!P`YSO$ z7B=2JGRQz`qbN<=#DFIjGJG^}zPo);h;>KIkzfgk4M++PGBvlFy~Z0%0oDO09r~Sh z_c%=s+m+^jKXq2N#|hmBgcgq*z23VC&vUdL(?;^#6?tRyfI0S?Ztd?k)`Z^cQ(kai zwkmoV61Q(p8yu{^LcC3||ya3ykkyPKH+UynT!~eJ#dv$)0;byYC?p(0zU9Q7SjqbRCM4^;~4or+>l@#;G6qssC=adIt*$ejqN zMxPunNbQoTVL_Y8Jq{c8wqh+E=BTBQj33U`>wwcLWUrOAZ_Y%-rrM<`(@%5d)ML>i zZHt+bX5RMSlXy_p>-seYoA5fS=?CJ2eTh?j63P>wF1qHfi4vKXcFtj8@*P3=3PYTn zcYg56*+<5!b0#X+iUlvcP7M~E9@1RiU+ixL(~ne(-t|y!b4BIFxQU1ARVIHS;piu3kuAK+$e^ab&8AIbvG9p{e1Nzg%zkq z6UmeA&pGL#ni@v%ItiM;_g1dK8vSH%mhQ(&$ooNw{JLS@t(Jyb5H|y9W8rV1?7vY_ za--2<8vC`nIr%?ja5L{WKqzGJ2`GHXHJM;+pvJZISgU`XuX>{l-Ca z$Lq%E!F0zD&v>!yWnLw(gC4TZYaiTT=$DG*yJw`L-x@c(gx!R%&vYNUpk;1XEhWs? z`Dy4ie5RWYqh%NV8vW|3ZDN!uY$e}FYSxqj)8+$T2g{-2@&IU>p=kSX^r~3fCm=F= z@Km=hnZ-r-bM67#NxL9!`H8o?VY3Cp_Aro2tSi%bRtPs9s6HFLp5zcvrk})Oz`<{$ z9<|cGtbCk`W-DrE-3?9eLsK5ii+{ZLee}>Kd6M5tPvGkEr^M-ZhO2ctN>dY$7{z{9 z!j|s27FAX(0^^he+#bU#fOI2{Yc(QxRE57NYc?jmKrnw4AnxT_GjIPl7g0|v;R>T$ zSFt~F)g@oL8b(G7p4N90`_D3X1;K3Cx1hj12wp`t<7Q!NIB1 zmD8&l$`>y3Uixnv{ogKv^c}Ia`+uB$F^sgZo*D8-;8|SxpRbDj?|%QiRqMZ3X8rg2 zEYcY!r61RWKhC~JMt;ZE|L*u-<=}s1fq}nE1pohz0ly>re+tQUgc{11uly(T((ixp M-+$#D{`=qm1unl!)&Kwi literal 4271 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 AQvd(} diff --git a/amt/audio.py b/amt/audio.py index f9bc27e..d03b1a3 100644 --- a/amt/audio.py +++ b/amt/audio.py @@ -116,7 +116,7 @@ def mel_filters(device, n_mels: int) -> torch.Tensor: mel_128=librosa.filters.mel(sr=16000, n_fft=400, n_mels=128), ) """ - assert n_mels in {80, 128}, f"Unsupported n_mels: {n_mels}" + assert n_mels in {80, 128, 256}, f"Unsupported n_mels: {n_mels}" filters_path = os.path.join( os.path.dirname(__file__), "assets", "mel_filters.npz" @@ -127,7 +127,7 @@ def mel_filters(device, n_mels: int) -> torch.Tensor: def log_mel_spectrogram( audio: Union[str, np.ndarray, torch.Tensor], - n_mels: int = 80, + n_mels: int = 256, padding: int = 0, device: Optional[Union[str, torch.device]] = None, ): diff --git a/amt/data.py b/amt/data.py index ec90141..f10bbaa 100644 --- a/amt/data.py +++ b/amt/data.py @@ -1,11 +1,9 @@ import mmap import os -import logging -import json -import jsonlines +import shutil +import orjson import torch -from typing import Callable from multiprocessing import Pool from aria.data.midi import MidiDict @@ -17,36 +15,19 @@ N_FRAMES, ) -config = load_config()["data"] -STRIDE_FACTOR = config["stride_factor"] +config = load_config() +STRIDE_FACTOR = config["data"]["stride_factor"] -def setup_logger(): - # Get logger and reset all handlers - logger = logging.getLogger(__name__) - for h in logger.handlers[:]: - logger.removeHandler(h) - - logger.propagate = False - logger.setLevel(logging.INFO) - formatter = logging.Formatter( - "[%(asctime)s] %(name)s: [%(levelname)s] %(message)s", - ) - - ch = logging.StreamHandler() - ch.setLevel(logging.INFO) - ch.setFormatter(formatter) - logger.addHandler(ch) - - return logger - - -def get_features(audio_path: str, mid_path: str = ""): +def get_features( + audio_path: str, mid_path: str = "", return_json: bool = False +): """This function yields tuples of matched log mel spectrograms and tokenized sequences (np.array, list). If it is given only an audio path then it will return an empty list for the mid_feature """ tokenizer = AmtTokenizer() + n_mels = config["audio"]["n_mels"] if not os.path.isfile(audio_path): return None @@ -57,7 +38,7 @@ def get_features(audio_path: str, mid_path: str = ""): return None try: - log_spec = log_mel_spectrogram(audio=audio_path) + log_spec = log_mel_spectrogram(audio=audio_path, n_mels=n_mels) if mid_path != "": midi_dict = MidiDict.from_midi(mid_path) else: @@ -79,19 +60,37 @@ def get_features(audio_path: str, mid_path: str = ""): else: mid_feature = [] + if return_json is True: + audio_feature = audio_feature.tolist() + res.append((audio_feature, mid_feature)) return res -def get_features_mp(args): - """Multiprocessing wrapper for get_features""" - res = get_features(*args) +def write_features(args): + audio_path, mid_path, save_path = args + features = get_features( + audio_path=audio_path, + mid_path=mid_path, + return_json=False, + ) + dirname, basename = os.path.split(save_path) + proc_save_path = os.path.join(dirname, str(os.getpid()) + basename) + + with open(proc_save_path, mode="ab") as file: + for mel, seq in features: + file.write( + orjson.dumps( + mel.numpy(), + option=orjson.OPT_SERIALIZE_NUMPY, + ) + ) + file.write(b"\n") + file.write(orjson.dumps(seq)) + file.write(b"\n") - if res is None: - return False, None - else: - return True, res + return proc_save_path class AmtDataset(torch.utils.data.Dataset): @@ -127,9 +126,9 @@ def _format(tok): self.file_mmap.seek(self.index[idx]) # Load data from line - spec, _seq = json.loads(self.file_mmap.readline()) + mel = torch.tensor(orjson.loads(self.file_mmap.readline())) + _seq = orjson.loads(self.file_mmap.readline()) - spec = torch.tensor(spec) # Format spectrogram into tensor _seq = [_format(tok) for tok in _seq] # Format seq _seq = self.aug_fn(_seq) # Data augmentation @@ -142,15 +141,15 @@ def _format(tok): seq_len=self.config["max_seq_len"], ) - return spec, self.tokenizer.encode(src), self.tokenizer.encode(tgt) + return mel, self.tokenizer.encode(src), self.tokenizer.encode(tgt) def _build_index(self): self.file_mmap.seek(0) index = [] while True: pos = self.file_mmap.tell() - line_buffer = self.file_mmap.readline() - if line_buffer == b"": + self.file_mmap.readline() + if self.file_mmap.readline() == b"": break else: index.append(pos) @@ -162,33 +161,33 @@ def build( cls, matched_load_paths: list[tuple[str, str]], save_path: str, - num_processes: int = 4, + num_processes: int = 1, ): - def _get_features(_matched_load_paths: list): - num_paths = len(_matched_load_paths) - for idx, entry in enumerate(_matched_load_paths): - success, res = get_features_mp(entry) + assert os.path.isfile(save_path) is False, f"{save_path} already exists" + num_paths = len(matched_load_paths) + with Pool(processes=num_processes) as pool: + sharded_save_paths = [] + res = pool.imap_unordered( + write_features, + ((ap, mp, save_path) for ap, mp in matched_load_paths), + ) + for idx, proc_save_path in enumerate(res): if idx % 10 == 0 and idx != 0: - print(f"Processed audio-mid pairs: {idx}/{num_paths}") - if success == False: - continue - for _audio_feature, _mid_feature in res: - yield _audio_feature.tolist(), _mid_feature - - # MP CODE DOESN'T WORK FOR SOME REASON !! - - # with Pool(num_processes) as pool: - # results = pool.imap(get_features_mp, _matched_load_paths) - # num_paths = len(_matched_load_paths) - # for idx, (success, res) in enumerate(results): - # if idx % 10 == 0 and idx != 0: - # print(f"Processed audio-mid pairs: {idx}/{num_paths}") - - # if success == False: - # continue - # for _audio_feature, _mid_feature in res: - # yield _audio_feature.tolist(), _mid_feature - - with jsonlines.open(save_path, mode="w") as writer: - for audio_feature, mid_feature in _get_features(matched_load_paths): - writer.write([audio_feature, mid_feature]) + print(f"Finished {idx}/{num_paths}") + if proc_save_path not in sharded_save_paths: + sharded_save_paths.append(proc_save_path) + + # This is bad, however cat is fast + if shutil.which("cat") is None: + print("The GNU cat command is not available") + else: + print("Concatinating sharded dataset files") + shell_cmd = f"cat " + for _path in sharded_save_paths: + shell_cmd += f"{_path} " + print() + shell_cmd += f">> {save_path}" + + os.system(shell_cmd) + for _path in sharded_save_paths: + os.remove(_path) diff --git a/amt/inference.py b/amt/inference.py index 83fd61d..31eca8f 100644 --- a/amt/inference.py +++ b/amt/inference.py @@ -17,6 +17,9 @@ # sort of branching to make sure that we don't miss notes, ect... Implement this # next week -- Exciting problem (checkout other inference algos) +# Implement maximum note len =5s +# Implement either beam search or decoding initial onset note on first + def greedy_sample( model: AmtEncoderDecoder, @@ -38,6 +41,7 @@ def _process_segment( audio_seg = audio_seg.unsqueeze(0).to(device) seq = tokenizer.encode(tokenizer.trunc_seq(prefix, MAX_SEQ_LEN)) seq = torch.tensor(seq).unsqueeze(0).to(device) + audio_feature = model.embed_audio(mel=audio_seg) for idx in ( pbar := tqdm( @@ -46,21 +50,14 @@ def _process_segment( leave=False, ) ): - logits = model.forward(mel=audio_seg, tokens=seq[:, :idx]) + logits = model.logits( + audio_features=audio_feature, tokens=seq[:, :idx] + ) next_tok_id = torch.argmax(logits[0, -1], dim=-1) - # probs = torch.softmax(logits[0, -1], dim=-1) - # next_tok_id = torch.argmax(probs, dim=-1) - - # Debug logging: - # print(f"input seq shape: {seq[:, :idx].shape}") - # print(f"logits shape: {logits.shape}") - # print(f"probs shape: {probs.shape}") - # print(int(next_tok_id), tokenizer.id_to_tok[int(next_tok_id)]) + seq[0, idx] = next_tok_id if next_tok_id == pad_id or next_tok_id == eos_id: break - else: - seq[0, idx] = next_tok_id if idx == MAX_SEQ_LEN - 2: print("WARNING: Ran out of context when generating sequence") @@ -81,7 +78,7 @@ def _process_segment( model.eval() tokenizer = AmtTokenizer() _unclosed_notes = [] - concat_seq = [] + concat_seq = [tokenizer.bos_tok] _onset_adj = 0 for idx, _audio_seg in enumerate(audio_segments): _seq = [("prev", p) for p in _unclosed_notes] + [tokenizer.bos_tok] @@ -99,14 +96,17 @@ def _process_segment( __midi = __midi_dict.to_midi() __midi.save(f"/weka/proj-aria/aria-amt/samples/res{idx}.mid") - print(f"Done {idx}/{len(audio_segments)}:\n{_seq}") - + print(f"Done {idx + 1}/{len(audio_segments)}") for tok in _seq: if type(tok) is tuple and tok[0] == "onset": _onset_orig = tok[1] _onset_adj = _onset_orig + (idx * LEN_MS) concat_seq.append(("onset", _onset_adj)) - elif tok is tokenizer.pad_tok: + elif type(tok) is tuple and tok[0] == "prev": + continue + elif tok is tokenizer.bos_tok: + continue + elif tok is tokenizer.pad_tok or tok is tokenizer.eos_tok: break else: concat_seq.append(tok) diff --git a/amt/run.py b/amt/run.py index 0c164dd..ec0eb9f 100644 --- a/amt/run.py +++ b/amt/run.py @@ -40,12 +40,15 @@ def build_maestro(args): assert os.path.isdir(args.dir), "MAESTRO directory not found" assert os.path.isfile(args.csv), "MAESTRO csv not found" - if ( - os.path.isfile(args.train) - or os.path.isfile(args.val) - or os.path.isfile(args.test) - ): - print("Dataset files already exist - overwriting") + if os.path.isfile(args.train): + print(f"Dataset file already exists at {args.train} - removing") + os.remove(args.train) + if os.path.isfile(args.val): + print(f"Dataset file already exists at {args.val} - removing") + os.remove(args.val) + if os.path.isfile(args.test): + print(f"Dataset file already exists at {args.test} - removing") + os.remove(args.test) matched_paths_train = [] matched_paths_val = [] diff --git a/amt/tokenizer.py b/amt/tokenizer.py index 180ab6c..3a8d58b 100644 --- a/amt/tokenizer.py +++ b/amt/tokenizer.py @@ -241,7 +241,6 @@ def _detokenize_midi_dict( if DEBUG: raise Exception else: - notes_to_close[tok_1_data] = (tok_2_data, tok_3_data) elif tok_1_type == "off": if tok_2_type != "onset": @@ -322,6 +321,9 @@ def export_data_aug(self): def export_msg_mixup(self): def msg_mixup(src: list): + def round_to_base(n, base=150): + return base * round(n / base) + # Process bos, eos, and pad tokens orig_len = len(src) seen_pad_tok = False diff --git a/amt/train.py b/amt/train.py index a6a846e..ed473bb 100644 --- a/amt/train.py +++ b/amt/train.py @@ -138,9 +138,9 @@ def _get_optim( optimizer = torch.optim.AdamW( model.parameters(), lr=lr, - weight_decay=0.01, - betas=(0.9, 0.95), - eps=1e-5, + weight_decay=0.1, + betas=(0.9, 0.98), + eps=1e-6, ) warmup_lrs = torch.optim.lr_scheduler.LinearLR( @@ -365,6 +365,9 @@ def train_loop( # Backwards step accelerator.backward(loss) + if accelerator.sync_gradients: + accelerator.clip_grad_norm_(model.parameters(), 1.0) + optimizer.step() optimizer.zero_grad() if scheduler: diff --git a/config/config.json b/config/config.json index 0e77778..be4cfd7 100644 --- a/config/config.json +++ b/config/config.json @@ -11,12 +11,13 @@ }, "audio": { "sample_rate": 16000, - "n_fft": 400, + "n_fft": 2048, "hop_len": 160, - "chunk_len": 30 + "chunk_len": 30, + "n_mels": 256 }, "data": { - "stride_factor": 3, + "stride_factor": 1, "max_seq_len": 4096 } } \ No newline at end of file diff --git a/config/models/medium.json b/config/models/medium.json index 9d93f9b..45c0de6 100644 --- a/config/models/medium.json +++ b/config/models/medium.json @@ -1,5 +1,5 @@ { - "n_mels": 80, + "n_mels": 256, "n_audio_ctx": 1500, "n_audio_state": 512, "n_audio_head": 8, diff --git a/config/models/small.json b/config/models/small.json index fd29fa3..1c87733 100644 --- a/config/models/small.json +++ b/config/models/small.json @@ -1,5 +1,5 @@ { - "n_mels": 80, + "n_mels": 256, "n_audio_ctx": 1500, "n_audio_state": 384, "n_audio_head": 6, diff --git a/config/models/test.json b/config/models/test.json index 5ad3f27..93c0f16 100644 --- a/config/models/test.json +++ b/config/models/test.json @@ -1,5 +1,5 @@ { - "n_mels": 80, + "n_mels": 256, "n_audio_ctx": 1500, "n_audio_state": 64, "n_audio_head": 4, diff --git a/requirements.txt b/requirements.txt index 571a4d0..ebf748f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ torchaudio accelerate mido tqdm -jsonlines +orjson diff --git a/tests/test_data.py b/tests/test_data.py index 7e2d85d..4ba3d4a 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,6 +1,7 @@ import unittest import logging import os +import time from amt.data import get_features, AmtDataset from amt.tokenizer import AmtTokenizer @@ -26,7 +27,13 @@ def test_feature_gen(self): class TestAmtDataset(unittest.TestCase): def test_build(self): - matched_paths = [("tests/test_data/147.wav", "tests/test_data/147.mid")] + matched_paths = [ + ("tests/test_data/147.wav", "tests/test_data/147.mid") + for _ in range(3) + ] + if os.path.isfile("tests/test_results/dataset.jsonl"): + os.remove("tests/test_results/dataset.jsonl") + AmtDataset.build( matched_load_paths=matched_paths, save_path="tests/test_results/dataset.jsonl", @@ -53,7 +60,7 @@ def test_maestro(self): dataset = AmtDataset(load_path=MAESTRO_PATH) for idx, (mel, src, tgt) in enumerate(dataset): src_dec, tgt_dec = tokenizer.decode(src), tokenizer.decode(tgt) - if (idx + 1) % 200 == 0: + if (idx + 1) % 100 == 0: break if idx % 7 == 0: src_mid_dict = tokenizer._detokenize_midi_dict(