Posts for Bisqwit


Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Dromiceius wrote:
I was more surprised by the fact that QBasic was doing code folding and some kind of prettifying.
QBasic** stores the program internally in a tokenized format for fast interpretation and small memory use, and as part of it, all redundant whitespace within* legal statements is deleted. For rendering, the statement is reconstructed with built-in prettifying rules. *) Excluding whitespace that precedes an apostrophe comment and indentation preceding the statement. (Whitespace in DATA or within string constants is not redundant.) **) GW-BASIC, MBASIC and BASICA, QBasic's predecessors, did the same, except they preserved whitespace. Identifiers were converted to uppercase. I think PET Basic / C64 Basic, branched from the same development tree, did that too.
Post subject: QBasic live programming
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
The first time ever that I do live* programming as a performance. http://www.youtube.com/watch?v=TUa5HJUebEA I seem to remember that someone once, many years ago, said something to me about this. Never thought I would actually do it. *) This was only live for the fact that I recorded it in real time without re-rerecording or cuts; it was rehearsed.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Hi Kirkq. I'm still somewhere, but not in any kind of control position over the TASVideos site. Which is what I wanted, too. Now, if situation demands, I can simply leave without warning and without liabilities. If you want to chat, feel welcome to come to the IRC channel. Even if I'm not on that channel, I'm still on the server, and a /msg reaches me sooner or later.
Post subject: Here's how it works.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Unless the settings have been changed since I last touched it, it works like this: If the user has 35 or more accesses within the last minute, a warning is issued in place of the actual page, but the user is not banned. This should wake up the user to slow down their pace. A margin of 10 accesses is still allowed for the same timeframe in case the user was just busy opening some tabs. If the user has 45 or more accesses within the last minute, a 6-hour ban is enacted. If the user accesses the trap link (blockme.cgi) once, a 1-minute ban is enacted immediately. If the user accesses the trap link while a 1-minute ban is active, a 1-hour ban is enacted immediately. If the user accesses the trap link while a 1-hour ban is active, a 36-hour ban is enacted immediately. If the user's agent is a known mass-downloading program and the access concerns autogenerated pages (i.e. not submission files etc), a warning is issued in place of the actual page, but the user is not banned. If the user accesses a referrer-restricted page (some particularly heavy autogenerated pages such as diffs) without being referred by the main site, a warning is issued in place of the actual page, but the user is not banned. Access blocks are stacked, i.e. acquiring a 1-minute ban while a 6-hour ban is active does not shorten the ban to 1 minute, nor does a warning remove the ban. I have originally developed these measures based on the known-good-in-practice model developed at Sensei's Library: http://senseis.xmp.net/?AccessBlocked
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
In other words, you are nearing the situation where the game is completed right after the title screen, right? ;)
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Tub wrote:
I think it takes up too much space when there are multiple rewards or large rewards. Here's a few suggestions: Image-link
Here's what I was thinking. Ignore the disappeared white line. And the red text and the blue text that tells you to ignore the red text.
Post subject: Re: Regarding the placement of awards in signtures
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
In any case I'd prefer them less obtrusive as suggested earlier…
Post subject: Re: Regarding the placement of awards in signtures
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
I wouldn't mind including them in the signature if (1) they're made to match the standard tone of the signature (i.e. #444444 monochrome), (2) they're made small (the height of two lines of text at maximum) and (3) text added to the signature does not add to the height of the signature.
Post subject: Me too (Instruments of choice: ocarina and Scream Tracker 3)
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Here is me with an ocarina. Recorded a few minutes ago. http://www.youtube.com/watch?v=ssG2AI1yxTU More often though, I do music using a computer. Like here: http://www.youtube.com/view_play_list?p=9D3E124A0B52A908 I also have tried singing a bit just to hear for myself how I sound. http://bisqwit.iki.fi/music/mp3/Bisqwit/taivasinva2.ogg Here's one where I sing the Finnish folk song "taivas on sininen ja valkoinen" (the sky is blue and white), in dual voice. Produced in 2007. The accompaniment was created with Scream Tracker 3 as an AdLib track, then played through a MIDI synthesizer.
Post subject: More on mp3 streaming and vintage NES songs
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Related to the above, here is something that I wrote in 1999:
#define _POSIX_SOURCE 1 /* Song length: 55 seconds (24 seconds of loop). */
/*/echo "$0 is not an executable but a C source file. Compiling it now..."
n=`basename $0`;gcc -O2 -Wall -W -pedantic -o /tmp/$n "$0";/tmp/$n $*;exit; */
static char*Title="Selfplayer for /dev/audio (8-bit, µ-law)\n" 
"This file has been compiled from a file created with intgen.";
typedef unsigned char by;char*A,*tmp="nesmusa.$$$",*a0;
static by*z=(by*)"+l=6*mM*JlmZmom+mom)omm5lm8CoZ@Ig3QCDe^l=B*0Z@mJ+NZK*RJ)OJ+"
"5B*7<N+CNJ4LbZ=NZKRRK)O;+5HHF<n51N20Vb:UFZ^UCAY<U?""=a4doK*bJCe5(*(ToM2*2bZ7"
"FV_5AAd<_?AAedmK5XJ9=Ud<OL@lflK2J*Ae5H*4<oMB60b:=CZK2BK)fJ+5T+(<OM2(Bb:;BZKg"
"X*8=Vd2O8@Tf?Sm)+J+4M>\\Cnfb^[o)7:+4m=BE1NZ(Db:1:*So)^Z<4]=J7GBnl*1fI[m)d8+5"
"J72<B+4NJ7<Ybo+(;K)M`+5^J7<6D1NJ7Z`:\\3d+^bm)Cn55(DM<V[(OROAa*Dnm+ZHH)g=45Bn"
"(^oBAYdS_CAedlKo*A)d3^CBa5PM)<6+6BAm*6:*^ABm5@o(bnN2*XHY]TU)+::@l=^*1N:1BRXY"
"K*ZT0mEL<6*3Ncm:*^*TU)+*DBlenN2*FBYm+fHY)m745fR^<NF\\N:PDbZ>0X+RoF?""=5JdK<^"
"_>N2*DZXYK*:+1m5VE7<R*=Ocm:\\<fKSUK)Pn(5:1UfnUcc:*V:Tm)P]DIL<6*B?`mZ*]ZK6\\*"
">m5>J)<2*7N*V?b*)RZK12I)RJ75L`(fnMZ81bJ+NZ+neJ)+J+5DJ5<N+CNB4Db*?ZZ+KLH)+*+L"
"L<6*;bcm:*:ZUU)+:6Ml=6*66`c:*6;Rm)+*+PL8oJ`cfISU8=VL4o2`;fIRm)+J+5HJ2<2+8NJ4"
"HJgYP5G=RL<oN`mJ+^Z+SJJ;=Il=^00ZcmJRDZK(Z*<m5H)6*o*`+fm+1BK)^*:NL<J7GNJ<:b*1"
"BY+A[J)Z(65B44<Z(KORT<b*?oZKW`J)LU(Ml=VL4NZo0b*BSJRU)DZ(5B+D<nK06`=fm+G^J)]R"
"+5X_7<B+1NJ7BcXYWU1=EL\\nM2*>bZ(FZ+2ZJ);b(5^J7<Z(BORG3*gm+CgJ)30(MLlnmcmJ7B["
"+Z8K)7W(Ml=6D1*`mJ7^Z+DJW)JW05b16<ZB<6`c*1N[RU;=IL4o>`m:*BZK7RJ):*+IL4oM2\\E"
"bJ7^Z+DZV)2K(5Z(4<b74NJ7^b*=([+KfJ)n265Z8)<*D:N2BKb*BGZ+LoJ)_A7ML<Z(DN2BEb:;"
"S:RU)6H75J7D<*^:NZB?b*Z([+\\WK)^Z85TH4<6^7N*o3b*nOZ+2:+8=5bD)<2\\7^`@gbPg)+J"
")QL=6*5^@l:*NZPa)+Z+Qd=6*5^`m:*N*P1FIRD=6*7J`7g1PeEE]4M_L2*0bgaQeE]5^*6<N+6^"
"`@gTP1GIPD=6*<R`o:*NJPc)+Z+P\\=6*5R@Egl+6CK)((+5:H6<647J`gfWS;9Q5(*6>o@`gfQS"
"e8EW41_L2*lRfaK*bJ8YWT1?MR7:RfASe8]5<(6>o@`gfaKMCK))F65L*3<F;DNbAHbZ)6[+bC+<"
"EN4c^L2*@JYaK*RJ;EI45_L2*NBfaK*RJ=EM4m^L2*@bYaK*RJ:EK4)_L2*N2faK*RJ)@KDB\\Z>"
"HCo:*B4Te)+Z+BT=6*5Jcl:*N*To)+:+Z\\<6*5*A6d0^a)1*(5:*3J?""(A7d?^m)+J+R\\;_H`"
")gbK*b+FU5(*B<fU>N2:3*g>PCFYRT;oI`MgoPgGMQ4=6*=^`n:**Y+fTHGmQlMOm@ogeQ[)+Z)]"
"<=6*ZNRZ?bZKEZK+RJEY]TM?m@lgmQM9eTD?oC@n:*nJSY)+:)5X@7>oA`mfoSg9MU4=6*=N`n:*"
"fZSg)+Z+UL=oL`mfm+f(m)[J;5N?0<2A6n`l:*NZQo)+Z+5Bd(:OLZ*F*g3Pe)4J+RT;OI`5g=Pm"
")OJ+5B*7<N+CNJ4LbZ=NZK<RK)O;+5HHF<n51N20Vb*V^*S=8IV43?8`VfaK+W*9U5F4)FoS@FgR"
"P;GUPL=6+7B`o*fFZK2J*GIP<=Vg6^`NgaKZ[J)I:+5Fa(DOLZH3bgcQeDM_4A?d@^gCQe)I:+5N"
"+)Fo=`OfeRU)+J)IT4?""?@@ffRQ;mUL=oO`c:*bZSS99UT<?O``f]PEGMPL<6*<R@8gVP3G9P\\"
"Fom`ogeQU)+J)]TL?o@`gfQQEE]\\LOo@agdQ_EA]lLoK``*ST*PeDE_<=6*4f@_g@QeEE5Na6Lo"
"L26D:dW^Y)+J+XDW_0aoZ?^:^QBU5(*)H_4A?daKTZH)e*15X)1<R32f`^g@Qg)Z((5HW3=n;12a"
"GdQS)9AT<?_A`dfRS?9ATd>_@@mY[U_=UMdmNmCnYdU_=AMD+_=`BfdRY;aI\\5?""<@AfdR_;QI"
"d=B+1NJ7\\[fnK^*K=M5X(74oO21FbYeK^:+9M5067<Z(CJ`*g4P9FaR\\;?H@)g4P=FQSd=f61N"
":2DbZB^Z+D:2G5PdFOP@Eg_P;GAPdFoS@Ogl+^bT;m]dMol@n*[DZ+(JKEM5(*<Jo+a)de+72K)3"
"*6Z4K?""(AngbKb_*BQZlA_e`_gBQQDA5(J5@oO2*bZgUQ=EM58J5<>+0b@fgRQ[DI5R`(N_``Ed"
"\\^ECIX\\V_O2*L:deK*JICMYlToO2+L:deK62KCIY4U?0AnZHEZ^gCm[dIo4A>dG^_)+Z)[l<6*"
"?6An:*N:^YBm5b4;@?L2*<RgCQMGaPLG?P`8gdK*b+GM5(*<Do_`AgeK6b+GM58*7D?\\@FgRP;G"
"UP<GOP`Ng`PMFaRL;?H`(gdK*b+FM5(*<8oW`1geK6b+FM58*78?T@6g2P[)[=+SD9o=@MfaR[;E"
"Id<6*=>`a:*Z+S=8M5FJ2<V*8NJ)>@fbR[;II45O8`VfcRY;QID5_<@OfaRe;]VL5?""=@LfnQWE"
"E]l<6*<n@n:*NZQY)+Z+]D=6*5n`o:*NZQc)+Z+RD:OK`a:*F*P;FIR4E_^`@geK*RJGIQ4E?d@S"
"gGQ])+J+_4A?d@^g8QU)O;<]\\=6*52A;dW^])+J)X4=6*76A3dG^])+:)[4=6*56an:*N:^g)+Z"
"+[L=6*56Al:*NZKdcJ)I)+5876@?d@^g2^7BEZl<6*?*An:*N*^Y)+Z+ZD=6*5*ao:*N*^c)+Z+Z"
"\\=6*5*Am:*N*^m)+J+5V06<F4<NZK8bgbQ[EI5JS)D?\\@NgbP[)4g(54J(:OL2*>ZfHS19M5(*"
"(>?@@ffRS[)Rg+5(K5<>E1NB*0BgcK*ZJ)_:15B_)<f36N2F2RgcK*ZJD1_\\@_c@[gWQ=EM\\lN"
"oc@fgRQ[)HZ(5R71<RM2NZ*:bZInZPYGQ5(J)DoL2*22e8\\QNA`DfOSa9eU\\=OM`4g?PA>e8\\"
"QNM5(*(X?TA>eB\\[A1e\\\\oO2*2bdb_[AIe4S_:aHdeK*R*@If4S?LZ(<bZ7FZ_W)+J+e\\\\o"
"OAodN\\W)+Z7c\\XoWA?eN\\m)+*65dS7F_R`8geK*R*GIP4G?LBUBbZ*_Z+<NK)d3(X4W?0AnJV"
"^ZK+R*@U5(*)<VV1NJWEb*<^Z+F_*CU5(*)<B?5NB2L*e(\\1NA50R)<R:3NZK22ecK*bJNQcDY_"
"LBJBbJ5;Z_Y)+J+5LH7<n[5N*<LBdX^QCA580)<F1<>ACdg^])+*+Y4U?""<ANdbK`5H)fM55ZK)"
"<V)7NbK^:eH\\1OAaDdO_aAee\\]OIa4e?LbdBbJY_ZKnLK)Xa(5P^7<BC>NBY;bZT+[+b8K)bY("
"5T;6<2@3N25;bZ?WZKgNKN1cLXoVacJPFZKGKJOIa4=FR4N:]8b:H@Z+e0K)6g)5nJ5^_BaXdU_="
"AMdl^?L2*<Zdc+\\=K):R+5B1)<^D6Nb_2BdcKDbK)b[)5VV7d?\\ANeb\\YM1m\\looaaee]]MI"
"5(J)lOl@cgYQUE9]\\LoOZ*>Rg8QUD=_T@Og`aJ6BZQ[EQ]LM?m@m:6B:Q[DQ_LA?e@]gmK1RIE1"
"5TK(LOl`ognQoEm_D<>)7f`nZ>:ZK<2K)OK+5b(5LoMR1DbZ7f[+\\BK)O+)QDDo^@@ggP]GIRD:"
"oJ`+g6P1FMPDFoR@8gWP]G1QL<2+6Nb=>:gHPU)8J+5HJ(<F?4NJ@2bZ2^Z+n[*C1XLV?3a8dU^;"
"B1ZLJo*A(d7^])69+542)^_Ba[dI_3AEdlR_:aKd)_3@Efl<^?7N2(NbJ6VZK22H)fJ+5HJ2Ro?A"
"CdY^UC9Y\\<^D4J@+gdK*JJ)K@+R<A_f@QgDQ_DA50f(@Ol@cgdQ_EA]d<>m7n`nJ2FZK:Z*C1Xd"
"V_3A9dd+c]*CU5^J7H_6A1dD^_BA[lHo7A>dB^[BU5BK7<f61Nb4Bc:LB[+?CK)IK75J7(=b;1BA"
"[ddK*JH)(O+d<_O@afdP_GA]fDR_;AId4__)5M+f<=FG12aFdS^GCQXLWo0aGd^^CCYX\\=6B7N:"
"Z>bJOGZKaXJ)B;25X(;<b@16a>dC^gBQ[LIo4a?dN^cBY[\\=:44NR*Fb:cCZ+_AK);b(]<MOl@o"
"g`QeE]]LM?m@lgnQa)DJ75:X7@Od`^g@QgD]_LAod@\\gNQcDe5n81<f6:NJ*Bc*R@Z++YH)Kg(5"
"Bd7X_VA1eD\\_NAclXoWA>eB\\[NU56e7\\_NAadd__AAel\\oOAndb_[AU5Ze7<:N7NBd>bJ)]Z"
"+^ZH)cK7f<SO8AWd0_E@]fLS?9ATd>_a);265D:)<*g1N2\\EbZKA[K9fH)WX(5@(D<>NKNJ[Jb*"
"Bob+(*H)Z7)5Z_7<>N1N2\\Eb:d_Z+@d*A]dT_?AAddo+D2VO1add__AAed\\]OMa4e?\\ANecK7"
"BK)8c(5(K7dO\\aNe`\\gO]aLeo\\ALen\\cOe5(K:*",
s[5][65536],l[299999],sn[32]="Logo by NESMusa";
#include <stdio.h>
#include <unistd.h>
static int S,h,OF,PN,d,p=1,r,k,i,a,c,CO,j,x,T=288,f=8000,LS=0,
P[12],H[99],IL[99];FILE*fo;
static int Out(int v){int e=0,s=1,f=0;if(OF)v=128+v/30;else for((s=(v<0))?v=-v
:0;e<8;e++)if((f=((v+32)>>(e+1))-16)<16)break;return fputc(OF?v:~((s<<7)|(e<<4
)|f),fo);}void gn(void){for(f=0;A[1]>='0'&&A[1]<='9';f=f*10+*++A-'0');}int
G(int n){while((k=1<<n)>p)d+=p*(by)((*z++^134)+84),p*=64;PN=d&(k-1);d/=k,p
/=k;return PN;}int main(int C,char**V){if(V)a0=*V;for(P[CO=i=0]=907;++i<12;P[i
]=P[i-1]*89/84);while(--C)if(*(A=*++V)=='-')while(*A&&A[1])
*++A=='r'?gn(),0:*A=='d'?OF=1:*A=='h'?h=1:0;
fprintf(stderr,"%s\n\nUsage:\t %s [options]\n"
"Example: %s\n\t %s -dr22050|esdcat -b -m -r 22050\nOptions:\n"
"\t -d\tPlay in linear format instead & use /dev/dsp if not piped\n"
"\t -r#\tSelect sampling rate in Hz\n\t -h\tThis help\n\n",Title,a0,a0,a0);
if(h)exit(0);for(r=3220,LS=1810,c=4;c--;)for(a=0;a<r;)if((S=G(7))-7)
l[c+8*a+4]=S,l[c+8*a++]=G(6)^63;else for(i=G(10),S=G(8);S--;a++)
l[a*8+4+c]=l[(a-i)*8+4+c],l[a*8+c]=l[(a-i)*8+c];
for(i=IL[0]=IL[1]=32;--i>=0;s[0][i]=(i&24)?170:20)s[1][i]=16*(i<16?i:31-i);
for(i=IL[2]=999;--i>=0;s[2][i]=(a=a*999+1)%200);
if(isatty(fileno(fo=stdout)))fo=fopen(A=OF?"/dev/dsp":
"/dev/audio","wb");if(!fo){perror(A);exit(-1);}a=c=i=sn[29]=0;
for(fprintf(stderr,"Playing built-in song (%s)...\n",sn);;
i<3?i++:((c?c--^Out(S/9):++a>=r?a=LS:(c=f*5/T)),i=S=0)){
x=l[a*8+4+i],j=i?i-1:0;H[i] += P[x%12]<<x/12;
if(IL[j])S += (s[j][((unsigned)H[i]/f)%IL[j]]-128)*l[a*8+i];}}
And another:
#define _POSIX_SOURCE 1 /* Song length: 94 seconds (89 seconds of loop). */
/* This source was optimized with Findbest of the Bisqwit's Midtools pack */
/*/echo "$0 is not an executable but a C source file. Compiling it now..."
n=`basename $0`;gcc -O2 -Wall -W -pedantic -o /tmp/$n "$0";/tmp/$n $*;exit; */
static char*Title="NES-S3M player for /dev/audio (8-bit, µ-law)\n"
"This file has been compiled from a file created with intgen.";
typedef unsigned char by;char*A,*In,*tmp="nesmusa.$$$",*a0;
static by*z=(by*)"1v;FLTehJaWk1``f2:dwP2J3l>XEAKRn<eAJSl>YECkoF=LdSRWk@ioFXve"
"SdWkHtoF@we3iWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhw"
"e3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFh"
"we3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoFhwe3vWkPwoF"
"hwe3vWkPwoFhwe3vWkPwoFhwe3vWkPw_nLPwLGn30BZWk=`aF=ve3]WkABYlSu0PLjoV1TdCQWk@"
"ioV\\LFuHG08_veO0?kLhoFLve?9=kPwoFuweCW?kdLafwCd_0=kPjoFoveSaDkl_bF=ve3]WkDb"
"aFuYdCQWk@ioFSTd3vWklwGmCZXLAnYwRUNZHow9nCPBZWMGZVLuXjAmgu2\\H_0=nWPRU:d<k9n"
"Cd_HGaveOP2OGfVTuhjamgu2\\H_0=fve?0=k@Aaf?LdC>]JGn30BeWk>AafN8dCPRHwnI\\RUP:"
"3k1n3d?HG`veOP2NGdVHu`jQmgu2LDkaV0@^uKG0:n[dRUL@H_0=dve_J=kb@afALd[=2k<X0ngu"
"X[H_]Vn1jgUPvKGj<nYgRu2hH_2fXVd?0<Lwn3PRU:XHO1UnCPRUVdHw8:n1gdULtK_Dg]Vd?0\\"
"Bwn;PSU:tK?:wnS\\RU`wK_?wnaSXU`wK_vwnYgSUNZHow9nCPRU@uK_KwnaH^UjCI_2wnQbgU>i"
"H_vLnY`gUXtK_AFn1wgUjwK_vFnIYRUw_HG1:YvMuXjAmSZ?Mwn;@RU2LHw1:nY`Bd_HGaVRu<ko"
"V1:TuhjamS[?Own;@RU2LhjoV0Ld3==kV@aFILZuHG08mveK<=kJQ`F1:RuK_A:n1ZDd?HG`VPu0"
"koV1:Hu`jQm3[?Nwn;@ad?k10Ingu0XHgb:nA1QU2L`joVZMd;3=k>AafN8dCP2HwnSfRUNjI?Xw"
"n1jguXCH_v:n;PSU:hRjaV0@@uKG0:nYPRU=lIG1:niaBY?k1@Zlgu2hH_2fnEORu<XH?VuUVd?0"
"\\FAn3@B_Wkl_`F1:BuwiaV0@vtgjaV0@Nu3jaV0@0ucjaV0@Huwiicc__GAn3@a_Wk@9aV2Ld3="
"C@APRd3=?khi`F@we3LMk1X`FMAeCW:knDaV2LBuwjYM<[8MAZveOP2OGdRN=]::mgu2L`jIMLZ8"
"LAXveOPRMGYR@=U:jlgu2LTjilcY_CAn3@aYWktMaV0Ld[V=k\\QbFuMdc8Gk:`aV1LdCQWk@ioV"
"\\Ld3vw0wn3PBZ[LOYfBe]joV1BBe]J;mNZgLWnWPRU:d@jRl=Y;BK\\n8eAJSl?YGBwn3PRU:@H"
"g0>niQD[;NOdfHeaJSm>[ENWdvHugjJmmZ[MK[nNeeJKmoZgMwnYPRu0XH?LtnQ2]UoXHG0:nm_R"
"u0XH?""=LnO0]UWAHg0:nA^QU:@UjjlmY[CK_nFeUJkloYgCwn3PRU@aK?:LnoRRUYAI?t:`fPU1"
"k2n>dEHK`nPu1k3nwd[IOcf^eEKKnndeIWcv^u;kBn]d;IKbn\\e9KCn_dGIWbv\\E:KLnXdAI_b"
"F]U:kLnZdMI[bN]u:kMn[dOIcb^]uK?:kbveC>]JOmfZUMK[nNeeJKmvZuMkoV0Ld3VVkl_`V1Dd"
"c3IktMafjMd3I:kPRoFhieS2:k;Pbfg8dO0=k8c`F=XFUUjjlmYeCK_nFeUjkloYWk1@aFXhe3=:"
"kKAaflXd3V=HO`fPU1K3n>dEHK`vPu1kWnmd[IOcn^eEKKnndgIWcv]U9kBn]dEIKbn\\e9kCn_d"
"GIWb>]E:KLnXdCI_bF]U:KMnZdMI[bV]u:kMn[dIIcbve3]UIwn9SBe[JOmfZeMK[nNeeJWmvZuK"
"G0=mfd?08kUP`F1TZuIG0Am>e?P_Jwcfd?0\\Iwhfd?08TKhn`uK_DCn]QRuUPDKKn^uEAH?""=A"
"n3PRUIDIO0@cv^uEkKnodWkDBaFojZu3kbV0@Pu?kbV0@RuK_2:dfd?0<Nwn3PRU:XH_?:nUcYU]"
"jI?Xwn1:_UKVJ_0:n1jRUcAAkbV0@XuKG0:nYPRUGXTkbV0@fuckbV0PheIG0@tveSn2k=8avUEd"
"C1ZLOn30BZWk1@av3:dCQ2kt@nvkMTUIG08fvegU=k1@aFXhe3=:kfAaV0Ld[G=k1@aF:Xdg0:ki"
"Z`V1BdSD3kLPJm]u0PdjoV0Ld3:Vk@QbvNLdkJDk2o`FWM^UIG08cv]UIG0@bnd?P=IwnQbfdWUO"
"n30RfWVOn302gWk1@aFhiewv2klg`FG\\ecO=kZOaF\\Yd3:VkP_oF:Xdg0:kiZ`V1BdSD3kLPJm"
"]u0PdjoV0Ld3:Vk@QbFYadSK3kbMIn]u0PDkOn]u009KcV0R\\u9kCn_dGIwnmbfdWUOn30RfWVO"
"n302gWk1XXn]u0tK?8=mveoD=TOn302fEk181ogUb\\LKcV0Bd;3]IKn3@bdWkXPbV0Ld;SYIWn3"
"@bdAk18IngudZH?GCmvd?P\\JwncgBd;k109nguG[HwuLnYPRuSCIoEUm>e?P\\J_n3@BeMk18aF"
"@we3LFkJFaV2@e3l=k^SRn]u0P@koV0LdC1=kV@in]u0PTkoV0VBUIG08Yve?0=kL@af?LFUIG08"
"_ve;S\\LKn3@BZWkcNam]u0PhJcV0BTuK_2LnSV2[Ek18QmwZ;k10Im^u0\\djoF=BNuKG0:ncgR"
"U=XHG0:nY0]uGXH?:wnEB\\UPvK?Xwn1ZXuKZHOhCnev2Y;k10^lgu0tK?8Kni8QuRBHg0>YnBe]"
"J;mOZGkG@aF=N8UAjRl=YEBK\\n8eAjSl_U>eH_2<n;PQUV8KoY?dvHuI?t<n9:aZgMW[veC1=kw"
"OaFXZ3t_j8mAZSL;YNBe\\J9mCZ_LwdFHU`jPm2[Wk=8Qm2[=N;dVHuHw1:nY`RUIXHG0:nE2Qu2"
"HH_?b`FPU0k0n2d=H;`NPu0k1ng[3O?fFTehJamR[=OGfVTuK_2:n3PRU@aK?:LnUSRu0XHOW:n3"
"PRUL@IW1Lnu=Qu2XH?I>nY0mZSM?[FNedJImbZ]MG[VNuKG0:n1=gUX@I_lHnMgQUiZDkHnadSI;"
"cN^eDKIncd_IwbF\\U8k@nRd=I;bN\\u8kAnSd?ICb^\\E9KBn]d;IObf\\e9KCn^dEIWbv\\u9k"
"CnXdAIwnQbfdWk1@inaeSK?oNfeTKinbe_KGovaUPk0o1f=T;hN`ePk1o3fWk1@aFhieCW2k=``F"
"G\\ecO=kZOaF\\Yd3:VkP_oF:Xdg0:kvR`V1LdSD3kLPJmaZSM?[NNedJImbZ_MG[ve?0=kPRoFL"
"XdCJDkhn`fTM^UDkHnad]I;cN^eDkIncdWI?bF\\U8KAnRd=I;bV\\u8kAnSd9ICb^\\E9kBn]d;"
"IObn\\e9KCn^dGIWbv\\u9KLnXdWk@IOngu0XHoE:mFd?0_J;n3`Ce?k1hYn\\u0tLkbV0:ZuGk`"
"V0@^uSk`V0T`ePK1o2f=Twn9c\\UIXHW?"">ni?QU>AMkoV0Ld;S9k<PInSu0TDkoFSLdcefJw`F"
"d?0<HwaFd?0\\HwnYP2[3k10aFhOdC1=kt@afKFe[LGk0uoF@idKcCkv<Inld9k0UafiXXUHG08l"
"ve?0=kL@af?LfUHG08oviUHG0FtNd?P?VGn3`1gWB3n3`<Y?k1PRl^u0<AjOlPu0P8jwlPu08Ujg"
"lPu08MjofK^8uK?""=_ne<:YEBK\\n8UJG0>n1mgU@a1J`V0^0uHG0LPnd?P3@wn]A2XWknLaF1M"
"vDHG0CWVd?0ZGKn30m_Ck10jcWZ1k109mgu0XH_2:`>d?0<Hwc>d?0\\Iwb>d?P9IGn30RdWk@IO"
"ngu0XHoE:m>d?PYJGn30MeEk1hXngUIDMkoFL^ZuKG0:h>d?P9TGn302fWV3n302gWU3n30RfWkb"
"81owe1k10ingUMUJ?Lwn1]\\U>YH?LCno^RUMYHG1;oVd?0YKwn]n\\UPfHoQ9nC`2g?k10af8Md"
"_0=kD2mFVNdkMvL3n30BZWk1@aF=LPEHG08`ve;O=I3n30RdWk1@aF\\_dKUgJGmVZeIG0LmFe?0"
"3kPWaf^Ydce9kbMaV2Ldc8ZJwn3PRUIDEKHnSu08EkoFoF[uK_]:a>d?0\\Hwn1Z2[1k10Qmgu0X"
"H_2:nA;]U]fK?Xwn1:_UKVFkIncdEk1h`F@OdKF:J3n302eWk1@aF=Ldk1]K3n30beWV3n3`<g?k"
"10QogUljIo`:[>d?0\\MwnmTRu<X`joF>ud3v<k9@afjMd_0=Hwf>d?09OwnMPRu0XHow:n=YRu0"
"XH_2LnwPRUXtKo8An1jgUPvK?XRngYRUQGIoj;Y>d?PYLGn30MZEk1h8mgu0tK?8AYFBU\\j8mBZ"
"]L;YNBu\\j9mG[3N?dFHe`joV1BHe`JQm2[?NGnWPRU:dHO>:nERSu2HH_?b`FPU0k0n2d=H;`NP"
"u0k1ng[3O?fFTehJamR[=OGfVTuK_2:nwgRU@M3",
o[256],w[256][64],NN[8],s[5][65536],l[299999],
sn[32]="DrWily Alpha by NESMusa";
#include <stdio.h>
#include <unistd.h>
static int ss,m,IN,IV[99],IP[256],PP[100],b,M,I,D[99],Bxx,Cxx,B,Ss,CP,
At[256][64],Np=0,S=SEEK_SET,h,OF,PN,d,p=1,r,k,i,a,c,CO,j,x,T=250,f=8000,LS=0,
P[12],H[99],IL[99];FILE*fo,*fi;
#define rw(a) a=fgetc(fi),a|=fgetc(fi)<<8
static int Out(int v){int e=0,s=1,f=0;if(OF)v=128+v/30;else for((s=(v<0))?v=-v
:0;e<8;e++)if((f=((v+32)>>(e+1))-16)<16)break;return fputc(OF?v:~((s<<7)|(e<<4
)|f),fo);}void gn(void){for(f=0;A[1]>='0'&&A[1]<='9';f=f*10+*++A-'0');}int
G(int n){while((k=1<<n)>p)d+=p*(by)((*z++^4)-52),p*=64;PN=d&(k-1);d/=k,p/=k;
return PN;}int main(int C,char**V){
if(V)fi=stdin,a0=*V;for(P[CO=a=i=C?0:(fseek(fo,0,SEEK_END),r=ftell(fo)/8,rewind(fo),
fread(l,8,r,fo),fclose(fo),remove(tmp),z=(by*)In,main(-1,0))]=907;++i<12;P[i
]=P[i-1]*89/84);if(C<0){if(isatty(fileno(fo=stdout)))fo=fopen(A=OF?"/dev/dsp":
"/dev/audio","wb");if(!fo){perror(A);exit(-1);}a=c=i=sn[29]=0;
for(fprintf(stderr,"Playing %s (%s)...\n",*z-'*'?z:z+1,sn);;
i<3?i++:((c?c--^Out(S/9):++a>=r?a=LS:(c=f*5/T)),i=S=0)){
x=l[a*8+4+i],j=i?i-1:0;H[i] += (D[j]?D[j]*P[x%12]/14500:P[x%12])<<x/12;
if(IL[j])S += (s[j][((unsigned)H[i]/f)%IL[j]]-128)*l[a*8+i];}}
while(--C)if(*(A=*++V)-'-')In=A;else if(A[1])while(*A&&A[1])
*++A=='r'?gn(),0:*A=='d'?OF=1:*A=='h'?h=1:0;else In="*stdin";
if(!In){fprintf(stderr,"%s\n\nUsage:\t %s [options] nesfile.s3m\n"
"Example: %s mman3_d2.s3m\n\t %s -dr22050 cv2b.s3m|esdcat "
"-b -m -r 22050\nOptions:\n"
"\t -d\tPlay in linear format instead & use /dev/dsp if not piped\n"
"\t -r#\tSelect sampling rate in Hz\n\t -h\tThis help\n\n",Title,a0,a0,a0);
if(h)exit(0);for(r=4704,LS=224,c=4;c--;)for(a=0;a<r;)if((S=G(7))-118)
l[c+8*a+4]=S,l[c+8*a++]=G(6)^63;else for(i=G(9),S=G(6);S--;a++)
l[a*8+4+c]=l[(a-i)*8+4+c],l[a*8+c]=l[(a-i)*8+c];
for(i=IL[0]=IL[1]=32;--i>=0;s[0][i]=(i&24)?170:20)s[1][i]=16*(i<16?i:31-i);
for(i=IL[2]=999;--i>=0;s[2][i]=(a=a*999+1)%200);z=(by*)"built-in song";
main(i,0);}if(*In-'*')fi=fopen(In,"rb");if(!fi||!(fo=fopen(tmp,"wb+")))
{perror(fi?tmp:In);return-1;}fread(sn,1,32,fi);rw(m);rw(IN);
rw(PN);fseek(fi,49,S);x=fgetc(fi);T=fgetc(fi)*2;fseek(fi,0x60,S);
fread(o,m,1,fi);for(i=0;i<IN;i++)rw(IP[i]);for(i=0;i<PN;i++)rw(PP[i]);
for(i=0;i<IN;i++)fseek(fi,IP[i]*16+13,S),fgetc(fi),rw(LS),rw(IL[i]),rw(j),
fseek(fi,8,SEEK_CUR),IV[i]=fgetc(fi),fseek(fi,3,SEEK_CUR),
rw(D[i]),fseek(fi,LS*16,S),fread(s[i],1,IL[i],fi);
for(;;){if(CO>=m)main(0,V);if((CP=o[CO])-254){fseek(fi,PP[CP]*16,S);
rw(i);if(!i)PP[CP]=0;if(PP[CP])fread(z=s[4],i-=2,1,fi);for(r=0;r<64;r++){if(!r)ss=1;
a?0:w[CO][r]?(LS=At[CO][r]),main(0,V):(At[CO][r]=ftell(fo)/8,(w[CO][r]=1));
Bxx=Cxx=-1,Ss=ss,j=B=ss=0;if(PP[CP])while((b=*z++)-(M=I=0)){
if(b&32){if(!a){if(*z<254)NN[(b&3)+4]=*z%16+12*(*z/16);else
if(*z==254)NN[b&3]=0;if(z[1]&&*z!=254)NN[b&3]=IV[z[1]-1];}z+=2;}
if(b&64){if(!a)NN[b&3]=*z;z++;}if(b&128){M=*z++;I=*z++;}
if(M==1&&!a)x=I;M==2?Bxx=I:M==3?Cxx=I:M==4?ss=1:0;
if(M==19){if(I/16==11)*((B=I&15)?&ss:&Ss)=1;if(I/16==14)j=I&15;}}if(a)
a--;else{if(Ss)Np=ftell(fo);for(i=(j+1)*x;i;i--)fwrite(NN,1,8,fo);
if(B){c=ftell(fo)-Np;fseek(fo,Np,S);fread(l,1,c,fo);
fseek(fo,Np+c,S);for(i=B;i;i--)fwrite(l,1,c,fo);}
if(Bxx>=0){CO=Bxx-1;if(Cxx<0)break;}
if(Cxx>=0){a=(Cxx&15)+10*(Cxx/16);break;}}}}CO++;}}
Running example:
./program -dr44100|aplay - -fcd -c1 -fU8
Thus the technology improves! :) I installed them also here:When I wrote these, I was not aware of the fact that NES has different square wave types. (But iNES didn't write the distinction to its .snd files either.) These are also weirdly transponed (play at wrong pitch).
Post subject: mp3 streaming and vintage NES songs
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
arflech wrote:
I uploaded it to a website and gave it the filename chrono.cgi after compiling, then sent it to ~/cgi-bin/ and gave it chmod 777 and tried to run it in Opera...and all I get is an HTTP 500 error
Here is code that is to be used in the manner you described. Requires libmp3lame (found in LAME, the mp3 encoder) to compile and to run. I would have added my own mp3 encoder but I found it too complex to write, even if I know the exact sine waves I'm going to produce… And I couldn't find any other streaming audio codec that would work and be less complex. Wav is simple, but cannot be streamed…
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <lame/lame.h>

static const char* const channels[4] = {
  "S3802A020m!5gYW!\\!W1gZ\221Zm!4gYW!\\m!4d\\\\\\\\2g!\\y!YS3803AB10yP3dMNP2"
  "gUPW1dSyS2mPNyP3dMNP2gYUW1dZyZ2mYWyP3dMNPgUjPyS3dPQSgXjS2gVSdVj[2gXUdXj]m_"
  "Vm_W1UmSVmSS38028F20UOdNPQSUWXWUWUSQSQPLNPQSUWUOdSUSQPONMNPQSUWXWOQRTUWXWj"
  "DW1dIgIW1jNdN4gSX]\\6dZ\\!Z\\Z2gYU4d!UWYgZ6dXZ!XZX2gVS4d!STVOeXTOLHCZVQNJE"
  "[XTOLH\\WTPKHP0",
  "S3702A020m!5gUU!U!W1gS\221Sm!4gUU!Um!4dTTTT2g!Ty!YS3703AB10yM3dIKM2gPMW1dN"  
  "yN2mNNyM3dIKM2gPMW1dSyS2mPNyM3dIKMgPjMyP3dLNPgSjP2gSOdSjV2gUQdUjXW12mWWW1m"
  "KVmKS37018F70U2jNIgL2jNIgN2jLGgK2jLGgM2jNIgL2jNIgOS37028000jDW1dDgDW1jIdI4"
  "gNSXY6dWY!WYW2gUP4d!PPUgV6dUV!UVU2gSN4d!NNSOeTOLHC@VQNJEBXTNLHBZWPNKDP0",
  "S81000000g=Ad==!===!===g!2d==g=2d==7g!8;8;<=Ad==!===!===g!6d==88882g!8S090"  
  "000008d!KKDD??8ZYS81000000g=Ad==!===!===g!2d;=g;Ad;;!;;;!;;;g!2d9;P1g=Ad=="
  "!===!===g!2d;=g@Ad@@!@@@!@@@g!2d>@6iCCCEEE\221GYgBP7Yg@P7YgBP3YgCP3YgDP7Yg"
  "=P7Yg;P7Yg<P3Yg>P3S090000004dKKKKgDAdK?DK!DK!??Q0",
  "YS390081103g!'-2d''g'2d''g-2d''g'2d-'g-6d''''''g-2d--P1Y3g!'-2d''g'2d''g-2"
 "d''g'2d-'g-6d''''''g-2d--P3Y3g!'-2d''g'2d''g-2d''g'2d-'g-6d''''''g-2d--P3P0" 
}; /* This data is indeed extracted (and converted) from the game's ROM. */ 
static const double tick_factor = (44100/60)*2.5/4.0;
static const double freqmul = (440.0)/(std::pow(2.0, 'Q'/12.0)) / 44100.0;
/* 'Q' = A3 (440 Hz). See the table in http://tasvideos.org/forum/p/222583#222583 . */
static class Decoder
{
    unsigned chno, length, n_notes, loopcount, shape,vib, remticks, remwait, remdecay, voltick;
    const char* cur, *A[2];
    double hz, pos, decaypower;
public:
    Decoder(int ch)
      : chno(ch), length(0), n_notes(0), loopcount(0), shape(0),vib(0), remticks(0),
        remwait(0), remdecay(0), voltick(0), cur(channels[ch]), pos(chno*0.2)
    {
    }
    double Tick() // Synthesize the waveform, read more song if necessary   
    {
        static int dutytables[4] = {1,2,4,6}, noise=1<<14;
        while(!remticks) Read();
        if(remwait > 0) { --remwait; return 0.5; } else { --remticks; ++voltick; }
        double hztmp = vib ? hz*(1 + 0.025*std::sin(voltick/700.0)) : hz; // apply vibrato
        pos = std::fmod(pos + hztmp, 1.0);
        int wave=0;  
        if(chno < 2) // square wave
            wave = (pos*8 < dutytables[(shape>>16)&3]) ? 0 : 4;
        else if(chno == 2) // triangle wave
            return (voltick*(3*240/44100.0) >= (shape>>24)) // check volumecurve
                     ? 0.5 : ((pos < 0.5) ? pos*2 : 2*(1-pos));
        else // noise generator
            for(; pos >= 0.005; pos -= 0.005, wave = ((noise+1) & 2))
                 noise = (((noise<<13)^(noise<<14))&16384) | (noise/2); 
        double basevol = (shape>>24) & 15; // base volume from volume envelope
        basevol -= decaypower * (voltick<remdecay ? voltick : remdecay);
        return basevol>0 ? wave * basevol / 92.0 : 0.0;
    }
private:
    void Read() // Read a command from the song data
    {
        const char* begin = cur; int n_notes_orig = n_notes, loop_orig = loopcount;
        if(n_notes > 0) { --n_notes; DoNote(*cur); }
        else switch(*cur)
        {
            case 'Y': case 'Z': A[*cur-'Y'] = cur; break; // loop anchors
            case 'W': remwait = (*++cur - '0') * tick_factor; break; // note delay
            case 'S': std::sscanf(cur+1, "%8X", &shape); cur += 8; // volume & waveform
                      // VVddLLSS (V=volume|env, d=duty, L=curve length, S=curve strength)   
                      voltick = 0; break;
            case 'U': case 'V': vib=*cur-'U'; break;
            case 'P': case 'Q': // loop
                if((cur[1] - '0') != 0 // loopcount 0 means infinite loop
                && (cur[1] - '0') == loopcount++)   //  break loop when
                  { loopcount=0; cur += 1; break; } // the loop count is reached
                cur = A[*cur-'P']; // go to the loop anchor
                break;
            default: // set note length for next N notes
                unsigned char t = *cur;
                if(t >= 'a') n_notes = 1;
                else if(t <= '9') { n_notes = (t - '0') + 0;  ++cur; }
                else              { n_notes = (t - 'A') + 10; ++cur; }
                length = 4*((unsigned char)(*cur) - 'a');
        }
        ++cur;
        /*fprintf(stderr, "n=%d, loop=%d, cur=%.*s\n", n_notes_orig, loop_orig,
            cur<begin ? 3 : cur-begin, begin);*/
    }
    void DoNote(char c) // Setup a note with current length & shape values
    {
        hz = c == '!' ? 0 : std::pow(2.0, c/12.0)*freqmul;
        remticks       = tick_factor*length - remwait;
        voltick        = 0;
        remdecay       = ((shape&0x7F00) >>  8) * (tick_factor) / 4.0;
        decaypower     = ((shape&0xF0)   >>  4) / (tick_factor) * 0.25;
        if(chno==3) { remdecay *= 64; decaypower *= 2; }
        /*fprintf(stderr, "Doing note %u (%g hz), %u ticks (len %u)\n",
            (unsigned)c, hz*44100,
            remticks, length);*/
    }
} Decoders[4] = { Decoder(0), Decoder(1), Decoder(2), Decoder(3) };

int main()
{
    std::fprintf(stdout, "Content-type: audio/mpeg\n\n");

    lame_global_flags* lame = lame_init();
    lame_set_in_samplerate(lame, 44100);  lame_set_num_channels(lame, 1);
    lame_set_out_samplerate(lame, 44100);
    lame_set_quality(lame, 7); lame_set_preset(lame, 40);
    lame_init_params(lame);

    unsigned char mp3buf[8192]; short audio[512];
    for(unsigned a=0;;)
    {
        double d = 0;
        for(int c=0; c<4; ++c) d += Decoders[c].Tick() / 4.0;
        if(d < -1) d = -1; else if(d > 1) d = 1;
        audio[a] = (int)(d*32767);
        if(++a >= sizeof(audio)/sizeof(*audio))
        {
            std::fwrite(mp3buf, 1, lame_encode_buffer(
                lame, audio, audio, a,
                mp3buf, sizeof(mp3buf)), stdout);
            a=0;
        }
    }
    std::fwrite(mp3buf, 1, lame_encode_flush(
        lame, mp3buf, sizeof(mp3buf)), stdout);
    return lame_close(lame);
}
Note: It's not an accurate emulation. Simple trial and error got me only so far with the volume and vibrato curves, and the noise formula. Try also this for the data (one of my favourite NES melodies):
"YS3D009210qCiDeF\201Ce!qAiA2eC9S3D0392108e[[Y![[Y!P1ZYS3D029210iH6eTH!FTHP1Y"
"iJ6eTJ!HTJP1YiH6eTH!FTHP1iJ6eTJ!HTJiG6eTJ!GTJW1S3D039210iWViWUAeWY![!Y!W!WiY"
"7eVW!V!T!W1eRViRe!m^Ui\\W1i[Vi[U7e[YW^!\\!m[e`W1e_Vq_U3e![_W1ebV\201bUiO2e!M"
"W1eMViMU3eK!MW1eMViMU3eHKMiOeM6iRPOMKJe!iO2e!MW1eMViMU3eK!MW1eMViMU3eHKMiOeM"
"6iRPOSVYe[Q0",
"YS37029220c!Ne?OK?OHKDOK?KO?KO>MJ>MFJcCS3D0392202eVVi!2eVVi!P1ZS38029220c!Oe"
"?OK?OHKDOK?KO?KO>MJ>MFJCOeMJ>JM>JM?OK?OHKDOK?KO?KOFe>MJ>MFJCMJ>JM>JcMS380392"
"10g!qWAeWY![!Y!W!WiY7eVW!V!T!W1eRiRe!m^i\\q[7e[YW^!\\!m[e`W1e_q_2e![c_\201bY"
"iK2e!JmJ3eH!JmJ3eDHJOc\\WT`\\WT`\\WT`\\WT`[VSO[VSO8c[VSO[VSOP1Q0",
"S35000000Oe?OK?OHKDOK?KO?KO>MJ>MFJCOeMJ>JM>JM?OK?OHKDOK?KO?KOCe>MJ>MFJCMJ>J8"
"cTTTTQQNNZOe?OK?OHKDOK?KO?KO>MJ>MFJC8eMJ>JM>JMQ34eHKJHiF4e!D!FiF4e!DHJYeDP7Y"
"eGP74eHKJHiF4e!D!FiFKe!DHJDHKDHKDHJCGJCGJOQ0",
"YS38008270e'PGS380084402e//i!2e//i!YS38008270e'PGS380084402e//i!8c//////..YS"
"3D0082702e''S3D008440e/S3D008270e'P0"
Or this (rather good):
"YS3E018410eI2cLLeL2cLL3eLI!2cIIeL2cLL7eLI!PNP!2cLLeL2cLLAeLI!P!N!L!Ni!e!2cNN"
"eN2cNNGeNK!P!N!L!K!I!IPSmRAeI!IPSR!WX!2cLLeL2cLL3eLI!2cIIeL2cLL7eLI!PNP!2cLL"
"eL2cLLAeLI!P!N!L!Ni!e!2cNNeN2cNNGeNK!P!N!L!K!I!IPSmR8eI!IPSR!SW2S3E029010eUi"
"UqUeS4iXUSUe!qSeSmU8ePQPL!LPSyUeS4iXUSUW1eSqS8eSPST!TTWq\\S3F009530VyDeB4iGE"
"DEW2eDqDiDeB4iGEDEW1e@q@3e@BD\201?S3F029530U5eI!IPSmR8eI!IPSR!SW1S3F039530Ve"
"UqUiU3eSU!mX4e\\ZXWqUiU3eSU!mX2eXZiXqW3eWUSm\\3iZXW3eWXWqU8eI!IPSR!SW2S3F029"
"530UeUiUqUeS4iXUSUe!qSeSmU8ePQPL!LPSyUeS4iXUSUW1eSqS8eSPST!TTWm\\S3F0195305e"
"L!LLKiLeU4iSQPN6eN!PP!Pi!DeN!PP!P!NL!LLKiLeU4iSQPN4e!KLKW1eIqI\201!P0",
"YS3A018410e!2cIIeI2cII3eID!2cDDeI2cII7eID!LKL!2cIIeI2cIIAeID!L!K!I!Ki!e!2cKK"
"eK2cKKAeKG!L!K!I!Gi!5eI!IPSmR8eI!IPSRSUP1S3A029210c!W2eUiUqUeS4iXUSUe!qSeSmU"
"8ePQPL!LPSyUeS4iXUSUW1eSqS8eSPST!TTWW2i\\cLS3A009210eI2cLLeL2cLL3eLI!2cIIeL2"
"cLL7eLI!PNP!2cLLeL2cLLAeLI!P!N!L!Ni!e!2cNNeN2cNNAeNK!P!N!L!KS3A029210i!5eI!I"
"PSmR8eI!IPSRZ\\S3A009210e!2cLLeL2cLL3eLI!2cIIeL2cLL7eLI!PNP!2cLLeL2cLLAeLI!P"
"!N!L!Ni!e!2cNNeN2cNNAeNK!P!N!L!KS3A0292106e!I!IPSqR8eI!IPSRZ\\S3A028A10c!W2e"
"UiUqUeS4iXUSUe!qSeSmU8ePQPL!LPSyUeS4iXUSUW1eSqS8eSPST!TTWg!S3A018A10V5eI!IIG"
"iIeQ4iNNLK6eK!LL!Li!DeK!LL!L!KI!IIGiIeQ4iNNLK4e!GIGW1eDqD\201!UP0",  
"ZYS41000000eI2cIIP7YeE2cEEP7YeG2cGGP7YeI2cIIP6eGW1eIYeI2cIIP7YeE2cEEP7YeG2cG"
"GP7YeI2cIIP6eGW12eIJ2cJJeJ2cJJeJ4cQQNN2eN!2cJJeN2cJJeU2cNNeQ2cJJeI2cIIeI2cII"
"eI4cPPLL2eS!2cIIeL2cIIeS2cLLeP2cIIeJ2cJJeJ2cJJeJ4cQQNN2eN!2cJJeN2cJJeU2cNNeQ"
"2cJJYeG2cGGP26eGH!HHH2iH!YeI2cIIP7YeE2cEEP7YeG2cGGP7YeI2cIIP6eGW1eIYeI2cIIP7" 
"YeE2cEEP7YeG2cGGP7YeI2cIIP6eGW12eIJ2cJJeJ2cJJeJ4cQQNN2eN!2cJJeN2cJJeU2cNNeQ2"
"cJJeI2cIIeI2cIIeI4cPPLL2eS!2cIIeL2cIIeS2cLLeP2cIIeJ2cJJeJ2cJJeJ4cQQNN2eN!2cJ"
"JeN2cJJeU2cNNeQ2cJJYeG2cGGP26eGH!HHHiHe!iE2cEEeE2cEEeE2cEEeEiG2cGGeG2cGGeG2c"
"GGeGYi=6eI?K@L?P1iE2cEEeE2cEEeE2cEEeEiG2cGGeG2cGGeG2cGG2iHI8cQQPPNNLL4eGI!Gi"
"I2eGHiIQ0",
"YS3F0082A04c&!&&S3F008340e+S3F0082A02c&&P0"
Talk about modular programming ;) Using mpg123, you can also run this without a web server:
./mp3gen | mpg123 -
Or you can save it to a file, and play it with either mpg123 or mplayer:
./mp3gen > tmp.mp3 &
mplayer tmp.mp3
P.S. I would like to see how this code would look like if written in Haskell. Apparently someone has written an MP3 decoder in Haskell and turned it into a teaching article: http://blog.bjrn.se/2008/10/lets-build-mp3-decoder.html If you're only interested in the music, you can listen to these at:
Post subject: Re: Input Visualization Ideas
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Aqfaq wrote:
One idea I had is to create something similar to Star Control, where the cockpit view of the ship pilot is animated to correspond the player input:
I once had a similar idea. I planned on making a povray animation showing an player holding the controller. I did make a movie, but it lacked the player. http://bisqwit.iki.fi/jutut/kuvat/nesvideos/notmedia/povray-assisted-clip.avi (15 MB) Sorry about the blurry quality. We did not have H.264 when I encoded it.
Post subject: On the timer speed (includes two demo fm2s)
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Randil wrote:
this RAM address increases by 34 each frame (so on average life decreases every 256/34=7.53 frames)
Actually, in rooms 13, 20, 24 and many others, it changes by 25 per frame. In room 34 and the fairy room, it changes by 45 per frame. Yes, this game is screwy. P.S. In room 34, you should take the white jar (visible), which will multiply your timer by 5 (and the timer decrement speed as well), and then the orangehourglass which is at (7,3) ― it will set the timer to 5000. As a result, the remaining timer at level completion should cut very close to 0, because after taking the hourglass, you've got about 9.5 seconds ((5000/50) * (256/45) / 60) before your time runs out. The same strategy is theoretically possible also in stages 17, 24, 26 and 46, but only in 34 and 46 it may save time (due to the order in which they coincide with Dana's path). EDIT: Example demonstrating the effects of the jar+hourglass. -- http://dehacked.2y.net/microstorage.php/info/1155111567/solokey34-fail.fm2 -- surprisingly quick death; this is in fact the fastest possible way and location in the game to get a "time over" death from the beginning of a room. -- http://dehacked.2y.net/microstorage.php/info/26091512/solokey34.fm2 -- death is narrowly averted by keeping the timer preoccupied at the last moment, allowing completing the room. Transition to room 35 is quite short.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
arflech wrote:
I tried playing the MIDI file but it wouldn't load in Anvil Studio (an editor for MIDI files).
Hmm. The program assumes that std::cout does not do any changes to the binary data, such as changing LFs to CRLFs. It is possible it only works properly on Unix systems. EDIT: I completely forgot that some systems require such changes. I suck at C (or C++). To change the program such that it works also on Windows, change the std::cout line to:
  std::string tmp = "MThd"+n(6,4)+n(1,2)+n(6,2)+"\3\232"+A.G()+B.G()+C.G()+D.G()+M.G()+N.G(); 
  std::fwrite(tmp.data(), 1, tmp.size(), stdout);
And add #include <cstdio> to the beginning of the file.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
One of my tiny musical tunes as a an ANSI C++ program. This will generate a MIDI file to stdout. Run by redirecting the output to a file, e.g:
./prog > result.mid
#include <string>
#include <iostream>
#include <cstdio>
typedef std::string S;int Kf=128,Ko=144;
static S n(int v,int k) {return (k--?n(v>>8,k)+(char)v:S());} /* Big-endian encoder */
struct mt{
  S s/*stream*/;int d/*delay*/,c/*channel*/,P/*last note number*/;
  mt(int t):d(0),c(t),P(0) {me(88,"\4\2\30\10");me(81,n(1000000,3));}
  S G() {return me(47),"MTrk"+n(s.size(),4)+s;} /* End track and return as string */
  void F() {if(d>>14)B((d>>14)|Kf);if(d>>7)B((d>>7)|Kf);B(d&127);d=0;} /* Flush delay */
  void B(char c) {s+=c;} /* Append raw byte */
  void by(int c) {if(c&0x80)F();B(c);} /* Append byte, flush delay when starts a new event */
  void d2(int T,int a) {by(T|c);B(a);} /* Two-byte event */
  void t3(int T,int a,int b=127) {d2(T,a);by(b);} /* Three-byte event */
  void me(int t,S a=S()) {by(255);B(t);B(a.size());s+=a;} /* Meta event */
  void dif(int c,int p,int b,int o=0)
    {if(c!='!')t3(Kf,P),t3(Ko,P=c+o,b); d+=p;} /* Ascii note */
  void com(int Patch,const char*s,int p,int b=127,int o=0)
    {for(d2(0xC0,Patch);*s;)dif(*s++,p,b,o);}/* Note parser */
}A(0),B(1),C(2),D(9),M(4),N(5);
#define X2(s) s s
#define X4(s) s s s s
#define ABI(b,c) #b"!"#b X2(#b"!")X2(#c"!")#c X2(#c"!")

