From 9a44380d45ace45595ee8115abf72c6a7c332e00 Mon Sep 17 00:00:00 2001
From: Bel LaPointe <153096461+breel-render@users.noreply.github.com>
Date: Wed, 15 Oct 2025 00:13:26 -0600
Subject: [PATCH] go
---
cmd/tts-room/main.go | 9 +++
go.mod | 9 ++-
go.sum | 6 ++
src/public/index.html | 158 ++++++++++++++++++++++++++++++++++++++++++
src/server/message.go | 9 +++
src/server/server.go | 60 ++++++++++++++++
src/server/session.go | 106 ++++++++++++++++++++++++++++
7 files changed, 356 insertions(+), 1 deletion(-)
create mode 100644 go.sum
create mode 100644 src/public/index.html
create mode 100644 src/server/message.go
create mode 100644 src/server/server.go
create mode 100644 src/server/session.go
diff --git a/cmd/tts-room/main.go b/cmd/tts-room/main.go
index da29a2c..9454b3f 100644
--- a/cmd/tts-room/main.go
+++ b/cmd/tts-room/main.go
@@ -1,4 +1,13 @@
package main
+import (
+ "net/http"
+ "tts-room/src/server"
+)
+
func main() {
+ s := server.NewServer()
+ if err := http.ListenAndServe(":10000", s); err != nil {
+ panic(err)
+ }
}
diff --git a/go.mod b/go.mod
index 69b15af..02c5cda 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,10 @@
module tts-room
-go 1.22.2
+go 1.24.0
+
+require (
+ github.com/gorilla/websocket v1.5.3
+ golang.org/x/time v0.14.0
+)
+
+require github.com/google/uuid v1.6.0 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..4290850
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,6 @@
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
diff --git a/src/public/index.html b/src/public/index.html
new file mode 100644
index 0000000..566d0d9
--- /dev/null
+++ b/src/public/index.html
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/server/message.go b/src/server/message.go
new file mode 100644
index 0000000..0b4c2e9
--- /dev/null
+++ b/src/server/message.go
@@ -0,0 +1,9 @@
+package server
+
+type message struct {
+ Text string
+ Pitch int
+ Rate float64
+ VoiceIdx int
+ room string
+}
diff --git a/src/server/server.go b/src/server/server.go
new file mode 100644
index 0000000..1461587
--- /dev/null
+++ b/src/server/server.go
@@ -0,0 +1,60 @@
+package server
+
+import (
+ "log"
+ "net/http"
+)
+
+type Server struct {
+ sessions []*session
+}
+
+func NewServer() *Server {
+ return &Server{}
+}
+
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/ws":
+ if err := s.WS(w, r); err != nil {
+ log.Println("[ws]", err)
+ }
+ default:
+ http.FileServer(http.Dir("./src/public")).ServeHTTP(w, r)
+ }
+}
+
+func (s *Server) WS(w http.ResponseWriter, r *http.Request) error {
+ sess, err := newSession(w, r, nil)
+ if err != nil {
+ return err
+ }
+ defer sess.Close()
+
+ log.Println("someone has joined", sess.room)
+ defer log.Println("someone has left", sess.room)
+
+ sess.cb = func(m message) error {
+ for i := range s.sessions {
+ if s.sessions[i].id != sess.id && s.sessions[i].room == sess.room {
+ select {
+ case s.sessions[i].scatterc <- m:
+ case <-s.sessions[i].ctx.Done():
+ }
+ }
+ }
+ return nil
+ }
+
+ s.sessions = append(s.sessions, sess)
+ defer func() {
+ for i := range s.sessions {
+ if s.sessions[i].id == sess.id {
+ s.sessions = append(s.sessions[:i], s.sessions[i+1:]...)
+ return
+ }
+ }
+ }()
+
+ return sess.Run()
+}
diff --git a/src/server/session.go b/src/server/session.go
new file mode 100644
index 0000000..a451562
--- /dev/null
+++ b/src/server/session.go
@@ -0,0 +1,106 @@
+package server
+
+import (
+ "context"
+ "encoding/json"
+ "log"
+ "net/http"
+ "sync"
+
+ "github.com/google/uuid"
+ "github.com/gorilla/websocket"
+ "golang.org/x/time/rate"
+)
+
+type session struct {
+ ctx context.Context
+ can context.CancelFunc
+ ws *websocket.Conn
+ wg sync.WaitGroup
+ cb func(message) error
+ id string
+ scatterc chan (message)
+ room string
+}
+
+var upgrader = websocket.Upgrader{}
+
+func newSession(w http.ResponseWriter, r *http.Request, cb func(message) error) (*session, error) {
+ c, err := upgrader.Upgrade(w, r, nil)
+ ctx, can := context.WithCancel(r.Context())
+ return &session{
+ ctx: ctx,
+ can: can,
+ ws: c,
+ cb: cb,
+ id: uuid.New().String(),
+ scatterc: make(chan message, 20),
+ room: r.URL.Query().Get("room"),
+ }, err
+}
+
+func (s *session) Close() {
+ if s.ws != nil {
+ s.ws.Close()
+ }
+ s.ws = nil
+ s.can()
+ s.wg.Wait()
+}
+
+func (s *session) Run() error {
+ defer s.Close()
+
+ go s.gather()
+ go s.scatter()
+
+ <-s.ctx.Done()
+ return s.ctx.Err()
+}
+
+func (s *session) gather() {
+ s.while(func() error {
+ mt, msg, err := s.ws.ReadMessage()
+ if err != nil {
+ return err
+ }
+ if mt != 1 {
+ return nil
+ }
+
+ var m message
+ if err := json.Unmarshal(msg, &m); err != nil {
+ return err
+ }
+ m.room = s.room
+
+ return s.cb(m)
+ })
+}
+
+func (s *session) scatter() {
+ s.while(func() error {
+ select {
+ case m := <-s.scatterc:
+ b, _ := json.Marshal(m)
+ return s.ws.WriteMessage(1, b)
+ case <-s.ctx.Done():
+ return s.ctx.Err()
+ }
+ })
+}
+
+func (s *session) while(foo func() error) {
+ defer s.can()
+
+ s.wg.Add(1)
+ defer s.wg.Done()
+
+ l := rate.NewLimiter(20, 1)
+ for l.Wait(s.ctx) == nil {
+ if err := foo(); err != nil {
+ log.Println(err)
+ return
+ }
+ }
+}