Skip to content

Instantly share code, notes, and snippets.

@ckmahoney
Last active August 7, 2024 03:05
Show Gist options
  • Save ckmahoney/f829372208d6e45c2b4763e334fcff01 to your computer and use it in GitHub Desktop.
Save ckmahoney/f829372208d6e45c2b4763e334fcff01 to your computer and use it in GitHub Desktop.
Comparison of methods for detuning a melody
(comment
"
Let the choir make the call!
I love Just Intonation and want it to be the absolute answer
for perfect composition.
However JI does not quite answer everything. Or does it?
For example a popular audio effect is detune,
also described as chorus. To produce a detune effect, one must
perform the target frequency alongside a few more instances of
the frequency, offset by some small percentage. For example, if
you want to create a detuned 100hz pitch, some values that sound
'detuned' when played with 100 are 101, 99, 100.3, and 98.98.
If perfect tuning produces perfect music, then why is it so
satisfying to hear a detune effect instead of perfect unison?
I have a hunch that 'detune' effects are optimal when applied at
values producing beat frequencies[1] of the root value, or fundamental
ratios of the root value.
To test this, we can create three groups of lists representing the voices
to perform the frequencies. The first element of each group
will be the same, representing the target melody to perform.
For each example we'll use 7 total voices. This is a modest number, and may
represent a section of violinists in an orchestra.
The first group will be 7 identical copies: Perfect unison among all voices.
The second group will use a detune value a random value in the range of +/- 1%
of the fundamental. We can call this the 'random' detune method.
The third group will use a detune value using harmonic ratios of the fundamental
as detune. We can call this the 'optimal' detune method.
The output of the file prints the three groups in hertz values.
You must provide your own audio synthesis :)
[1]A synonym for beat frequency is 'combination tone.' Read about the
science of it here
[http://hyperphysics.phy-astr.gsu.edu/hbase/Sound/subton.html#c2]
and the psychoacoustics of it here
[https://en.wikipedia.org/wiki/Combination_tone]
")
(comment
"
Update:
For a more exaggerated effect, bumped the n-voices to 30
and made range of optimal detune wider.
The results, the 'random' detune still sounds more like a
traditional detune effect while the optimal version
is a blend of tremelo and extremely clean chorus.
")
(def root
"An arbitary float value in range [1.0, 2.0)"
1.2)
(def melody
"Some tones to express some melody"
'(1 3 2 3 5 2 3 7 1))
(def voice
"Place the pitches into an arbitrary specific frequency band"
8)
(def n-voices
"The number of voices to perform the melody."
30)
(defn fit
"Transposes a frequency to be within 1 octave of the root value."
[r freq]
(if (< (* 2 r) freq)
(fit r (/ freq 2))
(if (> r freq)
(fit r (* 2 freq))
freq)))
(def test-fit
(let [ins-outs [[[1 0.5] 1]
[[1 0.75] 1.5]
[[1 3] 1.5]
[[1 4] 2]
[[1 5] 1.25]]
results (map #(== (apply fit (first %1)) (last %1)) ins-outs)]
(if (not (every? true? results)) (prn "Fit isn't fitting correctly"))))
(defn rand-sign
"Tosses a coin to choose positive or negative."
[]
(if (< 0.5 (rand)) 1 -1))
(defn detune-rand
"Provides a list of random small hertz values with which to offset a frequency value."
[root freqs]
(mapv (fn [& a] (* root (rand) (rand-sign))) freqs))
(defn detune-optimal
"Provides a list of random optimized hertz values with which to offset a frequency value. Has bias toward numbers closer to 0."
[root freqs]
(let [opts (range -5 3)
vals (map #(* root (Math/pow 2 %)) opts)]
(mapv (fn [freq]
(* (rand-nth vals) (rand-sign))) freqs)))
(defn map-fx
"Calls a sidefx function n times and returns the collected results"
[n f]
(mapv (fn [& a] (f)) (range n)))
(defn make-groups
"Make the groups as described in the opening comment."
[]
(let [octave (* root (Math/pow 2 voice))
freqs (map (partial fit octave) melody)
detuner-1 #(map + freqs (detune-rand root freqs))
detuner-2 #(map + freqs (detune-optimal root freqs))
group-1 (repeat n-voices freqs)
group-2 (cons freqs (map-fx (dec n-voices) detuner-1))
group-3 (cons freqs (map-fx (dec n-voices) detuner-2))]
[group-1 group-2 group-3]))
(comment
"
Link to github repository containing samples of each group, as well
as steps to reproduce in SuperCollider.
https://github.com/ckmahoney/samples-of-optimal-detune
")
(mapv prn (make-groups))
;; write results to disk
;; (spit "/tmp/clj-detuned" (doall (make-groups)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment