{- | Pure music-notation helpers shared by the text staff widget
("Termtab.UI.Widgets.Notation") and the rasterized image renderer
("Termtab.Graphics.Notation"): mapping pitches to treble-clef staff positions
and durations to SMuFL glyph code points.

Staff positions are integers where 0 = E4 (bottom line) and 8 = F5 (top line);
even positions are lines, odd positions are spaces. Positions below 0 / above 8
are ledger territory.
-}
module Termtab.Notation.Staff (
    pitchToStaffPos,
    isStaffLine,
    noteheadGlyph,
    restGlyph,
    gClefGlyph,
) where

import Termtab.Types

-- | Is this staff position a line (as opposed to a space)?
isStaffLine :: Int -> Bool
isStaffLine :: Int -> Bool
isStaffLine Int
pos = Int -> Bool
forall a. Integral a => a -> Bool
even Int
pos Bool -> Bool -> Bool
&& Int
pos Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0 Bool -> Bool -> Bool
&& Int
pos Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
8

{- | Convert a MIDI pitch to a treble-clef staff position.
Guitar/bass are transposed up an octave (standard tab convention).
Position 0 = E4 (bottom line), 8 = F5 (top line).

Every chromatic (sharp\/flat) pitch class maps to the same staff position
as the natural note just below it (e.g. both C and C# land on
@naturalPos 0@) — this renderer draws no accidentals, so a sharped note is
visually indistinguishable on the staff from its natural neighbor.
-}
pitchToStaffPos :: Instrument -> Pitch -> Int
pitchToStaffPos :: Instrument -> Pitch -> Int
pitchToStaffPos Instrument
instr (Pitch Int
midi) =
    let transposed :: Int
transposed = case Instrument
instr of
            Guitar [Pitch]
_ Int
_ -> Int
midi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
12
            Bass [Pitch]
_ Int
_ -> Int
midi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
12
            Standard MidiProgram
_ -> Int
midi
        octave :: Int
octave = Int
transposed Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
12 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
        noteInOctave :: Int
noteInOctave = Int
transposed Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
12
        naturalPos :: Int
naturalPos = case Int
noteInOctave of
            Int
0 -> Int
0
            Int
1 -> Int
0
            Int
2 -> Int
1
            Int
3 -> Int
1
            Int
4 -> Int
2
            Int
5 -> Int
3
            Int
6 -> Int
3
            Int
7 -> Int
4
            Int
8 -> Int
4
            Int
9 -> Int
5
            Int
10 -> Int
5
            Int
11 -> Int
6
            Int
_ -> Int
0
        posFromC4 :: Int
posFromC4 = (Int
octave Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
4) Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
7 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
naturalPos
     in Int
posFromC4 Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
2

-- | SMuFL notehead code point for a duration.
noteheadGlyph :: Duration -> Char
noteheadGlyph :: Duration -> Char
noteheadGlyph Duration
Whole = Char
'\xE0A2' -- noteheadWhole
noteheadGlyph Duration
Half = Char
'\xE0A3' -- noteheadHalf
noteheadGlyph (Dotted Duration
d) = Duration -> Char
noteheadGlyph Duration
d
noteheadGlyph (Triplet Duration
d) = Duration -> Char
noteheadGlyph Duration
d
noteheadGlyph Duration
_ = Char
'\xE0A4' -- noteheadBlack

-- | SMuFL rest code point for a duration.
restGlyph :: Duration -> Char
restGlyph :: Duration -> Char
restGlyph Duration
Whole = Char
'\xE4E3' -- restWhole
restGlyph Duration
Half = Char
'\xE4E4' -- restHalf
restGlyph Duration
Quarter = Char
'\xE4E5' -- restQuarter
restGlyph Duration
Eighth = Char
'\xE4E6' -- rest8th
restGlyph Duration
Sixteenth = Char
'\xE4E7' -- rest16th
restGlyph Duration
Thirty2nd = Char
'\xE4E8' -- rest32nd
restGlyph (Dotted Duration
d) = Duration -> Char
restGlyph Duration
d
restGlyph (Triplet Duration
d) = Duration -> Char
restGlyph Duration
d

-- | SMuFL G (treble) clef.
gClefGlyph :: Char
gClefGlyph :: Char
gClefGlyph = Char
'\xE050'