sointu/tracker/gioui/files.go
2023-10-18 14:40:16 +03:00

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.synthService, 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
}