Folder hierarchy:
./ + -- project root, run: cabal configure --enable-tests,
cabal build, cabal test, cabal haddock
./src + -- source files
RealSimpleMusic.hs -- module definition for music types and
conversion functions, implementation
files in Music and ScoreToMidi folders
./Music +
Data.hs -- Music types
Utils.hs -- Functions, majorScale, transposeNote,
transposePitch, etc.
./ScoreToMidi +
Utils.hs -- scoreToMidi and ScoreToMidiFiles functions
and support functions
./ScoreToLilypond +
Utils.hs -- scoreToLilypond function for converting a
score to a Lilypond text file
./tests + -- test files
MainTestSuite.hs -- tests for Music and ScoreToMidi functions
./Music +
UtilsTest.hs -- HUnit and QuickCheck tests for Music
functions
./ScoreToMidi +
UtilsTest.hs -- HUnit tests for ScoreToMidi functions
./ScoreToLilypond +
UtilsTest.hs -- HUnit tests for ScoreToLilypond functions
The file ./src/Music/Data.hs
declares Music types and builds them
up, element-by-element into a simple Score
type:
- a
Score
comprises aTitle
(a synonym forString
), a composer String, a list of(ScoreControl,Rhythm)
pairs, and a list ofVoice
s. - a
Voice
comprises anInstrument
, a list ofNote
s, and a list of a list ofVoiceControl
s. - an
Instrument
is anewtype
that wraps aString
and is converted to a Midi instrument by the Midi library methodinstrumentNameToProgram
. - a
Note
is one ofNote
, withPitch
,Rhythm
, andSet
ofVoiceControl
,Rest
withRyhthm
andSet
ofVoiceControl
, orPercussionNote
withRhythm
andSet
ofVoiceControl
. - a
VoiceControl
is one ofDynamicControl
withDynamic
,BalanceControl
withBalance
, etc. - a
Pitch
is aPitchClass
and anOctave
- a
PitchClass
is one ofBs
,C
,Dff
, and etc. spanning pitch classes up to two sharps and two flats, - an
Octave
is anewtype
wrapper for anInt
with aBounded
instance approximating the range of a concert piano - a
Rhythm
is anewtype
wrapper for aRational
, e.g.1%2
corresonds to a half-note,1%16
to a sixteenth, and etc, including multiples, e.g.7%16
,3%2
, or2%1
. - a
ScoreControl
is one ofTempoControl
withTempo
,KeySignatureControl
withKeySignature
, orTimeSignatureControl
withTimeSignature
.
The file ./src/ScoreToMidi/RealSimpleMusic.hs
contains the API
for the package. In addition to the types above, the main functions
are implemented in ./src/ScoreToMidi/Utils.hs
and
./src/ScoreToLilypond/Utils.hs
:
scoreToMidiFile
: render a score as a single Midi file, assigning one channel to each voice, with a limit of 16 voices, as per the Midi spec. Note that, as eachVoice
requires anInstrument
, each Midi track starts with aprogram change
event. This method combines voices by instrument into one track, combining control messages forVoiceControls
. To preserve per-voiceVoiceControl
s e.g. unique per-voice dynamic, or to render aScore
with more than 16 unique instruments, usescoreToMidiFiles
instead.scoreToByteString
: the same asscoreToMidiFile
except answer theByteString
directly instead of writing it to a file. Used to test a generated file against a reference instance created early, see e.g. the canon files in thetests/data
folder.scoreToMidiFiles
: render a score into many Midi files, one per voice. Allows you to create an arbitrarily large score and import the voices one-by-one into editors that handle more than 16 voices, e.g. GarageBand and Logic. Preserves all[[VoiceControls]]
values.scoreToLilypondFile
: render a score to a Lilypond format text file. Lilypond is a program to engrave scores to PDF files.
The ./src/ScoreToMidi/Utils.hs
file is the largest file of the
package (over 800 lines), with many functions to convert the
simple music types in ./src/Music/Data.hs
to the Midi types
in the Sound.MIDI
. The conversion maps instances for the
VoiceControls
and ScoreControls
enum to Midi effects, e.g.
Dynamic
in DynamicControl
maps to Midi volume, Balance
and Pan
map to Midi pan, and etc., including InstrumentControl
,
which, in principal, lets you swap the piccolo part to a tuba if you want.
Absent a tempo control, the Midi file defaults to 120 beats per
minute. It's a one-way trip. There's no conversion from MIDI to the
simple music types.
Here's a minimal Score
that contains one Voice
with one note,
middle C
that's one whole-note long:
Score
"Minimal" -- Title
"Me" -- Title
(ScoreControls (KeySignature 0) (TimeSignature 4 4) [(Tempo (Rhythm (1%4)) 60, Rhythm (0%4))])
[Voice
(Instrument "Marimba") -- Instrument
[Note (Pitch C 0) (Rhythm (1%1)) empty] -- Notes
] -- Voices
Here's a cabal repl
sesion to create the score and convert it to a
Midi file:
bash-3.2$ cabal repl
Preprocessing library RealSimpleMusic-0.1.0.0...
GHCi, version 7.8.3: http://www.haskell.org/ghc/ :? for help
...
λ::m +Data.Ratio
λ: :m +RealSimpleMusic
λ: :m +Data.Set
λ: let score = Score "Minimal" "Me" (ScoreControls (KeySignature 0) (TimeSignature 4 4) [(Tempo (Rhythm (1%4)) 60, Rhythm (0%4))]) [Voice (Instrument "Marimba") [Note (Pitch C 0) (Rhythm (1%1)) empty]]
λ: scoreToMidiFile score
For more complete examples, see Canon
.
For program examples generally, see the files in ./tests
, including
MainTestSuite.hs
- test driverMusic/UtilsTest.hs
- testMusic
types and methodsScoreToLilypond/UtilsTest.hs
- testLilypond
methodsScoreToMidi/UtilsTest.hs
- testScoreToMidi
methods
- Switch from Set to List for Note, IndexedNote, MidiNote.
- Validate rhythm denominator is power of 2 or hide type and add constructor that does validation.
- Fractional Pan.
- Set key signature per voice for score examples.
- Multi-note instruments, e.g. piano, marimaba, vibes, etc. Music types need for any Note to also be a Chord of multiple simultaneous Notes of equal duration. Ugh, that's not strictly true. With piano, you can hang onto pitches selectively. So a list of notes each with its own duration all of which start at the same time. Midi should be trivially able to render chords. Lilypond rendering requires rhythmic spaces to cover rests for multiple staves, disposition of notes by staff
- Allocate durations and values continuous controls to accelerate rate of change over time, e.g. Fibonacci.
- Automatic cleff disposition: add 8vb/8va.
- Expand Instrument: Need additional attributes: range, family, staff (Viola, DrumStaff vs. RhythmicStaff for relatively pitched, e.g. tam-tam, vs unpitched e.g. snare). With range attribute, add validation pass .. when? Rendering to Midi at the least, maybe to Lilypond as well. With family attribute, group staves by family for Lilypond.
- Validate Lilypond output. Just assume lilypond executable exists? Is it possible to configure test with command-line arguments? Right now, there's just a list of repl commands to create Lilyond files for the three canons, which I then check by running through lilypond to see they compile without errors.
- Update tests for better coverage.
-
Look at travis-ci.org. See if I can run Linux builds and tests.
-
Look at Overtone in Clojure, Chris Ford keynote, Clojure/Conj 2012.