feat(sointu-cli): Support importing/exporting in YAML

This commit is contained in:
Veikko Sariola 2020-12-08 10:57:23 +02:00
parent fa163b3884
commit 2106ebde56
7 changed files with 39 additions and 14 deletions

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.15
require ( require (
gioui.org v0.0.0-20201106195654-dbc0796d0207 gioui.org v0.0.0-20201106195654-dbc0796d0207
github.com/hajimehoshi/oto v0.6.6 github.com/hajimehoshi/oto v0.6.6
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
) )

3
go.sum
View File

@ -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/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/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= 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=

View File

@ -11,6 +11,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gopkg.in/yaml.v3"
"github.com/vsariola/sointu/go4k" "github.com/vsariola/sointu/go4k"
"github.com/vsariola/sointu/go4k/audio/oto" "github.com/vsariola/sointu/go4k/audio/oto"
"github.com/vsariola/sointu/go4k/bridge" "github.com/vsariola/sointu/go4k/bridge"
@ -23,6 +25,7 @@ func main() {
play := flag.Bool("p", false, "Play the input songs.") 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.") 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.") 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.") 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.") 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.") 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() flag.Usage()
os.Exit(0) 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 *play = true // if the user gives nothing to output, then the default behaviour is just to play the file
} }
needsRendering := *play || *exactLength || *rawOut needsRendering := *play || *exactLength || *rawOut
@ -78,12 +81,14 @@ func main() {
return fmt.Errorf("Could not read file %v: %v", filename, err) return fmt.Errorf("Could not read file %v: %v", filename, err)
} }
var song go4k.Song var song go4k.Song
if err := json.Unmarshal(inputBytes, &song); err != nil { if errJSON := json.Unmarshal(inputBytes, &song); errJSON != nil {
song2, err2 := go4k.ParseAsm(string(inputBytes)) if errYaml := yaml.Unmarshal(inputBytes, &song); errYaml != nil {
if err2 != nil { song2, errAsm := go4k.ParseAsm(string(inputBytes))
return fmt.Errorf("The song could not be parsed as .json (%v) nor .asm (%v)", err, err2) 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 var buffer []float32
if needsRendering { if needsRendering {
@ -141,6 +146,15 @@ func main() {
return fmt.Errorf("Error outputting JSON file: %v", err) 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 { if *rawOut {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err := binary.Write(buf, binary.LittleEndian, buffer) err := binary.Write(buf, binary.LittleEndian, buffer)
@ -168,7 +182,14 @@ func main() {
retval = 1 retval = 1
continue 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(asmfiles, jsonfiles...)
files = append(files, ymlfiles...)
for _, file := range files { for _, file := range files {
err := process(file) err := process(file)
if err != nil { if err != nil {

View File

@ -8,7 +8,7 @@ import (
// Unit is e.g. a filter, oscillator, envelope and its parameters // Unit is e.g. a filter, oscillator, envelope and its parameters
type Unit struct { type Unit struct {
Type string Type string
Parameters map[string]int Parameters map[string]int `yaml:",flow"`
} }
const ( const (
@ -34,7 +34,7 @@ type SampleOffset struct {
// Patch is simply a list of instruments used in a song // Patch is simply a list of instruments used in a song
type Patch struct { type Patch struct {
Instruments []Instrument Instruments []Instrument
DelayTimes []int DelayTimes []int `yaml:",flow"`
SampleOffsets []SampleOffset SampleOffsets []SampleOffset
} }
@ -62,7 +62,7 @@ func (patch Patch) InstrumentForVoice(voice int) (int, error) {
type Track struct { type Track struct {
NumVoices int NumVoices int
Sequence []byte Sequence []byte `yaml:",flow"`
} }
type Synth interface { type Synth interface {

View File

@ -7,11 +7,11 @@ import (
type Song struct { type Song struct {
BPM int BPM int
Patterns [][]byte
Tracks []Track
Patch Patch
Output16Bit bool Output16Bit bool
Hold byte Hold byte
Patterns [][]byte `yaml:",flow"`
Tracks []Track
Patch Patch
} }
func (s *Song) PatternRows() int { func (s *Song) PatternRows() int {

View File

@ -8,7 +8,7 @@ import (
"github.com/vsariola/sointu/go4k" "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{ var testSong = go4k.Song{
BPM: 100, BPM: 100,

View File

@ -37,7 +37,7 @@ func TestPlayer(t *testing.T) {
SampleOffsets: []go4k.SampleOffset{}} SampleOffsets: []go4k.SampleOffset{}}
patterns := [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}} 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}}} 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) synth, err := bridge.Synth(patch)
if err != nil { if err != nil {
t.Fatalf("Compiling patch failed: %v", err) t.Fatalf("Compiling patch failed: %v", err)