parent
bb8793316d
commit
70646185cd
3 changed files with 183 additions and 0 deletions
183
content/qrplay/index.md
Normal file
183
content/qrplay/index.md
Normal file
|
@ -0,0 +1,183 @@
|
|||
---
|
||||
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 XABGXXXX
|
||||
|
||||
</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!
|
BIN
static/qrplay/qrplay-v3-sunshine.png
Normal file
BIN
static/qrplay/qrplay-v3-sunshine.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Loading…
Add table
Reference in a new issue