ʜ⊥ᴚoℲ (and other Teletype ideas)

In my head the programming language used on the Teletype has always been a bit Lisp-ish, but without the brackets, after all it does use Polish (a.k.a. prefix) notation.

Anyway, a few weeks back I came across an AMA on Reddit with Olivier Gillet (pichenettes) from Mutable Instruments. It had this (rather sweet) quote in it:

Thank you for taking time to do this. Are there any modules from other makers where you have thought “fuck, why didn’t I come up with that”?

pichenettes
A Monome Teletype running Forth.

(source)

I don’t know much at all about Forth, I have discovered that it is a stack based programming language using reverse Polish notation (a.k.a. RPN, a.k.a. postfix). This chimes with what I know about how the code works internally, even though we enter code in prefix notation, it’s actually run as if it’s in postfix notation. So in fact in turns out that the Teletype is backwards Forth, hence my appalling abuse of Unicode for the post title (it was either that or some pun based on a Shakespearian insult).

(as an aside I’ve also discovered RPL which is a programming language for HP calculators and appears to be a mash up of Forth and Lisp.)

Now I’m all at sea when it comes to RPN, I never had a HP48/49 calculator at school. So even trying to read Forth code makes my head fuzz a little. With that said I am looking for inspiration for ideas to enhance the Teletype programming language, and maybe learning a bit of Forth might be the way.

(another aside, I now have an unwanted desire to buy an HP50g and use it’s USB port to send messages to Ansible to generate CV.)

The first major change I’d like to introduce is the idea of multiple statements per line via a ; separator. E.g.

X 1 ; Y 2 ; Z 3 ;

would be equivalent to:

X 1
Y 2
Z 3

This would be particularly useful in I scripts, and would serve as a (complementary) alternative to the timelines functionality proposed. Technically it should be much much easier to implement compared to timelines.

Naturally there are a few hiccups…

L 0 10 : PN 0 I I ; PN 1 I MUL 2 I

This could be interpreted 2 ways, i.e. which separator has more significance. Demonstrated with parentheses below

L 0 10 : (PN 0 I I ; PN 1 I MUL 2 I)   # option 1
(L 0 10 : PN 0 I I ); PN 1 I MUL 2 I   # option 2

I favour option 1.

Next:

X PARAM ; IF LT 100 X : TR.PULSE 0

Should we allow this? It’s only 1 pre statement…

Thoughts? Can anyone spot any other edge cases? (Does anyone have an HP RPN graphing calculator to send me and derail my plans for 2017.)

7 Likes

yes!

same.

confused by this one as the syntax of IF is

IF (condition) : (statement)

Having the language on TT use actual RPN would be fun but I think a lot of people would have a hard time getting used to it :slight_smile: on the plus side you wouldn’t need to worry about operator precedence any more.

1 Like

Ah, you’re right, the example should be

X PARAM ; IF LT 100 X : TR.PULSE 0

(I’ll edit the original too)

Heh, though it is using Polish notation, just not the reverse version (we have a lot more memory in our tiny MCU than they had in the 60s). So there aren’t any precedence issues.

It actually wouldn’t be that hard to switch to using RPN if someone wanted to create an alt version of the firmware. I think you’d only need to edit a few functions, and they’d be quite minor edits too…

forth seems super-interesting for embedded because of the advanced metaprogramming (allegedly) & absence of garbage collection. It occured to me whether we could somehow write blackfin programs for the aleph in forth, gluing together high-level words with a tonne of state such as delay line, oscillator etc. Interactive embedded zero-latency DSP coding!?

If I had to bet, though, My feeling is that picolisp GC times are not a practical problem for controller code, even on avr32. This is achieved with a very simple non-compacting GC. Here’s my picolisp fork, specially tailored for event-loop-driven ‘REPL’ (it’s not a true REPL, see below):
my embeddable minipicolisp fork
and the aleph branch where I was playing:
picolisp on github

Interesting for a picolisp-on-teletype app, also might be worth reviewing my commits there to port, for example, pforth to aleph/teletype - @zebra was interested in this at some point too. Suspect many ‘hackable’ language implementations drive the REPL from blocking reads to stdin, whereas in picolisp app I trigger the lisp interpreter non-blocking using serial chars, returning to wait for more serial chars until a read-able expression is received, then eval-print that expression.

Now I come to think of it this is not really a lisp REPL (read, eval, print, loop) but a TEP (trigger, eval, print).

