Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor UI #76

Merged
merged 14 commits into from
Aug 14, 2024
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ on:
branches: [ "main" ]

jobs:

build:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: install portaudio
run: sudo apt update && sudo apt -y install portaudio19-dev

- name: Set up Go
uses: actions/setup-go@v4
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
bin
examples/test.yaml
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ VERSION := $(shell git describe --tags --exact-match 2> /dev/null || git symboli
PHONY: build
build:
go build -o bin/synth -ldflags="-X github.com/iljarotar/synth/cmd.version=$(VERSION)"

PHONY: test
test:
go test ./...
12 changes: 7 additions & 5 deletions audio/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import (
"github.com/gordonklaus/portaudio"
)

type ProcessCallback func([]float32)
type AudioOutput struct {
Left, Right float64
}

type Context struct {
*portaudio.Stream
Input chan struct{ Left, Right float32 }
Input chan AudioOutput
}

func NewContext(input chan struct{ Left, Right float32 }, sampleRate float64) (*Context, error) {
func NewContext(input chan AudioOutput, sampleRate float64) (*Context, error) {
ctx := &Context{Input: input}

var err error
Expand All @@ -26,8 +28,8 @@ func NewContext(input chan struct{ Left, Right float32 }, sampleRate float64) (*
func (c *Context) Process(out [][]float32) {
for i := range out[0] {
y := <-c.Input
out[0][i] = y.Left
out[1][i] = y.Right
out[0][i] = float32(y.Left)
out[1][i] = float32(y.Right)
}
}

Expand Down
60 changes: 35 additions & 25 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"time"

"github.com/iljarotar/synth/audio"
"github.com/iljarotar/synth/config"

c "github.com/iljarotar/synth/config"
"github.com/iljarotar/synth/control"
f "github.com/iljarotar/synth/file"
Expand Down Expand Up @@ -53,19 +53,19 @@ documentation and usage: https://github.com/iljarotar/synth`,
if cfg == "" {
cfg = defaultConfigPath
}
err = c.LoadConfig(cfg)
config, err := c.LoadConfig(cfg)
if err != nil {
fmt.Printf("could not load config file: %v\n", err)
return
}

err = parseFlags(cmd)
err = parseFlags(cmd, config)
if err != nil {
fmt.Println(err)
return
}

err = start(file)
err = start(file, config)
if err != nil {
fmt.Println(err)
}
Expand All @@ -80,45 +80,55 @@ func Execute() {
}

func init() {
defaultConfigPath, err := config.GetDefaultConfigPath()
defaultConfigPath, err := c.GetDefaultConfigPath()
if err != nil {
os.Exit(1)
}
rootCmd.Flags().Float64P("sample-rate", "s", config.Default.SampleRate, "sample rate")
rootCmd.Flags().Float64("fade-in", config.Default.FadeIn, "fade-in in seconds")
rootCmd.Flags().Float64("fade-out", config.Default.FadeOut, "fade-out in seconds")
rootCmd.Flags().Float64P("sample-rate", "s", c.DefaultSampleRate, "sample rate")
rootCmd.Flags().Float64("fade-in", c.DefaultFadeIn, "fade-in in seconds")
rootCmd.Flags().Float64("fade-out", c.DefaultFadeOut, "fade-out in seconds")
rootCmd.Flags().StringP("config", "c", defaultConfigPath, "path to your config file")
rootCmd.Flags().Float64P("duration", "d", config.Default.Duration, "duration in seconds; if positive duration is specified, synth will stop playing after the defined time")
rootCmd.Flags().Float64P("duration", "d", c.DefaultDuration, "duration in seconds; if positive duration is specified, synth will stop playing after the defined time")
}

func parseFlags(cmd *cobra.Command) error {
func parseFlags(cmd *cobra.Command, config *c.Config) error {
s, _ := cmd.Flags().GetFloat64("sample-rate")
in, _ := cmd.Flags().GetFloat64("fade-in")
out, _ := cmd.Flags().GetFloat64("fade-out")
duration, _ := cmd.Flags().GetFloat64("duration")

c.Config.SampleRate = s
c.Config.FadeIn = in
c.Config.FadeOut = out
c.Config.Duration = duration
if cmd.Flag("sample-rate").Changed {
config.SampleRate = s
}
if cmd.Flag("fade-in").Changed {
config.FadeIn = in
}
if cmd.Flag("fade-out").Changed {
config.FadeOut = out
}
if cmd.Flag("duration").Changed {
config.Duration = duration
}

return c.Config.Validate()
return config.Validate()
}

func start(file string) error {
func start(file string, config *c.Config) error {
err := audio.Init()
if err != nil {
return err
}
defer audio.Terminate()

logger := ui.NewLogger(10)
quit := make(chan bool)
autoStop := make(chan bool)
u := ui.NewUI(file, quit, autoStop)
var closing bool
u := ui.NewUI(logger, file, quit, autoStop, config.Duration, &closing)
go u.Enter()

output := make(chan struct{ Left, Right float32 })
ctx, err := audio.NewContext(output, c.Config.SampleRate)
output := make(chan audio.AudioOutput)
ctx, err := audio.NewContext(output, config.SampleRate)
if err != nil {
return err
}
Expand All @@ -129,11 +139,11 @@ func start(file string) error {
return err
}

ctl := control.NewControl(output, autoStop)
ctl := control.NewControl(logger, *config, output, autoStop, &closing)
ctl.Start()
defer ctl.StopSynth()

loader, err := f.NewLoader(ctl, file)
loader, err := f.NewLoader(logger, ctl, file, &closing)
if err != nil {
return err
}
Expand All @@ -150,20 +160,20 @@ func start(file string) error {
interrupt := make(chan bool)
go catchInterrupt(interrupt, sig)

ctl.FadeIn(c.Config.FadeIn)
ctl.FadeIn(config.FadeIn)
var fadingOut bool

Loop:
for {
select {
case <-quit:
if fadingOut {
ui.Logger.Info("already received quit signal")
logger.Info("already received quit signal")
continue
}
fadingOut = true
ui.Logger.Info(fmt.Sprintf("fading out in %fs", c.Config.FadeOut))
ctl.Stop(c.Config.FadeOut)
logger.Info(fmt.Sprintf("fading out in %fs", config.FadeOut))
ctl.Stop(config.FadeOut)
case <-interrupt:
ctl.Stop(0.05)
case <-ctl.SynthDone:
Expand Down
54 changes: 32 additions & 22 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,26 @@ import (
)

const (
minSampleRate = 8000
maxSampleRate = 48000
maxFadeDuration = 3600
maxDuration = 7200
minSampleRate = 8000
maxSampleRate = 48000
maxFadeDuration = 3600
maxDuration = 7200

defaultConfigFile = "config.yaml"
defaultConfigDir = "synth"
DefaultSampleRate = 44100
DefaultFadeIn = 1
DefaultFadeOut = 1
DefaultDuration = -1
)

type config struct {
type Config struct {
SampleRate float64 `yaml:"sample-rate"`
FadeIn float64 `yaml:"fade-in"`
FadeOut float64 `yaml:"fade-out"`
Duration float64 `yaml:"duration"`
}

var Default = config{
SampleRate: 44100,
FadeIn: 1,
FadeOut: 1,
Duration: -1,
}

var Config = config{}

func GetDefaultConfigPath() (string, error) {
userConfigDir, err := os.UserConfigDir()
if err != nil {
Expand All @@ -55,7 +51,14 @@ func EnsureDefaultConfig() error {
return fmt.Errorf("unable to open config file: %w", err)
}

defaultConfig, err := yaml.Marshal(Default)
defaultConfig := Config{
SampleRate: DefaultSampleRate,
FadeIn: DefaultFadeIn,
FadeOut: DefaultFadeOut,
Duration: DefaultDuration,
}

defaultConfigBytes, err := yaml.Marshal(defaultConfig)
if err != nil {
return fmt.Errorf("unable to marshal default config: %w", err)
}
Expand All @@ -65,24 +68,31 @@ func EnsureDefaultConfig() error {
return fmt.Errorf("unable to create config directory: %w", err)
}

return os.WriteFile(configPath, defaultConfig, 0600)
return os.WriteFile(configPath, defaultConfigBytes, 0600)
}

func LoadConfig(path string) error {
func LoadConfig(path string) (*Config, error) {
raw, err := os.ReadFile(path)
if err != nil {
return err
return nil, err
}

err = yaml.Unmarshal(raw, &Config)
config := &Config{}

err = yaml.Unmarshal(raw, &config)
if err != nil {
return err
return nil, err
}

err = config.Validate()
if err != nil {
return nil, err
}

return Config.Validate()
return config, nil
}

func (c *config) Validate() error {
func (c *Config) Validate() error {
if c.SampleRate < minSampleRate {
return fmt.Errorf("sample rate must be greater than or equal to %d", minSampleRate)
}
Expand Down
Loading
Loading