module Neck.Fingering ( fingering, FingeringF, Fingering ) where import Control.Monad.Reader import Prelude import Partial.Unsafe (unsafeCrashWith) import Data.Argonaut.Decode import Data.Array (catMaybes, foldM, index, mapMaybe, zipWith, (..), filter) import Data.Foldable (intercalate, null, length) import Data.FunctorWithIndex import Data.Function (on) import Data.Int (toNumber, ceil) import Data.Maybe import Data.Ring import Data.Semigroup.Foldable (class Foldable1, foldMap1, fold1) import Data.Traversable import Data.Tuple import Debug.Trace import Neck.Fret import Music.Transpose (class Transpose, trans) newtype FingeringF a = Fingering { e4 :: a , b3 :: a , g3 :: a , d3 :: a , a2 :: a , e2 :: a } instance eq_chord :: (Eq a) => Eq (FingeringF a) where eq = eq `on` toArray instance ord_chord :: (Ord a) => Ord (FingeringF a) where compare = compare `on` toArray instance functor_chord :: Functor FingeringF where map f (Fingering x) = fingeringf (f x.e4) (f x.b3) (f x.g3) (f x.d3) (f x.a2) (f x.e2) instance functor_with_index_chord :: FunctorWithIndex Int FingeringF where mapWithIndex f (Fingering x) = fingeringf (f 0 x.e4) (f 1 x.b3) (f 2 x.g3) (f 3 x.d3) (f 4 x.a2) (f 5 x.e2) instance apply_chord :: Apply FingeringF where apply (Fingering f) (Fingering x) = fingeringf (f.e4 x.e4) (f.b3 x.b3) (f.g3 x.g3) (f.d3 x.d3) (f.a2 x.a2) (f.e2 x.e2) instance applicative_chord :: Applicative FingeringF where pure x = fingeringf x x x x x x instance foldable_chord :: Foldable FingeringF where foldl f z chord = foldl f z $ toArray chord foldr f z chord = foldr f z $ toArray chord foldMap f chord = foldMap f $ toArray chord instance traversable_chord :: Traversable FingeringF where traverse f (Fingering x) = fingeringf <$> f x.e4 <*> f x.b3 <*> f x.g3 <*> f x.d3 <*> f x.a2 <*> f x.e2 sequence = traverse identity instance foldable1_chord :: Foldable1 FingeringF where foldMap1 f (Fingering c) = f c.e4 <> f c.b3 <> f c.g3 <> f c.d3 <> f c.a2 <> f c.e2 fold1 = foldMap1 identity fingeringf :: forall a. a -> a -> a -> a -> a -> a -> FingeringF a fingeringf e4 b3 g3 d3 a2 e2 = Fingering { e4, b3, g3, d3, a2, e2 } type Fingering = FingeringF GuitarString instance show_fingering :: Show (FingeringF GuitarString) where show = intercalate "-" <<< map (maybe "x" show <<< unfret) <<< toArray instance transpose_fingering :: Transpose (FingeringF GuitarString) where trans n fing = normaliseFingering $ map (trans n) fing fingering :: GuitarString -> GuitarString -> GuitarString -> GuitarString -> GuitarString -> GuitarString -> Fingering fingering e4 b3 g3 d3 a2 e2 = if length (filter isFretted [e4, b3, g3, d3, a2, e2]) >= 1 then fingeringf e4 b3 g3 d3 a2 e2 else fingeringError fingeringError :: forall a. a fingeringError = unsafeCrashWith "All fingerings must have have at least 1 note note (a zero note fingering is not really a fingering)" {- The fret range of a fingering is the total span of the non-muted notes. -} fretRange :: Fingering -> Tuple Int Int fretRange fing = fromMaybe fingeringError (Tuple <$> minimum frets <*> maximum frets) where frets = catMaybes (map unfret $ toArray fing) {- A fingering shifted 12 frets (1 octave) is essentially the same fingering. Normalisation shifts a fingering down by the maximum number of octaves, as long as there are no negative frets produced. normaliseFingering "x-0-2-2-1-0" = "x-0-2-2-1-0" normaliseFingering "x-15-17-17-17-15" = "x-3-5-5-5-3" -} normaliseFingering :: Fingering -> Fingering normaliseFingering fing = map (trans (12*octaveShift)) fing where octaveShift = ceil (- toNumber minFret / 12.0) minFret = fst $ fretRange fing toArray :: FingeringF ~> Array toArray (Fingering c) = [c.e4, c.b3, c.g3, c.d3, c.a2, c.e2] {- A fingering is open if it contains at least one note freted at fret 0 -} isOpen :: Fingering -> Boolean isOpen = any $ case _ of Muted -> false Fret n -> n == 0 {- A fingering is valid if all fretted notes are in the range [0, 24] -} validFingering :: Fingering -> Boolean validFingering = all $ case _ of Muted -> true Fret n -> 0 <= n && n <= 24