From f07a5b17b15563cd2e640798a0f97d1319e08657 Mon Sep 17 00:00:00 2001 From: Peter Salomonsen Date: Sun, 19 Mar 2023 12:35:50 +0100 Subject: [PATCH] sointu-server --- .gitignore | 1 + README.md | 31 ++++++++ cmd/sointu-compile/server.go | 139 +++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 5 files changed, 174 insertions(+) create mode 100644 cmd/sointu-compile/server.go diff --git a/.gitignore b/.gitignore index 4273bce..39c96dc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ actual_output/ node_modules *.raw +sointu-server \ No newline at end of file diff --git a/README.md b/README.md index a9cfabd..cdefdec 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,11 @@ Building the compiler: go build -o sointu-compile cmd/sointu-compile/main.go ``` +or building it as a server where you can post yaml to the `/process` endpoint and get WAT in return: +``` +go build -o sointu-server cmd/sointu-compile/server.go +``` + On windows, replace `-o sointu-compile` with `-o sointu-compile.exe`. The compiler can then be used to compile a .yml song into .asm and .h files. For @@ -111,6 +116,32 @@ sointu-compile -o . -arch=wasm tests/test_chords.yml wat2wasm --enable-bulk-memory test_chords.wat ``` +Example using the server: + +``` +groove = `bpm: 120 +rowsperbeat: 4 +score: + length: 24 + rowsperpattern: 16 + tracks: + - numvoices: 1 + order: + - 0 + - 0 + - 0 + - 0 + ......`; + +fetch('http://localhost:8080/process', { + method: 'POST', + headers: { + 'content-type': 'application/json' + }, + body: JSON.stringify({content: groove}) +}); +``` + ### Building and running the tests as executables Building the [regression tests](tests/) as executables (testing that they work diff --git a/cmd/sointu-compile/server.go b/cmd/sointu-compile/server.go new file mode 100644 index 0000000..b94df41 --- /dev/null +++ b/cmd/sointu-compile/server.go @@ -0,0 +1,139 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + + "gopkg.in/yaml.v3" + + "github.com/gorilla/mux" + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/vm/compiler" +) + +func filterExtensions(input map[string]string, extensions []string) map[string]string { + ret := map[string]string{} + for _, ext := range extensions { + extWithDot := "." + ext + if inputVal, ok := input[extWithDot]; ok { + ret[extWithDot] = inputVal + } + } + return ret +} + +func output(filename string, extension string, contents []byte) error { + _, name := filepath.Split(filename) + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("could not get working directory, specify the output directory explicitly: %v", err) + } + + name = strings.TrimSuffix(name, filepath.Ext(name)) + extension + f := filepath.Join(dir, name) + err = ioutil.WriteFile(f, contents, 0644) + if err != nil { + return fmt.Errorf("could not write file %v: %v", f, err) + } + return nil +} + +func process(filename string) error { + inputBytes, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("could not read file %v: %v", filename, err) + } + var song sointu.Song + if errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil { + if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != nil { + return fmt.Errorf("song could not be unmarshaled as a .json (%v) or .yml (%v)", errJSON, errYaml) + } + } + if song.RowsPerBeat == 0 { + song.RowsPerBeat = 4 + } + if song.Score.Length == 0 { + song.Score.Length = len(song.Score.Tracks[0].Patterns) + } + + comp, err := compiler.New(runtime.GOOS, "wasm", false, false) + if err != nil { + return fmt.Errorf("error creating compiler: %v", err) + } + + compiledPlayer, err := comp.Song(&song) + if err != nil { + return fmt.Errorf("compiling player failed: %v", err) + } + + for extension, code := range compiledPlayer { + if err := output(filename, extension, []byte(code)); err != nil { + return fmt.Errorf("error outputting %v file: %v", extension, err) + } + } + + return nil +} + +func handleRequest(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) + return + } + + var input map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&input) + if err != nil { + http.Error(w, "Invalid JSON input", http.StatusBadRequest) + return + } + + content, ok := input["content"].(string) + if !ok { + http.Error(w, "Invalid JSON input: content is missing or not a string", http.StatusBadRequest) + return + } + + filename := "temp_song_file.yml" + if err := ioutil.WriteFile(filename, []byte(content), 0644); err != nil { + http.Error(w, fmt.Sprintf("Error writing temporary file: %v", err), http.StatusInternalServerError) + return + } + + err = process(filename) + if err != nil { + http.Error(w, fmt.Sprintf("Error processing file: %v", err), http.StatusInternalServerError) + return + } + + watFilename := strings.TrimSuffix(filename, filepath.Ext(filename)) + ".wat" + watContent, err := ioutil.ReadFile(watFilename) + if err != nil { + http.Error(w, fmt.Sprintf("Error reading WAT file: %v", err), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write(watContent) +} + +func main() { + // Set up router and server + router := mux.NewRouter() + router.HandleFunc("/process", handleRequest).Methods("POST") + http.Handle("/", router) + + port := "8080" + fmt.Printf("Starting server on port %s\n", port) + if err := http.ListenAndServe(":"+port, nil); err != nil { + fmt.Fprintf(os.Stderr, "Error starting server: %v\n", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index d62ec0a..33e0c1f 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible github.com/google/uuid v1.1.2 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/hajimehoshi/oto v0.6.6 github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.11 // indirect diff --git a/go.sum b/go.sum index dbf96eb..f0f90df 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hajimehoshi/oto v0.6.6 h1:HYSZ8cYZqOL4iHugvbcfhNN2smiSOsBMaoSBi4nnWcw= github.com/hajimehoshi/oto v0.6.6/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=