Skip to content

Commit

Permalink
Merge pull request #183 from eshaz/hifi-decode-spectral-nr
Browse files Browse the repository at this point in the history
hifi-decode: Better resampling, Spectral Noise Reduction, Multi processing
  • Loading branch information
harrypm authored Jan 30, 2025
2 parents 0bc2b9a + 61296da commit ce92544
Show file tree
Hide file tree
Showing 7 changed files with 611 additions and 348 deletions.
1 change: 0 additions & 1 deletion filter_tune/filter_tune.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
from matplotlib.figure import Figure

from vhsdecode.utils import filtfft
from vhsdecode.addons.FMdeemph import gen_high_shelf
from vhsdecode.formats import get_format_params
from vhsdecode import compute_video_filters
from vhsdecode.main import supported_tape_formats
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ classifiers = [
"Topic :: Multimedia :: Video",
]
dependencies = [
"numpy>=1.22","numba>=0.58.1","scipy>=1.10.1","samplerate", "static-ffmpeg", "cython", "soundfile", "sounddevice", "importlib_resources >= 5.0; python_version < '3.10'", "matplotlib"
"numpy>=1.22","numba>=0.58.1","scipy>=1.10.1","samplerate", "static-ffmpeg", "cython", "soundfile", "sounddevice", "importlib_resources >= 5.0; python_version < '3.10'", "matplotlib", "noisereduce>=2.0.0"
]
dynamic = ["version"]
# version = "0.2.1dev0"
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ samplerate
scipy >=1.10,<=1.12
soundfile
sounddevice
noisereduce
101 changes: 56 additions & 45 deletions vhsdecode/addons/FMdeemph.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,58 +10,63 @@


@njit(cache=True)
def gen_high_shelf(f0, dbgain, qfactor, fs):
"""Generate high shelving filter coeficcients (digital).
f0: The center frequency where the gain in decibel is at half the maximum value.
Normalized to sampling frequency, i.e output will be filter from 0 to 2pi.
dbgain: gain at the top of the shelf in decibels
qfactor: determines shape of filter TODO: Document better
fs: sampling frequency
TODO: Generate based on -3db
Based on: https://www.w3.org/2011/audio/audio-eq-cookbook.html
"""
a = 10 ** (dbgain / 40.0)
w0 = 2 * math.pi * (f0 / fs)
alpha = math.sin(w0) / (2 * qfactor)

