This commit is contained in:
parent
0228bf70ac
commit
0a3a5393d0
1 changed files with 247 additions and 0 deletions
247
static/qrplay/v3a.html
Normal file
247
static/qrplay/v3a.html
Normal file
|
@ -0,0 +1,247 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>qrplay</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav><a href="/">home</a></nav>
|
||||
|
||||
<h2>qrplay by nycki and SArpnt</h2>
|
||||
<p>a QR-code sized implementation of ZZT Play, which you may know from the <a href="https://chriskallen.com/zzt/hallofmusic.html">ZZT Hall of Music</a>.</p>
|
||||
|
||||
<p>I designed this because I wanted a program I could print on a business card, and <a href="https://git.disroot.org/SArpnt">SArpnt</a> found some clever ways to shrink it even further. The <a href="https://git.hatspace.net/nycki/nycki.net/src/branch/main/static/qrplay/index.html">source</a> on this page is commented, the source in the QR code is minified.</p>
|
||||
|
||||
<p>paste ZZT music code in this box, then click the button to #play!</p>
|
||||
|
||||
<!-- snip here -->
|
||||
<meta name=viewport content=initial-scale=1>
|
||||
<center>
|
||||
<p><a href=http://nycki.net/qrplay>qrplay v2c</a></p>
|
||||
<textarea id=f style=width:min(40em,99%);height:9lh>; #title nupa's theme
|
||||
; #authors nycki bsp
|
||||
z00
|
||||
@qcceg hcqeg dcdc d#h.d
|
||||
@qcceg hcqeg dcd#d wc
|
||||
z01
|
||||
@-wc b! a a!
|
||||
@-wc b! haqa!g w-c
|
||||
z02
|
||||
@i 0000001x 0000002x 0000004x 0000005x
|
||||
@i 0000006x 0000007x 0000008x 0000009x</textarea><p>
|
||||
<button onclick='
|
||||
s=`@`+f.value.toLowerCase();
|
||||
f.A?.close();
|
||||
A=f.A=new AudioContext;
|
||||
// stack for parallel stuff
|
||||
S=[];
|
||||
// variables / drum samples
|
||||
V=[
|
||||
"+g",
|
||||
"-b+c#deff#g",
|
||||
"g++ddb--g++ddb",
|
||||
,
|
||||
"+ea-g--b++g++c#",
|
||||
"a#ga#g-a#+g-b+g",
|
||||
"-a++c#-aae+e--a",
|
||||
"-ffed#ddc#",
|
||||
"dddddd#e",
|
||||
"--ba#bb-a+a#-b"
|
||||
].map(x=>`{o06m${x}}x`);
|
||||
// tempo in beats per minute
|
||||
u=137;
|
||||
// volume. 40 is default, 35 is half, 30 is half again.
|
||||
v=40;
|
||||
for(
|
||||
c=t=k=Z=0;D=s[c+1],E=D+s[c+2],C=s[c++];
|
||||
// no match
|
||||
z<0?0:
|
||||
// comments
|
||||
z<2?k=z:k?0:
|
||||
// - + octave
|
||||
z<4?o+=C+1|0:
|
||||
// dots and triplets
|
||||
z<5?l*=3/2:
|
||||
z<6?l/=3:
|
||||
// note or rest
|
||||
z<14?(
|
||||
z-=6,
|
||||
O=new OscillatorNode(A,{
|
||||
type:`square`,
|
||||
detune:100*(z*2-(z>2)-(D==`!`)+(D==`#`)+12*o-9)
|
||||
}),
|
||||
G=new GainNode(A,{gain:z<7&&.5**(12-v/5)}),
|
||||
O.connect(G),
|
||||
G.connect(A.destination),
|
||||
O.start(t),
|
||||
O.stop(t+=7.5*l/u)
|
||||
):
|
||||
// store sample
|
||||
z<23&&D==`[`?(
|
||||
m=0,
|
||||
d=V.findIndex.call(s,D=>
|
||||
D==`[`?!++m:
|
||||
D==`]`?!--m:
|
||||
0
|
||||
),
|
||||
V[C]=s.slice(c+1,d),
|
||||
s=s.slice(d+1),
|
||||
c=0,
|
||||
console.log(V[C],s)
|
||||
):
|
||||
// play sample
|
||||
z<23?(s=V[C]+s.slice(c),c=0):
|
||||
// reset octave and duration
|
||||
z<24?(o=0,l=1):
|
||||
// set note length
|
||||
z<31?l=2**(z-25):
|
||||
// microtone
|
||||
z<32?l=u/2**11:
|
||||
// change tempo
|
||||
z<33?u=E+s[c+=2,c++]:
|
||||
// change volume
|
||||
z<34?(v=E,c+=2):
|
||||
// set octave
|
||||
z<35?(o=E-4,c+=2):
|
||||
// play in parallel
|
||||
z<36?S[Z++]={t,l,u,v,o}:
|
||||
z<37?{t,l,u,v,o}=S[--Z]:
|
||||
// no effect
|
||||
0
|
||||
)z=`\n;-+.3cdefgabx012456789@jtsiqhwmuvo{}`.indexOf(C)
|
||||
'>#play</button> <button onclick=f.A.close()>#stop</button>
|
||||
<!-- stop snipping here -->
|
||||
</center>
|
||||
|
||||
<script>
|
||||
// set default value for input
|
||||
f.value=(
|
||||
window.location.search.slice(1)
|
||||
.replaceAll('%20', ' ')
|
||||
.replaceAll('%0a','\n')
|
||||
.replaceAll('%0A','\n')
|
||||
.replaceAll('%23', '#')
|
||||
.replaceAll('%27', "'")
|
||||
|| f.value
|
||||
);
|
||||
|
||||
// remove redundant title
|
||||
document.querySelector('center p').setAttribute('hidden', 'true');
|
||||
|
||||
// add save button
|
||||
document.querySelector('center p:last-child').innerHTML+=`
|
||||
<button onclick='
|
||||
window.location.search = "?"+f.value
|
||||
.replaceAll(" ","%20")
|
||||
.replaceAll("\\n","%0a")
|
||||
.replaceAll("#","%23")
|
||||
.replaceAll("\\x27","%27")
|
||||
'>#save</button>
|
||||
`;
|
||||
</script>
|
||||
|
||||
<h2>qr codes</h2>
|
||||
|
||||
<p>for security reasons, your phone probably won't open these as links. you'll have to copy and paste the text into your browser by yourself. it requires no permissions or internet access to run.</p>
|
||||
|
||||
<details open>
|
||||
<summary>qrplay v2: added percussion, chords, volume control, sample song.</summary>
|
||||
<div style="text-align: center;">
|
||||
<p><strong>qrplay v2</strong></p>
|
||||
<img src="qrplay-v2-nupas-theme.png">
|
||||
<p><textarea>data:text/html,<meta%20name=viewport%20content=initial-scale=1><center><p><a%20href=http://nycki.net/qrplay>qrplay%20v2c</a></p><textarea%20id=f%20style=width:min(40em,99%);height:9lh>;%20%23title%20nupa's%20theme%0A;%20%23authors%20nycki%20bsp%0Az00%0A@qcceg%20hcqeg%20dcdc%20d%23h.d%0A@qcceg%20hcqeg%20dcd%23d%20wc%0Az01%0A@-wc%20b!%20a%20a!%0A@-wc%20b!%20haqa!g%20w-c</textarea><p><button%20onclick='s=`@`+f.value.toLowerCase();f.A?.close();A=f.A=new%20AudioContext;T=[];L=[];u=137;v=40;for(c=t=k=Z=0;D=s[c+1],E=D+s[c+2],C=s[c++];z<0?0:z<2?k=z:k?0:z<4?o+=C+1|0:z<5?l*=3/2:z<6?l/=3:z<14?(z-=6,O=new%20OscillatorNode(A,{type:`square`,detune:100*(z*2-(z>2)-(D==`!`)+(D==`%23`)+12*o-9)}),G=new%20GainNode(A,{gain:z<7&&.5**(12-v/5)}),O.connect(G),G.connect(A.destination),O.start(t),O.stop(t+=7.5*l/u)):z<23?(s=`z99o06${["+g","-b+c%23deff%23gg%23aa%23b+cc%23d","g++ddb--g++ddb--g++ddb--g++ddb","+ea-g--b++g++c%23----b++g+cd%23-g+cec","a%23ga%23g-a%23+g-b+g","-a++c%23-aae+e--a++c%23-aae+e--a++c%23","-fffeeed%23d%23dddc%23c%23c%23","dddddedc%23ded%23fee","--ba%23bb-a+a%23-baaa+a%23bb-a",][z-14]}z${Z}%20o${o+4}%20x`+s.slice(c),c=0,T[99]=t,L[99]=u/2**12):z<24?(o=0,l=1):z<31?l=2**(z-25):z<32?(T[Z]=t,L[Z]=l,t=T[Z=E|0]||0,l=L[Z]||l,c+=2):z<33?u=E+s[c+=2,c++]:z<34?(v=E,c+=2):(o=E-4,c+=2))z=`\n;-+.3cdefgabx012456789@jtsiqhwzuvo`.indexOf(C)'>%23play</button>%20<button%20onclick=f.A.close()>%23stop</button></textarea></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>qrplay v1: proof of concept. musical notes only.</summary>
|
||||
<div style="text-align: center;">
|
||||
<p><strong>qrplay v1</strong></p>
|
||||
<img src="qrplay-v1.png">
|
||||
<p><textarea>data:text/html,<meta%20name="viewport"%20content="width=device-width%20initial-scale=1.0"><textarea%20id=f></textarea><br><button%20onclick='/*qrplay%20v1,%20nycki%20&%20SArpnt,%202025*/v=f.value;T=[];Z=0;f.A=A=f.A||new%20AudioContext;B=new%20GainNode(A,{gain:0.4});B.connect(A.destination);for(i=o=k=t=0,l=1;c=v[i++],d=v[i],c;z<0?0:z>29?k=1:z>28?k=0:k?0:z>27?(T[Z]=t,Z=+v.slice(i,i+=2),t=T[Z]||0):z>17?t+=l:z<3?z?o+=g:(o=0,l=1/8):(console.log(z),z)&1?console.log(l=g):(a=new%20OscillatorNode(A,{type:`square`,detune:100*(g+o+(d==`%23`)-(d==`!`))}),a.connect(B),a.start(t*.4),t+=l,a.stop(t*.4)))g=[,-12,12,4,-9,2,-7,1,-5,.5,-4,.25,-2,1/8,0,l/3,2,l*1.5][z=`@-+wchdqeifsgta3b.x012456789z\npovukr\x27`.indexOf(c.toLowerCase())]'>%23play</button><button%20onclick='f.A.close();f.A=0'>%23stop</button></textarea></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>song: Solfeggettio in C Minor</summary>
|
||||
<div style="text-align: center;">
|
||||
<p><textarea>;; title Solfeggettio
|
||||
;; artist JS Bach
|
||||
@i-e!ce!g+ce!dc-bgb+dgfe!de!
|
||||
@ice!g+ce!dcdc-bagfe!d
|
||||
@ie!ce!g+ce!dc-bgb+dgfe!d
|
||||
@i+e!ce!g+ce!dcdc-bagfe!d
|
||||
@i+e!c-ge!c++c-ge!a!--fa!+cfa!+ce!
|
||||
@i+d-b!fd-b!++b!fdg--e!gb!+dgb!+d
|
||||
@i+ge!dc-ge!dh.c
|
||||
</textarea></p>
|
||||
<img src="qrplay-solfeggettio.png">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>song: We Will Rock You</summary>
|
||||
<div style="text-align: center;">
|
||||
<p><textarea>U137V40
|
||||
@s.9x9x6xxx9x9x6xxx9x9x6xxx9x9x6xxx
|
||||
@s.deedi.es.eei.des.de
|
||||
@s.edi.es.eeedi.eags.g-b+dx
|
||||
@s.ei.es.dexxdexexe
|
||||
@s.xxxdddxdd-bxage+eq.x
|
||||
@q.gf#eds.exex6xxx9x9x6xxx
|
||||
@q.gf#eds.exex6xxx9x9x6xxx
|
||||
@s.deedi.es.eei.des.de
|
||||
@s.edi.es.eeedi.eags.g-b+dx
|
||||
@s.ei.es.dexxdexexe
|
||||
@s.xxxdddxdd-bxage+eq.x
|
||||
@q.gf#eds.exex6xxx9x9x6xxx
|
||||
@q.gf#eds.exex6xxx9x9x6xxx
|
||||
@q.gf#eds.exex6xxx9x9x6xxx
|
||||
@q.gf#eds.exex6xxx9x9x6xxx
|
||||
</textarea></p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<h2>command reference</h2>
|
||||
|
||||
basic commands:
|
||||
|
||||
<ul>
|
||||
<li>cdefgab: play a note in the current octave.</li>
|
||||
<li>012456789: play one of various percussion effects. note that there is no effect 3.</li>
|
||||
<li>x: rest for the length of a note.</li>
|
||||
<li>-+ change octaves. default is the 4th octave, the one containing middle C.</li>
|
||||
<li>whqistj: change note length to whole, half, quarter, eigth, sixteenth, thirtytwoth, or sixtyfourth. default is t.</li>
|
||||
<li>3: cut the current note length into triplets. for instance, <em>q3ceg</em> would play three notes that are each 1/3 the length of a quarter note.</li>
|
||||
<li>.: dotted note. increases the current note length by 50%. for instance, <em>q.c</em> would play a note that lasts 1.5 times a quarter note.</li>
|
||||
<li>@: reset octave and note length to defaults.</li>
|
||||
</ul>
|
||||
|
||||
extended commands: this stuff wasn't in ZZT!
|
||||
<ul>
|
||||
<li>;: comment. everything from a semicolon until the end of the line is ignored. you can put the name of your song in here!</li>
|
||||
<li>znn: select track. you can use this to play two or more tracks at once! so for instance, <em>z00c z01+c</em> would play an octave chord. tracks greater than 15 are reserved for internal use.</li>
|
||||
<li>unnn: set tempo in beats per minute. for historic reasons, u137 is the default.</li>
|
||||
<li>vnn: set volume, from 00 to 99. this scales exponentially: 40 is the default, then 45 is double that, then 50 is double that, etc.</li>
|
||||
<li>onn: set octave, from -9 to 99, but you should really only use 00 thru 08. default is 04.</li>
|
||||
</ul>
|
||||
|
||||
not implemented:
|
||||
<ul>
|
||||
<li>loops! I'm thinking of implementing something brainfuck-esque so you can make turing machine music... we'll see.</li>
|
||||
<li>there's a known bug where you can't play two percussion effects on different channels at the same time. but like, if I hadn't told you, I don't think you would have noticed.</li>
|
||||
<li>change tuning! currently this is tuned to A440 but ZZT is actually slightly out of tune and it would be cool to replicate that.</li>
|
||||
<li>slides and bends. ZZT Ultra has these and I could probably figure them out but I'd have to rework it to fit in the qr code.</li>
|
||||
<li>compression: maybe I could rewrite this whole thing to use uppercase letters only so it fits in the QR alphanumeric mode? I'd have to escape every punctuation mark though. might experiment with this later.</li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue