Bass Tilt Plug-in

Below is code for a bass tilt plug-in adapted from the 15-band equalizer plug-in by Josu Etxeberria and David Sky. It is designed to add “warmth” to a recording and works well with male vox by simulating proximity effect, like working a ribbon mic up close. It attenuates high frequencies in a diagonal ramp across the audio band. The one control, “warmth”, is the amount of attenuation in dB that will be applied to the highest frequency band (16 kHz). Less attenuation is applied as the frequency decreases, with no attenuation of the lowest band (25 Hz). Visualize a graphic equalizer with the controls set in a diagonal line with the lowest frequency band set to no attenuation and the higher-frequency bands set for gradually increasing attenuation (decreasing levels). Nothing is boosted in the plug-in due to the possible introduction of clipping.

;nyquist plug-in

;version 1

;type process

;name "Bass Tilt"

;action "Equalizing..."

;info "Bass Tilt"

; based on 15-band equalizer code by Josu Etxeberria and David Sky
; created on July 14, 2013

(setf width (/ 9.96578428466 15))

;control maxAtten "Warmth" real "db" 30 0 100
(setf maxAtten (- maxAtten))

; [convert to linear scale using octaves]
; [almost ten octaves between 20 Hz and 20 kHz]

(setf slope (/ maxAtten 19980))
(setf offset (mult 20 slope))

; calculate centers of different bands [in octaves above 20 Hz]
(setf center1 (mult width 0.5 (- (mult 1 2) 1)))
(setf center2 (mult width 0.5 (- (mult 2 2) 1)))
(setf center3 (mult width 0.5 (- (mult 3 2) 1)))
(setf center4 (mult width 0.5 (- (mult 4 2) 1)))
(setf center5 (mult width 0.5 (- (mult 5 2) 1)))
(setf center6 (mult width 0.5 (- (mult 6 2) 1)))
(setf center7 (mult width 0.5 (- (mult 7 2) 1)))
(setf center8 (mult width 0.5 (- (mult 8 2) 1)))
(setf center9 (mult width 0.5 (- (mult 9 2) 1)))
(setf center10 (mult width 0.5 (- (mult 10 2) 1)))
(setf center11 (mult width 0.5 (- (mult 11 2) 1)))
(setf center12 (mult width 0.5 (- (mult 12 2) 1)))
(setf center13 (mult width 0.5 (- (mult 13 2) 1)))
(setf center14 (mult width 0.5 (- (mult 14 2) 1)))
(setf center15 (mult width 0.5 (- (mult 15 2) 1)))

; convert centers [in octaves above 20 Hz] to frequency
(setf f1 (mult 20 (expt 2 center1)))
(setf f2 (mult 20 (expt 2 center2)))
(setf f3 (mult 20 (expt 2 center3)))
(setf f4 (mult 20 (expt 2 center4)))
(setf f5 (mult 20 (expt 2 center5)))
(setf f6 (mult 20 (expt 2 center6)))
(setf f7 (mult 20 (expt 2 center7)))
(setf f8 (mult 20 (expt 2 center8)))
(setf f9 (mult 20 (expt 2 center9)))
(setf f10 (mult 20 (expt 2 center10)))
(setf f11 (mult 20 (expt 2 center11)))
(setf f12 (mult 20 (expt 2 center12)))
(setf f13 (mult 20 (expt 2 center13)))
(setf f14 (mult 20 (expt 2 center14)))
(setf f15 (mult 20 (expt 2 center15)))

(setf fre1  ( - (mult f1 slope) offset))
(setf fre2  ( - (mult f2 slope) offset))
(setf fre3  ( - (mult f3 slope) offset))
(setf fre4  ( - (mult f4 slope) offset))
(setf fre5  ( - (mult f5 slope) offset))
(setf fre6  ( - (mult f6 slope) offset))
(setf fre7  ( - (mult f7 slope) offset))
(setf fre8  ( - (mult f8 slope) offset))
(setf fre9  ( - (mult f9 slope) offset))
(setf fre10 ( - (mult f10 slope) offset))
(setf fre11 ( - (mult f11 slope) offset))
(setf fre12 ( - (mult f12 slope) offset))
(setf fre13 ( - (mult f13 slope) offset))
(setf fre14 ( - (mult f14 slope) offset))
(setf fre15 ( - (mult f15 slope) offset))

; apply EQ to sound
 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
(eq-band 
s 
f15 Fre15 width) 
f14 Fre14 width) 
f13 Fre13 width) 
f12 Fre12 width) 
f11 Fre11 width) 
f10 Fre10 width) 
f9 Fre9 width)
f8 Fre8 width) 
f7 Fre7 width) 
f6 Fre6 width) 
f5 Fre5 width) 
f4 Fre4 width) 
f3 Fre3 width) 
f2 Fre2 width) 
f1 Fre1 width)
; Released under terms of the GNU Public License
; http://www.opensource.org/licenses/gpl-license.php

Congratulations on your first published plug-in.

That’s not quite strictly true. If you apply the effect to white noise and then use “Plot Spectrum” from the Analyze menu, you will notice that the “diagonal” is actually a bit lumpy, and then goes up above 16 kHz. This is not due to any errors in your code - it is due to the 15 band equalizer code that you started with.
eq.png

Here is an alternative multi-band equalizer function that you could try.
The first and last frequency bands are shelf filters (hardware graphic equalizers usually have shelf filters for the first and last frequency).
The “eq-band” parameters are calculated within a loop, which cuts out about 100 lines of code and makes it easier to maintain (less opportunity for typos :wink:)

;;; Graphic Eq.
;;; "gains" is an array of eq band gains in dB.
;;; Frequency calculations are in steps for equal pitch spacing.
(defun graphic (sig gains)
  (let* ((low (hz-to-step 20))                  ; low frequency limit in steps
         (high (hz-to-step 20000))              ; high frequency limit in steps
         (bands (length gains))                 ; number of frequency bands
         (bandwidth (/ (- high low) bands))     ; bandwidth of one eq band in steps
         (bw-octave (/ bandwidth 12))           ; bandwidth in octaves
         (lowstep (+ low (* 0.5 bandwidth))))   ; centre frequency of first band in steps
    ;; Low shelf filter for first frequency
    (setq half-gain-freq (+ lowstep (/ bandwidth 2.0)))
    (setf sig
      (eq-lowshelf sig 
                   (step-to-hz half-gain-freq)
                   (aref gains 0)
                   (/ bw-octave)))
    ;; High shelf filter for final frequency
    (setq cf (+ lowstep (* (1- bands) bandwidth)))
    (setq half-gain-freq (- cf (/ bandwidth 2.0)))
    (setf sig
      (eq-highshelf sig 
                    (step-to-hz half-gain-freq)
                    (aref gains (1- bands))
                    (/ bw-octave)))
    ;; Eq-band filters
    (do* ((band 1 (1+ band)))
         ((= band (1- bands)) sig)
      (setq gain (aref gains band))
      (setq frequency (+ lowstep (* band bandwidth)))
      (setf sig
        (eq-band sig
                 (step-to-hz frequency)
                 gain
                 bw-octave)))))

Thanks for posting that. I’ll see if I can replicate your graph. The quick fix might be to add equalizer bands where needed or fussing with the width of the EQ settings. I might try adding a band at 20 kHz.

It may not be a bad thing that the very top frequencies are not rolled off - you could try reproducing the curve using Audacity’s Equalization effect with the “Draw Curve” settings, and compare the sound with / without the top frequencies rolled off.

It may also be worth comparing the effect with using a more simple “shelf filter” http://www.cs.cmu.edu/~rbd/doc/nyquist/part8.html#index448