int main(void)
{
  int De=133;
  C.com(0,"!",10*2);
  A.com(0,"!",20*2);
  X4(X2(
  A.com(0, ABI(T,T)ABI(T,T)ABI(T,T)"SRS!V!V!V!!!!!!!", De,127);
  B.com(0, ABI(L,Q)ABI(P,O)ABI(M,K)"JJJ!M!O![!!!!!!!", De,127);
  C.com(0, ABI(O,M)ABI(K,K)ABI(P,P)"OOO!O!S!S!!!!!!!", De,127);
  M.com(35,"0!!!!!!5!!!!!!!!8!!0!3!7!!!!!!!!5!!!!!!3!!!!!!!!2!!!!!!!7!!!!!!!", De,127,-12);)
  N.com(75,X4(X4(X4("!")))
           "T!TT!Q!Y!X!V!T!!P!!WV!T!O!!!!!!!P!!PT!P!O!P!K!!!M!O!J!C!M!!!!!!",De,127);
  N.com(75,"$",De,0,0);)
  
//  std::cout << "MThd"+n(6,4)+n(1,2)+n(6,2)+"\3\232"+A.G()+B.G()+C.G()+D.G()+M.G()+N.G();

  std::string tmp = "MThd"+n(6,4)+n(1,2)+n(6,2)+"\3\232"+A.G()+B.G()+C.G()+D.G()+M.G()+N.G();
  std::fwrite(tmp.data(), 1, tmp.size(), stdout);
}


