DBX II NR decoder (expander)

Hi all.

I’ve not actually found any DBX II NR decoders (expanders), and so I set about making something.

The problems are that it needs EQ on the sidechain, then further EQ on the end result.

Furthermore the 2:1 expander gives quite a considerable expansion ratio when finished, so if you have recorded your signal at -18dBFS nominal, effectively you gain another 18dB headroom. So as to avoid clipping, this uses 0dBFS as reference, but this means your end result might be quite low. You will probably need to add make-up amplification or normalisation afterwards. I guess this could be added later, but I use this as part of a macro that has “perceived loudness” as the next stage anyway.

Currently the sidechain EQ isn’t perfect, neither is the final de-emphasis. They’re both within about 1dB, so the end result may be 3dB or so out, but it’s accurate enough to bring those old cassettes back to life (I use it for decoding audio from Portastudio whilst using the companding to gain advantage of the SNR).

Hope someone finds it useful!

It should be quite easy to make it Type I NR by removing the EQ in the side chain, but so far not bothered as I don’t have any Type I sources to decode.

You could do an encoder too, but I can’t see any point in going from Digital to Analog tape, that feels a backwards step back so I see no point in a DBX NR encoder.

Sam
dbxDecode.ny (3.52 KB)

Hi Sam, thanks for posting your plug-in.
I’m not able to fully test as I don’t have any DBX encoded audio, but a couple of comments regarding the code.

It is highly recommended to write all new plug-ins using the latest “version” syntax. Currently that is “;version 4”
Converting your code to version 4 is very easy:

  1. Change the ;version header to
;version 4
  1. When you use “S” to get the track audio, change it to “track
    This:
