diff --git a/extensions/MagentaDude/sam.js b/extensions/MagentaDude/sam.js
new file mode 100644
index 0000000000..c124ff89c4
--- /dev/null
+++ b/extensions/MagentaDude/sam.js
@@ -0,0 +1,522 @@
+// Name: SAM
+// ID: samtts
+// Description: Use the classic SAM text-to-speech voice via SamJs.
+// By: MagentaDude
+// By: lselden
+// License: MIT
+
+(function (Scratch) {
+ "use strict";
+
+ if (!Scratch.extensions.unsandboxed) {
+ throw new Error("The SAM extension must run unsandboxed");
+ }
+
+ // SamJs library taken from https://discordier.github.io/sam/dist/samjs.min.js
+ // The SamJs code in this extension does not have a license due to being reverse engineered from a commercial software
+ // All of the other code in this extension is under the MIT license though
+
+ /* eslint-disable */
+ // prettier-ignore
+ function initSamJs() {
+ // emulate commonjs environment so iife wrapper below doesn't pollute globals
+ const exports = {}; const module = {exports};
+ /**
+ * This is SamJs.js v0.3.0
+ *
+ * A Javascript port of "SAM Software Automatic Mouth".
+ *
+ * (c) 2017-2024 Christian Schiffler
+ *
+ * @link(https://github.com/discordier/sam)
+ *
+ * @author 2017 Christian Schiffler
+ */
+ function H(){let Y=(H,e)=>0!=(H&e),r=H=>{let A=new Uint8Array(H.length);return H.split("").forEach((H,e)=>{A[e]=H.charCodeAt(0)}),A},t=H=>{var e=new Uint8Array(4);return e[0]=H,e[1]=H>>8,e[2]=H>>16,e[3]=H>>24,e},O=H=>{var e=new Uint8Array(2);return e[0]=H,e[1]=H>>8,e},I=null,R=H=>{let e=new Uint8Array(44+H.length),A=0,E=H=>{e.set(H,A),A+=H.length};return E(r("RIFF")),E(t(H.length+12+16+8-8)),E(r("WAVE")),E(r("fmt ")),E(t(16)),E(O(1)),E(O(1)),E(t(22050)),E(t(22050)),E(O(1)),E(O(8)),E(r("data")),E(t(H.length)),E(H),e},l={" ":0,"!":2,'"':2,"#":2,$:2,"%":2,"&":2,"'":130,"(":0,")":0,"*":2,"+":2,",":2,"-":2,".":2,"/":2,0:3,1:3,2:3,3:3,4:3,5:3,6:3,7:3,8:3,9:3,":":2,";":2,"<":2,"=":2,">":2,"?":2,"@":2,A:192,B:168,C:176,D:172,E:192,F:160,G:184,H:160,I:192,J:188,K:160,L:172,M:168,N:172,O:192,P:160,Q:160,R:172,S:180,T:164,U:192,V:168,W:168,X:176,Y:192,Z:188,"[":0,"\\":0,"]":0,"^":2,_:0,"`":32},n=128,U=(H,e)=>0!=(l[H]&e),N=(H,e,A)=>U(H[e],A),W=(H,e)=>-1!==e.indexOf(H),A=H=>{let e=H.split("="),E=e.pop(),A=e.join("=").split("("),r=A.pop().split(")"),t=A[0],O=r[0],R=r[1],I=["T","C","S"],Y=["E","I","Y"],l=(H,e,A)=>{if(H.startsWith(O,e)&&((e,A)=>{for(let H=t.length-1;-1!N(e,--A,n),"#":()=>N(e,--A,64),".":()=>N(e,--A,8),"&":()=>N(e,--A,16)||W(e.substr(--A,2),["CH","SH"]),"@":()=>{var H;return!!N(e,--A,4)||"H"===(H=e[A])&&!!W(H,I)},"^":()=>N(e,--A,32),"+":()=>W(e[--A],Y),":":()=>{for(;0<=A&&N(e,A-1,32);)A--;return!0}}[E]())return!1}return!0})(H,e)&&((e,A)=>{for(let H=0;H!N(e,++A,n),"#":()=>N(e,++A,64),".":()=>N(e,++A,8),"&":()=>N(e,++A,16)||W(e.substr(++A-2,2),["HC","HS"]),"@":()=>{var H;return!!N(e,++A,4)||"H"===(H=e[A])&&!!W(H,I)},"^":()=>N(e,++A,32),"+":()=>W(e[++A],Y),":":()=>{for(;N(e,A+1,32);)A++;return!0},"%":()=>{if("E"!==e[A+1])return"ING"===e.substr(A+1,3)&&(A+=3,!0);if(N(e,A+2,n)){if(!W(e[A+2],["R","S","D"]))return"L"!==e[A+2]?"FUL"===e.substr(A+2,3)&&(A+=4,!0):"Y"===e[A+3]&&(A+=3,!0);A+=2}else A++;return!0}}[E]())return!1}return!0})(H,e+(O.length-1)))return A(E,O.length),!0};return l.c=O[0],l},S={},f=(" (A.)=EH4Y. |(A) =AH| (ARE) =AAR| (AR)O=AXR|(AR)#=EH4R| ^(AS)#=EY4S|(A)WA=AX|(AW)=AO5| :(ANY)=EH4NIY|(A)^+#=EY5|#:(ALLY)=ULIY| (AL)#=UL|(AGAIN)=AXGEH4N|#:(AG)E=IHJ|(A)^%=EY|(A)^+:#=AE| :(A)^+ =EY4| (ARR)=AXR|(ARR)=AE4R| ^(AR) =AA5R|(AR)=AA5R|(AIR)=EH4R|(AI)=EY4|(AY)=EY5|(AU)=AO4|#:(AL) =UL|#:(ALS) =ULZ|(ALK)=AO4K|(AL)^=AOL| :(ABLE)=EY4BUL|(ABLE)=AXBUL|(A)VO=EY4|(ANG)+=EY4NJ|(ATARI)=AHTAA4RIY|(A)TOM=AE|(A)TTI=AE| (AT) =AET| (A)T=AH|(A)=AE| (B) =BIY4| (BE)^#=BIH|(BEING)=BIY4IHNX| (BOTH) =BOW4TH| (BUS)#=BIH4Z|(BREAK)=BREY5K|(BUIL)=BIH4L|(B)=B| (C) =SIY4| (CH)^=K|^E(CH)=K|(CHA)R#=KEH5|(CH)=CH| S(CI)#=SAY4|(CI)A=SH|(CI)O=SH|(CI)EN=SH|(CITY)=SIHTIY|(C)+=S|(CK)=K|(COMMODORE)=KAA4MAHDOHR|(COM)=KAHM|(CUIT)=KIHT|(CREA)=KRIYEY|(C)=K| (D) =DIY4| (DR.) =DAA4KTER|#:(DED) =DIHD|.E(D) =D|#:^E(D) =T| (DE)^#=DIH| (DO) =DUW| (DOES)=DAHZ|(DONE) =DAH5N|(DOING)=DUW4IHNX| (DOW)=DAW|#(DU)A=JUW|#(DU)^#=JAX|(D)=D| (E) =IYIY4|#:(E) =|':^(E) =| :(E) =IY|#(ED) =D|#:(E)D =|(EV)ER=EH4V|(E)^%=IY4|(ERI)#=IY4RIY|(ERI)=EH4RIH|#:(ER)#=ER|(ERROR)=EH4ROHR|(ERASE)=IHREY5S|(ER)#=EHR|(ER)=ER| (EVEN)=IYVEHN|#:(E)W=|@(EW)=UW|(EW)=YUW|(E)O=IY|#:&(ES) =IHZ|#:(E)S =|#:(ELY) =LIY|#:(EMENT)=MEHNT|(EFUL)=FUHL|(EE)=IY4|(EARN)=ER5N| (EAR)^=ER5|(EAD)=EHD|#:(EA) =IYAX|(EA)SU=EH5|(EA)=IY5|(EIGH)=EY4|(EI)=IY4| (EYE)=AY4|(EY)=IY|(EU)=YUW5|(EQUAL)=IY4KWUL|(E)=EH| (F) =EH4F|(FUL)=FUHL|(FRIEND)=FREH5ND|(FATHER)=FAA4DHER|(F)F=|(F)=F| (G) =JIY4|(GIV)=GIH5V| (G)I^=G|(GE)T=GEH5|SU(GGES)=GJEH4S|(GG)=G| B#(G)=G|(G)+=J|(GREAT)=GREY4T|(GON)E=GAO5N|#(GH)=| (GN)=N|(G)=G| (H) =EY4CH| (HAV)=/HAE6V| (HERE)=/HIYR| (HOUR)=AW5ER|(HOW)=/HAW|(H)#=/H|(H)=| (IN)=IHN| (I) =AY4|(I) =AY|(IN)D=AY5N|SEM(I)=IY| ANT(I)=AY|(IER)=IYER|#:R(IED) =IYD|(IED) =AY5D|(IEN)=IYEHN|(IE)T=AY4EH|(I')=AY5| :(I)^%=AY5| :(IE) =AY4|(I)%=IY|(IE)=IY4| (IDEA)=AYDIY5AH|(I)^+:#=IH|(IR)#=AYR|(IZ)%=AYZ|(IS)%=AYZ|I^(I)^#=IH|+^(I)^+=AY|#:^(I)^+=IH|(I)^+=AY|(IR)=ER|(IGH)=AY4|(ILD)=AY5LD| (IGN)=IHGN|(IGN) =AY4N|(IGN)^=AY4N|(IGN)%=AY4N|(ICRO)=AY4KROH|(IQUE)=IY4K|(I)=IH| (J) =JEY4|(J)=J| (K) =KEY4| (K)N=|(K)=K| (L) =EH4L|(LO)C#=LOW|L(L)=|#:^(L)%=UL|(LEAD)=LIYD| (LAUGH)=LAE4F|(L)=L| (M) =EH4M| (MR.) =MIH4STER| (MS.)=MIH5Z| (MRS.) =MIH4SIXZ|(MOV)=MUW4V|(MACHIN)=MAHSHIY5N|M(M)=|(M)=M| (N) =EH4N|E(NG)+=NJ|(NG)R=NXG|(NG)#=NXG|(NGL)%=NXGUL|(NG)=NX|(NK)=NXK| (NOW) =NAW4|N(N)=|(NON)E=NAH4N|(N)=N| (O) =OH4W|(OF) =AHV| (OH) =OW5|(OROUGH)=ER4OW|#:(OR) =ER|#:(ORS) =ERZ|(OR)=AOR| (ONE)=WAHN|#(ONE) =WAHN|(OW)=OW| (OVER)=OW5VER|PR(O)V=UW4|(OV)=AH4V|(O)^%=OW5|(O)^EN=OW|(O)^I#=OW5|(OL)D=OW4L|(OUGHT)=AO5T|(OUGH)=AH5F| (OU)=AW|H(OU)S#=AW4|(OUS)=AXS|(OUR)=OHR|(OULD)=UH5D|(OU)^L=AH5|(OUP)=UW5P|(OU)=AW|(OY)=OY|(OING)=OW4IHNX|(OI)=OY5|(OOR)=OH5R|(OOK)=UH5K|F(OOD)=UW5D|L(OOD)=AH5D|M(OOD)=UW5D|(OOD)=UH5D|F(OOT)=UH5T|(OO)=UW5|(O')=OH|(O)E=OW|(O) =OW|(OA)=OW4| (ONLY)=OW4NLIY| (ONCE)=WAH4NS|(ON'T)=OW4NT|C(O)N=AA|(O)NG=AO| :^(O)N=AH|I(ON)=UN|#:(ON)=UN|#^(ON)=UN|(O)ST=OW|(OF)^=AO4F|(OTHER)=AH5DHER|R(O)B=RAA|^R(O):#=OW5|(OSS) =AO5S|#:^(OM)=AHM|(O)=AA| (P) =PIY4|(PH)=F|(PEOPL)=PIY5PUL|(POW)=PAW4|(PUT) =PUHT|(P)P=|(P)S=|(P)N=|(PROF.)=PROHFEH4SER|(P)=P| (Q) =KYUW4|(QUAR)=KWOH5R|(QU)=KW|(Q)=K| (R) =AA5R| (RE)^#=RIY|(R)R=|(R)=R| (S) =EH4S|(SH)=SH|#(SION)=ZHUN|(SOME)=SAHM|#(SUR)#=ZHER|(SUR)#=SHER|#(SU)#=ZHUW|#(SSU)#=SHUW|#(SED)=ZD|#(S)#=Z|(SAID)=SEHD|^(SION)=SHUN|(S)S=|.(S) =Z|#:.E(S) =Z|#:^#(S) =S|U(S) =S| :#(S) =Z|##(S) =Z| (SCH)=SK|(S)C+=|#(SM)=ZUM|#(SN)'=ZUM|(STLE)=SUL|(S)=S| (T) =TIY4| (THE) #=DHIY| (THE) =DHAX|(TO) =TUX| (THAT)=DHAET| (THIS) =DHIHS| (THEY)=DHEY| (THERE)=DHEHR|(THER)=DHER|(THEIR)=DHEHR| (THAN) =DHAEN| (THEM) =DHAEN|(THESE) =DHIYZ| (THEN)=DHEHN|(THROUGH)=THRUW4|(THOSE)=DHOHZ|(THOUGH) =DHOW|(TODAY)=TUXDEY|(TOMO)RROW=TUMAA5|(TO)TAL=TOW5| (THUS)=DHAH4S|(TH)=TH|#:(TED)=TIXD|S(TI)#N=CH|(TI)O=SH|(TI)A=SH|(TIEN)=SHUN|(TUR)#=CHER|(TU)A=CHUW| (TWO)=TUW|&(T)EN =|(T)=T| (U) =YUW4| (UN)I=YUWN| (UN)=AHN| (UPON)=AXPAON|@(UR)#=UH4R|(UR)#=YUH4R|(UR)=ER|(U)^ =AH|(U)^^=AH5|(UY)=AY5| G(U)#=|G(U)%=|G(U)#=W|#N(U)=YUW|@(U)=UW|(U)=YUW| (V) =VIY4|(VIEW)=VYUW5|(V)=V| (W) =DAH4BULYUW| (WERE)=WER|(WA)SH=WAA|(WA)ST=WEY|(WA)S=WAH|(WA)T=WAA|(WHERE)=WHEHR|(WHAT)=WHAHT|(WHOL)=/HOWL|(WHO)=/HUW|(WH)=WH|(WAR)#=WEHR|(WAR)=WAOR|(WOR)^=WER|(WR)=R|(WOM)A=WUHM|(WOM)E=WIHM|(WEA)R=WEH|(WANT)=WAA5NT|ANS(WER)=ER|(W)=W| (X) =EH4KR| (X)=Z|(X)=KS| (Y) =WAY4|(YOUNG)=YAHNX| (YOUR)=YOHR| (YOU)=YUW| (YES)=YEHS| (Y)=Y|F(Y)=AY|PS(YCH)=AYK|#:^(Y)=IY|#:^(Y)I=IY| :(Y) =AY| :(Y)#=AY| :(Y)^+:#=IH| :(Y)^#=AY|(Y)=IH| (Z) =ZIY4|(Z)=Z".split("|").map(H=>{var e=(H=A(H)).c;S[e]=S[e]||[],S[e].push(H)}),'(A)=|(!)=.|(") =-AH5NKWOWT-|(")=KWOW4T-|(#)= NAH4MBER|($)= DAA4LER|(%)= PERSEH4NT|(&)= AEND|(\')=|(*)= AE4STERIHSK|(+)= PLAH4S|(,)=,| (-) =-|(-)=|(.)= POYNT|(/)= SLAE4SH|(0)= ZIY4ROW| (1ST)=FER4ST| (10TH)=TEH4NTH|(1)= WAH4N| (2ND)=SEH4KUND|(2)= TUW4| (3RD)=THER4D|(3)= THRIY4|(4)= FOH4R| (5TH)=FIH4FTH|(5)= FAY4V| (64) =SIH4KSTIY FOHR|(6)= SIH4KS|(7)= SEH4VUN| (8TH)=EY4TH|(8)= EY4T|(9)= NAY4N|(:)=.|(;)=.|(<)= LEH4S DHAEN|(=)= IY4KWULZ|(>)= GREY4TER DHAEN|(?)=?|(@)= AE6T|(^)= KAE4RIXT'.split("|").map(A)),T="*12345678".split(""),E=" *.*?*,*-*IYIHEHAEAAAHAOUHAXIXERUXOHRXLXWXYXWHR*L*W*Y*M*N*NXDXQ*S*SHF*TH/H/XZ*ZHV*DHCH**J*******EYAYOYAWOWUWB*****D*****G*****GX****P*****T*****K*****KX****ULUMUN".match(/.{1,2}/g),i=[32768,49408,49408,49408,49408,164,164,164,164,164,164,132,132,164,164,132,132,132,132,132,132,132,68,4164,4164,4164,4164,2124,3148,2124,1096,16460,9280,8256,8256,9280,64,64,9284,8260,8260,9284,8264,8256,76,8260,0,0,180,180,180,148,148,148,78,78,78,1102,1102,1102,78,78,78,78,78,78,75,75,75,1099,1099,1099,75,75,75,75,75,75,128,193,193],o=[0,4626,4626,4626,2056,2824,2312,2824,3592,3851,2822,4108,3082,1541,1541,3595,3082,3594,3082,2825,2056,2055,2825,2567,2310,2056,2054,2055,2055,2055,770,1285,514,514,514,514,514,514,1542,1542,2055,1542,1542,514,2312,1027,513,286,3597,3852,3852,3852,3598,3593,2054,513,514,1797,513,257,1798,513,514,1798,513,514,2056,514,514,1540,514,514,1798,513,1028,1798,257,1028,1479,1535],Z=(e,A)=>{var H=E.findIndex(H=>H===e+A&&"*"!==H[1]);return-1!==H&&H},P=e=>{var H=E.findIndex(H=>H===e+"*");return-1!==H&&H},b=(r,t,O)=>{for(let E=0;EY(i[H],e),B=23,V=57,w=69,m=1,y=2,J=8192,k=4096,Q=2048,x=1024,j=256,a=128,u=64,s=32,$=16,_=8,L=4,G=2,h=1,q=(A,E,r,t)=>{let H=(H,e)=>{switch(H){case 53:D(r(e-1),x)&&E(e,16);break;case 42:A(e+1,43,t(e));break;case 44:A(e+1,45,t(e))}},e=(H,e)=>{E(H,13),A(H+1,e,t(H))},O=-1,R;for(;null!==(R=r(++O));)if(0!==R)if(D(R,$))A(O+1,D(R,s)?21:20,t(O)),H(R,O);else if(78===R)e(O,24);else if(79===R)e(O,27);else if(80===R)e(O,28);else if(D(R,a)&&t(O))0===r(O+1)&&null!==(R=r(O+2))&&D(R,a)&&t(O+2)&&A(O+2,31,0);else{var I,Y=0===O?null:r(O-1);if(R===B)switch(Y){case w:E(O-1,42);break;case V:E(O-1,44);break;default:D(Y,a)&&E(O,18)}else 24===R&&D(Y,a)?E(O,19):60===Y&&32===R?E(O,38):60===R?(I=r(O+1),D(I,s)||null===I||E(O,63)):(72===R&&(I=r(O+1),D(I,s)&&null!==I||(E(O,75),R=75)),D(R,h)&&32===Y?E(O,R-12):D(R,h)||H(R,O),69!==R&&57!==R||0{for(let H=0;null!==r(H);H++)if(D(r(H),j)){for(var e,A=H;1<--H&&!D(r(H),a););if(0===H)break;for(;H>1)+e+1))}let R=-1,I;for(;null!==(I=r(++R));){let H=R,e,A,E;if(D(I,a))I=r(++H),D(I,u)?(e=null===I?u|h:i[I],Y(e,L)?(E=O(R),t(R,(E>>2)+E+1)):Y(e,h)&&(A=O(R),t(R,A-(A>>3)))):18!==I&&19!==I||!D(r(++H),u)||t(R,O(R)-1);else if(D(I,Q))null!==(I=r(++H))&&D(I,G)&&(t(H,6),t(H-1,5));else if(D(I,G)){for(;0===(I=r(++H)););null!==I&&D(I,G)&&(t(H,1+(O(H)>>1)),t(R,1+(O(R)>>1)))}else 0{let E=0;for(var r;null!==(r=H(E));)D(r,u)&&null!==(r=H(E+1))&&D(r,a)&&0!==(r=e(E+1))&&r<128&&A(E,r+1),++E},e1=(H,e,A)=>{let E=0;for(var r;null!==(r=H(E));){var t=e(E);A(E,0===t||127>8),E++}},A1=(A,H,e)=>{let E=-1;for(var r;null!==(r=A(++E));)if(D(r,G)){if(D(r,h)){let H,e=E;for(;0===(H=A(++e)););if(null!==H&&(D(H,_)||36===H||37===H))continue}H(E+1,r+1,e(E),255&o[r+1]),H(E+2,r+2,e(E),255&o[r+2]),E+=2}},E1=[24,26,23,23,23],r1=[0,224,230,236,243,249,0,6,12,6],M=[0,31,31,31,31,2,2,2,2,2,2,2,2,2,5,5,2,10,2,8,5,5,11,10,9,8,8,160,8,8,23,31,18,18,18,18,30,30,20,20,20,20,23,23,26,26,29,29,2,2,2,2,2,2,26,29,27,26,29,27,26,29,27,26,29,27,23,29,23,23,29,23,23,29,23,23,29,23,23,23],d=[0,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,3,2,4,4,2,2,2,2,2,1,1,1,1,1,1,1,1,1,1,1,2,2,2,1,0,1,0,1,0,5,5,5,5,5,4,4,2,0,1,2,0,1,2,0,1,2,0,1,2,0,2,2,0,1,3,0,2,3,0,2,160,160],K=[0,2,2,2,2,4,4,4,4,4,4,4,4,4,4,4,4,4,3,3,4,4,3,3,3,3,3,1,2,3,2,1,3,3,3,3,1,1,3,3,3,2,2,3,2,3,0,0,5,5,5,5,4,4,2,0,2,2,0,3,2,0,4,2,0,3,2,0,2,2,0,2,3,0,3,3,0,3,176,160],t1=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,241,226,211,187,124,149,1,2,3,3,0,114,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,0,0,25,0,0,0,0,0,0,0,0,0],O1=[0,5980947,5980947,5980947,5980947,7230474,6113550,5980947,5783320,5842971,5712919,5775125,5383440,5844244,6113550,4075794,5383182,5774866,4076306,7218448,5250317,6112527,5904395,3944978,7216654,5904395,7230217,5320198,7943686,6641158,7943686,5980945,6506758,6967046,5315078,7946758,6113550,5383440,6107913,6767114,4990984,6106890,6639366,6639366,7946758,6639365,7958022,0,5916691,5777179,5775125,5778203,5774866,5382669,5315078,5315078,5315078,7946758,7946758,7946758,7368198,7237126,7237126,6181894,6181894,6181894,5315078,5315078,5315078,7946758,7946758,7946758,6647046,6641162,7367946,6181894,6181894,6181894,556844,98067],C=[0,0,0,0,0,526861,461581,527630,527887,68879,68623,3087,68367,2316,461581,330508,68623,3087,396301,67597,2061,461838,2061,330252,67597,2061,526861,780,2313,198153,0,0,0,0,0,0,0,0,779,66827,779,1035,0,0,1,66827,920064,66050,593422,68879,3087,68879,3087,2061,2,260,0,2,260,0,1,260,0,1,260,0,0,0,0,0,0,0,0,461324,0,0,330240,0,1245199,1048591],R1=[56,132,107,25,198,99,24,134,115,152,198,177,28,202,49,140,199,49,136,194,48,152,70,49,24,198,53,12,202,49,12,198,33,16,36,105,18,194,49,20,196,113,8,74,34,73,171,106,168,172,73,81,50,213,82,136,147,108,148,34,21,84,210,37,150,212,80,165,70,33,8,133,107,24,196,99,16,206,107,24,140,113,25,140,99,53,12,198,51,153,204,108,181,78,162,153,70,33,40,130,149,46,227,48,156,197,48,156,162,177,156,103,49,136,102,89,44,83,24,132,103,80,202,227,10,172,171,48,172,98,48,140,99,16,148,98,177,140,130,40,150,51,152,214,181,76,98,41,165,74,181,156,198,49,20,214,56,156,75,180,134,101,24,174,103,28,166,99,25,150,35,25,132,19,8,166,82,172,202,34,137,110,171,25,140,98,52,196,98,25,134,99,24,196,35,88,214,163,80,66,84,74,173,74,37,17,107,100,137,74,99,57,138,35,49,42,234,162,169,68,197,18,205,66,52,140,98,24,140,99,17,72,102,49,157,68,51,29,70,49,156,198,177,12,205,50,136,196,115,24,134,115,8,214,99,88,7,129,224,240,60,7,135,144,60,124,15,199,192,192,240,124,30,7,128,128,0,28,120,112,241,199,31,192,12,254,28,31,31,14,10,122,192,113,242,131,143,3,15,15,12,0,121,248,97,224,67,15,131,231,24,249,193,19,218,233,99,143,15,131,131,135,195,31,60,112,240,225,225,227,135,184,113,14,32,227,141,72,120,28,147,135,48,225,193,193,228,120,33,131,131,195,135,6,57,229,195,135,7,14,28,28,112,244,113,156,96,54,50,195,30,60,243,143,14,60,112,227,199,143,15,15,14,60,120,240,227,135,6,240,227,7,193,153,135,15,24,120,112,112,252,243,16,177,140,140,49,124,112,225,134,60,100,108,176,225,227,15,35,143,15,30,62,56,60,56,123,143,7,14,60,244,23,30,60,120,242,158,114,73,227,37,54,56,88,57,226,222,60,120,120,225,199,97,225,225,176,240,240,195,199,14,56,192,240,206,115,115,24,52,176,225,199,142,28,60,248,56,240,225,193,139,134,143,28,120,112,240,120,172,177,143,57,49,219,56,97,195,14,14,56,120,115,23,30,57,30,56,100,225,241,193,78,15,64,162,2,197,143,129,161,252,18,8,100,224,60,34,224,69,7,142,12,50,144,240,31,32,73,224,248,12,96,240,23,26,65,170,164,208,141,18,130,30,30,3,248,62,3,12,115,128,112,68,38,3,36,225,62,4,78,4,28,193,9,204,158,144,33,7,144,67,100,192,15,198,144,156,193,91,3,226,29,129,224,94,29,3,132,184,44,15,128,177,131,224,48,65,30,67,137,131,80,252,36,46,19,131,241,124,76,44,201,13,131,176,181,130,228,232,6,156,7,160,153,29,7,62,130,143,112,48,116,64,202,16,228,232,15,146,20,63,6,248,132,136,67,129,10,52,57,65,198,227,28,71,3,176,184,19,10,194,100,248,24,249,96,179,192,101,32,96,166,140,195,129,32,48,38,30,28,56,211,1,176,38,64,244,11,195,66,31,133,50,38,96,64,201,203,1,236,17,40,64,250,4,52,224,112,76,140,29,7,105,3,22,200,4,35,232,198,154,11,26,3,224,118,6,5,207,30,188,88,49,113,102,0,248,63,4,252,12,116,39,138,128,113,194,58,38,6,192,31,5,15,152,64,174,1,127,192,7,255,0,14,254,0,3,223,128,3,239,128,27,241,194,0,231,224,24,252,224,33,252,128,60,252,64,14,126,0,63,62,0,15,254,0,31,255,0,62,240,7,252,0,126,16,63,255,0,63,56,14,124,1,135,12,252,199,0,62,4,15,62,31,15,15,31,15,2,131,135,207,3,135,15,63,192,7,158,96,63,192,3,254,0,63,224,119,225,192,254,224,195,224,1,223,248,3,7,0,126,112,0,124,56,24,254,12,30,120,28,124,62,14,31,30,30,62,0,127,131,7,219,135,131,7,199,7,16,113,255,0,63,226,1,224,193,195,225,0,127,192,5,240,32,248,240,112,254,120,121,248,2,63,12,143,3,15,159,224,193,199,135,3,195,195,176,225,225,193,227,224,113,240,0,252,112,124,12,62,56,14,28,112,195,199,3,129,193,199,231,0,15,199,135,25,9,239,196,51,224,193,252,248,112,240,120,248,240,97,199,0,31,248,1,124,248,240,120,112,60,124,206,14,33,131,207,8,7,143,8,193,135,143,128,199,227,0,7,248,224,239,0,57,247,128,14,248,225,227,248,33,159,192,255,3,248,7,192,31,248,196,4,252,196,193,188,135,240,15,192,127,5,224,37,236,192,62,132,71,240,142,3,248,3,251,192,25,248,7,156,12,23,248,7,224,31,161,252,15,252,1,240,63,0,254,3,240,31,0,253,0,255,136,13,249,1,255,0,112,7,192,62,66,243,13,196,127,128,252,7,240,94,192,63,0,120,63,129,255,1,248,1,195,232,12,228,100,143,228,15,240,7,240,194,31,0,127,192,111,128,126,3,248,7,240,63,192,120,15,130,7,254,34,119,112,2,118,3,254,0,254,103,0,124,199,241,142,198,59,224,63,132,243,25,216,3,153,252,9,184,15,248,0,157,36,97,249,13,0,253,3,240,31,144,63,1,248,31,208,15,248,55,1,248,7,240,15,192,63,0,254,3,248,15,192,63,0,250,3,240,15,128,255,1,184,7,240,1,252,1,188,128,19,30,0,127,225,64,127,160,127,176,0,63,192,31,192,56,15,240,31,128,255,1,252,3,241,126,1,254,1,240,255,0,127,192,29,7,240,15,192,126,6,224,7,224,15,248,6,193,254,1,252,3,224,15,0,252],I1=(e,A)=>{let E=(H,e)=>(H*e>>8&255)<<1,r=[[],[],[]];O1.map((H,e)=>{r[0][e]=255&H,r[1][e]=H>>8&255,r[2][e]=H>>16&255});for(let H=5;H<30;H++)r[0][H]=E(e,r[0][H]),r[1][H]=E(A,r[1][H]);for(let H=48;H<54;H++)r[0][H]=E(e,r[0][H]),r[1][H]=E(A,r[1][H]);return r},Y1=(e,H,A,E)=>{let Y=[e,H[0],H[1],H[2],A[0],A[1],A[2]],l=(H,e)=>Y[H][e],r=(e,A,E,H)=>{let r=H<0,t=Math.abs(H)%e,O=H/e|0,R=0,I=e;for(;0<--I;){let H=l(A,E)+O;(R+=t)>=e&&(R-=e,r?H--:H&&H++),Y[A][++E]=H,H+=O}},t,O,R=0;for(let H=0;H>1)+(U=E[H+1][1]>>1),0,S,e[R+U]-e[R-N]);for(let H=1;H<7;H++){var T=l(H,W)-l(H,S);r(f,H,S,T)}}}return R+E[E.length-1][1]},l1=255,n1=1,U1=(A,E,r)=>{let H=(H,e,A)=>{var E=e;e<30?e=0:e-=30;let r;for(;127===(r=A[e]);)++e;for(;e!==E;)for(r+=H,A[e]=255&r;++e!==E&&255===A[e];);},t=[],O=[[],[],[]],R=[[],[],[]],I=[],Y=0;for(let e=0;e>8&255,R[2][Y]=C[l]>>16&255,I[Y]=t1[l],t[Y]=A+n&255,Y++}return[t,O,R,I]},N1=(H,e,A,E,r)=>{var A=I1(A,E),[t,O,R,E]=U1(e,H,A),e=Y1(t,O,R,H);if(!r)for(let H=0;H>1;var I=[0,1,2,2,2,3,3,4,4,5,6,8,9,11,13,15];for(let H=R[0].length-1;0<=H;H--)R[0][H]=I[R[0][H]],R[1][H]=I[R[1][H]],R[2][H]=I[R[2][H]];return[e,O,t,R,E]},W1=H=>{let A=new Uint8Array(H),E=0,r=0,t=(H,e)=>{e=16*(15&e),t.ary(H,[e,e,e,e,e])};return t.ary=(H,e)=>{if(((E+=[[162,167,167,127,128],[226,60,60,0,0],[225,60,59,0,0],[200,0,0,54,55],[199,0,0,54,54]][r][H])/50|0)>A.length)throw new Error;r=H;for(let H=0;H<5;H++)A[(E/50|0)+H]=e[H]},t.get=()=>A.slice(0,E/50|0),t},v=(O,e,H,A)=>{let E=(7&H)-1,R=256*E&65535,I=248&H,r=(H,e,A,E)=>{let r=8,t=R1[R+I];for(;0!=(128&t)?O(H,e):O(A,E),t<<=1,--r;);};if(0===I){let H=A>>4^255;for(I=255&e;r(3,26,4,6),I++,I&=255,255&++H;);return I}I^=255;for(var t=255&E1[E];r(2,5,1,t),255&++I;);return e},c=H=>127*Math.sin(2*Math.PI*(H/256))|0,S1=(H,e,A,t,E,O,r)=>{let R=A,I=0,Y=0,l=0,n=0,U=0,N=E[0],W=.75*N|0;for(;e;){var S=r[U];if(0!=(248&S))n=v(H,n,S,E[255&U]),U+=2,e-=2,R=A;else{{let e=[],A=256*I,E=256*Y,r=256*l;for(let H=0;H<5;H++){var f=c(255&A>>8),T=c(255&E>>8),i=(255&r>>8)<129?-112:112,f=(f*(15&O[0][U])+T*(15&O[1][U])+i*(15&O[2][U]))/32+128;e[H]=0|f,A+=256*t[0][U]/4,E+=256*t[1][U]/4,r+=256*t[2][U]/4}H.ary(0,e)}if(0==--R){if(U++,0==--e)return;R=A}if(0!=--N){if(0!=--W||0===S){I+=t[0][U],Y+=t[1][U],l+=t[2][U];continue}n=v(H,n,S,E[255&U])}}N=E[U],W=.75*N|0,I=0,Y=0,l=0}};function X(H){var e,A,E,r,t,O=1{if(!H)return!1;let e=H=>H===I.length?null:I[H],A=(e,H,A,E)=>{for(let H=I.length-1;H>=e;H--)I[H+1]=I[H],R[H+1]=t(H),O[H+1]=r(H);I[e]=H,R[e]=0|E,O[e]=A},r=H=>0|O[H],t=H=>0|R[H],E=(H,e)=>{R[H]=e},O=[],R=[],I=[],Y=0;return b(H,H=>{O[Y]=0,R[Y]=0,I[Y++]=H},H=>{O[Y-1]=H}),q(A,(H,e)=>{I[H]=e},e,r),H1(e,r,(H,e)=>{O[H]=e}),e1(e,r,E),z(e,E,t),A1(e,A,r),I.map((H,e)=>H?[H,0|R[e],0|O[e]]:null).filter(H=>H)})(H);return!1!==H&&(r=void 0===(r=O.pitch)?64:255&r,A=void 0===(A=O.mouth)?128:255&A,E=void 0===(E=O.throat)?128:255&E,e=255&(O.speed||72),O=O.singmode||!1,r=N1(H,r,A,E,O),A=W1(176.4*H.reduce((H,e)=>H+e[1],0)*e|0),[E,O,H,r,t]=r,S1(A,E,e,O,H,r,t),A.get())}let F=H=>{let e=" "+H.toUpperCase(),A=0,E="",r=(H,e)=>{A+=e,E+=H},t=0;for(;AH(e,A,r));else if(0!==l[O]){if(!U(O,n))return!1;S[O].some(H=>H(e,A,r))}else E+=" ",A++;else E+=".",A++}return E},g=X,p=(H,e)=>{if(!1===(H=X(H,e)))return!1;var A=H,E=new Float32Array(A.length);for(let H=0;He||A.phonetic?H.toUpperCase():F(H);this.buf8=(H,e)=>g(E(H,e),A),this.buf32=(H,e)=>p(E(H,e),A),this.speak=(e,H)=>{if(e=this.buf32(e,H),I=null===I?new AudioContext:I){var O=I,R=e;let t,H=new Promise((H,e)=>{let A=O.createBufferSource(),E=O.createBuffer(1,R.length,22050),r=E.getChannelData(0);for(let H=0;H{H(!0)},t=H=>{A.disconnect(),e(H)},A.start(0)});return H.abort=t,H}throw new Error},this.download=(H,e)=>{var A,H=this.buf8(H,e),e=new Blob([R(H)],{type:"audio/vnd.wave"}),e=(H=window.URL||window.webkitURL).createObjectURL(e);(A=document.createElement("a")).href=e,A.target="_blank",A.download="sam.wav",document.body.appendChild(A),A.click(),document.body.removeChild(A),H.revokeObjectURL(e)},this.wav=(H,e)=>R(this.buf8(H,e))}return H.buf8=g,H.buf32=p,H.convert=F,H}var e=this;"object"==typeof exports&&"undefined"!=typeof module?module.exports=H():"function"==typeof define&&define.amd?define(H):(e="undefined"!=typeof globalThis?globalThis:e||self).SamJs=H();
+
+ // module exports is now SamJs
+ return module.exports;
+ }
+ /* eslint-enable */
+
+ const PRESETS = {
+ SAM: {
+ speed: 72,
+ pitch: 64,
+ throat: 128,
+ mouth: 128,
+ },
+ elf: {
+ speed: 72,
+ pitch: 64,
+ throat: 110,
+ mouth: 160,
+ },
+ "little robot": {
+ speed: 92,
+ pitch: 60,
+ throat: 190,
+ mouth: 190,
+ },
+ "stuffy guy": {
+ speed: 82,
+ pitch: 72,
+ throat: 110,
+ mouth: 105,
+ },
+ "little old lady": {
+ speed: 82,
+ pitch: 32,
+ throat: 145,
+ mouth: 145,
+ },
+ "extra-terrestrial": {
+ speed: 100,
+ pitch: 64,
+ throat: 150,
+ mouth: 200,
+ },
+ };
+
+ let noDownload = true;
+ let presetMenu = []; // Useful when declaring the preset menu
+
+ Object.keys(PRESETS).forEach((key) => {
+ presetMenu.push({ text: Scratch.translate(key), value: key });
+ });
+
+ // taken from https://github.com/discordier/sam/blob/master/docs/manual.md#phonetic-input-to-sam
+ const phonemeMenu = [
+ {
+ value: "IY",
+ text: Scratch.translate("/i/ IY as in feet"),
+ },
+ {
+ value: "IH",
+ text: Scratch.translate("/ɪ/ IH as in pin"),
+ },
+ {
+ value: "EH",
+ text: Scratch.translate("/ɛ/ EH as in beg"),
+ },
+ {
+ value: "AE",
+ text: Scratch.translate("/æ/ AE as in Sam"),
+ },
+ {
+ value: "AA",
+ text: Scratch.translate("/ɑ/ AA as in pot"),
+ },
+ {
+ value: "AH",
+ text: Scratch.translate("/ᴧ/ AH as in budget"),
+ },
+ {
+ value: "AO",
+ text: Scratch.translate("/ɔː/ AO as in talk"),
+ },
+ {
+ value: "OH",
+ text: Scratch.translate("/oʊ/ OH as in cone"),
+ },
+ {
+ value: "UH",
+ text: Scratch.translate("/ʊ/ UH as in book"),
+ },
+ {
+ value: "UX",
+ text: Scratch.translate("/u/ UX as in loot"),
+ },
+ {
+ value: "ER",
+ text: Scratch.translate("/əː/ ER as in bird"),
+ },
+ {
+ value: "AX",
+ text: Scratch.translate("/æ/ AX as in gallon"),
+ },
+ {
+ value: "IX",
+ text: Scratch.translate("/ɪ/ IX as in digit"),
+ },
+ {
+ value: "EY",
+ text: Scratch.translate("/eɪ/ EY as in made"),
+ },
+ {
+ value: "AY",
+ text: Scratch.translate("/aɪ/ AY as in high"),
+ },
+ {
+ value: "OY",
+ text: Scratch.translate("/ɔɪ/ OY as in boy"),
+ },
+ {
+ value: "AW",
+ text: Scratch.translate("/aʊ/ AW as in how"),
+ },
+ {
+ value: "OW",
+ text: Scratch.translate("/əʊ/ OW as in slow"),
+ },
+ {
+ value: "UW",
+ text: Scratch.translate("/ʉː/ UW as in crew"),
+ },
+ // The following symbols are used internally by some of S.A.M.'s rules, but they are also available to the user.
+ // {
+ // value: "YX",
+ // text: Scratch.translate("Y diphthong ending")
+ // },
+ // {
+ // value: "WX",
+ // text: Scratch.translate("W diphthong ending")
+ // },
+ // {
+ // value: "RX",
+ // text: Scratch.translate("R after a vowel")
+ // },
+ // {
+ // value: "LX",
+ // text: Scratch.translate("L after a vowel")
+ // },
+ // {
+ // value: "/X",
+ // text: Scratch.translate("/X as in H before a non-front vowel or consonant")
+ // },
+ // {
+ // value: "DX",
+ // text: Scratch.translate("DX \"flap\" as in pity")
+ // },
+ {
+ value: "R",
+ text: Scratch.translate("/ɹ/ R as in red"),
+ },
+ {
+ value: "L",
+ text: Scratch.translate("/l/ L as in allow"),
+ },
+ {
+ value: "W",
+ text: Scratch.translate("/w/ W as in away"),
+ },
+ {
+ value: "WH",
+ text: Scratch.translate("/w/ WH as in whale"),
+ },
+ {
+ value: "Y",
+ text: Scratch.translate("/j/ Y as in you"),
+ },
+ {
+ value: "M",
+ text: Scratch.translate("/m/ M as in Sam"),
+ },
+ {
+ value: "N",
+ text: Scratch.translate("/n/ N as in man"),
+ },
+ {
+ value: "NX",
+ text: Scratch.translate("/ŋ/ NX as in song"),
+ },
+ {
+ value: "B",
+ text: Scratch.translate("/b/ B as in bad"),
+ },
+ {
+ value: "D",
+ text: Scratch.translate("/d/ D as in dog"),
+ },
+ {
+ value: "G",
+ text: Scratch.translate("/ɡ/ G as in again"),
+ },
+ {
+ value: "J",
+ text: Scratch.translate("/ʤ/ J as in judge"),
+ },
+ {
+ value: "Z",
+ text: Scratch.translate("/z/ Z as in zoo"),
+ },
+ {
+ value: "ZH",
+ text: Scratch.translate("/ʒ/ ZH as in pleasure"),
+ },
+ {
+ value: "V",
+ text: Scratch.translate("/v/ V as in seven"),
+ },
+ {
+ value: "DH",
+ text: Scratch.translate("/ð/ DH as in then"),
+ },
+ {
+ value: "S",
+ text: Scratch.translate("/s/ S as in Sam"),
+ },
+ {
+ value: "SH",
+ text: Scratch.translate("/ʃ/ SH as in fish"),
+ },
+ {
+ value: "F",
+ text: Scratch.translate("/f/ F as in fish"),
+ },
+ {
+ value: "TH",
+ text: Scratch.translate("/θ/ TH as in thin"),
+ },
+ {
+ value: "P",
+ text: Scratch.translate("/p/ P as in poke"),
+ },
+ {
+ value: "T",
+ text: Scratch.translate("/t/ T as in talk"),
+ },
+ {
+ value: "K",
+ text: Scratch.translate("/k/ K as in cake"),
+ },
+ {
+ value: "CH",
+ text: Scratch.translate("/ʧ/ CH as in speech"),
+ },
+ {
+ value: "/H",
+ text: Scratch.translate("/h/ /H as in ahead"),
+ },
+ {
+ value: "UL",
+ text: Scratch.translate("/əl/ UL as in settle"),
+ },
+ {
+ value: "UM",
+ text: Scratch.translate("/əm/ UM as in astronomy"),
+ },
+ {
+ value: "UN",
+ text: Scratch.translate("/ʌŋ/ UN as in function"),
+ },
+ {
+ value: "Q",
+ text: Scratch.translate("/ʔ/ Q as in kitt**-**en (glottal stop)"),
+ },
+ ];
+
+ const SamJs = initSamJs();
+
+ class SAM {
+ getInfo() {
+ return {
+ id: "samtts",
+ /* eslint-disable */
+ name: "SAM", // Lint wanted me to translate this, but I feel like it shouldn't be because it's an abbreviation/name
+ /* eslint-enable */
+ color1: "#706deb", // Light blue color from the Colodore color palette
+ color2: "#4f4dc3", // A mix of the two
+ color3: "#2e2c9b", // Blue color from Colodore
+ blocks: [
+ {
+ opcode: "speak",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("speak [SPEECH]"),
+ arguments: {
+ SPEECH: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Hello, my name is SAM.",
+ },
+ },
+ },
+ {
+ opcode: "loadPreset",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("load preset [PRESET]"),
+ arguments: {
+ PRESET: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PRESET_MENU",
+ },
+ },
+ },
+ {
+ opcode: "downloadSpeech",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("download speech of [SPEECH]"),
+ arguments: {
+ SPEECH: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Hello, my name is SAM.",
+ },
+ },
+ },
+ {
+ opcode: "addSound",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("add speech [SPEECH] as sound [NAME]"),
+ arguments: {
+ SPEECH: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Hello, my name is SAM.",
+ },
+ NAME: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "speech",
+ },
+ },
+ },
+ {
+ opcode: "setProp",
+ blockType: Scratch.BlockType.COMMAND,
+ text: Scratch.translate("set [PROP] amount to [NUM]"),
+ arguments: {
+ PROP: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PROP_MENU",
+ },
+ NUM: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 50,
+ },
+ },
+ },
+ {
+ opcode: "getProp",
+ blockType: Scratch.BlockType.REPORTER,
+ text: Scratch.translate("[PROP] amount"),
+ arguments: {
+ PROP: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PROP_MENU",
+ },
+ },
+ },
+ {
+ opcode: "getPhoneme",
+ blockType: Scratch.BlockType.REPORTER,
+ text: Scratch.translate("phoneme [PHONEME]"),
+ arguments: {
+ PHONEME: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "PHONEMES",
+ },
+ },
+ },
+ ],
+ menus: {
+ PROP_MENU: {
+ acceptReporters: true,
+ items: [
+ {
+ text: Scratch.translate("speed"),
+ value: "speed",
+ },
+ {
+ text: Scratch.translate("pitch"),
+ value: "pitch",
+ },
+ {
+ text: Scratch.translate("mouth"),
+ value: "mouth",
+ },
+ {
+ text: Scratch.translate("throat"),
+ value: "throat",
+ },
+ ],
+ },
+ PRESET_MENU: {
+ acceptReporters: true,
+ items: presetMenu,
+ },
+ PHONEMES: {
+ acceptReporters: true,
+ items: phonemeMenu,
+ },
+ },
+ };
+ }
+
+ checkProps(util) {
+ // Utility function to assign the sprite a voice if it doesn't have one
+ if (!util.target.extensionStorage.samtts) {
+ util.target.extensionStorage.samtts = { ...PRESETS.SAM }; // We need the {...foo} so it makes a *copy* of the preset
+ }
+ }
+ speak(args, util) {
+ this.checkProps(util);
+
+ new SamJs(util.target.extensionStorage.samtts).speak(args.SPEECH);
+ }
+ loadPreset(args, util) {
+ util.target.extensionStorage.samtts = { ...PRESETS[args.PRESET] };
+ }
+ downloadSpeech(args, util) {
+ if (noDownload) {
+ if (
+ confirm(
+ Scratch.translate(
+ "Do you want to allow this project to download voice clips to your computer?"
+ )
+ )
+ ) {
+ noDownload = false;
+ } else {
+ return;
+ }
+ }
+ this.checkProps(util);
+ new SamJs(util.target.extensionStorage.samtts).download(args.SPEECH);
+ }
+ /**
+ * Shamelessly copied from Lily/Assets.js addSound()
+ */
+ async addSound(args, util) {
+ const targetId = util.target.id;
+ const assetName = Scratch.Cast.toString(args.NAME);
+
+ this.checkProps(util);
+ const buffer = new SamJs(util.target.extensionStorage.samtts).wav(
+ args.SPEECH
+ );
+
+ const storage = Scratch.vm.runtime.storage;
+ const asset = storage.createAsset(
+ storage.AssetType.Sound,
+ storage.DataFormat.WAV,
+ buffer,
+ null,
+ true
+ );
+
+ try {
+ await Scratch.vm.addSound(
+ {
+ asset,
+ md5: asset.assetId + "." + asset.dataFormat,
+ name: assetName,
+ },
+ targetId
+ );
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ setProp(args, util) {
+ this.checkProps(util);
+ util.target.extensionStorage.samtts[args.PROP] = 2.55 * args.NUM; // Conversion from 0-100 to 0-255
+ // I could've kept the whole thing on a scale of 0-255 but decided not to for consistency with the sound and pen blocks
+ // The converted 0-100 scale is displayed to the user, but it's still 0-255 under the hood
+ }
+ getProp(args, util) {
+ this.checkProps(util);
+ return util.target.extensionStorage.samtts[args.PROP] / 2.55; // 0-255 -> 0-100
+ }
+ getPhoneme(args, util) {
+ return args.PHONEME;
+ }
+ }
+ Scratch.extensions.register(new SAM());
+})(Scratch);
diff --git a/extensions/extensions.json b/extensions/extensions.json
index d8ed71e33d..9f8b8d0aea 100644
--- a/extensions/extensions.json
+++ b/extensions/extensions.json
@@ -47,6 +47,7 @@
"XeroName/Deltatime",
"ar",
"encoding",
+ "MagentaDude/sam",
"Lily/SoundExpanded",
"Lily/TempVariables2",
"Lily/MoreTimers",