diff --git a/data.py b/data.py new file mode 100755 index 0000000..63cb28c --- /dev/null +++ b/data.py @@ -0,0 +1,156 @@ +# (product name, unit weight) + +# D'Addario stock via http://www.daddario.com/upload/tension_chart_13934.pdf +# Circle K String stock http://circlekstrings.com/CKSIMAGES/UnitWeightChart130105.pdf + +# string names don't necessarily match up with their actual product names + +daddario_plain_steel = ( + ('DAPL007' , .00001085), + ('DAPL008' , .00001418), + ('DAPL0085', .00001601), + ('DAPL009' , .00001794), + ('DAPL0095', .00001999), + ('DAPL010' , .00002215), + ('DAPL0105', .00002442), + ('DAPL011' , .00002680), + ('DAPL0115', .00002930), + ('DAPL012' , .00003190), + ('DAPL013' , .00003744), + ('DAPL0135', .00004037), + ('DAPL014' , .00004342), + ('DAPL015' , .00004984), + ('DAPL016' , .00005671), + ('DAPL017' , .00006402), + ('DAPL018' , .00007177), + ('DAPL019' , .00007997), + ('DAPL020' , .00008861), + ('DAPL022' , .00010722), + ('DAPL024' , .00012760), + ('DAPL027' , .00014975), +) + +daddario_nickle_wound = ( # XL + ('DANW017' , .00005524), + ('DANW018' , .00006215), + ('DANW019' , .00006947), + ('DANW020' , .00007495), + ('DANW021' , .00008293), + ('DANW022' , .00009184), + ('DANW024' , .00010857), + ('DANW025*', .00011875), # from the EXL110BT, an approximation + ('DANW026' , .00012671), + ('DANW028' , .00014666), + ('DANW030' , .00017236), + ('DANW032' , .00019347), + ('DANW034' , .00021590), + ('DANW036' , .00023964), + ('DANW037*', .00024872), # from the EXL115BT, an approximation + ('DANW038' , .00026471), + ('DANW039' , .00027932), + ('DANW040*', .00029570), # from the EXL120BT, an approximation + ('DANW042' , .00032279), + ('DANW044' , .00035182), + ('DANW046' , .00038216), + ('DANW048' , .00041382), + ('DANW049' , .00043014), + ('DANW050*', .00044720), # from the EXL115BT, an approximation + ('DANW052' , .00048109), + ('DANW054' , .00053838), + ('DANW056' , .00057598), + ('DANW059' , .00064191), + ('DANW060' , .00066542), + ('DANW062' , .00070697), + ('DANW064' , .00074984), + ('DANW066' , .00079889), + ('DANW068' , .00084614), + ('DANW070' , .00089304), + ('DANW072' , .00094124), + ('DANW074' , .00098869), + ('DANW080' , .00115011), +) + +kalium_plain = ( + ('CKPL008 ',.0000142401458191), + ('CKPL0085',.00001607510288), + ('CKPL009', .0000180219146483), + ('CKPL0095',.00002008032129), + ('CKPL010', .0000222518914107), + ('CKPL0105',.00002453144932), + ('CKPL011', .0000269251480883), + ('CKPL0115',.00002942561205), + ('CKPL012', .0000320389593746), + ('CKPL0125',.00003476567932), + ('CKPL013', .0000376052948255), + ('CKPL0135',.00004055150041), + ('CKPL014', .000043607), + ('CKPL015', .000050050), + ('CKPL016', .000056961), + ('CKPL017', .000064300), + ('CKPL018', .000072088), + ('CKPL019', .000080360), + ('CKPL020', .000089031), + ('CKPL021', .000098155), + ('CKPL022', .000107666), + ('CKPL023', .000117702), +) + +kalium_hybrid_wound = ( + ('CKHW021', .000093873), + ('CKHW022', .000103500), + ('CKHW023', .000113985), + ('CKHW024', .000124963), + ('CKHW025', .000136054), + ('CKHW026', .000144691), + ('CKHW027', .000153146), + ('CKHW028', .000161203), + ('CKHW029', .000178551), + ('CKHW031', .000198902), + ('CKHW033', .000223217), + ('CKHW035', .000249034), + ('CKHW037', .000276237), + ('CKHW039', .000304788), + ('CKHW041', .000334965), + ('CKHW043', .000366357), + ('CKHW045', .000404956), + ('CKHW047', .000447408), + ('CKHW049', .000475438), + ('CKHW051', .000512645), + ('CKHW053', .000551898), + ('CKHW055', .000584407), + ('CKHW057', .000625704), + ('CKHW059', .000679149), + ('CKHW061', .000720293), + ('CKHW063', .000765973), + ('CKHW065', .000821116), + ('CKHW067', .000870707), + ('CKHW070', .000939851), + ('CKHW073', .001021518), + ('CKHW076', .001110192), + ('CKHW079', .001188974), + ('CKHW082', .001293598), + ('CKHW086', .001416131), + ('CKHW090', .001544107), + ('CKHW094', .001677765), + ('CKHW098', .001831487), + ('CKHW102', .001986524), + ('CKHW106', .002127413), + ('CKHW112', .002367064), + ('CKHW118', .002616406), + ('CKHW124', .002880915), + ('CKHW130', .003154996), + ('CKHW136', .003441822), + ('CKHW142', .003741715), + ('CKHW150', .004051506), + ('CKHW158', .004375389), + ('CKHW166', .005078724), + ('CKHW174', .005469937), + ('CKHW182', .006071822), + ('CKHW190', .006605072), + ('CKHW200', .007311717), + ('CKHW210', .008037439), + ('CKHW222', .009091287), + ('CKHW232', .009888443), + ('CKHW244', .010907182), + ('CKHW254', .011787319), +) diff --git a/notes.py b/notes.py new file mode 100755 index 0000000..c9a4674 --- /dev/null +++ b/notes.py @@ -0,0 +1,50 @@ +#!/usr/bin/python + +A = 440 +notes = { + 'C' : 0, + 'D' : 2, + 'E' : 4, + 'F' : 5, + 'G' : 7, + 'A' : 9, + 'B' : 11, +} + +rel = (2**(1/12.)) +def note2freq(name): + sharp = name[1] == '#' + flat = name[1] == 'b' + if sharp or flat: + note = name[0:1] + octave = int(name[2]) + else: + note = name[0] + octave = int(name[1]) + num = notes[note] + if sharp: + num += 1 + if flat: + num -= 1 + fullnum = num + 12*(octave - 5) + return A*rel**(fullnum + 3) + +if __name__ == '__main__': + test_fmt = '{:3} gave {: 9.2f} Hz\nexpected {: 9.2f} Hz' + def test(name, expected): + print(test_fmt.format(name, note2freq(name), expected)) + + test('C2' , 65.41) + test('Ab4', 415.30) + test('A4' , 440.00) + test('B4' , 493.88) + test('B#4', 523.25) + test('Cb5', 493.88) + test('C5' , 523.25) + print() + test('E4' , 329.63) + test('B3' , 246.94) + test('G3' , 196.00) + test('D3' , 146.83) + test('A2' , 110.00) + test('E2' , 82.41) diff --git a/run.py b/run.py new file mode 100755 index 0000000..a8f2725 --- /dev/null +++ b/run.py @@ -0,0 +1,81 @@ +#!/usr/bin/python + +from strings import print_ideal_stock + +in_mm = 25.4 +default_scale_length = 648/in_mm + +sets = """ +regular light +E4 16 PD +B3 16 PD +G3 16 PD +D3 16 WD +A2 16 WD +E2 16 WD + +jazz medium +E4 24 PD +B3 24 PD +G3 24 WD +D3 24 WD +A2 24 WD +E2 24 WD + +regular light (DADGAD) +D4 16 PD +A3 16 PD +G3 16 PD +D3 16 WD +A2 16 WD +D2 16 WD + +fanned-fret bass +G2 35 WC 34.00 +D2 35 WC 34.75 +A1 35 WC 35.50 +E1 35 WC 36.25 +B0 35 WC 37.00 + +regular light (Open G 7-string) +D4 16 PD +B3 16 PD +G3 16 PD +D3 16 WD +B2 16 WD +G2 16 WD +D2 16 WD +""" + +string_sets = {} +sets = sets+'\n\n' +title = None +for line in sets.splitlines(): + if not line: + title = None + continue + if not title: + title = line + string_sets[title] = [] + else: + fields = line.split() + note, tension = fields[0:2] + tension = int(tension) + + req = '' + if len(fields) > 2: + req = fields[2] + + length = None + if len(fields) > 3: + length = float(fields[3]) + + string = (note, tension, req, length) + string_sets[title].append(string) + +print('for a scale length of {:>5.2f} inches'.format(default_scale_length)) +for name, strings in sorted(string_sets.items()): + print() + print('"{}"'.format(name)) + print_ideal_stock(strings, default_scale_length) + diff --git a/run2.py b/run2.py new file mode 100755 index 0000000..eb9dfb0 --- /dev/null +++ b/run2.py @@ -0,0 +1,84 @@ +#!/usr/bin/python + +from strings import print_tensions + +in_mm = 25.4 +default_scale_length = 648/in_mm + +sets = """ +E standard (super light balanced) +E4 DAPL009 +B3 DAPL012 +G3 DAPL015 +D3 DANW022 +A2 DANW030 +E2 DANW040 + +E standard (medium balanced) +E4 DAPL011 +B3 DAPL015 +G3 DAPL019 +D3 DANW028 +A2 DANW037 +E2 DANW050 + +C standard (medium balanced) +C4 DAPL011 +G3 DAPL015 +D#3 DAPL019 +A#2 DANW028 +F2 DANW037 +C2 DANW050 + +C standard (jazz medium) +C4 DAPL013 +G3 DAPL017 +D#3 DANW026 +A#2 DANW036 +F2 DANW046 +C2 DANW056 + +C standard (CKS-G6-14-59mb) +C4 CKPL014 +G3 CKPL019 +D#3 CKHW025 +A#2 CKHW033 +F2 CKHW045 +C2 CKHW059 + +B standard (CKS-G6-14-59mb) +B3 CKPL014 +F#3 CKPL019 +D3 CKHW025 +A2 CKHW033 +E2 CKHW045 +B1 CKHW059 + +D standard drop C (medium balanced) +D4 DAPL011 +A3 DAPL015 +F3 DAPL019 +C3 DANW028 +G2 DANW037 +C2 DANW050 +""" + +string_sets = {} +sets = sets+'\n\n' +title = None +for line in sets.splitlines(): + if not line: + title = None + continue + if not title: + title = line + string_sets[title] = [] + else: + note, string = line.split() + string_sets[title].append((note, string)) + +print('for a scale length of {:>5.2f} inches'.format(default_scale_length)) +for name, strings in sorted(string_sets.items()): + print() + print('"{}"'.format(name)) + print_tensions(strings, default_scale_length) diff --git a/strings.py b/strings.py new file mode 100755 index 0000000..b292869 --- /dev/null +++ b/strings.py @@ -0,0 +1,117 @@ +#!/usr/bin/python + +from data import * +from notes import note2freq + +stock = [] +stock += daddario_plain_steel +stock += daddario_nickle_wound +stock += kalium_plain +stock += kalium_hybrid_wound + +uw_const = 386.4 +def uw2tension(uw, freq, SL): + return (uw*(2*SL*freq)**2)/uw_const + +def tension2uw(t, freq, SL): + return (t*uw_const)/(2*SL*freq)**2 + +outfmt = '\ +{:<8} at {:>6.2f} lb ({:>+4.2f})' +finalfmt = '\ +average: {:>7.2f} lb ({:>+5.2f})\n\ +total: {:>7.2f} lb ({:>+5.2f})' +tension_outfmt = '\ +{:<3} {:<8} at {:>6.2f} lb' +tension_finalfmt = '\ +average: {:>7.2f} lb\n\ +total: {:>7.2f} lb' + +def print_ideal_stock(strings, scale_length=25.512): + SL = scale_length + total_tension = 0 + total_desired_tension = 0 + for note, tension, req, length in strings: + freq = note2freq(note) + if length: + SL = length + else: + SL = scale_length + uw = tension2uw(tension, freq, SL) + + kind = len(req) > 0 and req[0] + brand = len(req) > 1 and req[1] + + closest = ('n/a', 0) + for name, stock_uw in stock: + if kind and kind == 'P' and name[2] != 'P': + continue + if kind and kind == 'W' and name[3] != 'W': + continue + if brand and brand[0] != name[0]: + continue + if abs(stock_uw - uw) < abs(closest[1] - uw): + closest = (name, stock_uw) + + closest_tension = uw2tension(closest[1], freq, SL) + diff = closest_tension - tension + print(outfmt.format(closest[0], closest_tension, diff)) + + total_tension += closest_tension + total_desired_tension += tension + + error = total_tension - total_desired_tension + average_tension = total_tension/len(strings) + average_error = error/len(strings) + print(finalfmt.format(average_tension, average_error, total_tension, error)) + +def print_tensions(strings, scale_length=25.512): + SL = scale_length + total_tension = 0 + for note, name in strings: + freq = note2freq(note) + uw = 0 + for stock_name, stock_uw in stock: + if name == stock_name or name + '*' == stock_name: + uw = stock_uw + break + if uw: + tension = uw2tension(uw, freq, SL) + else: + tension = 0 + print(tension_outfmt.format(note, name, tension)) + total_tension += tension + + print(tension_finalfmt.format(total_tension/len(strings), total_tension)) + +if __name__ == '__main__': + # DAd's data is all screwy so we use change scale lengths for a best-fit + SL = 25.4825 + D3 = note2freq('D3') + A2 = note2freq('A2') + E2 = note2freq('E2') + + test_fmt = '{:8} {:10.8f} == {:10}' + def test(name, unit, tension, note): + print(test_fmt.format(name, tension2uw(tension, note, SL), unit)) + + test('NW024' , '0.00010857', 15.73, D3) + test('NW025*', '?' , 17.21, D3) + test('NW026' , '0.00012671', 18.38, D3) + print() + + test('NW036' , '0.00023964', 19.04, A2) + test('NW037*', '? ', 20.23, A2) + test('NW038' , '0.00026471', 20.96, A2) + print() + + SL = 25.18 + test('NW039' , '0.00027932', 12.46, E2) + test('NW040*', '? ', 13.18, E2) + test('NW042' , '0.00032279', 14.37, E2) + print() + + SL = 25.02 + test('NW049' , '0.00043014', 18.97, E2) + test('NW050*', '? ', 19.68, E2) + test('NW052' , '0.00048109', 21.15, E2)