mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
176 lines
5.1 KiB
Go
176 lines
5.1 KiB
Go
package gioui
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"gioui.org/app"
|
|
"gioui.org/font/gofont"
|
|
"gioui.org/layout"
|
|
"gioui.org/widget"
|
|
"gioui.org/widget/material"
|
|
"github.com/vsariola/sointu"
|
|
"github.com/vsariola/sointu/tracker"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
const (
|
|
ConfirmQuit = iota
|
|
ConfirmLoad
|
|
ConfirmNew
|
|
)
|
|
|
|
type Tracker struct {
|
|
Theme *material.Theme
|
|
MenuBar []widget.Clickable
|
|
Menus []Menu
|
|
OctaveNumberInput *NumberInput
|
|
BPM *NumberInput
|
|
RowsPerPattern *NumberInput
|
|
RowsPerBeat *NumberInput
|
|
Step *NumberInput
|
|
InstrumentVoices *NumberInput
|
|
SongLength *NumberInput
|
|
PanicBtn *widget.Clickable
|
|
AddUnitBtn *widget.Clickable
|
|
TrackHexCheckBox *widget.Bool
|
|
TopHorizontalSplit *Split
|
|
BottomHorizontalSplit *Split
|
|
VerticalSplit *Split
|
|
KeyPlaying map[string]uint32
|
|
Alert Alert
|
|
ConfirmSongDialog *Dialog
|
|
WaveTypeDialog *Dialog
|
|
OpenSongDialog *FileDialog
|
|
SaveSongDialog *FileDialog
|
|
OpenInstrumentDialog *FileDialog
|
|
SaveInstrumentDialog *FileDialog
|
|
ExportWavDialog *FileDialog
|
|
ConfirmSongActionType int
|
|
window *app.Window
|
|
ModalDialog layout.Widget
|
|
InstrumentEditor *InstrumentEditor
|
|
OrderEditor *OrderEditor
|
|
TrackEditor *TrackEditor
|
|
|
|
lastVolume tracker.Volume
|
|
volumeChan chan tracker.Volume
|
|
|
|
wavFilePath string
|
|
player *tracker.Player
|
|
refresh chan struct{}
|
|
playerCloser chan struct{}
|
|
errorChannel chan error
|
|
quitted bool
|
|
audioContext sointu.AudioContext
|
|
synthService sointu.SynthService
|
|
|
|
*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, syncChannel chan<- []float32, window *app.Window) *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),
|
|
|
|
PanicBtn: new(widget.Clickable),
|
|
TrackHexCheckBox: new(widget.Bool),
|
|
Menus: make([]Menu, 2),
|
|
MenuBar: make([]widget.Clickable, 2),
|
|
refresh: make(chan struct{}, 1), // use non-blocking sends; no need to queue extra ticks if one is queued already
|
|
|
|
TopHorizontalSplit: &Split{Ratio: -.6},
|
|
BottomHorizontalSplit: &Split{Ratio: -.6},
|
|
VerticalSplit: &Split{Axis: layout.Vertical},
|
|
|
|
KeyPlaying: make(map[string]uint32),
|
|
volumeChan: make(chan tracker.Volume, 1),
|
|
playerCloser: make(chan struct{}),
|
|
ConfirmSongDialog: new(Dialog),
|
|
WaveTypeDialog: new(Dialog),
|
|
OpenSongDialog: NewFileDialog(),
|
|
SaveSongDialog: NewFileDialog(),
|
|
OpenInstrumentDialog: NewFileDialog(),
|
|
SaveInstrumentDialog: NewFileDialog(),
|
|
InstrumentEditor: NewInstrumentEditor(),
|
|
OrderEditor: NewOrderEditor(),
|
|
TrackEditor: NewTrackEditor(),
|
|
|
|
ExportWavDialog: NewFileDialog(),
|
|
errorChannel: make(chan error, 32),
|
|
window: window,
|
|
synthService: synthService,
|
|
}
|
|
t.Model = tracker.NewModel()
|
|
vuBufferObserver := make(chan []float32)
|
|
go tracker.VuAnalyzer(0.3, 1e-4, 1, -100, 20, vuBufferObserver, t.volumeChan, t.errorChannel)
|
|
t.Theme.Palette.Fg = primaryColor
|
|
t.Theme.Palette.ContrastFg = black
|
|
t.TrackEditor.Focus()
|
|
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, syncChannel, audioChannel, vuBufferObserver)
|
|
audioOut := audioContext.Output()
|
|
go func() {
|
|
for buf := range audioChannel {
|
|
audioOut.WriteAudio(buf)
|
|
}
|
|
}()
|
|
t.ResetSong()
|
|
return t
|
|
}
|
|
|
|
func (t *Tracker) Quit(forced bool) bool {
|
|
if !forced && t.ChangedSinceSave() {
|
|
t.ConfirmSongActionType = ConfirmQuit
|
|
t.ConfirmSongDialog.Visible = true
|
|
return false
|
|
}
|
|
t.quitted = true
|
|
return true
|
|
}
|