Last active
August 7, 2024 03:05
-
-
Save ckmahoney/f829372208d6e45c2b4763e334fcff01 to your computer and use it in GitHub Desktop.
Comparison of methods for detuning a melody
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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