1 from __future__ import absolute_import
2 """Tuned MIDI file generation to reflect just intonation.
3
4 Uses the MIDI processing library by Giles Hall that I've worked on to
5 improve and fix bugs.
6
7 """
8 """
9 ============================== License ========================================
10 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
11
12 This file is part of The Jazz Parser.
13
14 The Jazz Parser is free software: you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation, either version 3 of the License, or
17 (at your option) any later version.
18
19 The Jazz Parser is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
26
27 ============================ End license ======================================
28
29 """
30 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
31
32 import math
33
34
35 from midi import single_note_tuning_event, NoteOnEvent, NoteOffEvent
36 from jazzparser.utils.tonalspace import coordinate_to_et, \
37 cents_to_pitch_ratio, pitch_ratio_to_cents
38 from ..tones import tonal_space_pitch
39
41 """
42 Creates the MIDI events needed to play the given tonal space
43 coordinate. These will be a single-note tuning event (to retune
44 the appropriate note), a note-on event and a note-off event.
45
46 @type coord: tuple
47 @param coord: 3D tonal space coordinate
48 @type start: int
49 @param start: start time of note in ticks
50 @type duration: int
51 @param duration: length of the note in ticks
52 @type origin_note: int
53 @param origin_note: midi note number to assume the origin of the
54 tonal space is equal to in pitch (default 60, middle C)
55 @rtype: tuple
56 @return: (tuning event, note on event, note off event)
57
58 """
59
60 change = tonal_space_tuning(coord)
61 note = change[0]
62
63
64 tuning = single_note_tuning_event([change])
65 tuning.tick = start
66
67 note_on = NoteOnEvent()
68 note_on.pitch = note
69 note_on.velocity = velocity
70 note_on.tick = start
71
72 note_off = NoteOffEvent()
73 note_off.pitch = note
74 note_off.tick = start+duration
75 note_off.velocity = velocity
76
77 return (tuning, note_on, note_off)
78
80 """
81 Produces MIDI single note tuning event data that will retune the
82 equal temperament equivalent note of the given coordinate to the
83 true pitch of that coordinate. This can, for example, be placed in
84 a stream directly before a note-on event for that note to play
85 the tonal space point.
86
87 If the resulting note number is outside the range of midi notes
88 (0-127), it is shifted to the nearest octave that is within the
89 range.
90
91 @type origin_note: int
92 @param origin_note: midi note number to assume the origin of the
93 tonal space is equal to in pitch (default 60, middle C)
94 @rtype: tuple
95 @return: (note,semitone,cents), where note is the note to be tuned,
96 and semitone and cents between them define the pitch to tune to.
97 This can be used as note change data for a midi single note
98 tuning event.
99
100 """
101 note = tonal_space_et_note(coord, valid_note=True, origin_note=origin_note)
102
103 origin_pitch = 440.0 * cents_to_pitch_ratio((origin_note-69)*100)
104 freq = origin_pitch * tonal_space_pitch(coord)
105
106 semitone = frequency_note_number_floor(freq)
107
108 cents_above = pitch_ratio_to_cents(freq/et_note_frequency(semitone))
109 return (note,semitone,cents_above)
110
112 """
113 Given a float frequency, returns the midi note number that
114 represents to nearest ET note below this frequency.
115
116 """
117
118
119 cents_from_a = pitch_ratio_to_cents(float(freq) / 440.0)
120
121 above_zero = (cents_from_a/100.0) + 69.0
122 return int(math.floor(above_zero))
123
125 """
126 Returns the MIDI note number of the note to which equal temperament
127 maps the given 3D tonal space coordinate.
128
129 @type valid_note: bool
130 @param valid_note: if True, permits only valid MIDI notes (i.e.
131 in range 0-127). If the true note is outside this range, returns
132 the note in the nearest octave that is in the range
133 @type origin_note: int
134 @param origin_note: midi note number to assume the origin of the
135 tonal space is equal to in pitch (default 60, middle C)
136
137 """
138
139 note_number = origin_note + coordinate_to_et(coord)
140 if valid_note:
141
142 if note_number < 0:
143
144 note_number = note_number % 12
145 elif note_number > 127:
146
147 chrom_number = note_number % 12
148 if chrom_number <= 7:
149 note_number = 120 + chrom_number
150 else:
151 note_number = 108 + chrom_number
152 return note_number
153
155 """
156 Calculates the frequency in Hz of the given MIDI note number, assuming
157 equal temperament and 440Hz as the A above middle C.
158
159 """
160
161 ratio = cents_to_pitch_ratio(100.0 * (note - 57))
162 return 220.0 * ratio
163