carlc27843 [Lexaloffle Blog Feed] Cursed Console - the pumpkins hack back! <p> <table><tr><td> <a href="/bbs/?pid=97954#p"> <img src="/bbs/thumbs/pico8_cursedconsole-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=97954#p"> cursedconsole</a><br><br> by <a href="/bbs/?uid=23375"> carlc27843</a> <br><br><br> <a href="/bbs/?pid=97954#p"> [Click to Play]</a> </td></tr></table> </p> <h3>Plot</h3> <p>Oh mighty <a href=""> @zep</a>, a quest for thee: to fix this bug in oh-two-three.<br /> Time's very fabric has been torn, and monsters from the past reborn!<br /> A pumpkin army's impossible roll befouls our once cozy console.</p> <p>Its humbled clock refunds too much, permitting Cauldron's villain's touch.<br /> Relentlessly they spin and taunt, they dare thee to remove their haunt.<br /> Please help us take our console back and save us from their cycle hack!</p> <p>Their jeering eyes provide the clue, all stars show what you must do.<br /> A table's count below sixteen, as low as any number's been,<br /> Negate it and they'll mock you more... the wellspring of this wretched flaw!</p> <h3>About</h3> <p>An entry in 1023 chars to the #pico1k jam. See <a href="">itchio page</a> for the cart frozen using PICO-8 0.2.3, in case zep completes his quest and this no longer works in later versions of the console. ;)</p> <p>The cart demonstrates a virtual CPU exploit in PICO-8 v0.2.3, as ordinarily the fantasy console would not be fast enough to do the rendering at 30fps letalone 60fps. Since we're breaking out of the virtual machine limit the speed will depend on your device. The standalone PICO-8 app runs at 60fps on my dekstop, but the web player is smoother at 30fps. Note the cart played above is a slightly earlier version in 1019 chars which runs at 30fps.</p> <p>Before we delve into the deciphered demo, feast your eyes on this grisly gist of the final code. You should be able to copy and paste this into your rebooted console and run it. Note I had to rewrite a tab character in one of the strings as \t to get around the lexaloffle website mangling it.</p> <h3>1023 Character Crafty Code</h3> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>_set_fps(60)๐˜ฉ=64โ˜…={}s=.001๐˜ด=sin while(#โ˜…&gt;=0)add(โ˜…,โ˜…) poke(24337,ord('โ–‘โด์›ƒ\t\n&hearts;โฌ‡๏ธโฌ‡๏ธ&sup3;&sup3;โฌ…๏ธโŒ‚\0\0',1,14))๐˜ข=abs fillp(โ–’\1)๐˜ค=cos?&quot;โถc0โถwโถt แถœeโ˜…&quot; function ๐˜ญ(๐˜บ)all(โ˜…)for t=0,โ–ˆ,s do w=r-r/3*๐˜ด(t*7%โ–ˆ)x=w*๐˜ค(t)๐˜น=๐˜ข(x)z=w*๐˜ด(t)l=(x*x+y*y+z*z)^.5b=1+๐˜ข(x/5+y/3+z)/l*5 if(y&gt;=11-x^2/200and y&lt;=27-x^2/83+๐˜น*.1or pget(๐˜น,-y)&gt;0)b=8 sset(๐˜ฉ+x,๐˜บ+y,b+((b+โ–ˆ)\1-b\1)*8)end end y=0while(y&lt;9)r=2+y/16๐˜ญ(22)y+=1 a=0while(a&lt;.43)d=30+15*๐˜ข(๐˜ด(a))y=d*๐˜ค(a)r=d*๐˜ด(a)๐˜ญ(๐˜ฉ)a+=s function p(v)poke2(d,v)d+=๐˜ฅ end ๐˜ฅ=2๐˜ฎ=12280d=๐˜ฎ for i=1,84do n=ord('@/\0ใ€@)t๐˜ธแถœแถœใ‚‰แถ แถ แต‰แต‰โ˜โ˜ใโ™โ™ใโ—โ—โ– โ– ใ‚‰โ—โฌ…๏ธแต‰แต‰&hellip;ใ‚จใ›ใ‚‰ใ‚ใฌ$+07&lt;70+ใฏ$,08&lt;80,ใƒฃโ—ใป$)05&lt;50)ใฏ$*06&lt;60*&amp;+2โ—†72โ—†ใ›*ใƒฃใฏ',i) if(n&lt;128)p(n)d-=1else for _=0,n\16%8do p(%(d-n%16*2-2))end end p(-32384)p(770) d+=316๐˜ฅ=68v=7169p(v+24)p(v+32)p(v)p(v)music() ๐˜ณ=0function ๐˜ฑ()p(%c+@a)c+=2end ::_::?&quot;โถ1&quot; while(๐˜ณ-8&amp;31!=stat(20))d=12800+๐˜ณ%32*2a=๐˜ณ%128+๐˜ฎ+8c=๐˜ฎ ๐˜ฑ()๐˜ฑ()a+=128๐˜ฑ()๐˜ฑ()๐˜ณ+=1 a=t()*.1c=๐˜ค(a)s=๐˜ด(a)๐˜ป=s+2for y=-๐˜ฉ,๐˜ฉ do all(โ˜…)for x=-๐˜ฉ,๐˜ฉ do v=๐˜ฉ+(s*x+c*y)*๐˜ป&amp;127 ๐˜ท=v-73if(๐˜ท&gt;0)v-=๐˜ท*๐˜ด(a*16)/2 k=sget(๐˜ฉ+(c*x-s*y)*๐˜ป&amp;127,v)d=k\8k%=8if(k&gt;0and v&lt;30)k+=7 pset(๐˜ฉ+x,๐˜ฉ+y,k+(k+d)*16)end end goto _</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <h3>Scintillating Source</h3> <p>Now let's rename the identifiers, remove variables that exist only to reduce code size, convert the strings to bytes, expand some shorthand ifs, whiles and prints, unfold some constants, and format it legibly to explain what it's doing. Once again you should be able to copy and paste this into your console and run it.</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>-- The standalone PICO-8 app may handle 60fps, depending on your device. -- Remove this to run at 30fps. _set_fps(60) -- The &quot;all star&quot; virtual CPU exploit! -- -- PICO-8's all(c) function very nicely refunds some virtual cpu cycles via -- &quot;_refund_cpu_((#c &gt;= 16) and -16 or -#c&quot;, where _refund_cpu is an -- internal function that actually adds to the virtual cycles consumed; -- so a negative argument will refund cycles (cart goes faster), -- and a positive argument will consume cycles (cart goes slower). -- -- all(c) where #c==0 causes no extra cycles to be refunded or consumed. -- all(c) where #c&gt;0 and #c&lt;16 results in #c cycles being refunded. -- all(c) where #c&gt;=16 results in only 16 cycles being refunded. -- -- To exploit _refund_cpu and achieve undeserved cycles refunded we -- need #c to be less than 16 but -#c to be negative and large. -- -- Negative numbers don't seem to help, even if we could make a negative -- sized table. -#c will just be positive so _refund_cpu will actually -- consume extra cycles and the cart will go slower! -- -- Fortunately for us, in 1945 John von Neumann anticipated humanity's -- need to exploit fantasy consoles 76 years hence and prankishly proposed -- using twos complement to represent negative numbers in future so- -- called &quot;electronic stored-program digital computers&quot;. -- -- In PICO-8, 0x8000 == 32768 == -32768 == -0x8000. So if we can make -- #c==-32768 then all(c) will call _refund_cpu(-#c == -32768) and John's -- prescient plan will reach its triumphant culmination. -- -- In lua the __len operation on a table's metatable will be called by #c. -- So we could do: -- -- c={__len=function() return -32768 end} -- setmetatable(c,c) -- -- but we can do it in fewer characters if we have plenty of memory: star={} while(#star&gt;=0)add(star,star) -- since 32767+1 == -32768 this loops until #star == -32768. -- now all(star) will call _refund_cpu(-32768) and time will flow backwards! -- -- Note: it doesn't matter what we add to the table but add (โ˜…,โ˜…) looks -- spookily like a malevolent pumpkin staring hungrily through the depths of -- time... -- Set the screen palette, equivalent to pal({...},1) but shorter. poke(0x5f11,ord(&quot;\x84\x04\x89\x09\x0a\x87\x83\x83\x03\x03\x8b\x8a\x00\x00&quot;,1,14)) -- The cart's palette is: -- Color 0 is unassigned as it defaults to zero (black), as desired. -- Colors 1-6 define a brown/orange/yellow gradient for the pumpkin's skin. -- Colors 7-12 define a green gradient for the pumpkin's stalk. -- Color 13 is black because the approximate math behind the pumpkin actually -- generates a 7th gradient color in the top-middle pixel of the stalk, -- and setting that to black results in a pleasing &quot;fork&quot; in the stalk, if -- you look closely and squint a bit. -- Color 14 is black because PICO-8's default spritesheet has a star drawn -- in color 7 in the top-left; later we'll see we add 7 to the spritesheet -- color near the top of the spritesheet, and setting color 14 to black was -- cheaper than clearing the unwanted pixels. -- (In retrospect we could have shifted the palette entries up one index -- and had the gradients start at color 2, to avoid the duplicate blacks -- and thereby saved two chars). -- In PICO-8 โ–’ is a variable set to 0b0101101001011010.1 which is a checkered -- fill pattern. We need to remove the .1 transparency bit using \1. fillp(โ–’\1) -- P8SCII code to clear the screen, set wide mode on, set tall mode on, skip -- a couple spaces, use color 14 (black but non-zero) and draw a star. We will -- pget these pixels later to cheaply carve the pumpkin's eyes (upside-down). print(&quot;โถc0โถwโถt แถœeโ˜…&quot;) -- plot_pumpkin_row draws one row of either the pumpkin face or stalk to the -- spritesheet. We draw the pumpkin to the spritesheet once on startup, then -- each frame read from the spritesheet to render it to the screen. -- We iterate angles to derive individual 3d pixel positions on the surface, -- then completely fake lighting in a mathematically unsound yet surprisingly -- pleasing way. -- The resulting pixel is assigned a pair of colors to represent a smoother -- gradient via the fill pattern. So in fact there are 11 &quot;orange&quot; values -- counting in-between colors, plus one for the mouth/eyes, and 12 &quot;green&quot; -- values. -- PICO-8 only stores 4 bits per pixel in the spritesheet, so we actually only -- store the uncolored lighting value which happens to coincide with the -- orange gradient colors. -- When rendering the pumpkin each frame, we check each pixel's y coordinate -- to determine whether it was a stalk, then shift the color to the green -- gradient by adding 7. (This is why that wayward star in the default -- spritesheet ends up with color 14, which is why we set index 14 to black.) function plot_pumpkin_row(yofs) -- exploit: accelerate this slow startup code! all(star) for t=0,.5,.001 do -- is a great demonstration of this -- &quot;pumpkin segment&quot; math to get the distance to the surface from the -- vertical axis. w=r-r/3*sin(t*7%.5) -- x is screen position relative to the origin (middle of the screen) x=w*cos(t) absx=abs(x) -- z is distance from XY plane through pumpkin's center z=w*sin(t) -- Fake diffuse lighting using surface position as normal and an -- unnormalized light vector. The value is put in the range 1-6. l=(x*x+y*y+z*z)^.5 b=1+abs(x/5+y/3+z)/l*5 -- Check if this pixel is part of the mouth (first expression) or -- the eyes (second expression) by reading the star we printed earlier. if (y&gt;=11-x^2/200 and y&lt;=27-x^2/83+absx*.1) or (pget(absx,-y)&gt;0) then b=8 -- base color is 0, and the high bit=8 indicates a 2-color pattern end -- If the pixel value's fractional part is &gt;= 0.5, then set the high bit=8 -- to remember to use a two color gradient. sset(64+x,yofs+y,b+((b+.5)\1-b\1)*8) end end -- Plot the pumpkin stalk to the spritesheet y=0 while y&lt;9 do -- Stalk is thicker at the base r=2+y/16 -- Plot one row of the stalk plot_pumpkin_row(22) y+=1 end -- Plot the pumpkin face to the spritesheet a=0 while a&lt;.43 do -- Distance to each horizontal slice d=30+15*abs(sin(a)) -- y coordinate of slice, relative to the screen origin y=d*cos(a) -- Horizontal radius of slice r=d*sin(a) -- Plot one row of the face plot_pumpkin_row(64) a+=.001 end -- Utility function to poke2 a value to the global 'dst' and advance dst -- by 'dststep'. Reused with two different dststep values. function poke2_step(v) poke2(dst,v) dst+=dststep end -- Uncompress the music data -- -- The uncompressed music data has some per-channel values (4 words), plus two -- sequences of 128 note pitches (256 bytes). -- The per-channel word values define the SFX instrument, volume and effect. -- Each note's pitch is added to this value when it's written to the SFX. -- Channel 0 is 0x2f40: organ, volume 7, vibrato. (sequence A) -- Channel 1 is 0x1900: pulse, volume 4, slide. (sequence A) -- Channel 2 is 0x2940: organ, volume 4, vibrato. (sequence B) -- Channel 3 is 0x5774: noise, volume 3, fade out. (sequence B, pitch offset -12) -- -- We use simple LZ style compression customized for the input. All the pitches -- are in the range 0-63, and serendipitously the per-channel words above also -- only have bytes less than 0x80, so we use bit 7 to mark offset/length pairs. -- It turns out the sequences have a lot of repetition, and limiting the offset -- to multiples of 2 between 2-32 and length to multiples of 2 between 2-16 -- works well and packs into 8 bits. -- -- The sound data is poked to just before 0x3100 so that the dst pointer is -- ready to setup music pattern data after uncompressing. -- -- Counting the compressed pitches plus uncompression code, 177 chars are -- used in the final cart, compared to 256 bytes for the raw pitches. snddata=0x3100-(256+8) dststep=2 dst=snddata -- Iterate compressed music data bytes for i=1,84 do n=ord(&quot;\x40\x2f\x00\x19\x40\x29\x74\x57\x0c\x0c\xc0\x0f\x0f\x0e\x0e\x14\x14\xa0\x13\x13\xa0\xff\xff\x11\x11\xc0\x86\x8b\x0e\x0e\x90\xcf\xa7\xc0\xbb\xb0\x24\x2b\x30\x37\x3c\x37\x30\x2b\xb3\x24\x2c\x30\x38\x3c\x38\x30\x2c\xfb\xff\xb7\x24\x29\x30\x35\x3c\x35\x30\x29\xb3\x24\x2a\x30\x36\x3c\x36\x30\x2a\x26\x2b\x32\x8f\x37\x32\x8f\xa7\x2a\xfb\xb3&quot;,i) if n&lt;128 then -- This is a payload byte, poke it poke2_step(n) -- Our utility function writes words, so step back a byte dst-=1 else -- This is an offset/length pair; copy from prior uncompressed data, word by word. -- (Allows reading words just copied to get repeated subsequences) for _=0,n\16%8 do poke2_step(%(dst-n%16*2-2)) end end end -- Poke the pattern data; we set up a single pattern that references SFX 0,1,2,3 and loops. poke2_step(0x8180) poke2_step(0x0302) -- Skip dst to 0x3200+64 where we will poke SFX control data dst+=316 -- Each SFX is 68 bytes dststep=68 v=0x1c01 -- Channel 0's SFX control is 0x1c19: speed=28, reverb=1 -- Channel 1's SFX control is 0x1c21: speed=28, reverb=1, detune=1 -- Channel 2's SFX control is 0x1c01: speed=28 -- Channel 3's SFX control is 0x1c01: speed=28 poke2_step(v+0x18) poke2_step(v+0x20) poke2_step(v) poke2_step(v) -- Same as music(0); starts the music music() -- sndrow tracks which music row we're about to stream out. -- sndrow%128 is the note index to read from the music data. -- sndrow%32 is the SFX row to poke. sndrow=0 -- poke_note_step is a utility function which reads the per- -- channel word value, and adds it to the current sequence's -- pitch value, then writes it to the SFX. function poke_note_step() -- Note: dststep is still 68, so this will advance the dst -- pointer to the next SFX. -- Add word per-channel value to byte sequence pitch. poke2_step(%c+@a) -- Advance to the next per-channel word. c+=2 end ::mainloop:: -- Flip print(&quot;โถ1&quot;) -- Keep 8 notes ahead of the current music playback row streamed -- to SFX 0-3. (If doing dynamic gameplay sounds reduce this to 2) -- Note: I tried instead setting up 16 SFX to contain the entire song -- but that used more chars than the streaming version. Streaming -- would also allow dynamic music - with a few more chars we could -- have the channels fade in one at a time like the C64 version of -- Cauldron... while sndrow-8&amp;31 != stat(20) do -- Setup dst to channel 0's (i.e. SFX 0's) current streaming row. dst=0x3200 + sndrow%32*2 -- Address of sequence A's streaming pitch a=sndrow%128 + snddata + 8 -- Address of channel 0's per-channel values c=snddata -- Channel 0 and 1 share sequence A's pitches poke_note_step() poke_note_step() a+=128 -- Channel 2 and 3 share sequence B's pitches poke_note_step() poke_note_step() sndrow+=1 end -- Render the pumpkin, pixel by pixel a=t()*.1 c=cos(a) s=sin(a) zoom=s+2 -- For each y coord relative to the middle of the screen for y=-64,64 do -- Exploit: accelerate this slow loop! all(star) -- For each x coord relative to the middle of the screen for x=-64,64 do -- Rotzoom screen x,y to get spritesheet coords u,v u=64+(c*x-s*y)*zoom &amp; 127 v=64+(s*x+c*y)*zoom &amp; 127 -- Animate the pumpkin's jaw up and down jawv=v-73 if jawv&gt;0 then v-=jawv*sin(a*16)/2 end -- Get spritesheet pixel and separate into base color value 'b' -- and dither flag 'd' b=sget(u,v) d=b\8 b%=8 -- Recolor the stalk from shades of orange to shades of green. -- Note: The stalk actually should start at v=30, but leaving that -- row orange fakes a little perspective on top of the center slice -- of the pumpkin face! -- Note: This is also where that unwanted default 'x' in the -- spritesheet gets recolored to color 14, which we set to black in -- the palette to hide it. It's drawn every frame! if (b&gt;0 and v&lt;30) then b+=7 end -- Plot the pixel on the screen. Note the second color for the dither -- pattern goes into the high nibble. pset(64+x,64+y,b+(b+d)*16) end end goto mainloop</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> Wed, 29 Sep 2021 02:15:10 UTC copy memory for 0% CPU cost <p>In 0.2.2c</p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>poke4(dst,peek4(src,len))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>takes 0% CPU. e.g. </p> <div> <div class=scrollable_with_touch style="width:100%; max-width:800px; overflow:auto; margin-bottom:12px"> <table style="width:100%" cellspacing=0 cellpadding=0> <tr><td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> <td background=/gfx/code_bg0.png> <div style="font-family : courier; color: #000000; display:absolute; padding-left:10px; padding-top:4px; padding-bottom:4px; "> <pre>poke4(0x6000,peek4(0x0000,2048))</pre></div></td> <td background=/gfx/code_bg1.png width=16><div style="width:16px;display:block"></div></td> </tr></table></div></div> <p>to copy spritesheet to screen for free.</p> <p>Can we rely on this behavior? (please)</p> Thu, 15 Apr 2021 07:18:49 UTC Emulated Amstrad CPC Chiptunes <p> <table><tr><td> <a href="/bbs/?pid=90136#p"> <img src="/bbs/thumbs/pico8_amstradchips1-1.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=90136#p"> amstradchips1</a><br><br> by <a href="/bbs/?uid=23375"> carlc27843</a> <br><br><br> <a href="/bbs/?pid=90136#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Hooked on Amstrad Chiptunes - Volume 1 - Dave Rogers</h2> <p>Experience the glory of some of the most revered 80's CPC/ZX chiptunes from the comfort of your PICO-8 console!</p> <ul> <li>Netherworld</li> <li>Zynaps</li> <li>Uridium</li> <li>Cybernoid</li> <li>Cybernoid 2: The Revenge</li> <li>Nebulus</li> <li>Marauder</li> <li>Stormlord</li> <li>Stormlord 2: Deliverance</li> <li>Anarchy</li> <li>Battle Valley</li> <li>Herobotix</li> <li>Turbo Boat Simulator</li> <li>Bear-A-Grudge</li> </ul> <h3>Controls</h3> <p><strong>right arrow</strong> - next song<br /> <strong>left arrow</strong> - previous song</p> <h3>Tech</h3> <p>This cart emulates the Amstrad CPC/ZX Spectrum AY-3-8910 audio chip to output chiptunes to PICO-8's 5512Hz 8-bit mono PCM serial buffer. To feed the emulation it contains a sound driver capable of generating AY register inputs across three channels of tone/volume/noise-enable as well as a single shared noise tone.</p> <p>The sound driver was derived from 13 different CPC games and one ZX game. All these games had their music composed or converted/arranged by (the legendary, in my opinion) J Dave Rogers, who also wrote the sound driver code for the games as was customary for chiptune musicians in the 80's. This common lineage made it reasonable to reverse engineer the Z80 code and generalize the driver to handle all the games' music.</p> <p>I think it sounds a little better in native PICO-8 rather than on the web player. Understand however that in the CPC the AY chip was clocked at 1,000,000Hz (and 1,773,447Hz on the ZX), and emulators typically default to sample at 44,100Hz. Whereas PICO-8 ticks at 60Hz and the serial buffer samples at 5512Hz. Only so much can be done - the percussion/noise is especially vulnerable to low sampling rates.</p> <p>The AY emulation code is augmented to support the waveform visualization by tracking zero-crossings - this extra work can be removed if the goal is pure audio.</p> <p>Without the graphics, the sound driver + AY emulator runs at a consistent 24-25% of frametime on a 60hz cart. This demonstrates that, for the appropriate PICO-8 game, it's reasonable to completely emulate the music and sfx via the serial buffer. Alternatively the sound driver portion could be used to drive PICO-8's sfx/music buffers in real time which would allow a lot more music in the cart with full audio sample rate quality. Or do both and synchronize native sfx/music with generated serial buffer output.</p> <p>The game logos were compressed with <a href="">PX9</a></p> <h3>Edit 4/10/2021</h3> <p>Fixed credits for Herobotix and Battle Valley, and fixed percussion on initial part of Zynaps - thanks to feedback from Dave Rogers!</p> <h3>Edit 4/19/2021</h3> <p>Now on <a href=""></a> using a suitably retro CRT effect!</p> Wed, 07 Apr 2021 18:11:32 UTC Impossible Mission R.T. <p> <table><tr><td> <a href="/bbs/?pid=88937#p"> <img src="/bbs/thumbs/pico8_impossible-2.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=88937#p"> Impossible Mission R.T.</a><br><br> by <a href="/bbs/?uid=23375"> carlc27843</a> <br><br><br> <a href="/bbs/?pid=88937#p"> [Click to Play]</a> </td></tr></table> </p> <h2>Impossible Mission R.T.</h2> <p>Discovered #pico8's secret 5512Hz 8-bit digital audio out API. </p> <p>Created a homage to this legendary 80's masterpiece to celebrate. </p> <p>Thanks zep!</p> Sat, 13 Mar 2021 22:14:40 UTC SFX instrument retrigger behavior inverts at index 0 <p>The pico8 manual states this about SFX instruments:</p> <p><em>&quot;SFX instruments are only retriggered when the pitch changes, or the previous note<br /> has zero volume. This is useful for instruments that change more slowly over time.<br /> For example: a bell that gradually fades out. To invert this behaviour, effect 3<br /> (normally 'drop') can be used when triggering the note. All other effect values have<br /> their usual meaning when triggering SFX instruments.&quot;</em></p> <p>However it seems that adjacent notes (with the same SFX instrument and with the same pitch and non-zero volume) do cause the instrument to be retriggered when the second note is at <strong>index zero in a looping sequence</strong>. Also, setting the second note's effect to 3 inverts the behavior and the instrument is continued rather than retriggered, but only when the second note is at index zero.</p> <p>The attached .p8 cart demonstrates this. It has these SFXs:<br /> 0: the SFX instrument<br /> 1: pattern looping from 0 to 8, with <strong>no effect</strong> on the note at index 0. The SFX instrument <strong> <em>is</em> retriggered</strong> at index 0.<br /> 2: pattern looping from 0 to 8, with effect 3 on the note at index 0. The SFX instrument is not retriggered at index 0.<br /> 3: pattern looping from 1 to 9, with <strong>no effect</strong> on the note at index 1. The SFX instrument is <strong>not retriggered</strong> at index 1.<br /> 4: pattern looping from 1 to 9, with effect 3 on the note at index 1. The SFX instrument is retriggered at index 1.<br /> 5: pattern looping from 24 to 32, with <strong>no effect</strong> on the note at index 24. The SFX instrument is <strong>not retriggered</strong> at index 24.</p> <p>You have to download the cart and play SFX 1..5 in the SFX editor. It does nothing on the web:<br /> <table><tr><td> <a href="/bbs/?pid=75534#p"> <img src="/bbs/thumbs/pico8_huyiwodimu-0.png" style="height:256px"></a> </td><td width=10></td><td valign=top> <a href="/bbs/?pid=75534#p"> SFX instrument bug demo</a><br><br> by <a href="/bbs/?uid=23375"> carlc27843</a> <br><br><br> <a href="/bbs/?pid=75534#p"> [Click to Play]</a> </td></tr></table> </p> <p>This behavior reproduced in version 8_0.2.0d and 8_0.1.12c. I only tried those versions.</p> Tue, 28 Apr 2020 04:31:43 UTC