From 70646185cd024973d224b3d264ef2816b98342c0 Mon Sep 17 00:00:00 2001 From: nycki Date: Thu, 17 Jul 2025 20:22:18 -0700 Subject: [PATCH] qrplay v3 --- content/qrplay/index.md | 183 ++++++++++++++++++++++ static/qrplay/{index.html => index2.html} | 0 static/qrplay/qrplay-v3-sunshine.png | Bin 0 -> 2617 bytes 3 files changed, 183 insertions(+) create mode 100644 content/qrplay/index.md rename static/qrplay/{index.html => index2.html} (100%) create mode 100644 static/qrplay/qrplay-v3-sunshine.png diff --git a/content/qrplay/index.md b/content/qrplay/index.md new file mode 100644 index 0000000..d8846b5 --- /dev/null +++ b/content/qrplay/index.md @@ -0,0 +1,183 @@ +--- +layout: base.njk +title: qrplay +permalink: /qrplay/ +--- +
+

QRPLAY V3

+ +

+

+ +--- + +A music box that fits in a QR code. [Source on Codeberg](https://codeberg.org/nupanick/qrplay). + + + +## scanning a code +- scan with your phone's photo app or barcode app and *copy* all the text. +- in an empty browser tab, *paste* the text into the URL bar. +- press play! + +## making music + +### notes and commands + +- `CDEFGAB` are musical notes. you can make a note sharp with `/` or flat with `:`. for example, C sharp is `C/` and A flat is `A:`. +- `X` rests for the current note length. +- `+-` changes the octave up or down. The default is the 4th octave, the one which contains middle C. +- `WHQISTJ` changes the rhythm of the following notes. The note lengths are Whole, Half, Quarter, eIghth, Sixteenth, Thirtysecond, and sixtyfourth (J). the default is T. +- `3` divides the current note length into triplets. for instance `Q3ABC` would play three notes that add up to one quarter note. +- `.` increases the current note length by 50%, so `H.A` would play a note that lasts for three quarters. +- `Vnn` sets the current volume to a number between 0 and 99. the default is `V30`. `V35` is twice as loud, and `V40` is twice as loud as that. +- `Unnn` sets the tempo in beats per minute. the default is `U137`, for [historic reasons](https://nicole.express/2025/zoo-of-zero-motivation.html). +- `R` resets the pitch and note duration back to the defaults. + +### samples + +`$𝑥...*` saves a sample in slot 𝑥. For instance, `$0RSDDI+D-A*` would save the Megalovania riff into slot 0. you then play the sample like a note, with `0`. + +the characters `012456789YZ` and space are QR-friendly and are reserved for storing samples. If you run out, you can also use ``[\]^_`{|}~``, newline, lowercase letters, or any UTF16 code unit. +note that `3` is missing as it is already used for triplets. + +special case: if a sample ends with the letter `V`, it will play asynchronously with the main track. for instance, you could play a chord like this: `$0CV*0 G`. +This would play the C from the sample and the G from the main track at the same time. + +samples keep separate octave and note length from the main track, but setting volume or tempo within a sample will change it globally when played. + +samples can save and play samples, including themselves. if a sample plays itself it will loop. +to prevent browser crashes, recursion and loops are capped at 100 iterations, and the limit decreases each time it is reached. +if a sample saves into its own slot, you won't be able to play it again. + +if a sample uses an incompatible character, then it cannot be played. you can use this to write comments, like `$:TITLE: MY COOL SONG*`. +you can nest samples/other comments in your comment, but you can't use `$` `*` as ordinary text. +you can also write lowercase letters as comments if no samples are written for them, such as at the top, before your song. + +## making a QR code + +- make a copy of *qrplay.html*. +- remove all comments, leading whitespace, and trailing whitespace. +- remove all newlines. +- add `DATA:TEXT/HTML,` at the start, including the comma. +- paste your song at the end. +- replace all spaces with `%20`, `#` with `%23`, newlines with `%0A`, and `%` with `%25` if it is followed by two numbers. +- use a QR generator like [Zint](https://sourceforge.net/projects/zint/) to convert your code. + +Your code will compress the best if it only uses the QR Alphanumeric subset. This means `0-9A-Z $%*+-./:`. There are no newlines or lowercase letters. + +Zint allows you to mix data of different types, so you don't have to write your *entire* song this way, but it will save space if you do. + +on a linux machine, with `qrencode` installed, you can use `make SONG=` to do this automatically, or use `make SONG=` to make a QR code with no song. + +## conversion + +qrplay is based on [ZZM](https://museumofzzt.com/file/browse/detail/zzm-audio/), which itself based on the [ZZT Music Format](https://chriskallen.com/zzt/zztoop.php#sound). +To convert from ZZT Ultra or ZZM to qrplay: + +- uppercase all notes. `abcdefg` becomes `ABCDEFG`. +- if converting from ZZM (not ZZT Ultra), add `R` to the start of every line. +- replace sharps (`#`) with `/`, flats (`!`) with `:`, and resets (`@`) with `R`. +- collect `Z` channels together by their number (so `Z01 A Z02 B Z01 C Z03 D` becomes `Z01 AC Z02 B Z03 D`) +- if the song uses any drums `012456789`, add these approximations at the beginning of the file. note that all of them will overwrite slot Z when played. + ``` + $0$ZRJ33+++GV*ZX* + $1$ZRJ33+B+C#DEFF#GV*ZX* + $2$ZRJ33++G++DDB--G++DDBV*ZX* + $4$ZRJ33+++EA-G--B++G++C#V*ZX* + $5$ZRJ33++A#GA#G-A#+G-B+GV*ZX* + $6$ZRJ33+A++C#-AAE+E--AV*ZX* + $7$ZRJ33+FFED#DDC#V*ZX* + $8$ZRJ33++DDDDDD#EV*ZX* + $9$ZRJ33BA#BB-A+A#-BV*ZX* + ``` + (at the default rate of 137bpm, J33 is 3.8ms, a little longer than the millisecond "clicks" used by ZZT. + the approximations here are based on source code from [The Reconstruction of ZZT](https://github.com/asiekierka/reconstruction-of-zzt/blob/master/SRC/SOUNDS.PAS#L105).) +- replace `Z` channels with immediately played async samples, optionally excluding the last one. for instance, `Z01 AC Z02 B Z03 D` becomes `$ZACV*Z $ZBV*Z D`. + +to choose a slot for immediately played async samples: +- if any drum (or Y or Z) isn't used, you can use that slot. +- if all drums are used, you can use a lowercase letter like `z`. +- if all drums are used but you can't use a lowercase letter (alphanumeric QR code), you can remove all spaces from the file and use space (`$ ACV* $ BV* D`). +- if all drums are used but you can't use a lowercase letter or space (alphanumeric QR code with formatting or escaped spaces), + you can use 0 and move the 0 drum sample to the start of each channel sample. + +There is no equivalent of some ZZT ultra commands. You should remove them to avoid confusion. +- P priority override. alternative: remove the notes that you don't want to play. +- O octave override. alternative: use `R` to reset to octave 4 and `-+` to choose an octave. +- K echo mode. alternative: play the same sample twice with a slight delay and a volume change. +- % pitch sliding. alternatives: none. you will have to play staccato instead. + +## thanks + +- SArpnt for their hard work and helpful feedback to make the code as small and QR-friendly as possible, even as I kept trying to cram in "one more feature". +- asie for the [Reconstruction of ZZT](https://github.com/asiekierka/reconstruction-of-zzt/blob/master/SRC/SOUNDS.PAS), which served as the blueprint for this project. +- Chris Allen for the [ZZT Ultra Hall of Music](https://chriskallen.com/zzt/hallofmusic.html), for documenting and showcasing the ZZM format and several awesome songs. +- Nicole Express for this [blog post](https://nicole.express/2025/zoo-of-zero-motivation.html) that helped me understand the timings. +- Thonky for the [QR Alphanumeric Mode](https://www.thonky.com/qr-code-tutorial/alphanumeric-mode-encoding) reference. +- the great people at Mozilla for documenting how to generate sound in a web browser, particularly the page about the [OscillatorNode](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode). +- there's a good chance this would not exist if I hadn't been exposed to [Jesse's Bookmarklets](https://www.squarefree.com/bookmarklets/) back in the 2000s. Thanks, Jesse! diff --git a/static/qrplay/index.html b/static/qrplay/index2.html similarity index 100% rename from static/qrplay/index.html rename to static/qrplay/index2.html diff --git a/static/qrplay/qrplay-v3-sunshine.png b/static/qrplay/qrplay-v3-sunshine.png new file mode 100644 index 0000000000000000000000000000000000000000..34b6dcaf7c534483115791b1814e962eae91096a GIT binary patch literal 2617 zcmV-93dZ$`P)rfeGL#b;dN1m;hu1hTdZQ`DfW` zbyu&S>NfwUfA$~pUxnPvdavJyS#`ebw07zJv~KB>S!>VK?CCUXf{?qPeOmWpDGvW0 zb?@s|@s7RI`LLw`-<`dOAyfS_AHf{;JNZ7>4=Vw`+PXN_sk7&CzN$dvrz*e4dUfZc zU9XA<<6J%+k8^;@vq|lx|kQOSGDfR$GHwf`uVZtRh9dEGPYM&?rz1g ze2meSV_M$F->doSNb2)F*Z=?VS0(>Cl0S;`RWTK8jb@)4#`$P@$287)o$T*j{OicA zvck*J%RL!*vvo6`ucNbGd{H~4pQDib!N!CW7C7P4HAA=h;d(ev7VFB5$IT1@NcZJ= zEd9)5Wn)@PL9SP84{J7CzkHtHs3_#_%}}l1R8H&>p3LjkmS5LS>F08wbd6t#Lb~>5 zGbS+Q(6UB#KQ}{LiYGPf`t6#%C?xxCh5#wW>f%Qcejn+n8SnBqfSturKUl1m@0L^L-*gr;0^w0eZ4Jpm zKSPE9?9&N6RFcBpc>($q|tv^4eesG$gEY3LfPpq|JQGt?#u+13c!dei@nqNo=ewe2}2TXu%MWqYE`W&Y^pLzL#QyxYdnZWO%QUc zr2bXXUI)oUGLVQ)6+4|Pv3R{T-K}gu|iQ=bP z)(i7**f1oLI6#H)CLdu-njqxX$!C%%b7zTst#Z}IWWZIqtJSvp9EV(YT&sVGB)G}33$jc;uJs?K($d&l+q#uEtaaoiihSPd-mh(_o$LFQsffbZm zc@Xl_62OOWQSN9_uF_(PRxdwx8sAD$NVwNj|LZ74fE(GK42)sdN^l<&1qWj|Rv0O=^c^HJWONzrm@)n{{Y zV`dEUmqj5jrR+u~-|t=%fp;miE;>jWrAp^dLWw|jIBoSe7~9q^eU>M@hwCMki3!1u zHbF?~pjn?7s=Ye(7$>DIyulqp$zMOcwFo48^GkWSx;h8}J`nf&!jP-yvwkI%{wHt# zKGMa0IyFH0CB(Q9)Ss7!Qm6X-otke!NV%*I>45}fIn?Zu{b+_3u27Tnd!R}lgrxk4 zd(4=*8$ z${Ozj0m^49C?nHl3S~h^w0>&kwJsggQTFf!pmNSznM1AYqu#!pE{ zZH-wWm*D@fx(GwMjx6E-71MRe>JtP?*>Ba1%%Q8-8il;1xo3_ZYNWr^J;wFH5-gV5 z>93C8X@ZbCLqT?t<2j!f9O!R(XKk6!mT~^&{C^)wL7H{}g#iPUx|3k)sDI@@N&UIe zk)n{7ddO%!x_@G~t_oSaj}diNKFAsV=_>{x36-zh$=|BnL{;3&|0lg1x+S+*n;_&a z+Cg^l0EGJdk@|aDBg%46=xFv7hMX;*(aV4KjtR^+BBW4v-R9F2P_6Jkwdd4Y|)M1tfAqgE&ljv7g6FF8#3ke?7 zRf8fhgzIEs$fM5Zl1QG;h~N3tK&OXT;?6#uHprcJBarzjf{(eI#}tiJOH$^79zX5* z)NPIPeRuW(kS_7He%3vZXFf1erx9Jyg#pda$ouY5$SghN*-3tk)gkPdJTOEdD|min z#^R80a{bV*)FHJiWl*a!WBshA6Xbl~MvFkIztk#gV|_pEqAQ3P5}jSDFm}0Liq?aW z_>KNxZ&C~}GciYZ|44sRrAD7@Fu5RwAuC;p(LDjVTwEXF3o1}=lQ_m)QKEO&jzH>; z1WPlYvUvnd$XRBXg3%Ew3sfBEPcjWa5-OFs(0z5%mUmKz0cK{3wrq!BD(j+<@De^G z2Wogpy0Gi!A>M#5=F=rY-`qqY6Sf}$6#qY&mP73h-9pqU9-L6?67tV{{yx&x!__;ad&;>~KSV-Ub8e|w!m(X&Bd~_F%p;8`LZno`hjL)An ziaso(&r!&K`u895 b--P@h2I&}mQOvk}00000NkvXXu0mjf8e$Gu literal 0 HcmV?d00001