Calculate resulting frequency from a given harmonic node, or multiple nodes, rounded to the nearest harmonic less than n

Viewed 67

I'm working on a musical composition tool and I need to figure out what the resulting frequency would be if I touched (created a harmonic node) some arbitrary pitched string in one or more locations.

These positions need to be given as Midi pitches. So if I needed to create a harmonic with a fundamental of middle C and touch it at the 5th above (G) that would be written like this;

Harmonic(60, 67) #3rd harmonic

which I'd expect to return 79, given:

def toFreq(m):
   return pow(2, (m-69)/12)
def toMidi(f):
   return 12 * math.log2( f/440 ) + 69

I have been able to implement this so far.

However, if I do:

Harmonic(60, 64) #5th harmonic

I get the wrong answer, this is because I want to 'round' the harmonic below -lets say- the 20th harmonic to reflect a real instrument. Which I have had some success getting to work by limiting the denominator of a Fraction. However this falls apart if I use a higher node from the same harmonic:

Harmonic(60, 69) # also the 5th harmonic, just a different node

The second issue I have, and I have had no success in getting this to work, is having multiple nodes like this.

Harmonic(60, [67, 65]) # where a 'C' string is touched at the 5th and 4th
# this is the 9th harmonic ( a compound major 2nd )

I was wondering if somebody could please help with the algorithm here? The actually classes and python stuff is no issue - this algorithm would go in the initaliser and set a member self.sounding -, and I'd happily have an answer is pseudo code! It also doesn't matter if the return is a midi pitch or frequency, it just needs to take a root pitch and one or more nodes (limited to two if that is easier).

Also, I hope I haven't over tagged this question!

2 Answers

Does this work? My memory of the physics involved is pretty dim, but basically, we find the lowest pitch that is a common overtone of all of the given MIDI pitches.

import math


def freq_from_midi(m):
    return 440 * 2 ** ((m - 69) / 12)


def nearest_midi_from_freq(f):
    return round(12 * math.log2(f / 440) + 69)


def midi_overtones(m):
    f = freq_from_midi(m)
    return {nearest_midi_from_freq(h * f) for h in range(1, 21)}


def harmonic(*ms):
    return min(set.intersection(*(midi_overtones(m) for m in ms)))


print(harmonic(60, 67))
print(harmonic(60, 64))
print(harmonic(60, 69))
print(harmonic(60, 67, 65))

Submitting answer to own question. Building off of David Eisenstat's answer taking account that an instrument will move the nodes slightly to find the lowest harmonic:

import math                                               
    
    
def freq_from_midi(m):    
    return 440 * 2 ** ((m - 69) / 12)    
    
    
def nearest_midi_from_freq(f):    
    return  round( 2 * (12 * math.log2(f / 440) + 69 )) / 2    
    
    
def midi_overtones(m):    
    f = freq_from_midi(m)    
    return { nearest_midi_from_freq(h * f) for h in range(1, 25)}    
    
    
def midi_overtones_flexible(m):    
    float_range = lambda start, stop, step: [start + step*r for r in range(0,int((stop - start)/step))]    
    
    freqs = (freq_from_midi(m + r) for r in float_range(-0.5, 0.5, 0.125) )    
    
    return { nearest_midi_from_freq(h * f) for f in freqs for h in range(1, 25)}    
    
    
def harmonic(base, *nodes):    
    overtones_of_base = midi_overtones(base)    
    overtones_of_nodes = [midi_overtones_flexible(m) for m in nodes]    
    r = set.intersection( overtones_of_base, *overtones_of_nodes )    
    return min(r)    
    
    
print(harmonic(60, 64, 66))  
print(harmonic(60, 65, 67))    

Thank you David!

Related