Last active
August 5, 2017 23:05
-
-
Save nogtini/5ab7bfa4087e49c12b8e2673421d38e2 to your computer and use it in GitHub Desktop.
Aiding practical application of notation described in https://medium.com/joeydinardo/creating-a-contemporary-performance-notation-for-sound-objects-f0d233af7efd
This file contains 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
import numpy as np | |
import math, os | |
''' | |
This application maps notes of a decimal time domain to a binary, a-metric domain, | |
such that each measure represents exactly one second and notation is accurate to within 30 ms. | |
1 = 1/4note | |
2 = 1/8note | |
4 = 1/16 note | |
8 = 1/32 note | |
16 = 1/64 note | |
using up to 64th notes, and tempos not exceeding 116.25 (31/16*60).. | |
..the error per measure is no greater than.. | |
..1/4 the time it takes to blink a human eye (50ms) (average time 100-400ms, took average) | |
or the time it takes for a human to process another human's speech | |
or for sound to travel 56 feet | |
notes must be at least 10ms apart for them to register | |
The less notes there are, the faster/closer together they may be; the more notes the greater the error | |
''' | |
__location__ = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) | |
############################################## | |
#convert between array and list | |
def to_array(lines): | |
return [[np.array(i) for i in element] for element in lines] | |
def to_list(array): | |
return [[i.tolist() for i in element] for element in array] | |
############################################## | |
#audition import conversion | |
def convert(s): | |
mins, secs = s.split(':') | |
mins = int(mins) | |
secs = float(secs) | |
secs = 60 * mins + secs | |
return secs | |
def convert_audition(p): | |
p0, p1 = convert(p[0]), convert(p[1]) | |
print p0,p1 | |
return [str(p0), str(p1)] | |
################################################# | |
#for sound collection ONLY | |
def norm2zero(lines): | |
#subtract firs titem | |
toSub = lines[0][0] | |
# for item in lines: | |
# print item | |
lines = [[format(inItem - toSub,str(3)) for inItem in item] for item in lines] | |
#convert strings to floats | |
lines = [[float(i) for i in element[:2]] for element in lines] | |
print lines | |
def int_split(data): | |
last_b = data[0][1] | |
for item in data: | |
a, b = item | |
# First, make sure we haven't missed anything from the last loop | |
if math.floor(a) - math.ceil(last_b) > 1.0: | |
for x in int_split([[last_b, a]]): | |
yield x | |
# Did we cross an integer boundary, i.e. | |
# Is ceil(b) - floor(a) > 1.0? | |
if math.ceil(b) - math.floor(a) > 1.0: | |
# Yes, so split, making sure to include all integers between a and b | |
# Find any integers in the range (and convert to floats) | |
ints = [float(x) for x in range(int(math.ceil(a)), int(b))] | |
for c in ints: | |
# output those values | |
yield [a, c] | |
a = c | |
else: | |
yield [a, math.floor(b)] | |
yield [math.floor(b), b] | |
else: | |
yield item | |
# remember where we are | |
last_b = b | |
def find_nearest(array,value): | |
#pass in numpy array: [ 0. 0.14285714 0.28571429 0.42857143 0.57142857 0.71428571 0.85714286 ] | |
#pass in value to find nearest of | |
idx = (np.abs(array-value)).argmin() | |
error = np.abs(array[idx]-value) | |
#values: index of NTV, error to NTV, divisions of 1 | |
#remember that length of array is always +1 due to the extra note value of the end | |
return idx, error, len(array)-1 | |
def split_equal(value, parts): | |
value = float(value) | |
hold = [] | |
#parts + 1 adds additional index, but allows last note to end | |
hold = [i*value/parts for i in range(1,parts+1)] | |
hold.insert(0,0) | |
return hold | |
def create_equals(): | |
equal_vals = [] | |
for x in range(31): | |
equal_vals.append(split_equal(1,x+1)) | |
equal_vals = np.array(equal_vals) | |
equal_vals = [np.array(x) for x in equal_vals] | |
return equal_vals | |
#could cause problems with names as built-infunctions | |
def neighborhood(iterable): | |
it = iter(iterable) | |
prev = None | |
item = it.next() # throws StopIteration if empty. | |
for next in it: | |
yield (prev,item,next) | |
prev = item | |
item = next | |
yield (prev,item,None) | |
def get_indices(lines,loval,hival): | |
#list must be list of numpy arrays of values from editor | |
#takes lines and returns numpy array of first values for range comparison | |
first_val_lines = [] | |
for i in lines: | |
first_val_lines.append(i[0]) | |
first_val_lines = np.array(first_val_lines) | |
indices = np.where(np.logical_and(first_val_lines>=loval, first_val_lines<hival))[0] | |
return indices.tolist() | |
# temp = np.array(first_val_lines) | |
# temp[indices].tolist() | |
#tol either .03 or .05 | |
def measure_errors(mant_lines, tol=.05): | |
splits = create_equals() | |
measure_errors = [] | |
for idx, i in enumerate(mant_lines): | |
#list holding seconds pairs | |
if i: | |
for idx2, a in enumerate(splits): | |
error = 0 | |
for j in i: | |
#individual seconds pairs | |
for k in j: | |
#every individual float | |
error += find_nearest(a,k)[1] | |
if error <= tol: | |
measure_errors.append(tuple((idx, idx2, error))) | |
break | |
#returns measure number, split index(number of divisions-1), first error that satisfies tolerance | |
return measure_errors | |
def largest_power(n): | |
return int('1'+'0'*(len(bin(n))-3), 2) | |
def sec_list(lines): | |
numseconds = int(lines[-1][0]) | |
list_outer = [] | |
for i in range(numseconds+1): | |
list_inner = [] | |
indices = get_indices(lines,i,i+1) | |
for j in indices: | |
list_inner.append(lines[j]) | |
list_outer.append(list_inner) | |
return list_outer | |
def get_mantissa(lines): | |
tomod = [[round(i % 1,6) for i in element] for element in lines] | |
#if last value of list is less then previous, should indicate tie to next note | |
if tomod: | |
if tomod[-1][1] <= tomod[-1][0]: | |
tomod[-1][1] = 1.0 | |
return tomod | |
else: | |
return tomod | |
def mant_list(lines): | |
mant_list = [] | |
for i in lines: | |
mant_list.append(get_mantissa(i)) | |
return mant_list | |
def make_notes(measure_errors, mant_lines): | |
splits = create_equals() | |
# print measure_errors | |
biglist = [] | |
for i in measure_errors: | |
# print tuple((i[0], mant_lines[i[0]],i[1]+1)) | |
biglist.append(tuple((i[0], mant_lines[i[0]],i[1]+1))) | |
# print '' | |
#print biglist | |
# print '' | |
bignote = [] | |
for i in measure_errors: | |
bignote.append(i[0]) | |
bignote.append(i[1]+1) | |
for j in mant_lines[i[0]]: | |
print'' | |
print '('+str(i[0])+')',str(i[0]/60)+str(':')+str(i[0]%60).zfill(2), j, i[1]+1 | |
print 'note range:' | |
littlenote = [] | |
for k in j: | |
littlenote.append(int(find_nearest(splits[i[1]],k)[0])) | |
bignote.append(littlenote) | |
print littlenote | |
if littlenote[1] == littlenote[0] == i[1]+1: | |
print "SEPERATOR" | |
elif littlenote[1] == littlenote[0]: | |
print "GRACE NOTE" | |
#print bignote | |
############################################################################## | |
############################################################################## | |
tol = float(raw_input("set tolerance:")) | |
#open file and read in lines | |
f = open(os.path.join(__location__, 'fruitbat/fruitbataudition.txt'), 'rb') | |
lines = f.readlines() | |
lines = [i.strip().split('\t') for i in lines] | |
#if file is from audition; default is for audacity | |
lines = map(convert_audition, lines) | |
f.close() | |
#convert strings to floats | |
lines = [[float(i) for i in element[:2]] for element in lines] | |
#!!!!!!!!!!!WARNING!!!!!!!!!!!!!!! | |
#!!!!!!!!!!!WARNING!!!!!!!!!!!!!!! | |
#only include if collecting sounds | |
#norm2zero(lines) | |
#!!!!!!!!!!!WARNING!!!!!!!!!!!!!!! | |
#!!!!!!!!!!!WARNING!!!!!!!!!!!!!!! | |
for prev,item,next in neighborhood(lines): | |
if next != None: | |
if item[0] == item[1]: | |
dur = next[0] - item[0] | |
item[1] = dur + item[0] | |
else: | |
break | |
lines = list(int_split(lines)) | |
lines = sec_list(lines) | |
mant_lines = mant_list(lines) | |
measure_errors = measure_errors(mant_lines,tol) | |
print mant_lines | |
############################################################################## | |
############################################################################## | |
make_notes(measure_errors,mant_lines) | |
''' | |
Rules: if we see something like: | |
[8,8] on one line followed by [0,2] on the next, it indicates it should be tied over the barline. | |
Notes should be around 50ms apart to register. If multiple notes in succession are 40ms apart, | |
consider the entire phrase as a trill. | |
Tol @ .5 yields around 25ms error | |
Tol @ .3 yields 18ms error | |
at .3 tol, at 20ms into a second, we need to see at least 80ms between notes to register below error | |
Play with both tolerances | |
''' | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment