Moduuli:Musiikin taulukot/Pianon sointuote

Wikikirjastosta

Moduuli kosketinsoitinsormitusten piirtämiseen.

Funktio SointuoteNuoteista[muokkaa wikitekstiä]

  • Numeroparametrit: jokainen parametri on nuottinimi tai "+". Nuotit annetaan nousevassa järjestyksessä eli jälkimmäinen nuotti tulkitaan aina edellistä ylemmäksi. Esim.:
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|c|b}}
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|c|c}}
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|c|d}}
  • Plusmerkillä (+) voi hypätä ylemmäs kokonaisen oktaavin:
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|c|+|d}}
  • Piirtäminen aloitetaan ja lopetetaan aina toiseen kahden valkoisen koskettimen väliin. Alku- ja loppukohta asetetaan automaattisesti ellei parametreilla alku ja pituus muuta määrätä. Nuotit c:stä e:hen aloitetaan c:stä ja f:stä b:hen f:stä:
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|f|a|c}}
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|d|f♯|a|c}}
  • Alkukohdan voi pakottaa c:hen ensimmäinen-parametrilla:
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteNuoteista|f|a|c|ensimmäinen=c}}

Funktio SointuoteSormio[muokkaa wikitekstiä]

Parametrit:

  • väri<n>: (jossa n on jokin kokonaisluku) painetun koskettimen väri muodossa <valkoisen väri>;<mustan väri>; jos värejä ei anneta, käytetään moduulissa valmiina olevaa neljää väriä
  • Kaikki seuraavat parametrit annetaan muodossa <nuotinnimi><oktaavi>, jossa oktaavi on tieteellisen merkintätavan mukainen; esim. "c4" (= keski-c)
  • ensimmäinen: parametrin arvo on ensimmäinen näytettävä kosketin
  • viimeinen: parametrin arvo on viimeinen näytettävä kosketin
  • Muut nimetyt parametrit ovat painetut nuotit, joilla parametrin arvo on nuotin väri
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteSormio|ensimmäinen=c4|viimeinen=b4|c4=1|e4=1|g4=1}}
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteSormio|ensimmäinen=c4|viimeinen=b4|c4=1|cis4=1|d4=2|dis4=2|f4=3|fis4=3|g4=4|gis4=4}}
  • ensimmäinen ja viimeinen voivat olla mitä tahansa valkoisia koskettimia
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteSormio|ensimmäinen=a0|viimeinen=c8|c4=1}}
  • Värit voi vaihtaa väri-parametreilla. Värit voi antaa missä tahansa Värit-moduulin tukemassa muodossa (paitsi rgba).
{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteSormio|ensimmäinen=f3|viimeinen=b4|väri1=rgb(234,83,122);rgb(10,25,222)|väri2=#34f3a3;#bc98f1|g3=2|c4=1|e4=1|g4=2}}


TODO

{{#invoke:Musiikin taulukot/Pianon sointuote|SointuoteSormio|ensimmäinen=f3|viimeinen=b4|väri2=gray(0.3);gray(0.8)|g3=2|c4=1|e4=1|g4=2}}

--- Moduuli kosketinsoittimen sointuotteiden piirtämiseen. Generoi timeline-lisäosan avulla kuvan.
local p = {}

local notedata    = require "Moduuli:Kitarakirja/Nuottidata"
local tekstipohja = require "Moduuli:Tekstipohja"
local hsl = require "Moduuli:Värit"

-- Valkoisen koskettimen leveys.
local KEY_W = 12

-- Vasen marginaali. Pitää olla sama kuin PlotArea left.
local LMARGIN = 0
-- Oikea marginaali. Pitää olla sama kuin PlotArea right.
local RMARGIN = 0

-- Originaali: [https://de.wikipedia.org/wiki/Vorlage:KlavierAkkord/Doku#Alte_Version_mit_Timeline]
local template = [=[

ImageSize = width:{{{WIDTH}}} height:60
PlotArea = left:0 right:0 top:1 bottom:1
TimeAxis = orientation:vertical
#AlignBars = justify
AlignBars = early
Colors =
  id:s value:gray(0.0)
  id:textcol value:gray(0.5)
  id:w value:gray(1)
  {{{COLORS
  }}}

Period = from:0 till:60

Define $keyB = from:24 till:end width:6
Define $keyW = from:start till:end width:10 color:wx
Define $spalt = from:2 till:end color:black width:0.2

LineData =
#  layer:front

  {{{WHITE_KEYS
  }}}

  {{{EDGES
  }}}

  {{{BLACK_KEYS
  }}}

  at:2 color:black width:0.2
  at:59 color:black width:0.2

TextData  =
#  pos:(11,50) textcolor:textcol fontsize:XL
  pos:(7,30) textcolor:textcol fontsize:S
  tabs:({{{TABS}}})
  text:{{{NOTENAMES}}}

# {{{DUMMY}}}
]=]


-- Mustien ja valkoisten koskettimien sarja. Voidaan aloittaa lukeminen joko indeksistä 1 tai 6.
local pattern = { 10, 1, 0, 1, 0, 10, 1, 0, 1, 0, 1, 0 }

-- Valkoisiin koskettimiin piirrettävät ja kommentteihin (tulostettavat) nuotinnimet.
local noteNames = { "c", "c♯/d♭", "d", "d♯/e♭", "e", "f", "f♯/g♭", "g", "g♯/a♭", "a", "a♯/b♭", "b" }


-- Mahdolliset merkinnät: http://ploticus.sourceforge.net/doc/color.html
-- Parittomat ovat valkoisia, parilliset mustia. Parametrin indeksi kerrotaan 2:lla.
-- Esim. 1 tarkoittaa 1. ja 2., 2 taas 3. ja 4.
local colors = {
   [1] = "rgb(0.91,0.68,0.69)",
   [2] = "rgb(0.8,0.14,0.16)",
   [3] = "rgb(0.74,0.8,0.9)",
   [4] = "rgb(0.22,0.41,0.69)",
   [5] = "rgb(0.73,0.85,0.64)",
   [6] = "rgb(0.25,0.6,0.32)",
   [7] = "rgb(0.94,0.79,0.64)",
   [8] = "rgb(0.85,0.49,0.19)",
   [9] = "rgb(0.71,0.72,0.72)",
   [10] = "rgb(0.33,0.31,0.33)",
   [11] = "rgb(0.75,0.65,0.8)",
   [12] = "rgb(0.42,0.3,0.6)",
   [13] = "rgb(0.81,0.66,0.61)",
   [14] = "rgb(0.57,0.14,0.15)",
   [15] = "rgb(0.91,0.89,0.75)",
   [16] = "rgb(0.58,0.55,0.24)",
}

--- Muotoilee viittauksen annettuun, numerolla viitattuun väriin.
-- @param index  väriparin numero
-- @param bw     'white' tai 'black'
-- @return       viittaus Colors-osan taulukkoon esim. 'color3'
local function get_color(index, bw)
   if index < 1 or index > math.floor(#colors / 2) then
      error("Virheellinen värin indeksi: " .. tostring(index))
   end

   if bw == "white" then
      return "color" .. tostring((index - 1) * 2 + 1)
   else
      return "color" .. tostring((index - 1) * 2 + 2)
   end
end

--- Asettaa väriparin.
-- @param index   väriparin numero
-- @param colorW  painetun valkoisen väri
-- @param colorW  painetun mustan väri
local function set_color(index, colorW, colorB)
   colors[(index - 1) * 2 + 1] = hsl.alter_color{ color = colorW, output = "rgb0.1" }
   colors[(index - 1) * 2 + 2] = hsl.alter_color{ color = colorB, output = "rgb0.1" }
end

--- Muotoilee väritaulukon (@see colors) Timelinen käyttämään muotoon.
local function format_colortable()
   local out = {}

   for key, val in ipairs(colors) do
      table.insert(out, "id:color" .. key .. " value:" .. val)
   end

   return table.concat(out, "\n")
end


--- Generoi timeline-koodin.
-- @param start    Ensimmäisen koskettimen kokonaislukuarvo.
-- @param length   Piirrettävien koskettimien määrä.
-- @param pressed  Taulukko, pituudelta length, jossa painettujen koskettimien arvona on väri muiden nil.
--                 Taulukon indeksi on (nuotin kokonaislukuarvo) - start ja pituus korkeintaan length.
-- @param dummy:   Tilapäinen bugin kiertoparametri, joka ei vaikuta
--                 lopputulokseen muuten kuin lisäämällä tyhjää, jolloin koodi
--                 poikkeaa aiemmasta ja kuva piirrettään uudellen.
--                 TODO: tarviiko lengthiä
local function create_image(start, length, pressed, dummy)
   local outputWhites = { }
   local outputEdges  = { }
   local outputBlacks = { }
   local outputTabs   = { }
   local outputNames  = { }

   -- 1-alkuinen indeksi pressed-taulukkoon
   local pressedIndex = 1

   -- Valkoisen ja mustan tai tyhjän muodostamaa kosketinparia kuvaava arvo.
   local pairIndex = 0

   -- 0-alkuinen mod 12 -indeksi
   local ch_idx = start % 12
   local octave

   --assert ( length == #pressed, "length: " .. tostring(length) .. ", #pressed: " .. tostring(#pressed) )

   for pressedIndex = 1, length, 1 do
      ch_idx = (start + (pressedIndex - 1)) % 12
      octave = math.floor((start + (pressedIndex - 1)) / 12)

      local tp = pattern[ch_idx + 1]
      if tp % 10 == 0 then -- Valkoinen kosketin.

	 table.insert(outputWhites, "# " .. noteNames[ch_idx + 1])
	 if pressed[pressedIndex] then
	    local color = get_color(tonumber(pressed[pressedIndex]), 'white')
	    -- Valkoinen tehdään kahdesta palkista.
	    table.insert(outputWhites, "$keyW atpos:" .. tostring(pairIndex * KEY_W + 4   + LMARGIN) .. " color:" .. color)
	    table.insert(outputWhites, "$keyW atpos:" .. tostring(pairIndex * KEY_W + 8.5 + LMARGIN) .. " color:" .. color)
	    --table.insert(outputWhites, "$keyW atpos:" .. tostring(pairIndex * KEY_W + KEY_W/2 + 4   + LMARGIN) .. " color:" .. color)
	    --table.insert(outputWhites, "$keyW atpos:" .. tostring(pairIndex * KEY_W + KEY_W/2 + 8.5 + LMARGIN) .. " color:" .. color)
	 else
	    -- Painamattomia ei tarvitse lisätä, koska tausta on valkoinen.
	    table.insert(outputWhites, "#$keyW atpos:" .. tostring(pairIndex * KEY_W + 4   + LMARGIN) .. " color:w")
	    table.insert(outputWhites, "#$keyW atpos:" .. tostring(pairIndex * KEY_W + 8.5 + LMARGIN) .. " color:w")
	 end
	 table.insert(outputWhites, "")

	 -- Valkosia koskettimia erottava pystyviiva.
	 table.insert(outputEdges, "$spalt atpos:" .. tostring(pairIndex * KEY_W + LMARGIN))

	 -- Nuotinnimien kohdat ja nuotinnimet.
	 table.insert(outputTabs, tostring(pairIndex * KEY_W + LMARGIN) .. "-center")
	 table.insert(outputNames, "^" .. noteNames[ch_idx + 1])

	 pairIndex = pairIndex + 1

	 -- Hypätään yksi yli mustien indekseissä, että saadaan väli kohtaan, jossa on kaksi valkoista vierekkäin.
	 -- Jos ollaan ensimmäisessä koskettimessa skipataan musta aloitettiin sitten mistä tahansa. Esim.
	 -- jos aloitetaan a:sta, jää edeltävä gis pois.
	 if tp == 10 or pressedIndex == 1 then
	    table.insert(outputBlacks, "")
	 end

      elseif tp == 1 then  -- Musta kosketin.
	 table.insert(outputBlacks, "# " .. noteNames[ch_idx + 1])
	 if pressed[pressedIndex] then
	    local color = get_color(tonumber(pressed[pressedIndex]), 'black')
	    table.insert(outputBlacks, "$keyB atpos:" .. tostring(pairIndex * KEY_W + LMARGIN) .. " color:" .. color)
	    --table.insert(outputBlacks, "$keyB atpos:" .. tostring(pairIndex * KEY_W - 3 + LMARGIN) .. " color:" .. color)
	    --table.insert(outputBlacks, "$keyB atpos:" .. tostring(pairIndex * KEY_W + 3 + LMARGIN) .. " color:" .. color)
	 else
	    table.insert(outputBlacks, "$keyB atpos:" .. tostring(pairIndex * KEY_W + LMARGIN) .. " color:s")
	    --table.insert(outputBlacks, "$keyB atpos:" .. tostring(pairIndex * KEY_W - 3 + LMARGIN) .. " color:s")
	    --table.insert(outputBlacks, "$keyB atpos:" .. tostring(pairIndex * KEY_W + 3 + LMARGIN) .. " color:s")
	 end
      end
   end

   table.insert(outputEdges, "$spalt atpos:" .. tostring(pairIndex * KEY_W + LMARGIN - 1))
   table.insert(outputEdges, "")
   table.insert(outputTabs, tostring(pairIndex * KEY_W) .. "-left")
   width = tostring(pairIndex * KEY_W + LMARGIN + RMARGIN)

   return tekstipohja.korvaaMuuttujat(template, {
   					 ["WIDTH"]      = width,
   					 ["WHITE_KEYS"] = table.concat(outputWhites, "\n"),
   					 ["BLACK_KEYS"] = table.concat(outputBlacks, "\n"),
   					 ["EDGES"]      = table.concat(outputEdges, "\n"),
   					 ["TABS"]       = table.concat(outputTabs, ","),
   					 ["NOTENAMES"]  = table.concat(outputNames, ""),
					 ["COLORS"]     = format_colortable(),
					 ["DUMMY"]      = string.rep("X", tonumber(dummy or "0")) })
end





--- Palauttaa annettua merkkijonomuotoista nuottia (esim. "c4") vastaavat arvot.
-- @return Nuotin nimi, esim. "c".
-- @return Oktaavi, esim. 4.
local function parse_notestring(notestr)
   local name, oct =  mw.ustring.match(notestr, "([^%d]*)(%d)")
   if not name or not oct then
      error("Virheellinen nuotti: " .. notestr)
   end

   return name, tonumber(oct)
end


--- Piirtää annetun alku- ja loppukohdan välisen sormion, jossa on painettuja koskettimia.
-- @param frame
--    ['ensimmäinen'] : ensimmäinen sisällytettävä näppäin
--    ['viimeinen'] :   viimeinen sisällytettävä näppäin
--    muut nimetyt :
--        (muotoa c0, c#0, d0 ... c1 ... b8) :
--                  painetut nuotit. Parametrin arvo on painetun
--                  koskettimen värinumero.
--        (muotoa väri1, väri2...) :
--                  arvo on väri Plotibuksen käyttämässä muodossa [linkki]. Väreihin
--                  viitataan nuottiparametreissa numeroilla.
function p.SointuoteSormio(frame)
   local first = frame.args["ensimmäinen"] or "c1"
   local last  = frame.args["viimeinen"] or "b8"

   -- Syötteeksi kelpaavat nuotit.
   local white_keys = "cdefgab"

   -- First_integer ja last_integer: Nuotit täysasteikolla kokonaislukuina esitettynä, esim. c0 = 0, c4 = 37
   local first_n, first_o = parse_notestring(first)
   first_integer = notedata.getNoteIndex(first_n, first_o)
   if not first_n then
      error("Virheellinen parametri ensimmäinen. Kelvolliset arvot ovat c, d, e, f, g ja b")
   end

   local last_n, last_o = parse_notestring(last)
   last_integer = notedata.getNoteIndex(last_n, last_o)
   if not last_n then
      error("Virheellinen parametri viimeinen. Kelvolliset arvot ovat c, d, e, f, g, a ja b")
   end

   -- Tarkistetaan, että arvoksi on annettu valkoinen kosketin.
   if not white_keys:find(first_n) then
      error("Virheellinen parametrin 'ensimmäinen' arvo: " .. first)
   end

   if not white_keys:find(last_n) then
      error("Virheellinen parametrin 'viimeinen' arvo: " .. last)
   end


   local length = last_integer - first_integer + 1

   if length < 0 then
      error("Parametrin 'viimeinen' pitää olla suurempi kuin parametrin 'ensimmäinen'.")
   elseif length < 3 then
      error("Pitää olla vähintään kolme kosketinta.")
   end


   local pressed = { }

   -- Katsotaan mitkä nuotit on annettu painettuina ja merkitään ne pressed-taulukkoon.
   for note, val in pairs(frame.args) do
      note = note:gsub("is", "♯")
      note = note:gsub("es", "♭")

      if note ~= "ensimmäinen" and note ~= "viimeinen" then
	 local note_name, octave = parse_notestring(note)
	 if note_name == "väri" then
	    local colors = mw.text.split(val, ";", true)
	    if #colors ~= 2 then
	       error("Tarvitaan kaksi väriä: " .. note .. "=" .. val)
	    end
	    set_color(octave, colors[1], colors[2])
	 else
	    note_integer = notedata.getNoteIndex(note_name, octave)
	    if note_integer < first_integer or note_integer > last_integer then
	       error("Nuotti ei mahdu annetulle välille: " .. note)
	    end
	    pressed[note_integer - first_integer + 1] = val
	 end
      end
   end

   if frame.args["nowiki"] then
      return "<pre>\n" .. create_image(first_integer, length, pressed, frame.args.dummy) .. "</pre>"
   else
      return frame:extensionTag{ name = "timeline", content = create_image(first_integer, length, pressed, frame.args.dummy) }
   end
end

function p.SointuoteNuoteista(frame)
   local notes = frame.args
   local octave = 0
   local pressed = {}
   local first_integer     -- 0-alkuinen indeksi koko asteikolla
   local last_integer      -- 0-alkuinen indeksi koko asteikolla
   local prevInteger = -1  -- 0-alkuinen indeksi koko asteikolla
   local first
   local last

   if frame.args["nuotit"] then
      notes = mw.text.split(frame.args.nuotit, "–", true)
   else
      notes = frame.args
   end

   if #notes < 3 then
      --error("Liian vähän nuotteja")
   end

   if frame.args["ensimmäinen"] then
      first_n = frame.args["ensimmäinen"]
   else
      local note_n = notes[1]
      if notedata.getNoteIndex(note_n, 0) < 5 then
	 first_n = "c"
      else
	 first_n = "f"
      end
   end
   first_integer = notedata.getNoteIndex(first_n, 0)
   prevInteger = first_integer - 1

   -- Syötetaulukon pituus. Lasketaan, koska # ei toimi frame.args-taulukolla.
   local n_notes = 0

   for index, note in ipairs(notes) do
      n_notes = n_notes + 1
      note = note:gsub("is", "♯")
      note = note:gsub("as", "♭")
      if note == "+" then
	 octave = octave + 1
      else
	 -- Oletetaan että nuotit on annettu nousevassa järjestyksessä.
	 local integer = notedata.getNoteIndex(note, 0)
	 if integer <= prevInteger then
	    octave = octave + 1
	 end
	 prevInteger = integer
	 pressed[(12 * octave + integer)  - first_integer + 1] = 1
      end
   end

   -- Viimeisen nuotin nimi. Jos käyttäjä ei anna, lopetaan e:hen tai b:hen
   -- riippuen siitä onko annetun soinnun viimeinen nuotti välillä c–e vai f–b.
   if frame.args["viimeinen"] then
      last_n = frame.args["viimeinen"]
   else
      local note_n = notes[n_notes]
      if notedata.getNoteIndex(note_n, 0) < 5 then
	 last_n = "e"

      else
	 last_n = "b"
      end
   end

   -- Viimeisen nuotin absoluuttinen luku.
   integer = notedata.getNoteIndex(last_n, 0)
   if integer < prevInteger then
      octave = octave + 1
   end
   last_integer = 12*octave + integer

   length = last_integer - first_integer + 1
   if frame.args["nowiki"] then
      return "<pre>\n" .. create_image(first_integer, length, pressed, frame.args.dummy) .. "</pre>"
   else
      return frame:extensionTag{ name = "timeline", content = create_image(first_integer, length, pressed, frame.args.dummy) }
   end
end

return p