Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rapidmidiex-ws-protocol v0.0.1 spec #17

Open
hansvb opened this issue Aug 3, 2022 · 8 comments
Open

rapidmidiex-ws-protocol v0.0.1 spec #17

hansvb opened this issue Aug 3, 2022 · 8 comments
Labels
enhancement New feature or request

Comments

@hansvb
Copy link

hansvb commented Aug 3, 2022

For v0.0.1 the proposal is to start with json text-messages over websocket.
The goal is to have a minimal working proof of concept.
We don't care about time-synchronisation or any other details.
We do want to measure latency as to have an idea what anti-jitter-mechanisms would be necessary for future upgrades of the protocol.

Client to server-messages

Connect-message

type Connect struct {
    Type    string     // a hard-coded string in each client to identify the
                       // kind of client, e.g. `tuicli v0.0.1` or `jscli-v0.0.1`,
                       // cannot be empty
}
  • Initiated by client
  • server responds with a UpdateClientList-message

SendNoteOn-message

type SendNoteOn struct {
    Note    int         // encoded as a (MIDI-compatible) number, e.g. `60` = `C-4`
}

SendNoteOff-message

type SendNoteOff struct {
    Note    int
}

Server to client-messages

UpdateClientList-message

type Client struct {
    ClientId    string      // a unique (UUID?) representation for each client,
                            // shared and visible by all connected clients 
    Nick        string      // A nickname chosen by the connecting client, can be empty
    Type        string      // a hard-coded string in each client to identify
                            // the kind of client, e.g. `tuicli v0.0.1` or `jscli v0.0.1`,
                            // cannot be empty
    IP          string      // no need for a `net.IP`?
    Latency     int         // roundtrip-time in milliseconds, measured and
                            // updated by the server at regular intervals with low-level
                            // websocket-ping/pong-messages
} 
type UpdateClientList struct {
    ClientList  []Client
}
  • sent by the server to all connected clients on receiving a Connect-message
  • sent at regular intervals (TBD: minimum refresh interval)

ForwardNoteOn-message

  • sent by the server to all connected client on receiving a NoteOn-message
type ForwardNoteOn struct {
    ClientId    string
    Note        int
}

ForwardNoteOff-message

  • sent by the server to all connected clients on receiving a NoteOff-message
type ForwardNoteOff struct {
    ClientId    string
    Note        int
}

LatencyUpdate-message

Although the UpdateClientList-message also sends this information already, maybe it's still interesting to define this message. The server can then still decide to use this or opt for the full-on UpdateClientList.

type LatencyUpdate struct {
  ClientId string
  Latency int
}

Assumptions

Some assumptions about rapidmidiex-srv v0.0.1:

  • one global session is always in progress (perhaps without connected clients)
  • no bpm- or tempo-settings, just free for all to send notes
  • the server basically acts as an echo-server for note's, forwarding all received notes to all connected clients
  • server has a Pool with all connected clients ([]Client)
  • server must send regular websocket-Ping-frames to measure latency (TBD: interval)
  • server sents a UpdateClientList-message to all connected clients every time a new client connects or a client-connection is obviously lost
  • a server needs to update all clients with the latest latency information. It can use UpdateClientList-messages or LatencyUpdate-messages for this purpose. The exact details are up to the specific server-implementation.

