Convert semi-tones (steps) to frequency

The proportional relationship between any two adjacent pitches in a normal scale (12 tone equal temperament) is the 12th root of 2.
There’s various ways that this may be calculated in Nyquist, but one easy method is:

(expt 2 (/ 12.0))

(note that we need 12.0 as a float so as to avoid division by zero error)

So if we have a frequency of 440 Hz (A4) and we want to calculate the frequency of the note that is one semi-tone higher (A#4)

(* 440 (expt 2 (/ 12.0)))

which is approximately 466.16

To calculate the frequency 2 semi-tones above 440 we need to calculate ((semi-tone)^2) and multiply that by our base frequency of 440
so this gives us:
440 * [{(2 ^ (1/12)} 2]
which in Nyquist we can write as:

(* 440 (expt (expt 2 (/ 12.0)) 2.0))

so the general formula for ‘n’ semitones above a base frequency ‘f0’ can be written as a function:

(defun fcalc (f0 n)
   (* f0 (expt (expt 2 (/ 12.0)) n)))

There’s a table here that can help to check that the results are correct

However, Nyquist has been written specifically with music in mind, so for many applications there is an even easier way.
Nyquist provides two functions:
(hz-to-step) and (step-to-hz)

As a convention that is used in the MIDI 1.0 specification, the note A4 (which is the standard pitch that orchestras tune to and almost everything else uses as the tuning reference) is assigned the number 69 and corresponds to the frequency 440 Hz.

(step-to-hz 69) ; returns 440.0
(hz-to-step 440) ; returns 69

so to calculate the frequency 2 semitones above 440 Hz we can use:

(step-to-hz (+ 2 (hz-to-step 440)))

To write this as a general function for ‘n’ semitones above a base frequency ‘f0’

(defun fcalc2 (f0 n)
 (step-to-hz (+ n (hz-to-step f0))))

Either of these (fcalc) or (fcalc2) functions can be useful where regular pitch spacing is required, for example, to calculate frequencies in the range 20 Hz to (over) 20 kHz that are (approximately) evenly spaced in 1/3 octave intervals:

1/3 octave is 4 semi-tones (12 semi-tone steps to an octave) so we can iterate through a loop:

(defun fcalc (f0 n)
 (step-to-hz (+ n (hz-to-step f0))))

(setf frequency-list
   (do* ((count 0 (setq count (1+ count)))(freq 20)(flist (list freq)))
      ((> freq 20000)(reverse flist))
         (setq freq (round (fcalc freq 4)))
         (setf flist (cons freq flist))))

(format NIL "~a" frequency-list)

It’s occurred to me that the final code example may be difficult to follow.

There’s a good explanation of ‘Do’ loops here:
and for DO*

Breaking down the loop structure into lines:

(setf frequency-list
   (do* ((count 0 (setq count (1+ count)))(freq 20)(flist (list freq)))
      ((> freq 20000)(reverse flist))
         (setq freq (round (fcalc freq 4)))
         (setf flist (cons freq flist))))

(setf frequency-list ; sets the variable 'frequency-list' to the result that is returned from the loop

(do* ((count 0 (setq count (1+ count)))(freq 20)(flist (list freq)))
; sets local variables that will be used in the loop
; 'count' is the loop counter. It is initialised at 0 and then incremented on each loop by the code (setq count (1+ count))
; 'freq' is our initial frequency and is set at 20 (Hz)
; 'flist' is our list of frequencies and initialised to hold the first frequency
; Note that we use DO* and not just 'DO'. This is important because DO* evaluates these initial values in sequential order.
; If we just used 'DO' then we would get an error from (flist (list freq)), but using 'DO*' ensures that 'freq' is evaluated before 'flist'.

((> freq 20000)(reverse flist))
; The 'test' that allows the loop to exit is (> freq 20000)
; The loop returns the value (reverse flist)

(setq freq (round (fcalc freq 4)))
; the start of the actual loop code block. Calculates the next frequency using the function (fcalc). The value is rounded to the nearest whole number.

(setf flist (cons freq flist))))
; adds the new value to the list 'flist', or to put it more technically, it constructs a new list node.