Move winamp-eqf into the monorepo (#1054)

* Move winamp-eqf into the monorepo

* [winamp-eqf] Upgrade Jest
This commit is contained in:
Jordan Eldredge 2021-01-01 00:33:44 -08:00 committed by GitHub
parent 7754730aec
commit 2d3f1095a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 761 additions and 5 deletions

View file

@ -0,0 +1,82 @@
# Winamp Equalizer Preset Parser
Winamp allows you to save your equalizser settings to `.eqf` file. This package allows you to parse these files.
## Installation
npm install --save winamp-eqf
## Ussage
import {parser, creator} from 'winamp-eqf';
// ... Get your .eqf or .q1 file as an ArrayBuffer
const eqf = parser(eqfArrayBuffer);
const eqfArrayBuffer = creator(eqf);
## API
### `parser(ArrayBuffer)`
#### Return value
```JavaScript
{
"presets": [
{
"name": "Entry1",
"preamp": 33, // 1-64
"hz60": 64, // 1-64
"hz170": 64, // ...
"hz310": 64,
"hz600": 64,
"hz1000": 64,
"hz3000": 64,
"hz6000": 64,
"hz12000": 64,
"hz14000": 64,
"hz16000": 64,
},
// Some files, such as winamp.q1, may contain multiple preset objects.
],
"type": "Winamp EQ library file v1.1",
}
```
### `creator(eqfObject)`
#### Return Value: `ArrayBuffer`
`eqfObject` is an object with the same shape as that returned by `parser()`.
## Source Material
Starting with this spec found here: <http://www.perlmonks.org/bare/?node_id=584875>:
> I've taken a look at some EQF files that I made for the purpose. The format is apparently very simple:
> The file is 299 bytes long.
>
> It starts with a text header, which in my case, is 37 bytes long. It is, in double-quotish notation — note the control-Z character:
>
> Winamp EQ library file v1.1\cZ!--Entry1
>
> Next is a block of null bytes ("\0") up till the next, final part.
> The real data is stored in the last 11 bytes of the file: the last byte is for the general volume, the 10 bytes before that are for each of the 10 EQ controls, in ascending order: the first of these 10 for the deepest bass, the last one (right in front of the volume byte) is for the highest treble.
> The values are 0x20 in neutral position, and are reversed in value: 0x00 is maximum, 0x3F is minimum. So there are 31 positions below, and 31 32 levels above neutral.
Additionally, I got some info from [Darren Owen](https://twitter.com/The_DoctorO) via Twitter:
<https://twitter.com/The_DoctorO/status/856223002530373632>
> Not that i'm aware off as sadly documentation of things was never great. Looking at the link vs files in a hex editor it seems mostly right.
> The current 1.1 format should be fine as I don't believe the format has changed for a very long time :)
And then via direct message:
> Will do it here as I can type a bit more, but the only obvious thing wrong with the link is the signature assumption as it's not guaranteed to be 'entry1' As you can have multiple eq blocks in a file.
> If you've looked at winamp.q1 you should see multiple presets in that file which follow one after each other so the file signature (winamp.q1 or a specific *.eqf file) is "Winamp EQ library file v1.1\x1A!--" (pulled that out from the disassembler) it's then a 257 byte buffer (256 + null character to terminate correctly) then the 10 byte block relating to the eq sliders (need to double-check the range base) followed by the 1 byte for the preamp slider then if there's more presets in the file, they follow on immediately after with the name block looking at the preamp slider, -12dB = 0x3F, 0dB = 0x1F, 12dB = 0 (so a 0-63 range) that seems to be the same for the other sliders (and matches 1:1 with the sdk details) and I think that's it :) in the winamp.q1 file, the 'default' entry is either a flat preset or what's been saved after customisation (in-case you're wanting to mirror the native behaviour via the preset -> save -> default action)

View file

