different large refactorings (subdirectores, removed obsolete stuff) and

bug fixes
This commit is contained in:
2020-08-31 21:25:41 +02:00
parent 7aeae6fc55
commit ffcb5ed744
2250 changed files with 764 additions and 142723 deletions

View File

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Fret Ruler</name>
<id>fablabchemnitz.de.fret_ruler</id>
<param name="active-tab" type="notebook">
<page name="ruler" gui-text="Ruler ">
<vbox>
<label>Draw a Ruler for stringed instrument Necks.</label>
<param name="draw_style" gui-text="Draw Style" type="optiongroup" appearance="combo">
<option value="ruler">Ruler</option>
<option value="template">Router Template</option>
<option value="neck">Neck</option>
</param>
<param name="method" gui-text="Calculation Method" type="optiongroup" appearance="combo">
<option value="12root2">12th Root of 2</option>
<option value="18">18</option>
<option value="17.817">17.817</option>
<option value="17.835">17.835</option>
<option value="scala">Scala</option>
<option value="Nroot2">Nth Root of 2</option>
</param>
<label>Next two, only if named method has been selected.</label>
<hbox>
<param name="nth" type="int" min="2" max="50" gui-text="(Method=Nth Root of 2): Notes in Scale:">0</param>
<param name="scala_filename" type="string" gui-text="(Method=Scala): Scala filename:">12tet</param>
</hbox>
</vbox>
<label>Dimensions:</label>
<param name="units" gui-text="Units for following dimensions" type="optiongroup" appearance="combo">
<option value="in">in</option>
<option value="mm">mm</option>
</param>
<param name="length" type="float" min="1" max="1000.0" precision="2" gui-text="Scale Length in units (Ruler length)">25.5</param>
<param name="width" type="float" min="0.5" max="1000.0" precision="2" gui-text="Width (Nut) in units">1.35</param>
<param name="frets" type="int" min="2" max="500" gui-text="Number of frets to draw">18</param>
<label>Fanned:</label>
<param name="fanned" type="bool" gui-text="Fanned fret style (Treble and Bass scale lengths)">false</param>
<hbox>
<param name="basslength" type="float" min="1" max="1000.0" precision="2" gui-text="Bass Scale Length">25.5</param>
<param name="perpendicular" type="int" min="0" max="500" gui-text="Perpendicular Fret">7</param>
</hbox>
<label>Styles:</label>
<param name="linewidth" type="float" min="0.01" max="2.0" precision="2" gui-text="Width of lines (mm)">0.1</param>
<param name="notch_width" type="float" min="0.0001" max="4.0" precision="4" gui-text="(Router template) Width of Fret notches">0.125</param>
<hbox>
<param name="annotate" type="bool" gui-text="Fret numbering">true</param>
<param name="centerline" type="bool" gui-text="Centerline">true</param>
</hbox>
</page>
<page name="neck" gui-text="Neck ">
<param name="descr" type="description">Extra parameters to draw the Neck.</param>
<param name="constant_width" type="bool" gui-text="Constant width (=Nut)">false</param>
<param name="width_bridge" type="float" min="0.5" max="1000.0" precision="2" gui-text="OR: Width (at Bridge) in units">2.0</param>
<param name="show_markers" type="bool" gui-text="Show Markers">false</param>
<param name="markers" type="string" gui-text="Marked frets:">3,5,7,10,12,12,15</param>
<param name="nutcomp" type="bool" gui-text="Include Nut Compensation">false</param>
<param name="nutcomp_value" gui-text="Preset values" type="optiongroup" appearance="combo">
<option value="0.012">0.012in (0.30mm)</option>
<option value="0.014">0.014in (0.36mm)</option>
<option value="manual">Manual</option>
</param>
<param name="nutcomp_manual" type="string" gui-text="Manual nut compensation distance">0.014</param>
</page>
<page name="curvature" gui-text="Curvature ">
<param name="descr" type="description">Additional Neck Curvature Ruler</param>
<param name="show_curves" type="bool" gui-text="Show neck curvature ruler">true</param>
<param name="neck_radius" type="float" min="4" max="100.0" precision="2" gui-text="Radius of Neck curvature in units">9.75</param>
<param name="arc_length" type="float" min="1" max="100.0" precision="2" gui-text="Arc Length (units)">5</param>
<param name="block_mode" type="bool" gui-text="Draw as a block (vs finger)">true</param>
<param name="arc_height" type="float" min="0.1" max="50.0" precision="2" gui-text="Height of the Arc(units)">0.5</param>
<param name="string_spacing" type="float" min="0.1" max="20.0" precision="2" gui-text="String separation(for finger style) (units)">0.3</param>
<param name="descr" type="description" xml:space="preserve">Print or Metal Lasercut as a thin radius guide,
or export to Openscad to make a longer 3d printed neck support or sanding block.
Sizes:
See Help Curve Tab
</param>
</page>
<page name="filters" gui-text="Scala ">
<param name="descr" type="description">This is a helper tab. It does not contribute to drawing the Fret Ruler/Neck.</param>
<param name="descr" type="description">It shows you all the scala files matching the search filters below. Enter the filename on the first tab.</param>
<param name="descr" type="description">This search only works if you can see this tab.</param>
<param name="descr" type="description">There may be &gt;4000 scala files. So choose wisely.</param>
<param name="filter_tones" type="bool" gui-text="Filter by number of tones in a scale.">true</param>
<param name="scale" type="int" min="2" max="1000" gui-text="Notes in a scale:">12</param>
<param name="filter_label" type="bool" gui-text="Filter by word in title / internal description.">true</param>
<param name="keywords" type="string" gui-text="Key word:">diatonic</param>
</page>
<page name="help" gui-text="Help ">
<param name="descr" type="description" xml:space="preserve">USE:
Export as PDF, print in Poster mode for full scale drawing.
Glue onto fretboard and cut - or lasercut Router template.
</param>
<param name="descr" type="description" xml:space="preserve">Methods:
12th Root of 2 - 'preferred' method for even temperment scales.
Note: 12th Root of 2 and the 17.817 give identical results.
17.835 is similar (unrounded calculation) to 17.817, and has max 0.2mm difference in a 24 inch scale.
Markers:
Fret markers are located at different positions based on instrument tuning. (Double entry draws two markers)
E.g. for Ukulele these are common variants:
[3,5,7,10,12,12] (17 frets),
[3,5,7,7,10], [5,7,10], [5,7,12]
[5,7,10,12,15] (19 frets)
Nut Compensation:
Moves the Nut forward a small amount, so that fret1 can have better intonation if Nut shape tuned per string.
Scala filenames:
Are in the scala subdirectory of inkscape extensions. More(&gt;4000) at www.huygens-fokker.org/scala
Neck Curvature Ruler:
Necks can have one radius (e.g. 7.25) at the nut and a wider radius (e.g. 12) at the body.
This called a Conical radius (incorrectly a compound radius). It enables easier single string control at the Nut and chords nearer the body.
</param>
</page>
<page name="help2" gui-text="Help Lengths">
<param name="descr" type="description" xml:space="preserve">Fretboard lengths:
Guitar scale lengths are usually between 24" and 26".
Bass scale lengths generally stay between 30" to 36".
Common scale lengths:
Banjo - 26_3/16 (665.2mm)
Mandolin - 13_7/8 (335mm)
Ukulele concert - 15 (381mm)
Ukulele soprano - 13_3/4 (349.25mm)
Ukulele tenor - 17_3/32 (434.2mm)
Ukulele Baritone - 20_1/8 (511.2mm)
Fender Jaguar - 24 (609.60mm)
Fender Stratocaster/Telecaster - 25.5 (647.70mm)
Fender Jazz Bass - 34 (863.60mm)
Rickenbacker - 24.75 (628.65mm)
Rickenbacker bass - 33.25 (844.55mm)
Gibson Les Paul - 24.75 (628.65mm)
Gibson - 24.625, 24.563, 25.3 (625.48, 623.9, 642.62mm)
Paul Reed Smith - 25 (635mm)
Hofner Beatle Bass - 30 (762mm)
Martin - 24.9, 25.34 (632.5, 643.64mm)
PRS - 25 (635mm)
Baritone - 27.67 (702.82mm)
Short scale bass - 30 (762mm)
Classical guitar 25.6, 26 (650, 660mm)
Baritone guitar 28.5, 30.2 (724, 767mm)
</param>
</page>
<page name="help3" gui-text="Help Curves">
<param name="descr" type="description" xml:space="preserve">Sizes:
Ukulele - typically flat
Guitars - 7.25 to 20inches (Classical is flat)
Violins,Cellos - typically have compound radii to accomodate natural finger reach.
Typical ruler set: 7.25, 9.5, 10, 12, 14, 15, 16, 20
- Fender strat vintage - 7.25 (184.1mm)
- Fender strat modern - 9.5 (241mm)
- Gibson - 12 (305mm)
- Danelectro - 14 (355mm)
- Ibanez RG,S - 15.75-17 (400-430mm)
- Ibanez Artcore, SZ - 12 (305mm)
- PRS - 10 (254mm), 11.5 (292mm)
- Jackson - 16 (406mm)
- Typical electric - 9.5-10 (241-254mm)
- Typical electric + FloydRose Bridge - 10 (254mm)
- Martin Acoustic - 16 (406.4mm)
- Violin - 42mm
</param>
</page>
</param>
<effect needs-live-preview="true">
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Dimensioning/Measuring"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">fablabchemnitz_fret_ruler.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,505 @@
#!/usr/bin/env python3
# Distributed under the terms of the GNU Lesser General Public License v3.0
### Author: Neon22 - github 2016
###
import inkex
import fablabchemnitz_fret_scale as fs
import os # for scala file filtering
from math import radians, cos, sin, pi
from lxml import etree
###----------------------------------------------------------------------------
### Styles - color and size settings
Black = "#000000"
Font_height = 5
# factor used for marker radius
marker_rad_factor = 4
Line_style = { 'stroke' : Black,
'stroke-width' : '0.2px',
'fill' : "none" }
Dash_style = { 'stroke' : Black,
'stroke-width' : '0.1px',
'stroke-dasharray' : '0.9,0.9',
'fill' : "none" }
Label_style = { 'font-size' : str(int(Font_height))+'px',
'font-family' : 'arial',
'text-anchor' : 'end', # middle
'fill' : Black }
Centerline_style = { 'stroke' : Black,
'stroke-width' : '0.1px',
'stroke-dasharray' : '1.2,0.7,0.3,0.7',
'fill' : "none" }
# Helper functions
def build_line(x1, y1, x2, y2, unitFactor):
path = 'M %s,%s L %s,%s' % (x1*unitFactor, y1*unitFactor, x2*unitFactor, y2*unitFactor)
return path
def build_notch(x,y, notch_width, unitFactor, dir=1):
""" draw a notch around the x value
- dir=-1 means notch is on other side
"""
w_2 = notch_width/2
x1 = x - w_2
x2 = x + w_2
y2 = y + notch_width*dir
path = 'L %s,%s L %s,%s' % (x1*unitFactor, y*unitFactor, x1*unitFactor, y2*unitFactor)
path += 'L %s,%s L %s,%s' % (x2*unitFactor, y2*unitFactor, x2*unitFactor, y*unitFactor)
return path
def draw_center_cross(x,y, parent, length=2, style=Line_style):
" center cross for holes "
d = 'M {0},{1} l {2},0 M {3},{4} l 0,{2}'.format(x-length,y, length*2, x,y-length)
cross_attribs = { inkex.addNS('label','inkscape'): 'Center cross',
'style': str(inkex.Style(style)), 'd': d }
etree.SubElement(parent, inkex.addNS('path','svg'), cross_attribs )
def draw_SVG_circle(cx, cy, radius, parent, name='circle', style=Line_style):
" structure an SVG circle entity under parent "
circ_attribs = {'style': str(inkex.Style(style)),
'cx': str(cx), 'cy': str(cy),
'r': str(radius),
inkex.addNS('label','inkscape'): name}
circle = etree.SubElement(parent, inkex.addNS('circle','svg'), circ_attribs )
def draw_circle_marker(x,y, radius, parent):
" circle with cross at center "
draw_center_cross(x, y, parent, radius/5.0)
draw_SVG_circle(x, y, radius, parent)
###
class Fret_ruler(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
# main tab
self.arg_parser.add_argument('--method', default='12th Root of 2', help="Method to calculate scale")
self.arg_parser.add_argument('--draw_style', default='Ruler', help="How to draw the Ruler/NEck")
self.arg_parser.add_argument("--nth", type=int,default=12, help="For different number of notes in a scale")
self.arg_parser.add_argument('--scala_filename', default='12tet', help="Name of file in scales directory")
self.arg_parser.add_argument("--units", default="in", help="The units of entered dimensions")
self.arg_parser.add_argument("--length", type=float, default=25.5, help="Length of the Scale (and Ruler)")
self.arg_parser.add_argument("--width", type=float, default=1.5, help="Width of the Ruler (= Nut if drawing a neck)")
self.arg_parser.add_argument("--frets", type=int, default=18, help="number of frets on the scale")
#
self.arg_parser.add_argument("--fanned", type=inkex.Boolean, default=False, help="Two scales on either side of the Neck")
self.arg_parser.add_argument("--basslength", type=float, default=25.5, help="Length of the Bass side Scale")
self.arg_parser.add_argument("--perpendicular", type=int, default=7, help="Fret number which is perpendicular to the Neck")
#
self.arg_parser.add_argument("--linewidth", type=float, default=0.1, help="Width of drawn lines")
self.arg_parser.add_argument("--notch_width", type=float, default=0.125, help="Width of Fret notches on Router template")
self.arg_parser.add_argument("--annotate", type=inkex.Boolean, default=True, help="Annotate with Markers etc")
self.arg_parser.add_argument("--centerline", type=inkex.Boolean, default=True, help="Draw a centerline")
# Neck
self.arg_parser.add_argument("--constant_width", type=inkex.Boolean, default=True, help="Use Bridge width as well to make Neck")
self.arg_parser.add_argument("--width_bridge", type=float, default=2.0, help="Width at the Bridge (drawing Neck not Ruler)")
self.arg_parser.add_argument("--show_markers", type=inkex.Boolean, default=False, help="Show Neck Marker Positions")
self.arg_parser.add_argument('--markers', default='3,5,7,10,12,12,15', help="List of frets to draw markers on")
#
self.arg_parser.add_argument("--nutcomp", type=inkex.Boolean, default=False, help="Modify Nut position")
self.arg_parser.add_argument("--nutcomp_value", default="0.012in (0.30mm)", help="Preset (usual) Nut compensation values")
self.arg_parser.add_argument("--nutcomp_manual", type=float, default=0.014, help="Manual distance to move Nut closer to Bridge")
#
self.arg_parser.add_argument("--show_curves", type=inkex.Boolean, default=False, help="Show a neck curvature ruler")
self.arg_parser.add_argument("--neck_radius", type=float, default=2.0, help="Radius of Neck curvature")
self.arg_parser.add_argument("--arc_length", type=float, default=2.0, help="Length of Arc")
self.arg_parser.add_argument("--block_mode", type=inkex.Boolean, default=False, help="Draw block or finger style")
self.arg_parser.add_argument("--arc_height", type=float, default=2.0, help="height of Arc")
self.arg_parser.add_argument("--string_spacing", type=float, default=2.0, help="Spacing between strings")
#
self.arg_parser.add_argument("--filter_tones", type=inkex.Boolean, default=True, help="Only show Scala files with this many notes in a scale.")
self.arg_parser.add_argument("--scale", type=int, default=12, help="number of Notes in the scale")
self.arg_parser.add_argument("--filter_label", type=inkex.Boolean, default=True, help="Only show Scala files with this keyword in them.")
self.arg_parser.add_argument("--keywords", default="diatonic", help="Keywords to search for")
# here so we can have tabs
self.arg_parser.add_argument("-t", "--active-tab", default='ruler', help="Active tab.")
def filter_scala_files(self, parent):
""" Look in the scale directory for files.
- show only files matching the filters
"""
filter_tones = self.options.filter_tones
filter_names = self.options.filter_label
numtones = self.options.scale
keywords = self.options.keywords
keywords = keywords.strip().split(',')
keywords = [k.lower() for k in keywords]
#
probable_dir = os.getcwd()+'/scales/'
files = os.listdir(probable_dir)
# inkex.utils.debug("%s"%([os.getcwd(),len(files)]))
# Display filenames in document
filenames = [["Searched %d files"%(len(files)), "Found no matches", 0]]
for f in files:
fname = probable_dir+f
data = fs.read_scala(fname, False)
# filter out files that don't match
if filter_tones and filter_names:
if numtones == data[1]:
if filter_names:
for k in keywords:
if data[0].find(k) > -1 or f.find(k) > -1:
filenames.append([f, data[0], data[1]])
elif filter_tones:
if numtones == data[1]:
filenames.append([f, data[0], data[1]])
elif filter_names:
for k in keywords:
if data[0].find(k) > -1 or f.find(k) > -1:
filenames.append([f, data[0], data[1]])
# inkex.utils.debug("%s"%(filenames))
# gathered them all - display them
if len(filenames) != 0:
filenames[0][1] = "Found %d matches"%(len(filenames)-1)
x = 0
y = 0
Label_style['text-anchor'] = 'start'
for f in filenames:
label = f[0]
if f[2] != 0:
label += " - (%d tones)"%(f[2])
self.draw_label(x, y, label, parent)
self.draw_label(x+Font_height*2, y+Font_height*1.2, f[1], parent)
if y ==0: y += Font_height
y += Font_height*2.8
Label_style['text-anchor'] = 'end'
###
def draw_label(self, x,y, label, parent, transform=False, style=Label_style):
" add a text entity "
text_atts = {'style':str(inkex.Style(style)),
'x': str(x), 'y': str(y) }
if transform: text_atts['transform'] = transform
text = etree.SubElement(parent, 'text', text_atts)
text.text = "%s" %(label)
###
def draw_ruler(self, neck, parent, show_numbers=False):
" draw the ruler with the centre of nut at 0,0 (unless fanned)"
# fanned frets have a bass side as well as the normal(treble) side scale length
# assume fanned
treble_length = neck.length
bass_length = treble_length if not neck.fanned else neck.bass_scale
y1 = neck.nut_width/2
y2 = neck.bridge_width/2
startx = 0
endx = 0
# if neck is fanned - adjust start, end
if neck.fanned:
if neck.fan_offset > 0:
startx = neck.fan_offset
else:
endx = -neck.fan_offset
pts = [[treble_length+startx,-y2], [bass_length+endx,y2], [endx,y1]]
# Create the boundary(neck) paths
path = 'M %s,%s ' % (startx*self.convFactor, -y1*self.convFactor)
for i in range(3):
path += " L %s,%s "%(pts[i][0]*self.convFactor, pts[i][1]*self.convFactor)
path += "Z"
line_attribs = {'style' : str(inkex.Style(Line_style)),
inkex.addNS('label','inkscape') : 'Outline' }
line_attribs['d'] = path
ell = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
# Draw the fret lines
distances = neck.frets # do the zeroth value as well
for count, xt in enumerate(distances): # seq of x offsets for each fret
xb = xt if not neck.fanned else neck.bass_frets[count]
# if neck is not straight, calc the extra bit to draw in Y
yt = yb = y1
if y1 != y2: # neck not straight
yt = y1 + ((xt-startx)/float(treble_length) * (y2-y1))
yb = y1 + ((xb-endx)/float(bass_length) * (y2-y1))
path = build_line(xt, -yt, xb, yb, self.convFactor)
line_attribs['d'] = path
ell = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs)
# Fret Numbers on odd frets(+octave)
if show_numbers and (count%2 == 0 or count == neck.notes_in_scale-1):
# try to push the lower fret numbers to the right a little
Label_style['text-anchor'] = 'start' if count < 9 else 'middle'
label_pos = neck.find_mid_point(count, -neck.nut_width/3)
self.draw_label(label_pos[0]*self.convFactor, label_pos[1]*self.convFactor, count+1, parent)
Label_style['text-anchor'] = 'end'
def draw_router_template(self, neck, parent, notch_width, show_numbers=False):
" draw the ruler as a notched router template "
length = neck.length
y = neck.nut_width/2
startx = notch_width*6
pts = [[length,-y], [length,y], [-startx,y]]
path = 'M %s,%s ' % (-startx*self.convFactor, -y*self.convFactor) # start
distances = [0]
distances.extend(neck.frets)
# style
line_attribs = {'style' : str(inkex.Style(Line_style)),
inkex.addNS('label','inkscape') : 'Outline' }
# draw the fret notches, lines, labels
for count, x in enumerate(distances):
path += build_notch(x,-y, notch_width, self.convFactor)
if show_numbers and (count%2 == 1 or count == 0 or count == neck.notes_in_scale):
Label_style['text-anchor'] = 'start' if count < 9 else 'middle'
self.draw_label(x*self.convFactor-Font_height, -y*self.convFactor+Font_height*2.2, count, parent)
Label_style['text-anchor'] = 'end'
# other side markers
path2 = build_line(x, y, x, notch_width*2-y, self.convFactor)
line_attribs['d'] = path2
ell = etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs)
# close other side of template
for i in range(3):
path += " L %s,%s "%(pts[i][0]*self.convFactor, pts[i][1]*self.convFactor)
path += "Z"
# Draw
line_attribs['d'] = path
etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
def draw_neck_curve_ruler(self, neck, radius, arc_length, arc_height, string_spacing, parent):
" draw arcs for curved fretboards "
# perfect world draw ruler and lines to curved ruler.
# mode = 'block1'
block_mode = self.options.block_mode
tab_length = arc_height*3 * self.convFactor
diam_in = radius * 2 * self.convFactor
angle_d = 180*arc_length / (2*pi*radius)
angle = radians(angle_d)
dist = arc_height*self.convFactor
path = "M%s %s L%s %s" %(diam_in + dist,0, diam_in,0)
x_a = diam_in * cos(angle)
y_a = diam_in * sin(angle)
x_b = (diam_in + dist) * cos(angle)
y_b = (diam_in + dist) * sin(angle)
path += " A %s,%s 0 0 1 %s %s" % (diam_in, diam_in, x_a, y_a)
path += " L%s %s" %(x_b, y_b)
if block_mode:
# use a solid block style
# add a midpoint for users to play with
path += " L%s %s" % (diam_in+dist+(x_b-diam_in-dist)/2, y_b/2)
tab_length = 0
else: # tab mode
# need another arc with tab sections
small_angle = radians(90*string_spacing / (2*pi*radius))
angle2 = angle/2 + small_angle
angle3 = angle/2 - small_angle
x_c = (diam_in + dist) * cos(angle2)
y_c = (diam_in + dist) * sin(angle2)
x_d = (diam_in + dist + tab_length) * cos(angle2)
y_d = (diam_in + dist + tab_length) * sin(angle2)
x_e = (diam_in + dist + tab_length) * cos(angle3)
y_e = (diam_in + dist + tab_length) * sin(angle3)
x_f = (diam_in + dist) * cos(angle3)
y_f = (diam_in + dist) * sin(angle3)
path += " A %s,%s 0 0 0 %s %s" % (diam_in, diam_in, x_c, y_c)
path += " L%s %s" %(x_d, y_d)
path += " L%s %s" %(x_e, y_e)
path += " L%s %s" %(x_f, y_f)
path += " A %s,%s 0 0 0 %s %s" % (diam_in, diam_in, diam_in + dist, 0)
# close path
path += 'z'
ypos = diam_in + dist + tab_length + self.options.width*self.convFactor
line_attribs = {'style' : str(inkex.Style(Line_style)), inkex.addNS('label','inkscape') : 'Neck Curve',
'transform': 'rotate(%f) translate(%s,%s)' % (-angle_d/2 -90, -ypos,-dist)}
line_attribs['d'] = path
etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
# label
size = "%d"%radius if radius-int(radius) == 0 else "%4.2f"%(radius)
Label_style['text-anchor'] = 'start'
self.draw_label(0, 0, "Radius: %s%s"% (size, neck.units), parent,
transform='translate(%s,%s)'%(0,ypos-diam_in-dist/2))
Label_style['text-anchor'] = 'end'
def draw_title(self, neck, parent, initial="Fret Ruler:"):
" Draw list of labels far right of ruler/Neck "
labels = [initial]
length = "%d"%neck.length if neck.length-int(neck.length) == 0 else "%4.2f"%(neck.length)
if neck.fanned:
basslength = "%d"%neck.bass_scale if neck.bass_scale-int(neck.bass_scale) == 0 else "%4.2f"%(neck.bass_scale)
labels.append("Scale(Fanned): %s%s - %s%s" %(length, neck.units, basslength, neck.units))
else: # not fanned
labels.append("Scale: %s%s, %d frets" %(length, neck.units, len(neck.frets)))
#
label2 = "Method: %s" % (neck.method.title())
if neck.method == 'scala':
label2 += " (%s) %d tones" %(neck.scala.split('/')[-1], len(neck.scala_notes))
labels.append(label2)
labels.append('"%s"' %(neck.description))
else:
labels.append(label2)
# unit formatting
units = self.options.units
precision = 1 if units=='mm' else 2
widthN = self.options.width
widthB = self.options.width_bridge
label_w = "{:4.{prec}f}{}".format(widthN, units, prec=precision)
if not self.options.constant_width:
label_w += "(Nut) - {:4.{prec}f}{}(Bridge)".format(widthB, units, prec=precision)
labels.append("Width: %s"%(label_w))
if not self.options.constant_width and len(neck.frets)>11:
distance12 = neck.frets[11]
# inkex.utils.debug("%s"%([distance12/float(neck.length)]))
width12 = widthN + (distance12/float(neck.length) * (widthB-widthN))
labels.append("(at 12th fret: {:4.{prec}f}{})".format(width12, units, prec=precision))
# where to draw
starty = widthN if self.options.constant_width else widthB
y = -starty/2*self.convFactor + Font_height*1.2
x_offset = 0
if neck.fanned and self.options.draw_style != 'template':
x_offset = neck.fan_offset
x = neck.length*self.convFactor - Font_height*1.5 + x_offset*self.convFactor
# Draw
for label in labels:
self.draw_label(x,y, label, parent)
y += Font_height*1.2
def draw_nut_compensation(self, neck, distance, parent):
" "
# inkex.utils.debug("%s"%([distance]))
startx = 0
endx = 0
if neck.fanned:
if neck.fan_offset > 0:
startx = neck.fan_offset
else:
endx = -neck.fan_offset
y = self.options.width/2
path = build_line(startx+distance, -y, endx+distance, y, self.convFactor)
line_attribs = {'style' : str(inkex.Style(Dash_style)), 'd':path,
inkex.addNS('label','inkscape') : 'Nut Compensation' }
etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs)
def draw_neck_markers(self, neck, parent):
" draw symbol at fret pos. N possible "
# list may contain several occurences of a fret - meaning draw dots equidistant
positions = neck.frets
try: # user input weirdness
locations = self.options.markers.strip().split(",")
counts = [[int(i),locations.count(i)] for i in locations if i]
fret_counts = []
for f in counts:
if f not in fret_counts: fret_counts.append(f)
except:
inkex.errormsg("Could not parse list of fret numbers. E.g. 3,5,7,7")
fret_counts = [[3,1]]
# marker radius based on thinnest of the (to be marked) fret spacings
spacings = [neck.frets[f-1] - neck.frets[max(0,f-2)] for f,c in fret_counts if f < len(neck.frets)+1]
thinnest = min(spacings)
marker_radius = thinnest/marker_rad_factor*self.convFactor
for fret, count in fret_counts:
if fret <= len(positions): # ignore if > #frets on this neck
# inkex.utils.debug("%s"%([fret,count,positions[fret]]))
fret = fret-1
if count == 1: # if odd, draw in center
markerpos = neck.find_mid_point(fret, 0)
draw_circle_marker(markerpos[0]*self.convFactor, markerpos[1]*self.convFactor, marker_radius, parent)
else: # draw several at that fret
sep = neck.nut_width/float(count+2)
for i in range(count):
markerpos = neck.find_mid_point(fret, sep*i*2 - sep*(count-1))
draw_circle_marker(markerpos[0]*self.convFactor, markerpos[1]*self.convFactor, marker_radius, parent)
###
def effect(self):
# calc units conversion
self.convFactor = self.svg.unittouu("1" + self.options.units)
# fix line width
Line_style['stroke-width'] = self.svg.unittouu(str(self.options.linewidth) + "mm")
# Usually we want 12 tone octaves
numtones = 12
if self.options.method == 'Nroot2':
numtones = int(self.options.nth)
self.options.method = '%droot2'%(numtones)
# Usually we don't want a scala file
scala_filename=False
if self.options.method == 'scala':
scala_filename = "scales/"+self.options.scala_filename
if scala_filename[-4:] != ".scl":
scala_filename += ".scl"
# Create group center of view
t = 'translate(%s,%s)' % (self.svg.namedview.center[0]-self.options.length*self.convFactor/2, self.svg.namedview.center[1])
grp_attribs = {inkex.addNS('label','inkscape'):'Fret Ruler', 'transform':t}
grp = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
page = self.options.active_tab[1:-1]
draw_style = self.options.draw_style
# check if on Scala filters page
if page == 'filters':
# display filtered scala files
self.filter_scala_files(grp)
else: # Regular action of drawing a Ruler...
# select which style to draw based on user choice and what page they're on...
# if on Ruler page then use draw_style
title = "Fret Ruler:"
if page == 'neck': draw_style = 'neck'
if page == 'ruler' and draw_style=='ruler' or draw_style=='template':
# override constant width if on Ruler page
self.options.constant_width = True
# calc fret widths
fret_width = self.options.width
if (page == 'neck' or draw_style=='neck'):
title = "Neck Ruler:"
if not self.options.constant_width:
fret_width = [self.options.width, self.options.width_bridge]
# Make the Neck
neck = fs.Neck(self.options.length, units=self.options.units, fret_width=fret_width)
neck.calc_fret_offsets(self.options.length, self.options.frets, self.options.method,
numtones=numtones, scala_filename=scala_filename)
if self.options.fanned:
# fanned frets so calc bass scale and adjust
perpendicular = min(self.options.perpendicular, len(neck.frets))
off = neck.set_fanned(self.options.basslength, perpendicular)
if draw_style=='template':
notch = self.options.notch_width
title = "Router Template:"
self.draw_router_template(neck, grp, notch, self.options.annotate)
else:
self.draw_ruler(neck, grp, self.options.annotate)
self.draw_title(neck, grp, title)
if self.options.centerline and self.options.draw_style != 'template':
path = build_line(-0.5,0, max(neck.length, neck.bass_scale)+0.5, 0, self.convFactor)
line_attribs = {'style' : str(inkex.Style(Centerline_style)), 'd':path,
inkex.addNS('label','inkscape') : 'Centerline' }
etree.SubElement(grp, inkex.addNS('path','svg'), line_attribs)
# Neck specials
if page == 'neck' or draw_style=='neck':
# Nut compensation
if self.options.nutcomp:
value = self.options.nutcomp_value
try:
compensation = float(value) if value != 'manual' else float(self.options.nutcomp_manual)
self.draw_nut_compensation(neck, compensation, grp)
except:
inkex.errormsg("Could not determine Nut compensation. Use a number.")
# Markers
if self.options.show_markers:
self.draw_neck_markers(neck, grp)
# inkex.utils.debug("#%s#"%(ordered_chords))
if self.options.show_curves:
# position below max height of title text
self.draw_neck_curve_ruler(neck, self.options.neck_radius, self.options.arc_length, self.options.arc_height, self.options.string_spacing, grp)
# Create effect instance and apply it.
if __name__ == '__main__':
Fret_ruler().run()
### TODO:
# - draw option for fret0 hole to hang ruler from
# - draw strings
# - how many strings
# - separation distance
# - work out interval offsets
# - calc bridge compensation
# - calc stretch compensation
# - draw side view with bridge, relief distances
#BUGS:
#
# Links:
# Nut compensation: http://www.lmii.com/scale-length-intonation

View File

@ -0,0 +1,399 @@
#!/usr/bin/env python3
# Distributed under the terms of the GNU Lesser General Public License v3.0
### Author: Neon22 - github 2016
### fret scale calculation code
from math import log, floor
def fret_calc_ratio(length, howmany, ratio):
" given the ratio between notes, calc distance between frets "
# typically 18, 17.817, 17.835 for equal temperment scales
distances = []
prev = 0
for i in range(howmany):
distance = length / ratio
distances.append(prev+distance)
length -= distance
prev += distance
# print "%02d %6.4f %s" %(i, prev, distance)
return distances
def fret_calc_root2(length, howmany, numtones=12):
" using Nroot2 method, calc distance between frets "
distances = []
for i in range(howmany):
# Calculating Fret Spacing for a Single Fret
# d = s-(s/ (2^ (n/12)))
distance = length - (length / (pow(2, (i+1)/(float(numtones))) ))
distances.append(distance)
# print "%02d %6.4f" %(i, distance)
return distances
def fret_calc_scala(length, howmany, scala_notes):
" use ratios from scala file, calc distance between frets "
distances = []
for i in range(howmany):
if i < len(scala_notes):
r = scala_notes[i]
else:
end = pow(scala_notes[-1], int(i / float(len(scala_notes))))
r = end * scala_notes[i%len(scala_notes)]
distance = length - (length / r)
distances.append(distance)
return distances
def cents_to_ratio(cents):
" given a value in cents, calculate the ratio "
return pow(2, cents / 1200.0)
def parse_scala(scala, filename, verbose=True):
""" Parse the readlines() from scala file into:
- description, numnotes,
- lists of pretty ratios, numeric ratios
"""
description = ""
numnotes = 0
notes = []
ratios = []
error = False
# print scala
for line in scala:
try:
# take out leading and trailing spaces - get everything up to first space if exists
line = line.strip() # hold onto this for when we need the description
first = line.split()[0] # first element in the line
# print line
if first and first[0] != "!": # ignore all blank and comment lines
if not description:
# expecting description line first
# may contain unprintable characters - force into unicode
description = unicode(line, errors='ignore')
elif numnotes == 0:
# expecting notes count after description
numnotes = int(first)
else: # expecting sequences of notes
notes.append(first) # for later ref
# remove comments at end of line if exist
if first.count("!") > 0:
first = first[:first.find("!")]
if first.find('.') > -1: # cents
ratios.append(cents_to_ratio(float(first)))
elif first.find("/") > -1: # ratio
num, denom = first.split('/')
ratios.append(int(num)/float(denom))
else:
ratios.append(int(first))
except:
error = "ERROR: Failed to load "+filename
#
if verbose:
print ("Found:", description)
print ("",numnotes, "notes found.")
for n,r in zip(notes,ratios):
print (" %4.4f : %s"%(r, n))
print (" check: indicated=found : %d=%d"%(numnotes,len(notes)))
if error:
return [error, numnotes, notes, ratios]
else:
return [description, numnotes, notes, ratios]
def read_scala(filename, verbose=False):
" read and parse scala file into interval ratios "
try:
inf = open(filename, 'rB')
content = inf.readlines()
inf.close()
flag = verbose
# if filename.find("dyadic") > -1: flag = True
return parse_scala(content, filename, flag)
except:
return ["ERROR: Failed to load "+filename, 2, [1], [1.01]]
### frequency to note
def log_note(freq):
" find the octave the note is in "
octave = (log(freq) - log(261.626)) / log (2) + 4.0
return octave
def freq_to_note(freq):
lnote = log_note(freq)
octave = floor(lnote)
cents = 1200 * (lnote - octave)
notes = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
offset = 50.0
x = 1
if cents < 50:
note = "C"
elif cents >= 1150:
note = "C"
cents -= 1200
octave += 1
else:
for j in range(1,12):
if offset <= cents < (offset + 100):
note = notes[x]
cents -= (j * 100)
break
offset += 100
x += 1
return "%s%d"%(note, int(octave)), "%4.2f"%(cents)
def int_or_float(value):
" true if value is an int or a float "
return type(value) == type(1) or type(value) == type(1.0)
### class to hold info about instrument necks
class Neck(object):
def __init__(self, length, strings=['G','C','E','A'], units='in', spacing=0.4, fret_width=1.5):
" "
# coerce single spacing value into a list of nut/bridge spacing
self.set_spacing(spacing)
# same for fret_width
self.set_width(fret_width)
#
self.length = length
self.strings = strings
self.units = units
self.frets = [] # Treble side frets if fanned
self.bass_frets =[]
self.fanned = False
self.bass_scale = 0
self.fanned_vertical = False
self.method = '12root2'
self.notes_in_scale = False
# Scala
self.scala = False
self.description = False
self.scala_notes = False
self.scala_ratios = False
def __repr__(self):
extra = ""
if len(self.frets)>0:
extra += "%d frets"%(len(self.frets))
if self.method == 'scala':
extra += "(%s)" %(self.scala.split('/')[-1]) # filename
return "<Neck: %s -%4.2f(%s) %s %d strings>"%(self.method, self.length, self.units, extra, len(self.strings))
def set_width(self, fret_width):
" get both values from this "
if int_or_float(fret_width):
fret_width = [fret_width,fret_width]
elif type(fret_width) != type([]):
fret_width = [1,1]
self.nut_width = fret_width[0]
self.bridge_width = fret_width[1]
def set_spacing(self, spacing):
" get both values from this "
if int_or_float(spacing):
spacing = [spacing,spacing]
elif type(spacing) != type([]):
spacing = [1,1]
self.nut_spacing = spacing[0]
self.bridge_spacing = spacing[1]
def set_fanned(self, bass_scale, vertical_fret):
""" keep existing treble calc and create Bass calc
- must have called calc_fret_offsets() before
(so notes_in_scale is set)
"""
# adjust the position of the treble side if required.
# calc fret_offset and if treble or bass side needs to be moved
# if treble - move self.frets
# if bass, add offset as calculated
treble = self.frets
# print treble
if self.method == 'scala':
bass = self.calc_fret_offsets(bass_scale, len(self.frets), method=self.method, scala_filename=self.scala)
else:
bass = self.calc_fret_offsets(bass_scale, len(self.frets), method=self.method, numtones=self.notes_in_scale)
offset = 0 if vertical_fret ==0 else bass[vertical_fret - 1] - treble[vertical_fret - 1]
# print "offset", offset, "bass",bass
if offset > 0:
# shift treble
for i in range(len(treble)):
treble[i] += offset
else: # shift bass
for i in range(len(bass)):
bass[i] -= offset
self.frets = treble
self.bass_frets = bass
self.bass_scale = bass_scale
self.fanned_vertical = vertical_fret
self.fan_offset = offset
self.fanned = True
return offset
def find_mid_point(self, fret_index, width_offset):
""" find midpoint of fret, fret-1 along neck
and ///y width where width_offset=0 means center of neck
"""
y_factor = (width_offset + self.nut_width/2) / float(self.nut_width)
# assume fanned
tpos_f1 = self.frets[fret_index]
bpos_f1 = tpos_f1 if not self.fanned else self.bass_frets[fret_index]
if self.fanned:
if self.fan_offset >= 0:
tpos_f0 = self.fan_offset if fret_index<=1 else self.frets[fret_index-1]
bpos_f0 = 0 if fret_index<=1 else self.bass_frets[fret_index-1]
else:
bpos_f0 = -self.fan_offset if fret_index<=1 else self.bass_frets[fret_index-1]
tpos_f0 = 0 if fret_index<=1 else self.frets[fret_index-1]
else:
tpos_f0 = 0 if fret_index<=1 else self.frets[fret_index-1]
bpos_f0 = 0 if fret_index<=1 else tpos_f0
#
mid_tpos = tpos_f0 + (tpos_f1 - tpos_f0)/2
mid_bpos = bpos_f0 + (bpos_f1 - bpos_f0)/2
# print fret_index, y_factor
# print " %4.2f %4.2f %4.2f"% (tpos_f0, tpos_f1, mid_tpos)
# print " %4.2f %4.2f %4.2f"% (bpos_f0, bpos_f1, mid_bpos)
# the mid_xx positions are self.nut_width apart
return [mid_tpos + (mid_bpos-mid_tpos)*y_factor, width_offset/self.nut_width*1.5]
def calc_fret_offsets(self, length, howmany, method='12root2', numtones=12, scala_filename=False):
" calc fret positions from Nut for all methods "
frets = False # store them in here
if scala_filename:
scala_notes = read_scala(scala_filename)
self.method = 'scala'
self.scala = scala_filename
self.description = scala_notes[0]
self.scala_notes = scala_notes[2]
self.scala_ratios = scala_notes[3] # [-1]
frets = fret_calc_scala(length, howmany, self.scala_ratios)
self.notes_in_scale = len(self.scala_ratios)
elif method.find('root2') > -1:
self.method = method
frets = fret_calc_root2(length, howmany, numtones)
self.notes_in_scale = numtones
elif method == '18':
self.method = method
ratio = 18
frets = fret_calc_ratio(length, howmany, ratio)
self.notes_in_scale = 12
elif method == '17.817':
self.method = method
ratio = 17.81715374510580
frets = fret_calc_ratio(length, howmany, ratio)
self.notes_in_scale = 12
elif method == '17.835':
self.method = method
ratio = 17.835
frets = fret_calc_ratio(length, howmany, ratio)
self.notes_in_scale = 12
# update the iv
self.frets = frets
return frets
def show_frets(self):
" pretty print "
for i,d in enumerate(self.frets):
print ("%2d: %4.4f" %(i+1,d))
if self.bass_frets:
for i,d in enumerate(self.bass_frets):
print ("%2d: %4.4f" %(i+1,d))
def compare_methods(self, howmany, verbose=True):
" show differences in length for the main methods (not scala) "
distances = []
differences = []
methods = ['12root2', '18', '17.817', '17.835']
n = Neck(30) # long one to maximise errors
for method in methods:
distances.append(n.calc_fret_offsets(n.length, howmany, method))
# print distances[-1]
for i in range(1, len(methods)):
differences.append( [a-b for (a,b) in zip(distances[0], distances[i])] )
if verbose:
print("Differences from 12root2")
for i,m in enumerate(methods[1:]):
print ("\nMethod = %s\n " %(m))
for d in differences[i]:
print ("%2.3f " %(d))
print("")
# package
combined = []
for i,m in enumerate(methods[1:]):
combined.append([m, max(differences[i]), differences[i]])
return combined
# Gibson "rule of 18" base scale is in sys 18.
# Martin 24.9 (24.84), 25.4 (act 25.34) rough approx and round up. not actually the scale length
# The difference between 17.817 and 17.835 came from rounding early and carrying the roundoff error through the rest of the work.
# where r = twelfth root of two and put the first fret where it would make the sounding length of the string 1/r of its original length
### tests
if __name__ == "__main__":
n = Neck(24)
f = n.calc_fret_offsets(n.length, 12, '12root2')
n.show_frets()
print (n)
errors = n.compare_methods(22, False)
for m,e,d in errors:
print ("for method '%s': max difference from 12Root2 = %4.3f%s (on highest fret)"%(m,e, n.units))
#
n = Neck(24)
f = n.calc_fret_offsets(n.length, 22, 'scala', scala_filename='scales/diat_chrom.scl')
n.show_frets()
print ("Fanning")
# n.set_fanned(25,0)
# n.show_frets()
# print n
# print n.description
# print n.scala
# print n.scala_notes
# print n.scala_ratios
# similar to scale=10 to scale = 9.94 but slightly diff neaer the nut.
# scala_notes = read_scala("scales/alembert2.scl")#, True)
# print "Notes=",len(scala_notes[-1]), scala_notes[1]
# for d in fret_calc_scala(24, scala_notes[-1]): print d
# test load all scala files
# import os
# probable_dir = "scales/"
# files = os.listdir(probable_dir)
# for f in files:
# fname = probable_dir+f
# # print f
# data = read_scala(fname)
# # print " ",data[0]
# if data[0][:5] == "ERROR":
# print "!!!! ERROR",fname
## freq conversion
print("")
for f in [440,443,456,457, 500,777, 1086]:
print (f, freq_to_note(f))
## fanned frets
# print
# for f in [1,11]:
# print n.find_mid_point(f,-0.75)
# get to this eventually
string_compensation = [
0.0086, 0.0119, 0.0107, 0.0124, 0.0151, 0.0175, 0.020, 0.0222, 0.0244, 0.0263,
0.0282, 0.030, 0.0371, 0.4235
]
### Optionally:
# how many strings,
# (associated sequence of intervals)
### refs:
#http://fretfocus.anastigmatix.net/
#http://windworld.com/features/tools-resources/exmis-fret-placement-calculator/
#http://www.huygens-fokker.org/scala/
# superstart:
# notes on the fretboard
# https://www.youtube.com/watch?v=-jW1Xx0t3ZI