yes, i got pforth compiling and (i think) running on the avr32 / aleph hardware. but indeed, it needs this part:

trigger the interpreter non-blocking using serial chars, returning
to wait for more serial chars until a read-able expression is received,
then eval-print that expression.

so yeah i should definteiyl add that! and having the picolisp example is very helpful. (would also want to be able to run programs from sdcard.)

the other thing i would have to do is add a bunch of custom forth words to interact with peripherals.

i agree that forth is a great candidate for a ready-to-go interpreted language for aleph/TT. it has no GC and it is relatively fast. but, it still needs to run keywords through a dictionary and do quite a lot of stuff for each operation, compared to compiled programs; keyword definitions can go many layers deep.

pforth in particular is super portable and was easy to get going (that’s what it’s for) but it’s not the most performant forth.

i honestly doubt it would be practical to run a REPL on the blackfin (could be wrong!) it seems almost more feasible to do something like what peter blasser has done with SHLISP - use the interpreted language to build a DSP graph, map ugens in the graph to blocks of ASM that are hardcoded to literal opcodes in memory. lots of work, but certainly worthwhile.

i honestly think the RPN thing is pretty easy to get used to. it helps to use good practices : small functions, and comments to clearly describe the expected state of the stack before and after the function call.

so yeah, in short, i would love to make some progress on that, just hard to find the time :slight_smile:

Wouldn’t know where to start in terms of writing the blocks of ASM! How does such a system keep track of state for multiple instances of the same ugen? How are input/output values passed through the net?

well, i haven’t attempted to make a full understanding of the shnth code and haven’t really looked at it for a while. it is still available from shbobo.net though.

that said…

How does such a system keep track of state for multiple instances of the same ugen?

pretty simple actually: there are either 4 or 8 instances each ugen, statically located at known SRAM addresses. so you can only use up to 4 independent FOURSES for example.

How are input/output values passed through the net?

(ok, actually looking now… )
it’s not super clear to me. but, i can see that each data register is given an intended use and an alias. some seem to describe the state machine of the bytecode interpreter (lispACC, lispRET), some for system I/O (dacLEFT, dacRITE) and some are for intermediate results or busses or whatever (workONE through workFIV).

the host program takes text, interprets it as SHLISP code, creates bytecode, and sends it in chunks to the shnth.

i won’t claim to grok the bytecode structure or its interpreter. but in a nutshell, each entry in the bytecode vector might represent atomic data to be pushed to the “stack” (dedicated registers, really), or an opcode pointing at a synthesis macro to be executed (“m-expression” in shlisp-speak), a language primitive (arithmetic, sensor input accessors), or an expression delimiter (). how exactly the bytecode interpreter deals with complex/nested expressions is the part i have not really dived into.

here’s peter’s tutorial doc, which doesn’t go deep into device-side implementation by any means, but provides a more thorough overview and a description of the opcodes and primitives.


PS: forgot that any posting of the shnth source should come with a trigger warning for ascii pornography

3 Likes
1 Like

Not to be taken as a rant, or a wet blanket… just a fond rememberance…

Many years ago, Ivan Tcherepnin sent me to see David Behrman in his New York studio. We were both using Apple ][ computers to control analog synthesizers. Behrman introduced me to using Forth. I came back to Cambridge - and went nuts. I spent the next several years writing music with Forth, a custom built interface (by Dave Wilson, of the W.A.D. fame) and Serge modules.

I still have all the discs (useless) and printouts (readable, but fading) — Alas, 100s of pages of gobbledegook, never to be recreated. In contrast, in the summer '82(3?) I spent at CCRMA, I produced a work in some really obscure languages: Bill Schottstaedt’s PLA and SAMBOX code. 30+ years later I dredged up the printouts, and rebuilt and expanded the piece.

Forth has been described as a “write-only” language. But, hey, some music is destined to be ephemeral…

13 Likes

wonderful to hear this, thank you! much respect to david behrman-- i must find a way to go visit him at bard, which is not too far from our farm.

1 Like

Does Teletype allow for the creation of words (which i guess are called OPs) the way other Forths do? I couldn’t find this in the manual. I guess the eqivalent is defining a script that you trigger, but that’s a bit different.

no. ttscript isn’t Forth and isn’t based on Forth. the OPs are literally effectively opcodes. (like forth’s “static instructions.”)

in this thread, some surface similarities have been pointed out (parsing order), and it has been proposed that it might nice to have a proper forth on TT (dynamic instructions, separate data/function stack, full RPN, etc. i agree!)

1 Like