@ -0,0 +1,443 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`parser can parse max.EQF 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 64,
"hz12000": 64,
"hz14000": 64,
"hz16000": 64,
"hz170": 64,
"hz3000": 64,
"hz310": 64,
"hz60": 64,
"hz600": 64,
"hz6000": 64,
"name": "Entry1",
"preamp": 33,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse midline.EQF 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 33,
"hz12000": 33,
"hz14000": 33,
"hz16000": 33,
"hz170": 33,
"hz3000": 33,
"hz310": 33,
"hz60": 33,
"hz600": 33,
"hz6000": 33,
"name": "Entry1",
"preamp": 33,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse min.EQF 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 1,
"hz12000": 1,
"hz14000": 1,
"hz16000": 1,
"hz170": 1,
"hz3000": 1,
"hz310": 1,
"hz60": 1,
"hz600": 1,
"hz6000": 1,
"name": "Entry1",
"preamp": 33,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse preampMax.EQF 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 33,
"hz12000": 33,
"hz14000": 33,
"hz16000": 33,
"hz170": 33,
"hz3000": 33,
"hz310": 33,
"hz60": 33,
"hz600": 33,
"hz6000": 33,
"name": "Entry1",
"preamp": 64,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse preampMin.EQF 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 33,
"hz12000": 33,
"hz14000": 33,
"hz16000": 33,
"hz170": 33,
"hz3000": 33,
"hz310": 33,
"hz60": 33,
"hz600": 33,
"hz6000": 33,
"name": "Entry1",
"preamp": 1,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse random.EQF 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 33,
"hz12000": 15,
"hz14000": 47,
"hz16000": 33,
"hz170": 35,
"hz3000": 48,
"hz310": 1,
"hz60": 64,
"hz600": 33,
"hz6000": 33,
"name": "Entry1",
"preamp": 55,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse winamp.q1 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 33,
"hz12000": 20,
"hz14000": 20,
"hz16000": 16,
"hz170": 33,
"hz3000": 33,
"hz310": 33,
"hz60": 33,
"hz600": 33,
"hz6000": 20,
"name": "Classical",
"preamp": 33,
},
Object {
"hz1000": 42,
"hz12000": 33,
"hz14000": 33,
"hz16000": 33,
"hz170": 33,
"hz3000": 42,
"hz310": 38,
"hz60": 33,
"hz600": 42,
"hz6000": 38,
"name": "Club",
"preamp": 33,
},
Object {
"hz1000": 32,
"hz12000": 20,
"hz14000": 32,
"hz16000": 32,
"hz170": 44,
"hz3000": 22,
"hz310": 36,
"hz60": 48,
"hz600": 32,
"hz6000": 20,
"name": "Dance",
"preamp": 33,
},
Object {
"hz1000": 28,
"hz12000": 48,
"hz14000": 53,
"hz16000": 56,
"hz170": 50,
"hz3000": 35,
"hz310": 41,
"hz60": 40,
"hz600": 26,
"hz6000": 40,
"name": "Laptop speakers/headphones",
"preamp": 33,
},
Object {
"hz1000": 33,
"hz12000": 24,
"hz14000": 33,
"hz16000": 33,
"hz170": 49,
"hz3000": 24,
"hz310": 42,
"hz60": 49,
"hz600": 42,
"hz6000": 24,
"name": "Large hall",
"preamp": 33,
},
Object {
"hz1000": 33,
"hz12000": 33,
"hz14000": 44,
"hz16000": 44,
"hz170": 44,
"hz3000": 33,
"hz310": 33,
"hz60": 44,
"hz600": 33,
"hz6000": 33,
"name": "Party",
"preamp": 33,
},
Object {
"hz1000": 41,
"hz12000": 28,
"hz14000": 29,
"hz16000": 29,
"hz170": 40,
"hz3000": 30,
"hz310": 44,
"hz60": 29,
"hz600": 45,
"hz6000": 28,
"name": "Pop",
"preamp": 33,
},
Object {
"hz1000": 33,
"hz12000": 33,
"hz14000": 33,
"hz16000": 33,
"hz170": 33,
"hz3000": 43,
"hz310": 31,
"hz60": 33,
"hz600": 22,
"hz6000": 43,
"name": "Reggae",
"preamp": 33,
},
Object {
"hz1000": 26,
"hz12000": 50,
"hz14000": 50,
"hz16000": 50,
"hz170": 40,
"hz3000": 39,
"hz310": 23,
"hz60": 45,
"hz600": 19,
"hz6000": 47,
"name": "Rock",
"preamp": 33,
},
Object {
"hz1000": 30,
"hz12000": 48,
"hz14000": 50,
"hz16000": 52,
"hz170": 35,
"hz3000": 39,
"hz310": 30,
"hz60": 40,
"hz600": 28,
"hz6000": 46,
"name": "Soft",
"preamp": 33,
},
Object {
"hz1000": 39,
"hz12000": 48,
"hz14000": 50,
"hz16000": 48,
"hz170": 24,
"hz3000": 42,
"hz310": 25,
"hz60": 28,
"hz600": 31,
"hz6000": 47,
"name": "Ska",
"preamp": 33,
},
Object {
"hz1000": 35,
"hz12000": 15,
"hz14000": 14,
"hz16000": 14,
"hz170": 48,
"hz3000": 25,
"hz310": 48,
"hz60": 48,
"hz600": 42,
"hz6000": 18,
"name": "Full Bass",
"preamp": 33,
},
Object {
"hz1000": 25,
"hz12000": 31,
"hz14000": 37,
"hz16000": 47,
"hz170": 39,
"hz3000": 23,
"hz310": 36,
"hz60": 39,
"hz600": 31,
"hz6000": 26,
"name": "Soft Rock",
"preamp": 33,
},
Object {
"hz1000": 37,
"hz12000": 58,
"hz14000": 58,
"hz16000": 60,
"hz170": 16,
"hz3000": 50,
"hz310": 16,
"hz60": 16,
"hz600": 25,
"hz6000": 58,
"name": "Full Treble",
"preamp": 33,
},
Object {
"hz1000": 24,
"hz12000": 50,
"hz14000": 52,
"hz16000": 52,
"hz170": 42,
"hz3000": 35,
"hz310": 33,
"hz60": 44,
"hz600": 20,
"hz6000": 46,
"name": "Full Bass & Treble",
"preamp": 33,
},
Object {
"hz1000": 42,
"hz12000": 37,
"hz14000": 37,
"hz16000": 36,
"hz170": 33,
"hz3000": 42,
"hz310": 39,
"hz60": 24,
"hz600": 41,
"hz6000": 39,
"name": "Live",
"preamp": 33,
},
Object {
"hz1000": 24,
"hz12000": 48,
"hz14000": 48,
"hz16000": 47,
"hz170": 42,
"hz3000": 33,
"hz310": 33,
"hz60": 45,
"hz600": 23,
"hz6000": 45,
"name": "Techno",
"preamp": 33,
},
],
"type": "Winamp EQ library file v1.1",
}
`;
exports[`parser can parse winamp_sample.q1 1`] = `
Object {
"presets": Array [
Object {
"hz1000": 33,
"hz12000": 33,
"hz14000": 33,
"hz16000": 33,
"hz170": 33,
"hz3000": 33,
"hz310": 33,
"hz60": 33,
"hz600": 33,
"hz6000": 33,
"name": "Normal",
"preamp": 33,
},
Object {
"hz1000": 15,
"hz12000": 42,
"hz14000": 47,
"hz16000": 52,
"hz170": 47,
"hz3000": 16,
"hz310": 31,
"hz60": 52,
"hz600": 21,
"hz6000": 28,
"name": "Clear",
"preamp": 33,
},
Object {
"hz1000": 18,
"hz12000": 48,
"hz14000": 48,
"hz16000": 42,
"hz170": 28,
"hz3000": 25,
"hz310": 21,
"hz60": 40,
"hz600": 16,
"hz6000": 40,
"name": "Alex",
"preamp": 33,
},
Object {
"hz1000": 26,
"hz12000": 56,
"hz14000": 56,
"hz16000": 51,
"hz170": 46,
"hz3000": 37,
"hz310": 40,
"hz60": 50,
"hz600": 26,
"hz6000": 47,
"name": "Tare",
"preamp": 33,
},
],
"type": "Winamp EQ library file v1.1",
}
`;