cosw0 = math.cos(w0)
asquared = math.sqrt(a)
def gen_shelf(f0, dbgain, type, fs, qfactor=None, bandwidth=None, slope=None):
"""Generate shelving filter coefficients (digital).
* f0:
The center frequency where the gain in decibel is at half the maximum value.
Normalized to sampling frequency, i.e output will be filter from 0 to 2pi.
* dbgain:
gain at the top of the shelf in decibels
* fs:
sampling frequency
* type:
"high" for high shelf, "low" for low shelf
and one of the following:
* qfactor:
determines shape of filter TODO: Document better
* bandwidth:
bandwidth in octaves between midpoint (dbGain / 2) gain frequencies
* slope:
shelf slope. When slope=1, the shelf slope is as steep as it can be and
remain monotonically increasing or decreasing gain with frequency.
The shelf slope, in dB/octave, remains proportional to S for all other
values for a fixed and
b0 = a * ((a + 1) + (a - 1) * cosw0 + 2 * asquared * alpha)
b1 = -2 * a * ((a - 1) + (a + 1) * cosw0)
b2 = a * ((a + 1) + (a - 1) * cosw0 - 2 * asquared * alpha)
a0 = (a + 1) - (a - 1) * cosw0 + 2 * asquared * alpha
a1 = 2 * ((a - 1) - (a + 1) * cosw0)
a2 = (a + 1) - (a - 1) * cosw0 - 2 * asquared * alpha
return [b0, b1, b2], [a0, a1, a2]


@njit(cache=True)
def gen_low_shelf(f0, dbgain, qfactor, fs):
"""Generate low shelving filter coeficcients (digital).
f0: The center frequency where the gain in decibel is at half the maximum value.
Normalized to sampling frequency, i.e output will be filter from 0 to 2pi.
dbgain: gain at the top of the shelf in decibels
qfactor: determines shape of filter TODO: Document better
fs: sampling frequency
TODO: Generate based on -3db
Based on: https://www.w3.org/2011/audio/audio-eq-cookbook.html
"""
a = 10 ** (dbgain / 40.0)
w0 = 2 * math.pi * (f0 / fs)
alpha = math.sin(w0) / (2 * qfactor)

if qfactor != None:
alpha = math.sin(w0) / (2 * qfactor)
elif bandwidth != None:
alpha = math.sin(w0) * math.sinh((math.log(2) / 2) * bandwidth * (w0 / math.sin(w0)))
elif slope != None:
alpha = math.sin(w0 / 2) * math.sqrt((a + 1/ a ) * (1 / slope - 1) + 2)
else:
raise Exception("Must specify one value for either qfactor, bandwidth, or slope")

cosw0 = math.cos(w0)
asquared = math.sqrt(a)

b0 = a * ((a + 1) - (a - 1) * cosw0 + 2 * asquared * alpha)
b1 = 2 * a * ((a - 1) - (a + 1) * cosw0)
b2 = a * ((a + 1) - (a - 1) * cosw0 - 2 * asquared * alpha)
a0 = (a + 1) + (a - 1) * cosw0 + 2 * asquared * alpha
a1 = -2 * ((a - 1) + (a + 1) * cosw0)
a2 = (a + 1) + (a - 1) * cosw0 - 2 * asquared * alpha
if type == "low":
b0 = a * ((a + 1) - (a - 1) * cosw0 + 2 * asquared * alpha)
b1 = 2 * a * ((a - 1) - (a + 1) * cosw0)
b2 = a * ((a + 1) - (a - 1) * cosw0 - 2 * asquared * alpha)
a0 = (a + 1) + (a - 1) * cosw0 + 2 * asquared * alpha
a1 = -2 * ((a - 1) + (a + 1) * cosw0)
a2 = (a + 1) + (a - 1) * cosw0 - 2 * asquared * alpha
elif type == "high":
b0 = a * ((a + 1) + (a - 1) * cosw0 + 2 * asquared * alpha)
b1 = -2 * a * ((a - 1) + (a + 1) * cosw0)
b2 = a * ((a + 1) + (a - 1) * cosw0 - 2 * asquared * alpha)
a0 = (a + 1) - (a - 1) * cosw0 + 2 * asquared * alpha
a1 = 2 * ((a - 1) - (a + 1) * cosw0)
a2 = (a + 1) - (a - 1) * cosw0 - 2 * asquared * alpha
else:
raise Exception("Must specify 'high' or 'low' for shelf type, instead got: ", type)

return [b0, b1, b2], [a0, a1, a2]


Expand All @@ -82,8 +87,14 @@ def __init__(self, fs, dBgain, mid_point, Q=1 / 2):
# TODO:
# We want to generate this based on time constant and 'X' value
# currently we use the mid frequency and a gain to get the correct shape
# with eyeballed mid value. Q=1/2 seems to give the corret slope.
self.ataps, self.btaps = gen_high_shelf(mid_point, dBgain, Q, fs)
# with eyeballed mid value. Q=1/2 seems to give the correct slope.
self.ataps, self.btaps = gen_shelf(
mid_point,
dBgain,
"high",
fs,
qfactor=Q
)

def get(self):
return self.btaps, self.ataps
Expand Down
6 changes: 3 additions & 3 deletions vhsdecode/compute_video_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from importlib.resources import files

from vhsdecode.utils import filtfft
from vhsdecode.addons.FMdeemph import FMDeEmphasisB, gen_low_shelf, gen_high_shelf
from vhsdecode.addons.FMdeemph import FMDeEmphasisB, gen_shelf

NONLINEAR_AMP_LPF_FREQ_DEFAULT = 700000
NONLINEAR_STATIC_FACTOR_DEFAULT = None
Expand Down Expand Up @@ -77,10 +77,10 @@ def gen_custom_video_filters(filter_list, freq_hz, block_len):
file=sys.stderr,
)
case "highshelf":
db, da = gen_high_shelf(f["midfreq"], f["gain"], f["q"], freq_hz / 2.0)
db, da = gen_shelf(f["midfreq"], f["gain"], "high", freq_hz / 2.0, qfactor=f["q"])
ret *= filtfft((db, da), block_len, whole=False)
case "lowshelf":
db, da = gen_low_shelf(f["midfreq"], f["gain"], f["q"], freq_hz / 2.0)
db, da = gen_shelf(f["midfreq"], f["gain"], "low", freq_hz / 2.0, qfactor=f["q"])
ret *= filtfft((db, da), block_len, whole=False)
return ret

Expand Down
Loading

0 comments on commit ce92544

Please sign in to comment.