diff --git a/cmd/mxl-pattern/main.go b/cmd/mxl-pattern/main.go index 62ac32a..45a889b 100644 --- a/cmd/mxl-pattern/main.go +++ b/cmd/mxl-pattern/main.go @@ -20,6 +20,7 @@ import ( "github.com/qvest-digital/go-mxl/mxl" "github.com/spf13/pflag" + "mxl-pattern-generator/internal/audio" "mxl-pattern-generator/internal/flowdef" "mxl-pattern-generator/internal/generator" "mxl-pattern-generator/internal/video" @@ -418,6 +419,42 @@ func buildVideoConfig(args appArgs) (video.Config, error) { }, nil } +// buildAudioConfig returns nil when audio is disabled. An explicit audio flow +// definition enables audio even when --channel is zero. +func buildAudioConfig(args appArgs) (*audio.Config, error) { + if args.audioFlowDefFile == "" && args.audioChannels == 0 { + return nil, nil + } + + var definition flowdef.Audio + if args.audioFlowDefFile != "" { + data, err := os.ReadFile(args.audioFlowDefFile) + if err != nil { + return nil, fmt.Errorf("read audio flow definition %q: %w", args.audioFlowDefFile, err) + } + definition, err = flowdef.ParseFloat32Audio(data) + if err != nil { + return nil, fmt.Errorf("parse audio flow definition %q: %w", args.audioFlowDefFile, err) + } + } else { + rate, ok := samplingRates[args.audioSamplingFreq] + if !ok { + return nil, fmt.Errorf("unsupported audio sample rate %q", args.audioSamplingFreq) + } + var err error + definition, err = flowdef.NewFloat32Audio( + args.audioUUID, + uint(args.audioChannels), + flowdef.Rational{Numerator: uint(rate.Num), Denominator: uint(rate.Den)}, + ) + if err != nil { + return nil, fmt.Errorf("build audio flow definition: %w", err) + } + } + + return &audio.Config{Definition: definition}, nil +} + func main() { parsed, err := parseArgs(os.Args[1:], os.Stdout, os.Stderr) if err != nil { diff --git a/cmd/mxl-pattern/main_test.go b/cmd/mxl-pattern/main_test.go index 494e5c7..3f1dee0 100644 --- a/cmd/mxl-pattern/main_test.go +++ b/cmd/mxl-pattern/main_test.go @@ -162,3 +162,70 @@ func TestBuildVideoConfigFromFile(t *testing.T) { t.Fatalf("rate = %d/%d, want file value 60000/1001", cfg.Rate().Num, cfg.Rate().Den) } } + +func TestBuildAudioConfigDisabled(t *testing.T) { + cfg, err := buildAudioConfig(appArgs{}) + if err != nil { + t.Fatalf("buildAudioConfig: %v", err) + } + if cfg != nil { + t.Fatalf("config = %+v, want nil for disabled audio", cfg) + } +} + +func TestBuildAudioConfigFromArgs(t *testing.T) { + cfg, err := buildAudioConfig(appArgs{ + audioUUID: "b3bb5be7-9fe9-4324-a5bb-4c70e1084449", + audioChannels: 2, + audioSamplingFreq: "48", + }) + if err != nil { + t.Fatalf("buildAudioConfig: %v", err) + } + if cfg == nil { + t.Fatal("config is nil") + } + if cfg.Channels() != 2 { + t.Fatalf("channels = %d, want 2", cfg.Channels()) + } + if cfg.Rate().Num != 48000 || cfg.Rate().Den != 1 { + t.Fatalf("rate = %d/%d, want 48000/1", cfg.Rate().Num, cfg.Rate().Den) + } +} + +func TestBuildAudioConfigFromFile(t *testing.T) { + definition, err := flowdef.NewFloat32Audio( + "b3bb5be7-9fe9-4324-a5bb-4c70e1084449", + 8, + flowdef.Rational{Numerator: 96000, Denominator: 1}, + ) + if err != nil { + t.Fatalf("NewFloat32Audio: %v", err) + } + data, err := json.Marshal(definition) + if err != nil { + t.Fatalf("json.Marshal: %v", err) + } + path := t.TempDir() + "/audio.json" + if err := os.WriteFile(path, data, 0o600); err != nil { + t.Fatalf("os.WriteFile: %v", err) + } + + cfg, err := buildAudioConfig(appArgs{ + audioFlowDefFile: path, + audioChannels: 2, + audioSamplingFreq: "48", + }) + if err != nil { + t.Fatalf("buildAudioConfig: %v", err) + } + if cfg == nil { + t.Fatal("config is nil") + } + if cfg.Channels() != 8 { + t.Fatalf("channels = %d, want file value 8", cfg.Channels()) + } + if cfg.Rate().Num != 96000 || cfg.Rate().Den != 1 { + t.Fatalf("rate = %d/%d, want file value 96000/1", cfg.Rate().Num, cfg.Rate().Den) + } +} diff --git a/internal/audio/config.go b/internal/audio/config.go new file mode 100644 index 0000000..34f295e --- /dev/null +++ b/internal/audio/config.go @@ -0,0 +1,26 @@ +package audio + +import ( + "mxl-pattern-generator/internal/flowdef" + + "github.com/qvest-digital/go-mxl/mxl" +) + +type Config struct { + Definition flowdef.Audio +} + +func (c Config) ID() string { + return c.Definition.ID +} + +func (c Config) Channels() uint { + return c.Definition.ChannelCount +} + +func (c Config) Rate() mxl.Rational { + return mxl.Rational{ + Num: int64(c.Definition.SampleRate.Numerator), + Den: int64(c.Definition.SampleRate.Denominator), + } +}