Generic teletype ops for interacting with other devices?

a thread to discuss adding generic i2c teletype ops for interacting with other devices.

background: the ecosystem of devices that can be integrated with teletype via i2c continues to grow with ADDAC221 being the latest addition. for each supported device there is a set of device specific ops (TI, FB, JF etc). this makes sense for things that are indeed device specific (such as JF.RUN).

it might however make more sense to create a set of device agnostic ops for things that are supported by more than one device. in particular, it might be helpful to have ops for:

  • reading CVs (supported by TXi, ansible, ADDAC221)
  • outputting CVs (TXo, ansible, ER-301)
  • reading controller values (TXi, 16n faderbank)
  • reading MIDI CC values (faderbank, ADDAC221)
  • sending MIDI notes and CCs (faderbank, ADDAC221)

(note: the last 2 are currently not officially supported, 221 is still a work in progress, faderbank MIDI ops are available as an experimental build)

(note2: it doesn’t have to be for i2c only, in theory we could connect controllers or MIDI interfaces to teletype via USB, however i wasn’t able to make it work).

having a set of generic ops would provide several benefits:

  • make sharing and re-using scripts easier
  • make scripts more readable
  • make it easier to add support for new devices

how could this work? let’s say we have a generic op to output a CV.

option #1:

do what teletype does for ansible in tt expander mode: the same op CV is used, and to output to ansible you simply use a different output number (1-4 will use teletype’s own outputs, 5-8 will output to ansible, and so on). pros: being able to easily use this in a loop. cons: you have to remember which numbers map to which device.

option #2:

introduce a new op, say GCV (“generic CV” - could likely have a better name!), which would have an additional parameter for the device number. the downside is that it’s more verbose.