(cond
  ((< dur 2) "Error.\nSelection must be at least 2 seconds.")
  ((= ratio 0) (mult (db-to-linear -1) (multichan-expand #'limit s)))
  (t (let ((s (multichan-expand #'limit s)))
       (eq-highshelf (mult s (get-env (max-sig s))) 750 -12 0.8))))

becomes

(cond
  ((< dur 2) "Error.\nSelection must be at least 2 seconds.")
  (t (let ((*track* (multichan-expand #'limit *track*)))
       (eq-highshelf (mult *track* (get-env (max-sig *track*))) 750 -12 0.8))))

The other thing I’d suggest is removing excess code and comments before publishing. For example this:

(defun get-env (sig)
  (let* ((sr (snd-srate sig))
         (lookahead 0)
         ; pad the end. Really we want to remain at the
         ; last sample value but in Nyquist loads the
         ; entire sound into ram to get the value.
         (sig (if (< dur 1000)
                  (progn
                    (setq end-lev (abs-env (sref sig (- dur (/ sr)))))
                    (sim sig (snd-const end-lev dur sr 0.6)))
                  (sim sig (snd-const 1 dur sr 0.6))))
         (sidech (hp sig 65))
         (sidech (eq-highshelf sidech 2500 20 .6))
         ;(sidech (lp sidech 15000))
         ;(sidech (eq-highshelf sidech 15000 -20 0.6))
       
         ;(env (s-log (snd-exp sidech)))
         (env (s-abs sidech))
         ;(env (snd-follow env 1 0.05 0.05 0))
         ;(env (extract-abs (+ risetime (* 10 sr)) (+ risetime (* 10 sr) dur) env))
         ;(env (lp env 7))
         ;(env (lowpass2 env 7))
         (env (lp env 5.8))
         ;(env (snd-follow env 1 0.5 0.5 0))
         ;(env (sum (- 0.5)(mult ratio env)))
         ;(env (agc env 0 risetime falltime))
         ;(env (s-log env))
         ;(env (snd-exp env))
         
         
         
;         (env (sum (- 0.5)(mult ratio env)))

         ;(env (snd-exp env))
         ;(env (snd-exp env))
         ;(env (snd-exp env))
         ;(env (mult 0.25 env))
         
         ;(env (exp env 2))
         ;(end (env 0.5))
         ;(env (snd-exp (sum env (snd-log env))))
         (env (snd-exp (sum (snd-log env)(mult 0.00001 (snd-log env)))))
         (env (mult 2 env))
         
         )
         
    env))

becomes

(defun get-env (sig)
  (let* ((sidech (hp sig 65))
         (sidech (eq-highshelf sidech 2500 20 .6))
         (env (s-abs sidech))
         (env (lp env 5.8))
         (env (snd-exp (sum (snd-log env)(mult 0.00001 (snd-log env))))))
    (mult 2 env)))

Thank you for this. I must admit I feel reasonably proficient with DSP (having built a DSP processor that is used on commercial radio stations, several radio data modems, RF circuits, etc), and happy with BASIC and C++, but this is my first project in Nyquist and it took a little getting my head around.

I shall have a look at your suggestions. My next project is a auto cross-fader for multiple microphones (such as radio interview), so the microphones that aren’t giving significant audio are auto-ducked by the “prominent” microphones. I’ve done this in C++ in embedded DSP, but would be good to have in Nyquist.

The idea is to make a script where volunteers at a charity radio station I am involved with can open their multi-track audio files (from DBX encoded 4 track recorders), press a single “go” script button, then it does everything and outputs a nice clean single track downmix with NR, DBX decode, cross-fade, etc, etc, all ready to go.

It will be quite tricky to do in Nyquist because Nyquist effects iterate over the selected tracks. That is, if you select 4 tracks and apply a Nyquist effect, it will process the first track and return a result, then the second track, and so on.

Probably the easiest approach would simply be to gate each track (and thus cut out the noise when each person is not actually speaking.

A more sophisticated approach could be to analyze each track in turn and get the RMS level with a fairly large window (low sample rate). If the tracks are not too long, these low sample rate RMS signals could be held in RAM as properties of the scratch variable (see: Missing features - Audacity Support)
The second plug-in would read the RMS signals, calculate the gain for each track, and apply the relevant gain envelope to each track in turn.
The two plug-ins could be included in a Macro so that they automatically run one after the other (see: Macros - Audacity Manual)

Example code to create a low frequency RMS follower:

;type process
;version 4

(setf window-size 0.1)  ;size of RMS window in seconds

;; If stereo, convert to mono
(when (arrayp *track*)
  (setf *track* (s-max (aref *track* 0)
                       (aref *track* 1))))

;; Get the RMS
(rms *track* (/ 1.0 window-size))

Thank you for the post, and the RMS follower, this is very helpful.

I have done similar in DSP code before for multiple microphones, and how you describe it isn’t far off the optimum with 100ms approx windows. Essentially if a channel forms less than a certain percentage of the sum of all channels (around 1/channels+1, so 20% for 4 channels, 25% for 3 channels, etc), then it’s “not active” and so gets gated.

As this would be the last stage I could just write a program in C++ and as the last stage in the script dump it to WAV then it can go into the C++ program to do the adaptive muti-gate. However I would prefer to go down the Nyquist route.

The process could be quite simple though, make the windowed RMS for each channel. Then sum them all into a “control” channel. Then compare each individual RMS with the “control” and make the actual gating decisions. Finally, downmix.

Sorry to revive this old thread, but I have a question about implement a dbx type encorder/decorder in software and it seems a few people might be able to help. I recently bought one of those inexpensive audio cassette players/records to make mix tape on as a hobby like I did back in the 90s. Needless to say the sound quality isn’t great by any measure so I started to look into why that is and came across the whole NR technology from the 90s. As such, I figured I would just find some code that those this in software, but really couldn’t find anything concrete on internet. My ultimately goal is to build a real-time dbx type NR on an STM32 board (not practical, just want to experiment). I already bought an old dbx NX40 to mess with, but I want to know how easy or had it whould be to implement in software?

Thanks for any help

I recently bought one of those inexpensive audio cassette players/records to make mix tape on as a hobby like I did back in the 90s. Needless to say the sound quality isn’t great by any measure

If you want good quality, why not keep it digital? :stuck_out_tongue:

This project is more about seeing what’s possible using modern MCU/DSP technology applied to old school cassette technology, and hopefully putting that knowledge out there for anyone else wondering thinking along similar lines. That’s assuming it would be relatively easy to implement, otherwise I am just going to stick to digital :slight_smile:

If you are an experienced software developer and you know a bit about DSP, then making a compressor and expander that are complimentary is not very difficult. However, making a compressor and expander that closely match the DBX effects is more difficult as you would need to either have access to the full specification, or reverse engineer a real DBX unit. Modelling analog effects can be very complex as they rarely follow simple mathematical transformations.

Thanks for the information. I suspected as much that an alternative compander method can be used since the point is not to play existing dbx tapes. I did find this site:
http://www.bobweitz.com/dbx_webpage/dbx.html)

which goes over a method for duplicating the dbx expander, so that should simplifies things a bit. That said, I would rather use something else that source code exist for such as ulaw companding, but I am not sure its suitable for use with audio tapes?