From 2106ebde569730fe14f5b014061d1cfbd670810e Mon Sep 17 00:00:00 2001 From: Veikko Sariola Date: Tue, 8 Dec 2020 10:57:23 +0200 Subject: [PATCH] feat(sointu-cli): Support importing/exporting in YAML --- go.mod | 1 + go.sum | 3 +++ go4k/cmd/sointu-cli/main.go | 33 +++++++++++++++++++++++++++------ go4k/go4k.go | 6 +++--- go4k/song.go | 6 +++--- go4k/song_json_test.go | 2 +- go4k/song_test.go | 2 +- 7 files changed, 39 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 5efb099..dd93ec7 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.15 require ( gioui.org v0.0.0-20201106195654-dbc0796d0207 github.com/hajimehoshi/oto v0.6.6 + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) diff --git a/go.sum b/go.sum index 8b93c81..9626742 100644 --- a/go.sum +++ b/go.sum @@ -30,3 +30,6 @@ golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go4k/cmd/sointu-cli/main.go b/go4k/cmd/sointu-cli/main.go index 05375c9..92c1df8 100644 --- a/go4k/cmd/sointu-cli/main.go +++ b/go4k/cmd/sointu-cli/main.go @@ -11,6 +11,8 @@ import ( "path/filepath" "strings" + "gopkg.in/yaml.v3" + "github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu/go4k/audio/oto" "github.com/vsariola/sointu/go4k/bridge" @@ -23,6 +25,7 @@ func main() { play := flag.Bool("p", false, "Play the input songs.") asmOut := flag.Bool("a", false, "Output the song as .asm file, to standard output unless otherwise specified.") jsonOut := flag.Bool("j", false, "Output the song as .json file, to standard output unless otherwise specified.") + yamlOut := flag.Bool("y", false, "Output the song as .yml file, to standard output unless otherwise specified.") headerOut := flag.Bool("c", false, "Output .h C header file, to standard output unless otherwise specified.") exactLength := flag.Bool("e", false, "When outputting the C header file, calculate the exact length of song by rendering it once. Only useful when using SPEED opcodes.") rawOut := flag.Bool("r", false, "Output the rendered song as .raw stereo float32 buffer, to standard output unless otherwise specified.") @@ -34,7 +37,7 @@ func main() { flag.Usage() os.Exit(0) } - if !*asmOut && !*jsonOut && !*rawOut && !*headerOut && !*play { + if !*asmOut && !*jsonOut && !*rawOut && !*headerOut && !*play && !*yamlOut { *play = true // if the user gives nothing to output, then the default behaviour is just to play the file } needsRendering := *play || *exactLength || *rawOut @@ -78,12 +81,14 @@ func main() { return fmt.Errorf("Could not read file %v: %v", filename, err) } var song go4k.Song - if err := json.Unmarshal(inputBytes, &song); err != nil { - song2, err2 := go4k.ParseAsm(string(inputBytes)) - if err2 != nil { - return fmt.Errorf("The song could not be parsed as .json (%v) nor .asm (%v)", err, err2) + if errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil { + if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != nil { + song2, errAsm := go4k.ParseAsm(string(inputBytes)) + if errAsm != nil { + return fmt.Errorf("The song could not be parsed as .json (%v), .yml (%v) nor .asm (%v)", errJSON, errYaml, errAsm) + } + song = *song2 } - song = *song2 } var buffer []float32 if needsRendering { @@ -141,6 +146,15 @@ func main() { return fmt.Errorf("Error outputting JSON file: %v", err) } } + if *yamlOut { + yamlSong, err := yaml.Marshal(song) + if err != nil { + return fmt.Errorf("Could not marshal the song as yaml file: %v", err) + } + if err := output(".yml", yamlSong); err != nil { + return fmt.Errorf("Error outputting yaml file: %v", err) + } + } if *rawOut { buf := new(bytes.Buffer) err := binary.Write(buf, binary.LittleEndian, buffer) @@ -168,7 +182,14 @@ func main() { retval = 1 continue } + ymlfiles, err := filepath.Glob(filepath.Join(param, "*.yml")) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not glob the path %v for yml files: %v\n", param, err) + retval = 1 + continue + } files := append(asmfiles, jsonfiles...) + files = append(files, ymlfiles...) for _, file := range files { err := process(file) if err != nil { diff --git a/go4k/go4k.go b/go4k/go4k.go index 2dea9a5..5275fe5 100644 --- a/go4k/go4k.go +++ b/go4k/go4k.go @@ -8,7 +8,7 @@ import ( // Unit is e.g. a filter, oscillator, envelope and its parameters type Unit struct { Type string - Parameters map[string]int + Parameters map[string]int `yaml:",flow"` } const ( @@ -34,7 +34,7 @@ type SampleOffset struct { // Patch is simply a list of instruments used in a song type Patch struct { Instruments []Instrument - DelayTimes []int + DelayTimes []int `yaml:",flow"` SampleOffsets []SampleOffset } @@ -62,7 +62,7 @@ func (patch Patch) InstrumentForVoice(voice int) (int, error) { type Track struct { NumVoices int - Sequence []byte + Sequence []byte `yaml:",flow"` } type Synth interface { diff --git a/go4k/song.go b/go4k/song.go index 5f07143..9d43a14 100644 --- a/go4k/song.go +++ b/go4k/song.go @@ -7,11 +7,11 @@ import ( type Song struct { BPM int - Patterns [][]byte - Tracks []Track - Patch Patch Output16Bit bool Hold byte + Patterns [][]byte `yaml:",flow"` + Tracks []Track + Patch Patch } func (s *Song) PatternRows() int { diff --git a/go4k/song_json_test.go b/go4k/song_json_test.go index 46eb936..a380a3c 100644 --- a/go4k/song_json_test.go +++ b/go4k/song_json_test.go @@ -8,7 +8,7 @@ import ( "github.com/vsariola/sointu/go4k" ) -const expectedMarshaled = `{"BPM":100,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"Patch":{"Instruments":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"out","Parameters":{"gain":128,"stereo":1}}]}],"DelayTimes":[],"SampleOffsets":[]},"Output16Bit":false,"Hold":1}` +const expectedMarshaled = `{"BPM":100,"Output16Bit":false,"Hold":1,"Patterns":["QABEACAAAABLAE4AAAAAAA=="],"Tracks":[{"NumVoices":1,"Sequence":"AA=="}],"Patch":{"Instruments":[{"NumVoices":1,"Units":[{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":96,"detune":64,"flags":64,"gain":128,"phase":0,"shape":64,"stereo":0,"transpose":64}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"envelope","Parameters":{"attack":32,"decay":32,"gain":128,"release":64,"stereo":0,"sustain":64}},{"Type":"oscillator","Parameters":{"color":64,"detune":64,"flags":64,"gain":128,"phase":64,"shape":96,"stereo":0,"transpose":72}},{"Type":"mulp","Parameters":{"stereo":0}},{"Type":"out","Parameters":{"gain":128,"stereo":1}}]}],"DelayTimes":[],"SampleOffsets":[]}}` var testSong = go4k.Song{ BPM: 100, diff --git a/go4k/song_test.go b/go4k/song_test.go index 0550c2f..9d57562 100644 --- a/go4k/song_test.go +++ b/go4k/song_test.go @@ -37,7 +37,7 @@ func TestPlayer(t *testing.T) { SampleOffsets: []go4k.SampleOffset{}} patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}} tracks := []go4k.Track{go4k.Track{1, []byte{0}}} - song := go4k.Song{100, patterns, tracks, patch, false, 1} + song := go4k.Song{BPM: 100, Patterns: patterns, Tracks: tracks, Patch: patch, Output16Bit: false, Hold: 1} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("Compiling patch failed: %v", err)