Interest in a module+protocol for sending text over VCV cable?

@pachde @baconpaul Interesting! Surprising! Allow me to probe that notion a bit more.

  1. Do you want a text editor (human edits a block of text) or a command line (human submits text line by line)?

    1.1 Text Editor: Do you want the editor to live inside your module or outside of it?

    1.1.1 Inside: I’ll humbly suggest you look at the editor embedded in my Fermata module. I wouldn’t say it’s encapsulated as cleanly as possible, but it is already shared between two of my modules. I’m happy to take suggestions on how to make that something you could use within your own module(s).

    1.1.2 Outside: Interesting! Why is that a superior experience for your users? How would you envision the interaction between your module Foo and the editor module Bar to work over time?

    1.2 Command Line: Interesting! Do you want the command line to live inside your module or outside of it?

    1.2.1 Inside: Has this proven hard to do within your module? I haven’t tried it, so I wouldn’t know.

    1.2.2 Outside: Interesting! Why is that a superior experience for your users?

I’m being brief here, so the questions above may look snarky, but I’m genuinely curious about your thinking, and wondering if the TTY module I’m conceptualizing should have an output port as well.

mahlen

Let’s take a case adjacent to mine, imagine you have a sequencer which can play a bit of music in abc notation, so clearly you can just type in the notation

But if you could externalize the text for the currently playing segment you would separate the problem of parse play etc the text from the problem of pick the text to play,

So imagine a simple case, module one has a port which takes abc notation text and a trigger, in trigger the current abc sequence heads out per the clock as v oct

A second module has 15 phrases in it and selects one at random to send down

But once you do that you can write modules which generate notation strings and send them to the sequencer, which has now really become a sequence phrase interpreter,

To do this you need to send text along a cable,

I have other worse ideas too, some of them based on squinky and me on discord chatting about making the Sam lib a bit more modular, a project I mostly have done,

But basically if you start thinking about notation as content and text as notation whether it is abc or lilypond fragments or other formats, you get a need to move text around

My description of these awesome sounding modules does not imply that either the ideas are fully baked or that I will ship them ever, chuckle,

1 Like

Another case I have is a chordal sequencer. So you enter notation like c7b9 g7 d- a-7 as your sequence with each chord in a cell and then you basically have a jazz lead sheet sequencer, would be cool if you could send sequence strings from external tools for same reason. Like stead of that being a lead sheet module it s really a “comp a single chord with reasonable state management for voice leading” and then you send a stream of chords on a cable.

2 Likes

Our old sequencer clipboard format was just utf-8 encoded JSON with a documented scheme. Isn’t that more powerful that an ascii blob?

2 Likes

How did you move it down a cable?

It’s a clipboard format, so it goes through the clipboard rather than a cable, so entirely user-ui-driven. This is fine for copy/paste data interchange, but there are good scenarios for automated and connection-oriented (point-to-point) channels. Automated pushing to the clipboard is not a good user experience.

As for my immediate use case, I have a simple text display module, ready to receive text. It is extended another of my modules (a color picker with modulation) that enables animating the color of the panel and the text. Clipboard would serve it fine for its current main patch notes usage. I can imagine expanding it so that it could be like one of those electronic billboards that can flash and scroll text, or accept simple formatted text. I imagine text sources could send text on a trigger for display. Other modules can interpret each bit of text as they will.

JSON is text (a UTF-8 blob in this case), as is YAML, Javascript, HTML, Markdown, laTex, CSound, Supercollider, LUA, lilypond, etc. Up to the receiver to interpret it (like OSC – the standard specifies no semantics for the data). Best to allow for UTF-8 (8-bit code units): no good reason to use any other encoding.

A future set of modules I’m slowly working out (think generic midi pad like a monome) are configured using a text-based notation. Since you only need to edit while you’re setting it up, it would be nice to plug in a text editing module for that data entry, then remove it when done (or leave it for convenience).

As you outlined, there are use cases for line-at-a-time (sending triggered by hitting enter at the end of a line) and for multiline text sent by a manual (button Ctrl+Enter, etc.) or CV trigger.

No down the cable, it was just for clipboard. I just meant to point out that there is no need to invent a new encoding of notes, as there already is one that a few ppl support. Wire part, no, that’s different, and I got nothing there.

See other reply.yes , I was thinking of any typical layered protocol. The meaning of the enclosed data doesn’t need to depend on the way it’s moved. Like how tcp can be on top of Ethernet, or on top of something else.

1 Like

Just a clarifying question:

Should the particular cable transmit text exclusively?

I was thinking if the musical encodings like abc and lily pond as my encodings, and the standard jazz chord names from iReal pro. But definitely curious what’s out there already of course!

I think a given module on its text ports (or ports switched to ‘text’ mode) would transmit and expect only text.

You could, of course run that output into something expecting CV/audio and get something weird, which can be good. Running audio/CV into an input expecting text will get garbage, and such modules may need validate that the data is valid UTF-8 before doing something with it. If valid UTF-8, it could still be random-looking characters, but that’s ok. The only real hard rule is that you shouldn’t crash with unexpected inputs.

