From 2c1b61d2f64549ba0a4a38f845112b78abd2a312 Mon Sep 17 00:00:00 2001 From: Francesco Farina <3093285+indiependente@users.noreply.github.com> Date: Sat, 1 Oct 2022 00:03:31 +0100 Subject: [PATCH] [ISSUE-28] Update preamp fetch (#29) * updated preamp fetching code to match new fixed band eq file format * minor improvement: parse preamp row before filters * update README * update README with link to latest release * add boundary check on preamp fields --- README.md | 23 ++++++++++---- autoeq/fixed_band.go | 59 +++++++++++++++++++++++++++--------- autoeq/fixed_band_test.go | 23 +++++++++++--- autoeq/mdparser.go | 1 + eqmac/mapping/mapper.go | 8 ++--- eqmac/mapping/mapper_mock.go | 2 +- eqmac/mapping/mapper_test.go | 11 ++++--- executor.go | 2 ++ server/server.go | 16 +++++----- server/server_test.go | 30 ++++++++++++++++-- 10 files changed, 131 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 9364f87..bf5f7cd 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,30 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/indiependente/autoEqMac)](https://goreportcard.com/report/github.com/indiependente/autoEqMac) ![gopherbadger-tag-do-not-edit](https://img.shields.io/badge/Go%20Coverage-63%25-brightgreen.svg?longCache=true&style=flat) [![Workflow Status](https://github.com/indiependente/autoEqMac/workflows/lint-test/badge.svg)](https://github.com/indiependente/autoEqMac/actions) + # autoEqMac + An interactive CLI that retrieves headphones EQ data from the [AutoEq Project](https://github.com/jaakkopasanen/AutoEq) and produces a JSON preset ready to be imported into [EqMac](https://github.com/bitgapp/eqMac/). ## Dependencies - - Go + +- Go ## How to ### Install -`go install github.com/indiependente/autoEqMac` +Download the latest [release](https://github.com/indiependente/autoEqMac/releases/latest). -### Supported commands +### Install with Go +```bash +go install github.com/indiependente/autoEqMac ``` + +### Supported commands + +```plaintext ▶ autoEqMac --help usage: autoEqMac [] @@ -42,11 +51,13 @@ By default `autoEqMac` saves a JSON file with the same name of the headphones mo You can provide a different path by passing it using the `-f, --file` flag. ## TODO + - [ ] GUI ## Credits Thanks to: - - https://github.com/jaakkopasanen/AutoEq - - https://github.com/bitgapp/eqMac/ - - https://github.com/c-bata/go-prompt + +- +- +- diff --git a/autoeq/fixed_band.go b/autoeq/fixed_band.go index 64b024b..df20263 100644 --- a/autoeq/fixed_band.go +++ b/autoeq/fixed_band.go @@ -1,39 +1,66 @@ package autoeq import ( + "errors" "fmt" "strconv" "strings" ) -const preampFields = 12 +const ( + preampFields = 3 + fixedBandFields = 12 +) -// FixedBandEQ represents a single EQ band. -type FixedBandEQ struct { +// ErrBadPreampFormat is returned when the preamp line can't be parsed. +var ErrBadPreampFormat = errors.New("bad preamp line format") + +// FixedBandFilter represents a single EQ band. +type FixedBandFilter struct { Frequency int // Hz - Gain float64 // Db + Gain float64 // dB Q float64 // fixed } -// FixedBandEQs represents a simple fixed bands EQ. -type FixedBandEQs []*FixedBandEQ +// FixedBandEQ represents a simple fixed bands EQ. +type FixedBandEQ struct { + Filters []*FixedBandFilter + Preamp float64 +} // ToFixedBandEQs transforms the raw EQ data into a fixed band EQ. // Returns an error if any. -func ToFixedBandEQs(data []byte) (FixedBandEQs, error) { +func ToFixedBandEQs(data []byte) (*FixedBandEQ, error) { rows := strings.Split(string(data), "\n") - eqs := make(FixedBandEQs, len(rows)) + + fbEQ := &FixedBandEQ{ + Filters: make([]*FixedBandFilter, len(rows)), + } + + startIdx := 0 // rows index, increment if first row is preamp + + // parse preamp + if strings.HasPrefix(rows[0], "Preamp") { + fields := strings.Fields(rows[0]) + if len(fields) != preampFields { + return nil, ErrBadPreampFormat + } + preamp, err := strconv.ParseFloat(strings.TrimSpace(fields[1]), bitSize) + if err != nil { + return nil, err + } + fbEQ.Preamp = preamp + startIdx++ + } i := 0 - for _, row := range rows { + for _, row := range rows[startIdx:] { if row == "" { continue } - if strings.HasPrefix(row, "Preamp") { - continue - } + eqFields := strings.Fields(row) - if len(eqFields) < preampFields { + if len(eqFields) < fixedBandFields { return nil, fmt.Errorf("could not parse : %s", row) } freq, err := strconv.Atoi(strings.TrimSpace(eqFields[5])) @@ -48,7 +75,7 @@ func ToFixedBandEQs(data []byte) (FixedBandEQs, error) { if err != nil { return nil, fmt.Errorf("could not parse Q: %w", err) } - eqs[i] = &FixedBandEQ{ + fbEQ.Filters[i] = &FixedBandFilter{ Frequency: freq, Gain: gain, Q: q, @@ -56,5 +83,7 @@ func ToFixedBandEQs(data []byte) (FixedBandEQs, error) { i++ } - return eqs[:i], nil + fbEQ.Filters = fbEQ.Filters[:i] + + return fbEQ, nil } diff --git a/autoeq/fixed_band_test.go b/autoeq/fixed_band_test.go index 42286e6..8929163 100644 --- a/autoeq/fixed_band_test.go +++ b/autoeq/fixed_band_test.go @@ -12,13 +12,22 @@ func TestToFixedBandEQs(t *testing.T) { tests := []struct { name string data []byte - want FixedBandEQs + want *FixedBandEQ wantErr bool }{ { - name: "Happy path", - data: []byte("Filter 1: ON PK Fc 31 Hz Gain 5.8 dB Q 1.41\n"), - want: FixedBandEQs{{Frequency: 31, Gain: 5.8, Q: 1.41}}, + name: "Happy path", + data: []byte("Preamp: -6.1 dB\nFilter 1: ON PK Fc 31 Hz Gain 5.8 dB Q 1.41\n"), + want: &FixedBandEQ{ + Filters: []*FixedBandFilter{{Frequency: 31, Gain: 5.8, Q: 1.41}}, + Preamp: -6.1, + }, + wantErr: false, + }, + { + name: "Happy path - No data", + data: nil, + want: &FixedBandEQ{Filters: []*FixedBandFilter{}, Preamp: 0}, wantErr: false, }, { @@ -39,6 +48,12 @@ func TestToFixedBandEQs(t *testing.T) { want: nil, wantErr: true, }, + { + name: "Sad path - Bad Preamp Line Format", + data: []byte("Preamp: -6.1\nFilter 1: ON PK Fc AB Hz Gain 5.8 dB Q 1.41"), + want: nil, + wantErr: true, + }, } for _, tt := range tests { tt := tt diff --git a/autoeq/mdparser.go b/autoeq/mdparser.go index fdd7f84..e29499c 100644 --- a/autoeq/mdparser.go +++ b/autoeq/mdparser.go @@ -21,6 +21,7 @@ type EQMetadata struct { Name string Author string Link string + // Deprecated: this field has been deprecated in favor of autoeq.FixedBandEQ.Preamp. Global float64 } diff --git a/eqmac/mapping/mapper.go b/eqmac/mapping/mapper.go index 76e964b..e7af7fb 100644 --- a/eqmac/mapping/mapper.go +++ b/eqmac/mapping/mapper.go @@ -22,7 +22,7 @@ func (g WrappedGenerator) UUID() string { // Mapper defines the behavior of a component capable of mapping AutoEQ data into EqMac format. type Mapper interface { - MapFixedBand(autoeq.FixedBandEQs, *autoeq.EQMetadata) (eqmac.EQPreset, error) + MapFixedBand(*autoeq.FixedBandEQ, *autoeq.EQMetadata) (eqmac.EQPreset, error) } // compile time interface implementation check. @@ -43,17 +43,17 @@ func NewAutoEQMapper(gen UUIDGenerator) AutoEQMapper { // MapFixedBand maps fixed bands EQ data into an EqMac preset. // Returns an error if any. -func (m AutoEQMapper) MapFixedBand(fbeq autoeq.FixedBandEQs, meta *autoeq.EQMetadata) (eqmac.EQPreset, error) { +func (m AutoEQMapper) MapFixedBand(fbeq *autoeq.FixedBandEQ, meta *autoeq.EQMetadata) (eqmac.EQPreset, error) { var preset eqmac.EQPreset preset.ID = m.gen.UUID() preset.IsDefault = false preset.Name = meta.Name preset.Gains = eqmac.Gains{ - Global: meta.Global, + Global: fbeq.Preamp, Bands: []float64{}, } bands := []float64{} - for _, band := range fbeq { + for _, band := range fbeq.Filters { bands = append(bands, band.Gain) } preset.Gains.Bands = bands diff --git a/eqmac/mapping/mapper_mock.go b/eqmac/mapping/mapper_mock.go index b32b9e9..d5436c4 100644 --- a/eqmac/mapping/mapper_mock.go +++ b/eqmac/mapping/mapper_mock.go @@ -70,7 +70,7 @@ func (m *MockMapper) EXPECT() *MockMapperMockRecorder { } // MapFixedBand mocks base method -func (m *MockMapper) MapFixedBand(arg0 autoeq.FixedBandEQs, arg1 autoeq.EQMetadata) (eqmac.EQPreset, error) { +func (m *MockMapper) MapFixedBand(arg0 autoeq.FixedBandEQ, arg1 autoeq.EQMetadata) (eqmac.EQPreset, error) { ret := m.ctrl.Call(m, "MapFixedBand", arg0, arg1) ret0, _ := ret[0].(eqmac.EQPreset) ret1, _ := ret[1].(error) diff --git a/eqmac/mapping/mapper_test.go b/eqmac/mapping/mapper_test.go index 785a74f..4ba95d4 100644 --- a/eqmac/mapping/mapper_test.go +++ b/eqmac/mapping/mapper_test.go @@ -11,11 +11,11 @@ import ( "github.com/indiependente/autoEqMac/eqmac" ) -func TestAutoEQMapper_MapFixedBand(t *testing.T) { +func TestAutoEQMapper_MapFixedBand(t *testing.T) { //nolint: funlen t.Parallel() id := uuid.New().String() type args struct { - fbeq autoeq.FixedBandEQs + fbeq *autoeq.FixedBandEQ meta *autoeq.EQMetadata } tests := []struct { @@ -28,12 +28,13 @@ func TestAutoEQMapper_MapFixedBand(t *testing.T) { { name: "Happy path", args: args{ - fbeq: autoeq.FixedBandEQs{ - { + fbeq: &autoeq.FixedBandEQ{ + Preamp: -6.4, + Filters: []*autoeq.FixedBandFilter{{ Frequency: 31, Gain: 5.8, Q: 1.41, - }, + }}, }, meta: &autoeq.EQMetadata{ ID: "0", diff --git a/executor.go b/executor.go index 895e2e6..a0955dc 100644 --- a/executor.go +++ b/executor.go @@ -58,5 +58,7 @@ func NewExecutor(srv *server.HTTPServer) func(string) { return } fmt.Printf("📝 Preset saved to %s\n", f.Name()) + + os.Exit(0) } } diff --git a/server/server.go b/server/server.go index 0b50fa6..aea6eaf 100644 --- a/server/server.go +++ b/server/server.go @@ -91,16 +91,18 @@ func (s *HTTPServer) GetFixedBandEQPreset(id string) (eqmac.EQPreset, error) { if err != nil { return eqmac.EQPreset{}, fmt.Errorf("could not get raw EQ data: %w", err) } - globalPreamp, err := s.eqGetter.GetFixedBandGlobalPreamp(eqMeta) - if err != nil { - return eqmac.EQPreset{}, fmt.Errorf("could not get global EQ preamp data: %w", err) - } - eqMeta.Global = globalPreamp - fbEQs, err := autoeq.ToFixedBandEQs(rawEQ) + fbEQ, err := autoeq.ToFixedBandEQs(rawEQ) if err != nil { return eqmac.EQPreset{}, fmt.Errorf("could not map raw EQ data: %w", err) } - eqPreset, err := s.mapper.MapFixedBand(fbEQs, eqMeta) + if fbEQ.Preamp == 0 { // fallback mechanism for Preamp + globalPreamp, err := s.eqGetter.GetFixedBandGlobalPreamp(eqMeta) //nolint:govet // error shadowing doesn't impact its handling. + if err != nil { + return eqmac.EQPreset{}, fmt.Errorf("could not get global EQ preamp data: %w", err) + } + fbEQ.Preamp = globalPreamp + } + eqPreset, err := s.mapper.MapFixedBand(fbEQ, eqMeta) if err != nil { return eqmac.EQPreset{}, fmt.Errorf("could not map raw EQ datato eqMac preset: %w", err) } diff --git a/server/server_test.go b/server/server_test.go index e1fae9c..4bae7f4 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -22,8 +22,10 @@ from the same source. - [Audio-Technica ATH-M50x](./oratory1990/harman_over-ear_2018/Audio-Technica%20ATH-M50x) by oratory1990 `) - rawEqData = []byte(`Filter 1: ON PK Fc 31 Hz Gain 5.8 dB Q 1.41`) - rawGlobalData = []byte(`# Audio-Technica ATH-M50x + rawEqData = []byte(`Preamp: -6.4 dB + Filter 1: ON PK Fc 31 Hz Gain 5.8 dB Q 1.41`) + rawEqDataNoPreamp = []byte(`Filter 1: ON PK Fc 31 Hz Gain 5.8 dB Q 1.41`) + rawGlobalData = []byte(`# Audio-Technica ATH-M50x See [usage instructions](https://github.com/jaakkopasanen/AutoEq#usage) for more options and info. ### Parametric EQs @@ -105,6 +107,30 @@ func TestHTTPServer(t *testing.T) { //nolint:funlen StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(rawEqData)), }, nil) + }, + want: struct { + meta []*autoeq.EQMetadata + preset eqmac.EQPreset + }{ + meta: []*autoeq.EQMetadata{ + eqMeta, + }, + preset: eqPreset, + }, + + wantErr: false, + }, + { + name: "Happy path - Missing Global Preamp requires extra HTTP call", + setupExpectations: func(doer *MockDoer) { + doer.EXPECT().Do(gomock.Any()).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(rawEqList)), + }, nil) + doer.EXPECT().Do(gomock.Any()).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(rawEqDataNoPreamp)), + }, nil) doer.EXPECT().Do(gomock.Any()).Return(&http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(rawGlobalData)),