feat: add ability to import 4klang patches and instruments

This commit is contained in:
5684185+vsariola@users.noreply.github.com
2023-07-06 23:47:55 +03:00
parent c06ac6ea5e
commit 248ba483c6
87 changed files with 643 additions and 55 deletions

View File

@ -106,9 +106,9 @@ func (f *FileDialogStyle) Layout(gtx C) D {
n = n[0 : len(n)-len(extension)]
switch f.dialog.UseAltExt.Value {
case true:
n += ".json"
n += f.ExtAlt
default:
n += ".yml"
n += f.ExtMain
}
f.dialog.FileName.SetText(n)
}

View File

@ -4,6 +4,7 @@
package gioui
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
@ -66,15 +67,23 @@ func (t *Tracker) SaveInstrument() {
}
func (t *Tracker) loadSong(filename string) {
bytes, err := ioutil.ReadFile(filename)
b, err := ioutil.ReadFile(filename)
if err != nil {
return
}
var song sointu.Song
if errJSON := json.Unmarshal(bytes, &song); errJSON != nil {
if errYaml := yaml.Unmarshal(bytes, &song); errYaml != nil {
t.Alert.Update(fmt.Sprintf("Error unmarshaling a song file: %v / %v", errYaml, errJSON), Error, time.Second*3)
return
if errJSON := json.Unmarshal(b, &song); errJSON != nil {
if errYaml := yaml.Unmarshal(b, &song); errYaml != nil {
var err4kp error
var patch sointu.Patch
if patch, err4kp = sointu.Read4klangPatch(bytes.NewReader(b)); err4kp != nil {
t.Alert.Update(fmt.Sprintf("Error unmarshaling a song file: %v / %v / %v", errYaml, errJSON, err4kp), Error, time.Second*3)
return
} else {
song = t.Song()
song.Score = t.Song().Score.Copy()
song.Patch = patch
}
}
}
if song.Score.Length <= 0 || len(song.Score.Tracks) == 0 || len(song.Patch) == 0 {
@ -148,17 +157,22 @@ func (t *Tracker) saveInstrument(filename string) bool {
}
func (t *Tracker) loadInstrument(filename string) bool {
bytes, err := ioutil.ReadFile(filename)
b, err := ioutil.ReadFile(filename)
if err != nil {
return false
}
var instrument sointu.Instrument
if errJSON := json.Unmarshal(bytes, &instrument); errJSON != nil {
if errYaml := yaml.Unmarshal(bytes, &instrument); errYaml != nil {
t.Alert.Update(fmt.Sprintf("Error unmarshaling an instrument file: %v / %v", errYaml, errJSON), Error, time.Second*3)
return false
if errJSON := json.Unmarshal(b, &instrument); errJSON != nil {
if errYaml := yaml.Unmarshal(b, &instrument); errYaml != nil {
var err4ki error
if instrument, err4ki = sointu.Read4klangInstrument(bytes.NewReader(b)); err4ki != nil {
t.Alert.Update(fmt.Sprintf("Error unmarshaling an instrument file: %v / %v / %v", errYaml, errJSON, err4ki), Error, time.Second*3)
return false
}
}
}
// 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

View File

@ -58,6 +58,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
}
fstyle := OpenFileDialog(t.Theme, t.OpenSongDialog)
fstyle.Title = "Open Song File"
fstyle.ExtAlt = ".4kp"
fstyle.Layout(gtx)
for ok, file := t.OpenSongDialog.FileSelected(); ok; ok, file = t.OpenSongDialog.FileSelected() {
t.loadSong(file)
@ -89,6 +90,7 @@ func (t *Tracker) Layout(gtx layout.Context) {
fstyle.Layout(gtx)
fstyle = OpenFileDialog(t.Theme, t.OpenInstrumentDialog)
fstyle.Title = "Open Instrument File"
fstyle.ExtAlt = ".4ki"
for ok, file := t.OpenInstrumentDialog.FileSelected(); ok; ok, file = t.OpenInstrumentDialog.FileSelected() {
t.loadInstrument(file)
}

View File

@ -66,7 +66,7 @@ type (
}
ModelSamplesPerRowChangedMessage struct {
int
BPM, RowsPerBeat int
}
ModelPanicMessage struct {
@ -1088,14 +1088,43 @@ func (m *Model) Param(index int) (Parameter, error) {
if index < len(unit.VarArgs) {
val := unit.VarArgs[index]
var text string
if unit.Parameters["notetracking"] == 1 {
switch unit.Parameters["notetracking"] {
default:
case 0:
text = fmt.Sprintf("%v / %.3f rows", val, float32(val)/float32(m.song.SamplesPerRow()))
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
case 1:
relPitch := float64(val) / 10787
semitones := -math.Log2(relPitch) * 12
text = fmt.Sprintf("%v / %.3f st", val, semitones)
} else {
text = fmt.Sprintf("%v / %.3f rows", val, float32(val)/float32(m.song.SamplesPerRow()))
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
case 2:
k := 0
v := val
for v&1 == 0 { // divide val by 2 until it is odd
v >>= 1
k++
}
text := ""
switch v {
case 1:
if k <= 7 {
text = fmt.Sprintf(" (1/%d triplet)", 1<<(7-k))
}
case 3:
if k <= 6 {
text = fmt.Sprintf(" (1/%d)", 1<<(6-k))
}
break
case 9:
if k <= 5 {
text = fmt.Sprintf(" (1/%d dotted)", 1<<(5-k))
}
}
text = fmt.Sprintf("%v / %.3f beats%s", val, float32(val)/48.0, text)
return Parameter{Type: IntegerParameter, Min: 1, Max: 576, Name: "delaytime", Hint: text, Value: val, LargeStep: 16}, nil
}
return Parameter{Type: IntegerParameter, Min: 1, Max: 65535, Name: "delaytime", Hint: text, Value: val, LargeStep: 256}, nil
}
}
return Parameter{}, errors.New("invalid parameter")
@ -1240,7 +1269,7 @@ func (m *Model) notifyScoreChange() {
func (m *Model) notifySamplesPerRowChange() {
select {
case m.modelMessages <- ModelSamplesPerRowChangedMessage{m.song.SamplesPerRow()}:
case m.modelMessages <- ModelSamplesPerRowChangedMessage{m.song.BPM, m.song.RowsPerBeat}:
default:
}
}

View File

@ -20,6 +20,7 @@ type (
position SongRow
samplesSinceEvent []int
samplesPerRow int
bpm int
volume Volume
voiceStates [vm.MAX_VOICES]float32
@ -242,7 +243,9 @@ loop:
}
}
case ModelSamplesPerRowChangedMessage:
p.samplesPerRow = m.int
p.samplesPerRow = 44100 * 60 / (m.BPM * m.RowsPerBeat)
p.bpm = m.BPM
p.compileOrUpdateSynth()
case ModelPlayFromPositionMessage:
p.playing = true
p.position = m.SongRow
@ -297,7 +300,7 @@ loop:
func (p *Player) compileOrUpdateSynth() {
if p.synth != nil {
err := p.synth.Update(p.patch)
err := p.synth.Update(p.patch, p.bpm)
if err != nil {
p.synth = nil
p.trySend(PlayerCrashMessage{fmt.Errorf("synth.Update: %w", err)})
@ -305,7 +308,7 @@ func (p *Player) compileOrUpdateSynth() {
}
} else {
var err error
p.synth, err = p.synthService.Compile(p.patch)
p.synth, err = p.synthService.Compile(p.patch, p.bpm)
if err != nil {
p.synth = nil
p.trySend(PlayerCrashMessage{fmt.Errorf("synthService.Compile: %w", err)})