feat: save recovery data to disk and/or DAW project

This commit is contained in:
5684185+vsariola@users.noreply.github.com 2023-10-15 15:28:35 +03:00
parent 97a1b2f766
commit 462faf5f4e
7 changed files with 126 additions and 95 deletions

View File

@ -6,8 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased ## Unreleased
### Added ### Added
- Save the GUI state periodically to a recovery file and load it on - Save the GUI state periodically to a recovery file and load it on
startup of the app, if present. The recovery file is located in the startup of the app, if present. The recovery files are located in the
home directory of the user. app config directory (e.g. AppData/Roaming/Sointu on Windows).
- Save the VSTI GUI state to the DAW project file, through GetChunk /
SetChunk mechanisms.
- Instrument presets. The presets are embedded in the executable and - Instrument presets. The presets are embedded in the executable and
there's a button to open a menu to load one of the presets. there's a button to open a menu to load one of the presets.
- Frequency modulation target for oscillator, as it was in 4klang - Frequency modulation target for oscillator, as it was in 4klang

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath"
"runtime" "runtime"
"runtime/pprof" "runtime/pprof"
@ -50,10 +51,11 @@ func main() {
defer audioContext.Close() defer audioContext.Close()
modelMessages := make(chan interface{}, 1024) modelMessages := make(chan interface{}, 1024)
playerMessages := make(chan tracker.PlayerMessage, 1024) playerMessages := make(chan tracker.PlayerMessage, 1024)
model, err := tracker.LoadRecovery(modelMessages, playerMessages) recoveryFile := ""
if err != nil { if configDir, err := os.UserConfigDir(); err == nil {
model = tracker.NewModel(modelMessages, playerMessages) recoveryFile = filepath.Join(configDir, "Sointu", "sointu-track-recovery")
} }
model := tracker.NewModel(modelMessages, playerMessages, recoveryFile)
player := tracker.NewPlayer(cmd.DefaultService, playerMessages, modelMessages) player := tracker.NewPlayer(cmd.DefaultService, playerMessages, modelMessages)
tracker := gioui.NewTracker(model, cmd.DefaultService) tracker := gioui.NewTracker(model, cmd.DefaultService)
output := audioContext.Output() output := audioContext.Output()

View File

@ -3,6 +3,11 @@
package main package main
import ( import (
"crypto/rand"
"encoding/hex"
"os"
"path/filepath"
"github.com/vsariola/sointu/cmd" "github.com/vsariola/sointu/cmd"
"github.com/vsariola/sointu/tracker" "github.com/vsariola/sointu/tracker"
"github.com/vsariola/sointu/tracker/gioui" "github.com/vsariola/sointu/tracker/gioui"
@ -49,10 +54,13 @@ func init() {
vst2.PluginAllocator = func(h vst2.Host) (vst2.Plugin, vst2.Dispatcher) { vst2.PluginAllocator = func(h vst2.Host) (vst2.Plugin, vst2.Dispatcher) {
modelMessages := make(chan interface{}, 1024) modelMessages := make(chan interface{}, 1024)
playerMessages := make(chan tracker.PlayerMessage, 1024) playerMessages := make(chan tracker.PlayerMessage, 1024)
model, err := tracker.LoadRecovery(modelMessages, playerMessages) recoveryFile := ""
if err != nil { if configDir, err := os.UserConfigDir(); err == nil {
model = tracker.NewModel(modelMessages, playerMessages) randBytes := make([]byte, 16)
rand.Read(randBytes)
recoveryFile = filepath.Join(configDir, "Sointu", "sointu-vsti-recovery-"+hex.EncodeToString(randBytes))
} }
model := tracker.NewModel(modelMessages, playerMessages, recoveryFile)
player := tracker.NewPlayer(cmd.DefaultService, playerMessages, modelMessages) player := tracker.NewPlayer(cmd.DefaultService, playerMessages, modelMessages)
tracker := gioui.NewTracker(model, cmd.DefaultService) tracker := gioui.NewTracker(model, cmd.DefaultService)
tracker.SetInstrEnlarged(true) // start the vsti with the instrument editor enlarged tracker.SetInstrEnlarged(true) // start the vsti with the instrument editor enlarged
@ -102,6 +110,12 @@ func init() {
tracker.Quit(true) tracker.Quit(true)
tracker.WaitQuitted() tracker.WaitQuitted()
}, },
GetChunkFunc: func(isPreset bool) []byte {
return tracker.SafeMarshalRecovery()
},
SetChunkFunc: func(data []byte, isPreset bool) {
tracker.SafeUnmarshalRecovery(data)
},
} }
} }

4
go.mod
View File

@ -7,10 +7,11 @@ require (
gioui.org/x v0.1.0 gioui.org/x v0.1.0
github.com/Masterminds/sprig v2.22.0+incompatible github.com/Masterminds/sprig v2.22.0+incompatible
github.com/hajimehoshi/oto v0.6.6 github.com/hajimehoshi/oto v0.6.6
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91
gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
pipelined.dev/audio/vst2 v0.10.1-0.20230718065422-be1242fca4ab pipelined.dev/audio/vst2 v0.10.1-0.20231016195025-8c5c6a64c826
) )
require ( require (
@ -28,7 +29,6 @@ require (
github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/stretchr/testify v1.6.1 // indirect github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 // indirect
golang.org/x/image v0.7.0 // indirect golang.org/x/image v0.7.0 // indirect
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f // indirect
golang.org/x/sys v0.7.0 // indirect golang.org/x/sys v0.7.0 // indirect

29
go.sum
View File

@ -1,9 +1,4 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.1.0/go.mod h1:a3hz8FyrPMkt899D9YrxMGtyRzpPrJpz1Lzbssn81vI=
gioui.org v0.2.1-0.20230823193131-cf5ae4aad92e h1:EdWCy7gaaBZoVFF0OCdo2Tj/LCJ4AI6HOGG8p5Zcljs=
gioui.org v0.2.1-0.20230823193131-cf5ae4aad92e/go.mod h1:1H72sKEk/fNFV+l0JNeM2Dt3co3Y4uaQcD+I+/GQ0e4=
gioui.org v0.3.0 h1:xZty/uLl1+/HNKpumX60JPQd46n8Zy6lc5T3IRMKoR4= gioui.org v0.3.0 h1:xZty/uLl1+/HNKpumX60JPQd46n8Zy6lc5T3IRMKoR4=
gioui.org v0.3.0/go.mod h1:1H72sKEk/fNFV+l0JNeM2Dt3co3Y4uaQcD+I+/GQ0e4= gioui.org v0.3.0/go.mod h1:1H72sKEk/fNFV+l0JNeM2Dt3co3Y4uaQcD+I+/GQ0e4=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
@ -13,7 +8,6 @@ gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
gioui.org/x v0.1.0 h1:CvphvaQSroRaNEZ+JbXBkV3J3klA76U3JpieyEwHFX4= gioui.org/x v0.1.0 h1:CvphvaQSroRaNEZ+JbXBkV3J3klA76U3JpieyEwHFX4=
gioui.org/x v0.1.0/go.mod h1:5qZxjtK/TVznMlcEOyn8OheiCZlArxF3IKnLqSehKXQ= gioui.org/x v0.1.0/go.mod h1:5qZxjtK/TVznMlcEOyn8OheiCZlArxF3IKnLqSehKXQ=
git.sr.ht/~jackmordaunt/go-toast v1.0.0/go.mod h1:aIuRX/HdBOz7yRS8rOVYQCwJQlFS7DbYBTpUV0SHeeg=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI= git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0 h1:bGG/g4ypjrCJoSvFrP5hafr9PPB5aw8SjcOWWila7ZI=
git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo= git.wow.st/gmp/jni v0.0.0-20210610011705-34026c7e22d0/go.mod h1:+axXBRUTIDlCeE73IKeD/os7LoEnTKdkp8/gQOFjqyo=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -23,22 +17,13 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/esiqveland/notify v0.11.0/go.mod h1:63UbVSaeJwF0LVJARHFuPgUAoM7o1BEvCZyknsuonBc=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-text/typesetting v0.0.0-20230602202114-9797aefac433/go.mod h1:KmrpWuSMFcO2yjmyhGpnBGQHSKAoEgMTSSzvLDzCuEA=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo=
github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k=
github.com/go-text/typesetting-utils v0.0.0-20230412163830-89e4bcfa3ecc/go.mod h1:RaqFwjcYyM5BjbYGwON0H5K0UqwO3sJlo9ukKha80ZE=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI= github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22 h1:LBQTFxP2MfsyEDqSKmUBZaDuDHN1vpqDyOZjcqS7MYI=
github.com/go-text/typesetting-utils v0.0.0-20230616150549-2a7df14b6a22/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hajimehoshi/oto v0.6.6 h1:HYSZ8cYZqOL4iHugvbcfhNN2smiSOsBMaoSBi4nnWcw= github.com/hajimehoshi/oto v0.6.6 h1:HYSZ8cYZqOL4iHugvbcfhNN2smiSOsBMaoSBi4nnWcw=
@ -47,7 +32,6 @@ github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jezek/xgb v1.0.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
@ -71,8 +55,6 @@ golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFd
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
@ -90,7 +72,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -98,25 +79,19 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
@ -135,8 +110,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
pipelined.dev/audio/vst2 v0.10.1-0.20230718065422-be1242fca4ab h1:rhVxuLIl32iwa3IaGwLKmEsPCV0XK3czP3pD4TDy/Ik= pipelined.dev/audio/vst2 v0.10.1-0.20231016195025-8c5c6a64c826 h1:4c7O6PJ/Zl677O2VhXHUZK7LJyVBhUI7Q39+ri+gKUs=
pipelined.dev/audio/vst2 v0.10.1-0.20230718065422-be1242fca4ab/go.mod h1:wETLxsbBPftj6t4iVBCXvH/Xgd27ZgIC4hNnHDYNuz8= pipelined.dev/audio/vst2 v0.10.1-0.20231016195025-8c5c6a64c826/go.mod h1:wETLxsbBPftj6t4iVBCXvH/Xgd27ZgIC4hNnHDYNuz8=
pipelined.dev/pipe v0.10.0/go.mod h1:aIt+NPlW0QLYByqYniG77lTxSvl7OtCNLws/m+Xz5ww= pipelined.dev/pipe v0.10.0/go.mod h1:aIt+NPlW0QLYByqYniG77lTxSvl7OtCNLws/m+Xz5ww=
pipelined.dev/pipe v0.11.0 h1:yRrbntKdqw/nbFqkz9dPaSHBoM7pK1LRHHDqdBJuqtc= pipelined.dev/pipe v0.11.0 h1:yRrbntKdqw/nbFqkz9dPaSHBoM7pK1LRHHDqdBJuqtc=
pipelined.dev/pipe v0.11.0/go.mod h1:aIt+NPlW0QLYByqYniG77lTxSvl7OtCNLws/m+Xz5ww= pipelined.dev/pipe v0.11.0/go.mod h1:aIt+NPlW0QLYByqYniG77lTxSvl7OtCNLws/m+Xz5ww=

View File

@ -57,16 +57,20 @@ type Tracker struct {
lastVolume tracker.Volume lastVolume tracker.Volume
wavFilePath string wavFilePath string
quitChannel chan struct{} quitChannel chan struct{}
quitWG sync.WaitGroup quitWG sync.WaitGroup
errorChannel chan error errorChannel chan error
quitted bool quitted bool
synthService sointu.SynthService unmarshalRecoveryChannel chan []byte
marshalRecoveryChannel chan (chan []byte)
synthService sointu.SynthService
*tracker.Model *trackerModel
} }
type trackerModel = tracker.Model
func (t *Tracker) UnmarshalContent(bytes []byte) error { func (t *Tracker) UnmarshalContent(bytes []byte) error {
var units []sointu.Unit var units []sointu.Unit
if errJSON := json.Unmarshal(bytes, &units); errJSON == nil { if errJSON := json.Unmarshal(bytes, &units); errJSON == nil {
@ -143,7 +147,10 @@ func NewTracker(model *tracker.Model, synthService sointu.SynthService) *Tracker
errorChannel: make(chan error, 32), errorChannel: make(chan error, 32),
synthService: synthService, synthService: synthService,
Model: model, trackerModel: model,
marshalRecoveryChannel: make(chan (chan []byte)),
unmarshalRecoveryChannel: make(chan []byte),
} }
t.Theme.Palette.Fg = primaryColor t.Theme.Palette.Fg = primaryColor
t.Theme.Palette.ContrastFg = black t.Theme.Palette.ContrastFg = black
@ -213,6 +220,10 @@ mainloop:
} }
case <-recoveryTicker.C: case <-recoveryTicker.C:
t.SaveRecovery() t.SaveRecovery()
case retChn := <-t.marshalRecoveryChannel:
retChn <- t.MarshalRecovery()
case bytes := <-t.unmarshalRecoveryChannel:
t.UnmarshalRecovery(bytes)
} }
} }
w.Perform(system.ActionClose) w.Perform(system.ActionClose)
@ -220,6 +231,18 @@ mainloop:
t.quitWG.Done() t.quitWG.Done()
} }
// thread safe, executed in the GUI thread
func (t *Tracker) SafeMarshalRecovery() []byte {
retChn := make(chan []byte)
t.marshalRecoveryChannel <- retChn
return <-retChn
}
// thread safe, executed in the GUI thread
func (t *Tracker) SafeUnmarshalRecovery(data []byte) {
t.unmarshalRecoveryChannel <- data
}
func (t *Tracker) sendQuit() { func (t *Tracker) sendQuit() {
select { select {
case t.quitChannel <- struct{}{}: case t.quitChannel <- struct{}{}:

View File

@ -13,7 +13,6 @@ import (
"github.com/vsariola/sointu" "github.com/vsariola/sointu"
"github.com/vsariola/sointu/vm" "github.com/vsariola/sointu/vm"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"gopkg.in/yaml.v3"
) )
// Model implements the mutable state for the tracker program GUI. // Model implements the mutable state for the tracker program GUI.
@ -26,25 +25,27 @@ import (
type ( type (
// modelData is the part of the model that gets save to recovery file // modelData is the part of the model that gets save to recovery file
modelData struct { modelData struct {
Song sointu.Song Song sointu.Song
SelectionCorner SongPoint SelectionCorner SongPoint
Cursor SongPoint Cursor SongPoint
LowNibble bool LowNibble bool
InstrIndex int InstrIndex int
UnitIndex int UnitIndex int
ParamIndex int ParamIndex int
Octave int Octave int
NoteTracking bool NoteTracking bool
UsedIDs map[int]bool UsedIDs map[int]bool
MaxID int MaxID int
FilePath string FilePath string
ChangedSinceSave bool ChangedSinceSave bool
PatternUseCount [][]int PatternUseCount [][]int
Panic bool Panic bool
Playing bool Playing bool
Recording bool Recording bool
PlayPosition SongRow PlayPosition SongRow
InstrEnlarged bool InstrEnlarged bool
RecoveryFilePath string
ChangedSinceRecovery bool
PrevUndoType string PrevUndoType string
UndoSkipCounter int UndoSkipCounter int
@ -116,63 +117,76 @@ const (
const maxUndo = 64 const maxUndo = 64
const RECOVERY_FILE = ".sointu_recovery" const RECOVERY_FILE = ".sointu_recovery"
func NewModel(modelMessages chan<- interface{}, playerMessages <-chan PlayerMessage) *Model { func NewModel(modelMessages chan<- interface{}, playerMessages <-chan PlayerMessage, recoveryFilePath string) *Model {
ret := new(Model) ret := new(Model)
ret.modelMessages = modelMessages ret.modelMessages = modelMessages
ret.PlayerMessages = playerMessages ret.PlayerMessages = playerMessages
ret.setSongNoUndo(defaultSong.Copy()) ret.setSongNoUndo(defaultSong.Copy())
ret.d.Octave = 4 ret.d.Octave = 4
ret.d.RecoveryFilePath = recoveryFilePath
if recoveryFilePath != "" {
if bytes2, err := os.ReadFile(ret.d.RecoveryFilePath); err == nil {
json.Unmarshal(bytes2, &ret.d)
}
}
return ret return ret
} }
func LoadRecovery(modelMessages chan<- interface{}, playerMessages <-chan PlayerMessage) (*Model, error) { func (m *Model) MarshalRecovery() []byte {
homeDir, err := os.UserHomeDir() out, err := json.Marshal(m.d)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get user home directory: %w", err) return nil
} }
filePath := filepath.Join(homeDir, RECOVERY_FILE) if m.d.RecoveryFilePath != "" {
b, err := os.ReadFile(filePath) os.Remove(m.d.RecoveryFilePath)
if err != nil {
return nil, fmt.Errorf("could not read recovery file: %w", err)
} }
var ret Model m.d.ChangedSinceRecovery = false
err = json.Unmarshal(b, &ret.d) return out
if err != nil {
err = yaml.Unmarshal(b, &ret.d)
if err != nil {
return nil, fmt.Errorf("could not unmarshal recovery file: %w", err)
}
}
ret.modelMessages = modelMessages
ret.PlayerMessages = playerMessages
ret.notifyPatchChange()
ret.notifySamplesPerRowChange()
ret.notifyScoreChange()
return &ret, nil
} }
func (m *Model) SaveRecovery() error { func (m *Model) SaveRecovery() error {
homeDir, err := os.UserHomeDir() if !m.d.ChangedSinceRecovery {
if err != nil { return nil
return fmt.Errorf("could not get user home directory: %w", err) }
if m.d.RecoveryFilePath == "" {
return errors.New("no backup file path")
} }
out, err := json.Marshal(m.d) out, err := json.Marshal(m.d)
if err != nil { if err != nil {
return fmt.Errorf("could not marshal the model: %w", err) return fmt.Errorf("could not marshal recovery data: %w", err)
} }
filePath := filepath.Join(homeDir, RECOVERY_FILE) dir := filepath.Dir(m.d.RecoveryFilePath)
file, err := os.Create(filePath) if _, err := os.Stat(dir); os.IsNotExist(err) {
os.MkdirAll(dir, os.ModePerm)
}
file, err := os.Create(m.d.RecoveryFilePath)
if err != nil { if err != nil {
return fmt.Errorf("could not open recovery file: %w", err) return fmt.Errorf("could not create recovery file: %w", err)
} }
_, err = file.Write(out) _, err = file.Write(out)
if err != nil { if err != nil {
return fmt.Errorf("could not write recovery file: %w", err) return fmt.Errorf("could not write recovery file: %w", err)
} }
m.d.ChangedSinceRecovery = false
return nil return nil
} }
func (m *Model) UnmarshalRecovery(bytes []byte) {
err := json.Unmarshal(bytes, &m.d)
if err != nil {
return
}
if m.d.RecoveryFilePath != "" { // check if there's a recovery file on disk and load it instead
if bytes2, err := os.ReadFile(m.d.RecoveryFilePath); err == nil {
json.Unmarshal(bytes2, &m.d)
}
}
m.d.ChangedSinceRecovery = false
m.notifyPatchChange()
m.notifySamplesPerRowChange()
m.notifyScoreChange()
}
func (m *Model) FilePath() string { func (m *Model) FilePath() string {
return m.d.FilePath return m.d.FilePath
} }
@ -1416,6 +1430,7 @@ func (m *Model) notifySamplesPerRowChange() {
func (m *Model) saveUndo(undoType string, undoSkipping int) { func (m *Model) saveUndo(undoType string, undoSkipping int) {
m.d.ChangedSinceSave = true m.d.ChangedSinceSave = true
m.d.ChangedSinceRecovery = true
if m.d.PrevUndoType == undoType && m.d.UndoSkipCounter < undoSkipping { if m.d.PrevUndoType == undoType && m.d.UndoSkipCounter < undoSkipping {
m.d.UndoSkipCounter++ m.d.UndoSkipCounter++
return return