Module:Instruments
From PickiPedia: A knowledge base of bluegrass, old time psychedelic jams, and other public domain music
Jump to navigationJump to search
Documentation for this module may be created at Module:Instruments/doc
-- Which icon stands for an instrument.
--
-- An instrument is recorded as it was played: "5-string banjo", not "banjo".
-- That detail is the point. Whether Cory brought a five-string or a tenor,
-- whether the banjo is Scruggs or clawhammer, is a fact about the cut, and
-- rounding it off to fit a picture would be throwing away the better half of
-- what we know.
--
-- But there are eleven icons and there will never be one per variant. So the
-- name and the icon are separated: the name is kept and shown verbatim, and
-- this decides which family the name belongs to. A name with no family shows
-- no icon, which is the correct outcome for a cello -- far better than the
-- broken image that "File:Instrument-icon-cello.png" produced before.
local p = {}
-- The icons that exist, as File:Instrument-icon-<family>.png.
local FAMILIES = {
"autoharp", "banjo", "bass", "dobro", "fiddle", "guitar",
"mandolin", "percussion", "tin whistle", "ukulele", "vocals",
}
-- Drawn at a playing angle rather than upright.
local ROTATED = {
banjo = true, guitar = true, mandolin = true, ukulele = true,
}
-- Names that do not contain their family's name. A name that does contain it
-- needs no entry: "5-string banjo", "tenor guitar" and "clawhammer banjo"
-- are all found without being listed.
--
-- Matched against the whole name first, then word by word, so "bass guitar"
-- lands on bass rather than on guitar.
local ALIASES = {
["bass guitar"] = "bass",
["electric bass"] = "bass",
["upright"] = "bass",
["double bass"] = "bass",
["doghouse"] = "bass",
["violin"] = "fiddle",
["viola"] = "fiddle",
["drums"] = "percussion",
["drum"] = "percussion",
["washboard"] = "percussion",
["spoons"] = "percussion",
["bones"] = "percussion",
["vocal"] = "vocals",
["voice"] = "vocals",
["singing"] = "vocals",
["throatsinging"] = "vocals",
["throat singing"] = "vocals",
["harmony"] = "vocals",
["spoken word"] = "vocals",
["whistle"] = "tin whistle",
["six-whistle"] = "tin whistle",
["pennywhistle"] = "tin whistle",
["flute"] = "tin whistle",
["recorder"] = "tin whistle",
["resonator"] = "dobro",
["squareneck"] = "dobro",
["uke"] = "ukulele",
}
local function normalise( name )
return ( tostring( name or "" ):lower():gsub( "%s+", " " ):gsub( "^ ", "" ):gsub( " $", "" ) )
end
-- Does needle appear in haystack as a whole word or phrase? Without this,
-- "bass" would be found inside "bassoon" and a bassoon would be a bass.
local function containsPhrase( haystack, needle )
local from = 1
while true do
local start, stop = haystack:find( needle, from, true )
if not start then return false end
local before = start == 1 and "" or haystack:sub( start - 1, start - 1 )
local after = haystack:sub( stop + 1, stop + 1 )
if not before:match( "%a" ) and not after:match( "%a" ) then
return true
end
from = start + 1
end
end
--- The family an instrument belongs to.
--
-- @param name an instrument as somebody wrote it down.
-- @return the family name, or "" when nothing here covers it.
function p._family( name )
name = normalise( name )
if name == "" then return "" end
-- The whole name, exactly. This is what lets "bass guitar" be a bass:
-- it is settled before anything goes looking for family names inside.
for _, family in ipairs( FAMILIES ) do
if name == family then return family end
end
if ALIASES[ name ] then return ALIASES[ name ] end
-- A family named within the name. Longest first, so a hypothetical
-- "tin whistle" beats a shorter match that overlaps it.
local byLength = {}
for _, family in ipairs( FAMILIES ) do table.insert( byLength, family ) end
table.sort( byLength, function( a, b ) return #a > #b end )
for _, family in ipairs( byLength ) do
if containsPhrase( name, family ) then return family end
end
-- Failing that, an aliased word inside the name: "wood flute", "electric
-- fiddle", "tenor uke".
for alias, family in pairs( ALIASES ) do
if containsPhrase( name, alias ) then return family end
end
return ""
end
--- The icon for an instrument, as wikitext, or nothing.
--
-- @param name the instrument, shown verbatim in the tooltip.
-- @param size pixel height of the icon.
-- @param tuck when set, pull the icon back over the name it follows -- the
-- tight inline form used beside a musician's link.
function p._badge( name, size, tuck )
local family = p._family( name )
if family == "" then return "" end
size = tonumber( size ) or 20
local outer = "display:inline-block;"
if tuck then
outer = outer .. "position: relative;top: -4px;left: -2px;width: 9px;"
end
if ROTATED[ family ] then
outer = outer .. "transform:rotate(-30deg);"
end
local label = normalise( name )
return '<span title="' .. label .. '" style="' .. outer .. '">'
.. '<span style="display:inline-block;width:' .. size
.. "px;height:" .. size .. 'px;">'
.. "[[File:Instrument-icon-" .. family .. ".png|x" .. size .. "px|"
.. label .. "|link=]]"
.. "</span></span>"
end
-- {{#invoke:Instruments|family|5-string banjo}} -> banjo
function p.family( frame )
return p._family( frame.args[1] )
end
-- {{#invoke:Instruments|badge|5-string banjo|size=18|tuck=1}}
function p.badge( frame )
local args = frame.args
return p._badge( args[1], args.size or args[2],
args.tuck ~= nil and args.tuck ~= "" )
end
return p