Skip to content

Instantly share code, notes, and snippets.

@nogtini
Last active August 5, 2017 23:05
Show Gist options
  • Save nogtini/5ab7bfa4087e49c12b8e2673421d38e2 to your computer and use it in GitHub Desktop.
Save nogtini/5ab7bfa4087e49c12b8e2673421d38e2 to your computer and use it in GitHub Desktop.
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