artc language
...seeks to be a moderately good option for expressing some forms of art concisely and precisely.
What is it?
It's a function, which deterministically evaluates/renders...
...from: to any one of these formats:
┌───────────────┐ ⎧ .usdz files of 3D objects / scenes / animations,⎫
│ │ ⎪ ⎪
│ .artc code │ --> ⎨ .json files of 2D Canvas drawing operations, ⎬
│ │ ⎪ ⎪
│ (Python fork) │ ⎩ .mid files of MIDI ⎭
└───────────────┘
⎧ language for, ⎫ ⎧ OpenUSD, ⎫
⎪ ⎪ ⎪ ⎪
It's a ⎨ library for, ⎬ ── ⎨ CanvasRenderingContext2D, ⎬
⎪ ⎪ ⎪ ⎪
⎪ intro to, ⎪ ⎪ MIDI, ⎪
⎪ ⎪ ⎪ ⎪
⎩ celebration of ⎭ ⎩ some basic physics ⎭
It does NOT involve generative AI or seek to replace or automate artistic choices or subtleties.What's OpenUSD?
What's CanvasRenderingContext2D?
What's MIDI?
Why is it?
It's for:
- art
- diagrams
- { learning / teaching / exploring } stuff
It doesn't make new stuff possible,
but it tries to make some forms of already-possible stuff somewhat easier.
Binary encoding
.artc code <-------> binary-encoded
(as text) form of .artc code
Questions
What works of art are we capable of expressing in at most 100 bytes of [additional] information?
If there's an information-storage bottleneck someday, how much art could losslessly survive?
Design goals
- Have the encoded hex be (somewhat) readable:
- Operate on syntax trees, not text
- Note: this will normalize / autoformat your code
- Note: there's a
.Gap
AST node for expressing additional blank lines
- Optimize for names and patterns specific to this language and standard library
(vs plain text or Python in general). - You should be able to simply (albeit tediously) decode it yourself, with pencil and paper
Examples
Example 1
B26B2
is a 2.5-byte encoding of the following:
scene.aspect_ratio = "√2:1"
scene.background = Gray(25%)
scene.padding = 6%
Note: this is a common initial pattern for concise 2D works ("set aspect ratio, background, and a padding percentage beyond the bounding box of whatever shapes end up in the scene graph"), so the encoding optimizes for it.Full walk-through of this example
Background:
- Current state: there's a finite set of states, each of which has its own Huffman tree which maps input-nibble prefix code sequences to action bundles. Some states (like
.end_sequence_b2_init
) map the empty string to one specific action bundle and do nothing else. - Input chunk: a full prefix code of one or more nibbles, which take us all the way from the root of this state's Huffman tree to one of its leaves
- Action bundle: a named list of actions
- State stack: like a to-do list, defining upcoming states. See also: Pushdown automaton
- Node stack: a temporary stack of AST nodes, to be used soon in upcoming action bundles
- Code: the AST that's been built so far. Its root is a
.Module
node.
Current state | Input chunk | Action bundle for this (state, input) |
---|
.initial Possible input
B → .use_draft_b | B (for "Draft B") | .use_draft_b :
Result
- State stack:
[.b_initial] - Node stack:
[]
|
.b_initial Possible input
2 → 2D a;b;p
3 → 3D main else : (reserved) | 2 (for "2D with aspect / background / padding") | .start_sequence_b2_init :
- Set state stack to
[.end_sequence_b2_init] - Push state
.get_aspect_ratio - Push state
.get_background_color - Push state
.get_padding_percent
Result
- State stack:
[.end_sequence_b2_init, .get_aspect_ratio, .get_background_color, .get_padding_percent] - Node stack:
[]
|
.get_padding_percent Possible input
0 → 0
1 → 1%
2 → 2%
3 → 3%
4 → 4%
5 → 5%
6 → 6%
7 → 7%
8 → 8%
9 → 9%
A → 10%
B → 15%
C → 20%
D* , E* , and F* are reserved. At least one of them will lead to additional nibbles. | 6 | .make_padding_percent with value=6 :
- Make temp node
6% (an AST node of type .Constant and subtype .ConstantQuantityPercent )
Result
- State stack:
[.end_sequence_b2_init, .get_aspect_ratio, .get_background_color] - Node stack:
[6%]
|
.get_background_color Possible input
0 → Gray(0)
1 → Gray(10%)
2 → Gray(20%)
3 → Gray(30%)
4 → Gray(40%)
5 → Gray(50%)
6 → Gray(60%)
7 → Gray(70%)
8 → Gray(80%)
9 → Gray(90%)
A → Gray(1)
B → Gray(25%)
C → Gray(75%)
D* , E* , and F* are reserved. At least one of them will lead to additional nibbles and the ability to express RGB or HSL colors. | B | .make_gray_percent with value=25 :
- Make temp node
Gray(25%) (node type .Call , containing a .Constant )
Result
- State stack:
[.end_sequence_b2_init, .get_aspect_ratio] - Node stack:
[6%, Gray(25%)]
|
.get_aspect_ratio Possible input
0 → "16:9"
1 → '"3:2"'
2 → '"√2:1"'
4 → '"4:3"'
5*, 6* : (reserved)
7 → "1:1" 8*, 9*, A*, B* : (reserved)
C → "3:4"
D → "1:√2"
E → "2:3"
F → "9:16" | 2 | .make_string with value = (index of "√2:1" in the list of string constants):
- Make temp node
"√2:1" (a .Constant of subtype .ConstantStr )
Result
- State stack:
[.end_sequence_b2_init] - Node stack:
[ 6%, Gray(25%), "√2:1" ]
|
.end_sequence_b2_init Possible input
- None. This state doesn't read any input.
| | .end_sequence_b2_init :
- Append node
scene.aspect_ratio = 🍿 (an .Assign node, where 🍿 is whatever gets popped from the node stack) - Append node
scene.background = 🍿 - Append node
scene.padding = 🍿 - Set state to
.b2_main
Result
- State stack:
[.b2_main] - Node stack:
[] - Code:
scene.aspect_ratio = "√2:1"
scene.background = Gray(25%)
scene.padding = 6%
|