Some assumptions about v0.0.1-clients:

  • Clients can send SendNoteOn- and SendNoteOff-messages to the server in any order and at the same time.
    • The Note is encoded as a (MIDI-compatible) number, e.g. 60 = C-4.
    • It's up to the client how to generate the note-numbers.
  • Clients can receive an UpdateClientList-message (with []Client).
    • In an extreme situation a client could choose to ignore this message completely
    • Clients can store the ClientId, for example to give visual feedback on which client generated a note-event (perhaps using color or other visual hints)
    • Clients can choose wether or not to display the latency-information of all other connected clients
  • Clients may or may not receive LatencyUpdate-messages. They should update their own table (if they chose to remember one).
  • Clients will receive ForwardNoteOn- and ForwardNoteOff-messages with a Note and a unique ClientId
    • They should have received this ClientId earlier via a UpdateClientList-message from the server.
    • A client could choose to ignore the ForwardNoteOn.ClientId and only act on the ForwardNoteOn.Note-information.
    • A UnknownClientIdError can be generated locally if a Note with an unknown ClientId is received but the protocol doesn't do error-reporting.
    • A client can also choose to ignore UnknownClientIdError and just play (or display) the note anyway.
  • Clients must reply to incoming websocket-Ping-frames with a Pong-frame (this is usually implemented by websocket-implementations as per the websocket-RFC's)
@adoublef adoublef added the enhancement New feature or request label Aug 3, 2022
@adoublef
Copy link
Contributor

adoublef commented Aug 3, 2022

I like this, one tweak, rather than two different structs for note on/note off why not an enum type Mode. Both of those structs have the same fields

@hansvb
Copy link
Author

hansvb commented Aug 3, 2022

I'm also thinking the UpdateClientList-message is a bit too much just to update the client's latency-table and use it only as a response to a Connect from a client.

Perhaps better to have a separate server-to-client-message:

type LatencyUpdate struct {
  ClientId string
  Latency int
}

@hansvb
Copy link
Author

hansvb commented Aug 4, 2022

I'm also thinking to give the client->server NoteOn/NoteOff-message a different name than the server->client ones for clarity.

We could use something like NoteOnIn/NoteOnOut put then it becomes more difficult to reason about because it's then a matter if you're using the perspective of the client or the server.

So I would propose for the client NoteOnGenerated / NoteOffGenerated.
And for the server NoteOnForward / NoteOffForward.

Any improvements on these names still possible?
Maybe NoteOnGenerate? Or SendNoteOn? since Generated is in the past but it would imply that the note is already played at the client that generated it...

@harveysanders
Copy link
Collaborator

harveysanders commented Aug 4, 2022

Good stuff!
A couple of questions:

I'm also thinking to give the client->server NoteOn/NoteOff-message a different name than the server->client ones for clarity.

Do we need to differentiate In and Out for the Note events? I think from each perspective, one would know if it's an incoming or outgoing message

Client.Type sounds a bit like a user agent. What do you think renaming about Type to UserAgent?

@harveysanders
Copy link
Collaborator

As a user, when viewing your peers' latency numbers:

  • Is it assumed that those numbers are their RTT from their client and server?
  • Should we attempt to estimate the latency of a peer to your client?

For example,
You, clientA, have an RTT (round-trip time) to the server of 10 ms
Your peer, clientB, has an RTT of 26 ms

From your (clientA) perspective, would clientB's latency be

  • 26 ms, their RTT to the server) or
  • 18ms, an estimate of their latency from your perspective ( 26/2 + 10/2 - half your RTT + half their RTT) ?

Does this make sense? Does it even matter? Am I overcomplicating things?

@hansvb
Copy link
Author

hansvb commented Aug 4, 2022

You're reasoning and calculations make perfect sense but for me it's still a big open question how all this will turn out in practice.. Like latency-spikes...

I can imagine in future versions of the protocol for example we temporarily start blocking notes once latency becomes too high. And eventually we might need some kind of anti-jitter-mechanism to even out all the reasonable spikes? But what is reasonable and how would we sync this across all connected clients? That's some big open questions right there ;-)

Anyway for simplicity's sake I would just have the latency-table (which clients CAN but don't HAVE TO display) initially be all the latencies (i.e. RTT's) in the perspective of the server. Maybe there can be an extra UI-widget that displays the client's OWN latency with the server.

@hansvb
Copy link
Author

hansvb commented Aug 4, 2022

There's also still a matter of security / spamming-clients.
For v0.0.1 I wouldn't put in any security-measures just so we can test what's possible.
But it would at least be nice if the UserAgent could contain a commit-hash so we always have a good idea which client is spamming during development. Go has facilities for this. (try go version -m <exe> for example).

@hansvb
Copy link
Author

hansvb commented Aug 5, 2022

I've updated the original post. I think the protocol is now in a decent enough state to have a draft put on the documentation-site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants