diff --git a/Makefile b/Makefile index 67e2e4e..94c9599 100644 --- a/Makefile +++ b/Makefile @@ -9,3 +9,8 @@ coverage: .PHONY: lint lint: golangci-lint run + +.PHONY: test +test: + go test -race -cover ./... + diff --git a/README.md b/README.md index ab97883..fe35226 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![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-72%25-brightgreen.svg?longCache=true&style=flat) +![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/). diff --git a/autoeq/mdparser.go b/autoeq/mdparser.go index 48a6f1c..2b0dab8 100644 --- a/autoeq/mdparser.go +++ b/autoeq/mdparser.go @@ -7,6 +7,11 @@ import ( "fmt" ) +const ( + eqResultsPrefix = `https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results` + fixedBandSuffix = `%20FixedBandEQ.txt` +) + // compile time interface implementation check var _ MarkDownParser = MetadataParser{} @@ -30,6 +35,14 @@ type MetadataParser struct { FixedBandEQSuffix string } +// NewMetadataParser returns a MetadataParser with populated fields. +func NewMetadataParser() MetadataParser { + return MetadataParser{ + LinkPrefix: eqResultsPrefix, + FixedBandEQSuffix: fixedBandSuffix, + } +} + // ParseMetadata returns a slice of EQ metadata parsed from the input raw bytes. // Returns an error if any. func (p MetadataParser) ParseMetadata(data []byte) ([]EQMetadata, error) { diff --git a/autoeq/mdparser_test.go b/autoeq/mdparser_test.go index 0043e63..b1d39f8 100644 --- a/autoeq/mdparser_test.go +++ b/autoeq/mdparser_test.go @@ -9,11 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -const ( - autoEQResults = `https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results` - fixedBandSuffix = `%20FixedBandEQ.txt` -) - func TestMetadataParser_ParseMetadata(t *testing.T) { t.Parallel() type fields struct { @@ -33,7 +28,7 @@ func TestMetadataParser_ParseMetadata(t *testing.T) { { name: "Happy path", fields: fields{ - LinkPrefix: autoEQResults, + LinkPrefix: eqResultsPrefix, FixedBandEQSuffix: fixedBandSuffix, }, args: args{ @@ -44,7 +39,7 @@ func TestMetadataParser_ParseMetadata(t *testing.T) { ID: "0", Name: "1Custom SA02", Author: "Crinacle", - Link: autoEQResults + "/crinacle/harman_in-ear_2019v2/1Custom%20SA02/1Custom%20SA02" + fixedBandSuffix, + Link: eqResultsPrefix + "/crinacle/harman_in-ear_2019v2/1Custom%20SA02/1Custom%20SA02" + fixedBandSuffix, Global: 0, }, }, diff --git a/main.go b/main.go index 279724a..0cbfaab 100644 --- a/main.go +++ b/main.go @@ -16,11 +16,6 @@ import ( "gopkg.in/alecthomas/kingpin.v2" ) -const ( - autoEQResults = `https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results` - fixedBandSuffix = `%20FixedBandEQ.txt` -) - var ( app = kingpin.New("autoEqMac", "EqMac preset generator powered by AutoEq.\n\nAn interactive CLI that retrieves headphones EQ data from the AutoEq project and produces a JSON preset ready to be imported into EqMac.") file = app.Flag("file", "Output file path. By default it's the name of the headphones model selected.").Short('f').String() @@ -38,10 +33,7 @@ func run() error { kingpin.MustParse(app.Parse(os.Args[1:])) client := http.DefaultClient - mdParser := autoeq.MetadataParser{ - LinkPrefix: autoEQResults, - FixedBandEQSuffix: fixedBandSuffix, - } + mdParser := autoeq.NewMetadataParser() eqGetter := autoeq.EQHTTPGetter{ Client: http.DefaultClient, } @@ -74,14 +66,7 @@ func run() error { return fmt.Errorf("⛔️ could not find fixed band EQ preset: %w", err) } - filename := *file - if filename == "" { - filename = strings.ReplaceAll(headphones, " ", "_") + ".json" - } - if !strings.HasSuffix(filename, ".json") { - filename += ".json" - } - + filename := filename(file, headphones) f, err := os.Create(filename) if err != nil { return fmt.Errorf("⛔️ could not create preset file: %w", err) @@ -98,12 +83,23 @@ func run() error { return nil } +func filename(file *string, headphones string) string { + filename := *file + if filename == "" { + filename = fmt.Sprintf("%s.json", strings.ReplaceAll(headphones, " ", "_")) + } + if !strings.HasSuffix(filename, ".json") { + filename += ".json" + } + return filename +} + func populatedCompleter(eqMetas []autoeq.EQMetadata) func(prompt.Document) []prompt.Suggest { return func(d prompt.Document) []prompt.Suggest { var suggs []prompt.Suggest for _, meta := range eqMetas { suggs = append(suggs, prompt.Suggest{ - Text: meta.Name, Description: meta.ID, + Text: meta.Name, Description: meta.Author, }) } return prompt.FilterContains(suggs, d.Text, true) diff --git a/server/interface.go b/server/interface.go index 32e5833..3297c5f 100644 --- a/server/interface.go +++ b/server/interface.go @@ -4,6 +4,7 @@ package server import ( "io" + "net/http" "github.com/indiependente/autoEqMac/autoeq" "github.com/indiependente/autoEqMac/eqmac" @@ -16,3 +17,9 @@ type Server interface { GetEQMetadataByName(name string) (autoeq.EQMetadata, error) WritePreset(w io.Writer, p eqmac.EQPreset) error } + +// Doer defines the behaviour of a component capable of doing an HTTP request, +// returning an HTTP response and an error. +type Doer interface { + Do(*http.Request) (*http.Response, error) +} diff --git a/server/server.go b/server/server.go index 3850582..f3f5c12 100644 --- a/server/server.go +++ b/server/server.go @@ -28,12 +28,6 @@ func (e Error) Error() string { return string(e) } -// Doer defines the behaviour of a component capable of doing an HTTP request, -// returning an HTTP response and an error. -type Doer interface { - Do(*http.Request) (*http.Response, error) -} - // HTTPServer is an HTTP implementation of a Server. // It fulfills the requests received by obtaining data via HTTP requests. type HTTPServer struct { @@ -60,7 +54,11 @@ func NewHTTPServer(d Doer, mdp autoeq.MarkDownParser, eqg autoeq.EQGetter, m map // ListEQsMetadata returns a list of all the EQ metadata found by the server. // Returns an error if any. func (s HTTPServer) ListEQsMetadata() ([]autoeq.EQMetadata, error) { - resp, err := http.Get(headphonesIndex) + req, err := http.NewRequest(http.MethodGet, headphonesIndex, nil) + if err != nil { + return nil, fmt.Errorf("could not create HTTP request: %w", err) + } + resp, err := s.client.Do(req) if err != nil { return nil, fmt.Errorf("could not get updated headphones list: %w", err) } diff --git a/server/server_mock.go b/server/server_mock.go index ae4d1ce..aa4ecf1 100644 --- a/server/server_mock.go +++ b/server/server_mock.go @@ -9,6 +9,7 @@ import ( autoeq "github.com/indiependente/autoEqMac/autoeq" eqmac "github.com/indiependente/autoEqMac/eqmac" io "io" + http "net/http" reflect "reflect" ) @@ -85,3 +86,39 @@ func (m *MockServer) WritePreset(w io.Writer, p eqmac.EQPreset) error { func (mr *MockServerMockRecorder) WritePreset(w, p interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WritePreset", reflect.TypeOf((*MockServer)(nil).WritePreset), w, p) } + +// MockDoer is a mock of Doer interface +type MockDoer struct { + ctrl *gomock.Controller + recorder *MockDoerMockRecorder +} + +// MockDoerMockRecorder is the mock recorder for MockDoer +type MockDoerMockRecorder struct { + mock *MockDoer +} + +// NewMockDoer creates a new mock instance +func NewMockDoer(ctrl *gomock.Controller) *MockDoer { + mock := &MockDoer{ctrl: ctrl} + mock.recorder = &MockDoerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockDoer) EXPECT() *MockDoerMockRecorder { + return m.recorder +} + +// Do mocks base method +func (m *MockDoer) Do(arg0 *http.Request) (*http.Response, error) { + ret := m.ctrl.Call(m, "Do", arg0) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Do indicates an expected call of Do +func (mr *MockDoerMockRecorder) Do(arg0 interface{}) *gomock.Call { + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockDoer)(nil).Do), arg0) +} diff --git a/server/server_test.go b/server/server_test.go new file mode 100644 index 0000000..e2028c6 --- /dev/null +++ b/server/server_test.go @@ -0,0 +1,157 @@ +package server + +import ( + "bytes" + "io/ioutil" + "net/http" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/uuid" + "github.com/indiependente/autoEqMac/autoeq" + "github.com/indiependente/autoEqMac/eqmac" + "github.com/indiependente/autoEqMac/eqmac/mapping" + "github.com/stretchr/testify/require" +) + +var ( + rawEqList = []byte(`# Index +This is a list of all equalization profiles. Target is in parentheses if there are results with multiple targets +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 +See [usage instructions](https://github.com/jaakkopasanen/AutoEq#usage) for more options and info. + +### Parametric EQs +In case of using parametric equalizer, apply preamp of **-7.0dB** and build filters manually +with these parameters. The first 5 filters can be used independently. +When using independent subset of filters, apply preamp of **-7.0dB**. + +| Type | Fc | Q | Gain | +|:--------|:---------|:-----|:--------| +| Peaking | 23 Hz | 0.95 | 6.3 dB | +| Peaking | 327 Hz | 2.37 | 3.2 dB | +| Peaking | 5826 Hz | 5.21 | 6.7 dB | +| Peaking | 18679 Hz | 0.06 | -2.4 dB | +| Peaking | 19122 Hz | 0.41 | -9.3 dB | +| Peaking | 167 Hz | 2.59 | -2.0 dB | +| Peaking | 1397 Hz | 0.62 | 1.2 dB | +| Peaking | 2608 Hz | 3.12 | -2.3 dB | +| Peaking | 7966 Hz | 2.55 | -2.1 dB | +| Peaking | 9166 Hz | 3.3 | 3.4 dB | + +### Fixed Band EQs +In case of using fixed band (also called graphic) equalizer, apply preamp of **-6.4dB** +(if available) and set gains manually with these parameters. + +| Type | Fc | Q | Gain | +|:--------|:---------|:-----|:---------| +| Peaking | 31 Hz | 1.41 | 5.8 dB | +| Peaking | 62 Hz | 1.41 | 0.9 dB | +| Peaking | 125 Hz | 1.41 | -1.7 dB | +| Peaking | 250 Hz | 1.41 | 1.7 dB | +| Peaking | 500 Hz | 1.41 | 0.9 dB | +| Peaking | 1000 Hz | 1.41 | 0.9 dB | +| Peaking | 2000 Hz | 1.41 | -0.7 dB | +| Peaking | 4000 Hz | 1.41 | -0.8 dB | +| Peaking | 8000 Hz | 1.41 | -0.6 dB | +| Peaking | 16000 Hz | 1.41 | -13.5 dB | + +### Graphs +![](./Audio-Technica%20ATH-M50x.png)`) + id = uuid.New().String() + eqMeta = autoeq.EQMetadata{ + ID: "0", + Name: "Audio-Technica ATH-M50x", + Author: "oratory1990", + Link: "https://raw.githubusercontent.com/jaakkopasanen/AutoEq/master/results/oratory1990/harman_over-ear_2018/Audio-Technica%20ATH-M50x/Audio-Technica%20ATH-M50x%20FixedBandEQ.txt", + Global: 0, + } + eqPreset = eqmac.EQPreset{ + Gains: eqmac.Gains{ + Global: -6.4, + Bands: []float64{5.8}, + }, + ID: id, + IsDefault: false, + Name: "Audio-Technica ATH-M50x", + } +) + +func TestHTTPServer(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + setupExpectations func(doer *MockDoer) + want struct { + meta []autoeq.EQMetadata + preset eqmac.EQPreset + } + wantErr bool + }{ + { + name: "Happy path", + setupExpectations: func(doer *MockDoer) { + doer.EXPECT().Do(gomock.Any()).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(rawEqList)), + }, nil) + doer.EXPECT().Do(gomock.Any()).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(rawEqData)), + }, nil) + doer.EXPECT().Do(gomock.Any()).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewReader(rawGlobalData)), + }, nil) + }, + want: struct { + meta []autoeq.EQMetadata + preset eqmac.EQPreset + }{ + meta: []autoeq.EQMetadata{ + eqMeta, + }, + preset: eqPreset, + }, + + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + doer := NewMockDoer(ctrl) + mdp := autoeq.NewMetadataParser() + eqg := autoeq.EQHTTPGetter{Client: doer} + mapp := mapping.NewAutoEQMapper(mapping.WrappedGenerator(func() string { + return id + })) + tt.setupExpectations(doer) + + s := HTTPServer{ + client: doer, + mdparser: mdp, + eqGetter: eqg, + mapper: mapp, + eqMetas: map[string]autoeq.EQMetadata{}, + eqNameID: map[string]string{}, + } + got, err := s.ListEQsMetadata() + require.NoError(t, err) + require.Equal(t, tt.want.meta, got) + gotMeta, err := s.GetEQMetadataByName(eqMeta.Name) + require.Equal(t, tt.want.meta[0], gotMeta) + gotPreset, err := s.GetFixedBandEQPreset(eqMeta.ID) + require.Equal(t, tt.want.preset, gotPreset) + err = s.WritePreset(ioutil.Discard, gotPreset) + require.NoError(t, err) + }) + } +}