/* Chart of note names:
 -  A-0     9  A-1      E  A-2    Q  A-3   ] A-4   i A-5   u A-6
 .          :           F         R        ^       j       v
 /  B-0     ;  B-1      G  B-2    S  B-3   _ B-4   k B-5   w B-6
 0  C-1     <  C-2      H  C-3    T  C-4   ` C-5   l C-6   x C-7
 1          =           I         U        a       m       y
 2  D-1     >  D-2      J  D-3    V  D-4   b D-5   n D-6   z D-7
 3          ?           K         W        c       o
 4  E-1     @  E-2      L  E-3    X  E-4   d E-5   p E-6
 5  F-1     A  F-2      M  F-3    Y  F-4   e F-5   q F-6
 6          B           N         Z        f       r
 7  G-1     C  G-2      O  G-3    [  G-4   g G-5   s G-6   ! is used
 8          D           P         \        h       t         for pauses
*/
In GCC, compiles without compiler warnings.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Randil wrote:
I wish you the best of luck with your bot project, hopefully it works out well. Will you post your progress in this thread or only on your personaly Solomon's Key page? And is there any chance that you'll upload your movie file on you SK page (or in this thread) so that we can take a look at your progress as it goes along?
Thanks Randil, but I think I just gave up. Here is my current WIP. http://dehacked.2y.net/microstorage.php/info/1912856927/solomonskey-bisqwit6-wip-3rooms.fm2 At the beginning of room 3, it is three frames behind your WIP (despite higher timer readings in both preceding stages). In room 3, it finishes with 8070 in the clock (yours has 8060), but the room is still 16 frames slower than yours. As it stands that aiming for only short-term optimizations is not enough in this game (extensive optimizations done early can screw up progress later on in the same room), I find the TAS demanding more work than I am comfortable with, and therefore I quit. I am however releasing my Lua scripts for those who want to use them. To run the bot, start FCEU with the solokey.lua script loaded and the movie loaded:
fceu/fcx --loadlua solokey.lua --playmov solokey.fm2 solokey.nes
It will load the savestate from slot #0 and immediately start working towards accomplishing the objective that is set using the state:setobjective function, with input generated by InputGenerator. Once it finds something that accomplishes the goal, it attempts to refine it with InputSimplifier, found in luafuncs.lua. Then it proceeds finding more solutions, with the refined frame&timer limits. When it finds a new, better solution, it saves the solution in savestate slot #8 and reports the input that generated that solution by using FCEU.message(). It is recommended to modify FCEUX such that FCEU.message() always outputs to stderr and that extreme frameskipping is used while the Lua script runs (so it runs faster). Objectives are specified as coordinate regions where Dana must reach (for example, X coordinate between 0x53..0xFF and Y coordinate between 0x70..0x80), as the maximum frame count to expend in doing so, and as whether possessing the key is mandatory for the objective to count. Input can be generated randomly or programmatically. I used both, at times. Programmatic approaches can amount to "hold R, until Y coordinate > 0x50; when X is (a randomly selected value), hit A and wait 17 frames", etc. This can be used to e.g. find the best location to cast magic where the timer gets most delayed while the framecount stays at minimum. The successfulness of the objective is quantified as a function of the frame count, dana's coordinates, the internal timer, and a sum of various things including the score, number of fireballs in inventory, and the such, with the frame count serving as the primary decisive measure. A death, or failing to reach the destined coordinates within the frame limit, amounts to failure.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
I started creating a new Solomon's Key TAS experimentally with most of the bulk work done by Lua scripts. As for the timing related goals, I'm doing somewhat a mixture here. From http://tasvideos.org/Bisqwit/Projects/SolomonsKey.html: ③Ⓐ least frame count ③Ⓑ biggest remaining timer readings Goals ③Ⓐ and ③Ⓑ are somewhat adjacent and vary. <...> I am primarily aiming for fastest level completions frame-wise, but within that goal, I am aiming to optimize for maximum remaining timer reading. However, I do take the hourglass items which reduce the timer to 5000 when possible, because it makes the score tallying go quicker after the room is completed. In short, I try to get the best of both worlds. I have programmed these goals in the Lua script I use, but I when working manually, I do not do frame count comparisons; I use the game's timer for that.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
NitroGenesis wrote:
And I think that it's a rule or something that if you know your TAS can be improved not to submit it.
This is just my opinion, but I think someone a long time ago said that TASVideos is about publishing TAS entertainment, or something to that effect. Is this movie not entertaining to watch? A movie is better than no movie. The only circumstances where I think it is reasonable to reject a movie for it lacks the use of a known trick but is still quite entertaining, is if A) it harms the entertainment greatly (such as failing to run, -cough-), or B) a replacement movie is not too big effort to create (i.e. it could be expected to be finished in another week).
Post subject: discoveries
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
After revising the disassembly a few times, I have discovered the source of the volatileness of the game's timings. This game uses coroutines (single-core multithreading with voluntary context switching). Such simple operations like responding to the player's input, or spawning new fairies, or running enemy AI, are handled by threads, where there is no guarantee whatsoever that any particular thread gets run during a single frame. Conversely, the same thread may well get run multiple times during the same frame. In other words, the game's running speed depends entirely on what it has to process. Pressing the up button during a jump may cause the timer to count an extra tick. Spawning 10 fairies may cause enemies to walk through walls, because their AI threads receive too little runtime in proportion to that thread that just pushes them all forward on their current trajectories. Yes. And oh, Dana does have subpixel coordinates and the timer has an invisible fractional counter. Now that I know the rules of this game, I have much better chances of creating an useful bot. I have managed to reach beat Randil's timing for the first stage already, with 9380 remaining in the timer. :) Interestingly enough, it is quite easy to optimize in this game for frame count. Optimizing for the game's timer, not easy. Do a 1 frame longer route, and you may +10 units better timer result...
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Derakon wrote:
When I asked Bisqwit how he did memory watch in FCEU, he said he just dumped the contents of memory to a file each frame and had written customized programs to scan the file for what he wanted.
Off-topic, but I think you're mixing an explanation of mine for something with an explanation of mine for something else. File handling was not involved in my memory watches. I just coded the relevant code in FCEU and that's it. IIRC, someone used to memorymap the FCEU memory space into a file or something, but that wasn't me.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Solomon's Key! I am surprised to see that there is no existing discussion thread for this game. It is a puzzle game where the protagonist, who is an old and clumsy magician, can conjure into existence and dissipate solid bricks. I have previously created a TAS of it. 498M completes all rooms, including all secrets, excluding the fairy bonus rooms, and thus gets the best ending. I have long planned to improve it, but progress has been extremely slow. This is a difficult game to TAS, because the game's timings are extremely volatile. Really. It seems like the game does not obey any frame granularity whatsoever. The timer ticks at different rates, and frames can be lost without any apparent lag occurring. Due to changes in my life I am not going to expend personal labor in TASing this game. Here is a table that collects the timings from my previous run (measured in game timer): [1] ― or [2] if Nach has noticed my URL restore request. Now, I created a bot once to play levels in this run. However, despite my extensive and best efforts, I have not been able to create a bot that bests the volatileness features of the game's timings. In fact, Randil managed to create a test that completed the 1st room with 9370 (more) left in the timer, while expending the same number of frames as my bot did. Here is the source code of my bot: http://bisqwit.iki.fi/kala/solomonbot1.cc (Modified to not require coroutines ― this version assumes that FCEUI_FrameAdvance() actually runs a frame and does not just set some flag. Should be easy (hah!) to port to LUA.) This bot is not autonomous. It works like this: 1. Operator (you) defines an objective. An objective is Dana's coordinates must reach a certain rectangle, and the maximum number of frames to expend doing the objective. 2. Operator creates a savestate at the beginning of the scene that the bot must play from 3. Operator runs the bot, tells to start from that savestate. 4. Bot runs, and when it reaches the objective, it saves and reports the success, refines the input, saves and reports it again, then tries harder (tighter time limits). 5. When the operator is satisfied, he takes the movie again, replays the input that the bot defined, then goes to step 1. To help the bot, the operator can add extra conditions occasionally. For example, if Dana's Y coordinate > 0x5C, it might indicate that Dana has fell from the platform and the attempt is failure. Or he might require that Dana must pick the key during this objective. I am releasing this to the TASvideos community in the hope that it is useful to someone. If anyone wants to obsolete my TAS, go ahead. It was created with Famtasia, too! Techniques involved in this bot: ― Generate random input (attributes: hold L or R for quite some frames, occasionally change, sometimes hit A, possibly while ducking; sometimes jump). ― If the given input accomplishes the goal within the allowed time, yay! Then attempt to refine that input with certain mutation types: ― Mutation method 1: Delete a random frame from the input ― Mutation method 2: Randomly duplicate some frame ― Mutation method 3: Randomly add a new input frame somewhere in between ― Mutation method 4: Swap two random sections of the input sequence ― Mutation method 5: Change some frame into new random input ― If the mutated input accomplishes the goal faster, yay ― If the mutated input accomplishes the goal in the same time, but with better bonus pickups, yay ― If the mutated input accomplishes the goal in the same time, but the input is simpler than the previous find, yay ― If no improvement has been achieved after N mutations, go back to generating new random inputs Input simplicity is emphasized because the bot is supposed to report its generated inputs to the human operator, and the human is supposed to repeat them by reading them from the text. RAM map at: http://bisqwit.iki.fi/jutut/solokey.map Disassembly of the game (with labels on RAM/ROM address where known). If you know more RAM / ROM addresses, let me know and I'll update the disassembly.
Post subject: Update and source code
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Hpmf, hardware failures seem to be conspiring against my continuing this TAS, but it seems that the bot managed to reaffirm the first shot of board 34. It took about 48 hours to do it. Now going for shot 2. Five balls remaining. I switched to Xvfb, hopefully the bot can now run without intervention -- no dependency on ssh link to another computer's X server. That is, unless more hardware blows up. Also, I just found out that the technical description in the previous post was slightly inaccurate. The method that candidates are chosen is slightly different for each "generation" they are tested. This is a genetic algorithm after all. For the first generation, all 1-shot attempts are tried (256 angles, 80 velocities (18..255)). For the second generation, many plausible 2-shot attempts are tried, excluding those where the first shot was already found to pocket something. The first shot includes all 256 angles and 16 velocities (18..63), and the second shot includes 20 velocities (191..255) for a set of different angles such that the more the angle differs from the first shot's angle, the sparser are the actual angle values tested. This helps limit the search space into manageable numbers, while still yielding rather good results. The second generation of the first round of board 34 had about 5.4 million candidates. The actual number varies because sometimes just 1 shot pockets something (even the cue ball). In those cases, the 2-shot attempts beginning with that 1st shot can be omitted. For the second round, the 2nd generation candidate count seems to be around 8 million. Which I estimate takes about three days to go through. Luckily this bot is not written in LUA. For the rest of the generations, the best candidates are chosen from the previous generation, and about 20000 new candidates are chosen by randomly mutating those slightly. If there has been no improvement in the last 80 generations, the best so far is considered the winner and is recorded into the movie. Then begins the next round. Here is the source code of the bot, current revision. (Slightly adjusted so it can be shown as a stand-alone C++ file that compiles without errors, though it does not link.) Two alternatives are provided: http://bisqwit.iki.fi/kala/lunarballbot1.cc : With coroutines. Use this if your FCEUI_FrameAdvance() does not actually run a frame, just sets some flag. http://bisqwit.iki.fi/kala/lunarballbot2.cc : Without coroutines. Use this if your FCEUI_FrameAdvance() actually runs a frame. This can be used as basis for porting to LUA. (These URLs may work or not work, at any given time. I said I'm having hardware problems. If it doesn't work, try later again...)
Post subject: Collection of bugs I found just by using fceux
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
This looks like a bug in sdl/input.cpp. g_config->getOption("SDL.Hotkeys.Pause", &bindStateKey); Should be: g_config->getOption("SDL.Hotkeys.BindState", &bindStateKey); ---- Here's my script.
local a=savestate.create(1)
savestate.load(a)
And here's how I start it:
fceu/fcx2 --loadlua test.lua game.nes
And fceux crashes. What am I doing wrong? Shouldn't the ensureLoad() method of LuaSaveState in lua-engine.cpp be called at some point? ---- fceulua.h defines FCEU_LuaFrameskip() yet in lua-engine.cpp it is FCEU_LuaFrameSkip(). Neither of which have any meaning -- they're never used anywhere. And hence, choosing the speedmode "maximum" has no effect!
Post subject: Include files in LUA?
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
So if I have a library of common functions, how do I #include it in LUA? Ah, that is dofile. Thanks Ilari and LUA manual :)
Post subject: Reading CPU state & breakpoints in LUA
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Many of my bots on FCEU have breakpoint handlers such that if the CPU happens to execute e.g. ROM location A56C, the value of the CPU register A can be captured into a variable, and used by the bot. This can be used to determine various things, from success in performing a glitch, to identifying an item that was just picked up. Now, how do I do that in FCEUX and LUA? (I've presented this question years ago and it was without a solution back then, but I imagine emulator technology has improved by heaps since then!) EDIT: It indeed has! Congratulations to all programmers. So, replying to myself for others' benefit:
memory.registerexec(0xC4D0, function()
    FCEU.message( "Acquired item " .. memory.getregister("A") )
  end)
Which can be rewritten as:
CPU=setmetatable({}, {__index=function(t,k) return memory.getregister(k) end })

memory.registerexec(0xC4D0, function()
    FCEU.message( "Acquired item " .. CPU.A )
  end)