Moduuli:Kitarakirja/Sointuote

Wikikirjastosta

Tämän moduulin ohjeistuksen voi tehdä sivulle Moduuli:Kitarakirja/Sointuote/ohje

--- Otekaavio, nuottkuva ja tabulatuuri.
local p = {}

local otekaavio = require "Moduuli:Kitarakirja/Otekaavio"
local kitaramalli = require "Moduuli:Kitarakirja/Kitaramalli"

local lily     = require "Moduuli:Kitarakirja/Lily"

--- Alustaa annetunkokoisen taulukon annetulla arvolla.
--
-- @param size: taulukon koko
-- @param init: alkioihin alustettava arvo
local function init_array(size, init)
    local arr = {}

    for i = 1, size do
        arr[i] = init
    end

    return arr
end



-- TODO pois
-- Nuoteista käytetyt nimitykset. Jos parametri nuotit1 tai nuotit2 on annettu korvataan
-- taulukon arvot annetuilla arvoilla. Nuotit ovat taulukossa abcdefg-järjestelmän mukaan.
-- H lisätään vasta tulostusvaiheessa.
local g_notenames1 = { "c", "c♯", "d", "d♯", "e", "f", "f♯", "g", "g♯", "a", "a♯", "b" }
local g_notenames2 = { "c", "d♭", "d", "e♭", "e", "f", "g♭", "g", "a♭", "a", "b♭", "b" }


-- Indeksit nuottinnimet-taulukoihin
local indeces = {
    ["c"]   =  0,
    ["c♯"] =  1,
    ["c𝄪"]  =  2,
    ["d𝄫"]  =  0,
    ["d♭"] =  1,
    ["d"]   =  2,
    ["d♯"] =  3,
    ["d𝄪"]  =  4,
    ["e𝄫"]  =  2,
    ["e♭"] =  3,
    ["e"]   =  4,
    ["e♯"] =  5,
    ["e𝄪"]  =  6,
    ["f𝄫"]  =  3,
    ["f♭"] =  4,
    ["f"]   =  5,
    ["f♯"] =  6,
    ["f𝄪"]  =  7,
    ["g𝄫"]  =  5,
    ["g♭"] =  6,
    ["g"]   =  7,
    ["g♯"] =  8,
    ["g𝄪"]  =  9,
    ["a𝄫"]  =  7,
    ["a♭"] =  8,
    ["a"]   =  9,
    ["a♯"] = 10,
    ["a𝄪"]  = 11,
    ["b𝄫"]  =  9,
    ["b♭"] = 10,
    ["b"]   = 11,
    ["b♯"] =  0,
    ["b𝄪"]  =  1,
    ["c𝄫"]  = 10,
    ["c♭"] = 11,
    ["b𝄫 (b♭)"]  =  9,
    ["b♭ (b)"] = 10,
    ["b (h)"]   = 11,
    ["b♯ (h♯)"] =  0,
    ["b𝄪 (h𝄪)"]  =  1,
}




--- Palauttaa tabulatuuriin tarkoitetun nuotin nimen Lilypond-notaatiolla, esim. "dis''".
-- Tässä ei tarvitse ottaa huomioon enharmonisia nuotteja vaan käytettään vain toista
-- niistä.
-- param index: nuotin absoluuttinen indeksi
function lilytabnote(index, stringno)
    if index == "x" then
        return ""
    end

    local octave = math.floor((index - (indeces["c"] + 0*12)) / 12)
    local note   = g_notenames1[index % 12 + 1]

    return lily.lilyfy{note} .. lily.getOctaveString(octave, note) .. "\\" .. stringno
end


-- Taulukko syötteen muuttamiseen sisäiseen muotoon.
local input_to_simple = {
    ["b♭ (b)"] = "b♭",
    ["b (h)"]   = "b",
    ["b♭ (b)"] = "b♭",
    ["b𝄫 (b♭)"]  = "b𝄫",
    ["b♯ (h♯)"] = "b♯",
    ["b𝄪 (h𝄪)"]  = "b𝄪"
}

-- Taulukko nuotinnimienm muuttamiseen sisäisestä muodosta tulostettavaan muotoon.
local internal_to_output = {
    ["b♭"] = "b♭<br/>(b)",
    ["b"]   = "b<br/>(h)",
    ["b♭"] = "b♭<br/>(b)",
    ["b𝄫"]  = "b𝄫<br/>(b♭)",
    ["b♯"] = "b♯<br/>(h♯)",
    ["b𝄪"]  = "b𝄪<br/>(h𝄪)"
}

local function notename_to_print_format(notename)
    if internal_to_output[notename] then
        return internal_to_output[notename]
    else
        return notename
    end
end





local function to_lilynote(args)
    local index = args.index
    local name  = args.name

    if index == "x" or index == nil then
        return ""
    end

    local octave = math.floor((index - indeces["c"]) / 12)
    
    return lily.lilyfy{name} .. lily.getOctaveString(octave, name)
end