1 Like

My approach would be:

  • a CV value of zero means no data or end-of-string
  • a value greater than zero stands for a single byte of an UTF-8 string
  • negative values will be ignored
  • to avoid large values on the cable, each byte is divided by 16 to get a float value less than 10.f (volts)
  • after converting back to a byte value, every data greater than 255 will be ignored
1 Like

Very good! – the only other thing is perhaps check that the text is valid UTF-8. I think there is code already in Rack that can be used.

Might be helpful: @carbon14 shared this proof-of-concept for a VCV rack modem:

Looks like it’s for Rack 0.6.1 FYI

1 Like

It seems a start of message and end of message marker or a checksum might be useful too; then you can implicitly trigger on new data in.

Oh this looks fantastic

Several of the UTF-8 characters with values in the 0 … 31 range are defined for transmission control. For example, value 1 is SOH (Start of Header), value 2 is STX (Start of Text), value 3 is ETX (End of Text), value 4 is EOT (End of Transmission) , etc.

1 Like

This part of UTF-8 comes from the precursor to ASCII and are in part the control codes for talking to teletypes on serial phone connections. This before those fancy video terminals like the VT100 existed. Extreme retro to actually use some of these codes for something. Takes me back to high school where the advanced math classes got to use the teletype which connected to a timeshare PDP-11 at the college.

2 Likes

@baconpaul, what you suggest in #42 and #43 is an interesting use case. I’m going to try and break it down into different parts to see if I understand what you’re driving at.

First is a desire to have a module receive something more than a single float value. You want a defined set of values. Or perhaps order matters here, in which case I’d think of it as a sequence. Either way, what the module wants is more than a single value. I’m presuming that each value in the sequence/set will eventually become a float, yes?

Second is an acknowledgement that, for some forms of user input, text can be a superior entry method. Especially if describing a number of (say) notes, writing “c#4 e4 g#4” is a lot easier than figuring out the values that make them up.

Third, there’s a need to translate the text from characters to the floats they represent. That process would have to be aware of the “grouped” aspect, though, to do so correctly. So most of the time, I guess the receiving module would have to be the interpreter, but I do wonder if new modules might do that kind of interpretation, maybe…

Fourth, that giving users control over these groups is important. Some affordances might include

  • being able to select between them
  • generate them by typing
  • generate them with other modules
  • manually or automatically trigger ingestion of a group into a module

Fifth, well, if these are going to be inputs to modules, then, latency matters a lot more than in my user-reading-text case. So the “boring” suggestion from you #4 post starts sounding a lot more compelling. And if this is a struct being sent to the receiver, than it can be an array/list, etc.

So there’s a bunch of goals kind of being mushed together here:

  • Text can represent a group of things as long as the creator and receiver agree on a delimiter.
  • Text can represent floats; not in the most byte efficient way, but reasonably well. And it allows whatever cares to to do the text->float conversion.

All this is a little out of scope for my immediate goals, by the way, but it’s great to discuss before I make a useful but kinda stupid standard.

Am I reading what you are suggesting correctly?

Yes, except the content is more complicated. Lilypond for instance isn’t just a list of floats from note names. It includes polyphony, rhythm, rests, and more. Like here’s an expression of a multi rhythm 3 voice theme.

It similarly has names for chords and has non-voiced chords. So like this

In a 2017 project I wrote a parser from this format to manipulatable music data structures which I could then render. It was all in clojure but I have a sort of reboot of it cooking in PEGTL / C++ in my ‘half done’ folder.

So really this drives to the conclusion which is: you want to send delimited chunks of texts as data along cables. And the endpoints agree on what the text means. That’s exactly correct.

I think probably what we want to do is to have a shared library for text interchange ports. Lets call it the “text Interchange port system” or “tipsy” :slight_smile: And it would contain the marker interfaces and functions.

At a very high level you I think you would need three chunks of function. A marker interface like

struct TipsyParticipant_V1
{
   bool isInputTipsy(int) const;
   bool isOutputTipsy(int) const;
}

This is the only API which needs to be ABI stable.

Then a function to see if i am connected like

bool isTheOtherEndOfMyOutputCableAlsoTipsy(rack::Module *m, int outputPortId);
bool isTheOtherEndOfMyInputCableAlsoTipsy(rack::Module *m, int outputPortId);

which does the obvious traverse cable, grab module end point, do a dynamic cast to tipsy, and check the port in the tipsy list and then a function to be data encoding stable like

int createTipsyBuffer(const std::string &, float *data, size_t maxN); // return how many bytes you created

and probably a running object to decode it like

struct TipsyDecoder
{
    enum { MessageStarted, MessageComplete, ProcessingOK, Error } decodeState;
    decodeState process(float f);
    std::string getMostRecentMessage() const;
}

which you could use in your class like

   TipsyDecoder tc;
   void process(...) { 
     auto state = tc.process(inputs[MY_IN].getVoltage(0));
     if (state == MessageComplete) rack::LOG(state.getMostRecentMessage()); 
  }

something like that

2 Likes