different large refactorings (subdirectores, removed obsolete stuff) and
bug fixes
This commit is contained in:
@ -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 >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(>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>
|
505
extensions/fablabchemnitz/fretruler/fablabchemnitz_fret_ruler.py
Normal file
505
extensions/fablabchemnitz/fretruler/fablabchemnitz_fret_ruler.py
Normal 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
|
399
extensions/fablabchemnitz/fretruler/fablabchemnitz_fret_scale.py
Normal file
399
extensions/fablabchemnitz/fretruler/fablabchemnitz_fret_scale.py
Normal 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
|
Reference in New Issue
Block a user