--- Palauttaa soinnun nuotit lilypond notaatiolla.
-- @param noteindeces: taulukko, jossa on joka kielelle oma nuotti-indeksi
-- @param notenames:   nuottien nimet sisäisessä muodossa (esim. "b♯")
-- @return:            taulukko, jossa on joka kielelle nuotti Lilypondin käyttämässä muodossa
local function make_lilynotes(noteindeces, notenames)
    local lilynotes = init_array(6, "")


    for i = 1, #noteindeces do
        lilynotes[i]  = to_lilynote{ index = noteindeces[i], name = notenames[i] }
    end

    return lilynotes
end

--- Palauttaa nuottimerkinnän tuottavan Lilypond-koodin (\Staff-lohkon).
-- Jos sointuja on kaksi varianttia, on palautettavan koodilohkon aikayksikkö 2/1. Jos
-- yksi on aikayksikkö 1/1.
-- param lilynotes1: ensimmäisen sointuvariantin nuotit Lilypond-muodossa
-- param lilynotes2: toisen sointuvariantin nuotit Lilypond-muodossa
-- return:           \Staff-lohkon koodi
-- return:           1 jos yksi sointu, 2 jos kaksi sointua
local function get_staff_markup(lilynotes1, lilynotes2)
    local notes_str1 = table.concat(lilynotes1, " ")
    local notes_str2 = table.concat(lilynotes2, " ")
    
    if notes_str2 == notes_str1 then
        notes_str2 = ""
    elseif notes_str1 == "" then
        notes_str1 = notes_str2
        notes_str2 = ""
    end


    if notes_str1 == "" and notes_str2 == "" then
        return "", 1
    elseif notes_str1 ~= "" and notes_str2 ~= "" then
        return [=[
    \new Staff  {
    \clef "treble_8"
        \once \override Staff.TimeSignature #'stencil = ##f
        <]=] .. notes_str1 .. [=[>1 | <]=] .. notes_str2 .. [=[>1 |
    }
]=], 2
    elseif notes_str1 ~= "" then
        return [=[
    \new Staff  {
    \clef "treble_8"
        \once \override Staff.TimeSignature #'stencil = ##f
        <]=] .. notes_str1 .. [=[>1
    }
]=], 1
    end

end


--- Muotoilee TabStaff-lohkon.
--  param noteindeces: nuotinnettava sointu absoluuttisina indekseinä
--  param time:        aikayksikön nimittäjä (1 tai 2)
--  return:            TabStaff-lohkon koodi
local function get_tabstaff_markup(noteindeces, time)
    local lilynotestab = { "", "", "", "", "", "" }

    -- Haetaan nuotit, josta generoidaan tabulatuuri. Nuottien nimillä
    -- (onko esim. cis vai des) ei tässä ole väliä.
    lilynotestab[1]  = lilytabnote(noteindeces[1], 6)
    lilynotestab[2]  = lilytabnote(noteindeces[2], 5)
    lilynotestab[3]  = lilytabnote(noteindeces[3], 4)
    lilynotestab[4]  = lilytabnote(noteindeces[4], 3)
    lilynotestab[5]  = lilytabnote(noteindeces[5], 2)
    lilynotestab[6]  = lilytabnote(noteindeces[6], 1)

    if time == 2 then
        return [=[
     \new TabStaff {
       \override Stem #'transparent = ##t
       \override Beam #'transparent = ##t 
      s2 <]=] .. table.concat(lilynotestab, " ") .. [=[>1 s2
  }
]=]
    else
        return [=[
     \new TabStaff {
       \override Stem #'transparent = ##t
       \override Beam #'transparent = ##t 
      <]=] .. table.concat(lilynotestab, " ") .. [=[>1
  }
]=]
    end   
end

local function get_lilymarkup(noteindeces, notenames1, notenames2)
    local lilynotes1 = {}
    local lilynotes2 = {}
    local notation_markup, tabstaff_markup
    local time
    local success

    -- Nuotit, joista generoidaan nuottimerkintä.
    lilynotes1 = make_lilynotes(noteindeces, notenames1)
    if #notenames2 > 0 then
        lilynotes2 = make_lilynotes(noteindeces, notenames2)
    end
    notation_markup, time = get_staff_markup(lilynotes1, lilynotes2)

    -- Nuotit, joista generoidaan tabulatuuri.
    tabstaff_markup = get_tabstaff_markup(noteindeces, time)

    return [=[ 
<<
  %\override Score.BarLine.break-visibility = ##(#f #t #t)
  \time ]=] .. time .. [=[/1
]=] .. notation_markup .. [=[

]=] .. tabstaff_markup .. [=[
>>
]=]
end