A bit of an improvement was obtained when calculating the width by using 14 as a divisor instead of 15. When looking at the spectrum analysis, disregard frequencies above 20 kHz.

    ;nyquist plug-in

    ;version 1

    ;type process

    ;name "Bass Tilt"

    ;action "Equalizing..."

    ;info "Bass Tilt"

    ; based on 15-band equalizer code by Josu Etxeberria and David Sky
    ; created on July 14, 2013

    (setf width (/ 9.96578428466 14))

    ;control maxAtten "Warmth" real "db" 30 0 100
    (setf maxAtten (- maxAtten))

    ; [convert to linear scale using octaves]
    ; [almost ten octaves between 20 Hz and 20 kHz]

    (setf slope (/ maxAtten 19980))
    (setf offset (mult 20 slope))

    ; calculate centers of different bands [in octaves above 20 Hz]
    (setf center1 (mult width 0.5 (- (mult 1 2) 1)))
    (setf center2 (mult width 0.5 (- (mult 2 2) 1)))
    (setf center3 (mult width 0.5 (- (mult 3 2) 1)))
    (setf center4 (mult width 0.5 (- (mult 4 2) 1)))
    (setf center5 (mult width 0.5 (- (mult 5 2) 1)))
    (setf center6 (mult width 0.5 (- (mult 6 2) 1)))
    (setf center7 (mult width 0.5 (- (mult 7 2) 1)))
    (setf center8 (mult width 0.5 (- (mult 8 2) 1)))
    (setf center9 (mult width 0.5 (- (mult 9 2) 1)))
    (setf center10 (mult width 0.5 (- (mult 10 2) 1)))
    (setf center11 (mult width 0.5 (- (mult 11 2) 1)))
    (setf center12 (mult width 0.5 (- (mult 12 2) 1)))
    (setf center13 (mult width 0.5 (- (mult 13 2) 1)))
    (setf center14 (mult width 0.5 (- (mult 14 2) 1)))
    (setf center15 (mult width 0.5 (- (mult 15 2) 1)))

    ; convert centers [in octaves above 20 Hz] to frequency
    (setf f1 (mult 20 (expt 2 center1)))
    (setf f2 (mult 20 (expt 2 center2)))
    (setf f3 (mult 20 (expt 2 center3)))
    (setf f4 (mult 20 (expt 2 center4)))
    (setf f5 (mult 20 (expt 2 center5)))
    (setf f6 (mult 20 (expt 2 center6)))
    (setf f7 (mult 20 (expt 2 center7)))
    (setf f8 (mult 20 (expt 2 center8)))
    (setf f9 (mult 20 (expt 2 center9)))
    (setf f10 (mult 20 (expt 2 center10)))
    (setf f11 (mult 20 (expt 2 center11)))
    (setf f12 (mult 20 (expt 2 center12)))
    (setf f13 (mult 20 (expt 2 center13)))
    (setf f14 (mult 20 (expt 2 center14)))
    (setf f15 (mult 20 (expt 2 center15)))

    (setf fre1  ( - (mult f1 slope) offset))
    (setf fre2  ( - (mult f2 slope) offset))
    (setf fre3  ( - (mult f3 slope) offset))
    (setf fre4  ( - (mult f4 slope) offset))
    (setf fre5  ( - (mult f5 slope) offset))
    (setf fre6  ( - (mult f6 slope) offset))
    (setf fre7  ( - (mult f7 slope) offset))
    (setf fre8  ( - (mult f8 slope) offset))
    (setf fre9  ( - (mult f9 slope) offset))
    (setf fre10 ( - (mult f10 slope) offset))
    (setf fre11 ( - (mult f11 slope) offset))
    (setf fre12 ( - (mult f12 slope) offset))
    (setf fre13 ( - (mult f13 slope) offset))
    (setf fre14 ( - (mult f14 slope) offset))
    (setf fre15 ( - (mult f15 slope) offset))

    ; apply EQ to sound
     
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    (eq-band
    s
    f15 Fre15 width)
    f14 Fre14 width)
    f13 Fre13 width)
    f12 Fre12 width)
    f11 Fre11 width)
    f10 Fre10 width)
    f9 Fre9 width)
    f8 Fre8 width)
    f7 Fre7 width)
    f6 Fre6 width)
    f5 Fre5 width)
    f4 Fre4 width)
    f3 Fre3 width)
    f2 Fre2 width)
    f1 Fre1 width)
    ; Released under terms of the GNU Public License
    ; http://www.opensource.org/licenses/gpl-license.php

Yes that definitely looks smoother.

Is the intention is to model a close microphone (proximity effect)?
This graph shows a fairly typical change in frequency response due to proximity:
chartbeta87a.jpg

The intent is to apply a linear ramp to the frequency response curve across the band from 20 to 20,000 Hz. High frequencies are attenuated the most and 20 Hz not at al, thus the name “bass tilt” rather than “proximity”.

In the graph you posted, the dotted line shows the bass boost due to proximity effect together with the mic’s frequency response. Note the bend around 90 Hz which follows the frequency response of the mic. Also, the graph only goes down to 50 Hz. Without a plug-in, you would control proximity effect through distance and mic selection.