for either option we would need a way to map output range (for option #1) or device number (for option #2) to actual devices. this would require a separate op, something like CV.MAP 100 120 4 (map output range 100-120 to device 4) pr CV.MAP 1 3 (map device #1 to device 3).

instead of using device numbers for mapping we could probably even map to the actual i2c addresses (i think this is what crow does too?). this would mean more devices could be supported in the future without having to create a new version of teletype firmware.


I think the simplicity in scripting and flexibility in customization makes option #1 seem pretty appealing to me. I also think using i2c addresses as the basis of identification for generic ops is a great way to efficiently incorporate new devices. If both of these were to be implemented it would also be extremely helpful to save mapping preferences to flash memory on Teletype itself, if possible.

1 Like

i was thinking you’d just put mapping ops into the init script but saving to flash might be a better idea!

I can see where you come from but also feel a bit reluctant about this. Some concerns:

  • How would the system behave when you change your configuration? Changing the internal i2c connections is a pain anyway but how about unplugging the 16n from txb? Would the input numbers configuration of your script still work then for the other modules? Or would you have to remember your 16n connection state for each script even it does not use it just because it was plugged in or out when you wrote it?

  • I am not sure if it would really make sharing and reusing scripts easier in the end since it is harder to figure out what all those generic inputs and outputs where meant for. Also not all modules react in the same way to CV op’s. TXo for example takes CV for amplitude in osc modes. And I assume not all future devices will be able to apply slew on CV outputs.

  • Personally I would be heavily affected by the “one has to remember all the numbers”-issue so I would not want the current op’s to be removed for those who want (need) to use them for their intuitiveness. Would implementing a second layer of CV op’s lead to memory issues on teletype that might prevent other useful developments in the future like more scenes or patterns?

  • Is this really needed anyway? My feeling is that in the current framework of scenes, lines and variables extensive mapping of i2c inputs and outputs for realistic use cases comes against borders easily. Also since these are all pretty niche products, how many people would really make use of it beyond the current/common set of 16n, ER-301, Ansible, JF and TX modules? So another way could be to leave it within the current set and therefore keep the op’s more intuitive, i2c more stable and everything more simple.

I certainly see the attraction of extensive and flexible i2c configurations. On the other side when I think things through I always come to the point where I recognize that this would probably not work reliably due to i2c’s master/slave nature and I cannot connect all devices without loosing functionality or have to reconfigure things again and again.

mapping would simply mean that sending CV (using my example) would send it to a specific i2c address. if there is nothing listening to that address nothing would happen, just like it does now when you use an i2c op for a device you don’t have.

if you have a script that generates 4 CVs by sending them to, say, ER-301, and you want to use it for TXo instead, you have to find and replace all instances of SC.CV with TO.CV. with the proposed approach all you have to do is execute a mapping op.

these would fall under the category of device specific ops, so not covered by this proposal.

this would be in addition to existing ops, not replacing them.


the alternative is introducing many niche ops, which bloats the language and limits sharing scripts. and i would argue that using the same set of ops for commonly done things is more intuitive as you don’t have to remember the many variations.

these ops would behave exactly the same way the existing i2c ops do, so they don’t make i2c any more “unstable”.

again, the existing functionality will not be affected.


But would it work? Or would TXo firmware need to be revised? On the TXo the command for setting the amplitude of an oscillator or a CV output is the same. Also would it work with the hardware jumpers on the TX modules which set the output number. How would these op’s interfere?

So the output numbers would be somehow implemented in the firmware? Like 1 - 4 is always TT, 5 - 8 is always one possible Ansible (connected or not), then 100 outputs for ER-301 and 32 to for 8 possibel TXo?

I am not sure if I get this right - basically the device op SC would be exchanged with an abstract number like 3?

I am afraid this is a misunderstanding. What I meant was that there are devices which can work in master or in slave mode and this affects their functionality and the that of the whole i2c configuration. This, in combination with the notion that the CV op does different things in conjunction with different modules (the ER-301 somewhow processes it, Ansible outputs it, TXo either outputs it or processes it as volume and already has an internal system for mapping output numbers…), made me wonder if it is really a good thing to combine and if people would realistically use it. Or if it would get too complex and lost in numbers.

I can see that the ADDAC has a similar functionality to the TX and the TT outputs and adds MIDI to the TT universe, so I would propose to add the needed commands, maybe with some specific to this module and wait what comes next and if it is really needed. JF for example does not work with CV op’s at all, I think. So why combining parts of the external op’s while others stay separated? That seems confusing to me.

I think a design that doesn’t introduce more init-ing would be good (or increasing space of the init script, though that’s a whole other can of worms).

I haven’t been using teletype a ton recently, but when I was, I would very easily run out of init space and start having to SCRIPT 8 the 6th line to get more space. Having a lot of i2c bus stuff going on exacerbates the problem, so I could see doing something that requires more init-ing highlighting the limitation.

1 Like

again, this would be a new set of ops that would not affect any of the existing ops or scripts at all. if you prefer to use the old ops you can still do so.

the main idea is future proofing teletype and not bloating the language with many device specific ops for things that have exact same meaning for any of the devices that would support it.

imagine at some point in the future we are able to connect faderfox, livig ohm and midi twister to teletype. we need an op to read a knob value. would you rather have: FF.CC, LO.CC and MT.CC or just CC?

now imagine you have a script that reads from a faderfox knob: X FF.CC 1. now you want to use a midi twister instead. with a generic op you simply swap controllers, no script changes are necessary (in this case we don’t even need to map anything). with device specific ops you have to find and replace all FF.CC with MT.CC. what’s easier? what’s more intuitive?

no. for something like setting CVs it would work exactly like using TO.CV. and if your script is using features available on TXo only, you don’t need the generic ops anyway.

you would specify which TXo you are targeting when you map. so you could map outputs 11-14 to TXo #1, outputs 15-18 to TXo #2 etc. the documentation would specify the device id for each specific device (or if we use i2c addresses for mapping there will be a list of addresses provided for each device).

no, that’s what mapping is for. your script could simply use whatever outputs you want. say, 11-18. then you map them to the actual device. let’s say we use device id for mapping, where we have the following ids:

0 - teletype itself
1-4 - ansible 1-4
5 - er-301

your script generates 8 CVs using CV 11CV 18. you want to use your script with teletype. you do something like this:

CV.MAP 11-14 0 (map outputs 11-14 to teletype)

now you add ansible in tt expander mode so you can use CVs 15-18. you simply execute:

CV.MAP 15-18 1

now 11-14 will send to teletype and 15-18 will send to ansible. now let’s say you decided to use your script with er-301 instead:

CV.MAP 11-18 5

that’s it! we could also add an op to reset mappings to the default state (so if we use CV op for generic CV it would reset it to outputs 1-4 mapped to teletype and 5-8 mapped to ansible).

not only it’s simple, it would allow things like sending same CVs to multiple outputs, muting CVs (just unmap), using various devices in the same loop (right now you can’t use a loop to send CVs to teletype AND TXo, for instance, since they use different ops). this is also what would make scripts more shareable and reusable, since you don’t tie them to specific devices.

again, existing ops will not be affected. you can still use SC.

leader/follower configuration does not apply here as these are teletype ops. teletype only works as a leader.

for the reasons outlined in my post: it removes device specific stuff from scripts that don’t use it. this makes sharing and reusing such scripts easier for different devices. it reduces the need to add device specific ops for common operations. it future proofs teletype.

o that’s an interesting way of putting it, especially with the multipass-enabled alt firmwares for other modules. Could make it a bit easier to write generic firmwares that interface with tt via i2c

this is not really multipass related - it’s more of a question of establishing and documenting the message format for common operations. it kinda works that way already - i’m pretty sure for all i2c CV ops the format right now is:

i2c_device_address output_number value

ah gotcha 20 charcharchar

It’s just an assumption, but if the aim is ‘future proofing’ then new devices will probably make use of it and it will become the new standard. So there won’t be more new old op’s.

You mean connecting a Livid Ohm via USB and skipping the keyboard. I am not sure if I think this is a good direction to develop TT. Would the Livid get enough power from TT’s USB port? Anyway I would rather have FF.CC , LO.CC and MT.CC.

I struggle a bit to understand how this would work without script changes and remapping things? Just because there is only one USB connection so CC has to come from there? I would prefer a simple MIDI.CC op then and the whole FF.CC, LO.CC, MT.CC point would not make much sense anymore.

I am afraid one would need a second INIT script to map all the outputs just to ged rid of the clear labeling within the scripts.

Yes, I know that - it seems that I am not able to word my point in a way that is understandable. So i just skip this.

I think this concepts only applies to use cases where one strictly uses output module configurations and wants to switch modules for what ever reason but also has kept track of the mapped numbers somewhere. It probably would still overload the INIT script then and misses the point for most other cases. Take a look at the ER-301 for example. Sending CV to it could mean modifying a delay, a start point in a sample player or activating a looper - it would make no sense to send that CV elsewhere via simple remapping. I guess it only really applies to TT, Ansible in tt-mode and TXo when you stay strictly with the simple CV output w/o SCALE use. As soon as old school op’s or special module functions get used in parallel it will be confusing and not easily convertible by just remapping anymore.
If the main use case is to just send meaningful pitch CV to many oscillators and algorithmically switch the outputs/oscillators between the current packs of four outputs it might make sense but this seems very specific for a whole new output mapping system in parallel to the existing one.

this most certainly does not prevent adding new device specific ops when it makes sense.

it was simply an example to illustrate my point, and my OP makes a mention of connecting MIDI controllers directly via the USB port.

so how is it different from what i’m proposing?

there is already a suggestion to store mapping to flash instead of putting it in the init script.

what point?


To me there is a difference between a MIDI op that reads or emits CC values to and from the USB port and generic CV op’s for i2c ports that change between several connected devices. Like if the device is USB MIDI and its physical apperance does not matter as there is always only one connected and Midi is specified.

I think it should be screen readable then to check how it is configured at a given moment.

The point that ER-301 is not a CV output module that interfaces betweeen TT and other modues and therefore is not as interchangeable as ttansible, TT or TX outputs might be.

the proposal also covers being able to output MIDI via capable devices connected with i2c. so should it then be ADDAC.MIDI.CC and FB.MIDI.CC?

i’m sure many scripts are used for generating sequences. you might want to be able to send a sequence to ER-301 or to physical outputs.

duplicating CVs sent to ER-301 on physical outputs could also be beneficial. filter key tracking is a pretty common thing to do if you’re using an external filter, as just one example.

and these generic ops are meant for specific operations. outputting CV is the same thing whether you send that CV as a virtual value to a SC unit in ER-301 or to a physical output.

“generating notes” (which is what you really mean by pointing out the er-301 and txo specifics) could also be abstracted and could take into account device specific details (see this thread) but it is not the goal here.

I’m in favour of option 1 and being able to write mapping configurations to flash. Basically, anything that keeps scripts concise is a benefit in my book. Being able to loop over all your inputs/outputs makes a lot of sense, and writing configuration to flash gets round any INIT bloat. This feels like something that would make sense as part of the DEVICE ops maybe? (Something to keep my favourite DEVICE.FLIP company!)

By the way, while we’re on the topic… Its a bit of a shame that mapping a range of teletype’s PARAM and INs uses SCALE but TXi’s PARAMs and INs uses MAP.


I think this makes total sense and also think Option 1 might work better.

This seems like a great idea. I also like the idea of storing the mappings in flash to conserve precious init script space.