From a6dfec305a53c6a7618b98f43a6b4b89cfb76102 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Sun, 7 Oct 2018 23:40:35 +0200 Subject: Add support for PulseEffects --- Cargo.lock | 24 + Cargo.toml | 2 + res/default-pa-effects-preset.json | 898 +++++++++++++++++++++++++++++++++++++ src/cli.rs | 113 +++-- src/main.rs | 49 +- src/pa_effects.rs | 125 ++++++ src/pa_eq.rs | 110 +++++ src/parsing/equalizer_apo.lalrpop | 8 +- src/parsing/equalizer_apo.rs | 9 +- src/utils.rs | 96 ++-- 10 files changed, 1282 insertions(+), 152 deletions(-) create mode 100644 res/default-pa-effects-preset.json create mode 100644 src/pa_effects.rs create mode 100644 src/pa_eq.rs diff --git a/Cargo.lock b/Cargo.lock index 3e0b2fa..a2d4b2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,11 @@ dependencies = [ "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -552,6 +557,11 @@ name = "rustc-demangle" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ryu" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "serde" version = "1.0.75" @@ -567,6 +577,16 @@ dependencies = [ "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde_json" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "set_eq" version = "0.1.0" @@ -580,6 +600,7 @@ dependencies = [ "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -853,6 +874,7 @@ dependencies = [ "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" "checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" "checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba451f7bd819b7afc99d4cf4bdcd5a4861e64955ba9680ac70df3a50625ad6cf" "checksum lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60013fd6be14317d43f47658b1440956a9ca48a9ed0257e0e0a59aac13e43a1f" @@ -885,8 +907,10 @@ dependencies = [ "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "747ba3b235651f6e2f67dfa8bcdcd073ddb7c243cb21c442fc12395dfcac212d" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum ryu 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7153dd96dade874ab973e098cb62fcdbb89a03682e46b144fd09550998d4a4a7" "checksum serde 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)" = "22d340507cea0b7e6632900a176101fea959c7065d93ba555072da90aaaafc87" "checksum serde_derive 1.0.75 (registry+https://github.com/rust-lang/crates.io-index)" = "234fc8b737737b148ccd625175fc6390f5e4dacfdaa543cb93a3430d984a9119" +"checksum serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "43344e7ce05d0d8280c5940cabb4964bea626aa58b1ec0e8c73fa2a8512a38ce" "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" diff --git a/Cargo.toml b/Cargo.toml index e6093d1..840e065 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,5 @@ failure = "0.1.2" lalrpop-util = "0.15.2" regex = "1.0.4" + +serde_json = "1.0.32" diff --git a/res/default-pa-effects-preset.json b/res/default-pa-effects-preset.json new file mode 100644 index 0000000..0d24279 --- /dev/null +++ b/res/default-pa-effects-preset.json @@ -0,0 +1,898 @@ +{ + "spectrum": { + "show": "true", + "n-points": "150", + "height": "100", + "use-custom-color": "false", + "color": [ + "1", + "1", + "1", + "1" + ] + }, + "input": { + "plugins_order": [ + "gate", + "multiband_gate", + "webrtc", + "limiter", + "compressor", + "multiband_compressor", + "filter", + "equalizer", + "deesser", + "reverb", + "pitch" + ], + "compressor": { + "state": "false", + "detection": "RMS", + "stereo-link": "Average", + "mix": "0", + "attack": "20", + "release": "250", + "threshold": "-18", + "ratio": "2", + "knee": "9", + "makeup": "0" + }, + "deesser": { + "state": "false", + "detection": "RMS", + "mode": "Wide", + "threshold": "-18", + "ratio": "3", + "laxity": "15", + "makeup": "0", + "f1-freq": "6000", + "f2-freq": "4500", + "f1-level": "0", + "f2-level": "12", + "f2-q": "1", + "sc-listen": "false" + }, + "equalizer": { + "state": "false", + "num-bands": "30", + "input-gain": "0", + "output-gain": "0", + "band0": { + "gain": "0", + "frequency": "22.59", + "width": "5.1799999999999997", + "type": "peak" + }, + "band1": { + "gain": "0", + "frequency": "28.440000000000001", + "width": "6.5199999999999996", + "type": "peak" + }, + "band2": { + "gain": "0", + "frequency": "35.799999999999997", + "width": "8.2100000000000009", + "type": "peak" + }, + "band3": { + "gain": "0", + "frequency": "45.07", + "width": "10.33", + "type": "peak" + }, + "band4": { + "gain": "0", + "frequency": "56.740000000000002", + "width": "13", + "type": "peak" + }, + "band5": { + "gain": "0", + "frequency": "71.430000000000007", + "width": "16.379999999999999", + "type": "peak" + }, + "band6": { + "gain": "0", + "frequency": "89.930000000000007", + "width": "20.620000000000001", + "type": "peak" + }, + "band7": { + "gain": "0", + "frequency": "113.20999999999999", + "width": "25.949999999999999", + "type": "peak" + }, + "band8": { + "gain": "0", + "frequency": "142.53", + "width": "32.670000000000002", + "type": "peak" + }, + "band9": { + "gain": "0", + "frequency": "179.43000000000001", + "width": "41.130000000000003", + "type": "peak" + }, + "band10": { + "gain": "0", + "frequency": "225.88999999999999", + "width": "51.789999999999999", + "type": "peak" + }, + "band11": { + "gain": "0", + "frequency": "284.38", + "width": "65.189999999999998", + "type": "peak" + }, + "band12": { + "gain": "0", + "frequency": "358.01999999999998", + "width": "82.069999999999993", + "type": "peak" + }, + "band13": { + "gain": "0", + "frequency": "450.72000000000003", + "width": "103.33", + "type": "peak" + }, + "band14": { + "gain": "0", + "frequency": "567.41999999999996", + "width": "130.08000000000001", + "type": "peak" + }, + "band15": { + "gain": "0", + "frequency": "714.34000000000003", + "width": "163.75999999999999", + "type": "peak" + }, + "band16": { + "gain": "0", + "frequency": "899.28999999999996", + "width": "206.16", + "type": "peak" + }, + "band17": { + "gain": "0", + "frequency": "1132.1500000000001", + "width": "259.54000000000002", + "type": "peak" + }, + "band18": { + "gain": "0", + "frequency": "1425.29", + "width": "326.74000000000001", + "type": "peak" + }, + "band19": { + "gain": "0", + "frequency": "1794.3299999999999", + "width": "411.33999999999997", + "type": "peak" + }, + "band20": { + "gain": "0", + "frequency": "2258.9299999999998", + "width": "517.85000000000002", + "type": "peak" + }, + "band21": { + "gain": "0", + "frequency": "2843.8200000000002", + "width": "651.94000000000005", + "type": "peak" + }, + "band22": { + "gain": "0", + "frequency": "3580.1599999999999", + "width": "820.74000000000001", + "type": "peak" + }, + "band23": { + "gain": "0", + "frequency": "4507.1499999999996", + "width": "1033.25", + "type": "peak" + }, + "band24": { + "gain": "0", + "frequency": "5674.1599999999999", + "width": "1300.78", + "type": "peak" + }, + "band25": { + "gain": "0", + "frequency": "7143.3500000000004", + "width": "1637.5899999999999", + "type": "peak" + }, + "band26": { + "gain": "0", + "frequency": "8992.9400000000005", + "width": "2061.5999999999999", + "type": "peak" + }, + "band27": { + "gain": "0", + "frequency": "11321.450000000001", + "width": "2595.4000000000001", + "type": "peak" + }, + "band28": { + "gain": "0", + "frequency": "14252.860000000001", + "width": "3267.4200000000001", + "type": "peak" + }, + "band29": { + "gain": "0", + "frequency": "17943.279999999999", + "width": "4113.4399999999996", + "type": "peak" + } + }, + "filter": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "frequency": "2000", + "resonance": "-3", + "mode": "12dB\/oct Lowpass", + "inertia": "20" + }, + "gate": { + "state": "false", + "detection": "RMS", + "stereo-link": "Average", + "range": "-24", + "attack": "20", + "release": "250", + "threshold": "-18", + "ratio": "2", + "knee": "9", + "makeup": "0" + }, + "limiter": { + "state": "false", + "input-gain": "0", + "limit": "0", + "lookahead": "5", + "release": "50", + "asc": "false", + "asc-level": "0.5", + "oversampling": "1" + }, + "pitch": { + "state": "false", + "cents": "0", + "semitones": "0", + "octaves": "0", + "crispness": "3", + "formant-preserving": "false", + "faster": "false" + }, + "reverb": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "room-size": "Large", + "decay-time": "1.5", + "hf-damp": "5000", + "diffusion": "0.5", + "amount": "-12", + "dry": "0", + "predelay": "0", + "bass-cut": "300", + "treble-cut": "5000" + }, + "webrtc": { + "state": "false", + "high-pass-filter": "true", + "echo-cancel": "true", + "echo-suppression-level": "moderate", + "noise-suppression": "true", + "noise-suppression-level": "moderate", + "gain-control": "true", + "extended-filter": "true", + "delay-agnostic": "false", + "target-level-dbfs": "3", + "compression-gain-db": "9", + "limiter": "true", + "gain-control-mode": "adaptive-digital", + "voice-detection": "false", + "voice-detection-frame-size-ms": "10", + "voice-detection-likelihood": "low" + }, + "multiband_compressor": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "freq0": "120", + "freq1": "1000", + "freq2": "6000", + "mode": "LR8", + "subband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "lowband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "midband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "highband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + } + }, + "multiband_gate": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "freq0": "120", + "freq1": "1000", + "freq2": "6000", + "mode": "LR8", + "subband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "lowband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "midband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "highband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + } + } + }, + "output": { + "plugins_order": [ + "limiter", + "autogain", + "gate", + "multiband_gate", + "compressor", + "multiband_compressor", + "convolver", + "bass_enhancer", + "exciter", + "crystalizer", + "stereo_tools", + "reverb", + "equalizer", + "deesser", + "crossfeed", + "loudness", + "maximizer", + "filter" + ], + "bass_enhancer": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "amount": "0", + "harmonics": "8.5", + "scope": "100", + "floor": "20", + "blend": "0", + "floor-active": "false", + "listen": "false" + }, + "compressor": { + "state": "false", + "detection": "RMS", + "stereo-link": "Average", + "mix": "0", + "attack": "20", + "release": "250", + "threshold": "-18", + "ratio": "2", + "knee": "9", + "makeup": "0" + }, + "crossfeed": { + "state": "false", + "fcut": "700", + "feed": "4.5" + }, + "deesser": { + "state": "false", + "detection": "RMS", + "mode": "Wide", + "threshold": "-18", + "ratio": "3", + "laxity": "15", + "makeup": "0", + "f1-freq": "6000", + "f2-freq": "4500", + "f1-level": "0", + "f2-level": "12", + "f2-q": "1", + "sc-listen": "false" + }, + "equalizer": { + "state": "false", + "num-bands": "30", + "input-gain": "0", + "output-gain": "0", + "band0": { + "gain": "0", + "frequency": "22.59", + "width": "5.1799999999999997", + "type": "peak" + }, + "band1": { + "gain": "0", + "frequency": "28.440000000000001", + "width": "6.5199999999999996", + "type": "peak" + }, + "band2": { + "gain": "0", + "frequency": "35.799999999999997", + "width": "8.2100000000000009", + "type": "peak" + }, + "band3": { + "gain": "0", + "frequency": "45.07", + "width": "10.33", + "type": "peak" + }, + "band4": { + "gain": "0", + "frequency": "56.740000000000002", + "width": "13", + "type": "peak" + }, + "band5": { + "gain": "0", + "frequency": "71.430000000000007", + "width": "16.379999999999999", + "type": "peak" + }, + "band6": { + "gain": "0", + "frequency": "89.930000000000007", + "width": "20.620000000000001", + "type": "peak" + }, + "band7": { + "gain": "0", + "frequency": "113.20999999999999", + "width": "25.949999999999999", + "type": "peak" + }, + "band8": { + "gain": "0", + "frequency": "142.53", + "width": "32.670000000000002", + "type": "peak" + }, + "band9": { + "gain": "0", + "frequency": "179.43000000000001", + "width": "41.130000000000003", + "type": "peak" + }, + "band10": { + "gain": "0", + "frequency": "225.88999999999999", + "width": "51.789999999999999", + "type": "peak" + }, + "band11": { + "gain": "0", + "frequency": "284.38", + "width": "65.189999999999998", + "type": "peak" + }, + "band12": { + "gain": "0", + "frequency": "358.01999999999998", + "width": "82.069999999999993", + "type": "peak" + }, + "band13": { + "gain": "0", + "frequency": "450.72000000000003", + "width": "103.33", + "type": "peak" + }, + "band14": { + "gain": "0", + "frequency": "567.41999999999996", + "width": "130.08000000000001", + "type": "peak" + }, + "band15": { + "gain": "0", + "frequency": "714.34000000000003", + "width": "163.75999999999999", + "type": "peak" + }, + "band16": { + "gain": "0", + "frequency": "899.28999999999996", + "width": "206.16", + "type": "peak" + }, + "band17": { + "gain": "0", + "frequency": "1132.1500000000001", + "width": "259.54000000000002", + "type": "peak" + }, + "band18": { + "gain": "0", + "frequency": "1425.29", + "width": "326.74000000000001", + "type": "peak" + }, + "band19": { + "gain": "0", + "frequency": "1794.3299999999999", + "width": "411.33999999999997", + "type": "peak" + }, + "band20": { + "gain": "0", + "frequency": "2258.9299999999998", + "width": "517.85000000000002", + "type": "peak" + }, + "band21": { + "gain": "0", + "frequency": "2843.8200000000002", + "width": "651.94000000000005", + "type": "peak" + }, + "band22": { + "gain": "0", + "frequency": "3580.1599999999999", + "width": "820.74000000000001", + "type": "peak" + }, + "band23": { + "gain": "0", + "frequency": "4507.1499999999996", + "width": "1033.25", + "type": "peak" + }, + "band24": { + "gain": "0", + "frequency": "5674.1599999999999", + "width": "1300.78", + "type": "peak" + }, + "band25": { + "gain": "0", + "frequency": "7143.3500000000004", + "width": "1637.5899999999999", + "type": "peak" + }, + "band26": { + "gain": "0", + "frequency": "8992.9400000000005", + "width": "2061.5999999999999", + "type": "peak" + }, + "band27": { + "gain": "0", + "frequency": "11321.450000000001", + "width": "2595.4000000000001", + "type": "peak" + }, + "band28": { + "gain": "0", + "frequency": "14252.860000000001", + "width": "3267.4200000000001", + "type": "peak" + }, + "band29": { + "gain": "0", + "frequency": "17943.279999999999", + "width": "4113.4399999999996", + "type": "peak" + } + }, + "exciter": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "amount": "0", + "harmonics": "8.5", + "scope": "7500", + "ceil": "16000", + "blend": "0", + "ceil-active": "false", + "listen": "false" + }, + "filter": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "frequency": "2000", + "resonance": "-3", + "mode": "12dB\/oct Lowpass", + "inertia": "20" + }, + "gate": { + "state": "false", + "detection": "RMS", + "stereo-link": "Average", + "range": "-24", + "attack": "20", + "release": "250", + "threshold": "-18", + "ratio": "2", + "knee": "9", + "makeup": "0" + }, + "limiter": { + "state": "false", + "input-gain": "0", + "limit": "0", + "lookahead": "5", + "release": "50", + "asc": "false", + "asc-level": "0.5", + "oversampling": "1" + }, + "maximizer": { + "state": "false", + "release": "3.1600000000000001", + "ceiling": "0", + "threshold": "0" + }, + "reverb": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "room-size": "Large", + "decay-time": "1.5", + "hf-damp": "5000", + "diffusion": "0.5", + "amount": "-12", + "dry": "0", + "predelay": "0", + "bass-cut": "300", + "treble-cut": "5000" + }, + "multiband_compressor": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "freq0": "120", + "freq1": "1000", + "freq2": "6000", + "mode": "LR8", + "subband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "lowband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "midband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "highband": { + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + } + }, + "loudness": { + "state": "false", + "loudness": "-3.1000000000000001", + "output": "-6", + "link": "-9.0999999999999996" + }, + "multiband_gate": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "freq0": "120", + "freq1": "1000", + "freq2": "6000", + "mode": "LR8", + "subband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "lowband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "midband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + }, + "highband": { + "reduction": "-24", + "threshold": "-12", + "ratio": "2", + "attack": "150", + "release": "300", + "makeup": "0", + "knee": "9", + "detection": "RMS", + "bypass": "false", + "solo": "false" + } + }, + "stereo_tools": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "balance-in": "0", + "balance-out": "0", + "softclip": "false", + "mutel": "false", + "muter": "false", + "phasel": "false", + "phaser": "false", + "mode": "LR > LR (Stereo Default)", + "side-level": "0", + "side-balance": "0", + "middle-level": "0", + "middle-panorama": "0", + "stereo-base": "0", + "delay": "0", + "sc-level": "1", + "stereo-phase": "0" + }, + "convolver": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "kernel-path": "", + "ir-width": "100" + }, + "crystalizer": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "intensity": "1" + }, + "autogain": { + "state": "false", + "input-gain": "0", + "output-gain": "0", + "target": "-23", + "weight-m": "1", + "weight-s": "1", + "weight-i": "1" + } + } +} diff --git a/src/cli.rs b/src/cli.rs index f24b222..4a499b5 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -21,31 +21,16 @@ pub struct Cli { #[derive(StructOpt, Debug)] pub enum Command { - #[structopt(name = "load",)] - /// Load and switch to a given equalizer configuration - Load(LoadCli), - #[structopt(name = "reset")] - /// Switch to a neutral equalizer - Reset(ResetCli), -} - -#[derive(StructOpt, Debug)] -pub struct LoadCli { - #[structopt(default_value = "-")] - /// The file from which to load the equalizer configuration - /// - /// If "-" is given, read the configuration from the command-line. - pub file: String, - #[structopt( - short = "f", - raw( - possible_values = "&EqualizerConfFormat::variants()", - case_insensitive = "true" - ), - default_value = "EqualizerAPO" - )] - /// The file format of the equalizer configuration - pub format: EqualizerConfFormat, + #[structopt(name = "pa-eq")] + /// PulseAudio equalizer related commands + /// + /// Warning: the PulseAudio equalizer has been deprecated for a while, + /// and is known to sometimes cause crashes, latency or audible + /// artifacts + PaEq(pa_eq::Command), + #[structopt(name = "pa-effects")] + /// PulseEffects equalizer related commands + PaEffects(pa_effects::Command), } arg_enum! { @@ -55,5 +40,79 @@ arg_enum! { } } -#[derive(StructOpt, Debug)] -pub struct ResetCli {} +pub mod pa_eq { + use super::EqualizerConfFormat; + + #[derive(StructOpt, Debug)] + pub enum Command { + #[structopt(name = "load",)] + /// Load and switch to a given equalizer configuration + Load(LoadCli), + #[structopt(name = "reset")] + /// Switch to a neutral equalizer + Reset(ResetCli), + } + + #[derive(StructOpt, Debug)] + pub struct LoadCli { + #[structopt(default_value = "-")] + /// The file from which to load the equalizer configuration + /// + /// If "-" is given, read the configuration from the command-line. + pub file: String, + #[structopt( + short = "f", + raw( + possible_values = "&EqualizerConfFormat::variants()", + case_insensitive = "true" + ), + default_value = "EqualizerAPO" + )] + /// The file format of the equalizer configuration + pub format: EqualizerConfFormat, + } + + #[derive(StructOpt, Debug)] + pub struct ResetCli {} + +} + +pub mod pa_effects { + use super::EqualizerConfFormat; + + #[derive(StructOpt, Debug)] + pub enum Command { + #[structopt(name = "export-preset",)] + /// Export a PulseEffects preset + ExportPreset(ExportPresetCli), + } + + #[derive(StructOpt, Debug)] + pub struct ExportPresetCli { + #[structopt(default_value = "-")] + /// The file from which to load the equalizer configuration + /// + /// If "-" is given, read the configuration from the command-line. + pub file: String, + #[structopt( + short = "f", + raw( + possible_values = "&EqualizerConfFormat::variants()", + case_insensitive = "true" + ), + default_value = "EqualizerAPO" + )] + /// The file format of the equalizer configuration + pub format: EqualizerConfFormat, + #[structopt(short = "p")] + /// Use a given file as a base for PulseEffects preset instead of the + /// default one. + /// + /// If "-" is given, read the base preset from the command-line. + pub base_preset: Option, + #[structopt(short = "o")] + /// Write the preset to the given file, instead of the standard output + pub output: Option, + } + +} diff --git a/src/main.rs b/src/main.rs index 85d2443..a23034e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,21 +14,22 @@ extern crate structopt; extern crate lalrpop_util; +#[macro_use] +extern crate serde_json; + mod cli; mod dbus_api; mod parsing; mod utils; -use utils::*; -use dbus_api::sink::OrgPulseAudioExtEqualizing1Equalizer; +mod pa_eq; +mod pa_effects; + use cli::*; use failure::Error; use structopt::StructOpt; -use std::fs::File; -use std::io; - #[derive(Debug)] pub struct Filter { preamp: f64, @@ -74,41 +75,7 @@ fn start() -> Result<(), Error> { use Command::*; match args.cmd { - Load(args) => load(args), - Reset(args) => reset(args), + PaEq(args) => pa_eq::main(args), + PaEffects(args) => pa_effects::main(args), } } - -fn reset(args: ResetCli) -> Result<(), Error> { - let conn = connect()?; - let conn_sink = get_equalized_sink(&conn)?; - let filter_rate = conn_sink.get_filter_sample_rate()?; - let filter = Filter { - preamp: 1f64, - frequencies: vec![], - coefficients: vec![], - }.pad(filter_rate); - - send_filter(&conn_sink, filter)?; - - Ok(()) -} - -fn load(args: LoadCli) -> Result<(), Error> { - let conn = connect()?; - let conn_sink = get_equalized_sink(&conn)?; - - let filter = if args.file == "-" { - let stdin = io::stdin(); - let mut handle = stdin.lock(); - read_filter(&mut handle)? - } else { - let mut file = File::open(args.file)?; - read_filter(&mut file)? - }; - - let filter_rate = conn_sink.get_filter_sample_rate()?; - send_filter(&conn_sink, filter.pad(filter_rate))?; - - Ok(()) -} diff --git a/src/pa_effects.rs b/src/pa_effects.rs new file mode 100644 index 0000000..9ed0a1f --- /dev/null +++ b/src/pa_effects.rs @@ -0,0 +1,125 @@ +use cli::pa_effects::*; +use utils::*; +use Filter; + +use failure::Error; + +use serde_json; + +const DEFAULT_PRESET: &'static str = include_str!("../res/default-pa-effects-preset.json"); + +pub fn main(cmd: Command) -> Result<(), Error> { + use cli::pa_effects::Command::*; + + match cmd { + ExportPreset(args) => export_preset(args), + } +} + +fn export_preset(args: ExportPresetCli) -> Result<(), Error> { + debug!("Parsing base preset"); + let mut preset: serde_json::Value = match args.base_preset { + Some(file) => serde_json::from_str(&read_filearg_to_str(&file)?), + None => serde_json::from_str(&DEFAULT_PRESET), + }?; + + let filter = read_filter_from_arg(&args.file)?; + + preset["output"]["equalizer"] = filter_to_eq_preset(filter); + + println!("{}", preset); + Ok(()) +} + +fn filter_to_eq_preset(mut filter: Filter) -> serde_json::Value { + if filter.frequencies.len() > 30 { + info!("More than 30 frequencies specified, taking the approximative approach"); + filter = simplify_filter(filter); + } + + let mut equalizer: serde_json::Value = json!({ + "state": "true", + "num-bands": filter.frequencies.len(), + "input-gain": 0, + "output-gain": 0, + }); + + for (i, (frequency, coeff)) in filter + .frequencies + .into_iter() + .zip(filter.coefficients) + .enumerate() + { + equalizer[format!("band{}", i)] = json!({ + "gain": coeff, + "frequency": frequency, + "type": "peak", + }); + } + + equalizer +} + +fn simplify_filter(filter: Filter) -> Filter { + //let partition_size = (filter.frequencies.len() as f64 / 30f64).floor() as usize; + let mut partition_size = filter.frequencies.len() / 30; + let step_error = filter.frequencies.len() as f64 % 30f64; + if step_error != 0f64 { + info!("The approximation will be imperfect"); + partition_size += 1; + } + + //let mut cumulative_error = 0f64; + + let frequencies = filter.frequencies.chunks(partition_size).map(|vec| { + let sum: u32 = vec.iter().sum(); + sum / vec.len() as u32 + }).collect(); + + let coefficients = filter.coefficients.chunks(partition_size).map(|vec| { + let sum: f64 = vec.iter().sum(); + sum / vec.len() as f64 + }).collect(); + + Filter { + preamp: filter.preamp, + frequencies, coefficients + } +} + +/* +trait MultiPartitionnable { + type Item; + + fn multi_partition(self, size: usize, wanted_parts: usize) -> MultiPartition + where + Self: Iterator + Sized, + { + MultiPartition { + iter: self, + size, + wanted_parts, + cumulative_error: 0f64, + } + } +} + +impl MultiPartitionnable for Iterator { + type Item = I; +} + +struct MultiPartition { + iter: I, + size: usize, + wanted_parts: usize, + cumulative_error: f64, +} + +impl Iterator for MultiPartition { + type Item = Vec; + + fn next(&mut self) -> Option { + + } +} +*/ diff --git a/src/pa_eq.rs b/src/pa_eq.rs new file mode 100644 index 0000000..4086c31 --- /dev/null +++ b/src/pa_eq.rs @@ -0,0 +1,110 @@ +use Filter; +use cli::pa_eq::*; +use utils::*; + +use dbus_api::equalizing_manager::OrgPulseAudioExtEqualizing1Manager; +use dbus_api::server_lookup::OrgPulseAudioServerLookup1; +use dbus_api::sink::OrgPulseAudioExtEqualizing1Equalizer; + +use dbus::{BusType, ConnPath, Connection}; + +use failure::{Error, ResultExt}; + +#[derive(Fail, Debug)] +#[fail(display = "No equalized sink found")] +struct NoEqualizedSink; + +pub fn main(cmd: Command) -> Result<(), Error> { + use cli::pa_eq::Command::*; + + warn!("The PulseAudio equalizer has been deprecated for a while, and is known to sometimes cause crashes, latency or audible artifacts"); + + match cmd { + Load(args) => load(args), + Reset(args) => reset(args), + } +} + +pub fn reset(_args: ResetCli) -> Result<(), Error> { + let conn = connect()?; + let conn_sink = get_equalized_sink(&conn)?; + let filter_rate = conn_sink.get_filter_sample_rate()?; + let filter = Filter { + preamp: 1f64, + frequencies: vec![], + coefficients: vec![], + }.pad(filter_rate); + + send_filter(&conn_sink, filter)?; + + Ok(()) +} + +pub fn load(args: LoadCli) -> Result<(), Error> { + let conn = connect()?; + let conn_sink = get_equalized_sink(&conn)?; + + let filter = read_filter_from_arg(&args.file)?; + + let filter_rate = conn_sink.get_filter_sample_rate()?; + send_filter(&conn_sink, filter.pad(filter_rate))?; + + Ok(()) +} + +fn connect() -> Result { + Ok(connect_impl().context( + "Could not connect to PulseAudio's D-Bus socket. Have you loaded the 'module-dbus-protocol' module?" + )?) +} + +fn connect_impl() -> Result { + let pulse_sock_path = get_pulse_dbus_sock()?; + info!("PulseAudio's D-Bus socket path is: {}", pulse_sock_path); + + trace!("Connecting to PulseAudio's D-Bus socket"); + Ok(Connection::open_private(&pulse_sock_path)?) +} + +fn get_equalized_sink<'a>(conn: &'a Connection) -> Result, Error> { + Ok(get_equalized_sink_impl(conn).context( + "Could not find an equalized sink. Have you loaded the 'module-equalizer-sink' module?", + )?) +} + +fn get_equalized_sink_impl<'a>( + conn: &'a Connection, +) -> Result, Error> { + let conn_manager = conn.with_path("org.PulseAudio.Core1", "/org/pulseaudio/equalizing1", 2000); + + // TODO: make that a command-line option + trace!("Getting (one of) the equalized sink(s)"); + let mut sinks = conn_manager.get_equalized_sinks()?; + let sink_path = sinks.pop().ok_or(NoEqualizedSink {})?; + info!("Using equalized sink: {:?}", sink_path.as_cstr()); + + trace!("Connecting to equalized sink"); + Ok(conn.with_path("org.PulseAudio.Core1", sink_path, 2000)) +} + +fn send_filter(conn_sink: &ConnPath<&Connection>, filter: Filter) -> Result<(), Error> { + let channel = conn_sink.get_nchannels()?; + info!("Using channel: {}", channel); + trace!("Sending filter: {:?}", filter); + conn_sink.seed_filter( + channel, + filter.frequencies, + filter.coefficients.into_iter().map(decibel_to_ratio).collect(), + decibel_to_ratio(filter.preamp), + )?; + Ok(()) +} + +fn get_pulse_dbus_sock() -> Result { + trace!("Connecting to the D-Bus' session bus"); + let conn = Connection::get_private(BusType::Session)?; + let conn = conn.with_path("org.PulseAudio1", "/org/pulseaudio/server_lookup1", 2000); + + trace!("Checking PulseAudio's D-Bus socket path"); + Ok(conn.get_address()?) +} diff --git a/src/parsing/equalizer_apo.lalrpop b/src/parsing/equalizer_apo.lalrpop index 39dda67..752aee4 100644 --- a/src/parsing/equalizer_apo.lalrpop +++ b/src/parsing/equalizer_apo.lalrpop @@ -5,12 +5,8 @@ use std::str::FromStr; grammar; pub Main: Filter = { - => { - let coefficients: Vec<_> = eq.1.iter().map(|decibel| 10f64.powf(decibel / 10f64).sqrt()).collect(); - // TODO: add decibel_to_ratio conversion function - let preamp = 10f64.powf(preamp / 10f64); - Filter { preamp, frequencies: eq.0, coefficients } - } + => Filter { preamp, frequencies: eq.0, coefficients: eq.1 } + } Preamp: f64 = { diff --git a/src/parsing/equalizer_apo.rs b/src/parsing/equalizer_apo.rs index a24cf99..b0aa8f8 100644 --- a/src/parsing/equalizer_apo.rs +++ b/src/parsing/equalizer_apo.rs @@ -1,5 +1,5 @@ // auto-generated: "lalrpop 0.15.2" -// sha256: a735e8f9bd5cf5f3aac915d12b24752d366481c3952258e22871eef395f44 +// sha256: 3981cad2c0ee5c1d80c3b7278bb1bc9926ee7b273c2b68423accdd84e43c494 use ::Filter; use std::str::FromStr; #[allow(unused_extern_crates)] @@ -879,12 +879,7 @@ fn __action1< (_, eq, _): (usize, (Vec, Vec), usize), ) -> Filter { - { - let coefficients: Vec<_> = eq.1.iter().map(|decibel| 10f64.powf(decibel / 10f64).sqrt()).collect(); - // TODO: add decibel_to_ratio conversion function - let preamp = 10f64.powf(preamp / 10f64); - Filter { preamp, frequencies: eq.0, coefficients } - } + Filter { preamp, frequencies: eq.0, coefficients: eq.1 } } #[allow(unused_variables)] diff --git a/src/utils.rs b/src/utils.rs index de9ed3c..d75971a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,22 +1,15 @@ -use {EqualizerConfFormat, Filter}; +use Filter; +use cli::EqualizerConfFormat; use parsing::EqualizerApoParser; -use dbus_api::equalizing_manager::OrgPulseAudioExtEqualizing1Manager; -use dbus_api::server_lookup::OrgPulseAudioServerLookup1; -use dbus_api::sink::OrgPulseAudioExtEqualizing1Equalizer; - -use dbus::{BusType, ConnPath, Connection}; -use failure::{Error, ResultExt}; +use failure::Error; use lalrpop_util; use std::fmt; +use std::fs::File; use std::io; -#[derive(Fail, Debug)] -#[fail(display = "No equalized sink found")] -struct NoEqualizedSink; - #[derive(Fail, Debug)] #[fail( display = "Could not parse using the {} format: {}", @@ -28,66 +21,32 @@ struct ParseError { message: String, } -pub fn connect() -> Result { - Ok(connect_impl().context( - "Could not connect to PulseAudio's D-Bus socket. Have you loaded the 'module-dbus-protocol' module?" - )?) -} - -fn connect_impl() -> Result { - let pulse_sock_path = get_pulse_dbus_sock()?; - info!("PulseAudio's D-Bus socket path is: {}", pulse_sock_path); - - trace!("Connecting to PulseAudio's D-Bus socket"); - Ok(Connection::open_private(&pulse_sock_path)?) -} - -pub fn get_equalized_sink<'a>(conn: &'a Connection) -> Result, Error> { - Ok(get_equalized_sink_impl(conn).context( - "Could not find an equalized sink. Have you loaded the 'module-equalizer-sink' module?", - )?) -} - -fn get_equalized_sink_impl<'a>( - conn: &'a Connection, -) -> Result, Error> { - let conn_manager = conn.with_path("org.PulseAudio.Core1", "/org/pulseaudio/equalizing1", 2000); +pub fn read_filearg_to_str(file: &str) -> Result { + use std::io::Read; - // TODO: make that a command-line option - trace!("Getting (one of) the equalized sink(s)"); - let mut sinks = conn_manager.get_equalized_sinks()?; - let sink_path = sinks.pop().ok_or(NoEqualizedSink {})?; - info!("Using equalized sink: {:?}", sink_path.as_cstr()); - - trace!("Connecting to equalized sink"); - Ok(conn.with_path("org.PulseAudio.Core1", sink_path, 2000)) + let mut buffer = String::new(); + if file == "-" { + info!("Reading file from the command line"); + let stdin = io::stdin(); + let mut handle = stdin.lock(); + handle.read_to_string(&mut buffer)?; + } else { + let mut file = File::open(file)?; + file.read_to_string(&mut buffer)?; + } + Ok(buffer) } -pub fn send_filter(conn_sink: &ConnPath<&Connection>, filter: Filter) -> Result<(), Error> { - let channel = conn_sink.get_nchannels()?; - info!("Using channel: {}", channel); - trace!("Sending filter: {:?}", filter); - conn_sink.seed_filter( - channel, - filter.frequencies, - filter.coefficients, - filter.preamp, - )?; - Ok(()) +pub fn read_filter_from_arg(file: &str) -> Result { + debug!("Reading filter from '{}' in the EqualizerAPO format", file); + let content = read_filearg_to_str(file)?; + parse_filter(&content) } -pub fn read_filter(file: &mut T) -> Result -where - T: io::Read, -{ - let mut buffer = String::new(); - - info!("Reading filter in GraphicEQ format from the command line"); - file.read_to_string(&mut buffer)?; - +pub fn parse_filter(content: &str) -> Result { // TODO: lifetime issue when "throwing" parse error let filter = EqualizerApoParser::new() - .parse(&buffer) + .parse(&content) .map_err(|e| convert_parse_error(EqualizerConfFormat::EqualizerAPO, e))?; trace!("Parsed filter: {:?}", filter); @@ -109,13 +68,8 @@ where } } -fn get_pulse_dbus_sock() -> Result { - trace!("Connecting to the D-Bus' session bus"); - let conn = Connection::get_private(BusType::Session)?; - let conn = conn.with_path("org.PulseAudio1", "/org/pulseaudio/server_lookup1", 2000); - - trace!("Checking PulseAudio's D-Bus socket path"); - Ok(conn.get_address()?) +pub fn decibel_to_ratio(decibel: f64) -> f64 { + 10f64.powf(decibel / 10f64).sqrt() } /* -- cgit v1.2.3