Modul:Lilypond

Zur Navigation springen Zur Suche springen

score

Dieses Programm soll über die Vorlage {{Score}} aufgerufen werden, ersetzt das Tag <score> und macht die Erweiterung Score transcludierbar.

hili

Dieses Programm soll über die Vorlage {{Hili}} aufgerufen werden, ersetzt das Tag <syntaxhighlight> und macht diees Erweiterung transcludierbar.

midi

Dieses Programm soll über die Vorlage {{Midi}} aufgerufen werden und macht die Erweiterung Score transcludierbar. Es werden Midi-Dateien erzeugt.

pdf

Die Funktion pdf wendet die Vorlage {{pdf}} an und bildet eine Liste von Links für jede Tonleiter in der Reihenfolge des Quintenzirkels, wobei zwischen Dur und Moll unterschieden wird.

Unbenannte Parameter werden getrimmt, benannte nicht!

Prozedur proc aufrufen: {{#invoke:Lilypond|proc}} · Modul einbinden: local t = require('Module:Lilypond')

Unterseiten

Programmcode


local p = {} -- p stands for package

local music = '\\paper { tagline = ##f }\
\\language \"deutsch\"\
\\relative c\'\' { c-\\markup \"Standardmusic\" d e b }'
--(rgb-color 0.88 0.72 0.53) (\225 255) (\ 184 255) (\ 135 255) x11 burlywood
local gclef = '\\musicglyph #"clefs.G"'
local elise = '\\score { \\new Staff { c4 } }'
local eiffel = '\\null'

local qidur = { "C", "G", "D", "A", "E", "H", "Fis", "Ges", "Des", "As", "Es", "B", "F" } -- Quintenzirkel Dur
local qiall = { "C", "G", "D", "A", "E", "H", "Fis", "Cis", "Gis", "Dis", "Ges", "Des", "As", "Es", "B", "F" } -- Quintenzirkel Dur
local qimol = { "A", "E", "H", "Fis", "Cis", "Gis", "Dis", "Es",  "B", "F",  "C",  "G", "D" } -- Quintenzirkel Moll

function p.multipaths(frame)
	local a = frame.args              -- direkt per #invoke: übergeben
	local b = frame:getParent().args  -- an die Vorlage übergebene Parameter
	local pfad = trim(b["pfad"] or a["pfad"] or b[1] or a[1] or '')
	if pfad == '' then return "<div class=\"blue-gradient fett noclick lily\" style=\"font-size: 1.4em; padding: 10px\">Pfad fehlt</div>" end
	local retour = trim(b["test"] or a["test"] or '')
	local was = trim(b["was"] or a["was"] or '')
	local mypat= "<path[%s%c]+d=\"([^\"]*)"
	local zpath, ___ = '', ''
	local vbox, _, __
	local path, lily, fehler, minx, maxx, miny, maxy, mysvg
	local pfade, lilys, svgs, minmax, viewb = {}, {}, {}, {}, {}
	local output = ''
	local lilyout, lily, lilys = '', '', ''
	local svgout, svg = '', ''
	local viewbox, vboxen = '', ''
	local svgheader = '\n<svg version=\"1.1\" viewBox=\"$\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"'
	local width, height, svgstring = '', '', ''

	for n in pfad:gmatch(mypat) do
		table.insert(pfade, n)
	end

	if retour ~= '' and was == 'pfade' then 
		return "Zahl der Pfade: " .. #pfade .. "\n" .. table.concat(pfade,'\n----\n') 
	end

	for i = 1, #pfade do
		path = pfade[i]
		lily, fehler, minx, maxx, miny, maxy, mysvg = lpath(path)
		if type(fehler) == "string" then
			return "Pfad enthält Fehler\n" .. "\n" .. fehler .. "\n" .. path .. "\n" .. lily
		end
		_ = "\n#(define mypath$ \'(\n" .. lily .. "\n))" -- pfaddef:gsub("[§]",lily)
		__ = _:gsub("[$]", tostring(i))
		___ = "\n_: " .. _ .. '\ngsub("[$]", tostring(i)) ' .. __
		output = output .. __
		lilys = lilys .. lily
		width = tonumber(maxx) - tonumber(minx)
		height = tonumber(maxy) - tonumber(miny)
		viewbox = minx .. " " .. miny .. " " .. tostring(width) .. " " .. tostring(height)
		vboxen = vboxen .. "\nPfad Nr. " .. i .. ' viewbox: ' .. viewbox .. " min/max: " .. table.concat({ minx, miny, maxx, maxy }, ', ')
		vbox = svgheader:gsub("[$]", viewbox)
		svgout = svgout .. vbox .. mysvg .. "\" />\n</svg>"
	end
	
	if retour ~= '' and was == "boxen" then return vboxen end
	if retour ~= '' and was == "lily"  then return lilys end
	if retour ~= '' and was == "out"  then return ___ end
	lilyout = '{{#tag:tabber|Pfad(e) oneclick=<syntaxhighlight lang=\"latex\" style=\"display: inline-block\" class=\"oneclick\">' ..
	output .. '</syntaxhighlight>\n{{!}}-{{!}}\nPfad(e)=\n<syntaxhighlight lang=\"latex\" style=\"display: inline-block\">' ..
	output .. "</syntaxhighlight>}}"
	svg    = '{{#tag:tabber|SVG Pfad oneclick=<syntaxhighlight lang=\"xml\" style=\"display: inline-block\" class=\"oneclick\">' ..
	svgout .. '</syntaxhighlight>\n{{!}}-{{!}}\nSVG Pfad=\n<syntaxhighlight lang=\"xml\" style=\"display: inline-block\">' ..
	svgout .. "</syntaxhighlight>}}"
	return  frame:preprocess('\n==Lilypond Path==\n' .. lilyout .. "\n==SVG Code==\n" .. svg)
end

function lpath(s)
	local mpath = trim(s)
	local number, letter, coords
	-- Define the regular expression pattern
	local pattern = "[cCmMsSlLhHvVzZ][^cCmMsSlLhHvVzZ]*" -- wir zerteilen den String anhand der Pfadelemente und analysieren in einer
	-- separaten Schleife die Zahlen
	local numpattern = "[-]?%d*%.?%d+"
	local mypath, mycube  = {}, {}
	local i, j, elts = 1, 1
	local exppat = "[-+]?%d+%.?%d*[eE][-+]?%d+"
	local error = ""
	
	if type(mpath) ~= "string" or mpath == '' then return "kein Pfad angegeben", "error" .. tostring(mpath) end

	pfad = mpath:gsub(exppat, function(w) return " " .. w*1 end)  -- wir ersetzen Exponentialzahlen durch Kommazahlen
	-- if 1 then return pfad end
	-- Iterate over the matches
	for match in pfad:gmatch(pattern) do -- ein ganzes Pfadelement
		letter = match:match("[cCmMsSlLhHvVzZ]") -- zulässige Pfadkommandos
		-- a, q fehlen weil keine Umrechnung auf c vorhnaden
		table.insert(mypath, letter)
		local nn = {}
		for number in match:gmatch(numpattern) do
			table.insert(nn, number)
		end
		table.insert(mycube, nn)
	end
	
	local x0, x1, x2, x3, x4, x5, x6  -- Koordinaten der Kontrollpunkte
	local y0, y1, y2, y3, y4, y5, y6  -- Koordinaten; stimmen aus historischen Gründen nicht mit x und y überein

	letter = mypath[1]
	if #mycube[1] ~= 2 or (letter ~= "m" and letter ~= "M") then
		return "", "kein gültiger Pfad, falscher Beginn mit " .. letter .. table.concat(mycube[1],', ')
	end
	local actx, acty = unpack(mycube[1])  -- wir legen den Startpunkt fest
	local minx, maxx, miny, maxy = tonumber(actx), tonumber(actx), tonumber(acty), tonumber(acty)
	
	-- if 1 then return table.concat({actx, acty, minx, maxx, miny, maxy}, ', ') end
	
	local lily = "(moveto " .. table.concat(mycube[1],' ') .. ")"
	local svg = letter .. table.concat(mycube[1],' ')
	for i = 2, #mypath do
		letter = mypath[i]
		mydim = #mycube[i]

		-------------------------------------------------------------------------------------------------
		if letter == "m" then -- m
			if mydim ~= 2 then
			--	return "ungültiger Pfad " .. letter .. table.concat( mycube[i], ", " ) .. "\n" .. output
				ung = ung + 1
			end
			svg = svg .. "\n" .. letter .. " " .. table.concat(mycube[i],', ')
			--return table.concat(mycube[i],', ') .. letter
			x1, x2 = unpack(mycube[i])
			actx = actx + x1  -- nwuw Koordinaten berechnen
			acty = acty + x2
			if letter == "m" then -- wir bewegen uns ohne zu Zeichnen
				lily = lily .. "\n" .. "(rmoveto " .. x1 .. " " .. x2 .. ")"
			end
		end

		-------------------------------------------------------------------------------------------------
		if letter == "l" then -- lineto
			if mydim % 2 ~= 0 then
				return lily, "ungültiger Pfad " .. letter .. table.concat( mycube[i], ", " )
			end

			elts = mydim / 2
			for j = 1, elts do
				x1, x2 = unpack(mycube[i], j * 2 - 1, j * 2)
				actx = actx + x1  -- nwuw Koordinaten berechnen
				acty = acty + x2
				lily = lily .. "\n" .. "(rlineto " .. x1 .. " " .. x2 .. ")"
				svg = svg .. "\nl " .. x1 .. " " .. x2
				if tonumber(actx) < minx then minx = tonumber(actx) end  -- Ausmaße des Pfades mitberechnen
				if tonumber(actx) > maxx then maxx = tonumber(actx) end
				if tonumber(acty) < miny then miny = tonumber(acty) end
				if tonumber(acty) > maxy then maxy = tonumber(acty) end
			end
		end

		-------------------------------------------------------------------------------------------------
		if letter == "v" or letter == "h" then  -- vertikal oder horizonal
			if mydim ~= 1 then -- mehrere h's oder v's hintereinander ergeben keinen Sinn
				return lily, "ungültiger Pfad " .. letter .. table.concat( mycube[i], ", " )
			end
			x1 = mycube[i][1]
			if letter == "h" then -- horizontale Linie
				actx = actx + x1  -- nur x-Koordinate des Endpunktes ändert sich
				lily = lily .. "\n" .. "(rlineto " .. x1 .. " 0)"
				svg = svg .. "\nl " .. x1 .. " 0"
			end
			if letter == "v" then -- vertikale Linie
				acty = acty + x1
				lily = lily .. "\n" .. "(rlineto 0 " .. x1 .. ")"
				svg = svg .. "\nl 0 " .. x1
			end
		end

		-------------------------------------------------------------------------------------------------
		if letter == "s" then  -- spline
			if mydim ~= 4 then
				return lily, "ungültiger Pfad " .. letter .. " hat nicht 4 Angaben " .. table.concat( mycube[i], ", " ) 
			end
			if mypath[i-1] ~= "c" and mypath[i-1] ~= "s" and mypath[i-1] ~= "m" and mypath[i-1] ~= "l" 
			and mypath[i-1] ~= "h" and mypath[i-1] ~= "v" then
				return lily, "Pfadangabe s nur nach c, m, l, v, h oder s zulässig " .. table.concat( mycube[i], ", " )
			end
			x1, x2, x3, x4 = unpack(mycube[i])
			
			actx = actx + x3
			acty = acty + x4 -- neuen aktuellen Endpunkt berechnen, unabhängig von der Umwandlung nach c
			-- wir ergänzen s zu einem c
			-- die Koordinaten des ersten Punktes errechnen sich (relativ) aus
			-- x2 = x0 - x1
			-- y3 = y0 - y1
			-- x0, y0 sind die Koordinaten des Zielpunktes der vorigen Kurve
			-- x1, y1 sind die Koordinaten des 2. Stützpunktes
			if mypath[i-1] == "c" then -- voriger Abschnitt war c
				y1, y2, y3, y4, y5, y6 = unpack(mycube[i-1])
			end
			if mypath[i-1] == "c" then -- voriger Abschnitt war c
				elts = #mycube[i-1] / 6 -- der letzte Teil des Pfades ist entscheidend
				y3, y4, y5, y6 = mycube[i-1][elts * 6 - 3], mycube[i-1][elts * 6 - 2], mycube[i-1][elts * 6 - 1], mycube[i-1][elts * 6]
			end
			if mypath[i-1] == "m" or mypath[i-1] == "l" or mypath[i-1] == "h" or mypath[i-1] == "v"
			then -- voriger Abschnitt war m, l, h oder v
				-- c wird folgendermaßen berechnet:
				-- 1. Kontrollpunkt wird 0 0
				-- die vier Kontrollpunkte werden die letzten vier Kontrollpunkte der c-Kurve
				local koordinaten
				koordinaten = table.concat({0, 0, x1, x2, x3, x4},' ')
				lily = lily .. "\n(rcurveto " .. koordinaten .. ")"
				svg = svg .. "\nc " .. koordinaten
			end
			if mypath[i-1] == "c" or mypath[i-1] == "s" then
				z1 = y5 - y3
				z2 = y6 - y4
				lily = lily .. "\n(rcurveto " .. z1 .. " " .. z2 .. " " .. table.concat(mycube[i],' ') .. ")"
				svg = svg .. "\nc " .. z1 .. " " .. z2 .. " " .. table.concat(mycube[i],' ')
			end
		end
		-------------------------------------------------------------------------------------------------
		if letter == "c" then  -- bezier kurve
			if mydim % 6 ~= 0 then
				return lily, "ungültiger Pfad " .. letter .. table.concat( mycube[i], ", " ) .. "\n" .. output
			end
			-- wenn mehrere c hintereinander, aufdröseln!
			elts = mydim / 6 
			for j = 1, elts do
				x1, x2, x3, x4, x5, x6 = unpack(mycube[i], j * 6 - 5, j * 6)
				coords = { unpack(mycube[i], j * 6 - 5, j * 6) }
				actx = actx + x5 -- Koordinaten des aktuellen Punktes aktualisieren
				acty = acty + x6
				lily = lily .. "\n(rcurveto " .. table.concat(coords,' ') .. ")"
				svg = svg .. "\nc ".. table.concat(coords,' ')
				if tonumber(actx) < minx then minx = tonumber(actx) end  -- Ausmaße des Pfades mitberechnen
				if tonumber(actx) > maxx then maxx = tonumber(actx) end
				if tonumber(acty) < miny then miny = tonumber(acty) end
				if tonumber(acty) > maxy then maxy = tonumber(acty) end
			end
			--svg = svg .. "\nc ".. table.concat(mycube[i],' ')
		end

		-------------------------------------------------------------------------------------------------
		if tonumber(actx) < minx then minx = tonumber(actx) end  -- Ausmaße des Pfades mitberechnen
		if tonumber(actx) > maxx then maxx = tonumber(actx) end
		if tonumber(acty) < miny then miny = tonumber(acty) end
		if tonumber(acty) > maxy then maxy = tonumber(acty) end
		-------------------------------------------------------------------------------------------------
		
		if letter == "z" then -- Pfad wird geschlossen
			svg = svg .. "\nz"
			lily = lily .. "\n(closepath)"
		end
	end	
	
	return lily, false, tostring(minx), tostring(maxx), tostring(miny), tostring(maxy), svg
end

function p.svg2lily(frame)
	local a = frame.args              -- direkt per #invoke: übergeben
	local b = frame:getParent().args  -- an die Vorlage übergebene Parameter
	local pfad = trim(b["pfad"] or a["pfad"] or b[1] or a[1] or '')

	if pfad == '' then return "<div class=\"blue-gradient fett noclick lily\" style=\"font-size: 1.4em; padding: 10px\">Pfad fehlt</div>" end
	local retour = trim(b["test"] or a["test"] or '')
	local was = trim(b["was"] or a["was"] or '')

	local zpath, ___ = '', ''
	local vbox, _, __
	local path, fehler, minx, maxx, miny, maxy, mysvg
	local minmax, viewb = {}, {}
	local output = ''
	local lilyout, lily = '', ''
	local svgout, svg = '', ''
	local viewbox, vboxen = '', ''
	local svgheader = '\n<svg version=\"1.1\" viewBox=\"$\" xmlns=\"http://www.w3.org/2000/svg\">\n<path d=\"'
	local width, height, svgstring = '', '', ''

	if pfad == '' then return "Modul Lilypond: kein SVG-Pfad angegeben" end
	lily, fehler, minx, maxx, miny, maxy, mysvg = lpath(path)
	if type(fehler) == "string" then
		return "Pfad enthält Fehler\n" .. "\n" .. fehler .. "\n" .. path .. "\n" .. lily
	end
	_ = "\n#(define mypath$ \'(\n" .. lily .. "\n))" -- pfaddef:gsub("[§]",lily)
	__ = _:gsub("[$]", tostring(i))
	___ = "\n_: " .. _ .. '\ngsub("[$]", tostring(i)) ' .. __
	output = output .. __

	width = tonumber(maxx) - tonumber(minx)
	height = tonumber(maxy) - tonumber(miny)
	viewbox = minx .. " " .. miny .. " " .. tostring(width) .. " " .. tostring(height)
	vboxen = vboxen .. "\nPfad Nr. " .. i .. ' viewbox: ' .. viewbox .. " min/max: " .. table.concat({ minx, miny, maxx, maxy }, ', ')
	vbox = svgheader:gsub("[$]", viewbox)
	svgout = svgout .. vbox .. mysvg .. "\" />\n</svg>"

	if retour ~= '' and was == "boxen" then return vboxen end
	if retour ~= '' and was == "lily"  then return lilys end
	if retour ~= '' and was == "out"  then return ___ end
	lilyout = '{{#tag:tabber|Pfad(e) oneclick=<syntaxhighlight lang=\"latex\" style=\"display: inline-block\" class=\"oneclick\">' ..
	output .. '</syntaxhighlight>\n{{!}}-{{!}}\nPfad(e)=\n<syntaxhighlight lang=\"latex\" style=\"display: inline-block\">' ..
	output .. "</syntaxhighlight>}}"
	svg    = '{{#tag:tabber|SVG Pfad oneclick=<syntaxhighlight lang=\"xml\" style=\"display: inline-block\" class=\"oneclick\">' ..
	svgout .. '</syntaxhighlight>\n{{!}}-{{!}}\nSVG Pfad=\n<syntaxhighlight lang=\"xml\" style=\"display: inline-block\">' ..
	svgout .. "</syntaxhighlight>}}"
	return  frame:preprocess('\n==Lilypond Path==\n' .. lilyout .. "\n==SVG Code==\n" .. svg)

end

function p.score(frame)
	local a = mw.getCurrentFrame():getParent().args -- sollte alle Aufrufe abdecken
	if empty(a) then a = mw.getCurrentFrame().args end
	
	local mus = music
	
	for k, v in pairs(a) do
		mus = v 
		break 
	end	
	local b = frame:extensionTag { name = 'score', content = mus, args = { raw = '1' } }
	return b
end

function p.midi(frame)
	-- dieser Modul macht die Erweiterung Score transkludierbar
	-- die Anzeige der Noten wird unterdrückt, statt dessen wird ein wählbares Markup angezeigt
	-- dies kann z.B. eine Überschrift etc. sein
	local a = mw.getCurrentFrame():getParent().args -- sollte alle Aufrufe abdecken
	if empty(a) then a = mw.getCurrentFrame().args end
	
	local _mus = ''
	local mus = music
	local _midi = a.midi or ''
	local _markup = a.markup or gclef
	_markup = '\\markup ' .. _markup
	
	for k, v in pairs(a) do
		if string.lower(k) ~= "midi" and string.lower(k) ~= "markup" then
			mus = v 
			break 
		end
	end
	
	_mus = '\\language "deutsch"\
\\paper { tagline=##f }\
' .. _markup .. '\
\\score { \
' .. mus .. '\
\\midi { ' .. _midi .. ' }\
}\
'
	local b = frame:extensionTag { name = 'score', content = _mus, args = { raw = '1', vorbis = '1' } }
	return b
end

function p.mixi(frame)
	-- dieser Modul macht die Erweiterung Score transkludierbar
	-- die Anzeige der Noten wird unterdrückt, statt dessen wird ein wählbares Markup angezeigt
	-- dies kann z.B. eine Überschrift etc. sein
	local a = mw.getCurrentFrame():getParent().args -- sollte alle Aufrufe abdecken
	if empty(a) then a = mw.getCurrentFrame().args end
	
	local _mus = ''
	local mus = music
	local _midi = a.midi or ''
	local _markup = a.markup or elise
	_markup = '\\markup ' .. _markup
	
	for k, v in pairs(a) do
		if string.lower(k) ~= "midi" and string.lower(k) ~= "markup" then
			mus = v 
			break 
		end
	end
	
	_mus = '\\language "deutsch"\
\\paper { tagline=##f }\
' .. _markup .. '\
' .. mus .. '\
'
	local b = frame:extensionTag { name = 'score', content = _mus, args = { raw = '1', vorbis = '1' } }
	return b
end

function p.midix(frame)
	-- dieser Modul macht die Erweiterung Score transkludierbar
	-- die Anzeige der Noten wird unterdrückt, statt dessen wird ein wählbares Markup angezeigt
	-- dies kann z.B. eine Überschrift etc. sein
	-- zusätzlich können Variablen definiert werden
	
	local a = mw.getCurrentFrame():getParent().args -- sollte alle Aufrufe abdecken
	if empty(a) then a = mw.getCurrentFrame().args end
	
	local _mus = ''
	local mus = a.mus or a[1] or music
	local var = a.var or a[2] or ''     -- Lilypond Variablendefinition, muss außerhalb von score stehen
	local _midi = a.midi or ''
	local _markup = a.markup or gclef
	_markup = '\\markup ' .. _markup

	_mus = '\\language "deutsch"\
\\paper { tagline=##f }\
' .. _markup .. '\
' .. var .. '\
\\score { \
' .. mus .. '\
\\midi { ' .. _midi .. ' }\
}\
'
	local b = frame:extensionTag { name = 'score', content = _mus, args = { raw = '1', vorbis = '1' } }
	return b
end

function p.hili(frame)
	-- Syntaxhighlighting von Lilypond-Code
	-- standardmäßig als Latex formatiert, weil es keine spezielle Lilypond-Syntax gibt
	
	local a = frame.args             -- Direktaufruf via invoke
	local b = frame:getParent().args -- Aufruf via Vorlage

	local mus = music

	for k, v in pairs(a) do
		if string.lower(k) ~= "lang" then -- damit kann der Parameter beliebig benannt werden, nur lang wird ausgenommen
			mus = v 
			break 
		end
	end
	for k, v in pairs(b) do
		if string.lower(k) ~= "lang" then -- damit kann der Parameter beliebig benannt werden, nur lang wird ausgenommen
			mus = v 
			break 
		end
	end
	local _lang = a.lang or b.lang or 'latex'
	local _ = frame:extensionTag { name = 'syntaxhighlight', content = mus, args = { lang = _lang } }
	return _
end

function p.hilio(frame)
	-- Syntaxhighlighting von Lilypond-Code
	-- wird nur bei Adminaccount angezeigt
	-- standardmäßig als Latex formatiert, weil es keine spezielle Lilypond-Syntax gibt
	
	local a = frame.args             -- Direktaufruf via invoke
	local b = frame:getParent().args -- Aufruf via Vorlage

	local mus = music

	for k, v in pairs(a) do
		if string.lower(k) ~= "lang" then -- damit kann der Parameter beliebig benannt werden, nur lang wird ausgenommen
			mus = v 
			break 
		end
	end
	for k, v in pairs(b) do
		if string.lower(k) ~= "lang" then -- damit kann der Parameter beliebig benannt werden, nur lang wird ausgenommen
			mus = v 
			break 
		end
	end
	local _lang = a.lang or b.lang or 'latex'
	local _ = frame:extensionTag { name = 'syntaxhighlight', content = mus, args = { lang = _lang } }
	return frame:preprocess('{{Only|1=' .. _ .. '}}')
end

function p.pdf(frame)
	local a = mw.getCurrentFrame():getParent().args
	if empty(a) then a = mw.getCurrentFrame().args end
	local name = a[1] or '{{PAGENAME}}'  -- Name der PDF-Datei
	local titel = a[5] or name     -- Linkbeschreibung

	-- Achtung, bei benannten Parametern werden Leerzeichen am Anfang und am Ende entfernt
	local tonart = a.art or a[2] or 'dur'  -- Tonart: Dur, Moll etc
	local pre = a.pre or a[3] or ''    -- was vor der Tonartbezeichnung kommen soll, z.B. " Gipsy-Scale" überflüssig???
	local suf = a.suf or a[4] or 'Dur'    -- was nach der Tonartbezeichnung kommen soll, z-B, " Dur"
	-- der Name der PDF-Datei setzt sich dann folgendermaßen zusammen: name .. pre .. tonart .. suf
	
	if string.lower(tonart) == 'dur' then
		leiter = qidur
	else
		leiter = qimol
	end
	
	local vorname = trim(name) .. ' ' .. trim(pre) .. ' '
	local suffx = trim(suf)

	pdf = frame:expandTemplate { title = 'pdf', args = { vorname .. leiter[1] .. ' ' .. suffx } }
	qilen = #leiter  -- Zahl der Elemente im Quintenzirkel
	
	for i=2,qilen  do  -- alle Tonleitern im Quintenzirkel abarbeiten
		pdf = pdf .. ' · ' .. frame:expandTemplate { title = 'pdf', args = { vorname .. leiter[i] .. ' ' .. suffx } }
	end
	
	return pdf

end

function p.scale(frame)
	local a = mw.getCurrentFrame():getParent().args   -- bei Aufruf über Vorlage ist :getParent(). erforderlich
	if empty(a) then a = mw.getCurrentFrame().args end  -- entweder Direktaufruf oder über Vorlage, so bin ich für alles gerüstet

	local pre  = a.pre or a[1] or '{{PAGENAME}}'
	local suf  = a.suf or a[2] or 'Dur' 
	local art  = a.art or a.geschlecht or a[3] or 'dur' 
	local link = a.link or a[4] or ''          -- Linkbeschriftung
	local templ = a.template or a.templ or a.vorlage or a[5] or '' -- soll eine Vorlage angewendet oder nur ein Link erzeugt werden
	local trenn = a.trenn or a.trennzeichen or a[6] or '·'   -- das Trennzeichen kann umdefiniert werden
	
	local leiter = qidur
	if art == 'moll' then leiter = qimol end
	if art == 'all' then leiter = qiall end

	pre = trim(pre) .. ' '
	suf = ' ' .. trim(suf)
	trenn = ' ' .. trenn .. ' '
	
	local page_name = pre .. leiter[1] .. suf
	if link == '' then 
		linkp = page_name 
	else	
		link  = link .. ' '
		linkp = link .. leiter[1] .. suf
	end
	
	qilen = #leiter  -- Zahl der Elemente im Quintenzirkel
	
	local _ = ''
	if templ ~= '' then 
		_ = frame:expandTemplate { title = templ, args = { page_name } } 
		
		for i=2,qilen do
			page_name = pre .. leiter[i] .. suf
			_ = _ .. trenn .. frame:expandTemplate { title = templ, args = { page_name } }
		end
		return _
	end
	
	-- es wurde keine Vorlage angegeben, nur Links erzeugen
	_ = frame:preprocess( '[[' .. page_name .. '|' .. linkp .. ']]' )

	for i=2,qilen  do  -- alle Tonleitern im Quintenzirkel abarbeiten
		page_name = pre .. leiter[i] .. suf
		if link == '' then 
			linkp = page_name 
		else	
			linkp = link .. leiter[i] .. suf
		end
		_ = _ .. trenn .. frame:preprocess( '[[' .. page_name .. '|' .. linkp .. ']]' )
	end
	return _
end

function p.rep(frame)
	local repetitions = tonumber(frame.args[2])
	if not repetitions then
		return frame.args[1] or ''
	end
	return string.rep( frame.args[1] or '', repetitions )
end

function trim(s)
  -- entfernt Weißraum an den beiden Enden des Strings
  if type(s) ~= "string" then return s end -- es muss ein String sein
  return (s:gsub("^%s*(.-)%s*$", "%1"))
end

function empty (self)
	-- es gibt keine eingebaute Funktion, um zu überprüfen, ob eine Tabelle leer ist
	-- diese Funktion liefert true, wenn die Tabelle leer ist
	-- hilfreich bei frame.args oder frame:parent().args etc.
  for _, _ in pairs(self) do
    return false
  end
  return true
end

function round(num, numDecimalPlaces)
	if type(num) ~= "number" or type(numDecimalPlaces) ~= "number" then return false end
	return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
end

return p