diff --git a/.gitignore b/.gitignore index 753c3d9..69d478b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ data_mrs/* .DS_Store /devicePEQ/ +dev.sh +/plugins/devicePEQ diff --git a/Documentation.md b/Documentation.md index 6b9b3c3..c3d275d 100644 --- a/Documentation.md +++ b/Documentation.md @@ -24,9 +24,10 @@ Most parts of the interface are arranged using flexboxes, and rearranged with CSS media queries to detect screen width and aspect ratio. There are three main layouts: -* The desktop layout places the graph window at the top with the selector and manager side by side below it. -* The mobile layout (for narrow screens) stacks everything vertically with the selector above the manager. -* When the screen is very wide relative to its height, the selector and manager are stacked as in the mobile layout but placed right of the graph window. + +- The desktop layout places the graph window at the top with the selector and manager side by side below it. +- The mobile layout (for narrow screens) stacks everything vertically with the selector above the manager. +- When the screen is very wide relative to its height, the selector and manager are stacked as in the mobile layout but placed right of the graph window. If the screen is narrow enough, the toolbar below the graph window will collapse to avoid clutter. The entire toolbar can be shown by clicking @@ -120,6 +121,7 @@ natural spline tends to emphasize little bumps in the data, making it worse even than linear interpolation. Mathematically, a smoothing spline minimizes a weighted sum of: + 1. All the square differences between the original and smoothed values, and 2. The integral of the square of the second derivative of the smoothed function. @@ -324,9 +326,10 @@ cross-section along the ring, like a thick washer. Three modifications are made to this ring in order to account for human perception, or maybe imperfect perceptual uniformity of HCL space, or even unsuitability for lines rather than color fields. -* Hues are shifted so cool colors like blues and greens appear less often, and reds and yellows more often. -* Hues are shifted towards six colors with evenly spaced hues—the primary and secondary colors red, yellow, green, cyan, blue, and purple. -* Chroma and luminance are shifted so that yellows are brighter and bolder, and blues darker. + +- Hues are shifted so cool colors like blues and greens appear less often, and reds and yellows more often. +- Hues are shifted towards six colors with evenly spaced hues—the primary and secondary colors red, yellow, green, cyan, blue, and purple. +- Chroma and luminance are shifted so that yellows are brighter and bolder, and blues darker. Channels are separated from one another primarily by adjusting hue and chroma. Channels with different luminance don't look related. The @@ -355,4 +358,4 @@ it does not and they may overlap. If there is only one label, or if a suitable position for a label can't be found, it's placed at the top left corner. If there is a hidden -baseline curve, its label is placed at the bottom of the graph. +baseline curve, its label is placed at the bottom of the graph. \ No newline at end of file diff --git a/config.js b/config.js index 1c1cfcc..ab7e6ba 100644 --- a/config.js +++ b/config.js @@ -1,18 +1,19 @@ // Configuration options const init_phones = ["BKF"], // Optional. Which graphs to display on initial load. Note: Share URLs will override this set - DIR = "data/", // Directory where graph files are stored +// DIR = "/data/", // Directory where graph files are stored + DIR = "https://squig.link/data/", // Directory where graph files are stored +// num_samples = 3, default_channels = ["L","R"], // Which channels to display. Avoid javascript errors if loading just one channel per phone default_normalization = "dB", // Sets default graph normalization mode. Accepts "dB" or "Hz" default_norm_db = 60, // Sets default dB normalization point default_norm_hz = 500, // Sets default Hz normalization point (500Hz is recommended by IEC) max_channel_imbalance = 5, // Channel imbalance threshold to show ! in the channel selector - alt_layout = true, // Toggle between classic and alt layouts alt_sticky_graph = true, // If active graphs overflows the viewport, does the graph scroll with the page or stick to the viewport? alt_animated = false, // Determines if new graphs are drawn with a 1-second animation, or appear instantly alt_header = true, // Display a configurable header at the top of the alt layout alt_header_new_tab = false, // Clicking alt_header links opens in new tab alt_tutorial = true, // Display a configurable frequency response guide below the graph - alt_augment = false, // Display augment card in phone list, e.g. review sore, shop link + alt_augment = true, // Display augment card in phone list, e.g. review sore, shop link site_url = 'graph.html', // URL of your graph "homepage" share_url = true, // If true, enables shareable URLs watermark_text = "CrinGraph", // Optional. Watermark appears behind graphs @@ -30,7 +31,7 @@ const init_phones = ["BKF"], // Optional. Which graphs to display on targetDashed = false, // If true, makes target curves dashed lines targetColorCustom = false, // If false, targets appear as a random gray value. Can replace with a fixed color value to make all targets the specified color, e.g. "black" targetRestoreLastUsed = false, // Restore user's last-used target settings on load - labelsPosition = "default", // Up to four labels will be grouped in a specified corner. Accepts "top-left," bottom-left," "bottom-right," and "default" + labelsPosition = "bottom-left", // Up to four labels will be grouped in a specified corner. Accepts "top-left," bottom-left," "bottom-right," and "default" stickyLabels = true, // "Sticky" labels analyticsEnabled = true, // Enables Google Analytics 4 measurement of site usage exportableGraphs = true, // Enables export graph button @@ -39,15 +40,41 @@ const init_phones = ["BKF"], // Optional. Which graphs to display on extraEQEnabled = true, // Enable parametic eq function extraEQBands = 10, // Default EQ bands available extraEQBandsMax = 20, // Max EQ bands available - extraToneGeneratorEnabled = true; // Enable tone generator function + extraToneGeneratorEnabled = true, // Enable tone generator function + extraPinkNoiseEnabled = true, // Pink noise through parametric EQ (Equalizer tab) + extraMusicEnabled = true; // Local file music player through parametric EQ (Equalizer tab) // Specify which targets to display const targets = [ - { type:"Neutral", files:["Diffuse Field","Etymotic","Free Field","Innerfidelity ID"] }, + { type:"Neutral", files:["KEMAR DF","Diffuse Field","Etymotic","Free Field","Innerfidelity ID"] }, { type:"Reviewer", files:["Antdroid","Bad Guy","Banbeucmas","Crinacle","Precogvision","Super Review"] }, { type:"Preference", files:["Harman","Rtings","Sonarworks"] } ]; +// Tilt / Preference Adjustments +const + default_y_scale = "40db", // Default Y scale; values: ["20db", "30db", "40db", "50db", "crin"] + default_DF_name = "KEMAR DF", // Default RAW DF name + dfBaseline = true, // If true, DF is used as baseline when custom df tilt is on + default_bass_shelf = 8, // Default Custom DF bass shelf value + default_tilt = -0.8, // Default Custom DF tilt value + default_ear = 0, // Default Custom DF ear gain value + default_treble = 0, + tiltableTargets = [], // Targets that are allowed to be tilted + compTargets = [], // Targets that are allowed to be used for compensation + preference_bounds_name = "Bounds", // Preference bounds file prefix (null to disable) + preference_bounds_dir = "data/pref_bounds/",// Directory containing bounds files + preference_bounds_startup = false; // Show bounds curve on startup + +const harmanFilters = [ + { name: "Harman C1 2024 IE", tilt: -0.9, bass_shelf: 1, ear: 0, treble: 0.5 }, + { name: "Harman C2 2024 IE", tilt: -0.3, bass_shelf: 0.5, ear: -0.2, treble: 1 }, + { name: "Harman C3 2024 IE", tilt: -2.1, bass_shelf: 0, ear: 0, treble: 10 }, + { name: "Harman C4 2024 IE", tilt: -2.1, bass_shelf: 0, ear: 0.5, treble: 3.7 }, + { name: "Harman 2013 OE", tilt: 0, bass_shelf: 4.8, ear: 0, treble: -4.4 }, + { name: "Harman 2015 OE", tilt: 0, bass_shelf: 6.6, ear: 0, treble: -1.4 }, + { name: "Harman 2018 OE", tilt: 0, bass_shelf: 6, ear: -1, treble: -4 }, +]; // ************************************************************* @@ -88,7 +115,8 @@ function tsvParse(fr) { .filter(t => !isNaN(t[0]) && !isNaN(t[1])); } -// Apply stylesheet based layout options above +// Main app uses style-alt (+ theme) only. Legacy classic layout lives in style.css for old +// standalone pages (e.g. graph_hp.html) that link it directly — not loaded here. function setLayout() { function applyStylesheet(styleSheet) { var docHead = document.querySelector("head"), @@ -101,12 +129,8 @@ function setLayout() { docHead.append(linkTag); } - if ( !alt_layout ) { - applyStylesheet("style.css"); - } else { - applyStylesheet("style-alt.css"); - applyStylesheet("style-alt-theme.css"); - } + applyStylesheet("style-alt.css"); + applyStylesheet("style-alt-theme.css"); } setLayout(); @@ -299,5 +323,15 @@ let tutorialDefinitions = [ ] // Configure paths to extraEQ plugins here let extraEQplugins = [ - //'./devicePEQ/plugin.js' // Path to one or more "extraEQ" plugins + '/plugins/devicePEQ/devicePEQ/plugin.js' // Path to one or more "extraEQ" plugins ]; + +let devicePEQConfig = { + advanced: false, // Shows connection-type dropdown instead of the old modal + showLogs: false, + pullValuesOnConnect: true, + connectButtonLabel: "Link Device", // Override default "Connect to device" label + showTitle: false, // Hide the "Device PEQ" heading in this context + showInfoButton: false, // Hide the ℹ️ info button for a cleaner look + showSuccessToasts: true, // Hide "pulled/pushed successfully" toasts (spinner communicates state) +} \ No newline at end of file diff --git a/data/IEF Cal.txt b/data/IEF Cal.txt new file mode 100644 index 0000000..3fe7d39 --- /dev/null +++ b/data/IEF Cal.txt @@ -0,0 +1,495 @@ +* Measurement data measured by REW V5.20.13 +* Source: Trace Arithmetic result A / B +* Format: Trace Arithmetic result A / B +* Dated: Jul 27, 2023 3:43:43 PM +* REW Settings: +* C-weighting compensation: Off +* Target level: 75.0 dB +* Note: ; Trace Arithmetic A over B A = MRS 10dB B = Crin BETA +* Measurement: MRS ∆ +* Smoothing: 1/24 octave +* Frequency Step: 1/48 octave +* Start Frequency: 20.000 Hz +* +* Freq(Hz) SPL(dB) +20.000000 1.407 +20.299999 1.392 +20.600000 1.368 +20.900000 1.342 +21.200001 1.316 +21.400000 1.299 +21.799999 1.266 +22.000000 1.251 +22.400000 1.223 +22.700001 1.202 +23.000000 1.183 +23.299999 1.164 +23.600000 1.147 +24.000000 1.125 +24.299999 1.110 +24.700001 1.091 +25.000000 1.078 +25.400000 1.061 +25.799999 1.046 +26.200001 1.031 +26.500000 1.021 +26.900000 1.009 +27.200001 1.000 +27.600000 0.990 +28.000000 0.980 +28.500000 0.969 +29.000000 0.960 +29.500000 0.951 +30.000000 0.943 +30.400000 0.938 +30.700001 0.935 +31.100000 0.930 +31.500000 0.927 +32.000000 0.923 +32.500000 0.920 +33.000000 0.917 +33.500000 0.915 +34.000000 0.914 +34.500000 0.912 +35.000000 0.911 +35.500000 0.909 +36.000000 0.909 +36.500000 0.908 +37.000000 0.908 +37.500000 0.907 +38.200001 0.905 +38.700001 0.905 +39.500000 0.904 +40.000000 0.903 +40.599998 0.902 +41.200001 0.900 +41.799999 0.899 +42.500000 0.897 +43.099998 0.894 +43.700001 0.892 +44.400002 0.889 +45.000000 0.886 +45.500000 0.884 +46.200001 0.881 +46.799999 0.877 +47.500000 0.873 +48.200001 0.869 +48.700001 0.866 +49.400002 0.861 +50.000000 0.857 +50.799999 0.851 +51.500000 0.846 +52.200001 0.841 +53.000000 0.835 +53.799999 0.829 +54.500000 0.824 +55.200001 0.819 +56.000000 0.812 +57.000000 0.804 +58.000000 0.797 +59.000000 0.788 +60.000000 0.781 +60.799999 0.774 +61.500000 0.770 +62.200001 0.765 +63.000000 0.760 +64.000000 0.753 +65.000000 0.746 +66.000000 0.739 +67.000000 0.732 +68.000000 0.727 +69.000000 0.721 +70.000000 0.715 +71.000000 0.710 +72.000000 0.705 +73.000000 0.700 +74.000000 0.695 +75.000000 0.691 +76.199997 0.686 +77.500000 0.682 +78.800003 0.678 +80.000000 0.675 +81.199997 0.672 +82.500000 0.669 +83.800003 0.667 +85.000000 0.664 +86.199997 0.663 +87.500000 0.661 +88.500000 0.660 +90.000000 0.659 +91.199997 0.659 +92.500000 0.658 +93.800003 0.658 +95.000000 0.658 +96.199997 0.658 +97.500000 0.658 +98.800003 0.659 +100.000000 0.659 +101.500000 0.659 +103.000000 0.661 +104.400002 0.661 +106.000000 0.662 +107.500000 0.662 +109.000000 0.663 +110.599998 0.664 +112.000000 0.664 +113.800003 0.665 +115.000000 0.666 +117.199997 0.667 +118.000000 0.667 +120.599998 0.668 +122.000000 0.668 +124.099998 0.668 +125.000000 0.668 +126.500000 0.668 +128.000000 0.668 +131.500000 0.667 +132.000000 0.667 +134.000000 0.667 +136.000000 0.667 +138.000000 0.665 +140.000000 0.664 +143.000000 0.662 +145.000000 0.661 +147.500000 0.658 +150.000000 0.656 +152.500000 0.654 +155.000000 0.652 +157.500000 0.649 +160.000000 0.646 +162.500000 0.644 +165.000000 0.642 +167.500000 0.638 +170.000000 0.635 +172.500000 0.633 +175.000000 0.630 +177.500000 0.627 +180.000000 0.623 +182.500000 0.620 +185.000000 0.617 +187.500000 0.615 +190.000000 0.612 +192.500000 0.609 +195.000000 0.606 +197.500000 0.603 +200.000000 0.600 +203.000000 0.597 +206.000000 0.594 +209.000000 0.590 +212.000000 0.587 +214.000000 0.584 +218.000000 0.580 +220.000000 0.578 +224.000000 0.573 +227.000000 0.570 +230.000000 0.567 +233.000000 0.564 +236.000000 0.561 +240.000000 0.557 +243.000000 0.554 +247.000000 0.550 +250.000000 0.547 +254.000000 0.542 +258.000000 0.538 +262.000000 0.534 +265.000000 0.530 +269.000000 0.527 +272.000000 0.524 +276.000000 0.519 +280.000000 0.515 +285.000000 0.510 +290.000000 0.505 +295.000000 0.500 +300.000000 0.495 +304.000000 0.490 +307.000000 0.487 +311.000000 0.483 +315.000000 0.478 +320.000000 0.472 +325.000000 0.467 +330.000000 0.461 +335.000000 0.455 +340.000000 0.449 +345.000000 0.443 +350.000000 0.438 +355.000000 0.432 +360.000000 0.425 +365.000000 0.419 +370.000000 0.413 +375.000000 0.407 +382.000000 0.399 +387.000000 0.392 +395.000000 0.381 +400.000000 0.375 +406.000000 0.367 +412.000000 0.359 +418.000000 0.350 +425.000000 0.340 +431.000000 0.331 +437.000000 0.323 +444.000000 0.312 +450.000000 0.303 +455.000000 0.295 +462.000000 0.284 +468.000000 0.274 +475.000000 0.263 +482.000000 0.252 +487.000000 0.243 +494.000000 0.232 +500.000000 0.221 +508.000000 0.207 +515.000000 0.195 +522.000000 0.183 +530.000000 0.169 +538.000000 0.155 +545.000000 0.142 +552.000000 0.130 +560.000000 0.116 +570.000000 0.098 +580.000000 0.082 +590.000000 0.066 +600.000000 0.050 +608.000000 0.039 +615.000000 0.030 +622.000000 0.021 +630.000000 0.012 +640.000000 0.003 +650.000000 -0.004 +660.000000 -0.010 +670.000000 -0.014 +680.000000 -0.017 +690.000000 -0.017 +700.000000 -0.015 +710.000000 -0.011 +720.000000 -0.007 +730.000000 -0.001 +740.000000 0.005 +750.000000 0.011 +762.000000 0.020 +775.000000 0.029 +788.000000 0.039 +800.000000 0.049 +812.000000 0.058 +825.000000 0.069 +838.000000 0.081 +850.000000 0.092 +862.000000 0.103 +875.000000 0.116 +885.000000 0.127 +900.000000 0.144 +912.000000 0.157 +925.000000 0.171 +938.000000 0.183 +950.000000 0.192 +962.000000 0.199 +975.000000 0.202 +988.000000 0.199 +1000.000000 0.191 +1015.000000 0.173 +1030.000000 0.145 +1044.000000 0.112 +1060.000000 0.065 +1075.000000 0.013 +1090.000000 -0.044 +1106.000000 -0.109 +1120.000000 -0.169 +1138.000000 -0.248 +1150.000000 -0.302 +1172.000000 -0.400 +1180.000000 -0.435 +1206.000000 -0.546 +1220.000000 -0.604 +1241.000000 -0.685 +1250.000000 -0.718 +1265.000000 -0.769 +1280.000000 -0.817 +1315.000000 -0.913 +1320.000000 -0.925 +1340.000000 -0.970 +1360.000000 -1.009 +1380.000000 -1.041 +1400.000000 -1.066 +1430.000000 -1.097 +1450.000000 -1.113 +1475.000000 -1.128 +1500.000000 -1.139 +1525.000000 -1.147 +1550.000000 -1.153 +1575.000000 -1.158 +1600.000000 -1.164 +1625.000000 -1.171 +1650.000000 -1.180 +1675.000000 -1.191 +1700.000000 -1.204 +1725.000000 -1.219 +1750.000000 -1.236 +1775.000000 -1.256 +1800.000000 -1.278 +1825.000000 -1.302 +1850.000000 -1.327 +1875.000000 -1.354 +1900.000000 -1.382 +1925.000000 -1.411 +1950.000000 -1.441 +1975.000000 -1.473 +2000.000000 -1.505 +2030.000000 -1.543 +2060.000000 -1.580 +2090.000000 -1.615 +2120.000000 -1.648 +2140.000000 -1.668 +2180.000000 -1.705 +2200.000000 -1.720 +2240.000000 -1.745 +2270.000000 -1.760 +2300.000000 -1.770 +2330.000000 -1.778 +2360.000000 -1.784 +2400.000000 -1.787 +2430.000000 -1.787 +2470.000000 -1.785 +2500.000000 -1.783 +2540.000000 -1.778 +2580.000000 -1.772 +2620.000000 -1.764 +2650.000000 -1.756 +2690.000000 -1.744 +2720.000000 -1.733 +2760.000000 -1.716 +2800.000000 -1.696 +2850.000000 -1.666 +2900.000000 -1.630 +2950.000000 -1.589 +3000.000000 -1.542 +3040.000000 -1.500 +3070.000000 -1.466 +3110.000000 -1.418 +3150.000000 -1.367 +3200.000000 -1.301 +3250.000000 -1.231 +3300.000000 -1.160 +3350.000000 -1.087 +3400.000000 -1.011 +3450.000000 -0.935 +3500.000000 -0.858 +3550.000000 -0.780 +3600.000000 -0.703 +3650.000000 -0.624 +3700.000000 -0.546 +3750.000000 -0.469 +3820.000000 -0.362 +3870.000000 -0.287 +3950.000000 -0.171 +4000.000000 -0.103 +4060.000000 -0.024 +4120.000000 0.048 +4180.000000 0.113 +4250.000000 0.176 +4310.000000 0.219 +4370.000000 0.251 +4440.000000 0.273 +4500.000000 0.279 +4550.000000 0.276 +4620.000000 0.263 +4680.000000 0.247 +4750.000000 0.226 +4820.000000 0.209 +4870.000000 0.202 +4940.000000 0.199 +5000.000000 0.206 +5080.000000 0.228 +5150.000000 0.258 +5220.000000 0.296 +5300.000000 0.347 +5380.000000 0.404 +5450.000000 0.456 +5520.000000 0.508 +5600.000000 0.566 +5700.000000 0.634 +5800.000000 0.694 +5900.000000 0.746 +6000.000000 0.788 +6080.000000 0.815 +6150.000000 0.832 +6220.000000 0.844 +6300.000000 0.853 +6400.000000 0.855 +6500.000000 0.849 +6600.000000 0.835 +6700.000000 0.814 +6800.000000 0.788 +6900.000000 0.759 +7000.000000 0.728 +7100.000000 0.699 +7200.000000 0.673 +7300.000000 0.653 +7400.000000 0.640 +7500.000000 0.638 +7620.000000 0.652 +7750.000000 0.690 +7880.000000 0.755 +8000.000000 0.842 +8120.000000 0.956 +8250.000000 1.109 +8380.000000 1.291 +8500.000000 1.484 +8620.000000 1.696 +8750.000000 1.943 +8850.000000 2.140 +9000.000000 2.435 +9120.000000 2.658 +9250.000000 2.875 +9380.000000 3.059 +9500.000000 3.195 +9620.000000 3.298 +9750.000000 3.374 +9880.000000 3.414 +10000.000000 3.417 +10150.000000 3.375 +10300.000000 3.281 +10440.000000 3.160 +10600.000000 3.038 +10750.000000 3.019 +10900.000000 3.142 +11060.000000 3.394 +11200.000000 3.635 +11380.000000 3.888 +11500.000000 4.000 +11720.000000 4.110 +11800.000000 4.118 +12060.000000 4.029 +12200.000000 3.886 +12410.000000 3.542 +12500.000000 3.363 +12650.000000 3.041 +12800.000000 2.714 +13150.000000 2.007 +13200.000000 1.917 +13400.000000 1.602 +13600.000000 1.362 +13800.000000 1.196 +14000.000000 1.105 +14300.000000 1.086 +14500.000000 1.131 +14750.000000 1.221 +15000.000000 1.307 +15250.000000 1.351 +15500.000000 1.331 +15750.000000 1.234 +16000.000000 1.065 +16250.000000 0.840 +16500.000000 0.578 +16750.000000 0.296 +17000.000000 0.009 +17250.000000 -0.274 +17500.000000 -0.541 +17750.000000 -0.777 +18000.000000 -0.968 +18250.000000 -1.095 +18500.000000 -1.142 +18750.000000 -1.103 +19000.000000 -0.984 +19250.000000 -0.813 +19500.000000 -0.689 +19750.000000 -0.350 +20000.000000 -0.350 diff --git a/data/phone_book.json b/data/phone_book.json index dd76dc9..b0055fa 100644 --- a/data/phone_book.json +++ b/data/phone_book.json @@ -15,20 +15,6 @@ "reviewLink":"https://www.youtube.com/", "shopLink":"https://www.amazon.com/", "price":"¥100" - }, - {"name":["Variations 2"], - "file":["Combo Variant 1", - "Combo Variant 2", - "Combo Variant 3", - "Combo Variant 4"], - "suffix":["var1", - "var2", - "var3", - "var4"], - "reviewScore":"A+", - "reviewLink":"https://www.head-fi.org/forums/", - "shopLink":"https://www.aliexpress.com/", - "price":"$100" } ] }, diff --git a/equalizer-constraints.json b/equalizer-constraints.json new file mode 100644 index 0000000..d0901b1 --- /dev/null +++ b/equalizer-constraints.json @@ -0,0 +1,93 @@ +{ + "presets": [ + { + "id": "default", + "label": "Default", + "twoChannelSupport": false, + "maxBands": 0, + "allowPk": true, + "allowLsq": true, + "allowHsq": true, + "freqMin": "20", + "freqMax": "20000", + "gainMin": "0", + "gainMax": "0", + "qMin": "0.1", + "qMax": "10" + }, + { + "id": "auto-eq", + "kind": "system", + "label": "Auto EQ", + "twoChannelSupport": false, + "maxBands": 0, + "allowPk": true, + "allowLsq": true, + "allowHsq": true, + "freqMin": "20", + "freqMax": "6000", + "gainMin": "0", + "gainMax": "0", + "qMin": "0.1", + "qMax": "10" + }, + { + "id": "generic-10-band", + "label": "Generic 10-band graphic EQ", + "twoChannelSupport": false, + "maxBands": 10, + "allowPk": true, + "allowLsq": false, + "allowHsq": false, + "freqGraphicList": "31, 62, 125, 250, 500, 1000, 2000, 4000, 8000, 16000", + "gainMin": "-10", + "gainMax": "10", + "qMin": "0", + "qMax": "1.4" + }, + { + "id": "fiio-eh11", + "label": "Fiio EH11", + "twoChannelSupport": false, + "maxBands": 10, + "allowPk": true, + "allowLsq": true, + "allowHsq": true, + "freqMin": "20", + "freqMax": "20000", + "gainMin": "-12", + "gainMax": "12", + "qMin": "0.25", + "qMax": "8" + }, + { + "id": "sennheiser-hdb-630", + "label": "Sennheiser HDB 630", + "twoChannelSupport": false, + "maxBands": 5, + "allowPk": true, + "allowLsq": true, + "allowHsq": true, + "freqMin": "20", + "freqMax": "20000", + "gainMin": "-6", + "gainMax": "6", + "qMin": "0.25", + "qMax": "8" + }, + { + "id": "sony-wh-1000xm6", + "label": "Sony WH-1000XM6", + "twoChannelSupport": false, + "maxBands": 10, + "allowPk": true, + "allowLsq": false, + "allowHsq": false, + "freqGraphicList": "31, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000", + "gainMin": "-6", + "gainMax": "6", + "qMin": "0", + "qMax": "1" + } + ] +} diff --git a/equalizer.js b/equalizer.js deleted file mode 100644 index a306b4e..0000000 --- a/equalizer.js +++ /dev/null @@ -1,408 +0,0 @@ -/* -Biquad algorithms are taken from: -https://github.com/jaakkopasanen/AutoEq/blob/master/biquad.py -https://github.com/mohayonao/biquad-coeffs/tree/master/packages/biquad-coeffs-cookbook -*/ - -Equalizer = (function() { - let config = { - // Change sample rate will affect the curve of filters close to nyquist frequency - // Here I choosed a common used value, but not all DSP software use this sample rate for EQ - DefaultSampleRate: 48000, - // AutoEQ will avoid filters above this frequency at first batch - TrebleStartFrom: 7000, - // Avoid filters close to nyquist frequency by default, because the behavior is implementation dependent - // https://github.com/jaakkopasanen/AutoEq/issues/240 - // https://github.com/jaakkopasanen/AutoEq/issues/411 - AutoEQRange: [20, 15000], - // Minimum and maximum Q for AutoEQ feature - OptimizeQRange: [0.5, 2], - // Minimum and maximum Gain for AutoEQ feature - OptimizeGainRange: [-12, 12], - // Delta and step of Freq, Q and Gain used for AutoEQ optimizing - OptimizeDeltas: [ - [10, 10, 10, 5, 0.1, 0.5], - [10, 10, 10, 2, 0.1, 0.2], - [10, 10, 10, 1, 0.1, 0.1], - ], - // Use to get response diff by EQ before smoothing - GraphicEQRawFrequences: ( // ~= 1/96 octave - new Array(Math.ceil(Math.log(20000 / 20) / Math.log(1.0072))).fill(null) - .map((_, i) => 20 * Math.pow(1.0072, i))), - // Smoothed 127 bands frequencies for graphic eq (wavelet) - GraphicEQFrequences: Array.from(new Set( - new Array(Math.ceil(Math.log(20000 / 20) / Math.log(1.0563))).fill(null) - .map((_, i) => Math.floor(20 * Math.pow(1.0563, i))))).sort((a, b) => a - b) - }; - - let interp = function (fv, fr) { - let i = 0; - return fv.map(f => { - for (; i < fr.length-1; ++i) { - let [f0, v0] = fr[i]; - let [f1, v1] = fr[i+1]; - if (i == 0 && f < f0) { - return [f, v0]; - } else if (f >= f0 && f < f1) { - let v = v0 + (v1 - v0) * (f - f0) / (f1 - f0); - return [f, v]; - } - } - return [f, fr[fr.length-1][1]]; - }); - }; - - let lowshelf = function (freq, q, gain, sampleRate) { - freq = freq / (sampleRate || config.DefaultSampleRate); - freq = Math.max(1e-6, Math.min(freq, 1)); - q = Math.max(1e-4, Math.min(q, 1000)); - gain = Math.max(-40, Math.min(gain, 40)); - - let w0 = 2 * Math.PI * freq; - let sin = Math.sin(w0); - let cos = Math.cos(w0); - let a = Math.pow(10, (gain / 40)); - let alpha = sin / (2 * q); - let alphamod = (2 * Math.sqrt(a) * alpha) || 0; - - let a0 = ((a+1) + (a-1) * cos + alphamod); - let a1 = -2 * ((a-1) + (a+1) * cos ); - let a2 = ((a+1) + (a-1) * cos - alphamod); - let b0 = a * ((a+1) - (a-1) * cos + alphamod); - let b1 = 2 * a * ((a-1) - (a+1) * cos ); - let b2 = a * ((a+1) - (a-1) * cos - alphamod); - - return [ 1.0, a1/a0, a2/a0, b0/a0, b1/a0, b2/a0 ]; - }; - - let highshelf = function (freq, q, gain, sampleRate) { - freq = freq / (sampleRate || config.DefaultSampleRate); - freq = Math.max(1e-6, Math.min(freq, 1)); - q = Math.max(1e-4, Math.min(q, 1000)); - gain = Math.max(-40, Math.min(gain, 40)); - - let w0 = 2 * Math.PI * freq; - let sin = Math.sin(w0); - let cos = Math.cos(w0); - let a = Math.pow(10, (gain / 40)); - let alpha = sin / (2 * q); - let alphamod = (2 * Math.sqrt(a) * alpha) || 0; - - let a0 = ((a+1) - (a-1) * cos + alphamod); - let a1 = 2 * ((a-1) - (a+1) * cos ); - let a2 = ((a+1) - (a-1) * cos - alphamod); - let b0 = a * ((a+1) + (a-1) * cos + alphamod); - let b1 = -2 * a * ((a-1) + (a+1) * cos ); - let b2 = a * ((a+1) + (a-1) * cos - alphamod); - - return [ 1.0, a1/a0, a2/a0, b0/a0, b1/a0, b2/a0 ]; - }; - - let peaking = function (freq, q, gain, sampleRate) { - freq = freq / (sampleRate || config.DefaultSampleRate); - freq = Math.max(1e-6, Math.min(freq, 1)); - q = Math.max(1e-4, Math.min(q, 1000)); - gain = Math.max(-40, Math.min(gain, 40)); - - let w0 = 2 * Math.PI * freq; - let sin = Math.sin(w0); - let cos = Math.cos(w0); - let a = Math.pow(10, (gain / 40)); - let alpha = sin / (2 * q); - - let a0 = 1 + alpha / a; - let a1 = -2 * cos; - let a2 = 1 - alpha / a; - let b0 = 1 + alpha * a; - let b1 = -2 * cos; - let b2 = 1 - alpha * a; - - return [ 1.0, a1/a0, a2/a0, b0/a0, b1/a0, b2/a0 ]; - }; - - let calc_gains = function (freqs, coeffs, sampleRate) { - sampleRate = sampleRate || config.DefaultSampleRate; - let gains = new Array(freqs.length).fill(0); - - for (let i = 0; i < coeffs.length; ++i) { - let [ a0, a1, a2, b0, b1, b2] = coeffs[i]; - for (let j = 0; j < freqs.length; ++j) { - let w = 2 * Math.PI * freqs[j] / sampleRate; - let phi = 4 * Math.pow(Math.sin(w / 2), 2); - let c = ( - 10 * Math.log10(Math.pow(b0 + b1 + b2, 2) + - (b0 * b2 * phi - (b1 * (b0 + b2) + 4 * b0 * b2)) * phi) - - 10 * Math.log10(Math.pow(a0 + a1 + a2, 2) + - (a0 * a2 * phi - (a1 * (a0 + a2) + 4 * a0 * a2)) * phi)); - gains[j] += c; - } - } - return gains; - }; - - let calc_preamp = function (fr1, fr2) { - let maxGain = -Infinity; - for (let i = 0; i < fr1.length; ++i) { - maxGain = Math.max(maxGain, fr2[i][1] - fr1[i][1]); - } - return -maxGain; - }; - - let calc_distance = function (fr1, fr2) { - let distance = 0; - for (let i = 0; i < fr1.length; ++i) { - let d = Math.abs(fr1[i][1] - fr2[i][1]); - distance += (d >= 0.1 ? d : 0); - } - return distance / fr1.length; - }; - - let filters_to_coeffs = function (filters, sampleRate) { - return filters.map(f => { - if (!f.freq || !f.gain || !f.q) { - return null; - } else if (f.type === "LSQ") { - return lowshelf(f.freq, f.q, f.gain, sampleRate); - } else if (f.type === "HSQ") { - return highshelf(f.freq, f.q, f.gain, sampleRate); - } else if (f.type === "PK") { - return peaking(f.freq, f.q, f.gain, sampleRate); - } - return null; - }).filter(f => f); - }; - - let apply = function (fr, filters, sampleRate) { - let freqs = new Array(fr.length).fill(null); - for (let i = 0; i < fr.length; ++i) { - freqs[i] = fr[i][0]; - } - let coeffs = filters_to_coeffs(filters, sampleRate); - let gains = calc_gains(freqs, coeffs, sampleRate); - let fr_eq = new Array(fr.length).fill(null); - for (let i = 0; i < fr.length; ++i) { - fr_eq[i] = [fr[i][0], fr[i][1] + gains[i]]; - } - return fr_eq; - }; - - let as_graphic_eq = function (filters, sampleRate) { - let rawFS = config.GraphicEQRawFrequences, fs = config.GraphicEQFrequences; - let coeffs = filters_to_coeffs(filters, sampleRate); - let gains = calc_gains(rawFS, coeffs, sampleRate); - let rawFR = rawFS.map((f, i) => [f, gains[i]]); - // Interpolate and smoothing with moving average - let i = 0; - let resultFR = fs.map((f, j) => { - let freqTo = (j < fs.length-1) ? Math.sqrt(f * fs[j+1]) : 20000; - let points = []; - for (; i < rawFS.length; ++i) { - if (rawFS[i] < freqTo) { - points.push(rawFR[i][1]); - } else { - break - } - } - let avg = points.reduce((a, b) => a + b, 0) / points.length; - return [f, avg]; - }); - // Normalize (apply preamp) - let maxGain = resultFR.reduce((a, b) => a > b[1] ? a : b[1], -Infinity); - resultFR = resultFR.map(([f, v]) => [f, v-maxGain]); - return resultFR; - }; - - let search_candidates = function (fr, frTarget, threshold) { - let state = 0; // 1: peak, 0: matched, -1: dip - let startIndex = -1; - let candidates = []; - let [minFreq, maxFreq] = config.AutoEQRange; - for (let i = 0; i < fr.length; ++i) { - let [f, v0] = fr[i]; - let v1 = frTarget[i][1]; - let delta = v0 - v1; - let deltaAbs = Math.abs(delta); - let nextState = (deltaAbs < threshold) ? 0 : (delta / deltaAbs); - if (nextState === state) { - continue; - } - if (startIndex >= 0) { - if (state != 0) { - let start = fr[startIndex][0]; - let end = f; - let center = Math.sqrt(start * end); - let gain = ( - interp([center], frTarget.slice(startIndex, i))[0][1] - - interp([center], fr.slice(startIndex, i))[0][1]); - let q = center / (end - start); - if (center >= minFreq && center <= maxFreq) { - candidates.push({ type: "PK", freq: center, q, gain }); - } - } - startIndex = -1; - } else { - startIndex = i; - } - state = nextState; - } - return candidates; - }; - - let freq_unit = function (freq) { - if (freq < 100) { - return 1; - } else if (freq < 1000) { - return 10; - } else if (freq < 10000) { - return 100; - } - return 1000; - }; - - let strip = function (filters) { - // Make freq, q and gain look better and more compatible to some DSP device - let [minQ, maxQ] = config.OptimizeQRange; - let [minGain, maxGain] = config.OptimizeGainRange; - return filters.map(f => ({ - type: f.type, - freq: Math.floor(f.freq - f.freq % freq_unit(f.freq)), - q: Math.min(Math.max(Math.floor(f.q * 10) / 10, minQ), maxQ), - gain: Math.min(Math.max(Math.floor(f.gain * 10) / 10, minGain), maxGain) - })); - }; - - let optimize = function (fr, frTarget, filters, iteration, dir) { - filters = strip(filters); - let combinations = []; - let [minFreq, maxFreq] = config.AutoEQRange; - let [minQ, maxQ] = config.OptimizeQRange; - let [minGain, maxGain] = config.OptimizeGainRange; - let [maxDF, maxDQ, maxDG, stepDF, stepDQ, stepDG] = ( - config.OptimizeDeltas[iteration]); - let [begin, end, step] = (dir ? - [filters.length-1, -1, -1] : [0, filters.length, 1]); - // Optimize freq, q, gain - for (let i = begin; i != end; i += step) { - let f = filters[i]; - let fr1 = apply(fr, filters.filter((f, fi) => fi !== i)); - let fr2 = apply(fr1, [f]); - let fr3 = apply(fr, filters); - let bestFilter = f; - let bestDistance = calc_distance(fr2, frTarget); - let testNewFilter = (df, dq, dg) => { - let freq = f.freq + df * freq_unit(f.freq) * stepDF; - let q = f.q + dq * stepDQ; - let gain = f.gain + dg * stepDG; - if (freq < minFreq || freq > maxFreq || q < minQ || - q > maxQ || gain < minGain || gain > maxGain) { - return false; - } - let newFilter = { type: f.type, freq, q, gain }; - let newFR = apply(fr1, [newFilter]); - let newDistance = calc_distance(newFR, frTarget); - if (newDistance < bestDistance) { - bestFilter = newFilter; - bestDistance = newDistance; - return true; - } - return false; - } - for (let df = -maxDF; df < maxDF; ++df) { - // Use smaller Q as possible - for (let dq = maxDQ-1; dq >= -maxDQ; --dq) { - for (let dg = 1; dg < maxDG; ++dg) { - if (!testNewFilter(df, dq, dg)) { - break; - } - } - for (let dg = -1; dg >= -maxDG; --dg) { - if (!testNewFilter(df, dq, dg)) { - break; - } - } - } - } - filters[i] = bestFilter; - } - if (!dir) { - return optimize(fr, frTarget, filters, iteration, 1); - } else { - filters = filters.sort((a, b) => a.freq - b.freq); - // Merge closed filters - for (let i = 0; i < filters.length-1;) { - let f1 = filters[i]; - let f2 = filters[i+1]; - if (Math.abs(f1.freq - f2.freq) <= freq_unit(f1.freq) && - Math.abs(f1.q - f2.q) <= 0.1) { - f1.gain += f2.gain; - filters.splice(i+1, 1); - } else { - ++i; - } - } - // Remove unnecessary filters - let bestDistance = calc_distance(apply(fr, filters), frTarget); - for (let i = 0; i < filters.length;) { - if (Math.abs(filters[i].gain) <= 0.1) { - filters.splice(i, 1); - continue; - } - let newDistance = calc_distance(apply(fr, - filters.filter((f, fi) => fi !== i)), frTarget); - if (newDistance < bestDistance) { - filters.splice(i, 1); - bestDistance = newDistance; - } else { - ++i; - } - } - return filters; - } - }; - - let autoeq = function (fr, frTarget, maxFilters) { - // 2 steps manual optimized algorithm - // fr, frTarget should has same resolution and normalized - let firstBatchSize = Math.max(Math.floor(maxFilters / 2) - 1, 1); - let firstCandidates = search_candidates(fr, frTarget, 1); - let firstFilters = (firstCandidates - // Dont adjust treble in the first batch - .filter(c => c.freq <= config.TrebleStartFrom) - // Wider bandwidth (smaller Q) come first - .sort((a, b) => a.q - b.q) - .slice(0, firstBatchSize) - .sort((a, b) => a.freq - b.freq)); - for (let i = 0; i < config.OptimizeDeltas.length; ++i) { - firstFilters = optimize(fr, frTarget, firstFilters, i); - } - let secondFR = apply(fr, firstFilters); - let secondBatchSize = maxFilters - firstFilters.length; - let secondCandidates = search_candidates(secondFR, frTarget, 0.5); - let secondFilters = (secondCandidates - .sort((a, b) => a.q - b.q) - .slice(0, secondBatchSize) - .sort((a, b) => a.freq - b.freq)); - for (let i = 0; i < config.OptimizeDeltas.length; ++i) { - secondFilters = optimize(secondFR, frTarget, secondFilters, i); - } - let allFilters = firstFilters.concat(secondFilters); - for (let i = 0; i < config.OptimizeDeltas.length; ++i) { - allFilters = optimize(fr, frTarget, allFilters, i); - } - return strip(allFilters); - }; - - return { - config, - interp, - lowshelf, - highshelf, - peaking, - calc_gains, - calc_preamp, - apply, - as_graphic_eq, - autoeq - } -})(); - diff --git a/graph_free.html b/graph_free.html index af9a2f5..e350d74 100644 --- a/graph_free.html +++ b/graph_free.html @@ -18,8 +18,13 @@ const disallow_target = true; const premium_html = "
To use target curves, or more than two graphs, upgrade to Patreon Silver tier and switch to the premium tool.
"; - - + + + + + + +