How do I plot this logarithm without a “while True

2020-04-15 15:09发布

I have been trying to plot the twelve tone scale, from the lowest to the highest audible pitch, in hertz.

I found a way to do this using a while loop that is always true and then use break to exit the loop once the pitch is either lower or higher than the audible human range.

I know that this kind of loop is bad practice, but--as a novice programmer--I can't think of how to do this the proper way. How can I achieve the same result without using "while True"?

I have tried putting the condition "note > highest or note < lowest" where "True" is currently in the while loop, but then "note" is only defined within the loop so I get a "NameError: name 'note' is not defined" error.

highest = 20000
lowest = 20

key = 440
TET = 12

equal_temper = [key]

i = 1
while True:
  note = key * (2**(1/TET))**i
  if note > highest or note < lowest:
    break
  equal_temper.append(note)
  i += 1

i = 1
while True:
  note = key * (2**(1/TET))**-i
  if note > highest or note < lowest:
    break
  equal_temper.append(note)
  i += 1

equal_tempered = sorted(equal_temper)
for i in range(len(equal_temper)):
  print(equal_tempered[i])

The output to the terminal seems to be correct. It may round down or up differently than other tables I have seen. I just want to know how to do this without using "while True."

2条回答
家丑人穷心不美
2楼-- · 2020-04-15 15:22

I notice you're making your compiler re-make the same calculations over and over again while they only need to be done once! (And they can be done by you).

You have a set equation for your note value: note = key * ( 2 ** (1/TET) ) ** i. This can easily be inverted to solve for i as a function of note: i = TET * log_2(note / key). With this, and knowing your upper and lower bounds for note, we can determine the bounds for i.

Plugging in your upper limit, 20000 gives i=66.07. Anything above this will result in a note greater than your upper limit, so we want to take the floor of this value. i_upper = 66.

Plugging in your lower limit, 20, gives i=-53.51. Anything lower than this will give a note lower than your lower limit, so we want to take the ceiling of this value. i_lower = -53

Now all we have to do is loop from i_lower to i_upper and fill your array with the note values.

This method avoids using while loops, is easily generalized if you change any of the parameters, and just through construction, returns a sorted array so you don't have to sort it afterwards.

import math

highest = 20000
lowest = 20

key = 440
TET = 12

def note_value(key, TET, i):
    """ Returns the note value for a given key, TET, and i"""
    return key * (2**(1/TET))**i

def i_value(key, TET, N):
    """ The inverse of note_value. Re-arranged to solve for i."""
    return TET * math.log(N/key) / math.log(2)

notes_above = math.floor(i_value(key, TET, highest))
print(f"Number of notes above = {notes_above}")

notes_below = - math.ceil(i_value(key, TET, lowest))
print(f"Number of notes below = {notes_below}")

print(f"Total number of notes = {notes_below+notes_above+1}")

equal_temper = [ ]
for i in range(-notes_below, notes_above+1):
    # We have to add 1 to notes_above since range is NOT inclusive of the ending term.
    equal_temper.append(note_value(key, TET, i))

print(*equal_temper, sep="\n")

Some notes: Log(x)/Log(B) gives log base B of X, no matter what the original base of Log is. This is why we divide by math.log(2) in our i_value function -- it gives us log base 2 of N/key.

The letter f used right before a string indicates a format string and will replace anything within {} with the whatever it evaluates to. For example, x=5; print(f"My x is {x}") will print My x is 5. This shorthand feature is new to python 3.6. If you're using a version prior to python 3.6, then you will need to replace the format statements with the longer version: print("Number of notes above = {}".format(notes_above))

The very last print statement at the end uses * to 'unpack' the list of equal_temper notes and then prints them with a newline (\n) between each element.

查看更多
劳资没心,怎么记你
3楼-- · 2020-04-15 15:41

You could simply write something like

while lowest <= note <= highest:
  ...

That way you make the reverse of the condition in the if-statement the condition for the while loop and get rid of the hardcoded boolean value.

Edit: I have made some slight modifications to your code and came up with this:

def get_equal_tempers(multiplier):
    tempers = list()
    note = 20
    i = 1
    while lowest <= note <= highest:
        note = key * (2 ** (1 / TET)) ** (i*multiplier)
        if note > highest or note < lowest:
            break
        tempers.append(note)
        i += 1
    return tempers



highest = 20000 
lowest = 20

key = 440 
TET = 12

equal_temper = [key] 
equal_temper += get_equal_tempers(1) 
equal_temper += get_equal_tempers(-1)

equal_tempered = sorted(equal_temper)
for i in range(len(equal_temper)):
  print(equal_tempered[i])

When i run it on my machine, it produces the same output as your code. Can you please check?

查看更多
登录 后发表回答