#!/usr/bin/python
import math
import sys
import getopt

basiclen = 3.88;			# F-horn
basiclen = 2.91;

basic_valves = [1/8.0, 1/15.0, 1/5.0];
all_fingerings = [(0,0,0), (0,1,0), (1,0,0), (1,1,0), (0,0,1), (0,1,1), (1,0,1), (1,1,1)]


valve_tuning = [0,0,0]
basic_tuning = 0.002

def help():
	sys.stdout.write (r"""
hoorn-tab.py -- generate fingerings for french horn, given tube lengths. 
usage:
  hoorn-tab.py [OPTIONS] notenames
options:
  --pollard          Use Bas Pollard's tunings
  --tuning=X         Set main tuning slide to X milimeters
  --valves=X,Y,Z     Set valve tuning slides to X,Y,Z milimeters

note names are dutch (-es means flat, -is means sharp). Notes are
printed as [name fingering distance], where distance is measured with
2 decimals in half-tones.
""")

(options, files) = getopt.getopt (sys.argv[1:], '', ['help', 'tuning=', 'pollard', 'valves=']) 

for (o,a) in options:
	if o == '--help':
		help ()
		sys.exit (0)
	elif o == '--tuning':
		basic_tuning = string.atof (a)/1000.0;
	elif o == '--valves':
		as = string.split (',', a)
		valve_tuning = map (lambda x: string.atof (x)/1000.0, as)
	elif o == '--pollard':
		basic_tuning = -0.02
		valve_tuning = [0.011, 0.008, 0.015]

geluidsnelheid = 342.32

tube_length = basiclen - basic_tuning;


def total_tunings ():
	valves = []
	for x in  0,1,2:
		valves.append (basic_valves[x] * basiclen + valve_tuning[x])
	return valves


valves = total_tunings ()



print 'basic tube length %f, ratio valves: %s\n' % (basiclen, str(basic_valves))
print 'tuning slide %f, valve slide: %s' % (basic_tuning, valve_tuning)

names = (
	['c', 'des',
	 'd','es',
	 'e',
	 'f','ges',
	 'g','as',
	 'a','bes',
	 'b',],
	['c', 'cis',
	 'd','dis',
	 'e',
	 'f','fis',
	 'g','gis',
	 'a','ais',
	 'b',])

# sharp or flat in this key?
accidental = [0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1]

def notename (h, accidx = 0):
	i = h % 12;
	return names[accidx][i]

def tubelen (l, vent, vlens):
	for v in 0,1,2:
		if vent[v]:
			l = l + vlens[v]
	return l

def frequence (overtone, v):
	l = tubelen (tube_length, v, valves)
	wavelength = 2 * (l / overtone )
	return geluidsnelheid / wavelength;

basicfreq = frequence (1, (0,0,0))

def equal_temperament (freq):
	return (math.log (freq / basicfreq) / math.log (2.0))*12.0 + 5.0;

def fingeringstr (t):
	s = ''
	k = 0
	for x in t:
		k =k + 1
		if x:
			s = s + '%d' % k

	if s == '':
		s = '0'
	return s


table = {}
hoogste = 20

for ov in range (1,hoogste):
	for v in all_fingerings:
		freq = frequence (ov, v)
		halvetonen = equal_temperament(freq)

		tone = int (round (halvetonen))

		if tone not in table.keys ():
			table[tone] = []

		afwijking = halvetonen - tone
		table[tone].append ((v, freq, afwijking))


def entry_str (t):
	if t:
		return '[%3s %+.2f]' % (fingeringstr (t[0]), t[2]);
	else:
		return '[none %4s]' % ''
	


ks = table.keys ();
ks.sort ()
highest_step = 48

for x in  range (ks[0], highest_step):
	sys.stdout.write ('%4s: ' % notename (x, 1))
	try:
		for t in table[x]:
			sys.stdout.write(entry_str (t) + ' ')
	except KeyError:
		pass
	sys.stdout.write ('\n')




aristoxenus_freqs = [8./9., 9./10., 15./16., 8./9., 8./9., 9./10., 15./16.]
zarlino_freqs = [8./9., 9./10., 15./16., 8./9., 9./10., 8./9., 15./16.]


aristoxenus_scale = (
	'aristoxenus',
	map (lambda x:  -math.log (x) / math.log (2.0)  * 12.0, aristoxenus_freqs));

zarlino_scale = ('zarlino',
	   map (lambda x:  -math.log (x) / math.log (2.0)  * 12.0, zarlino_freqs))

equal_scale = ('equal temperament',
	       [2,2,1, 2,2,2,1])

INFTY = 10000

def scale (steps, startnote, table):
	s = startnote
	sharp = accidental[s]
	exact_pitch = startnote
	start_pitch = startnote
	n = 0
	while s < highest_step:
		best =None
		if table.has_key (s):
			best_difference = INFTY
			for o in table[s]:
				d = (s + o[2] ) - exact_pitch
 				if math.fabs (d) < math.fabs (best_difference):
					best_difference = d
					best = o


		sys.stdout.write (notename (s, sharp) + ' ')
		if best:
			best = (best[0], 0, best_difference)
		sys.stdout.write (entry_str (best) + ' ')

			
		exact_pitch = exact_pitch  + steps[n]
		s = startnote + int (round (exact_pitch-start_pitch ))
		n = (n+1) % 7
		if (n % 7) == 0:
			sys.stdout.write ('\n')
	sys.stdout.write ('\n')




for f in files:
	idx = -1
	for n in names:
		if f in n:
			idx = n.index (f)
	
	for sc in equal_scale, aristoxenus_scale, zarlino_scale:
		sys.stdout.write ('Scale: %s\n' % sc[0])
		scale (sc[1], idx,  table)

