Amplitude Modulation

Here is my very simple amplitude modulation plugin.

Parameters:
LFO frequency (0.01 to 20 Hz)
Left channel phase (-180 to 180 degrees)
Right channel phase (-180 to 180 degrees)
Wet signal (0 to 100 %)

File:
modulation.ny (974 Bytes)

Nice one vpd, it works well.

I particularly like the “phase” options, great for swishy stereo effects, though the 0 degrees point looks a bit strange. I would have expected 0 degrees to be either the highest or lowest point of the modulation. I can see why 0 degrees is where it is (it is the zero crossing point of the sine wave before being offset) but from a user perspective I think it looks odd.

The question of phase also ties in with a question about the wet/dry scaling.
Again I don’t think that your choice of how to define 100% is “wrong”, but is perhaps “unexpected” from a user perspective. As a user, my expectation for “100 % modulation” would be like this:

That is equivalent to your “50% wet” setting.
If the above were called 100%, then (assuming that you want to extend into the range where the modulation wave drops below zero), your 100% would become 200%. I’m not sure how useful that is. In most cases I would imagine that 0 to 100% (your 0 to 50%) would be adequate. Beyond that the LFO “frequency” is effectively doubled and the phase of the signal is inverted.


A quick note about the code formatting. For LISP programming, indentation is generally considered mandatory, and “trailing” parentheses considered bad. For short code, as here, it does not make much difference, but for longer code it makes a huge difference to readability for others, If your current text editor does not have parentheses matching (select an open bracket and it highlight the matching closing bracket), then I would highly recommend getting one that does, Counting brackets is not fun :wink: For Windows, try Notepad++.

As an example, this is how I would format the main body of your code:

(if (= wet 0)
    (format nil "Bypassing 100% dry signal! ~%")
    (if (arrayp s)
        (vector
          (sum (mult (aref s 0) dry) (mult (aref s 0) (hzosc freq *sine-table* phl) wet))
          (sum (mult (aref s 1) dry) (mult (aref s 1) (hzosc freq *sine-table* phr) wet)))
        (format nil "Error: Audio is not stereo. ~%")))

There is a good guide to LISP indentation conventions here: http://dept-info.labri.fr/~idurand/enseignement/lst-info/PFS/Common/Strandh-Tutorial/indentation.html

Would it be worth making the plug-in compatible with mono tracks?
It would be quite easy to do that - just change the “Left channel phase” control to “Left/Mono channel phase”.

In the code you could have something like:

(cond
  ((= wet 0) (format nil "Bypassing 100% dry signal! ~%"))
  ((arrayp s)
    (vector
      (sum (mult (aref s 0) dry) (mult (aref s 0) (hzosc freq *sine-table* phl) wet))
      (sum (mult (aref s 1) dry) (mult (aref s 1) (hzosc freq *sine-table* phr) wet))))
  (T (sum (mult s dry) (mult s (hzosc freq *sine-table* phl) wet))))

(Using COND is generally clearer to read and more compact than nested IF conditionals when there are more than 2 conditions. Obviously more important when there are many conditions :wink:)

Thank you Steve! I’m a newbie in Nyquist and really appreciate your support and your patience about my first steps! :slight_smile:
Just opened the .ny file with Notepad++, picked LISP language and it’s far more handy :slight_smile:

I thought it might be better to use absolute sine wave for modulation, but (abs (hzosc freq)) didn’t work :smiley:

Edit:
We can “trick” it and get some “absolute sine”-alike wave with expression:
(mult (sum 1 (hzosc frequency sine-table (- (* 2 phase) 90))) 0.5)

and then the new code would look like this:

(cond
  ((= wet 0) (format nil "Bypassing 100% dry signal! ~%"))
  ((arrayp s)
    (vector
      (sum (mult (aref s 0) dry) (mult (aref s 0) (sum 1 (hzosc freq *sine-table* (- (* 2 phl) 90))) wet 0.5))
      (sum (mult (aref s 1) dry) (mult (aref s 1) (sum 1 (hzosc freq *sine-table* (- (* 2 phr) 90))) wet 0.5))))
  (T (sum (mult s dry) (mult s (sum 1 (hzosc freq *sine-table* (- (* 2 phl) 90))) wet 0.5))))

But I still wonder why (abs (hzosc freq)) doesn’t work? :slight_smile:

Because ‘abs’ works with numbers solely. Use ‘s-abs’ instead.
However, this is not equivalent to your replacement code, which is a raised cosine.

Thanks for the help Robert! :wink:

Good, I thought you would like it :wink:


For “normal” modulation, you would want the modulation wave to peak at +1
As the Nyquist oscillators, by default, go between +/- 1.0, a suitable modulation waveform can be created:

(mult 0.5 (sum 1 mod-wave))

or

(sum 0.5 (mult 0.5 mod-wave))

where “mod-wave” is the generated waveform.

For “partial” modulation (where the signal being modulated is not pushed all the way to silence), the modulation wave will need to oscillate between +1 and some positive value less than 1.

So, if “modulation” is our modulation wave, scaled between 0 and +1, then for a “mix” value called “mix”, we need to amplify the modulation wave by “mix” and add “1 - mix”

(sum (- 1 mix) (mult mix modulation))

Rather than repeating this formula for each channel option, we could put this into a function, such as:

(defun mod-wave (hz mix)
  (let ((modulation (mult 0.5 (sum 1 (hzosc hz)))))
    (sum (- 1 mix)
      (mult mix modulation))))

Now, each time we want to modulate a sound, all we need is:

(mult mysound (mod-wave frequency mix))

where “mysound” is the (mono) sound that we want to modulate. (for clarity I’ve not included phase control).

As an example, we could have something like:

(setq frequency 2)
(setq mix 0.7)

;;; Generate the modulation waveform
(defun mod-wave (hz mix)
  (let ((modulation (mult 0.5 (sum 1 (hzosc hz)))))
    (sum (- 1 mix)
      (mult mix modulation))))

;;; Apply the modulation
(if (arrayp s)
    (vector
      (mult (aref s 0) (mod-wave frequency mix))
      (mult (aref s 1) (mod-wave frequency mix)))
    (mult s (mod-wave frequency mix)))

As the code that applies the modulation is now much simplified, it becomes easy to do interesting things such as modulating with two (or more) LFOs. For example:

(setq frequency1 2)
(setq mix1 0.7)
(setq frequency2 3)
(setq mix2 0.5)

(defun mod-wave (hz mix)
  (let ((modulation (mult 0.5 (sum 1 (hzosc hz)))))
    (sum (- 1 mix)
      (mult mix modulation))))

(defun stereo-mod (sig hz mix)
  (if (arrayp sig)
      (vector
        (mult (aref sig 0) (mod-wave hz mix))
        (mult (aref sig 1) (mod-wave hz mix)))
      (mult sig (mod-wave hz mix))))

(stereo-mod (stereo-mod s frequency1 mix1)
            frequency2 mix2)

Of course you can also add in phase controls :slight_smile:

Thank you Steve and Robert! :slight_smile: Practical examples like these teach more than the “dry” theory :slight_smile:
I guess this code here looks far better than my first one:

(defun mod-wave (hz mix ph)
  (let ((modulation (s-abs (hzosc hz *sine-table* ph))))
    (sum (- 1 mix) (mult mix modulation))))

(if (arrayp s)
    (vector
      (mult (aref s 0) (mod-wave freq wet phl))
      (mult (aref s 1) (mod-wave freq wet phr)))
  (mult s (mod-wave freq wet phl)))

imo, that code is much easier to read. I can immediately see that you are creating a “mod-wave” that is a “rectified” sine wave (Rectifier - Wikipedia), and that you then multiply (“modulate”) the track audio with “mod-wave”.

However, that does raise the question of why the modulation wave is “rectified”. That is not the “normal” way to perform amplitude modulation.

Adapting your code for a more conventional AM effect:

; example settings:
(setq freq 2)
(setq mix 0.5)
(setq phl 0)
(setq phr 180)

;; Generate modulation wave.
(defun mod-wave (hz mix ph)
  (let ((modulation (hzosc hz *sine-table* ph))
        (wet (/ mix 2.0)))
    (sum (- 1 wet)
      (mult wet modulation))))

;; Apply modulation
(if (arrayp s)
    (vector
      (mult (aref s 0) (mod-wave freq mix phl))
      (mult (aref s 1) (mod-wave freq mix phr)))
  (mult s (mod-wave freq mix phl)))

would also be a useful modification of the envelope of a track to type mask (line by line) with 2 settings for weet and signal for each channel. Instead of a LFO it would take a choice of type musical notation then according to the time (every 16 or 32 or 64 … (google traduction)

I merged my plug-in with the classic tremolo effect and here is the result:
tremolo.ny (1.75 KB)
Added:

  • modulation type choice: frequency or number of trembles (in the selection)
  • stereo processing with left/right channel modulation phase

Big thanks to Steve and Robert for the help!

Well done. It works fine. Have you used it for anything yet?

Thank you! :slight_smile: I still haven’t used it. I made manually the stereo imaging of mono-recorded turntable cuts for a few hip-hop tracks. Now, with this edition of the plugin, I can do it far better and faster.