diff --git a/extensions/fablabchemnitz_pianoscale.inx b/extensions/fablabchemnitz_pianoscale.inx
new file mode 100644
index 00000000..0ae7b671
--- /dev/null
+++ b/extensions/fablabchemnitz_pianoscale.inx
@@ -0,0 +1,46 @@
+
+
+ Piano Scale
+ fablabchemnitz.de.pianoscale
+ C1
+ B2
+ C1
+
+
+ 2212221
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz_pianoscale.py b/extensions/fablabchemnitz_pianoscale.py
new file mode 100644
index 00000000..15cc3834
--- /dev/null
+++ b/extensions/fablabchemnitz_pianoscale.py
@@ -0,0 +1,331 @@
+#!/usr/bin/env python3
+
+'''
+svgPianoScale.py
+Inkscape generator plugin for automatic creation schemes of musical scales and chords.
+
+Copyright (C) 2011 Iljin Alexender
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+'''
+
+__version__ = "1.0.1"
+# Original by Alexander Iljin
+# Some mods to 0.91 by Neon22 2016
+
+import inkex
+import re
+import math
+from datetime import *
+from lxml import etree
+
+notes = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
+keys_color = ('W', 'B', 'W', 'B', 'W', 'W', 'B', 'W', 'B', 'W', 'B', 'W') # 12 notes
+keys = {'C':'W', 'C#':'B', 'D':'W', 'D#':'B', 'E':'W', 'F':'W', 'F#':'B', 'G':'W',
+ 'G#':'B', 'A':'W', 'A#':'B', 'B':'W' }
+keys_numbers = {'C':'0', 'C#':'0', 'D':'1', 'D#':'1', 'E':'2', 'F':'3', 'F#':'3', 'G':'4',
+ 'G#':'4', 'A':'5', 'A#':'5', 'B':'6' }
+keys_order = {'C':'0', 'C#':'1', 'D':'2', 'D#':'3', 'E':'4', 'F':'5', 'F#':'6', 'G':'7',
+ 'G#':'8', 'A':'9', 'A#':'10', 'B':'11' }
+
+intervals = ("2212221", "2122212", "1222122", "2221221", "2212212", "2122122", "1221222")
+
+# Drawing style
+White = '#ffffff'
+Black = '#000000'
+Marker_color = '#b3b3b3' # Ellipse fill color
+
+helpSheets = [["Ionian (major) scale", "2212221"],
+ ["Dorian scale", "2122212"],
+ ["Phrygian scale", "1222122"],
+ ["Lydian scale", "2221221"],
+ ["Mixolydian scale", "2212212"],
+ ["Aeolian (natural minor) scale", "2122122"],
+ ["Locrian scale", "1221222"]
+ ]
+
+def keyNumberFromNote(note):
+ """ Given a note such as C1 or C#1 where:
+ - the 1 defines the octave (starts from 1)
+ - the # defines note is sharp
+ return the notes numeric value, from 0
+ """
+ note = note.upper().strip()
+ octave = 1
+ if '#' in note : # sharp
+ if (len(note) > 2) and note[2].isdigit():
+ octave = int(note[2])
+ note = note[0:2]
+ else:
+ if (len(note) > 1) and note[1].isdigit():
+ octave = int(note[1])
+ note = note[0]
+ return int(keys_order[note])+(octave-1)*12
+
+def whiteKeyCountInRange(firstNote, lastNote):
+ """ Count the White notes between
+ - used by createPiano
+ """
+ count = 0
+ for key in range(firstNote, lastNote+1):
+ if keys_color[key%12] == "W":
+ count += 1
+ return count
+
+def colorFromKey(keyNumber):
+ """ Return B or W based on key. Use octaves
+ - used by create_markers
+ """
+ return keys_color[keyNumber%12]
+
+class SVGPianoScale (inkex.Effect):
+ marker_radius_factor = 0.42 # position marker in X on piano key
+ marker_y_offset_factor = 0.92 # position marker in Y
+
+ def __init__(self):
+ inkex.Effect.__init__(self)
+ self.arg_parser.add_argument("--firstNote", default="C1")
+ self.arg_parser.add_argument("--lastNote", default="B2")
+ self.arg_parser.add_argument("--tab")
+ self.arg_parser.add_argument("--intervals")
+ self.arg_parser.add_argument("--keynote")
+ self.arg_parser.add_argument("--scale", type=int)
+ self.arg_parser.add_argument("--helpSheet", type=int)
+
+ def calculate_size_and_positions(self):
+ " Determine page size and define key dimensions "
+ self.doc_width = self.svg.unittouu(self.document.getroot().get('width'))
+ self.doc_height = self.svg.unittouu(self.document.getroot().get('height'))
+ # Size of the keys
+ self.black_key_width = self.svg.unittouu('3.6 mm');
+ self.white_key_width = self.svg.unittouu('6 mm');
+ self.black_key_height = self.svg.unittouu('18 mm');
+ self.white_key_height = self.svg.unittouu('30 mm');
+
+ def createBlackKey(self, parent, number):
+ """ Insert Black key into scene
+ - number times width is X position
+ """
+ key_atts = {'x':str(self.white_key_width * number + self.white_key_width - self.black_key_width/2),
+ 'y':'0.0',
+ 'width':str(self.black_key_width),
+ 'height':str(self.black_key_height),
+ 'ry':str(self.svg.unittouu('0.7 mm')),
+ 'style':'fill:%s;stroke:%s;stroke-width:%s;stroke-opacity:1;fill-opacity:1' %(Black, Black, self.svg.unittouu('0.1 mm')) }
+ white_key = etree.SubElement(parent, 'rect', key_atts)
+
+ def createWhiteKey(self, parent, number):
+ """ Insert White key into scene
+ - number times width is X position
+ """
+ key_atts = {'x':str(self.white_key_width * number),
+ 'y':'0.0',
+ 'width':str(self.white_key_width),
+ 'height':str(self.white_key_height),
+ 'ry':str(self.svg.unittouu('0.7 mm')),
+ 'style':'fill:%s;stroke:%s;stroke-width:%s;stroke-opacity:1;fill-opacity:1' % (White, Black, self.svg.unittouu('0.25 mm'))}
+ white_key = etree.SubElement(parent, 'rect', key_atts)
+
+ def createKeyByNumber(self, parent, keyNumber):
+ """ Use Keynumber to detrmine octave and position within
+ - draw correct key on basis of note in octave sequence.
+ """
+ octave = math.floor(keyNumber / 12) + 1
+ note = keyNumber % 12
+ key = int(keys_numbers[notes[note]])
+ if keys_color[note] == "W":
+ self.createWhiteKey(parent, key+7*(octave-1))
+ else:
+ self.createBlackKey(parent, key+7*(octave-1))
+
+ def createKeyInRange(self, parent, firstKeyNum, lastKeyNum):
+ """ Draw keys in a range
+ - do it twice so Black keys are drawn over White ones
+ """
+ for key in range(firstKeyNum, lastKeyNum+1):
+ if keys_color[key % 12] == 'W':
+ self.createKeyByNumber(parent, key)
+ for key in range(firstKeyNum, lastKeyNum+1):
+ if keys_color[key % 12] == 'B':
+ self.createKeyByNumber(parent, key)
+
+ def createPiano(self, parent):
+ """ Draw keys defined by options
+ - add Piano 'box' above
+ """
+ firstKeyNumber = keyNumberFromNote(self.options.firstNote)
+ lastKeyNumber = keyNumberFromNote(self.options.lastNote)
+ self.createKeyInRange(parent, firstKeyNumber, lastKeyNumber)
+ # Draw the Piano box above keys
+ rectBump = (self.white_key_width - self.black_key_width/2)
+ rectBump = self.svg.unittouu('1 mm')
+ rect_x1 = self.white_key_width * (whiteKeyCountInRange(0, firstKeyNumber)-1)- rectBump
+ rect_y1 = self.svg.unittouu('-3 mm')
+ rect_width = self.white_key_width * (whiteKeyCountInRange(firstKeyNumber, lastKeyNumber)) + rectBump*2
+ rect_height = self.svg.unittouu('4 mm')
+ rect_atts = {'x':str(rect_x1),
+ 'y':str(rect_y1),
+ 'width':str(rect_width),
+ 'height':str(rect_height),
+ 'ry':str(0),
+ 'style':'fill:%s;stroke:none;fill-opacity:1' %(White) }
+ rect = etree.SubElement(parent, 'rect', rect_atts)
+ path_atts = {'style':'fill:%s;stroke:%s;stroke-width:%s;stroke-opacity:1' %(White, Black, self.svg.unittouu('0.25 mm')),
+ 'd':'m %s,%s l 0,%s %s,0 0, %s' % (rect_x1, rect_y1, rect_height, rect_width, -rect_height) }
+ path = etree.SubElement(parent, 'path', path_atts)
+
+ def createMarkerAt(self, parent, x, y, radius, markerText):
+ " Draw a Marker at position x,y "
+ markerGroup = etree.SubElement(parent, 'g')
+ # should replace with svg:circle
+ ellipce_atts = {
+ inkex.addNS('cx','sodipodi'):str(x),
+ inkex.addNS('cy','sodipodi'):str(y),
+ inkex.addNS('rx','sodipodi'):str(radius),
+ inkex.addNS('ry','sodipodi'):str(radius),
+ inkex.addNS('type','sodipodi'):'arc',
+ 'd':'m %s,%s a %s,%s 0 1 1 %s,0 %s,%s 0 1 1 %s,0 z' %(x+radius, y, x, y, -radius*2, x, y, radius*2),
+ 'style':'fill:%s;stroke:%s;stroke-width:%s;stroke-opacity:1;fill-opacity:1' %(Marker_color, Black, self.svg.unittouu('0.125 mm'))}
+ ellipse = etree.SubElement(markerGroup, 'path', ellipce_atts)
+ # draw the text
+ textstyle = {'font-size': '3 px',
+ 'font-family': 'arial',
+ 'text-anchor': 'middle',
+ 'text-align': 'center',
+ 'fill': Black }
+ text_atts = {'style':str(inkex.Style(textstyle)),
+ 'x': str(x),
+ 'y': str(y + radius*0.5) }
+ text = etree.SubElement(markerGroup, 'text', text_atts)
+ text.text = str(markerText)
+
+ def createMarkerOnWhite(self, parent, whiteNumber, markerText):
+ " Position Marker on White key "
+ radius = self.white_key_width * self.marker_radius_factor
+ center_x = self.white_key_width * (whiteNumber + 0.5)
+ center_y = self.white_key_height * self.marker_y_offset_factor - radius
+ self.createMarkerAt(parent, center_x, center_y, radius, markerText)
+
+ def createMarkerOnBlack(self, parent, whiteNumber, markerText):
+ " Position Marker on Black key "
+ radius = self.white_key_width * self.marker_radius_factor
+ center_x = self.white_key_width * (whiteNumber + 1)
+ center_y = self.black_key_height * self.marker_y_offset_factor - radius
+ self.createMarkerAt(parent, center_x, center_y, radius, markerText)
+
+ def createMarkers(self, parent, keyNumberList, markerTextList):
+ current=0
+ for key in keyNumberList:
+ octave = math.floor(key/12)
+ if colorFromKey(key) == "W":
+ self.createMarkerOnWhite(parent, int(keys_numbers[notes[key%12]])+(octave)*7, markerTextList[current])
+ else:
+ self.createMarkerOnBlack(parent, int(keys_numbers[notes[key%12]])+(octave)*7, markerTextList[current])
+ current += 1;
+
+ def createMarkersFromIntervals(self, parent, intervals):
+ """ Check intervals.
+ Then gather keys which need markers
+ and the text for each one.
+ Make markers.
+ """
+ # Check intervals are well defined and markers are legit.
+ intervalSumm = sum([int(i) for i in intervals])
+ if intervalSumm != 12:
+ inkex.debug("Warning! Scale must have 12 half-tones. But %d defined."%(intervalSumm))
+
+ firstKeyNum = keyNumberFromNote(self.options.firstNote)
+ lastKeyNum = keyNumberFromNote(self.options.lastNote)
+
+ markedKeys = ()
+ markerText = ()
+ if keyNumberFromNote(self.options.keynote) in range(firstKeyNum, lastKeyNum+1):
+ currentKey = keyNumberFromNote(self.options.keynote)
+ markedKeys = (currentKey,)
+ markerText = ('1',)
+ currentInterval = 0
+ for key in range(keyNumberFromNote(self.options.keynote), lastKeyNum+1):
+ if key - currentKey == int(intervals[currentInterval]):
+ markedKeys += (key,)
+ currentInterval += 1
+ markerText += (str(currentInterval+1),)
+ if currentInterval == len(intervals):
+ currentInterval = 0
+ currentKey = key
+ #
+ currentKey = keyNumberFromNote(self.options.keynote)
+ currentInterval = len(intervals)-1
+ for key in range(keyNumberFromNote(self.options.keynote), firstKeyNum-1, -1):
+ if currentKey - key == int(intervals[currentInterval]):
+ markedKeys += (key,)
+ markerText += (str(currentInterval+1),)
+ currentInterval -= 1
+ if currentInterval == -1:
+ currentInterval = len(intervals)-1
+ currentKey = key
+ # make the markers
+ self.createMarkers(parent, markedKeys, markerText)
+
+ def createHelpSheet(self, parent, title, intervals):
+ """ Draw big text Label and draw 12 different scales
+ """
+ textstyle = {'font-size': '22px',
+ 'font-family': 'arial',
+ 'text-anchor': 'middle',
+ 'text-align': 'center',
+ 'fill': Black }
+ text_atts = {'style':str(inkex.Style(textstyle)),
+ 'x': str( self.doc_width/2 ),
+ 'y': str( self.black_key_height) }
+ text = etree.SubElement(parent, 'text', text_atts)
+ text.text = title
+ #
+ for i in range(0, 12):
+ # override the ui input value for each note in the scale
+ self.options.keynote = notes[i]
+ # calculate the piano position on the page
+ if keys_color[i] == "W":
+ t = 'translate(%s,%s)' % (self.doc_width/2,
+ self.doc_height - self.white_key_height*1.5
+ - (self.white_key_height + self.svg.unittouu('7 mm')) * int(keys_numbers[self.options.keynote]) )
+ else: # Black key
+ t = 'translate(%s,%s)' % (self.svg.unittouu('7 mm'),
+ self.doc_height- self.white_key_height*1.5
+ - (self.white_key_height+self.svg.unittouu('7 mm')) * int(keys_numbers[self.options.keynote]) - self.white_key_height*0.5 )
+ group = etree.SubElement(parent, 'g', { 'transform':t})
+ # Create a piano using that keynote in the Scale (defined in intervals)
+ self.createPiano(group)
+ self.createMarkersFromIntervals(group, intervals)
+
+ def effect(self):
+ self.calculate_size_and_positions()
+ parent = self.document.getroot()
+ if str(self.options.tab) == "scale":
+ t = 'translate(%s,%s)' % (self.svg.namedview.center[0], self.svg.namedview.center[1])
+ group = etree.SubElement(parent, 'g', { 'transform':t})
+ self.createPiano(group)
+ self.createMarkersFromIntervals(group, intervals[self.options.scale])
+ elif str(self.options.tab) == "helpSheet":
+ t = 'translate(%s,%s)' % (self.svg.unittouu('5 mm'), self.svg.unittouu('5 mm'))
+ group = etree.SubElement(parent, 'g', { 'transform':t})
+ scale_index = self.options.helpSheet
+ self.createHelpSheet(group, helpSheets[scale_index][0], helpSheets[scale_index][1])
+ else: # direct intervals
+ t = 'translate(%s,%s)' % (self.svg.namedview.center[0], self.svg.namedview.center[1])
+ group = etree.SubElement(parent, 'g', { 'transform':t})
+ self.createPiano(group)
+ self.createMarkersFromIntervals(group, self.options.intervals)
+
+SVGPianoScale().run()
\ No newline at end of file