mirror of
https://github.com/vsariola/sointu.git
synced 2025-06-04 01:28:45 -04:00
The -er suffix is more idiomatic for single method interfaces, and the interface is not doing much more than converting the patch to a synth. Names were updated throughout the project to reflect this change. In particular, the "Service" in SynthService was not telling anything helpful.
225 lines
5.1 KiB
Go
225 lines
5.1 KiB
Go
//go:build !js
|
|
// +build !js
|
|
|
|
package gioui
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/vsariola/sointu"
|
|
)
|
|
|
|
func (t *Tracker) OpenSongFile(forced bool) {
|
|
if !forced && t.ChangedSinceSave() {
|
|
t.ConfirmSongActionType = ConfirmLoad
|
|
t.ConfirmSongDialog.Visible = true
|
|
return
|
|
}
|
|
reader, err := t.Explorer.ChooseFile(".yml", ".json")
|
|
if err != nil {
|
|
return
|
|
}
|
|
t.loadSong(reader)
|
|
}
|
|
|
|
func (t *Tracker) SaveSongFile() bool {
|
|
if p := t.FilePath(); p != "" {
|
|
if f, err := os.Create(p); err == nil {
|
|
return t.saveSong(f)
|
|
}
|
|
}
|
|
t.SaveSongAsFile()
|
|
return false
|
|
}
|
|
|
|
func (t *Tracker) SaveSongAsFile() {
|
|
p := t.FilePath()
|
|
if p == "" {
|
|
p = "song.yml"
|
|
}
|
|
writer, err := t.Explorer.CreateFile(p)
|
|
if err != nil {
|
|
return
|
|
}
|
|
t.saveSong(writer)
|
|
}
|
|
|
|
func (t *Tracker) ExportWav(pcm16 bool) {
|
|
filename := "song.wav"
|
|
if p := t.FilePath(); p != "" {
|
|
filename = p[:len(p)-len(filepath.Ext(p))] + ".wav"
|
|
}
|
|
writer, err := t.Explorer.CreateFile(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
t.exportWav(writer, pcm16)
|
|
}
|
|
|
|
func (t *Tracker) LoadInstrument() {
|
|
reader, err := t.Explorer.ChooseFile(".yml", ".json", ".4ki", ".4kp")
|
|
if err != nil {
|
|
return
|
|
}
|
|
t.loadInstrument(reader)
|
|
}
|
|
|
|
func (t *Tracker) SaveInstrument() {
|
|
writer, err := t.Explorer.CreateFile(t.Instrument().Name + ".yml")
|
|
if err != nil {
|
|
return
|
|
}
|
|
t.saveInstrument(writer)
|
|
}
|
|
|
|
func (t *Tracker) loadSong(r io.ReadCloser) {
|
|
b, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = r.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
var song sointu.Song
|
|
if errJSON := json.Unmarshal(b, &song); errJSON != nil {
|
|
if errYaml := yaml.Unmarshal(b, &song); errYaml != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error unmarshaling a song file: %v / %v", errYaml, errJSON), Error, time.Second*3)
|
|
}
|
|
}
|
|
if song.Score.Length <= 0 || len(song.Score.Tracks) == 0 || len(song.Patch) == 0 {
|
|
t.Alert.Update("The song file is malformed", Error, time.Second*3)
|
|
return
|
|
}
|
|
t.SetSong(song)
|
|
path := ""
|
|
if f, ok := r.(*os.File); ok {
|
|
path = f.Name()
|
|
}
|
|
t.SetFilePath(path)
|
|
t.ClearUndoHistory()
|
|
t.SetChangedSinceSave(false)
|
|
}
|
|
|
|
func (t *Tracker) saveSong(w io.WriteCloser) bool {
|
|
path := ""
|
|
if f, ok := w.(*os.File); ok {
|
|
path = f.Name()
|
|
}
|
|
var extension = filepath.Ext(path)
|
|
var contents []byte
|
|
var err error
|
|
if extension == ".json" {
|
|
contents, err = json.Marshal(t.Song())
|
|
} else {
|
|
contents, err = yaml.Marshal(t.Song())
|
|
}
|
|
if err != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error marshaling a song file: %v", err), Error, time.Second*3)
|
|
return false
|
|
}
|
|
if _, err := w.Write(contents); err != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error writing to file: %v", err), Error, time.Second*3)
|
|
return false
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error closing file: %v", err), Error, time.Second*3)
|
|
return false
|
|
}
|
|
t.SetFilePath(path)
|
|
t.SetChangedSinceSave(false)
|
|
return true
|
|
}
|
|
|
|
func (t *Tracker) exportWav(w io.WriteCloser, pcm16 bool) {
|
|
data, err := sointu.Play(t.synther, t.Song(), true) // render the song to calculate its length
|
|
if err != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error rendering the song during export: %v", err), Error, time.Second*3)
|
|
return
|
|
}
|
|
buffer, err := data.Wav(pcm16)
|
|
if err != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error converting to .wav: %v", err), Error, time.Second*3)
|
|
return
|
|
}
|
|
w.Write(buffer)
|
|
w.Close()
|
|
}
|
|
|
|
func (t *Tracker) saveInstrument(w io.WriteCloser) bool {
|
|
path := ""
|
|
if f, ok := w.(*os.File); ok {
|
|
path = f.Name()
|
|
}
|
|
var extension = filepath.Ext(path)
|
|
var contents []byte
|
|
var err error
|
|
if extension == ".json" {
|
|
contents, err = json.Marshal(t.Instrument())
|
|
} else {
|
|
contents, err = yaml.Marshal(t.Instrument())
|
|
}
|
|
if err != nil {
|
|
t.Alert.Update(fmt.Sprintf("Error marshaling a ínstrument file: %v", err), Error, time.Second*3)
|
|
return false
|
|
}
|
|
w.Write(contents)
|
|
w.Close()
|
|
return true
|
|
}
|
|
|
|
func (t *Tracker) loadInstrument(r io.ReadCloser) bool {
|
|
b, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
var instrument sointu.Instrument
|
|
var errJSON, errYaml, err4ki, err4kp error
|
|
var patch sointu.Patch
|
|
errJSON = json.Unmarshal(b, &instrument)
|
|
if errJSON == nil {
|
|
goto success
|
|
}
|
|
errYaml = yaml.Unmarshal(b, &instrument)
|
|
if errYaml == nil {
|
|
goto success
|
|
}
|
|
patch, err4kp = sointu.Read4klangPatch(bytes.NewReader(b))
|
|
if err4kp == nil {
|
|
song := t.Song()
|
|
song.Score = t.Song().Score.Copy()
|
|
song.Patch = patch
|
|
t.SetSong(song)
|
|
return true
|
|
}
|
|
instrument, err4ki = sointu.Read4klangInstrument(bytes.NewReader(b))
|
|
if err4ki == nil {
|
|
goto success
|
|
}
|
|
t.Alert.Update(fmt.Sprintf("Error unmarshaling an instrument file: %v / %v / %v / %v", errYaml, errJSON, err4ki, err4kp), Error, time.Second*3)
|
|
return false
|
|
success:
|
|
if f, ok := r.(*os.File); ok {
|
|
filename := f.Name()
|
|
// the 4klang instrument names are junk, replace them with the filename without extension
|
|
instrument.Name = filepath.Base(filename[:len(filename)-len(filepath.Ext(filename))])
|
|
}
|
|
if len(instrument.Units) == 0 {
|
|
t.Alert.Update("The instrument file is malformed", Error, time.Second*3)
|
|
return false
|
|
}
|
|
t.SetInstrument(instrument)
|
|
if t.Instrument().Comment != "" {
|
|
t.InstrumentEditor.ExpandComment()
|
|
}
|
|
return true
|
|
}
|