View file

@ -0,0 +1,20 @@
var PRESET_VALUES = [
"hz60",
"hz170",
"hz310",
"hz600",
"hz1000",
"hz3000",
"hz6000",
"hz12000",
"hz14000",
"hz16000",
"preamp"
];
var HEADER = "Winamp EQ library file v1.1";
module.exports = {
PRESET_VALUES: PRESET_VALUES,
HEADER: HEADER
};

View file

@ -0,0 +1,35 @@
var CONSTANTS = require("./constants");
var FILL_SIZE = 4;
var PRESET_LENGTH = 257;
function creator(data) {
var buffer = [];
for (var i = 0; i < CONSTANTS.HEADER.length; i++) {
buffer.push(CONSTANTS.HEADER.charCodeAt(i));
}
buffer.push(26); // <ctrl-z>
var ending = "!--";
for (var i = 0; i < ending.length; i++) {
buffer.push(ending.charCodeAt(i));
}
if (!data.presets) {
throw new Error("Eqf data is missing presets");
}
data.presets.forEach(function(preset) {
var k = 0;
for (; k < preset.name.length; k++) {
buffer.push(preset.name.charCodeAt(k));
}
for (; k < PRESET_LENGTH; k++) {
buffer.push(0);
}
CONSTANTS.PRESET_VALUES.forEach(function(valueName) {
buffer.push(64 - preset[valueName]); // Adjust for inverse values
});
});
return new Uint8Array(buffer).buffer;
}
module.exports = creator;

View file

@ -0,0 +1,7 @@
var parser = require("./parser");
var creator = require("./creator");
module.exports = {
parser: parser,
creator: creator
};

View file