--- Lukee kielten soivat nauhavälit.
--
-- @param conf:
-- @param args:
-- @param fret_pos: ensimmäisen kuvattavan nauhan nauhaväli
-- @return:         taulukko, jossa on jokaiselta kieleltä soiva nauhaväli. Jos kieltä painetaan useasta kohdasta palauttaa suurimman.
--                  0 tarkoittaa vapaata kieltä. Nil tarkoittaa että kieli ei soi.
local function read_played_positions(conf, args, fret_pos)
    local played_frets = init_array(conf.n_strings, nil)
    local s_idx = 0
    local f_idx = 0
    local cur
    assert ( fret_pos and fret_pos >= 1, "Virheellinen parametrin fret_pos arvo" )
    
    -- Vapaat kielet.
    for s_idx = 1, conf.n_strings do
        if args[s_idx]:gsub("^%s*(.-)%s*$", "%1") == "o" then
            played_frets[s_idx] = 0
        else
            played_frets[s_idx] = "x"
        end
    end

    local from = conf.n_strings + 1
    local till = conf.n_strings * (1 + 5) -- avoimet kielet + 5 nauhaväliä
    
    for i = from, till do    
        cur = args[i]:gsub("^%s*(.-)%s*$", "%1")
        
        -- Kielen ja nauhan numero.
        s_idx = ((i - 1) % conf.n_strings) + 1
        if s_idx == 1 then
            f_idx = f_idx + 1
        end
        
        if cur == "o" or cur == "C" or cur == "D" or cur == "H" or cur == "-" then
            played_frets[s_idx] = (fret_pos - 1) + f_idx
        end

    end
    
    return played_frets
end


local function get_notename_table_row(notenames)
    local out = {}

    
    out[1] = '| style="padding-left: 10px; width: 20px;" | '
        .. notename_to_print_format(notenames[1])

    for i = 2, #notenames - 1 do
        out[i] = '| style="width: 20px;" | '
            .. notename_to_print_format(notenames[i])
    end
    
    out[#notenames] = '| style="padding-right: 10px; width: 20px;" | '
        .. notename_to_print_format(notenames[#notenames])

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

end

--- Tekee nuotinnimistä kaavion alle tulostettavan taulukon.
local function get_notename_table(conf, notenames1, notenames2)
    
    local row1 = get_notename_table_row(notenames1)
    if not notenames2 or #notenames2 == 0 then
        return [=[
{| style="border-collapse: collapse; font-size: 80%; width: ]=] .. conf.min_width .. [=[px; "
|- style="vertical-align: top;"
]=] .. row1 .. 

        [=[

|}
]=]
    end

    local row2 = get_notename_table_row(notenames2)

    return [=[
{| style="border-collapse: collapse; font-size: 80%; width: ]=] .. conf.min_width .. [=[px; "
|- style="vertical-align: top;"
]=] .. row1 .. [=[

|- style="vertical-align: top;"
]=] .. row2 .. [=[

|}
]=]
end


function p.Sointuote(frame)
    local n_strings = 6
    local conf = {
        n_strings = n_strings,
        min_width = (n_strings + 1) * 20,
    }

    local malli = kitaramalli:new{
        kitaramalli.indeces["e"] + -1*12,
        kitaramalli.indeces["a"] + -1*12,
        kitaramalli.indeces["d"] +  0*12,
        kitaramalli.indeces["g"] +  0*12,
        kitaramalli.indeces["b"] +  0*12,
        kitaramalli.indeces["e"] +  1*12,
    }
    
    local nuotit1 = frame.args.nuotit1
    local nuotit2 = frame.args.nuotit2
    local get_notename1 = nil
    local get_notename2 = nil

    local luokka = ""
    if not nuotit1 and not nuotit2 then
        luokka = "[[Luokka:Sointuotteet, joista puuttuu nuotinnimet]]"
    end
    
    if not nuotit1 and nuotit2 then
    	nuotit1 = nuotit2
    	nuotit2 = nil
    end
    	
    get_notename1 = malli:get_notename_function(mw.text.split(nuotit1, "–"))
    
    if nuotit2 then
        get_notename2 = malli:get_notename_function(mw.text.split(nuotit2, "–"))
    end

    local frets = read_played_positions(conf, frame.args, (tonumber(frame.args.nauhanumero) or 1))
    local notenames1 = {}
    local notenames2 = {}
    local noteindeces = {}

    if get_notename1 then    
        for i,fret in ipairs(frets) do
            if fret == "x" then
                notenames1[i] = ""
                if get_notename2 then
                    notenames2[i] = ""
                end
                noteindeces[i] = "x"
            else
                notenames1[i] = get_notename1{ string = i, fret = fret }
                if get_notename2 then
                    notenames2[i] = get_notename2{ string = i, fret = fret }
                end
                noteindeces[i] = malli:get_note_index{ string = i, fret = fret }
            end
        end
    end

    local notenames_table = ""
    local score = ""
    if #notenames1 > 0 then
        notenames_table = get_notename_table(conf, notenames1, notenames2)
        local lilyoutput = get_lilymarkup(noteindeces, notenames1, notenames2)
        score = frame:extensionTag{ name = "score", content = lilyoutput }
    end

    return [=[
<div style="display: inline-block; text-align: center;">
<div>
]=] ..  score .. [=[

</div>
<div>
]=] .. otekaavio.Otekaavio(frame) .. [=[

</div>
]=] .. notenames_table .. [=[
</div>]=] .. luokka

end

return p