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)
+ })
+ }
+}