Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions src/Brainstorm.hs
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.
Comment on lines +7 to +8
Copy link
Collaborator

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 Playable or Audible typeclass (or something like that) that encapsulates stuff that can actually be played via MIDI.

Copy link
Owner Author

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?

--
-- 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 FlexibleInstances and a couple of other language extensions, but sadly I'm not sure if we have the option to have, say, only one typeclass capturing all notions of distance.

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?

Copy link
Owner Author

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 Chord in this type.

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 PositionedChord?

I also wonder - why do we want the bass note to be wrapped in a Maybe? I mean, every chord of this type will actually have a sensible notion of a bass note; I assume that the intended semantics for a Nothing in that field are something like "this chord is in root position"? If that's the case, I think the redundancy (between the getBass field and the Chord's root) is actually fine.

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 getBass accessor function directly, and instead handle all the Maybes internally, only exposing stuff that makes it clear you can always get back a sensible bass note.

Copy link
Owner Author

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 :)

Copy link
Owner Author

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 :)

Copy link
Owner Author

@edwisdom edwisdom Sep 13, 2020

Choose a reason for hiding this comment

The 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.