@ -0,0 +1,96 @@
const { join } = require("path");
const { readFileSync } = require("fs");
const { parser, creator } = require("./");
var bufferToArrayBuffer = require("buffer-to-arraybuffer");
// TODO: Abstract this into its own library.
expect.extend({
arrayBufferToEqual(received, argument) {
if (received.byteLength !== argument.byteLength) {
return {
message: `ArrayBuffers do not match. Expected length ${received.byteLength} but got ${argument.byteLenth}`,
pass: false
};
}
const a = new Uint8Array(received);
const b = new Uint8Array(argument);
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return {
message: `ArrayBuffers do not match. Expected ${a[i]} to equal ${b[
i
]} at index ${i}`,
pass: false
};
}
}
return {
message: `ArrayBuffers are equal.`,
pass: true
};
}
});
const fixtures = [
// All bands max, preamp mid
"max.EQF",
// All bands min, preamp mid
"min.EQF",
// All bands mid, preamp mid
"midline.EQF",
// All bands mid, preamp max
"preampMax.EQF",
// All bands mid, preamp min
"preampMin.EQF",
"random.EQF",
"winamp_sample.q1",
"winamp.q1"
];
describe("parser", () => {
fixtures.forEach(fileName => {
const buffer = readFileSync(join("sample_data", fileName));
const arrayBuffer = bufferToArrayBuffer(buffer);
it(`can parse ${fileName}`, () => {
const data = parser(arrayBuffer);
expect(data).toMatchSnapshot();
});
});
});
describe("creator", () => {
fixtures.forEach(fileName => {
const buffer = readFileSync(join("sample_data", fileName));
const arrayBuffer = bufferToArrayBuffer(buffer);
const data = parser(arrayBuffer);
it(`can create and parse ${fileName}`, () => {
expect(parser(creator(data))).toEqual(data);
});
});
});
const eqfFixtures = [
// All bands max, preamp mid
"max.EQF",
// All bands min, preamp mid
"min.EQF",
// All bands mid, preamp mid
"midline.EQF",
// All bands mid, preamp max
"preampMax.EQF",
// All bands mid, preamp min
"preampMin.EQF",
"random.EQF"
];
describe("creator", () => {
eqfFixtures.forEach(fileName => {
const buffer = readFileSync(join("sample_data", fileName));
const arrayBuffer = bufferToArrayBuffer(buffer);
const data = parser(arrayBuffer);
it(`can create ${fileName}`, () => {
data.type = "foo";
expect(creator(data)).arrayBufferToEqual(arrayBuffer);
});
});
});

View file

@ -0,0 +1,31 @@
{
"name": "winamp-eqf",
"version": "1.0.0",
"description": "Parse and create Winamp .EQF files which describe equalizer settings",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/captbaritone/webamp.git",
"directory": "packages/winamp-eqf"
},
"bugs": {
"url": "https://github.com/captbaritone/webamp/issues"
},
"homepage": "https://github.com/captbaritone/webamp/tree/master/packages/winamp-eqf",
"scripts": {
"test": "jest",
"tdd": "jest --watch"
},
"keywords": [
"winamp",
"equalizer",
"parse",
"create"
],
"author": "Jordan Eldgredge",
"license": "ISC",
"devDependencies": {
"buffer-to-arraybuffer": "0.0.4",
"jest": "24.9.0"
}
}

View file

@ -0,0 +1,41 @@
var CONSTANTS = require("./constants");
function parser(arrayBuffer) {
var data = {};
var i = 0;
var arr = new Int8Array(arrayBuffer);
// Parse header
data.type = String.fromCharCode.apply(
null,
arr.slice(i, CONSTANTS.HEADER.length)
);
if (data.type !== CONSTANTS.HEADER) {
throw new Error("Invalid .eqf file.");
}
i += CONSTANTS.HEADER.length;
// Skip "<ctrl-z>!--"
i += 4;
// Get the presets
data.presets = [];
while (i < arr.length) {
var preset = {};
// Get the name
var nameStart = i;
var nameEnd = nameStart + 257; // Str is fixed length
// Str is null terminated
while (arr[i] !== 0 && i <= nameEnd) {
i++;
}
preset.name = String.fromCharCode.apply(null, arr.slice(nameStart, i));
i = nameEnd; // Skip over any unused bytes
// Get the levels
CONSTANTS.PRESET_VALUES.forEach(function(valueName) {
preset[valueName] = 64 - arr[i++]; // Adjust for inverse values
});
data.presets.push(preset);
}
return data;
}
module.exports = parser;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -4219,6 +4219,11 @@ buffer-indexof@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
buffer-to-arraybuffer@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.4.tgz#f3e5e3f6f2632c71e7cdebe76ed1718fad421d4c"
integrity sha1-8+Xj9vJjLHHnzevnbtFxj61CHUw=
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@ -9441,7 +9446,7 @@ jest-worker@^26.6.2:
merge-stream "^2.0.0"
supports-color "^7.0.0"
jest@^24.9.0:
jest@24.9.0, jest@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171"
dependencies:
@ -14596,10 +14601,6 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2"
winamp-eqf@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/winamp-eqf/-/winamp-eqf-1.0.0.tgz#0c3702e579a25511d0934b78ea8e6a8b9cbb259a"
winston-transport@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66"