Add eqf export

This commit is contained in:
Jordan Eldredge 2017-06-09 17:52:50 -07:00
parent 3e6759a0a6
commit 8ab39b7205
13 changed files with 269 additions and 145 deletions

View file

@ -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");
};
}

View file

@ -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);
});

View file

@ -22,7 +22,7 @@ const Band = ({ value, backgroundPosition, id, onChange }) =>
<div id={id} className="band" style={{ backgroundPosition }}>
<Slider
type="range"
min={0}
min={1}
max={MAX_VALUE}
step={1}
value={MAX_VALUE - value}

View file

@ -1,12 +1,16 @@
import React from "react";
import { connect } from "react-redux";
import { getCurvePoints } from "cardinal-spline-js";
import { rebound } from "../utils";
import { BANDS } from "../constants";
export const roundToEven = value => 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) {

View file

@ -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 => {
<EqOn />
<EqAuto />
<EqGraph />
<div id="presets" />
<div id="presets" onClick={props.togglePresetsContextMenu}>
<PresetsContextMenu
selected={props.contextMenuSelected}
fileInput={props.fileInput}
/>
</div>
<Band id="preamp" band="preamp" onChange={props.setPreampValue} />
<div id="plus12db" onClick={props.setEqToMax} />
<div id="zerodb" onClick={props.setEqToMid} />
@ -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);

View file

@ -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 =>
<ContextMenu closeMenu={props.closeMenu} selected={props.selected} top>
<Node onClick={props.openFileDialog} label="Load" />
<Node onClick={props.downloadPreset} label="Save" />
</ContextMenu>;
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);

View file

@ -19,89 +19,102 @@ exports[`MainWindow renders to snapshot 1`] = `
id="title-bar"
>
<div
className=""
id="option"
onClick={[Function]}
>
<ul
id="context-menu"
<div
className="bottom"
>
<li>
<a
href="https://github.com/captbaritone/winamp2-js"
target="_blank"
<ul
id="context-menu"
>
<li>
<a
href="https://github.com/captbaritone/winamp2-js"
label="Winamp2-js"
target="_blank"
>
Winamp2-js
</a>
</li>
<li
className="hr"
>
Winamp2-js...
</a>
</li>
<li
className="hr"
>
<hr />
</li>
<li
onClick={[Function]}
>
Play File...
</li>
<li
className="parent"
>
<ul>
<li
onClick={[Function]}
>
Load Skin...
</li>
<li
className="hr"
>
<hr />
</li>
<li
onClick={[Function]}
>
&lt;Base Skin&gt;
</li>
<li
onClick={[Function]}
>
Mac OSX v1.5 (Aqua)
</li>
<li
onClick={[Function]}
>
TopazAmp
</li>
<li
onClick={[Function]}
>
Vizor
</li>
<li
onClick={[Function]}
>
XMMS Turquoise
</li>
<li
onClick={[Function]}
>
Zaxon Remake
</li>
</ul>
Skins
</li>
<li
className="hr"
>
<hr />
</li>
<li
onClick={[Function]}
>
Exit
</li>
</ul>
<hr />
</li>
<li
label="Play File..."
onClick={[Function]}
>
Play File...
</li>
<li
className="parent"
>
<ul>
<li
label="Load Skin..."
onClick={[Function]}
>
Load Skin...
</li>
<li
className="hr"
>
<hr />
</li>
<li
label="<Base Skin>"
onClick={[Function]}
>
&lt;Base Skin&gt;
</li>
<li
label="Mac OSX v1.5 (Aqua)"
onClick={[Function]}
>
Mac OSX v1.5 (Aqua)
</li>
<li
label="TopazAmp"
onClick={[Function]}
>
TopazAmp
</li>
<li
label="Vizor"
onClick={[Function]}
>
Vizor
</li>
<li
label="XMMS Turquoise "
onClick={[Function]}
>
XMMS Turquoise
</li>
<li
label="Zaxon Remake"
onClick={[Function]}
>
Zaxon Remake
</li>
</ul>
Skins
</li>
<li
className="hr"
>
<hr />
</li>
<li
label="Exit"
onClick={[Function]}
>
Exit
</li>
</ul>
</div>
</div>
<div
className="countdown"

View file

@ -31,8 +31,8 @@ if (new Browser(window).isCompatible) {
</Skin>
<WindowManager>
<MainWindow fileInput={winamp.fileInput} mediaPlayer={winamp.media} />
{playlist ? <PlaylistWindow /> : ""}
{equalizer ? <EqualizerWindow /> : ""}
{playlist && <PlaylistWindow />}
{equalizer && <EqualizerWindow fileInput={winamp.fileInput} />}
</WindowManager>
</div>
</Provider>,

19
js/selectors.js Normal file
View file

@ -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;
};

29
js/selectors.test.js Normal file
View file

@ -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);
});
});

View file

@ -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);

View file

@ -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);
});

View file

@ -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"