-
Notifications
You must be signed in to change notification settings - Fork 1
WIP: Pitches, Voicings, and Lead Sheets Brainstorm #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| -- Placeholder code | ||
| newtype Note = Note {getN :: Int} | ||
| newtype Interval = Interval {getI :: Int} | ||
| newtype Chord = Chord {getC :: Int} | ||
|
|
||
|
|
||
| -- | We might wanna make a type class of things convertible | ||
| -- to a frequency value. For this simple type, it's just the one field. | ||
| -- | ||
| -- All of these datatypes are also places where we can return distances | ||
| -- given 2 values. For pitch, we can return 3 types of distances; for MIDI, 2; | ||
| -- for frequencies, 1. | ||
|
Comment on lines
+10
to
+12
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, a typeclass can unfortunately only have one instance per type in plain ol' Haskell. We can get around this limitation a little bit by using Is there a clean way to split the notions of distance you're referring to into categories such that we'd only have one instance per type we care about? That is, are the notions of distance themselves seperable into more refined typeclasses?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could have 3 different typeclasses for each of the different distances -- for frequency it's just the difference, for MIDI it's also the difference, and for pitch, it's an extended interval. |
||
| -- | ||
| -- Frequency as a number of hertz. | ||
| newtype Frequency = Frequency {hertz :: Int} | ||
|
|
||
| -- | Smart constructor for frequency, checks for i > 0. | ||
| frequencyFrom :: Int -> Frequency | ||
| frequencyFrom i = undefined | ||
|
|
||
| -- | A MIDI note has a number. | ||
| newtype MIDI = MIDI {getNum :: Int} | ||
|
|
||
| -- | Smart constructor for MIDI notes, checks for 0 <= i < 128. | ||
| midiFrom :: Int -> Maybe MIDI | ||
| midiFrom = undefined | ||
|
|
||
| -- | Convert a MIDI to a frequency. | ||
| midiToFreq :: MIDI -> Frequency | ||
| midiToFreq = undefined | ||
|
|
||
| -- | Transpose a MIDI note by some amount. We could also use intervals here if needed. | ||
| transposeM :: MIDI -> Int -> MIDI | ||
| transposeM = undefined | ||
|
|
||
| -- | Pitch in scientific pitch notation (SPN) | ||
| data Pitch = Pitch {getNote :: Note, octave :: Int} | ||
|
|
||
| -- | Smart constructor for Pitch | ||
| pitchFrom :: Note -> Int -> Pitch | ||
| pitchFrom = undefined | ||
|
|
||
| -- | Convert pitch to a MIDI | ||
| pitchToMIDI :: Pitch -> MIDI | ||
| pitchToMIDI = undefined | ||
|
|
||
| -- | Convert pitch to a frequency | ||
| pitchToFreq :: Pitch -> Frequency | ||
| pitchToFreq = midiToFreq . pitchToMIDI | ||
|
|
||
| -- | Transpose a pitch by some interval. We could also use integers here if needed. | ||
| transposeP :: Pitch -> Interval -> Pitch | ||
| transposeP = undefined | ||
|
|
||
| -- | Convert MIDI to the simplest possible pitches (i.e. using the fewest accidentals) | ||
| midiToPitches :: MIDI -> [Pitch] | ||
| midiToPitches = undefined | ||
|
|
||
| -- | A chord with a bass note (not the best name, but hey) that can be specified or not | ||
| data ChordWithBass = ChordWithBass {getChord :: Chord, | ||
| getBass :: Maybe Note} | ||
|
Comment on lines
+59
to
+61
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, as far as the name of this type is concerned, maybe the right approach is to think about what new information we have about a The way I see it, this is the type that gives us a way to talk about chords in a particular inversion. So, maybe there's something we can take from that to turn into a more reasonable name? Maybe something like I also wonder - why do we want the bass note to be wrapped in a Honestly, though, as I'm writing this review, and still assuming my above assumption about intended semantics is true, I'm realizing this choice depends on what we actually want to reveal in this module. I can totally envision not exporting the
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, you just said it! We could call it Inversion! (Or ChordInversion) I think the point of the Maybe is that it's unspecified, so when we're voice leading, we can choose to put it any position that makes for the best voice leading and we know there's no constraint. And yeah, when we actually give people the inversion, there will always a reasonable bass note. |
||
|
|
||
| -- | Checks whether the voicing is in root position (i.e. bass note is chord root) | ||
| isRootPosition :: ChordWithBass -> Bool | ||
| isRootPosition = undefined | ||
|
|
||
| -- | A voicing is a chord with a bass note potentially specified, and a list of pitches. | ||
| data Voicing = Voicing {getCWB :: ChordWithBass, | ||
| getPitches :: [Pitch]} | ||
|
Comment on lines
+67
to
+69
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems great; I love how simple this type ends up being with all of the thought we've put into other representations this builds on :)
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! It's wild how simple yet structured this is |
||
|
|
||
| -- | Smart constructor for voicings. | ||
| voicingFrom :: ChordWithBass -> [Pitch] -> Maybe Voicing | ||
| voicingFrom = undefined | ||
|
|
||
| -- | There will be a number of such functions for different voicing strategies. | ||
| generateVoicing :: ChordWithBass -> [Pitch] | ||
| generateVoicing = undefined | ||
|
|
||
| -- | Given two voicings, returns the voice leading distance between them in half steps. | ||
| voiceLeadingDistance :: Voicing -> Voicing -> Int | ||
| voiceLeadingDistance = undefined | ||
|
|
||
| -- | Given a voicing and a chord we want to reach, find the voicing with the minimum voice leading distance. | ||
| voiceLeadFromTo :: Voicing -> ChordWithBass -> Voicing | ||
| voiceLeadFromTo = undefined | ||
|
|
||
| newtype Progression = Progression {getChords :: [ChordWithBass]} | ||
|
|
||
| -- | Given a chord progression and an initial voicing, voice lead each successive chord | ||
| -- to minimize voice leading distance from the previous one. | ||
| progressionToVoicings :: Progression -> Voicing -> [Voicing] | ||
| progressionToVoicings = undefined | ||
|
|
||
| -- | Tritone sub an entire progression by subbing each individual chord. | ||
| -- We can come up with a number of different substitutions that operate on | ||
| -- the whole progression rather than just individual chords. | ||
| tritoneSub :: Progression -> Progression | ||
| tritoneSub = undefined | ||
|
|
||
| -- | Given a progression, a number of beats per bar, and a list of | ||
| -- durations (in number of beats), i.e. one duration per chord in progression, | ||
| -- we can create a lead sheet datatype. | ||
| data LeadSheet = LeadSheet {getProg :: Progression, | ||
| getMeter :: Int, | ||
| getDurations :: [Int]} | ||
|
Comment on lines
+100
to
+105
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seeing this type makes me so excited; it's elegant, easily expandable if we need more information later, and totally captures all the information we expect to be in the initial language frontend. All to say, this type is screaming "it's almost time for a release!" On that note: We might want to consider getting this stuff done, and actually preparing for a release soon after ( this prep will take a fair amount of bookkeeping effort; checking documentation consistency, getting a README in order, confirming that public APIs are correct, etc), since we have such a huge library of stuff that's frankly already useful to people who, like us, are at the intersection of music and CS. I've personally been thinking of this project as both an effort to put together really solid Haskell libraries for representing musical ideas and an effort to provide a convenient tool (ie the language) that builds on those libraries' power. I would totally defer to your judgment on any of this, though; after all, you own the concept as far as I'm concerned :)
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do seem so close to a prototype! I love the idea of getting a release ready after this -- this would've all just been a random thought without your input making it actually seem/be possible, so if you think we could be ready soon, that means a ton. I defer all decision-making about what needs to be done to be release-ready to you, since you actually know what that takes. I completely agree with the broader idea about this project as both representation of musical ideas and convenient tooling -- I also see this as a first step in structuring/reducing the state space of music production and thereby making feasible the automation of increasingly larger and more complex compositional tasks. |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting idea! I can totally imagine a
PlayableorAudibletypeclass (or something like that) that encapsulates stuff that can actually be played via MIDI.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! I wonder what existing Haskell music libraries exist for this piece of the puzzle -- maybe we don't have to write the complete conversion of MIDI or notes to frequencies/sounds ourselves?