a6d9ce6079cafa2e50a0b4b09e4f0aa91dc0ce4d
[aubio.git] / python / lib / aubio / midiconv.py
1 # -*- coding: utf-8 -*-
2 """ utilities to convert midi note number to and from note names """
3
4 import sys
5 from ._aubio import freqtomidi, miditofreq
6
7 __all__ = ['note2midi', 'midi2note', 'freq2note', 'note2freq']
8
9 py3 = sys.version_info[0] == 3
10 if py3:
11     str_instances = str
12     int_instances = int
13 else:
14     str_instances = (str, unicode)
15     int_instances = (int, long)
16
17
18 def note2midi(note):
19     """Convert note name to midi note number.
20
21     Input string `note` should be composed of one note root
22     and one octave, with optionally one modifier in between.
23
24     List of valid components:
25
26     - note roots: `C`, `D`, `E`, `F`, `G`, `A`, `B`,
27     - modifiers: `b`, `#`, as well as unicode characters
28       `𝄫`, `♭`, `♮`, `♯` and `𝄪`,
29     - octave numbers: `-1` -> `11`.
30
31     Parameters
32     ----------
33     note : str
34         note name
35
36     Returns
37     -------
38     int
39         corresponding midi note number
40
41     Examples
42     --------
43     >>> aubio.note2midi('C#4')
44     61
45     >>> aubio.note2midi('B♭5')
46     82
47
48     Raises
49     ------
50     TypeError
51         If `note` was not a string.
52     ValueError
53         If an error was found while converting `note`.
54
55     See Also
56     --------
57     midi2note, freqtomidi, miditofreq
58     """
59     _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7,
60                         'A': 9, 'B': 11}
61     _valid_modifiers = {
62             u'𝄫': -2,                         # double flat
63             u'♭': -1, 'b': -1, '\u266d': -1,  # simple flat
64             u'♮': 0, '\u266e': 0, None: 0,    # natural
65             '#': +1, u'♯': +1, '\u266f': +1,  # sharp
66             u'𝄪': +2,                         # double sharp
67             }
68     _valid_octaves = range(-1, 10)
69     if not isinstance(note, str_instances):
70         msg = "a string is required, got {:s} ({:s})"
71         raise TypeError(msg.format(str(type(note)), repr(note)))
72     if len(note) not in range(2, 5):
73         msg = "string of 2 to 4 characters expected, got {:d} ({:s})"
74         raise ValueError(msg.format(len(note), note))
75     notename, modifier, octave = [None]*3
76
77     if len(note) == 4:
78         notename, modifier, octave_sign, octave = note
79         octave = octave_sign + octave
80     elif len(note) == 3:
81         notename, modifier, octave = note
82         if modifier == '-':
83             octave = modifier + octave
84             modifier = None
85     else:
86         notename, octave = note
87
88     notename = notename.upper()
89     octave = int(octave)
90
91     if notename not in _valid_notenames:
92         raise ValueError("%s is not a valid note name" % notename)
93     if modifier not in _valid_modifiers:
94         raise ValueError("%s is not a valid modifier" % modifier)
95     if octave not in _valid_octaves:
96         raise ValueError("%s is not a valid octave" % octave)
97
98     midi = (octave + 1) * 12 + _valid_notenames[notename] \
99                              + _valid_modifiers[modifier]
100     if midi > 127:
101         raise ValueError("%s is outside of the range C-2 to G8" % note)
102     return midi
103
104
105 def midi2note(midi):
106     """Convert midi note number to note name.
107
108     Parameters
109     ----------
110     midi : int [0, 128]
111         input midi note number
112
113     Returns
114     -------
115     str
116         note name
117
118     Examples
119     --------
120     >>> aubio.midi2note(70)
121     'A#4'
122     >>> aubio.midi2note(59)
123     'B3'
124
125     Raises
126     ------
127     TypeError
128         If `midi` was not an integer.
129     ValueError
130         If `midi` is out of the range `[0, 128]`.
131
132     See Also
133     --------
134     note2midi, miditofreq, freqtomidi
135     """
136     if not isinstance(midi, int_instances):
137         raise TypeError("an integer is required, got %s" % midi)
138     if midi not in range(0, 128):
139         msg = "an integer between 0 and 127 is excepted, got {:d}"
140         raise ValueError(msg.format(midi))
141     _valid_notenames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#',
142                         'A', 'A#', 'B']
143     return _valid_notenames[midi % 12] + str(int(midi / 12) - 1)
144
145
146 def freq2note(freq):
147     """Convert frequency in Hz to nearest note name.
148
149     Parameters
150     ----------
151     freq : float [0, 23000[
152         input frequency, in Hz
153
154     Returns
155     -------
156     str
157         name of the nearest note
158
159     Example
160     -------
161     >>> aubio.freq2note(440)
162     'A4'
163     >>> aubio.freq2note(220.1)
164     'A3'
165     """
166     nearest_note = int(freqtomidi(freq) + .5)
167     return midi2note(nearest_note)
168
169
170 def note2freq(note):
171     """Convert note name to corresponding frequency, in Hz.
172
173     Parameters
174     ----------
175     note : str
176         input note name
177
178     Returns
179     -------
180     freq : float [0, 23000[
181         frequency, in Hz
182
183     Example
184     -------
185     >>> aubio.note2freq('A4')
186     440
187     >>> aubio.note2freq('A3')
188     220.1
189     """
190     midi = note2midi(note)
191     return miditofreq(midi)