nycki.net/content/QRPLAY/index.md
nycki 77344b4a71
All checks were successful
/ build (push) Successful in 56s
qrplay -> QRPLAY
2025-07-22 21:11:13 -07:00

183 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
layout: base.njk
title: QRPLAY
permalink: /QRPLAY/
---
<CENTER>
<H2>QRPLAY V3</H2>
<BUTTON ONCLICK='
// variables
// A: Audio context
// C: Command Character from P
// D: temporary Data
// E: peek and check Equal
// F: main Function and object to store samples
// G: Gain node
// H: textarea Html element
// I: Index into M
// L: note Length
// M: Music text
// N: semitone Note offset for octave changes
// O: Oscillator node
// P: Pop M
// Q: numeric command id
// R: Recursion counter
// S: set Sample
// T: note Time
// U: bpm
// V: sample start time
H.A?.close(),
H.A=A=new AudioContext,
F=(
M=``,L,N,V=T,
E=D=>M[I]==D,
I=0,P=_=>C=M[I++],
S=F=>(D+=E`$`-E`*`)?S(F+P()):F
)=>{for(
;
P();
Q>7?
(Q-=9)? // DECREASE Q!
Q>9?L/=+C||2/3: // divide note length
Q>7?
(D=P()+P())?
Q>8?U=D+P(): // set bpm
G=new GainNode(A,{gain:2**(D/5-11)}): // set volume
T=V:
L=
~Q?1<<Q: // set note duration
N=4: // reset
F[P(D=1)]=S``: // set sample
~Q?( // play note or rest
O=new OscillatorNode(A,{
type:`square`,
detune:100*(Q*2-(Q>3)+E`/`-E`:`-N-7)
}),
Q&&O.connect(G,G.connect(A.destination)),
O.start(T),
O.stop(T+=L/U*15/8)
):
C-3|C>`X`?
R++<99&&F(F[C],L,N): // play sample
N-=C+12|0 // change octave or nop
)Q=`XCDEFGABR$JTSIQHWVU3.`.indexOf(C);R--},
F(`RU137V30`+H.value,T=R=0)
'>PLAY</BUTTON> <BUTTON ONCLICK=H.A.close()>STOP</BUTTON>
<P><TEXTAREA ID=H STYLE=WIDTH:MIN(40EM,99%);HEIGHT:9LH>
$:YOU ARE MY SUNSHINE*
RI DGABXBXX BABGXGXX
RI GAB+CXEXX EDC-BXXXX
RI GAB+CXEXX EDC-BXGXX
RI XGABXX+C-A XABGX-SGXGXGX
</TEXTAREA>
</CENTER>
---
A music box that fits in a QR code. [Source on Codeberg](https://codeberg.org/nupanick/qrplay).
<img src="/QRPLAY/qrplay-v3-sunshine.png">
## 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=<filepath>` 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!