0

I was playing with musical note names having the goal to not confuse enharmonic equals, i.e. I wanted to get the accidentals (sharps and flats) right. The note a perfect fifth above the note B needs to be Fs and not Gb, even though Fs and Gb are the same key on a piano keyboard.

Also I wanted the convenience of writing e.e. Fs in a haskell program, without spaces, quotes or an extra function.

I ended up defining 35 constructors, ranging from Cbb to Bss. While this worked and did get the accidentals right, I was unhappy about the limitation to at most two accidentals. Internally, the accidentals we represented asInts anyways.

  • Is there a way to define an infinite number of constructors as indicated in the title, so notes with any number of accidentals (like Cbbbb) could be used? Template haskell maybe?
  • Or alternatively, can I get the convenience of writing Cbbbb in a haskell program (without quotes, spaces or an extra function) without making Cbbbb a constructor?
Martin Drautzburg
  • 5,143
  • 1
  • 27
  • 39
  • 2
    not an actual answer to your question but a remark: I modelled this a couple of times and I don't think you should put this into the types (you'll regret later for example if you want to derive scales where you want to switch enharmonics so that each base-note is listed exactly once) - I would model this as an ADT/Tuple with one part the base-note and one the accidential (flat, natural, sharp) and then implement functions to test on equality modulo enharmonics and one to list all enharmonics – Random Dev Mar 29 '21 at 11:15
  • more to the actual question: you could model it as `(Note, SharpCount)` where `SharpCount = Int` or you could make `data Sharp a = Sharp a` and add as many `Sharp` Constructors as you want - you could go as far and use type-families to make a relation happen at the type-level - IMO this is overengineering of the problem though – Random Dev Mar 29 '21 at 11:17
  • You might know that there is a whole book to teach Haskell thru music and conversely. Author: Paul Hudak. The PDF file (350 pages) is freely available. [The Haskell School of Music](https://www.cs.yale.edu/~hudak/Papers/HSoM.pdf) – jpmarinier Mar 29 '21 at 11:37
  • Instead of quotes, perhaps overloaded labels could be used https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_labels.html but a prefix hash like #bss would still be required. – danidiaz Mar 29 '21 at 12:07
  • 1
    Why is "without quotes, spaces, or an extra function" such an emphatic requirement? You're going to twist yourself into a pretzel trying to meet it when there are perfectly idiomatic solutions that are very convenient, despite breaking it. – Daniel Wagner Mar 29 '21 at 14:09

1 Answers1

4

I agree with Carsten that actually having lots of disperate constructors like that is a bad idea. It's much more sensible to use data like

data BaseNote = C | D | E | F | G | A | B
data PitchClass = PitchClass
  { baseNote :: BaseNote
  , accidentals :: Int }
data Note = Note
  { pitchClass :: PitchClass
  , octave :: Int }

As for

Also I wanted the convenience of writing e.e. Fs in a haskell program, without spaces, quotes or an extra function.

you have multiple options.

  • You could use -XPatternSynonyms. This lets you procure matchable constructors for already-defined data types.

    {-# LANGUAGE PatternSynonyms #-}
    pattern Cn = PitchClass C 0
    pattern Cs = PitchClass C 1
    pattern Cb = PitchClass C (-1)
    ...
    

    These can be provided by a TemplateHaskell macro to avoid code duplication.

  • You could provide a function that makes it look as compact as single constructor names, but actually isn't.

    (♮), (♯), (♭) :: BaseNote -> Int -> Note
    bn♮octv = Note (PitchClass bn 0) octv
    bn♯octv = Note (PitchClass bn 1) octv
    bn♭octv = Note (PitchClass bn (-1)) octv
    

    Now you can write things like

    [A♮2, A♮2, C♯3, C♯3, D♮3, D♮3, C♯3]
    

TBH I don't think either of these is really good though. IMO it makes more sense to specify musical material not in absolute pitches at all, but rather as a sequence of either scale degrees or interval steps.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • I am doing all my calculations with a PitchClass like you describe. My issue is entirely cosmetic. On the output side a suitable Show instance would solve everything. But I'd also like to specify a PitchClass value without too much noise and with any number of accidentals (I overemphasized "constructors" a bit, sorry). I cannot use read without writing read or can I? How does Integer cope with any number of digits? – Martin Drautzburg Mar 30 '21 at 02:24