This is a frequency plot obtained by “tilting” White noise with the Bass and Treble effect (bass turned up, treble turned down to create a fairly even “tilt”.
tilt.png

That looks more like what I had in mind. What settings did you use? When I do it the curve has a parabolic shape.

Again, no boost should be applied so as not to send the samples into clipping.

I thought it might be.

I think that was Bass at maximum, Treble at minimum.

Do you have Plot Spectrum set for “Axis: Linear” or “Axis: Logarithmic”?

In audio it is more usual to look at spectra with a logarithmic frequency axis, because we hear like that. (One “octave” is a doubling of frequency. A logarithmic frequency scale has octaves at equal intervals. A linear frequency axis bunches up the first 6 octaves (20 Hz to 1280 Hz) in the audio range on the extreme left of the graph and the highest octave (10 kHz to 20 kHz) takes up about half the graph. Spectrum plots for microphones, loudspeakers, and other audio equipment are invariably shown with a logarithmic frequency scale (as seen in that Shure microphone graph).

The graph in my previous post shows a “tilt” of about 6 dB per octave.

Can you guide me to the source code for this? There appears to be more than one version.

In boosting the bass, does it add gain to the samples?

Depending on the size of the FFT window, Bass and Treble flattens out below 40 Hz.

/src/effects/BassTreble.cpp

The version in the SVN source code is new for Audacity 2.0.4 (not yet released). The filter is the same as in the current release version, but it has a “Level” control to normalize the output to the specified level.

Boosting the bass will increase the gain of low frequencies, but it is easy to compensate for the gain increase.

Audacity uses 32 bit float format by default, and 32 bit float format can handle very large values (a long way over 0 dB). That means that you can compensate for any gain increase after applying the filter.

In Plot Spectrum, higher FFT windows will show a more accurate depiction of the low frequency content.


The BassTreble filter is easy to code in Nyquist. It is just two shelf filters (see eq-highshelf and eq-lowshelf)

This is an exactly the same filter written in Nyquist:

(setq BassGain 15)
(setq TrebleGain -15)

(eq-highshelf
  (eq-lowshelf s 250 BassGain 0.4)
  4000 TrebleGain 0.4)

The most simple way to compensate for any possible overall gain would be an additional “gain” factor, for example, something like:

(setq tilt 15)
(setq TrebleGain -15)

(mult (/ (db-to-linear tilt))
  (eq-highshelf
    (eq-lowshelf s 250 tilt 0.4)
    4000 (- tilt) 0.4))

Steve -

The shelf filters work great! Thanks for all of your help. Here is my touched-up version:

;nyquist plug-in

;version 1

;type process

;name "Bass Tilt"

;action "Equalizing..."

;info "Bass Tilt"

;control tilt "Warmth" real "db" 15 0 100

(mult (/ (db-to-linear tilt)))

(eq-highshelf
(eq-lowshelf s 250 tilt 0.4)
4000 (- tilt) 0.4)

Cool, it works well :sunglasses:

If I could make a couple of suggestions:

The extra line spaces in the header should not really be there. David Sky, who wrote the plug-in that you were copying, was blind, and so was unaware of the double line spacing. Nyquist is based on LISP, and in well written LISP the code is spaced out no more than necessary, keeping related code together and separating unrelated blocks with a small space. Indentation is used a lot to indicate the code structure. Indentation is important for readability in LISP, otherwise it’s far too easy to get lost in a forest of brackets :wink:

If you plan to do more with Nyquist, there is a guide to LISP indentation here: http://dept-info.labri.u-bordeaux.fr/~idurand/enseignement/PFS/Common/Strandh-Tutorial/indentation.html
I’ve also posted a few examples here: https://forum.audacityteam.org/t/conventions-for-nyquist-plug-ins/16324/16
Unlike the Python language, indentation is not part of the syntax in LISP, so Nyquist code will still run even if the indentation is hopelessly wrong, but it will be horrible to read.

I’d also highly recommend using a text editor that has “bracket matching” (automatically highlights the matching bracket when one bracket is selected). If you’re on Windows, NotePad++ is very good (and free).

How I would lay out your code would be like this:

;nyquist plug-in
;version 1
;type process
;name "Bass Tilt"
;action "Equalizing..."
;info "Bass Tilt"

;control tilt "Warmth" real "db" 15 0 100

(mult (/ (db-to-linear tilt)))
  (eq-highshelf
    (eq-lowshelf s 250 tilt 0.4)
    4000 (- tilt) 0.4)

The other thing that I noticed is that a slope from +100 dB to -100 dB is rather excessive (I can’t imagine many uses over, say about 20 dB), and more than the simple “gain compensation” can cope with.

We can improve the gain compensation by applying a compensation of 2/tilt dB to the sound before filtering.
A couple of different examples of how that might be coded:

; scale "s" before filtering
(setf s (mult s (/ 2.0 (db-to-linear tilt))))

(eq-highshelf
  (eq-lowshelf s 250 tilt 0.4)
  4000 (- tilt) 0.4)

or scale “S” within a “Let” block

(let ((s (mult s (/ 2.0 (db-to-linear tilt)))))
  (eq-highshelf
    (eq-lowshelf s 250 tilt 0.4)
    4000 (- tilt) 0.4))

Please note that the “warmth” setting does not go negative; if it did, we would have to call it something else for when it goes negative and emphasizes the highs. The choice of the upper limit is purely arbitrary. Do you have a lower value in mind and a reason for it? It could be a lower value but I don’t like to second-guess the user who may want a more extreme setting beyond what we think is reasonable. Maybe “bass tilt” isn’t the best name for this because it doesn’t tilt both ways?

I’m happy with the current implementation.

How does this look?

;nyquist plug-in
;version 1
;type process
;name "Bass Tilt"
;action "Equalizing..."
;info "Bass Tilt"

;control tilt "Warmth" real "db" 15 -10 30

(setf s (mult s (/ 2.0 (db-to-linear tilt))))
(eq-highshelf
(eq-lowshelf s 250 tilt 0.4)
4000 (- tilt) 0.4)

The way that we usually handle that is to set the slider range to what we consider “reasonable”, but do not restrict the range elsewhere in the code. This way, if a user wants to use a really extreme setting, then they can type it into the text box rather than using the slider. The text box value overrides the slider position. The only downside to this is that the setting will be restored to the “normal” range the next time that the effect is used (but that could also be a good thing).

If we really need to limit the user input range (for example if an extreme setting will cause the plug-in to fail), then we can “sanitize” the input before we use it.
For example, if we have a slider range of 0 to 50 and we need to prevent negative values, or values over 1000, then we can do so like this:

;control value "Input a value" real "" 10 0 50

(setq value (min 1000 (max 0 value)))



I’d probably go with something like 30 dB as a reasonably wide range (especially considering that it is +30 dB Bass and -30 dB Treble, so actually the range is double what the slider says). A nice thing about a 30 dB slider range is that if you mouse click to the side of the slider knob, it moves in steps of 3.0 dB, which in audio terms is a “round” number. The decision is up to you - it’s your creation :wink:

I’ve modified the code to 0 to 30 dB. It doesn’t change the performance but if you’re more comfortable with that value, we’ll go with it.

considering that it is +30 dB Bass and -30 dB Treble, so actually the range is double what the slider says)

]
As originally envisioned, there is supposed to be no bass boost. 30 dB was supposed to be the attenuation applied at 20 kHz. The range available to the user through the slider is now 30 dB. 60 dB is internal to the program if you apply 30 dB of bass boost in addition to 30 dB of treble attenuation but not available through the slider…

I have placed my plug-in in the plug-ins folder and it is not showing up. It is nowhere to be found. Latest version of audacity downloaded today. All the other plug-ins show up. Audacity finds this plug-in OK on my other computer but not the one I’m using now. The name of the file is Bass Tilt.ny. It is located in D:ProgramsAudacityPlug-Ins



To add a Nyquist plug-in, put it in the Audacity “Plug-ins” folder.
On Windows and OS X the “Plug-ins” folder is in the directory where Audacity resides - usually C:Program Files on Windows or the “Applications” folder on OS X.
On Linux, the “plug-ins” folder is in usr/share/audacity if you installed an Audacity package supplied by your distribution, or usr/local/share/audacity if you compiled Audacity from source code. Optionally a plug-in folder can be created in the home directory ~/.audacity-files/plug-ins.
The next time you launch Audacity, plug-ins you added will appear in the Effect, Generate or Analyze menus as appropriate.

I did all this (Windows 7) except the .exe file is in D:ProgramsAudacity and the plug-ins folder is there too.