1 """Justly intoned chord sequence realization
2
3 Production of justly intoned chord sequences in the form of MIDI
4 files with tuning instructions included.
5
6 """
7 """
8 ============================== License ========================================
9 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
10
11 This file is part of The Jazz Parser.
12
13 The Jazz Parser is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 The Jazz Parser is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
25
26 ============================ End license ======================================
27
28 """
29 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
30
31 import copy
32 from .. import CHORD_TYPES
33 from midi import write_midifile, EventStream, SetTempoEvent, \
34 ProgramChangeEvent, NoteOnEvent, NoteOffEvent
35 from . import tonal_space_note_events
36 from jazzparser.utils.base import group_pairs
37 from jazzparser.data import Fraction
38
40 """
41 A factory class to produce a MIDI file (represented using the midi
42 library package's C{EventStream})) from a sequence of tonal space
43 roots and chord types.
44
45 The resulting MIDI file will be tuned so that the realization is
46 justly intoned.
47
48 This provides an interface similar to
49 L{path_to_tones<jazzparser.harmonical.tones.path_to_tones>}, which
50 renders the path to raw wave data containing sine waves.
51
52 """
53 - def __init__(self, path, chord_types=None, tempo=120, \
54 root_octave=0, double_root=False, equal_temperament=False, \
55 instrument=0, bass_root=None):
56 """
57
58 @type path: list of (3d-coordinate,length) tuples
59 @param path: coordinates of the points in the sequence with their
60 associated lengths, given in beats (ints or
61 L{Fractions<jazzparser.data.Fraction>})
62 @type tempo: int
63 @param tempo: speed in beats per second (Maelzel's metronome)
64 @type chord_types: list of (string,length) tuples
65 @param chord_types: the type of chord to use for each tone, with
66 its associated length. See
67 L{..CHORD_TYPES<jazzparser.harmonical.CHORD_TYPES>} keys for possible values.
68 @type equal_temperament: bool
69 @param equal_temperament: render all the pitches as they would be
70 played in equal temperament.
71 @type root_octave: int
72 @param root_octave: octave to transpose the root to relative
73 to other notes. Default (0) has the other notes in the
74 octave above the root.
75 @type double_root: bool
76 @param double_root: if True, an extra tone will be added an
77 octave below the root
78 @type instrument: int
79 @param instrument: MIDI instrument to set at the beginning of
80 the file.
81 @type bass_root: int
82 @param bass_root: like double_root, adds an extra note an
83 octave below the root. In this case, adds it to a seperate
84 track, with the midi instrument number given. Set to None
85 not to add the note at all (default)
86
87 """
88 self.path = path
89
90 if chord_types is None:
91
92 self.chord_types = [('',length) for coord,length in path]
93 else:
94
95 types = set([type for type,length in chord_types])
96 for ctype in types:
97 if ctype not in CHORD_TYPES:
98 raise ChordSequenceRealizationError, "don't know "\
99 "how to realize the chord type '%s'" % ctype
100 self.chord_types = chord_types
101
102 self.tempo = tempo
103 self.root_octave = root_octave
104 self.double_root = double_root
105 self.equal_temperament = equal_temperament
106 self.instrument = instrument
107 self.bass_root = bass_root
108
147
148
149 time = Fraction(0)
150 bass_events = []
151 bass = self.bass_root is not None
152 for chord_type,length in self.chord_types:
153 tick_length = length * beat_length - 10
154 tick_time = time * beat_length
155
156 root = _root_at_time(time)
157
158 chord_events = events_for_chord(root, chord_type, int(tick_time),
159 int(tick_length),
160 equal_temperament=self.equal_temperament,
161 root_octave=self.root_octave,
162 double_root=(self.double_root or bass))
163 if bass:
164
165 bass_events.extend([copy.copy(ev) for ev in chord_events[-1]])
166 if bass and not self.double_root:
167
168 chord_events = sum(chord_events[:-1], [])
169
170 for ev in chord_events:
171 mid.add_event(ev)
172 time += length
173
174 if bass:
175 bass_channel = 1
176
177 mid.add_track()
178
179 instr = ProgramChangeEvent()
180 instr.value = 33
181 instr.channel = bass_channel
182 mid.add_event(instr)
183
184 for ev in bass_events:
185 ev.channel = bass_channel
186 mid.add_event(ev)
187 return mid
188
189 - def write(self, outfile):
190 """
191 Renders MIDI data and writes it out the the given file.
192
193 @type outfile: string or open file
194 @param outfile: filename to write to or an open file(-like) object
195
196 """
197 mid = self.render()
198 write_midifile(mid, outfile)
199
200 -def events_for_chord(coord, chord_type, start, duration, origin_note=60,
201 equal_temperament=False, root_octave=0, double_root=False,
202 velocity=100):
203 """
204 Builds a chord's midi events (note on and note off) from a root
205 coordinate and a chord type. Uses the chord definitions in
206 L{jazzparser.harmonical.CHORD_TYPES} to decide what notes to
207 play.
208
209 The list is grouped into sublists, one for each note.
210 For each note, three events are created: tuning, note on and note
211 off.
212
213 @type root_octave: int
214 @param root_octave: octave to transpose the root to relative
215 to other notes. Default (0) has the other notes in the
216 octave above the root.
217 @type double_root: bool
218 @param double_root: if True, an extra tone will be added an
219 octave below the root. This will be the last event in the list
220
221 """
222 notes = copy.copy(CHORD_TYPES[chord_type])
223 if root_octave != 0 or double_root:
224
225 root = None
226 for (x,y,z) in notes:
227 if x == y == 0:
228 root = (x,y,z)
229 break
230 if root is not None:
231
232 notes.remove(root)
233
234 notes.append((0,0,root[2]+root_octave))
235 if double_root:
236
237 notes.append((0,0,root[2]+root_octave-1))
238 notes = [(coord[0]+note[0], coord[1]+note[1], coord[2]+note[2]) for note in notes]
239
240 events = []
241 for note in notes:
242 tuning,noteon,noteoff = tonal_space_note_events(note, start, duration, origin_note=origin_note, velocity=velocity)
243 note_events = []
244
245 if not equal_temperament:
246 note_events.append(tuning)
247 note_events.append(noteon)
248 note_events.append(noteoff)
249 events.append(note_events)
250 return events
251
253 """
254 Convenience function that takes a path and set of timings and
255 uses the harmonical to render MIDI from it and writes it to a
256 file.
257
258 Additional args/kwargs are passed to the L{ChordSequenceRealizer}
259 init.
260
261 """
262 realizer = ChordSequenceRealizer(path, *args, **kwargs)
263 realizer.write(filename)
264
267