From adcf3ebce82636a385b8bc53ca1434f303ce5d5d Mon Sep 17 00:00:00 2001 From: vsariola <5684185+vsariola@users.noreply.github.com> Date: Tue, 23 Feb 2021 23:55:42 +0200 Subject: [PATCH] feat(sointu, tracker,...): restructure domain & tracker models send targets are now by ID and Song has "Score" part, which is the notes for it. also, moved the model part separate of the actual gioui dependend stuff. sorry to my future self about the code bomb; ended up too far and did not find an easy way to rewrite the history to make the steps smaller, so in the end, just squashed everything. --- audio.go | 11 + bridge/bridge.go | 4 +- bridge/bridge_test.go | 105 ++- cmd/sointu-compile/main.go | 3 + cmd/sointu-generate/main.go | 2 +- cmd/sointu-track/main.go | 19 +- compiler/compiler.go | 2 +- compiler/encoded_patch.go | 91 ++- compiler/featureset.go | 41 +- compiler/patterns.go | 14 +- compiler/patterns_test.go | 54 +- compiler/song_macros.go | 6 +- instrument.go | 16 + patch.go | 150 ++++ score.go | 35 + sointu.go | 587 ---------------- song.go | 61 ++ synth.go | 85 +++ templates/amd64-386/player.asm | 6 +- templates/amd64-386/player.h | 4 +- templates/amd64-386/sinks.asm | 2 +- templates/wasm/player.wat | 2 +- templates/wasm/sinks.wat | 4 +- tests/test_add.yml | 33 +- tests/test_add_stereo.yml | 53 +- tests/test_addp.yml | 45 +- tests/test_addp_stereo.yml | 41 +- tests/test_aux.yml | 33 +- tests/test_aux_stereo.yml | 57 +- tests/test_chords.yml | 53 +- tests/test_clip.yml | 53 +- tests/test_clip_stereo.yml | 53 +- tests/test_compressor.yml | 104 +-- tests/test_compressor_stereo.yml | 104 +-- tests/test_crush.yml | 53 +- tests/test_crush_stereo.yml | 49 +- tests/test_delay.yml | 43 +- tests/test_delay_dampmod.yml | 52 +- tests/test_delay_drymod.yml | 52 +- tests/test_delay_feedbackmod.yml | 52 +- tests/test_delay_flanger.yml | 52 +- tests/test_delay_notetracking.yml | 59 +- tests/test_delay_pregainmod.yml | 52 +- tests/test_delay_reverb.yml | 43 +- tests/test_delay_stereo.yml | 43 +- tests/test_distort.yml | 37 +- tests/test_distort_mod.yml | 51 +- tests/test_distort_stereo.yml | 29 +- tests/test_envelope.yml | 29 +- tests/test_envelope_mod.yml | 51 +- tests/test_envelope_stereo.yml | 25 +- tests/test_filter_band.yml | 41 +- tests/test_filter_freqmod.yml | 50 +- tests/test_filter_high.yml | 41 +- tests/test_filter_low.yml | 41 +- tests/test_filter_peak.yml | 41 +- tests/test_filter_resmod.yml | 50 +- tests/test_filter_stereo.yml | 41 +- tests/test_gain.yml | 33 +- tests/test_gain_stereo.yml | 37 +- tests/test_hold.yml | 37 +- tests/test_hold_mod.yml | 51 +- tests/test_hold_stereo.yml | 33 +- tests/test_in.yml | 53 +- tests/test_in_stereo.yml | 49 +- tests/test_invgain.yml | 37 +- tests/test_invgain_stereo.yml | 33 +- tests/test_loadnote.yml | 29 +- tests/test_loadnote_stereo.yml | 25 +- tests/test_loadval.yml | 29 +- tests/test_loadval_stereo.yml | 25 +- tests/test_mul.yml | 33 +- tests/test_mul_stereo.yml | 53 +- tests/test_mulp.yml | 45 +- tests/test_mulp_stereo.yml | 41 +- tests/test_multiple_instruments.yml | 51 +- tests/test_noise.yml | 45 +- tests/test_noise_stereo.yml | 37 +- tests/test_oscillat_colormod.yml | 46 +- tests/test_oscillat_detunemod.yml | 46 +- tests/test_oscillat_gainmod.yml | 46 +- tests/test_oscillat_gate.yml | 41 +- tests/test_oscillat_lfo.yml | 41 +- tests/test_oscillat_phasemod.yml | 46 +- tests/test_oscillat_pulse.yml | 41 +- tests/test_oscillat_sample.yml | 75 +- tests/test_oscillat_sample_stereo.yml | 67 +- tests/test_oscillat_shapemod.yml | 46 +- tests/test_oscillat_sine.yml | 41 +- tests/test_oscillat_stereo.yml | 37 +- tests/test_oscillat_transposemod.yml | 46 +- tests/test_oscillat_trisaw.yml | 41 +- tests/test_oscillat_unison.yml | 37 +- tests/test_oscillat_unison_stereo.yml | 33 +- tests/test_outaux.yml | 49 +- tests/test_outaux_stereo.yml | 53 +- tests/test_panning.yml | 29 +- tests/test_panning_stereo.yml | 33 +- tests/test_polyphony.yml | 69 +- tests/test_pop.yml | 37 +- tests/test_pop_stereo.yml | 41 +- tests/test_push.yml | 37 +- tests/test_push_stereo.yml | 41 +- tests/test_receive.yml | 51 +- tests/test_receive_stereo.yml | 46 +- tests/test_send.yml | 51 +- tests/test_send_global.yml | 63 +- tests/test_send_global_stereo.yml | 63 +- tests/test_send_stereo.yml | 50 +- tests/test_speed.yml | 59 +- tests/test_xch.yml | 33 +- tests/test_xch_stereo.yml | 49 +- track.go | 25 + tracker/defaultsong.go | 38 +- tracker/{ => gioui}/alert.go | 2 +- tracker/{ => gioui}/draglist.go | 2 +- tracker/{ => gioui}/files.go | 8 +- tracker/{ => gioui}/iconcache.go | 2 +- tracker/{ => gioui}/instruments.go | 65 +- tracker/gioui/keyevent.go | 454 +++++++++++++ tracker/{ => gioui}/label.go | 2 +- tracker/{ => gioui}/layout.go | 7 +- tracker/{ => gioui}/menu.go | 2 +- tracker/{ => gioui}/numericupdown.go | 2 +- tracker/gioui/parameter.go | 205 ++++++ tracker/{ => gioui}/patterns.go | 39 +- tracker/{ => gioui}/popup.go | 2 +- tracker/gioui/rowmarkers.go | 72 ++ tracker/gioui/run.go | 69 ++ tracker/{ => gioui}/scrollbar.go | 2 +- tracker/{ => gioui}/songpanel.go | 28 +- tracker/{ => gioui}/split.go | 2 +- tracker/{ => gioui}/surface.go | 2 +- tracker/{ => gioui}/theme.go | 2 +- tracker/{ => gioui}/track.go | 130 ++-- tracker/gioui/tracker.go | 172 +++++ tracker/gioui/uniteditor.go | 146 ++++ tracker/gioui/vumeter.go | 47 ++ tracker/gmdlsentries.go | 2 +- tracker/gmdlsentry.go | 6 +- tracker/keyevent.go | 448 ------------ tracker/model.go | 939 ++++++++++++++++++++++++++ tracker/music.go | 6 +- tracker/player.go | 274 ++++++++ tracker/rowmarkers.go | 61 -- tracker/run.go | 39 -- tracker/sequencer.go | 271 -------- tracker/songpoint.go | 52 +- tracker/tracker.go | 823 ---------------------- tracker/undo.go | 37 - tracker/uniteditor.go | 238 ------- tracker/vuanalyzer.go | 60 ++ tracker/vumeter.go | 78 --- unit.go | 55 ++ unittype.go | 148 ++++ 155 files changed, 5520 insertions(+), 4914 deletions(-) create mode 100644 audio.go create mode 100644 instrument.go create mode 100644 patch.go create mode 100644 score.go delete mode 100644 sointu.go create mode 100644 song.go create mode 100644 synth.go create mode 100644 track.go rename tracker/{ => gioui}/alert.go (99%) rename tracker/{ => gioui}/draglist.go (99%) rename tracker/{ => gioui}/files.go (89%) rename tracker/{ => gioui}/iconcache.go (96%) rename tracker/{ => gioui}/instruments.go (80%) create mode 100644 tracker/gioui/keyevent.go rename tracker/{ => gioui}/label.go (98%) rename tracker/{ => gioui}/layout.go (73%) rename tracker/{ => gioui}/menu.go (99%) rename tracker/{ => gioui}/numericupdown.go (99%) create mode 100644 tracker/gioui/parameter.go rename tracker/{ => gioui}/patterns.go (58%) rename tracker/{ => gioui}/popup.go (99%) create mode 100644 tracker/gioui/rowmarkers.go create mode 100644 tracker/gioui/run.go rename tracker/{ => gioui}/scrollbar.go (99%) rename tracker/{ => gioui}/songpanel.go (91%) rename tracker/{ => gioui}/split.go (99%) rename tracker/{ => gioui}/surface.go (98%) rename tracker/{ => gioui}/theme.go (99%) rename tracker/{ => gioui}/track.go (68%) create mode 100644 tracker/gioui/tracker.go create mode 100644 tracker/gioui/uniteditor.go create mode 100644 tracker/gioui/vumeter.go delete mode 100644 tracker/keyevent.go create mode 100644 tracker/model.go create mode 100644 tracker/player.go delete mode 100644 tracker/rowmarkers.go delete mode 100644 tracker/run.go delete mode 100644 tracker/sequencer.go delete mode 100644 tracker/tracker.go delete mode 100644 tracker/undo.go delete mode 100644 tracker/uniteditor.go create mode 100644 tracker/vuanalyzer.go delete mode 100644 tracker/vumeter.go create mode 100644 unit.go create mode 100644 unittype.go diff --git a/audio.go b/audio.go new file mode 100644 index 0000000..f26076c --- /dev/null +++ b/audio.go @@ -0,0 +1,11 @@ +package sointu + +type AudioSink interface { + WriteAudio(buffer []float32) error + Close() error +} + +type AudioContext interface { + Output() AudioSink + Close() error +} diff --git a/bridge/bridge.go b/bridge/bridge.go index 4cf210b..9865c78 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -23,7 +23,7 @@ func (s BridgeService) Compile(patch sointu.Patch) (sointu.Synth, error) { func Synth(patch sointu.Patch) (*C.Synth, error) { s := new(C.Synth) - comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{}) + comPatch, err := compiler.Encode(patch, compiler.AllFeatures{}) if err != nil { return nil, fmt.Errorf("error compiling patch: %v", err) } @@ -102,7 +102,7 @@ func (s *C.Synth) Release(voice int) { // Update func (s *C.Synth) Update(patch sointu.Patch) error { - comPatch, err := compiler.Encode(&patch, compiler.AllFeatures{}) + comPatch, err := compiler.Encode(patch, compiler.AllFeatures{}) if err != nil { return fmt.Errorf("error compiling patch: %v", err) } diff --git a/bridge/bridge_test.go b/bridge/bridge_test.go index da996c4..0bb8808 100644 --- a/bridge/bridge_test.go +++ b/bridge/bridge_test.go @@ -29,18 +29,17 @@ const su_max_samples = SAMPLES_PER_ROW * TOTAL_ROWS // const bufsize = su_max_samples * 2 func TestOscillatSine(t *testing.T) { - patch := sointu.Patch{ - Instruments: []sointu.Instrument{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ - sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, - sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}}, - sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, - sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, - sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}}, - sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, - sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, - }}}} - tracks := []sointu.Track{{NumVoices: 1, Sequence: []byte{0}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}} - song := sointu.Song{BPM: 100, RowsPerPattern: 16, RowsPerBeat: 4, Tracks: tracks, Patch: patch} + patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, + sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 96, "shape": 64, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}}, + sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 32, "decay": 32, "sustain": 64, "release": 64, "gain": 128}}, + sointu.Unit{Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 72, "detune": 64, "phase": 64, "color": 64, "shape": 96, "gain": 128, "type": sointu.Sine, "lfo": 0, "unison": 0}}, + sointu.Unit{Type: "mulp", Parameters: map[string]int{"stereo": 0}}, + sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, + }}} + tracks := []sointu.Track{{NumVoices: 1, Order: []int{0}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}}}} + song := sointu.Song{BPM: 100, RowsPerBeat: 4, Score: sointu.Score{RowsPerPattern: 16, Length: 1, Tracks: tracks}, Patch: patch} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("Compiling patch failed: %v", err) @@ -53,13 +52,11 @@ func TestOscillatSine(t *testing.T) { } func TestRenderSamples(t *testing.T) { - patch := sointu.Patch{ - Instruments: []sointu.Instrument{ - sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ - sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, - sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, - sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, - }}}} + patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, + sointu.Unit{Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 95, "decay": 64, "sustain": 64, "release": 80, "gain": 128}}, + sointu.Unit{Type: "out", Parameters: map[string]int{"stereo": 1, "gain": 128}}, + }}} synth, err := bridge.Synth(patch) if err != nil { @@ -107,7 +104,7 @@ func TestAllRegressionTests(t *testing.T) { t.Fatalf("Compiling patch failed: %v", err) } buffer, err := sointu.Play(synth, song) - buffer = buffer[:song.TotalRows()*song.SamplesPerRow()*2] // extend to the nominal length always. + buffer = buffer[:song.Score.LengthInRows()*song.SamplesPerRow()*2] // extend to the nominal length always. if err != nil { t.Fatalf("Play failed: %v", err) } @@ -138,11 +135,9 @@ func TestAllRegressionTests(t *testing.T) { } func TestStackUnderflow(t *testing.T) { - patch := sointu.Patch{ - Instruments: []sointu.Instrument{ - sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - }}}} + patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + }}} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("bridge compile error: %v", err) @@ -156,10 +151,9 @@ func TestStackUnderflow(t *testing.T) { func TestStackBalancing(t *testing.T) { patch := sointu.Patch{ - Instruments: []sointu.Instrument{ - sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ - sointu.Unit{Type: "push", Parameters: map[string]int{}}, - }}}} + sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ + sointu.Unit{Type: "push", Parameters: map[string]int{}}, + }}} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("bridge compile error: %v", err) @@ -173,27 +167,26 @@ func TestStackBalancing(t *testing.T) { func TestStackOverflow(t *testing.T) { patch := sointu.Patch{ - Instruments: []sointu.Instrument{ - sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - }}}} + sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + }}} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("bridge compile error: %v", err) @@ -206,13 +199,11 @@ func TestStackOverflow(t *testing.T) { } func TestDivideByZero(t *testing.T) { - patch := sointu.Patch{ - Instruments: []sointu.Instrument{ - sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ - sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, - sointu.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}}, - sointu.Unit{Type: "pop", Parameters: map[string]int{}}, - }}}} + patch := sointu.Patch{sointu.Instrument{NumVoices: 1, Units: []sointu.Unit{ + sointu.Unit{Type: "loadval", Parameters: map[string]int{"value": 128}}, + sointu.Unit{Type: "invgain", Parameters: map[string]int{"invgain": 0}}, + sointu.Unit{Type: "pop", Parameters: map[string]int{}}, + }}} synth, err := bridge.Synth(patch) if err != nil { t.Fatalf("bridge compile error: %v", err) diff --git a/cmd/sointu-compile/main.go b/cmd/sointu-compile/main.go index baa2d61..666f2ad 100644 --- a/cmd/sointu-compile/main.go +++ b/cmd/sointu-compile/main.go @@ -122,6 +122,9 @@ func main() { if song.RowsPerBeat == 0 { song.RowsPerBeat = 4 } + if song.Score.Length == 0 { + song.Score.Length = len(song.Score.Tracks[0].Patterns) + } var compiledPlayer map[string]string if compile { var err error diff --git a/cmd/sointu-generate/main.go b/cmd/sointu-generate/main.go index d8b699d..b377cf8 100644 --- a/cmd/sointu-generate/main.go +++ b/cmd/sointu-generate/main.go @@ -125,7 +125,7 @@ func main() { fmt.Fprintln(outputFile, "// Code generated by go generate; DO NOT EDIT.") fmt.Fprintln(outputFile, "package tracker") fmt.Fprintln(outputFile, "") - fmt.Fprintln(outputFile, "var gmDlsEntries = []GmDlsEntry{") + fmt.Fprintln(outputFile, "var GmDlsEntries = []GmDlsEntry{") readChunk(0, inputFile, &gmDlsVisitor{outputFile: outputFile}) fmt.Fprintln(outputFile, "}") } diff --git a/cmd/sointu-track/main.go b/cmd/sointu-track/main.go index 14c463f..0c7607c 100644 --- a/cmd/sointu-track/main.go +++ b/cmd/sointu-track/main.go @@ -4,11 +4,9 @@ import ( "fmt" "os" - "gioui.org/app" - "gioui.org/unit" "github.com/vsariola/sointu/bridge" "github.com/vsariola/sointu/oto" - "github.com/vsariola/sointu/tracker" + "github.com/vsariola/sointu/tracker/gioui" ) func main() { @@ -19,18 +17,5 @@ func main() { } defer audioContext.Close() synthService := bridge.BridgeService{} - go func() { - w := app.NewWindow( - app.Size(unit.Dp(800), unit.Dp(600)), - app.Title("Sointu Tracker"), - ) - t := tracker.New(audioContext, synthService) - defer t.Close() - if err := t.Run(w); err != nil { - fmt.Println(err) - os.Exit(1) - } - os.Exit(0) - }() - app.Main() + gioui.Main(audioContext, synthService) } diff --git a/compiler/compiler.go b/compiler/compiler.go index 70a63f9..42d4b31 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -82,7 +82,7 @@ func (com *Compiler) Song(song *sointu.Song) (map[string]string, error) { } features := NecessaryFeaturesFor(song.Patch) retmap := map[string]string{} - encodedPatch, err := Encode(&song.Patch, features) + encodedPatch, err := Encode(song.Patch, features) if err != nil { return nil, fmt.Errorf(`could not encode patch: %v`, err) } diff --git a/compiler/encoded_patch.go b/compiler/encoded_patch.go index cb6953f..67fc343 100644 --- a/compiler/encoded_patch.go +++ b/compiler/encoded_patch.go @@ -22,16 +22,22 @@ type SampleOffset struct { LoopLength uint16 } -func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) { +func Encode(patch sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) { var c EncodedPatch sampleOffsetMap := map[SampleOffset]int{} - for instrIndex, instr := range patch.Instruments { + globalAddrs := map[int]uint16{} + globalFixups := map[int]([]int){} + voiceNo := 0 + for instrIndex, instr := range patch { if len(instr.Units) > 63 { return nil, errors.New("An instrument can have a maximum of 63 units") } if instr.NumVoices < 1 { return nil, errors.New("Each instrument must have at least 1 voice") } + localAddrs := map[int]uint16{} + localFixups := map[int]([]int){} + localUnitNo := 0 for _, unit := range instr.Units { if unit.Type == "" { // empty units are just ignored & skipped continue @@ -113,44 +119,62 @@ func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) { } values = append(values, byte(flags)) } else if unit.Type == "send" { + targetID := unit.Parameters["target"] + targetInstrIndex, _, err := patch.FindSendTarget(targetID) targetVoice := unit.Parameters["voice"] - var targetInstrument int - if targetVoice == 0 { - targetInstrument = instrIndex - } else { - var err error - targetInstrument, err = patch.InstrumentForVoice(targetVoice - 1) - if err != nil { - return nil, fmt.Errorf("send targeted a voice %v, which out of the range of instruments", targetVoice-1) - } - } - origTarget := unit.Parameters["unit"] - targetUnit := origTarget - for k := 0; k < origTarget; k++ { - units := patch.Instruments[targetInstrument].Units - if k >= len(units) { - break - } - if units[k].Type == "" { - targetUnit-- - } - } - address := ((targetUnit + 1) << 4) + unit.Parameters["port"] // each unit is 16 dwords, 8 workspace followed by 8 ports. +1 is for skipping the note/release/inputs - if unit.Parameters["voice"] > 0 { - address += 0x8000 + 16 + (unit.Parameters["voice"]-1)*1024 // global send, +16 is for skipping the out/aux ports - } + var addr uint16 = uint16(unit.Parameters["port"]) & 7 if unit.Parameters["sendpop"] == 1 { - address += 0x8 + addr += 0x8 } - values = append(values, byte(address&255), byte(address>>8)) + + if err == nil { + // local send is only possible if targetVoice is "auto" (0) and + // the targeted unit is in the same instrument as send + if targetInstrIndex == instrIndex && targetVoice == 0 { + if v, ok := localAddrs[targetID]; ok { + addr += v + } else { + localFixups[targetID] = append(localFixups[targetID], len(c.Values)+len(values)) + } + } else { + addr += 0x8000 + if targetVoice > 0 { // "auto" (0) means for global send that it targets voice 0 of that instrument + addr += uint16((targetVoice - 1) * 0x400) + } + if v, ok := globalAddrs[targetID]; ok { + addr += v + } else { + globalFixups[targetID] = append(globalFixups[targetID], len(c.Values)+len(values)) + } + } + } else { + // if no target will be found, the send will trash some of + // the last values of the last port of the last voice, which + // is unlikely to cause issues. We still honor the POP bit. + addr &= 0x8 + addr |= 0xFFF7 + } + values = append(values, byte(addr&255), byte(addr>>8)) } else if unit.Type == "delay" { countTrack := (unit.Parameters["count"] << 1) - 1 + unit.Parameters["notetracking"] // 1 means no note tracking and 1 delay, 2 means notetracking with 1 delay, 3 means no note tracking and 2 delays etc. values = append(values, byte(unit.Parameters["delay"]), byte(countTrack)) } c.Commands = append(c.Commands, byte(opcode+unit.Parameters["stereo"])) c.Values = append(c.Values, values...) + if unit.ID != 0 { + localAddr := uint16((localUnitNo + 1) << 4) + fixUp(c.Values, localFixups[unit.ID], localAddr) + localFixups[unit.ID] = nil + localAddrs[unit.ID] = localAddr + globalAddr := localAddr + 16 + uint16(voiceNo)*1024 + fixUp(c.Values, globalFixups[unit.ID], globalAddr) + globalFixups[unit.ID] = nil + globalAddrs[unit.ID] = globalAddr + } + localUnitNo++ // a command in command stream means the wrkspace addr gets also increased } c.Commands = append(c.Commands, byte(0)) // advance + voiceNo += instr.NumVoices c.NumVoices += uint32(instr.NumVoices) for k := 0; k < instr.NumVoices-1; k++ { c.PolyphonyBitmask = (c.PolyphonyBitmask << 1) + 1 @@ -163,3 +187,12 @@ func Encode(patch *sointu.Patch, featureSet FeatureSet) (*EncodedPatch, error) { return &c, nil } + +func fixUp(values []byte, positions []int, delta uint16) { + for _, pos := range positions { + orig := (uint16(values[pos+1]) << 8) + uint16(values[pos]) + new := orig + delta + values[pos] = byte(new & 255) + values[pos+1] = byte(new >> 8) + } +} diff --git a/compiler/featureset.go b/compiler/featureset.go index 523ed50..b9425b9 100644 --- a/compiler/featureset.go +++ b/compiler/featureset.go @@ -17,6 +17,7 @@ type FeatureSet interface { SupportsParamValueOtherThan(unitType string, paramName string, value int) bool SupportsModulation(unitType string, paramName string) bool SupportsPolyphony() bool + SupportsGlobalSend() bool } type Instruction struct { @@ -58,6 +59,10 @@ func (_ AllFeatures) SupportsPolyphony() bool { return true } +func (_ AllFeatures) SupportsGlobalSend() bool { + return true +} + func (_ AllFeatures) Opcode(unitType string) (int, bool) { code, ok := allOpcodes[unitType] return code, ok @@ -114,12 +119,13 @@ type NecessaryFeatures struct { instructions []string supportsParamValue map[paramKey](map[int]bool) supportsModulation map[paramKey]bool + globalSend bool polyphony bool } func NecessaryFeaturesFor(patch sointu.Patch) NecessaryFeatures { features := NecessaryFeatures{opcodes: map[string]int{}, supportsParamValue: map[paramKey](map[int]bool){}, supportsModulation: map[paramKey]bool{}} - for instrNo, instrument := range patch.Instruments { + for instrIndex, instrument := range patch { for _, unit := range instrument.Units { if _, ok := features.opcodes[unit.Type]; !ok { features.instructions = append(features.instructions, unit.Type) @@ -133,27 +139,20 @@ func NecessaryFeaturesFor(patch sointu.Patch) NecessaryFeatures { features.supportsParamValue[key][v] = true } if unit.Type == "send" { - targetInstrument := instrNo - if unit.Parameters["voice"] > 0 { - v, err := patch.InstrumentForVoice(unit.Parameters["voice"] - 1) - if err != nil { - continue - } - targetInstrument = v + targetInstrIndex, targetUnitIndex, err := patch.FindSendTarget(unit.Parameters["target"]) + if err != nil { + continue } - if unit.Parameters["unit"] < 0 || unit.Parameters["unit"] >= len(patch.Instruments[targetInstrument].Units) { - continue // send is modulating outside the range of the target instrument; probably a bug in patch, but at least it's not modulating the uniType we're after + targetUnit := patch[targetInstrIndex].Units[targetUnitIndex] + portList := sointu.Ports[targetUnit.Type] + portIndex := unit.Parameters["port"] + if portIndex < 0 || portIndex >= len(portList) { + continue } - targetUnit := patch.Instruments[targetInstrument].Units[unit.Parameters["unit"]] - modulatedPortNo := 0 - for _, v := range sointu.UnitTypes[targetUnit.Type] { - if v.CanModulate { - if modulatedPortNo == unit.Parameters["port"] { - features.supportsModulation[paramKey{targetUnit.Type, v.Name}] = true - } - modulatedPortNo++ - } + if targetInstrIndex != instrIndex || unit.Parameters["voice"] > 0 { + features.globalSend = true } + features.supportsModulation[paramKey{targetUnit.Type, portList[portIndex]}] = true } } if instrument.NumVoices > 1 { @@ -204,3 +203,7 @@ func (n NecessaryFeatures) InputNumber(unitType string, paramName string) int { func (_ NecessaryFeatures) TransformCount(unitType string) int { return allTransformCounts[unitType] } + +func (n NecessaryFeatures) SupportsGlobalSend() bool { + return n.globalSend +} diff --git a/compiler/patterns.go b/compiler/patterns.go index a877ac7..68a0447 100644 --- a/compiler/patterns.go +++ b/compiler/patterns.go @@ -32,11 +32,17 @@ func fixPatternLength(patterns [][]byte, fixedLength int) [][]int { func flattenSequence(patterns [][]int, sequence []int) []int { sumLen := 0 for _, patIndex := range sequence { + if patIndex < 0 || patIndex >= len(patterns) { + continue + } sumLen += len(patterns[patIndex]) } notes := make([]int, sumLen) window := notes for _, patIndex := range sequence { + if patIndex < 0 || patIndex >= len(patterns) { + continue + } elementsCopied := copy(window, patterns[patIndex]) window = window[elementsCopied:] } @@ -167,12 +173,12 @@ func bytesToInts(array []byte) []int { } func ConstructPatterns(song *sointu.Song) ([][]byte, [][]byte, error) { - patLength := song.RowsPerPattern - sequences := make([][]byte, len(song.Tracks)) + patLength := song.Score.RowsPerPattern + sequences := make([][]byte, len(song.Score.Tracks)) var patterns [][]int - for i, t := range song.Tracks { + for i, t := range song.Score.Tracks { fixed := fixPatternLength(t.Patterns, patLength) - flat := flattenSequence(fixed, bytesToInts(t.Sequence)) + flat := flattenSequence(fixed, t.Order) dontCares := markDontCares(flat) // TODO: we could give the user the possibility to use another length during encoding that during composing chunks := splitSequence(dontCares, patLength) diff --git a/compiler/patterns_test.go b/compiler/patterns_test.go index 425c355..f808c69 100644 --- a/compiler/patterns_test.go +++ b/compiler/patterns_test.go @@ -10,14 +10,16 @@ import ( func TestPatternReusing(t *testing.T) { song := sointu.Song{ - RowsPerPattern: 8, - Tracks: []sointu.Track{{ - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {72, 0, 0, 0, 0, 0, 0, 0}}, - Sequence: []byte{0, 1}, - }, { - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {84, 0, 0, 0, 0, 0, 0, 0}}, - Sequence: []byte{0, 1}, - }}, + Score: sointu.Score{ + RowsPerPattern: 8, + Tracks: []sointu.Track{{ + Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {72, 0, 0, 0, 0, 0, 0, 0}}, + Order: []int{0, 1}, + }, { + Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {84, 0, 0, 0, 0, 0, 0, 0}}, + Order: []int{0, 1}, + }}, + }, } patterns, sequences, err := compiler.ConstructPatterns(&song) if err != nil { @@ -35,14 +37,15 @@ func TestPatternReusing(t *testing.T) { func TestUnnecessaryHolds(t *testing.T) { song := sointu.Song{ - RowsPerPattern: 8, - Tracks: []sointu.Track{{ - Patterns: [][]byte{{64, 1, 1, 1, 0, 1, 0, 0}, {72, 0, 1, 0, 1, 0, 0, 0}}, - Sequence: []byte{0, 1}, - }, { - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 1, 0}, {84, 0, 0, 0, 1, 1, 0, 0}}, - Sequence: []byte{0, 1}, - }}, + Score: sointu.Score{ + RowsPerPattern: 8, + Tracks: []sointu.Track{{ + Patterns: [][]byte{{64, 1, 1, 1, 0, 1, 0, 0}, {72, 0, 1, 0, 1, 0, 0, 0}}, + Order: []int{0, 1}, + }, { + Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 1, 0}, {84, 0, 0, 0, 1, 1, 0, 0}}, + Order: []int{0, 1}, + }}}, } patterns, sequences, err := compiler.ConstructPatterns(&song) if err != nil { @@ -60,14 +63,17 @@ func TestUnnecessaryHolds(t *testing.T) { func TestDontCares(t *testing.T) { song := sointu.Song{ - RowsPerPattern: 8, - Tracks: []sointu.Track{{ - Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}}, - Sequence: []byte{0, 1}, - }, { - Patterns: [][]byte{{64, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 0, 0, 0, 0, 0}}, - Sequence: []byte{0, 1}, - }}, + Score: sointu.Score{ + Length: 2, + RowsPerPattern: 8, + Tracks: []sointu.Track{{ + Patterns: [][]byte{{64, 1, 1, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}}, + Order: []int{0, 1}, + }, { + Patterns: [][]byte{{64, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 0, 0, 0, 0, 0}}, + Order: []int{0, 1}, + }}, + }, } patterns, sequences, err := compiler.ConstructPatterns(&song) if err != nil { diff --git a/compiler/song_macros.go b/compiler/song_macros.go index 8472a1e..c644d06 100644 --- a/compiler/song_macros.go +++ b/compiler/song_macros.go @@ -13,10 +13,10 @@ type SongMacros struct { } func NewSongMacros(s *sointu.Song) *SongMacros { - maxSamples := s.SamplesPerRow() * s.TotalRows() + maxSamples := s.SamplesPerRow() * s.Score.LengthInRows() p := SongMacros{Song: s, MaxSamples: maxSamples} trackVoiceNumber := 0 - for _, t := range s.Tracks { + for _, t := range s.Score.Tracks { for b := 0; b < t.NumVoices-1; b++ { p.VoiceTrackBitmask += 1 << trackVoiceNumber trackVoiceNumber++ @@ -28,7 +28,7 @@ func NewSongMacros(s *sointu.Song) *SongMacros { func (p *SongMacros) NumDelayLines() string { total := 0 - for _, instr := range p.Song.Patch.Instruments { + for _, instr := range p.Song.Patch { for _, unit := range instr.Units { if unit.Type == "delay" { total += unit.Parameters["count"] * (1 + unit.Parameters["stereo"]) diff --git a/instrument.go b/instrument.go new file mode 100644 index 0000000..b6f1133 --- /dev/null +++ b/instrument.go @@ -0,0 +1,16 @@ +package sointu + +// Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument +type Instrument struct { + Name string `yaml:",omitempty"` + NumVoices int + Units []Unit +} + +func (instr *Instrument) Copy() Instrument { + units := make([]Unit, len(instr.Units)) + for i, u := range instr.Units { + units[i] = u.Copy() + } + return Instrument{Name: instr.Name, NumVoices: instr.NumVoices, Units: units} +} diff --git a/patch.go b/patch.go new file mode 100644 index 0000000..dec55c5 --- /dev/null +++ b/patch.go @@ -0,0 +1,150 @@ +package sointu + +import ( + "errors" + "fmt" + "math" +) + +// Patch is simply a list of instruments used in a song +type Patch []Instrument + +func (p Patch) Copy() Patch { + instruments := make([]Instrument, len(p)) + for i, instr := range p { + instruments[i] = instr.Copy() + } + return instruments +} + +func (p Patch) NumVoices() int { + ret := 0 + for _, i := range p { + ret += i.NumVoices + } + return ret +} + +func (p Patch) FirstVoiceForInstrument(instrIndex int) int { + ret := 0 + for _, t := range p[:instrIndex] { + ret += t.NumVoices + } + return ret +} + +func (p Patch) InstrumentForVoice(voice int) (int, error) { + if voice < 0 { + return 0, errors.New("voice cannot be negative") + } + for i, instr := range p { + if voice < instr.NumVoices { + return i, nil + } + voice -= instr.NumVoices + } + return 0, errors.New("voice number is beyond the total voices of an instrument") +} + +func (p Patch) FindSendTarget(id int) (int, int, error) { + if id == 0 { + return 0, 0, errors.New("send targets unit id 0") + } + for i, instr := range p { + for u, unit := range instr.Units { + if unit.ID == id { + return i, u, nil + } + } + } + return 0, 0, fmt.Errorf("send targets an unit with id %v, could not find a unit with such an ID in the patch", id) +} + +func (p Patch) ParamHintString(instrIndex, unitIndex int, param string) string { + if instrIndex < 0 || instrIndex >= len(p) { + return "" + } + instr := p[instrIndex] + if unitIndex < 0 || unitIndex >= len(instr.Units) { + return "" + } + unit := instr.Units[unitIndex] + value := unit.Parameters[param] + switch unit.Type { + case "envelope": + switch param { + case "attack": + return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100) + case "decay": + return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * (1 - float64(unit.Parameters["sustain"])/128)) + case "release": + return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * float64(unit.Parameters["sustain"]) / 128) + } + case "oscillator": + switch param { + case "type": + switch value { + case Sine: + return "Sine" + case Trisaw: + return "Trisaw" + case Pulse: + return "Pulse" + case Gate: + return "Gate" + case Sample: + return "Sample" + default: + return "Unknown" + } + case "transpose": + relvalue := value - 64 + octaves := relvalue / 12 + semitones := relvalue % 12 + if octaves != 0 { + return fmt.Sprintf("%v oct, %v st", octaves, semitones) + } + return fmt.Sprintf("%v st", semitones) + case "detune": + return fmt.Sprintf("%v st", float32(value-64)/64.0) + } + case "compressor": + switch param { + case "attack": + fallthrough + case "release": + alpha := math.Pow(2, -24*float64(value)/128) // alpha is the "smoothing factor" of first order low pass iir + sec := -1 / (44100 * math.Log(1-alpha)) // from smoothing factor to time constant, https://en.wikipedia.org/wiki/Exponential_smoothing + return engineeringTime(sec) + case "ratio": + return fmt.Sprintf("1 : %.3f", 1-float64(value)/128) + } + case "send": + switch param { + case "voice": + if value == 0 { + return "auto" + } + return fmt.Sprintf("%v", value) + case "target": + instrIndex, unitIndex, err := p.FindSendTarget(unit.Parameters["target"]) + if err != nil { + return "invalid target" + } + instr := p[instrIndex] + unit := instr.Units[unitIndex] + return fmt.Sprintf("%v / %v%v", instr.Name, unit.Type, unitIndex) + case "port": + instrIndex, unitIndex, err := p.FindSendTarget(unit.Parameters["target"]) + if err != nil { + return fmt.Sprintf("%v ???", value) + } + portList := Ports[p[instrIndex].Units[unitIndex].Type] + if value < 0 || value >= len(portList) { + return fmt.Sprintf("%v ???", value) + } + return fmt.Sprintf(portList[value]) + } + } + return "" +} diff --git a/score.go b/score.go new file mode 100644 index 0000000..d7ec303 --- /dev/null +++ b/score.go @@ -0,0 +1,35 @@ +package sointu + +type Score struct { + Tracks []Track + RowsPerPattern int + Length int // length of the song, in number of patterns +} + +func (l Score) Copy() Score { + tracks := make([]Track, len(l.Tracks)) + for i, t := range l.Tracks { + tracks[i] = t.Copy() + } + return Score{Tracks: tracks, RowsPerPattern: l.RowsPerPattern, Length: l.Length} +} + +func (l Score) NumVoices() int { + ret := 0 + for _, t := range l.Tracks { + ret += t.NumVoices + } + return ret +} + +func (l Score) FirstVoiceForTrack(track int) int { + ret := 0 + for _, t := range l.Tracks[:track] { + ret += t.NumVoices + } + return ret +} + +func (l Score) LengthInRows() int { + return l.RowsPerPattern * l.Length +} diff --git a/sointu.go b/sointu.go deleted file mode 100644 index fd8b3ff..0000000 --- a/sointu.go +++ /dev/null @@ -1,587 +0,0 @@ -package sointu - -import ( - "errors" - "fmt" - "math" -) - -// Unit is e.g. a filter, oscillator, envelope and its parameters -type Unit struct { - Type string - Parameters map[string]int `yaml:",flow"` - VarArgs []int `yaml:",flow,omitempty"` -} - -func (u *Unit) Copy() Unit { - parameters := make(map[string]int) - for k, v := range u.Parameters { - parameters[k] = v - } - varArgs := make([]int, len(u.VarArgs)) - copy(varArgs, u.VarArgs) - return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs} -} - -const ( - Sine = iota - Trisaw = iota - Pulse = iota - Gate = iota - Sample = iota -) - -// Instrument includes a list of units consisting of the instrument, and the number of polyphonic voices for this instrument -type Instrument struct { - Name string - NumVoices int - Units []Unit -} - -func (instr *Instrument) Copy() Instrument { - units := make([]Unit, len(instr.Units)) - for i, u := range instr.Units { - units[i] = u.Copy() - } - return Instrument{Name: instr.Name, NumVoices: instr.NumVoices, Units: units} -} - -// Patch is simply a list of instruments used in a song -type Patch struct { - Instruments []Instrument -} - -func (p *Patch) Copy() Patch { - instruments := make([]Instrument, len(p.Instruments)) - for i, instr := range p.Instruments { - instruments[i] = instr.Copy() - } - return Patch{Instruments: instruments} -} - -func (p Patch) TotalVoices() int { - ret := 0 - for _, i := range p.Instruments { - ret += i.NumVoices - } - return ret -} - -func (patch Patch) InstrumentForVoice(voice int) (int, error) { - if voice < 0 { - return 0, errors.New("voice cannot be negative") - } - for i, instr := range patch.Instruments { - if voice < instr.NumVoices { - return i, nil - } else { - voice -= instr.NumVoices - } - } - return 0, errors.New("voice number is beyond the total voices of an instrument") -} - -type Track struct { - NumVoices int - Sequence []byte `yaml:",flow"` - Patterns [][]byte `yaml:",flow"` -} - -func (t *Track) Copy() Track { - sequence := make([]byte, len(t.Sequence)) - copy(sequence, t.Sequence) - patterns := make([][]byte, len(t.Patterns)) - for i, oldPat := range t.Patterns { - newPat := make([]byte, len(oldPat)) - copy(newPat, oldPat) - patterns[i] = newPat - } - return Track{ - NumVoices: t.NumVoices, - Sequence: sequence, - Patterns: patterns, - } -} - -type Synth interface { - Render(buffer []float32, maxtime int) (int, int, error) - Update(patch Patch) error - Trigger(voice int, note byte) - Release(voice int) -} - -func Render(synth Synth, buffer []float32) error { - s, _, err := synth.Render(buffer, math.MaxInt32) - if err != nil { - return fmt.Errorf("sointu.Render failed: %v", err) - } - if s != len(buffer)/2 { - return errors.New("in sointu.Render, synth.Render should have filled the whole buffer but did not") - } - return nil -} - -type SynthService interface { - Compile(patch Patch) (Synth, error) -} - -type AudioSink interface { - WriteAudio(buffer []float32) (err error) - Close() error -} - -type AudioSource interface { - ReadAudio(buffer []float32) (n int, err error) - Close() error -} - -type AudioContext interface { - Output() AudioSink - Close() error -} - -// UnitParameter documents one parameter that an unit takes -type UnitParameter struct { - Name string // thould be found with this name in the Unit.Parameters map - MinValue int // minimum value of the parameter, inclusive - MaxValue int // maximum value of the parameter, inclusive - CanSet bool // if this parameter can be set before hand i.e. through the gui - CanModulate bool // if this parameter can be modulated i.e. has a port number in "send" unit -} - -func engineeringTime(sec float64) string { - if sec < 1e-3 { - return fmt.Sprintf("%.2f us", sec*1e6) - } else if sec < 1 { - return fmt.Sprintf("%.2f ms", sec*1e3) - } - return fmt.Sprintf("%.2f s", sec) -} - -// UnitTypes documents all the available unit types and if they support stereo variant -// and what parameters they take. -var UnitTypes = map[string]([]UnitParameter){ - "add": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "addp": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "pop": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "loadnote": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "mul": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "mulp": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "push": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "xch": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "distort": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "drive", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "hold": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "holdfreq", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "crush": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "resolution", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "gain": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "invgain": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "filter": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "frequency", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "resonance", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "lowpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "bandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "highpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "negbandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "neghighpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "clip": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "pan": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "panning", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "delay": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "pregain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "dry", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}}, - "compressor": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "threshold", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "ratio", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "speed": []UnitParameter{}, - "out": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "outaux": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "outgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "auxgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "aux": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}}, - "send": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "amount", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "voice", MinValue: 0, MaxValue: 32, CanSet: true, CanModulate: false}, - {Name: "unit", MinValue: 0, MaxValue: 63, CanSet: true, CanModulate: false}, - {Name: "port", MinValue: 0, MaxValue: 7, CanSet: true, CanModulate: false}, - {Name: "sendpop", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, - "envelope": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "decay", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "sustain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "noise": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "oscillator": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "transpose", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "detune", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "phase", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "color", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, - {Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false}, - {Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}, - {Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false}, - {Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, - {Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}}, - "loadval": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, - "receive": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "left", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}, - {Name: "right", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}}, - "in": []UnitParameter{ - {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, - {Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}}, -} - -var Ports = make(map[string]([]string)) - -func init() { - for name, unitType := range UnitTypes { - unitPorts := make([]string, 0) - for _, param := range unitType { - if param.CanModulate { - unitPorts = append(unitPorts, param.Name) - } - } - Ports[name] = unitPorts - } -} - -type Song struct { - BPM int - RowsPerPattern int - RowsPerBeat int - Tracks []Track - Patch Patch -} - -func (s *Song) Copy() Song { - tracks := make([]Track, len(s.Tracks)) - for i, t := range s.Tracks { - tracks[i] = t.Copy() - } - return Song{BPM: s.BPM, RowsPerPattern: s.RowsPerPattern, RowsPerBeat: s.RowsPerBeat, Tracks: tracks, Patch: s.Patch.Copy()} -} - -func (s *Song) SequenceLength() int { - return len(s.Tracks[0].Sequence) -} - -func (s *Song) TotalRows() int { - return s.RowsPerPattern * s.SequenceLength() -} - -func (s *Song) SamplesPerRow() int { - return 44100 * 60 / (s.BPM * s.RowsPerBeat) -} - -func (s *Song) FirstTrackVoice(track int) int { - ret := 0 - for _, t := range s.Tracks[:track] { - ret += t.NumVoices - } - return ret -} - -func (s *Song) FirstInstrumentVoice(instrument int) int { - ret := 0 - for _, i := range s.Patch.Instruments[:instrument] { - ret += i.NumVoices - } - return ret -} - -func (s *Song) TotalTrackVoices() int { - ret := 0 - for _, t := range s.Tracks { - ret += t.NumVoices - } - return ret -} - -// TBD: Where shall we put methods that work on pure domain types and have no dependencies -// e.g. Validate here -func (s *Song) Validate() error { - if s.BPM < 1 { - return errors.New("BPM should be > 0") - } - var patternLen int - for i, t := range s.Tracks { - for j, pat := range t.Patterns { - if i == 0 && j == 0 { - patternLen = len(pat) - } else { - if len(pat) != patternLen { - return errors.New("Every pattern should have the same length") - } - } - } - } - for i := range s.Tracks[:len(s.Tracks)-1] { - if len(s.Tracks[i].Sequence) != len(s.Tracks[i+1].Sequence) { - return errors.New("Every track should have the same sequence length") - } - } - totalTrackVoices := 0 - for _, track := range s.Tracks { - totalTrackVoices += track.NumVoices - for _, p := range track.Sequence { - if p < 0 || int(p) >= len(track.Patterns) { - return errors.New("Tracks use a non-existing pattern") - } - } - } - if totalTrackVoices > s.Patch.TotalVoices() { - return errors.New("Tracks use too many voices") - } - return nil -} - -func (p *Patch) FindSendTarget(i, u int) (int, int, int, error) { - if i < 0 || i >= len(p.Instruments) { - return -1, -1, -1, errors.New("instrument index out of range") - } - instr := p.Instruments[i] - if u < 0 || u >= len(instr.Units) { - return -1, -1, -1, errors.New("unit index out of range") - } - unit := instr.Units[u] - var targetInstrIndex int - if unit.Parameters["voice"] == 0 { - targetInstrIndex = i - } else { - var err error - targetInstrIndex, err = p.InstrumentForVoice(unit.Parameters["voice"] - 1) - if err != nil { - return -1, -1, -1, errors.New("the target voice was out of range") - } - } - targetInstr := p.Instruments[targetInstrIndex] - targetUnitIndex := unit.Parameters["unit"] - if targetUnitIndex < 0 || targetUnitIndex >= len(targetInstr.Units) { - return targetInstrIndex, -1, -1, errors.New("the target unit was out of range") - } - targetUnit := targetInstr.Units[targetUnitIndex] - port := unit.Parameters["port"] - if port < 0 { - return targetInstrIndex, targetUnitIndex, -1, errors.New("the target port was out of range") - } - for k, param := range UnitTypes[targetUnit.Type] { - if param.CanModulate { - port-- - if port < 0 { - return targetInstrIndex, targetUnitIndex, k, nil - } - } - } - return targetInstrIndex, targetUnitIndex, -1, errors.New("the target port was out of range") -} - -func (s *Song) ParamHintString(instrIndex, unitIndex int, param string) string { - if instrIndex < 0 || instrIndex >= len(s.Patch.Instruments) { - return "" - } - instr := s.Patch.Instruments[instrIndex] - if unitIndex < 0 || unitIndex >= len(instr.Units) { - return "" - } - unit := instr.Units[unitIndex] - value := unit.Parameters[param] - switch unit.Type { - case "envelope": - switch param { - case "attack": - return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100) - case "decay": - return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * (1 - float64(unit.Parameters["sustain"])/128)) - case "release": - return engineeringTime(math.Pow(2, 24*float64(value)/128) / 44100 * float64(unit.Parameters["sustain"]) / 128) - } - case "oscillator": - switch param { - case "type": - switch value { - case Sine: - return "Sine" - case Trisaw: - return "Trisaw" - case Pulse: - return "Pulse" - case Gate: - return "Gate" - case Sample: - return "Sample" - default: - return "Unknown" - } - case "transpose": - relvalue := value - 64 - octaves := relvalue / 12 - semitones := relvalue % 12 - if octaves != 0 { - return fmt.Sprintf("%v oct, %v st", octaves, semitones) - } - return fmt.Sprintf("%v st", semitones) - case "detune": - return fmt.Sprintf("%v st", float32(value-64)/64.0) - } - case "compressor": - switch param { - case "attack": - fallthrough - case "release": - alpha := math.Pow(2, -24*float64(value)/128) // alpha is the "smoothing factor" of first order low pass iir - sec := -1 / (44100 * math.Log(1-alpha)) // from smoothing factor to time constant, https://en.wikipedia.org/wiki/Exponential_smoothing - return engineeringTime(sec) - case "ratio": - return fmt.Sprintf("1 : %.3f", 1-float64(value)/128) - } - case "send": - if param == "voice" || param == "unit" || param == "port" { - targetVoice := unit.Parameters["voice"] - if param == "voice" && targetVoice == 0 { - return "self" - } - targetInstrument := instrIndex - if targetVoice > 0 { // global send, find the instrument - if targetVoice > s.Patch.TotalVoices() { - return "" - } - targetVoice-- - targetInstrument = 0 - for targetVoice >= s.Patch.Instruments[targetInstrument].NumVoices { - targetVoice -= s.Patch.Instruments[targetInstrument].NumVoices - targetInstrument++ - } - } - if param == "voice" { - return fmt.Sprintf("%v (voice %v)", s.Patch.Instruments[targetInstrument].Name, targetVoice) - } - targetUnitIndex := unit.Parameters["unit"] - units := s.Patch.Instruments[targetInstrument].Units - if targetUnitIndex < 0 || targetUnitIndex >= len(units) { - return "" - } - if param == "unit" { - return fmt.Sprintf("%v#%v", units[targetUnitIndex].Type, targetUnitIndex) - } - port := value - for _, param := range UnitTypes[units[targetUnitIndex].Type] { - if param.CanModulate { - port-- - if port < 0 { - return param.Name - } - } - } - } - } - return "" -} - -func (u *Unit) StackChange() int { - switch u.Type { - case "addp", "mulp", "pop", "out", "outaux", "aux": - return -1 - u.Parameters["stereo"] - case "envelope", "oscillator", "push", "noise", "receive", "loadnote", "loadval", "in", "compressor": - return 1 + u.Parameters["stereo"] - case "pan": - return 1 - u.Parameters["stereo"] - case "speed": - return -1 - case "send": - return (-1 - u.Parameters["stereo"]) * u.Parameters["sendpop"] - } - return 0 -} - -func (u *Unit) StackNeed() int { - switch u.Type { - case "", "envelope", "oscillator", "noise", "receive", "loadnote", "loadval", "in": - return 0 - case "mulp", "mul", "add", "addp", "xch": - return 2 * (1 + u.Parameters["stereo"]) - case "speed": - return 1 - } - return 1 + u.Parameters["stereo"] -} - -func Play(synth Synth, song Song) ([]float32, error) { - err := song.Validate() - if err != nil { - return nil, err - } - curVoices := make([]int, len(song.Tracks)) - for i := range curVoices { - curVoices[i] = song.FirstTrackVoice(i) - } - initialCapacity := song.TotalRows() * song.SamplesPerRow() * 2 - buffer := make([]float32, 0, initialCapacity) - rowbuffer := make([]float32, song.SamplesPerRow()*2) - for row := 0; row < song.TotalRows(); row++ { - patternRow := row % song.RowsPerPattern - pattern := row / song.RowsPerPattern - for t := range song.Tracks { - patternIndex := song.Tracks[t].Sequence[pattern] - note := song.Tracks[t].Patterns[patternIndex][patternRow] - if note > 0 && note <= 1 { // anything but hold causes an action. - continue - } - synth.Release(curVoices[t]) - if note > 1 { - curVoices[t]++ - first := song.FirstTrackVoice(t) - if curVoices[t] >= first+song.Tracks[t].NumVoices { - curVoices[t] = first - } - synth.Trigger(curVoices[t], note) - } - } - tries := 0 - for rowtime := 0; rowtime < song.SamplesPerRow(); { - samples, time, _ := synth.Render(rowbuffer, song.SamplesPerRow()-rowtime) - rowtime += time - buffer = append(buffer, rowbuffer[:samples*2]...) - if tries > 100 { - return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow) - } - } - } - return buffer, nil -} diff --git a/song.go b/song.go new file mode 100644 index 0000000..06a8b13 --- /dev/null +++ b/song.go @@ -0,0 +1,61 @@ +package sointu + +import ( + "errors" +) + +type Song struct { + BPM int + RowsPerBeat int + Score Score + Patch Patch +} + +func (s *Song) Copy() Song { + return Song{BPM: s.BPM, RowsPerBeat: s.RowsPerBeat, Score: s.Score.Copy(), Patch: s.Patch.Copy()} +} + +func (s *Song) SamplesPerRow() int { + return 44100 * 60 / (s.BPM * s.RowsPerBeat) +} + +// TBD: Where shall we put methods that work on pure domain types and have no dependencies +// e.g. Validate here +func (s *Song) Validate() error { + if s.BPM < 1 { + return errors.New("BPM should be > 0") + } + if len(s.Score.Tracks) == 0 { + return errors.New("song contains no tracks") + } + var patternLen int + for i, t := range s.Score.Tracks { + for j, pat := range t.Patterns { + if i == 0 && j == 0 { + patternLen = len(pat) + } else { + if len(pat) != patternLen { + return errors.New("Every pattern should have the same length") + } + } + } + } + for i := range s.Score.Tracks[:len(s.Score.Tracks)-1] { + if len(s.Score.Tracks[i].Order) != len(s.Score.Tracks[i+1].Order) { + return errors.New("Every track should have the same sequence length") + } + } + totalTrackVoices := 0 + for _, track := range s.Score.Tracks { + totalTrackVoices += track.NumVoices + for _, p := range track.Order { + if p < 0 || int(p) >= len(track.Patterns) { + return errors.New("Tracks use a non-existing pattern") + } + } + } + if totalTrackVoices > s.Patch.NumVoices() { + return errors.New("Tracks use too many voices") + } + return nil +} diff --git a/synth.go b/synth.go new file mode 100644 index 0000000..6ea7981 --- /dev/null +++ b/synth.go @@ -0,0 +1,85 @@ +package sointu + +import ( + "errors" + "fmt" + "math" +) + +type Synth interface { + Render(buffer []float32, maxtime int) (int, int, error) + Update(patch Patch) error + Trigger(voice int, note byte) + Release(voice int) +} + +type SynthService interface { + Compile(patch Patch) (Synth, error) +} + +func Render(synth Synth, buffer []float32) error { + s, _, err := synth.Render(buffer, math.MaxInt32) + if err != nil { + return fmt.Errorf("sointu.Render failed: %v", err) + } + if s != len(buffer)/2 { + return errors.New("in sointu.Render, synth.Render should have filled the whole buffer but did not") + } + return nil +} + +func Play(synth Synth, song Song) ([]float32, error) { + err := song.Validate() + if err != nil { + return nil, err + } + curVoices := make([]int, len(song.Score.Tracks)) + for i := range curVoices { + curVoices[i] = song.Score.FirstVoiceForTrack(i) + } + initialCapacity := song.Score.LengthInRows() * song.SamplesPerRow() * 2 + buffer := make([]float32, 0, initialCapacity) + rowbuffer := make([]float32, song.SamplesPerRow()*2) + for row := 0; row < song.Score.LengthInRows(); row++ { + patternRow := row % song.Score.RowsPerPattern + pattern := row / song.Score.RowsPerPattern + for t := range song.Score.Tracks { + order := song.Score.Tracks[t].Order + if pattern < 0 || pattern >= len(order) { + continue + } + patternIndex := song.Score.Tracks[t].Order[pattern] + patterns := song.Score.Tracks[t].Patterns + if patternIndex < 0 || int(patternIndex) >= len(patterns) { + continue + } + pattern := patterns[patternIndex] + if patternRow < 0 || patternRow >= len(pattern) { + continue + } + note := pattern[patternRow] + if note > 0 && note <= 1 { // anything but hold causes an action. + continue + } + synth.Release(curVoices[t]) + if note > 1 { + curVoices[t]++ + first := song.Score.FirstVoiceForTrack(t) + if curVoices[t] >= first+song.Score.Tracks[t].NumVoices { + curVoices[t] = first + } + synth.Trigger(curVoices[t], note) + } + } + tries := 0 + for rowtime := 0; rowtime < song.SamplesPerRow(); { + samples, time, _ := synth.Render(rowbuffer, song.SamplesPerRow()-rowtime) + rowtime += time + buffer = append(buffer, rowbuffer[:samples*2]...) + if tries > 100 { + return nil, fmt.Errorf("Song speed modulation likely so slow that row never advances; error at pattern %v, row %v", pattern, patternRow) + } + } + } + return buffer, nil +} diff --git a/templates/amd64-386/player.asm b/templates/amd64-386/player.asm index bc18a39..9718ca9 100644 --- a/templates/amd64-386/player.asm +++ b/templates/amd64-386/player.asm @@ -40,7 +40,7 @@ su_render_sampleloop: ; loop through every sample in the row {{- if .SupportsPolyphony}} {{.Push (.PolyphonyBitmask | printf "%v") "PolyphonyBitmask"}} ; does the next voice reuse the current opcodes? {{- end}} - {{.Push (.Song.Patch.TotalVoices | printf "%v") "VoicesRemain"}} + {{.Push (.Song.Patch.NumVoices | printf "%v") "VoicesRemain"}} mov {{.DX}}, {{.PTRWORD}} su_synth_obj ; {{.DX}} points to the synth object mov {{.COM}}, {{.PTRWORD}} su_patch_code ; COM points to vm code mov {{.VAL}}, {{.PTRWORD}} su_patch_parameters ; VAL points to unit params @@ -140,7 +140,7 @@ su_update_voices_nexttrack: pop {{.DX}} ; edx=patrnrow add {{.SI}}, {{.SequenceLength}} inc {{.BP}} -{{- $addrname := len .Song.Tracks | printf "su_synth_obj + %v"}} +{{- $addrname := len .Song.Score.Tracks | printf "su_synth_obj + %v"}} {{- .Prepare $addrname | indent 8}} cmp {{.BP}},{{.Use $addrname}} jl su_update_voices_trackloop @@ -154,7 +154,7 @@ su_update_voices_nexttrack: {{- .Prepare "su_tracks" | indent 4}} lea {{.SI}}, [{{.Use "su_tracks"}}+{{.AX}}]; esi points to the pattern data for current track mov {{.DI}}, {{.PTRWORD}} su_synth_obj+su_synthworkspace.voices - mov bl, {{len .Song.Tracks}} ; MAX_TRACKS is always <= 32 so this is ok + mov bl, {{len .Song.Score.Tracks}} ; MAX_TRACKS is always <= 32 so this is ok su_update_voices_trackloop: movzx eax, byte [{{.SI}}] ; eax = current pattern imul eax, {{.PatternLength}} ; multiply by rows per pattern, eax = offset to current pattern data diff --git a/templates/amd64-386/player.h b/templates/amd64-386/player.h index d2ce07a..b115578 100644 --- a/templates/amd64-386/player.h +++ b/templates/amd64-386/player.h @@ -8,8 +8,8 @@ #define SU_SAMPLE_RATE 44100 #define SU_BPM {{.Song.BPM}} #define SU_ROWS_PER_BEAT {{.Song.RowsPerBeat}} -#define SU_PATTERN_SIZE {{.Song.RowsPerPattern}} -#define SU_MAX_PATTERNS {{.Song.SequenceLength}} +#define SU_PATTERN_SIZE {{.Song.Score.RowsPerPattern}} +#define SU_MAX_PATTERNS {{.Song.Score.Length}} #define SU_TOTAL_ROWS (SU_MAX_PATTERNS*SU_PATTERN_SIZE) #define SU_SAMPLES_PER_ROW (SU_SAMPLE_RATE*60/(SU_BPM*SU_ROWS_PER_BEAT)) diff --git a/templates/amd64-386/sinks.asm b/templates/amd64-386/sinks.asm index 9f83171..c57f5d4 100644 --- a/templates/amd64-386/sinks.asm +++ b/templates/amd64-386/sinks.asm @@ -89,7 +89,7 @@ su_op_aux_mono: {{.Func "su_op_send" "Opcode"}} lodsw mov {{.CX}}, [{{.Stack "Voice"}}] ; load pointer to voice -{{- if .SupportsParamValueOtherThan "send" "voice" 0}} +{{- if .SupportsGlobalSend}} pushf ; uh ugly: we save the flags just for the stereo carry bit. Doing the .CX loading later crashed the synth for stereo sends as loading the synth address from stack was f'd up by the "call su_op_send_mono" test {{.AX}}, 0x8000 jz su_op_send_skipglobal diff --git a/templates/wasm/player.wat b/templates/wasm/player.wat index 85bd87a..2fa2f36 100644 --- a/templates/wasm/player.wat +++ b/templates/wasm/player.wat @@ -170,7 +170,7 @@ {{- end}} (global.set $WRK (i32.const 4160)) (global.set $voice (i32.const 4160)) - (global.set $voicesRemain (i32.const {{.Song.Patch.TotalVoices | printf "%v"}})) + (global.set $voicesRemain (i32.const {{.Song.Patch.NumVoices | printf "%v"}})) {{- if .HasOp "delay"}} (global.set $delayWRK (i32.const 262144)) ;; BAD IDEA: we are limited to something like 30 delay lines ;; after that, the delay lines start to overwrite the outputbuffer. Find a way to layout the memory diff --git a/templates/wasm/sinks.wat b/templates/wasm/sinks.wat index 7ea4310..7d3a83d 100644 --- a/templates/wasm/sinks.wat +++ b/templates/wasm/sinks.wat @@ -95,12 +95,12 @@ loop $stereoLoop {{- end}} (local.set $scaledAddress (i32.add (i32.mul (i32.and (local.get $address) (i32.const 0x7FF7)) (i32.const 4)) -{{- if .SupportsParamValueOtherThan "send" "voice" 0}} +{{- if .SupportsGlobalSend}} (select (i32.const 4096) {{- end}} (global.get $voice) -{{- if .SupportsParamValueOtherThan "send" "voice" 0}} +{{- if .SupportsGlobalSend}} (i32.and (local.get $address)(i32.const 0x8000)) ) {{- end}} diff --git a/tests/test_add.yml b/tests/test_add.yml index d3cef35..f3ca10d 100644 --- a/tests/test_add.yml +++ b/tests/test_add.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: add - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: add + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_add_stereo.yml b/tests/test_add_stereo.yml index 5b62dfa..24407b0 100644 --- a/tests/test_add_stereo.yml +++ b/tests/test_add_stereo.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: add - parameters: {stereo: 1} - - type: xch - parameters: {stereo: 1} - - type: pop - parameters: {stereo: 0} - - type: pop - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: add + parameters: {stereo: 1} + - type: xch + parameters: {stereo: 1} + - type: pop + parameters: {stereo: 0} + - type: pop + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_addp.yml b/tests/test_addp.yml index 1d6b0c6..4c97199 100644 --- a/tests/test_addp.yml +++ b/tests/test_addp.yml @@ -1,25 +1,26 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 48} - - type: loadval - parameters: {stereo: 0, value: 48} - - type: addp - parameters: {stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 80} - - type: loadval - parameters: {stereo: 0, value: 80} - - type: addp - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 48} + - type: loadval + parameters: {stereo: 0, value: 48} + - type: addp + parameters: {stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 80} + - type: loadval + parameters: {stereo: 0, value: 80} + - type: addp + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_addp_stereo.yml b/tests/test_addp_stereo.yml index 79601ac..8d3f6ed 100644 --- a/tests/test_addp_stereo.yml +++ b/tests/test_addp_stereo.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: addp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: addp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_aux.yml b/tests/test_aux.yml index 9f572cd..4317d68 100644 --- a/tests/test_aux.yml +++ b/tests/test_aux.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: aux - parameters: {channel: 1, gain: 64, stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: aux - parameters: {channel: 0, gain: 128, stereo: 0} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: aux + parameters: {channel: 1, gain: 64, stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: aux + parameters: {channel: 0, gain: 128, stereo: 0} diff --git a/tests/test_aux_stereo.yml b/tests/test_aux_stereo.yml index 14c4614..fd3df21 100644 --- a/tests/test_aux_stereo.yml +++ b/tests/test_aux_stereo.yml @@ -1,31 +1,32 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: aux - parameters: {channel: 0, gain: 128, stereo: 1} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: aux - parameters: {channel: 2, gain: 64, stereo: 1} - - type: in - parameters: {channel: 0, stereo: 1} - - type: in - parameters: {channel: 2, stereo: 1} - - type: addp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: aux + parameters: {channel: 0, gain: 128, stereo: 1} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: aux + parameters: {channel: 2, gain: 64, stereo: 1} + - type: in + parameters: {channel: 0, stereo: 1} + - type: in + parameters: {channel: 2, stereo: 1} + - type: addp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_chords.yml b/tests/test_chords.yml index eb76529..e21a629 100644 --- a/tests/test_chords.yml +++ b/tests/test_chords.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 0, 0, 68, 0, 0, 0, 66, 0, 0, 0, 69, 0, 0, 0]] - - numvoices: 1 - sequence: [0] - patterns: [[0, 68, 0, 0, 71, 0, 0, 0, 69, 0, 0, 0, 73, 0, 0, 0]] - - numvoices: 1 - sequence: [0] - patterns: [[0, 0, 71, 0, 75, 0, 0, 0, 73, 0, 0, 0, 76, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[64, 0, 0, 0, 68, 0, 0, 0, 66, 0, 0, 0, 69, 0, 0, 0]] + - numvoices: 1 + order: [0] + patterns: [[0, 68, 0, 0, 71, 0, 0, 0, 69, 0, 0, 0, 73, 0, 0, 0]] + - numvoices: 1 + order: [0] + patterns: [[0, 0, 71, 0, 75, 0, 0, 0, 73, 0, 0, 0, 76, 0, 0, 0]] patch: - instruments: - - numvoices: 3 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 32, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 32, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 3 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 32, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 32, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_clip.yml b/tests/test_clip.yml index ed755b6..85a6bb2 100644 --- a/tests/test_clip.yml +++ b/tests/test_clip.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: oscillator - parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: invgain - parameters: {invgain: 64, stereo: 1} - - type: clip - parameters: {stereo: 0} - - type: gain - parameters: {gain: 64, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: oscillator + parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: invgain + parameters: {invgain: 64, stereo: 1} + - type: clip + parameters: {stereo: 0} + - type: gain + parameters: {gain: 64, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_clip_stereo.yml b/tests/test_clip_stereo.yml index c31d3f6..e553008 100644 --- a/tests/test_clip_stereo.yml +++ b/tests/test_clip_stereo.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: oscillator - parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: invgain - parameters: {invgain: 64, stereo: 1} - - type: clip - parameters: {stereo: 1} - - type: gain - parameters: {gain: 64, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: oscillator + parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: invgain + parameters: {invgain: 64, stereo: 1} + - type: clip + parameters: {stereo: 1} + - type: gain + parameters: {gain: 64, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_compressor.yml b/tests/test_compressor.yml index 0b33a52..845670f 100644 --- a/tests/test_compressor.yml +++ b/tests/test_compressor.yml @@ -1,54 +1,56 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65]] - - numvoices: 1 - sequence: [0] - patterns: [[76, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65]] + - numvoices: 1 + order: [0] + patterns: [[76, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - type: send - parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - type: send - parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - numvoices: 1 - units: - - type: receive - parameters: {stereo: 1} - - type: compressor - parameters: {attack: 32, invgain: 32, ratio: 96, release: 64, stereo: 0, threshold: 64} - - type: mulp - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: send + parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, target: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: send + parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, target: 1} + - numvoices: 1 + units: + - type: receive + parameters: {stereo: 1} + id: 1 + - type: compressor + parameters: {attack: 32, invgain: 32, ratio: 96, release: 64, stereo: 0, threshold: 64} + - type: mulp + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_compressor_stereo.yml b/tests/test_compressor_stereo.yml index baf6a35..4e512ac 100644 --- a/tests/test_compressor_stereo.yml +++ b/tests/test_compressor_stereo.yml @@ -1,54 +1,56 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65]] - - numvoices: 1 - sequence: [0] - patterns: [[76, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65]] + - numvoices: 1 + order: [0] + patterns: [[76, 0, 0, 0, 0, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - type: send - parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - type: send - parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, unit: 0, voice: 3} - - numvoices: 1 - units: - - type: receive - parameters: {stereo: 1} - - type: compressor - parameters: {attack: 32, invgain: 32, ratio: 96, release: 64, stereo: 1, threshold: 64} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 16, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: send + parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, target: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 88, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: send + parameters: {amount: 128, port: 1, sendpop: 1, stereo: 0, target: 1} + - numvoices: 1 + units: + - type: receive + parameters: {stereo: 1} + id: 1 + - type: compressor + parameters: {attack: 32, invgain: 32, ratio: 96, release: 64, stereo: 1, threshold: 64} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_crush.yml b/tests/test_crush.yml index 4a5a78b..109d242 100644 --- a/tests/test_crush.yml +++ b/tests/test_crush.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: oscillator - parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: crush - parameters: {resolution: 3, stereo: 0} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: crush - parameters: {resolution: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: oscillator + parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: crush + parameters: {resolution: 3, stereo: 0} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: crush + parameters: {resolution: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_crush_stereo.yml b/tests/test_crush_stereo.yml index cf1081d..142577e 100644 --- a/tests/test_crush_stereo.yml +++ b/tests/test_crush_stereo.yml @@ -1,27 +1,28 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: oscillator - parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: crush - parameters: {resolution: 32, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: oscillator + parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 128} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: crush + parameters: {resolution: 32, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_delay.yml b/tests/test_delay.yml index 2be64b1..dfbd146 100644 --- a/tests/test_delay.yml +++ b/tests/test_delay.yml @@ -1,24 +1,25 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} - varargs: [11025] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} + varargs: [11025] + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_delay_dampmod.yml b/tests/test_delay_dampmod.yml index 6aa2610..d6b64a9 100644 --- a/tests/test_delay_dampmod.yml +++ b/tests/test_delay_dampmod.yml @@ -1,28 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} - varargs: [11025] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 32, port: 3, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} + varargs: [11025] + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 32, port: 3, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_delay_drymod.yml b/tests/test_delay_drymod.yml index fb79830..be9cede 100644 --- a/tests/test_delay_drymod.yml +++ b/tests/test_delay_drymod.yml @@ -1,28 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} - varargs: [11025] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} + varargs: [11025] + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_delay_feedbackmod.yml b/tests/test_delay_feedbackmod.yml index fbbf627..d1ec80f 100644 --- a/tests/test_delay_feedbackmod.yml +++ b/tests/test_delay_feedbackmod.yml @@ -1,28 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} - varargs: [11025] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 32, port: 2, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} + varargs: [11025] + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 32, port: 2, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_delay_flanger.yml b/tests/test_delay_flanger.yml index 7400ba4..d445bee 100644 --- a/tests/test_delay_flanger.yml +++ b/tests/test_delay_flanger.yml @@ -1,28 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 0, notetracking: 0, pregain: 40, stereo: 0} - varargs: [1000] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 50, type: 0, unison: 0} - - type: send - parameters: {amount: 65, port: 4, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 0, notetracking: 0, pregain: 40, stereo: 0} + varargs: [1000] + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 50, type: 0, unison: 0} + - type: send + parameters: {amount: 65, port: 4, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_delay_notetracking.yml b/tests/test_delay_notetracking.yml index 164a1d8..01342d9 100644 --- a/tests/test_delay_notetracking.yml +++ b/tests/test_delay_notetracking.yml @@ -1,32 +1,33 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 0, decay: 0, gain: 128, release: 96, stereo: 0, sustain: 96} - - type: envelope - parameters: {attack: 0, decay: 48, gain: 128, release: 0, stereo: 0, sustain: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 64, lfo: 0, phase: 0, shape: 127, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 1, frequency: 32, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0} - - type: delay - parameters: {damp: 16, dry: 128, feedback: 128, notetracking: 1, pregain: 128, stereo: 0} - varargs: [10787] - - type: filter - parameters: {bandpass: 1, frequency: 24, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0} - - type: mulp - parameters: {stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 0, decay: 0, gain: 128, release: 96, stereo: 0, sustain: 96} + - type: envelope + parameters: {attack: 0, decay: 48, gain: 128, release: 0, stereo: 0, sustain: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 64, lfo: 0, phase: 0, shape: 127, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 1, frequency: 32, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0} + - type: delay + parameters: {damp: 16, dry: 128, feedback: 128, notetracking: 1, pregain: 128, stereo: 0} + varargs: [10787] + - type: filter + parameters: {bandpass: 1, frequency: 24, highpass: 1, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 128, stereo: 0} + - type: mulp + parameters: {stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_delay_pregainmod.yml b/tests/test_delay_pregainmod.yml index 00756d9..3c8425f 100644 --- a/tests/test_delay_pregainmod.yml +++ b/tests/test_delay_pregainmod.yml @@ -1,28 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} - varargs: [11025] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} + varargs: [11025] + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_delay_reverb.yml b/tests/test_delay_reverb.yml index 7216677..6a2189f 100644 --- a/tests/test_delay_reverb.yml +++ b/tests/test_delay_reverb.yml @@ -1,24 +1,25 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} - varargs: [1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618] - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 0} + varargs: [1116, 1188, 1276, 1356, 1422, 1492, 1556, 1618] + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_delay_stereo.yml b/tests/test_delay_stereo.yml index 06fe53b..63cc218 100644 --- a/tests/test_delay_stereo.yml +++ b/tests/test_delay_stereo.yml @@ -1,24 +1,25 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: delay - parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 1} - varargs: [11025, 21025] - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: delay + parameters: {damp: 64, dry: 128, feedback: 125, notetracking: 0, pregain: 40, stereo: 1} + varargs: [11025, 21025] + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_distort.yml b/tests/test_distort.yml index cdf0835..b70cde7 100644 --- a/tests/test_distort.yml +++ b/tests/test_distort.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: distort - parameters: {drive: 32, stereo: 0} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: distort - parameters: {drive: 96, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: distort + parameters: {drive: 32, stereo: 0} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: distort + parameters: {drive: 96, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_distort_mod.yml b/tests/test_distort_mod.yml index 21e7e37..8f45357 100644 --- a/tests/test_distort_mod.yml +++ b/tests/test_distort_mod.yml @@ -1,27 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: distort - parameters: {drive: 32, stereo: 0} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: distort - parameters: {drive: 96, stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 0, sendpop: 0, stereo: 0, unit: 1, voice: 0} - - type: send - parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: distort + parameters: {drive: 32, stereo: 0} + id: 1 + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: distort + parameters: {drive: 96, stereo: 0} + id: 2 + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 0, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_distort_stereo.yml b/tests/test_distort_stereo.yml index a0d0962..1a56fa3 100644 --- a/tests/test_distort_stereo.yml +++ b/tests/test_distort_stereo.yml @@ -1,17 +1,18 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64} - - type: distort - parameters: {drive: 96, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64} + - type: distort + parameters: {drive: 96, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_envelope.yml b/tests/test_envelope.yml index 0476bbf..2040fde 100644 --- a/tests/test_envelope.yml +++ b/tests/test_envelope.yml @@ -1,17 +1,18 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 95, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_envelope_mod.yml b/tests/test_envelope_mod.yml index 0f87765..ddee478 100644 --- a/tests/test_envelope_mod.yml +++ b/tests/test_envelope_mod.yml @@ -1,27 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 0, shape: 96, stereo: 0, transpose: 120, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 0, sendpop: 0, stereo: 0, unit: 0, voice: 0} - - type: send - parameters: {amount: 68, port: 1, sendpop: 0, stereo: 0, unit: 0, voice: 0} - - type: send - parameters: {amount: 68, port: 3, sendpop: 0, stereo: 0, unit: 0, voice: 0} - - type: send - parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 110, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + id: 1 + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + id: 2 + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 0, shape: 96, stereo: 0, transpose: 120, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 0, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 68, port: 1, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 68, port: 3, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, target: 2} + - type: out + parameters: {gain: 110, stereo: 1} diff --git a/tests/test_envelope_stereo.yml b/tests/test_envelope_stereo.yml index ea30b5d..00a267b 100644 --- a/tests/test_envelope_stereo.yml +++ b/tests/test_envelope_stereo.yml @@ -1,15 +1,16 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 1, sustain: 64} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_filter_band.yml b/tests/test_filter_band.yml index 0abf3fa..c37d66d 100644 --- a/tests/test_filter_band.yml +++ b/tests/test_filter_band.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_filter_freqmod.yml b/tests/test_filter_freqmod.yml index 128ef36..895601d 100644 --- a/tests/test_filter_freqmod.yml +++ b/tests/test_filter_freqmod.yml @@ -1,27 +1,29 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 32, port: 0, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_filter_high.yml b/tests/test_filter_high.yml index 2f6fc26..2931a90 100644 --- a/tests/test_filter_high.yml +++ b/tests/test_filter_high.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 0, frequency: 32, highpass: 1, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 0, frequency: 32, highpass: 1, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_filter_low.yml b/tests/test_filter_low.yml index 10cf30a..0f75f52 100644 --- a/tests/test_filter_low.yml +++ b/tests/test_filter_low.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 0, frequency: 32, highpass: 0, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 0, frequency: 32, highpass: 0, lowpass: 1, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_filter_peak.yml b/tests/test_filter_peak.yml index 8c03973..d3e190b 100644 --- a/tests/test_filter_peak.yml +++ b/tests/test_filter_peak.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 0, frequency: 32, highpass: 0, lowpass: 1, negbandpass: 0, neghighpass: 1, resonance: 64, stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 0, frequency: 32, highpass: 0, lowpass: 1, negbandpass: 0, neghighpass: 1, resonance: 64, stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_filter_resmod.yml b/tests/test_filter_resmod.yml index bf33d07..a967164 100644 --- a/tests/test_filter_resmod.yml +++ b/tests/test_filter_resmod.yml @@ -1,27 +1,29 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: filter - parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, unit: 3, voice: 0} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: filter + parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 0} + id: 1 + - type: pan + parameters: {panning: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 32, port: 1, sendpop: 1, stereo: 0, target: 1} diff --git a/tests/test_filter_stereo.yml b/tests/test_filter_stereo.yml index 3a7752b..6ba4be4 100644 --- a/tests/test_filter_stereo.yml +++ b/tests/test_filter_stereo.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: pan - parameters: {panning: 64, stereo: 0} - - type: filter - parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 72, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 0} + - type: pan + parameters: {panning: 64, stereo: 0} + - type: filter + parameters: {bandpass: 1, frequency: 32, highpass: 0, lowpass: 0, negbandpass: 0, neghighpass: 0, resonance: 64, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_gain.yml b/tests/test_gain.yml index a2f1862..a4e67f4 100644 --- a/tests/test_gain.yml +++ b/tests/test_gain.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: gain - parameters: {gain: 64, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: gain + parameters: {gain: 64, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_gain_stereo.yml b/tests/test_gain_stereo.yml index bfa3364..4db2944 100644 --- a/tests/test_gain_stereo.yml +++ b/tests/test_gain_stereo.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: gain - parameters: {gain: 128, stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: gain - parameters: {gain: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: gain + parameters: {gain: 128, stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: gain + parameters: {gain: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_hold.yml b/tests/test_hold.yml index 73d5a68..8dd50b0 100644 --- a/tests/test_hold.yml +++ b/tests/test_hold.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: hold - parameters: {holdfreq: 3, stereo: 0} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: hold - parameters: {holdfreq: 3, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: hold + parameters: {holdfreq: 3, stereo: 0} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: hold + parameters: {holdfreq: 3, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_hold_mod.yml b/tests/test_hold_mod.yml index 5a0ad2d..133ca20 100644 --- a/tests/test_hold_mod.yml +++ b/tests/test_hold_mod.yml @@ -1,27 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: hold - parameters: {holdfreq: 3, stereo: 0} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: hold - parameters: {holdfreq: 3, stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 0, sendpop: 0, stereo: 0, unit: 1, voice: 0} - - type: send - parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: hold + parameters: {holdfreq: 3, stereo: 0} + id: 1 + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: hold + parameters: {holdfreq: 3, stereo: 0} + id: 2 + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 0, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_hold_stereo.yml b/tests/test_hold_stereo.yml index 4290598..d85ca0c 100644 --- a/tests/test_hold_stereo.yml +++ b/tests/test_hold_stereo.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: hold - parameters: {holdfreq: 3, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: hold + parameters: {holdfreq: 3, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_in.yml b/tests/test_in.yml index 1dd6772..943e596 100644 --- a/tests/test_in.yml +++ b/tests/test_in.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: out - parameters: {gain: 128, stereo: 1} - - type: in - parameters: {channel: 1, stereo: 0} - - type: in - parameters: {channel: 0, stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: addp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: out + parameters: {gain: 128, stereo: 1} + - type: in + parameters: {channel: 1, stereo: 0} + - type: in + parameters: {channel: 0, stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: addp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_in_stereo.yml b/tests/test_in_stereo.yml index cdb385b..7b465fb 100644 --- a/tests/test_in_stereo.yml +++ b/tests/test_in_stereo.yml @@ -1,27 +1,28 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: out - parameters: {gain: 128, stereo: 1} - - type: in - parameters: {channel: 0, stereo: 1} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: addp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: out + parameters: {gain: 128, stereo: 1} + - type: in + parameters: {channel: 0, stereo: 1} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: addp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_invgain.yml b/tests/test_invgain.yml index b1cf0b3..9f30be3 100644 --- a/tests/test_invgain.yml +++ b/tests/test_invgain.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 48} - - type: invgain - parameters: {invgain: 64, stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 80} - - type: invgain - parameters: {invgain: 64, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 48} + - type: invgain + parameters: {invgain: 64, stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 80} + - type: invgain + parameters: {invgain: 64, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_invgain_stereo.yml b/tests/test_invgain_stereo.yml index d483140..85fe4d5 100644 --- a/tests/test_invgain_stereo.yml +++ b/tests/test_invgain_stereo.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 48} - - type: loadval - parameters: {stereo: 0, value: 80} - - type: invgain - parameters: {invgain: 64, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 48} + - type: loadval + parameters: {stereo: 0, value: 80} + - type: invgain + parameters: {invgain: 64, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_loadnote.yml b/tests/test_loadnote.yml index 16a1ca5..772584d 100644 --- a/tests/test_loadnote.yml +++ b/tests/test_loadnote.yml @@ -1,17 +1,18 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadnote - parameters: {stereo: 0} - - type: loadnote - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadnote + parameters: {stereo: 0} + - type: loadnote + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_loadnote_stereo.yml b/tests/test_loadnote_stereo.yml index 6393d29..a8e5c52 100644 --- a/tests/test_loadnote_stereo.yml +++ b/tests/test_loadnote_stereo.yml @@ -1,15 +1,16 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadnote - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadnote + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_loadval.yml b/tests/test_loadval.yml index ea302f8..a7485f2 100644 --- a/tests/test_loadval.yml +++ b/tests/test_loadval.yml @@ -1,17 +1,18 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 40} - - type: loadval - parameters: {stereo: 0, value: 80} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 40} + - type: loadval + parameters: {stereo: 0, value: 80} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_loadval_stereo.yml b/tests/test_loadval_stereo.yml index 2ea6dad..f9fc35d 100644 --- a/tests/test_loadval_stereo.yml +++ b/tests/test_loadval_stereo.yml @@ -1,15 +1,16 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 1, value: 40} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 1, value: 40} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_mul.yml b/tests/test_mul.yml index 8977269..43b08d8 100644 --- a/tests/test_mul.yml +++ b/tests/test_mul.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: mul - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: mul + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_mul_stereo.yml b/tests/test_mul_stereo.yml index be82a2c..eafb114 100644 --- a/tests/test_mul_stereo.yml +++ b/tests/test_mul_stereo.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: mul - parameters: {stereo: 1} - - type: xch - parameters: {stereo: 1} - - type: pop - parameters: {stereo: 0} - - type: pop - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: mul + parameters: {stereo: 1} + - type: xch + parameters: {stereo: 1} + - type: pop + parameters: {stereo: 0} + - type: pop + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_mulp.yml b/tests/test_mulp.yml index 835d2ef..d613a98 100644 --- a/tests/test_mulp.yml +++ b/tests/test_mulp.yml @@ -1,25 +1,26 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: mulp - parameters: {stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: mulp - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: mulp + parameters: {stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: mulp + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_mulp_stereo.yml b/tests/test_mulp_stereo.yml index 6a1a3ce..78f901c 100644 --- a/tests/test_mulp_stereo.yml +++ b/tests/test_mulp_stereo.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_multiple_instruments.yml b/tests/test_multiple_instruments.yml index 3b0e2f4..622da88 100644 --- a/tests/test_multiple_instruments.yml +++ b/tests/test_multiple_instruments.yml @@ -1,28 +1,29 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] - - numvoices: 1 - sequence: [0] - patterns: [[0, 0, 0, 0, 0, 0, 0, 0, 64, 1, 1, 0, 0, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] + - numvoices: 1 + order: [0] + patterns: [[0, 0, 0, 0, 0, 0, 0, 0, 64, 1, 1, 0, 0, 0, 0, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 0, release: 80, stereo: 0, sustain: 64} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 0, release: 80, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 0, release: 80, stereo: 0, sustain: 64} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 0, release: 80, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_noise.yml b/tests/test_noise.yml index beaa1a4..ee346a7 100644 --- a/tests/test_noise.yml +++ b/tests/test_noise.yml @@ -1,25 +1,26 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: noise - parameters: {gain: 128, shape: 64, stereo: 0} - - type: mulp - parameters: {stereo: 0} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: noise - parameters: {gain: 128, shape: 96, stereo: 0} - - type: mulp - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: noise + parameters: {gain: 128, shape: 64, stereo: 0} + - type: mulp + parameters: {stereo: 0} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: noise + parameters: {gain: 128, shape: 96, stereo: 0} + - type: mulp + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_noise_stereo.yml b/tests/test_noise_stereo.yml index 4f19789..3b3afd2 100644 --- a/tests/test_noise_stereo.yml +++ b/tests/test_noise_stereo.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: noise - parameters: {gain: 128, shape: 96, stereo: 1} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: noise + parameters: {gain: 128, shape: 96, stereo: 1} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_colormod.yml b/tests/test_oscillat_colormod.yml index f5b6485..76330aa 100644 --- a/tests/test_oscillat_colormod.yml +++ b/tests/test_oscillat_colormod.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 3, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 3, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_detunemod.yml b/tests/test_oscillat_detunemod.yml index 9496f9d..977b4e4 100644 --- a/tests/test_oscillat_detunemod.yml +++ b/tests/test_oscillat_detunemod.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 96, port: 1, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 96, port: 1, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_gainmod.yml b/tests/test_oscillat_gainmod.yml index fa3bee9..cc06ede 100644 --- a/tests/test_oscillat_gainmod.yml +++ b/tests/test_oscillat_gainmod.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 5, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 5, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_gate.yml b/tests/test_oscillat_gate.yml index ce86015..dbb1c08 100644 --- a/tests/test_oscillat_gate.yml +++ b/tests/test_oscillat_gate.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 15, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 96, stereo: 0, transpose: 64, type: 3, unison: 0} - - type: oscillator - parameters: {color: 170, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 64, stereo: 0, transpose: 72, type: 3, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 15, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 96, stereo: 0, transpose: 64, type: 3, unison: 0} + - type: oscillator + parameters: {color: 170, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 64, stereo: 0, transpose: 72, type: 3, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_lfo.yml b/tests/test_oscillat_lfo.yml index 133cfaf..7356d08 100644 --- a/tests/test_oscillat_lfo.yml +++ b/tests/test_oscillat_lfo.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 0, shape: 96, stereo: 0, transpose: 90, type: 0, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 1, phase: 0, shape: 96, stereo: 0, transpose: 100, type: 2, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 0, shape: 96, stereo: 0, transpose: 90, type: 0, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 1, phase: 0, shape: 96, stereo: 0, transpose: 100, type: 2, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_phasemod.yml b/tests/test_oscillat_phasemod.yml index 11caf02..74d95c8 100644 --- a/tests/test_oscillat_phasemod.yml +++ b/tests/test_oscillat_phasemod.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 128, port: 2, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 128, port: 2, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_pulse.yml b/tests/test_oscillat_pulse.yml index 266f644..5e25216 100644 --- a/tests/test_oscillat_pulse.yml +++ b/tests/test_oscillat_pulse.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 2, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 2, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 2, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 2, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_sample.yml b/tests/test_oscillat_sample.yml index 007ebc1..30c535b 100644 --- a/tests/test_oscillat_sample.yml +++ b/tests/test_oscillat_sample.yml @@ -1,40 +1,41 @@ bpm: 100 -rowsperpattern: 8 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [1, 0, 2, 0, 3, 0, 4, 0] - patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] - - numvoices: 1 - sequence: [0, 1, 0, 2, 0, 3, 0, 4] - patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] +score: + rowsperpattern: 8 + length: 8 + tracks: + - numvoices: 1 + order: [1, 0, 2, 0, 3, 0, 4, 0] + patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] + - numvoices: 1 + order: [0, 1, 0, 2, 0, 3, 0, 4] + patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 0, detune: 64, gain: 128, lfo: 0, looplength: 106, loopstart: 1341, phase: 64, shape: 64, samplestart: 1678611, stereo: 0, transpose: 68, type: 4, unison: 0} - - type: oscillator - parameters: {color: 0, detune: 64, gain: 128, lfo: 0, looplength: 95, loopstart: 1483, phase: 64, shape: 64, samplestart: 1680142, stereo: 0, transpose: 66, type: 4, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 0, detune: 64, gain: 128, lfo: 0, looplength: 106, loopstart: 1341, phase: 64, samplestart: 1678611, shape: 64, stereo: 0, transpose: 68, type: 4, unison: 0} + - type: oscillator + parameters: {color: 0, detune: 64, gain: 128, lfo: 0, looplength: 95, loopstart: 1483, phase: 64, samplestart: 1680142, shape: 64, stereo: 0, transpose: 66, type: 4, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_sample_stereo.yml b/tests/test_oscillat_sample_stereo.yml index b9cf04d..8bf3542 100644 --- a/tests/test_oscillat_sample_stereo.yml +++ b/tests/test_oscillat_sample_stereo.yml @@ -1,36 +1,37 @@ bpm: 100 -rowsperpattern: 8 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [1, 0, 2, 0, 3, 0, 4, 0] - patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] - - numvoices: 1 - sequence: [0, 1, 0, 2, 0, 3, 0, 4] - patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] +score: + rowsperpattern: 8 + length: 8 + tracks: + - numvoices: 1 + order: [1, 0, 2, 0, 3, 0, 4, 0] + patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] + - numvoices: 1 + order: [0, 1, 0, 2, 0, 3, 0, 4] + patterns: [[0, 0, 0, 0, 0, 0, 0, 0], [72, 1, 1, 1, 1, 1, 1, 0], [64, 1, 1, 1, 1, 1, 1, 0], [60, 1, 1, 1, 1, 1, 1, 0], [40, 1, 1, 1, 1, 1, 1, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 0, detune: 32, gain: 128, lfo: 0, looplength: 106, loopstart: 1341, phase: 64, shape: 64, samplestart: 1678611, stereo: 1, transpose: 68, type: 4, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 1, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 0, detune: 32, gain: 128, lfo: 0, looplength: 106, loopstart: 1341, phase: 64, samplestart: 1678611, shape: 64, stereo: 1, transpose: 68, type: 4, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 1, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_shapemod.yml b/tests/test_oscillat_shapemod.yml index e6996c6..5180d2a 100644 --- a/tests/test_oscillat_shapemod.yml +++ b/tests/test_oscillat_shapemod.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 4, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_sine.yml b/tests/test_oscillat_sine.yml index bf40b74..fea50ef 100644 --- a/tests/test_oscillat_sine.yml +++ b/tests/test_oscillat_sine.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 96, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_stereo.yml b/tests/test_oscillat_stereo.yml index 599f25e..e93cec3 100644 --- a/tests/test_oscillat_stereo.yml +++ b/tests/test_oscillat_stereo.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 1, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 1, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_transposemod.yml b/tests/test_oscillat_transposemod.yml index 945ae43..da558ff 100644 --- a/tests/test_oscillat_transposemod.yml +++ b/tests/test_oscillat_transposemod.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} - - type: send - parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, unit: 1, voice: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[80, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 80, decay: 80, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + id: 1 + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 1, phase: 64, shape: 64, stereo: 0, transpose: 70, type: 0, unison: 0} + - type: send + parameters: {amount: 68, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_trisaw.yml b/tests/test_oscillat_trisaw.yml index 31699d8..4475e79 100644 --- a/tests/test_oscillat_trisaw.yml +++ b/tests/test_oscillat_trisaw.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_unison.yml b/tests/test_oscillat_unison.yml index 774468e..dd858c8 100644 --- a/tests/test_oscillat_unison.yml +++ b/tests/test_oscillat_unison.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 0, gain: 32, lfo: 0, phase: 64, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 3} - - type: mulp - parameters: {stereo: 0} - - type: push - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 0, gain: 32, lfo: 0, phase: 64, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 3} + - type: mulp + parameters: {stereo: 0} + - type: push + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_oscillat_unison_stereo.yml b/tests/test_oscillat_unison_stereo.yml index 2b1dac0..a42d7a8 100644 --- a/tests/test_oscillat_unison_stereo.yml +++ b/tests/test_oscillat_unison_stereo.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 1, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 0, gain: 32, lfo: 0, phase: 64, shape: 64, stereo: 1, transpose: 64, type: 1, unison: 3} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 32, decay: 32, gain: 128, release: 64, stereo: 1, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 0, gain: 32, lfo: 0, phase: 64, shape: 64, stereo: 1, transpose: 64, type: 1, unison: 3} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_outaux.yml b/tests/test_outaux.yml index d40f480..3274c7c 100644 --- a/tests/test_outaux.yml +++ b/tests/test_outaux.yml @@ -1,27 +1,28 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: outaux - parameters: {auxgain: 64, outgain: 32, stereo: 0} - - type: in - parameters: {channel: 0, stereo: 0} - - type: in - parameters: {channel: 2, stereo: 0} - - type: loadval - parameters: {stereo: 0, value: 48} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: addp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: outaux + parameters: {auxgain: 64, outgain: 32, stereo: 0} + - type: in + parameters: {channel: 0, stereo: 0} + - type: in + parameters: {channel: 2, stereo: 0} + - type: loadval + parameters: {stereo: 0, value: 48} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: addp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_outaux_stereo.yml b/tests/test_outaux_stereo.yml index da8a1b4..228e591 100644 --- a/tests/test_outaux_stereo.yml +++ b/tests/test_outaux_stereo.yml @@ -1,29 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: outaux - parameters: {auxgain: 48, outgain: 16, stereo: 1} - - type: in - parameters: {channel: 1, stereo: 0} - - type: in - parameters: {channel: 0, stereo: 0} - - type: in - parameters: {channel: 3, stereo: 0} - - type: in - parameters: {channel: 2, stereo: 0} - - type: addp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: outaux + parameters: {auxgain: 48, outgain: 16, stereo: 1} + - type: in + parameters: {channel: 1, stereo: 0} + - type: in + parameters: {channel: 0, stereo: 0} + - type: in + parameters: {channel: 3, stereo: 0} + - type: in + parameters: {channel: 2, stereo: 0} + - type: addp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_panning.yml b/tests/test_panning.yml index 9f03be1..3ff7da8 100644 --- a/tests/test_panning.yml +++ b/tests/test_panning.yml @@ -1,17 +1,18 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: pan - parameters: {panning: 40, stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: pan + parameters: {panning: 40, stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_panning_stereo.yml b/tests/test_panning_stereo.yml index 4259f84..b714855 100644 --- a/tests/test_panning_stereo.yml +++ b/tests/test_panning_stereo.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} - - type: pan - parameters: {panning: 40, stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 80, stereo: 0, sustain: 64} + - type: pan + parameters: {panning: 40, stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_polyphony.yml b/tests/test_polyphony.yml index 34ec8ea..242abae 100644 --- a/tests/test_polyphony.yml +++ b/tests/test_polyphony.yml @@ -1,37 +1,38 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 2 - sequence: [0] - patterns: [[64, 1, 68, 1, 32, 1, 1, 1, 75, 1, 78, 1, 1, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 2 + order: [0] + patterns: [[64, 1, 68, 1, 32, 1, 1, 1, 75, 1, 78, 1, 1, 0, 0, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: oscillator - parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 64} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: oscillator + parameters: {color: 128, detune: 64, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 0, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_pop.yml b/tests/test_pop.yml index b09c5e6..26f7a5a 100644 --- a/tests/test_pop.yml +++ b/tests/test_pop.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: pop - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: pop + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_pop_stereo.yml b/tests/test_pop_stereo.yml index 66c8bde..f3d86a5 100644 --- a/tests/test_pop_stereo.yml +++ b/tests/test_pop_stereo.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: pop - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: pop + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_push.yml b/tests/test_push.yml index 73e1c27..94259e3 100644 --- a/tests/test_push.yml +++ b/tests/test_push.yml @@ -1,21 +1,22 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: push - parameters: {stereo: 0} - - type: pop - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: push + parameters: {stereo: 0} + - type: pop + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_push_stereo.yml b/tests/test_push_stereo.yml index 7cce297..75f7687 100644 --- a/tests/test_push_stereo.yml +++ b/tests/test_push_stereo.yml @@ -1,23 +1,24 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: push - parameters: {stereo: 1} - - type: pop - parameters: {stereo: 0} - - type: pop - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: push + parameters: {stereo: 1} + - type: pop + parameters: {stereo: 0} + - type: pop + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_receive.yml b/tests/test_receive.yml index 363269b..a609eb7 100644 --- a/tests/test_receive.yml +++ b/tests/test_receive.yml @@ -1,27 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: send - parameters: {amount: 128, port: 0, sendpop: 0, stereo: 0, unit: 5, voice: 0} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 6, voice: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 6, voice: 0} - - type: receive - parameters: {stereo: 0} - - type: receive - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: send + parameters: {amount: 128, port: 0, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: receive + parameters: {stereo: 0} + id: 1 + - type: receive + parameters: {stereo: 0} + id: 2 + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_receive_stereo.yml b/tests/test_receive_stereo.yml index fa4137c..dbbfcd5 100644 --- a/tests/test_receive_stereo.yml +++ b/tests/test_receive_stereo.yml @@ -1,25 +1,27 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: send - parameters: {amount: 128, port: 1, sendpop: 0, stereo: 0, unit: 5, voice: 0} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 5, voice: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, unit: 5, voice: 0} - - type: receive - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: send + parameters: {amount: 128, port: 1, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: receive + parameters: {stereo: 1} + id: 1 + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_send.yml b/tests/test_send.yml index 7ba6a3d..107f463 100644 --- a/tests/test_send.yml +++ b/tests/test_send.yml @@ -1,27 +1,30 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: send - parameters: {amount: 96, port: 0, sendpop: 0, stereo: 0, unit: 5, voice: 0} - - type: send - parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, unit: 6, voice: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: send - parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, unit: 6, voice: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: send + parameters: {amount: 96, port: 0, sendpop: 0, stereo: 0, target: 1} + - type: send + parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: send + parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: loadval + id: 1 + parameters: {stereo: 0, value: 64} + - type: loadval + id: 2 + parameters: {stereo: 0, value: 64} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_send_global.yml b/tests/test_send_global.yml index 13ae5a0..066c992 100644 --- a/tests/test_send_global.yml +++ b/tests/test_send_global.yml @@ -1,33 +1,36 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 96} - - type: send - parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, unit: 3, voice: 2} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 32} - - type: send - parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, unit: 2, voice: 1} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 96} + - type: send + parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, target: 1} + - type: loadval + id: 2 + parameters: {stereo: 0, value: 64} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 32} + - type: send + parameters: {amount: 96, port: 0, sendpop: 1, stereo: 0, target: 2} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: loadval + id: 1 + parameters: {stereo: 0, value: 64} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_send_global_stereo.yml b/tests/test_send_global_stereo.yml index 1dc6132..cc0b198 100644 --- a/tests/test_send_global_stereo.yml +++ b/tests/test_send_global_stereo.yml @@ -1,33 +1,36 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +score: + rowsperpattern: 16 + length: 1 + tracks: + - numvoices: 1 + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 1, unit: 3, voice: 2} - - type: receive - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: send - parameters: {amount: 96, port: 0, sendpop: 1, stereo: 1, unit: 3, voice: 1} - - type: receive - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 64} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 1, target: 1} + - type: receive + id: 2 + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: send + parameters: {amount: 96, port: 0, sendpop: 1, stereo: 1, target: 2} + - type: receive + id: 1 + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_send_stereo.yml b/tests/test_send_stereo.yml index b810a9f..11d69b8 100644 --- a/tests/test_send_stereo.yml +++ b/tests/test_send_stereo.yml @@ -1,27 +1,29 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 0} - - type: send - parameters: {amount: 96, port: 0, sendpop: 1, stereo: 1, unit: 6, voice: 0} - - type: loadval - parameters: {stereo: 0, value: 64} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: send - parameters: {amount: 128, port: 0, sendpop: 1, stereo: 1, unit: 6, voice: 0} - - type: receive - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 0} + - type: send + parameters: {amount: 96, port: 0, sendpop: 1, stereo: 1, target: 1} + - type: loadval + parameters: {stereo: 0, value: 64} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: send + parameters: {amount: 128, port: 0, sendpop: 1, stereo: 1, target: 1} + - type: receive + id: 1 + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_speed.yml b/tests/test_speed.yml index 0807d39..9f9214a 100644 --- a/tests/test_speed.yml +++ b/tests/test_speed.yml @@ -1,32 +1,33 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0, 0] - patterns: [[64, 0, 64, 64, 64, 0, 64, 64, 64, 0, 64, 64, 65, 0, 65, 65]] - - numvoices: 1 - sequence: [0, 1] - patterns: [[64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [81, 0, 52, 0, 81, 0, 52, 0, 81, 0, 52, 0, 81, 0, 52, 0]] +score: + rowsperpattern: 16 + length: 2 + tracks: + - numvoices: 1 + order: [0, 0] + patterns: [[64, 0, 64, 64, 64, 0, 64, 64, 64, 0, 64, 64, 65, 0, 65, 65]] + - numvoices: 1 + order: [0, 1] + patterns: [[64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [81, 0, 52, 0, 81, 0, 52, 0, 81, 0, 52, 0, 81, 0, 52, 0]] patch: - instruments: - - numvoices: 1 - units: - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 0} - - type: envelope - parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 0} - - type: oscillator - parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} - - type: oscillator - parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 1, unison: 0} - - type: mulp - parameters: {stereo: 1} - - type: out - parameters: {gain: 128, stereo: 1} - - numvoices: 1 - units: - - type: loadnote - parameters: {stereo: 0} - - type: speed - parameters: {} + - numvoices: 1 + units: + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 0} + - type: envelope + parameters: {attack: 64, decay: 64, gain: 128, release: 64, stereo: 0, sustain: 0} + - type: oscillator + parameters: {color: 96, detune: 32, gain: 128, lfo: 0, phase: 0, shape: 64, stereo: 0, transpose: 64, type: 1, unison: 0} + - type: oscillator + parameters: {color: 64, detune: 64, gain: 128, lfo: 0, phase: 64, shape: 96, stereo: 0, transpose: 72, type: 1, unison: 0} + - type: mulp + parameters: {stereo: 1} + - type: out + parameters: {gain: 128, stereo: 1} + - numvoices: 1 + units: + - type: loadnote + parameters: {stereo: 0} + - type: speed + parameters: {} diff --git a/tests/test_xch.yml b/tests/test_xch.yml index 97baf2a..4b12d4b 100644 --- a/tests/test_xch.yml +++ b/tests/test_xch.yml @@ -1,19 +1,20 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 96} - - type: loadval - parameters: {stereo: 0, value: 32} - - type: xch - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 96} + - type: loadval + parameters: {stereo: 0, value: 32} + - type: xch + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/tests/test_xch_stereo.yml b/tests/test_xch_stereo.yml index 64cd22c..a4ebf74 100644 --- a/tests/test_xch_stereo.yml +++ b/tests/test_xch_stereo.yml @@ -1,27 +1,28 @@ bpm: 100 -rowsperpattern: 16 rowsperbeat: 4 -tracks: - - numvoices: 1 - sequence: [0] - patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] -patch: - instruments: +score: + rowsperpattern: 16 + length: 1 + tracks: - numvoices: 1 - units: - - type: loadval - parameters: {stereo: 0, value: 0} - - type: loadval - parameters: {stereo: 0, value: 128} - - type: loadval - parameters: {stereo: 0, value: 32} - - type: loadval - parameters: {stereo: 0, value: 96} - - type: xch - parameters: {stereo: 1} - - type: pop - parameters: {stereo: 0} - - type: pop - parameters: {stereo: 0} - - type: out - parameters: {gain: 128, stereo: 1} + order: [0] + patterns: [[64, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]] +patch: + - numvoices: 1 + units: + - type: loadval + parameters: {stereo: 0, value: 0} + - type: loadval + parameters: {stereo: 0, value: 128} + - type: loadval + parameters: {stereo: 0, value: 32} + - type: loadval + parameters: {stereo: 0, value: 96} + - type: xch + parameters: {stereo: 1} + - type: pop + parameters: {stereo: 0} + - type: pop + parameters: {stereo: 0} + - type: out + parameters: {gain: 128, stereo: 1} diff --git a/track.go b/track.go new file mode 100644 index 0000000..7e212f2 --- /dev/null +++ b/track.go @@ -0,0 +1,25 @@ +package sointu + +type Track struct { + NumVoices int + Effect bool `yaml:",omitempty"` + Order []int `yaml:",flow"` + Patterns [][]byte `yaml:",flow"` +} + +func (t *Track) Copy() Track { + order := make([]int, len(t.Order)) + copy(order, t.Order) + patterns := make([][]byte, len(t.Patterns)) + for i, oldPat := range t.Patterns { + newPat := make([]byte, len(oldPat)) + copy(newPat, oldPat) + patterns[i] = newPat + } + return Track{ + NumVoices: t.NumVoices, + Effect: t.Effect, + Order: order, + Patterns: patterns, + } +} diff --git a/tracker/defaultsong.go b/tracker/defaultsong.go index f78260d..1ea0ef7 100644 --- a/tracker/defaultsong.go +++ b/tracker/defaultsong.go @@ -6,6 +6,8 @@ import ( "github.com/vsariola/sointu" ) +var UnitTypeNames []string + var defaultUnits = map[string]sointu.Unit{ "envelope": {Type: "envelope", Parameters: map[string]int{"stereo": 0, "attack": 64, "decay": 64, "sustain": 64, "release": 64, "gain": 64}}, "oscillator": {Type: "oscillator", Parameters: map[string]int{"stereo": 0, "transpose": 64, "detune": 64, "phase": 0, "color": 64, "shape": 64, "gain": 64, "type": sointu.Sine}}, @@ -40,16 +42,6 @@ var defaultUnits = map[string]sointu.Unit{ "send": {Type: "send", Parameters: map[string]int{"stereo": 0, "amount": 128, "voice": 0, "unit": 0, "port": 0, "sendpop": 1}}, } -var allUnits []string - -func init() { - allUnits = make([]string, 0, len(sointu.UnitTypes)) - for k := range sointu.UnitTypes { - allUnits = append(allUnits, k) - } - sort.Strings(allUnits) -} - var defaultInstrument = sointu.Instrument{ Name: "Instr", NumVoices: 1, @@ -64,14 +56,16 @@ var defaultInstrument = sointu.Instrument{ } var defaultSong = sointu.Song{ - BPM: 100, - RowsPerPattern: 16, - RowsPerBeat: 4, - Tracks: []sointu.Track{ - {NumVoices: 1, Sequence: []byte{0, 0, 0, 1}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}, {64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 75, 0, 75, 0, 80, 0}}}, + BPM: 100, + RowsPerBeat: 4, + Score: sointu.Score{ + RowsPerPattern: 16, + Length: 4, + Tracks: []sointu.Track{ + {NumVoices: 1, Order: []int{0, 0, 0, 1}, Patterns: [][]byte{{64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 78, 0, 0, 0, 0, 0}, {64, 0, 68, 0, 32, 0, 0, 0, 75, 0, 75, 0, 75, 0, 80, 0}}}, + }, }, - Patch: sointu.Patch{Instruments: []sointu.Instrument{ - defaultInstrument, + Patch: sointu.Patch{defaultInstrument, {Name: "Global", NumVoices: 1, Units: []sointu.Unit{ defaultUnits["in"], {Type: "delay", @@ -80,5 +74,13 @@ var defaultSong = sointu.Song{ 1140, 1212, 1300, 1380, 1446, 1516, 1580, 1642, }}, defaultUnits["out"], - }}}}, + }}}, +} + +func init() { + UnitTypeNames = make([]string, 0, len(sointu.UnitTypes)) + for k := range sointu.UnitTypes { + UnitTypeNames = append(UnitTypeNames, k) + } + sort.Strings(UnitTypeNames) } diff --git a/tracker/alert.go b/tracker/gioui/alert.go similarity index 99% rename from tracker/alert.go rename to tracker/gioui/alert.go index 468ff51..66d63d9 100644 --- a/tracker/alert.go +++ b/tracker/gioui/alert.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image/color" diff --git a/tracker/draglist.go b/tracker/gioui/draglist.go similarity index 99% rename from tracker/draglist.go rename to tracker/gioui/draglist.go index 31616a0..c4aec10 100644 --- a/tracker/draglist.go +++ b/tracker/gioui/draglist.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" diff --git a/tracker/files.go b/tracker/gioui/files.go similarity index 89% rename from tracker/files.go rename to tracker/gioui/files.go index 8c976a8..b3ade49 100644 --- a/tracker/files.go +++ b/tracker/gioui/files.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "encoding/json" @@ -26,7 +26,7 @@ func (t *Tracker) LoadSongFile() { return } } - t.LoadSong(song) + t.SetSong(song) } func (t *Tracker) SaveSongFile() { @@ -37,9 +37,9 @@ func (t *Tracker) SaveSongFile() { var extension = filepath.Ext(filename) var contents []byte if extension == "json" { - contents, err = json.Marshal(t.song) + contents, err = json.Marshal(t.Song()) } else { - contents, err = yaml.Marshal(t.song) + contents, err = yaml.Marshal(t.Song()) } if err != nil { return diff --git a/tracker/iconcache.go b/tracker/gioui/iconcache.go similarity index 96% rename from tracker/iconcache.go rename to tracker/gioui/iconcache.go index b0a4b78..b79eadb 100644 --- a/tracker/iconcache.go +++ b/tracker/gioui/iconcache.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "log" diff --git a/tracker/instruments.go b/tracker/gioui/instruments.go similarity index 80% rename from tracker/instruments.go rename to tracker/gioui/instruments.go index 27e9366..2ee38a4 100644 --- a/tracker/instruments.go +++ b/tracker/gioui/instruments.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "fmt" @@ -14,6 +14,7 @@ import ( "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" + "github.com/vsariola/sointu/tracker" "golang.org/x/exp/shiny/materialdesign/icons" "gopkg.in/yaml.v3" ) @@ -26,8 +27,8 @@ func (t *Tracker) layoutInstruments(gtx C) D { if !ok { continue } - if e.Type == pointer.Press && (t.EditMode != EditUnits && t.EditMode != EditParameters) { - t.EditMode = EditUnits + if e.Type == pointer.Press && (t.EditMode() != tracker.EditUnits && t.EditMode() != tracker.EditParameters) { + t.SetEditMode(tracker.EditUnits) } } rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y) @@ -36,12 +37,12 @@ func (t *Tracker) layoutInstruments(gtx C) D { Types: pointer.Press, }.Add(gtx.Ops) for t.NewInstrumentBtn.Clicked() { - t.AddInstrument() + t.AddInstrument(true) } btnStyle := material.IconButton(t.Theme, t.NewInstrumentBtn, widgetForIcon(icons.ContentAdd)) btnStyle.Background = transparent btnStyle.Inset = layout.UniformInset(unit.Dp(6)) - if t.song.Patch.TotalVoices() < 32 { + if t.CanAddInstrument() { btnStyle.Color = primaryColor } else { btnStyle.Color = disabledTextColor @@ -54,7 +55,7 @@ func (t *Tracker) layoutInstruments(gtx C) D { return layout.Stack{}.Layout(gtx, layout.Stacked(t.layoutInstrumentNames), layout.Expanded(func(gtx C) D { - return t.InstrumentScrollBar.Layout(gtx, unit.Dp(6), len(t.song.Patch.Instruments), &t.InstrumentDragList.List.Position) + return t.InstrumentScrollBar.Layout(gtx, unit.Dp(6), len(t.Song().Patch), &t.InstrumentDragList.List.Position) }), ) }), @@ -77,7 +78,7 @@ func (t *Tracker) layoutInstrumentHeader(gtx C) D { deleteInstrumentBtnStyle := material.IconButton(t.Theme, t.DeleteInstrumentBtn, widgetForIcon(icons.ActionDelete)) deleteInstrumentBtnStyle.Background = transparent deleteInstrumentBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) - if len(t.song.Patch.Instruments) > 1 { + if t.CanDeleteInstrument() { deleteInstrumentBtnStyle.Color = primaryColor } else { deleteInstrumentBtnStyle.Color = disabledTextColor @@ -86,11 +87,8 @@ func (t *Tracker) layoutInstrumentHeader(gtx C) D { return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(Label("Voices: ", white)), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - maxRemain := 32 - t.song.Patch.TotalVoices() + t.song.Patch.Instruments[t.CurrentInstrument].NumVoices - if maxRemain < 0 { - maxRemain = 0 - } - t.InstrumentVoices.Value = t.song.Patch.Instruments[t.CurrentInstrument].NumVoices + maxRemain := t.MaxInstrumentVoices() + t.InstrumentVoices.Value = t.Instrument().NumVoices numStyle := NumericUpDown(t.Theme, t.InstrumentVoices, 0, maxRemain) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) @@ -103,16 +101,16 @@ func (t *Tracker) layoutInstrumentHeader(gtx C) D { layout.Rigid(deleteInstrumentBtnStyle.Layout)) } for t.CopyInstrumentBtn.Clicked() { - contents, err := yaml.Marshal(t.song.Patch.Instruments[t.CurrentInstrument]) + contents, err := yaml.Marshal(t.Instrument()) if err == nil { clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops) t.Alert.Update("Instrument copied to clipboard", Notify, time.Second*3) } } for t.DeleteInstrumentBtn.Clicked() { - t.DeleteInstrument() + t.DeleteInstrument(false) } - return Surface{Gray: 37, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, header) + return Surface{Gray: 37, Focus: t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters}.Layout(gtx, header) } func (t *Tracker) layoutInstrumentNames(gtx C) D { @@ -120,11 +118,11 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D { gtx.Constraints.Min.Y = gtx.Px(unit.Dp(36)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(30)) grabhandle := LabelStyle{Text: "", ShadeColor: black, Color: white, FontSize: unit.Sp(10), Alignment: layout.Center} - if i == t.CurrentInstrument { + if i == t.InstrIndex() { grabhandle.Text = ":::" } label := func(gtx C) D { - if i == t.CurrentInstrument { + if i == t.InstrIndex() { for _, ev := range t.InstrumentNameEditor.Events() { _, ok := ev.(widget.SubmitEvent) if ok { @@ -132,7 +130,7 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D { break } } - if n := t.song.Patch.Instruments[t.CurrentInstrument].Name; n != t.InstrumentNameEditor.Text() { + if n := t.Instrument().Name; n != t.InstrumentNameEditor.Text() { t.InstrumentNameEditor.SetText(n) } editor := material.Editor(t.Theme, t.InstrumentNameEditor, "Instr") @@ -143,7 +141,7 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D { t.SetInstrumentName(t.InstrumentNameEditor.Text()) return dims } - text := t.song.Patch.Instruments[i].Name + text := t.Song().Patch[i].Name if text == "" { text = "Instr" } @@ -159,22 +157,19 @@ func (t *Tracker) layoutInstrumentNames(gtx C) D { } color := inactiveLightSurfaceColor - if t.EditMode == EditUnits || t.EditMode == EditParameters { + if t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters { color = activeLightSurfaceColor } - instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.song.Patch.Instruments), element, t.SwapInstruments) + instrumentList := FilledDragList(t.Theme, t.InstrumentDragList, len(t.Song().Patch), element, t.SwapInstruments) instrumentList.SelectedColor = color instrumentList.HoverColor = instrumentHoverColor - t.InstrumentDragList.SelectedItem = t.CurrentInstrument + t.InstrumentDragList.SelectedItem = t.InstrIndex() defer op.Save(gtx.Ops).Load() pointer.PassOp{Pass: true}.Add(gtx.Ops) dims := instrumentList.Layout(gtx) - if t.CurrentInstrument != t.InstrumentDragList.SelectedItem { - t.CurrentInstrument = t.InstrumentDragList.SelectedItem - if l := len(t.song.Patch.Instruments[t.CurrentInstrument].Units); t.CurrentUnit >= l { - t.CurrentUnit = l - 1 - } + if t.InstrIndex() != t.InstrumentDragList.SelectedItem { + t.SetInstrIndex(t.InstrumentDragList.SelectedItem) op.InvalidateOp{}.Add(gtx.Ops) } return dims @@ -188,7 +183,7 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D { addUnitBtnStyle.Background = t.Theme.Fg addUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(4)) - units := t.song.Patch.Instruments[t.CurrentInstrument].Units + units := t.Instrument().Units for len(t.StackUse) < len(units) { t.StackUse = append(t.StackUse, 0) } @@ -238,26 +233,26 @@ func (t *Tracker) layoutInstrumentEditor(gtx C) D { unitList := FilledDragList(t.Theme, t.UnitDragList, len(units), element, t.SwapUnits) - if t.EditMode == EditUnits { + if t.EditMode() == tracker.EditUnits { unitList.SelectedColor = cursorColor } - t.UnitDragList.SelectedItem = t.CurrentUnit - return Surface{Gray: 30, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D { + t.UnitDragList.SelectedItem = t.UnitIndex() + return Surface{Gray: 30, Focus: t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters}.Layout(gtx, func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(func(gtx C) D { return layout.Stack{Alignment: layout.SE}.Layout(gtx, layout.Expanded(func(gtx C) D { dims := unitList.Layout(gtx) - if t.CurrentUnit != t.UnitDragList.SelectedItem { - t.CurrentUnit = t.UnitDragList.SelectedItem - t.EditMode = EditUnits + if t.UnitIndex() != t.UnitDragList.SelectedItem { + t.SetUnitIndex(t.UnitDragList.SelectedItem) + t.SetEditMode(tracker.EditUnits) op.InvalidateOp{}.Add(gtx.Ops) } return dims }), layout.Expanded(func(gtx C) D { - return t.UnitScrollBar.Layout(gtx, unit.Dp(10), len(t.song.Patch.Instruments[t.CurrentInstrument].Units), &t.UnitDragList.List.Position) + return t.UnitScrollBar.Layout(gtx, unit.Dp(10), len(t.Instrument().Units), &t.UnitDragList.List.Position) }), layout.Stacked(func(gtx C) D { margin := layout.Inset{Right: unit.Dp(20), Bottom: unit.Dp(1)} diff --git a/tracker/gioui/keyevent.go b/tracker/gioui/keyevent.go new file mode 100644 index 0000000..69d9916 --- /dev/null +++ b/tracker/gioui/keyevent.go @@ -0,0 +1,454 @@ +package gioui + +import ( + "strconv" + "strings" + "time" + + "gioui.org/app" + "gioui.org/io/key" + "github.com/vsariola/sointu/tracker" + "gopkg.in/yaml.v3" +) + +var noteMap = map[string]int{ + "Z": -12, + "S": -11, + "X": -10, + "D": -9, + "C": -8, + "V": -7, + "G": -6, + "B": -5, + "H": -4, + "N": -3, + "J": -2, + "M": -1, + ",": 0, + "L": 1, + ".": 2, + "Q": 0, + "2": 1, + "W": 2, + "3": 3, + "E": 4, + "R": 5, + "5": 6, + "T": 7, + "6": 8, + "Y": 9, + "7": 10, + "U": 11, + "I": 12, + "9": 13, + "O": 14, + "0": 15, + "P": 16, +} + +var unitKeyMap = map[string]string{ + "e": "envelope", + "o": "oscillator", + "m": "mulp", + "M": "mul", + "a": "addp", + "A": "add", + "p": "pan", + "S": "push", + "P": "pop", + "O": "out", + "l": "loadnote", + "L": "loadval", + "h": "xch", + "d": "delay", + "D": "distort", + "H": "hold", + "b": "crush", + "g": "gain", + "i": "invgain", + "f": "filter", + "I": "clip", + "E": "speed", + "r": "compressor", + "u": "outaux", + "U": "aux", + "s": "send", + "n": "noise", + "N": "in", + "R": "receive", +} + +// KeyEvent handles incoming key events and returns true if repaint is needed. +func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool { + if e.State == key.Press { + if t.InstrumentNameEditor.Focused() { + return false + } + switch e.Name { + case "C": + if e.Modifiers.Contain(key.ModShortcut) { + contents, err := yaml.Marshal(t.Song()) + if err == nil { + w.WriteClipboard(string(contents)) + t.Alert.Update("Song copied to clipboard", Notify, time.Second*3) + } + return true + } + case "V": + if e.Modifiers.Contain(key.ModShortcut) { + w.ReadClipboard() + return true + } + case "Z": + if e.Modifiers.Contain(key.ModShortcut) { + t.Undo() + return true + } + case "Y": + if e.Modifiers.Contain(key.ModShortcut) { + t.Redo() + return true + } + case "N": + if e.Modifiers.Contain(key.ModShortcut) { + t.ResetSong() + return true + } + case "S": + if e.Modifiers.Contain(key.ModShortcut) { + t.SaveSongFile() + return false + } + case "O": + if e.Modifiers.Contain(key.ModShortcut) { + t.LoadSongFile() + return true + } + case "F1": + t.SetEditMode(tracker.EditPatterns) + return true + case "F2": + t.SetEditMode(tracker.EditTracks) + return true + case "F3": + t.SetEditMode(tracker.EditUnits) + return true + case "F4": + t.SetEditMode(tracker.EditParameters) + return true + case "F5": + t.SetNoteTracking(true) + startRow := t.Cursor().SongRow + if t.EditMode() == tracker.EditPatterns { + startRow.Row = 0 + } + t.player.Play(startRow) + return true + case "F6": + t.SetNoteTracking(false) + startRow := t.Cursor().SongRow + if t.EditMode() == tracker.EditPatterns { + startRow.Row = 0 + } + t.player.Play(startRow) + return true + case "F8": + t.player.Stop() + return true + case key.NameDeleteForward, key.NameDeleteBackward: + switch t.EditMode() { + case tracker.EditPatterns: + if e.Modifiers.Contain(key.ModShortcut) { + t.DeleteOrderRow(e.Name == key.NameDeleteForward) + } else { + t.DeletePatternSelection() + if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 { + t.SetCursor(t.Cursor().AddPatterns(1)) + t.SetSelectionCorner(t.Cursor()) + } + } + return true + case tracker.EditTracks: + t.DeleteSelection() + if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 { + t.SetCursor(t.Cursor().AddRows(t.Step.Value)) + t.SetSelectionCorner(t.Cursor()) + } + return true + case tracker.EditUnits: + t.DeleteUnit(e.Name == key.NameDeleteForward) + return true + } + case "Space": + _, playing := t.player.Position() + if !playing { + t.SetNoteTracking(!e.Modifiers.Contain(key.ModShortcut)) + startRow := t.Cursor().SongRow + if t.EditMode() == tracker.EditPatterns { + startRow.Row = 0 + } + t.player.Play(startRow) + } else { + t.player.Stop() + } + return true + case `\`, `<`, `>`: + if e.Modifiers.Contain(key.ModShift) { + return t.SetOctave(t.Octave() + 1) + } + return t.SetOctave(t.Octave() - 1) + case key.NameTab: + if e.Modifiers.Contain(key.ModShift) { + t.SetEditMode((t.EditMode() - 1 + 4) % 4) + } else { + t.SetEditMode((t.EditMode() + 1) % 4) + } + return true + case key.NameReturn: + switch t.EditMode() { + case tracker.EditPatterns: + t.AddOrderRow(!e.Modifiers.Contain(key.ModShortcut)) + case tracker.EditUnits: + t.AddUnit(!e.Modifiers.Contain(key.ModShortcut)) + } + case key.NameUpArrow: + cursor := t.Cursor() + switch t.EditMode() { + case tracker.EditPatterns: + if e.Modifiers.Contain(key.ModShortcut) { + cursor.SongRow = tracker.SongRow{} + } else { + cursor.Row -= t.Song().Score.RowsPerPattern + } + t.SetNoteTracking(false) + case tracker.EditTracks: + if e.Modifiers.Contain(key.ModShortcut) { + cursor.Row -= t.Song().Score.RowsPerPattern + } else { + if t.Step.Value > 0 { + cursor.Row -= t.Step.Value + } else { + cursor.Row-- + } + } + t.SetNoteTracking(false) + case tracker.EditUnits: + t.SetUnitIndex(t.UnitIndex() - 1) + case tracker.EditParameters: + t.SetParamIndex(t.ParamIndex() - 1) + } + t.SetCursor(cursor) + if !e.Modifiers.Contain(key.ModShift) { + t.SetSelectionCorner(t.Cursor()) + } + return true + case key.NameDownArrow: + cursor := t.Cursor() + switch t.EditMode() { + case tracker.EditPatterns: + if e.Modifiers.Contain(key.ModShortcut) { + cursor.Row = t.Song().Score.LengthInRows() - 1 + } else { + cursor.Row += t.Song().Score.RowsPerPattern + } + t.SetNoteTracking(false) + case tracker.EditTracks: + if e.Modifiers.Contain(key.ModShortcut) { + cursor.Row += t.Song().Score.RowsPerPattern + } else { + if t.Step.Value > 0 { + cursor.Row += t.Step.Value + } else { + cursor.Row++ + } + } + t.SetNoteTracking(false) + case tracker.EditUnits: + t.SetUnitIndex(t.UnitIndex() + 1) + case tracker.EditParameters: + t.SetParamIndex(t.ParamIndex() + 1) + } + t.SetCursor(cursor) + if !e.Modifiers.Contain(key.ModShift) { + t.SetSelectionCorner(t.Cursor()) + } + return true + case key.NameLeftArrow: + cursor := t.Cursor() + switch t.EditMode() { + case tracker.EditPatterns: + if e.Modifiers.Contain(key.ModShortcut) { + cursor.Track = 0 + } else { + cursor.Track-- + } + case tracker.EditTracks: + if !t.LowNibble() || !t.Song().Score.Tracks[t.Cursor().Track].Effect || e.Modifiers.Contain(key.ModShortcut) { + cursor.Track-- + t.SetLowNibble(true) + } else { + t.SetLowNibble(false) + } + case tracker.EditUnits: + t.SetInstrIndex(t.InstrIndex() - 1) + case tracker.EditParameters: + param, _ := t.Param(t.ParamIndex()) + if e.Modifiers.Contain(key.ModShift) { + t.SetParam(param.Value - 16) + } else { + t.SetParam(param.Value - 1) + } + } + t.SetCursor(cursor) + if !e.Modifiers.Contain(key.ModShift) { + t.SetSelectionCorner(t.Cursor()) + } + return true + case key.NameRightArrow: + switch t.EditMode() { + case tracker.EditPatterns: + cursor := t.Cursor() + if e.Modifiers.Contain(key.ModShortcut) { + cursor.Track = len(t.Song().Score.Tracks) - 1 + } else { + cursor.Track++ + } + t.SetCursor(cursor) + case tracker.EditTracks: + if t.LowNibble() || !t.Song().Score.Tracks[t.Cursor().Track].Effect || e.Modifiers.Contain(key.ModShortcut) { + cursor := t.Cursor() + cursor.Track++ + t.SetCursor(cursor) + t.SetLowNibble(false) + } else { + t.SetLowNibble(true) + } + case tracker.EditUnits: + t.SetInstrIndex(t.InstrIndex() + 1) + case tracker.EditParameters: + param, _ := t.Param(t.ParamIndex()) + if e.Modifiers.Contain(key.ModShift) { + t.SetParam(param.Value + 16) + } else { + t.SetParam(param.Value + 1) + } + } + if !e.Modifiers.Contain(key.ModShift) { + t.SetSelectionCorner(t.Cursor()) + } + return true + case "+": + switch t.EditMode() { + case tracker.EditTracks: + if e.Modifiers.Contain(key.ModShortcut) { + t.AdjustSelectionPitch(12) + } else { + t.AdjustSelectionPitch(1) + } + return true + } + case "-": + switch t.EditMode() { + case tracker.EditTracks: + if e.Modifiers.Contain(key.ModShortcut) { + t.AdjustSelectionPitch(-12) + } else { + t.AdjustSelectionPitch(-1) + } + return true + } + } + switch t.EditMode() { + case tracker.EditPatterns: + if iv, err := strconv.Atoi(e.Name); err == nil { + t.SetCurrentPattern(iv) + if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 { + t.SetCursor(t.Cursor().AddPatterns(1)) + t.SetSelectionCorner(t.Cursor()) + } + return true + } + if b := int(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 { + t.SetCurrentPattern(b + 10) + if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 { + t.SetCursor(t.Cursor().AddPatterns(1)) + t.SetSelectionCorner(t.Cursor()) + } + return true + } + case tracker.EditTracks: + if t.Song().Score.Tracks[t.Cursor().Track].Effect { + if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil { + t.NumberPressed(byte(iv)) + } + } else { + if e.Name == "A" { + t.SetNote(0) + } else { + if val, ok := noteMap[e.Name]; ok { + if _, ok := t.KeyPlaying[e.Name]; !ok { + n := tracker.NoteAsValue(t.OctaveNumberInput.Value, val) + t.SetNote(n) + trk := t.Cursor().Track + start := t.Song().Score.FirstVoiceForTrack(trk) + end := start + t.Song().Score.Tracks[trk].NumVoices + t.KeyPlaying[e.Name] = t.player.Trigger(start, end, n) + } + } + } + } + if !(t.NoteTracking() && t.player.Playing()) && t.Step.Value > 0 { + t.SetCursor(t.Cursor().AddRows(t.Step.Value)) + t.SetSelectionCorner(t.Cursor()) + } + return true + case tracker.EditUnits: + name := e.Name + if !e.Modifiers.Contain(key.ModShift) { + name = strings.ToLower(name) + } + if val, ok := unitKeyMap[name]; ok { + if e.Modifiers.Contain(key.ModShortcut) { + t.SetUnitType(val) + return true + } + } + fallthrough + case tracker.EditParameters: + if val, ok := noteMap[e.Name]; ok { + if _, ok := t.KeyPlaying[e.Name]; !ok { + n := tracker.NoteAsValue(t.OctaveNumberInput.Value, val) + instr := t.InstrIndex() + start := t.Song().Patch.FirstVoiceForInstrument(instr) + end := start + t.Instrument().NumVoices + t.KeyPlaying[e.Name] = t.player.Trigger(start, end, n) + return false + } + } + } + } + if e.State == key.Release { + if ID, ok := t.KeyPlaying[e.Name]; ok { + t.player.Release(ID) + delete(t.KeyPlaying, e.Name) + if _, playing := t.player.Position(); t.EditMode() == tracker.EditTracks && playing && t.Note() == 1 && t.NoteTracking() { + t.SetNote(0) + } + } + } + return false +} + +// NumberPressed handles incoming presses while in either of the hex number columns +func (t *Tracker) NumberPressed(iv byte) { + val := t.Note() + if val == 1 { + val = 0 + } + if t.LowNibble() { + val = (val & 0xF0) | (iv & 0xF) + } else { + val = ((iv & 0xF) << 4) | (val & 0xF) + } + t.SetNote(val) +} diff --git a/tracker/label.go b/tracker/gioui/label.go similarity index 98% rename from tracker/label.go rename to tracker/gioui/label.go index 4b2ef6f..1aae016 100644 --- a/tracker/label.go +++ b/tracker/gioui/label.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" diff --git a/tracker/layout.go b/tracker/gioui/layout.go similarity index 73% rename from tracker/layout.go rename to tracker/gioui/layout.go index dde8301..ba2583d 100644 --- a/tracker/layout.go +++ b/tracker/gioui/layout.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" @@ -6,6 +6,7 @@ import ( "gioui.org/layout" "gioui.org/op/clip" "gioui.org/op/paint" + "github.com/vsariola/sointu/tracker" ) type C = layout.Context @@ -22,10 +23,10 @@ func (t *Tracker) Layout(gtx layout.Context) { func (t *Tracker) layoutBottom(gtx layout.Context) layout.Dimensions { return t.BottomHorizontalSplit.Layout(gtx, func(gtx C) D { - return Surface{Gray: 24, Focus: t.EditMode == 0}.Layout(gtx, t.layoutPatterns) + return Surface{Gray: 24, Focus: t.EditMode() == tracker.EditPatterns}.Layout(gtx, t.layoutPatterns) }, func(gtx C) D { - return Surface{Gray: 24, Focus: t.EditMode == 1}.Layout(gtx, t.layoutTracker) + return Surface{Gray: 24, Focus: t.EditMode() == tracker.EditTracks}.Layout(gtx, t.layoutTracker) }, ) } diff --git a/tracker/menu.go b/tracker/gioui/menu.go similarity index 99% rename from tracker/menu.go rename to tracker/gioui/menu.go index b50332c..a498a7b 100644 --- a/tracker/menu.go +++ b/tracker/gioui/menu.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" diff --git a/tracker/numericupdown.go b/tracker/gioui/numericupdown.go similarity index 99% rename from tracker/numericupdown.go rename to tracker/gioui/numericupdown.go index 88cf6f4..7917e51 100644 --- a/tracker/numericupdown.go +++ b/tracker/gioui/numericupdown.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "fmt" diff --git a/tracker/gioui/parameter.go b/tracker/gioui/parameter.go new file mode 100644 index 0000000..3b7e173 --- /dev/null +++ b/tracker/gioui/parameter.go @@ -0,0 +1,205 @@ +package gioui + +import ( + "fmt" + + "gioui.org/layout" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" + "github.com/vsariola/sointu/tracker" + "golang.org/x/exp/shiny/materialdesign/icons" +) + +type ParameterWidget struct { + floatWidget widget.Float + boolWidget widget.Bool + labelBtn widget.Clickable + instrBtn widget.Clickable + instrMenu Menu + unitBtn widget.Clickable + unitMenu Menu +} + +type ParameterStyle struct { + tracker *Tracker + Parameter *tracker.Parameter + ParameterWidget *ParameterWidget + Theme *material.Theme + Focus bool +} + +func (t *Tracker) ParamStyle(th *material.Theme, param *tracker.Parameter, paramWidget *ParameterWidget) ParameterStyle { + return ParameterStyle{ + tracker: t, // TODO: we need this to pull the instrument names for ID style parameters, find out another way + Parameter: param, + Theme: th, + ParameterWidget: paramWidget, + } +} + +func (p *ParameterWidget) Clicked() bool { + return p.labelBtn.Clicked() +} + +func (p ParameterStyle) Layout(gtx C) D { + return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(func(gtx C) D { + return layout.Stack{}.Layout(gtx, + layout.Stacked(func(gtx C) D { + gtx.Constraints.Min.X = gtx.Px(unit.Dp(110)) + return layout.E.Layout(gtx, Label(p.Parameter.Name, white)) + }), + layout.Expanded(p.ParameterWidget.labelBtn.Layout), + ) + }), + layout.Rigid(func(gtx C) D { + switch p.Parameter.Type { + case tracker.IntegerParameter: + gtx.Constraints.Min.X = gtx.Px(unit.Dp(200)) + gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40)) + if p.Focus { + paint.FillShape(gtx.Ops, cursorColor, clip.Rect{ + Max: gtx.Constraints.Min, + }.Op()) + } + if !p.ParameterWidget.floatWidget.Dragging() { + p.ParameterWidget.floatWidget.Value = float32(p.Parameter.Value) + } + sliderStyle := material.Slider(p.Theme, &p.ParameterWidget.floatWidget, float32(p.Parameter.Min), float32(p.Parameter.Max)) + sliderStyle.Color = p.Theme.Fg + dims := sliderStyle.Layout(gtx) + p.Parameter.Value = int(p.ParameterWidget.floatWidget.Value + 0.5) + return dims + case tracker.BoolParameter: + gtx.Constraints.Min.X = gtx.Px(unit.Dp(60)) + gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40)) + if p.Focus { + paint.FillShape(gtx.Ops, cursorColor, clip.Rect{ + Max: gtx.Constraints.Min, + }.Op()) + } + p.ParameterWidget.boolWidget.Value = p.Parameter.Value > p.Parameter.Min + boolStyle := material.Switch(p.Theme, &p.ParameterWidget.boolWidget) + boolStyle.Color.Disabled = p.Theme.Fg + boolStyle.Color.Enabled = white + dims := layout.Center.Layout(gtx, boolStyle.Layout) + if p.ParameterWidget.boolWidget.Value { + p.Parameter.Value = p.Parameter.Max + } else { + p.Parameter.Value = p.Parameter.Min + } + return dims + case tracker.IDParameter: + gtx.Constraints.Min.X = gtx.Px(unit.Dp(200)) + gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40)) + if p.Focus { + paint.FillShape(gtx.Ops, cursorColor, clip.Rect{ + Max: gtx.Constraints.Min, + }.Op()) + } + for clickedItem, hasClicked := p.ParameterWidget.instrMenu.Clicked(); hasClicked; { + p.Parameter.Value = p.tracker.Song().Patch[clickedItem].Units[0].ID + clickedItem, hasClicked = p.ParameterWidget.instrMenu.Clicked() + } + instrItems := make([]MenuItem, len(p.tracker.Song().Patch)) + for i, instr := range p.tracker.Song().Patch { + instrItems[i].Text = instr.Name + instrItems[i].IconBytes = icons.NavigationChevronRight + } + var unitItems []MenuItem + instrName := "" + unitName := "" + targetI, targetU, err := p.tracker.Song().Patch.FindSendTarget(p.Parameter.Value) + if err == nil { + targetInstrument := p.tracker.Song().Patch[targetI] + instrName = targetInstrument.Name + units := targetInstrument.Units + unitName = fmt.Sprintf("%v: %v", targetU, units[targetU].Type) + unitItems = make([]MenuItem, len(units)) + for clickedItem, hasClicked := p.ParameterWidget.unitMenu.Clicked(); hasClicked; { + p.Parameter.Value = units[clickedItem].ID + clickedItem, hasClicked = p.ParameterWidget.unitMenu.Clicked() + } + for j, unit := range units { + unitItems[j].Text = fmt.Sprintf("%v: %v", j, unit.Type) + unitItems[j].IconBytes = icons.NavigationChevronRight + } + } + return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, + layout.Rigid(p.tracker.layoutMenu(instrName, &p.ParameterWidget.instrBtn, &p.ParameterWidget.instrMenu, unit.Dp(200), + instrItems..., + )), + layout.Rigid(p.tracker.layoutMenu(unitName, &p.ParameterWidget.unitBtn, &p.ParameterWidget.unitMenu, unit.Dp(200), + unitItems..., + )), + ) + } + return D{} + }), + layout.Rigid(func(gtx C) D { + if p.Parameter.Type != tracker.IDParameter { + return Label(p.Parameter.Hint, white)(gtx) + } + return D{} + }), + ) +} + +/* + +func (t *Tracker) layoutParameter(gtx C, index int) D { + u := t.Unit() + ut, _ := sointu.UnitTypes[u.Type] + + params := u.Parameters + var name string + var value, min, max int + var valueText string + if u.Type == "oscillator" && index == len(ut) { + name = "sample" + key := compiler.SampleOffset{Start: uint32(params["samplestart"]), LoopStart: uint16(params["loopstart"]), LoopLength: uint16(params["looplength"])} + if v, ok := tracker.GmDlsEntryMap[key]; ok { + value = v + 1 + valueText = fmt.Sprintf("%v / %v", value, tracker.GmDlsEntries[v].Name) + } else { + value = 0 + valueText = "0 / custom" + } + min, max = 0, len(tracker.GmDlsEntries) + } else { + if ut[index].MaxValue < ut[index].MinValue { + return layout.Dimensions{} + } + name = ut[index].Name + if u.Type == "oscillator" && (name == "samplestart" || name == "loopstart" || name == "looplength") { + if params["type"] != sointu.Sample { + return layout.Dimensions{} + } + } + value = params[name] + min, max = ut[index].MinValue, ut[index].MaxValue + if u.Type == "send" && name == "voice" { + max = t.Song().Patch.NumVoices() + } else if u.Type == "send" && name == "unit" { // set the maximum values depending on the send target + instrIndex, _, _ := t.Song().Patch.FindSendTarget(t.Unit().Parameters["target"]) + if instrIndex != -1 { + max = len(t.Song().Patch[instrIndex].Units) - 1 + } + } else if u.Type == "send" && name == "port" { // set the maximum values depending on the send target + instrIndex, unitIndex, _ := t.Song().Patch.FindSendTarget(t.Unit().Parameters["target"]) + if instrIndex != -1 && unitIndex != -1 { + max = len(sointu.Ports[t.Song().Patch[instrIndex].Units[unitIndex].Type]) - 1 + } + } + hint := t.Song().Patch.ParamHintString(t.InstrIndex(), t.UnitIndex(), name) + if hint != "" { + valueText = fmt.Sprintf("%v / %v", value, hint) + } else { + valueText = fmt.Sprintf("%v", value) + } + } + +}*/ diff --git a/tracker/patterns.go b/tracker/gioui/patterns.go similarity index 58% rename from tracker/patterns.go rename to tracker/gioui/patterns.go index c0f9eb5..29cfae7 100644 --- a/tracker/patterns.go +++ b/tracker/gioui/patterns.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "fmt" @@ -12,6 +12,7 @@ import ( "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/widget" + "github.com/vsariola/sointu/tracker" ) const patternCellHeight = 16 @@ -29,7 +30,7 @@ func (t *Tracker) layoutPatterns(gtx C) D { continue } if e.Type == pointer.Press { - t.EditMode = EditPatterns + t.SetEditMode(tracker.EditPatterns) } } rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y) @@ -37,28 +38,30 @@ func (t *Tracker) layoutPatterns(gtx C) D { pointer.InputOp{Tag: &patternPointerTag, Types: pointer.Press, }.Add(gtx.Ops) - patternRect := SongRect{ - Corner1: SongPoint{SongRow: SongRow{Pattern: t.Cursor.Pattern}, Track: t.Cursor.Track}, - Corner2: SongPoint{SongRow: SongRow{Pattern: t.SelectionCorner.Pattern}, Track: t.SelectionCorner.Track}, + patternRect := tracker.SongRect{ + Corner1: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.Cursor().Pattern}, Track: t.Cursor().Track}, + Corner2: tracker.SongPoint{SongRow: tracker.SongRow{Pattern: t.SelectionCorner().Pattern}, Track: t.SelectionCorner().Track}, } - for j := 0; j < t.song.SequenceLength(); j++ { - if j == t.PlayPosition.Pattern && t.Playing { + for j := 0; j < t.Song().Score.Length; j++ { + if playPos, ok := t.player.Position(); ok && j == playPos.Pattern { paint.FillShape(gtx.Ops, patternPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, patternCellHeight)}.Op()) } paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops) widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j))) stack := op.Save(gtx.Ops) op.Offset(f32.Pt(patternRowMarkerWidth, 0)).Add(gtx.Ops) - for i, track := range t.song.Tracks { + for i, track := range t.Song().Score.Tracks { paint.ColorOp{Color: patternTextColor}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Sequence[j])) - point := SongPoint{Track: i, SongRow: SongRow{Pattern: j}} - if t.EditMode == EditPatterns || t.EditMode == EditTracks { + if j < len(track.Order) && track.Order[j] >= 0 { + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(track.Order[j])) + } + point := tracker.SongPoint{Track: i, SongRow: tracker.SongRow{Pattern: j}} + if t.EditMode() == tracker.EditPatterns || t.EditMode() == tracker.EditTracks { if patternRect.Contains(point) { color := inactiveSelectionColor - if t.EditMode == EditPatterns { + if t.EditMode() == tracker.EditPatterns { color = selectionColor - if point.Pattern == t.Cursor.Pattern && point.Track == t.Cursor.Track { + if point.Pattern == t.Cursor().Pattern && point.Track == t.Cursor().Track { color = cursorColor } } @@ -73,9 +76,11 @@ func (t *Tracker) layoutPatterns(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Max} } -func patternIndexToString(index byte) string { - if index < 10 { - return string([]byte{'0' + index}) +func patternIndexToString(index int) string { + if index < 0 { + return "" + } else if index < 10 { + return string('0' + byte(index)) } - return string([]byte{'A' + index - 10}) + return string('A' + byte(index-10)) } diff --git a/tracker/popup.go b/tracker/gioui/popup.go similarity index 99% rename from tracker/popup.go rename to tracker/gioui/popup.go index 286e2c8..4280d73 100644 --- a/tracker/popup.go +++ b/tracker/gioui/popup.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image/color" diff --git a/tracker/gioui/rowmarkers.go b/tracker/gioui/rowmarkers.go new file mode 100644 index 0000000..3f2c663 --- /dev/null +++ b/tracker/gioui/rowmarkers.go @@ -0,0 +1,72 @@ +package gioui + +import ( + "fmt" + "image" + "strings" + + "gioui.org/f32" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/widget" + "github.com/vsariola/sointu/tracker" +) + +const rowMarkerWidth = 50 + +func (t *Tracker) layoutRowMarkers(gtx C) D { + gtx.Constraints.Min.X = rowMarkerWidth + paint.FillShape(gtx.Ops, rowMarkerSurfaceColor, clip.Rect{ + Max: gtx.Constraints.Max, + }.Op()) + defer op.Save(gtx.Ops).Load() + clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) + op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops) + cursorSongRow := t.Cursor().Pattern*t.Song().Score.RowsPerPattern + t.Cursor().Row + playPos, playing := t.player.Position() + playSongRow := playPos.Pattern*t.Song().Score.RowsPerPattern + playPos.Row + op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops) + beatMarkerDensity := t.Song().RowsPerBeat + for beatMarkerDensity <= 2 { + beatMarkerDensity *= 2 + } + for i := 0; i < t.Song().Score.Length; i++ { + for j := 0; j < t.Song().Score.RowsPerPattern; j++ { + songRow := i*t.Song().Score.RowsPerPattern + j + if mod(songRow, beatMarkerDensity*2) == 0 { + paint.FillShape(gtx.Ops, twoBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) + } else if mod(songRow, beatMarkerDensity) == 0 { + paint.FillShape(gtx.Ops, oneBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) + } + if playing && songRow == playSongRow { + paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) + } + if j == 0 { + paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops) + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i))) + } + if t.EditMode() == tracker.EditTracks && songRow == cursorSongRow { + paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops) + } else { + paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops) + } + op.Offset(f32.Pt(rowMarkerWidth/2, 0)).Add(gtx.Ops) + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j))) + op.Offset(f32.Pt(-rowMarkerWidth/2, trackRowHeight)).Add(gtx.Ops) + } + } + return layout.Dimensions{Size: image.Pt(rowMarkerWidth, gtx.Constraints.Max.Y)} +} + +func mod(a, b int) int { + m := a % b + if a < 0 && b < 0 { + m -= b + } + if a < 0 && b > 0 { + m += b + } + return m +} diff --git a/tracker/gioui/run.go b/tracker/gioui/run.go new file mode 100644 index 0000000..0c05990 --- /dev/null +++ b/tracker/gioui/run.go @@ -0,0 +1,69 @@ +package gioui + +import ( + "fmt" + "os" + + "gioui.org/app" + "gioui.org/io/clipboard" + "gioui.org/io/key" + "gioui.org/io/system" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/unit" + "github.com/vsariola/sointu" +) + +func (t *Tracker) Run(w *app.Window) error { + var ops op.Ops + for { + if pos, playing := t.player.Position(); t.NoteTracking() && playing { + cursor := t.Cursor() + cursor.SongRow = pos + t.SetCursor(cursor) + t.SetSelectionCorner(cursor) + } + select { + case <-t.refresh: + w.Invalidate() + case v := <-t.volumeChan: + t.lastVolume = v + w.Invalidate() + case e := <-w.Events(): + switch e := e.(type) { + case system.DestroyEvent: + return e.Err + case key.Event: + if t.KeyEvent(w, e) { + w.Invalidate() + } + case clipboard.Event: + err := t.UnmarshalContent([]byte(e.Text)) + if err == nil { + w.Invalidate() + } + case system.FrameEvent: + gtx := layout.NewContext(&ops, e) + t.Layout(gtx) + e.Frame(gtx.Ops) + } + } + } +} + +func Main(audioContext sointu.AudioContext, synthService sointu.SynthService) { + go func() { + w := app.NewWindow( + app.Size(unit.Dp(800), unit.Dp(600)), + app.Title("Sointu Tracker"), + ) + t := New(audioContext, synthService) + defer t.Close() + if err := t.Run(w); err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) + }() + app.Main() +} diff --git a/tracker/scrollbar.go b/tracker/gioui/scrollbar.go similarity index 99% rename from tracker/scrollbar.go rename to tracker/gioui/scrollbar.go index 4627f69..5819df7 100644 --- a/tracker/scrollbar.go +++ b/tracker/gioui/scrollbar.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" diff --git a/tracker/songpanel.go b/tracker/gioui/songpanel.go similarity index 91% rename from tracker/songpanel.go rename to tracker/gioui/songpanel.go index 2d5509d..5a07f9c 100644 --- a/tracker/songpanel.go +++ b/tracker/gioui/songpanel.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" @@ -53,7 +53,7 @@ func (t *Tracker) layoutMenuBar(gtx C) D { for clickedItem, hasClicked := t.Menus[0].Clicked(); hasClicked; { switch clickedItem { case 0: - t.LoadSong(defaultSong.Copy()) + t.ResetSong() case 1: t.LoadSongFile() case 2: @@ -69,7 +69,7 @@ func (t *Tracker) layoutMenuBar(gtx C) D { case 1: t.Redo() case 2: - if contents, err := yaml.Marshal(t.song); err == nil { + if contents, err := yaml.Marshal(t.Song()); err == nil { clipboard.WriteOp{Text: string(contents)}.Add(gtx.Ops) t.Alert.Update("Song copied to clipboard", Notify, time.Second*3) } @@ -90,8 +90,8 @@ func (t *Tracker) layoutMenuBar(gtx C) D { MenuItem{IconBytes: icons.ContentSave, Text: "Save Song", ShortcutText: shortcutKey + "S"}, )), layout.Rigid(t.layoutMenu("Edit", &t.MenuBar[1], &t.Menus[1], unit.Dp(160), - MenuItem{IconBytes: icons.ContentUndo, Text: "Undo", ShortcutText: shortcutKey + "Z", Disabled: len(t.undoStack) == 0}, - MenuItem{IconBytes: icons.ContentRedo, Text: "Redo", ShortcutText: shortcutKey + "Y", Disabled: len(t.redoStack) == 0}, + MenuItem{IconBytes: icons.ContentUndo, Text: "Undo", ShortcutText: shortcutKey + "Z", Disabled: !t.CanUndo()}, + MenuItem{IconBytes: icons.ContentRedo, Text: "Redo", ShortcutText: shortcutKey + "Y", Disabled: !t.CanRedo()}, MenuItem{IconBytes: icons.ContentContentCopy, Text: "Copy", ShortcutText: shortcutKey + "C"}, MenuItem{IconBytes: icons.ContentContentPaste, Text: "Paste", ShortcutText: shortcutKey + "V"}, )), @@ -104,7 +104,7 @@ func (t *Tracker) layoutSongOptions(gtx C) D { in := layout.UniformInset(unit.Dp(1)) panicBtnStyle := material.Button(t.Theme, t.PanicBtn, "Panic") - if t.sequencer.Enabled() { + if t.player.Enabled() { panicBtnStyle.Background = transparent panicBtnStyle.Color = t.Theme.Palette.Fg } else { @@ -113,11 +113,7 @@ func (t *Tracker) layoutSongOptions(gtx C) D { } for t.PanicBtn.Clicked() { - if t.sequencer.Enabled() { - t.sequencer.Disable() - } else { - t.sequencer.SetPatch(t.song.Patch) - } + t.player.Disable() } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, @@ -125,7 +121,7 @@ func (t *Tracker) layoutSongOptions(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(Label("LEN:", white)), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - t.SongLength.Value = t.song.SequenceLength() + t.SongLength.Value = t.Song().Score.Length numStyle := NumericUpDown(t.Theme, t.SongLength, 1, math.MaxInt32) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) @@ -139,7 +135,7 @@ func (t *Tracker) layoutSongOptions(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(Label("BPM:", white)), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - t.BPM.Value = t.song.BPM + t.BPM.Value = t.Song().BPM numStyle := NumericUpDown(t.Theme, t.BPM, 1, 999) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) @@ -153,7 +149,7 @@ func (t *Tracker) layoutSongOptions(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(Label("RPP:", white)), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - t.RowsPerPattern.Value = t.song.RowsPerPattern + t.RowsPerPattern.Value = t.Song().Score.RowsPerPattern numStyle := NumericUpDown(t.Theme, t.RowsPerPattern, 1, 255) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) @@ -167,7 +163,7 @@ func (t *Tracker) layoutSongOptions(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, layout.Rigid(Label("RPB:", white)), layout.Rigid(func(gtx layout.Context) layout.Dimensions { - t.RowsPerBeat.Value = t.song.RowsPerBeat + t.RowsPerBeat.Value = t.Song().RowsPerBeat numStyle := NumericUpDown(t.Theme, t.RowsPerBeat, 1, 32) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) @@ -194,6 +190,6 @@ func (t *Tracker) layoutSongOptions(gtx C) D { gtx.Constraints.Min = image.Pt(0, 0) return panicBtnStyle.Layout(gtx) }), - layout.Rigid(t.VuMeter.Layout), + layout.Rigid(VuMeter{Volume: t.lastVolume, Range: 100}.Layout), ) } diff --git a/tracker/split.go b/tracker/gioui/split.go similarity index 99% rename from tracker/split.go rename to tracker/gioui/split.go index c279fd4..1211ab0 100644 --- a/tracker/split.go +++ b/tracker/gioui/split.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image" diff --git a/tracker/surface.go b/tracker/gioui/surface.go similarity index 98% rename from tracker/surface.go rename to tracker/gioui/surface.go index 582170f..0dbaad6 100644 --- a/tracker/surface.go +++ b/tracker/gioui/surface.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image/color" diff --git a/tracker/theme.go b/tracker/gioui/theme.go similarity index 99% rename from tracker/theme.go rename to tracker/gioui/theme.go index 1d1cf5e..7592876 100644 --- a/tracker/theme.go +++ b/tracker/gioui/theme.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "image/color" diff --git a/tracker/track.go b/tracker/gioui/track.go similarity index 68% rename from tracker/track.go rename to tracker/gioui/track.go index 21d114f..6cb207e 100644 --- a/tracker/track.go +++ b/tracker/gioui/track.go @@ -1,4 +1,4 @@ -package tracker +package gioui import ( "fmt" @@ -14,6 +14,7 @@ import ( "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" + "github.com/vsariola/sointu/tracker" "golang.org/x/exp/shiny/materialdesign/icons" ) @@ -25,30 +26,10 @@ var trackPointerTag bool var trackJumpPointerTag bool func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { - t.playRowPatMutex.RLock() - defer t.playRowPatMutex.RUnlock() - - playPat := t.PlayPosition.Pattern - if !t.Playing { - playPat = -1 - } - - rowMarkers := layout.Rigid(t.layoutRowMarkers( - t.song.RowsPerPattern, - len(t.song.Tracks[0].Sequence), - t.Cursor.Row, - t.Cursor.Pattern, - t.CursorColumn, - t.PlayPosition.Row, - playPat, - )) + rowMarkers := layout.Rigid(t.layoutRowMarkers) for t.NewTrackBtn.Clicked() { - t.AddTrack() - } - - for len(t.TrackShowHex) < len(t.song.Tracks) { - t.TrackShowHex = append(t.TrackShowHex, false) + t.AddTrack(true) } //t.TrackHexCheckBoxes[i2].Value = t.TrackShowHex[i2] @@ -92,31 +73,30 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { newTrackBtnStyle := material.IconButton(t.Theme, t.NewTrackBtn, widgetForIcon(icons.ContentAdd)) newTrackBtnStyle.Background = transparent newTrackBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) - if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() { + if t.CanAddTrack() { newTrackBtnStyle.Color = primaryColor } else { newTrackBtnStyle.Color = disabledTextColor } in := layout.UniformInset(unit.Dp(1)) octave := func(gtx C) D { - numStyle := NumericUpDown(t.Theme, t.Octave, 0, 9) + t.OctaveNumberInput.Value = t.Octave() + numStyle := NumericUpDown(t.Theme, t.OctaveNumberInput, 0, 9) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) - return in.Layout(gtx, numStyle.Layout) - } - n := t.song.Tracks[t.Cursor.Track].NumVoices - maxRemain := t.song.Patch.TotalVoices() - t.song.TotalTrackVoices() + n - if maxRemain < 1 { - maxRemain = 1 + dims := in.Layout(gtx, numStyle.Layout) + t.SetOctave(t.OctaveNumberInput.Value) + return dims } + n := t.Song().Score.Tracks[t.Cursor().Track].NumVoices t.TrackVoices.Value = n voiceUpDown := func(gtx C) D { - numStyle := NumericUpDown(t.Theme, t.TrackVoices, 1, maxRemain) + numStyle := NumericUpDown(t.Theme, t.TrackVoices, 1, t.MaxTrackVoices()) gtx.Constraints.Min.Y = gtx.Px(unit.Dp(20)) gtx.Constraints.Min.X = gtx.Px(unit.Dp(70)) return in.Layout(gtx, numStyle.Layout) } - t.TrackHexCheckBox.Value = t.TrackShowHex[t.Cursor.Track] + t.TrackHexCheckBox.Value = t.Song().Score.Tracks[t.Cursor().Track].Effect hexCheckBoxStyle := material.CheckBox(t.Theme, t.TrackHexCheckBox, "Hex") dims := layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(Label("OCT:", white)), @@ -131,7 +111,7 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { layout.Rigid(voiceUpDown), layout.Flexed(1, func(gtx C) D { return layout.Dimensions{Size: gtx.Constraints.Min} }), layout.Rigid(newTrackBtnStyle.Layout)) - t.TrackShowHex[t.Cursor.Track] = t.TrackHexCheckBox.Value + t.Song().Score.Tracks[t.Cursor().Track].Effect = t.TrackHexCheckBox.Value // TODO: we should not modify the model, but how should this be done t.SetTrackVoices(t.TrackVoices.Value) return dims } @@ -142,7 +122,7 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { continue } if e.Type == pointer.Press { - t.EditMode = EditTracks + t.SetEditMode(tracker.EditTracks) } } rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y) @@ -153,7 +133,7 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { return layout.Flex{Axis: layout.Vertical}.Layout(gtx, layout.Rigid(func(gtx C) D { - return Surface{Gray: 37, Focus: t.EditMode == 1, FitSize: true}.Layout(gtx, menu) + return Surface{Gray: 37, Focus: t.EditMode() == tracker.EditTracks, FitSize: true}.Layout(gtx, menu) }), layout.Flexed(1, func(gtx C) D { return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, @@ -166,20 +146,20 @@ func (t *Tracker) layoutTracker(gtx layout.Context) layout.Dimensions { func (t *Tracker) layoutTracks(gtx C) D { defer op.Save(gtx.Ops).Load() clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) - cursorSongRow := t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row + cursorSongRow := t.Cursor().Pattern*t.Song().Score.RowsPerPattern + t.Cursor().Row for _, ev := range gtx.Events(&trackJumpPointerTag) { e, ok := ev.(pointer.Event) if !ok { continue } if e.Type == pointer.Press { - t.EditMode = EditTracks - t.Cursor.Track = int(e.Position.X) / trackColWidth - t.Cursor.Pattern = 0 - t.Cursor.Row = int((e.Position.Y-float32(gtx.Constraints.Max.Y-trackRowHeight)/2)/trackRowHeight + float32(cursorSongRow)) - t.Cursor.Clamp(t.song) - t.SelectionCorner = t.Cursor - cursorSongRow = t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row + t.SetEditMode(tracker.EditTracks) + track := int(e.Position.X) / trackColWidth + row := int((e.Position.Y-float32(gtx.Constraints.Max.Y-trackRowHeight)/2)/trackRowHeight + float32(cursorSongRow)) + cursor := tracker.SongPoint{Track: track, SongRow: tracker.SongRow{Row: row}}.Clamp(t.Song().Score) + t.SetCursor(cursor) + t.SetSelectionCorner(cursor) + cursorSongRow = cursor.Pattern*t.Song().Score.RowsPerPattern + cursor.Row } } rect := image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y) @@ -189,9 +169,9 @@ func (t *Tracker) layoutTracks(gtx C) D { }.Add(gtx.Ops) op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops) op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops) - if t.EditMode == EditPatterns || t.EditMode == EditTracks { - x1, y1 := t.Cursor.Track, t.Cursor.Pattern - x2, y2 := t.SelectionCorner.Track, t.SelectionCorner.Pattern + if t.EditMode() == tracker.EditPatterns || t.EditMode() == tracker.EditTracks { + x1, y1 := t.Cursor().Track, t.Cursor().Pattern + x2, y2 := t.SelectionCorner().Track, t.SelectionCorner().Pattern if x1 > x2 { x1, x2 = x2, x1 } @@ -201,14 +181,14 @@ func (t *Tracker) layoutTracks(gtx C) D { x2++ y2++ x1 *= trackColWidth - y1 *= trackRowHeight * t.song.RowsPerPattern + y1 *= trackRowHeight * t.Song().Score.RowsPerPattern x2 *= trackColWidth - y2 *= trackRowHeight * t.song.RowsPerPattern + y2 *= trackRowHeight * t.Song().Score.RowsPerPattern paint.FillShape(gtx.Ops, inactiveSelectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op()) } - if t.EditMode == EditTracks { - x1, y1 := t.Cursor.Track, t.Cursor.Pattern*t.song.RowsPerPattern+t.Cursor.Row - x2, y2 := t.SelectionCorner.Track, t.SelectionCorner.Pattern*t.song.RowsPerPattern+t.SelectionCorner.Row + if t.EditMode() == tracker.EditTracks { + x1, y1 := t.Cursor().Track, t.Cursor().Pattern*t.Song().Score.RowsPerPattern+t.Cursor().Row + x2, y2 := t.SelectionCorner().Track, t.SelectionCorner().Pattern*t.Song().Score.RowsPerPattern+t.SelectionCorner().Row if x1 > x2 { x1, x2 = x2, x1 } @@ -222,9 +202,16 @@ func (t *Tracker) layoutTracks(gtx C) D { x2 *= trackColWidth y2 *= trackRowHeight paint.FillShape(gtx.Ops, selectionColor, clip.Rect{Min: image.Pt(x1, y1), Max: image.Pt(x2, y2)}.Op()) - cx := t.Cursor.Track * trackColWidth - cy := (t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row) * trackRowHeight - paint.FillShape(gtx.Ops, cursorColor, clip.Rect{Min: image.Pt(cx, cy), Max: image.Pt(cx+trackColWidth, cy+trackRowHeight)}.Op()) + cx := t.Cursor().Track * trackColWidth + cy := (t.Cursor().Pattern*t.Song().Score.RowsPerPattern + t.Cursor().Row) * trackRowHeight + cw := trackColWidth + if t.Song().Score.Tracks[t.Cursor().Track].Effect { + cw /= 2 + if t.LowNibble() { + cx += cw + } + } + paint.FillShape(gtx.Ops, cursorColor, clip.Rect{Min: image.Pt(cx, cy), Max: image.Pt(cx+cw, cy+trackRowHeight)}.Op()) } delta := (gtx.Constraints.Max.Y/2 + trackRowHeight - 1) / trackRowHeight firstRow := cursorSongRow - delta @@ -232,28 +219,41 @@ func (t *Tracker) layoutTracks(gtx C) D { if firstRow < 0 { firstRow = 0 } - if l := t.song.TotalRows(); lastRow >= l { + if l := t.Song().Score.LengthInRows(); lastRow >= l { lastRow = l - 1 } op.Offset(f32.Pt(0, float32(trackRowHeight*firstRow))).Add(gtx.Ops) - for trkIndex, trk := range t.song.Tracks { + for _, trk := range t.Song().Score.Tracks { stack := op.Save(gtx.Ops) for row := firstRow; row <= lastRow; row++ { - pat := row / t.song.RowsPerPattern - patRow := row % t.song.RowsPerPattern - s := trk.Sequence[pat] - if patRow == 0 { + pat := row / t.Song().Score.RowsPerPattern + patRow := row % t.Song().Score.RowsPerPattern + s := -1 + if pat >= 0 && pat < len(trk.Order) { + s = trk.Order[pat] + } + if s < 0 { + op.Offset(f32.Pt(0, trackRowHeight)).Add(gtx.Ops) + continue + } + if s >= 0 && patRow == 0 { paint.ColorOp{Color: trackerPatMarker}.Add(gtx.Ops) widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, patternIndexToString(s)) } op.Offset(f32.Pt(patmarkWidth, 0)).Add(gtx.Ops) - if t.EditMode == EditTracks && t.Cursor.SongRow.Row == patRow && t.Cursor.SongRow.Pattern == pat { + if t.EditMode() == tracker.EditTracks && t.Cursor().Row == patRow && t.Cursor().Pattern == pat { paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops) } else { paint.ColorOp{Color: trackerInactiveTextColor}.Add(gtx.Ops) } - c := trk.Patterns[s][patRow] - if t.TrackShowHex[trkIndex] { + var c byte = 1 + if s >= 0 && s < len(trk.Patterns) { + pattern := trk.Patterns[s] + if patRow >= 0 && patRow < len(pattern) { + c = pattern[patRow] + } + } + if trk.Effect { var text string switch c { case 0: @@ -265,7 +265,7 @@ func (t *Tracker) layoutTracks(gtx C) D { } widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(text)) } else { - widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, valueAsNote(c)) + widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, tracker.NoteStr(c)) } op.Offset(f32.Pt(-patmarkWidth, trackRowHeight)).Add(gtx.Ops) } diff --git a/tracker/gioui/tracker.go b/tracker/gioui/tracker.go new file mode 100644 index 0000000..be9d967 --- /dev/null +++ b/tracker/gioui/tracker.go @@ -0,0 +1,172 @@ +package gioui + +import ( + "encoding/json" + "errors" + "fmt" + + "gioui.org/font/gofont" + "gioui.org/layout" + "gioui.org/text" + "gioui.org/widget" + "gioui.org/widget/material" + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/tracker" + "gopkg.in/yaml.v3" +) + +type Tracker struct { + Theme *material.Theme + MenuBar []widget.Clickable + Menus []Menu + OctaveNumberInput *NumberInput + BPM *NumberInput + RowsPerPattern *NumberInput + RowsPerBeat *NumberInput + Step *NumberInput + InstrumentVoices *NumberInput + TrackVoices *NumberInput + InstrumentNameEditor *widget.Editor + NewTrackBtn *widget.Clickable + NewInstrumentBtn *widget.Clickable + DeleteInstrumentBtn *widget.Clickable + AddSemitoneBtn *widget.Clickable + SubtractSemitoneBtn *widget.Clickable + AddOctaveBtn *widget.Clickable + SubtractOctaveBtn *widget.Clickable + SongLength *NumberInput + PanicBtn *widget.Clickable + CopyInstrumentBtn *widget.Clickable + ParameterList *layout.List + ParameterScrollBar *ScrollBar + Parameters []*ParameterWidget + UnitDragList *DragList + UnitScrollBar *ScrollBar + DeleteUnitBtn *widget.Clickable + ClearUnitBtn *widget.Clickable + ChooseUnitTypeList *layout.List + ChooseUnitScrollBar *ScrollBar + ChooseUnitTypeBtns []*widget.Clickable + AddUnitBtn *widget.Clickable + InstrumentDragList *DragList + InstrumentScrollBar *ScrollBar + TrackHexCheckBox *widget.Bool + TopHorizontalSplit *Split + BottomHorizontalSplit *Split + VerticalSplit *Split + StackUse []int + KeyPlaying map[string]uint32 + Alert Alert + + lastVolume tracker.Volume + volumeChan chan tracker.Volume + + player *tracker.Player + refresh chan struct{} + playerCloser chan struct{} + audioContext sointu.AudioContext + + *tracker.Model +} + +func (t *Tracker) UnmarshalContent(bytes []byte) error { + var instr sointu.Instrument + if errJSON := json.Unmarshal(bytes, &instr); errJSON == nil { + if t.SetInstrument(instr) { + return nil + } + } + if errYaml := yaml.Unmarshal(bytes, &instr); errYaml == nil { + if t.SetInstrument(instr) { + return nil + } + } + var song sointu.Song + if errJSON := json.Unmarshal(bytes, &song); errJSON != nil { + if errYaml := yaml.Unmarshal(bytes, &song); errYaml != nil { + return fmt.Errorf("the song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml) + } + } + if song.BPM > 0 { + t.SetSong(song) + return nil + } + return errors.New("was able to unmarshal a song, but the bpm was 0") +} + +func (t *Tracker) Close() { + t.playerCloser <- struct{}{} + t.audioContext.Close() +} + +func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tracker { + t := &Tracker{ + Theme: material.NewTheme(gofont.Collection()), + audioContext: audioContext, + BPM: new(NumberInput), + OctaveNumberInput: &NumberInput{Value: 4}, + SongLength: new(NumberInput), + RowsPerPattern: new(NumberInput), + RowsPerBeat: new(NumberInput), + Step: &NumberInput{Value: 1}, + InstrumentVoices: new(NumberInput), + TrackVoices: new(NumberInput), + InstrumentNameEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle}, + NewTrackBtn: new(widget.Clickable), + NewInstrumentBtn: new(widget.Clickable), + DeleteInstrumentBtn: new(widget.Clickable), + AddSemitoneBtn: new(widget.Clickable), + SubtractSemitoneBtn: new(widget.Clickable), + AddOctaveBtn: new(widget.Clickable), + SubtractOctaveBtn: new(widget.Clickable), + AddUnitBtn: new(widget.Clickable), + DeleteUnitBtn: new(widget.Clickable), + ClearUnitBtn: new(widget.Clickable), + PanicBtn: new(widget.Clickable), + CopyInstrumentBtn: new(widget.Clickable), + TrackHexCheckBox: new(widget.Bool), + Menus: make([]Menu, 2), + MenuBar: make([]widget.Clickable, 2), + UnitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}, HoverItem: -1}, + UnitScrollBar: &ScrollBar{Axis: layout.Vertical}, + refresh: make(chan struct{}, 1), // use non-blocking sends; no need to queue extra ticks if one is queued already + InstrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}, HoverItem: -1}, + InstrumentScrollBar: &ScrollBar{Axis: layout.Horizontal}, + ParameterList: &layout.List{Axis: layout.Vertical}, + ParameterScrollBar: &ScrollBar{Axis: layout.Vertical}, + TopHorizontalSplit: &Split{Ratio: -.6}, + BottomHorizontalSplit: &Split{Ratio: -.6}, + VerticalSplit: &Split{Axis: layout.Vertical}, + ChooseUnitTypeList: &layout.List{Axis: layout.Vertical}, + ChooseUnitScrollBar: &ScrollBar{Axis: layout.Vertical}, + KeyPlaying: make(map[string]uint32), + volumeChan: make(chan tracker.Volume, 1), + playerCloser: make(chan struct{}), + } + t.Model = tracker.NewModel() + vuBufferObserver := make(chan []float32) + go tracker.VuAnalyzer(0.3, 1e-4, 1, -100, vuBufferObserver, t.volumeChan) + t.Theme.Palette.Fg = primaryColor + t.Theme.Palette.ContrastFg = black + t.SetEditMode(tracker.EditTracks) + for range tracker.UnitTypeNames { + t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable)) + } + t.SetOctave(4) + patchObserver := make(chan sointu.Patch, 16) + t.AddPatchObserver(patchObserver) + scoreObserver := make(chan sointu.Score, 16) + t.AddScoreObserver(scoreObserver) + sprObserver := make(chan int, 16) + t.AddSamplesPerRowObserver(sprObserver) + audioChannel := make(chan []float32) + t.player = tracker.NewPlayer(synthService, t.playerCloser, patchObserver, scoreObserver, sprObserver, t.refresh, audioChannel, vuBufferObserver) + audioOut := audioContext.Output() + go func() { + for buf := range audioChannel { + audioOut.WriteAudio(buf) + } + }() + t.ResetSong() + return t +} diff --git a/tracker/gioui/uniteditor.go b/tracker/gioui/uniteditor.go new file mode 100644 index 0000000..61fa6db --- /dev/null +++ b/tracker/gioui/uniteditor.go @@ -0,0 +1,146 @@ +package gioui + +import ( + "image" + "image/color" + "strings" + + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" + "gioui.org/widget/material" + "github.com/vsariola/sointu/tracker" + "golang.org/x/exp/shiny/materialdesign/icons" +) + +func (t *Tracker) layoutUnitEditor(gtx C) D { + editorFunc := t.layoutUnitSliders + if t.Unit().Type == "" { + editorFunc = t.layoutUnitTypeChooser + } + return Surface{Gray: 24, Focus: t.EditMode() == tracker.EditUnits || t.EditMode() == tracker.EditParameters}.Layout(gtx, func(gtx C) D { + return layout.Flex{Axis: layout.Vertical}.Layout(gtx, + layout.Flexed(1, editorFunc), + layout.Rigid(t.layoutUnitFooter())) + }) +} + +func (t *Tracker) layoutUnitSliders(gtx C) D { + numItems := t.NumParams() + + for len(t.Parameters) <= numItems { + t.Parameters = append(t.Parameters, new(ParameterWidget)) + } + + listItem := func(gtx C, index int) D { + for t.Parameters[index].Clicked() { + if t.EditMode() != tracker.EditParameters || t.ParamIndex() != index { + t.SetEditMode(tracker.EditParameters) + t.SetParamIndex(index) + } else { + t.ResetParam() + } + } + param, err := t.Param(index) + if err != nil { + return D{} + } + oldVal := param.Value + paramStyle := t.ParamStyle(t.Theme, ¶m, t.Parameters[index]) + paramStyle.Focus = t.EditMode() == tracker.EditParameters && t.ParamIndex() == index + dims := paramStyle.Layout(gtx) + if oldVal != param.Value { + t.SetEditMode(tracker.EditParameters) + t.SetParamIndex(index) + t.SetParam(param.Value) + } + return dims + } + + return layout.Stack{}.Layout(gtx, + layout.Stacked(func(gtx C) D { + return t.ParameterList.Layout(gtx, numItems, listItem) + }), + layout.Stacked(func(gtx C) D { + gtx.Constraints.Min = gtx.Constraints.Max + return t.ParameterScrollBar.Layout(gtx, unit.Dp(10), numItems, &t.ParameterList.Position) + })) +} + +func (t *Tracker) layoutUnitFooter() layout.Widget { + return func(gtx C) D { + for t.ClearUnitBtn.Clicked() { + t.SetUnitType("") + op.InvalidateOp{}.Add(gtx.Ops) + } + for t.DeleteUnitBtn.Clicked() { + t.DeleteUnit(false) + op.InvalidateOp{}.Add(gtx.Ops) + } + deleteUnitBtnStyle := material.IconButton(t.Theme, t.DeleteUnitBtn, widgetForIcon(icons.ActionDelete)) + deleteUnitBtnStyle.Background = transparent + deleteUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) + if t.CanDeleteUnit() { + deleteUnitBtnStyle.Color = primaryColor + } else { + deleteUnitBtnStyle.Color = disabledTextColor + } + text := t.Unit().Type + if text == "" { + text = "Choose unit type" + } else { + text = strings.Title(text) + } + hintText := Label(text, white) + return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, + layout.Rigid(deleteUnitBtnStyle.Layout), + layout.Rigid(func(gtx C) D { + var dims D + if t.Unit().Type != "" { + clearUnitBtnStyle := material.IconButton(t.Theme, t.ClearUnitBtn, widgetForIcon(icons.ContentClear)) + clearUnitBtnStyle.Color = primaryColor + clearUnitBtnStyle.Background = transparent + clearUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) + dims = clearUnitBtnStyle.Layout(gtx) + } + return D{Size: image.Pt(gtx.Px(unit.Dp(48)), dims.Size.Y)} + }), + layout.Flexed(1, hintText), + ) + } +} + +func (t *Tracker) layoutUnitTypeChooser(gtx C) D { + listElem := func(gtx C, i int) D { + for t.ChooseUnitTypeBtns[i].Clicked() { + t.SetUnitType(tracker.UnitTypeNames[i]) + } + labelStyle := LabelStyle{Text: tracker.UnitTypeNames[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} + bg := func(gtx C) D { + gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20)) + var color color.NRGBA + if t.ChooseUnitTypeBtns[i].Hovered() { + color = unitTypeListHighlightColor + } + paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op()) + return D{Size: gtx.Constraints.Min} + } + leftMargin := layout.Inset{Left: unit.Dp(10)} + return layout.Stack{Alignment: layout.W}.Layout(gtx, + layout.Stacked(bg), + layout.Expanded(func(gtx C) D { + return leftMargin.Layout(gtx, labelStyle.Layout) + }), + layout.Expanded(t.ChooseUnitTypeBtns[i].Layout)) + } + return layout.Stack{}.Layout(gtx, + layout.Stacked(func(gtx C) D { + return t.ChooseUnitTypeList.Layout(gtx, len(tracker.UnitTypeNames), listElem) + }), + layout.Expanded(func(gtx C) D { + return t.ChooseUnitScrollBar.Layout(gtx, unit.Dp(10), len(tracker.UnitTypeNames), &t.ChooseUnitTypeList.Position) + }), + ) +} diff --git a/tracker/gioui/vumeter.go b/tracker/gioui/vumeter.go new file mode 100644 index 0000000..dee5166 --- /dev/null +++ b/tracker/gioui/vumeter.go @@ -0,0 +1,47 @@ +package gioui + +import ( + "image" + + "gioui.org/f32" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "gioui.org/unit" + "github.com/vsariola/sointu/tracker" +) + +type VuMeter struct { + Volume tracker.Volume + Range float32 +} + +func (v VuMeter) Layout(gtx C) D { + defer op.Save(gtx.Ops).Load() + gtx.Constraints.Max.Y = gtx.Px(unit.Dp(12)) + height := gtx.Px(unit.Dp(6)) + for j := 0; j < 2; j++ { + value := v.Volume.Average[j] + v.Range + if value > 0 { + x := int(value/v.Range*float32(gtx.Constraints.Max.X) + 0.5) + if x > gtx.Constraints.Max.X { + x = gtx.Constraints.Max.X + } + paint.FillShape(gtx.Ops, mediumEmphasisTextColor, clip.Rect(image.Rect(0, 0, x, height)).Op()) + } + valueMax := v.Volume.Peak[j] + v.Range + if valueMax > 0 { + color := white + if valueMax >= v.Range { + color = errorColor + } + x := int(valueMax/v.Range*float32(gtx.Constraints.Max.X) + 0.5) + if x > gtx.Constraints.Max.X { + x = gtx.Constraints.Max.X + } + paint.FillShape(gtx.Ops, color, clip.Rect(image.Rect(x-1, 0, x, height)).Op()) + } + op.Offset(f32.Pt(0, float32(height))).Add(gtx.Ops) + } + return D{Size: gtx.Constraints.Max} +} diff --git a/tracker/gmdlsentries.go b/tracker/gmdlsentries.go index 62be166..781138d 100644 --- a/tracker/gmdlsentries.go +++ b/tracker/gmdlsentries.go @@ -1,7 +1,7 @@ // Code generated by go generate; DO NOT EDIT. package tracker -var gmDlsEntries = []GmDlsEntry{ +var GmDlsEntries = []GmDlsEntry{ {Start: 140078, LoopStart: 1353, LoopLength: 91, SuggestedTranspose: 1, Name: "101BS35"}, {Start: 141606, LoopStart: 1380, LoopLength: 43, SuggestedTranspose: -12, Name: "101BS48"}, {Start: 143113, LoopStart: 5448, LoopLength: 563, SuggestedTranspose: 5, Name: "12STR55A"}, diff --git a/tracker/gmdlsentry.go b/tracker/gmdlsentry.go index 82510d7..1bd4659 100644 --- a/tracker/gmdlsentry.go +++ b/tracker/gmdlsentry.go @@ -10,12 +10,12 @@ type GmDlsEntry struct { Name string } -var gmDlsEntryMap = make(map[compiler.SampleOffset]int) +var GmDlsEntryMap = make(map[compiler.SampleOffset]int) func init() { - for i, e := range gmDlsEntries { + for i, e := range GmDlsEntries { key := compiler.SampleOffset{Start: uint32(e.Start), LoopStart: uint16(e.LoopStart), LoopLength: uint16(e.LoopLength)} - gmDlsEntryMap[key] = i + GmDlsEntryMap[key] = i } } diff --git a/tracker/keyevent.go b/tracker/keyevent.go deleted file mode 100644 index f767212..0000000 --- a/tracker/keyevent.go +++ /dev/null @@ -1,448 +0,0 @@ -package tracker - -import ( - "strconv" - "strings" - "time" - - "gioui.org/app" - "gioui.org/io/key" - "gopkg.in/yaml.v3" -) - -var noteMap = map[string]int{ - "Z": -12, - "S": -11, - "X": -10, - "D": -9, - "C": -8, - "V": -7, - "G": -6, - "B": -5, - "H": -4, - "N": -3, - "J": -2, - "M": -1, - ",": 0, - "L": 1, - ".": 2, - "Q": 0, - "2": 1, - "W": 2, - "3": 3, - "E": 4, - "R": 5, - "5": 6, - "T": 7, - "6": 8, - "Y": 9, - "7": 10, - "U": 11, - "I": 12, - "9": 13, - "O": 14, - "0": 15, - "P": 16, -} - -var unitKeyMap = map[string]string{ - "e": "envelope", - "o": "oscillator", - "m": "mulp", - "M": "mul", - "a": "addp", - "A": "add", - "p": "pan", - "S": "push", - "P": "pop", - "O": "out", - "l": "loadnote", - "L": "loadval", - "h": "xch", - "d": "delay", - "D": "distort", - "H": "hold", - "b": "crush", - "g": "gain", - "i": "invgain", - "f": "filter", - "I": "clip", - "E": "speed", - "r": "compressor", - "u": "outaux", - "U": "aux", - "s": "send", - "n": "noise", - "N": "in", - "R": "receive", -} - -// KeyEvent handles incoming key events and returns true if repaint is needed. -func (t *Tracker) KeyEvent(w *app.Window, e key.Event) bool { - if e.State == key.Press { - if t.InstrumentNameEditor.Focused() { - return false - } - switch e.Name { - case "C": - if e.Modifiers.Contain(key.ModShortcut) { - contents, err := yaml.Marshal(t.song) - if err == nil { - w.WriteClipboard(string(contents)) - t.Alert.Update("Song copied to clipboard", Notify, time.Second*3) - } - return true - } - case "V": - if e.Modifiers.Contain(key.ModShortcut) { - w.ReadClipboard() - return true - } - case "Z": - if e.Modifiers.Contain(key.ModShortcut) { - t.Undo() - return true - } - case "Y": - if e.Modifiers.Contain(key.ModShortcut) { - t.Redo() - return true - } - case "N": - if e.Modifiers.Contain(key.ModShortcut) { - t.LoadSong(defaultSong.Copy()) - return true - } - case "S": - if e.Modifiers.Contain(key.ModShortcut) { - t.SaveSongFile() - return false - } - case "O": - if e.Modifiers.Contain(key.ModShortcut) { - t.LoadSongFile() - return true - } - case "F1": - t.EditMode = EditPatterns - return true - case "F2": - t.EditMode = EditTracks - return true - case "F3": - t.EditMode = EditUnits - return true - case "F4": - t.EditMode = EditParameters - return true - case "F5": - t.NoteTracking = true - t.PlayPosition.Pattern = t.Cursor.Pattern - if t.EditMode == EditPatterns { - t.PlayPosition.Row = 0 - } else { - t.PlayPosition.Row = t.Cursor.Row - } - t.PlayPosition.Row-- // TODO: we advance soon to make up for this -1, but this is not very elegant way to do it - t.SetPlaying(true) - return true - case "F6": - t.NoteTracking = false - t.PlayPosition.Pattern = t.Cursor.Pattern - if t.EditMode == EditPatterns { - t.PlayPosition.Row = 0 - } else { - t.PlayPosition.Row = t.Cursor.Row - } - t.PlayPosition.Row-- // TODO: we advance soon to make up for this -1, but this is not very elegant way to do it - t.SetPlaying(true) - return true - case "F7": - t.NoteTracking = false - t.SetPlaying(true) - return true - case "F8": - t.SetPlaying(false) - return true - case key.NameDeleteForward: - switch t.EditMode { - case EditPatterns: - t.DeleteOrderRow(true) - return true - case EditTracks: - t.DeleteSelection() - return true - case EditUnits: - t.DeleteUnit(true) - return true - } - case key.NameDeleteBackward: - switch t.EditMode { - case EditPatterns: - t.DeleteOrderRow(false) - return true - case EditTracks: - t.DeleteSelection() - return true - case EditUnits: - t.DeleteUnit(false) - return true - } - case "Space": - t.SetPlaying(!t.Playing) - if t.Playing { - if !e.Modifiers.Contain(key.ModShortcut) { - t.NoteTracking = true - } - t.PlayPosition.Pattern = t.Cursor.Pattern - if t.EditMode == EditPatterns { - t.PlayPosition.Row = 0 - } else { - t.PlayPosition.Row = t.Cursor.Row - } - t.PlayPosition.Row-- - } - return true - case `\`, `<`, `>`: - if e.Modifiers.Contain(key.ModShift) { - return t.ChangeOctave(1) - } - return t.ChangeOctave(-1) - case key.NameTab: - if e.Modifiers.Contain(key.ModShift) { - t.EditMode = (t.EditMode - 1 + 4) % 4 - } else { - t.EditMode = (t.EditMode + 1) % 4 - } - return true - case key.NameReturn: - switch t.EditMode { - case EditPatterns: - t.AddOrderRow(!e.Modifiers.Contain(key.ModShortcut)) - case EditUnits: - t.AddUnit(!e.Modifiers.Contain(key.ModShortcut)) - } - case key.NameUpArrow: - switch t.EditMode { - case EditPatterns: - if e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.SongRow = SongRow{} - } else { - t.Cursor.Row -= t.song.RowsPerPattern - } - t.NoteTracking = false - case EditTracks: - if e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Row -= t.song.RowsPerPattern - } else { - if t.Step.Value > 0 { - t.Cursor.Row -= t.Step.Value - } else { - t.Cursor.Row-- - } - } - t.NoteTracking = false - case EditUnits: - t.CurrentUnit-- - case EditParameters: - t.CurrentParam-- - } - t.ClampPositions() - if !e.Modifiers.Contain(key.ModShift) { - t.Unselect() - } - return true - case key.NameDownArrow: - switch t.EditMode { - case EditPatterns: - if e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Row = t.song.TotalRows() - 1 - } else { - t.Cursor.Row += t.song.RowsPerPattern - } - t.NoteTracking = false - case EditTracks: - if e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Row += t.song.RowsPerPattern - } else { - if t.Step.Value > 0 { - t.Cursor.Row += t.Step.Value - } else { - t.Cursor.Row++ - } - } - t.NoteTracking = false - case EditUnits: - t.CurrentUnit++ - case EditParameters: - t.CurrentParam++ - } - t.ClampPositions() - if !e.Modifiers.Contain(key.ModShift) { - t.Unselect() - } - return true - case key.NameLeftArrow: - switch t.EditMode { - case EditPatterns: - if e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Track = 0 - } else { - t.Cursor.Track-- - } - case EditTracks: - if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Track-- - t.CursorColumn = 1 - } else { - t.CursorColumn-- - } - case EditUnits: - t.CurrentInstrument-- - case EditParameters: - if e.Modifiers.Contain(key.ModShift) { - t.SetUnitParam(t.GetUnitParam() - 16) - } else { - t.SetUnitParam(t.GetUnitParam() - 1) - } - } - t.ClampPositions() - if !e.Modifiers.Contain(key.ModShift) { - t.Unselect() - } - return true - case key.NameRightArrow: - switch t.EditMode { - case EditPatterns: - if e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Track = len(t.song.Tracks) - 1 - } else { - t.Cursor.Track++ - } - case EditTracks: - if t.CursorColumn == 0 || !t.TrackShowHex[t.Cursor.Track] || e.Modifiers.Contain(key.ModShortcut) { - t.Cursor.Track++ - t.CursorColumn = 0 - } else { - t.CursorColumn++ - } - case EditUnits: - t.CurrentInstrument++ - case EditParameters: - if e.Modifiers.Contain(key.ModShift) { - t.SetUnitParam(t.GetUnitParam() + 16) - } else { - t.SetUnitParam(t.GetUnitParam() + 1) - } - } - t.ClampPositions() - if !e.Modifiers.Contain(key.ModShift) { - t.Unselect() - } - return true - case "+": - switch t.EditMode { - case EditTracks: - if e.Modifiers.Contain(key.ModShortcut) { - t.AdjustSelectionPitch(12) - } else { - t.AdjustSelectionPitch(1) - } - return true - } - case "-": - switch t.EditMode { - case EditTracks: - if e.Modifiers.Contain(key.ModShortcut) { - t.AdjustSelectionPitch(-12) - } else { - t.AdjustSelectionPitch(-1) - } - return true - } - } - switch t.EditMode { - case EditPatterns: - if iv, err := strconv.Atoi(e.Name); err == nil { - t.SetCurrentPattern(byte(iv)) - return true - } - if b := byte(e.Name[0]) - 'A'; len(e.Name) == 1 && b >= 0 && b < 26 { - t.SetCurrentPattern(b + 10) - return true - } - case EditTracks: - if t.TrackShowHex[t.Cursor.Track] { - if iv, err := strconv.ParseInt(e.Name, 16, 8); err == nil { - t.NumberPressed(byte(iv)) - return true - } - } else { - if e.Name == "A" { - t.SetCurrentNote(0) - return true - } - if val, ok := noteMap[e.Name]; ok { - if _, ok := t.KeyPlaying[e.Name]; !ok { - n := getNoteValue(int(t.Octave.Value), val) - t.SetCurrentNote(n) - trk := t.Cursor.Track - start := t.song.FirstTrackVoice(trk) - end := start + t.song.Tracks[trk].NumVoices - t.KeyPlaying[e.Name] = t.sequencer.Trigger(start, end, n) - return true - } - } - } - case EditUnits: - name := e.Name - if !e.Modifiers.Contain(key.ModShift) { - name = strings.ToLower(name) - } - if val, ok := unitKeyMap[name]; ok { - if e.Modifiers.Contain(key.ModShortcut) { - t.SetUnit(val) - return true - } - } - fallthrough - case EditParameters: - if val, ok := noteMap[e.Name]; ok { - if _, ok := t.KeyPlaying[e.Name]; !ok { - note := getNoteValue(int(t.Octave.Value), val) - instr := t.CurrentInstrument - start := t.song.FirstInstrumentVoice(instr) - end := start + t.song.Patch.Instruments[instr].NumVoices - t.KeyPlaying[e.Name] = t.sequencer.Trigger(start, end, note) - return false - } - } - } - } - if e.State == key.Release { - if f, ok := t.KeyPlaying[e.Name]; ok { - f() - delete(t.KeyPlaying, e.Name) - if t.EditMode == EditTracks && t.Playing && t.getCurrent() == 1 && t.NoteTracking { - t.SetCurrentNote(0) - } - } - } - return false -} - -// getCurrent returns the current (note) value in current pattern under the cursor -func (t *Tracker) getCurrent() byte { - return t.song.Tracks[t.Cursor.Track].Patterns[t.song.Tracks[t.Cursor.Track].Sequence[t.Cursor.Pattern]][t.Cursor.Row] -} - -// NumberPressed handles incoming presses while in either of the hex number columns -func (t *Tracker) NumberPressed(iv byte) { - val := t.getCurrent() - if t.CursorColumn == 0 { - val = ((iv & 0xF) << 4) | (val & 0xF) - } else if t.CursorColumn == 1 { - val = (val & 0xF0) | (iv & 0xF) - } - t.SetCurrentNote(val) -} diff --git a/tracker/model.go b/tracker/model.go new file mode 100644 index 0000000..f21da37 --- /dev/null +++ b/tracker/model.go @@ -0,0 +1,939 @@ +package tracker + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/vsariola/sointu" + "github.com/vsariola/sointu/compiler" +) + +// Model implements the mutable state for the tracker program GUI. +// +// Go does not have immutable slices, so there's no efficient way to guarantee +// accidental mutations in the song. But at least the value members are +// protected. +type Model struct { + song sointu.Song + editMode EditMode + selectionCorner SongPoint + cursor SongPoint + lowNibble bool + instrIndex int + unitIndex int + paramIndex int + octave int + noteTracking bool + usedIDs map[int]bool + maxID int + + prevUndoType string + undoSkipCounter int + undoStack []sointu.Song + redoStack []sointu.Song + + samplesPerRowObservers []chan<- int + patchObservers []chan<- sointu.Patch + scoreObservers []chan<- sointu.Score + playingObservers []chan<- bool +} + +type Parameter struct { + Type ParameterType + Name string + Hint string + Value int + Min int + Max int +} + +type EditMode int + +type ParameterType int + +const ( + EditPatterns EditMode = iota + EditTracks + EditUnits + EditParameters +) + +const ( + IntegerParameter ParameterType = iota + BoolParameter + IDParameter +) + +const maxUndo = 256 + +func NewModel() *Model { + ret := new(Model) + ret.setSongNoUndo(defaultSong.Copy()) + return ret +} + +func (m *Model) ResetSong() { + m.SetSong(defaultSong.Copy()) +} + +func (m *Model) SetSong(song sointu.Song) { + m.saveUndo("SetSong", 0) + m.setSongNoUndo(song) +} + +func (m *Model) SetOctave(value int) bool { + if value < 0 { + value = 0 + } + if value > 9 { + value = 9 + } + if m.octave == value { + return false + } + m.octave = value + return true +} + +func (m *Model) SetInstrument(instrument sointu.Instrument) bool { + if len(instrument.Units) == 0 { + return false + } + m.saveUndo("SetInstrument", 0) + m.freeUnitIDs(m.song.Patch[m.instrIndex].Units) + m.assignUnitIDs(instrument.Units) + m.song.Patch[m.instrIndex] = instrument + m.clampPositions() + m.notifyPatchChange() + return true +} + +func (m *Model) SetInstrIndex(value int) { + m.instrIndex = value + m.clampPositions() +} + +func (m *Model) SetInstrumentVoices(value int) { + if value < 1 { + value = 1 + } + maxRemain := m.MaxInstrumentVoices() + if value > maxRemain { + value = maxRemain + } + if m.Instrument().NumVoices == value { + return + } + m.saveUndo("SetInstrumentVoices", 10) + m.song.Patch[m.instrIndex].NumVoices = value + m.notifyPatchChange() +} + +func (m *Model) MaxInstrumentVoices() int { + maxRemain := 32 - m.song.Patch.NumVoices() + m.Instrument().NumVoices + if maxRemain < 1 { + return 1 + } + return maxRemain +} + +func (m *Model) SetInstrumentName(name string) { + name = strings.TrimSpace(name) + if m.Instrument().Name == name { + return + } + m.saveUndo("SetInstrumentName", 10) + m.song.Patch[m.instrIndex].Name = name +} + +func (m *Model) SetBPM(value int) { + if value < 1 { + value = 1 + } + if value > 999 { + value = 999 + } + if m.song.BPM == value { + return + } + m.saveUndo("SetBPM", 100) + m.song.BPM = value + m.notifySamplesPerRowChange() +} + +func (m *Model) SetRowsPerBeat(value int) { + if value < 1 { + value = 1 + } + if value > 32 { + value = 32 + } + if m.song.RowsPerBeat == value { + return + } + m.saveUndo("SetRowsPerBeat", 10) + m.song.RowsPerBeat = value + m.notifySamplesPerRowChange() +} + +func (m *Model) AddTrack(after bool) { + if !m.CanAddTrack() { + return + } + m.saveUndo("AddTrack", 0) + newTracks := make([]sointu.Track, len(m.song.Score.Tracks)+1) + if after { + m.cursor.Track++ + } + copy(newTracks, m.song.Score.Tracks[:m.cursor.Track]) + copy(newTracks[m.cursor.Track+1:], m.song.Score.Tracks[m.cursor.Track:]) + newTracks[m.cursor.Track] = sointu.Track{ + NumVoices: 1, + Patterns: [][]byte{make([]byte, m.song.Score.RowsPerPattern)}, + } + m.song.Score.Tracks = newTracks + m.clampPositions() + m.notifyScoreChange() +} + +func (m *Model) CanAddTrack() bool { + return m.song.Score.NumVoices() < 32 +} + +func (m *Model) SetTrackVoices(value int) { + if value < 1 { + value = 1 + } + maxRemain := m.MaxTrackVoices() + if value > maxRemain { + value = maxRemain + } + if m.song.Score.Tracks[m.cursor.Track].NumVoices == value { + return + } + m.saveUndo("SetTrackVoices", 10) + m.song.Score.Tracks[m.cursor.Track].NumVoices = value + m.notifyScoreChange() +} + +func (m *Model) MaxTrackVoices() int { + maxRemain := 32 - m.song.Score.NumVoices() + m.song.Score.Tracks[m.cursor.Track].NumVoices + if maxRemain < 1 { + maxRemain = 1 + } + return maxRemain +} + +func (m *Model) AddInstrument(after bool) { + if !m.CanAddInstrument() { + return + } + m.saveUndo("AddInstrument", 0) + newInstruments := make([]sointu.Instrument, len(m.song.Patch)+1) + if after { + m.instrIndex++ + } + copy(newInstruments, m.song.Patch[:m.instrIndex]) + copy(newInstruments[m.instrIndex+1:], m.song.Patch[m.instrIndex:]) + newInstr := defaultInstrument.Copy() + m.assignUnitIDs(newInstr.Units) + newInstruments[m.instrIndex] = newInstr + m.unitIndex = 0 + m.paramIndex = 0 + m.song.Patch = newInstruments + m.notifyPatchChange() +} + +func (m *Model) CanAddInstrument() bool { + return m.song.Patch.NumVoices() < 32 +} + +func (m *Model) SwapInstruments(i, j int) { + if i < 0 || j < 0 || i >= len(m.song.Patch) || j >= len(m.song.Patch) || i == j { + return + } + m.saveUndo("SwapInstruments", 10) + instruments := m.song.Patch + instruments[i], instruments[j] = instruments[j], instruments[i] + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) DeleteInstrument(forward bool) { + if !m.CanDeleteInstrument() { + return + } + m.saveUndo("DeleteInstrument", 0) + m.freeUnitIDs(m.song.Patch[m.instrIndex].Units) + m.song.Patch = append(m.song.Patch[:m.instrIndex], m.song.Patch[m.instrIndex+1:]...) + if (!forward && m.instrIndex > 0) || m.instrIndex >= len(m.song.Patch) { + m.instrIndex-- + } + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) CanDeleteInstrument() bool { + return len(m.song.Patch) > 1 +} + +func (m *Model) Note() byte { + trk := m.song.Score.Tracks[m.cursor.Track] + if m.cursor.Pattern < 0 || m.cursor.Pattern >= len(trk.Order) { + return 1 + } + p := trk.Order[m.cursor.Pattern] + if p < 0 || p >= len(trk.Patterns) { + return 1 + } + pat := trk.Patterns[p] + if m.cursor.Row < 0 || m.cursor.Row >= len(pat) { + return 1 + } + return pat[m.cursor.Row] +} + +// SetCurrentNote sets the (note) value in current pattern under cursor to iv +func (m *Model) SetNote(iv byte) { + m.saveUndo("SetNote", 10) + tracks := m.song.Score.Tracks + order := tracks[m.cursor.Track].Order + if m.cursor.Pattern < 0 || m.cursor.Pattern >= len(order) || m.cursor.Row < 0 { + return + } + patIndex := order[m.cursor.Pattern] + if patIndex < 0 { + return + } + for len(tracks[m.cursor.Track].Patterns) <= patIndex { + tracks[m.cursor.Track].Patterns = append(tracks[m.cursor.Track].Patterns, nil) + } + patterns := tracks[m.cursor.Track].Patterns + for len(patterns[patIndex]) <= m.cursor.Row { + patterns[patIndex] = append(patterns[patIndex], 1) + } + patterns[patIndex][m.cursor.Row] = iv + m.notifyScoreChange() +} + +func (m *Model) SetCurrentPattern(pat int) { + m.saveUndo("SetCurrentPattern", 0) + track := &m.song.Score.Tracks[m.cursor.Track] + for len(track.Order) <= m.cursor.Pattern { + track.Order = append(track.Order, -1) + } + track.Order[m.cursor.Pattern] = pat + m.notifyScoreChange() +} + +func (m *Model) SetSongLength(value int) { + if value < 1 { + value = 1 + } + if value == m.song.Score.Length { + return + } + m.saveUndo("SetSongLength", 10) + m.song.Score.Length = value + m.clampPositions() + m.notifyScoreChange() +} + +func (m *Model) SetRowsPerPattern(value int) { + if value < 1 { + value = 1 + } + if value > 255 { + value = 255 + } + if value == m.song.Score.RowsPerPattern { + return + } + m.saveUndo("SetRowsPerPattern", 10) + m.song.Score.RowsPerPattern = value + m.clampPositions() + m.notifyScoreChange() +} + +func (m *Model) SetUnitType(t string) { + unit, ok := defaultUnits[t] + if !ok { // if the type is invalid, we just set it to empty unit + unit = sointu.Unit{Parameters: make(map[string]int)} + } else { + unit = unit.Copy() + } + if m.Unit().Type == unit.Type { + return + } + m.saveUndo("SetUnitType", 0) + oldID := m.Unit().ID + m.Instrument().Units[m.unitIndex] = unit + m.Instrument().Units[m.unitIndex].ID = oldID // keep the ID of the replaced unit + m.notifyPatchChange() +} + +func (m *Model) SetUnitIndex(value int) { + m.unitIndex = value + m.paramIndex = 0 + m.clampPositions() +} + +func (m *Model) AddUnit(after bool) { + m.saveUndo("AddUnit", 10) + newUnits := make([]sointu.Unit, len(m.Instrument().Units)+1) + if after { + m.unitIndex++ + } + copy(newUnits, m.Instrument().Units[:m.unitIndex]) + copy(newUnits[m.unitIndex+1:], m.Instrument().Units[m.unitIndex:]) + m.assignUnitIDs(newUnits[m.unitIndex : m.unitIndex+1]) + m.song.Patch[m.instrIndex].Units = newUnits + m.paramIndex = 0 + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) AddOrderRow(after bool) { + m.saveUndo("AddOrderRow", 10) + if after { + m.cursor.Pattern++ + } + for i, trk := range m.song.Score.Tracks { + if l := len(trk.Order); l > m.cursor.Pattern { + newOrder := make([]int, l+1) + copy(newOrder, trk.Order[:m.cursor.Pattern]) + copy(newOrder[m.cursor.Pattern+1:], trk.Order[m.cursor.Pattern:]) + newOrder[m.cursor.Pattern] = -1 + m.song.Score.Tracks[i].Order = newOrder + } + } + m.song.Score.Length++ + m.selectionCorner = m.cursor + m.clampPositions() + m.notifyScoreChange() +} + +func (m *Model) DeleteOrderRow(forward bool) { + if m.song.Score.Length <= 1 { + return + } + m.saveUndo("DeleteOrderRow", 0) + for i, trk := range m.song.Score.Tracks { + if l := len(trk.Order); l > m.cursor.Pattern { + newOrder := make([]int, l-1) + copy(newOrder, trk.Order[:m.cursor.Pattern]) + copy(newOrder[m.cursor.Pattern:], trk.Order[m.cursor.Pattern+1:]) + m.song.Score.Tracks[i].Order = newOrder + } + } + if !forward && m.cursor.Pattern > 0 { + m.cursor.Pattern-- + } + m.song.Score.Length-- + m.selectionCorner = m.cursor + m.clampPositions() + m.notifyScoreChange() +} + +func (m *Model) DeleteUnit(forward bool) { + if !m.CanDeleteUnit() { + return + } + instr := m.Instrument() + m.saveUndo("DeleteUnit", 0) + delete(m.usedIDs, instr.Units[m.unitIndex].ID) + newUnits := make([]sointu.Unit, len(instr.Units)-1) + copy(newUnits, instr.Units[:m.unitIndex]) + copy(newUnits[m.unitIndex:], instr.Units[m.unitIndex+1:]) + m.song.Patch[m.instrIndex].Units = newUnits + if !forward && m.unitIndex > 0 { + m.unitIndex-- + } + m.paramIndex = 0 + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) CanDeleteUnit() bool { + return len(m.Instrument().Units) > 1 +} + +func (m *Model) ResetParam() { + p, err := m.Param(m.paramIndex) + if err != nil { + return + } + unit := m.Unit() + paramList, ok := sointu.UnitTypes[unit.Type] + if !ok || m.paramIndex < 0 || m.paramIndex >= len(paramList) { + return + } + paramType := paramList[m.paramIndex] + defaultValue, ok := defaultUnits[unit.Type].Parameters[paramType.Name] + if unit.Parameters[p.Name] == defaultValue { + return + } + m.saveUndo("ResetParam", 0) + unit.Parameters[paramType.Name] = defaultValue + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) SetParamIndex(value int) { + m.paramIndex = value + m.clampPositions() +} + +func (m *Model) setGmDlsEntry(index int) { + if index < 0 || index >= len(GmDlsEntries) { + return + } + entry := GmDlsEntries[index] + unit := m.Unit() + if unit.Type != "oscillator" || unit.Parameters["type"] != sointu.Sample { + return + } + if unit.Parameters["samplestart"] == entry.Start && unit.Parameters["loopstart"] == entry.LoopStart && unit.Parameters["looplength"] == entry.LoopLength { + return + } + m.saveUndo("SetGmDlsEntry", 20) + unit.Parameters["samplestart"] = entry.Start + unit.Parameters["loopstart"] = entry.LoopStart + unit.Parameters["looplength"] = entry.LoopLength + unit.Parameters["transpose"] = 64 + entry.SuggestedTranspose + m.notifyPatchChange() +} + +func (m *Model) SwapUnits(i, j int) { + units := m.Instrument().Units + if i < 0 || j < 0 || i >= len(units) || j >= len(units) || i == j { + return + } + m.saveUndo("SwapUnits", 10) + units[i], units[j] = units[j], units[i] + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) getSelectionRange() (int, int, int, int) { + r1 := m.cursor.Pattern*m.song.Score.RowsPerPattern + m.cursor.Row + r2 := m.selectionCorner.Pattern*m.song.Score.RowsPerPattern + m.selectionCorner.Row + if r2 < r1 { + r1, r2 = r2, r1 + } + t1 := m.cursor.Track + t2 := m.selectionCorner.Track + if t2 < t1 { + t1, t2 = t2, t1 + } + return r1, r2, t1, t2 +} + +func (m *Model) AdjustSelectionPitch(delta int) { + m.saveUndo("AdjustSelectionPitch", 10) + r1, r2, t1, t2 := m.getSelectionRange() + for c := t1; c <= t2; c++ { + adjustedNotes := map[struct { + Pat int + Row int + }]bool{} + for r := r1; r <= r2; r++ { + s := SongRow{Row: r}.Wrap(m.song.Score) + if s.Pattern >= len(m.song.Score.Tracks[c].Order) { + break + } + p := m.song.Score.Tracks[c].Order[s.Pattern] + if p < 0 { + continue + } + noteIndex := struct { + Pat int + Row int + }{p, s.Row} + if !adjustedNotes[noteIndex] { + patterns := m.song.Score.Tracks[c].Patterns + if p >= len(patterns) { + continue + } + pattern := patterns[p] + if s.Row >= len(pattern) { + continue + } + if val := pattern[s.Row]; val > 1 { + newVal := int(val) + delta + if newVal < 2 { + newVal = 2 + } else if newVal > 255 { + newVal = 255 + } + pattern[s.Row] = byte(newVal) + } + adjustedNotes[noteIndex] = true + } + } + } + m.notifyScoreChange() +} + +func (m *Model) DeleteSelection() { + m.saveUndo("DeleteSelection", 0) + r1, r2, t1, t2 := m.getSelectionRange() + for r := r1; r <= r2; r++ { + s := SongRow{Row: r}.Wrap(m.song.Score) + for c := t1; c <= t2; c++ { + p := m.song.Score.Tracks[c].Order[s.Pattern] + if p < 0 { + continue + } + patterns := m.song.Score.Tracks[c].Patterns + if p >= len(patterns) { + continue + } + pattern := patterns[p] + if s.Row >= len(pattern) { + continue + } + m.song.Score.Tracks[c].Patterns[p][s.Row] = 1 + } + } + m.notifyScoreChange() +} + +func (m *Model) DeletePatternSelection() { + m.saveUndo("DeletePatternSelection", 0) + r1, r2, t1, t2 := m.getSelectionRange() + p1 := SongRow{Row: r1}.Wrap(m.song.Score).Pattern + p2 := SongRow{Row: r2}.Wrap(m.song.Score).Pattern + for p := p1; p <= p2; p++ { + for c := t1; c <= t2; c++ { + if p < len(m.song.Score.Tracks[c].Order) { + m.song.Score.Tracks[c].Order[p] = -1 + } + } + } + m.notifyScoreChange() +} + +func (m *Model) SetEditMode(value EditMode) { + m.editMode = value +} + +func (m *Model) Undo() { + if !m.CanUndo() { + return + } + if len(m.redoStack) >= maxUndo { + m.redoStack = m.redoStack[1:] + } + m.redoStack = append(m.redoStack, m.song.Copy()) + m.setSongNoUndo(m.undoStack[len(m.undoStack)-1]) + m.undoStack = m.undoStack[:len(m.undoStack)-1] +} + +func (m *Model) CanUndo() bool { + return len(m.undoStack) > 0 +} + +func (m *Model) Redo() { + if !m.CanRedo() { + return + } + if len(m.undoStack) >= maxUndo { + m.undoStack = m.undoStack[1:] + } + m.undoStack = append(m.undoStack, m.song.Copy()) + m.setSongNoUndo(m.redoStack[len(m.redoStack)-1]) + m.redoStack = m.redoStack[:len(m.redoStack)-1] +} + +func (m *Model) CanRedo() bool { + return len(m.redoStack) > 0 +} + +func (m *Model) SetNoteTracking(value bool) { + m.noteTracking = value +} + +func (m *Model) NoteTracking() bool { + return m.noteTracking +} + +func (m *Model) Octave() int { + return m.octave +} + +func (m *Model) Song() sointu.Song { + return m.song +} + +func (m *Model) EditMode() EditMode { + return m.editMode +} + +func (m *Model) SelectionCorner() SongPoint { + return m.selectionCorner +} + +func (m *Model) SetSelectionCorner(value SongPoint) { + m.selectionCorner = value + m.clampPositions() +} + +func (m *Model) Cursor() SongPoint { + return m.cursor +} + +func (m *Model) SetCursor(value SongPoint) { + m.cursor = value + m.clampPositions() +} + +func (m *Model) LowNibble() bool { + return m.lowNibble +} + +func (m *Model) SetLowNibble(value bool) { + m.lowNibble = value +} + +func (m *Model) InstrIndex() int { + return m.instrIndex +} + +func (m *Model) Track() sointu.Track { + return m.song.Score.Tracks[m.cursor.Track] +} + +func (m *Model) Instrument() sointu.Instrument { + return m.song.Patch[m.instrIndex] +} + +func (m *Model) Unit() sointu.Unit { + return m.song.Patch[m.instrIndex].Units[m.unitIndex] +} + +func (m *Model) UnitIndex() int { + return m.unitIndex +} + +func (m *Model) ParamIndex() int { + return m.paramIndex +} + +func (m *Model) clampPositions() { + m.cursor = m.cursor.Clamp(m.song.Score) + m.selectionCorner = m.selectionCorner.Clamp(m.song.Score) + if !m.Track().Effect { + m.lowNibble = false + } + m.instrIndex = clamp(m.instrIndex, 0, len(m.song.Patch)-1) + m.unitIndex = clamp(m.unitIndex, 0, len(m.Instrument().Units)-1) + for m.paramIndex < 0 { + if m.unitIndex == 0 { + m.paramIndex = 0 + break + } + m.unitIndex-- + m.paramIndex += m.NumParams() + } + for n := m.NumParams(); m.paramIndex >= n; n = m.NumParams() { + if m.unitIndex == len(m.Instrument().Units)-1 { + m.paramIndex = n - 1 + break + } + m.paramIndex -= n + m.unitIndex++ + } +} + +func (m *Model) NumParams() int { + unit := m.Unit() + if unit.Type == "oscillator" { + if unit.Parameters["type"] != sointu.Sample { + return 10 + } + return 14 + } + numSettableParams := 0 + for _, t := range sointu.UnitTypes[m.Unit().Type] { + if t.CanSet { + numSettableParams++ + } + } + if numSettableParams == 0 { + numSettableParams = 1 + } + return numSettableParams +} + +func (m *Model) Param(index int) (Parameter, error) { + unit := m.Unit() + for _, t := range sointu.UnitTypes[unit.Type] { + if !t.CanSet { + continue + } + if index != 0 { + index-- + continue + } + typ := IntegerParameter + if t.MaxValue == t.MinValue+1 { + typ = BoolParameter + } + val := m.Unit().Parameters[t.Name] + name := t.Name + hint := m.song.Patch.ParamHintString(m.instrIndex, m.unitIndex, name) + var text string + if hint != "" { + text = fmt.Sprintf("%v / %v", val, hint) + } else { + text = strconv.Itoa(val) + } + min, max := t.MinValue, t.MaxValue + if unit.Type == "send" { + if t.Name == "voice" { + i, _, err := m.song.Patch.FindSendTarget(unit.Parameters["target"]) + if err == nil { + max = m.song.Patch[i].NumVoices + } + } else if t.Name == "target" { + typ = IDParameter + } + } + return Parameter{Type: typ, Min: min, Max: max, Name: name, Hint: text, Value: val}, nil + } + if unit.Type == "oscillator" && index == 0 { + key := compiler.SampleOffset{Start: uint32(unit.Parameters["samplestart"]), LoopStart: uint16(unit.Parameters["loopstart"]), LoopLength: uint16(unit.Parameters["looplength"])} + val := 0 + hint := "0 / custom" + if v, ok := GmDlsEntryMap[key]; ok { + val = v + 1 + hint = fmt.Sprintf("%v / %v", val, GmDlsEntries[v].Name) + } + return Parameter{Type: IntegerParameter, Min: 0, Max: len(GmDlsEntries), Name: "sample", Hint: hint, Value: val}, nil + } + return Parameter{}, errors.New("invalid parameter") +} + +func (m *Model) SetParam(value int) { + p, err := m.Param(m.paramIndex) + if err != nil { + return + } + if value < p.Min { + value = p.Min + } else if value > p.Max { + value = p.Max + } + if p.Name == "sample" { + m.setGmDlsEntry(value - 1) + return + } + unit := m.Unit() + if unit.Parameters[p.Name] == value { + return + } + m.saveUndo("SetParam", 20) + unit.Parameters[p.Name] = value + m.clampPositions() + m.notifyPatchChange() +} + +func (m *Model) AddPatchObserver(observer chan<- sointu.Patch) { + m.patchObservers = append(m.patchObservers, observer) +} + +func (m *Model) AddScoreObserver(observer chan<- sointu.Score) { + m.scoreObservers = append(m.scoreObservers, observer) +} + +func (m *Model) AddSamplesPerRowObserver(observer chan<- int) { + m.samplesPerRowObservers = append(m.samplesPerRowObservers, observer) +} + +func (m *Model) AddPlayingObserver(observer chan<- bool) { + m.playingObservers = append(m.playingObservers, observer) +} + +func (m *Model) setSongNoUndo(song sointu.Song) { + m.song = song + m.usedIDs = make(map[int]bool) + m.maxID = 0 + for _, instr := range m.song.Patch { + for _, unit := range instr.Units { + if m.maxID < unit.ID { + m.maxID = unit.ID + } + } + } + for _, instr := range m.song.Patch { + m.assignUnitIDs(instr.Units) + } + m.clampPositions() + m.notifySamplesPerRowChange() + m.notifyPatchChange() + m.notifyScoreChange() +} + +func (m *Model) notifyPatchChange() { + for _, channel := range m.patchObservers { + channel <- m.song.Patch.Copy() + } +} + +func (m *Model) notifyScoreChange() { + for _, channel := range m.scoreObservers { + channel <- m.song.Score.Copy() + } +} + +func (m *Model) notifySamplesPerRowChange() { + for _, channel := range m.samplesPerRowObservers { + channel <- m.song.SamplesPerRow() + } +} + +func (m *Model) saveUndo(undoType string, undoSkipping int) { + if m.prevUndoType == undoType && m.undoSkipCounter < undoSkipping { + m.undoSkipCounter++ + return + } + m.prevUndoType = undoType + m.undoSkipCounter = 0 + if len(m.undoStack) >= maxUndo { + m.undoStack = m.undoStack[1:] + } + m.undoStack = append(m.undoStack, m.song.Copy()) + m.redoStack = m.redoStack[:0] +} + +func (m *Model) freeUnitIDs(units []sointu.Unit) { + for _, u := range units { + delete(m.usedIDs, u.ID) + } +} + +func (m *Model) assignUnitIDs(units []sointu.Unit) { + for i := range units { + if units[i].ID == 0 || m.usedIDs[units[i].ID] { + m.maxID++ + units[i].ID = m.maxID + } + m.usedIDs[units[i].ID] = true + if m.maxID < units[i].ID { + m.maxID = units[i].ID + } + } +} + +func clamp(a, min, max int) int { + if a < min { + return min + } + if a > max { + return max + } + return a +} diff --git a/tracker/music.go b/tracker/music.go index 0d530c1..bcd6ba6 100644 --- a/tracker/music.go +++ b/tracker/music.go @@ -19,8 +19,7 @@ var notes = []string{ "B-", } -// valueAsNote returns the textual representation of a note value -func valueAsNote(val byte) string { +func NoteStr(val byte) string { if val == 1 { return "..." // hold } @@ -38,7 +37,6 @@ func valueAsNote(val byte) string { return fmt.Sprintf("%s%d", notes[oNote], octave) } -// noteValue return the note value for a particular note and octave combination -func getNoteValue(octave, note int) byte { +func NoteAsValue(octave, note int) byte { return byte(baseNote + (octave * 12) + note) } diff --git a/tracker/player.go b/tracker/player.go new file mode 100644 index 0000000..ddec090 --- /dev/null +++ b/tracker/player.go @@ -0,0 +1,274 @@ +package tracker + +import ( + "math" + "sync" + "sync/atomic" + + "github.com/vsariola/sointu" +) + +type Player struct { + packedPos uint64 + + playCmds chan uint64 + + mutex sync.Mutex + runningID uint32 + voiceNoteID []uint32 + voiceReleased []bool + synth sointu.Synth + patch sointu.Patch + + synthNotNil int32 +} + +type voiceNote struct { + voice int + note byte +} + +// Position returns the current play position (song row), and a bool indicating +// if the player is currently playing. The function is threadsafe. +func (p *Player) Position() (SongRow, bool) { + packedPos := atomic.LoadUint64(&p.packedPos) + if packedPos == math.MaxUint64 { // stopped + return SongRow{}, false + } + return unpackPosition(packedPos), true +} + +func (p *Player) Playing() bool { + packedPos := atomic.LoadUint64(&p.packedPos) + if packedPos == math.MaxUint64 { // stopped + return false + } + return true +} + +func (p *Player) Play(position SongRow) { + position.Row-- // we'll advance this very shortly + p.playCmds <- packPosition(position) +} + +func (p *Player) Stop() { + p.playCmds <- math.MaxUint64 +} + +func (p *Player) Disable() { + p.mutex.Lock() + p.synth = nil + atomic.StoreInt32(&p.synthNotNil, 0) + p.mutex.Unlock() +} + +func (p *Player) Enabled() bool { + return atomic.LoadInt32(&p.synthNotNil) == 1 +} + +func NewPlayer(service sointu.SynthService, closer <-chan struct{}, patchs <-chan sointu.Patch, scores <-chan sointu.Score, samplesPerRows <-chan int, posChanged chan<- struct{}, outputs ...chan<- []float32) *Player { + p := &Player{playCmds: make(chan uint64, 16)} + go func() { + var score sointu.Score + buffer := make([]float32, 2048) + buffer2 := make([]float32, 2048) + zeros := make([]float32, 2048) + rowTime := 0 + samplesPerRow := math.MaxInt32 + var trackIDs []uint32 + atomic.StoreUint64(&p.packedPos, math.MaxUint64) + for { + select { + case <-closer: + for _, o := range outputs { + close(o) + } + return + case patch := <-patchs: + p.mutex.Lock() + p.patch = patch + if p.synth != nil { + err := p.synth.Update(patch) + if err != nil { + p.synth = nil + atomic.StoreInt32(&p.synthNotNil, 0) + } + } else { + s, err := service.Compile(patch) + if err == nil { + p.synth = s + atomic.StoreInt32(&p.synthNotNil, 1) + for i := 0; i < 32; i++ { + s.Release(i) + } + } + } + p.mutex.Unlock() + case score = <-scores: + if row, playing := p.Position(); playing { + atomic.StoreUint64(&p.packedPos, packPosition(row.Wrap(score))) + } + case samplesPerRow = <-samplesPerRows: + case packedPos := <-p.playCmds: + atomic.StoreUint64(&p.packedPos, packedPos) + if packedPos == math.MaxUint64 { + p.mutex.Lock() + for _, id := range trackIDs { + p.release(id) + } + p.mutex.Unlock() + } + rowTime = math.MaxInt32 + default: + row, playing := p.Position() + if playing && rowTime >= samplesPerRow && score.Length > 0 && score.RowsPerPattern > 0 { + row.Row++ // advance row (this is why we subtracted one in Play()) + row = row.Wrap(score) + atomic.StoreUint64(&p.packedPos, packPosition(row)) + select { + case posChanged <- struct{}{}: + default: + } + p.mutex.Lock() + lastVoice := 0 + for i, t := range score.Tracks { + start := lastVoice + lastVoice = start + t.NumVoices + if row.Pattern < 0 || row.Pattern >= len(t.Order) { + continue + } + o := t.Order[row.Pattern] + if o < 0 || o >= len(t.Patterns) { + continue + } + pat := t.Patterns[o] + if row.Row < 0 || row.Row >= len(pat) { + continue + } + n := pat[row.Row] + for len(trackIDs) <= i { + trackIDs = append(trackIDs, 0) + } + if n != 1 && trackIDs[i] > 0 { + p.release(trackIDs[i]) + } + if n > 1 && p.synth != nil { + trackIDs[i] = p.trigger(start, lastVoice, n) + } + } + p.mutex.Unlock() + rowTime = 0 + } + if p.synth != nil { + renderTime := samplesPerRow - rowTime + if !playing { + renderTime = math.MaxInt32 + } + p.mutex.Lock() + rendered, timeAdvanced, err := p.synth.Render(buffer, renderTime) + if err != nil { + p.synth = nil + atomic.StoreInt32(&p.synthNotNil, 0) + } + p.mutex.Unlock() + rowTime += timeAdvanced + for _, o := range outputs { + o <- buffer[:rendered*2] + } + buffer2, buffer = buffer, buffer2 + } else { + rowTime += len(zeros) / 2 + for _, o := range outputs { + o <- zeros + } + } + } + } + }() + return p +} + +// Trigger is used to manually play a note on the sequencer when jamming. It is +// thread-safe. It starts to play one of the voice in the range voiceStart +// (inclusive) and voiceEnd (exclusive). It returns a id that can be called to +// release the voice playing the note (in case the voice has not been captured +// by someone else already). +func (p *Player) Trigger(voiceStart, voiceEnd int, note byte) uint32 { + if note <= 1 { + return 0 + } + p.mutex.Lock() + id := p.trigger(voiceStart, voiceEnd, note) + p.mutex.Unlock() + return id +} + +// Release is used to manually release a note on the player when jamming. +// Expects an ID that was previously acquired by calling Trigger. +func (p *Player) Release(ID uint32) { + if ID == 0 { + return + } + p.mutex.Lock() + p.release(ID) + p.mutex.Unlock() +} + +func (p *Player) trigger(voiceStart, voiceEnd int, note byte) uint32 { + if p.synth == nil { + return 0 + } + var oldestID uint32 = math.MaxUint32 + p.runningID++ + newID := p.runningID + oldestReleased := false + oldestVoice := 0 + for i := voiceStart; i < voiceEnd; i++ { + for len(p.voiceReleased) <= i { + p.voiceReleased = append(p.voiceReleased, true) + } + for len(p.voiceNoteID) <= i { + p.voiceNoteID = append(p.voiceNoteID, 0) + } + // find a suitable voice to trigger. if the voice has been released, + // then we prefer to trigger that over a voice that is still playing. in + // case two voices are both playing or or both are released, we prefer + // the older one + id := p.voiceNoteID[i] + isReleased := p.voiceReleased[i] + if id < oldestID && (oldestReleased == isReleased) || (!oldestReleased && isReleased) { + oldestVoice = i + oldestID = id + oldestReleased = isReleased + } + } + p.voiceNoteID[oldestVoice] = newID + p.voiceReleased[oldestVoice] = false + if p.synth != nil { + p.synth.Trigger(oldestVoice, note) + } + return newID +} + +func (p *Player) release(ID uint32) { + if p.synth == nil { + return + } + for i := 0; i < len(p.voiceNoteID); i++ { + if p.voiceNoteID[i] == ID && !p.voiceReleased[i] { + p.voiceReleased[i] = true + p.synth.Release(i) + return + } + } +} + +func packPosition(pos SongRow) uint64 { + return (uint64(uint32(pos.Pattern)) << 32) + uint64(uint32(pos.Row)) +} + +func unpackPosition(packedPos uint64) SongRow { + pattern := int(int32(packedPos >> 32)) + row := int(int32(packedPos & 0xFFFFFFFF)) + return SongRow{Pattern: pattern, Row: row} +} diff --git a/tracker/rowmarkers.go b/tracker/rowmarkers.go deleted file mode 100644 index 49a3fd0..0000000 --- a/tracker/rowmarkers.go +++ /dev/null @@ -1,61 +0,0 @@ -package tracker - -import ( - "fmt" - "image" - "strings" - - "gioui.org/f32" - "gioui.org/layout" - "gioui.org/op" - "gioui.org/op/clip" - "gioui.org/op/paint" - "gioui.org/widget" -) - -const rowMarkerWidth = 50 - -func (t *Tracker) layoutRowMarkers(patternRows, sequenceLength, cursorRow, cursorPattern, cursorCol, playRow, playPattern int) layout.Widget { - return func(gtx layout.Context) layout.Dimensions { - gtx.Constraints.Min.X = rowMarkerWidth - paint.FillShape(gtx.Ops, rowMarkerSurfaceColor, clip.Rect{ - Max: gtx.Constraints.Max, - }.Op()) - defer op.Save(gtx.Ops).Load() - clip.Rect{Max: gtx.Constraints.Max}.Add(gtx.Ops) - op.Offset(f32.Pt(0, float32(gtx.Constraints.Max.Y-trackRowHeight)/2)).Add(gtx.Ops) - cursorSongRow := cursorPattern*patternRows + cursorRow - playSongRow := playPattern*patternRows + playRow - op.Offset(f32.Pt(0, (-1*trackRowHeight)*float32(cursorSongRow))).Add(gtx.Ops) - beatMarkerDensity := t.song.RowsPerBeat - for beatMarkerDensity <= 2 { - beatMarkerDensity *= 2 - } - for i := 0; i < sequenceLength; i++ { - for j := 0; j < patternRows; j++ { - songRow := i*patternRows + j - if mod(songRow, beatMarkerDensity*2) == 0 { - paint.FillShape(gtx.Ops, twoBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) - } else if mod(songRow, beatMarkerDensity) == 0 { - paint.FillShape(gtx.Ops, oneBeatHighlight, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) - } - if songRow == playSongRow { - paint.FillShape(gtx.Ops, trackerPlayColor, clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, trackRowHeight)}.Op()) - } - if j == 0 { - paint.ColorOp{Color: rowMarkerPatternTextColor}.Add(gtx.Ops) - widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", i))) - } - if t.EditMode == EditTracks && songRow == cursorSongRow { - paint.ColorOp{Color: trackerActiveTextColor}.Add(gtx.Ops) - } else { - paint.ColorOp{Color: rowMarkerRowTextColor}.Add(gtx.Ops) - } - op.Offset(f32.Pt(rowMarkerWidth/2, 0)).Add(gtx.Ops) - widget.Label{}.Layout(gtx, textShaper, trackerFont, trackerFontSize, strings.ToUpper(fmt.Sprintf("%02x", j))) - op.Offset(f32.Pt(-rowMarkerWidth/2, trackRowHeight)).Add(gtx.Ops) - } - } - return layout.Dimensions{Size: image.Pt(rowMarkerWidth, gtx.Constraints.Max.Y)} - } -} diff --git a/tracker/run.go b/tracker/run.go deleted file mode 100644 index c12ea39..0000000 --- a/tracker/run.go +++ /dev/null @@ -1,39 +0,0 @@ -package tracker - -import ( - "gioui.org/app" - "gioui.org/io/clipboard" - "gioui.org/io/key" - "gioui.org/io/system" - "gioui.org/layout" - "gioui.org/op" -) - -func (t *Tracker) Run(w *app.Window) error { - var ops op.Ops - for { - select { - case <-t.refresh: - w.Invalidate() - case e := <-w.Events(): - switch e := e.(type) { - case system.DestroyEvent: - return e.Err - case key.Event: - if t.KeyEvent(w, e) { - w.Invalidate() - } - case clipboard.Event: - err := t.UnmarshalContent([]byte(e.Text)) - if err == nil { - w.Invalidate() - } - case system.FrameEvent: - gtx := layout.NewContext(&ops, e) - t.Layout(gtx) - e.Frame(gtx.Ops) - } - } - } - -} diff --git a/tracker/sequencer.go b/tracker/sequencer.go deleted file mode 100644 index 47a0f95..0000000 --- a/tracker/sequencer.go +++ /dev/null @@ -1,271 +0,0 @@ -package tracker - -import ( - "math" - "sync/atomic" - - "github.com/vsariola/sointu" -) - -// how many times the sequencer tries to fill the buffer. If the buffer is not -// filled after this many tries, there's probably an issue with rowlength (e.g. -// infinite BPM, rowlength = 0) or something else, so we error instead of -// letting ReadAudio hang. -const SEQUENCER_MAX_READ_TRIES = 1000 - -// Sequencer is a AudioSource that uses the given synth to render audio. In -// periods of rowLength, it pulls new notes to trigger/release from the given -// iterator. Note that the iterator should be thread safe, as the ReadAudio -// might be called from another go routine. -type Sequencer struct { - validSynth int32 - closer chan struct{} - setPatch chan sointu.Patch - setRowLength chan int - noteOn chan noteOnEvent - noteOff chan uint32 - synth sointu.Synth - voiceNoteID []uint32 - voiceReleased []bool - idCounter uint32 -} - -type RowNote struct { - NumVoices int - Note byte -} - -type noteOnEvent struct { - voiceStart int - voiceEnd int - note byte - id uint32 -} - -type noteID struct { - voice int - id uint32 -} - -func NewSequencer(bufferSize int, service sointu.SynthService, context sointu.AudioContext, callBack func([]float32), iterator func([]RowNote) []RowNote) *Sequencer { - ret := &Sequencer{ - closer: make(chan struct{}), - setPatch: make(chan sointu.Patch, 32), - setRowLength: make(chan int, 32), - noteOn: make(chan noteOnEvent, 32), - noteOff: make(chan uint32, 32), - voiceNoteID: make([]uint32, 32), - voiceReleased: make([]bool, 32), - } - // the iterator is a bit unconventional in the sense that it might return - // false to indicate that there is no row available, but might still return - // true in future attempts if new rows become available. - go ret.loop(bufferSize, service, context, callBack, iterator) - return ret -} - -func (s *Sequencer) loop(bufferSize int, service sointu.SynthService, context sointu.AudioContext, callBack func([]float32), iterator func([]RowNote) []RowNote) { - buffer := make([]float32, bufferSize) - renderTries := 0 - audioOut := context.Output() - defer audioOut.Close() - rowIn := make([]RowNote, 32) - rowLength := math.MaxInt32 - rowTimeRemaining := 0 - trackNotes := make([]uint32, 32) - for { - for !s.Enabled() { - select { - case <-s.closer: - return - case <-s.noteOn: - case <-s.noteOff: - case rowLength = <-s.setRowLength: - case patch := <-s.setPatch: - var err error - s.synth, err = service.Compile(patch) - if err == nil { - s.enable() - for i := range s.voiceReleased { - s.voiceReleased[i] = true - s.synth.Release(i) - } - break - } - } - } - released := false - for s.Enabled() { - select { - case <-s.closer: - return - case n := <-s.noteOn: - s.trigger(n.voiceStart, n.voiceEnd, n.note, n.id) - case n := <-s.noteOff: - s.release(n) - case rowLength = <-s.setRowLength: - case patch := <-s.setPatch: - err := s.synth.Update(patch) - if err != nil { - s.Disable() - break - } - default: - renderTime := rowTimeRemaining - if rowTimeRemaining <= 0 { - rowOut := iterator(rowIn[:0]) - if len(rowOut) > 0 { - curVoice := 0 - for i, rn := range rowOut { - end := curVoice + rn.NumVoices - if rn.Note != 1 { - s.release(trackNotes[i]) - } - if rn.Note > 1 { - id := s.getNewID() - s.trigger(curVoice, end, rn.Note, id) - trackNotes[i] = id - } - curVoice = end - } - rowTimeRemaining = rowLength - renderTime = rowLength - released = false - } else { - if !released { - s.releaseVoiceRange(0, len(s.voiceNoteID)) - released = true - } - rowTimeRemaining = 0 - renderTime = math.MaxInt32 - } - } - rendered, timeAdvanced, err := s.synth.Render(buffer, renderTime) - callBack(buffer) - if err != nil { - s.Disable() - break - } - rowTimeRemaining -= timeAdvanced - if timeAdvanced == 0 { - renderTries++ - } else { - renderTries = 0 - } - if renderTries >= SEQUENCER_MAX_READ_TRIES { - s.Disable() - break - } - err = audioOut.WriteAudio(buffer[:2*rendered]) - if err != nil { - s.Disable() - break - } - } - } - } -} - -func (s *Sequencer) Enabled() bool { - return atomic.LoadInt32(&s.validSynth) == 1 -} - -func (s *Sequencer) Disable() { - atomic.StoreInt32(&s.validSynth, 0) -} - -func (s *Sequencer) SetRowLength(rowLength int) { - s.setRowLength <- rowLength -} - -// Close closes the sequencer and releases all its resources -func (s *Sequencer) Close() { - s.closer <- struct{}{} -} - -// SetPatch updates the synth to match given patch -func (s *Sequencer) SetPatch(patch sointu.Patch) { - s.setPatch <- patch.Copy() -} - -// Trigger is used to manually play a note on the sequencer when jamming. It is -// thread-safe. It starts to play one of the voice in the range voiceStart -// (inclusive) and voiceEnd (exclusive). It returns a release function that can -// be called to release the voice playing the note (in case the voice has not -// been captured by someone else already). Note that Trigger will never block, -// but calling the release function might block until the sequencer has been -// able to assign a voice to the note. -func (s *Sequencer) Trigger(voiceStart, voiceEnd int, note byte) func() { - if note <= 1 { - return func() {} - } - id := s.getNewID() - e := noteOnEvent{ - voiceStart: voiceStart, - voiceEnd: voiceEnd, - note: note, - id: id, - } - s.noteOn <- e - return func() { - s.noteOff <- id // now, tell the sequencer to stop it - } -} - -func (s *Sequencer) getNewID() uint32 { - return atomic.AddUint32(&s.idCounter, 1) -} - -func (s *Sequencer) enable() { - atomic.StoreInt32(&s.validSynth, 1) -} - -func (s *Sequencer) trigger(voiceStart, voiceEnd int, note byte, newID uint32) { - if !s.Enabled() { - return - } - var oldestID uint32 = math.MaxUint32 - oldestReleased := false - oldestVoice := 0 - for i := voiceStart; i < voiceEnd; i++ { - // find a suitable voice to trigger. if the voice has been released, - // then we prefer to trigger that over a voice that is still playing. in - // case two voices are both playing or or both are released, we prefer - // the older one - id := s.voiceNoteID[i] - isReleased := s.voiceReleased[i] - if id < oldestID && (oldestReleased == isReleased) || (!oldestReleased && isReleased) { - oldestVoice = i - oldestID = id - oldestReleased = isReleased - } - } - s.voiceNoteID[oldestVoice] = newID - s.voiceReleased[oldestVoice] = false - s.synth.Trigger(oldestVoice, note) -} - -func (s *Sequencer) release(id uint32) { - if !s.Enabled() { - return - } - for i := 0; i < len(s.voiceNoteID); i++ { - if s.voiceNoteID[i] == id && !s.voiceReleased[i] { - s.voiceReleased[i] = true - s.synth.Release(i) - return - } - } -} - -func (s *Sequencer) releaseVoiceRange(voiceStart, voiceEnd int) { - if !s.Enabled() { - return - } - for i := voiceStart; i < voiceEnd; i++ { - if !s.voiceReleased[i] { - s.voiceReleased[i] = true - s.synth.Release(i) - } - } -} diff --git a/tracker/songpoint.go b/tracker/songpoint.go index e5f105c..d0e3232 100644 --- a/tracker/songpoint.go +++ b/tracker/songpoint.go @@ -17,36 +17,56 @@ type SongRect struct { Corner2 SongPoint } -func (r *SongRow) Wrap(song sointu.Song) { - totalRow := r.Pattern*song.RowsPerPattern + r.Row - r.Row = mod(totalRow, song.RowsPerPattern) - r.Pattern = mod((totalRow-r.Row)/song.RowsPerPattern, song.SequenceLength()) +func (r SongRow) AddRows(rows int) SongRow { + return SongRow{Row: r.Row + rows, Pattern: r.Pattern} } -func (r *SongRow) Clamp(song sointu.Song) { - totalRow := r.Pattern*song.RowsPerPattern + r.Row +func (r SongRow) AddPatterns(patterns int) SongRow { + return SongRow{Row: r.Row, Pattern: r.Pattern + patterns} +} + +func (r SongRow) Wrap(score sointu.Score) SongRow { + totalRow := r.Pattern*score.RowsPerPattern + r.Row + r.Row = mod(totalRow, score.RowsPerPattern) + r.Pattern = mod((totalRow-r.Row)/score.RowsPerPattern, score.Length) + return r +} + +func (r SongRow) Clamp(score sointu.Score) SongRow { + totalRow := r.Pattern*score.RowsPerPattern + r.Row if totalRow < 0 { totalRow = 0 } - if totalRow >= song.TotalRows() { - totalRow = song.TotalRows() - 1 + if totalRow >= score.LengthInRows() { + totalRow = score.LengthInRows() - 1 } - r.Row = totalRow % song.RowsPerPattern - r.Pattern = ((totalRow - r.Row) / song.RowsPerPattern) % song.SequenceLength() + r.Row = totalRow % score.RowsPerPattern + r.Pattern = ((totalRow - r.Row) / score.RowsPerPattern) % score.Length + return r } -func (p *SongPoint) Wrap(song sointu.Song) { - p.Track = mod(p.Track, len(song.Tracks)) - p.SongRow.Wrap(song) +func (r SongPoint) AddRows(rows int) SongPoint { + return SongPoint{Track: r.Track, SongRow: r.SongRow.AddRows(rows)} } -func (p *SongPoint) Clamp(song sointu.Song) { +func (r SongPoint) AddPatterns(patterns int) SongPoint { + return SongPoint{Track: r.Track, SongRow: r.SongRow.AddPatterns(patterns)} +} + +func (p SongPoint) Wrap(score sointu.Score) SongPoint { + p.Track = mod(p.Track, len(score.Tracks)) + p.SongRow = p.SongRow.Wrap(score) + return p +} + +func (p SongPoint) Clamp(score sointu.Score) SongPoint { if p.Track < 0 { p.Track = 0 - } else if l := len(song.Tracks); p.Track >= l { + } else if l := len(score.Tracks); p.Track >= l { p.Track = l - 1 } - p.SongRow.Clamp(song) + p.SongRow = p.SongRow.Clamp(score) + return p } func (r *SongRect) Contains(p SongPoint) bool { diff --git a/tracker/tracker.go b/tracker/tracker.go deleted file mode 100644 index b2d42cf..0000000 --- a/tracker/tracker.go +++ /dev/null @@ -1,823 +0,0 @@ -package tracker - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - "sync" - - "gioui.org/font/gofont" - "gioui.org/layout" - "gioui.org/text" - "gioui.org/widget" - "gioui.org/widget/material" - "github.com/vsariola/sointu" - "gopkg.in/yaml.v3" -) - -type EditMode int - -const ( - EditPatterns EditMode = iota - EditTracks - EditUnits - EditParameters -) - -type Tracker struct { - songPlayMutex sync.RWMutex // protects song and playing - song sointu.Song - Playing bool - // protects PlayPattern and PlayRow - playRowPatMutex sync.RWMutex // protects song and playing - PlayPosition SongRow - EditMode EditMode - SelectionCorner SongPoint - Cursor SongPoint - MenuBar []widget.Clickable - Menus []Menu - CursorColumn int - CurrentInstrument int - CurrentUnit int - CurrentParam int - NoteTracking bool - Theme *material.Theme - Octave *NumberInput - BPM *NumberInput - RowsPerPattern *NumberInput - RowsPerBeat *NumberInput - Step *NumberInput - InstrumentVoices *NumberInput - TrackVoices *NumberInput - InstrumentNameEditor *widget.Editor - NewTrackBtn *widget.Clickable - NewInstrumentBtn *widget.Clickable - DeleteInstrumentBtn *widget.Clickable - AddSemitoneBtn *widget.Clickable - SubtractSemitoneBtn *widget.Clickable - AddOctaveBtn *widget.Clickable - SubtractOctaveBtn *widget.Clickable - SongLength *NumberInput - PanicBtn *widget.Clickable - CopyInstrumentBtn *widget.Clickable - ParameterSliders []*widget.Float - ParameterList *layout.List - ParameterScrollBar *ScrollBar - UnitDragList *DragList - UnitScrollBar *ScrollBar - DeleteUnitBtn *widget.Clickable - ClearUnitBtn *widget.Clickable - ChooseUnitTypeList *layout.List - ChooseUnitScrollBar *ScrollBar - ChooseUnitTypeBtns []*widget.Clickable - AddUnitBtn *widget.Clickable - ParameterLabelBtns []*widget.Clickable - InstrumentDragList *DragList - InstrumentScrollBar *ScrollBar - TrackHexCheckBox *widget.Bool - TrackShowHex []bool - VuMeter VuMeter - TopHorizontalSplit *Split - BottomHorizontalSplit *Split - VerticalSplit *Split - StackUse []int - KeyPlaying map[string]func() - Alert Alert - - sequencer *Sequencer - refresh chan struct{} - audioContext sointu.AudioContext - undoStack []sointu.Song - redoStack []sointu.Song -} - -func (t *Tracker) LoadSong(song sointu.Song) error { - if err := song.Validate(); err != nil { - return fmt.Errorf("invalid song: %w", err) - } - t.songPlayMutex.Lock() - defer t.songPlayMutex.Unlock() - t.song = song - t.ClampPositions() - if t.sequencer != nil { - t.sequencer.SetPatch(song.Patch) - t.sequencer.SetRowLength(song.SamplesPerRow()) - } - return nil -} - -func (t *Tracker) UnmarshalContent(bytes []byte) error { - var instr sointu.Instrument - if errJSON := json.Unmarshal(bytes, &instr); errJSON == nil { - if t.SetInstrument(instr) { - return nil - } - } - if errYaml := yaml.Unmarshal(bytes, &instr); errYaml == nil { - if t.SetInstrument(instr) { - return nil - } - } - var song sointu.Song - if errJSON := json.Unmarshal(bytes, &song); errJSON != nil { - if errYaml := yaml.Unmarshal(bytes, &song); errYaml != nil { - return fmt.Errorf("the song could not be parsed as .json (%v) or .yml (%v)", errJSON, errYaml) - } - } - if song.BPM > 0 { - t.LoadSong(song) - return nil - } - return errors.New("was able to unmarshal a song, but the bpm was 0") -} - -func clamp(a, min, max int) int { - if a < min { - return min - } - if a >= max { - return max - 1 - } - return a -} - -func (t *Tracker) Close() { - t.audioContext.Close() -} - -func (t *Tracker) SetPlaying(value bool) { - t.songPlayMutex.Lock() - defer t.songPlayMutex.Unlock() - t.Playing = value -} - -func (t *Tracker) ChangeOctave(delta int) bool { - newOctave := t.Octave.Value + delta - if newOctave < 0 { - newOctave = 0 - } - if newOctave > 9 { - newOctave = 9 - } - if newOctave != t.Octave.Value { - t.Octave.Value = newOctave - return true - } - return false -} - -func (t *Tracker) SetInstrument(instrument sointu.Instrument) bool { - if len(instrument.Units) > 0 { - t.SaveUndo() - t.song.Patch.Instruments[t.CurrentInstrument] = instrument - t.sequencer.SetPatch(t.song.Patch) - return true - } - return false -} - -func (t *Tracker) SetInstrumentVoices(value int) bool { - if value < 1 { - value = 1 - } - maxRemain := 32 - t.song.Patch.TotalVoices() + t.song.Patch.Instruments[t.CurrentInstrument].NumVoices - if maxRemain < 1 { - maxRemain = 1 - } - if value > maxRemain { - value = maxRemain - } - if value != int(t.song.Patch.Instruments[t.CurrentInstrument].NumVoices) { - t.SaveUndo() - t.song.Patch.Instruments[t.CurrentInstrument].NumVoices = value - t.sequencer.SetPatch(t.song.Patch) - return true - } - return false -} - -func (t *Tracker) SetInstrumentName(name string) { - name = strings.TrimSpace(name) - if name != t.song.Patch.Instruments[t.CurrentInstrument].Name { - t.SaveUndo() - t.song.Patch.Instruments[t.CurrentInstrument].Name = name - } -} - -func (t *Tracker) SetBPM(value int) bool { - if value < 1 { - value = 1 - } - if value > 999 { - value = 999 - } - if value != int(t.song.BPM) { - t.SaveUndo() - t.song.BPM = value - t.sequencer.SetRowLength(t.song.SamplesPerRow()) - return true - } - return false -} - -func (t *Tracker) SetRowsPerBeat(value int) bool { - if value < 1 { - value = 1 - } - if value > 32 { - value = 32 - } - if value != int(t.song.RowsPerBeat) { - t.SaveUndo() - t.song.RowsPerBeat = value - t.sequencer.SetRowLength(t.song.SamplesPerRow()) - return true - } - return false -} - -func (t *Tracker) AddTrack() { - t.SaveUndo() - if t.song.TotalTrackVoices() < t.song.Patch.TotalVoices() { - seq := make([]byte, t.song.SequenceLength()) - patterns := [][]byte{make([]byte, t.song.RowsPerPattern)} - t.song.Tracks = append(t.song.Tracks, sointu.Track{ - NumVoices: 1, - Patterns: patterns, - Sequence: seq, - }) - } -} - -func (t *Tracker) SetTrackVoices(value int) bool { - if value < 1 { - value = 1 - } - maxRemain := t.song.Patch.TotalVoices() - t.song.TotalTrackVoices() + t.song.Tracks[t.Cursor.Track].NumVoices - if maxRemain < 1 { - maxRemain = 1 - } - if value > maxRemain { - value = maxRemain - } - if value != int(t.song.Tracks[t.Cursor.Track].NumVoices) { - t.SaveUndo() - t.song.Tracks[t.Cursor.Track].NumVoices = value - return true - } - return false -} - -func (t *Tracker) AddInstrument() { - if t.song.Patch.TotalVoices() >= 32 { - return - } - t.SaveUndo() - instr := make([]sointu.Instrument, len(t.song.Patch.Instruments)+1) - copy(instr, t.song.Patch.Instruments[:t.CurrentInstrument+1]) - instr[t.CurrentInstrument+1] = defaultInstrument.Copy() - copy(instr[t.CurrentInstrument+2:], t.song.Patch.Instruments[t.CurrentInstrument+1:]) - t.song.Patch.Instruments = instr - t.CurrentInstrument++ - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) SwapInstruments(i, j int) { - if i < 0 || j < 0 || i >= len(t.song.Patch.Instruments) || j >= len(t.song.Patch.Instruments) { - return - } - t.SaveUndo() - if j < i { - i, j = j, i - } - startI := t.song.FirstInstrumentVoice(i) - voicesI := t.song.Patch.Instruments[i].NumVoices - endI := startI + voicesI - startJ := t.song.FirstInstrumentVoice(j) - voicesJ := t.song.Patch.Instruments[j].NumVoices - endJ := startI + voicesJ - for _, instr := range t.song.Patch.Instruments { - for _, u := range instr.Units { - if u.Type == "send" { - v := u.Parameters["voice"] - if v == 0 { - continue - } - if v > startI && v <= endI { // voice belonged to the instrument I - u.Parameters["voice"] = v + endJ - endI - } else if v > startJ && v <= endJ { // voice belonged to the instrument J - u.Parameters["voice"] = v - startJ + startI - } else if v > endI && v <= startJ { // voice was between the two instruments - u.Parameters["voice"] = v - voicesI + voicesJ - } - } - } - } - instruments := t.song.Patch.Instruments - instruments[i], instruments[j] = instruments[j], instruments[i] - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) DeleteInstrument() { - if len(t.song.Patch.Instruments) <= 1 { - return - } - t.SaveUndo() - start := t.song.FirstInstrumentVoice(t.CurrentInstrument) - numVoices := t.song.Patch.Instruments[t.CurrentInstrument].NumVoices - end := start + numVoices - for _, i := range t.song.Patch.Instruments { - for _, u := range i.Units { - if u.Type == "send" { - if v := u.Parameters["voice"]; v > 0 { - if v > start && v <= end { - u.Parameters["voice"] = 0 - u.Parameters["unit"] = 63 - } else if v > end { - u.Parameters["voice"] -= numVoices - } - } - } - } - } - t.song.Patch.Instruments = append(t.song.Patch.Instruments[:t.CurrentInstrument], t.song.Patch.Instruments[t.CurrentInstrument+1:]...) - if t.CurrentInstrument >= len(t.song.Patch.Instruments) { - t.CurrentInstrument = len(t.song.Patch.Instruments) - 1 - } - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -// SetCurrentNote sets the (note) value in current pattern under cursor to iv -func (t *Tracker) SetCurrentNote(iv byte) { - t.SaveUndo() - t.song.Tracks[t.Cursor.Track].Patterns[t.song.Tracks[t.Cursor.Track].Sequence[t.Cursor.Pattern]][t.Cursor.Row] = iv - if !t.Playing || !t.NoteTracking { - t.Cursor.Row += t.Step.Value - t.ClampPositions() - t.Unselect() - } -} - -func (t *Tracker) SetCurrentPattern(pat byte) { - t.SaveUndo() - length := len(t.song.Tracks[t.Cursor.Track].Patterns) - if int(pat) >= length { - tail := make([][]byte, int(pat)-length+1) - for i := range tail { - tail[i] = make([]byte, t.song.RowsPerPattern) - } - t.song.Tracks[t.Cursor.Track].Patterns = append(t.song.Tracks[t.Cursor.Track].Patterns, tail...) - } - t.song.Tracks[t.Cursor.Track].Sequence[t.Cursor.Pattern] = pat - if t.Step.Value > 0 && (!t.Playing || !t.NoteTracking) { - t.Cursor.Pattern++ - t.ClampPositions() - t.Unselect() - } -} - -func (t *Tracker) SetSongLength(value int) { - if value < 1 { - value = 1 - } - if value != t.song.SequenceLength() { - t.SaveUndo() - for i := range t.song.Tracks { - seq := t.song.Tracks[i].Sequence - if len(t.song.Tracks[i].Sequence) > value { - t.song.Tracks[i].Sequence = t.song.Tracks[i].Sequence[:value] - } else if len(t.song.Tracks[i].Sequence) < value { - for k := len(t.song.Tracks[i].Sequence); k < value; k++ { - t.song.Tracks[i].Sequence = append(seq, seq[len(seq)-1]) - } - } - - } - t.ClampPositions() - } -} - -func (t *Tracker) SetRowsPerPattern(value int) { - if value < 1 { - value = 1 - } - if value > 255 { - value = 255 - } - if value != t.song.RowsPerPattern { - t.SaveUndo() - for i := range t.song.Tracks { - for j := range t.song.Tracks[i].Patterns { - pat := t.song.Tracks[i].Patterns[j] - if l := len(pat); l < value { - tail := make([]byte, value-l) - for k := range tail { - tail[k] = 1 - } - t.song.Tracks[i].Patterns[j] = append(pat, tail...) - } - } - } - t.song.RowsPerPattern = value - t.ClampPositions() - } -} - -func (t *Tracker) SetUnit(typ string) { - unit, ok := defaultUnits[typ] - if !ok { - return - } - if unit.Type == t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type { - return - } - t.SaveUndo() - t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] = unit.Copy() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) AddUnit(after bool) { - t.SaveUndo() - start := t.song.FirstInstrumentVoice(t.CurrentInstrument) - end := start + t.song.Patch.Instruments[t.CurrentInstrument].NumVoices - for ind, i := range t.song.Patch.Instruments { - for _, u := range i.Units { - if u.Type == "send" { - if v := u.Parameters["voice"]; (ind == t.CurrentInstrument && v == 0) || (v > 0 && v > start && v <= end) { - if u.Parameters["unit"] > t.CurrentUnit { - u.Parameters["unit"]++ - } - } - } - } - } - units := make([]sointu.Unit, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)+1) - newUnitIndex := t.CurrentUnit - if after { - newUnitIndex++ - } - copy(units, t.song.Patch.Instruments[t.CurrentInstrument].Units[:newUnitIndex]) - copy(units[newUnitIndex+1:], t.song.Patch.Instruments[t.CurrentInstrument].Units[newUnitIndex:]) - t.song.Patch.Instruments[t.CurrentInstrument].Units = units - t.CurrentUnit = newUnitIndex - t.CurrentParam = 0 - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) AddOrderRow(after bool) { - t.SaveUndo() - l := t.song.SequenceLength() - newRowIndex := t.Cursor.Pattern - if after { - newRowIndex++ - } - for i, trk := range t.song.Tracks { - seq := make([]byte, l+1) - copy(seq, trk.Sequence[:newRowIndex]) - copy(seq[newRowIndex+1:], trk.Sequence[newRowIndex:]) - t.song.Tracks[i].Sequence = seq - } - t.Cursor.Pattern = newRowIndex - t.SelectionCorner = t.Cursor - t.ClampPositions() -} - -func (t *Tracker) DeleteOrderRow(forward bool) { - l := t.song.SequenceLength() - if l <= 1 { - return - } - t.SaveUndo() - c := t.Cursor.Pattern - for i, trk := range t.song.Tracks { - seq := make([]byte, l-1) - copy(seq, trk.Sequence[:c]) - copy(seq[c:], trk.Sequence[c+1:]) - t.song.Tracks[i].Sequence = seq - } - if !forward { - if t.Cursor.Pattern > 0 { - t.Cursor.Pattern-- - } - } - t.SelectionCorner = t.Cursor - t.ClampPositions() -} - -func (t *Tracker) ClearUnit() { - t.SaveUndo() - t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type = "" - t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Parameters = make(map[string]int) - t.CurrentParam = 0 - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) DeleteUnit(forward bool) { - if len(t.song.Patch.Instruments[t.CurrentInstrument].Units) <= 1 { - return - } - t.SaveUndo() - start := t.song.FirstInstrumentVoice(t.CurrentInstrument) - end := start + t.song.Patch.Instruments[t.CurrentInstrument].NumVoices - for ind, i := range t.song.Patch.Instruments { - for _, u := range i.Units { - if u.Type == "send" { - if v := u.Parameters["voice"]; (ind == t.CurrentInstrument && v == 0) || (v > 0 && v > start && v <= end) { - if u.Parameters["unit"] > t.CurrentUnit { - u.Parameters["unit"]-- - } else if u.Parameters["unit"] == t.CurrentUnit { - u.Parameters["unit"] = 63 - } - } - } - } - } - units := make([]sointu.Unit, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)-1) - copy(units, t.song.Patch.Instruments[t.CurrentInstrument].Units[:t.CurrentUnit]) - copy(units[t.CurrentUnit:], t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit+1:]) - t.song.Patch.Instruments[t.CurrentInstrument].Units = units - if !forward && t.CurrentUnit > 0 { - t.CurrentUnit-- - } - t.CurrentParam = 0 - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) GetUnitParam() int { - unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] - paramtype := sointu.UnitTypes[unit.Type][t.CurrentParam] - return unit.Parameters[paramtype.Name] -} - -func (t *Tracker) SetUnitParam(value int) { - unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] - unittype := sointu.UnitTypes[unit.Type][t.CurrentParam] - if value < unittype.MinValue { - value = unittype.MinValue - } else if value > unittype.MaxValue { - value = unittype.MaxValue - } - if unit.Parameters[unittype.Name] == value { - return - } - t.SaveUndo() - unit.Parameters[unittype.Name] = value - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) SetGmDlsEntry(index int) { - if index < 0 || index >= len(gmDlsEntries) { - return - } - entry := gmDlsEntries[index] - unit := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] - if unit.Type != "oscillator" || unit.Parameters["type"] != sointu.Sample { - return - } - if unit.Parameters["samplestart"] == entry.Start && unit.Parameters["loopstart"] == entry.LoopStart && unit.Parameters["looplength"] == entry.LoopLength { - return - } - t.SaveUndo() - unit.Parameters["samplestart"] = entry.Start - unit.Parameters["loopstart"] = entry.LoopStart - unit.Parameters["looplength"] = entry.LoopLength - unit.Parameters["transpose"] = 64 + entry.SuggestedTranspose - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) SwapUnits(i, j int) { - if i < 0 || j < 0 || i >= len(t.song.Patch.Instruments[t.CurrentInstrument].Units) || j >= len(t.song.Patch.Instruments[t.CurrentInstrument].Units) { - return - } - t.SaveUndo() - start := t.song.FirstInstrumentVoice(t.CurrentInstrument) - end := start + t.song.Patch.Instruments[t.CurrentInstrument].NumVoices - for ind, instr := range t.song.Patch.Instruments { - for _, u := range instr.Units { - if u.Type == "send" { - if v := u.Parameters["voice"]; (ind == t.CurrentInstrument && v == 0) || (v > 0 && v > start && v <= end) { - if u.Parameters["unit"] == i { - u.Parameters["unit"] = j - } else if u.Parameters["unit"] == j { - u.Parameters["unit"] = i - } - } - } - } - } - units := t.song.Patch.Instruments[t.CurrentInstrument].Units - units[i], units[j] = units[j], units[i] - t.ClampPositions() - t.sequencer.SetPatch(t.song.Patch) -} - -func (t *Tracker) ClampPositions() { - t.PlayPosition.Clamp(t.song) - t.Cursor.Clamp(t.song) - t.SelectionCorner.Clamp(t.song) - if t.Cursor.Track >= len(t.TrackShowHex) || !t.TrackShowHex[t.Cursor.Track] { - t.CursorColumn = 0 - } - t.CurrentInstrument = clamp(t.CurrentInstrument, 0, len(t.song.Patch.Instruments)) - t.CurrentUnit = clamp(t.CurrentUnit, 0, len(t.song.Patch.Instruments[t.CurrentInstrument].Units)) - numSettableParams := 0 - for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] { - if t.CanSet { - numSettableParams++ - } - } - if numSettableParams == 0 { - numSettableParams = 1 - } - if t.CurrentParam < 0 && t.CurrentUnit > 0 { - t.CurrentUnit-- - numSettableParams = 0 - for _, t := range sointu.UnitTypes[t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type] { - if t.CanSet { - numSettableParams++ - } - } - t.CurrentParam = numSettableParams - 1 - } else if t.CurrentParam >= numSettableParams && t.CurrentUnit < len(t.song.Patch.Instruments[t.CurrentInstrument].Units)-1 { - t.CurrentUnit++ - t.CurrentParam = 0 - } else { - t.CurrentParam = clamp(t.CurrentParam, 0, numSettableParams) - } -} - -func (t *Tracker) getSelectionRange() (int, int, int, int) { - r1 := t.Cursor.Pattern*t.song.RowsPerPattern + t.Cursor.Row - r2 := t.SelectionCorner.Pattern*t.song.RowsPerPattern + t.SelectionCorner.Row - if r2 < r1 { - r1, r2 = r2, r1 - } - t1 := t.Cursor.Track - t2 := t.SelectionCorner.Track - if t2 < t1 { - t1, t2 = t2, t1 - } - return r1, r2, t1, t2 -} - -func (t *Tracker) AdjustSelectionPitch(delta int) { - t.SaveUndo() - r1, r2, t1, t2 := t.getSelectionRange() - for c := t1; c <= t2; c++ { - adjustedNotes := map[struct { - Pat byte - Row int - }]bool{} - for r := r1; r <= r2; r++ { - s := SongRow{Row: r} - s.Wrap(t.song) - p := t.song.Tracks[c].Sequence[s.Pattern] - noteIndex := struct { - Pat byte - Row int - }{p, s.Row} - if !adjustedNotes[noteIndex] { - if val := t.song.Tracks[c].Patterns[p][s.Row]; val > 1 { - newVal := int(val) + delta - if newVal < 2 { - newVal = 2 - } else if newVal > 255 { - newVal = 255 - } - t.song.Tracks[c].Patterns[p][s.Row] = byte(newVal) - } - adjustedNotes[noteIndex] = true - } - } - } -} - -func (t *Tracker) DeleteSelection() { - t.SaveUndo() - r1, r2, t1, t2 := t.getSelectionRange() - for r := r1; r <= r2; r++ { - s := SongRow{Row: r} - s.Wrap(t.song) - for c := t1; c <= t2; c++ { - p := t.song.Tracks[c].Sequence[s.Pattern] - t.song.Tracks[c].Patterns[p][s.Row] = 1 - } - } - if (!t.Playing || !t.NoteTracking) && t.Step.Value > 0 && r1 == r2 { - t.Cursor.Row += t.Step.Value - t.ClampPositions() - t.Unselect() - } -} - -func (t *Tracker) Unselect() { - t.SelectionCorner = t.Cursor -} - -func New(audioContext sointu.AudioContext, synthService sointu.SynthService) *Tracker { - t := &Tracker{ - Theme: material.NewTheme(gofont.Collection()), - audioContext: audioContext, - BPM: new(NumberInput), - Octave: new(NumberInput), - SongLength: new(NumberInput), - RowsPerPattern: new(NumberInput), - RowsPerBeat: new(NumberInput), - Step: new(NumberInput), - InstrumentVoices: new(NumberInput), - TrackVoices: new(NumberInput), - InstrumentNameEditor: &widget.Editor{SingleLine: true, Submit: true, Alignment: text.Middle}, - NewTrackBtn: new(widget.Clickable), - NewInstrumentBtn: new(widget.Clickable), - DeleteInstrumentBtn: new(widget.Clickable), - AddSemitoneBtn: new(widget.Clickable), - SubtractSemitoneBtn: new(widget.Clickable), - AddOctaveBtn: new(widget.Clickable), - SubtractOctaveBtn: new(widget.Clickable), - AddUnitBtn: new(widget.Clickable), - DeleteUnitBtn: new(widget.Clickable), - ClearUnitBtn: new(widget.Clickable), - PanicBtn: new(widget.Clickable), - CopyInstrumentBtn: new(widget.Clickable), - TrackHexCheckBox: new(widget.Bool), - Menus: make([]Menu, 2), - MenuBar: make([]widget.Clickable, 2), - UnitDragList: &DragList{List: &layout.List{Axis: layout.Vertical}}, - UnitScrollBar: &ScrollBar{Axis: layout.Vertical}, - refresh: make(chan struct{}, 1), // use non-blocking sends; no need to queue extra ticks if one is queued already - undoStack: []sointu.Song{}, - redoStack: []sointu.Song{}, - InstrumentDragList: &DragList{List: &layout.List{Axis: layout.Horizontal}}, - InstrumentScrollBar: &ScrollBar{Axis: layout.Horizontal}, - ParameterList: &layout.List{Axis: layout.Vertical}, - ParameterScrollBar: &ScrollBar{Axis: layout.Vertical}, - TopHorizontalSplit: new(Split), - BottomHorizontalSplit: new(Split), - VerticalSplit: new(Split), - ChooseUnitTypeList: &layout.List{Axis: layout.Vertical}, - ChooseUnitScrollBar: &ScrollBar{Axis: layout.Vertical}, - KeyPlaying: make(map[string]func()), - } - t.UnitDragList.HoverItem = -1 - t.InstrumentDragList.HoverItem = -1 - t.Octave.Value = 4 - t.VerticalSplit.Axis = layout.Vertical - t.BottomHorizontalSplit.Ratio = -.6 - t.TopHorizontalSplit.Ratio = -.6 - t.Theme.Palette.Fg = primaryColor - t.Theme.Palette.ContrastFg = black - t.EditMode = EditTracks - t.Step.Value = 1 - t.VuMeter.FallOff = 2e-8 - t.VuMeter.RangeDb = 80 - t.VuMeter.Decay = 1e-3 - for range allUnits { - t.ChooseUnitTypeBtns = append(t.ChooseUnitTypeBtns, new(widget.Clickable)) - } - callBack := func(buf []float32) { - t.VuMeter.Update(buf) - select { - case t.refresh <- struct{}{}: - default: - // message dropped, there's already a tick queued, so no need to queue extra - } - } - t.sequencer = NewSequencer(2048, synthService, audioContext, callBack, func(row []RowNote) []RowNote { - t.playRowPatMutex.Lock() - if !t.Playing { - t.playRowPatMutex.Unlock() - return nil - } - t.PlayPosition.Row++ - t.PlayPosition.Wrap(t.song) - if t.NoteTracking { - t.Cursor.SongRow = t.PlayPosition - t.SelectionCorner.SongRow = t.PlayPosition - } - for _, track := range t.song.Tracks { - patternIndex := track.Sequence[t.PlayPosition.Pattern] - note := track.Patterns[patternIndex][t.PlayPosition.Row] - row = append(row, RowNote{Note: note, NumVoices: track.NumVoices}) - } - t.playRowPatMutex.Unlock() - select { - case t.refresh <- struct{}{}: - default: - // message dropped, there's already a tick queued, so no need to queue extra - } - - return row - }) - if err := t.LoadSong(defaultSong.Copy()); err != nil { - panic(fmt.Errorf("cannot load default song: %w", err)) - } - return t -} diff --git a/tracker/undo.go b/tracker/undo.go deleted file mode 100644 index cf1fb86..0000000 --- a/tracker/undo.go +++ /dev/null @@ -1,37 +0,0 @@ -package tracker - -var undoSkip = map[string]int{ - "setNote": 10, -} - -const maxUndo = 256 - -func (t *Tracker) SaveUndo() { - if len(t.undoStack) >= maxUndo { - t.undoStack = t.undoStack[1:] - } - t.undoStack = append(t.undoStack, t.song.Copy()) - t.redoStack = t.redoStack[:0] -} - -func (t *Tracker) Undo() { - if len(t.undoStack) > 0 { - if len(t.redoStack) >= maxUndo { - t.redoStack = t.redoStack[1:] - } - t.redoStack = append(t.redoStack, t.song.Copy()) - t.LoadSong(t.undoStack[len(t.undoStack)-1]) - t.undoStack = t.undoStack[:len(t.undoStack)-1] - } -} - -func (t *Tracker) Redo() { - if len(t.redoStack) > 0 { - if len(t.undoStack) >= maxUndo { - t.undoStack = t.undoStack[1:] - } - t.undoStack = append(t.undoStack, t.song.Copy()) - t.LoadSong(t.redoStack[len(t.redoStack)-1]) - t.redoStack = t.redoStack[:len(t.redoStack)-1] - } -} diff --git a/tracker/uniteditor.go b/tracker/uniteditor.go deleted file mode 100644 index c315991..0000000 --- a/tracker/uniteditor.go +++ /dev/null @@ -1,238 +0,0 @@ -package tracker - -import ( - "fmt" - "image" - "image/color" - "strings" - - "gioui.org/layout" - "gioui.org/op" - "gioui.org/op/clip" - "gioui.org/op/paint" - "gioui.org/unit" - "gioui.org/widget" - "gioui.org/widget/material" - "github.com/vsariola/sointu" - "github.com/vsariola/sointu/compiler" - "golang.org/x/exp/shiny/materialdesign/icons" -) - -func (t *Tracker) layoutUnitEditor(gtx C) D { - editorFunc := t.layoutUnitSliders - if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type == "" { - editorFunc = t.layoutUnitTypeChooser - } - return Surface{Gray: 24, Focus: t.EditMode == EditUnits || t.EditMode == EditParameters}.Layout(gtx, func(gtx C) D { - return layout.Flex{Axis: layout.Vertical}.Layout(gtx, - layout.Flexed(1, editorFunc), - layout.Rigid(t.layoutUnitFooter())) - }) - -} - -func (t *Tracker) layoutUnitSliders(gtx C) D { - u := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit] - ut, ok := sointu.UnitTypes[u.Type] - if !ok { - return layout.Dimensions{} - } - listElements := func(gtx C, index int) D { - for len(t.ParameterSliders) <= index { - t.ParameterSliders = append(t.ParameterSliders, new(widget.Float)) - } - for len(t.ParameterLabelBtns) <= index { - t.ParameterLabelBtns = append(t.ParameterLabelBtns, new(widget.Clickable)) - } - for t.ParameterLabelBtns[index].Clicked() { - if t.EditMode != EditParameters || t.CurrentParam != index { - t.EditMode = EditParameters - t.CurrentParam = index - op.InvalidateOp{}.Add(gtx.Ops) - } else { - if index < len(ut) { - t.SetUnitParam(defaultUnits[u.Type].Parameters[ut[index].Name]) - op.InvalidateOp{}.Add(gtx.Ops) - } - } - } - params := u.Parameters - var name string - var value, min, max int - var valueText string - if u.Type == "oscillator" && index == len(ut) { - name = "sample" - key := compiler.SampleOffset{Start: uint32(params["samplestart"]), LoopStart: uint16(params["loopstart"]), LoopLength: uint16(params["looplength"])} - if v, ok := gmDlsEntryMap[key]; ok { - value = v + 1 - valueText = fmt.Sprintf("%v / %v", value, gmDlsEntries[v].Name) - } else { - value = 0 - valueText = "0 / custom" - } - min, max = 0, len(gmDlsEntries) - } else { - if ut[index].MaxValue < ut[index].MinValue { - return layout.Dimensions{} - } - name = ut[index].Name - if u.Type == "oscillator" && (name == "samplestart" || name == "loopstart" || name == "looplength") { - if params["type"] != sointu.Sample { - return layout.Dimensions{} - } - } - value = params[name] - min, max = ut[index].MinValue, ut[index].MaxValue - if u.Type == "send" && name == "voice" { - max = t.song.Patch.TotalVoices() - } else if u.Type == "send" && name == "unit" { // set the maximum values depending on the send target - instrIndex, _, _, _ := t.song.Patch.FindSendTarget(t.CurrentInstrument, t.CurrentUnit) - if instrIndex != -1 { - max = len(t.song.Patch.Instruments[instrIndex].Units) - 1 - } - } else if u.Type == "send" && name == "port" { // set the maximum values depending on the send target - instrIndex, unitIndex, _, _ := t.song.Patch.FindSendTarget(t.CurrentInstrument, t.CurrentUnit) - if instrIndex != -1 && unitIndex != -1 { - max = len(sointu.Ports[t.song.Patch.Instruments[instrIndex].Units[unitIndex].Type]) - 1 - } - } - hint := t.song.ParamHintString(t.CurrentInstrument, t.CurrentUnit, name) - if hint != "" { - valueText = fmt.Sprintf("%v / %v", value, hint) - } else { - valueText = fmt.Sprintf("%v", value) - } - } - if !t.ParameterSliders[index].Dragging() { - t.ParameterSliders[index].Value = float32(value) - } - if max < min { - max = min - } - sliderStyle := material.Slider(t.Theme, t.ParameterSliders[index], float32(min), float32(max)) - sliderStyle.Color = t.Theme.Fg - return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(func(gtx C) D { - return layout.Stack{}.Layout(gtx, - layout.Stacked(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Px(unit.Dp(110)) - return layout.E.Layout(gtx, Label(name, white)) - }), - layout.Expanded(t.ParameterLabelBtns[index].Layout), - ) - }), - layout.Rigid(func(gtx C) D { - gtx.Constraints.Min.X = gtx.Px(unit.Dp(200)) - gtx.Constraints.Min.Y = gtx.Px(unit.Dp(40)) - if t.EditMode == EditParameters && t.CurrentParam == index { - paint.FillShape(gtx.Ops, cursorColor, clip.Rect{ - Max: gtx.Constraints.Min, - }.Op()) - } - dims := sliderStyle.Layout(gtx) - for sliderStyle.Float.Changed() { - t.EditMode = EditParameters - t.CurrentParam = index - if u.Type == "oscillator" && name == "sample" { - v := int(t.ParameterSliders[index].Value+0.5) - 1 - if v >= 0 { - t.SetGmDlsEntry(v) - } - } else { - t.SetUnitParam(int(t.ParameterSliders[index].Value + 0.5)) - } - } - return dims - }), - layout.Rigid(Label(valueText, white)), - ) - } - l := len(ut) - if u.Type == "oscillator" && u.Parameters["type"] == sointu.Sample { - l++ - } - return layout.Stack{}.Layout(gtx, - layout.Stacked(func(gtx C) D { - return t.ParameterList.Layout(gtx, l, listElements) - }), - layout.Stacked(func(gtx C) D { - gtx.Constraints.Min = gtx.Constraints.Max - return t.ParameterScrollBar.Layout(gtx, unit.Dp(10), l, &t.ParameterList.Position) - })) -} - -func (t *Tracker) layoutUnitFooter() layout.Widget { - return func(gtx C) D { - for t.ClearUnitBtn.Clicked() { - t.ClearUnit() - op.InvalidateOp{}.Add(gtx.Ops) - } - for t.DeleteUnitBtn.Clicked() { - t.DeleteUnit(false) - op.InvalidateOp{}.Add(gtx.Ops) - } - deleteUnitBtnStyle := material.IconButton(t.Theme, t.DeleteUnitBtn, widgetForIcon(icons.ActionDelete)) - deleteUnitBtnStyle.Background = transparent - deleteUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) - if len(t.song.Patch.Instruments[t.CurrentInstrument].Units) > 1 { - deleteUnitBtnStyle.Color = primaryColor - } else { - deleteUnitBtnStyle.Color = disabledTextColor - } - text := t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type - if text == "" { - text = "Choose unit type" - } else { - text = strings.Title(text) - } - hintText := Label(text, white) - return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, - layout.Rigid(deleteUnitBtnStyle.Layout), - layout.Rigid(func(gtx C) D { - var dims D - if t.song.Patch.Instruments[t.CurrentInstrument].Units[t.CurrentUnit].Type != "" { - clearUnitBtnStyle := material.IconButton(t.Theme, t.ClearUnitBtn, widgetForIcon(icons.ContentClear)) - clearUnitBtnStyle.Color = primaryColor - clearUnitBtnStyle.Background = transparent - clearUnitBtnStyle.Inset = layout.UniformInset(unit.Dp(6)) - dims = clearUnitBtnStyle.Layout(gtx) - } - return D{Size: image.Pt(gtx.Px(unit.Dp(48)), dims.Size.Y)} - }), - layout.Flexed(1, hintText), - ) - } -} - -func (t *Tracker) layoutUnitTypeChooser(gtx C) D { - listElem := func(gtx C, i int) D { - for t.ChooseUnitTypeBtns[i].Clicked() { - t.SetUnit(allUnits[i]) - } - labelStyle := LabelStyle{Text: allUnits[i], ShadeColor: black, Color: white, Font: labelDefaultFont, FontSize: unit.Sp(12)} - bg := func(gtx C) D { - gtx.Constraints = layout.Exact(image.Pt(gtx.Constraints.Max.X, 20)) - var color color.NRGBA - if t.ChooseUnitTypeBtns[i].Hovered() { - color = unitTypeListHighlightColor - } - paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op()) - return D{Size: gtx.Constraints.Min} - } - leftMargin := layout.Inset{Left: unit.Dp(10)} - return layout.Stack{Alignment: layout.W}.Layout(gtx, - layout.Stacked(bg), - layout.Expanded(func(gtx C) D { - return leftMargin.Layout(gtx, labelStyle.Layout) - }), - layout.Expanded(t.ChooseUnitTypeBtns[i].Layout)) - } - return layout.Stack{}.Layout(gtx, - layout.Stacked(func(gtx C) D { - return t.ChooseUnitTypeList.Layout(gtx, len(allUnits), listElem) - }), - layout.Expanded(func(gtx C) D { - return t.ChooseUnitScrollBar.Layout(gtx, unit.Dp(10), len(allUnits), &t.ChooseUnitTypeList.Position) - }), - ) -} diff --git a/tracker/vuanalyzer.go b/tracker/vuanalyzer.go new file mode 100644 index 0000000..198634c --- /dev/null +++ b/tracker/vuanalyzer.go @@ -0,0 +1,60 @@ +package tracker + +import ( + "math" +) + +// Volume represents an average and peak volume measurement, in decibels. 0 dB = +// signal level of +-1. +type Volume struct { + Average [2]float32 + Peak [2]float32 +} + +// VuAnalyzer receives stereo from the bc channel and converts these into peak & +// average volume measurements, and pushes Volume values into the vc channel. +// The pushes are nonblocking so if e.g. a GUI does not have enough time to +// process redraw the volume meter, the values is just skipped. Thus, the vc +// chan should have a capacity of at least 1 (!). +// +// Internally, it first converts the signal to decibels (0 dB = +-1). Then, the +// average volume level is computed by smoothing the decibel values with a +// exponentially decaying average, with a time constant tau (in seconds). +// Typical value could be 0.3 (seconds). +// +// Peak volume detection is similar exponential smoothing, but the time +// constants for attack and release are different. Generally attack << release. +// Typical values could be attack 1.5e-3 and release 1.5 (seconds) +// +// minVolume is just a hard limit for the vuanalyzer volumes, in decibels, just to +// prevent negative infinities for volumes +func VuAnalyzer(tau float64, attack float64, release float64, minVolume float32, bc <-chan []float32, vc chan<- Volume) { + v := Volume{Average: [2]float32{minVolume, minVolume}, Peak: [2]float32{minVolume, minVolume}} + alpha := 1 - float32(math.Exp(-1.0/(tau*44100))) // from https://en.wikipedia.org/wiki/Exponential_smoothing + alphaAttack := 1 - float32(math.Exp(-1.0/(attack*44100))) + alphaRelease := 1 - float32(math.Exp(-1.0/(release*44100))) + for buffer := range bc { + for j := 0; j < 2; j++ { + for i := 0; i < len(buffer); i += 2 { + sample2 := float64(buffer[i+j] * buffer[i+j]) + if math.IsNaN(sample2) { + sample2 = float64(minVolume) + } + dB := float32(10 * math.Log10(float64(sample2))) + if dB < minVolume { + dB = minVolume + } + v.Average[j] += (dB - v.Average[j]) * alpha + alphaPeak := alphaAttack + if dB < v.Peak[j] { + alphaPeak = alphaRelease + } + v.Peak[j] += (dB - v.Peak[j]) * alphaPeak + } + } + select { + case vc <- v: + default: + } + } +} diff --git a/tracker/vumeter.go b/tracker/vumeter.go deleted file mode 100644 index 99eb8b1..0000000 --- a/tracker/vumeter.go +++ /dev/null @@ -1,78 +0,0 @@ -package tracker - -import ( - "image" - "math" - - "gioui.org/f32" - "gioui.org/op" - "gioui.org/op/clip" - "gioui.org/op/paint" - "gioui.org/unit" -) - -type VuMeter struct { - avg [2]float32 - max [2]float32 - speed [2]float32 - FallOff float32 - Decay float32 - RangeDb float32 -} - -func (v *VuMeter) Update(buffer []float32) { - for j := 0; j < 2; j++ { - for i := 0; i < len(buffer); i += 2 { - sample2 := buffer[i+j] * buffer[i+j] - db := float32(10*math.Log10(float64(sample2))) + v.RangeDb - v.speed[j] += v.FallOff - v.max[j] -= v.speed[j] - if v.max[j] < 0 { - v.max[j] = 0 - } - if v.max[j] < db { - v.max[j] = db - v.speed[j] = 0 - } - v.avg[j] += (sample2 - v.avg[j]) * v.Decay - if math.IsNaN(float64(v.avg[j])) { - v.avg[j] = 0 - } - } - } -} - -func (v *VuMeter) Reset() { - v.avg = [2]float32{} - v.max = [2]float32{} -} - -func (v *VuMeter) Layout(gtx C) D { - defer op.Save(gtx.Ops).Load() - gtx.Constraints.Max.Y = gtx.Px(unit.Dp(12)) - height := gtx.Px(unit.Dp(6)) - for j := 0; j < 2; j++ { - value := float32(10*math.Log10(float64(v.avg[j]))) + v.RangeDb - if value > 0 { - x := int(value/v.RangeDb*float32(gtx.Constraints.Max.X) + 0.5) - if x > gtx.Constraints.Max.X { - x = gtx.Constraints.Max.X - } - paint.FillShape(gtx.Ops, mediumEmphasisTextColor, clip.Rect(image.Rect(0, 0, x, height)).Op()) - } - valueMax := v.max[j] - if valueMax > 0 { - color := white - if valueMax >= v.RangeDb { - color = errorColor - } - x := int(valueMax/v.RangeDb*float32(gtx.Constraints.Max.X) + 0.5) - if x > gtx.Constraints.Max.X { - x = gtx.Constraints.Max.X - } - paint.FillShape(gtx.Ops, color, clip.Rect(image.Rect(x-1, 0, x, height)).Op()) - } - op.Offset(f32.Pt(0, float32(height))).Add(gtx.Ops) - } - return D{Size: gtx.Constraints.Max} -} diff --git a/unit.go b/unit.go new file mode 100644 index 0000000..fc3a09f --- /dev/null +++ b/unit.go @@ -0,0 +1,55 @@ +package sointu + +// Unit is e.g. a filter, oscillator, envelope and its parameters +type Unit struct { + Type string `yaml:",omitempty"` + ID int `yaml:",omitempty"` + Parameters map[string]int `yaml:",flow"` + VarArgs []int `yaml:",flow,omitempty"` +} + +const ( + Sine = iota + Trisaw = iota + Pulse = iota + Gate = iota + Sample = iota +) + +func (u *Unit) Copy() Unit { + parameters := make(map[string]int) + for k, v := range u.Parameters { + parameters[k] = v + } + varArgs := make([]int, len(u.VarArgs)) + copy(varArgs, u.VarArgs) + return Unit{Type: u.Type, Parameters: parameters, VarArgs: varArgs, ID: u.ID} +} + +func (u *Unit) StackChange() int { + switch u.Type { + case "addp", "mulp", "pop", "out", "outaux", "aux": + return -1 - u.Parameters["stereo"] + case "envelope", "oscillator", "push", "noise", "receive", "loadnote", "loadval", "in", "compressor": + return 1 + u.Parameters["stereo"] + case "pan": + return 1 - u.Parameters["stereo"] + case "speed": + return -1 + case "send": + return (-1 - u.Parameters["stereo"]) * u.Parameters["sendpop"] + } + return 0 +} + +func (u *Unit) StackNeed() int { + switch u.Type { + case "", "envelope", "oscillator", "noise", "receive", "loadnote", "loadval", "in": + return 0 + case "mulp", "mul", "add", "addp", "xch": + return 2 * (1 + u.Parameters["stereo"]) + case "speed": + return 1 + } + return 1 + u.Parameters["stereo"] +} diff --git a/unittype.go b/unittype.go new file mode 100644 index 0000000..818e322 --- /dev/null +++ b/unittype.go @@ -0,0 +1,148 @@ +package sointu + +import ( + "fmt" + "math" +) + +// UnitParameter documents one parameter that an unit takes +type UnitParameter struct { + Name string // thould be found with this name in the Unit.Parameters map + MinValue int // minimum value of the parameter, inclusive + MaxValue int // maximum value of the parameter, inclusive + CanSet bool // if this parameter can be set before hand i.e. through the gui + CanModulate bool // if this parameter can be modulated i.e. has a port number in "send" unit +} + +func engineeringTime(sec float64) string { + if sec < 1e-3 { + return fmt.Sprintf("%.2f us", sec*1e6) + } else if sec < 1 { + return fmt.Sprintf("%.2f ms", sec*1e3) + } + return fmt.Sprintf("%.2f s", sec) +} + +// UnitTypes documents all the available unit types and if they support stereo variant +// and what parameters they take. +var UnitTypes = map[string]([]UnitParameter){ + "add": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "addp": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "pop": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "loadnote": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "mul": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "mulp": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "push": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "xch": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "distort": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "drive", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "hold": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "holdfreq", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "crush": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "resolution", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "gain": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "invgain": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "filter": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "frequency", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "resonance", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "lowpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "bandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "highpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "negbandpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "neghighpass", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "clip": []UnitParameter{{Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "pan": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "panning", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "delay": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "pregain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "dry", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "feedback", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "damp", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "notetracking", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "delaytime", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}}, + "compressor": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "invgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "threshold", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "ratio", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "speed": []UnitParameter{}, + "out": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "outaux": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "outgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "auxgain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "aux": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}}, + "send": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "amount", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "voice", MinValue: 0, MaxValue: 32, CanSet: true, CanModulate: false}, + {Name: "target", MinValue: 0, MaxValue: math.MaxInt32, CanSet: true, CanModulate: false}, + {Name: "port", MinValue: 0, MaxValue: 7, CanSet: true, CanModulate: false}, + {Name: "sendpop", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}}, + "envelope": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "attack", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "decay", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "sustain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "release", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "noise": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "oscillator": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "transpose", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "detune", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "phase", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "color", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "shape", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "gain", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}, + {Name: "type", MinValue: int(Sine), MaxValue: int(Sample), CanSet: true, CanModulate: false}, + {Name: "lfo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "unison", MinValue: 0, MaxValue: 3, CanSet: true, CanModulate: false}, + {Name: "samplestart", MinValue: 0, MaxValue: 1720329, CanSet: true, CanModulate: false}, + {Name: "loopstart", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}, + {Name: "looplength", MinValue: 0, MaxValue: 65535, CanSet: true, CanModulate: false}}, + "loadval": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "value", MinValue: 0, MaxValue: 128, CanSet: true, CanModulate: true}}, + "receive": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "left", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}, + {Name: "right", MinValue: 0, MaxValue: -1, CanSet: false, CanModulate: true}}, + "in": []UnitParameter{ + {Name: "stereo", MinValue: 0, MaxValue: 1, CanSet: true, CanModulate: false}, + {Name: "channel", MinValue: 0, MaxValue: 6, CanSet: true, CanModulate: false}}, +} + +var Ports = make(map[string]([]string)) + +func init() { + for name, unitType := range UnitTypes { + unitPorts := make([]string, 0) + for _, param := range unitType { + if param.CanModulate { + unitPorts = append(unitPorts, param.Name) + } + } + Ports[name] = unitPorts + } +}