diff --git a/js/actionCreators.js b/js/actionCreators.js
index 656ffe52..9337c728 100644
--- a/js/actionCreators.js
+++ b/js/actionCreators.js
@@ -1,9 +1,10 @@
-import { parser } from "winamp-eqf";
+import { parser, creator } from "winamp-eqf";
import MyFile from "./myFile";
import skinParser from "./skinParser";
import { BANDS } from "./constants";
+import { getEqfData } from "./selectors";
-import { clamp } from "./utils";
+import { clamp, base64FromArrayBuffer, downloadURI, normalize } from "./utils";
import {
CLOSE_WINAMP,
LOAD_AUDIO_FILE,
@@ -95,8 +96,6 @@ export function toggleShuffle() {
return { type: TOGGLE_SHUFFLE };
}
-export const normalize = hrz => Math.round((hrz - 1) / 63 * 100);
-
function setEqFromFile(file) {
return dispatch => {
file.processBuffer(arrayBuffer => {
@@ -212,3 +211,14 @@ export function toggleEq() {
dispatch({ type });
};
}
+
+export function downloadPreset() {
+ return (dispatch, getState) => {
+ const state = getState();
+ const data = getEqfData(state);
+ const arrayBuffer = creator(data);
+ const base64 = base64FromArrayBuffer(arrayBuffer);
+ const dataURI = `data:application/zip;base64,${base64}`;
+ downloadURI(dataURI, "entry.eqf");
+ };
+}
diff --git a/js/actionCreators.test.js b/js/actionCreators.test.js
index 51f5eca7..9d9b026b 100644
--- a/js/actionCreators.test.js
+++ b/js/actionCreators.test.js
@@ -18,8 +18,7 @@ import {
setEqBand,
setEqToMax,
setEqToMin,
- setEqToMid,
- normalize
+ setEqToMid
} from "./actionCreators";
test("stop", () => {
@@ -124,8 +123,3 @@ test("setEqToMid", () => {
]);
expect(mockDispatch.mock.calls).toEqual(expectedCalls);
});
-
-test("normalize", () => {
- expect(normalize(1)).toBe(0);
- expect(normalize(64)).toBe(100);
-});
diff --git a/js/components/Band.js b/js/components/Band.js
index 1fd275da..93271a77 100644
--- a/js/components/Band.js
+++ b/js/components/Band.js
@@ -22,7 +22,7 @@ const Band = ({ value, backgroundPosition, id, onChange }) =>
2 * Math.round(value / 2);
-export const getY = value => roundToEven(32 - value / 100 * 32);
+// The line is two pixels wide, and sits "above" the offset specified. So we use 30 not 32 as our max.
+const reboundTo32 = rebound(1, 100, 0, 30);
+
+export const getY = value => roundToEven(reboundTo32(value));
class EqGraph extends React.Component {
constructor(props) {
diff --git a/js/components/EqualizerWindow.js b/js/components/EqualizerWindow.js
index 4a80cc80..747ab993 100644
--- a/js/components/EqualizerWindow.js
+++ b/js/components/EqualizerWindow.js
@@ -11,12 +11,16 @@ import {
setEqToMin
} from "../actionCreators";
-import { SET_FOCUSED_WINDOW } from "../actionTypes";
+import {
+ SET_FOCUSED_WINDOW,
+ TOGGLE_PRESETS_CONTEXT_MENU
+} from "../actionTypes";
import Band from "./Band";
import EqOn from "./EqOn";
import EqAuto from "./EqAuto";
import EqGraph from "./EqGraph";
+import PresetsContextMenu from "./PresetsContextMenu";
import "../../css/equalizer-window.css";
@@ -42,7 +46,12 @@ const EqualizerWindow = props => {
-
+
@@ -72,13 +81,20 @@ const mapDispatchToProps = dispatch => ({
setEqToMin: () => dispatch(setEqToMin()),
setEqToMid: () => dispatch(setEqToMid()),
setEqToMax: () => dispatch(setEqToMax()),
- setHertzValue: hertz => value => dispatch(setEqBand(hertz, value))
+ setHertzValue: hertz => value => dispatch(setEqBand(hertz, value)),
+ togglePresetsContextMenu: e => {
+ dispatch({ type: TOGGLE_PRESETS_CONTEXT_MENU });
+ // TODO: Consider binding to a ref instead.
+ // https://stackoverflow.com/a/24421834
+ e.nativeEvent.stopImmediatePropagation();
+ }
});
const mapStateToProps = state => ({
doubled: state.display.doubled,
selected: state.windows.focused === WINDOWS.EQUALIZER,
- closed: !state.windows.equalizer
+ closed: !state.windows.equalizer,
+ contextMenuSelected: state.presetsContextMenu.selected
});
export default connect(mapStateToProps, mapDispatchToProps)(EqualizerWindow);
diff --git a/js/components/PresetsContextMenu.js b/js/components/PresetsContextMenu.js
new file mode 100644
index 00000000..2137cd91
--- /dev/null
+++ b/js/components/PresetsContextMenu.js
@@ -0,0 +1,19 @@
+import React from "react";
+import { connect } from "react-redux";
+import { CLOSE_PRESETS_CONTEXT_MENU } from "../actionTypes";
+import { openFileDialog, downloadPreset } from "../actionCreators";
+import { ContextMenu, Node } from "./contextMenu";
+
+const MainContextMenu = props =>
+
+
+
+ ;
+
+const mapDispatchToProps = (dispatch, ownProps) => ({
+ closeMenu: () => dispatch({ type: CLOSE_PRESETS_CONTEXT_MENU }),
+ openFileDialog: () => dispatch(openFileDialog(ownProps.fileInput)),
+ downloadPreset: () => dispatch(downloadPreset())
+});
+
+export default connect(null, mapDispatchToProps)(MainContextMenu);
diff --git a/js/components/__snapshots__/MainWindow.test.js.snap b/js/components/__snapshots__/MainWindow.test.js.snap
index 95cd9fd1..84c6c3e0 100644
--- a/js/components/__snapshots__/MainWindow.test.js.snap
+++ b/js/components/__snapshots__/MainWindow.test.js.snap
@@ -19,89 +19,102 @@ exports[`MainWindow renders to snapshot 1`] = `
id="title-bar"
>
-
+
+
+
+ Play File...
+
+
+
+ -
+ Load Skin...
+
+ -
+
+
+ -
+ <Base Skin>
+
+ -
+ Mac OSX v1.5 (Aqua)
+
+ -
+ TopazAmp
+
+ -
+ Vizor
+
+ -
+ XMMS Turquoise
+
+ -
+ Zaxon Remake
+
+
+ Skins
+
+
+
+
+
+ Exit
+
+
+
- {playlist ? : ""}
- {equalizer ? : ""}
+ {playlist && }
+ {equalizer && }
,
diff --git a/js/selectors.js b/js/selectors.js
new file mode 100644
index 00000000..c9fa7a86
--- /dev/null
+++ b/js/selectors.js
@@ -0,0 +1,19 @@
+import { denormalize } from "./utils";
+import { BANDS } from "./constants";
+
+export const getEqfData = state => {
+ // state.equalizer.sliders
+ const { sliders } = state.equalizer;
+ const preset = {
+ name: "Entry1",
+ preamp: denormalize(sliders.preamp)
+ };
+ BANDS.forEach(band => {
+ preset[`hz${band}`] = denormalize(sliders[band]);
+ });
+ const eqfData = {
+ presets: [preset],
+ type: "Winamp EQ library file v1.1"
+ };
+ return eqfData;
+};
diff --git a/js/selectors.test.js b/js/selectors.test.js
new file mode 100644
index 00000000..4b15a51f
--- /dev/null
+++ b/js/selectors.test.js
@@ -0,0 +1,29 @@
+import reducer from "./reducers";
+import { getEqfData } from "./selectors";
+describe("getEqfData", () => {
+ it("can extract EQF data from the current state", () => {
+ const state = reducer();
+ //state = reducer(state, { type: "SET_BAND_VALUE", band: 60, value: 100 });
+ const actual = getEqfData(state);
+ const expected = {
+ presets: [
+ {
+ hz60: 32,
+ hz170: 32,
+ hz310: 32,
+ hz600: 32,
+ hz1000: 32,
+ hz3000: 32,
+ hz12000: 32,
+ hz14000: 32,
+ hz16000: 32,
+ hz6000: 32,
+ name: "Entry1",
+ preamp: 32
+ }
+ ],
+ type: "Winamp EQ library file v1.1"
+ };
+ expect(actual).toEqual(expected);
+ });
+});
diff --git a/js/utils.js b/js/utils.js
index 82ebda25..f459f791 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -52,3 +52,29 @@ export const parseIni = text => {
};
export const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
+
+export const base64FromArrayBuffer = arrayBuffer => {
+ const dataView = new Uint8Array(arrayBuffer);
+ return window.btoa(String.fromCharCode(...dataView));
+};
+
+// https://stackoverflow.com/a/15832662/1263117
+export function downloadURI(uri, name) {
+ const link = document.createElement("a");
+ link.download = name;
+ link.href = uri;
+ window.document.body.appendChild(link);
+ link.click();
+ window.document.body.removeChild(link);
+}
+
+export const rebound = (oldMin, oldMax, newMin, newMax) => oldValue =>
+ Math.round(
+ (oldValue - oldMin) * (newMax - newMin) / (oldMax - oldMin) + newMin
+ );
+
+// Convert a .eqf value to a 1-100
+export const normalize = rebound(1, 64, 1, 100);
+
+// Convert a 0-100 to an .eqf value
+export const denormalize = rebound(1, 100, 1, 64);
diff --git a/js/utils.test.js b/js/utils.test.js
index 1af18e5a..d048a74a 100644
--- a/js/utils.test.js
+++ b/js/utils.test.js
@@ -5,7 +5,9 @@ import {
getTimeStr,
clamp,
parseViscolors,
- parseIni
+ parseIni,
+ normalize,
+ denormalize
} from "./utils";
const fixture = filename =>
@@ -123,3 +125,12 @@ describe("parseIni", () => {
expect(actual).toEqual(expected);
});
});
+
+test("normalize", () => {
+ expect(normalize(1)).toBe(1);
+ expect(normalize(64)).toBe(100);
+});
+test("denormalize", () => {
+ expect(denormalize(1)).toBe(1);
+ expect(denormalize(100)).toBe(64);
+});
diff --git a/yarn.lock b/yarn.lock
index fa9faf79..f14147fc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -209,10 +209,6 @@ assert@^1.1.1:
dependencies:
util "0.10.3"
-ast-types@0.9.8:
- version "0.9.8"
- resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.8.tgz#6cb6a40beba31f49f20928e28439fc14a3dab078"
-
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@@ -271,7 +267,7 @@ babel-cli@^6.14.0:
optionalDependencies:
chokidar "^1.6.1"
-babel-code-frame@6.22.0, babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
+babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
dependencies:
@@ -787,10 +783,6 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23
lodash "^4.2.0"
to-fast-properties "^1.0.1"
-babylon@7.0.0-beta.8:
- version "7.0.0-beta.8"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.8.tgz#2bdc5ae366041442c27e068cce6f0d7c06ea9949"
-
babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0:
version "6.16.1"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
@@ -1025,7 +1017,7 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
-chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -1721,6 +1713,13 @@ eslint-config-prettier@^1.5.0:
dependencies:
get-stdin "^5.0.1"
+eslint-plugin-prettier@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.1.1.tgz#2fb7e2ab961f2b61d2c8cf91bc17716ca8c53868"
+ dependencies:
+ fast-diff "^1.1.1"
+ jest-docblock "^20.0.1"
+
eslint-plugin-react@6.10.3:
version "6.10.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78"
@@ -1811,7 +1810,7 @@ estraverse@~4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2"
-esutils@2.0.2, esutils@^2.0.0, esutils@^2.0.2:
+esutils@^2.0.0, esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@@ -1915,6 +1914,10 @@ extsprintf@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+fast-diff@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.1.tgz#0aea0e4e605b6a2189f0e936d4b7fbaf1b7cfd9b"
+
fast-levenshtein@~2.0.4:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
@@ -2046,10 +2049,6 @@ flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
-flow-parser@0.45.0:
- version "0.45.0"
- resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.45.0.tgz#aa29d4ae27f06aa02817772bba0fcbefef7e62f0"
-
for-in@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -2147,14 +2146,14 @@ get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
-get-stdin@5.0.1, get-stdin@^5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
-
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+get-stdin@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
+
getpass@^0.1.1:
version "0.1.6"
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
@@ -2174,7 +2173,7 @@ glob-parent@^2.0.0:
dependencies:
is-glob "^2.0.0"
-glob@7.1.1, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
dependencies:
@@ -2790,6 +2789,10 @@ jest-diff@^19.0.0:
jest-matcher-utils "^19.0.0"
pretty-format "^19.0.0"
+jest-docblock@^20.0.1:
+ version "20.0.3"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712"
+
jest-environment-jsdom@^19.0.2:
version "19.0.2"
resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-19.0.2.tgz#ceda859c4a4b94ab35e4de7dab54b926f293e4a3"
@@ -2919,15 +2922,6 @@ jest-util@^19.0.2:
leven "^2.0.0"
mkdirp "^0.5.1"
-jest-validate@19.0.0:
- version "19.0.0"
- resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.0.tgz#8c6318a20ecfeaba0ba5378bfbb8277abded4173"
- dependencies:
- chalk "^1.1.1"
- jest-matcher-utils "^19.0.0"
- leven "^2.0.0"
- pretty-format "^19.0.0"
-
jest-validate@^19.0.2:
version "19.0.2"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-19.0.2.tgz#dc534df5f1278d5b63df32b14241d4dbf7244c0c"
@@ -3295,7 +3289,7 @@ minimist@0.0.8, minimist@~0.0.1:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@1.2.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
+minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
@@ -3941,20 +3935,9 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
-prettier@^1.0.2:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.3.1.tgz#fa0ea84b45ac0ba6de6a1e4cecdcff900d563151"
- dependencies:
- ast-types "0.9.8"
- babel-code-frame "6.22.0"
- babylon "7.0.0-beta.8"
- chalk "1.1.3"
- esutils "2.0.2"
- flow-parser "0.45.0"
- get-stdin "5.0.1"
- glob "7.1.1"
- jest-validate "19.0.0"
- minimist "1.2.0"
+prettier@^1.4.1:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.4.4.tgz#a8d1447b14c9bf67e6d420dcadd10fb9a4fad65a"
pretty-bytes-cli@^2.0.0:
version "2.0.0"
@@ -5154,9 +5137,9 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.1"
-winamp-eqf@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/winamp-eqf/-/winamp-eqf-0.1.0.tgz#f8a60aee30591343df60e72c0c290a203d1c9615"
+winamp-eqf@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/winamp-eqf/-/winamp-eqf-1.0.0.tgz#0c3702e579a25511d0934b78ea8e6a8b9cbb259a"
window-size@0.1.0:
version "0.1.0"