lib/aubio/midiconv.py: add note2midi
authorPaul Brossier <piem@piem.org>
Sun, 10 Mar 2013 15:34:45 +0000 (10:34 -0500)
committerPaul Brossier <piem@piem.org>
Sun, 10 Mar 2013 15:34:45 +0000 (10:34 -0500)
python/lib/aubio/__init__.py
python/lib/aubio/midiconv.py [new file with mode: 0644]
python/tests/test_note2midi.py [new file with mode: 0755]

index 136848b..5fdc0ae 100644 (file)
@@ -1,5 +1,6 @@
 import numpy
 from _aubio import *
+from midiconv import *
 
 class fvec(numpy.ndarray):
 
diff --git a/python/lib/aubio/midiconv.py b/python/lib/aubio/midiconv.py
new file mode 100644 (file)
index 0000000..8ba3d58
--- /dev/null
@@ -0,0 +1,37 @@
+# -*- encoding: utf8 -*-
+
+def note2midi(note):
+    " convert a note name to a midi note value "
+    # from C-2 to G8, though we do accept everything in the upper octave
+    _valid_notenames = {'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11}
+    _valid_modifiers = {None: 0, u'♮': 0, '#': +1, u'♯': +1, u'\udd2a': +2, 'b': -1, u'♭': -1, u'\ufffd': -2}
+    _valid_octaves = range(-1, 10) 
+    if not (1 < len(note) < 5):
+        raise ValueError, "string of 2 to 4 characters expected, got %d (%s)" % (len(note), note)
+    notename, modifier, octave = [None]*3
+
+    if len(note) == 4:
+        notename, modifier, octave_sign, octave = note
+        octave = octave_sign + octave
+    elif len(note) == 3:
+        notename, modifier, octave = note
+        if modifier == '-':
+            octave = modifier + octave
+            modifier = None
+    else:
+        notename, octave = note
+
+    notename = notename.upper()
+    octave = int(octave)
+
+    if notename not in _valid_notenames:
+        raise ValueError, "%s is not a valid note name" % notename
+    if modifier not in _valid_modifiers:
+        raise ValueError, "only # and b are acceptable modifiers, not %s" % modifier
+    if octave not in _valid_octaves:
+        raise ValueError, "%s is not a valid octave" % octave
+
+    midi = 12 + octave * 12 + _valid_notenames[notename] + _valid_modifiers[modifier]
+    if midi > 127:
+        raise ValueError, "%s is outside of the range C-2 to G8" % note
+    return midi
diff --git a/python/tests/test_note2midi.py b/python/tests/test_note2midi.py
new file mode 100755 (executable)
index 0000000..9004ff8
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- encoding: utf8 -*-
+
+from aubio import note2midi
+import unittest
+
+list_of_known_notes = (
+        ( 'C-1', 0 ),
+        ( 'C#-1', 1 ),
+        ( 'd2', 38 ),
+        ( 'C3', 48 ),
+        ( 'B3', 59 ),
+        ( 'B#3', 60 ),
+        ( 'A4', 69 ),
+        ( 'A#4', 70 ),
+        ( 'G8', 115 ),
+        ( u'G♯8', 116 ),
+        ( u'G♭9', 126 ),
+        ( 'G9', 127 ),
+        ( u'G\udd2a2', 45 ),
+        ( u'B\ufffd2', 45 ),
+        ( u'A♮2', 45 ),
+        )
+
+class TestNote2MidiGoodValues(unittest.TestCase):
+
+    def test_note2midi_known_values(self):
+        " known values are correctly converted "
+        for note, midi in list_of_known_notes:
+            self.assertEqual ( note2midi(note), midi )
+
+class TestNote2MidiWrongValues(unittest.TestCase):
+
+    def test_note2midi_missing_octave(self):
+        " fails when passed only one character"
+        self.assertRaises(ValueError, note2midi, 'C')
+
+    def test_note2midi_wrong_modifier(self):
+        " fails when passed an invalid note name"
+        self.assertRaises(ValueError, note2midi, 'C.1')
+
+    def test_note2midi_wronge_midifier_again(self):
+        " fails when passed a wrong modifier"
+        self.assertRaises(ValueError, note2midi, 'CB-3')
+
+    def test_note2midi_wrong_octave(self):
+        " fails when passed a wrong octave"
+        self.assertRaises(ValueError, note2midi, 'CBc')
+
+    def test_note2midi_out_of_range(self):
+        " fails when passed a non-existing note"
+        self.assertRaises(ValueError, note2midi, 'A9')
+
+if __name__ == '__main__':
+    unittest.main()